作为创建自动化交易系统新方法的自动机编程·进阶篇
(2/3)· 从概念到示例,看 SWITCH 技术如何让 MQL5 程序与算法严丝合缝
「状态机怎么把过去和未来切开」
自动机编程里,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 历史态就够绕晕。
<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> { <span class="keyword">class="kw">switch</span>(STATUS) { <span class="keyword">case</span> <span class="number">class="num">0</span>: <span class="comment">class=class="str">"cmt">//--- start state of the program</span> <span class="keyword">if</span>(T><span class="number">class="num">0</span> && T<<span class="number">class="num">100</span>) STATUS=<span class="number">class="num">1</span>; <span class="keyword">if</span>(T>=<span class="number">class="num">100</span>) STATUS=<span class="number">class="num">2</span>; <span class="keyword">if</span>(T<=<span class="number">class="num">0</span>) STATUS=<span class="number">class="num">3</span>; <span class="keyword">class="kw">break</span>; <span class="keyword">case</span> <span class="number">class="num">1</span>: <span class="comment">class=class="str">"cmt">//--- liquid</span> <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> <span class="comment">class=class="str">"cmt">// and calls of other nested automata A4, A5;</span> <span class="keyword">if</span>(T>=<span class="number">class="num">100</span> ) { 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>} <span class="keyword">if</span>(T<<span class="number">class="num">0</span>) { 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>} <span class="comment">class=class="str">"cmt">// logging transitions and actions when the condition is met.</span> <span class="keyword">class="kw">break</span>; <span class="keyword">case</span> <span class="number">class="num">2</span>: <span class="comment">class=class="str">"cmt">//--- gas</span> <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> <span class="comment">class=class="str">"cmt">// and calls of other nested automata A4, A5;</span> <span class="keyword">if</span>(T><span class="number">class="num">0</span> && T<<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>} <span class="keyword">if</span>(T<=<span class="number">class="num">0</span>) { 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>} <span class="comment">class=class="str">"cmt">// logging transitions and actions when the condition is met.</span> <span class="keyword">class="kw">break</span>; <span class="keyword">case</span> <span class="number">class="num">3</span>: <span class="comment">class=class="str">"cmt">//--- solid</span> <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> <span class="comment">class=class="str">"cmt">// and calls of other nested automata A4, A5;</span> <span class="keyword">if</span>(T><span class="number">class="num">0</span> && T<<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 换成你盯的货币对日线布林带宽,跑一周看状态切换次数是否和肉眼数的一致。
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。
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 标记的打印频次。外汇和贵金属杠杆高,这类重复触发在实盘可能放大回撤,先用策略测试器摸清概率再上。
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;