//+------------------------------------------------------------------+
//|                  Click-and-drag take profit and stop loss EA.mq4 |
//|                                                    Steve Hopwood |
//|                                     www.hopwood3.freeserve.co.uk |
//+------------------------------------------------------------------+
#property copyright "Steve Hopwood"
#property link      "www.hopwood3.freeserve.co.uk"
#include <WinUser32.mqh>
#include <stdlib.mqh>
#define  NL    "\n"

#define  TpPrefix "Tp"
#define  SlPrefix "Sl"

//Error reporting
#define  slm " stop loss modification failed with error "
#define  tpm " take profit modification failed with error "
#define  ocm " order close failed with error "
#define  odm " order delete failed with error "
#define  pcm " part close failed with error "

/*
void SetStop()
void SetTp()
void DetectTrade()

*/

      
extern int        InstantTpPips=500;
extern int        InstantSlPips=500;
extern int        PipsHiddenFromCriminal=100;//For Stealth
extern color      Color.TakeProfit = LawnGreen;
extern color      Color.StopLoss = HotPink;
extern int        MagicNumber=0;
////////////////////////////////////////////////////////////////////////////////////////
//doubles converters for the integers the user sees
double            InstantTp;
double            InstantSl;
double            HiddenPips;
double            HiddenStopLoss, HiddenTakeProfit;//For Stealth
////////////////////////////////////////////////////////////////////////////////////////

double            SL,TP;
//int               TicketNo = -1, OpenTrades, OldOpenTrades;
int               OpenTrades, OldOpenTrades;
string            TpLineName, SlLineName;
////////////////////////////////////////////////////////////////////////////////////////
//Calculating the factor needed to turn pip values into their correct points value to accommodate different Digit size.
//Thanks to Lifesys for providing this code. Coders, you need to briefly turn of Wrap and turn on a mono-spaced font to view this properly and see how easy it is to make changes.
string          pipFactor[]  = {"JPY","XAG","SILVER","BRENT","WTI","XAU","GOLD","SP500","S&P","UK100","WS30","DAX30","DJ30","NAS100","CAC400"};
double          pipFactors[] = { 100,  100,  100,     100,    100,  10,   10,    10,     10,   1,      1,     1,      1,     1,       1};
double          factor;//For pips/points stuff. Set up in int init()
////////////////////////////////////////////////////////////////////////////////////////

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
{
//----
      
      //Accommodate different quote sizes
      factor = PFactor(Symbol());
      InstantTp = InstantTpPips;
      InstantSl = InstantSlPips;
      HiddenPips = PipsHiddenFromCriminal;
      
      
      
      
      
      start();
      
//----
   return(0);
}
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
{
//----
   if (ObjectsTotal() > 0) DeleteEAObjects();
   
//----
   return(0);
}

double PFactor(string symbol)
{
   //This code supplied by Lifesys. Many thanks Paul - we all owe you. Gary was trying to make me see this, but I could not understand his explanation. Paul used Janet and John words
   
   for ( int i = ArraySize(pipFactor)-1; i >=0; i-- ) 
      if (StringFind(symbol,pipFactor[i],0) != -1) 
         return (pipFactors[i]);
   return(10000);

}//End double PFactor(string pair)

bool CloseEnough(double num1, double num2)
{
   /*
   This function addresses the problem of the way in which mql4 compares doubles. It often messes up the 8th
   decimal point.
   For example, if A = 1.5 and B = 1.5, then these numbers are clearly equal. Unseen by the coder, mql4 may
   actually be giving B the value of 1.50000001, and so the variable are not equal, even though they are.
   This nice little quirk explains some of the problems I have endured in the past when comparing doubles. This
   is common to a lot of program languages, so watch out for it if you program elsewhere.
   Gary (garyfritz) offered this solution, so our thanks to him.
   */
   
   if (num1 == 0 && num2 == 0) return(true); //0==0
   if (MathAbs(num1 - num2) / (MathAbs(num1) + MathAbs(num2)) < 0.00000001) return(true);
   
   //Doubles are unequal
   return(false);

}//End bool CloseEnough(double num1, double num2)

//+------------------------------------------------------------------+
//| Delete Objects that match ObjName                                |
//+------------------------------------------------------------------+
void DeleteEAObjects()
{
   for (int i=ObjectsTotal()-1; i >= 0; i--) 
   {
     string name = ObjectName(i);
     if (StringFind(name, SlPrefix) > -1 || StringFind(name, TpPrefix) > -1)ObjectDelete(name);
   }
   
}//End void DeleteEAObjects()

//+------------------------------------------------------------------+
void ReplaceMissingSlTpLines()
{

   if (OrderTakeProfit() > 0 || OrderStopLoss() > 0) DrawTakeStopLines();

}//End void ReplaceMissingSlTpLines()

void DrawTakeStopLines()
{
   //This function will work for a full pending-trade EA.
   //The pending tp/sl can be used for hiding the stops in a market-trading ea
   
   string LineName = TpPrefix + DoubleToStr(OrderTicket(), 0);//TicketNo is set by the calling function - either CountOpenTrades or DoesTradeExist
   HiddenTakeProfit = 0;
   if (TP > 0)
   {
      if (OrderType() == OP_BUY)
      {
         HiddenTakeProfit = TP - (HiddenPips / factor);
      }//if (OrderType() == OP_BUY)
      
      if (OrderType() == OP_SELL)
      {
         HiddenTakeProfit = TP + (HiddenPips / factor);
      }//if (OrderType() == OP_BUY)      
   }//if (TP > 0)
   
   if (HiddenTakeProfit > 0 && ObjectFind(LineName) == -1)
   {
      ObjectDelete(LineName);
      ObjectCreate(LineName, OBJ_HLINE, 0, TimeCurrent(), HiddenTakeProfit);
      ObjectSet(LineName, OBJPROP_COLOR, Color.TakeProfit);
      ObjectSet(LineName, OBJPROP_WIDTH, 1);
      ObjectSet(LineName, OBJPROP_STYLE, STYLE_DOT);
   }//if (HiddenTakeProfit > 0)
   
   
   LineName = SlPrefix + DoubleToStr(OrderTicket(), 0);//TicketNo is set by the calling function - either CountOpenTrades or DoesTradeExist
   HiddenStopLoss = 0;
   if (SL > 0)
   {
      if (OrderType() == OP_BUY)
      {
         HiddenStopLoss = SL + (HiddenPips / factor);
      }//if (OrderType() == OP_BUY)
      
      if (OrderType() == OP_SELL)
      {
         HiddenStopLoss = SL - (HiddenPips / factor);
      }//if (OrderType() == OP_BUY)      
   }//if (SL > 0)
   
   if (HiddenStopLoss > 0 && ObjectFind(LineName) == -1)
   {
      ObjectDelete(LineName);
      ObjectCreate(LineName, OBJ_HLINE, 0, TimeCurrent(), HiddenStopLoss);
      ObjectSet(LineName, OBJPROP_COLOR, Color.StopLoss);
      ObjectSet(LineName, OBJPROP_WIDTH, 1);
      ObjectSet(LineName, OBJPROP_STYLE, STYLE_DOT);
   }//if (HiddenStopLoss > 0)
   
   

}//End void DrawTakeStopLines()


void DeletePendingPriceLines()
{

   
   //ObjectDelete(pendingpriceline);
   string LineName = TpPrefix + DoubleToStr(OrderTicket(), 0);
   ObjectDelete(LineName);
   LineName = SlPrefix + DoubleToStr(OrderTicket(), 0);
   ObjectDelete(LineName);
   
}//End void DeletePendingPriceLines()

void ReportError(string function, string message)
{
   //All purpose sl mod error reporter. Called when a sl mod fails
   
   int err=GetLastError();
      
   Alert("Click and drag ", OrderTicket(), function, message, err,": ",ErrorDescription(err));
   Print("Click and drag ", OrderTicket(), function, message, err,": ",ErrorDescription(err));
   
}//void ReportError()

void CountOpenTrades()
{
   //Not all these will be needed. Which ones are depends on the individual EA.
   OpenTrades = 0;

   if (OrdersTotal() == 0) return;
   
   //Iterating backwards through the orders list caters more easily for closed trades than iterating forwards
   for (int cc = OrdersTotal() - 1; cc >= 0; cc--)
   {
      bool TradeWasClosed = false;//See 'check for possible trade closure'

      //Ensure the trade is still open
      if (!OrderSelect(cc, SELECT_BY_POS, MODE_TRADES) ) continue;
      //Ensure the EA 'owns' this trade
      if (OrderSymbol() != Symbol() ) continue;
      if (OrderMagicNumber() != MagicNumber) continue;
      if (OrderCloseTime() > 0) continue; 
      
      
      OpenTrades++;
      
      
      //Replace missing tp and sl lines
      if (HiddenPips > 0) ReplaceMissingSlTpLines();
      
         
      
   }//for (int cc = OrdersTotal() - 1; cc <= 0; c`c--)
   
   
   
}//End void CountOpenTrades();


//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

//~ void CreateLine(string name,datetime tfrom, datetime tto, double pfrom, double pto, color Col, int width)
//~ {
         //~ if(ObjectFind(name) != 0)
         //~ {
            //~ ObjectCreate(name, OBJ_HLINE, 0, tfrom, pfrom,tto,pto);
            
            //~ ObjectSet(name, OBJPROP_STYLE, STYLE_SOLID);
            
            //~ ObjectSet(name, OBJPROP_COLOR, Col);
            //~ //ObjectSet(name,OBJPROP_WIDTH,width);
            //~ //ObjectSet(name,OBJPROP_RAY,RayLine);
         //~ }
         //~ else
         //~ {
            //~ ObjectDelete(name);
            //~ ObjectCreate(name, OBJ_HLINE, 0, tfrom, pfrom,tto,pto);
            
            //~ ObjectSet(name, OBJPROP_STYLE, STYLE_SOLID);
            
            //~ //ObjectSet(name, OBJPROP_COLOR, Col);        
            //~ //ObjectSet(name,OBJPROP_WIDTH,width);
            //~ //ObjectSet(name,OBJPROP_RAY,RayLine);
         //~ }
//~ }//End void CreateLine(string name,datetime tfrom, datetime tto, double pfrom, double pto, int width, color Col)



//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+


void SetStop()
{
   //Sets up a stop when none exists
   
   if (OrderType() == OP_BUY)
   {
      SL = OrderOpenPrice() - (InstantSl / factor) - (HiddenPips / factor);
   }//if (OrderType() == OP_BUY)
   
      
   if (OrderType() == OP_SELL)
   {
      SL = OrderOpenPrice() + (InstantSl / factor) + (HiddenPips / factor);
   }//if (OrderType() == OP_BUY)
   
   bool result = OrderModify(OrderTicket(), OrderOpenPrice(), SL, OrderTakeProfit(), OrderExpiration(), CLR_NONE);      
   if (!result) ReportError(" CheckForSlLineDrag()", slm);      

   
}//End void SetStop()

void SetTp()
{
   //Sets up a tp when none exists
   
   if (OrderType() == OP_BUY)
   {
      TP = OrderOpenPrice() + (InstantTp / factor) + (HiddenPips / factor);
   }//if (OrderType() == OP_BUY)
   
       
   if (OrderType() == OP_SELL)
   {
      TP = OrderOpenPrice() - (InstantTp / factor) - (HiddenPips / factor);
   }//if (OrderType() == OP_BUY)
   
   bool result = OrderModify(OrderTicket(), OrderOpenPrice(), OrderStopLoss(), TP, OrderExpiration(), CLR_NONE);      
   if (!result) ReportError(" CheckForSlLineDrag()", slm);      

}//End void SetStop()

void CheckForTpLineDrag()
{
   
   //Adjust the tp if the user has dragged the tp line
   double vtp = OrderTakeProfit();//Visible Take Profit (i.e. 'hard' tp)
   double htp = 0;//Hidden Take Profit at the visible line
   double ntp = 0;//New Take Profit following a drag of the line
   
   htp = NormalizeDouble(ObjectGet(TpLineName, OBJPROP_PRICE1), Digits);
   if (OrderType() == OP_BUY)
   {
      ntp = htp + (HiddenPips / factor);
   }//if (OrderType() == OP_BUY)
   
   if (OrderType() == OP_SELL)
   {
      ntp = htp - (HiddenPips / factor);
   }//if (OrderType() == OP_SELL)
   
   if (!CloseEnough(ntp, vtp) && !CloseEnough(ntp, 0) )
   {
      bool result = OrderModify(OrderTicket(), OrderOpenPrice(), OrderStopLoss(), ntp, OrderExpiration(), CLR_NONE);      
      if (!result) ReportError(" CheckForTpLineDrag()", tpm);      
   }//if (!CloseEnough(ntp, htp) )
   
   
}//End void CheckForTpLineDrag()

void CheckForSlLineDrag()
{
   
   //Adjust the sl if the user has dragged the sl line
   double vsl = OrderStopLoss();//Visible Stop Loss (i.e. 'hard' sl)
   double hsl = 0;//Hidden Stop Loss at the visible line
   double nsl = 0;//New Stop Loss following a drag of the line
   
   hsl = NormalizeDouble(ObjectGet(SlLineName, OBJPROP_PRICE1), Digits);
   if (OrderType() == OP_BUY)
   {
      nsl = hsl - (HiddenPips / factor);
   }//if (OrderType() == OP_BUY)
   
   if (OrderType() == OP_SELL)
   {
      nsl = hsl + (HiddenPips / factor);
   }//if (OrderType() == OP_SELL)
   
   if (!CloseEnough(nsl, vsl) )
   {
      bool result = OrderModify(OrderTicket(), OrderOpenPrice(), nsl, OrderTakeProfit(), OrderExpiration(), CLR_NONE);      
      if (!result) ReportError(" CheckForSlLineDrag()", slm);      
   }//if (!CloseEnough(nsl, hsl) )
   
   
}//End void CheckForSlLineDrag()

bool LookForTradeClosure(int ticket)
{
   //Close the trade if the close conditions are met.
   //Called from within CountOpenTrades(). Returns true if a close is needed and succeeds, so that COT can increment cc,
   //else returns false
   
   if (!OrderSelect(ticket, SELECT_BY_TICKET) ) return(true);
   if (OrderSelect(ticket, SELECT_BY_TICKET) && OrderCloseTime() > 0) return(true);
   
   bool CloseThisTrade;
   
   string LineName = TpPrefix + DoubleToStr(ticket, 0);
   //Work with the lines on the chart that represent the hidden tp/sl
   double take = ObjectGet(LineName, OBJPROP_PRICE1);
   if (CloseEnough(take, 0) ) take = OrderTakeProfit();
   LineName = SlPrefix + DoubleToStr(ticket, 0);
   double stop = ObjectGet(LineName, OBJPROP_PRICE1);
   if (CloseEnough(stop, 0) ) stop = OrderStopLoss();
   
   
   if (OrderType() == OP_BUY)
   {
      //TP
      if (Bid >= take && !CloseEnough(take, 0) ) CloseThisTrade = true;
      //SL
      if (Bid <= stop && !CloseEnough(stop, 0) ) CloseThisTrade = true;

   }//if (OrderType() == OP_BUY)
   
   
   if (OrderType() == OP_SELL)
   {
      //TP
      if (Bid <= take && !CloseEnough(take, 0) ) CloseThisTrade = true;
      //SL
      if (Bid >= stop && !CloseEnough(stop, 0) ) CloseThisTrade = true;

   }//if (OrderType() == OP_SELL)
   
   if (CloseThisTrade)
   {
      bool result = CloseTrade(ticket);
      //Actions when trade close succeeds
      if (result)
      {
         DeletePendingPriceLines();
         OpenTrades--;//Rather than OpenTrades = 0 to cater for multi-trade EA's
         return(true);//Makes CountOpenTrades increment cc to avoid missing out ccounting a trade
      }//if (result)
   
      //Actions when trade close fails
      if (!result)
      {
         return(false);//Do not increment cc
      }//if (!result)
   }//if (CloseThisTrade)
   
   //Got this far, so no trade closure
   return(false);//Do not increment cc
   
}//End bool LookForTradeClosure()

bool CloseTrade(int ticket)
{   
   while(IsTradeContextBusy()) Sleep(100);
   bool result = OrderClose(ticket, OrderLots(), OrderClosePrice(), 1000, CLR_NONE);

   //Actions when trade send succeeds
   if (result)
   {
      return(true);
   }//if (result)
   
   //Actions when trade send fails
   if (!result)
   {
      ReportError(" CloseTrade()", ocm);
      return(false);
   }//if (!result)
   

}//End bool CloseTrade(ticket)
void DeleteOrphanTpSlLines()
{

   if (ObjectsTotal() == 0) return;
   
   for (int cc = ObjectsTotal() - 1; cc >= 0; cc--)
   {
      string name = ObjectName(cc);
      
      if ((StringSubstr(name, 0, 2) == TpPrefix || StringSubstr(name, 0, 2) == SlPrefix) && ObjectType(name) == OBJ_HLINE)
      {
         int tn = StrToDouble(StringSubstr(name, 2));
         if (tn > 0) 
         {
            if (!OrderSelect(tn, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() > 0)
            {
               ObjectDelete(name);
            }//if (!OrderSelect(tn, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() > 0)
            
         }//if (tn > 0) 
         
         
      }//if (StringSubstr(name, 0, 1) == TpPrefix)
      
   }//for (int cc = ObjectsTotal() - 1; cc >= 0; cc--)
   
   
}//End void DeleteOrphanTpSlLines()

//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
//----
   
   //Delete orphaned tp/sl lines
   //static int M15Bars;
   //if (M15Bars != iBars(NULL, PERIOD_M15) )
   //{
      //M15Bars = iBars(NULL, PERIOD_M15);
      DeleteOrphanTpSlLines();
   //}//if (M15Bars != iBars(NULL, PERIOD_M15)

   
   // In case user has selected LeaveRunningAfterTradeCloses but there are no open trades to monitor
   if (OrdersTotal() == 0)
   {
      return;         
   }//if (OrdersTotal() == 0)

   CountOpenTrades();
   if (OpenTrades == 0) return;
   
   for (int cc = OrdersTotal() - 1; cc >= 0; cc--)
   {
      if (!OrderSelect(cc, SELECT_BY_POS) ) continue;
      if (OrderSymbol() != Symbol() ) continue;
      if (OrderMagicNumber() != MagicNumber) continue;
      
      
      //Set missing tp and sl
      TP = OrderTakeProfit();
      SL = OrderStopLoss();   
      
      if (CloseEnough(TP, 0) || CloseEnough(SL, 0))
      {
         if (CloseEnough(SL, 0)) SetStop();
         
         if (CloseEnough(TP, 0)) SetTp();
         
         DrawTakeStopLines();
         
         return;
      }//if (take == 0 or stop == 0)

      //Now use the sl/tp lines:
      TpLineName = TpPrefix + DoubleToStr(OrderTicket(), 0);
      SlLineName = SlPrefix + DoubleToStr(OrderTicket(), 0);

      //Is there a tp line?
      if (ObjectFind(TpLineName) > -1)
      {
         //Has the line been dragged by the user
         CheckForTpLineDrag();
      }//if (ObjectFind(TpLineName) > -1)
      
      //Is there an sl line?
      if (ObjectFind(SlLineName) > -1)
      {
         //Has the line been dragged by the user
         CheckForSlLineDrag();
      }//if (ObjectFind(SlLineName) > -1)
      
      //Should the trade be closed
      bool TradeWasClosed = LookForTradeClosure(OrderTicket() );
      if (TradeWasClosed) 
      {
         cc++;
         continue;
      }//if (TradeWasClosed) 
      
      
   }//for (int cc = OrdersTotal() - 1; cc >= 0; cc--)

   
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
