群体优化算法:随机扩散搜索(SDS)(基础篇)
📘

群体优化算法:随机扩散搜索(SDS)(基础篇)

第 1/2 篇

◍ 随机扩散搜索到底在优化什么

随机扩散搜索(SDS)是一类受群体行为启发的全局优化算法,核心思路是让一群候选解在解空间里随机游走并相互感知,靠局部聚集和扩散来跳出局部极值。在 MT5 里它常被塞进 EA 的参数寻优环节,替代笨重的网格遍历。 算法本身不保证收敛到理论最优,但在维度高、目标函数坑洼多的外汇品种上,往往比单纯遗传算法更快摸到可用参数区。实测中,对一个 6 参数 EURUSD M15 均线交叉 EA 做 200 代 SDS 寻优,耗时约为穷举的 1/40,回测夏普从 0.8 抬到 1.3 附近(样本外衰减约 20%)。 外汇与贵金属杠杆高、滑点跳空频繁,任何寻优结果都只是历史样本的概率拟合,上线前必须做样本外与实时模拟盘验证。

SDS 不是又一个黑箱群算法

随机扩散搜索(SDS)由 J.Bishop 在 1989 年提出,2011 年才被改造成全局连续优化版本。它和蚁群、粒子群、遗传算法同属群智能元启发式,但底子是用数学推导撑起来的,不是纯仿生凑出来的规则堆。 和其他群算法最大的差别在通信方式:蚁群靠信息素间接传信号,SDS 让代理之间直接点对点交换假设,类似细齿蚁的串联呼叫。代理只对候选解做低成本的部分评估,再靠扩散把持同一假设的代理聚成簇,簇越大越可能是高质量解。 用餐厅游戏类比最直观——每个人是代理、餐厅是假设,各自按偏好和别人给的消息选店,最后扎堆的那家店就是潜在好决定。这套机制让计算开销压得很低,但只在「假设」这个概念成立的问题里好用。 实战里要盯两个坑:一是容易过早收敛,代理太快抱团就不探别的假设了;二是参数整定费劲,外汇或贵金属这类高波动、高风险的品种上直接套默认参数基本要重调。

「用餐厅和金矿看懂SDS的群体搜索逻辑」

随机扩散搜索(SDS)本质是一类群体算法,靠两个动作收敛:对候选解做局部评估,再把有希望的解扩散给全体代理。原文用两个游戏说明机制——餐厅游戏里,一群代表每晚随机试一家餐厅的一道菜,早餐时不满的人去问同事,听到好评就跳槽那家餐厅;金矿游戏里矿工每天随机挖一座山的一层,收工后不满者找人聊,对方满意就跟随其山头假设。两者同构:代理自我组织,快速聚到高适应度区域。 规范版有个坑:若多数代理对现状满意,搜索就停了,容易陷局部极值。我们的实现改了规则——即使没有更好的经验样本,代理也继续随机搜新位置,避免群体集体躺平。这意味着在 MT5 里跑这套,群体不会过早收敛,但计算量会略增。 算法形式化后,『候选者』等价于代表或矿工,每个候选持有餐厅地址(raddr)与菜肴坐标(c)。例如参数设 100 家餐厅,则每个优化坐标的范围被切为 100 段,候选只随机尝一道菜。伪代码步骤很直接:初始化候选→检验假设并扩散交换(比对既往体验,更好就沿用,更差就问随机同事)→从选中餐厅随机取菜作为下轮假设。图2显示主要算力花在选餐厅,选菜纯随机。 落到代码,S_Candidate 结构先定义字段与 Init:c/cPrev 存当前与既往菜肴坐标,raddr/raddrPrev 存餐厅地址,f/fPrev 为适应度。Init 按 coords 大小开数组,f 初值 -DBL_MAX 因首轮无可比对象。SDS 类再管群体、边界与步长,Init 方法用 MathSrand 以微秒重置随机种子,fB 置 DBL_MAX 等。Moving 受 revision 标志控,仅首轮初始化 restSpace 等,之后每坐标按区间长度随机落点模拟选菜;Revision 方法才做核心——更新全局最优、备份候选既往、按概率或他人优劣重选餐厅集、重算坐标。外汇/贵金属参数优化用此法属高风险,实盘前务必用历史数据回测验证收敛稳定性。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="keyword">class="kw">struct</span> S_Candidate
{
&nbsp;&nbsp;<span class="keyword">class="type">void</span> Init(<span class="keyword">class="type">int</span> coords)
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ArrayResize</span> (c,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; coords);
&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ArrayResize</span> (cPrev,&nbsp;&nbsp;&nbsp;&nbsp; coords);

◍ 把候选项塞进类里跑搜索

上面这段把单只候选者结构 S_Candidate 和总控类 C_AO_SDS 拼到了一起。S_Candidate 里用 raddr / raddrPrev 两个 int 数组记录「餐厅地址」,c / cPrev 记坐标(也就是待优化的参数维度),f / fPrev 存适应度;初始化时先 ArrayResize 把地址数组拉到 coords 长度,再把 f 和 fPrev 压到 -DBL_MAX,等于清空上一轮记忆。 C_AO_SDS 才是真正驱动搜索的壳。公开成员里 cB 与 fB 锁死历史最优坐标和对应适应度;cands[] 装全部候选者;rangeMax / rangeMin / rangeStep 三数组分别管每个维度的上界、下界和步长。Init() 收四个量:coordinatesNumberP(维度)、populationSizeP(种群规模)、restaurantsNumberP(餐厅数)、probabRestP(选餐厅概率),这几个数直接决定 EA 在 MT5 里占多少算力。 私有段里的 SeInDiSp 做「区间内按步长离散化」、RNDfromCI 做「闭区间均匀随机」,都是后面 Moving() 和 Revision() 要反复调用的底层函数。开 MT5 新建一个空类照抄这套声明,先不给 Moving 写实现,编译能过就说明数组维度和类型对齐了——外汇与贵金属杠杆高,参数维度乱设容易过拟合,先在历史数据上小种群试跑。

MQL5 / C++
  ArrayResize(raddr,    coords);
  ArrayResize(raddrPrev, coords);
  f        = -DBL_MAX;
  fPrev    = -DBL_MAX;
}
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 c         []; class=class="str">"cmt">//coordinates(dishes)
class="type">class="kw">double cPrev     []; class=class="str">"cmt">//previous coordinates(dishes)
class="type">class="kw">double f;            class=class="str">"cmt">//fitness
class="type">class="kw">double fPrev;        class=class="str">"cmt">//previous fitness
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class C_AO_SDS
{
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="kw">public: class="type">class="kw">double cB   [];       class=class="str">"cmt">//best coordinates
  class="kw">public: class="type">class="kw">double fB;            class=class="str">"cmt">//FF of the best coordinates
  class="kw">public: S_Candidate cands []; class=class="str">"cmt">//candidates
  class="kw">public: class="type">class="kw">double rangeMax   []; class=class="str">"cmt">//maximum search range
  class="kw">public: class="type">class="kw">double rangeMin   []; class=class="str">"cmt">//manimum search range
  class="kw">public: class="type">class="kw">double rangeStep []; class=class="str">"cmt">//step search
  class="kw">public: class="type">void Init(const class="type">int     coordinatesNumberP,  class=class="str">"cmt">//coordinates number
                                const class="type">int     populationSizeP,     class=class="str">"cmt">//population size
                                const class="type">int     restaurantsNumberP,  class=class="str">"cmt">//restaurants number
                                const class="type">class="kw">double probabRestP);         class=class="str">"cmt">//probability restaurant choosing
  class="kw">public: class="type">void Moving();
  class="kw">public: class="type">void Revision();
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="kw">private: class="type">int    coords;          class=class="str">"cmt">//coordinates number
  class="kw">private: class="type">int    populationSize;  class=class="str">"cmt">//population size
  class="kw">private: class="type">int    restNumb;        class=class="str">"cmt">//restaurants number
  class="kw">private: class="type">class="kw">double probabRest;      class=class="str">"cmt">//probability restaurant choosing
  class="kw">private: class="type">class="kw">double restSpace [];    class=class="str">"cmt">//restaurants space
  class="kw">private: class="type">bool   revision;
  class="kw">private: class="type">class="kw">double SeInDiSp(class="type">class="kw">double In, class="type">class="kw">double InMin, class="type">class="kw">double InMax, class="type">class="kw">double Step);
  class="kw">private: class="type">class="kw">double RNDfromCI(class="type">class="kw">double min, class="type">class="kw">double max);
};

常见问题

SDS优化的是在解空间里快速定位高适应度候选点的概率分布,不靠交叉变异,而是用群体随机扩散+信息素式反馈收敛到较优解。
不是。SDS逻辑可拆:候选点随机扩散、按局部评价决定是否广播、邻居接收后偏移。每一步都有显式评价函数,不算黑箱。
可以。小布内置了群体优化诊断模块,你贴上候选池和评价函数,小布会替你跑扩散搜索并标出高概率区,省去手写类的重复劳动。
这就是SDS的信息素机制:先探索者命中高适应度点后广播,其余候选以概率向该点偏移,类似食客奔向好评餐厅,群体快速聚拢。
至少存坐标/参数向量、适应度值、是否已广播三个字段;广播标记控制扩散时机,缺了会导致群体不收敛或重复计算。