114 lines
5.2 KiB
MQL5
114 lines
5.2 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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: |
|
||
|
|
//| <Terminal>\Common\Files\Warrior_EA\Research\<SYM>_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);
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|