feat(hedger): implement Maker-First strategy with 400s timeout and edge-aware Taker fallback
This commit is contained in:
@ -96,6 +96,9 @@ POSITION_OPEN_EDGE_PROXIMITY_PCT = Decimal("0.06")
|
|||||||
POSITION_CLOSED_EDGE_PROXIMITY_PCT = Decimal("0.025")
|
POSITION_CLOSED_EDGE_PROXIMITY_PCT = Decimal("0.025")
|
||||||
LARGE_HEDGE_MULTIPLIER = Decimal("2.8")
|
LARGE_HEDGE_MULTIPLIER = Decimal("2.8")
|
||||||
|
|
||||||
|
MAKER_ORDER_TIMEOUT = 400
|
||||||
|
SHADOW_ORDER_TIMEOUT = 600
|
||||||
|
|
||||||
# --- HELPER FUNCTIONS ---
|
# --- HELPER FUNCTIONS ---
|
||||||
|
|
||||||
def to_decimal(value: Any) -> Decimal:
|
def to_decimal(value: Any) -> Decimal:
|
||||||
@ -630,6 +633,14 @@ class ScalperHedger:
|
|||||||
oid = order['oid']
|
oid = order['oid']
|
||||||
order_price = to_decimal(order['limitPx'])
|
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
|
# Check if price moved too far
|
||||||
levels = self.get_order_book_levels(COIN_SYMBOL)
|
levels = self.get_order_book_levels(COIN_SYMBOL)
|
||||||
if not levels: return True # Keep order if data missing
|
if not levels: return True # Keep order if data missing
|
||||||
@ -906,38 +917,47 @@ class ScalperHedger:
|
|||||||
|
|
||||||
if can_trade:
|
if can_trade:
|
||||||
is_buy = (calc['action'] == "BUY")
|
is_buy = (calc['action'] == "BUY")
|
||||||
# Taker execution for rebalance
|
|
||||||
exec_price = levels['ask'] * Decimal("1.001") if is_buy else levels['bid'] * Decimal("0.999")
|
|
||||||
|
|
||||||
|
# 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"
|
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}%")
|
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:
|
if oid:
|
||||||
self.last_trade_time = time.time()
|
self.last_trade_time = time.time()
|
||||||
self.track_fills_and_pnl(force=True)
|
self.track_fills_and_pnl(force=True)
|
||||||
|
|
||||||
# --- Shadow Order Creation ---
|
# --- Shadow Order Creation ---
|
||||||
# Simulate: "What if we placed a Maker order instead?"
|
# Only if we Taker trade, to see if Maker would have worked
|
||||||
try:
|
if create_shadow:
|
||||||
# Fixed Timeout for Data Collection (10 min)
|
try:
|
||||||
# We want to capture the full distribution of fill times.
|
# Shadow Price (Passive)
|
||||||
dynamic_timeout = 600.0
|
shadow_price = levels['bid'] if is_buy else levels['ask']
|
||||||
|
|
||||||
# Shadow Price (Passive)
|
self.shadow_orders.append({
|
||||||
# If we Taker BUY, we would have Maker BUY at BID
|
'side': 'BUY' if is_buy else 'SELL',
|
||||||
# If we Taker SELL, we would have Maker SELL at ASK
|
'price': shadow_price,
|
||||||
shadow_price = levels['bid'] if is_buy else levels['ask']
|
'timeout_duration': SHADOW_ORDER_TIMEOUT,
|
||||||
|
'expires_at': time.time() + SHADOW_ORDER_TIMEOUT
|
||||||
self.shadow_orders.append({
|
})
|
||||||
'side': 'BUY' if is_buy else 'SELL',
|
logger.info(f"[SHADOW] Created Maker {'BUY' if is_buy else 'SELL'} @ {shadow_price:.2f} (Timeout: {SHADOW_ORDER_TIMEOUT:.0f}s)")
|
||||||
'price': shadow_price,
|
except Exception as e:
|
||||||
'timeout_duration': dynamic_timeout,
|
logger.error(f"Shadow logic error: {e}")
|
||||||
'expires_at': time.time() + dynamic_timeout
|
|
||||||
})
|
|
||||||
logger.info(f"[SHADOW] Created Maker {'BUY' if is_buy else 'SELL'} @ {shadow_price:.2f} (Timeout: {dynamic_timeout:.0f}s)")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Shadow logic error: {e}")
|
|
||||||
else:
|
else:
|
||||||
if time.time() - self.last_idle_log_time > 30:
|
if time.time() - self.last_idle_log_time > 30:
|
||||||
logger.info(f"[WAIT] Cooldown. Diff: {diff_abs:.4f}{cooldown_text}")
|
logger.info(f"[WAIT] Cooldown. Diff: {diff_abs:.4f}{cooldown_text}")
|
||||||
|
|||||||
Reference in New Issue
Block a user