1699 lines
41 KiB
Plaintext
1699 lines
41 KiB
Plaintext
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| OLAPcube.mqh |
|
||
|
|
//| Copyright © 2016-2019, Marketeer |
|
||
|
|
//| https://www.mql5.com/en/users/marketeer |
|
||
|
|
//| Online Analytical Processing of trading hypercubes |
|
||
|
|
//| https://www.mql5.com/ru/articles/6602 |
|
||
|
|
//| https://www.mql5.com/ru/articles/6603 |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
|
||
|
|
#define MT4ORDERS_FASTHISTORY_OFF
|
||
|
|
#include <MT4orders.mqh>
|
||
|
|
|
||
|
|
#include <Marketeer/IndexMap.mqh>
|
||
|
|
#include <Marketeer/TimeMT4.mqh>
|
||
|
|
#include <Marketeer/Converter.mqh>
|
||
|
|
#include <OLAP/PairArray.mqh>
|
||
|
|
|
||
|
|
#ifndef OP_BALANCE
|
||
|
|
#define OP_BALANCE 6
|
||
|
|
#endif
|
||
|
|
|
||
|
|
enum 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_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)
|
||
|
|
};
|
||
|
|
|
||
|
|
enum AGGREGATORS
|
||
|
|
{
|
||
|
|
AGGREGATOR_SUM, // SUM
|
||
|
|
AGGREGATOR_AVERAGE, // AVERAGE
|
||
|
|
AGGREGATOR_MAX, // MAX
|
||
|
|
AGGREGATOR_MIN, // MIN
|
||
|
|
AGGREGATOR_COUNT, // COUNT
|
||
|
|
AGGREGATOR_PROFITFACTOR, // PROFIT FACTOR
|
||
|
|
AGGREGATOR_PROGRESSIVE, // PROGRESSIVE TOTAL
|
||
|
|
AGGREGATOR_IDENTITY // IDENTITY
|
||
|
|
};
|
||
|
|
|
||
|
|
enum DATA_TYPES
|
||
|
|
{
|
||
|
|
DATA_TYPE_NONE,
|
||
|
|
DATA_TYPE_NUMBER = 'd',
|
||
|
|
DATA_TYPE_INTEGER = 'i',
|
||
|
|
DATA_TYPE_TIME = 't',
|
||
|
|
DATA_TYPE_STRING = 's'
|
||
|
|
};
|
||
|
|
|
||
|
|
class Record
|
||
|
|
{
|
||
|
|
private:
|
||
|
|
double data[];
|
||
|
|
|
||
|
|
public:
|
||
|
|
Record(const int length)
|
||
|
|
{
|
||
|
|
ArrayResize(data, length);
|
||
|
|
ArrayInitialize(data, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void set(const int index, double value)
|
||
|
|
{
|
||
|
|
data[index] = value;
|
||
|
|
}
|
||
|
|
|
||
|
|
double get(const int index) const
|
||
|
|
{
|
||
|
|
return data[index];
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string legend(const int index) const
|
||
|
|
{
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual char datatype(const int index) const
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void fillCustomFields() {/* does nothing */};
|
||
|
|
};
|
||
|
|
|
||
|
|
// single pass data reader
|
||
|
|
class DataAdapter
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
virtual Record *getNext() = 0;
|
||
|
|
virtual int reservedSize() = 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
int EnumToArray(E dummy, int &values[], const int start = INT_MIN, const int stop = INT_MAX)
|
||
|
|
{
|
||
|
|
string t = typename(E) + "::";
|
||
|
|
int length = StringLen(t);
|
||
|
|
|
||
|
|
ArrayResize(values, 0);
|
||
|
|
int count = 0;
|
||
|
|
|
||
|
|
for(int i = start; i < stop && !IsStopped(); i++)
|
||
|
|
{
|
||
|
|
E e = (E)i;
|
||
|
|
if(StringCompare(StringSubstr(EnumToString(e), 0, length), t) != 0)
|
||
|
|
{
|
||
|
|
ArrayResize(values, count + 1);
|
||
|
|
values[count++] = i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
|
||
|
|
// MT4 and MT5 hedge
|
||
|
|
enum TRADE_RECORD_FIELDS
|
||
|
|
{
|
||
|
|
FIELD_NONE, // none
|
||
|
|
FIELD_NUMBER, // serial number
|
||
|
|
FIELD_TICKET, // ticket
|
||
|
|
FIELD_SYMBOL, // symbol
|
||
|
|
FIELD_TYPE, // type (OP_BUY/OP_SELL)
|
||
|
|
FIELD_DATETIME1, // open datetime
|
||
|
|
FIELD_DATETIME2, // close datetime
|
||
|
|
FIELD_DURATION, // duration
|
||
|
|
FIELD_PRICE1, // open price
|
||
|
|
FIELD_PRICE2, // 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_CUSTOM1, // custom 1
|
||
|
|
FIELD_CUSTOM2 // custom 2
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class Selector
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
E selector;
|
||
|
|
string _typename;
|
||
|
|
|
||
|
|
public:
|
||
|
|
Selector(const E field): selector(field)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void prepare(const Record *r)
|
||
|
|
{
|
||
|
|
// no action by default
|
||
|
|
}
|
||
|
|
|
||
|
|
// returns index of cell to store values from the record
|
||
|
|
virtual bool select(const Record *r, int &index) const = 0;
|
||
|
|
|
||
|
|
virtual int getRange() const = 0;
|
||
|
|
virtual double getMin() const = 0;
|
||
|
|
virtual double getMax() const = 0;
|
||
|
|
|
||
|
|
virtual E getField() const
|
||
|
|
{
|
||
|
|
return selector;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getLabel(const int index) const = 0;
|
||
|
|
|
||
|
|
virtual string getTitle() const
|
||
|
|
{
|
||
|
|
return _typename + "(" + EnumToString(selector) + ")";
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
class TradeSelector: public Selector<TRADE_RECORD_FIELDS>
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
TradeSelector(const TRADE_RECORD_FIELDS field): Selector(field)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool select(const Record *r, int &index) const override
|
||
|
|
{
|
||
|
|
index = 0;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual int getRange() const override
|
||
|
|
{
|
||
|
|
return 1; // this is a scalar by default, returns 1 value
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual double getMin() const override
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual double getMax() const override
|
||
|
|
{
|
||
|
|
return (double)(getRange() - 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getLabel(const int index) const override
|
||
|
|
{
|
||
|
|
return EnumToString(selector) + "[" + (string)index + "]"; // "scalar"
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
virtual string format(const double value) const override
|
||
|
|
{
|
||
|
|
if(selector == FIELD_DATETIME1 || selector == FIELD_DATETIME2)
|
||
|
|
{
|
||
|
|
return TimeToString(value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
*/
|
||
|
|
};
|
||
|
|
|
||
|
|
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];
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class DateTimeSelector: public TradeSelector
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
int granularity;
|
||
|
|
|
||
|
|
public:
|
||
|
|
DateTimeSelector(const E field, const int size): TradeSelector(field), granularity(size)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual int getRange() const
|
||
|
|
{
|
||
|
|
return granularity;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
class WeekDaySelector: public DateTimeSelector<TRADE_RECORD_FIELDS>
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
WeekDaySelector(const TRADE_RECORD_FIELDS f): DateTimeSelector<TRADE_RECORD_FIELDS>(f, 7)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool select(const Record *r, int &index) const
|
||
|
|
{
|
||
|
|
double d = r.get(selector);
|
||
|
|
datetime t = (datetime)d;
|
||
|
|
index = TimeDayOfWeek(t);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getLabel(const int index) const
|
||
|
|
{
|
||
|
|
static string days[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
|
||
|
|
return days[index];
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
class DayHourSelector: public DateTimeSelector<TRADE_RECORD_FIELDS>
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
DayHourSelector(const TRADE_RECORD_FIELDS f): DateTimeSelector<TRADE_RECORD_FIELDS>(f, 24)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool select(const Record *r, int &index) const
|
||
|
|
{
|
||
|
|
double d = r.get(selector);
|
||
|
|
datetime t = (datetime)d;
|
||
|
|
index = TimeHour(t);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getLabel(const int index) const
|
||
|
|
{
|
||
|
|
return (string)index;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
class HourMinuteSelector: public DateTimeSelector<TRADE_RECORD_FIELDS>
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
HourMinuteSelector(const TRADE_RECORD_FIELDS f): DateTimeSelector<TRADE_RECORD_FIELDS>(f, 60)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool select(const Record *r, int &index) const
|
||
|
|
{
|
||
|
|
double d = r.get(selector);
|
||
|
|
datetime t = (datetime)d;
|
||
|
|
index = TimeMinute(t);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getLabel(const int index) const
|
||
|
|
{
|
||
|
|
return (string)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);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename T>
|
||
|
|
class Vocabulary
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
T index[];
|
||
|
|
|
||
|
|
public:
|
||
|
|
int get(const T &text) const
|
||
|
|
{
|
||
|
|
int n = ArraySize(index);
|
||
|
|
for(int i = 0; i < n; i++)
|
||
|
|
{
|
||
|
|
if(index[i] == text) return i;
|
||
|
|
}
|
||
|
|
return -(n + 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
int add(const T text)
|
||
|
|
{
|
||
|
|
int n = get(text);
|
||
|
|
if(n < 0)
|
||
|
|
{
|
||
|
|
n = -n;
|
||
|
|
ArrayResize(index, n);
|
||
|
|
index[n - 1] = text;
|
||
|
|
return n - 1;
|
||
|
|
}
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
int size() const
|
||
|
|
{
|
||
|
|
return ArraySize(index);
|
||
|
|
}
|
||
|
|
|
||
|
|
T operator[](const int position) const
|
||
|
|
{
|
||
|
|
return index[position];
|
||
|
|
}
|
||
|
|
|
||
|
|
void clear()
|
||
|
|
{
|
||
|
|
ArrayResize(index, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
class QuantizationSelector: public TradeSelector
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
Vocabulary<double> quants;
|
||
|
|
|
||
|
|
public:
|
||
|
|
QuantizationSelector(const TRADE_RECORD_FIELDS field): TradeSelector(field)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void prepare(const Record *r) override
|
||
|
|
{
|
||
|
|
double value = r.get(selector);
|
||
|
|
quants.add(value);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool select(const Record *r, int &index) const override
|
||
|
|
{
|
||
|
|
double value = r.get(selector);
|
||
|
|
index = quants.get(value);
|
||
|
|
return (index >= 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual int getRange() const override
|
||
|
|
{
|
||
|
|
return quants.size();
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getLabel(const int index) const override
|
||
|
|
{
|
||
|
|
return (string)(float)quants[index];
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
class SerialNumberSelector: public TradeSelector
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
SerialNumberSelector(): TradeSelector(FIELD_NUMBER)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool select(const Record *r, int &index) const override
|
||
|
|
{
|
||
|
|
index = (int)r.get(selector);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual int getRange() const override
|
||
|
|
{
|
||
|
|
return TradeRecord::getRecordCount();
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getLabel(const int index) const override
|
||
|
|
{
|
||
|
|
return (string)(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 Filter
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
Selector<E> *selector;
|
||
|
|
double filter;
|
||
|
|
|
||
|
|
public:
|
||
|
|
Filter(Selector<E> &s, const double value): selector(&s), filter(value)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool matches(const Record *r) const
|
||
|
|
{
|
||
|
|
int index;
|
||
|
|
if(selector.select(r, index))
|
||
|
|
{
|
||
|
|
if(index == (int)filter) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
Selector<E> *getSelector() const
|
||
|
|
{
|
||
|
|
return selector;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getTitle() const
|
||
|
|
{
|
||
|
|
return selector.getTitle() + "[" + (string)filter + "]";
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class FilterRange: public Filter<E>
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
double filterMax;
|
||
|
|
|
||
|
|
public:
|
||
|
|
FilterRange(Selector<E> &s, const double valueMin, const double valueMax): Filter(&s, valueMin), filterMax(valueMax)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool matches(const Record *r) const override
|
||
|
|
{
|
||
|
|
int index;
|
||
|
|
if(selector.select(r, index))
|
||
|
|
{
|
||
|
|
if(index >= filter && index <= filterMax) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getTitle() const override
|
||
|
|
{
|
||
|
|
return selector.getTitle() + "[" + (string)filter + ".." + (string)filterMax + "]";
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
enum SORT_BY // applicable only for 1-dimensional cubes
|
||
|
|
{
|
||
|
|
SORT_BY_NONE, // none
|
||
|
|
SORT_BY_VALUE_ASCENDING, // value (ascending)
|
||
|
|
SORT_BY_VALUE_DESCENDING, // value (descending)
|
||
|
|
SORT_BY_LABEL_ASCENDING, // label (ascending)
|
||
|
|
SORT_BY_LABEL_DESCENDING // label (descending)
|
||
|
|
};
|
||
|
|
|
||
|
|
#define SORT_ASCENDING(A) (((A) & 1) != 0)
|
||
|
|
#define SORT_VALUE(A) ((A) < 3)
|
||
|
|
|
||
|
|
|
||
|
|
class MetaCube
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
int dimensions[];
|
||
|
|
int offsets[];
|
||
|
|
double totals[];
|
||
|
|
string _typename;
|
||
|
|
|
||
|
|
public:
|
||
|
|
int getDimension() const
|
||
|
|
{
|
||
|
|
return ArraySize(dimensions);
|
||
|
|
}
|
||
|
|
|
||
|
|
int getDimensionRange(const int n) const
|
||
|
|
{
|
||
|
|
return dimensions[n];
|
||
|
|
}
|
||
|
|
|
||
|
|
int getCubeSize() const
|
||
|
|
{
|
||
|
|
return ArraySize(totals);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual double getValue(const int &indices[]) const = 0;
|
||
|
|
virtual string getMetaCubeTitle() const = 0;
|
||
|
|
virtual string getDimensionTitle(const int d) const = 0;
|
||
|
|
virtual string getDimensionIndexLabel(const int d, const int index) const = 0;
|
||
|
|
virtual string getFilterTitles() const = 0;
|
||
|
|
virtual bool getVector(const int dimension, const int &consts[], PairArray *&result, const SORT_BY sortby = SORT_BY_NONE) const = 0;
|
||
|
|
//virtual string getDimensionFormat(const int d) const = 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class Aggregator: public MetaCube
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
const E field;
|
||
|
|
const int selectorCount;
|
||
|
|
const Selector<E> *selectors[];
|
||
|
|
const int filterCount;
|
||
|
|
const Filter<E> *filters[];
|
||
|
|
|
||
|
|
virtual int mixIndex(const int &k[]) const
|
||
|
|
{
|
||
|
|
int result = 0;
|
||
|
|
for(int i = 0; i < selectorCount; i++)
|
||
|
|
{
|
||
|
|
result += k[i] * offsets[i];
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool decode(const int _input, int &k[]) const
|
||
|
|
{
|
||
|
|
int index = _input;
|
||
|
|
ArrayResize(k, selectorCount);
|
||
|
|
ArrayInitialize(k, 0);
|
||
|
|
for(int i = selectorCount - 1; i >= 0; i--)
|
||
|
|
{
|
||
|
|
k[i] = index / offsets[i];
|
||
|
|
index -= k[i] * offsets[i];
|
||
|
|
}
|
||
|
|
|
||
|
|
if(index != 0)
|
||
|
|
{
|
||
|
|
Print("Bad index decode: ", _input);
|
||
|
|
ArrayPrint(k);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool filter(const Record *data) const
|
||
|
|
{
|
||
|
|
int q = 0;
|
||
|
|
for(; q < filterCount; q++)
|
||
|
|
{
|
||
|
|
if(!filters[q].matches(data))
|
||
|
|
{
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(q < filterCount) return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
public:
|
||
|
|
Aggregator(const E f, const Selector<E> *&s[], const Filter<E> *&t[]): field(f), selectorCount(ArraySize(s)), filterCount(ArraySize(t))
|
||
|
|
{
|
||
|
|
ArrayResize(selectors, selectorCount);
|
||
|
|
for(int i = 0; i < selectorCount; i++)
|
||
|
|
{
|
||
|
|
selectors[i] = s[i];
|
||
|
|
}
|
||
|
|
ArrayResize(filters, filterCount);
|
||
|
|
for(int i = 0; i < filterCount; i++)
|
||
|
|
{
|
||
|
|
filters[i] = t[i];
|
||
|
|
}
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void setSelectorBounds(const int length = 0)
|
||
|
|
{
|
||
|
|
ArrayResize(dimensions, selectorCount);
|
||
|
|
int total = 1;
|
||
|
|
for(int i = 0; i < selectorCount; i++)
|
||
|
|
{
|
||
|
|
dimensions[i] = selectors[i].getRange();
|
||
|
|
total *= dimensions[i];
|
||
|
|
}
|
||
|
|
ArrayResize(totals, total);
|
||
|
|
ArrayInitialize(totals, 0);
|
||
|
|
|
||
|
|
ArrayResize(offsets, selectorCount);
|
||
|
|
offsets[0] = 1;
|
||
|
|
for(int i = 1; i < selectorCount; i++)
|
||
|
|
{
|
||
|
|
offsets[i] = dimensions[i - 1] * offsets[i - 1]; // 1, X, Y*X
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void prepareSelectors(const Record *&data[])
|
||
|
|
{
|
||
|
|
int n = ArraySize(data);
|
||
|
|
for(int i = 0; i < n; i++)
|
||
|
|
{
|
||
|
|
for(int j = 0; j < selectorCount; j++)
|
||
|
|
{
|
||
|
|
Selector<E> *s = (Selector<E> *)selectors[j];
|
||
|
|
s.prepare(data[i]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// build an array with number of dimensions equal to number of selectors
|
||
|
|
virtual void calculate(const Record *&data[])
|
||
|
|
{
|
||
|
|
int k[];
|
||
|
|
ArrayResize(k, selectorCount);
|
||
|
|
int n = ArraySize(data);
|
||
|
|
for(int i = 0; i < n; i++)
|
||
|
|
{
|
||
|
|
if(!filter(data[i])) continue;
|
||
|
|
|
||
|
|
int j = 0;
|
||
|
|
for(; j < selectorCount; j++)
|
||
|
|
{
|
||
|
|
int d;
|
||
|
|
if(!selectors[j].select(data[i], d)) // record successfully mapped to a cell of selector?
|
||
|
|
{
|
||
|
|
break; // skip it, if not
|
||
|
|
}
|
||
|
|
k[j] = d; // save index in j-th dimension in array
|
||
|
|
}
|
||
|
|
if(j == selectorCount) // all coordinates are resolved
|
||
|
|
{
|
||
|
|
update(mixIndex(k), data[i].get(field)); // apply maths/stats
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
double getValue(const int &indices[]) const override
|
||
|
|
{
|
||
|
|
return totals[mixIndex(indices)];
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getMetaCubeTitle() const override
|
||
|
|
{
|
||
|
|
return _typename + " " + EnumToString(field);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getDimensionTitle(const int d) const override
|
||
|
|
{
|
||
|
|
if(d >= ArraySize(selectors)) return "n/a";
|
||
|
|
return selectors[d].getTitle();
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
virtual string getDimensionFormat(const int d) const override
|
||
|
|
{
|
||
|
|
if(d >= ArraySize(selectors)) return NULL;
|
||
|
|
return selectors[d].format(); // there's no such thing like datetime format
|
||
|
|
}
|
||
|
|
*/
|
||
|
|
|
||
|
|
virtual string getDimensionIndexLabel(const int d, const int index) const override
|
||
|
|
{
|
||
|
|
if(d >= ArraySize(selectors)) return "n/a";
|
||
|
|
return selectors[d].getLabel(index);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getFilterTitles() const override
|
||
|
|
{
|
||
|
|
string titles = "";
|
||
|
|
for(int i = 0; i < ArraySize(filters); i++)
|
||
|
|
{
|
||
|
|
titles += filters[i].getTitle() + ";";
|
||
|
|
}
|
||
|
|
if(titles == "") titles = "no";
|
||
|
|
return titles;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void update(const int index, const double value) = 0;
|
||
|
|
|
||
|
|
virtual bool getVector(const int dimension, const int &consts[], PairArray *&result, const SORT_BY sortby = SORT_BY_NONE) const
|
||
|
|
{
|
||
|
|
const int n = getDimension();
|
||
|
|
if(dimension >= n || n < 0) return false;
|
||
|
|
|
||
|
|
result = new PairArray;
|
||
|
|
|
||
|
|
int indices[];
|
||
|
|
ArrayResize(indices, n);
|
||
|
|
|
||
|
|
if(sortby != SORT_BY_NONE)
|
||
|
|
{
|
||
|
|
result.compareBy((SORT_ASCENDING(sortby) ? (Comparator *)(new Greater()) : (Comparator *)(new Lesser())));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
result.compareBy(NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
int m = getDimensionRange(dimension);
|
||
|
|
result.allocate(m);
|
||
|
|
int count = 0;
|
||
|
|
for(int i = 0; i < m; i++)
|
||
|
|
{
|
||
|
|
ArrayCopy(indices, consts);
|
||
|
|
indices[dimension] = i;
|
||
|
|
|
||
|
|
double v = getValue(indices);
|
||
|
|
if(SORT_VALUE(sortby))
|
||
|
|
{
|
||
|
|
result.insert(count, v, getDimensionIndexLabel(dimension, i));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
result.insert(count, getDimensionIndexLabel(dimension, i), v);
|
||
|
|
}
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
|
||
|
|
result.allocate(count);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class IdentityAggregator: public Aggregator<E>
|
||
|
|
{
|
||
|
|
private:
|
||
|
|
int size;
|
||
|
|
|
||
|
|
protected:
|
||
|
|
virtual int mixIndex(const int &k[/*0 - record number, 1 - field number*/]) const override
|
||
|
|
{
|
||
|
|
int result = 0;
|
||
|
|
for(int i = 0; i < size; i++)
|
||
|
|
{
|
||
|
|
result += k[i] * offsets[i];
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool decode(const int _input, int &k[]) const override
|
||
|
|
{
|
||
|
|
int index = _input;
|
||
|
|
ArrayResize(k, size);
|
||
|
|
ArrayInitialize(k, 0);
|
||
|
|
for(int i = 1; i >= 0; i--)
|
||
|
|
{
|
||
|
|
k[i] = index / offsets[i];
|
||
|
|
index -= k[i] * offsets[i];
|
||
|
|
}
|
||
|
|
|
||
|
|
if(index != 0)
|
||
|
|
{
|
||
|
|
Print("Bad index decode: ", _input);
|
||
|
|
ArrayPrint(k);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public:
|
||
|
|
IdentityAggregator(const E f, const Selector<E> *&s[], const Filter<E> *&t[]): Aggregator(f, s, t)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void setSelectorBounds(const int length = 0) override
|
||
|
|
{
|
||
|
|
size = 1 + (selectorCount > 1);
|
||
|
|
ArrayResize(dimensions, size);
|
||
|
|
int total = length * selectorCount;
|
||
|
|
dimensions[0] = length;
|
||
|
|
if(selectorCount > 1) dimensions[1] = selectorCount;
|
||
|
|
ArrayResize(totals, total);
|
||
|
|
ArrayInitialize(totals, 0);
|
||
|
|
|
||
|
|
ArrayResize(offsets, size);
|
||
|
|
offsets[0] = 1;
|
||
|
|
if(selectorCount > 1)
|
||
|
|
{
|
||
|
|
offsets[1] = dimensions[0];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void calculate(const Record *&data[]) override
|
||
|
|
{
|
||
|
|
int k[];
|
||
|
|
ArrayResize(k, size);
|
||
|
|
int n = ArraySize(data);
|
||
|
|
for(int i = 0; i < n; i++)
|
||
|
|
{
|
||
|
|
if(!filter(data[i])) continue;
|
||
|
|
|
||
|
|
k[0] = i;
|
||
|
|
for(int j = 0; j < selectorCount; j++)
|
||
|
|
{
|
||
|
|
if(selectorCount > 1) k[1] = j;
|
||
|
|
update(mixIndex(k), data[i].get(selectors[j].getField()));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void update(const int index, const double value) override
|
||
|
|
{
|
||
|
|
totals[index] = value;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getMetaCubeTitle() const override
|
||
|
|
{
|
||
|
|
return _typename + (field != FIELD_NONE ? " (field has no effect and ignored)" : "");
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getDimensionTitle(const int d) const override
|
||
|
|
{
|
||
|
|
if(d < 0)
|
||
|
|
{
|
||
|
|
return Aggregator<E>::getDimensionTitle(-d - 1);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
if(d == 0)
|
||
|
|
{
|
||
|
|
if(selectorCount > 1) return "index";
|
||
|
|
else return selectors[0].getTitle() + " by index";
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
string titles = "";
|
||
|
|
for(int i = 0; i < selectorCount; i++)
|
||
|
|
{
|
||
|
|
titles += (string)i + ":" + selectors[i].getTitle() + "; ";
|
||
|
|
}
|
||
|
|
return titles;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string getDimensionIndexLabel(const int d, const int index) const override
|
||
|
|
{
|
||
|
|
return "[" + (string)index + "]";
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class SumAggregator: public Aggregator<E>
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
SumAggregator(const E f, const Selector<E> *&s[], const Filter<E> *&t[]): Aggregator(f, s, t)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void update(const int index, const double value) override
|
||
|
|
{
|
||
|
|
totals[index] += value;
|
||
|
|
}
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class ProgressiveTotalAggregator: public Aggregator<E>
|
||
|
|
{
|
||
|
|
private:
|
||
|
|
double accumulators[];
|
||
|
|
|
||
|
|
public:
|
||
|
|
ProgressiveTotalAggregator(const E f, const Selector<E> *&s[], const Filter<E> *&t[]): Aggregator(f, s, t)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void setSelectorBounds(const int length = 0) override
|
||
|
|
{
|
||
|
|
Aggregator<E>::setSelectorBounds();
|
||
|
|
int dim = 1;
|
||
|
|
for(int i = 1; i < selectorCount; i++) // except 1-st dimention, for which progressive total is calculated
|
||
|
|
{
|
||
|
|
dim *= dimensions[i];
|
||
|
|
}
|
||
|
|
ArrayResize(accumulators, dim);
|
||
|
|
ArrayInitialize(accumulators, 0);
|
||
|
|
|
||
|
|
Converter<ulong,double> converter;
|
||
|
|
double nan = converter[0x7FF8000000000000]; // quiet NaN
|
||
|
|
ArrayInitialize(totals, nan);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void update(const int index, const double value) override
|
||
|
|
{
|
||
|
|
int subindex = 0;
|
||
|
|
if(selectorCount > 1)
|
||
|
|
{
|
||
|
|
int k[];
|
||
|
|
decode(index, k);
|
||
|
|
// eliminate 1-st dimension, special case of index mix
|
||
|
|
for(int i = 1; i < selectorCount; i++)
|
||
|
|
{
|
||
|
|
subindex += k[i] * (offsets[i] / dimensions[0]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
accumulators[subindex] += value;
|
||
|
|
totals[index] = accumulators[subindex];
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class AverageAggregator: public Aggregator<E>
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
int counters[];
|
||
|
|
|
||
|
|
public:
|
||
|
|
AverageAggregator(const E f, const Selector<E> *&s[], const Filter<E> *&t[]): Aggregator(f, s, t)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void setSelectorBounds(const int length = 0) override
|
||
|
|
{
|
||
|
|
Aggregator<E>::setSelectorBounds();
|
||
|
|
ArrayResize(counters, ArraySize(totals));
|
||
|
|
ArrayInitialize(counters, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void update(const int index, const double value) override
|
||
|
|
{
|
||
|
|
totals[index] = (totals[index] * counters[index] + value) / (counters[index] + 1);
|
||
|
|
counters[index]++;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class ProfitFactorAggregator: public Aggregator<E>
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
double positives[];
|
||
|
|
double negatives[];
|
||
|
|
|
||
|
|
public:
|
||
|
|
ProfitFactorAggregator(const E f, const Selector<E> *&s[], const Filter<E> *&t[]): Aggregator(f, s, t)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void setSelectorBounds(const int length = 0) override
|
||
|
|
{
|
||
|
|
Aggregator<E>::setSelectorBounds();
|
||
|
|
ArrayResize(positives, ArraySize(totals));
|
||
|
|
ArrayResize(negatives, ArraySize(totals));
|
||
|
|
ArrayInitialize(positives, 0);
|
||
|
|
ArrayInitialize(negatives, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void update(const int index, const double value) override
|
||
|
|
{
|
||
|
|
if(value >= 0) positives[index] += value;
|
||
|
|
else negatives[index] -= value;
|
||
|
|
|
||
|
|
if(negatives[index] > 0)
|
||
|
|
{
|
||
|
|
totals[index] = positives[index] / negatives[index];
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Converter<ulong,double> converter;
|
||
|
|
|
||
|
|
totals[index] = converter[0x7FF0000000000000]; // infinity
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class MaxAggregator: public Aggregator<E>
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
MaxAggregator(const E f, const Selector<E> *&s[], const Filter<E> *&t[]): Aggregator(f, s, t)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void update(const int index, const double value) override
|
||
|
|
{
|
||
|
|
totals[index] = MathMax(totals[index], value);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class MinAggregator: public Aggregator<E>
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
MinAggregator(const E f, const Selector<E> *&s[], const Filter<E> *&t[]): Aggregator(f, s, t)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void setSelectorBounds(const int length = 0) override
|
||
|
|
{
|
||
|
|
Aggregator<E>::setSelectorBounds();
|
||
|
|
ArrayInitialize(totals, DBL_MAX);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void update(const int index, const double value) override
|
||
|
|
{
|
||
|
|
totals[index] = MathMin(totals[index], value);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class CountAggregator: public Aggregator<E>
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
CountAggregator(const E f, const Selector<E> *&s[], const Filter<E> *&t[]): Aggregator(f, s, t)
|
||
|
|
{
|
||
|
|
_typename = typename(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void update(const int index, const double value) override
|
||
|
|
{
|
||
|
|
totals[index]++;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
#define TRADE_RECORD_FIELDS_NUMBER_DEFAULT 19
|
||
|
|
static TRADE_RECORD_FIELDS _f;
|
||
|
|
static int _dummy[];
|
||
|
|
const static int TRADE_RECORD_FIELDS_NUMBER = EnumToArray(_f, _dummy, 0, 100);
|
||
|
|
|
||
|
|
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_NUMBER_DEFAULT];
|
||
|
|
|
||
|
|
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_DATETIME1, OrderOpenTime());
|
||
|
|
set(FIELD_DATETIME2, OrderCloseTime());
|
||
|
|
set(FIELD_DURATION, OrderCloseTime() - OrderOpenTime());
|
||
|
|
set(FIELD_PRICE1, OrderOpenPrice());
|
||
|
|
set(FIELD_PRICE2, 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());
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
class _StaticCheck
|
||
|
|
{
|
||
|
|
private:
|
||
|
|
_StaticCheck()
|
||
|
|
{
|
||
|
|
// ASSERT that datatypes[] are provided for all TRADE_RECORD_FIELDS elements
|
||
|
|
if(TRADE_RECORD_FIELDS_NUMBER != TRADE_RECORD_FIELDS_NUMBER_DEFAULT)
|
||
|
|
{
|
||
|
|
Print("TRADE_RECORD_FIELDS cardinality mismatch: actual ", TRADE_RECORD_FIELDS_NUMBER, ", supposed ", TRADE_RECORD_FIELDS_NUMBER_DEFAULT);
|
||
|
|
Print("Execution stopped");
|
||
|
|
ExpertRemove();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void instantiate()
|
||
|
|
{
|
||
|
|
static _StaticCheck staticCheck;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
public:
|
||
|
|
static string realsymbol(const string symbol)
|
||
|
|
{
|
||
|
|
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_NUMBER)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
TradeRecord(const double balance): Record(TRADE_RECORD_FIELDS_NUMBER)
|
||
|
|
{
|
||
|
|
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_NUMBER)
|
||
|
|
{
|
||
|
|
return EnumToString((TRADE_RECORD_FIELDS)index);
|
||
|
|
}
|
||
|
|
return "unknown";
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual char datatype(const int index) const override
|
||
|
|
{
|
||
|
|
return datatypes[index];
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
class CustomTradeRecord: public TradeRecord
|
||
|
|
{
|
||
|
|
private:
|
||
|
|
// Start-up time announcement of custom fields specialization
|
||
|
|
class CustomFieldsDescription
|
||
|
|
{
|
||
|
|
private:
|
||
|
|
CustomFieldsDescription()
|
||
|
|
{
|
||
|
|
Print("Custom fields processing attached:");
|
||
|
|
Print("FIELD_CUSTOM1 == Single Trade MFE (%)");
|
||
|
|
Print("FIELD_CUSTOM2 == Single Trade MAE (%)");
|
||
|
|
}
|
||
|
|
|
||
|
|
public:
|
||
|
|
static CustomFieldsDescription *instantiate()
|
||
|
|
{
|
||
|
|
static CustomFieldsDescription instance;
|
||
|
|
return &instance;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
public:
|
||
|
|
CustomTradeRecord(): TradeRecord()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
CustomTradeRecord(const double balance): TradeRecord(balance)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
// calculate MFE/MAE in percents
|
||
|
|
virtual void fillCustomFields() override
|
||
|
|
{
|
||
|
|
static CustomFieldsDescription *legend = CustomFieldsDescription::instantiate();
|
||
|
|
|
||
|
|
double positiveExcursion = 0, negativeExcursion = 0;
|
||
|
|
string symbol = symbols[(int)get(FIELD_SYMBOL)];
|
||
|
|
int t1 = iBarShift(symbol, _Period, (datetime)get(FIELD_DATETIME1), false);
|
||
|
|
int t2 = iBarShift(symbol, _Period, (datetime)get(FIELD_DATETIME2), false);
|
||
|
|
int type = (int)get(FIELD_TYPE);
|
||
|
|
double open = get(FIELD_PRICE1);
|
||
|
|
double close = get(FIELD_PRICE2);
|
||
|
|
|
||
|
|
for(int t = t1; t >= t2; t--)
|
||
|
|
{
|
||
|
|
if(type == OP_BUY)
|
||
|
|
{
|
||
|
|
positiveExcursion = MathMax(positiveExcursion, MathMax((iHigh(symbol, _Period, t) - close), 0) / close);
|
||
|
|
negativeExcursion = MathMin(negativeExcursion, MathMin((iLow(symbol, _Period, t) - open), 0) / open);
|
||
|
|
}
|
||
|
|
else if(type == OP_SELL)
|
||
|
|
{
|
||
|
|
positiveExcursion = MathMax(positiveExcursion, MathMax((close - iLow(symbol, _Period, t)), 0) / close);
|
||
|
|
negativeExcursion = MathMin(negativeExcursion, MathMin((open - iHigh(symbol, _Period, t)), 0) / open);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
set(FIELD_CUSTOM1, positiveExcursion * 100);
|
||
|
|
set(FIELD_CUSTOM2, negativeExcursion * 100);
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual string legend(const int index) const override
|
||
|
|
{
|
||
|
|
if(index == FIELD_CUSTOM1) return "MFE per trade(%)";
|
||
|
|
else
|
||
|
|
if(index == FIELD_CUSTOM2) return "MAE per trade(%)";
|
||
|
|
|
||
|
|
return TradeRecord::legend(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_NUMBER_DEFAULT] =
|
||
|
|
{
|
||
|
|
0, // none
|
||
|
|
'i', // serial number
|
||
|
|
'i', // ticket
|
||
|
|
's', // symbol
|
||
|
|
'i', // type (OP_BUY/OP_SELL)
|
||
|
|
't', // open datetime
|
||
|
|
't', // close datetime
|
||
|
|
'i', // duration (seconds)
|
||
|
|
'd', // open price
|
||
|
|
'd', // close price
|
||
|
|
'i', // magic number
|
||
|
|
'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 HistoryTradeRecord: public T // CustomTradeRecord
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
HistoryTradeRecord()
|
||
|
|
{
|
||
|
|
fillCustomFields();
|
||
|
|
}
|
||
|
|
HistoryTradeRecord(const double balance): T(balance)
|
||
|
|
{
|
||
|
|
fillCustomFields();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename T>
|
||
|
|
class HistoryDataAdapter: public DataAdapter
|
||
|
|
{
|
||
|
|
private:
|
||
|
|
int size;
|
||
|
|
int cursor;
|
||
|
|
double balance;
|
||
|
|
|
||
|
|
protected:
|
||
|
|
void reset()
|
||
|
|
{
|
||
|
|
cursor = 0;
|
||
|
|
size = OrdersHistoryTotal();
|
||
|
|
balance = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
public:
|
||
|
|
HistoryDataAdapter()
|
||
|
|
{
|
||
|
|
reset();
|
||
|
|
T::reset();
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual int reservedSize()
|
||
|
|
{
|
||
|
|
return size;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual Record *getNext()
|
||
|
|
{
|
||
|
|
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 HistoryTradeRecord<T>(balance);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
class Display
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
virtual void display(MetaCube *metaData, const SORT_BY sortby = SORT_BY_NONE, const bool identity = false) = 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
class LogDisplay: public Display
|
||
|
|
{
|
||
|
|
private:
|
||
|
|
string format;
|
||
|
|
int digits;
|
||
|
|
|
||
|
|
public:
|
||
|
|
LogDisplay(const int w, const int d)
|
||
|
|
{
|
||
|
|
digits = d;
|
||
|
|
format = StringFormat("%%%d.%df", w, d);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
virtual void display(MetaCube *metaData, const SORT_BY sortby = SORT_BY_NONE, const bool identity = false) override
|
||
|
|
{
|
||
|
|
int n = metaData.getDimension();
|
||
|
|
int indices[], cursors[];
|
||
|
|
ArrayResize(indices, n);
|
||
|
|
ArrayResize(cursors, n);
|
||
|
|
ArrayInitialize(cursors, 0);
|
||
|
|
|
||
|
|
Print(metaData.getMetaCubeTitle(), " [", metaData.getCubeSize(), "]");
|
||
|
|
|
||
|
|
bool sorting = n == 1 && sortby != SORT_BY_NONE;
|
||
|
|
|
||
|
|
PairArray *flat = NULL;
|
||
|
|
|
||
|
|
if(sorting)
|
||
|
|
{
|
||
|
|
flat = new PairArray(metaData.getCubeSize(), (SORT_ASCENDING(sortby) ? (Comparator *)(new Greater()) : (Comparator *)(new Lesser())));
|
||
|
|
}
|
||
|
|
|
||
|
|
for(int i = 0; i < n; i++)
|
||
|
|
{
|
||
|
|
indices[i] = metaData.getDimensionRange(i);
|
||
|
|
Print(CharToString((uchar)('X' + i)), ": ", metaData.getDimensionTitle(i), " [", indices[i], "]");
|
||
|
|
}
|
||
|
|
|
||
|
|
string labels[];
|
||
|
|
ArrayResize(labels, n);
|
||
|
|
|
||
|
|
bool looping = false;
|
||
|
|
int count = 0;
|
||
|
|
do
|
||
|
|
{
|
||
|
|
for(int j = 0; j < n; j++)
|
||
|
|
{
|
||
|
|
labels[j] = metaData.getDimensionIndexLabel(j, cursors[j]);
|
||
|
|
}
|
||
|
|
|
||
|
|
if(sorting)
|
||
|
|
{
|
||
|
|
// sort single (first) dimension by sort_by
|
||
|
|
if(SORT_VALUE(sortby))
|
||
|
|
{
|
||
|
|
flat.insert(count++, metaData.getValue(cursors), labels[0]);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
flat.insert(count++, labels[0], metaData.getValue(cursors));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
arrayPrint(StringFormat(format, metaData.getValue(cursors)), labels);
|
||
|
|
}
|
||
|
|
|
||
|
|
for(int i = 0; i < n; i++)
|
||
|
|
{
|
||
|
|
if(cursors[i] < indices[i] - 1)
|
||
|
|
{
|
||
|
|
looping = true;
|
||
|
|
cursors[i]++;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
cursors[i] = 0;
|
||
|
|
}
|
||
|
|
looping = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
while(looping && !IsStopped());
|
||
|
|
|
||
|
|
if(sorting)
|
||
|
|
{
|
||
|
|
ArrayPrint(flat.array, digits);
|
||
|
|
delete flat;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
template<typename T>
|
||
|
|
bool Comparator::compare(const Pair &v1, const T v2)
|
||
|
|
{
|
||
|
|
Greater *g = dynamic_cast<Greater *>(&this);
|
||
|
|
if(g != NULL) return g.compare(v1, v2);
|
||
|
|
Lesser *l = dynamic_cast<Lesser *>(&this);
|
||
|
|
if(l != NULL) return l.compare(v1, v2);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
template<typename T>
|
||
|
|
void arrayPrint(const string title, const T &V[])
|
||
|
|
{
|
||
|
|
int n = ArraySize(V);
|
||
|
|
string s;
|
||
|
|
for(int i = 0; i < n; i++)
|
||
|
|
{
|
||
|
|
s = s + " " + (string)V[i];
|
||
|
|
}
|
||
|
|
Print(title + ":" + s);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
template<typename E>
|
||
|
|
class Analyst
|
||
|
|
{
|
||
|
|
private:
|
||
|
|
DataAdapter *adapter;
|
||
|
|
Record *data[];
|
||
|
|
Aggregator<E> *aggregator;
|
||
|
|
Display *output;
|
||
|
|
|
||
|
|
public:
|
||
|
|
Analyst(DataAdapter &a, Aggregator<E> &g, Display &d): adapter(&a), aggregator(&g), output(&d)
|
||
|
|
{
|
||
|
|
ArrayResize(data, adapter.reservedSize());
|
||
|
|
}
|
||
|
|
|
||
|
|
~Analyst()
|
||
|
|
{
|
||
|
|
int n = ArraySize(data);
|
||
|
|
for(int i = 0; i < n; i++)
|
||
|
|
{
|
||
|
|
if(CheckPointer(data[i]) == POINTER_DYNAMIC) delete data[i];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void acquireData()
|
||
|
|
{
|
||
|
|
Record *record;
|
||
|
|
int i = 0;
|
||
|
|
while((record = adapter.getNext()) != NULL)
|
||
|
|
{
|
||
|
|
data[i++] = record;
|
||
|
|
}
|
||
|
|
ArrayResize(data, i);
|
||
|
|
|
||
|
|
aggregator.prepareSelectors(data);
|
||
|
|
|
||
|
|
aggregator.setSelectorBounds(i);
|
||
|
|
}
|
||
|
|
|
||
|
|
void build()
|
||
|
|
{
|
||
|
|
aggregator.calculate(data);
|
||
|
|
}
|
||
|
|
|
||
|
|
void display(const SORT_BY sortby = SORT_BY_NONE, const bool identity = false)
|
||
|
|
{
|
||
|
|
output.display(aggregator, sortby, identity);
|
||
|
|
}
|
||
|
|
};
|