feat(hedger): implement Shadow Order simulator with dynamic timeout
This commit is contained in:
@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user