639dd4fb6d
New sim/ module — 7 files + init, replays stored L2/trade data
through a realistic market-making simulation:
sim/engine.py (SimulationEngine):
Event-driven core — processes L2 updates, trades, mark prices
sequentially. Orchestrates queue model, maker quotes, fill sim,
constraints, scenarios. Supports periodic re-quoting and
stale order cancellation.
sim/queue.py (QueueModel):
Price-time FIFO queue per price level. Tracks where maker orders
sit in queue. Simulates order eating by aggressor trades.
fill_probability() — Poisson thinning model for fill odds.
sim/maker.py:
AvellanedaStoikovMaker — stochastic control quoting with
aeta, k, tau parameters. Reservation price based on inventory.
quote() and quote_with_skew() with configurable inventory tilt.
GridMaker — evenly-spaced grid quoting at N levels.
sim/fills.py:
FillSimulator — partial fills, adverse selection probability,
cancel latency (gaussian RTT). FillEvent/CancelEvent tracking.
adverse_selection_intensity() — measures post-fill price moves.
sim/constraints.py:
InventoryConstraint — long/short/net/gross position limits.
FundingConstraint — hourly funding cost estimation.
FeeSchedule — maker/taker fee calculation.
LiquidationRisk — liquidation price and safety distance.
CircuitBreaker — PnL, trade count, toxic rate, slippage trips.
ConstraintManager — unified pre-trade constraint check.
sim/scenario.py:
ScenarioEngine — randomized exchange downtimes, latency spikes,
volatility bursts. State query per sim_time for spread/trade-rate.
sim/reporter.py:
PnLReporter — component-level PnL breakdown:
spread_capture, inventory_pnl, fees, funding, adverse_selection.
SimulationStats — trade counts, fill rates, drawdown, sharpe.
Equity curve tracking and max drawdown computation.
53 new tests across 4 files (all pass):
test_sim_queue.py (12) — order placement, FIFO, fills, cancels
test_sim_maker.py (9) — A-S quotes, inventory skew, grid maker
test_sim_constraints.py (14) — limits, funding, fees, liquidation, breakers
test_sim_reporter.py (12) — PnL components, equity curve, stats
test_sim_engine.py (6) — full engine integration
Total test suite: 134 tests, all passing.
70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
"""
|
|
Tests for sim/maker.py — market-making quoting logic.
|
|
"""
|
|
from sim.maker import AvellanedaStoikovMaker, GridMaker, MakerConfig, Quote
|
|
|
|
|
|
class TestAvellanedaStoikovMaker:
|
|
def test_initial_sigma(self):
|
|
maker = AvellanedaStoikovMaker()
|
|
assert maker.sigma == 0.02 # default
|
|
|
|
def test_observe_updates_volatility(self):
|
|
maker = AvellanedaStoikovMaker()
|
|
for i in range(20):
|
|
maker.observe(100000.0 + i * 100)
|
|
assert maker.sigma > 0
|
|
|
|
def test_quote_no_inventory(self):
|
|
maker = AvellanedaStoikovMaker()
|
|
maker.observe(100000.0)
|
|
q = maker.quote(mid_price=100000.0, inventory=0.0, elapsed_hours=0.0)
|
|
assert q.bid > 0
|
|
assert q.ask > q.bid
|
|
assert q.spread_bps >= 0
|
|
|
|
def test_quote_short_inventory_skew(self):
|
|
maker = AvellanedaStoikovMaker(MakerConfig(gamma=0.5, k=1.0))
|
|
for i in range(50):
|
|
maker.observe(100000.0 + i * 50 * (1 if i % 2 == 0 else -1))
|
|
q_short = maker.quote(mid_price=100000.0, inventory=-0.003, elapsed_hours=0.0)
|
|
q_long = maker.quote(mid_price=100000.0, inventory=0.003, elapsed_hours=0.0)
|
|
assert q_short.reservation > q_long.reservation
|
|
|
|
def test_quote_long_inventory_skew(self):
|
|
maker = AvellanedaStoikovMaker()
|
|
maker.observe(100000.0)
|
|
q = maker.quote(mid_price=100000.0, inventory=0.004, elapsed_hours=0.1)
|
|
assert q.reservation < q.ask
|
|
|
|
def test_quote_with_skew(self):
|
|
maker = AvellanedaStoikovMaker()
|
|
maker.observe(100000.0)
|
|
q = maker.quote_with_skew(mid_price=100000.0, inventory=0.003, elapsed_hours=0.0, target_inventory=0.0)
|
|
assert q.ask_size > q.bid_size # long → bigger asks to reduce position
|
|
|
|
def test_elapsed_reduces_spread(self):
|
|
maker = AvellanedaStoikovMaker()
|
|
maker.observe(100000.0)
|
|
q_early = maker.quote(mid_price=100000.0, inventory=0.0, elapsed_hours=0.0)
|
|
q_late = maker.quote(mid_price=100000.0, inventory=0.0, elapsed_hours=0.9)
|
|
# Near session end → narrower spread (less time to recover)
|
|
assert q_late.spread_bps <= q_early.spread_bps + 1
|
|
|
|
|
|
class TestGridMaker:
|
|
def test_grid_levels(self):
|
|
gm = GridMaker(grid_levels=3, spacing_bps=10.0, size_per_level=0.001)
|
|
quotes = gm.quotes(100000.0)
|
|
assert len(quotes) == 6 # 3 bid + 3 ask levels
|
|
|
|
def test_symmetric(self):
|
|
gm = GridMaker(grid_levels=2, spacing_bps=50.0, size_per_level=0.001)
|
|
quotes = gm.quotes(100000.0)
|
|
bids = [q for q in quotes if q["side"] == "bid"]
|
|
asks = [q for q in quotes if q["side"] == "ask"]
|
|
assert len(bids) == 2
|
|
assert len(asks) == 2
|
|
for b, a in zip(bids[::-1], asks):
|
|
assert a["price"] > b["price"]
|