//+------------------------------------------------------------------+
//| PZ_ReversalTrendFollowingEA.mq4
//| --
//| You can either be long or short at any given time. You buy if the market 
//| makes a new 100-day high and sell if the market makes a new 100-day low. 
//| You are always in the market and break-even all trades as soon as possible.
//+------------------------------------------------------------------+
#property copyright "Copyright © http://www.pointzero-trading.com"
#property link      "http://www.pointzero-trading.com"

//---- Dependencies
#import "stdlib.ex4"
   string ErrorDescription(int e);
#import

//---- Constants
#define  ShortName         "PZ Reversal Trend Following EA"
#define  Shift             1

//---- External variables
extern string TR_Ex                    = "------- Trade period";
extern int    TradingPeriod            = 100;
extern string MM_Ex                    = "------- Money management";
extern bool   MoneyManagement          = true;
extern double RiskPercent              = 2;
extern double LotSize                  = 0.1;
extern string EA_Ex                    = "------- EA Settings";
extern int    Slippage                 = 6;
extern int    MagicNumber              = 2000;

//---- Internal
double   DecimalPip;
double   PBuy = EMPTY_VALUE;
double   PSell = EMPTY_VALUE;

//+------------------------------------------------------------------+
//| Custom EA initialization function
//+------------------------------------------------------------------+
int init()
{
   DecimalPip = GetDecimalPip();
   Comment("Copyright © http://www.pointzero-trading.com");
   return(0);
}

//+------------------------------------------------------------------+
//| Custom EA deinit function
//+------------------------------------------------------------------+
int deinit()
{
   return(0);
}

//+------------------------------------------------------------------+
//| Custom EA start function
//+------------------------------------------------------------------+
int start()
{   
   //--
   //-- Trade if trigger price is breached
   //--
   
   // Buy
   if(PBuy != EMPTY_VALUE && Bid > PBuy)
   { 
      PlaceOrder(OP_BUY, GetLotSize()); 
      PBuy = EMPTY_VALUE;
   }
   
   // Sell
   if(PSell != EMPTY_VALUE && Ask < PSell) 
   { 
      PlaceOrder(OP_SELL, GetLotSize()); 
      PSell = EMPTY_VALUE; 
   }
   
   //--
   //-- Check entry
   //--
   
   // Do not continue unless the bar is closed
   if(!IsBarClosed(0, true)) return(0);
   
   // Trading thresholds
   double rhigh  = iHigh(Symbol(), Period(), iHighest(Symbol(), Period(), MODE_HIGH, TradingPeriod, Shift+1));
   double rlow   = iLow(Symbol(), Period(), iLowest(Symbol(), Period(), MODE_LOW, TradingPeriod, Shift+1));
                        
   // Trades opened
   int l_TotalTrades_buy = GetTotalTrades(OP_BUY, MagicNumber);
   int l_TotalTrades_sell = GetTotalTrades(OP_SELL, MagicNumber);
  
   // Bars
   double CLOSE = iClose(Symbol(),0, Shift);
   double HIGH = iHigh(Symbol(),0, Shift);
   double LOW = iLow(Symbol(),0, Shift);
   
   // Check if buy conditions apply
   if(CLOSE > rhigh && l_TotalTrades_buy == 0)
   {
      // Buy
      PBuy = HIGH;
      CloseOrder(OP_SELL);
   }
   
   // Check if sell conditions apply
   if(CLOSE < rlow && l_TotalTrades_sell == 0)
   {
      // Sell
      PSell = LOW;
      CloseOrder(OP_BUY);
   }
   
   return (0);
}

//+------------------------------------------------------------------+
//| My functions
//+------------------------------------------------------------------+

/**
* Returns total opened trades
* @param    int   Type
* @param    int   Magic
* @return   int
*/
int GetTotalTrades(int Type, int Magic)
{
   int counter = 0;
   for (int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
        Print(ShortName +" (OrderSelect Error) "+ ErrorDescription(GetLastError()));
      } else if(OrderSymbol() == Symbol() && (OrderType() == Type || Type == EMPTY_VALUE) && OrderMagicNumber() == Magic) {
            counter++;
      }
   }
   return(counter);
}

/**
* Places an order
* @param    int      Type
* @param    double   Lotz
* @param    double   PendingPrice
*/
void PlaceOrder(int Type, double Lotz, double PendingPrice = 0)
{
   int err;
   color  l_color;
   double l_price, l_sprice = 0;
   RefreshRates();
   
   // Price and color for the trade type
   if(Type == OP_BUY){ l_price = Ask;  l_color = Blue; }
   if(Type == OP_SELL){ l_price = Bid; l_color = Red; } 
   
   // Avoid collusions
   while (IsTradeContextBusy()) Sleep(1000);
   int l_datetime = TimeCurrent();
   
   // Send order
   int l_ticket = OrderSend(Symbol(), Type, Lotz, l_price, Slippage, 0, 0, "", MagicNumber, 0, l_color);
   
   // Rety if failure
   if (l_ticket == -1)
   {
      while(l_ticket == -1 && TimeCurrent() - l_datetime < 60 && !IsTesting())
      {
         err = GetLastError();
         if (err == 148) return;
         Sleep(1000);
         while (IsTradeContextBusy()) Sleep(1000);
         RefreshRates();
         l_ticket = OrderSend(Symbol(), Type, Lotz, l_price, Slippage, 0, 0, "", MagicNumber, 0, l_color);
      }
      if (l_ticket == -1)
         Print(ShortName +" (OrderSend Error) "+ ErrorDescription(GetLastError()));
   }
}

/**
* Closes desired orders 
* @param    int   Type
*/
void CloseOrder(int Type)
{
   int l_type;
	for(int i = OrdersTotal()-1; i >= 0; i--)
	{
		OrderSelect(i, SELECT_BY_POS, MODE_TRADES); l_type = OrderType();
		if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && Type == l_type)
		{ 
	      if(Type == OP_BUY || Type == OP_SELL)  
	      {
            if(!OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), Slippage, Gold))
               Print(ShortName +" (OrderClose Error) "+ ErrorDescription(GetLastError()));
         } else {
            if(!OrderDelete(OrderTicket()))
               Print(ShortName +" (OrderDelete Error) "+ ErrorDescription(GetLastError()));
         }
      }
   }
}

/**
* Calculates lot size according to risk and the weight of this trade
* @return   double
*/
double GetLotSize()
{
   // Lots
   double l_lotz = LotSize;
   
   // Lotsize and restrictions 
   double l_minlot = MarketInfo(Symbol(), MODE_MINLOT);
   double l_maxlot = MarketInfo(Symbol(), MODE_MAXLOT);
   double l_lotstep = MarketInfo(Symbol(), MODE_LOTSTEP);
   int vp = 0; if(l_lotstep == 0.01) vp = 2; else vp = 1;
   
   // Apply money management
   if(MoneyManagement == true)
      l_lotz = MathFloor(AccountBalance() * RiskPercent / 100.0) / 1000.0;
  
   // Normalize to lotstep
   l_lotz = NormalizeDouble(l_lotz, vp);
   
   // Check max/minlot here
   if (l_lotz < l_minlot) l_lotz = l_minlot;
   if(l_lotz > l_maxlot) l_lotz = l_maxlot; 
   
   // Bye!
   return (l_lotz);
}

/**
* Returns decimal pip value
* @return   double
*/
double GetDecimalPip()
{
   switch(Digits)
   {
      case 5: return(0.0001);
      case 4: return(0.0001);
      case 3: return(0.001);
      default: return(0.01);
   }
}

/**
* Checks if the bar has closed
*/
bool IsBarClosed(int timeframe,bool reset)
{
    static datetime lastbartime;
    if(timeframe==-1)
    {
        if(reset)
            lastbartime=0;
        else
            lastbartime=iTime(NULL,timeframe,0);
        return(true);
    }
    if(iTime(NULL,timeframe,0)==lastbartime) // wait for new bar
        return(false);
    if(reset)
        lastbartime=iTime(NULL,timeframe,0);
    return(true);
}