Add doctrine playbook backtest cohorts

This commit is contained in:
Codex
2026-05-21 20:19:31 +08:00
parent ebb1b12b0a
commit dfef7b951c
22 changed files with 868 additions and 58 deletions
+16
View File
@@ -134,6 +134,22 @@ class ApiTests(unittest.TestCase):
self.assertEqual(payload["phase"], "D1_MARKET_LANGUAGE_KERNEL")
self.assertEqual(payload["rules"][0]["rule_id"], "range_defined")
def test_route_request_trading_doctrine_playbooks_returns_d2_registry(self) -> None:
status, payload = route_request(
"/trading/doctrine-playbooks",
{},
dependencies={
"trading_doctrine_playbooks": lambda: {
"phase": "D2_PLAYBOOK_BACKTEST_COHORT",
"playbooks": [{"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL"}],
}
},
)
self.assertEqual(status, 200)
self.assertEqual(payload["phase"], "D2_PLAYBOOK_BACKTEST_COHORT")
self.assertEqual(payload["playbooks"][0]["setup_playbook_id"], "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL")
def test_route_request_trading_candidates_returns_items_payload(self) -> None:
status, payload = route_request(
"/trading/candidates",
+16
View File
@@ -102,6 +102,22 @@ class ApiServerRoutingTests(unittest.TestCase):
self.assertEqual(payload["latest"]["id"], 7)
self.assertTrue(payload["quality_gate"]["passed"])
def test_routes_trading_doctrine_playbooks(self) -> None:
status, payload = route_request(
"/trading/doctrine-playbooks",
{},
dependencies={
"trading_doctrine_playbooks": lambda: {
"phase": "D2_PLAYBOOK_BACKTEST_COHORT",
"playbook_count": 1,
}
},
)
self.assertEqual(status, 200)
self.assertEqual(payload["phase"], "D2_PLAYBOOK_BACKTEST_COHORT")
self.assertEqual(payload["playbook_count"], 1)
def test_routes_trading_alert_quality_summary(self) -> None:
status, payload = route_request(
"/trading/alerts/quality-summary",
+6
View File
@@ -7,6 +7,7 @@ from scripts.run_backtest_batch import (
build_candidate_model_codes_from_env,
build_instrument_ids_from_env,
build_session_groups_from_env,
build_setup_playbook_ids_from_env,
build_subject_types_from_env,
build_timeframe_sets_from_env,
run_backtest_batch as run_batch,
@@ -22,6 +23,7 @@ class RunBacktestBatchScriptTests(unittest.TestCase):
"BACKTEST_BATCH_TIMEFRAME_SETS": "1h/15m/5m;15m/5m/1m",
"BACKTEST_BATCH_SESSION_GROUPS": "ALL;LONDON,NY_AM;ASIA",
"BACKTEST_BATCH_CANDIDATE_MODEL_CODES": "ALL,OSOK",
"BACKTEST_BATCH_SETUP_PLAYBOOK_IDS": "ALL,ICT-CH1-6-LONDON-DISCOUNT-REVERSAL",
}
self.assertEqual(build_instrument_ids_from_env(env), [1, 2])
@@ -35,6 +37,7 @@ class RunBacktestBatchScriptTests(unittest.TestCase):
)
self.assertEqual(build_session_groups_from_env(env), [[], ["LONDON", "NY_AM"], ["ASIA"]])
self.assertEqual(build_candidate_model_codes_from_env(env), [None, "OSOK"])
self.assertEqual(build_setup_playbook_ids_from_env(env), [None, "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL"])
def test_run_batch_aggregates_quality_gate_and_metadata(self) -> None:
service = FakeBatchBacktestService(
@@ -54,6 +57,7 @@ class RunBacktestBatchScriptTests(unittest.TestCase):
timeframe_sets=[{"bias_tf": "1h", "setup_tf": "15m", "trigger_tf": "5m"}],
session_groups=[[], ["LONDON"]],
candidate_model_codes=[None],
setup_playbook_ids=["ICT-CH1-6-LONDON-DISCOUNT-REVERSAL"],
require_quality_gate=False,
quality_gate=BacktestQualityGate(min_total_signals=1, min_executed_signals=1, min_profit_factor_r=0.0),
cost_config=None,
@@ -65,7 +69,9 @@ class RunBacktestBatchScriptTests(unittest.TestCase):
self.assertEqual(payload["total_result_count"], 3)
self.assertEqual(payload["aggregate_by_subject"]["plan_replay"]["run_count"], 1)
self.assertEqual(payload["aggregate_by_subject"]["signal"]["run_count"], 2)
self.assertIn("ICT-CH1-6-LONDON-DISCOUNT-REVERSAL::complete", payload["aggregate_by_doctrine_cohort"])
self.assertEqual(service.calls[0]["run_metadata"]["batch_id"], "batch-test")
self.assertEqual(service.calls[0]["run_metadata"]["setup_playbook_id"], "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL")
self.assertEqual(service.calls[0]["run_metadata"]["execution_boundary"], "read_only_backtest_no_order_submission")
self.assertEqual(payload["execution_boundary"], "read_only_backtest_no_order_submission")
+10
View File
@@ -698,6 +698,7 @@ class BacktestServiceRunTests(unittest.TestCase):
"candidate_id": 11,
"trading_plan_id": 77,
"candidate_model_code": "OSOK",
"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL",
"candidate_status": "awaiting_confirmation",
"side": "buy",
"setup_timeframe": "15m",
@@ -716,6 +717,7 @@ class BacktestServiceRunTests(unittest.TestCase):
"candidate_id": 12,
"trading_plan_id": 77,
"candidate_model_code": "OSOK",
"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL",
"candidate_status": "executable",
"side": "buy",
"setup_timeframe": "15m",
@@ -787,6 +789,14 @@ class BacktestServiceRunTests(unittest.TestCase):
self.assertEqual(repository.summary["confirmed_candidates"], 1)
self.assertAlmostEqual(repository.summary["candidate_confirmation_rate"], 1.0)
self.assertIn("draw_target_hit_rate", repository.summary)
self.assertEqual(
repository.summary["doctrine_backtest_cohort"]["cohort_key"],
"ICT-CH1-6-LONDON-DISCOUNT-REVERSAL::complete",
)
self.assertEqual(
repository.summary["by_doctrine_cohort"]["ICT-CH1-6-LONDON-DISCOUNT-REVERSAL::complete"]["count"],
1,
)
def test_normalize_timeframe_set_and_bucket_helpers(self) -> None:
normalized = normalize_timeframe_set({"bias_tf": "1h", "setup_tf": "15m"})
@@ -19,6 +19,7 @@ NONEXISTENT_VISUAL_PLAN_NAME = "AI_ICT_" + "视觉盯盘与主动告警增强方
ACTIVE_TOP_LEVEL_DOCS = {
"AI_ICT_DoctrineKernel市场语言规则内核_D1_2026-05-21_v1.md",
"AI_ICT_DoctrineKernel_PlaybookBacktestCohort_D2_2026-05-21_v1.md",
"AI_ICT_硬性主线约束_必须遵循ICT交易全书_2026-05-21_v1.md",
"AI_ICT_实盘辅助分析决策助手ICT主线开发方案_2026-05-21_v1.md",
"AI_ICT_主线裁剪审计_S1_2026-05-21_v1.md",
+45
View File
@@ -2,6 +2,10 @@ import unittest
from src.services.ict_mainline import (
DOCTRINE_KERNEL_PHASE,
DOCTRINE_PLAYBOOK_PHASE,
build_doctrine_backtest_cohort,
build_doctrine_playbook_gate,
build_doctrine_playbook_registry,
build_doctrine_rule_matrix,
evaluate_doctrine_research_status,
summarize_doctrine_rule_gaps,
@@ -21,6 +25,30 @@ class IctDoctrineKernelTests(unittest.TestCase):
self.assertGreaterEqual(len(matrix["rules"]), 7)
self.assertEqual(matrix["rules"][0]["rule_id"], "range_defined")
def test_d2_playbook_registry_exposes_first_real_setup_schema(self) -> None:
registry = build_doctrine_playbook_registry()
self.assertEqual(registry["phase"], DOCTRINE_PLAYBOOK_PHASE)
self.assertEqual(registry["playbook_count"], 1)
playbook = registry["playbooks"][0]
self.assertEqual(playbook["setup_playbook_id"], "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL")
self.assertIn("range_definition", playbook)
self.assertIn("target_rule", playbook)
self.assertTrue(playbook["promotion_gate"]["requires_backtest_quality_gate"])
def test_playbook_gate_blocks_missing_and_passes_registered_schema(self) -> None:
missing = build_doctrine_playbook_gate({"model_code": "SWEEP_MSS_FVG"})
self.assertFalse(missing["passed"])
self.assertEqual(missing["reason"], "missing_doctrine_setup_playbook")
passed = build_doctrine_playbook_gate(
{"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL"}
)
self.assertTrue(passed["passed"])
self.assertEqual(passed["schema_completeness"], "complete")
def test_surface_shape_candidate_without_doctrine_playbook_is_research_only(self) -> None:
status = evaluate_doctrine_research_status(
{
@@ -83,6 +111,23 @@ class IctDoctrineKernelTests(unittest.TestCase):
self.assertEqual(summary["research_only_reason_counts"]["missing_doctrine_setup_playbook"], 2)
self.assertGreater(summary["missing_rule_counts"]["draw_defined"], 0)
def test_doctrine_backtest_cohort_uses_playbook_and_completeness(self) -> None:
cohort = build_doctrine_backtest_cohort(
{
"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL",
"summary": {"quality_gate": {"passed": True, "reasons": []}},
}
)
self.assertEqual(cohort["cohort_key"], "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL::complete")
self.assertEqual(cohort["doctrine_completeness"], "complete")
self.assertTrue(cohort["promotion_gate"]["passed"])
missing = build_doctrine_backtest_cohort({"model_code": "SWEEP_MSS_FVG"})
self.assertEqual(missing["cohort_key"], "missing_playbook::incomplete")
self.assertIn("missing_doctrine_setup_playbook", missing["promotion_gate"]["reasons"])
if __name__ == "__main__":
unittest.main()
+1
View File
@@ -132,6 +132,7 @@ class IctMainlineTaxonomyTests(unittest.TestCase):
active_trading_plan={
"id": 10,
"status": "active",
"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL",
"primary_draw": {"side": "buy_side"},
"invalidations": [{"code": "range_low_lost"}],
"no_trade_conditions": [],
@@ -1458,6 +1458,7 @@ class _CandidateWatchlistRepo:
"instrument_id": instrument_id,
"plan_date": "2026-05-12",
"status": "active",
"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL",
"primary_draw": {"side": "buy_side", "source": "liquidity_pool"},
"invalidations": [{"code": "range_low_lost"}],
"no_trade_conditions": [],