315 lines
		
	
	
		
			No EOL
		
	
	
		
			16 KiB
		
	
	
	
		
			MQL5
		
	
	
	
	
	
			
		
		
	
	
			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);
 | 
						|
  }
 | 
						|
}; |