Virtual_by_fxsaber/Include/fxsaber/Virtual/VirtualTester.mqh

315 lines
No EOL
16 KiB
MQL5

#include <fxsaber\Virtual\Virtual.mqh> // https://www.mql5.com/ru/code/22577
enum ENUM_VIRTUALTESTER_TYPE
{
VIRTUALTESTER_CLASSIC, // Classic
VIRTUALTESTER_APART // Apart
};
class VIRTUAL_TESTER
{
private:
string Symbols[];
TICKS_ARRAY TicksArray[];
ulong LengthGetTicks[];
int AmountTicks;
datetime FromTicks;
datetime ToTicks;
datetime From;
datetime To;
double Balance;
ulong Length;
double Result;
static void StringTrim( string &Str )
{
::StringTrimLeft(Str);
::StringTrimRight(Str);
return;
}
static string LengthToString( const datetime Length )
{
const int Days = (int)(Length / (24 * 3600));
return(((Days) ? (string)Days + "d ": "") + ::TimeToString(Length, TIME_SECONDS));
}
void Parse( const string &sSymbols )
{
::ArrayFree(this.Symbols);
for (int i = ::StringSplit(sSymbols, ',', this.Symbols) - 1; i >= 0; i--)
VIRTUAL_TESTER::StringTrim(this.Symbols[i]);
return;
}
virtual int Check( void )
{
const int Size = ::ArraySize(this.Symbols);
int Amount = 0;
bool Custom = false;
for (int i = 0; i < Size; i++)
// if (::SymbolInfoInteger(this.Symbols[i], SYMBOL_EXIST)) // VIRTUAL_NOCHECK_NULL
if (::SymbolExist(this.Symbols[i], Custom))
this.Symbols[Amount++] = this.Symbols[i];
else
::Alert("Symbol " + this.Symbols[i] + " is not exist!");
return(::ArrayResize(this.Symbols, Amount));
}
int GetSymbols( const string &sSymbols )
{
this.Parse(sSymbols);
return(this.Check());
}
void ToNull( void )
{
this.Length = 0;
this.Result = 0;
return;
}
protected:
ulong GetSumLengthGetTick( void ) const
{
ulong Sum = 0;
for (uint i = ::ArraySize(this.LengthGetTicks); (bool)i--;)
Sum += this.LengthGetTicks[i];
return(Sum);
}
virtual double CalcOnTester( void )
{
this.Result = ::IsStopped() ? -DBL_MAX : ::TesterStatistics(STAT_PROFIT);
return(this.Result);
}
public:
VIRTUAL_TESTER( const string sSymbols, const double dBalance = DBL_MIN ) : From(INT_MAX), To(0)
{
this.SetSymbols(sSymbols);
this.SetBalance(dBalance);
}
bool SetSymbols( const string sSymbols )
{
string PrevSymbols[];
::ArraySwap(PrevSymbols, this.Symbols);
const bool Res = this.GetSymbols(sSymbols);
if (Res)
this.FreeTicks();
else
::ArraySwap(PrevSymbols, this.Symbols);
return(Res);
}
bool SetBalance( const double dBalance = DBL_MIN )
{
const bool Res = (dBalance > 0);
if (Res)
{
this.Balance = dBalance;
this.ToNull();
}
return(Res);
}
bool SetInterval( const datetime dFrom, const datetime dTo = INT_MAX)
{
const bool Res = (dFrom < dTo);
if (Res)
{
this.FreeTicks();
this.From = dFrom;
this.To = dTo;
}
return(Res);
}
void FreeTicks( void )
{
::ArrayFree(this.TicksArray);
::ArrayFree(this.LengthGetTicks);
this.AmountTicks = 0;
this.FromTicks = INT_MAX;
this.ToTicks = 0;
this.ToNull();
return;
}
virtual int GetTicks( void )
{
this.FreeTicks();
if (this.From != INT_MAX)
{
::Print("Getting Ticks (" + (string)this.From + " - " + (string)this.To + ")...");
::ArrayPrint(this.Symbols);
const int Size = ::ArrayResize(this.LengthGetTicks, ::ArrayResize(this.TicksArray, ::ArraySize(this.Symbols)));
for (int i = 0; i < Size; i++)
{
this.LengthGetTicks[i] = ::GetMicrosecondCount();
const int Amount = ::CopyTicksRange(Symbols[i], TicksArray[i].Ticks, COPY_TICKS_ALL,
(long)this.From * 1000, (long)this.To * 1000 - 1);
this.LengthGetTicks[i] = ::GetMicrosecondCount() - this.LengthGetTicks[i];
if (Amount > 0)
{
this.FromTicks = ::MathMin(this.FromTicks, TicksArray[i].Ticks[0].time);
this.ToTicks = ::MathMax(this.ToTicks, TicksArray[i].Ticks[Amount - 1].time);
::Print((string)(i + 1) + ". " + this.Symbols[i] + ": " + (string)Amount + " ticks (" +
(string)TicksArray[i].Ticks[0].time + " - " +
(string)TicksArray[i].Ticks[Amount - 1].time + ")");
this.AmountTicks += Amount;
}
}
}
return(this.AmountTicks);
}
int Run( const STRATEGY_MULTI StrategyMulti, const bool StopFlag = true, const bool Netting = false,
const ENUM_VIRTUALTESTER_TYPE Type = VIRTUALTESTER_CLASSIC )
{
if (!AmountTicks)
this.GetTicks();
::Print("Calculating (" + ::StringSubstr(::EnumToString(Type), ::StringLen("VIRTUALTESTER_")) +
") " + (string)this.AmountTicks + " ticks...");
this.Length = ::GetMicrosecondCount();
this.Result = 0;
const int Res = (Type == VIRTUALTESTER_CLASSIC)
? VIRTUAL::TesterMulti(this.Symbols, this.TicksArray, StrategyMulti, this.Balance, StopFlag, Netting)
: VIRTUAL::TesterMultiApart(this.Symbols, this.TicksArray, StrategyMulti, this.Balance, StopFlag, Netting);
if (Res != -1)
{
this.CalcOnTester();
this.Length = ::GetMicrosecondCount() - this.Length;
}
else
this.Length = 0;
return(Res);
}
double OnTester( void ) const
{
return(this.Result);
}
double GetPerformance( void ) const
{
return(this.Length ? 1e6 * this.AmountTicks / this.Length : 0);
}
ulong GetLength( void ) const
{
return(this.Length);
}
static string LengthToString( const long Length )
{
return(VIRTUAL_TESTER::LengthToString((datetime)(Length / 1000)) + "." + ::IntegerToString(Length % 1000, 3, '0'));
}
static string NumToString( double dVolume, const int PosLen )
{
static int Len[] = {0, 0, 0, 0, 0, 0};
const string Sign = (dVolume < 0) ? "-" : NULL;
dVolume = ::MathAbs(dVolume) + 0.001;
long Round = (long)dVolume;
string Res = ((bool)(Round / 1000) ? ::IntegerToString(Round % 1000, 3, '0') : (string)(Round % 1000)) +
"." + ::IntegerToString((long)(dVolume * 10) % 10, 1, '0');
Round /= 1000;
while (Round)
{
Res = ((bool)(Round / 1000) ? ::IntegerToString(Round % 1000, 3, '0') : (string)(Round % 1000)) + " " + Res;
Round /= 1000;
}
Res = Sign + Res;
const int Length = ::StringLen(Res);
string TmpStr = NULL;
if (Length > Len[PosLen])
Len[PosLen] = Length;
else
::StringInit(TmpStr, Len[PosLen] - Length, ' ');
return(TmpStr + Res);
}
virtual string ToString( void ) const
{
string Str = NULL;
if (this.Length)
{
Str += "final balance " + ::DoubleToString(::AccountInfoDouble(ACCOUNT_BALANCE), 2) + " pips";
Str += "\nOnTester result " + (string)this.Result;
Str += "\nTest passed in " + VIRTUAL_TESTER::LengthToString((this.Length + this.GetSumLengthGetTick()) / 1000) +
" (including ticks preprocessing " + VIRTUAL_TESTER::LengthToString(this.GetSumLengthGetTick() / 1000) + ").";
Str += "\ntotal ticks for all symbols " + (string)this.AmountTicks +
" (" + (string)this.FromTicks + " - " + (string)this.ToTicks + ")";
const int Size = ::ArraySize(this.LengthGetTicks);
for (int i = 0; i < Size; i++)
Str += "\n" + this.Symbols[i] + ": generate " + (string)::ArraySize(this.TicksArray[i].Ticks) +
" ticks in " + VIRTUAL_TESTER::LengthToString(this.LengthGetTicks[i] / 1000);
Str += "\nPerformance: " + VIRTUAL_TESTER::NumToString(this.GetPerformance(), 3) + " ticks/sec., " +
VIRTUAL_TESTER::LengthToString(this.Length / 1000);
}
return(Str);
}
};