495 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			MQL5
		
	
	
	
	
	
			
		
		
	
	
			495 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			MQL5
		
	
	
	
	
	
#define RESERVE_HISTORY_ORDERS 2000
 | 
						|
 | 
						|
#include "Order.mqh"
 | 
						|
 | 
						|
class HISTORYORDERS
 | 
						|
{
 | 
						|
private:
 | 
						|
  // HISTORYORDERS::Swap
 | 
						|
  int StartPos;
 | 
						|
  int LastPos;
 | 
						|
 | 
						|
  int PrevAmountHistoryOrders;
 | 
						|
 | 
						|
  int TradesProfit;
 | 
						|
  int TradesLoss;
 | 
						|
 | 
						|
  int TradesShortTotal;
 | 
						|
  int TradesLongTotal;
 | 
						|
 | 
						|
  int TradesShortProfit;
 | 
						|
  int TradesLongProfit;
 | 
						|
 | 
						|
  double InitialDeposit;
 | 
						|
  double Withdrawal;
 | 
						|
 | 
						|
  double GrossProfit;
 | 
						|
  double GrossLoss;
 | 
						|
 | 
						|
  double MaxProfit;
 | 
						|
  double MaxLoss;
 | 
						|
 | 
						|
  double Balance;
 | 
						|
  double MaxBalance;
 | 
						|
  double BalanceDD;
 | 
						|
 | 
						|
  void InitStatistic()
 | 
						|
  {
 | 
						|
    this.LastPos = this.StartPos;
 | 
						|
 | 
						|
    this.TradesProfit = 0;
 | 
						|
    this.TradesLoss = 0;
 | 
						|
 | 
						|
    this.TradesShortTotal = 0;
 | 
						|
    this.TradesLongTotal = 0;
 | 
						|
 | 
						|
    this.TradesShortProfit = 0;
 | 
						|
    this.TradesLongProfit = 0;
 | 
						|
 | 
						|
    this.InitialDeposit = ((this.StartPos < this.AmountHistoryOrders) &&
 | 
						|
                           (this.HistoryOrders[this.LastPos] == OP_BALANCE)) ? this.HistoryOrders[this.LastPos++].GetProfit() : 0;
 | 
						|
    this.Withdrawal = 0;
 | 
						|
 | 
						|
    this.GrossProfit = 0;
 | 
						|
    this.GrossLoss = 0;
 | 
						|
 | 
						|
    this.MaxProfit = 0;
 | 
						|
    this.MaxLoss = 0;
 | 
						|
 | 
						|
    this.Balance = 0;
 | 
						|
    this.MaxBalance = 0;
 | 
						|
    this.BalanceDD = 0;
 | 
						|
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  void SetOrder( const double Profit, int &TradesLongShortProfit, int &TradesLongShortTotal )
 | 
						|
  {
 | 
						|
    if (Profit > 0)
 | 
						|
    {
 | 
						|
      if (Profit > this.MaxProfit)
 | 
						|
        this.MaxProfit = Profit;
 | 
						|
 | 
						|
      this.GrossProfit += Profit;
 | 
						|
 | 
						|
      this.TradesProfit++;
 | 
						|
      TradesLongShortProfit++;
 | 
						|
    }
 | 
						|
    else if (Profit < 0)
 | 
						|
    {
 | 
						|
      if (Profit < this.MaxLoss)
 | 
						|
        this.MaxLoss = Profit;
 | 
						|
 | 
						|
      this.GrossLoss += Profit;
 | 
						|
 | 
						|
      this.TradesLoss++;
 | 
						|
    }
 | 
						|
 | 
						|
    this.Balance += Profit;
 | 
						|
 | 
						|
    if (this.Balance > this.MaxBalance)
 | 
						|
      this.MaxBalance = this.Balance;
 | 
						|
    else if (this.MaxBalance - this.Balance > this.BalanceDD)
 | 
						|
      this.BalanceDD = this.MaxBalance - this.Balance;
 | 
						|
 | 
						|
    TradesLongShortTotal++;
 | 
						|
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  void Refresh( void )
 | 
						|
  {
 | 
						|
    while (this.LastPos < this.AmountHistoryOrders)
 | 
						|
    {
 | 
						|
      switch (this.HistoryOrders[this.LastPos].GetType())
 | 
						|
      {
 | 
						|
      case ORDER_TYPE_BUY:
 | 
						|
        this.SetOrder(this.HistoryOrders[this.LastPos].GetFullProfit(), this.TradesLongProfit, this.TradesLongTotal);
 | 
						|
 | 
						|
        break;
 | 
						|
      case ORDER_TYPE_SELL:
 | 
						|
        this.SetOrder(this.HistoryOrders[this.LastPos].GetFullProfit(), this.TradesShortProfit, this.TradesShortTotal);
 | 
						|
 | 
						|
        break;
 | 
						|
      case OP_BALANCE:
 | 
						|
        if (this.LastPos != this.StartPos)
 | 
						|
          this.Withdrawal += this.HistoryOrders[this.LastPos].GetProfit();
 | 
						|
        else
 | 
						|
          this.InitialDeposit = this.HistoryOrders[this.LastPos].GetProfit();
 | 
						|
      }
 | 
						|
 | 
						|
      this.LastPos++;
 | 
						|
    }
 | 
						|
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  static string DoubleToString( const double Num, const int digits )
 | 
						|
  {
 | 
						|
    return((Num == DBL_MAX) ? "Max" : ::DoubleToString(Num, digits));
 | 
						|
  }
 | 
						|
 | 
						|
protected:
 | 
						|
  // HISTORYORDERS::Swap
 | 
						|
  ORDER_BASE HistoryOrders[];
 | 
						|
 | 
						|
  int ReserveHistoryOrders;
 | 
						|
 | 
						|
  bool FlagChange;
 | 
						|
 | 
						|
  bool Swap( HISTORYORDERS &OtherHistoryOrders )
 | 
						|
  {
 | 
						|
  #ifdef __MQL5__
 | 
						|
    const bool Res = ::ArraySwap(this.HistoryOrders, OtherHistoryOrders.HistoryOrders);
 | 
						|
  #else // #ifdef __MQL5__
 | 
						|
    const bool Res = false;
 | 
						|
  #endif // #ifdef __MQL5__ #else
 | 
						|
 | 
						|
    if (Res)
 | 
						|
    {
 | 
						|
      this.AmountHistoryOrders = OtherHistoryOrders.AmountHistoryOrders;
 | 
						|
      this.ReserveHistoryOrders = OtherHistoryOrders.ReserveHistoryOrders;
 | 
						|
      this.FlagChange = OtherHistoryOrders.FlagChange;
 | 
						|
 | 
						|
      this.StartPos = OtherHistoryOrders.StartPos;
 | 
						|
      this.LastPos = OtherHistoryOrders.LastPos;
 | 
						|
 | 
						|
      this.PrevAmountHistoryOrders = OtherHistoryOrders.PrevAmountHistoryOrders;
 | 
						|
 | 
						|
      this.TradesProfit = OtherHistoryOrders.TradesProfit;
 | 
						|
      this.TradesLoss = OtherHistoryOrders.TradesLoss;
 | 
						|
 | 
						|
      this.TradesShortTotal = OtherHistoryOrders.TradesShortTotal;
 | 
						|
      this.TradesLongTotal = OtherHistoryOrders.TradesLongTotal;
 | 
						|
 | 
						|
      this.TradesShortProfit = OtherHistoryOrders.TradesShortProfit;
 | 
						|
      this.TradesLongProfit = OtherHistoryOrders.TradesLongProfit;
 | 
						|
 | 
						|
      this.InitialDeposit = OtherHistoryOrders.InitialDeposit;
 | 
						|
      this.Withdrawal = OtherHistoryOrders.Withdrawal;
 | 
						|
 | 
						|
      this.GrossProfit = OtherHistoryOrders.GrossProfit;
 | 
						|
      this.GrossLoss = OtherHistoryOrders.GrossLoss;
 | 
						|
 | 
						|
      this.MaxProfit = OtherHistoryOrders.MaxProfit;
 | 
						|
      this.MaxLoss = OtherHistoryOrders.MaxLoss;
 | 
						|
 | 
						|
      this.Balance = OtherHistoryOrders.Balance;
 | 
						|
      this.MaxBalance = OtherHistoryOrders.MaxBalance;
 | 
						|
      this.BalanceDD = OtherHistoryOrders.BalanceDD;
 | 
						|
    }
 | 
						|
 | 
						|
    return(Res);
 | 
						|
  }
 | 
						|
 | 
						|
public: // ORDER::IsChange2
 | 
						|
  void AddHistoryOrder( const ORDER_BASE &Order )
 | 
						|
  {
 | 
						|
    if (this.AmountHistoryOrders == this.ReserveHistoryOrders)
 | 
						|
      // https://www.mql5.com/ru/forum/282062/page48#comment_50770216
 | 
						|
      this.ReserveHistoryOrders = ::ArrayResize(this.HistoryOrders, ::MathMax(RESERVE_HISTORY_ORDERS,
 | 
						|
                                                                              this.ReserveHistoryOrders << 1));
 | 
						|
 | 
						|
//    ORDER OrderTmp = Order; // https://www.mql5.com/ru/forum/492248/page3#comment_57718727
 | 
						|
    ORDER OrderTmp; OrderTmp = Order;
 | 
						|
 | 
						|
    if (OrderTmp.IsNotNull() && this.AmountHistoryOrders < this.ReserveHistoryOrders)
 | 
						|
    {
 | 
						|
      OrderTmp.ToHistory();
 | 
						|
 | 
						|
      this.HistoryOrders[this.AmountHistoryOrders++] = OrderTmp;
 | 
						|
 | 
						|
      this.FlagChange = true;
 | 
						|
    }
 | 
						|
 | 
						|
    return;
 | 
						|
  }
 | 
						|
protected:
 | 
						|
 | 
						|
  bool CopyFrom( const HISTORYORDERS* SourceOrders )
 | 
						|
  {
 | 
						|
    this.AmountHistoryOrders = SourceOrders.AmountHistoryOrders;
 | 
						|
    this.ReserveHistoryOrders = ::ArrayCopy(this.HistoryOrders, SourceOrders.HistoryOrders);
 | 
						|
 | 
						|
    this.InitStat();
 | 
						|
 | 
						|
    return(this.ReserveHistoryOrders == ::ArraySize(SourceOrders.HistoryOrders));
 | 
						|
  }
 | 
						|
 | 
						|
  bool GetReduceOrder( ORDER_BASE &Order, const int Pos ) const
 | 
						|
  {
 | 
						|
    const bool Res = (Pos >= 0) && (Pos < this.AmountHistoryOrders);
 | 
						|
 | 
						|
    if (Res)
 | 
						|
    {
 | 
						|
      double Sum = 0;
 | 
						|
 | 
						|
      for (int i = 0; i <= Pos; i++)
 | 
						|
        Sum += this.HistoryOrders[i].GetFullProfit();
 | 
						|
 | 
						|
      Order.Set(this.HistoryOrders[Pos].GetTicket(), OP_BALANCE, 0, NULL, "ReduceHistory", 0,
 | 
						|
                this.HistoryOrders[Pos].GetCloseTimeMsc(), 0, 0, 0,
 | 
						|
                this.HistoryOrders[Pos].GetCloseTimeMsc(), 0, Sum);
 | 
						|
    }
 | 
						|
 | 
						|
    return(Res);
 | 
						|
  }
 | 
						|
 | 
						|
  bool GetReduceOrder( ORDER_BASE &Order, const int Pos, const bool ChangeHistory )
 | 
						|
  {
 | 
						|
    const bool Res = this.GetReduceOrder(Order, Pos);
 | 
						|
 | 
						|
    if (Res && ChangeHistory && Pos)
 | 
						|
    {
 | 
						|
      int Amount = 1;
 | 
						|
 | 
						|
      for (int i = Pos + 1; i < this.AmountHistoryOrders; i++)
 | 
						|
        this.HistoryOrders[Amount++] = this.HistoryOrders[i];
 | 
						|
 | 
						|
      this.HistoryOrders[0] = Order;
 | 
						|
      this.AmountHistoryOrders = Amount;
 | 
						|
 | 
						|
      this.InitStatistic();
 | 
						|
    }
 | 
						|
 | 
						|
    return(Res);
 | 
						|
  }
 | 
						|
 | 
						|
  TICKET_TYPE GetMaxHistoryTicket( void ) const
 | 
						|
  {
 | 
						|
    TICKET_TYPE MaxTicket = 0;
 | 
						|
 | 
						|
    for (int i = this.AmountHistoryOrders - 1; i >= 0; i--)
 | 
						|
    {
 | 
						|
      const TICKET_TYPE Ticket = this.HistoryOrders[i].GetTicket();
 | 
						|
 | 
						|
      if (Ticket > MaxTicket)
 | 
						|
        MaxTicket = Ticket;
 | 
						|
    }
 | 
						|
 | 
						|
    return(MaxTicket);
 | 
						|
  }
 | 
						|
 | 
						|
public:
 | 
						|
  int AmountHistoryOrders;
 | 
						|
 | 
						|
  HISTORYORDERS() : AmountHistoryOrders(0), ReserveHistoryOrders(0), PrevAmountHistoryOrders(0), StartPos(0), FlagChange(true)
 | 
						|
  {
 | 
						|
    this.InitStatistic();
 | 
						|
  }
 | 
						|
 | 
						|
  bool ResetTickets( void )
 | 
						|
  {
 | 
						|
    long Tickets[][2];
 | 
						|
    const int Size = ::ArrayResize(Tickets, this.AmountHistoryOrders);
 | 
						|
 | 
						|
  #define VALUE 0
 | 
						|
  #define INDEX 1
 | 
						|
    for (int i = 0; i < Size; i++)
 | 
						|
    {
 | 
						|
//      Tickets[i][VALUE] = this.HistoryOrders[i].GetTicket();
 | 
						|
      Tickets[i][VALUE] = this.HistoryOrders[i].GetOpenTimeMsc();
 | 
						|
      Tickets[i][INDEX] = i;
 | 
						|
    }
 | 
						|
 | 
						|
    ::ArraySort(Tickets);
 | 
						|
 | 
						|
    for (int i = 0; i < Size; i++)
 | 
						|
      this.HistoryOrders[(int)Tickets[i][INDEX]].SetTicket(i + 1);
 | 
						|
  #undef INDEX
 | 
						|
  #undef VALUE
 | 
						|
 | 
						|
    ORDER::SetTicketCount(Size);
 | 
						|
 | 
						|
    return(true);
 | 
						|
  }
 | 
						|
 | 
						|
  bool InitStat( const long StartTime = 0 )
 | 
						|
  {
 | 
						|
    if (this.AmountHistoryOrders)
 | 
						|
    {
 | 
						|
      if (this.StartPos < this.AmountHistoryOrders)
 | 
						|
      {
 | 
						|
        if (this.HistoryOrders[this.StartPos] != StartTime)
 | 
						|
        {
 | 
						|
          if (this.HistoryOrders[this.StartPos] < StartTime)
 | 
						|
          {
 | 
						|
            while ((++this.StartPos < this.AmountHistoryOrders) && (this.HistoryOrders[this.StartPos] < StartTime))
 | 
						|
              ;
 | 
						|
 | 
						|
            this.InitStatistic();
 | 
						|
          }
 | 
						|
          else if (this.StartPos && (this.HistoryOrders[0] <= StartTime))
 | 
						|
          {
 | 
						|
            const int PrevStartPos = this.StartPos;
 | 
						|
 | 
						|
            while ((--this.StartPos >= 0) && (this.HistoryOrders[this.StartPos] >= StartTime))
 | 
						|
              ;
 | 
						|
 | 
						|
            this.StartPos++;
 | 
						|
 | 
						|
            if (this.StartPos != PrevStartPos)
 | 
						|
              this.InitStatistic();
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
      else if (this.HistoryOrders[this.StartPos - 1] > StartTime)
 | 
						|
      {
 | 
						|
        while ((--this.StartPos >= 0) && (this.HistoryOrders[this.StartPos] >= StartTime))
 | 
						|
          ;
 | 
						|
 | 
						|
        this.StartPos++;
 | 
						|
 | 
						|
        this.InitStatistic();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return(true);
 | 
						|
  }
 | 
						|
 | 
						|
  double TesterStatistics( const ENUM_STATISTICS StatID )
 | 
						|
  {
 | 
						|
    double Res = 0;
 | 
						|
 | 
						|
    this.Refresh();
 | 
						|
 | 
						|
    switch (StatID)
 | 
						|
    {
 | 
						|
    case STAT_INITIAL_DEPOSIT: // Значение начального депозита
 | 
						|
      Res = this.InitialDeposit;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_WITHDRAWAL: // Количество выведенных со счета средств
 | 
						|
      Res = this.Withdrawal;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_PROFIT: // Чистая прибыль по окончании тестирования
 | 
						|
      Res = this.GrossLoss + this.GrossProfit;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_GROSS_PROFIT: // Общая прибыль, сумма всех прибыльных (положительных) трейдов. Значение больше или равно нулю
 | 
						|
      Res = this.GrossProfit;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_GROSS_LOSS: // Общий убыток, сумма всех убыточных (отрицательных) трейдов. Значение меньше или равно нулю
 | 
						|
      Res = this.GrossLoss;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_MAX_PROFITTRADE: // Максимальная прибыль – наибольшее значение среди всех прибыльных трейдов. Значение больше или равно нулю
 | 
						|
      Res = this.MaxProfit;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_MAX_LOSSTRADE: // Максимальный убыток – наименьшее значение среди всех убыточных трейдов. Значение меньше или равно нулю
 | 
						|
      Res = this.MaxLoss;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_BALANCE_DD: // Максимальная просадка баланса в деньгах. В процессе торговли баланс может испытать множество просадок, берется наибольшее значение.
 | 
						|
      Res = this.BalanceDD;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_EXPECTED_PAYOFF: // Математическое ожидание выигрыша
 | 
						|
      {
 | 
						|
        const int Total = this.TradesLongTotal + this.TradesShortTotal;
 | 
						|
 | 
						|
        Res = Total ? (this.GrossLoss + this.GrossProfit) / Total : 0;
 | 
						|
      }
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_PROFIT_FACTOR: // Прибыльность – отношение STAT_GROSS_PROFIT/STAT_GROSS_LOSS. Если STAT_GROSS_LOSS=0, то прибыльность принимает значение DBL_MAX
 | 
						|
      Res = this.GrossLoss ? -this.GrossProfit / this.GrossLoss : DBL_MAX;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_RECOVERY_FACTOR: // Фактор восстановления – отношение STAT_PROFIT/STAT_BALANCE_DD
 | 
						|
      Res = this.BalanceDD ? (this.GrossLoss + this.GrossProfit) / this.BalanceDD : 0;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_DEALS: // Количество совершенных сделок
 | 
						|
      Res = (this.TradesLongTotal + this.TradesShortTotal) << 1;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_TRADES: // Количество трейдов
 | 
						|
      Res = this.TradesLongTotal + this.TradesShortTotal;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_PROFIT_TRADES: // Прибыльные трейды
 | 
						|
      Res = this.TradesProfit;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_LOSS_TRADES: // Убыточные трейды
 | 
						|
      Res = this.TradesLoss;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_SHORT_TRADES: // Короткие трейды
 | 
						|
      Res = this.TradesShortTotal;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_LONG_TRADES: // Длинные трейды
 | 
						|
      Res = this.TradesLongTotal;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_PROFIT_SHORTTRADES: // Короткие прибыльные трейды
 | 
						|
      Res = this.TradesShortProfit;
 | 
						|
 | 
						|
      break;
 | 
						|
    case STAT_PROFIT_LONGTRADES: // Длинные прибыльные трейды
 | 
						|
      Res = this.TradesLongProfit;
 | 
						|
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    return(Res);
 | 
						|
  }
 | 
						|
 | 
						|
  bool IsHistoryChanged( void )
 | 
						|
  {
 | 
						|
    const bool Res = (this.AmountHistoryOrders != this.PrevAmountHistoryOrders);
 | 
						|
 | 
						|
    if (Res)
 | 
						|
      this.PrevAmountHistoryOrders = this.AmountHistoryOrders;
 | 
						|
 | 
						|
    return(Res);
 | 
						|
  }
 | 
						|
 | 
						|
  bool GetFlagChange( void )
 | 
						|
  {
 | 
						|
    const bool Res = this.FlagChange;
 | 
						|
 | 
						|
    this.FlagChange = false;
 | 
						|
 | 
						|
    return(Res);
 | 
						|
  }
 | 
						|
 | 
						|
  string StatToString( const int &Amount )
 | 
						|
  {
 | 
						|
    return("Profit = " + HISTORYORDERS::DoubleToString(this.TesterStatistics(STAT_PROFIT) / Amount, 2) + ", " +
 | 
						|
           "Trades = " + HISTORYORDERS::DoubleToString(this.TesterStatistics(STAT_TRADES) / Amount, 0) + ", " +
 | 
						|
           "PF = " + HISTORYORDERS::DoubleToString(this.TesterStatistics(STAT_PROFIT_FACTOR), 2) + ", " +
 | 
						|
           "Mean = " + HISTORYORDERS::DoubleToString(this.TesterStatistics(STAT_EXPECTED_PAYOFF), 2) + ", " +
 | 
						|
           "MaxDD = " + HISTORYORDERS::DoubleToString(-this.TesterStatistics(STAT_BALANCE_DD) / Amount, 2) + ", " +
 | 
						|
           "RF = " + HISTORYORDERS::DoubleToString(this.TesterStatistics(STAT_RECOVERY_FACTOR), 2));
 | 
						|
  }
 | 
						|
 | 
						|
  double CalcSwaps( const double &SwapShort, const double &SwapLong, const datetime &RolloverTime, const int &Rollover3Days )
 | 
						|
  {
 | 
						|
    double ChangeSwaps = 0;
 | 
						|
 | 
						|
    for (int i = this.AmountHistoryOrders - 1; i >= 0; i--)
 | 
						|
      ChangeSwaps += this.HistoryOrders[i].SetSwap(SwapShort, SwapLong, RolloverTime, Rollover3Days);
 | 
						|
 | 
						|
    this.InitStatistic();
 | 
						|
 | 
						|
    return(ChangeSwaps);
 | 
						|
  }
 | 
						|
 | 
						|
  void MoveHistory( HISTORYORDERS& pOrders )
 | 
						|
  {
 | 
						|
    for (int i = 0; i < this.AmountHistoryOrders; i++)
 | 
						|
      pOrders.AddHistoryOrder(this.HistoryOrders[i]);
 | 
						|
 | 
						|
    this.AmountHistoryOrders = 0;
 | 
						|
 | 
						|
    return;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
#undef RESERVE_HISTORY_ORDERS
 |