1830 lines
92 KiB
Plaintext
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;
|
|
}
|
|
};
|