群体算法的基类作为高效优化的支柱·进阶篇
🧬

群体算法的基类作为高效优化的支柱·进阶篇

(2/3)·从遗传算法到蚁群策略,看基类如何把多种群体智能揉成一把快刀

案例拆解 第 2/3 篇

不少开发者把群体算法当成孤立工具,写完一个遗传脚本就丢在一边,遇到收敛慢只会调参数。其实把不同算法的优势塞进同一个基类,往往比死磕单法更快摸到最优解。

「优化器基类的参数容器与初始化」

这段基类代码把遗传/进化类优化器的公共骨架定下来了:用 rangeMin、rangeMax、rangeStep 三个数组锁死每个待优参数的搜索边界与步长,coords 记录维度数量。StandardInit 里先拿 GetMicrosecondCount 做 MathSrand 种子重置,避免每次 MT5 重启后种群演化轨迹完全复刻。 初始化时会做一道维度校验——coords 必须等于三个范围数组的 size,否则直接 return false。实测若你只传了 2 个参数的 min 却给了 3 个 step,该函数返回假,后续种群 ArrayResize 不会执行,EA 加载可能静默失败。 尾部那串 include 和 E_AO 枚举是调度层:SelectAO 按枚举 new 出具体算法实例(BGA、P_O_ES、SDSm、ESG、DE)。想接自己的算子,照着 C_AO 派生后在这里加一个 case 即可,外汇与贵金属参数寻优属高风险实验,回测过拟合概率偏高,建议先单品种验证。

MQL5 / C++
str += (class="type">class="kw">string)params [i].val + "|";
}
class="kw">return str;
}
class="kw">protected: class=class="str">"cmt">//-----------------------------------------------------------------
class="type">class="kw">string ao_name;      class=class="str">"cmt">//ao name;
class="type">class="kw">string ao_desc;      class=class="str">"cmt">//ao description
class="type">class="kw">double rangeMin  []; class=class="str">"cmt">//minimum search range
class="type">class="kw">double rangeMax  []; class=class="str">"cmt">//maximum search range
class="type">class="kw">double rangeStep []; class=class="str">"cmt">//step search
class="type">int    coords;       class=class="str">"cmt">//coordinates number
class="type">int    popSize;      class=class="str">"cmt">//population size
class="type">bool   revision;
C_AO_Utilities u;    class=class="str">"cmt">//auxiliary functions
class="type">bool StandardInit(const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">//minimum search range
                   const class="type">class="kw">double &rangeMaxP  [], class=class="str">"cmt">//maximum search range
                   const class="type">class="kw">double &rangeStepP []) class=class="str">"cmt">//step search
{
   MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">//reset of the generator
   fB       = -DBL_MAX;
   revision = class="kw">false;
   coords   = ArraySize(rangeMinP);
   if (coords == class="num">0 || coords != ArraySize(rangeMaxP) || coords != ArraySize(rangeStepP)) class="kw">return class="kw">false;
   ArrayResize(rangeMin,  coords);
   ArrayResize(rangeMax,  coords);
   ArrayResize(rangeStep, coords);
   ArrayResize(cB,         coords);
   ArrayCopy(rangeMin,  rangeMinP,  class="num">0, class="num">0, WHOLE_ARRAY);
   ArrayCopy(rangeMax,  rangeMaxP,  class="num">0, class="num">0, WHOLE_ARRAY);
   ArrayCopy(rangeStep, rangeStepP, class="num">0, class="num">0, WHOLE_ARRAY);
   class="kw">return true;
}
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="macro">#include "AO_BGA_Binary_Genetic_Algorithm.mqh"
class="macro">#include "AO_(P_O)ES_Evolution_Strategies.mqh"
class="macro">#include "AO_DE_Differential_Evolution.mqh"
class="macro">#include "AO_SDSm_Stochastic_Diffusion_Search.mqh"
class="macro">#include "AO_ESG_Evolution_of_Social_Groups.mqh";
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
enum E_AO
{
   AO_BGA,
   AO_P_O_ES,
   AO_SDSm,
   AO_ESG,
   AO_DE,
   AO_NONE
};
C_AO *SelectAO(E_AO a)
{
   C_AO *ao;
   class="kw">switch (a)
   {
      case  AO_BGA:
         ao = new C_AO_BGA(); class="kw">return (GetPointer(ao));
      case  AO_P_O_ES:
         ao = new C_AO_P_O_ES(); class="kw">return (GetPointer(ao));
      case  AO_SDSm:
         ao = new C_AO_SDSm(); class="kw">return (GetPointer(ao));
      case  AO_ESG:

◍ 用工厂方法隔离不同 AO 算法实例

这段片段属于一个 AO 指标对象的工厂构造逻辑,依据传入的枚举类型决定具体实例化哪一个派生类。 当分支为 AO_ESG 时,用 new 创建 C_AO_ESG 对象并通过 GetPointer 返回其指针;遇到 AO_DE 则改为构造 C_AO_DE,同样返回指针。 default 分支直接把 ao 置为 NULL 并返回 NULL,意味着调用方在拿到空指针时必须自行处理,否则后续访问会触发无效指针异常。 在 MT5 里跑这段,只需确认你的枚举里确实定义了 AO_ESG 与 AO_DE,否则编译器会报 case 标签不匹配。外汇与贵金属行情波动剧烈,此类对象创建失败不会提示盈亏,仅表现为指标不绘制,高风险环境下需自行加日志排查。

MQL5 / C++
    ao = new C_AO_ESG(); class="kw">return (GetPointer(ao));
    case   AO_DE:
      ao = new C_AO_DE(); class="kw">return (GetPointer(ao));
    class="kw">default:
      ao = NULL; class="kw">return NULL;
  }
}

从基类派生 SDSm 代理与算法类

随机扩散搜索(SDSm)在 MT5 里适合作为优化代理的继承范例:用 C_SDS_Agent 继承基础类 C_AO_Agent,代理里只补了餐厅地址、前次坐标与适应度暂存字段,而 c 坐标和 f 适应度直接沿用基类,不重复声明——所有优化算法代理都吃这两样,写一遍就够。 C_AO_SDSm 继承自 C_AO,构造时把外部参数塞进数组 params,默认 popSize=100、restNumb=100、probabRest=0.05。用数组而非散参,是为了不和测试台(如策略测试器参数接口)打架,用户之后随时可改。 Init 方法先调基类的 StandardInit 并传入 rangeMinP / rangeMaxP / rangeStepP;若返回 false 则整体初始化失败。随后 delete 旧代理(重用对象时防内存泄漏),再把 agent 数组与基类 a 的尺寸改到 popSize 并做类型转换,最后走 SDSm 原论文的扩散逻辑。 下面这段是类声明的骨架,注意 C_SDS_Agent::Init 里把 f 和 fPrev 都预置为 -DBL_MAX,意味着未评估前适应度视为负无穷,避免被误选为最优。

MQL5 / C++
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class C_SDS_Agent : class="kw">public C_AO_Agent
{
  class="kw">public: class=class="str">"cmt">//--------------------------------------------------------------------
  ~C_SDS_Agent() { }
  class="type">int     raddr     []; class=class="str">"cmt">//restaurant address
  class="type">int     raddrPrev []; class=class="str">"cmt">//previous restaurant address
  class="type">class="kw">double  cPrev     []; class=class="str">"cmt">//previous coordinates(dishes)
  class="type">class="kw">double  fPrev;        class=class="str">"cmt">//previous fitness
  class="type">void Init(class="type">int coords)
  {
    ArrayResize(c,        coords);
    ArrayResize(cPrev,    coords);
    ArrayResize(raddr,    coords);
    ArrayResize(raddrPrev, coords);
    f        = -DBL_MAX;
    fPrev    = -DBL_MAX;
  }
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class C_AO_SDSm : class="kw">public C_AO
{
  class="kw">public: class=class="str">"cmt">//--------------------------------------------------------------------
  ~C_AO_SDSm() { }
  C_AO_SDSm()
  {
    ao_name = "SDSm";
    ao_desc = "Stochastic Diffusion Search";
    popSize    = class="num">100; class=class="str">"cmt">//population size
    restNumb   = class="num">100;  class=class="str">"cmt">//restaurants number
    probabRest = class="num">0.05; class=class="str">"cmt">//probability restaurant choosing
    ArrayResize(params, class="num">3);
    params [class="num">0].name = "popSize";    params [class="num">0].val  = popSize;
    params [class="num">1].name = "restNumb";   params [class="num">1].val  = restNumb;
    params [class="num">2].name = "probabRest"; params [class="num">2].val  = probabRest;
  }
  class="type">void SetParams()
  {
    popSize    = (class="type">int)params [class="num">0].val;
    restNumb   = (class="type">int)params [class="num">1].val;
    probabRest = params      [class="num">2].val;
  }
  class="type">bool Init(const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">//minimum search range
             const class="type">class="kw">double &rangeMaxP  [], class=class="str">"cmt">//maximum search range
             const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">//step search
             const class="type">int    epochsP = class="num">0);   class=class="str">"cmt">//number of epochs
  class="type">void Moving();
  class="type">void Revision();
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">int     restNumb;          class=class="str">"cmt">//restaurants number

「SDS代理的初始化与河床结构铺设」

这段 MQL5 实现的是 SDS(随机扩散搜索)多代理模型的初始化环节,核心是把搜索空间切成若干‘餐厅’格子,并为每个坐标维度准备好‘河床’记录结构。 Init 函数先调用 StandardInit 做范围与步长校验,失败直接返回 false;随后按 popSize 重整 agent 与 a 两个数组,逐个 new 出 C_SDS_Agent 并绑定到 coords 维度上。 河床 rb 按坐标数 coords 展开,每个 rb[i].coordOnSector 再按 restNumb resize,并用 -DBL_MAX 初始化——这意味着该格点尚未被任何代理探过。restSpace[i] 则等于 (rangeMax[i]-rangeMin[i])/restNumb,即单维上每个餐厅的物理跨度。 别把河床当摆设 rb 里的 -DBL_MAX 是后续 Research 判断‘是否首次到达该格’的哨兵值,若你改了初始化填充值,探坑逻辑会直接漏掉冷门区间。外汇与贵金属参数寻优用这套结构时,网格过密会放大过拟合风险,建议先在 MT5 策略测试器里用小 restNumb 验证内存与耗时。

MQL5 / C++
class="type">class="kw">double probabRest;      class=class="str">"cmt">//probability restaurant choosing
C_SDS_Agent *agent []; class=class="str">"cmt">//candidates
class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
class="kw">struct S_Riverbed class=class="str">"cmt">//river bed
{
   class="type">class="kw">double coordOnSector []; class=class="str">"cmt">//coordinate on the sector(number of cells: number of sectors on the coordinate, cell value: specific coordinate on the sector)
};
class="type">class="kw">double restSpace [];      class=class="str">"cmt">//restaurants space
S_Riverbed    rb [];      class=class="str">"cmt">//riverbed
class="type">void Research(const class="type">class="kw">double   ko,
               const class="type">int      raddr,
               const class="type">class="kw">double   restSpace,
               const class="type">class="kw">double   rangeMin,
               const class="type">class="kw">double   rangeStep,
               const class="type">class="kw">double   pitOld,
               class="type">class="kw">double         &pitNew);
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">bool C_AO_SDSm::Init(const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">//minimum search range
                      const class="type">class="kw">double &rangeMaxP  [], class=class="str">"cmt">//maximum search range
                      const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">//step search
                      const class="type">int     epochsP = class="num">0)   class=class="str">"cmt">//number of epochs
{
   if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false;
   class=class="str">"cmt">//----------------------------------------------------------------------------
   for (class="type">int i = class="num">0; i < ArraySize(agent); i++) class="kw">delete agent [i];
   ArrayResize(agent, popSize);
   ArrayResize(a,       popSize);
   for (class="type">int i = class="num">0; i < popSize; i++)
   {
      a      [i] = new C_SDS_Agent();
      agent [i] = (C_SDS_Agent *)a [i];
      agent [i].Init(coords);
   }
   ArrayResize(restSpace, coords);
   ArrayResize(rb,          coords);
   for (class="type">int i = class="num">0; i < coords; i++)
   {
      ArrayResize(rb [i].coordOnSector, restNumb);
      ArrayInitialize(rb [i].coordOnSector, -DBL_MAX);
   }
   for (class="type">int i = class="num">0; i < coords; i++)
   {
      restSpace [i] = (rangeMax [i] - rangeMin [i]) / restNumb;
   }
   class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

◍ 把测试台功能收进一个类里

现在不需要往测试台里加新东西,但把全部功能搬进 C_TestStand 类之后,调用和封装都省事很多。类内部逻辑和之前差别不大,重点在对外接口干净了:初始化时直接传画布宽高,不用再到处改硬编码。 标准输入参数里已经能选优化算法和测试函数,目前只支持平滑函数或离散函数两种基准,但你可以任意勾选组合来压测算法。比如同时挂两个离散函数,就能看出某算法在多点极小值附近的逃逸倾向。 下面这段是类的骨架代码,Init 里 W 默认可设 750、H 设 375,实际由外部传入;画布名固定为 AO_Test_Func_Canvas,创建失败会打印错误码。MaxMinDr 负责把全局最大最小值用黑色圆圈标出来,半径从 12 到 15 像素叠了四层,视觉上是个实心黑环。 代码逐行拆: #include <Canvas\Canvas.mqh> 引入画布库 #include <\Math\Functions.mqh> 引入数学函数库 class C_TestStand 定义测试台类 public: void Init(int width, int height) 公开初始化方法,接收宽高 W = width; 画布宽赋值(注释里原 750 为旧值) H = height; 画布高赋值(注释里原 375 为旧值) WscrFunc = H - 2; 函数区宽按高减边距 HscrFunc = H - 2; 函数区高减边距 string canvasName = "AO_Test_Func_Canvas"; 画布对象名 if (!Canvas.CreateBitmapLabel(canvasName, 5, 30, W, H, COLOR_FORMAT_ARGB_RAW)) 在坐标(5,30)建位图画布 Print("Error creating Canvas: ", GetLastError()); 失败则打印错误 return; 退出 Init ObjectSetInteger(0, canvasName, OBJPROP_HIDDEN, false); 画布不隐藏 ObjectSetInteger(0, canvasName, OBJPROP_SELECTABLE, true); 画布可选中 ArrayResize(FunctScrin, HscrFunc); 函数色板数组按高分配 for (int i=0; i<HscrFunc; i++) ArrayResize(FunctScrin[i].clr, HscrFunc); 每行颜色数组再按高分配 struct S_CLR { color clr []; }; 颜色结构体 public: void CanvasErase() 清屏方法 Canvas.Erase(XRGB(0,0,0)); 先擦成黑底 Canvas.FillRectangle(1,1,H-2,H-2, COLOR2RGB(clrWhite)); 左半填白 Canvas.FillRectangle(H+1,1,W-2,H-2, COLOR2RGB(clrWhite)); 右半填白 public: void MaxMinDr(C_Function & f) 画极值方法,引用函数对象 int x = (int)Scale(f.GetMaxFuncX(), f.GetMinRangeX(), f.GetMaxRangeX(), 1, W/2-1, false); 最大值X映射到左半区 int y = (int)Scale(f.GetMaxFuncY(), f.GetMinRangeY(), f.GetMaxRangeY(), 1, H-1, true); 最大值Y映射(Y反转) Canvas.Circle(x,y,12~15, COLOR2RGB(clrBlack)); 四层黑圈标最大值 x = (int)Scale(f.GetMinFuncX(), f.GetMinRangeX(), f.GetMaxRangeX(), 0, W/2-1, false); 最小值X映射 y = (int)Scale(f.GetMinFuncY(), f.GetMinRangeY(), f.GetMaxRangeY(), 0, H-1, true); 最小值Y映射 Canvas.Circle(x,y,12~13, COLOR2RGB(clrBlack)); 两层黑圈标最小值

MQL5 / C++
class="macro">#include <Canvas\Canvas.mqh>
class="macro">#include <\Math\Functions.mqh>
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class C_TestStand
{
  class="kw">public: class="type">void Init(class="type">int width, class="type">int height)
  {
    W = width;  class=class="str">"cmt">//class="num">750;
    H = height; class=class="str">"cmt">//class="num">375;
    WscrFunc = H - class="num">2;
    HscrFunc = H - class="num">2;
    class=class="str">"cmt">//creating a table ---------------------------------------------------------
    class="type">class="kw">string canvasName = "AO_Test_Func_Canvas";
    if (!Canvas.CreateBitmapLabel(canvasName, class="num">5, class="num">30, W, H, COLOR_FORMAT_ARGB_RAW))
    {
      Print("Error creating Canvas: ", GetLastError());
      class="kw">return;
    }
    ObjectSetInteger(class="num">0, canvasName, OBJPROP_HIDDEN, class="kw">false);
    ObjectSetInteger(class="num">0, canvasName, OBJPROP_SELECTABLE, true);
    ArrayResize(FunctScrin, HscrFunc);
    for (class="type">int i = class="num">0; i < HscrFunc; i++) ArrayResize(FunctScrin [i].clr, HscrFunc);
  }
  class="kw">struct S_CLR
  {
    class="type">color clr [];
  };
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="kw">public: class="type">void CanvasErase()
  {
    Canvas.Erase(XRGB(class="num">0, class="num">0, class="num">0));
    Canvas.FillRectangle(class="num">1,     class="num">1, H - class="num">2, H - class="num">2, COLOR2RGB(clrWhite));
    Canvas.FillRectangle(H + class="num">1, class="num">1, W - class="num">2, H - class="num">2, COLOR2RGB(clrWhite));
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="kw">public: class="type">void MaxMinDr(C_Function & f)
  {
    class=class="str">"cmt">//draw Max global-------------------------------------------------------------
    class="type">int x = (class="type">int)Scale(f.GetMaxFuncX(), f.GetMinRangeX(), f.GetMaxRangeX(), class="num">1, W/class="num">2 - class="num">1, class="kw">false);
    class="type">int y = (class="type">int)Scale(f.GetMaxFuncY(), f.GetMinRangeY(), f.GetMaxRangeY(), class="num">1, H   - class="num">1, true);
    Canvas.Circle(x, y, class="num">12, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, class="num">13, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, class="num">14, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, class="num">15, COLOR2RGB(clrBlack));
    class=class="str">"cmt">//draw Min global-------------------------------------------------------------
    x = (class="type">int)Scale(f.GetMinFuncX(), f.GetMinRangeX(), f.GetMaxRangeX(), class="num">0, W/class="num">2 - class="num">1, class="kw">false);
    y = (class="type">int)Scale(f.GetMinFuncY(), f.GetMinRangeY(), f.GetMaxRangeY(), class="num">0, H - class="num">1, true);
    Canvas.Circle(x, y, class="num">12, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, class="num">13, COLOR2RGB(clrBlack));
  }
  class=class="str">"cmt">//----------------------------------------------------------------------------

把函数点阵画到画布上的两套画法

PointDr 方法负责把一组 (x,y) 散点映射到画布坐标并上色。循环里每个点先累加求均值,再用 Scale 把数值域换算到 0~WscrFunc-1 和 0~HscrFunc-1 的像素区间,色相由 DoubleToColor 按序号从 0 到 270 度渐变,半径 1 像素实心圆。 均值点区分主从:非主序列画外径 3 黑、内径 2 白的双环,主序列则描 5 和 6 像素两道黑圈,视觉上能把基准轨迹从陪衬点里拎出来。外汇与贵金属图表叠加这类自绘层时,注意高价波动下坐标缩放可能挤压点距,属高风险观测手段。 SendGraphToCanvas 用双重循环把 FunctScrin[w].clr[h] 逐像素搬进 Canvas,偏移 +1 避免贴边;DrawFunctionGraph 则按 HscrFunc 分辨率反推每个像素对应的 x、y 值域再交回函数求值。开 MT5 把 WscrFunc、HscrFunc 调到 200 以上,能直接看清单点密度是否够支撑你的价格行为判断。

MQL5 / C++
class="kw">public: class="type">void PointDr(class="type">class="kw">double &args [], C_Function & f, class="type">int shiftX, class="type">int shiftY, class="type">int count, class="type">bool main)
  {
    class="type">class="kw">double x = class="num">0.0;
    class="type">class="kw">double y = class="num">0.0;
    class="type">class="kw">double xAve = class="num">0.0;
    class="type">class="kw">double yAve = class="num">0.0;
    class="type">int width  = class="num">0;
    class="type">int height = class="num">0;
    class="type">color clrF = clrNONE;
    for(class="type">int i = class="num">0; i < count; i++)
    {
      xAve += args [i * class="num">2];
      yAve += args [i * class="num">2 + class="num">1];
      x = args [i * class="num">2];
      y = args [i * class="num">2 + class="num">1];
      width  = (class="type">int)Scale(x, f.GetMinRangeX(), f.GetMaxRangeX(), class="num">0, WscrFunc - class="num">1, class="kw">false);
      height = (class="type">int)Scale(y, f.GetMinRangeY(), f.GetMaxRangeY(), class="num">0, HscrFunc - class="num">1, true);
      clrF = DoubleToColor(i, class="num">0, count - class="num">1, class="num">0, class="num">270);
      Canvas.FillCircle(width + shiftX, height + shiftY, class="num">1, COLOR2RGB(clrF));
    }
    xAve /=(class="type">class="kw">double)count;
    yAve /=(class="type">class="kw">double)count;
    width  = (class="type">int)Scale(xAve, f.GetMinRangeX(), f.GetMaxRangeX(), class="num">0, WscrFunc - class="num">1, class="kw">false);
    height = (class="type">int)Scale(yAve, f.GetMinRangeY(), f.GetMaxRangeY(), class="num">0, HscrFunc - class="num">1, true);
    if(!main)
    {
      Canvas.FillCircle(width + shiftX, height + shiftY, class="num">3, COLOR2RGB(clrBlack));
      Canvas.FillCircle(width + shiftX, height + shiftY, class="num">2, COLOR2RGB(clrWhite));
    }
    else
    {
      Canvas.Circle(width + shiftX, height + shiftY, class="num">5, COLOR2RGB(clrBlack));
      Canvas.Circle(width + shiftX, height + shiftY, class="num">6, COLOR2RGB(clrBlack));
    }
  }
  class=class="str">"cmt">//--------------------------------------------------------------------------------
  class="kw">public: class="type">void SendGraphToCanvas()
  {
    for (class="type">int w = class="num">0; w < HscrFunc; w++)
    {
      for (class="type">int h = class="num">0; h < HscrFunc; h++)
      {
        Canvas.PixelSet(w + class="num">1, h + class="num">1, COLOR2RGB(FunctScrin [w].clr [h]));
      }
    }
  }
  class=class="str">"cmt">//--------------------------------------------------------------------------------
  class="kw">public: class="type">void DrawFunctionGraph(C_Function & f)
  {
    class="type">class="kw">double ar [class="num">2];
    class="type">class="kw">double fV;
    for (class="type">int w = class="num">0; w < HscrFunc; w++)
    {
      ar [class="num">0] = Scale(w, class="num">0, H, f.GetMinRangeX(), f.GetMaxRangeX(), class="kw">false);
      for (class="type">int h = class="num">0; h < HscrFunc; h++)
      {
        ar [class="num">1] = Scale(h, class="num">0, H, f.GetMinRangeY(), f.GetMaxRangeY(), true);
把混合回测交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多算法回测对比,你专注挑哪套混合逻辑更贴合盘面。

常见问题

主要实现基类定义的虚拟接口,如初始化、迭代步进与适应度回传,统一测试代码会自动识别新类并跑标准函数。
目前小布内置的是通用诊断与回测视图,自定义 EA 需先导出测试报告,再在品种页导入对照,不强制重写基类。
3D 面能暴露算法在多维丘陵与狭谷间的逃逸能力,单看 2D 容易高估收敛稳定性,尤其对金融时序拟合偏差大。
倾向用于离散与连续混合空间,例如仓位网格加阈值过滤,蚁群找结构、进化调步长,概率上比纯遗传更省代。