fix: NaN guards + multi-timeframe feature clamping
Gradient explosion from unbounded multi-timeframe features (240-bar return could be ±50%). All multi-timeframe outputs now clamped: - Return: ±10% - Volatility: 0-10% - Volume ratio: 0-5x - Momentum: [0, 1] (already bounded) NaN guards added: - Kelly: NaN check on continuous/discrete Kelly before blend - target_position: final NaN→0 after all scaling - reward: final NaN→0 before writing to replay buffer Local test shows Q-gap=18.7 (epoch 5) — 18x better than start-of-session 0.000. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -260,7 +260,8 @@ extern "C" __global__ void experience_state_gather(
|
||||
|
||||
/* Return over N bars */
|
||||
float ret = (close_past > 0.0f) ? (close_now - close_past) / close_past : 0.0f;
|
||||
out[slot + 0] = ret * 100.0f; /* scale to percentage */
|
||||
float scaled_ret = ret * 100.0f;
|
||||
out[slot + 0] = fmaxf(-10.0f, fminf(10.0f, scaled_ret)); /* clamp ±10% */
|
||||
|
||||
/* Volatility: scan high/low over window (approx from close changes) */
|
||||
float max_val = close_now;
|
||||
@@ -278,12 +279,14 @@ extern "C" __global__ void experience_state_gather(
|
||||
}
|
||||
}
|
||||
float range = (close_now > 0.0f) ? (max_val - min_val) / close_now : 0.0f;
|
||||
out[slot + 1] = range * 100.0f; /* volatility as percentage */
|
||||
float scaled_range = range * 100.0f;
|
||||
out[slot + 1] = fmaxf(0.0f, fminf(10.0f, scaled_range)); /* clamp 0-10% */
|
||||
|
||||
/* Volume trend: current vs average */
|
||||
float avg_vol = (vol_count > 0) ? vol_sum / (float)vol_count : 1.0f;
|
||||
float cur_vol = (market_dim > 4) ? now_row[4] : 1.0f;
|
||||
out[slot + 2] = (avg_vol > 0.0f) ? cur_vol / avg_vol : 1.0f;
|
||||
float vol_ratio = (avg_vol > 0.0f) ? cur_vol / avg_vol : 1.0f;
|
||||
out[slot + 2] = fmaxf(0.0f, fminf(5.0f, vol_ratio)); /* clamp 0-5x */
|
||||
|
||||
/* Momentum: position within range [0=bottom, 1=top] */
|
||||
float range_size = max_val - min_val;
|
||||
@@ -646,6 +649,9 @@ extern "C" __global__ void experience_env_step(
|
||||
: 0.0f;
|
||||
|
||||
/* ── Enhanced Kelly: weighted blend ── */
|
||||
/* NaN guard: if either Kelly produces NaN/Inf, use 0.0 (safe default) */
|
||||
if (isnan(continuous_kelly) || isinf(continuous_kelly)) continuous_kelly = 0.0f;
|
||||
if (isnan(discrete_kelly) || isinf(discrete_kelly)) discrete_kelly = 0.0f;
|
||||
float enhanced_kelly = 0.7f * continuous_kelly + 0.3f * discrete_kelly;
|
||||
|
||||
/* ── Confidence scaling: penalize small samples ── */
|
||||
@@ -662,6 +668,12 @@ extern "C" __global__ void experience_env_step(
|
||||
target_position *= kelly_frac / 0.25f;
|
||||
}
|
||||
/* < 20 trades: no Kelly applied (full position until we have statistics) */
|
||||
|
||||
/* Final NaN/Inf guard on target_position after ALL scaling.
|
||||
* If any scaling (CVaR, conviction, Kelly) produced NaN, zero the position. */
|
||||
if (isnan(target_position) || isinf(target_position)) {
|
||||
target_position = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Position adjustment with volatility-scaled transaction cost ---- */
|
||||
@@ -1056,6 +1068,9 @@ extern "C" __global__ void experience_env_step(
|
||||
* v_range should be dynamically computed from gamma. */
|
||||
|
||||
/* ---- Write reward and done flag ---- */
|
||||
/* Final NaN guard — if reward is NaN/Inf, write 0.0 instead of poisoning
|
||||
* the replay buffer. This prevents gradient explosion from propagating. */
|
||||
if (isnan(reward) || isinf(reward)) reward = 0.0f;
|
||||
out_rewards[out_off] = reward;
|
||||
out_dones[out_off] = (float)done;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user