Add doctrine model playbook kernel
This commit is contained in:
+2
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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 |
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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', '不完整'],
|
||||
|
||||
@@ -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