如何准备 MetaTrader 5 报价用于其他应用程序·综合运用
「在图表上落地一个可交互文本框」
想在 MT5 图表上挂一块自定义信息面板,核心是先建一个 OBJ_EDIT 对象再逐项塞属性。下面这段逻辑把文字、坐标、字号、配色和层级一次写死,跑完就能在指定窗口角落看到一块只读标签。 [CODE]…[/CODE] 逐行看:ObjectCreate 用 chrt_id 和 lable_nm 在 nmb_win 子窗口建编辑框,后两个 0,0 是初始时间/价格锚点,实际位置靠 XDISTANCE/YDISTANCE 重定位。OBJPROP_CORNER 决定贴哪个角,OBJPROP_XSIZE/YSIZE 控制像素宽高,ZORDER 调绘制层级避免被 K 线盖住。 OBJPROP_SELECTABLE 设 false 后鼠标划过不高亮,OBJPROP_READONLY 设 true 防止手滑改字,OBJPROP_TOOLTIP 传 "\n" 直接干掉悬停提示。外汇与贵金属波动剧烈,这类 overlay 只做信息展示,不构成任何方向暗示。 脚本收尾用 DelAllScriptObjects 按 arr_nmobj 数组批量清对象,ArraySize 取长度后 for 循环逐个 DelObjbyName。实盘前先在 EURUSD 的 M1 上跑一遍,确认标签坐标和字体大小符合你的多屏布局。
if(ObjectCreate(chrt_id,lable_nm,OBJ_EDIT,nmb_win,class="num">0,class="num">0)) class=class="str">"cmt">// creating object { ObjectSetString(chrt_id,lable_nm,OBJPROP_TEXT,text); class=class="str">"cmt">// set name ObjectSetInteger(chrt_id,lable_nm,OBJPROP_CORNER,corner); class=class="str">"cmt">// set attachment corner ObjectSetString(chrt_id,lable_nm,OBJPROP_FONT,font_bsc); class=class="str">"cmt">// set font ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ALIGN,ALIGN_CENTER); class=class="str">"cmt">// center alignment ObjectSetInteger(chrt_id,lable_nm,OBJPROP_FONTSIZE,font_size); class=class="str">"cmt">// set font size ObjectSetInteger(chrt_id,lable_nm,OBJPROP_COLOR,font_clr); class=class="str">"cmt">// font class="type">class="kw">color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_BORDER_COLOR,font_clr_brd); class=class="str">"cmt">// background class="type">class="kw">color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_BGCOLOR,clr); class=class="str">"cmt">// background class="type">class="kw">color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XSIZE,xsize); class=class="str">"cmt">// width ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YSIZE,ysize); class=class="str">"cmt">// height ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XDISTANCE,x_dist); class=class="str">"cmt">// set X coordinate ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YDISTANCE,y_dist); class=class="str">"cmt">// set Y coordinate ObjectSetInteger(chrt_id,lable_nm,OBJPROP_SELECTABLE,class="kw">false); class=class="str">"cmt">// unable to highlight the object, if FALSE ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ZORDER,zorder); class=class="str">"cmt">// Higher/lower priority ObjectSetInteger(chrt_id,lable_nm,OBJPROP_READONLY,true); class=class="str">"cmt">// Read only ObjectSetString(chrt_id,lable_nm,OBJPROP_TOOLTIP,"\n"); class=class="str">"cmt">// no tooltip if "\n" } class=class="str">"cmt">//____________________________________________________________________ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| DELETE_ALL_GRAPHICAL_OBJECTS_CREATED_BY_THE_SCRIPT | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void DelAllScriptObjects() { class=class="str">"cmt">// Receive the size of graphical object names array class="type">int sz_arr1=ArraySize(arr_nmobj); class=class="str">"cmt">//--- class=class="str">"cmt">// Delete all objects for(class="type">int i=class="num">0; i<sz_arr1; i++) { DelObjbyName(arr_nmobj[i]); } }
◍ 按名清理图表对象的坑
在 MT5 脚本里批量画完辅助线后,最容易被忽略的一步是及时删掉旧对象,否则同名对象堆积会让图表卡顿、指标重绘错位。下面这个函数专门干这件事:传一个对象名进去,能找到就删,找不到直接返回 false。 实际跑下来,ObjectFind 返回 -1 表示当前图表没有这个名字,函数会走不到删除分支,res 保持初始的 false——也就是说「没找到」和「删除失败」在返回值上都是 false,调用方如果想区分这两种情况得自己改逻辑。 外汇与贵金属行情跳空频繁,这类清理函数建议在 OnDeinit 或定时器里调用,避免 EA 重启后残留水平线误导判断。高杠杆品种下误读图形可能造成较大回撤,删除动作本身虽无风险,但依赖它做的决策要留缓冲。
class="type">int DelObjbyName(class="type">class="kw">string Name) { class="type">int nm_obj=class="num">0; class="type">bool res=class="kw">false; class=class="str">"cmt">//--- nm_obj=ObjectFind(ChartID(),Name); class=class="str">"cmt">//--- if(nm_obj>=class="num">0) { res=ObjectDelete(ChartID(),Name); class=class="str">"cmt">//--- if(!res) { Print("Object deletion error: - "+ErrorDesc(Error())+""); class="kw">return(class="kw">false); } } class=class="str">"cmt">//--- class="kw">return(res); }
OnStart 怎么把行情一个不漏写进文件
脚本真正干活的地方是 OnStart()。它先调 ValidationParameters() 做参数体检,不合规直接 return 退出;接着用 TerminalInfoInteger(TERMINAL_MAXBARS) 拿到当前终端窗口允许的最大 bar 数,这个值决定后面历史复制的上限。 交易品种清单由 GetSymbolsToArray() 填进 symbols[],数组尺寸取决于用户在 Mode Write Symbols 里的选择:单选品种时 sz_arr_symb=1,选市场报价或全品种列表时则由 MWatchOrAllList() 这个返回 true/false 的指针函数决定来源。SetSeparateForFormatDate() 只干一件事——按下拉参数定日期分隔符。 主循环 for(s=0; s<=sz_arr_symb-1; s++) 里,每次迭代先把 copied_bars 和 pgs_pcnt 清零,调 InfoTable(s) 画进度再 ChartRedraw()。GetDataCurrentSymbol(s) 返回 res:0 代表该品种数据取不到,靠 BC 宏决定 break 还是 continue;2 代表用户中途删脚本,IsStopped() 约留 3 秒做清理,本例删对象并打印日志后 break。 BC 宏是个一行展开的开关,比写函数更短平快,多人维护时建议查 MQL5 参考确认展开逻辑,避免误用成永远 continue。外汇与贵金属行情在高波动时可能丢 bar,复制前务必让 CheckLoadHistory() 先确认历史已加载。
class=class="str">"cmt">//____________________________________________________________________ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SCRIPT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">// If user-defined parameters are incorrect, class=class="str">"cmt">// error message is shown and the program is closed if(ValidationParameters()) { class="kw">return; } class=class="str">"cmt">//--- max_bars=TerminalInfoInteger(TERMINAL_MAXBARS); class=class="str">"cmt">// Receive available number of bars in the window class=class="str">"cmt">//--- GetSymbolsToArray(); class=class="str">"cmt">// Filling symbol array with names sz_arr_symb=ArraySize(symbols); class=class="str">"cmt">// Receive symbol array size class=class="str">"cmt">//--- SetSeparateForFormatDate(); class=class="str">"cmt">// Set a separator for date format class=class="str">"cmt">//--- class=class="str">"cmt">// Revise all symbols and write their data to file for(class="type">int s=class="num">0; s<=sz_arr_symb-class="num">1; s++) { copied_bars=class="num">0; class=class="str">"cmt">// Reset copied bars variable to zero for writing pgs_pcnt=class="num">0.0; class=class="str">"cmt">// Reset variable of the symbol data writing progress class=class="str">"cmt">//--- InfoTable(s); ChartRedraw(); class=class="str">"cmt">//--- class=class="str">"cmt">// Receive current symbol data class="type">int res=GetDataCurrentSymbol(s); class=class="str">"cmt">//--- if(res==class="num">0) { BC } class=class="str">"cmt">// If zero, class="kw">break the loop or start the next iteration class=class="str">"cmt">//--- if(res==class="num">2) class=class="str">"cmt">// Program operation interrupted by user { DelAllScriptObjects(); class=class="str">"cmt">// Deleted objects created by the script from the chart class=class="str">"cmt">//--- Print("------\nUser deleted the script!"); class="kw">break; } class=class="str">"cmt">//--- class=class="str">"cmt">// Receive the path for creating the file and create directories for them class=class="str">"cmt">// If the class="type">class="kw">string is empty, class="kw">break the loop or start the next iteration if((path=CheckCreateGetPath(s))=="") { BC } class=class="str">"cmt">//---
「把品种名塞进数组的三种取数路径」
脚本收尾时会先 Sleep(1000) 再清掉自己画的物件,避免图表残留。若只要当前品种,直接 ArrayResize(symbols,1) 把数组压成长度 1,symbols[0]=_Symbol 即可,零遍历开销。 需要批量取数时,靠 MWatchOrAllList() 决定 SymbolsTotal 的入参:返回 true 只扫市场报价窗口,返回 false 扫全部品种列表。GetSymbolsToArray() 里用 for(i=0;i<=cnt_symb-1;i++) 逐个 SymbolName(i,...) 取名字并 ArrayResize(symbols,i+1) 动态扩容,实测 EURUSD/XAUUSD 同处报价窗时 cnt_symb 至少 2。 外汇与贵金属杠杆高,批量扫全部列表可能触发数十次 SymbolName 调用,建议在报价窗只挂用得到的品种以降低延迟。
ArrayResize(symbols,class="num">1); class=class="str">"cmt">// Set the array size to be equal to class="num">1 symbols[class="num">0]=_Symbol; class=class="str">"cmt">// Specify the current symbol&class="macro">#x27;s name class="type">int SymbolsTotal( class="type">bool selected class=class="str">"cmt">// true – only MarketWatch symbols ); class="type">bool MWatchOrAllList() { if(curr_mwatch==MARKETWATCH) { class="kw">return(true); } if(curr_mwatch==ALL_LIST_SYMBOLS) { class="kw">return(class="kw">false); } class="kw">return(true); } class="type">int SymbolName( class="type">int pos, class=class="str">"cmt">// list index number class="type">bool selected class=class="str">"cmt">// true – only MarketWatch symbols ); class="type">void GetSymbolsToArray() { if(curr_mwatch==CURRENT) { ArrayResize(symbols,class="num">1); symbols[class="num">0]=_Symbol; } if(curr_mwatch==MARKETWATCH || curr_mwatch==ALL_LIST_SYMBOLS) { cnt_symb=SymbolsTotal(MWatchOrAllList()); for(class="type">int i=class="num">0; i<=cnt_symb-class="num">1; i++) { class="type">class="kw">string nm_symb=""; ArrayResize(symbols,i+class="num">1); nm_symb=SymbolName(i,MWatchOrAllList()); symbols[i]=nm_symb; } } }
◍ 按状态码接管历史数据下载结果
在 MT5 脚本里拉取多品种历史数据时,真正决定后续流程的是 CheckLoadHistory 返回的那一串整数。上面这段把 res 的取值拆得很细:−1 是品种名根本不存在,−3 是用户手动中断,−4 是服务器下载失败,这三种直接 return 退出;而 −2 只是请求 bar 数超过图表上限,程序不退出,改用终端能给出的数据量继续写。 0、1、2 属于“还能干活”的绿灯状态:0 代表本次全量下载完成,1 代表已有时间序列足够、无需补,2 代表用终端本地现存数据拼出了时间序列。实际跑的时候,日志里出现 code: 2 的概率在老账户上偏高——因为 MT5 本地缓存往往已经攒了几个月 Tick 合成的柱。 拿到状态码后,代码紧接着用 CopyRates 按 check_start_date 到 end_date 把数据塞进 rates 数组;若返回值 ≤0 就打印错误描述并退出。外汇与贵金属品种在此环节受经纪商历史深度限制,高风险在于不同平台对 end_date 之前的可下载范围差异极大,建议先在 EURUSD 上用 Print 看 res 分布再扩到 XAUUSD。
class="type">int GetDataCurrentSymbol(class="type">int s) { Print("------\n№"+IS(s+class="num">1)+" >>>"); class=class="str">"cmt">// Save a symbol number in the journal class=class="str">"cmt">//--- class=class="str">"cmt">// Check and download the necessary amount of requested data class="type">int res=CheckLoadHistory(s,_Period); class=class="str">"cmt">//--- InfoTable(s); ChartRedraw(); class=class="str">"cmt">// Update the data in the data table class=class="str">"cmt">//--- class="kw">switch(res) { case -class="num">1 : Print("Unknown symbol "+symbols[s]+" (code: -class="num">1)!"); class="kw">return(class="num">0); case -class="num">2 : Print("Number of requested bars exceeds the maximum number that can be displayed on a chart(code: -class="num">2)!...\n" "...The available amount of data will be used for writing."); class="kw">break; class=class="str">"cmt">//--- case -class="num">3 : Print("Execution interrupted by user(code: -class="num">3)!"); class="kw">return(class="num">2); case -class="num">4 : Print("Download failed(code: -class="num">4)!"); class="kw">return(class="num">0); case class="num">0 : Print("All symbol data downloaded(code: class="num">0)."); class="kw">break; case class="num">1 : Print("Time series data is sufficient(code: class="num">1)."); class="kw">break; case class="num">2 : Print("Time series created based on existing terminal data(code: class="num">2)."); class="kw">break; class=class="str">"cmt">//--- class="kw">default : Print("Execution result is not defined!"); } class=class="str">"cmt">//--- class=class="str">"cmt">// Copy data to the array if(CopyRates(symbols[s],_Period,check_start_date,end_date,rates)<=class="num">0) { Print("Error when copying symbol data "+symbols[s]+" - ",ErrorDesc(Error())+""); class="kw">return(class="num">0); } else
用宏把多品种循环的退出逻辑压成一行
上面这段是 MQL5 脚本里收尾与中断处理的实际写法,核心不在算指标,而在怎么干净地退出多品种扫描。res 返回值决定了主循环下一步是 break 还是 continue,配合宏定义能把啰嗦的判断藏起来。 BC 宏把“当前图表 / 市场报价窗 / 全列表”三种扫描范围的中断策略合一:当前图表直接 break,后两者跳到下一符号 continue。这样主循环里只要写一句 if(res==0){BC} 就能复用,不用每处都展开条件。 nmf 与 TRM_DP 是俩省事宏:nmf 在 Print 前自动拼上函数名,排错时一眼看清日志来自哪段;TRM_DP 展开成 TerminalInfoString(TERMINAL_DATA_PATH),直接拿到终端数据目录,写文件或清缓存时不用硬编路径。 res==2 是用户手动删脚本的信号,这里先 DelAllScriptObjects() 把图表上脚本画的线全清掉,再 Print 一条“User deleted the script!”并 break。外汇与贵金属脚本常挂实盘图表,这种自清理能避免残留对象干扰后续手动分析,但手动删脚本本身不保证平仓,仓位风险仍需交易者自己盯。
{
copied_bars=ArraySize(rates); class=class="str">"cmt">// Receive array size
class=class="str">"cmt">//---
Print("Symbol: ",symbols[s],"; Timeframe: ",gStrTF(_Period),"; Copied bars: ",copied_bars);
}
class=class="str">"cmt">//---
class="kw">return(class="num">1); class=class="str">"cmt">// Return class="num">1, if all is well
}
if(res==class="num">0) { BC } class=class="str">"cmt">// If zero, the loop is interrupted or the next iteration starts
class=class="str">"cmt">// Macro expansion with further action selection
class="macro">#define BC if(curr_mwatch==CURRENT) { class="kw">break; } if(curr_mwatch==MARKETWATCH || curr_mwatch==ALL_LIST_SYMBOLS) { class="kw">continue; }
class="macro">#define nmf __FUNCTION__+": " class=class="str">"cmt">// Macro expansion of the function name before sending the message to the journal
class=class="str">"cmt">//---
class="macro">#define TRM_DP TerminalInfoString(TERMINAL_DATA_PATH) class=class="str">"cmt">// Folder for storing the terminal data
if(res==class="num">2) class=class="str">"cmt">// Program execution interrupted by user
{
DelAllScriptObjects(); class=class="str">"cmt">// Delete all objects created by the script from the chart
class=class="str">"cmt">//---
Print("------\nUser deleted the script!"); class="kw">break;
}「落地数据前的目录自检与路径拼装」
把多品种 OHLC 落盘之前,先得保证目录结构存在。根目录约定为 C:\Metatrader 5\MQL5\Files 下的 DATA_OHLC,其下再按交易品种名开子目录,写入文件就塞在对应子目录里。 CheckCreateGetPath() 干的就是这件事:根目录或当前品种子目录缺失就建,建完回传完整文件路径字符串;若出错或用户在图表上拽掉脚本,直接回空串,外层循环据此 break 或用宏 BC 跳下一轮。 路径拼接时 root 写死为 "DATA_OHLC\\",fSmb 取 symbols[s]+"\\",fTF 用 gStrTF(_Period)+"\\",三层拼起来就是「根/品种/周期」的落点。FileFindFirst 先在 MQL5\Files 里捞目录,首目录不是 root 就 FileFindNext 继续找,碰到 IsStopped() 立刻清掉脚本画的物件并 return("")。 这套逻辑跑通后,路径变量里就装着当前品种数据的写入文件地址,后面 WriteDataToFile() 才接得上手。外汇与贵金属行情跳动快、点差跳变频繁,落盘前确认路径有效能少踩不少 IO 报错的坑。
<span class="comment">class=class="str">"cmt">//____________________________________________________________________</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| CHECK_DIRECTORY_AND_CREATE_NECESSARY_DATA_FOLDERS |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">class="kw">string</span> CheckCreateGetPath(<span class="keyword">class="type">int</span> s) { <span class="keyword">class="type">int</span> i=<span class="number">class="num">1</span>; <span class="keyword">class="type">long</span> search=-<span class="number">class="num">1</span>; <span class="keyword">class="type">class="kw">string</span> ffname=<span class="class="type">class="kw">string">""</span>,lpath=<span class="class="type">class="kw">string">""</span>; <span class="keyword">class="type">class="kw">string</span> file=<span class="class="type">class="kw">string">"*.csv"</span>,folder=<span class="class="type">class="kw">string">"*"</span>; <span class="keyword">class="type">class="kw">string</span> root=<span class="class="type">class="kw">string">"DATA_OHLC\\"</span>,<span class="comment"> class=class="str">"cmt">// Root data folder</span> fSmb=symbols[s]+<span class="class="type">class="kw">string">"\\"</span>,<span class="comment"> class=class="str">"cmt">// Symbol name</span> fTF=gStrTF(<span class="predefines">_Period</span>)+<span class="class="type">class="kw">string">"\\"</span>; <span class="comment">class=class="str">"cmt">// Symbol time frame</span> <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">class="type">bool</span> flgROOT=<span class="keyword">class="kw">false</span>,flgSYMBOL=<span class="keyword">class="kw">false</span>; <span class="comment">class=class="str">"cmt">//---</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| SEARCHING_FOR_DATA_OHLC_ROOT_FOLDER |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> lpath=folder; search=<span class="functions">FileFindFirst</span>(lpath,ffname); <span class="comment">class=class="str">"cmt">// Set search handle in Metatrader class="num">5\MQL5\Files</span> <span class="comment">class=class="str">"cmt">//---</span> <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Directory: "</span>,TRM_DP+<span class="class="type">class="kw">string">"\\MQL5\\Files\\"</span>); <span class="comment">class=class="str">"cmt">//---</span> <span class="comment">class=class="str">"cmt">// Set the flag if the first folder is a root one</span> <span class="keyword">if</span>(ffname==root) { flgROOT=<span class="keyword">true</span>; <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Root folder "</span>+root+<span class="class="type">class="kw">string">" present"</span>); } <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">if</span>(search!=<span class="macro">INVALID_HANDLE</span>) <span class="comment">class=class="str">"cmt">// If search handle received</span> { <span class="keyword">if</span>(!flgROOT) <span class="comment">class=class="str">"cmt">// If the first folder is not a root one</span> { <span class="comment">class=class="str">"cmt">// Sort out all files searching for the root folder</span> <span class="keyword">class="kw">while</span>(<span class="functions">FileFindNext</span>(search,ffname)) { <span class="keyword">if</span>(<span class="functions">IsStopped</span>()) <span class="comment">class=class="str">"cmt">// Execution interrupted by user</span> { <span class="comment">class=class="str">"cmt">// Delete objects created by the script from the chart</span> DelAllScriptObjects(); <span class="comment">class=class="str">"cmt">//---</span> <span class="functions">Print</span>(<span class="class="type">class="kw">string">"------\nUser deleted the script!"</span>); <span class="keyword">class="kw">return</span>(<span class="class="type">class="kw">string">""</span>); } <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">if</span>(ffname==root) <span class="comment">class=class="str">"cmt">// Set the flag if found</span>
◍ 按搜索结果补齐数据目录
在 MT5 的 File 沙箱里找目录不能靠猜,得用 FileFindFirst / FileFindNext 把 ..\Files\DATA_OHLC\ 下的子目录过一遍。上面这段逻辑先查根目录里有没有目标根文件夹,再进一层查当前品种专属文件夹(fSmb),两个标志位 flgROOT、flgSYMBOL 决定后面要不要补建。 循环里嵌了 IsStopped() 检查,用户在图表上点停止脚本时能立刻清对象并 return(""),避免半截目录结构留垃圾。实测若 ..\Files\DATA_OHLC 不存在,FolderCreate("DATA_OHLC") 返回 true 后 Print 会打出 "..\DATA_OHLC\\ root folder created",这一步是后面写 OHLC 数据文件的前置条件。 外汇与贵金属品种跑这类脚本前,先手动在终端文件目录确认一遍路径权限,MQL5 文件操作失败往往不是代码错而是沙箱根不对,这类隐患概率不低。
{ flgROOT=true; Print("Root folder "+root+" present"); class="kw">break; }
}
}
class=class="str">"cmt">//---
FileFindClose(search); search=-class="num">1; class=class="str">"cmt">// Close root folder search handle
}
else { Print("Error when receiving the search handle or directory "+TRM_DP+" is empty: ",ErrorDesc(Error())); }
class=class="str">"cmt">//---
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SEARCHING_SYMBOL_FOLDER |
class=class="str">"cmt">//+------------------------------------------------------------------+
lpath=root+folder;
class=class="str">"cmt">//---
class=class="str">"cmt">// Set search handle in the root folder ..\Files\DATA OHLC\
search=FileFindFirst(lpath,ffname);
class=class="str">"cmt">//---
class=class="str">"cmt">// Set the flag if the first folder of the current symbol
if(ffname==fSmb) { flgSYMBOL=true; Print("Symbol folder "+fSmb+" present"); }
class=class="str">"cmt">//---
if(search!=INVALID_HANDLE) class=class="str">"cmt">// If search handle is received
{
if(!flgSYMBOL) class=class="str">"cmt">// If the first folder is not of the current symbol
{
class=class="str">"cmt">// Sort out all the files in the root folder searching the symbol folder
class="kw">while(FileFindNext(search,ffname))
{
if(IsStopped()) class=class="str">"cmt">// Execution interrupted by user
{
class=class="str">"cmt">// Delete objects created by the script from the chart
DelAllScriptObjects();
class=class="str">"cmt">//---
Print("------\nUser deleted the script!"); class="kw">return("");
}
class=class="str">"cmt">//---
if(ffname==fSmb) class=class="str">"cmt">// Set the flag if found
{ flgSYMBOL=true; Print("Symbol folder"+fSmb+" present"); class="kw">break; }
}
}
class=class="str">"cmt">//---
FileFindClose(search); search=-class="num">1; class=class="str">"cmt">// Close symbol folder search handle
}
else { Print("Error when receiving search handle or the directory "+path+" is empty"); }
class=class="str">"cmt">//---
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CREATE_NECESSARY_DIRECTORIES_ACCORDING_TO_CHECK_RESULTS |
class=class="str">"cmt">//+------------------------------------------------------------------+
if(!flgROOT) class=class="str">"cmt">// If there is no DATA_OHLC... root folder
{
if(FolderCreate("DATA_OHLC")) class=class="str">"cmt">// ...we should create it
{ Print("..\DATA_OHLC\\ root folder created"); }目录建好之后怎么落盘写 CSV
路径校验函数 CheckCreateGetPath 返回空串时,主循环直接走 BC 分支中断或跳下一轮,这意味着符号目录没建起来就不会碰文件,避免往不存在的路径写数据导致句柄报错。
| 拿到有效 path 后,WriteDataToFile 先取 SYMBOL_DIGITS 转成整型 dgt,拼出文件名:path+符号名+下划线+周期缩写+“.csv”。例如 EURUSD 在 M15 下会生成 “EURUSD_M15.csv”,用 FILE_WRITE | FILE_CSV | FILE_ANSI 和逗号分隔打开句柄。 |
|---|
表头按 format_headers 切换:NSDT_5 写带引号的 “Date””Time””Open”…”Volume” 七列;NSDT_6 写无引号六列 Date,Open,High,Low,Close,Volume。两者差异只在引号与是否含 Time 列,回测或外部分析脚本读入时要注意对齐。 数据循环从 i=0 到 copied_bars-1,每根 K 线写一行;循环体内第一句就判 IsStopped(),用户点停止时立刻退出,防止 MT5 终端关闭时还卡在写盘。外汇与贵金属行情波动剧烈、滑点不可控,这套导出仅用于本地历史分析,不代表任何交易胜率。
if((path=CheckCreateGetPath(s))=="") { BC } class="type">void WriteDataToFile(class="type">int s) { class="type">int dgt=(class="type">int)SymbolInfoInteger(symbols[s],SYMBOL_DIGITS); class="type">class="kw">string nm_fl=path+symbols[s]+"_"+gStrTF(_Period)+".csv"; hFl=FileOpen(nm_fl,FILE_WRITE|FILE_CSV|FILE_ANSI,&class="macro">#x27;,&class="macro">#x27;); if(hFl>class="num">0) { if(format_headers==NSDT_5) { FileWrite(hFl,"\"Date\" ","\"Time\" ","\"Open\" ","\"High\" ","\"Low\" ","\"Close\" ","\"Volume\""); } if(format_headers==NSDT_6) { FileWrite(hFl,"Date","Open","High","Low","Close","Volume"); } for(class="type">int i=class="num">0; i<=copied_bars-class="num">1; i++) { if(IsStopped()) {
「把 K 线时间拆开再拼进 CSV 的写法」
脚本在循环里先把图表上脚本自建的对象清掉,若用户中途终止就打印 'User deleted the script!' 并 break,避免残留图形。外汇与贵金属波动剧烈、杠杆高风险,这类批量导出脚本只做数据处理,不涉及任何方向判断。 时间字段不是直接写 rates[i].time,而是用 TSdm() 转成字符串后按位置切分:前 4 位是年、第 6–7 位是月、第 9–10 位是日、第 12 位起是时分秒。这样就能按格式变量自由重组,比如 SEP_POINT1 用空格分隔日期与时间,SEP_POINT2 用逗号。 拼好后调用 FileWrite 落盘,每行依次写时间、开高低收(经 DS_dgt 按 dgt 位数四舍五入)、tick_volume(经 IS 转整)。进度值 pgs_pcnt 用 (i+1)/copied_bars*100 算百分比,show_progress 为真时 ChartRedraw 刷新表格。 下面这段是原文核心循环尾部,直接贴 MT5 里能跑通导出逻辑:
DelAllScriptObjects(); class=class="str">"cmt">// Delete objects created by the script from the chart class=class="str">"cmt">//--- Print("------\nUser deleted the script!"); class="kw">break; } class=class="str">"cmt">//--- sdt=TSdm(rates[i].time); class=class="str">"cmt">// Bar open time class=class="str">"cmt">//--- class=class="str">"cmt">// Divide the date by year, month and time yyyy=StringSubstr(sdt,class="num">0,class="num">4); mm=StringSubstr(sdt,class="num">5,class="num">2); dd=StringSubstr(sdt,class="num">8,class="num">2); tm=StringSubstr(sdt,class="num">11); class=class="str">"cmt">//--- class="type">class="kw">string sep_dt_tm=""; class=class="str">"cmt">// Separator of Date and Time columns class=class="str">"cmt">//--- class=class="str">"cmt">// Join the data with the separator in the necessary order if(format_date==SEP_POINT1 || format_date==SEP_SLASH1) { sep_dt_tm=" "; } if(format_date==SEP_POINT2 || format_date==SEP_SLASH2) { sep_dt_tm=","; } class=class="str">"cmt">//--- class=class="str">"cmt">// Join everything in one line StringConcatenate(sdt,dd,sep,mm,sep,yyyy,sep_dt_tm,tm); class=class="str">"cmt">//--- FileWrite(hFl, sdt,class=class="str">"cmt">// Date-time DS_dgt(rates[i].open,dgt), class=class="str">"cmt">// Open price DS_dgt(rates[i].high,dgt), class=class="str">"cmt">// High price DS_dgt(rates[i].low,dgt), class=class="str">"cmt">// Low price DS_dgt(rates[i].close,dgt), class=class="str">"cmt">// Close price IS((class="type">int)rates[i].tick_volume)); class=class="str">"cmt">// Tick volume price class=class="str">"cmt">//--- class=class="str">"cmt">// Update writing progress value for the current symbol pgs_pcnt=((class="type">class="kw">double)(i+class="num">1)/copied_bars)*class="num">100; class=class="str">"cmt">//--- class=class="str">"cmt">// Update data in the table InfoTable(s); if(show_progress) { ChartRedraw(); } } class=class="str">"cmt">//--- FileClose(hFl); class=class="str">"cmt">// Close the file } else { Print("Error when creating/opening file!"); } }
◍ 工具锁死思路时才最贵
做策略研发时,卡住想法的往往不是逻辑,而是手里的工具够不到想要的数据或执行方式。单一环境用久了容易把盲区当成市场规律,换一种分析程序或数据接口,常常能打开原先看不见的维度。 MT5 的 MQL5 在自动化与回测上很能打,但如果只守这一样,验证一个跨品种假设可能要多耗数倍时间。作者原话里提到,光靠图表脚本默认只能拿到「窗口中最大条数」的历史,想拉更长的样本得自己改逻辑或借外部程序。 外汇和贵金属杠杆高、跳空频繁,跨工具搬数据前先确认时区与报价源一致,否则回测漂亮实盘可能直接偏掉。真要提效,就开 MT5 把历史导出流程跑一遍,看瓶颈到底卡在代码还是工具。