diff --git a/crates/ml-alpha/cuda/rl_avg_win_loss_ema_update.cu b/crates/ml-alpha/cuda/rl_avg_win_loss_ema_update.cu index c2a4df105..fbf27a144 100644 --- a/crates/ml-alpha/cuda/rl_avg_win_loss_ema_update.cu +++ b/crates/ml-alpha/cuda/rl_avg_win_loss_ema_update.cu @@ -28,18 +28,20 @@ #define RL_AVG_WIN_USD_EMA_INDEX 678 #define RL_AVG_LOSS_USD_EMA_INDEX 679 #define EMA_ALPHA_FAST 0.05f -// B-5 asymmetric Wiener-α (2026-06-01). REPLACES B-4 multiplicative cap -// which did not bind over many steps (1.5^N → unbounded). Math: a per- -// step rate-cap cannot bound EMA convergence to E[X] given enough -// sustained observations. Asymmetric α gives provably biased estimates -// that are MATHEMATICALLY conservative for Kelly safety: -// avg_win: α_slow on upward moves (slow to trust large wins) -// α_fast on downward moves (normal correction) -// avg_loss: α_fast on upward moves (admit losses fast = safety) -// α_slow on downward moves (slow to forget losses) -// At α_slow=0.001: ema_N = X × (1 - 0.999^N) → after 100 steps reaches -// only 10% of true tail. zh96b avg_win peak $40k → bounded ~$4k. -#define RL_EMA_ALPHA_SLOW_INDEX 721 +// B-6 ISV-driven adaptive asymmetric Wiener-α (2026-06-01 spec). +// α_slow_eff = α_slow_min + (α_fast − α_slow_min) × trust_eff +// trust_eff = trust(n) × stability(CV) +// trust(n) = min(1, cum_dones / n_full_threshold) [Phase 1] +// stability(CV) = exp(-CV × cv_gain) [Phase 2] +// Slot 721 α_slow_min, 722 n_full_threshold, 723 cv_gain. +// Welford triplet for reward_magnitude provides CV (Phase 2). +#define RL_EMA_ALPHA_SLOW_MIN_INDEX 721 +#define RL_EMA_TRUST_FULL_THRESHOLD_INDEX 722 +#define RL_EMA_CV_GAIN_INDEX 723 +#define RL_CUMULATIVE_DONES_INDEX 660 +#define RL_REWARD_MAG_VAR_COUNT_INDEX 615 +#define RL_REWARD_MAG_VAR_MEAN_INDEX 616 +#define RL_REWARD_MAG_VAR_M2_INDEX 617 extern "C" __global__ void rl_avg_win_loss_ema_update( float* __restrict__ isv, @@ -67,29 +69,45 @@ extern "C" __global__ void rl_avg_win_loss_ema_update( } } - // B-5 asymmetric Wiener-α (2026-06-01). Conservative for Kelly safety: - // - avg_win: slow up (skeptical of large wins), fast down (correct quickly) - // - avg_loss: fast up (admit losses promptly), slow down (slow to forget) - // Both bias Kelly's b̂ = avg_win/avg_loss DOWNWARD → smaller f_safe. - // Math proof: with α_slow=0.001, sustained observation X reaches - // ema = X × (1 - 0.999^N) → ema/X = 9.5% at N=100, 18% at N=200. - const float a_slow = isv[RL_EMA_ALPHA_SLOW_INDEX]; - const float a_fast = EMA_ALPHA_FAST; + // B-6 ISV-driven adaptive asymmetric Wiener-α (Bayesian shrinkage). + // α_slow_eff blends α_slow_min → α_fast as data confidence grows. + const float a_slow_min = isv[RL_EMA_ALPHA_SLOW_MIN_INDEX]; + const float a_fast = EMA_ALPHA_FAST; + const float n_trades = isv[RL_CUMULATIVE_DONES_INDEX]; + const float n_full = isv[RL_EMA_TRUST_FULL_THRESHOLD_INDEX]; + const float cv_gain = isv[RL_EMA_CV_GAIN_INDEX]; + + // Phase 1: data-quantity trust (resets at fold boundary via reset_session_state) + float trust = (n_full > 0.0f) ? fminf(1.0f, n_trades / n_full) : 1.0f; + + // Phase 2: signal-volatility gain (set cv_gain=0 to disable) + if (cv_gain > 0.0f) { + const float wf_count = isv[RL_REWARD_MAG_VAR_COUNT_INDEX]; + if (wf_count > 1.0f) { + const float wf_m2 = isv[RL_REWARD_MAG_VAR_M2_INDEX]; + const float wf_mean = isv[RL_REWARD_MAG_VAR_MEAN_INDEX]; + const float wf_var = wf_m2 / (wf_count - 1.0f); + const float cv = (wf_mean > 1e-6f) ? sqrtf(wf_var) / wf_mean : 0.0f; + const float stability = expf(-cv * cv_gain); + trust *= stability; + } + } + + const float a_slow_eff = a_slow_min + (a_fast - a_slow_min) * trust; if (n_win > 0) { const float step_avg = sum_win / (float)n_win; const float prev = isv[RL_AVG_WIN_USD_EMA_INDEX]; - // Asymmetric: slow when admitting larger wins, fast when correcting down. - const float alpha = (step_avg > prev) ? a_slow : a_fast; + // avg_win: slow-up (skeptical of wins), fast-down (correct quickly) + const float alpha = (step_avg > prev) ? a_slow_eff : a_fast; isv[RL_AVG_WIN_USD_EMA_INDEX] = (1.0f - alpha) * prev + alpha * step_avg; } if (n_loss > 0) { const float step_avg = sum_loss / (float)n_loss; const float prev = isv[RL_AVG_LOSS_USD_EMA_INDEX]; - // Asymmetric: fast when admitting larger losses (safety), - // slow when "forgetting" (post-trauma persistence). - const float alpha = (step_avg > prev) ? a_fast : a_slow; + // avg_loss: fast-up (admit losses, safety), slow-down (slow forget) + const float alpha = (step_avg > prev) ? a_fast : a_slow_eff; isv[RL_AVG_LOSS_USD_EMA_INDEX] = (1.0f - alpha) * prev + alpha * step_avg; } } diff --git a/crates/ml-alpha/cuda/rl_win_rate_ema_update.cu b/crates/ml-alpha/cuda/rl_win_rate_ema_update.cu index 1fdefb409..b717984ec 100644 --- a/crates/ml-alpha/cuda/rl_win_rate_ema_update.cu +++ b/crates/ml-alpha/cuda/rl_win_rate_ema_update.cu @@ -29,14 +29,14 @@ #define RL_WIN_RATE_EMA_INDEX 677 #define EMA_ALPHA_FAST 0.05f #define SENTINEL 0.5f -// B-5 asymmetric Wiener-α (2026-06-01). REPLACES B-4 additive cap which -// never engaged at α=0.05 (natural Wiener step ≤ 0.035 < cap=0.05). -// Asymmetric: α_slow on upward (slow to trust high WR optimism), -// α_fast on downward (admit pessimism quickly). -// Math: at α_slow=0.001, single 100% obs from prev=0.3 lifts ema by -// 0.001×0.7 = 0.0007. To reach 0.87 from 0.3: 0.999^N = 0.186 → N=694 steps. -// vs prior cascade reaching 0.87 in 140 steps. -#define RL_EMA_ALPHA_SLOW_INDEX 721 +// B-6 ISV-driven adaptive asymmetric Wiener-α. See B-6 spec for math. +#define RL_EMA_ALPHA_SLOW_MIN_INDEX 721 +#define RL_EMA_TRUST_FULL_THRESHOLD_INDEX 722 +#define RL_EMA_CV_GAIN_INDEX 723 +#define RL_CUMULATIVE_DONES_INDEX 660 +#define RL_REWARD_MAG_VAR_COUNT_INDEX 615 +#define RL_REWARD_MAG_VAR_MEAN_INDEX 616 +#define RL_REWARD_MAG_VAR_M2_INDEX 617 extern "C" __global__ void rl_win_rate_ema_update( float* __restrict__ isv, @@ -62,14 +62,29 @@ extern "C" __global__ void rl_win_rate_ema_update( const float step_wr = (float)wins / (float)closed; const float prev = isv[RL_WIN_RATE_EMA_INDEX]; - // B-5 asymmetric Wiener-α. No first-observation bootstrap branch - // (B-4 removed). Asymmetric admission rates: slow on upward moves - // (skeptical of high win-rate signals), fast on downward (admit - // pessimism). Conservatism for Kelly: under-estimated wr → smaller - // Kelly fraction → safer sizing. - const float a_slow = isv[RL_EMA_ALPHA_SLOW_INDEX]; - const float a_fast = EMA_ALPHA_FAST; - const float alpha = (step_wr > prev) ? a_slow : a_fast; + // B-6 ISV-driven adaptive asymmetric Wiener-α. + // α_slow_eff = α_slow_min + (α_fast − α_slow_min) × trust_eff + const float a_slow_min = isv[RL_EMA_ALPHA_SLOW_MIN_INDEX]; + const float a_fast = EMA_ALPHA_FAST; + const float n_trades = isv[RL_CUMULATIVE_DONES_INDEX]; + const float n_full = isv[RL_EMA_TRUST_FULL_THRESHOLD_INDEX]; + const float cv_gain = isv[RL_EMA_CV_GAIN_INDEX]; + + float trust = (n_full > 0.0f) ? fminf(1.0f, n_trades / n_full) : 1.0f; + if (cv_gain > 0.0f) { + const float wf_count = isv[RL_REWARD_MAG_VAR_COUNT_INDEX]; + if (wf_count > 1.0f) { + const float wf_m2 = isv[RL_REWARD_MAG_VAR_M2_INDEX]; + const float wf_mean = isv[RL_REWARD_MAG_VAR_MEAN_INDEX]; + const float wf_var = wf_m2 / (wf_count - 1.0f); + const float cv = (wf_mean > 1e-6f) ? sqrtf(wf_var) / wf_mean : 0.0f; + trust *= expf(-cv * cv_gain); + } + } + const float a_slow_eff = a_slow_min + (a_fast - a_slow_min) * trust; + + // wr_ema: slow-up (skeptical of high WR), fast-down (admit pessimism) + const float alpha = (step_wr > prev) ? a_slow_eff : a_fast; float ema_new = (1.0f - alpha) * prev + alpha * step_wr; // Hard-bound [0, 1] in case of any precision corner case. if (ema_new < 0.0f) ema_new = 0.0f; diff --git a/crates/ml-alpha/src/rl/isv_slots.rs b/crates/ml-alpha/src/rl/isv_slots.rs index ff5176723..99058255e 100644 --- a/crates/ml-alpha/src/rl/isv_slots.rs +++ b/crates/ml-alpha/src/rl/isv_slots.rs @@ -1585,23 +1585,35 @@ pub const RL_POS_MAX_EMA_GROWTH_CAP_CV_GAIN_INDEX: usize = 719; // positive position alive during warmup. pub const RL_KELLY_BOOTSTRAP_FLOOR_INDEX: usize = 720; -// B-5 asymmetric Wiener-α for Kelly-input EMAs (2026-06-01). -// Math: single per-step rate-cap CANNOT bound EMA convergence to tail- -// dominated means — α=0.05 sustained observations admit ANY tail magnitude -// over O(50) steps. Asymmetric α gives provably biased estimates skewed -// toward safety (under-estimate b̂ in Kelly): -// avg_win: α_slow on up (slow trust optimism), α_fast on down -// avg_loss: α_fast on up (admit losses fast), α_slow on down -// wr_ema: α_slow on up (slow trust high WR), α_fast on down -// At α_slow=0.001: 100 steps of $40k tail → ema = $3800 (10% of true) -// rather than B-4's $40k peak (admit-anything via Wiener). Provably -// underestimates Kelly's b ratio → conservative sizing by construction. -// B-5 supersedes B-4's caps (which didn't engage / leaked over time). -pub const RL_EMA_ALPHA_SLOW_INDEX: usize = 721; +// B-5/B-6 asymmetric Wiener-α with adaptive trust schedule (2026-06-01). +// +// Math: Bayesian shrinkage. The "skeptical" admission rate α_slow blends +// toward the "normal" α_fast=0.05 as data confidence accumulates. +// +// trust_n = min(1, cum_dones / n_full_threshold) [Phase 1] +// stability = exp(-CV(reward_mag) × cv_gain) [Phase 2] +// trust_eff = trust_n × stability +// α_slow_eff = α_slow_min + (α_fast − α_slow_min) × trust_eff +// +// Phase 1 (slots 721, 722): data-quantity gating. cum_dones reset by +// reset_session_state → asymmetry resumes at every fold boundary. +// Phase 2 (slot 723): data-quality gating. Volatile signals (high CV) +// stay skeptical even with abundant data. cv_gain=0 disables Phase 2. +// +// Per-EMA asymmetry direction encodes Kelly safety semantics: +// avg_win: slow-up (skeptical of wins), fast-down +// avg_loss: fast-up (admit losses), slow-down (slow-forget) +// wr_ema: slow-up (skeptical of high WR), fast-down +// +// Spec: docs/superpowers/specs/2026-06-01-ema-asymmetric-trust-with-cv-gain.md +pub const RL_EMA_ALPHA_SLOW_MIN_INDEX: usize = 721; +pub const RL_EMA_TRUST_FULL_THRESHOLD_INDEX: usize = 722; +pub const RL_EMA_CV_GAIN_INDEX: usize = 723; /// Last RL-allocated slot index (exclusive). /// Pre-risk-stack: 662. Post-Fix-B: 685. Post-v9 (warmup boundary): 696. /// Post-regime-observer (F1.1): 716. Post-warmed-flag addendum: 717. /// Post-cold-start-redesign B-2: 720. Post-Kelly-trust B-3: 721. -/// Post-EMA-rate-cap-extension B-4 / B-5 asymmetric-α: 722. -pub const RL_SLOTS_END: usize = 722; +/// Post-EMA-rate-cap B-4 / B-5 asymmetric-α: 722. +/// Post-B-6 trust + CV gain: 724. +pub const RL_SLOTS_END: usize = 724; diff --git a/crates/ml-alpha/src/trainer/integrated.rs b/crates/ml-alpha/src/trainer/integrated.rs index 56a13f531..96a68440a 100644 --- a/crates/ml-alpha/src/trainer/integrated.rs +++ b/crates/ml-alpha/src/trainer/integrated.rs @@ -3364,7 +3364,7 @@ impl IntegratedTrainer { // (slot, value) pair — pure device write, no HtoD per // `feedback_no_htod_htoh_only_mapped_pinned`. { - let isv_constants: [(usize, f32); 209] = [ + let isv_constants: [(usize, f32); 211] = [ // Static seeds for the adaptive reward-clamp controller — // these are the initial values that // `rl_reward_clamp_controller` will replace once it @@ -3715,12 +3715,15 @@ impl IntegratedTrainer { (crate::rl::isv_slots::RL_KELLY_BOOTSTRAP_FLOOR_INDEX, 0.05_f32), (crate::rl::isv_slots::RL_AVG_WIN_USD_EMA_INDEX, 1.0_f32), (crate::rl::isv_slots::RL_AVG_LOSS_USD_EMA_INDEX, 1.0_f32), - // B-5 asymmetric Wiener-α for Kelly inputs (replaces B-4 caps). - // α_slow=0.001 in the SAFETY direction (slow trust optimism for - // avg_win/wr; slow forget pessimism for avg_loss). Math: half-life - // 693 steps in slow direction → eval[100] reaches only 10% of true - // tail. Existing α=0.05 retained as fast direction (half-life 14). - (crate::rl::isv_slots::RL_EMA_ALPHA_SLOW_INDEX, 0.001_f32), + // B-6 ISV-driven adaptive asymmetric Wiener-α (Bayesian shrinkage). + // α_slow_min = floor admission rate (full skepticism at cold-start). + // n_full_threshold = cumulative dones for full data trust (asymmetry + // fades fully). Reset_session_state zeroes cum_dones → asymmetry + // resumes at every fold boundary. Phase 2: cv_gain modulates trust + // by signal volatility (Welford CV); 0 disables Phase 2. + (crate::rl::isv_slots::RL_EMA_ALPHA_SLOW_MIN_INDEX, 0.001_f32), + (crate::rl::isv_slots::RL_EMA_TRUST_FULL_THRESHOLD_INDEX, 30000.0_f32), + (crate::rl::isv_slots::RL_EMA_CV_GAIN_INDEX, 1.0_f32), ]; for (slot, value) in isv_constants.iter() { let slot_i32 = *slot as i32; @@ -9530,7 +9533,9 @@ impl IntegratedTrainer { "pos_max_ema_growth_cap_base": isv[crate::rl::isv_slots::RL_POS_MAX_EMA_GROWTH_CAP_BASE_INDEX], "pos_max_ema_growth_cap_cv_gain": isv[crate::rl::isv_slots::RL_POS_MAX_EMA_GROWTH_CAP_CV_GAIN_INDEX], "kelly_bootstrap_floor": isv[crate::rl::isv_slots::RL_KELLY_BOOTSTRAP_FLOOR_INDEX], - "ema_alpha_slow": isv[crate::rl::isv_slots::RL_EMA_ALPHA_SLOW_INDEX], + "ema_alpha_slow_min": isv[crate::rl::isv_slots::RL_EMA_ALPHA_SLOW_MIN_INDEX], + "ema_trust_full_threshold": isv[crate::rl::isv_slots::RL_EMA_TRUST_FULL_THRESHOLD_INDEX], + "ema_cv_gain": isv[crate::rl::isv_slots::RL_EMA_CV_GAIN_INDEX], }, "isv_lr": { "bce": isv[RL_LR_BCE_INDEX], diff --git a/crates/ml-alpha/tests/eval_diag_emission.rs b/crates/ml-alpha/tests/eval_diag_emission.rs index ad6f4f99e..113a00bbb 100644 --- a/crates/ml-alpha/tests/eval_diag_emission.rs +++ b/crates/ml-alpha/tests/eval_diag_emission.rs @@ -212,8 +212,12 @@ fn eval_diag_jsonl_emitted_with_train_schema_parity() -> Result<()> { // 2026-05-31 B-3: +1 leaf for kelly_bootstrap_floor (fractional-trust schedule). // New total: 648. // 2026-06-01 B-4: +1 leaf for wr_ema_max_delta (Hoeffding-bound additive cap). - // New total: 649. - const EXPECTED_LEAVES: usize = 649; + // New total: 649. B-5 reused slot 721 (renamed wr_ema_max_delta → ema_alpha_slow, + // diag leaf renamed; count unchanged). + // 2026-06-01 B-6: +2 leaves for ema_trust_full_threshold + ema_cv_gain + // (Bayesian shrinkage adaptive trust schedule + Welford-CV gain). + // New total: 651. + const EXPECTED_LEAVES: usize = 651; anyhow::ensure!( train_paths.len() == EXPECTED_LEAVES, "train leaf count {} != expected {EXPECTED_LEAVES} (schema drifted; \ diff --git a/docs/superpowers/specs/2026-06-01-ema-asymmetric-trust-with-cv-gain.md b/docs/superpowers/specs/2026-06-01-ema-asymmetric-trust-with-cv-gain.md new file mode 100644 index 000000000..978ae77f7 --- /dev/null +++ b/docs/superpowers/specs/2026-06-01-ema-asymmetric-trust-with-cv-gain.md @@ -0,0 +1,259 @@ +# Asymmetric Wiener-α EMA with Trust Schedule and Welford-CV Gain (B-6) + +**Date:** 2026-06-01 +**Status:** Draft (post B-5 cluster diagnosis) +**Parent docs:** +- `2026-05-31-pos-max-ema-cold-start-redesign.md` (B-2) +- `2026-05-31-eval-boundary-addendum-reward-scale-and-train-spike.md` (A3) +**Pearls referenced:** +- `pearl_controller_anchors_isv_driven` — every controller anchor ISV-driven +- `feedback_adaptive_not_tuned` — signal-driven, not tuned constants +- `feedback_isv_for_adaptive_bounds` — bounds in ISV +- `pearl_first_observation_bootstrap` — the underlying anti-pattern +- `pearl_adaptive_carryover_discipline` — predictive vs normalization EMAs +- `pearl_multiplicative_controllers_need_bounded_step_and_noise_floor` + +--- + +## 1. Motivation + +The B-4 multiplicative/additive caps failed math gap (1.5^N unbounded; additive cap never engaged). B-5 introduced **asymmetric Wiener-α** with α_slow=0.001 in safety direction → provably biased estimator → Kelly under-sized by construction. + +Cluster diagnosis (alpha-rl-dckcc step 800, B-5): +- avg_w max peak: **$2,037** (vs B-4's $40,911 — **20× reduction** ✓) +- wr_ema max: **0.50** (vs B-4's 0.88 — never exceeded ✓) +- BUT wr_ema crashed to 0.145 by step 800, avg_l > avg_w → Kelly says don't trade +- Model trains on near-zero positions → can't discover edges + +**The fundamental tension:** static α_slow can't satisfy both: +- Train: asymmetry must FADE so model learns from real magnitudes +- Eval boundary: asymmetry must ENGAGE so no cascade + +## 2. Goals + +### G1 — ISV-driven adaptive α_slow +Replace static α_slow=0.001 with signal-derived value that adapts to data confidence. Per `pearl_controller_anchors_isv_driven`, every controller anchor must be ISV-driven (signal-derived, not just stored). + +### G2 — Boundary safety via resettable counter +Asymmetry must resume at every eval boundary. Use `RL_CUMULATIVE_DONES_INDEX` (slot 660) which is reset by `reset_session_state` — same counter as B-3 Kelly trust schedule. Single source of truth for "data confidence". + +### G3 — Train convergence +After sufficient data accumulation, asymmetry fades to standard Wiener-α=0.05. Model learns from real signal magnitudes; b̂ and wr̂ approach true values. + +### G4 — Volatility-aware skepticism +Beyond cumulative count, the EFFECTIVE confidence depends on signal stability. A volatile signal (high CV) demands more skepticism even at high trade counts. Phase 2 adds Welford-CV gain modulating the trust schedule. + +### G5 — Non-goals +- Don't change asymmetry DIRECTION (slow-up for wins/wr, fast-up for losses) — that encodes the loss-aversion safety semantic +- Don't replace Wiener-α with Kalman/median estimators — out of scope, bigger refactor +- Don't add additional EMAs beyond avg_win, avg_loss, wr_ema in this phase + +## 3. Design + +### 3.1 Phase 1 — Trust schedule on α_slow (data-confidence) + +``` +trust(n) = min(1, n_cumulative_dones / n_full_threshold) +α_slow_eff = α_slow_min + (α_fast − α_slow_min) × trust(n) +``` + +For each EMA, asymmetric direction blends α_fast and α_slow_eff: +```c +// avg_win: slow-up, fast-down (skeptical of wins) +alpha = (step_avg > prev) ? α_slow_eff : α_fast; + +// avg_loss: fast-up, slow-down (admit losses, slow forget) +alpha = (step_avg > prev) ? α_fast : α_slow_eff; + +// wr_ema: slow-up, fast-down (skeptical of high WR) +alpha = (step_wr > prev) ? α_slow_eff : α_fast; +``` + +#### Math properties + +**Cold-start** (n=0): α_slow_eff = α_slow_min = 0.001 → full asymmetry → b̂ underestimated → conservative Kelly. + +**Mid-training** (n=n_full/2): α_slow_eff = (α_slow_min + α_fast)/2 = 0.0255 → moderate asymmetry → b̂ slightly biased low. + +**Post-warmup** (n ≥ n_full): α_slow_eff = α_fast = 0.05 → standard Wiener → unbiased. + +**Eval boundary** (reset_session_state zeros cum_dones): α_slow_eff = α_slow_min → asymmetry RESUMES → conservative re-engagement. + +#### Threshold calibration + +`n_full_threshold = 30,000` derived from: + +1. **Hoeffding for proportions**: `n = 1.844 / ε²` for confidence 95%. For wr precision ε=0.05: n=738. For ε=0.01 (very tight): n=18,440. + +2. **For heavy-tailed magnitudes**: variance bounds don't apply. Need larger n for reliable mean estimation. Conservative choice: 30k. + +3. **Eval phase coverage**: at b=1024 with ~30 dones/step, eval phase (500 steps) generates ~15k dones → trust climbs to 0.5 by eval end. Asymmetry remains partially active throughout eval. Good safety property. + +4. **Train phase**: ~1000 training steps for trust to fully open (~30k dones / 30 dones/step). Sufficient for cascade prevention during cold-start (first 30 steps where peak occurs). + +### 3.2 Phase 2 — Welford-CV gain on trust (volatility-aware) + +The Phase 1 trust schedule treats all data as equally trustworthy after n_full. But noisy signals demand more skepticism. Phase 2 modulates trust by the signal's Coefficient of Variation: + +``` +CV(signal) = σ_welford / μ_welford (from existing Welford triplet) +stability = exp(-CV × γ_cv) // bounded [0, 1] +trust_eff = trust(n) × stability +α_slow_eff = α_slow_min + (α_fast − α_slow_min) × trust_eff +``` + +Where: +- `γ_cv` (ISV slot 723, default 1.0): sensitivity to CV. Higher = more skeptical of volatile signals. +- CV=0 (perfectly stable): stability=1, trust_eff = trust(n) +- CV=1 (σ=μ, classical "noisy"): stability=exp(-1)=0.37, trust gated to 37% of n-based value +- CV=2 (σ=2μ, highly volatile): stability=0.14, heavily skeptical + +#### Math properties (Phase 2) + +**Stable regime** (low CV): trust opens normally with data accumulation. + +**Volatile regime** (high CV): trust capped low even with abundant data → α_slow_eff stays near α_slow_min → continued conservatism in volatile markets. Matches surfer-trading philosophy: be skeptical when the wave isn't clean. + +**Adaptive across regimes**: as market changes volatility, asymmetry naturally tightens/loosens. No manual retuning needed. + +### 3.3 Welford signals available + +The signal_variance_update kernel maintains Welford triplets for: +- `RL_REWARD_MAGNITUDE_EMA_INDEX` (slot 614) — used by reward_scale controller +- `RL_PPO_RATIO_VAR_M2_INDEX` (slot ~422) +- `RL_ADV_VAR_M2_INDEX`, `RL_ENTROPY_OBS_VAR_M2_INDEX`, etc. + +For our purposes we need Welford on: +- `RL_AVG_WIN_USD_EMA_INDEX` (slot 678) — per-step avg_win signal +- `RL_AVG_LOSS_USD_EMA_INDEX` (slot 679) — per-step avg_loss signal +- `RL_WIN_RATE_EMA_INDEX` (slot 677) — per-step wr signal + +Two options: +- **A**: Extend signal_variance_update to also track avg_win/loss/wr triplets (new Welford slots) +- **B**: Approximate CV using existing reward_magnitude Welford (slot 614) — same underlying volatility + +Option B is simpler and avoids slot proliferation. The reward magnitude EMA captures overall signal volatility, which is correlated with the per-EMA volatility we'd otherwise track separately. + +**Decision**: Phase 2 uses Option B (existing Welford). If empirically inadequate, Phase 2.5 adds per-EMA Welford. + +## 4. ISV slot allocation + +| Slot | Name | Default | Description | +|---|---|---:|---| +| 721 | `RL_EMA_ALPHA_SLOW_MIN_INDEX` | 0.001 | Minimum α for asymmetric admission (= full skepticism) | +| 722 | `RL_EMA_TRUST_FULL_THRESHOLD_INDEX` | 30,000 | Cumulative dones for trust=1.0 (asymmetry fades) | +| 723 | `RL_EMA_CV_GAIN_INDEX` | 1.0 | Sensitivity to signal CV for Phase 2 gating. Set to 0.0 to disable Phase 2 (pure Phase 1) | + +All ISV-CONFIGURABLE constants. The α_slow_eff itself is signal-DRIVEN (computed at runtime from cum_dones + Welford). + +## 5. Implementation + +### 5.1 Kernel updates + +**`rl_avg_win_loss_ema_update.cu`** + **`rl_win_rate_ema_update.cu`**: +```c +const float a_slow_min = isv[RL_EMA_ALPHA_SLOW_MIN_INDEX]; +const float a_fast = EMA_ALPHA_FAST; // 0.05 +const float n_trades = isv[RL_CUMULATIVE_DONES_INDEX]; +const float n_full = isv[RL_EMA_TRUST_FULL_THRESHOLD_INDEX]; +const float cv_gain = isv[RL_EMA_CV_GAIN_INDEX]; + +// Phase 1: trust schedule +float trust = (n_full > 0.0f) ? fminf(1.0f, n_trades / n_full) : 1.0f; + +// Phase 2: CV gain (only if cv_gain > 0) +if (cv_gain > 0.0f) { + const float wf_count = isv[RL_REWARD_MAG_VAR_COUNT_INDEX]; + if (wf_count > 1.0f) { + const float wf_m2 = isv[RL_REWARD_MAG_VAR_M2_INDEX]; + const float wf_mean = isv[RL_REWARD_MAG_VAR_MEAN_INDEX]; + const float wf_var = wf_m2 / (wf_count - 1.0f); + const float cv = (wf_mean > 1e-6f) ? sqrtf(wf_var) / wf_mean : 0.0f; + const float stability = expf(-cv * cv_gain); + trust *= stability; + } +} + +const float a_slow_eff = a_slow_min + (a_fast - a_slow_min) * trust; + +// Asymmetric direction (per-EMA semantics) +// avg_win: slow-up, fast-down +// avg_loss: fast-up, slow-down (mirror) +// wr_ema: slow-up, fast-down +const float alpha = (X > prev) ? a_slow_eff : a_fast; // avg_win, wr_ema +// OR for avg_loss: +const float alpha = (X > prev) ? a_fast : a_slow_eff; +``` + +### 5.2 Bootstrap entries (`with_controllers_bootstrapped`) + +```rust +(RL_EMA_ALPHA_SLOW_MIN_INDEX, 0.001_f32), // was just _SLOW_, renamed +(RL_EMA_TRUST_FULL_THRESHOLD_INDEX, 30000.0_f32), +(RL_EMA_CV_GAIN_INDEX, 1.0_f32), // Phase 2 enabled by default +``` + +### 5.3 Diag emission + +Add to `build_diag_value`: +```rust +"ema_alpha_slow_min": isv[RL_EMA_ALPHA_SLOW_MIN_INDEX], +"ema_trust_full_threshold": isv[RL_EMA_TRUST_FULL_THRESHOLD_INDEX], +"ema_cv_gain": isv[RL_EMA_CV_GAIN_INDEX], +``` + +### 5.4 Test schema update + +`tests/eval_diag_emission.rs`: EXPECTED_LEAVES 649 → 651 (+2 new leaves; one slot renamed). + +## 6. Validation gates + +### V1 — Math invariants +- At step 1 (cold-start, cum_dones=0): observed alpha = α_slow_min when admission direction is safety, α_fast otherwise. +- At step 1000 (cum_dones ≫ n_full): observed alpha → α_fast in both directions. +- At eval[1] (post-reset_session_state): observed alpha = α_slow_min again. + +### V2 — Cascade bounds +- avg_win peak ≤ $5k (vs B-4 $40k, B-5 $2k baseline) +- wr_ema max ≤ 0.50 in first 100 steps; can rise above as trust ramps in train +- avg_loss > avg_win NOT permanent (should normalize as data accumulates) + +### V3 — Train convergence +- By step 1000+: wr_ema ≈ true wr (0.30-0.40 range typical for this codebase) +- avg_win > avg_loss for profitable runs (model finds edges) +- Kelly active sizing (not constantly at f_floor) + +### V4 — Cluster eval pnl +- vs B-3 (x56wn): -$118M +- vs B-4 (zh96b, killed): partial data showed similar peaks +- vs B-5 (dckcc): pending (likely terrible due to over-conservatism) +- Target: ≤ -$30M (10× improvement) + +## 7. Risks + +- **Risk α (low)**: n_full_threshold = 30,000 too high → asymmetry persists into train, hampers learning. Mitigation: ISV-tunable, can lower to 5,000 if needed. +- **Risk β (medium)**: Welford CV from reward_magnitude (Option B) inadequate proxy for per-EMA volatility. Mitigation: Phase 2.5 adds per-EMA Welford if validation fails. +- **Risk γ (low)**: CV gain γ_cv=1.0 too aggressive → trust permanently low in volatile markets. Mitigation: tunable, can disable Phase 2 by setting γ_cv=0. +- **Risk δ (low)**: Resetting cum_dones at boundary disrupts both Kelly trust AND EMA asymmetry → double safety effect at boundary might over-suppress. Mitigation: empirical — eval result reveals if too conservative; can split into separate counters if needed. + +## 8. Generalization + +The pattern is: + +> **Every adaptive EMA whose value gates downstream behavior should use asymmetric Wiener-α with the SAFETY direction admitting at a rate that depends on (a) cumulative data confidence and (b) signal volatility. The asymmetry direction encodes the controller's risk preference; the magnitude adapts to data.** + +This is the same statistical principle as **Bayesian shrinkage**: estimates are pulled toward a conservative prior in the absence of strong evidence, and toward empirical values as evidence accumulates. Welford CV provides the noise floor that gates the shrinkage rate. + +Future controllers using this template: +- popart envelope (slot 714) — currently uses fast-up slow-decay; could be ISV-driven too +- inventory variance EMA — currently has dead-signal hold; could integrate this template +- regime_observer EMAs — currently varied semantics; unify under this framework + +## 9. Done means + +- 3 ISV slots allocated (or 2 if reusing 721) +- Both EMA kernels updated with trust-gated asymmetric α +- Diag emission for the new state +- Local smoke shows cold-start asymmetry → mid-train fading → eval boundary re-engagement +- Cluster validation V1-V4 pass +- Pearl update: refine `pearl_first_observation_bootstrap` to include trust-gated asymmetric α as the canonical fix pattern