简介:围绕厦门“博雅杯”C题黄金价格走势分析与预测撰写的数学建模研究文档,面向高校师生、科研机构研究者以及关注贵金属市场的个人投资者,重点回应如何筛选关键影响因素、建立定量模型并据此研判中长期金价趋势的问题。压缩包内仅含1个PDF文件,大小约117KB,内容集中,适合在课堂教学、学术研讨和投资者教育中直接查阅。正文从黄金基本属性与价格决定机制切入,梳理模型选择与构建过程,解释各关键变量对金价的影响程度及显著性,并进一步讨论2024—2030年金价预测思路,以及在全球经济复苏、政治稳定或不稳定等情景下的投资策略与风险管理建议。文末还给出问题导向任务列表,便于读者按步骤自主探究,并提示从经济学、统计学等角度开展跨学科分析。已有474人学习,适合需要完整赛题框架、建模流程参考和情景决策素材的读者。
1. 一道黄金建模赛题,真正要交的是什么
黄金价格走势分析与预测这类题目,最容易被写成“先画图、再套 ARIMA、最后给一句‘长期看涨’”。可真到评审环节,问题一要的是显著性,问题二要的是 2024-2030 的可解释区间,问题三要的是不同情景下能落地的策略,三样东西缺一条都撑不住。这套方案面向的是要交数学建模课程设计、统计建模竞赛论文或金融数据方向毕设的读者,核心交付物是一条从变量筛选、回归建模、显著性检验到分段预测、情景策略的完整链路,而不是一张拟合曲线。下面按需求、数据、模型、预测、策略、验收的顺序拆开讲,每一步都给出可复现的做法和参数口径。
2. 数据与变量工程:把行情、宏观和供需拼成一张可回归的表
建模成败有一大半在数据表上。很多队伍把日频金价和月频宏观指标直接按日期内连接,样本一下从二十年缩到五年,回归出来系数漂亮但没意义。稳妥的做法是先定口径,再定频率,最后才谈变量。
2.1 口径先统一:盎司与克、名义价与实际价
国际金价以美元/盎司报价,国内习惯用元/克,两个口径不能混着进模型。换算关系是 1 盎司等于 31.1035 克,换算时还要乘上当期汇率。更关键的是名义价和实际价的选择:黄金的长期价格里含通胀成分,若直接用名义金价对名义利率回归,系数会被通胀趋势带偏。通常的做法是主模型用对数收益率或实际金价,描述性统计里再放名义价。
频率上,日频数据噪声大、宏观变量多为月度,直接混用会引入大量插值。我一般把金价重采样到月末,宏观变量取当月值,供需类季度数据用前向填充并加一个“是否插值”的哑变量,避免把插值点当成真实观测。
import pandas as pd import numpy as np OZ_TO_GRAM = 31.1035 def build_monthly(gold_path, macro_path): gold = pd.read_csv(gold_path, parse_dates=["date"]).set_index("date").sort_index() macro = pd.read_csv(macro_path, parse_dates=["date"]).set_index("date").sort_index() # 统一到"元/克"辅助口径,主模型仍用美元/盎司收益率 gold["gold_cny_g"] = gold["close"] * gold["usdcny"] / OZ_TO_GRAM # 月末重采样,避免日频噪声主导回归 gold_m = gold.resample("ME").last() macro_m = macro.resample("ME").last() df = gold_m.join(macro_m, how="inner") # 标记插值点,后续做稳健性检验时剔除 df["is_filled"] = df["cpi"].isna().astype(int) df["cpi"] = df["cpi"].ffill() return df.dropna(subset=["close"])这段脚本做了三件事:换算单位、统一到月度、给插值点打标记。参数上,resample("ME")的月末取值比月均值更能对齐宏观数据发布节奏;is_filled这个标记在后面的稳健性检验里会用到,凡是靠插值撑起来的样本,剔除后系数方向不应反转,否则说明模型不稳。
2.2 变量池与筛选:十几个候选因子怎么砍到五个
候选变量通常分四类:汇率与利率类、通胀类、供需类、市场情绪类。汇率用美元指数,利率用通胀保值债券收益率作为实际利率代理,通胀用 CPI 同比,供需用官方储备购金量和矿产金产量同比,情绪用黄金 ETF 持仓变化和波动率指数。十几个变量一次性入模,Almost 一定出现共线和符号反转,所以要先做相关性矩阵和 VIF 粗筛,再交给逐步回归或岭回归定最终变量集。
筛选时按两条硬规则执行:第一,与金价收益率的单变量相关系数绝对值低于 0.1 的直接剔除,避免凑数;第二,两两相关系数超过 0.8 的变量只留一个,优先留经济学含义更直接的那个。实际利率和通胀预期往往高度负相关,这时留实际利率,因为它是持有黄金的机会成本,解释链更短。
提示:变量筛选的结果要写进论文附录,包含每个候选变量的来源、频率、处理方式和剔除理由。评审看的是决策过程,不是最后那五个变量本身。
2.3 处理链落地:一份能跑通的数据脚本
筛选完之后,还要做平稳性检验。价格水平序列几乎都是非平稳的,直接回归会得到高 R² 和一堆显著 t 值,这就是典型的伪回归。正确做法是对金价取对数收益率,对宏观变量取差分或同比,再检验一次。
from statsmodels.tsa.stattools import adfuller def adf_table(df, cols): rows = [] for c in cols: s = df[c].dropna() stat, p, lags, nobs, crit, _ = adfuller(s, autolag="AIC") rows.append({"var": c, "adf": round(stat, 3), "p": round(p, 4), "lags": lags, "stationary": p < 0.05}) return pd.DataFrame(rows) FEATURES = ["dxy_ret", "real_rate_chg", "cpi_yoy", "cb_gold_yoy", "etf_flow"] print(adf_table(df.assign(gold_ret=np.log(df["close"]).diff()), ["gold_ret"] + FEATURES))autolag="AIC"让滞后阶数按信息准则自动选,比手工指定稳健。输出里stationary为 False 的变量必须再处理一次,要么再差分,要么换成同比增速。这张表也是答辩时的挡箭牌——被问到“为什么能用线性回归”,直接把 ADF 结果翻出来,说明因变量和自变量都过了平稳性检验。
3. 问题一的落地:多因素回归、显著性与影响度拆解
问题一要的是“选至少两个关键因素,建模并解释影响程度和显著性”。这句话拆开是三个交付点:模型形式、系数显著性、影响程度排序。三者对应的检验和表达方式完全不同,混在一起写就会被追问。
3.1 基准模型与检验顺序
基准模型用多元线性回归,但检验顺序不能乱。先看 F 检验判断模型整体是否显著,再看单个系数的 t 检验和 p 值,然后看调整 R²,最后才看系数符号是否符合经济直觉。顺序反了,容易陷入“R² 很高但系数全不显著”的尴尬。
import statsmodels.api as sm X = sm.add_constant(df[FEATURES]) y = df["gold_ret"] # HAC 稳健标准误,处理残差自相关和异方差 model = sm.OLS(y, X).fit(cov_type="HAC", cov_kwds={"maxlags": 6}) print(model.summary())这里用cov_type="HAC"而不是默认的普通标准误,原因是月度金融数据普遍存在残差自相关,普通标准误会把 t 值高估,p 值虚低。maxlags=6对应半年的滞后窗口,可按样本长度调整,一般取样本量的立方根。换成稳健标准误后,如果某个变量从显著变不显著,说明它原来的显著性有一部分是自相关撑起来的,这个发现本身就值得写进论文。
3.2 共线性:VIF、岭回归和逐步回归怎么选
共线性是这道题最常见的翻车点。实际利率、通胀、汇率三者之间相关性往往在 0.7 以上,硬塞进 OLS 会出现系数符号和经济学常识相反的情况——比如实际利率上升,金价系数却是正的。这时候不能改数据,要改方法。
处理路径有三条:VIF 大于 10 的变量直接剔除;用岭回归牺牲一点无偏性换系数稳定;用逐步回归按 AIC 做变量增减。我一般先跑 VIF 定位问题变量,再用岭回归做对照,两条路径的系数方向一致才敢写进结论。
from statsmodels.stats.outliers_influence import variance_inflation_factor vif = pd.DataFrame({ "var": X.columns[1:], "vif": [variance_inflation_factor(X.values, i) for i in range(1, X.shape[1])] }).sort_values("vif", ascending=False) print(vif)VIF 表里超过 10 的变量,要么剔除,要么合并成主成分。合并会损失可解释性,而问题一恰恰要解释影响程度,所以优先剔除。剔除后模型调整 R² 下降不超过 0.05,就说明被剔除的变量是冗余的,这个判断标准要写清楚。
3.3 影响程度怎么写进论文:标准化系数与弹性
“影响程度”不是看原始系数大小,因为变量量纲不同。正确做法是两条:一是标准化系数,把所有变量做 z-score 后重新回归,系数绝对值直接可比;二是弹性系数,用双对数模型,系数解释为“自变量变动 1%,因变量变动百分之几”。
# 标准化系数:变量先 z-score,再回归 z = (df[FEATURES] - df[FEATURES].mean()) / df[FEATURES].std() zy = (y - y.mean()) / y.std() std_model = sm.OLS(zy, sm.add_constant(z)).fit(cov_type="HAC", cov_kwds={"maxlags": 6}) print(std_model.params.sort_values(key=abs, ascending=False))标准化系数的排序就是影响程度的排序,论文里用一张表列出“变量、原始系数、标准化系数、p 值、影响方向”,四列足够说清问题。显著性用 p 值分档:小于 0.01 写“高度显著”,0.01 到 0.05 写“显著”,大于 0.05 写“不显著并说明原因”。不要只写“显著”两个字,评审会追问显著性水平。
3.4 残差诊断与稳健标准误
残差图不能省。残差如果呈现明显的波动聚集,说明模型漏了非线性或结构突变。常见处理是加一个危机哑变量,或者把样本按时间段切开做子样本回归。子样本系数方向一致,结论才站得住;方向反转,就要在论文里老实说明结构性差异,而不是硬套全样本结论。
4. 问题二的落地:2024-2030 预测的模型组合与区间估计
问题二要预测 2024-2030 年的金价走向。七年跨度,任何单模型都给不出可信的点预测,所以交付重点不是那个数字,而是“预测路径 + 区间 + 假设条件”。把这三样写清楚,比精确到小数点后两位有用得多。
4.1 三条预测路线与组合加权
常用三条路线:回归模型外推、SARIMA 时间序列、随机游走基准。回归外推依赖自变量路径假设,适合做情景分析;SARIMA 只利用金价自身历史,适合捕捉短期惯性;随机游走是金融预测的基准线,任何模型跑不赢它就没有增量价值。
组合方式是按回测误差的倒数加权,误差越小的模型权重越高。
import numpy as np def inv_error_weights(errors): # errors: dict, 模型名 -> 回测 RMSE inv = {k: 1.0 / v for k, v in errors.items()} total = sum(inv.values()) return {k: v / total for k, v in inv.items()} errors = {"reg": 0.082, "sarima": 0.091, "rw": 0.095} w = inv_error_weights(errors) forecast = sum(w[k] * path[k] for k in w)权重函数的意义是把表现好的模型放大,但不让单一模型独占。三个模型的 RMSE 接近时权重会趋于平均,这本身就是一种稳健性保护。如果某个模型权重超过 0.7,要回头检查回测窗口是不是选得太短,导致偶然性被放大。
4.2 分段预测与区间构造
七年不能一条直线外推。通常拆成两段:2024-2026 用回归模型加当前宏观假设,2027-2030 用长期均值回归或情景路径。分段点要写明依据,比如“以主要经济体利率周期拐点为界”,不能拍脑袋。
区间构造用残差 bootstrap:把历史残差有放回抽样,累加到点预测路径上,重复几千次取分位数。
rng = np.random.default_rng(42) resid = model.resid.values h = 84 # 2024-2030 共 84 个月 sims = rng.choice(resid, size=(5000, h), replace=True) paths = point_forecast[:, None] + np.cumsum(sims, axis=1) lower, upper = np.percentile(paths, [5, 95], axis=0)np.cumsum是关键,残差要累积而不是简单相加,因为预测误差随时间发散。90% 区间对应 5% 和 95% 分位,论文里同时给 50% 区间,读者能看出不确定性随年限扩张的速度。
4.3 回测:滚动窗口和误差指标
回测不能只做一次样本外切分。用滚动窗口,每次用前 N 个月预测后 12 个月,滚动前进,统计 RMSE、MAE、MAPE 三个指标。MAPE 在金价这种有零附近波动的序列上会失真,所以主指标用 RMSE,MAPE 只作辅助。
回测结果要能和随机游走基准对比。如果组合模型的 RMSE 只比随机游走低 2%,老实写“预测增益有限,模型主要用于解释而非精确预测”,这比硬吹准确率可信得多。
5. 问题三的落地与避坑:情景量化、策略参数与五个翻车点
问题三考的是把模型结论翻译成投资语言。很多论文在这里突然变成财经评论,前面建的模型一点没用上。正确的做法是让情景假设直接对应模型里的自变量,策略参数直接来自预测区间。
5.1 三类情景的量化设定表
情景不能只写“乐观、中性、悲观”三个词,每个情景要给出自变量的具体取值区间和对应概率。
| 情景 | 实际利率变化 | 汇率因子变化 | 官方购金增速 | 年度均价区间 | 主观概率 |
|---|---|---|---|---|---|
| 复苏情景 | 上行 50-80bp | 走强 3%-5% | 放缓至 5% | 温和回落 | 30% |
| 基准情景 | 震荡 ±20bp | 窄幅波动 | 维持 10% | 区间震荡 | 45% |
| 压力情景 | 下行 80-120bp | 走弱 5%-8% | 加速至 20% | 明显上行 | 25% |
这张表的作用是把定性判断变成可代入模型的数字。把每行的自变量取值代进第 3 章的回归方程,得到三个情景下的金价路径,再与第 4 章的区间预测交叉验证。两边对不上,说明情景假设和模型系数有矛盾,要回去调,而不是各写各的。
5.2 策略输出:从结论句到可执行参数
策略建议要落到参数:配置比例、入场方式、止损线、再平衡频率。比如基准情景下建议黄金资产占组合的 10%-15%,采用分批定投而非一次性建仓,压力情景下上调至 20% 并提高再平衡频率。每个参数都要能追溯到前面的模型结论,比如“配置比例上限来自压力情景下金价与股票资产的相关性由负转正的阈值”。
注意:策略部分必须写风险提示,包括模型假设失效、历史关系不稳定、交易成本未计入。这不是免责,是方案完整性的一部分。
5.3 五个最容易翻车的点
第一个,用价格水平直接回归。现象是 R² 高达 0.95、系数全显著,但残差有明显趋势。原因是价格序列非平稳,回归捕捉的是共同趋势而非因果关系。解决办法是先做 ADF 检验,非平稳就取收益率或差分,再回归。
第二个,忽略单位换算。现象是预测出来的金价和实际差 31 倍。原因是美元/盎司和元/克混用。解决办法是在数据层固定一个主口径,换算函数只写一次,所有下游统一调用。
第三个,样本外预测用了未来信息。现象是回测准确率异常高。原因是宏观变量的同比或插值用了全样本均值。解决办法是所有预处理在训练窗口内完成,再 apply 到测试窗口。
第四个,共线变量硬塞进模型。现象是实际利率系数符号为正,和常识相反。原因是 VIF 超 10 没处理。解决办法是先跑 VIF,超阈值的剔除或合并,再用岭回归做方向对照。
第五个,把点预测当确定结论。现象是论文写“2030 年金价将达到某具体数值”。原因是只报了均值没报区间。解决办法是同时给出 50% 和 90% 区间,并在结论里写明假设条件。
6. 验收与答辩:怎么证明模型不是调参调出来的
验收这套方案,我一般看四件事:数据快照能不能复现、变量筛选有没有留痕、回测是不是滚动做的、结论有没有区间。答辩现场被问得最多的不是模型多复杂,而是“你这个系数为什么是正的”“换个样本还成立吗”。把下面这张表准备好,大部分追问都能接住。
| 验收项 | 检查内容 | 合格线 |
|---|---|---|
| 数据可复现 | 原始数据快照、处理脚本、随机种子 | 换机器能跑出同一张表 |
| 变量留痕 | 候选池、剔除理由、VIF 表 | 每个入模变量有来源 |
| 平稳性 | ADF 检验表 | 因变量和自变量均通过 |
| 显著性 | t 值、p 值、稳健标准误 | 核心变量 p<0.05 |
| 回测 | 滚动窗口、对照随机游走 | RMSE 不劣于基准 |
| 预测 | 点预测 + 50%/90% 区间 | 区间随年限扩张 |
| 策略 | 参数可追溯到模型结论 | 每条建议有出处 |
答辩陈述按“变量→系数→预测→策略”四步走,三分钟讲完,每一步只留一个数字。比如“实际利率每下行 100bp,金价月度收益率平均上行 1.2 个百分点,p 值 0.003;据此在压力情景下给出 20% 配置上限”。评审听到数字和链条,就不会纠缠细枝末节。
血泪经验是:所有图表都要有“如果这个假设不成立会怎样”的一句话备注。当年我第一次讲这套模型,被问“利率为零下限时你的线性关系还成立吗”,当场卡住。从那以后我每次做回归类方案,都强制给核心假设加一条失效边界说明,哪怕只是一行小字。希望帮到你。
本文还有配套的精品资源,点击获取