Files

542 lines
30 KiB
Plaintext
Raw Permalink Normal View History

2025-02-07 19:13:59 +03:30
//+------------------------------------------------------------------+
//| 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 <MT4Bridge/MT4Orders.mqh>
#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<TRADE_RECORD_FIELDS>
{
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<typename E>
class DaysRangeSelector: public DateTimeSelector<E>
{
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<string> symbols;
static Vocabulary<long> magics;
static int counter;
static IndexMap symbol2symbol;
static Vocabulary<string> 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<string> TradeRecord::symbols;
static Vocabulary<long> TradeRecord::magics;
static int TradeRecord::counter = 0;
static IndexMap TradeRecord::symbol2symbol;
static Vocabulary<string> 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<typename T>
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<TRADE_SELECTORS,TRADE_RECORD_FIELDS>
{
protected:
virtual Selector<TRADE_RECORD_FIELDS> *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<TRADE_RECORD_FIELDS,TradeRecord>(FIELD_NUMBER);
case SELECTOR_MAGIC:
return new MagicSelector();
case SELECTOR_PROFITABLE:
return new ProfitableSelector();
case SELECTOR_DURATION:
return new DaysRangeSelector<TRADE_RECORD_FIELDS>(15, FIELD_DURATION); // up to 14 days
case SELECTOR_MONTH:
return field != FIELD_NONE ? new MonthSelector<TRADE_RECORD_FIELDS>(field) : NULL;
case SELECTOR_WEEKDAY:
return field != FIELD_NONE ? new WorkWeekDaySelector<TRADE_RECORD_FIELDS>(field) : NULL;
case SELECTOR_DAYHOUR:
return field != FIELD_NONE ? new DayHourSelector<TRADE_RECORD_FIELDS>(field) : NULL;
case SELECTOR_HOURMINUTE:
return field != FIELD_NONE ? new DayHourSelector<TRADE_RECORD_FIELDS>(field) : NULL;
case SELECTOR_SCALAR:
return field != FIELD_NONE ? new BaseSelector<TRADE_RECORD_FIELDS>(field) : NULL;
case SELECTOR_QUANTS:
return field != FIELD_NONE ? new QuantizationSelector<TRADE_RECORD_FIELDS>(field, quantGranularity) : NULL;
case SELECTOR_FILTER:
return field != FIELD_NONE ? new FilterSelector<TRADE_RECORD_FIELDS>(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<RECORD_CLASS> _defaultHistoryAdapter; // CustomTradeRecord
OLAPEngineTrade _defaultEngine;