# 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
