824 lines
24 KiB
MQL5
824 lines
24 KiB
MQL5
|
//+------------------------------------------------------------------+
|
||
|
//| CalendarCache.mqh |
|
||
|
//| Copyright 2022, MetaQuotes Ltd. |
|
||
|
//| https://www.mql5.com |
|
||
|
//+------------------------------------------------------------------+
|
||
|
#ifndef PRTF
|
||
|
#include "PRTF.mqh"
|
||
|
#endif
|
||
|
#include "Defines.mqh"
|
||
|
#include "QuickSortStructT.mqh"
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Helper struct with indexed storage of strings |
|
||
|
//+------------------------------------------------------------------+
|
||
|
template<typename T>
|
||
|
struct StringRef
|
||
|
{
|
||
|
static string cache[];
|
||
|
int index;
|
||
|
StringRef(): index(-1) { }
|
||
|
|
||
|
void operator=(const string s)
|
||
|
{
|
||
|
if(index == -1)
|
||
|
{
|
||
|
PUSH(cache, s);
|
||
|
index = ArraySize(cache) - 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cache[index] = s;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
string operator[](int x = 0) const
|
||
|
{
|
||
|
if(index != -1)
|
||
|
{
|
||
|
return cache[index];
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static bool save(const int handle)
|
||
|
{
|
||
|
FileWriteInteger(handle, ArraySize(cache));
|
||
|
for(int i = 0; i < ArraySize(cache); ++i)
|
||
|
{
|
||
|
FileWriteInteger(handle, StringLen(cache[i]));
|
||
|
FileWriteString(handle, cache[i]);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool load(const int handle)
|
||
|
{
|
||
|
const int n = FileReadInteger(handle);
|
||
|
for(int i = 0; i < n; ++i)
|
||
|
{
|
||
|
PUSH(cache, FileReadString(handle, FileReadInteger(handle)));
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
template<typename T>
|
||
|
static string StringRef::cache[];
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Main class for calendar cache |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CalendarCache
|
||
|
{
|
||
|
string context;
|
||
|
datetime from, to;
|
||
|
datetime t;
|
||
|
ulong eventId;
|
||
|
MqlCalendarValue values[];
|
||
|
MqlCalendarEvent events[];
|
||
|
MqlCalendarCountry countries[];
|
||
|
|
||
|
// indices of values by event_id, country, currency, time - ArraySort()-ed
|
||
|
ulong value2event[][2]; // [0] - event_id, [1] - value_id
|
||
|
ulong value2country[][2]; // [0] - country_id, [1] - value_id
|
||
|
ulong value2currency[][2]; // [0] - currency ushort[4]<->long, [1] - value_id
|
||
|
ulong value2time[][2]; // [0] - time, [1] - value_id
|
||
|
|
||
|
// mapping of ids into indices in MqlCalendar-arrays
|
||
|
int id4country[];
|
||
|
int id4event[];
|
||
|
int id4value[];
|
||
|
|
||
|
int collisions;
|
||
|
int worse;
|
||
|
int collided;
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Identificator hash support |
|
||
|
//+------------------------------------------------------------------+
|
||
|
|
||
|
static int size2prime(const int size)
|
||
|
{
|
||
|
static int primes[] =
|
||
|
{
|
||
|
17, 53, 97, 193, 389,
|
||
|
769, 1543, 3079, 6151,
|
||
|
12289, 24593, 49157, 98317,
|
||
|
196613, 393241, 786433, 1572869,
|
||
|
3145739, 6291469, 12582917, 25165843,
|
||
|
50331653, 100663319, 201326611, 402653189,
|
||
|
805306457, 1610612741
|
||
|
};
|
||
|
|
||
|
const int pmax = ArraySize(primes);
|
||
|
for(int p = 0; p < pmax; ++p)
|
||
|
{
|
||
|
if(primes[p] >= 2 * size)
|
||
|
{
|
||
|
return primes[p];
|
||
|
}
|
||
|
}
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
int place(const ulong id, const int index, int &array[])
|
||
|
{
|
||
|
const int n = ArraySize(array);
|
||
|
int p = (int)((MathSwap(id) ^ 0xEFCDAB8967452301) % n); // hash-function
|
||
|
int attempt = 0;
|
||
|
while(array[p] != -1)
|
||
|
{
|
||
|
if(++attempt > n / 10)
|
||
|
{
|
||
|
return -1; // not added
|
||
|
}
|
||
|
#ifdef DEBUG_LOG
|
||
|
Print("Collision ", attempt, ": ", id, "=", index, " / ", array[p]);
|
||
|
#endif
|
||
|
p = (p + attempt) % n;
|
||
|
}
|
||
|
collisions += attempt;
|
||
|
worse = fmax(worse, attempt);
|
||
|
if(attempt) collided++;
|
||
|
array[p] = index;
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
template<typename S>
|
||
|
int find(const ulong id, const int &array[], const S &structs[])
|
||
|
{
|
||
|
const int n = ArraySize(array);
|
||
|
if(!n) return false;
|
||
|
int p = (int)((MathSwap(id) ^ 0xEFCDAB8967452301) % n); // hash-function
|
||
|
int attempt = 0;
|
||
|
while(structs[array[p]].id != id)
|
||
|
{
|
||
|
if(++attempt > n / 10)
|
||
|
{
|
||
|
return -1; // not found
|
||
|
}
|
||
|
p = (p + attempt) % n;
|
||
|
}
|
||
|
return array[p];
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calendar data collecting, hashing and relational binding |
|
||
|
//+------------------------------------------------------------------+
|
||
|
|
||
|
bool update()
|
||
|
{
|
||
|
string country = NULL, currency = NULL;
|
||
|
if(StringLen(context) == 3)
|
||
|
{
|
||
|
currency = context;
|
||
|
}
|
||
|
else if(StringLen(context) == 2)
|
||
|
{
|
||
|
country = context;
|
||
|
}
|
||
|
|
||
|
Print("Reading online calendar base...");
|
||
|
reset();
|
||
|
|
||
|
if(!PRTF(CalendarValueHistory(values, from, to, country, currency))
|
||
|
|| (currency != NULL ?
|
||
|
!PRTF(CalendarEventByCurrency(currency, events)) :
|
||
|
!PRTF(CalendarEventByCountry(country, events)))
|
||
|
|| !PRTF(CalendarCountries(countries)))
|
||
|
{
|
||
|
// not ready, t = 0
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
t = TimeTradeServer();
|
||
|
}
|
||
|
return (bool)t;
|
||
|
}
|
||
|
|
||
|
bool hash()
|
||
|
{
|
||
|
Print("Hashing calendar...");
|
||
|
|
||
|
collisions = 0;
|
||
|
worse = 0;
|
||
|
collided = 0;
|
||
|
|
||
|
const int c = PRTF(ArraySize(countries));
|
||
|
PRTF(ArrayResize(id4country, size2prime(c)));
|
||
|
ArrayInitialize(id4country, -1);
|
||
|
|
||
|
for(int i = 0; i < c; ++i)
|
||
|
{
|
||
|
if(place(countries[i].id, i, id4country) == -1)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Print("Total collisions: ", collisions, ", worse:", worse,
|
||
|
", average: ", (float)collisions / collided, " in ", collided);
|
||
|
|
||
|
collisions = 0;
|
||
|
worse = 0;
|
||
|
collided = 0;
|
||
|
|
||
|
const int e = PRTF(ArraySize(events));
|
||
|
PRTF(ArrayResize(id4event, size2prime(e)));
|
||
|
ArrayInitialize(id4event, -1);
|
||
|
|
||
|
for(int i = 0; i < e && !IsStopped(); ++i)
|
||
|
{
|
||
|
if(place(events[i].id, i, id4event) == -1)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Print("Total collisions: ", collisions, ", worse:", worse,
|
||
|
", average: ", (float)collisions / collided, " in ", collided);
|
||
|
|
||
|
collisions = 0;
|
||
|
worse = 0;
|
||
|
collided = 0;
|
||
|
|
||
|
const int v = PRTF(ArraySize(values));
|
||
|
PRTF(ArrayResize(id4value, size2prime(v)));
|
||
|
ArrayInitialize(id4value, -1);
|
||
|
|
||
|
for(int i = 0; i < v && !IsStopped(); ++i)
|
||
|
{
|
||
|
if(place(values[i].id, i, id4value) == -1)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Print("Total collisions: ", collisions, ", worse:", worse,
|
||
|
", average: ", (float)collisions / collided, " in ", collided);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool bind()
|
||
|
{
|
||
|
Print("Binding calendar tables...");
|
||
|
const int n = ArraySize(values);
|
||
|
ArrayResize(value2event, n);
|
||
|
ArrayResize(value2country, n);
|
||
|
ArrayResize(value2currency, n);
|
||
|
ArrayResize(value2time, n);
|
||
|
for(int i = 0; i < n; ++i)
|
||
|
{
|
||
|
value2event[i][0] = values[i].event_id;
|
||
|
value2event[i][1] = values[i].id;
|
||
|
|
||
|
const int e = find(values[i].event_id, id4event, events);
|
||
|
if(e == -1) return false;
|
||
|
|
||
|
value2country[i][0] = events[e].country_id;
|
||
|
value2country[i][1] = values[i].id;
|
||
|
|
||
|
const int c = find(events[e].country_id, id4country, countries);
|
||
|
if(c == -1) return false;
|
||
|
|
||
|
value2currency[i][0] = currencyId(countries[c].currency);
|
||
|
value2currency[i][1] = values[i].id;
|
||
|
|
||
|
value2time[i][0] = values[i].time;
|
||
|
value2time[i][1] = values[i].id;
|
||
|
}
|
||
|
ArraySort(value2event);
|
||
|
ArraySort(value2country);
|
||
|
ArraySort(value2currency);
|
||
|
ArraySort(value2time); // should it be already in chronologic order?
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void reset()
|
||
|
{
|
||
|
ArrayFree(values);
|
||
|
ArrayFree(events);
|
||
|
ArrayFree(countries);
|
||
|
ArrayFree(value2country);
|
||
|
ArrayFree(value2currency);
|
||
|
ArrayFree(value2event);
|
||
|
ArrayFree(value2time);
|
||
|
ArrayFree(id4country);
|
||
|
ArrayFree(id4event);
|
||
|
ArrayFree(id4value);
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
const static string CALENDAR_CACHE_HEADER;
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Public interface |
|
||
|
//+------------------------------------------------------------------+
|
||
|
|
||
|
CalendarCache(const string _context = NULL,
|
||
|
const datetime _from = 0, const datetime _to = 0):
|
||
|
context(_context), from(_from), to(_to), t(0), eventId(0)
|
||
|
{
|
||
|
if(from > to) // context is a filename 'flag'
|
||
|
{
|
||
|
if(!load(_context))
|
||
|
{
|
||
|
load(_context, FILE_COMMON);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(!update() || !hash() || !bind())
|
||
|
{
|
||
|
t = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
string getContext() const
|
||
|
{
|
||
|
return context;
|
||
|
}
|
||
|
|
||
|
datetime getFrom() const
|
||
|
{
|
||
|
return from;
|
||
|
}
|
||
|
|
||
|
datetime getTo() const
|
||
|
{
|
||
|
return to;
|
||
|
}
|
||
|
|
||
|
bool isLoaded() const
|
||
|
{
|
||
|
return t != 0;
|
||
|
}
|
||
|
|
||
|
static ulong currencyId(const string s)
|
||
|
{
|
||
|
union CRNC4
|
||
|
{
|
||
|
ushort word[4];
|
||
|
ulong ul;
|
||
|
} v;
|
||
|
|
||
|
StringToShortArray(s, v.word);
|
||
|
return v.ul;
|
||
|
}
|
||
|
|
||
|
template<typename T1,typename T2>
|
||
|
void static store(T1 &array[], T2 &origin[])
|
||
|
{
|
||
|
ArrayResize(array, ArraySize(origin));
|
||
|
for(int i = 0; i < ArraySize(origin); ++i)
|
||
|
{
|
||
|
array[i] = origin[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template<typename T1,typename T2>
|
||
|
void static restore(T1 &array[], T2 &origin[])
|
||
|
{
|
||
|
ArrayResize(array, ArraySize(origin));
|
||
|
for(int i = 0; i < ArraySize(origin); ++i)
|
||
|
{
|
||
|
array[i] = origin[i][];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct MqlCalendarCountryRef
|
||
|
{
|
||
|
ulong id;
|
||
|
StringRef<MqlCalendarCountry> name;
|
||
|
StringRef<MqlCalendarCountry> code;
|
||
|
StringRef<MqlCalendarCountry> currency;
|
||
|
StringRef<MqlCalendarCountry> currency_symbol;
|
||
|
StringRef<MqlCalendarCountry> url_name;
|
||
|
|
||
|
void operator=(const MqlCalendarCountry &c)
|
||
|
{
|
||
|
id = c.id;
|
||
|
name = c.name;
|
||
|
code = c.code;
|
||
|
currency = c.currency;
|
||
|
currency_symbol = c.currency_symbol;
|
||
|
url_name = c.url_name;
|
||
|
}
|
||
|
|
||
|
MqlCalendarCountry operator[](int x = 0) const
|
||
|
{
|
||
|
MqlCalendarCountry r;
|
||
|
r.id = id;
|
||
|
r.name = name[];
|
||
|
r.code = code[];
|
||
|
r.currency = currency[];
|
||
|
r.currency_symbol = currency_symbol[];
|
||
|
r.url_name = url_name[];
|
||
|
return r;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
struct MqlCalendarEventRef
|
||
|
{
|
||
|
ulong id;
|
||
|
ENUM_CALENDAR_EVENT_TYPE type;
|
||
|
ENUM_CALENDAR_EVENT_SECTOR sector;
|
||
|
ENUM_CALENDAR_EVENT_FREQUENCY frequency;
|
||
|
ENUM_CALENDAR_EVENT_TIMEMODE time_mode;
|
||
|
ulong country_id;
|
||
|
ENUM_CALENDAR_EVENT_UNIT unit;
|
||
|
ENUM_CALENDAR_EVENT_IMPORTANCE importance;
|
||
|
ENUM_CALENDAR_EVENT_MULTIPLIER multiplier;
|
||
|
uint digits;
|
||
|
StringRef<MqlCalendarEvent> source_url;
|
||
|
StringRef<MqlCalendarEvent> event_code;
|
||
|
StringRef<MqlCalendarEvent> name;
|
||
|
|
||
|
void operator=(const MqlCalendarEvent &e)
|
||
|
{
|
||
|
id = e.id;
|
||
|
type = e.type;
|
||
|
sector = e.sector;
|
||
|
frequency = e.frequency;
|
||
|
time_mode = e.time_mode;
|
||
|
country_id = e.country_id;
|
||
|
unit = e.unit;
|
||
|
importance = e.importance;
|
||
|
multiplier = e.multiplier;
|
||
|
digits = e.digits;
|
||
|
source_url = e.source_url;
|
||
|
event_code = e.event_code;
|
||
|
name = e.name;
|
||
|
}
|
||
|
|
||
|
MqlCalendarEvent operator[](int x = 0) const
|
||
|
{
|
||
|
MqlCalendarEvent r;
|
||
|
r.id = id;
|
||
|
r.type = type;
|
||
|
r.sector = sector;
|
||
|
r.frequency = frequency;
|
||
|
r.time_mode = time_mode;
|
||
|
r.country_id = country_id;
|
||
|
r.unit = unit;
|
||
|
r.importance = importance;
|
||
|
r.multiplier = multiplier;
|
||
|
r.digits = digits;
|
||
|
r.source_url = source_url[];
|
||
|
r.event_code = event_code[];
|
||
|
r.name = name[];
|
||
|
return r;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
bool save(string filename = NULL, const int flags = 0)
|
||
|
{
|
||
|
if(!t) return false;
|
||
|
|
||
|
MqlDateTime mdt;
|
||
|
TimeToStruct(t, mdt);
|
||
|
if(filename == NULL) filename = "calendar-" +
|
||
|
StringFormat("%04d-%02d-%02d-%02d-%02d.cal",
|
||
|
mdt.year, mdt.mon, mdt.day, mdt.hour, mdt.min);
|
||
|
int handle = PRTF(FileOpen(filename, FILE_WRITE | FILE_BIN | flags));
|
||
|
if(handle == INVALID_HANDLE) return false;
|
||
|
|
||
|
FileWriteString(handle, CALENDAR_CACHE_HEADER);
|
||
|
FileWriteString(handle, context, 4);
|
||
|
FileWriteLong(handle, from);
|
||
|
FileWriteLong(handle, to);
|
||
|
FileWriteLong(handle, t);
|
||
|
const int n = ArraySize(values);
|
||
|
FileWriteInteger(handle, n);
|
||
|
if(n > 0)
|
||
|
{
|
||
|
FileWriteArray(handle, values);
|
||
|
Print("First and last records: ", values[0].time, "-", values[n - 1].time);
|
||
|
}
|
||
|
|
||
|
MqlCalendarEventRef erefs[];
|
||
|
store(erefs, events);
|
||
|
FileWriteInteger(handle, ArraySize(erefs));
|
||
|
FileWriteArray(handle, erefs);
|
||
|
StringRef<MqlCalendarEvent>::save(handle);
|
||
|
|
||
|
MqlCalendarCountryRef crefs[];
|
||
|
store(crefs, countries);
|
||
|
FileWriteInteger(handle, ArraySize(crefs));
|
||
|
FileWriteArray(handle, crefs);
|
||
|
StringRef<MqlCalendarCountry>::save(handle);
|
||
|
|
||
|
FileClose(handle);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool load(const string filename, const int flags = 0)
|
||
|
{
|
||
|
Print("Loading calendar cache ", filename);
|
||
|
t = 0;
|
||
|
int handle = PRTF(FileOpen(filename, FILE_READ | FILE_BIN | flags));
|
||
|
if(handle == INVALID_HANDLE) return false;
|
||
|
|
||
|
const string header = FileReadString(handle, StringLen(CALENDAR_CACHE_HEADER));
|
||
|
if(header != CALENDAR_CACHE_HEADER) return false;
|
||
|
|
||
|
reset();
|
||
|
ResetLastError();
|
||
|
|
||
|
context = FileReadString(handle, 4);
|
||
|
if(!StringLen(context)) context = NULL;
|
||
|
from = (datetime)FileReadLong(handle);
|
||
|
to = (datetime)FileReadLong(handle);
|
||
|
t = (datetime)FileReadLong(handle);
|
||
|
Print("Calendar cache interval: ", from, "-", to);
|
||
|
Print("Calendar cache saved at: ", t);
|
||
|
int n = FileReadInteger(handle);
|
||
|
if(n > 0)
|
||
|
{
|
||
|
FileReadArray(handle, values, 0, n);
|
||
|
Print("First and last records: ", values[0].time, "-", values[n - 1].time);
|
||
|
}
|
||
|
|
||
|
MqlCalendarEventRef erefs[];
|
||
|
n = FileReadInteger(handle);
|
||
|
FileReadArray(handle, erefs, 0, n);
|
||
|
StringRef<MqlCalendarEvent>::load(handle);
|
||
|
restore(events, erefs);
|
||
|
|
||
|
MqlCalendarCountryRef crefs[];
|
||
|
n = FileReadInteger(handle);
|
||
|
FileReadArray(handle, crefs, 0, n);
|
||
|
StringRef<MqlCalendarCountry>::load(handle);
|
||
|
restore(countries, crefs);
|
||
|
|
||
|
FileClose(handle);
|
||
|
|
||
|
if(_LastError) Print("Error in load: ", _LastError);
|
||
|
|
||
|
const bool result = hash() && bind();
|
||
|
if(!result) t = 0;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Methods to emulate standard calendar API |
|
||
|
//+------------------------------------------------------------------+
|
||
|
|
||
|
bool calendarCountryById(ulong country_id, MqlCalendarCountry &cnt)
|
||
|
{
|
||
|
const int index = find(country_id, id4country, countries);
|
||
|
if(index == -1) return false;
|
||
|
|
||
|
cnt = countries[index];
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool calendarEventById(ulong event_id, MqlCalendarEvent &event)
|
||
|
{
|
||
|
const int index = find(event_id, id4event, events);
|
||
|
if(index == -1) return false;
|
||
|
|
||
|
event = events[index];
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static int ArrayBlowerBound(const ulong &array[][2], const ulong value, const int index)
|
||
|
{
|
||
|
if(index >= ArrayRange(array, 0)) return false;
|
||
|
if(array[index][0] != value) return index; // no exact match
|
||
|
for(int i = index - 1; i >= 0; --i)
|
||
|
{
|
||
|
if(array[i][0] != value) return i + 1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int calendarValueHistoryByEvent(ulong event_id, MqlCalendarValue &temp[],
|
||
|
datetime _from, datetime _to = 0)
|
||
|
{
|
||
|
if(_to == 0) _to = LONG_MAX;
|
||
|
ArrayFree(temp);
|
||
|
|
||
|
const int index = ArrayBsearch(value2event, event_id);
|
||
|
if(index < 0 || index >= ArrayRange(value2event, 0)) return 0;
|
||
|
int i = ArrayBlowerBound(value2event, event_id, index);
|
||
|
|
||
|
while(value2event[i][0] == event_id)
|
||
|
{
|
||
|
const ulong value_id = value2event[i][1];
|
||
|
const int p = find(value_id, id4value, values);
|
||
|
if(p != -1 && values[p].time >= _from && values[p].time < _to)
|
||
|
{
|
||
|
PUSH(temp, values[p]);
|
||
|
}
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
if(ArraySize(temp) > 0)
|
||
|
{
|
||
|
SORT_STRUCT(MqlCalendarValue, temp, time);
|
||
|
}
|
||
|
|
||
|
return ArraySize(temp);
|
||
|
}
|
||
|
|
||
|
int calendarValueHistory(MqlCalendarValue &temp[],
|
||
|
datetime _from, datetime _to = 0,
|
||
|
const string _code = NULL, const string _coin = NULL)
|
||
|
{
|
||
|
if(_to == 0) _to = LONG_MAX;
|
||
|
ArrayFree(temp);
|
||
|
|
||
|
ulong country_id = 0;
|
||
|
ulong currency_id = _coin != NULL ? currencyId(_coin) : 0;
|
||
|
|
||
|
if(_code != NULL)
|
||
|
{
|
||
|
for(int i = 0; i < ArraySize(countries); ++i)
|
||
|
{
|
||
|
if(countries[i].code == _code)
|
||
|
{
|
||
|
country_id = countries[i].id;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NB1: country and currency are considered more narrow filters than time,
|
||
|
// hence try to apply them in first place. This is debatable.
|
||
|
// NB2: if we manage to load actual times of changes into the cache,
|
||
|
// then selection by from/to should be applied to these times,
|
||
|
// instead of values[p].time
|
||
|
|
||
|
if(country_id)
|
||
|
{
|
||
|
const int index = ArrayBsearch(value2country, country_id);
|
||
|
if(index < 0 || index >= ArrayRange(value2country, 0)) return 0;
|
||
|
if(value2country[index][0] != country_id) return 0;
|
||
|
|
||
|
int i = ArrayBlowerBound(value2country, country_id, index);
|
||
|
while(value2country[i][0] == country_id)
|
||
|
{
|
||
|
const ulong value_id = value2country[i][1];
|
||
|
const int p = find(value_id, id4value, values);
|
||
|
if(p != -1 && values[p].time >= _from && values[p].time < _to)
|
||
|
{
|
||
|
PUSH(temp, values[p]);
|
||
|
}
|
||
|
i++;
|
||
|
}
|
||
|
}
|
||
|
else if(currency_id)
|
||
|
{
|
||
|
const int index = ArrayBsearch(value2currency, currency_id);
|
||
|
if(index < 0 || index >= ArrayRange(value2currency, 0)) return 0;
|
||
|
if(value2currency[index][0] != currency_id) return 0;
|
||
|
|
||
|
int i = ArrayBlowerBound(value2currency, currency_id, index);
|
||
|
while(value2currency[i][0] == currency_id)
|
||
|
{
|
||
|
const ulong value_id = value2currency[i][1];
|
||
|
const int p = find(value_id, id4value, values);
|
||
|
if(p != -1 && values[p].time >= _from && values[p].time < _to)
|
||
|
{
|
||
|
PUSH(temp, values[p]);
|
||
|
}
|
||
|
i++;
|
||
|
}
|
||
|
}
|
||
|
else if(_from) // no filters, only start and end time (optional)
|
||
|
{
|
||
|
const int index = ArrayBsearch(value2time, _from);
|
||
|
if(index < 0 || index >= ArrayRange(value2time, 0)) return 0;
|
||
|
|
||
|
int i = ArrayBlowerBound(value2time, value2time[index][0], index);
|
||
|
while(value2time[i][0] < (ulong)_from && i < ArrayRange(value2time, 0)) ++i;
|
||
|
|
||
|
if(i >= ArrayRange(value2time, 0)) return 0;
|
||
|
|
||
|
for(int j = i; j < ArrayRange(value2time, 0) && value2time[j][0] < (ulong)_to; ++j)
|
||
|
{
|
||
|
const int p = find(value2time[j][1], id4value, values);
|
||
|
if(p != -1)
|
||
|
{
|
||
|
PUSH(temp, values[p]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if(_to != LONG_MAX) // no filters, only end time
|
||
|
{
|
||
|
const int index = ArrayBsearch(value2time, _to);
|
||
|
if(index < 0 || index >= ArrayRange(value2time, 0)) return 0;
|
||
|
|
||
|
int i = ArrayBlowerBound(value2time, value2time[index][0], index);
|
||
|
while(value2time[i][0] >= (ulong)_to && i > 0) --i;
|
||
|
|
||
|
for(int j = 0; j <= i; ++j)
|
||
|
{
|
||
|
const int p = find(value2time[j][1], id4value, values);
|
||
|
if(p != -1)
|
||
|
{
|
||
|
PUSH(temp, values[p]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ArrayCopy(temp, values);
|
||
|
}
|
||
|
|
||
|
if(ArraySize(temp) > 0)
|
||
|
{
|
||
|
SORT_STRUCT(MqlCalendarValue, temp, time);
|
||
|
}
|
||
|
|
||
|
return ArraySize(temp);
|
||
|
}
|
||
|
|
||
|
int calendarValueLast(ulong &change, MqlCalendarValue &result[],
|
||
|
const string code = NULL, const string currency = NULL)
|
||
|
{
|
||
|
/*
|
||
|
|
||
|
straightforward equivalent is shown in this comment, but
|
||
|
it's too slow for requests on short latest periods of time
|
||
|
|
||
|
const int n = change ? calendarValueHistory(result,
|
||
|
change, TimeTradeServer(), code, currency) : 0;
|
||
|
change = TimeTradeServer();
|
||
|
return n;
|
||
|
*/
|
||
|
|
||
|
if(!change)
|
||
|
{
|
||
|
change = TimeTradeServer();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ulong country_id = 0;
|
||
|
ulong currency_id = currency != NULL ? currencyId(currency) : 0;
|
||
|
|
||
|
if(code != NULL)
|
||
|
{
|
||
|
for(int i = 0; i < ArraySize(countries); ++i)
|
||
|
{
|
||
|
if(countries[i].code == code)
|
||
|
{
|
||
|
country_id = countries[i].id;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const ulong past = change;
|
||
|
const int index = ArrayBsearch(value2time, past);
|
||
|
if(index < 0 || index >= ArrayRange(value2time, 0)) return 0;
|
||
|
|
||
|
int i = ArrayBlowerBound(value2time, value2time[index][0], index);
|
||
|
while(value2time[i][0] <= (ulong)past && i < ArrayRange(value2time, 0)) ++i;
|
||
|
|
||
|
if(i >= ArrayRange(value2time, 0)) return 0;
|
||
|
|
||
|
for(int j = i; j < ArrayRange(value2time, 0) && value2time[j][0] <= (ulong)TimeTradeServer(); ++j)
|
||
|
{
|
||
|
const int p = find(value2time[j][1], id4value, values);
|
||
|
if(p != -1)
|
||
|
{
|
||
|
change = TimeTradeServer();
|
||
|
if(country_id != 0 || currency_id != 0)
|
||
|
{
|
||
|
const int q = find(values[p].event_id, id4event, events);
|
||
|
if(country_id != 0 && country_id != events[q].country_id) continue;
|
||
|
if(currency_id != 0)
|
||
|
{
|
||
|
const int m = find(events[q].country_id, id4country, countries);
|
||
|
if(countries[m].currency != currency) continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!eventId || eventId == values[p].event_id)
|
||
|
{
|
||
|
PUSH(result, values[p]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ArraySize(result);
|
||
|
}
|
||
|
|
||
|
int calendarValueLastByEvent(ulong event_id, ulong &change,
|
||
|
MqlCalendarValue &result[])
|
||
|
{
|
||
|
eventId = event_id; // enable internal filtering by event id for the next call
|
||
|
const int n = calendarValueLast(change, result);
|
||
|
change = TimeTradeServer();
|
||
|
eventId = 0;
|
||
|
return n;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const static string CalendarCache::CALENDAR_CACHE_HEADER = "MQL5 Calendar Cache\r\nv.1.0\r\n";
|
||
|
//+------------------------------------------------------------------+
|