功能

日内与逐笔回测

回测分钟线、逐笔与交易时段,并用更细数据判断止损

一根 K 线只告诉您开盘、最高、最低和收盘价。日内策略的止损较紧时,内部价格路径决定结果。VBT 可在 Python 中基于任意大小的 K 线、不规则时间戳及交易时段进行日内与逐笔回测,并用比信号更细的数据判断止损。

Resolve the same stops on hourly and on minute bars
minute_data = vbt.GBMOHLCData.pull(  
    "SYN",
    start="2024-01-01",
    end="2024-02-01",
    timeframe="1min",
    std=0.0001,
    seed=42,
)
hourly_data = minute_data.resample("1h")
hourly_entries = hourly_data.symbol_wrapper.fill(False)
hourly_entries.iloc[::4] = True  

minute_entries = minute_data.symbol_wrapper.fill(False)
entry_minutes = hourly_entries.index[hourly_entries] + pd.Timedelta(minutes=59)
minute_entries[entry_minutes] = True  

kwargs = dict(sl_stop=0.002, tp_stop=0.002)
hourly_pf = vbt.PF.from_signals(hourly_data, hourly_entries, **kwargs)
minute_pf = vbt.PF.from_signals(minute_data, minute_entries, **kwargs)

def summary(pf):
    stop_types = pf.orders.readable["Stop Type"].value_counts()
    return pd.Series({
        "trades": pf.trades.count(),
        "stop losses": stop_types.get("SL", 0),
        "take profits": stop_types.get("TP", 0),
        "win rate": round(pf.trades.win_rate, 3),
        "total return": round(pf.total_return, 3),
    })

pd.DataFrame({"hourly bars": summary(hourly_pf), "minute bars": summary(minute_pf)})
              hourly bars  minute bars
trades            186.000      186.000
stop losses       134.000      102.000
take profits       52.000       84.000
win rate            0.280        0.452
total return       -0.152       -0.036

两次运行都有相同 186 笔交易,使用相同的 0.2% 止损和止盈。小时 K 线中,许多根同时触及两者,无法判断先后时,模拟器假定止损先发生。分钟 K 线能区分更多此类情况,胜率从 28% 升至 45%,更接近随机游走配对称止损本应产生的 50%。

逐笔与不规则 K 线

引擎不假设固定 K 线大小。逐笔价格作为 close 传入,每行作为独立时刻处理,无须开盘、最高和最低价。其他地方构建的成交量 K 线、价格区间 K 线、金额 K 线和成交笔数 K 线也同样适用,只需索引有序:

Trade on ticks with irregular timestamps
index = pd.date_range("2024-01-01 09:30", periods=8, freq="1s").vbt.randomize(
    step="100ms",
    seed=42,
)
prices = [100.0, 100.2, 100.1, 100.4, 100.3, 100.6, 100.5, 100.7]
ticks = pd.Series(prices, index=index)
ticks.index.to_series().diff().dt.total_seconds().tolist()
[nan, 0.8, 1.6, 1.2, 0.8, 0.6, 1.0, 1.0]
entries = pd.Series([True] + [False] * 7, index=index)
exits = pd.Series([False] * 6 + [True, False], index=index)
pf = vbt.PF.from_signals(ticks, entries, exits)
pf.orders.readable[["Fill Index", "Side", "Price"]]
           Fill Index  Side  Price
0 2024-01-01 09:30:00   Buy  100.0
1 2024-01-01 09:30:06  Sell  100.5

夏普比率等年化度量需要频率,而不规则索引没有固定频率。可显式传入 freq,用 freq="index_mean" 取时间戳平均间隔,或将投资组合收益重采样到规则网格再分析。时长按 K 线数量乘频率计算,因此采用平均间隔时只是估计。一年按多长计算也很重要,详见收益率。

构建测试用合成逐笔数据时,vbt.RandomOHLCData 和 vbt.GBMOHLCData 生成价格,.vbt.randomize() 像上例一样将规则索引变成不规则时间戳。

逐笔窗口按行数,而非时间计数

多数指标以行数指定窗口。逐笔数据中的 20 行可能跨一秒或一小时,取决于活跃程度。可在时间 K 线上计算指标,或使用 ticks.rolling("5min") 等时间窗口,再将结果对齐回逐笔索引。

K 线内部

有三种方式观察 K 线内部。第一种就是上例:在交易周期上计算信号,在更细 K 线上模拟,使止损与限价以更高分辨率检查。报告时可将结果重采样回原始周期。

因此,小时策略可以使用分钟或逐笔价格执行,而无须将指标窗口改成分钟或逐笔。VBT 的对齐工具可将小时收盘信号放在该小时结束时,并只在日收盘后提供日线趋势过滤器。不同周期信号、过滤器与价格的组合,参见多时间周期分析。

第二种是在同一根内下多个订单。信号模拟器每列每根最多处理一个订单;灵活订单函数则可返回任意数量订单,例如同根开盘买入、收盘卖出,详见事件驱动回测。

第三种保留信号模拟器,将每根拆成三根子 K 线:开盘一根、中间区间一根、收盘一根。止损在前两根成交,自定义信号在最后一根执行,持仓便可在原来一根内平仓又重开。pf.resample 将结果映射回原始索引。

实用示例

会员专享的每根 K 线执行多个操作(英文)示例构建三根子 K 线,再将组合重采样回原周期。

K 线无法告诉您的信息

一根 K 线中只有开盘与收盘的时间先后已知。策略若依赖最高价与最低价谁先发生,应使用更细数据,或接受悲观假设。具体规则见真实止损成交。

交易时段

交易时段是索引上的掩码。将入场限制在时段内,并在每个时段最后一根添加离场,策略就能每晚空仓:

Trade a New York session and be flat at every close
data = minute_data.loc["2024-01-08":"2024-01-12"]
local_index = data.index.tz_convert("America/New_York")
local_time = local_index.time
in_session = (local_time >= pd.Timestamp("09:30").time()) & (
    local_time < pd.Timestamp("16:00").time()
)
session_end = local_time == pd.Timestamp("15:59").time()

sma = data.close.rolling(30).mean()
entries = data.close.vbt.crossed_above(sma) & in_session & ~session_end
exits = data.close.vbt.crossed_below(sma) | session_end
pf = vbt.PF.from_signals(data, entries, exits)

print(pf.trades.count())
91
pf.assets.groupby(local_index.date).last().tolist()  
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

没有价格的行不下单。使用 price="nextopen" 时,星期五信号若下一行是空的周末行,永远不会成交。应删除闭市行,例如拉取多个品种时使用 missing_index="drop",使下一行就是下个时段开盘。

闭市期间出现的信号需要规则:可以丢弃,也可延续到下一时段第一根,使实盘持仓与回测对应。“入场当天不离场”等规则也可用掩码表示,将每个离场与此前入场日期比较。

若要构建只覆盖交易时段的 K 线,例如从 24 小时数据得到 09:30 至 16:00 的日线,应先过滤到交易时段,或在自定义边界之间重采样。

也可独立测试各时段。将等长时段放入不同列,每个时段使用新现金余额,比较不同日期的策略。这适合每日平仓策略,也可判断结果是否主要由少数时段贡献。

教程

会员专享的多时间周期分析(英文)教程使用自定义边界,将日内数据聚合为交易时段 K 线。

更长的日内历史

先用逐笔数据模拟,再缩放观察全局。将已完成投资组合重采样为分钟、小时或日线分析。VBT 将订单记录映射到新索引,并在该频率重建净值与收益,使整体结果需要处理和绘制的数据更少。保留原始投资组合,以检查逐笔成交。

连续分批处理历史时,投资组合也可携带现金、持仓与止损继续下一批。实时模拟使用的同一机制,让历史运行跨越批次边界,无须每次边界都平仓。

版权所有 © 2021–2026 Oleg Polakow。保留所有权利。

本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。