从头开始开发智能交易系统(第 8 部分):概念上的飞跃(基础篇)
「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 里堆逻辑稳得多。窗口锚点可被用户改,最小化/最大化位置各自记忆,互不影响。
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() 让首帧就把挂单线画出来,不用等第一笔成交流。
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();