Virtual_by_fxsaber/Include/fxsaber/Virtual/HistoryOrders.mqh

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