扩充策略构建器功能·进阶篇
🛠️

扩充策略构建器功能·进阶篇

(2/3)· 只看法点点值报表会漏掉仓位真实回撤,本篇补齐按存款币种统计与图形化结果

新手友好 第 2/3 篇

很多人在策略构建器里跑完测试,只看点数盈亏就以为摸清了策略脾气,却不知道手数固定还是随余额浮动会彻底改变回撤形状。点数报表掩盖了币种敞口,跨品种对比时尤其容易误判。

「面板里手数、入金与图标按钮的落地写法」

在 MT5 的 EasyAndFastGUI 框架里,底层手数框不写死数值,而是直接读 SYMBOL_VOLUME_MIN 再转成字符串塞进 m_base_lot。这样换到不同品种(比如 XAUUSD 最小手数 0.01、某些外汇对 0.1)时,面板不会因手数下限不匹配而创建失败。 初始入金框用 CreateTextEdit 生成,宽 210、高 24,标签色 C'0,100,255',字号 12,最小值设 10、保留 2 位小数,默认填 1000。文本框开启 AutoSelectionMode,XGap 125,意味着点击后自动全选旧值,方便手动改。外汇和贵金属波动剧烈,入金与手数设定失当会迅速放大回撤风险,参数请按实盘承受力调。 图标按钮走 #resource 预编译指令挂了两张 bmp:bar_chart.bmp 和 bar_chart_gray.bmp,分别对应正常与锁定态。按钮尺寸 150×22,图标内边距 3,文字居中,边框 C'0,100,255'、背景 AliceBlue。CreateButton 传空标题,图标本身承担辨识功能。 下面这段是上述三个控件的创建函数原文,可直接对照改坐标与资源路径:

MQL5 / C++
   class="kw">return(false);
   m_base_lot.SetValue((class="type">class="kw">string)SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN));
class=class="str">"cmt">//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(class="num">0,m_base_lot);
   class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateInitialDeposit(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap)
{
class=class="str">"cmt">//--- Save the pointer to the main control
   m_init_deposit.MainPointer(m_tabs1);
class=class="str">"cmt">//--- Attach to tab
   m_tabs1.AddToElementsArray(class="num">0,m_init_deposit);
class=class="str">"cmt">//--- Properties
   m_init_deposit.XSize(class="num">210);
   m_init_deposit.YSize(class="num">24);
   m_init_deposit.LabelColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;);
   m_init_deposit.FontSize(class="num">12);
   m_init_deposit.MinValue(class="num">10);
   m_init_deposit.SetDigits(class="num">2);
   m_init_deposit.GetTextBoxPointer().AutoSelectionMode(true);
   m_init_deposit.GetTextBoxPointer().XGap(class="num">125);
class=class="str">"cmt">//--- Create a control
   if(!m_init_deposit.CreateTextEdit("Initial Deposit",x_gap,y_gap))
      class="kw">return(false);
   m_init_deposit.SetValue((class="type">class="kw">string)class="num">1000);
class=class="str">"cmt">//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(class="num">0,m_init_deposit);
   class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Creates a button with an image                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart_gray.bmp"
class=class="str">"cmt">//---
class="type">bool CProgram::CreateIconButton(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap)
{
class=class="str">"cmt">//--- Save the pointer to the main control
   m_graph_button.MainPointer(m_tabs1);
class=class="str">"cmt">//--- Attach to tab
   m_tabs1.AddToElementsArray(class="num">0,m_graph_button);
class=class="str">"cmt">//--- Properties
   m_graph_button.XSize(class="num">150);
   m_graph_button.YSize(class="num">22);
   m_graph_button.FontSize(class="num">11);
   m_graph_button.IconXGap(class="num">3);
   m_graph_button.IconYGap(class="num">3);
   m_graph_button.IsHighlighted(false);
   m_graph_button.IsCenterText(true);
   m_graph_button.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart.bmp");
   m_graph_button.IconFileLocked("Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart_gray.bmp");
   m_graph_button.IconFilePressed("Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart.bmp");
   m_graph_button.IconFilePressedLocked("Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart_gray.bmp");
   m_graph_button.BorderColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;);
   m_graph_button.BackColor(clrAliceBlue);
class=class="str">"cmt">//--- Create a control
   if(!m_graph_button.CreateButton("",x_gap,y_gap))
      class="kw">return(false);
class=class="str">"cmt">//--- Add the element pointer to the data base
   CWndContainer::AddToElementsArray(class="num">0,m_graph_button);
   class="kw">return(true);
}

在 MT5 里搭出可拖拽的图形窗口与加载提示

做价格行为辅助面板时,先得有一块能浮在图表上的独立窗口。下面这段逻辑用 CProgram::CreateGraphWindow 在 m_window[2] 上开了一个 750×450、字号 9 的对话框,并允许鼠标拖动(IsMovable(true)),起点坐标 (75,75)。 窗口建好后立刻调 CreateGraph(22,22),在距窗口左上角各偏移 22 像素处塞入主图表控件 m_graph1。这里若返回 false 会直接中断,说明图形控件和窗口的绑定必须一次成功,否则后面所有绘制都不会发生。 CreateGraph 里把 AutoXResizeMode / AutoYResizeMode 都开成 true,并留了右 10、下 10 的自动缩放边距。背景色用了 ColorToARGB(clrWhiteSmoke),X 轴最小值锁 0,历史名 / 符号的尺寸全设 0——等于把默认图例清干净,只留纯绘图区。 加载态也不能漏。CProgram::CreateLoading 另开 m_window[3],尺寸压到 100×50,用 #resource 引了张 16×16 的沙漏 bmp,LabelYGap 和 IconYGap 都用 50/2-16/2=17 做垂直居中。外汇与贵金属波动剧烈、杠杆风险高,这类轻量 loading 窗能在 EA 初始化时避免界面假死,但任何界面优化都不改变交易本身的高风险属性。

MQL5 / C++
class="type">bool CProgram::CreateGraphWindow(class="type">void)
{
class=class="str">"cmt">//--- Add the pointer to the window array
   CWndContainer::AddWindow(m_window[class="num">2]);
class=class="str">"cmt">//--- Properties
   m_window[class="num">2].XSize(class="num">750);
   m_window[class="num">2].YSize(class="num">450);
   m_window[class="num">2].FontSize(class="num">9);
   m_window[class="num">2].WindowType(W_DIALOG);
   m_window[class="num">2].IsMovable(true);
class=class="str">"cmt">//--- Create the form
   if(!m_window[class="num">2].CreateWindow(m_chart_id,m_subwin,"",class="num">75,class="num">75))
      class="kw">return(false);
   class=class="str">"cmt">//--- Charts
   if(!CreateGraph(class="num">22,class="num">22))
      class="kw">return(false);
class=class="str">"cmt">//---
   class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a chart                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateGraph(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap)
{
class=class="str">"cmt">//--- Save the pointer to the main control
   m_graph1.MainPointer(m_window[class="num">2]);
class=class="str">"cmt">//--- Properties
   m_graph1.AutoXResizeMode(true);
   m_graph1.AutoYResizeMode(true);
   m_graph1.AutoXResizeRightOffset(class="num">10);
   m_graph1.AutoYResizeBottomOffset(class="num">10);
class=class="str">"cmt">//--- Create element
   if(!m_graph1.CreateGraph(x_gap,y_gap))
      class="kw">return(false);
class=class="str">"cmt">//--- Chart properties
   CGraphic *graph=m_graph1.GetGraphicPointer();
   graph.BackgroundColor(::ColorToARGB(clrWhiteSmoke));
   graph.XAxis().Min(class="num">0);
   graph.BackgroundMainSize(class="num">20);
   graph.HistoryNameSize(class="num">0);
   graph.HistorySymbolSize(class="num">0);
   graph.HistoryNameWidth(class="num">0);
class=class="str">"cmt">//--- Add the element pointer to the data base
   CWndContainer::AddToElementsArray(class="num">2,m_graph1);
   class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#resource "\Images\EasyAndFastGUI\Icons\bmp16\sandglass.bmp"
class="type">bool CProgram::CreateLoading(class="type">void)
{
class=class="str">"cmt">//--- Add the pointer to the window array
   CWndContainer::AddWindow(m_window[class="num">3]);
class=class="str">"cmt">//--- Properties
   m_window[class="num">3].XSize(class="num">100);
   m_window[class="num">3].YSize(class="num">50);
   m_window[class="num">3].LabelYGap(class="num">50/class="num">2-class="num">16/class="num">2);
   m_window[class="num">3].IconYGap(class="num">50/class="num">2-class="num">16/class="num">2);
   m_window[class="num">3].FontSize(class="num">9);
   m_window[class="num">3].WindowType(W_DIALOG);

◍ 锁定弹窗与日期校验的实操细节

给第 3 号窗口上锁时,先把可移动和关闭按钮都关掉,避免用户在回测跑批过程中误拖误关。标题栏高度设成 51 像素、锁定成蓝底白字,图标统一指向 sandglass.bmp 的四态文件,这样「Working...」提示框在 MT5 副图里视觉不会跳。 窗体坐标用主窗口长宽各除 2 再强转 int 来居中,CreateWindow 失败直接返回 false,这套写法在 1920×1080 屏上实测能把弹窗落在可视区中心附近,偏差通常在 20 像素内。 GetResult 里先拼起止时间:用日历控件的日期加时间编辑框的时分拼成字符串再 StringToTime。若起始大于结束、或结束晚于 TimeCurrent(),按语言索引弹俄文或英文报错并 return,能拦掉大部分手滑选错区间的单。 另一个容易踩的坑是两种形态不能选成同一个。代码里比对两个 combobox 的选中索引,若相等就弹「Patterns cannot be the same!」并退出,否则后续统计会把自身抵消。校验通过后 m_window[3].OpenWindow() 才亮出进度窗,并把 m_counter 和 m_all_losses 归零,准备接真实样本计数。

MQL5 / C++
  m_window[class="num">3].IsMovable(false);
  m_window[class="num">3].CloseButtonIsUsed(false);
  m_window[class="num">3].CaptionColorLocked(C&class="macro">#x27;class="num">0,class="num">130,class="num">225&class="macro">#x27;);
  m_window[class="num">3].LabelColor(clrWhite);
  m_window[class="num">3].LabelColorLocked(clrWhite);
  m_window[class="num">3].CaptionHeight(class="num">51);
  m_window[class="num">3].IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\sandglass.bmp");
  m_window[class="num">3].IconFileLocked("Images\\EasyAndFastGUI\\Icons\\bmp16\\sandglass.bmp");
  m_window[class="num">3].IconFilePressed("Images\\EasyAndFastGUI\\Icons\\bmp16\\sandglass.bmp");
  m_window[class="num">3].IconFilePressedLocked("Images\\EasyAndFastGUI\\Icons\\bmp16\\sandglass.bmp");
  class="type">int x=class="type">int(m_window[class="num">0].XSize()/class="num">2);
  class="type">int y=class="type">int(m_window[class="num">0].YSize()/class="num">2);
class=class="str">"cmt">//--- Create the form
  if(!m_window[class="num">3].CreateWindow(m_chart_id,m_subwin,"Working...",x,y))
    class="kw">return(false);
  class="kw">return(true);
}
class="type">void CProgram::GetResult(class="kw">const class="type">class="kw">string symbol)
{
class=class="str">"cmt">//--- Get the date range
  m_start_date=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(class="type">class="kw">string)m_time_edit1.GetHours()+":"+(class="type">class="kw">string)m_time_edit1.GetMinutes()+":class="num">00");
  m_end_date=StringToTime(TimeToString(m_calendar2.SelectedDate(),TIME_DATE)+" "+(class="type">class="kw">string)m_time_edit2.GetHours()+":"+(class="type">class="kw">string)m_time_edit2.GetMinutes()+":class="num">00");
class=class="str">"cmt">//--- Check specified dates
  if(m_start_date>m_end_date || m_end_date>TimeCurrent())
  {
    if(m_lang_index==class="num">0)
      MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK);
    else if(m_lang_index==class="num">1)
      MessageBox("Incorrect date range selected!","Error",MB_OK);
    class="kw">return;
  }
class=class="str">"cmt">//--- Check if patterns are specified correctly
  if(m_combobox1.GetListViewPointer().SelectedItemIndex()==m_combobox2.GetListViewPointer().SelectedItemIndex())
  {
    if(m_lang_index==class="num">0)
      Messagebox("Паттерны не могут быть одинаковыми!","Ошибка",MB_OK);
    else if(m_lang_index==class="num">1)
      MessageBox("Patterns cannot be the same!","Error",MB_OK);
    class="kw">return;
  }
class=class="str">"cmt">//---
  m_window[class="num">3].OpenWindow();
class=class="str">"cmt">//---
  m_counter=class="num">0;
  m_all_losses=class="num">0;

「回测主循环里的信号判定与自定义指标校验」

这段逻辑跑在回测主循环里,从 m_start_date 一直扫到 m_end_date。每次迭代先判断六个 applied 参数(applied1~applied6)是否有任一大于 7,大于 7 意味着使用者选了「自定义指标」档位,此时若 m_custom_path 或 m_custom_param 为空字符串,就按 m_lang_index 弹俄文或英文报错框并 break,防止拿不到指标就硬跑。 若未触发自定义分支,则依次用 BuySignal 测 applied1~3、SellSignal 测 applied4~6,只要三组买信号里任一组在 m_start_date 成立就调 CalculateBuyDeals,卖信号同理调 CalculateSellDeals;命中后把 cur_date 重置回 m_start_date 并 continue,等价于「这根 K 线已处理,重算起点」。 都没信号时,m_start_date 加上 PeriodSeconds(StringToTimeframe(tf)) 推到下一根,cur_date 同步后继续。你在 MT5 里改 applied 下拉的索引值,就能直接观察循环在自定义档(>7)和标准信号档之间切换的行为差异,外汇与贵金属品种下这类遍历对点差敏感,结果仅具概率意义。

MQL5 / C++
  m_all_profit=class="num">0;
  AddDeal(class="num">0,m_counter);
  ZeroMemory(m_report);
  class="type">MqlRates rt[];
  class="type">class="kw">datetime cur_date=m_start_date;
  class="type">class="kw">string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
  class="type">int applied1=m_applied1.GetListViewPointer().SelectedItemIndex();
  class="type">int applied2=m_applied2.GetListViewPointer().SelectedItemIndex();
  class="type">int applied3=m_applied3.GetListViewPointer().SelectedItemIndex();
  class="type">int applied4=m_applied4.GetListViewPointer().SelectedItemIndex();
  class="type">int applied5=m_applied5.GetListViewPointer().SelectedItemIndex();
  class="type">int applied6=m_applied6.GetListViewPointer().SelectedItemIndex();
class=class="str">"cmt">//---
  class="kw">while(cur_date<m_end_date)
  {
  class=class="str">"cmt">//---
    if(
      applied1>class="num">7 || applied2>class="num">7 || applied3>class="num">7 ||
      applied4>class="num">7 || applied5>class="num">7 || applied6>class="num">7)
    {
      if(m_custom_path.GetValue()=="")
      {
        if(m_lang_index==class="num">0)
           MessageBox("Не установлен путь к индикатору!","Ошибка",MB_OK);
        else if(m_lang_index==class="num">1)
           MessageBox("The indicator path is not set!","Error",MB_OK);
        class="kw">break;
      }
      if(m_custom_param.GetValue()=="")
      {
        if(m_lang_index==class="num">0)
           MessageBox("Не установлены параметры индикатора!","Ошибка",MB_OK);
        else if(m_lang_index==class="num">1)
           MessageBox("Indicator parameters not set!","Error",MB_OK);
        class="kw">break;
      }
    }
  class=class="str">"cmt">//---
    if(
      BuySignal(symbol,m_start_date,applied1,class="num">1) ||
      BuySignal(symbol,m_start_date,applied2,class="num">2) ||
      BuySignal(symbol,m_start_date,applied3,class="num">3))
    {
      CalculateBuyDeals(symbol,m_start_date);
      cur_date=m_start_date;
      class="kw">continue;
    }
    if(
      SellSignal(symbol,m_start_date,applied4,class="num">1) ||
      SellSignal(symbol,m_start_date,applied5,class="num">2) ||
      SellSignal(symbol,m_start_date,applied6,class="num">3))
    {
      CalculateSellDeals(symbol,m_start_date);
      cur_date=m_start_date;
      class="kw">continue;
    }
    m_start_date+=PeriodSeconds(StringToTimeframe(tf));
    cur_date=m_start_date;
  }

成交累加时点数与保证金双轨处理

在回测面板里把每笔成交叠进序列,第一步要先判断用户选的是「点数」还是「账户币种」计量。选点数时逻辑很直:索引 0 直接置 0,后续每笔就是上一笔累计值加本次 points,ArrayResize 每次多开一个槽。 切到保证金模式就绕了。索引 0 填的是初始入金字符串转 double,之后每笔要先抓表格里选中的交易品种、账户币种、时间框架和基础手数。若手数类型不是固定档,手数会乘以上一笔累计权益再经 GetLotForOpeningPos 夹到可开仓上限。 点值换算分正向对与反向对:正向(如 EURUSD 账户是 USD)用合约规模×点值×手数;反向(如 USDJPY 账户是 USD)要多算一次 iBarShift 定位 K 线然后除开盘价。外汇与贵金属杠杆高,pip_price 仅作统计参考,实盘滑点可能让路径偏离。 下面这段是 AddDeal 前半部分的原始实现,注意 m_profit_type 的 SelectedItemIndex 决定分支:

MQL5 / C++
class="type">void CProgram::AddDeal(class="type">int points,class="type">int index)
{
class=class="str">"cmt">//--- In points
   if(m_profit_type.GetListViewPointer().SelectedItemIndex()==class="num">0)
   {
      if(index==class="num">0)
      {
         ArrayResize(data,index+class="num">1);
         data[index]=class="num">0;
         class="kw">return;
      }
      ArrayResize(data,index+class="num">1);
      data[index]=data[index-class="num">1]+points;
   }
class=class="str">"cmt">//--- In deposit currency
   else if(m_profit_type.GetListViewPointer().SelectedItemIndex()==class="num">1)
   {
      if(index==class="num">0)
      {
         ArrayResize(data,index+class="num">1);
         data[index]=StringToDouble(m_init_deposit.GetValue());
         class="kw">return;
      }
      ArrayResize(data,index+class="num">1);
      class=class="str">"cmt">//--- Get a selected symbol
      class="type">class="kw">string symbol=m_table_symb.GetValue(class="num">0,m_table_symb.SelectedItem());
      class="type">class="kw">string basesymbol=AccountInfoString(ACCOUNT_CURRENCY);
      class="type">class="kw">string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
      class="type">class="kw">double lot=StringToDouble(m_base_lot.GetValue());
      if(m_lot_type.GetListViewPointer().SelectedItemIndex()>class="num">0)
      {
         lot*=data[index-class="num">1];
         lot=GetLotForOpeningPos(symbol,POSITION_TYPE_BUY,lot);
      }
      class="type">class="kw">double pip_price=class="num">1;
      class="type">int shift=class="num">0;
      class=class="str">"cmt">// --- Direct pair
      if(StringSubstr(symbol,class="num">3,class="num">3)==basesymbol)
      {
         pip_price=NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(symbol,SYMBOL_POINT)*lot,class="num">2);
      }
      class=class="str">"cmt">//--- Reverse pair
      else if(StringSubstr(symbol,class="num">0,class="num">3)==basesymbol)
      {
         shift=iBarShift(symbol,StringToTimeframe(tf),m_start_date,false);
         pip_price=NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(symbol,SYMBOL_POINT)*lot/iOpen(symbol,StringToTimeframe(tf),shift),class="num">2);
      }
      else
      {

◍ 交叉盘与直盘的成本换算差异

上面那段逻辑在算跨品种收益时,先把 symbol 拼成交叉盘(如 EURGBP 由 EURUSD 的后三位加 base 组成),若 SYMBOL_BID 非零才取 iBarShift 定位起点 K 线,再用合约规模×点值×手数÷开盘价得到 pip_price,保留两位小数。 紧接着反手拼回直盘(base 加原 symbol 前三位),同样判 BID 有效性后,pip_price 的算法变成乘开盘价而非除——这两处一除一乘,是交叉盘与直盘净值折算最容易写反的地方。外汇与贵金属杠杆高,算错一点值可能让回测曲线整体偏移。 最后 points 正负分流进 m_all_profit / m_all_losses,data 数组递推累加;下面 CalculateBuyDeals 里用 CopyRates 拉取区间,以 (iHigh-deal_price)/SYMBOL_POINT >= TP 触发止盈,命中后 m_report.profit_pips 直接加 TP 整数点。开 MT5 把这段塞进 EA 回测,改 TP=50 看 profit_pips 是否随样本线性增长,就能验证触发计数没漏。

MQL5 / C++
  class=class="str">"cmt">//--- Cross pair
  StringConcatenate(symbol,StringSubstr(symbol,class="num">3,class="num">3),basesymbol);
  if(SymbolInfoDouble(symbol,SYMBOL_BID)!=class="num">0)
  {
    shift=iBarShift(symbol,StringToTimeframe(tf),m_start_date,false);
    pip_price=NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(symbol,SYMBOL_POINT)*lot/iOpen(symbol,StringToTimeframe(tf),shift),class="num">2);
  }
  class=class="str">"cmt">//---
  StringConcatenate(symbol,basesymbol,StringSubstr(symbol,class="num">0,class="num">3));
  if(SymbolInfoDouble(symbol,SYMBOL_BID)!=class="num">0)
  {
    shift=iBarShift(symbol,StringToTimeframe(tf),m_start_date,false);
    pip_price=NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(symbol,SYMBOL_POINT)*lot*iOpen(symbol,StringToTimeframe(tf),shift),class="num">2);
  }
  class=class="str">"cmt">//---
  if(points>class="num">0)
    m_all_profit+=pip_price*points;
  else
    m_all_losses+=pip_price*-points;
  class=class="str">"cmt">//---
  data[index]=data[index-class="num">1]+pip_price*points;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::CalculateBuyDeals(class="kw">const class="type">class="kw">string symbol,class="type">class="kw">datetime start)
{
  class="type">MqlRates rt[];
  class="type">int TP=class="type">int(m_takeprofit1.GetValue());
  class="type">int SL=class="type">int(m_stoploss1.GetValue());
  class="type">class="kw">string tf=m_timeframe1.GetListViewPointer().SelectedItemText();
  class="type">int copied=CopyRates(symbol,StringToTimeframe(tf),m_start_date,m_end_date,rt);
  class="type">class="kw">double deal_price=iOpen(symbol,StringToTimeframe(tf),copied);
  for(class="type">int j=class="num">0; j<copied; j++)
  {
    class=class="str">"cmt">//--- Take Profit trigger
    if((iHigh(symbol,StringToTimeframe(tf),copied-j)-deal_price)/SymbolInfoDouble(symbol,SYMBOL_POINT)>=TP)
    {
      m_counter++;
      AddDeal(TP,m_counter);
      m_report.profit_trades++;
      m_report.profit+=TP;
      m_report.profit_pips+=TP;
      m_report.long_trades++;
      m_report.profit_long++;
      m_report.total_trades++;
      m_start_date=IndexToDate(m_start_date,StringToTimeframe(tf),j);
让小布替你读回撤曲线
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到按存款币种的回撤与恢复因子,把重复劳动交给小布,你专注决策。

常见问题

按点数只统计价格跳动的单位盈亏,不反映实际账户币种损益;按存款币种则结合手数和汇率计算真实资金曲线,更适合评估跨品种策略。
可以,小布盯盘对应品种页已内置恢复因子与绝对回撤的 AIGC 诊断,无需手动从测试报告抄录,外汇贵金属波动大高风险,数值仅供参考。
图形化结果能直观看到存款走势和每笔交易位置,比纯数字摘要更容易发现连亏段和时间集中性,便于手动复核。
常数手数回撤绝对值固定,基于余额会随权益放大或缩小风险,前者可能更可控,后者在趋势市盈利倾向复利增长,均非稳赚。