b59dcc3629
Set up the directory structure and wrote placeholder logic for: - Order Book Imbalance: trades on L2 bid/ask skew - Iceberg/TWAP detection: follows whale accumulation patterns - Funding rate arbitrage: delta-neutral carry on perp funding - Pairs trading: BTC/ETH spread mean reversion - Avellaneda-Stoikov market making: optimal bid/ask quoting Also added shared risk manager, portfolio tracker, and a plain-language strategy walkthrough in docs/.
77 lines
2.5 KiB
Python
77 lines
2.5 KiB
Python
"""
|
|
Order Book Imbalance strategy.
|
|
|
|
Enters positions when bid/ask volume at the top of the order book
|
|
shows a significant directional skew. The idea: when one side of
|
|
the book is much heavier, price tends to move toward the thinner
|
|
side as the heavy side absorbs market orders.
|
|
"""
|
|
from nautilus_trader.trading.strategy import Strategy
|
|
from nautilus_trader.config import StrategyConfig
|
|
|
|
|
|
class OrderBookImbalanceConfig(StrategyConfig, frozen=True):
|
|
instrument_id: str
|
|
depth: int = 10
|
|
imbalance_threshold: float = 0.6
|
|
trade_size: float = 0.001
|
|
max_position: float = 0.003
|
|
cooldown_bars: int = 5
|
|
|
|
|
|
class OrderBookImbalance(Strategy):
|
|
"""
|
|
Trades on L2 order book imbalance.
|
|
|
|
- imbalance > threshold -> bid side heavy -> buy
|
|
- imbalance < 1-threshold -> ask side heavy -> sell
|
|
"""
|
|
|
|
def __init__(self, config: OrderBookImbalanceConfig) -> None:
|
|
super().__init__(config)
|
|
self.config = config
|
|
self.bars_since_last_trade = 0
|
|
|
|
def on_start(self) -> None:
|
|
self.subscribe_order_book_deltas(
|
|
self.config.instrument_id,
|
|
depth=self.config.depth,
|
|
)
|
|
self.log.info(
|
|
f"OFI started on {self.config.instrument_id} "
|
|
f"(depth={self.config.depth})"
|
|
)
|
|
|
|
def on_order_book_deltas(self, deltas) -> None:
|
|
self.bars_since_last_trade += 1
|
|
if self.bars_since_last_trade < self.config.cooldown_bars:
|
|
return
|
|
|
|
book = self.cache.order_book(self.config.instrument_id)
|
|
if not book or len(book.bids) == 0 or len(book.asks) == 0:
|
|
return
|
|
|
|
depth = min(self.config.depth, len(book.bids), len(book.asks))
|
|
bid_vol = sum(book.bids[i].size for i in range(depth))
|
|
ask_vol = sum(book.asks[i].size for i in range(depth))
|
|
total = bid_vol + ask_vol
|
|
if total == 0:
|
|
return
|
|
|
|
imbalance = bid_vol / total
|
|
pos = self.portfolio.net_position(self.config.instrument_id)
|
|
|
|
if imbalance > self.config.imbalance_threshold and pos <= 0:
|
|
self._enter("BUY")
|
|
self.bars_since_last_trade = 0
|
|
elif imbalance < (1 - self.config.imbalance_threshold) and pos >= 0:
|
|
self._enter("SELL")
|
|
self.bars_since_last_trade = 0
|
|
|
|
def _enter(self, side: str) -> None:
|
|
self.submit_order(self.order_factory.market(
|
|
instrument_id=self.config.instrument_id,
|
|
order_side=side,
|
|
quantity=self.config.trade_size,
|
|
))
|