Implement 'Always Buy' Fishing Order at Hedge Entry
This commit is contained in:
@ -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,8 +644,14 @@ 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)
|
||||
|
||||
|
||||
@ -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]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user