Files
2025-02-07 19:13:59 +03:30

542 lines
30 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+------------------------------------------------------------------+
//| 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;