283 lines
16 KiB
Plaintext
283 lines
16 KiB
Plaintext
|
|
//+------------------------------------------------------------------+
|
|||
|
|
//| 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<QUOTE_RECORD_FIELDS>
|
|||
|
|
{
|
|||
|
|
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>((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<typename T>
|
|||
|
|
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<QUOTE_SELECTORS,QUOTE_RECORD_FIELDS>
|
|||
|
|
{
|
|||
|
|
protected:
|
|||
|
|
virtual Selector<QUOTE_RECORD_FIELDS> *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<QUOTE_RECORD_FIELDS,QuotesRecord>(FIELD_INDEX);
|
|||
|
|
case SELECTOR_MONTH:
|
|||
|
|
return new MonthSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME);
|
|||
|
|
case SELECTOR_WEEKDAY:
|
|||
|
|
return new WorkWeekDaySelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME);
|
|||
|
|
case SELECTOR_DAYHOUR:
|
|||
|
|
return new DayHourSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME);
|
|||
|
|
case SELECTOR_HOURMINUTE:
|
|||
|
|
return new DayHourSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME);
|
|||
|
|
case SELECTOR_SCALAR:
|
|||
|
|
return field != FIELD_NONE ? new BaseSelector<QUOTE_RECORD_FIELDS>(field) : NULL;
|
|||
|
|
case SELECTOR_QUANTS:
|
|||
|
|
return field != FIELD_NONE ? new QuantizationSelector<QUOTE_RECORD_FIELDS>(field, quantGranularity) : NULL;
|
|||
|
|
case SELECTOR_FILTER:
|
|||
|
|
return field != FIELD_NONE ? new FilterSelector<QUOTE_RECORD_FIELDS>(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<RECORD_CLASS> _defaultQuotesAdapter;
|
|||
|
|
OLAPEngineQuotes _defaultEngine;
|
|||
|
|
�
|