forked from rosh/MQL5Book
919 lines
28 KiB
MQL5
919 lines
28 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| CalendarFilter.mqh |
| |||
//| Copyright 2022, MetaQuotes Ltd. |
| |||
//| https://www.mql5.com |
| |||
//+------------------------------------------------------------------+
| |||
| |||
/*
| |||
| |||
Filters:
| |||
| |||
event ID
| |||
country code
| |||
currency code
| |||
type
| |||
sector
| |||
importance
| |||
date range (day/week/month/quarter in past/future)
| |||
has (actual, forecast, previous, revised)
| |||
event name full-text
| |||
other properties
| |||
| |||
May be applied multiple per property via OR/AND with EQUAL, NOT_EQUAL, GREATER conditions
| |||
| |||
examples:
| |||
CalendarFilter f;
| |||
f.let(CALENDAR_IMPORTANCE_MODERATE, GREATER) // high priority news
| |||
.let(CALENDAR_IMPACT_POSITIVE) // only with better actual value than expected
| |||
.let(CALENDAR_TIMEMODE_DATETIME) // only events with exact timing
| |||
.let("DE").let("FR") // collect 2 countries, OR...
| |||
.let("USD").let("GBP") // ...collect 2 currencies (but do not combine these 2 conditions!)
| |||
.let(TimeCurrent() - MONTH_LONG, TimeCurrent() + WEEK_LONG) // date range
| |||
.let("farm"); // full-text search
| |||
| |||
MqlCalendarValue records[];
| |||
f.select(records);
| |||
| |||
*/
| |||
| |||
#include "Defines.mqh"
| |||
#include "IS.mqh"
| |||
#include "CalendarDefines.mqh"
| |||
#include "QuickSortStructT.mqh"
| |||
| |||
#ifdef LOGGING
| |||
#include "PRTF.mqh"
| |||
#else
| |||
#define PRTF
| |||
#endif
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Calendar filter class |
| |||
//+------------------------------------------------------------------+
| |||
class CalendarFilter
| |||
{
| |||
protected:
| |||
// initial (optional) selectors passed via ctor, invariants
| |||
string context;
| |||
datetime from, to;
| |||
bool fixedDates; // true if 'from'/'to' assigned in ctor and can't be changed
| |||
| |||
// common selectors for event fields, assigned via let-methods
| |||
long selectors[][3]; // [0] - property, [1] - value, [2] - condition
| |||
string stringCache[]; // cache of all strings in 'selectors'
| |||
| |||
// specific selectors (country/currency/id/change)
| |||
ulong change; // for last changes requests
| |||
string country[], currency[];
| |||
ulong ids[]; // kinds of events
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Block of virtual methods for calendar access |
| |||
//+------------------------------------------------------------------+
| |||
| |||
virtual bool calendarCountryById(ulong country_id, MqlCalendarCountry &cnt)
| |||
{
| |||
return CalendarCountryById(country_id, cnt);
| |||
}
| |||
| |||
virtual bool calendarEventById(ulong event_id, MqlCalendarEvent &event)
| |||
{
| |||
return CalendarEventById(event_id, event);
| |||
}
| |||
| |||
virtual int calendarValueHistoryByEvent(ulong event_id, MqlCalendarValue &temp[],
| |||
datetime _from, datetime _to = 0)
| |||
{
| |||
return CalendarValueHistoryByEvent(event_id, temp, _from, _to);
| |||
}
| |||
| |||
virtual int calendarValueHistory(MqlCalendarValue &temp[],
| |||
datetime _from, datetime _to = 0,
| |||
const string _code = NULL, const string _coin = NULL)
| |||
{
| |||
return CalendarValueHistory(temp, _from, _to, _code, _coin);
| |||
}
| |||
| |||
virtual int calendarValueLast(ulong &_change, MqlCalendarValue &result[],
| |||
const string _code = NULL, const string _coin = NULL)
| |||
{
| |||
return CalendarValueLast(_change, result, _code, _coin);
| |||
}
| |||
| |||
virtual int calendarValueLastByEvent(ulong event_id, ulong &_change, MqlCalendarValue &result[])
| |||
{
| |||
return CalendarValueLastByEvent(event_id, _change, result);
| |||
}
| |||
| |||
virtual void init()
| |||
{
| |||
fixedDates = from != 0 || to != 0;
| |||
change = 0;
| |||
if(StringLen(context) == 3)
| |||
{
| |||
PUSH(currency, context);
| |||
}
| |||
else
| |||
{
| |||
// even if context is NULL, we save it to request entire calendar
| |||
PUSH(country, context);
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Block of overloads to resolve specific ENUM into PROPERTY |
| |||
//+------------------------------------------------------------------+
| |||
| |||
ENUM_CALENDAR_PROPERTY resolve(const ENUM_CALENDAR_EVENT_TYPE e)
| |||
{
| |||
return CALENDAR_PROPERTY_EVENT_TYPE;
| |||
}
| |||
| |||
ENUM_CALENDAR_PROPERTY resolve(const ENUM_CALENDAR_EVENT_SECTOR e)
| |||
{
| |||
return CALENDAR_PROPERTY_EVENT_SECTOR;
| |||
}
| |||
| |||
ENUM_CALENDAR_PROPERTY resolve(const ENUM_CALENDAR_EVENT_FREQUENCY e)
| |||
{
| |||
return CALENDAR_PROPERTY_EVENT_FREQUENCY;
| |||
}
| |||
| |||
ENUM_CALENDAR_PROPERTY resolve(const ENUM_CALENDAR_EVENT_TIMEMODE e)
| |||
{
| |||
return CALENDAR_PROPERTY_EVENT_TIMEMODE;
| |||
}
| |||
| |||
ENUM_CALENDAR_PROPERTY resolve(const ENUM_CALENDAR_EVENT_UNIT e)
| |||
{
| |||
return CALENDAR_PROPERTY_EVENT_UNIT;
| |||
}
| |||
| |||
ENUM_CALENDAR_PROPERTY resolve(const ENUM_CALENDAR_EVENT_IMPORTANCE e)
| |||
{
| |||
return CALENDAR_PROPERTY_EVENT_IMPORTANCE;
| |||
}
| |||
| |||
ENUM_CALENDAR_PROPERTY resolve(const ENUM_CALENDAR_EVENT_MULTIPLIER e)
| |||
{
| |||
return CALENDAR_PROPERTY_EVENT_MULTIPLIER;
| |||
}
| |||
| |||
ENUM_CALENDAR_PROPERTY resolve(const ENUM_CALENDAR_EVENT_IMPACT e)
| |||
{
| |||
return CALENDAR_PROPERTY_RECORD_IMPACT;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Comparison methods for scalars of different prop types |
| |||
//+------------------------------------------------------------------+
| |||
| |||
template<typename V>
| |||
static bool equal(const V v1, const V v2)
| |||
{
| |||
return v1 == v2;
| |||
}
| |||
| |||
template<typename V>
| |||
static bool greater(const V v1, const V v2)
| |||
{
| |||
return v1 > v2;
| |||
}
| |||
| |||
static bool equal(const string v1, const string v2)
| |||
{
| |||
if(StringFind(v2, "*") > -1)
| |||
{
| |||
int previous = 0;
| |||
string words[];
| |||
const int n = StringSplit(v2, '*', words);
| |||
for(int i = 0; i < n; ++i)
| |||
{
| |||
if(StringLen(words[i]) == 0) continue;
| |||
int index = StringFind(v1, words[i], previous);
| |||
if(index == -1)
| |||
{
| |||
return false;
| |||
}
| |||
previous = index + StringLen(words[i]);
| |||
}
| |||
return true;
| |||
}
| |||
else if(v2[0] == '\'' && v2[StringLen(v2) - 1] == '\'')
| |||
{
| |||
return v1 == v2;
| |||
}
| |||
return StringFind(v1, v2) > -1;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Internal filtering stuff (condition checks) |
| |||
//+------------------------------------------------------------------+
| |||
| |||
void filter(MqlCalendarValue &result[])
| |||
{
| |||
#ifdef LOGGING
| |||
PrintFormat("Filtering %d records", ArraySize(result));
| |||
#endif
| |||
for(int i = ArraySize(result) - 1; i >= 0; --i)
| |||
{
| |||
if(!match(result[i]))
| |||
{
| |||
ArrayRemove(result, i, 1);
| |||
}
| |||
}
| |||
}
| |||
| |||
bool match(const MqlCalendarValue &v)
| |||
{
| |||
int or_totals = 0, or_matches = 0;
| |||
| |||
MqlCalendarEvent event;
| |||
if(!calendarEventById(v.event_id, event)) return false;
| |||
| |||
for(int j = 0; j < ArrayRange(selectors, 0); ++j)
| |||
{
| |||
// retrieve selector value
| |||
long field = 0;
| |||
string text = NULL;
| |||
| |||
switch((int)selectors[j][0])
| |||
{
| |||
// NB: multiple countries or currencies are supported by
| |||
// multiple calls to Calendar-functions, so records presented to 'match'
| |||
// are already selected according to the filter
| |||
// CALENDAR_PROPERTY_COUNTRY_CODE, // +string (2 chars)
| |||
// CALENDAR_PROPERTY_COUNTRY_CURRENCY, // +string (3 chars)
| |||
case CALENDAR_PROPERTY_EVENT_ID:
| |||
field = (long)event.id;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_TYPE:
| |||
field = event.type;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_SECTOR:
| |||
field = event.sector;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_FREQUENCY:
| |||
field = event.frequency;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_TIMEMODE:
| |||
field = event.time_mode;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_UNIT:
| |||
field = event.unit;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_IMPORTANCE:
| |||
field = event.importance;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_MULTIPLIER:
| |||
field = event.multiplier;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_SOURCE:
| |||
text = event.source_url;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_NAME:
| |||
text = event.name;
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_REVISION:
| |||
field = v.revision;
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_IMPACT:
| |||
field = v.impact_type;
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_ACTUAL:
| |||
field = v.actual_value;
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_PREVIOUS:
| |||
field = v.prev_value;
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_REVISED:
| |||
field = v.revised_prev_value;
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_PREVISED:
| |||
field = v.revised_prev_value != LONG_MIN ? v.revised_prev_value : v.prev_value;
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_FORECAST:
| |||
field = v.forecast_value;
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_PERIOD:
| |||
field = v.period;
| |||
break;
| |||
}
| |||
| |||
// compare the value with the record
| |||
if(text == NULL)
| |||
{
| |||
switch((IS)selectors[j][2])
| |||
{
| |||
case EQUAL:
| |||
if(!equal(field, selectors[j][1])) return false;
| |||
break;
| |||
case NOT_EQUAL:
| |||
if(equal(field, selectors[j][1])) return false;
| |||
break;
| |||
case GREATER:
| |||
if(!greater(field, selectors[j][1])) return false;
| |||
break;
| |||
case LESS:
| |||
if(greater(field, selectors[j][1])) return false;
| |||
break;
| |||
case OR_EQUAL:
| |||
or_totals++;
| |||
if(equal(field, selectors[j][1])) or_matches++;
| |||
break;
| |||
}
| |||
}
| |||
else
| |||
{
| |||
const string find = stringCache[(int)selectors[j][1]];
| |||
switch((IS)selectors[j][2])
| |||
{
| |||
case EQUAL:
| |||
if(!equal(text, find)) return false;
| |||
break;
| |||
case NOT_EQUAL:
| |||
if(equal(text, find)) return false;
| |||
break;
| |||
case GREATER:
| |||
if(!greater(text, find)) return false;
| |||
break;
| |||
case LESS:
| |||
if(greater(text, find)) return false;
| |||
break;
| |||
case OR_EQUAL:
| |||
or_totals++;
| |||
if(equal(text, find)) or_matches++;
| |||
break;
| |||
}
| |||
}
| |||
}
| |||
| |||
if(or_totals > 0) return or_matches > 0;
| |||
| |||
return true;
| |||
}
| |||
| |||
int insertDelimiter(MqlCalendarValue &result[])
| |||
{
| |||
static const MqlCalendarValue empty[1] = {};
| |||
for(int i = 1; i < ArraySize(result); ++i)
| |||
{
| |||
if(result[i].time > TimeTradeServer() && result[i - 1].time <= TimeTradeServer())
| |||
{
| |||
ArrayInsert(result, empty, i);
| |||
return i;
| |||
}
| |||
}
| |||
return -1;
| |||
}
| |||
| |||
public:
| |||
CalendarFilter(const string _context = NULL,
| |||
const datetime _from = 0, const datetime _to = 0):
| |||
context(_context), from(_from), to(_to)
| |||
{
| |||
init();
| |||
}
| |||
| |||
virtual bool isLoaded() const
| |||
{
| |||
return true; // will be overriden in descendants
| |||
}
| |||
| |||
void describe() const
| |||
{
| |||
Print("-= Calendar filter description =-");
| |||
Print("Dates: ", from, "-", to);
| |||
Print("Countries:");
| |||
ArrayPrint(country);
| |||
Print("Currencies:");
| |||
ArrayPrint(currency);
| |||
Print("Event IDs:");
| |||
ArrayPrint(ids);
| |||
Print("Selectors:");
| |||
ArrayPrint(selectors);
| |||
Print("Strings:");
| |||
ArrayPrint(stringCache);
| |||
}
| |||
| |||
template<typename E>
| |||
static string stringify(const E e)
| |||
{
| |||
string result = EnumToString(e);
| |||
string words[];
| |||
if(StringSplit(result, '_', words) > 0)
| |||
{
| |||
return words[ArraySize(words) - 1];
| |||
}
| |||
return result;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Block of let-methods to setup conditions on event properties |
| |||
//+------------------------------------------------------------------+
| |||
| |||
CalendarFilter *let(const int r, const IS c = EQUAL)
| |||
{
| |||
const int n = EXPAND(selectors);
| |||
selectors[n][0] = CALENDAR_PROPERTY_RECORD_REVISION;
| |||
selectors[n][1] = r;
| |||
selectors[n][2] = c;
| |||
return &this;
| |||
}
| |||
| |||
CalendarFilter *let(const ulong event)
| |||
{
| |||
PUSH(ids, event);
| |||
return &this;
| |||
}
| |||
| |||
CalendarFilter *let(const long value, const ENUM_CALENDAR_PROPERTY property,
| |||
const IS c = EQUAL)
| |||
{
| |||
/*
| |||
fields covered by this overload:
| |||
CALENDAR_PROPERTY_RECORD_PERIOD,
| |||
CALENDAR_PROPERTY_RECORD_ACTUAL,
| |||
CALENDAR_PROPERTY_RECORD_PREVIOUS,
| |||
CALENDAR_PROPERTY_RECORD_REVISED,
| |||
CALENDAR_PROPERTY_RECORD_FORECAST,
| |||
*/
| |||
const int n = EXPAND(selectors);
| |||
selectors[n][0] = property;
| |||
selectors[n][1] = value;
| |||
selectors[n][2] = c;
| |||
return &this;
| |||
}
| |||
| |||
template<typename E>
| |||
CalendarFilter *let(const E e, const IS c = EQUAL)
| |||
{
| |||
// enumerations are processed here
| |||
const int n = EXPAND(selectors);
| |||
selectors[n][0] = resolve(e);
| |||
selectors[n][1] = e;
| |||
selectors[n][2] = c;
| |||
return &this;
| |||
}
| |||
| |||
CalendarFilter *let(const datetime _from, const datetime _to = 0)
| |||
{
| |||
if(!fixedDates) // we can narrow down complete date range,
| |||
{ // but do not change it if already specified in ctor
| |||
from = _from;
| |||
to = _to;
| |||
}
| |||
else
| |||
{
| |||
PrintFormat("A limited date range %s-%s was cached in filter constructor,"
| |||
" can't change it ad hoc", (string)from, (string)to);
| |||
}
| |||
return &this;
| |||
}
| |||
| |||
CalendarFilter *let(const string find, const IS c = EQUAL)
| |||
{
| |||
const int wildcard = (StringFind(find, "*") + 1) * 10;
| |||
switch(StringLen(find) + wildcard)
| |||
{
| |||
case 0:
| |||
case 1:
| |||
break;
| |||
case 2:
| |||
// if initial context was other than country, we can mix it with a country
| |||
if(StringLen(context) != 2)
| |||
{
| |||
if(ArraySize(country) == 1 && StringLen(country[0]) == 0) // narrowing all countries to specific one
| |||
{
| |||
country[0] = find;
| |||
}
| |||
else
| |||
{
| |||
PUSH(country, find);
| |||
}
| |||
}
| |||
else
| |||
{
| |||
PrintFormat("Specific country '%s' was cached in filter constructor,"
| |||
" can't change it ad hoc", context);
| |||
}
| |||
break;
| |||
case 3:
| |||
if(StringLen(context) != 3) // initially selected currency can not be changed
| |||
{
| |||
PUSH(currency, find);
| |||
}
| |||
else
| |||
{
| |||
PrintFormat("Specific currency '%s' was cached in filter constructor,"
| |||
" can't change it ad hoc", context);
| |||
}
| |||
break;
| |||
default:
| |||
{
| |||
const int n = EXPAND(selectors);
| |||
PUSH(stringCache, find);
| |||
if(StringFind(find, "http://") == 0 || StringFind(find, "https://") == 0)
| |||
{
| |||
selectors[n][0] = CALENDAR_PROPERTY_EVENT_SOURCE;
| |||
}
| |||
else
| |||
{
| |||
selectors[n][0] = CALENDAR_PROPERTY_EVENT_NAME;
| |||
}
| |||
selectors[n][1] = ArraySize(stringCache) - 1;
| |||
selectors[n][2] = c;
| |||
break;
| |||
}
| |||
}
| |||
| |||
return &this;
| |||
}
| |||
| |||
void reset()
| |||
{
| |||
ArrayFree(selectors);
| |||
ArrayFree(stringCache);
| |||
ArrayFree(ids);
| |||
ArrayFree(country);
| |||
ArrayFree(currency);
| |||
if(context != NULL)
| |||
{
| |||
if(StringLen(context) == 3) PUSH(currency, context);
| |||
else PUSH(country, context);
| |||
}
| |||
if(!fixedDates)
| |||
{
| |||
from = to = 0;
| |||
}
| |||
change = 0;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Main public methods |
| |||
//+------------------------------------------------------------------+
| |||
| |||
bool select(MqlCalendarValue &result[], const bool delimiter = false, const int limit = -1)
| |||
{
| |||
int count = 0;
| |||
#ifdef LOGGING
| |||
Print("Selecting calendar records... (now:", TimeCurrent(), ", server:", TimeTradeServer(), ")");
| |||
#endif
| |||
ArrayFree(result);
| |||
if(ArraySize(ids)) // some kinds of events specified
| |||
{
| |||
for(int i = 0; i < ArraySize(ids); ++i)
| |||
{
| |||
#ifdef LOGGING
| |||
PRTF(ids[i]);
| |||
#endif
| |||
MqlCalendarValue temp[];
| |||
if(PRTF(calendarValueHistoryByEvent(ids[i], temp, from, to)))
| |||
{
| |||
ArrayCopy(result, temp, ArraySize(result));
| |||
++count;
| |||
}
| |||
}
| |||
}
| |||
else
| |||
{
| |||
// many countries or currencies
| |||
if(ArraySize(country) > ArraySize(currency))
| |||
{
| |||
const string c = ArraySize(currency) > 0 ? currency[0] : NULL;
| |||
for(int i = 0; i < ArraySize(country); ++i)
| |||
{
| |||
#ifdef LOGGING
| |||
PRTF(country[i]);
| |||
#endif
| |||
MqlCalendarValue temp[];
| |||
if(PRTF(calendarValueHistory(temp, from, to, country[i], c)))
| |||
{
| |||
ArrayCopy(result, temp, ArraySize(result));
| |||
++count;
| |||
}
| |||
}
| |||
}
| |||
else
| |||
{
| |||
const string c = ArraySize(country) > 0 ? country[0] : NULL;
| |||
for(int i = 0; i < ArraySize(currency); ++i)
| |||
{
| |||
#ifdef LOGGING
| |||
PRTF(currency[i]);
| |||
#endif
| |||
MqlCalendarValue temp[];
| |||
if(PRTF(calendarValueHistory(temp, from, to, c, currency[i])))
| |||
{
| |||
ArrayCopy(result, temp, ArraySize(result));
| |||
++count;
| |||
}
| |||
}
| |||
}
| |||
}
| |||
| |||
// get current change_id
| |||
change = 0;
| |||
MqlCalendarValue dummy[];
| |||
calendarValueLast(change, dummy);
| |||
| |||
if(ArraySize(result) > 0)
| |||
{
| |||
filter(result);
| |||
}
| |||
| |||
if(count > 1 && ArraySize(result) > 1)
| |||
{
| |||
#ifdef LOGGING
| |||
PrintFormat("Sorting %d records by time", ArraySize(result));
| |||
#endif
| |||
SORT_STRUCT(MqlCalendarValue, result, time);
| |||
}
| |||
| |||
if(ArraySize(result) > 1)
| |||
{
| |||
int now = -1;
| |||
if(delimiter)
| |||
{
| |||
now = insertDelimiter(result);
| |||
}
| |||
| |||
if(limit > 0 && limit < ArraySize(result))
| |||
{
| |||
#ifdef LOGGING
| |||
PrintFormat("Limit %d is exceeded with %d records ('now' is at index %d)",
| |||
limit, ArraySize(result), now);
| |||
#endif
| |||
if(now == -1)
| |||
{
| |||
ArrayResize(result, limit);
| |||
}
| |||
else
| |||
{
| |||
int start = fmax(now - limit, 0);
| |||
int stop = fmin(now + limit, ArraySize(result) - 1);
| |||
| |||
if(stop - start > limit)
| |||
{
| |||
const int excess = (stop - start - limit) / 2;
| |||
const int odd = (stop - start - limit) % 2;
| |||
start += excess;
| |||
stop -= excess + odd;
| |||
}
| |||
| |||
#ifdef LOGGING
| |||
PrintFormat("Removing records outside %d to %d", start, stop);
| |||
#endif
| |||
if(stop < ArraySize(result)) ArrayRemove(result, stop);
| |||
if(start > 0) ArrayRemove(result, 0, start);
| |||
}
| |||
}
| |||
}
| |||
| |||
#ifdef LOGGING
| |||
PrintFormat("Got %d records", ArraySize(result));
| |||
#endif
| |||
| |||
return ArraySize(result) > 0;
| |||
}
| |||
| |||
bool update(MqlCalendarValue &result[])
| |||
{
| |||
ArrayFree(result);
| |||
| |||
// 'update' is only applicable after 'select' call or previous 'update'
| |||
if(change == 0)
| |||
{
| |||
calendarValueLast(change, result);
| |||
return false;
| |||
}
| |||
| |||
int count = 0; // number of combined requests with results
| |||
ulong _change = 0; // change placeholder during partial requests
| |||
ulong new_change = 0; // final change id after all requests done
| |||
if(ArraySize(ids))
| |||
{
| |||
for(int i = 0; i < ArraySize(ids); ++i)
| |||
{
| |||
MqlCalendarValue temp[];
| |||
_change = change;
| |||
if(calendarValueLastByEvent(ids[i], _change, temp))
| |||
{
| |||
ArrayCopy(result, temp, ArraySize(result));
| |||
++count;
| |||
}
| |||
new_change = fmax(new_change, _change);
| |||
}
| |||
}
| |||
else
| |||
{
| |||
// many countries or currencies
| |||
if(ArraySize(country) > ArraySize(currency))
| |||
{
| |||
const string c = ArraySize(currency) > 0 ? currency[0] : NULL;
| |||
for(int i = 0; i < ArraySize(country); ++i)
| |||
{
| |||
MqlCalendarValue temp[];
| |||
_change = change;
| |||
if(calendarValueLast(_change, temp, country[i], c))
| |||
{
| |||
ArrayCopy(result, temp, ArraySize(result));
| |||
++count;
| |||
}
| |||
new_change = fmax(new_change, _change);
| |||
}
| |||
}
| |||
else
| |||
{
| |||
const string c = ArraySize(country) > 0 ? country[0] : NULL;
| |||
for(int i = 0; i < ArraySize(currency); ++i)
| |||
{
| |||
MqlCalendarValue temp[];
| |||
_change = change;
| |||
if(calendarValueLast(_change, temp, c, currency[i]))
| |||
{
| |||
ArrayCopy(result, temp, ArraySize(result));
| |||
++count;
| |||
}
| |||
new_change = fmax(new_change, _change);
| |||
}
| |||
}
| |||
}
| |||
change = new_change;
| |||
| |||
if(ArraySize(result) > 0)
| |||
{
| |||
filter(result);
| |||
}
| |||
| |||
if(count > 1 && ArraySize(result) > 1)
| |||
{
| |||
#ifdef LOGGING
| |||
PrintFormat("Sorting %d records by time", ArraySize(result));
| |||
#endif
| |||
SORT_STRUCT(MqlCalendarValue, result, time);
| |||
}
| |||
| |||
return ArraySize(result) > 0;
| |||
}
| |||
| |||
ulong getChangeID() const
| |||
{
| |||
return change;
| |||
}
| |||
| |||
bool format(const MqlCalendarValue &data[],
| |||
const ENUM_CALENDAR_PROPERTY &props[], string &result[],
| |||
const bool padding = false, const bool header = false)
| |||
{
| |||
const int cols = ArraySize(props);
| |||
const int rows = ArraySize(data);
| |||
if(!cols || !(rows + header)) return false;
| |||
if(ArrayResize(result, cols * (rows + header)) <= 0) return false;
| |||
| |||
int c = 0;
| |||
int widths[];
| |||
| |||
if(header)
| |||
{
| |||
for(int j = 0; j < cols; ++j)
| |||
{
| |||
result[c++] = stringify(props[j]);
| |||
}
| |||
ArrayResize(widths, cols);
| |||
ArrayInitialize(widths, 0);
| |||
}
| |||
| |||
for(int i = 0; i < rows; ++i)
| |||
{
| |||
MqlCalendarValue v = data[i];
| |||
MqlCalendarEvent event = {};
| |||
MqlCalendarCountry cnt = {};
| |||
if(!v.event_id || !calendarEventById(v.event_id, event))
| |||
{
| |||
for(int j = 0; j < cols; ++j) result[c++] = ShortToString(0x2014);
| |||
continue;
| |||
}
| |||
if(!event.country_id || !calendarCountryById(event.country_id, cnt))
| |||
{
| |||
for(int j = 0; j < cols; ++j) result[c++] = ShortToString(0x2014);
| |||
continue;
| |||
}
| |||
| |||
for(int j = 0; j < cols; ++j)
| |||
{
| |||
// retrieve field value
| |||
switch((int)props[j])
| |||
{
| |||
case CALENDAR_PROPERTY_COUNTRY_CODE:
| |||
result[c++] = cnt.code;
| |||
break;
| |||
case CALENDAR_PROPERTY_COUNTRY_CURRENCY:
| |||
result[c++] = cnt.currency;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_TYPE:
| |||
result[c++] = stringify(event.type);
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_SECTOR:
| |||
result[c++] = stringify(event.sector);
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_FREQUENCY:
| |||
result[c++] = stringify(event.frequency);
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_TIMEMODE:
| |||
result[c++] = stringify(event.time_mode);
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_UNIT:
| |||
result[c++] = stringify(event.unit);
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_IMPORTANCE:
| |||
result[c++] = stringify(event.importance);
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_MULTIPLIER:
| |||
result[c++] = stringify(event.multiplier);
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_SOURCE:
| |||
result[c++] = event.source_url;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_NAME:
| |||
result[c++] = event.name;
| |||
break;
| |||
case CALENDAR_PROPERTY_EVENT_DIGITS:
| |||
result[c++] = (string)event.digits;
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_REVISION:
| |||
result[c++] = (string)v.revision;
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_IMPACT:
| |||
result[c++] = stringify(v.impact_type);
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_ACTUAL:
| |||
result[c++] = StringFormat("%+.*f", event.digits, v.GetActualValue());
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_PREVIOUS:
| |||
result[c++] = StringFormat("%+.*f", event.digits, v.GetPreviousValue());
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_REVISED:
| |||
result[c++] = StringFormat("%+.*f", event.digits, v.GetRevisedValue());
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_PREVISED:
| |||
result[c++] = StringFormat("%+.*f", event.digits,
| |||
v.HasRevisedValue() ? v.GetRevisedValue() : v.GetPreviousValue());
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_FORECAST:
| |||
result[c++] = StringFormat("%+.*f", event.digits, v.GetForecastValue());
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_TIME:
| |||
result[c++] = TimeToString(v.time);
| |||
break;
| |||
case CALENDAR_PROPERTY_RECORD_PERIOD:
| |||
result[c++] = TimeToString(v.period, TIME_DATE);
| |||
break;
| |||
}
| |||
| |||
if(header)
| |||
{
| |||
widths[j] = fmax(widths[j], StringLen(result[c - 1]));
| |||
}
| |||
}
| |||
}
| |||
| |||
if(header && rows > 0)
| |||
{
| |||
for(int j = 0; j < cols; ++j)
| |||
{
| |||
if(widths[j] < StringLen(result[j]) - 1)
| |||
{
| |||
StringSetLength(result[j], widths[j]); // truncate
| |||
result[j] += ShortToString(0x205E); // add ellipsis
| |||
}
| |||
}
| |||
}
| |||
| |||
if(padding) // for table pretty-printing in monotype logs
| |||
{
| |||
int width[];
| |||
ArrayResize(width, cols);
| |||
ArrayInitialize(width, 0);
| |||
for(int i = 0; i < rows + header; ++i)
| |||
{
| |||
for(int j = 0; j < cols; ++j)
| |||
{
| |||
width[j] = fmax(width[j], StringLen(result[i * cols + j]));
| |||
}
| |||
}
| |||
| |||
for(int i = 0; i < rows + header; ++i)
| |||
{
| |||
for(int j = 0; j < cols; ++j)
| |||
{
| |||
const int pad = width[j] - StringLen(result[i * cols + j]);
| |||
result[i * cols + j] = StringFormat("%*.*s%s", pad, pad, " ", result[i * cols + j]);
| |||
}
| |||
}
| |||
}
| |||
| |||
return true;
| |||
}
| |||
};
| |||
//+------------------------------------------------------------------+
|