功能
手续费、滑点与杠杆
模拟佣金、滑点、杠杆、期货乘数和负价格
成本前表现出色的策略,成本后可能失效。VBT 可在回测中加入手续费与滑点、固定及按股佣金、杠杆和期货合约乘数,并在一次调用中测试多个成本水平,观察交易优势究竟有多少余量。
data = vbt.YFData.pull("SPY", start="2015-01-01", end="2025-01-01")
fast = data.close.rolling(10).mean()
slow = data.close.rolling(30).mean()
pf = vbt.PF.from_signals(
data,
fast.vbt.crossed_above(slow),
fast.vbt.crossed_below(slow),
fees=vbt.Param([0, 0.0005, 0.001, 0.002]),
init_cash=10_000,
)
metrics = ["total_return", "total_fees_paid", "sharpe_ratio", "total_trades"]
pf.stats(metrics, agg_func=None).round(2) Total Return [%] Total Fees Paid Sharpe Ratio Total Trades
fees
0.0000 99.33 0.00 0.83 37
0.0005 92.09 476.16 0.78 37
0.0010 85.11 933.33 0.74 37
0.0020 71.91 1793.57 0.66 37pf.value.vbt.plot().show()十年只有 37 笔交易,每笔 20 基点手续费仍使总收益率减少 27 个百分点。每日交易的策略损失会大得多,因此每次回测都应从一开始计入真实成本。
手续费与佣金
fees 是每笔订单金额的比例,fixed_fees 是每笔固定金额。按股佣金除以价格即可转化为比例:
close = pd.Series([50.0], index=pd.date_range("2026-01-01", periods=1))
pf = vbt.PF.from_orders(
close,
size=100,
fees=vbt.Param(
[0.001, 0.0, 0.005 / 50],
keys=["0.1% of value", "$1 per order", "$0.005 per share"],
level=0,
),
fixed_fees=vbt.Param([0.0, 1.0, 0.0], level=0),
init_cash=10_000,
)
pf.orders.fees.to_pd().iloc[0].droplevel("fixed_fees")fees
0.1% of value 5.0
$1 per order 1.0
$0.005 per share 0.5
Name: 2026-01-01 00:00:00, dtype: float64手续费与其他参数一样是数组,可按资产和 K 线变化。负手续费模拟返佣。若要区分挂单与吃单费率,可按信号类型填充费用数组,例如限价入场位置设置较低费率。若实际成交订单类型无法预先确定,例如止损,或费率依赖近期交易量等级,应在执行前由回调设置手续费。
滑点
slippage
按价格比例,将市价成交向不利方向移动:买入更高,卖出更低。信号模拟器中的限价单则使用限价成交规则。
index = pd.date_range("2026-01-01", periods=2)
pf = vbt.PF.from_signals(
pd.Series([100.0, 110.0], index=index),
pd.Series([True, False], index=index),
pd.Series([False, True], index=index),
slippage=vbt.Param([0, 0.001]),
)
pf.orders.readable[["Column", "Side", "Price"]] Column Side Price
0 0.000 Buy 100.00
1 0.000 Sell 110.00
2 0.001 Buy 100.10
3 0.001 Sell 109.89模拟器决定订单可成交后,再应用滑点。100 美元的止损带 10 基点滑点,价格达到 100 美元时触发,以 99.90
美元成交。滑点可能使价格超出本根区间,price_area_vio_mode="cap" 可防止这种情况。
滑点可作为数组传入,为各资产和 K 线设置不同估计,也可在回调中随条件更新。例如,对流动性较低资产或高波动时期使用更大估计,再比较多种假设下的策略。若有买卖报价数据,执行价格示例展示如何按卖价买入、按买价卖出。
杠杆与借贷
杠杆决定持仓中有多少可以由借款支持,本身不会改变订单数量。因此,杠杆策略需要请求超过自身净值 100% 的仓位,并允许账户借入其余资金:
data = vbt.YFData.pull("SPY", start="2020-01-01", end="2025-01-01")
pf = vbt.PF.from_orders(
data,
size=2.0,
size_type="targetpercent",
leverage=vbt.Param([1, 2]),
init_cash=10_000,
)
pd.DataFrame({
"shares": pf.assets.iloc[0],
"total return": pf.total_return,
"max drawdown": pf.max_drawdown,
}).round(3) shares total return max drawdown
leverage
1 33.853 0.946 -0.337
2 67.707 1.206 -0.586没有杠杆,账户只能用自身现金买入,200% 目标被限制为 100%。杠杆为 2 时,持有股数翻倍,并像杠杆 ETF 一样每日再平衡。总收益率从 95% 升至 121%,最大回撤从 34% 升至 59%。
数量类型决定订单请求多少借贷能力。设 leverage=3、现金 1,000 美元、每股 100 美元,size=1.0 配合 size_type="percent" 买入 30
股,即全部杠杆购买力;"valuepercent" 则买入 10 股,即净值的 100%。
默认 leverage_mode="lazy" 只在自身现金用尽后借款,"eager"
对每个订单应用杠杆,只使用部分可用现金。手续费从自有现金支付。账户逐根跟踪债务、锁定现金和自由现金。净值降至零时,引擎不会自动强平;订单停止成交,由您决定分析中如何处理该时点。
借贷成本与资金费用
融资成本取决于借款金额和持仓时长。回调可读取债务,将利息作为负 cash_earnings 扣除,即使当天没有交易。这里积极杠杆用 500 美元借款支持 1,000 美元持仓。示例每日费率
0.1%,入场后每日收取 0.50 美元:
close = pd.Series([100.0, 100.0, 100.0], index=pd.date_range("2026-01-01", periods=3))
@njit
def financing_nb(ctx, cash_earnings, daily_rate):
cash_earnings[ctx.i, ctx.col] = -ctx.last_debt[ctx.col] * daily_rate
pf = vbt.PF.from_signals(
close,
entries=[True, False, False],
size=10,
init_cash=1000,
leverage=2,
leverage_mode="eager",
save_state=True,
cash_earnings=np.zeros(3),
adjust_func_nb=financing_nb,
adjust_args=(vbt.Rep("cash_earnings"), 0.001),
)
pd.DataFrame({"debt": pf.debt, "financing": pf.cash_earnings, "value": pf.value}) debt financing value
2026-01-01 500.0 0.0 1000.0
2026-01-02 500.0 -0.5 999.5
2026-01-03 500.0 -0.5 999.0价格不变,因此净值变化完全来自融资。示例保留现金支付成本。负现金收益最多扣至自由现金,因此满仓账户无法支付它们。
永续合约资金费用方式相同:回调在每次资金结算时,将持仓价值乘以费率。按持仓方向设置支付正负号,并使用自己的费率序列和计划。VBT 将这些现金流计入投资组合净值与收益率。
期货与特殊品种
期货合约控制其报价的若干倍金额。multiplier 将该倍数应用于订单金额、现金流和利润,因此一份 E-mini 标普 500 合约每点对应 50 美元。价格也可为零或负数,价差和 2020 年
4
月原油期货都出现过这种情况。下方合约乘数与负价格使用真实数据展示两者。合约换月不会自动处理:可以对连续序列作向后调整(back-adjustment),或用自己的信号平掉到期合约、开立下一合约。
平仓时实现损益,未平仓头寸逐根按市值计价。交易所每日将未实现利润转入现金的逐日盯市保证金,不单独建模。多数策略研究中,最终净值曲线相同,但持仓期间现金余额会与期货账户结单不同。
多币种
现金与价格必须使用同一货币。单一货币对应以账户货币报价,必要时倒转货币对,并同时转换订单价格数组。海外股票等持仓,应在模拟前将各价格序列换算为账户货币,这会将汇率暴露纳入各资产净值。独立币种余额、兑换成本及杠杆外汇损益换算需要自定义记账。
✅ 所有模拟入口现在都接受乘数,按固定系数缩放每个订单,便于模拟期货及其他一份合约代表多个标的单位的衍生品。
data = vbt.YFData.pull("ES=F", start="2023", end="2024")
fast_sma = data.run("talib_func:sma", timeperiod=10)
slow_sma = data.run("talib_func:sma", timeperiod=30)
entries = fast_sma.vbt.crossed_above(slow_sma)
exits = fast_sma.vbt.crossed_below(slow_sma)
pf_stock = vbt.PF.from_signals(
data,
entries=entries,
exits=exits,
size=1,
init_cash=500_000,
)
pf_futures = vbt.PF.from_signals(
data,
entries=entries,
exits=exits,
size=1,
multiplier=50,
init_cash=500_000,
)
print(pf_stock.total_profit)627.25print(pf_futures.total_profit) 31362.5print(pf_futures.trades.readable[["Avg Entry Price", "Avg Exit Price", "PnL", "Return"]]) Avg Entry Price Avg Exit Price PnL Return
0 4057.50 4138.00 4025.0 0.019840
1 4212.00 4480.75 13437.5 0.063806
2 4490.25 4378.75 -5575.0 -0.024832
3 4430.50 4820.00 19475.0 0.087913✅ 模拟引擎此前在验证时拒绝负价格,无法模拟价格确实可能低于零的品种,例如著名的 2020 年 4 月 WTI 原油事件。现在整个流程已全面支持负价格。
data = vbt.YFData.pull("CL=F", start="2020-02-01", end="2020-07-01")
entries = pd.Series(False, index=data.index)
exits = pd.Series(False, index=data.index)
entries["2020-03-03"] = True
exits["2020-05-04"] = True
pf = vbt.PF.from_signals(
data,
entries=entries,
exits=exits,
size=1,
multiplier=1000,
init_cash=200_000,
)
print(data.close[data.close < 0]) Date
2020-04-20 00:00:00-04:00 -37.630001
Name: Close, dtype: float64print(pf.trades.readable[["Avg Entry Price", "Avg Exit Price", "PnL", "Return"]]) Avg Entry Price Avg Exit Price PnL Return
0 47.18 20.389999 -26790.000916 -0.567825print(pf.value[["2020-03-03", "2020-03-09", "2020-04-20", "2020-05-04"]].round(2)) Date
2020-03-03 00:00:00-05:00 200000.0
2020-03-09 00:00:00-04:00 183950.0
2020-04-20 00:00:00-04:00 115190.0
2020-05-04 00:00:00-04:00 173210.0
Name: value, dtype: float64✅ 杠杆现已成为投资组合模拟的一部分。支持两种模式:lazy(现金不足时才启用)和
eager(使用部分可用现金,同时启用杠杆)。可逐笔订单设置杠杆,还能自动确定满足订单要求的最佳杠杆值!🏋️
data = vbt.YFData.pull("BTC-USD", start="2020", end="2022")
pf = vbt.PF.from_random_signals(
data,
n=100,
seed=42,
leverage=vbt.Param([0.5, 1, 2, 3]),
)
pf.value.vbt.plot().show()版权所有 © 2021–2026 Oleg Polakow。保留所有权利。
本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。