Add doctrine model playbook kernel

This commit is contained in:
Codex
2026-05-22 09:08:04 +08:00
parent 990900e638
commit 4ecc40b284
16 changed files with 835 additions and 5 deletions
+20
View File
@@ -99,6 +99,8 @@ class ApiTests(unittest.TestCase):
self.assertIn("missing_pd_array", result[0]["pd_array_reaction_status"]["reasons"])
self.assertEqual(result[0]["time_session_kernel_status"]["status"], "study_only")
self.assertIn("missing_session_window", result[0]["time_session_kernel_status"]["reasons"])
self.assertEqual(result[0]["model_playbook_kernel_status"]["status"], "study_only")
self.assertIn("missing_model_playbook", result[0]["model_playbook_kernel_status"]["reasons"])
def test_normalize_backtest_summary_row_preserves_existing_quality_gate(self) -> None:
row = {"id": 10, "summary": {"total_signals": 3, "quality_gate": {"passed": True}, "by_symbol": {"BTC-USDT": {"count": 3}}}}
@@ -134,6 +136,8 @@ class ApiTests(unittest.TestCase):
self.assertIn("missing_pd_array", result["pd_array_reaction_summary"]["reason_counts"])
self.assertEqual(result["time_session_kernel_summary"]["item_count"], 1)
self.assertIn("missing_session_window", result["time_session_kernel_summary"]["reason_counts"])
self.assertEqual(result["model_playbook_kernel_summary"]["item_count"], 1)
self.assertIn("missing_model_playbook", result["model_playbook_kernel_summary"]["reason_counts"])
def test_route_request_trading_doctrine_rules_returns_d1_matrix(self) -> None:
status, payload = route_request(
@@ -210,6 +214,22 @@ class ApiTests(unittest.TestCase):
self.assertEqual(payload["phase"], "D5_TIME_SESSION_KERNEL")
self.assertEqual(payload["rules"][0]["rule_id"], "session_window_verifiable")
def test_route_request_trading_model_playbook_kernel_returns_d6_registry(self) -> None:
status, payload = route_request(
"/trading/model-playbook-kernel",
{},
dependencies={
"trading_model_playbook_kernel": lambda: {
"phase": "D6_MODEL_PLAYBOOK_KERNEL",
"rules": [{"rule_id": "model_family_declared"}],
}
},
)
self.assertEqual(status, 200)
self.assertEqual(payload["phase"], "D6_MODEL_PLAYBOOK_KERNEL")
self.assertEqual(payload["rules"][0]["rule_id"], "model_family_declared")
def test_route_request_trading_candidates_returns_items_payload(self) -> None:
status, payload = route_request(
"/trading/candidates",
+16
View File
@@ -166,6 +166,22 @@ class ApiServerRoutingTests(unittest.TestCase):
self.assertEqual(payload["phase"], "D5_TIME_SESSION_KERNEL")
self.assertEqual(payload["rule_count"], 6)
def test_routes_trading_model_playbook_kernel(self) -> None:
status, payload = route_request(
"/trading/model-playbook-kernel",
{},
dependencies={
"trading_model_playbook_kernel": lambda: {
"phase": "D6_MODEL_PLAYBOOK_KERNEL",
"rule_count": 6,
}
},
)
self.assertEqual(status, 200)
self.assertEqual(payload["phase"], "D6_MODEL_PLAYBOOK_KERNEL")
self.assertEqual(payload["rule_count"], 6)
def test_routes_trading_alert_quality_summary(self) -> None:
status, payload = route_request(
"/trading/alerts/quality-summary",
+12
View File
@@ -324,6 +324,9 @@ class BacktestOutcomeTests(unittest.TestCase):
self.assertEqual(summary["time_session_kernel_status_counts"]["study_only"], 4)
self.assertEqual(summary["time_session_kernel_reason_counts"]["missing_session_window"], 4)
self.assertEqual(summary["by_time_session_code"]["LONDON"]["count"], 3)
self.assertEqual(summary["model_playbook_kernel_status_counts"]["study_only"], 4)
self.assertEqual(summary["model_playbook_kernel_reason_counts"]["missing_model_playbook"], 4)
self.assertEqual(summary["by_model_playbook"]["missing_model_playbook"]["count"], 4)
self.assertEqual(summary["by_session"]["LONDON"]["max_consecutive_losses"], 2)
self.assertEqual(summary["by_session"]["LONDON"]["executed"], 3)
self.assertAlmostEqual(summary["by_session"]["LONDON"]["executed_expectancy_r"], 0.2 / 3)
@@ -411,6 +414,7 @@ class BacktestOutcomeTests(unittest.TestCase):
"candidate_id": 1,
"trading_plan_id": 77,
"candidate_model_code": "OSOK",
"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL",
"side": "buy",
"setup_timeframe": "15m",
"trigger_timeframe": "5m",
@@ -428,6 +432,7 @@ class BacktestOutcomeTests(unittest.TestCase):
"candidate_id": 2,
"trading_plan_id": 77,
"candidate_model_code": "OSOK",
"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL",
"side": "buy",
"setup_timeframe": "15m",
"trigger_timeframe": "5m",
@@ -510,6 +515,8 @@ class BacktestOutcomeTests(unittest.TestCase):
self.assertEqual(units[0]["pd_array_reaction_status"]["status"], "pd_array_mapped")
self.assertEqual(units[0]["time_session_backtest_features"]["session_code"], "LONDON")
self.assertEqual(units[0]["time_session_kernel_status"]["status"], "time_session_mapped")
self.assertEqual(units[0]["model_playbook_backtest_features"]["model_playbook_id"], "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL")
self.assertEqual(units[0]["model_playbook_kernel_status"]["status"], "model_playbook_mapped")
class BacktestServiceRunTests(unittest.TestCase):
@@ -883,6 +890,11 @@ class BacktestServiceRunTests(unittest.TestCase):
self.assertEqual(repository.summary["by_pd_array_type"]["fvg"]["count"], 1)
self.assertEqual(repository.summary["time_session_kernel_status"]["status"], "time_session_mapped")
self.assertEqual(repository.summary["by_time_session_code"]["LONDON"]["count"], 1)
self.assertEqual(repository.summary["model_playbook_kernel_status"]["status"], "model_playbook_mapped")
self.assertEqual(
repository.summary["by_model_playbook"]["ICT-CH1-6-LONDON-DISCOUNT-REVERSAL"]["count"],
1,
)
def test_normalize_timeframe_set_and_bucket_helpers(self) -> None:
normalized = normalize_timeframe_set({"bias_tf": "1h", "setup_tf": "15m"})
@@ -23,6 +23,7 @@ ACTIVE_TOP_LEVEL_DOCS = {
"AI_ICT_DoctrineKernel_LiquidityKernel_D3_2026-05-21_v1.md",
"AI_ICT_DoctrineKernel_PDArrayReactionKernel_D4_2026-05-22_v1.md",
"AI_ICT_DoctrineKernel_TimeSessionKernel_D5_2026-05-22_v1.md",
"AI_ICT_DoctrineKernel_ModelPlaybookKernel_D6_2026-05-22_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",
+68
View File
@@ -3,24 +3,29 @@ import unittest
from src.services.ict_mainline import (
DOCTRINE_KERNEL_PHASE,
DOCTRINE_LIQUIDITY_PHASE,
DOCTRINE_MODEL_PLAYBOOK_PHASE,
DOCTRINE_PLAYBOOK_PHASE,
DOCTRINE_REACTION_PHASE,
DOCTRINE_SESSION_PHASE,
build_doctrine_backtest_cohort,
build_liquidity_backtest_features,
build_model_playbook_backtest_features,
build_pd_array_backtest_features,
build_time_session_backtest_features,
build_doctrine_playbook_gate,
build_doctrine_playbook_registry,
build_doctrine_rule_matrix,
build_liquidity_kernel_registry,
build_model_playbook_kernel_registry,
build_pd_array_reaction_registry,
build_time_session_kernel_registry,
evaluate_doctrine_research_status,
evaluate_liquidity_kernel_status,
evaluate_model_playbook_kernel_status,
evaluate_pd_array_reaction_status,
evaluate_time_session_kernel_status,
summarize_liquidity_kernel_statuses,
summarize_model_playbook_kernel_statuses,
summarize_pd_array_reaction_statuses,
summarize_time_session_kernel_statuses,
summarize_doctrine_rule_gaps,
@@ -214,6 +219,51 @@ class IctDoctrineKernelTests(unittest.TestCase):
self.assertGreater(summary["reason_counts"]["outside_session_window"], 0)
self.assertEqual(summary["session_code_counts"]["LONDON"], 1)
def test_d6_model_playbook_kernel_registry_exposes_full_chain_contracts(self) -> None:
registry = build_model_playbook_kernel_registry()
self.assertEqual(registry["phase"], DOCTRINE_MODEL_PLAYBOOK_PHASE)
self.assertEqual(registry["status"], "active")
self.assertGreaterEqual(registry["rule_count"], 6)
self.assertIn("model_playbook_id", registry["required_backtest_feature_fields"])
self.assertIn("Raw model_code is not a tradable setup.", registry["hard_rules"])
def test_raw_model_without_playbook_stays_study_only(self) -> None:
status = evaluate_model_playbook_kernel_status(
{"model_code": "OSOK_DAILY_RANGE_SWEEP_MSS_FVG_RETRACE"}
)
self.assertFalse(status["passed"])
self.assertEqual(status["phase"], DOCTRINE_MODEL_PLAYBOOK_PHASE)
self.assertEqual(status["status"], "study_only")
self.assertIn("missing_model_playbook", status["reasons"])
self.assertIn("missing_model_range_contract", status["reasons"])
self.assertIn("missing_model_backtest_cohort", status["reasons"])
def test_registered_model_playbook_maps_without_execution_permission(self) -> None:
status = evaluate_model_playbook_kernel_status(
{"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL"}
)
self.assertTrue(status["passed"])
self.assertEqual(status["status"], "model_playbook_mapped")
self.assertEqual(status["features"]["model_family"], "london_discount_reversal")
self.assertIn("range_id", status["features"]["range_contract"])
self.assertEqual(status["execution_boundary"], "read_only_analysis_no_order_submission")
def test_model_playbook_summary_counts_blockers(self) -> None:
summary = summarize_model_playbook_kernel_statuses(
[
{"model_code": "SWEEP_MSS_FVG"},
{"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL"},
]
)
self.assertEqual(summary["item_count"], 2)
self.assertEqual(summary["passed_count"], 1)
self.assertGreater(summary["reason_counts"]["missing_model_playbook"], 0)
self.assertEqual(summary["playbook_counts"]["ICT-CH1-6-LONDON-DISCOUNT-REVERSAL"], 1)
def test_playbook_gate_blocks_missing_and_passes_registered_schema(self) -> None:
missing = build_doctrine_playbook_gate({"model_code": "SWEEP_MSS_FVG"})
@@ -313,6 +363,8 @@ class IctDoctrineKernelTests(unittest.TestCase):
self.assertEqual(cohort["liquidity_kernel_status"]["status"], "liquidity_mapped")
self.assertEqual(cohort["pd_array_reaction_status"]["status"], "pd_array_mapped")
self.assertEqual(cohort["time_session_kernel_status"]["status"], "time_session_mapped")
self.assertEqual(cohort["model_playbook_kernel_status"]["status"], "model_playbook_mapped")
self.assertTrue(cohort["promotion_gate"]["requires_model_playbook_kernel_complete"])
missing = build_doctrine_backtest_cohort({"model_code": "SWEEP_MSS_FVG"})
@@ -321,6 +373,7 @@ class IctDoctrineKernelTests(unittest.TestCase):
self.assertIn("missing_liquidity_target", missing["promotion_gate"]["reasons"])
self.assertIn("missing_pd_array", missing["promotion_gate"]["reasons"])
self.assertIn("missing_session_window", missing["promotion_gate"]["reasons"])
self.assertIn("missing_model_playbook", missing["promotion_gate"]["reasons"])
def test_liquidity_backtest_features_extract_nested_candidate_fields(self) -> None:
features = build_liquidity_backtest_features(
@@ -385,6 +438,21 @@ class IctDoctrineKernelTests(unittest.TestCase):
self.assertEqual(features["killzone_name"], "NY_AM_KILLZONE")
self.assertEqual(features["opening_range_state"], "defined")
def test_model_playbook_features_extract_nested_playbook_fields(self) -> None:
features = build_model_playbook_backtest_features(
{
"candidate": {
"entry": {
"setup_playbook_id": "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL",
}
}
}
)
self.assertEqual(features["model_playbook_id"], "ICT-CH1-6-LONDON-DISCOUNT-REVERSAL")
self.assertEqual(features["model_family"], "london_discount_reversal")
self.assertIn("liquidity_target_side", features["liquidity_contract"])
def _complete_pd_array_payload() -> dict:
return {