Учимся писать советник. Выстраивание колен. Часть 7

В предпраздничные дни решил преподнести женщинам подарок в виде реализации в нашем форекс-советнике доливки по мартингейлу в случае убыточной серии. Ничего нового, но так как разрабатываешь сам с нуля, то лучше понимаешь смысл содеянного.

Итак, в предыдущей версии 1.6 я ввел структуры для хранения настроек торгов по одной валютной паре. Нам это еще не скоро пригодится, а может и не пригодится вообще, но узнать и сделать что-то новое всегда полезно. Как обычно стараюсь комментировать нововведения.


//|      Forex-Grail.1.8.mq4 |
/*
Тут описание что сделано или нужно сделать
1.6 Пробуем возможность тестирования на нескольких валютных парах
1.7 Торгуем несколькими ордерами
1.8 Выстраиваем колена в случае убытка
*/
extern int    MaxOpenedOrders=10; //Максимальное число открытых ордеров
//пунктов между коленями. Т.е. если пошли не в нашу сторону, через столько пунктов открываем новое колено
extern int    PipStep=15; 

...
//-- 1.6 --
//Структура для описания настроек пары по которой ведем торговлю на случай мультивалютной торговли
struct PAIRS {
    double StartLot ; // Размер лота для начала торгов
    double LimitLot ; //Максимально допустимый лот
    double Rastvor;    // Расстояние между МА 
    double KoeffMartingale; //Коэффициент мартингейла
    
    string Symbol; //Символ EURUSD
    
    int SlipPage ; //Проскальзывание
    int StopLoss ; //Стоп-лосс
    int TakeProfit ; //Тейк-профит
    int Risk; //Риск в % от депозита
    int TrailingStopStart; //Минимальный профит для трейлинга
    int TrailingStopStep; //Шаг трейлинга

    int Period_MA_1;      // Период МА 1
    int Period_MA_2;      // Период МА 2
    int TimeFramePeriod_MA;
 
    int MaxOpenedOrders; //число максимально открытых ордеров
    
    bool EnablePair; //торгуем этой парой 
    bool InvertEnter; //Вход в противоположную сторону от сигнала
    bool EnableTrailing ; //Использовать трейлинг стоп 
    
    bool UsePreviousResult ; //Если предыдущая сделка прибыльная, откроемся в том же направлении 
    bool EnableMartingale; //Использовать мартингейл
    bool EnableMoneyManagement; //Включить управление деньгами
 
};

PAIRS          Pairs[1]; //пока одна пара
datetime  TimePrev = 0; //Для контроля баров
....

Массив нашей структуры нужно проинициализировать заданными значениями.


...
int OnInit()
  {
 
   //--Настройка пар 
   Pairs[0].Symbol=Symbol();
   Pairs[0].EnablePair=true;
   Pairs[0].SlipPage = SlipPage;
   
   Pairs[0].StopLoss     = StopLoss; 
   Pairs[0].TakeProfit   = TakeProfit;
    
   Pairs[0].StartLot =StartLot; 
   Pairs[0].LimitLot = LimitLot; 
   
   Pairs[0].InvertEnter = InvertEnter;
   Pairs[0].EnableTrailing = EnableTrailing; 
   
   Pairs[0].TrailingStopStart=TrailingStopStart; 
   Pairs[0].TrailingStopStep=TrailingStopStep; 
   
   Pairs[0].Rastvor=Rastvor;
   Pairs[0].Period_MA_1=Period_MA_1; 
   Pairs[0].Period_MA_2=Period_MA_2; 
   Pairs[0].TimeFramePeriod_MA=TimeFramePeriod_MA;
   Pairs[0].UsePreviousResult=UsePreviousResult;
   
   Pairs[0].KoeffMartingale=KoeffMartingale;
   Pairs[0].EnableMartingale=EnableMartingale;
   Pairs[0].EnableMoneyManagement=EnableMoneyManagement;
   Pairs[0].Risk=Risk;
   
    Pairs[0].MaxOpenedOrders=MaxOpenedOrders;
   //--Настройка пар 
   
   //корректируем некоторые значения для 5 или 3 значных котировок
   if (Digits == 5 || Digits == 3)
   {
      Pairs[0].TakeProfit *= 10;
      Pairs[0].StopLoss *= 10;
      Pairs[0].TrailingStopStart*=10;
      Pairs[0].TrailingStopStep*=10;
      Pairs[0].SlipPage*=10;
      PipStep*=10;
   }
...

В функции OnTick мы убрали свою функцию Closing(), так как теперь сами не закрываем сделки, это за нас делает рынок в лице тёти Поли и дяди Коли 🙂
Кроме того, теперь мы отслеживаем бары и все входы/модификации делаем на новом баре.


...
void OnTick()
{
   if (Pairs[0].EnableTrailing) TrailingPositions(Pairs[0]); //трейлин пары
   if ( TimePrev == Time[0]) return;
   TimePrev = Time[0];
   //Closing(Pairs[0]); //проверка условия закрытия пары - но мы не боимся падения и будем доливаться!
   HaveMoney=CheckMoney(Pairs[0]); //проверка наличия денег для торговли на паре
   if (HaveMoney) Traiding(Pairs[0]); //проверка условий и открытие торговли по паре

}
...

Основное изменение коснулось пользовательской функции Traiding(). Вкратце идея такова: если еще нет открытых ордеров, то открываемся по техническому сигналу. Если задано, и ранее уже были закрыты сделки, то смотрим убыточны они или нет и в зависимости от настроек открываемся в нужном направлении.
Интересное начинается, если у нас уже есть в рынке открытые ордера, до версии 1.7 такого не допускалось. В 1.7 я просто ввел параметр, допускающий открытие нескольких сделок, из-за незначительности модификации отдельно афишировать это не стал.
Итак, если у нас есть открытые ордера, то ищем цену открытия последнего. И если рынок идет против него, и уже прошел более PipStep пунктов, то открываем в том же направлении новый ордер с лотом, умноженным на коэффициент мартингейла в степени номера колена (тут не использую настройки риска, а только стартовый лот). Затем рассчитываем значение цены тейкпрофита, чтобы при закрытии всех ордеров в данном направлении мы достигли таки нужной нам прибыли TakeProfit. После чего модифицируем все ордера соответствующим образом. Вот в принципе и весь смысл.


...
void Traiding(PAIRS &pair)
{
string symbol=pair.Symbol;
double lastBuyPrice=0,lastSellPrice=0;
int direction=0,ticket=-1;
int digits=(int)MarketInfo(symbol,MODE_DIGITS); 
double bid=MarketInfo(symbol,MODE_BID);
double ask=MarketInfo(symbol,MODE_ASK);
double point=MarketInfo(symbol,MODE_POINT);

int tradesCountLong=CountTradesLong(symbol,MagicNumber);
int tradesCountShort=CountTradesShort(symbol,MagicNumber);
int tradesCount=tradesCountLong+tradesCountShort;

bool newOrdersPlacedLong=false,newOrdersPlacedShort=false;
double lot=0;
double priceTarget=0,averagePrice=0;
double countLong=0,countShort=0;

   if (tradesCount>=pair.MaxOpenedOrders) return; // открытых ордеров больше заданного предела
    
    if (tradesCount==0) // куда будем открываться при отсутствии ордеров 
    {
       if (pair.UsePreviousResult && GetTicketLastClosePos(symbol,MagicNumber)) //если есть закрытый ордер, узнаем прибыльный ли он и в каком направлении
       {
          int type= Orders[0].OrderType;
          double take= Orders[0].OrderProfit;
             if (take>0) 
             {
             Print("открываемся туда же type=",type,"  take=",take);
             direction=type; //если последний ордер был в плюсе, откроемся в ту же сторону
             }
             else
             {
             direction=InvertPosition(type); //вы противном случае перевернемся
             Print("открываемся в противоположную type=",type,"  take=",take);
             }
         
       }
       else //ордеров в истории еще нет или не установлен UsePreviousResult, откроемся по сигналу
       {
        direction = TradeDirection(pair); //-- 1.5 --
        
        if (pair.InvertEnter && direction>-1) direction=InvertPosition(direction); //переменим позицию если задано и направление известно, а не =-1
       }
       
       //--открытие первого ордера 
      if (direction==-1) return; //нет торговых сигналов на открытие позиции ///--1.5--
      
      //если идем в лонг, то нас открывают по Ask а закрывают по Bid,т.к. продажа, а если идем в шорт, нас открывают по Bid, закрывают по Ask, так как уже покупка:
      if (direction==OP_BUY)
       {
        ticket= MyOrderSend(symbol,OP_BUY,GetLot(pair),NormalizeDouble(ask,digits),pair.SlipPage,NormalizeDouble(bid - pair.StopLoss*point,digits),NormalizeDouble(bid + pair.TakeProfit*point,digits),ExpertName,MagicNumber,0,Blue);          
       }
      else if (direction==OP_SELL) 
       {
        ticket= MyOrderSend(symbol,OP_SELL, GetLot(pair),NormalizeDouble(bid,digits), pair.SlipPage, NormalizeDouble(ask + pair.StopLoss*point,digits),NormalizeDouble(ask - pair.TakeProfit*point,digits),ExpertName,MagicNumber,0,Green);                       
       }
       
       return; //вышли
    }
    
    //Если ордера есть, посмотрим, может пора убыточные перекрывать    
    //--Лонг--
    if (tradesCountLong>0) //есть открытые лонги
    {
    lastBuyPrice = FindLastBuyPrice(symbol,MagicNumber); //нашли последнюю цену открытого лонга
        if (lastBuyPrice - ask >= PipStep * point) //упали до уровня нового открытия
        {
               lot =GetNextLot(pair,tradesCountLong);
               ticket= MyOrderSend(symbol,OP_BUY,lot,NormalizeDouble(ask,digits),pair.SlipPage,0,0,ExpertName + "-" + IntegerToString(tradesCountLong),MagicNumber,0,Blue);          
               if (ticket < 0) 
               {
                  Print("Ошибка открытия следующего колена Long: ", GetLastError());
                  //return (0);
               }
               newOrdersPlacedLong = true;        
        }
     }
    
    if (newOrdersPlacedLong) //если новый ордер открыт, модифицируем  предыдущие до среднего профита PriceTarget.  
    {
       tradesCountLong= CountTradesLong(symbol,MagicNumber); //заново посчитаем длинных ордеров
       averagePrice = 0;
       countLong = 0;
       priceTarget=0;
        for (int cnt = OrdersTotal() - 1; cnt >= 0; cnt--)
         {
            if (OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES))
            {
            if (OrderSymbol() == symbol && OrderMagicNumber() == MagicNumber)
             {
               if (OrderType() == OP_BUY)
               {
                  averagePrice += OrderOpenPrice() * OrderLots(); //подсчитываем среднюю цену открытия лота*лот
                  countLong += OrderLots(); //число лотов открытых вверх
               }
             }
            }
         }
         
         if (countLong > 0)
         {
          averagePrice = NormalizeDouble(averagePrice / countLong, digits); //средняя цена на 1 лот
          priceTarget = averagePrice + pair.TakeProfit * point; 
         }
         
         priceTarget=NormalizeDouble(priceTarget,digits);
         if (priceTarget>0)
         {
         for (int cnt = OrdersTotal() - 1; cnt >= 0; cnt--) 
         {
           if ( OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES))
           {
            if (OrderSymbol() == symbol && OrderMagicNumber() == MagicNumber) 
            {
             if (!MyOrderModify(OrderTicket(),OrderOpenPrice(),OrderStopLoss(),priceTarget,0,Yellow)) 
                      Print("Ошибка модификации ордера №",OrderTicket()," Long с профитом=",priceTarget,": ", GetLastError());       
            }
           }
         }
         }      
   }   
   
    if (tradesCountShort>0) //есть открытые шорты
    {
    lastSellPrice = FindLastSellPrice(symbol,MagicNumber); //нашли последнюю цену открытого шорта
          if (bid - lastSellPrice >= PipStep * point) //доросли до уровня когда надо открывать увеличенный ордер
          {

               lot =GetNextLot(pair,tradesCountShort);
               ticket= MyOrderSend(symbol,OP_SELL, lot,NormalizeDouble(bid,digits), pair.SlipPage, 0,0,ExpertName+ "-" +IntegerToString(tradesCountShort),MagicNumber,0,Green);                       

               if (ticket < 0) {
                  Print("Ошибка открытия следующего колена Short: ", GetLastError());
                  //return (0);
               }
               newOrdersPlacedShort = true;
       
          }
     }


   if (newOrdersPlacedShort) //Разместили перекрывающий короткий ордер, нужно модифицировать другие до нужного тейка
    {
      tradesCountShort = CountTradesShort(symbol,MagicNumber); //заново посчитаем коротких ордеров
      
      averagePrice = 0;
      countShort = 0;
      priceTarget=0;
       
      for (int cnt = OrdersTotal() - 1; cnt >= 0; cnt--) //посчитаем требуемый уровень тейкпрофита
       {
         if (OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES))
         {
         if (OrderSymbol() == symbol && OrderMagicNumber() == MagicNumber)
          {
            if (OrderType() == OP_SELL) 
            {
               averagePrice += OrderOpenPrice() * OrderLots();
               countShort += OrderLots();
            }
          }
         }
      }
      if (countShort > 0)
      {
       averagePrice = NormalizeDouble(averagePrice / countShort, digits);
       priceTarget = averagePrice - pair.TakeProfit * point;
      }
   
       priceTarget=NormalizeDouble(priceTarget,digits);
       if (priceTarget>0)
        {
         for (int cnt = OrdersTotal() - 1; cnt >= 0; cnt--)
          {
            if (OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES))
            {
               if (OrderSymbol() == symbol && OrderMagicNumber() == MagicNumber) 
               {
                 if (!MyOrderModify(OrderTicket(),OrderOpenPrice(),OrderStopLoss(),priceTarget,0,Yellow)) 
                       Print("Ошибка модификации ордера №",OrderTicket()," Short с профитом=",priceTarget,": ", GetLastError());
               }
            }
         }
     }
   }

}
...

Ну а это дополнительная функция для вычисления размера лота для следующего колена.


//-- получить лот для следующего колена
//pair - наша пара, numOfTrades- число открытых ордеров 
double GetNextLot(PAIRS &pair,int numOfTrades) 
{
string symbol=pair.Symbol;
double limitLot=pair.LimitLot;
double koeffMartingale=pair.EnableMartingale ? pair.KoeffMartingale:1; //если мартин дисаблед, просто умножаем
double minLot = MarketInfo(symbol, MODE_MINLOT);
double maxLot = MarketInfo(symbol, MODE_MAXLOT);
double lotStep=MarketInfo(symbol,MODE_LOTSTEP);

double resLot=pair.StartLot* MathPow(koeffMartingale, numOfTrades);
  
  resLot = MathFloor(resLot/lotStep)*lotStep; //нормализуем до разрешенной дробности в зависимости от пары
 
   if(resLot > limitLot) resLot = limitLot; //не более нашего ограничения
   if(resLot < minLot) resLot = minLot; //не менее допустимого брокером
   if(resLot > maxLot) resLot = maxLot; //не более допустимого брокером
 
   return resLot;
   
} 
...

На этом наш мартингейловский скальпер готов к тестированию. Картинки напоминают от экспертов с подобными стратегиями. В принципе, уже можно выставлять на Демо-аккаунт и тестировать

Вот это стейтмент по пятиминуткам EURUSD с 2002.01.01 — 2015.09.01

forex-grail-1.8.eurusd

А этот от такого же таймфрейма но по USDJPY и с 2015.01.01 — 2016.03.07

forex-grail-1.8.usdjpy

Предыдущая часть 6: Скользящие средние.

Следующая часть 8: Демо версия.

Вы можете оставить комментарий, или ссылку на Ваш сайт.

Оставить комментарий

Вы должны быть авторизованы, чтобы разместить комментарий.