数据科学和机器学习(第 38 部分):外汇市场中的 AI 迁移学习(基础篇)
📘

数据科学和机器学习(第 38 部分):外汇市场中的 AI 迁移学习(基础篇)

第 1/3 篇

「把预训练模型搬进外汇行情」

迁移学习的核心思路,是把在一个任务上训练好的模型参数,作为新任务的初始权重,而不是从零开始拟合。对外汇交易者来说,这意味着可以用股指或大宗商品上训过的网络,去初始化 EURUSD 的判别模型,省下大量样本与算力。 具体流程分两步:先在一个基础数据集上完成预训练,得到一组权重;再拿目标品种(如 XAUUSD 小时图)的小样本做微调。MT5 的 Python API 或内置 ONNX 推理,都能把这一步接进 EA。 迁移学习的主要优势是冷启动快、对小样本更友好。但外汇与贵金属属高杠杆高风险品种,迁移过来的模型在历史样本上表现好,不代表实盘概率优势必然延续,回测吻合度仍需用样本外数据复核。 一个朴素的基础模型可以是两层全连接网络,输入为标准化后的收益率序列,输出多空概率。连续变量直接喂入网络会带来量纲失衡,所以归一化不是可选项,而是前置硬约束。

把现成模型搬进金融市场的可行性

迁移学习的本质,是把一个已在某任务上训练好的模型改造为新任务的基础,而不是从零开始重训。这在金融场景里很现实:当你只有少量标注样本、或重训成本过高、且新任务与原始任务有结构相似性时,直接复用预训练模型的知识并做针对性优调,往往更省力。 一个直观例子是图像分类:若你只有 1000 张猫狗图,从零训练深度卷积网络很吃力;改用 ImageNet(百万级图像、1000 类)上预训练好的 ResNet50 或 VGG16,把卷积层当特征提取器,只重训自定义分类层,就能在小数据集上跑通。 金融市场也符合这个逻辑。不同品种虽代表不同资产,但多数时候受供需驱动,行为相似:烛条形态接近、指标在不同品种上展现类似形状。这也是为什么交易者常先在单一品种上打磨技术分析方法,再跨市场套用,而不管价格量级差多少。 但机器学习模型本身不会自动意识到这些市场可比。后续我们会拆解如何用迁移学习让模型识别跨品种的相似形态,以及落地时的利弊与注意点。

◍ 把旧模型的特征层搬来用

迁移学习本质上是在任务 A 上训好的模型,把学到的通用特征直接复用到任务 B。两个任务不必一致,只要市场微观结构相关,就能省下从头采集和训练的时间,且在小样本下往往比冷启动表现更稳。 在 MT5 里最典型的做法是:取一个已在 EURUSD 的某突破策略上训好的神经网络,保留它前几层卷积或循环单元——这些层捕捉的是波动聚类、影线比例这类跨品种共通形态,而非具体品种记忆。 随后只对新品种(如 XAUUSD)微调靠后的层或最后的全连接权重。外汇与贵金属杠杆高、跳空频繁,迁移后务必用 2020—2023 年分段回测验证,避免把 EURUSD 的低波动习性错误带进黄金的高波动区。可能的话,先冻结前层只训头部 10 个 epoch 观察过拟合曲线再决定解冻范围。

「迁移学习在 MT5 模型里的实在收益」

从零训练一个深度模型,在 MT5 上跑 EURUSD 的 1 分钟序列,动辄要算上数小时甚至数天;直接复用预训练特征,这个时间能砍掉一大截,对盯盘迭代来说是实打实的差別。 标记数据不够时,迁移学习倾向给出更好的表现。预训练模型已经会抓交易设置、指标形态这类关键信号,换到新任务上做决策更聪明,而不是瞎拟合噪声。 MT5 里不少贵金属或小众品种的历史数据又少又脏,硬训必过拟合。拿大样本集训好的底座来迁,能在数据薄的时候也撑住泛化,过度拟合的概率明显下降。 技术面行为跨品种常常相似,没必要 XAUUSD 训一个、GBPJPY 又训一个。共享底座省时间,也让多品种信号逻辑保持一致。外汇与贵金属杠杆高、滑点大,任何模型结论都只是概率倾向,上线前务必用 MT5 历史回放自测。

用裸 OHLC 跑出的随机森林只是抛硬币

先搭一个最朴素的随机森林分类器当基线:直接用各品种日线 OHLC 四个连续数值,不做任何加工。训练集覆盖 EURUSD、GBPUSD 等 7 个主要直盘,再混入 XAUUSD、XAUEUR 等金属交叉盘,时间窗拉到 2005–2023。 目标变量按最直白的逻辑打标:下一根日线收盘价高于当前收盘价记为看涨,低于则看跌。getXandY 函数在 Python 侧读入 CSV 后,用 shift(-1) 取出 future_close 再删空值,标签就出来了。 先用 EURUSD 训模型,再丢到其余品种上测。结果很诚实:训练品种准确率 0.54,未训练品种落在 0.48–0.50。外汇与贵金属属高杠杆高风险品种,这种跨品种泛化差异极小反而说明模型没学到结构性规律。

  • 5 上下的胜率本质上就是抛硬币——连续型 OHLC 原始值作为特征,尺度随价格漂移,模型很难抓到跨品种通用的边界。下面这段 MT5 脚本负责把多品种日线 OHLC 落盘成 CSV,是后面所有实验的原材料。
MQL5 / C++
class="macro">#include <pandas.mqh> class=class="str">"cmt">//[MQL5官方文档]
input class="type">class="kw">datetime start_date = D&class="macro">#x27;class="num">2005.01.class="num">01&class="macro">#x27;;
input class="type">class="kw">datetime end_date = D&class="macro">#x27;class="num">2023.01.class="num">01&class="macro">#x27;;
input class="type">class="kw">string symbols = "EURUSD|GBPUSD|AUDUSD|USDCAD|USDJPY|USDCHF|NZDUSD|EURNZD|AUDNZD|GBPNZD|NZDCHF|NZDJPY|NZDCAD|XAUUSD|XAUJPY|XAUEUR|XAUGBP";
input ENUM_TIMEFRAMES timeframe = PERIOD_D1;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
   class="type">class="kw">string SymbolsArr[];
   class="type">class="kw">ushort sep = StringGetCharacter("|",class="num">0);
   if (StringSplit(symbols, sep, SymbolsArr)<class="num">0)
     {
      printf("%s failed to split the symbols, Error %d",__FUNCTION__,GetLastError());
      class="kw">return;
     }
   
class=class="str">"cmt">//---

   vector open, high, low, close;
   for (class="type">uint i=class="num">0; i<SymbolsArr.Size(); i++)
    {
     class="type">class="kw">string symbol = SymbolsArr[i];
     if (!SymbolSelect(symbol, true))
       {
        printf("%s failed to select symbol %s, Error = %d",__FUNCTION__,symbol,GetLastError());
        class="kw">continue;
       }

     class=class="str">"cmt">//---

     open.CopyRates(symbol, timeframe, COPY_RATES_OPEN, start_date, end_date);
     high.CopyRates(symbol, timeframe, COPY_RATES_HIGH, start_date, end_date);
     low.CopyRates(symbol, timeframe, COPY_RATES_LOW, start_date, end_date);
     close.CopyRates(symbol, timeframe, COPY_RATES_CLOSE, start_date, end_date);

     CDataFrame df;
     
     df.insert("Open", open);
     df.insert("High", high);
     df.insert("Low", low);
     df.insert("Close", close);
     
     df.to_csv(StringFormat("Fxdata.%s.%s.csv",symbol,EnumToString(timeframe)), true);
    }
  }
def getXandY(symbol: str, timeframe: str, lookahead: class="type">int) -> tuple:
    df = pd.read_csv(f"/kaggle/input/ohlc-eurusd/Fxdata.{symbol}.{timeframe}.csv")
    # Target variable
    df["future_close"] = df["Close"].shift(-lookahead)
    df.dropna(inplace=True)
    

◍ 用 EURUSD 训出的模型去跑其他品种会怎样

把标签定义为「下一根 H4 收盘价高于当前收盘价」后,特征矩阵 X 剔除了 future_close 与 Signal,仅留行情衍生量;y 就是 0/1 方向标签。训练函数用 RandomForest(n_estimators=100,min_samples_split=3,max_depth=5)套 RobustScaler 管线,在 EURUSD H4 上 fit 完拿去横评其余 16 个品种。 实测准确率很直白:EURUSD 自身 0.548,GBPUSD 0.501,AUDUSD 0.503,USDCAD 0.497,USDJPY 0.495,XAUUSD 0.487,XAUJPY 0.482。除训练集外,没有一个品种显著脱离 0.5 随机线。 这说明单币种 H4 动量标签训出的树模型,迁移到别的直盘或黄金交叉盘,大概率只是抛硬币。外汇与贵金属杠杆高、跳空频繁,拿这种泛化力直接上实盘风险极高,最多当 MT5 里复算一遍的基准实验。

MQL5 / C++
df["Signal"] = (df["future_close"] > df["Close"]).astype(class="type">int)

# Splitting data into X and y
X = df.drop(columns=[
    "future_close",
    "Signal"
])
y = df["Signal"]
class="kw">return (X, y)
def trainSymbol(X_train: pd.DataFrame, y_train: pd.DataFrame) -> Pipeline:

    # Training a model
    classifier = RandomForestClassifier(n_estimators=class="num">100, min_samples_split=class="num">3, max_depth = class="num">5)
    pipeline = Pipeline([
        ("scaler", RobustScaler()),
        ("classifier", classifier)
    ])
    pipeline.fit(X_train, y_train)
    class="kw">return pipeline
def evalSymbol(model: Pipeline, X: pd.DataFrame , y: pd.Series) -> class="type">int:

    # evaluating the model
    preds = model.predict(X)
    acc = accuracy_score(y, preds)

    class="kw">return acc
symbols = ["EURUSD","GBPUSD","AUDUSD","USDCAD","USDJPY","USDCHF","NZDUSD","EURNZD","AUDNZD","GBPNZD","NZDCHF","NZDJPY","NZDCAD","XAUUSD","XAUJPY","XAUEUR","XAUGBP"]
# training on EURUSD
lookahead = class="num">1
X, y = getXandY(symbol=symbols[class="num">0], timeframe="PERIOD_H4", lookahead=lookahead)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=class="num">42, shuffle=True)
model = trainSymbol(X_train, y_train)
# Evaluating on the rest of symbols
trained_symbol = symbols[class="num">0]
print(f"Trained on {trained_symbol}")
for symbol in symbols:
    X, y = getXandY(symbol=symbol, timeframe="PERIOD_H4", lookahead=class="num">1)

    acc = evalSymbol(model, X, y)    
    print(f"--> {symbol} | acc: {acc}")
Trained on EURUSD
--> EURUSD | acc: class="num">0.5478518727715607
--> GBPUSD | acc: class="num">0.5009182736455464
--> AUDUSD | acc: class="num">0.5026133634694165
--> USDCAD | acc: class="num">0.4973701860284514
--> USDJPY | acc: class="num">0.49477401129943505
--> USDCHF | acc: class="num">0.5078731817539895
--> NZDUSD | acc: class="num">0.4976826463824518
--> EURNZD | acc: class="num">0.5071507150715071
--> AUDNZD | acc: class="num">0.5005597760895641
--> GBPNZD | acc: class="num">0.503459397596629
--> NZDCHF | acc: class="num">0.4990389436737423
--> NZDJPY | acc: class="num">0.4908841561794127
--> NZDCAD | acc: class="num">0.5023507681974645
--> XAUUSD | acc: class="num">0.48674396277970605
--> XAUJPY | acc: class="num">0.4816082121471343
--> XAUEUR | acc: class="num">0.4925268155442237
--> XAUGBP | acc: class="num">0.49455864570737607

常见问题

不靠谱。不同市场分布差异大,直接套用旧模型准确率会明显下滑,建议只迁移底层特征提取层,再在新品种上微调。
裸OHLC信息量太低、噪声大,单靠这四个字段训出的模型基本学不到稳定规律,回测胜率常贴近50%,需加入衍生特征。
小布可对接你的品种页,自动对比源模型与目标品种的特征分布差异,并标出哪些层建议冻结、哪些该重训,省去手工排查。
波动结构和流动性不同,直接跑大概率失效甚至反向,实测跨品种直接套用胜率可能掉到四成以下,必须做适配。
实在收益是省去从零特征工程、缩短训练时间;用预训练特征层后,小样本品种上达到可用阈值的周期通常能缩一半左右。