2026-11-06 16:16:49 -08:00 | | | //+------------------------------------------------------------------+
|
| | | //| TkzExport.mq5 |
|
| | | //| Exports tick history (time_msc, bid, ask) into a .tkz archive. |
|
| | | //+------------------------------------------------------------------+
|
| | | #property copyright "TKZ tick compression"
|
2026-11-06 20:31:08 -08:00 | | | #property version "1.1"
|
2026-11-06 16:16:49 -08:00 | | | #property description "Exports the tick history of a symbol (time_msc, bid, ask) into a compressed .tkz archive."
|
| | | #property script_show_inputs
|
| | |
|
2026-11-06 20:26:15 -08:00 | | | #include "../lib/TkzArchive.mqh"
|
2026-11-06 16:16:49 -08:00 | | |
|
| | | 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 = <symbol>.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(cur<to && !IsStopped())
|
| | | {
|
| | | datetime next=(datetime)(DayStart(cur)+86400);
|
| | | if(next>boundary)
|
| | | 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<got; i++)
|
| | | {
|
| | | tms[n+i]=day[i].time_msc;
|
| | | bid[n+i]=day[i].bid;
|
| | | ask[n+i]=day[i].ask;
|
| | | }
|
| | | n+=got;
|
| | | }
|
| | | cur=next;
|
| | | if(cur>=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",
|
2026-11-06 16:25:46 -08:00 | | | sym,TimeToString(cur,TIME_DATE),wt,(double)writer.BytesWritten()/(double)(wt>0 ? wt : (long)1)));
|
2026-11-06 16:16:49 -08:00 | | | }
|
| | | 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");
|
| | | }
|
| | | //+------------------------------------------------------------------+
|