//+------------------------------------------------------------------+
//|                             Ex_Timer_OrderLimits_TrailByTime.mq4 |
//|                                    (c) 2008 by Sergey A. Gridnev |
//|                                                                * |
//+------------------------------------------------------------------+
#property copyright "(c) 2008 by Sergey A. Gridnev"
#property link      "*"
// --
#include "include/ExLib_Timer.mqh"
// --
#define  COUNTERID_RESERVED      100   // резерв для использование на служебные цели
#define  COUNTERID_TRADINGTIMEOUT  1   // ID счетчика торговой паузы
// --
//---- input parameters
// --
extern color   ArrowColor=CLR_NONE;
// рабочие параметры трейлинга
extern int  MagicNumber=0;
// - stop loss -
extern int  SL_StartPeriod;         // стартовый период
extern int  SL_TrailPeriodicity;    // периодичность
extern int  SL_TrailStep;           // шаг
// - take profit -
extern int  TP_StartPeriod;         // стартовый период
extern int  TP_TrailPeriodicity;    // периодичность
extern int  TP_TrailStep;           // шаг
//
// MagicNumber - используется для избирательного трейлинга
//             если MagicNumber=0, то трейлятся триггеры всех ордаров,
//             открытых на финансовом инструменте текущего графика,
//             иначе - только тех ордеров, которые имеют заданный MagicNumber
//
// _StartPeriod      - период без трейлинга для нового ордера
// _TrailPeriodicity - может принимать значения > 0
//                      0 и отрицательные значения не обрабатываются
// _TrailStep        - может принимать любые значения
//                      0  - отсутствие движения;
//                      >0 - движение от Ордера;
//                      <0 - движение к Ордеру;
//

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
{
   SL_StartPeriod = MathMax(SL_StartPeriod,SL_TrailPeriodicity);
   TP_StartPeriod = MathMax(TP_StartPeriod,TP_TrailPeriodicity);
   timer_Init();
   return(0);
} //init
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
{
   timer_DeleteAll();
   return(0);
} //deinit
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
{
   int      indx;
   int      CounterID;
   int      ErrorCode;
   int      type,ticket;
   double   price;
   double   SL,TP;
   double   dSL,dTP;

   if ( ! IsDemo() )
      return(0);
      
   if ( IsTesting() && OrdersTotal() < 2 )
   {
      bool flBuy=true;
      bool flSell=true;
      for ( indx=0; indx < OrdersTotal(); indx++ )
         if ( OrderSelect(indx,SELECT_BY_POS,MODE_TRADES) )
            if ( OrderType() == OP_BUY )
               flSell   = false;
            else if ( OrderType() == OP_SELL )
               flBuy   = false;
      if ( flSell )
         OrderSend(Symbol(),OP_SELL,0.1,Bid,3,Ask+25*Point,Ask-25*Point,"* Ordep for test *",12345,0,OrangeRed);
      if ( flBuy )
         OrderSend(Symbol(),OP_BUY,0.1,Ask,3,Bid-25*Point,Bid+25*Point,"* Ordep for test *",12345,0,MediumBlue);
   }

   if ( SL_TrailPeriodicity > 0 || TP_TrailPeriodicity > 0 )
   {
      for ( indx=0; indx < OrdersTotal(); indx++ )
         if ( OrderSelect(indx,SELECT_BY_POS,MODE_TRADES) )
         {
            // если задан MagicNumber, то пропуск ордеров
            // с иным значением параметра MagicNumber
            if ( MagicNumber != 0 && MagicNumber != OrderMagicNumber() )
               continue;
            
            type     = OrderType();
            ticket   = OrderTicket();
            SL       = OrderStopLoss();
            TP       = OrderTakeProfit();
            dTP      = 0.0;
            dSL      = 0.0;
            
            CounterID   = ConvertTicketToCounterID(ticket);
            // для SL в качестве <CounterID> используентся отрицательный номер тикета
            // если ордер новый, то установить значения выдержек
            if ( timer_GetType(CounterID) == 0 )
               if ( ! timer_StartWaitable(CounterID, TP_StartPeriod) ) // выдержка перед началом трейлинга TP
                  Print("ОШИБКА! Ошибка при вызове timer_StartWaitable: нет свободных счетчиков!");
               
            if ( timer_GetType(-CounterID) == 0 )
               if ( ! timer_StartWaitable(-CounterID, SL_StartPeriod) ) // выдержка перед началом трейлинга SL
                  Print("ОШИБКА! Ошибка при вызове timer_StartWaitable: нет свободных счетчиков!");
            
            if ( TP_TrailPeriodicity > 0 && timer_GetCounter(CounterID) == 0 )
               if ( type == OP_BUY )
                  dTP =  TP_TrailStep * Point;
               else if ( type == OP_SELL )
                  dTP = -TP_TrailStep * Point;
            
            if ( SL_TrailPeriodicity > 0 && timer_GetCounter(-CounterID) == 0 )
               if ( type == OP_BUY )
                  dSL = -SL_TrailStep * Point;
               else if ( type == OP_SELL )
                  dSL =  SL_TrailStep * Point;
            
            // подготовка новых значений TP и SL
            if ( TP > 0 )
               TP = NormalizeDouble( TP + dTP, Digits );
            if ( SL > 0 )
               SL = NormalizeDouble( SL + dSL, Digits );
            
            // проверка новых значений на допустимость по уровню STOPLEVEL
            RefreshRates();
            switch( type )
            {
            case OP_BUY:
               price = Bid;
               if ( TP - price < MarketInfo(Symbol(),MODE_STOPLEVEL) * Point
                  || TP - price <= MarketInfo(Symbol(),MODE_FREEZELEVEL) * Point
                  )
               {
                  TP    = OrderTakeProfit();
                  dTP   = 0.0;
               }
               if ( price - SL < MarketInfo(Symbol(),MODE_STOPLEVEL) * Point
                  || price - SL <= MarketInfo(Symbol(),MODE_FREEZELEVEL) * Point
                  )
               {
                  SL    = OrderStopLoss();
                  dSL   = 0.0;
               }
               break;
            case OP_SELL:
               price = Ask;
               if ( price - TP < MarketInfo(Symbol(),MODE_STOPLEVEL) * Point
                  || price - TP <= MarketInfo(Symbol(),MODE_FREEZELEVEL) * Point
                  )
               {
                  TP    = OrderTakeProfit();
                  dTP   = 0.0;
               }
               if ( SL - price < MarketInfo(Symbol(),MODE_STOPLEVEL) * Point
                  || SL - price <= MarketInfo(Symbol(),MODE_FREEZELEVEL) * Point
                  )
               {
                  SL    = OrderStopLoss();
                  dSL   = 0.0;
               }
               break;
            default:
               {}
            }
            
            if ( dTP == 0.0 && dSL == 0.0 )
            {
               /* модификация ордера не требуется */
            }
            else if ( timer_GetCounter(COUNTERID_TRADINGTIMEOUT) == 0 )
            {
               Print("Попытка модификации ордера ",ticket," cur.price=",price," sl:",OrderStopLoss(),">",SL," tp:",OrderTakeProfit(),">",TP);
               if ( OrderModify(ticket,NULL,SL,TP,NULL,ArrowColor) )
               {  // модификация ордера состоялась,
                  // запускаем таймер модифицированного параметра
                  if ( dTP != 0.0 )
                     if ( ! timer_StartWaitable(CounterID, TP_TrailPeriodicity) ) // выдержка для трейлинга TP
                        Print("ОШИБКА! Ошибка при вызове timer_StartWaitable: нет свободных счетчиков!");
                  if ( dSL != 0.0 )
                     if ( ! timer_StartWaitable(-CounterID, SL_TrailPeriodicity) ) // выдержка для трейлинга SL
                        Print("ОШИБКА! Ошибка при вызове timer_StartWaitable: нет свободных счетчиков!");
               }
               else
               {
                  ErrorCode   = GetLastError();
                  Print("ОШИБКА! Ошибка при вызове OrderModify! Код ошибки: ",ErrorCode);
                  Print(" ! STOPLEVEL * Point = ",MarketInfo(Symbol(),MODE_STOPLEVEL) * Point);
                  Print(" ! FREEZELEVEL * Point = ",MarketInfo(Symbol(),MODE_FREEZELEVEL) * Point);
                  // В случае ошибки установлена пауза между торговыми операциями 60 сек.
                  // На самом деле пауза должна быть не меньше значения,
                  // указанного в документации для соответствующей ошибки
                  if ( ! timer_StartWaitable(COUNTERID_TRADINGTIMEOUT,60) )
                     Print("ОШИБКА! Ошибка при вызове timer_StartWaitable: нет свободных счетчиков!");
               }
            }
         }
   }
   
   // удаление счетчиков таймера для номеров тикетов,
   // отсутствующих среди открытых и отложенных
   for ( indx=0; indx < timer_NumberUsed(); indx++ )
   {
      CounterID   = timer_GetCounterIDbyPOS(indx);
      if ( CounterID > 0 )
         ticket   = ConvertCounterIDToTicket(CounterID);
      else
         ticket   = ConvertCounterIDToTicket(-CounterID);

      if ( OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES) )
      {
         if ( OrderCloseTime() != 0 )
            timer_Delete(CounterID);
      }
      else
         timer_Delete(CounterID);
   }
   
   return(0);
} //start
//+------------------------------------------------------------------+
int ConvertTicketToCounterID(int ticket)
{
   int   CounterID   = COUNTERID_RESERVED + ticket;
   return(CounterID);
} //ConvertTicketToCounterID
//+------------------------------------------------------------------+
int ConvertCounterIDToTicket(int CounterID)
{
   int   ticket   = CounterID - COUNTERID_RESERVED;
   if ( ticket < 0 )
      ticket   = 0;
   return(ticket);
} //ConvertCounterIDToTicket
//+------------------------------------------------------------------+

