Files
include/fxsaber/TesterCache/TesterCache.mqh
T
2025-02-07 19:08:31 +03:30

298 lines
20 KiB
Plaintext

#include "TestCacheHeader.mqh"
#include "ExpTradeSummary.mqh"
#include "TestCacheInput.mqh"
#include "Mathematics.mqh"
#include "TestCacheRecord.mqh"
#include "TestCacheSymbolRecord.mqh"
template <typename T>
class TESTERCACHE
{
private:
bool IsCorrectType( void ) const
{
return((typename(T) == "MATHEMATICS") && (this.Header.symbol[] == "")) ||
((typename(T) == "TestCacheSymbolRecord") && (this.Header.opt_mode == 2)) ||
((typename(T) == "ExpTradeSummary") && (this.Header.symbol[] != "") && (this.Header.opt_mode < 2));
}
public:
TestCacheHeader Header;
TestCacheInput Inputs[];
uchar ParametersBuffer[];
TestCacheRecord<T> Record[];
int UnknownNums[];
bool Load( const string FileName )
{
const int handle = ::FileOpen(FileName, FILE_READ | FILE_BIN);
bool Res = (handle != INVALID_HANDLE);
if (Res)
{
::ArrayFree(this.Inputs);
::ArrayFree(this.ParametersBuffer);
::ArrayFree(this.Record);
::ArrayFree(this.UnknownNums);
::FileReadStruct(handle, this.Header);
if (Res = this.IsCorrectType() && this.Header.passes_passed)
{
::FileReadArray(handle, this.Inputs, 0, this.Header.parameters_total);
::FileReadArray(handle, this.ParametersBuffer, 0, this.Header.parameters_size);
::FileReadArray(handle, this.UnknownNums, 0, this.Header.snapshot_size);
const int Size = ::ArrayResize(this.Record, this.Header.passes_passed);
const bool Offset = (this.Header.record_size - this.Header.opt_params_size - sizeof(T) == sizeof(long));
for (int i = 0; i < Size; i++)
{
::FileReadStruct(handle, this.Record[i].Result);
::FileReadArray(handle, this.Record[i].OptBuffer, 0, this.Header.opt_params_size);
if (Offset)
::FileReadArray(handle, this.Record[i].Genetic, 0, sizeof(long) + this.Header.dwords_cnt * sizeof(int));
else
::FileReadArray(handle, this.Record[i].Genetic, 0, this.Header.dwords_cnt * sizeof(int));
}
}
::FileClose(handle);
}
return(Res);
}
bool Load( const uchar &Bytes[] )
{
bool Res = ::ArraySize(Bytes);
if (Res)
{
::ArrayFree(this.Inputs);
::ArrayFree(this.ParametersBuffer);
::ArrayFree(this.Record);
::ArrayFree(this.UnknownNums);
_W(this.Header) = Bytes;
if (Res = this.IsCorrectType() && this.Header.passes_passed)
{
int Pos = sizeof(this.Header);
Pos += ::_ArrayCopy(this.Inputs, Bytes, 0, Pos, this.Header.parameters_total * sizeof(TestCacheInput));
Pos += ::_ArrayCopy(this.ParametersBuffer, Bytes, 0, Pos, this.Header.parameters_size);
Pos += ::_ArrayCopy(this.UnknownNums, Bytes, 0, Pos, this.Header.snapshot_size * sizeof(int));
const int Size = ::ArrayResize(this.Record, this.Header.passes_passed);
const bool Offset = (this.Header.record_size - this.Header.opt_params_size - sizeof(T) == sizeof(long));
for (int i = 0; i < Size; i++)
{
T Result[1];
Pos += ::_ArrayCopy(Result, Bytes, 0, Pos, sizeof(T));
this.Record[i].Result = Result[0];
Pos += ::_ArrayCopy(this.Record[i].OptBuffer, Bytes, 0, Pos, this.Header.opt_params_size);
if (Offset)
Pos += ::_ArrayCopy(this.Record[i].Genetic, Bytes, 0, Pos, sizeof(long) + this.Header.dwords_cnt * sizeof(int));
else
Pos += ::_ArrayCopy(this.Record[i].Genetic, Bytes, 0, Pos, this.Header.dwords_cnt * sizeof(int));
}
}
}
return(Res);
}
bool Save( const string FileName )
{
const int handle = ::FileOpen(FileName, FILE_WRITE | FILE_BIN);
bool Res = (handle != INVALID_HANDLE);
if (Res)
{
::FileWriteStruct(handle, this.Header);
::FileWriteArray(handle, this.Inputs);
::FileWriteArray(handle, this.ParametersBuffer);
::FileWriteArray(handle, this.UnknownNums);
const int Size = ::ArraySize(this.Record);
for (int i = 0; i < Size; i++)
{
::FileWriteStruct(handle, this.Record[i].Result);
::FileWriteArray(handle, this.Record[i].OptBuffer);
::FileWriteArray(handle, this.Record[i].Genetic);
}
::FileClose(handle);
}
return(Res);
}
TESTERCACHE( void )
{
}
TESTERCACHE( const string FileName )
{
this.Load(FileName);
}
// https://www.mql5.com/ru/forum/324536/page20#comment_13850494
TESTERCACHE( const uchar &Bytes[] )
{
this.Load(Bytes);
}
#define PARAM_NAME 0
#define PARAM_VALUE 1
#define PARAM_START 2
#define PARAM_STEP 3
#define PARAM_STOP 4
#define MACROS(A, B) \
const string Str##A = ((Type == TYPE_FLOAT) || (Type == TYPE_DOUBLE)) ? (string)this.Inputs[i].StartStepStop.Number.##A \
: (string)this.Inputs[i].StartStepStop.Integer.##A; \
Params[j][PARAM_##B].type = Params[j][PARAM_VALUE].type; \
Params[j][PARAM_##B].integer_value = (long)Str##A; \
Params[j][PARAM_##B].double_value = (double)Str##A; \
Params[j][PARAM_##B].string_value = Str##A;
int GetInputs( const int Num, MqlParam &Params[][5], const bool Full = false ) const
{
int j = 0;
int Count = 0;
const int Size = ::ArraySize(this.Inputs);
::ArrayResize(Params, Size);
::ZeroMemory(Params);
for (int i = 0; i < Size; i++)
if (Full || this.Inputs[i].flag)
{
Params[j][PARAM_NAME].type = TYPE_STRING;
Params[j][PARAM_NAME].string_value = this.Inputs[i].name[];
const int Type = this.Inputs[i].type - TYPE_OFFSET;
string StrValue;
if (this.Inputs[i].flag)
{
StrValue = ((Type == TYPE_FLOAT) || (Type == TYPE_DOUBLE)) ? (string)_R(this.Record[Num].OptBuffer)[(double)Count]
: (string)_R(this.Record[Num].OptBuffer)[(long)Count];
Count += sizeof(long);
}
else
{
const string Str = this.Inputs[i].ToString(this.ParametersBuffer);
StrValue = (Type == TYPE_BOOL) ? ((Str == "true") ? "1" : "0") :
((Type == TYPE_COLOR) ? (string)(long)(color)Str :
((Type == TYPE_DATETIME) ? (string)(long)(datetime)Str : Str));
}
Params[j][PARAM_VALUE].type = (Type >= TYPE_BOOL) && (Type <= TYPE_STRING) ? (ENUM_DATATYPE)Type : TYPE_INT;
Params[j][PARAM_VALUE].integer_value = (long)StrValue;
Params[j][PARAM_VALUE].double_value = (double)StrValue;
Params[j][PARAM_VALUE].string_value = StrValue;
MACROS(Start, START)
MACROS(Step, STEP)
MACROS(Stop, STOP)
j++;
}
return(::ArrayResize(Params, j));
}
#undef MACROS
bool SaveSet( const int Num, string FileName = NULL, const bool Details = true, string AddInformation = NULL )
{
if (FileName == NULL)
FileName = this.Header.expert_name[] + "_" + (string)Num + ".set";
const int handle = ::FileOpen(FileName, FILE_WRITE | FILE_UNICODE | FILE_TXT);
const bool Res= (handle != INVALID_HANDLE);
if (Res)
{
MqlParam Params[][5];
const int Size = this.GetInputs(Num, Params, true);
if (AddInformation != NULL)
{
AddInformation += "\n";
::StringReplace(AddInformation, "\n", "\n; ");
::FileWriteString(handle, "; " + AddInformation);
}
if (Details)
{
::FileWriteString(handle, ((AddInformation == NULL) ? NULL : "\n") +
"; saved on " + (string)::TimeLocal() + "\n; " +
this.Header.expert_path[] +"\n; " +
this.Header.symbol[] +"\n; " +
::TimeToString(this.Header.date_from, TIME_DATE) + " - " + ::TimeToString(this.Header.date_to, TIME_DATE) + "\n; " +
::DoubleToString(this[Num].TesterStatistics(STAT_PROFIT), 0) + ", " +
::DoubleToString(this[Num].TesterStatistics(STAT_TRADES), 0) + ", " +
::DoubleToString(this[Num].TesterStatistics(STAT_PROFIT_FACTOR), 2) + ", " +
::DoubleToString(this[Num].TesterStatistics(STAT_EXPECTED_PAYOFF), 2) + ", -" +
::DoubleToString(this[Num].TesterStatistics(STAT_EQUITY_DD), 2) +
"\n;\n");
}
for (int i = 0; i < Size; i++)
::FileWriteString(handle, Params[i][PARAM_NAME].string_value == "" ? ";\n" :
Params[i][PARAM_NAME].string_value + "=" +
Params[i][PARAM_VALUE].string_value +
((!Params[i][PARAM_START].double_value &&
!Params[i][PARAM_STEP].double_value &&
!Params[i][PARAM_STOP].double_value) ? NULL : "||" +
Params[i][PARAM_START].string_value + "||" +
Params[i][PARAM_STEP].string_value + "||" +
Params[i][PARAM_STOP].string_value + "||" +
(this.Inputs[i].flag ? "Y" : "N")) + "\n");
if (Details)
{
string Str = ";\n" + this[Num].ToString() + "\n" + this.Header.ToString();
::StringReplace(Str, "\n", "\n; ");
::FileWriteString(handle, Str);
}
::FileClose(handle);
}
return(Res);
}
const T operator[]( const int Num ) const
{
return(this.Record[Num].Result);
}
int GetAmount( void ) const
{
return(::ArraySize(this.Record));
}
};