hedge and auto hedger in separate folders
This commit is contained in:
256
clp_hedger/CLP_HEDGING_IMPLEMENTATION_PLAN.md
Normal file
256
clp_hedger/CLP_HEDGING_IMPLEMENTATION_PLAN.md
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
# CLP Hedging Zone Strategy Implementation Plan
|
||||||
|
*Generated: 2025-12-16*
|
||||||
|
*Session Focus: Risk analysis and zone-based hedge optimization*
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
This plan implements a zone-based hedging strategy for narrow CLP ranges (+/- 0.3%) with $100 position size and $10 minimum trade constraints. The strategy maintains the existing 7.5-minute hedge delay for mean reversion while adding preparation zones for potential CLP closing.
|
||||||
|
|
||||||
|
## Current System Analysis
|
||||||
|
|
||||||
|
### Scripts & Configuration
|
||||||
|
- **uniswap_manager.py**: CLP lifecycle management (451-second interval)
|
||||||
|
- **clp_scalper_hedger.py**: Active hedging (4-second interval)
|
||||||
|
- **Strategy**: Mean reversion with intentional 7.5-minute unhedged period
|
||||||
|
- **Position Size**: $100 CLP position
|
||||||
|
- **Range Width**: +/- 0.3% (extremely narrow, requiring precise zone management)
|
||||||
|
- **Minimum Trade**: $10 (10% of position size - significant constraint)
|
||||||
|
|
||||||
|
### Risk Assessment
|
||||||
|
- **Strategic Risk**: Intentional unhedged exposure during 7.5-minute delay (accepted)
|
||||||
|
- **Technical Risks**: JSON file corruption, price source divergence, oscillation
|
||||||
|
- **Financial Impact**: $10 minimum trades create risk of overshooting hedge targets
|
||||||
|
|
||||||
|
## Proposed Zone Strategy
|
||||||
|
|
||||||
|
### Zone Structure
|
||||||
|
```
|
||||||
|
Range Position (% from bottom):
|
||||||
|
├── TOP PREPARE ZONE (90-100%): Gradual reduction 100% → 0%
|
||||||
|
├── TOP HYSTERESIS ZONE (85-90%): Maintain current hedge
|
||||||
|
├── MIDDLE NORMAL ZONE (10-85%): Normal hedge (100%)
|
||||||
|
├── BOTTOM HYSTERESIS ZONE (5-10%): Maintain current hedge
|
||||||
|
└── BOTTOM MAX ZONE (0-5%): Enhanced over-hedge (112.5%)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zone Rationale
|
||||||
|
- **90% Preparation Start**: Adequate preparation time while minimizing whipsaw risk
|
||||||
|
- **85-90% Hysteresis Buffer**: Prevents oscillation near top boundary
|
||||||
|
- **5-10% Bottom Buffer**: Reduces frequency of over-hedge adjustments
|
||||||
|
- **0-5% Enhanced Over-hedge**: Maximum protection when CLP is fully WETH
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Configuration Updates
|
||||||
|
```python
|
||||||
|
# Zone Boundaries for Narrow Range
|
||||||
|
TOP_PREPARE_START = 0.90 # Start unhedging at 90%
|
||||||
|
TOP_HYSTERESIS_START = 0.85 # Hysteresis buffer zone
|
||||||
|
BOTTOM_HYSTERESIS_END = 0.10 # Bottom hysteresis buffer
|
||||||
|
BOTTOM_MAX_ZONE_END = 0.05 # Enhanced over-hedge until 5%
|
||||||
|
|
||||||
|
# $10 Minimum Trade Controls
|
||||||
|
MIN_PRICE_MOVEMENT_PCT = 0.10 # 10% range movement before adjustment
|
||||||
|
MIN_TIME_BETWEEN_ADJUSTMENTS = 60 # 1 minute minimum between trades
|
||||||
|
MIN_TRADE_SIZE_USD = 10.0 # $10 minimum trade size
|
||||||
|
|
||||||
|
# Hedge Multipliers
|
||||||
|
TOP_PREPARE_MULTIPLIER = 0.0 # 0% hedge in prepare zone
|
||||||
|
NORMAL_HEDGE_MULTIPLIER = 1.0 # 100% normal hedge
|
||||||
|
BOTTOM_MAX_MULTIPLIER = 1.125 # 112.5% over-hedge
|
||||||
|
|
||||||
|
# Risk Management
|
||||||
|
MAX_DAILY_TRADES = 3 # Maximum trades per day
|
||||||
|
MAX_DAILY_EXPOSURE_USD = 30.0 # Maximum daily trade exposure
|
||||||
|
OVERSHOOT_TOLERANCE_PCT = 0.05 # 5% tolerance on $10 trades
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core Methods to Implement
|
||||||
|
|
||||||
|
#### 1. Zone Calculation Method
|
||||||
|
```python
|
||||||
|
def calculate_zone_multiplier(self, price_pct):
|
||||||
|
"""
|
||||||
|
Calculate hedge multiplier based on price position within CLP range.
|
||||||
|
Implements gradual transitions and hysteresis.
|
||||||
|
"""
|
||||||
|
if price_pct >= 0.90: # 90-100%: Gradual reduction
|
||||||
|
return (1.0 - (price_pct - 0.90) / 0.10)
|
||||||
|
elif price_pct <= 0.05: # 0-5%: Enhanced over-hedge
|
||||||
|
return 1.0 + (0.05 - price_pct) * 0.25 # 112.5% at 0%, 100% at 5%
|
||||||
|
else: # 5-90%: Normal hedge
|
||||||
|
return 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Hysteresis Control
|
||||||
|
```python
|
||||||
|
def should_adjust_hedge(self, current_price_pct, last_adjustment_pct, last_adjustment_time):
|
||||||
|
"""
|
||||||
|
Prevent frequent small adjustments due to $10 minimum trade constraint.
|
||||||
|
"""
|
||||||
|
# Minimum price movement (equivalent to $10 trade)
|
||||||
|
if abs(current_price_pct - last_adjustment_pct) < self.MIN_PRICE_MOVEMENT_PCT:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Minimum time between adjustments
|
||||||
|
if time.time() - last_adjustment_time < self.MIN_TIME_BETWEEN_ADJUSTMENTS:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Trade Size Optimization
|
||||||
|
```python
|
||||||
|
def calculate_optimal_trade_size(self, diff, position_value):
|
||||||
|
"""
|
||||||
|
Round trades to $10 increments and enforce minimum trade size.
|
||||||
|
"""
|
||||||
|
trade_value_usd = abs(diff * position_value)
|
||||||
|
|
||||||
|
# Skip if below minimum
|
||||||
|
if trade_value_usd < self.MIN_TRADE_SIZE_USD:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Round to nearest $10 increment for efficiency
|
||||||
|
rounded_trade_value = round(trade_value_usd / 10.0) * 10.0
|
||||||
|
|
||||||
|
# Convert back to position units
|
||||||
|
return rounded_trade_value / position_value
|
||||||
|
```
|
||||||
|
|
||||||
|
### Files to Modify
|
||||||
|
|
||||||
|
#### Primary: clp_scalper_hedger.py
|
||||||
|
**Lines to Update:**
|
||||||
|
- **44-53**: Zone configuration constants
|
||||||
|
- **252-284**: Core `calculate_rebalance()` method
|
||||||
|
- **255-265**: Integrate with existing over-hedge logic
|
||||||
|
|
||||||
|
**Methods to Add:**
|
||||||
|
- `calculate_zone_multiplier()` - Zone-based hedge calculation
|
||||||
|
- `should_adjust_hedge()` - $10 minimum trade logic
|
||||||
|
- `calculate_optimal_trade_size()` - Rounding to $10 increments
|
||||||
|
- `update_zone_state()` - Hysteresis zone management
|
||||||
|
|
||||||
|
#### Secondary: hedge_status.json (runtime)
|
||||||
|
- Add zone transition tracking fields
|
||||||
|
- Add last adjustment timestamps
|
||||||
|
- Add daily trade count tracking
|
||||||
|
|
||||||
|
## Risk Management Strategy
|
||||||
|
|
||||||
|
### Financial Risk Controls
|
||||||
|
- **Position Size Limit**: $100 maximum CLP position
|
||||||
|
- **Daily Trade Limit**: Maximum 3 trades ($30 exposure)
|
||||||
|
- **Over-hedge Cap**: 125% absolute maximum (vs 112.5% target)
|
||||||
|
- **Transaction Cost Budget**: $5 maximum daily trading costs
|
||||||
|
|
||||||
|
### Technical Risk Mitigation
|
||||||
|
- **JSON File Locking**: Prevent concurrent access corruption
|
||||||
|
- **Hysteresis Implementation**: Prevent oscillation trading
|
||||||
|
- **Position Validation**: Verify hedge calculations before execution
|
||||||
|
- **Emergency Stops**: Circuit breakers on extreme market moves
|
||||||
|
|
||||||
|
### Operational Risk Controls
|
||||||
|
- **Time-based Limits**: Minimum intervals between adjustments
|
||||||
|
- **Movement Thresholds**: Minimum price changes before trading
|
||||||
|
- **Overshoot Protection**: Tolerance bands around target hedge ratios
|
||||||
|
- **Daily Cumulative Limits**: Maximum position change per day
|
||||||
|
|
||||||
|
## Implementation Sequence
|
||||||
|
|
||||||
|
### Phase 1: Core Zone Logic (Priority 1)
|
||||||
|
1. **Implement zone calculation method**
|
||||||
|
2. **Add hysteresis controls**
|
||||||
|
3. **Integrate with existing over-hedge logic**
|
||||||
|
4. **Update configuration constants**
|
||||||
|
|
||||||
|
### Phase 2: Trade Optimization (Priority 2)
|
||||||
|
1. **Implement $10 minimum trade logic**
|
||||||
|
2. **Add rounding to nearest $10 increment**
|
||||||
|
3. **Add minimum time between trades**
|
||||||
|
4. **Integrate with existing `manage_orders()` method**
|
||||||
|
|
||||||
|
### Phase 3: Risk Controls (Priority 3)
|
||||||
|
1. **Add daily trade count limits**
|
||||||
|
2. **Implement overshoot protection**
|
||||||
|
3. **Add position validation checks**
|
||||||
|
4. **Create monitoring/logging for zone transitions**
|
||||||
|
|
||||||
|
### Phase 4: Live Deployment & Optimization (Priority 4)
|
||||||
|
1. **Deploy with $100 position**
|
||||||
|
2. **Monitor zone transition frequency**
|
||||||
|
3. **Adjust zone boundaries based on observations**
|
||||||
|
4. **Optimize trade timing and size**
|
||||||
|
|
||||||
|
## Key Questions for Finalization
|
||||||
|
|
||||||
|
### Configuration Preferences
|
||||||
|
1. **Zone Boundaries**: Are 90%/85%/10%/5% boundaries optimal, or should they be adjusted?
|
||||||
|
2. **Trade Frequency**: Is 3 trades per day acceptable, or prefer fewer/larger trades?
|
||||||
|
3. **Over-hedge Level**: Is 112.5% multiplier appropriate, or more/less aggressive?
|
||||||
|
4. **Time Buffers**: Is 1-minute minimum between trades sufficient?
|
||||||
|
|
||||||
|
### Risk Tolerance
|
||||||
|
5. **Maximum Daily Exposure**: Is $30 daily trade exposure acceptable?
|
||||||
|
6. **Overshoot Tolerance**: Is 5% tolerance on $10 trades appropriate?
|
||||||
|
7. **Position Size**: Should we start with smaller position during testing?
|
||||||
|
|
||||||
|
### Strategy Behavior
|
||||||
|
8. **Zone Entry Logic**: Should we implement different thresholds for entering vs exiting zones?
|
||||||
|
9. **Trade Timing**: Should trades occur immediately on zone entry or wait for confirmation?
|
||||||
|
10. **Market Conditions**: Should zones adapt based on volatility or time of day?
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
### Primary Metrics
|
||||||
|
- **Oscillation Frequency**: < 2 zone changes per hour
|
||||||
|
- **Trade Efficiency**: > 80% of trades executed at optimal size ($10+)
|
||||||
|
- **Hedge Accuracy**: Average hedge ratio within 5% of target
|
||||||
|
- **Transaction Costs**: < 3% of position value per day
|
||||||
|
|
||||||
|
### Secondary Metrics
|
||||||
|
- **Zone Transition Smoothness**: Gradual transitions without sudden jumps
|
||||||
|
- **Risk Control Compliance**: No violations of daily limits
|
||||||
|
- **System Stability**: No JSON corruption or sync issues
|
||||||
|
- **Strategy Performance**: Improvement over current baseline
|
||||||
|
|
||||||
|
## Monitoring & Alerts
|
||||||
|
|
||||||
|
### Real-time Monitoring
|
||||||
|
- Zone transition logging
|
||||||
|
- Hedge ratio tracking
|
||||||
|
- Trade execution verification
|
||||||
|
- Price source divergence detection
|
||||||
|
|
||||||
|
### Alert Conditions
|
||||||
|
- Excessive oscillation (> 5 zone changes/hour)
|
||||||
|
- Approaching daily trade limits
|
||||||
|
- Large hedge ratio deviations (> 10% from target)
|
||||||
|
- JSON file access conflicts
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
### Immediate Rollback Triggers
|
||||||
|
- Financial losses > 15% of position value
|
||||||
|
- System instability or crashes
|
||||||
|
- Excessive trading frequency (> 5 trades/hour)
|
||||||
|
- Hedge calculation errors
|
||||||
|
|
||||||
|
### Rollback Procedure
|
||||||
|
1. Stop both scripts
|
||||||
|
2. Restore original configuration
|
||||||
|
3. Verify position status
|
||||||
|
4. Resume with baseline strategy
|
||||||
|
5. Analyze failure causes
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Confirm Final Configuration**: Zone boundaries, trade limits, risk tolerances
|
||||||
|
2. **Implement Core Logic**: Zone calculation and hysteresis methods
|
||||||
|
3. **Integrate with Existing Code**: Update calculate_rebalance() method
|
||||||
|
4. **Test with Small Position**: Validate with $100 position
|
||||||
|
5. **Monitor and Optimize**: Adjust based on observed behavior
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This plan serves as the complete technical specification for implementing zone-based hedging strategy with $10 minimum trade constraints. The solution maintains the existing mean reversion strategy while adding sophisticated preparation zones for CLP closing scenarios.*
|
||||||
@ -4,7 +4,9 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
import math
|
import math
|
||||||
import json
|
import json
|
||||||
|
import threading
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
from web3 import Web3
|
||||||
|
|
||||||
# --- FIX: Add project root to sys.path to import local modules ---
|
# --- FIX: Add project root to sys.path to import local modules ---
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
@ -30,29 +32,75 @@ setup_logging("normal", "SCALPER_HEDGER")
|
|||||||
|
|
||||||
# --- CONFIGURATION ---
|
# --- CONFIGURATION ---
|
||||||
COIN_SYMBOL = "ETH"
|
COIN_SYMBOL = "ETH"
|
||||||
CHECK_INTERVAL = 5 # Optimized for cost/noise reduction (was 1)
|
CHECK_INTERVAL = 4 # Optimized for speed (was 5)
|
||||||
LEVERAGE = 5 # 3x Leverage
|
LEVERAGE = 5 # 3x Leverage
|
||||||
STATUS_FILE = "hedge_status.json"
|
STATUS_FILE = "hedge_status.json"
|
||||||
|
RPC_URL = os.environ.get("MAINNET_RPC_URL") # Required for Uniswap Monitor
|
||||||
|
|
||||||
|
# Uniswap V3 Pool (Arbitrum WETH/USDC 0.05%)
|
||||||
|
UNISWAP_POOL_ADDRESS = "0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443"
|
||||||
|
UNISWAP_POOL_ABI = json.loads('[{"inputs":[],"name":"slot0","outputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"internalType":"int24","name":"tick","type":"int24"},{"internalType":"uint16","name":"observationIndex","type":"uint16"},{"internalType":"uint16","name":"observationCardinality","type":"uint16"},{"internalType":"uint16","name":"observationCardinalityNext","type":"uint16"},{"internalType":"uint8","name":"feeProtocol","type":"uint8"},{"internalType":"bool","name":"unlocked","type":"bool"}],"stateMutability":"view","type":"function"}]')
|
||||||
|
|
||||||
# --- STRATEGY ZONES (Percent of Range Width) ---
|
# --- STRATEGY ZONES (Percent of Range Width) ---
|
||||||
# Bottom Hedge Zone: 0% to 15% -> Active Hedging
|
# Bottom Hedge Zone: Covers entire range (0.0 to 1.5) -> Always Active
|
||||||
ZONE_BOTTOM_HEDGE_LIMIT = 0.5
|
ZONE_BOTTOM_HEDGE_LIMIT = 1
|
||||||
|
|
||||||
# Close Zone: 15% to 20% -> Close All Hedges (Flatten)
|
# Close Zone: Disabled (Set > 1.0)
|
||||||
ZONE_CLOSE_START = 0.52
|
ZONE_CLOSE_START = 10.0
|
||||||
ZONE_CLOSE_END = 0.54
|
ZONE_CLOSE_END = 11.0
|
||||||
|
|
||||||
# Middle Zone: 20% to 85% -> Idle (No new orders, keep existing)
|
# Top Hedge Zone: Disabled/Redundant
|
||||||
# Implied by gaps between other zones.
|
ZONE_TOP_HEDGE_START = 10.0
|
||||||
|
|
||||||
# Top Hedge Zone: 85% to 100% -> Active Hedging
|
|
||||||
ZONE_TOP_HEDGE_START = 0.8
|
|
||||||
|
|
||||||
# --- ORDER SETTINGS ---
|
# --- ORDER SETTINGS ---
|
||||||
PRICE_BUFFER_PCT = 0.002 # 0.2% price move triggers order update (Relaxed for cost)
|
PRICE_BUFFER_PCT = 0.0001 # 0.2% price move triggers order update (Relaxed for cost)
|
||||||
MIN_THRESHOLD_ETH = 0.02 # Minimum trade size in ETH (~$60, Reduced frequency)
|
MIN_THRESHOLD_ETH = 0.0025 # Minimum trade size in ETH (~$60, Reduced frequency)
|
||||||
MIN_ORDER_VALUE_USD = 10.0 # Minimum order value for API safety
|
MIN_ORDER_VALUE_USD = 10.0 # Minimum order value for API safety
|
||||||
|
|
||||||
|
class UniswapPriceMonitor:
|
||||||
|
def __init__(self, rpc_url, pool_address):
|
||||||
|
self.w3 = Web3(Web3.HTTPProvider(rpc_url))
|
||||||
|
self.pool_contract = self.w3.eth.contract(address=pool_address, abi=UNISWAP_POOL_ABI)
|
||||||
|
self.latest_price = None
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self._loop, daemon=True)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def _loop(self):
|
||||||
|
logging.info("Uniswap Monitor Started.")
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
slot0 = self.pool_contract.functions.slot0().call()
|
||||||
|
sqrt_price_x96 = slot0[0]
|
||||||
|
# Price = (sqrtPriceX96 / 2^96)^2 * 10^(18-6) (WETH/USDC)
|
||||||
|
# But typically WETH is token1? Let's verify standard Arbitrum Pool.
|
||||||
|
# 0xC31E... Token0=WETH, Token1=USDC.
|
||||||
|
# Price = (sqrt / 2^96)^2 * (10^12) -> This gives USDC per ETH? No, Token1/Token0.
|
||||||
|
# Wait, usually Token0 is WETH (18) and Token1 is USDC (6).
|
||||||
|
# P = (1.0001^tick) * 10^(decimals0 - decimals1)? No.
|
||||||
|
# Standard conversion: Price = (sqrtRatioX96 / Q96) ** 2
|
||||||
|
# Adjusted for decimals: Price = Price_raw / (10**(Dec0 - Dec1)) ? No.
|
||||||
|
# Price (Quote/Base) = (sqrt / Q96)^2 * 10^(BaseDec - QuoteDec)
|
||||||
|
|
||||||
|
# Let's rely on standard logic: Price = (sqrt / 2^96)^2 * 10^(12) for ETH(18)/USDC(6)
|
||||||
|
raw_price = (sqrt_price_x96 / (2**96)) ** 2
|
||||||
|
price = raw_price * (10**(18-6)) # 10^12
|
||||||
|
# If Token0 is WETH, price is USDC per WETH.
|
||||||
|
# Note: If the pool is inverted (USDC/WETH), we invert.
|
||||||
|
# On Arb, WETH is usually Token0?
|
||||||
|
# 0x82aF... < 0xaf88... (WETH < USDC). So WETH is Token0.
|
||||||
|
# Price is Token1 per Token0.
|
||||||
|
|
||||||
|
self.latest_price = 1 / price if price < 1 else price # Sanity check, ETH should be > 2000
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# logging.error(f"Uniswap Monitor Error: {e}")
|
||||||
|
pass
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
def get_price(self):
|
||||||
|
return self.latest_price
|
||||||
|
|
||||||
def get_active_automatic_position():
|
def get_active_automatic_position():
|
||||||
if not os.path.exists(STATUS_FILE):
|
if not os.path.exists(STATUS_FILE):
|
||||||
return None
|
return None
|
||||||
@ -203,9 +251,25 @@ class HyperliquidStrategy:
|
|||||||
|
|
||||||
def calculate_rebalance(self, current_price, current_short_position_size):
|
def calculate_rebalance(self, current_price, current_short_position_size):
|
||||||
pool_delta = self.get_pool_delta(current_price)
|
pool_delta = self.get_pool_delta(current_price)
|
||||||
|
|
||||||
|
# --- Over-Hedge Logic ---
|
||||||
|
overhedge_pct = 0.0
|
||||||
|
range_width = self.high_range - self.low_range
|
||||||
|
if range_width > 0:
|
||||||
|
price_pct = (current_price - self.low_range) / range_width
|
||||||
|
|
||||||
|
# If below 0.8 (80%) of range
|
||||||
|
if price_pct < 0.8:
|
||||||
|
# Formula: 0.75% boost for every 0.1 drop below 0.8
|
||||||
|
# Example: At 0.6 (60%), diff is 0.2. (0.2/0.1)*0.0075 = 0.015 (1.5%)
|
||||||
|
overhedge_pct = ((0.8 - max(0.0, price_pct)) / 0.1) * 0.0075
|
||||||
|
|
||||||
raw_target_short = pool_delta + self.static_long
|
raw_target_short = pool_delta + self.static_long
|
||||||
|
|
||||||
target_short_size = raw_target_short
|
# Apply Boost
|
||||||
|
adjusted_target_short = raw_target_short * (1.0 + overhedge_pct)
|
||||||
|
|
||||||
|
target_short_size = adjusted_target_short
|
||||||
diff = target_short_size - abs(current_short_position_size)
|
diff = target_short_size - abs(current_short_position_size)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -215,7 +279,8 @@ class HyperliquidStrategy:
|
|||||||
"current_short": abs(current_short_position_size),
|
"current_short": abs(current_short_position_size),
|
||||||
"diff": diff,
|
"diff": diff,
|
||||||
"action": "SELL" if diff > 0 else "BUY",
|
"action": "SELL" if diff > 0 else "BUY",
|
||||||
"mode": "NORMAL"
|
"mode": "OVERHEDGE" if overhedge_pct > 0 else "NORMAL",
|
||||||
|
"overhedge_pct": overhedge_pct
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScalperHedger:
|
class ScalperHedger:
|
||||||
@ -242,6 +307,9 @@ class ScalperHedger:
|
|||||||
self.active_position_id = None
|
self.active_position_id = None
|
||||||
self.active_order = None
|
self.active_order = None
|
||||||
|
|
||||||
|
# --- Start Uniswap Monitor ---
|
||||||
|
self.uni_monitor = UniswapPriceMonitor(RPC_URL, UNISWAP_POOL_ADDRESS)
|
||||||
|
|
||||||
logging.info(f"Scalper Hedger initialized. Agent: {self.account.address}")
|
logging.info(f"Scalper Hedger initialized. Agent: {self.account.address}")
|
||||||
|
|
||||||
def _init_strategy(self, position_data):
|
def _init_strategy(self, position_data):
|
||||||
@ -553,10 +621,23 @@ class ScalperHedger:
|
|||||||
current_pos_size = pos_data['size']
|
current_pos_size = pos_data['size']
|
||||||
current_pnl = pos_data['pnl']
|
current_pnl = pos_data['pnl']
|
||||||
|
|
||||||
|
# --- SPREAD MONITOR LOG ---
|
||||||
|
uni_price = self.uni_monitor.get_price()
|
||||||
|
spread_text = ""
|
||||||
|
if uni_price:
|
||||||
|
diff = price - uni_price
|
||||||
|
pct = (diff / uni_price) * 100
|
||||||
|
spread_text = f" | Sprd: {pct:+.2f}% (H:{price:.0f}/U:{uni_price:.0f})"
|
||||||
|
|
||||||
# 3. Calculate Logic
|
# 3. Calculate Logic
|
||||||
calc = self.strategy.calculate_rebalance(price, current_pos_size)
|
calc = self.strategy.calculate_rebalance(price, current_pos_size)
|
||||||
diff_abs = abs(calc['diff'])
|
diff_abs = abs(calc['diff'])
|
||||||
|
|
||||||
|
# --- LOGGING OVERHEDGE ---
|
||||||
|
oh_text = ""
|
||||||
|
if calc.get('overhedge_pct', 0) > 0:
|
||||||
|
oh_text = f" | 🔥 OH: +{calc['overhedge_pct']*100:.2f}%"
|
||||||
|
|
||||||
# 4. Dynamic Threshold Calculation
|
# 4. Dynamic Threshold Calculation
|
||||||
sqrt_Pa = math.sqrt(self.strategy.low_range)
|
sqrt_Pa = math.sqrt(self.strategy.low_range)
|
||||||
sqrt_Pb = math.sqrt(self.strategy.high_range)
|
sqrt_Pb = math.sqrt(self.strategy.high_range)
|
||||||
@ -571,23 +652,32 @@ class ScalperHedger:
|
|||||||
range_width = clp_high_range - clp_low_range
|
range_width = clp_high_range - clp_low_range
|
||||||
|
|
||||||
# Calculate Prices for Zones
|
# Calculate Prices for Zones
|
||||||
zone_bottom_limit_price = clp_low_range + (range_width * ZONE_BOTTOM_HEDGE_LIMIT)
|
# If config > 9, set to None (Disabled Zone)
|
||||||
zone_close_bottom_price = clp_low_range + (range_width * ZONE_CLOSE_START)
|
zone_bottom_limit_price = (clp_low_range + (range_width * ZONE_BOTTOM_HEDGE_LIMIT)) if ZONE_BOTTOM_HEDGE_LIMIT <= 9 else None
|
||||||
zone_close_top_price = clp_low_range + (range_width * ZONE_CLOSE_END)
|
zone_close_bottom_price = (clp_low_range + (range_width * ZONE_CLOSE_START)) if ZONE_CLOSE_START <= 9 else None
|
||||||
zone_top_start_price = clp_low_range + (range_width * ZONE_TOP_HEDGE_START)
|
zone_close_top_price = (clp_low_range + (range_width * ZONE_CLOSE_END)) if ZONE_CLOSE_END <= 9 else None
|
||||||
|
zone_top_start_price = (clp_low_range + (range_width * ZONE_TOP_HEDGE_START)) if ZONE_TOP_HEDGE_START <= 9 else None
|
||||||
|
|
||||||
# Update JSON with zone prices if they are None (initially set by uniswap_manager.py)
|
# Update JSON with zone prices if they are None (initially set by uniswap_manager.py)
|
||||||
if active_pos.get('zone_bottom_limit_price') is None:
|
if active_pos.get('zone_bottom_limit_price') is None:
|
||||||
update_position_zones_in_json(active_pos['token_id'], {
|
update_position_zones_in_json(active_pos['token_id'], {
|
||||||
'zone_top_start_price': round(zone_top_start_price, 2),
|
'zone_top_start_price': round(zone_top_start_price, 2) if zone_top_start_price else None,
|
||||||
'zone_close_top_price': round(zone_close_top_price, 2),
|
'zone_close_top_price': round(zone_close_top_price, 2) if zone_close_top_price else None,
|
||||||
'zone_close_bottom_price': round(zone_close_bottom_price, 2),
|
'zone_close_bottom_price': round(zone_close_bottom_price, 2) if zone_close_bottom_price else None,
|
||||||
'zone_bottom_limit_price': round(zone_bottom_limit_price, 2)
|
'zone_bottom_limit_price': round(zone_bottom_limit_price, 2) if zone_bottom_limit_price else None
|
||||||
})
|
})
|
||||||
|
|
||||||
# Check Zones
|
# Check Zones (Handle None)
|
||||||
in_close_zone = (price >= zone_close_bottom_price and price <= zone_close_top_price)
|
# If zone price is None, condition fails safe (False)
|
||||||
in_hedge_zone = (price <= zone_bottom_limit_price) or (price >= zone_top_start_price)
|
in_close_zone = False
|
||||||
|
if zone_close_bottom_price is not None and zone_close_top_price is not None:
|
||||||
|
in_close_zone = (price >= zone_close_bottom_price and price <= zone_close_top_price)
|
||||||
|
|
||||||
|
in_hedge_zone = False
|
||||||
|
if zone_bottom_limit_price is not None and price <= zone_bottom_limit_price:
|
||||||
|
in_hedge_zone = True
|
||||||
|
if zone_top_start_price is not None and price >= zone_top_start_price:
|
||||||
|
in_hedge_zone = True
|
||||||
|
|
||||||
# --- Execute Logic ---
|
# --- Execute Logic ---
|
||||||
if in_close_zone:
|
if in_close_zone:
|
||||||
@ -601,18 +691,12 @@ class ScalperHedger:
|
|||||||
if diff_abs > rebalance_threshold:
|
if diff_abs > rebalance_threshold:
|
||||||
trade_size = round_to_sz_decimals(diff_abs, self.sz_decimals)
|
trade_size = round_to_sz_decimals(diff_abs, self.sz_decimals)
|
||||||
|
|
||||||
# --- SOFT START LOGIC (Bottom Zone Only) ---
|
|
||||||
# If in Bottom Zone, opening a NEW Short (SELL), and current position is 0 -> Cut size by 50%
|
|
||||||
if (price <= zone_bottom_limit_price) and (current_pos_size == 0) and (calc['action'] == "SELL"):
|
|
||||||
logging.info(f"🔰 SOFT START: Reducing initial hedge size by 50% in Bottom Zone.")
|
|
||||||
trade_size = round_to_sz_decimals(trade_size * 0.5, self.sz_decimals)
|
|
||||||
|
|
||||||
min_trade_size = MIN_ORDER_VALUE_USD / price
|
min_trade_size = MIN_ORDER_VALUE_USD / price
|
||||||
|
|
||||||
if trade_size < min_trade_size:
|
if trade_size < min_trade_size:
|
||||||
logging.info(f"Idle. Trade size {trade_size} < Min Order Size {min_trade_size:.4f} (${MIN_ORDER_VALUE_USD:.2f}). PNL: ${current_pnl:.2f}")
|
logging.info(f"Idle. Trade size {trade_size} < Min Order Size {min_trade_size:.4f} (${MIN_ORDER_VALUE_USD:.2f}). PNL: ${current_pnl:.2f}{spread_text}{oh_text}")
|
||||||
elif trade_size > 0:
|
elif trade_size > 0:
|
||||||
logging.info(f"⚡ THRESHOLD TRIGGERED ({diff_abs:.4f} >= {rebalance_threshold:.4f}). In Hedge Zone. PNL: ${current_pnl:.2f}")
|
logging.info(f"⚡ THRESHOLD TRIGGERED ({diff_abs:.4f} >= {rebalance_threshold:.4f}). In Hedge Zone. PNL: ${current_pnl:.2f}{spread_text}{oh_text}")
|
||||||
# Execute Passively for Alo
|
# Execute Passively for Alo
|
||||||
# Force 1 tick offset (0.1) away from BBO to ensure rounding doesn't cause cross
|
# Force 1 tick offset (0.1) away from BBO to ensure rounding doesn't cause cross
|
||||||
# Sell at Ask + 0.1, Buy at Bid - 0.1
|
# Sell at Ask + 0.1, Buy at Bid - 0.1
|
||||||
@ -627,14 +711,14 @@ class ScalperHedger:
|
|||||||
|
|
||||||
self.place_limit_order(COIN_SYMBOL, is_buy, trade_size, exec_price)
|
self.place_limit_order(COIN_SYMBOL, is_buy, trade_size, exec_price)
|
||||||
else:
|
else:
|
||||||
logging.info(f"Trade size rounds to 0. Skipping. PNL: ${current_pnl:.2f}")
|
logging.info(f"Trade size rounds to 0. Skipping. PNL: ${current_pnl:.2f}{spread_text}{oh_text}")
|
||||||
else:
|
else:
|
||||||
logging.info(f"Idle. Diff {diff_abs:.4f} < Threshold {rebalance_threshold:.4f}. In Hedge Zone. PNL: ${current_pnl:.2f}")
|
logging.info(f"Idle. Diff {diff_abs:.4f} < Threshold {rebalance_threshold:.4f}. In Hedge Zone. PNL: ${current_pnl:.2f}{spread_text}{oh_text}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# MIDDLE ZONE (IDLE)
|
# MIDDLE ZONE (IDLE)
|
||||||
pct_position = (price - clp_low_range) / range_width
|
pct_position = (price - clp_low_range) / range_width
|
||||||
logging.info(f"Idle. In Middle Zone ({pct_position*100:.1f}%). PNL: ${current_pnl:.2f}. No Actions.")
|
logging.info(f"Idle. In Middle Zone ({pct_position*100:.1f}%). PNL: ${current_pnl:.2f}{spread_text}{oh_text}. No Actions.")
|
||||||
|
|
||||||
time.sleep(CHECK_INTERVAL)
|
time.sleep(CHECK_INTERVAL)
|
||||||
|
|
||||||
|
|||||||
@ -454,7 +454,7 @@
|
|||||||
"type": "AUTOMATIC",
|
"type": "AUTOMATIC",
|
||||||
"token_id": 5158011,
|
"token_id": 5158011,
|
||||||
"opened": "03:32 15/12/25",
|
"opened": "03:32 15/12/25",
|
||||||
"status": "OPEN",
|
"status": "CLOSED",
|
||||||
"entry_price": 3135.31,
|
"entry_price": 3135.31,
|
||||||
"target_value": 1983.24,
|
"target_value": 1983.24,
|
||||||
"amount0_initial": 0.3009,
|
"amount0_initial": 0.3009,
|
||||||
@ -467,6 +467,153 @@
|
|||||||
"range_lower": 3102.62,
|
"range_lower": 3102.62,
|
||||||
"static_long": 0.0,
|
"static_long": 0.0,
|
||||||
"timestamp_open": 1765765971,
|
"timestamp_open": 1765765971,
|
||||||
|
"timestamp_close": 1765794574,
|
||||||
|
"fees_collected_usd": 6.69,
|
||||||
|
"closed_position_value_usd": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "AUTOMATIC",
|
||||||
|
"token_id": 5158409,
|
||||||
|
"opened": "11:37 15/12/25",
|
||||||
|
"status": "CLOSED",
|
||||||
|
"entry_price": 3166.4,
|
||||||
|
"target_value": 1921.57,
|
||||||
|
"amount0_initial": 0.2816,
|
||||||
|
"amount1_initial": 1029.9,
|
||||||
|
"range_upper": 3197.1,
|
||||||
|
"zone_top_start_price": null,
|
||||||
|
"zone_close_top_price": null,
|
||||||
|
"zone_close_bottom_price": null,
|
||||||
|
"zone_bottom_limit_price": 3228.75,
|
||||||
|
"range_lower": 3133.8,
|
||||||
|
"static_long": 0.0,
|
||||||
|
"timestamp_open": 1765795041,
|
||||||
|
"timestamp_close": 1765808903,
|
||||||
|
"fees_collected_usd": 4.36,
|
||||||
|
"closed_position_value_usd": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "AUTOMATIC",
|
||||||
|
"token_id": 5158857,
|
||||||
|
"opened": "15:36 15/12/25",
|
||||||
|
"status": "CLOSED",
|
||||||
|
"entry_price": 3127.7,
|
||||||
|
"target_value": 1956.0,
|
||||||
|
"amount0_initial": 0.2889,
|
||||||
|
"amount1_initial": 1052.48,
|
||||||
|
"range_upper": 3155.81,
|
||||||
|
"zone_top_start_price": null,
|
||||||
|
"zone_close_top_price": null,
|
||||||
|
"zone_close_bottom_price": null,
|
||||||
|
"zone_bottom_limit_price": 3185.51,
|
||||||
|
"range_lower": 3096.42,
|
||||||
|
"static_long": 0.0,
|
||||||
|
"timestamp_open": 1765809371,
|
||||||
|
"timestamp_close": 1765810294,
|
||||||
|
"fees_collected_usd": 3.06,
|
||||||
|
"closed_position_value_usd": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "AUTOMATIC",
|
||||||
|
"token_id": 5158950,
|
||||||
|
"opened": "15:59 15/12/25",
|
||||||
|
"status": "CLOSED",
|
||||||
|
"entry_price": 3054.98,
|
||||||
|
"target_value": 1973.85,
|
||||||
|
"amount0_initial": 0.3079,
|
||||||
|
"amount1_initial": 1033.2,
|
||||||
|
"range_upper": 3099.51,
|
||||||
|
"zone_top_start_price": null,
|
||||||
|
"zone_close_top_price": null,
|
||||||
|
"zone_close_bottom_price": null,
|
||||||
|
"zone_bottom_limit_price": 3099.51,
|
||||||
|
"range_lower": 3007.91,
|
||||||
|
"static_long": 0.0,
|
||||||
|
"timestamp_open": 1765810753,
|
||||||
|
"timestamp_close": 1765812125,
|
||||||
|
"fees_collected_usd": 4.94,
|
||||||
|
"closed_position_value_usd": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "AUTOMATIC",
|
||||||
|
"token_id": 5159085,
|
||||||
|
"opened": "16:29 15/12/25",
|
||||||
|
"status": "CLOSED",
|
||||||
|
"entry_price": 3003.17,
|
||||||
|
"target_value": 1985.39,
|
||||||
|
"amount0_initial": 0.3193,
|
||||||
|
"amount1_initial": 1026.56,
|
||||||
|
"range_upper": 3047.27,
|
||||||
|
"zone_top_start_price": null,
|
||||||
|
"zone_close_top_price": null,
|
||||||
|
"zone_close_bottom_price": null,
|
||||||
|
"zone_bottom_limit_price": 3047.27,
|
||||||
|
"range_lower": 2957.21,
|
||||||
|
"static_long": 0.0,
|
||||||
|
"timestamp_open": 1765812592,
|
||||||
|
"timestamp_close": 1765820307,
|
||||||
|
"fees_collected_usd": 9.28,
|
||||||
|
"closed_position_value_usd": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "AUTOMATIC",
|
||||||
|
"token_id": 5159604,
|
||||||
|
"opened": "18:46 15/12/25",
|
||||||
|
"status": "CLOSED",
|
||||||
|
"entry_price": 2956.0,
|
||||||
|
"target_value": 1977.09,
|
||||||
|
"amount0_initial": 0.3271,
|
||||||
|
"amount1_initial": 1010.26,
|
||||||
|
"range_upper": 2998.9,
|
||||||
|
"zone_top_start_price": null,
|
||||||
|
"zone_close_top_price": null,
|
||||||
|
"zone_close_bottom_price": null,
|
||||||
|
"zone_bottom_limit_price": 2998.9,
|
||||||
|
"range_lower": 2910.28,
|
||||||
|
"static_long": 0.0,
|
||||||
|
"timestamp_open": 1765820775,
|
||||||
|
"timestamp_close": 1765860714,
|
||||||
|
"fees_collected_usd": 20.27,
|
||||||
|
"closed_position_value_usd": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "AUTOMATIC",
|
||||||
|
"token_id": 5160824,
|
||||||
|
"opened": "05:59 16/12/25",
|
||||||
|
"status": "CLOSED",
|
||||||
|
"entry_price": 2917.24,
|
||||||
|
"target_value": 1989.32,
|
||||||
|
"amount0_initial": 0.3323,
|
||||||
|
"amount1_initial": 1019.88,
|
||||||
|
"range_upper": 2960.17,
|
||||||
|
"zone_top_start_price": null,
|
||||||
|
"zone_close_top_price": null,
|
||||||
|
"zone_close_bottom_price": null,
|
||||||
|
"zone_bottom_limit_price": 2960.17,
|
||||||
|
"range_lower": 2872.69,
|
||||||
|
"static_long": 0.0,
|
||||||
|
"timestamp_open": 1765861181,
|
||||||
"timestamp_close": null
|
"timestamp_close": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "AUTOMATIC",
|
||||||
|
"token_id": 5161116,
|
||||||
|
"opened": "09:37 16/12/25",
|
||||||
|
"status": "CLOSED",
|
||||||
|
"entry_price": 2931.06,
|
||||||
|
"target_value": 199.06,
|
||||||
|
"amount0_initial": 0.0327,
|
||||||
|
"amount1_initial": 103.33,
|
||||||
|
"range_upper": 2939.53,
|
||||||
|
"zone_top_start_price": null,
|
||||||
|
"zone_close_top_price": null,
|
||||||
|
"zone_close_bottom_price": null,
|
||||||
|
"zone_bottom_limit_price": 2939.53,
|
||||||
|
"range_lower": 2921.94,
|
||||||
|
"static_long": 0.0,
|
||||||
|
"timestamp_open": 1765874274,
|
||||||
|
"timestamp_close": 1765881607,
|
||||||
|
"fees_collected_usd": 0.7,
|
||||||
|
"closed_position_value_usd": 0.0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -76,7 +76,7 @@ RPC_URL = os.environ.get("MAINNET_RPC_URL")
|
|||||||
PRIVATE_KEY = os.environ.get("MAIN_WALLET_PRIVATE_KEY") or os.environ.get("PRIVATE_KEY")
|
PRIVATE_KEY = os.environ.get("MAIN_WALLET_PRIVATE_KEY") or os.environ.get("PRIVATE_KEY")
|
||||||
|
|
||||||
# Script behavior flags
|
# Script behavior flags
|
||||||
MONITOR_INTERVAL_SECONDS = 451
|
MONITOR_INTERVAL_SECONDS = 120
|
||||||
COLLECT_FEES_ENABLED = False # If True, will attempt to collect fees once and exit if no open auto position
|
COLLECT_FEES_ENABLED = False # If True, will attempt to collect fees once and exit if no open auto position
|
||||||
CLOSE_POSITION_ENABLED = True # If True, will attempt to close auto position when out of range
|
CLOSE_POSITION_ENABLED = True # If True, will attempt to close auto position when out of range
|
||||||
CLOSE_IF_OUT_OF_RANGE_ONLY = True # If True, closes only if out of range; if False, closes immediately
|
CLOSE_IF_OUT_OF_RANGE_ONLY = True # If True, closes only if out of range; if False, closes immediately
|
||||||
@ -84,8 +84,8 @@ OPEN_POSITION_ENABLED = True # If True, will open a new position if no auto posi
|
|||||||
REBALANCE_ON_CLOSE_BELOW_RANGE = False # If True, will sell 50% of WETH to USDC when closing below range
|
REBALANCE_ON_CLOSE_BELOW_RANGE = False # If True, will sell 50% of WETH to USDC when closing below range
|
||||||
|
|
||||||
# New Position Parameters
|
# New Position Parameters
|
||||||
TARGET_INVESTMENT_VALUE_TOKEN1 = 2000.0 # Target total investment value in Token1 terms (e.g. 350 USDC)
|
TARGET_INVESTMENT_VALUE_TOKEN1 = 200 # Target total investment value in Token1 terms (e.g. 350 USDC)
|
||||||
RANGE_WIDTH_PCT = 0.01 # +/- 2% range for new positions
|
RANGE_WIDTH_PCT = 0.003 # +/- 2% range for new positions
|
||||||
|
|
||||||
# JSON File for tracking position state
|
# JSON File for tracking position state
|
||||||
STATUS_FILE = "hedge_status.json"
|
STATUS_FILE = "hedge_status.json"
|
||||||
@ -153,7 +153,7 @@ def update_hedge_status_file(action, position_data):
|
|||||||
"opened": opened_str,
|
"opened": opened_str,
|
||||||
"status": "OPEN",
|
"status": "OPEN",
|
||||||
"entry_price": round(position_data['entry_price'], 2),
|
"entry_price": round(position_data['entry_price'], 2),
|
||||||
"target_value": round(position_data.get('target_value', 0.0), 2),
|
"target_value": round(position_data['target_value'], 2), # Use actual calculated value
|
||||||
"amount0_initial": fmt_amt0,
|
"amount0_initial": fmt_amt0,
|
||||||
"amount1_initial": fmt_amt1,
|
"amount1_initial": fmt_amt1,
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ def update_hedge_status_file(action, position_data):
|
|||||||
current_data.append(new_entry)
|
current_data.append(new_entry)
|
||||||
print(f"Recorded new AUTOMATIC position {position_data['token_id']} in {STATUS_FILE}")
|
print(f"Recorded new AUTOMATIC position {position_data['token_id']} in {STATUS_FILE}")
|
||||||
|
|
||||||
elif action == "CLOSE":
|
elif action == "CLOSING":
|
||||||
found = False
|
found = False
|
||||||
for entry in current_data:
|
for entry in current_data:
|
||||||
if (
|
if (
|
||||||
@ -183,9 +183,31 @@ def update_hedge_status_file(action, position_data):
|
|||||||
entry.get('status') == "OPEN" and
|
entry.get('status') == "OPEN" and
|
||||||
entry.get('token_id') == position_data['token_id']
|
entry.get('token_id') == position_data['token_id']
|
||||||
):
|
):
|
||||||
|
entry['status'] = "CLOSING"
|
||||||
|
found = True
|
||||||
|
print(f"Marked position {entry['token_id']} as CLOSING in {STATUS_FILE}")
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
print(f"WARNING: Could not find open AUTOMATIC position {position_data['token_id']} to mark closing.")
|
||||||
|
|
||||||
|
elif action == "CLOSE":
|
||||||
|
found = False
|
||||||
|
for entry in current_data:
|
||||||
|
if (
|
||||||
|
entry.get('type') == "AUTOMATIC" and
|
||||||
|
(entry.get('status') == "OPEN" or entry.get('status') == "CLOSING") and
|
||||||
|
entry.get('token_id') == position_data['token_id']
|
||||||
|
):
|
||||||
|
|
||||||
entry['status'] = "CLOSED"
|
entry['status'] = "CLOSED"
|
||||||
entry['timestamp_close'] = int(time.time())
|
entry['timestamp_close'] = int(time.time())
|
||||||
|
|
||||||
|
# Add Closing Stats if provided
|
||||||
|
if 'fees_collected_usd' in position_data:
|
||||||
|
entry['fees_collected_usd'] = round(position_data['fees_collected_usd'], 2)
|
||||||
|
if 'closed_position_value_usd' in position_data:
|
||||||
|
entry['closed_position_value_usd'] = round(position_data['closed_position_value_usd'], 2)
|
||||||
|
|
||||||
found = True
|
found = True
|
||||||
print(f"Marked position {entry['token_id']} as CLOSED in {STATUS_FILE}")
|
print(f"Marked position {entry['token_id']} as CLOSED in {STATUS_FILE}")
|
||||||
break
|
break
|
||||||
@ -669,11 +691,36 @@ def main():
|
|||||||
print(f"⚠️ Automatic Position {token_id} is OUT OF RANGE! Initiating Close...")
|
print(f"⚠️ Automatic Position {token_id} is OUT OF RANGE! Initiating Close...")
|
||||||
liq = pos_details['liquidity']
|
liq = pos_details['liquidity']
|
||||||
if liq > 0:
|
if liq > 0:
|
||||||
if decrease_liquidity(w3, npm_contract, account, token_id, liq):
|
# Mark as CLOSING immediately to notify Hedger
|
||||||
time.sleep(5)
|
update_hedge_status_file("CLOSING", {'token_id': token_id})
|
||||||
collect_fees(w3, npm_contract, account, token_id)
|
|
||||||
update_hedge_status_file("CLOSE", {'token_id': token_id})
|
# Capture Balances Before Close
|
||||||
print("Position Closed & Status Updated.")
|
b0_start, b1_start = get_token_balances(w3, account.address, pos_details['token0_address'], pos_details['token1_address'])
|
||||||
|
|
||||||
|
# Execute Close
|
||||||
|
decrease_success = decrease_liquidity(w3, npm_contract, account, token_id, liq)
|
||||||
|
time.sleep(2)
|
||||||
|
collect_fees(w3, npm_contract, account, token_id)
|
||||||
|
|
||||||
|
if decrease_success:
|
||||||
|
# Capture Balances After Close
|
||||||
|
b0_end, b1_end = get_token_balances(w3, account.address, pos_details['token0_address'], pos_details['token1_address'])
|
||||||
|
|
||||||
|
# Calculate Deltas (Principal + Fees)
|
||||||
|
delta0 = from_wei(b0_end - b0_start, pos_details['token0_decimals'])
|
||||||
|
delta1 = from_wei(b1_end - b1_start, pos_details['token1_decimals'])
|
||||||
|
|
||||||
|
# Calculate Values
|
||||||
|
total_exit_usd = (delta0 * current_price) + delta1
|
||||||
|
# We calculated total_fees_usd earlier in the loop
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'token_id': token_id,
|
||||||
|
'fees_collected_usd': total_fees_usd,
|
||||||
|
'closed_position_value_usd': total_exit_usd
|
||||||
|
}
|
||||||
|
update_hedge_status_file("CLOSE", update_data)
|
||||||
|
print(f"Position Closed. Value: ${total_exit_usd:.2f}, Fees: ${total_fees_usd:.2f}")
|
||||||
|
|
||||||
# --- REBALANCE ON CLOSE (If Price Dropped) ---
|
# --- REBALANCE ON CLOSE (If Price Dropped) ---
|
||||||
if REBALANCE_ON_CLOSE_BELOW_RANGE and status_str == "OUT OF RANGE (BELOW)":
|
if REBALANCE_ON_CLOSE_BELOW_RANGE and status_str == "OUT OF RANGE (BELOW)":
|
||||||
@ -715,7 +762,7 @@ def main():
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
print("Liquidity 0. Marking closed.")
|
print("Liquidity 0. Marking closed.")
|
||||||
update_hedge_status_file("CLOSE", {'token_id': token_id})
|
update_hedge_status_file("CLOSE", {'token_id': token_id, 'fees_collected_usd': 0.0, 'closed_position_value_usd': 0.0})
|
||||||
|
|
||||||
# 2. Opening Logic (If no active automatic position)
|
# 2. Opening Logic (If no active automatic position)
|
||||||
if not active_automatic_position and OPEN_POSITION_ENABLED:
|
if not active_automatic_position and OPEN_POSITION_ENABLED:
|
||||||
|
|||||||
53
clp_hedger_auto/AGENTS.md
Normal file
53
clp_hedger_auto/AGENTS.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# AGENTS.md - CLP Hedger Project Guide
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the Application
|
||||||
|
```bash
|
||||||
|
# Main hedger bot
|
||||||
|
python clp_hedger.py
|
||||||
|
|
||||||
|
# Development with debug logging
|
||||||
|
python -c "from logging_utils import setup_logging; setup_logging('debug', 'CLP_HEDGER'); import clp_hedger"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
No formal test framework. Manual testing:
|
||||||
|
```bash
|
||||||
|
# Check configuration
|
||||||
|
python -c "import clp_hedger; print(clp_hedger.get_manual_position_config())"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### Imports
|
||||||
|
- Order: standard library → third-party → local modules
|
||||||
|
- Add project root to sys.path for local imports
|
||||||
|
- Use absolute imports from project root
|
||||||
|
|
||||||
|
### Environment & Logging
|
||||||
|
- Use `.env` files with python-dotenv
|
||||||
|
- Use `setup_logging("normal"/"debug", "MODULE_NAME")` convention
|
||||||
|
- Include emojis: 🚀, ✅, ⚡, 🔄
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
- PascalCase classes (HyperliquidStrategy, CLPHedger)
|
||||||
|
- Private methods start with underscore (_init_strategy)
|
||||||
|
- Module-level constants: UPPER_SNAKE_CASE
|
||||||
|
- Functions/variables: snake_case
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Wrap API calls in try/except blocks
|
||||||
|
- Log errors with context
|
||||||
|
- Return None/0.0 for non-critical failures
|
||||||
|
- Use sys.exit(1) for critical failures
|
||||||
|
|
||||||
|
### Mathematical Operations
|
||||||
|
- Use math.sqrt() for square roots
|
||||||
|
- Implement proper rounding for API requirements
|
||||||
|
- Handle floating-point precision appropriately
|
||||||
@ -36,7 +36,7 @@ LEVERAGE = 5
|
|||||||
STATUS_FILE = "hedge_status.json"
|
STATUS_FILE = "hedge_status.json"
|
||||||
|
|
||||||
# Gap Recovery Configuration
|
# Gap Recovery Configuration
|
||||||
PRICE_BUFFER_PCT = 0.004 # 0.5% buffer to prevent churn
|
PRICE_BUFFER_PCT = 0.002 # 0.5% buffer to prevent churn
|
||||||
TIME_BUFFER_SECONDS = 120 # 2 minutes wait between mode switches
|
TIME_BUFFER_SECONDS = 120 # 2 minutes wait between mode switches
|
||||||
|
|
||||||
def get_manual_position_config():
|
def get_manual_position_config():
|
||||||
@ -181,7 +181,7 @@ def round_to_sig_figs(x, sig_figs=5):
|
|||||||
|
|
||||||
class CLPHedger:
|
class CLPHedger:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.private_key = os.environ.get("HEDGER_PRIVATE_KEY") or os.environ.get("AGENT_PRIVATE_KEY")
|
self.private_key = os.environ.get("SWING_AGENT_PK")
|
||||||
self.vault_address = os.environ.get("MAIN_WALLET_ADDRESS")
|
self.vault_address = os.environ.get("MAIN_WALLET_ADDRESS")
|
||||||
|
|
||||||
if not self.private_key:
|
if not self.private_key:
|
||||||
@ -351,13 +351,13 @@ class CLPHedger:
|
|||||||
logging.info("Attempting to close all open positions...")
|
logging.info("Attempting to close all open positions...")
|
||||||
try:
|
try:
|
||||||
# 1. Get latest price
|
# 1. Get latest price
|
||||||
price = self.get_market_price(COIN_SYMBOL)
|
price = self.get_market_price(self.coin_symbol)
|
||||||
if price is None:
|
if price is None:
|
||||||
logging.error("Could not fetch price to close positions. Aborting close.")
|
logging.error("Could not fetch price to close positions. Aborting close.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 2. Get current position
|
# 2. Get current position
|
||||||
current_pos = self.get_current_position(COIN_SYMBOL)
|
current_pos = self.get_current_position(self.coin_symbol)
|
||||||
if current_pos == 0:
|
if current_pos == 0:
|
||||||
logging.info("No open positions to close.")
|
logging.info("No open positions to close.")
|
||||||
return
|
return
|
||||||
@ -375,10 +375,10 @@ class CLPHedger:
|
|||||||
logging.info("Position size effectively 0 after rounding.")
|
logging.info("Position size effectively 0 after rounding.")
|
||||||
return
|
return
|
||||||
|
|
||||||
logging.info(f"Closing Position: {current_pos} {COIN_SYMBOL} -> Action: {'BUY' if is_buy else 'SELL'} {final_size}")
|
logging.info(f"Closing Position: {current_pos} {self.coin_symbol} -> Action: {'BUY' if is_buy else 'SELL'} {final_size}")
|
||||||
|
|
||||||
# 4. Execute
|
# 4. Execute
|
||||||
self.execute_trade(COIN_SYMBOL, is_buy, final_size, price)
|
self.execute_trade(self.coin_symbol, is_buy, final_size, price)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error during close_all_positions: {e}")
|
logging.error(f"Error during close_all_positions: {e}")
|
||||||
@ -415,14 +415,14 @@ class CLPHedger:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# 1. Get Data
|
# 1. Get Data
|
||||||
price = self.get_market_price(COIN_SYMBOL)
|
price = self.get_market_price(self.coin_symbol)
|
||||||
if price is None:
|
if price is None:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
funding_rate = self.get_funding_rate(COIN_SYMBOL)
|
funding_rate = self.get_funding_rate(self.coin_symbol)
|
||||||
|
|
||||||
current_pos_size = self.get_current_position(COIN_SYMBOL)
|
current_pos_size = self.get_current_position(self.coin_symbol)
|
||||||
|
|
||||||
# 2. Calculate Logic
|
# 2. Calculate Logic
|
||||||
# Pass raw size (e.g. -1.5). The strategy handles the logic.
|
# Pass raw size (e.g. -1.5). The strategy handles the logic.
|
||||||
@ -450,7 +450,7 @@ class CLPHedger:
|
|||||||
if trade_size > 0:
|
if trade_size > 0:
|
||||||
logging.info(f"⚡ THRESHOLD TRIGGERED ({diff_abs:.3f} >= {REBALANCE_THRESHOLD})")
|
logging.info(f"⚡ THRESHOLD TRIGGERED ({diff_abs:.3f} >= {REBALANCE_THRESHOLD})")
|
||||||
is_buy = (calc['action'] == "BUY")
|
is_buy = (calc['action'] == "BUY")
|
||||||
self.execute_trade(COIN_SYMBOL, is_buy, trade_size, price)
|
self.execute_trade(self.coin_symbol, is_buy, trade_size, price)
|
||||||
else:
|
else:
|
||||||
logging.info("Trade size rounds to 0. Skipping.")
|
logging.info("Trade size rounds to 0. Skipping.")
|
||||||
|
|
||||||
18
clp_hedger_auto/hedge_status.json
Normal file
18
clp_hedger_auto/hedge_status.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "MANUAL",
|
||||||
|
"token_id": 5147464,
|
||||||
|
"status": "OPEN",
|
||||||
|
"hedge_enabled": true,
|
||||||
|
"coin_symbol": "ETH",
|
||||||
|
"entry_price": 3332.66,
|
||||||
|
"range_lower": 2844.11,
|
||||||
|
"range_upper": 3477.24,
|
||||||
|
"target_value": 6938.95,
|
||||||
|
"amount0_initial": 0.45,
|
||||||
|
"amount1_initial": 5439.23,
|
||||||
|
"static_long": 0.0,
|
||||||
|
"timestamp_open": 1765575924,
|
||||||
|
"timestamp_close": null
|
||||||
|
}
|
||||||
|
]
|
||||||
85
clp_hedger_auto/working_configuration.md
Normal file
85
clp_hedger_auto/working_configuration.md
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# CLP Hedger - Working Configuration Summary
|
||||||
|
|
||||||
|
## Current Setup Status
|
||||||
|
✅ **ACTIVE**: Hedger is running and successfully trading on Hyperliquid
|
||||||
|
|
||||||
|
## Position Configuration (`hedge_status.json`)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "MANUAL",
|
||||||
|
"token_id": 5147464,
|
||||||
|
"status": "OPEN",
|
||||||
|
"hedge_enabled": true,
|
||||||
|
"coin_symbol": "ETH",
|
||||||
|
"entry_price": 3332.66,
|
||||||
|
"range_lower": 2844.11,
|
||||||
|
"range_upper": 3477.24,
|
||||||
|
"target_value": 6938.95,
|
||||||
|
"amount0_initial": 0.45,
|
||||||
|
"amount1_initial": 5439.23,
|
||||||
|
"static_long": 0.0,
|
||||||
|
"timestamp_open": 1765575924,
|
||||||
|
"timestamp_close": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Trading Parameters
|
||||||
|
- **Coin**: ETH
|
||||||
|
- **Leverage**: 5x (Cross)
|
||||||
|
- **Entry Price**: $3,332.66
|
||||||
|
- **Price Range**: $2,844.11 - $3,477.24
|
||||||
|
- **Position Size**: 0.45 ETH
|
||||||
|
- **Static Long**: 0% (fully hedged)
|
||||||
|
- **Target Value**: $6,938.95
|
||||||
|
|
||||||
|
## Hedger Configuration (`clp_hedger.py`)
|
||||||
|
- **Check Interval**: 30 seconds
|
||||||
|
- **Rebalance Threshold**: 0.15 ETH
|
||||||
|
- **Price Buffer**: 0.2% (prevents churn)
|
||||||
|
- **Time Buffer**: 120 seconds (between mode switches)
|
||||||
|
- **Status File**: `hedge_status.json`
|
||||||
|
|
||||||
|
## Strategy Parameters
|
||||||
|
- **Entry WETH**: 0.45 ETH
|
||||||
|
- **Low Range**: $2,844.11
|
||||||
|
- **High Range**: $3,477.24
|
||||||
|
- **Start Price**: $3,332.66
|
||||||
|
- **Static Long Ratio**: 0.0 (0% static long exposure)
|
||||||
|
|
||||||
|
## Gap Recovery Settings
|
||||||
|
- **Current Mode**: NORMAL (100% hedge)
|
||||||
|
- **Gap Recovery**: Enabled
|
||||||
|
- **Recovery Target**: Entry price + (2 × Gap)
|
||||||
|
- **Price Buffer**: 0.2%
|
||||||
|
- **Mode Switch Delay**: 120 seconds
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
- **Wallet**: 0xcb262ceaae5d8a99b713f87a43dd18e6be892739
|
||||||
|
- **Network**: Hyperliquid Mainnet
|
||||||
|
- **Logging Level**: Normal
|
||||||
|
- **Virtual Environment**: Active
|
||||||
|
|
||||||
|
## Last Status
|
||||||
|
- ✅ API Connection: Working
|
||||||
|
- ✅ Price Feed: Active
|
||||||
|
- ✅ Position Tracking: Enabled
|
||||||
|
- ✅ Hedge Logic: Operational
|
||||||
|
- ✅ Order Execution: Successful
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
- `clp_hedger.py`: Main hedger bot
|
||||||
|
- `hedge_status.json`: Position configuration
|
||||||
|
- `.env`: API credentials (not shown for security)
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
The hedger runs a continuous loop every 30 seconds, checking:
|
||||||
|
1. Current market price
|
||||||
|
2. Position size deviation
|
||||||
|
3. Gap recovery conditions
|
||||||
|
4. Funding rate opportunities
|
||||||
|
5. Automatic rebalancing needs
|
||||||
|
|
||||||
|
## Operations
|
||||||
|
- **Normal Mode**: Maintains 100% hedge against ETH exposure
|
||||||
|
- **Recovery Mode**: Reduces hedge to 0% when gap recovery conditions are met
|
||||||
|
- **Auto-Rebalancing**: Triggers when position deviates by >0.15 ETH
|
||||||
Reference in New Issue
Block a user