feat(hedger): implement Shadow Order simulator with dynamic timeout

This commit is contained in:
2025-12-21 10:21:59 +01:00
parent 4b30f4a62b
commit 149800b426

View File

@ -346,6 +346,7 @@ class ScalperHedger:
# Order Tracking
self.original_order_side = None
self.shadow_orders = [] # Store theoretical Maker orders for analysis
logger.info(f"[DELTA] Delta-Zero Scalper Hedger initialized. Agent: {self.account.address}")
@ -500,6 +501,51 @@ class ScalperHedger:
return self.info.open_orders(self.vault_address or self.account.address)
except: return []
def check_shadow_orders(self, levels: Dict[str, Decimal]):
"""
Check if pending shadow (theoretical Maker) orders would have been filled.
"""
if not self.shadow_orders or not levels:
return
now = time.time()
remaining_orders = []
for order in self.shadow_orders:
# 1. Check Fill
filled = False
fill_time = now - (order['expires_at'] - order['timeout_duration'])
if order['side'] == 'BUY':
# Filled if someone SOLD into our Bid (Current Ask <= Our Bid Price)
# Wait... Maker Buy sits at Bid. It fills if Market Price drops to it.
# Actually, we need to track if TRADE price hit it.
# Proxy: If Current Best Ask <= Our Shadow Bid, it DEFINITELY filled (crossed).
# Conservative Proxy: If Current Best Bid < Our Shadow Bid? No.
# Standard Sim: If Low Price <= Our Limit.
# Here we only have snapshots.
# If 'levels["bid"]' goes below our price, did we fill? Maybe not.
# If 'levels["ask"]' goes below our price, we definitely filled.
if levels['ask'] <= order['price']:
filled = True
else: # SELL
# Filled if Current Best Bid >= Our Shadow Ask
if levels['bid'] >= order['price']:
filled = True
if filled:
logger.info(f"[SHADOW] ✅ SUCCESS: Maker {order['side']} @ {order['price']:.2f} filled in {fill_time:.1f}s (Timeout: {order['timeout_duration']:.0f}s)")
continue # Remove from list
# 2. Check Expiry
if now > order['expires_at']:
logger.info(f"[SHADOW] ❌ FAILED: Maker {order['side']} @ {order['price']:.2f} timed out after {order['timeout_duration']:.0f}s")
continue # Remove from list
remaining_orders.append(order)
self.shadow_orders = remaining_orders
def cancel_order(self, coin: str, oid: int):
logger.info(f"Cancelling order {oid}...")
try:
@ -704,6 +750,9 @@ class ScalperHedger:
time.sleep(0.1)
continue
# Check Shadow Orders (Market Maker Simulation)
self.check_shadow_orders(levels)
price = levels['mid']
pos_data = self.get_current_position(COIN_SYMBOL)
current_size = pos_data['size']
@ -836,6 +885,32 @@ class ScalperHedger:
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?"
try:
# Dynamic Timeout: Inverse to Volatility
# Low Vol (0.025%) -> 60s wait. High Vol (0.15%) -> 10s wait.
base_vol = Decimal("0.0005") # 0.05%
# Avoid div by zero
safe_vol = max(Decimal("0.0001"), vol_pct)
calc_timeout = float(30 * (base_vol / safe_vol))
dynamic_timeout = max(10.0, min(60.0, calc_timeout))
# 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
})
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:
if time.time() - self.last_idle_log_time > 30:
logger.info(f"[WAIT] Cooldown. Diff: {diff_abs:.4f}{cooldown_text}")