来自专业程序员的提示(第二部分):在智能交易系统、脚本和外部程序之间存储和交换参数·综合运用
用隐藏对象在脚本与EA间传令
MT5 里想让独立脚本给正在跑的 EA 下指令,又不想走文件或全局变量,可以借用一张隐藏的图表对象当信箱。下面这段把对象的时间框架属性锁成 0,保证它只在当前图表可见、不被多周期复读。 TakeCommand 先按名字找对象,找不到就回 cmdUnknown;找到后把对象文本读进 m_path,把 ZORDER 整数读进 m_cmd,随即删掉对象并完成 Load。也就是说,一条指令自带路径与动作码,用完即焚,不残留。 Dump 则把参数表里每一项用 PrintFormat 打到日志,前缀带 sender 方便在多EA环境里区分来源。实盘前可在策略测试器里跑一次脚本发令,看 EA 日志是否精确打印出 sender 与条目,验证通道通畅。外汇与贵金属波动剧烈,这类自动联动逻辑上线前务必在模拟盘跑够样本。
ObjectSetInteger(class="num">0, nameObj, OBJPROP_TIMEFRAMES, class="num">0); } class=class="str">"cmt">// ----------------------------------------------------------------------------- class=class="str">"cmt">// 从脚本接收命令 | class=class="str">"cmt">// ----------------------------------------------------------------------------- NCommand Params::TakeCommand() { class="type">class="kw">string nameObj = NAME_OBJECT_CMD; if (ObjectFind(class="num">0, nameObj) < class="num">0) class="kw">return cmdUnknown; m_path = ObjectGetString(class="num">0, nameObj, OBJPROP_TEXT); m_cmd = (NCommand)ObjectGetInteger(class="num">0, nameObj, OBJPROP_ZORDER); ObjectDelete(class="num">0, nameObj); Load(m_path); class="kw">return m_cmd; } class=class="str">"cmt">// ----------------------------------------------------------------------------- class=class="str">"cmt">// | 转储参数 | class=class="str">"cmt">// ----------------------------------------------------------------------------- class="type">void Params::Dump(class="type">class="kw">string sender) { for (class="type">int j = class="num">0; j < m_items.Total(); j++) { PrintFormat("%s / %s", sender, m_items.At(j)); } }
◍ 将参数传递给外部程序
不同系统之间的数据交换单元实际上是一个 json 文件。 以前它曾是一个 xml 文件。 json 文件的主要优点是: 易于创建(生成/格式化) 所有高级语言均完美支持 可读性 例如,有一个包含以下字段的 Bar 类:m_time、m_open、m_high、m_low、m_close、m_body。 其中 m_body 是烛条颜色:阳线、阴线或十字星。 Bar 类有一个 ToJson() 方法,它生成一个 json 字符串 我们可以改用 StringFormat,但这会在重新排列或删除数值时导致出问题。 格式化 “\n\t” 可以删除,因为网上有很多 json 格式化服务。 其中之一是 JSON 解析器 。 一次设置有效 json 的接收,并在您需要时调用 bar.ToJson() 函数。 一个外部程序,例如 C# 应用程序,可以将任何复杂度的 json 文件转换为对象。 如何从 MQL 传输一个 json 文件? 这很简单。 例如,将 json 文件加载(保存)到 Files/Json 终端目录。 一个外部程序监视此目录中是否有新文件。 若发现文件,程序读取它,将其转换为对象,并立即删除文件,或将其移动到存档(用于统计)。 [CODE] <span class="keyword">string</span> Bar::ToJson() { <span class="keyword">return</span> <span class="string">"{"</span> + <span class="string">"\n\t\"symbol\":\""</span> + <span class="predefines">_Symbol</span> + <span class="string">"\","</span> + <span class="string">"\n\t\"period\":"</span> + <span class="functions">IntegerToString</span>(<span class="predefines">_Period</span>) + <span class="string">","</span> + <span class="string">"\n\t\"digits\":"</span> + <span class="functions">IntegerToString</span>(<span class="predefines">_Digits</span>) + <span class="string">","</span> + <span class="string">"\n\t\"timeBar\":\""</span> + TimeToStr(m_time) + <span class="string">"\","</span> +
「用文本文件给 EA 喂外部参数」
把 json 库硬接进 MQL 程序,或者自己重写一套解析逻辑,都会带来额外维护负担和编译麻烦。更干净的做法是:外部程序写一份纯文本文件,里面用“关键字=参数值”的格式存放指令,MQL 端用前文提到的 Params 类直接读。 智能交易系统和指标都适合做接收方。典型流程是在 OnTick 里调用 CheckExternalCommand(),它负责扫描 Files/ExtCmd 目录;一旦命中文件,就读取内容、按 Params 解析参数、执行对应动作,随后把文件删掉,避免重复触发。 这种机制对自动化协作很有用:比如外部 Python 信号源算完策略,丢个 cmd.txt 进 ExtCmd,MT5 这边下一 tick 就能吃到参数。外汇和贵金属波动剧烈、杠杆高风险大,外部参数若未经校验直接用于下单,可能瞬间放大亏损。 MQL 市场禁止带 DLL 的程序,根子上是安全——DLL 能碰进程内任何东西。文本文件通道绕开了这点,又保留了外部交互能力。
把交易事件推到安卓手机
想在 MT5 跑 EA 时不被绑在屏幕前,可以用 WirePusher 这类安卓推送服务(免费、无插件)把关键事件发到手机。iPhone 用户需自行找同类方案,原文作者未验证 iOS 端可用性。 接入只需四步:手机装好 WirePusher 并记下主屏显示的 ID;在 MT5 的 服务/设置/智能系统/允许 WebRequest 访问的 URL 里加入 https://wirepusher.com;把脚本里 id 参数的星号换成你的 ID;启动脚本。Cayman EA 会在挂单触发、价格突破交易级别、成交平仓三个时刻调用推送函数。 每种通知类型可分配独立提示音。作者曾给盈利平仓设“ta-da”、亏损平仓设“炸弹”,但频繁亏损时被炸弹音搞到疲惫,说明声音策略本身也会影响执行心态。外汇与贵金属杠杆高,这类提醒只解决“知道”,不解决“做对”,信号到手仍要自行判断。 下面这段 MQL5 是最小可跑的推送示例与函数封装,注意 WebRequest 超时设的 3000 毫秒,返回码非 200 会打印错误并返 false。
class="type">void OnStart() { class="type">class="kw">string id = "**********"; class=class="str">"cmt">// 在 WirePusher 里填写您的智能手机 id WirePusher("Profit $class="num">1000", "Deal", "Closed", id); } class=class="str">"cmt">// ------------------------------------------------------------------------------------------------ class=class="str">"cmt">// 通过 WirePusher 网页服务向智能手机发送通知 class=class="str">"cmt">// 在终端的服务/设置/智能系统/允许 WebRequest 访问的 URL 里加入 https://wirepusher.com class=class="str">"cmt">// message - 通知文本 class=class="str">"cmt">// title - 通知标题(例如,Attention / Alert / Deal) class=class="str">"cmt">// type - 通知类型(例如,触发挂单/级别突破/平仓) class=class="str">"cmt">// id - 来自 WirePusher 安卓应用程序的唯一智能手机 id class=class="str">"cmt">// ------------------------------------------------------------------------------------------------ class="type">bool WirePusher(class="type">class="kw">string message, class="type">class="kw">string title, class="type">class="kw">string type, class="type">class="kw">string id) { class="type">char data[]; class=class="str">"cmt">// HTTP 消息主体数据数组 class="type">char result[]; class=class="str">"cmt">// Web 服务响应数据数组 class="type">class="kw">string answer; class=class="str">"cmt">// Web 服务响应头 class="type">class="kw">string url = "https:class=class="str">"cmt">//wirepusher.com/send?id={id}&title={title}&message={message}&type={type}"; StringReplace(url, "{id}", id); StringReplace(url, "{type}", type); StringReplace(url, "{title}", title); StringReplace(url, "{message}", message); ResetLastError(); class="type">int rcode = WebRequest("GET", url, NULL, class="num">3000, data, result, answer); if (rcode != class="num">200) { PrintFormat("%s / error=%i / url=%s / answer=%s / %s", __FUNCTION__, GetLastError(), url, answer, CharArrayToString(result)); class="kw">return class="kw">false; } PrintFormat("%s / %s / %s", __FUNCTION__, title, message); class="kw">return true; }
◍ 记住这一条就够了
把 EA 参数落盘到文本文件,是跨系统最稳的存储办法。文件系统在任何操作系统和终端里都被完整支持,不像全局变量或图形对象那样容易因为命名碰撞或线程竞争出问题。 作者给出的路径范例是 Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt,其中 128968168864101576 是图表 ID,靠这个前缀隔离,别的程序基本摸不到你的参数文件。 下面这段脚本是反例:它不停用随机数覆写终端里所有全局变量,若你真在跑实盘的终端上执行,会把正常 EA 的全局变量全冲掉,概率虽低但后果直接。外汇和贵金属本身波动剧烈、杠杆高危,参数被误改可能放大亏损。 [CODE] 里的代码只适合在闲置测试终端里跑着看热闹,别挂到正式环境。参数这东西,写进文件、加好图表 ID 前缀,比什么都省心。
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span>() { <span class="keyword">class="kw">while</span>(!<span class="functions">IsStopped</span>()) <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="functions">GlobalVariablesTotal</span>();i>=<span class="number">class="num">0</span>;--i) <span class="functions">GlobalVariableSet</span>(<span class="functions">GlobalVariableName</span>(i),<span class="functions">MathRand</span>()); }