复盘数学建模竞赛:构建基于GARCH+LSTM的基金推荐系统
Sawana Huang, Gemini 3 Pro Preview, Gemini 2.5 Pro - Sun Dec 07 2025
记录一次将GARCH波动率预测与LSTM时间序列分析相结合,应用于基金与ETF绩效评估和推荐系统的实践。重点关注模型实现、训练细节与推荐逻辑。
在这次的数模竞赛中,我们的团队尝试将经典的金融计量模型(GARCH)与前沿的深度学习模型(LSTM)相结合,构建了一个‘风险-收益’联动的基金推荐系统。本文将复盘我们从0到1的全过程,从数据清洗、五因子模型、特征工程,到最终GARCH-LSTM融合模型的构建与回测,既是一次记录,也是一份未来可以使用的指南。
TL;DR
我会在这篇博客中讲述我和队友们是如何一步一步地
- 基金绩效和可视化:金融数据的可视化和绩效评估理论基础
- 清洗数据:对基金净值日线数据进行去重,日期对齐和缺失值处理。
- 五因子模型建模:根据五因子模型构建回归系数
- 特征工程:根据五因子回归模型增加新的特征,通过回归系数、随机森林特征选择筛选最后最合适的特征
- Garch 预测基金波动率:使用GARCH(1,1)模型滚动预测未来季度波动率,作为动态风险特征。
- LSTM 模型构建和预测收益率:构建堆叠式LSTM网络,融合多维特征,预测未来季度收益率。
- 季度回测框架和推荐系统:基于模型预测构建Top-10等权重投资组合,并在历史数据上进行季度调仓回测与评估。
基金和金融可视化
对于金融数据,我们最常见接触到的就是日线数据。具体来说就是指单个标的(如基金和股票)当日股价,最高价最低价,开盘价和收盘价。这四个数据(有时候只有收盘价一个)加上作为index的日期就构成了我们最常见的金融数据。
而由此我们会计算出绩效指标,用于评价一个标的的表现
绩效指标
- 收益率/年化收益率:计算两天之间收盘价的涨跌百分比,年化收益率则是用日收益率平均值去估计一整年的收益。越高的年化收益率代表越高的收益。
- 年化波动率:衡量基金净值(或收益率)的波动幅度,是风险的直接体现。计算方式通常是将日收益率的标准差乘以√252(一年的交易日数)。波动率越低,通常认为风险越小。
- 最大回撤:指在选定周期内,基金净值从前期最高点回落到最低点的最大幅度。它衡量了投资者可能面临的最坏亏损情况,是评估风险控制能力的重要指标。
- 相对于基准的超额收益率:我们通常在比较收益率时会和基准比较,例如 “沪深300” 这样代表了大盘的的基准;计算公式是
基金收益率 - 基准收益率。这个指标也常被称为阿尔法(Alpha)收益。 - 跟踪误差:衡量基金的净值波动偏离基准指数的程度。跟踪误差越小,说明基金的走势与基准越相似,这在评估指数基金时尤为重要。
- 夏普比率:衡量每承受一单位总风险,所获得的超额回报。计算公式为
(年化收益率 - 无风险利率) / 年化波动率。夏普比率越高,说明基金的“性价比”越高。 - 信息比率:衡量基金主动管理所带来的超额收益的稳定性。计算公式为
(基金年化收益率 - 基准年化收益率) / 跟踪误差。该比率越高,说明基金经理跑赢大盘的能力越稳定。
此外,这里还有一些小知识
- 交易日:一年中约有252个交易日。在计算年化指标或对齐不同数据源(如将基金数据与市场因子数据结合)时,使用精确的交易日历而非估算,是保证准确性的基本要求。
- 平均收益率计算公式:简单收益率,是指
(期末价格 / 期初价格) - 1,它直观易懂,常用于展示单期表现;对数收益率,计算公式是ln(期末价格 / 期初价格),具有良好的时间可加性(多期对数收益率之和等于总区间的对数收益率),在金融建模和长期表现衡量中是更专业的选择。在我们的模型中,统一使用对数收益率进行计算。
对于计算基金/股票组合的绩效,我在我的博客量化策略业绩报告神器开源库Quantstats中推荐过 python 的 quantstats 包,它能根据你组合的日线数据生成基金绩效的撕页报告,非常方便。
金融数据探索
在定义了绩效指标后,我们不妨深入数据本身,看看能发现什么有趣的特征。理解这些特征,是后续选择正确模型的关键。我们还是以沪深300 ETF (510300.SS) 为例来展开。
import yfinance as yf
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from scipy import stats
sns.set_style('whitegrid')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
etf_ticker = '510300.SS'
data = yf.download(etf_ticker, start='2022-01-01', end='2023-12-31')
data['daily_return'] = data['Adj Close'].pct_change().dropna()收益率分布的“尖峰肥尾”
很多经典金融理论都爱假设收益率服从正态分布,但这个假设真的成立吗?我们画个图就知道了。将收益率的直方图与标准正态分布曲线放在一起,可以清晰地看到,实际分布的峰部比正态分布更“尖”,而尾部则更“肥”。
这意味着,大部分时间的收益波动都挤在一个很小的范围内,但极端的大涨或大跌发生的概率,却远比我们想象的要高。QQ图也证实了这一点:代表收益率的红点在图像的两端明显偏离了那条代表正态分布的黑线。
这就告诉我们一个很重要的事:如果模型依赖正态分布去评估风险,那它很可能会严重低估市场中“黑天鹅”事件的发生概率。
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 收益率直方图
sns.histplot(data['daily_return'], kde=True, stat="density", ax=axes[0])
mu, std = stats.norm.fit(data['daily_return'])
x = np.linspace(data['daily_return'].min(), data['daily_return'].max(), 100)
axes[0].plot(x, stats.norm.pdf(x, mu, std), 'k', linewidth=2)
axes[0].set_title(f'{etf_ticker} 日收益率分布')
# 收益率QQ图
stats.probplot(data['daily_return'], dist="norm", plot=axes[1])
axes[1].set_title(f'{etf_ticker} 收益率QQ图')
plt.show()波动率的“聚集性”
另一个更有趣的特征,是波动率的“扎堆”现象,行话叫“波动率聚集”(Volatility Clustering)。我们把每天的收益率按时间顺序画出来,就能很直观地看到,市场的波动并非均匀分布。在某些时期,价格波动非常剧烈,形成“高波动”集群;而在另一些时期,价格又会相当平稳,形成“低波动”集群。
这个发现是本次项目选择GARCH模型的关键。它说明风险(波动率)本身是随时间动态变化的,而且似乎有迹可循。这就给了我们用GARCH这类模型去捕捉甚至预测它的信心。
plt.figure(figsize=(12, 5))
data['daily_return'].plot()
plt.title(f'{etf_ticker} 日收益率序列(波动率聚集性)')
plt.show()价格的“突变”现象:合并与分割
最后,我们还得处理一个常见的“数据陷阱”,就是基金或股票因为分红、拆股等行为造成的价格“跳变”。比如,一个10元的基金进行“1拆2”后,它的净值会直接变为5元。如果我们直接用这个价格计算收益率,就会得到一个-50%的巨额亏损,但这显然不是由市场交易行为导致的。
所以,在做任何量化分析时,我们都要养成一个好习惯:始终使用“后复权价”(Adjusted Close),而不是原始的“收盘价”(Close)。像 yfinance 这样的专业数据源已经帮我们处理好了这个问题,它提供的Adj Close列已经抹平了这些非市场因素造成的跳变,让我们可以直接使用,省去了很多麻烦。
如果我们使用的是自己爬取的数据,或是数据源未提供后复权价格,情况就复杂一些。当价格走势出现非市场因素导致的“断崖式”下跌时(例如因拆股或大额分红),就需要特别小心。一个常见的处理方法是,手动识别出由这类事件造成的单日异常收益率,并将其替换为一个合理值。例如,直接将其设为0,这相当于假设在复权调整日,资产的实际价值没有发生变动,从而剔除了这种非交易因素的干扰。
金融数据的预处理
在将原始数据输入模型之前,必须进行严格的预处理,以确保数据的质量和一致性。金融数据往往充满“陷阱”,如非交易日导致的日期不连续、基金拆分合并带来的价格“断崖”,以及各种原因造成的缺失值。忽略这些问题会导致模型训练出错误的结果。本章节将重点介绍我们处理以下几个常见问题的方法。
交易日历匹配
数据预处理的首要挑战是多源时间序列的对齐。例如,不同基金的净值数据与我们使用的市场因子数据的日期序列可能因节假日、临时停牌等原因存在差异。若直接合并,会导致数据在时间维度上错位,进而扭曲分析结果。
因此,建立一个统一的基准日历是必要的。在我们的项目中,我们直接采用已获取的每日市场五因子数据的日期序列作为基准交易日历,因为它能最准确地反映研究时段内的市场活动。随后,我们将所有基金的净值数据对该基准日历进行reindex操作,确保所有时间序列具有完全一致的日期索引。此步骤中因对齐而产生的缺失值(NaN),将留待后续步骤处理。
处理价格突变
在数据预处理中,我们遇到了一个棘手的问题。比赛提供的基金净值数据并未明确其复权状态,而在我们初步的收益率计算中,我们发现了存在个别单日收益率绝对值超过20%的极端情况。这种幅度的波动在常规市场环境下极为罕见,因此我们高度怀疑这些是由于基金分红、拆分等行为造成的结构性断点,而非真实的价值波动。
针对这一问题,我们的处理方案是设定一个收益率阈值(例如20%),并对超出此阈值的异常点进行审查和修正。对于经判断确认的结构性断点,我们将其当日收益率修正为0。这一操作等价于假设资产的实际价值在该结构调整日未发生改变,从而有效地剔除了非市场因素的干扰。这个步骤至关重要,因为它确保了我们后续模型所使用的收益率序列能真实反映市场驱动的资产价值变化。
作为对比,一个标准的、高质量的数据源通常会直接提供处理好的后复权数据。为了更直观地理解这一点,我们可以使用yfinance获取苹果公司(AAPL)在2020年8月“一拆四”拆股事件前后的数据,并对比其“收盘价”和“后复权收盘价”的走势。
aapl_data = yf.download('AAPL', start='2020-07-01', end='2020-09-30')
plt.figure(figsize=(12, 6))
aapl_data['Close'].plot(label='收盘价 (Close)', linestyle='--')
aapl_data['Adj Close'].plot(label='后复权收盘价 (Adj Close)')
plt.title('苹果公司(AAPL)拆股前后价格对比 (2020年)')
plt.legend()
plt.show()从上图中可以清晰地看到,Close价格在拆股日(2020年8月31日)出现了断崖式下跌,而Adj Close则平滑地延续了之前的走势,真实地反映了其价值的连续增长。这直观地展示了在进行量化分析时,优先使用后复权数据的必要性和便利性。
缺失值处理
经过日历对齐后,数据中出现的NaN值主要对应了基金在某些交易日的停牌情况。对于这类时间序列中的缺失值,我们采用“前向填充”(Forward Fill)进行处理。
该方法的依据是,在资产暂停交易期间,其公允价值理论上应维持在最后一个交易日的水平。这种处理方式不仅符合会计和金融实践的逻辑,也避免了使用插值或后向填充等方法可能引入未来信息或人造数据点的问题。在pandas库中,通过.fillna(method='ffill')方法即可高效、准确地实现这一操作。
五因子模型和时间序列回归
在完成数据预处理后,我们进入了项目的核心环节之一:使用Fama-French五因子模型对基金的收益进行归因分析,目的是精准识别基金收益的来源,区分其收益是来自于承担系统性风险(Beta),还是来自于基金经理的主动管理能力(Alpha)。
五因子模型是什么?
Fama-French五因子模型是金融资产定价领域的基石模型之一。它在经典的三因子模型基础上,增加了“盈利能力”和“投资模式”两个因子,旨在更全面地解释股票及基金的收益波动。其标准回归方程如下:
其中:
- : 基金在t时期的超额收益率。
- 五个因子:
- MktRf: 市场风险溢价因子,衡量市场整体的涨跌。
- SMB (Small Minus Big): 市值因子,反映小盘股相对于大盘股的超额收益。
- HML (High Minus Low): 价值因子,反映价值股相对于成长股的超额收益。
- RMW (Robust Minus Weak): 盈利能力因子,反映高盈利能力公司相对于低盈利能力公司的超额收益。
- CMA (Conservative Minus Aggressive): 投资模式因子,反映投资保守的公司相对于投资激进的公司的超额收益。
- : 模型的残差项。
解读五因子模型 alpha 和 beta
模型回归出的系数,即alpha和beta,为我们提供了洞察基金内在特性的钥匙。
-
Alpha (α): 截距项,代表了在排除了五大类系统性风险后,基金所取得的、无法被模型解释的超额收益。它通常被视为衡量基金经理主动管理能力(如择时、选股)的代理指标。然而,在我们的实证分析中,一个核心发现是:尽管许多基金的历史Alpha值为正,但经过严格的统计检验后,真正显著为正的Alpha凤毛麟角(在我们的样本中仅占约2%)。 这说明大部分所谓的“超额收益”可能仅仅是“运气”,而非可复制的“能力”。
-
Beta (β): 各个因子的回归系数,量化了基金收益对相应风险因子的敏感度或暴露程度,描绘了基金的“投资风格画像”。例如:
- 一个显著为正的
SMB系数意味着基金偏好小盘股风格。 - 一个显著为负的
HML系数则代表其重仓“成长股”。 - 在我们的分析中,样本基金的平均风格呈现出“防御性(MktRf Beta < 1)、大盘、成长”的特征。
- 一个显著为正的
建立回归模型
在项目中,我们对筛选出的每只基金,使用其过去五年的日度超额收益率作为因变量(Y),对齐同期的五因子收益率作为自变量(X),进行时间序列回归。
我们主要使用Python的sklearn库进行线性回归拟合。值得注意的是,sklearn本身不直接提供系数的统计显著性检验。因此,为了进行严谨的统计推断,我们还基于回归结果手动计算了每个系数的t统计量和p值,以判断Alpha和各个Beta在统计上是否显著。
一个简化的建模流程示例如下:
from sklearn.linear_model import LinearRegression
# 假设 Y 是基金超额收益序列 (n_samples,)
# 假设 X 是五因子收益序列 (n_samples, 5)
model = LinearRegression()
model.fit(X, Y)
alpha = model.intercept_ # 获取 alpha
betas = model.coef_ # 获取五个beta系数
# 后续进行统计显著性检验(此步涉及较多手动计算,此处不展示)
# ...通过这个流程,我们为样本中的每一只基金都计算出了一套完整的alpha、beta系数及其对应的统计显著性,为后续的特征工程和推荐系统构建打下了坚实的数据基础。
特征工程
特征工程的目的十分明确:如果仅使用原始的价格序列作为输入,那模型的视角将极其受限,相当于只有一个自变量(X)来预测因变量(y)。为了构建一个更强大、更具解释性的预测模型,我们必须从原始数据中提取更多维度的信息,构建一个丰富的特征矩阵(Xn)。
构建新的特征
在我们的项目中,特征工程的核心思想是利用Fama-French五因子模型作为“特征提取器”。但与之前章节中为获得静态画像而进行的一次性回归不同,我们在此采用了一种更能捕捉动态变化的**滚动窗口回归(Rolling Window Regression)**方法。
具体来说,我们以252个交易日(约一年)为窗口长度,以1天为步长,在每个交易日 t 都利用从 t-251 到 t 的历史数据进行一次五因子回归。这次回归产生的系数和统计量,就成为了该基金在 t 日这一天的特征。通过日复一日地滑动窗口,我们为每只基金都生成了一套日频的、动态变化的特征时间序列。
我们构建的特征主要分为以下几类:
-
动态风格特征(Beta系数): 每日滚动回归产生的五个Beta系数(
Beta_Mkt,Beta_SMB,Beta_HML,Beta_RMW,Beta_CMA),构成了基金动态的“风格指纹”。它们不再是静态的标签,而是能够逐日追踪基金在市场、规模、价值等风险因子上的暴露度变化,为我们揭示了基金经理可能存在的“风格漂移”现象。 -
动态Alpha与显著性: 同样,每日滚动回归也为我们提供了动态的Alpha及其t统计量(
Alpha_t)。这组特征尤为关键,因为它不仅告诉我们基金在特定时期有无超额收益,更通过t统计量揭示了这份超额收益的可信度——它究竟是源于基金经理的真实能力,还是仅仅是市场噪音或运气。 -
模型质量与特质风险: 我们还提取了每次回归的R²(
R2)和残差波动率(Resid_Vol)。R²衡量了在那个时间窗口内,五因子模型对基金收益的解释力度,而残差波动率则量化了无法被系统性风险解释的、基金独有的“特质风险”(Idiosyncratic Risk)。 -
辅助技术指标: 最后,我们还计算了几个经典的技术指标作为补充特征,如滚动波动率(
Volatility)、最大回撤(MaxDrawdown)和动量(Momentum),为模型提供了更多维度的信息。
通过这一系列特征工程,我们成功地将每一只基金从单一的价格时间序列,转换为了一个包含其动态风格、超额收益能力、风险特征和技术指标的多维特征矩阵。这为我们后续使用LSTM等深度学习模型进行收益率预测,奠定了坚实的数据基础。
特征筛选
构建了丰富的特征集之后,下一步就是“少即是多”的艺术——特征筛选。引入过多的特征,尤其是相关性高或与目标无关的特征,可能会导致模型过拟合、训练速度变慢,并增加模型的复杂度。我们的目标是筛选出一个更小但更强大的特征子集。在业界实践中,通常会组合使用多种方法。
基于相关性的筛选
这种方法主要用于解决特征之间的多重共线性问题。当两个特征高度相关时,它们在模型中可能提供了冗余的信息,保留两者可能会影响模型系数的稳定性和可解释性。
-
实践方案:计算所有特征之间的相关系数矩阵,并设定一个阈值(例如,相关系数的绝对值大于0.8)。当一对特征的相关性超过这个阈值时,我们通常会根据业务理解或它们与目标变量的相关性,从中移除一个。
-
决策要点:Pearson vs. Spearman 相关系数?
- 皮尔逊(Pearson)相关系数:衡量的是变量间的线性关系。它计算速度快,结果直观,但其有效性建立在线性关系和数据近似正态分布的假设之上。
- 斯皮尔曼(Spearman)相关系数:衡量的是变量间的单调关系,它首先对变量进行排序,然后计算排序值之间的相关性。因此,它对异常值不敏感,且不要求数据满足正态分布或线性关系。
- 我们的选择:考虑到金融特征数据往往不满足正态分布,且特征间的关系也未必是线性的,斯皮尔曼相关系数通常是更稳健、更保守的选择,能帮助我们捕捉到更广泛的非线性关联。
基于树模型的特征重要性
以随机森林(Random Forest)为代表的集成树模型,在训练后可以提供每个特征对模型预测贡献大小的“重要性”评分,这是一个非常强大且常用的特征筛选工具。
-
实践方案:训练一个随机森林模型(可用于分类或回归),然后提取其
feature_importances_属性,并据此对特征进行排序和筛选。 -
决策要点:基尼重要性 vs. 置换重要性?
- 基尼重要性(Gini Importance / Mean Decrease in Impurity):这是
sklearn中随机森林默认的特征重要性度量。它计算每个特征在分裂决策树节点时,平均带来了多少“不纯度”的降低。它的优点是计算速度快,因为它是模型训练的副产品。但缺点也很明显:它倾向于高估高基数(high-cardinality)特征和有相关性的特征组的重要性,有时会产生误导。 - 置换重要性(Permutation Importance / Mean Decrease in Accuracy):这种方法更为可靠。在模型训练完成后,它会随机打乱(shuffle)某一个特征的取值,破坏它与目标变量的联系,然后重新评估模型的性能(如准确率或R²)。性能下降得越多,说明这个特征越重要。它的计算成本更高,但它能更真实地反映特征在已经训练好的模型中的预测价值,且在很大程度上避免了基尼重要性的偏误。
- 我们的选择:在探索性分析阶段,基尼重要性是一个快速的初筛工具。但在要求严谨性的项目中,置换重要性是更值得信赖的最终评判标准,尤其是在我们的特征(如多个Beta)本身就可能存在一定相关性的情况下。
- 基尼重要性(Gini Importance / Mean Decrease in Impurity):这是
递归特征消除(Recursive Feature Elimination, RFE)
RFE是一种封装(Wrapper)方法,它通过一个迭代的过程来寻找最佳的特征子集。
- 实践方案:首先,在一个能够评估特征重要性的外部模型(如带有系数的线性模型,或随机森林)上,用所有特征进行训练。然后,它会剔除掉最不重要的一个或多个特征,在剩余的特征上重新训练模型。这个过程不断递归,直到特征数量达到预设的目标。
- 优点:与前两种方法不同,RFE在每一步都考虑了剩余特征之间的相互作用,而不是孤立地评估每个特征。这使得它在寻找最优特征组合方面可能更胜一筹。
- 缺点:计算成本非常高,尤其是在特征数量庞大的情况下。
我们的策略
在我们的项目中,我们正是结合了以上方法来寻找最优特征集。首先,我们通过斯皮尔曼相关性分析,剔除了特征间的冗余信息。接着,针对剩余的特征,我们同时计算了基尼重要性和置换重要性。
为了做出最稳健的选择,我们绘制了两种重要性得分的累积重要性曲线(Cumulative Importance Curve)。通过对比两条曲线,我们发现两种方法都共同指向了相同的三个特征作为最重要的顶级特征。因此,我们最终将这三个共同选出的核心特征(例如 Alpha_t, Resid_Vol, Momentum)作为核心特征子集,用于后续模型的初步构建。这种交叉验证的策略,确保了我们选择的特征是真正稳健且具有高预测价值的。
Garch 预测基金波动率
在“金融数据探索”一章中,我们发现了收益率序列存在显著的“波动率聚集性”,即高波动的时期和低波动的时期会各自扎堆出现。这一现象表明,波动率(风险)本身是时变的,并且可能具有可预测性。为了量化并预测这种时变波动率,我们引入了金融计量学中经典的GARCH(广义自回归条件异方差)模型。
在本项目中,我们并非直接用GARCH模型来预测收益,而是将其作为一种先进的特征提取器,为后续的LSTM模型生成一个关键的、代表未来风险预期的动态指标。
模型设定
在众多GARCH模型族中,我们选用了兼具简洁与有效性的 GARCH(1,1) 模型。GARCH(1,1)意味着今天的波动率是昨天的波动率信息和昨天市场冲击(即收益率的平方)的一个加权平均,这很直观地刻画了金融市场中波动的持续性或“记忆性”。
更进一步,为了让模型更贴合金融数据的真实特性,我们对模型的两个关键假设进行了设定:
- 零均值(
mean='Zero'): 我们假设日度收益率的长期均值为零,让模型专注于对波动率本身的建模。这在处理高噪声、均值不显著的日度收益数据时,是简化模型、提高稳健性的常用技巧。 - 学生t分布(
dist='t'): 考虑到金融收益率显著的“尖峰肥尾”特性,我们没有使用标准的正态分布,而是假设模型的误差项服从学生t分布。这使得我们的模型能更好地捕捉和应对市场中极端事件的发生。
滚动预测实现
在具体实现上,我们采用了一种滚动的、按季度更新的预测方案,以模拟真实的投资决策过程。在每个季度的开始(即1、4、7、10月的首个交易日),我们对样本内的每一只基金执行以下操作:
- 提取其过去252个交易日(约一年)的对数收益率序列作为训练数据。
- 使用这252天的数据,拟合我们设定的GARCH(1,1)模型。
- 我们将该模型拟合出的最后一个交易日的条件波动率,作为对未来整个季度的波动率预测值。
- 这个预测出的波动率值(
GARCH_Vol)被赋予该季度内的每一天,成为一个全新的、动态的、**向前看(Forward-looking)**的风险预测特征。
一个简化的GARCH拟合与预测流程代码示例如下:
from arch import arch_model
# 假设 recent_returns 是某基金过去252天的收益率序列 (已乘以100)
# 1. 定义GARCH(1,1)模型,使用学生t分布
am = arch_model(recent_returns, vol='GARCH', p=1, q=1, mean='Zero', dist='t')
# 2. 拟合模型,不显示过程信息
res = am.fit(disp='off')
# 3. 获取最新的条件波动率作为对下一期的预测值
# 预测值需要从百分比尺度缩放回原始尺度
predicted_vol = res.conditional_volatility[-1] / 100通过这个严谨的流程,我们为每一条数据都附加上了一个基于历史信息对未来风险的量化预测,这极大地丰富了后续LSTM模型可利用的信息维度。
LSTM 预测收益率
在获得了包含动态风格和风险信息的多维特征序列后,我们需要一个能有效处理和学习时间序列数据中复杂模式的模型。传统的线性模型难以捕捉特征随时间演变的非线性关系,因此我们选择了长短期记忆网络(LSTM),这是一种特别擅长处理序列依赖问题的循环神经网络(RNN)。我们的目标是利用LSTM,根据过去一段时间的特征序列,来预测未来一个季度的累计收益率。
模型架构
我们基于PyTorch框架,构建了一个包含两层堆叠的LSTM网络。一个健壮的深度学习模型结构,是保证其捕捉复杂模式能力的基础。我们的模型设计如下:
- 输入层: 接收我们精心筛选出的特征序列,在我们的项目中,这包括了如
Beta_SMB,Momentum,t_HML和我们前一阶段计算出的GARCH_Vol等关键特征。 - 第一层LSTM: 包含64个隐藏单元,用于从输入序列中捕捉初步的时间模式。
- 第二层LSTM: 包含32个隐藏单元,堆叠在第一层之上,用于学习和提取更深层次、更抽象的时间依赖关系。
- Dropout层: 为了防止模型在训练过程中过拟合,我们在每层LSTM之后都加入了Dropout(丢弃率为0.3),在训练时随机“丢弃”一部分神经元,以增强模型的泛化能力。
- 输出层: 最后,通过一个全连接线性层(Linear Layer),将LSTM网络在最后一个时间步的输出,映射为对未来季度累计收益率的单点预测值。
一个简化的模型结构代码示例如下:
import torch
import torch.nn as nn
# 一个简化的LSTM回归模型结构示例
class LSTMRegressor(nn.Module):
def __init__(self, input_size, hidden_size_1, hidden_size_2, dropout_rate):
super(LSTMRegressor, self).__init__()
self.lstm1 = nn.LSTM(input_size, hidden_size_1, batch_first=True)
self.dropout1 = nn.Dropout(dropout_rate)
self.lstm2 = nn.LSTM(hidden_size_1, hidden_size_2, batch_first=True)
self.dropout2 = nn.Dropout(dropout_rate)
self.fc = nn.Linear(hidden_size_2, 1) # 输出维度为1
def forward(self, x):
# x 形状: (batch_size, seq_length, input_size)
lstm_out, _ = self.lstm1(x)
lstm_out = self.dropout1(lstm_out)
lstm_out, _ = self.lstm2(lstm_out)
# 我们只关心序列最后一个时间点的输出,用它来进行最终预测
last_time_step_out = lstm_out[:, -1, :]
last_time_step_out = self.dropout2(last_time_step_out)
out = self.fc(last_time_step_out)
return out数据准备与训练过程
一个好的模型需要高质量的“养料”。在数据准备和训练环节,我们同样采用了一套严谨的流程。
-
数据准备与序列构建: 为了训练LSTM,我们将时间序列数据转化为了监督学习样本。我们采用了滑动窗口方法:使用过去20个交易日的特征序列(
X)作为模型的单个输入样本,其对应的标签(y)则是一个经过精心设计的目标——即从这个20天窗口结束时起,到目标季度末的累计对数收益率。这种直接预测持有期回报的设计,使得模型训练的目标与我们最终的应用场景高度一致。 -
滚动训练与验证: 我们采用了按季度滚动的训练和预测方案。在每个季度初,我们对每一只基金,都利用其过去一年(252天)的数据重新训练一个独立的LSTM模型。为了保证模型的质量,我们将这一年的数据进一步划分为训练集(75%)和验证集(25%),并引入了**早停(Early Stopping)**机制:如果在验证集上的表现连续10个周期(epochs)没有提升,就提前终止训练,并采用表现最好的模型。这既保证了模型得到充分训练,又有效防止了过拟合。
-
优化器与损失函数: 在训练过程中,我们使用了对深度学习模型优化效果很好的AdamW优化器,并选择**均方误差(MSE)**作为损失函数,这对于回归预测任务是标准且有效的选择。
Garch 融合 LSTM
单独的GARCH模型擅长捕捉波动率,而单独的LSTM模型擅长学习序列中的非线性模式。将两者结合,构建一个既能理解风险、又能预测收益的强大模型,是金融量化领域一个非常活跃的研究方向。本章我们将首先探讨几种主流的融合思路,然后详细介绍我们项目中采用的具体路径。
常见的模型融合思路
在学术界和工业界,GARCH与LSTM(或更广泛的深度学习模型)的融合并非只有一种方式,根据两个模型“对话”的深度和方式,大致可以分为几种流派:
-
串联/特征工程法(Serial / Feature Engineering Approach)
- 思路:这是最直观、最常用的一种方法。首先,使用GARCH模型对收益率序列进行拟合,提取出我们关心的条件波动率序列。然后,将这个波动率序列作为一个全新的特征,与其他特征(如因子暴露、技术指标等)拼接在一起,共同作为LSTM模型的输入。
- 比喻:GARCH模型像一个经验丰富的“风险分析师”,它首先对市场进行评估,给出一份关于未来风险(波动率)的专业报告。然后,LSTM模型作为“最终决策者”,会阅读这份风险报告,并结合其他多方信息,做出对未来收益的综合判断。
- 优缺点:优点是逻辑清晰、实现简单、模块化强,能充分发挥GARCH在波动率建模上的优势。缺点是信息流动是单向的,LSTM的判断无法反过来影响GARCH的建模。
-
并联/混合加权法(Parallel / Hybrid Approach)
- 思路:让GARCH模型和LSTM模型同时独立工作。例如,GARCH模型专门用来预测波动率,而LSTM模型专门用来预测收益率的均值部分。最终的收益率预测结果,是两个模型输出的某种组合。或者,两个模型都预测收益率,然后对它们的预测结果进行加权平均。
- 比喻:这是一个“专家小组会诊”。GARCH是风险领域的专家,LSTM是非线性模式领域的专家,他们各自给出独立的专业意见,我们最后将这些意见综合起来,形成最终结论。
- 优缺点:优点是可以从不同角度捕捉市场的动态。缺点是如何确定最优的权重本身是一个难题,且两个模型之间没有直接的信息交流。
-
深度融合/整合模型法(Deep Fusion / Integrated Approach)
- 思路:这是最复杂但潜力也最大的方法。它不再将两个模型视为独立的黑箱,而是将它们在结构上深度融合。例如,用LSTM去预测GARCH模型的时变参数(即让GARCH的p和q值是动态变化的),或者将GARCH的波动率方程直接作为一项损失函数(Loss Function)加入到LSTM的训练过程中。
- 比喻:这不是简单的合作,而是“基因层面”的改造。LSTM的输出直接改变了GARCH模型的内部运作机制,两个模型融为一体,形成一个全新的、更强大的物种。
- 优缺点:优点是能捕捉极度复杂的非线性关系,潜力巨大。缺点是模型设计和实现的难度极高,训练不稳定,且结果的可解释性大大降低。
本项目采用的融合路径
在充分考量了各种方案的复杂度和有效性后,我们在本次项目中采用了逻辑最清晰、实践最稳健的第一种路径——串联/特征工程法。
我们的具体实现流程如下:
- 首先,如前一章所述,我们对每只基金的收益率序列,通过滚动的方式拟合GARCH(1,1)-t模型,得到了对未来一个季度的**条件波动率(Conditional Volatility)**的预测值。
- 然后,我们将这个代表着未来风险预期的
GARCH_Vol,作为一个全新的、极具价值的动态特征。 - 最后,我们将这个
GARCH_Vol特征,连同我们之前通过因子模型和技术分析提取的其他特征(如Beta_SMB,Momentum,t_HML等),一同作为输入,喂给LSTM模型进行最终的收益率预测。
我们选择这种路径,因为它逻辑清晰、易于实现和解释。GARCH模型在波动率建模上久经考验,而LSTM擅长处理复杂的非线性时序关系。通过这种方式,我们让两个模型各司其职、扬长避短:GARCH负责精准地量化风险,LSTM则在此基础上,结合更多维度的信息,进行最终的收益率预测。这构成了一个‘风险-收益’联动的预测框架,也是我们后续构建推荐系统的基石。
回测系统构建
一个模型的好坏,不能只看预测精度(如MSE),最终必须落实到它能否在模拟的真实投资环境中持续创造价值。为此,我们设计并实现了一套完整的回测系统。这个系统如同一台“金融时间机器”,让我们可以在过去数年的历史数据上,检验我们GARCH-LSTM策略的有效性,并客观地评估其风险与收益。
回测框架设计与原则
我们的回测框架在设计上严格遵循了几个核心原则:
- 季度滚动调仓:我们的回测区间为2019年至2024年。策略采用季度调仓的模式,在每年1月、4月、7月、10月的第一个交易日,根据最新的模型预测,对投资组合进行再平衡。
- 杜绝数据泄露:整个回测框架最关键的原则是杜绝任何形式的数据泄露(Data Leakage)。在任何一个调仓日
t,我们用以构建投资组合的所有信息(包括模型训练、预测、筛选)都严格地只使用了在t时刻之前可以获得的历史数据。 - 模块化流水线:我们将整个回测流程设计为一个标准化的多阶段流水线(
信号生成 → 策略执行 → 业绩评估),确保了每个环节的独立性和可复现性。
投资组合构建策略(信号生成)
这是整个策略的“大脑”。在每个季度初的调仓日,我们基于GARCH-LSTM模型的预测,构建了一个寻求高alpha的进取型投资组合。具体决策流程如下:
- 预测与排序: 利用我们前述的GARCH-LSTM融合模型,对样本池中所有基金未来一个季度的累计收益率进行预测。然后,我们只根据此预测收益率的高低进行排序。
- 筛选与加权: 我们选取排序最高的Top 10基金,构成我们当期的投资组合,并对这10只基金采用等权重(10%)配置。
这个策略的核心非常纯粹:我们完全相信LSTM模型对未来收益的预测能力,并直接追逐它认为未来会涨得最好的资产组合。
策略执行与业绩评估
在确定了持仓信号后,我们开发的BacktestEngine(回测引擎)开始模拟真实交易:
-
策略执行:引擎根据季度初的Top 10持仓信号,模拟“买入”并持有该投资组合一个季度。在计算每日净值时,我们的引擎依据真实复权价格,并扣除了0.15%的单边交易佣金,以确保结果贴近真实。
-
业绩评估: 在长达六年的回测期结束后,我们得到了一条完整的策略净值曲线。随后,我们开发的
PerformanceAnalyzer(绩效分析器)对策略表现进行全方位分析。我们将策略与沪深300指数进行对标,并计算了一系列关键绩效指标,例如:- 收益类:年化收益率、累计收益率
- 风险类:年化波动率、最大回撤
- 风险调整后收益:夏普比率(其中无风险利率根据代码设定为3%)、卡玛比率
- 相对表现:信息比率、相对基准的胜率
通过这个完整的回测与评估流程,我们可以对我们GARCH-LSTM模型的真实表现得出一个客观、量化的结论。
总结和收获
经过从数据探索到最终回测的完整流程,我们团队不仅完成了一次复杂的课程项目,更在实践中收获了诸多宝贵的经验和认知。
从理论到实践:点亮端到端的量化策略技能树
本次项目是一次将理论知识应用于复杂现实问题的绝佳实践。我们完整地走通了从数据获取、特征工程、模型训练到策略回测的全流程。这不仅让我们在**机器学习和深度学习(尤其是时序模型LSTM)的应用上更加熟练,更重要的是,我们亲手构建了一个完整的“预测-筛选-回测”**的量化推荐系统雏形。这种端到端的项目经验,是纯粹的理论学习无法替代的。
“垃圾进,垃圾出”:再谈数据预处理的关键性
项目初期,我们在数据处理上投入了大量精力,包括处理复权状态未知的价格突变、匹配交易日历、填充缺失值等。这段经历让我们深刻体会到“Garbage In, Garbage Out”这句格言的含义。任何一个微小的数据瑕疵,都可能在模型中被放大,并最终导致整个策略的失效。一个稳健、可靠的数据预处理流程,是所有成功量化分析的基石。
拥抱开源:基于PyTorch构建深度学习模型的体会
本次项目中,我们选择并实践了使用PyTorch来搭建LSTM模型。通过亲手定义网络结构、编写训练循环、设置优化器和损失函数,我们对深度学习模型的工作原理有了更直观、更深入的理解。这让我们不再仅仅是调用API的“调包侠”,而是能根据实际问题,灵活设计和调试模型。掌握PyTorch这样的主流深度学习框架,为我们未来解决更复杂的非线性问题提供了有力的工具。
“1+1>2”:对“计量+AI”融合模型的思考
本项目最大的特色之一,就是采用了GARCH + LSTM的“串联/特征工程法”融合路径。我们没有简单地抛弃经典的金融计量模型,也没有盲目地信仰深度学习。而是让GARCH模型负责它最擅长的波动率刻画,将其输出作为高质量特征,再由LSTM来进行更高维度的时序模式学习。这种“计量模型+机器学习模型”的融合思路,让我们认识到不同模型各有所长,通过巧妙的组合可以让它们优势互补,达到1+1>2的效果。这为我们未来解决复杂问题提供了新的视角。
参考文献
[1]李哲,张蜀琪.时空并蓄,择基而优:ConvLSTM赋能FOF策略的实证新证[J].金融市场研究, 2025(9).
[2]操玮,任思儒.基于LSTM与GARCH族混合模型的人民币汇率波动预测研究[J].计算机应用研究, 2020(S01):4.