Files
include/OLAP/OLAPCommon.mqh
T
2025-02-07 19:13:59 +03:30

1830 lines
92 KiB
Plaintext

//+------------------------------------------------------------------+
//| OLAPCommon.mqh |
//| Copyright © 2016-2020, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//| Online Analytical Processing of trading hypercubes |
//| https://www.mql5.com/en/articles/6602 |
//| https://www.mql5.com/en/articles/6603 |
//| https://www.mql5.com/en/articles/7535 |
//| https://www.mql5.com/en/articles/7656 |
//| rev. 25.02.2020 |
//+------------------------------------------------------------------+
#include <Marketeer/IndexMap.mqh>
#include <MT4Bridge/MT4Time.mqh>
#include <Marketeer/Converter.mqh>
#include <OLAP/PairArray.mqh>
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
AGGREGATOR_STDDEV // DEVIATION
};
enum DATA_TYPES
{
DATA_TYPE_NONE,
DATA_TYPE_NUMBER = 'd',
DATA_TYPE_INTEGER = 'i',
DATA_TYPE_TIME = 't',
DATA_TYPE_STRING = 's'
};
int MathSign(const double x)
{
return x > 0 ? +1 : (x < 0 ? -1 : 0);
}
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;
}
template<typename T>
static char datatype2(const int index)
{
return T::datatype(index);
}
template<typename E>
static string legendFromEnum(E e)
{
string text = EnumToString(e);
// don't return NULL as a flag of overflow (custom fields not in enum)
// better to show lengthy wording then empty space
if(StringFind(text, "::") > 0) return text;
StringToLower(text);
StringReplace(text, "_", " ");
const string field = "field ";
if(StringFind(text, field) == 0)
{
return StringSubstr(text, StringLen(field));
}
return text;
}
static char datatype(const int index)
{
return 0;
}
virtual void fillCustomFields() {/* does nothing */};
};
// single pass data reader
class DataAdapter
{
public:
virtual void reset() = 0;
virtual Record *getNext() = 0;
virtual int reservedSize() const = 0;
virtual bool isOwner() const = 0;
virtual int getFieldCount() const = 0;
virtual int getCustomFieldCount() const { return 0; };
virtual int getCustomFields(string &names[]) const { return 0; };
};
interface Progress
{
void progress(const int cursor, const int total) const;
};
class PackedEnum
{
public:
static bool type;
};
static bool PackedEnum::type = true;
template<typename E,typename PACKED>
int EnumToArray(E dummy, int &values[], const int start = 0, 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;
}
else if(PACKED::type)
{
break;
}
}
return count;
}
template<typename E,typename PACKED>
int EnumSize(E dummy, const int start = 0, const int stop = INT_MAX)
{
string t = typename(E) + "::";
int length = StringLen(t);
int count = 0;
for(int i = start; i < stop && !IsStopped(); i++)
{
E e = (E)i;
if(StringCompare(StringSubstr(EnumToString(e), 0, length), t) != 0)
{
count++;
}
else if(PACKED::type)
{
break;
}
}
return count;
}
// COMMON SELECTORS
template<typename E>
class Selector
{
protected:
E selector;
string _typename;
int size;
static bool shortTitles;
public:
Selector(const E field): selector(field)
{
size = EnumSize<E,PackedEnum>(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
{
if(shortTitles)
{
return Record::legendFromEnum(selector);
}
return _typename + "(" + EnumToString(selector) + ")";
}
virtual int getFieldSize() const
{
return size;
}
static void setShortTitles(const bool t)
{
shortTitles = t;
}
};
template<typename E>
static bool Selector::shortTitles = false;
template<typename T>
class BaseSelector: public Selector<T>
{
public:
BaseSelector(const T 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"
}
};
template<typename T>
class FilterSelector: public BaseSelector<T>
{
public:
FilterSelector(const T field): BaseSelector(field)
{
_typename = typename(this);
}
virtual bool select(const Record *r, int &index) const override
{
index = -1;
return true;
}
virtual int getRange() const override
{
return 0;
}
virtual double getMin() const override
{
return 0;
}
virtual double getMax() const override
{
return 0;
}
virtual string getLabel(const int index) const override
{
return EnumToString(selector);
}
};
template<typename E>
class DateTimeSelector: public BaseSelector<E>
{
protected:
int granularity;
public:
DateTimeSelector(const E field, const int gsize): BaseSelector(field), granularity(gsize)
{
_typename = typename(this);
}
virtual int getRange() const
{
return granularity;
}
};
template<typename E>
class MonthSelector: public DateTimeSelector<E>
{
public:
MonthSelector(const E f): DateTimeSelector(f, 12)
{
_typename = typename(this);
}
virtual bool select(const Record *r, int &index) const
{
double d = r.get(selector);
datetime t = (datetime)d;
index = TimeMonth(t) - 1;
return true;
}
virtual string getLabel(const int index) const
{
static string months[12] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
return months[index];
}
};
template<typename E>
class WeekDaySelector: public DateTimeSelector<E>
{
public:
WeekDaySelector(const E 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] = {"7`Sunday", "1`Monday", "1`Tuesday", "3`Wednesday", "4`Thursday", "5`Friday", "6`Saturday"};
return days[index];
}
};
template<typename E>
class WorkWeekDaySelector: public DateTimeSelector<E>
{
public:
WorkWeekDaySelector(const E f): DateTimeSelector(f, 5)
{
_typename = typename(this);
}
virtual bool select(const Record *r, int &index) const
{
double d = r.get(selector);
datetime t = (datetime)d;
index = TimeDayOfWeek(t) - 1;
return (index >= 0 && index < 5);
}
virtual string getLabel(const int index) const
{
static string days[5] = {"1`Monday", "2`Tuesday", "3`Wednesday", "4`Thursday", "5`Friday"};
return days[index];
}
};
template<typename E>
class DayHourSelector: public DateTimeSelector<E>
{
public:
DayHourSelector(const E 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 StringFormat("%02d", index);
}
};
template<typename E>
class HourMinuteSelector: public DateTimeSelector<E>
{
public:
HourMinuteSelector(const E 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 StringFormat("%02d", index);
}
};
// aux class for QuantizationSelector below
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);
}
};
template<typename T>
class QuantizationSelector: public BaseSelector<T>
{
protected:
Vocabulary<double> quants;
uint cell;
double min, max;
public:
QuantizationSelector(const T field, const uint granularity = 0): BaseSelector<T>(field), cell(granularity)
{
_typename = typename(this);
min = DBL_MAX;
max = -DBL_MAX;
}
virtual void prepare(const Record *r) override
{
double value = r.get(selector);
if(cell != 0) value = MathSign(value) * MathFloor(MathAbs(value) / cell) * cell;
quants.add(value);
double abs = MathAbs(value);
if(abs > max) max = abs;
abs = NormalizeDouble(abs - MathFloor(abs), 8);
if(abs > 0.0000001 && abs < min) min = abs;
}
virtual bool select(const Record *r, int &index) const override
{
double value = r.get(selector);
if(cell != 0) value = MathSign(value) * MathFloor(MathAbs(value) / cell) * cell;
index = quants.get(value);
return (index >= 0);
}
virtual int getRange() const override
{
return quants.size();
}
virtual string getLabel(const int index) const override
{
int minbase = 0;
int maxbase = 0;
if(min > 0 && min != DBL_MAX)
{
double b1 = NormalizeDouble(min, (int)MathRound(MathAbs(MathLog10(min)) + 0.5));
if(b1 > 0) minbase = (int)MathAbs(MathFloor(MathLog10(b1)));
}
if(max > 0)
{
maxbase = (int)MathFloor(MathLog10(max));
}
string format; // %5.2f
format = StringFormat("%c%d.%df", '%', ((int)MathAbs(minbase) + (int)MathAbs(maxbase) + 3), (int)MathAbs(minbase));
string result = StringFormat(format, quants[index]);
return result;
}
};
template<typename T,typename R>
class SerialNumberSelector: public BaseSelector<T>
{
public:
SerialNumberSelector(const T field): BaseSelector<T>(field)
{
_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 R::getRecordCount();
}
virtual string getLabel(const int index) const override
{
return (string)(index);
}
};
// FILTERS
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 == -1)
{
if(dynamic_cast<FilterSelector<E> *>(selector) != NULL)
{
return r.get(selector.getField()) == filter;
}
}
else
{
if(index == (int)filter) return true;
}
}
return false;
}
Selector<E> *getSelector() const
{
return selector;
}
virtual string getTitle() const
{
return "Filter::" + 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 == -1)
{
if(dynamic_cast<FilterSelector<E> *>(selector) != NULL)
{
const double v = r.get(selector.getField());
if(filterMax > filter)
{
return v >= filter && v < filterMax; // range [;) is included
}
else
{
return v >= filter || v < filterMax; // range [;) is excluded
}
}
}
else
{
if(filterMax > filter)
{
return index >= (int)filter && index < (int)filterMax;
}
else
{
return index >= (int)filter || index < (int)filterMax;
}
}
}
return false;
}
virtual string getTitle() const override
{
return "FilterRange::" + 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) > 0 && (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 bool shortNames = false) 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 int getDimensionField(const int d) const = 0;
virtual bool hasSpecialFormat() const
{
return false;
}
virtual string getValueFormatted(const double value) const
{
return (string)value; // stub
}
//virtual string getDimensionFormat(const int d) const = 0;
virtual bool isSerial() const
{
return false;
}
};
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[];
string customNames[];
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);
dimensions[0] = length == 0 ? selectors[0].getRange() : length;
int total = dimensions[0];
for(int i = 1; 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 int prepareSelectors(const Record *&data[])
{
int n = ArraySize(data);
int count = 0;
for(int i = 0; i < n; i++)
{
if(!filter(data[i])) continue;
for(int j = 0; j < selectorCount; j++)
{
Selector<E> *s = (Selector<E> *)selectors[j];
s.prepare(data[i]);
}
count++;
}
return count;
}
// build an array with number of dimensions equal to number of selectors
virtual int calculate(const Record *&data[], const Progress *callback = NULL)
{
int k[];
int processed = 0;
ArrayResize(k, selectorCount);
int n = ArraySize(data);
for(int i = 0; i < n && !IsStopped(); 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
processed++;
}
if(callback != NULL) callback.progress(i, n);
}
return processed;
}
double getValue(const int &indices[]) const override
{
return totals[mixIndex(indices)];
}
virtual string getMetaCubeTitle(const bool shortNames = false) const override
{
const int size = EnumSize<E,PackedEnum>(field);
if(field >= size && field - size < ArraySize(customNames))
{
return _typename + " " + customNames[field - size];
}
return _typename + " " + (shortNames ? Record::legendFromEnum(field) : EnumToString(field));
// return _typename + " " + EnumToString(field);
}
virtual string getDimensionTitle(const int d) const override
{
if(d >= ArraySize(selectors)) return "n/a";
string title = selectors[d].getTitle();
if(StringFind(title, "::") > 0) // not found in enum, assume dynamically added custom field
{
const int n = getDimensionField(d);
const int m = selectors[d].getFieldSize();
if(n != -1 && n >= m && n - m < ArraySize(customNames))
{
return customNames[n - m];
}
}
return title;
}
virtual int getDimensionField(const int d) const override
{
if(d >= ArraySize(selectors)) return -1;
return (int)selectors[d].getField();
}
/*
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;
}
virtual void assignCustomFields(const string &fields[])
{
ArrayCopy(customNames, fields);
}
};
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 int calculate(const Record *&data[], const Progress *callback = NULL) override
{
int k[];
int processed = 0;
ArrayResize(k, size);
int n = ArraySize(data);
for(int i = 0; i < n && !IsStopped(); i++)
{
if(!filter(data[i])) continue;
k[0] = processed; // i;
for(int j = 0; j < selectorCount; j++)
{
if(selectorCount > 1) k[1] = j;
update(mixIndex(k), data[i].get(selectors[j].getField()));
}
processed++;
if(callback != NULL) callback.progress(i, n);
}
return processed;
}
virtual void update(const int index, const double value) override
{
totals[index] = value;
}
virtual string getMetaCubeTitle(const bool shortNames = false) 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 int getDimensionField(const int d) const override
{
return Aggregator<E>::getDimensionField((d < 0 ? -d - 1 : d));
}
virtual string getDimensionIndexLabel(const int d, const int index) const override
{
return "[" + (string)index + "]";
}
virtual bool isSerial() const override
{
return true;
}
};
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(length);
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;
const double nan = converter[0x7FF8000000000000]; // quiet NaN
ArrayInitialize(totals, nan);
}
virtual int calculate(const Record *&data[], const Progress *callback = NULL) override
{
int k[];
int processed = 0;
int cursor = 0;
ArrayResize(k, selectorCount);
int n = ArraySize(data);
for(int i = 0; i < n && !IsStopped(); 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] = j == 0 ? cursor : 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
processed++;
}
cursor++;
if(callback != NULL) callback.progress(i, n);
}
return processed;
}
virtual void update(const int index, const double value) override
{
if(index < 0 || index >= ArraySize(totals))
{
Print(__FUNCSIG__, ": Index out of bound: ", index, " size: ", ArraySize(totals));
}
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];
}
virtual bool isSerial() const override
{
return true;
}
};
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 VarianceAggregator: public Aggregator<E>
{
protected:
int counters[];
double sumx[];
double sumx2[];
public:
VarianceAggregator(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));
ArrayResize(sumx, ArraySize(totals));
ArrayResize(sumx2, ArraySize(totals));
ArrayInitialize(counters, 0);
ArrayInitialize(sumx, 0);
ArrayInitialize(sumx2, 0);
}
/*
union floats
{
double d1;
struct f2
{
float mean;
float deviation;
}
ff;
};
*/
virtual void update(const int index, const double value) override
{
// TODO: probably replace with Welford's algorithm
counters[index]++;
sumx[index] += value;
sumx2[index] += value * value;
const int n = counters[index];
// const double mean = sumx[index] / n;
const double variance = (sumx2[index] - sumx[index] * sumx[index] / n) / MathMax(n - 1, 1);
/*
floats f;
f.ff.mean = (float)mean;
f.ff.deviation = (float)MathSqrt(variance);
totals[index] = f.d1;
*/
totals[index] = MathSqrt(variance);
}
/*
virtual bool hasSpecialFormat() const override
{
return true;
}
virtual string getValueFormatted(const double value) const override
{
floats f;
f.d1 = value;
return (f.ff.mean > 0 ? "+" : "") + (string)f.ff.mean + ShortToString(0x0B1) + (string)f.ff.deviation;
}
*/
};
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]++;
}
};
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;
bool dropZeros;
public:
LogDisplay(const int w, const int d, const bool omitZeros = false)
{
digits = d;
format = StringFormat("%%%d.%df", w, d);
dropZeros = omitZeros;
}
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);
for(int i = 0; i < n; i++)
{
indices[i] = metaData.getDimensionRange(i);
}
if(metaData.getCubeSize() == 0)
{
Print("[empty]");
return;
}
bool sorting = (n == 1 && sortby != SORT_BY_NONE) || SORT_VALUE(sortby);
if(n > 1 && !sorting)
{
Print("Sorting of multidimentional arrays is fully supported for values only, labels are sorted as is");
}
PairArray *flat = NULL;
if(sorting)
{
flat = new PairArray(metaData.getCubeSize(), (SORT_ASCENDING(sortby) ? (Comparator *)(new Greater()) : (Comparator *)(new Lesser())));
}
string labels[];
string allLabels;
ArrayResize(labels, n);
bool looping = false;
int count = 0;
Converter<ulong,double> converter;
const double nan = converter[0x7FF8000000000000]; // quiet NaN
do
{
allLabels = "";
for(int j = 0; j < n; j++)
{
labels[j] = metaData.getDimensionIndexLabel(j, cursors[j]);
allLabels += (j > 0 ? ";\t" : "") + labels[j];
}
if(!dropZeros || (metaData.getValue(cursors) != 0 && MathIsValidNumber(metaData.getValue(cursors))))
{
if(sorting)
{
// sort single (first) dimension by sort_by
if(SORT_VALUE(sortby))
{
flat.insert(count++, metaData.getValue(cursors), allLabels);
}
else
{
flat.insert(count++, allLabels, metaData.getValue(cursors));
}
}
else
{
if(metaData.hasSpecialFormat())
{
arrayPrint(metaData.getValueFormatted(metaData.getValue(cursors)), labels);
}
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)
{
flat.allocate(count); // shrink (if neccessary)
if(metaData.hasSpecialFormat())
{
for(int i = 0; i < ArraySize(flat.array); i++)
{
Print(i, ": ", flat.array[i].title, " ", metaData.getValueFormatted(flat.array[i].value));
}
}
else
{
ArrayPrint(flat.array, digits);
}
delete flat;
}
}
};
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;
bool dataOwner;
public:
Analyst(DataAdapter &a, Aggregator<E> &g, Display &d): adapter(&a), aggregator(&g), output(&d)
{
ArrayResize(data, adapter.reservedSize());
dataOwner = false;
}
~Analyst()
{
if(dataOwner)
{
const int n = ArraySize(data);
for(int i = 0; i < n; i++)
{
if(CheckPointer(data[i]) == POINTER_DYNAMIC) delete data[i];
}
}
}
void setDataOwner(const bool owner)
{
dataOwner = owner;
}
void acquireData()
{
Record *record;
int i = 0;
dataOwner = !adapter.isOwner();
adapter.reset();
while((record = adapter.getNext()) != NULL)
{
data[i++] = record;
}
ArrayResize(data, i);
const int n = aggregator.prepareSelectors(data);
if(aggregator.isSerial())
{
aggregator.setSelectorBounds(n);
}
else
{
aggregator.setSelectorBounds();
}
string names[];
if(adapter.getCustomFields(names) > 0)
{
aggregator.assignCustomFields(names);
}
}
int build(const Progress *ptr = NULL)
{
return aggregator.calculate(data, ptr);
}
void display(const SORT_BY sortby = SORT_BY_NONE, const bool identity = false)
{
output.display(aggregator, sortby, identity);
}
};
template<typename S,typename T>
class OLAPEngine
{
protected:
DataAdapter *adapter;
const Progress *progress;
uint quantGranularity;
bool shortTitles;
virtual Selector<T> *createSelector(const S selector, const T field) = 0;
virtual void initialize() = 0;
public:
OLAPEngine(): adapter(NULL), progress(NULL), shortTitles(false) {}
OLAPEngine(DataAdapter *ptr, const uint quant = 0, const Progress *show = NULL):
adapter(ptr), quantGranularity(quant), progress(show), shortTitles(false) {}
void setAdapter(DataAdapter *ptr)
{
adapter = ptr;
}
void setProgress(Progress *ptr)
{
progress = ptr;
}
void setQuant(const uint quant)
{
quantGranularity = quant; // FIXME: need to be selector property
}
void setShortTitles(const bool t)
{
shortTitles = t;
Selector<T>::setShortTitles(t);
}
int process(
const S &selectorArray[], const T &selectorField[],
const AGGREGATORS AggregatorType, const T AggregatorField,
Display &display,
const SORT_BY SortBy = SORT_BY_NONE,
const double Filter1value1 = 0, const double Filter1value2 = 0)
{
int selectorCount = 0;
for(int i = 0; i < MathMin(ArraySize(selectorArray), 3); i++)
{
selectorCount += selectorArray[i] != SELECTOR_NONE;
}
if(selectorCount == 0)
{
Alert("No selectors. Setup at least one of them.");
return 0;
}
// filter section part.1 starts
S Filter1 = SELECTOR_NONE;
T Filter1Field = FIELD_NONE;
if(ArraySize(selectorArray) > 3)
{
Filter1 = selectorArray[3];
}
if(ArraySize(selectorField) > 3)
{
Filter1Field = selectorField[3];
}
// filter section part.1 ends
Selector<T> *selectors[];
ArrayResize(selectors, selectorCount);
for(int i = 0; i < selectorCount; i++)
{
selectors[i] = createSelector(selectorArray[i], selectorField[i]);
if(selectors[i] == NULL)
{
Print("Selector ", i, " is empty. Setup selectors successively (don't leave a hole in-between), specify a field when required");
return 0;
}
}
// filter section part.2 starts
Filter<T> *filters[];
if(Filter1 != SELECTOR_NONE)
{
ArrayResize(filters, 1);
Selector<T> *filterSelector = createSelector(Filter1, Filter1Field);
if(Filter1value1 != Filter1value2)
{
filters[0] = new FilterRange<T>(filterSelector, Filter1value1, Filter1value2);
}
else
{
filters[0] = new Filter<T>(filterSelector, Filter1value1);
}
}
// filter section part.2 ends
Aggregator<T> *aggregator;
// MQL does not support a 'class info' metaclass.
// Otherwise we could use an array of classes instead of the switch
switch(AggregatorType)
{
case AGGREGATOR_SUM:
aggregator = new SumAggregator<T>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_AVERAGE:
aggregator = new AverageAggregator<T>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_MAX:
aggregator = new MaxAggregator<T>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_MIN:
aggregator = new MinAggregator<T>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_COUNT:
aggregator = new CountAggregator<T>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_PROFITFACTOR:
aggregator = new ProfitFactorAggregator<T>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_PROGRESSIVE:
aggregator = new ProgressiveTotalAggregator<T>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_IDENTITY:
aggregator = new IdentityAggregator<T>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_STDDEV:
aggregator = new VarianceAggregator<T>(AggregatorField, selectors, filters);
break;
}
Analyst<T> *analyst = new Analyst<T>(adapter, aggregator, display);
analyst.acquireData();
initialize();
Print("Aggregator: ", aggregator.getMetaCubeTitle(shortTitles), " [", aggregator.getCubeSize(), "]", " dimensions: ", aggregator.getDimension());
Print("Filters: ", aggregator.getFilterTitles());
Print("Selectors: ", selectorCount);
for(int i = 0; i < aggregator.getDimension(); i++)
{
Print(CharToString((uchar)('X' + i)), ": ", aggregator.getDimensionTitle(i) /*display.getCustomDimensionTitle(aggregator, i)*/, " [", aggregator.getDimensionRange(i), "]");
}
const int p = analyst.build(progress);
Print("Processed records: ", p);
if(p > 0)
{
analyst.display(SortBy, AggregatorType == AGGREGATOR_IDENTITY);
}
delete analyst;
delete aggregator;
for(int i = 0; i < selectorCount; i++)
{
delete selectors[i];
}
for(int i = 0; i < ArraySize(filters); i++)
{
delete filters[i].getSelector();
delete filters[i];
}
return p;
}
};