
本文介绍如何利用 Pandas 与 Numba 对大规模金融时序数据(千万级行)进行完全向量化的止盈(Take Profit)与止损(Stop Loss)回测计算,避免逐行循环或 apply(),显著提升多仓位并发退出判定效率。
本文介绍如何利用 pandas 与 numba 对大规模金融时序数据(千万级行)进行**完全向量化**的止盈(take profit)与止损(stop loss)回测计算,避免逐行循环或 `apply()`,显著提升多仓位并发退出判定效率。
在高频或中频策略回测中,当持仓信号密集、价格波动频繁时,传统基于 apply() 或 iterrows() 的退出逻辑极易成为性能瓶颈——尤其面对 700 万+ 行的 OHLC 数据。关键挑战在于:每个开仓点需独立搜索其后首个满足 TP/SL 条件的 K 线,且需严格按时间顺序判定触发优先级(SL 与 TP 同时触发时,以更早发生者为准)。纯 Pandas 实现难以避免广播爆炸或冗余扫描,而 Numba 提供的 JIT 编译能力可将核心搜索逻辑编译为原生机器码,在保持逻辑清晰的同时实现近 C 语言级性能。
以下为完整、可直接运行的向量化解决方案:
✅ 核心思路:预分配 + 原地写入 + 时间索引对齐
- 预分配输出列:exitPrice 和 exitTime 列初始化为 NaN;
- 提取底层 NumPy 数组:绕过 Pandas 开销,直接操作 values(注意 dtype 一致性);
-
Numba 加速双层循环:
- 外层遍历所有潜在开仓行(idx1),跳过无 TP/SL 设置的行;
- 内层从 idx1+1 开始线性扫描后续 K 线,检查 high ≥ TP 或 low ≤ SL;
- 首次命中即终止内层循环(保证“最早触发”语义),并写入对应 exitPrice 和 exitTime;
- 时间索引安全转换:因 Numba 不支持 datetime64[ns],需临时转为 float64(纳秒时间戳),计算完成后再转回。
import pandas as pd
import numpy as np
import numba
@numba.njit
def get_long_exit(
index_float, # datetime64[ns] → float64 (nanoseconds since epoch)
high_vals,
low_vals,
tp_prices,
sl_prices,
out_exit_price,
out_exit_index_float # 输出为 float64 时间戳
):
n = len(index_float)
for idx1 in range(n - 1):
# 跳过未设置 TP/SL 的开仓点
if np.isnan(tp_prices[idx1]) or np.isnan(sl_prices[idx1]):
continue
tp_val, sl_val = tp_prices[idx1], sl_prices[idx1]
# 向后搜索首个触发点
for idx2 in range(idx1 + 1, n):
h, l = high_vals[idx2], low_vals[idx2]
# 止损触发:价格跌破 SL(做多时 low ≤ SL)
if l = tp_val:
out_exit_price[idx1] = tp_val
out_exit_index_float[idx1] = index_float[idx2]
break
# 初始化输出列
df["exitPrice"] = np.nan
df["exitTime"] = pd.NaT # 使用 NaT 而非 NaN,语义更准确
# 提取底层数组(关键:确保 dtype 兼容 Numba)
index_float = df.index.astype("int64").values.astype(np.float64) # ns → float64
high_arr = df["high"].values
low_arr = df["low"].values
tp_arr = df["longTpPrice"].values
sl_arr = df["longSlPrice"].values
exit_price_arr = df["exitPrice"].values
exit_time_float = np.full(len(df), np.nan) # 临时 float64 存储时间戳
# 执行 Numba 加速计算
get_long_exit(
index_float, high_arr, low_arr, tp_arr, sl_arr,
exit_price_arr, exit_time_float
)
# 将 float64 时间戳转回 datetime64[ns]
df["exitTime"] = pd.to_datetime(exit_time_float, unit="ns", errors="coerce")
⚠️ 关键注意事项
- 触发逻辑严谨性:代码中 elif h >= tp_val 确保在 l
- 时间索引兼容性:df.index.astype("int64") 获取纳秒级整数时间戳,再转 float64 是 Numba 安全的通用方案;pd.to_datetime(..., unit="ns") 精确保留原始精度;
- 内存与性能平衡:该方案空间复杂度 O(1)(仅预分配两列),时间复杂度最坏 O(N²),但实践中因提前 break,实际耗时接近 O(N×avg_holding_period),远优于 Python 循环;
- 扩展性提示:如需支持空仓、短仓、动态 SL/TP 或滑点模拟,可在 get_long_exit 内部增加参数与分支逻辑,无需改变主框架。
最终,df["exitPrice"] 与 df["exitTime"] 即为向量化生成的退出结果,可直接用于计算盈亏、胜率、最大回撤等核心绩效指标,彻底摆脱 apply() 性能枷锁。











