English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Доктор Трейдлав, или Как я перестал беспокоиться и написал самообучающийся эксперт

Доктор Трейдлав, или Как я перестал беспокоиться и написал самообучающийся эксперт

MetaTrader 5Трейдинг | 27 октября 2011, 13:05
11 187 6
Roman Zamozhnyy
Roman Zamozhnyy

Замысел

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

Можно ли возложить на сам эксперт принятие решения о переоптимизации и непосредственно саму переоптимизацию, не прерывая при этом, естественно, работы самого эксперта?

Один из вариантов решения этой проблемы предложил нам Quantum в своей статье "Адаптивные торговые системы и их использование в терминале MetaTrader 5", в которой рассмотрел использование наряду с реальной торговой системой нескольких (неограниченное количество) виртуальных торговых стратегий, из которых и происходил выбор стратегии, принесшей к текущему моменту наибольшую прибыль. Решение о смене торговой стратегии принимается по прошествии какого-то фиксированного значения баров.

Я же предлагаю воспользоваться кодом генетического алгоритма (ГА), описанным joo в статье "Генетические алгоритмы - это просто!". Рассмотрим реализацию такого эксперта (в качестве одного из примеров - эксперт, выставленный на чемпионат Automated Trading Championship 2011).


Трудимся

Итак, определимся, что должен уметь делать эксперт. Во-первых, само собой разумеется, торговать по выбранной стратегии. Во-вторых, принимать решение: пришло ли время переоптимизироваться (провести новую оптимизацию входных параметров). И в-третьих, используя ГА - переоптимизироваться. На первых порах рассмотрим простейшую переоптимизацию - есть стратегия и мы просто подбираем новые параметры. В дальнейшем задумаемся над тем, можно ли, и если можно - то как, с помощью ГА выбрать другую стратегию для изменившихся рыночных условий.

Также, для облегчения моделирования в fitness-функции, примем решение о торговле только на сформированных барах по одному инструменту. Доливок и частичных закрытий также не будет. Тех, кто предпочитает использовать жесткие стопы и тейки, а также тралить стопы, отсылаю к статье "Алгоритм генерации тиков в тестере стратегий терминала MetaTrader 5" для реализации в fitness-функции проверок на срабатывание ордеров Stop Loss и Take Profit. Остановлюсь подробнее на этой хитрой фразе:

Я для себя моделирую в фитнесс-функции режим тестирования, называемый в тестере "Только цены открытия". НО! Это не означает, что это единственно возможное моделирование процесса тестирования в фитнесс-функции. Более дотошные люди, возможно, захотят реализовать в фитнесс-функции тестирование в режиме "Все тики". И вот для того, чтобы они не изобретали велосипед, не выдумывали сами эти "все тики", я рекомендую обратить их внимание на уже готовый алгоритм - компании MetaQuotes. Т.е., прочитав эту статью, человек в фитнесс-функции может смоделировать режим "Все тики", что является необходимым условием для корректного моделирования в ФФ и стопов с тейками.

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

//+------------------------------------------------------------------+
//| Определяем, не наступил ли новый бар                             |
//+------------------------------------------------------------------+
bool isNewBars()
  {
   CopyTime(s,tf,0,1,curBT);
   TimeToStruct(curBT[0],curT);
   if(tf==PERIOD_M1||
      tf==PERIOD_M2||
      tf==PERIOD_M3||
      tf==PERIOD_M4||
      tf==PERIOD_M5||
      tf==PERIOD_M6||
      tf==PERIOD_M10||
      tf==PERIOD_M12||
      tf==PERIOD_M15||
      tf==PERIOD_M20||
      tf==PERIOD_M30)
      if(curT.min!=prevT.min)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_H1||
      tf==PERIOD_H2||
      tf==PERIOD_H3||
      tf==PERIOD_H4||
      tf==PERIOD_H6||
      tf==PERIOD_H8||
      tf==PERIOD_M12)
      if(curT.hour!=prevT.hour)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_D1||
      tf==PERIOD_W1)
      if(curT.day!=prevT.day)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_MN1)
      if(curT.mon!=prevT.mon)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   return(false);
  }
//+------------------------------------------------------------------+
//|  ClosePosition                                                   |
//+------------------------------------------------------------------+
void ClosePosition()
  {
   request.action=TRADE_ACTION_DEAL;
   request.symbol=PositionGetSymbol(0);
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) request.type=ORDER_TYPE_SELL; 
   else request.type=ORDER_TYPE_BUY;
   request.type_filling=ORDER_FILLING_FOK;
   if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
      SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      request.sl=NULL;
      request.tp=NULL;
      request.deviation=100;
     }
   while(PositionsTotal()>0)
     {
      request.volume=NormalizeDouble(MathMin(PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(PositionGetSymbol(0),SYMBOL_VOLUME_MAX)),2);
      if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
     }
  }
//+------------------------------------------------------------------+
//|  OpenPosition                                                    |
//+------------------------------------------------------------------+
void OpenPosition()
  {
   double vol;
   request.action=TRADE_ACTION_DEAL;
   request.symbol=s;
   request.type_filling=ORDER_FILLING_FOK;
   if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
      SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      request.sl=NULL;
      request.tp=NULL;
      request.deviation=100;
     }
   vol=MathFloor(AccountInfoDouble(ACCOUNT_FREEMARGIN)*optF*AccountInfoInteger(ACCOUNT_LEVERAGE)
       /(SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP)))*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP);
   vol=MathMax(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MIN));
   vol=MathMin(vol,GetPossibleLots()*0.95);
   if(SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)!=0) vol=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)),2);
   request.volume=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2);
   while(PositionSelect(s)==false)
     {
      if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID); 
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
      PositionSelect(s);
     }
   while(PositionGetDouble(POSITION_VOLUME)<vol)
     {
      request.volume=NormalizeDouble(MathMin(vol-PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2);
      if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
      PositionSelect(s);
     }
  }
//+------------------------------------------------------------------+

При внимательном изучении в функции открытия позиции можно заметить три существенные вещи: переменные s, optF и вызов функции GetPossibleLots():

  • s - инструмент, которым будем торговать, является одной из оптимизируемых ГА переменных,
  • optF - часть депозита, которой хотелось бы торговать (еще одна из оптимизируемых ГА переменных), 
  • функция GetPossibleLots() - возвращает часть депозита, которой можно торговать:
//+------------------------------------------------------------------+
//|  GetPossibleLots                                                 |
//+------------------------------------------------------------------+
double GetPossibleLots()
  {
   request.volume=1.0;
   if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
   else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
   OrderCheck(request,check);
   return(NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)/check.margin,2));
  }

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

//+------------------------------------------------------------------+
//|  InitRelDD                                                       |
//+------------------------------------------------------------------+
void InitRelDD()
  {
   ulong DealTicket;
   double curBalance;
   prevBT[0]=D'2000.01.01 00:00:00';
   TimeToStruct(prevBT[0],prevT);
   curBalance=AccountInfoDouble(ACCOUNT_BALANCE);
   maxBalance=curBalance;
   HistorySelect(D'2000.01.01 00:00:00',TimeCurrent());
   for(int i=HistoryDealsTotal();i>0;i--)
     {
      DealTicket=HistoryDealGetTicket(i);
      curBalance=curBalance+HistoryDealGetDouble(DealTicket,DEAL_PROFIT);
      if(curBalance>maxBalance) maxBalance=curBalance;
     }
  }
//+------------------------------------------------------------------+
//|  GetRelDD                                                        |
//+------------------------------------------------------------------+
double GetRelDD()
  {
   if(AccountInfoDouble(ACCOUNT_BALANCE)>maxBalance) maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);
   return((maxBalance-AccountInfoDouble(ACCOUNT_BALANCE))/maxBalance);
  }

Что здесь? Первая функция определяет максимальное значение баланса счета, вторая вычисляет относительную текущую просадку счета. Особенности их использования подробнее рассмотрим при описании шага номер Два.

Теперь, собственно, сами эксперты. Так как мы еще начинающие, не будем заставлять эксперт выбирать стратегию и реализуем жестко два эксперта со следующими стратегиями:
  • один торгует по пересечениям скользящих средних (золотой крест - покупаем инструмент, мертвый - продаем);
  • второй - простейшая нейросеть, которая получает на вход приведенные к диапазону [0..1] изменения цен за пять предыдущих торговых сессий.

Алгоритмически работа самооптимизирующегося эксперта выглядит следующим образом:

  1. Инициализируем переменные, используемые экспертом: определяем и инициализируем индикаторные буферы, либо задаем топологию нейросети (число слоев/нейронов в слое, в примере простейшая сеть с числом нейронов, одинаковым во всех слоях), указываем рабочий таймфрейм. Далее, наверное, самый ответственный шаг - вызываем функцию Генетической оптимизации, которая, в свою очередь, обращается к самой главной функции - fitness-функции (далее по тексту - ФФ).

    ВАЖНО! Для каждой новой торговой стратегии ФФ новая, т.е. переписывается, для одной средней ФФ совершенно не такая, как ФФ для двух средних, и существенно отличается от ФФ нейросети.

    Результатом работы ФФ в моих экспертах является максимум баланса при условии непревышения относительной просадкой критического значения, задаваемого внешней переменной (в наших примерах - 0,5). То есть если очередной прогон ГА дал баланс 100 000, но относительная просадка по балансу - 0,6, то ФФ=0,0. Для вас, уважаемый читатель, результатом в ФФ могут быть совершенно иные критерии.

    Забираем результаты работы Генетического алгоритма: для пересечения скользящих это будут, естественно, периоды скользящих, для нейросети - веса синапсов, для них обоих (и для других моих экспертов) - инструмент, на котором будем торговать до следующей переоптимизации, затем уже знакомое нам optF - часть депозита, которой бы хотелось торговать. Вы вольны добавлять в свои ФФ оптимизируемые параметры по желанию, например, выбирать еще и таймфрейм, да мало-ли...

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

    ВАЖНО! Как принимается решение о переоптимизации: при достижении относительной просадкой по БАЛАНСУ определенной критической величины, задаваемой в качестве внешней переменной (в наших примерах - 0,2) - переоптимизируемся. Для того чтобы не принуждать эксперт проводить переоптимизацию на каждом баре после достижения критической просадки, подменяем после каждой переоптимизации максимальное значение баланса текущим значением.

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

  2. Проводим торговые операции.

  3. После каждой закрытой позиции проверяем, не достигла ли просадка по балансу критической величины. Если достигла, то вызываем ГА, забираем результаты его работы (вот она, переоптимизация!).

  4. Ждем или звонка от директора форекса с просьбой не банкротить мир, или (что чаще) - Stop Out, Margin Call, карета скорой помощи...

Приведем программную реализацию вышесказанного для эксперта на средних (исходники также прилагаются), для нейросети - все в исходниках.

Код распространяется на условиях лицензии GPL.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   tf=Period();
//--- для побарного тестирования...
   prevBT[0]=D'2001.01.01';
//---... очень давно
   TimeToStruct(prevBT[0],prevT);
//--- глубина истории (задаем, так как оптимизируем на исторических данных)
   depth=10000;
//--- сколько за раз копируем (задаем, так как оптимизируем на исторических данных)
   count=2;
   ArrayResize(LongBuffer,count);
   ArrayResize(ShortBuffer,count);
   ArrayInitialize(LongBuffer,0);
   ArrayInitialize(ShortBuffer,0);
//--- вызываем функцию генетической оптимизации нейросети
   GA();
//--- получаем оптимизированные параметры нейросети и других переменных
   GetTrainResults();
//--- получаем просадку по балансу
   InitRelDD();
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(isNewBars()==true)
     {
      bool trig=false;
      CopyBuffer(MAshort,0,0,count,ShortBuffer);
      CopyBuffer(MAlong,0,0,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         if(PositionsTotal()>0)
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               ClosePosition();
               trig=true;
              }
           }
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         if(PositionsTotal()>0)
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               ClosePosition();
               trig=true;
              }
           }
        }
      if(trig==true)
        {
         //--- если просадка баланса превысила допустимую:
         if(GetRelDD()>maxDD)
           {
            //--- вызываем функцию генетической оптимизации нейросети
            GA();
            //--- получаем оптимизированные параметры нейросети и других переменных
            GetTrainResults();
            //--- отсчет просадки будем теперь вести не от максимума баланса, а от текущего баланса
            maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);
           }
        }
      CopyBuffer(MAshort,0,0,count,ShortBuffer);
      CopyBuffer(MAlong,0,0,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         request.type=ORDER_TYPE_SELL;
         OpenPosition();
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         request.type=ORDER_TYPE_BUY;
         OpenPosition();
        }
     };
  }
//+------------------------------------------------------------------+
//| Подготовка и вызов генетического оптимизатора                    |
//+------------------------------------------------------------------+
void GA()
  {
//--- кол-во генов (равно кол-ву оптимизируемых переменных, 
//--- всех их необходимо не забывать упомянуть в FitnessFunction())
   GeneCount      =OptParamCount+2;    
//--- кол-во хромосом в колонии
   ChromosomeCount=GeneCount*11;
//--- минимум диапазона поиска
   RangeMinimum   =0.0;
//--- максимум диапазона поиска
   RangeMaximum   =1.0;
//--- шаг поиска
   Precision      =0.0001;
//--- 1-минимум, любое другое-максимум
   OptimizeMethod =2;                                                 
   ArrayResize(Chromosome,GeneCount+1);
   ArrayInitialize(Chromosome,0);
//--- кол-во эпох без улучшения
   Epoch          =100;                                               
//--- доля репликации, естественной мутации, искусственной мутации, заимствования генов, 
//--- кроссинговера, коэффициент смещения границ интервала, вероятность мутации каждого гена в %
   UGA(100.0,1.0,1.0,1.0,1.0,0.5,1.0);                                
  }
//+------------------------------------------------------------------+
//| Fitness-функция для генетического оптимизатора нейросети:        | 
//| выбирает пару, optF, веса синапсов;                              |
//| можно оптимизировать что-угодно, но необходимо                   |
//| внимательно следить за количествами генов                        |
//+------------------------------------------------------------------+
void FitnessFunction(int chromos)
  {
   int    b;
//--- есть открытая позиция?
   bool   trig=false;
//--- направление открытой позиции
   string dir="";
//--- цена открытия позиции
   double OpenPrice=0;
//--- промежуточное звено между колонией генов и оптимизируемыми параметрами
   int    z;
//--- текущий баланс
   double t=cap;
//--- максимальный баланс
   double maxt=t;
//--- абсолютная просадка
   double aDD=0;
//--- относительная просадка
   double rDD=0.000001;
//--- непосредственно fitness-функция
   double ff=0;
//--- ГА выбирает пару
   z=(int)MathRound(Colony[GeneCount-1][chromos]*12);
   switch(z)
     {
      case  0: {s="AUDUSD"; break;};
      case  1: {s="AUDUSD"; break;};
      case  2: {s="EURAUD"; break;};
      case  3: {s="EURCHF"; break;};
      case  4: {s="EURGBP"; break;};
      case  5: {s="EURJPY"; break;};
      case  6: {s="EURUSD"; break;};
      case  7: {s="GBPCHF"; break;};
      case  8: {s="GBPJPY"; break;};
      case  9: {s="GBPUSD"; break;};
      case 10: {s="USDCAD"; break;};
      case 11: {s="USDCHF"; break;};
      case 12: {s="USDJPY"; break;};
      default: {s="EURUSD"; break;};
     }
   MAshort=iMA(s,tf,(int)MathRound(Colony[1][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   MAlong =iMA(s,tf,(int)MathRound(Colony[2][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   dig=MathPow(10.0,(double)SymbolInfoInteger(s,SYMBOL_DIGITS));
   
//--- ГА выбирает оптимальное F
   optF=Colony[GeneCount][chromos];                                   
   
   leverage=AccountInfoInteger(ACCOUNT_LEVERAGE);
   contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE);
   b=MathMin(Bars(s,tf)-1-count-MaxMAPeriod,depth);
   
//--- для нейросети, использующей исторические данные - откуда начинаем их копировать
   for(from=b;from>=1;from--) 
     {
      CopyBuffer(MAshort,0,from,count,ShortBuffer);
      CopyBuffer(MAlong,0,from,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         if(trig==false)
           {
            CopyOpen(s,tf,from,count,o);
            OpenPrice=o[1];
            dir="SELL";
            trig=true;
           }
         else
           {
            if(dir=="BUY")
              {
               CopyOpen(s,tf,from,count,o);
               if(t>0) t=t+t*optF*leverage*(o[1]-OpenPrice)*dig/contractSize; else t=0;
               if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
               if((maxt>0) && (aDD/maxt>rDD)) rDD=aDD/maxt;
               OpenPrice=o[1];
               dir="SELL";
               trig=true;
              }
           }
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         if(trig==false)
           {
            CopyOpen(s,tf,from,count,o);
            OpenPrice=o[1];
            dir="BUY";
            trig=true;
           }
         else
           {
            if(dir=="SELL")
              {
               CopyOpen(s,tf,from,count,o);
               if(t>0) t=t+t*optF*leverage*(OpenPrice-o[1])*dig/contractSize; else t=0;
               if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
               if((maxt>0) && (aDD/maxt>rDD)) rDD=aDD/maxt;
               OpenPrice=o[1];
               dir="BUY";
               trig=true;
              }
           }
        }
     }
   if(rDD<=trainDD) ff=t; else ff=0.0;
   AmountStartsFF++;
   Colony[0][chromos]=ff;
  }

//+-------------------------------------------------------------------+
//| Получаем оптимизированные параметры нейросети и других переменных |
//| всегда должно быть равно кол-ву генов                             |
//+-------------------------------------------------------------------+
void GetTrainResults()
  {
//--- промежуточное звено между колонией генов и оптимизируемыми параметрами
   int z;                                                             
   MAshort=iMA(s,tf,(int)MathRound(Chromosome[1]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   MAlong =iMA(s,tf,(int)MathRound(Chromosome[2]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   CopyBuffer(MAshort,0,from,count,ShortBuffer);
   CopyBuffer(MAlong,0,from,count,LongBuffer);
//--- запоминаем лучшую пару
   z=(int)MathRound(Chromosome[GeneCount-1]*12);                      
   switch(z)
     {
      case  0: {s="AUDUSD"; break;};
      case  1: {s="AUDUSD"; break;};
      case  2: {s="EURAUD"; break;};
      case  3: {s="EURCHF"; break;};
      case  4: {s="EURGBP"; break;};
      case  5: {s="EURJPY"; break;};
      case  6: {s="EURUSD"; break;};
      case  7: {s="GBPCHF"; break;};
      case  8: {s="GBPJPY"; break;};
      case  9: {s="GBPUSD"; break;};
      case 10: {s="USDCAD"; break;};
      case 11: {s="USDCHF"; break;};
      case 12: {s="USDJPY"; break;};
      default: {s="EURUSD"; break;};
     }
//--- запоминаем лучший оптимальное F
   optF=Chromosome[GeneCount];                                        
  }
//+------------------------------------------------------------------+

Попробуем подробнее разобраться в работе главной функции алгоритма  -  фитнес-функции.

Вся затея самооптимизирующегося эксперта основана на моделировании процесса торговли (как в стандартном тестере от MetaQuotes) во времени (допустим, на истории в 10000 баров) в фитнес-функции, на вход которой генетический алгоритм (функция GA()) подает оптимизируемые переменные. В случае с алгоритмом, основанном на пересечениях скользящих средних, к оптимизируемым переменным относятся:

  • инструмент (для форекса - валютная пара); да-да, это типичный мультивалютник, и генетический алгоритм выбирает инструмент (так как код взят из эксперта, выставленного на Чемпионат, то и пары в нем соответствуют валютным парам Чемпионата; в общем случае могут быть все инструменты, котируемые брокером).
    Замечание: к огромному сожалению, в режиме тестирования эксперт не имеет возможности получить перечень пар из окошка Market Watch (спасибо MetaQuotes-ам, здесь мы это выяснили - Нельзя!). Поэтому, если вы захотите прогнать эксперт в тестере отдельно для форекса и отдельно для акций, то просто возьмите и укажите перечень своих инструментов в ФФ и в функции GetTrainResults().
  • доля депозита, которой будем торговать;
  • периоды двух скользящих средних.

В приведенных примерах приведен вариант эксперта ДЛЯ ТЕСТИРОВАНИЯ!

Код для РЕАЛЬНОЙ ТОРГОВЛИ можно значительно облегчить, получая перечень инструментов из окошка Market Watch.

Для этого в ФФ и функции GetTrainResults() с комментариями "//--- ГА выбирает пару" и "//--- запоминаем лучшую пару" просто пишем:

//--- ГА выбирает пару
  z=(int)MathRound(Colony[GeneCount-1][chromos]*(SymbolsTotal(true)-1));
  s=SymbolName(z,true);

Таким образом, в начале ФФ описываем и инициализируем при необходимости переменные для моделирования торговли на истории. На втором этапе принимаем из генетического алгоритма различные значения оптимизируемых переменных, например в строке "optF=Colony[GeneCount][chromos];" внутрь ФФ передается из ГА значение доли депозита.

Далее узнаем доступное число баров в истории и, начиная или с 10000-го, или с первого доступного бара начинаем моделировать процесс поступления котировок в режиме "Только цены открытия" и принятия торговых решений:

  • Копируем в буфера значения скользящих средних;
  • Проверяем, это мертвый крест?
  • Если мертвый крест и нет открытых позиций (if(trig==false)) - открываем виртуально позицию SELL (просто запоминаем цену открытия и направление);
  • Если мертвый крест и открыта позиция BUY (if(dir=="BUY")) - берем цену открытия бара и далее три очень важных строки:
  1. Моделируем закрытие позиции и изменение баланса: к текущему балансу прибавляем значение текущего баланса, умноженного на долю депозита, разрешенного для торговли, умноженного на разницу цен открытия и закрытия, умноженного на цену пипса (неточную);
  2. Проверяем, не стал ли текущий баланс максимальным за историю моделирования торговли; если не стал, то вычисляем просадку в деньгах от макcимального баланса;
  3. Переводим полученную ранее просадку в деньгах в относительную просадку по балансу;
  • Открываем виртуально позицию SELL (просто запоминаем цену открытия и направление);
  • Выполняем аналогичные проверки и расчеты для золотого креста.

Пройдя всю доступную историю и смоделировав виртуально торговлю, рассчитываем итоговое значение ФФ: если полученная относительная просадка меньше задаваемой для тестирования просадки, то ФФ=балансу, иначе ФФ=0. Генетический алгоритм настроен на максимизацию фитнес-функции!

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


Заключение

Краткий вывод таков: написать самообучающийся эксперт просто, сложно найти то, что ему подавать на вход (важна идея, реализация - дело техники).

Предупреждая вопрос пессимистов - "а оно работает?", скажу - работает; оптимистам скажу - не грааль.

В чем принципиальное отличие предлагаемого метода от метода Quantum-а? Наиболее ярко можно описать, сопоставив экспертов на МАшках:

  1. В Адаптивной торговой системе решение о периодах МАшек необходимо принимать до компиляции, жестко прописывать в код и делать выбор только из этого ограниченного числа вариантов; в Генетически оптимизируемом эксперте до компиляции мы не принимаем никаких решений о периодах, это решение примет ГА и количество вариантов ограничивает только здравый смысл.
  2. В Адаптивной торговой системе виртуальная торговля ведется побарно; в Генетически оптимизируемом эксперте изредка, при наступлении условий переоптимизации. При увеличении числа стратегий, параметров, инструментов производительность компьютера может стать для АТС ограничивающим фактором.


Приложение

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

Отчет Тестера стратегий
MetaQuotes-Demo (Build 523)
Советник: ANNExample
Символ: EURUSD
Период: Daily (2010.01.01 - 2011.09.30)
Входные параметры: trainDD=0.9
maxDD=0.1
Брокер: Alpari NZ Limited
Валюта: USD
Начальный депозит: 10 000.00
Плечо: 1:100
Результаты
Качество истории: 100%
Бары: 454 Тики: 2554879
Чистая прибыль: -9 094.49 Общая прибыль: 29 401.09 Общий убыток: -38 495.58
Прибыльность: 0.76 Матожидание выигрыша: -20.53 Уровень маржи: 732.30%
Фактор восстановления: -0.76 Коэффициент Шарпа: -0.06 Результат OnTester: 0
Просадка баланса:
Абс. просадка по балансу: 9 102.56 Максимальная просадка по балансу: 11 464.70 (92.74%) Относительная просадка по балансу: 92.74% (11 464.70)
Просадка средств:
Абс. просадка по средствам: 9 176.99 Максимальная просадка по средствам: 11 904.00 (93.53%) Относительная просадка по средствам: 93.53% (11 904.00)
Всего трейдов: 443 Короткие трейды (% выигравших): 7 (14.29%) Длинные трейды (% выигравших): 436 (53.44%)
Всего сделок: 886 Прибыльные трейды (% от всех): 234 (52.82%) Убыточные трейды (% от всех): 209 (47.18%)
Самый прибыльный трейд: 1 095.57 Самый убыточный трейд: -1 438.85
Средний прибыльный трейд: 125.65 Средний убыточный трейд: -184.19
Макс. серия выигрышей (прибыль): 8 (397.45) Макс. серия проигрышей(убыток) : 8 (-1 431.44)
Макс. прибыль в серии(число выигрышей): 1 095.57 (1) Макс. убыток в серии(число проигрышей): -3 433.21 (6)
Средняя серия выигрышей: 2 Средняя серия проигрышей: 2

и на выбор три момента переоптимизации:

раз...

Время Сделка Символ Тип Направление Объем Цена Ордер Комиссия Своп Прибыль Баланс
2010.01.01 00:00 1 balance 0.00 0.00 10 000.00 10 000.00
2010.01.04 00:00 2 AUDUSD buy in 0.90 0.89977 2 0.00 0.00 0.00 10 000.00
2010.01.05 00:00 3 AUDUSD sell out 0.90 0.91188 3 0.00 5.67 1 089.90 11 095.57
2010.01.05 00:00 4 AUDUSD buy in 0.99 0.91220 4 0.00 0.00 0.00 11 095.57
2010.01.06 00:00 5 AUDUSD sell out 0.99 0.91157 5 0.00 6.24 -62.37 11 039.44
2010.01.06 00:00 6 AUDUSD buy in 0.99 0.91190 6 0.00 0.00 0.00 11 039.44
2010.01.07 00:00 7 AUDUSD sell out 0.99 0.91924 7 0.00 18.71 726.66 11 784.81


два...

Время Сделка Символ Тип Направление Объем Цена Ордер Комиссия Своп Прибыль Баланс
2010.05.19 00:00 189 AUDUSD sell out 0.36 0.86110 189 0.00 2.27 -595.44 4 221.30
2010.05.19 00:00 190 EURAUD sell in 0.30 1.41280 190 0.00 0.00 0.00 4 221.30
2010.05.20 00:00 191 EURAUD buy out 0.30 1.46207 191 0.00 7.43 -1 273.26 2 955.47
2010.05.20 00:00 192 AUDUSD buy in 0.21 0.84983 192 0.00 0.00 0.00 2 955.47


три

Время Сделка Символ Тип Направление Объем Цена Ордер Комиссия Своп Прибыль Баланс
2010.06.16 00:00 230 GBPCHF buy in 0.06 1.67872 230 0.00 0.00 0.00 2 128.80
2010.06.17 00:00 231 GBPCHF sell out 0.06 1.66547 231 0.00 0.13 -70.25 2 058.68
2010.06.17 00:00 232 GBPCHF buy in 0.06 1.66635 232 0.00 0.00 0.00 2 058.68
2010.06.18 00:00 233 GBPCHF sell out 0.06 1.64705 233 0.00 0.04 -104.14 1 954.58
2010.06.18 00:00 234 AUDUSD buy in 0.09 0.86741 234 0.00 0.00 0.00 1 954.58
2010.06.21 00:00 235 AUDUSD sell out 0.09 0.87184 235 0.00 0.57 39.87 1 995.02
2010.06.21 00:00 236 AUDUSD buy in 0.09 0.88105 236 0.00 0.00 0.00 1 995.02
2010.06.22 00:00 237 AUDUSD sell out 0.09 0.87606 237 0.00 0.57 -44.91 1 950.68
2010.06.22 00:00 238 AUDUSD buy in 0.09 0.87637 238 0.00 0.00 0.00 1 950.68
2010.06.23 00:00 239 AUDUSD sell out 0.09 0.87140 239 0.00 0.57 -44.73 1 906.52
2010.06.23 00:00 240 AUDUSD buy in 0.08 0.87197 240 0.00 0.00 0.00 1 906.52
2010.06.24 00:00 241 AUDUSD sell out 0.08 0.87385 241 0.00 1.51 15.04 1 923.07
2010.06.24 00:00 242 AUDUSD buy in 0.08 0.87413 242 0.00 0.00 0.00 1 923.07
2010.06.25 00:00 243 AUDUSD sell out 0.08 0.86632 243 0.00 0.50 -62.48 1 861.09
2010.06.25 00:00 244 AUDUSD buy in 0.08 0.86663 244 0.00 0.00 0.00 1 861.09
2010.06.28 00:00 245 AUDUSD sell out 0.08 0.87375 245 0.00 0.50 56.96 1 918.55
2010.06.28 00:00 246 AUDUSD buy in 0.08 0.87415 246 0.00 0.00 0.00 1 918.55
2010.06.29 00:00 247 AUDUSD sell out 0.08 0.87140 247 0.00 0.50 -22.00 1 897.05
2010.06.29 00:00 248 AUDUSD buy in 0.08 0.87173 248 0.00 0.00 0.00 1 897.05
2010.07.01 00:00 249 AUDUSD sell out 0.08 0.84053 249 0.00 2.01 -249.60 1 649.46
2010.07.01 00:00 250 EURGBP sell in 0.07 0.81841 250 0.00 0.00 0.00 1 649.46
2010.07.02 00:00 251 EURGBP buy out 0.07 0.82535 251 0.00 -0.04 -73.69 1 575.73
2010.07.02 00:00 252 EURGBP sell in 0.07 0.82498 252 0.00 0.00 0.00 1 575.73
2010.07.05 00:00 253 EURGBP buy out 0.07 0.82676 253 0.00 -0.04 -18.93 1 556.76
2010.07.05 00:00 254 EURGBP sell in 0.06 0.82604 254 0.00 0.00 0.00 1 556.76
2010.07.06 00:00 255 EURGBP buy out 0.06 0.82862 255 0.00 -0.04 -23.43 1 533.29


P.S. В качестве домашнего задания: реализовать выбор не только параметров определенной системы, но и выбор самой системы, наиболее соответствующей в данный момент рынку (подсказка - из банка систем).


Прикрепленные файлы |
ugalib.mqh (32.55 KB)
matrainlib.mqh (8.89 KB)
maexample.mq5 (4.16 KB)
anntrainlib.mqh (9.55 KB)
annexample.mq5 (4.25 KB)
musthavelib.mqh (8.15 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
investeo
investeo | 4 нояб. 2011 в 19:05

Google Translate. But seriously I consider this as a motivation to learn Russian :-)
Rashid Umarov
Rashid Umarov | 4 нояб. 2011 в 19:13

We will translate this article as soon as possible. Sorry.
Михаил Янович
Михаил Янович | 20 нояб. 2011 в 16:31

Добрый день.

Какие параметры следует ввести для того чтобы увидеть работу вашего примера? 

 пока он не открывает ни одной сделки. 

Anatoliy Ivanov
Anatoliy Ivanov | 21 янв. 2012 в 13:49

bool isNewBars()
  {
  ....
      tf==PERIOD_H8||
      tf==PERIOD_M12)

Опечатка?-вместо   PERIOD_M12 в   MustHave.mqh/    bool isNewBars(), надо     PERIOD_H12?

_anatoly
_anatoly | 18 нояб. 2013 в 17:33

Господа, профессионалы!

Помогите чайнику собрать вместе это все,  чтоб заработало.

Сори за тупой вопрос. :-)

Интервью с Тимом Фассом (ATC 2011) Интервью с Тимом Фассом (ATC 2011)
Несмотря на то что студент Тим Фасс (Tim) из Германии в первый раз участвует в Чемпионате Automated Trading Championship, его советник The_Wild_13 сумел побывать на самой вершине турнирной таблицы и никому не собирается уступать свое место в первой десятке. Тим рассказал нам об участвующем эксперте, о вере в успех простых стратегий и о своей мечте.
Лига Чемпионов ATC: Интервью с Александром Топчило (ATC 2011) Лига Чемпионов ATC: Интервью с Александром Топчило (ATC 2011)
Вторым в рамках проекта "Лига Чемпионов ATC" мы публикуем интервью с Александром Топчило (Better). Победив в Automated Trading Championship 2007, этот профессиональный трейдер привлек внимание инвесторов. По признанию Александра, эта победа стала одним из знаменательных событий его трейдерской карьере. Однако впоследствии обретенная слава открыла и наибольшее разочарование - так легко потерять инвесторов после первых же просадок на счете.
Как создать эксперта за несколько минут при помощи EA Tree: Часть 1 Как создать эксперта за несколько минут при помощи EA Tree: Часть 1
Программа EA Tree является первым инструментом, позволяющим построить код советника на базе блок-схем методом "drag and drop". Создание советников в EA Tree осуществляется путем построения блоков, которые могут содержать функции языка MQL5, технические и пользовательские индикаторы, или численные значения. Выходы блоков могут быть соединены с входами других блоков, образуя "дерево блоков". На базе дерева блоков программа EA Tree генерирует исходный код советника, который затем может быть скомпилирован в торговой платформе MetaTrader 5.
Интервью с Ацуси Яманака (ATC 2011) Интервью с Ацуси Яманака (ATC 2011)
Что общего между скайдайвингом, фьючерсами, Гавайями, переводами и шпионами? Мы тоже не знали, пока не пообщались с дисквалифицированным участником Ацуси Яманака (alohafx). Его кредо - "Life is Good! - Жизнь прекрасна!", и с этим трудно не согласиться. Было интересно узнать, что расстояние между разными континентами - не помеха в общении участников нашего Чемпионата.