//+------------------------------------------------------------------+ //| ExportTicks.mq5 | //| AnimateDread | //| | //| Dumps real tick history to CSV for offline research. | //| | //| Attach to a chart of the symbol you want (or set InpSymbol), run | //| once. Writes to the COMMON files folder so the research scripts | //| under research/ can read it without a copy step: | //| \Common\Files\Warrior_EA\Research\_ticks.csv | //| | //| Columns: time_msc,bid,ask,last,volume,flags | //| | //| `flags` is kept raw rather than decoded. On FX/CFD it will be | //| TICK_FLAG_BID/ASK only - TICK_FLAG_BUY/SELL and volume/volume_real| //| are empty for Forex instruments, so signed trade direction does | //| NOT exist in this feed. The research side synthesises flow from | //| quote dynamics instead (tick rule, bid-vs-ask update asymmetry, | //| arrival rate), which is why the raw bitmask has to survive export.| //+------------------------------------------------------------------+ #property copyright "AnimateDread" #property script_show_inputs input string InpSymbol = ""; // Symbol (empty = chart symbol) input datetime InpFrom = D'2019.01.01 00:00'; // From input datetime InpTo = 0; // To (0 = now) input int InpChunkHrs = 24; // Hours per request chunk //+------------------------------------------------------------------+ //| CopyTicksRange takes MILLISECONDS, not datetime - the single | //| easiest mistake to make with this API. | //+------------------------------------------------------------------+ void OnStart() { string sym = (InpSymbol == "") ? _Symbol : InpSymbol; datetime to = (InpTo == 0) ? TimeCurrent() : InpTo; if(!SymbolSelect(sym, true)) { PrintFormat("ExportTicks: cannot select %s", sym); return; } string dir = "Warrior_EA\\Research\\"; string path = dir + sym + "_ticks.csv"; //--- FILE_COMMON so tester agents and the offline python kit share one location int fh = FileOpen(path, FILE_WRITE | FILE_CSV | FILE_ANSI | FILE_COMMON, ',', CP_UTF8); if(fh == INVALID_HANDLE) { PrintFormat("ExportTicks: FileOpen('%s') failed, error %d", path, GetLastError()); return; } FileWrite(fh, "time_msc", "bid", "ask", "last", "volume", "flags"); int digits = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS); long stepMs = (long)InpChunkHrs * 3600 * 1000; if(stepMs <= 0) stepMs = 24 * 3600 * 1000; long cursor = (long)InpFrom * 1000; long endMs = (long)to * 1000; ulong total = 0; int emptyRuns = 0; PrintFormat("ExportTicks: %s from %s to %s -> Common\\Files\\%s", sym, TimeToString(InpFrom), TimeToString(to), path); while(cursor < endMs) { long sliceEnd = cursor + stepMs; if(sliceEnd > endMs) sliceEnd = endMs; MqlTick t[]; //--- Dynamic array: a static one too small for the range fails with //--- ERR_HISTORY_SMALL_BUFFER (4407) and returns only a partial fill. int n = CopyTicksRange(sym, t, COPY_TICKS_ALL, (ulong)cursor, (ulong)sliceEnd); if(n < 0) { //--- In generated-tick tester modes this is 4014 FUNCTION_NOT_ALLOWED. Live it usually //--- means the range predates the broker's tick history - keep walking rather than abort, //--- since the early chunks of a long range are routinely empty. PrintFormat("ExportTicks: CopyTicksRange failed at %s, error %d", TimeToString((datetime)(cursor / 1000)), GetLastError()); emptyRuns++; if(emptyRuns > 400) { Print("ExportTicks: too many consecutive failures, stopping."); break; } cursor = sliceEnd; continue; } emptyRuns = 0; for(int i = 0; i < n; i++) { //--- Guard the boundary explicitly. CopyTicksRange is inclusive at both ends, so adjacent //--- chunks would otherwise duplicate any tick landing exactly on the split. if((long)t[i].time_msc < cursor || (long)t[i].time_msc >= sliceEnd) continue; FileWrite(fh, (string)t[i].time_msc, DoubleToString(t[i].bid, digits), DoubleToString(t[i].ask, digits), DoubleToString(t[i].last, digits), (string)t[i].volume, (string)t[i].flags); total++; } cursor = sliceEnd; if((total % 1000000) < (ulong)n && total > 0) PrintFormat("ExportTicks: %s ... %I64u ticks", TimeToString((datetime)(cursor / 1000)), total); } FileClose(fh); PrintFormat("ExportTicks: DONE - %I64u ticks written to Common\\Files\\%s", total, path); } //+------------------------------------------------------------------+