功能
实时模拟
在新 K 线上延续回测、串联运行,并向外部交易系统提供结果
新 K 线到达时,回测能否不重算历史而继续运行?VBT 从最后状态延续投资组合:现金、持仓、待触发止损与限价单都会保留,每根新 K 线从上次停止处推进模拟。您可以回放历史,再让同一个模拟账户在新数据上继续,用于 Python 增量回测和模拟交易。
data = vbt.YFData.pull("BTC-USD", start="2023-01-01", end="2024-01-01")
def get_signals(data):
sma = data.close.rolling(20).mean()
return data.close.vbt.crossed_above(sma), data.close.vbt.crossed_below(sma)
history = data.iloc[:330]
entries, exits = get_signals(history)
pf = vbt.PF.from_signals(
history,
entries,
exits,
tsl_stop=0.05,
attach_preparer=True,
)
emitted = []
for i in range(330, len(data.index)):
available = data.iloc[:i + 1]
entries, exits = get_signals(available)
pf = pf.update(
available.iloc[-1:],
entries=entries.iloc[-1:],
exits=exits.iloc[-1:],
)
orders = pf.orders.readable
emitted.append(orders[orders["Fill Index"] == available.index[-1]])
pd.concat(emitted)[["Fill Index", "Side", "Price", "Stop Type"]] Fill Index Side Price Stop Type
45 2023-12-11 00:00:00+00:00 Sell 42470.239844 TSL
46 2023-12-18 00:00:00+00:00 Buy 42623.539062 None
47 2023-12-26 00:00:00+00:00 Sell 42149.559180 TSL
48 2023-12-27 00:00:00+00:00 Buy 43442.855469 None
49 2023-12-28 00:00:00+00:00 Sell 42627.855469 Noneentries, exits = get_signals(data)
full_pf = vbt.PF.from_signals(data, entries, exits, tsl_stop=0.05)
pf.orders.records.equals(full_pf.orders.records), pf.value.equals(full_pf.value)(True, True)12 月 11 日触发的移动止损,属于回放开始前已开立的持仓。其峰值价格在每次更新中延续。经过 35 次单根更新,订单和净值曲线与一次性回测完全一致。
从研究更新到实时数据
让研究随着新数据推进。延续机制连接历史回测、增量分析和模拟交易:
| 您想做什么 | VBT 如何处理 |
|---|---|
| 新增一天价格后更新策略 | 用 pf.update 延伸投资组合 |
| 回放历史数据流或监控模拟账户 | 每次处理一根或一批新 K 线 |
| 在时期之间重训模型或修改参数 | 串联模拟,同时延续账户 |
| 让原生模拟器在新 K 线到达间保持运行 | 推进 Rust 步进器,并保存检查点 |
下文同时介绍 Python 投资组合更新和原生 Rust 步进。数据源与执行系统可以继续独立于模拟。
从最后状态继续
pf.update
接收新数据与新输入,从投资组合最后状态继续。状态包括现金、持仓、债务、估值价格、入场价、待成交限价单,以及全部止损,包括移动峰值和时间止损计数器。结果默认包含完整历史;stack=False
只包含新片段,仅需最新订单时成本更低。
延续也适用于自定义交易规则。例如,亏损后的冷却期可跨越多次更新。将回调记忆与投资组合状态一同保存,传给下次更新。事件驱动回测介绍此类规则,实时教程展示如何延续其记忆。
重启后恢复
状态也能存储。save_state=True 保留模拟期间计算的现金和持仓序列,pf.last_state 保存后续运行所需的最终状态。以 attach_preparer=True
构建的投资组合,可用 pf.save(path) 写入磁盘,再在后续会话用 vbt.PF.load(path) 恢复,准备下一次
pf.update。完整实时流程还应保留指标状态和应用自身拥有的策略记忆。下方投资组合延续验证移动止损与时间止损跨更新保持一致。
串联与混合运行
一些工作流需要有意暂停模拟:每月重训模型、重新优化权重,或在时期之间修改参数。分段回测,让每段从上一段继续,再用 vbt.PF.row_stack
合成一段历史。下方串联模拟构建此类链条;同一模式也让投资组合优化器在每次再平衡时重新估计权重,同时延续账户。
逐根 K 线步进
原生 Rust 引擎提供步进器:驻留内存、每次推进一根 K 线的模拟器对象,在步与步之间保留策略及执行状态。步进器可克隆或序列化为检查点,之后恢复,例如重启后;输出可存为 NumPy 文件,由 Python 加载回投资组合。
教程
会员专享的从 Python 到 Rust(英文)教程分别在 Python、Numba 和原生 Rust 步进器中延续有状态策略,并检查三者一致。
实时流程
实时循环有三个阶段,各自保留状态:数据、指标和投资组合。data.update() 获取新 K 线,设置 return_meta=True
时报告哪些行被追加、哪些被修订。指标可重算所需窗口,流式指标也可从最后状态更新。投资组合用 pf.update 继续。vbt.wait(timeframe, floor=True) 等待下一个
K 线边界,让循环在每根收盘后立即运行:
while True:
vbt.wait("1h", floor=True)
update = data.update(return_meta=True)
data = update["data"]
for row in update["appended"]:
... # compute signals for the closed bar, then call pf.update连接券商
VBT 不向券商发单。它负责决策与模拟,由您的执行层路由订单、处理成交并反馈。以下规则帮助两者保持一致:
- 基于已收盘 K 线决策。 信号只使用完整 K 线,避免执行后决策又变化。许多数据源还返回仍在形成的 K 线,因此计算前应删除它。
- 成交前规划。 下一开盘执行时,应下订单来自最后已收盘 K 线的信号。模拟器只在下一根到达后才记录它,止损或冲突信号仍可能改变订单。
- 对账。 每次更新后比较券商成交与持仓和模拟结果;K 线修订时避免重复发单。实际成交也可作为独立投资组合分析,详见订单与执行。
订单记录只含已成交订单。待触发止损和限价单保存在最终状态,因此券商端止损可以对应模拟止损:
data = vbt.YFData.pull("ETH-USD", start="2024-09-01", end="2024-11-13")
sma = data.close.rolling(20).mean()
pf = vbt.PF.from_signals(
data,
data.close.vbt.crossed_above(sma),
data.close.vbt.crossed_below(sma),
tsl_stop=0.08,
)
state = pf.last_state
position = float(state.last_position[0])
tsl_info = state.last_tsl_info[0]
position, float(tsl_info["peak_price"])(0.0333110557990236, 3444.154052734375)vbt.pf_nb.get_tsl_info_target_price_nb(tsl_info, position) 3168.621728515625持仓在 11 月 6 日开立,11 月 12 日收盘后仍未平仓。若下一根穿过 3168.62,回测将在该水平卖出;若开盘已低于它,则按开盘卖出。执行层可在券商设置相同止损水平,并将实际成交与模拟对账。
✅ 新数据到达时,从上次模拟结束处继续。VBT 将持仓、订单 ID 和止损状态带入每次更新,包括入场时间戳和移动最高价。移动止损记住峰值,时间止损继续计数,每次更新返回包含合并历史的新投资组合。
close = pd.Series(
[100.0, 110.0, 108.0, 98.0, 103.0, 115.0],
index=pd.date_range("2026-09-01", periods=6, freq="D")
)
entries = pd.Series([True, False, False, False, True, False], index=close.index)
pf_kwargs = dict(size=1, init_cash=1000, tsl_stop=0.05, freq="1D")
pf = vbt.PF.from_signals(
close.iloc[:2],
entries=entries.iloc[:2],
attach_preparer=True,
**pf_kwargs,
)
for start in [2, 4]:
pf = pf.update(
close.iloc[start:start + 2],
entries=entries.iloc[start:start + 2],
)
print(pf.orders.readable[["Order Id", "Fill Index", "Side", "Price", "Stop Type"]]) Order Id Fill Index Side Price Stop Type
0 0 2026-09-01 Buy 100.0 None
1 1 2026-09-04 Sell 98.0 TSL
2 2 2026-09-05 Buy 103.0 Nonefull_pf = vbt.PF.from_signals(close, entries=entries, **pf_kwargs)
pf.orders.records.equals(full_pf.orders.records) and pf.value.equals(full_pf.value)True教程
更多内容参见实时模拟(英文)教程。
✅ 每个内置模拟函数现在处理完全部数据后都会返回最终模拟状态,包括最后现金、持仓、待处理订单及其他相关信息。该状态可传入下一次运行,从上次结束处继续,实现跨不同时期或数据集的无缝串联。
data = vbt.BinanceData.pull(
"BTCUSDT",
start="one year ago",
timeframe="5 minutes",
cache=True
)
fast_sma = data.run("talib_func:sma", timeperiod=20)
slow_sma = data.run("talib_func:sma", timeperiod=50)
long_entries = fast_sma.vbt.crossed_above(slow_sma)
short_entries = fast_sma.vbt.crossed_below(slow_sma)
single_pf = vbt.PF.from_signals(
data,
long_entries=long_entries,
short_entries=short_entries,
)
data_splits = data.split(by="month")
long_entries_splits = long_entries.vbt.split(by="month", into=None)
short_entries_splits = short_entries.vbt.split(by="month", into=None)
pf_list = []
last_state = None
for i in range(len(data_splits)):
pf = vbt.PF.from_signals(
data_splits.iloc[i],
long_entries=long_entries_splits.iloc[i],
short_entries=short_entries_splits.iloc[i],
last_state=last_state,
)
pf_list.append(pf)
last_state = pf.last_state
stacked_pf = vbt.PF.row_stack(*pf_list, chained=True)
print(stacked_pf.returns.equals(single_pf.returns))True版权所有 © 2021–2026 Oleg Polakow。保留所有权利。
本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。