#property copyright "Copyright 2017-2021, Artur Zas"
// GNU General Public License v3.0 -> https://github.com/9nix6/Median-and-Turbo-Renko-indicator-bundle/blob/master/LICENSE
#property link      "https://www.az-invest.eu"
#define VERSION "1.10"
#property version VERSION
#property description "Example EA: Trading based on moving average && price crossover." 
#property description "MA1 needs to be enabled on the inicator creating the chart." 
#property description " "
#property description "GNU General Public License v3.0"

//#define RANGEBAR_LICENSE // uncomment when used on a Tick & Volume bar chart from https://www.az-invest.eu/rangebars-for-metatrader-5
//#define ULTIMATE_RENKO_LICENSE // uncomment when used on Ultimate Renko chart from https://www.az-invest.eu/ultimate-renko-indicator-generator-for-metatrader-5
//#define VOLUMECHART_LICENSE // uncomment when used on a Tick & Volume bar chart from https://www.az-invest.eu/Tick-chart-and-volume-chart-for-mt5
//#define SECONDSCHART_LICENSE // uncomment when used on a Seconds TF bar chart from https://www.az-invest.eu/seconds-timeframe-chart-for-metatrader-5
//#define LINEBREAKCHART_LICENSE // uncomment when used on a Line Break chart from https://www.az-invest.eu/linebreak-chart-for-metatrader-5

//
// Uncomment only ONE of the 5 directives listed below and recompile
// -----------------------------------------------------------------
//
#define EA_ON_RANGE_BARS          // Use EA on RangeBar chart 
//#define EA_ON_RENKO               // Use EA on Renko charts
//#define EA_ON_XTICK_CHART         // Use EA on XTick Chart (obsolete)
//#define EA_ON_TICK_VOLUME_CHART   // Use EA on Tick & Volume Bar Chart
//#define EA_ON_SECONDS_CHART       // Use EA on Seconds Interval chart
//#define EA_ON_LINEBREAK_CHART     // Use EA on LineBreak charts

//#define DEVELOPER_VERSION // used when I develop ;) should always be commented out

// Uncomment the directive below and recompile if EA is used with P-Renko BR Ultimate
// ----------------------------------------------------------------------------------
//
//#define P_RENKO_BR_PRO     // Use in P-Renko BR Ultimate version

//
// Uncomment the directive below and recompile for use in a backtest only
// ----------------------------------------------------------------------
//
// #define SHOW_INDICATOR_INPUTS

// Include all needed files

#ifdef EA_ON_RANGE_BARS
   #include <AZ-INVEST/SDK/RangeBars.mqh>
   RangeBars *customBars = NULL;
#endif
#ifdef EA_ON_RENKO
   #include <AZ-INVEST/SDK/MedianRenko.mqh>
   MedianRenko *customBars = NULL;
#endif
#ifdef EA_ON_XTICK_CHART
   #include <AZ-INVEST/SDK/TickChart.mqh>
   TickChart *customBars = NULL;
#endif
#ifdef EA_ON_TICK_VOLUME_CHART
   #include <AZ-INVEST/SDK/VolumeBarChart.mqh>
   TickChart *customBars = NULL;
#endif
#ifdef EA_ON_SECONDS_CHART
   #include <AZ-INVEST/SDK/SecondsChart.mqh>
   SecondsChart *customBars = NULL;
#endif
#ifdef EA_ON_LINEBREAK_CHART
   #include <AZ-INVEST/SDK/LineBreakChart.mqh>
   LineBreakChart *customBars = NULL;
#endif

#include <AZ-INVEST/SDK/TimeControl.mqh>
#include <AZ-INVEST/SDK/TradeFunctions.mqh>
#include <AZ-INVEST/SDK/TradeManager.mqh>

enum ENUM_TRADE_DIRECTION 
{
   TRADE_DIRECTION_BUY = POSITION_TYPE_BUY,    // Buy
   TRADE_DIRECTION_SELL = POSITION_TYPE_SELL,  // Sell
   TRADE_DIRECTION_ALL = 1000,                 // Buy & Sell
};

#ifdef SHOW_INDICATOR_INPUTS
   input group "EA parameters"
#endif
input double               Lots = 0.1;                                  // Traded lots
input uint                 StopLoss = 0;                                // Stop Loss
input uint                 TakeProfit = 0;                              // Take profit
input int                  ConfirmationBars = 1;                        // Signal confirmation bars
input int                  PrevSignalBars = 1;                          // Prev. signal confirmation bars
input ENUM_TRADE_DIRECTION ValidTradeDirection = TRADE_DIRECTION_ALL;   // Valid trading type
input bool                 CloseTradeOnSignalChange = true;             // Close trade on signal change
input bool                 ForceSR = false;                             // Force Stop & Reverse
input group    "### Trading schedule (Non stop if start = 0 & end = 0)"
input string               Start="9:00";                                // Start trading at 
input string               End="17:55";                                 // End trading at
input bool                 CloseTradeAfterTradingHours = true;          // Close trade after trading hours
input group    "### Trade management";
input int                  InpBEPoints          = 0;                    // BreakEven (Points) [ 0 = OFF ]
input int                  InpTrailByPoints     = 0;                    // Trail by (Points) [ 0 = OFF ]
input int                  InpTrailStartPoints  = 150;                  // Start trailing after (Points)
input int                  InpPartialCloseAtProfitPoints = 0;           // Partial close at (Points) [ 0 = OFF ]
input int                  InpPartialClosePercentage = 50;              // Partial close %
input group    "### Misc";
input ulong                MagicNumber=8888;                            // Assign trade ID 
input ulong                DeviationPoints = 0;                         // Maximum defiation (in points)
input double               ManualTickSize = 0.000;                      // Tick Size (0 = auto detect) 
input int                  NumberOfRetries = 50;                        // Maximum number of retries
input int                  BusyTimeout_ms = 1000;                       // Wait [ms] before retry on busy errors
input int                  RequoteTimeout_ms = 250;                     // Wait [ms] before retry on requotes

// Global data buffers 

MqlRates RateInfo[];  // Buffer for custom price bars
double MA1[];         // Buffer for moving average 1 

// Read 4 rates MA1 values starting from current (uncompleted) bar

int startAtBar   = 0;   
int numberOfBars;   
int _confirmationBars;
int _prevSignalBars;

// EA variables

CMarketOrder   *marketOrder = NULL;
CTimeControl   *timeControl = NULL;
CTradeManager  *tradeManager = NULL;

ulong currentTicket;
CTradeManagerState tradeManagerState;
ENUM_POSITION_TYPE currentPositionType;
ENUM_POSITION_TYPE signal;
ENUM_POSITION_TYPE validation;

#ifdef EA_ON_RANGE_BARS
   static int _MA1 = RANGEBAR_MA1;
   static int _MA2 = RANGEBAR_MA2;
#endif
#ifdef EA_ON_RENKO
   static int _MA1 = RENKO_MA1;
   static int _MA2 = RENKO_MA2;
#endif
#ifdef EA_ON_XTICK_CHART
   static int _MA1 = TICKCHART_MA1;
   static int _MA2 = TICKCHART_MA2;
#endif
#ifdef EA_ON_TICK_VOLUME_CHART
   static int _MA1 = VOLUMECHART_MA1;
   static int _MA2 = VOLUMECHART_MA2;
#endif
#ifdef EA_ON_SECONDS_CHART
   static int _MA1 = SECONDS_MA1;
   static int _MA2 = SECONDS_MA2;
#endif
#ifdef EA_ON_LINEBREAK_CHART
   static int _MA1 = LINEBREAK_MA1;
   static int _MA2 = LINEBREAK_MA2;
#endif

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   if(customBars == NULL)
   {
      #ifdef EA_ON_RANGE_BARS
         customBars = new RangeBars(MQLInfoInteger((int)MQL5_TESTING) ? false : true);
      #endif
      #ifdef EA_ON_RENKO
         customBars = new MedianRenko(MQLInfoInteger((int)MQL5_TESTING) ? false : true);
      #endif
      #ifdef EA_ON_XTICK_CHART   
         customBars = new TickChart(MQLInfoInteger((int)MQL5_TESTING) ? false : true);
      #endif   
      #ifdef EA_ON_TICK_VOLUME_CHART
         customBars = new TickChart(MQLInfoInteger((int)MQL5_TESTING) ? false : true);
      #endif         
      #ifdef EA_ON_SECONDS_CHART
         customBars = new SecondsChart(MQLInfoInteger((int)MQL5_TESTING) ? false : true);
      #endif    
      #ifdef EA_ON_LINEBREAK_CHART
         customBars = new LineBreakChart(MQLInfoInteger((int)MQL5_TESTING) ? false : true);
      #endif        
   }

   customBars.Init();
   if(customBars.GetHandle() == INVALID_HANDLE)
      return(INIT_FAILED);
   
   signal = POSITION_TYPE_NONE;
   _confirmationBars = (ConfirmationBars < 1) ? 1 : ConfirmationBars;
   _prevSignalBars = (PrevSignalBars < 1) ? 1 : PrevSignalBars;
   numberOfBars = _confirmationBars + _prevSignalBars + 1; 
   
   CMarketOrderParameters params;
   {
      params.m_async_mode = false;
      params.m_magic = MagicNumber;
      params.m_deviation = DeviationPoints;
      params.m_type_filling = ORDER_FILLING_FOK;
      
      params.numberOfRetries = NumberOfRetries;
      params.busyTimeout_ms = BusyTimeout_ms; 
      params.requoteTimeout_ms = RequoteTimeout_ms;         
   }
   
   marketOrder = new CMarketOrder(params);
   
   if(timeControl == NULL)
   {
      timeControl = new CTimeControl();
   }
   
   timeControl.SetValidTraingHours(Start,End);
   
   //
   // Init TradeManager
   //        

   CTradeManagerParameters params2;
   {
      params2.BEPoints           = InpBEPoints;
      params2.TrailByPoints      = InpTrailByPoints;
      params2.TrailStartPoints   = InpTrailStartPoints;
      params2.PartialCloseAtProfitPoints = InpPartialCloseAtProfitPoints;
      params2.PartialClosePercentage = InpPartialClosePercentage;      
   }
        
   if(tradeManager == NULL)
   {
      tradeManager = new CTradeManager(params2, marketOrder);
   }
   
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   customBars.Deinit();

   //  delete TimeControl class
   
   if(timeControl != NULL)
   {
      delete timeControl;
      timeControl = NULL;
   }
      
   //  delete MarketOrder class
   
   if(marketOrder != NULL)
   {
      delete marketOrder;
      marketOrder = NULL;
   }
  
   // delete MedianRenko class
   
   if(customBars != NULL)
   {
      delete customBars;
      customBars = NULL;
   }   
      
   if(tradeManager != NULL)
   {
      delete tradeManager;      
      tradeManager = NULL;
   }
      
   Comment("");
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   if(marketOrder == NULL || customBars == NULL || timeControl == NULL || tradeManager == NULL)
      return;
   
   // trade management
     
   if(marketOrder.IsOpen(currentTicket, _Symbol, MagicNumber))
   {
      // checks done on every tick

      if(!timeControl.IsTradingTimeValid())
      {
         if(marketOrder.IsOpen(currentTicket,_Symbol,MagicNumber))
         {
            if(currentTicket > 0 && CloseTradeAfterTradingHours)
            {
               // close position outside of trading hours
               marketOrder.Close(currentTicket);
            }
         }

         return;
      }
                 
      tradeManager.Manage(currentTicket, tradeManagerState);               
   }      
     
   // Signal handler
      
   if(customBars.IsNewBar())
   {
      if(timeControl.IsScheduleEnabled())
      {
         Comment("EA trading schedule ON ("+Start+" to "+End+") | trading enabled = "+(string)timeControl.IsTradingTimeValid());
      }
      else
      {
         Comment("EA trading schedule OFF");
      }
                      
      //
      //  Get MqlRateInfo & moving average values for current, last completed bar and the bar before that...
      //
      
      if(!customBars.GetMqlRates(RateInfo,startAtBar,numberOfBars))
      {
         Print("Error getting MqlRates for custom chart");
      }
      else if(!customBars.GetMA(_MA1, MA1, startAtBar, numberOfBars))
      {
         Print("Error getting values from MA1 - please enable MA1 on chart");
      }
      else
      {
        
         signal = PriceAndMovingAverageCross(_confirmationBars, _prevSignalBars);
  
         if(timeControl.IsScheduleEnabled())
         {
            Comment("EA trading schedule ("+Start+" to "+End+") | trading enabled = "+(string)timeControl.IsTradingTimeValid()+
            //"\n MA_1 [2]: "+DoubleToString(MA1[2],_Digits)+" [1]: "+DoubleToString(MA1[1],_Digits)+
            //"\n Close[2]: "+DoubleToString(RateInfo[2].close,_Digits)+" [1]: "+DoubleToString(RateInfo[1].close,_Digits)+
            "\n Price & MA cross signal = "+marketOrder.PositionTypeToString(signal)+
            "\n Trade manager: "+tradeManager.ToString()+
            "\n");
         }
         else
         {
            Comment("EA trading schedule not used. Trading is enabled."+
            //"\n MA_1 [2]: "+DoubleToString(MA1[2],_Digits)+" [1]: "+DoubleToString(MA1[1],_Digits)+
            //"\n Close[2]: "+DoubleToString(RateInfo[2].close,_Digits)+" [1]: "+DoubleToString(RateInfo[1].close,_Digits)+
            "\n Price & MA cross signal = "+marketOrder.PositionTypeToString(signal)+
            "\n Trade manager: "+tradeManager.ToString()+
            "\n");
         }

         if(signal == POSITION_TYPE_BUY)
         {
            if(marketOrder.IsOpen(currentTicket,_Symbol,POSITION_TYPE_SELL,MagicNumber))
            {
               if(currentTicket > 0 && ForceSR)
               {
                  if(IsTradeDirectionValid(POSITION_TYPE_SELL))
                  {
                     PrintFormat("Reversing %s position on Stop&Reverse condition (ticket:%d)", _Symbol, currentTicket);
                     marketOrder.Reverse(currentTicket,Lots,StopLoss,TakeProfit);
                  }
               }  
               else if(currentTicket > 0)
               {
                  // close trade on signal change
                  if(CloseTradeOnSignalChange)
                  {
                     PrintFormat("Closing %s position on signal change (ticket:%d)", _Symbol, currentTicket);
                     marketOrder.Close(currentTicket);                  
                  }
               }
               return;
            }
            
            if(!marketOrder.IsOpen(_Symbol,POSITION_TYPE_BUY,MagicNumber))
            {
               if(IsTradeDirectionValid(POSITION_TYPE_BUY))
                  marketOrder.Long(_Symbol,Lots,StopLoss,TakeProfit);
                  
               return;
            }
         }
         else if(signal == POSITION_TYPE_SELL)
         {
            if(marketOrder.IsOpen(currentTicket,_Symbol,POSITION_TYPE_BUY,MagicNumber))
            {
               if(currentTicket > 0 && ForceSR)
               {
                  if(IsTradeDirectionValid(POSITION_TYPE_BUY))
                  {
                     PrintFormat("Reversing %s position on Stop&Reverse condition (ticket:%d)", _Symbol, currentTicket);
                     marketOrder.Reverse(currentTicket,Lots,StopLoss,TakeProfit);
                  }
               }   
               else if(currentTicket > 0)
               {
                  // close trade on signal change
                  if(CloseTradeOnSignalChange)
                  {
                     PrintFormat("Closing %s position on signal change (ticket:%d)", _Symbol, currentTicket);
                     marketOrder.Close(currentTicket);                                    
                  }
               }
               return;
            }

            if(!marketOrder.IsOpen(_Symbol,POSITION_TYPE_SELL,MagicNumber))
            {
               if(IsTradeDirectionValid(POSITION_TYPE_SELL))
                  marketOrder.Short(_Symbol,Lots,StopLoss,TakeProfit);
                  
               return;
            }
         }         
      }      
   } 
}

//
// Trade direction validation (Is it OK to trade in the given direction?)
//

bool IsTradeDirectionValid(ENUM_POSITION_TYPE signalDirection)
{
   if(ValidTradeDirection == TRADE_DIRECTION_ALL)
      return true;
      
   if(signalDirection == POSITION_TYPE_BUY && ValidTradeDirection == TRADE_DIRECTION_BUY)
      return true;
   else if(signalDirection == POSITION_TYPE_SELL && ValidTradeDirection == TRADE_DIRECTION_SELL)
      return true;
   else      
      return false;
}

//
// Price & MA cross logic
//

ENUM_POSITION_TYPE PriceAndMovingAverageCross(int confirmationBars, int prevSignalBars)
{
   if(numberOfBars < confirmationBars+1)
   {
      Alert("Invalid number of MqlRates and MA readings defined! Crossover cannot be determined.");
      return POSITION_TYPE_NONE;
   }
   
   bool confirmedSell = true;
   bool confirmedBuy  = true;

   // check trailing bar for confirmation of previous signal   
   for(int i=(confirmationBars+1); i<=(confirmationBars+prevSignalBars); i++)
   {
      if(RateInfo[i].close > MA1[i])
      {
        confirmedBuy = false;
      }
      else if(RateInfo[i].close < MA1[i])
      {
         confirmedSell = false;
      }
   }
      
   // check confirmation bars for current signal
   for(int i=1; i<=confirmationBars; i++)
   {
      if(RateInfo[i].close == MA1[i])
      {
         confirmedSell = false;
         confirmedBuy  = false;
      }
      else if(RateInfo[i].close < MA1[i])
      {
         confirmedBuy  = false;
      }
      else if(RateInfo[i].close > MA1[i])
      {
         confirmedSell = false;
      }
   }
   
   // signal aggregate
   if(confirmedSell)
      return POSITION_TYPE_SELL;
   else if(confirmedBuy)
      return POSITION_TYPE_BUY;
   else
      return POSITION_TYPE_NONE;
}

