// rl_kelly_fraction_controller.cu — Layer 4: half-Kelly position-size multiplier.
//
// Spec: docs/superpowers/specs/2026-05-30-adaptive-risk-management-design.md
//
// Half-Kelly (Thorp) from observed win_rate × R-multiple:
//
//   f_kelly = (p × b − q) / b      where p = win_rate, q = 1 − p,
//                                  b = avg_win / avg_loss
//   f       = clamp(safety × f_kelly, 0, 1)   where safety = 0.5 (half-Kelly)
//
// Warmup gate (`pearl_first_observation_bootstrap` + warmup discipline):
// hold f at bootstrap = 1.0 until `cumulative_dones >= MIN_TRADES_FOR_RELEASE`
// so the EMA inputs (win_rate, avg_win, avg_loss) have time to converge
// past cold-start noise. After release, f tracks observed edge — if
// edge is negative or zero, Kelly clamps to 0 = no commitment.
//
// Dead-signal guard: if avg_loss <= 0 (sentinel, no losses observed yet)
// hold f at 1.0 — we don't have a denominator for the R-multiple.
//
// Per `feedback_no_atomicadd`: single-thread single-block.
// Per `feedback_cpu_is_read_only`: pure device kernel.
// Per `feedback_isv_for_adaptive_bounds`: safety multiplier + warmup gate
// are ISV-driven (slots 680, 681).

#include <stdint.h>

#define RL_KELLY_FRACTION_INDEX                676
#define RL_WIN_RATE_EMA_INDEX                  677
#define RL_AVG_WIN_USD_EMA_INDEX               678
#define RL_AVG_LOSS_USD_EMA_INDEX              679
#define RL_KELLY_SAFETY_FRAC_INDEX             680
#define RL_KELLY_MIN_TRADES_FOR_RELEASE_INDEX  681
#define RL_CUMULATIVE_DONES_INDEX              660
#define RL_REGIME_DEAD_ZONE_FLAG_INDEX         696
#define RL_REGIME_DEAD_ZONE_TIMEOUT_FLAG_INDEX 698
#define RL_KELLY_EPS_RECOVERY_LIVE_INDEX       706
// Fractional-Kelly trust floor (2026-05-31 B-3). Prevents trade-death
// absorbing state during warmup; replaces the prior binary "f=1.0 during
// warmup" gate that caused 53% sizing at every fold boundary.
#define RL_KELLY_BOOTSTRAP_FLOOR_INDEX         720

extern "C" __global__ void rl_kelly_fraction_controller(
    float* __restrict__ isv
) {
    if (threadIdx.x != 0 || threadIdx.y != 0 || threadIdx.z != 0) return;
    if (blockIdx.x  != 0 || blockIdx.y  != 0 || blockIdx.z  != 0) return;

    const float p        = isv[RL_WIN_RATE_EMA_INDEX];
    const float avg_win  = isv[RL_AVG_WIN_USD_EMA_INDEX];
    const float avg_loss = isv[RL_AVG_LOSS_USD_EMA_INDEX];
    const float safety   = isv[RL_KELLY_SAFETY_FRAC_INDEX];  // typically 0.5

    // ── Fractional-trust schedule (2026-05-31 B-3 fix). Math:
    //    Hoeffding gives |p̂ − p*| ≤ √(ln(40)/(2n)) with 95% confidence.
    //    At N_full = 200 trades: ε = √(1.844/200) ≈ 0.10 (10% Kelly precision).
    //    Below N_full: trust(n) = max(f_floor, n/N_full) gradually opens.
    //    f_floor=0.05 (5%) prevents Kelly trade-stream death — see
    //    `pearl_kelly_trade_stream_death`. Replaces the prior binary gate
    //    `if (n < N_min) kelly = 1.0` which was catastrophic at every
    //    boundary (4xmxm eval[1]: trade_count=0 → kelly=1.0 → 53% sizing
    //    → -$100M eval pnl).
    const float n_trades = isv[RL_CUMULATIVE_DONES_INDEX];
    const float n_full   = isv[RL_KELLY_MIN_TRADES_FOR_RELEASE_INDEX];
    const float f_floor  = isv[RL_KELLY_BOOTSTRAP_FLOOR_INDEX];
    const float trust    = (n_full > 0.0f)
        ? fmaxf(f_floor, fminf(1.0f, n_trades / n_full))
        : 1.0f;  // n_full=0 → trust=1 (gate disabled, for legacy configs)

    // Dead-signal guards: need positive magnitudes for Kelly formula.
    // After B-3 bootstrap (avg_win=avg_loss=1.0 in `with_controllers_bootstrapped`
    // AND in `reset_session_state`), these branches only fire if a controller
    // upstream zeroed them (shouldn't happen). Trust × safety provides a small
    // positive position to keep trades flowing for the trade-stream warmup.
    if (avg_loss <= 0.0f || avg_win <= 0.0f) {
        isv[RL_KELLY_FRACTION_INDEX] = fmaxf(0.0f, fminf(trust * safety, 1.0f));
        return;
    }

    const float b       = avg_win / avg_loss;
    const float q       = 1.0f - p;
    const float f_kelly = (p * b - q) / b;
    // f_safe = max(f_floor·safety, f_kelly · trust · safety) — trust schedule
    // gates aggressive Kelly during warmup; floor prevents trade-stream death.
    const float f_raw   = f_kelly * trust * safety;
    const float f_min   = f_floor * safety;
    float       f       = fmaxf(f_min, f_raw);
    f = fmaxf(0.0f, fminf(f, 1.0f));
    isv[RL_KELLY_FRACTION_INDEX] = f;

    // Kelly resurrection (Theorem 1): override analytic kelly if DEAD_ZONE_FLAG fires.
    //
    // Two safety checks:
    //   1. DEAD_ZONE_TIMEOUT_FLAG: if dead-zone has persisted > MAX_DURATION steps,
    //      stop trying to resurrect (let kelly stay at 0; halt the bleed).
    //      The trainer monitors TIMEOUT_FLAG as a halt-training signal.
    //   2. Otherwise: kelly_f overridden with ε_recovery_live (computed by regime_observer).
    const int dead_zone = (int)isv[RL_REGIME_DEAD_ZONE_FLAG_INDEX];
    const int timeout   = (int)isv[RL_REGIME_DEAD_ZONE_TIMEOUT_FLAG_INDEX];
    if (dead_zone && !timeout) {
        isv[RL_KELLY_FRACTION_INDEX] = isv[RL_KELLY_EPS_RECOVERY_LIVE_INDEX];
    }
}
