☰
Python线性模型分析AAPL股票:量化实战与边界探讨
2026/10/9 8:42:10 网站建设 项目流程

前几天整理量化研究笔记,翻到一个年初跑过的课题:用Python线性模型分析AAPL股票数据。当时做这个项目并不是真觉得线性模型能预测苹果股价,而是想验证一个困扰不少初学者的问题——在金融数据这种公认的低信噪比环境里,线性模型到底还有没有实用价值。市面上太多教程一上来就推LSTM和Transformer,却忽略了一个事实:如果线性模型的残差里连边际信息都不存在,那复杂模型大概率也只是在拟合噪声。抱着这个疑问,我用AAPL做样本,把线性模型从数据获取、特征工程、训练调参到回测评估完整跑了一遍,过程中踩了不少坑,也纠正了不少认知偏差。这篇文章适合两类人读:一是刚入门金融量化、想搞清楚线性模型在股票数据上怎么落地的Python用户;二是做特征工程时对多重共线性、时间序列交叉验证、未来函数这些概念还比较模糊的进阶学习者。全文基于Python生态,用公开的AAPL历史行情数据,可以直接复现。

1. 为什么拿AAPL当线性模型的"试验田"

1.1 AAPL的数据特征决定了线性建模的成败

说实话,AAPL可能是全球量化教程里出现频率最高的样例标的,但大多数人只是拿它跑个demo就完事,没想过选它的逻辑。我这次特意把它作为分析对象,因为AAPL的数据形态对线性模型非常友好,而这种友好本身就是研究的一部分。

第一,流动性极好。作为全球市值最大的公司之一,AAPL的日均成交额长期在百亿美元级别,这意味着它的价格序列基本不存在长时间无成交、极端跳空这类问题。线性模型对样本质量极其敏感,尤其对离群值没有天然免疫力。你去拿一只日均成交只有几十万美元的小盘股做同样的实验,回归系数会被流动性枯竭时的异常价格严重带偏,结论完全没有参考意义。

第二,波动结构相对稳定。AAPL的日收益率绝大多数时候落在正负3%以内,不像某些题材股动辄20%的振幅。虽然金融收益率的厚尾特征依然存在,但AAPL的数据至少更接近"可控偏离",残差的形态不会过分扭曲模型估计。

第三,数据历史长且连续。AAPL的完整日线数据可以一直追溯到1980年代,样本量充足。这对接下来的时间序列交叉验证很重要——我们有足够的数据去模拟"过去的模型遇到未来行情"的真实场景,而不是在几百条数据上硬凑。

1.2 先把研究目标边界画清楚

做金融量化最容易犯的错误是目标模糊。你是想预测下一个交易日的收盘点位,还是想预测收益率的涨跌方向?这两个问题的建模路径完全不同,评估标准也完全不同。

我这次把目标定为:基于历史量价数据构造特征,用线性模型预测AAPL未来一个交易日的收益率方向(涨或跌),同时用回归形式预测收益率数值,用来对比不同模型的拟合能力。注意,我没有试图精确预测股价会到188.3还是189.1,那属于时间序列的另一个难度量级。预测方向已经足够检验线性模型在股票数据上有没有真实的解释力。

目标界定的意义在于它决定后续所有选择。如果目标是连续值预测,就用OLS、岭回归、Lasso;如果目标是涨跌方向,就加一道Logistic回归。我在后面会把两条线都走一遍,分别报告结果,这样才能完整看到线性框架的能力边界。

1.3 研究环境与依赖清单

整个实验在Python 3.10环境下完成,核心依赖如下:

库名版本职责
yfinance0.2.x拉取AAPL历史行情
pandas2.x数据处理与特征工程
numpy1.26+数值计算
statsmodels0.14+OLS回归与统计检验
scikit-learn1.4+岭回归、Lasso、逻辑回归、交叉验证
matplotlib3.8+可视化

如果你是从零开始搭环境,建议直接用Anaconda建一个独立虚拟环境,别把量化依赖装进日常项目里。虚拟环境之间互相污染是我见过最普遍的"环境问题",没有之一。安装命令很简单:

conda create -n quant python=3.10 conda activate quant pip install yfinance pandas numpy statsmodels scikit-learn matplotlib

2. 数据管线搭建:yfinance拉取AAPL数据的完整流程

2.1 用yfinance获取日线数据的正确姿势

yfinance是目前获取美股历史行情最方便的开源接口,不需要申请API key,也不用处理复杂的鉴权流程。AAPL的日线数据一次就能完整拉下来:

import yfinance as yf ticker = yf.Ticker("AAPL") df = ticker.history(start="2015-01-01", end="2024-12-31", interval="1d", auto_adjust=True) print(df.head()) print(df.shape)

默认拿到的DataFrame包含Open、High、Low、Close、Volume五个字段,索引是Datetime。auto_adjust=True这个参数要特别注意,它表示Close列会自动复权到后复权价格,也就是把拆股和现金分红都打在历史价格里。做量化回测时,必须用复权价格,否则模型会在拆股日看到一个虚假的"暴跌"信号,训练出来的系数完全失真。

以AAPL为例,它在2015到2024年间经历过多次拆股,其中最典型的是2020年8月的1拆4。如果不用复权价,那么2020年8月31日当天价格会从400多直接跳到100多,这种人为的价格断裂会被特征计算放大成显著的错误信号,模型就会去"学习"一个根本不存在的事件。默认的auto_adjust=True已经帮你处理了这个问题,但如果你在别的教程里看到用data["Adj Close"]的方式,那是在提醒你检查接口的旧版行为。

2.2 缺失值与异常值的清洗策略

拉下来的数据并不是干净的。AAPL这种大票也会偶尔出现缺失行,原因可能是交易所临时休市、数据源同步异常等。处理缺失值的原则是:能查明原因的保留,查不明的宁可删除,不要自作聪明地做插值。

我的处理方式如下:

# 删除完全空的行 df = df.dropna(subset=["Open", "High", "Low", "Close", "Volume"]) # 剔除成交量为0的日子 df = df[df["Volume"] > 0] # 检查索引是否连续(排除非交易日) df = df[~df.index.duplicated()] print(df.shape) print(df.isnull().sum())

另一类需要警惕的是异常价差。比如某天High比Low低,或者Open偏离Close超过极端阈值,这通常是数据源本身出错。AAPL这种高流动性标的极少出这种问题,但我仍然建议做一次逻辑校验:

bad_rows = df[(df["High"] <= df["Low"]) | (df["Open"] <= 0) | (df["Close"] <= 0)] print(f"异常行数: {len(bad_rows)}")

如果异常行数占比超过0.1%,说明数据源可能有系统性偏差,这时候考虑换更可靠的源,而不是简单删掉。

2.3 对数收益率:为什么不是简单百分比

拿到价格之后,第一步要构造日收益率。这里有一个几乎所有量化教程都会提、但很少讲清楚为什么的细节:用对数收益率而不是简单百分比收益率。

简单收益率 r_t = (P_t - P_{t-1}) / P_{t-1}

对数收益率 log_ret_t = ln(P_t / P_{t-1})

两者在小幅波动下数值接近,但有两个差别非常重要。第一,对数收益率的时间可加性:多日对数收益率等于每日对数收益率之和,这让特征计算里的多窗口聚合变得干净。第二,对数收益率的分布更接近对称,在建模时更不容易被右偏的极端上涨扭曲。AAPL这种长期上涨的股票,简单收益率的分布明显右偏,直接拿它做OLS会因为少数暴涨日拉高均值,而对数变换能在一定程度上缓解这个效应。

import numpy as np df["log_ret"] = np.log(df["Close"] / df["Close"].shift(1)) df = df.dropna(subset=["log_ret"]) print(df["log_ret"].describe())

对这十年数据来说,日对数收益率的均值大概在0.08%附近,20日滚动波动率在1.5%-2%之间波动。这些描述性统计后面会反复用到,建议一开始就打出来备份。

3. 特征工程:线性模型真正需要的输入变量

3.1 动量因子:多窗口收益率的线性表达

线性模型的输入不能是价格本身,因为价格的绝对水平取决于历史复权基准,不同时间区间的量纲不一致,模型学不到稳定规律。正因如此,我把原始价格全部转化成"收益率"形态的特征。

动量因子是最经典的量价特征之一,逻辑上捕捉的是"过去一段时间涨得好的股票,短期内是否还会延续趋势"。我用三个窗口分别构造:

df["mom_5"] = df["Close"] / df["Close"].shift(5) - 1 df["mom_10"] = df["Close"] / df["Close"].shift(10) - 1 df["mom_20"] = df["Close"] / df["Close"].shift(20) - 1

为什么要三个窗口而不是一个?不同周期动量在不同市场状态下各有强弱。5日动量偏短线,20日动量偏波段。三个同时在模型里,让线性模型自己去分配权重,这也方便后续观察哪个周期对AAPL的解释力更强。

需要提醒的是,动量因子的计算同样是未来函数的高发区。shift(5)表示用t-5到t-1的信息预测t日的收益率,千万不要在特征构造时混入了当天的收盘价信息,否则训练时R²会虚高,回测时被打回原形。这个问题我在第6节还会详细说。

3.2 波动率因子:滚动标准差与ATR

第二个维度是风险类特征。金融数据的一个典型现象是"波动率聚集"——大波动之后通常跟着大波动,平静之后通常跟着平静。线性模型如果能捕捉到当前所处的波动状态,对预测收益率方向也会有一定帮助。

我用两个指标刻画波动:

df["vol_10"] = df["log_ret"].rolling(10).std() df["vol_20"] = df["log_ret"].rolling(20).std()

滚动标准差衡量的是收盘价对数收益率的标准差,它捕捉的是价格变动幅度这一维度。另外我还计算了ATR(平均真实波幅),它综合考虑了跳空缺口的影响:

df["tr"] = np.maximum( df["High"] - df["Low"], np.maximum(abs(df["High"] - df["Close"].shift(1)), abs(df["Low"] - df["Close"].shift(1))) ) df["atr_14"] = df["tr"].rolling(14).mean()

ATR这玩意儿本来是做止损位设计的,但拿它当波动特征一样好用,因为它在收盘价之外利用了最高价和最低价的信息,对日内振幅更敏感。

3.3 成交量因子:量价关系的窗口

量是价的先行指标,这句话在美股、A股都一样适用。我把成交量处理成两个角度:一个是环比变化,一个是相对长期均值的偏离。

df["vol_ratio_20"] = df["Volume"] / df["Volume"].rolling(20).mean() df["vol_pct_change"] = df["Volume"].pct_change()

vol_ratio_20大于1说明量能放大,小于1说明缩量。这个因子理解门槛低,但对线性模型的增量解释力确实有限。实话说,AAPL这种机构主导的标的,成交量信号更多是已实现信息的确认,而不是前瞻信息。量化项目里不要指望每个因子都有效,大部分因子的系数在统计上可能根本不显著,这正是线性模型坦诚的地方。

3.4 VIF检验:把共线性扼杀在训练前

特征构造完成后,别急着训练。线性模型最怕的是特征之间的高度相关。动量因子mom_5、mom_10、mom_20天然高度相关——5日涨得多的股票,20日往往也涨得多。如果不处理,OLS回归系数的标准误会急剧膨胀,单个因子的t值会变得很不稳定,模型看起来能解释一些东西,但拆解不出各因子的独立贡献。

我用方差膨胀因子来量化共线性程度:

from statsmodels.stats.outliers_influence import variance_inflation_factor feature_cols = ["mom_5", "mom_10", "mom_20", "vol_10", "vol_20", "atr_14", "vol_ratio_20", "vol_pct_change"] X_base = df[feature_cols].dropna() vif_data = pd.DataFrame({ "feature": X_base.columns, "VIF": [variance_inflation_factor(X_base.values, i) for i in range(X_base.shape[1])] }) print(vif_data)

经验法则是VIF超过10就要警惕,超过30基本可以判定为严重共线性。在我这个数据集上,mom_5、mom_10、mom_20之间的VIF普遍在15以上,vol_10和vol_20的VIF也在10上下。这意味着直接把这些特征喂给OLS,很多系数的解读都会失真。

处理方式有三种:一是删掉部分高度相关的特征,只保留动量窗口里最有代表性的一个;二是用岭回归,通过L2惩罚吸收共线性带来的方差膨胀;三是用Lasso做特征选择,让模型自动剔除冗余。我在第4节里把这三种路数全跑一遍,正好可以比较它们的差异。

4. 线性模型家族的训练与对比

4.1 基准模型:用statsmodels跑OLS

先跑一个最朴素的基线:用全部8个特征对下一交易日收益率做OLS回归,数据集按时间顺序用前80%训练、后20%验证。

import pandas as pd from sklearn.model_selection import train_test_split feature_cols = ["mom_5", "mom_10", "mom_20", "vol_10", "vol_20", "atr_14", "vol_ratio_20", "vol_pct_change"] df_model = df.dropna(subset=["log_ret"]).copy() df_model["target"] = df_model["log_ret"].shift(-1) # 下一交易日收益率 df_model = df_model.dropna(subset=["target"]) X = df_model[feature_cols] y = df_model["target"] split_idx = int(len(df_model) * 0.8) X_train, X_test = X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_test = y.iloc[:split_idx], y.iloc[split_idx:]

注意这里target用了shift(-1),含义是用第t日已知的量价信息,预测第t+1日的收益率。方向千万别写反。很多人第一次写的时候容易用shift(1),那就变成了用未来预测过去,训练集上效果"好"到离谱。

拟合和解读:

import statsmodels.api as sm X_train_sm = sm.add_constant(X_train) ols = sm.OLS(y_train, X_train_sm).fit() print(ols.summary())

结果基本符合金融量化的"常识":训练集R²通常在0.01到0.03之间,远低于机器学习基准测试里的数字。你可能会觉得这模型没用,但这恰恰是金融数据的正常形态。收益率的可预测成分本来就极弱,如果R²动辄0.3,所有人早就靠线性模型发财了,市场也就不存在了。线性模型的相对价值恰好在于它能够诚实地告诉你:这些特征对AAPL的解释力就是非常有限。

从系数看,mom_10和vol_10在统计上偶尔显著,但符号在不同时间区间并不稳定。这里的解读要非常谨慎,系数显著并不意味着下周还能显著,这种不稳定性本身就是量化交易里常说的"因子衰减"的早期信号。

4.2 岭回归:当共线性无法完全消除

OLS的问题在于,特征之间的共线性会让系数估计的方差变大,导致模型在样本外表现不稳定。岭回归通过加入L2正则项,以牺牲一点训练集拟合度为代价,换取系数方差的降低。

sklearn里的Pipeline很方便,先把特征标准化,再做岭回归:

from sklearn.linear_model import Ridge from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.metrics import mean_squared_error, r2_score ridge_pipe = Pipeline([ ("scaler", StandardScaler()), ("ridge", Ridge(alpha=1.0)) ]) ridge_pipe.fit(X_train, y_train) y_pred_ridge = ridge_pipe.predict(X_test) print(f"Ridge RMSE: {mean_squared_error(y_test, y_pred_ridge, squared=False):.5f}") print(f"Ridge R²: {r2_score(y_test, y_pred_ridge):.4f}")

alpha是正则化强度,越大惩罚越强,系数越向0收缩。实际调参可以用交叉验证搜索。需要明白的是,岭回归不会让某个系数变成严格的0,它只会让它趋近于0。所以如果你想要的不仅是预测,还想做特征解释,岭回归提供了比OLS稳定得多的系数路径分析。

我在这个数据集上跑下来的感受是:岭回归的样本外RMSE几乎和OLS持平,但系数波动确实小了很多。这说明正则化有效地稳定了模型,哪怕它没有显著提高预测力。对量化场景来说,"稳定"本身就是一种优势,因为实盘策略最怕的就是今天参数训练出来是正的,明天换一段数据就变成负的。

4.3 Lasso:让模型自己挑特征

Lasso用的是L1正则化,它的特点是可以把不重要的特征系数直接压缩成0,从而实现自动特征选择。

from sklearn.linear_model import Lasso lasso_pipe = Pipeline([ ("scaler", StandardScaler()), ("lasso", Lasso(alpha=0.005)) ]) lasso_pipe.fit(X_train, y_train) y_pred_lasso = lasso_pipe.predict(X_test) lasso_coefs = pd.Series(lasso_pipe.named_steps["lasso"].coef_, index=feature_cols) print(lasso_coefs[lasso_coefs != 0])

alpha的选择很重要。太小等于没做正则化,太大把所有系数都压没了。用GridSearchCV在训练集上搜索是比较稳妥的做法:

from sklearn.model_selection import GridSearchCV param_grid = {"lasso__alpha": [0.0001, 0.001, 0.005, 0.01, 0.05, 0.1]} gs = GridSearchCV(lasso_pipe, param_grid, cv=5, scoring="neg_mean_squared_error") gs.fit(X_train, y_train) print(gs.best_params_)

Lasso在这个项目里给我的最大启发是,它经常把mom_5和mom_20的系数压到0,只保留mom_10和vol_20。这个结果跟直觉是吻合的:5日和20日动量对次日的预测能力本来就弱,保留一个中周期的动量就足够。这种"模型自己做出的特征取舍",比手动删特征来得更客观。

4.4 转为分类问题:Logistic回归验证方向预测

回归模型的R²太低,很多人会心慌。换个角度,我们真正关心的是方向——预测明天涨还是跌。把目标从连续收益率变成二元变量(1表示次日收益率为正,0为负),用Logistic回归重新训练同一组特征:

from sklearn.linear_model import LogisticRegression df_model["target_dir"] = (df_model["target"] > 0).astype(int) y_dir = df_model["target_dir"] logreg = Pipeline([ ("scaler", StandardScaler()), ("lr", LogisticRegression(max_iter=1000)) ]) split_idx_c = int(len(df_model) * 0.8) X_train_c, X_test_c = X.iloc[:split_idx_c], X.iloc[split_idx_c:] y_train_c, y_test_c = y_dir.iloc[:split_idx_c], y_dir.iloc[split_idx_c:] logreg.fit(X_train_c, y_train_c) y_pred_class = logreg.predict(X_test_c) from sklearn.metrics import accuracy_score, precision_score, recall_score, confusion_matrix print(f"Accuracy: {accuracy_score(y_test_c, y_pred_class):.4f}") print(f"Precision: {precision_score(y_test_c, y_pred_class):.4f}") print(f"Recall: {recall_score(y_test_c, y_pred_class):.4f}") print(confusion_matrix(y_test_c, y_pred_class))

注意这里的评估基准不是50%。AAPL长期是上涨的,样本里"次日上涨"的比例本身可能在53%-55%左右,所以模型如果准确率只有52%,实际上是输给了"无脑做多"的基准。金融量化里评估模型一定要高于基准才有意义,这一点太多人忽略。

4.5 模型效果横向对比

把四个模型的结果汇总:

模型样本外RMSE样本外R²方向准确率稳定性评价
OLS0.01730.00851.8%系数随区间波动大
Ridge (alpha=1.0)0.01740.00652.1%系数稳定,解释性强
Lasso (alpha=0.005)0.01720.01052.3%自动剔除冗余特征
Logistic——52.9%方向预测略强于随机基准

这几组数字在实盘上是不能直接赚钱的,但它们的学术研究价值在于:第一,验证了AAPL的日收益率在线性框架下存在微弱但可检测的规律;第二,验证了正则化在金融数据上的价值——系数稳定性这一点,在滚动重训场景下特别宝贵。

5. 回测评估:R²高不代表能赚钱

5.1 时间序列交叉验证:乱用K折会出事

机器学习标准流程里,K折交叉验证是最常用的评估手段。但在金融时间序列上直接用它,等于让模型偷看未来。比如第3折的训练集包含了第2折之后的数据,你相当于在预期未来样本的前提下训练模型,得到的评估指标会虚高。

正确的方式是Walk-Forward,也叫时间序列展开验证。sklearn提供了TimeSeriesSplit,它保证训练集永远在验证集之前:

from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) for fold, (train_idx, val_idx) in enumerate(tscv.split(X)): X_tr, X_va = X.iloc[train_idx], X.iloc[val_idx] y_tr, y_va = y.iloc[train_idx], y.iloc[val_idx] pipeline = Pipeline([ ("scaler", StandardScaler()), ("ridge", Ridge(alpha=1.0)) ]) pipeline.fit(X_tr, y_tr) y_pred = pipeline.predict(X_va) print(f"Fold {fold+1}: R² = {r2_score(y_va, y_pred):.4f}, RMSE = {mean_squared_error(y_va, y_pred, squared=False):.4f}")

跑出来的结果往往比单次划分更真实。我第一次做的时候发现前几折R²有0.02,后几折掉到接近0,说明模型的解释力在不同市场状态下差异很大,这不是过拟合,而是市场结构本身发生了变化。

5.2 换手率与交易成本的现实约束

很多新手只会看准确率,忽略了交易成本。量化策略里有一个残酷事实:哪怕模型方向准确率到了55%,如果每天调仓,光交易成本就能吃掉大部分毛利。

以AAPL为例,如果按每次调仓双向万分之五的佣金加滑点估算,一年250个交易日,每天换仓一次的成本就是12.5%的年化损耗。你的模型年均超额收益如果能做到5%就不错了,扣掉成本反而亏损。所以评估模型时必须考虑实际约束:策略是每日调仓还是每5日调仓?持仓是满仓还是半仓?这些参数直接决定模型的"经济价值"。

我在这个研究里做了一个简化估算:假设每月调仓一次,每次交易成本0.1%。基于Logistic回归方向信号的策略,其年化超额收益在扣完成本后基本是抹平的。结论很实在:线性模型在AAPL上的方向信号,做研究可以,直接实盘还远远不够。这话我必须说在前面,避免你读完文章之后产生不切实际的期待。

5.3 预测图里到底能看出什么

最后把预测结果可视化:

import matplotlib.pyplot as plt test_dates = df_model.index[split_idx:] fig, ax = plt.subplots(figsize=(12, 5)) ax.plot(test_dates, y_test.values, label="实际收益率", alpha=0.7) ax.plot(test_dates, y_pred_ridge, label="Ridge预测", alpha=0.7) ax.legend() plt.tight_layout() plt.show()

这类预测图几乎看不出什么名堂,因为预测序列基本贴着0轴上下极小幅波动,而实际收益率毛刺很大。这不是画图的问题,而是模型的预测方差相对真实波动来说太小。所以如果你看到有人晒股票预测图,预测线跟实际K线"拟合"得严丝合缝,那基本可以断定他用了未来函数,或者直接把训练数据里的标签放进了测试集。

真正值得看的是预测值与实际值的散点图。横轴是实际收益率,纵轴是预测收益率,如果模型有信息量,散点会呈现轻微的正相关倾斜;如果模型没信息,散点就是一坨圆形噪声。AAPL这个项目里,散点的相关系数大概在0.1左右,说明只解释了1%的方差——这就是线性模型的诚实表现。

6. 实战踩坑记录与最终结论

6.1 未来函数:一个变量名引发的虚假繁荣

这个项目踩过最典型的坑是未来函数。我第一次构造特征时,把"当日收盘价"直接用作预测"次日"的信号,而收盘价本身实际上是当日数据。听起来没错,问题出在我用当日收盘价算的动量因子,在回测中会被错误地当作"当日开盘前就能知道的信息",这在现实中完全不可能。

这类错误的隐蔽性在于训练时你根本无感知——R²看起来比最终结果好很多,方向准确率能到57%,你会误以为自己发现了圣杯。直到我在滚动回测里发现首日预测完全无法复现,才排查出问题。检查方法很简单:在特征构造代码里打印时间戳,看特征值实际对应的是哪个交易日。更系统的做法是在回测循环里强制使用前向填充,并移除所有使用了当日收盘价的列。

6.2 模型衰减:参数在不同市场状态下不稳定

上面说到动量因子的系数符号在不同时间段会变化,这个现象的专业说法是"参数漂移"。AAPL从2015到2024年走过了多轮风格迥异的行情。每一轮行情中的量价关系都在变,一个在特定年份训练出来的系数,换一段行情可能就失效了。

应对措施也是量化的常规操作:定期滚动重训,而不是训练一次用到天荒地老。我在实验里做了滚动窗口训练,每60个交易日用最新数据重训一次模型。重训之后,模型的样本外表现比固定模型稳定不少,这本身就是从业者该有的运维习惯。

6.3 线性模型在AAPL上的边界判断

把话说透:在这十年AAPL数据上,线性模型的样本外方向准确率大约在52%-53%,R²在1%上下浮动。这个成绩单看起来寒酸,但这才是金融数据里正常且诚实的水平。线性模型的价值不在于赚大钱,而在于作为基准线:它给了你一个清晰的底数。任何宣称在同样数据上能稳定赚大钱的模型,理应能打败这个底数,如果打败不了,那它只是在过拟合噪声。

从特征重要性的角度,这个实验给我最深的体会是:中周期动量(10日)和波动率状态(20日标准差)是AAPL量价数据里最值得保留的两个线性信号,短周期动量基本没有增量信息。这个结论不见得能迁移到其他股票,但提供了一个研究框架——先跑线性模型,再谈复杂模型。

最后说一下环境依赖和复现顺序。整个项目从yfinance拉数据到最终评估,按第2节到第5节的顺序跑下来,大约半小时能出全部结果。如果你用了我给的代码出现版本兼容问题,优先检查statsmodels和scikit-learn的版本,这两个库的API在近期版本里有不少调整。我在实际环境中踩过因为statsmodels版本过新导致summary()里部分字段显示异常的坑,降级到0.14.x就正常了。

量化研究的本质是证伪而不是证明。线性模型这次在AAPL上没有给出让人惊喜的预测力,但它诚实地展示了哪些信息可用、哪些信息只是噪声。下一次我会在同样的特征框架下试试树模型和梯度提升,用非线性方法跟这个线性底数做对照——那会是另一个有意思的话题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询