malkhut(tests): 1140 test functions across 46 test files
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
This commit is contained in:
181
MALKHUT/malkhut/tests/test_counterparties.py
Normal file
181
MALKHUT/malkhut/tests/test_counterparties.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
Counterparty ecology — adversarial agent behavior.
|
||||
"""
|
||||
import random
|
||||
import pytest
|
||||
from malkhut.state import (
|
||||
AccountState, MarketWorldState, Mode, OrderBookState, PriceLevel,
|
||||
Side, TradePathState, VenueRules,
|
||||
)
|
||||
from malkhut.counterparties import (
|
||||
ToxicTakerPolicy, PassiveMakerPolicy, LatencyArbPolicy,
|
||||
NoiseTraderPolicy, default_counterparty_ecology,
|
||||
)
|
||||
from malkhut.actions import ActionKind, AgentRole
|
||||
|
||||
|
||||
def _state(**kw):
|
||||
tp_kw = kw.get("trade_path_kw", {})
|
||||
tp = TradePathState(
|
||||
symbol="BTCUSDT", side=Side.BUY, entry_ts_ns=0, now_ts_ns=100_000_000,
|
||||
bars_held=10, seconds_held=100.0, pnl_bps=0.0, mae_bps=-10.0,
|
||||
mfe_bps=15.0, distance_from_mfe_bps=15.0, distance_from_entry_bps=0.0,
|
||||
time_to_mfe_s=30.0, time_in_loss_s=50.0, time_in_profit_s=50.0,
|
||||
time_since_last_profit_s=10.0, time_since_deep_mae_s=20.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=kw.get("toxicity", 0.3),
|
||||
queue_churn_score=0.2, book_imbalance=0.1,
|
||||
cross_venue_lead_score=kw.get("lead", 0.1),
|
||||
) if kw.get("with_path", True) else None
|
||||
|
||||
return MarketWorldState(
|
||||
ts_ns=1_000_000_000, mode=Mode.REPLAY_NO_IMPACT,
|
||||
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_000_000_000, symbol="BTCUSDT",
|
||||
bids=(PriceLevel(50000.0, 1.0),), asks=(PriceLevel(50001.0, 1.0),),
|
||||
),
|
||||
account=AccountState(
|
||||
ts_ns=1_000_000_000, equity=10000.0, wallet_balance=10000.0,
|
||||
available_balance=10000.0, margin_used=0.0, total_notional=0.0,
|
||||
),
|
||||
trade_path=tp,
|
||||
)
|
||||
|
||||
|
||||
class TestToxicTaker:
|
||||
def test_noop_when_low_toxicity(self):
|
||||
p = ToxicTakerPolicy()
|
||||
s = _state(toxicity=0.3)
|
||||
rng = random.Random(42)
|
||||
a = p.rollout_action(s, rng)
|
||||
assert a.kind == ActionKind.NOOP
|
||||
|
||||
def test_cross_when_high_toxicity(self):
|
||||
p = ToxicTakerPolicy()
|
||||
s = _state(toxicity=0.9)
|
||||
rng = random.Random(42)
|
||||
a = p.rollout_action(s, rng)
|
||||
assert a.kind == ActionKind.CROSS_SPREAD
|
||||
|
||||
def test_always_has_legal_actions(self):
|
||||
p = ToxicTakerPolicy()
|
||||
s = _state()
|
||||
actions = p.legal_actions(s)
|
||||
assert len(actions) == 3
|
||||
|
||||
def test_role_is_toxic_taker(self):
|
||||
p = ToxicTakerPolicy()
|
||||
assert p.role == AgentRole.TOXIC_TAKER
|
||||
|
||||
def test_toxicity_field_set(self):
|
||||
p = ToxicTakerPolicy()
|
||||
s = _state(toxicity=0.9)
|
||||
rng = random.Random(42)
|
||||
a = p.rollout_action(s, rng)
|
||||
if a.kind == ActionKind.CROSS_SPREAD:
|
||||
assert a.toxicity > 0
|
||||
|
||||
|
||||
class TestPassiveMaker:
|
||||
def test_always_has_legal_actions(self):
|
||||
p = PassiveMakerPolicy()
|
||||
s = _state()
|
||||
actions = p.legal_actions(s)
|
||||
assert len(actions) == 4
|
||||
|
||||
def test_role_is_passive_maker(self):
|
||||
p = PassiveMakerPolicy()
|
||||
assert p.role == AgentRole.PASSIVE_MAKER
|
||||
|
||||
def test_rollout_can_place(self):
|
||||
p = PassiveMakerPolicy(join_probability=1.0)
|
||||
s = _state()
|
||||
rng = random.Random(42)
|
||||
a = p.rollout_action(s, rng)
|
||||
assert a.kind == ActionKind.PLACE
|
||||
|
||||
def test_rollout_can_noop(self):
|
||||
p = PassiveMakerPolicy(join_probability=0.0)
|
||||
s = _state()
|
||||
rng = random.Random(42)
|
||||
a = p.rollout_action(s, rng)
|
||||
assert a.kind == ActionKind.NOOP
|
||||
|
||||
def test_deterministic_with_same_seed(self):
|
||||
p = PassiveMakerPolicy()
|
||||
s = _state()
|
||||
a1 = p.rollout_action(s, random.Random(99))
|
||||
a2 = p.rollout_action(s, random.Random(99))
|
||||
assert a1.kind == a2.kind
|
||||
|
||||
|
||||
class TestLatencyArb:
|
||||
def test_noop_when_low_lead(self):
|
||||
p = LatencyArbPolicy()
|
||||
s = _state(lead=0.3)
|
||||
rng = random.Random(42)
|
||||
a = p.rollout_action(s, rng)
|
||||
assert a.kind == ActionKind.NOOP
|
||||
|
||||
def test_cross_when_high_lead(self):
|
||||
p = LatencyArbPolicy()
|
||||
s = _state(lead=0.8)
|
||||
rng = random.Random(42)
|
||||
a = p.rollout_action(s, rng)
|
||||
assert a.kind == ActionKind.CROSS_SPREAD
|
||||
|
||||
def test_always_has_legal_actions(self):
|
||||
p = LatencyArbPolicy()
|
||||
s = _state()
|
||||
actions = p.legal_actions(s)
|
||||
assert len(actions) == 3
|
||||
|
||||
|
||||
class TestNoiseTrader:
|
||||
def test_always_has_legal_actions(self):
|
||||
p = NoiseTraderPolicy()
|
||||
s = _state()
|
||||
actions = p.legal_actions(s)
|
||||
assert len(actions) == 3
|
||||
|
||||
def test_role_is_noise_trader(self):
|
||||
p = NoiseTraderPolicy()
|
||||
assert p.role == AgentRole.NOISE_TRADER
|
||||
|
||||
def test_rollout_can_cross(self):
|
||||
p = NoiseTraderPolicy()
|
||||
s = _state()
|
||||
rng = random.Random(42)
|
||||
crosses = 0
|
||||
for seed in range(100):
|
||||
a = p.rollout_action(s, random.Random(seed))
|
||||
if a.kind == ActionKind.CROSS_SPREAD:
|
||||
crosses += 1
|
||||
assert crosses > 0
|
||||
|
||||
|
||||
class TestDefaultEcology:
|
||||
def test_has_four_agents(self):
|
||||
eco = default_counterparty_ecology()
|
||||
assert len(eco) == 4
|
||||
|
||||
def test_unique_roles(self):
|
||||
eco = default_counterparty_ecology()
|
||||
roles = [p.role for p in eco]
|
||||
assert len(set(roles)) == 4
|
||||
|
||||
def test_all_have_legal_actions(self):
|
||||
eco = default_counterparty_ecology()
|
||||
s = _state()
|
||||
for p in eco:
|
||||
actions = p.legal_actions(s)
|
||||
assert len(actions) > 0
|
||||
Reference in New Issue
Block a user