Implement 'Always Buy' Fishing Order at Hedge Entry

This commit is contained in:
2025-12-22 14:19:30 +01:00
parent e5151d9d66
commit b3cdf98161
2 changed files with 96 additions and 5 deletions

View File

@ -92,7 +92,7 @@ MAX_HEDGE_MULTIPLIER = Decimal("1.25")
# 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.08")
BASE_REBALANCE_THRESHOLD_PCT = Decimal("0.12") # 12%
# Edge Protection
EDGE_PROXIMITY_PCT = Decimal("0.04")
@ -101,6 +101,9 @@ POSITION_OPEN_EDGE_PROXIMITY_PCT = Decimal("0.06")
POSITION_CLOSED_EDGE_PROXIMITY_PCT = Decimal("0.025")
LARGE_HEDGE_MULTIPLIER = Decimal("2.8")
# Fishing Order (Maker "Fishing" at Entry Price)
FISHING_ORDER_SIZE_PCT = Decimal("0.10") # 10% of hedge size
MAKER_ORDER_TIMEOUT = 400
SHADOW_ORDER_TIMEOUT = 600
@ -355,6 +358,7 @@ class ScalperHedger:
# Order Tracking
self.original_order_side = None
self.shadow_orders = [] # Store theoretical Maker orders for analysis
self.fishing_oid = None # Track the resting "fishing" order
logger.info(f"[DELTA] Delta-Zero Scalper Hedger initialized. Agent: {self.account.address}")
@ -475,6 +479,18 @@ class ScalperHedger:
logger.info(f"[DELTA] Strat Init: Pos {self.active_position_id} | Range: {lower}-{upper} | Entry: {entry_price} | Start Px: {start_price:.2f} | Resumed PnL: {self.accumulated_pnl:.2f}")
# --- Adopt Orphaned Fishing Order ---
# Check if there is already an order at the entry price and adopt it
open_orders = self.get_open_orders()
for o in open_orders:
if o['coin'] == COIN_SYMBOL:
# Check if price matches entry_price (with small tolerance)
o_px = to_decimal(o['limitPx'])
if abs(o_px - entry_price) / entry_price < Decimal("0.0001"):
logger.info(f"[FISHING] Adopted existing fishing order {o['oid']} @ {o_px}")
self.fishing_oid = o['oid']
break
except Exception as e:
logger.error(f"Failed to init strategy: {e}")
self.strategy = None
@ -524,10 +540,11 @@ class ScalperHedger:
return {
'size': to_decimal(pos["position"]["szi"]),
'pnl': to_decimal(pos["position"]["unrealizedPnl"]),
'entry_price': to_decimal(pos["position"]["entryPx"]),
'equity': equity
}
return {'size': Decimal("0"), 'pnl': Decimal("0"), 'equity': equity}
except: return {'size': Decimal("0"), 'pnl': Decimal("0"), 'equity': Decimal("0")}
return {'size': Decimal("0"), 'pnl': Decimal("0"), 'entry_price': Decimal("0"), 'equity': equity}
except: return {'size': Decimal("0"), 'pnl': Decimal("0"), 'entry_price': Decimal("0"), 'equity': Decimal("0")}
def get_open_orders(self) -> List[Dict]:
try:
@ -627,7 +644,13 @@ class ScalperHedger:
def manage_orders(self) -> bool:
"""Returns True if there is an active order that should prevent new trades."""
open_orders = self.get_open_orders()
my_orders = [o for o in open_orders if o['coin'] == COIN_SYMBOL]
# Filter out the fishing order from active management
my_orders = [o for o in open_orders if o['coin'] == COIN_SYMBOL and o['oid'] != self.fishing_oid]
# Verify if fishing_oid is still alive
all_oids = [o['oid'] for o in open_orders]
if self.fishing_oid and self.fishing_oid not in all_oids:
self.fishing_oid = None
if not my_orders:
return False
@ -914,6 +937,12 @@ class ScalperHedger:
cooldown_text = ""
if diff_abs > rebalance_threshold:
# CANCEL FISHING ORDER BEFORE REBALANCE
if self.fishing_oid:
logger.info(f"[FISHING] Cancelling fishing order {self.fishing_oid} to rebalance.")
self.cancel_order(COIN_SYMBOL, self.fishing_oid)
self.fishing_oid = None
if bypass_cooldown:
can_trade = True
logger.info(f"[WARN] COOLDOWN BYPASSED: {override_reason}")
@ -975,15 +1004,72 @@ class ScalperHedger:
# Calculate approximate trigger prices (Linear Approximation)
# G = 0.5 * L * P^-1.5
gamma = (Decimal("0.5") * self.strategy.L * (price ** Decimal("-1.5")))
# Equilibrium Price (where diff would be 0)
# If diff > 0 (Need Sell), we need Target to drop, so Price must Rise.
# Gamma is positive absolute value here, but delta/price relationship is inverse.
# Delta ~ 1/sqrt(P). Slope is negative.
# So P_target = P_current + (Diff / Gamma)
p_mid = price + (calc['diff'] / gamma)
# Price where Diff reaches -threshold (BUY)
p_buy = price + (rebalance_threshold + calc['diff']) / gamma
# Price where Diff reaches +threshold (SELL)
p_sell = price - (rebalance_threshold - calc['diff']) / gamma
net_pnl = self.accumulated_pnl - self.accumulated_fees
logger.info(f"[IDLE] Px: {price:.2f} | 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}")
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():
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.
# 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).
# --- 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:
# 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)")
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}")
self.track_fills_and_pnl()
time.sleep(CHECK_INTERVAL)

View File

@ -11,6 +11,11 @@ All notable changes to this project will be documented in this file.
- This change aims to improve net profitability by ensuring that rebalancing fees are only paid when the delta imbalance is statistically significant.
- **Logging Improvements**:
- Updated `[IDLE]` log format to show estimated **BUY (B)** and **SELL (S)** trigger price levels instead of raw delta difference. This provides better visual feedback on how far the price is from the next rebalance.
- **Fishing Order Logic**:
- Implemented a "Fishing Order" mechanism that keeps a Maker order (10% of hedge size) resting at the `entry_price` while in the safe zone.
- **Logic Update:** Configured to always place a **Limit BUY** at the entry price (when Px > Entry) to reduce the hedge size at break-even.
- **Fix:** Updated position tracking to use the actual Hedge Entry Price from Hyperliquid instead of the LP Strategy entry price.
- Integrated fishing order tracking to distinguish it from standard rebalance orders.
## [2025-12-20]