feat(ml-backtesting): add stop-controller state slots + accessors
Three CudaSlice<f32> per-backtest slots (prev_mid_d, atr_mid_ema_d, trail_hwm_d) plus test-only accessors. Foundation for the ISV-driven stop controller; no behavioral change until subsequent tasks wire them into kernels. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -103,6 +103,11 @@ pub struct LobSimCuda {
|
||||
threshold_d: CudaSlice<f32>, // [n_backtests]
|
||||
cost_per_lot_per_side_d: CudaSlice<f32>, // [n_backtests]
|
||||
total_fees_per_b_d: CudaSlice<f32>, // [n_backtests] — accumulator
|
||||
|
||||
// Stop-controller state slots (ISV-driven stop controller).
|
||||
prev_mid_d: CudaSlice<f32>, // [n_backtests] — previous-event mid, sentinel 0 = first-obs bootstrap
|
||||
atr_mid_ema_d: CudaSlice<f32>, // [n_backtests] — Wiener-α=0.4 EMA on |Δmid|; floor for §5 SL/trail
|
||||
trail_hwm_d: CudaSlice<f32>, // [n_backtests] — per-position unrealized-P&L-per-lot high-water-mark
|
||||
}
|
||||
|
||||
impl LobSimCuda {
|
||||
@@ -256,6 +261,16 @@ impl LobSimCuda {
|
||||
let total_fees_per_b_d = stream.alloc_zeros::<f32>(n_backtests)
|
||||
.context("alloc total_fees_per_b_d")?;
|
||||
|
||||
let prev_mid_d = stream
|
||||
.alloc_zeros::<f32>(n_backtests)
|
||||
.context("alloc prev_mid_d")?;
|
||||
let atr_mid_ema_d = stream
|
||||
.alloc_zeros::<f32>(n_backtests)
|
||||
.context("alloc atr_mid_ema_d")?;
|
||||
let trail_hwm_d = stream
|
||||
.alloc_zeros::<f32>(n_backtests)
|
||||
.context("alloc trail_hwm_d")?;
|
||||
|
||||
Ok(Self {
|
||||
n_backtests,
|
||||
stream,
|
||||
@@ -305,6 +320,9 @@ impl LobSimCuda {
|
||||
threshold_d,
|
||||
cost_per_lot_per_side_d,
|
||||
total_fees_per_b_d,
|
||||
prev_mid_d,
|
||||
atr_mid_ema_d,
|
||||
trail_hwm_d,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -401,6 +419,45 @@ impl LobSimCuda {
|
||||
Ok(heads.iter().map(|&h| h as u64).sum())
|
||||
}
|
||||
|
||||
/// Read `atr_mid_ema_d[backtest_idx]`. Test-only accessor.
|
||||
pub fn read_atr_mid_ema(&self, backtest_idx: usize) -> Result<f32> {
|
||||
anyhow::ensure!(
|
||||
backtest_idx < self.n_backtests,
|
||||
"backtest_idx {} >= n_backtests {}",
|
||||
backtest_idx,
|
||||
self.n_backtests
|
||||
);
|
||||
let mut buf = vec![0.0f32; self.n_backtests];
|
||||
self.stream.memcpy_dtoh(&self.atr_mid_ema_d, buf.as_mut_slice())?;
|
||||
Ok(buf[backtest_idx])
|
||||
}
|
||||
|
||||
/// Read `prev_mid_d[backtest_idx]`. Test-only accessor.
|
||||
pub fn read_prev_mid(&self, backtest_idx: usize) -> Result<f32> {
|
||||
anyhow::ensure!(
|
||||
backtest_idx < self.n_backtests,
|
||||
"backtest_idx {} >= n_backtests {}",
|
||||
backtest_idx,
|
||||
self.n_backtests
|
||||
);
|
||||
let mut buf = vec![0.0f32; self.n_backtests];
|
||||
self.stream.memcpy_dtoh(&self.prev_mid_d, buf.as_mut_slice())?;
|
||||
Ok(buf[backtest_idx])
|
||||
}
|
||||
|
||||
/// Read `trail_hwm_d[backtest_idx]`. Test-only accessor.
|
||||
pub fn read_trail_hwm(&self, backtest_idx: usize) -> Result<f32> {
|
||||
anyhow::ensure!(
|
||||
backtest_idx < self.n_backtests,
|
||||
"backtest_idx {} >= n_backtests {}",
|
||||
backtest_idx,
|
||||
self.n_backtests
|
||||
);
|
||||
let mut buf = vec![0.0f32; self.n_backtests];
|
||||
self.stream.memcpy_dtoh(&self.trail_hwm_d, buf.as_mut_slice())?;
|
||||
Ok(buf[backtest_idx])
|
||||
}
|
||||
|
||||
/// Apply one MBP-10 snapshot's top-10 levels to every backtest's book.
|
||||
/// v1 inputs are single-snapshot broadcast (same market data to all
|
||||
/// parallel backtests — see spec §7 inference scope, policy sweeps only).
|
||||
|
||||
Reference in New Issue
Block a user