# Aux Supervision Candidate B Pivot — A+B Paired (Profitable Binary + Sized Regression)

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task.

**Goal:** Replace the D-style asymmetric labels (Layer B of prior plan) with Candidate B's A+B paired design from E3's original recommendation: binary "profitable-after-cost" + σ-normalized magnitude regression, per (long, short) direction per horizon. Driven by Smoke-1-v2 empirical finding: D labels are 99.99% negative at K=100 and 94-96% negative at K=1000, making them un-learnable. A+B's class-weighted BCE on a balanced (per-horizon-prior-corrected) binary plus a conditional-Huber on magnitude is empirically learnable.

**Architecture:** Keep aux trunk infrastructure (B3 — AUX_HIDDEN=64 CfC). Replace D label generator (B1), replace aux loss kernel (B4), add second head to aux_heads.cu (B4), adjust trainer wiring (B5) to feed both losses through separate Adam moments.

## Pre-conditions

Branch: `ml-alpha-phase-a` HEAD `2e0629767`. Working tree clean. Smoke-1-v2 (`alpha-perception-dz6r4`) validated:
- BCE training non-regressed (bit-identical to baseline)
- Aux supervision FIRES on real data (Huber drops 2029→1268)
- Stop-grad never lifts (threshold never reached due to D-label scale mismatch)

## Sub-decisions (resolve before implementation)

| # | Decision | Default | Rationale |
|---|---|---|---|
| 1 | y_prof label definition | `y_prof[t,k,d] = 1 if signed_pnl_d > 2×cost else 0` | E3 default. The `2×cost` threshold (vs just `>0`) lifts the bar above breakeven noise. |
| 2 | y_size label definition | `y_size[t,k,d] = signed_pnl_d / σ_K[t]` | σ_K[t] = rolling 1000-bar std of K-step log-returns (Welford EMA). Per-horizon σ. |
| 3 | Aux head output structure | 4 outputs per (direction): `[prof_long, size_long, prof_short, size_short] × N_HORIZONS` = 12 total | Single fused head for cache locality; loss kernel splits at output. Alternative: 4 separate heads (rejected — more launches, no perf gain). |
| 4 | Loss balancing | `total_aux = α_prof × BCE(prof) + α_size × Huber(size if prof else 0)` per direction; per-Adam-group LR scaling | E3 + `pearl_adam_normalizes_loss_weights`: don't sum-weight, use separate optimizer groups OR per-loss-function gradient scaling. Simplest first cut: single aux Adam group, but BCE and Huber inherently produce different gradient scales — no need for explicit α. |
| 5 | Class-weight for BCE | `pos_weight = n_neg / n_pos` per horizon, recomputed each epoch from EMA-tracked counts | Per E3. Without this, the 99.99% negative class drives all-negative predictions (same trap D fell into). |
| 6 | Conditional-Huber gating | Mask y_size loss if `y_prof_true == 0` (don't penalize size estimates for unprofitable trades) | Per E3. Keeps the size head focused on the actually-profitable subset. Alternative: train size on ALL samples (rejected — would force size head to predict large negative magnitudes for unprofitable cases, defeating the purpose). |
| 7 | σ_K window | 1000-snapshot Welford EMA | Matches K=1000 horizon naturally. Smaller windows would be noisier at K=10. |
| 8 | Aux head initialization | Smaller Xavier scale (0.1× instead of standard) to keep initial outputs close to 0 | Empirical fix from D-label run: standard Xavier gave outputs ~5× label magnitude. Smaller init = faster convergence. |
| 9 | Aux gate in policy (B7) | Gate trade entry on `prof_long_prob > 0.55 AND size_long_pred > 0.3 × σ_K` (or the short equivalent) | Two-threshold gate per direction: profitability confidence + minimum expected size. |

## File scope

### Modified files (most reused from B1-B5)
- `crates/ml-alpha/src/multi_horizon_labels.rs` — REPLACE `generate_outcome_labels_d` → `generate_outcome_labels_ab` (add σ_K rolling computation)
- `crates/ml-alpha/src/data/loader.rs` — `OutcomeLabelsD` → `OutcomeLabelsAB` field rename + rolling-σ_K wiring
- `crates/ml-alpha/cuda/aux_heads.cu` — change output structure (4 values per direction × N_HORIZONS = 12 outputs from 64-dim aux trunk hidden)
- `crates/ml-alpha/cuda/aux_loss.cu` — REPLACE Huber with class-weighted BCE for prof + conditional-Huber for size
- `crates/ml-alpha/src/aux_heads.rs` — adjust weight structs, fwd/bwd wrappers
- `crates/ml-alpha/src/trainer/perception.rs` — wire 2 losses + ISV signals (`aux_prof_bce_ema`, `aux_size_huber_ema`, `aux_prof_dir_acc_ema` per horizon)
- `crates/ml-alpha/examples/alpha_train.rs` — tracing log lines for new aux metrics
- `crates/ml-backtesting/cuda/decision_policy.cu` — TBD in B7 (after this lands)

### Reused as-is
- `crates/ml-alpha/src/cfc/aux_trunk.rs` + `aux_trunk.cu` (no change — same 64-hidden CfC)
- `crates/ml-alpha/cuda/aux_vec_add.cu` (no change — still needed for stop-grad lift)
- Tests in `aux_heads.rs`, `aux_trunk.rs` — need updates for new output structure

## Estimated cost

| Task | Hours |
|---|---|
| Label generator: D → A+B with σ_K | 3 |
| Loader integration changes | 1.5 |
| aux_loss.cu rewrite (BCE + conditional Huber + class weights) | 3 |
| aux_heads.cu output structure change | 1.5 |
| Trainer wiring + ISV signals | 2 |
| Local smoke validation | 0.5 |
| Argo Smoke 1 | 0.5 (wallclock wait) |
| **Total** | **~12 hours** |

## Risks

| Risk | Likelihood | Mitigation |
|---|---|---|
| Class-weighted BCE causes pos-pred explosion | Medium | Cap `pos_weight ≤ 50` per horizon |
| σ_K is too noisy at K=10 | Medium | Floor σ_K at cost-tick scale per `pearl_trade_level_vol_for_stop_distance` |
| Conditional-Huber masks too much data, size head doesn't train | Low | Verify positive-class fraction is reasonable (≥5%) before masking |
| Loss-scale mismatch between BCE and Huber overweights one | Low-Medium | Track per-loss EMAs separately. If divergence, add per-loss LR per `pearl_adam_normalizes_loss_weights` |
| Aux head init still too large after 0.1× Xavier | Medium | Bias init = 0 + small Xavier should put outputs near 0; verify on local smoke |
