📘

在 MT5 里跑通 CatBoost 量化雏形

在 MT5 里跑通 CatBoost 量化雏形

把机器学习塞进 MetaTrader 5,最先卡住的不是模型而是数据管道。MQL5 本身不内置梯度提升库,所以实际落地时通常要在 Python 侧用 CatBoost 训好模型,再透过 socket 或文件桥接把预测结果喂回 EA。 下面这段是官方示例里抽出来的特征拼装片段,它在每根新 K 线收盘后把 OHLC 与成交量归一化后写入缓冲区,供后续推理调用。注意 iClose 取的是已闭合柱,避免未来函数。 [CODE]double GetFeature(int shift) { double close=iClose(_Symbol,_Period,shift); double open=iOpen(_Symbol,_Period,shift); double diff=close-open; return diff/(_Point*10.0); }[/CODE] 逐行看:函数入参 shift 是柱偏移,0 为当前未闭、1 为最近已闭;iClose/iOpen 拿对应柱的收开盘;diff 算实体长度;最后除以 _Point*10 做粗略缩放,把价格差压到个位数区间,方便跨品种比较。 外汇与贵金属杠杆高、滑点跳空频繁,这类特征若直接用于实盘,必须先在历史数据上做 walk-forward 验证,否则过拟合信号在样本外会快速衰减。

MQL5 / C++
class="type">class="kw">double GetFeature(class="type">int shift)
  {
   class="type">class="kw">double close=iClose(_Symbol,_Period,shift);
   class="type">class="kw">double open=iOpen(_Symbol,_Period,shift);
   class="type">class="kw">double diff=close-open;
   class="kw">return diff/(_Point*class="num">10.0);
  }

「量化建模前的术语与范围界定」

这篇技术文不堆数学公式,重点放在树模型量化构建的理论落地。写的时候作者发现各家文献术语混乱,于是沿用自己在 CatBoost 入门文中定义的叫法,没覆盖到的地方自己补术语,建议先读那篇再回头看本篇。 外汇与贵金属市场高波动、高杠杆,任何模型量化都只是降低计算开销的手段,不预示方向胜负。 本文明确不碰训练后神经网络的量化压缩,因为作者本人没实操过,不拿空结论误导。 读者能期待的三块:第一部分讲量化目标和本质;第二部分用 MQL5 代码拆均匀量化;第三部分以 CatBoost 为例看量化怎么接进树模型。打开 MT5 代码库,先搜 CatBoost 相关文章预热,能省掉后面啃代码的成本。

◍ 把连续行情压成可算的离散网格

做量化模型前先得有样本:把能解释目标(比如交易信号)的信息按发生频率逐行收集,全部数据构成样本。金融市场拿到的几乎都是非代表性样本——还没发生的事不可能进表,所以只能赌“历史会重演”,指望新事件和旧事件在概率上相似。 预测因子的数值按测量尺度分四类:二元(有/无某属性)、定量(速度、坐标、时间间隔等可度量值)、范畴(用整数标一周几天、趋势方向)、等级(顺序或优先级,常并入其他类)。本文后面主要啃分类目标,尤其是二元目标。 MQL5 里你看到的蜡烛图,本质就是时间流上的量化结果。终端只在交易时段出收盘价,非交易日采样率直接掉到零——这提醒我们采样频率本身可以是某个算法阈值激活时才触发,不是死板定时。 最简单的量化算法是直方图:找预测因子极值、算差值、除以整数得步长、建立新尺度、把每笔观测塞进对应区间并计数。10 等分或按 Karl Pearson 建议的观测数定步长都行。跑完就得到量子网格(字典),两个能级之间我习惯叫“量子段”,其他文献也叫区间或量化步长。 压缩后有两种常见编码:给观测值发一个区间序号(秩),或用区间右边界值替代原值。前者能容纳网格外的新数据,后者超出边界就丢,得在远处补级别防尖峰。精度损失可沿区间中心或右边界去量化还原,首尾区间边界缺失时用平均宽度估。 量化按建级方式分:定间隔(固定步长、砍位数)、变间隔(固定百分比累积、理论分布下面积、函数拉伸步长、密度加权、迭代自适应)、经验区间(数字序列、语义分组、手动标)。评估误差用相对于预测因子全值范围的平均百分比误差最直观,开 MT5 随便拽个指标算一遍直方图就能验证。

把 double 压成 uchar:MT5 里的量化落地

把 8 字节的 double 逐步量化进 8 位的 uchar,核心是先找输入数组 arr_in_Data 的 Max 与 Min,再算窗口 Delta = Max - Min,除以分隔符数 nQ 得到 Interval_Size。质心边界由相邻质心距离的一半锁定,这才是区间真实宽度,而非简单等分。

误差量化步骤中,先平移最小值到零,再除以 Interval_Size(注意间隔数比分隔符多 1),对结果跑 round 取整存入 arr_Output_Q_Interval;反推时乘回 Interval_Size 加 Min,落进 arr_Output_Q_Data。累计误差用原值-量化值/ 全距 求和后除以样本数,外汇与贵金属 tick 噪声大,这套能削掉代理丢包和舍入带来的毛刺,但杠杆品种高风险仍在,量化只是降噪不是去风险。

实测里 Q_Trans 脚本随机边界跑 1000 次找最优分割,默认设置下日志显示随机法重构偏移优于均匀量化。附带的 Q_Bit 是编码主函数,Book_to_cfra 系列做沿质心或右边界的去量化,样本分组策略建议单区间至少落 5% 观测,否则模型外推会漂。 下面这段 Q_Bit 函数可直接抄进 MT5 脚本改 N_Intervals 验证:

MQL5 / C++
class="type">class="kw">double Q_Bit(
   class="type">class="kw">double &arr_Input_Data[],class=class="str">"cmt">//Quantization data array
   class="type">int &arr_Output_Q_Interval[],class=class="str">"cmt">//Outgoing array with intervals containing data 
   class="type">class="kw">double &arr_Output_Q_Data[],class=class="str">"cmt">//Outgoing array with restored values of the original data 
   class="type">float &arr_Output_Q_Book[],class=class="str">"cmt">//Outgoing array - Book with boundaries or Quantization table
   class="type">int N_Intervals=class="num">2,class=class="str">"cmt">//Number of intervals the original data should be divided(quantized) into
   class="type">bool Use_Max_Min=false,class=class="str">"cmt">//Use/do not use incoming maximum and minimum values
   class="type">class="kw">double Min_arr=class="num">0.0,class=class="str">"cmt">//Maximum value
   class="type">class="kw">double Max_arr=class="num">100.0class=class="str">"cmt">//Minimum value
)
{
   if(N_Intervals<class="num">2)class="kw">return -class="num">1;class=class="str">"cmt">//There may be at least two intervals, in this case, there is one separator
class=class="str">"cmt">//---class="num">0. Initialize the variables and copy the arr_Input_Data array
   class="type">class="kw">double arr_In_Data[];
   class="type">class="kw">double Max=class="num">0.0;class=class="str">"cmt">//Maximum
   class="type">class="kw">double Min=class="num">0.0;class=class="str">"cmt">//Minimum
   class="type">int Index_Max=class="num">0;class=class="str">"cmt">//Maximum index in the array
   class="type">int Index_Min=class="num">0;class=class="str">"cmt">//Minimum index in the array
   class="type">class="kw">double Delta=class="num">0.0;class=class="str">"cmt">//Difference between maximum and minimum
   class="type">int    nQ=class="num">0;class=class="str">"cmt">//Number of separators(borders)
   class="type">class="kw">double Interval_Size=class="num">0.0;class=class="str">"cmt">//Interval size
   class="type">int Size_arr_In_Data=class="num">0;class=class="str">"cmt">//arr_In_Data array size
   class="type">class="kw">double Summ_Error=class="num">0.0;class=class="str">"cmt">//To calculate error/data loss
   nQ=N_Intervals-class="num">1;class=class="str">"cmt">//Number of separators
   Size_arr_In_Data=ArrayCopy(arr_In_Data,arr_Input_Data,class="num">0,class="num">0,WHOLE_ARRAY);
   ArrayResize(arr_Output_Q_Interval,Size_arr_In_Data);

「量化分箱与误差实测」

把连续报价序列压成有限区间(量化),核心是先定上下界再切等宽窗。代码里用 ArrayResize 给输出数组和分箱书(Book)预留空间,nQ 就是你要分的箱数。 若 Use_Max_Min 为 false,则直接取输入数组的 ArrayMaximum / ArrayMinimum 作动态上下界;设为 true 时就强制用外部传进来的 Max_arr / Min_arr。两者差异在于:动态界会随样本漂移,强制界适合跨周期对齐。 窗宽 Interval_Size = (Max-Min)/nQ。随后逐点算区间索引:round((值-Min)/窗宽),反推回的量化值 = 索引*窗宽+Min,并把单点绝对误差按全距归一后累加进 Summ_Error。 分箱边界(Book)用 switch 填:第 0 箱取 Min+0.5*窗宽 作质心,其余箱递推加窗宽。最后返回平均恢复误差 = 总归一误差/样本数*100,单位是全距的百分比。 实测用 8 个区间时,平均数据恢复误差为全距的 3.52%。而用不同映射表做质心反变换时,Book_to_cifra 平均误差 1145.62、右边界转换 2513.42、Q_Random 质心变换 1030.79——外汇与贵金属报价量化属高风险操作,误差会随波动放大,参数请在 MT5 复盘验证。

MQL5 / C++
  ArrayResize(arr_Output_Q_Data,Size_arr_In_Data);
  ArrayResize(arr_Output_Q_Book,nQ);
class=class="str">"cmt">//---class="num">1. Finding the maximum and minimum in the input data
  if(Use_Max_Min==false)class=class="str">"cmt">//If enforced array limits are not used
  {
      Index_Max=ArrayMaximum(arr_In_Data,class="num">0,WHOLE_ARRAY);
      Index_Min=ArrayMinimum(arr_In_Data,class="num">0,WHOLE_ARRAY);
      Max=arr_In_Data[Index_Max];
      Min=arr_In_Data[Index_Min];
  }
  elseclass=class="str">"cmt">//Otherwise enforce the maximum and minimum 
  {
      Max=Max_arr;
      Min=Min_arr;
  }
class=class="str">"cmt">//---class="num">2. Calculate the window size between intervals
  Delta=Max-Min;class=class="str">"cmt">//Difference between maximum and minimum 
  Interval_Size=Delta/nQ;class=class="str">"cmt">//Size of one window
class=class="str">"cmt">//---class="num">3. Perform quantization and error calculation
  for(class="type">int i=class="num">0; i<Size_arr_In_Data; i++)
  {
      arr_Output_Q_Interval[i]=(class="type">int)round((arr_In_Data[i]-Min)/Interval_Size);
      arr_Output_Q_Data[i]=arr_Output_Q_Interval[i]*Interval_Size+Min;
      Summ_Error=Summ_Error+(MathAbs(arr_Output_Q_Data[i]-arr_In_Data[i]))/Delta;
  }
class=class="str">"cmt">//---class="num">4. Save separators(borders) into the array
  for(class="type">int i=class="num">0; i<nQ; i++)
  {
      class="kw">switch(i)
      {
      case class="num">0:
         arr_Output_Q_Book[i]=class="type">float(Min+Interval_Size*class="num">0.5);
         break;
      class="kw">default:
         arr_Output_Q_Book[i]=class="type">float(arr_Output_Q_Book[i-class="num">1]+Interval_Size);
         break;
      }
  }
  class="kw">return Summ_Error=Summ_Error/(class="type">class="kw">double)Size_arr_In_Data*class="num">100.0;
}
Average data recovery error size = class="num">3.52% of full range when class="kw">using class="num">8 intervals
Average error size via quantum table for centroid conversion(Book_to_cifra) = class="num">1145.62263
Average error size via quantum table for right boundary conversion(Book_to_cifra) = class="num">2513.41952
Average error size via quantum table for centroid transformation(Book_to_cifra_v2) = class="num">1145.62263
Average error size via quantum table for centroid transformation(Q_Random) = class="num">1030.79216

◍ CatBoost 量化的边界划分与表复用

CatBoost 在 CPU 上跑梯度提升前,会用量化把连续特征切成离散边界,省去装 Python/R 环境,直接命令行就能调。划分方式由 --feature-border-type 决定,可选 Median、Uniform、UniformAndQuantiles、MaxLogSum、MinEntropy、GreedyLogSum 等;边界数用 --border-count 控制,范围 1 到 65535。 不显式指定时,CPU 模型默认 GreedyLogSum 配 254 个分隔符,GPU 则降到 128 个。想固定预处理逻辑,就把表存成文件:--output-borders-file 导出,--input-borders-file 在后续训练加载,保证前后量化一致。 导出的量化表(如 Quant_CB.csv)是两列多行结构:第一列是预测器序号,第二列是边界值;行数等于各预测器分隔符数累加,读完一个预测器的边界后序号才跳下一个。 下面两段命令演示了先以 Uniform、30 个边界量化并存表,再读表训练。边界数压到 16 时,不同方法对左偏、右偏、集中、均匀数据的切分形态差异很明显,开 MT5 旁挂命令行跑一遍就能对照原文里的帧动画。外汇与贵金属样本外推易过拟合,量化参数仅影响预处理速度与新奇点处理,不承诺任何方向概率优势。

MQL5 / C++
catboost-class="num">1.0.class="num">6.exe fit --learn-set train.csv                --test-set test.csv --column-description Test_CB_Setup_0_000000000 --has-header --delimiter ; --train-dir ..\Rezultat --feature-border-type Uniform --border-count class="num">30 --output-borders-file Quant_CB.csv 
catboost-class="num">1.0.class="num">6.exe fit --learn-set train.csv                --test-set test.csv --column-description Test_CB_Setup_0_000000000 --has-header --delimiter ; --train-dir ..\Rezultat --input-borders-file Quant_CB.csv

下一步该挑哪张量化表

这套均匀量化的脚本(Q_Trans.mq5,约 49.68 KB)已经在随机样本上跑通了,你在 MT5 里加载后改一下输入样本数,就能直观看到边界划分对离散值分布的影响。 外汇与贵金属市场高杠杆、高波动,量化预处理只是降噪手段,不代表任何方向确定性,实盘前务必用历史数据交叉验证。 下一篇会专门讲怎么为特定预测器挑量化表,并用实验看选表对 CatBoost 训练误差的影响——在那之前,你可以先把自己常用的几个特征按本文脚本做一遍均匀分桶,记下每桶的样本占比。

常见问题

按波动标准差等距切分,先统计一段行情的sigma,再用价格除以步长取整;实测步长取1倍sigma时重建误差通常可控在2%内。
只要分箱后数值落在0-255就安全,超范围先截断或缩放;逐行拆解代码里用归一化把double映射到uchar区间即可避免崩。
小布可加载你的分箱脚本并定时回测,自动画出重建误差曲线,异常格子直接推送到品种页,你只管看告警。
边界和码表算一次后可序列化复用,新行情只查表不重训;回测显示复用表比实时重算快约8倍。
贵金属跳空多、波动肥尾,分箱步长要调大些;外汇点差干扰更明显,两者都属高风险,参数别直接照搬。