forked from rosh/MQL5Book
583 lines
22 KiB
MQL5
583 lines
22 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| OptReader.mqh |
| |
//| Copyright 2022, MetaQuotes Ltd. |
| |
//| https://www.mql5.com |
| |
//+------------------------------------------------------------------+
| |
#include "AutoPtr.mqh"
| |
#include "StructPrint.mqh"
| |
| |
#define TYPE_OFFSET 75
| |
#define _STR(A) StringFormat("%s,%s\n", #A, (string)(A))
| |
#define ASTR(A) StringFormat("%s,\"%s\"\n", #A, ShortArrayToString(A))
| |
| |
//+------------------------------------------------------------------+
| |
//| Opt-file header |
| |
//+------------------------------------------------------------------+
| |
struct TesterOptCacheHeader
| |
{
| |
uint version; // cache version
| |
ushort copyright[64]; // copyright
| |
ushort name[16]; // signature "TesterOptCache"
| |
int head_reserve[66];
| |
uint header_size; // overall header size
| |
uint record_size; // single test record size including optimized parameters
| |
ushort expert_name[64]; // expert name
| |
ushort expert_path[128]; // expert name and path from MQL5
| |
ushort server[64]; // trade server
| |
ushort symbol[32]; // instrument
| |
ushort period; // timeframe
| |
datetime date_from; // test start date
| |
datetime date_to; // test end date
| |
datetime date_forward; // forward end date
| |
int opt_mode; // optimization mode (0-complete, 1-genetics, 2/3-forward)
| |
int ticks_mode; // tick generation mode
| |
int last_criterion; // optimization criterion
| |
uint msc_min; // minimal duration of a single pass (milliseconds)
| |
uint msc_max; // maximal duration of a single pass (milliseconds)
| |
uint msc_avg; // average duration of a single pass (milliseconds)
| |
int common_reserve[16];
| |
| |
ushort group[80]; // group
| |
ushort trade_currency[32]; // account currency
| |
int trade_deposit; // initial deposit
| |
int trade_condition; // trading conditions (0-no delays, -1-arbitrary delays, nnn-millisecond delays)
| |
int trade_leverage; // leverage
| |
int trade_hedging; // 1 - netting, 2 - hedging
| |
int trade_currency_digits; // account currency digits
| |
int trade_pips; // pips mode
| |
int trade_reserve[5];
| |
| |
char hash_ex5[16]; // hash of expert's binary file
| |
uint parameters_size; // size of the buffer for all parameters (common)
| |
uint parameters_total; // number of parameters in total
| |
uint opt_params_size; // size of the buffer for optimized parameters (per pass)
| |
uint opt_params_total; // number of optimized parameters
| |
uint dwords_cnt; // size of a pass in dwords
| |
uint snapshot_size; // snapshot size
| |
uint passes_total; // number of passes to cover o-space (0 for genetics)
| |
uint passes_passed; // number of completed passes
| |
| |
string CSVtext()
| |
{
| |
return "Field,Value\n"
| |
+ _STR(version) + ASTR(copyright) + ASTR(name) + ASTR(expert_name)
| |
+ ASTR(expert_path) + ASTR(server) + ASTR(symbol) + _STR(period)
| |
+ _STR(date_from) + _STR(date_to) + _STR(date_forward)
| |
+ _STR(opt_mode) + _STR(ticks_mode) + _STR(last_criterion)
| |
+ _STR(msc_min) + _STR(msc_max) + _STR(msc_avg)
| |
+ ASTR(group) + ASTR(trade_currency)
| |
+ _STR(trade_deposit) + _STR(trade_condition) + _STR(trade_leverage)
| |
+ _STR(trade_hedging) + _STR(trade_currency_digits) + _STR(trade_pips)
| |
+ _STR(parameters_size) + _STR(parameters_total) + _STR(opt_params_size) + _STR(opt_params_total)
| |
+ _STR(dwords_cnt) + _STR(snapshot_size) + _STR(passes_total) + _STR(passes_passed);
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Input struct (raw, as is) |
| |
//+------------------------------------------------------------------+
| |
struct TestCacheInput
| |
{
| |
ushort name[64]; // parameter name
| |
int flag; // optimized (yes-1, no-0)
| |
int type; // data type TYPE_XXX
| |
int digits; // number of digits
| |
int offset; // offset in a buffer of parameters (used separately for common and per pass buffers)
| |
int size; // size (in bytes) of the value in the buffer
| |
int unknown;
| |
| |
union Range // 0 - start, 1 - step, 2 - stop
| |
{
| |
long integers[3];
| |
double numbers[3];
| |
} range;
| |
| |
template<typename T>
| |
union Number
| |
{
| |
T V;
| |
uchar A[sizeof(T)];
| |
Number(const uchar &buffer[], const int offset)
| |
{
| |
ArrayCopy(A, buffer, 0, offset, sizeof(T));
| |
}
| |
};
| |
| |
string CSVname() const
| |
{
| |
return ShortArrayToString(name);
| |
}
| |
| |
string CSVvalue(const uchar &buffer[], const int opt = -1)
| |
{
| |
if(size == 0) return "";
| |
int _offset = (opt == -1) ? offset : opt;
| |
const ENUM_DATATYPE t = (ENUM_DATATYPE)(type - TYPE_OFFSET);
| |
switch(t)
| |
{
| |
case TYPE_BOOL: {Number<bool> r(buffer, _offset); return (string)r.V;}
| |
case TYPE_CHAR: {Number<char> r(buffer, _offset); return (string)r.V;}
| |
case TYPE_UCHAR: {Number<uchar> r(buffer, _offset); return CharToString(r.V);}
| |
case TYPE_SHORT: {Number<short> r(buffer, _offset); return (string)r.V;}
| |
case TYPE_USHORT: {Number<ushort> r(buffer, _offset); return ShortToString(r.V);}
| |
case TYPE_COLOR:
| |
case TYPE_INT:
| |
case TYPE_UINT:
| |
default: {Number<uint> r(buffer, _offset); return (string)(r.V);}
| |
case TYPE_DATETIME: {Number<datetime> r(buffer, _offset); return TimeToString(r.V, TIME_DATE | TIME_SECONDS);}
| |
case TYPE_LONG:
| |
case TYPE_ULONG: {Number<ulong> r(buffer, _offset); return (string)(r.V);}
| |
| |
case TYPE_FLOAT:
| |
{
| |
Number<float> r(buffer, _offset);
| |
return (r.V == FLT_MAX) ? "~" : (string)(r.V);
| |
}
| |
case TYPE_DOUBLE:
| |
{
| |
Number<double> r(buffer, _offset);
| |
return (r.V == DBL_MAX) ? "~" : (string)(r.V);
| |
}
| |
case TYPE_STRING:
| |
{
| |
ushort a[];
| |
ArrayResize(a, size / 2);
| |
for(int i = 0; i < size / 2; ++i)
| |
{
| |
a[i] = buffer[offset + i * 2] | (buffer[offset + i * 2 + 1] << 8);
| |
}
| |
return ShortArrayToString(a);
| |
}
| |
}
| |
| |
return NULL;
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Input struct with stringified fields |
| |
//+------------------------------------------------------------------+
| |
struct TestCacheInputExtended: public TestCacheInput
| |
{
| |
string _name;
| |
string _type;
| |
string _range;
| |
void extend()
| |
{
| |
_name = ShortArrayToString(name);
| |
_type = EnumToString((ENUM_DATATYPE)(type - TYPE_OFFSET));
| |
const ENUM_DATATYPE t = (ENUM_DATATYPE)(type - TYPE_OFFSET);
| |
switch(t)
| |
{
| |
default:
| |
_range = "[" + (string)range.integers[0]
| |
+ "/" + (string)range.integers[1] + "/" + (string)range.integers[2] + "]";
| |
break;
| |
| |
case TYPE_FLOAT:
| |
case TYPE_DOUBLE:
| |
_range = "[" + (string)range.numbers[0]
| |
+ "/" + (string)range.numbers[1] + "/" + (string)range.numbers[2] + "]";
| |
}
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Base struct for optimization pass |
| |
//+------------------------------------------------------------------+
| |
struct TestCacheRecord
| |
{
| |
ulong pass;
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Trade optimization pass by input parameters |
| |
//+------------------------------------------------------------------+
| |
struct ExpTradeSummary: public TestCacheRecord
| |
{
| |
union UD
| |
{
| |
struct Doubles
| |
{
| |
double deposit; // initial deposit
| |
double withdrawal; // withdrawals
| |
double profit; // net profit
| |
double grossprofit; // gross profit
| |
double grossloss; // gross loss
| |
double maxprofit; // maximal profitable trade
| |
double minprofit; // maximal loss trade
| |
double conprofitmax; // profit of longest sequence of profitable trades
| |
double maxconprofit; // maximal profit in a sequence of profitable trades
| |
double conlossmax; // loss of longest sequence of loss trades
| |
double maxconloss; // maximal loss in a sequence of loss trades
| |
double balancemin; // minimal balance (for balance absolute drawdown)
| |
double maxdrawdown; // maximal absolute balance drawdown
| |
double drawdownpercent; // maximal absolute balance drawdown (in percents)
| |
double reldrawdown; // maximal relative balance drawdown (in money)
| |
double reldrawdownpercent; // maximal relative balance drawdown
| |
double equitymin; // minimal equity (for equity absolute drawdown)
| |
double maxdrawdown_e; // maximal absolute equity drawdown
| |
double drawdownpercent_e; // maximal absolute equity drawdown (in percents)
| |
double reldrawdown_e; // maximal relative equity drawdown (in money)
| |
double reldrawdownpercnt_e; // maximal relative equity drawdown
| |
double expected_payoff; // average trade result
| |
double profit_factor; // profit factor
| |
double recovery_factor; // recovery factor
| |
double sharpe_ratio; // Sharpe ratio
| |
double margin_level; // minimal margin level
| |
double custom_fitness; // custom criterion value - OnTester result
| |
};
| |
double array[27];
| |
} doubles;
| |
union UI
| |
{
| |
struct Integers
| |
{
| |
int deals; // total number of deals
| |
int trades; // number of trades (out/inout deals)
| |
int profittrades; // number of profitable trades
| |
int losstrades; // number of loss trades
| |
int shorttrades; // number of sells
| |
int longtrades; // number of buys
| |
int winshorttrades; // number of profitable sells
| |
int winlongtrades; // number of profitable buys
| |
int conprofitmax_trades; // longest sequence of profit trades
| |
int maxconprofit_trades; // maximal profit in a sequence of profit trades
| |
int conlossmax_trades; // longest sequence of loss trades
| |
int maxconloss_trades; // maximal loss in a sequence of loss trades
| |
int avgconwinners; // average number of trades in a sequence of profits
| |
int avgconloosers; // average number of trades in a sequence of losses
| |
};
| |
int array[14];
| |
} integers;
| |
| |
static string CSVheader()
| |
{
| |
return "Pass,Deposit,Withdrawal,Profit,Grossprofit,Grossloss,Maxprofit,"
| |
"Minprofit,Conprofitmax,Maxconprofit,Conlossmax,Maxconloss,Balancemin,"
| |
"Maxdrawdown,Drawdownpercent,Reldrawdown,Reldrawdownpercent,Equitymin,"
| |
"Maxdrawdown_E,Drawdownpercent_E,Reldrawdown_E,Reldrawdownpercnt_E,"
| |
"Expected_Payoff,Profit_Factor,Recovery_Factor,Sharpe_Ratio,Margin_Level,Custom_Fitness,"
| |
"Deals,Trades,Profittrades,Losstrades,Shorttrades,Longtrades,Winshorttrades,Winlongtrades,"
| |
"Conprofitmax_Trades,Maxconprofit_Trades,Conlossmax_Trades,Maxconloss_Trades,"
| |
"Avgconwinners,Avgconloosers";
| |
}
| |
| |
string CSVline() const
| |
{
| |
string result = (string)pass;
| |
for(int i = 0; i < ArraySize(doubles.array); ++i)
| |
{
| |
result += "," + (doubles.array[i] == DBL_MAX ? "~" : DoubleToString(doubles.array[i], 2));
| |
}
| |
for(int i = 0; i < ArraySize(integers.array); ++i)
| |
{
| |
result += "," + (string)integers.array[i];
| |
}
| |
return result;
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Trade optimization pass by symbol |
| |
//+------------------------------------------------------------------+
| |
struct ExpTradeSummaryBySymbols: public ExpTradeSummary
| |
{
| |
ushort watch[32];
| |
| |
static string CSVheader()
| |
{
| |
return ExpTradeSummary::CSVheader() + ",Symbol";
| |
}
| |
| |
string CSVline() const
| |
{
| |
return ExpTradeSummary::CSVline() + "," + ShortArrayToString(watch);
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Math optimization pass |
| |
//+------------------------------------------------------------------+
| |
struct MathTestCacheRecord: public TestCacheRecord
| |
{
| |
double fitness;
| |
| |
static string CSVheader()
| |
{
| |
return "Pass,Fitness";
| |
}
| |
| |
string CSVline() const
| |
{
| |
return (string)pass + "," + DoubleToString(fitness);
| |
}
| |
};
| |
| |
#define SAFE(A) {const uint _ = A; if(!_) return false;}
| |
| |
//+------------------------------------------------------------------+
| |
//| Single optimization pass interface |
| |
//+------------------------------------------------------------------+
| |
class RecordBase
| |
{
| |
public:
| |
virtual int size() const = 0;
| |
virtual bool read(const int handle, const int opt, const int gen) = 0;
| |
virtual void print() const = 0;
| |
virtual void optimized(uchar &buffer[], const int i) const = 0;
| |
virtual string CSVheader() const = 0;
| |
virtual string CSVline(const int i) const = 0;
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Single optimization pass with inputs data |
| |
//+------------------------------------------------------------------+
| |
template<typename T>
| |
struct ExtendedRecord
| |
{
| |
T result;
| |
uchar bufferOfOptimizedInputs[];
| |
uchar genetics[];
| |
| |
bool read(const int handle, const int opt, const int gen)
| |
{
| |
SAFE(FileReadStruct(handle, result));
| |
SAFE(FileReadArray(handle, bufferOfOptimizedInputs, 0, opt));
| |
SAFE(FileReadArray(handle, genetics, 0, gen));
| |
return true;
| |
}
| |
| |
void print() const
| |
{
| |
StructPrint(result);
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Array of optimization passes of T type |
| |
//+------------------------------------------------------------------+
| |
template<typename T>
| |
class RecordArray: public RecordBase
| |
{
| |
T array[];
| |
public:
| |
RecordArray(const int allocate)
| |
{
| |
ArrayResize(array, allocate);
| |
}
| |
| |
virtual int size() const override
| |
{
| |
return ArraySize(array);
| |
}
| |
| |
virtual bool read(const int handle, const int opt, const int gen) override
| |
{
| |
for(int i = 0; i < ArraySize(array); ++i)
| |
{
| |
array[i].read(handle, opt, gen);
| |
}
| |
return true;
| |
}
| |
| |
virtual void print() const override
| |
{
| |
for(int i = 0; i < ArraySize(array); ++i)
| |
{
| |
array[i].print();
| |
}
| |
}
| |
| |
virtual string CSVheader() const override
| |
{
| |
return array[0].result.CSVheader();
| |
}
| |
| |
virtual string CSVline(const int i) const override
| |
{
| |
return array[i].result.CSVline();
| |
}
| |
| |
virtual void optimized(uchar &buffer[], const int i = -1) const override
| |
{
| |
ArrayCopy(buffer, array[i].bufferOfOptimizedInputs);
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Main class to read opt-file and export it to CSVs |
| |
//+------------------------------------------------------------------+
| |
class OptReader
| |
{
| |
TesterOptCacheHeader header;
| |
TestCacheInputExtended inputs[];
| |
uchar bufferOfInputs[];
| |
int shapshot[];
| |
AutoPtr<RecordBase> records;
| |
| |
bool read(const int handle)
| |
{
| |
SAFE(FileReadStruct(handle, header));
| |
if(header.parameters_total)
| |
{
| |
TestCacheInput temp[];
| |
SAFE(FileReadArray(handle, temp, 0, header.parameters_total));
| |
const int n = ArrayResize(inputs, header.parameters_total);
| |
for(int i = 0; i < n; ++i)
| |
{
| |
inputs[i] = temp[i];
| |
inputs[i].extend();
| |
}
| |
}
| |
if(header.parameters_size)
| |
{
| |
SAFE(FileReadArray(handle, bufferOfInputs, 0, header.parameters_size));
| |
}
| |
if(header.snapshot_size)
| |
{
| |
SAFE(FileReadArray(handle, shapshot, 0, header.snapshot_size));
| |
}
| |
uint size = header.record_size - header.opt_params_size - header.dwords_cnt * sizeof(int);
| |
uint appendix = 0;
| |
switch(size)
| |
{
| |
case sizeof(ExpTradeSummary) + sizeof(long):
| |
appendix = sizeof(long);
| |
case sizeof(ExpTradeSummary):
| |
records = new RecordArray<ExtendedRecord<ExpTradeSummary>>(header.passes_passed);
| |
break;
| |
case sizeof(ExpTradeSummaryBySymbols) + sizeof(long):
| |
appendix = sizeof(long);
| |
case sizeof(ExpTradeSummaryBySymbols):
| |
records = new RecordArray<ExtendedRecord<ExpTradeSummaryBySymbols>>(header.passes_passed);
| |
break;
| |
case sizeof(MathTestCacheRecord) + sizeof(long):
| |
appendix = sizeof(long);
| |
case sizeof(MathTestCacheRecord):
| |
records = new RecordArray<ExtendedRecord<MathTestCacheRecord>>(header.passes_passed);
| |
break;
| |
default:
| |
Print("Unknown TestCacheRecord side: ", size);
| |
return false;
| |
}
| |
records[].read(handle, header.opt_params_size, header.dwords_cnt * sizeof(int) + appendix);
| |
return true;
| |
}
| |
public:
| |
OptReader(const string filename)
| |
{
| |
const int handle = FileOpen(filename, FILE_READ | FILE_BIN | FILE_SHARE_READ | FILE_SHARE_WRITE);
| |
if(handle != INVALID_HANDLE)
| |
{
| |
read(handle);
| |
}
| |
FileClose(handle);
| |
}
| |
| |
string CSVheader() const
| |
{
| |
string result = records[].CSVheader();
| |
for(int i = 0; i < ArraySize(inputs); ++i)
| |
{
| |
if(inputs[i].flag)
| |
{
| |
result += "," + inputs[i].CSVname();
| |
}
| |
}
| |
return result;
| |
}
| |
| |
string CSVline(const int p) const
| |
{
| |
string result = records[].CSVline(p);
| |
uchar buf[];
| |
records[].optimized(buf, p);
| |
for(int i = 0, k = 0; i < ArraySize(inputs); ++i)
| |
{
| |
if(inputs[i].flag)
| |
{
| |
result += "," + inputs[i].CSVvalue(buf, k++ * sizeof(long));
| |
}
| |
}
| |
return result;
| |
}
| |
| |
void print() const
| |
{
| |
StructPrint(header);
| |
ArrayPrint(inputs);
| |
const int size = ArraySize(inputs);
| |
for(int i = 0; i < size; ++i)
| |
{
| |
Print((inputs[i].flag ? "*" : ""),
| |
inputs[i].CSVname(), "=", inputs[i].CSVvalue(bufferOfInputs));
| |
}
| |
}
| |
| |
void inputs2CSV(const string name)
| |
{
| |
int output = FileOpen(name, FILE_WRITE | FILE_CSV | FILE_ANSI);
| |
if(output != INVALID_HANDLE)
| |
{
| |
FileWriteString(output, "Parameter,Value,Opt\n");
| |
const int size = ArraySize(inputs);
| |
for(int i = 0; i < size; ++i)
| |
{
| |
string value = inputs[i].CSVvalue(bufferOfInputs);
| |
if(StringFind(value, ",") > -1) value = "\"" + value + "\"";
| |
FileWriteString(output, inputs[i].CSVname() + "," + value + "," + (inputs[i].flag ? "Y" : "N") + "\n");
| |
}
| |
PrintFormat("File '%s' saved", name);
| |
}
| |
else
| |
{
| |
PrintFormat("File '%s' failed", name);
| |
}
| |
}
| |
| |
void header2CSV(const string name)
| |
{
| |
int output = FileOpen(name, FILE_WRITE | FILE_CSV | FILE_ANSI);
| |
if(output != INVALID_HANDLE)
| |
{
| |
FileWriteString(output, header.CSVtext());
| |
FileClose(output);
| |
PrintFormat("File '%s' saved", name);
| |
}
| |
else
| |
{
| |
PrintFormat("File '%s' failed", name);
| |
}
| |
}
| |
| |
void export2CSV(const string name)
| |
{
| |
int output = FileOpen(name, FILE_WRITE | FILE_CSV | FILE_ANSI);
| |
if(output != INVALID_HANDLE)
| |
{
| |
FileWriteString(output, CSVheader() + "\n");
| |
for(int i = 0; i < records[].size(); ++i)
| |
{
| |
FileWriteString(output, CSVline(i) + "\n");
| |
}
| |
FileClose(output);
| |
PrintFormat("File '%s' saved", name);
| |
}
| |
else
| |
{
| |
PrintFormat("File '%s' failed", name);
| |
}
| |
}
| |
};
| |
//+------------------------------------------------------------------+
| |
/*
| |
| |
OptReader reader(OptFilename);
| |
reader.print();
| |
reader.header2CSV(OptFilename + "-header.csv");
| |
reader.inputs2CSV(OptFilename + "-inputs.csv");
| |
reader.export2CSV(OptFilename + "-data.csv");
| |
| |
*/
| |
//+------------------------------------------------------------------+
|