如何分析图表中所选择信号的交易·综合运用
(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 的步进值,就能验证自己屏幕上的行距。外汇和贵金属波动大,面板只是看数,别把它当信号源。
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 不挤版。
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,面板不会显示。外汇与贵金属交易高风险,这类面板仅作历史复盘,不代表未来盈亏倾向。
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 八成出在显隐开关而非取数。
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,任一变动即重算面板。
外汇与贵金属杠杆高、滑点跳空频繁,面板数字仅反映历史统计,实盘表现可能明显偏离。
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 新建空指标,把上面三段直接贴进对应函数,编译后挂到单一图表,关闭其余窗口再加载信号历史,面板就可能正常收集统计。
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 信号,对于对冲账户的历史交易,策略测试器可能读不到汇总头寸,验证前先确认信号类型。外汇与贵金属波动剧烈,用这类工具回溯不等于未来收益,仅作概率参考。