利用CatBoost机器学习模型作为趋势跟踪策略的过滤器·进阶篇
「买单执行函数的落地写法」
在 MT5 的 EA 逻辑里,市价买单的触发不宜直接丢给裸价,先取 SYMBOL_ASK 再 NormalizeDouble 到当前品种小数位,能避开报价精度不一致导致的无效下单。 下面这段函数把‘取价—规整—发送—记录订单号’四步压进一个 void,实战中复制即用。注意 trade.Buy 的第三位传了规整后的 ask,buypos 存的是 ResultOrder 返回的订单号,后续平仓或改仓直接挂这个变量。 外汇与贵金属杠杆高,市价单滑点可能吃掉你预设的止损空间,跑之前先在策略测试器用 2023 年 XAUUSD 的 M1 数据验一遍成交逻辑。
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目录下。外汇与贵金属杠杆高、滑点跳空频繁,这套采集逻辑在实盘复采时可能出现分布偏移,样本仅供参考。
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 即可,但回测和实盘表现可能倾向不同。
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,改两行指标句柄就能跑出你自己的快照表。
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 跑一夜验证。
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!"); } }