数据科学与机器学习(第23部分):为什么LightGBM和XGBoost能超越许多AI模型?·综合运用
📘

数据科学与机器学习(第23部分):为什么LightGBM和XGBoost能超越许多AI模型?·综合运用

第 3/3 篇

「用管道跑通 LightGBM 二分类」

把 LightGBM 塞进 sklearn 的 Pipeline,配合 StandardScaler 做归一化,几行就能训出一个二分类模型。核心超参里,num_leaves 控制单树叶子数,原文示例取 25,典型区间 20–50;max_depth 设 5,常见范围 3–10;learning_rate 用 0.05,偏低更稳但需更多树;feature_fraction 0.9 表示每轮随机用 90% 特征来抑过拟合。 下面这段是原文给出的训练代码,直接 pip 装包后可在 Python 复现。注意 LGBMClassifier 被包在 pipe 里,fit 时自动走 scaler → 模型;控制台里 feature_fraction 警告只是提示 colsample_bytree 被忽略,不影响结果。 [CODE] pip install lightgbm params = { 'boosting_type': 'gbdt', # Gradient Boosting Decision Tree 'objective': 'binary', # For binary classification (use 'regression' for regression tasks) 'metric': ['auc','binary_logloss'], # Evaluation metric 'num_leaves': 25, # Number of leaves in one tree 'n_estimators' : 100, # number of trees 'max_depth': 5, 'learning_rate': 0.05, # Learning rate 'feature_fraction': 0.9 # Fraction of features to be used for each boosting round } pipe = Pipeline([ ("scaler", StandardScaler()), ("lgbm", lgb.LGBMClassifier(**params)) ]) # Fit the pipeline to the training data pipe.fit(X_train, y_train) [/CODE] 训练完用 X_test 跑预测,原文示例测试集 104 个 0 类、96 个 1 类,模型整体准确率约 69%,分类报告里 0 类 f1 0.70、1 类 f1 0.66。这个精度不算高,但说明管道化流程已通,后续可换 ONNX 丢进 MT5 做信号推断。 调参别拍脑袋:n_estimators 从 100 起手,配合早停;num_leaves 和 max_depth 用交叉验证卡过拟合。外汇与贵金属行情噪声大,这类模型实盘前务必做样本外验证,信号失效概率不低。

MQL5 / C++
pip install lightgbm
params = {
    &class="macro">#x27;boosting_type&class="macro">#x27;: &class="macro">#x27;gbdt&class="macro">#x27;,  # Gradient Boosting Decision Tree
    &class="macro">#x27;objective&class="macro">#x27;: &class="macro">#x27;binary&class="macro">#x27;,     # For binary classification(use &class="macro">#x27;regression&class="macro">#x27; for regression tasks)
    &class="macro">#x27;metric&class="macro">#x27;: [&class="macro">#x27;auc&class="macro">#x27;,&class="macro">#x27;binary_logloss&class="macro">#x27;],  # Evaluation metric
    &class="macro">#x27;num_leaves&class="macro">#x27;: class="num">25,          # Number of leaves in one tree
    &class="macro">#x27;n_estimators&class="macro">#x27; : class="num">100,      # number of trees
    &class="macro">#x27;max_depth&class="macro">#x27;: class="num">5,
    &class="macro">#x27;learning_rate&class="macro">#x27;: class="num">0.05,     # Learning rate
    &class="macro">#x27;feature_fraction&class="macro">#x27;: class="num">0.9    # Fraction of features to be used for each boosting round
}
pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("lgbm", lgb.LGBMClassifier(**params))
])
# Fit the pipeline to the training data
pipe.fit(X_train, y_train)

◍ 分类报告里的 0.69 意味着什么

上面这段输出是某次二分类模型在 200 个样本上的 sklearn 风格评估表。accuracy 落在 0.69,macro avg 与 weighted avg 的 F1 都在 0.68 附近,说明模型对两类样本的平均辨识力仅略高于随机猜。 在 MT5 里拿这类指标做信号过滤时,要清楚 0.69 的准确率放到外汇小时线里,扣除点差和滑点后大概率失去正期望。贵金属同样属于高杠杆高风险品种,单看这份报告不能直接用作开仓依据。 若你想复现,把模型预测结果和真实标签喂进 ClassificationReport 类,样本量设 200 跑一遍,重点看 weighted avg 的 precision 是否随品种波动明显偏离 0.69。

MQL5 / C++
              accuracy                                       class="num">0.69       class="num">200
   macro avg       class="num">0.68      class="num">0.68      class="num">0.68       class="num">200
weighted avg       class="num">0.68      class="num">0.69      class="num">0.68       class="num">200

把 LightGBM 与 XGBoost 管线导成 ONNX

这两个 boosting 模型不像 sklearn 或 Keras 那样一行 dump 就能落地,它们各自带自定义算子,得借 skl2onnx 与 onnxmltools 的桥接转换器走一遍注册流程。 实际保存时两个模型流程完全一致:先 update_registered_converter 把分类器形状计算和转换器挂上,再 convert_sklearn 指定输入张量维度与 target_opset,最后 SerializeToString 写文件。下面代码里 EUUSD H1 的 LightGBM 落盘为 lightgbm.eurusd.h1.onnx,XGBoost 落盘为 xgboost.eurusd.h1.onnx,opset 主图 12、ml 域 2。 外汇与贵金属模型导出涉及历史样本过拟合风险,实盘加载前应在 MT5 用 out-of-sample 时段重测,概率层面验证信号衰减。

MQL5 / C++
from skl2onnx.common.data_types class="kw">import FloatTensorType
from skl2onnx class="kw">import convert_sklearn, to_onnx, update_registered_converter
from skl2onnx.common.shape_calculator class="kw">import calculate_linear_classifier_output_shapes
from onnxmltools.convert.xgboost.operator_converters.XGBoost class="kw">import convert_xgboost
from onnxmltools.convert class="kw">import convert_xgboost as convert_xgboost_booster
update_registered_converter(
    lgb.LGBMClassifier,
    "GBMClassifier",
    calculate_linear_classifier_output_shapes,
    convert_lightgbm,
    options={"nocl": [False], "zipmap": [True, False, "columns"]},
)
model_onnx = convert_sklearn(
    pipe,
    "pipeline_lightgbm",
    [("input", FloatTensorType([None, X_train.shape[class="num">1]]))],
    target_opset={"": class="num">12, "ai.onnx.ml": class="num">2},
)
# And save.
with open("lightgbm.eurusd.h1.onnx", "wb") as f:
    f.write(model_onnx.SerializeToString())
update_registered_converter(
    xgb.XGBClassifier,
    "XGBClassifier",
    calculate_linear_classifier_output_shapes,
    convert_xgboost,
    options={"nocl": [False], "zipmap": [True, False, "columns"]},
)
model_onnx = convert_sklearn(
    pipe,
    "pipeline_xgboost",
    [("input", FloatTensorType([None, X_train.shape[class="num">1]]))],
    target_opset={"": class="num">12, "ai.onnx.ml": class="num">2},
)
# And save.
with open("xgboost.eurusd.h1.onnx", "wb") as f:
    f.write(model_onnx.SerializeToString())

「GBDT模型在MT5里的输入层坑点」

把 LightGBM 导出的 ONNX 塞进 MT5,表面流程和别的模型差不多,实际卡点集中在张量维度声明。日志里写得很直白:输入层 shape 是 [-1, 8],输出层有两个,一个是 output_label(shape [-1]),另一个是 output_probability,类型是 ONNX_TYPE_SEQUENCE,底层是 ZipMap,也就是带标签和概率的两个一维数组打包成的映射。 用 Netron 打开模型能确认,第二输出层这种序列套映射的结构,用 OnnxSetOutputShape 去硬改尺寸会报 Err=5802,而且抛出的警告信息并不好懂。实测把第二输出层维度强行设为 1,虽然终端会刷一条警告,但模型推理仍能跑通,EURUSD H1 上加载后显示 ONNX model Initialized 即代表可用。 外汇与贵金属杠杆高,这类模型推理只是辅助信号,实盘前务必在策略测试器用历史数据回验。下面这段是 LightGBM 类里负责枚举输入输出张量信息的核心方法,debug 模式下会把层数、名字、维度全打印出来,方便你核对模型是不是真加载对了。

MQL5 / C++
class="type">bool CLightGBM::OnnxLoad(class="type">long &handle)
{
class=class="str">"cmt">//--- since not all sizes defined in the input tensor we must set them explicitly
class=class="str">"cmt">//--- first index - batch size, second index - series size, third index - number of series(only Close)

  OnnxTypeInfo type_info; class=class="str">"cmt">//Getting onnx information for Reference In case you forgot what the loaded ONNX is all about
  class="type">long input_count=OnnxGetInputCount(handle);
  if (MQLInfoInteger(MQL_DEBUG))
      Print("model has ",input_count," input(s)");

  for(class="type">long i=class="num">0; i<input_count; i++)
   {
      class="type">class="kw">string input_name=OnnxGetInputName(handle,i);
      if (MQLInfoInteger(MQL_DEBUG))
          Print(i," input name is ",input_name);

      if(OnnxGetInputTypeInfo(handle,i,type_info))
        {
          if (MQLInfoInteger(MQL_DEBUG))
              PrintTypeInfo(i,"input",type_info);
          ArrayCopy(inputs, type_info.tensor.dimensions);
        }
   }
  class="type">long output_count=OnnxGetOutputCount(handle);
  if (MQLInfoInteger(MQL_DEBUG))
      Print("model has ",output_count," output(s)");

  for(class="type">long i=class="num">0; i<output_count; i++)
   {
      class="type">class="kw">string output_name=OnnxGetOutputName(handle,i);
      if (MQLInfoInteger(MQL_DEBUG))
          Print(i," output name is ",output_name);

      if(OnnxGetOutputTypeInfo(handle,i,type_info))
       {
          if (MQLInfoInteger(MQL_DEBUG))
              PrintTypeInfo(i,"output",type_info);
          ArrayCopy(outputs, type_info.tensor.dimensions);
       }
   }

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

◍ 给 ONNX 模型喂形状与跑推理的细节

在 MT5 里加载 LightGBM 导出的 ONNX 模型,第一步是把输入输出的维度告诉推理句柄。代码里用 OnnxSetInputShape 逐节点绑定 inputs 数组,若返回 false 会打印错误码并触发 DebugBreak,方便你当场抓出维度不匹配的问题。 输出侧的处理更宽松:OnnxSetOutputShape 失败时只打印 'Failed to set the Output[%d] shape',注释掉了 DebugBreak 和 return false。这意味着某个输出节点形状异常时,初始化流程仍会继续,但后续推理可能拿到错位数据,建议手动把这两行放开做严格校验。 真正跑预测靠 OnnxRun,先把 output_data 用 ArrayResize 撑到 outputs.Size() 大小,再以 ONNX_DATA_TYPE_FLOAT 类型传入特征 x_float。外汇与贵金属行情的高波动特性下,模型推理结果仅代表概率倾向,实盘使用前务必用历史 tick 回测验证维度链路。 下面这段是核心初始化与推理调用的节选,逐行拆一下关键动作:replace 把负数维度替成 UNDEFINED_REPLACE;循环设 shape 失败即停;OnnxRun 失败则返回空 proba 避免脏数据外流。

MQL5 / C++
  replace(inputs);
  replace(outputs);

class=class="str">"cmt">//--- Setting the input size
  for (class="type">long i=class="num">0; i<input_count; i++)
    if (!OnnxSetInputShape(handle, i, inputs)) class=class="str">"cmt">//Giving the Onnx handle the input shape
      {
        printf("Failed to set the input shape Err=%d",GetLastError());
        DebugBreak();
        class="kw">return class="kw">false;
      }

class=class="str">"cmt">//--- Setting the output size

  for(class="type">long i=class="num">0; i<output_count; i++)
    {
      if(!OnnxSetOutputShape(handle,i,outputs))
      {
        printf("Failed to set the Output[%d] shape Err=%d",i,GetLastError());
        class=class="str">"cmt">//DebugBreak();
        class=class="str">"cmt">//class="kw">return class="kw">false;
      }
    }

  initialized = true;

  Print("ONNX model Initialized");
  class="kw">return true;
}
  class="type">class="kw">float output_data[];
  class="kw">struct Map
    {
      class="type">class="kw">ulong          key[];
      class="type">class="kw">float          value[];
    } output_data_map[];

class=class="str">"cmt">//---
  ArrayResize(output_data, outputs.Size());

  if (!OnnxRun(onnx_handle, ONNX_DATA_TYPE_FLOAT, x_float, output_data, output_data_map))
    {
      printf("Failed to get predictions from Onnx err %d",GetLastError());
      class="kw">return proba;
    }

实时与测试两套预测接口怎么选

在 MT5 里做模型推理,得先分清实时预测和离线测试走的是哪套接口。给单个特征向量 x 调用 predict_bin,返回的是该样本的分类标签(长整型),适合挂在 EA 的 OnTick 里做逐根 K 线实时判定。 同名的 predict_bin 还有接收 matrix 的重载,一次吐出整批样本的标签向量,主要用在回测脚本里批量验证,而不是线上逐笔触发。 概率类接口 predict_proba 同理:vector 入参对应单样本实时概率,matrix 入参对应全样本矩阵概率。外汇与贵金属杠杆高、滑点跳空频繁,实时概率只代表模型当下的倾向,不等于成交价,实盘前务必用 matrix 版在 historical 数据上跑一遍误差分布。 打开 MT5 的 MetaEditor,把这两个重载直接塞进你的模型类声明里,编译后先用一组 EURUSD 的 H1 特征矩阵调 predict_proba(matrix),看输出维度是否等于样本数,再决定接不接实时分支。

MQL5 / C++
class="kw">virtual class="type">long predict_bin(const vector &x); class=class="str">"cmt">//REturns the predictions for the current given matrix | useful in real-time prediction
class="kw">virtual vector predict_proba(const vector &x); class=class="str">"cmt">//Returns the predictions in probability terms | useful in real-time prediction
class="kw">virtual matrix predict_proba(const matrix &x); class=class="str">"cmt">//Returns the predicted probability for the whole matrix | useful for testing
class="kw">virtual vector predict_bin(const matrix &x); class=class="str">"cmt">//gives out the vector for all the predictions | useful for testing

「GBDT模型实盘回测与样本外准确率拆解」

在 OnInit 里把 ONNX 模型加载进来后,EA 就能用和训练时同构的特征去跑推理。简单策略就是:每根新 K 线开盘时取信号,0 为空头、1 为多头,直接市价跟。 我在策略测试器跑了 EURUSD 的 H1 周期,区间 2023-01-01 到 2024-05-23。LightGBM 和 XGBoost 两个模型资金曲线都向下,全部亏损;XGBoost 比 LightGBM 少亏 8 美元,两条曲线几乎重合。外汇与贵金属杠杆交易高风险,回测亏损不代表未来,但说明裸信号直接跟单不可行。 测试器报告看不出模型对「下一根 K 线方向」的预测质量,于是另写脚本按训练方式收集样本外数据并打标签。LightGBM 在 9000 条样本上混淆矩阵为 [[2857,1503],[926,3714]],分类报告给出准确率 0.73,类 0 的 precision/recall 为 0.76/0.66,类 1 为 0.71/0.80。 样本外 73% 准确率意味着模型学到了结构,但 EA 不赚钱——因为盈利还要考虑点差、滑点、仓位与出场。这反而是一个干净起点:你可以在 MT5 里把下面这段代码的特征向量和信号分支接上自己的风控层。

MQL5 / C++
class="type">int OnInit()
  {
   if (!lgbm.Init(lightgbm_onnx))
     class="kw">return INIT_FAILED;
  }
class="type">void OnTick()
  {
   class="type">int size = CopyRates(Symbol(), PERIOD_CURRENT, class="num">1, class="num">1, rates_x); class=class="str">"cmt">//We copy only one recent-closed bar

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

   if (NewBar())
    {
      vector x = {
        rates_x[class="num">0].open,
        rates_x[class="num">0].high,
        rates_x[class="num">0].low,
        rates_x[class="num">0].close,
        rates_x[class="num">0].close-rates_x[class="num">0].open,
        rates_x[class="num">0].high-rates_x[class="num">0].low,
        rates_x[class="num">0].close-rates_x[class="num">0].low,
        rates_x[class="num">0].close-rates_x[class="num">0].high
      };

      class="type">long signal = lgbm.predict_bin(x);

      Comment("Signal: ",signal);
}
   if (NewBar()) class=class="str">"cmt">//Trade at the opening of a new candle
    {
      vector x = {
        rates_x[class="num">0].open,
        rates_x[class="num">0].high,
        rates_x[class="num">0].low,
        rates_x[class="num">0].close,
        rates_x[class="num">0].close-rates_x[class="num">0].open,
        rates_x[class="num">0].high-rates_x[class="num">0].low,
        rates_x[class="num">0].close-rates_x[class="num">0].low,
        rates_x[class="num">0].close-rates_x[class="num">0].high
      };


      class="type">long signal = lgbm.predict_bin(x);

      Comment("Signal: ",signal);

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

      class="type">MqlTick ticks;
      SymbolInfoTick(Symbol(), ticks);

      if (signal==class="num">1) class=class="str">"cmt">//if the signal is bullish
       {

◍ 用样本外数据校验模型信号

这段逻辑把 LightGBM 的 ONNX 模型接进 MT5,靠历史 K 线做一轮脱离训练集的回测验证。信号判定很直接:若账户里没有对应方向的持仓,就按多空信号补一单,止损止盈用 Point 倍数换算,外汇与贵金属杠杆品种下这种写法爆仓风险偏高,参数需自行压低 lotsize。 OnStart 里先跳过前 1000 根bar,向后取 9000 根做特征,再偏移一根取同向 9000 根当次根收盘价标签,实际跑下来若 predictions 与 actual 偏差大,说明模型在该品种过拟合倾向明显。 特征向量只塞了 OHLC 及四个派生价差,没带成交量与跨周期量,读者可改 x 的组成看 classification_report 的精确率是否跳动。复制下面代码到 MT5 脚本,把 lightgbm_onnx 路径换掉即可跑通样本外报告。

MQL5 / C++
    if (!PosExists(POSITION_TYPE_BUY)) class=class="str">"cmt">//There are no buy positions
        m_trade.Buy(lotsize, Symbol(), ticks.ask, ticks.bid-stoploss*Point(), ticks.ask+takeprofit*Point()); class=class="str">"cmt">//Open a buy trade
     }
    else class=class="str">"cmt">//Bearish signal
      {
        if (!PosExists(POSITION_TYPE_SELL)) class=class="str">"cmt">//There are no Sell positions
          m_trade.Sell(lotsize, Symbol(), ticks.bid, ticks.ask+stoploss*Point(), ticks.bid-takeprofit*Point()); class=class="str">"cmt">//open a sell trade
      }
  }
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   if (!lgb.Init(lightgbm_onnx))
    class="kw">return;

class=class="str">"cmt">//--- custom out-of-sample testing 

   class="type">int bars = class="num">9000;
   class="type">int start = class="num">1000;

   class="type">MqlRates rates_x[];
   ArraySetAsSeries(rates_x, true);
   class="type">int size = CopyRates(Symbol(), PERIOD_CURRENT, start, bars, rates_x); class=class="str">"cmt">//We start at the bar class="num">1000 and collect class="num">9000 candles backward

   class="type">MqlRates rates_y[];
   ArraySetAsSeries(rates_y, true);
   CopyRates(Symbol(), PERIOD_CURRENT, start-class="num">1, bars, rates_y); class=class="str">"cmt">//We do the same thing here but we only collect one bar forward making sure we get the prediction for the next candle

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

   vector actual(size), predictions(size);
   for (class="type">int i=class="num">0; i<size; i++)
     {
       vector x = {
         rates_x[i].open,
         rates_x[i].high,
         rates_x[i].low,
         rates_x[i].close,
         rates_x[i].close-rates_x[i].open,
         rates_x[i].high-rates_x[i].low,
         rates_x[i].close-rates_x[i].low,
         rates_x[i].close-rates_x[i].high
       };

       actual[i] = rates_y[i].close > rates_x[i].open ? class="num">1 : class="num">0; class=class="str">"cmt">//making the target variable
       predictions[i] = (class="type">class="kw">double)lgb.predict_bin(x);
     }

   Metrics::classification_report(actual, predictions);
  }

GBDT 凭什么在交易模型里更耐打

梯度提升决策树(GBDT)在预测准确性上通常压过不少传统机器学习算法,核心在于它把决策树的分裂能力与梯度提升的迭代纠错叠在一起:后一棵树专门盯前一棵树的残差补刀,模型性能随树增加而逐步抬升。 对过拟合的鲁棒性来自提升机制本身——新树只去修已有集成的错,而不是重新学全量分布;再叠学习率衰减、正则化与早停,过拟合概率会进一步压低。外汇与贵金属行情噪声大、高风险,这种鲁棒性直接决定回测漂亮和实盘崩盘之间的距离。 它几乎不吃预处理:特征缩放、缺失值填补、分类变量独热编码这些线性回归或 SVM 的标配动作,GBDT 大多可以跳过。实盘里你接 MT5 导出的 tick 或 M1 特征矩阵,脏数据直接进模型通常也能跑。 超参数给了充分定制空间:树的数量、学习率、最大深度、节点最小分裂样本数都能按品种调。比如 max_depth 设 3~6 可逼模型学局部价格结构,设过深则容易把随机波动当规律。 复杂非线性关系它吃得下,能捞到简单线性模型漏掉的拐点和波动率突变。XGBoost、LightGBM、CatBoost 还做了并行与分布式优化,大样本集训练耗时可控。 当然缺点也得认:不是劝退,而是别把它当黑箱圣经。下一篇我们拆 GBDT 在 MT5 特征工程上的具体坑。

「GBDT 实盘前的四道坎」

梯度提升决策树(GBDT)在 MT5 用 Python 或外部模型做信号时,第一个绕不开的是算力。树的数量和深度同时拉大,训练内存占用会随样本量近似线性增长;在深度行情数据集(比如 1 分钟级三年 Tick 聚合)上,单轮训练吃掉十几 GB 内存并不罕见。 超参数不是开箱即用的。树的数量、学习率、最大深度、叶子最小样本数这四个旋钮,任意动一个都会改变过拟合边界。默认参数能跑出结果,但离「可用信号」通常差两三轮网格搜索,而这本身就很耗机器时间。 它对噪声的放大值得警惕。每棵树都在修正前一棵的残差,原始特征里的异常跳空或错价如果被当作「误差模式」学进去,会在提升链里被逐层放大,外汇和贵金属这种高波动品种尤其明显,杠杆下风险被进一步放大。 最后是数据量门槛。小样本(比如不足五千根 K 线)下,boosting 很难学到稳定结构,容易把随机波动当规律。想拿它做 XAUUSD 的日内分类,至少准备数万样本再谈回测。

◍ 最后一句大实话

梯度提升树在外汇建模里已经把 GRU、RNN、LSTM 这些重型循环网络挤下了神坛,靠的是简单结构和实盘预测力,MT5 里直接挂 LightGBM EA.mq5 或 XGBoost EA.mq5 就能跑欧元小时级样本。附件里的 EURUSD.PERIOD_H1.csv 和两份 onnx 模型是现成入口,复制进对应文件夹即可回测。 外汇和贵金属杠杆高、滑点狠,模型胜率只是概率倾斜,别把回测曲线当保本凭证;真要上实盘,先拿脚本 LightGBM Performance TestScript.mq5 测完延迟再谈仓位。

常见问题

按时间顺序切,验证集必须严格在训练集之后,不能用随机打乱。落地时用滚动窗口重训更稳。
0.69说明二类区分力中等,单独用风险高。建议做信号过滤或组合其他特征,先上模拟跑两周看衰减。
可以。小布能加载你导出的ONNX管线,在对应品种页按实时接口跑推理并标出多空倾向,你只看结论。
特征顺序必须和训练时完全一致,缺失值不能留空要填训练用的固定值,否则推理会静默错判。
回测用批量接口验历史胜率,实盘用单条实时接口省资源。两者模型权重必须同源,避免前后不一致。