//+------------------------------------------------------------------+ //| 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 #include #include #include #include #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 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 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 { 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 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 { public: WeekDaySelector(const TRADE_RECORD_FIELDS f): DateTimeSelector(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 { public: DayHourSelector(const TRADE_RECORD_FIELDS f): DateTimeSelector(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 { public: HourMinuteSelector(const TRADE_RECORD_FIELDS f): DateTimeSelector(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 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 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 class Filter { protected: Selector *selector; double filter; public: Filter(Selector &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 *getSelector() const { return selector; } virtual string getTitle() const { return selector.getTitle() + "[" + (string)filter + "]"; } }; template class FilterRange: public Filter { protected: double filterMax; public: FilterRange(Selector &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 class Aggregator: public MetaCube { protected: const E field; const int selectorCount; const Selector *selectors[]; const int filterCount; const Filter *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 *&s[], const Filter *&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 *s = (Selector *)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 class IdentityAggregator: public Aggregator { 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 *&s[], const Filter *&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::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 class SumAggregator: public Aggregator { public: SumAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) { _typename = typename(this); } virtual void update(const int index, const double value) override { totals[index] += value; } }; template class ProgressiveTotalAggregator: public Aggregator { private: double accumulators[]; public: ProgressiveTotalAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) { _typename = typename(this); } virtual void setSelectorBounds(const int length = 0) override { Aggregator::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 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 class AverageAggregator: public Aggregator { protected: int counters[]; public: AverageAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) { _typename = typename(this); } virtual void setSelectorBounds(const int length = 0) override { Aggregator::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 class ProfitFactorAggregator: public Aggregator { protected: double positives[]; double negatives[]; public: ProfitFactorAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) { _typename = typename(this); } virtual void setSelectorBounds(const int length = 0) override { Aggregator::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 converter; totals[index] = converter[0x7FF0000000000000]; // infinity } } }; template class MaxAggregator: public Aggregator { public: MaxAggregator(const E f, const Selector *&s[], const Filter *&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 class MinAggregator: public Aggregator { public: MinAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) { _typename = typename(this); } virtual void setSelectorBounds(const int length = 0) override { Aggregator::setSelectorBounds(); ArrayInitialize(totals, DBL_MAX); } virtual void update(const int index, const double value) override { totals[index] = MathMin(totals[index], value); } }; template class CountAggregator: public Aggregator { public: CountAggregator(const E f, const Selector *&s[], const Filter *&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 symbols; static Vocabulary magics; static int counter; static IndexMap symbol2symbol; static Vocabulary 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 TradeRecord::symbols; static Vocabulary TradeRecord::magics; static int TradeRecord::counter = 0; static IndexMap TradeRecord::symbol2symbol; static Vocabulary TradeRecord::missing; const static char TradeRecord::datatypes[TRADE_RECORD_FIELDS_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 class HistoryTradeRecord: public T // CustomTradeRecord { public: HistoryTradeRecord() { fillCustomFields(); } HistoryTradeRecord(const double balance): T(balance) { fillCustomFields(); } }; template 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(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 bool Comparator::compare(const Pair &v1, const T v2) { Greater *g = dynamic_cast(&this); if(g != NULL) return g.compare(v1, v2); Lesser *l = dynamic_cast(&this); if(l != NULL) return l.compare(v1, v2); return false; } template 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 class Analyst { private: DataAdapter *adapter; Record *data[]; Aggregator *aggregator; Display *output; public: Analyst(DataAdapter &a, Aggregator &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); } };