以下是一个强大的 Python 实现,展示了如何在自定义的 Pandas 回测系统中,对基于成交量的动态滑点进行建模。

Python 编写动态滑点模型

与其对每笔交易应用一成不变且不切实际的固定惩罚,这段代码使用了一种非线性市场冲击模型(通常被称为“平方根定律”变体)。该模型会根据两个核心变量动态调整执行摩擦:

  1. 相对于市场流动性的订单规模: 订单规模越大,吃掉的订单簿深度就越深,遭受的滑点惩罚也就越严重。
  2. 历史波动率: 在市场剧烈波动期间,由于买卖价差自然拉大,滑点也会随之增加。

Python

import numpy as np
import pandas as pd


def apply_dynamic_slippage(
    df: pd.DataFrame,
    order_size_col: str,
    market_vol_col: str,
    market_return_col: str,
    participation_rate: float = 0.10,
    impact_exponent: float = 0.5,
) -> pd.DataFrame:
    """模拟基于成交量和波动率驱动的非线性市场冲击模型,

    以便在回测执行中进行精准的惩罚。

    参数:
    -----------
    df : pd.DataFrame
        回测时间序列的 DataFrame。
    order_size_col : str
        代表策略在该 K 线(Bar)上计划交易的股票/单位数量的列名。(正数代表买入,负数代表卖出)。
    market_vol_col : str
        该 K 线上的历史市场总成交量的列名。
    market_return_col : str
        代表资产 volatility 的列名(例如:对数收益率的滚动标准差,或滚动 ATR 百分比)。
    participation_rate : float, 默认 0.10
        缩放因子,决定订单成交量对订单簿价差的冲击激进程度。
    impact_exponent : float, 默认 0.5
        平方根幂次因子。微观结构经验数据表明,市场冲击大致与订单规模的平方根(0.5)成正比。

    返回:
    --------
    pd.DataFrame
        增加了 'slippage_pct'(滑点百分比)和 'execution_price'(执行价格)列的 DataFrame。
    """
    # 创建深拷贝以防止修改原始 DataFrame
    backtest_df = df.copy()

    # 计算成交量参与率: (你的订单规模 / 该 K 线市场总成交量)
    # 使用绝对值,因为买单和卖单都会消耗流动性
    backtest_df["volume_participation"] = backtest_df[order_size_col].abs() / (
        backtest_df[market_vol_col] + 1e-8
    )

    # 非线性市场冲击公式:
    # 滑点 % = 参与率 * (成交量参与率)^冲击指数 * 市场波动率
    backtest_df["slippage_pct"] = (
        participation_rate
        * (backtest_df["volume_participation"] ** impact_exponent)
        * backtest_df[market_return_col]
    )

    # 用 0 填充 NaN 值(适用于策略未发出任何订单的 K 线)
    backtest_df["slippage_pct"] = backtest_df["slippage_pct"].fillna(0.0)

    # 应用执行方向的约束条件:
    # 买单(正订单)的成交价格要高于(HIGHER)打印出的市场价。
    # 卖单(负订单)的成交价格要低于(LOWER)打印出的市场价。
    backtest_df["execution_price"] = np.where(
        backtest_df[order_size_col] > 0,
        backtest_df["close"] * (1 + backtest_df["slippage_pct"]),  # 买入惩罚
        np.where(
            backtest_df[order_size_col] < 0,
            backtest_df["close"] * (1 - backtest_df["slippage_pct"]),  # 卖出惩罚
            backtest_df["close"],  # 未发出交易信号
        ),
    )

    return backtest_df


# ==========================================
# 使用示例及样本数据模拟
# ==========================================
if __name__ == "__main__":
    # 生成模拟的 15 分钟 K 线市场数据
    np.random.seed(42)
    dates = pd.date_range(start="2026-07-10 09:30", periods=5, freq="15min")

    data = {
        "close": [150.00, 150.50, 152.00, 151.20, 151.80],
        "market_volume": [
            50000,
            12000,
            85000,
            22000,
            45000,
        ],  # 注意:第 2 根 K 线流动性极低
        "rolling_volatility": [
            0.012,
            0.015,
            0.025,
            0.018,
            0.011,
        ],  # 第 3 根 K 线波动率极高
        "strategy_order": [
            2500,
            2500,
            -5000,
            0,
            100,
        ],  # 正数 = 买入,负数 = 卖出
    }

    raw_backtest = pd.DataFrame(data, index=dates)

    # 通过防御性滑点引擎进行处理
    realistic_backtest = apply_dynamic_slippage(
        df=raw_backtest,
        order_size_col="strategy_order",
        market_vol_col="market_volume",
        market_return_col="rolling_volatility",
    )

    # 打印专注于执行偏差的结果
    display_cols = [
        "close",
        "strategy_order",
        "volume_participation",
        "slippage_pct",
        "execution_price",
    ]
    print(realistic_backtest[display_cols].to_string())

代码输出逻辑详解

  • 第 1 根 K 线(标准买入): 在 50,000 的市场总成交量中买入 2,500 股,产生了 5% 的成交量参与率。最终的成交价格相比于 150.00 美元的基准价格轻微上滑至 150.40 美元。
  • 第 2 根 K 线(低流动性陷阱): 在流动性枯竭、市场总成交量仅有 12,000 股的情况下,买入完全相同的 2,500 股,导致成交量参与率飙升至接近 21%。尽管当时市场价格仅上涨了 0.50 美元,但由于缺乏可用流动性,你激进的订单属性导致执行价格被进一步大幅推高。
  • 第 3 根 K 线(高波动率下的流动性横扫): 在高波动率(0.025)的窗口期大举卖出 5,000 股。这加剧了微观结构上的业绩损耗,迫使最终的成交价被严重惩罚,远低于历史基准收盘价 152.00 美元。

如果您正在寻找专业的定制交易软件开发服务,欢迎联系我们。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理