1.引言
招商证券发布的这篇研究报告《AI系列研究之一:端到端的动态Alpha模型》探讨了一种基于深度神经网络的动态Alpha因子模型,旨在解决传统线性Alpha模型的局限性。这项研究不仅展示了机器学习在量化金融领域的应用,也为投资决策提供了新的思路和方法。本文将详细分析报告中提出的模型架构、实验设计、创新点以及实际效果。
2.传统因子投资框架及其局限性
2.1 传统多因子Alpha模型的构建流程
传统的多因子Alpha模型构建一般包括以下步骤:
- 单因子研究与筛选
- 因子预处理(异常值处理、标准化、市值行业中性化)
- 大类因子合成
- 组合优化
- 回测与实盘检验
2.2 传统线性因子模型的局限性
报告指出传统线性因子模型存在以下几个主要问题:
1.线性结构假设不准确:无套利条件中随机折现因子的线性结构在学术界尚存争议。
2.模型处理复杂性:线性模型中残差的时序和截面相关性以及残差的异方差性需要特殊手段处理。
3.多头策略日趋拥挤:随着量化策略资金规模扩大,常见因子的多头收益持续下降。
4.量价类因子的负Alpha属性显著:量价类因子的负Alpha属性明显,正Alpha相对较弱。
5.策略趋同问题:因子合成及组合优化方法相似,导致市场策略趋同。
报告以BP因子和AR因子为例,展示了这些问题的具体表现:
- BP因子的多空净值在2011-2023年间呈现下降趋势
- AR因子的多头端呈现拥挤现象,9-10分组收益率单调性消失
- 空头端收益率显著高于多头端收益率
这些问题促使研究者引入非线性方法来改进Alpha模型。
3.非线性Alpha模型的设计与实现
3.1 模型设计思路
报告提出的非线性Alpha模型主要考虑三个关键问题:
- 如何学习原始因子的非线性表示
- 如何获得收益率预测性能最好的因子
- 如何降低所学习到的因子之间的线性和非线性交互作用
3.2 基准线性网络与非线性网络架构
3.2.1 线性基准网络
线性网络结构保持与传统Alpha框架基本一致,包括原始因子组合、大类因子合成及收益率预测部分。
线性网络在测试期间(2016-2023年)表现如下:
- 平均RankIC: 10.12%
- ICIR(未年化): 1.13
- 多头年化收益率: 11.34%
- 空头年化收益率: -28.4%
值得注意的是,IC的贡献主要来自于空头端。
3.2.2 非线性Alpha网络
非线性网络使用多层感知机(MLP)构建,结构与线性网络类似,但引入了非线性激活函数。主要结构包括输入层、隐藏层和输出层。
关于隐藏层的设计,报告指出:
- 隐藏层为0时,神经网络只能表示线性可分的函数
- 隐藏层为2时,可以拟合任何一个有限空间到另一个有限空间的连续映射
- 隐藏层大于3时,额外的隐藏层可以学习复杂的特征描述
3.3 模型优化创新点
3.3.1 因子正交化正则
为了降低因子之间的相关性,报告引入了正交惩罚机制,使得网络学习到的大类因子保持低相关性。这种方法借鉴了传统Alpha模型中细分因子到大类因子分步合成的思路。
3.3.2 优化目标的改进
报告测试了三种不同的损失函数对模型表现的影响:
- MSE损失:均方误差,传统回归任务的标准损失函数
# 在模型编译时使用
model.compile(
optimizer=Adam(learning_rate=0.001),
loss='mean_squared_error' # 或简写为 'mse'
)
- IC损失:直接优化因子与未来收益率的相关性
def ic_loss(y_true, y_pred):
"""
计算预测值和真实值之间的相关系数的负值
IC越高越好,所以损失函数取负值
"""
# 去均值
y_true_centered = y_true - K.mean(y_true)
y_pred_centered = y_pred - K.mean(y_pred)
# 计算相关系数
numerator = K.sum(y_true_centered * y_pred_centered)
denominator = K.sqrt(K.sum(K.square(y_true_centered)) * K.sum(K.square(y_pred_centered)) + K.epsilon())
# 返回负相关系数作为损失
return -numerator / denominator
- CCC损失:一致性相关系数,结合了相关性和一致性的指标
def ccc_loss(y_true, y_pred):
"""
计算一致性相关系数的负值
CCC越高越好,所以损失函数取负值
"""
# 计算均值
mean_true = K.mean(y_true)
mean_pred = K.mean(y_pred)
# 计算方差
var_true = K.var(y_true)
var_pred = K.var(y_pred)
# 计算协方差
covar = K.mean((y_true - mean_true) * (y_pred - mean_pred))
# 计算CCC
numerator = 2 * covar
denominator = var_true + var_pred + K.square(mean_true - mean_pred) + K.epsilon()
# 返回负CCC作为损失
return -numerator / denominator
4. 实验结果分析
4.1 不同模型的性能比较
报告比较了多种模型的表现,包括基准线性模型和各种改进的非线性模型:
模型 | RankIC均值 | 年化ICIR | IC胜率 | 多空年化收益率 | 多空夏普 | 多空最大回撤 |
---|---|---|---|---|---|---|
基准线性模型 | 10.43% | 4.09 | 87.29% | 52.90% | 3.97 | -15.14% |
MLP模型 | 9.27% | 4.40 | 87.94% | 56.34% | 4.92 | -7.76% |
MLP+正交+MSE | 10.07% | 4.33 | 88.60% | 62.39% | 4.95 | -9.50% |
MLP+正交+IC | 11.02% | 4.57 | 89.90% | 63.49% | 4.48 | -12.49% |
MLP+正交+CCC | 9.11% | 4.49 | 91.60% | 51.38% | 5.26 | -6.60% |
4.2 关键发现
- RankIC与策略表现不一致:研究发现IC作为传统单因子表现指标,在多头选股场景下并不能完全反映最终的选股表现。需要结合多空最大回撤、多空夏普以及多头组合表现来综合评估。
- 不同损失函数的特点:
- IC损失函数的综合表现最好
- CCC损失函数对应模型的合成zscore因子稳定性最好
- 大类因子正交化的效果:加入正交惩罚后,合成因子的平均RankIC略有提高,多头对冲基准的年化收益率显著提高。
- 在不同成分股中的表现:
- 中证1000成分股表现最好,多头年化收益率为14.18%
- 中证500次之,多头收益率为9.02%
- 中证800成分股表现相对较弱,为4.93%
4.3 模型特征重要性分析
报告使用SHAP归因分析了模型中各特征的重要性,这有助于理解模型决策过程及不同因子的贡献度。
5. 非线性Alpha模型的应用流程
由于数据源只有基础量价指标,所以修改了部分设计,但结构大体不变
以下是根据报告描述实现的核心模型代码:
def add_features(self, df):
"""添加技术指标和特征"""
# 价格特征
df['price_change'] = df['close_price'].pct_change()
df['price_range'] = (df['high_price'] - df['low_price']) / df['close_price']
df['price_std'] = df['close_price'].rolling(window=5).std()
# 移动平均线
df['ma5'] = df['close_price'].rolling(window=5).mean()
df['ma10'] = df['close_price'].rolling(window=10).mean()
# 相对强弱指标 (RSI)
delta = df['close_price'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
df['rsi'] = 100 - (100 / (1 + rs))
# 成交量特征
df['volume_change'] = df['volume'].pct_change()
df['volume_ma5'] = df['volume'].rolling(window=5).mean()
df['volume_ratio'] = df['volume'] / df['volume_ma5']
# 市值特征
df['market_value_change'] = df['current_market_value'].pct_change()
# 价格与成交量关系
df['price_volume_ratio'] = df['close_price'] / df['volume']
# Alpha因子
# Alpha1: 收盘价与开盘价的差值除以振幅
df['alpha1'] = (df['close_price'] - df['open_price']) / ((df['high_price'] - df['low_price']) + 1e-5)
# Alpha2: 收盘价与前一天收盘价的差值
df['alpha2'] = df['close_price'].diff()
# Alpha3: 收盘价与5日均线的差值
df['alpha3'] = df['close_price'] - df['ma5']
# Alpha4: 成交量变化率与价格变化率的比值
df['alpha4'] = df['volume_change'] / (df['price_change'] + 1e-5)
# Alpha5: RSI与价格变化的乘积
df['alpha5'] = df['rsi'] * df['price_change']
# 填充NaN值
df = df.fillna(method='bfill').fillna(method='ffill').fillna(0)
return df
def build_alpha_model(self, input_dim, activation):
"""构建改进的非线性Alpha模型,解决因子输出为0的问题"""
inputs = Input(shape=(input_dim,))
# 第一层 - 使用更多神经元,减小dropout率
x = Dense(256, activation=activation, kernel_initializer='he_normal')(inputs)
x = BatchNormalization()(x)
x = Dropout(0.2)(x) # 降低dropout率
# 第二层
x = Dense(128, activation=activation, kernel_initializer='he_normal')(x)
x = BatchNormalization()(x)
x = Dropout(0.2)(x)
# Alpha因子层 - 使用tanh激活函数确保输出不全为0
# tanh输出范围为[-1,1],可以产生更丰富的因子表示
alpha_factors = Dense(32, activation='tanh', name='alpha_factors',
kernel_regularizer=tf.keras.regularizers.l1_l2(l1=1e-5, l2=1e-4))(x)
# 输出层
outputs = Dense(1)(alpha_factors)
# 创建模型
model = Model(inputs=inputs, outputs=outputs)
return model
def train_alpha_model(self, X_train, y_train, X_test, y_test):
"""训练非线性Alpha模型"""
print("训练非线性Alpha模型...")
# 定义不同的激活函数
activations = {
'relu': 'relu',
'sigmoid': 'sigmoid'
}
# 定义不同的损失函数
losses = {
'mse': 'mean_squared_error',
'ic': ic_loss,
'ccc': ccc_loss
}
best_model = None
best_loss = float('inf')
best_metrics = None
# 尝试不同的激活函数和损失函数组合
for act_name, activation in activations.items():
for loss_name, loss_fn in losses.items():
print(f"\n尝试 激活函数: {act_name}, 损失函数: {loss_name}")
# 构建模型
model = self.build_alpha_model(X_train.shape[1], activation)
# 编译模型
model.compile(
optimizer=Adam(learning_rate=0.001),
loss=loss_fn
)
# 早停策略
early_stopping = EarlyStopping(
monitor='val_loss',
patience=15,
restore_best_weights=True
)
# 模型检查点
checkpoint_path = os.path.join(
self.model_dir,
f'model_{act_name}_{loss_name}.keras'
)
checkpoint = ModelCheckpoint(
checkpoint_path,
save_best_only=True,
monitor='val_loss'
)
# 训练模型
history = model.fit(
X_train, y_train,
epochs=100,
batch_size=64,
validation_split=0.2,
callbacks=[early_stopping, checkpoint],
verbose=1
)
# 评估模型
y_pred = model.predict(X_test)
# 反标准化预测结果
y_test_orig = self.scaler_y.inverse_transform(y_test)
y_pred_orig = self.scaler_y.inverse_transform(y_pred)
# 计算评估指标
mse = mean_squared_error(y_test_orig, y_pred_orig)
mae = mean_absolute_error(y_test_orig, y_pred_orig)
r2 = r2_score(y_test_orig, y_pred_orig)
# 计算IC值
ic = np.corrcoef(y_test_orig.flatten(), y_pred_orig.flatten())[0, 1]
# 计算CCC值
mean_true = np.mean(y_test_orig)
mean_pred = np.mean(y_pred_orig)
var_true = np.var(y_test_orig)
var_pred = np.var(y_pred_orig)
covar = np.mean((y_test_orig - mean_true) * (y_pred_orig - mean_pred))
ccc = (2 * covar) / (var_true + var_pred + (mean_true - mean_pred)**2)
print(f"测试集MSE: {mse:.6f}")
print(f"测试集MAE: {mae:.6f}")
print(f"测试集R²: {r2:.6f}")
print(f"测试集IC: {ic:.6f}")
print(f"测试集CCC: {ccc:.6f}")
# 保存评估指标
metrics = {
'activation': act_name,
'loss': loss_name,
'mse': mse,
'mae': mae,
'r2': r2,
'ic': ic,
'ccc': ccc,
'y_test': y_test_orig,
'y_pred': y_pred_orig,
'history': history.history
}
# 保存评估结果
results_path = os.path.join(
self.model_dir,
f'results_{act_name}_{loss_name}.pkl'
)
joblib.dump(metrics, results_path)
# 更新最佳模型
if loss_name == 'mse' and mse < best_loss:
best_loss = mse
best_model = model
best_metrics = metrics
elif loss_name == 'ic' and ic > best_metrics.get('ic', -1) if best_metrics else -1:
best_model = model
best_metrics = metrics
elif loss_name == 'ccc' and ccc > best_metrics.get('ccc', -1) if best_metrics else -1:
best_model = model
best_metrics = metrics
# 保存最佳模型
best_model_path = os.path.join(self.model_dir, 'best_alpha_model.keras')
best_model.save(best_model_path)
print(f"\n最佳模型: 激活函数={best_metrics['activation']}, 损失函数={best_metrics['loss']}")
print(f"最佳MSE: {best_metrics['mse']:.6f}")
print(f"最佳IC: {best_metrics['ic']:.6f}")
print(f"最佳CCC: {best_metrics['ccc']:.6f}")
return best_model, best_metrics
def extract_alpha_factors(self, model, stock_id, lookback=10):
"""提取股票的Alpha因子并进行后处理,确保因子有意义"""
# 从数据库获取最新数据
with self.get_connection() as conn:
query = """
SELECT sd.trade_date, sd.open_price, sd.high_price, sd.low_price,
sd.close_price, sd.volume, si.current_market_value
FROM stock_daily sd
JOIN stock_info si ON sd.stock_id = si.stock_id
WHERE sd.stock_id = ?
ORDER BY sd.trade_date DESC
LIMIT ?
"""
df = pd.read_sql_query(query, conn, params=[stock_id, lookback + 20])
if df.empty:
print(f"错误: 没有找到股票 {stock_id} 的数据")
return None
# 确保数据按日期升序排列
df = df.sort_values('trade_date')
# 添加特征
df = self.add_features(df)
if len(df) < lookback:
print(f"错误: 数据不足 {lookback} 行")
return None
# 提取特征
features = df.drop(['trade_date'], axis=1).values
# 获取最新的lookback天数据
latest_data = features[-lookback:].reshape(1, -1)
# 标准化特征
latest_data_scaled = self.scaler_X.transform(latest_data)
# 创建一个新模型,用于提取中间层输出
alpha_layer_model = Model(
inputs=model.input,
outputs=model.get_layer('alpha_factors').output
)
# 提取Alpha因子
alpha_factors = alpha_layer_model.predict(latest_data_scaled)
# 对因子进行后处理,确保它们有意义
# 1. 标准化因子值
alpha_factors_normalized = (alpha_factors[0] - np.mean(alpha_factors[0])) / (np.std(alpha_factors[0]) + 1e-8)
# 2. 过滤掉接近0的因子(绝对值小于阈值)
threshold = 0.01
significant_factors = np.where(np.abs(alpha_factors_normalized) > threshold, alpha_factors_normalized, 0)
return significant_factors
6. 非线性Alpha模型的应用流程
下面是将非线性Alpha模型应用于实际投资决策的完整流程:
7. 模型训练成果
预测未来5天收益率最高的10只股票:
- SZ000030 富奥股份: 12.50%
当前价: 5.87, 预测价: 6.60 - SZ000571 新大洲A: 12.50%
当前价: 4.31, 预测价: 4.85 - SZ000680 山推股份: 12.50% ⚠️(已调整)
当前价: 8.71, 预测价: 9.80 - SZ000757 浩物股份: 12.50%
当前价: 4.10, 预测价: 4.61 - SZ000959 首钢股份: 12.50% ⚠️(已调整)
当前价: 3.61, 预测价: 4.06 - SZ001207 联科科技: 12.50%
当前价: 20.96, 预测价: 23.58 - SZ001300 三柏硕: 12.50%
当前价: 12.28, 预测价: 13.81 - SZ002072 凯瑞德: 12.50%
当前价: 4.46, 预测价: 5.02 - SZ002107 沃华医药: 12.50% ⚠️(已调整)
当前价: 4.88, 预测价: 5.49 - SZ002206 海 利 得: 12.50%
当前价: 4.92, 预测价: 5.54
8. 总结与思考
8.1 主要发现与贡献
- 模型性能提升:原文研究证明了在因子模型中引入非线性可以显著提高多空收益率和多空夏普比率,尤其是多头收益的贡献提升,但本文因条件受限,不能完整复现过程,但提供基本框架和思路
- 正交惩罚的有效性:引入因子正交惩罚能够保持大类因子的低相关性,提高合成因子的稳定性和多头收益率。
- 损失函数的重要性:不同的损失函数对模型表现有显著影响。IC损失函数在综合表现上最佳,而CCC损失函数在因子稳定性方面表现最好。
- 评估指标的重新思考:研究指出IC作为传统单因子评估指标的局限性,提出需要结合多种指标综合评估模型表现。
8.2 局限性与未来研究方向
- 可解释性挑战:深度学习模型的"黑盒"性质使得模型决策过程难以解释,这在投资决策中可能造成信任问题。未来可以进一步研究模型可解释性方法。
- 模型稳定性:随着市场环境变化,模型是否能保持稳定的表现需要进一步研究。
- 计算复杂度:相比传统线性模型,非线性模型的计算复杂度更高,这可能对实时交易决策产生影响。
- 超参数敏感性:非线性模型对超参数选择更为敏感,未来可以探索更稳健的参数优化方法。
- 与其他机器学习方法的比较:可以将MLP模型与其他机器学习方法(如随机森林、梯度提升树等)进行比较,评估不同方法的优缺点。
8.3 实践启示
- 多层次评估机制:投资决策不应仅依赖单一指标,而应建立多层次的评估机制。
- 动态自适应策略:市场环境不断变化,应构建具有自适应能力的动态策略。
- 风险管理的重要性:即使模型表现良好,仍需关注风险管理,特别是最大回撤控制。
- 股票池选择的影响:模型在不同成分股池中表现各异,小市值股票(如中证1000成分股)中表现更佳,这提示在策略设计时应考虑股票池的选择。
本报告展示了将深度学习方法应用于量化投资的一次成功尝试,不仅提高了选股策略的效果,也为未来量化投资的研究方向提供了新的思路。然而,需要注意的是,任何基于历史数据的量化策略都存在模型失效的风险,投资决策时应当保持谨慎态度,并结合多种风险控制手段。