//+------------------------------------------------------------------+ //| OLAPTrades.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 #include "OLAPCommon.mqh" #ifndef OP_BALANCE #define OP_BALANCE 6 #endif #define SELECTORS TRADE_SELECTORS #define ENUM_FIELDS TRADE_RECORD_FIELDS #define DEFAULT_SELECTOR_TYPE SELECTOR_SYMBOL #define DEFAULT_SELECTOR_FIELD FIELD_NONE #define DEFAULT_AGGREGATOR_TYPE AGGREGATOR_SUM #define DEFAULT_AGGREGATOR_FIELD FIELD_PROFIT_AMOUNT // TRADE SELECTORS enum TRADE_SELECTORS { SELECTOR_NONE, // none SELECTOR_TYPE, // type SELECTOR_SYMBOL, // symbol SELECTOR_SERIAL, // ordinal SELECTOR_MAGIC, // magic SELECTOR_PROFITABLE, // profitable /* custom selector (see demo) */ SELECTOR_DURATION, // duration in days /* all the next require a field as parameter */ SELECTOR_MONTH, // month-of-year(datetime field) SELECTOR_WEEKDAY, // day-of-week(datetime field) SELECTOR_DAYHOUR, // hour-of-day(datetime field) SELECTOR_HOURMINUTE, // minute-of-hour(datetime field) SELECTOR_SCALAR, // scalar(field) SELECTOR_QUANTS, // quants(field) SELECTOR_FILTER // filter(field) }; // MT4 and MT5 hedge enum TRADE_RECORD_FIELDS { FIELD_NONE, // none FIELD_NUMBER, // serial number FIELD_TICKET, // ticket FIELD_SYMBOL, // symbol FIELD_TYPE, // type (buy/sell) FIELD_OPEN_DATETIME, // open datetime FIELD_CLOSE_DATETIME,// close datetime FIELD_DURATION, // duration FIELD_OPEN_PRICE, // open price FIELD_CLOSE_PRICE, // close price FIELD_MAGIC, // magic number FIELD_LOT, // lot FIELD_PROFIT_AMOUNT, // profit amount FIELD_PROFIT_PERCENT,// profit percent FIELD_PROFIT_POINT, // profit points FIELD_COMMISSION, // commission FIELD_SWAP, // swap FIELD_CUSTOM_1, // custom 1 FIELD_CUSTOM_2, // custom 2 TRADE_RECORD_FIELDS_LAST //   }; class TradeSelector: public BaseSelector { public: TradeSelector(const TRADE_RECORD_FIELDS field): BaseSelector(field) { } }; class TypeSelector: public TradeSelector { public: TypeSelector(): TradeSelector(FIELD_TYPE) { _typename = typename(this); } virtual bool select(const Record *r, int &index) const { index = (int)r.get(selector); return index >= getMin() && index <= getMax(); } virtual int getRange() const { return 2; // OP_BUY, OP_SELL } virtual double getMin() const { return OP_BUY; } virtual double getMax() const { return OP_SELL; } virtual string getLabel(const int index) const { const static string types[2] = {"buy", "sell"}; return types[index]; } }; class SymbolSelector: public TradeSelector { public: SymbolSelector(): TradeSelector(FIELD_SYMBOL) { _typename = typename(this); } virtual bool select(const Record *r, int &index) const override { index = (int)r.get(selector); // symbols are stored as indices in vocabulary return (index >= 0); } virtual int getRange() const override { return TradeRecord::getSymbolCount(); } virtual string getLabel(const int index) const override { return TradeRecord::getSymbol(index); } }; class MagicSelector: public TradeSelector { public: MagicSelector(): TradeSelector(FIELD_MAGIC) { _typename = typename(this); } virtual bool select(const Record *r, int &index) const override { index = TradeRecord::getMagicIndex((int)r.get(selector)); return true; } virtual int getRange() const override { return TradeRecord::getMagicCount(); } }; class ProfitableSelector: public TradeSelector { public: ProfitableSelector(): TradeSelector(FIELD_PROFIT_AMOUNT) { _typename = typename(this); } virtual bool select(const Record *r, int &index) const override { index = (r.get(selector) > 0) ? 1 : 0; return true; } virtual int getRange() const override { return 2; // 0(false) - loss, 1(true) - profit } virtual string getLabel(const int index) const override { return index ? "profit" : "loss"; } }; template class DaysRangeSelector: public DateTimeSelector { public: DaysRangeSelector(const int n, const E field): DateTimeSelector(field, n) { _typename = typename(this); } virtual bool select(const Record *r, int &index) const override { double d = r.get(selector); int days = (int)(d / (60 * 60 * 24)); index = MathMin(days, granularity - 1); return true; } virtual string getLabel(const int index) const override { return index < granularity - 1 ? ((index < 10 ? " ": "") + (string)index + "D") : ((string)index + "D+"); } }; class TradeRecord: public Record { protected: static Vocabulary symbols; static Vocabulary magics; static int counter; static IndexMap symbol2symbol; static Vocabulary missing; const static char datatypes[TRADE_RECORD_FIELDS_LAST]; void fillByOrder(const double balance) { set(FIELD_NUMBER, counter++); set(FIELD_TICKET, OrderTicket()); set(FIELD_SYMBOL, symbols.add(OrderSymbol())); set(FIELD_TYPE, OrderType()); set(FIELD_OPEN_DATETIME, OrderOpenTime()); set(FIELD_CLOSE_DATETIME, OrderCloseTime()); set(FIELD_DURATION, OrderCloseTime() - OrderOpenTime()); set(FIELD_OPEN_PRICE, OrderOpenPrice()); set(FIELD_CLOSE_PRICE, OrderClosePrice()); set(FIELD_MAGIC, OrderMagicNumber()); magics.add(OrderMagicNumber()); set(FIELD_LOT, OrderLots()); set(FIELD_PROFIT_AMOUNT, OrderProfit()); set(FIELD_PROFIT_PERCENT, (OrderProfit() / balance)); set(FIELD_PROFIT_POINT, ((OrderType() == OP_BUY ? +1 : -1) * (OrderClosePrice() - OrderOpenPrice()) / SymbolInfoDouble(OrderSymbol(), SYMBOL_POINT))); set(FIELD_COMMISSION, OrderCommission()); set(FIELD_SWAP, OrderSwap()); } public: static string realsymbol(const string symbol, const string prefix = "", const string suffix = "") { string real; double temp; if(!SymbolInfoDouble(symbol, SYMBOL_BID, temp) && GetLastError() == ERR_MARKET_UNKNOWN_SYMBOL) { real = symbol2symbol.get(symbol); if(real != NULL) return real; if(Suffix != "") { int pos = StringLen(symbol) - StringLen(suffix); if((pos > 0) && (StringFind(symbol, suffix) == pos)) { real = StringSubstr(symbol, 0, pos); if(SymbolInfoDouble(real, SYMBOL_BID, temp)) { symbol2symbol.setValue(symbol, real); return real; } } if(StringFind(symbol, Suffix) == -1) { real = symbol + Suffix; if(SymbolInfoDouble(real, SYMBOL_BID, temp)) { symbol2symbol.setValue(symbol, real); return real; } } } if(prefix != "") { int diff = StringLen(symbol) - StringLen(prefix); if((diff > 0) && (StringFind(symbol, prefix) == 0)) { real = StringSubstr(symbol, StringLen(prefix)); if(SymbolInfoDouble(real, SYMBOL_BID, temp)) { symbol2symbol.setValue(symbol, real); return real; } } if(StringFind(symbol, prefix) == -1) { real = prefix + symbol; if(SymbolInfoDouble(real, SYMBOL_BID, temp)) { symbol2symbol.setValue(symbol, real); return real; } } } int size = missing.size(); if(missing.add(symbol) == size) { Print("Can't find correct symbol for ", symbol); } return NULL; } return symbol; } public: TradeRecord(): Record(TRADE_RECORD_FIELDS_LAST) { } TradeRecord(const double balance): Record(TRADE_RECORD_FIELDS_LAST) { fillByOrder(balance); } static int getSymbolCount() { return symbols.size(); } static string getSymbol(const int index) { if(index < 0 || index >= symbols.size()) return NULL; return symbols[index]; } static int getSymbolIndex(const string s) { return symbols.get(s); } static int getMagicCount() { return magics.size(); } static long getMagic(const int index) { return magics[index]; } static int getMagicIndex(const long m) { return magics.get(m); } static int getRecordCount() { return counter; } static void reset() { symbols.clear(); magics.clear(); counter = 0; } virtual string legend(const int index) const override { if(index >= 0 && index < TRADE_RECORD_FIELDS_LAST) { return legendFromEnum((TRADE_RECORD_FIELDS)index); //return EnumToString((TRADE_RECORD_FIELDS)index); } return "unknown"; } static char datatype(const int index) { return datatypes[index]; } }; static Vocabulary TradeRecord::symbols; static Vocabulary TradeRecord::magics; static int TradeRecord::counter = 0; static IndexMap TradeRecord::symbol2symbol; static Vocabulary TradeRecord::missing; const static char TradeRecord::datatypes[TRADE_RECORD_FIELDS_LAST] = // not used yet { 0, // none 'i', // serial number 'i', // ticket 's', // symbol ('i'?, index in vocabulary) 'i', // type (OP_BUY/OP_SELL) 't', // open datetime 't', // close datetime 'i', // duration (seconds) 'd', // open price 'd', // close price 'i', // magic number (index in vocabulary) 'd', // lot 'd', // profit amount 'd', // profit percent 'i', // profit points 'd', // commission 'd', // swap 'd', // custom 1 'd' // custom 2 }; template class HistoryDataAdapter: public DataAdapter { private: int size; int cursor; double balance; public: HistoryDataAdapter() { reset(); T::reset(); } virtual void reset() override { cursor = 0; size = OrdersHistoryTotal(); balance = 0; } virtual int reservedSize() const override { return size; } virtual Record *getNext() override { if(cursor < size) { while(OrderSelect(cursor++, SELECT_BY_POS, MODE_HISTORY)) { if(OrderType() < 2 || OrderType() == OP_BALANCE) { if(SymbolInfoDouble(OrderSymbol(), SYMBOL_POINT) == 0) { Print("MarketInfo is missing:"); OrderPrint(); continue; } balance += OrderProfit(); if(OrderType() != OP_BALANCE) { return new T(balance); } } } return NULL; } return NULL; } virtual bool isOwner() const override { return false; } virtual int getFieldCount() const override { return TRADE_RECORD_FIELDS_LAST; } }; class OLAPEngineTrade: public OLAPEngine { protected: virtual Selector *createSelector(const TRADE_SELECTORS selector, const TRADE_RECORD_FIELDS field) override { switch(selector) { case SELECTOR_TYPE: return new TypeSelector(); case SELECTOR_SYMBOL: return new SymbolSelector(); case SELECTOR_SERIAL: return new SerialNumberSelector(FIELD_NUMBER); case SELECTOR_MAGIC: return new MagicSelector(); case SELECTOR_PROFITABLE: return new ProfitableSelector(); case SELECTOR_DURATION: return new DaysRangeSelector(15, FIELD_DURATION); // up to 14 days case SELECTOR_MONTH: return field != FIELD_NONE ? new MonthSelector(field) : NULL; case SELECTOR_WEEKDAY: return field != FIELD_NONE ? new WorkWeekDaySelector(field) : NULL; case SELECTOR_DAYHOUR: return field != FIELD_NONE ? new DayHourSelector(field) : NULL; case SELECTOR_HOURMINUTE: return field != FIELD_NONE ? new DayHourSelector(field) : NULL; 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("Symbol number: ", TradeRecord::getSymbolCount()); for(int i = 0; i < TradeRecord::getSymbolCount(); i++) { Print(i, "] ", TradeRecord::getSymbol(i)); } Print("Magic number: ", TradeRecord::getMagicCount()); for(int i = 0; i < TradeRecord::getMagicCount(); i++) { Print(i, "] ", TradeRecord::getMagic(i)); } } public: OLAPEngineTrade(): OLAPEngine() {} OLAPEngineTrade(DataAdapter *ptr): OLAPEngine(ptr) {} }; #ifndef RECORD_CLASS #define RECORD_CLASS TradeRecord #endif HistoryDataAdapter _defaultHistoryAdapter; // CustomTradeRecord OLAPEngineTrade _defaultEngine;