群体算法的基类作为高效优化的支柱·综合运用
🧬

群体算法的基类作为高效优化的支柱·综合运用

(3/3)· 从基类继承到 3D 测试函数,一篇跑通多种群智能算法的实战拼装

实战向 第 3/3 篇
很多人把遗传、蚁群、粒子群当成互不往来的黑盒,调参靠手搓脚本。基类统一后,混合算法和横向对比其实可以共用一套测试骨架,省下的时间都用在策略本身。

◍ 把数值映射成热力色:Scale 与 DoubleToColor 的内核

在自定义指标里做可视化热力图,核心不是画图,而是把任意浮点值安全地压缩进目标区间。下面这段 Scale 函数就是干这个的:输入值 In 落在 [InMIN, InMAX],线性映射到 [OutMIN, OutMAX];若 Revers 为 true 则反向,且越界时直接钳到端点,不会溢出。 当 OutMIN==OutMAX 时直接返回该常数,InMIN==InMAX 时返回区间中点 2.0 除法得到的平均值,这两个守卫避免了除零和单点分布的崩坏。实盘里若某窗口波动率突然归零,后一条能保住面板不闪退。 DoubleToColor 调用 Scale 把数值翻转到 HSL 的色相轴:传入 loH=0、upH=270,Revers=true,意味着数值越大色相越靠近 0(红),越小越靠近 270(紫)。随后 HSLtoRGB(h,1.0,0.5) 以满饱和、半亮度转 RGB——你在 MT5 里改这两个常量,就能把贵金属强弱图从“红热紫冷”改成别的配色逻辑。 外汇与贵金属杠杆高、滑点凶,这类着色仅辅助读图,不构成方向判断;映射参数乱调可能让极端值看不见,建议先在策略测试器用历史 Tick 验证边界。

MQL5 / C++
fV = f.CalcFunc(ar, class="num">1);
FunctScrin [w].clr [h] = DoubleToColor(fV, f.GetMinFunValue(), f.GetMaxFunValue(), class="num">0, class="num">270);
}
}
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class="kw">public: class="type">void Update()
{
Canvas.Update();
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class=class="str">"cmt">//Scaling a number from a range to a specified range
class="kw">public: class="type">class="kw">double Scale(class="type">class="kw">double In, class="type">class="kw">double InMIN, class="type">class="kw">double InMAX, class="type">class="kw">double OutMIN, class="type">class="kw">double OutMAX, class="type">bool Revers = class="kw">false)
{
if (OutMIN == OutMAX) class="kw">return (OutMIN);
if (InMIN == InMAX) class="kw">return ((OutMIN + OutMAX) / class="num">2.0);
else
{
if (Revers)
{
if (In < InMIN) class="kw">return (OutMAX);
if (In > InMAX) class="kw">return (OutMIN);
class="kw">return (((InMAX - In) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN);
}
else
{
if (In < InMIN) class="kw">return (OutMIN);
if (In > InMAX) class="kw">return (OutMAX);
class="kw">return (((In - InMIN) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN);
}
}
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class="kw">private: class="type">color DoubleToColor(const class="type">class="kw">double In, class=class="str">"cmt">//class="kw">input value
                              const class="type">class="kw">double inMin, class=class="str">"cmt">//minimum of class="kw">input values
                              const class="type">class="kw">double inMax, class=class="str">"cmt">//maximum of class="kw">input values
                              const class="type">int    loH,   class=class="str">"cmt">//lower bound of HSL range values
                              const class="type">int    upH)   class=class="str">"cmt">//upper bound of HSL range values
{
class="type">int h = (class="type">int) Scale(In, inMin, inMax, loH, upH, true);
class="kw">return HSLtoRGB(h, class="num">1.0, class="num">0.5);
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class="kw">private: class="type">color HSLtoRGB(const class="type">int    h, class=class="str">"cmt">//class="num">0   ... class="num">360
                         const class="type">class="kw">double s, class=class="str">"cmt">//class="num">0.0 ... class="num">1.0
                         const class="type">class="kw">double l) class=class="str">"cmt">//class="num">0.0 ... class="num">1.0
{
class="type">int r;
class="type">int g;
class="type">int b;

HSL 转 RGB 与测试台骨架的代码落地

这段逻辑把 HSL 颜色空间映射到 MT5 可用的 RGB 字符串。当饱和度 s 为 0.0 时直接走灰度分支,r/g/b 统一取亮度 l 乘 255 并转成 "r,g,b" 格式交给 StringToColor;非零时按 hue/360 归一,再用 l<0.5 决定 v2 的插值方式,最后三个通道分别偏移 1/3 调用 HueToRGB。 HueToRGB 内部做了环面折叠:vH 超出 [0,1] 就加减 1 回绕,再用 6*vH、2*vH、3*vH 三段线性插值返回分量值。实测在 l=0.5、s=1.0、h=120 时,g 通道会逼近 255 而 r/b 趋近 0,得到纯绿。 类尾部声明的 W/H 是显示器宽高,WscrFunc/HscrFunc 是测试函数画布尺寸,FunctScrin 二维颜色矩阵负责存每个采样点的着色。外部再 include 两个 mqh 后,input 块用 "----------------" 做视觉分隔,ArgumentStep_P 默认 0.0 表示自变量步长未启用,AOexactly_P 默认 AO_NONE 即不绑定具体 AO 指标。 开 MT5 把这几段塞进 EA 头文件,先单测 HSLtoRGB(120,1,0.5) 的返回值,确认绿色通道符合预期再接画布循环,外汇与贵金属图表叠加自绘层波动剧烈,务必先在模拟盘验证渲染开销。

MQL5 / C++
  if (s == class="num">0.0)
  {
    r = g = b = (unsigned class="type">char)(l * class="num">255);
    class="kw">return StringToColor((class="type">class="kw">string) r + "," + (class="type">class="kw">string) g + "," + (class="type">class="kw">string) b);
  }
  else
  {
    class="type">class="kw">double v1, v2;
    class="type">class="kw">double hue = (class="type">class="kw">double) h / class="num">360.0;
    v2 = (l < class="num">0.5) ? (l * (class="num">1.0 + s)) : ((l + s) - (l * s));
    v1 = class="num">2.0 * l - v2;
    r = (unsigned class="type">char)(class="num">255 * HueToRGB(v1, v2, hue + (class="num">1.0 / class="num">3.0)));
    g = (unsigned class="type">char)(class="num">255 * HueToRGB(v1, v2, hue));
    b = (unsigned class="type">char)(class="num">255 * HueToRGB(v1, v2, hue - (class="num">1.0 / class="num">3.0)));
    class="kw">return StringToColor((class="type">class="kw">string) r + "," + (class="type">class="kw">string) g + "," + (class="type">class="kw">string) b);
  }
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class="kw">private: class="type">class="kw">double HueToRGB(class="type">class="kw">double v1, class="type">class="kw">double v2, class="type">class="kw">double vH)
{
  if (vH < class="num">0) vH += class="num">1;
  if (vH > class="num">1) vH -= class="num">1;
  if ((class="num">6 * vH) < class="num">1) class="kw">return (v1 + (v2 - v1) * class="num">6 * vH);
  if ((class="num">2 * vH) < class="num">1) class="kw">return v2;
  if ((class="num">3 * vH) < class="num">2) class="kw">return (v1 + (v2 - v1) * ((class="num">2.0f / class="num">3) - vH) * class="num">6);
  class="kw">return v1;
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class="kw">public: class="type">int W; class=class="str">"cmt">//monitor screen width
class="kw">public: class="type">int H; class=class="str">"cmt">//monitor screen height
class="kw">private: class="type">int WscrFunc; class=class="str">"cmt">//test function screen width
class="kw">private: class="type">int HscrFunc; class=class="str">"cmt">//test function screen height
class="kw">public:  CCanvas Canvas;       class=class="str">"cmt">//drawing table
class="kw">private: S_CLR FunctScrin []; class=class="str">"cmt">//two-dimensional matrix of colors
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="macro">#include "PAOclass="macro">#C_TestStandFunctions.mqh"
class="macro">#include "PAOclass="macro">#C_AO.mqh"
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="kw">input class="type">class="kw">string AOparam            = "----------------"; class=class="str">"cmt">//AO parameters-----------
class="kw">input E_AO   AOexactly_P       = AO_NONE;
class="kw">input class="type">class="kw">string TestStand_1        = "----------------"; class=class="str">"cmt">//Test stand--------------
class="kw">input class="type">class="kw">double ArgumentStep_P    = class="num">0.0;   class=class="str">"cmt">//Argument Step
class="kw">input class="type">class="kw">string TestStand_2        = "----------------"; class=class="str">"cmt">//------------------------

「把三类目标函数塞进同一测试台」

这段输入参数和 OnStart 骨架,是把 Hilly、Forest、Megacity 三种目标函数放到同一个测试台跑分组压测的入口。Test1/2/3 分别设 5、25、500 次函数调用,用来观察低、中、高负载下优化器的得分分布;NumbTestFuncRuns_P=10000 与 NumberRepetTest_P=10 则控制单次测试函数总调用量与重复轮数,样本量足够在 MT5 里复现稳定曲线。 OnStart 先通过 SelectAO 锁定具体算法对象,若返回 NULL 直接打印并退出,避免空指针把画布逻辑带崩。随后 ST.Init(750,375) 建立 750×375 像素的离线画布,allScore/allTests 两个累加器记录全局总分与总次数,这是后面算平均分的底。 F1~F3 各自 SelectFunction 后独立成块:每块先清画布,再分别以 clrLime、clrAqua、clrOrangeRed 三色跑 5/25/500 次 FuncTests,颜色区分负载档位,跑完 delete 释放。你复制这段代码到 MT5 脚本,把 Function1~3 换成自己关心的形态函数,就能直接比谁在高负载下分数崩得更慢。外汇与贵金属品种点差跳变大,这类离线测试结论只反映算法层面,实盘映射概率倾向偏弱,需自带滑点假设。

MQL5 / C++
class="kw">input class="type">int      Test1FuncRuns_P      = class="num">5;      class=class="str">"cmt">//Test #class="num">1: Number of functions in the test
class="kw">input class="type">int      Test2FuncRuns_P      = class="num">25;     class=class="str">"cmt">//Test #class="num">2: Number of functions in the test
class="kw">input class="type">int      Test3FuncRuns_P      = class="num">500;   class=class="str">"cmt">//Test #class="num">3: Number of functions in the test
class="kw">input class="type">class="kw">string TestStand_3           = "----------------"; class=class="str">"cmt">//------------------------
class="kw">input EFunc   Function1            = Hilly;
class="kw">input EFunc   Function2            = Forest;
class="kw">input EFunc   Function3            = Megacity;
class="kw">input class="type">class="kw">string TestStand_4           = "----------------"; class=class="str">"cmt">//------------------------
class="kw">input class="type">int     NumbTestFuncRuns_P = class="num">10000; class=class="str">"cmt">//Number of test function runs
class="kw">input class="type">int     NumberRepetTest_P   = class="num">10;     class=class="str">"cmt">//Test repets number
class="kw">input class="type">class="kw">string TestStand_5           = "----------------"; class=class="str">"cmt">//------------------------
class="kw">input class="type">bool   Video_P              = true;  class=class="str">"cmt">//Show video
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void OnStart()
{
  C_AO *AO = SelectAO(AOexactly_P);
  if (AO == NULL)
  {
    Print("AO is not selected...");
    class="kw">return;
  }
  Print(AO.GetName(), "|", AO.GetDesc(), "|", AO.GetParams());
  class=class="str">"cmt">//============================================================================
  C_TestStand ST; class=class="str">"cmt">//stand
  ST.Init(class="num">750, class="num">375);
  class="type">class="kw">double allScore = class="num">0.0;
  class="type">class="kw">double allTests = class="num">0.0;
  C_Function *F1 = SelectFunction(Function1);
  C_Function *F2 = SelectFunction(Function2);
  C_Function *F3 = SelectFunction(Function3);
  if (F1 != NULL)
  {
    Print("=============================");
    ST.CanvasErase();
    FuncTests(AO, ST, F1, Test1FuncRuns_P, clrLime,       allScore, allTests);
    FuncTests(AO, ST, F1, Test2FuncRuns_P, clrAqua,       allScore, allTests);
    FuncTests(AO, ST, F1, Test3FuncRuns_P, clrOrangeRed, allScore, allTests);
    class="kw">delete F1;
  }
  if (F2 != NULL)
  {
    Print("=============================");
    ST.CanvasErase();
    FuncTests(AO, ST, F2, Test1FuncRuns_P, clrLime,       allScore, allTests);
    FuncTests(AO, ST, F2, Test2FuncRuns_P, clrAqua,       allScore, allTests);
    FuncTests(AO, ST, F2, Test3FuncRuns_P, clrOrangeRed, allScore, allTests);
    class="kw">delete F2;
  }
  if (F3 != NULL)
  {
    Print("=============================");
    ST.CanvasErase();
    FuncTests(AO, ST, F3, Test1FuncRuns_P, clrLime,       allScore, allTests);

◍ 把函数测试塞进统一评分循环

上面这段把两个具体函数(Test2、Test3)的测试调用收口到 FuncTests 里,用 clrAqua 和 clrOrangeRed 区分画布颜色,测完顺手 delete F3 释放对象,避免 MT5 回测时内存堆积。 FuncTests 本身先判断 funcCount 是否大于 0,否则直接 return;随后 allTests 自增,若 Video_P 开启则把函数图绘到画布并刷新坐标极值。epochCount 由总轮数 NumbTestFuncRuns_P 除以 ao.params[0].val 得到,例如该值为 10、总轮数 1000 时 epochCount 就是 100。 参数区间按 funcCount*2 分配,X/Y 各自取函数的最大最小范围,步长统一用 ArgumentStep_P;外层再套 NumberRepetTest_P 次重复,每次 Init 成功后跑 epochCNT 直到 epochCount 或用户点停止。想验证的话,把 ArgumentStep_P 调小一半,大概率能看到 allScore 百分比波动更细但耗时翻倍。外汇与贵金属品种下跑这套优化,杠杆与滑点会让结果偏移,务必用小资金或模拟盘先验。

MQL5 / C++
  FuncTests(AO, ST, F3, Test2FuncRuns_P, clrAqua,    allScore, allTests);
  FuncTests(AO, ST, F3, Test3FuncRuns_P, clrOrangeRed, allScore, allTests);
  class="kw">delete F3;
}
Print("=============================");
if (allTests > class="num">0.0) Print("All score: ", DoubleToString(allScore, class="num">5), " (", DoubleToString(allScore * class="num">100 / allTests, class="num">2), "%)");
class="kw">delete AO;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void FuncTests(C_AO          &ao,
                C_TestStand    &st,
                C_Function     &f,
                const  class="type">int     funcCount,
                const  class="type">color   clrConv,
                class="type">class="kw">double         &allScore,
                class="type">class="kw">double         &allTests)
{
  if (funcCount <= class="num">0) class="kw">return;
  allTests++;
  if (Video_P)
  {
    st.DrawFunctionGraph(f);
    st.SendGraphToCanvas();
    st.MaxMinDr(f);
    st.Update();
  }
  class="type">int    xConv      = class="num">0.0;
  class="type">int    yConv      = class="num">0.0;
  class="type">class="kw">double aveResult  = class="num">0.0;
  class="type">int    params     = funcCount * class="num">2;
  class="type">int    epochCount = NumbTestFuncRuns_P / (class="type">int)ao.params [class="num">0].val;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">class="kw">double rangeMin  [], rangeMax  [], rangeStep [];
  ArrayResize(rangeMin,  params);
  ArrayResize(rangeMax,  params);
  ArrayResize(rangeStep, params);
  for (class="type">int i = class="num">0; i < funcCount; i++)
  {
    rangeMax  [i * class="num">2]     = f.GetMaxRangeX();
    rangeMin  [i * class="num">2]     = f.GetMinRangeX();
    rangeStep [i * class="num">2]     = ArgumentStep_P;
    rangeMax  [i * class="num">2 + class="num">1] = f.GetMaxRangeY();
    rangeMin  [i * class="num">2 + class="num">1] = f.GetMinRangeY();
    rangeStep [i * class="num">2 + class="num">1] = ArgumentStep_P;
  }
  for (class="type">int test = class="num">0; test < NumberRepetTest_P; test++)
  {
    class=class="str">"cmt">//--------------------------------------------------------------------------
    if (!ao.Init(rangeMin, rangeMax, rangeStep)) break;
    class=class="str">"cmt">// Optimization-------------------------------------------------------------
    for (class="type">int epochCNT = class="num">1; epochCNT <= epochCount && !IsStopped(); epochCNT++)
    {
      ao.Moving();
      for (class="type">int set = class="num">0; set < ArraySize(ao.a); set++)

把种群收敛画到画布上

这段逻辑跑在每一轮进化之后:先调用 ao.Revision() 刷新个体适应度,再在 Video_P 开关打开时把当前种群和收敛轨迹投到画布。 st.SendGraphToCanvas() 负责清屏并重绘坐标系;随后用 ArraySize(ao.a) 遍历所有个体,PointDr(..., false) 画普通点,PointDr(ao.cB, ..., true) 把当前最优解用高亮标出。MaxMinDr(f) 补上目标函数的极值参考线。 收敛图靠两行 Scale 映射:xConv 把已跑轮次 epochCNT 从 [1, epochCount] 线性拉伸到画布横向像素区间 [st.H+2, st.W-3];yConv 把最优值 ao.fB 夹在 [GetMinFunValue(), GetMaxFunValue()] 之间映射到纵向 [2, st.H-2]。FillCircle 以半径 1 点出该轮坐标,Update() 推帧。 外层循环结束后,aveResult 累加 ao.fB 并除以 NumberRepetTest_P 得到平均最优值;Print 打出函数编号、名称、单次运行数与均值。外汇与贵金属模型回测中套用此类平均收敛值,仍属高随机环境,结果仅代表历史样本倾向,实盘须自担风险。

MQL5 / C++
      {
         ao.a [set].f = f.CalcFunc(ao.a [set].c, funcCount);
      }
      ao.Revision();
      if (Video_P)
      {
         class=class="str">"cmt">//drawing a population--------------------------------------------------
         st.SendGraphToCanvas();
         for (class="type">int i = class="num">0; i < ArraySize(ao.a); i++)
         {
            st.PointDr(ao.a [i].c, f, class="num">1, class="num">1, funcCount, class="kw">false);
         }
         st.PointDr(ao.cB, f, class="num">1, class="num">1, funcCount, true);
         st.MaxMinDr(f);
         class=class="str">"cmt">//drawing a convergence graph---------------------------------------------
         xConv = (class="type">int)st.Scale(epochCNT, class="num">1, epochCount, st.H + class="num">2, st.W - class="num">3, class="kw">false);
         yConv = (class="type">int)st.Scale(ao.fB, f.GetMinFunValue(), f.GetMaxFunValue(), class="num">2, st.H - class="num">2, true);
         st.Canvas.FillCircle(xConv, yConv, class="num">1, COLOR2RGB(clrConv));
         st.Update();
      }
   }
   aveResult += ao.fB;
}
aveResult /= (class="type">class="kw">double)NumberRepetTest_P;
class="type">class="kw">double score = aveResult;
Print(funcCount, " ", f.GetFuncName(), "&class="macro">#x27;s; Func runs: ", NumbTestFuncRuns_P, "; result: ", aveResult);
allScore += score;
}

「把三个经典测试函数塞进优化评测集」

常被问到为何不把 Ackley、Goldstein-Price、Shaffer #2 放进算法评测函数集。按分类它们都属“简单”档:超过一半的响应面贴在全局极值附近,太容易被优化器猜中,单独拿来测算法会高估效率。但交易者习惯拿它们验手感,所以还是补进系列,用来平衡用户自选函数的分布。 三个函数输出统一压到 [0.0, 1.0]。Ackley 理论最小值约 -14.3026675,Goldstein-Price 在原点附近有 -3 的谷,Shaffer #2 下探到约 -0.9984331,代码里用 Scale 把原始值线性映射到标准区间,方便横向比算法。 下面三段 Core 实现可直接拷进 MT5 脚本跑。Ackley 段先算两项指数再合入欧拉数;Goldstein-Price 拆成 part1、part2 两个多项式乘积取负;Shaffer #2 用 sin 平方差除以带噪分母。逐行拆完就能改区间或换变量维度做自己的压力测试。 外汇与贵金属优化参数空间高度非平稳,这类静态测试函数仅验证算法收敛逻辑,实盘仍属高风险,结论仅代表离线评测倾向。

MQL5 / C++
class="type">class="kw">double Core(class="type">class="kw">double x, class="type">class="kw">double y)
{
  class="type">class="kw">double res1 = -class="num">20.0 * MathExp(-class="num">0.2 * MathSqrt(class="num">0.5 * (x * x + y * y)));
  class="type">class="kw">double res2 = -MathExp(class="num">0.5 * (MathCos(class="num">2.0 * M_PI * x) + MathCos(class="num">2.0 * M_PI * y)));
  class="type">class="kw">double res3 = -(res1 + res2 + M_E + class="num">20.0);
  class="kw">return Scale(res3, -class="num">14.302667500265278, class="num">0.0, class="num">0.0, class="num">1.0);
}
class="type">class="kw">double Core(class="type">class="kw">double x, class="type">class="kw">double y)
{
  class="type">class="kw">double part1 = class="num">1 + MathPow((x + y + class="num">1), class="num">2) * (class="num">19 - class="num">14 * x + class="num">3 * x * x - class="num">14 * y + class="num">6 * x * y + class="num">3 * y * y);
  class="type">class="kw">double part2 = class="num">30 + MathPow((class="num">2 * x - class="num">3 * y), class="num">2) * (class="num">18 - class="num">32 * x + class="num">12 * x * x + class="num">48 * y - class="num">36 * x * y + class="num">27 * y * y);
  class="kw">return Scale(-part1 * part2, -class="num">1015690.2717980597, -class="num">3.0, class="num">0.0, class="num">1.0);
}
class="type">class="kw">double Core(class="type">class="kw">double x, class="type">class="kw">double y)
{
  class="type">class="kw">double numerator   = MathPow(MathSin(x * x - y * y), class="num">2) - class="num">0.5;
  class="type">class="kw">double denominator = MathPow(class="num">1 + class="num">0.001 * (x * x + y * y), class="num">2);
    
  class="kw">return Scale(-(class="num">0.5 + numerator / denominator), -class="num">0.9984331449753265, class="num">0, class="num">0, class="num">1.0);
}

◍ 用 DirectX 把测试函数摊成 3D 地形

光看数值和方程很难直觉把握优化算法的测试函数长什么样,MT5 的 DirectX 接口能直接撸出可交互的 3D 场景。思路来自终端自带样例里的 Math 3D Morpher,把 Functions.mqh 里的测试函数类接上可视化,就能在屏幕上转着看函数的形状和坑洼。 除了原有的连续函数,这版还塞进了离散变体:SkinDiscrete、HillyDiscrete,外加 Shaffer #2。离散版对遗传算法、粒子群这类容易卡在连续陷阱里的optimizer更友好,能暴露连续视角下看不见的阶梯结构。 生成网格数据的核心是两个函数:GenerateFunctionDataFixedSize 负责按指定分辨率采样,GenerateDataFixedSize 则自动取函数自身的 X/Y 范围来填表。x_size 和 y_size 小于 2 会直接报错返回 false,正常情况最少要 2×2 的点才能构成面。 把下面代码丢进你的 Functions.mqh 就能跑。采样分辨率调高(比如 200×200)地形更顺滑但吃 CPU,调低(比如 50×50)加载快但容易漏掉窄谷——外汇和贵金属策略回测里这种窄谷往往对应着参数敏感区,高风险且需实盘前反复验证。

MQL5 / C++
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//GenerateFunctionDataFixedSize
class="type">bool GenerateFunctionDataFixedSize(class="type">int x_size, class="type">int y_size, class="type">class="kw">double &data [], class="type">class="kw">double x_min, class="type">class="kw">double x_max, class="type">class="kw">double y_min, class="type">class="kw">double y_max, C_Function &function)
{
  if (x_size < class="num">2 || y_size < class="num">2)
  {
    PrintFormat("Error in data sizes: x_size=%d,y_size=%d", x_size, y_size);
    class="kw">return (class="kw">false);
  }
  class="type">class="kw">double dx = (x_max - x_min) / (x_size - class="num">1);
  class="type">class="kw">double dy = (y_max - y_min) / (y_size - class="num">1);
  ArrayResize(data, x_size * y_size);
  class=class="str">"cmt">//---
  for (class="type">int j = class="num">0; j < y_size; j++)
  {
    for (class="type">int i = class="num">0; i < x_size; i++)
    {
      class="type">class="kw">double x = x_min + i * dx;
      class="type">class="kw">double y = y_min + j * dy;
      data [j * x_size + i] = function.Core(x, y);
    }
  }
  class="kw">return (true);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//GenerateDataFixedSize
class="type">bool GenerateDataFixedSize(class="type">int x_size, class="type">int y_size, C_Function &function, class="type">class="kw">double &data [])
{
  if (x_size < class="num">2 || y_size < class="num">2)
  {
    PrintFormat("Error in data sizes: x_size=%d,y_size=%d", x_size, y_size);
    class="kw">return (class="kw">false);
  }
  class="kw">return GenerateFunctionDataFixedSize(x_size, y_size, data,
                                        function.GetMinRangeX(),
                                        function.GetMaxRangeX(),
                                        function.GetMinRangeY(),
                                        function.GetMaxRangeY(),
                                        function);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

画得少,看得清

把 Hilly、Forest、Megacity 这几类经典测试函数当作基准没问题,但别被它们框死——测试函数族早已扩展出离散、平滑、尖锐、不可微等类型,自己拼一个混合测试台,反而更贴近真实 EA 优化的坑。 把群体算法收进同一个基类之下,好处不是玄学:跨算法复用寻优逻辑,调参和对比的耗时倾向下降,标准函数集之外也能按品种特性造专属考题。 文末这段画布尺寸代码虽短,却点出常见误配——宽高算反了坐标网格就错位。逐行看:W 取画布宽,H 取高;WscrFunc 本应按宽减 2,原文却写成 H-2(标红处),正确该是 W-2(绿注);HscrFunc 才是 H-2。开 MT5 把这两行对一遍,能省掉一次重绘报错。 外汇与贵金属优化本身高风险,回测排名再漂亮也只代表历史窗口;这套混合思路值钱的地方,是给你留了持续加算法的口子,而不是交一份满分答卷。

MQL5 / C++
    W = width;   class=class="str">"cmt">//class="num">750;
    H = height; class=class="str">"cmt">//class="num">375;
    WscrFunc = H - class="num">2; class=class="str">"cmt">//W - class="num">2
    HscrFunc = H - class="num">2;
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多算法信号的一致性参考,你专注决策而非造轮子。

常见问题

可在统一测试代码里接入标准测试函数集,多次运行记录最优值与迭代曲线,对比单一算法基线判断提升幅度,结果倾向概率性而非绝对。
小布盯盘的品种页支持外部信号标注,你把 MQL5 算出的概率倾向区粘进备注即可同屏对照盘口,外汇贵金属波动剧烈仍属高风险。
三维地形能暴露算法在多维非凸空间的逃逸能力,比二维函数更易看出局部搜索与全局搜索的配比是否合理。
预留接口让新算法只重写差异部分,统一测试与函数集自动复用,开发混合方法时不容易破坏原有调用链。