AlgoMasterNNFX-V1/Backtester/VirtualTrades.mqh
2024-02-28 15:36:47 +01:00

729 lines
No EOL
22 KiB
MQL5

//+------------------------------------------------------------------+
//| VirtualTrades.mqh |
//| Copyright 2020, Alejandro Cercós |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, Alejandro Cercós"
#property link "https://www.mql5.com"
#ifdef __MQL5__
#include "../EAUtils\RiskCalc.mqh"
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
CTrade m_trade;
CPositionInfo m_position_info;
#endif
class VirtualTrade
{
private:
double openPrice;
double stopLoss;
double takeProfit;
double openAtr;
int tradeType;
bool touchedFirstTP;
bool startedMovingSL;
bool isOpen;
datetime openTime;
double tradeValue;
bool scaleOut;
#ifdef __MQL5__
bool perform_real_trades;
ulong reference_ticket_tp;
ulong reference_ticket_scale;
#endif
//Generic
string _symbol;
bool _debugVirtualTrades;
bool _drawArrows;
int _sy_digits;
double _exposure;
bool _profit_in_pips;
double slAtr, tpAtr, startMoveAtr;
bool drawSquares;
string currentSquareSL, currentSquareTP;
double lastTrailStop;
void CreateNewSquare(datetime timeCurrent, double priceOpen, double priceSL, double priceTP);
void UpdateCurrentSquare(datetime timeCurrent);
void DrawTrailingStop(datetime currentTime, double currentStop);
void CreateArrow(string name, datetime time, double price, color arrowColor, bool isOpenArrow=false);
void CreateTrendline(string name, datetime time1, double price1, datetime time2, double price2, color lineColor);
public:
VirtualTrade(string symbol, bool debug_virtual_trades, bool draw_arrows, bool profitPips=false);
void AdvancedSettings(double sl_atr=1.5, double tp_atr=1.0, double start_move_sl=2.0, bool squares=false);
void OpenTrade(double open_price, double atr, int order_type, datetime open_time, double trade_value, bool scale_out, double exposure = 2.0);
double CloseTrade(double closePrice, datetime closeTime, double &tradeElapsed);
//void OpenTradePercent(double open_price, datetime open_time, double trade_value);
//double CloseTradePercent(double closePrice, datetime closeTime);
void UpdateTrailingStop(double closePrice, datetime currentTime);
void UpdateTrailingStopToValue(double closePrice, double newTSValue, datetime currentTime);
double CheckStops(double priceHigh, double priceLow, datetime currentTime, double &tradeElapsed);
bool CheckIfOpen() { return isOpen; }
double GetExposure() { return _exposure; }
bool HasTouchedTP() { return isOpen && touchedFirstTP; }
double GetOpenPrice() { return openPrice; }
double GetStopLoss() { return stopLoss; }
#ifdef __MQL5__
void SetRealTrades() { perform_real_trades = true; }
#endif
};
// Open New Trade
void VirtualTrade::VirtualTrade(string symbol, bool debug_virtual_trades, bool draw_arrows, bool profitPips=false)
{
touchedFirstTP = false;
startedMovingSL = false;
_symbol = symbol;
_debugVirtualTrades = debug_virtual_trades;
_drawArrows = draw_arrows;
scaleOut = false;
_profit_in_pips = profitPips;
_exposure = 0.0;
_sy_digits = (int)SymbolInfoInteger(_symbol, SYMBOL_DIGITS);
slAtr = 1.5;
tpAtr = 1.0;
startMoveAtr = 2.0;
drawSquares = false;
#ifdef __MQL5__
perform_real_trades=false;
reference_ticket_scale = 0;
reference_ticket_tp = 0;
#endif
currentSquareSL = NULL;
currentSquareTP = NULL;
}
void VirtualTrade::AdvancedSettings(double sl_atr=1.5,double tp_atr=1.0,double start_move_sl=2.0, bool squares=false)
{
slAtr = sl_atr;
tpAtr = tp_atr;
startMoveAtr = start_move_sl;
drawSquares = squares;
}
void VirtualTrade::CreateArrow(string name,datetime time,double price,color arrowColor, bool isOpenArrow=false)
{
ObjectCreate(0, name, isOpenArrow?OBJ_ARROW_BUY:OBJ_ARROW_SELL, 0, time, price); //draw an up arrow
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, name, OBJPROP_COLOR, arrowColor);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}
void VirtualTrade::CreateTrendline(string name,datetime time1,double price1,datetime time2,double price2,color lineColor)
{
ObjectCreate(0, name, OBJ_TREND, 0, time1, price1, time2, price2);
ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor);
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}
void VirtualTrade::CreateNewSquare(datetime timeCurrent,double priceOpen,double priceSL,double priceTP)
{
currentSquareSL = "SquareSL" + IntegerToString(timeCurrent);
currentSquareTP = "SquareTP" + IntegerToString(timeCurrent);
ObjectCreate(0, currentSquareSL, OBJ_RECTANGLE, 0, timeCurrent, priceOpen, timeCurrent + PeriodSeconds(), priceSL);
ObjectSetInteger(0, currentSquareSL, OBJPROP_FILL, true);
ObjectSetInteger(0, currentSquareSL, OBJPROP_COLOR, clrDarkRed);
ObjectSetInteger(0, currentSquareSL, OBJPROP_BACK, true);
ObjectSetInteger(0, currentSquareSL, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, currentSquareSL, OBJPROP_HIDDEN, true);
ObjectCreate(0, currentSquareTP, OBJ_RECTANGLE, 0, timeCurrent, priceOpen, timeCurrent + PeriodSeconds(), priceTP);
ObjectSetInteger(0, currentSquareTP, OBJPROP_FILL, true);
ObjectSetInteger(0, currentSquareTP, OBJPROP_COLOR, clrGreen);
ObjectSetInteger(0, currentSquareTP, OBJPROP_BACK, true);
ObjectSetInteger(0, currentSquareTP, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, currentSquareTP, OBJPROP_HIDDEN, true);
}
void VirtualTrade::UpdateCurrentSquare(datetime timeCurrent)
{
ObjectSetInteger(0, currentSquareSL, OBJPROP_TIME, 1, timeCurrent);
ObjectSetInteger(0, currentSquareTP, OBJPROP_TIME, 1, timeCurrent);
}
void VirtualTrade::DrawTrailingStop(datetime currentTime,double currentStop)
{
#ifdef __MQL5__
if (perform_real_trades) return;
#endif
string name = "LineTrailing" + IntegerToString(currentTime);
ObjectCreate(0, name, OBJ_TREND, 0, currentTime - PeriodSeconds(), lastTrailStop, currentTime, currentStop);
ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, name, OBJPROP_COLOR, clrGold);
ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
lastTrailStop = currentStop;
}
void VirtualTrade::OpenTrade(double open_price, double atr, int order_type, datetime open_time, double trade_value, bool scale_out, double exposure = 2.0)
{
if (atr==0.0) return;
isOpen = true;
openPrice = open_price;
openAtr = atr;
tradeType = order_type;
openTime = open_time;
tradeValue = trade_value;
lastTrailStop = openPrice;
scaleOut = scale_out;
_exposure = exposure;
#ifdef __MQL5__
#define SELECT_OPEN_TRADE m_position_info.SelectByIndex(PositionsTotal()-1);
if (perform_real_trades)
{
double lots = CalculateRiskPercentLots(atr*slAtr, exposure/2.0, _symbol);
if (tradeType == ORDER_TYPE_BUY)
{
double ask = SymbolInfoDouble(_symbol, SYMBOL_ASK);
openPrice = ask;
stopLoss = ask - atr * slAtr;
takeProfit = ask + atr * tpAtr;
m_trade.Buy(lots, _symbol, 0.0, stopLoss, takeProfit);
SELECT_OPEN_TRADE
reference_ticket_tp = m_position_info.Ticket();
m_trade.Buy(lots, _symbol, 0.0, stopLoss, 0.0);
SELECT_OPEN_TRADE
reference_ticket_scale = m_position_info.Ticket();
}
else //ORDER_TYPE_SELL
{
double bid = SymbolInfoDouble(_symbol, SYMBOL_BID);
openPrice = bid;
stopLoss = bid + atr * slAtr;
takeProfit = bid - atr * tpAtr;
m_trade.Sell(lots, _symbol, 0.0, stopLoss, takeProfit);
SELECT_OPEN_TRADE
reference_ticket_tp = m_position_info.Ticket();
m_trade.Sell(lots, _symbol, 0.0, stopLoss, 0.0);
SELECT_OPEN_TRADE
reference_ticket_scale = m_position_info.Ticket();
}
return;
}
#endif
if (tradeType == ORDER_TYPE_BUY)
{
stopLoss = openPrice - atr * slAtr;
takeProfit = openPrice + atr * tpAtr;
}
else //ORDER_TYPE_SELL
{
stopLoss = openPrice + atr * slAtr;
takeProfit = openPrice - atr * tpAtr;
}
if (_debugVirtualTrades)
{
Print(_symbol + ": " +
"Open VIRTUAL " + GetSignalTypeString(tradeType) +
" at " + DoubleToString(openPrice, _sy_digits) +
" sl: " + DoubleToString(stopLoss, _sy_digits) +
" tp: " + DoubleToString(takeProfit, _sy_digits));
}
if (_drawArrows && _symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE))
{
if (drawSquares) CreateNewSquare(openTime, openPrice, stopLoss, takeProfit);
color arrowColor;
if (tradeType == ORDER_TYPE_BUY)
{
arrowColor = clrAqua;
}
else
{
arrowColor = clrOrange;
}
CreateArrow("ArrowOpen" + IntegerToString((long)openTime), openTime, openPrice, arrowColor, true);
}
}
// Close Trade (Manual or touched Stop/TP)
// Returns profit of trade
double VirtualTrade::CloseTrade(double closePrice, datetime closeTime, double &tradeElapsed)
{
double finalProfit = 0.0;
#ifdef __MQL5__
if (perform_real_trades)
{
if (m_position_info.SelectByTicket(reference_ticket_tp))
m_trade.PositionClose(reference_ticket_tp);
if (m_position_info.SelectByTicket(reference_ticket_scale))
m_trade.PositionClose(reference_ticket_scale);
}
else // To block changing stop loss
{
#endif
if (tradeType == ORDER_TYPE_BUY)
{
if (_profit_in_pips)
{
finalProfit += (closePrice - openPrice);
}
else
{
finalProfit += tradeValue * (closePrice - openPrice) / (openAtr * slAtr);
}
}
else //ORDER_TYPE_SELL
{
if (_profit_in_pips)
{
finalProfit += (openPrice - closePrice);
}
else
{
finalProfit += tradeValue * (openPrice - closePrice) / (openAtr * slAtr);
}
}
if (touchedFirstTP)
{
tradeElapsed = MathAbs((takeProfit + closePrice)/2.0 - openPrice) / (openAtr * slAtr);
if (_profit_in_pips)
{
finalProfit += MathAbs(openPrice - takeProfit);
}
else
{
finalProfit += tradeValue * MathAbs(openPrice - takeProfit) / (openAtr * slAtr);
}
}
else
{
tradeElapsed = MathAbs(closePrice - openPrice) / (openAtr * slAtr);
finalProfit = finalProfit * 2.0;
}
if (_profit_in_pips)
{
finalProfit /= (SymbolInfoDouble(_symbol, SYMBOL_POINT)*2.0);
}
if (_drawArrows && _symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE))
{
color arrowColor, lineColor;
if (tradeType == ORDER_TYPE_BUY)
{
arrowColor = clrBlue;
lineColor = clrDodgerBlue;
}
else
{
arrowColor = clrPurple;
lineColor = clrYellow;
}
if (drawSquares && !touchedFirstTP) UpdateCurrentSquare(closeTime);
CreateArrow("ArrowClose" + IntegerToString((long)openTime), closeTime, closePrice, arrowColor);
CreateTrendline("LineClose" + IntegerToString((long)openTime), openTime, openPrice, closeTime, closePrice, lineColor);
}
if (_debugVirtualTrades)
{
Print(_symbol + ": " +
"Close VIRTUAL " + GetSignalTypeString(tradeType) +
" at " + DoubleToString(openPrice, _sy_digits) +
" sl: " + DoubleToString(stopLoss, _sy_digits) +
" tp: " + DoubleToString(takeProfit, _sy_digits) +
" at price: " + DoubleToString(closePrice, _sy_digits) +
" profit: " + DoubleToString(finalProfit, _profit_in_pips?0:2));
}
#ifdef __MQL5__
} //end of real trade
#endif
// Reset
isOpen = false;
touchedFirstTP = false;
startedMovingSL = false;
openPrice = 0.0;
takeProfit = 0.0;
stopLoss = 0.0;
openAtr = 0.0;
_exposure = 0.0;
if (_profit_in_pips) return NormalizeDouble(finalProfit, 0);
else return NormalizeDouble(finalProfit, 2);
}
// Update Trailing Stops
void VirtualTrade::UpdateTrailingStop(double closePrice, datetime currentTime)
{
if (!isOpen) return;
if (touchedFirstTP)
{
#ifdef __MQL5__
if (perform_real_trades)
{
closePrice = tradeType==ORDER_TYPE_BUY?SymbolInfoDouble(_symbol, SYMBOL_BID):SymbolInfoDouble(_symbol, SYMBOL_ASK);
}
#endif
double startMove = openAtr * startMoveAtr;
double distanceToOpen = MathAbs(closePrice - openPrice);
if (distanceToOpen > startMove) startedMovingSL = true;
if (startedMovingSL)
{
double currentDistance = MathAbs(closePrice - stopLoss);
if (currentDistance > openAtr * slAtr)
{
if (tradeType == ORDER_TYPE_BUY)
{
stopLoss = closePrice - openAtr * slAtr;
}
else //ORDER_TYPE_SELL
{
stopLoss = closePrice + openAtr * slAtr;
}
#ifdef __MQL5__
if (perform_real_trades)
{
double point = SymbolInfoDouble(_symbol, SYMBOL_POINT);
double pos_stopLoss = m_position_info.StopLoss();
if ( (tradeType==ORDER_TYPE_BUY && pos_stopLoss + point < stopLoss) ||
(tradeType==ORDER_TYPE_SELL && pos_stopLoss - point > stopLoss))
m_trade.PositionModify(reference_ticket_scale, stopLoss, 0.0);
return;
}
#endif
if (_debugVirtualTrades)
{
Print(_symbol + ": " +
"Modify VIRTUAL " + GetSignalTypeString(tradeType) +
" at " + DoubleToString(openPrice, _sy_digits) +
" price " + DoubleToString(closePrice, _sy_digits) +
" sl: " + DoubleToString(stopLoss, _sy_digits));
}
}
}
if (_symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE) && _drawArrows && drawSquares) DrawTrailingStop(currentTime, stopLoss);
}
}
void VirtualTrade::UpdateTrailingStopToValue(double closePrice, double newTSValue, datetime currentTime)
{
if (!isOpen) return;
if (touchedFirstTP)
{
double startMove = openAtr * startMoveAtr;
double distanceToOpen = MathAbs(closePrice - openPrice);
if (distanceToOpen > startMove) startedMovingSL = true;
if (newTSValue!=EMPTY_VALUE && newTSValue>0.0 && startedMovingSL)
{
bool changedSL = false;
if (tradeType == ORDER_TYPE_BUY)
{
if (newTSValue > stopLoss && newTSValue < closePrice)
{
stopLoss = newTSValue;
changedSL = true;
}
}
else //ORDER_TYPE_SELL
{
if (newTSValue < stopLoss && newTSValue > closePrice)
{
stopLoss = newTSValue;
changedSL = true;
}
}
#ifdef __MQL5__
if (perform_real_trades && changedSL)
{
double point = SymbolInfoDouble(_symbol, SYMBOL_POINT);
double pos_stopLoss = m_position_info.StopLoss();
if ( (tradeType==ORDER_TYPE_BUY && pos_stopLoss + point < stopLoss) ||
(tradeType==ORDER_TYPE_SELL && pos_stopLoss - point > stopLoss))
m_trade.PositionModify(reference_ticket_scale, stopLoss, 0.0);
return;
}
#endif
if (changedSL && _debugVirtualTrades)
{
Print(_symbol + ": " +
"Modify VIRTUAL " + GetSignalTypeString(tradeType) +
" at " + DoubleToString(openPrice, _sy_digits) +
" price " + DoubleToString(closePrice, _sy_digits) +
" sl: " + DoubleToString(stopLoss, _sy_digits));
}
}
if (_symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE) && _drawArrows && drawSquares) DrawTrailingStop(currentTime, stopLoss);
}
}
// Check If price touched stop or TP
double VirtualTrade::CheckStops(double priceHigh, double priceLow, datetime currentTime, double &tradeElapsed)
{
if (!isOpen) return 0.0;
#ifdef __MQL5__
if (perform_real_trades)
{
if (!m_position_info.SelectByTicket(reference_ticket_tp))
{
if (!m_position_info.SelectByTicket(reference_ticket_scale))
{
//Stop loss
CloseTrade(stopLoss, currentTime, tradeElapsed);
}
else
{
touchedFirstTP = true;
_exposure = 0.0; // Breakeven
stopLoss = openPrice;
if (!touchedFirstTP)
m_trade.PositionModify(reference_ticket_scale, stopLoss, 0.0);
}
}
return 0.0;
}
#endif
if (tradeType == ORDER_TYPE_BUY)
{
// Check stop first
if (priceLow <= stopLoss)
{
if (touchedFirstTP && _drawArrows && drawSquares && _symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE)) DrawTrailingStop(currentTime, stopLoss);
return CloseTrade(stopLoss, currentTime, tradeElapsed);
}
else if (!touchedFirstTP)
{
if (priceHigh >= takeProfit) //breakeven
{
if (!scaleOut)
{
return CloseTrade(takeProfit, currentTime, tradeElapsed);
}
touchedFirstTP = true;
_exposure = 0.0; // Breakeven
if (_drawArrows && _symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE))
{
if (drawSquares) UpdateCurrentSquare(currentTime);
CreateArrow("ArrowTP" + IntegerToString((long)openTime), currentTime, takeProfit, clrBlue);
CreateTrendline("LineTP" + IntegerToString((long)openTime), openTime, openPrice, currentTime, takeProfit, clrDodgerBlue);
}
stopLoss = openPrice;
}
else if (_drawArrows && drawSquares && _symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE)) UpdateCurrentSquare(currentTime);
}
}
else //ORDER_TYPE_SELL
{
// Check stop first
if (priceHigh >= stopLoss)
{
if (touchedFirstTP && _drawArrows && drawSquares && _symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE)) DrawTrailingStop(currentTime, stopLoss);
return CloseTrade(stopLoss, currentTime, tradeElapsed);
}
else if (!touchedFirstTP)
{
if (priceLow <= takeProfit) //breakeven
{
if (!scaleOut)
{
return CloseTrade(takeProfit, currentTime, tradeElapsed);
}
touchedFirstTP = true;
_exposure = 0.0; // Breakeven
if (_drawArrows && _symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE))
{
if (drawSquares) UpdateCurrentSquare(currentTime);
CreateArrow("ArrowTP" + IntegerToString((long)openTime), currentTime, takeProfit, clrPurple);
CreateTrendline("LineTP" + IntegerToString((long)openTime), openTime, openPrice, currentTime, takeProfit, clrYellow);
}
stopLoss = openPrice;
}
else if (_drawArrows && drawSquares && _symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE)) UpdateCurrentSquare(currentTime);
}
}
return 0.0;
}
string GetSignalTypeString(int type)
{
if (type == ORDER_TYPE_BUY) return "BUY";
else if (type == ORDER_TYPE_SELL) return "SELL";
else return "NEUTRAL";
}
//void VirtualTrade::OpenTradePercent(double open_price, datetime open_time, double trade_value) //para ibex
//{
// isOpen = true;
//
// openPrice = open_price;
// tradeType = ORDER_TYPE_BUY;
// openTime = open_time;
// tradeValue = trade_value;
// takeProfit = openPrice*2;
// stopLoss = 0.0;
// openAtr = 0.0;
//
//
// if (_debugVirtualTrades)
// {
// Print(_symbol + ": " +
// "Open VIRTUAL " + GetSignalTypeString(tradeType) +
// " at " + DoubleToString(openPrice, _sy_digits));
// }
//
// if (_drawArrows && _symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE))
// {
// color arrowColor;
// if (tradeType == ORDER_TYPE_BUY)
// {
// arrowColor = clrAqua;
// }
// else
// {
// arrowColor = clrOrange;
// }
//
// CreateArrow("ArrowOpenP" + IntegerToString((long)openTime), openTime, openPrice, arrowColor, true);
// }
//}
//
//double VirtualTrade::CloseTradePercent(double closePrice,datetime closeTime)
//{
//double finalProfit = 0.0;
//
// if (tradeType == ORDER_TYPE_BUY)
// {
// finalProfit = (closePrice - openPrice) / openPrice * tradeValue;
// }
//
// if (_drawArrows && _symbol == Symbol() && MQLInfoInteger(MQL_VISUAL_MODE))
// {
// color arrowColor, lineColor;
// if (tradeType == ORDER_TYPE_BUY)
// {
// arrowColor = clrBlue;
// lineColor = clrDodgerBlue;
// }
// else
// {
// arrowColor = clrPurple;
// lineColor = clrYellow;
// }
//
// CreateArrow("ArrowCloseP" + IntegerToString((long)openTime), closeTime, closePrice, arrowColor);
// CreateTrendline("LineCloseP" + IntegerToString((long)openTime), openTime, openPrice, closeTime, closePrice, lineColor);
//
// }
//
// if (_debugVirtualTrades)
// {
// Print(_symbol + ": " +
// "Close VIRTUAL " + GetSignalTypeString(tradeType) +
// " at " + DoubleToString(openPrice, _sy_digits) +
// " at price: " + DoubleToString(closePrice, _sy_digits) +
// " profit: " + DoubleToString(finalProfit, 5));
// }
//
// // Reset
//
// isOpen = false;
// touchedFirstTP = false;
// startedMovingSL = false;
// openPrice = 0.0;
// takeProfit = 0.0;
// stopLoss = 0.0;
// openAtr = 0.0;
//
// return NormalizeDouble(finalProfit, 5);
//
//}