feat(hedger): implement Shadow Order simulator with dynamic timeout
This commit is contained in:
@ -346,6 +346,7 @@ class ScalperHedger:
|
|||||||
|
|
||||||
# Order Tracking
|
# Order Tracking
|
||||||
self.original_order_side = None
|
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}")
|
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)
|
return self.info.open_orders(self.vault_address or self.account.address)
|
||||||
except: return []
|
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):
|
def cancel_order(self, coin: str, oid: int):
|
||||||
logger.info(f"Cancelling order {oid}...")
|
logger.info(f"Cancelling order {oid}...")
|
||||||
try:
|
try:
|
||||||
@ -704,6 +750,9 @@ class ScalperHedger:
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Check Shadow Orders (Market Maker Simulation)
|
||||||
|
self.check_shadow_orders(levels)
|
||||||
|
|
||||||
price = levels['mid']
|
price = levels['mid']
|
||||||
pos_data = self.get_current_position(COIN_SYMBOL)
|
pos_data = self.get_current_position(COIN_SYMBOL)
|
||||||
current_size = pos_data['size']
|
current_size = pos_data['size']
|
||||||
@ -836,6 +885,32 @@ class ScalperHedger:
|
|||||||
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 ---
|
||||||
|
# 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:
|
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