Доктор Трейдлав, или Как я перестал беспокоиться и написал самообучающийся эксперт
Замысел
Все мы после написания эксперта прибегаем к помощи встроенного Тестера стратегий для подбора оптимальных параметров. Найдя их, запускаем эксперт и при наступлении каких-либо, значимых для нас изменений в работе эксперта, останавливаем его, оптимизируем в Тестере еще раз, и еще раз - и так все время.
Можно ли возложить на сам эксперт принятие решения о переоптимизации и непосредственно саму переоптимизацию, не прерывая при этом, естественно, работы самого эксперта?
Один из вариантов решения этой проблемы предложил нам 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] изменения цен за пять предыдущих торговых сессий.
Алгоритмически работа самооптимизирующегося эксперта выглядит следующим образом:
Инициализируем переменные, используемые экспертом: определяем и инициализируем индикаторные буферы, либо задаем топологию нейросети (число слоев/нейронов в слое, в примере простейшая сеть с числом нейронов, одинаковым во всех слоях), указываем рабочий таймфрейм. Далее, наверное, самый ответственный шаг - вызываем функцию Генетической оптимизации, которая, в свою очередь, обращается к самой главной функции - fitness-функции (далее по тексту - ФФ).
ВАЖНО! Для каждой новой торговой стратегии ФФ новая, т.е. переписывается, для одной средней ФФ совершенно не такая, как ФФ для двух средних, и существенно отличается от ФФ нейросети.
Результатом работы ФФ в моих экспертах является максимум баланса при условии непревышения относительной просадкой критического значения, задаваемого внешней переменной (в наших примерах - 0,5). То есть если очередной прогон ГА дал баланс 100 000, но относительная просадка по балансу - 0,6, то ФФ=0,0. Для вас, уважаемый читатель, результатом в ФФ могут быть совершенно иные критерии.
Забираем результаты работы Генетического алгоритма: для пересечения скользящих это будут, естественно, периоды скользящих, для нейросети - веса синапсов, для них обоих (и для других моих экспертов) - инструмент, на котором будем торговать до следующей переоптимизации, затем уже знакомое нам optF - часть депозита, которой бы хотелось торговать. Вы вольны добавлять в свои ФФ оптимизируемые параметры по желанию, например, выбирать еще и таймфрейм, да мало-ли...
Последний пункт в инициализации - узнать максимальное значения баланса счета. В чем важность этого пункта? В том, что это - отправная точка для принятия решения о проведении переоптимизации.
У вас, уважаемый читатель, критерий наступления момента переоптимизации может быть совершенно иной.ВАЖНО! Как принимается решение о переоптимизации: при достижении относительной просадкой по БАЛАНСУ определенной критической величины, задаваемой в качестве внешней переменной (в наших примерах - 0,2) - переоптимизируемся. Для того чтобы не принуждать эксперт проводить переоптимизацию на каждом баре после достижения критической просадки, подменяем после каждой переоптимизации максимальное значение баланса текущим значением.
- Проводим торговые операции.
- После каждой закрытой позиции проверяем, не достигла ли просадка по
балансу критической величины. Если достигла, то вызываем ГА, забираем
результаты его работы (вот она, переоптимизация!).
-
Ждем или звонка от директора форекса с просьбой не банкротить мир, или (что чаще) - 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")) - берем цену открытия бара и далее три очень важных строки:
- Моделируем закрытие позиции и изменение баланса: к текущему балансу прибавляем значение текущего баланса, умноженного на долю депозита, разрешенного для торговли, умноженного на разницу цен открытия и закрытия, умноженного на цену пипса (неточную);
- Проверяем, не стал ли текущий баланс максимальным за историю моделирования торговли; если не стал, то вычисляем просадку в деньгах от макcимального баланса;
- Переводим полученную ранее просадку в деньгах в относительную просадку по балансу;
- Открываем виртуально позицию SELL (просто запоминаем цену открытия и направление);
- Выполняем аналогичные проверки и расчеты для золотого креста.
Пройдя всю доступную историю и смоделировав виртуально торговлю, рассчитываем итоговое значение ФФ: если полученная относительная просадка меньше задаваемой для тестирования просадки, то ФФ=балансу, иначе ФФ=0. Генетический алгоритм настроен на максимизацию фитнес-функции!
В конце концов, генетический алгоритм, подавая всевозможные значения инструментов, долей депозита, периодов скользящих cредних, найдет те их значения, которые максимизируют баланс при минимальной (минимум задается пользователем) относительной просадке.
Заключение
Краткий вывод таков: написать самообучающийся эксперт просто, сложно найти то, что ему подавать на вход (важна идея, реализация - дело техники).
Предупреждая вопрос пессимистов - "а оно работает?", скажу - работает; оптимистам скажу - не грааль.
В чем принципиальное отличие предлагаемого метода от метода Quantum-а? Наиболее ярко можно описать, сопоставив экспертов на МАшках:
- В Адаптивной торговой системе решение о периодах МАшек необходимо принимать до компиляции, жестко прописывать в код и делать выбор только из этого ограниченного числа вариантов; в Генетически оптимизируемом эксперте до компиляции мы не принимаем никаких решений о периодах, это решение примет ГА и количество вариантов ограничивает только здравый смысл.
- В Адаптивной торговой системе виртуальная торговля ведется побарно; в Генетически оптимизируемом эксперте изредка, при наступлении условий переоптимизации. При увеличении числа стратегий, параметров, инструментов производительность компьютера может стать для АТС ограничивающим фактором.
Приложение
Вот что получится, если нейросеть загнать в тестер не включая никакой оптимизации на дневках с 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. В качестве домашнего задания: реализовать выбор не только параметров определенной системы, но и выбор самой системы, наиболее соответствующей в данный момент рынку (подсказка - из банка систем).
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Google Translate. But seriously I consider this as a motivation to learn Russian :-)
We will translate this article as soon as possible. Sorry.
Добрый день.
Какие параметры следует ввести для того чтобы увидеть работу вашего примера?
пока он не открывает ни одной сделки.
Опечатка?-вместо PERIOD_M12 в MustHave.mqh/ bool isNewBars(), надо PERIOD_H12?
Господа, профессионалы!
Помогите чайнику собрать вместе это все, чтоб заработало.
Сори за тупой вопрос. :-)