Сказки торговых роботов: лучше меньше - да лучше?

Roman Zamozhnyy | 11 апреля, 2014

Чтобы решить проблему, ее нужно сначала сформулировать. Если я считаю, что нашел решение, его нужно проверить в деле, чтобы убедиться в своей правоте.

Я знаю только один способ проверки — на собственных деньгах.

Д.Ливермор


Пролог

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

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

Copyright (c) 2012-2014 Roman Rich
Euro vs US Dollar, Box-20, Reverse-3


    1.4588 | \.....\.................................................................... | 1.4588
    1.4521 | X\....X\................................................................... | 1.4521
    1.4454 | XO\.\.XO\.................................................................. | 1.4454
    1.4388 | XOX\X\XO.\................................................................. | 1.4388
    1.4322 | XOXOXOXO..\................................................................ | 1.4322
    1.4256 | XOXOXOXO...\....\.......................................................... | 1.4256
    1.4191 | XOXO/OXO....\...X\......................................................... | 1.4191
    1.4125 | XOX/.O/O.....\..XO\........................................................ | 1.4125
    1.4060 | XO/../.O......\.XO.\....................................................... | 1.4060
    1.3996 | ./.....O.......\XO..\...................................................... | 1.3996
    1.3932 | .......OX.......XO...\....................................................X | 1.3932
    1.3868 | .......OXO..X.X.XOX...\.................................................X.X | 1.3868
    1.3804 | .......OXO..XOXOXOXOX..\..............................................X.XOX | 1.3804
    1.3740 | .......OXO..XOXOXOXOXO..\.................................\...........XOXOX | 1.3740
    1.3677 | .......OXOX.XO.O.OXOXO...\................................X\..........XOXOX | 1.3677
    1.3614 | .......OXOXOX....O.OXO....\...............................XO\.........XOXOX | 1.3614
    1.3552 | .......O.OXOX...../OXO.....\..............................XO.\........XOXOX | 1.3552
    1.3490 | .........OXOX..../.O.OX.....\.............................XO..\.......XOXO. | 1.3490
    1.3428 | .........OXOX.../....OXO.....\X.\.........................XO...\\...X.XOX.. | 1.3428
    1.3366 | .........O.OX../.....OXO......XOX\........................XO....X\..XOXOX.. | 1.3366
    1.3305 | ...........OX./......OXO....X.XOXO\.....................X.XO....XO\.XOXO... | 1.3305
    1.3243 | ...........OX/.......O.O....XOXOXOX\....................XOXO....XO.\XOX.../ | 1.3243
    1.3183 | ...........O/..........OX...XOXOXOXO\...................XOXOX.X.XOX.XOX../. | 1.3183
    1.3122 | .........../...........OXO..XOXOXOXO.\..........X...X.X.XOXOXOXOXOXOXO../.. | 1.3122
    1.3062 | .......................OXOX.XOXO.OXO..\.........XOX.XOXOXOXOXOXOXOXOX../... | 1.3062
    1.3002 | .......................O.OXOXO...O/O...\........XOXOXOXOXO.OXO.OXOXO../.... | 1.3002
    1.2942 | .........................OXOX..../.O....\.......XOXOXOXOX..OX..OXOX../..... | 1.2942
    1.2882 | .........................O.OX.../..O.....\......XOXO.OXO...OX..OXOX./...... | 1.2882
    1.2823 | ...........................OX../...OX.....\.....XO...OX.../OX..O/OX/....... | 1.2823
    1.2764 | ...........................OX./....OXO.....\....X....OX../.O.../.O/........ | 1.2764
    1.2706 | ...........................OX/.....OXO..X...\...X....O../......../......... | 1.2706
    1.2647 | ...........................O/......O.OX.XOX..\..X....../................... | 1.2647
    1.2589 | .........................../.........OXOXOXO..\.X...../.................... | 1.2589
    1.2531 | .....................................OXOXOXO...\X..../..................... | 1.2531
    1.2474 | .....................................OXO.OXO....X.../...................... | 1.2474
    1.2417 | .....................................OX..O.O..X.X../....................... | 1.2417
    1.2359 | .....................................OX....OX.XOX./........................ | 1.2359
    1.2303 | .....................................O.....OXOXOX/......................... | 1.2303
    1.2246 | ...........................................OXOXO/.......................... | 1.2246
    1.2190 | ...........................................OXO./........................... | 1.2190
    1.2134 | ...........................................OX.............................. | 1.2134
    1.2078 | ...........................................O............................... | 1.2078
    1.2023 | ........................................................................... | 1.2023

             222222222222222222222222222222222222222222222222222222222222222222222222222
             000000000000000000000000000000000000000000000000000000000000000000000000000
             111111111111111111111111111111111111111111111111111111111111111111111111111
             111111111111111111111111112222222222222222222222222222222333333333333333344
             ...........................................................................
             000000000001111111111111110000000000000000000000011111111000000000000011100
             788888899990000001111112221122233445566666677888900001222123444567778901213
             ...........................................................................
             200011211220111220011231220101212121201112222001100010001002123110112020231
             658801925683489071404504193396436668111288937260415979579417630739120547713
                                                                                        
             000100001012111111110111111100112010210001111101101101011111111101011101110
             910501876933613095500253237788652909250001557626626824655375907538165785367
             :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
             550433251023230204310404232105354323532031240033315125241340044324523153453
             000000000000000000000000000000000000000000000000000000000000000000000000000

Не стану утверждать, что торговые возможности явно видны на этом графике, предложу лишь проверить две торговые гипотезы:

Торгуйте по тренду — покупайте на бычьем рынке и продавайте на медвежьем.

Торгуйте со стоп-ордерами, определенными до входа в рынок.

Дайте прибыли расти.

Закрывайте сделки, которые показывают потери (хорошие сделки, обычно, сразу же показывают прибыль).


А каким объемом входим?

Обратите внимание на приведенные выше цитаты классика биржевых спекуляций: торгуйте со стоп-ордерами. Я предпочитаю входить в рынок таким объемом, чтобы при срабатывании ордера Стоп Лосс потери баланса составляли не более приемлемого для меня процента от этого самого баланса (то, что Ральф Винс в своей Математике управления капиталом назвал оптимальное F). Риск потери с одной сделки - отлично оптимизируемая переменная (в приведенном ниже коде - opt_f).

Таким образом, имеем функцию установки ордера на покупку/продажу с механизмом расчета объема, зависящего от приемлемого для вас риска на одну сделку:

//+------------------------------------------------------------------+
//| Функция выставления ордера с предварительно подсчитанным объемом |
//+------------------------------------------------------------------+
void PlaceOrder()
  {
//--- Переменные для расчета лота
   uint digits_2_lot=(uint)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   double trade_risk=AccountInfoDouble(ACCOUNT_EQUITY)*opt_f;
   double one_tick_loss_min_lot=SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_VALUE_LOSS)*SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
//--- Заполняем основные поля запроса
   trade_request.magic=magic;
   trade_request.symbol=symbol;
   trade_request.action=TRADE_ACTION_PENDING;
   trade_request.tp=NULL;
   trade_request.comment=NULL;
   trade_request.type_filling=NULL;
   trade_request.stoplimit=NULL;
   trade_request.type_time=NULL;
   trade_request.expiration=NULL;
   if(is_const_lot==true)
     {
      order_vol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
     }
   else
     {
      order_vol=trade_risk/(MathAbs(trade_request.price-trade_request.sl)*MathPow(10,digits_2_lot)*one_tick_loss_min_lot)*SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
      order_vol=MathMax(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN));
      if(SymbolInfoDouble(symbol,SYMBOL_VOLUME_LIMIT)!=0) order_vol=MathMin(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_LIMIT));
      order_vol=NormalizeDouble(order_vol,(int)MathAbs(MathLog10(SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP))));
     }
//--- Ставим ордер
   while(order_vol>0)
     {
      trade_request.volume=MathMin(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX));
      if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order);
      order_vol=order_vol-SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
     };
   ticket=trade_result.order;
  };


А по какому критерию оптимизируем?

Реально критериев оптимизации всего два: либо минимизация просадки при заданном уровне доходности, либо максимизация баланса при заданном уровне просадки. Я предпочитаю оптимизировать по второму критерию:

//+------------------------------------------------------------------+
//| Результат работы стратегии в режиме тестирования                 |
//+------------------------------------------------------------------+
double OnTester()
  {
   if(TesterStatistics(STAT_EQUITY_DDREL_PERCENT)>(risk*100))
      return(0);
   else
      return(NormalizeDouble(TesterStatistics(STAT_PROFIT),(uint)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  };

где risk - уровень просадки, выше которого стратегия для меня считается неприемлемой. Эта также одна из оптимизируемых переменных.


А когда необходимо оптимизировать повторно?

Кто-то проводит повторную оптимизацию через интервалы времени (например, раз в неделю, в месяц), кто-то - через интервалы сделок (через 50 сделок, через 100), кто-то - как рынок поменяется. Реально же необходимость в повторной оптимизации диктуется лишь принятым критерием оптимизации в предыдущем пункте. Просела система ниже максимально допустимого параметра risk - проводим повторную оптимизацию, не просела - не трогаем. Для себя считаю неприемлемой просадку больше 10%. Таким образом, если просадка в процессе реальной работы системы превышает это значение - оптимизирую повторно.


А оптимизировать сразу все?

К сожалению, в тестере стратегий пока не реализована возможность оптимизировать внешние переменные для режима "Все символы, выбранные в окне 'Обзор рынка'". Поэтому выбор рыночного инструмента наряду с прочими оптимизируемыми внешними переменными произведем следующим нехитрым способом:

//+------------------------------------------------------------------+
//| Перечисление символов                                            |
//+------------------------------------------------------------------+
enum  SYMBOLS
  {
   AA=1,
   AIG,
   AXP,
   BA,
   C,
   CAT,
   DD,
   DIS,
   GE,
   HD,
   HON,
   HPQ,
   IBM,
   IP,
   INTC,
   JNJ,
   JPM,
   KO,
   MCD,
   MMM,
   MO,
   MRK,
   MSFT,
   PFE,
   PG,
   QQQ,
   T,
   SPY,
   UTX,
   VZ,
   WMT,
   XOM
  };
//+------------------------------------------------------------------+
//| Функция выбора символа                                           |
//+------------------------------------------------------------------+
void  SelectSymbol()
  {
   switch(selected_symbol)
     {
      case  1: symbol="#AA";   break;
      case  2: symbol="#AIG";  break;
      case  3: symbol="#AXP";  break;
      case  4: symbol="#BA";   break;
      case  5: symbol="#C";    break;
      case  6: symbol="#CAT";  break;
      case  7: symbol="#DD";   break;
      case  8: symbol="#DIS";  break;
      case  9: symbol="#GE";   break;
      case 10: symbol="#HD";   break;
      case 11: symbol="#HON";  break;
      case 12: symbol="#HPQ";  break;
      case 13: symbol="#IBM";  break;
      case 14: symbol="#IP";   break;
      case 15: symbol="#INTC"; break;
      case 16: symbol="#JNJ";  break;
      case 17: symbol="#JPM";  break;
      case 18: symbol="#KO";   break;
      case 19: symbol="#MCD";  break;
      case 20: symbol="#MMM";  break;
      case 21: symbol="#MO";   break;
      case 22: symbol="#MRK";  break;
      case 23: symbol="#MSFT"; break;
      case 24: symbol="#PFE";  break;
      case 25: symbol="#PG";   break;
      case 26: symbol="#QQQ";  break;
      case 27: symbol="#T";    break;
      case 28: symbol="#SPY";  break;
      case 29: symbol="#UTX";  break;
      case 30: symbol="#VZ";   break;
      case 31: symbol="#WMT";  break;
      case 32: symbol="#XOM";  break;
      default: symbol="#SPY";  break;
     };
  };

При необходимости вы можете добавлять в перечисление и функцию выбора символа (которую вызовем в OnInit()) нужные вам инструменты.


Что за робот у нас получился?

Обработчик тиков:

//+------------------------------------------------------------------+
//| Типичный обработчик тиков OnTick()                               |
//|     График строим только по сформированным барам и сначала       |
//|     проверяем, не новый ли сейчас бар?                           |
//|     Если бар новый, то при наличии позиций проверяем,            |
//|     не нужно ли передвинуть стоп-лосс,                           |
//|     а при отсутствии позиций проверяем,                          |
//|     может есть условия для открытия сделки?                      |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Если новый бар
   if(IsNewBar()==true)
     {
      RecalcIndicators();
      //--- Режим тестера/оптимизатора?
      if((MQLInfoInteger(MQL_TESTER)==true) || (MQLInfoInteger(MQL_OPTIMIZATION)==true))
        {
         //--- Это уже период тестирования?
         if(cur_bar_time_dig[0]>begin_of_test)
           {
            //--- Если есть открытая по символу позиция
            if(PositionSelect(symbol)==true)
               //--- проверяем, не нужно-ли передвинуть SL, и если нужно - передвигаем
               TrailCondition();
            //--- а если позиций нет
            else
            //--- проверяем, нужно-ли открыть позицию, и если нужно - открываем
               TradeCondition();
           }
        }
      else
        {
         //--- Если есть открытая по символу позиция
         if(PositionSelect(symbol)==true)
            //--- проверяем, не нужно-ли передвинуть SL, и если нужно - передвигаем
            TrailCondition();
         //--- а если позиций нет
         else
         //--- проверяем, нужно-ли открыть позицию, и если нужно - открываем
            TradeCondition();
        }

     };
  };

Для стратегии №1 "покупаем над линией поддержки стоп-ордером выше максимума предыдущей колонки "Х", продаем под линией сопротивления стоп-ордером ниже минимума предыдущей колонки "О", скользящий стоп - на уровне разворота":

//+------------------------------------------------------------------+
//| Функция проверки торговых условий для открытия сделки            |
//+------------------------------------------------------------------+
void TradeCondition()
  {
   if(order_col_number!=column_count)
      //--- Завалялись какие-то ордера по символу?
     {
      if(OrdersTotal()>0)
        {
         //--- Удалить их!
         for(int loc_count_1=0;loc_count_1<OrdersTotal();loc_count_1++)
           {
            ticket=OrderGetTicket(loc_count_1);
            if(!OrderSelect(ticket)) Print("Failed to select order #",ticket);
            if(OrderGetString(ORDER_SYMBOL)==symbol)
              {
               trade_request.order=ticket;
               trade_request.action=TRADE_ACTION_REMOVE;
               if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order);
              };
           };
         order_col_number=column_count;
         return;
        }
      else
        {
         order_col_number=column_count;
         return;
        }
     }
   else
      if((MathPow(10,pnf[column_count-1].resist_price)<SymbolInfoDouble(symbol,SYMBOL_ASK)) && 
         (pnf[column_count-1].column_type=='X') && 
         (pnf[column_count-1].max_column_price<=pnf[column_count-3].max_column_price))
        {
         //--- Условия для BUY выполнены, смотрим, а нет ли отложенных ордеров BUY по символу с нужной ценой?
         trade_request.price=NormalizeDouble(MathPow(10,pnf[column_count-3].max_column_price+double_box),digit_2_orders);
         trade_request.sl=NormalizeDouble(MathPow(10,pnf[column_count-3].max_column_price-(reverse-1)*double_box),digit_2_orders);
         trade_request.type=ORDER_TYPE_BUY_STOP;
         if(OrderSelect(ticket)==false)
            //--- Нет, отложенных ордеров нет - размещаем ордер
           {
            PlaceOrder();
            order_col_number=column_count;
           }
         else
         //--- или отложенный ордер есть
           {
            //--- а что за тип и цена у отложенного ордера?
            if((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP) || 
               ((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP) && (OrderGetDouble(ORDER_PRICE_OPEN)!=trade_request.price)))
              {
               //--- Тип не тот или цена отличается - закрываем ордер
               trade_request.order=ticket;
               trade_request.action=TRADE_ACTION_REMOVE;
               if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order);
               //--- и открываем с нужной ценой
               PlaceOrder();
               order_col_number=column_count;
              };
           };
         return;
        }
   else
      if((MathPow(10,pnf[column_count-1].resist_price)>SymbolInfoDouble(symbol,SYMBOL_ASK)) && 
         (pnf[column_count-1].column_type=='O') && 
         (pnf[column_count-1].min_column_price>=pnf[column_count-3].min_column_price))
        {
         //--- Условия для SELL выполнены, смотрим, а нет ли отложенных ордеров SELL по символу с нужной ценой?
         trade_request.price=NormalizeDouble(MathPow(10,pnf[column_count-3].min_column_price-double_box),digit_2_orders);
         trade_request.sl=NormalizeDouble(MathPow(10,pnf[column_count-3].min_column_price+(reverse-1)*double_box),digit_2_orders);
         trade_request.type=ORDER_TYPE_SELL_STOP;
         if(OrderSelect(ticket)==false)
            //--- Нет, отложенных ордеров нет - размещаем ордер
           {
            PlaceOrder();
            order_col_number=column_count;
           }
         else
         //--- или отложенный ордер есть
           {
            //--- а что за тип и цена у отложенного ордера?
            if((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP) || 
               ((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP) && (OrderGetDouble(ORDER_PRICE_OPEN)!=trade_request.price)))
              {
               //--- Тип не тот или цена отличается - закрываем ордер
               trade_request.order=ticket;
               trade_request.action=TRADE_ACTION_REMOVE;
               if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order);
               //--- и открываем с нужной ценой
               PlaceOrder();
               order_col_number=column_count;
              };
           };
         return;
        }
   else
      return;
  };
//+------------------------------------------------------------------+
//| Функция проверки условия для перемещения стоп-лосса              |
//+------------------------------------------------------------------+
void TrailCondition()
  {
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
      trade_request.sl=NormalizeDouble(MathPow(10,pnf[column_count-1].max_column_price-reverse*double_box),digit_2_orders);
   else
      trade_request.sl=NormalizeDouble(MathPow(10,pnf[column_count-1].min_column_price+reverse*double_box),digit_2_orders);
   if(PositionGetDouble(POSITION_SL)!=trade_request.sl)
      PlaceTrailOrder();
  };

Для стратегии №2 "покупаем при пробое линии сопротивления, продаем при пробое линии поддержки, стоп-лосс - на уровне разворота, скользящий стоп - по трендовой линии":

//+------------------------------------------------------------------+
//| Функция проверки торговых условий для открытия сделки            |
//+------------------------------------------------------------------+
void TradeCondition()
  {
   if(order_col_number!=column_count)
      //--- Завалялись какие-то ордера по символу?
     {
      if(OrdersTotal()>0)
        {
         //--- Удалить их!
         for(int loc_count_1=0;loc_count_1<OrdersTotal();loc_count_1++)
           {
            ticket=OrderGetTicket(loc_count_1);
            if(!OrderSelect(ticket)) Print("Failed to select order #",ticket);
            if(OrderGetString(ORDER_SYMBOL)==symbol)
              {
               trade_request.order=ticket;
               trade_request.action=TRADE_ACTION_REMOVE;
               if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order);
              };
           };
         order_col_number=column_count;
         return;
        }
      else
        {
         order_col_number=column_count;
         return;
        }
     }
   else
   if(MathPow(10,pnf[column_count-1].resist_price)>SymbolInfoDouble(symbol,SYMBOL_ASK))
     {
      //--- Условия для BUY выполнены, смотрим, а нет ли отложенных ордеров BUY по символу с нужной ценой?
      trade_request.price=NormalizeDouble(MathPow(10,pnf[column_count-1].resist_price),digit_2_orders);
      trade_request.sl=NormalizeDouble(MathPow(10,pnf[column_count-1].resist_price-(reverse-1)*double_box),digit_2_orders);
      trade_request.type=ORDER_TYPE_BUY_STOP;
      if(OrderSelect(ticket)==false)
         //--- Нет, отложенных ордеров нет - размещаем ордер
        {
         PlaceOrder();
         order_col_number=column_count;
        }
      else
      //--- или отложенный ордер есть
        {
         //--- а что за тип и цена у отложенного ордера?
         if((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP) || 
            ((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP) && (OrderGetDouble(ORDER_PRICE_OPEN)!=trade_request.price)))
           {
            //--- Тип не тот или цена отличается - закрываем ордер
            trade_request.order=ticket;
            trade_request.action=TRADE_ACTION_REMOVE;
            if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order);
            //--- и открываем с нужной ценой
            PlaceOrder();
            order_col_number=column_count;
           };
        };
      return;
     }
   else
   if(MathPow(10,pnf[column_count-1].resist_price)<SymbolInfoDouble(symbol,SYMBOL_ASK))
     {
      //--- Условия для SELL выполнены, смотрим, а нет ли отложенных ордеров SELL по символу с нужной ценой?
      trade_request.price=NormalizeDouble(MathPow(10,pnf[column_count-1].supp_price),digit_2_orders);
      trade_request.sl=NormalizeDouble(MathPow(10,pnf[column_count-1].supp_price+(reverse-1)*double_box),digit_2_orders);
      trade_request.type=ORDER_TYPE_SELL_STOP;
      if(OrderSelect(ticket)==false)
         //--- Нет, отложенных ордеров нет - размещаем ордер
        {
         PlaceOrder();
         order_col_number=column_count;
        }
      else
      //--- или отложенный ордер есть
        {
         //--- а что за тип и цена у отложенного ордера?
         if((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP) || 
            ((OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP) && (OrderGetDouble(ORDER_PRICE_OPEN)!=trade_request.price)))
           {
            //--- Тип не тот или цена отличается - закрываем ордер
            trade_request.order=ticket;
            trade_request.action=TRADE_ACTION_REMOVE;
            if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order);
            //--- и открываем с нужной ценой
            PlaceOrder();
            order_col_number=column_count;
           };
        };
      return;
     }
   else
      return;
  };
//+------------------------------------------------------------------+
//| Функция проверки условия для перемещения стоп-лосса              |
//+------------------------------------------------------------------+
void TrailCondition()
  {
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
      trade_request.sl=NormalizeDouble(MathMax(SymbolInfoDouble(symbol,SYMBOL_ASK),MathPow(10,pnf[column_count-1].max_column_price-reverse*double_box)),digit_2_orders);
   else
      trade_request.sl=NormalizeDouble(MathMin(SymbolInfoDouble(symbol,SYMBOL_BID),MathPow(10,pnf[column_count-1].min_column_price+reverse*double_box)),digit_2_orders);
   if(PositionGetDouble(POSITION_SL)!=trade_request.sl)
      PlaceTrailOrder();
  };

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

Код вспомогательных функций не привожу в статье, а прилагаю во вложениях. Кодом построения самого графика ввиду его объемности также засорять статью не буду, он с комментариями во вложении.


Результаты роботрейдинга

Для оптимизации я подготовил два набора рыночных инструментов в файлах symbol_list_1.mhq и symbol_list_2.mhq: валютные пары и контракты на разницу по акциям, входящим в индекс Доу.

Окно настроек:

Окно параметров в тестере в первом случае для нашей стратегии имеет следующий вид:

Обратите внимание на строку "Начало тестирования". Для анализа и принятия решений роботу нужно хотя-бы несколько колонок графика, а при выборе размера бокса от 50 пипсов и выше, годичной истории зачастую не хватает даже для одной колонки. Поэтому для графиков с размером бокса от 50 пипсов я рекомендую задавать значение интервала порядка трех лет и выше от начала работы робота, а само начало работы робота задавать в окне параметров строкой "Начало тестирования". В нашем примере для тестирования по инструменту с размером бокса в 100 пипсов начиная с 01.01.2012 г. на вкладке "Настройки" указываем интервал с 01.01.2009 г., а на вкладке "Параметры" - с 01.01.2012 г.

Значение false для параметра "Торгуем минимальным лотом?" говорит о том, что размер лота у нас переменный, зависит от баланса и переменной "Риск на сделку, %" (в нашем случае 1% на сделку, но его можно также оптимизировать). "Допустимая просадка, %" - тот самый критерий оптимизации из функции OnTester(). Для примера я выбрал для оптимизации лишь две переменные: торговый инструмент "Символ" и "Размер бокса в пипсах".

Оптимизацию проведем на данных 2012-2013 года. Сам робот лучше всего прикрепить к графику EURUSD, как символу с наибольшим покрытием тиками. В таблице ниже приведу полный отчет для тестирования по валютным парам для размера бокса 10 по первой стратегии:

Pass Result Profit Expected Payoff Profit Factor Recovery Factor Sharpe Ratio Custom Equity DD % Trades selected_symbol box
0,00 0,00 -1 002,12 -18,91 0,54 -0,79 -0,24 0,00 12,67 53,00 AUDCAD 10,00
1,00 886,56 886,56 14,53 1,40 1,52 0,13 886,56 5,76 61,00 AUDCHF 10,00
2,00 0,00 -1 451,63 -10,60 0,77 -0,70 -0,09 0,00 19,92 137,00 AUDJPY 10,00
3,00 -647,66 -647,66 -17,50 0,57 -0,68 -0,24 -647,66 9,46 37,00 AUDNZD 10,00
4,00 -269,22 -269,22 -3,17 0,92 -0,26 -0,03 -269,22 9,78 85,00 AUDUSD 10,00
5,00 0,00 -811,44 -13,52 0,72 -0,64 -0,14 0,00 12,20 60,00 CADCHF 10,00
6,00 0,00 1 686,34 16,53 1,36 1,17 0,12 0,00 11,78 102,00 CHFJPY 10,00
7,00 356,68 356,68 5,66 1,13 0,40 0,06 356,68 8,04 63,00 EURAUD 10,00
8,00 0,00 -1 437,91 -25,68 0,53 -0,92 -0,25 0,00 15,47 56,00 EURCAD 10,00
9,00 0,00 -886,66 -46,67 0,34 -0,74 -0,46 0,00 11,56 19,00 EURCHF 10,00
10,00 0,00 -789,59 -21,93 0,54 -0,75 -0,26 0,00 10,34 36,00 EURGBP 10,00
11,00 0,00 3 074,86 28,47 1,62 1,72 0,20 0,00 12,67 108,00 EURJPY 10,00
12,00 0,00 -1 621,85 -19,78 0,55 -0,97 -0,25 0,00 16,75 82,00 EURNZD 10,00
13,00 152,73 152,73 2,88 1,07 0,21 0,03 152,73 6,90 53,00 EURUSD 10,00
14,00 0,00 -1 058,85 -14,50 0,65 -0,66 -0,16 0,00 15,87 73,00 GBPAUD 10,00
15,00 0,00 -1 343,47 -25,35 0,43 -0,64 -0,34 0,00 20,90 53,00 GBPCAD 10,00
16,00 0,00 -2 607,22 -44,19 0,27 -0,95 -0,59 0,00 27,15 59,00 GBPCHF 10,00
17,00 0,00 1 160,54 11,72 1,27 0,81 0,10 0,00 12,30 99,00 GBPJPY 10,00
18,00 0,00 -1 249,91 -14,70 0,69 -0,85 -0,15 0,00 14,41 85,00 GBPNZD 10,00
19,00 208,94 208,94 5,36 1,12 0,25 0,05 208,94 7,81 39,00 GBPUSD 10,00
20,00 0,00 -2 137,68 -21,17 0,53 -0,79 -0,24 0,00 25,62 101,00 NZDUSD 10,00
21,00 0,00 -1 766,80 -38,41 0,30 -0,97 -0,53 0,00 18,10 46,00 USDCAD 10,00
22,00 -824,69 -824,69 -11,95 0,73 -0,90 -0,13 -824,69 9,11 69,00 USDCHF 10,00
23,00 2 166,53 2 166,53 26,10 1,58 2,40 0,18 2 166,53 7,13 83,00 USDJPY 10,00

2 029,87 -10 213,52




13,40 1 659,00

и сводную таблицу для разных инструментов и размеров бокса:

Стратегия Инструменты Размер бокса Trades Equity DD % Profit Result Прогноз баланса
1 Валюты 10 1 659 13 -10 214 2 030 2 030
1 Валюты 20 400 5 1 638 2 484 2 484
1 Акции 50 350 4 7 599 7 599 15 199
1 Акции 100 81 2 4 415 4 415 17 659
2 Валюты 10 338 20 -4 055 138 138
2 Валюты 20 116 8 4 687 3 986 3 986
2 Акции 50 65 6 6 770 9 244 9 244
2 Акции 100 12 1 -332 -332 -5 315

Что мы видим?

Существуют круглые дураки, которые все и всегда делают неверно.

И существуют дураки с Уолл-Стрит, которые считают, что торговать надо всегда.

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

Видим неожиданную для многих картину: депозит с большей вероятностью будет выше при меньшем числе сделок. Если бы мы без всякой оптимизации два года назад накинули на акции наш эксперт и задали размер бокса 100, а риск на сделку 1%, то за два года робот совершил бы всего 81 сделку (в среднем за год по одному инструменту 1,25 сделки), наш депо вырос бы на 44%, и при этом в среднем просадка по эквити была бы чуть выше 2%. Принимая для себя допустимую просадку в 10%, мы бы могли рисковать на сделку в 4% и за два года депозит бы прибавил 177%, доходность - под 90% годовых в долларах США!


Эпилог

Курс никогда не бывает слишком высоким, чтобы начать покупать, и никогда не бывает слишком низким, чтобы начать продавать.

Большие деньги делаются не в раздумьях, а в ожидании.

Предложенные к рассмотрению стратегии могут быть модифицированы и они покажут даже большую доходность при просадке не выше 10%. Не пытайтесь мельтешить, торговать часто, лучше найдите брокера, который предоставляет не просто "стандартный набор" инструментов из двух десятков валютных пар и трех десятков акций, а хотя бы сотни три-четыре инструментов (акций, фьючерсов). С большей вероятностью инструменты не будут коррелированы и ваш депозит будет в большей безопасности. И да, акции почему-то показывают лучшие результаты, чем валютные пары.


P.S. (на правах рекламы)

В Маркете я предлагаю скрипт PnF Chartist для построения графиков крестиков-ноликов в текстовых файлах из котировок терминалов МТ4, МТ5 или Yahoo finance. Используйте его для визуального поиска паттернов поведения цен, ибо нет лучше тестера/оптимизатора, чем своя голова, а найдя закономерности - воспользуйтесь шаблонами экспертов из статьи для проверки в боевых условиях ваших задумок.