黄金价格预测系统:ARIMA模型与Flask可视化实战
简介这份毕业设计源码面向金融数据分析方向的高校学生与开发者集中展示以现货黄金价格数据为对象的机器学习与统计分析完整链路——从数据清洗、特征工程到计算 MACD、BOLL 等技术指标再利用 ARIMA 模型对价格走势做出预测并通过 Flask 搭建后台接口用 Bootstrap、ECharts、jQuery 完成前端可视化适合用于课程设计、毕业设计或论文复现。资源包共 55 个文件、约 31.59MB其中包含 4 个 Python 程序、16 个 JavaScript、8 个 CSS、3 个 HTML 页面和 1 个系统演示视频同时提供依赖清单目录结构覆盖数据处理、指标计算、模型预测、接口服务、前端渲染等核心模块便于按层阅读或二次开发。已有 268 人学习附带的演示录屏能快速呈现运行效果源代码中工具模块职责清晰适合直接将其中的时序预测与可视化逻辑迁移到其他金融数据分析项目中。1. 别急着谈神经网络先让ARIMA把短期趋势讲清楚拿到这个黄金价格预测系统的源代码第一反应是“为什么不是LSTM”但把项目里gold-visualize的目录展开后会发现真正的预测核心是util.py与technical_util.py里的统计计算加上 ARIMA 时序模型。黄金现货价格受外盘、美元指数、地缘事件影响波动非平稳在这个体量下做日频或小时频预测ARIMA 比神经网络更容易解释、更容易调参也更容易让答辩现场的老师听懂预测结果是怎么来的。整套系统用 pandas 做数据清洗sklearn 做统计分析Flask 暴露标准 RESTful 接口Bootstrap Echarts 完成前端可视化适合拿来做毕业设计或课程设计的代码底座。2. 数据清洗与MACD、BOLL指标计算2.1 项目里负责数据加工的模块源码包里的ghistory_data相关数据没有直接给出csv但从gold_history.html页面结构和technical_util.py的函数可以看出后端读取的行情表通常包含time、open、high、low、close、volume六列。实际跑通项目前我会先看一下util.py里有没有时间对齐的逻辑因为现货黄金在非交易时段会有缺口直接丢给 ARIMA 会导致预测值偏移。technical_util.py承担的是技术指标计算它至少导出三个函数macd、boll和ma。这里有一个很常见的坑MACD 和 BOLL 依赖滚动窗口如果原始数据在周末缺失rolling会按自然日连续计算导致周一开盘的指标被周五收盘“平滑”掉。常见做法是先用pandas.to_datetime统一时间列再按时间索引重采样到交易日最后对仍然缺失的close用前向填充。import pandas as pd def load_klines(path): df pd.read_csv(path, parse_dates[time]) df.set_index(time, inplaceTrue) df df[[open, high, low, close, volume]].astype(float) df df[~df.index.duplicated(keepfirst)] df.sort_index(inplaceTrue) # 重采样到小时级别空缺用前值填充 df df.resample(H).ffill() df.dropna(subset[close], inplaceTrue) return df这里parse_dates把字符串时间列转成DatetimeIndexresample(H).ffill()是处理缺口最省事的办法但它会制造大量重复值所以后文 ARIMA 训练时要指定start和end只截取真实交易区间不能把填充的假样本也送进模型。2.2 MACD指标的实现口径MACD 的计算路径是先算 12 日指数移动平均线 EMA12再算 26 日 EMA26DIF EMA12 - EMA26DEA 是 DIF 的 9 日 EMA最后 MACD 柱 2 * (DIF - DEA)。很多教程把最后一步写成 DIF - DEA但国内行情软件普遍再乘 2和项目里technical_util.py的口径保持一致。def ema(series, period): return series.ewm(spanperiod, adjustFalse).mean() def macd(close, fast12, slow26, signal9): dif ema(close, fast) - ema(close, slow) dea ema(dif, signal) macd_bar 2 * (dif - dea) return dif, dea, macd_bar参数adjustFalse是精确计算 EMA 的关键否则 pandas 默认使用调整权重结果会和同花顺、MT4 有肉眼可见的差异。项目源码里的pick-a-color是前端配色插件与计算无关。MACD 是否算对可以用最后一个 bar 的值和财经网站对比误差在 0.01 以内算合格。2.3 BOLL布林带与统计边界BOLL 以 20 日收盘价均线为中轨上下轨分别为中轨加减 2 倍标准差。标准差这里默认是无偏修正也就是ddof1如果项目里用的是pandas.Series.rolling(20).std()它就是ddof1不要额外改否则上下轨和行情软件对不上。def boll(close, window20, num_std2): mid close.rolling(window).mean() std close.rolling(window).std(ddof1) upper mid num_std * std lower mid - num_std * std return mid, upper, lower在项目里BOLL 更多用于可视化Echarts 图中叠加三条轨道看价格是否突破上轨或击穿下轨辅助判断趋势强度。它不直接作为 ARIMA 的输入因为 ARIMA 是单变量时间序列模型把多维指标硬塞进去会破坏自回归结构的假设。指标用途关键参数与预测的关系MACD判断动能拐点12, 26, 9不直接进模型用于人工验证预测方向BOLL观察波动边界20, 2辅助设定 ARIMA 预测置信区间MA5/MA10短线趋势5, 10仅用于前端图表不作为模型特征ATR波动率参考14可扩展本项目未使用3. ARIMA模型构建与预测3.1 平稳性检验与差分阶数dARIMA 的三个参数(p, d, q)中d 代表差分次数。黄金价格原始序列几乎必然是非平稳的因为价格围绕长期趋势上下波动。先用 ADF 检验确认当 p-value 大于 0.05 时需要做一阶差分。from statsmodels.tsa.stattools import adfuller def check_stationarity(series): result adfuller(series.dropna()) print(ADF Statistic:, result[0]) print(p-value:, result[1]) return result[1] 0.05 price df[close] if not check_stationarity(price): diff1 price.diff().dropna() if check_stationarity(diff1): d 1adfuller返回的第一个值是检验统计量第二个值是 p-value。通常看到 p-value 接近 0.001就认为一阶差分后序列平稳。项目里如果数据是按小时采集的可能一阶差分后仍不平稳这时需要尝试二阶差分但 d 不建议超过 2否则会损失过多趋势信息预测曲线会趋于直线。3.2 定阶ACF/PACF与AIC对比差分平稳后用acf和pacf观察截尾和拖尾。一阶差分序列的 ACF 在 1 阶后迅速截尾PACF 拖尾常见判断是 MA(1)对应(0,1,1)。但更稳妥的做法是枚举几个候选组合用 AIC 选最小的。import itertools from statsmodels.tsa.arima.model import ARIMA import warnings warnings.filterwarnings(ignore) best_aic float(inf) best_order None for p in range(0, 4): for q in range(0, 4): try: model ARIMA(price, order(p, d, q)) result model.fit() if result.aic best_aic: best_aic result.aic best_order (p, d, q) except Exception: continue print(Best order:, best_order, AIC:, best_aic)这段代码放在gold_predict.html对应的后端服务里会在每次启动时重新定阶。要留意如果数据量大全枚举会非常慢小时频数据超过 3000 条时建议把范围缩小到range(0,3)。AIC 的值本身没有绝对意义只用于相对比较。3.3 模型拟合与预测区间选定(p,d,q)后调用model.fit()。预测时常用get_forecast(stepsn)它返回均值和置信区间比直接调用forecast()信息更多前端可以画阴影带。final_model ARIMA(price, order(2, 1, 2)) fitted final_model.fit() fc_result fitted.get_forecast(steps10) mean fc_result.predicted_mean conf_int fc_result.conf_int(alpha0.05) print(mean) print(conf_int)alpha0.05表示 95% 置信区间置信区间越窄说明预测越确定。实际表现中黄金价格的一小时级预测10 步之后的置信区间宽度会明显增加这是正常现象前端渲染时可以把区间用半透明颜色显示。3.4 差分还原别直接用模型输出画图如果原始序列做过一阶差分fitted.predict()返回的是差分后的值要还原成真实价格需要使用原始序列最后一个值累加。statsmodels 在传入原始序列时会自己处理差分还原但如果手动做了diff()再进模型输出就必须手动还原last_price price.iloc[-1] predict_diff fitted.forecast(steps10) predict_price last_price predict_diff.cumsum()这段逻辑很容易被忽略很多人在做毕业设计的时候发现预测曲线整体水平偏移一条固定值就是少做了还原。项目里config.py中的MODEL_FREQ字段可以理解为建模时用的时间频率它会影响差分步长改成日线后还原方式不变但置信区间会大很多。3.5 神经网络与ARIMA的边界项目标题里有“神经网络”但实际模型文件里没有keras或torch依赖。这是因为该场景下ARIMA 在可解释性和小样本表现上优于神经网络。神经网络擅长多变量、非线性的高维拟合但需要大量数据防止过拟合现货黄金小时线数据量级在几千条时ARIMA 足够稳定。如果答辩被问到“为什么不换LSTM”可以回答当前系统先以ARIMA做统计基线后续可扩展用MACD、BOLL、RSI组成的特征矩阵训练LSTM接口层不变量化对比后再决定是否切换。4. Flask后端接口设计和前端可视化4.1 RESTful接口的路径规划项目采用 Flask 做后端而不是 Django原因是轻量适合课程设计和部署。接口设计上不推荐把页面渲染和 JSON 混在一起而是分离出几个核心路由历史数据、最新行情、预测结果、指标数据。接口路径方法参数返回说明/api/historyGETlimit,start返回时间、收盘价、成交量/api/indicatorsGETtypemacd返回DIF、DEA、MACD柱/api/predictGETdays返回预测均值与置信区间/GET无重定向到gold_history.html/api/predict的days参数我建议限制在 1 到 30 之间避免有人传 1000导致 ARIMA 在长期预测上输出噪声。响应统一用jsonify包裹前端拿到的是一个标准对象。from flask import Flask, jsonify, request from datetime import datetime, timedelta app Flask(__name__) app.route(/api/predict, methods[GET]) def predict(): try: days int(request.args.get(days, 5)) days max(1, min(days, 30)) except ValueError: days 5 model ARIMA(price, orderbest_order).fit() fc model.get_forecast(stepsdays) items [] last_time datetime.now() for i, pred in enumerate(fc.predicted_mean): items.append({ time: (last_time timedelta(hoursi 1)).strftime(%Y-%m-%d %H:%M:%S), close: round(pred, 2) }) return jsonify({code: 0, data: items})注意这里每次请求都重新训练模型优点是数据更新后立刻自适应缺点是并发时会阻塞。课程设计阶段没问题如果要在生产环境用需要把模型缓存到内存或 Redis只有当价格文件变化时才重新训练。4.2 前端通过jQuery调后台接口gold_predict.html中页面加载后执行一次$.getJSON然后把返回结果塞进 Echarts 的series数组中。Echarts 官方推荐用setOption来做增量更新而不是重新初始化实例。$.getJSON(/api/predict, { days: 10 }, function(resp) { if (resp.code ! 0) return; let times resp.data.map(item item.time); let closes resp.data.map(item item.close); let chart echarts.init(document.getElementById(main)); chart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: times }, yAxis: { type: value, scale: true }, series: [{ name: 预测收盘价, type: line, smooth: true, data: closes, lineStyle: { color: #d48265, width: 2 } }] }); });这里的resp.data.map把后端 JSON 数组转成 Echarts 要求的xAxis.data和series.data。smooth: true会让折线变圆滑但会忽视小波动如果有做策略验证建议设为false以显示真实预测拐点。4.3 模板继承与静态资源结构templates目录下layout.html是公共脚手架gold_history.html继承它gold_predict.html也继承它。static里 bootstrap、fonts、pick-a-color 分别对应 CSS、字体和颜色选择器jQuery 放在static/js下。这个结构比单页写死更合理修改导航栏只动一个模板。{% extends layout.html %} {% block content %} div idmain stylewidth:100%;height:500px;/div {% endblock %}Flask 的render_template会自动定位templates目录url_for(static, filenamejs/jquery.js)用于生成静态资源路径。这里有一个容易踩的坑如果直接把 HTML 文件放在项目根目录双击打开Ajax 请求会被浏览器的 CORS 策略拦截必须通过 Flask 启动后访问127.0.0.1:5000才能正常调用接口。5. 部署时容易踩的坑和ARIMA调参技巧5.1 Python版本与依赖锁定项目源码里出现了technical_util.cpython-37.pyc和config.cpython-37.pyc说明打包环境是 Python 3.7。3.8 到 3.11 版本跑statsmodels时要注意from pandas import DataFrame的兼容性建议直接新建虚拟环境并按照requirements.txt安装。python3.7 -m venv venv source venv/bin/activate pip install -r requirements.txt如果本机没有python3.7常见做法是把statsmodels升级到 0.13 以上这样 Python 3.10/3.11 也能跑。requirements.txt里至少包含flask,pandas,numpy,statsmodels,scikit-learn。sklearn 在这个项目里主要用于数据标准化和模型评估如果只做 ARIMA可以不要 sklearn但接口设计里更通用的做法是保留它。5.2 config.py中路径与模型参数优先读取config.py一般包含历史数据的路径、模型频率和预测步长。小项目里很容易出现“在根目录能跑换到子目录就报错”的现象原因是相对路径写成了gold_analysis/gold.csv。推荐用os.path.dirname(__file__)拼绝对路径。import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) DATA_FILE os.path.join(BASE_DIR, data, gold.csv) MODEL_FREQ H MAX_PRED_STEPS 30把DATA_FILE独立出来后前端调用历史数据接口时就不需要感知文件路径后端内部统一读取。这样在多目录部署时可以少一个排查点。5.3 用滚动预测验证ARIMA稳定性单次预测的曲线很漂亮不能说明模型可用。我在做这类系统时会保留最后 5% 的样本作为测试集用前面 95% 训练然后不断把真实值加入训练集重新滚动预测下一点。from sklearn.metrics import mean_absolute_error train_size int(len(price) * 0.95) train, test price[:train_size], price[train_size:] history list(train) preds [] for t in range(len(test)): model ARIMA(history, orderbest_order).fit() yhat model.forecast(steps1)[0] preds.append(yhat) history.append(test.iloc[t]) mae mean_absolute_error(test, preds) print(MAE:, round(mae, 3))MAE 的单位是“元”如果价格在 400 到 500 美元区间MAE 低于 2 美元说明模型在小时级别有参考价值。超过 5 美元就说明序列噪声过大需要增加更多历史数据或换用包含波动率特征的外生变量。5.4 用最新真实值重拟合是最后的技巧默认的predict(days10)是一次性预测未来 10 个点但 ARIMA 在多步预测时会不断使用自己预测出的值作为输入误差会随步长累积。最简单的应对是每隔 1 小时或每天刷新时把新到的真实价格追加进训练集重新拟合一次模型只预测未来 1 到 3 个点。这种方式不需要改动接口只要在后端通过定时任务更新price全局变量即可。对于课程设计演示预测 5 天以内的走势ARIMA 的均值和置信区间已经足够如果要发布成公开服务建议把重拟合间隔做成config.py里的REFIT_INTERVAL参数和数据拉取频率保持一致。本文还有配套的精品资源点击获取