功能
止损与止盈
回测止损、移动止损、止盈、时间止损和阶梯离场
离场决定了策略的大部分风险。VBT 可将止损、移动止损、止盈和时间止损作为 vbt.PF.from_signals 参数,在 Python
中回测,并一次测试完整参数网格。止损对每根开盘、最高、最低和收盘价检查,明确规定谁先成交。
data = vbt.YFData.pull("BTC-USD", start="2020-01-01", end="2024-01-01")
fast = data.close.rolling(20).mean()
slow = data.close.rolling(50).mean()
entries = fast.vbt.crossed_above(slow)
pf = vbt.PF.from_signals(
data,
entries,
sl_stop=vbt.Param(np.arange(2, 17, 2) / 100),
tp_stop=vbt.Param(np.arange(5, 45, 5) / 100),
fees=0.001,
)
pf.sharpe_ratio.sort_values(ascending=False).head(3)sl_stop tp_stop
0.08 0.4 0.941107
0.12 0.4 0.917926
0.08 0.1 0.910485
Name: sharpe_ratio, dtype: float64pf.sharpe_ratio.vbt.heatmap(x_level="sl_stop", y_level="tp_stop").show()最佳单元位于网格边缘,与邻近值差异很大。热图让您在依据单个最优值决策前,就能看到这一点。
止损与目标
内置四种价格止损机制。sl_stop 为止损,tsl_stop 为跟随入场以来最佳价格的移动止损,tp_stop 为止盈,tsl_th
将移动止损变成移动止盈,只有价格变动达到阈值后才开始跟随。默认都以入场价格的比例衡量:
index = pd.date_range("2026-01-01", periods=5)
ohlc = pd.DataFrame({
"open": [100.0, 101.0, 104.0, 108.0, 103.0],
"high": [101.0, 105.0, 111.0, 109.0, 104.0],
"low": [99.0, 100.0, 103.0, 102.0, 94.0],
"close": [100.0, 104.0, 108.0, 103.0, 95.0],
}, index=index)
entries = pd.Series([True, False, False, False, False], index=index)
pf = vbt.PF.from_signals(
ohlc["close"],
entries,
open=ohlc["open"],
high=ohlc["high"],
low=ohlc["low"],
sl_stop=0.05,
tsl_stop=vbt.Param([np.nan, 0.04], level=0),
tp_stop=vbt.Param([0.1, np.nan], level=0),
)
pf.orders.readable[["Column", "Fill Index", "Side", "Price", "Stop Type"]] Column Fill Index Side Price Stop Type
0 (nan, 0.1) 2026-01-01 Buy 100.00 None
1 (nan, 0.1) 2026-01-03 Sell 110.00 TP
2 (0.04, nan) 2026-01-01 Buy 100.00 None
3 (0.04, nan) 2026-01-04 Sell 106.56 TSL止盈在第三根以 110 美元成交。移动止损从 111 美元最高价向下跟随至 106.56 美元,在第四根成交。delta_format="absolute"
将止损值解释为价格距离而非比例,"target" 则解释为价格水平。
止损值与其他参数一样可广播,因此能按资产、K 线和方向不同。多空使用不同止损时,可在多头入场位置填多头值,在空头入场位置填空头值。止损在开仓时读取,之后保持固定,除非回调修改。
止损从入场后一根才检查,即使入场在开盘成交,因为无法判断本根最高与最低在成交之前还是之后。触发时平仓。stop_exit_type="reverse" 使同一订单还开立反向持仓。
将止损与止盈结合,可为每个持仓测试一组保护边界。任一规则平仓后,VBT 清除其他止损,旧目标不会影响后续持仓。
风险收益比设置只需两个数组。这里风险是到过去五根最低价的距离,目标为该距离的两倍:
data = vbt.YFData.pull("BTC-USD", start="2023-01-01", end="2024-01-01")
entries = data.close.vbt.crossed_above(data.close.rolling(20).max().shift(1))
risk = data.close - data.low.rolling(5).min()
pf = vbt.PF.from_signals(
data,
entries,
sl_stop=risk,
tp_stop=2 * risk,
delta_format="absolute",
)
cols = ["Entry Index", "Avg Entry Price", "Avg Exit Price", "Return"]
pf.trades.readable[cols].head(4) Entry Index Avg Entry Price Avg Exit Price Return
0 2023-01-23 00:00:00+00:00 22934.431641 20685.380859 -0.098064
1 2023-03-14 00:00:00+00:00 24746.074219 34981.714844 0.413627
2 2023-10-29 00:00:00+00:00 34538.480469 36781.667969 0.064947
3 2023-11-15 00:00:00+00:00 37880.582031 43744.746094 0.154807移动止盈
固定目标到达即离场;移动止盈允许走势继续,回调时再离场。tsl_th 设置激活所需涨幅,tsl_stop 设置跟随距离。达到阈值前,可保留独立 sl_stop。
同一价格路径先回落,再上涨超过 10%。一个组合从一开始跟随,另一个等达到该涨幅后才启用 5% 移动离场:
index = pd.date_range("2026-01-01", periods=6)
close = pd.Series([100.0, 104.0, 98.0, 112.0, 120.0, 113.0], index=index)
pf = vbt.PF.from_signals(
close,
entries=[True, False, False, False, False, False],
sl_stop=0.1,
tsl_stop=0.05,
tsl_th=vbt.Param([np.nan, 0.1], keys=["trail immediately", "trail after 10%"]),
)
pf.orders.side_sell.readable[["Column", "Fill Index", "Price", "Stop Type"]] Column Fill Index Price Stop Type
0 trail immediately 2026-01-03 98.0 TSL
1 trail after 10% 2026-01-06 113.0 TTP两个组合都有 10% 止损。阈值改变移动离场何时接管。示例只检查给出的收盘价,因此离场在这些收盘价成交。
按时间离场
时间止损在持有一定时长后或指定时刻离场,不受价格影响。td_stop 接受 "7D" 等时长,或配合 time_delta_format="rows" 接受 K 线数量。dt_stop
接受日期、"M"(月末)等周期,或 "18:00" 等日内时间。持有期从入场 K 线开盘计起,因此时间止损在到期 K
线开盘成交。下方时间止损在月末前关闭每个持仓。
pf = vbt.PF.from_signals(data, entries, td_stop=5, time_delta_format="rows")
pf.orders.readable[["Fill Index", "Side", "Stop Type"]].head(4) Fill Index Side Stop Type
0 2023-01-23 00:00:00+00:00 Buy None
1 2023-01-28 00:00:00+00:00 Sell TD
2 2023-01-29 00:00:00+00:00 Buy None
3 2023-02-03 00:00:00+00:00 Sell TD自适应止损
按波动率缩放的止损也是数组。传入 ATR 倍数,配合 delta_format="absolute",每笔交易就会获得符合入场市场状况的距离。距离只读取一次,因此止损序列不会让移动止损跟随当前
ATR。若要在持仓期间移动止损,应编写调整函数。它每根开始运行,可修改 c.last_sl_info 中的活动止损。这里在上一收盘已盈利一个风险单位后,将止损移到入场价:
@njit
def breakeven_nb(c):
sl_info = c.last_sl_info[c.col]
if c.last_position[c.col] > 0 and vbt.pf_nb.is_stop_info_active_nb(sl_info):
prev_close = vbt.pf_nb.select_nb(c, c.close, i=c.i - 1)
target = sl_info["init_price"] + sl_info["stop"]
if sl_info["stop"] > 0 and prev_close >= target:
sl_info["stop"] = 0.0
atr = data.run("atr", window=14).atr
kwargs = dict(sl_stop=2 * atr, tp_stop=6 * atr, delta_format="absolute")
fixed = vbt.PF.from_signals(data, entries, **kwargs)
breakeven = vbt.PF.from_signals(data, entries, adjust_func_nb=breakeven_nb, **kwargs)
pd.DataFrame({
"entry": fixed.trades.readable["Entry Index"],
"fixed": fixed.trades.readable["Return"],
"breakeven": breakeven.trades.readable["Return"],
}).iloc[3:6] entry fixed breakeven
3 2023-05-28 00:00:00+00:00 -0.056052 -0.056052
4 2023-06-20 00:00:00+00:00 -0.060298 0.000000
5 2023-10-01 00:00:00+00:00 -0.041770 -0.041770六月交易上涨超过初始风险后回落。固定止损亏损 6%,保本规则则平价离场。同一模式可按更高周期收紧止损、使用吊灯止损距离跟随,或加仓后按平均入场价重新设置止损。
加仓
每种止损每列保留一个活动水平。入场增加持仓时,upon_stop_update 决定保留已有止损,还是用新止损替换。
阶梯止损与部分离场
止损通常关闭全部持仓。配合 stop_ladder 传入止损值列表,则在连续水平分批离场。"uniform" 每级退出相同比例,"weighted"
根据距上一级的距离确定每次数量,自适应变体则根据剩余持仓重新计算。下方阶梯止损并列测试两种阶梯止盈。
部分止损离场后,阶梯机制保持余下止损有效,将分批止盈与剩余持仓保护结合。若需控制每一级,可在回调中设置止损记录的 exit_size 和 exit_type,例如反转而非平仓。
止损市价与止损限价离场
默认触发止损创建市价单。stop_order_type="limit" 改为创建限价单,stop_limit_delta
设置相对止损离场价的偏移。这样可比较触发后立即离场与等待指定价格。若始终未达到该价,限价可能无法成交。待成交订单和价格选择参见订单与执行。
真实止损成交
日线隐藏了日内事件顺序。VBT 将每根拆为开盘、开收盘之间及收盘,先成交先触及者。若止损与止盈在同一部分同时达到,止损优先:
index = pd.date_range("2026-01-01", periods=2)
pf = vbt.PF.from_signals(
pd.Series([100.0, 100.0], index=index),
pd.Series([True, False], index=index),
open=pd.Series([100.0, 100.0], index=index),
high=pd.Series([100.0, 112.0], index=index),
low=pd.Series([100.0, 94.0], index=index),
sl_stop=0.05,
tp_stop=0.1,
)
pf.orders.readable[["Fill Index", "Side", "Price", "Stop Type"]] Fill Index Side Price Stop Type
0 2026-01-01 Buy 100.0 None
1 2026-01-02 Sell 95.0 SL同样的悲观假设也用于移动止损:每根先检查止损,再允许新高提高止损位。若止损在本根不同部分触及,较早者成交。
跳空是另一陷阱。价格开盘越过止损时,真实订单按开盘而非止损水平成交。stop_exit_price 控制此行为:
pf = vbt.PF.from_signals(
pd.Series([100.0, 92.0], index=index),
pd.Series([True, False], index=index),
open=pd.Series([100.0, 90.0], index=index),
high=pd.Series([100.0, 93.0], index=index),
low=pd.Series([100.0, 89.0], index=index),
sl_stop=0.05,
stop_exit_price=vbt.Param(["stop", "hardstop", "close"]),
)
pf.orders.readable[["Column", "Side", "Price"]] Column Side Price
0 stop Buy 100.0
1 stop Sell 90.0
2 hardstop Buy 100.0
3 hardstop Sell 95.0
4 close Buy 100.0
5 close Sell 92.0默认 "stop" 在跳空后按开盘成交;"hardstop" 总按止损水平成交,属于乐观假设;"close" 按本根收盘。入场侧,stop_entry_price
设置止损参考价。默认是订单价格,例如 price="nextopen" 时为开盘。"fillprice" 将滑点计入参考价,"close" 则以入场 K 线收盘衡量。
紧止损应使用更细数据
止损相对 K 线振幅较小时,许多根会触及两个水平,结果由悲观规则决定。应在日内数据上测试,此时同时包含两个水平的 K 线少得多。
模拟前计算止损信号
止损也可在没有投资组合时计算为离场信号,适合信号研究、标签生成,以及在确定仓位前检查止损触发频率:
exits = entries.vbt.signals.generate_ohlc_stop_exits(
entry_price=data.close,
open=data.open,
high=data.high,
low=data.low,
close=data.close,
sl_stop=0.05,
tp_stop=0.1,
out_dict=(out := {}),
)
stop_names = dict(enumerate(vbt.sig_enums.StopType._fields))
pd.DataFrame({
"stop price": out["stop_price"][exits],
"stop type": out["stop_type"][exits].map(stop_names),
}).head(4) stop price stop type
Date
2023-02-09 00:00:00+00:00 22585.838086 SL
2023-02-24 00:00:00+00:00 23587.691016 SL
2023-03-17 00:00:00+00:00 27558.067969 TP
2023-03-22 00:00:00+00:00 26767.025586 SL默认仅在两个入场之间放置一次离场,因此止损仍等待时到达的新入场,不会启动新止损。
✅ 阶梯止损是一种逐步退出持仓的技术。无需提供单一止损值一次平仓,而可提供止损数组,各水平触发时退出一定数量。选择不同阶梯模式即可控制数量。新的广播功能允许数组仅沿一个轴广播,因此止损值无需与数据形状相同,甚至可将不同形状的止损数组作为参数!
data = vbt.YFData.pull("BTC-USD", end="2017-01")
pf = vbt.PF.from_holding(
data,
stop_ladder="uniform",
tp_stop=vbt.Param([
[0.1, 0.2, 0.3, 0.4, 0.5],
[0.4, 0.5, 0.6],
], keys=["tp_ladder_1", "tp_ladder_2"])
)
pf.trades.plot(column="tp_ladder_1").show()✅ 除其他止损单外,时间止损可在经过一定时长或指定日期时平仓。
data = vbt.YFData.pull("BTC-USD", start="2022-01", end="2022-04")
entries = vbt.pd_acc.signals.generate_random(data.symbol_wrapper, n=10, seed=42)
pf = vbt.PF.from_signals(data, entries, dt_stop="M")
pf.orders.readable[["Fill Index", "Side", "Stop Type"]] Fill Index Side Stop Type
0 2022-01-19 00:00:00+00:00 Buy None
1 2022-01-31 00:00:00+00:00 Sell DT
2 2022-02-25 00:00:00+00:00 Buy None
3 2022-02-28 00:00:00+00:00 Sell DT
4 2022-03-11 00:00:00+00:00 Buy None
5 2022-03-31 00:00:00+00:00 Sell DT版权所有 © 2021–2026 Oleg Polakow。保留所有权利。
本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。