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:
123
crates/ml/src/cuda_pipeline/backtest_env_kernel.cu
Normal file
123
crates/ml/src/cuda_pipeline/backtest_env_kernel.cu
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user