Add doctrine playbook backtest cohorts
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
Reference in New Issue
Block a user