feat(cuda): add vectorized backtest environment step kernel

One thread per walk-forward window, parallel across all windows.
Handles: action→exposure mapping, trade execution with tx costs,
mark-to-market, step return calculation, drawdown tracking.
Portfolio state persists across steps in GPU global memory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-11 10:57:46 +01:00
parent b122c2d0e7
commit f501cc50cf

View File

@@ -0,0 +1,123 @@
// Vectorized backtest environment step kernel.
// One thread per walk-forward window. Each thread steps sequentially.
//
// Portfolio state layout per window [8 floats]:
// [0] value - current portfolio value
// [1] position - current position size (-1.0 to +1.0)
// [2] cash - cash balance
// [3] entry_price - entry price of current position (0 if flat)
// [4] max_equity - peak equity for drawdown tracking
// [5] step_pnl - PnL this step (for reward)
// [6] cum_return - cumulative log return
// [7] step_count - number of completed steps
#define PORTFOLIO_STATE_SIZE 8
extern "C" __global__ void backtest_env_step(
// Market data (read-only, uploaded once)
const float* __restrict__ prices, // [n_windows * max_len * 4] (OHLC)
const int* __restrict__ window_lens, // [n_windows]
// Actions from model for current step
const int* __restrict__ actions, // [n_windows] (0-4: Short100..Long100)
// Portfolio state (read-write, persistent across steps)
float* portfolio_state, // [n_windows * PORTFOLIO_STATE_SIZE]
// Step outputs
float* step_rewards, // [n_windows]
float* step_returns, // [n_windows * max_len] (accumulated)
int* done_flags, // [n_windows]
// Config
int n_windows,
int max_len,
float max_position,
float tx_cost_bps,
float spread_cost,
int current_step
) {
int w = blockIdx.x * blockDim.x + threadIdx.x;
if (w >= n_windows) return;
if (done_flags[w]) return;
int wlen = window_lens[w];
if (current_step >= wlen) {
done_flags[w] = 1;
return;
}
// Read current prices
int price_base = (w * max_len + current_step) * 4;
float open = prices[price_base + 0];
float high = prices[price_base + 1];
float low = prices[price_base + 2];
float close = prices[price_base + 3];
// Read portfolio state
int ps = w * PORTFOLIO_STATE_SIZE;
float value = portfolio_state[ps + 0];
float position = portfolio_state[ps + 1];
float cash = portfolio_state[ps + 2];
float entry_price = portfolio_state[ps + 3];
float max_equity = portfolio_state[ps + 4];
float cum_return = portfolio_state[ps + 6];
// Map action (0-4) to target exposure
float target_exposure;
switch (actions[w]) {
case 0: target_exposure = -1.0f; break; // Short100
case 1: target_exposure = -0.5f; break; // Short50
case 2: target_exposure = 0.0f; break; // Flat
case 3: target_exposure = 0.5f; break; // Long50
case 4: target_exposure = 1.0f; break; // Long100
default: target_exposure = 0.0f; break;
}
target_exposure *= max_position;
// Execute trade if position changes
float delta = target_exposure - position;
float trade_cost = 0.0f;
if (fabsf(delta) > 0.001f && close > 0.0f) {
trade_cost = fabsf(delta) * close * tx_cost_bps * 0.0001f
+ fabsf(delta) * spread_cost * 0.5f;
cash -= trade_cost;
// Mark-to-market old position
if (fabsf(position) > 0.001f && entry_price > 0.0f) {
float pnl = position * (close - entry_price);
cash += pnl;
}
position = target_exposure;
entry_price = close;
}
// Mark-to-market current position
float unrealized = 0.0f;
if (fabsf(position) > 0.001f && entry_price > 0.0f) {
unrealized = position * (close - entry_price);
}
float new_value = cash + unrealized;
// Step return
float step_ret = (value > 0.0f) ? (new_value - value) / value : 0.0f;
float new_cum_return = cum_return + step_ret;
// Update max equity for drawdown
float new_max = fmaxf(max_equity, new_value);
// Write portfolio state
portfolio_state[ps + 0] = new_value;
portfolio_state[ps + 1] = position;
portfolio_state[ps + 2] = cash;
portfolio_state[ps + 3] = entry_price;
portfolio_state[ps + 4] = new_max;
portfolio_state[ps + 5] = step_ret; // step PnL (for reward)
portfolio_state[ps + 6] = new_cum_return;
portfolio_state[ps + 7] += 1.0f; // step count
// Outputs
step_rewards[w] = step_ret;
step_returns[w * max_len + current_step] = step_ret;
}