BalanceInfo

Функция для рассчёта основных балансовых показателей, с раздельным учётом по MAGIC+SYMBOL (то есть даёт возможность отчасти вести «виртуальный» суб-баланс для робота)

Вычисляются следующие показатели:

  • начальный баланс то есть последний после снятия/пополнения или самый старый известный по истории
  • максимальный рост баланса
  • падение баланса после роста
  • восстановление после падения
  • текущее состояние
BalanceInfo.mq4
enum ENUM_BALANCE_RECORD {
   BALANCE_RECORD_BEGIN,   // начальное значение баланса
   BALANCE_RECORD_MAXIMUM, // максимум 
   BALANCE_RECORD_DD,      // просадка после достижение максимума
   BALANCE_RECORD_GAIN,    // восстановление после просадка
   BALANCE_RECORD_CURRENT  // текущий баланс
};   
#define TOTAL_BALANCE_RECORDS 5
 
struct BalanceRecord {
   ENUM_BALANCE_RECORD type;
   int ticket;
   int pos;
   double balance;
   datetime time;
};
/* 
   рассчёт балансовых показателей
   - начальный баланс (или последнее снятие/пополнение) - значение и дата/время
   - максимальный баланс с того момента - значение и дата/время (с учётом MAGIC+SYMBOL)
   - просадка(минимум) баланс после достижения максимума - значение и дата/время и также с учётом MAGIC+SYMBOL
   - восстановление (следующий рост) минимум после максимума - значение и дата/время
   - текущий баланс
   прим.: ордера по текущему символу _Symbol всегда учитываются, вне зависимости от параметра symbol
*/
 
double
BalanceInfo(BalanceRecord &rec[TOTAL_BALANCE_RECORDS],int magic=-1,string symbol="*",double percents=100.0)
{
   rec[BALANCE_RECORD_BEGIN].type=BALANCE_RECORD_BEGIN;
   rec[BALANCE_RECORD_MAXIMUM].type=BALANCE_RECORD_MAXIMUM;
   rec[BALANCE_RECORD_DD].type=BALANCE_RECORD_DD;
   rec[BALANCE_RECORD_GAIN].type=BALANCE_RECORD_GAIN;
   rec[BALANCE_RECORD_CURRENT].type=BALANCE_RECORD_CURRENT;
   for(int t=ArraySize(rec)-1;t>=0;t--) {
      rec[t].ticket=-1;
      rec[t].pos=-1;
      rec[t].balance=0;
      rec[t].time=0;
   }
 
   if (symbol=="") symbol=_Symbol;
   // 1. получить (найти) начальное значение баланса + найти максимум и текущий баланс
   // FIXME: полагаясь на то что в истории ордера сортированы от старых к новым
   double profit=0;  // накапливать профит по ордерам от новых к старым
   for(int pos=OrdersHistoryTotal()-1;pos>=0;pos--) {
      if (!OrderSelect(pos,SELECT_BY_POS,MODE_HISTORY)) continue;
      int orderType=OrderType();
      if (OrderSymbol()=="" || (orderType!=OP_BUY && orderType!=OP_SELL && orderType!=OP_BUYLIMIT && orderType!=OP_BUYSTOP && orderType!=OP_SELLLIMIT && orderType!=OP_SELLSTOP) ) {
         // балансовая запись
         rec[BALANCE_RECORD_BEGIN].time=OrderCloseTime();
         rec[BALANCE_RECORD_BEGIN].pos=pos;
         rec[BALANCE_RECORD_BEGIN].ticket=OrderTicket();
         rec[BALANCE_RECORD_BEGIN].balance=OrderProfit()+OrderSwap()+OrderCommission();
         break;
      }
      if (magic!=-1 && magic!=OrderMagicNumber()) continue;
      if (symbol!="*" && OrderSymbol()!=_Symbol && symbol!=OrderSymbol()) continue;
      profit+=OrderProfit()+OrderSwap()+OrderCommission();
      if (orderType!=OP_BUY && orderType!=OP_SELL) {
         // удаление отложек не изменяет баланс
         continue;
      }
      // тикет,время и позиция в BALANCE_RECORD_BEGIN - от самого старого рыночного ордера
      rec[BALANCE_RECORD_BEGIN].time=OrderCloseTime();
      rec[BALANCE_RECORD_BEGIN].ticket=OrderTicket();
      rec[BALANCE_RECORD_BEGIN].pos=pos;
 
      if (rec[BALANCE_RECORD_CURRENT].time==0) {
         rec[BALANCE_RECORD_CURRENT].time=OrderCloseTime();
         rec[BALANCE_RECORD_CURRENT].ticket=OrderTicket();
         rec[BALANCE_RECORD_CURRENT].pos=pos;
         rec[BALANCE_RECORD_CURRENT].balance=profit;
      }
      if (rec[BALANCE_RECORD_MAXIMUM].time==0 || rec[BALANCE_RECORD_MAXIMUM].balance>profit) {
         rec[BALANCE_RECORD_MAXIMUM].time=OrderCloseTime();
         rec[BALANCE_RECORD_MAXIMUM].ticket=OrderTicket();
         rec[BALANCE_RECORD_MAXIMUM].pos=pos;
         rec[BALANCE_RECORD_MAXIMUM].balance=profit;
      }
   }
   if (rec[BALANCE_RECORD_BEGIN].balance==0) {
      // почему-то балансовая запись ненайдена
      rec[BALANCE_RECORD_BEGIN].balance=AccountInfoDouble(ACCOUNT_BALANCE)-profit;
   }
   //
   if (percents<100.0 && percents>0.0) {
      // баланс робота ограничен %% от полного баланса
      rec[BALANCE_RECORD_BEGIN].balance*=percents/100.0;
   }
   if (rec[BALANCE_RECORD_CURRENT].time!=0) {
      // если вообще были найдены наши ордера
      // надо перерассчитать поле balance
      rec[BALANCE_RECORD_MAXIMUM].balance=rec[BALANCE_RECORD_BEGIN].balance+profit-rec[BALANCE_RECORD_MAXIMUM].balance;
      rec[BALANCE_RECORD_CURRENT].balance=rec[BALANCE_RECORD_BEGIN].balance+profit;
      // найти просадку после максимума
      profit=rec[BALANCE_RECORD_MAXIMUM].balance;
      for(int pos=rec[BALANCE_RECORD_MAXIMUM].pos+1;pos<=rec[BALANCE_RECORD_CURRENT].pos;pos++) {
         if (!OrderSelect(pos,SELECT_BY_POS,MODE_HISTORY)) continue;
         if (magic!=-1 && magic!=OrderMagicNumber()) continue;
         if (symbol!="*" && OrderSymbol()!=_Symbol && symbol!=OrderSymbol()) continue;
         if (OrderType()!=OP_BUY && OrderType()!=OP_SELL) continue;
         profit+=OrderProfit()+OrderSwap()+OrderCommission();
         if (rec[BALANCE_RECORD_DD].time==0 || rec[BALANCE_RECORD_DD].balance>profit) {
            rec[BALANCE_RECORD_DD].time=OrderCloseTime();
            rec[BALANCE_RECORD_DD].ticket=OrderTicket();
            rec[BALANCE_RECORD_DD].pos=pos;
            rec[BALANCE_RECORD_DD].balance=profit;
         }
      }
      // найти рост после просадки
      profit=rec[BALANCE_RECORD_DD].balance;
      for(int pos=rec[BALANCE_RECORD_DD].pos+1;pos<=rec[BALANCE_RECORD_CURRENT].pos;pos++) {
         if (!OrderSelect(pos,SELECT_BY_POS,MODE_HISTORY)) continue;
         if (magic!=-1 && magic!=OrderMagicNumber()) continue;
         if (symbol!="*" && OrderSymbol()!=_Symbol && symbol!=OrderSymbol()) continue;
         if (OrderType()!=OP_BUY && OrderType()!=OP_SELL) continue;
         profit+=OrderProfit()+OrderSwap()+OrderCommission();
         if (rec[BALANCE_RECORD_GAIN].time==0 || rec[BALANCE_RECORD_GAIN].balance<profit) {
            rec[BALANCE_RECORD_GAIN].time=OrderCloseTime();
            rec[BALANCE_RECORD_GAIN].ticket=OrderTicket();
            rec[BALANCE_RECORD_GAIN].pos=pos;
            rec[BALANCE_RECORD_GAIN].balance=profit;
         }
      }
   } else {
      // небыло торговли по SYMBOL+MAGIC
      // все записи=начальная
      for(int pos=ArraySize(rec)-1;pos>0;pos--) {
         rec[pos].balance=rec[0].balance;
         rec[pos].time=rec[0].time;
         rec[pos].pos=rec[0].pos;
         rec[pos].ticket=rec[0].ticket;
      }
   }
   // есть вариант когда DD,GAIN небыли найдены
   // значит они попадают на CURRENT
   if (rec[BALANCE_RECORD_DD].time==0) {
      rec[BALANCE_RECORD_DD].time=rec[BALANCE_RECORD_CURRENT].time;
      rec[BALANCE_RECORD_DD].ticket=rec[BALANCE_RECORD_CURRENT].ticket;
      rec[BALANCE_RECORD_DD].pos=rec[BALANCE_RECORD_CURRENT].pos;
      rec[BALANCE_RECORD_DD].balance=rec[BALANCE_RECORD_CURRENT].balance;
   }
   if (rec[BALANCE_RECORD_GAIN].time==0) {
      rec[BALANCE_RECORD_GAIN].time=rec[BALANCE_RECORD_CURRENT].time;
      rec[BALANCE_RECORD_GAIN].ticket=rec[BALANCE_RECORD_CURRENT].ticket;
      rec[BALANCE_RECORD_GAIN].pos=rec[BALANCE_RECORD_CURRENT].pos;
      rec[BALANCE_RECORD_GAIN].balance=rec[BALANCE_RECORD_CURRENT].balance;
   }
   // вернуть абс. текущую просадку - она чаще всего требуется
   return rec[BALANCE_RECORD_MAXIMUM].balance-rec[BALANCE_RECORD_CURRENT].balance;
}