可视化交易图表(第二部分):数据图形化展示·进阶篇
开平仓分桶落数组的写法
这段逻辑把每一笔成交按入场和出场拆进不同的数组尾部,靠 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 会静默丢数据。
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 字段务必留痕,点差加佣金会直接吃掉小周期策略的期望。
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。实盘跑脚本时,这种显式报错能省掉一半瞎猜时间,但外汇与贵金属杠杆品种本身高风险,脚本只帮你看清账目,不替你判断方向。
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) 关图,免得大量历史成交把终端窗口堆满。外汇与贵金属杠杆高,历史回看仅作复盘参考,实盘仍属高风险。
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 线上。外汇与贵金属杠杆高,脚本仅做历史复盘标记,实盘请先在模拟盘验证。
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 后台会累积隐图表,跑几百笔历史持仓可能拖到终端卡死。外汇与贵金属波动剧烈、杠杆风险高,这套截图仅作复盘留痕,不代表任何方向判断。
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