混沌优化算法(COA):续篇·进阶篇
(2/3)·从 ApplyMutation 到 UpdateSigma,拆解 COA 避免局部最优的底层代码逻辑
◍ 收敛判定与停滞重置的实现细节
混沌优化代理里,历史最优值用长度 10 的环形缓冲保存,UpdateBestHistory 每次写入后 historyIndex 按 (historyIndex+1)%10 推进,避免数组越界同时保留最近 10 次记录。写前先用 MathIsValidNumber 拦截非数,防止 NaN 污染后续收敛判断。 IsConverged 扫描这 10 个槽,跳过未初始化(-DBL_MAX)或非法值,累计有效值、极值与和。有效样本不足 5 个、或极大极小相等时直接返回未收敛,样本门槛设 5 而非 10,是为让算法在预热阶段不误报。 相对收敛阈值定为 relDiff < 0.001,即历史最优波动小于均值的 0.1% 才认为收敛;若均值本身接近零,则退化为绝对差小于 eps*10.0。这个 0.1% 是硬编码经验值,黄金小时图调参时可放宽到 0.005 观察早熟概率。 ResetStagnatingAgents 给每个代理维护 stagnationCounter:当本次适应度 f 未超过历史最好 fB 就加一,否则清零。计数器超过 5 才进入重置候选,重置概率 resetProb = 0.2*(1+counter/10),代际越久越容易被突变救活,但单次最多触发约三成个体,保留种群多样性。外汇与贵金属波动剧烈,这类重置逻辑可能显著改变回测曲线,实盘前务必在 MT5 策略测试器用历史数据验证。
class="type">class="kw">double violation = CalculateConstraintValue(agentIdx, c); if (violation > eps) { class="kw">return false; } } class="kw">return true; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_COA_chaos::UpdateBestHistory(class="type">class="kw">double newBest) { class=class="str">"cmt">// Protection against invalid values if (!MathIsValidNumber(newBest)) class="kw">return; class=class="str">"cmt">// Update the history of the best values globalBestHistory [historyIndex] = newBest; historyIndex = (historyIndex + class="num">1) % class="num">10; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">bool C_AO_COA_chaos::IsConverged() { class=class="str">"cmt">// Check if there is enough data in the history class="type">int validValues = class="num">0; class="type">class="kw">double sum = class="num">0.0; class="type">class="kw">double minVal = DBL_MAX; class="type">class="kw">double maxVal = -DBL_MAX; class=class="str">"cmt">// Find min, max, and sum of values in history for (class="type">int i = class="num">0; i < class="num">10; i++) { if (globalBestHistory [i] == -DBL_MAX || !MathIsValidNumber(globalBestHistory [i])) class="kw">continue; minVal = MathMin(minVal, globalBestHistory [i]); maxVal = MathMax(maxVal, globalBestHistory [i]); sum += globalBestHistory [i]; validValues++; } class=class="str">"cmt">// If there is not enough data or all values are the same if (validValues < class="num">5 || minVal == maxVal) class="kw">return false; class=class="str">"cmt">// Calculate the average value class="type">class="kw">double average = sum / validValues; class=class="str">"cmt">// Check the case when the mean is close to zero if (MathAbs(average) < eps) class="kw">return MathAbs(maxVal - minVal) < eps * class="num">10.0; class=class="str">"cmt">// Relative difference for convergence testing class="type">class="kw">double relDiff = MathAbs(maxVal - minVal) / MathAbs(average); class="kw">return relDiff < class="num">0.001; class=class="str">"cmt">// Convergence threshold - class="num">0.1% } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_COA_chaos::ResetStagnatingAgents() { class="type">int resetCount = class="num">0; for (class="type">int i = class="num">0; i < popSize; i++) { class=class="str">"cmt">// Increase the stagnation counter if there is no improvement if (a [i].f <= a [i].fB) { agent [i].stagnationCounter++; } else { agent [i].stagnationCounter = class="num">0; } class=class="str">"cmt">// Reset the agent if it has been stagnating for too class="type">long if (agent [i].stagnationCounter > class="num">5) { class=class="str">"cmt">// Reset only some of the agents with a probability depending on stagnation class="type">class="kw">double resetProb = class="num">0.2 * (class="num">1.0 + (class="type">class="kw">double)agent [i].stagnationCounter / class="num">10.0); if (u.RNDprobab() < resetProb) { ApplyMutation(i); agent [i].stagnationCounter = class="num">0;
「约束违反与惩罚项的梯度计算逻辑」
在混沌优化代理类里,约束处理靠三个底层函数接力:先算单维越界量,再算越界梯度方向,最后把越界平方汇总成惩罚项。 CalculateConstraintValue 对第 coordIdx 维取代理坐标 x,对照 rangeMin/rangeMax:低于下界则 violation 累加 min-x,高于上界则累加 x-max,界内返回 0.0。这里用的是硬线性越界,没有平滑过渡,越界多少记多少。 CalculateConstraintGradient 返回方向信号:x<min 给 -1.0(需增大),x>max 给 1.0(需减小),界内 0.0。CalculateWeightedGradient 则先扫全部维取 maxViolation(下限 eps),若当前维 violation<=eps 直接返 0;否则用 violation/maxViolation 做权重乘梯度,越界占比越大推动越强。 CalculatePenaltyFunction 把各维 violation 平方求和(violation>eps 才计),再乘动态系数 currentSigma 得 penalty,目标函数按最大化处理为 f-penalty。外汇与贵金属参数寻优用这套,模型可能把极端杠杆解罚到失效,实盘高风险,建议开 MT5 把 eps 调到 1e-6 看代理收敛是否变慢。
class="type">class="kw">double C_AO_COA_chaos::CalculateWeightedGradient(class="type">int agentIdx, class="type">int coordIdx) { class=class="str">"cmt">// Calculate the maximum value of the constraint violation class="type">class="kw">double maxViolation = eps; for (class="type">int c = class="num">0; c < coords; c++) { class="type">class="kw">double violation = CalculateConstraintValue(agentIdx, c); maxViolation = MathMax(maxViolation, violation); } class=class="str">"cmt">// Violation for the current coordinate class="type">class="kw">double violation = CalculateConstraintValue(agentIdx, coordIdx); class=class="str">"cmt">// If there is no significant violation, class="kw">return class="num">0 if (violation <= eps) class="kw">return class="num">0.0; class=class="str">"cmt">// Calculate the gradient class="type">class="kw">double gradient = CalculateConstraintGradient(agentIdx, coordIdx); class=class="str">"cmt">// Normalize the impact depending on the degree of violation class="type">class="kw">double weight = violation / maxViolation; class="kw">return gradient * weight; } class="type">class="kw">double C_AO_COA_chaos::CalculateConstraintValue(class="type">int agentIdx, class="type">int coordIdx) { class="type">class="kw">double x = a [agentIdx].c [coordIdx]; class="type">class="kw">double min = rangeMin [coordIdx]; class="type">class="kw">double max = rangeMax [coordIdx]; class=class="str">"cmt">// Smoothed constraint violation function with a smooth transition at the boundary class="type">class="kw">double violation = class="num">0.0; class=class="str">"cmt">// Check the lower bound if (x < min) { violation += min - x; } class=class="str">"cmt">// Check the upper bound else if (x > max) { violation += x - max; } class="kw">return violation; } class="type">class="kw">double C_AO_COA_chaos::CalculateConstraintGradient(class="type">int agentIdx, class="type">int coordIdx) { class="type">class="kw">double x = a [agentIdx].c [coordIdx]; class="type">class="kw">double min = rangeMin [coordIdx]; class="type">class="kw">double max = rangeMax [coordIdx]; class=class="str">"cmt">// Smoothed gradient for better stability if (x < min) class="kw">return -class="num">1.0; class=class="str">"cmt">// Need to increase the value else if (x > max) class="kw">return class="num">1.0; class=class="str">"cmt">// Need to decrease the value class="kw">return class="num">0.0; } class="type">class="kw">double C_AO_COA_chaos::CalculatePenaltyFunction(class="type">int agentIdx) { class=class="str">"cmt">// Base value of the objective function class="type">class="kw">double baseValue = a [agentIdx].f; class=class="str">"cmt">// Penalty term class="type">class="kw">double penaltySum = class="num">0.0; for (class="type">int c = class="num">0; c < coords; c++) { class="type">class="kw">double violation = CalculateConstraintValue(agentIdx, c); class=class="str">"cmt">// Quadratic penalty for better resolution of solutions close to the boundary if (violation > eps) { penaltySum += violation * violation; } } class=class="str">"cmt">// Apply a dynamic penalty ratio class="type">class="kw">double penalty = currentSigma * penaltySum; class=class="str">"cmt">// For the maximization problem: f_penalty = f - penalty
混沌优化里的代际推进与参数自适应
这段逻辑跑在 MT5 的 EA 或指标类的逐根 K 线驱动里,核心是一个叫 Moving 的步进函数。它先自增 epochNow 纪元计数,首帧做种群初始化并置 revision 标记后直接返回,后续每帧才进入真正的搜索循环。 每 5 个纪元(epochNow % 5 == 0)触发一次 ResetStagnatingAgents,把长时间不进化的个体重置,避免种群早熟收敛。搜索阶段按纪元切分:epochNow <= S1 走第一载波搜索,<= S1+S2 走第二载波,相当于先广域后局部的两次收敛波。 Revision 函数负责评估与更新。它遍历 popSize 个解,用 CalculatePenaltyFunction 算适应度,遇非有限数直接跳过;个人最优与全局最优分开更新,全局更新时把提升量写进 bestImprovement 并追加到最优值历史。 参数自适应看 successRate:用 improvementCount 除以 2*popSize(至少为1防零除)。当 epochNow<=S1 的全局搜索期,若成功率低于 0.1 就把 alpha[c] 乘 t3 放大搜索半径;低于 0.3 则进入另一档收缩逻辑。把 t3 设为 1.15 左右,能在 EURUSD 15M 回测中让早期探索覆盖更宽参数空间,但外汇与贵金属属高风险品种,参数放大也可能拉长无效计算。
class="type">void C_AO_COA_chaos::Moving() { epochNow++; if (!revision) { InitialPopulation(); revision = true; class="kw">return; } class=class="str">"cmt">// Dynamically update the penalty parameter UpdateSigma(); class=class="str">"cmt">// Reset stagnating agents if (epochNow % class="num">5 == class="num">0) { ResetStagnatingAgents(); } class=class="str">"cmt">// Determine the search phase if (epochNow <= S1) { FirstCarrierWaveSearch(); } else if (epochNow <= S1 + S2) { SecondCarrierWaveSearch(); } } class="type">void C_AO_COA_chaos::Revision() { class="type">int improvementCount = class="num">0; class="type">class="kw">double bestImprovement = class="num">0.0; class=class="str">"cmt">// Evaluate all solutions for (class="type">int i = class="num">0; i < popSize; i++) { class="type">class="kw">double newValue = CalculatePenaltyFunction(i); class=class="str">"cmt">// Check the validity of the new value if (!MathIsValidNumber(newValue)) class="kw">continue; class=class="str">"cmt">// Save the current value for the next iteration a [i].f = newValue; class=class="str">"cmt">// Update the personal best solution(before a global one for efficiency) if (newValue > a [i].fB) { a [i].fB = newValue; ArrayCopy(a [i].cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY); improvementCount++; } class=class="str">"cmt">// Update the global best solution if (newValue > fB) { class="type">class="kw">double improvement = newValue - fB; fB = newValue; ArrayCopy(cB, a [i].c, class="num">0, class="num">0, WHOLE_ARRAY); class=class="str">"cmt">// Update the history of the best values UpdateBestHistory(fB); bestImprovement = MathMax(bestImprovement, improvement); improvementCount++; } } class=class="str">"cmt">// Adapt search parameters depending on the phase and success if (epochNow > class="num">1) { class=class="str">"cmt">// Search success rate(prevention of zero divide) class="type">class="kw">double successRate = (class="type">class="kw">double)improvementCount / MathMax(class="num">1, class="num">2 * popSize); class=class="str">"cmt">// Adaptation of the alpha parameter for (class="type">int c = class="num">0; c < coords; c++) { class="type">class="kw">double oldAlpha = alpha [c]; if (epochNow <= S1) { class=class="str">"cmt">// In the global search phase if (successRate < class="num">0.1) { class=class="str">"cmt">// Very few improvements - increasing the search scope alpha [c] *= t3; } else if (successRate < class="num">0.3) {
◍ 按胜率动态收放搜索半径
这段逻辑处在优化器的步长更新环节,核心是用 successRate(本轮改进成功率)反向调节每个维度的 alpha[c] 搜索半径。全局搜索阶段,成功率低于 0.05 时把 alpha[c] 乘 1.2 略微放大范围;高于 0.7 则乘 0.9 收紧,避免在无价值区域空转。 局部搜索阶段更激进:成功率低于 0.05 用 t3 系数扩圈,高于 0.2 直接乘 0.8 缩圈。也就是说,越容易出改进解,算法越倾向精细挖掘,反之才放宽网。 最后一段是硬边界,maxAlpha 锁在维度跨度的 0.2 倍、minAlpha 不低于 0.0001 倍,越界即拉回。外汇与贵金属行情跳变频繁,这种上下限能防止参数在极端 K 线后跑飞,但策略本身仍属概率博弈,实盘前请在 MT5 用历史数据跑通再上模拟盘。
class=class="str">"cmt">// Few improvements - slightly expanding the scope alpha [c] *= class="num">1.2; } else if (successRate > class="num">0.7) { class=class="str">"cmt">// Many improvements - narrowing the scope alpha [c] *= class="num">0.9; } } else { class=class="str">"cmt">// In the local search phase if (successRate < class="num">0.05) { class=class="str">"cmt">// Very few improvements - increasing the search scope alpha [c] *= t3; } else if (successRate > class="num">0.2) { class=class="str">"cmt">// Enough improvements - narrowing the scope alpha [c] *= class="num">0.8; } } class=class="str">"cmt">// Limits on alpha range with safe bounds verification if (c < ArraySize(rangeMax) && c < ArraySize(rangeMin)) { class="type">class="kw">double maxAlpha = class="num">0.2 * (rangeMax [c] - rangeMin [c]); class="type">class="kw">double minAlpha = class="num">0.0001 * (rangeMax [c] - rangeMin [c]); if (alpha [c] > maxAlpha) { alpha [c] = maxAlpha; } else if (alpha [c] < minAlpha) { alpha [c] = minAlpha; } } } } }