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. SAA vs TAA¶
| 策略 | 定义 | 适用场景 |
|---|---|---|
| SAA(战略配置) | 长期固定权重,如60/40股债 | 长期资产配置 |
| TAA(战术配置) | 根据市场信号动态调整 | 择时增强收益 |
TAA 的核心信号:趋势(动量)、估值、宏观因子。
In [3]:
Copied!
# 模拟 5 类资产的月度收益率:股票、债券、黄金、商品、现金
np.random.seed(42)
n_months = 120
assets = ['股票', '债券', '黄金', '商品', '现金']
# 各资产年化收益率和波动率
params = {'股票':(0.10, 0.18), '债券':(0.04, 0.06), '黄金':(0.06, 0.15), '商品':(0.05, 0.20), '现金':(0.02, 0.005)}
returns = pd.DataFrame({a: np.random.normal(p[0]/12, p[1]/np.sqrt(12), n_months) for a,p in params.items()})
print(f'模拟资产月度收益矩阵: {returns.shape}')
# 模拟 5 类资产的月度收益率:股票、债券、黄金、商品、现金
np.random.seed(42)
n_months = 120
assets = ['股票', '债券', '黄金', '商品', '现金']
# 各资产年化收益率和波动率
params = {'股票':(0.10, 0.18), '债券':(0.04, 0.06), '黄金':(0.06, 0.15), '商品':(0.05, 0.20), '现金':(0.02, 0.005)}
returns = pd.DataFrame({a: np.random.normal(p[0]/12, p[1]/np.sqrt(12), n_months) for a,p in params.items()})
print(f'模拟资产月度收益矩阵: {returns.shape}')
模拟资产月度收益矩阵: (120, 5)
2. 动量轮动策略:以 12 月收益率为信号持有排名前 2 资产¶
In [4]:
Copied!
lookback = 12
weights_TAA = pd.DataFrame(0.0, index=returns.index, columns=assets)
for t in range(lookback, n_months):
mom_12m = (1 + returns.iloc[t-lookback:t]).prod() - 1
top2 = mom_12m.nlargest(2).index
weights_TAA.iloc[t][top2] = 0.5
# 等权买入持有
weights_SAA = pd.DataFrame({a: 1/5 for a in assets}, index=returns.index)
ret_TAA = (weights_TAA * returns).sum(axis=1)
ret_SAA = (weights_SAA * returns).sum(axis=1)
cum_TAA = (1 + ret_TAA).cumprod()
cum_SAA = (1 + ret_SAA).cumprod()
plt.figure(figsize=(12, 5))
plt.plot(cum_TAA, 'b-', lw=2, label='TAA 动量轮动')
plt.plot(cum_SAA, color='gray', lw=2, linestyle='--', label='等权买入持有 (SAA)')
plt.title('战术动量轮动 vs 等权 SAA(120 个月)')
plt.ylabel('累积净值'); plt.legend(); plt.grid(alpha=0.3); plt.show()
def sharpe(r): return r.mean() / r.std() * np.sqrt(12)
print(f'TAA Sharpe: {sharpe(ret_TAA.iloc[lookback:]):.3f}')
print(f'SAA Sharpe: {sharpe(ret_SAA.iloc[lookback:]):.3f}')
lookback = 12
weights_TAA = pd.DataFrame(0.0, index=returns.index, columns=assets)
for t in range(lookback, n_months):
mom_12m = (1 + returns.iloc[t-lookback:t]).prod() - 1
top2 = mom_12m.nlargest(2).index
weights_TAA.iloc[t][top2] = 0.5
# 等权买入持有
weights_SAA = pd.DataFrame({a: 1/5 for a in assets}, index=returns.index)
ret_TAA = (weights_TAA * returns).sum(axis=1)
ret_SAA = (weights_SAA * returns).sum(axis=1)
cum_TAA = (1 + ret_TAA).cumprod()
cum_SAA = (1 + ret_SAA).cumprod()
plt.figure(figsize=(12, 5))
plt.plot(cum_TAA, 'b-', lw=2, label='TAA 动量轮动')
plt.plot(cum_SAA, color='gray', lw=2, linestyle='--', label='等权买入持有 (SAA)')
plt.title('战术动量轮动 vs 等权 SAA(120 个月)')
plt.ylabel('累积净值'); plt.legend(); plt.grid(alpha=0.3); plt.show()
def sharpe(r): return r.mean() / r.std() * np.sqrt(12)
print(f'TAA Sharpe: {sharpe(ret_TAA.iloc[lookback:]):.3f}')
print(f'SAA Sharpe: {sharpe(ret_SAA.iloc[lookback:]):.3f}')
C:\Users\DELL\AppData\Local\Temp\ipykernel_26088\3150449744.py:7: ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment. Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write). Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step. See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment weights_TAA.iloc[t][top2] = 0.5
TAA Sharpe: nan SAA Sharpe: 0.822
C:\Users\DELL\AppData\Local\Temp\ipykernel_26088\3150449744.py:23: RuntimeWarning: invalid value encountered in scalar divide def sharpe(r): return r.mean() / r.std() * np.sqrt(12)
3. 再平衡成本考量¶
动态配置每月换仓会产生交易成本。需要评估换手率。
In [5]:
Copied!
turnover = weights_TAA.diff().abs().sum(axis=1).mean()
print(f'平均月换手率: {turnover:.2%}')
print(f'年化换手率: {turnover*12:.2%}')
# 扣除0.1%单边交易成本后的净收益
tcost = 0.001 # 0.1% 单边
monthly_tcost = turnover * tcost * 2 # 双边
ret_TAA_net = ret_TAA - monthly_tcost
cum_net = (1 + ret_TAA_net).cumprod()
plt.figure(figsize=(12, 4))
plt.plot(cum_TAA, 'b-', lw=2, label='TAA 税前')
plt.plot(cum_net, 'green', lw=2, linestyle='--', label='TAA 扣除0.1%成本后')
plt.title('TAA:扣除交易成本前后对比'); plt.legend(); plt.grid(alpha=0.3); plt.show()
turnover = weights_TAA.diff().abs().sum(axis=1).mean()
print(f'平均月换手率: {turnover:.2%}')
print(f'年化换手率: {turnover*12:.2%}')
# 扣除0.1%单边交易成本后的净收益
tcost = 0.001 # 0.1% 单边
monthly_tcost = turnover * tcost * 2 # 双边
ret_TAA_net = ret_TAA - monthly_tcost
cum_net = (1 + ret_TAA_net).cumprod()
plt.figure(figsize=(12, 4))
plt.plot(cum_TAA, 'b-', lw=2, label='TAA 税前')
plt.plot(cum_net, 'green', lw=2, linestyle='--', label='TAA 扣除0.1%成本后')
plt.title('TAA:扣除交易成本前后对比'); plt.legend(); plt.grid(alpha=0.3); plt.show()
平均月换手率: 0.00% 年化换手率: 0.00%
🎯 练习¶
- 将动量信号改为 1 个月和 3 个月反转信号,观察均值回归型 TAA 的表现。
- 加入宏观过滤器:当 12 月动量信号为负时,全部切换为现金(防御模式),对比结果。
- 研究 Faber (2007) 的 GTAA 策略(10 个月均线),复现其在 5 类资产上的历史胜率。
下一节 → 09_regime_switching.ipynb
In [ ]:
Copied!