//+-------------------------------------------------------------------------+
//|                              EA Studio Portfolio                        |
//|                              Created with: Expert Advisor Studio        |
//|                              Copyright 2017, Forex Software Ltd.        |
//|                              https://forexsb.com                        |
//|                                                                         |
//|                                                                         |
//| This Portfolio Expert works in MetaTrader 4.                            |
//| It opens separate positions for each strategy.                          |
//| Every position has an unique magic number,                              |
//| which corresponds to the index of the strategy.                         |
//|                                                                         |
//| EA Studio User Guide: https://forexsb.com/wiki/eas-guide/start          |
//| Support forum:        https://forexsb.com/forum/                        |
//|                                                                         |
//+-------------------------------------------------------------------------+

#property copyright "Forex Software Ltd."
#property version   "2.1"
#property strict

static input double Entry_Amount      = 0.01; // Entry lots
static input int    Base_Magic_Number = 100;  // Base Magic Number

#define TRADE_RETRY_COUNT 4
#define TRADE_RETRY_WAIT  100
#define OP_FLAT           -1

// Session time is set in seconds from 00:00
const int sessionSundayOpen           = 0;     // 00:00
const int sessionSundayClose          = 86400; // 24:00
const int sessionMondayThursdayOpen   = 0;     // 00:00
const int sessionMondayThursdayClose  = 86400; // 24:00
const int sessionFridayOpen           = 0;     // 00:00
const int sessionFridayClose          = 86400; // 24:00
const bool sessionIgnoreSunday        = true;
const bool sessionCloseAtSessionClose = false;
const bool sessionCloseAtFridayClose  = true;

const int    strategiesCount = 1;
const double sigma           = 0.000001;

datetime barTime;
int      digits;
double   stopLevel;
double   pip;
bool     setProtectionSeparately = false;

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum OrderScope
  {
   ORDER_SCOPE_UNDEFINED,
   ORDER_SCOPE_ENTRY,
   ORDER_SCOPE_EXIT
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum OrderDirection
  {
   ORDER_DIRECTION_NONE,
   ORDER_DIRECTION_BUY,
   ORDER_DIRECTION_SELL,
   ORDER_DIRECTION_BOTH
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
struct Position
  {
   int               Type;
   int               Ticket;
   int               MagicNumber;
   double            Lots;
   double            Price;
   double            StopLoss;
   double            TakeProfit;
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
struct Signal
  {
   int               MagicNumber;
   OrderScope        Scope;
   OrderDirection    Direction;
   int               StopLossPips;
   int               TakeProfitPips;
   bool              IsTrailingStop;
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   barTime   = Time[0];
   digits    = (int) MarketInfo(_Symbol, MODE_DIGITS);
   stopLevel = MarketInfo(_Symbol, MODE_STOPLEVEL);
   pip       = GetPipValue();

   return (INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(IsForceSessionClose())
     {
      CloseAllPositions();
      return;
     }

   if(Time[0]>barTime)
     {
      barTime=Time[0];
      OnBar();
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnBar()
  {
   if(IsOutOfSession()) return;

   Signal signalList[];
   SetSignals(signalList);
   int signalsCount=ArraySize(signalList);

   for(int i=0;i<signalsCount;i++)
     {
      Signal signal=signalList[i];
      if(signal.Direction!=ORDER_DIRECTION_NONE)
        {
         ManageSignal(signal);
        }
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ManageSignal(Signal &signal)
  {
   Position position=CreatePosition(signal.MagicNumber);
   if(position.Type==OP_FLAT && signal.Scope==ORDER_SCOPE_ENTRY)
     {
      if(signal.Direction==ORDER_DIRECTION_BUY || signal.Direction==ORDER_DIRECTION_SELL)
        {
         OpenPosition(signal);
        }
     }
   else if(position.Type!=OP_FLAT && signal.Scope==ORDER_SCOPE_EXIT)
     {
      if(signal.Direction==ORDER_DIRECTION_BOTH ||
         (position.Type==OP_BUY && signal.Direction==ORDER_DIRECTION_SELL) ||
         (position.Type==OP_SELL && signal.Direction==ORDER_DIRECTION_BUY))
        {
         ClosePosition(position);
        }
      else
        {
         if(signal.IsTrailingStop)
           {
            double trailingStop=GetTrailingStop(position,signal.StopLossPips);
            ManageTrailingStop(position,trailingStop);
           }
        }
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
Position CreatePosition(const int magicNumber)
  {
   Position position;
   position.MagicNumber = magicNumber;
   position.Type        = OP_FLAT;
   position.Ticket      = 0;
   position.Lots        = 0;
   position.Price       = 0;
   position.StopLoss    = 0;
   position.TakeProfit  = 0;

   int total=OrdersTotal();
   for(int pos=total-1; pos>=0; pos--)
     {
      if(OrderSelect(pos,SELECT_BY_POS,MODE_TRADES) &&
         OrderSymbol()      == _Symbol &&
         OrderMagicNumber() == magicNumber &&
         OrderCloseTime()   == 0)
        {
         position.Type       = OrderType();
         position.Lots       = OrderLots();
         position.Ticket     = OrderTicket();
         position.Price      = OrderOpenPrice();
         position.StopLoss   = OrderStopLoss();
         position.TakeProfit = OrderTakeProfit();
         break;
        }
     }

   return (position);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
Signal CreateEntrySignal(const int strategyIndex,const bool canOpenLong,const bool canOpenShort,const int stopLossPips,const int takeProfitPips, const bool isTrailingStop)
  {
   Signal signal;
   signal.MagicNumber    = GetMagicNumber(strategyIndex);
   signal.Scope          = ORDER_SCOPE_ENTRY;
   signal.Direction      = canOpenLong&&canOpenShort ? ORDER_DIRECTION_BOTH : canOpenLong ? ORDER_DIRECTION_BUY : canOpenShort ? ORDER_DIRECTION_SELL : ORDER_DIRECTION_NONE;
   signal.StopLossPips   = stopLossPips;
   signal.TakeProfitPips = takeProfitPips;
   signal.IsTrailingStop = isTrailingStop;

   return (signal);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
Signal CreateExitSignal(const int strategyIndex,const bool canCloseLong,const bool canCloseShorts,const int stopLossPips,const int takeProfitPips, const bool isTrailingStop)
  {
   Signal signal;
   signal.MagicNumber    = GetMagicNumber(strategyIndex);
   signal.Scope          = ORDER_SCOPE_EXIT;
   signal.Direction      = canCloseLong&&canCloseShorts ? ORDER_DIRECTION_BOTH : canCloseLong ? ORDER_DIRECTION_SELL : canCloseShorts ? ORDER_DIRECTION_BUY : ORDER_DIRECTION_NONE;
   signal.StopLossPips   = stopLossPips;
   signal.TakeProfitPips = takeProfitPips;
   signal.IsTrailingStop = isTrailingStop;

   return (signal);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OpenPosition(Signal &signal)
  {
   for(int attempt=0; attempt<TRADE_RETRY_COUNT; attempt++)
     {
      int    ticket     = 0;
      int    lastError  = 0;
      bool   modified   = false;
      int    command    = OrderDirectionToCommand(signal.Direction);
      double amount     = Entry_Amount;
      double stopLoss   = signal.StopLossPips>0 ? GetStopLoss(command, signal.StopLossPips) : 0;
      double takeProfit = signal.TakeProfitPips>0 ? GetTakeProfit(command, signal.TakeProfitPips) : 0;
      string comment    = IntegerToString(signal.MagicNumber);
      color  arrowColor = command==OP_BUY ? clrGreen : clrRed;

      if(IsTradeContextFree())
        {
         double price=MarketInfo(_Symbol,command==OP_BUY ? MODE_ASK : MODE_BID);
         if(setProtectionSeparately)
           {
            ticket=OrderSend(_Symbol,command,amount,price,10,0,0,comment,signal.MagicNumber,0,arrowColor);
            if(ticket>0 && (signal.StopLossPips>0 || signal.TakeProfitPips>0))
               modified=OrderModify(ticket,0,stopLoss,takeProfit,0,clrBlue);
           }
         else
           {
            ticket=OrderSend(_Symbol,command,amount,price,10,stopLoss,takeProfit,comment,signal.MagicNumber,0,arrowColor);
            lastError=GetLastError();
            if(ticket<=0 && lastError==130)
              {
               ticket=OrderSend(_Symbol,command,amount,price,10,0,0,comment,signal.MagicNumber,0,arrowColor);
               if(ticket>0 && (signal.StopLossPips>0 || signal.TakeProfitPips>0))
                  modified=OrderModify(ticket,0,stopLoss,takeProfit,0,clrBlue);
               if(ticket>0 && modified)
                 {
                  setProtectionSeparately=true;
                  Print("Detected ECN type position protection.");
                 }
              }
           }
        }

      if(ticket>0) break;

      lastError=GetLastError();

      if(lastError!=135 && lastError!=136 && lastError!=137 && lastError!=138) break;

      Sleep(TRADE_RETRY_WAIT);
      Print("Open Position retry no: "+IntegerToString(attempt+2));
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ClosePosition(Position &position)
  {
   for(int attempt=0; attempt<TRADE_RETRY_COUNT; attempt++)
     {
      bool closed;
      int lastError=0;

      if(IsTradeContextFree())
        {
         double price=MarketInfo(_Symbol,position.Type==OP_BUY ? MODE_BID : MODE_ASK);
         closed=OrderClose(position.Ticket,position.Lots,price,10,clrYellow);
         lastError=GetLastError();
        }

      if(closed) break;
      if(lastError==4108) break;

      Sleep(TRADE_RETRY_WAIT);
      Print("Close Position retry no: "+IntegerToString(attempt+2));
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ModifyPosition(Position &position)
  {
   for(int attempt=0; attempt<TRADE_RETRY_COUNT; attempt++)
     {
      bool modified;
      int lastError=0;

      if(IsTradeContextFree())
        {
         modified=OrderModify(position.Ticket,0,position.StopLoss,position.TakeProfit,0,clrBlue);
         lastError=GetLastError();
        }

      if(modified)
         break;

      if(lastError==4108)
         break;

      Sleep(TRADE_RETRY_WAIT);
      Print("Modify Position retry no: "+IntegerToString(attempt+2));
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CloseAllPositions()
  {
   for(int i=0;i<strategiesCount;i++)
     {
      int magicNumber=GetMagicNumber(i);
      Position position=CreatePosition(magicNumber);

      if(position.Type==OP_BUY || position.Type==OP_SELL)
         ClosePosition(position);
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double GetStopLoss(int command,int stopLossPips)
  {
   if(stopLossPips==0) return (0);

   double deltaPips      = MathMax(pip*stopLossPips, _Point*stopLevel);
   double referencePrice = MarketInfo(_Symbol, command==OP_BUY ? MODE_BID : MODE_ASK);
   double stopLossPoints = command==OP_BUY ? referencePrice-deltaPips : referencePrice+deltaPips;
   double finalStopLoss  = NormalizeDouble(stopLossPoints, digits);

   return (finalStopLoss);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double GetTakeProfit(int command,int takeProfitPips)
  {
   if(takeProfitPips==0) return (0);

   double deltaPips        = MathMax(pip*takeProfitPips, _Point*stopLevel);
   double referencePrice   = MarketInfo(_Symbol, command==OP_BUY ? MODE_BID : MODE_ASK);
   double takeProfitPoints = command==OP_BUY ? referencePrice+deltaPips : referencePrice-deltaPips;
   double finalTakeProfit  = NormalizeDouble(takeProfitPoints, digits);

   return (finalTakeProfit);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double GetTrailingStop(Position &position, int stopLoss)
  {
   double bid=MarketInfo(_Symbol,MODE_BID);
   double ask=MarketInfo(_Symbol,MODE_ASK);
   double stopLevelPoints=_Point*stopLevel;
   double stopLossPoints=pip*stopLoss;

   if(position.Type==OP_BUY)
     {
      double stopLossPrice=High[1]-stopLossPoints;
      if(position.StopLoss<stopLossPrice-pip)
        {
         if(stopLossPrice<bid)
           {
            if(stopLossPrice>=bid-stopLevelPoints)
               return (bid - stopLevelPoints);
            else
               return (stopLossPrice);
           }
         else
           {
            return (bid);
           }
        }
     }

   else if(position.Type==OP_SELL)
     {
      double stopLossPrice=Low[1]+stopLossPoints;
      if(position.StopLoss>stopLossPrice+pip)
        {
         if(stopLossPrice>ask)
           {
            if(stopLossPrice<=ask+stopLevelPoints)
               return (ask + stopLevelPoints);
            else
               return (stopLossPrice);
           }
         else
           {
            return (ask);
           }
        }
     }

   return (position.StopLoss);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ManageTrailingStop(Position &position, double trailingStop)
  {
   double bid=MarketInfo(_Symbol,MODE_BID);
   double ask=MarketInfo(_Symbol,MODE_ASK);

   if(position.Type==OP_BUY && MathAbs(trailingStop-bid)<_Point)
     {
      ClosePosition(position);
     }

   else if(position.Type==OP_SELL && MathAbs(trailingStop-ask)<_Point)
     {
      ClosePosition(position);
     }

   else if(MathAbs(trailingStop-position.StopLoss)>_Point)
     {
      position.StopLoss=NormalizeDouble(trailingStop,digits);
      ModifyPosition(position);
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool IsTradeContextFree()
  {
   if(IsTradeAllowed()) return (true);

   uint startWait=GetTickCount();
   Print("Trade context is busy! Waiting...");

   while(true)
     {
      if(IsStopped()) return (false);

      uint diff=GetTickCount()-startWait;
      if(diff>30*1000)
        {
         Print("The waiting limit exceeded!");
         return (false);
        }

      if(IsTradeAllowed())
        {
         RefreshRates();
         return (true);
        }
      Sleep(TRADE_RETRY_WAIT);
     }

   return (true);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool IsOutOfSession()
  {
   MqlDateTime time0; TimeToStruct(Time[0],time0);
   int weekDay           = time0.day_of_week;
   long timeFromMidnight = Time[0]%86400;
   int periodLength      = PeriodSeconds(_Period);
   bool skipTrade        = false;

   if(weekDay==0)
     {
      if(sessionIgnoreSunday) return true;
      int lastBarFix=sessionCloseAtSessionClose ? periodLength : 0;
      skipTrade=timeFromMidnight<sessionSundayOpen || timeFromMidnight+lastBarFix>sessionSundayClose;
     }
   else if(weekDay<5)
     {
      int lastBarFix=sessionCloseAtSessionClose ? periodLength : 0;
      skipTrade=timeFromMidnight<sessionMondayThursdayOpen || timeFromMidnight+lastBarFix>sessionMondayThursdayClose;
     }
   else
     {
      int lastBarFix=sessionCloseAtFridayClose || sessionCloseAtSessionClose ? periodLength : 0;
      skipTrade=timeFromMidnight<sessionFridayOpen || timeFromMidnight+lastBarFix>sessionFridayClose;
     }

   return (skipTrade);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool IsForceSessionClose()
  {
   if(!sessionCloseAtFridayClose && !sessionCloseAtSessionClose)
      return (false);

   MqlDateTime time0; TimeToStruct(Time[0],time0);
   int weekDay           = time0.day_of_week;
   long timeFromMidnight = Time[0]%86400;
   int periodLength      = PeriodSeconds(_Period);
   int lastBarFix        = sessionCloseAtSessionClose ? periodLength : 0;

   bool forceExit=false;
   if(weekDay==0)
     {
      forceExit=timeFromMidnight+lastBarFix>sessionSundayClose;
     }
   else if(weekDay<5)
     {
      forceExit=timeFromMidnight+lastBarFix>sessionMondayThursdayClose;
     }
   else
     {
      int fridayBarFix=sessionCloseAtFridayClose || sessionCloseAtSessionClose ? periodLength : 0;
      forceExit=timeFromMidnight+fridayBarFix>sessionFridayClose;
     }

   return (forceExit);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double GetPipValue()
  {
   switch((int) MarketInfo(_Symbol,MODE_DIGITS))
     {
      case  5: case  4: return (0.0001);
      case  3: case  2: return (0.01);
      case  1:          return (0.1);
     }

   return (1);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int GetMagicNumber(int strategyIndex)
  {
   int magic=1000*Base_Magic_Number+strategyIndex;
   return (magic);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OrderDirectionToCommand(OrderDirection dir)
  {
   if(dir==ORDER_DIRECTION_BUY)  return (OP_BUY);
   if(dir==ORDER_DIRECTION_SELL) return (OP_SELL);

   return (OP_FLAT);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void SetSignals(Signal &signalList[])
  {
   int i=0;
   ArrayResize(signalList,2*strategiesCount);
   HideTestIndicators(true);

   /*STRATEGY CODE {"openFilters":[{"listIndexes":[0,0,0,0,0],"numValues":[15,25,0,0,0,0],"name":"Moving Averages Crossover"}],"closeFilters":[{"listIndexes":[2,0,0,0,0],"numValues":[0,0,0,0,0,0],"name":"Accumulation Distribution"}],"properties":{"entryLots":0.1,"stopLoss":100,"takeProfit":100,"useStopLoss":false,"useTakeProfit":false,"isTrailingStop":false}} */
   signalList[i++] = GetExitSignal_00();
   signalList[i++] = GetEntrySignal_00();

   HideTestIndicators(false);
   if(i!=2*strategiesCount)
      ArrayResize(signalList,i);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
Signal GetEntrySignal_00()
  {
   double ind0val1 = iMA(NULL,0,15,0,MODE_SMA,PRICE_CLOSE,1);
   double ind0val2 = iMA(NULL,0,25,0,MODE_SMA,PRICE_CLOSE,1);
   double ind0val3 = iMA(NULL,0,15,0,MODE_SMA,PRICE_CLOSE,2);
   double ind0val4 = iMA(NULL,0,25,0,MODE_SMA,PRICE_CLOSE,2);
   bool ind0long  = ind0val1 > ind0val2 + sigma && ind0val3 < ind0val4 - sigma;
   bool ind0short = ind0val1 < ind0val2 - sigma && ind0val3 > ind0val4 + sigma;

   Signal signal = CreateEntrySignal(0, ind0long, ind0short, 0, 0, false);
   return (signal);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
Signal GetExitSignal_00()
  {
   double ind1val1 = iAD(NULL,0,1);
   double ind1val2 = iAD(NULL,0,2);
   double ind1val3 = iAD(NULL,0,3);
   bool ind1long  = ind1val1 > ind1val2 + sigma && ind1val2 < ind1val3 - sigma;
   bool ind1short = ind1val1 < ind1val2 - sigma && ind1val2 > ind1val3 + sigma;

   Signal signal = CreateExitSignal(0, ind1long, ind1short, 0, 0, false);
   return (signal);
  }
//+------------------------------------------------------------------+
/*STRATEGY MARKET Darwinex-Live; EURUSD; D1 */
