Учимся писать советник. Управление деньгами. Часть 3.

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

Учитывайте спреды!

Как известно, спред — это хлеб вашего брокера. Он может меняться и очень часто. Ночью он может расти, а перед важными новостями или в момент нестабильного рынка он обязательно увеличивается! А теперь представьте ситуацию, когда вы протестировали вроде бы прибыльную стратегию на стандартных установках Метатрейдера в 2 пункта. Или даже пусть и по «Текущему» спреду, предоставленному в терминале вашим дилинг центром. Все вроде замечательно. Советник снимает по 10 пунктов прибыли в сделке. Вы ставите его на реал и ложитесь спать. А утром у вас куча сделок закрытых по тейк-профиту, но они все убыточны. А все потому что ночью брокер повысил спред до 20 и вы торговали себе в убыток. Так что же делать?

Для начала найдите информацию размеров Спреда / Свопа для своего брокера. Но не полностью доверяйте ей! Там могут быть ошибки, не будем углубляться в причины их появления. Вот для примера красным обведен спред пары HKDJPY (гонконгский доллар/йена). Конечно, ноль это подозрительно. Давайте проверим.

any-spread

Советую скачать и перед работой на неизвестной паре и/или у нового брокера запустить Monitoring-Spread — индикатор для MetaTrader 4 (для 5 версии наверняка есть аналог). С его помощью вы можете наблюдать и сохранять на диске информацию по изменениям спреда. В нашем случае с HKDJPY индикатор показывает значение 234 что никоим образом не является 0.

В качестве самообразования можете ввести в свой советник/индикатор такую проверку. Думаю лучшим способом будет не запрос от брокера рыночной информации MarketInfo(«HKDJPY»,MODE_SPREAD), а реальная разница между покупкой ASK и продажей BID!

Но вернемся к нашему Граалю. В этой версии 1.2 мы просто добавим возможность торговать переменным лотом, а не жестко фиксированным. Для этого введем выключатель управления деньгами MoneyManagement (истина — лот будет выставляться в % от депозита), и собственно этот самый процент Risk. Общепринято рисковать в одной сделке не более 2-5 % денег, но это когда их много. Для разгона депозита как раз и желательно завысить риск в надежде на удачные сделки.

Далее приведены новые и изменившиеся куски исходного кода программы MQL4.

...
 //+------------------------------------------------------------------+
 //| Forex-Grail.1.2.mq4 |
 //+------------------------------------------------------------------+
 /*
 1.2 Выбор размера лота в % от свободных средств.
 */
extern string startMoneyManagement = "--- начало Управления финансами от 0 до 100 ----";
 extern bool MoneyManagement = false; //Включить управление деньгами
 extern int Risk=10; //Риск в % от депозита
 //MinimalAccountBalance не зависит от MoneyManagement. Но по логике его место в управлении капиталом
 extern double MinimalAccountBalance=0;//Если денег меньше - не торгуем
 extern string endMoneyManagement = "--- конец Управления финансами ----";
 ....

Кстати, в функции CheckMoney обнаружилась ошибка, так как информация MODE_MARGINREQUIRED дает размер необходимых средств для покупки 1 целого лота (зачастую это 100 000 $), но мы же оперируем плечами и значительно меньшими лотами! Ничего не поделаешь, ошибки были есть и будут неотъемлемой частью программирования. И пока читателей моего блога о форексе еще нет или мало, приходится самому их находить.

Итак, новая модификация функции проверки достаточности капитала а также, если MinimalAccountBalance не равен 0, то и остаток на балансе должен быть не менее такого количества валюты депозита.

bool CheckMoney()
{
  double margin= MarketInfo(Symbol(), MODE_MARGINREQUIRED);
  //if ( AccountFreeMargin() < margin) //не совсем корректно, так как мы может открывать не 1 стандартный лот, а меньшую её часть, например 0.01
  //т.е нужно подсчитать сколько средств необходимо для одного минимального лота
   double minLot = MarketInfo(Symbol(),MODE_MINLOT);
  //if ( AccountFreeMargin() < margin*minLot) - вот так если средств не хватает на 1 минимальный, а не стандартный лот. 
  
   if ( AccountFreeMargin() < margin*minLot || AccountBalance() < MinimalAccountBalance) //MinimalAccountBalance для теста или например чтобы хоть что-то оставалось
   {
     if (!AlreadyNotMoneyAlert) //уже сообщали что не хватает денег. Не будем долбить постоянно
     {
     Alert ("Денег нет! AccountFreeMargin= ",AccountFreeMargin()," margin= ",margin);
     AlreadyNotMoneyAlert=true;
     }
     
     return false;
   }
AlreadyNotMoneyAlert=false;
return true; 
}
...

Далее введем функцию расчета величины лота в зависимости от установленного риска. Из всего множества подобных кодов я остановился на примере из учебника, так как на мой взгляд он более правилен и не требует дополнительных переменных, типа числа округления для функции NormalizeDouble, которые пришлось бы менять в зависимости от типов лотов (стандартных 1, мини 0.1, микро 0.01) — ни к чему плодить сущности. И как обычно я привел код к своей привычке программирования: стараться использовать понятные наименования и определенные соглашения их формирования (к сожалению, зачастую я сам от них отхожу, но стараюсь).

...
//----------- Мои пользовательские функции ---------------------------
// В зависимости от управления деньгами определяем размер лота
double GetLot()                                    
{
double marginRequired=MarketInfo(Symbol(),MODE_MARGINREQUIRED);
double minLot=MarketInfo(Symbol(),MODE_MINLOT);
double lotStep=MarketInfo(Symbol(),MODE_LOTSTEP);
double freeMargin=AccountFreeMargin();   
double resLot=0; // в вызывающем коде проверим на 0 - значит денег нет
double percent=Risk;

   if (MoneyManagement)               //включено управление деньгами      
     {     
      if (percent > 100) percent=100;  //процентов не более 100, т.е. максимально допустимый лот
      
      if (percent==0)
         resLot=minLot;  // Если Риск= 0 то лот минимальный
      else                                   
         resLot=MathFloor(freeMargin* percent/100.0/marginRequired/lotStep)*lotStep; //Расчет лота в зависимости от риска                                   

     }
   else                                        //лот фиксированный, проверим хватает ли на него денег
     {                                       
      double needMoney=StartLot*marginRequired;      
      if(needMoney<=AccountFreeMargin())       // денег достаточно на наш лот
         resLot=StartLot;                        
      else                                     // Если денег не хватает, расчитаем на что хватит
         resLot=MathFloor(freeMargin/marginRequired/lotStep)*lotStep;
     }

//окончательная проверка на допустимые пределы
   if (resLot < minLot) resLot=minLot; // Если меньше минимально возможного то скорректируем if (resLot*marginRequired > AccountFreeMargin()) // После перерасчетов еще раз проверим на возможность купить
     {                                         
      resLot=0;   //к сожалению, даже на минимальный лот нет денег                      
     }
     
   return(resLot);              
}
...

Затем в местах использования заменим StartLot на вызов нашей функции GetLot().

...
   if (direction==OP_BUY)
    {
     ticket= MyOrderSend(Symbol(),OP_BUY,GetLot(),NormalizeDouble(Ask,Digits),SlipPage,NormalizeDouble(Bid - StopLoss*Point,Digits),NormalizeDouble(Bid + TakeProfit*Point,Digits),ExpertName,MagicNumber,0,Blue);          
    }
   else if (direction==OP_SELL) 
    {
     ticket= MyOrderSend(Symbol(),OP_SELL, GetLot(),NormalizeDouble(Bid,Digits), SlipPage, NormalizeDouble(Ask + StopLoss*Point,Digits),NormalizeDouble(Ask - TakeProfit*Point,Digits),ExpertName,MagicNumber,0,Green);                       
    }
...	

Так как GetLot() при нехватке денег может возвращать 0, то чтобы не посылать ошибочную команду на сервер дилера, проконтролируем такую ситуацию и сообщим о ней. Вот тут и пригодилось вынесение стандартной функции OrderSend в нашу обертку.

...
int MyOrderSend(string symbol,int cmd,double volume,double price,int slippage,double stoploss,double takeprofit,
                string comment=NULL,int magic=0,datetime expiration=0, color arrow_color=clrNONE)
{
int ticket=-1;
if (volume<=0) //при нехватке денег может сюда попасть 0, просто проигнорируем такой ордер,
   {
   Print("В MyOrderSend(",GetNameOP(cmd),") передан неправильный лот :",volume); //сообщим об этом в журнал
   return ticket;
   }
...

Для более удобного информирования я воспользовался функцией от Кима для перевода торговых команд 0…6 в человеческий язык. За это отвечает GetNameOP(cmd).

...
//+----------------------------------------------------------------------------+
//|  Автор    : Ким Игорь В. aka KimIV,  http://www.kimiv.ru                   |
//+----------------------------------------------------------------------------+
//|  Версия   : 01.09.2005                                                     |
//|  Описание : Возвращает наименование торговой операции                      |
//+----------------------------------------------------------------------------+
//|  Параметры:                                                                |
//|    op - идентификатор торговой операции                                    |
//+----------------------------------------------------------------------------+
string GetNameOP(int op) {
  switch (op) {
    case OP_BUY      : return("Buy");
    case OP_SELL     : return("Sell");
    case OP_BUYLIMIT : return("Buy Limit");
    case OP_SELLLIMIT: return("Sell Limit");
    case OP_BUYSTOP  : return("Buy Stop");
    case OP_SELLSTOP : return("Sell Stop");
    default          : return("Unknown Operation");
  }
}
...

Ну вот на сегодня и все. Скомпилируйте советник и побалуйтесь настройками риска, посмотрите, надолго ли хватит вашего депозита 🙂

Предыдущая часть 2: Трейлинг стоп прибыльной позиции.
Продолжение создания советника: Отображаем спреды.

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

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

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