//+------------------------------------------------------------------+ //| TradeFilter.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "MapArray.mqh" #include "QuickSortTm.mqh" #include "Defines.mqh" #include "IS.mqh" //+------------------------------------------------------------------+ //| Class for collecting orders/deals/positions and their properties | //| if they match specific conditions. | //| Type T should be one of: | //| #include "PositionMonitor.mqh" | //| #include "DealMonitor.mqh" | //| #include "OrderMonitor.mqh" | //+------------------------------------------------------------------+ template class TradeFilter { protected: MapArray longs; MapArray doubles; MapArray strings; MapArray conditions; static bool eps(const double v1, const double v2) { return fabs(v1 - v2) < DBL_EPSILON * fmax(v1, v2); } template static bool eps(const V v1, const V v2) { return false; } template static bool equal(const V v1, const V v2) { return v1 == v2 || eps(v1, v2); } template static bool greater(const V v1, const V v2) { return v1 > v2; } static bool bitwise_or(const long v1, const long v2) { return (((1 << v1) & v2) != 0); } static bool bitwise_or(const string v1, const string v2) { return false; } template static bool bitwise_or(const V v1, const V v2) { return false; } static bool equal(const string v1, const string v2) { if(StringFind(v2, "*") > -1) { int previous = 0; string words[]; const int n = StringSplit(v2, '*', words); for(int i = 0; i < n; ++i) { if(StringLen(words[i]) == 0) continue; int index = StringFind(v1, words[i], previous); if(index == -1) { return false; } previous = index + StringLen(words[i]); } return true; } return v1 == v2; } template bool match(const T &m, const MapArray &data) const { static const V type = (V)NULL; int or_totals = 0, or_matches = 0; for(int i = 0; i < data.getSize(); ++i) { const ENUM_ANY key = data.getKey(i); switch(conditions[key]) { case EQUAL_OR_ZERO: if(equal(m.get(key, type), type)) { continue; // zero is acceptable } // otherwise fallthrough case EQUAL: if(!equal(m.get(key, type), data.getValue(i))) { return false; } break; case NOT_EQUAL: if(equal(m.get(key, type), data.getValue(i))) { return false; } break; case OR_EQUAL: or_totals++; if(equal(m.get(key, type), data.getValue(i))) { or_matches++; } break; case OR_BITWISE: if(!bitwise_or(m.get(key, type), data.getValue(i))) { return false; } break; case GREATER: if(!greater(m.get(key, type), data.getValue(i))) { return false; } break; case LESS: if(greater(m.get(key, type), data.getValue(i))) { return false; } break; } } if(or_totals > 0) return or_matches > 0; return true; } template void sortTuple(U &data[], const T1 dummy) const { // we need aux array to keep track of original locations // to reorder records according to new sequence T1 array[][2]; const int p = ArraySize(data); ArrayResize(array, p); for(int i = 0; i < p; ++i) { array[i][0] = data[i]._1; array[i][1] = (T1)i; } ArraySort(array); U copy[]; // make a copy of original array and then // place elements from it into original array on new locations // ArrayCopy(copy, data); is not applicable for possible strings ArrayResize(copy, p); for(int i = 0; i < p; ++i) { copy[i] = data[i]; } for(int i = 0; i < p; ++i) { data[i] = copy[(int)array[i][1]]; } } virtual int total() const = 0; virtual ulong get(const int i) const = 0; public: TradeFilter *let(const I property, const long value, const IS cmp = EQUAL) { longs.put((ENUM_ANY)property, value); conditions.put((ENUM_ANY)property, cmp); return &this; } TradeFilter *let(const D property, const double value, const IS cmp = EQUAL) { doubles.put((ENUM_ANY)property, value); conditions.put((ENUM_ANY)property, cmp); return &this; } TradeFilter *let(const S property, const string value, const IS cmp = EQUAL) { strings.put((ENUM_ANY)property, value); conditions.put((ENUM_ANY)property, cmp); return &this; } template bool select(const E property, ulong &tickets[], V &data[], const bool sort = false) const { E properties[1] = {property}; V tuples[][1]; const bool result = select(properties, tickets, tuples, sort); ArrayCopy(data, tuples); return result; } // we need this overload because built-in ArraySort // does not support arrays of strings void ArraySort(string &s[][]) const { QuickSortTm qt(s); } // a handy implementation of select for Tuples with shortened assign(m) template // U is expected to be a custom Tuple bool select(U &data[], const bool sort = false) const { const int n = total(); ArrayResize(data, 0); // loop through items for(int i = 0; i < n; ++i) { const ulong t = get(i); // access all properties via monitor T m(t); // check all filtering conditions if(match(m, longs) && match(m, doubles) && match(m, strings)) { // for a matching item, feed its properties into output array const int k = EXPAND(data); data[k].assign(m); } } if(sort) { static const U u; sortTuple(data, u._1); } return true; } template // U is expected to be a Tuple<>, for example Tuple3 bool select(const int &property[], U &data[], const bool sort = false) const { const int q = ArraySize(property); static const U u; // U::size() does not compile if(q != u.size()) return false; // constraint const int n = total(); ArrayResize(data, 0); // loop through items for(int i = 0; i < n; ++i) { const ulong t = get(i); // access all properties via monitor T m(t); // check all filtering conditions if(match(m, longs) && match(m, doubles) && match(m, strings)) { // for a matching item, feed its properties into output array const int k = EXPAND(data); data[k].assign(property, m); } } if(sort) { sortTuple(data, u._1); } return true; } template bool select(const E &property[], ulong &tickets[], V &data[][], const bool sort = false) const { // size of array with properties must match output tuple size const int q = ArrayRange(data, 1); if(ArraySize(property) != q) return false; // error const int n = total(); ArrayResize(tickets, 0); ArrayResize(data, 0); // loop through items for(int i = 0; i < n; ++i) { const ulong t = get(i); // access all properties via monitor T m(t); // check all filtering conditions if(match(m, longs) && match(m, doubles) && match(m, strings)) { // for a matching item, feed its ticket and properties into output arrays const int k = EXPAND(data); for(int j = 0; j < q; ++j) { data[k][j] = m.get(property[j]); } PUSH(tickets, t); } } if(sort) { // we need aux array to keep track of original locations // to reorder records according to new sequence V array[][2]; const int p = ArrayRange(data, 0); ArrayResize(array, p); for(int i = 0; i < p; ++i) { array[i][0] = data[i][0]; array[i][1] = (V)i; } ArraySort(array); ulong temp[]; V array2d[]; // make a flat copy of original array and then // place elements from it into original array on new locations ArrayCopy(array2d, data); for(int i = 0; i < p; ++i) { const int k = (int)array[i][1]; PUSH(temp, tickets[k]); for(int j = 0; j < q; ++j) { data[i][j] = array2d[k * q + j]; } } ArraySwap(tickets, temp); } return true; } bool select(ulong &tickets[]) const { const int n = total(); ArrayResize(tickets, 0); for(int i = 0; i < n; ++i) { const ulong t = get(i); T m(t); if(match(m, longs) && match(m, doubles) && match(m, strings)) { PUSH(tickets, t); } } return ArraySize(tickets) > 0; } }; //+------------------------------------------------------------------+