1. 这节究竟解决什么实际问题?¶
5.8 战术资产配置与动量轮动(TAA) 的具体任务是:按状态或动量调整资产权重,并计入再平衡成本。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
理解收益率、协方差、波动率和矩阵的基本含义。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
固定多资产价格样本用于演示;不把少量资产、单一时期的最优权重当作配置建议。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 5.8 战术资产配置与动量轮动(TAA) 的输入、计算步骤、输出和局限;尤其能说明:按状态或动量调整资产权重,并计入再平衡成本。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
组合配置同时考虑单项资产风险和资产之间的协方差,目标是控制整体风险暴露。
组合收益是权重加权和;风险由协方差矩阵决定,权重之和、杠杆和做空限制必须写清楚。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
优化器可以求满足约束的数值解;它不会告诉你输入的预期收益是否可信。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“按状态或动量调整资产权重,并计入再平衡成本。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
协方差矩阵不可逆、权重未归一化、用样本内最优权重宣称可投资、忽略再平衡成本。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
均值估计噪声极大;需要样本外比较、参数敏感性和等权等简单基线。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/05_portfolio/08_dynamic_asset_allocation_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/05_portfolio/08_dynamic_asset_allocation_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:Portfolio Selection(Markowitz, 1952)。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
下一节是 09_regime_switching。本节的概念会成为理解它的输入,而不是孤立的名词。
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. SAA vs TAA¶
| 策略 | 定义 | 适用场景 |
|---|---|---|
| SAA(战略配置) | 长期固定权重,如60/40股债 | 长期资产配置 |
| TAA(战术配置) | 根据市场信号动态调整 | 择时增强收益 |
TAA 的核心信号:趋势(动量)、估值、宏观因子。
# 模拟 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 资产¶
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. 再平衡成本考量¶
动态配置每月换仓会产生交易成本。需要评估换手率。
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