可视化交易图表(第二部分):数据图形化展示·进阶篇
📘

可视化交易图表(第二部分):数据图形化展示·进阶篇

第 2/3 篇

开平仓分桶落数组的写法

这段逻辑把每一笔成交按入场和出场拆进不同的数组尾部,靠 ArraySize()-1 定位当前末尾下标,避免手动维护计数器。 入场分支(DEAL_ENTRY_OUT 之外)写入 magic、comment、externalID、volume、commission 等字段;出场分支则补 time_close、close、reason、swap、profit、fee。 [CODE] 里能看到:每次赋值都用 ArraySize(arr_xxx)-1,意味着数组在之前已被动态扩容过(例如 ArrayResize 或动态数组自动增长),否则越界会直接报错。 你在 MT5 里接这段时,先确认那些 arr_ 开头数组已用 ArrayResize 预扩到成交数规模,否则第 1 笔就崩。外汇和贵金属杠杆高,回测和实盘成交结构可能有差异,数组越界类 bug 会静默丢数据。

MQL5 / C++
   arr_magic[    ArraySize(arr_magic)-class="num">1]     = magic;     class=class="str">"cmt">// Magic
   arr_comment[  ArraySize(arr_comment)-class="num">1]  = comment;   class=class="str">"cmt">// comment
   arr_extermalID[ ArraySize(arr_extermalID)-class="num">1] = externalID; class=class="str">"cmt">// external id
   arr_volume[   ArraySize(arr_volume)-class="num">1]    = volume;    class=class="str">"cmt">// volume
   arr_commission[ ArraySize(arr_commission)-class="num">1] = commission; class=class="str">"cmt">// commission
   }
   if(entry == DEAL_ENTRY_OUT)                          class=class="str">"cmt">// if this is an exit
   {
   arr_time_close[ ArraySize(arr_time_close)-class="num">1] = time_close; class=class="str">"cmt">// close time
   arr_close[     ArraySize(arr_close)-class="num">1]      = close;     class=class="str">"cmt">// close prices
   class=class="str">"cmt">//---
   arr_reason[    ArraySize(arr_reason)-class="num">1]     = reason;    class=class="str">"cmt">// reason
   arr_swap[      ArraySize(arr_swap)-class="num">1]       = swap;      class=class="str">"cmt">// swap
   arr_profit[    ArraySize(arr_profit)-class="num">1]     = profit;    class=class="str">"cmt">// profit
   arr_fee[       ArraySize(arr_fee)-class="num">1]        = fee;       class=class="str">"cmt">// fee
   }
  }
  else
  {
   class="type">int index = Find(PositionID,position_id);   class=class="str">"cmt">// if there was a record already,

◍ 开平仓字段分别落数组

在 MT5 的成交回放逻辑里,区分 DEAL_ENTRY_IN 与 DEAL_ENTRY_OUT 是拆分交易生命周期的第一步。进场分支负责把开仓相关的上下文写进数组,出场分支只记录平仓时间与价格等收尾字段,两者不混写。 下面这段判断直接对应上述分工:进场时把 time_open、symbol、stop_loss、take_profit、open 等 11 个字段按 index 落位;出场时仅写入 time_close、close、reason。注意 arr_stop_loss 与 arr_take_profit 只在 IN 分支赋值,回测里若想统计盈亏比,缺了出场分支的补充就只能用开仓时的静态止损止盈。 实盘验证时建议把 externalID 和 magic 也打出来,MT5 终端里多 EA 混跑时,靠 magic 过滤才能避免把别的策略平仓误认成当前持仓退出。外汇与贵金属杠杆高,数组里 commission 字段务必留痕,点差加佣金会直接吃掉小周期策略的期望。

MQL5 / C++
if(entry == DEAL_ENTRY_IN)              class=class="str">"cmt">// if this was an entry
      {
      arr_time_open[index]    = time_open;          class=class="str">"cmt">// deal time
      arr_symbol[index]        = symbol;             class=class="str">"cmt">// symbol
      arr_stop_loss[index]     = stop_loss;          class=class="str">"cmt">// deal stop loss
      arr_take_profit[index]   = take_profit;        class=class="str">"cmt">// deal take profit
      arr_open[index]          = open;               class=class="str">"cmt">// open price
      class=class="str">"cmt">//---
      arr_magic[index]         = magic;              class=class="str">"cmt">// Magic
      arr_comment[index]       = comment;            class=class="str">"cmt">// comment
      arr_extermalID[index]    = externalID;         class=class="str">"cmt">// external id
      arr_volume[index]        = volume;             class=class="str">"cmt">// volume
      arr_commission[index]    = commission;         class=class="str">"cmt">// commission
      }
      if(entry == DEAL_ENTRY_OUT)                    class=class="str">"cmt">// if this is an exit
      {
      arr_time_close[index]   = time_close;          class=class="str">"cmt">// deal close time
      arr_close[index]        = close;               class=class="str">"cmt">// deal close price
      class=class="str">"cmt">//---
      arr_reason[index]       = reason;              class=class="str">"cmt">// reason
      }

「持仓成本的三项数组落库」

这段片段承接前面的历史成交遍历,把单笔持仓的掉期、浮动盈亏与手续费分别写入三个平行数组。索引对齐后,后续统计函数可以直接按位读取,不必反复回调账户接口。 arr_swap[index] = swap; // 掉期成本 arr_profit[index] = profit; // 浮动盈亏 arr_fee[index] = fee; // 手续费 三行赋值就是成本拆解的落库动作,index 通常来自前面 HistoryDealGetTicket 的计数,掉期和手续费在贵金属隔夜仓上往往比外汇点值更刺眼,需留意 MT5 点差外的隐性支出。 若 HistorySelect 返回失败,代码走 else 分支:用 printf 把函数名、行号与 GetLastError 打到日志,再弹 MessageBox 提示“按 ticket 未找到成交”并直接 return。实盘跑脚本时,这种显式报错能省掉一半瞎猜时间,但外汇与贵金属杠杆品种本身高风险,脚本只帮你看清账目,不替你判断方向。

MQL5 / C++
arr_swap[index]    = swap;          class=class="str">"cmt">// swap
arr_profit[index]   = profit;         class=class="str">"cmt">// profit
arr_fee[index]      = fee;            class=class="str">"cmt">// fee
     }
    }
   }
  }
 }
 else
  {
   printf("%s - %d -> Error of selecting history deals: %d",__FUNCTION__,__LINE__,GetLastError()); class=class="str">"cmt">// informed in the journal
   printf("%s - %d -> Deal was not found.",__FUNCTION__,__LINE__);                                 class=class="str">"cmt">// informed in the journal
   MessageBox("Deal was not found with this tiket: "+IntegerToString(inp_d_ticket)+". Script is done."); class=class="str">"cmt">// informed in the message
   class="kw">return;
  }
 break;

把历史成交画进图表并落盘截图

想在 MT5 里把某笔历史成交完整留痕,第一步不是画图,而是先在程序层打开对应品种的新图表窗口,再把右侧缩进调开,让整段交易清晰入镜,最后调一个预定义函数把图存到指定目录。 品种有效性是绕不开的坑。同一家经纪商可能给标准、黄金、铂金、ESN 等账户配不同后缀:标准账户上的 EURUSD,换到别的账户类型可能叫 EURUSDb、EURUSDz 或 EURUSD_i。若直接 ChartOpen 一个历史里存在但当前市场报价没订阅的品种,终端会报“不存在的交易品种”。用 SymbolSelect(name, true) 做检查,第二个参数传 true 能在品种有效但未订阅时自动勾选到市场报价。 图表真正打开后,先 ChartApplyTemplate(handle, "dailyHistorytemp") 套用模板——注意脚本里写模板名不带 .tpl 后缀,文件得提前丢进 MQL5\Profiles\Templates。套完必须 Sleep(2000),这是实踩出来的 2 秒延迟,专治历史数据还没拉满就截图导致的空白屏;机器慢或网速差可自己改大。 接着 ChartSetInteger(handle, CHART_AUTOSCROLL, false) 关掉自动滚动,因为看的是历史不是实时。用 iBarShift(symbol, tf, deal_close_time, false) 算出交易结束 K 线在图上的位移,再 ChartNavigate(handle, CHART_CURRENT_POS, bars+user_offset) 把图推到那段历史。ChartRedraw() 确认位移生效,paintDeal() 和 paintPanel() 负责画开平仓线、止损止盈线和信息表。 截图前用 ChartSetInteger 取 CHART_WIDTH_IN_PIXELS 和 CHART_HEIGHT_IN_PIXELS,按“经纪商/账号/年/月/日/仓位号”拼路径,ChartScreenShot() 存盘,目录不存在终端自己建。存完 ChartClose(handle) 关图,免得大量历史成交把终端窗口堆满。外汇与贵金属杠杆高,历史回看仅作复盘参考,实盘仍属高风险。

MQL5 / C++
class=class="str">"cmt">//--- data collected, moving on to printing
   class="type">int bars = -class="num">1;                                                                       class=class="str">"cmt">// number of bars in a shift
   class="type">int chart_width = -class="num">1;                                                                class=class="str">"cmt">// chart width
   class="type">int chart_height =-class="num">1;                                                                class=class="str">"cmt">// chart height

◍ 先把品种塞进市场报价窗再开图

遍历成交品种数组时,第一步不是画图,而是确认符号已存在于市场报价簿。若 SymbolSelect 返回 false,说明该品种未被终端加载,此时直接打印错误并弹窗提示用户去报价窗点“显示全部”,随后 return 终止脚本,避免后续对空句柄操作。 MqlDateTime 结构在这里承接成交平仓时间,TimeToStruct 把时间戳拆成年月日时分秒,便于后续按交易日对齐图表位置。若主图周期不是 PERIOD_CURRENT,脚本会用 ChartOpen 按指定品种和周期开新图,并套用预设模板。 ChartOpen 之后必须 Sleep(2000),给 MT5 至少 2 秒加载图表数据;不等待就取 iBarShift 很可能拿到 -1。接着关掉 CHART_AUTOSCROLL,再用 iBarShift 以平仓时间反推柱线偏移量,这一步偏移若算错,标记线就会画到不对的 K 线上。外汇与贵金属杠杆高,脚本仅做历史复盘标记,实盘请先在模拟盘验证。

MQL5 / C++
class="type">long handle =-class="num">1;                                                                      class=class="str">"cmt">// chart handle
for(class="type">int i=class="num">0; i<ArraySize(arr_symbol); i++)                                                class=class="str">"cmt">// iterate through all deal symbols
class=class="str">"cmt">//--- check for symbol availability
for(class="type">int i=class="num">0; i<ArraySize(arr_symbol); i++)                                                class=class="str">"cmt">// iterate through all deal symbols
   {
   if(!SymbolSelect(arr_symbol[i],true))                                                  class=class="str">"cmt">// check if the symbol is in the book and add if not
      {
      printf("%s - %d -> Failed to add a symbol %s to the marketbook. Error: %d",
            __FUNCTION__,__LINE__,arr_symbol[i],GetLastError()); class=class="str">"cmt">// informed in the journal
      MessageBox("Failed to add a symbol to the marketbook: "+arr_symbol[i]+
            ". Please select &class="macro">#x27;show all&class="macro">#x27; in the your market book and try again. Script is done."); class=class="str">"cmt">// informed in the message
      class="kw">return;                                                                              class=class="str">"cmt">// if failed, abort
      }
   }
      class="type">MqlDateTime deal_close_date;                                                         class=class="str">"cmt">// deal closure date in the structure
      TimeToStruct(arr_time_close[i],deal_close_date);                                     class=class="str">"cmt">// pass date to the structure
      class=class="str">"cmt">//--- check the main one
      if(main_graph != PERIOD_CURRENT)                                                     class=class="str">"cmt">// if the main one selected
         class=class="str">"cmt">//--- open the required chart
         handle = ChartOpen(arr_symbol[i],main_graph);                                     class=class="str">"cmt">// open the necessary symbol chart
         ChartApplyTemplate(handle,main_template);                                         class=class="str">"cmt">// apply class="kw">template
         Sleep(class="num">2000);                                                                      class=class="str">"cmt">// wait for the chart to load
         ChartSetInteger(handle,CHART_AUTOSCROLL,false);                                   class=class="str">"cmt">// disable auto scroll
         bars = iBarShift(arr_symbol[i],main_graph,arr_time_close[i],false); class=class="str">"cmt">// get the shift for the deal time

「把单笔持仓渲染成可归档的 PNG」

遍历到某一笔持仓时,先按自定义右边距把图表平移,避免成交箭头被压在最新 K 线边缘。ChartNavigate 的 -bars+bars_from_right_main 偏移量直接决定截图里左侧留白,实战中建议 bars_from_right_main 取 10~20 根,太窄则止损线容易出框。 随后 paintDeal 负责画开平价位与止损止盈线,paintPanel 把魔术码、注释、库存费、佣金、利润等 15 个字段铺到信息面板。注意 paintPanel 末参用了 (int)SymbolInfoInteger(arr_symbol[i],SYMBOL_DIGITS) 动态取小数位,跨品种(如 XAUUSD 对比 EURUSD)截图才不会把价格标成 1.23456 或 2345.6 的错位格式。 截图前必须重读屏幕尺寸:ChartGetInteger 取 CHART_WIDTH_IN_PIXELS 与 CHART_HEIGHT_IN_PIXELS,否则用旧分辨率存图会在 4K 屏上截出半屏黑边。文件名按 经纪名/账号/年-月-日/持仓ID/品种+ID.png 拼,方便后续按日归档回看哪一笔被洗掉。 ChartScreenShot 用 ALIGN_LEFT 对齐存盘后立刻 ChartClose 释放句柄;若漏掉 Close,MT5 后台会累积隐图表,跑几百笔历史持仓可能拖到终端卡死。外汇与贵金属波动剧烈、杠杆风险高,这套截图仅作复盘留痕,不代表任何方向判断。

MQL5 / C++
ChartNavigate(handle,CHART_CURRENT_POS,-bars+bars_from_right_main); class=class="str">"cmt">// shifted the chart with a custom margin
class=class="str">"cmt">//--- draw the deal
paintDeal(handle,PositionID[i],arr_stop_loss[i],arr_take_profit[i],arr_open[i],arr_close[i],arr_time_open[i],arr_time_close[i]);
class=class="str">"cmt">//--- draw the information panel
paintPanel(handle,PositionID[i],arr_stop_loss[i],arr_take_profit[i],arr_open[i],
            arr_close[i],arr_time_open[i],arr_time_close[i],arr_magic[i],arr_comment[i],
            arr_extermalID[i],arr_volume[i],arr_commission[i],arr_reason[i],arr_swap[i],
            arr_profit[i],arr_fee[i],arr_symbol[i],(class="type">int)SymbolInfoInteger(arr_symbol[i],SYMBOL_DIGITS));
class=class="str">"cmt">//--- get data by screen size
chart_width = (class="type">int) ChartGetInteger(handle,CHART_WIDTH_IN_PIXELS);  class=class="str">"cmt">// look at the chart width
chart_height = (class="type">int) ChartGetInteger(handle,CHART_HEIGHT_IN_PIXELS); class=class="str">"cmt">// look at the chart height
class="type">class="kw">string name_main_screen = brok_name+"/"+
            IntegerToString(account_num)+"/"+
            IntegerToString(deal_close_date.year)+"-"+IntegerToString(deal_close_date.mon)+
                   "-"+IntegerToString(deal_close_date.day)+"/"+
            IntegerToString(PositionID[i])+"/"+
            EnumToString(main_graph)+IntegerToString(PositionID[i])+".png"; class=class="str">"cmt">// assign the name
ChartScreenShot(handle,name_main_screen,chart_width,chart_height,ALIGN_LEFT);              class=class="str">"cmt">// make a screenshot
ChartClose(handle);                                                                       class=class="str">"cmt">// closed the chart

常见问题

用两个独立数组,开仓时向开仓数组推入价位与时间,平仓时向平仓数组推入对应数据,避免混在一个结构里难追溯。
建议落库开仓价、当前价、浮动盈亏三项数组,这样复盘时可直接用这三个值渲染成本线和小浮亏标记。
可以,小布能读取你的成交记录并生成图形化展示,一键导出可归档的 PNG,省去手动截图排版。
若品种未加入市场报价窗,直接调开图函数会失败,先塞进报价窗再开图才能保证渲染和落盘截图稳定。
在平仓或定时触发时把持仓渲染到独立图表,调用截图接口存为带时间戳的 PNG,归入本地归档目录即可。