//+------------------------------------------------------------------+ //| TkzExport.mq5 | //| Exports tick history (time_msc, bid, ask) into a .tkz archive. | //+------------------------------------------------------------------+ #property copyright "TKZ tick compression" #property version "1.1" #property description "Exports the tick history of a symbol (time_msc, bid, ask) into a compressed .tkz archive." #property script_show_inputs #include "../lib/TkzArchive.mqh" enum ENUM_TKZ_CHUNK { TKZ_CHUNK_DAY=0, // Day TKZ_CHUNK_WEEK=1, // Week TKZ_CHUNK_MONTH=2 // Month }; enum ENUM_TKZ_TICKS { TKZ_TICKS_ALL=0, // All ticks (COPY_TICKS_ALL) TKZ_TICKS_INFO=1 // Bid/Ask changes only (COPY_TICKS_INFO) }; input string InpSymbol=""; // Symbol (empty = chart symbol) input datetime InpFrom=D'2024.01.01 00:00'; // From (server time) input datetime InpTo=0; // To (0 = now) input ENUM_TKZ_CHUNK InpChunk=TKZ_CHUNK_MONTH; // One chunk per input ENUM_TKZ_TICKS InpTicks=TKZ_TICKS_ALL; // Tick selection input string InpFile=""; // File name (empty = .tkz) input bool InpCommon=false; // Use the common data folder input bool InpVerify=true; // Decode every chunk again and compare //+------------------------------------------------------------------+ long DayStart(const datetime t) { long v=(long)t; return v-v%86400; } //+------------------------------------------------------------------+ //| First moment of the next chunk after t | //+------------------------------------------------------------------+ datetime NextBoundary(const datetime t,const ENUM_TKZ_CHUNK mode) { long d=DayStart(t); if(mode==TKZ_CHUNK_DAY) return (datetime)(d+86400); MqlDateTime s; TimeToStruct((datetime)d,s); if(mode==TKZ_CHUNK_WEEK) return (datetime)(d+(long)(7-s.day_of_week)*86400); // next Sunday 00:00 s.day=1; s.hour=0; s.min=0; s.sec=0; s.mon++; if(s.mon>12) { s.mon=1; s.year++; } return StructToTime(s); } //+------------------------------------------------------------------+ //| Ticks with from <= time < to, retried while history loads | //+------------------------------------------------------------------+ int FetchTicks(const string sym,const uint flags,const datetime from,const datetime to,MqlTick &ticks[]) { ulong from_msc=(ulong)from*1000; ulong to_msc=(ulong)to*1000-1; for(int attempt=0; attempt<5 && !IsStopped(); attempt++) { ResetLastError(); int n=CopyTicksRange(sym,ticks,flags,from_msc,to_msc); if(n>=0) return n; PrintFormat("TKZ: CopyTicksRange(%s, %s) failed, error %d, retrying",sym,TimeToString(from),GetLastError()); Sleep(2000); } return -1; } //+------------------------------------------------------------------+ void OnStart() { string sym=(InpSymbol=="") ? _Symbol : InpSymbol; if(!SymbolSelect(sym,true)) { PrintFormat("TKZ: symbol %s is not available",sym); return; } int digits=(int)SymbolInfoInteger(sym,SYMBOL_DIGITS); datetime to=(InpTo==0) ? (datetime)(TimeCurrent()+1) : InpTo; if(to<=InpFrom) { Print("TKZ: empty time range"); return; } string file=(InpFile=="") ? sym+".tkz" : InpFile; uint flags=(InpTicks==TKZ_TICKS_INFO) ? COPY_TICKS_INFO : COPY_TICKS_ALL; CTkzWriter writer; writer.SetVerify(InpVerify); if(!writer.Open(file,sym,digits,InpCommon)) { PrintFormat("TKZ: cannot create %s: %s",file,writer.LastError()); return; } long tms[]; double bid[]; double ask[]; MqlTick day[]; int n=0; int chunks=0; bool ok=true; ulong t_start=GetMicrosecondCount(); ulong fetch_us=0; datetime cur=InpFrom; datetime boundary=NextBoundary(cur,InpChunk); while(curboundary) next=boundary; if(next>to) next=to; ulong f0=GetMicrosecondCount(); int got=FetchTicks(sym,flags,cur,next,day); fetch_us+=GetMicrosecondCount()-f0; if(got<0) { PrintFormat("TKZ: no tick history for %s at %s, stopping",sym,TimeToString(cur)); ok=false; break; } if(got>0) { if(ArrayResize(tms,n+got,1000000)<0 || ArrayResize(bid,n+got,1000000)<0 || ArrayResize(ask,n+got,1000000)<0) { Print("TKZ: out of memory, use a shorter chunk period"); ok=false; break; } for(int i=0; i=boundary || cur>=to) { if(n>0) { if(!writer.Write(tms,bid,ask,0,n)) { PrintFormat("TKZ: chunk ending %s failed: %s",TimeToString(cur),writer.LastError()); ok=false; break; } chunks++; long wt=writer.TicksWritten(); Comment(StringFormat("TKZ export %s: up to %s, %I64d ticks, %.3f bytes/tick", sym,TimeToString(cur,TIME_DATE),wt,(double)writer.BytesWritten()/(double)(wt>0 ? wt : (long)1))); } n=0; boundary=NextBoundary(cur,InpChunk); } } long ticks=writer.TicksWritten(); long bytes=writer.BytesWritten(); double enc_s=(double)writer.EncodeMicroseconds()/1e6; double ver_s=(double)writer.VerifyMicroseconds()/1e6; writer.Close(); Comment(""); double total_s=(double)(GetMicrosecondCount()-t_start)/1e6; string folder=InpCommon ? TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"\\Files\\" : TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Files\\"; PrintFormat("TKZ: %s %s -> %s%s",ok ? "exported" : "PARTIALLY exported",sym,folder,file); if(ticks>0) { PrintFormat("TKZ: %I64d ticks in %d chunks, %I64d bytes: %.4f bytes/tick, %.1fx smaller than 24-byte records, %.1fx smaller than MqlTick", ticks,chunks,bytes,(double)bytes/(double)ticks,24.0*(double)ticks/(double)bytes,(double)(ticks*sizeof(MqlTick))/(double)bytes); PrintFormat("TKZ: encode %.2f s (%.0f ticks/s), verify %.2f s (%.0f ticks/s), history %.2f s, total %.2f s", enc_s,enc_s>0 ? (double)ticks/enc_s : 0.0,ver_s,ver_s>0 ? (double)ticks/ver_s : 0.0,(double)fetch_us/1e6,total_s); } else Print("TKZ: no ticks in the requested range"); } //+------------------------------------------------------------------+