diff --git a/clp_hedger.py b/clp_hedger.py index 2ac2acb..c50279e 100644 --- a/clp_hedger.py +++ b/clp_hedger.py @@ -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,38 +917,47 @@ class ScalperHedger: if can_trade: 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" 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?" - 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 - }) - 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}") + # Only if we Taker trade, to see if Maker would have worked + if create_shadow: + try: + # Shadow Price (Passive) + 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': 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: {SHADOW_ORDER_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}")