1. 这节究竟解决什么实际问题?¶
4.8 市场冲击与滑点模型 (Market Impact & Slippage) 的具体任务是:把滑点、冲击和成交量限制转成回测中可量化的保守成本。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
理解收益率、仓位、交易成本与基本技术指标。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
默认固定历史样本。历史数据只能说明过去在既定假设下发生了什么,不能承诺未来收益。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 4.8 市场冲击与滑点模型 (Market Impact & Slippage) 的输入、计算步骤、输出和局限;尤其能说明:把滑点、冲击和成交量限制转成回测中可量化的保守成本。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
回测按照明确的成交和成本假设重放历史数据。它不能完整复现未来流动性、订单竞争和系统故障。
策略收益 = 滞后仓位 × 资产收益 − 换手 × 成本;每一项都要有明确单位。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
回测框架替你管理订单和记录,不会自动判断数据、成本或策略假设是否合理。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“把滑点、冲击和成交量限制转成回测中可量化的保守成本。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
信号未滞后、把手续费只扣一次、忽略空仓收益、年化频率错配、将净值曲线当显著性证明。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
检查未来函数、幸存者偏差、重复测试集调参、成交可得性和失败区间;必须报告基线。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/04_backtesting/08_market_impact_slippage_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/04_backtesting/08_market_impact_slippage_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:Advances in Financial Machine Learning(Lopez de Prado)。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
下一节是 09_long_short_equity。本节的概念会成为理解它的输入,而不是孤立的名词。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
print('Libraries loaded')
Libraries loaded
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
1. 滑点对 Alpha 的侵蚀¶
量化策略在回测中表现往往远好于实盘,交易摩擦是主因。
| 成本类型 | 说明 | 典型量级 |
|---|---|---|
| 价差 (Bid-Ask Spread) | 点价差 | 小盘 0.1~0.5%, 大盘 0.01~0.05% |
| 市场冲击 (Market Impact) | 下单导致价格不利移动 | 与参与率成正比 |
| 手续费 (Commission) | 万二到万六 | 固定比例 |
| 执行延迟 (Latency) | 以日频而言可忽略 | ~0 |
# 演示:滑点如何吃掉 Alpha
num_trades_per_year = 504 # 每天 2 次交易信号
annual_alpha = 0.10 # 假设策略 Alpha 10%
slippage_rates = [0.0, 0.001, 0.002, 0.005, 0.010]
results = []
for slip in slippage_rates:
total_cost = slip * num_trades_per_year * 2 # 买卖两方向
net = annual_alpha - total_cost
results.append({'滑点率': f'{slip:.1%}', '年化滑点成本': f'{total_cost:.2%}', '净 Alpha': f'{net:.2%}'})
df = pd.DataFrame(results)
print('年化 Alpha=10%,每年交易 504 次,滑点侵蚀效应:')
print(df.to_string(index=False))
年化 Alpha=10%,每年交易 504 次,滑点侵蚀效应: 滑点率 年化滑点成本 净 Alpha 0.0% 0.00% 10.00% 0.1% 100.80% -90.80% 0.2% 201.60% -191.60% 0.5% 504.00% -494.00% 1.0% 1008.00% -998.00%
2. 市场冲击模型:平方根参与率模型¶
$$MI = k \cdot \sigma_D \cdot \sqrt{\frac{Q}{V}}$$
- $MI$: 市场冲击(%)
- $k$: 系数(约 0.1)
- $\sigma_D$: 日内收益率波动率
- $Q$: 下单量(股)
- $V$: 日均成交量(股)
daily_vol = 0.015
k = 0.1
participation = np.linspace(0, 0.20, 100)
impact = k * daily_vol * np.sqrt(participation)
plt.figure(figsize=(10, 5))
plt.plot(participation * 100, impact * 100, color='red', linewidth=2)
plt.fill_between(participation * 100, impact * 100, alpha=0.2, color='red')
plt.axvline(5, color='green', linestyle='--', label='5% 参与率上限')
plt.xlabel('参与率 Q/V (%)')
plt.ylabel('市场冲击成本 (%)')
plt.title('市场冲击成本 vs 参与率(日内波动率=1.5%, k=0.1)')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
for p in [0.01, 0.05, 0.10, 0.20]:
print(f'参与率 {p:.0%}: 冲击成本 = {k * daily_vol * np.sqrt(p) * 100:.4f}%')
参与率 1%: 冲击成本 = 0.0150% 参与率 5%: 冲击成本 = 0.0335% 参与率 10%: 冲击成本 = 0.0474% 参与率 20%: 冲击成本 = 0.0671%
3. VWAP 执行算法¶
VWAP (Volume-Weighted Average Price) 按市场每日成交量分布分批下单, 避免在低流动性时段集中下单导致过大冲击。
hours = [9.5, 10.0, 10.5, 11.0, 11.5, 12.0, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5, 15.9]
vwap_weights = [0.14, 0.10, 0.08, 0.07, 0.06, 0.06, 0.07, 0.08, 0.08, 0.09, 0.10, 0.04, 0.03]
total_order = 100_000
batches = [int(w * total_order) for w in vwap_weights]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
ax1.bar(hours, [w*100 for w in vwap_weights], color='steelblue', alpha=0.7, width=0.4)
ax1.set_title('市场常见时内成交量分布 (VWAP 权重)')
ax1.set_xlabel('时间 (时)')
ax1.set_ylabel('占全天成交量比例 (%)')
ax1.grid(alpha=0.3)
ax2.bar(hours, batches, color='darkorange', alpha=0.7, width=0.4)
ax2.set_title(f'VWAP 分批下单 (总计 {total_order:,} 股)')
ax2.set_xlabel('时间 (时)')
ax2.set_ylabel('分批下单量 (股)')
ax2.grid(alpha=0.3)
plt.suptitle('VWAP 执行算法:按成交量分布分批减少市场冲击', fontsize=13)
plt.tight_layout()
plt.show()
练习¶
- 尝试将滑点率设定为延迟一天(今天信号明天开盘价成交)的模型,重新计算双均线策略的年化收益。
- 根据上述市场冲击公式,对南海股市的常见值小盘股进行天内参与率测度(需要实际成交量数据)。
- 模拟 5 天尘基策略的流动性(ADV 10% 下单),对比小/中/大盘股的冲击成本差异。
下一节 → ../05_portfolio/03_capm_apt.ipynb