Files
foxhunt/docs
jgrusewski 7e54dedd22 fix(data): Fix 30 Stale-C — scripted_policy raw_close from fxcache targets
Closes Fix 29 audit row #12. Pre-fix, `scripted_policy_select` read
`state[MARKET_START]` as `close_now` and computed
`recent_ret = (close_now - prev_close) / prev_close`. Post Bug-1
(commit `5a5dd0fed`) `state[MARKET_START]` is z-normed log-return at
the WRITER, not raw_close, so the formula mixed two unrelated signals
(z-normed return vs dollar price). The seed-phase MOMENTUM /
MEAN_REV / VWAP_DEV branches degenerated to noise-floor below the
±0.0001f cutoffs, leaving 60% of seed-phase episodes (the non-UNIFORM
20%+20%+20%) effectively in DIR_HOLD instead of expressing the
intended scripted-policy diversity.

Resolution: route raw_close from the same fxcache target buffer the
env_step kernel reads from. Kernel signature gains
`const float* targets`, `const int* episode_starts`, `int t`,
`int total_bars` parameters. Per-thread `bar_idx = episode_starts[i]
+ t` (matching `experience_kernels.cu:1769`'s indexing convention),
then `close_now = targets[bar_idx*6 + TARGET_RAW_CLOSE]`. Bounds-clamp
to `[0, total_bars-1]` mirrors env_step's out-of-bounds early-return.

Sites fixed:
  - crates/ml/src/cuda_pipeline/scripted_policy_kernel.cu
    Kernel signature extended; close_now read source switched;
    `FXCACHE_TARGET_STRIDE` / `FXCACHE_TARGET_RAW_CLOSE` mirrored as
    #defines (kernels can't import Rust constants; co-locating the
    literals in a comment-block keeps the cross-language contract
    visible at the call site for any future TARGET_DIM bump). The
    previous `bar_idx` parameter renamed `t`; the LCG seed now mixes
    per-thread `bar_idx = episode_starts[i] + t` instead of the
    per-step `t` — stronger entropy across episodes, no behavioural
    regression (UNIFORM policy still produces 4-direction uniform).
  - crates/ml/src/cuda_pipeline/gpu_experience_collector.rs:~3735
    Single launcher updated to pass `&targets_buf.dev_ptr`,
    `&self.episode_starts_buf`, `t as i32`, and `total_bars` (already
    in scope from line 3301). Inline comment explains Bug-1 origin.

Migration scope per `feedback_no_partial_refactor`: single launcher;
no other call sites. Verified via
`grep -rn scripted_policy_select crates/ml/src/`. NO new `PS_*` slot
or `PORTFOLIO_STRIDE` bump required — the targets buffer is the
canonical raw_close source and routing it directly avoids cascading
through the ~12 PORTFOLIO_STRIDE consumers (kelly_cap_update_kernel,
trade_stats_kernel, gpu_experience_collector, ml-core mirror, etc.).
This is the "route raw_close directly" branch of the audit's contract
decision; the alternative "add an extra state slot" branch was
explicitly rejected as cascade-heavy for a seed-phase-only signal.

Verification:
  - SQLX_OFFLINE=true cargo check -p ml --offline (43.87s) clean.
  - SQLX_OFFLINE=true cargo build -p ml --release --offline
    --features cuda (1m 30s) clean; cubin recompiled via nvcc.
  - No host-side compute added; all reads on GPU.
  - `targets_buf` is mapped pinned by upstream caller (per
    `feedback_no_htod_htoh_only_mapped_pinned`).

Refs Fix 29 row #12. `feedback_no_partial_refactor` (single launcher
migrated in same commit), `feedback_no_functionality_removal`
(`recent_ret` signal preserved — only its data source is fixed; the
CUSUM-substitution alternative path explicitly rejected because the
recent_ret signal IS the scripted-policy contract, not an
implementation accident), `feedback_no_hiding` (no fallback to
z-normed-log-return reads; kernel either gets real raw_close or
clamps to total_bars-1 boundary), `feedback_no_cpu_compute_strict`
n/a (zero new host-side compute), `feedback_trust_code_not_docs` (the
kernel comment said `state[MARKET_START + 0] = close_now` for months
— accurate-when-written, stale-after-Bug-1; verify-against-code
disambiguates).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 22:36:28 +02:00
..