# 手续费、滑点与杠杆 (/zh/features/backtesting/fees-slippage-and-leverage)

成本前表现出色的策略，成本后可能失效。VBT 可在回测中加入手续费与滑点、固定及按股佣金、杠杆和期货合约乘数，并在一次调用中测试多个成本水平，观察交易优势究竟有多少余量。

```python title="Test one strategy at four fee levels in one call"
>>> 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]),  # (1)
...     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            37

>>> pf.value.vbt.plot().show()
```

1.  每个订单金额的 0、5、10 和 20 个基点。

2015 至 2024 年 SPY 均线交叉投资组合在 0、5、10、20 基点手续费下的净值. [Figure data (JSON)](/assets/figures/features/backtesting/fees-cost-sensitivity.6e3bd01c30da.json)

十年只有 37 笔交易，每笔 20 基点手续费仍使总收益率减少 27 个百分点。每日交易的策略损失会大得多，因此每次回测都应从一开始计入真实成本。

## 手续费与佣金 \[#fees-and-commissions]

`fees` 是每笔订单金额的比例，`fixed_fees` 是每笔固定金额。按股佣金除以价格即可转化为比例：

```python title="Compare three commission models on a 100-share order"
>>> 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],  # (1)
...         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
```

1.  真实数据中，传入 `0.005 / close`，即可逐根应用按股费率。

手续费与其他参数一样是数组，可按资产和 K
线变化。负手续费模拟返佣。若要区分挂单与吃单费率，可按信号类型填充费用数组，例如限价入场位置设置较低费率。若实际成交订单类型无法预先确定，例如止损，或费率依赖近期交易量等级，应在执行前由回调设置手续费。

## 滑点 \[#slippage]

`slippage`
按价格比例，将市价成交向不利方向移动：买入更高，卖出更低。信号模拟器中的限价单则使用[限价成交规则](/zh/features/backtesting/orders-and-execution/#pending-limit-and-stop-entry-orders)。

```python title="Apply 10 basis points of 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
线设置不同估计，也可在回调中随条件更新。例如，对流动性较低资产或高波动时期使用更大估计，再比较多种假设下的策略。若有买卖报价数据，[执行价格示例](/zh/features/backtesting/orders-and-execution/#execution-price-and-timing)展示如何按卖价买入、按买价卖出。

## 杠杆与借贷 \[#leverage-and-borrowing]

杠杆决定持仓中有多少可以由借款支持，本身不会改变订单数量。因此，杠杆策略需要请求超过自身净值 100% 的仓位，并允许账户借入其余资金：

```python title="Hold 200% of equity in SPY, rebalanced daily"
>>> 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"`
对每个订单应用杠杆，只使用部分可用现金。手续费从自有现金支付。账户逐根跟踪债务、锁定现金和自由现金。净值降至零时，引擎不会自动强平；订单停止成交，由您决定分析中如何处理该时点。

## 借贷成本与资金费用 \[#borrowing-costs-and-funding-payments]

融资成本取决于借款金额和持仓时长。回调可读取债务，将利息作为负 `cash_earnings` 扣除，即使当天没有交易。这里积极杠杆用 500 美元借款支持 1,000 美元持仓。示例每日费率
0.1%，入场后每日收取 0.50 美元：

```python title="Charge interest on borrowed cash each day"
>>> 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 将这些现金流计入投资组合净值与收益率。

## 期货与特殊品种 \[#futures-and-unusual-instruments]

期货合约控制其报价的若干倍金额。`multiplier` 将该倍数应用于订单金额、现金流和利润，因此一份 E-mini 标普 500 合约每点对应 50 美元。价格也可为零或负数，价差和 2020 年
4
月原油期货都出现过这种情况。下方[合约乘数](#contract-multiplier)与[负价格](#negative-price)使用真实数据展示两者。合约换月不会自动处理：可以对连续序列作向后调整（back-adjustment），或用自己的信号平掉到期合约、开立下一合约。

平仓时实现损益，未平仓头寸逐根按市值计价。交易所每日将未实现利润转入现金的逐日盯市保证金，不单独建模。多数策略研究中，最终净值曲线相同，但持仓期间现金余额会与期货账户结单不同。

## 多币种 \[#multiple-currencies]

现金与价格必须使用同一货币。单一货币对应以账户货币报价，必要时倒转货币对，并同时转换订单价格数组。海外股票等持仓，应在模拟前将各价格序列换算为账户货币，这会将汇率暴露纳入各资产净值。独立币种余额、兑换成本及杠杆外汇损益换算需要自定义记账。

## 合约乘数 \[#contract-multiplier]

新增于 v2026.4.7

✅ 所有模拟入口现在都接受乘数，按固定系数缩放每个订单，便于模拟期货及其他一份合约代表多个标的单位的衍生品。

=== "示例 1：E-mini 标普 500 期货"
    ```python title="SMA crossover on E-mini S&P 500 futures"
    >>> data = vbt.YFData.pull("ES=F", start="2023", end="2024")  # (1)

    >>> fast_sma = data.run("talib_func:sma", timeperiod=10)  # (2)
    >>> 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(  # (3)
    ...     data,
    ...     entries=entries,
    ...     exits=exits,
    ...     size=1,
    ...     init_cash=500_000,
    ... )

    >>> pf_futures = vbt.PF.from_signals(  # (4)
    ...     data,
    ...     entries=entries,
    ...     exits=exits,
    ...     size=1,
    ...     multiplier=50,
    ...     init_cash=500_000,
    ... )

    >>> print(pf_stock.total_profit)
    627.25
    >>> print(pf_futures.total_profit)  # (5)
    31362.5
    >>> print(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
    ```

    1.  从 Yahoo Finance 拉取 2023 年 E-mini 标普 500 期货日线。
    2.  根据 10/30 SMA 交叉生成多头入场和离场信号。同一逻辑适用于任意品种，只有乘数改变美元金额影响。
    3.  不使用乘数模拟，相当于交易一股跟踪指数的股票，每点变动恰好对应 1 美元。
    4.  添加乘数以符合 E-mini 标普 500 实际合约规格：一份合约控制指数值的 50 倍，因此每点对应 50 美元。
    5.  利润相对股票式模拟恰好放大 50 倍。

=== "示例 2：混合期货投资组合"
    ```python title="SMA crossover on ES (x50) and NQ (x20) futures simultaneously"
    >>> data = vbt.YFData.pull(  # (1)
    ...     ["ES=F", "NQ=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 = vbt.PF.from_signals(  # (2)
    ...     data,
    ...     entries=entries,
    ...     exits=exits,
    ...     size=1,
    ...     multiplier=[50, 20],
    ...     init_cash=1_000_000,
    ... )

    >>> print(pf.total_profit)  # (3)
    symbol
    ES=F    31362.5
    NQ=F    55995.0
    dtype: float64
    ```

    1.  拉取 E-mini 标普 500（ES，50 倍）和 E-mini 纳斯达克 100（NQ，20 倍）期货日线。
    2.  以列表传入 multiplier，为每个品种指定正确合约规格。信号和策略逻辑相同，只有每点美元价值不同。
    3.  每列损益反映自身乘数，因此利润直接以美元表示，无需手动缩放。

## 负价格 \[#negative-price]

新增于 v2026.4.7

✅ 模拟引擎此前在验证时拒绝负价格，无法模拟价格确实可能低于零的品种，例如著名的 2020 年 4 月 WTI 原油事件。现在整个流程已全面支持负价格。

```python title="Hold WTI crude oil through its negative close in April 2020"
>>> data = vbt.YFData.pull("CL=F", start="2020-02-01", end="2020-07-01")  # (1)

>>> entries = pd.Series(False, index=data.index)  # (2)
>>> exits = pd.Series(False, index=data.index)
>>> entries["2020-03-03"] = True
>>> exits["2020-05-04"] = True

>>> pf = vbt.PF.from_signals(  # (3)
...     data,
...     entries=entries,
...     exits=exits,
...     size=1,
...     multiplier=1000,
...     init_cash=200_000,
... )

>>> print(data.close[data.close < 0])  # (4)
Date
2020-04-20 00:00:00-04:00   -37.630001
Name: Close, dtype: float64

>>> print(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.567825

>>> print(pf.value[["2020-03-03", "2020-03-09", "2020-04-20", "2020-05-04"]].round(2))  # (5)
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
```

1.  拉取 WTI 原油期货日线。
2.  3 月 3 日做多，5 月 4 日离场，持仓跨过负结算价格。
3.  一份 WTI 合约控制 1,000 桶。
4.  确认数据包含负结算价。
5.  负结算当日投资组合净值大幅下降，随后随价格反弹恢复。

## 杠杆 \[#leverage]

新增于 1.9.0

✅ 杠杆现已成为投资组合模拟的一部分。支持两种模式：`lazy`（现金不足时才启用）和
`eager`（使用部分可用现金，同时启用杠杆）。可逐笔订单设置杠杆，还能自动确定满足订单要求的最佳杠杆值！🏋️

```python title="Explore how leverage affects the equity curve in a random portfolio"
>>> 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()
```

2020 至 2021 年 BTC-USD 随机投资组合在四种杠杆水平下的净值曲线. [Figure data (JSON)](/assets/figures/features/portfolio/leverage.560bae250dca.json)
