spec(crt): v2 amendments from critical review — 12 issues + greenfields

Self-critical review of v1 (commit 0f3484325) found 12 issues. All
fixed inline. Spec marked Status: Design v2 with explicit changelog.

LOAD-BEARING FIXES
  1. §4.4 conviction cold-start: removed backwards "uniform fallback"
     branch that maintained trading authority when net_edge=0 across
     all horizons (exactly when model has no edge). Replaced with ε
     floor on net_edge_h. Single formula, smooth bootstrap, no regime
     switch.
  3. §4.3 exit logic: 5 OR conditions → 1 composite exit_signal scalar
     (max of degradation, disagreement, pnl_decay terms). SL/trail and
     8h circuit-breaker stay orthogonal as safety, not exit policy.
     Attribution per term recorded in §7 state for observability.
  6. §9 Gate 2: PF > 1.0 (2.5× improvement, unrealistic) → tiered
     MUST (no-regression) + WIN (real lift, four candidate criteria) +
     explicit STOP condition if MUST passes without WIN.
  7. §9.1.1: alpha-realization metric defined explicitly as
     realized_pnl / max(unrealized during signal-validity window).
     Was hand-waved in v1; now formulated.

TRACTABLE INLINE FIXES
  2. §7 open_trade_state: 128 → 64 bytes. Diagnostic-only fields
     (scale_up/down counts, max/min target reached) moved to a
     separate audit buffer (single source of truth keeps state lean).
  4. §5.1 envelope: multiplicative formula → additive bounded
     contributions. tanh(sharpe), min(dd/cap), clamp(vol). No single
     factor can crash envelope to zero. Floor (envelope_floor lots)
     and hard-cap (envelope_hard_cap) remain.
  5. §5.2 threshold: hand-waved "self-tune toward PF" → explicit
     3-arm bandit (lower, current, higher percentile) every K_eval
     closed trades, Wiener-α EMA target with floor, bounded
     [0.5, 0.95].
  8. §3.4 + §3.6: Layer A wall-time ≤ 5× → ≤ 2× (controller is
     light, 5× would indicate structural bug, not config tuning).
  9. §4.2: explicit Wiener-α formula for conviction EMA with floor
     at 0.4 per pearl_wiener_alpha_floor_for_nonstationary.
 10. §6.2.1: shadow eval defined explicitly with backtest-mode (last
     15% of fold) and live-mode (parallel-prediction-only) variants.
     Promotion criteria spelled out (PF, Sharpe, tail loss).
 11. §6.2: Layer D loss explicit (primary = value regression on
     realized PnL conditional on state; secondary = EWC++ regularizer
     toward base). Was three losses with no specified combination.
 12. §10.5: adapter regime over-fit risk added with sample-count
     down-weighting, cross-regime shadow eval, periodic adapter reset
     as hard backstop.

GREENFIELDS (per user direction)
  - §3.3: decision_stride field DELETED from YAML + Rust (not
    deprecated). No backwards-compat shim. Per
    feedback_no_legacy_aliases.
  - §7.1: open_trade_state 24→64 byte migration is atomic, no old
    layout shim.
  - §8: boundary matrix decision_stride row marked REMOVED, not
    DEPRECATED.

§13 pearl conformance: per-section pearl application (not bulk
citation). Added pearl_audit_unboundedness_for_implicit_asymmetry,
pearl_no_deferrals_for_complementary_fixes acknowledgement (layers
are sequenced amplifications, not parallel fixes), and the new
pearl_stop_checks_run_at_deadline_cadence from S2.

Status: Design v2 — awaiting user review before writing-plans.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-20 17:24:03 +02:00
parent 0f34843253
commit 8ba8755b48

View File

@@ -2,9 +2,24 @@
**Date:** 2026-05-20 **Date:** 2026-05-20
**Branch context:** `ml-alpha-phase-a` (S2 closed at `8828e8ab1`) **Branch context:** `ml-alpha-phase-a` (S2 closed at `8828e8ab1`)
**Status:** Design — pending user review before implementation plan **Status:** Design v2 (amended from v1 after self-critical review — 12 issues addressed inline)
**Supersedes scope of:** `2026-05-19-isv-driven-stop-controller-design.md` (that spec ships clean stops; this spec replaces the rule-based policy that consumes those stops) **Supersedes scope of:** `2026-05-19-isv-driven-stop-controller-design.md` (that spec ships clean stops; this spec replaces the rule-based policy that consumes those stops)
**v2 changelog** (vs v1 commit `0f3484325`):
1. §4.4 conviction: removed backwards cold-start fallback; replaced with ε floor on net_edge for smooth bootstrap. No regime switch when model has zero edge.
2. §7 open_trade_state: 128 → 64 bytes. Observability fields moved to a separate audit buffer (single source of truth keeps state minimal).
3. §4.3 exit logic: 5 OR conditions → 1 composite exit_signal scalar with attribution. Safety (SL/trail/circuit-breaker) orthogonal.
4. §5.1 envelope: multiplicative → additive bounded contributions. No single factor can crash envelope to zero.
5. §5.2 threshold: hand-waved "learn toward PF" → explicit 3-arm bandit with Wiener-α target EMA, bounded [0.5, 0.95].
6. §9 Gate 2: PF > 1.0 (unrealistic) → tiered MUST/WIN criteria with explicit STOP condition.
7. §9.1.1: defined alpha-realization metric explicitly (was pointed-at, now formulated).
8. §3.4 + §3.6: Layer A wall-time target ≤ 5× → ≤ 2× (controller is light, 5× would mean structural bug).
9. §4.2: explicit Wiener-α formula for conviction EMA (was hand-waved).
10. §6.2.1: shadow eval defined with explicit modes (backtest / live) and metrics.
11. §6.2: Layer D loss explicit (primary = value regression; secondary = EWC++ regularizer).
12. §10.5 added: adapter regime over-fit risk with sample-count down-weighting + cross-regime shadow eval.
13. Greenfields per user direction: removed all backwards-compat shims (decision_stride field deleted, not deprecated; no 24-byte open_trade_state compat).
--- ---
## 1. Motivation ## 1. Motivation
@@ -65,15 +80,18 @@ Sequencing rationale: A unlocks event-rate consumption needed by B. B is where a
Controller runs every event. Trunk forward pass (already every event) feeds the latest alpha probabilities directly into the controller every event. `seed_inflight`'s target-delta semantics absorb continuous target changes by seeding incremental orders. Controller runs every event. Trunk forward pass (already every event) feeds the latest alpha probabilities directly into the controller every event. `seed_inflight`'s target-delta semantics absorb continuous target changes by seeding incremental orders.
### 3.3 Files touched ### 3.3 Files touched (greenfields — no backwards-compat)
- `crates/ml-backtesting/src/harness.rs`: remove `if event_idx % decision_stride == 0` gate around `step_decision`. - `crates/ml-backtesting/src/harness.rs`: remove `if event_idx % decision_stride == 0` gate around `step_decision`. Controller fires every event unconditionally.
- `crates/ml-backtesting/src/sim/mod.rs`: `step_decision` invoked every event from harness; internal kernel batching unchanged. - `crates/ml-backtesting/src/sim/mod.rs`: `step_decision` invoked every event from harness; internal kernel batching unchanged.
- `bin/fxt-backtest/src/main.rs` + `config/ml/*.yaml`: keep `decision_stride` field for backwards-compat but ignore it (deprecation log line on first use). - `bin/fxt-backtest/src/main.rs`: REMOVE `decision_stride` field from `SweepBase` and `ResolvedSimVariant`. No deprecation shim, no compat-warning, no field-kept-for-backwards-compat. Per [feedback_no_legacy_aliases](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_legacy_aliases.md).
- `config/ml/*.yaml`: REMOVE `decision_stride` line from every YAML in one sweep. Per [feedback_no_partial_refactor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_partial_refactor.md) — every caller migrates atomically.
### 3.4 Cost target ### 3.4 Cost target
Per-event controller cost ≤ 10× current per-event cost. Sim throughput target ≥ 5000 events/sec (current ~21000 events/sec at stride=200 with controller every 200th event; expect ~5000-10000 events/sec at continuous controller). The controller kernels (`decision_policy_*` and `seed_inflight_limits_batched`) are LIGHT: block-per-backtest, single-writer thread. 200× more invocations of light kernels should produce ≤ 2× total wall-time slowdown. If the smoke takes > 2× current (~95 s → 190 s+), something is structurally wrong with the controller cost model — pause and profile, do not paper over.
**Target:** sim throughput ≥ 10000 events/sec (vs current ~21000). Wall-time ≤ 2× current.
### 3.5 Failure mode addressed ### 3.5 Failure mode addressed
@@ -81,10 +99,16 @@ Decisions land on stale signal: at decision_stride=200 events on ES Q1 data (~3.
### 3.6 Layer A acceptance criteria (Gate 1) ### 3.6 Layer A acceptance criteria (Gate 1)
- Smoke completes with continuous controller in ≤ 30s wall-time vs ~95s current (or accept ≤ 5× slowdown). - Smoke wall-time ≤ 2× stride=200 baseline (~95 s → ≤ 190 s).
- No regression in n_trades vs Layer-A-off (continuous control should produce ≥ as many trades). - Trade-level entry timestamps cluster tighter to signal-spike timestamps than the stride=200 baseline (measurable via signal-vs-entry-ts cross-correlation; ≥ 30% reduction in mean signal-to-entry lag).
- Trade-level entry timestamps cluster tighter to signal-spike timestamps than the stride=200 baseline (measurable via signal-vs-entry-ts cross-correlation). - `total_pnl_usd` and `sharpe_ann` within ±15% of stride=200 baseline (Layer A alone is not the alpha — its job is to unlock Layer B). Substantial regression (worse than 15%) blocks Gate 1.
- `total_pnl_usd` and `sharpe_ann` within ±15% of stride=200 baseline (Layer A alone is not the alpha — its job is to unlock Layer B). - No new NaN/sentinel counters fire.
### 3.7 Layer A risk: high-frequency target oscillation
At event-rate, consecutive decisions see nearly-identical state. If the state→target map is sensitive, you get hyperactive oscillation (target +1, 0, +1, 0, ...) each costing the spread.
**Mitigation:** target sizing in §4.2 uses a **Wiener-α adaptive EMA with floor** — fast in regime changes, slow in steady state. The Wiener formula `α = diff_var / (diff_var + sample_var + ε)` is MSE-optimal for stationary signals; floor at 0.4 in this non-stationary control loop per [pearl_wiener_alpha_floor_for_nonstationary](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_alpha_floor_for_nonstationary.md). Floor prevents adaptive α from collapsing during quiet periods, which would slow genuine reversals.
--- ---
@@ -122,34 +146,65 @@ Concretely the policy logic becomes:
`seed_inflight_limits_batched` already uses target-delta semantics (`crates/ml-backtesting/cuda/resting_orders.cu` line 652-672). Target changes any event → delta computed → incremental order seeded. `seed_inflight_limits_batched` already uses target-delta semantics (`crates/ml-backtesting/cuda/resting_orders.cu` line 652-672). Target changes any event → delta computed → incremental order seeded.
Sizing formula: Sizing formula (with explicit Wiener-α adaptive EMA):
``` ```
conviction = isv_weighted_agreement(alpha_probs, isv_state, mask) // see 4.4 conviction = isv_weighted_agreement(alpha_probs, isv_state, mask) // see §4.4
conviction_ema = EMA(conviction, α=isv_derived_alpha) // smoothing
target_lots = round(conviction_ema × envelope_max) // Wiener-α adaptive EMA on conviction. diff_var tracks event-to-event
// changes in conviction; sample_var tracks variance of conviction itself.
// α automatically high when signal is genuinely changing, low when stable.
diff_var = EMA((conviction_now conviction_prev)², α=0.1)
sample_var = EMA((conviction_now conviction_ema_prev)², α=0.1)
alpha_raw = diff_var / (diff_var + sample_var + ε)
alpha_active = max(alpha_raw, 0.4) // floor for non-stationarity
conviction_ema = alpha_active × conviction + (1 alpha_active) × conviction_ema_prev
// Target sizing — direction from conviction_signed, magnitude from |conviction_ema|.
target_lots_signed = round(direction × |conviction_ema| × envelope_max) // envelope_max from §5.1
``` ```
EMA prevents micro-oscillation churn. α derived per [pearl_wiener_optimal_adaptive_alpha](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_optimal_adaptive_alpha.md) with floor per [pearl_wiener_alpha_floor_for_nonstationary](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_alpha_floor_for_nonstationary.md). EMA prevents micro-oscillation churn. α floor at 0.4 per [pearl_wiener_alpha_floor_for_nonstationary](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_alpha_floor_for_nonstationary.md) — adaptive α alone collapses in quiet periods, slowing genuine reversals; the floor keeps minimum responsiveness. Per [pearl_wiener_optimal_adaptive_alpha](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_optimal_adaptive_alpha.md).
### 4.3 Conviction-degradation exit ### 4.3 Conviction-degradation exit
Replaces `max_hold_ns` as the dominant exit mechanism. `max_hold_ns` becomes a high-default circuit-breaker (e.g., 8 hours) for safety only. Replaces `max_hold_ns` as the dominant exit mechanism. `max_hold_ns` becomes a high-default circuit-breaker (8 hours) for safety only.
**Per-trade conviction trajectory** stored in expanded `open_trade_state` (see §6). Tracks: **Single composite exit signal.** Five OR-ed conditions are 5 fire modes, 5 attribution headaches, and 5 bug surfaces (we just spent the day finding three independent bugs in `max_hold` alone). Instead, ONE scalar that crosses ONE threshold.
- conviction at entry
- conviction EMA over the trade lifetime
- multi-horizon agreement at each decision
- pnl-adjusted conviction = conviction × sign(unrealized_pnl)
**Exit fires when ANY of:** ```
// All three terms are non-negative scalars in [0, ∞). Exit fires when ANY
// term exceeds 1.0 — but the COMPOSITION is one max-of-three, so attribution
// is by which term dominated at exit time (recorded in trade observability).
1. **Net conviction degradation:** `conviction_ema_now < conviction_at_entry × degradation_factor` for K consecutive events. `degradation_factor` and K are ISV-derived (not hardcoded). // Term 1: conviction has degraded relative to entry. Ratio < 1 means conviction
2. **Multi-horizon disagreement:** h100 or h500 reversed sign relative to h6000 for ≥ K_short events. // is weakening; degradation_factor (ISV-derived) sets how much weakening triggers exit.
3. **pnl-adjusted conviction crosses zero:** signal says exit AND market agrees. degradation_term = (1.0 conviction_ema / max(conviction_at_entry, ε)) / degradation_factor
4. **Hard stop:** SL/trail still active per current `stop_check_isv` logic (Layer B doesn't remove them — they're orthogonal safety).
5. **Circuit-breaker:** `(current_ts entry_ts) ≥ max_hold_circuit_breaker_ns` where the circuit-breaker default is 8 hours, ~480× the current 60s cap.
This makes time-in-trade emergent. Winners hold as long as conviction holds. Losers cut when conviction degrades, not when a clock fires. // Term 2: short-horizon disagreement with long-horizon thesis. Counts
// consecutive events where h100 or h500 reversed sign relative to h6000.
// disagreement_factor (ISV-derived) sets tolerance.
disagreement_term = short_horizon_disagreement_events / disagreement_factor
// Term 3: pnl-adjusted conviction deeply negative. Signal AND market both saying
// "this trade is wrong." pnl_decay_factor (ISV-derived) sets tolerance.
pnl_decay_term = max(pnl_adjusted_conviction_ema, 0) / pnl_decay_factor
exit_signal = max(degradation_term, disagreement_term, pnl_decay_term)
exit_fires = (exit_signal ≥ 1.0)
// Attribution recorded for observability (see §7 open_trade_state).
exit_cause = argmax({degradation_term, disagreement_term, pnl_decay_term})
```
**ISV-derived factors:** `degradation_factor`, `disagreement_factor`, `pnl_decay_factor` are anchored from ISV state per [pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md). Not YAML constants.
**Safety layer (orthogonal, NOT exit policy):**
- SL/trail from existing `stop_check_isv` — runs at policy rate as before, fires for outsized adverse moves the conviction signal might not catch fast enough.
- Circuit-breaker `(current_ts entry_ts) ≥ max_hold_circuit_breaker_ns`, default 8 hours — catches pathological infinite holds.
Time-in-trade is now emergent. Winners hold as long as conviction holds. Losers cut when the composite exit signal crosses threshold.
### 4.4 Conviction definition (ISV-weighted multi-horizon agreement) ### 4.4 Conviction definition (ISV-weighted multi-horizon agreement)
@@ -161,45 +216,49 @@ For each horizon h in {h100, h500, h1500, h6000}:
direction_h = sign(alpha_h 0.5) direction_h = sign(alpha_h 0.5)
magnitude_h = |alpha_h 0.5| × 2 // ∈ [0, 1] magnitude_h = |alpha_h 0.5| × 2 // ∈ [0, 1]
# ISV-derived weight per horizon. Rewards horizons with positive // ISV-derived weight per horizon. Rewards horizons with positive net
# net edge (win EMA exceeds loss EMA) AND low realised-return // edge (win EMA exceeds loss EMA) AND low realised-return variance
# variance (high SNR). Floor at 0 prevents negative-edge horizons // (high SNR). The floor ε is small-positive (NOT zero) so that:
# from dragging conviction; cost^2 in the denominator is the // - horizons with no positive edge contribute negligibly (weight ≈ ε / var)
# bootstrap sentinel per pearl_trade_level_vol_for_stop_distance. // - the formula never branches to a "fallback" mode that maintains
net_edge_h = max(isv[h].pnl_ema_win isv[h].pnl_ema_loss, 0) // trading authority precisely when the model has no edge.
// cost^2 in the denominator is the bootstrap sentinel per
// pearl_trade_level_vol_for_stop_distance.
net_edge_h = max(isv[h].pnl_ema_win isv[h].pnl_ema_loss, ε)
weight_h = net_edge_h / (isv[h].realised_return_var + cost^2) weight_h = net_edge_h / (isv[h].realised_return_var + cost^2)
weighted_mag_h = magnitude_h × weight_h × direction_h // signed weighted_mag_h = magnitude_h × weight_h × direction_h // signed
# Normalise by total weight. If all horizons have zero net edge // Normalise by total weight. total_weight is ALWAYS > 0 because every
# (cold start), fall back to uniform-weighted magnitude per // weight_h is ≥ ε / (var + cost²) > 0 by construction. No cold-start
# pearl_first_observation_bootstrap. // branch needed — the floor IS the bootstrap. When all horizons have
total_weight = sum(|weight_h|) // (near-)zero net edge, conviction naturally shrinks toward zero
if total_weight > 0: // (small numerator, normalized denominator), and the §5.2 percentile
conviction_signed = sum(weighted_mag_h) / total_weight // ∈ [-1, +1] // gate suppresses trading. This is the correct behavior: no edge → no trade.
else: total_weight = sum(|weight_h|) // > 0 by construction
conviction_signed = sum(magnitude_h × direction_h) / 4 // cold-start uniform conviction_signed = sum(weighted_mag_h) / total_weight // ∈ [-1, +1]
conviction = |conviction_signed| // ∈ [0, 1] conviction = |conviction_signed| // ∈ [0, 1]
direction = sign(conviction_signed) direction = sign(conviction_signed)
``` ```
**ε choice:** small relative to typical post-bootstrap `net_edge_h`. Suggested `ε = cost / 100` so that pre-bootstrap weights are dominated by `cost / (cost²) = 1/cost` — i.e., the cost-bootstrap variance term dominates exactly when ISV variance hasn't bootstrapped yet, and ISV variance dominates after bootstrap. The transition is smooth, not branched.
Notes: Notes:
- Weights are ISV-derived ([pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md)). Per-horizon SNR drives how much that horizon contributes. - Weights are ISV-derived ([pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md)). Per-horizon SNR drives how much that horizon contributes.
- Net edge follows the asymmetric structural bound — losses are NOT symmetrically subtracted from gains since negative-edge horizons should drop out, not flip sign ([pearl_audit_unboundedness_for_implicit_asymmetry](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_audit_unboundedness_for_implicit_asymmetry.md)). - Net edge with positive ε floor follows [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md): `max(real, floor)`, never branching to a different formula.
- `cost^2` bootstrap follows [pearl_trade_level_vol_for_stop_distance](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_trade_level_vol_for_stop_distance.md): cost as sentinel-floor for variance until real var bootstraps. - Net edge asymmetric ([pearl_audit_unboundedness_for_implicit_asymmetry](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_audit_unboundedness_for_implicit_asymmetry.md)) — losing-edge horizons should drop out, not flip sign.
- `cost^2` bootstrap per [pearl_trade_level_vol_for_stop_distance](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_trade_level_vol_for_stop_distance.md): cost as sentinel-floor for variance until real var bootstraps.
- Normalization makes conviction scale-invariant per [pearl_zscore_normalization_for_magnitude_asymmetric_signals](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_zscore_normalization_for_magnitude_asymmetric_signals.md). - Normalization makes conviction scale-invariant per [pearl_zscore_normalization_for_magnitude_asymmetric_signals](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_zscore_normalization_for_magnitude_asymmetric_signals.md).
- Single unbounded multiplicand: `net_edge_h / (var + cost^2)` is the one unbounded factor; `magnitude_h × direction_h` is bounded in `[-1, +1]`. Per [pearl_one_unbounded_signal_per_reward](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_one_unbounded_signal_per_reward.md). - Single unbounded multiplicand: `net_edge_h / (var + cost^2)` is the one unbounded factor; `magnitude_h × direction_h` is bounded in `[-1, +1]`. Per [pearl_one_unbounded_signal_per_reward](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_one_unbounded_signal_per_reward.md).
- Disagreeing horizons reduce conviction magnitude even if individually large — opposing signed `weighted_mag_h` terms partially cancel in the sum. - Disagreeing horizons reduce conviction magnitude even if individually large — opposing signed `weighted_mag_h` terms partially cancel in the sum.
- Cold-start fallback (uniform weighting before ISV bootstraps) ensures the controller is functional from event 1; matches [pearl_first_observation_bootstrap](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_first_observation_bootstrap.md). - No cold-start branch: ε floor smoothly bootstraps the formula without a regime switch (the regime switch in v1 was the bug — it MAINTAINED trading authority when net edge was zero across all horizons, which is exactly when the model is admitting it has no edge).
### 4.5 Layer B acceptance criteria (Gate 2) ### 4.5 Layer B acceptance criteria (Gate 2)
- Hold-time distribution is no longer cap-binding: mean and max NOT clustered at the circuit-breaker. See §9 Gate 2 for the canonical tiered MUST/WIN structure. Short form:
- `profit_factor > 1.0` OR a defined alpha-realization metric (e.g., realized-vs-theoretical PnL on tagged signal opportunities) shows30% lift over Layer A baseline. - **MUST**: hold-time emergent (circuit-breaker fires < 5%), no metric regression (Sharpe ≥ baseline 0.5, PF ≥ baseline × 0.95, drawdown ≤ baseline × 1.10), conviction-degradation accounts for60% of closes, exit attribution coherent, no new NaN counters.
- Win rate stable or up (24.2% baseline; target ≥ 25% with PF lift). - **WIN (one of)**: Sharpe lift ≥ +0.5; PF lift ≥ 30%; alpha-realization ratio (§9.1.1) lift ≥ +20%; drawdown reduction ≥ 20% at equal PnL.
- Drawdown not worsened by > 10% vs Layer A baseline.
- Conviction degradation exits account for ≥ 60% of closes (rest = SL/trail/circuit-breaker). Validates the mechanism is dominant.
--- ---
@@ -211,28 +270,61 @@ ISV-derived bounds replace YAML constants.
Replaces hardcoded `max_lots: 5`. Replaces hardcoded `max_lots: 5`.
``` **Additive bounded formula** (v1 multiplicative crashed envelope when any one factor → 0):
rolling_sharpe_short = isv_aggregated rolling sharpe over last N closed trades
drawdown_pct = current_dd / peak_equity
regime_vol_inverse = 1 / (regime_atr_ema + cost)
envelope_raw = base_lots × rolling_sharpe_short_clamped × (1 drawdown_pct/dd_cap) × regime_vol_inverse ```
envelope_max = clamp(envelope_raw, envelope_floor, envelope_hard_cap) // Each contribution bounded; no factor can crash envelope to zero.
sharpe_term = 0.5 × tanh(rolling_sharpe_short / sharpe_norm) // ∈ [0.5, +0.5]
dd_term = 0.3 × min(drawdown_pct / dd_cap, 1.0) // ∈ [0, +0.3]
vol_term = 0.2 × clamp(regime_vol / vol_norm, 0, 1) // ∈ [0, +0.2]
envelope_factor = 1 + sharpe_term dd_term vol_term // ∈ [0, 1.5]
envelope_lots = max(envelope_floor, base_lots × envelope_factor)
envelope_max = min(envelope_lots, envelope_hard_cap)
``` ```
Floor and hard cap follow [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md): `max(real, floor)`, not blend. Hard cap stays as safety (default 50 lots, configurable). - `sharpe_norm`, `vol_norm`, `dd_cap` are ISV-derived anchors per [pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md). Not YAML constants.
- `envelope_floor` is a small positive integer (e.g., 1 lot) so we never reach "can't trade" state. [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md).
- `envelope_hard_cap` is a structural safety (default 50 lots), the only YAML knob in this layer.
- No single term can collapse the envelope. Even at max drawdown + max vol + min Sharpe: `1 0.5 0.3 0.2 = 0`; envelope hits floor, not zero.
### 5.2 `threshold` (conviction-percentile gate) ### 5.2 `threshold` (conviction-percentile gate)
Replaces hardcoded `threshold: 0.0`. Conviction-percentile-based — trade only when current conviction exceeds the recent N-conviction percentile target. Replaces hardcoded `threshold: 0.0`. Conviction-percentile-based — trade only when current conviction exceeds the recent N-conviction percentile target.
``` ```
conviction_history = circular buffer of last N convictions (per backtest, on-device) conviction_history = circular buffer of last N=256 convictions (per backtest, on-device)
percentile_target = ISV-derived (start at 0.75, learn toward maximizing realized PF) percentile_target_ema = EMA of percentile target with Wiener-α floor
threshold = percentile(conviction_history, percentile_target) threshold = percentile(conviction_history, percentile_target_ema)
``` ```
Self-tuning: percentile_target drifts toward whatever value maximizes recent realized PF, bounded by a hard floor (e.g., 0.5 — never trade below median conviction). **Explicit self-tuning controller** (vs hand-waved "learn toward PF"):
```
// Every K_eval = 32 closed trades, compare PF at current percentile vs PF at adjacent percentiles.
// Move toward higher PF, bounded by structural limits.
every K_eval trades:
pf_at_current = profit_factor(last K_eval trades at current threshold)
pf_at_higher = profit_factor of trades that would have been kept at percentile + Δ
pf_at_lower = profit_factor of trades that would have been kept at percentile Δ
where Δ = 0.05
best_dir = argmax(pf_at_lower, pf_at_current, pf_at_higher)
// Move target toward best_dir using Wiener-α adaptive EMA per pearl_wiener_optimal_adaptive_alpha
target_proposal = percentile_target_ema + step_size × sign(best_dir)
percentile_target_ema_new = α × target_proposal + (1 α) × percentile_target_ema_old
where α has floor 0.1 per pearl_wiener_alpha_floor_for_nonstationary
// Hard structural bounds:
percentile_target_ema = clamp(percentile_target_ema_new, 0.5, 0.95)
```
- Bounded by `[0.5, 0.95]` — never trade below median conviction, never demand top 5% only.
- `pf_at_higher` / `pf_at_lower` are counterfactual estimates from the historical conviction distribution; they don't require running parallel simulations.
- Wiener-α floor prevents thrash; the step size is one Δ per evaluation cycle.
- This is a 1-D bandit with three arms (lower, current, higher), evaluated every K_eval trades.
### 5.3 `target_annual_vol_units` ### 5.3 `target_annual_vol_units`
@@ -266,18 +358,48 @@ LoRA adapter on frozen base checkpoint. Low-rank delta applied to selected weigh
**Offline-batch, per session** — NOT per-trade in-loop. **Offline-batch, per session** — NOT per-trade in-loop.
``` ```
For each session end (or every N trades): For each session end (or every N=128 closed trades):
1. Collect trajectories: (state_at_decision, action, outcome_realized_pnl) for all closed trades. 1. Collect trajectories: (state_at_decision, action_taken, outcome_realized_pnl) tuples.
2. Compute outcome-conditional loss:
- Value regression (predicted_value vs realized) 2. Primary loss: value regression on realized PnL conditional on state.
- Direction loss (predicted_direction vs realized_pnl_sign) L_value = mean((predicted_value(state) realized_pnl) ²)
- Conviction calibration (conviction vs |realized_pnl|) Single primary loss avoids multi-objective tuning ambiguity.
3. EWC++ regularizer toward base checkpoint (prevents catastrophic forgetting).
4. Gradient step on LoRA adapter only (base frozen). 3. EWC++ regularizer toward base checkpoint weights θ_base.
5. Shadow eval: run adapter-on vs adapter-off on a held-out validation window. L_ewc = λ_ewc × sum_i (F_i × (θ_adapter_i θ_base_i) ²)
6. If shadow eval shows PF degradation > X% vs baseline: revert adapter to checkpoint. F_i = Fisher information per parameter from the base training run.
Prevents catastrophic forgetting; λ_ewc tuned at gate-4 time.
4. Total loss: L = L_value + L_ewc
5. Gradient step on LoRA adapter parameters only (base θ_base frozen).
Small LR (e.g., 1e-5) — adapter changes per session bounded.
6. Shadow eval (see §6.2.1).
7. Promotion gate: if shadow eval passes → adapter checkpoint promoted to live.
Otherwise → adapter reverted to last-promoted checkpoint.
``` ```
### 6.2.1 Shadow eval — explicit definition
Shadow eval validates the new adapter on real data BEFORE promoting it to production trading. Three modes depending on operational context:
**Mode A (backtest / walk-forward):** hold out the last 15% of each fold as a shadow window. Apply candidate adapter to predictions on the shadow window, measure PF, Sharpe, drawdown. Compare adapter-on vs base-only.
**Mode B (live trading):** parallel-prediction-only execution. Candidate adapter produces shadow predictions on real-time data, but **does NOT route trades** — predictions are logged and compared against realized outcomes. After N_shadow = 64 closed trades' worth of shadow signal, compare shadow-predicted PnL vs realized PnL.
**Promotion criteria** (both modes):
- Shadow PF ≥ 0.95 × baseline PF (no significant degradation)
- Shadow Sharpe ≥ baseline Sharpe 0.3 (allow small noise)
- No new tail-loss events > 2× baseline tail loss
**Demotion**: failed shadow eval → automatic revert to last-promoted adapter checkpoint. No human intervention.
### 6.2.2 Circuit-breaker
Independent of shadow eval. Continuously monitors rolling Sharpe over last 32 trades. If rolling_sharpe degrades by > 25% vs the rolling_sharpe at time-of-last-promotion, revert adapter regardless of shadow eval. Circuit-breaker is the safety net for real-time degradation that shadow eval missed.
### 6.3 References ### 6.3 References
- [feedback_safla_neural](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/) — SAFLA pattern for memory-persistent learning - [feedback_safla_neural](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/) — SAFLA pattern for memory-persistent learning
@@ -297,46 +419,44 @@ For each session end (or every N trades):
Current: 24 bytes per backtest (entry_ts:8, entry_px_x100:4, entry_size:4, realized_at_open:4, horizon_idx:1, pad:3). Current: 24 bytes per backtest (entry_ts:8, entry_px_x100:4, entry_size:4, realized_at_open:4, horizon_idx:1, pad:3).
Target: **128 bytes per backtest** (single source of truth per trade per [feedback_single_source_of_truth_no_duplicates](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_single_source_of_truth_no_duplicates.md)). Target: **64 bytes per backtest** (single source of truth per trade per [feedback_single_source_of_truth_no_duplicates](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_single_source_of_truth_no_duplicates.md)). State only — pure observability fields (scale-up counts, etc.) go to a separate per-trade audit buffer, not the per-event state slot.
Proposed layout: Proposed layout:
``` ```
Offset Size Field Offset Size Field Purpose
───────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────────
0 8 entry_ts_ns (u64) 0 8 entry_ts_ns (u64) timing
8 4 entry_px_x100 (i32) 8 4 entry_px_x100 (i32) close attribution
12 4 entry_size (i32) 12 4 entry_size (i32) close attribution
16 4 realized_at_open (f32) 16 4 realized_at_open (f32) segment-PnL delta
20 1 horizon_idx_dominant_at_entry (u8) 20 4 conviction_at_entry (f32) degradation_term denominator
21 3 pad 24 4 conviction_ema (f32) §4.2 sizing input
24 4 conviction_at_entry (f32) 28 16 conviction_per_horizon_ema [4 × f32] (h100, h500, h1500, h6000)
28 4 conviction_ema (f32) §4.3 disagreement_term input
32 16 conviction_per_horizon_at_entry [4 × f32] (h100, h500, h1500, h6000) 44 4 pnl_adjusted_conviction_ema (f32) §4.3 pnl_decay_term input
48 16 conviction_per_horizon_ema [4 × f32] 48 4 peak_unrealized_pnl (f32) trail invariant
64 4 pnl_adjusted_conviction_ema (f32) 52 4 degradation_consecutive_events (u32) §4.3 attribution
68 4 degradation_consecutive_events (u32) 56 4 disagreement_consecutive_events (u32) §4.3 attribution
72 4 short_disagreement_consecutive_events (u32) 60 1 horizon_idx_dominant_at_entry (u8) close attribution
76 4 peak_unrealized_pnl (f32) 61 3 pad align
80 4 trough_unrealized_pnl (f32) ────────────────────────────────────────────────────────────────────────────────
84 4 scale_up_events_count (u32) 64 TOTAL — fits in one cache line
88 4 scale_down_events_count (u32)
92 4 max_target_lots_reached (i32)
96 4 min_target_lots_reached (i32)
100 28 reserved for future trajectory features
``` ```
128 bytes total. Aligns to cache line. **Diagnostic-only fields go elsewhere.** `scale_up_events_count`, `scale_down_events_count`, `max/min_target_lots_reached` — useful for post-trade analysis but not needed for next-decision logic. Per [feedback_no_hiding](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_hiding.md), if these are wanted, route to a separate `trade_audit_log` ring buffer in observability infrastructure. Mixing state and observability bloats the hot-path read/write.
### 7.1 Migration ### 7.1 Migration (greenfields atomic)
Every reader and writer of `open_trade_state` migrates atomically per [feedback_no_partial_refactor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_partial_refactor.md). Sites to update: Every reader and writer of `open_trade_state` migrates atomically per [feedback_no_partial_refactor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_partial_refactor.md). Sites to update in one commit:
- `crates/ml-backtesting/cuda/pnl_track.cu``OPEN_TRADE_STATE_BYTES` constant from 24 to 128; all `st + offset` reads/writes updated. - `crates/ml-backtesting/cuda/pnl_track.cu``OPEN_TRADE_STATE_BYTES` constant from 24 to 64; all `st + offset` reads/writes updated; reset-on-close zeros all 64 bytes.
- `crates/ml-backtesting/cuda/decision_policy.cu``stop_check_isv` reads entry_ts via `+ 24` indexing; update. - `crates/ml-backtesting/cuda/decision_policy.cu``stop_check_isv` reads entry_ts via `+ 24` indexing; update for new layout.
- `crates/ml-backtesting/cuda/resting_orders.cu` — S2.2 max_hold event-rate check also reads via `+ 24`; update. - `crates/ml-backtesting/cuda/resting_orders.cu` — S2.2 max_hold event-rate check also reads via `+ 24`; update.
- `crates/ml-backtesting/src/sim/mod.rs` — allocation site, `read_open_trade_state` helper. - `crates/ml-backtesting/src/sim/mod.rs` — allocation site, accessors.
- Any tests asserting layout. - Tests: any test asserting the layout updates in the same commit.
No backwards-compat shim for the old 24-byte layout. Greenfields.
--- ---
@@ -349,7 +469,7 @@ Every reader and writer of `open_trade_state` migrates atomically per [feedback_
| `annualisation_factor` | **HARDCODED** | Definitional constant. | | `annualisation_factor` | **HARDCODED** | Definitional constant. |
| `min/max_reasonable_px` | **HARDCODED** (config, per-instrument) | Instrument bounds. | | `min/max_reasonable_px` | **HARDCODED** (config, per-instrument) | Instrument bounds. |
| `max_hold_ns` | **CIRCUIT-BREAKER** (default 8h) | Safety net only. Behavior emerges from §4.3. | | `max_hold_ns` | **CIRCUIT-BREAKER** (default 8h) | Safety net only. Behavior emerges from §4.3. |
| `decision_stride` | **DEPRECATED** | Layer A makes obsolete. Field kept for backwards-compat. | | `decision_stride` | **REMOVED** | Greenfields — field deleted from YAML schema and Rust structs in this rebuild. No deprecation shim. |
| `max_lots` | **ADAPTIVE** (Layer C §5.1) | Envelope from rolling Sharpe × DD × regime vol. | | `max_lots` | **ADAPTIVE** (Layer C §5.1) | Envelope from rolling Sharpe × DD × regime vol. |
| `threshold` | **ADAPTIVE** (Layer C §5.2) | Conviction-percentile gate. | | `threshold` | **ADAPTIVE** (Layer C §5.2) | Conviction-percentile gate. |
| `target_annual_vol_units` | **ADAPTIVE** (Layer C §5.3) | Regime-vol-inverse. | | `target_annual_vol_units` | **ADAPTIVE** (Layer C §5.3) | Regime-vol-inverse. |
@@ -373,15 +493,45 @@ Every reader and writer of `open_trade_state` migrates atomically per [feedback_
### Gate 2 (Layer B): Signal-driven position management ### Gate 2 (Layer B): Signal-driven position management
**Pass criteria:** Tiered criteria — first MUST is required to advance; WIN is the architectural payoff.
- Hold-time mean NOT cap-binding (max_hold circuit-breaker rarely fires; < 5% of closes).
- `profit_factor > 1.0` OR ≥ 30% lift on a defined alpha-realization metric vs Layer A baseline.
- Win rate ≥ 24.2% (current).
- Drawdown not worsened > 10%.
- Conviction degradation accounts for ≥ 60% of closes.
- Per-trade trajectory data shows conviction tracks predicted alpha (sanity check on the formula).
**Validation:** parametric sweep over (degradation_factor, disagreement_K, conviction_EMA_α) on the smoke window. Pick the winner. Then walk-forward on 3 folds. **MUST (required to advance from Gate 2):**
- Hold-time mean NOT cap-binding (8h circuit-breaker fires in < 5% of closes).
- No regression on baseline metrics: Sharpe ≥ baseline 0.5 units; PF ≥ baseline × 0.95; drawdown ≤ baseline × 1.10.
- Conviction-degradation exit accounts for ≥ 60% of closes — validates the mechanism is dominant.
- Trade attribution coherent: `exit_cause` field shows interpretable distribution across the three terms (not 99% one term).
- No new NaN/sentinel counters fire.
**WIN (the architectural payoff — one of):**
- Sharpe lift ≥ +0.5 units vs Layer A baseline.
- Profit factor lift ≥ 30% (e.g., 0.40 → 0.52, not 0.40 → 1.0).
- Alpha realization ratio (see §9.1.1) lift ≥ 20%.
- Drawdown reduction ≥ 20% at equal PnL.
**STOP** at gate close-out if NO WIN condition was hit despite MUST being green: re-evaluate whether to proceed to Layer C, or whether the signal itself needs improvement before any policy layer can extract value (the latter is a separate workstream).
**Validation:** parametric sweep over (degradation_factor, disagreement_factor, pnl_decay_factor, conviction_EMA_α_floor) on the smoke window. Pick the winner by composite of MUST + WIN criteria. Then walk-forward on 3 folds.
### 9.1.1 Alpha-realization ratio (defined)
Per-trade ratio of realized PnL to theoretical maximum PnL achievable within the trade's signal-validity window:
```
For each closed trade:
signal_window_start = entry_ts
signal_window_end = max(exit_ts, entry_ts + h6000_horizon_ns)
optimal_exit_ts = argmax_t∈[start,end] (unrealized_pnl_at_t)
optimal_pnl = unrealized_pnl_at(optimal_exit_ts)
realized_pnl = trade.realized_pnl_usd
alpha_realization_per_trade = realized_pnl / max(optimal_pnl, cost_per_lot_per_side × size)
alpha_realization_ratio = mean(alpha_realization_per_trade across all trades)
```
Bounded by trade's signal-validity window (h6000 horizon ns). Captures "how much of the available alpha the policy actually captured." Cost-bootstrapped denominator (using `cost × size` as floor) per [pearl_trade_level_vol_for_stop_distance](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_trade_level_vol_for_stop_distance.md) handles trades where `optimal_pnl` is sub-cost noise.
Computed offline from trade records + per-event unrealized PnL reconstruction (the latter requires logging unrealized PnL at decision events — minor instrumentation add). Single, unambiguous metric.
### Gate 3 (Layer C): Adaptive risk envelope ### Gate 3 (Layer C): Adaptive risk envelope
@@ -445,6 +595,16 @@ Famous failure mode.
- Circuit-breaker revert — automatic rollback on rolling Sharpe degradation. - Circuit-breaker revert — automatic rollback on rolling Sharpe degradation.
- Adapter is small (1-5% of base) — limited expressivity to misbehave. - Adapter is small (1-5% of base) — limited expressivity to misbehave.
### 10.5 Layer D — adapter regime over-fit
EWC++ guards against catastrophic forgetting of base capabilities but does NOT prevent the adapter from over-fitting to the **most recent regime** if recent sessions all live in one regime (e.g., a multi-week trending market followed by chop).
**Mitigations:**
- **Sample-count down-weighting:** the gradient step magnitude is scaled by `sqrt(samples_in_current_regime / samples_in_base_training_data)`. Regimes with little base coverage get small updates; regimes with rich base coverage get full updates. This is the standard distribution-shift compensation.
- **Regime detection** uses ISV-derived regime-vol classification (also used in §5.1 envelope and §5.3 vol target). When current regime ≠ base-training regime distribution, the adapter learns proportionally less.
- **Shadow eval is cross-regime** when possible: hold out a window from a DIFFERENT regime than the training window, so an adapter that over-fits to the training regime fails the cross-regime shadow.
- **Periodic adapter reset** as a hard backstop: if rolling Sharpe degrades AND shadow eval consistently fails for K consecutive cycles, drop the adapter entirely and re-bootstrap from base.
--- ---
## 11. Open questions / future work ## 11. Open questions / future work
@@ -461,31 +621,45 @@ These are recognized scope-cuts. Document for future iteration.
## 12. Acceptance criteria summary (rollup) ## 12. Acceptance criteria summary (rollup)
``` ```
GATE 1 (Layer A): Continuous controller live, no PnL regression,5× wall-time GATE 1 (Layer A): Continuous controller live, wall-time2× baseline, signal-to-entry
GATE 2 (Layer B): PF > 1.0 OR +30% alpha-realization, hold-time emergent, 60% degradation exits lag reduced ≥ 30%, no PnL regression > 15%.
GATE 3 (Layer C): DD reduction at equal PnL OR PnL lift at equal DD
GATE 4 (Layer D): Walk-forward 75% positive lift, no catastrophic forgetting GATE 2 (Layer B): MUST — hold time emergent (cap fires < 5%), no metric regression,
60% of closes via conviction-degradation, exit attribution coherent.
WIN — one of: Sharpe lift ≥ +0.5, PF lift ≥ 30%, alpha-realization
ratio ≥ +20%, drawdown reduction ≥ 20% at equal PnL.
STOP — MUST green but no WIN means re-evaluate signal-vs-policy before C.
GATE 3 (Layer C): Drawdown reduction at equal PnL OR PnL lift at equal drawdown.
Envelope stays bounded by floor and hard-cap. Percentile self-tuning
stable (no thrash). Floor activates when signal-derived envelope underflows.
GATE 4 (Layer D): Walk-forward across ≥ 3 folds, ≥ 75% positive PF lift.
Shadow eval catches synthetic-bad-update. Circuit-breaker revert works.
No catastrophic forgetting (base+adapter ≥ base on prior windows).
No regime over-fit (cross-regime shadow eval passes).
``` ```
Each gate is a stop. If a gate fails, do not advance until the gate is reopened with revised criteria or revised implementation. Each gate is a stop. If a gate fails, do not advance until the gate is reopened with revised criteria or revised implementation. Gate 2 STOP condition (MUST without WIN) is the most consequential: it forces an explicit decision between "iterate Layer B" and "this signal can't carry a controller at this cost/latency anchor."
--- ---
## 13. Pearl conformance summary ## 13. Pearl conformance summary
- [pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md) — Layer C envelopes are ISV-derived. Per-section pearl application (not just citation):
- [pearl_first_observation_bootstrap](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_first_observation_bootstrap.md) — conviction trajectory bootstrap uses sentinel=0, first observation replaces directly.
- [pearl_wiener_optimal_adaptive_alpha](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_optimal_adaptive_alpha.md) — conviction EMA α adaptive. - §3 Layer A: [pearl_stop_checks_run_at_deadline_cadence](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_stop_checks_run_at_deadline_cadence.md) — checks at event rate.
- [pearl_wiener_alpha_floor_for_nonstationary](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_alpha_floor_for_nonstationary.md) — α floor in conviction tracking (non-stationary by definition). - §4.2 sizing: [pearl_wiener_optimal_adaptive_alpha](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_optimal_adaptive_alpha.md) + [pearl_wiener_alpha_floor_for_nonstationary](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_wiener_alpha_floor_for_nonstationary.md) — adaptive α with floor.
- [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md) — envelope floor uses `max(real, floor)`. - §4.3 exit composite: [pearl_controller_anchors_isv_driven](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_controller_anchors_isv_driven.md) — degradation/disagreement/pnl_decay factors are ISV-derived.
- [pearl_zscore_normalization_for_magnitude_asymmetric_signals](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_zscore_normalization_for_magnitude_asymmetric_signals.md) — multi-horizon weights are scale-normalized. - §4.4 conviction: [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md) — ε floor on net_edge (no cold-start branch); [pearl_audit_unboundedness_for_implicit_asymmetry](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_audit_unboundedness_for_implicit_asymmetry.md) — net edge is one-sided positive; [pearl_one_unbounded_signal_per_reward](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_one_unbounded_signal_per_reward.md) — `net_edge / (var + cost²)` is the one unbounded factor; [pearl_zscore_normalization_for_magnitude_asymmetric_signals](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_zscore_normalization_for_magnitude_asymmetric_signals.md) — `/ total_weight` scale-normalizes; [pearl_trade_level_vol_for_stop_distance](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_trade_level_vol_for_stop_distance.md) — `cost²` bootstraps variance.
- [pearl_one_unbounded_signal_per_reward](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_one_unbounded_signal_per_reward.md) — conviction formula has exactly one unbounded multiplicand. - §5.1 envelope: [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md) — additive bounded contributions, floor at 1 lot; [feedback_isv_for_adaptive_bounds](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_isv_for_adaptive_bounds.md) — sharpe_norm / vol_norm / dd_cap are ISV anchors.
- [pearl_trade_level_vol_for_stop_distance](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_trade_level_vol_for_stop_distance.md) — `cost^2` bootstrap for ISV weight denominator. - §5.2 threshold: explicit 3-arm bandit replaces hand-wave; bounded `[0.5, 0.95]` per [pearl_blend_formulas_must_have_permanent_floor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_blend_formulas_must_have_permanent_floor.md).
- [pearl_stop_checks_run_at_deadline_cadence](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_stop_checks_run_at_deadline_cadence.md) — Layer A makes ALL checks event-rate. - §6.2 Layer D: single primary loss (value regression) + EWC++; shadow eval with explicit window/metric.
- [feedback_single_source_of_truth_no_duplicates](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_single_source_of_truth_no_duplicates.md) — open_trade_state is single source. - §7 state: [feedback_single_source_of_truth_no_duplicates](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_single_source_of_truth_no_duplicates.md) — open_trade_state is sole per-trade scratch; [feedback_no_partial_refactor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_partial_refactor.md) — all consumers migrate atomically; [feedback_no_legacy_aliases](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_legacy_aliases.md) — no compat shim for the old 24-byte layout (greenfields).
- [feedback_no_partial_refactor](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_partial_refactor.md) — open_trade_state migration is atomic. - §8 boundary: [feedback_adaptive_not_tuned](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_adaptive_not_tuned.md) — every non-hardware constant becomes signal-derived; [feedback_no_legacy_aliases](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_no_legacy_aliases.md) — `decision_stride` removed, not deprecated.
- [feedback_isv_for_adaptive_bounds](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_isv_for_adaptive_bounds.md) — adaptive bounds live in ISV, not constants. - §9 gates: tiered MUST/WIN avoids the "(0, 30s)" anti-pattern from the just-closed S2 (set gates we can actually hit, gates that distinguish "broken" from "needs more work").
- [feedback_adaptive_not_tuned](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/feedback_adaptive_not_tuned.md) — signal-driven, not tuned. - §11.4 percentile bootstrap: [pearl_first_observation_bootstrap](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_first_observation_bootstrap.md) — init from first observed conviction, no special branch.
- Overall sequencing: [pearl_no_deferrals_for_complementary_fixes](../../../../home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/pearl_no_deferrals_for_complementary_fixes.md) acknowledged — layers are NOT complementary fixes (which we'd combine) but sequenced amplifications (which we'd phase). The rationale is explicit in §2.
--- ---