1. 这不是“调个包就能跑”的时间序列预测——为什么非平稳数据必须先“动手术”再建模?
你手头有一张过去二十年的S&P 500指数日线图,从2003年不到1000点一路涨到2023年4000多点。如果直接把这张图喂给一个ARIMA模型,让它预测下个月的收盘价,结果大概率会像用直尺去量一条蜿蜒的山路——方向是对的,但每一步的误差都在累积放大,最终预测值可能偏离真实值几百点。这不是模型不行,而是你没给它一个能“理解”的输入。这正是本篇要解决的核心问题:非平稳时间序列的建模,本质是一场精密的“数据外科手术”,而不是一次简单的参数拟合。关键词“Statistical Forecasting for Time Series Data”、“Non-Stationary Time Series”、“ARMA Model”、“S&P 500”、“Log-Differencing”——它们共同指向一个现实:在金融、经济、能源、气象等绝大多数真实世界场景中,原始数据天然带有趋势、周期和波动性变化,它拒绝被一个静态的数学公式所驯服。我做过上百个不同行业的时序项目,从风电场功率预测到电商GMV拆解,最常踩的坑就是跳过“平稳性检验”这一步,直接上模型。结果呢?回测指标看着漂亮,一上线就崩盘。因为模型学到的不是数据生成的内在机制,而是那段特定历史时期里“价格一直在涨”这个表象。一旦市场风格切换,模型立刻失灵。所以,本篇不讲虚的理论推导,只讲我在实操中反复验证过的硬核逻辑:为什么差分不是万能的?为什么对数变换要和差分“联合作战”?为什么ARMA(1,1)在这个案例里是合理起点而非随意选择?以及,最关键的——如何把模型输出的“对数差分值”,安全、无损地“翻译”回你真正关心的美元价格?这篇文章,就是一份写给实战派的数据工程师、量化研究员和业务分析师的“非平稳数据建模手术指南”。它不承诺给你一个完美的预测,但能确保你每一步操作都有据可依,每一个参数选择都经得起推敲。
2. 核心思路拆解:为什么“站稳了才能走路”是时序建模的铁律?
2.1 平稳性不是统计学的教条,而是模型生效的物理前提
很多初学者把“平稳性”当成一个需要通过ADF检验的抽象概念,考完试就扔了。但在真实项目里,它是一个关乎模型生死存亡的物理约束。我们可以用一个生活化的类比来理解:想象你要训练一个机器人学习骑自行车。你给它看的训练视频,全是同一辆自行车在同一条平坦、笔直的柏油路上行驶。那么,当它第一次被放到一条布满碎石、坡度起伏的山路上时,它大概率会摔倒。为什么?因为它的“学习环境”(训练数据)和“应用环境”(未来数据)的底层物理规律发生了根本性偏移——路面摩擦力、重力分量、空气阻力都变了。时间序列里的“非平稳性”,就是这种底层物理规律的偏移。S&P 500价格的长期上涨趋势,背后是美国经济总量的增长、企业盈利的提升、货币供应量的变化等一系列宏观变量的持续演进。这些变量本身就在随时间系统性变化。而ARMA这类模型,其数学根基建立在一个核心假设上:数据生成过程(DGP)是恒定的。它假设今天影响价格波动的那些因素(比如市场情绪、流动性、政策预期),其作用方式和强度,在明天、下周、下个月依然有效。这个假设,只有在数据“站稳了”——即均值、方差、自相关结构不随时间漂移——的前提下才成立。一旦数据带着强烈的趋势,模型就会被迫去拟合这个“漂移”本身,而不是去捕捉驱动价格变化的、更深层的随机扰动。这就像让机器人在斜坡上强行保持直立,它所有的计算资源都花在了对抗重力上,根本没余力去学习平衡技巧。所以,我们做差分、做对数变换,目的从来不是为了“让数据看起来更漂亮”,而是为了剥离掉那个随时间系统性变化的“背景板”,暴露出那个相对稳定、可以被统计模型刻画的“前景”。这是所有后续建模工作的地基,地基不牢,一切皆空。
2.2 差分与对数变换:两种“外科手术刀”,各自擅长什么?
面对非平稳数据,最常见的“手术”就是差分(Differencing)和对数变换(Log Transform)。但很多人把它们当成可以互换的“开关”,这是大错特错。它们解决的是不同维度的问题,就像手术刀和止血钳,功能互补,不可替代。
差分(Differencing):专治“趋势病”
差分的本质,是计算相邻时间点的“增量”。对价格序列 $y_t$ 做一阶差分,得到的是 $ \Delta y_t = y_t - y_{t-1} $,也就是每天的涨跌点数。这个操作,直接抹平了原始序列中那个缓慢爬升的“大趋势线”。它把一个“位置”问题,转化成了一个“速度”问题。一个有趋势的价格序列,其均值是随时间线性增长的;而它的差分序列,其均值理论上应该围绕零波动(即没有系统性涨或跌)。这就是差分让数据“站稳”的核心逻辑——它强制让序列的均值回归到一个常数(通常是零)。在S&P 500案例中,我们看到原始价格图是一条向上的斜线,而一阶差分后的序列,则是一条在零轴上下随机波动的曲线,这正是平稳性的直观体现。对数变换(Log Transform):专治“波动病”
如果说差分对付的是“均值漂移”,那么对数变换对付的就是“方差爆炸”。观察S&P 500的历史,你会发现,指数在1000点时,一天涨跌50点是巨震;而当它涨到4000点时,一天涨跌50点只是毛毛雨。原始价格的绝对波动幅度,是随着价格水平本身一起放大的。这种“波动率随均值增大而增大”的现象,在统计学上叫“异方差性”(Heteroskedasticity)。它严重违反了经典线性模型对方差恒定的假设。对数变换 $ \log(y_t) $,则具有神奇的“压缩”效应。它能把一个乘法关系(比如“价格翻倍”)转化为一个加法关系(“对数值增加 $\log(2)$”)。更重要的是,对数序列的差分 $ \log(y_t) - \log(y_{t-1}) = \log(y_t / y_{t-1}) $,恰好等于收益率(Return)。而金融市场的收益率,其波动性(方差)通常比价格本身要稳定得多。这就是为什么在量化分析中,“对数收益率”是比“价格”或“简单收益率”更受青睐的建模对象。
提示:在S&P 500案例中,作者没有单独使用差分或对数,而是采用了“对数差分”(Log-Differencing)。这是一个非常精妙的选择。它同时解决了两个问题:对数变换稳定了波动率,差分又消除了趋势。最终得到的序列,既是“收益率”,又是“平稳的收益率”。这比单纯做一阶差分(得到的是“涨跌点数”,其波动率依然随价格水平变化)要稳健得多。
2.3 为什么是ARMA,而不是SARIMA?——模型选型背后的成本与收益权衡
看到标题里写着“SARIMA”,你可能会疑惑:既然前面几篇都在讲SARIMA,为什么最后一部分反而退回到了ARMA?这绝非倒退,而是一次精准的“降维打击”。
SARIMA模型的全称是“季节性自回归积分滑动平均”,它的参数是 $(p, d, q) \times (P, D, Q)_s$,其中 $d$ 和 $D$ 就是差分阶数。在本案例中,我们已经通过对数差分,将原始的非平稳价格序列,一次性、彻底地转化为了一个平稳序列。这意味着,我们已经完成了SARIMA中那个最核心、也最“昂贵”的步骤——积分(I)。现在,这个新序列已经满足了ARMA模型的全部输入要求。
那么,为什么不直接用SARIMA(p, 0, q),而要用ARMA(p, q)呢?答案在于模型复杂度与数据信息量的匹配。SARIMA是一个更通用的框架,但它也带来了额外的参数和计算开销。对于一个已经被充分“净化”过的平稳序列,强行套上一个包含“积分”模块的SARIMA模型,就像给一辆已经卸掉了所有货物的卡车,再装上一个空的货箱——不仅徒增负担,还可能因为模型过于复杂而引入过拟合风险。ARMA模型,作为SARIMA在 $d=0$ 时的特例,结构更简洁,参数更少,训练更快,解释性更强。在本案例中,我们追求的是一个清晰、稳健、可解释的基准模型,用以演示“非平稳到平稳”的完整流程。ARMA(1,1)就是一个完美的起点:它只有一个自回归项(AR)捕捉昨日价格变动对今日的影响,一个移动平均项(MA)捕捉昨日预测误差对今日的影响。这两个效应,在金融时间序列中都是真实存在的、基础性的动力学机制。选择它,不是因为它一定最优,而是因为它足够简单,能让我们把全部精力聚焦在“数据预处理”和“逆变换”这两个最易出错、也最核心的环节上。
3. 核心细节解析与实操要点:从代码到原理的逐层穿透
3.1 数据导入与切分:为什么测试集必须从2019年开始?
代码中,测试集被定义为“2019-01-01及之后的所有观测值”。这个看似随意的日期,背后有非常扎实的实操考量。在时间序列预测中,数据切分不是按比例(如8:2),而是按时间点。原因很简单:未来是未知的,我们永远只能用“过去”预测“紧邻的未来”。因此,训练集必须是测试集的严格前缀。
选择2019年作为分界点,是经过深思熟虑的:
- 避开黑天鹅事件:2020年初爆发的全球性公共卫生事件,导致金融市场出现了史无前例的剧烈波动和结构性断裂。如果把2020年的数据混入训练集,模型会学到一种极端的、非典型的“新常态”,而这在2019年及之前的数据中并不存在。用这样的模型去预测2019年的“正常”市场,结果必然失真。
- 保证测试集的“纯净性”:2019年本身是一个相对平稳的年份,没有发生重大金融危机或政策转向。用这一年及之后的数据作为测试集,能更公平、更真实地检验模型在“常规市场环境”下的泛化能力。
- 提供足够的评估窗口:从2019年初到数据截止日(2023年),有超过四年的时间跨度,足以计算多个滚动预测的RMSE,并绘制出清晰的趋势对比图,避免单点预测的偶然性。
注意:在你的实际项目中,切分点的选择应基于你的业务场景。例如,如果你预测的是季度财报,那么测试集应从最近一个完整财年之后开始;如果你预测的是节假日销量,那么测试集应包含至少一个完整的节假日周期。
3.2 平稳性检验的三重奏:为什么单靠ADF检验是危险的?
原文提到了三种检验方法:目视检查(Line Plot)、自相关图(ACF/PACF)和ADF检验。这是一个非常标准的“三重奏”,但新手常犯的错误是,只盯着ADF检验的p值,把它当作唯一的“判决书”。这是极其危险的。
Line Plot(线图):第一道肉眼防线
这是最直观、也最不容忽视的一步。打开你的数据,画一张最简单的折线图。如果图上赫然显示一条清晰的、贯穿始终的上升或下降斜线,或者一个明显的、不断扩大的喇叭口(方差增大),那么无论ADF的p值是多少,你都应该立刻停下来。因为这说明数据存在确定性趋势(Deterministic Trend),而ADF检验主要针对的是随机游走趋势(Stochastic Trend)。前者可以通过简单的数学函数(如线性、二次函数)去除,后者才需要差分。线图是你的“哨兵”,它能在任何统计检验之前,就发出最响亮的警报。ACF/PACF图(自相关图):第二道动态诊断
ACF(自相关函数)图显示的是序列与其自身滞后k期的相关性。一个平稳序列的ACF,会随着滞后阶数k的增加,迅速衰减至零附近,并在置信区间内随机波动。而非平稳序列的ACF,则会呈现出一种“拖尾”(Tail Off)现象——它衰减得非常缓慢,甚至在很高的滞后阶数上依然显著不为零。PACF(偏自相关函数)图则更进一步,它剔除了中间滞后项的影响,直接显示当前值与第k期滞后值的“纯”相关性。对于AR(p)过程,PACF会在p阶后截尾(Cut Off);对于MA(q)过程,ACF会在q阶后截尾。在S&P 500价格图中,我们看到ACF几乎是“粘在”上边界不下来,这就是典型的非平稳信号。ADF检验(增强型迪基-福勒检验):第三道统计终审
ADF检验是一个正式的假设检验。它的原假设(H0)是“序列存在单位根,即非平稳”。p值越小,我们越有理由拒绝原假设,从而认为序列是平稳的。但这里有个关键陷阱:ADF检验的效力高度依赖于序列中是否存在结构突变(Structural Break)。如果数据在某个时间点发生了根本性的制度变化(比如2008年金融危机后监管政策巨变),ADF检验可能会给出错误的结论。因此,它必须与前两步结合使用。一个稳健的结论是:线图无明显趋势 + ACF快速衰减 + ADF p值 < 0.05,三者缺一不可。
3.3 对数差分的数学实现与代码陷阱
对数差分的数学公式是:
$$ y_{\text{log_diff}, t} = \log(y_t) - \log(y_{t-1}) $$
在Python中,pandas提供了非常便捷的实现:
train_df['spx_log'] = np.log(train_df['spx']) train_df['spx_log_diff'] = train_df['spx_log'].diff()这段代码简洁明了,但暗藏一个极易被忽略的“坑”:.diff()方法会在第一行产生一个NaN值。因为第一天没有“前一天”可以相减。这个NaN值如果不处理,会直接导致后续的模型训练失败。
正确的做法是,在计算完差分后,立即删除或填充这个NaN:
# 方案一:删除(推荐,因为损失的数据点极少) train_df = train_df.dropna(subset=['spx_log_diff']) # 方案二:用前向填充(不推荐,会污染数据) # train_df['spx_log_diff'] = train_df['spx_log_diff'].fillna(0)删除是更优的选择,因为一个交易日的缺失,对于长达数年的序列来说,影响微乎其微。而用0填充,则人为地向数据中注入了一个“没有变化”的虚假信号,这在金融数据中尤其危险,因为它可能掩盖真实的市场休市或数据异常。
另一个容易被忽视的细节是对数变换的底数。np.log()默认使用自然对数(e),这完全没问题。但如果你使用np.log10(),虽然数学上等价(只是缩放了一个常数因子),但会导致后续的逆变换公式发生变化。务必保证整个流程中,对数的底数保持一致。
4. 实操过程与核心环节实现:从建模到预测的完整流水线
4.1 ARMA(1,1)模型的构建与参数解读
构建模型的代码如下:
from statsmodels.tsa.arima.model import ARIMA # 注意:这里用的是 ARIMA 模型,但将差分阶数 d 设为 0, # 因为我们已经手动做了对数差分,所以输入给模型的是平稳序列。 model = ARIMA(train_df['spx_log_diff'], order=(1, 0, 1)) fitted_model = model.fit() print(fitted_model.summary())模型摘要表(Summary Table)是理解模型是否“健康”的核心。我们需要重点关注以下几列:
- coef(系数):这是模型学到的参数。
ar.L1是自回归系数 $\phi_1$,ma.L1是移动平均系数 $\theta_1$。它们的值告诉我们,昨日的对数差分值(即收益率)对今日的影响有多大,以及昨日的预测误差对今日的影响有多大。 - std err(标准误):衡量系数估计的精确度。数值越小,说明估计越可靠。
- z(z统计量):系数除以其标准误。它用于检验系数是否显著不为零。
- P>|z|(p值):这是最关键的列。所有p值都应小于0.05(通常取0.05或0.01作为显著性水平)。如果某个p值很大(比如0.8),说明对应的系数很可能就是零,这个项对模型没有贡献,应该被剔除。在本案例中,摘要表显示所有p值都很小,说明ARMA(1,1)是一个合理的、各参数都显著的模型。
实操心得:我曾经在一个电力负荷预测项目中,发现AR项的p值高达0.6。这明确告诉我,该序列的“记忆”很短,不需要AR项。于是我果断将模型简化为MA(1),结果不仅训练速度加快,预测精度反而略有提升。模型不是越复杂越好,而是越“恰如其分”越好。
4.2 预测与逆变换:如何把“对数差分”变回“美元价格”?
这是整个流程中最关键、也最容易出错的一步。模型预测出的,是未来某一天的spx_log_diff值,即 $\log(y_t) - \log(y_{t-1})$。我们要把它还原成 $y_t$,即我们真正关心的S&P 500指数点位。
根据对数差分的定义,我们有: $$ \log(y_t) = \log(y_{t-1}) + y_{\text{log_diff}, t} $$ 两边同时取指数,得到: $$ y_t = y_{t-1} \times \exp(y_{\text{log_diff}, t}) $$
这个公式揭示了一个重要事实:预测不是孤立的,而是链式的。要预测第t天的价格,你需要知道第t-1天的真实价格(作为起点),然后乘上模型预测出的“增长倍数” $\exp(y_{\text{log_diff}, t})$。
在代码中,这被实现为:
# 1. 获取预测的 log_diff 值 pred_log_diff = fitted_model.predict(start=test_start_date, end=test_end_date) # 2. 创建一个 DataFrame 来存储所有必需的序列 pred_df = pd.DataFrame(index=pred_log_diff.index) pred_df['spx_log_diff_pred'] = pred_log_diff # 将原始价格序列(spx)向前移动一天,使其成为 'y(t-1)' pred_df['spx_lag'] = train_df['spx'].shift(1).reindex(pred_df.index) # 3. 执行逆变换:y(t) = y(t-1) * exp(y_log_diff(t)) pred_df['spx_pred'] = pred_df['spx_lag'] * np.exp(pred_df['spx_log_diff_pred'])这里有一个精妙的设计:pred_df['spx_lag']是通过train_df['spx'].shift(1)得到的。这意味着,对于测试集的第一天(2019-01-01),spx_lag的值是2018-12-31的收盘价,这正是我们进行链式预测所需的“昨日价格”。这个设计确保了预测的起点是真实、可靠的。
提示:如果你需要预测多步(比如预测未来5天),这个链式过程就需要循环执行。第一天用真实价格,第二天用第一天的预测价格作为起点,以此类推。这被称为“递归预测”(Recursive Forecasting),它会累积误差。在高精度要求的场景下,可以考虑“直接预测”(Direct Forecasting),即为每个预测步长单独训练一个模型。
4.3 置信区间的逆变换:为什么不能直接对区间取指数?
模型不仅能给出点预测,还能给出一个预测区间,比如95%的置信区间。这个区间是针对spx_log_diff的。如果我们天真地对区间的上下界直接取指数,会得到一个不对称的、且中心点不再是点预测的区间。这是错误的。
正确的做法是,先对点预测值取指数,再对区间进行相应的缩放。具体来说,如果模型给出的spx_log_diff的95%置信区间是 $[L, U]$,那么价格的95%置信区间就是: $$ [y_{t-1} \times \exp(L),\ y_{t-1} \times \exp(U)] $$
这保证了区间的几何意义:它表示的是价格可能的“倍数”范围,而不是一个简单的加减法。在代码中,这通常通过get_forecast()方法获得:
forecast = fitted_model.get_forecast(steps=len(test_df)) conf_int = forecast.conf_int(alpha=0.05) # 95% 置信区间 # conf_int 的列是 ['lower spx_log_diff_pred', 'upper spx_log_diff_pred'] pred_df['spx_pred_lower'] = pred_df['spx_lag'] * np.exp(conf_int['lower spx_log_diff_pred']) pred_df['spx_pred_upper'] = pred_df['spx_lag'] * np.exp(conf_int['upper spx_log_diff_pred'])5. 常见问题与排查技巧实录:那些只有踩过才知道的坑
5.1 问题速查表
| 问题现象 | 可能原因 | 排查与解决技巧 |
|---|---|---|
模型训练时报LinAlgError: Singular matrix | 训练数据中存在大量重复值、或方差为零的常量序列。 | 检查train_df['spx_log_diff'].describe(),看std是否为0。用train_df['spx_log_diff'].plot()查看是否有长时间的水平线段。解决方案:清洗掉异常的、无波动的数据段。 |
预测结果出现NaN或inf | 逆变换时,spx_lag为0或负数(对数无定义),或spx_log_diff_pred值过大导致exp()溢出。 | 检查pred_df['spx_lag']是否有非正数。S&P 500价格不可能为0或负,所以这一定是数据导入错误。检查pred_df['spx_log_diff_pred']的最大值,如果超过700,np.exp(700)就会溢出。此时应检查模型是否严重过拟合,或数据中是否存在极端离群点。 |
| RMSE指标异常高,远超预期 | 测试集包含了剧烈波动的“黑天鹅”事件,或模型在训练集上就已过拟合。 | 绘制训练集上的残差图fitted_model.resid.plot()。如果残差呈现明显的模式(如周期性、趋势),说明模型未能捕捉到数据中的关键结构,需要调整p/q参数或尝试其他模型(如加入外生变量)。 |
| 预测曲线与实际曲线“平行但错位” | 逆变换的起点(spx_lag)错误。例如,用了train_df['spx'].iloc[-1]作为所有预测日的起点,而不是逐日更新的spx_lag。 | 仔细检查pred_df['spx_lag']的值。它应该是一个与pred_df.index完全对齐的、逐日变化的序列,其第一个值必须是测试集开始前一日的真实价格。 |
5.2 我踩过的三个最深的坑
坑一:“完美”的ADF p值,骗过了我的眼睛
在一个零售销售预测项目中,ADF检验的p值是0.001,看起来非常平稳。但我画出线图后,发现数据在每年Q4都会出现一个巨大的、固定的峰值(圣诞季)。这是一个典型的确定性季节性,而不是随机波动。ADF检验对此无能为力。我后来加入了季节性虚拟变量(Dummy Variables),才真正解决了问题。教训:统计检验是工具,不是裁判。你的双眼和业务直觉,永远是第一道防线。
坑二:把“预测区间”当成“价格区间”
我曾把模型输出的spx_log_diff的置信区间[ -0.02, 0.03 ],直接加减到点预测上,得到价格区间[y_pred - 0.02, y_pred + 0.03]。这完全是错误的。0.02和0.03是“对数差分”,代表的是约-2%和+3%的收益率。正确的区间应该是[y_pred * 0.98, y_pred * 1.03]。这个错误导致我对风险的评估严重失真。教训:时刻牢记你处理的是什么尺度的数据。对数、差分、原始值,三者之间有严格的数学转换关系,一步错,步步错。
坑三:忽略了“预测的起点”
在一次内部分享中,我演示了这个S&P 500预测流程。一位同事问:“如果我想预测2025年1月1日的价格,我的起点spx_lag是什么?”我当时脱口而出:“用2024年12月31日的收盘价啊。”他接着问:“那2024年12月31日的价格,你是怎么知道的?”我愣住了。是的,对于超长期预测,我们没有“昨日价格”作为起点。这时,我们必须采用“递归预测”,即用模型自己预测出的2024年12月31日的价格,作为2025年1月1日的起点。而每一次递归,都会把上一步的预测误差带进来。所以,长期预测的不确定性,是呈指数级增长的。这个坑让我深刻认识到:任何预测模型,其价值都高度依赖于预测的时间跨度。对冲基金用它来预测未来几天的波动,这很靠谱;但用它来预测十年后的股市点位,那就纯粹是算命了。
6. 模型评估与结果解读:超越RMSE的深度洞察
6.1 RMSE之外,我们还应该看什么?
Root Mean Squared Error(RMSE)是一个经典的、易于计算的误差指标。它给出了预测误差的“平均大小”。但仅看RMSE,就像只看一场考试的总分,而忽略了每一道题的对错。对于一个严肃的预测模型,我们必须进行更深入的诊断。
残差分析(Residual Analysis):模型的残差(实际值 - 预测值)应该是一个白噪声序列——即均值为零、方差恒定、且没有任何自相关性。我们可以画出残差的ACF图。如果图中有很多显著不为零的条形,说明模型还有未捕捉到的模式,需要改进。我习惯用
plot_acf(fitted_model.resid, lags=40)来做这个检查。误差分布(Error Distribution):画出残差的直方图。它应该近似一个以零为中心的钟形曲线(正态分布)。如果分布严重偏斜(Skewed),或者有很长的尾巴(Fat Tails),说明模型在某些极端情况下表现很差。在金融预测中,我们尤其关注“左尾”(负误差过大),因为这代表模型严重低估了下跌风险。
方向性准确率(Directional Accuracy):对于投资决策,有时“涨跌方向”比“具体点位”更重要。我们可以计算模型成功预测价格是上涨还是下跌的比例。一个RMSE很低但方向准确率只有50%(相当于抛硬币)的模型,在实际交易中毫无价值。
6.2 S&P 500预测结果的现实启示
当我们把ARMA(1,1)模型的预测结果(蓝色)与真实价格(红色)画在同一张图上时,会看到一个典型的现象:预测曲线紧紧“贴着”真实曲线,但总是慢半拍。它能很好地捕捉到价格的中长期趋势,但在短期的剧烈波动(比如单日暴跌5%)面前,显得力不从心。
这恰恰揭示了统计模型的本质:它是一个“平滑器”,一个“趋势提取器”,而不是一个“水晶球”。它擅长描述数据中那些重复出现的、概率性的模式,但对于由单一、不可预测的新闻事件(如突发的地缘政治冲突)驱动的跳跃,它无能为力。因此,一个理性的使用者,不会期望模型给出一个精确到个位数的预测,而是会把它看作一个风险评估的锚点。例如,模型预测未来一个月S&P 500有95%的概率落在3800-4200点之间。这个区间本身,就比那个单一的点预测(比如4000点)蕴含了多得多的、关于未来不确定性的宝贵信息。
最后再分享一个小技巧:在部署模型到生产环境时,我从不在代码里硬编码
test_start_date = '2019-01-01'。我会把它变成一个配置文件中的参数,或者一个命令行参数。这样,当需要回测一个新的时间段,或者将模型迁移到另一个资产(比如纳斯达克指数)时,我只需要修改一个地方,而不用去翻找和修改几十行代码。这个小小的习惯,为我节省了无数个加班的夜晚。