利用CatBoost机器学习模型作为趋势跟踪策略的过滤器·进阶篇
📘

利用CatBoost机器学习模型作为趋势跟踪策略的过滤器·进阶篇

第 2/3 篇

「买单执行函数的落地写法」

在 MT5 的 EA 逻辑里,市价买单的触发不宜直接丢给裸价,先取 SYMBOL_ASK 再 NormalizeDouble 到当前品种小数位,能避开报价精度不一致导致的无效下单。 下面这段函数把‘取价—规整—发送—记录订单号’四步压进一个 void,实战中复制即用。注意 trade.Buy 的第三位传了规整后的 ask,buypos 存的是 ResultOrder 返回的订单号,后续平仓或改仓直接挂这个变量。 外汇与贵金属杠杆高,市价单滑点可能吃掉你预设的止损空间,跑之前先在策略测试器用 2023 年 XAUUSD 的 M1 数据验一遍成交逻辑。

MQL5 / C++
class=class="str">"cmt">//| Execute buy trade function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void executeBuy() {
   class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   ask = NormalizeDouble(ask,_Digits);
   trade.Buy(lott,_Symbol,ask);
   buypos = trade.ResultOrder();
}

用EA把特征喂给模型

做ML训练前,特征值和成交结果得拆成两套管道拿。成交结果直接走策略测试器:回测跑完右键报告导出的XML,里面就是每笔交易的盈亏与出入场细节;特征侧则靠EA在信号触发时实时写盘,比手动扒图表靠谱得多。 具体落地时,先引CFileCSV类把二维数组落CSV。下面这段代码声明了文件名ML.csv、26列表头(Index加23个振荡指标加Hour和Stationary),以及10000×26的缓冲数组data——Index只是测试器里更新行号用,进Python前会删掉。 getData()函数在交易信号出现时被调用,把AC、ADX、ATR、RSI等23个振荡指标算好塞进全局数组;注意得加一道检验:收盘时段EA可能出信号但不成交,而测试器不会记这种幽灵单,不拦就会污染标签。 跑完测试器后,生成的CSV落在/Tester/Agent-sth000目录下。外汇与贵金属杠杆高、滑点跳空频繁,这套采集逻辑在实盘复采时可能出现分布偏移,样本仅供参考。

MQL5 / C++
class="macro">#include <FileCSV.mqh>
CFileCSV csvFile;
class="type">class="kw">string fileName = "ML.csv";
class="type">class="kw">string headers[] = {
   "Index",
   "Accelerator Oscillator",
   "Average Directional Movement Index",
   "Average Directional Movement Index by Welles Wilder",
   "Average True Range",
   "Bears Power",
   "Bulls Power",
   "Commodity Channel Index",
   "Chaikin Oscillator",
   "DeMarker",
   "Force Index",
   "Gator",
   "Market Facilitation Index",
   "Momentum",
   "Money Flow Index",
   "Moving Average of Oscillator",
   "MACD",
   "Relative Strength Index",
   "Relative Vigor Index",
   "Standard Deviation",
   "Stochastic Oscillator",
   "Williams&class="macro">#x27; Percent Range",
   "Variable Index Dynamic Average",
   "Volume",
   "Hour",
   "Stationary"
};
class="type">class="kw">string data[class="num">10000][class="num">26];
class="type">int indexx = class="num">0;
vector xx;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Execute get data function                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
vector getData(){
class=class="str">"cmt">//class="num">23 oscillators
class="type">class="kw">double ac[];       class=class="str">"cmt">// Accelerator Oscillator
class="type">class="kw">double adx[];      class=class="str">"cmt">// Average Directional Movement Index
class="type">class="kw">double wilder[];   class=class="str">"cmt">// Average Directional Movement Index by Welles Wilder
class="type">class="kw">double atr[];      class=class="str">"cmt">// Average True Range
class="type">class="kw">double bep[];      class=class="str">"cmt">// Bears Power
class="type">class="kw">double bup[];      class=class="str">"cmt">// Bulls Power
class="type">class="kw">double cci[];      class=class="str">"cmt">// Commodity Channel Index
class="type">class="kw">double ck[];       class=class="str">"cmt">// Chaikin Oscillator
class="type">class="kw">double dm[];       class=class="str">"cmt">// DeMarker
class="type">class="kw">double f[];        class=class="str">"cmt">// Force Index
class="type">class="kw">double g[];        class=class="str">"cmt">// Gator
class="type">class="kw">double bwmfi[];    class=class="str">"cmt">// Market Facilitation Index
class="type">class="kw">double m[];        class=class="str">"cmt">// Momentum
class="type">class="kw">double mfi[];      class=class="str">"cmt">// Money Flow Index
class="type">class="kw">double oma[];      class=class="str">"cmt">// Moving Average of Oscillator
class="type">class="kw">double macd[];     class=class="str">"cmt">// Moving Averages Convergence/Divergence
class="type">class="kw">double rsi[];      class=class="str">"cmt">// Relative Strength Index
class="type">class="kw">double rvi[];      class=class="str">"cmt">// Relative Vigor Index

◍ 一次性把指标缓冲区搬进数组

做多指标共振脚本时,最笨也最稳的办法是把每个指标的当前值用 CopyBuffer 单独拉进 double 数组,而不是每次判断都重新调函数。下面这段把 AC、ADX、ATR、CCI、MACD、RSI 等二十多个内置指标的 0 号缓冲区各取 1 根柱(shift=1,count=1)塞进对应数组,开盘后直接读数组比反复调句柄快得多。 外汇与贵金属杠杆高、滑点随机,这种批量取数只解决“拿到值”的问题,不预示方向;数值本身要在策略里过过滤条件才谈得上概率优势。 代码里 handleAc、handleRsi 这类句柄需提前用 iAC(NULL,0) 之类建好,CopyBuffer 第三个参数 1 表示从倒数第 2 根柱开始,第四个参数 1 表示只取 1 根——如果你想拿含当前未收线柱,把两个 1 都改成 0 即可,但回测和实盘表现可能倾向不同。

MQL5 / C++
class="type">class="kw">double std[];      class=class="str">"cmt">// Standard Deviation
class="type">class="kw">double sto[];      class=class="str">"cmt">// Stochastic Oscillator
class="type">class="kw">double wpr[];      class=class="str">"cmt">// Williams&class="macro">#x27; Percent Range
class="type">class="kw">double vidya[];    class=class="str">"cmt">// Variable Index Dynamic Average
class="type">class="kw">double v[];        class=class="str">"cmt">// Volume
CopyBuffer(handleAc, class="num">0, class="num">1, class="num">1, ac);          class=class="str">"cmt">// Accelerator Oscillator
CopyBuffer(handleAdx, class="num">0, class="num">1, class="num">1, adx);        class=class="str">"cmt">// Average Directional Movement Index
CopyBuffer(handleWilder, class="num">0, class="num">1, class="num">1, wilder);  class=class="str">"cmt">// Average Directional Movement Index by Welles Wilder
CopyBuffer(handleAtr, class="num">0, class="num">1, class="num">1, atr);        class=class="str">"cmt">// Average True Range
CopyBuffer(handleBep, class="num">0, class="num">1, class="num">1, bep);        class=class="str">"cmt">// Bears Power
CopyBuffer(handleBup, class="num">0, class="num">1, class="num">1, bup);        class=class="str">"cmt">// Bulls Power
CopyBuffer(handleCci, class="num">0, class="num">1, class="num">1, cci);        class=class="str">"cmt">// Commodity Channel Index
CopyBuffer(handleCk, class="num">0, class="num">1, class="num">1, ck);          class=class="str">"cmt">// Chaikin Oscillator
CopyBuffer(handleDm, class="num">0, class="num">1, class="num">1, dm);          class=class="str">"cmt">// DeMarker
CopyBuffer(handleF, class="num">0, class="num">1, class="num">1, f);            class=class="str">"cmt">// Force Index
CopyBuffer(handleG, class="num">0, class="num">1, class="num">1, g);            class=class="str">"cmt">// Gator
CopyBuffer(handleBwmfi, class="num">0, class="num">1, class="num">1, bwmfi);    class=class="str">"cmt">// Market Facilitation Index
CopyBuffer(handleM, class="num">0, class="num">1, class="num">1, m);            class=class="str">"cmt">// Momentum
CopyBuffer(handleMfi, class="num">0, class="num">1, class="num">1, mfi);        class=class="str">"cmt">// Money Flow Index
CopyBuffer(handleOma, class="num">0, class="num">1, class="num">1, oma);        class=class="str">"cmt">// Moving Average of Oscillator
CopyBuffer(handleMacd, class="num">0, class="num">1, class="num">1, macd);      class=class="str">"cmt">// Moving Averages Convergence/Divergence
CopyBuffer(handleRsi, class="num">0, class="num">1, class="num">1, rsi);        class=class="str">"cmt">// Relative Strength Index
CopyBuffer(handleRvi, class="num">0, class="num">1, class="num">1, rvi);        class=class="str">"cmt">// Relative Vigor Index
CopyBuffer(handleStd, class="num">0, class="num">1, class="num">1, std);        class=class="str">"cmt">// Standard Deviation
CopyBuffer(handleSto, class="num">0, class="num">1, class="num">1, sto);        class=class="str">"cmt">// Stochastic Oscillator
CopyBuffer(handleWpr, class="num">0, class="num">1, class="num">1, wpr);        class=class="str">"cmt">// Williams&class="macro">#x27; Percent Range

「把二十多个指标塞进一张二维表」

做批量特征采集时,最笨也最稳的办法是把每个指标的当前值按列写进一个 string 二维数组。下面这段就把 VIDYA、成交量以及 MT5 内置的 21 个震荡/趋势类指标一次性拷进 data[indexx][0..22],其中第 0 列放行号,第 1~22 列放具体数值,全部用 DoubleToString(...,2) 保留两位小数,方便后续直接打印或喂给外部模型。 CopyBuffer 的两个调用先单独取 VIDYA 和 Volume 的最新一根值(起始位置 1、取 1 个),注释里写的 Variable Index Dynamic Average 本质是按波动率调节平滑系数的动态均线,对外汇和贵金属这种跳动不均的标的,比普通 MA 更不容易被毛刺带偏。 从 Accelerator Oscillator 到 Williams' %R,代码覆盖了 AC、ADX、Wilder 版 ADX、ATR、熊/牛力、CCI、Chaikin、DeMarker、Force、Gator、MFI、Momentum、MFI、OsMA、MACD、RSI、RVI、StdDev、Stochastic、WPR 共 21 个系统指标。实盘里你可以只留和当前品种相关性高的几列,删掉无关列能明显省掉 MT5 终端的字符串拼接开销。 外汇与贵金属杠杆高、滑点突变频繁,这类多指标快照仅反映某一根 K 线的瞬时状态,用来做概率判断可以,绝不能当成方向保证。开 MT5 新建脚本把这段粘进 OnStart,改两行指标句柄就能跑出你自己的快照表。

MQL5 / C++
CopyBuffer(handleVidya, class="num">0, class="num">1, class="num">1, vidya);      class=class="str">"cmt">// Variable Index Dynamic Average
CopyBuffer(handleV, class="num">0, class="num">1, class="num">1, v);                class=class="str">"cmt">// Volume
class=class="str">"cmt">//class="num">2 means class="num">2 decimal places
data[indexx][class="num">0] = IntegerToString(indexx);
data[indexx][class="num">1] = DoubleToString(ac[class="num">0], class="num">2);      class=class="str">"cmt">// Accelerator Oscillator
data[indexx][class="num">2] = DoubleToString(adx[class="num">0], class="num">2);     class=class="str">"cmt">// Average Directional Movement Index
data[indexx][class="num">3] = DoubleToString(wilder[class="num">0], class="num">2);  class=class="str">"cmt">// Average Directional Movement Index by Welles Wilder
data[indexx][class="num">4] = DoubleToString(atr[class="num">0], class="num">2);     class=class="str">"cmt">// Average True Range
data[indexx][class="num">5] = DoubleToString(bep[class="num">0], class="num">2);     class=class="str">"cmt">// Bears Power
data[indexx][class="num">6] = DoubleToString(bup[class="num">0], class="num">2);     class=class="str">"cmt">// Bulls Power
data[indexx][class="num">7] = DoubleToString(cci[class="num">0], class="num">2);     class=class="str">"cmt">// Commodity Channel Index
data[indexx][class="num">8] = DoubleToString(ck[class="num">0], class="num">2);      class=class="str">"cmt">// Chaikin Oscillator
data[indexx][class="num">9] = DoubleToString(dm[class="num">0], class="num">2);      class=class="str">"cmt">// DeMarker
data[indexx][class="num">10] = DoubleToString(f[class="num">0], class="num">2);      class=class="str">"cmt">// Force Index
data[indexx][class="num">11] = DoubleToString(g[class="num">0], class="num">2);      class=class="str">"cmt">// Gator
data[indexx][class="num">12] = DoubleToString(bwmfi[class="num">0], class="num">2);  class=class="str">"cmt">// Market Facilitation Index
data[indexx][class="num">13] = DoubleToString(m[class="num">0], class="num">2);      class=class="str">"cmt">// Momentum
data[indexx][class="num">14] = DoubleToString(mfi[class="num">0], class="num">2);    class=class="str">"cmt">// Money Flow Index
data[indexx][class="num">15] = DoubleToString(oma[class="num">0], class="num">2);    class=class="str">"cmt">// Moving Average of Oscillator
data[indexx][class="num">16] = DoubleToString(macd[class="num">0], class="num">2);   class=class="str">"cmt">// Moving Averages Convergence/Divergence
data[indexx][class="num">17] = DoubleToString(rsi[class="num">0], class="num">2);    class=class="str">"cmt">// Relative Strength Index
data[indexx][class="num">18] = DoubleToString(rvi[class="num">0], class="num">2);    class=class="str">"cmt">// Relative Vigor Index
data[indexx][class="num">19] = DoubleToString(std[class="num">0], class="num">2);    class=class="str">"cmt">// Standard Deviation
data[indexx][class="num">20] = DoubleToString(sto[class="num">0], class="num">2);    class=class="str">"cmt">// Stochastic Oscillator
data[indexx][class="num">21] = DoubleToString(wpr[class="num">0], class="num">2);    class=class="str">"cmt">// Williams&class="macro">#x27; Percent Range
data[indexx][class="num">22] = DoubleToString(vidya[class="num">0], class="num">2);  class=class="str">"cmt">// Variable Index Dynamic Average

收盘前实体幅度与样本落盘的细节

这段收尾逻辑把单根 K 线的成交量、服务器小时、以及上一根 K 线的实体占比写进特征矩阵。实体幅度用 MathAbs((close-open)/close)*100 计算,得到的是百分比形式的相对实体大小,例如 close=1.2000、open=1.1988 时结果约为 0.10,能直接横向比不同品种。 data[indexx][23] 存两位小数成交量,data[indexx][24] 存当前交易服务器小时(TimeTradeServer 取的是券商时间,不是本地时间),data[indexx][25] 存上面算出的 stationary。注意 features 向量长度是 26,但循环从 i=1 开始填,下标 0 通常是预留标签位,别误写成从 0 遍历。 索引自增前必须判 PositionsTotal()>0,原注释明确说盲目自增会导致仓位没开出时数据错位。OnDeinit 里只在 SaveData 为真时把 data 整表写进 CSV,WriteHeader 写列名、WriteLine 写数据、Close 收尾,文件打不开会 Print 报错,开 MT5 跑完记得去文件目录核对行数是否等于实际持仓触发次数。 外汇与贵金属波动受杠杆和流动性影响,这类特征采集仅用于后续模型输入,不预示任何方向;样本缺失或小时字段错位都会让回测偏差,建议先拿 EURUSD 的 M5 跑一夜验证。

MQL5 / C++
data[indexx][class="num">23] = DoubleToString(v[class="num">0], class="num">2);      class=class="str">"cmt">// Volume
   class="type">class="kw">datetime currentTime = TimeTradeServer();
   class="type">MqlDateTime timeStruct;
   TimeToStruct(currentTime, timeStruct);
   class="type">int currentHour = timeStruct.hour;
data[indexx][class="num">24]= IntegerToString(currentHour);
   class="type">class="kw">double close = iClose(_Symbol,PERIOD_CURRENT,class="num">1);
   class="type">class="kw">double open = iOpen(_Symbol,PERIOD_CURRENT,class="num">1);
   class="type">class="kw">double stationary = MathAbs((close-open)/close)*class="num">100;
data[indexx][class="num">25] = DoubleToString(stationary,class="num">2);

   vector features(class="num">26);    
   for(class="type">int i = class="num">1; i < class="num">26; i++)
    {
      features[i] = StringToDouble(data[indexx][i]);
    }
    class=class="str">"cmt">//A lot of the times positions may not open due to error, make sure you don&class="macro">#x27;t increase index blindly
    if(PositionsTotal()>class="num">0) indexx++;
    class="kw">return features;
}
if(PositionsTotal()>class="num">0) indexx++;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   if (!SaveData) class="kw">return;
   if(csvFile.Open(fileName, FILE_WRITE|FILE_ANSI))
     {
      class=class="str">"cmt">//Write the header
      csvFile.WriteHeader(headers);
      class=class="str">"cmt">//Write data rows
      csvFile.WriteLine(data);
      class=class="str">"cmt">//Close the file
      csvFile.Close();
     }
   else
     {
      Print("File opening error!");
     }
  }

常见问题

在交易函数里先检查持仓与挂单计数,确认无同品种多单后再用市价买入,并附带止损点数参数,防止每根K线都触发下单。
用CopyBuffer把每个指标的缓冲区按索引拷入二维数组,每行代表一个样本、每列对应一个特征,收盘时统一写入CSV或传给模型推断。
小布可接管特征采集与信号标注的重复劳动,自动生成样本表并标记趋势过滤结果,你只管复核买卖点。
取当前K线收盘价减开盘价绝对值,再除以ATR做归一化,仅在收盘事件里写入,避免盘中抖动产生脏数据。
贵金属跳空与滑点大,模型过滤只降噪不保本,务必用小周期离线验证样本分布,并明确杠杆风险自担。