feat: disable vol multiplier inside range & add edge cleanup logic
This commit is contained in:
120
clp_hedger.py
120
clp_hedger.py
@ -86,13 +86,13 @@ MIN_ORDER_VALUE_USD = Decimal("10.0")
|
||||
|
||||
# Capital Safety
|
||||
DYNAMIC_THRESHOLD_MULTIPLIER = Decimal("1.2")
|
||||
MIN_TIME_BETWEEN_TRADES = 25
|
||||
MIN_TIME_BETWEEN_TRADES = 60
|
||||
MAX_HEDGE_MULTIPLIER = Decimal("1.25")
|
||||
# Rebalance Threshold (Base)
|
||||
# Adjust this based on expected volatility:
|
||||
# - Low Volatility (Weekend/Chop): 0.08 - 0.10 (8-10%) to reduce churn
|
||||
# - High Volatility (Events): 0.05 (5%) to track price closely
|
||||
BASE_REBALANCE_THRESHOLD_PCT = Decimal("0.12") # 12%
|
||||
BASE_REBALANCE_THRESHOLD_PCT = Decimal("0.09") # 9%
|
||||
|
||||
# Edge Protection
|
||||
EDGE_PROXIMITY_PCT = Decimal("0.04")
|
||||
@ -101,10 +101,15 @@ POSITION_OPEN_EDGE_PROXIMITY_PCT = Decimal("0.06")
|
||||
POSITION_CLOSED_EDGE_PROXIMITY_PCT = Decimal("0.025")
|
||||
LARGE_HEDGE_MULTIPLIER = Decimal("2.8")
|
||||
|
||||
# Edge Cleanup Settings
|
||||
ENABLE_EDGE_CLEANUP = True
|
||||
EDGE_CLEANUP_MARGIN_PCT = Decimal("0.02") # 2% of range width
|
||||
|
||||
# Fishing Order (Maker "Fishing" at Entry Price)
|
||||
ENABLE_FISHING = True
|
||||
FISHING_ORDER_SIZE_PCT = Decimal("0.10") # 10% of hedge size
|
||||
|
||||
MAKER_ORDER_TIMEOUT = 400
|
||||
MAKER_ORDER_TIMEOUT = 600
|
||||
SHADOW_ORDER_TIMEOUT = 600
|
||||
|
||||
# --- HELPER FUNCTIONS ---
|
||||
@ -354,6 +359,7 @@ class ScalperHedger:
|
||||
# Logging Rate Limiting
|
||||
self.last_idle_log_time = 0
|
||||
self.last_pending_log_time = 0
|
||||
self.api_backoff_until = 0
|
||||
|
||||
# Order Tracking
|
||||
self.original_order_side = None
|
||||
@ -638,7 +644,11 @@ class ScalperHedger:
|
||||
logger.error(f"Order Failed: {order_result}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Exception during trade: {e}")
|
||||
if "429" in str(e):
|
||||
logger.warning(f"Rate limit hit during trade (429). Backing off for 30s.")
|
||||
self.api_backoff_until = time.time() + 30
|
||||
else:
|
||||
logger.error(f"Exception during trade: {e}")
|
||||
return None
|
||||
|
||||
def manage_orders(self) -> bool:
|
||||
@ -697,7 +707,8 @@ class ScalperHedger:
|
||||
def track_fills_and_pnl(self, force: bool = False):
|
||||
try:
|
||||
now = time.time()
|
||||
if not force and now - self.last_pnl_check_time < 10:
|
||||
# Increase interval to 60s to avoid 429 Rate Limits
|
||||
if not force and now - self.last_pnl_check_time < 60:
|
||||
return
|
||||
self.last_pnl_check_time = now
|
||||
|
||||
@ -722,8 +733,15 @@ class ScalperHedger:
|
||||
|
||||
if new_activity:
|
||||
logger.info(f"Fills tracked. Total Price PnL: {self.accumulated_pnl:.2f} | Total Fees: {self.accumulated_fees:.2f}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error tracking fills: {e}")
|
||||
# Handle 429 specifically
|
||||
if "429" in str(e):
|
||||
logger.warning(f"Rate limit hitting while tracking fills (429). Backing off.")
|
||||
# Add a small penalty delay to last_check to prevent immediate retry
|
||||
self.last_pnl_check_time = time.time() + 30
|
||||
else:
|
||||
logger.error(f"Error tracking fills: {e}")
|
||||
|
||||
def close_all_positions(self, force_taker: bool = False):
|
||||
logger.info("Closing all positions...")
|
||||
@ -775,6 +793,14 @@ class ScalperHedger:
|
||||
|
||||
while True:
|
||||
try:
|
||||
# API Backoff Check
|
||||
if time.time() < self.api_backoff_until:
|
||||
wait_time = self.api_backoff_until - time.time()
|
||||
if int(wait_time) % 5 == 0: # Log every 5s
|
||||
logger.warning(f"Backing off due to 429... ({wait_time:.1f}s)")
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
active_pos = get_active_automatic_position()
|
||||
|
||||
# Check Global Disable or Missing Position
|
||||
@ -858,6 +884,10 @@ class ScalperHedger:
|
||||
if vol_pct > 0:
|
||||
vol_multiplier = max(Decimal("1.0"), min(Decimal("3.0"), vol_pct / base_vol_ref))
|
||||
|
||||
# Disable volatility index when price is strictly inside edges (top and bottom) of range
|
||||
if self.strategy.low_range < price < self.strategy.high_range:
|
||||
vol_multiplier = Decimal("1.0")
|
||||
|
||||
# 3. Base Threshold Calculation (Range Dependent)
|
||||
range_width_pct = (self.strategy.high_range - self.strategy.low_range) / self.strategy.low_range
|
||||
|
||||
@ -885,6 +915,21 @@ class ScalperHedger:
|
||||
if pct_change > Decimal("0.003"):
|
||||
rebalance_threshold *= DYNAMIC_THRESHOLD_MULTIPLIER
|
||||
|
||||
# --- FORCE EDGE CLEANUP (Dynamic Margin) ---
|
||||
# Ensure we trade at the X% line if we haven't already.
|
||||
if ENABLE_EDGE_CLEANUP:
|
||||
dist_bottom_pct = (price - self.strategy.low_range) / self.strategy.low_range
|
||||
dist_top_pct = (self.strategy.high_range - price) / self.strategy.high_range
|
||||
|
||||
range_width_pct = (self.strategy.high_range - self.strategy.low_range) / self.strategy.low_range
|
||||
safety_margin_pct = range_width_pct * EDGE_CLEANUP_MARGIN_PCT
|
||||
|
||||
if dist_bottom_pct < safety_margin_pct or dist_top_pct < safety_margin_pct:
|
||||
if rebalance_threshold > MIN_THRESHOLD_ETH:
|
||||
if rebalance_threshold > MIN_THRESHOLD_ETH * Decimal("1.5"):
|
||||
logger.info(f"[EDGE] Inside {EDGE_CLEANUP_MARGIN_PCT*100}% Safety Zone. Forcing tight threshold: {rebalance_threshold:.4f} -> {MIN_THRESHOLD_ETH:.4f}")
|
||||
rebalance_threshold = MIN_THRESHOLD_ETH
|
||||
|
||||
self.last_price = price
|
||||
|
||||
# 5. Check Zones
|
||||
@ -1017,56 +1062,57 @@ class ScalperHedger:
|
||||
# Price where Diff reaches +threshold (SELL)
|
||||
p_sell = price - (rebalance_threshold - calc['diff']) / gamma
|
||||
|
||||
# Recalculate triggers if outside range (Safety Buffer: Dynamic Margin)
|
||||
# User Request: "at range is too late", "recalculated to be bellow it"
|
||||
r_width = self.strategy.high_range - self.strategy.low_range
|
||||
safety_margin = r_width * EDGE_CLEANUP_MARGIN_PCT
|
||||
|
||||
if p_buy > self.strategy.high_range:
|
||||
p_buy = self.strategy.high_range - safety_margin
|
||||
|
||||
if p_sell < self.strategy.low_range:
|
||||
p_sell = self.strategy.low_range + safety_margin
|
||||
|
||||
net_pnl = self.accumulated_pnl - self.accumulated_fees
|
||||
logger.info(f"[IDLE] Px: {price:.2f} | M: {p_mid:.1f} | B: {p_buy:.1f} / S: {p_sell:.1f} (Vol: {vol_pct*100:.3f}% x{vol_multiplier:.1f} | Thresh: {final_threshold_pct*100:.1f}%) | TotPnL: {net_pnl:.2f}")
|
||||
self.last_idle_log_time = time.time()
|
||||
|
||||
# --- FISHING ORDER LOGIC (SAFE ZONE) ---
|
||||
# Always keep a maker order open at Entry Price for 10% of hedge size
|
||||
if self.fishing_oid is None and not self.get_open_orders():
|
||||
if ENABLE_FISHING and self.fishing_oid is None and not self.get_open_orders():
|
||||
try:
|
||||
# Use REAL Hedge Entry Price from Hyperliquid, not LP Entry
|
||||
hedge_entry = pos_data.get('entry_price', Decimal("0"))
|
||||
|
||||
# Only fish if we actually have a position
|
||||
if hedge_entry > 0 and current_size != 0:
|
||||
# Determine side:
|
||||
# We are SHORT.
|
||||
# If Px < Entry: We are in profit. We want to BUY to take profit/scale out.
|
||||
# If Px > Entry: We are in loss. We want to SELL to average up/scale in.
|
||||
# WAIT. "Fishing" usually means closing.
|
||||
# User said: "Maker position ... at price of entry price"
|
||||
# If we are Short, placing a BUY at Entry (when Px < Entry) acts as Take Profit.
|
||||
# If we are Short, placing a SELL at Entry (when Px > Entry) acts as Averaging Up.
|
||||
# Standard Grid logic:
|
||||
# If Px > Entry: Place SELL at Entry? No, price is above entry. Limit Sell at Entry would fill instantly as Taker.
|
||||
# If Px < Entry: Place BUY at Entry? No, price is below entry. Limit Buy at Entry would fill instantly.
|
||||
# SYMMETRIC FISHING LOGIC:
|
||||
# We want an order that SITS on the book at the entry price.
|
||||
# If Price > Entry: Place BUY at Entry (Waiting for a dip to reduce short).
|
||||
# If Price < Entry: Place SELL at Entry (Waiting for a pump to increase short).
|
||||
|
||||
# CORRECT LOGIC for "Return to Entry":
|
||||
# We want an order that SITS on the book.
|
||||
# If Current Price < Entry: We want to SELL if price goes back UP to Entry. (Add to Short / Re-enter).
|
||||
# If Current Price > Entry: We want to BUY if price goes back DOWN to Entry. (Reduce Short / Escape).
|
||||
if price > hedge_entry:
|
||||
is_buy = True
|
||||
target_price = hedge_entry
|
||||
action_desc = "Reducing Short"
|
||||
elif price < hedge_entry:
|
||||
is_buy = False
|
||||
target_price = hedge_entry
|
||||
action_desc = "Increasing Short"
|
||||
else:
|
||||
# Exactly at entry, skip to avoid immediate Taker matching
|
||||
is_buy = None
|
||||
|
||||
# --- ALWAYS BUY LOGIC ---
|
||||
# We only place a Limit BUY at Entry Price.
|
||||
# This only works as a "Fishing" Maker order if Px > Entry (Waiting for a dip).
|
||||
target_price = hedge_entry
|
||||
is_buy = True
|
||||
|
||||
# Only place if it's a valid Maker order (Target < Current Price)
|
||||
if price > target_price:
|
||||
if is_buy is not None:
|
||||
# Size = 10% of Target Delta
|
||||
fishing_size = abs(calc['target_short']) * FISHING_ORDER_SIZE_PCT
|
||||
|
||||
# Check distance - only place if price is not literally on top of entry
|
||||
dist_pct = (price - target_price) / price
|
||||
if dist_pct > Decimal("0.001"): # 0.1% buffer
|
||||
logger.info(f"[FISHING] Placing Maker BUY {fishing_size:.4f} at Hedge Entry: {target_price:.2f} (Reducing Short at break-even)")
|
||||
# 0.1% buffer to ensure it stays as a Maker order
|
||||
dist_pct = abs(price - target_price) / price
|
||||
if dist_pct > Decimal("0.001"):
|
||||
logger.info(f"[FISHING] Placing Maker {'BUY' if is_buy else 'SELL'} {fishing_size:.4f} at Hedge Entry: {target_price:.2f} ({action_desc})")
|
||||
self.fishing_oid = self.place_limit_order(COIN_SYMBOL, is_buy, fishing_size, target_price, "Alo")
|
||||
else:
|
||||
# Price is already below entry (In profit).
|
||||
# Placing a BUY at entry would be a Taker trade at a worse price.
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error(f"Error placing fishing order: {e}")
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# GEMINI Project Context & Setup
|
||||
|
||||
**Last Updated:** 2025-12-19
|
||||
**Last Updated:** 2025-12-26
|
||||
**Project:** Uniswap V3 Automated Concentrated Liquidity Pool (CLP) Hedger
|
||||
|
||||
## 1. Project Overview
|
||||
@ -38,6 +38,7 @@ This project automates the management and hedging of Uniswap V3 Concentrated Liq
|
||||
* **Refactoring:** Removed `clp_scalper_hedger.py` after merging its advanced features into `clp_hedger.py`.
|
||||
* **Logging:** Fixed duplicate terminal output by disabling logger propagation.
|
||||
* **Feature:** Implemented "Comprehensive Edge Protection" in `clp_hedger.py` (Dynamic Proximity + Large Hedge Override).
|
||||
* **Logic:** Disabled Volatility Multiplier when price is strictly inside range edges (to prevent hedging pauses during volatility spikes when safe).
|
||||
|
||||
## 4. Key Files
|
||||
* `uniswap_manager.py`: Core logic for Uniswap V3 interaction.
|
||||
|
||||
Reference in New Issue
Block a user