//+------------------------------------------------------------------+ //| OLAPQuotes.mqh | //| Copyright © 2016-2020, Marketeer | //| https://www.mql5.com/en/users/marketeer | //| Online Analytical Processing of trading hypercubes | //| https://www.mql5.com/en/articles/6602 | //| https://www.mql5.com/en/articles/6603 | //| https://www.mql5.com/en/articles/7535 | //| https://www.mql5.com/en/articles/7656 | //| rev. 25.02.2020 | //+------------------------------------------------------------------+ #include "OLAPCommon.mqh" #define SELECTORS QUOTE_SELECTORS #define ENUM_FIELDS QUOTE_RECORD_FIELDS #define DEFAULT_SELECTOR_TYPE SELECTOR_SHAPE #define DEFAULT_SELECTOR_FIELD FIELD_NONE #define DEFAULT_AGGREGATOR_TYPE AGGREGATOR_COUNT #define DEFAULT_AGGREGATOR_FIELD FIELD_NONE // QUOTE SELECTORS enum QUOTE_SELECTORS { SELECTOR_NONE, // none SELECTOR_SHAPE, // type SELECTOR_INDEX, // ordinal number /* below datetime field assumed */ SELECTOR_MONTH, // month-of-year SELECTOR_WEEKDAY, // day-of-week SELECTOR_DAYHOUR, // hour-of-day SELECTOR_HOURMINUTE, // minute-of-hour /* the next require a field as parameter */ SELECTOR_SCALAR, // scalar(field) SELECTOR_QUANTS, // quants(field) SELECTOR_FILTER // filter(field) }; enum QUOTE_RECORD_FIELDS { FIELD_NONE, // none FIELD_INDEX, // index FIELD_SHAPE, // type (bearish/flat/bullish) FIELD_DATETIME, // datetime FIELD_PRICE_OPEN, // open price FIELD_PRICE_HIGH, // high price FIELD_PRICE_LOW, // low price FIELD_PRICE_CLOSE, // close price FIELD_PRICE_RANGE_OC,// price range (OC) FIELD_PRICE_RANGE_HL,// price range (HL) FIELD_SPREAD, // spread FIELD_TICK_VOLUME, // tick volume FIELD_REAL_VOLUME, // real volume FIELD_CUSTOM1, // custom 1 FIELD_CUSTOM2, // custom 2 FIELD_CUSTOM3, // custom 3 FIELD_CUSTOM4, // custom 4 QUOTE_RECORD_FIELDS_LAST //   }; // ^ invisible non-breaking space to hide this element name (it's auxiliary) class QuoteSelector: public BaseSelector { public: QuoteSelector(const QUOTE_RECORD_FIELDS field): BaseSelector(field) { } }; class ShapeSelector: public QuoteSelector { public: ShapeSelector(): QuoteSelector(FIELD_SHAPE) { _typename = typename(this); } virtual bool select(const Record *r, int &index) const { index = (int)r.get(selector); index += 1; // shift from -1, 0, +1 to [0..2] return index >= getMin() && index <= getMax(); } virtual int getRange() const { return 3; // 0 through 2 } virtual string getLabel(const int index) const { const static string types[3] = {"bearish", "flat", "bullish"}; return types[index]; } }; class QuotesRecord: public Record { protected: static int counter; // number of bars const static char datatypes[QUOTE_RECORD_FIELDS_LAST]; void fillByQuotes(const MqlRates &rate) { set(FIELD_INDEX, counter++); set(FIELD_SHAPE, rate.close > rate.open ? +1 : (rate.close < rate.open ? -1 : 0)); set(FIELD_DATETIME, (double)rate.time); set(FIELD_PRICE_OPEN, rate.open); set(FIELD_PRICE_HIGH, rate.high); set(FIELD_PRICE_LOW, rate.low); set(FIELD_PRICE_CLOSE, rate.close); set(FIELD_PRICE_RANGE_OC, (rate.close - rate.open) / _Point); set(FIELD_PRICE_RANGE_HL, (rate.high - rate.low) * MathSign(rate.close - rate.open) / _Point); set(FIELD_SPREAD, (double)rate.spread); set(FIELD_TICK_VOLUME, (double)rate.tick_volume); set(FIELD_REAL_VOLUME, (double)rate.real_volume); } public: QuotesRecord(): Record(QUOTE_RECORD_FIELDS_LAST) { } QuotesRecord(const MqlRates &rate): Record(QUOTE_RECORD_FIELDS_LAST) { fillByQuotes(rate); } static int getRecordCount() { return counter; } static void reset() { counter = 0; } virtual string legend(const int index) const override { if(index >= 0 && index < QUOTE_RECORD_FIELDS_LAST) { return legendFromEnum((QUOTE_RECORD_FIELDS)index); //return EnumToString((QUOTE_RECORD_FIELDS)index); } return "unknown"; } static char datatype(const int index) { return datatypes[index]; } }; static int QuotesRecord::counter = 0; const static char QuotesRecord::datatypes[QUOTE_RECORD_FIELDS_LAST] = { 0, // none 'i', // index, serial number 'i', // type (-1 down/0/+1 up) 't', // datetime 'd', // open price 'd', // high price 'd', // low price 'd', // close price 'd', // range OC 'd', // range HL 'i', // spread 'i', // tick 'i', // real 'd', // custom 1 'd', // custom 2 'd', // custom 3 'd' // custom 4 }; template class QuotesDataAdapter: public DataAdapter { private: int size; int cursor; public: QuotesDataAdapter() { reset(); } virtual void reset() override { size = MathMin(Bars(_Symbol, _Period), TerminalInfoInteger(TERMINAL_MAXBARS)); cursor = size - 1; T::reset(); } virtual int reservedSize() const override { return size; } virtual Record *getNext() override { if(cursor >= 0) { MqlRates rate[1]; if(CopyRates(_Symbol, _Period, cursor, 1, rate) > 0) { cursor--; return new T(rate[0]); } Print(__FILE__, " ", __LINE__, " ", GetLastError()); return NULL; } return NULL; } virtual bool isOwner() const override { return false; } virtual int getFieldCount() const override { return QUOTE_RECORD_FIELDS_LAST; } }; class OLAPEngineQuotes: public OLAPEngine { protected: virtual Selector *createSelector(const QUOTE_SELECTORS selector, const QUOTE_RECORD_FIELDS field) override { switch(selector) { case SELECTOR_SHAPE: return new ShapeSelector(); case SELECTOR_INDEX: return new SerialNumberSelector(FIELD_INDEX); case SELECTOR_MONTH: return new MonthSelector(FIELD_DATETIME); case SELECTOR_WEEKDAY: return new WorkWeekDaySelector(FIELD_DATETIME); case SELECTOR_DAYHOUR: return new DayHourSelector(FIELD_DATETIME); case SELECTOR_HOURMINUTE: return new DayHourSelector(FIELD_DATETIME); case SELECTOR_SCALAR: return field != FIELD_NONE ? new BaseSelector(field) : NULL; case SELECTOR_QUANTS: return field != FIELD_NONE ? new QuantizationSelector(field, quantGranularity) : NULL; case SELECTOR_FILTER: return field != FIELD_NONE ? new FilterSelector(field) : NULL; } return NULL; } virtual void initialize() override { Print("Bars read: ", QuotesRecord::getRecordCount()); } public: OLAPEngineQuotes(): OLAPEngine() {} OLAPEngineQuotes(DataAdapter *ptr): OLAPEngine(ptr) {} }; #ifndef RECORD_CLASS #define RECORD_CLASS QuotesRecord #endif QuotesDataAdapter _defaultQuotesAdapter; OLAPEngineQuotes _defaultEngine;