开发多币种 EA 交易(第 17 部分):为真实交易做进一步准备·综合运用
(3/3)·从几GB优化库到247字符启动串,真实账户跑EA前必做的最后一步
◍ 把参数组导出成可复用头文件
在 MT5 里做批量回测时,常要把几十个保存的 pass 参数固化成代码。上面这段逻辑就是从一个参数组库里把名称与初始化串抽出来,自动拼成 enum 加静态数组,再写进 ExportedGroupsLibrary.mqh,下次直接 #include 就能用,不必每次手填。
核心动作在 ExportParams:先拼枚举头,用 FOREACH 把 p_names 逐条写成 GL_PARAMS_0, // xxx 这种格式;随后开 s_params[] 数组,对每条初始化串做三次清洗——删回车、换行变空格、双引号加反斜杠转义,避免写入文件后编译报错。
实际调用时,input string passes_ 里塞了 20 个逗号分隔的 ID(802150 到 802173 之间,缺了 802163),OnInit 里一句 CGroupsLibrary::Export(passes_) 触发导出,OnTick 直接 ExpertRemove 让 EA 跑完初始化就退,纯做代码生成用。
外汇与贵金属测试环境参数导出涉及历史数据质量,不同经纪商 tick 粒度差异可能导致回测结果偏离实盘,属高风险操作,导出后务必在策略测试器里抽查几个 pass 的成交逻辑。
class=class="str">"cmt">//| initialization strings in the form of MQL5 code | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CGroupsLibrary::ExportParams(class="type">class="kw">string &p_names[], class="type">class="kw">string &p_params[]) { class=class="str">"cmt">// ENUM_GROUPS_LIBRARY enumeration header class="type">class="kw">string data = "enum ENUM_GROUPS_LIBRARY {\n"; class=class="str">"cmt">// Fill the enumeration with group names FOREACH(p_names, { data += StringFormat(" GL_PARAMS_%d, class=class="str">"cmt">// %s\n", i, p_names[i]); }); class=class="str">"cmt">// Close the enumeration data += "};\n\n"; class=class="str">"cmt">// Group initialization class="type">class="kw">string array header and its opening bracket data += "class="type">class="kw">string CGroupsLibrary::s_params[] = {"; class=class="str">"cmt">// Fill the array by replacing invalid characters in the initialization strings class="type">class="kw">string param; FOREACH(p_names, { param = p_params[i]; StringReplace(param, "\r", ""); StringReplace(param, "\n", " "); StringReplace(param, "\"", "\\\""); data += StringFormat("\"%s\",\n", param); }); class=class="str">"cmt">// Close the array data += "};\n"; class=class="str">"cmt">// Open the file to write data class="type">int f = FileOpen("ExportedGroupsLibrary.mqh", FILE_WRITE | FILE_TXT | FILE_ANSI); class=class="str">"cmt">// Write the generated code FileWriteString(f, data); class=class="str">"cmt">// Close the file FileClose(f); } class=class="str">"cmt">// Connecting the exported mqh file. class=class="str">"cmt">// It will initialize the CGroupsLibrary::s_params[] class="kw">static variable class=class="str">"cmt">// and ENUM_GROUPS_LIBRARY enumeration class="macro">#include "ExportedGroupsLibrary.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Inputs | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input group "::: Exporting from library" class="kw">input class="type">class="kw">string passes_ = "class="num">802150,class="num">802151,class="num">802152,class="num">802153,class="num">802154," "class="num">802155,class="num">802156,class="num">802157,class="num">802158,class="num">802159," "class="num">802160,class="num">802161,class="num">802162,class="num">802164,class="num">802165," "class="num">802166,class="num">802167,class="num">802168,class="num">802169,class="num">802173"; class=class="str">"cmt">// - Comma-separated IDs of the saved passes class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// Call the group library class="kw">export method CGroupsLibrary::Export(passes_); class=class="str">"cmt">// Successful initialization class="kw">return(INIT_SUCCEEDED); } class="type">void OnTick() { ExpertRemove(); }
把参数组塞进 EA 里跑通
EA 收尾只改三处:引入 GroupsLibrary、用 groupId_ 替掉旧 passes_、OnInit 里改从 CGroupsLibrary::s_params[] 按索引取初始化串。这样 MT5 参数框里直接显示组名而非数字序列,选组不再靠猜。 由于给 ENUM_GROUPS_LIBRARY 每个元素加了注释名,EA 参数对话框会出现可读名称。选列表最后一组做测试,平均年度归一化利润和库里存的值接近,差异主要来自最终 EA 用了标准化组——最大相对回撤约 10%,而 Stage3 生成该组初始化串时还没归一化,回撤仅约 5.4%。外汇与贵金属品种波动大,这种回撤放大属正常风险暴露。 代码层面最关键的是 isValid 判断与取串:groupId_ 落在 [0, ArraySize) 才允许取 s_params[groupId_],否则 INIT_FAILED 中断。复制下面片段到 SimpleVolumesExpert.mq5 即可验证。
class="macro">#include "GroupsLibrary.mqh" class="kw">input group "::: Selection for the group" class="kw">input ENUM_GROUPS_LIBRARY groupId_ = -class="num">1; class=class="str">"cmt">// - Group from the library class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { ... class=class="str">"cmt">// Initialization class="type">class="kw">string with strategy parameter sets class="type">class="kw">string strategiesParams = NULL; class=class="str">"cmt">// If the selected strategy group index from the library is valid, then if(groupId_ >= class="num">0 && groupId_ < ArraySize(CGroupsLibrary::s_params)) { class=class="str">"cmt">// Take the initialization class="type">class="kw">string from the library for the selected group strategiesParams = CGroupsLibrary::s_params[groupId_]; } class=class="str">"cmt">// If the strategy group from the library is not specified, then we interrupt the operation if(strategiesParams == NULL) { class="kw">return INIT_FAILED; } ... class=class="str">"cmt">// Successful initialization class="kw">return(INIT_SUCCEEDED); }
「实盘前必须认清的边界」
这套 EA 已经能脱离优化阶段填充的数据库独立跑起来,但别把它当成终点。回测里暴露过一个硬伤:同一套代码在策略测试器里,切换“主要交易品种”或换报价服务器,结果会出现明显漂移,说明它对环境参数相当敏感。 前面几节从没承诺过跟这个方向走就能拿到保底收益。实际跑下来,某些阶段给的就是失望的曲线,而不是漂亮净值。即便把准备实盘要做的检查都做一遍,也没人敢说“已穷尽所有手段确保真账户不出错”——那更像永远在逼近、却总差一口气的完美态。 本系列所有数字都来自历史测试,外汇与贵金属杠杆品种的高风险意味着它们不预示未来利润。研究性质的工作,代码随便你拿去验,盈亏自己扛。
◍ 多币种 EA 工程文件清单与版本落点
这套多币种 EA 框架在 MT5 的 MQL5/Experts 目录下以 31 个文件组成工程树,版本号从 1.00 到 1.20 不等,最近修改标注对应到具体开发章节。比如 SimpleVolumesExpert.mq5 停在 1.20,负责多组模型策略并行运行并从内置组库取参;VirtualAdvisor.mqh 到 1.06,管虚拟仓位与订单;TesterHandler.mqh 到 1.03,接优化事件。打开 MT5 导航器把 ZIP 解进对应路径,就能直接看到这些类之间的引用关系。 下载包 MQL5.zip 体积 72.95 KB,塞进了基类、数据库处理、历史回放和 GUI 等模块。想验证工程完整性,可在 MT5 里新建一个空 EA,#include 其中的 Strategy.mqh(1.04 交易策略基类)和 VirtualStrategy.mqh(1.05 虚拟仓位策略类),编译若不报找不到符号,说明文件摆放位置正确。 评论区有人跑 SimpleVolumesStage3.mq5 写库时报 'FOREIGN KEY constraint failed',作者回应该错误源于后续章节改动牵扯了前文未覆盖的逻辑。外汇与贵金属交易本就高风险,这类多模块 EA 在实盘前务必用策略测试器以 M1 OHLC 模式先做回放,别直接挂真仓。
if(m_volumes[class="num">0] > avrVolume * (class="num">1 + m_signalDeviation + m_ordersTotal * m_signaAddlDeviation)) { class=class="str">"cmt">// 如果烛台开盘价低于当前价(收盘价),则 if(iOpen(m_symbol, m_timeframe, class="num">0) < iClose(m_symbol, m_timeframe, class="num">0)) {
把工具请下神坛
回测日志里那行 CDatabase::Execute | ERROR: 5619 和后续的 failed with code 5039,暴露了多品种组合 EA 在落库时的真实脆弱点:策略组对象序列化进 SQLite 时,嵌套的 CVirtualStrategyGroup 层数一多,INSERT 语句就可能在读取行阶段直接断掉。
同一轮测试里 final balance 跑到了 24603.99 USD,初始入金 10000,但 passes 表里的峰值权益 21542.31、最大回撤 -10446.11 说明这条曲线靠的是高敞口换收益,外汇与贵金属品种叠加这类波动率,实盘爆仓概率不低。
日志中 CSimpleVolumesStrategy("CADCHF",16385,220,1.40,1.70,150,2200.00,200.00,46000,24) 这种参数结构,你复制进 MT5 策略测试器大概率也会撞上同样的数据库写入瓶颈——别神化任何自动化框架,先确认你的 DatabaseReadBind 返回路径兜住了异常。
工具只是把人的假设跑得快一点,读得懂报错里的 5619 和 5039,比追着余额曲线更有用。
if(DatabaseReadBind(request, row)) {