如何分析图表中所选择信号的交易·综合运用
📊

如何分析图表中所选择信号的交易·综合运用

(3/3)· 当信号同时在五个品种上开仓,终端自带的历史视图根本拼不出资金负荷的真实形状

含代码示例实战向 第 3/3 篇
很多人以为在终端里点开信号历史就完成了分析,其实你只是看到了分散的碎片。不同品种上的重合开仓、同步回撤,分开看永远拼不出全貌。把统计、图形和指标打通,才看得清信号到底怎么吃你的保证金。

在面板里塞进统计控件的坐标约定

做 MT5 自定义统计面板时,CStatisticsPanel 的虚函数先把选择器和大图元的位置接口留好:CreateLineSelector、CreateDealsSelector、CreateCheckGroup、CreateGraphic 都吃两组坐标 (x1,y1)-(x2,y2),分别对应控件左上角和右下角像素。最大化最小化用 Maximize()/Minimize() 接管,UpdateChart() 负责把新成交刷到图上。 实际 Create() 里可以看到坐标不是拍脑袋:Total Profit 标签放在 (5,80)-(115,95),取值框 TotalProfitVal 紧挨右边放在 (135,80)-(250,95),Gross Profit 标签下沉到 (5,100)-(115,115)。也就是说每行高度约 20 像素,左边标签宽 110、右边数值宽 115,这种硬编码栅格在 1920×1080 下看着整齐,换 4K 屏就偏。 开 MT5 把这段抄进 EA 的 panel 类,改一下 y1/y2 的步进值,就能验证自己屏幕上的行距。外汇和贵金属波动大,面板只是看数,别把它当信号源。

MQL5 / C++
class="kw">virtual class="type">bool      CreateLineSelector(const class="type">class="kw">string name,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2);
class="kw">virtual class="type">bool      CreateDealsSelector(const class="type">class="kw">string name,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2);
class="kw">virtual class="type">bool      CreateCheckGroup(const class="type">class="kw">string name,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2);
class="kw">virtual class="type">bool      CreateGraphic(const class="type">class="kw">string name,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2);
class=class="str">"cmt">//---
class="kw">virtual class="type">void      Maximize(class="type">void);
class="kw">virtual class="type">void      Minimize(class="type">void);
class=class="str">"cmt">//---
class="kw">virtual class="type">bool      UpdateChart(class="type">void);
};
class="type">bool CStatisticsPanel::Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      class="kw">return false;
class=class="str">"cmt">//---
   if(!TotalProfit.Create(m_chart_id,m_name+"Total Profit",m_subwin,class="num">5,class="num">80,class="num">115,class="num">95))
      class="kw">return false;
   if(!TotalProfit.Text("Total Profit"))
      class="kw">return false;
   if(!Add(TotalProfit))
      class="kw">return false;
class=class="str">"cmt">//---
   if(!TotalProfitVal.Create(m_chart_id,m_name+"Total Profit Value",m_subwin,class="num">135,class="num">80,class="num">250,class="num">95))
      class="kw">return false;
   if(!TotalProfitVal.Text("class="num">0"))
      class="kw">return false;
   if(!Add(TotalProfitVal))
      class="kw">return false;
class=class="str">"cmt">//---
   if(!GrossProfit.Create(m_chart_id,m_name+"Gross Profit",m_subwin,class="num">5,class="num">100,class="num">115,class="num">115))
      class="kw">return false;
   if(!GrossProfit.Text("Gross Profit"))
      class="kw">return false;
   if(!Add(GrossProfit))
      class="kw">return false;
class=class="str">"cmt">//---

「在子窗口铺开盈亏与笔数面板」

想在 MT5 图表子窗口里实时盯总盈利、总亏损、总交易笔数和多空分布,得先把每个文本标签的锚点坐标定死。下面这段初始化把 8 组标签按 y 从 100 到 215 像素逐行排开,左列放名称、右列放数值,右列 x 固定从 135 到 250。 任何一组 Create 或 Text 或 Add 失败就直接 return false,意味着面板要么完整出现、要么整块不挂。外汇与贵金属波动剧烈,这类面板只作概率参考,不构成方向判断。 坐标里有个细节:Gross Profit 标签区高 15 像素(100–115),而 Gross Loss 名称区也是 15 像素(120–135),但 x 宽度左列仅 110 像素(5–115),右列拉到 115 像素(135–250),留白明显偏向数值列,方便后期直接改 Text 不挤版。

MQL5 / C++
  if(!GrossProfitVal.Create(m_chart_id,m_name+"Gross Profit Value",m_subwin,class="num">135,class="num">100,class="num">250,class="num">115))
      class="kw">return false;
  if(!GrossProfitVal.Text("class="num">0"))
      class="kw">return false;
  if(!Add(GrossProfitVal))
      class="kw">return false;
class=class="str">"cmt">//---
  if(!GrossLoss.Create(m_chart_id,m_name+"Gross Loss",m_subwin,class="num">5,class="num">120,class="num">115,class="num">135))
      class="kw">return false;
  if(!GrossLoss.Text("Gross Loss"))
      class="kw">return false;
  if(!Add(GrossLoss))
      class="kw">return false;
class=class="str">"cmt">//---
  if(!GrossLossVal.Create(m_chart_id,m_name+"Gross Loss Value",m_subwin,class="num">135,class="num">120,class="num">250,class="num">135))
      class="kw">return false;
  if(!GrossLossVal.Text("class="num">0"))
      class="kw">return false;
  if(!Add(GrossLossVal))
      class="kw">return false;
class=class="str">"cmt">//---
  if(!TotalTrades.Create(m_chart_id,m_name+"Total Trades",m_subwin,class="num">5,class="num">150,class="num">115,class="num">165))
      class="kw">return false;
  if(!TotalTrades.Text("Total Trades"))
      class="kw">return false;
  if(!Add(TotalTrades))
      class="kw">return false;
class=class="str">"cmt">//---
  if(!TotalTradesVal.Create(m_chart_id,m_name+"Total Trades Value",m_subwin,class="num">135,class="num">150,class="num">250,class="num">165))
      class="kw">return false;
  if(!TotalTradesVal.Text("class="num">0"))
      class="kw">return false;
  if(!Add(TotalTradesVal))
      class="kw">return false;
class=class="str">"cmt">//---
  if(!LongTrades.Create(m_chart_id,m_name+"Long Trades",m_subwin,class="num">5,class="num">170,class="num">115,class="num">185))
      class="kw">return false;
  if(!LongTrades.Text("Long Trades"))
      class="kw">return false;
  if(!Add(LongTrades))
      class="kw">return false;
class=class="str">"cmt">//---
  if(!LongTradesVal.Create(m_chart_id,m_name+"Long Trades Value",m_subwin,class="num">135,class="num">170,class="num">250,class="num">185))
      class="kw">return false;
  if(!LongTradesVal.Text("class="num">0"))
      class="kw">return false;
  if(!Add(LongTradesVal))
      class="kw">return false;
class=class="str">"cmt">//---
  if(!ShortTrades.Create(m_chart_id,m_name+"Short Trades",m_subwin,class="num">5,class="num">190,class="num">115,class="num">215))
      class="kw">return false;
  if(!ShortTrades.Text("Short Trades"))

◍ 面板控件落位与初始区间绑定

这段片段是自定义交易统计面板初始化函数的收尾部分,负责把各个 GUI 控件按坐标塞进子窗口并绑定初始数据。从坐标参数看,子窗口被划分成左半边筛选区(x 最大到 360)和右半边图形区(x 从 370 起),整体布局是固定像素定位,不随图表缩放自适应。 ShortTradesVal 控件建在 (135,190) 到 (250,215) 的区块,初始文本硬编码为 "0",说明空仓状态下卖单价值显示为零;ShowLabel 放在 (285,8) 尺寸 75×20,仅作 "Symbols" 文字标签,不参与交互。 三个选择器函数依次落位:LineSelector 占 (2,30)-(115,70),DealsSelector 占 (135,30)-(250,70),CheckGroup 从 y=30 一直拉到 ClientAreaHeight()-5,高度随子窗变化。日期控件 StartDate、EndDate 分别占 (5,5)-(115,28) 与 (135,5)-(250,28),随后用 Orders.FirstOrder() 和 LastOrder() 直接填进起止值——意味着面板打开时默认拉取账户全部历史订单的时间跨度。 最后 CreateGraphic 把右侧图表区从 x=370 铺到客户端右边界,调用 UpdateChart() 刷新一次便 return true。若其中任一 Create 或 Add 失败则提前 return false,面板不会显示。外汇与贵金属交易高风险,这类面板仅作历史复盘,不代表未来盈亏倾向。

MQL5 / C++
   class="kw">return false;
   if(!Add(ShortTrades))
      class="kw">return false;
class=class="str">"cmt">//---
   if(!ShortTradesVal.Create(m_chart_id,m_name+"Short Trades Value",m_subwin,class="num">135,class="num">190,class="num">250,class="num">215))
      class="kw">return false;
   if(!ShortTradesVal.Text("class="num">0"))
      class="kw">return false;
   if(!Add(ShortTradesVal))
      class="kw">return false;
class=class="str">"cmt">//---
   if(!Orders.Create())
      class="kw">return false;
class=class="str">"cmt">//---
   if(!ShowLabel.Create(m_chart_id,m_name+"Show Selector",m_subwin,class="num">285,class="num">8,class="num">360,class="num">28))
      class="kw">return false;
   if(!ShowLabel.Text("Symbols"))
      class="kw">return false;
   if(!Add(ShowLabel))
      class="kw">return false;
   if(!CreateLineSelector("LineSelector",class="num">2,class="num">30,class="num">115,class="num">70))
      class="kw">return false;
   if(!CreateDealsSelector("DealsSelector",class="num">135,class="num">30,class="num">250,class="num">70))
      class="kw">return false;
   if(!CreateCheckGroup("CheckGroup",class="num">260,class="num">30,class="num">360,ClientAreaHeight()-class="num">5))
      class="kw">return false;
class=class="str">"cmt">//---
   if(!Date.Create(m_chart_id,m_name+"->",m_subwin,class="num">118,class="num">8,class="num">133,class="num">28))
      class="kw">return false;
   if(!Date.Text("->"))
      class="kw">return false;
   if(!Add(Date))
      class="kw">return false;
class=class="str">"cmt">//---
   if(!StartDate.Create(m_chart_id,m_name+"StartDate",m_subwin,class="num">5,class="num">5,class="num">115,class="num">28))
      class="kw">return false;
   if(!Add(StartDate))
      class="kw">return false;
class=class="str">"cmt">//---
   if(!EndDate.Create(m_chart_id,m_name+"EndDate",m_subwin,class="num">135,class="num">5,class="num">250,class="num">28))
      class="kw">return false;
   if(!Add(EndDate))
      class="kw">return false;
class=class="str">"cmt">//---
   StartDate.Value(Orders.FirstOrder());
   EndDate.Value(Orders.LastOrder());
class=class="str">"cmt">//---
   if(!CreateGraphic("Chraphic",class="num">370,class="num">5,ClientAreaWidth()-class="num">5,ClientAreaHeight()-class="num">5))
      class="kw">return false;
class=class="str">"cmt">//---
   UpdateChart();
class=class="str">"cmt">//---
   class="kw">return true;
   }

统计面板的图形与曲线刷新逻辑

CStatisticsPanel 的 CreateGraphic 负责在子窗口按坐标建图。先用 Graphic.Create 以客户端区域左上角为基准偏移 x1/y1/x2/y2,失败直接返回 false;随后按 Orders.Symbols 拿到的品种总数 total 循环,为每个品种加两条默认隐藏的曲线:XXX Balance 与 XXX Equity,颜色由 CColorGenerator 顺序生成。 X 轴被显式设为不自动缩放、类型为 AXIS_TYPE_DATETIME、仅显示 TIME_DATE,历史图例区宽 60、符号字号 20、名称字号 10。这些硬编码值若在你的高清屏上挤成一团,直接改 Graphic.HistoryNameWidth(60) 这几个数即可。 UpdateChart 才是真正喂数据的地方。它先按起止日期和交易方向过滤(deals 取值 -2/0/1 对应全部、买、卖),再对每个勾选品种调 Orders.GetTimeSeries 取 balance/equity/time 及盈亏、多空笔数。曲线索引严格按 i*2 取 Balance、i*2+1 取 Equity,所以 CreateGraphic 里添加顺序不能乱。 BalEquit 两个勾选项分别控制 Balance / Equity 曲线 Visible。若你发现图表只画了权益不画净值,优先查 BalEquit.Check(0) 的返回而不是数据接口——这类面板 bug 八成出在显隐开关而非取数。

MQL5 / C++
class="type">bool CStatisticsPanel::CreateGraphic(const class="type">class="kw">string name,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2)
  {
   if(!Graphic.Create(m_chart_id,m_name+name,m_subwin,ClientAreaLeft()+x1,ClientAreaTop()+y1,ClientAreaLeft()+x2,ClientAreaTop()+y2))
      class="kw">return false;
   class="type">int total=Orders.Symbols(ar_Symbols);
   CColorGenerator ColorGenerator;
   class="type">class="kw">double array[];
   ArrayFree(array);
   for(class="type">int i=class="num">0;i<total;i++)
     {
      class=class="str">"cmt">//---
      CCurve *curve=Graphic.CurveAdd(array,array,ColorGenerator.Next(),CURVE_LINES,ar_Symbols[i]+" Balance");
      curve.Visible(false);
      curve=Graphic.CurveAdd(array,array,ColorGenerator.Next(),CURVE_LINES,ar_Symbols[i]+" Equity");
      curve.Visible(false);
     }
   CAxis *axis=Graphic.XAxis();
   axis.AutoScale(false);
   axis.Type(AXIS_TYPE_DATETIME);
   axis.ValuesDateTimeMode(TIME_DATE);
   Graphic.HistorySymbolSize(class="num">20);
   Graphic.HistoryNameSize(class="num">10);
   Graphic.HistoryNameWidth(class="num">60);
   Graphic.CurvePlotAll();
   Graphic.Update();
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CStatisticsPanel::UpdateChart(class="type">void)
  {
   class="type">class="kw">double balance[];
   class="type">class="kw">double equity[];
   class="type">class="kw">double time[];
   class="type">class="kw">double total_profit=class="num">0, total_loss=class="num">0;
   class="type">int total_long=class="num">0, total_short=class="num">0;
   CCurve *Balance, *Equity;
   class="type">class="kw">datetime start=StartDate.Value();
   class="type">class="kw">datetime end=EndDate.Value();
   class="type">int deals=-class="num">2;
   if(Deals.Check(class="num">0))
       deals=(Deals.Check(class="num">1) ? -class="num">1 : POSITION_TYPE_BUY);
   else
       deals=(Deals.Check(class="num">1) ? POSITION_TYPE_SELL : -class="num">2);
   class="type">int total=ArraySize(ar_Symbols);
   for(class="type">int i=class="num">0;i<total;i++)
     {
      Balance  =  Graphic.CurveGetByIndex(i*class="num">2);
      Equity   =  Graphic.CurveGetByIndex(i*class="num">2+class="num">1);
      class="type">class="kw">double profit,loss;
      class="type">int long_trades, short_trades;
      if(deals>-class="num">2 && Symbols.Check(i) && Orders.GetTimeSeries(ar_Symbols[i],start,end,deals,balance,equity,time,profit,loss,long_trades,short_trades))
        {
         if(BalEquit.Check(class="num">0))
           {
            Balance.Update(time,balance);
            Balance.Visible(true);
           }
         else
           Balance.Visible(false);
         if(BalEquit.Check(class="num">1))
           {
            Equity.Update(time,equity);
            Equity.Visible(true);
           }
         else
           Equity.Visible(false);

「把统计结果刷到面板与坐标轴上」

这段收尾逻辑干两件事:先按统计开关把累计盈亏和买卖次数写进图形对象文本,再把 X 轴范围锁死在选定区间。 轴设置里 DefaultStep 直接取 (end-start)/5,意味着无论你拉多长的时间窗,X 轴都只标 5 个主刻度。做贵金属回测时若区间跨几个月,刻度会稀到看不出波动结构,可手动改分母。 [CODE] total_profit+=profit; total_loss+=loss; total_long+=long_trades; total_short+=short_trades; } else { Balance.Visible(false); Equity.Visible(false); } } CAxis *axis=Graphic.XAxis(); axis.Min((double)start); axis.Max((double)end); axis.DefaultStep((end-start)/5); if(!Graphic.Redraw(true)) return false; Graphic.Update(); if(!TotalProfitVal.Text(DoubleToString(total_profit+total_loss,2))) return false; if(!GrossProfitVal.Text(DoubleToString(total_profit,2))) return false; if(!GrossLossVal.Text(DoubleToString(total_loss,2))) return false; if(!TotalTradesVal.Text(IntegerToString(total_long+total_short))) return false; if(!LongTradesVal.Text(IntegerToString(total_long))) return false; if(!ShortTradesVal.Text(IntegerToString(total_short))) return false; //--- return true; } EVENT_MAP_BEGIN(CStatisticsPanel) ON_EVENT(ON_CHANGE,Symbols,UpdateChart) ON_EVENT(ON_CHANGE,BalEquit,UpdateChart) ON_EVENT(ON_CHANGE,Deals,UpdateChart) ON_EVENT(ON_CHANGE,StartDate,UpdateChart) ON_EVENT(ON_CHANGE,EndDate,UpdateChart) ON_NO_ID_EVENT(1222,UpdateChart) EVENT_MAP_END(CAppDialog) [/CODE] 代码逐行拆解: total_profit+=profit; 把单标的历史利润累加进总盈利变量。 total_loss+=loss; 同理累加总亏损。 total_long+=long_trades; 多单计数累加。 total_short+=short_trades; 空单计数累加。 } 结束统计分支。 else 若统计开关关闭。 { Balance.Visible(false); 隐藏权益曲线。 Equity.Visible(false); 隐藏余额曲线。 } } 两层闭合。 CAxis *axis=Graphic.XAxis(); 取图形 X 轴指针。 axis.Min((double)start); 轴起点设为起始时间。 axis.Max((double)end); 轴终点设为结束时间。 axis.DefaultStep((end-start)/5); 5 等分刻度步长。 if(!Graphic.Redraw(true)) return false; 重绘失败则退出。 Graphic.Update(); 立即刷新。 后续六个 if(!...Val.Text(...)) 分别把总盈亏、总盈利、总亏损、总交易数、多单、空单写进面板文本,精度 2 位小数用 DoubleToString(...,2)return true; 函数成功收尾。 事件映射块把符号、余额权益、成交、起止日期变化和匿名事件 1222 都绑到 UpdateChart,任一变动即重算面板。 外汇与贵金属杠杆高、滑点跳空频繁,面板数字仅反映历史统计,实盘表现可能明显偏离。

MQL5 / C++
   total_profit+=profit;
   total_loss+=loss;
   total_long+=long_trades;
   total_short+=short_trades;
   }
   else
     {
      Balance.Visible(false);
      Equity.Visible(false);
     }
   }
  CAxis *axis=Graphic.XAxis();
  axis.Min((class="type">class="kw">double)start);
  axis.Max((class="type">class="kw">double)end);
  axis.DefaultStep((end-start)/class="num">5);
  if(!Graphic.Redraw(true))
    class="kw">return false;
  Graphic.Update();
  if(!TotalProfitVal.Text(DoubleToString(total_profit+total_loss,class="num">2)))
    class="kw">return false;
  if(!GrossProfitVal.Text(DoubleToString(total_profit,class="num">2)))
    class="kw">return false;
  if(!GrossLossVal.Text(DoubleToString(total_loss,class="num">2)))
    class="kw">return false;
  if(!TotalTradesVal.Text(IntegerToString(total_long+total_short)))
    class="kw">return false;
  if(!LongTradesVal.Text(IntegerToString(total_long)))
    class="kw">return false;
  if(!ShortTradesVal.Text(IntegerToString(total_short)))
    class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
  }
EVENT_MAP_BEGIN(CStatisticsPanel)
  ON_EVENT(ON_CHANGE,Symbols,UpdateChart)
  ON_EVENT(ON_CHANGE,BalEquit,UpdateChart)
  ON_EVENT(ON_CHANGE,Deals,UpdateChart)
  ON_EVENT(ON_CHANGE,StartDate,UpdateChart)
  ON_EVENT(ON_CHANGE,EndDate,UpdateChart)
  ON_NO_ID_EVENT(class="num">1222,UpdateChart)
EVENT_MAP_END(CAppDialog)

◍ 把统计面板封成独立指标

把前面所有信号统计逻辑收进一个指标,最实用的地方在于:它只在子窗口画图,不碰主图 K 线,分析时图表干净。核心都藏在 CStatisticsPanel 类里,指标程序本身只负责把类实例化并接管生命周期。 OnCalculate 直接留空就行,因为面板不靠逐笔报价驱动;事件响应全部转给 OnChartEvent 和 OnDeinit 里的方法调用。挂指标前记得先关掉终端里其它已打开的图表——指标会扫描所有打开图表抓历史,不关掉容易把别的品种交易混进统计,导致信号归因偏差。 下面的 OnInit 片段展示了最小可跑骨架:拿到 chart ID 和子窗口后 new 一个 CStatisticsPanel,任一步 Create / Run 失败就删指标并返回 INIT_FAILED。 // 获取当前图表 ID 与子窗口序号 long chart=ChartID(); int subwin=ChartWindowFind(); // 设指标短名,供 ChartIndicatorDelete 精准移除 IndicatorSetString(INDICATOR_SHORTNAME,"Signal Statistics"); ReloadHistory=false; // 实例化面板类,指针无效则清理并返回失败 Dialog=new CStatisticsPanel; if(CheckPointer(Dialog)==POINTER_INVALID) { ChartIndicatorDelete(chart,subwin,"Signal Statistics"); return INIT_FAILED; } // 在子窗口(0,0)处建 250 像素宽面板 if(!Dialog.Create(chart,"Signal Statistics",subwin,0,0,0,250)) { ChartIndicatorDelete(chart,subwin,"Signal Statistics"); return INIT_FAILED; } if(!Dialog.Run()) { ChartIndicatorDelete(chart,subwin,"Signal Statistics"); return INIT_FAILED; } return(INIT_SUCCEEDED); OnChartEvent 只做一行转发,把 id/lparam/dparam/sparam 塞给 Dialog.ChartEvent;OnDeinit 里先 Dialog.Destroy(reason) 再 delete Dialog,避免句柄泄漏。外汇与贵金属市场波动剧烈、杠杆风险高,用这类面板做历史信号复盘仅辅助判断,不代表未来概率确定。 开 MT5 新建空指标,把上面三段直接贴进对应函数,编译后挂到单一图表,关闭其余窗口再加载信号历史,面板就可能正常收集统计。

MQL5 / C++
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   class="type">long chart=ChartID();
   class="type">int subwin=ChartWindowFind();
   IndicatorSetString(INDICATOR_SHORTNAME,"Signal Statistics");
   ReloadHistory=false;
class=class="str">"cmt">//---
   Dialog=new CStatisticsPanel;
   if(CheckPointer(Dialog)==POINTER_INVALID)
     {
       ChartIndicatorDelete(chart,subwin,"Signal Statistics");
       class="kw">return INIT_FAILED;
     }
   if(!Dialog.Create(chart,"Signal Statistics",subwin,class="num">0,class="num">0,class="num">0,class="num">250))
     {
       ChartIndicatorDelete(chart,subwin,"Signal Statistics");
       class="kw">return INIT_FAILED;
     }
   if(!Dialog.Run())
     {
       ChartIndicatorDelete(chart,subwin,"Signal Statistics");
       class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
   }
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">//---
   Dialog.ChartEvent(id,lparam,dparam,sparam);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   Dialog.Destroy(reason);
   class="kw">delete Dialog;
   }

把标记信息用起来

指标在图上打出买卖标记只是第一步,真正有价值的是拿这些标记去反推策略的适用边界。把历史标记按品种分类统计,就能看出哪几个品种上信号连续失效,后续在这类品种上直接停跑原策略,比硬扛回撤更划算。 这类标记系统还能接两层扩展:一是写脚本自动扫描多个品种,挑出标记胜率明显占优的信号组合,相当于让程序替你做信号初筛;二是把展示层换成图形面板,支持透明度调节和背景色自定义,代码上从 CAppDialog 或 CWndClient 继承即可获得更灵活的容器。 外汇与贵金属杠杆高、滑点跳空频繁,标记统计的结论只代表历史样本概率,换周期或换经纪商环境后分布可能漂移,实盘前务必在 MT5 策略测试器里按品种逐个回测确认。

「收束」

这套信号分析工具由四个文件构成:Order.mqh 与 OrdersCollection.mqh 负责把每笔交易和整体持仓抽象成类,StatisticsPanel.mqh 画图形面板,SignalStatistics.mq5 则是挂在图表上跑统计的指标。 原文附带的 MQL5.zip 约 470.5 KB,里面是上述完整源码,直接丢进 MT5 的 MQL5/Include 与 Indicators 目录就能编译验证。 需注意,该方案依赖净头寸核算的 MT5 信号,对于对冲账户的历史交易,策略测试器可能读不到汇总头寸,验证前先确认信号类型。外汇与贵金属波动剧烈,用这类工具回溯不等于未来收益,仅作概率参考。

让小布替你跑这套
这些多品种信号历史的对齐与负荷诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到同步开仓与回撤热力,你只管判断策略是否合拍。

常见问题

终端按品种分页展示,时间重合的仓位被拆在多个窗口,人工翻页难以识别同时开仓数与资金峰值,概率上容易低估集中风险。
基类默认比较逻辑不满足按成交时间升序排列订单的需求,重写后排序方法才能把跨品种订单串成连续时间线供后续统计。
可以,小布盯盘的品种页已集成多品种信号历史对齐与回撤标记,省去自己写类与图形外壳的重复劳动,外汇贵金属波动剧烈仍须自判风险。
优先暴露同时开仓数、各品种保证金占用比与最大回撤时刻,这三项能直接反映信号在极端行情下的资金断裂概率。
本篇用保存订单信息的类承接前文目标,把收集、图形外壳与分析指标连起来,关于自动初筛标准的完整讨论见《如何分析图表中所选择信号的交易·基础篇》。