自定义指标:为净额结算账户绘制部分入场、出场和反转交易·进阶篇
(2/3)· 净额账户下多头寸合并与反转逻辑,靠手工盯盘极易漏看均价漂移
◍ 从一张实盘图看指标怎么画线
下面这张实盘截图是我们正在做的自定义指标的真实用法示例:图上是一笔 3 手的买仓,源头是一笔更大的交易,中间经历过多次部分入场、部分出场,甚至方向反转。 因为存在分批操作,你会看到图表上的平均持仓价和实时报价之间有肉眼可见的偏离,同时手数也和初始建仓量对不上——这不是 bug,是部分平仓后的自然结果。 盯着交易事件流就能搞清楚:算法在什么条件下删掉旧线、又在部分出场发生时如何把屏幕上的手数刷新成当前值。开 MT5 把这批事件日志调出来对照图表,比看说明文档直观得多。外汇与贵金属高杠杆,这类平均价跟踪仅作辅助,实际仓位风险仍需自行把控。
「加载页参数从哪几行定」
写自定义指标时,文件头部的 #property 指令直接决定 MT5 指标加载弹窗里呈现的内容与初始样式。改了这些行,不用动核心逻辑,用户在加载界面看到的缓冲区数量、绘图类型和线条颜色就会变。 下面这段声明把指标挂到主图窗口,缓冲区与绘图数量先置 0,随后又给第 1 个 plot 补了标签和线型。注意 indicator_buffers 0 与 indicator_plots 0 并存时,后面再用 indicator_label1 等定义第一个绘图,是 MT5 编译器允许的写法,但容易让初学者误以为真的没有绘图。 [CODE] #property indicator_chart_window // Indicator displayed in the main window #property indicator_buffers 0 // Zero buffers #property indicator_plots 0 // No plotting //--- plot Label1 #property indicator_label1 "Line properties" #property indicator_type1 DRAW_LINE // Line type for the first plot #property indicator_color1 clrRoyalBlue // Line color for the first plot #property indicator_style1 STYLE_SOLID // Line style for the first plot #property indicator_width1 1 // Line width for the first plot [/CODE] 逐行拆一下:indicator_chart_window 指定指标画在主图而非独立子窗;indicator_buffers 0 表示暂不分配数据缓冲区;indicator_plots 0 先声明无绘图序列;从 indicator_label1 开始补第 1 个绘图的名号“Line properties”;type 用 DRAW_LINE 画连续线;color 取 clrRoyalBlue Royal蓝;style 为 STYLE_SOLID 实线;width 1 即 1 像素细线。 开 MT5 随便建个指标把这几行贴进去编译,加载时就能看到主图弹出 RoyalBlue 细线选项。外汇与贵金属波动剧烈,指标仅作辅助,实际信号失效概率不低,请自行回测验证。
class="macro">#class="kw">property indicator_chart_window class=class="str">"cmt">// Indicator displayed in the main window class="macro">#class="kw">property indicator_buffers class="num">0 class=class="str">"cmt">// Zero buffers class="macro">#class="kw">property indicator_plots class="num">0 class=class="str">"cmt">// No plotting class=class="str">"cmt">//--- plot Label1 class="macro">#class="kw">property indicator_label1 "Line properties" class="macro">#class="kw">property indicator_type1 DRAW_LINE class=class="str">"cmt">// Line type for the first plot class="macro">#class="kw">property indicator_color1 clrRoyalBlue class=class="str">"cmt">// Line class="type">color for the first plot class="macro">#class="kw">property indicator_style1 STYLE_SOLID class=class="str">"cmt">// Line style for the first plot class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">// Line width for the first plot
指标缓冲区的初始化与交易事件触发链
MT5 自定义指标启动时,OnInit() 先给名为 Element 的 double 数组分配空间,这个数组就是实际指标缓冲区。它固定三列:索引 0 存价格、1 存成交量、2 存单号,每一行对应历史里的一笔成交。若账户被识别为对冲模式(ACCOUNT_MARGIN_MODE_RETAIL_HEDGING),初始化直接返回 INIT_FAILED,指标从图表移除;否则调 OnTrade() 继续。 OnTrade() 靠 isNewBar 加 isOldBar 的过滤,保证只在每根 K 线成型后触发一次,而不是每次 tick 乱跑。它被激活的时机只有三种:初始化时、新 K 线出现时、以及发生交易事件时。触发后把事件读出来写进 Element,再以线条和文本对象画到屏幕上。 函数开头用静态 datetime 变量 date 记起始时间;若一开始没持仓,date 取当前 K 线开盘时间。有持仓时 PositionsTotal() 大于 0,循环里筛出与当前图表品种相同的持仓,用 HistorySelectByPosition 拉出对应订单,并把 date 更新成这些订单里最早的时间——也就是仓位建仓时间。 当出现不同 ID 的第二笔持仓,先调 ClearRectangles() 清掉旧图形,再把 Element 大小置 0 清空数据;无持仓时同样清图形并重置数组,date 改为服务器当前时间,最后扔给 ListOrdersPositions()。 ListOrdersPositions() 拿到 dateStart 后分两种走向:HistorySelect 区间内没历史就跳到末尾直接 PlotRectangles() 刷新画面;有历史则 HistoryDealsTotal() 非零,逐笔按 DEAL_ENTRY_IN / OUT / INOUT 分类。入场调 AddValue(),出场调 RemoveValue(),反转且单号不在数组里则 AddVolume() 并补价格和成交量差。最后 Sort() 按价格升序排、删成交量为 0 的行,并清掉价格与成交量双零的脏数据,相当于在缓冲区里重建了历史仓位。 外汇与贵金属杠杆高,这类持仓重建逻辑仅用于辅助看盘,不预示方向,实盘前请在策略测试器用历史数据核对 Element 行列数是否符合预期。
class="type">int OnInit() { ArrayResize(Element,class="num">0,class="num">0); class="type">int res=INIT_SUCCEEDED; if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) res=INIT_FAILED; else OnTrade(); class="kw">return(res); } class="type">void OnTrade() { class=class="str">"cmt">//--- class="kw">static class="type">class="kw">datetime date=class="num">0; if(date==class="num">0) date=lastTime; class="type">long positionId=-class="num">1,numberOfPositions=class="num">0; for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) if(m_position.SelectByIndex(i)) if(m_position.Symbol()==ativo000) { numberOfPositions++; positionId=m_position.Identifier(); oldPositionId=positionId; } if(numberOfPositions!=class="num">0) { class=class="str">"cmt">//Print("PositionId: "+positionId); HistorySelectByPosition(positionId); date=TimeCurrent(); for(class="type">int j=class="num">0; j<HistoryDealsTotal(); j++) { class="type">ulong ticket = HistoryDealGetTicket(j); if(ticket > class="num">0) if(HistoryDealGetInteger(ticket,DEAL_TIME)<date) date=(class="type">class="kw">datetime)HistoryDealGetInteger(ticket,DEAL_TIME); } if(HistoryDealsTotal()==class="num">1 && (ArraySize(Element)/class="num">3)>class="num">1) if(ClearRectangles()) ArrayResize(Element,class="num">0,class="num">0); } else { class="type">bool isClean=ClearRectangles();
◍ 从成交历史反推当前持仓结构
清仓后把 Element 数组缩容到 0,若 isClean 为真则把 date 刷成 TimeCurrent(),意思是新仓出来前不再动这个数组。ArrayPrint 在零长度数组上不会真打印,这段代码只是占位,避免误用。 ListOrdersPositions 接一个 datetime 入参,内部用 inicio 到 TimeCurrent() 圈定回看区间;若入参为 0 直接 return,不做任何历史扫描。HistorySelect 拉完区间后,按 HistoryDealsTotal 遍历每笔成交。 只处理 _Symbol 匹配的成交:开仓(DEAL_ENTRY_IN)调 AddValue 写进数组,平仓(DEAL_ENTRY_OUT)调 RemoveValue 扣减,同笔单子的 INOUT 则先按 ticket 在 Element 里扫一遍。扫不到再走循环补逻辑,这里每次循环都要用 ArraySize(Element)/3 重算行数,因为 Add/Remove 会让数组实际长度漂移。 外汇与贵金属杠杆高,历史持仓还原仅作仓位透视参考,不预示后续方向,实盘前请在 MT5 策略测试器用真实品种跑一遍确认数组行为。
ArrayResize(Element,class="num">0,class="num">0); if(isClean) date=TimeCurrent(); class=class="str">"cmt">// Do not use the array until there is new open position ArrayPrint(Element); class=class="str">"cmt">// If there are no errors, this function will not be called here: the array with zero size } ListOrdersPositions(date); } class="type">void ListOrdersPositions(class="type">class="kw">datetime dateInicio) { class=class="str">"cmt">//Analyze the history class="type">class="kw">datetime inicio=dateInicio,fim=TimeCurrent(); if(inicio==class="num">0) class="kw">return; HistorySelect(inicio, fim); class="type">class="kw">double deal_price=class="num">0, volume=class="num">0,newVolume; class="type">bool encontrouTicket; class="type">uint tamanhoElement=class="num">0; for(class="type">int j=class="num">0; j<HistoryDealsTotal(); j++) { class="type">ulong ticket = HistoryDealGetTicket(j); if(ticket <= class="num">0) class="kw">return; if(HistoryDealGetString(ticket, DEAL_SYMBOL)==_Symbol) { encontrouTicket=false; newVolume=class="num">0; class=class="str">"cmt">// Need to reset each &class="macro">#x27;for&class="macro">#x27; loop volume=HistoryDealGetDouble(ticket,DEAL_VOLUME); deal_price=HistoryDealGetDouble(ticket,DEAL_PRICE); class="type">class="kw">double auxArray[class="num">1][class="num">3] = {deal_price,volume,(class="type">class="kw">double)ticket}; if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_IN) AddValue(deal_price,volume,(class="type">class="kw">double)ticket); if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT) RemoveValue(deal_price,volume,(class="type">class="kw">double)ticket); if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_INOUT) { tamanhoElement = ArraySize(Element)/class="num">3; class=class="str">"cmt">//Always check the array size, it can vary with the Add/RemoveValue() functions for(class="type">uint i=class="num">0; i<tamanhoElement; i++) if(Element[i][class="num">2]==ticket) { encontrouTicket=true; break; } if(!encontrouTicket) class=class="str">"cmt">// If after the previous scanning we don&class="macro">#x27;t find mentioning of the ticket in the array { for(class="type">uint i=class="num">0; i<tamanhoElement; i++) {