Files
poly-maker/tests/test_risk.py
T
Daniel Sapkota c7b686116e Getting there
2026-07-05 17:50:04 -04:00

87 lines
3.0 KiB
Python

"""Tests for the RiskManager gates and circuit breakers."""
from __future__ import annotations
from polymaker.config import RiskConfig
from polymaker.domain import Fill, Side
from polymaker.risk.manager import RiskManager
from polymaker.state.store import StateStore
def _rm(tmp_path, **over):
cfg = RiskConfig(**{
"max_total_exposure_usdc": 5000, "max_market_notional_usdc": 800,
"max_event_group_loss_usdc": 1000, "daily_loss_kill_usdc": 250,
**over,
})
store = StateStore(tmp_path / "s.db")
return RiskManager(cfg, store), store
def test_daily_loss_kill_switch(tmp_path, meta):
rm, store = _rm(tmp_path)
# buy 1000 shares @ 0.50 -> -500 cash, +1000 inventory
store.apply_fill(Fill(meta.yes.token_id, Side.BUY, 0.50, 1000, "t1"))
rm.note_fill(Fill(meta.yes.token_id, Side.BUY, 0.50, 1000, "t1"))
rm.update_mark(meta.yes.token_id, 0.50)
rm.reset_day()
assert rm.global_halt()[0] is False
# fair value collapses to 0.20 -> unrealized loss 300 > 250 kill
rm.update_mark(meta.yes.token_id, 0.20)
halted, why = rm.global_halt()
assert halted and "daily_loss" in why
store.close()
def test_market_cap_triggers_reduce_only(tmp_path, meta):
rm, store = _rm(tmp_path, max_market_notional_usdc=100)
store.apply_fill(Fill(meta.yes.token_id, Side.BUY, 0.50, 300, "t1")) # 150 notional > 100
rm.update_mark(meta.yes.token_id, 0.50)
rm.update_mark(meta.no.token_id, 0.50)
d = rm.evaluate(meta, ws_stale=False, event_group_cost=0.0)
assert d.reduce_only and d.reason == "market_cap"
store.close()
def test_ws_stale_halts_market(tmp_path, meta):
rm, store = _rm(tmp_path)
d = rm.evaluate(meta, ws_stale=True, event_group_cost=0.0)
assert d.halt and d.reason == "ws_stale"
store.close()
def test_size_scale_tapers_near_cap(tmp_path, meta):
rm, store = _rm(tmp_path, max_market_notional_usdc=100)
# 85 notional -> 85% of cap -> should scale below 1.0 but not reduce-only
store.apply_fill(Fill(meta.yes.token_id, Side.BUY, 0.50, 170, "t1")) # 85 notional
rm.update_mark(meta.yes.token_id, 0.50)
rm.update_mark(meta.no.token_id, 0.50)
d = rm.evaluate(meta, ws_stale=False, event_group_cost=0.0)
assert not d.reduce_only
assert 0.0 < d.size_scale < 1.0
store.close()
def test_event_group_cap(tmp_path, meta):
rm, store = _rm(tmp_path, max_event_group_loss_usdc=50)
d = rm.evaluate(meta, ws_stale=False, event_group_cost=60.0)
assert d.reduce_only and d.reason == "event_group_cap"
store.close()
def test_error_rate_breaker(tmp_path, meta):
rm, store = _rm(tmp_path, max_order_error_rate=0.25)
for _ in range(15):
rm.note_order_result(False)
for _ in range(10):
rm.note_order_result(True) # 15/25 = 0.6 > 0.25
assert rm.global_halt()[0] is True
store.close()
def test_manual_kill(tmp_path, meta):
rm, store = _rm(tmp_path)
rm.kill()
assert rm.global_halt() == (True, "manual_kill")
store.close()