作为创建自动化交易系统新方法的自动机编程·进阶篇
⚙️

作为创建自动化交易系统新方法的自动机编程·进阶篇

(2/3)· 从概念到示例,看 SWITCH 技术如何让 MQL5 程序与算法严丝合缝

含代码示例实战向 第 2/3 篇
很多交易者写 EA 时算法在脑中、代码在屏幕上,两者早跑偏了却没人核对。大程序只靠读文本查一致性基本不可能,逻辑漂移往往等到实盘异动才暴露。自动机编程正是冲着这种'写出来和想的不一样'来的。

「状态机怎么把过去和未来切开」

自动机编程里,STATE(状态)是唯一把系统过去和未来在时刻 t0 切开的量。它隐含了所有决定当前反应所需的过去输入信息,你写逻辑时不需要去翻历史序列,只要看「当前状态 + 输入动作」就够了。 输入动作最常见是向量,按意义和产生机制拆成两类:事件(event)和输入变量(input variable)。有限个状态配上有限个输入动作,就构成一个不带输出的有限自动机,它靠迁移函数改自己的状态。 加上输出动作后,自动机对输入的反应变成两件事:改状态、在输出口吐一个值,吐值的规则叫自动输出函数。设计复杂系统(比如盯盘风控)时,通常反过来从控制对象和市场事件集出发,先定状态和事件,再把每次请求映射成输入变量、每条命令映射成输出变量。 实际落地时,初始数据往往是一段行为文字描述,加一份从市场环境和控制对象涌进来的事件规格。先把控制状态建出来,再拿状态/事件/输入输出变量拼自动机,行为才能收敛。外汇和贵金属行情跳变频繁,这种状态切分能降低逻辑耦合,但市场高风险,状态迁移规则仍需回测验证。

自动机结构如何砍掉一堆标志变量

自动机程序的第一条硬规矩,是必须有一个且只有一个外层循环,包住全部逻辑。所有子逻辑都靠一个 switch 结构分发到具体 case,被调用时执行完对应动作就挂起,直到下一轮 tick 才继续——这个外层循环就是整个 EA 里唯一的循环体。 由此引出的第二个特征更实用:因为状态本身是多值的,传统 EA 里满屏的 TRUE/FALSE 中间标志(比如「条件已满足」旗标)基本可以删掉。原先要在不同地方手动置 TRUE 再费劲清 FALSE,现在只需改一个状态变量的值。 举一个可验证的现象:在 MT5 里写个最小自动机,外层 while/for 只跑一次 tick 分发,内部不用任何额外 bool 标志,仅靠 enum 状态切换。你会看到全局变量数明显下降,逻辑分支反而更清晰。外汇与贵金属波动随机,这种简化只降低代码复杂度,不提高胜率。 状态的本质就是「等待」:稳定态等事件,不稳定态等数值变化。任一时刻程序必处某一态,所以该态下的输出变量值完全确定——这意味着你随时能反推当前所有输出,不用追一堆标志的历史。 迁移函数直接嵌在自动机函数里,状态跳转和输出动作顺序始终可追踪。对交易者来说,开 MT5 用这种结构重写一遍旧 EA 的信号模块,就能直观比对标志变量减少后的维护成本。

◍ 用状态机把交易逻辑逼到无死角

把系统看成有限个状态的组合,是自动机编程的核心。以水为例,固态、液态、气态三类状态,在恒压下只受温度 t 驱动迁移;若再加压力 P 并引入交叉依赖,状态对变成 3×2=9 种迁移条件,箭头数正好等于 N²(N=3)。 数学上有个直白结论:非零起始的 N 状态系统,迁移条件总数恒为 N²。表里算过,变量一多就是指数级膨胀——选主系统变量时,漏一个维度后面全要补。 但实际写 EA 不必被数字吓住。按逻辑和意义筛一遍,50–95% 的迁移在真实行情里根本不会发生,状态量也能砍掉 60–95%。剩下的才是你要在代码里覆盖的硬分支。 下面这段 MT5 全局整型当多值标志的用法,把温度阈值映射成三相状态机,可直接抄进脚本改 T 为你关心的价格或指标值: 别把 N² 当必须填满的表格 组合学公式能算最大状态数,但真上线前先用业务逻辑删枝,否则 9 状态×9 变量模板光 MEGASTATUS 历史态就够绕晕。

MQL5 / C++
<span class="keyword">class="type">int</span> STATUS=<span class="number">class="num">0</span>; <span class="comment">class=class="str">"cmt">// a global integer is by all means always a variable !!! STATUS is a multi-valued flag</span>
<span class="comment">class=class="str">"cmt">//----------------------------------------------------------------------------------------------//</span>
<span class="keyword">class="type">int</span> start() <span class="comment">class=class="str">"cmt">// outer loop is a must</span>
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="kw">switch</span>(STATUS)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> <span class="number">class="num">0</span>:&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- start state of the program</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(T&gt;<span class="number">class="num">0</span> &amp;&amp; T&lt;<span class="number">class="num">100</span>) STATUS=<span class="number">class="num">1</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(T&gt;=<span class="number">class="num">100</span>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS=<span class="number">class="num">2</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(T&lt;=<span class="number">class="num">0</span>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS=<span class="number">class="num">3</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> <span class="number">class="num">1</span>:&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//---&nbsp;&nbsp;liquid</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// set of calculations or actions in this situation(repeating the 1st status -- a loop in automata-based programming) //</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// and calls of other nested automata A4, A5;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(T&gt;=<span class="number">class="num">100</span> )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ STATUS=<span class="number">class="num">2</span>; <span class="comment">class=class="str">"cmt">/* set of actions when transitioning, calls of other nested automata A2, A3;*/</span>}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(T&lt;<span class="number">class="num">0</span>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ STATUS=<span class="number">class="num">3</span>; <span class="comment">class=class="str">"cmt">/* set of actions when transitioning, calls of other nested automata A2, A3;*/</span>}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// logging transitions and actions when the condition is met.</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> <span class="number">class="num">2</span>:&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- gas</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// set of calculations or actions in this situation(repeating the 2nd status -- a loop in automata-based programming) //</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// and calls of other nested automata A4, A5;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(T&gt;<span class="number">class="num">0</span> &amp;&amp; T&lt;<span class="number">class="num">100</span>) { STATUS=<span class="number">class="num">1</span>; <span class="comment">class=class="str">"cmt">/* set of actions when transitioning, calls of other nested automata A2, A3;*/</span>}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(T&lt;=<span class="number">class="num">0</span>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { STATUS=<span class="number">class="num">3</span>; <span class="comment">class=class="str">"cmt">/* set of actions when transitioning, calls of other nested automata A2, A3;*/</span>}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// logging transitions and actions when the condition is met.</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> <span class="number">class="num">3</span>:&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- solid</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// set of calculations or actions in this situation(repeating the 3rd status -- a loop in automata-based programming) //</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// and calls of other nested automata A4, A5;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(T&gt;<span class="number">class="num">0</span> &amp;&amp; T&lt;<span class="number">class="num">100</span>) {STATUS=<span class="number">class="num">1</span>; <span class="comment">class=class="str">"cmt">/* set of actions when transitioning, calls of other nested automata A2, A3;*/</span>}

「用状态机切分价格区间」

把行情塞进有限状态机,比一堆 if-else 嵌套更不容易在 MT5 实盘里跑飞。下面这段用全局 status 记录当前所处区间,xmax / xmin 是你自己定义的边界,比如 ATR 通道的上下轨。 初始 status=0 时只做一件事:把 Y 记成当前 x,越上限切到 1、破下限切到 2。状态 1 里只要价格还高于 xmax 就持续刷新 Y=x,回落到 xmax 下方就把 Y 拉到 xmin,直到触碰 xmin 才翻状态 2——相当于把「突破—回踩—反转」的链条固化成代码路径。 MEGASTATUS 是外层调度器,A0(Y) 模板里 case 1 用 Y=1 到 Y=9 枚举了九种当前态,注释里留了嵌套调用 A2、A3 的口子。外汇与贵金属波动跳空频繁,状态边界建议用收盘价判定而非 tick,否则可能频繁误触发。 直接把下面代码贴进 MT5 脚本,先把 xmax、xmin 换成你盯的货币对日线布林带宽,跑一周看状态切换次数是否和肉眼数的一致。

MQL5 / C++
class="type">int status=class="num">0; class=class="str">"cmt">// at the beginning of the program we globally assign
class=class="str">"cmt">//------------------------------------------------------------------//
class="kw">switch(status)
  {
  case class="num">0: class=class="str">"cmt">//  start 
    Y=x;
    if(x>xmax)  {status=class="num">1;}
    if(x<xmin)  {status=class="num">2;}
    class="kw">break;
  case class="num">1: class=class="str">"cmt">//++++++++++++++++++++
    if(x>xmax)  Y=x;
    if(x<xmax)  Y=xmin;
    if(x<=xmin) {status=class="num">2; Y=xmin;}
    class="kw">break;
  case class="num">2: class=class="str">"cmt">//--------------------
    if(x<xmin)  Y=x;
    if(x>xmin)  Y=xmax;
    if(x>=xmax) {status=class="num">1; Y=xmax;}
    class="kw">break;
  }
class="type">int MEGASTATUS=class="num">0; class=class="str">"cmt">// at the beginning of the program we globally assign
class=class="str">"cmt">//---------------------------------------------------------------------//
class="type">void A0(class="type">int Y) class=class="str">"cmt">// automaton class="kw">template
  {
  class="kw">switch(MEGASTATUS)
    {
    case class="num">0:  class=class="str">"cmt">// start
      MEGASTATUS=Y;
      class="kw">break;
    case class="num">1: class=class="str">"cmt">// it was the past
      class=class="str">"cmt">// it became current, repeating
      if(Y=class="num">1) { class=class="str">"cmt">/*set of actions in this situation, calls of other nested automata A2, A3, ... */ } class=class="str">"cmt">// Loop//
      class=class="str">"cmt">// new current
      if(Y=class="num">2) { class=class="str">"cmt">/* set of actions in this situation */ }
      if(Y=class="num">3) { class=class="str">"cmt">/* set of actions in this situation */ }
      if(Y=class="num">4) { class=class="str">"cmt">/* set of actions in this situation */ }
      if(Y=class="num">5) { class=class="str">"cmt">/* set of actions in this situation */ }
      if(Y=class="num">6) { class=class="str">"cmt">/* set of actions in this situation */ }
      if(Y=class="num">7) { class=class="str">"cmt">/* set of actions in this situation */ }
      if(Y=class="num">8) { class=class="str">"cmt">/* set of actions in this situation */ }
      if(Y=class="num">9) { class=class="str">"cmt">/* set of actions in this situation */ }
      class=class="str">"cmt">// logging transitions and actions when the condition is met.
      class="kw">break;
    case class="num">2: class=class="str">"cmt">// it was the past

状态机里 Y 分支的写法与剪枝

把市场状态当成有限状态机来跑,核心是在每个 case 里对 Y=1 到 Y=9 做动作分发。下面这段是 X=2/3/4 三个历史态转入当前态后的标准分支骨架,Y 代表上一根 K 线或上一状态的特征编号。 注意注释里的坑:如果某些转移在逻辑上根本不可能发生(比如从状态 2 跳到 6 不存在),对应 Y 的 if 块直接留空即可,不用删掉模板。这样自动机规模会缩小,但模板完整性保留,后续接别的品种时改起来快。 外汇和贵金属波动受杠杆与消息面影响,状态转移概率随时在变,实盘使用前建议在 MT5 策略测试器用 2020—2023 年 H1 数据跑一遍,确认每个 Y 分支的触发次数非零或符合预期。 别把空分支当冗余 留空的 if(Y=6) 不是废代码,它是给状态图补边的占位。哪天你发现黄金在美盘常从 2 直跳 6,补进动作即可,不用重构整个 switch。

MQL5 / C++
case class="num">2: class=class="str">"cmt">// it was the past
				class=class="str">"cmt">// it has become current
				if(Y=class="num">1) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">2) { class=class="str">"cmt">/* set of actions in this situation */ } class=class="str">"cmt">//Loop//
				if(Y=class="num">3) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">4) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">5) { class=class="str">"cmt">/* set of actions in this situation */ }
				class=class="str">"cmt">// e.g. if the transition from class="num">2 to class="num">6 is in essence impossible or does not exist, do not write anything
				if(Y=class="num">6) { class=class="str">"cmt">/* set of actions in this situation */ }
				class=class="str">"cmt">// the automaton will then be reduced but the automaton class="kw">template shall be complete to count in everything
				if(Y=class="num">7) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">8) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">9) { class=class="str">"cmt">/* set of actions in this situation */ }
				class=class="str">"cmt">// logging transitions and actions when the condition is met.
				class="kw">break;
		case class="num">3: class=class="str">"cmt">// it was the past
				class=class="str">"cmt">// it has become current
				if(Y=class="num">1) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">2) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">3) { class=class="str">"cmt">/* set of actions in this situation */ } class=class="str">"cmt">//Loop//
				if(Y=class="num">4) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">5) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">6) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">7) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">8) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">9) { class=class="str">"cmt">/* set of actions in this situation */ }
				class=class="str">"cmt">// logging transitions and actions when the condition is met.
				class="kw">break;
		case class="num">4: class=class="str">"cmt">// it was the past
				class=class="str">"cmt">// it has become current
				if(Y=class="num">1) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">2) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">3) { class=class="str">"cmt">/* set of actions in this situation */ }
				if(Y=class="num">4) { class=class="str">"cmt">/* set of actions in this situation */ } class=class="str">"cmt">//Loop//

◍ 状态机里自循环的坑

上面这段 switch-case 把 X 取值 4、5、6 三种历史状态各自展开,每个分支里又对 Y 从 1 到 9 做了九路判断。注意 case 5 里 if(Y=5) 和 case 6 里 if(Y=6) 后面都标了 Loop——意思是当历史状态和当前状态编号撞车时,状态机不往外跳,而是在原地自循环。 这种自循环在 MT5 回测里很容易造成单根 K 线上重复触发同组动作。如果你在 Y=5 的括号里写了下单或改止损,那么 X 也等于 5 的那一根可能连续吃多次信号,成交笔数会偏离你的预期。 真要验证,把下面代码粘进 EA 的 OnTick,用 2023 年 EURUSD 的 M5 数据跑一遍,观察 Experts 日志里 Loop 标记的打印频次。外汇和贵金属杠杆高,这类重复触发在实盘可能放大回撤,先用策略测试器摸清概率再上。

MQL5 / C++
if(Y=class="num">5) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">6) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">7) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">8) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">9) { class=class="str">"cmt">/* set of actions in this situation */ }
class=class="str">"cmt">// logging transitions and actions when the condition is met.
class="kw">break;
case class="num">5: class=class="str">"cmt">// it was the past
class=class="str">"cmt">// it has become current
if(Y=class="num">1) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">2) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">3) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">4) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">5) { class=class="str">"cmt">/* set of actions in this situation */ } class=class="str">"cmt">//Loop//
if(Y=class="num">6) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">7) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">8) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">9) { class=class="str">"cmt">/* set of actions in this situation */ }
class=class="str">"cmt">// logging transitions and actions when the condition is met.
class="kw">break;
case class="num">6: class=class="str">"cmt">// it was the past
class=class="str">"cmt">// it has become current
if(Y=class="num">1) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">2) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">3) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">4) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">5) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">6) { class=class="str">"cmt">/* set of actions in this situation */ } class=class="str">"cmt">//Loop//
if(Y=class="num">7) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">8) { class=class="str">"cmt">/* set of actions in this situation */ }
if(Y=class="num">9) { class=class="str">"cmt">/* set of actions in this situation */ }
class=class="str">"cmt">// logging transitions and actions when the condition is met.
class="kw">break;
把状态核对交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 EA 状态机是否偏离你设定的算法路径,你只管调决策。

常见问题

通常指有限状态机,用状态集合加事件迁移描述程序行为,MQL5 里常用 switch 实现各状态分支。
程序膨胀后分支路径指数级增长,人工读代码无法覆盖所有迁移,需借状态表或自动机模型校验。
目前小布盯盘侧重状态诊断与偏离提示,框架编写仍需在 MQL5 编辑器中按 SWITCH 技术手搭,但可对照小布看到的运行态反推漏洞。
状态显式分离,新增事件只改对应 case,逻辑与算法文档可一一映射,降低隐性 bug 概率。
倾向适合,因状态清晰利于排查成交逻辑冲突,但高频下需注意每次状态切换的开销是否在容忍内。