射箭算法(Archery Algorithm, AA)(基础篇)
📘

射箭算法(Archery Algorithm, AA)(基础篇)

第 1/2 篇

射箭算法在 MT5 的落点

射箭算法(Archery Algorithm, AA)是一套面向 MetaTrader 5 的优化搜索逻辑,由 Andrey Dik 在 2025 年 4 月 30 日公开测试,社区页面记录曝光量为 987 次、互动 2 条。 该算法把参数空间类比成靶面,迭代过程像射箭一样不断逼近高适应度区域,而非暴力网格遍历。对做外汇或贵金属 EA 调参的交易者来说,这类方法在高波动品种上可能缩短寻优时间,但实盘前必须在 MT5 策略测试器里跑通,毕竟杠杆品种回撤失控的风险很高。 原文给出的结构只列了「概述 / 算法实现 / 测试结果」三段骨架,尚未展开代码层。你可以先在 MT5 里新建一个 EA 骨架,预留这三个函数区块,等后续小节补完再填射箭寻优内核。

「确定性还是随机:优化方法的两路分野」

优化在金融与交易中早已不是锦上添花,而是策略开发与改进的基础动作。它既能用来孵化新策略,也能给旧策略调参提效。但方法本身有条件地分成两类:走数学导数的确定性方法,和靠随机过程的随机方法。 像梯度下降这类确定性方法,用导数硬算最优解,严谨、可预测,适合把不同场景先模拟一遍。可一旦问题变成非线性或多变量,它的有效性可能断崖式下滑。这时候随机方法就顶上来了——靠随机过程在复杂约束里掏出能用的解,对波动市场尤其对口。 现实里两类常常混着用:确定性保底稳定条件,随机兜住变化条件,组合起来模型更皮实,预测质量上去的同时把风险压一压,这对投资管理很关键。 后面要聊的射箭算法(AA)是 Fatemeh Ahmadi Zeidabadi 等人 2022 年 2 月发的新随机类算法,靠随机选元素更新搜索空间里群体的位置生成准最优解。我们会拿标准目标函数跑一遍,跟已知算法比效率,看它怎么改写优化思路。外汇与贵金属市场高杠杆、高波动,任何优化结论都只是概率倾向,实盘前务必在 MT5 用历史数据自测。

◍ 射箭算法怎么落进 MT5 代码

射箭算法(AA)把每个候选解当成一名“射箭者”,用轮盘赌式概率挑一个目标成员,再按高斯扰动更新位置。训练强度参数 I 由 round(1 + rand(0,1)) 得出,取值 1 或 2 各占一半概率:I=1 时位置微调,I=2 时步幅放大,这直接决定了解空间探索的激进程度。 原始伪代码里群体用矩阵、成员用向量描述,但没说清矩阵具体操作。MT5 实现里把它拆成 S_AA_Agent 结构体加 C_AO_AAm 类,继承通用优化基类 C_AO。默认种群 50、继承概率 0.3,这两个值写在构造函数里,复制代码后第一件事就是调 SetParams 改掉。 Moving 方法首次调用时在边界内随机撒点,之后算最差适应度做归一化,填概率向量 P 和累积向量 C,按累积概率选伙伴射箭者。若随机值小于 inhProbab 就直接抄坐标,否则套高斯更新式。Revision 则盯全局最优和每个智能体自己的历史最优,越好的就写进 cPrev。 下面这段是结构定义与类骨架,逐行看:cPrev[] 存上一轮坐标,fPrev 存上一轮适应度;InitfPrev=-DBL_MAX 是因为还没算过,保证任何真实值都更优;C_AO_AAm 构造函数把 ao_link 指向原文地址,但你在 EA 里用不到,可删。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="keyword">class="kw">struct</span> S_AA_Agent
{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> cPrev []; <span class="comment">class=class="str">"cmt">// previous coordinates</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> fPrev;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// previous fitness</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">void</span> Init(<span class="keyword">class="type">int</span> coords)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ArrayResize</span> (cPrev, coords);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fPrev = -<span class="macro">DBL_MAX</span>;
&nbsp;&nbsp;&nbsp;&nbsp;}
};
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="comment">class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————</span>
<span class="keyword">class</span> C_AO_AAm : <span class="keyword">class="kw">public</span> C_AO
{
&nbsp;&nbsp;<span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">//--------------------------------------------------------------------</span>
&nbsp;&nbsp;~C_AO_AAm() { }
&nbsp;&nbsp;C_AO_AAm()
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;ao_name = <span class="class="type">class="kw">string">"AAm"</span>;
&nbsp;&nbsp;&nbsp;&nbsp;ao_desc = <span class="class="type">class="kw">string">"Archery Algorithm M"</span>;
&nbsp;&nbsp;&nbsp;&nbsp;ao_link = <span class="class="type">class="kw">string">"[MQL5官方文档]
&nbsp;&nbsp;&nbsp;&nbsp;popSize&nbsp;&nbsp; = <span class="number">class="num">50</span>;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// population size</span>
&nbsp;&nbsp;&nbsp;&nbsp;inhProbab = <span class="number">class="num">0.3</span>;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ArrayResize</span> (params, <span class="number">class="num">2</span>);

代理群初始化与游走阶段的代码落点

上面这段是某自适应代理算法类的收尾实现,核心是把种群规模 popSize 和继承概率 inhProbab 通过 params 数组对外暴露,再在 SetParams 里回写成员变量,方便在 MT5 输入框里直接调参而不碰源码。 Init 函数先调用 StandardInit 做范围校验,随后按 popSize 用 ArrayResize 拉起 agent 数组,并逐个 Init(coords) 把每个代理的坐标维度初始化好;如果 popSize 设成 50,就会精确生成 50 个 agent 实例。 Moving 里分两步走:revision 为 false 时,全体代理在 [rangeMin, rangeMax] 内均匀随机并吸附到搜索步长 grid 上,随后把 revision 置真;之后每轮才计算适应度最差值 F_worst 与概率向量 P、累积向量 C,进入带继承概率的筛选游走。外汇与贵金属市场波动剧烈、杠杆风险高,这类算法参数若直接接实盘,需先在历史数据上验证稳定性。 把 inhProbab 从默认 0.5 调到 0.8,代理更倾向沿用父代坐标,收敛可能更快但易陷局部极值;建议开 MT5 把下面代码贴进 EA 测试台,改两个数跑一遍观察种群分布。

MQL5 / C++
    params [class="num">0].name = "popSize";    params [class="num">0].val = popSize;
    params [class="num">1].name = "inhProbab"; params [class="num">1].val = inhProbab;
  }
  class="type">void SetParams()
  {
    popSize   = (class="type">int)params [class="num">0].val;
    inhProbab = params      [class="num">1].val;
  }
  class="type">bool Init(class="kw">const class="type">class="kw">double &rangeMinP  [], class=class="str">"cmt">// minimum search range
             class="kw">const class="type">class="kw">double &rangeMaxP  [], class=class="str">"cmt">// maximum search range
             class="kw">const class="type">class="kw">double &rangeStepP [], class=class="str">"cmt">// step search
             class="kw">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">class="kw">double  inhProbab; class=class="str">"cmt">//probability of inheritance
  S_AA_Agent agent [];
  class="kw">private: class=class="str">"cmt">//-------------------------------------------------------------------
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">bool C_AO_AAm::Init(class="kw">const class="type">class="kw">double &rangeMinP  [],
                     class="kw">const class="type">class="kw">double &rangeMaxP  [],
                     class="kw">const class="type">class="kw">double &rangeStepP [],
                     class="kw">const class="type">int    epochsP = class="num">0)
{
  if (!StandardInit(rangeMinP, rangeMaxP, rangeStepP)) class="kw">return class="kw">false;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  ArrayResize(agent, popSize);
  for (class="type">int i = class="num">0; i < popSize; i++) agent [i].Init(coords);
  class="kw">return true;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_AAm::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">//-------------------------------------------------------------------------
  class=class="str">"cmt">// Calculate probability vector P and cumulative probability C
  class="type">class="kw">double P [], C [];
  ArrayResize(P, popSize);
  ArrayResize(C, popSize);
  class="type">class="kw">double F_worst = DBL_MAX;
  class="type">class="kw">double sum = class="num">0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (a [i].f < F_worst) F_worst = a [i].f;
  }

常见问题

取决于是否固定随机种子与代理初始化方式;不固定种子时游走路径会变化,建议锁定种子再对比参数敏感性。
在品种价格区间内均匀撒点作初始代理,再叠加小幅扰动,能避免开局全挤在边界导致早收敛。
可以,小布能按你给的边界自动批量初始化代理群并输出收敛分布,你只需复核最终区间是否符合盘口逻辑。
步长过大会让代理反复跳过最优带,看连续代际适应值方差若居高不下就该下调步长系数。
30到80个代理通常够覆盖主要局部极值,少于20容易漏掉次级拐点,可先试50再按需加减。