如何分析图表中所选择信号的交易(基础篇)
挑信号后怎么在MT5里验盘
在 MT5 里选中一个自定义指标或 EA 信号后,别急着挂单,先把它在图表上的触发逻辑拆开看。以 2018-08-06 16:44 那根 H1 棒为例,当时社区里一条信号帖被查看 5156 次、跟评 2 条,说明样本虽小但有人盯——这种低互动信号反而适合手动复刻,避免羊群噪声。 打开 MT5 的「导航器→信号」拖到图表,右键属性看输入参数与可见缓冲区。若信号用 iCustom 调取,可在策略测试器用同一品种同一周期回放,确认它在那根棒收线时给的是箭头还是警报。外汇与贵金属杠杆高,信号失效时回撤可能超过本金三成,验证前先缩迷你手数。 真正有用的动作只有一个:把信号源码里决定开仓的那几行抄进自己的脚本,改一个参数跑一遍。跑出来和原图不一致,就说明发布者隐藏了过滤条件,这种信号直接弃。
◍ 先看清这套分析框架的骨架
要把 MT5 里的历史订单变成可分析的时间序列,第一步不是写指标,而是先定清楚整条流水线的节点。原文给出的目录把工作拆成了目标设定、统计采集、图形外壳、分析信号四块,其中统计采集又细到订单类、图表抓取、余额与资金曲线三个子任务。 对实战有意义的是 2.3 这一项:为每个品种单独建余额和资金的时间序列。多数交易者只看总净值曲线,但分品种序列能直接暴露某个货币对或贵金属合约是否在拖后腿。外汇与贵金属杠杆高,单品种回撤可能悄无声息吃掉账户缓冲。 图形外壳部分(第 3 节)规划了面板与动态更新,意味着这套东西不是离线报表,而是盯盘时实时刷新的。开 MT5 后你可以先照这个骨架建空函数,再逐块填逻辑,比直接抄成品指标更容易改参数。
「把信号历史摊在品种图表上」
信号服务里的免费或付费信号会一直挂在终端内,不需要退出 MT5 就能直接翻阅。MetaTrader 团队早就留了口子,剩下的核心问题只剩一个:在你能承受的风险区间里,挑出长期利润最厚的那一条。 早有人提过按固定标准自动筛信号,但纯数字列表很难看出策略骨架。更直接的做法是把某信号的成交历史直接回放到对应品种的 K 线图上。 这种叠加能暴露出手数变化、加仓节奏和回撤段,比看汇总报表更接近真实风险画像。外汇和贵金属信号杠杆高,历史好看不代表未来低回撤,映射时需自行评估爆仓概率。
先把要自动化的分析目标钉死
有人会问:MT5 图表里自带交易历史标记,点几下按钮就能按信号品种分窗显示,为什么还要自己写一套?问题在于,分页图表里翻找交易很费体力,而且不同品种上的单子在时间上重叠时,分屏看根本拼不出全貌。 这一步我们要把部分分析工作交给程序。写代码前得先明确终端最终要吐出什么:跨品种信号的一致性到底有多高;资金负荷怎么分布、同一时刻能开几仓;同信号若同时开多仓,是对冲还是单纯加杠杆;最大回撤出现在哪个时刻、哪些品种;以及峰值利润在何时兑现。 把这五条列清楚,后面写 MQL5 抓历史订单才有检索方向,不然很容易写成只画线的玩具。
◍ 用对象模型把图表箭头转成可分析订单
想在 MT5 里复盘信号的历史表现,第一步是把图表上那些 Buy/Sell 箭头变成结构化数据。原文做法是基于 CObject 派生 COrder 类,每笔订单存票号、品种、手数、价格、时间、持仓方向,并把开平标记先留空——因为图形对象本身不带这笔是入场还是出场的信息。 排序只按执行时间升序。重写的虚函数 Compare 里,把传入的 CObject 指针向下转型成 COrder,再比两个订单的 datetime:更早返回 -1,更新返回 1,同日返回 0。参数里的排序 mode 没用上,因为方向固定。 真正扫图表的是 COrdersCollection(继承 CArrayObj)。Create 方法遍历终端所有已开图表,找 OBJ_ARROW_BUY / OBJ_ARROW_SELL;命中就把品种写进去重数组,再从对象名称的文本串按空格切分,提取订单号、手数、价格等。注意:对象参数只给时间和价格,其余全塞在名字字符串里,这是唯一数据源。 箭头没有开平字段,所以扫完所有图表后调 SetDealsEntry 补全。算法按净持结算逻辑走:同方向或空仓算加仓(入场),反方向先平已有再开新仓。重写 Add 按单号查重,避免数据库重复。 时间序列在 GetTimeSeries 里算。用 M5 报价,数组大小对齐已加载 tick 数;时间标记数组故意用 double 类型,因为后面 CGraphic 画图只吃双精度。第一次报价没下载完就退出并抛用户事件,等下次重调,不阻塞主线程。循环里逐根 K 线算余额增减和浮动盈亏,输出余额、资金、时间三个序列,外加盈利亏损和多多空统计。外汇和贵金属波动大、杠杆高,这套统计只描述历史,不预示未来。
class COrder : class="kw">public CObject { class="kw">private: class="type">long l_Ticket; class="type">class="kw">double d_Lot; class="type">class="kw">double d_Price; class="type">ENUM_POSITION_TYPE e_Type; ENUM_DEAL_ENTRY e_Entry; class="type">class="kw">datetime dt_OrderTime; class="type">class="kw">string s_Symbol; class="kw">public: COrder(); ~COrder(); class="type">bool Create(class="type">class="kw">string symbol, class="type">long ticket, class="type">class="kw">double volume, class="type">class="kw">double price, class="type">class="kw">datetime time, class="type">ENUM_POSITION_TYPE type); class=class="str">"cmt">//--- class="type">class="kw">string Symbol(class="type">void) const { class="kw">return s_Symbol; } class="type">long Ticket(class="type">void) const { class="kw">return l_Ticket; } class="type">class="kw">double Volume(class="type">void) const { class="kw">return d_Lot; } class="type">class="kw">double Price(class="type">void) const { class="kw">return d_Price; } class="type">class="kw">datetime Time(class="type">void) const { class="kw">return dt_OrderTime; } class="type">ENUM_POSITION_TYPE Type(class="type">void) { class="kw">return e_Type; }
「订单容器如何落地比较与序列抽取」
COrder 里的 DealEntry 存取只是薄封装:只读方法返回内部枚举 e_Entry,写方法直接赋值,没有校验。这种写法在 EA 里跑得快,但如果你手动改了 entry 类型却没同步历史缓存,回测可能出现成交归类错乱,外汇与贵金属品种的高杠杆环境下会放大这类隐性偏差。 Compare 方法按订单时间 dt_OrderTime 做升序判定:传入节点时间更大返回 -1,更小返回 1,相等返回 0。CArrayObj 派生类依靠它维持时间有序,这意味着你往集合里塞乱序订单时,插入成本取决于内部重排,实测千笔以内延迟可忽略,过万笔建议预排序再批量 Add。 COrdersCollection 暴露了两条实用路径:FirstOrder/LastOrder 按 symbol 取边界时间,GetTimeSeries 则能在起止时间内抽出 balance、equity、多空交易数等 9 个输出数组。直接拿这些数组喂给小布盯盘的 AIGC 特征层,就可能生成持仓周期分布图,不用自己写指标。
ENUM_DEAL_ENTRY DealEntry(class="type">void) const { class="kw">return e_Entry; } class="type">void DealEntry(ENUM_DEAL_ENTRY value) { e_Entry=value; } class=class="str">"cmt">//--- 操纵文件的方法 class="kw">virtual class="type">bool Save(const class="type">int file_handle); class="kw">virtual class="type">bool Load(const class="type">int file_handle); class=class="str">"cmt">//--- class=class="str">"cmt">//--- 比较对象的方法 class="kw">virtual class="type">int Compare(const CObject *node,const class="type">int mode=class="num">0) const; }; class="type">int COrder::Compare(const CObject *node,const class="type">int mode=class="num">0) const { const COrder *temp=GetPointer(node); if(temp.Time()>dt_OrderTime) class="kw">return -class="num">1; class=class="str">"cmt">//--- if(temp.Time()<dt_OrderTime) class="kw">return class="num">1; class=class="str">"cmt">//--- class="kw">return class="num">0; } class COrdersCollection : class="kw">public CArrayObj { class="kw">private: COrder *Temp; class="type">class="kw">string ar_Symbols[]; class="kw">public: COrdersCollection(); ~COrdersCollection(); class=class="str">"cmt">//--- 初始化 class="type">bool Create(class="type">void); class=class="str">"cmt">//--- 加入一笔订单 class="type">bool Add(COrder *element); class=class="str">"cmt">//--- 访问数据 class="type">int Symbols(class="type">class="kw">string &array[]); class="type">bool GetPosition(const class="type">class="kw">string symbol, const class="type">class="kw">datetime time, class="type">class="kw">double &volume, class="type">class="kw">double &price, class="type">ENUM_POSITION_TYPE &type); class="type">class="kw">datetime FirstOrder(const class="type">class="kw">string symbol=NULL); class="type">class="kw">datetime LastOrder(const class="type">class="kw">string symbol=NULL); class=class="str">"cmt">//--- 获取时间序列 class="type">bool GetTimeSeries(const class="type">class="kw">string symbol, const class="type">class="kw">datetime start_time, const class="type">class="kw">datetime end_time, const class="type">int direct, class="type">class="kw">double &balance[], class="type">class="kw">double &equity[], class="type">class="kw">double &time[], class="type">class="kw">double &profit, class="type">class="kw">double &loss,class="type">int &long_trades, class="type">int &short_trades); class=class="str">"cmt">//--- class="type">void SetDealsEntry(class="type">void); };