""" Deep Limit Order Book Strategy. Analyzes the full orderbook beyond top-of-book to detect: 1. **Wall detection** — large resting orders that indicate support/resistance 2. **Depth imbalance** — ratio of cumulative depth on bid vs ask side 3. **Orderbook skew** — asymmetry in volume distribution across price levels 4. **Thin-side prediction** — when one side of the book is thin, price likely moves that way When a large wall sits at a certain price level, the market is unlikely to break through it quickly. When the ask side is thin relative to the bid side, buying pressure is likely to push price up. Usage: from strategies.deep_lob import DeepLOB model = DeepLOB() signal = model.analyze(orderbook_bids, orderbook_asks, mark_price) """ import math from collections import deque class DeepLOB: """Analyze full orderbook depth for trade signals.""" def __init__(self, depth_levels: int = 10): self.depth_levels = depth_levels self.signal_history: deque = deque(maxlen=50) def analyze(self, bids: list, asks: list, mark_price: float) -> dict: """Analyze orderbook and return a signal. Args: bids: list of [price, size] pairs sorted best→worst (descending) asks: list of [price, size] pairs sorted best→worst (ascending) mark_price: current mark price Returns: dict with signal, strength, and metrics """ if not bids or not asks or mark_price <= 0: return {"signal": None, "strength": 0.0} # 1. Cumulative depth on each side bid_depth = sum(sz for _, sz in bids[:self.depth_levels]) ask_depth = sum(sz for _, sz in asks[:self.depth_levels]) # 2. Wall detection — find largest single order on each side bid_walls = sorted([(px, sz) for px, sz in bids[:self.depth_levels]], key=lambda x: x[1], reverse=True) ask_walls = sorted([(px, sz) for px, sz in asks[:self.depth_levels]], key=lambda x: x[1], reverse=True) largest_bid_wall = bid_walls[0] if bid_walls else (0, 0) largest_ask_wall = ask_walls[0] if ask_walls else (0, 0) # 3. Wall-to-depth ratio — how concentrated is the orderbook? bid_concentration = largest_bid_wall[1] / bid_depth if bid_depth > 0 else 0 ask_concentration = largest_ask_wall[1] / ask_depth if ask_depth > 0 else 0 # 4. Depth-weighted mid price (more accurate than top-of-book mid) # Weight prices by their size to get "fair value" bid_weighted = sum(px * sz for px, sz in bids[:self.depth_levels]) / bid_depth if bid_depth > 0 else 0 ask_weighted = sum(px * sz for px, sz in asks[:self.depth_levels]) / ask_depth if ask_depth > 0 else 0 fair_price = (bid_weighted + ask_weighted) / 2 if bid_weighted > 0 and ask_weighted > 0 else mark_price # 5. Depth imbalance ratio total_depth = bid_depth + ask_depth depth_imbalance = (bid_depth - ask_depth) / total_depth if total_depth > 0 else 0 # 6. Thin-side detection # If one side is much thinner, price likely moves that way depth_ratio = bid_depth / ask_depth if ask_depth > 0 else 999 thin_side = None if depth_ratio > 3.0: thin_side = "ask" # Ask side thin → buyers will push through → bullish elif depth_ratio < 0.33: thin_side = "bid" # Bid side thin → sellers will push through → bearish # 7. Mark price vs fair value fv_deviation = (mark_price - fair_price) / fair_price if fair_price > 0 else 0 # 8. Volume-weighted average spread vwap_spread = 0 for i in range(min(len(bids), len(asks), 5)): spread_i = asks[i][0] - bids[i][0] vwap_spread += spread_i avg_spread = vwap_spread / min(len(bids), len(asks), 5) if bids and asks else 0 # ═══════════════ Signal Generation ═══════════════ signal = None strength = 0.0 reasons = [] # Wall imbalance signal if largest_bid_wall[1] > 2 * largest_ask_wall[1]: strength += 0.3 reasons.append("large_bid_wall") elif largest_ask_wall[1] > 2 * largest_bid_wall[1]: strength -= 0.3 reasons.append("large_ask_wall") # Depth imbalance signal if depth_imbalance > 0.4: strength += 0.25 reasons.append("bid_depth_dominant") elif depth_imbalance < -0.4: strength -= 0.25 reasons.append("ask_depth_dominant") # Thin-side prediction if thin_side == "ask": strength += 0.35 reasons.append("thin_ask_pressure") elif thin_side == "bid": strength -= 0.35 reasons.append("thin_bid_pressure") # Fair value deviation if fv_deviation > 0.002: strength -= 0.15 # Overpriced relative to weighted depth reasons.append("overvalued") elif fv_deviation < -0.002: strength += 0.15 # Underpriced reasons.append("undervalued") # Determine final signal threshold = 0.3 if strength > threshold: signal = "BUY" elif strength < -threshold: signal = "SELL" self.signal_history.append({ "signal": signal, "strength": strength, "depth_imbalance": round(depth_imbalance, 4), "thin_side": thin_side, }) return { "signal": signal, "strength": round(abs(strength) if signal else strength, 4), "fair_price": round(fair_price, 1), "depth_imbalance": round(depth_imbalance, 4), "depth_ratio": round(depth_ratio, 2), "thin_side": thin_side, "bid_concentration": round(bid_concentration, 3), "ask_concentration": round(ask_concentration, 3), "avg_spread": round(avg_spread, 1), "largest_bid_wall": round(largest_bid_wall[1], 2), "largest_ask_wall": round(largest_ask_wall[1], 2), "reasons": reasons, }