作为创建自动化交易系统新方法的自动机编程·综合运用
⚙️

作为创建自动化交易系统新方法的自动机编程·综合运用

(3/3)·从问题建模到Shalyto执行建议,用状态机终结源码与算法脱节的隐性坑

偏理论进阶 第 3/3 篇
很多EA跑着跑着就偏离了当初设计的交易逻辑,因为程序文本和书面算法根本不是一回事。自动机编程把状态显式化,让代码和决策图对齐,后续排查不再靠脑补。这一篇把前两部分的方法落到综合运用的收口上。

◍ 状态机里 past 转 current 的九宫分支

在 MT5 的 EA 状态机写法里,case 7/8/9 代表「上一根 K 线还是 past,这一根已变成 current」的过渡态。每个 case 内部用 Y=1 到 Y=9 的九路 if 去匹配当前市场情境,Y 的取值直接决定该情境下要触发的动作集合。 注意 case 7 里 Y=7 后面标了 //Loop//,意味着状态 7 自转到自身时仍走同一套动作;case 8 则是 Y=8 处成环。这种结构让你能在 Past→Current 切换瞬间,不丢日志地记录转移与动作,外汇与贵金属波动快,这类留痕对复盘高风险的假突破有帮助。 下面这段是原文给出的裸骨架,逐行看:case 7 先注释说明「曾是 past 现已 current」,随后 9 个 if(Y=n) 都是待填的动作块,最后 break 防穿透;case 8、case 9 同理只是循环标记位不同。 开 MT5 新建 EA 时,把 /* set of actions */ 换成具体下单或改止损语句,就能跑通这套九宫判定。

MQL5 / C++
case class="num">7: 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 */ }
      if(Y=class="num">7) { class=class="str">"cmt">/* set of actions in this situation */ } class=class="str">"cmt">//Loop//
      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">8: 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 */ }
      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 */ } class=class="str">"cmt">//Loop//
      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">9: 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 */ }

用状态机把播放器逻辑拆成可验证分支

上面这段 MQL5 把媒体播放器的行为完全塞进一个 switch(STATUS) 状态机里,一共定义了 0 到 5 共 6 个主状态。每个 case 内部只处理当前状态下允许的 Event 转移,比如 case 2「Playing」里碰到 Event==2 就回 STATUS=0,碰到 Event==5 就进 STATUS=5,边界清晰,不会出现乱跳。 注意 case 3 和 case 4 在调用 z3()/z4() 之后直接用花括号把 STATUS 写死成 2,意味着快进、快退完成自动回到播放态,而不是停在过渡态。这种写法在 EA 里做订单状态管理时很实用:把「挂单中 / 已成交 / 部分平仓」也映射成数字常量,比满屏 if-else 好查。 外汇和贵金属波动大、滑点随机,任何状态切换逻辑都建议在策略测试器里用不同点差预设跑一遍,确认事件丢失时不会卡死在某个非 0 状态。你可以直接把这段 switch 骨架复制到自己的指标脚本,把 z1()~z6() 换成对应的图表绘制或报警函数,MT5 里编译就能看状态流转。

MQL5 / C++
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">//Loop//
class=class="str">"cmt">// logging transitions and actions when the condition is met.
class="kw">break;
   }
   MEGASTATUS=Y;
}
class="kw">switch(STATUS)
  {
   case class="num">0: class=class="str">"cmt">//--- "Ready"
     if(Event == class="num">3) { STATUS = class="num">3; } class=class="str">"cmt">//«>>» button pressed
     if(Event == class="num">6) { STATUS = class="num">1; } class=class="str">"cmt">//Audio file not found 
     if(Event == class="num">1) { STATUS = class="num">2; } class=class="str">"cmt">//«PLAY» button pressed

     z1();  class=class="str">"cmt">// Set the indicator to the initial state
     class="kw">break;
   case class="num">1: class=class="str">"cmt">//--- "No Track"
     z6();  class=class="str">"cmt">// Give the «No Track» message
     class="kw">break;
   case class="num">2: class=class="str">"cmt">//--- "Playing"
     if(Event == class="num">4) { STATUS = class="num">4; } class=class="str">"cmt">//«<<» button pressed
     if(Event == class="num">5) { STATUS = class="num">5; } class=class="str">"cmt">//«PAUSE»( | | ) button pressed
     if(Event == class="num">3) { STATUS = class="num">3; } class=class="str">"cmt">//«>>» button pressed
     if(Event == class="num">2) { STATUS = class="num">0; } class=class="str">"cmt">//«STOP» button pressed
     z2(); class=class="str">"cmt">// Playing
     class="kw">break;
   case class="num">3: class=class="str">"cmt">//--- "Fast-Forward"
     z3();  class=class="str">"cmt">// Next track
     { STATUS=class="num">2; }
     class="kw">break;
   case class="num">4: class=class="str">"cmt">//--- "Rewind"
     z4(); class=class="str">"cmt">// Previous track
     { STATUS=class="num">2; }
     class="kw">break;
   case class="num">5: class=class="str">"cmt">//--- "Pause"
     if(Event == class="num">5) { STATUS = class="num">2; } class=class="str">"cmt">//«PAUSE» button pressed
     if(Event == class="num">1) { STATUS = class="num">2; } class=class="str">"cmt">//«PLAY» button pressed
     if(Event == class="num">2) { STATUS = class="num">0; } class=class="str">"cmt">//«STOP» button pressed
     if(Event == class="num">3) { STATUS = class="num">3; } class=class="str">"cmt">//«>>» button pressed
     if(Event == class="num">4) { STATUS = class="num">4; } class=class="str">"cmt">//«<<» button pressed
     z5(); class=class="str">"cmt">//Pause
     class="kw">break;
  }

「Shalyto 给自动化交易项目文档的硬性拆分」

做 MT5 上的状态机式交易模块,文档不能拍脑袋写。A.A. Shalyto 在《Logic Control》里把项目拆成固定几块,核心思路是:每个自动机都要有语言描述、链接图、迁移图三件套,少一份后面调试就抓瞎。 链接图左侧必须列清数据来源、输入变量全名、事件全名,以及像 Y8 == 6 这种谓词——意思是第 8 号自动机跑到第 6 态时该谓词置一,用来做跨机联动。右侧则放输出变量 z、生成事件和数据接收端。 用 OOP 就补一张类图,主类逐方法写清楚和自动机怎么搭。复杂算法单开 Computational Algorithms 节,用数学描述框定 x/z 在计算中的角色。最后 Implementation 要给同构模板,Conclusion 只写优劣和改进方向,不吹收益。 外汇/贵金属杠杆高,这类状态机逻辑若直接挂实盘,一处谓词写错可能瞬间放大亏损,上 MT5 前务必用策略测试器跑通迁移图。

◍ 自动机编程的演化想象

把自动机编程当成 EA 开发里的常规手段,目前看更像一种方法论试探,而不是 MT5 里能直接勾选的开关。想在自家策略里验证,得先把手写状态机改成可组合的自动机模块,否则只是换了个说法包装旧逻辑。 原文作者设想过一条演化路径:这类写法逐步变成编程标准,甚至倒逼出 MetaQuotes Language 6 与 MetaTrader 6,把复杂决策下沉到自动机层。这条路径目前没有任何官方路线图支撑,属于个人推演,外汇与贵金属交易者拿去用须清楚高杠杆下的试错成本。 若你真打算试,建议先在策略测试器里跑一个最小自动机原型,对比同逻辑手写 EA 的订单触发一致性。能跑通再谈扩展,别直接信「无亏损 EA 可实现」这类表述——市场结构变了,历史回测再干净也只是概率样本。

光读源码为什么救不了复杂 EA

做 MT5 上的 EA 或指标时,很多人以为把源码下下来逐行看就能搞懂逻辑。实际碰过大项目就知道,代码量一涨,理解成本不是线性加,而是接近指数级往上蹦——几千行编制不良的 MQL5,核心设计全埋在订单循环和数组下标里,光靠眼睛扫根本捞不出架构。 经历过大型重构的人都有过那种感觉:面对一堆能编译、但结构乱的源码,找不到原作者聊就基本抓瞎。尤其用相对低阶写法堆出来的程序,主要决策散落在细节中,这时候接口说明和架构图比 .mq5 文件本身更值钱。 所以后来才有人尝试把源码和高阶文档绑在一起。如果你没赶上项目早期,又缺当初开发者的口述或能筛出架构决定的文档,去啃那种“史前” MQL5 代码,大概率就是最头疼的事——不是看不看得懂语法,是还原不了他当初为什么这么写。

「为什么多数 EA 连设计文档都没有」

打开 MT5 里那些从论坛扒来的 EA,你常会发现一个现象:能跑,但完全看不出作者当初怎么想的。硬件出厂会附带长年可读的详细设计说明,软件却普遍缺失——不是因为不需要,而是开发和生产在代码世界里往往是同一拨人,文档写粗一点没人当场惩罚他。 更现实的原因是,软件“软”,改起来成本低,程序员又普遍讨厌写东西。经验上看,连数学功底很好的新手也几乎写不出逻辑严谨的设计文档:同一个灯泡,他能一会儿写 bulb、一会儿写 Lamp,大小写随意换。编译器不报错,文档就更加没人管。 这股风气蔓延到系统级项目后,结果就是“作者不在就没人敢动”。限制信息不落文档,开发者反而变得不可替代。早期大型机时代反而没有这问题——那时跑错一次要等一天重算,逼着人先设计再写码。现在技术宽松了,认真做设计的反而少了。外汇与贵金属 EA 本就高杠杆高风险,拿这种无文档裸奔的程序上实盘,踩坑概率只会更高。

◍ 状态机流派的书单与项目源

把交易逻辑写成有限状态机(FSM)这条路,国内资料少,俄系学派倒是成体系。A.A. Shalyto 团队长期在 ifmo.ru 维护一套「基于自动机的编程」教材与工程样例,从电梯控制、核反应堆逻辑到咖啡机状态流都有,对写 MT5 EA 的状态切换模块有参照价值。 他们公开了十余本 PDF 与多个建模项目,例如《Automata-Based Programming》《Switch-Technology》以及 ATM 建模、无人驾驶自动机设计等。站点未提供与我们价格行为定义直接对应的示例,但状态图(flow graph / transition graph)的画法可照搬进订单生命周期管理。 想验证可用性,直接抓《Programming in...1 Minute》里的极简状态模板,改造成「挂单—持仓—平仓」三态切换,在 MT5 策略测试器跑一轮 EURUSD 的 M15 数据,外汇与贵金属杠杆高,回测盈利不代表实盘概率占优。

把工具请下神坛

一个标准三阶魔方的可还原状态数是 (8!×3^8−1)×(12!×2^12−1)/2 = 43,252,003,274,489,856,000,若再把中心块朝向算进去还要再乘 2048 倍,逼近 8.86×10^22。外汇与贵金属市场的连续变量虽没这么夸张,但用基于状态机的编程范式,往往能在 100–200 步内把一类博弈问题收敛到可解。 这类思路和 α-β 剪枝的国际象棋引擎(如 Rybka)是同一路数:你永远不知道对手下一手怎么出,EA 之间也在互相拆招。市场高风险,任何范式都只提高概率,不担保结果。 真要把它用起来,别神化现成库或某篇神文。开 MT5 自己写个最小状态机,接实盘tick跑一遍,比读十篇隐喻更有用。

把状态巡检交给小布盯盘
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到EA状态迁移是否卡死在异常节点,你只管判断该不该人工干预。

常见问题

按事件-状态二维矩阵逐格核对,并用Shalyto建议的正式化记录锁定每一次迁移,避免靠记忆补逻辑。
可以,小布盯盘的品种页内置了状态节点巡检,能提示EA是否停留在非预期自动机状态,省去你翻日志。
源码是算法在开发者脑内二次转译后的产物,文本与原始设计常不一致,只读代码会忽略掉未落盘的隐性决策。
从简单条件分支到多事件驱动的复杂决策体系都适用,复杂度越高,状态显式化带来的可维护性收益越明显。
对照书面算法做一致性testing,并按本篇附言与总结归档设计文档,确保离职或交接时逻辑不丢失。