//+------------------------------------------------------------------+
//|                                         Championship_2010_v2.mq4 |
//|                                                        avoitenko |
//|                        https://login.mql5.com/ru/users/avoitenko |
//+------------------------------------------------------------------+
#property copyright ""
#property link      "https://login.mql5.com/ru/users/avoitenko"

//+------------------------------------------------------------------+
//| Входные параметры                                                |
//+------------------------------------------------------------------+
extern string  General         = "=== Основные параметры ===";
extern int     PERIOD          =     250; // Период расчета
extern int     STOP_LOSS       =      50; // Уровень стоп лосса 
extern int     TAKE_PROFIT     =     100; // Уровень тейк профита
extern int     INSIDE_LEVEL    =      30; // Уровень возврата внутрь канала
extern int     TRAILING_STOP   =      20; // Уровень трейлинг стопа
extern int     TRAILING_STEP   =      10; // Шаг перемещения трейлинг стопа
extern int     ORDER_STEP      =      10; // Шаг перемещения отложенного ордера
extern int     SLIPPAGE        =       2; // Проскальзывание
extern double  LOT             =       0.01; // Величина лота
extern bool    LOT_IS_FIXED    =    true; // Тип лота для торговли
extern bool    LOT_CORRECTION  =   false; // Коррекция величины лота
extern int     MAGIC_NUMBER    =     11111111; // Уникальный номер советника
extern string  Additional      = "=== Дополнительные параметры ===";
extern bool    PRINT_REPORT    =   false; // Вывод в журнал описания действий советника

//+------------------------------------------------------------------+
//| Глобальные переменные                                            |
//+------------------------------------------------------------------+
int stop_loss;            // стоп лосс
int take_profit;          // тейк профит
int inside_level;         // величина возврата цены внутрь канала
int trailing_level;       // Уровень трейлинг стопа
int trailing_step;        // шаг трейлинг стопа
int order_step;           // шаг перемещения ордера
int slippage;             // проскальзывание
double lot;               // лот
//---
bool buy_open    = false; // флаг необходимости открытия позиции на покупку
bool sell_open   = false; // флаг необходимости открытия позиции на продажу
double CalcHigh;          // уровень сопротивления
double CalcLow;           // уровень поддержки
//---
double order_open_price;  // цена открытия ордера
double spread;            // величина спрэда
int stop_level;           // минимальный отступ от цены для установки стоп лосса / тейк профита
int order_type;           // тип ордера
int order_ticket;         // тикет ордера 
int orders_total;
double order_stop_loss;
double sl;
double tp;
double target;
double price;
string err_msg;
bool start;
bool alert;
double margin;

//+------------------------------------------------------------------+
//| функция инициализации эксперта                                   |
//+------------------------------------------------------------------+
int init()
//+------------------------------------------------------------------+
{
   
   start = true;
   alert = false;
//--- Проверка периода расчета
   if(PERIOD < 3)
   {
      err_msg = "Ошибка, неверно задан параметр PERIOD = " + DoubleToStr(PERIOD,0) + " допустимые значения >= 3";
      start = false;
   }

//--- Проверка значения стоп лосса
   if(STOP_LOSS <= 0) 
   {
      err_msg = "Ошибка, неверно задан параметр STOP_LOSS = " + DoubleToStr(STOP_LOSS,0) + " допустимые значения > 0";
      start = false;
   }

//--- Проверка значения тейк профита
   if(TAKE_PROFIT<0)
   {
      err_msg = "Ошибка, неверно задан параметр TAKE_PROFIT = " + DoubleToStr(TAKE_PROFIT,0) + " допустимые значения >= 0";
      start = false;
   }
//--- Проверка уровня возврата внутрь канала
   if(INSIDE_LEVEL<1)
   {
      err_msg = "Ошибка, неверно задан параметр INSIDE_LEVEL = " + DoubleToStr(INSIDE_LEVEL,0) + " допустимые значения >= 1";
      start = false;
   }

//--- Проверка уровня трейлинг стопа
   if(TRAILING_STOP<0)
   {
      err_msg = "Ошибка, неверно задан параметр TRAILING_STOP = " + DoubleToStr(TRAILING_STOP,0) + " допустимые значения >= 0";
      start = false;
   }
//--- Проверка шага перемещения трейлинг стопа   
   if(TRAILING_STEP < 0)
   {
      err_msg = "Ошибка, неверно задан параметр TRAILING_STEP = " + DoubleToStr(TRAILING_STEP,0) + " допустимые значения >= 0";
      start = false;
   }
//---   
   if(TRAILING_STEP > TRAILING_STOP)
   {
      err_msg = "Ошибка, неверно задан параметр TRAILING_STEP = " + DoubleToStr(TRAILING_STEP,0) + " допустимые значения < TRAILING_STOP";
      start = false;
   }
//--- Проверка шага перемещения отложенного ордера
   if(ORDER_STEP<0)
   {
      err_msg = "Ошибка, неверно задан параметр ORDER_STEP = " + DoubleToStr(ORDER_STEP,0) + " допустимые значения >=0";
      start = false;
   }
//--- Проверка проскальзывания
   if(SLIPPAGE<0)
   {
      err_msg = "Ошибка, неверно задан параметр SLIPPAGE = " + DoubleToStr(SLIPPAGE,0) + " допустимые значения >=0";
      start = false;
   }
//--- 
   if(!OrderCalcMargin(Symbol(),1,Ask,margin) && !LOT_IS_FIXED)
   {
      err_msg = "Ошибка, Для данного инструмента " + Symbol() +" возможна торговля только фиксированным лотом: LOT_IS_FIXED = true";
      start = false;
   }


//---
   stop_loss      = STOP_LOSS;
   take_profit    = TAKE_PROFIT;
   inside_level   = INSIDE_LEVEL;
   trailing_level = TRAILING_STOP;
   trailing_step  = TRAILING_STEP;
   slippage       = SLIPPAGE;
   order_step     = ORDER_STEP;

//--- Если цена состоит из 3-x / 5-и цифр
   if(Digits == 3 || Digits == 5)
   {
      stop_loss      = stop_loss      * 10;
      take_profit    = take_profit    * 10;
      inside_level   = inside_level   * 10;
      trailing_level = trailing_level * 10;
      trailing_step  = trailing_step  * 10;
      slippage       = slippage       * 10;
      order_step     = order_step     * 10;
   }
   start();
   return(0);
}

//+------------------------------------------------------------------+
//|   функция деинициализации эксперта                               |
//+------------------------------------------------------------------+
int deinit()
//+------------------------------------------------------------------+
  {
   return(0);
  }
//+------------------------------------------------------------------+
//|   основная функция эксперта                                      |
//+------------------------------------------------------------------+
int start()
//+------------------------------------------------------------------+
  {
   
   if(!IsExpertEnabled() && !IsTesting())
   {
      err_msg = "Предупреждение: Работа советника не разрешена. Пожалуйста, нажмите кнопку \"Expert Advisors\"";
      start=false;
   } 

//--- Если есть ошибка в значениях внешних переменных, тогда - выход
   if(!start)
   {
      if(!alert)
      {
         Alert("Ошибка входных данных советника","\n",err_msg);
         Print(err_msg);
         alert=true;
      }
      return(-1);
   }

//--- Проверка наличия исторических данных для таймфрейма M5
   if(iBars(Symbol(), PERIOD_M5) < PERIOD)
   {
      Print("Недостаточно исторических данных для торговли");
      return;
   }
//--- Получение данных массивов High и Low
   else
   {
//--- Нахождение минимума и максимума
      CalcHigh = 0.0;
      CalcLow  = 0.0;
      
      int high_index = iHighest(Symbol(), PERIOD_M5, MODE_HIGH, PERIOD,0);
      int low_index = iLowest(Symbol(), PERIOD_M5, MODE_LOW, PERIOD,0);
      
      CalcHigh = iHigh(Symbol(), PERIOD_M5, high_index);
      CalcLow  =  iLow(Symbol(), PERIOD_M5, low_index);
   }

//--- Выход, если CalcHigh и CalcLow не посчитаны
   if(CalcHigh < Point || CalcLow < Point) return;

//--- Обновление параметров
   stop_level = MarketInfo(Symbol(),MODE_STOPLEVEL);
   spread = NormalizeDouble(Ask - Bid, Digits); 

//--- Условия возврата внутрь канала
   if(Ask <= NormalizeDouble(CalcHigh - inside_level * Point, Digits)) buy_open  = true;
   if(Bid >= NormalizeDouble(CalcLow  + inside_level * Point, Digits)) sell_open = true;

//--- Обработка открытых ордеров
   WorkWithPositions();

//--- Обработка отложенных ордеров
   WorkWithPendidngOrders();

//--- Выставление нового отложенного ордера на покупку
   if(buy_open) OpenOrderBuyStop();

//--- Выставление нового отложенного ордера на продажу
   if(sell_open) OpenOrderSellStop();

   return(0);
 }
 

//+------------------------------------------------------------------+
//|   Работа с позициями                                             |
//+------------------------------------------------------------------+
void WorkWithPositions()
//+------------------------------------------------------------------+
{
   orders_total = OrdersTotal();
   for(int pos = orders_total-1; pos>=0; pos--)
   {
      if(OrderSelect(pos, SELECT_BY_POS, MODE_TRADES))
      {
         if (OrderSymbol() != Symbol() || OrderMagicNumber() != MAGIC_NUMBER) continue; //не наш ордер
         {
            order_open_price = OrderOpenPrice();
            order_type       = OrderType();
            order_stop_loss  = OrderStopLoss();
            order_ticket     = OrderTicket();
            
            if(order_type == OP_BUY)
            {
               buy_open = false;

               //--- Трейлинг стоп
               if(order_stop_loss == 0 || trailing_level == 0 || trailing_step == 0) continue; // условие, при котором трейлинг стоп не работает
   
               target = NormalizeDouble(MathMax(order_open_price, order_stop_loss) + trailing_level*Point, Digits);
 
               if(Bid > target)
               {
                  sl = MathMax(order_open_price, order_stop_loss) + trailing_step * Point;
     
                  if(OrderModify(order_ticket, order_open_price, sl, OrderTakeProfit(), 0, Blue))
                     if(PRINT_REPORT)Print("Перенос стоп лосса ордера Buy # ", order_ticket);  
                  else
                     if(PRINT_REPORT)Print("ОШИБКА переноса стоп лосса ордера Buy #", order_ticket);
                  
                  Sleep(2000);
                  RefreshRates(); 

               }
               continue;
            }// end OP_BUY

            else if(order_type == OP_SELL)
            {
               sell_open = false;

               //--- Трейлинг стоп
               if(order_stop_loss == 0 || trailing_level == 0 || trailing_step == 0) continue;

               target = NormalizeDouble(MathMin(order_open_price, order_stop_loss) - trailing_level*Point, Digits);

               if(Ask < target)
               {
   
                  sl = MathMin(order_open_price, order_stop_loss) - trailing_step * Point;

                  if(OrderModify(order_ticket, order_open_price, sl, OrderTakeProfit(),0,Blue))
                     if(PRINT_REPORT)Print("Перенос стоп лосса Sell # ",order_ticket);
                  else
                     if(PRINT_REPORT)Print("ОШИБКА переноса стоп лосса ордера Sell #",order_ticket);
                     
                  Sleep(2000);
                  RefreshRates(); 
               }

            }// end OP_SELL
            
         }// end if

      }// end if select                

   }// end for
}

//+------------------------------------------------------------------+
//|   Работа с отложенными ордерами                                  |
//+------------------------------------------------------------------+
void WorkWithPendidngOrders()
//+------------------------------------------------------------------+
{
   //--- Обработка отложенных ордеров
   for(int pos=0; pos < OrdersTotal(); pos++)
   {
      if(OrderSelect(pos,SELECT_BY_POS,MODE_TRADES))
      {

         if( OrderSymbol() != Symbol() || OrderMagicNumber() != MAGIC_NUMBER) continue;
         
         order_ticket = OrderTicket();
         order_type = OrderType();
         order_open_price = OrderOpenPrice();
         
         if(order_type == OP_BUYSTOP)
         {
            buy_open = false;
            
            //--- Условия для перемещения отложенного ордера
            if(( NormalizeDouble(CalcHigh - order_step * Point + spread, Digits) < NormalizeDouble(order_open_price - order_step * Point + spread, Digits)) && 
               ( NormalizeDouble(CalcHigh - order_step * Point + spread, Digits) > Ask + stop_level * Point))
            {

               price = NormalizeDouble(CalcHigh - order_step * Point + spread, Digits);

               if(stop_loss == 0) sl = 0;
               else sl = NormalizeDouble(CalcHigh - order_step * Point - MathMax(stop_loss, stop_level) * Point, Digits);

               if(take_profit == 0) tp = 0;
               else tp = NormalizeDouble(CalcHigh - order_step * Point + MathMax(take_profit, stop_level) * Point, Digits);
               
               if (OrderModify(order_ticket, price, sl, tp, 0))
                  if(PRINT_REPORT)Print("Модифицирован ордер BuyStop # ",order_ticket);
               else
                  if(PRINT_REPORT)Print("ОШИБКА модификации ордера BuyStop # ",order_ticket);

               Sleep(2000);
               RefreshRates(); 
            }
               
         }//end ORDER_TYPE_BUY_STOP  

         else if(order_type == OP_SELLSTOP)
           {
            sell_open = false;
            
            //--- Условия для перемещения отложенного ордера
            if(( NormalizeDouble(CalcLow + order_step * Point, Digits) > order_open_price + order_step * Point)&& 
               ( NormalizeDouble(CalcLow + order_step * Point, Digits) < Bid - stop_level * Point))
              {
               price = NormalizeDouble(CalcLow + order_step * Point, Digits);

               if(stop_loss == 0) sl = 0;
               else sl = NormalizeDouble(price + MathMax(stop_loss * Point, stop_level * Point) + spread, Digits);

               if(take_profit == 0) tp = 0;
               else tp = NormalizeDouble(price - MathMax(take_profit * Point, stop_level * Point) + spread, Digits);

               if (OrderModify(OrderTicket(), price, sl, tp, 0))
                  if(PRINT_REPORT)Print("Модифицирован ордер SellStop # ",order_ticket);
               else
                  if(PRINT_REPORT)Print("ОШИБКА модификации ордера SellStop # ",GetLastError());
              
               Sleep(2000);
               RefreshRates(); 

              }

           }//end ORDER_TYPE_SELL_STOP   

        }// end order_ticket

     }// end for
  }

//+------------------------------------------------------------------+
//|   Установка отложенного ордера на покупку                        |
//+------------------------------------------------------------------+
void OpenOrderBuyStop()
//+------------------------------------------------------------------+
{
   lot = Calculate_Lot(LOT, LOT_IS_FIXED, OP_BUY); //Не указывайте ORDER_TYPE_BUY_STOP. Эта константа не обрабатывается в OrderCalcMargin!

   price = NormalizeDouble(CalcHigh - order_step * Point, Digits);

   //--- Если расчетное значение цены открытия ордера недопустимо близко к рыночной цене, тогда корректируем цену открытия ордера
   if(NormalizeDouble(Ask + stop_level * Point, Digits) > price) return(0);//price = NormalizeDouble(Ask + stop_level * Point, Digits);
  
   //--- Расчет стоп лосса
   if(stop_loss == 0) sl = 0;
   else sl = NormalizeDouble(price - MathMax(stop_loss, stop_level) * Point - spread, Digits);
   
   //--- Расчет тейк профита
   if(take_profit == 0) tp = 0;
   else tp = NormalizeDouble(price + MathMax(take_profit, stop_level) * Point - spread, Digits);

   if(OrderSend(Symbol(), OP_BUYSTOP, lot, NormalizeDouble(price,4), 0, NormalizeDouble(sl,4), NormalizeDouble(tp,4), DoubleToStr(MAGIC_NUMBER,0), MAGIC_NUMBER, 0, Blue) >= 0)
   {
      buy_open = false;
      if(PRINT_REPORT)Print("Открыт ордер BuyStop #" + DoubleToStr(OrderTicket(),0));
   }
   else 
   {
      if(PRINT_REPORT)Print("ОШИБКА Открытия ордера BuyStop, код ошибки:" + GetLastError());
   }
   
   Sleep(2000);
   RefreshRates();

}
//+------------------------------------------------------------------+  
//|   Установка отложенного ордера на продажу                        |
//+------------------------------------------------------------------+
void OpenOrderSellStop()
//+------------------------------------------------------------------+
  {
   lot = Calculate_Lot(LOT, LOT_IS_FIXED, OP_SELL); //Не указывайте ORDER_TYPE_SELL_STOP. Эта константа не обрабатывается в OrderCalcMargin!

   price = NormalizeDouble(CalcLow + order_step * Point, Digits);
   
   //--- Если расчетное значение цены открытия ордера недопустимо близко к рыночной цене, тогда корректируем цену открытия ордера
   if(NormalizeDouble(Bid - stop_level * Point, Digits) < price) return(0);//price = NormalizeDouble(Bid - stop_level * Point, Digits);
   
   //--- Расчет стоп лосса
   if(stop_loss == 0) sl = 0;
   else sl = NormalizeDouble(price + MathMax(stop_loss, stop_level)* Point + spread, Digits);
   
   //--- Расчет тейк профита
   if(take_profit == 0) tp = 0;
   else tp = NormalizeDouble(price - MathMax(take_profit, stop_level) * Point + spread, Digits);

   if(OrderSend( Symbol(), OP_SELLSTOP, lot, NormalizeDouble(price,4), 0, NormalizeDouble(sl,4), NormalizeDouble(tp,4), DoubleToStr(MAGIC_NUMBER,0),MAGIC_NUMBER, 0, Blue)>=0)
   {
      sell_open = false;
      if(PRINT_REPORT)Print("Открыт ордер SellStop #" + DoubleToStr(OrderTicket(),0));
   }
   else 
   {
      if(PRINT_REPORT)Print("ОШИБКА Открытия ордера SellStop, код ошибки:" + GetLastError());
   }
   
   Sleep(2000);
   RefreshRates();
}

//+------------------------------------------------------------------+
//|   Расчет лота                                                    |
//+------------------------------------------------------------------+
double Calculate_Lot(double lot_value, bool lot_is_fixed, int direction)
//+------------------------------------------------------------------+
  {
   double acc_free_margin = AccountFreeMargin();
   double calc_margin;
   double price = 0;

   if(direction == OP_BUY)  price = Ask;
   if(direction == OP_SELL) price = Bid;

   if(lot_is_fixed)
   {
      if(LOT_CORRECTION)//--- Коррекция величины лота
      {
         OrderCalcMargin(Symbol(), lot_value, price, calc_margin);
         
         //--- коррекция величины лота до 90% от свободных средств
         if(acc_free_margin < calc_margin)
         {
            lot_value = lot_value * acc_free_margin * 0.9 / calc_margin;
            if(PRINT_REPORT)Print("Скорректировано значение лота: ",lot);
         }
      }

   }
   else
   {
         //--- объем свободных средств для открытия позиции
         OrderCalcMargin(Symbol(), 1, price, calc_margin);
         lot_value = acc_free_margin * 0.01 * LOT / calc_margin;
   }

   return(NormalizeLot(lot_value));
  }

//+------------------------------------------------------------------+
//|   Нормализация величины лота                                     |
//+------------------------------------------------------------------+
double NormalizeLot(double lot_value)
//+------------------------------------------------------------------+
{
   double lot_min  = MarketInfo(Symbol(), MODE_MINLOT);
   double lot_max  = MarketInfo(Symbol(), MODE_MAXLOT);
   double lot_step = MarketInfo(Symbol(), MODE_LOTSTEP);

   if(lot_value <= lot_min ) lot_value = lot_min;                 // проверка на минимальный лот
   else  if(lot_value >= lot_max ) lot_value = lot_max;           // проверка на максимальный лот
   else lot_value  = MathFloor(lot_value / lot_step) * lot_step;  // округление до ближайшего меньшего

   return (NormalizeDouble(lot_value,2)); // нормировка объема
}

//+------------------------------------------------------------------+  
//|   Вычисление свободной маржи                                     |
//|   Актуально только для стандартных Форекс символов               |
//+------------------------------------------------------------------+  
bool  OrderCalcMargin(string    symbol,      // имя символа
                      double    volume,      // объем
                      double    price,       // цена открытия
                      double&   margin)      // переменная для получения значения маржи
//+------------------------------------------------------------------+
{
   string first    = StringSubstr(symbol,0,3);
   string second   = StringSubstr(symbol,3,3);
   string currency = AccountCurrency();        
   double leverage = AccountLeverage();        

   double contract = MarketInfo(symbol, MODE_LOTSIZE);
   margin = 0.0;

//---   
   if(StringLen(symbol) != 6)
     {
      Print("OrderCalcMargin: ",symbol," - должен быть стандартным инструментом Форекс по типу: XXXYYY");
      return(false);
     }
//--- 
   if( price <= 0.0 || contract <= 0) 
     {
      Print("OrderCalcMargin: нет рыночной информации по ",symbol);
      return(false);
     }
//---
   if(first == currency)
   {  
      margin=contract * volume / leverage; // USDxxx
      return(true);
   }
//---   
   if((second == currency))
   {
      margin=contract * price * volume / leverage; // xxxUSD
      return(true);
   }
//---
   Print("OrderCalcMargin: невозможно вычислить для ",symbol);
   return(false);
}