每年MCM的C题都会刷掉一批把预测当策略的队伍2022年的Problem C尤其典型。题目名字叫Trading Strategies很多队伍拿到数据后第一反应是调LSTM去预测明天收盘价然后拿着预测结果画一条漂亮的净值曲线。等真正提交才发现光预测准确根本没完成题目——2%的交易手续费、最多20笔交易、至少3种资产这些冷冰冰的约束才是决定分数高低的分水岭。这篇复盘我想站在参赛者的角度把这道题从破题到建模、从特征工程到策略回测完整拆一遍。适合三类人读要打数学建模竞赛的本科生、对量化交易策略感兴趣但没有系统入门的朋友以及想用真实金融数据练手时序建模的爱好者。1. 赛题拆解C题真正要的是决策系统不是预测模型1.1 四个任务环环相扣别只看表面题目总共给了四件事。第一件是开发一个模型根据价格动态和其他变量找到买入/卖出/持有的最佳时机并解释模型第二件是把这个模型套到历史每日数据上假设初始资金1000美元、每笔交易收2%手续费展示策略表现第三件是在第二件基础上加一条苛刻的限制——整个时间段内最多交易20次而且组合里至少要包含3种资产第四件要求量化比特币主导地位这个概念并评估它作为交易信号到底有没有用。连起来看任务一给模型任务二给标准回测任务三给约束优化任务四给宏观维度四件事合起来才是一个完整的从信号到交易再到风控的决策系统。我见过不少队伍把精力全砸在任务一上觉得把预测准确率做到80%就赢了。但MCM的C题从来不是单项精度挑战评委更在意的是你的模型能不能在真实成本条件下把一份1000美元的本金滚成一个有说服力的组合净值。预测只是中间变量买卖决策、仓位管理、成本控制才是命题人真正想考的。拿到题目先列一个约束清单再动手写代码通常比直接建模更稳。清单上至少要有手续费率、最大交易次数、初始资金、资产数量下限、可用的数据字段、验证方式。我当时把这个清单贴在编辑器上方每次写一个模块都要抬头看一眼避免跑着跑着就忘掉约束。1.2 数据长什么样决定了模型能吃什么这道题给的是加密资产的日频OHLCV数据也就是每天的开盘价、最高价、最低价、收盘价和成交量时间跨度大致从2016年底到2021年底五六年、一千多个交易日。加密资产的数据有几个显著特点波动剧烈日收益率动辄百分之几没有涨跌停极端行情可以一天翻倍或腰斩交易时段是7×24小时容易出现隔夜跳空宏观季节性很弱节日效应不明显更多的是事件驱动。实操层面有三件事必须做。第一检查数据完整性日频数据经常有缺失尤其是早期资产缺一天两天需要决定是前向填充还是直接丢弃千万不要让NaN静悄悄地渗透进模型。第二把资产分成核心资产和替代资产两组来看题面本身就是这么设计的核心资产流动性和市值大替代资产波动更大、相关性更复杂。建模前先算一下资产间的相关矩阵如果发现某几个替代币的相关性高到0.9以上后面做组合管理时就要小心分散化效果并没有表面看起来那么美好。第三数据划分必须按时间顺序做训练集、验证集、测试集我在后面会专门说为什么绝对不能用未来信息。这些看起来是预处理的小事实际上决定了整个模型的成败。2. 特征工程把价格序列翻译成模型能读的交易语言2.1 三类可靠特征趋势、波动与资金拿到OHLCV之后直接喂给模型的是原始价格和成交量这种做法的效果通常很差。价格的绝对值跨度大、非平稳、且容易被异常值带偏。我习惯先把价格变成对数收益率再在这个基础上构建三类特征。第一类是趋势/动量类特征。RSI、MACD、不同周期的均线斜率、N日收益率变化率都算这一类。它们回答的问题很直接当前是上涨趋势还是下跌趋势这种趋势还有没有余力。以RSI(14)为例大于70说明短期超买小于30说明超卖但直接用超买超卖当反转信号并不安全更合理的用法是把它作为特征让模型自己学。MACD(12,26,9)的DIF与DEA交叉虽然经常被吐槽滞后但在日频数据上仍然能提供一定的趋势滤波能力。第二类是波动类特征。滚动标准差、真实波幅ATR、布林带的带宽、收益率分布的分位数都属于这一类。波动率在交易策略里的作用是调节器波动率高的时候同样的价格变动可能只是噪音仓位要保守一点波动率低的时候趋势容易启动反而是布局时机。我在模型里加了20日滚动标准差和ATR(14)之后策略的最大回撤明显可控了这两个特征的信息量比价格本身大得多。第三类是资金/活跃度类特征。成交量变化率、成交额占比、成交量与价格变动的背离情况都能反映资金态度。加密市场的成交量噪声很大直接用原始成交量做特征容易被个别异常天带偏我建议先取对数再求变化率。量价背离类特征在上涨放量、下跌缩量这类场景里很有区分度但构造的时候要小心幸存者偏差。下面这张表是我在这道题里实际用过的特征给后来的队伍一个参考起点特征组具体特征常用参数在策略中的含义动量RSI14日短期超买超卖压力动量MACD DIF/DEA12,26,9趋势动能动量对数收益率1/5/20日不同周期的价格变化波动滚动标准差20日近期波动水平波动ATR14日真实波动幅度波动布林带带宽20日,2倍标准差当前价格在区间中的位置资金成交量对数变化率5日资金活跃程度资金量价相关系数20日上涨是否伴随放量宏观比特币主导度变化10日市场资金风向第6部分细讲2.2 金融数据的三个坑非平稳、噪声、未来信息第一个坑是非平稳。原始价格序列的均值和方差随时间剧烈变化直接丢进模型模型会学到一堆假规律。最好的解法是统一用对数收益率建模特征也尽量用比率、差分、变化率构造避免用绝对数值作为输入。对数收益率还有近似可加的优势算多日累计收益时直接求和比普通百分比直观许多。第二个坑是噪声。金融日频数据的信噪比极低传统的R方可能连0.1都到不了。这说明模型的目标根本不是预测精确价格而是预测方向或概率。我在做特征筛选时不会只看模型内的特征重要性还会单独计算每个特征与未来收益的秩相关系数IC值IC稳定在0.03以上的特征才值得保留。这个习惯帮我砍掉了一大堆看似花哨、实际没用的指标。当时构造了50多个特征最后经过IC和互信息双重筛选只剩不到20个进入模型训练速度和稳定性都上了一个台阶。第三个坑是未来信息也是最致命的一个。特征构造时如果用了全样本的均值和标准差做归一化模型在验证阶段就偷看了未来统计量回测曲线自然漂亮。正确做法是只允许使用截至t日的数据计算t日的特征归一化参数也必须在训练窗口内估计。我第一年踩过这个坑回测收益高得吓人改成walk-forward重训之后直接缩水三分之一那一刻才真正理解什么叫回测是回测实盘是实盘。数据泄露在MCM C题里是最容易被评委揪出来的硬伤一旦被发现模型再花哨也白搭。3. 预测模型选型XGBoost与LSTM的分工协作3.1 为什么我不建议单靠LSTM硬扛很多队伍看到时间序列就想到LSTM但在这道题里单一LSTM并不是最优解。原因很现实每个资产大约一千多个交易日样本量远不够深层网络充分训练金融数据信噪比又低LSTM容易把噪声当规律出现严重的过拟合。相比之下XGBoost这类梯度提升树模型在中小表格数据上往往表现更稳定自带正则化还能输出特征重要性对评委解释模型特别友好。当然LSTM不是一无是处。它对序列内部的短期依赖更敏感如果能把最近30天的特征窗口拼成一个三维张量喂进去作为XGBoost的补充特征或者直接用它的预测概率与XGBoost做加权集成效果通常比单模型略好。我的习惯是让XGBoost当主力、LSTM当辅助信号源而不是反过来。原因很简单树模型对噪声和缺失值更鲁棒训练速度快可以频繁做滚动重训LSTM训练慢在有限比赛时间内很难精细调参。做个直觉类比XGBoost像一个经验老到的交易员能从大量的价格行为特征里快速总结规律LSTM像一个对最近这段走势记忆超强的新人擅长短线情绪但容易过度反应。实际调参时我把XGBoost的树深控制在4到6层学习率0.05附近列采样比例0.8早停轮数设成50。这个配置处理日频数据一般够用。树深太深会把噪声背下来太浅又学不动非线性的量价关系4到6层是我在多次交叉验证里试出来的平衡点。3.2 滚动训练与预测未来N日收益的目标设计建模的第一步是定义标签。我不用预测明天涨跌这种二分类而是预测未来5天或10天累计对数收益率再转成三分类显著下跌、横盘、显著上涨。为什么是5天或10天因为2%手续费决定了每笔交易至少要持有几天甚至几周才有意义预测超短期方向根本覆盖不了成本。预测窗口拉长到5天以上模型学到的才是趋势级别的信号。三分类的阈值可以这么设未来5天累计收益低于-2%定义为下跌高于2%定义为上涨中间算横盘。这样设置的含义是至少要赚到能抵消一次往返手续费的幅度才算一次有效上涨逻辑上说得通。模型输出softmax概率策略层再根据上涨概率和下跌概率的差来决定动作。我也试过用回归直接预测连续收益再转信号但分类任务在论文里更容易展示混淆矩阵、精确率和召回率评委读起来不费劲。训练方式必须用滚动窗口。我以250个交易日为训练窗口每次滚动20个交易日重训一次预测未来20个交易日然后在验证集上评估。所有特征都只使用截至预测日的历史信息。模型的输出不是涨而是上涨概率和期望收益率。再到策略层设定触发阈值期望收益率超过手续费成本风险补偿阈值才开仓低于止损线才清仓。阈值怎么定我给出的经验值是单次交易的期望收益率至少要覆盖两次手续费的2倍也就是大约8%的预期收益才值得动一次手。低于这个值交易次数再多也是在给平台交手续费。有了阈值模型输出的连续分数就变成了稀疏的交易指令这比让模型每天输出一个买/卖/持的哑变量稳健得多。我在论文里会把模型的预测分布画出来再叠加阈值线评委一眼就能看懂决策逻辑。4. 策略层手续费、仓位和订单模拟才是收益的真瓶颈4.1 2%手续费到底有多可怕先算一笔账。初始资金1000美元买入时按交易金额的2%扣手续费意味着真正用于持仓的只有980美元假设价格没涨没跌你卖出时又要扣2%最后剩下960.4美元。也就是说一次完整的买入卖出动作哪怕价格完全不动你也要亏掉约3.96%的本金。要想不亏价格必须先涨 1000 ÷ 960.4 - 1 ≈ 4.12%。这个数字就是盈亏平衡点。把这笔账放到交易频率上看更吓人。如果每月满仓买卖两次一年24次完整往返光手续费的复利损耗就是 0.9604^24 ≈ 0.378本金缩水到三分之一。这就是为什么频繁交易必死在交易策略题目里是一条铁律。很多队伍回测出漂亮的年化收益却没意识到那是建立在每次预测都恰好能覆盖手续费的幻觉之上。正确思路是让交易变得稀疏只在高胜率、高盈亏比的时点出手。完整往返次数剩余本金按复利单位美元010001960.45约81810约66620约444这张表我建议直接放进论文的问题分析部分比写十句话都更有冲击力。评委看到这张表自然就理解为什么题目要限制交易次数——因为真实交易里成本才是最大的敌人。4.2 仓位、止损和订单模拟的通用做法模型输出了信号策略层还要决定三件事买多少、什么时候止损、怎么记账。仓位管理上最简单的做法是固定分数仓位比如每次只动用总资金的25%或三分之一。不要满仓押一个信号因为模型预测的错误率一定存在满仓遇到一次极端行情基本就没有下一笔交易了。想稍微进阶一点可以用简化的凯利公式最佳仓位比例≈(胜率×盈亏比 - 亏损率) / 盈亏比但这个公式对参数估计误差极敏感比赛里建议把算出的仓位再砍一半使用求稳。止损方面我按ATR设置动态止损线。比如单笔持仓亏损超过1.5倍ATR就强制离场这比固定百分比止损更能适应不同资产的波动节奏。ATR止损的逻辑是波动大的资产允许更大的回撤空间波动小的资产止损紧一点避免被正常波动洗出场。订单模拟要注意成交价的处理。日频数据回测通常用当日收盘价成交但更严谨的作法是把买入价和卖出价分开后一天的开盘价或最高/最低价附近成交更接近实际情况。模拟器同时维护现金和持仓市值两个变量每产生一笔交易就扣一次手续费然后逐日计算总资产净值。我建议把这段模拟代码写成一个独立函数输入信号表、输出净值和交易明细。这样后面做敏感性分析和图表都方便不用每次重跑模型。def backtest(price_df, signals, init_cash1000.0, fee0.02): cash init_cash holdings {} equity_curve [] for date in price_df.index: # 先处理当日的买卖信号 for asset, side in signals.get(date, []): if side buy: budget cash * 0.25 # 固定分数仓位 shares budget * (1 - fee) / price_df.loc[date, asset] cash - budget holdings[asset] holdings.get(asset, 0) shares elif side sell and asset in holdings: proceeds holdings[asset] * price_df.loc[date, asset] * (1 - fee) cash proceeds holdings.pop(asset) # 每日资产估值 total cash for asset, shares in holdings.items(): total shares * price_df.loc[date, asset] equity_curve.append(total) return equity_curve这段代码刻意保持朴素没有加止盈止损的复杂逻辑目的是让读者先跑通主干骨架。真实比赛里我建议把ATR止损、最大持仓周期、单资产持仓上限都写进去每加一个规则就对照一次手续费成本防止逻辑越复杂、损耗越大。5. 20次交易上限稀缺交易权如何配置5.1 交易次数不是限制是命题人在帮你过滤垃圾信号第三问加了一个看似苛刻的限制整个时间段内最多20次交易组合至少包含3种资产。先算算如果乱来会怎样。假设每次买入和卖出各算一次交易20次动作最多对应10次完整往返。每次完整往返在价格不动的情形下资金乘以0.960410轮下来 1000×0.9604^10 ≈ 666直接蒸发三分之一本金。哪怕每次往返都猜对了方向但只赚5%扣掉成本也就勉强跑平。所以20次限制的潜台词不是让你少交易而是只给你20次开枪机会每一枪都要有足够的期望收益。这个限制还隐含了一个组合管理要求至少要包含3种资产。如果只挑BTC和ETH两只相关性极高的币分散化名不副实。我建议在候选池里加一只与主流资产相关性相对较低的替代币这样当比特币主导度上升或下降切换时组合才有真正独立的收益来源。我处理这个问题的思路分两步。第一步用前几部分的连续交易模型在全时间段内生成一个候选交易池记录每笔潜在交易的触发时间、方向、资产、预期收益和预期回撤。第二步把候选交易按单位风险的期望收益排序用贪心或背包思想选出不超过预算的Top组合。这里的预算有两个总交易次数≤20总投入资金≤可用资金。贪心法虽然不保证全局最优但在候选交易数量不大的情况下配合随机扰动做多次筛选结果已经足够稳定。5.2 用蒙特卡洛给策略做压力测试挑出了20笔交易怎么证明这套选择不是运气好我的做法是蒙特卡洛模拟。思路很简单把每个资产的日收益率序列做有放回抽样生成500条改写后的价格路径再在这些路径上按同一套信号和同一套交易规则回测统计最终净值的分布。如果策略净值的中位数能稳稳跑赢买入持有基准且5%分位净值仍然为正就说明策略本身有稳健性而不是踩中了某段时间的狗屎运。蒙特卡洛还可以顺手回答一个评委爱问的问题策略对参数敏感吗把手续费率从2%改到3%、把ATR止损系数从1.5改到2.0、把预测窗口从5天改到10天分别重跑画一张敏感性分析表。参数轻微变化时收益不应剧烈波动这是评价一个策略工程化程度的重要指标。我在提交前把所有关键参数都扫了一遍论文里放了一张参数-收益-最大回撤的表格评委看到这个细节通常会给出不错的评价。6. 比特币主导度一个常被忽略的宏观交易信号6.1 主导度的含义资金风向标比特币主导度最直白的定义是比特币的市值或成交量占整个加密货币市场的比例。2016年前后这个数字高达80%以上2017年山寨季跌到40%左右2020到2021年又经历了多次拉锯。主导度上升通常意味着资金正在从替代币撤向比特币市场进入避险或抱团模式主导度下降则说明资金外溢到各类替代币山寨行情往往更活跃。这个指标本身不复杂但它回答了一个很关键的问题当下市场是由比特币主导的风险回避状态还是由替代币主导的risk-on状态。在题目里要量化主导度我建议至少算两个口径市值口径和成交额口径。两者变化方向偶尔会背离市值口径更适合反映存量资金的配置成交额口径更适合反映短期交易情绪。把两个口径都做成特征比只用一个更严谨。严格来说题目里要求量化主导度不只要给一个定义最好还要讨论这个指标在什么场景下会出现异常和失真比如某只替代币突然暴涨时市值占比突变这时候成交额口径可能比市值口径更敏感。6.2 把主导度变成可用的交易信号主导度的用法有三种我都在模型里试过。第一种最简单作为所有资产的公共特征加入XGBoost模型。主导度变化率、主导度相对其20日均值的位置、主导度斜率都是可以直接喂给模型的特征。这样模型会自动学到主导度上升时小心替代币、主导度下降时更乐观这类非线性关系。第二种是状态切换。我按主导度的方向性把市场切成两个状态主导度20日趋势向上时策略只允许交易比特币主导度趋势向下时策略优先考虑替代币。这个规则朴素但回测下来确实降低了最大回撤原因是它自动避开了比特币吸血、替代币阴跌的那几个危险阶段。第三种是独立因子检验。计算主导度变化率与未来10天替代币组合超额收益的Spearman秩相关系数作为IC值。如果IC的绝对值稳定超过0.05就说明这个指标有独立预测能力。代码实现不复杂from scipy.stats import spearmanr dom_change dominance.diff(10) alt_ret altcoins[close].pct_change(10).mean(axis1) ic spearmanr(dom_change.dropna(), alt_ret.reindex(dom_change.index).dropna())我在实际数据上跑出的结果比较有意思主导度快速上升后替代币在未来5到10天往往跑输这个信号在极端行情里尤其明显可以作为止盈或减仓的辅助条件。不过要提醒一句任何单一因子的IC都不会太高主导度也不例外它更适合当状态开关而不是精确择时器。论文里呈现时我建议画三张图主导度时间序列、主导度变化率与替代币收益的散点热力图、按主导度状态分组后的两组净值对比曲线。这三张图基本就能把量化-验证-结论这个逻辑闭环撑起来。不要把主导度的检验停留在相关性嘴上说说一定要落到分组收益和净值曲线上这样才经得起评审批判。最后分享一个我真实踩过的坑。第一次做这道题为了图省事我用全样本的均值和标准差做了特征标准化结果回测曲线漂亮得像是发现了圣杯。后来被提醒改用严格的分段训练、分段预测收益立马缩水接近三分之一那一刻才意识到回测里80%的利润可能只是数据泄露的礼物。MCM的C题拼到最后比的不是谁的模型更炫而是谁更诚实地对待约束、成本和不确定性。管住手算清手续费尊重市场状态的变化——这三件事比任何花哨的网络结构都值钱。如果你也打算冲这个题建议先把回测引擎写得足够干净再回头调模型顺序反了会很痛苦。
阅读完成 · 觉得有帮助?