feat(hedger): implement Maker-First strategy with 400s timeout and edge-aware Taker fallback

This commit is contained in:
2025-12-21 16:21:44 +01:00
parent 2194c71d5f
commit ee457d0a51

View File

@ -96,6 +96,9 @@ POSITION_OPEN_EDGE_PROXIMITY_PCT = Decimal("0.06")
POSITION_CLOSED_EDGE_PROXIMITY_PCT = Decimal("0.025")
LARGE_HEDGE_MULTIPLIER = Decimal("2.8")
MAKER_ORDER_TIMEOUT = 400
SHADOW_ORDER_TIMEOUT = 600
# --- HELPER FUNCTIONS ---
def to_decimal(value: Any) -> Decimal:
@ -630,6 +633,14 @@ class ScalperHedger:
oid = order['oid']
order_price = to_decimal(order['limitPx'])
# Check Timeout
if 'timestamp' in order:
order_age = time.time() - (order['timestamp'] / 1000.0)
if order_age > MAKER_ORDER_TIMEOUT:
logger.info(f"Order {oid} timed out ({order_age:.1f}s > {MAKER_ORDER_TIMEOUT}s). Cancelling.")
self.cancel_order(COIN_SYMBOL, oid)
return False
# Check if price moved too far
levels = self.get_order_book_levels(COIN_SYMBOL)
if not levels: return True # Keep order if data missing
@ -906,36 +917,45 @@ class ScalperHedger:
if can_trade:
is_buy = (calc['action'] == "BUY")
# Taker execution for rebalance
# EXECUTION STRATEGY
if bypass_cooldown:
# URGENT / UNSAFE ZONE -> TAKER (Ioc)
order_type = "Ioc"
# Aggressive Taker Price
exec_price = levels['ask'] * Decimal("1.001") if is_buy else levels['bid'] * Decimal("0.999")
# Shadow Order for Data Collection (Only when taking)
create_shadow = True
else:
# SAFE ZONE -> MAKER (Alo)
order_type = "Alo"
# Passive Maker Price
exec_price = levels['bid'] if is_buy else levels['ask']
create_shadow = False
urgency = "URGENT" if bypass_cooldown else "NORMAL"
logger.info(f"[TRIG] Rebalance ({urgency}): {calc['action']} {diff_abs:.4f} > {rebalance_threshold:.4f} | Book: {levels['bid']}/{levels['ask']} | Vol: {vol_pct*100:.3f}% x{vol_multiplier:.1f} | Thresh: {final_threshold_pct*100:.1f}%")
oid = self.place_limit_order(COIN_SYMBOL, is_buy, diff_abs, exec_price, "Ioc")
oid = self.place_limit_order(COIN_SYMBOL, is_buy, diff_abs, exec_price, order_type)
if oid:
self.last_trade_time = time.time()
self.track_fills_and_pnl(force=True)
# --- Shadow Order Creation ---
# Simulate: "What if we placed a Maker order instead?"
# Only if we Taker trade, to see if Maker would have worked
if create_shadow:
try:
# Fixed Timeout for Data Collection (10 min)
# We want to capture the full distribution of fill times.
dynamic_timeout = 600.0
# Shadow Price (Passive)
# If we Taker BUY, we would have Maker BUY at BID
# If we Taker SELL, we would have Maker SELL at ASK
shadow_price = levels['bid'] if is_buy else levels['ask']
self.shadow_orders.append({
'side': 'BUY' if is_buy else 'SELL',
'price': shadow_price,
'timeout_duration': dynamic_timeout,
'expires_at': time.time() + dynamic_timeout
'timeout_duration': SHADOW_ORDER_TIMEOUT,
'expires_at': time.time() + SHADOW_ORDER_TIMEOUT
})
logger.info(f"[SHADOW] Created Maker {'BUY' if is_buy else 'SELL'} @ {shadow_price:.2f} (Timeout: {dynamic_timeout:.0f}s)")
logger.info(f"[SHADOW] Created Maker {'BUY' if is_buy else 'SELL'} @ {shadow_price:.2f} (Timeout: {SHADOW_ORDER_TIMEOUT:.0f}s)")
except Exception as e:
logger.error(f"Shadow logic error: {e}")
else: