name: mark-spec-fram-feat-fork description: Add and validate market-specific frame-feature label forks in ict-engine when raw pre-Bayes labels are wrong even though factor signal quality is strong.
When to use
- A latest-sample debug run shows strong factor signal but raw pre-Bayes labels are clearly mismatched with multi-timeframe evidence.
- Example pattern: latest signal is bullish with strong HTF alignment, but raw build_frame_features labels still come out bear/hostile and force observe_only.
- Use especially in ~/projects-ict-engine/ict-engine for structure_ict / expansion_manipulation work.
Workflow
Prove the issue is upstream labeling, not factor weakness.
- Run factor-pipeline-debug first.
- Check latest_signal, raw_pre_bayes_labels, filtered_pre_bayes_labels, gating_status, and raw_label_trace.
- If factor signal is still strong but raw regime/liquidity are wrong, treat build_frame_features as the target surface instead of blindly mutating factor params.
Add a narrow market-aware fork at the frame-feature layer.
- Extend FrameFeatures with:
- market: Option
- market: Option
- Keep build_frame_features(candles) as the baseline generic builder.
- Add:
- infer_market_from_symbol(symbol: &str) -> String
- build_frame_features_for_market(candles: &[Candle], market: Option<&str>) -> Result
- In the market-aware wrapper, call the generic builder first, then overwrite only the labels that need market-specific handling.
- Extend FrameFeatures with:
Start with pragmatic heuristics, not broad rewrites.
- For NQ, the successful pattern was:
- if sweep_count > fvg_count * 2, set regime_label = "range"
- if base liquidity_label == "hostile" and sweep_count > 0 and fvg_count > 0, set liquidity_label = "neutral"
- Preserve the original counts/evidence so the debug report still shows why the relabel happened.
- For NQ, the successful pattern was:
Wire only the paths that need market-aware labels first.
- The important production/debug surfaces were the expansion factor pipeline builders:
- build_expansion_factor_pipeline_report(...)
- build_expansion_factor_pipeline_report_from_registry(...)
- Pass symbol-derived market via infer_market_from_symbol(symbol), not factor_name.
- Avoid global replacement based on factor_name; factor_name like structure_ict is not the market.
- The important production/debug surfaces were the expansion factor pipeline builders:
4.5. Once frame-feature and pre-bayes market forks exist, do not stop at labels if the product goal is market-specific trading behavior.
- Extend the Symbol surface itself if coverage is incomplete.
- In this repo, the next necessary step after NQ/ES/YM was adding
GC/CLtoSymbol,parse_symbol(...), and trade-plan symbol parsing.
- In this repo, the next necessary step after NQ/ES/YM was adding
- Group symbols into reusable market families instead of hard-coding per-symbol behavior everywhere:
NQ/ES/YM -> futures_indexGC -> metalsCL -> energy
- Keep symbol-level heuristics in
build_frame_features_for_market(...), but shift belief/gate/report behavior to market-family policy so later symbols can inherit the right regime behavior cheaply. - The reusable sequence that worked was:
- expand
Symbol - patch every symbol parser/caller
- add missing frame-feature overrides (
YM,GChere) - expand pre-bayes market overrides (
NQ/YM/GChere) - inject
market_categoryandmarket_behavior_profileinto canonical belief packet evidence - only then make posterior/gate/strategy/temporal surfaces market-family aware
- make exact/shadow belief node posteriors family-aware too (entry_quality, trade_outcome, risk_posture, market-profile node, shadow family weights)
- expose
market_family,market_behavior_profile, andselected_market_subgraphon report surfaces and human-facing output - if backtest/history is unnecessary for a symbol, prefer proving the path with
analyze-liveinstead of blocking on cleaned historical data - once online defaults are known, make
analyze-live --symbol <SYM>auto-inferfutures_symbol/spot_symbol/options_symbol/spot_kind
- expand
- For this repo, reusable live defaults discovered were:
GC -> GC=F / GLD / GLD / etfCL -> CL=F / USO / USO / etfNQ -> NQ=F / QQQ / QQQ / equityES -> ES=F / SPY / SPY / equityYM -> YM=F / DIA / DIA / equity
Expect collateral compile fixes after signature changes.
Expect collateral compile fixes after signature changes.
- In this repo, once objective/pipeline helpers changed, dependent call sites needed cleanup.
- Typical fixes included:
- passing symbol into apply_expansion_manipulation_objective(...)
- threading symbol through build_factor_mutation_metric_set(...)
- using report.workflow_snapshot.symbol where only a report object is available
- extending AnalyzeBuildContext with
symbol: &strso analyze/backtest paths can derive market-specific policy - updating all AnalyzeBuildContext construction sites after adding the new field
- adding
None/Some(market)to every build_pre_bayes_evidence_filter(...) test and call site after the market arg was introduced
- Re-run cargo check after each batch instead of stacking many edits blindly.
Validate with both tests and real data.
- Add at least one focused unit test around build_frame_features_for_market.
- If pre-Bayes filter now accepts market, update legacy tests/callers to pass
Noneexplicitly so cargo test failures are resolved quickly. - Then run:
- cargo fmt
- cargo check
- cargo test
- cargo run -- factor-pipeline-debug --symbol NQ --data ~/Downloads/Tomac/ict-cleaned-mtf/cleaned-15m/nq.continuous-15m.json --factor structure_ict --objective expansion_manipulation
Push market-family semantics through canonical belief, not just frame labels.
- Once
market_categoryandmarket_behavior_profileexist in packet evidence, make the canonical belief path consume them directly. - The reusable sequence that worked here was:
- extend
RegimePosteriorwithmarket_familyandmarket_behavior_profile - extend
RegimeGateDecisionwithmarket_family - derive posterior from the belief packet, not only from a generic pre-bayes filter
- change
selected_subgraphfrom<regime>_subgraphto<market_family>_<regime>_subgraphwhen family is known
- extend
- In this repo, practical family behavior was:
metals-> add stress biasenergy-> add transition + stress biasfutures_index-> preserve baseline trend path
- Once
Carry market-family behavior into downstream belief/report surfaces.
- Do not stop at posterior/gate.
- Also thread market-family into:
StrategyRecommendationParticleBeliefSummaryBeliefReportPacket- human-facing analyze output
- Useful reusable fields were:
market_familymarket_behavior_profileselected_market_subgraph
- In this repo, strategy sizing was reduced for
energy, lightly reduced formetals, and left baseline forfutures_index. bootstrap_particle_summary(...)also became family-aware:energy-> larger particle budget / ESSmetals-> medium upliftfutures_index-> baseline
belief_posteriorsand shadow/sampling outputs can safely carry compact market-weight hints such as:market_family_weightshadow_market_family_weight
Expose market-family conclusions in final product surfaces so the change is inspectable.
- Add explicit report fields, not only hidden internal behavior.
- In this repo, useful surfacing points were:
BeliefReportPacket.market_familyBeliefReportPacket.market_behavior_profileBeliefReportPacket.selected_market_subgraph- analyze JSON top-level
market_family_summary - human report string including
market_family=... market_profile=... subgraph=...
- This matters because otherwise the architecture changes are real but invisible to downstream users/agents.
Validate code-path success separately from data-path success.
- After market-family code lands, inspect the local cleaned-data roots before assuming a symbol is runnable.
- In this repo, the critical findings were:
- repo-local
state/initially existed only forNQ - repo-local
data/did not exist - analyze via
--data-roothard-requirescleaned-1d,cleaned-1h, andcleaned-15m
- repo-local
- Therefore a 15m-only root will fail analyze even if 15m data exists.
- Check the exact root shape first using filesystem commands; do not assume sibling intervals exist.
- For commodities, map practical local aliases before blocking on perfect symbol purity.
- In this environment, gold cleaned files existed as
xau.continuous-*, while product symbol support was added asGC. - A pragmatic bridge that worked was:
- build a temporary clean root with copied files renamed from
xau.continuous-{15m,1h,1d}.jsontogc.continuous-{15m,1h,1d}.json - run
analyze --symbol GC --data-root <temp-root> --market gc --state-dir <repo-state>
- build a temporary clean root with copied files renamed from
- This is acceptable for pipeline validation when the market family is what matters and no true GC-cleaned set is present yet.
- Record clearly that this validates code-path and state generation, not final data purity.
- Treat missing
CLdata as a data-source blocker, not a code blocker — unless live analysis is acceptable.
- In this repo, after full-code integration:
GCcould be validated by aliasingxaudataCLstill could not run on local historical roots because no localcl/wti/crudecleaned data was found
- But if the user does not need backtest/history for that symbol, do not block on cleaned inputs.
- Prefer proving the path with
analyze-live:ict-engine analyze-live --symbol GC --futures-symbol GC=F --spot-symbol GLD --options-symbol GLD --spot-kind etf ...ict-engine analyze-live --symbol CL --futures-symbol CL=F --spot-symbol USO --options-symbol USO --spot-kind etf ...
- This successfully generated
state/GCandstate/CLand verified:GC -> metalsCL -> energy
- Once these defaults are known, push them into the command surface so users no longer need to pass the full live tuple every time.
- Make
analyze-liveauto-infer market defaults from--symbolwhen the mappings are stable.
- In this repo, a reusable implementation pattern was:
- change
AnalyzeLiveCLI fields from requiredStringto optionalOption<String>for:futures_symbolspot_symboloptions_symbolspot_kind
- in
analyze_live_command(...), derive defaults fromsymbol.to_ascii_uppercase() - fill missing args from the inferred tuple and only error if no mapping exists
- change
- Validated default tuples were:
NQ -> NQ=F / QQQ / QQQ / equityES -> ES=F / SPY / SPY / equityYM -> YM=F / DIA / DIA / equityGC -> GC=F / GLD / GLD / etfCL -> CL=F / USO / USO / etf
- After this patch, the command surface simplified to:
ict-engine analyze-live --symbol GCict-engine analyze-live --symbol CL
- Also verify the generated help text changes from requiring explicit futures/spot args to:
Usage: ict-engine analyze-live [OPTIONS] --symbol <SYMBOL>
- Human-facing market-specific productization should not stop at the regime block.
- Once market-family routing works, the raw labels are still too mechanical for product use.
- In this repo, the useful next productization step was to rewrite the first three human-report blocks as family-aware prose:
metals- price action: emphasize defensive liquidity and waiting for the post-sweep return to trend
- technicals: emphasize mean-reversion then secondary confirmation
- SMT: say correlation is only supportive; own-symbol liquidity reaction dominates
energy- price action: emphasize shock risk, false breaks, violent reversals
- technicals: note indicators can be exaggerated by volatility and need rhythm confirmation
- SMT: reduce confidence when related markets diverge because volatility often spreads across the complex
- Keep the original machine label appended as
原始标签=...so debugging remains possible. - Example validated outputs were:
金属结构偏向:偏多,但不宜追。这类盘先看流动性是否被扫完,再等回到顺势一侧;原始标签=...能源结构偏向:空头占优,但随时防剧烈反抽。这类盘最怕突发冲击,先防假突破和急反转;原始标签=...
- In giant repeated output blocks, patch both analyze and analyze-live together only when the match is exact and verified.
- In this repo, the human report assembly existed in more than one near-identical block.
- Safe pattern:
- first patch one exact full block with unique function context (e.g.
fn emit_analyze_output) - then patch the second block via
replace_all=trueonly after confirming the full string really matches both occurrences
- first patch one exact full block with unique function context (e.g.
- If live refresh fails during verification, distinguish code success from source instability.
- Here, code changes were correct, but live validation temporarily failed due to upstream Yahoo timeout/parse errors, not because the human-report patch was wrong.
- For Yahoo/OpenBB-backed live fetches, a minimal retry loop can be worth adding before more invasive fallback work.
- In this repo, a simple 3-attempt retry with short sleep around the blocking chart request materially reduced transient failures.
- Keep it local to the request site first; do not over-generalize retry abstractions prematurely.
- Verification should include refreshing fresh live JSON after the retry patch, not only
cargo check.
- Route no-superior-mutation outcomes away from blind global tuning.
- When evaluating expansion_manipulation mutations, treat this case explicitly:
- score_delta <= 0.0
- and no pre_bayes_gate_regressed
- Add a dedicated failure tag such as:
- no_superior_mutation_found
- Then route action-plan state changes to reflect the real conclusion instead of a generic rejection.
- The useful state mapping in ict-engine was:
- factor_mutation_evaluation -> near_local_optimum
- factor_mutation_focus -> pivot_to_label_refinement_or_market_specific_fork
- Also extend recommended_mutation_directions_from_failure_tags(...) so this outcome produces guidance that says:
- stop blind global tuning
- treat the default as near-local-optimum unless stronger evidence appears
- shift the next cycle to label refinement or market-specific fork validation
- For post-stagnation factor-autoresearch, jump clusters must be real spec surfaces, not empty labels.
- A reusable failure pattern in ict-engine was:
- repeated
best_factor_composite_regressed - plus
no_superior_mutation_found
- repeated
- A useful first repair was adding a forced jump-template path instead of continuing
structure_ict:next:next...narrow drift. - The forced jump spec should carry at least:
cluster_jumpcluster_jump_cycleavailable_clustersmarket_specific_fork
- Persist
cluster_jump_cyclein the generated spec itself so the next autoresearch iteration can rotate families instead of resetting to the first cluster.
- Do not keep every cluster on
structure_ictif the cluster semantics imply another factor family.
- The important ict-engine-specific lesson was:
displacement_fvg_cluster-> keepbase_factor = structure_ictmss_bos_cluster-> keepbase_factor = structure_ictpremium_discount_ote_cluster-> keepbase_factor = structure_ictsmt_cluster-> switchbase_factor = cross_market_smt
- Otherwise SMT becomes only a renamed hint on the old factor and the loop never actually explores the SMT factor family.
- Give each post-stagnation cluster its own real parameter map.
- In ict-engine, the reusable v1 cluster-specific overrides were:
displacement_fvg_clusterpost_sweep_displacement_weight = 1.35sweep_weight = 1.10unconfirmed_sweep_weight = 0.45expansion_threshold = 1.05
mss_bos_clusterlookback = 10.0expansion_threshold = 1.18sweep_return_bars = 5.0opposing_sweep_penalty = 1.25
premium_discount_ote_clusterlookback = 14.0expansion_threshold = 0.92sweep_recency_bars = 8.0sweep_return_bars = 6.0
smt_clusterbase_factor = cross_market_smtlookback = 24.0sweep_atr_multiplier = 0.60sweep_weight = 0.72opposing_sweep_penalty = 1.05
- This is still only a spec-surface improvement, not proof of trading value, but it prevents fake cluster diversity.
- Validate cluster rotation from persisted attempt history, not only from final summary JSON.
- A real failure mode encountered was background/long-running autoresearch ending without a final stdout summary or with truncated
/tmp/*.jsonoutput. - The durable verification path was reading:
state_<...>/NQ/factor_autoresearch_attempts.json
- Confirm actual rotation by checking successive attempts for:
candidate_mutation_spec.direction_hints.cluster_jumpcandidate_mutation_spec.direction_hints.cluster_jump_cycle
- In the validated run, attempts rotated as:
mss_bos_cluster-> cycle 2premium_discount_ote_cluster-> cycle 3smt_cluster-> cycle 4displacement_fvg_cluster-> cycle 5
- This is the correct proof that the controller stopped narrow same-family looping, even if every attempt was still discarded.
- Productize latest-sample debug into SOP outputs when handoff/agent guidance depends on the full chain.
- If the real need is not just standalone
factor-pipeline-debugbut making SOP outputs agent-ready, embed the latest-sample debug directly in the SOP report structs. - In this repo, the reusable pattern was:
- add
recommended_global_pipeline_debug: Option<FactorPipelineDebugReport>to bothFuturesSopReportandExpansionSopReport - mark with
#[serde(skip_serializing_if = "Option::is_none")] - populate it from the market whose
best_factormatchesrecommended_global_factorand whose pipeline is present
- add
- Reuse
build_factor_pipeline_debug_report(...)rather than building a second ad-hoc summary.
- Add an explicit pipeline verdict layer rather than forcing downstream agents to infer gate state manually.
- Extend
FactorPipelineDebugReportwithpipeline_verdict: String. - A practical verdict mapping used successfully here:
pass_hardand bridge probability gap >= 0.20 ->clear_through_pre_bayes_and_bridgepass_neutralized->pre_bayes_pass_but_bridge_needs_confirmationobserve_only->blocked_at_pre_bayes_gate- anything else ->
pipeline_unclear
- Keep the raw fields too; verdict is a shortcut, not a replacement for evidence.
- Preserve or store multi-timeframe context explicitly when embedding debug outside the standalone command path.
- Standalone
factor-pipeline-debugrebuilds multi-timeframe summary from CLI inputs, but SOP embedding may not have that context available later. - In this repo,
FuturesSopMarketReportalready carriedmulti_timeframe_summary, butExpansionMarketReportdid not. - Fix by adding
multi_timeframe_summary: Vec<String>toExpansionMarketReportand storing it at construction time, then pass&market.multi_timeframe_summaryintobuild_factor_pipeline_debug_report(...). - Do not guess nonexistent fields on
ExpansionBbnSupport; compile first and inspect structs.
- After adding report fields, patch test fixture initializers immediately.
- Rust test fixtures constructing report structs will fail on missing fields even when production code is correct.
- After adding embedded debug/report fields, update existing unit-test initializers with
recommended_global_pipeline_debug: Noneor the appropriate fixture. - This was required here for
FuturesSopReportandExpansionSopReporttest initializers beforecargo testwould pass.
- Prefer compact native commands over skills/MCP first when token reduction is the actual objective.
- In ict-engine, the cheapest path was not more prompt engineering; it was adding low-token CLI surfaces that expose only decision-critical state.
- Implement compact commands/views in this order:
factor-pipeline-debug --compactnext-actionresearch-compactmarket-fork-status
- Keep full JSON reports for persistence, but expose compact views for agent consumption.
- Design compact outputs around fields an agent can act on immediately.
factor-pipeline-debug --compactshould include only:gating_statusevidence_quality_scoreraw_pre_bayes_labelsfiltered_pre_bayes_labelsdirection_conflictselected_entry_qualitybridge_gapmarket_specific_appliedpipeline_verdict
next-actionshould include only:current_focus_phasecurrent_focus_reasonblocking_stageblocking_statusblocking_reasonnext_command- top
pending_actions - top
risk_flags
research-compactshould include only:objectivebest_factorrecommended_next_command- top
top_factor_actions - compact
family_decisions
market-fork-statusshould include only:market_specific_fork- compact
structure_ictscore/action pre_bayes_gate_statusnext_command
- Use existing persisted state instead of recomputing or re-reading huge objects.
next-actionshould read the latestWorkflowSnapshotand compress it.research-compactandmarket-fork-statusshould read the latestResearchRunRecordfrom persisted state files.- Avoid feeding
AgentPromptPack, fullWorkflowSnapshot, lineage history, or full artifacts back into the model when compact state is enough.
- Verify token reduction with serialized-size tests, not just intuition.
- Add tests asserting the compact JSON stays small enough to be useful.
- The successful pattern here was checking serialized byte length thresholds for compact views rather than hand-waving about “smaller output”.
- README and router behavior must make compact-first onboarding obvious.
- If agent onboarding pain shows README still points to old heavy paths, move compact-first entrypoints to the front:
next-actionresearch-compactmarket-fork-statuspre-bayes-compactartifact-gate-compactfactor-pipeline-debug --compact
scripts/compact_router.pyshould have a real--helppath, not only a JSON usage error on missing args.- Empty-state commands should prefer actionable guidance over giant null-filled JSON.
- Separate internal compact evidence from human-facing output.
- In ict-engine, internal routing should keep using compact commands and layered debug state.
- But human-facing answers should be translated into five explicit sections:
- 基本价格结构分析
- 技术面价格分析
- SMT相关性分析
- Regime分类结合贝叶斯分析并给推测概率
- 交易计划
- Do not expose raw internal terms like
pre_bayes_gate,structure_ict verdict, ormarket_specific_forkdirectly to end users unless they explicitly ask for internals.
- Enforce the five-block human format at the project prompt/guidance layer first.
- A low-risk first landing is to patch
src/agent/prompts.rssoAgentPromptPack.workflowinstructs agents to keep internal evidence compact but answer humans in exactly the five readable sections above. - This improves downstream agent behavior immediately, even before all CLI output surfaces are refactored.
- When wiring five-block output into analyze/analyze-live, avoid invasive edits to the giant assembly chain.
- The safe path is:
- add a dedicated formatter like
build_human_output(&AnalyzeReport) -> String - populate it only at the final output layer
- do not deeply rewrite the existing analyze assembly while debugging unrelated pipeline work
- add a dedicated formatter like
- This matters because
src/main.rsis large and tightly coupled; directly splicing the formatter into the analyze construction path can easily break unrelated assembly code.
- For layered pipeline debug, add explicit layer objects rather than relying only on raw traces.
- Extend
FactorPipelineDebugReportwith higher-level objects such as:feature_derivationlabel_policy
- These should summarize:
- market
- regime/liquidity labels
- counts/evidence summaries
- raw vs filtered assignments
- gating status and rationale
- Keep raw trace fields too; the new layers are for clarity, not replacement.
Pitfalls
- Do not infer market from factor_name; use symbol.
- Do not assume a previously patched struct field actually exists; verify FrameFeatures definition before compiling.
- A naive test may fail because sample data does not guarantee sweeps/FVG counts; assert conditionally on the baseline counts/labels instead of forcing nonexistent conditions.
- In this environment, read_file snapshots and local file reality can diverge; when the codebase seems inconsistent, prefer shell/fs verification.
- Graphify rebuild may be blocked by permissions. If you already know it is blocked, do not retry blindly.
- For EML/nonlinear fusion PoCs, do not promote a global regime reclassifier first. Prefer a local gate/multiplier inside an already-identified regime, with true sweep/rejection features rather than loose proxies like price_range + volume_spike.
- When backtest trade records lack excursion fields (
max_runup,max_drawdown), do not invent precision labels from unavailable data. Either add the fields in the backtest model or use an explicitly weaker proxy and label it as such. - In trading PoCs, improvement in sample redistribution alone is not evidence of value. If expansion buckets grow but reversal precision/win rate do not improve, treat the nonlinear branch as failed and stop early.
Verification notes
- For the validated NQ case, raw labels became range/neutral and resonance became aligned while structure_ict remained the active factor.
- This confirmed the bottleneck was label derivation, not factor weakness.