
用 Python 揭秘均值回归策略:你的收益从何而来?
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 500 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
每隔一段时间,总会冒出一些标题党:「AI 已经能预测股市了!」「用机器学习躺着赚钱!」作为一个同时对机器学习和量化金融感兴趣的人,你可能也很想知道:这些说法到底有几分是真的?
与其刷视频、翻论文,不如自己动手从零搭一套股价预测系统。这篇文章就带你走一遍完整流程:从数据采集、特征工程、模型训练,到新闻情感分析,再到用 FastAPI 把结果暴露成一个 Web 接口。
先把结论放在最前面:这个项目的目标从来不是「打败华尔街」,而是回答一个更实际的问题——我能不能设计一套端到端的系统,自动完成数据采集、特征构造、模型训练、新闻分析,并通过接口对外提供预测?
答案是:能。但真正的收获,往往不在预测本身。
这个项目把好几块技能揉在了一起:
与其把它们当成零散的技能点,不如整合进一个完整应用里。最终这套系统会自动完成下面这一整条流水线:
它不是一堆孤立的 Notebook,而是一个像模像样的真实应用。
整个系统主要由四大部分组成。
系统会自动下载历史 OHLCV 数据,也就是开盘价(Open)、最高价(High)、最低价(Low)、收盘价(Close)和成交量(Volume)。
但只喂原始价格是不够的,流水线会先把数据加工成适合机器学习的特征,比如:
一个关键认知:原始行情数据本身很少能给模型足够的上下文,特征工程带来的提升,往往比单纯换算法要大得多。
这个项目里最大的教训之一是:机器学习模型根本「看不懂」股价,它只理解由数值特征表示的模式。
部分构造出来的特征包括:
这里有个很聪明的设计:预测目标不是明天的绝对价格,而是明天的百分比收益率,之后再把收益率换算回预测价格。相比直接预测原始价格,这样学习问题更稳定。
下面用 Python 简单演示几个核心特征怎么算(假设已经用 pandas 拿到了行情 df):
import pandas as pd
# df 至少包含一列 'Close' 收盘价
# 计算每日百分比收益率(作为预测目标)
df["return"] = df["Close"].pct_change()
# 计算多周期移动平均,捕捉不同时间尺度的趋势
df["ma_7"] = df["Close"].rolling(window=7).mean() # 7 日均线
df["ma_14"] = df["Close"].rolling(window=14).mean() # 14 日均线
df["ma_21"] = df["Close"].rolling(window=21).mean() # 21 日均线
# 滚动波动率:用收益率的标准差衡量近期波动大小
df["volatility"] = df["return"].rolling(window=7).std()
# 滞后特征:把过去几天的收盘价作为「已知信息」喂给模型
df["lag_1"] = df["Close"].shift(1) # 昨天收盘价
df["lag_2"] = df["Close"].shift(2) # 前天收盘价
df["lag_3"] = df["Close"].shift(3) # 大前天收盘价
# rolling 和 shift 会在开头产生 NaN,训练前需要丢弃
df = df.dropna()易错点提醒:rolling 和 shift 都会在序列开头产生空值(NaN),如果不处理,模型训练时会直接报错,所以最后一定要 dropna()。
这里尝试了多种算法:
作者没有想当然地认为「越复杂越好」,而是用同一套流水线公平地评估每个模型,指标包括:
一个反直觉的结论:树模型并没有稳定地打败线性回归,在好几组实验里,反而是更简单的线性回归在没见过的数据上泛化得更好。
这提醒我们:在噪声常常盖过信号的金融时间序列里,更复杂,并不自动意味着更准确。
下面是一个「用同一套流程对比多个模型」的思路示例:
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import numpy as np
# 待对比的模型放进字典,方便统一循环评估
models = {
"线性回归": LinearRegression(),
"随机森林": RandomForestRegressor(n_estimators=100, random_state=42),
}
for name, model in models.items():
model.fit(X_train, y_train) # 在训练集上拟合
y_pred = model.predict(X_test) # 在测试集上预测
mae = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
# 用同样的指标横向对比,避免「凭感觉」选模型
print(f"{name} -> MAE: {mae:.4f}, RMSE: {rmse:.4f}, R2: {r2:.4f}")市场不只被历史价格驱动,新闻也在起作用。为了捕捉这部分信息,系统加了一条情感分析流水线,它会:
最终界面会展示:正面标题、负面标题、中性标题、平均情感分数,以及整体市场情绪。
这里有个很重要的设计决策:情感分数并没有被喂进预测模型,而是和预测结果并排展示,让用户自己独立解读两部分信息。把这两个组件解耦,反而让系统更容易理解和评估。
VADER 是一个针对社交媒体、新闻等短文本设计的规则型情感分析工具,用起来非常轻量:
from nltk.sentiment import SentimentIntensityAnalyzer
analyzer = SentimentIntensityAnalyzer()
headlines = [
"Company beats earnings expectations", # 偏正面
"Stock plunges amid market fears", # 偏负面
]
for text in headlines:
scores = analyzer.polarity_scores(text)
# compound 是归一化后的综合分数,范围 [-1, 1]
print(text, "->", scores["compound"])模型只有被别的应用调用,才真正有价值。所以最后用 FastAPI 搭了一个后端,接口接收一个股票代码,就自动跑完整条流程:
代码 → 下载数据 → 特征工程 → 预测 → 采集新闻 → 情感分析 → 返回 JSON
一个典型的返回结果长这样:
{
"ticker": "AAPL",
"current_price": 272.40,
"predicted_price": 274.15,
"predicted_return": 0.64,
"overall_sentiment": "Positive"
}因为一切都自动化了,想支持另一只股票,只需要换个代码符号即可。
一个最小可用的 FastAPI 接口大概是这样:
from fastapi import FastAPI
app = FastAPI()
# 定义一个接口,路径参数为股票代码 ticker
@app.get("/predict/{ticker}")
def predict(ticker: str):
# 这里依次调用:下载数据 -> 特征工程 -> 模型预测 -> 新闻情感
result = run_pipeline(ticker) # 假设已封装好整条流水线
return {
"ticker": ticker,
"current_price": result["current_price"],
"predicted_price": result["predicted_price"],
"predicted_return": result["predicted_return"],
"overall_sentiment": result["sentiment"],
}搭这套流水线,绝不只是「训练个模型」那么简单,开发中冒出了不少工程问题。
1. 动态数据
一开始系统用的是写死的日期区间,很快就出问题了——预测结果会过期。解决办法是让流水线每次运行时都自动下载最新可用数据。
2. 时间序列验证
和普通机器学习数据集不同,股价数据不能随机打乱。如果拿未来的数据训练、拿过去的数据测试,会得到虚高的、不真实的性能。所以训练集和测试集的划分必须保持时间先后顺序,这是普通机器学习和金融预测之间最重要的区别之一。
3. 特征泄漏(Feature Leakage)
金融预测特别容易在不知不觉中把「未来信息」漏进训练过程。每一个特征都必须小心构造,确保做预测的那一刻,只用到了历史上真实可得的信息。避免泄漏,往往比选一个高级模型更重要。
这里补充一个新手最常踩的坑:用 scikit-learn 做标准化时,如果先对全量数据 fit 再切分训练测试集,测试集的统计信息就会「泄漏」到训练里。正确做法是只在训练集上 fit:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
# 正确:只在训练集上学习均值和方差
X_train_scaled = scaler.fit_transform(X_train)
# 测试集只做 transform,绝不 fit,避免未来信息泄漏
X_test_scaled = scaler.transform(X_test)这个项目教会我的,远不止「怎么训练一个回归模型」,而是如何构建一套完整的机器学习系统,包括数据采集、清洗流水线、特征工程、模型评估、后端接口、项目组织、自动化等等。
更重要的一课是:成功的机器学习项目,很少是靠找到某个「神奇算法」。大部分工作,其实发生在模型训练之前——好的数据流水线、可复现的工作流、严谨的验证、用心的特征工程,通常比「换个算法」重要得多。
如果你想继续深挖,还有很多可以扩展的方向:引入宏观经济指标、加入 RSI 和 MACD 等技术指标、尝试 LSTM 或 Transformer 模型、做前向滚动验证(walk-forward validation)、超参数优化、模型版本管理、云端部署、实时预测面板等。
那么,AI 到底能不能预测股价?
答案是:能,但有重要的前提和限制。
AI 可以从历史数据里识别模式,有时也能给出还算合理的短期预测。但金融市场受无数不可预测因素影响,「完美预测」是不可能的。任何声称能稳定精准预测市场的人,都值得你保持健康的怀疑。
对学习 Python 和量化的人来说,这个项目最大的价值,不是那个预测数字本身,而是搭建它的整个过程:你会真切体会到,一个看似简单的预测背后,藏着多少数据工程和软件工程的功夫。有时候,一个机器学习项目最宝贵的产出,不是预测结果,而是你在搭建它的过程中学到的一切。
在量化金融里,理解你模型的局限,和构建模型本身一样重要。
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用
9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解
好书推荐