十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python股票时间序列分析实战:ARIMA建模与预测全流程

Python股票时间序列分析实战:ARIMA建模与预测全流程 简介面向金融数据分析课程与量化投资入门者这份Python代码资源完整覆盖时间序列分析在股票场景中的典型作业任务从台湾证交所抓取股价与市场数据完成均价/标准差统计、最高最低价及日期定位、成交量前三名排序并结合EPS≥1进行股票筛选与增幅排名进一步实现多数据源合并、RSI超买/超卖买卖信号识别以及基于ARIMA和FBProphet的30日股价预测最终开展策略回测与投资组合损益计算。资源共9个文件以Jupyter Notebook和Python脚本为主另有Markdown说明文档压缩包大小567KB结构清晰按作业编号HW1~HW8逐项组织便于对照学习。目前已有1375人学习下载适用于金融作业参考、量化入门实践或课程设计复用。代码思路完整可直接运行与二次扩展能帮助读者快速掌握股票数据分析、建模预测与回测评估的全流程。1. 先从一段翻车代码说起股票时间序列分析作业到底卡在哪一门量化金融课的期末作业用 Python 对一只股票的收盘价做时间序列分析并预测。班里一半同学的成品长得很像预测起点之前拟合完美预测起点之后是一条几乎水平的虚线。老师说这不是模型的问题是数据、差分、定阶三个环节至少有一个在空转。这篇文章把股票的时间序列分析完整落地路径写清楚从数据拉取、ADF 平稳性检验、ARIMA 定阶、残差诊断到样本外验证都给了能直接跑的 Python 代码参数怎么调、坑在哪也一并说明代码结构与金融作业的要求对齐照着改股票代码就能交。2. 股票数据获取与清洗用 akshare 把行情拉下来并处理干净2.1 数据源怎么选为什么是 akshare 而不是自己写爬虫常见的数据获取方案有 tushare、baostock、akshare、yfinance 四类。tushare 需要先注册拿 token部分 Pro 接口还要积分学生作业场景下门槛偏高baostock 免登录但接口风格偏老返回字段命名需要额外适配yfinance 对 A 股覆盖有限列名习惯也和国内教材不一样。akshare 的好处是不需要 token、直接返回 DataFrame、A 股日线覆盖完整对金融作业来说基本是开箱即用。自己写爬虫去抓网页或接口从 58 同城数据、东方财富到新浪财经每个站点的反爬策略都不一样作业周期就那么一两周真没必要把时间耗在这上面搜索到的历史数据还未必能保证连续。2.2 先把 Python 环境盘明白库清单与一行安装命令开始之前先把环境准备好。如果你还没装 Python先去官网下载 3.9 以上版本安装时记得勾选 Add Python to PATH之后不管是 VSCode 还是 PyCharm新建项目时把解释器指到刚才装好的那个 Python这一步就是网上常说的 python 环境配置很多同学后面 import 报错都是解释器选错了导致的。库的安装用 pip 一行搞定核心是 pandas、numpy、matplotlib、statsmodels 以及行情接口 aksharepython -m pip install --upgrade pip pip install pandas numpy matplotlib statsmodels akshare如果下载慢在命令末尾加上清华镜像源参数即可。pandas 负责数据清洗statsmodels 是时间序列分析的主库akshare 负责拉行情matplotlib 负责画预测图这几个就是金融作业里的主力 python 第三方库。装完后在 Python 交互环境里输入import akshare不报错环境就算通了。2.3 拉取行情并清洗最小可用代码与参数说明下面这段代码完成数据拉取、列名重命名、时间索引设置三步。以平安银行000001为例时间是 2019 年到 2024 年 6 月import akshare as ak import pandas as pd # symbol 是股票代码6 开头是沪市0 开头是深市8/4 开头是北交所 # period 用 daily日线adjust 用 qfq 表示前复权 df ak.stock_zh_a_hist( symbol000001, perioddaily, start_date20190101, end_date20240601, adjustqfq, ) # akshare 返回中文列名统一改成英文方便后面调用 df df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume, }) df[date] pd.to_datetime(df[date]) # 字符串转成时间类型 df df.set_index(date).sort_index() # 设置时间索引并升序排列 close df[close].astype(float) # 只要收盘价后面所有建模都基于它 close.head()stock_zh_a_hist返回的 DataFrame 里日期列是字符串必须先转成datetime再设为索引否则后面画图、差分、按时间切训练集时全部会乱。adjustqfq表示前复权也就是把历史价格按照分红送股调整到当前口径这样除权日不会出现人为跳空。close是一个带时间索引的 Series从这一步开始它就是整个时间序列分析的处理对象后续的平稳性检验、ARIMA 拟合都作用在这个序列上。2.4 把数据固定下来保存一份 CSV 防止结果不可复现作业写到后面你会发现akshare 接口偶尔会改字段名或返回格式连续几天拉的同一只股票数据可能有细微差别。为了避免今天跑出来的 ADF 统计量和明天不一样我一般会把清洗好的序列存成 CSVclose.to_csv(stock_000001_close.csv, header[close])下次作业直接pd.read_csv(stock_000001_close.csv, index_col0, parse_datesTrue)读回来结果可以完全复现。这个细节在答辩时经常被问到数据固定了老师复跑你的代码才不会得出另一个结论。3. 平稳性检验与差分时间序列分析里最容易被跳过的一步3.1 为什么非平稳数据会让模型直接翻车ARIMA 模型的全称是自回归积分滑动平均模型其中 I 代表差分目标就是把非平稳序列变成平稳序列再建模。很多股票价格序列天生非平稳均值随时间变化方差也不稳定。如果你拿原始价格序列直接拟合模型估计出的自回归系数在统计上不可靠预测结果就变成对趋势线的简单外推画出来就是作业里最常见的“一条水平虚线”。这不是模型不行而是输入序列不满足模型的平稳性前提这在回归里叫伪回归在时间序列分析里同样是灾难。所以建 ARIMA 之前必须先做单位根检验确认序列平稳不平稳就差分。3.2 ADF 检验Python 实现与结果判读ADF 检验是判断平稳性的标准做法statsmodels 里直接封装好了。把上一章的收盘价序列拿来做测试from statsmodels.tsa.stattools import adfuller adf_result adfuller(close, autolagAIC) print(fADF 统计量: {adf_result[0]:.4f}) print(fp-value: {adf_result[1]:.4f}) print(f临界值: { {k: round(v, 4) for k, v in adf_result[4].items()} }) if adf_result[1] 0.05: print(p 0.05拒绝单位根假设原始序列平稳) else: print(p 0.05不能拒绝单位根假设序列非平稳需要差分)adfuller 返回的元组包含四个关键部分第一个是 ADF 统计量第二个是 p 值第四个是字典形式的 1%、5%、10% 临界值。autolagAIC表示自动选择最优滞后阶数一般保持默认即可。判读逻辑很简单p 值小于 0.05拒绝存在单位根的原假设说明序列平稳反过来则说明序列非平稳。实际拿平安银行收盘价跑这段代码p 值通常远大于 0.05这符合绝大多数股票价格的特性继续差分。3.3 差分阶数怎么定一阶差分、对数收益率与 KPSS 辅助最常见的处理是做一阶差分close_diff1 close.diff().dropna()。注意diff()之后第一个值一定是 NaN要用dropna()去掉否则后面建模会报错。更规范的金融做法是取对数收益率也就是np.log(close).diff()好处是消除量纲影响让序列的波动水平更稳定而且对数收益率的 ADF 检验结果几乎必然是平稳的p 值通常小于 0.01。我一般会再做一次 KPSS 检验作为交叉验证。K PSS 和 ADF 的假设正好相反KPSS 的原假设是序列平稳如果 KPSS 的 p 值小于 0.05说明序列不平稳from statsmodels.tsa.stattools import kpss kpss_stat, kpss_p, _, _ kpss(close_diff1, regressionc) print(fKPSS 统计量: {kpss_stat:.4f}, p-value: {kpss_p:.4f})regressionc表示只含常数项适合差分后的序列。ADF 和 KPSS 结论一致时差分阶数基本就定了。对股票价格序列来说一阶差分或对数一阶差分足够不要动不动做二阶差分二阶差分会损失过多信息而且收益率的二阶差分在金融意义上是讲不通的。4. ARIMA 定阶与拟合从读图到网格搜索的完整流程4.1 先画 ACF 和 PACF读出初步的 p 和 q差分做完下一步是确定 ARIMA 的阶数p 是自回归阶数q 是移动平均阶数d 是差分阶数上面已经定了。定阶的第一判断工具是自相关图和偏自相关图from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import matplotlib.pyplot as plt fig, axes plt.subplots(2, 1, figsize(12, 8)) plot_acf(close_diff1, axaxes[0], lags20) plot_pacf(close_diff1, axaxes[1], lags20) plt.show()lags20表示画到滞后 20 阶。图里的蓝色区域是 95% 置信带柱子超出蓝色带就代表该阶的自相关或偏自相关显著不为零。ACF 和 PACF 的形态对应关系如下序列特征模型选择ACF 表现PACF 表现AR(p)自回归模型拖尾衰减p 阶后截尾MA(q)移动平均模型q 阶后截尾拖尾衰减ARMA(p, q)混合模型拖尾衰减拖尾衰减读图只是一个方向不是铁律样本有限时经常出现两三个柱子同时超出置信带的情况。此时把读图结果作为网格搜索的范围比直接拍脑袋定阶靠谱得多。4.2 网格搜索定阶让代码替你把 AIC 算一遍入门教程喜欢让你对着 ACF/PACF 图手动猜 p、q但实际做作业时我更推荐用小范围网格搜索自动选参。p 和 q 各取 0 到 3d 固定为前面检验得到的 1逐个拟合 ARIMA记录 AIC选择 AIC 最小的组合import warnings import itertools warnings.filterwarnings(ignore) from statsmodels.tsa.arima.model import ARIMA best_aic float(inf) best_order None for p, q in itertools.product(range(0, 4), range(0, 4)): try: model ARIMA(close, order(p, 1, q)).fit() # d 固定为 1 if model.aic best_aic: best_aic model.aic best_order (p, 1, q) except Exception: continue # 个别参数组合可能不收敛跳过即可 print(f最优阶数: {best_order}, AIC: {best_aic:.2f})itertools.product(range(0, 4), range(0, 4))会生成 16 个 (p, q) 组合。ARIMA(close, order(p, 1, q)).fit()里的close是原始收盘价序列注意不是差分后的序列因为 ARIMA 的 d 参数已经在内部完成差分了这就是为什么建模时传原始序列、order 里写 d1。warnings.filterwarnings(ignore)是为了屏蔽部分参数组合在拟合过程中的收敛警告模型本身能出结果但不收敛的情况用 AIC 做比较已经足够粗糙容错。为什么范围只取 0 到 3日线股票收益率的自相关结构通常较弱高阶模型反而容易过拟合16 个组合已经覆盖大多数作业场景。如果你的序列是周期较强的数据可以适当扩大到 0 到 5但要注意样本量不够时高阶模型参数估计方差很大。4.3 拟合之后的残差检验模型信息的最后一道闸门定完阶、拟合完模型很多人直接拿预测结果画图交差了这漏掉了最关键的一步残差检验。一个合格的 ARIMA 模型拟合残差应该表现为白噪声也就是残差序列没有自相关性否则说明还有信息没被模型提取干净。用 Ljung-Box 检验判断from statsmodels.stats.diagnostic import acorr_ljungbox resid model.resid # 最优模型的残差 lb_test acorr_ljungbox(resid, lags[10, 20], return_dfTrue) print(lb_test)lags[10, 20]同时检验 10 阶和 20 阶滞后下残差的自相关性。输出的 p 值如果大于 0.05说明残差是白噪声模型可用如果小于 0.05回去把 p、q 范围扩大重搜一遍。这一步的意义是让你的模型在统计上站得住脚作业答辩时老师问“为什么你的模型是可信的”残差检验结果就是最直接的证据。5. 金融作业常见坑这 5 个问题我几乎每年都见一次5.1 预测图“拟合完美”却在测试集上断崖未来数据泄露现象把全部历史数据拟合 ARIMA 后再用predict去画后半段的预测发现预测线和真实值几乎重合你截图交作业老师一句“你的模型偷看了答案”直接打回。原因模型是在全量数据上拟合的测试期间的信息已经通过参数悄悄进入了模型这属于典型的数据泄露。解决先切分训练集和测试集再在训练集上拟合最后用forecast对测试集做真正的样本外预测train close.iloc[:int(len(close) * 0.8)] test close.iloc[int(len(close) * 0.8):] model ARIMA(train, orderbest_order).fit() pred model.forecast(len(test))核心区别在于forecast只基于训练集信息外推。切分比例 8:2 是作业里比较常见的默认选择时间序列不能随机打乱切必须按时间先后切这是和时间序列分析课程里交叉验证最大的不同。5.2 差分后索引断档画图时所有曲线对不齐现象close.diff()之后用reset_index()重建索引再画预测图时发现预测起点对不上或者预测值和真实值错位了一个时间点。原因差分后的序列扔掉了一行时间索引没有跟着保住还有人把差分序列存成普通列表丢失了 datetime 索引。解决差分后不要reset_index保留原有的时间索引即可缺失的第一个点用dropna()处理。如果你需要把预测值和真实值画在同一张图上用pd.DataFrame({真实值: test, 预测值: pred}, indextest.index)构造 DataFramepandas 会自动按索引对齐。这是一个很经典的坑错位一天在图上不显眼但 RMSE 数字会明显变差。5.3 除权日“股价暴跌”没复权的数据把你带进沟里现象某只股票某天收盘价突然从 20 元跳到 12 元当天没有任何利空消息模型却把这个跳空当成重大结构变动。原因股票除权除息后不复权的价格序列出现人为跳空时间序列分析会把它当成一个真实的价格断点影响差分和自相关估计。解决拉数据时adjustqfq做前复权把历史价格统一到当前股本口径。如果你用的是从其他渠道下载的不复权数据先做复权处理再进入分析流程。注意前复权也不是万能的长期高分红的股票复权后早期价格可能被调成负值但对日线短期作业来说影响不大。5.4 statsmodels 的 ARIMA 导入路径报错现象照着网上老教程写from statsmodels.tsa.arima_model import ARIMA运行直接ImportError。原因新版 statsmodels 把 ARIMA 迁移到了statsmodels.tsa.arima.model旧路径在 0.13 版本后已经移除。解决统一使用新路径from statsmodels.tsa.arima.model import ARIMA如果你的环境还跑着旧版本pip install -U statsmodels升级即可。这个坑每年都有同学踩看到带arima_model的教程先看发布时间时间太旧的直接跳过代码要用能跑起来的路径。5.5 同一组 p、q 套所有股票换个标的就翻车现象平安银行跑出来 ARIMA(1,1,2) 效果不错直接把同样参数套到恒瑞医药上预测结果明显变差。原因不同股票的波动结构不一样行业属性、流动性、交易制度都会影响收益序列的自相关特征p、q 必须重新定阶。解决把定阶流程写成一个函数对每个标的单独跑 ACF/PACF 和网格搜索。参考前面第 4 章的网格搜索代码封装成select_order(code, start, end)输入股票代码、输出该标的最优 (p, d, q)。作业里如果涉及多只股票对比用函数批量处理既省时间又显得专业。6. 让作业再扎实一点滚动预测、出图细节与我的习惯6.1 用滚动预测让样本外验证更有说服力单次forecast的样本外预测只能给出一个固定长度的预测老师可能觉得不够硬核。常见做法是滚动预测每预测一个点就把这个真实值回填到训练集中再预测下一个点。代码实现history list(train) preds [] for t in range(len(test)): model ARIMA(history, orderbest_order).fit() y_hat model.forecast(1)[0] preds.append(y_hat) history.append(test.iloc[t]) # 把真实值回填到历史窗口这种方式贴近真实交易场景每次只外推一步误差不会累积。缺点是每步都要重新拟合一次模型测试集较长时运行会变慢作业里展示最后 20 个交易日的滚动预测就足够了。6.2 出图细节提交前把图调到论文级预测图和真实值画在一起时建议用实线画真实值、虚线画预测值并标注分割线import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Windows 中文字体 plt.rcParams[axes.unicode_minus] False plt.figure(figsize(12, 6)) plt.plot(test.index, test.values, label真实值, linewidth1.5) plt.plot(test.index, preds, label预测值, linestyle--) plt.axvline(xtrain.index[-1], colorgray, linestyle:, alpha0.8) plt.xlabel(date) plt.ylabel(close) plt.legend() plt.grid(alpha0.3) plt.tight_layout() plt.show()axvline画出的竖线用于标明训练集与测试集的分界老师一眼就能看出预测是样本外的。如果是在 macOS 上跑把SimHei换成Arial Unicode MS否则中文标签会变成方块。6.3 我的一个习惯先跑基线再谈优化给你一个我自己的习惯也是被坑过之后养成的拿到一个时间序列分析任务第一件事永远是用最笨的模型跑通全流程——拉数据、差分、ARIMA(1,1,1)、画图确认流程没问题再回头做网格搜索和滚动预测。直接上手调参报错时你分不清是数据问题还是模型问题先把基准线画出来后面每一步优化都能对比出真实效果。这份代码结构也是我处理 A 股数据时的固定模板换标的、换区间、换周期改几个参数就能复用。希望帮到你。本文还有配套的精品资源点击获取
返回列表