CWM (103): core mechanics, exhaustive edge cases, numba, exchange mechanics Replay (118): exhaustive verification, microstructure, trajectory Training (190): asset classification, phase0 extensive, pipeline, exhaustive DSL (102): v2 syntax, expanded, new features ASEx (33): validate-before-mutate, single-writer Planner (48): MCTS, alternatives, hooks Counterparties (19): 9 adversarial agent policies Clock (30): event-driven reactor BingX (28): venue adapter IPC (8): Zinc SHM Storage (9): ClickHouse Risk (4): hard invariants State (17): frozen dataclass invariants Integration: E2E, concurrency, sync/async seams, hypothesis, fuzz, adversarial
171 lines
6.8 KiB
Python
171 lines
6.8 KiB
Python
"""
|
|
State invariants — frozen dataclasses, immutability, serialization.
|
|
"""
|
|
import json
|
|
import pytest
|
|
from malkhut.state import (
|
|
AccountState, ExecutionIntent, FulfilmentPolicyParams, IntentKind,
|
|
MarketWorldState, Mode, OpenOrderState, OrderBookState, PositionState,
|
|
PriceLevel, Side, TradePathState, VenueRules,
|
|
)
|
|
from malkhut.actions import (
|
|
ActionKind, CounterpartyAction, FulfilmentAction, OrderType,
|
|
PlannedPolicy, RiskDecision,
|
|
)
|
|
|
|
|
|
class TestFrozenInvariants:
|
|
def test_venue_rules_immutable(self):
|
|
v = VenueRules(
|
|
exchange="bingx", symbol="BTCUSDT", tick_size=0.1, lot_size=0.001,
|
|
min_qty=0.001, min_notional=5.0, maker_fee_bps=-0.2, taker_fee_bps=0.5,
|
|
post_only_supported=True, reduce_only_supported=True,
|
|
max_orders_per_second=100, max_cancels_per_minute=120,
|
|
)
|
|
with pytest.raises(AttributeError):
|
|
v.symbol = "ETHUSDT"
|
|
|
|
def test_price_level_immutable(self):
|
|
p = PriceLevel(price=50000.0, qty=1.0)
|
|
with pytest.raises(AttributeError):
|
|
p.price = 51000.0
|
|
|
|
def test_order_book_immutable(self):
|
|
b = OrderBookState(
|
|
ts_ns=1, symbol="BTCUSDT",
|
|
bids=(PriceLevel(50000.0, 1.0),),
|
|
asks=(PriceLevel(50001.0, 1.0),),
|
|
)
|
|
with pytest.raises(AttributeError):
|
|
b.bids = ()
|
|
|
|
def test_account_state_immutable(self):
|
|
a = AccountState(ts_ns=1, equity=10000.0, wallet_balance=10000.0,
|
|
available_balance=10000.0, margin_used=0.0, total_notional=0.0)
|
|
with pytest.raises(AttributeError):
|
|
a.equity = 20000.0
|
|
|
|
def test_fulfilment_action_immutable(self):
|
|
a = FulfilmentAction(ActionKind.PLACE, Side.BUY, OrderType.LIMIT, 0, 0.1, 200)
|
|
with pytest.raises(AttributeError):
|
|
a.qty_fraction = 0.5
|
|
|
|
def test_market_world_state_immutable(self):
|
|
s = MarketWorldState(
|
|
ts_ns=1, mode=Mode.LIVE,
|
|
venue=VenueRules(exchange="bingx", symbol="BTCUSDT", tick_size=0.1, lot_size=0.001,
|
|
min_qty=0.001, min_notional=5.0, maker_fee_bps=-0.2, taker_fee_bps=0.5,
|
|
post_only_supported=True, reduce_only_supported=True,
|
|
max_orders_per_second=100, max_cancels_per_minute=120),
|
|
book=OrderBookState(ts_ns=1, symbol="BTCUSDT",
|
|
bids=(PriceLevel(50000.0, 1.0),),
|
|
asks=(PriceLevel(50001.0, 1.0),)),
|
|
account=AccountState(ts_ns=1, equity=10000.0, wallet_balance=10000.0,
|
|
available_balance=10000.0, margin_used=0.0, total_notional=0.0),
|
|
)
|
|
with pytest.raises(AttributeError):
|
|
s.ts_ns = 2
|
|
|
|
|
|
class TestOrderBookProperties:
|
|
def test_best_bid_highest(self):
|
|
b = OrderBookState(
|
|
ts_ns=1, symbol="BTCUSDT",
|
|
bids=(PriceLevel(50000.0, 1.0), PriceLevel(49999.0, 2.0)),
|
|
asks=(PriceLevel(50001.0, 1.0),),
|
|
)
|
|
assert b.best_bid == 50000.0
|
|
|
|
def test_best_ask_lowest(self):
|
|
b = OrderBookState(
|
|
ts_ns=1, symbol="BTCUSDT",
|
|
bids=(PriceLevel(50000.0, 1.0),),
|
|
asks=(PriceLevel(50001.0, 1.0), PriceLevel(50002.0, 2.0)),
|
|
)
|
|
assert b.best_ask == 50001.0
|
|
|
|
def test_mid_calculation(self):
|
|
b = OrderBookState(
|
|
ts_ns=1, symbol="BTCUSDT",
|
|
bids=(PriceLevel(50000.0, 1.0),),
|
|
asks=(PriceLevel(50002.0, 1.0),),
|
|
)
|
|
assert b.mid == 50001.0
|
|
|
|
def test_spread_calculation(self):
|
|
b = OrderBookState(
|
|
ts_ns=1, symbol="BTCUSDT",
|
|
bids=(PriceLevel(50000.0, 1.0),),
|
|
asks=(PriceLevel(50001.0, 1.0),),
|
|
)
|
|
assert b.spread == 1.0
|
|
|
|
def test_spread_bps(self):
|
|
b = OrderBookState(
|
|
ts_ns=1, symbol="BTCUSDT",
|
|
bids=(PriceLevel(50000.0, 1.0),),
|
|
asks=(PriceLevel(50001.0, 1.0),),
|
|
)
|
|
expected_bps = 10_000.0 * 1.0 / 50000.5
|
|
assert abs(b.spread_bps - expected_bps) < 1e-6
|
|
|
|
|
|
class TestEnums:
|
|
def test_side_values(self):
|
|
assert Side.BUY.value == "BUY"
|
|
assert Side.SELL.value == "SELL"
|
|
|
|
def test_action_kind_values(self):
|
|
assert ActionKind.NOOP.value == "NOOP"
|
|
assert ActionKind.PLACE.value == "PLACE"
|
|
|
|
def test_intent_kind_values(self):
|
|
assert IntentKind.ENTER_LONG.value == "ENTER_LONG"
|
|
|
|
def test_order_type_values(self):
|
|
assert OrderType.LIMIT.value == "LIMIT"
|
|
assert OrderType.POST_ONLY.value == "POST_ONLY"
|
|
|
|
|
|
class TestTradePathState:
|
|
def test_frozen(self):
|
|
tp = TradePathState(
|
|
symbol="BTCUSDT", side=Side.BUY, entry_ts_ns=0, now_ts_ns=100,
|
|
bars_held=5, seconds_held=50.0, pnl_bps=10.0, mae_bps=-5.0,
|
|
mfe_bps=15.0, distance_from_mfe_bps=5.0, distance_from_entry_bps=10.0,
|
|
time_to_mfe_s=20.0, time_in_loss_s=10.0, time_in_profit_s=40.0,
|
|
time_since_last_profit_s=5.0, time_since_deep_mae_s=15.0,
|
|
loss_to_profit_transitions=1, deep_loss_recoveries=0,
|
|
failed_recovery_count=0, recovery_velocity_bps_per_s=1.0,
|
|
adverse_velocity_bps_per_s=-0.5,
|
|
dolphin_regime_score=0.5, jericho_signal_strength=0.3,
|
|
volatility_bps=15.0, orderflow_toxicity=0.3,
|
|
queue_churn_score=0.2, book_imbalance=0.1, cross_venue_lead_score=0.1,
|
|
)
|
|
with pytest.raises(AttributeError):
|
|
tp.pnl_bps = 20.0
|
|
|
|
|
|
class TestFulfilmentPolicyParams:
|
|
def test_frozen(self):
|
|
p = FulfilmentPolicyParams(
|
|
version="v1", ucb_c=1.414, max_sims=256, max_depth=3,
|
|
rollout_depth=3, root_temperature=0.5, min_root_entropy=0.25,
|
|
quote_offsets_ticks=(0, 1, 2), quote_size_fractions=(0.1, 0.25, 0.5),
|
|
passive_ttl_ms=200, aggressive_ttl_ms=50,
|
|
maker_edge_min_bps=0.5, cross_spread_edge_min_bps=5.0,
|
|
adverse_toxicity_cancel_threshold=0.5, queue_churn_cancel_threshold=0.5,
|
|
mae_tail_cut_bps=50.0, mfe_giveback_cut_fraction=0.5,
|
|
max_time_in_loss_s=300.0, failed_recovery_cut_count=3,
|
|
recovery_velocity_min_bps_per_s=0.0,
|
|
max_symbol_notional_fraction=0.20, max_single_order_notional_fraction=0.05,
|
|
reduce_when_global_up_fraction=0.30, session_profit_lock_fraction=0.02,
|
|
w_expected_pnl=1.0, w_fill_probability=0.5, w_adverse_selection=2.0,
|
|
w_queue_priority=0.5, w_inventory_risk=1.5, w_tail_loss=5.0,
|
|
w_fee_quality=0.5, w_time_decay=0.3, w_policy_entropy=0.5,
|
|
robust_tail_weight=2.0, toxic_counterparty_weight=3.0,
|
|
low_liquidity_weight=2.0, latency_stress_weight=1.0,
|
|
)
|
|
with pytest.raises(AttributeError):
|
|
p.version = "v2"
|