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
+2 -1
View File
@@ -850,10 +850,11 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
- D3 Liquidity Kernel is complete locally: added `DOCTRINE_LIQUIDITY_PHASE=D3_LIQUIDITY_KERNEL`, `LiquidityTargetRule`, `GET /trading/liquidity-kernel`, Strategy Backtest `liquidity_backtest_features` / `liquidity_kernel_status` / `liquidity_kernel_summary`, and `docs/AI_ICT_DoctrineKernel_LiquidityKernel_D3_2026-05-21_v1.md`. BSL / SSL / liquidity pool / stop run / sweep / raid are now target-ranking and no-trade/wait rules; `sweep_is_not_setup`, `missing_liquidity_target`, `missing_liquidity_pool`, `missing_working_draw`, and `manipulation_window_wait` block promotion until the Working Draw, ranked pool, post-take reaction, and objective reorder evidence are present. No execution/readiness/risk/trust/operator-confirmation gate was changed.
- D4 PD Array / Reaction Kernel is complete, deployed, and remote-verified: added `DOCTRINE_REACTION_PHASE=D4_PD_ARRAY_REACTION_KERNEL`, `PdArrayReactionRule`, `GET /trading/pd-array-kernel`, Strategy Backtest `pd_array_backtest_features` / `pd_array_reaction_status` / `pd_array_reaction_summary`, and `docs/AI_ICT_DoctrineKernel_PDArrayReactionKernel_D4_2026-05-22_v1.md`. FVG / OB / Breaker / Mitigation / BPR/Rebalancing now stay reaction-location quality rankings rather than direction tags; `missing_pd_array`, `missing_pd_array_role`, `missing_array_location`, `fvg_is_not_setup`, `order_block_not_authorized`, and `breaker_without_failure_precondition` block promotion until role, rank, location, freshness, origin, reaction/retest, and invalidation are explicit. Validation passed focused D4/API/backtest/docs tests, full unit discovery (`839` tests), script static checks (`79` scripts), JS syntax checks, deploy smoke over 11 endpoints, remote ops smoke, read-only live analysis smoke, `/trading/pd-array-kernel` field smoke, `/trading/backtests` D4 field smoke, and Playwright screenshot evidence at `output/playwright/d4-pd-array-strategy-backtest-1366x768.png`. The read-only smoke initially exposed the expected external proxy blocker; starting the local v2rayN/sing-box proxy restored `192.168.3.73:10808` and the gate passed. No execution/readiness/risk/trust/operator-confirmation gate was changed.
- D5 Time / Session Kernel is complete, deployed, and remote-verified: added `DOCTRINE_SESSION_PHASE=D5_TIME_SESSION_KERNEL`, `TimeSessionRule`, `GET /trading/time-session-kernel`, Strategy Backtest `time_session_backtest_features` / `time_session_kernel_status` / `time_session_kernel_summary`, and `docs/AI_ICT_DoctrineKernel_TimeSessionKernel_D5_2026-05-22_v1.md`. Asia / London / NY AM / NY PM, killzone, Judas/manipulation window, opening range, weekly profile, and news/no-trade time are now measurable session-quality gates; `missing_session_window`, `session_window_unverified`, `outside_session_window`, `session_not_allowed`, `missing_killzone`, `killzone_not_active`, `judas_window_wait`, `opening_range_unresolved`, `weekly_profile_missing`, `news_time_no_trade`, and `session_invalidation_missing` block promotion until timing is explicit. Validation passed focused D5/API/backtest/docs tests, full unit discovery (`846` tests), script static checks (`79` scripts), secret static checks, JS syntax checks, deploy smoke over 11 endpoints, remote ops smoke, read-only live analysis smoke, `/trading/time-session-kernel` field smoke, `/trading/backtests` D5 field smoke, and Playwright screenshot evidence at `output/playwright/d5-time-session-strategy-backtest-1366x768.png`. Remote latest backtest `#513` is correctly blocked by D5 as `study_only / session_window_unverified` instead of treating `ASIA` plus `mark_outside` as a verified session window. No execution/readiness/risk/trust/operator-confirmation gate was changed.
- D6 Model Playbook Kernel is complete, deployed, and remote-verified: added `DOCTRINE_MODEL_PLAYBOOK_PHASE=D6_MODEL_PLAYBOOK_KERNEL`, `ModelPlaybookRule`, `GET /trading/model-playbook-kernel`, Strategy Backtest `model_playbook_backtest_features` / `model_playbook_kernel_status` / `model_playbook_kernel_summary`, and `docs/AI_ICT_DoctrineKernel_ModelPlaybookKernel_D6_2026-05-22_v1.md`. Raw `model_code` values such as `SWEEP_MSS_FVG` now stay `study_only` until they resolve to a registered setup playbook with explicit Range / Draw / Liquidity / PD Array / Session / Confirmation / Invalidation / Target, batch cohort, quality-gate, journal-feedback, and operator-runbook contracts. Validation passed focused D6/API/backtest/docs tests, full unit discovery (`853` tests), script static checks (`79` scripts), secret static checks, JS syntax checks, deploy smoke over 11 endpoints, remote ops smoke, read-only live analysis smoke, `/trading/model-playbook-kernel` field smoke, `/trading/backtests` D6 field smoke, and Playwright screenshot evidence at `output/playwright/d6-model-playbook-strategy-backtest-fullpage-zh.png`. Remote latest backtest `#513` is correctly blocked by D6 as `study_only / missing_model_playbook` instead of promoting a surface-shape model as a tradeable setup. No execution/readiness/risk/trust/operator-confirmation gate was changed.
## Exact Next Step
Use `docs/AI_ICT_DoctrineKernel全书规则内核重建_D0_2026-05-21_v1.md`, `docs/AI_ICT_DoctrineKernel市场语言规则内核_D1_2026-05-21_v1.md`, `docs/AI_ICT_DoctrineKernel_PlaybookBacktestCohort_D2_2026-05-21_v1.md`, `docs/AI_ICT_DoctrineKernel_LiquidityKernel_D3_2026-05-21_v1.md`, `docs/AI_ICT_DoctrineKernel_PDArrayReactionKernel_D4_2026-05-22_v1.md`, `docs/AI_ICT_DoctrineKernel_TimeSessionKernel_D5_2026-05-22_v1.md`, `docs/ICT交易全书_出版终校版.md`, `docs/AI_ICT_实盘辅助分析决策助手ICT主线开发方案_2026-05-21_v1.md`, `docs/AI_ICT_批量历史回测验证_S16_2026-05-21_v1.md`, `SESSION.md`, and `TASKS.md` as the active anchors. Start D6 Model Playbook Kernel: convert the book-derived models into full setup playbooks with required Range / Draw / Liquidity / PD Array / Session / Confirmation / Invalidation / Target fields and batch-backtest cohorts. Do not restart external-context/archive/digest long-tail work, do not add order-submission features, do not physically delete S1 deprecated surfaces yet, and do not fabricate R2 paper samples from blocked candidates. Keep `execution_allowed`, `decision_support_ready`, `real_live_ready`, risk governor, trust bucket, and operator confirmation protocol unchanged.
Use `docs/AI_ICT_DoctrineKernel全书规则内核重建_D0_2026-05-21_v1.md`, `docs/AI_ICT_DoctrineKernel市场语言规则内核_D1_2026-05-21_v1.md`, `docs/AI_ICT_DoctrineKernel_PlaybookBacktestCohort_D2_2026-05-21_v1.md`, `docs/AI_ICT_DoctrineKernel_LiquidityKernel_D3_2026-05-21_v1.md`, `docs/AI_ICT_DoctrineKernel_PDArrayReactionKernel_D4_2026-05-22_v1.md`, `docs/AI_ICT_DoctrineKernel_TimeSessionKernel_D5_2026-05-22_v1.md`, `docs/AI_ICT_DoctrineKernel_ModelPlaybookKernel_D6_2026-05-22_v1.md`, `docs/ICT交易全书_出版终校版.md`, `docs/AI_ICT_硬性主线约束_必须遵循ICT交易全书_2026-05-21_v1.md`, `docs/AI_ICT_实盘辅助分析决策助手ICT主线开发方案_2026-05-21_v1.md`, `docs/AI_ICT_批量历史回测验证_S16_2026-05-21_v1.md`, `SESSION.md`, and `TASKS.md` as the active anchors. Start D7 Trader Product Rebuild / Playbook Promotion Gate: only playbook-mapped, backtest-supported, plan-approved setups can re-enter Trader Cockpit focus; keep study-only evidence in Strategy Backtest / Study Notes / System Ops, not trader-facing Review Now. Do not restart external-context/archive/digest long-tail work, do not add order-submission features, do not physically delete S1 deprecated surfaces yet, and do not fabricate R2 paper samples from blocked candidates. Keep `execution_allowed`, `decision_support_ready`, `real_live_ready`, risk governor, trust bucket, and operator confirmation protocol unchanged.
## Validation Commands Used Recently
+2 -1
View File
@@ -957,7 +957,8 @@
- [x] D3 Liquidity Kernel: rebuilt BSL / SSL / liquidity pool / stop run / sweep / raid from shape labels into target-ranking and no-trade/wait rules; sweep remains evidence component only, Strategy Backtest exposes liquidity features/status/summary, and `/trading/liquidity-kernel` exposes the D3 registry
- [x] D4 PD Array / Reaction Kernel: rebuilt FVG / OB / Breaker / Mitigation / Rebalance / Void into reaction-location quality ranking and no-trade/wait rules; PD Array remains location/reaction evidence only, Strategy Backtest exposes PD Array features/status/summary, `/trading/pd-array-kernel` exposes the D4 registry, and remote deploy/ops/read-only smoke passed after the local proxy was restarted
- [x] D5 Time / Session Kernel: rebuilt Asia / London / NY AM / NY PM / killzone / Judas window / opening range / weekly profile / news time into measurable session-quality gates instead of loose time labels; Strategy Backtest now exposes `time_session_backtest_features`, `time_session_kernel_status`, `time_session_kernel_summary`, and `/trading/time-session-kernel` without changing execution/readiness gates
- [ ] D6 next product gate: rebuild Model Playbook Kernel so book-derived models become full setup playbooks with required Range / Draw / Liquidity / PD Array / Session / Confirmation / Invalidation / Target fields and batch-backtest cohorts
- [x] D6 Model Playbook Kernel: raw model codes now stay study-only until they resolve to registered setup playbooks with Range / Draw / Liquidity / PD Array / Session / Confirmation / Invalidation / Target, batch-cohort, quality-gate, journal-feedback, and operator-runbook contracts; Strategy Backtest exposes D6 features/status/summary and `/trading/model-playbook-kernel` exposes the D6 registry without changing execution/readiness gates
- [ ] D7 Trader Product Rebuild / Playbook Promotion Gate: only playbook-mapped, backtest-supported, plan-approved setups may re-enter Trader Cockpit focus; keep study-only evidence in Strategy Backtest / Study Notes / System Ops rather than Review Now
- [x] R3 OKX sandbox isolated smoke tooling: guarded script and service can call OKX demo submit/query/cancel/query/fills only when `OKX_SANDBOX_SMOKE_EXECUTE=1`; local validation, deploy smoke, and remote ops smoke passed
- [ ] R3 sandbox credential permission gate deferred: current phase is assisted analysis / decision support only, so do not request trade-permission credentials or make submit/cancel smoke the next gate
- [ ] Long-term target: complete `Real-time Assisted Trading V1` without enabling default live auto-trading
@@ -0,0 +1,89 @@
# AI_ICT Doctrine Kernel Model Playbook Kernel D6 2026-05-22 v1
## Scope
D6 rebuilds model identity from raw shape labels into book-derived setup playbooks. It follows `docs/ICT交易全书_出版终校版.md` and the hard mainline chain:
`Range / Draw / Location -> Liquidity / Sweep / Inducement / Path -> Displacement / MSS / FVG / Retest / Session -> backtest evidence -> pre-market plan -> intraday unique focus or no-trade reason -> journal / study-notes feedback`.
## Product Rule
Raw `model_code` values such as `SWEEP_MSS_FVG` or `OSOK_DAILY_RANGE_SWEEP_MSS_FVG_RETRACE` are not tradable setups. They can remain historical research material, but they cannot support Trader Cockpit `Review Now` unless they resolve into a registered full-chain setup playbook.
Every model playbook must declare:
- Range contract
- Draw contract
- Liquidity contract
- PD Array contract
- Session contract
- Confirmation / trigger contract
- Invalidation contract
- Target / management contract
- Batch backtest cohort
- Journal / study-note feedback contract
If any contract is missing, Strategy Backtest and Trader Cockpit must keep the setup as `study_only` or `wait`.
## Implemented Kernel
New phase:
- `DOCTRINE_MODEL_PLAYBOOK_PHASE = D6_MODEL_PLAYBOOK_KERNEL`
New registry/API:
- `ModelPlaybookRule`
- `D6_MODEL_PLAYBOOK_RULES`
- `build_model_playbook_kernel_registry`
- `list_model_playbook_rules`
- `GET /trading/model-playbook-kernel`
New backtest surface fields:
- `model_playbook_backtest_features`
- `model_playbook_kernel_status`
- `model_playbook_kernel_summary`
- `model_playbook_kernel_status_counts`
- `model_playbook_kernel_reason_counts`
- `by_model_playbook`
## D6 Rules
1. `model_family_declared`
A model must point to a registered book-derived playbook before any backtest result can be trusted.
2. `full_chain_contract_complete`
A playbook must define Range, Draw, Liquidity, PD Array, Session, Confirmation, Invalidation, and Target contracts.
3. `confirmation_trigger_bound`
Displacement / MSS / FVG / Retest can confirm only after they serve the declared Draw.
4. `risk_target_contract_bound`
Invalidation and IRL / ERL target architecture must be explicit before promotion.
5. `batch_cohort_contract_ready`
Backtest evidence must be grouped by playbook cohort, not raw model code.
6. `journal_feedback_contract_ready`
Every model failure must map back to journal and study-note error classes.
## Promotion Gate
The Doctrine Backtest Cohort now blocks promotion unless:
- D2 playbook schema is complete,
- D3 Liquidity Kernel passes,
- D4 PD Array / Reaction Kernel passes,
- D5 Time / Session Kernel passes,
- D6 Model Playbook Kernel passes,
- Strategy Backtest quality gate passes,
- active Trading Plan gate passes.
## Read-Only Boundary
D6 does not add order submission, does not change `execution_allowed`, does not change `decision_support_ready`, does not change `real_live_ready`, and does not loosen risk governor / trust bucket / operator confirmation protocol.
## Next Slice
D7 should rebuild the trader-facing product gate: only playbook-mapped, backtest-supported, plan-approved setups can re-enter Trader Cockpit focus; all other evidence should remain under Strategy Backtest / Trading Plan / Journal / System Ops as study material.
Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

+34
View File
@@ -8,17 +8,20 @@ from src.services.backtest import evaluate_backtest_quality
from src.services.ict_mainline import (
build_doctrine_backtest_cohort,
build_liquidity_backtest_features,
build_model_playbook_backtest_features,
build_pd_array_backtest_features,
build_time_session_backtest_features,
build_backtest_quality_gate,
build_ict_rulebook_mapping,
evaluate_doctrine_research_status,
evaluate_liquidity_kernel_status,
evaluate_model_playbook_kernel_status,
evaluate_pd_array_reaction_status,
evaluate_time_session_kernel_status,
summarize_doctrine_backtest_cohorts,
summarize_doctrine_rule_gaps,
summarize_liquidity_kernel_statuses,
summarize_model_playbook_kernel_statuses,
summarize_pd_array_reaction_statuses,
summarize_time_session_kernel_statuses,
)
@@ -45,6 +48,7 @@ def get_strategy_backtest_surface(instrument_id: Optional[int] = None, limit: in
liquidity_kernel_summary = summarize_liquidity_kernel_statuses(items)
pd_array_reaction_summary = summarize_pd_array_reaction_statuses(items)
time_session_kernel_summary = summarize_time_session_kernel_statuses(items)
model_playbook_kernel_summary = summarize_model_playbook_kernel_statuses(items)
return {
"instrument_id": instrument_id,
"item_count": len(items),
@@ -57,6 +61,7 @@ def get_strategy_backtest_surface(instrument_id: Optional[int] = None, limit: in
"liquidity_kernel_summary": liquidity_kernel_summary,
"pd_array_reaction_summary": pd_array_reaction_summary,
"time_session_kernel_summary": time_session_kernel_summary,
"model_playbook_kernel_summary": model_playbook_kernel_summary,
"ict_rulebook_mapping": ict_rulebook_mapping,
"items": items,
}
@@ -183,6 +188,25 @@ def normalize_backtest_summary_row(row: dict) -> dict:
"time_session_backtest_features": time_session_backtest_features,
}
)
model_playbook_backtest_features = build_model_playbook_backtest_features(
{
"subject_type": row.get("subject_type"),
"model_code": row.get("model_code"),
"candidate_model_code": row.get("candidate_model_code"),
"config": config,
"summary": summary,
}
)
model_playbook_kernel_status = evaluate_model_playbook_kernel_status(
{
"subject_type": row.get("subject_type"),
"model_code": row.get("model_code"),
"candidate_model_code": row.get("candidate_model_code"),
"config": config,
"summary": summary,
"model_playbook_backtest_features": model_playbook_backtest_features,
}
)
doctrine_backtest_cohort = build_doctrine_backtest_cohort(
{
"subject_type": row.get("subject_type"),
@@ -194,6 +218,7 @@ def normalize_backtest_summary_row(row: dict) -> dict:
"liquidity_backtest_features": liquidity_backtest_features,
"pd_array_backtest_features": pd_array_backtest_features,
"time_session_backtest_features": time_session_backtest_features,
"model_playbook_backtest_features": model_playbook_backtest_features,
},
quality_gate=dict(summary.get("quality_gate") or {}),
)
@@ -210,6 +235,8 @@ def normalize_backtest_summary_row(row: dict) -> dict:
"pd_array_reaction_status": pd_array_reaction_status,
"time_session_backtest_features": time_session_backtest_features,
"time_session_kernel_status": time_session_kernel_status,
"model_playbook_backtest_features": model_playbook_backtest_features,
"model_playbook_kernel_status": model_playbook_kernel_status,
"doctrine_backtest_cohort": doctrine_backtest_cohort,
}
@@ -261,6 +288,13 @@ def _build_operator_summary(latest: Optional[dict]) -> dict:
status = "time_session_kernel_blocked"
decision = "do_not_promote_strategy"
reason = str(time_session_reasons[0])
model_playbook_status = dict(latest.get("model_playbook_kernel_status") or {})
if not model_playbook_status.get("passed"):
model_playbook_reasons = list(model_playbook_status.get("reasons") or [])
if model_playbook_reasons:
status = "model_playbook_kernel_blocked"
decision = "do_not_promote_strategy"
reason = str(model_playbook_reasons[0])
if not (doctrine_cohort.get("promotion_gate") or {}).get("passed"):
cohort_reasons = list((doctrine_cohort.get("promotion_gate") or {}).get("reasons") or [])
if cohort_reasons:
+4
View File
@@ -126,6 +126,7 @@ from src.services.ict_mainline import (
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,
)
@@ -233,6 +234,7 @@ def build_api_dependencies() -> dict:
"trading_liquidity_kernel": build_liquidity_kernel_registry,
"trading_pd_array_kernel": build_pd_array_reaction_registry,
"trading_time_session_kernel": build_time_session_kernel_registry,
"trading_model_playbook_kernel": build_model_playbook_kernel_registry,
"training_cases": get_training_cases,
"training_case": get_training_case,
"training_export_cases": export_training_case_package,
@@ -864,6 +866,8 @@ def route_request(
return 200, deps["trading_pd_array_kernel"]()
if method == "GET" and path == "/trading/time-session-kernel":
return 200, deps["trading_time_session_kernel"]()
if method == "GET" and path == "/trading/model-playbook-kernel":
return 200, deps["trading_model_playbook_kernel"]()
if method == "GET" and len(path_parts) == 3 and path_parts[:2] == ["trading", "candidates"]:
candidate = deps["trading_candidate_by_id"](_parse_path_int(path_parts[2], "candidate_id"))
if candidate is None:
+88
View File
@@ -7,9 +7,11 @@ from src.services.backtest.replay import ReplaySwing, iter_historical_replay
from src.services.ict_mainline import (
build_doctrine_backtest_cohort,
build_liquidity_backtest_features,
build_model_playbook_backtest_features,
build_pd_array_backtest_features,
build_time_session_backtest_features,
evaluate_liquidity_kernel_status,
evaluate_model_playbook_kernel_status,
evaluate_pd_array_reaction_status,
evaluate_time_session_kernel_status,
extract_setup_playbook_id,
@@ -167,6 +169,17 @@ class BacktestService:
'active_trading_plan': active_trading_plan,
}
)
model_playbook_backtest_features = build_model_playbook_backtest_features(
{
'subject_type': subject_type,
'model_code': 'SWEEP_MSS_FVG',
'candidate_model_code': candidate_model_code,
'timeframe_set': timeframe_set,
'session_codes': list(session_codes or []),
'run_metadata': run_metadata,
'active_trading_plan': active_trading_plan,
}
)
doctrine_backtest_cohort = build_doctrine_backtest_cohort(
{
'subject_type': subject_type,
@@ -177,6 +190,7 @@ class BacktestService:
'liquidity_backtest_features': liquidity_backtest_features,
'pd_array_backtest_features': pd_array_backtest_features,
'time_session_backtest_features': time_session_backtest_features,
'model_playbook_backtest_features': model_playbook_backtest_features,
}
)
run_id = self.repository.insert_backtest_run(
@@ -205,6 +219,8 @@ class BacktestService:
'pd_array_reaction_status': evaluate_pd_array_reaction_status(pd_array_backtest_features),
'time_session_backtest_features': time_session_backtest_features,
'time_session_kernel_status': evaluate_time_session_kernel_status(time_session_backtest_features),
'model_playbook_backtest_features': model_playbook_backtest_features,
'model_playbook_kernel_status': evaluate_model_playbook_kernel_status(model_playbook_backtest_features),
'doctrine_backtest_cohort': doctrine_backtest_cohort,
'run_metadata': run_metadata,
},
@@ -227,12 +243,15 @@ class BacktestService:
outcome_liquidity_features = _first_outcome_meta_value(outcomes, 'liquidity_backtest_features') or liquidity_backtest_features
outcome_pd_array_features = _first_outcome_meta_value(outcomes, 'pd_array_backtest_features') or pd_array_backtest_features
outcome_time_session_features = _first_outcome_meta_value(outcomes, 'time_session_backtest_features') or time_session_backtest_features
outcome_model_playbook_features = _first_outcome_meta_value(outcomes, 'model_playbook_backtest_features') or model_playbook_backtest_features
summary['liquidity_backtest_features'] = dict(outcome_liquidity_features or {})
summary['liquidity_kernel_status'] = evaluate_liquidity_kernel_status(summary['liquidity_backtest_features'])
summary['pd_array_backtest_features'] = dict(outcome_pd_array_features or {})
summary['pd_array_reaction_status'] = evaluate_pd_array_reaction_status(summary['pd_array_backtest_features'])
summary['time_session_backtest_features'] = dict(outcome_time_session_features or {})
summary['time_session_kernel_status'] = evaluate_time_session_kernel_status(summary['time_session_backtest_features'])
summary['model_playbook_backtest_features'] = dict(outcome_model_playbook_features or {})
summary['model_playbook_kernel_status'] = evaluate_model_playbook_kernel_status(summary['model_playbook_backtest_features'])
summary['doctrine_backtest_cohort'] = build_doctrine_backtest_cohort(
{
'subject_type': subject_type,
@@ -244,6 +263,7 @@ class BacktestService:
'liquidity_backtest_features': summary['liquidity_backtest_features'],
'pd_array_backtest_features': summary['pd_array_backtest_features'],
'time_session_backtest_features': summary['time_session_backtest_features'],
'model_playbook_backtest_features': summary['model_playbook_backtest_features'],
},
quality_gate=summary['quality_gate'],
)
@@ -297,6 +317,15 @@ class BacktestService:
'session_filter': 'outside_window' if len(sessions) == 0 else 'covered',
}
)
signal_model_playbook_features = build_model_playbook_backtest_features(
{
'signal': signal,
'target_plan': target_plan,
'run_metadata': run_metadata,
'timeframe_set': timeframe_set,
'session_codes': session_codes,
}
)
error_tags = ['no_future_candles']
if len(sessions) == 0:
error_tags.append('outside_session_window')
@@ -339,6 +368,8 @@ class BacktestService:
'pd_array_reaction_status': evaluate_pd_array_reaction_status(signal_pd_array_features),
'time_session_backtest_features': signal_time_session_features,
'time_session_kernel_status': evaluate_time_session_kernel_status(signal_time_session_features),
'model_playbook_backtest_features': signal_model_playbook_features,
'model_playbook_kernel_status': evaluate_model_playbook_kernel_status(signal_model_playbook_features),
'cost_r': 0.0,
'cost_model': cost_config.__dict__,
'backtest_mode': mode,
@@ -438,12 +469,22 @@ class BacktestService:
'session_filter': 'outside_window' if session_filtered else 'covered',
}
),
'model_playbook_backtest_features': build_model_playbook_backtest_features(
{
'signal': signal,
'target_plan': extract_target_plan_from_signal_row(signal),
'run_metadata': run_metadata,
'timeframe_set': timeframe_set,
'session_codes': session_codes,
}
),
'weekday': _extract_weekday(signal['created_ts']),
}
)
record.meta['liquidity_kernel_status'] = evaluate_liquidity_kernel_status(record.meta['liquidity_backtest_features'])
record.meta['pd_array_reaction_status'] = evaluate_pd_array_reaction_status(record.meta['pd_array_backtest_features'])
record.meta['time_session_kernel_status'] = evaluate_time_session_kernel_status(record.meta['time_session_backtest_features'])
record.meta['model_playbook_kernel_status'] = evaluate_model_playbook_kernel_status(record.meta['model_playbook_backtest_features'])
outcomes.append(record)
self.repository.insert_backtest_result(
backtest_run_id=run_id,
@@ -465,12 +506,15 @@ class BacktestService:
outcome_liquidity_features = _first_outcome_meta_value(outcomes, 'liquidity_backtest_features') or liquidity_backtest_features
outcome_pd_array_features = _first_outcome_meta_value(outcomes, 'pd_array_backtest_features') or pd_array_backtest_features
outcome_time_session_features = _first_outcome_meta_value(outcomes, 'time_session_backtest_features') or time_session_backtest_features
outcome_model_playbook_features = _first_outcome_meta_value(outcomes, 'model_playbook_backtest_features') or model_playbook_backtest_features
summary['liquidity_backtest_features'] = dict(outcome_liquidity_features or {})
summary['liquidity_kernel_status'] = evaluate_liquidity_kernel_status(summary['liquidity_backtest_features'])
summary['pd_array_backtest_features'] = dict(outcome_pd_array_features or {})
summary['pd_array_reaction_status'] = evaluate_pd_array_reaction_status(summary['pd_array_backtest_features'])
summary['time_session_backtest_features'] = dict(outcome_time_session_features or {})
summary['time_session_kernel_status'] = evaluate_time_session_kernel_status(summary['time_session_backtest_features'])
summary['model_playbook_backtest_features'] = dict(outcome_model_playbook_features or {})
summary['model_playbook_kernel_status'] = evaluate_model_playbook_kernel_status(summary['model_playbook_backtest_features'])
summary['doctrine_backtest_cohort'] = build_doctrine_backtest_cohort(
{
'subject_type': subject_type,
@@ -482,6 +526,7 @@ class BacktestService:
'liquidity_backtest_features': summary['liquidity_backtest_features'],
'pd_array_backtest_features': summary['pd_array_backtest_features'],
'time_session_backtest_features': summary['time_session_backtest_features'],
'model_playbook_backtest_features': summary['model_playbook_backtest_features'],
},
quality_gate=summary['quality_gate'],
)
@@ -1072,6 +1117,22 @@ def build_plan_replay_units(candidate_rows: list[dict]) -> list[dict]:
}
)
time_session_kernel_status = evaluate_time_session_kernel_status(time_session_backtest_features)
model_playbook_backtest_features = build_model_playbook_backtest_features(
{
'first_candidate': first,
'latest_candidate': latest,
'confirmed_candidate': confirmed_row or {},
'entry_payload': entry_payload,
'target_plan': target_plan,
'setup_playbook_id': setup_playbook_id,
'candidate_model_code': first.get('candidate_model_code'),
'timeframe_set': {
'setup_tf': first.get('setup_timeframe'),
'trigger_tf': first.get('trigger_timeframe'),
},
}
)
model_playbook_kernel_status = evaluate_model_playbook_kernel_status(model_playbook_backtest_features)
doctrine_backtest_cohort = build_doctrine_backtest_cohort(
{
'setup_playbook_id': setup_playbook_id,
@@ -1083,6 +1144,7 @@ def build_plan_replay_units(candidate_rows: list[dict]) -> list[dict]:
'liquidity_backtest_features': liquidity_backtest_features,
'pd_array_backtest_features': pd_array_backtest_features,
'time_session_backtest_features': time_session_backtest_features,
'model_playbook_backtest_features': model_playbook_backtest_features,
}
)
units.append(
@@ -1097,6 +1159,8 @@ def build_plan_replay_units(candidate_rows: list[dict]) -> list[dict]:
'pd_array_reaction_status': pd_array_reaction_status,
'time_session_backtest_features': time_session_backtest_features,
'time_session_kernel_status': time_session_kernel_status,
'model_playbook_backtest_features': model_playbook_backtest_features,
'model_playbook_kernel_status': model_playbook_kernel_status,
'candidate_model_code': first.get('candidate_model_code'),
'side': first.get('side'),
'session_code': first.get('session_code'),
@@ -1190,6 +1254,14 @@ def build_plan_replay_meta(
}
)
time_session_kernel_status = evaluate_time_session_kernel_status(time_session_backtest_features)
model_playbook_backtest_features = build_model_playbook_backtest_features(
{
'unit': unit,
'model_playbook_backtest_features': dict(unit.get('model_playbook_backtest_features') or {}),
'timeframe_set': timeframe_set,
}
)
model_playbook_kernel_status = evaluate_model_playbook_kernel_status(model_playbook_backtest_features)
return {
'subject_type': BACKTEST_SUBJECT_PLAN_REPLAY,
'trading_plan_id': unit.get('trading_plan_id'),
@@ -1203,6 +1275,8 @@ def build_plan_replay_meta(
'pd_array_reaction_status': dict(unit.get('pd_array_reaction_status') or {}),
'time_session_backtest_features': time_session_backtest_features,
'time_session_kernel_status': time_session_kernel_status,
'model_playbook_backtest_features': model_playbook_backtest_features,
'model_playbook_kernel_status': model_playbook_kernel_status,
'plan_activated_ts': _normalize_meta_ts(unit.get('plan_activated_ts')),
'plan_created_ts': _normalize_meta_ts(unit.get('plan_created_ts')),
'candidate_id': unit.get('candidate_id'),
@@ -1392,6 +1466,7 @@ def summarize_backtest_outcomes(outcomes: list[BacktestOutcome], subject_type: s
grouped_by_liquidity_target_side: dict[str, dict] = {}
grouped_by_pd_array_type: dict[str, dict] = {}
grouped_by_time_session_code: dict[str, dict] = {}
grouped_by_model_playbook: dict[str, dict] = {}
r_multiples: list[float] = []
cost_values: list[float] = []
mfe_r_values: list[float] = []
@@ -1408,6 +1483,8 @@ def summarize_backtest_outcomes(outcomes: list[BacktestOutcome], subject_type: s
pd_array_reaction_status_counts: dict[str, int] = {}
time_session_kernel_reason_counts: dict[str, int] = {}
time_session_kernel_status_counts: dict[str, int] = {}
model_playbook_kernel_reason_counts: dict[str, int] = {}
model_playbook_kernel_status_counts: dict[str, int] = {}
plan_total_r: dict[int, float] = {}
plan_seen: set[int] = set()
@@ -1464,6 +1541,13 @@ def summarize_backtest_outcomes(outcomes: list[BacktestOutcome], subject_type: s
time_session_kernel_status_counts[time_session_status_key] = time_session_kernel_status_counts.get(time_session_status_key, 0) + 1
for reason in list(time_session_status.get('reasons') or []):
time_session_kernel_reason_counts[str(reason)] = time_session_kernel_reason_counts.get(str(reason), 0) + 1
model_playbook_features = dict(outcome.meta.get('model_playbook_backtest_features') or {})
model_playbook_id = str(model_playbook_features.get('model_playbook_id') or outcome.meta.get('setup_playbook_id') or 'missing_model_playbook')
model_playbook_status = dict(outcome.meta.get('model_playbook_kernel_status') or evaluate_model_playbook_kernel_status(model_playbook_features))
model_playbook_status_key = str(model_playbook_status.get('status') or 'unknown')
model_playbook_kernel_status_counts[model_playbook_status_key] = model_playbook_kernel_status_counts.get(model_playbook_status_key, 0) + 1
for reason in list(model_playbook_status.get('reasons') or []):
model_playbook_kernel_reason_counts[str(reason)] = model_playbook_kernel_reason_counts.get(str(reason), 0) + 1
r_multiples.append(float(outcome.meta.get('r_multiple') or 0.0))
cost_values.append(float(outcome.meta.get('cost_r') or 0.0))
mfe_r_values.append(float(outcome.meta.get('mfe_r') or 0.0))
@@ -1477,6 +1561,7 @@ def summarize_backtest_outcomes(outcomes: list[BacktestOutcome], subject_type: s
_accumulate_bucket(grouped_by_liquidity_target_side, liquidity_target_side, outcome)
_accumulate_bucket(grouped_by_pd_array_type, pd_array_type, outcome)
_accumulate_bucket(grouped_by_time_session_code, time_session_code, outcome)
_accumulate_bucket(grouped_by_model_playbook, model_playbook_id, outcome)
for session_code in session_codes:
_accumulate_bucket(grouped_by_session, str(session_code), outcome)
@@ -1513,6 +1598,8 @@ def summarize_backtest_outcomes(outcomes: list[BacktestOutcome], subject_type: s
'pd_array_reaction_reason_counts': pd_array_reaction_reason_counts,
'time_session_kernel_status_counts': time_session_kernel_status_counts,
'time_session_kernel_reason_counts': time_session_kernel_reason_counts,
'model_playbook_kernel_status_counts': model_playbook_kernel_status_counts,
'model_playbook_kernel_reason_counts': model_playbook_kernel_reason_counts,
'avg_mfe': _average([outcome.mfe for outcome in outcomes]),
'avg_mae': _average([outcome.mae for outcome in outcomes]),
'avg_mfe_r': _average(mfe_r_values),
@@ -1534,6 +1621,7 @@ def summarize_backtest_outcomes(outcomes: list[BacktestOutcome], subject_type: s
'by_liquidity_target_side': grouped_by_liquidity_target_side,
'by_pd_array_type': grouped_by_pd_array_type,
'by_time_session_code': grouped_by_time_session_code,
'by_model_playbook': grouped_by_model_playbook,
}
result['session_performance'] = result['by_session']
return result
+12
View File
@@ -20,21 +20,25 @@ from src.services.ict_mainline.feedback import (
from src.services.ict_mainline.doctrine 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_rule_matrix,
build_doctrine_playbook_gate,
build_doctrine_playbook_registry,
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,
extract_setup_playbook_id,
@@ -42,11 +46,13 @@ from src.services.ict_mainline.doctrine import (
list_doctrine_setup_playbooks,
list_doctrine_rules,
list_liquidity_rules,
list_model_playbook_rules,
list_pd_array_rules,
list_time_session_rules,
summarize_doctrine_backtest_cohorts,
summarize_doctrine_rule_gaps,
summarize_liquidity_kernel_statuses,
summarize_model_playbook_kernel_statuses,
summarize_pd_array_reaction_statuses,
summarize_time_session_kernel_statuses,
)
@@ -54,18 +60,21 @@ from src.services.ict_mainline.doctrine import (
__all__ = [
"DOCTRINE_KERNEL_PHASE",
"DOCTRINE_LIQUIDITY_PHASE",
"DOCTRINE_MODEL_PLAYBOOK_PHASE",
"DOCTRINE_PLAYBOOK_PHASE",
"DOCTRINE_REACTION_PHASE",
"DOCTRINE_SESSION_PHASE",
"ICT_MAINLINE_CHAIN",
"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_rule_matrix",
"build_doctrine_playbook_gate",
"build_doctrine_playbook_registry",
"build_liquidity_kernel_registry",
"build_model_playbook_kernel_registry",
"build_pd_array_reaction_registry",
"build_time_session_kernel_registry",
"build_ict_rulebook_mapping",
@@ -79,6 +88,7 @@ __all__ = [
"classify_ict_concepts",
"evaluate_doctrine_research_status",
"evaluate_liquidity_kernel_status",
"evaluate_model_playbook_kernel_status",
"evaluate_pd_array_reaction_status",
"evaluate_time_session_kernel_status",
"extract_setup_playbook_id",
@@ -87,12 +97,14 @@ __all__ = [
"list_doctrine_setup_playbooks",
"list_doctrine_rules",
"list_liquidity_rules",
"list_model_playbook_rules",
"list_pd_array_rules",
"list_time_session_rules",
"list_ict_concepts",
"summarize_doctrine_backtest_cohorts",
"summarize_doctrine_rule_gaps",
"summarize_liquidity_kernel_statuses",
"summarize_model_playbook_kernel_statuses",
"summarize_pd_array_reaction_statuses",
"summarize_time_session_kernel_statuses",
"summarize_book_rule_errors",
+411
View File
@@ -11,6 +11,7 @@ DOCTRINE_PLAYBOOK_PHASE = "D2_PLAYBOOK_BACKTEST_COHORT"
DOCTRINE_LIQUIDITY_PHASE = "D3_LIQUIDITY_KERNEL"
DOCTRINE_REACTION_PHASE = "D4_PD_ARRAY_REACTION_KERNEL"
DOCTRINE_SESSION_PHASE = "D5_TIME_SESSION_KERNEL"
DOCTRINE_MODEL_PLAYBOOK_PHASE = "D6_MODEL_PLAYBOOK_KERNEL"
@dataclass(frozen=True)
@@ -163,6 +164,34 @@ class TimeSessionRule:
return payload
@dataclass(frozen=True)
class ModelPlaybookRule:
rule_id: str
stage: str
book_anchor: str
model_family: str
ranking_weight: int
required_inputs: tuple[str, ...]
measurable_fields: tuple[str, ...]
no_trade_when: tuple[str, ...]
wait_when: tuple[str, ...]
backtest_observable: str
operator_rule: str
aliases: tuple[str, ...]
def to_public_dict(self) -> dict:
payload = asdict(self)
for key in (
"required_inputs",
"measurable_fields",
"no_trade_when",
"wait_when",
"aliases",
):
payload[key] = list(payload[key])
return payload
D1_DOCTRINE_RULES: tuple[DoctrineRule, ...] = (
DoctrineRule(
rule_id="range_defined",
@@ -796,6 +825,121 @@ D5_TIME_SESSION_RULES: tuple[TimeSessionRule, ...] = (
)
D6_MODEL_PLAYBOOK_RULES: tuple[ModelPlaybookRule, ...] = (
ModelPlaybookRule(
rule_id="model_family_declared",
stage="Model Identity",
book_anchor="ICT trading book models: continuation, reversal, sweep-to-displacement, HTF draw pullback, London/NY model",
model_family="book_derived_model",
ranking_weight=100,
required_inputs=("setup_playbook_id", "model_family", "model_variant"),
measurable_fields=("model_playbook_id", "model_family", "model_variant", "model_stage"),
no_trade_when=("missing_model_playbook", "unknown_model_playbook", "missing_model_family"),
wait_when=(),
backtest_observable="run_without_registered_model_playbook",
operator_rule="A model must point to a registered book-derived playbook before any backtest result can be trusted.",
aliases=("model_playbook_id", "setup_playbook_id", "model_family", "model_variant", "setup_family"),
),
ModelPlaybookRule(
rule_id="full_chain_contract_complete",
stage="Full ICT Chain",
book_anchor="ICT trading book full setup chain: Range/Draw/Liquidity/PD Array/Session/Confirmation/Invalidation/Target",
model_family="full_chain_contract",
ranking_weight=95,
required_inputs=(
"range_contract",
"draw_contract",
"liquidity_contract",
"pd_array_contract",
"session_contract",
"confirmation_contract",
"invalidation_contract",
"target_contract",
),
measurable_fields=(
"range_contract",
"draw_contract",
"liquidity_contract",
"pd_array_contract",
"session_contract",
"confirmation_contract",
"invalidation_contract",
"target_contract",
),
no_trade_when=(
"missing_model_range_contract",
"missing_model_draw_contract",
"missing_model_liquidity_contract",
"missing_model_pd_array_contract",
"missing_model_session_contract",
"missing_model_confirmation_contract",
"missing_model_invalidation_contract",
"missing_model_target_contract",
),
wait_when=(),
backtest_observable="model_playbook_without_full_chain_contract",
operator_rule="A playbook must define every upstream and downstream contract; no single shape is a setup by itself.",
aliases=("range_definition", "draw_rule", "liquidity_event_rule", "pd_array_rule", "session_timing_rule", "target_rule"),
),
ModelPlaybookRule(
rule_id="confirmation_trigger_bound",
stage="Confirmation Trigger",
book_anchor="ICT trading book structure confirmation: displacement, MSS, FVG, and retest must serve the Draw",
model_family="confirmation_chain",
ranking_weight=90,
required_inputs=("confirmation_contract", "displacement_contract", "mss_contract", "fvg_retest_contract"),
measurable_fields=("confirmation_contract", "displacement_contract", "mss_contract", "fvg_retest_contract"),
no_trade_when=("missing_model_confirmation_contract",),
wait_when=("missing_model_trigger_contract",),
backtest_observable="model_without_confirmation_trigger_contract",
operator_rule="The model must state what confirms the setup and what is still only a wait condition.",
aliases=("confirmation_rule", "displacement_structure_rule", "mss", "fvg_retest", "trigger_contract"),
),
ModelPlaybookRule(
rule_id="risk_target_contract_bound",
stage="Risk / Target",
book_anchor="ICT trading book invalidation and IRL/ERL target architecture",
model_family="risk_target_contract",
ranking_weight=90,
required_inputs=("invalidation_contract", "target_contract", "management_contract"),
measurable_fields=("invalidation_contract", "target_contract", "management_contract"),
no_trade_when=("missing_model_invalidation_contract", "missing_model_target_contract"),
wait_when=("missing_model_management_contract",),
backtest_observable="model_without_risk_target_contract",
operator_rule="The playbook must define invalidation and IRL/ERL targets before any historical result can become live focus evidence.",
aliases=("invalidation_rule", "target_rule", "management_rules", "irl", "erl"),
),
ModelPlaybookRule(
rule_id="batch_cohort_contract_ready",
stage="Backtest Cohort",
book_anchor="ICT trading book study/backtesting: test the exact model, session, timeframe, and context cohort",
model_family="batch_backtest_cohort",
ranking_weight=85,
required_inputs=("backtest_cohort_family", "cohort_key_fields", "sample_basis", "quality_gate_contract"),
measurable_fields=("backtest_cohort_family", "cohort_key_fields", "sample_basis", "quality_gate_contract"),
no_trade_when=("missing_model_backtest_cohort",),
wait_when=("missing_model_quality_gate_contract",),
backtest_observable="model_without_batch_backtest_cohort",
operator_rule="Backtest evidence must be grouped by playbook cohort rather than raw model_code.",
aliases=("backtest_cohort", "cohort_family", "cohort_key_fields", "sample_basis", "quality_gate"),
),
ModelPlaybookRule(
rule_id="journal_feedback_contract_ready",
stage="Journal / Study Notes",
book_anchor="ICT trading book study notes and journal feedback loop",
model_family="feedback_contract",
ranking_weight=75,
required_inputs=("journal_error_classes", "study_note_contract", "operator_runbook"),
measurable_fields=("journal_error_classes", "study_note_contract", "operator_runbook"),
no_trade_when=("missing_model_journal_feedback",),
wait_when=("missing_model_operator_runbook",),
backtest_observable="model_without_journal_feedback_contract",
operator_rule="Every model failure must map back to journal and study-note error classes.",
aliases=("journal_error_classes", "study_notes", "operator_runbook", "cockpit_sentence"),
),
)
def list_doctrine_rules() -> list[dict]:
return [rule.to_public_dict() for rule in D1_DOCTRINE_RULES]
@@ -846,6 +990,10 @@ def list_time_session_rules() -> list[dict]:
return [rule.to_public_dict() for rule in D5_TIME_SESSION_RULES]
def list_model_playbook_rules() -> list[dict]:
return [rule.to_public_dict() for rule in D6_MODEL_PLAYBOOK_RULES]
def build_liquidity_kernel_registry() -> dict:
return {
"phase": DOCTRINE_LIQUIDITY_PHASE,
@@ -915,6 +1063,31 @@ def build_time_session_kernel_registry() -> dict:
}
def build_model_playbook_kernel_registry() -> dict:
return {
"phase": DOCTRINE_MODEL_PLAYBOOK_PHASE,
"source": "docs/ICT交易全书_出版终校版.md model playbook chapters and docs/AI_ICT_DoctrineKernel全书规则内核重建_D0_2026-05-21_v1.md",
"status": "active",
"rule_count": len(D6_MODEL_PLAYBOOK_RULES),
"registered_playbook_count": len(D2_DOCTRINE_SETUP_PLAYBOOKS),
"hard_rules": [
"Raw model_code is not a tradable setup.",
"Every model must resolve into a registered setup playbook before promotion.",
"A playbook must include Range, Draw, Liquidity, PD Array, Session, Confirmation, Invalidation, and Target contracts.",
"Batch backtest cohorts must be playbook-based, not shape-label based.",
"Journal and study-note errors must map back to the playbook rules.",
],
"required_backtest_feature_fields": _dedupe(
field
for rule in D6_MODEL_PLAYBOOK_RULES
for field in rule.measurable_fields
),
"rules": list_model_playbook_rules(),
"registered_playbooks": list_doctrine_setup_playbooks(),
"execution_boundary": "read_only_analysis_no_order_submission",
}
def get_doctrine_setup_playbook(setup_playbook_id: str | None) -> dict | None:
key = str(setup_playbook_id or "").strip()
if not key:
@@ -1001,6 +1174,7 @@ def build_doctrine_backtest_cohort(payload: object, *, quality_gate: dict | None
liquidity_status = evaluate_liquidity_kernel_status(payload)
pd_array_status = evaluate_pd_array_reaction_status(payload)
time_session_status = evaluate_time_session_kernel_status(payload)
model_playbook_status = evaluate_model_playbook_kernel_status(payload)
quality = dict(quality_gate or _extract_quality_gate(payload) or {})
quality_reasons = [str(item) for item in list(quality.get("reasons") or []) if item]
quality_passed = bool(quality.get("passed"))
@@ -1016,6 +1190,7 @@ def build_doctrine_backtest_cohort(payload: object, *, quality_gate: dict | None
*([] if liquidity_status.get("passed") else [str(item) for item in list(liquidity_status.get("reasons") or []) if item]),
*([] if pd_array_status.get("passed") else [str(item) for item in list(pd_array_status.get("reasons") or []) if item]),
*([] if time_session_status.get("passed") else [str(item) for item in list(time_session_status.get("reasons") or []) if item]),
*([] if model_playbook_status.get("passed") else [str(item) for item in list(model_playbook_status.get("reasons") or []) if item]),
*([] if quality_passed else quality_reasons),
]
promotion_reasons = _dedupe(promotion_reasons)
@@ -1035,6 +1210,8 @@ def build_doctrine_backtest_cohort(payload: object, *, quality_gate: dict | None
"pd_array_backtest_features": dict(pd_array_status.get("features") or {}),
"time_session_kernel_status": time_session_status,
"time_session_backtest_features": dict(time_session_status.get("features") or {}),
"model_playbook_kernel_status": model_playbook_status,
"model_playbook_backtest_features": dict(model_playbook_status.get("features") or {}),
"promotion_gate": {
"passed": not promotion_reasons,
"reasons": promotion_reasons,
@@ -1042,6 +1219,7 @@ def build_doctrine_backtest_cohort(payload: object, *, quality_gate: dict | None
"requires_liquidity_kernel_complete": True,
"requires_pd_array_reaction_complete": True,
"requires_time_session_kernel_complete": True,
"requires_model_playbook_kernel_complete": True,
"requires_backtest_quality_gate": True,
"requires_active_trading_plan": True,
"execution_boundary": "read_only_analysis_no_order_submission",
@@ -1072,6 +1250,8 @@ def summarize_doctrine_backtest_cohorts(items: Iterable[dict]) -> dict:
"promotion_blocker_counts": {},
"liquidity_status_counts": {},
"pd_array_status_counts": {},
"time_session_status_counts": {},
"model_playbook_status_counts": {},
},
)
bucket["run_count"] += 1
@@ -1083,6 +1263,10 @@ def summarize_doctrine_backtest_cohorts(items: Iterable[dict]) -> dict:
bucket["liquidity_status_counts"][liquidity_status] = int(bucket["liquidity_status_counts"].get(liquidity_status) or 0) + 1
pd_array_status = str((cohort.get("pd_array_reaction_status") or {}).get("status") or "unknown")
bucket["pd_array_status_counts"][pd_array_status] = int(bucket["pd_array_status_counts"].get(pd_array_status) or 0) + 1
time_session_status = str((cohort.get("time_session_kernel_status") or {}).get("status") or "unknown")
bucket["time_session_status_counts"][time_session_status] = int(bucket["time_session_status_counts"].get(time_session_status) or 0) + 1
model_playbook_status = str((cohort.get("model_playbook_kernel_status") or {}).get("status") or "unknown")
bucket["model_playbook_status_counts"][model_playbook_status] = int(bucket["model_playbook_status_counts"].get(model_playbook_status) or 0) + 1
for reason in list((cohort.get("promotion_gate") or {}).get("reasons") or []):
reason_key = str(reason)
bucket["promotion_blocker_counts"][reason_key] = int(bucket["promotion_blocker_counts"].get(reason_key) or 0) + 1
@@ -1651,6 +1835,225 @@ def summarize_time_session_kernel_statuses(items: Iterable[dict]) -> dict:
}
def build_model_playbook_backtest_features(payload: object) -> dict:
model_playbook_id = _string_or_empty(
_extract_first_present_value(
payload,
keys=("model_playbook_id", "setup_playbook_id", "doctrine_setup_id", "playbook_id"),
)
)
playbook = get_doctrine_setup_playbook(model_playbook_id)
playbook_backtest_cohort = dict((playbook or {}).get("backtest_cohort") or {})
playbook_promotion_gate = dict((playbook or {}).get("promotion_gate") or {})
model_family = _string_or_empty(
_extract_first_present_value(
payload,
keys=("model_family", "playbook_model_family", "setup_family", "cohort_family"),
)
or playbook_backtest_cohort.get("cohort_family")
)
model_variant = _string_or_empty(
_extract_first_present_value(payload, keys=("model_variant", "setup_variant", "model_name"))
or (playbook or {}).get("setup_name")
)
cohort_key_fields = _as_string_list(
_extract_first_present_value(payload, keys=("cohort_key_fields",))
or playbook_backtest_cohort.get("cohort_key_fields")
)
model_timeframe_contract = _as_string_list(
_extract_first_present_value(payload, keys=("model_timeframe_contract", "timeframe_contract"))
or [field for field in cohort_key_fields if str(field).endswith("_tf")]
)
journal_error_classes = _as_string_list(
_extract_first_present_value(payload, keys=("journal_error_classes", "journal_errors", "book_rule_error_classes"))
or (playbook or {}).get("journal_error_classes")
)
target_contract = _contract_fields(payload, playbook, "target_contract", "target_rule")
features = {
"model_playbook_id": model_playbook_id,
"model_playbook_known": bool(playbook),
"model_family": model_family,
"model_variant": model_variant,
"model_stage": _string_or_empty(
_extract_first_present_value(payload, keys=("model_stage", "playbook_stage"))
or ("registered_setup_playbook" if playbook else "")
),
"model_timeframe_contract": model_timeframe_contract,
"required_rule_ids": _as_string_list((playbook or {}).get("required_rule_ids")),
"range_contract": _contract_fields(payload, playbook, "range_contract", "range_definition"),
"draw_contract": _contract_fields(payload, playbook, "draw_contract", "draw_rule"),
"liquidity_contract": _contract_fields(payload, playbook, "liquidity_contract", "liquidity_event_rule"),
"pd_array_contract": _contract_fields(payload, playbook, "pd_array_contract", "pd_array_rule"),
"session_contract": _contract_fields(payload, playbook, "session_contract", "session_timing_rule"),
"confirmation_contract": _contract_fields(payload, playbook, "confirmation_contract", "confirmation_rule"),
"displacement_contract": _contract_fields(payload, playbook, "displacement_contract", "displacement_structure_rule"),
"mss_contract": _as_string_list(
_extract_first_present_value(payload, keys=("mss_contract",))
or _contract_fields(payload, playbook, "confirmation_contract", "confirmation_rule")
),
"fvg_retest_contract": _as_string_list(
_extract_first_present_value(payload, keys=("fvg_retest_contract",))
or _contract_fields(payload, playbook, "pd_array_contract", "pd_array_rule")
),
"invalidation_contract": _contract_fields(payload, playbook, "invalidation_contract", "invalidation_rule"),
"target_contract": target_contract,
"management_contract": _as_string_list(
_extract_first_present_value(payload, keys=("management_contract", "management_rules"))
or [field for field in target_contract if "management" in str(field).lower()]
),
"backtest_cohort_family": _string_or_empty(
_extract_first_present_value(payload, keys=("backtest_cohort_family", "cohort_family"))
or playbook_backtest_cohort.get("cohort_family")
),
"cohort_key_fields": cohort_key_fields,
"sample_basis": _string_or_empty(
_extract_first_present_value(payload, keys=("sample_basis",))
or playbook_backtest_cohort.get("sample_basis")
),
"quality_gate_contract": _as_string_list(
_extract_first_present_value(payload, keys=("quality_gate_contract",))
or (
["requires_backtest_quality_gate"]
if playbook_promotion_gate.get("requires_backtest_quality_gate")
else []
)
),
"journal_error_classes": journal_error_classes,
"study_note_contract": _as_string_list(
_extract_first_present_value(payload, keys=("study_note_contract", "study_notes_contract"))
or journal_error_classes
),
"operator_runbook": _string_or_empty(
_extract_first_present_value(payload, keys=("operator_runbook", "playbook_operator_runbook"))
or (playbook or {}).get("cockpit_sentence")
),
}
features["has_full_chain_contracts"] = all(
bool(features.get(key))
for key in (
"range_contract",
"draw_contract",
"liquidity_contract",
"pd_array_contract",
"session_contract",
"confirmation_contract",
"invalidation_contract",
"target_contract",
)
)
features["has_batch_backtest_cohort"] = bool(
features["backtest_cohort_family"] and features["cohort_key_fields"] and features["sample_basis"]
)
features["has_journal_feedback_contract"] = bool(features["journal_error_classes"] and features["study_note_contract"])
return features
def evaluate_model_playbook_kernel_status(payload: object) -> dict:
features = build_model_playbook_backtest_features(payload)
no_trade_reasons: list[str] = []
wait_reasons: list[str] = []
if not features["model_playbook_id"]:
no_trade_reasons.append("missing_model_playbook")
elif not features["model_playbook_known"]:
no_trade_reasons.append("unknown_model_playbook")
if not features["model_family"]:
no_trade_reasons.append("missing_model_family")
for contract_key, reason in (
("range_contract", "missing_model_range_contract"),
("draw_contract", "missing_model_draw_contract"),
("liquidity_contract", "missing_model_liquidity_contract"),
("pd_array_contract", "missing_model_pd_array_contract"),
("session_contract", "missing_model_session_contract"),
("confirmation_contract", "missing_model_confirmation_contract"),
("invalidation_contract", "missing_model_invalidation_contract"),
("target_contract", "missing_model_target_contract"),
):
if not features.get(contract_key):
no_trade_reasons.append(reason)
if not features["model_timeframe_contract"]:
wait_reasons.append("missing_model_timeframe_contract")
if not features["displacement_contract"] or not features["mss_contract"] or not features["fvg_retest_contract"]:
wait_reasons.append("missing_model_trigger_contract")
if not features["management_contract"]:
wait_reasons.append("missing_model_management_contract")
if not features["has_batch_backtest_cohort"]:
no_trade_reasons.append("missing_model_backtest_cohort")
elif not features["quality_gate_contract"]:
wait_reasons.append("missing_model_quality_gate_contract")
if not features["has_journal_feedback_contract"]:
no_trade_reasons.append("missing_model_journal_feedback")
elif not features["operator_runbook"]:
wait_reasons.append("missing_model_operator_runbook")
reasons = _dedupe([*no_trade_reasons, *wait_reasons])
passed = not reasons
if passed:
status = "model_playbook_mapped"
elif no_trade_reasons:
status = "study_only"
else:
status = "wait"
return {
"phase": DOCTRINE_MODEL_PLAYBOOK_PHASE,
"status": status,
"passed": passed,
"reason": "" if passed else reasons[0],
"reasons": reasons,
"no_trade_reasons": _dedupe(no_trade_reasons),
"wait_reasons": _dedupe(wait_reasons),
"features": features,
"can_promote_to_playbook": passed,
"operator_summary": (
"Model resolves to a full book-derived setup playbook with batch cohort and feedback contracts."
if passed
else "Keep this model study-only until it maps to a registered full-chain playbook and cohort contract."
),
"execution_boundary": "read_only_analysis_no_order_submission",
}
def summarize_model_playbook_kernel_statuses(items: Iterable[dict]) -> dict:
reason_counts: dict[str, int] = {}
status_counts: dict[str, int] = {}
playbook_counts: dict[str, int] = {}
model_family_counts: dict[str, int] = {}
total = 0
passed_count = 0
for item in items:
total += 1
status = dict(item.get("model_playbook_kernel_status") or evaluate_model_playbook_kernel_status(item))
status_key = str(status.get("status") or "unknown")
status_counts[status_key] = int(status_counts.get(status_key) or 0) + 1
if status.get("passed"):
passed_count += 1
features = dict(status.get("features") or {})
playbook_id = str(features.get("model_playbook_id") or "missing_model_playbook")
model_family = str(features.get("model_family") or "missing_model_family")
playbook_counts[playbook_id] = int(playbook_counts.get(playbook_id) or 0) + 1
model_family_counts[model_family] = int(model_family_counts.get(model_family) or 0) + 1
for reason in list(status.get("reasons") or []):
key = str(reason)
reason_counts[key] = int(reason_counts.get(key) or 0) + 1
return {
"phase": DOCTRINE_MODEL_PLAYBOOK_PHASE,
"item_count": total,
"passed_count": passed_count,
"blocked_count": max(0, total - passed_count),
"status_counts": status_counts,
"reason_counts": reason_counts,
"playbook_counts": playbook_counts,
"model_family_counts": model_family_counts,
"operator_summary": (
"No backtest items available for Model Playbook review."
if not total
else "Review Model Playbook blockers before trusting raw model_code backtests."
),
}
def evaluate_doctrine_research_status(
payload: object,
*,
@@ -1823,6 +2226,14 @@ def _missing_playbook_sections(playbook: dict) -> list[str]:
return missing
def _contract_fields(payload: object, playbook: dict | None, payload_key: str, playbook_section: str) -> list[str]:
payload_fields = _as_string_list(_extract_first_present_value(payload, keys=(payload_key,)))
if payload_fields:
return payload_fields
section = dict((playbook or {}).get(playbook_section) or {})
return _as_string_list(section.get("required_fields"))
def _extract_quality_gate(payload: object) -> dict | None:
if isinstance(payload, dict):
quality_gate = payload.get("quality_gate")
+38 -2
View File
@@ -5561,6 +5561,17 @@ function renderStrategyBacktests(payload) {
doctrineCohort.time_session_backtest_features ||
timeSessionStatus.features ||
{};
const modelPlaybookStatus =
latest.model_playbook_kernel_status ||
summary.model_playbook_kernel_status ||
doctrineCohort.model_playbook_kernel_status ||
{};
const modelPlaybookFeatures =
latest.model_playbook_backtest_features ||
summary.model_playbook_backtest_features ||
doctrineCohort.model_playbook_backtest_features ||
modelPlaybookStatus.features ||
{};
strategyBacktestPanel.appendChild(
buildInfoGrid([
@@ -5583,6 +5594,10 @@ function renderStrategyBacktests(payload) {
['Time / Session Reason', formatBacktestDecision(timeSessionStatus.reason || 'missing_session_window')],
['Session', timeSessionFeatures.session_code || 'missing_session_window'],
['Killzone', timeSessionFeatures.killzone_name || 'missing_killzone'],
['Model Playbook Kernel', formatBacktestDecision(modelPlaybookStatus.status || 'study_only')],
['Model Playbook Reason', formatBacktestDecision(modelPlaybookStatus.reason || 'missing_model_playbook')],
['Model Playbook', modelPlaybookFeatures.model_playbook_id || 'missing_model_playbook'],
['Model Family', modelPlaybookFeatures.model_family || 'missing_model_family'],
['Promotion Gate', doctrinePromotionGate.passed ? 'Passed' : 'Blocked'],
['Instrument ID', latest.instrument_id ?? 'n/a'],
['Timeframe', formatJsonInline(latest.timeframe_set || {})],
@@ -5632,6 +5647,13 @@ function renderStrategyBacktests(payload) {
formatList((timeSessionStatus.reasons || []).map(formatBacktestDecision), 'Time / Session chain mapped')
)
);
cards.appendChild(
buildMetricCard(
'Model Playbook Kernel',
modelPlaybookStatus.passed ? 'Mapped' : 'Blocked',
formatList((modelPlaybookStatus.reasons || []).map(formatBacktestDecision), 'Model Playbook chain mapped')
)
);
strategyBacktestPanel.appendChild(cards);
strategyBacktestPanel.appendChild(buildBacktestCurve(summary.cumulative_r_curve || []));
@@ -5673,6 +5695,15 @@ function renderStrategyBacktests(payload) {
.map((item) => [formatBacktestDecision(item.key), String(item.value)])
)
);
detailGrid.appendChild(
buildItemCard(
'Model Playbook Kernel',
`${payload?.model_playbook_kernel_summary?.passed_count ?? 0} mapped / ${payload?.model_playbook_kernel_summary?.blocked_count ?? 0} blocked`,
topCountEntries(payload?.model_playbook_kernel_summary?.reason_counts || summary.model_playbook_kernel_reason_counts || {})
.slice(0, 5)
.map((item) => [formatBacktestDecision(item.key), String(item.value)])
)
);
detailGrid.appendChild(
buildItemCard(
'PD Array Kernel',
@@ -5753,8 +5784,13 @@ function topCountEntries(countMap) {
}
function formatBacktestDecision(value) {
const text = String(value || 'n/a').replace(/_/g, ' ');
return text.replace(/\b\w/g, (letter) => letter.toUpperCase());
const raw = String(value || 'n/a');
const translate = window.I18N?.t;
const translatedRaw = typeof translate === 'function' ? translate(raw) : raw;
if (translatedRaw !== raw) return translatedRaw;
const text = raw.replace(/_/g, ' ');
const title = text.replace(/\b\w/g, (letter) => letter.toUpperCase());
return typeof translate === 'function' ? translate(title) : title;
}
function renderLiveReadiness(payload) {
+38 -1
View File
@@ -90,6 +90,11 @@
'Time / Session Kernel': '时间 / 时段内核',
'Time / Session Reason': '时间 / 时段原因',
'Time / Session chain mapped': '时间 / 时段链路已映射',
'Model Playbook Kernel': '模型 Playbook 内核',
'Model Playbook Reason': '模型 Playbook 原因',
'Model Playbook': '模型 Playbook',
'Model Family': '模型族',
'Model Playbook chain mapped': '模型 Playbook 链路已映射',
'Killzone': 'Killzone 时段',
'Mapped': '已映射',
'mapped': '已映射',
@@ -810,7 +815,7 @@
['missing_playbook', '缺少 playbook'],
['Doctrine Cohort', 'Doctrine 回测分组'],
['Doctrine Cohorts', 'Doctrine 回测分组'],
['Setup Playbook', 'Setup Playbook'],
['Setup Playbook', 'Setup 剧本'],
['Doctrine Completeness', 'Doctrine 完整度'],
['Liquidity Kernel', '流动性内核'],
['Liquidity Reason', '流动性原因'],
@@ -820,7 +825,39 @@
['Time / Session Kernel', '时间 / 时段内核'],
['Time / Session Reason', '时间 / 时段原因'],
['Time / Session chain mapped', '时间 / 时段链路已映射'],
['Model Playbook Kernel', '模型 Playbook 内核'],
['Model Playbook Reason', '模型 Playbook 原因'],
['Model Playbook', '模型 Playbook'],
['Model Family', '模型族'],
['Model Playbook chain mapped', '模型 Playbook 链路已映射'],
['model_playbook_mapped', '模型 Playbook 已映射'],
['model_playbook_kernel_blocked', '模型 Playbook 内核阻塞'],
['missing_model_playbook', '缺少模型 Playbook'],
['unknown_model_playbook', '未知模型 Playbook'],
['missing_model_family', '缺少模型族'],
['missing_model_range_contract', '缺少模型区间合同'],
['missing_model_draw_contract', '缺少模型 Draw 合同'],
['missing_model_liquidity_contract', '缺少模型流动性合同'],
['missing_model_pd_array_contract', '缺少模型 PD Array 合同'],
['missing_model_session_contract', '缺少模型时段合同'],
['missing_model_confirmation_contract', '缺少模型确认合同'],
['missing_model_invalidation_contract', '缺少模型失效合同'],
['missing_model_target_contract', '缺少模型目标合同'],
['missing_model_timeframe_contract', '缺少模型周期合同'],
['missing_model_trigger_contract', '缺少模型触发合同'],
['missing_model_management_contract', '缺少模型管理合同'],
['missing_model_backtest_cohort', '缺少模型回测分组'],
['missing_model_quality_gate_contract', '缺少模型质量门合同'],
['missing_model_journal_feedback', '缺少模型复盘反馈合同'],
['missing_model_operator_runbook', '缺少模型操作话术'],
['Killzone', 'Killzone 时段'],
['cohorts', '个分组'],
['cohort', '个分组'],
['runs', '次运行'],
['run', '次运行'],
['passed', '通过'],
['mapped', '已映射'],
['blocked', '已阻塞'],
['Promotion Gate', '晋级门'],
['complete', '完整'],
['incomplete', '不完整'],
+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 {