Files

1699 lines
41 KiB
Plaintext
Raw Permalink Normal View History

2025-02-07 19:15:50 +03:30
//+------------------------------------------------------------------+
//| 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);
}
};