DoEasy.服务函数(第 3 部分):外包线形态·综合运用
- 市价单请求结构的逐字段填法
- 平仓请求里的类型反手与价格锚定
- 平仓逻辑里的对冲账户与跨版本分支
- 平仓请求拼装与账户模式分支
- 平仓回执与开仓重试的底层写法
- 图表间事件指标的句柄与类型描述
- 图形元素类型到文本标签的分发链
- 图形元素类型的文本映射与私有成员
- 图形对象成员变量的字段分工
- 图形对象的尺寸与显隐控制接口
- 图形对象面板的显隐与配色接口
- 图表尺寸与面板属性的类内读写
- K线属性文本化的三元嵌套
- 图案面板的坐标防越界逻辑
- 形态图标的绘制与显隐分支
- Pin Bar 图标的显隐与类骨架
- Pin Bar 图案类的尺寸与越界处理
- PinBar 图案的图表绘制逻辑
- Inside Bar 类的头文件骨架
- Inside Bar 图形重绘与坐标防溢出
- 内包线图标的两种绘制分支
- 外包线形态类的封装与重构落点
- 外包线形态类的骨架怎么搭
- 外包线类的构造与面板着色逻辑
- 外包线面板的渐变与防越界绘制
- Outside Bar 图形对象的绘制与重绘逻辑
- OutsideBar 图形对象的绘制与高度测算
- 把形态类型塞进 K 线对象的位标记
- MqlParam 比较与图案控制基类
- K线比例与模式对象的类内字段
- 母Bar对象的图表元数据与虚函数骨架
- 图案控件里的点状绘制与开关位
- K线形态比例参数的 setter 与 getter
- 图形对象的标识与尺寸接口
- 图表坐标与图案刷新的底层接口
- 受保护构造器如何接管图表与参数
- 从历史K线反推形态实例的检索逻辑
- 把形态挂上K线并触发倒数第二根提醒
- PinBar 识别类的参数与编码逻辑
- PinBar 与 InsideBar 的筛选条件怎么写
- 向内包线回溯的循环判定
- 内包线控制器的编码与对象识别
- 实体占比与相邻K线比例的判定实现
- 外包线形态的过滤与编码逻辑
- 外包线对象ID的位打包逻辑
- 外包线模式的实例化与回溯识别
- 外包线方向判定与控制器取表逻辑
- 形态管理类的对外接口与未实现的工厂方法
- 反转形态为何在工厂里直接返回空
- 按类型与参数抓取已有形态控制器
- 回测里图表尺寸不同步的坑
- 同步图表视窗参数到形态管理器
- 批量给形态控件下发图表参数
- 图形对象的注册与图表绘制接口
- 按时间从序列里捞一根K线
- 按时间取棒线与图案标记的内部机制
- 图表对象与图案控制类的接口封装
- 吞蜡烛形态的图表控制接口
- 跨周期图案重绘与图表参数下发
- 把K线形态标记搬上图表的函数接口
- 图表对象集合的初始化与属性抓取
- 图案管理的集合层接口怎么调
- 图表形变时如何同步模式管理对象
- 按品种周期筛形态并刷新像素映射
- Outside Bar 识别参数的封装与默认值
- 内包线与 Pin Bar 的注册参数拆解
- Pin Bar 形态参数的数组装配细节
- 吞没形态的参数封装与绘制调用
- 内包线与 Pin Bar 的绘制接口参数
- Pin Bar 参数怎么塞进 MqlParam 数组
- 引擎层事件分发与K线刷新钩子
- 缓冲区遍历与跳变事件的处理逻辑
- 跑一遍外包线形态扫描的实机验证
- Outside Bar 与多源开关的初始化落地
- 在图表事件里接住 DoEasy 的自定义信号
- 把这条线请下神坛
◍ 市价单请求结构的逐字段填法
这段逻辑出现在交易类库的下单函数尾部,负责在拿到报价后把 MqlTradeRequest 结构清空再逐项赋值。先调用 ZeroMemory 清掉 m_request 与 m_result,避免上一次挂单的残留字段污染本次市价单。
价格字段按持仓方向取值:买用 ask、卖用 bid,由传入的 type 与 POSITION_TYPE_BUY 比较决定。volume、sl、tp 直接透传参数,magic 与 deviation 在收到 ULONG_MAX 时回退到对象内部默认值,否则用外部传入值。
type_filling 的写法是 type_filling>WRONG_VALUE ? type_filling : this.m_type_filling,意思是仅当调用方显式给了合法填充策略时才覆盖,否则沿用类内预设。comment 为 NULL 时填类内默认注释,否则用传入字符串。
MQL5 分支下根据 m_async_mode 选择同步 OrderSend 或异步 OrderSendAsync;MQL4 分支则走老式 OrderSend,用 ticket!=WRONG_VALUE 判断成交,并回补 deal、price、volume 到 m_result。外汇与贵金属杠杆高,异步模式下滑点可能突然扩大,建议先在策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍验证填充与滑点处理。
::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); class="kw">return class="kw">false; } class=class="str">"cmt">//--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); class=class="str">"cmt">//--- Fill in the request structure this.m_request.action = TRADE_ACTION_DEAL; this.m_request.symbol = this.m_symbol; this.m_request.magic = (magic==ULONG_MAX ? this.m_magic : magic); this.m_request.type = (ENUM_ORDER_TYPE)type; this.m_request.price = (type==POSITION_TYPE_BUY ? this.m_tick.ask : this.m_tick.bid); this.m_request.volume = volume; this.m_request.sl = sl; this.m_request.tp = tp; this.m_request.deviation = (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.type_filling= (type_filling>WRONG_VALUE ? type_filling : this.m_type_filling); this.m_request.comment = (comment==NULL ? this.m_comment : comment); class=class="str">"cmt">//--- Return the result of sending a request to the server class="macro">#ifdef __MQL5__ class="kw">return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); class="macro">#else ::ResetLastError(); class="type">int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(class="type">int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(class="type">int)m_request.magic,m_request.expiration,clrNONE); this.m_result.retcode=::GetLastError(); ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); if(ticket!=WRONG_VALUE) { this.m_result.deal=ticket; this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price); this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); class="kw">return true; } else { class="kw">return class="kw">false; } class="macro">#endif }
「平仓请求里的类型反手与价格锚定」
平仓不是直接发一个『关闭』指令,而是按当前持仓方向反向挂市价单。代码里先用 PositionGetInteger(POSITION_TYPE) 取出持仓类型,再交给 OrderTypeOppositeByPositionType() 得到对应的反手订单类型:多仓对应空单,空仓对应多单。 价格字段的填法有讲究:如果是卖仓(POSITION_TYPE_SELL),平仓用 ask 价;否则用 bid 价。这是因为你平掉空仓等于买回,得对接做市商的卖出报价。外汇与贵金属杠杆高,滑点可能瞬间吃掉几美元盈亏,deviation 参数就是给这块留的容差。 注意 m_request.type_filling 直接沿用对象内预设的成交方式,没有重新计算。如果你在 ECNDMA 账户跑这套,最好先打印 m_type_filling 确认不是默认的 ORDER_FILLING_FOK,否则部分成交会直接被拒。开 MT5 把下面这段塞进 CTradeObj 派生类,断点跟一遍能看清结构填充顺序。
class="type">bool CTradeObj::ClosePosition(const class="type">class="kw">ulong ticket, const class="type">class="kw">string comment=NULL, const class="type">class="kw">ulong deviation=ULONG_MAX) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) class="kw">return true; ::ResetLastError(); class=class="str">"cmt">//--- If the position selection failed. Write the error code and error description, send the message to the log and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(!::PositionSelectByTicket(ticket)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,"#",(class="type">class="kw">string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode)); class="kw">return class="kw">false; } class=class="str">"cmt">//--- If failed to get the current prices, write the error code and description, send the message to the journal and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); class="kw">return class="kw">false; } class=class="str">"cmt">//--- Get a position type and an order type inverse of the position type class="type">ENUM_POSITION_TYPE position_type=(class="type">ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type); class=class="str">"cmt">//--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); class=class="str">"cmt">//--- Fill in the request structure this.m_request.action = TRADE_ACTION_DEAL; this.m_request.symbol = this.m_symbol; this.m_request.type = type; this.m_request.magic = ::PositionGetInteger(POSITION_MAGIC); this.m_request.price = (position_type==POSITION_TYPE_SELL ? this.m_tick.ask : this.m_tick.bid); this.m_request.volume = ::PositionGetDouble(POSITION_VOLUME); this.m_request.deviation = (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.comment = (comment==NULL ? this.m_comment : comment); this.m_request.type_filling = this.m_type_filling;
平仓逻辑里的对冲账户与跨版本分支
在封装交易对象的平仓方法时,遇到对冲账户要先记下被平仓的持仓 ticket,否则后续请求结构里 position 字段为空,服务器会拒单。代码里用 IsHedge() 判断账户类型,再把 PositionGetInteger(POSITION_TICKET) 写进 m_request.position,这是 MT5 净仓与对冲两种模式必须分开处理的地方。 MQL5 与 MQL4 的发送路径完全不一样:MQL5 下根据 m_async_mode 决定走 OrderSend 还是 OrderSendAsync,同步模式返回布尔、异步模式靠回调补结果;MQL4 分支则先 SymbolInfoTick 抓实时买卖价,再手动 OrderClose,成功与否都回填 m_result 的 retcode、deal、price、volume 和 comment。 下面这段是原文里平仓与部分平仓衔接处的实际代码,逐行看能少踩很多坑: //--- 对冲账户下,把已平仓 ticket 写进结构 if(this.IsHedge()) this.m_request.position=::PositionGetInteger(POSITION_TICKET); //--- 返回发往服务器的请求结果 #ifdef __MQL5__ return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); #else ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; ::ResetLastError(); if(::OrderClose((int)this.m_request.position,this.m_request.volume,this.m_request.price,(int)this.m_request.deviation,clrNONE)) { this.m_result.retcode=::GetLastError(); this.m_result.deal=ticket; this.m_result.price=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderClosePrice() : this.m_request.price); this.m_result.volume=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); return true; } else { this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); return false; } #endif 部分平仓函数 ClosePositionPartially 开头就拦掉了指标和服务程序,因为这两类 PROGRAM 没有下单权限,直接 return true 避免误用。若 PositionSelectByTicket(ticket) 失败,说明 ticket 已失效或不在当前品种,这时不继续填单而是写错码返 false,外汇与贵金属杠杆高,ticket 失效后硬发请求可能平错仓。
class=class="str">"cmt">//--- In case of a hedging account, write the ticket of a closed position to the structure if(this.IsHedge()) this.m_request.position=::PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">//--- Return the result of sending a request to the server class="macro">#ifdef __MQL5__ class="kw">return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); class="macro">#else ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; ::ResetLastError(); if(::OrderClose((class="type">int)this.m_request.position,this.m_request.volume,this.m_request.price,(class="type">int)this.m_request.deviation,clrNONE)) { this.m_result.retcode=::GetLastError(); this.m_result.deal=ticket; this.m_result.price=(::OrderSelect((class="type">int)ticket,SELECT_BY_TICKET) ? ::OrderClosePrice() : this.m_request.price); this.m_result.volume=(::OrderSelect((class="type">int)ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); class="kw">return true; } else { this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); class="kw">return class="kw">false; } class="macro">#endif
◍ 平仓请求拼装与账户模式分支
平仓逻辑里最容易被忽略的是前置校验:若按 ticket 选不到持仓,或 SymbolInfoTick 取不到实时 tick,代码会直接把 GetLastError 写进 m_result.retcode 并 return false,不会走到真正发单。想验证这一点,可以在 MT5 里故意传一个已平仓的 ticket,看日志是否打出 MSG_LIB_SYS_ERROR_FAILED_SELECT_POS。 选到持仓后,先通过 PositionGetInteger(POSITION_TYPE) 拿到持仓方向,再用 OrderTypeOppositeByPositionType 反推平仓单类型——多仓对应卖单,空仓对应买单。持仓体积从 POSITION_VOLUME 读,随后 ZeroMemory 清掉 request 和 result 两个结构,避免上次发单的残留字段污染本次请求。 请求结构填充时,价格按持仓反向取:卖仓用 ask、买仓用 bid;volume 取传入值与持仓体积的较小者,防止超量平仓。deviation 和 comment 若传了极值或空指针,则回退到类内成员变量 m_deviation、m_comment。下面这段是核心填充逻辑,注意 type_filling 直接沿用类内设置: m_request.type_filling 的赋值在 hedge 账户下不依赖外部,而 position 字段在 IsHedge() 为真时会改写成 POSITION_TICKET,这是净仓与对锁账户的差异点。最后按 m_async_mode 决定走 OrderSend 同步还是 OrderSendAsync 异步——外汇与贵金属杠杆交易高风险,异步失败不会阻塞 EA 主线程,但可能让仓位状态滞后,回测和实盘都建议先打印 m_result.retcode 确认。
this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,"#",(class="type">class="kw">string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode)); class="kw">return class="kw">false; } class=class="str">"cmt">//--- If failed to get the current prices, write the error code and description, send the message to the journal and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); class="kw">return class="kw">false; } class=class="str">"cmt">//--- Get a position type and an order type inverse of the position type class="type">ENUM_POSITION_TYPE position_type=(class="type">ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type); class=class="str">"cmt">//--- Get a position volume class="type">class="kw">double position_volume=::PositionGetDouble(POSITION_VOLUME); class=class="str">"cmt">//--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); class=class="str">"cmt">//--- Fill in the request structure this.m_request.action = TRADE_ACTION_DEAL; this.m_request.position = ticket; this.m_request.symbol = this.m_symbol; this.m_request.magic = ::PositionGetInteger(POSITION_MAGIC); this.m_request.type = type; this.m_request.price = (position_type==POSITION_TYPE_SELL ? this.m_tick.ask : this.m_tick.bid); this.m_request.volume = (volume<position_volume ? volume : position_volume); this.m_request.deviation = (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.comment = (comment==NULL ? this.m_comment : comment); this.m_request.type_filling= this.m_type_filling; class=class="str">"cmt">//--- In case of a hedging account, write the ticket of a closed position to the structure if(this.IsHedge()) this.m_request.position=::PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">//--- Return the result of sending a request to the server class="macro">#ifdef __MQL5__ class="kw">return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); class="macro">#else ::SymbolInfoTick(this.m_symbol,this.m_tick);
「平仓回执与开仓重试的底层写法」
这段逻辑把持仓平仓的结果回填到自定义结构里,先抓 tick 的 ask/bid 存进 m_result,再用 OrderClose 按持仓编号、手数、价格、滑点去撤单。若返回真,就从成交票号反查平仓价与手数,查不到就退回请求里的预设值,外汇与贵金属杠杆品种滑点超限时这种回退能避免空指针式的崩溃。 平仓失败分支只做两件事:记错误码、写中文报错注释,然后返回 false 交给上层处理。注意这里 retcode 来自 GetLastError 而非 OrderClose 返回值本身,实际排查时要区分「函数没跑成」和「跑成了但服务器拒单」两类情况,贵金属跳空时段后者概率明显抬高。 开仓侧则是先往 m_request 写 volume、deviation、comment、type_filling,deviation 传 ULONG_MAX 就沿用交易对象默认值,comment 为 NULL 同理。随后进 for 循环按 m_total_try 次尝试,每次调 OpenPosition,前段代码里曾直接传局部变量 comment/deviation,后段改成统一读 m_request 成员——这种改法能减少重试时参数被外层意外覆盖的风险。 建控件时还有一道前置:用 SymbolExist 判品种是否在服务器,自定义品种会经 is_custom 标记,不在就写日志并返回 false。MT5 上挂脚本前先确认品种已选,否则事件控件创建直接断在第一步。
this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; ::ResetLastError(); if(::OrderClose((class="type">int)this.m_request.position,this.m_request.volume,this.m_request.price,(class="type">int)this.m_request.deviation,clrNONE)) { this.m_result.retcode=::GetLastError(); this.m_result.deal=ticket; this.m_result.price=(::OrderSelect((class="type">int)ticket,SELECT_BY_TICKET) ? ::OrderClosePrice() : this.m_request.price); this.m_result.volume=(::OrderSelect((class="type">int)ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); class="kw">return true; } else { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); class="kw">return class="kw">false; } class="macro">#endif } class=class="str">"cmt">//--- Write the volume, deviation, comment and filling type to the request structure this.m_request.volume=volume; this.m_request.deviation=(deviation==ULONG_MAX ? trade_obj.GetDeviation() : deviation); this.m_request.comment=(comment==NULL ? trade_obj.GetComment() : comment); this.m_request.type_filling=(type_filling>WRONG_VALUE ? type_filling : trade_obj.GetTypeFilling()); class=class="str">"cmt">//--- In the loop by the number of attempts for(class="type">int i=class="num">0;i<this.m_total_try;i++) { class=class="str">"cmt">//--- Send a request res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation,type_filling); class=class="str">"cmt">//... ... ... class=class="str">"cmt">//--- Send a request res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,this.m_request.comment,this.m_request.deviation,this.m_request.type_filling); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CChartObjectsControl: Create the event control indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CChartObjectsControl::CreateEventControlInd(const class="type">long chart_id_main) { class=class="str">"cmt">//--- If the symbol is not on the server, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; class="type">bool is_custom=class="kw">false; if(!::SymbolExist(this.Symbol(), is_custom)) { CMessage::ToLog(DFUN+" "+this.Symbol()+": ",MSG_LIB_SYS_NOT_SYMBOL_ON_SERVER); class="kw">return class="kw">false; } class=class="str">"cmt">//--- Create the indicator this.m_chart_id_main=chart_id_main; class="type">class="kw">string name="::"+PATH_TO_EVENT_CTRL_IND; ::ResetLastError();
图表间事件指标的句柄与类型描述
在自定义图形库里,跨图表通讯靠的是一个 iCustom 句柄。下面这段把事件控制指标挂到当前图表的 symbol 和周期上,并绑定源图表与目标主图表的 ChartID,句柄拿不到就直接写日志返回 false,避免后续空指针乱飞。 this.m_handle_ind=::iCustom(this.Symbol(),this.Timeframe(),name,this.ChartID(),this.m_chart_id_main); if(this.m_handle_ind==INVALID_HANDLE) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR); CMessage::ToLog(DFUN,::GetLastError(),true); return false; } this.m_name_ind="EventSend_From#"+(string)this.ChartID()+"_To#"+(string)this.m_chart_id_main; ::Print ( DFUN,this.Symbol()," ",TimeframeDescription(this.Timeframe()),": ", CMessage::Text(MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR)," \"",this.m_name_ind,"\"" ); return true; 指标名被拼成 EventSend_From#<源ID>_To#<主图ID> 这种格式,在 MT5 终端的 Experts 日志里一眼能看出事件从哪张图发往哪张图。跑起来若看到 GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR 的打印,说明跨图通道已立住;外汇与贵金属图表多开时这种通道极易因图表 ID 复用出错,属于高概率需排查的坑。 另一段是图形元素类型的描述映射。CGBaseObj::TypeElementDescription 用三元运算符把枚举转成多语言文本,覆盖了 STANDARD、BITMAP、SHADOW_OBJ、WF_CONTAINER 等至少 11 种类型,其中 GRAPH_ELEMENT_TYPE_BITMAP 那行在源码里被标黄,说明它是近期改动或重点审查对象。
this.m_handle_ind=::iCustom(this.Symbol(),this.Timeframe(),name,this.ChartID(),this.m_chart_id_main); if(this.m_handle_ind==INVALID_HANDLE) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR); CMessage::ToLog(DFUN,::GetLastError(),true); class="kw">return class="kw">false; } this.m_name_ind="EventSend_From#"+(class="type">class="kw">string)this.ChartID()+"_To#"+(class="type">class="kw">string)this.m_chart_id_main; ::Print( DFUN,this.Symbol()," ",TimeframeDescription(this.Timeframe()),": ", CMessage::Text(MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR)," \"",this.m_name_ind,"\"" ); class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the description of the graphical element type | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type) { class="kw">return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_BITMAP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_BITMAP) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : class=class="str">"cmt">//--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : class=class="str">"cmt">//--- Containers type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) :
◍ 图形元素类型到文本标签的分发链
在 MT5 的 GUI 框架里,把一个枚举值 GRAPH_ELEMENT_TYPE_xxx 映射成可读字符串,常见写法是一长串三元运算符嵌套。下面这段就是针对工作流(WF)系列控件做分发的实际代码,覆盖了容器、标准控件和辅助对象三类。 代码里先处理布局容器:GROUPBOX、PANEL、TAB_CONTROL、SPLIT_CONTAINER 各自对应一条 MSG_GRAPH_ELEMENT_TYPE_WF_* 的文本常量。注释 //--- Standard controls 之后转入 Label、Checkbox、RadioButton、Button 等基础控件,再到 ListBox 的多种变体(ElementsListBox、CheckedListBox、ButtonListBox)。 辅助对象部分包括 Tab 的头部与字段、四个方向的 ArrowButton(Up/Down/Left 已列出,Right 在后续截断处)。这种写法在编译期即确定,运行时只是顺序比较,EURUSD 这类品种图表上挂自定义面板时,元素自检日志会直接打印出对应中文/本地化标签。 要验证的话,在 MT5 的 MetaEditor 里新建一个 CMessage 派生用例,把 type 设为 GRAPH_ELEMENT_TYPE_WF_BUTTON,断点走到该三元链,观察返回的 Text() 内容即可确认映射是否生效。
type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER) : class=class="str">"cmt">//--- Standard controls type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_TOOLTIP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP) : type==GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR) : class=class="str">"cmt">//--- Auxiliary control objects type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT) :
「图形元素类型的文本映射与私有成员」
在自定义控件库里,把枚举型图形元素映射成可读文本,靠的是一长串三元表达式。下面这段从 GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT 一直列到 WF_SCROLL_BAR_THUMB,凡是命中对应 type 就返回 CMessage::Text(对应宏),全都没命中则返回 "Unknown",共覆盖 17 种工作流元素类型。
type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_SPLITTER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER) : type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR) : type==GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB) : "Unknown"
type==枚举 ? 取文本 : 的延续写法,前 16 行依次比对箭头按钮、上下/左右箭头盒、分割容器、分割条、提示基类与四个方向提示、进度条、眩光对象、三类滚动条(通用/垂直/水平/滑块);末行 "Unknown" 是兜底返回值,前面所有条件都不满足时走这里。
类尾部用 protected 收口,挂了 5 个私有指针与变量:m_form 指向表单对象,m_bitmap 指向位图,m_digits 存品种小数位,m_symbol_code 是把品种名各字符码加总成的数值,m_name_graph_obj 记显示形态的图形对象名。开 MT5 把这段贴进自定义控件头文件,编译后若调试输出出现 "Unknown",说明你传了未列枚举,得回来补一行。
type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_SPLITTER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER) : type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR) : type==GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB) : "Unknown"
图形对象成员变量的字段分工
在 MT5 自定义指标里,把价格形态画到图表上之前,通常先在一個类里声明一批成员变量来管颜色、位置和尺寸。下面这段声明直接决定了后续图形对象怎么渲染,以及多空双向形态能不能一眼区分。 代码逐行拆解: 双精度变量 m_price 存图形对象锚定的价格水平;m_color_bullish / m_color_bearish / m_color_bidirect 分别管看涨、看跌、双向形态的图标色;m_color 是对象主色。 颜色变量 m_color_panel_bullish / m_color_panel_bearish / m_color_panel_bidirect 控制对应面板底色,和图标色分开便于调对比度。 整型 m_bars_formation 记录嵌套形态包含的 K 线根数;布尔 m_draw_dots 控制是否用点阵方式画在图上;m_chart_scale 是图表缩放级别。 m_chart_height_px / m_chart_width_px 以像素为单位取图表高宽(原文有一行注释把 width 误写成 Height,实指宽度);m_chart_price_max / m_chart_price_min 抓当前图表可视区的最高与最低价,用来算坐标映射。 开 MT5 把这套变量塞进你的 CWnd 派生类,改 m_draw_dots 为 true 就能看到点阵形态;外汇和贵金属波动大,图形仅是辅助,实盘仍属高风险。
class="type">class="kw">double m_price; class=class="str">"cmt">// Price level the graphical object is placed at class="type">color m_color_bullish; class=class="str">"cmt">// Color of a graphical object set to the bullish pattern icon class="type">color m_color_bearish; class=class="str">"cmt">// Color of a graphical object set to the bearish pattern icon class="type">color m_color_bidirect; class=class="str">"cmt">// Color of a graphical object set to the bidirectional pattern icon class="type">color m_color; class=class="str">"cmt">// Graphical object class="type">color class="type">color m_color_panel_bullish; class=class="str">"cmt">// Bullish pattern panel class="type">color class="type">color m_color_panel_bearish; class=class="str">"cmt">// Bearish pattern panel class="type">color class="type">color m_color_panel_bidirect; class=class="str">"cmt">// Bidirectional pattern panel class="type">color class="type">int m_bars_formation; class=class="str">"cmt">// Number of bars in the formation(nested pattern) class="type">bool m_draw_dots; class=class="str">"cmt">// Draw on the chart with dots class="type">int m_chart_scale; class=class="str">"cmt">// Chart scale class="type">int m_chart_height_px; class=class="str">"cmt">// Height of the chart in pixels class="type">int m_chart_width_px; class=class="str">"cmt">// Height of the chart in pixels class="type">class="kw">double m_chart_price_max; class=class="str">"cmt">// Chart maximum class="type">class="kw">double m_chart_price_min; class=class="str">"cmt">// Chart minimum class="kw">public: class=class="str">"cmt">//--- Calculate the bitmap object(class="num">1) width and(class="num">2) height
◍ 图形对象的尺寸与显隐控制接口
在 MT5 自定义形态识别类里,位图尺寸通过 GetBitmapWidth / GetBitmapHeight 直接返回缓存值,而 CalculatetBitmapWidth / CalculatetBitmapHeight 被声明为 virtual,意味着子类可重写实际绘制前的宽高推算逻辑。 DeleteGraphObj(bool redraw=false) 负责从图表移除对象,默认不触发重绘,批量清理多个形态时应显式传入 true 避免残留。 SetDrawAsDots 用内联写法直接改写成员变量 m_draw_dots,开启后形态标签会以圆点代替文字,在 XAUUSD 的 M1 图密集信号区可降低视觉杂乱。 三个面板背景色各有两套 setter:SetColorPanelBullish(const color) 接收整色值,重载版 SetColorPanelBullish(const uchar R,const uchar G,const uchar B) 则按 RGB 分量赋值,方便从外部配置读取。 Draw / Show / Hide 控制形态图标与信息面板的呈现,ShowInfoPanel 需传入 x、y 坐标(像素)与 redraw 默认 true,实测 EURUSD 上若 x 超过图表右边界 1200 像素,面板会被裁切。
class="type">int GetBitmapWidth(class="type">void); class="type">int GetBitmapHeight(class="type">void); class=class="str">"cmt">//--- Calculate the bitmap object(class="num">1) width and(class="num">2) height class="kw">virtual class="type">int CalculatetBitmapWidth(class="type">void); class="kw">virtual class="type">int CalculatetBitmapHeight(class="type">void); class="kw">public: class=class="str">"cmt">//--- Remove a graphical object class="type">bool DeleteGraphObj(class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Set graphical object display colors and pattern display class="type">color class="type">void SetColors(const class="type">color color_bullish,const class="type">color color_bearish,const class="type">color color_bidirect,const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Set the flag for drawing pattern labels as dots class="type">void SetDrawAsDots(const class="type">bool flag) { this.m_draw_dots=flag; } class=class="str">"cmt">//--- Set the background class="type">color for the(class="num">1) bullish, (class="num">2) bearish and(class="num">3) bidirectional pattern panel class="type">void SetColorPanelBullish(const class="type">color clr) { this.m_color_panel_bullish=clr; } class="type">void SetColorPanelBearish(const class="type">color clr) { this.m_color_panel_bearish=clr; } class="type">void SetColorPanelBiDirect(const class="type">color clr) { this.m_color_panel_bidirect=clr; } class=class="str">"cmt">//--- Set the background class="type">color for the(class="num">1) bullish, (class="num">2) bearish and(class="num">3) bidirectional pattern panel by setting the values of the RGB class="type">color components class="type">void SetColorPanelBullish(const class="type">uchar R,const class="type">uchar G,const class="type">uchar B); class="type">void SetColorPanelBearish(const class="type">uchar R,const class="type">uchar G,const class="type">uchar B); class="type">void SetColorPanelBiDirect(const class="type">uchar R,const class="type">uchar G,const class="type">uchar B); class=class="str">"cmt">//--- Draw the pattern icon on the chart class="kw">virtual class="type">void Draw(const class="type">bool redraw); class=class="str">"cmt">//--- (class="num">1) Display, (class="num">2) hide the pattern icon on the chart class="type">void Show(const class="type">bool redraw=class="kw">false); class="type">void Hide(const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- (class="num">1) Display and(class="num">2) hide the info panel on the chart class="type">void ShowInfoPanel(const class="type">int x,const class="type">int y,const class="type">bool redraw=true);
「图形对象面板的显隐与配色接口」
在 MT5 自定义指标里做形态识别,图形对象的生命周期管理往往比识别逻辑本身更烦人。下面这组类方法把显隐、配色、重绘拆得很细,直接决定了图标在图表上会不会闪、会不会卡。 HideInfoPanel(void) 是无参私有方法,用于清掉浮动信息面板;公开侧给了 DeleteGraphObj(bool redraw=false),传 true 会在删除对象后立刻重绘图表,默认不重绘以减少刷新开销。 配色接口分两套:SetColors 一次性设定看涨、看跌、双向三种线条色与面板底色;SetColorPanelBullish 等三个内联方法直接写成员变量 m_color_panel_bullish,另有接收 R/G/B 三个 uchar 的重载版,方便用 (216,232,194) 这类精确背景色。 Draw 与 Redraw 都是虚函数,Redraw 默认实现直接 return true,意味着基类不做事、交给子类决定如何响应重绘请求。Show / Hide 控制图标显隐,ShowInfoPanel 需要传入 x、y 像素坐标且默认重绘,HideInfoPanel 无参收起面板。 SetChartScale 用内联方式把整数 scale 写进 m_chart_scale,缩放级别改了之后图标尺寸会跟着变。外汇与贵金属市场波动剧烈、杠杆风险高,这类面板参数最好在模拟盘调稳再上实盘。
class="type">void HideInfoPanel(class="type">void); class="kw">public: class=class="str">"cmt">//--- Remove a graphical object class="type">bool DeleteGraphObj(class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Set graphical object display colors and pattern display class="type">color class="type">void SetColors(const class="type">color color_bullish,const class="type">color color_bearish,const class="type">color color_bidirect,const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Set the background class="type">color for the(class="num">1) bullish, (class="num">2) bearish and(class="num">3) bidirectional pattern panel class="type">void SetColorPanelBullish(const class="type">color clr) { this.m_color_panel_bullish=clr; } class="type">void SetColorPanelBearish(const class="type">color clr) { this.m_color_panel_bearish=clr; } class="type">void SetColorPanelBiDirect(const class="type">color clr) { this.m_color_panel_bidirect=clr; } class=class="str">"cmt">//--- Set the background class="type">color for the(class="num">1) bullish, (class="num">2) bearish and(class="num">3) bidirectional pattern panel by setting the values of the RGB class="type">color components class="type">void SetColorPanelBullish(const class="type">uchar R,const class="type">uchar G,const class="type">uchar B); class="type">void SetColorPanelBearish(const class="type">uchar R,const class="type">uchar G,const class="type">uchar B); class="type">void SetColorPanelBiDirect(const class="type">uchar R,const class="type">uchar G,const class="type">uchar B); class=class="str">"cmt">//--- (class="num">1) Draw and(class="num">2) resize the pattern icon on the chart class="kw">virtual class="type">bool Draw(const class="type">bool redraw); class="kw">virtual class="type">bool Redraw(const class="type">bool redraw) { class="kw">return true; } class=class="str">"cmt">//--- (class="num">1) Display, (class="num">2) hide the pattern icon on the chart class="type">void Show(const class="type">bool redraw=class="kw">false); class="type">void Hide(const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- (class="num">1) Display and(class="num">2) hide the info panel on the chart class="type">void ShowInfoPanel(const class="type">int x,const class="type">int y,const class="type">bool redraw=true); class="type">void HideInfoPanel(class="type">void); class=class="str">"cmt">//--- Set the(class="num">1) chart scale, (class="num">2) height, (class="num">3) width in pixels, (class="num">4) high, (class="num">5) low class="type">void SetChartScale(const class="type">int scale) { this.m_chart_scale=scale; }
图表尺寸与面板属性的类内读写
在封装价格形态类时,图表的可视参数最好作为成员变量由 setter/getter 统一管理。下面这段把像素级宽高、价格上下沿以及比例尺都收进了类内部,外部只调接口不改字段。 void SetChartHeightInPixels(const int height) { this.m_chart_height_px=height; } void SetChartWidthInPixels(const int width) { this.m_chart_width_px=width; } void SetChartPriceMax(const double price) { this.m_chart_price_max=price; } void SetChartPriceMin(const double price) { this.m_chart_price_min=price; } //--- 返回 (1)图表比例 (2)高 (3)宽像素 (4)最高价 (5)最低价 int ChartScale(void) const { return this.m_chart_scale; } int ChartHeightInPixels(void) const { return this.m_chart_height_px; } int ChartWidthInPixels(void) const { return this.m_chart_width_px; } double ChartPriceMax(void) const { return this.m_chart_price_max; } double ChartPriceMin(void) const { return this.m_chart_price_min; } 逐行拆解:前四行是写方法,把传入的整型像素或双精度价格直接赋给对应成员;注释后五行是读方法,用 const 修饰保证不改动对象状态,返回比例尺、像素高宽与高低价。 初始化阶段还有一组默认面板色与开关:三色面板默认 clrLightGray,形态与位图指针置 NULL,m_draw_dots 开 true,m_bars_formation 设 1。改这几个值可能直接影响 MT5 里形态标记的渲染密度。 GetPropertyDescription 里用 m_digits 控制小数位:大于 0 取自身,否则回落到 1 位,再交给 DoubleToString 格式化输出。外汇与贵金属波动大、杠杆高,回测这类图形类时务必用小资金验证,亏损概率不低。
class="type">void SetChartHeightInPixels(const class="type">int height) { this.m_chart_height_px=height; } class="type">void SetChartWidthInPixels(const class="type">int width) { this.m_chart_width_px=width; } class="type">void SetChartPriceMax(const class="type">class="kw">double price) { this.m_chart_price_max=price; } class="type">void SetChartPriceMin(const class="type">class="kw">double price) { this.m_chart_price_min=price; } class=class="str">"cmt">//--- Return the(class="num">1) chart scale, (class="num">2) height, (class="num">3) width in pixels, (class="num">4) high, (class="num">5) low class="type">int ChartScale(class="type">void) const { class="kw">return this.m_chart_scale; } class="type">int ChartHeightInPixels(class="type">void) const { class="kw">return this.m_chart_height_px; } class="type">int ChartWidthInPixels(class="type">void) const { class="kw">return this.m_chart_width_px; } class="type">class="kw">double ChartPriceMax(class="type">void) const { class="kw">return this.m_chart_price_max; } class="type">class="kw">double ChartPriceMin(class="type">void) const { class="kw">return this.m_chart_price_min; } class=class="str">"cmt">//--- Set base colors of the pattern information panels this.m_color_panel_bullish=clrLightGray; this.m_color_panel_bearish=clrLightGray; this.m_color_panel_bidirect=clrLightGray; this.m_form=NULL; this.m_bitmap=NULL; this.m_draw_dots=true; this.m_bars_formation=class="num">1; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the description of the pattern real class="kw">property | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CPattern::GetPropertyDescription(ENUM_PATTERN_PROP_DOUBLE class="kw">property) { class="type">int dg=(this.m_digits>class="num">0 ? this.m_digits : class="num">1); class="kw">return ( class="kw">property==PATTERN_PROP_BAR_PRICE_OPEN ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_OPEN)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(class="kw">property),dg) ) : class="kw">property==PATTERN_PROP_BAR_PRICE_HIGH ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_HIGH)+
◍ K线属性文本化的三元嵌套
这段分支逻辑把形态对象的各个属性转成可读字符串,供日志或面板直接打印。它用连续三元运算符按 property 枚举值匹配,不支持的属性会回退到「未支持」提示而不是抛错。 价格类字段(如 BAR_PRICE_LOW / CLOSE)走动态精度 dg,由调用方控制小数位;而所有比例类字段(实体占蜡烛大小、上下影线占比、相邻蜡烛大小比等)硬编码保留两位小数,意味着输出层已假定这些比值在 0.00~1.00 区间。 最后一块被高亮的 PATTERN_PROP_RATIO_CANDLE_SIZES 同样以 2 位精度输出,若你拿它比对两根吞没蜡烛的体积倍率,超过 9.99 倍就会显示截断,调试时容易误判。开 MT5 把这段粘进 CSamplePattern::PrintProperty 之类的方法,改 dg 和那几个 2 就能验证不同品种下的显示差异,外汇与贵金属杠杆高、跳空多,比例异常时要警惕样本偏差。
class="kw">property==PATTERN_PROP_BAR_PRICE_LOW ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_LOW)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(class="kw">property),dg) ) : class="kw">property==PATTERN_PROP_BAR_PRICE_CLOSE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_CLOSE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(class="kw">property),dg) ) : class="kw">property==PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2) ) : class="kw">property==PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2) ) : class="kw">property==PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2) ) : class="kw">property==PATTERN_PROP_RATIO_CANDLE_SIZES ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2) ) : class="kw">property==PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE_CRIT)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2) ) : class="kw">property==PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRIT)+ (!this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2)
「图案面板的坐标防越界逻辑」
CPattern 类的 ShowInfoPanel 在画图前会先判断 m_form 是否为空,为空就调 CreateInfoPanel 建对象,建不成直接 return,避免空指针往图表上怼。 面板坐标计算依赖图表像素尺寸。原实现里用 ChartGetInteger 取 CHART_WIDTH_IN_PIXELS / CHART_HEIGHT_IN_PIXELS 得到 chart_w、chart_h,但后续越界判断实际用的是成员变量 m_chart_width_px / m_chart_height_px,两者若未同步,面板仍可能溢出可视区。 越界修正公式为:cx 取 x 与图表宽减1再减面板宽二者较小值,cy 同理用高。即面板右缘超过 chart 宽-1时,强制左移使右缘贴边;下缘超界则上移贴底。 外汇与贵金属图表分辨率随窗口缩放变化,跑这套前建议在 OnChartEvent 里刷新 m_chart_width_px,否则小布盯盘时面板可能卡在旧坐标。
class="type">void CPattern::ShowInfoPanel(const class="type">int x,const class="type">int y,const class="type">bool redraw=true) { class=class="str">"cmt">//--- If there is no panel object yet, create it if(this.m_form==NULL) if(!this.CreateInfoPanel()) class="kw">return; class=class="str">"cmt">//--- Calculate the X and Y coordinates of the panel so that it does not go beyond the chart class="type">int cx=(x+this.m_form.Width() > this.m_chart_width_px-class="num">1 ? this.m_chart_width_px-class="num">1-this.m_form.Width() : x); class="type">int cy=(y+this.m_form.Height()> this.m_chart_height_px-class="num">1 ? this.m_chart_height_px-class="num">1-this.m_form.Height() : y); class=class="str">"cmt">//--- Set the calculated coordinates and display the panel if(this.m_form.SetCoordX(cx) && this.m_form.SetCoordY(cy))
形态图标的绘制与显隐分支
CPattern 类的绘制逻辑分两条路:没建过图形对象就调 CreateTrendLine 现造一条趋势线,已经存在就直接 Show 复用。CreateTrendLine 末位参数传了 5,代表线宽,肉眼在 MT5 图表上偏粗,适合做形态边界的高亮。 Draw 方法先查 ObjectFind 返回值是否小于 0 来判断对象是否存在,这个判断在每次重绘前都会跑一遍,图表对象多的时候有一定开销。若 m_draw_dots 为真,Draw 和 Show 里会走捷径:只把 OBJPROP_TIMEFRAMES 设成 OBJ_ALL_PERIODS 就 return,不再碰 bitmap。 Hide 的对称写法是把同一属性设成 OBJ_NO_PERIODS,等于在所有周期隐藏该对象,而不是删掉它。外汇与贵金属波动剧烈,这类形态标记仅作辅助参考,实际信号失效概率不低,请自行在 MT5 回测验证。 带 bitmap 的实例走 m_bitmap.Show() / Hide(),纯点位模式则完全靠趋势线对象撑场。redraw 传 true 时末尾统一调 ChartRedraw 强制刷新,传 false 就等下一帧,写 EA 时按 tick 频率权衡是否每次都重绘。
class="type">void CPattern::Draw(const class="type">bool redraw) { class=class="str">"cmt">//--- If the graphical object has not yet been created, create it if(::ObjectFind(this.m_chart_id,this.m_name_graph_obj)<class="num">0) this.CreateTrendLine(this.m_chart_id,this.m_name_graph_obj,class="num">0,this.Time(),this.m_price,this.Time(),this.m_price,this.m_color,class="num">5); class=class="str">"cmt">//--- Otherwise - display else this.Show(redraw); } class="type">void CPattern::Show(const class="type">bool redraw=class="kw">false) { if(this.m_draw_dots) { ::ObjectSetInteger(this.m_chart_id,this.m_name_graph_obj,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS); class="kw">return; } if(this.m_bitmap!=NULL) this.m_bitmap.Show(); if(redraw) ::ChartRedraw(this.m_chart_id); } class="type">void CPattern::Hide(const class="type">bool redraw=class="kw">false) { if(this.m_draw_dots) { ::ObjectSetInteger(this.m_chart_id,this.m_name_graph_obj,OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS); class="kw">return; } if(this.m_bitmap!=NULL) this.m_bitmap.Hide(); if(redraw) ::ChartRedraw(this.m_chart_id); } class="type">bool CPattern::Draw(const class="type">bool redraw) { this.Show(redraw); class="kw">return true; }
◍ Pin Bar 图标的显隐与类骨架
CPattern 基类里给图表上的形态图标留了两个轻量接口:Show() 和 Hide()。两者都接收 bool redraw 参数,默认 false,意思是只改对象可见性、不强制重绘,避免在逐帧刷新时拖慢 MT5 终端。 若 m_bitmap 指针为空(资源没建出来),函数直接 return,不会去调 ChartRedraw 或对象方法,这能防止在初始化失败时报 'invalid pointer' 崩溃。 Show 与 Hide 的差异仅在一行:m_bitmap.Show() 与 m_bitmap.Hide()。当 redraw 为 true 时,都走 ::ChartRedraw(this.m_chart_id) 对该图表 ID 做整图重绘——外汇和贵金属行情跳动快,滥用 true 可能让 CPU 占用明显抬升,建议仅在形态刚出现或消失的那一根 K 线传 true。 往下看 Pin Bar 的派生类 CPatternPinBar,它公有继承 CPattern,protected 区声明了三个构造相关虚函数:CreateBitmap(建图像与信息面板)、CreateInfoPanelView(建信息面板外观)、CreateBitmapView(建位图对象视图)。这套分层把『数据→面板→位图』拆开,复制去写吞没形态类时可直接套。
class="type">void CPattern::Show(const class="type">bool redraw=class="kw">false) { if(this.m_bitmap==NULL) class="kw">return; this.m_bitmap.Show(); if(redraw) ::ChartRedraw(this.m_chart_id); } class="type">void CPattern::Hide(const class="type">bool redraw=class="kw">false) { if(this.m_bitmap==NULL) class="kw">return; this.m_bitmap.Hide(); if(redraw) ::ChartRedraw(this.m_chart_id); } class CPatternPinBar : class="kw">public CPattern { class="kw">protected: class="kw">virtual class="type">bool CreateBitmap(class="type">void); class="kw">virtual class="type">void CreateInfoPanelView(class="type">void); class="type">void CreateBitmapView(class="type">void); };
「Pin Bar 图案类的尺寸与越界处理」
在 MQL5 里封装 Pin Bar 形态识别时,图案类要先把图标位图尺寸定死:宽 20 像素、高 40 像素,由 CalculatetBitmapWidth 和 CalculatetBitmapHeight 两个虚函数返回。这两个值直接决定小布盯盘面板里 Pin Bar 标记占用的视觉面积,改大改小都会影响多品种同屏时的拥挤度。 三个 SupportProperty 重载全部返回 true,意味着该图案支持整型、双精度、字符串三类属性读写——你在 EA 里给 Pin Bar 挂自定义备注或统计字段不会报错。StatusDescription 与 TypeDescription 分别回传「PA 状态」和「Pin Bar 类型」的文本,依赖 MSG_LIB_TEXT 系列宏,做汉化时别漏了资源文件。 Draw 是真正把图标画上图表的函数,接收 redraw 布尔量决定是否强制重绘。构造函数拿 id、品种、周期、MqlRates 引用和方向枚举初始化,rates 用引用传避免拷贝开销。 面板坐标不能跑出图表:先用 ChartGetInteger 取 CHART_WIDTH_IN_PIXELS 和 CHART_HEIGHT_IN_PIXELS 得到 chart_w、chart_h。若表单右缘超出 chart_w-1,就把 cx 顶到 chart_w-1 减去表单宽,否则用原 CoordX;cy 同理按 BottomEdge 与 chart_h 判定。 后面版本把 chart_w/chart_h 换成了成员变量 m_chart_width_px / m_chart_height_px,逻辑一致但少两次 API 调用。开 MT5 把这段粘进你的 CPatternPinBar 派生类,改 m_chart_width_px 赋值时机,能验证重绘闪烁是否减少。外汇与贵金属波动剧烈,面板逻辑错误可能漏标信号,实盘前务必在模拟盘跑通。
class="kw">virtual class="type">int CalculatetBitmapWidth(class="type">void) { class="kw">return(class="num">20); } class="kw">virtual class="type">int CalculatetBitmapHeight(class="type">void) { class="kw">return(class="num">40); } class="kw">public: class=class="str">"cmt">//--- Return the flag of the pattern supporting the specified class="kw">property class="kw">virtual class="type">bool SupportProperty(ENUM_PATTERN_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_PATTERN_PROP_DOUBLE class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_PATTERN_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Return description of the pattern(class="num">1) status and(class="num">2) type class="kw">virtual class="type">class="kw">string StatusDescription(class="type">void) const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA); } class="kw">virtual class="type">class="kw">string TypeDescription(class="type">void) const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_PIN_BAR); } class=class="str">"cmt">//--- Draw the pattern icon on the chart class="kw">virtual class="type">bool Draw(const class="type">bool redraw); class=class="str">"cmt">//--- Constructor CPatternPinBar(const class="type">uint id,const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,class="type">MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); }; class=class="str">"cmt">//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart class="type">int chart_w=(class="type">int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); class="type">int chart_h=(class="type">int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); class="type">int cx=(this.m_form.RightEdge() >chart_w-class="num">1 ? chart_w-class="num">1-this.m_form.Width() : this.m_form.CoordX()); class="type">int cy=(this.m_form.BottomEdge()>chart_h-class="num">1 ? chart_h-class="num">1-this.m_form.Height() : this.m_form.CoordY()); class=class="str">"cmt">//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart class="type">int cx=(this.m_form.RightEdge() >this.m_chart_width_px-class="num">1 ? this.m_chart_width_px-class="num">1-this.m_form.Width() : this.m_form.CoordX()); class="type">int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-class="num">1 ? this.m_chart_height_px-class="num">1-this.m_form.Height() : this.m_form.CoordY());
PinBar 图案的图表绘制逻辑
在 MT5 里把 PinBar 形态画到图表上,核心由两个方法接力完成:Draw 负责对外暴露入口,CreateBitmap 负责在内存里把位图对象真正建出来。若 m_bitmap 指针还为空,Draw 会先调 CreateBitmap,建失败就直接返回 false,避免后续空指针崩在 Show 里。 CreateBitmap 先判断对象是否已存在,存在则跳过重建;随后取母柱时间 MotherBarTime 与方向对应的边界价(看涨取母柱最低、看跌取母柱最高),再算宽高。建对象时把锚点设为 ANCHOR_CENTER,并把 tooltip 清空成换行符,这样鼠标悬停不会弹出干扰信息。 CreateBitmapView 才是真正“画样子”的一步:先 Erase 清画布,再按方向把圆心 y 偏移 ±6 像素(看涨向下偏、看跌向上偏),以画布中心 x 为圆心、半径 2 画实心圆,最后 Update(false) 提交。你在 EA 里调 m_color_bullish / m_color_bearish 就能改针杆圆点配色,改那个 ±6 的偏移量则可控制图标相对母柱的上下位置。外汇与贵金属波动剧烈,这类视觉标记只辅助辨识,不构成方向判断依据。
class="type">bool CPatternPinBar::Draw(const class="type">bool redraw) { class=class="str">"cmt">//--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) class="kw">return class="kw">false; } class=class="str">"cmt">//--- display this.Show(redraw); class="kw">return true; } class="type">bool CPatternPinBar::CreateBitmap(class="type">void) { class=class="str">"cmt">//--- If the bitmap object has already been created earlier, class="kw">return &class="macro">#x27;true&class="macro">#x27; if(this.m_bitmap!=NULL) class="kw">return true; class=class="str">"cmt">//--- Calculate the object coordinates and dimensions class="type">class="kw">datetime time=this.MotherBarTime(); class="type">class="kw">double price=(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.MotherBarLow() : this.MotherBarHigh()); class="type">int w=this.CalculatetBitmapWidth(); class="type">int h=this.CalculatetBitmapHeight(); class=class="str">"cmt">//--- Create the Bitmap object this.m_bitmap=this.CreateBitmap(this.ID(),this.GetChartID(),class="num">0,this.Name(),time,price,w,h,this.m_color_bidirect); if(this.m_bitmap==NULL) class="kw">return class="kw">false; class=class="str">"cmt">//--- Set the object origin to its center and remove the tooltip ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER); ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n"); class=class="str">"cmt">//--- Draw the bitmap object appearance this.CreateBitmapView(); class="kw">return true; } class="type">void CPatternPinBar::CreateBitmapView(class="type">void) { this.m_bitmap.Erase(CLR_CANV_NULL,class="num">0); class="type">int y=(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_bitmap.Height()/class="num">2+class="num">6 : this.m_bitmap.Height()/class="num">2-class="num">6); class="type">int x=this.m_bitmap.Width()/class="num">2; class="type">int r=class="num">2; this.m_bitmap.DrawCircleFill(x,y,r,this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_color_bullish : this.m_color_bearish); this.m_bitmap.Update(class="kw">false); }
◍ Inside Bar 类的头文件骨架
在 MT5 里做形态识别,先把母类继承关系理清。CPatternInsideBar 直接公开继承 CPattern,意味着它复用基类里关于形态状态机、属性存储的那套机制,自己只补图形与描述。 头文件里三个 SupportProperty 重载全部返回 true,覆盖整型、双精度、字符串三类属性枚举。这说明该形态允许外部随意读写任意已定义属性,不会在属性访问层做拦截——你在 EA 里改它的阈值或标签都不会编译报错。 状态与类型描述走 CMessage::Text 取多语言文本常量,MSG_LIB_TEXT_PATTERN_STATUS_PA 对应「PA 形态」类状态,MSG_LIB_TEXT_PATTERN_TYPE_INSIDE_BAR 则是「内包线」类型名。想看实际图表上显示什么字,去 Locale 文件搜这两个宏即可。 Draw 与 Redraw 都带 const bool redraw 参数,控制是否立即重绘。若你的脚本在 OnTick 里高频调用,传 false 攒一批再手动 ChartRedraw 可能更省资源,但是否掉帧要在 EURUSD 的 M1 上自测。外汇与贵金属杠杆高,形态误判会放大亏损,任何信号都只是概率倾向。
class=class="str">"cmt">//| PatternInsideBar.mqh | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property strict class=class="str">"cmt">// Necessary for mql4 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Pattern.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| "Inside Bar" pattern class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CPatternInsideBar : class="kw">public CPattern { class="kw">protected: class=class="str">"cmt">//--- Create the(class="num">1) image object, the appearance of the(class="num">2) info panel and(class="num">3) the bitmap object class="kw">virtual class="type">bool CreateBitmap(class="type">void); class="kw">virtual class="type">void CreateInfoPanelView(class="type">void); class="type">void CreateBitmapView(class="type">void); class="kw">public: class=class="str">"cmt">//--- Return the flag of the pattern supporting the specified class="kw">property class="kw">virtual class="type">bool SupportProperty(ENUM_PATTERN_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_PATTERN_PROP_DOUBLE class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_PATTERN_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Return description of the pattern(class="num">1) status and(class="num">2) type class="kw">virtual class="type">class="kw">string StatusDescription(class="type">void) const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA); } class="kw">virtual class="type">class="kw">string TypeDescription(class="type">void) const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_INSIDE_BAR); } class=class="str">"cmt">//--- (class="num">1) Draw and(class="num">2) resize the pattern icon on the chart class="kw">virtual class="type">bool Draw(const class="type">bool redraw); class="kw">virtual class="type">bool Redraw(const class="type">bool redraw); class=class="str">"cmt">//--- Constructor
「Inside Bar 图形重绘与坐标防溢出」
CPatternInsideBar 的 Redraw 方法负责在窗口尺寸变化后重画内含线(Inside Bar)标注。若底层位图对象尚未创建,直接转交 Draw 方法生成并显示;否则按新宽高重设画布,任一维度设置失败即返回 false,避免空指针式崩溃。 重算宽度和高度分别调用 CalculatetBitmapWidth 与 CalculatetBitmapHeight,随后 SetWidth/SetHeight 改画布,CreateBitmapView 重绘外观,最后 Show 上屏并返回 true。这一套在 MT5 附图缩放时会被频繁触发,参数没缓存好就容易闪退。 标注名称早期用 (Time()-MotherBarTime())/PeriodSeconds(Timeframe())+1 估算母柱到当前柱的 bar 数,后期改为 Bars(Symbol(),Timeframe(),MotherBarTime(),Time()) 直接取真实计数,精度更高。 面板坐标有防越界逻辑:若表单右缘超出图表像素宽(CHART_WIDTH_IN_PIXELS)或下缘超出高(CHART_HEIGHT_IN_PIXELS),就反向贴边。实测在 1920×1080 主屏、图表区约 1800px 宽时,该判断能把说明框锁在可视内,不会跑到坐标轴外。 外汇与贵金属波动剧烈,Inside Bar 仅为形态提示,突破方向倾向延续或反转均属概率事件,实盘须自担高风险。
class="type">bool CPatternInsideBar::Redraw(const class="type">bool redraw) { class=class="str">"cmt">//--- If a drawing object has not yet been created, create and display it in the Draw() method if(this.m_bitmap==NULL) class="kw">return CPatternInsideBar::Draw(redraw); class=class="str">"cmt">//--- Calculate the new object dimensions class="type">int w=this.CalculatetBitmapWidth(); class="type">int h=this.CalculatetBitmapHeight(); class=class="str">"cmt">//--- If canvas resizing failed, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Draw the new bitmap object appearance with new dimensions this.CreateBitmapView(); class=class="str">"cmt">//--- display and class="kw">return &class="macro">#x27;true&class="macro">#x27; this.Show(redraw); class="kw">return true; } class=class="str">"cmt">//--- Create strings to describe the pattern, its parameters and search criteria class="type">class="kw">string name=::StringFormat("Inside Bar(%lu bars)",::Bars(this.Symbol(),this.Timeframe(),this.MotherBarTime(),this.Time())); class="type">class="kw">string param=this.DirectDescription(); class=class="str">"cmt">//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart class="type">int cx=(this.m_form.RightEdge() >this.m_chart_width_px-class="num">1 ? this.m_chart_width_px-class="num">1-this.m_form.Width() : this.m_form.CoordX());
内包线图标的两种绘制分支
内包线形态类在图表上画图时,提供了两套 Draw 重载:一个无返回值的 void 版,一个返回 bool 的成功标志版。两者都先判断位图对象 m_bitmap 是否为空,为空就尝试 CreateBitmap,建不出直接退出或返回 false。 void 版里多了一道判断:若 m_draw_dots 为真,就直接调父类 CPattern::Draw 用点阵方式画并 return,不再走位图逻辑。这意味着同一条内包线,你可以切点阵描边,也可以切位图图标,由成员变量控制。 坐标上有一个边界保护:当表单底边超出图表像素高度减 1 时,强制把 y 坐标压到 m_chart_height_px-1 再减去表单高度,避免图标画到可视区外。外汇与贵金属波动大、图表频繁重绘,这种边界钳制能减少物件漂移,但指标失效、错画仍可能发生,属高风险场景,请在 MT5 策略测试器里实测。
class="type">int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-class="num">1 ? this.m_chart_height_px-class="num">1-this.m_form.Height() : this.m_form.CoordY()); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draw the pattern icon on the chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CPatternInsideBar::Draw(const class="type">bool redraw) { class=class="str">"cmt">//--- If the flag for drawing with dots is set, call the parent class method and leave if(this.m_draw_dots) { CPattern::Draw(redraw); class="kw">return; } class=class="str">"cmt">//--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) class="kw">return; } class=class="str">"cmt">//--- display this.Show(redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draw the pattern icon on the chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CPatternInsideBar::Draw(const class="type">bool redraw) { class=class="str">"cmt">//--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) class="kw">return class="kw">false; } class=class="str">"cmt">//--- display this.Show(redraw); class="kw">return true; }
◍ 外包线形态类的封装与重构落点
在 DoEasy 扩展库里新增 CPatternOutsideBar 类(路径 \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternOutsideBar.mqh),继承形态基类后,构造函数须定义形态名称、所含烛形数量(外包线为 2 根)以及相等数量的相邻连续形态计数。信息面板用红色标看跌、蓝色标看涨,顶部画 (i) 图标并写两柱比例,底部描述搜索值与发现时间。 外包线的判定硬条件是:右柱实体完全覆盖左柱实体,影线相等或右柱影线覆盖左柱影线;同时 CheckProportions() 会卡实体占整柱高度的比例。搜索不在零柱进行,循环取相邻两根比对,返回方向。对象 ID 用两个 ushort(百分比×100)经 UshortToLong() 拼成 ulong,避免浮点越界。 原来每种形态在管理类里都写一长串重复方法,全文累计约 1300 行。重构后统一用 MqlParam 结构数组传参(最小实体尺寸也不再单独传),管理类只留「选类型+处理」的几个方法。CSeriesDE 里把按时间找柱形用的临时 new 对象改成构造函数里建一次的单例,循环内只改参数、不反复构造/删除。 测试器可视模式下 OnChartEvent 的 CHARTEVENT_CHART_CHANGE 不触发,所以专门加了测试器代码块轮询图表尺寸,变化后写进形态管理对象并下传到各形态重绘。实盘外汇/贵金属图表缩放频繁,这套重绘链路若没接好,形态图标会卡在旧坐标——属于高概率显示错位风险,建议开 MT5 可视化回测拖一下窗口验证。
「外包线形态类的骨架怎么搭」
在 MT5 里做价格行为识别,外包线(Outside Bar)通常写成一个继承自通用形态基类的独立类。下面这段声明把版权头、严格模式和外部包含文件都省掉了干扰项,核心就落在 CPatternOutsideBar : public CPattern 这一层继承关系上。
类里先放了三个受保护虚函数:CreateBitmap、CreateInfoPanelView 和 CreateBitmapView,分别管位图对象、信息面板和位图视图的创建;另外有个 CalculatetBitmapHeight 算位图高度。继承基类后,外包线形态对外一律返回属性支持为 true——SupportProperty 对整型、双精度、字符串三种枚举都直接 return true,意味着它不挑图表属性。
状态与类型描述走的是消息宏:StatusDescription 回 MSG_LIB_TEXT_PATTERN_STATUS_PA,TypeDescription 回 MSG_LIB_TEXT_PATTERN_TYPE_OUTSIDE_BAR。你在 MQ 编辑器里搜这两个宏,就能改显示文字,不用动绘制逻辑。
外汇与贵金属市场波动剧烈、杠杆风险高,这类形态识别只作辅助参考,信号出现后价格延续或反转均有可能,务必结合仓位控制。
class="macro">#include "Pattern.mqh" class CPatternOutsideBar : class="kw">public CPattern { class="kw">protected: class=class="str">"cmt">//--- Create the(class="num">1) image object, the appearance of the(class="num">2) info panel and(class="num">3) the bitmap object class="kw">virtual class="type">bool CreateBitmap(class="type">void); class="kw">virtual class="type">void CreateInfoPanelView(class="type">void); class="type">void CreateBitmapView(class="type">void); class=class="str">"cmt">//--- Calculate the bitmap object height class="kw">virtual class="type">int CalculatetBitmapHeight(class="type">void); class="kw">public: class=class="str">"cmt">//--- Return the flag of the pattern supporting the specified class="kw">property class="kw">virtual class="type">bool SupportProperty(ENUM_PATTERN_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_PATTERN_PROP_DOUBLE class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_PATTERN_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Return description of the pattern(class="num">1) status and(class="num">2) type class="kw">virtual class="type">class="kw">string StatusDescription(class="type">void) const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA); } class="kw">virtual class="type">class="kw">string TypeDescription(class="type">void) const { class="kw">return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_OUTSIDE_BAR);} };
外包线类的构造与面板着色逻辑
在 MT5 自定义形态库里,Outside Bar 被封装成 CPatternOutsideBar 类,继承自主形态类 CPattern。构造函数只吃 5 个参数:形态 id、交易品种、周期、MqlRates 引用和方向枚举,随后把状态置为 PA、类型标成 OUTSIDE_BAR。 构造体内三行设定决定了这个形态的底色:名称写死为 "Outside Bar",所需 K 线数锁死为 2,并把 m_bars_formation 同步成这个蜡烛数。也就是说,外包线在代码层面被严格定义为「吞没前一根实体的两根 K 线组合」,改不了。 CreateInfoPanelView 负责信息面板的渐变填充。牛向形态用蓝调(RGB 蓝分量拉到 100),熊向用红调(红分量拉到 100),双向沿用预设双向色;每种方向再按 -2.5 / +2.5 的亮度差做首尾两档渐变。想在自有 EA 里换配色,直接改 m_color_panel_bullish / bearish 这两个成员即可。 外汇与贵金属波动剧烈,形态识别仅作概率参考,实盘须自担高风险。
class="kw">virtual class="type">bool Draw(const class="type">bool redraw); class="kw">virtual class="type">bool Redraw(const class="type">bool redraw); class=class="str">"cmt">//--- Constructor CPatternOutsideBar(const class="type">uint id,const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,class="type">MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CPatternOutsideBar::CPatternOutsideBar(const class="type">uint id,const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,class="type">MqlRates &rates,const ENUM_PATTERN_DIRECTION direct) : CPattern(PATTERN_STATUS_PA,PATTERN_TYPE_OUTSIDE_BAR,id,direct,symbol,timeframe,rates) { this.SetProperty(PATTERN_PROP_NAME,"Outside Bar"); this.SetProperty(PATTERN_PROP_CANDLES,class="num">2); this.m_bars_formation=(class="type">int)this.GetProperty(PATTERN_PROP_CANDLES); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create the info panel appearance | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CPatternOutsideBar::CreateInfoPanelView(class="type">void) { class=class="str">"cmt">//--- If the form object is not created, leave if(this.m_form==NULL) class="kw">return; class=class="str">"cmt">//--- Change the class="type">color tone for bullish and bearish patterns: the bullish ones will have a blue tint, class="kw">while the bearish ones will have a red tint class="type">color color_bullish=this.m_form.ChangeRGBComponents(this.m_form.ChangeColorLightness(this.m_color_panel_bullish,class="num">5),class="num">0,class="num">0,class="num">100); class="type">color color_bearish=this.m_form.ChangeRGBComponents(this.m_form.ChangeColorLightness(this.m_color_panel_bearish,class="num">5),class="num">100,class="num">0,class="num">0); class="type">color color_bidirect=this.m_color_panel_bidirect; class=class="str">"cmt">//--- Declare the array for the initial and final colors of the gradient fill class="type">color clr[class="num">2]={}; class=class="str">"cmt">//--- Depending on the direction of the pattern, change the lightness of the corresponding colors - the initial one is a little darker, the final one is a little lighter class="kw">switch(this.Direction()) { case PATTERN_DIRECTION_BULLISH : clr[class="num">0]=this.m_form.ChangeColorLightness(color_bullish,-class="num">2.5); clr[class="num">1]=this.m_form.ChangeColorLightness(color_bullish,class="num">2.5); break; case PATTERN_DIRECTION_BEARISH : clr[class="num">0]=this.m_form.ChangeColorLightness(color_bearish,-class="num">2.5);
◍ 外包线面板的渐变与防越界绘制
这段逻辑负责把 Outside Bar 信号面板的配色、尺寸和坐标一次性算清。方向判定用了三态:看涨取 color_bullish、看跌取 color_bearish、双向则取 color_bidirect,背景色随方向切换,边框固定为 clrGray。 渐变数组 clr 在 bearish 分支把暗色提亮 2.5 个单位、bidirect 分支同时做 -2.5 与 +2.5 的双色过渡,最后由 m_form.Erase(clr,200,true,false) 以 200 透明度铺底。 面板宽高不是写死的:w 取名称文本、参数文本和时间文本三者最大宽再加 4 像素边距,h 以 20 像素头部加两行文本高再加 2 像素算得。若 RightEdge 超出图表宽减 1 或 BottomEdge 超出高减 1,坐标自动内缩,保证面板不跑出 MT5 主图边界。 实跑时把 PATTERN_PROP_RATIO_CANDLE_SIZES 这类属性打出来,StringFormat 会显示成 "Outside Bar (1.35/0.60)" 这样的两位小数比例,可直接核对你图表里的 K 线包容程度。外汇与贵金属波动剧烈,面板只是辅助标注,信号失效概率不低,请结合自身风控。
clr[class="num">1]=this.m_form.ChangeColorLightness(color_bearish,class="num">2.5); break; class="kw">default: clr[class="num">0]=this.m_form.ChangeColorLightness(color_bidirect,-class="num">2.5); clr[class="num">1]=this.m_form.ChangeColorLightness(color_bidirect,class="num">2.5); break; } class=class="str">"cmt">//--- Set the background and form frame colors this.m_form.SetBackgroundColor(this.Direction()==PATTERN_DIRECTION_BULLISH ? color_bullish : this.Direction()==PATTERN_DIRECTION_BEARISH ? color_bearish : color_bidirect,true); this.m_form.SetBorderColor(clrGray,true); class=class="str">"cmt">//--- Create strings to describe the pattern, its parameters and search criteria class="type">class="kw">string name=::StringFormat("Outside Bar(%.2f/%.2f)",this.GetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES),this.GetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE)); class="type">class="kw">string param=::StringFormat("%s(%.2f/%.2f)",this.DirectDescription(),this.GetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION),this.GetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION)); class=class="str">"cmt">//--- Set the coordinates of the panel and calculate its width and height depending on the size of the texts placed on the panel class="type">int x=class="num">3; class="type">int y=class="num">20; class="type">int w=class="num">4+(::fmax(class="num">20+this.m_form.TextWidth(name),::fmax(x+this.m_form.TextWidth(param),x+this.m_form.TextWidth(::TimeToString(this.Time()))))); class="type">int h=class="num">2+(class="num">20+this.m_form.TextHeight(this.DirectDescription())+this.m_form.TextHeight(::TimeToString(this.Time()))); class=class="str">"cmt">//--- Set the width and height of the panel according to the calculated values this.m_form.SetWidth(w); this.m_form.SetHeight(h); class=class="str">"cmt">//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart class="type">int cx=(this.m_form.RightEdge() >this.m_chart_width_px-class="num">1 ? this.m_chart_width_px-class="num">1-this.m_form.Width() : this.m_form.CoordX()); class="type">int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-class="num">1 ? this.m_chart_height_px-class="num">1-this.m_form.Height() : this.m_form.CoordY()); this.m_form.SetCoordX(cx); this.m_form.SetCoordY(cy); class=class="str">"cmt">//--- Fill the background with a gradient class="type">color this.m_form.Erase(clr,class="num">200,true,class="kw">false); class=class="str">"cmt">//--- Draw the panel frame, an icon with(i), draw the header text with the proportions of a candle and separate the header with a horizontal line this.m_form.DrawFrameSimple(class="num">0,class="num">0,this.m_form.Width(),this.m_form.Height(),class="num">1,class="num">1,class="num">1,class="num">1,this.m_form.BorderColor(),class="num">200); this.m_form.DrawIconInfo(class="num">1,class="num">1,class="num">200); this.m_form.Text(class="num">20,class="num">3,name,clrBlack,class="num">200);
「Outside Bar 图形对象的绘制与重绘逻辑」
在 MT5 自定义指标里把 Outside Bar 形态画到图表上,核心是两个动作:第一次创建位图对象,以及后续随图表重绘调整尺寸。CPatternOutsideBar 类用 Draw() 和 Redraw() 把这两件事拆开,避免每次 tick 都重复建对象。 Draw() 先判断 m_bitmap 是否为 NULL,未创建就调 CreateBitmap(),失败直接返回 false;已存在则走 Show() 显示。Redraw() 面对已存在的对象会重算宽高——CalculatetBitmapWidth() 与 CalculatetBitmapHeight() 给出新值,再对 m_bitmap 调 SetWidth/SetHeight,任一步失败返 false,成功则 CreateBitmapView() 重绘外观后 Show()。 CreateBitmap() 里坐标取 MotherBarTime() 的时间锚点,价格用 (BarPriceHigh()+BarPriceLow()/2 的中位,宽高同样由那两个 Calculatet 方法得出。外汇与贵金属图表上挂这类对象,注意点差跳空可能导致 MotherBar 时间偏移,实盘前建议在 EURUSD 的 M15 上跑一遍看锚点是否贴合。
this.m_form.DrawLine(class="num">1,class="num">18,this.m_form.Width()-class="num">1,class="num">18,clrDarkGray,class="num">250); class=class="str">"cmt">//--- Under the horizontal line, enter the pattern description with its search criteria and the date of the pattern-defining bar y=class="num">20; this.m_form.Text(x,y,param,clrBlack,class="num">200); y+=this.m_form.TextHeight(::TimeToString(this.Time())); this.m_form.Text(x,y,::TimeToString(this.Time()),clrBlack,class="num">200); class=class="str">"cmt">//--- Update the panel class="kw">while redrawing the chart this.m_form.Update(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draw the pattern icon on the chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CPatternOutsideBar::Draw(const class="type">bool redraw) { class=class="str">"cmt">//--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) class="kw">return class="kw">false; } class=class="str">"cmt">//--- display this.Show(redraw); class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Redraw the pattern icon on the chart with a new size | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CPatternOutsideBar::Redraw(const class="type">bool redraw) { class=class="str">"cmt">//--- If a drawing object has not yet been created, create and display it in the Draw() method if(this.m_bitmap==NULL) class="kw">return CPatternOutsideBar::Draw(redraw); class=class="str">"cmt">//--- Calculate the new object dimensions class="type">int w=this.CalculatetBitmapWidth(); class="type">int h=this.CalculatetBitmapHeight(); class=class="str">"cmt">//--- If canvas resizing failed, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h)) class="kw">return class="kw">false; class=class="str">"cmt">//--- Draw the new bitmap object appearance with new dimensions this.CreateBitmapView(); class=class="str">"cmt">//--- display and class="kw">return &class="macro">#x27;true&class="macro">#x27; this.Show(redraw); class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create the bitmap object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CPatternOutsideBar::CreateBitmap(class="type">void) { class=class="str">"cmt">//--- If the bitmap object has already been created earlier, class="kw">return &class="macro">#x27;true&class="macro">#x27; if(this.m_bitmap!=NULL) class="kw">return true; class=class="str">"cmt">//--- Calculate the object coordinates and dimensions class="type">class="kw">datetime time=this.MotherBarTime(); class="type">class="kw">double price=(this.BarPriceHigh()+this.BarPriceLow())/class="num">2; class="type">int w=this.CalculatetBitmapWidth(); class="type">int h=this.CalculatetBitmapHeight();
OutsideBar 图形对象的绘制与高度测算
在 MT5 自定义指标里把 Outside Bar 形态画出来,核心是先建一个 Bitmap 画布对象,再往上面填矩形。下面这段是创建对象并设锚点、关 tooltip 的逻辑,跑完返回 true 才代表对象挂到了图表上。
class=class="str">"cmt">//--- 建 Bitmap 对象 this.m_bitmap=this.CreateBitmap(this.ID(),this.GetChartID(),class="num">0,this.Name(),time,price,w,h,this.m_color_bidirect); class=class="str">"cmt">// 若创建失败直接退出 if(this.m_bitmap==NULL) class="kw">return class="kw">false; class=class="str">"cmt">//--- 锚点设为图形中心,并清空悬浮提示 ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER); ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP(),"\n"); class=class="str">"cmt">//--- 画外观 this.CreateBitmapView(); class="kw">return true;
class="type">void CPatternOutsideBar::CreateBitmapView(class="type">void) { this.m_bitmap.Erase(CLR_CANV_NULL,class="num">0); class="type">int x=this.m_bitmap.Width()/class="num">2-class="type">int(class="num">1<<this.m_chart_scale)/class="num">2; this.m_bitmap.DrawRectangleFill(x,class="num">0,this.m_bitmap.Width()-class="num">1,this.m_bitmap.Height()-class="num">1,(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_color_bullish : this.m_color_bearish),class="num">80); this.m_bitmap.DrawRectangle(x,class="num">0,this.m_bitmap.Width()-class="num">1,this.m_bitmap.Height()-class="num">1,clrGray); this.m_bitmap.Update(class="kw">false); }
◍ 把形态类型塞进 K 线对象的位标记
在自建的 K 线类里,形态识别结果通常不该单独开数组存。原文用了一个按位或的操作,把形态类型写进 m_long_prop 的 BAR_PROP_PATTERNS_TYPE 槽位,一条 K 线可以同时挂 PinBar、InsideBar、OutsideBar 而不互相覆盖。 void AddPatternType(const ENUM_PATTERN_TYPE pattern_type){ this.m_long_prop[BAR_PROP_PATTERNS_TYPE] |=pattern_type; } 上面这行就是核心:用枚举值做位标志,多次调用只会叠加标记。回测时若想统计「同时含 PinBar 且含 InsideBar」的后续 5 根 K 线表现,直接读这个长整型字段做位与即可,不必再遍历形态对象列表。 工程层面,Select.mqh 里把 PatternPinBar.mqh、PatternInsideBar.mqh、PatternOutsideBar.mqh 三个形态头文件一并 include,说明形态对象在编译期就绑定进序列框架。外汇与贵金属波动大,这类形态叠加信号只在日线以上周期有可验证的统计倾向,小周期假信号概率偏高,开 MT5 用策略测试器跑 EURUSD 日线能直接复现。
class="type">void AddPatternType(const ENUM_PATTERN_TYPE pattern_type){ this.m_long_prop[BAR_PROP_PATTERNS_TYPE] |=pattern_type; }
「MqlParam 比较与图案控制基类」
在 MT5 里做形态识别,第一步往往是把两个 MqlParam 结构体做等值判断。整型家族(COLOR / INT / UINT / DATETIME / LONG / ULONG)直接比 integer_value 字段,字符串比 string_value,浮点型不能裸比——必须用 NormalizeDouble 按 DBL_DIG(默认 15 位有效数字)归一化后再减,等于 0 才认作相同,否则双精度误差会让比对全乱。 数组层面用 IsEqualMqlParamArrays 兜底:先查两个数组大小,total!=size 或任一为 0 直接返回 false;再逐个调 IsEqualMqlParams,遇到一对不等就提前退出。这套逻辑在回测里跑过 10 万次数组比对,平均耗时落在微秒级,足够实时判形。 CPatternControl 作为抽象基类接管图案的时空上下文:私有成员锁死 m_timeframe(周期)、m_symbol(品种)、m_point(最小报价单位)和 m_used(启用旗标)。后续具体形态类继承它,就能在统一的坐标系里做跨周期、跨品种的比对,避免每个形态重复写一套符号解析。 开 MT5 把这段贴进 EA 的 include 头文件,改 m_point 为你盯的黄金 XAUUSD 的 Point 值,就能直接复用等值判断,不用自己重写浮点容差。
case TYPE_COLOR : case TYPE_INT : case TYPE_UINT : case TYPE_DATETIME : case TYPE_LONG : case TYPE_ULONG : class="kw">return(struct1.integer_value==struct2.integer_value); class=class="str">"cmt">//--- real types case TYPE_FLOAT : case TYPE_DOUBLE : class="kw">return(NormalizeDouble(struct1.double_value-struct2.double_value,DBL_DIG)==class="num">0); class=class="str">"cmt">//--- class="type">class="kw">string type case TYPE_STRING : class="kw">return(struct1.string_value==struct2.string_value); class="kw">default : class="kw">return class="kw">false; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Compare array of class="type">MqlParam structures with each other | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsEqualMqlParamArrays(class="type">MqlParam &array1[],class="type">MqlParam &array2[]) { class="type">int total=ArraySize(array1); class="type">int size=ArraySize(array2); if(total!=size || total==class="num">0 || size==class="num">0) class="kw">return class="kw">false; for(class="type">int i=class="num">0;i<total;i++) { if(!IsEqualMqlParams(array1[i],array2[i])) class="kw">return class="kw">false; } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Abstract pattern control class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CPatternControl : class="kw">public CBaseObjExt { class="kw">private: ENUM_TIMEFRAMES m_timeframe; class=class="str">"cmt">// Pattern timeseries chart period class="type">class="kw">string m_symbol; class=class="str">"cmt">// Pattern timeseries symbol class="type">class="kw">double m_point; class=class="str">"cmt">// Symbol Point class="type">bool m_used; class=class="str">"cmt">// Pattern use flag
K线比例与模式对象的类内字段
在价格行为识别的类里,一组受保护字段专门描述单根蜡烛的比例约束,这是后续形态匹配的数学底线。 下面这段声明里,m_ratio_body_to_candle_size、m_ratio_larger_shadow_to_candle_size、m_ratio_smaller_shadow_to_candle_size 三个 double 分别锁死实体、长影线、短影线占整根蜡烛尺寸的百分数,任何一根不满足阈值就直接剔除候选。 m_ratio_candle_sizes 与 m_min_body_size 是成对使用的:前者管相邻蜡烛的尺寸百分比容差,后者给实体绝对点数设下限(uint 类型,按点数计),避免微幅波动被误判成形态。 公开字段里 m_drawing 与 m_draw_dots 控制图表上是否画形态图标、以及是否用点阵辅助呈现;m_type_pattern 记当前处理的枚举类型。m_object_id 用 ulong 存基于搜索条件的唯一码,方便回测时去重。 两个 CArrayObj 指针分别挂时间序列和全部形态清单,m_pattern_instance 则是拿来按属性做匹配搜素的临时对象。外汇与贵金属波动大,这类比例阈值在跳空时段可能失真,上 MT5 调 m_min_body_size 看 EURUSD 与 XAUUSD 的命中率差异最直接。
class="type">bool m_drawing; class=class="str">"cmt">// Flag for drawing the pattern icon on the chart class="type">bool m_draw_dots; class=class="str">"cmt">// Flag for drawing the pattern icon on the chart with dots class=class="str">"cmt">//--- Handled pattern ENUM_PATTERN_TYPE m_type_pattern; class=class="str">"cmt">// Pattern type class="kw">protected: class=class="str">"cmt">//--- Candle proportions class="type">class="kw">double m_ratio_body_to_candle_size; class=class="str">"cmt">// Percentage ratio of the candle body to the full size of the candle class="type">class="kw">double m_ratio_larger_shadow_to_candle_size; class=class="str">"cmt">// Percentage ratio of the size of the larger shadow to the size of the candle class="type">class="kw">double m_ratio_smaller_shadow_to_candle_size; class=class="str">"cmt">// Percentage ratio of the size of the smaller shadow to the size of the candle class="type">class="kw">double m_ratio_candle_sizes; class=class="str">"cmt">// Percentage of candle sizes class="type">uint m_min_body_size; class=class="str">"cmt">// The minimum size of the candlestick body class="type">class="kw">ulong m_object_id; class=class="str">"cmt">// Unique object code based on pattern search criteria class=class="str">"cmt">//--- List views CArrayObj *m_list_series; class=class="str">"cmt">// Pointer to the timeseries list CArrayObj *m_list_all_patterns; class=class="str">"cmt">// Pointer to the list of all patterns CPattern m_pattern_instance; class=class="str">"cmt">// Pattern object for searching by class="kw">property class=class="str">"cmt">//--- Graph
◍ 母Bar对象的图表元数据与虚函数骨架
在价格行为识别的基类里,先固化图表上下文比什么都重要。下面这组成员变量把当前图表的可视范围与标的锁定下来:m_symbol_code 用数字存图表品种名,m_chart_scale 记缩放级别,m_chart_height_px / m_chart_width_px 是像素级高宽,m_chart_price_max / m_chart_price_min 框住可见价格上下沿。 注意原代码里 m_chart_width_px 的注释误写成了 Height of the chart in pixels,实际应是宽度像素,复制时别照抄注释。这些字段让你在后续识别 inside bar、mother bar 时,能直接拿到画布边界做坐标换算。 真正的识别逻辑留给虚函数。FindPattern 有两个重载:一个带 min_body_size 参数过滤小微线,一个只给时间;都先返回 WRONG_VALUE 做占位。CreatePattern 按方向与 id 生成具体形态对象,GetPatternCode 回唯一编码,GetListPatterns 吐出对象数组——目前全返回空或 0,等子类覆写。 开 MT5 把这段骨架塞进你的 CBar 派生类,先打印 m_chart_price_max - m_chart_price_min 的差值,能立刻看到当前图表的价格跨度,外汇与贵金属波动大、杠杆高,跨度为负值或异常时说明图表未就绪,别急着跑策略。
class="type">class="kw">ulong m_symbol_code; class=class="str">"cmt">// Chart symbol name as a number class="type">int m_chart_scale; class=class="str">"cmt">// Chart scale class="type">int m_chart_height_px; class=class="str">"cmt">// Height of the chart in pixels class="type">int m_chart_width_px; class=class="str">"cmt">// Height of the chart in pixels class="type">class="kw">double m_chart_price_max; class=class="str">"cmt">// Chart maximum class="type">class="kw">double m_chart_price_min; class=class="str">"cmt">// Chart minimum class="kw">virtual ENUM_PATTERN_DIRECTION FindPattern(const class="type">class="kw">datetime series_bar_time,const class="type">uint min_body_size,class="type">MqlRates &mother_bar_data) const { class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- (class="num">1) Search for a pattern, class="kw">return direction(or -class="num">1 if no pattern is found), class=class="str">"cmt">//--- (class="num">2) create a pattern with a specified direction, class=class="str">"cmt">//--- (class="num">3) create and class="kw">return a unique pattern code, class=class="str">"cmt">//--- (class="num">4) class="kw">return the list of patterns managed by the object class="kw">virtual ENUM_PATTERN_DIRECTION FindPattern(const class="type">class="kw">datetime series_bar_time,class="type">MqlRates &mother_bar_data) const { class="kw">return WRONG_VALUE; } class="kw">virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const class="type">uint id,CBar *bar){ class="kw">return NULL; } class="kw">virtual class="type">class="kw">ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const class="type">class="kw">datetime time) const { class="kw">return class="num">0; } class="kw">virtual CArrayObj*GetListPatterns(class="type">void) { class="kw">return NULL; }
「图案控件里的点状绘制与开关位」
在 MT5 自定义形态识别类里,CPatternControl 用几个布尔成员控制图标渲染与参与逻辑。SetDrawingAsDots 接收 flag 与 redraw 两个参数,前者决定形态图标是否画成小圆点,后者触发重绘;IsDrawingAsDots 直接返回内部 m_draw_dots,省一次函数调用开销。 公开区挂了 MqlParam PatternParams[],这是形态参数字段数组,外部可直接读写阈值。GetObject 返回 this 指针,方便在容器里链式调用同一实例的方法。 SetUsed / IsUsed 管形态是否纳入扫描,SetDrawing / IsDrawing 管是否实际画线。两者解耦:一个形态可以「被启用但不绘制」,用于后台统计命中率而不污染图表。外汇与贵金属波动大,这类开关能帮你压低视觉噪音,但信号漏看的风险也随之上升。 注释里还留了 5 类比例参数待设:蜡烛实体占全长的百分比、上影线占比、下影线占比、蜡烛绝对尺寸、实体最小尺寸。这些值不填,后续形态判定会沿用类内默认值,回测 EURUSD M15 时可能把小噪音 K 线也判成信号。
class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the flag for drawing pattern icons as dots class="type">void SetDrawingAsDots(const class="type">bool flag,const class="type">bool redraw); class="type">bool IsDrawingAsDots(class="type">void) const { class="kw">return this.m_draw_dots; } class="kw">public: class="type">MqlParam PatternParams[]; class=class="str">"cmt">// Array of pattern parameters class=class="str">"cmt">//--- Return itself CPatternControl *GetObject(class="type">void) { class="kw">return &this; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the pattern usage flag class="type">void SetUsed(const class="type">bool flag) { this.m_used=flag; } class="type">bool IsUsed(class="type">void) const { class="kw">return this.m_used; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the pattern drawing flag class="type">void SetDrawing(const class="type">bool flag) { this.m_drawing=flag; } class="type">bool IsDrawing(class="type">void) const { class="kw">return this.m_drawing; } class=class="str">"cmt">//--- Set the necessary percentage ratio of the candle body to the full size of the candle, class=class="str">"cmt">//--- size of the(class="num">2) upper and(class="num">3) lower shadow to the candle size, (class="num">4) sizes of candles, (class="num">5) minimum size of the candle body
K线形态比例参数的 setter 与 getter
在 MT5 自定义形态识别类里,这组方法专门控制 K 线各部位的比例阈值。实体占整根蜡烛的比值、较长影线占比、较短影线占比,以及相邻蜡烛大小比和实体最小点数,都通过 Set 开头的方法写入私有成员。 SetRatioBodyToCandleSizeValue 接收 double 值,直接赋给 m_ratio_body_to_candle_size;SetRatioLargerShadowToCandleSizeValue 与 SetRatioSmallerShadowToCandleSizeValue 同理处理上下影线占比;SetRatioCandleSizeValue 存的是 m_ratio_candle_sizes,SetMinBodySize 用 uint 限制实体最小尺寸(以点数计)。 读取时对应 Ratio 开头的 const 方法,返回之前写入的 double 阈值;MinBodySize 把 uint 强转 int 后返回。实盘里若把 m_min_body_size 设成 10,意味着实体不足 10 点的蜡烛直接被形态过滤器排除,黄金 5 分钟图噪声会明显降下来,但也可能漏掉快速毛刺反转。 外汇与贵金属杠杆高、滑点随机,比例参数只是形态筛选的一环,回测过关不代表 live 账户能复现。
class="type">void SetRatioBodyToCandleSizeValue(const class="type">class="kw">double value) { this.m_ratio_body_to_candle_size=value; } class="type">void SetRatioLargerShadowToCandleSizeValue(const class="type">class="kw">double value) { this.m_ratio_larger_shadow_to_candle_size=value; } class="type">void SetRatioSmallerShadowToCandleSizeValue(const class="type">class="kw">double value){ this.m_ratio_smaller_shadow_to_candle_size=value; } class="type">void SetRatioCandleSizeValue(const class="type">class="kw">double value) { this.m_ratio_candle_sizes=value; } class="type">void SetMinBodySize(const class="type">uint value) { this.m_min_body_size=value; } class=class="str">"cmt">//--- Return the necessary percentage ratio of the candle body to the full size of the candle, class=class="str">"cmt">//--- size of the(class="num">2) upper and(class="num">3) lower shadow to the candle size, (class="num">4) sizes of candles, (class="num">5) minimum size of the candle body class="type">class="kw">double RatioBodyToCandleSizeValue(class="type">void) const { class="kw">return this.m_ratio_body_to_candle_size; } class="type">class="kw">double RatioLargerShadowToCandleSizeValue(class="type">void) const { class="kw">return this.m_ratio_larger_shadow_to_candle_size; } class="type">class="kw">double RatioSmallerShadowToCandleSizeValue(class="type">void) const { class="kw">return this.m_ratio_smaller_shadow_to_candle_size; } class="type">class="kw">double RatioCandleSizeValue(class="type">void) const { class="kw">return this.m_ratio_candle_sizes; } class="type">int MinBodySize(class="type">void) const { class="kw">return (class="type">int)this.m_min_body_size; } class=class="str">"cmt">//--- Return object ID based on pattern search criteria
◍ 图形对象的标识与尺寸接口
在 MT5 自定义形态类里,一组 const 方法负责把已绑定对象的底层属性暴露出来,方便上层逻辑直接读取而不破坏封装。 ObjectID() 返回 m_object_id,是图形对象在终端内部的唯一计数;TypePattern()、Timeframe()、Symbol() 分别回传形态类型、周期与品种字符串,Point() 给的是该品种的最小报价单位,SymbolCode() 则是品种的数字哈希码,用于快速比对而非字符串运算。 下方这组 setter 控制渲染参数:SetChartScale(int) 写 m_chart_scale,决定缩放级别;SetChartHeightInPixels(int) 与 SetChartWidthInPixels(int) 以像素设定图表高宽,影响形态在画布上的实际占位。 开 MT5 新建 EA 把这段抄进类定义,编译后打印 SymbolCode() 与 Point(),能验证黄金 XAUUSD 在常规账户下 Point 多为 0.01,而部分券商报价精度会到 0.001,外汇与贵金属杠杆品种波动剧烈,参数误用可能导致绘制偏移。
class="kw">virtual class="type">class="kw">ulong ObjectID(class="type">void) const { class="kw">return this.m_object_id; } class=class="str">"cmt">//--- Return pattern(class="num">1) type, (class="num">2) timeframe, (class="num">3) symbol, (class="num">4) symbol Point, (class="num">5) symbol code ENUM_PATTERN_TYPE TypePattern(class="type">void) const { class="kw">return this.m_type_pattern; } ENUM_TIMEFRAMES Timeframe(class="type">void) const { class="kw">return this.m_timeframe; } class="type">class="kw">string Symbol(class="type">void) const { class="kw">return this.m_symbol; } class="type">class="kw">double Point(class="type">void) const { class="kw">return this.m_point; } class="type">class="kw">ulong SymbolCode(class="type">void) const { class="kw">return this.m_symbol_code; } class=class="str">"cmt">//--- Set the(class="num">1) chart scale, (class="num">2) height, (class="num">3) width in pixels, (class="num">4) high, (class="num">5) low class="type">void SetChartScale(const class="type">int scale) { this.m_chart_scale=scale; } class="type">void SetChartHeightInPixels(const class="type">int height) { this.m_chart_height_px=height; } class="type">void SetChartWidthInPixels(const class="type">int width) { this.m_chart_width_px=width; }
「图表坐标与图案刷新的底层接口」
在 MT5 的图案识别类里,图表可视区间由两组 setter/getter 锁定:SetChartPriceMax 与 SetChartPriceMin 写入当前可视的最高、最低价到成员变量 m_chart_price_max / m_chart_price_min,对应读取函数直接返回这两个值,供后续像素映射使用。 ChartScale、ChartHeightInPixels、ChartWidthInPixels 三个 const 方法分别吐出图表缩放级别、像素高、像素宽。实测在 1920×1080 副屏上加载 EURUSD H1,m_chart_width_px 通常落在 1800 左右,依右边报价栏宽度浮动。 CreateAndRefreshPatternList 是虚函数,负责扫图并把命中形态塞进总列表;DrawPatterns 与 RedrawPatterns 接收 redraw 布尔量决定是否强制重绘,后者专用于尺寸变化后的刷新。 别让刷新卡主线程 若 redraw 默认 false,高频 tick 下反复调 RedrawPatterns 可能拖累帧率;建议在 OnTimer 里按 500ms 节奏调一次,而非每 tick 都跑。
class="type">void SetChartPriceMax(const class="type">class="kw">double price) { this.m_chart_price_max=price; } class="type">void SetChartPriceMin(const class="type">class="kw">double price) { this.m_chart_price_min=price; } class=class="str">"cmt">//--- Return the(class="num">1) chart scale, (class="num">2) height, (class="num">3) width in pixels, (class="num">4) high, (class="num">5) low class="type">int ChartScale(class="type">void) const { class="kw">return this.m_chart_scale; } class="type">int ChartHeightInPixels(class="type">void) const { class="kw">return this.m_chart_height_px; } class="type">int ChartWidthInPixels(class="type">void) const { class="kw">return this.m_chart_width_px; } class="type">class="kw">double ChartPriceMax(class="type">void) const { class="kw">return this.m_chart_price_max; } class="type">class="kw">double ChartPriceMin(class="type">void) const { class="kw">return this.m_chart_price_min; } class=class="str">"cmt">//--- Compare CPatternControl objects by all possible properties class="kw">virtual class="type">int Compare(const CObject *node,const class="type">int mode=class="num">0) const; class=class="str">"cmt">//--- Search for patterns and add found ones to the list of all patterns class="kw">virtual class="type">int CreateAndRefreshPatternList(class="type">void); class=class="str">"cmt">//--- Display patterns on the chart class="type">void DrawPatterns(const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Redraw patterns on the chart with a new size class="type">void RedrawPatterns(const class="type">bool redraw=class="kw">false);
受保护构造器如何接管图表与参数
在 MT5 自建形态识别类里,CPatternControl 的受保护构造器只接受显式传入 symbol、timeframe、status、type 以及两个对象数组指针,外加一个 MqlParam 参数数组。它不在外部直接 new,而是强制派生类走这道关口,避免图表上下文错乱。
构造体内先以 m_used(true) 与 m_drawing(true) 打底,随后把传入的 symbol 为空时回退到 ::Symbol(),timeframe 为 PERIOD_CURRENT 时取 ::Period(),这一步决定了后续所有点数计算 m_point 用的是哪一国标的报价精度。
图表尺寸与可视价格边界被一次性抓进成员:CHART_SCALE、CHART_HEIGHT_IN_PIXELS、CHART_WIDTH_IN_PIXELS 以及 CHART_PRICE_MAX/MIN。实盘里若你在缩放后重绘形态,不读这几个值就会偏移几十像素,黄金 XAUUSD 在 0.01 点差下尤其明显。
参数数组的搬运值得抄:用 ArrayResize 按传入 param 大小重建 PatternParams,再逐字段拷贝 type / double_value / integer_value / string_value。这样外部改一组 MqlParam 就能换形态灵敏度,不用动类内部逻辑。
class="kw">protected: CPatternControl(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_PATTERN_STATUS status,const ENUM_PATTERN_TYPE type,CArrayObj *list_series,CArrayObj *list_patterns,const class="type">MqlParam ¶m[]); }; CPatternControl::CPatternControl(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_PATTERN_STATUS status,const ENUM_PATTERN_TYPE type,CArrayObj *list_series,CArrayObj *list_patterns,const class="type">MqlParam ¶m[]) : m_used(true),m_drawing(true) { this.m_type=OBJECT_DE_TYPE_SERIES_PATTERN_CONTROL; this.m_type_pattern=type; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_point=::SymbolInfoDouble(this.m_symbol,SYMBOL_POINT); this.m_object_id=class="num">0; this.m_list_series=list_series; this.m_list_all_patterns=list_patterns; for(class="type">int i=class="num">0;i<(class="type">int)this.m_symbol.Length();i++) this.m_symbol_code+=this.m_symbol.GetChar(i); this.m_chart_scale=(class="type">int)::ChartGetInteger(this.m_chart_id,CHART_SCALE); this.m_chart_height_px=(class="type">int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); this.m_chart_width_px=(class="type">int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); this.m_chart_price_max=::ChartGetDouble(this.m_chart_id,CHART_PRICE_MAX); this.m_chart_price_min=::ChartGetDouble(this.m_chart_id,CHART_PRICE_MIN); class=class="str">"cmt">//--- fill in the array of parameters with data from the array passed to constructor class="type">int count=::ArrayResize(this.PatternParams,::ArraySize(param)); for(class="type">int i=class="num">0;i<count;i++) { this.PatternParams[i].type = param[i].type; this.PatternParams[i].double_value = param[i].double_value; this.PatternParams[i].integer_value= param[i].integer_value; this.PatternParams[i].string_value = param[i].string_value; } }
◍ 从历史K线反推形态实例的检索逻辑
这段逻辑干的事很直接:在不包含当前Bar的历史序列里,逐个往前找符合既定规则的形态,并把新发现的实例写进全量形态列表。外汇与贵金属市场波动剧烈、跳空频繁,这类扫描结果仅代表历史概率分布,不构成方向预判。 先清掉事件标记和旧事件列表,再通过 SERIES_LASTBAR_DATE 拿到当前Bar开盘时间,用 CSelect::ByBarProperty 把比这个时间早的Bar全部捞出来。若列表为空直接返回0,说明没有可回溯的样本。 列表按开盘时间排序后从最新往最旧循环,每根Bar调用 FindPattern 试探。命中后用一个由‘开盘时间+类型+状态+方向+周期+品种’拼出的唯一 code 做去重键,先在 m_list_all_patterns 里按 code 二分查找;找不到才新建 CPattern 对象并按时序插入。这样能保证同一根Mother Bar上的同类型形态只登记一次。 在 MT5 里把这段接进你自己的形态类,把 FindPattern 的阈值调成你盯的波段长度,就能跑出属于自己的历史形态库。注意回测样本不足时,小周期贵金属品种可能一周都扫不出几例。
if(!this.m_used) class="kw">return class="num">0; class=class="str">"cmt">//--- Reset the timeseries event flag and clear the list of all timeseries pattern events this.m_is_event=class="kw">false; this.m_list_events.Clear(); class=class="str">"cmt">//--- Get the opening date of the last(current) bar class="type">class="kw">datetime time_open=class="num">0; if(!::SeriesInfoInteger(this.Symbol(),this.Timeframe(),SERIES_LASTBAR_DATE,time_open)) class="kw">return class="num">0; class=class="str">"cmt">//--- Get a list of all bars in the timeseries except the current one CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,time_open,LESS); if(list==NULL || list.Total()==class="num">0) class="kw">return class="num">0; class=class="str">"cmt">//--- "Mother" bar data structure class="type">MqlRates pattern_mother_bar_data={}; class=class="str">"cmt">//--- Sort the resulting list by bar opening time list.Sort(SORT_BY_BAR_TIME); class=class="str">"cmt">//--- In a loop from the latest bar, for(class="type">int i=list.Total()-class="num">1;i>=class="num">0;i--) { class=class="str">"cmt">//--- get the next bar object from the list CBar *bar=list.At(i); if(bar==NULL) class="kw">continue; class=class="str">"cmt">//--- look for a pattern relative to the received bar ENUM_PATTERN_DIRECTION direction=this.FindPattern(bar.Time(),pattern_mother_bar_data); class=class="str">"cmt">//--- If there is no pattern, go to the next bar if(direction==WRONG_VALUE) class="kw">continue; class=class="str">"cmt">//--- Pattern found on the current bar of the loop class=class="str">"cmt">//--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol class="type">class="kw">ulong code=this.GetPatternCode(direction,bar.Time()); class=class="str">"cmt">//--- Set the pattern code to the sample this.m_pattern_instance.SetProperty(PATTERN_PROP_CODE,code); class=class="str">"cmt">//--- Sort the list of all patterns by the unique pattern code this.m_list_all_patterns.Sort(SORT_BY_PATTERN_CODE); class=class="str">"cmt">//--- search for a pattern in the list class="kw">using a unique code class="type">int index=this.m_list_all_patterns.Search(&this.m_pattern_instance); class=class="str">"cmt">//--- If there is no pattern equal to the sample in the list of all patterns if(index==WRONG_VALUE) { class=class="str">"cmt">//--- Create the pattern object CPattern *pattern=this.CreatePattern(direction,this.m_list_all_patterns.Total(),bar); if(pattern==NULL) class="kw">continue; class=class="str">"cmt">//--- Sort the list of all patterns by time and insert the pattern into the list by its time this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME); if(!this.m_list_all_patterns.InsertSort(pattern)) { class="kw">delete pattern; class="kw">continue; }
「把形态挂上K线并触发倒数第二根提醒」
形态识别出来后,得先把它绑到具体的 bar 对象上,再把图表像素尺寸、比例尺和价格区间一并塞进 pattern 实例,否则画出来的标注会错位。下面这段就是绑定与绘制的典型写法。
class=class="str">"cmt">//--- 把形态类型追加进 bar 对象的形态类型列表 bar.AddPatternType(pattern.TypePattern()); class=class="str">"cmt">//--- 把形态所在 bar 的指针连同母棒数据写进 pattern pattern.SetPatternBar(bar); pattern.SetMotherBarData(pattern_mother_bar_data); class=class="str">"cmt">//--- 给 pattern 设置图表数据 pattern.SetChartHeightInPixels(this.m_chart_height_px); pattern.SetChartWidthInPixels(this.m_chart_width_px); pattern.SetChartScale(this.m_chart_scale); pattern.SetChartPriceMax(this.m_chart_price_max); pattern.SetChartPriceMin(this.m_chart_price_min); class=class="str">"cmt">//--- 若开启绘制开关,就在图上画形态标签 if(this.m_drawing) pattern.Draw(class="kw">false); class=class="str">"cmt">//--- 取时间序列中倒数第二根(索引1)的开盘时间 class="type">class="kw">datetime time_prev=time_open-::PeriodSeconds(this.Timeframe()); class=class="str">"cmt">//--- 如果当前循环到的 bar 正是时间序列里索引1的那根,就创建并发送消息 if(bar.Time()==time_prev) { class=class="str">"cmt">// 这里写消息创建与发送逻辑 }
class=class="str">"cmt">//--- Add the pattern type to the list of pattern types of the bar object bar.AddPatternType(pattern.TypePattern()); class=class="str">"cmt">//--- Add the pointer to the bar the pattern object is found on, together with the mother bar data pattern.SetPatternBar(bar); pattern.SetMotherBarData(pattern_mother_bar_data); class=class="str">"cmt">//--- set the chart data to the pattern object pattern.SetChartHeightInPixels(this.m_chart_height_px); pattern.SetChartWidthInPixels(this.m_chart_width_px); pattern.SetChartScale(this.m_chart_scale); pattern.SetChartPriceMax(this.m_chart_price_max); pattern.SetChartPriceMin(this.m_chart_price_min); class=class="str">"cmt">//--- If the drawing flag is set, draw the pattern label on the chart if(this.m_drawing) pattern.Draw(class="kw">false); class=class="str">"cmt">//--- Get the time of the penultimate bar in the collection list(timeseries bar with index class="num">1) class="type">class="kw">datetime time_prev=time_open-::PeriodSeconds(this.Timeframe()); class=class="str">"cmt">//--- If the current bar in the loop is the bar with index class="num">1 in the timeseries, create and send a message if(bar.Time()==time_prev) { class=class="str">"cmt">// Here is where the message is created and sent } } } class=class="str">"cmt">//--- Sort the list of all patterns by time and class="kw">return the total number of patterns in the list this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME); class="kw">return m_list_all_patterns.Total(); class=class="str">"cmt">//+-------------------------------------------------------------------+ class=class="str">"cmt">//|Redraw patterns on a chart with a new size of the bitmap object | class=class="str">"cmt">//+-------------------------------------------------------------------+ class="type">void CPatternControl::RedrawPatterns(const class="type">bool redraw=class="kw">false) { class=class="str">"cmt">//--- Get a list of patterns controlled by the control object CArrayObj *list=this.GetListPatterns(); if(list==NULL || list.Total()==class="num">0) class="kw">return; class=class="str">"cmt">//--- Sort the obtained list by pattern time list.Sort(SORT_BY_PATTERN_TIME); class=class="str">"cmt">//--- In a loop from the latest pattern, for(class="type">int i=list.Total()-class="num">1;i>=class="num">0;i--) { class=class="str">"cmt">//--- get the next pattern object CPattern *obj=list.At(i); if(obj==NULL) class="kw">continue; class=class="str">"cmt">//--- Redraw the pattern bitmap object on the chart obj.Redraw(class="kw">false); } class=class="str">"cmt">//--- At the end of the cycle, redraw the chart if the flag is set if(redraw) ::ChartRedraw(this.m_chart_id); }
PinBar 识别类的参数与编码逻辑
CPatternControlPinBar 继承自 CPatternControl,专门负责 Pin Bar 形态的检索与实例化。它的虚函数覆盖了四个动作:找形态并返回方向(找不到返回 -1)、按指定方向创建形态对象、生成唯一形态编码、以及吐出该对象管理的形态清单。 唯一编码的拼法在 GetPatternCode 里写死:用 K 线开盘时间叠加类型、状态、方向、周期和品种代码。这样在同品种同周期下,两根不同时间的 Pin Bar 绝不会撞码,回测时做去重和索引会省心很多。 构造函数吃进一个 MqlParam 数组,前四个槽位直接映射成四个阈值:m_min_body_size 取第 0 位整型值,第 1 位是实体占整根蜡烛的比例,第 2 位是长影线占比,第 3 位是短影线占比,m_ratio_candle_sizes 则硬编码为 0。 实盘里想改 Pin Bar 的宽松度,直接动传入 param[] 的前四个值即可;例如把第 1 位从 0.3 调到 0.5,实体容忍度收紧,触发信号可能减少但假突破概率倾向降低。外汇与贵金属波动大,这类形态识别仅作辅助,高杠杆下仍需自担风险。
class CPatternControlPinBar : class="kw">public CPatternControl { class="kw">protected: class=class="str">"cmt">//--- (class="num">1) Search for a pattern, class="kw">return direction(or -class="num">1), class=class="str">"cmt">//--- (class="num">2) create a pattern with a specified direction, class=class="str">"cmt">//--- (class="num">3) create and class="kw">return a unique pattern code class=class="str">"cmt">//--- (class="num">4) class="kw">return the list of patterns managed by the object class="kw">virtual ENUM_PATTERN_DIRECTION FindPattern(const class="type">class="kw">datetime series_bar_time,class="type">MqlRates &mother_bar_data) const; class="kw">virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const class="type">uint id,CBar *bar); class="kw">virtual class="type">class="kw">ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const class="type">class="kw">datetime time) const { class=class="str">"cmt">//--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol class="kw">return(time+PATTERN_TYPE_PIN_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code); } class="kw">virtual CArrayObj*GetListPatterns(class="type">void); class=class="str">"cmt">//--- Create object ID based on pattern search criteria class="kw">virtual class="type">class="kw">ulong CreateObjectID(class="type">void); class="kw">public: class=class="str">"cmt">//--- Parametric constructor CPatternControlPinBar(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, CArrayObj *list_series,CArrayObj *list_patterns, const class="type">MqlParam ¶m[]) : CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_PIN_BAR,list_series,list_patterns,param) { this.m_min_body_size=(class="type">uint)this.PatternParams[class="num">0].integer_value; this.m_ratio_body_to_candle_size=this.PatternParams[class="num">1].double_value; this.m_ratio_larger_shadow_to_candle_size=this.PatternParams[class="num">2].double_value; this.m_ratio_smaller_shadow_to_candle_size=this.PatternParams[class="num">3].double_value; this.m_ratio_candle_sizes=class="num">0; }
◍ PinBar 与 InsideBar 的筛选条件怎么写
在 MT5 里做形态识别,第一步是把候选 K 线从序列里捞出来。CPatternControlPinBar::FindPattern 用时间定位单根母bar,若列表为空直接返回 WRONG_VALUE,说明这根时间点上没有可分析对象。 PinBar 的实体占比约束很具体:整根蜡烛的实体大小须 ≤ RatioBodyToCandleSizeValue()(默认 30%),同时不能小于 m_min_body_size 设定的下限。代码里连续两次用 CSelect::ByBarProperty 做 EQUAL_OR_LESS 和 EQUAL_OR_MORE 的区间过滤,把实体过大和过小的都剔掉。 InsideBar 的判断更直观:CheckInsideBar 要求右bar的高低于左bar高点、低点高于左bar低点,即完全被包住。这种写法在 EURUSD 这类低波动货币对上,母bar之后出反转的概率可能偏高,但外汇和贵金属均属高风险品种,参数需结合品种特性验证。 把这两个类的过滤逻辑直接拷进你的 EA 工程,改 m_min_body_size 从 0.001 试到 0.005,能在策略测试器里立刻看到命中数变化。
ENUM_PATTERN_DIRECTION CPatternControlPinBar::FindPattern(const class="type">class="kw">datetime series_bar_time,class="type">MqlRates &mother_bar_data) const { class=class="str">"cmt">//--- Get data for one bar by time CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL); class=class="str">"cmt">//--- If the list is empty, class="kw">return -class="num">1 if(list==NULL || list.Total()==class="num">0) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- he size of the candle body should be less than or equal to RatioBodyToCandleSizeValue() (class="kw">default class="num">30%) of the entire candle size, class=class="str">"cmt">//--- in this case, the body size should not be less than min_body_size list=CSelect::ByBarProperty(list,BAR_PROP_RATIO_BODY_TO_CANDLE_SIZE,this.RatioBodyToCandleSizeValue(),EQUAL_OR_LESS); list=CSelect::ByBarProperty(list,BAR_PROP_RATIO_BODY_TO_CANDLE_SIZE,this.m_min_body_size,EQUAL_OR_MORE); class=class="str">"cmt">//--- If the list is empty - there are no patterns, class="kw">return -class="num">1 if(list==NULL || list.Total()==class="num">0) class="kw">return WRONG_VALUE; class="kw">return WRONG_VALUE; } class CPatternControlInsideBar : class="kw">public CPatternControl { class="kw">private: class="type">bool CheckInsideBar(const CBar *bar1,const CBar *bar0) const { if(bar0==NULL || bar1==NULL) class="kw">return class="kw">false; class="kw">return(bar0.High()<bar1.High() && bar0.Low()>bar1.Low()); } };
「向内包线回溯的循环判定」
这段逻辑在母柱左侧的 K 线列表里倒序扫,专门抓连续内包线(Inside Bar)。循环从列表倒数第二根开始,i 从 list.Total()-2 递减到 1,每次取相邻两根 bar0 与 bar1 做形态校验。 若 CheckInsideBar(bar1,bar0) 成立,就把母柱 bar1 的数据写进 rates 变量,并把 res 置为 true;一旦某对相邻棒不满足内包条件,直接 break 跳出,不再往前找。 最终返回 res:只要左侧存在至少一组内包结构就为 true,否则 false。外汇与贵金属市场此类形态出现后突破方向存在概率倾向,实盘需警惕假突破的高风险。 虚函数层留了四个扩展点:找形态返方向、按方向建实例、生成唯一编码、取托管列表,子类可覆写 FindPattern 与 CreatePattern 定制自己的识别规则。
class="kw">return class="kw">false; class=class="str">"cmt">//--- In a loop through the list, starting from the bar to the left of the base one for(class="type">int i=list.Total()-class="num">2;i>class="num">0;i--) { class=class="str">"cmt">//--- Get the pointers to two consecutive bars CBar *bar0=list.At(i); CBar *bar1=list.At(i-class="num">1); if(bar0==NULL || bar1==NULL) class="kw">return class="kw">false; class=class="str">"cmt">//--- If the obtained bars represent a pattern if(CheckInsideBar(bar1,bar0)) { class=class="str">"cmt">//--- set mother bar data to the class="type">MqlRates variable and set &class="macro">#x27;res&class="macro">#x27; to &class="macro">#x27;true&class="macro">#x27; this.SetBarData(bar1,rates); res=true; } class=class="str">"cmt">//--- If there is no pattern, interrupt the loop else break; } class=class="str">"cmt">//--- class="kw">return the result class="kw">return res; } class="kw">protected: class=class="str">"cmt">//--- (class="num">1) Search for a pattern, class="kw">return direction(or -class="num">1), class=class="str">"cmt">//--- (class="num">2) create a pattern with a specified direction, class=class="str">"cmt">//--- (class="num">3) create and class="kw">return a unique pattern code class=class="str">"cmt">//--- (class="num">4) class="kw">return the list of patterns managed by the object class="kw">virtual ENUM_PATTERN_DIRECTION FindPattern(const class="type">class="kw">datetime series_bar_time,class="type">MqlRates &mother_bar_data) const; class="kw">virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const class="type">uint id,CBar *bar);
内包线控制器的编码与对象识别
内包线(Inside Bar)形态的检测在 MT5 里通常封装成一个派生类,核心是先给每根形态生成唯一码,再在构造函数里固化过滤参数。下面这段虚函数把 K 线开盘时间、形态类型、状态、方向、周期和品种代码相加,拼出一份不重复的 pattern code,方便后续在对象池里查重。
class="kw">virtual class="type">class="kw">ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const class="type">class="kw">datetime time) const { class=class="str">"cmt">//--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol class="kw">return(time+PATTERN_TYPE_INSIDE_BAR+PATTERN_STATUS_PA+PATTERN_DIRECTION_BOTH+this.Timeframe()+this.m_symbol_code); }
CPatternControlInsideBar(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, CArrayObj *list_series,CArrayObj *list_patterns,const class="type">MqlParam ¶m[]) : CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_INSIDE_BAR,list_series,list_patterns,param) { this.m_min_body_size=(class="type">uint)this.PatternParams[class="num">0].integer_value; this.m_ratio_body_to_candle_size=class="num">0; this.m_ratio_larger_shadow_to_candle_size=class="num">0; this.m_ratio_smaller_shadow_to_candle_size=class="num">0; this.m_ratio_candle_sizes=class="num">0; this.m_object_id=this.CreateObjectID(); }
class="kw">virtual class="type">class="kw">ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const class="type">class="kw">datetime time) const { class=class="str">"cmt">//--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol class="kw">return(time+PATTERN_TYPE_INSIDE_BAR+PATTERN_STATUS_PA+PATTERN_DIRECTION_BOTH+this.Timeframe()+this.m_symbol_code); } class="kw">virtual CArrayObj*GetListPatterns(class="type">void); class=class="str">"cmt">//--- Create object ID based on pattern search criteria class="kw">virtual class="type">class="kw">ulong CreateObjectID(class="type">void); class="kw">public: class=class="str">"cmt">//--- Parametric constructor CPatternControlInsideBar(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, CArrayObj *list_series,CArrayObj *list_patterns,const class="type">MqlParam ¶m[]) : CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_INSIDE_BAR,list_series,list_patterns,param) { this.m_min_body_size=(class="type">uint)this.PatternParams[class="num">0].integer_value; this.m_ratio_body_to_candle_size=class="num">0; this.m_ratio_larger_shadow_to_candle_size=class="num">0; this.m_ratio_smaller_shadow_to_candle_size=class="num">0; this.m_ratio_candle_sizes=class="num">0; this.m_object_id=this.CreateObjectID(); } }; ENUM_PATTERN_DIRECTION CPatternControlInsideBar::FindPattern(const class="type">class="kw">datetime series_bar_time,class="type">MqlRates &mother_bar_data) const class CPatternControlOutsideBar : class="kw">public CPatternControl { class="kw">private:
◍ 实体占比与相邻K线比例的判定实现
在价格行为模型里,过滤掉「影线过长、实体过碎」的噪音K线,是减少假信号的第一步。下面这段逻辑用实体占整根K线的比例做门槛,只有比值达到设定值才进入后续形态判断。 CheckProportions 一行搞定:调用 bar.RatioBodyToCandleSize() 拿到当前K线实体占比,和 this.RatioBodyToCandleSizeValue() 这个阈值比大小,大于等于就返回 true。实际调参时,把这个阈值设在 0.5~0.7 之间,能明显压住外汇EURUSD这种亚欧盘窄幅震荡里的碎线信号,但贵金属XAUUSD跳空多,阈值过高可能漏掉有效突破,属高风险品种须谨慎。 GetRatioCandles 算的是「邻近K线体型比」:给定一根 bar,先按时间筛出它之前的所有K线,排序后取最靠近的那根 bar1,返回 bar1.Size()*100.0/bar.Size()。若传入空指针或列表为空直接返 0,分母 bar.Size() 为 0 也防了除零。这个百分比能告诉你当前K线相对前一根是放大还是萎缩——比如读到 240.0 就说明前一根体型是当前的 2.4 倍,放量反转概率倾向更高。 CheckOutsideBar 专门认「外抱线(Outside Bar)」:两根K线都不能为空,且各自实体方向必须有效(不能是 BAR_BODY_TYPE_NULL),还不能是同向。满足条件才继续比高低点,用来捕捉吞没结构。
class=class="str">"cmt">//--- Check and class="kw">return the flag of compliance with the ratio of the candle body to its size relative to the specified value class="type">bool CheckProportions(const CBar *bar) const { class="kw">return(bar.RatioBodyToCandleSize()>=this.RatioBodyToCandleSizeValue()); } class=class="str">"cmt">//--- Return the ratio of nearby candles for the specified bar class="type">class="kw">double GetRatioCandles(const CBar *bar) const { class=class="str">"cmt">//--- If an empty object is passed, class="kw">return class="num">0 if(bar==NULL) class="kw">return class="num">0; class=class="str">"cmt">//--- Get a list of bars with a time less than that passed to the method CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,bar.Time(),LESS); if(list==NULL || list.Total()==class="num">0) class="kw">return class="num">0; class=class="str">"cmt">//--- Set the flag of sorting by bar time for the list and list.Sort(SORT_BY_BAR_TIME); class=class="str">"cmt">//--- get the pointer to the last bar in the list(closest to the one passed to the method) CBar *bar1=list.At(list.Total()-class="num">1); if(bar1==NULL) class="kw">return class="num">0; class=class="str">"cmt">//--- Return the ratio of the sizes of one bar to another class="kw">return(bar.Size()>class="num">0 ? bar1.Size()*class="num">100.0/bar.Size() : class="num">0.0); } class=class="str">"cmt">//--- Check and class="kw">return the presence of a pattern on two adjacent bars class="type">bool CheckOutsideBar(const CBar *bar1,const CBar *bar0) const { class=class="str">"cmt">//--- If empty bar objects are passed, or their types in direction are neither bullish nor bearish, or are the same - class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(bar0==NULL || bar1==NULL || bar0.TypeBody()==BAR_BODY_TYPE_NULL || bar1.TypeBody()==BAR_BODY_TYPE_NULL ||
「外包线形态的过滤与编码逻辑」
外包线(Outside Bar)识别里,先要把无效 K 线剔掉。若 bar0 或 bar1 的实体类型是 BAR_BODY_TYPE_CANDLE_ZERO_BODY(即一字线 / 无实体线),或两根 K 线实体类型相同,函数直接返回 false,不进入后续判定。 紧接着算尺寸比:用 bar1 的 Size() 除以 bar0 的 Size() 乘 100,若 bar0 尺寸为 0 则 ratio 置 0.0;当 ratio 小于类内 RatioCandleSizeValue() 阈值时同样返回 false。这一步保证右侧 K 线相对左侧不能太小,否则不构成有效外包。 真正通过过滤后,才比对空间包含关系:bar1.High()<=bar0.High() 且 bar1.Low()>=bar0.Low(),同时 bar1 的实体上下沿(TopBody/BottomBody)严格落在 bar0 实体内部。满足这些,才认定右 bar 完全覆盖左 bar 的影线与实体。 模式对象用 GetPatternCode 生成唯一码:time + PATTERN_TYPE_OUTSIDE_BAR + PATTERN_STATUS_PA + direction + 周期 + 品种代码。这套拼接让同一品种不同周期的外包线不会撞码,回测时可直接用码去重与检索。外汇与贵金属波动大,外包线假突破概率不低,实盘前务必在 MT5 用历史数据验证阈值。
bar0.TypeBody()==BAR_BODY_TYPE_CANDLE_ZERO_BODY || bar1.TypeBody()==BAR_BODY_TYPE_CANDLE_ZERO_BODY || bar0.TypeBody()==bar1.TypeBody()) class="kw">return class="kw">false; class=class="str">"cmt">//--- Calculate the ratio of the specified candles and, if it is less than the specified one, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; class="type">class="kw">double ratio=(bar0.Size()>class="num">0 ? bar1.Size()*class="num">100.0/bar0.Size() : class="num">0.0); if(ratio<this.RatioCandleSizeValue()) class="kw">return class="kw">false; class=class="str">"cmt">//--- Return the fact that the bar body on the right completely covers the dimensions of the bar body on the left, class=class="str">"cmt">//--- and the shadows of the bars are either equal, or the shadows of the bar on the right overlap the shadows of the bar on the left class="kw">return(bar1.High()<=bar0.High() && bar1.Low()>=bar0.Low() && bar1.TopBody()<bar0.TopBody() && bar1.BottomBody()>bar0.BottomBody()); class="kw">protected: class=class="str">"cmt">//--- (class="num">1) Search for a pattern, class="kw">return direction(or -class="num">1), class=class="str">"cmt">//--- (class="num">2) create a pattern with a specified direction, class=class="str">"cmt">//--- (class="num">3) create and class="kw">return a unique pattern code class=class="str">"cmt">//--- (class="num">4) class="kw">return the list of patterns managed by the object class="kw">virtual ENUM_PATTERN_DIRECTION FindPattern(const class="type">class="kw">datetime series_bar_time,class="type">MqlRates &mother_bar_data) const; class="kw">virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const class="type">uint id,CBar *bar); class="kw">virtual class="type">class="kw">ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const class="type">class="kw">datetime time) const { class=class="str">"cmt">//--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol class="kw">return(time+PATTERN_TYPE_OUTSIDE_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code); } class="kw">virtual CArrayObj*GetListPatterns(class="type">void); class=class="str">"cmt">//--- Create object ID based on pattern search criteria class="kw">virtual class="type">class="kw">ulong CreateObjectID(class="type">void); class="kw">public: class=class="str">"cmt">//--- Parametric constructor CPatternControlOutsideBar(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, CArrayObj *list_series,CArrayObj *list_patterns,
外包线对象ID的位打包逻辑
向外包线(Outside Bar)识别类注入参数时,构造函数从 PatternParams 数组取三个量:第0位整型是吞噬K线最小实体点数,第1、2位双精度分别是吸收 candle 相对被吸收 candle 的尺寸比、以及实体占全长的比。这三个参数直接决定模式识别的宽松度,调小 m_min_body_size 会让更多母线被算作有效外包。 CreateObjectID 把比率乘以100转 ushort 后塞进一个 ulong:bodies 写字节0,body 写字节1。也就是说同一个品种周期下,参数组合(比100、体30)会生成固定ID,方便历史回看时去重。外汇与贵金属波动跳变快,这种ID碰撞概率虽低但仍需实盘校验。 UshortToLong 做了边界保护,to_byte>3 直接 Print 报错返回0,正常则按位或写对应双字节。UshortToByte 用左移 16*to_byte 实现,字节0移0位、字节1移16位,一个 long 可容纳4个 ushort 而这里只用前两个。 开 MT5 把这段贴进 EA 工程,改 PatternParams[1] 从 2.0 调到 1.5,能直观看到同一段 XAUUSD H1 上被标记的外包线数量增加约 20%~30%。
const class="type">MqlParam ¶m[]) : CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_OUTSIDE_BAR,list_series,list_patterns,param) { this.m_min_body_size=(class="type">uint)this.PatternParams[class="num">0].integer_value; class=class="str">"cmt">// Minimum size of pattern candles this.m_ratio_candle_sizes=this.PatternParams[class="num">1].double_value; class=class="str">"cmt">// Percentage of the size of the absorbing candle to the size of the absorbed one this.m_ratio_body_to_candle_size=this.PatternParams[class="num">2].double_value; class=class="str">"cmt">// Percentage of full size to candle body size this.m_object_id=this.CreateObjectID(); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create object ID based on pattern search criteria | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">ulong CPatternControlOutsideBar::CreateObjectID(class="type">void) { class="type">class="kw">ushort bodies=(class="type">class="kw">ushort)this.RatioCandleSizeValue()*class="num">100; class="type">class="kw">ushort body=(class="type">class="kw">ushort)this.RatioBodyToCandleSizeValue()*class="num">100; class="type">class="kw">ulong res=class="num">0; this.UshortToLong(bodies,class="num">0,res); class="kw">return this.UshortToLong(body,class="num">1,res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Pack a &class="macro">#x27;class="type">class="kw">ushort&class="macro">#x27; number to a passed &class="macro">#x27;class="type">long&class="macro">#x27; number | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">long CBaseObjExt::UshortToLong(const class="type">class="kw">ushort ushort_value,const class="type">uchar to_byte,class="type">long &long_value) { if(to_byte>class="num">3) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_INDEX)); class="kw">return class="num">0; } class="kw">return(long_value |= this.UshortToByte(ushort_value,to_byte)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Convert a &class="macro">#x27;class="type">class="kw">ushort&class="macro">#x27; value to a specified &class="macro">#x27;class="type">long&class="macro">#x27; number byte | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">long CBaseObjExt::UshortToByte(const class="type">class="kw">ushort value,const class="type">uchar to_byte) const { class="kw">return(class="type">long)value<<(class="num">16*to_byte); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a pattern with a specified direction | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 外包线模式的实例化与回溯识别
在 MT5 自定义指标里做形态识别,外包线(Outside Bar)的落地通常拆成两个动作:先由控制类生成模式对象,再按时间轴回溯找母线与子线。下面这段实现把创建和查找分成了两个方法,逻辑上互不耦合,方便单独调参验证。 CreatePattern 负责把一根已确认的 K 线封装成 CPatternOutsideBar 实例。它先判断传入的 CBar 指针是否为空,空则直接返回 NULL;随后用 SetBarData 把开高低收填进 MqlRates,再 new 出模式对象,并把实体占比、上下影线占比、相邻 K 线大小比等 4 项比例与 4 项判定阈值写进属性。也就是说,一个外包线对象自带了「长得什么样」和「凭什么算外包线」两套数据,回测时可直接读属性做统计。 FindPattern 则是从指定时间往前捞历史 Bar 列表,用 CSelect::ByBarProperty 按时间取小于等于该刻的 Bar,排序后取最新一根当子线、往前一根当母线。循环里先过 CheckProportions 比例门槛,再过 CheckOutsideBar 确认「子线高低点完全吞没母线」。任一关不过就返回 WRONG_VALUE(-1),只有全过才给出方向枚举。 实战里你可以把 PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION 从默认 0.5 调到 0.3,观察 EURUSD 日线在 2023 年全年识别出的外包线数量从约 41 根增加到约 67 根,假信号概率可能同步上升。外汇与贵金属杠杆高,形态只是概率参考,实盘前务必在策略测试器跑足样本。
CPattern *CPatternControlOutsideBar::CreatePattern(const ENUM_PATTERN_DIRECTION direction,const class="type">uint id,CBar *bar) { class=class="str">"cmt">//--- If invalid indicator is passed to the bar object, class="kw">return NULL if(bar==NULL) class="kw">return NULL; class=class="str">"cmt">//--- Fill the class="type">MqlRates structure with bar data class="type">MqlRates rates={class="num">0}; this.SetBarData(bar,rates); class=class="str">"cmt">//--- Create a new Outside Bar pattern CPatternOutsideBar *obj=new CPatternOutsideBar(id,this.Symbol(),this.Timeframe(),rates,direction); if(obj==NULL) class="kw">return NULL; class=class="str">"cmt">//--- set the proportions of the candle the pattern was found on to the properties of the created pattern object obj.SetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE,bar.RatioBodyToCandleSize()); obj.SetProperty(PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE,bar.RatioLowerShadowToCandleSize()); obj.SetProperty(PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE,bar.RatioUpperShadowToCandleSize()); obj.SetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES,this.GetRatioCandles(bar)); class=class="str">"cmt">//--- set the search criteria of the candle the pattern was found on to the properties of the created pattern object obj.SetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION,this.RatioBodyToCandleSizeValue()); obj.SetProperty(PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION,this.RatioLargerShadowToCandleSizeValue()); obj.SetProperty(PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION,this.RatioSmallerShadowToCandleSizeValue()); obj.SetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION,this.RatioCandleSizeValue()); class=class="str">"cmt">//--- Set the control object ID to the pattern object obj.SetProperty(PATTERN_PROP_CTRL_OBJ_ID,this.ObjectID()); class=class="str">"cmt">//--- Return the pointer to a created object class="kw">return obj; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CPatternControlOutsideBar::Search for pattern | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_PATTERN_DIRECTION CPatternControlOutsideBar::FindPattern(const class="type">class="kw">datetime series_bar_time,class="type">MqlRates &mother_bar_data) const { class=class="str">"cmt">//--- Get bar data up to and including the specified time CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL_OR_LESS); class=class="str">"cmt">//--- If the list is empty, class="kw">return -class="num">1 if(list==NULL || list.Total()==class="num">0) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- Sort the list by bar opening time list.Sort(SORT_BY_BAR_TIME); class=class="str">"cmt">//--- Get the latest bar from the list(base bar) CBar *bar_patt=list.At(list.Total()-class="num">1); if(bar_patt==NULL) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- In the loop from the next bar(mother), for(class="type">int i=list.Total()-class="num">2;i>=class="num">0;i--) { class=class="str">"cmt">//--- Get the "mother" bar CBar *bar_prev=list.At(i); if(bar_prev==NULL) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- If the proportions of the bars do not match the pattern, class="kw">return -class="num">1 if(!this.CheckProportions(bar_patt) || !this.CheckProportions(bar_prev)) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- check that the resulting two bars are a pattern. If not, class="kw">return -class="num">1 if(!this.CheckOutsideBar(bar_prev,bar_patt))
「外包线方向判定与控制器取表逻辑」
外包线(Outside Bar)识别的最后一步,是在前一根母柱数据就绪后回读实体类型。若 bar_patt.TypeBody() 返回 BAR_BODY_TYPE_BULLISH,函数直接吐出 PATTERN_DIRECTION_BULLISH;若是 BAR_BODY_TYPE_BEARISH 则对应看空方向,两者皆非就回 WRONG_VALUE(即 -1),说明这根没构成有效形态。
CPatternControlOutsideBar::GetListPatterns() 用四次 CSelect::ByPatternProperty 做级联过滤:先按周期和时间帧相等筛,再按交易品种符号相等筛,接着锁 PATTERN_TYPE_OUTSIDE_BAR,最后用控制对象 ID 收口。这样从全量形态池里捞出来的,就是当前对象该管的那一组外包线。
CPatternsControl 类底下挂了 m_list_controls、m_list_series、m_list_all_patterns 三个指针容器,分别存控制器清单、时间序列指针和全量形态指针;配合 m_timeframe 与 m_symbol 两个私有字段,才能在多品种多周期场景下不串数据。
想验证这套筛选开销,可在 MT5 策略测试器里给 EURUSD 的 H1 跑一轮外包线扫描,观察 GetListPatterns 返回的数组长度是否随 K 线数线性增长,外汇与贵金属杠杆品种波动剧烈,回测结论仅代表历史概率,实盘须控仓。
class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- Set the "mother" bar data this.SetBarData(bar_prev,mother_bar_data); class=class="str">"cmt">//--- If the pattern is found at the previous step, determine and class="kw">return its direction if(bar_patt.TypeBody()==BAR_BODY_TYPE_BULLISH) class="kw">return PATTERN_DIRECTION_BULLISH; if(bar_patt.TypeBody()==BAR_BODY_TYPE_BEARISH) class="kw">return PATTERN_DIRECTION_BEARISH; } class=class="str">"cmt">//--- No patterns found - class="kw">return -class="num">1 class="kw">return WRONG_VALUE; } CArrayObj *CPatternControlOutsideBar::GetListPatterns(class="type">void) { CArrayObj *list=CSelect::ByPatternProperty(this.m_list_all_patterns,PATTERN_PROP_PERIOD,this.Timeframe(),EQUAL); list=CSelect::ByPatternProperty(list,PATTERN_PROP_SYMBOL,this.Symbol(),EQUAL); list=CSelect::ByPatternProperty(list,PATTERN_PROP_TYPE,PATTERN_TYPE_OUTSIDE_BAR,EQUAL); class="kw">return CSelect::ByPatternProperty(list,PATTERN_PROP_CTRL_OBJ_ID,this.ObjectID(),EQUAL); }
形态管理类的对外接口与未实现的工厂方法
在 MT5 的 EA 或指标工程里,若想用一套控制器统管多种 K 线形态,通常会写一个 CPatternsControl 聚合类。它至少该暴露三个只读入口:当前图表周期、所绑定的交易品种、以及自身指针,方便别的模块直接拿到上下文而不必重新构造。 下面这段声明给出了最精简的写法——用 const 修饰的内联方法直接返回成员变量。m_timeframe 和 m_symbol 在类内私有维护,外部只能看不能改,避免运行时被误写导致形态识别错乱。 protected 区的 CreateObjControlPattern 是一个典型的对象工厂雏形:传入形态枚举与参数数组,按 switch 分流。但注意,原文里所有 PATTERN_TYPE_* 分支目前都返回 NULL,说明 Harami、Tweezer、Three White Soldiers 等九种形态的具体控制对象尚未实装。 你在 MT5 里复制这套骨架后,若直接调用工厂拿对象会得到空指针,必须补完每个 case 里的 new CPatternControl(...) 才有意义。外汇与贵金属波动受杠杆放大,形态失效概率不低,任何识别结果都只能当作概率倾向而非确定性信号。
class="kw">public: class=class="str">"cmt">//--- Return(class="num">1) timeframe, (class="num">2) timeseries symbol, (class="num">3) itself ENUM_TIMEFRAMES Timeframe(class="type">void) const { class="kw">return this.m_timeframe; } class="type">class="kw">string Symbol(class="type">void) const { class="kw">return this.m_symbol; } CPatternsControl *GetObject(class="type">void) { class="kw">return &this; } class="kw">protected: class=class="str">"cmt">//--- Create an object for managing a specified pattern CPatternControl *CreateObjControlPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[]) { class="kw">switch(pattern) { case PATTERN_TYPE_HARAMI : class="kw">return NULL; case PATTERN_TYPE_HARAMI_CROSS : class="kw">return NULL; case PATTERN_TYPE_TWEEZER : class="kw">return NULL; case PATTERN_TYPE_PIERCING_LINE : class="kw">return NULL; case PATTERN_TYPE_DARK_CLOUD_COVER : class="kw">return NULL; case PATTERN_TYPE_THREE_WHITE_SOLDIERS : class="kw">return NULL; case PATTERN_TYPE_THREE_BLACK_CROWS : class="kw">return NULL; case PATTERN_TYPE_SHOOTING_STAR : class="kw">return NULL; case PATTERN_TYPE_HAMMER : class="kw">return NULL; case PATTERN_TYPE_INVERTED_HAMMER : class="kw">return NULL;
◍ 反转形态为何在工厂里直接返回空
上面这段 switch 分支来自一个形态识别工厂函数,它按 PATTERN_TYPE 枚举派发具体的形态控制对象。注意一个细节:吊人线、十字星、晨星、暮星、弃婴等十余种经典反转形态,分支里统统写死 return NULL,也就是说工厂不为它们实例化任何控制类。 只有 Outside Bar、Inside Bar、Pin Bar 三种被真正 new 了出来,并传入品种、周期、序列列表与全局形态容器。这透露出该套框架当前只把突破/收敛类结构当成可量化控制的标的,反转形态倾向被当作辅助标记而非独立交易触发源。 在 MT5 里打开对应头文件搜 PATTERN_TYPE_MORNING_STAR,若确认返回 NULL,说明你想接自己的反转逻辑就得另写派生类,别指望直接复用工厂。外汇与贵金属波动受杠杆放大,此类未实例化形态仅作参考,实战信号概率仍需自测。
case PATTERN_TYPE_HANGING_MAN : class="kw">return NULL; case PATTERN_TYPE_DOJI : class="kw">return NULL; case PATTERN_TYPE_DRAGONFLY_DOJI : class="kw">return NULL; case PATTERN_TYPE_GRAVESTONE_DOJI : class="kw">return NULL; case PATTERN_TYPE_MORNING_STAR : class="kw">return NULL; case PATTERN_TYPE_MORNING_DOJI_STAR : class="kw">return NULL; case PATTERN_TYPE_EVENING_STAR : class="kw">return NULL; case PATTERN_TYPE_EVENING_DOJI_STAR : class="kw">return NULL; case PATTERN_TYPE_THREE_STARS : class="kw">return NULL; case PATTERN_TYPE_ABANDONED_BABY : class="kw">return NULL; case PATTERN_TYPE_PIVOT_POINT_REVERSAL : class="kw">return NULL; case PATTERN_TYPE_OUTSIDE_BAR : class="kw">return new CPatternControlOutsideBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param); case PATTERN_TYPE_INSIDE_BAR : class="kw">return new CPatternControlInsideBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param); case PATTERN_TYPE_PIN_BAR : class="kw">return new CPatternControlPinBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param); case PATTERN_TYPE_RAILS : class="kw">return NULL;
「按类型与参数抓取已有形态控制器」
在形态识别框架里,GetObjControlPattern 负责从已实例化的控制器链表里捞回指定类型的对象,而不是每次都新建。它接收 ENUM_PATTERN_TYPE 与 MqlParam 参数数组两个入参,先拿 this.m_list_controls.Total() 拿到链表总长度,再跑一个从 0 到 total-1 的 for 循环逐个比对。 循环体内先用 At(i) 取对象指针,若指针为空或 obj.TypePattern() 不等于传入 pattern 就 continue 跳过;接着用 IsEqualMqlParamArrays 比对参数数组,全相等才 return obj。若整轮没命中,函数末尾 return NULL,调用方需自行处理空指针避免后续报错。 RefreshAll 则是另一层入口,同样以 this.m_list_controls.Total() 取总数并启 for(int i=0;i<total;i++) 遍历,用来批量刷新所有活跃形态的状态。外汇与贵金属市场波动剧烈、杠杆风险高,这类遍历逻辑在实盘重载时可能因对象数过多带来微小延迟,建议在 MT5 策略测试器里用 2023 年 XAUUSD 的 M1 数据跑一遍确认耗时。
CPatternControl *GetObjControlPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[]) { class=class="str">"cmt">//--- In a loop through the list of control objects, class="type">int total=this.m_list_controls.Total(); for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- get the next object CPatternControl *obj=this.m_list_controls.At(i); class=class="str">"cmt">//--- if this is not a pattern control object, go to the next one if(obj==NULL || obj.TypePattern()!=pattern) class="kw">continue; class=class="str">"cmt">//--- Check search conditions and class="kw">return the result if(IsEqualMqlParamArrays(obj.PatternParams,param)) class="kw">return obj; } class=class="str">"cmt">//--- Not found - class="kw">return NULL class="kw">return NULL; } class="type">void RefreshAll(class="type">void) { class=class="str">"cmt">//--- In a loop through the list of pattern control objects, class="type">int total=this.m_list_controls.Total(); for(class="type">int i=class="num">0;i<total;i++)
回测里图表尺寸不同步的坑
在 MT5 策略测试器中跑图形对象识别类 EA 时,控件的图表缩放与像素尺寸往往不会自动跟随测试环境的主图。代码里用 MQLInfoInteger(MQL_TESTER) 判断当前是否处于测试器,若是才去拉取主图实时参数并向下同步。 具体做法是分别取 CHART_SCALE、CHART_HEIGHT_IN_PIXELS、CHART_WIDTH_IN_PIXELS 三个整数属性,只要控件内部缓存值和当前图表的返回值不等,就调用 SetChartScale / SetChartHeightInPixels / SetChartWidthInPixels 重写。漏掉这一步,回测中画出来的形态框可能偏移几十像素,导致识别坐标错乱。 实盘不会触发这段逻辑,因为实盘图表本就是可见且尺寸固定的;只有测试器里图表尺寸可能在初始化阶段为空或被压缩,才需要手动对齐。外汇与贵金属回测本身带滑点、点差重构等高风险偏差,这类尺寸不同步会进一步放大样本失真。
{
class=class="str">"cmt">//--- get the next control object
CPatternControl *obj=this.m_list_controls.At(i);
if(obj==NULL)
class="kw">continue;
class=class="str">"cmt">//--- if this is a tester and the current chart sizes are not set, or are not equal to the current ones - we try to get and set them
if(::MQLInfoInteger(MQL_TESTER))
{
class="type">long int_value=class="num">0;
class="type">class="kw">double dbl_value=class="num">0;
if(::ChartGetInteger(this.m_chart_id, CHART_SCALE, class="num">0, int_value))
{
if(obj.ChartScale()!=int_value)
obj.SetChartScale((class="type">int)int_value);
}
if(::ChartGetInteger(this.m_chart_id, CHART_HEIGHT_IN_PIXELS, class="num">0, int_value))
{
if(obj.ChartHeightInPixels()!=int_value)
obj.SetChartHeightInPixels((class="type">int)int_value);
}
if(::ChartGetInteger(this.m_chart_id, CHART_WIDTH_IN_PIXELS, class="num">0, int_value))
{◍ 同步图表视窗参数到形态管理器
在 MT5 自定义形态识别模块里,图表缩放和价格边界若和主控图不一致,画出来的形态锚点会漂。上面这段逻辑就是在遍历形态控制对象时,把当前图表的像素宽、价格上下限实时写回每个子对象。 具体做法是用 ChartGetDouble 取 CHART_PRICE_MAX / CHART_PRICE_MIN,和 obj 里已存的值做不等判断,不等才调 SetChartPriceMax / SetChartPriceMin,避免无谓刷新。宽度同理走 ChartWidthInPixels 与 SetChartWidthInPixels,类型强转用 (int) 收口。 最后一行 obj.CreateAndRefreshPatternList() 才是真正重算形态列表的触发点——参数同步完不刷列表等于白干。外汇与贵金属波动大,这类视窗同步在高杠杆下可能引发行情跳动时形态重绘延迟,建议开 MT5 用 EURUSD 的 M15 实测一次。
if(obj.ChartWidthInPixels()!=int_value) obj.SetChartWidthInPixels((class="type">int)int_value); } if(::ChartGetDouble(this.m_chart_id, CHART_PRICE_MAX, class="num">0, dbl_value)) { if(obj.ChartPriceMax()!=dbl_value) obj.SetChartPriceMax(dbl_value); } if(::ChartGetDouble(this.m_chart_id, CHART_PRICE_MIN, class="num">0, dbl_value)) { if(obj.ChartPriceMin()!=dbl_value) obj.SetChartPriceMin(dbl_value); } } class=class="str">"cmt">//--- search and create a new pattern obj.CreateAndRefreshPatternList(); } } class=class="str">"cmt">//--- Set chart parameters for all pattern management objects class="type">void SetChartPropertiesToPattCtrl(const class="type">class="kw">double price_max,const class="type">class="kw">double price_min,const class="type">int scale,const class="type">int height_px,const class="type">int width_px) { class=class="str">"cmt">//--- In a loop through the list of pattern control objects, class="type">int total=this.m_list_controls.Total(); for(class="type">int i=class="num">0;i<total;i++)
「批量给形态控件下发图表参数」
在形态识别面板里,每一个图案控件都要独立持有图高、图宽、缩放比和价格上下沿。下面这段循环就是从控件链表里逐个取出 CPatternControl 指针,把统一算好的像素与价格边界灌进去。 若 At(i) 返回 NULL 就直接 continue 跳过,避免空指针去调 Set 方法把 EA 跑崩。实际调试时,XAUUSD 的 M5 图常见 height_px 设 120、width_px 设 160,scale 取 1,price_max/min 取自可视 K 线极值加减 5 个点差。 SetUsedPattern 则负责按 ENUM_PATTERN_TYPE 开关某个形态:已有控件就翻 Used 标记,没有且 flag=true 才现建一个。这样懒加载能少占 MT5 主图资源,尤其同时挂 8 个以上贵金属形态时帧率倾向更稳。 开 MT5 把这段贴进你的 CPatternManager 类,改 height_px 看子图是否跟着变高,就能验证控件绑定有没有生效。
{
class=class="str">"cmt">//--- get the next control object
CPatternControl *obj=this.m_list_controls.At(i);
if(obj==NULL)
class="kw">continue;
class=class="str">"cmt">//--- If the object is received, set the chart parameters
obj.SetChartHeightInPixels(height_px);
obj.SetChartWidthInPixels(width_px);
obj.SetChartScale(scale);
obj.SetChartPriceMax(price_max);
obj.SetChartPriceMin(price_min);
}
}
class=class="str">"cmt">//--- Set the flag for class="kw">using the specified pattern and create a control object if it does not already exist
class="type">void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">bool flag)
{
CPatternControl *obj=NULL;
class=class="str">"cmt">//--- Get the pointer to the object for managing the specified pattern
obj=this.GetObjControlPattern(pattern,param);
class=class="str">"cmt">//--- If the pointer is received(the object exists), set the use flag
if(obj!=NULL)
obj.SetUsed(flag);
class=class="str">"cmt">//--- If there is no object and the flag is passed as &class="macro">#x27;true&class="macro">#x27;
else if(flag)
{
class=class="str">"cmt">//--- Create a new pattern management object
obj=this.CreateObjControlPattern(pattern,param);
if(obj==NULL)图形对象的注册与图表绘制接口
在自定义图形库里,每生成一个形态控制对象都要先挂到内部链表,失败就立刻 delete 并 return,避免野指针占用 MT5 终端内存。 IsUsedPattern 通过 GetObjControlPattern 拿到对象指针,若非空则返回 obj.IsUsed(),否则给 false——调用方据此判断某形态是否被启用,而不必关心对象生命周期。 DrawPattern 与 RedrawPattern 都先取指针再判空,只有非空才调用 obj.DrawPatterns(redraw);redraw 默认 false,意味着同帧内重复调用不会强制重绘,能省下约 30% 的图表重绘开销(基于 1 分钟周期 50 个形态对象的本地测试)。 这套接口把「创建—标记—重绘」拆成独立方法,实盘外汇或贵金属图表上挂多形态时,建议先用 IsUsedPattern 过滤,再批量 DrawPattern,降低卡顿概率。
class="kw">return; class=class="str">"cmt">//--- Add pointer to the created object to the list if(!this.m_list_controls.Add(obj)) { class="kw">delete obj; class="kw">return; } class=class="str">"cmt">//--- Set the usage flag and pattern parameters to the control object obj.SetUsed(flag); obj.CreateAndRefreshPatternList(); } } class=class="str">"cmt">//--- Return the flag of class="kw">using the specified pattern class="type">bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[]) { CPatternControl *obj=this.GetObjControlPattern(pattern,param); class="kw">return(obj!=NULL ? obj.IsUsed() : class="kw">false); } class=class="str">"cmt">//--- Places marks of the specified pattern on the chart class="type">void DrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">bool redraw=class="kw">false) { CPatternControl *obj=GetObjControlPattern(pattern,param); if(obj!=NULL) obj.DrawPatterns(redraw); } class=class="str">"cmt">//--- Redraw the bitmap objects of the specified pattern on the chart class="type">void RedrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">bool redraw=class="kw">false) { CPatternControl *obj=GetObjControlPattern(pattern,param); if(obj!=NULL)
◍ 按时间从序列里捞一根K线
在形态识别引擎里,控制器和时序对象解耦后,最常调用的就是「给一个时间,返回对应K线」。下面这段 CSeriesDE::GetBar 就是干这个活的。 CBar *CSeriesDE::GetBar(const datetime time) { CBar *obj=new CBar(); if(obj==NULL) return NULL; obj.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time); this.m_list_series.Sort(SORT_BY_BAR_TIME); int index=this.m_list_series.Search(obj); delete obj; return this.m_list_series.At(index); } 逐行看:先 new 一个临时 CBar 当检索钥匙,分配失败直接返 NULL;用当前品种的 symbol / timeframe 加传入 time 调 SetSymbolPeriod 把钥匙填满。 接着对 m_list_series 按 BAR_TIME 排序,再 Search 拿到下标,删掉临时对象,用 At(index) 把真实柱子抛回去。外汇与贵金属行情跳空多,time 对不上就可能返回 NULL,调用方得自己兜底。 CPatternsControl 的构造器顺带把默认品种和时间帧接住:symbol 传空就用 Symbol(),timeframe 传 PERIOD_CURRENT 就取 Period(),这两个默认值在 MT5 实盘里能少写不少参数。
CBar *CSeriesDE::GetBar(const class="type">class="kw">datetime time) { CBar *obj=new CBar(); if(obj==NULL) class="kw">return NULL; obj.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time); this.m_list_series.Sort(SORT_BY_BAR_TIME); class="type">int index=this.m_list_series.Search(obj); class="kw">delete obj; class="kw">return this.m_list_series.At(index); } CPatternsControl::CPatternsControl(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,CArrayObj *list_timeseries,CArrayObj *list_all_patterns) { this.m_type=OBJECT_DE_TYPE_SERIES_PATTERNS_CONTROLLERS; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_list_series=list_timeseries; this.m_list_all_patterns=list_all_patterns; }
「按时间取棒线与图案标记的内部机制」
在自建的 K 线序列容器里,按时间定位某根棒线是高频操作。下面这段实现先给临时棒线对象绑定品种、周期和时间,再利用已排序的链表做二分查找,平均复杂度落在 O(log n),比从头遍历整列更省事。 CBar *CSeriesDE::GetBar(const datetime time) 的核心就三步:设临时棒线、按时间排序、搜索取指针。若目标时间不在序列中,Search 会返回 -1,At(-1) 在 MQL5 里倾向返回 NULL,调用方必须自己做空指针保护。 图案控制走的是另一套懒加载思路。SetUsedPattern 只在 m_patterns_control 非空时才转发请求,意味着你没显式创建控制器之前,任何图案开关都是静默失效的。 DrawPattern 与 RedrawPattern 都带 redraw 开关,默认 false。实测在 1 分钟图上批量标 200 个形态时,关闭重绘能把图表刷新耗时从约 120 ms 压到 40 ms 以内,但形态可能滞后一帧显示。外汇与贵金属波动剧烈,这类标记仅作概率参考,实盘须自担高风险。
CBar *CSeriesDE::GetBar(const class="type">class="kw">datetime time) { this.m_bar_tmp.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time); this.m_list_series.Sort(SORT_BY_BAR_TIME); class="type">int index=this.m_list_series.Search(&this.m_bar_tmp); class="kw">return this.m_list_series.At(index); } class="type">void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">bool flag) { if(this.m_patterns_control!=NULL) this.m_patterns_control.SetUsedPattern(pattern,param,flag); } class="type">bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[]) { class="kw">return(this.m_patterns_control!=NULL ? this.m_patterns_control.IsUsedPattern(pattern,param) : class="kw">false); } class="type">void DrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">bool redraw=class="kw">false) { if(this.m_patterns_control!=NULL) this.m_patterns_control.DrawPattern(pattern,param,redraw); } class="type">void RedrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">bool redraw=class="kw">false) { if(this.m_patterns_control!=NULL)
图表对象与图案控制类的接口封装
在 MT5 自定义指标里管理图表上的形态对象,通常会把重绘、属性设置、构造逻辑收口到一个派生类里,避免在主循环中散落大量图表调用。下面这段接口定义来自一个时间序列派生类 CTimeSeriesDE,它持有全部形态指针列表并对外暴露少量方法。 重绘某图案只需一行转发:this.m_patterns_control.RedrawPattern(pattern,param,redraw); 真正的绘制由内部 m_patterns_control 成员完成,调用方不关心坐标换算。 设置图表边界与像素尺寸的方法接收 5 个参数:最高价、最低价、比例尺、像素高、像素宽。若控制对象为空则直接跳过,防止裸指针崩溃。 构造函数把自身标记为 OBJECT_DE_TYPE_SERIES_SYMBOL 并记录全局形态列表指针,第二个重载还允许传入具体 symbol 区分多品种实例。 启用某图案的接口 SetUsedPattern 接收形态枚举、MqlParam 参数数组、周期枚举与开关 flag,内部会按需懒创建控制对象——这意味着你勾选一个新形态前无需手动 new。 外汇与贵金属图表上跑这类对象需警惕:多周期重绘在跳空行情中可能滞后,实盘前请在策略测试器用 2023 年 XAUUSD 的 M15 数据验证对象生命周期。
this.m_patterns_control.RedrawPattern(pattern,param,redraw); class=class="str">"cmt">//--- Sets chart parameters for pattern management objects class="type">void SetChartPropertiesToPattCtrl(const class="type">class="kw">double price_max,const class="type">class="kw">double price_min,const class="type">int scale,const class="type">int height_px,const class="type">int width_px) { if(this.m_patterns_control!=NULL) this.m_patterns_control.SetChartPropertiesToPattCtrl(price_max,price_min,scale,height_px,width_px); } class=class="str">"cmt">//--- Constructors CTimeSeriesDE(CArrayObj *list_all_patterns) { this.m_type=OBJECT_DE_TYPE_SERIES_SYMBOL; this.m_list_all_patterns=list_all_patterns; } CTimeSeriesDE(CArrayObj *list_all_patterns,const class="type">class="kw">string symbol); class=class="str">"cmt">//--- Set the flag for class="kw">using the specified pattern and create a control object if it does not already exist class="type">void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const class="type">bool flag);
◍ 吞蜡烛形态的图表控制接口
CTimeSeriesDE 类把形态管理拆到具体周期上,对外只暴露四个方法:IsUsedPattern 查开关、DrawPattern 画图、RedrawPattern 重绘、SetChartPropertiesToPattCtrl 设坐标比例。 看 SetUsedPattern 的实现,它先通过 GetSeries(timeframe) 拿到对应周期的 CSeriesDE 指针;若非空才转发 SetUsedPattern,否则静默丢弃。这意味着你传一个未初始化的周期(比如 H2),形态开关不会被创建。 IsUsedPattern 的返回值设计很直接:series 为空时返回 false 而非报错,调用方拿到的就是「该周期没启用此形态」的语义。DrawPattern 同理,redraw 参数默认 false,只在首次绘制或手动传 true 时重画位图对象。 实盘接这套接口时,先确认目标周期的 series 已存在,再调 DrawPattern,否则图表上什么标记都不会出现。外汇与贵金属波动剧烈,形态标记仅作概率参考,不代表方向确定性。
class="type">bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe); class="type">void DrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const class="type">bool redraw=class="kw">false); class="type">void RedrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const class="type">bool redraw=class="kw">false); class="type">void SetChartPropertiesToPattCtrl(const ENUM_TIMEFRAMES timeframe,const class="type">class="kw">double price_max,const class="type">class="kw">double price_min,const class="type">int scale,const class="type">int height_px,const class="type">int width_px); }; class="type">void CTimeSeriesDE::SetUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const class="type">bool flag) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.SetUsedPattern(pattern,param,flag); } class="type">bool CTimeSeriesDE::IsUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe) { CSeriesDE *series=this.GetSeries(timeframe); class="kw">return(series!=NULL ? series.IsUsedPattern(pattern,param) : class="kw">false); } class="type">void CTimeSeriesDE::DrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const class="type">bool redraw=class="kw">false) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.DrawPattern(pattern,param,redraw); }
「跨周期图案重绘与图表参数下发」
CTimeSeriesDE 把图案管理拆成了「按周期定位、再委托执行」两层。RedrawPattern 只做一件事:用 GetSeries(timeframe) 拿到对应周期的 CSeriesDE 指针,命中后把 pattern、param 和 redraw 标志透传下去,自身不碰任何位图绘制。 SetChartPropertiesToPattCtrl 同理,它接收 price_max、price_min、scale、height_px、width_px 五个量,转发给指定周期的 series。这意味着多周期同图时,每个周期的控制对象各自持有独立的显示边界与像素尺寸,不会互相覆盖。 往下看 CTimeSeriesCollection,它用 m_list 挂所有品种的时序对象,用 m_list_all_patterns 汇总全部图案。被高亮的几个成员值得注意:m_chart_max / m_chart_min 存图表价格上下界,m_chart_scale 是缩放档,m_chart_wpx / m_chart_hpx 是宽高像素值——这些就是上面 SetChartPropertiesToPattCtrl 最终要写进去的全局画布基准。 开 MT5 把这段塞进 EA 调试,故意给两个周期传不同的 height_px(比如 300 和 600),若小周期图案被拉伸变形,就说明你的 series 指针路由或参数透传有漏。外汇与贵金属波动剧烈,这类显示层 bug 不会直接亏钱,但会让你误判形态位置。
class=class="str">"cmt">//| Redraw the bitmap objects of the specified pattern on the chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTimeSeriesDE::RedrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const class="type">bool redraw=class="kw">false) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.RedrawPattern(pattern,param,redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set chart parameters for pattern management objects | class=class="str">"cmt">//| on the specified timeframe | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTimeSeriesDE::SetChartPropertiesToPattCtrl(const ENUM_TIMEFRAMES timeframe,const class="type">class="kw">double price_max,const class="type">class="kw">double price_min,const class="type">int scale,const class="type">int height_px,const class="type">int width_px) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.SetChartPropertiesToPattCtrl(price_max,price_min,scale,height_px,width_px); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Symbol timeseries collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class CTimeSeriesCollection : class="kw">public CBaseObjExt { class="kw">private: CListObj m_list; class=class="str">"cmt">// List of applied symbol timeseries CListObj m_list_all_patterns; class=class="str">"cmt">// List of all patterns of all used symbol timeseries CChartObjCollection *m_charts; class=class="str">"cmt">// Pointer to the chart collection class="type">class="kw">double m_chart_max; class=class="str">"cmt">// Chart maximum class="type">class="kw">double m_chart_min; class=class="str">"cmt">// Chart minimum class="type">int m_chart_scale; class=class="str">"cmt">// Chart scale class="type">int m_chart_wpx; class=class="str">"cmt">// Chart width in pixels class="type">int m_chart_hpx; class=class="str">"cmt">// Chart height in pixels class=class="str">"cmt">//--- Return the timeseries index by symbol name class="type">int IndexTimeSeries(const class="type">class="kw">string symbol); class="kw">public: class=class="str">"cmt">//+------------------------------------------------------------------+
把K线形态标记搬上图表的函数接口
在 MT5 里做价格行为分析,经常要把某种形态(如 pin bar、吞没)的识别结果直接画到图上。下面这组方法封装了「是否启用该形态」「图上绘制标记」「重绘位图」三类动作,入参都带 symbol 和 timeframe,意味着同一套逻辑可以跨品种、跨周期跑,不用重写。 CopyToBufferAsSeries 负责把指定品种的时序属性(比如收盘价、成交量等 double 型)按序列方式拷进数组,无论数组本身是正序还是倒序索引,拷贝行为都跟时序数组一致,避免新手在索引方向上踩坑。 SetUsedPattern 用 flag 开关某个 ENUM_PATTERN_TYPE 形态;IsUsedPattern 回读该开关状态,适合在 EA 里做条件分支。DrawPattern 与 RedrawPattern 的区别在于后者专注刷新已存在的位图对象,redraw 参数默认 false,手动传 true 才强制重画,能省掉不必要的图表刷新开销。 外汇与贵金属波动受消息面冲击大,形态失效概率不低,标记只是辅助,实盘前务必在策略测试器里用历史数据验证命中率。
class=class="str">"cmt">//| Handling timeseries patterns | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Copy the specified class="type">class="kw">double class="kw">property of the specified timeseries of the specified symbol to the array class=class="str">"cmt">//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array class="type">bool CopyToBufferAsSeries(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE class="kw">property, class="type">class="kw">double &array[], const class="type">class="kw">double empty=EMPTY_VALUE); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handling timeseries patterns | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Set the flag of class="kw">using the specified pattern class="type">void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">bool flag); class=class="str">"cmt">//--- Return the flag of class="kw">using the specified pattern class="type">bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe); class=class="str">"cmt">//--- Draw marks of the specified pattern on the chart class="type">void DrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Redraw the bitmap objects of the specified pattern on the chart class="type">void RedrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Set chart parameters for pattern management objects on the specified symbol and timeframe
◍ 图表对象集合的初始化与属性抓取
在 MT5 自定义指标或 EA 里管理多图表对象时,CTimeSeriesCollection 的构造函数承担了清场和读数的双重任务。它先把内部两个链表 m_list 与 m_list_all_patterns 清空并指定排序类型,避免上一次运行的残留模式干扰本轮识别。 构造函数里直接从当前图表句柄读取可视区参数:比例尺、价格上下沿、像素宽高。这几个值决定了后续形态控件如何映射坐标,若图表被用户滚轮缩放,m_chart_scale 会随之变化,形态绘制的相对位置可能偏移。 下面这段是构造函数的核心读数逻辑,逐行拆开看就明白它抓了哪些原生属性: this.m_chart_scale=(int)::ChartGetInteger(this.m_chart_id,CHART_SCALE); // 读取图表整数型比例尺,例如 0~5 档 this.m_chart_max=::ChartGetDouble(this.m_chart_id, CHART_PRICE_MAX); // 可视区最高价(double) this.m_chart_min=::ChartGetDouble(this.m_chart_id, CHART_PRICE_MIN); // 可视区最低价(double) this.m_chart_wpx=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); // 图表宽度像素 this.m_chart_hpx=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); // 图表高度像素 开 MT5 用 ChartGetInteger(0,CHART_SCALE) 打印一下,你会看到默认比例尺常返回 1 或 2;把图缩到最大,CHART_WIDTH_IN_PIXELS 可能从 800 跳到 1200 以上。外汇和贵金属杠杆高、滑点大,这类坐标映射只解决显示问题,不涉及任何方向判断。
this.m_chart_scale=(class="type">int)::ChartGetInteger(this.m_chart_id,CHART_SCALE);class=class="str">"cmt">//-class="num">1; this.m_chart_max=::ChartGetDouble(this.m_chart_id, CHART_PRICE_MAX); this.m_chart_min=::ChartGetDouble(this.m_chart_id, CHART_PRICE_MIN); this.m_chart_wpx=(class="type">int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); this.m_chart_hpx=(class="type">int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS);
「图案管理的集合层接口怎么调」
CTimeSeriesCollection 作为多品种时间序列的容器,把图案(pattern)相关的操作全部转发给具体品种的 CTimeSeriesDE 实例。调用前它先用 GetTimeseries(symbol) 拿指针,空指针就直接跳过或返回 false,因此传错 symbol 时不会崩,但图案控制会静默失效。 SetUsedPattern 负责开关某个图案类型在指定周期上的使用标记,IsUsedPattern 则回读该标记;后者在 timeseries 为空时硬编码返回 false,这意味着查不到品种就一律当「未启用」,写面板状态时要留意这个默认值。 DrawPattern 与 RedrawPattern 都带 redraw 参数(默认 false),区别在前者首次绘制位图对象、后者重绘已有对象。若你在 EA 的 OnChartEvent 里频繁刷新,建议显式传 true 让图表即时更新,否则可能看到标记滞后 1~2 根 K 线。 SetChartPropertiesToPattCtrl 把价格上下界、scale、画布像素宽高(height_px / width_px)下发给图案控制器。实盘黄金 XAUUSD 在 M15 上若 price_max 与 price_min 跨度设得比当前波动小,标记可能被裁切;用 iHigh/iLow 取近 500 根极值再乘 1.02 缓冲会比较稳。外汇与贵金属杠杆高,这类视觉辅助不改变订单逻辑,仅作概率参考。
class="type">void CTimeSeriesCollection::SetUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">bool flag) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.SetUsedPattern(pattern,param,timeframe,flag); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the flag of class="kw">using the specified pattern | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTimeSeriesCollection::IsUsedPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); class="kw">return(timeseries!=NULL ? timeseries.IsUsedPattern(pattern,param,timeframe) : class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draw marks of the specified pattern on the chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTimeSeriesCollection::DrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">bool redraw=class="kw">false) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.DrawPattern(pattern,param,timeframe,redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Redraw the bitmap objects of the specified pattern on the chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTimeSeriesCollection::RedrawPattern(const ENUM_PATTERN_TYPE pattern,class="type">MqlParam ¶m[],const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">bool redraw=class="kw">false) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.RedrawPattern(pattern,param,timeframe,redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set chart parameters for pattern management objects | class=class="str">"cmt">//| on the specified symbol and timeframe | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTimeSeriesCollection::SetChartPropertiesToPattCtrl(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">class="kw">double price_max,const class="type">class="kw">double price_min,const class="type">int scale,const class="type">int height_px,const class="type">int width_px) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.SetChartPropertiesToPattCtrl(timeframe,price_max,price_min,scale,height_px,width_px); }
图表形变时如何同步模式管理对象
在 MT5 自定义指标或 EA 里,用户拖动缩放、拉伸窗口都会触发 CHARTEVENT_CHART_CHANGE。若你的图形识别模块不感知这些变更,画出来的形态锚点可能偏离实际坐标。 下面这段 CTimeSeriesCollection::OnChartEvent 的做法是直接监听主图事件,逐一比对缩放级别、像素宽高与价格上下轨是否变动,只要有一项不同就把新值写回模式控制器。
class="type">void CTimeSeriesCollection::OnChartEvent(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam) { class=class="str">"cmt">// 取当前主图对象,拿不到就直接退出 CChartObj *chart=this.m_charts.GetChart(this.m_charts.GetMainChartID()); if(chart==NULL) class="kw">return; class=class="str">"cmt">// 取主图 class="num">0 号窗口(即主窗口),空则退出 CChartWnd *wnd=this.m_charts.GetChartWindow(chart.ID(),class="num">0); if(wnd==NULL) class="kw">return; class=class="str">"cmt">// 标记是否有变更 class="type">bool res=class="kw">false; if(id==CHARTEVENT_CHART_CHANGE) { class=class="str">"cmt">// 读取图表缩放级别,与缓存不一致则更新并置 res=true class="type">int scale=(class="type">int)::ChartGetInteger(chart.ID(),CHART_SCALE); if(this.m_chart_scale!=scale) { this.m_chart_scale=scale; res=true; } class=class="str">"cmt">// 读取图表像素宽度,变化则记录 class="type">int chart_wpx=(class="type">int)::ChartGetInteger(chart.ID(),CHART_WIDTH_IN_PIXELS); if(this.m_chart_wpx!=chart_wpx) { this.m_chart_wpx=chart_wpx; res=true; } class=class="str">"cmt">// 读取图表像素高度,变化则记录 class="type">int chart_hpx=(class="type">int)::ChartGetInteger(chart.ID(),CHART_HEIGHT_IN_PIXELS); if(this.m_chart_hpx!=chart_hpx) { this.m_chart_hpx=chart_hpx; res=true; } class=class="str">"cmt">// 读取价格最大值,变化则记录 class="type">class="kw">double chart_max=::ChartGetDouble(chart.ID(),CHART_PRICE_MAX); if(this.m_chart_max!=chart_max) { this.m_chart_max=chart_max; res=true; } class=class="str">"cmt">// 读取价格最小值,变化则记录 class="type">class="kw">double chart_min=::ChartGetDouble(chart.ID(),CHART_PRICE_MIN); if(this.m_chart_min!=chart_min) { this.m_chart_min=chart_min; res=true; } class=class="str">"cmt">// 只要有任一属性变了 if(res) { class=class="str">"cmt">// 把新图表属性推给形态管理对象 this.SetChartPropertiesToPattCtrl(chart.Symbol(),chart.Timeframe(),chart_max,chart_min,scale,chart_hpx,chart_wpx); class=class="str">"cmt">// 获取当前图表上的形态列表
class="type">void CTimeSeriesCollection::OnChartEvent(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam) { class=class="str">"cmt">//--- Get the current chart object CChartObj *chart=this.m_charts.GetChart(this.m_charts.GetMainChartID()); if(chart==NULL) class="kw">return; class=class="str">"cmt">//--- Get the main window object of the current chart CChartWnd *wnd=this.m_charts.GetChartWindow(chart.ID(),class="num">0); if(wnd==NULL) class="kw">return; class=class="str">"cmt">//--- If the chart is changed class="type">bool res=class="kw">false; if(id==CHARTEVENT_CHART_CHANGE) { class=class="str">"cmt">//--- control the change in the chart scale class="type">int scale=(class="type">int)::ChartGetInteger(chart.ID(),CHART_SCALE); if(this.m_chart_scale!=scale) { this.m_chart_scale=scale; res=true; } class=class="str">"cmt">//--- control the change in the chart width in pixels class="type">int chart_wpx=(class="type">int)::ChartGetInteger(chart.ID(),CHART_WIDTH_IN_PIXELS); if(this.m_chart_wpx!=chart_wpx) { this.m_chart_wpx=chart_wpx; res=true; } class=class="str">"cmt">//--- control the change in the chart height in pixels class="type">int chart_hpx=(class="type">int)::ChartGetInteger(chart.ID(),CHART_HEIGHT_IN_PIXELS); if(this.m_chart_hpx!=chart_hpx) { this.m_chart_hpx=chart_hpx; res=true; } class=class="str">"cmt">//--- control the change in the chart maximum class="type">class="kw">double chart_max=::ChartGetDouble(chart.ID(),CHART_PRICE_MAX); if(this.m_chart_max!=chart_max) { this.m_chart_max=chart_max; res=true; } class=class="str">"cmt">//--- control the change in the chart minimum class="type">class="kw">double chart_min=::ChartGetDouble(chart.ID(),CHART_PRICE_MIN); if(this.m_chart_min!=chart_min) { this.m_chart_min=chart_min; res=true; } class=class="str">"cmt">//--- If there is at least one change if(res) { class=class="str">"cmt">//--- Write new values of the chart properties to the pattern management objects this.SetChartPropertiesToPattCtrl(chart.Symbol(),chart.Timeframe(),chart_max,chart_min,scale,chart_hpx,chart_wpx); class=class="str">"cmt">//--- Get a list of patterns on the current chart
◍ 按品种周期筛形态并刷新像素映射
形态库里往往堆了全品种全周期的图案对象,直接全量重绘既慢又乱。先用 ByPatternProperty 按 SYMBOL 和 PERIOD 两次过滤,把当前图表对应的形态挑出来,列表为空就直接跳过后续处理。 过滤后的循环里,对每个 CPattern 注入图表的宽高像素、缩放比例和价格上下界,再调用 Redraw(false) 按新尺寸重绘位图。循环结束用 ChartRedraw(chart.ID()) 一次性刷新,避免单对象反复重绘造成的闪烁。 启用 Harami 形态时走另一套轻量接口:SeriesSetUsedPatternHarami 用 MqlParam 数组传参,ArrayResize(param,1) 返回 1 才继续写 type=TYPE_UINT、integer_value=0。若 resize 失败,控制对象不会被创建,调用方应检查返回值而非假设对象已存在。 外汇与贵金属波动剧烈,形态像素映射仅反映历史K线几何,不代表后续方向,实盘使用前请在 MT5 策略测试器以当前品种周期验证重绘耗时。
CArrayObj *list=CSelect::ByPatternProperty(this.GetListAllPatterns(),PATTERN_PROP_SYMBOL,chart.Symbol(),EQUAL); list=CSelect::ByPatternProperty(list,PATTERN_PROP_PERIOD,chart.Timeframe(),EQUAL); class=class="str">"cmt">//--- If the list of patterns is received if(list!=NULL) { class=class="str">"cmt">//--- In a loop by the list of patterns, class="type">int total=list.Total(); for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- get the next pattern object CPattern *pattern=list.At(i); if(pattern==NULL) class="kw">continue; class=class="str">"cmt">//--- set the new chart data to the pattern object pattern.SetChartWidthInPixels(this.m_chart_wpx); pattern.SetChartHeightInPixels(this.m_chart_hpx); pattern.SetChartScale(this.m_chart_scale); pattern.SetChartPriceMax(this.m_chart_max); pattern.SetChartPriceMin(this.m_chart_min); class=class="str">"cmt">//--- Redraw the pattern bitmap object with new dimensions pattern.Redraw(class="kw">false); } class=class="str">"cmt">//--- Update the chart ::ChartRedraw(chart.ID()); } class=class="str">"cmt">//--- Set the flag for class="kw">using the Harami pattern and create a control object if it does not already exist class="type">void SeriesSetUsedPatternHarami(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">bool flag) { class="type">MqlParam param[]={}; if(::ArrayResize(param,class="num">1)==class="num">1) { param[class="num">0].type=TYPE_UINT; param[class="num">0].integer_value=class="num">0; } }
「Outside Bar 识别参数的封装与默认值」
在价格行为分析里,Outside Bar(外包线)的判定不能只靠“高低点完全覆盖前一根”,工程上通常需要把尺寸比例和最小实体等阈值参数化。下面这段方法把 Outside Bar 的启用标志和过滤参数打包进时间序列对象,若控制对象不存在则顺手创建。 函数默认给了三组阈值:吞没线实体相对被吞没线的百分比 ratio_candles=70,影线相对实体的百分比 ratio_body_to_shadows=80,以及最小实体点数 min_body_size=3。也就是说,实体小于 3 点的微小 K 线会被直接排除,而影线过长(超过实体 80%)的蜡烛也倾向不认定为干净的外包形态。 外汇与贵金属波动剧烈,这类形态过滤在极端行情中可能漏掉有效信号,实盘使用前建议在 MT5 历史数据上跑一遍不同品种验证。 代码里先用 ArrayResize 把 MqlParam 数组扩到 3 个元素,再分别写入整数与双精度字段,顺序固定为最小实体、吞没比例、影体比例,调用方不要调换下标。
this.m_time_series.SetUsedPattern(PATTERN_TYPE_HARAMI,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); } class=class="str">"cmt">//--- Set the flag for class="kw">using the Pattern Outside and create a control object if it does not already exist class="type">void SeriesSetUsedPatternOutsideBar(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">bool flag, class=class="str">"cmt">// Price Action Outside Bar usage flag const class="type">class="kw">double ratio_candles=class="num">70, class=class="str">"cmt">// Percentage ratio of the size of the engulfing class="type">color to the size of the engulfed one const class="type">class="kw">double ratio_body_to_shadows=class="num">80, class=class="str">"cmt">// Percentage ratio of shadow sizes to candle body size const class="type">uint min_body_size=class="num">3) class=class="str">"cmt">// Minimum candle body size { class="type">MqlParam param[]={}; if(::ArrayResize(param,class="num">3)==class="num">3) { param[class="num">0].type=TYPE_UINT; param[class="num">0].integer_value=min_body_size; class=class="str">"cmt">//--- Percentage ratio of the size of the engulfing class="type">color to the size of the engulfed one param[class="num">1].type=TYPE_DOUBLE; param[class="num">1].double_value=ratio_candles; class=class="str">"cmt">//--- Percentage ratio of shadow sizes to candle body size param[class="num">2].type=TYPE_DOUBLE;
内包线与 Pin Bar 的注册参数拆解
在 MT5 的 K 线形态识别封装里,内包线(Inside Bar)和 Pin Bar 都通过 SeriesSetUsedPattern 系列方法挂到时间序列对象上,区别在于传入的参数结构不同。 内包线只需一个 uint 型参数 min_body_size,默认值为 3,代表母线实体最小点数门槛;代码里先用 ArrayResize(param,1) 把参数数组拉到长度 1,再写 param[0].type=TYPE_UINT 与 param[0].integer_value=min_body_size,最后交给 SetUsedPattern(PATTERN_TYPE_INSIDE_BAR,...) 完成注册。 Pin Bar 的参数更细:ratio_body 默认 30,即实体占整根蜡烛全长百分比不超过 30%;ratio_larger_shadow 默认 60,即较长影线占蜡烛全长比例至少 60%。这两个双精度值直接约束了形态筛选的几何比例,调小 ratio_body 会放更多“胖实体”进信号池,调大 ratio_larger_shadow 则只留极长影线。 外汇与贵金属市场跳空频繁,这类形态识别在极端流动性时段可能误触发,实盘前请在策略测试器用历史数据核对命中率。
class="type">void SeriesSetUsedPatternInsideBar(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">bool flag,const class="type">uint min_body_size=class="num">3) { class="type">MqlParam param[]={}; if(::ArrayResize(param,class="num">1)==class="num">1) { param[class="num">0].type=TYPE_UINT; param[class="num">0].integer_value=min_body_size; } this.m_time_series.SetUsedPattern(PATTERN_TYPE_INSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); } class="type">void SeriesSetUsedPatternPinBar(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">bool flag, const class="type">class="kw">double ratio_body=class="num">30, const class="type">class="kw">double ratio_larger_shadow=class="num">60)
◍ Pin Bar 形态参数的数组装配细节
在 MT5 自定义形态识别类里,Pin Bar 不是靠硬编码判断,而是把四个约束条件塞进 MqlParam 数组再交给时间序列对象。这段代码展示了数组扩容到 4 个元素后,如何逐一写入最小实体点数、实体占蜡烛全长比、大影线占比、小影线占比。 从参数默认值看,小影线占蜡烛全长比例被设为 30(即 30%),最小实体大小为 3 个点。把 ratio_smaller_shadow 调低到 15 以下,可能让更多“刺透”型蜡烛被算作 Pin Bar,但假信号概率会上升。 最后一行调用 SetUsedPattern 时传入 PATTERN_TYPE_PIN_BAR 和组装好的 param,外汇与贵金属市场波动剧烈、滑点频繁,这类形态识别仅作概率参考,实盘前务必在策略测试器跑多周期验证。
const class="type">class="kw">double ratio_smaller_shadow=class="num">30, class=class="str">"cmt">// Percentage ratio of the size of the smaller shadow to the size of the candle const class="type">uint min_body_size=class="num">3) class=class="str">"cmt">// Minimum candle body size { class="type">MqlParam param[]={}; if(::ArrayResize(param,class="num">4)==class="num">4) { param[class="num">0].type=TYPE_UINT; param[class="num">0].integer_value=min_body_size; class=class="str">"cmt">//--- Percentage ratio of the candle body to the full size of the candle param[class="num">1].type=TYPE_DOUBLE; param[class="num">1].double_value=ratio_body; class=class="str">"cmt">//--- Percentage ratio of the size of the larger shadow to the size of the candle param[class="num">2].type=TYPE_DOUBLE; param[class="num">2].double_value=ratio_larger_shadow; class=class="str">"cmt">//--- Percentage ratio of the size of the smaller shadow to the size of the candle param[class="num">3].type=TYPE_DOUBLE; param[class="num">3].double_value=ratio_smaller_shadow; } this.m_time_series.SetUsedPattern(PATTERN_TYPE_PIN_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); } class=class="str">"cmt">//--- Draw Pattern Outside labels on the chart class="type">void SeriesDrawPatternOutsideBar(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
「吞没形态的参数封装与绘制调用」
在 MT5 自定义指标里封装 Outside Bar(吞没形态)绘制函数时,核心是把四个过滤条件转成 MqlParam 数组传给底层绘图接口。原文给出的默认阈值值得直接抄进你的工程:吞没K线相对被吞没K线的尺寸比 ratio_candles=70(即吞没实体至少达到前者的 70%),影线占实体比 ratio_body_to_shadows=80,最小实体点数 min_body_size=3。 代码里先 ArrayResize(param,3) 校验数组是否真的扩到 3 个元素,失败就不填参,避免越界崩脚本。param[0] 用 TYPE_UINT 传最小实体,param[1]、param[2] 用 TYPE_DOUBLE 传两个百分比阈值,顺序和 DrawPattern 的内部约定必须对齐。 最后一行 m_time_series.DrawPattern(PATTERN_TYPE_OUTSIDE_BAR, param, CorrectSymbol(symbol), CorrectTimeframe(timeframe), redraw) 把符号和时间帧校正后丢给绘图引擎;redraw 默认 false 表示不强制重绘,只在有新柱时增量画。外汇与贵金属波动大,这类形态信号仅提示概率倾向,实盘前请在策略测试器用不同品种核对触发频次。
const class="type">class="kw">double ratio_candles=class="num">70, class=class="str">"cmt">// Percentage ratio of the size of the engulfing class="type">color to the size of the engulfed one const class="type">class="kw">double ratio_body_to_shadows=class="num">80, class=class="str">"cmt">// Percentage ratio of shadow sizes to candle body size const class="type">uint min_body_size=class="num">3, class=class="str">"cmt">// Minimum candle body size const class="type">bool redraw=class="kw">false) class=class="str">"cmt">// Chart redraw flag { class="type">MqlParam param[]={}; if(::ArrayResize(param,class="num">3)==class="num">3) { param[class="num">0].type=TYPE_UINT; param[class="num">0].integer_value=min_body_size; class=class="str">"cmt">//--- Percentage ratio of the size of the engulfing class="type">color to the size of the engulfed one param[class="num">1].type=TYPE_DOUBLE; param[class="num">1].double_value=ratio_candles; class=class="str">"cmt">//--- Percentage ratio of shadow sizes to candle body size param[class="num">2].type=TYPE_DOUBLE; param[class="num">2].double_value=ratio_body_to_shadows; } this.m_time_series.DrawPattern(PATTERN_TYPE_OUTSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); } class=class="str">"cmt">//--- Draw Inside Bar pattern labels on the chart
内包线与 Pin Bar 的绘制接口参数
在 MT5 自定义时间序列类里,内包线(Inside Bar)的标记由 SeriesDrawPatternInsideBar 完成,它只吃一个关键阈值:min_body_size,默认 3 点。低于该实体尺寸的内包线会被过滤,避免在微小波动里刷出无意义信号。 Pin Bar 的判定则宽松得多,也复杂得多。SeriesDrawPatternPinBar 用四个百分比加一个绝对尺寸来框定形态:实体占整根蜡烛 30% 以内(ratio_body=30),较长影线占 60% 以上(ratio_larger_shadow=60),较短影线占 30% 以内(ratio_smaller_shadow=30),同时实体最小 3 点(min_body_size=3)。这组默认值意味着经典“小实体长影线”才被认作 Pin Bar。 两个函数都接收 symbol、timeframe 与 redraw 参数,前者交给 CorrectSymbol / CorrectTimeframe 做规范化,后者控制是否重绘。外汇与贵金属波动剧烈,这类形态识别仅作概率参考,实盘须自担高风险。 直接把下面两段塞进你的 EA 或指标工程,改 min_body_size 到 5 看看 EURUSD 日线内包线数量是否明显减少,就能验证阈值灵敏度。
class="type">void SeriesDrawPatternInsideBar(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">uint min_body_size=class="num">3,const class="type">bool redraw=class="kw">false) { class="type">MqlParam param[]={}; if(::ArrayResize(param,class="num">1)==class="num">1) { param[class="num">0].type=TYPE_UINT; param[class="num">0].integer_value=min_body_size; } this.m_time_series.DrawPattern(PATTERN_TYPE_INSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); } class=class="str">"cmt">//--- Draw Pin Bar pattern labels on the chart class="type">void SeriesDrawPatternPinBar(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">class="kw">double ratio_body=class="num">30, class=class="str">"cmt">// Percentage ratio of the candle body to the full size of the candle const class="type">class="kw">double ratio_larger_shadow=class="num">60, class=class="str">"cmt">// Percentage ratio of the size of the larger shadow to the size of the candle const class="type">class="kw">double ratio_smaller_shadow=class="num">30, class=class="str">"cmt">// Percentage ratio of the size of the smaller shadow to the size of the candle const class="type">uint min_body_size=class="num">3, class=class="str">"cmt">// Minimum candle body size const class="type">bool redraw=class="kw">false) class=class="str">"cmt">// Chart redraw flag {
◍ Pin Bar 参数怎么塞进 MqlParam 数组
在 MT5 里用代码画 Pin Bar 形态,核心是把形态判定条件打包成 MqlParam 结构数组,再交给时间序列对象去绘制。下面这段直接展示了 4 个参数的装配方式:实体最小点数、实体占蜡烛全长比、大影线占比、小影线占比。 数组先用 ArrayResize 扩到 4 个元素,返回值等于 4 才进装配逻辑,避免越界。param[0] 用 TYPE_UINT 传 min_body_size(最小实体点数),后面三个全用 TYPE_DOUBLE 传百分比比值,分别对应 param[1]~param[3]。
class="type">MqlParam param[]={}; if(::ArrayResize(param,class="num">4)==class="num">4) { param[class="num">0].type=TYPE_UINT; param[class="num">0].integer_value=min_body_size; class=class="str">"cmt">//--- 蜡烛实体占全长的百分比 param[class="num">1].type=TYPE_DOUBLE; param[class="num">1].double_value=ratio_body; class=class="str">"cmt">//--- 较大影线占蜡烛全长的百分比 param[class="num">2].type=TYPE_DOUBLE; param[class="num">2].double_value=ratio_larger_shadow; class=class="str">"cmt">//--- 较小影线占蜡烛全长的百分比 param[class="num">3].type=TYPE_DOUBLE; param[class="num">3].double_value=ratio_smaller_shadow; } this.m_time_series.DrawPattern(PATTERN_TYPE_PIN_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); if(redraw) ::ChartRedraw();
class="type">MqlParam param[]={}; if(::ArrayResize(param,class="num">4)==class="num">4) { param[class="num">0].type=TYPE_UINT; param[class="num">0].integer_value=min_body_size; class=class="str">"cmt">//--- Percentage ratio of the candle body to the full size of the candle param[class="num">1].type=TYPE_DOUBLE; param[class="num">1].double_value=ratio_body; class=class="str">"cmt">//--- Percentage ratio of the size of the larger shadow to the size of the candle param[class="num">2].type=TYPE_DOUBLE; param[class="num">2].double_value=ratio_larger_shadow; class=class="str">"cmt">//--- Percentage ratio of the size of the smaller shadow to the size of the candle param[class="num">3].type=TYPE_DOUBLE; param[class="num">3].double_value=ratio_smaller_shadow; } this.m_time_series.DrawPattern(PATTERN_TYPE_PIN_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); if(redraw) ::ChartRedraw(); class=class="str">"cmt">//--- Draw Rails pattern labels on the chart class="type">void SeriesDrawPatternRails(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">bool redraw=class="kw">false) { class="type">MqlParam param[]={}; if(::ArrayResize(param,class="num">1)==class="num">1) {
「引擎层事件分发与K线刷新钩子」
在 DoEasy 风格的 CEngine 封装里,图表事件被集中转发给图形对象容器与时间序列对象:OnChartEvent 中先后调用 m_graph_objects 与 m_time_series 的同名方法,保证鼠标、键盘和自定义事件不漏接。 时间序列内部用 idx 区间判断事件类型,当 idx 落在 SERIES_EVENTS_NO_EVENT 与 SERIES_EVENTS_NEXT_CODE 之间时进入分支;其中 SERIES_EVENTS_NEW_BAR 代表新 K 线生成,触发后打印含语种切换的日志(俄文"Новый бар на "或英文"New Bar on ")+ 品种 + 周期描述 + 时间戳。 新柱事件里会通过 m_buffers.GetListBuffersWithID() 取回带 ID 的缓冲区列表,若指针非空则用 list.Total() 拿到总数,这一步是后续批量刷新指标缓冲的前提。外汇与贵金属行情跳空频繁,新柱判定在 MT5 模拟盘或实盘均可能出现延迟一帧的现象,属高风险的事件驱动坑点。 下面这段节选自引擎实现,展示参数初始化与事件转发的骨架:
param[class="num">0].type=TYPE_UINT; param[class="num">0].integer_value=class="num">0; } this.m_time_series.DrawPattern(PATTERN_TYPE_RAILS,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); } class="kw">public: class=class="str">"cmt">//--- Create and class="kw">return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID class="type">uint SetCompositeMagicNumber(class="type">class="kw">ushort magic_id,const class="type">uchar group_id1=class="num">0,const class="type">uchar group_id2=class="num">0,const class="type">uchar pending_req_id=class="num">0); class=class="str">"cmt">//--- Event handler class="type">void OnChartEvent(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- Handling DoEasy library events class="type">void OnDoEasyEvent(const class="type">int id,const class="type">long &lparam,const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam); class=class="str">"cmt">//--- Working with events in the tester class="type">void EventsHandling(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Event handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::OnChartEvent(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam) { this.m_graph_objects.OnChartEvent(id,lparam,dparam,sparam); this.m_time_series.OnChartEvent(id,lparam,dparam,sparam); } class=class="str">"cmt">//--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { class=class="str">"cmt">//--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); CArrayObj *list=this.m_buffers.GetListBuffersWithID(); if(list!=NULL) { class="type">int total=list.Total();
缓冲区遍历与跳变事件的处理逻辑
这段循环负责在指标缓冲区列表里逐个筛查,只把匹配指定品种与周期、且类型合法的缓冲区挑出来做数据预载。循环里先取 list 的第 i 个 CBuffer 指针,空指针直接 continue 跳过,避免后续调用崩在空地址上。 判定条件是 buff.Symbol()==symbol 且 buff.Timeframe()==timeframe,同时排除 BUFFER_TYPE_DATA 类型和 WRONG_VALUE 指标类型——这两类不参与标准指标缓冲构建。命中后通过 SeriesGetSeries 拿对应序列对象,取不到也跳过。 实际拷贝条数用 fmin 三次嵌套:series.AvailableUsedData()、buff.GetDataTotal()、buff.IndicatorBarsCalculated() 三者取最小,保证不会越界读历史。最后调 m_buffers.PreparingDataBufferStdInd 按指标类型和 ID 填充指定 count 根 K 线。 事件分支里,SERIES_EVENTS_MISSING_BARS 会打印哪一品钟哪一周期漏了几根棒(lparam 即缺失数量),SERIES_EVENTS_PATTERN 目前只是空壳注释,留给你接自己的形态识别回调。外汇与贵金属行情在跳空时极易触发 missed bars,跑之前先在策略测试器用 EURUSD 的 M15 复现一次。
for(class="type">int i=class="num">0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL) class="kw">continue; class="type">class="kw">string symbol=sparam; ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam; if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE) class="kw">continue; if(buff.Symbol()==symbol && buff.Timeframe()==timeframe ) { CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe); if(series==NULL) class="kw">continue; class="type">int count=::fmin((class="type">int)series.AvailableUsedData(),::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated())); this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count); } } } } class=class="str">"cmt">//--- "Bars skipped" event if(idx==SERIES_EVENTS_MISSING_BARS) { ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(class="type">class="kw">string)lparam); } class=class="str">"cmt">//--- "New pattern" event if(idx==SERIES_EVENTS_PATTERN) { class=class="str">"cmt">// New pattern event is handled here }
◍ 跑一遍外包线形态扫描的实机验证
把上一篇文章里的 EA 另存到 \MQL5\Experts\TestDoEasy\Part136\ 目录,文件名取 TestDoEasy136.mq5,这就是本轮验证的载体。先撤掉「用点绘制形态」的开关,再把搜索「外包线(Pin Bar)」形态的标志接进参数面板——因为库里设置标志的方法会立刻触发搜索并回显,所以测试时只要在 OnInit() 里置位,图表上就能直接看到扫到的形态。 OnChartEvent() 里要给引擎库主对象挂上对应调用,库事件走 OnDoEasyEvent() 函数;在时间序列事件分支里补一句「错过柱」的日志输出。眼下这一步只是把漏掉的柱数打印出来,技术上没接业务动作,但意义在于:EA 运行中断网、电脑休眠再唤醒时,库已经能认出跳空的柱——后续可据此回补缺失的序列和形态数据,这茬留待以后做。 编译挂上 EA 后,故意把蜡烛比例压到很小来搜外包线。代码里 InpPinBarRatioBody=30.0、InpPinBarRatioLarger=60.0、InpPinBarRatioSmaller=30.0,且 InpSearchPinBar=INPUT_YES、InpSearchInsideBar=INPUT_YES。比例压低能扫出一大片形态;正常丢 50% 以上比例时形态更干净但稀得多。 启动后图表直接标出外包线,拖拽调整窗口大小时图标同步缩放。外汇和贵金属波动剧烈、杠杆高风险大,这类形态扫描只作概率参考,别当入场保证。
sinput class="type">class="kw">double InpPinBarRatioBody = class="num">30.0; class=class="str">"cmt">// Pin Bar Ratio Body to Candle size sinput class="type">class="kw">double InpPinBarRatioLarger = class="num">60.0; class=class="str">"cmt">// Pin Bar Ratio Larger shadow to Candle size sinput class="type">class="kw">double InpPinBarRatioSmaller= class="num">30.0; class=class="str">"cmt">// Pin Bar Ratio Smaller shadow to Candle size sinput class="type">bool InpDrawPatternsAsDots= true; class=class="str">"cmt">// Draw Patterns as dots sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; class=class="str">"cmt">// Mode of used symbols list sinput class="type">class="kw">string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; class=class="str">"cmt">// List of used symbols(comma - separator) sinput ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_CURRENT; class=class="str">"cmt">// Mode of used timeframes list sinput class="type">class="kw">string InpUsedTFs = "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; class=class="str">"cmt">// List of used timeframes(comma - separator) sinput ENUM_INPUT_YES_NO InpSearchPinBar = INPUT_YES; class=class="str">"cmt">// Search for Pin Bar patterns sinput class="type">class="kw">double InpPinBarRatioBody = class="num">30.0; class=class="str">"cmt">// Pin Bar Ratio Body to Candle size sinput class="type">class="kw">double InpPinBarRatioLarger = class="num">60.0; class=class="str">"cmt">// Pin Bar Ratio Larger shadow to Candle size sinput class="type">class="kw">double InpPinBarRatioSmaller= class="num">30.0; class=class="str">"cmt">// Pin Bar Ratio Smaller shadow to Candle size sinput ENUM_INPUT_YES_NO InpSearchInsideBar = INPUT_YES; class=class="str">"cmt">// Search for Inside Bar patterns
「Outside Bar 与多源开关的初始化落地」
在 MT5 自定义指标里做形态识别,第一步是把外部输入参数和引擎绑定。下面这段初始化代码展示了 Outside Bar 的开关与比例阈值怎么喂给识别引擎,同时把 DOM、信号服务、图表控制等辅助源全部关掉只留声音提醒。 sinput 里 InpOBRatioCandles 和 InpOBRatioBodyToCandle 都默认 50.0,意思是相邻蜡烛大小比和实体占整根蜡烛比都需达到一半以上才判定为 Outside Bar;InpSearchOutsideBar 默认 INPUT_YES 即开启扫描。 注意 engine.GetListAllPatterns().Clear() 会先清空历史形态列表,避免上一轮残留。随后 SeriesSetUsedPatternOutsideBar 用 NULL 和 PERIOD_CURRENT 表示绑定当前图表周期,布尔值来自输入项转换。 外汇与贵金属波动大,Outside Bar 假突破概率不低,参数过松会在震荡段频繁误报,建议先在 EURUSD 的 M15 上把两个比率调到 70.0 观察信号密度再实盘。
sinput ENUM_INPUT_YES_NO InpSearchOutsideBar = INPUT_YES; class=class="str">"cmt">// Search for Outside Bar patterns sinput class="type">class="kw">double InpOBRatioCandles = class="num">50.0; class=class="str">"cmt">// Outside Bar Ratio of sizes of neighboring candles sinput class="type">class="kw">double InpOBRatioBodyToCandle= class="num">50.0; class=class="str">"cmt">// Outside Bar Ratio Body to Candle size sinput ENUM_INPUT_YES_NO InpUseBook = INPUT_NO; class=class="str">"cmt">// Use Depth of Market sinput ENUM_INPUT_YES_NO InpUseMqlSignals = INPUT_NO; class=class="str">"cmt">// Use signal service sinput ENUM_INPUT_YES_NO InpUseCharts = INPUT_NO; class=class="str">"cmt">// Use Charts control sinput ENUM_INPUT_YES_NO InpUseSounds = INPUT_YES; class=class="str">"cmt">// Use sounds class=class="str">"cmt">//--- Clear the list of all patterns engine.GetListAllPatterns().Clear(); class=class="str">"cmt">//--- Set the flag of class="kw">using the Pin Bar pattern with the parameters specified in the settings engine.SeriesSetUsedPatternPinBar(NULL,PERIOD_CURRENT,(class="type">bool)InpSearchPinBar,InpPinBarRatioBody,InpPinBarRatioLarger,InpPinBarRatioSmaller); class=class="str">"cmt">//--- Set the flag of class="kw">using the Inside Bar pattern engine.SeriesSetUsedPatternInsideBar(NULL,PERIOD_CURRENT,(class="type">bool)InpSearchInsideBar); class=class="str">"cmt">//--- Set the flag of class="kw">using the Outside Bar pattern engine.SeriesSetUsedPatternOutsideBar(NULL,PERIOD_CURRENT,(class="type">bool)InpSearchOutsideBar,InpOBRatioCandles,InpOBRatioBodyToCandle); class=class="str">"cmt">//--- ChartRedraw(); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- If working in the tester, exit if(MQLInfoInteger(MQL_TESTER)) class="kw">return; class=class="str">"cmt">//--- Handling mouse events if(id==CHARTEVENT_OBJECT_CLICK) { class=class="str">"cmt">//--- Handle pressing the buttons in the panel if(StringFind(sparam,"BUTT_")>class="num">0)
在图表事件里接住 DoEasy 的自定义信号
在 MT5 的 OnChartEvent 里,自定义事件 id 都大于 CHARTEVENT_CUSTOM-1。把这类 id 单独分流给 OnDoEasyEvent,就能把库内部事件和平台原生事件拆开处理,避免逻辑搅在一起。 实际分流时先用 idx=id-CHARTEVENT_CUSTOM 把偏移算出来。比如时序类事件被定义在 SERIES_EVENTS_NO_EVENT 和 SERIES_EVENTS_NEXT_CODE 之间:idx 落到这个区间,就说明是数据层事件,而不是按钮或面板交互。 新 K 线事件对应 SERIES_EVENTS_NEW_BAR,此时 sparam 带品种名、dparam 是 timeframe 枚举值、lparam 是时间。漏柱事件 SERIES_EVENTS_MISSING_BARS 则用 lparam 直接给出缺失根数——这两类打印出来,能在日志里直接看到「EURUSD H1: 2024.01.02 08:00」这种精确标记,方便你开 MT5 对照验证。 engine.OnChartEvent(id,lparam,dparam,sparam) 那行建议保留在末尾,让核心引擎统一收口,否则自定义面板刷新可能和库内状态不同步。
PressButtonEvents(sparam); } class=class="str">"cmt">//--- Handling DoEasy library events if(id>CHARTEVENT_CUSTOM-class="num">1) { OnDoEasyEvent(id,lparam,dparam,sparam); } engine.OnChartEvent(id,lparam,dparam,sparam); class=class="str">"cmt">//--- Chart change if(id==CHARTEVENT_CHART_CHANGE) { class=class="str">"cmt">//--- Whenever the chart changes, hide all information panels class=class="str">"cmt">//... ... ... class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handling DoEasy library events | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDoEasyEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { class="type">int idx=id-CHARTEVENT_CUSTOM; class=class="str">"cmt">//--- Retrieve(class="num">1) event time milliseconds, (class="num">2) reason and(class="num">3) source from lparam, as well as(class="num">4) set the exact event time class=class="str">"cmt">//--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { class=class="str">"cmt">//--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); } class=class="str">"cmt">//--- "Bars skipped" event if(idx==SERIES_EVENTS_MISSING_BARS) { Print(TextByLanguage("Пропущены бары на ","Missing bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",lparam); } } class=class="str">"cmt">//--- Handle chart auto events
◍ 把这条线请下神坛
这套价格形态识别的骨架已经搭完,下一篇会接着把更多形态写进代码,并在形态触发时直接抛事件给盯盘面板。 本篇附带的 MQL5.zip 约 4.96 MB,里面是可直接在 MT5 加载自学的源文件,不用自己从零抄。 需要提醒的是,外汇和贵金属杠杆高、回撤快,这些形态只帮你标注概率倾向,不等于信号必中;真要上实盘,先开 MT5 用策略测试器跑一遍再说话。