Add doctrine model playbook kernel
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user