In [1]:
Copied!
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
print('Libraries loaded')
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
print('Libraries loaded')
Libraries loaded
In [2]:
Copied!
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
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 |
In [3]:
Copied!
# 演示:滑点如何吃掉 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
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$: 日均成交量(股)
In [4]:
Copied!
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}%')
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) 按市场每日成交量分布分批下单, 避免在低流动性时段集中下单导致过大冲击。
In [5]:
Copied!
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()
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
In [ ]:
Copied!