Add files via upload

This commit is contained in:
amirghadiri1987
2025-02-07 19:15:50 +03:30
committed by GitHub
parent e829dab822
commit 781d26ab5c
46 changed files with 12926 additions and 0 deletions
+151
View File
@@ -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;
}
};
+350
View File
@@ -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;
}
};
+241
View File
@@ -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
+148
View File
@@ -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;
}
}
};