malkhut(scoring): fast scalar + advantage mode, reward execution quality
Fast scalar mode (default, for CMA loop): - Rewards: fill quality (PnL when fills happen), moderate fill rate (5-15% sweet spot) - Tolerates: no-fills (valid advisory recommendation) - Penalizes: extreme fill rates (<3% lazy, >30% picked off), adverse selection, drawdown - Light noop penalty (-0.5) vs old heavy (-50) — no-fills are valid signals Advantage mode (for offline analysis): - advantage = raw_performance - baseline_performance - baseline = exponential moving average (decay=0.995) - Clipped to [-10, +10] - Reduces score variance 5.5x vs raw scoring Scoring mode selection: PolicyEvaluator(scoring_mode='fast') — default for CMA loop PolicyEvaluator(scoring_mode='advantage') — for offline analysis 8 new tests for scoring modes. Total: 1186 tests, 50 files, all green.
This commit is contained in:
116
MALKHUT/malkhut/tests/test_scoring_modes.py
Normal file
116
MALKHUT/malkhut/tests/test_scoring_modes.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Tests for scoring modes: fast scalar + advantage estimation.
|
||||
"""
|
||||
import pytest
|
||||
from malkhut.training.cma_trainer import (
|
||||
ScenarioFactory, PolicyEvaluator, EpisodeResult
|
||||
)
|
||||
from malkhut.cwm.core import MinimalCryptoLOBCWM
|
||||
from malkhut.state import FulfilmentPolicyParams
|
||||
|
||||
|
||||
def _make_params(max_sims=16):
|
||||
return FulfilmentPolicyParams(
|
||||
version='test', ucb_c=1.414, max_sims=max_sims, max_depth=2,
|
||||
rollout_depth=2, root_temperature=0.5, min_root_entropy=0.25,
|
||||
quote_offsets_ticks=(0, 1), quote_size_fractions=(0.25, 0.50),
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
class TestFastScoring:
|
||||
def test_fast_mode_default(self):
|
||||
evaluator = PolicyEvaluator(cwm_factory=MinimalCryptoLOBCWM)
|
||||
assert evaluator._scoring_mode == "fast"
|
||||
|
||||
def test_advantage_mode_init(self):
|
||||
evaluator = PolicyEvaluator(cwm_factory=MinimalCryptoLOBCWM, scoring_mode="advantage")
|
||||
assert evaluator._scoring_mode == "advantage"
|
||||
|
||||
def test_fast_score_with_fills(self):
|
||||
evaluator = PolicyEvaluator(cwm_factory=MinimalCryptoLOBCWM)
|
||||
factory = ScenarioFactory()
|
||||
suite = factory.build_suite(symbols=('BTCUSDT',), steps_per_scenario=5)
|
||||
params = _make_params()
|
||||
score, results = evaluator.evaluate_candidate(
|
||||
params=params, scenarios=suite[:5], rng_seed=42, workers=0)
|
||||
assert isinstance(score, float)
|
||||
assert score > -1000
|
||||
|
||||
def test_fast_score_empty_results(self):
|
||||
evaluator = PolicyEvaluator(cwm_factory=MinimalCryptoLOBCWM)
|
||||
score = evaluator._robust_score([], _make_params())
|
||||
assert score == -1000.0
|
||||
|
||||
def test_fast_score_bounded(self):
|
||||
evaluator = PolicyEvaluator(cwm_factory=MinimalCryptoLOBCWM)
|
||||
factory = ScenarioFactory()
|
||||
suite = factory.build_suite(symbols=('BTCUSDT', 'ETHUSDT'), steps_per_scenario=5)
|
||||
params = _make_params()
|
||||
scores = []
|
||||
for i in range(20):
|
||||
score, _ = evaluator.evaluate_candidate(
|
||||
params=params, scenarios=suite[:5], rng_seed=i, workers=0)
|
||||
scores.append(score)
|
||||
for s in scores:
|
||||
assert isinstance(s, float)
|
||||
assert s > -10000 # no NaN or inf
|
||||
|
||||
|
||||
class TestAdvantageScoring:
|
||||
def test_advantage_mode(self):
|
||||
evaluator = PolicyEvaluator(
|
||||
cwm_factory=MinimalCryptoLOBCWM, scoring_mode="advantage")
|
||||
factory = ScenarioFactory()
|
||||
suite = factory.build_suite(symbols=('BTCUSDT',), steps_per_scenario=5)
|
||||
params = _make_params()
|
||||
score, results = evaluator.evaluate_candidate(
|
||||
params=params, scenarios=suite[:3], rng_seed=42, workers=0)
|
||||
assert isinstance(score, float)
|
||||
assert -10.0 <= score <= 10.0
|
||||
|
||||
def test_advantage_baseline_evolution(self):
|
||||
evaluator = PolicyEvaluator(
|
||||
cwm_factory=MinimalCryptoLOBCWM, scoring_mode="advantage")
|
||||
factory = ScenarioFactory()
|
||||
suite = factory.build_suite(symbols=('BTCUSDT',), steps_per_scenario=3)
|
||||
params = _make_params()
|
||||
baselines = []
|
||||
for i in range(10):
|
||||
evaluator.evaluate_candidate(
|
||||
params=params, scenarios=suite[:2], rng_seed=i, workers=0)
|
||||
baselines.append(evaluator._adv_baseline)
|
||||
# Baseline should converge (not oscillate wildly)
|
||||
last_5 = baselines[-5:]
|
||||
assert max(last_5) - min(last_5) < abs(baselines[0]) + 1.0
|
||||
|
||||
|
||||
class TestScoringModeIntegration:
|
||||
def test_evaluate_candidate_with_mode(self):
|
||||
factory = ScenarioFactory()
|
||||
suite = factory.build_suite(symbols=('BTCUSDT',), steps_per_scenario=3)
|
||||
params = _make_params()
|
||||
|
||||
# Fast mode
|
||||
ev_fast = PolicyEvaluator(cwm_factory=MinimalCryptoLOBCWM, scoring_mode="fast")
|
||||
score_f, _ = ev_fast.evaluate_candidate(params=params, scenarios=suite[:2], rng_seed=0, workers=0)
|
||||
|
||||
# Advantage mode
|
||||
ev_adv = PolicyEvaluator(cwm_factory=MinimalCryptoLOBCWM, scoring_mode="advantage")
|
||||
score_a, _ = ev_adv.evaluate_candidate(params=params, scenarios=suite[:2], rng_seed=0, workers=0)
|
||||
|
||||
# Both should produce valid scores
|
||||
assert isinstance(score_f, float)
|
||||
assert isinstance(score_a, float)
|
||||
Reference in New Issue
Block a user