feat(sp15-wave3a): kernel-side foundation — baseline output buffers + position_history derivation
Wave 3a half of the val-cost-streams refactor (3b host-side wire-up
follows). Atomically migrates the kernel-side contracts; 5 launchers
remain orphan transiently awaiting 3b production callers.
Baseline kernels (1.4):
- 4 baseline_*_kernel signatures gain 'out: float*' parameter writing
per-window [mean, std, raw_sharpe] (matches 1.1.b sharpe_per_bar shape)
- ISV writes to slots 409, 410, 412, 416 removed entirely
(per-window output is correct for WindowMetrics consumption;
ISV-scalar writes were spec scaffolding for a single-fold-aggregate
version that 1.4.b's per-window contract supersedes)
- 4 ISV slot constants removed from sp15_isv_slots.rs
- state_reset_registry: NO entries to remove (verified via grep —
the 4 slots never had registry entries / dispatch arms in the first
place; they were single-fold-aggregate scalars defaulted at every
fold start by the constructor-write that initialises the ISV bus).
Task 4 from the dispatch is a no-op; the
every_fold_and_soft_reset_entry_has_dispatch_arm regression test
continues to pass unchanged.
- 4 oracle tests migrated to output-buffer assertion
- layout_fingerprint_seed string updated (4 retired entries removed,
4 trunk-shared entries retained; layout-break-class change)
New action_decoding_helpers.cuh:
- Extracts factored_action_to_dir_idx + factored_action_to_position
__device__ helpers (the latter is a higher-level position state-
machine helper not previously available)
- Mirrors trade_physics.cuh::decode_direction_4b semantics exactly so
on-policy and counterfactual paths agree on factored-action meaning
- Single source of truth for action→direction→position mapping;
consumers #include the header
New position_history_derivation_kernel.cu (post-loop derivation for
cost_net_sharpe consumer in Wave 3b):
- Reads actions_history_buf, reconstructs per-bar position_history
(-1/0/+1), side_ind (1.0 on position change), rt_ind (1.0 on
transition-to-flat from non-flat) via sequential walk (single
block per window, no atomicAdd per feedback_no_atomicadd)
- New launcher launch_sp15_position_history_derivation in
gpu_dqn_trainer.rs
- New cubin manifest entry in build.rs
- 1 oracle test covering 8-bar Short→Hold→Long→Hold→Flat→Long→Flat→
Short sequence; expected position/side_ind/rt_ind triples match
hand-computed values
Atomic per feedback_no_partial_refactor for the ISV-contract change
(every consumer of slots 409/410/412/416 migrated in this commit; their
consumers were the 4 oracle tests, all migrated). The orphan launcher
transient state for the 5 baselines + derivation kernel is explicitly
the 3a/3b split point — production callers land in 3b's
GpuBacktestEvaluator::new constructor signature change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -854,7 +854,7 @@ fn main() {
|
|||||||
// DD_PCT (406). The 1e-4 calmar floor eliminates the saturation-
|
// DD_PCT (406). The 1e-4 calmar floor eliminates the saturation-
|
||||||
// at-100 artifact previously seen in the train-dd4xl HEALTH_DIAG.
|
// at-100 artifact previously seen in the train-dd4xl HEALTH_DIAG.
|
||||||
"dd_state_kernel.cu",
|
"dd_state_kernel.cu",
|
||||||
// SP15 Phase 1.4 partial (2026-05-06): 4 constant-policy
|
// SP15 Phase 1.4 + Wave 3a (2026-05-06): 4 constant-policy
|
||||||
// counterfactual baselines per spec §6.4. Single source file with
|
// counterfactual baselines per spec §6.4. Single source file with
|
||||||
// 4 `extern "C" __global__` symbols (baseline_buyhold_kernel,
|
// 4 `extern "C" __global__` symbols (baseline_buyhold_kernel,
|
||||||
// baseline_hold_only_kernel, baseline_naive_momentum_kernel,
|
// baseline_hold_only_kernel, baseline_naive_momentum_kernel,
|
||||||
@@ -865,14 +865,26 @@ fn main() {
|
|||||||
// BLOCK=256 with a templated 2-pass shared-memory tree-reduce
|
// BLOCK=256 with a templated 2-pass shared-memory tree-reduce
|
||||||
// (no atomicAdd). Reads price/half_spread/ofi arrays + commission
|
// (no atomicAdd). Reads price/half_spread/ofi arrays + commission
|
||||||
// scalar; computes constant-policy or last-bar-return-based
|
// scalar; computes constant-policy or last-bar-return-based
|
||||||
// actions; writes sharpe_net to its own ISV slot (409 buyhold,
|
// actions; writes per-window `[mean, std, raw_sharpe]` to a
|
||||||
// 410 hold_only, 412 naive_momentum, 416 naive_reversion).
|
// caller-provided output buffer (Wave 3a contract change — was
|
||||||
// Trunk-shared baselines (random_dir_kelly slot 411, aux_only
|
// ISV-scalar slots 409/410/412/416 pre-Wave-3a). Trunk-shared
|
||||||
// slot 413, mag_quarter_fixed slot 414, trail_only slot 415) are
|
// baselines (random_dir_kelly slot 411, aux_only slot 413,
|
||||||
// out of scope for this commit — they need partial-policy-
|
// mag_quarter_fixed slot 414, trail_only slot 415) are out of
|
||||||
// forward access from the main eval pass and are deferred to
|
// scope for this commit — they need partial-policy-forward
|
||||||
// Task 1.4.b.
|
// access from the main eval pass and are deferred to Task 1.4.b.
|
||||||
"baseline_kernels.cu",
|
"baseline_kernels.cu",
|
||||||
|
// SP15 Wave 3a (2026-05-06): post-loop derivation of per-bar
|
||||||
|
// position state from `actions_history_buf` (factored ints
|
||||||
|
// recorded by env_step). Single-block-per-window launch with one
|
||||||
|
// thread per block (the position state machine is sequential per
|
||||||
|
// window — no in-window parallelism). Emits 3 f32 streams per
|
||||||
|
// (window, bar): position_history (-1/0/+1), side_ind (1.0 on
|
||||||
|
// position change), rt_ind (1.0 on transition-to-flat from
|
||||||
|
// non-flat). Consumed by Wave 3b's cost-net sharpe path; uses
|
||||||
|
// `action_decoding_helpers.cuh::factored_action_to_position`
|
||||||
|
// single-source-of-truth for the action→direction→position
|
||||||
|
// mapping (mirrors `trade_physics.cuh::decode_direction_4b`).
|
||||||
|
"position_history_derivation_kernel.cu",
|
||||||
// SP15 Phase 1.5 (2026-05-06): dd_pct foundational state input
|
// SP15 Phase 1.5 (2026-05-06): dd_pct foundational state input
|
||||||
// concat. LAYOUT FINGERPRINT BREAK — pre-SP15 checkpoints WILL
|
// concat. LAYOUT FINGERPRINT BREAK — pre-SP15 checkpoints WILL
|
||||||
// NOT LOAD after this commit. Greenfield OK per spec Q1. Per
|
// NOT LOAD after this commit. Greenfield OK per spec Q1. Per
|
||||||
|
|||||||
63
crates/ml/src/cuda_pipeline/action_decoding_helpers.cuh
Normal file
63
crates/ml/src/cuda_pipeline/action_decoding_helpers.cuh
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// crates/ml/src/cuda_pipeline/action_decoding_helpers.cuh
|
||||||
|
//
|
||||||
|
// SP15 Wave 3a (2026-05-06) — single-source-of-truth `__device__` helpers
|
||||||
|
// for the factored action → direction / position mapping.
|
||||||
|
//
|
||||||
|
// Background: the canonical decoding lives in `trade_physics.cuh`
|
||||||
|
// (`decode_direction_4b`, `decode_magnitude_4b`, …). Those helpers are
|
||||||
|
// already used inline by every consumer that needs the dir/mag/order/urg
|
||||||
|
// indices — `experience_kernels.cu`, `backtest_env_kernel.cu`,
|
||||||
|
// `experience_action_select`, etc. The new SP15 Wave 3a derivation
|
||||||
|
// kernels (`position_history_derivation_kernel.cu`) consume the same
|
||||||
|
// factored-action ints but need a higher-level helper that emits the
|
||||||
|
// signed *position* directly: -1 / 0 / +1.
|
||||||
|
//
|
||||||
|
// This header DOES NOT replace `trade_physics.cuh`'s primitives; it adds
|
||||||
|
// one helper on top of them so derivation kernels (which don't need the
|
||||||
|
// trade-physics machinery) avoid pulling that whole header. Both this
|
||||||
|
// header and `trade_physics.cuh` ultimately read the same DIR_* macros
|
||||||
|
// from `state_layout.cuh`, so the source-of-truth for the direction
|
||||||
|
// mapping (DIR_SHORT=0, DIR_HOLD=1, DIR_LONG=2, DIR_FLAT=3) is unchanged.
|
||||||
|
|
||||||
|
#ifndef ACTION_DECODING_HELPERS_CUH
|
||||||
|
#define ACTION_DECODING_HELPERS_CUH
|
||||||
|
|
||||||
|
#include "state_layout.cuh"
|
||||||
|
|
||||||
|
// Decode direction-axis index from a factored action int.
|
||||||
|
// Mirrors `trade_physics.cuh::decode_direction_4b` exactly — duplicated
|
||||||
|
// here as a stand-alone helper so derivation kernels can `#include` this
|
||||||
|
// header without dragging in `trade_physics.cuh`'s GPU-only Kelly-cap
|
||||||
|
// machinery. If `b1`, `b2`, `b3` are all 3 (production setup) the
|
||||||
|
// product is 27 and direction = action / 27 (matches the FlatAction
|
||||||
|
// encoding `3 * b1 * b2 * b3` used by `handle_capital_floor_breach`).
|
||||||
|
__device__ __forceinline__ int factored_action_to_dir_idx(
|
||||||
|
int action, int b1, int b2, int b3
|
||||||
|
) {
|
||||||
|
return action / (b1 * b2 * b3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map a factored action int to a signed position scalar tracking the
|
||||||
|
// state machine of (-1=Short, 0=Flat, +1=Long, prev=Hold). Reads the
|
||||||
|
// previous step's position and returns the new position.
|
||||||
|
//
|
||||||
|
// DIR_SHORT (0) → -1
|
||||||
|
// DIR_HOLD (1) → prev_position (no-op, keeps current state)
|
||||||
|
// DIR_LONG (2) → +1
|
||||||
|
// DIR_FLAT (3) → 0
|
||||||
|
//
|
||||||
|
// `prev_position` is the integer position (−1/0/+1) at bar t-1. At t=0
|
||||||
|
// the caller passes 0 (every window starts flat by convention; matches
|
||||||
|
// `experience_kernels.cu`'s init-portfolio-state convention).
|
||||||
|
__device__ __forceinline__ int factored_action_to_position(
|
||||||
|
int action, int b1, int b2, int b3, int prev_position
|
||||||
|
) {
|
||||||
|
const int dir = factored_action_to_dir_idx(action, b1, b2, b3);
|
||||||
|
if (dir == DIR_SHORT) return -1;
|
||||||
|
if (dir == DIR_LONG ) return 1;
|
||||||
|
if (dir == DIR_FLAT ) return 0;
|
||||||
|
/* DIR_HOLD or any out-of-band sentinel: keep prev. */
|
||||||
|
return prev_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ACTION_DECODING_HELPERS_CUH
|
||||||
@@ -1,12 +1,23 @@
|
|||||||
// crates/ml/src/cuda_pipeline/baseline_kernels.cu
|
// crates/ml/src/cuda_pipeline/baseline_kernels.cu
|
||||||
//
|
//
|
||||||
// SP15 Phase 1.4 (partial) — 4 constant-policy counterfactual baselines.
|
// SP15 Phase 1.4 + Wave 3a (2026-05-06) — 4 constant-policy counterfactual baselines.
|
||||||
// Per spec §6.4. Pure-CUDA kernels (no trunk-share). Each kernel reads
|
// Per spec §6.4. Pure-CUDA kernels (no trunk-share). Each kernel reads
|
||||||
// the price/spread/ofi arrays + a commission scalar, computes the
|
// the price/spread/ofi arrays + a commission scalar, computes the
|
||||||
// baseline's per-bar PnL stream from a constant or last-bar-return-based
|
// baseline's per-bar PnL stream from a constant or last-bar-return-based
|
||||||
// action policy, then runs a 2-pass shared-memory tree-reduce mean/std
|
// action policy, then runs a 2-pass shared-memory tree-reduce mean/std
|
||||||
// (no atomicAdd per `feedback_no_atomicadd`) and writes sharpe_net to
|
// (no atomicAdd per `feedback_no_atomicadd`) and writes per-window
|
||||||
// its allocated ISV slot.
|
// `[mean, std, raw_sharpe]` to a caller-provided output buffer (matches
|
||||||
|
// the 1.1.b `sharpe_per_bar_kernel` shape).
|
||||||
|
//
|
||||||
|
// Wave 3a contract change (2026-05-06): the prior ISV-scalar writes to
|
||||||
|
// slots 409/410/412/416 are removed entirely. Per-window output is the
|
||||||
|
// correct shape for `WindowMetrics` consumption; ISV scalars were spec
|
||||||
|
// scaffolding for a single-fold-aggregate version that 1.4.b's per-window
|
||||||
|
// contract supersedes. Migration is atomic per `feedback_no_partial_refactor`:
|
||||||
|
// the 4 oracle tests that previously read ISV slots are migrated in the
|
||||||
|
// same commit to read from the output buffer instead. Production callers
|
||||||
|
// (Wave 3b) will allocate an `[n_windows, 3]` MappedF32Buffer and invoke
|
||||||
|
// the launchers per-window (mirrors the 1.1.b sharpe_per_bar pattern).
|
||||||
//
|
//
|
||||||
// Single source file — multiple `extern "C" __global__` symbols share
|
// Single source file — multiple `extern "C" __global__` symbols share
|
||||||
// one cubin per the build.rs 1:1-source-to-cubin convention. The four
|
// one cubin per the build.rs 1:1-source-to-cubin convention. The four
|
||||||
@@ -14,12 +25,6 @@
|
|||||||
// different `get_function()` symbols, mirroring the multi-kernel-per-
|
// different `get_function()` symbols, mirroring the multi-kernel-per-
|
||||||
// file pattern from SP11 (`novelty_simhash_kernel.cu`).
|
// file pattern from SP11 (`novelty_simhash_kernel.cu`).
|
||||||
//
|
//
|
||||||
// ISV slots written (allocated in `sp15_isv_slots.rs`, spec §4.3):
|
|
||||||
// slot 409 — BASELINE_BUYHOLD_SHARPE_INDEX
|
|
||||||
// slot 410 — BASELINE_HOLD_ONLY_SHARPE_INDEX
|
|
||||||
// slot 412 — BASELINE_NAIVE_MOMENTUM_SHARPE_INDEX
|
|
||||||
// slot 416 — BASELINE_NAIVE_REVERSION_SHARPE_INDEX
|
|
||||||
//
|
|
||||||
// Trunk-shared baselines (random_dir_kelly slot 411, aux_only slot 413,
|
// Trunk-shared baselines (random_dir_kelly slot 411, aux_only slot 413,
|
||||||
// mag_quarter_fixed slot 414, trail_only slot 415) are out of scope
|
// mag_quarter_fixed slot 414, trail_only slot 415) are out of scope
|
||||||
// for this commit — they need partial-policy-forward access from the
|
// for this commit — they need partial-policy-forward access from the
|
||||||
@@ -38,26 +43,22 @@
|
|||||||
// the model-under-test (both pay full RT commission + entry/exit
|
// the model-under-test (both pay full RT commission + entry/exit
|
||||||
// spread, neither pays OFI impact for this baseline class).
|
// spread, neither pays OFI impact for this baseline class).
|
||||||
|
|
||||||
#define BUYHOLD_SHARPE_SLOT 409
|
|
||||||
#define HOLD_ONLY_SHARPE_SLOT 410
|
|
||||||
#define MOMENTUM_SHARPE_SLOT 412
|
|
||||||
#define REVERSION_SHARPE_SLOT 416
|
|
||||||
|
|
||||||
// Templated helper: 2-pass shared-memory tree-reduce of mean and std of
|
// Templated helper: 2-pass shared-memory tree-reduce of mean and std of
|
||||||
// the per-bar PnL stream produced by `ActionFn`. Returns sharpe to thread
|
// the per-bar PnL stream produced by `ActionFn`. Writes `[mean, std,
|
||||||
// 0 only — caller stores it to the appropriate ISV slot.
|
// raw_sharpe]` to `out[0..3]` from thread 0 only.
|
||||||
//
|
//
|
||||||
// Template parameter `ActionFn` is a CUDA functor:
|
// Template parameter `ActionFn` is a CUDA functor:
|
||||||
// __device__ int operator()(const float* prices, int i, int n) const;
|
// __device__ int operator()(const float* prices, int i, int n) const;
|
||||||
// returns +1 (long), 0 (flat/hold), -1 (short) for bar `i`.
|
// returns +1 (long), 0 (flat/hold), -1 (short) for bar `i`.
|
||||||
template <typename ActionFn>
|
template <typename ActionFn>
|
||||||
__device__ float compute_baseline_sharpe(
|
__device__ void compute_baseline_sharpe(
|
||||||
const float* __restrict__ prices,
|
const float* __restrict__ prices,
|
||||||
const float* __restrict__ half_spread,
|
const float* __restrict__ half_spread,
|
||||||
const float* __restrict__ /* ofi */, // unused for this baseline class (lambda=0)
|
const float* __restrict__ /* ofi */, // unused for this baseline class (lambda=0)
|
||||||
int n,
|
int n,
|
||||||
float commission_per_rt,
|
float commission_per_rt,
|
||||||
ActionFn action_fn
|
ActionFn action_fn,
|
||||||
|
float* out /* [mean, std, raw_sharpe] */
|
||||||
) {
|
) {
|
||||||
const int tid = (int)threadIdx.x;
|
const int tid = (int)threadIdx.x;
|
||||||
const int BLOCK = (int)blockDim.x;
|
const int BLOCK = (int)blockDim.x;
|
||||||
@@ -117,9 +118,12 @@ __device__ float compute_baseline_sharpe(
|
|||||||
if (tid == 0) {
|
if (tid == 0) {
|
||||||
const float var = (n > 1) ? reduce_buf[0] / (float)(n - 1) : 0.0f;
|
const float var = (n > 1) ? reduce_buf[0] / (float)(n - 1) : 0.0f;
|
||||||
const float std = sqrtf(fmaxf(var, 1e-12f));
|
const float std = sqrtf(fmaxf(var, 1e-12f));
|
||||||
return (std > 0.0f) ? mean_shared / std : 0.0f;
|
const float sharpe = (std > 0.0f) ? mean_shared / std : 0.0f;
|
||||||
|
out[0] = mean_shared;
|
||||||
|
out[1] = std;
|
||||||
|
out[2] = sharpe;
|
||||||
|
__threadfence_system();
|
||||||
}
|
}
|
||||||
return 0.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CUDA functor structs — one per baseline policy.
|
// CUDA functor structs — one per baseline policy.
|
||||||
@@ -158,16 +162,12 @@ extern "C" __global__ void baseline_buyhold_kernel(
|
|||||||
const float* __restrict__ ofi,
|
const float* __restrict__ ofi,
|
||||||
int n,
|
int n,
|
||||||
float commission_per_rt,
|
float commission_per_rt,
|
||||||
float* __restrict__ isv
|
float* __restrict__ out /* [mean, std, raw_sharpe] */
|
||||||
) {
|
) {
|
||||||
if (blockIdx.x != 0) return;
|
if (blockIdx.x != 0) return;
|
||||||
const float sharpe = compute_baseline_sharpe(
|
compute_baseline_sharpe(
|
||||||
prices, half_spread, ofi, n, commission_per_rt, BuyholdAction()
|
prices, half_spread, ofi, n, commission_per_rt, BuyholdAction(), out
|
||||||
);
|
);
|
||||||
if (threadIdx.x == 0) {
|
|
||||||
isv[BUYHOLD_SHARPE_SLOT] = sharpe;
|
|
||||||
__threadfence_system();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" __global__ void baseline_hold_only_kernel(
|
extern "C" __global__ void baseline_hold_only_kernel(
|
||||||
@@ -176,16 +176,12 @@ extern "C" __global__ void baseline_hold_only_kernel(
|
|||||||
const float* __restrict__ ofi,
|
const float* __restrict__ ofi,
|
||||||
int n,
|
int n,
|
||||||
float commission_per_rt,
|
float commission_per_rt,
|
||||||
float* __restrict__ isv
|
float* __restrict__ out /* [mean, std, raw_sharpe] */
|
||||||
) {
|
) {
|
||||||
if (blockIdx.x != 0) return;
|
if (blockIdx.x != 0) return;
|
||||||
const float sharpe = compute_baseline_sharpe(
|
compute_baseline_sharpe(
|
||||||
prices, half_spread, ofi, n, commission_per_rt, HoldOnlyAction()
|
prices, half_spread, ofi, n, commission_per_rt, HoldOnlyAction(), out
|
||||||
);
|
);
|
||||||
if (threadIdx.x == 0) {
|
|
||||||
isv[HOLD_ONLY_SHARPE_SLOT] = sharpe;
|
|
||||||
__threadfence_system();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" __global__ void baseline_naive_momentum_kernel(
|
extern "C" __global__ void baseline_naive_momentum_kernel(
|
||||||
@@ -194,16 +190,12 @@ extern "C" __global__ void baseline_naive_momentum_kernel(
|
|||||||
const float* __restrict__ ofi,
|
const float* __restrict__ ofi,
|
||||||
int n,
|
int n,
|
||||||
float commission_per_rt,
|
float commission_per_rt,
|
||||||
float* __restrict__ isv
|
float* __restrict__ out /* [mean, std, raw_sharpe] */
|
||||||
) {
|
) {
|
||||||
if (blockIdx.x != 0) return;
|
if (blockIdx.x != 0) return;
|
||||||
const float sharpe = compute_baseline_sharpe(
|
compute_baseline_sharpe(
|
||||||
prices, half_spread, ofi, n, commission_per_rt, MomentumAction()
|
prices, half_spread, ofi, n, commission_per_rt, MomentumAction(), out
|
||||||
);
|
);
|
||||||
if (threadIdx.x == 0) {
|
|
||||||
isv[MOMENTUM_SHARPE_SLOT] = sharpe;
|
|
||||||
__threadfence_system();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" __global__ void baseline_naive_reversion_kernel(
|
extern "C" __global__ void baseline_naive_reversion_kernel(
|
||||||
@@ -212,14 +204,10 @@ extern "C" __global__ void baseline_naive_reversion_kernel(
|
|||||||
const float* __restrict__ ofi,
|
const float* __restrict__ ofi,
|
||||||
int n,
|
int n,
|
||||||
float commission_per_rt,
|
float commission_per_rt,
|
||||||
float* __restrict__ isv
|
float* __restrict__ out /* [mean, std, raw_sharpe] */
|
||||||
) {
|
) {
|
||||||
if (blockIdx.x != 0) return;
|
if (blockIdx.x != 0) return;
|
||||||
const float sharpe = compute_baseline_sharpe(
|
compute_baseline_sharpe(
|
||||||
prices, half_spread, ofi, n, commission_per_rt, ReversionAction()
|
prices, half_spread, ofi, n, commission_per_rt, ReversionAction(), out
|
||||||
);
|
);
|
||||||
if (threadIdx.x == 0) {
|
|
||||||
isv[REVERSION_SHARPE_SLOT] = sharpe;
|
|
||||||
__threadfence_system();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -993,35 +993,48 @@ pub fn launch_sp15_dd_pct_concat(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SP15 Phase 1.4 partial (2026-05-06): cubin shared by all 4 constant-
|
/// SP15 Phase 1.4 + Wave 3a (2026-05-06): cubin shared by all 4 constant-
|
||||||
/// policy counterfactual baselines (`baseline_buyhold_kernel`,
|
/// policy counterfactual baselines (`baseline_buyhold_kernel`,
|
||||||
/// `baseline_hold_only_kernel`, `baseline_naive_momentum_kernel`,
|
/// `baseline_hold_only_kernel`, `baseline_naive_momentum_kernel`,
|
||||||
/// `baseline_naive_reversion_kernel`). Per spec §6.4. Each baseline
|
/// `baseline_naive_reversion_kernel`). Per spec §6.4. Each baseline
|
||||||
/// computes its sharpe_net by running a constant-policy or last-bar-
|
/// computes its per-window `[mean, std, raw_sharpe]` by running a
|
||||||
/// return-based action stream through a 2-pass shared-memory tree-reduce
|
/// constant-policy or last-bar-return-based action stream through a
|
||||||
/// (single-block BLOCK=256, no atomicAdd) and writing to its own ISV
|
/// 2-pass shared-memory tree-reduce (single-block BLOCK=256, no atomicAdd)
|
||||||
/// slot. The four launchers below all load this single cubin and resolve
|
/// and writing to a caller-provided output buffer. Wave 3a contract
|
||||||
/// different `get_function()` symbols — multi-kernel-per-file pattern
|
/// change: ISV-scalar writes to slots 409/410/412/416 are removed entirely
|
||||||
/// per `novelty_simhash_kernel.cu` precedent.
|
/// — per-window output is the correct shape for `WindowMetrics`
|
||||||
|
/// consumption, mirrors the 1.1.b sharpe_per_bar pattern. The four
|
||||||
|
/// launchers below all load this single cubin and resolve different
|
||||||
|
/// `get_function()` symbols — multi-kernel-per-file pattern per
|
||||||
|
/// `novelty_simhash_kernel.cu` precedent.
|
||||||
///
|
///
|
||||||
/// Trunk-shared baselines (random_dir_kelly slot 411, aux_only slot 413,
|
/// Trunk-shared baselines (random_dir_kelly slot 411, aux_only slot 413,
|
||||||
/// mag_quarter_fixed slot 414, trail_only slot 415) are out of scope for
|
/// mag_quarter_fixed slot 414, trail_only slot 415) are out of scope for
|
||||||
/// this commit — they need partial-policy-forward access from the main
|
/// this commit — they need partial-policy-forward access from the main
|
||||||
/// eval pass and are deferred to Task 1.4.b. Their slots stay at sentinel
|
/// eval pass and are deferred to Task 1.4.b. Their slots stay at sentinel
|
||||||
/// 0.0 in the meantime.
|
/// 0.0 in the meantime.
|
||||||
|
///
|
||||||
|
/// Wave 3a transient state (2026-05-06): the 4 baseline launchers below
|
||||||
|
/// are currently ORPHAN — production callers in `GpuBacktestEvaluator`
|
||||||
|
/// (`Wave 3b`) invoke them per-window once that constructor signature
|
||||||
|
/// migration lands. Oracle tests in `sp15_phase1_oracle_tests.rs` drive
|
||||||
|
/// the launchers directly. Acceptable per the documented 3a/3b split.
|
||||||
pub static SP15_BASELINE_KERNELS_CUBIN: &[u8] =
|
pub static SP15_BASELINE_KERNELS_CUBIN: &[u8] =
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/baseline_kernels.cubin"));
|
include_bytes!(concat!(env!("OUT_DIR"), "/baseline_kernels.cubin"));
|
||||||
|
|
||||||
/// SP15 Phase 1.4 partial (2026-05-06): launcher for the buyhold
|
/// SP15 Phase 1.4 + Wave 3a (2026-05-06): launcher for the buyhold
|
||||||
/// counterfactual baseline. Free function (matches `launch_sp15_dd_state`
|
/// counterfactual baseline. Free function (matches `launch_sp15_dd_state`
|
||||||
/// precedent) so unit/oracle tests can drive the kernel directly without
|
/// precedent) so unit/oracle tests can drive the kernel directly without
|
||||||
/// the trainer struct; production callers (eval-pass baseline launches)
|
/// the trainer struct; production callers (eval-pass baseline launches
|
||||||
/// invoke the same launcher.
|
/// in Wave 3b's `GpuBacktestEvaluator::new`) invoke the same launcher.
|
||||||
///
|
///
|
||||||
/// Policy: position=+1 every bar (no exits). Writes sharpe_net to
|
/// Policy: position=+1 every bar (no exits). Writes per-window
|
||||||
/// `ISV[BASELINE_BUYHOLD_SHARPE_INDEX=409]`. `prices`, `half_spread`,
|
/// `[mean, std, raw_sharpe]` to `out[0..3]`. `prices`, `half_spread`,
|
||||||
/// `ofi`, `isv` are device f32 pointers; `n` is bar count. `isv` MUST
|
/// `ofi`, `out` are device f32 pointers; `n` is bar count. `out` MUST
|
||||||
/// be the ISV bus (≥443 f32 slots).
|
/// point to ≥3 f32 slots — typically the `dev_ptr` of a `MappedF32Buffer`
|
||||||
|
/// of length `n_windows * 3`, with the launcher invoked sequentially per
|
||||||
|
/// window with the per-window slice (mirror of 1.1.b sharpe_per_bar
|
||||||
|
/// per-window launch sequence).
|
||||||
pub fn launch_sp15_baseline_buyhold(
|
pub fn launch_sp15_baseline_buyhold(
|
||||||
stream: &Arc<CudaStream>,
|
stream: &Arc<CudaStream>,
|
||||||
prices: cudarc::driver::sys::CUdeviceptr,
|
prices: cudarc::driver::sys::CUdeviceptr,
|
||||||
@@ -1029,7 +1042,7 @@ pub fn launch_sp15_baseline_buyhold(
|
|||||||
ofi: cudarc::driver::sys::CUdeviceptr,
|
ofi: cudarc::driver::sys::CUdeviceptr,
|
||||||
n: i32,
|
n: i32,
|
||||||
commission_per_rt: f32,
|
commission_per_rt: f32,
|
||||||
isv: cudarc::driver::sys::CUdeviceptr,
|
out: cudarc::driver::sys::CUdeviceptr,
|
||||||
) -> Result<(), MLError> {
|
) -> Result<(), MLError> {
|
||||||
let module = stream
|
let module = stream
|
||||||
.context()
|
.context()
|
||||||
@@ -1050,7 +1063,7 @@ pub fn launch_sp15_baseline_buyhold(
|
|||||||
.arg(&ofi)
|
.arg(&ofi)
|
||||||
.arg(&n)
|
.arg(&n)
|
||||||
.arg(&commission_per_rt)
|
.arg(&commission_per_rt)
|
||||||
.arg(&isv)
|
.arg(&out)
|
||||||
.launch(LaunchConfig {
|
.launch(LaunchConfig {
|
||||||
grid_dim: (1, 1, 1),
|
grid_dim: (1, 1, 1),
|
||||||
block_dim: (256, 1, 1),
|
block_dim: (256, 1, 1),
|
||||||
@@ -1063,12 +1076,12 @@ pub fn launch_sp15_baseline_buyhold(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SP15 Phase 1.4 partial (2026-05-06): launcher for the hold-only
|
/// SP15 Phase 1.4 + Wave 3a (2026-05-06): launcher for the hold-only
|
||||||
/// counterfactual baseline. Policy: action=Hold every bar (no entry, no
|
/// counterfactual baseline. Policy: action=Hold every bar (no entry, no
|
||||||
/// exit). Writes sharpe_net to `ISV[BASELINE_HOLD_ONLY_SHARPE_INDEX=410]`.
|
/// exit). Writes per-window `[mean, std, raw_sharpe]` to `out[0..3]`.
|
||||||
/// On a strict no-trade trajectory `mean(pnl)=0` and `std(pnl)=0` so
|
/// On a strict no-trade trajectory `mean(pnl)=0` and `std(pnl)=0` so
|
||||||
/// the kernel's `(std > 0) ? mean / std : 0` guard emits 0, which is
|
/// the kernel's `(std > 0) ? mean / std : 0` guard emits sharpe=0, which
|
||||||
/// the spec-mandated sentinel for an undefined sharpe.
|
/// is the spec-mandated sentinel for an undefined sharpe.
|
||||||
pub fn launch_sp15_baseline_hold_only(
|
pub fn launch_sp15_baseline_hold_only(
|
||||||
stream: &Arc<CudaStream>,
|
stream: &Arc<CudaStream>,
|
||||||
prices: cudarc::driver::sys::CUdeviceptr,
|
prices: cudarc::driver::sys::CUdeviceptr,
|
||||||
@@ -1076,7 +1089,7 @@ pub fn launch_sp15_baseline_hold_only(
|
|||||||
ofi: cudarc::driver::sys::CUdeviceptr,
|
ofi: cudarc::driver::sys::CUdeviceptr,
|
||||||
n: i32,
|
n: i32,
|
||||||
commission_per_rt: f32,
|
commission_per_rt: f32,
|
||||||
isv: cudarc::driver::sys::CUdeviceptr,
|
out: cudarc::driver::sys::CUdeviceptr,
|
||||||
) -> Result<(), MLError> {
|
) -> Result<(), MLError> {
|
||||||
let module = stream
|
let module = stream
|
||||||
.context()
|
.context()
|
||||||
@@ -1097,7 +1110,7 @@ pub fn launch_sp15_baseline_hold_only(
|
|||||||
.arg(&ofi)
|
.arg(&ofi)
|
||||||
.arg(&n)
|
.arg(&n)
|
||||||
.arg(&commission_per_rt)
|
.arg(&commission_per_rt)
|
||||||
.arg(&isv)
|
.arg(&out)
|
||||||
.launch(LaunchConfig {
|
.launch(LaunchConfig {
|
||||||
grid_dim: (1, 1, 1),
|
grid_dim: (1, 1, 1),
|
||||||
block_dim: (256, 1, 1),
|
block_dim: (256, 1, 1),
|
||||||
@@ -1110,10 +1123,10 @@ pub fn launch_sp15_baseline_hold_only(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SP15 Phase 1.4 partial (2026-05-06): launcher for the naive-momentum
|
/// SP15 Phase 1.4 + Wave 3a (2026-05-06): launcher for the naive-momentum
|
||||||
/// counterfactual baseline. Policy: `direction = sign(prices[i] −
|
/// counterfactual baseline. Policy: `direction = sign(prices[i] −
|
||||||
/// prices[i-1])` with |position|=1. Writes sharpe_net to
|
/// prices[i-1])` with |position|=1. Writes per-window `[mean, std,
|
||||||
/// `ISV[BASELINE_NAIVE_MOMENTUM_SHARPE_INDEX=412]`.
|
/// raw_sharpe]` to `out[0..3]`.
|
||||||
pub fn launch_sp15_baseline_naive_momentum(
|
pub fn launch_sp15_baseline_naive_momentum(
|
||||||
stream: &Arc<CudaStream>,
|
stream: &Arc<CudaStream>,
|
||||||
prices: cudarc::driver::sys::CUdeviceptr,
|
prices: cudarc::driver::sys::CUdeviceptr,
|
||||||
@@ -1121,7 +1134,7 @@ pub fn launch_sp15_baseline_naive_momentum(
|
|||||||
ofi: cudarc::driver::sys::CUdeviceptr,
|
ofi: cudarc::driver::sys::CUdeviceptr,
|
||||||
n: i32,
|
n: i32,
|
||||||
commission_per_rt: f32,
|
commission_per_rt: f32,
|
||||||
isv: cudarc::driver::sys::CUdeviceptr,
|
out: cudarc::driver::sys::CUdeviceptr,
|
||||||
) -> Result<(), MLError> {
|
) -> Result<(), MLError> {
|
||||||
let module = stream
|
let module = stream
|
||||||
.context()
|
.context()
|
||||||
@@ -1142,7 +1155,7 @@ pub fn launch_sp15_baseline_naive_momentum(
|
|||||||
.arg(&ofi)
|
.arg(&ofi)
|
||||||
.arg(&n)
|
.arg(&n)
|
||||||
.arg(&commission_per_rt)
|
.arg(&commission_per_rt)
|
||||||
.arg(&isv)
|
.arg(&out)
|
||||||
.launch(LaunchConfig {
|
.launch(LaunchConfig {
|
||||||
grid_dim: (1, 1, 1),
|
grid_dim: (1, 1, 1),
|
||||||
block_dim: (256, 1, 1),
|
block_dim: (256, 1, 1),
|
||||||
@@ -1155,10 +1168,10 @@ pub fn launch_sp15_baseline_naive_momentum(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SP15 Phase 1.4 partial (2026-05-06): launcher for the naive-reversion
|
/// SP15 Phase 1.4 + Wave 3a (2026-05-06): launcher for the naive-reversion
|
||||||
/// counterfactual baseline. Policy: `direction = -sign(prices[i] −
|
/// counterfactual baseline. Policy: `direction = -sign(prices[i] −
|
||||||
/// prices[i-1])` with |position|=1 (mirror of naive_momentum). Writes
|
/// prices[i-1])` with |position|=1 (mirror of naive_momentum). Writes
|
||||||
/// sharpe_net to `ISV[BASELINE_NAIVE_REVERSION_SHARPE_INDEX=416]`.
|
/// per-window `[mean, std, raw_sharpe]` to `out[0..3]`.
|
||||||
pub fn launch_sp15_baseline_naive_reversion(
|
pub fn launch_sp15_baseline_naive_reversion(
|
||||||
stream: &Arc<CudaStream>,
|
stream: &Arc<CudaStream>,
|
||||||
prices: cudarc::driver::sys::CUdeviceptr,
|
prices: cudarc::driver::sys::CUdeviceptr,
|
||||||
@@ -1166,7 +1179,7 @@ pub fn launch_sp15_baseline_naive_reversion(
|
|||||||
ofi: cudarc::driver::sys::CUdeviceptr,
|
ofi: cudarc::driver::sys::CUdeviceptr,
|
||||||
n: i32,
|
n: i32,
|
||||||
commission_per_rt: f32,
|
commission_per_rt: f32,
|
||||||
isv: cudarc::driver::sys::CUdeviceptr,
|
out: cudarc::driver::sys::CUdeviceptr,
|
||||||
) -> Result<(), MLError> {
|
) -> Result<(), MLError> {
|
||||||
let module = stream
|
let module = stream
|
||||||
.context()
|
.context()
|
||||||
@@ -1187,7 +1200,7 @@ pub fn launch_sp15_baseline_naive_reversion(
|
|||||||
.arg(&ofi)
|
.arg(&ofi)
|
||||||
.arg(&n)
|
.arg(&n)
|
||||||
.arg(&commission_per_rt)
|
.arg(&commission_per_rt)
|
||||||
.arg(&isv)
|
.arg(&out)
|
||||||
.launch(LaunchConfig {
|
.launch(LaunchConfig {
|
||||||
grid_dim: (1, 1, 1),
|
grid_dim: (1, 1, 1),
|
||||||
block_dim: (256, 1, 1),
|
block_dim: (256, 1, 1),
|
||||||
@@ -1200,6 +1213,91 @@ pub fn launch_sp15_baseline_naive_reversion(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SP15 Wave 3a (2026-05-06): cubin for the post-loop position-history
|
||||||
|
/// derivation kernel (`sp15_position_history_derivation_kernel`). Reads
|
||||||
|
/// `actions_history_buf` (factored ints recorded by env_step) and emits
|
||||||
|
/// per-bar `position_history` / `side_ind` / `rt_ind` f32 streams
|
||||||
|
/// consumed by Wave 3b's cost-net sharpe path. Uses the new
|
||||||
|
/// `action_decoding_helpers.cuh` single-source-of-truth helper for the
|
||||||
|
/// action→direction→position mapping (mirrors
|
||||||
|
/// `trade_physics.cuh::decode_direction_4b` semantics so on-policy and
|
||||||
|
/// counterfactual paths agree on what each factored action means).
|
||||||
|
///
|
||||||
|
/// Wave 3a transient state: the launcher below is currently ORPHAN — the
|
||||||
|
/// production caller in `GpuBacktestEvaluator` lands in Wave 3b alongside
|
||||||
|
/// the cost-net sharpe wire-up. Oracle tests in
|
||||||
|
/// `sp15_phase1_oracle_tests.rs` drive the launcher directly. Acceptable
|
||||||
|
/// per the documented 3a/3b split.
|
||||||
|
pub static SP15_POSITION_HISTORY_DERIVATION_CUBIN: &[u8] =
|
||||||
|
include_bytes!(concat!(env!("OUT_DIR"), "/position_history_derivation_kernel.cubin"));
|
||||||
|
|
||||||
|
/// SP15 Wave 3a (2026-05-06): launcher for the position-history derivation
|
||||||
|
/// kernel. Free function (matches `launch_sp15_dd_state` /
|
||||||
|
/// `launch_sp15_baseline_*` precedent) so unit/oracle tests can drive
|
||||||
|
/// the kernel directly without the trainer struct.
|
||||||
|
///
|
||||||
|
/// `actions_history_buf` is the device pointer to `[n_windows * max_len]`
|
||||||
|
/// i32 factored actions (recorded by `experience_env_step` /
|
||||||
|
/// `backtest_env_step`; sentinel -1 marks unwritten / early-terminated
|
||||||
|
/// bars and is treated as "keep prev position"). The 3 output f32 streams
|
||||||
|
/// are caller-provided device pointers, each `[n_windows * max_len]` long.
|
||||||
|
///
|
||||||
|
/// Grid: `[n_windows, 1, 1]` × block `[1, 1, 1]`. The position state
|
||||||
|
/// machine is sequential per window (position[t] depends on position[t-1]),
|
||||||
|
/// so within-window parallelism is zero by construction; the across-
|
||||||
|
/// windows axis carries all the parallelism. No atomicAdd per
|
||||||
|
/// `feedback_no_atomicadd` (the kernel is a pure scan, not a reduction).
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn launch_sp15_position_history_derivation(
|
||||||
|
stream: &Arc<CudaStream>,
|
||||||
|
actions_history_buf: cudarc::driver::sys::CUdeviceptr,
|
||||||
|
n_windows: i32,
|
||||||
|
max_len: i32,
|
||||||
|
b1_size: i32,
|
||||||
|
b2_size: i32,
|
||||||
|
b3_size: i32,
|
||||||
|
position_history_out: cudarc::driver::sys::CUdeviceptr,
|
||||||
|
side_ind_out: cudarc::driver::sys::CUdeviceptr,
|
||||||
|
rt_ind_out: cudarc::driver::sys::CUdeviceptr,
|
||||||
|
) -> Result<(), MLError> {
|
||||||
|
let module = stream
|
||||||
|
.context()
|
||||||
|
.load_cubin(SP15_POSITION_HISTORY_DERIVATION_CUBIN.to_vec())
|
||||||
|
.map_err(|e| MLError::ModelError(format!(
|
||||||
|
"load sp15_position_history_derivation cubin: {e}"
|
||||||
|
)))?;
|
||||||
|
let kernel = module
|
||||||
|
.load_function("sp15_position_history_derivation_kernel")
|
||||||
|
.map_err(|e| MLError::ModelError(format!(
|
||||||
|
"load sp15_position_history_derivation_kernel function: {e}"
|
||||||
|
)))?;
|
||||||
|
if n_windows <= 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
stream
|
||||||
|
.launch_builder(&kernel)
|
||||||
|
.arg(&actions_history_buf)
|
||||||
|
.arg(&n_windows)
|
||||||
|
.arg(&max_len)
|
||||||
|
.arg(&b1_size)
|
||||||
|
.arg(&b2_size)
|
||||||
|
.arg(&b3_size)
|
||||||
|
.arg(&position_history_out)
|
||||||
|
.arg(&side_ind_out)
|
||||||
|
.arg(&rt_ind_out)
|
||||||
|
.launch(LaunchConfig {
|
||||||
|
grid_dim: (n_windows as u32, 1, 1),
|
||||||
|
block_dim: (1, 1, 1),
|
||||||
|
shared_mem_bytes: 0,
|
||||||
|
})
|
||||||
|
.map_err(|e| MLError::ModelError(format!(
|
||||||
|
"launch sp15_position_history_derivation_kernel: {e}"
|
||||||
|
)))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// SP15 Wave 2 (2026-05-06): cubin for the per-step ISV-driven α
|
/// SP15 Wave 2 (2026-05-06): cubin for the per-step ISV-driven α
|
||||||
/// producer (`alpha_split_producer_kernel`). Replaces the pre-Wave-2
|
/// producer (`alpha_split_producer_kernel`). Replaces the pre-Wave-2
|
||||||
/// `SP15_R_QUALITY_DISCIPLINE_SPLIT_CUBIN` which housed both the
|
/// `SP15_R_QUALITY_DISCIPLINE_SPLIT_CUBIN` which housed both the
|
||||||
@@ -3222,10 +3320,9 @@ const fn layout_fingerprint_seed() -> &'static [u8] {
|
|||||||
EGF_SCHMITT_HI=397;EGF_SCHMITT_LO=398;EGF_VAR_AUX_REF=399;EGF_VAR_Q_REF=400;\
|
EGF_SCHMITT_HI=397;EGF_SCHMITT_LO=398;EGF_VAR_AUX_REF=399;EGF_VAR_Q_REF=400;\
|
||||||
DD_CURRENT=401;DD_MAX=402;DD_RECOVERY_BARS=403;DD_PERSISTENCE=404;CALMAR=405;DD_PCT=406;\
|
DD_CURRENT=401;DD_MAX=402;DD_RECOVERY_BARS=403;DD_PERSISTENCE=404;CALMAR=405;DD_PCT=406;\
|
||||||
OFI_IMPACT_LAMBDA=407;COST_PER_BAR_AVG=408;\
|
OFI_IMPACT_LAMBDA=407;COST_PER_BAR_AVG=408;\
|
||||||
BASELINE_BUYHOLD_SHARPE=409;BASELINE_HOLD_ONLY_SHARPE=410;\
|
BASELINE_RANDOM_DIR_KELLY_SHARPE=411;\
|
||||||
BASELINE_RANDOM_DIR_KELLY_SHARPE=411;BASELINE_NAIVE_MOMENTUM_SHARPE=412;\
|
|
||||||
BASELINE_AUX_ONLY_SHARPE=413;BASELINE_MAG_QUARTER_FIXED_SHARPE=414;\
|
BASELINE_AUX_ONLY_SHARPE=413;BASELINE_MAG_QUARTER_FIXED_SHARPE=414;\
|
||||||
BASELINE_TRAIL_ONLY_SHARPE=415;BASELINE_NAIVE_REVERSION_SHARPE=416;\
|
BASELINE_TRAIL_ONLY_SHARPE=415;\
|
||||||
ALPHA_SPLIT=417;GRAD_NORM_QUALITY=418;GRAD_NORM_DISCIPLINE=419;\
|
ALPHA_SPLIT=417;GRAD_NORM_QUALITY=418;GRAD_NORM_DISCIPLINE=419;\
|
||||||
LAMBDA_DD=420;DD_THRESHOLD=421;DD_PENALTY_GRAD_NORM=422;\
|
LAMBDA_DD=420;DD_THRESHOLD=421;DD_PENALTY_GRAD_NORM=422;\
|
||||||
REGRET_EMA=423;LAMBDA_REGRET=424;REGRET_GRAD_NORM=425;\
|
REGRET_EMA=423;LAMBDA_REGRET=424;REGRET_GRAD_NORM=425;\
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// crates/ml/src/cuda_pipeline/position_history_derivation_kernel.cu
|
||||||
|
//
|
||||||
|
// SP15 Wave 3a (2026-05-06) — post-loop derivation of per-bar position
|
||||||
|
// state from the recorded factored-action history.
|
||||||
|
//
|
||||||
|
// Reads `actions_history_buf[w * max_len + t]` (factored ints recorded
|
||||||
|
// by `experience_env_step` / `backtest_env_step`) and emits three f32
|
||||||
|
// streams per (window, bar) consumed by Wave 3b's cost-net sharpe path:
|
||||||
|
//
|
||||||
|
// position_history_out[w * max_len + t] ∈ {-1.0, 0.0, +1.0}
|
||||||
|
// side_ind_out[w * max_len + t] 1.0 when position changed
|
||||||
|
// from prev step, else 0.0
|
||||||
|
// rt_ind_out[w * max_len + t] 1.0 when transitioning to
|
||||||
|
// flat from a non-flat
|
||||||
|
// position (round-trip
|
||||||
|
// close), else 0.0
|
||||||
|
//
|
||||||
|
// Single-block-per-window launch with one thread per window. Each thread
|
||||||
|
// walks bars sequentially because position(t) depends on position(t-1)
|
||||||
|
// — there's no parallelism within a window for this state machine. The
|
||||||
|
// per-window walks ARE parallel across windows (n_windows blocks).
|
||||||
|
// No atomicAdd per `feedback_no_atomicadd` (the kernel doesn't reduce;
|
||||||
|
// it's a pure scan).
|
||||||
|
//
|
||||||
|
// Sentinel handling: `actions_history_buf` is initialised to -1 by
|
||||||
|
// `experience_kernels.cu` (per the actions_history measurement-bug fix
|
||||||
|
// from a prior commit). Bars where `action == -1` (never written by
|
||||||
|
// env_step — typically because the window terminated early) are treated
|
||||||
|
// as "no position change" — we keep the prior position. For windows
|
||||||
|
// shorter than `max_len`, the trailing -1 sentinels emit position =
|
||||||
|
// last-known-position with side_ind=0, rt_ind=0; downstream consumers
|
||||||
|
// (Wave 3b) gate on `window_lens[w]` to suppress these tail bars.
|
||||||
|
|
||||||
|
#include "action_decoding_helpers.cuh"
|
||||||
|
|
||||||
|
extern "C" __global__ void sp15_position_history_derivation_kernel(
|
||||||
|
const int* __restrict__ actions_history_buf, /* [n_windows * max_len] */
|
||||||
|
int n_windows,
|
||||||
|
int max_len,
|
||||||
|
int b1_size,
|
||||||
|
int b2_size,
|
||||||
|
int b3_size,
|
||||||
|
float* __restrict__ position_history_out, /* [n_windows * max_len] */
|
||||||
|
float* __restrict__ side_ind_out, /* [n_windows * max_len] */
|
||||||
|
float* __restrict__ rt_ind_out /* [n_windows * max_len] */
|
||||||
|
) {
|
||||||
|
const int w = (int)blockIdx.x;
|
||||||
|
if (w >= n_windows) return;
|
||||||
|
if (threadIdx.x != 0) return; /* one thread per block per window */
|
||||||
|
|
||||||
|
int prev_position = 0; /* every window starts flat by convention */
|
||||||
|
for (int t = 0; t < max_len; ++t) {
|
||||||
|
const int idx = w * max_len + t;
|
||||||
|
const int action = actions_history_buf[idx];
|
||||||
|
|
||||||
|
int new_position;
|
||||||
|
if (action < 0) {
|
||||||
|
/* sentinel — keep prior position, emit no-change indicators */
|
||||||
|
new_position = prev_position;
|
||||||
|
} else {
|
||||||
|
new_position = factored_action_to_position(
|
||||||
|
action, b1_size, b2_size, b3_size, prev_position
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int changed = (new_position != prev_position) ? 1 : 0;
|
||||||
|
const int round_trip = (changed && new_position == 0 && prev_position != 0) ? 1 : 0;
|
||||||
|
|
||||||
|
position_history_out[idx] = (float)new_position;
|
||||||
|
side_ind_out[idx] = (float)changed;
|
||||||
|
rt_ind_out[idx] = (float)round_trip;
|
||||||
|
|
||||||
|
prev_position = new_position;
|
||||||
|
}
|
||||||
|
__threadfence_system();
|
||||||
|
}
|
||||||
@@ -24,15 +24,29 @@ pub const DD_PCT_INDEX: usize = 406;
|
|||||||
pub const OFI_IMPACT_LAMBDA_INDEX: usize = 407;
|
pub const OFI_IMPACT_LAMBDA_INDEX: usize = 407;
|
||||||
pub const COST_PER_BAR_AVG_INDEX: usize = 408;
|
pub const COST_PER_BAR_AVG_INDEX: usize = 408;
|
||||||
|
|
||||||
// === Phase 1.4: 8 counterfactual baselines (8 slots) ===
|
// === Phase 1.4 + Wave 3a: 8 counterfactual baseline slots (4 retired, 4 reserved) ===
|
||||||
pub const BASELINE_BUYHOLD_SHARPE_INDEX: usize = 409;
|
//
|
||||||
pub const BASELINE_HOLD_ONLY_SHARPE_INDEX: usize = 410;
|
// Wave 3a (2026-05-06): the 4 constant-policy baselines (buyhold,
|
||||||
|
// hold_only, naive_momentum, naive_reversion) had their own ISV slots
|
||||||
|
// (409, 410, 412, 416) for a single-fold-aggregate sharpe scalar. Wave 3a
|
||||||
|
// migrates those baselines to per-window output buffers (matching the
|
||||||
|
// 1.1.b sharpe_per_bar shape) — the kernels now write `[mean, std,
|
||||||
|
// raw_sharpe]` per window to a caller-provided `MappedF32Buffer`. The
|
||||||
|
// 4 ISV slot constants are retired here per `feedback_no_legacy_aliases`
|
||||||
|
// (no ghost constants left in the codebase). The slot indices 409/410/
|
||||||
|
// 412/416 themselves remain reserved (gap in the SP15 layout) per the
|
||||||
|
// layout fingerprint contract — re-allocation is left to a future SP
|
||||||
|
// to keep the fingerprint stable across this transitional release.
|
||||||
|
//
|
||||||
|
// Trunk-shared baseline slots (411, 413, 414, 415) remain allocated:
|
||||||
|
// random_dir_kelly, aux_only, mag_quarter_fixed, trail_only need
|
||||||
|
// partial-policy-forward access from the main eval pass and stay on
|
||||||
|
// the ISV-scalar path until follow-up Task 1.4.b. They sit at sentinel
|
||||||
|
// 0.0 in the meantime.
|
||||||
pub const BASELINE_RANDOM_DIR_KELLY_SHARPE_INDEX: usize = 411;
|
pub const BASELINE_RANDOM_DIR_KELLY_SHARPE_INDEX: usize = 411;
|
||||||
pub const BASELINE_NAIVE_MOMENTUM_SHARPE_INDEX: usize = 412;
|
|
||||||
pub const BASELINE_AUX_ONLY_SHARPE_INDEX: usize = 413;
|
pub const BASELINE_AUX_ONLY_SHARPE_INDEX: usize = 413;
|
||||||
pub const BASELINE_MAG_QUARTER_FIXED_SHARPE_INDEX: usize = 414;
|
pub const BASELINE_MAG_QUARTER_FIXED_SHARPE_INDEX: usize = 414;
|
||||||
pub const BASELINE_TRAIL_ONLY_SHARPE_INDEX: usize = 415;
|
pub const BASELINE_TRAIL_ONLY_SHARPE_INDEX: usize = 415;
|
||||||
pub const BASELINE_NAIVE_REVERSION_SHARPE_INDEX: usize = 416;
|
|
||||||
|
|
||||||
// === Phase 3.1: r_quality + r_discipline split (3 slots) ===
|
// === Phase 3.1: r_quality + r_discipline split (3 slots) ===
|
||||||
pub const ALPHA_SPLIT_INDEX: usize = 417;
|
pub const ALPHA_SPLIT_INDEX: usize = 417;
|
||||||
@@ -108,8 +122,15 @@ mod tests {
|
|||||||
assert_eq!(DD_CURRENT_INDEX, 401);
|
assert_eq!(DD_CURRENT_INDEX, 401);
|
||||||
assert_eq!(DD_PCT_INDEX, 406);
|
assert_eq!(DD_PCT_INDEX, 406);
|
||||||
assert_eq!(OFI_IMPACT_LAMBDA_INDEX, 407);
|
assert_eq!(OFI_IMPACT_LAMBDA_INDEX, 407);
|
||||||
assert_eq!(BASELINE_BUYHOLD_SHARPE_INDEX, 409);
|
// Wave 3a (2026-05-06): BASELINE_BUYHOLD_SHARPE_INDEX (409) +
|
||||||
assert_eq!(BASELINE_NAIVE_REVERSION_SHARPE_INDEX, 416);
|
// BASELINE_HOLD_ONLY_SHARPE_INDEX (410) + BASELINE_NAIVE_MOMENTUM_SHARPE_INDEX
|
||||||
|
// (412) + BASELINE_NAIVE_REVERSION_SHARPE_INDEX (416) retired
|
||||||
|
// alongside the kernel contract change to per-window output buffers
|
||||||
|
// — slot indices remain reserved gaps for layout fingerprint stability.
|
||||||
|
assert_eq!(BASELINE_RANDOM_DIR_KELLY_SHARPE_INDEX, 411);
|
||||||
|
assert_eq!(BASELINE_AUX_ONLY_SHARPE_INDEX, 413);
|
||||||
|
assert_eq!(BASELINE_MAG_QUARTER_FIXED_SHARPE_INDEX, 414);
|
||||||
|
assert_eq!(BASELINE_TRAIL_ONLY_SHARPE_INDEX, 415);
|
||||||
assert_eq!(ALPHA_SPLIT_INDEX, 417);
|
assert_eq!(ALPHA_SPLIT_INDEX, 417);
|
||||||
assert_eq!(LAMBDA_DD_INDEX, 420);
|
assert_eq!(LAMBDA_DD_INDEX, 420);
|
||||||
assert_eq!(REGRET_EMA_INDEX, 423);
|
assert_eq!(REGRET_EMA_INDEX, 423);
|
||||||
|
|||||||
@@ -13,15 +13,13 @@ mod gpu {
|
|||||||
use ml::cuda_pipeline::gpu_dqn_trainer::{
|
use ml::cuda_pipeline::gpu_dqn_trainer::{
|
||||||
launch_sp15_baseline_buyhold, launch_sp15_baseline_hold_only,
|
launch_sp15_baseline_buyhold, launch_sp15_baseline_hold_only,
|
||||||
launch_sp15_baseline_naive_momentum, launch_sp15_baseline_naive_reversion,
|
launch_sp15_baseline_naive_momentum, launch_sp15_baseline_naive_reversion,
|
||||||
launch_sp15_cost_net_sharpe, launch_sp15_dd_state, launch_sp15_sharpe_per_bar,
|
launch_sp15_cost_net_sharpe, launch_sp15_dd_state,
|
||||||
|
launch_sp15_position_history_derivation, launch_sp15_sharpe_per_bar,
|
||||||
};
|
};
|
||||||
use ml::cuda_pipeline::mapped_pinned::{MappedF32Buffer, MappedU32Buffer};
|
use ml::cuda_pipeline::mapped_pinned::{MappedF32Buffer, MappedI32Buffer, MappedU32Buffer};
|
||||||
use ml::cuda_pipeline::sp15_isv_slots::{
|
use ml::cuda_pipeline::sp15_isv_slots::{
|
||||||
ALPHA_SPLIT_INDEX, BASELINE_BUYHOLD_SHARPE_INDEX, BASELINE_HOLD_ONLY_SHARPE_INDEX,
|
ALPHA_SPLIT_INDEX, COST_PER_BAR_AVG_INDEX, DD_CURRENT_INDEX, DD_MAX_INDEX, DD_PCT_INDEX,
|
||||||
BASELINE_NAIVE_MOMENTUM_SHARPE_INDEX, BASELINE_NAIVE_REVERSION_SHARPE_INDEX,
|
DD_RECOVERY_BARS_INDEX, OFI_IMPACT_LAMBDA_INDEX, SP15_SLOT_END,
|
||||||
COST_PER_BAR_AVG_INDEX, DD_CURRENT_INDEX, DD_MAX_INDEX, DD_PCT_INDEX,
|
|
||||||
DD_RECOVERY_BARS_INDEX, GRAD_NORM_DISCIPLINE_INDEX, GRAD_NORM_QUALITY_INDEX,
|
|
||||||
OFI_IMPACT_LAMBDA_INDEX, SP15_SLOT_END,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Upper bound for any ISV index touched by SP15 oracle tests. Matches
|
/// Upper bound for any ISV index touched by SP15 oracle tests. Matches
|
||||||
@@ -337,6 +335,10 @@ mod gpu {
|
|||||||
/// rules out a sharpe-near-zero degenerate baseline; upper 1.0
|
/// rules out a sharpe-near-zero degenerate baseline; upper 1.0
|
||||||
/// catches accidental zero-cost or reduction bugs that would
|
/// catches accidental zero-cost or reduction bugs that would
|
||||||
/// inflate the sharpe.
|
/// inflate the sharpe.
|
||||||
|
///
|
||||||
|
/// Wave 3a (2026-05-06): assertion reads from caller-provided output
|
||||||
|
/// buffer `[mean, std, raw_sharpe]` instead of ISV slot 409 (kernel
|
||||||
|
/// signature change).
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "requires GPU"]
|
#[ignore = "requires GPU"]
|
||||||
fn baseline_buyhold_positive_on_drift() {
|
fn baseline_buyhold_positive_on_drift() {
|
||||||
@@ -365,11 +367,9 @@ mod gpu {
|
|||||||
.expect("alloc MappedF32Buffer for ofi");
|
.expect("alloc MappedF32Buffer for ofi");
|
||||||
ofi_buf.write_from_slice(&ofi);
|
ofi_buf.write_from_slice(&ofi);
|
||||||
|
|
||||||
let isv_buf = unsafe { MappedF32Buffer::new(ISV_LEN) }
|
let out_buf = unsafe { MappedF32Buffer::new(3) }
|
||||||
.expect("alloc MappedF32Buffer for isv bus");
|
.expect("alloc MappedF32Buffer for baseline output [mean, std, raw_sharpe]");
|
||||||
let mut isv_init = vec![0.0f32; ISV_LEN];
|
out_buf.write_from_slice(&[0.0f32; 3]);
|
||||||
isv_init[OFI_IMPACT_LAMBDA_INDEX] = 0.0;
|
|
||||||
isv_buf.write_from_slice(&isv_init);
|
|
||||||
|
|
||||||
launch_sp15_baseline_buyhold(
|
launch_sp15_baseline_buyhold(
|
||||||
&stream,
|
&stream,
|
||||||
@@ -378,24 +378,24 @@ mod gpu {
|
|||||||
ofi_buf.dev_ptr,
|
ofi_buf.dev_ptr,
|
||||||
n as i32,
|
n as i32,
|
||||||
/* commission_per_rt = */ 0.0,
|
/* commission_per_rt = */ 0.0,
|
||||||
isv_buf.dev_ptr,
|
out_buf.dev_ptr,
|
||||||
)
|
)
|
||||||
.expect("launch baseline_buyhold_kernel");
|
.expect("launch baseline_buyhold_kernel");
|
||||||
stream
|
stream
|
||||||
.synchronize()
|
.synchronize()
|
||||||
.expect("synchronize after baseline_buyhold_kernel launch");
|
.expect("synchronize after baseline_buyhold_kernel launch");
|
||||||
|
|
||||||
let isv = isv_buf.read_all();
|
let out = out_buf.read_all();
|
||||||
let sharpe = isv[BASELINE_BUYHOLD_SHARPE_INDEX];
|
let sharpe = out[2];
|
||||||
assert!(
|
assert!(
|
||||||
sharpe > 0.2,
|
sharpe > 0.2,
|
||||||
"buyhold sharpe = {}, expected > 0.2 on +drift series",
|
"buyhold sharpe = {}, expected > 0.2 on +drift series (mean={}, std={})",
|
||||||
sharpe
|
sharpe, out[0], out[1]
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
sharpe < 1.0,
|
sharpe < 1.0,
|
||||||
"buyhold sharpe = {}, expected < 1.0 (sanity bound)",
|
"buyhold sharpe = {}, expected < 1.0 (sanity bound; mean={}, std={})",
|
||||||
sharpe
|
sharpe, out[0], out[1]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,6 +405,9 @@ mod gpu {
|
|||||||
/// zero on every bar. The kernel's reduction yields mean=0 std=0
|
/// zero on every bar. The kernel's reduction yields mean=0 std=0
|
||||||
/// and the `(std > 0) ? mean / std : 0` guard returns 0, the
|
/// and the `(std > 0) ? mean / std : 0` guard returns 0, the
|
||||||
/// spec-mandated sentinel for an undefined sharpe (no trades).
|
/// spec-mandated sentinel for an undefined sharpe (no trades).
|
||||||
|
///
|
||||||
|
/// Wave 3a (2026-05-06): assertion reads from caller-provided output
|
||||||
|
/// buffer `[mean, std, raw_sharpe]` instead of ISV slot 410.
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "requires GPU"]
|
#[ignore = "requires GPU"]
|
||||||
fn baseline_hold_only_emits_zero() {
|
fn baseline_hold_only_emits_zero() {
|
||||||
@@ -424,9 +427,9 @@ mod gpu {
|
|||||||
let ofi_buf = unsafe { MappedF32Buffer::new(n) }
|
let ofi_buf = unsafe { MappedF32Buffer::new(n) }
|
||||||
.expect("alloc MappedF32Buffer for ofi");
|
.expect("alloc MappedF32Buffer for ofi");
|
||||||
ofi_buf.write_from_slice(&ofi);
|
ofi_buf.write_from_slice(&ofi);
|
||||||
let isv_buf = unsafe { MappedF32Buffer::new(ISV_LEN) }
|
let out_buf = unsafe { MappedF32Buffer::new(3) }
|
||||||
.expect("alloc MappedF32Buffer for isv bus");
|
.expect("alloc MappedF32Buffer for baseline output");
|
||||||
isv_buf.write_from_slice(&vec![0.0f32; ISV_LEN]);
|
out_buf.write_from_slice(&[0.0f32; 3]);
|
||||||
|
|
||||||
launch_sp15_baseline_hold_only(
|
launch_sp15_baseline_hold_only(
|
||||||
&stream,
|
&stream,
|
||||||
@@ -435,19 +438,26 @@ mod gpu {
|
|||||||
ofi_buf.dev_ptr,
|
ofi_buf.dev_ptr,
|
||||||
n as i32,
|
n as i32,
|
||||||
/* commission_per_rt = */ 0.0,
|
/* commission_per_rt = */ 0.0,
|
||||||
isv_buf.dev_ptr,
|
out_buf.dev_ptr,
|
||||||
)
|
)
|
||||||
.expect("launch baseline_hold_only_kernel");
|
.expect("launch baseline_hold_only_kernel");
|
||||||
stream
|
stream
|
||||||
.synchronize()
|
.synchronize()
|
||||||
.expect("synchronize after baseline_hold_only_kernel launch");
|
.expect("synchronize after baseline_hold_only_kernel launch");
|
||||||
|
|
||||||
let isv = isv_buf.read_all();
|
let out = out_buf.read_all();
|
||||||
let sharpe = isv[BASELINE_HOLD_ONLY_SHARPE_INDEX];
|
let sharpe = out[2];
|
||||||
assert!(
|
assert!(
|
||||||
sharpe.abs() < 1e-5,
|
sharpe.abs() < 1e-5,
|
||||||
"hold_only sharpe = {}, expected ~0 (no trades)",
|
"hold_only sharpe = {}, expected ~0 (no trades; mean={}, std={})",
|
||||||
sharpe
|
sharpe, out[0], out[1]
|
||||||
|
);
|
||||||
|
// Also verify mean/std are themselves zero — hold_only's per-bar
|
||||||
|
// PnL stream is identically zero, not just zero-mean noise.
|
||||||
|
assert!(
|
||||||
|
out[0].abs() < 1e-5,
|
||||||
|
"hold_only mean = {}, expected ~0 (no trades)",
|
||||||
|
out[0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,6 +466,9 @@ mod gpu {
|
|||||||
/// sharpe values should sum to ~0. The series is deliberately
|
/// sharpe values should sum to ~0. The series is deliberately
|
||||||
/// mean-reverting (reversion outperforms) but the symmetry test is
|
/// mean-reverting (reversion outperforms) but the symmetry test is
|
||||||
/// invariant to which side wins.
|
/// invariant to which side wins.
|
||||||
|
///
|
||||||
|
/// Wave 3a (2026-05-06): each launcher writes to its own output
|
||||||
|
/// buffer `[mean, std, raw_sharpe]` (was ISV slots 412/416).
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "requires GPU"]
|
#[ignore = "requires GPU"]
|
||||||
fn baseline_momentum_reversion_symmetry() {
|
fn baseline_momentum_reversion_symmetry() {
|
||||||
@@ -486,9 +499,12 @@ mod gpu {
|
|||||||
.expect("alloc MappedF32Buffer for ofi");
|
.expect("alloc MappedF32Buffer for ofi");
|
||||||
ofi_buf.write_from_slice(&ofi);
|
ofi_buf.write_from_slice(&ofi);
|
||||||
|
|
||||||
let isv_buf = unsafe { MappedF32Buffer::new(ISV_LEN) }
|
let momentum_out = unsafe { MappedF32Buffer::new(3) }
|
||||||
.expect("alloc MappedF32Buffer for isv bus");
|
.expect("alloc MappedF32Buffer for momentum output");
|
||||||
isv_buf.write_from_slice(&vec![0.0f32; ISV_LEN]);
|
momentum_out.write_from_slice(&[0.0f32; 3]);
|
||||||
|
let reversion_out = unsafe { MappedF32Buffer::new(3) }
|
||||||
|
.expect("alloc MappedF32Buffer for reversion output");
|
||||||
|
reversion_out.write_from_slice(&[0.0f32; 3]);
|
||||||
|
|
||||||
launch_sp15_baseline_naive_momentum(
|
launch_sp15_baseline_naive_momentum(
|
||||||
&stream,
|
&stream,
|
||||||
@@ -497,13 +513,9 @@ mod gpu {
|
|||||||
ofi_buf.dev_ptr,
|
ofi_buf.dev_ptr,
|
||||||
n as i32,
|
n as i32,
|
||||||
0.0,
|
0.0,
|
||||||
isv_buf.dev_ptr,
|
momentum_out.dev_ptr,
|
||||||
)
|
)
|
||||||
.expect("launch baseline_naive_momentum_kernel");
|
.expect("launch baseline_naive_momentum_kernel");
|
||||||
stream
|
|
||||||
.synchronize()
|
|
||||||
.expect("synchronize after baseline_naive_momentum_kernel launch");
|
|
||||||
|
|
||||||
launch_sp15_baseline_naive_reversion(
|
launch_sp15_baseline_naive_reversion(
|
||||||
&stream,
|
&stream,
|
||||||
prices_buf.dev_ptr,
|
prices_buf.dev_ptr,
|
||||||
@@ -511,16 +523,15 @@ mod gpu {
|
|||||||
ofi_buf.dev_ptr,
|
ofi_buf.dev_ptr,
|
||||||
n as i32,
|
n as i32,
|
||||||
0.0,
|
0.0,
|
||||||
isv_buf.dev_ptr,
|
reversion_out.dev_ptr,
|
||||||
)
|
)
|
||||||
.expect("launch baseline_naive_reversion_kernel");
|
.expect("launch baseline_naive_reversion_kernel");
|
||||||
stream
|
stream
|
||||||
.synchronize()
|
.synchronize()
|
||||||
.expect("synchronize after baseline_naive_reversion_kernel launch");
|
.expect("synchronize after baseline_naive_{momentum,reversion}_kernel launches");
|
||||||
|
|
||||||
let isv = isv_buf.read_all();
|
let momentum_sharpe = momentum_out.read_all()[2];
|
||||||
let momentum_sharpe = isv[BASELINE_NAIVE_MOMENTUM_SHARPE_INDEX];
|
let reversion_sharpe = reversion_out.read_all()[2];
|
||||||
let reversion_sharpe = isv[BASELINE_NAIVE_REVERSION_SHARPE_INDEX];
|
|
||||||
|
|
||||||
// Sign-flipped policies: their sum should be near zero (anti-
|
// Sign-flipped policies: their sum should be near zero (anti-
|
||||||
// correlated picks on the same per-bar return stream). Bound 0.5
|
// correlated picks on the same per-bar return stream). Bound 0.5
|
||||||
@@ -535,6 +546,99 @@ mod gpu {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test 1.4.j (Wave 3a, 2026-05-06) — position_history derivation kernel
|
||||||
|
/// reconstructs per-bar position/side_ind/rt_ind from a known factored
|
||||||
|
/// action sequence.
|
||||||
|
///
|
||||||
|
/// Fixture: 1 window, 8 bars, b1=b2=b3=3 (production sizes). The
|
||||||
|
/// factored-action encoding is `dir * 27 + mag * 9 + order * 3 + urg`.
|
||||||
|
/// Per `state_layout.cuh`: DIR_SHORT=0, DIR_HOLD=1, DIR_LONG=2,
|
||||||
|
/// DIR_FLAT=3. So:
|
||||||
|
/// action= 0 → DIR_SHORT (position −1)
|
||||||
|
/// action= 27 → DIR_HOLD (keep prev)
|
||||||
|
/// action= 54 → DIR_LONG (position +1)
|
||||||
|
/// action= 81 → DIR_FLAT (position 0)
|
||||||
|
///
|
||||||
|
/// The action sequence below exercises every transition: Short→Hold→
|
||||||
|
/// Long→Hold→Flat→Long→Flat→Short. Expected per-bar derived state:
|
||||||
|
/// position : [-1, -1, +1, +1, 0, +1, 0, -1]
|
||||||
|
/// side_ind : [ 1, 0, 1, 0, 1, 1, 1, 1] /* changed-from-prev */
|
||||||
|
/// rt_ind : [ 0, 0, 0, 0, 1, 0, 1, 0] /* transitioned to flat */
|
||||||
|
/// (initial prev_position=0 — every window starts flat by convention.)
|
||||||
|
#[test]
|
||||||
|
#[ignore = "requires GPU"]
|
||||||
|
fn position_history_derivation_walks_action_sequence() {
|
||||||
|
let stream = make_test_stream();
|
||||||
|
|
||||||
|
const N_WINDOWS: usize = 1;
|
||||||
|
const MAX_LEN: usize = 8;
|
||||||
|
const B1: i32 = 3;
|
||||||
|
const B2: i32 = 3;
|
||||||
|
const B3: i32 = 3;
|
||||||
|
|
||||||
|
// Action sequence: Short, Hold, Long, Hold, Flat, Long, Flat, Short.
|
||||||
|
// dir codes: 0, 1, 2, 1, 3, 2, 3, 0 → action = dir * 27.
|
||||||
|
let actions: [i32; MAX_LEN] = [0, 27, 54, 27, 81, 54, 81, 0];
|
||||||
|
|
||||||
|
// Safety: CUDA context active via `make_test_stream` above.
|
||||||
|
let actions_buf = unsafe { MappedI32Buffer::new(N_WINDOWS * MAX_LEN) }
|
||||||
|
.expect("alloc MappedI32Buffer for actions_history");
|
||||||
|
actions_buf.write_from_slice(&actions);
|
||||||
|
|
||||||
|
let position_buf = unsafe { MappedF32Buffer::new(N_WINDOWS * MAX_LEN) }
|
||||||
|
.expect("alloc MappedF32Buffer for position_history");
|
||||||
|
position_buf.write_from_slice(&vec![0.0f32; N_WINDOWS * MAX_LEN]);
|
||||||
|
let side_ind_buf = unsafe { MappedF32Buffer::new(N_WINDOWS * MAX_LEN) }
|
||||||
|
.expect("alloc MappedF32Buffer for side_ind");
|
||||||
|
side_ind_buf.write_from_slice(&vec![0.0f32; N_WINDOWS * MAX_LEN]);
|
||||||
|
let rt_ind_buf = unsafe { MappedF32Buffer::new(N_WINDOWS * MAX_LEN) }
|
||||||
|
.expect("alloc MappedF32Buffer for rt_ind");
|
||||||
|
rt_ind_buf.write_from_slice(&vec![0.0f32; N_WINDOWS * MAX_LEN]);
|
||||||
|
|
||||||
|
launch_sp15_position_history_derivation(
|
||||||
|
&stream,
|
||||||
|
actions_buf.dev_ptr,
|
||||||
|
N_WINDOWS as i32,
|
||||||
|
MAX_LEN as i32,
|
||||||
|
B1,
|
||||||
|
B2,
|
||||||
|
B3,
|
||||||
|
position_buf.dev_ptr,
|
||||||
|
side_ind_buf.dev_ptr,
|
||||||
|
rt_ind_buf.dev_ptr,
|
||||||
|
)
|
||||||
|
.expect("launch sp15_position_history_derivation_kernel");
|
||||||
|
stream
|
||||||
|
.synchronize()
|
||||||
|
.expect("synchronize after position_history_derivation launch");
|
||||||
|
|
||||||
|
let position = position_buf.read_all();
|
||||||
|
let side_ind = side_ind_buf.read_all();
|
||||||
|
let rt_ind = rt_ind_buf.read_all();
|
||||||
|
|
||||||
|
let expected_position = [-1.0_f32, -1.0, 1.0, 1.0, 0.0, 1.0, 0.0, -1.0];
|
||||||
|
let expected_side_ind = [1.0_f32, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0];
|
||||||
|
let expected_rt_ind = [0.0_f32, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0];
|
||||||
|
|
||||||
|
for t in 0..MAX_LEN {
|
||||||
|
assert!(
|
||||||
|
(position[t] - expected_position[t]).abs() < 1e-5,
|
||||||
|
"position[{}] = {}, expected {}",
|
||||||
|
t, position[t], expected_position[t]
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
(side_ind[t] - expected_side_ind[t]).abs() < 1e-5,
|
||||||
|
"side_ind[{}] = {}, expected {}",
|
||||||
|
t, side_ind[t], expected_side_ind[t]
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
(rt_ind[t] - expected_rt_ind[t]).abs() < 1e-5,
|
||||||
|
"rt_ind[{}] = {}, expected {}",
|
||||||
|
t, rt_ind[t], expected_rt_ind[t]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Test 1.5 — dd_pct foundational state input concat (LAYOUT FINGERPRINT
|
/// Test 1.5 — dd_pct foundational state input concat (LAYOUT FINGERPRINT
|
||||||
/// BREAK).
|
/// BREAK).
|
||||||
///
|
///
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user