自动机编程:EA逻辑全新范式
用状态切换替代杂乱标志,构建可靠交易系统
传统EA开发的逻辑困境
绝大多数MQL4/5交易者写EA时,习惯在脑海中构思逻辑后直接编码,随后才补一份粗略文档。开发者常引入大量中间布尔标志(如 bool flagA=true)来表示“条件已满足”,并在程序其他角落费力重置为false。当程序规模扩大、逻辑分支增多,标志之间耦合混乱,无人能单凭文本核对程序是否忠实于原算法。
更深层问题在于:书面算法与心中算法分离,最终代码与二者皆可能不一致。若开发者离职或生病,后来者面对数千行夹杂全局标志的代码,极难定位逻辑缺陷——这里指的并非语法错误或API误用,而是业务规则的错误实施。调试器虽能监视变量,却无法自动揭示“系统当前处于何种业务状态”。
自动机编程的核心理念
1991年俄罗斯学者A.A. Shalyto提出“自动机编程”:将程序逻辑视作有限自动机,系统在任何时刻处于唯一明确状态(STATE),状态由多值控制变量(如 int STATUS)区分,而非多个二进制标志。输入动作(事件或变量)触发状态间迁移,迁移时执行输出动作并写日志。
数学上,若用n个二进制标志,组合可达2^n,其中许多组合在业务中无意义,成为“非可视化状态”(unvisualized)。EA若进入此类状态,表现等同死机:不处理缺口、负余额、错单等。自动机编程要求设计阶段枚举所有必需状态,用单一多值变量标识,从而根除未指定状态。
该方法包含三要素:有限自动机数学模型、图形化迁移图、由图到代码的同构映射(SWITCH结构)。因主要用switch实现,也称SWITCH技术。它把“等待某输入”作为状态天然属性,稳定状态持续响应,不稳定状态完成迁移即离开。
显式状态编程与反应式系统
自动机方案特别适合事件驱动的反应式系统——EA正是典型:按行情与订单事件速度与环境交互。显式状态编程将输出动作挂到迁移弧或节点(混合Moore/Mealy机),逻辑从事件处理函数抽离,形成互连自动机系统,独立于具体交易平台接口。
自动机以三位一体使用:规格设计用迁移图、实施保留在代码switch中、运行依据自动机日志审计。日志自动记录每次状态跃迁与动作,大规模复杂逻辑下,每份日志如同脚本,可复盘真实活动实体,而非静态图片。
基础概念:状态与输入动作
STATE的核心属性是将未来与过去分隔:当前状态含一切决定反应的过往信息,无需查历史。输入动作常为向量,分事件(如OnTick收到新柱)和输入变量(如账户净值)。不带输出的自动机对输入作反应改状态;带输出自动机另经输出函数发动作。
设计复杂系统从控制对象与外部事件集出发:为每请求配输入变量,每命令配输出变量,基于控制状态构建自动机。实际中50–95%理论迁移因业务不可能而剔除,状态数亦可减60–95%,大幅降低描述难度。
MQL5代码模板与实例
最简骨架:全局 int STATUS=0;外层函数唯一循环内 switch(STATUS),各case检查迁移条件并改STATUS。如下为水三态模型(温度T驱动),展示循环即“重复当前状态”的自动机-loop。
int STATUS=0; // a global integer is by all means always a variable !!! STATUS is a multi-valued flag
//----------------------------------------------------------------------------------------------//
int start() // outer loop is a must
{
switch(STATUS)
{
case 0: //--- start state of the program
if(T>0 && T<100) STATUS=1;
if(T>=100) STATUS=2;
if(T<=0) STATUS=3;
break;
case 1: //--- liquid
// set of calculations or actions in this situation (repeating the 1st status -- a loop in automata-based programming) //
// and calls of other nested automata A4, A5;
if(T>=100 ) { STATUS=2; /* set of actions when transitioning, calls of other nested automata A2, A3;*/}
if(T<0) { STATUS=3; /* set of actions when transitioning, calls of other nested automata A2, A3;*/}
// logging transitions and actions when the condition is met.
break;
case 2: //--- gas
// set of calculations or actions in this situation (repeating the 2nd status -- a loop in automata-based programming) //
// and calls of other nested automata A4, A5;
if(T>0 && T<100) { STATUS=1; /* set of actions when transitioning, calls of other nested automata A2, A3;*/}
if(T<=0) { STATUS=3; /* set of actions when transitioning, calls of other nested automata A2, A3;*/}
// logging transitions and actions when the condition is met.
break;
case 3: //--- solid
// set of calculations or actions in this situation (repeating the 3rd status -- a loop in automata-based programming) //
// and calls of other nested automata A4, A5;
if(T>0 && T<100) {STATUS=1; /* set of actions when transitioning, calls of other nested automata A2, A3;*/}
if(T>=100) {STATUS=2; /* set of actions when transitioning, calls of other nested automata A2, A3;*/}
// logging transitions and actions when the condition is met.
break;
}
return(0);
}
延迟继电器示例:用STATUS区分拾取/释放,输入x与阈值比较,输出Y跟随,无额外标志。
int status=0; // at the beginning of the program we globally assign
//------------------------------------------------------------------//
switch(status)
{
case 0: // start
Y=x;
if(x>xmax) {status=1;}
if(x<xmin) {status=2;}
break;
case 1: //++++++++++++++++++++
if(x>xmax) Y=x;
if(x<xmax) Y=xmin;
if(x<=xmin) {status=2; Y=xmin;}
break;
case 2: //--------------------
if(x<xmin) Y=x;
if(x>xmin) Y=xmax;
if(x>=xmax) {status=1; Y=xmax;}
break;
}
音频播放器自动机:6状态(就绪、无音轨、播放、快进、倒回、暂停),按钮事件驱动迁移,输出函数控界面。理论36迁移仅15真实存在。
switch(STATUS)
{
case 0: //--- "Ready"
if(Event == 3) { STATUS = 3; } //«>>» button pressed
if(Event == 6) { STATUS = 1; } //Audio file not found
if(Event == 1) { STATUS = 2; } //«PLAY» button pressed
z1(); // Set the indicator to the initial state
break;
case 1: //--- "No Track"
z6(); // Give the «No Track» message
break;
case 2: //--- "Playing"
if(Event == 4) { STATUS = 4; } //«<<» button pressed
if(Event == 5) { STATUS = 5; } //«PAUSE»( | | ) button pressed
if(Event == 3) { STATUS = 3; } //«>>» button pressed
if(Event == 2) { STATUS = 0; } //«STOP» button pressed
z2(); // Playing
break;
case 3: //--- "Fast-Forward"
z3(); // Next track
{ STATUS=2; }
break;
case 4: //--- "Rewind"
z4(); // Previous track
{ STATUS=2; }
break;
case 5: //--- "Pause"
if(Event == 5) { STATUS = 2; } //«PAUSE» button pressed
if(Event == 1) { STATUS = 2; } //«PLAY» button pressed
if(Event == 2) { STATUS = 0; } //«STOP» button pressed
if(Event == 3) { STATUS = 3; } //«>>» button pressed
if(Event == 4) { STATUS = 4; } //«<<» button pressed
z5(); //Pause
break;
}
项目文档与实施建议
Shalyto强调设计文档:简介、问题描述、类图(若OOP)、每自动机三件套(语言描述+链接图+迁移图)。链接图左列输入源/变量/事件/谓词,右列输出变量/生成事件/接收装置。计算算法单独章节,实施章节给同构模板。
对比传统“代码即文档”的错觉:源码量增长,理解难度指数升。无高阶文档时,重构史前代码极难。自动机法因图代码同构,文档天然不脱节,新成员按迁移图即可懂逻辑。
总结与对交易者的价值
自动机编程不是语法糖,而是思维转换:从“用变量记发生过什么”到“系统现在是什么态,允许去哪”。它迫使你穷举市况,规避未可视化状态,特别契合EA需常年无人值守运行的要求。虽学习曲线陡,但一旦建模完成,扩展新功能只需加case,不破旧逻辑。
原文作者畅想未来MQL6原生支持自动机,当前我们完全可用MQL5手写switch实现。外汇事件变量虽多,但合理降维后百步内可解,如同象棋引擎面对未知对手。掌握此法,是迈向严谨量化交易工程的第一步。