混沌博弈优化(CGO)(基础篇)
◍ CGO 算法在 MT5 上的实现与实测概况
混沌博弈优化(CGO)是一套面向 MetaTrader 5 的自动化交易算法,由 Andrey Dik 在 2025 年 12 月 19 日 14:38 发布于 MT5 社区,截至统计时点该帖获得 1,431 次浏览、0 条评论,说明其仍属低讨论度的小众策略。 原文给出的结构只包含「概述 / 算法实现 / 测试结果」三个锚点,没有披露具体参数与品种,因此读者在 MT5 里直接搜 CGO 大概率只能看到作者发布的公开版 EA 或脚本。 外汇与贵金属杠杆品种波动剧烈,CGO 类混沌优化模型在历史回测中可能出现过拟合倾向;实盘前建议先用策略测试器跑 2023—2025 年的 EURUSD 与 XAUUSD 数据,观察样本外表现是否衰减。
混沌博弈优化为何适合交易寻优
复杂交易系统的参数寻优,本质是在高维空间里避开局部极值、逼近全局更优解。传统网格或随机搜索在维度升高后算力消耗陡增,而2020年Siamak Talatahari与Mehdi Azizi提出的CGO(混沌博弈优化)把混沌序列引入解的生成与迭代,借混沌轨迹的遍历性降低陷进局部坑的概率。 这套思路对MT5上的EA参数调校有直接价值:用混沌映射替代纯随机初始化,可能在相同迭代次数下搜到更稳的参数组合。后续我们会把算法阶段拆开,并用MQL5实现后在标准测试函数上跑一遍,看实际收敛表现。
「CGO 算法在 MT5 里的类结构与移动逻辑」
混沌博弈优化(CGO)把一群搜索智能体撒进多维边界,靠群体最优、子组均值和当前位置三者的混合来推进。默认种群规模在类里写死为 25,而原文伪代码建议 50,实盘调参时先用 SetParams 改掉这个值再跑,否则搜索覆盖度可能偏窄。 C_AO_CGO 继承自 C_AO,核心公开方法就四个:SetParams 灌入种群规模、Init 接收每维的 min/max/step 与迭代次数、Moving 管个体移动、Revision 刷新全局最优。私有侧 GetAlpha 按四种随机规则出 α,GenerateNewSolution 按公式算新坐标。 Moving 分两段:首次调用(revision=false)给每个个体随机初始化坐标并四舍五入步长;之后 revision=true,对每个个体随机抽 1~popSize 个同伴算 meanGroup,再交给 GenerateNewSolution 出新高。Revision 则扫一遍适应度,谁更优就更新 fB 和 cB 数组。 作者试过在基础公式前按二次分布 rnd*=rnd 偏向小索引个体复制坐标,想加速收敛,结果群体多样性掉得太快、容易陷局部最优,改进无效,最终退回原版。外汇与贵金属市场高波动,这类群体算法极易过拟合历史窗口,上 MT5 前务必用多段行情做样本外验证。
class C_AO_CGO : class="kw">public C_AO { class="kw">public: class=class="str">"cmt">//-------------------------------------------------------------------- ~C_AO_CGO() { } C_AO_CGO() { ao_name = "CGO"; ao_desc = "Chaos Game Optimization"; ao_link = "[MQL5官方文档] popSize = class="num">25; ArrayResize(params, class="num">1); params [class="num">0].name = "popSize"; params [class="num">0].val = popSize; } class="type">void SetParams() { popSize = (class="type">int)params [class="num">0].val; } class="type">bool Init(class="kw">const class="type">class="kw">double &rangeMinP [], class=class="str">"cmt">// minimum values class="kw">const class="type">class="kw">double &rangeMaxP [], class=class="str">"cmt">// maximum values class="kw">const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">// step change class="kw">const class="type">int epochsP = class="num">0); class=class="str">"cmt">// number of epochs
◍ 群体均值扰动与精英保留的实现细节
C_AO_CGO 的 Moving 函数在首次调用时走初始化分支:若 revision 标志为 false,直接按各维度区间随机撒点并做步长对齐,随后把 revision 置 true 并返回,这一步只跑一次。 后续每次 Moving 调用进入正式搜索:对种群中每个个体 i,先取 randGroupSize = RNDminusOne(popSize)+1,即 1 到 popSize 之间的随机群体规模;为该群体算各坐标均值 meanGroup,再交给 GenerateNewSolution 生成新解。这种随机子群均值引导,是混沌博弈优化的核心移动逻辑。 Revision 函数负责精英留存:遍历种群,若某个体适应度 a[i].f 大于当前最优 fB,就更新 fB 并拷贝坐标到 cB。注意它不修改个体本身,只维护全局最优快照。 在 MT5 里把这段挂到 EA 的 OnTick 前先 Init 一次,能看到 popSize 较大时首次 Moving 的随机撒点会明显拖慢前几帧计算;外汇与贵金属品种点差跳变频繁,这类群体算法过拟合历史区间的概率偏高,实盘前务必用真实tick回测。
class="type">void Moving(); class="type">void Revision(); class="kw">private: class=class="str">"cmt">//------------------------------------------------------------------- class="type">class="kw">double GetAlpha(); class="type">void GenerateNewSolution(class="type">int seedIndex, class="type">class="kw">double &meanGroup []); }; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">bool C_AO_CGO::Init(class="kw">const class="type">class="kw">double &rangeMinP [], class=class="str">"cmt">// minimum values class="kw">const class="type">class="kw">double &rangeMaxP [], class=class="str">"cmt">// maximum values class="kw">const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">// step change class="kw">const class="type">int epochsP = class="num">0) class=class="str">"cmt">// number of epochs { if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return false; class="kw">return true; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_CGO::Moving() { class=class="str">"cmt">//---------------------------------------------------------------------------- if (!revision) { for (class="type">int i = class="num">0; i < popSize; i++) { for (class="type">int c = class="num">0; c < coords; c++) { a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } revision = true; class="kw">return; } class=class="str">"cmt">//---------------------------------------------------------------------------- for (class="type">int i = class="num">0; i < popSize; i++) { class="type">int randGroupSize = u.RNDminusOne(popSize) + class="num">1; class="type">class="kw">double meanGroup []; ArrayResize(meanGroup, coords); ArrayInitialize(meanGroup, class="num">0); class="type">int randIndices []; ArrayResize(randIndices, randGroupSize); for (class="type">int j = class="num">0; j < randGroupSize; j++) randIndices [j] = u.RNDminusOne(popSize); for (class="type">int j = class="num">0; j < randGroupSize; j++) { for (class="type">int c = class="num">0; c < coords; c++) { meanGroup [c] += a [randIndices [j]].c [c]; } } for (class="type">int c = class="num">0; c < coords; c++) meanGroup [c] /= randGroupSize; GenerateNewSolution(i, meanGroup); } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_CGO::Revision() { for (class="type">int i = class="num">0; i < popSize; i++) { if (a [i].f > fB) { fB = a [i].f; ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY); } } } class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————