Add files via upload
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| CSVcube.mqh |
|
||||
//| Copyright (c) 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 |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
#include <Marketeer/CSVReader.mqh>
|
||||
#include <Marketeer/CSVcolumns.mqh>
|
||||
|
||||
template<typename T>
|
||||
class CSVTradeRecord: public T // TradeRecord
|
||||
{
|
||||
public:
|
||||
CSVTradeRecord(const double balance, const string symbol, const IndexMap *row)
|
||||
{
|
||||
const int add = row.getSize() == 13 ? 2 : 0;
|
||||
set(FIELD_NUMBER, counter);
|
||||
set(FIELD_TICKET, counter++);
|
||||
set(FIELD_SYMBOL, symbols.add(symbol));
|
||||
string t = row[CSV_COLUMN_TYPE].get<string>();
|
||||
StringToLower(t);
|
||||
const int _type = t == "buy" ? +1 : (t == "sell" ? -1 : 0);
|
||||
set(FIELD_TYPE, _type == +1 ? OP_BUY : (_type == -1 ? OP_SELL : OP_BALANCE));
|
||||
datetime time1 = StringToTime(row[CSV_COLUMN_TIME1].get<string>()) + TimeShift;
|
||||
datetime time2 = StringToTime(row[CSV_COLUMN_TIME2 + add].get<string>()) + TimeShift;
|
||||
set(FIELD_DATETIME1, time1);
|
||||
set(FIELD_DATETIME2, time2);
|
||||
set(FIELD_DURATION, time2 - time1);
|
||||
double price1 = StringToDouble(row[CSV_COLUMN_PRICE1].get<string>());
|
||||
double price2 = StringToDouble(row[CSV_COLUMN_PRICE2 + add].get<string>());
|
||||
set(FIELD_PRICE1, price1);
|
||||
set(FIELD_PRICE2, price2);
|
||||
set(FIELD_MAGIC, 0);
|
||||
magics.add(0);
|
||||
set(FIELD_LOT, StringToDouble(row[CSV_COLUMN_VOLUME].get<string>()));
|
||||
t = row[CSV_COLUMN_PROFIT + add].get<string>();
|
||||
StringReplace(t, " ", "");
|
||||
const double profit = StringToDouble(t);
|
||||
set(FIELD_PROFIT_AMOUNT, profit);
|
||||
set(FIELD_PROFIT_PERCENT, (profit / balance));
|
||||
set(FIELD_PROFIT_POINT, (_type * (price2 - price1) / SymbolInfoDouble(symbol, SYMBOL_POINT)));
|
||||
set(FIELD_COMMISSION, StringToDouble(row[CSV_COLUMN_COMMISSION + add].get<string>()));
|
||||
set(FIELD_SWAP, StringToDouble(row[CSV_COLUMN_SWAP + add].get<string>()));
|
||||
|
||||
fillCustomFields();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class CSVReportAdapter: public DataAdapter
|
||||
{
|
||||
private:
|
||||
RubbArray<CSVTradeRecord<T> *> trades;
|
||||
|
||||
int cursor;
|
||||
int size;
|
||||
double balance;
|
||||
IndexMap *data;
|
||||
|
||||
void reset()
|
||||
{
|
||||
size = 0;
|
||||
cursor = 0;
|
||||
balance = 0;
|
||||
if(CheckPointer(data) == POINTER_DYNAMIC) delete data;
|
||||
}
|
||||
|
||||
public:
|
||||
CSVReportAdapter()
|
||||
{
|
||||
reset();
|
||||
TradeRecord::reset();
|
||||
}
|
||||
|
||||
~CSVReportAdapter()
|
||||
{
|
||||
if(CheckPointer(data) == POINTER_DYNAMIC) delete data;
|
||||
}
|
||||
|
||||
bool load(const string file)
|
||||
{
|
||||
reset();
|
||||
data = CSVConverter::ReadCSV(file);
|
||||
if(data != NULL)
|
||||
{
|
||||
size = generate();
|
||||
Print(data.getSize(), " records transferred to ", size, " trades");
|
||||
}
|
||||
return data != NULL;
|
||||
}
|
||||
|
||||
virtual int reservedSize() override
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
virtual Record *getNext() override
|
||||
{
|
||||
if(cursor < size)
|
||||
{
|
||||
return trades[cursor++];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
protected:
|
||||
int generate()
|
||||
{
|
||||
int count = 0;
|
||||
balance = 0;
|
||||
for(int i = data.getSize() - 1; i >= 0; --i) // csv-files have reverse chronological order
|
||||
{
|
||||
IndexMap *row = data[i];
|
||||
const int add = row.getSize() == 13 ? 2 : 0;
|
||||
string s = row[CSV_COLUMN_SYMBOL].get<string>();
|
||||
StringTrimLeft(s);
|
||||
if(StringLen(s) > 0)
|
||||
{
|
||||
if(balance == 0)
|
||||
{
|
||||
Print("Zero balance, 10000 emulated");
|
||||
balance = 10000;
|
||||
}
|
||||
|
||||
string real = TradeRecord::realsymbol(s);
|
||||
if(real == NULL) continue;
|
||||
|
||||
trades << new CSVTradeRecord<T>(balance, real, row);
|
||||
++count;
|
||||
}
|
||||
else
|
||||
{
|
||||
string type = row[CSV_COLUMN_TYPE].get<string>();
|
||||
StringToLower(type);
|
||||
if(type == "balance")
|
||||
{
|
||||
string t = row[CSV_COLUMN_PROFIT + add].get<string>();
|
||||
StringReplace(t, " ", "");
|
||||
balance += StringToDouble(t);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,350 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| HTMLcube.mqh |
|
||||
//| Copyright (c) 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 |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
#include <Marketeer/GroupSettings.mqh>
|
||||
|
||||
input GroupSettings Common_Settings; // G E N E R A L S E T T I N G S
|
||||
|
||||
input string ReportFile = ""; // · ReportFile
|
||||
input string Prefix = ""; // · Prefix
|
||||
input string Suffix = ""; // · Suffix
|
||||
input int TimeShift = 0; // · TimeShift
|
||||
|
||||
|
||||
#include <Marketeer/WebDataExtractor.mqh>
|
||||
#include <Marketeer/RubbArray.mqh>
|
||||
#include <Marketeer/HTMLcolumns.mqh>
|
||||
|
||||
|
||||
template<typename T>
|
||||
class HTMLTradeRecord: public T // TradeRecord
|
||||
{
|
||||
public:
|
||||
HTMLTradeRecord(
|
||||
const double balance,
|
||||
const long ticket,
|
||||
const string symbol,
|
||||
const int type,
|
||||
const datetime time1,
|
||||
const datetime time2,
|
||||
const double price1,
|
||||
const double price2,
|
||||
const double lot,
|
||||
const double profit,
|
||||
const double commission,
|
||||
const double swap)
|
||||
{
|
||||
set(FIELD_NUMBER, counter++);
|
||||
set(FIELD_TICKET, ticket);
|
||||
set(FIELD_SYMBOL, symbols.add(symbol));
|
||||
set(FIELD_TYPE, type);
|
||||
set(FIELD_DATETIME1, time1);
|
||||
set(FIELD_DATETIME2, time2);
|
||||
set(FIELD_DURATION, time2 - time1);
|
||||
set(FIELD_PRICE1, (float)price1);
|
||||
set(FIELD_PRICE2, (float)price2);
|
||||
set(FIELD_MAGIC, 0);
|
||||
magics.add(0);
|
||||
set(FIELD_LOT, (float)lot);
|
||||
set(FIELD_PROFIT_AMOUNT, (float)profit);
|
||||
set(FIELD_PROFIT_PERCENT, (float)(profit / balance));
|
||||
set(FIELD_PROFIT_POINT, (float)((type == OP_BUY ? +1 : -1) * (price2 - price1) / SymbolInfoDouble(symbol, SYMBOL_POINT)));
|
||||
set(FIELD_COMMISSION, (float)commission);
|
||||
set(FIELD_SWAP, (float)swap);
|
||||
|
||||
fillCustomFields(); // calls implementation from T
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class HTMLReportAdapter: public DataAdapter
|
||||
{
|
||||
private:
|
||||
|
||||
class Deal // if MQL5 could respect private access specifier for classes,
|
||||
{ // Trades will be unreachable from outer world, so it would be fine to have
|
||||
public: // fields made public for direct access from Processor only
|
||||
datetime time;
|
||||
double price;
|
||||
int type; // +1 - buy, -1 - sell
|
||||
int direction; // +1 - in, -1 - out, 0 - in/out
|
||||
double volume;
|
||||
double profit;
|
||||
long deal;
|
||||
long order;
|
||||
string comment;
|
||||
string symbol;
|
||||
double commission;
|
||||
double swap;
|
||||
|
||||
public:
|
||||
Deal(const IndexMap *row) // this is MT5 deal
|
||||
{
|
||||
time = StringToTime(row[COLUMN_TIME].get<string>()) + TimeShift;
|
||||
price = StringToDouble(row[COLUMN_PRICE].get<string>());
|
||||
string t = row[COLUMN_TYPE].get<string>();
|
||||
type = t == "buy" ? +1 : (t == "sell" ? -1 : 0);
|
||||
t = row[COLUMN_DIRECTION].get<string>();
|
||||
direction = 0;
|
||||
if(StringFind(t, "in") > -1) ++direction;
|
||||
if(StringFind(t, "out") > -1) --direction;
|
||||
volume = StringToDouble(row[COLUMN_VOLUME].get<string>());
|
||||
t = row[COLUMN_PROFIT].get<string>();
|
||||
StringReplace(t, " ", "");
|
||||
profit = StringToDouble(t);
|
||||
deal = StringToInteger(row[COLUMN_DEAL].get<string>());
|
||||
order = StringToInteger(row[COLUMN_ORDER].get<string>());
|
||||
comment = row[COLUMN_COMMENT].get<string>();
|
||||
symbol = row[COLUMN_SYMBOL].get<string>();
|
||||
commission = StringToDouble(row[COLUMN_COMISSION].get<string>());
|
||||
swap = StringToDouble(row[COLUMN_SWAP].get<string>());
|
||||
}
|
||||
|
||||
bool isIn() const
|
||||
{
|
||||
return direction >= 0;
|
||||
}
|
||||
|
||||
bool isOut() const
|
||||
{
|
||||
return direction <= 0;
|
||||
}
|
||||
|
||||
bool isOpposite(const Deal *t) const
|
||||
{
|
||||
return type * t.type < 0;
|
||||
}
|
||||
|
||||
bool isActive() const
|
||||
{
|
||||
return volume > 0;
|
||||
}
|
||||
|
||||
int op_type() const
|
||||
{
|
||||
if(type == +1) return OP_BUY;
|
||||
else if(type == -1) return OP_SELL;
|
||||
return OP_BALANCE;
|
||||
}
|
||||
};
|
||||
|
||||
RubbArray<Deal *> array;
|
||||
RubbArray<Deal *> queue;
|
||||
|
||||
|
||||
int size;
|
||||
int cursor;
|
||||
double balance;
|
||||
IndexMap *data;
|
||||
|
||||
RubbArray<HTMLTradeRecord<T> *> trades;
|
||||
|
||||
|
||||
protected:
|
||||
int generate()
|
||||
{
|
||||
array.clear();
|
||||
balance = 0;
|
||||
for(int i = 0; i < data.getSize(); ++i)
|
||||
{
|
||||
IndexMap *row = data[i];
|
||||
if(CheckPointer(row) == POINTER_INVALID || row.getSize() != COLUMNS_COUNT) return 0; // something is broken
|
||||
string s = row[COLUMN_SYMBOL].get<string>();
|
||||
StringTrimLeft(s);
|
||||
if(StringLen(s) > 0)
|
||||
{
|
||||
array << new Deal(row);
|
||||
}
|
||||
else if(row[COLUMN_TYPE].get<string>() == "balance")
|
||||
{
|
||||
string t = row[COLUMN_PROFIT].get<string>();
|
||||
StringReplace(t, " ", "");
|
||||
balance += StringToDouble(t);
|
||||
}
|
||||
}
|
||||
|
||||
if(balance == 0) balance = 10000; // default, if missing
|
||||
|
||||
int count = 0;
|
||||
// abstract:
|
||||
// if direction <= 0
|
||||
// collect all Trades from the queue which have direction >= 0 and opposite type
|
||||
// if this volume is greater than collected volumes
|
||||
// reduce volume in this Deal by the total volume of collected Trades
|
||||
// else if collected volumes are greater than this volume
|
||||
// reduce volume in matched Trades in a loop until all volume of this Deal is exhausted
|
||||
// create object-lines from all affected Trades to this Deal
|
||||
// 'delete' all affected Trades with zero volume from queue
|
||||
// if volume == 0, 'delete' this Deal (disactivate)
|
||||
// if direction >= 0 push the new Deal object to the queue
|
||||
|
||||
for(int i = 0; i < array.size(); ++i)
|
||||
{
|
||||
Deal *current = array[i];
|
||||
|
||||
if(!current.isActive()) continue;
|
||||
|
||||
string real = TradeRecord::realsymbol(current.symbol);
|
||||
if(real == NULL) continue;
|
||||
|
||||
if(current.isOut())
|
||||
{
|
||||
// first try to find exact match
|
||||
for(int j = 0; j < queue.size(); ++j)
|
||||
{
|
||||
if(queue[j].isIn() && queue[j].isOpposite(current) && queue[j].volume == current.volume && queue[j].symbol == current.symbol)
|
||||
{
|
||||
trades << new HTMLTradeRecord<T>(
|
||||
balance,
|
||||
queue[j].deal,
|
||||
real, // current.symbol,
|
||||
queue[j].op_type(),
|
||||
queue[j].time,
|
||||
current.time,
|
||||
queue[j].price,
|
||||
current.price,
|
||||
current.volume,
|
||||
current.profit,
|
||||
queue[j].commission + current.commission,
|
||||
current.swap);
|
||||
balance += current.profit;
|
||||
|
||||
current.volume = 0;
|
||||
queue >> j; // remove from queue
|
||||
++count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!current.isActive()) continue;
|
||||
|
||||
// second try to perform partial close
|
||||
for(int j = 0; j < queue.size(); ++j)
|
||||
{
|
||||
if(queue[j].isIn() && queue[j].isOpposite(current) && queue[j].symbol == current.symbol)
|
||||
{
|
||||
if(current.volume >= queue[j].volume)
|
||||
{
|
||||
double fraction = queue[j].volume / current.volume;
|
||||
|
||||
trades << new HTMLTradeRecord<T>(
|
||||
balance,
|
||||
queue[j].deal,
|
||||
real, // current.symbol,
|
||||
queue[j].op_type(),
|
||||
queue[j].time,
|
||||
current.time,
|
||||
queue[j].price,
|
||||
current.price,
|
||||
queue[j].volume,
|
||||
current.profit * fraction,
|
||||
queue[j].commission + current.commission * fraction,
|
||||
current.swap * fraction);
|
||||
balance += current.profit * fraction;
|
||||
|
||||
current.volume -= queue[j].volume;
|
||||
queue[j].volume = 0;
|
||||
++count;
|
||||
}
|
||||
else
|
||||
{
|
||||
double fraction = current.volume / queue[j].volume;
|
||||
|
||||
trades << new HTMLTradeRecord<T>(
|
||||
balance,
|
||||
queue[j].deal,
|
||||
real, // current.symbol,
|
||||
queue[j].op_type(),
|
||||
queue[j].time,
|
||||
current.time,
|
||||
queue[j].price,
|
||||
current.price,
|
||||
current.volume,
|
||||
queue[j].profit * fraction, // should be 0
|
||||
queue[j].commission * fraction + current.commission,
|
||||
current.swap);
|
||||
balance += queue[j].profit * fraction;
|
||||
|
||||
queue[j].volume -= current.volume;
|
||||
current.volume = 0;
|
||||
++count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// purge all inactive from queue
|
||||
for(int j = queue.size() - 1; j >= 0; --j)
|
||||
{
|
||||
if(!queue[j].isActive())
|
||||
{
|
||||
queue >> j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(current.isActive()) // is _still_ active
|
||||
{
|
||||
if(current.isIn())
|
||||
{
|
||||
queue << current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
cursor = 0;
|
||||
balance = 0;
|
||||
if(CheckPointer(data) == POINTER_DYNAMIC) delete data;
|
||||
}
|
||||
|
||||
public:
|
||||
HTMLReportAdapter()
|
||||
{
|
||||
reset();
|
||||
TradeRecord::reset();
|
||||
}
|
||||
|
||||
~HTMLReportAdapter()
|
||||
{
|
||||
if(CheckPointer(data) == POINTER_DYNAMIC) delete data;
|
||||
((BaseArray<Deal *> *)&queue).clear();
|
||||
}
|
||||
|
||||
bool load(const string file)
|
||||
{
|
||||
reset();
|
||||
data = HTMLConverter::convertReport2Map(file, true);
|
||||
if(data != NULL)
|
||||
{
|
||||
size = generate();
|
||||
Print(data.getSize(), " deals transferred to ", size, " trades");
|
||||
}
|
||||
return data != NULL;
|
||||
}
|
||||
|
||||
virtual int reservedSize() override
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
virtual Record *getNext() override
|
||||
{
|
||||
if(cursor < size)
|
||||
{
|
||||
return trades[cursor++];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,241 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| OLAPcore.mqh |
|
||||
//| Copyright © 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 |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
#include <OLAP/OLAPcube.mqh>
|
||||
#include <OLAP/HTMLcube.mqh>
|
||||
#include <OLAP/CSVcube.mqh>
|
||||
|
||||
|
||||
class DaysRangeSelector: public DateTimeSelector<TRADE_RECORD_FIELDS>
|
||||
{
|
||||
protected:
|
||||
int granulatity;
|
||||
|
||||
public:
|
||||
DaysRangeSelector(const int n): DateTimeSelector<TRADE_RECORD_FIELDS>(FIELD_DURATION, 7), granulatity(n)
|
||||
{
|
||||
_typename = typename(this);
|
||||
}
|
||||
|
||||
virtual int getRange() const
|
||||
{
|
||||
return granulatity;
|
||||
}
|
||||
|
||||
virtual bool select(const Record *r, int &index) const
|
||||
{
|
||||
double d = r.get(selector);
|
||||
int days = (int)(d / (60 * 60 * 24));
|
||||
index = MathMin(days, granulatity - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual string getLabel(const int index) const
|
||||
{
|
||||
return index < granulatity - 1 ? ((index < 10 ? " ": "") + (string)index + "D") : ((string)index + "D+");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class OLAPWrapper
|
||||
{
|
||||
protected:
|
||||
Selector<TRADE_RECORD_FIELDS> *createSelector(const SELECTORS selector, const TRADE_RECORD_FIELDS field)
|
||||
{
|
||||
switch(selector)
|
||||
{
|
||||
case SELECTOR_TYPE:
|
||||
return new TypeSelector();
|
||||
case SELECTOR_SYMBOL:
|
||||
return new SymbolSelector();
|
||||
case SELECTOR_SERIAL:
|
||||
return new SerialNumberSelector();
|
||||
case SELECTOR_MAGIC:
|
||||
return new MagicSelector();
|
||||
case SELECTOR_PROFITABLE:
|
||||
return new ProfitableSelector();
|
||||
case SELECTOR_DURATION:
|
||||
return new DaysRangeSelector(15); // up to 14 days
|
||||
case SELECTOR_WEEKDAY:
|
||||
return field != FIELD_NONE ? new WeekDaySelector(field) : NULL;
|
||||
case SELECTOR_DAYHOUR:
|
||||
return field != FIELD_NONE ? new DayHourSelector(field) : NULL;
|
||||
case SELECTOR_HOURMINUTE:
|
||||
return field != FIELD_NONE ? new DayHourSelector(field) : NULL;
|
||||
case SELECTOR_SCALAR:
|
||||
return field != FIELD_NONE ? new TradeSelector(field) : NULL;
|
||||
case SELECTOR_QUANTS:
|
||||
return field != FIELD_NONE ? new QuantizationSelector(field) : NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
public:
|
||||
void process(
|
||||
const SELECTORS &selectorArray[], const TRADE_RECORD_FIELDS &selectorField[],
|
||||
const AGGREGATORS AggregatorType, const TRADE_RECORD_FIELDS 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;
|
||||
}
|
||||
|
||||
// filter section not used yet >>>
|
||||
SELECTORS Filter1 = SELECTOR_NONE;
|
||||
TRADE_RECORD_FIELDS Filter1Field = FIELD_NONE;
|
||||
|
||||
if(ArraySize(selectorArray) > 3)
|
||||
{
|
||||
Filter1 = selectorArray[3];
|
||||
}
|
||||
|
||||
if(ArraySize(selectorField) > 3)
|
||||
{
|
||||
Filter1Field = selectorField[3];
|
||||
}
|
||||
// <<< filter section not used
|
||||
|
||||
HistoryDataAdapter<CustomTradeRecord> history;
|
||||
HTMLReportAdapter<CustomTradeRecord> report;
|
||||
CSVReportAdapter<CustomTradeRecord> external;
|
||||
|
||||
DataAdapter *adapter = &history;
|
||||
|
||||
if(ReportFile != "")
|
||||
{
|
||||
if(StringFind(ReportFile, ".htm") > 0 && report.load(ReportFile))
|
||||
{
|
||||
adapter = &report;
|
||||
}
|
||||
else
|
||||
if(StringFind(ReportFile, ".csv") > 0 && external.load(ReportFile))
|
||||
{
|
||||
adapter = &external;
|
||||
}
|
||||
else
|
||||
{
|
||||
Alert("Unknown file format: ", ReportFile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Analyzing account history");
|
||||
}
|
||||
|
||||
Analyst<TRADE_RECORD_FIELDS> *analyst;
|
||||
|
||||
Selector<TRADE_RECORD_FIELDS> *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;
|
||||
}
|
||||
}
|
||||
|
||||
// filter section not used yet >>>
|
||||
Filter<TRADE_RECORD_FIELDS> *filters[];
|
||||
if(Filter1 != SELECTOR_NONE)
|
||||
{
|
||||
ArrayResize(filters, 1);
|
||||
Selector<TRADE_RECORD_FIELDS> *filterSelector = createSelector(Filter1, Filter1Field);
|
||||
if(Filter1value1 != Filter1value2)
|
||||
{
|
||||
filters[0] = new FilterRange<TRADE_RECORD_FIELDS>(filterSelector, Filter1value1, Filter1value2);
|
||||
}
|
||||
else
|
||||
{
|
||||
filters[0] = new Filter<TRADE_RECORD_FIELDS>(filterSelector, Filter1value1);
|
||||
}
|
||||
}
|
||||
// <<< filter section not used
|
||||
|
||||
Aggregator<TRADE_RECORD_FIELDS> *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<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
|
||||
break;
|
||||
case AGGREGATOR_AVERAGE:
|
||||
aggregator = new AverageAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
|
||||
break;
|
||||
case AGGREGATOR_MAX:
|
||||
aggregator = new MaxAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
|
||||
break;
|
||||
case AGGREGATOR_MIN:
|
||||
aggregator = new MinAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
|
||||
break;
|
||||
case AGGREGATOR_COUNT:
|
||||
aggregator = new CountAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
|
||||
break;
|
||||
case AGGREGATOR_PROFITFACTOR:
|
||||
aggregator = new ProfitFactorAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
|
||||
break;
|
||||
case AGGREGATOR_PROGRESSIVE:
|
||||
aggregator = new ProgressiveTotalAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
|
||||
break;
|
||||
case AGGREGATOR_IDENTITY:
|
||||
aggregator = new IdentityAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
|
||||
break;
|
||||
}
|
||||
|
||||
analyst = new Analyst<TRADE_RECORD_FIELDS>(adapter, aggregator, display);
|
||||
|
||||
analyst.acquireData();
|
||||
|
||||
Print("Symbol number: ", TradeRecord::getSymbolCount());
|
||||
for(int i = 0; i < TradeRecord::getSymbolCount(); i++)
|
||||
{
|
||||
Print(i, "] ", TradeRecord::getSymbol(i));
|
||||
}
|
||||
|
||||
Print("Magic number: ", TradeRecord::getMagicCount());
|
||||
for(int i = 0; i < TradeRecord::getMagicCount(); i++)
|
||||
{
|
||||
Print(i, "] ", TradeRecord::getMagic(i));
|
||||
}
|
||||
|
||||
Print("Filters: ", aggregator.getFilterTitles());
|
||||
|
||||
Print("Selectors: ", selectorCount);
|
||||
|
||||
analyst.build();
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,148 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| PairArray.mqh |
|
||||
//| Copyright © 2019, Marketeer |
|
||||
//| https://www.mql5.com/en/users/marketeer |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
class PairArray
|
||||
{
|
||||
public:
|
||||
// aux struct to populate temp array when sorting is enabled
|
||||
struct Pair
|
||||
{
|
||||
double value;
|
||||
string title;
|
||||
Pair(): value(DBL_MAX), title(NULL) {}
|
||||
Pair(const double v, const string s): value(v), title(s) {}
|
||||
Pair(const string s, const double v): value(v), title(s) {}
|
||||
bool operator>(const double v) const
|
||||
{
|
||||
return value > v;
|
||||
}
|
||||
bool operator>(const string s) const
|
||||
{
|
||||
return title > s;
|
||||
}
|
||||
};
|
||||
|
||||
// this is a common parent, so it can not be templatized
|
||||
class Comparator
|
||||
{
|
||||
public:
|
||||
// templatized method can not be virtual,
|
||||
// so we do artificial dynamic dispatching manually
|
||||
// (see below after declaration of descendant classes)
|
||||
template<typename T>
|
||||
bool compare(const Pair &v1, const T v2);
|
||||
};
|
||||
|
||||
class Greater: public Comparator
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
bool compare(const Pair &v1, const T v2)
|
||||
{
|
||||
return v1 > v2;
|
||||
}
|
||||
};
|
||||
|
||||
class Lesser: public Comparator
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
bool compare(const Pair &v1, const T v2)
|
||||
{
|
||||
return !(v1 > v2);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
Comparator *comparator;
|
||||
|
||||
public:
|
||||
// temp array for sorting (if enabled)
|
||||
Pair array[];
|
||||
|
||||
PairArray(): comparator(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
PairArray(const int reserved, Comparator *c = NULL)
|
||||
{
|
||||
comparator = c;
|
||||
ArrayResize(array, reserved);
|
||||
}
|
||||
|
||||
~PairArray()
|
||||
{
|
||||
ArrayResize(array, 0);
|
||||
if(CheckPointer(comparator) == POINTER_DYNAMIC) delete comparator;
|
||||
}
|
||||
|
||||
void allocate(const int reserved)
|
||||
{
|
||||
ArrayResize(array, reserved);
|
||||
}
|
||||
|
||||
void compareBy(Comparator *c)
|
||||
{
|
||||
if(CheckPointer(comparator) == POINTER_DYNAMIC) delete comparator;
|
||||
comparator = c;
|
||||
}
|
||||
|
||||
void move(const int index, const int count)
|
||||
{
|
||||
for(int i = count - 1; i >= index; --i)
|
||||
{
|
||||
array[i + 1] = array[i];
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T1, typename T2>
|
||||
void insert(const int count, const T1 v, const T2 s)
|
||||
{
|
||||
Pair p(v, s);
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
if(comparator != NULL && comparator.compare(array[i], v))
|
||||
{
|
||||
move(i, count);
|
||||
array[i] = p;
|
||||
return;
|
||||
}
|
||||
}
|
||||
array[count] = p;
|
||||
}
|
||||
|
||||
void convert(double &x[], string &s[]) const
|
||||
{
|
||||
int n = ArraySize(array);
|
||||
ArrayResize(x, n);
|
||||
ArrayResize(s, n);
|
||||
for(int i = 0; i < n; i++)
|
||||
{
|
||||
x[i] = array[i].value;
|
||||
s[i] = array[i].title;
|
||||
}
|
||||
}
|
||||
|
||||
void convert(double &x[]) const
|
||||
{
|
||||
int n = ArraySize(array);
|
||||
ArrayResize(x, n);
|
||||
for(int i = 0; i < n; i++)
|
||||
{
|
||||
x[i] = array[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
void convert(string &s[]) const
|
||||
{
|
||||
int n = ArraySize(array);
|
||||
ArrayResize(s, n);
|
||||
for(int i = 0; i < n; i++)
|
||||
{
|
||||
s[i] = array[i].title;
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user