好的,我们来深入总结一下这份海通证券关于基于回归树的因子择时模型的研究报告,并提供相应的Python策略实现思路。
报告核心内容总结
这份报告探讨了利用回归树模型进行因子择时的新方法,旨在克服传统线性模型在捕捉因子收益与择时变量之间复杂非线性关系上的局限性。
-
问题背景:
- 传统因子择时模型常假设择时变量与因子未来收益存在线性关系,这在现实中往往不成立。
- 近年来因子波动性增加,使得因子择时愈发重要。
-
核心方法:回归树 (Regression Tree):
- 优势: 无需预设函数形式,能自动捕捉择时变量与因子收益间的非线性、交互作用。
- 应用: 将历史数据根据不同的市场环境(由择时变量刻画)进行划分,统计各环境下因子的平均表现,从而预测未来因子收益。
- 场景化: 回归树提供了一种“情景化”的因子择时思路,帮助投资者理解不同市场状态下的因子表现。
- 变量: 择时变量库可包括宏观(利率、利差)、市场(波动、成交、风险偏好)、因子本身(拥挤度、估值价差、长期收益)等指标。
- 因子收益: 报告中使用带约束的市值加权回归计算的因子月度溢价。
-
模型变种与策略:
- 基础回归树择时: 直接使用历史数据(如5年、10年)训练回归树。报告以市值因子为例,发现对数市场波动率是有效的划分变量,在低波动时小盘股(正因子收益)表现更好,高波动时则相反(使用特定阈值如-2.06)。
- 衰减加权回归树: 引入时间衰减(如指数衰减),赋予近期数据更高权重,使模型更快适应市场变化。这可能导致更复杂的树结构,并可能提升近期拟合度。
- 因子方向性择时:
- 每月滚动训练回归树(如用过去5年数据)。
- 用当前择时变量输入训练好的树,预测下月因子收益方向(正/负)。
- 根据预测方向决定因子暴露(如+1或-1单位)。
- 效果: 相比长期持有,波动和回撤可能减小(尤其在因子转向时,如2017年市值风格切换),但在因子趋势强劲时可能损失收益(如2015、2016年)。结论是纯粹替换可能不优。
- 防御性因子择时 (Defensive Factor Timing):
- 核心思想: 结合两种不同逻辑的模型(如因子动量模型和回归树模型),只有当两者预测方向一致时才进行暴露,否则空仓(0暴露),以提高稳健性,控制不确定性。
- 流程:
- 获取因子动量模型的预测方向。
- 获取回归树(标准或衰减)模型的预测方向。
- 若方向一致,则按该方向暴露1单位敞口。
- 若方向不一致,则当期因子敞口设为0。
- 效果: 相比单一模型(动量或回归树),防御性策略通常更稳健,尤其在市场环境复杂、模型判断不一致的时期(如2017年),能有效控制风险和回撤,代价是可能牺牲部分强趋势行情下的收益。报告针对市值、换手率、反转、波动、盈利等因子进行了回测验证。
- 多因子模型应用:
- 将防御性择时思路应用于多因子模型的因子权重分配。
- 判断动量模型和回归树模型的预测方向是否一致。
- 若不一致,该因子当期预期收益(用于加权)设为0。
- 若一致,则使用回归树预测的**因子收益大小(幅度)**作为该因子的最终预期收益。
- 基于调整后的各因子预期收益,进行因子加权(如回归法)和组合构建(如选Top N股票)。
- 效果: 多因子组合应用防御性择时后,虽然整体年化超额收益可能略有下降,但在特定年份(如2017年)表现得到显著改善,提升了策略的稳定性。
-
结论与展望:
- 回归树为因子择时提供了直观、非线性的解决方案。
- 纯粹的方向性择时灵活性有余但稳健性不足。
- 防御性因子择时结合了不同模型的优点,提高了策略的稳健性。
- 可利用集成学习方法(如Averaging, Boosting/如Random Forest, Gradient Boosting Trees)进一步提升回归树模型的稳定性和预测能力。
-
风险提示: 市场系统性风险、流动性风险、政策风险等。
目录
- 引言
- 核心方法论:回归树与因子择时
- 单因子择时策略
3.1. 基础回归树择时
3.2. 衰减加权回归树择时
3.3. 因子方向性择时 - 防御性因子择时策略
4.1. 策略逻辑
4.2. 单因子回测效果 - 多因子模型中的应用
- 总结与展望
- Python 代码示例
7.1. 依赖库导入
7.2. 模拟数据生成
7.3. 因子动量计算
7.4. 回归树择时函数
7.5. 方向性择时模拟
7.6. 防御性择时模拟
7.7. 结果展示与分析
Python 代码示例
我们将使用 pandas
进行数据处理,scikit-learn
实现回归树模型,并用模拟数据演示方向性择时和防御性因子择时的核心逻辑。
# 7.1. 依赖库导入
import pandas as pd
import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import train_test_split # 可用于更复杂的交叉验证
import matplotlib.pyplot as plt
# 7.2. 模拟数据生成
# 模拟未来120个月的数据
np.random.seed(42)
n_periods = 120
dates = pd.date_range(start='2010-01-01', periods=n_periods, freq='M')
# 模拟一个择时变量(例如市场波动率),假设其影响因子表现
# 假设波动率低于-1.5时,因子表现差;高于-1.5时,因子表现好 (非线性关系)
timing_variable = np.random.normal(0, 1.5, n_periods)
# 模拟因子月度收益(例如市值因子,正代表小盘优)
# 基础收益 + 波动率影响 + 随机噪声
factor_returns = np.zeros(n_periods)
factor_returns[timing_variable <= -1.5] = np.random.normal(-0.015, 0.03, size=sum(timing_variable <= -1.5)) # 低波环境因子表现差
factor_returns[timing_variable > -1.5] = np.random.normal(0.010, 0.04, size=sum(timing_variable > -1.5)) # 高波环境因子表现好
factor_returns += np.random.normal(0, 0.01, n_periods) # 叠加噪声
data = pd.DataFrame({
'FactorReturn': factor_returns,
'TimingVar': timing_variable
}, index=dates)
# 将择时变量和因子收益滞后,用于预测
# 用 t-1 月的 TimingVar 预测 t 月的 FactorReturn
data['TimingVar_Lag1'] = data['TimingVar'].shift(1)
data['FactorReturn_T+1'] = data['FactorReturn'].shift(-1) # t月的因子收益是我们要预测的目标
# 删除因滞后产生的NaN
data = data.dropna()
print("模拟数据预览:")
print(data.head())
# 7.3. 因子动量计算 (示例: 12个月动量)
momentum_window = 12
data['FactorMomentum'] = data['FactorReturn'].rolling(momentum_window).mean().shift(1) # 用过去12个月均值作为动量信号(同样滞后)
# 再次处理NaN
data = data.dropna()
print("\n加入动量后的数据预览:")
print(data.head())
# 7.4. 回归树择时函数
def train_regression_tree(X_train, y_train, sample_weights=None, max_depth=3):
"""训练回归树模型"""
# 实际应用中 max_depth 等超参数需要仔细选择或交叉验证
# 报告中提到用 CART 和 时间序列交叉验证
model = DecisionTreeRegressor(max_depth=max_depth, random_state=42)
model.fit(X_train, y_train, sample_weight=sample_weights)
return model
# 7.5. 方向性择时模拟 (滚动预测)
rolling_window = 60 # 假设使用过去60个月 (5年) 的数据进行滚动训练
directional_signals = pd.Series(index=data.index, dtype=float)
for i in range(rolling_window, len(data)):
train_start_idx = i - rolling_window
train_end_idx = i
# 准备训练数据 (t-1的变量预测t的收益)
X_train = data[['TimingVar_Lag1']].iloc[train_start_idx:train_end_idx]
y_train = data['FactorReturn'].iloc[train_start_idx:train_end_idx] # 预测当期收益
# 训练模型 (不带衰减)
tree_model = train_regression_tree(X_train, y_train, max_depth=1) # 简化为1层深度,类似报告图1
# 准备预测数据 (用当前的t-1变量预测t的收益)
X_predict = data[['TimingVar_Lag1']].iloc[[i]]
# 预测因子收益
predicted_return = tree_model.predict(X_predict)[0]
# 生成方向信号
directional_signals.iloc[i] = np.sign(predicted_return)
data['DirectionalSignal'] = directional_signals
# 7.6. 防御性因子择时模拟
# 结合因子动量信号和回归树信号
defensive_signals = pd.Series(index=data.index, dtype=float)
# 计算动量信号方向
data['MomentumSignalSign'] = np.sign(data['FactorMomentum'])
for i in range(rolling_window, len(data)):
# 获取已计算的回归树信号方向
tree_signal_sign = data['DirectionalSignal'].iloc[i]
# 获取动量信号方向
momentum_signal_sign = data['MomentumSignalSign'].iloc[i]
# 判断信号是否一致
if pd.notna(tree_signal_sign) and pd.notna(momentum_signal_sign) and tree_signal_sign != 0 and momentum_signal_sign != 0:
if tree_signal_sign == momentum_signal_sign:
defensive_signals.iloc[i] = tree_signal_sign # 方向一致,采用该方向
else:
defensive_signals.iloc[i] = 0 # 方向不一致,空仓
else:
defensive_signals.iloc[i] = 0 # 如果任一信号无效或为0,也空仓
data['DefensiveSignal'] = defensive_signals
# 7.7. 结果展示与分析
# 计算各策略的表现 (简单模拟,未考虑交易成本)
# 注意: 信号是基于 t-1 信息在 t 时刻产生的,应用于 t 到 t+1 的收益
# 因此,计算 PnL 时要用信号乘以 *未来* 的因子收益 FactorReturn_T+1
# 或者,将信号 shift(1) 再乘以 FactorReturn
data['BuyAndHold_PnL'] = data['FactorReturn'] # 简单持有因子本身
data['Momentum_PnL'] = data['MomentumSignalSign'].shift(1) * data['FactorReturn']
data['Directional_PnL'] = data['DirectionalSignal'].shift(1) * data['FactorReturn']
data['Defensive_PnL'] = data['DefensiveSignal'].shift(1) * data['FactorReturn']
# 清理策略开始时的 NaN PnL
data = data.iloc[rolling_window:] # 从第一个有效信号开始评估
data = data.fillna(0) # 将 PnL 中的 NaN 替换为 0 (代表无操作)
# 计算累计收益
cumulative_returns = data[['BuyAndHold_PnL', 'Momentum_PnL', 'Directional_PnL', 'Defensive_PnL']].cumsum()
# 绘制累计收益曲线
plt.figure(figsize=(12, 7))
plt.plot(cumulative_returns.index, cumulative_returns['BuyAndHold_PnL'], label='Buy and Hold Factor')
plt.plot(cumulative_returns.index, cumulative_returns['Momentum_PnL'], label='Factor Momentum (12M)')
plt.plot(cumulative_returns.index, cumulative_returns['Directional_PnL'], label='Directional Timing (Regression Tree)')
plt.plot(cumulative_returns.index, cumulative_returns['Defensive_PnL'], label='Defensive Timing (Momentum + Tree)')
plt.title('因子择时策略累计收益 (模拟数据)')
plt.xlabel('日期')
plt.ylabel('累计收益')
plt.legend()
plt.grid(True)
plt.show()
# 计算年化收益和波动率等指标 (示例)
def calculate_performance(pnl_series, periods_per_year=12):
"""计算简单性能指标"""
total_return = pnl_series.sum()
avg_return = pnl_series.mean()
std_dev = pnl_series.std()
sharpe_ratio = (avg_return / std_dev) * np.sqrt(periods_per_year) if std_dev != 0 else 0
annualized_return = avg_return * periods_per_year
annualized_vol = std_dev * np.sqrt(periods_per_year)
return {
'年化收益': annualized_return,
'年化波动': annualized_vol,
'夏普比率': sharpe_ratio,
'总收益': total_return
}
performance = {}
performance['买入持有'] = calculate_performance(data['BuyAndHold_PnL'])
performance['因子动量'] = calculate_performance(data['Momentum_PnL'])
performance['方向性择时'] = calculate_performance(data['Directional_PnL'])
performance['防御性择时'] = calculate_performance(data['Defensive_PnL'])
performance_df = pd.DataFrame(performance).T
print("\n策略表现指标:")
print(performance_df)
# 注意:
# 1. 衰减加权回归树: 在 `model.fit()` 时传入 `sample_weight` 参数。需要定义一个衰减函数(如指数衰减)来计算每个样本的权重。
# 2. 多因子应用: 代码未实现多因子权重部分,这需要更复杂的设置,包括多个因子的数据、因子收益预测幅度的使用、以及组合构建逻辑。
# 3. 超参数选择: 回归树的深度 (`max_depth`)、最小叶片样本数等对结果影响很大,实际应用中需要通过交叉验证(特别是时间序列交叉验证)来选择。
# 4. 择时变量: 示例只用了一个模拟变量,实际中可以输入多个择时变量 `X_train = data[['Var1', 'Var2', ...]]`。
# 5. 真实数据: 使用真实数据时,需要处理数据清洗、缺失值、标准化等问题。
# 6. 性能评估: 示例仅计算了基本指标,实际评估应包括最大回撤、胜率、信息比率等,并考虑交易成本。
这份总结和代码示例涵盖了报告的主要思想和核心策略的实现框架。请注意,代码使用的是模拟数据,旨在说明方法和流程,实际应用需要基于真实数据进行严谨的回测和参数调优。