Files

153 lines
6.1 KiB
Python

"""
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,
}