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;
| |
}
| |
};
| |
//+------------------------------------------------------------------+
|