从头开始开发智能交易系统(第 8 部分):概念上的飞跃(基础篇)
📘

从头开始开发智能交易系统(第 8 部分):概念上的飞跃(基础篇)

第 1/3 篇

「EA 开发里的概念跃迁起点」

很多交易者写 MT5 智能交易系统,前七篇都卡在把指标信号翻译成下单指令,逻辑是线性的:指标金叉→买,死叉→卖。但真正拉开差距的,是意识到 EA 不该只复述人工规则,而要接管「状态」与「上下文」。 原文作者 Daniel Jose 在 2022 年 6 月 24 日发布的系列第 8 篇,阅读量 2 188、收藏 5,说明这类偏架构的内容受众窄但刚需。MT5 的 EA 生命周期里,OnTick 之外还有 OnTrade、OnTimer,忽略它们就会在滑点成交后状态错乱。 外汇与贵金属杠杆高、跳空频繁,纯信号驱动 EA 在重大数据发布时可能连续错单。先把「当前持仓从哪来、因何还在」写成可查询的状态机,再谈策略,才是这一节指向的跃迁。

◍ 在不动旧功能的前提下加新类

做 EA 时最烦的不是写新功能,而是加新功能要把之前写的逻辑推倒重来。我写了 20 多年 C++,踩过无数次这种坑:原本以为很小的改动,最后复杂度远超预估,只能删库重来。 我们的底线一直是——EA 能接收新代码,但不丢现有功能。做法很直接:只新建类、往上叠,不碰老类。这样老策略继续跑,新模块独立存在。 这一篇要‘退一步进两步’:先退一步,引入一个基于模板的窗口类来承载新信息结构;重构时保住目前所有功能不失效。下一篇再动 IDE 部分。外汇和贵金属波动剧烈,任何架构改动都先在策略测试器里回测验证再上实盘。

为什么要把 EA 对象结构推倒重来

当前智能交易系统的对象类布局已经能稳定跑起来,从既有结构图看,C_TemplateChart 与 C_SubWindow 的归属关系清晰,实盘验证中未出现崩溃或异常重绘。 但接下来要动一次结构性重构:新增一个独立类,并把 C_TemplateChart、C_SubWindow 的层级位置整体挪动。直接诱因是浮动窗口的实现方式塞不进承载资产数据的图表窗口,硬套只会让数据同步逻辑越写越乱。 这次改动不只是“看着顺眼”。结构美化背后是对底层通信路径的重写,新代码和旧版差异会非常大,复制老函数基本不可用。外汇与贵金属品种波动剧烈、点差跳变频繁,重构后务必在策略测试器用真实tick重跑一遍再上模拟盘。

「EA 初始化与消息系统的重构落点」

EA 初始化部分现在只用单个变量控制模板加载。关键改动在初始化高亮代码:若用户未改模板,它阻止浮动窗口被误销毁;一旦模板变更,EA 照常重启。这避开了切换时间帧时 EA 重启、浮动窗口回归原状、废弃对象堆积的麻烦。 内部消息系统删掉了一个附加变量,改为更贴合 MQL5 原生消息交换的写法,调用形态接近 OnChartEvent。好处是每个对象类能独立接收并处理 MT5 事件,类间隔离更彻底,EA 针对不同用户形态扩展时代价最小。 子窗口以前有个硬伤:EA 删不掉已建的子窗口,重开就新建一个并失控。修复方式是在支持文件里给 EA 建别名,EA 靠别名探测子窗口系统是否已加载,与具体文件名解耦。加载逻辑放到 C_TemplateChart 类里——若 EA 子窗口内无指标,类会自动删掉子窗口,需要时再建。 C_TemplateChart 现在扩展 C_SubWindow,并引入标准化请求串:Parameter_00(Parameter_01,Parameter_02,Parameter_03,Parameter_04)。目前五个参数顺序固定——1.TEMPLATE/ASSET,2.PERIOD(取自 ENUM_TIMEFRAME,如 1M~1MONTH),3.SCALE(0~5,0 最远 5 最近),4.WIDTH,5.HEIGHT(浮动窗口开关)。 GetCommand 解析里有个隐蔽漏洞:原代码不检查最大参数数。若用户塞了第六个参数,越界数据会污染状态。补一行「若期望末参是 HEIGHT 但实际不是,则忽略后续所有声明」即可堵住。 参数 1 可取 1M,2M,3M,4M,5M,6M,10M,12M,15M,20M,30M,1H,2H,3H,4H,6H,8H,12H,1D,1S,1MONTH;参数 2 范围 0~5,对应 CHART_SCALE。 浮动窗口由 C_ChartFloating 类维护,不用标准库 Control 类——后者像火箭筒打苍蝇,过重。该类仅建 4 个图形对象,创建函数只需模板名,新窗口默认略偏移防重叠;消息处理函数统一接管所有浮动窗事件,比在 EA 的 OnChartEvent 里堆逻辑稳得多。窗口锚点可被用户改,最小化/最大化位置各自记忆,互不影响。

MQL5 / C++
class="kw">input group "Window Indicators"
class="kw">input class="type">class="kw">string                user01 = "";                class=class="str">"cmt">//Subwindow indicators
class="kw">input group "WallPaper"
class="kw">input class="type">class="kw">string                user10 = "Wallpaper_01";     class=class="str">"cmt">//Used BitMap

◍ 把参数面板接进图表对象

这段初始化代码把前面几组外部输入直接喂给三个图表组件:背景图层、模板交易框、以及价位成交量柱。 input char user11 = 60; //背景透明度 0–100 input C_WallPaper::eTypeImage user12 = C_WallPaper::IMAGEM; //背景图类型 input int user20 = 1; //杠杆系数 input int user21 = 100; //止盈金额 input int user22 = 75; //止损金额 input color user23 = clrBlue; //价格线颜色 input color user24 = clrForestGreen; //止盈线颜色 input color user25 = clrFireBrick; //止损线颜色 input bool user26 = true; //是否日内交易 input color user30 = clrBlack; //成交量柱颜色 input char user31 = 20; //成交量柱透明度 0–100 OnInit 里先用静态变量 memSzUser01 缓存上次的 user01 字符串,只有发生变化才清模板并重载,避免每次刷新图表都重画。外汇与贵金属波动剧烈,user21/user22 写的 100/75 是金额而非点数,实盘杠杆下可能瞬间触损,须按账户体量重算。 C_TemplateChart Chart; C_WallPaper WallPaper; C_VolumeAtPrice VolumeAtPrice; 三个实例在文件作用域声明,OnInit 中分别 Init。最后直接调 OnTrade() 让首帧就把挂单线画出来,不用等第一笔成交流。

MQL5 / C++
class="kw">input class="type">char                        user11 = class="num">60;                class=class="str">"cmt">//Transparency(from class="num">0 to class="num">100)
class="kw">input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;  class=class="str">"cmt">//Chart background type
class="kw">input group "Chart Trader"
class="kw">input class="type">int                        user20  = class="num">1;                 class=class="str">"cmt">//Leverage factor
class="kw">input class="type">int                        user21  = class="num">100;               class=class="str">"cmt">//Take Profit(financial)
class="kw">input class="type">int                        user22  = class="num">75;                class=class="str">"cmt">//Stop Loss(financial)
class="kw">input class="type">class="kw">color                      user23  = clrBlue;           class=class="str">"cmt">//Price line class="type">class="kw">color
class="kw">input class="type">class="kw">color                      user24  = clrForestGreen;    class=class="str">"cmt">//Take Profit line class="type">class="kw">color
class="kw">input class="type">class="kw">color                      user25  = clrFireBrick;      class=class="str">"cmt">//Stop line class="type">class="kw">color
class="kw">input class="type">bool                       user26  = true;              class=class="str">"cmt">//Day Trade?
class="kw">input group "Volume At Price"
class="kw">input class="type">class="kw">color                      user30  = clrBlack;          class=class="str">"cmt">//Bar class="type">class="kw">color
class="kw">input class="type">char                       user31  = class="num">20;                class=class="str">"cmt">//Transparency(from class="num">0 to class="num">100 )
class=class="str">"cmt">//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_WallPaper     WallPaper;
C_VolumeAtPrice VolumeAtPrice;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
      class="kw">static class="type">class="kw">string   memSzUser01 = "";
      
      Terminal.Init();
      WallPaper.Init(user10, user12, user11);
      if (memSzUser01 != user01)
      {
            Chart.ClearTemplateChart();
            Chart.AddThese(memSzUser01 = user01);
      }
      Chart.InitilizeChartTrade(user20, user21, user22, user23, user24, user25, user26);
      VolumeAtPrice.Init(user24, user25, user30, user31);
      
   OnTrade();

常见问题

先不动旧类,单独建一个新类继承或组合原有结构,把新逻辑隔离在新增代码里,跑通后再逐步替换。
前期把消息处理和初始化耦合太死,后期加图表交互就难改;起步就用松散的消息系统分层,能少返工。
小布可基于你的品种页自动生成参数面板骨架并接进图表对象,你只需填交易逻辑,不用手写底层绑定。
把初始化从主循环拆出,单独做消息驱动的启动例程,只在收到首次报价事件后才建对象,能避卡死。
若原结构把参数写死在弹窗,需抽出一个图表对象层接参数接口;用组合方式加层,核心算价代码可一行不动。