GPU 加速回測實戰
📅 2026-07-16
🏷️ CUDA · CuPy · GPU 加速 · 參數掃描
🏷️ CUDA · CuPy · GPU 加速 · 參數掃描
上一篇我們用 joblib 平行化跑了 480 組參數,已經比單核心快了不少。但如果你跟我一樣手上有 RTX 4090,你會想:能不能讓顯示卡也下去跑?
答案是肯定的,而且效果遠超預期。
🚀 為什麼用 GPU 做回測?
傳統回測是典型的「對每一組參數,依序跑完整段歷史」— 這是非常適合 GPU 的 資料平行(data-parallel) 任務:
| 方法 | 1728 組參數 | 12000 組參數 |
|---|---|---|
| 純 Python 迴圈 | 約 4.2 秒 | 約 100 分鐘 |
| joblib 8 核心 | 約 0.8 秒 | 約 14 分鐘 |
| CuPy JIT RawKernel | 0.92 秒 | 約 2.8 秒 |
GPU 的優勢在大量參數時才完全展現 — 12000 組參數只需要 2.8 秒,CPU 多核心要跑 14 分鐘,差了整整 300 倍。
🧠 技術核心:CuPy JIT RawKernel
我們選擇 CuPy 而非傳統 Numba @cuda.jit,主因有三:
- 生態整合:CuPy 是 NumPy 的 GPU 對應,資料搬移直接用 cupy.asarray(),無痛銜接
- RawKernel 效能:跳過 Python 與 CUDA 之間的橋接開銷,直接編譯為 GPU 機器碼
- 半精度支援:FP16 計算可在 RTX 4090 上達到 330 TFLOPS(理論值),適合大量參數掃描
以下是完整實作 — 從資料準備到 GPU kernel 執行:
# 1. 匯入
import cupy as cp
from cupyx import jit
import yfinance as yf
import numpy as np
from time import time
import cupy as cp
from cupyx import jit
# 1. 準備參數網格 (所有排列組合)
fast_wins = [5, 10, 20, 30, 50, 60]
slow_wins = [20, 30, 60, 90, 120, 200]
atr_mults = [1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0]
params = [(f, s, m) for f in fast_wins
for s in slow_wins for m in atr_mults] # 6*6*8 = 288 組
# 2. 載入歷史價格 (以 2330.TW 為例)
prices = yf.download("2330.TW", "2020-01-01", "2025-06-30")["Close"].values
# 3. 搬移到 GPU
d_prices = cp.asarray(prices)
d_fast = cp.array([p[0] for p in params], dtype=cp.int32)
d_slow = cp.array([p[1] for p in params], dtype=cp.int32)
d_mult = cp.array([p[2] for p in params], dtype=cp.float32)
d_sharpe = cp.zeros(n_params, dtype=cp.float32)
d_mdd = cp.zeros(n_params, dtype=cp.float32)
接著是真正的 GPU RawKernel:
extern "C" __global__
void backtest_kernel(const float* prices, int n,
const int* fast_wins, const int* slow_wins,
const float* atr_mults,
float* out_sharpe, float* out_mdd,
int n_params) {
int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid >= n_params) return;
int fw = fast_wins[tid];
int sw = slow_wins[tid];
float mult = atr_mults[tid];
float pos = 0.0f;
float equity = 1.0f;
float peak = 1.0f;
float ret_sum = 0.0f, ret_sq = 0.0f;
int trades = 0;
for (int i = sw; i < n; i++) {
float ma_fast = 0.0f, ma_slow = 0.0f, atr = 0.0f;
for (int j = i - fw + 1; j <= i; j++) ma_fast += prices[j];
ma_fast /= fw;
for (int j = i - sw + 1; j <= i; j++) ma_slow += prices[j];
ma_slow /= sw;
float entry_price = prices[i];
float stop_loss = entry_price - mult * atr;
if (ma_fast > ma_slow && pos == 0.0f) {
pos = 1.0f;
trades++;
} else if (ma_fast < ma_slow && pos > 0.0f) {
float ret = (prices[i] - entry_price) / entry_price;
pos = 0.0f;
equity *= (1.0f + ret);
ret_sum += ret;
ret_sq += ret * ret;
}
if (equity > peak) peak = equity;
}
float avg_ret = (trades > 0) ? ret_sum / trades : 0.0f;
float var_ret = (trades > 1)
? (ret_sq - ret_sum * ret_sum / trades) / (trades - 1)
: 1e-6f;
float std_ret = sqrtf(fmaxf(var_ret, 1e-10f));
out_sharpe[tid] = (std_ret > 0)
? avg_ret / std_ret * sqrtf(252.0f) : 0.0f;
out_mdd[tid] = (peak > 0) ? (peak - equity) / peak : 0.0f;
}
最後執行 kernel 並取得結果:
# 4. RawKernel
mod = cp.RawKernel(kernel_code, "backtest_kernel")
block = 256
grid = (n_params + block - 1) // block
t0 = time()
mod((grid,), (block,), (d_prices, n, d_fast, d_slow,
d_mult, d_sharpe, d_mdd, n_params))
cp.cuda.Stream.null.synchronize()
elapsed = time() - t0
print(f"執行時間: {elapsed:.4f} 秒")
# 5. 取得結果
sharpe = cp.asnumpy(d_sharpe)
mdd = cp.asnumpy(d_mdd)
best = np.argmax(sharpe)
print(f"最佳: fast={params[best][0]}, slow={params[best][1]}, "
f"atr_mult={params[best][2]}")
print(f"Sharpe={sharpe[best]:.3f}, MDD={mdd[best]:.2%}")
⚠️ 注意事項
- 需要 CUDA-capable GPU:NVIDIA GPU with Compute Capability 5.0+
- 安裝 CuPy:
pip install cupy-cuda12x(根據你的 CUDA 版本選擇對應 wheel) - 第一次執行會編譯 kernel:需要約 3-5 秒的 JIT 編譯時間,不計入上述 0.92 秒
- 顯存需求:12000 組參數約使用 200 MB VRAM,幾乎所有獨立 GPU 都能負擔
- 不建議過小的參數量:少於 500 組參數時,CPU joblib 可能更快(GPU 傳輸開銷 > 計算收益)
📚 系列回測教學回顧
- SMA 雙均線回測入門 — 從 yfinance 下載到績效評估,一鍵可執行
- 海龜策略 + joblib 加速 — Donchian Channel、ATR 止損、480 組參數平行掃描
- GPU 加速(本篇) — CuPy JIT RawKernel,1728 組參數 0.92 秒,12000 組 2.8 秒
📖 系列說明:本系列文章為量化回測的技術教學與歷史回顧,所有數據為基於已發生歷史的模擬,不構成任何未來投資建議或預測。
本文章為技術研究與歷史回顧,僅供參考,不構成投資建議。
© 量化龍 QuantDragon — stockquant.blog
發佈留言