Нечеткая логика в торговых стратегиях

18 сентября 2017, 12:05
Maxim Dmitrievsky
29
8 405

Введение

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

Приведем пример: в рамках четкой логики мы точно определяем скорость движущегося автомобиля приборами — например, 60 км\ч. Но будь мы сторонним наблюдателем, без соответствующих измерительных приборов, то смогли бы только примерно оценить скорость автомобиля, опираясь на наш опыт или базу знаний. Например, мы знаем, что автомобиль может ехать быстро, а "быстро" мы определяем примерно как 100 км/ч и выше. Также мы знаем, что автомобиль может ехать и медленно, а это 5 — 10 км/ч. И, наконец, мы визуально определяем скорость как среднюю (около 60 км/ч), если приближающийся к нам автомобиль увеличивается в размерах в умеренном темпе. Таким образом, мы можем охарактеризовать 60км\ч четырьмя различными выражениями:

  • как среднюю скорость;
  • как скорость, близкую к средней;
  • как скорее средне, чем быстро;
  • и, наконец, как скорее средне, чем медленно. 

Так капсулируется информация в нашем сознании, позволяя ухватывать только необходимую в данный момент информацию — например,  "успею ли я перебежать дорогу, когда автомобиль движется не очень быстро?". Если бы мы думали сразу обо всем и в мельчайших подробностях, то должны были бы затратить колоссальные временные и энергетические ресурсы прежде чем принять какое-то конкретное решение: перебегать дорогу или пропустить автомобиль. В то же время, мы бы досконально изучили текущую ситуацию, которая уже может никогда не повториться в будущем идентичным образом, а будет иметь лишь похожие очертания. В машинном обучении такой процесс называется переобучением или оверфиттингом.

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

Для построения модели используем библиотеку Fuzzy, которая включена в стандартную поставку терминала MetaTrader 5.

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

Создание прототипа торговой системы

Перейдем к созданию четкой логики ТС, на которую будем опираться в дальнейших изысканиях. Затем мы можем сравнить 2 идентичные системы, но вторая будет уже с применением нечеткой логики.

За основу возьмем 3 осциллятора RSI с разными периодами:

hnd1 = iRSI(_Symbol,0,9,PRICE_CLOSE);
hnd2 = iRSI(_Symbol,0,14,PRICE_CLOSE);
hnd3 = iRSI(_Symbol,0,21,PRICE_CLOSE);

Сформулируем четкие условия сигналов и определим их в функции:

double CalculateSignal()
{
 double res =0.5;
 CopyBuffer(hnd1,0,0,1,arr1);
 CopyBuffer(hnd2,0,0,1,arr2);
 CopyBuffer(hnd3,0,0,1,arr3);
 
 if(arr1[0]>70 && arr2[0]>70 && arr3[0]>70) res=1.0;                    //если все индикаторы в зоне перекупленности, то продаем
 if(arr1[0]<30 && arr2[0]<30 && arr3[0]<30) res=0.0;                    //если все индикаторы в зоне перепроданности, то покупаем
 
 if(arr1[0]<30 && arr2[0]<30 && arr3[0]>70) res=0.5;                    //если 2 перепроданы, а один перекуплен, то нет сигнала
 if(arr1[0]<30 && arr2[0]>70 && arr3[0]<30) res=0.5;
 if(arr1[0]>70 && arr2[0]<30 && arr3[0]<30) res=0.5;
 
 if(arr1[0]>70 && arr2[0]>70 && arr3[0]<30) res=0.5;                    //если 2 перекуплены, а один перепродан, то нет сигнала
 if(arr1[0]>70 && arr2[0]<30 && arr3[0]>70) res=0.5;
 if(arr1[0]<30 && arr2[0]>70 && arr3[0]>70) res=0.5;
 
 if(arr1[0]<30 && arr2[0]<30 && (arr3[0]>40 && arr3[0]<60)) res=0.0;    //если 2 перепроданы, а 3-й в зоне от 40 до 60, то сигнал на покупку
 if(arr1[0]<30 && (arr2[0]>40 && arr2[0]<60) && arr3[0]<30) res=0.0;
 if((arr1[0]>40 && arr1[0]<60) && arr2[0]<30 && arr3[0]<30) res=0.0;
 
 if(arr1[0]>70 && arr2[0]>70 && (arr3[0]>40 && arr3[0]<60)) res=1.0;    //если 2 перекуплены, а 3-й в зоне от 40 до 60, то сигнал на продажу
 if(arr1[0]>70 && (arr2[0]>40 && arr2[0]<60) && arr3[0]>70) res=1.0;
 if((arr1[0]>40 && arr1[0]<60) && arr2[0]>70 && arr3[0]>70) res=1.0;
 
 return(res);
}

Далее напишем все остальные сервисные функции и протестируем эксперта с начала 2017 года на EURUSD, таймфреймах М15 и М5 (полный код эксперта приложен в конце статьи):

EURUSD M15

EURUSD M5

Видно, что хотя мы и описали четкие условия к комбинациям трёх индикаторов и условия логичны и непротиворечивы, такой подход оказался слишком прямолинейным и негибким. В среднем, система не теряет и не зарабатывает за период 8 месяцев. Чтобы заставить ее зарабатывать, следовало бы перебрать множество комбинаций условий, возможно — добавить больше осцилляторов. Но здесь совершенно нечего оптимизировать, поскольку условия заданы предельно конкретно.

Попытаемся "размазать по тарелке" наши представления о том, в каких условиях ТС начнет зарабатывать, применяя нечеткую логику.

Создание модели нечеткой логики

Сначала нужно подключить библиотеку Fuzzy, а конкретнее — одну из двух доступных моделей нечеткой логики, Мамдани или Сугено. Различия между ними в том, что в случае Сугено на выходе получается линейная модель без создания выходной переменной в виде нечеткого терм-множества, тогда как в Мамдани присутствует этот элемент. Поскольку статья пишется именно для нечетких трейдеров, будем использовать Мамдани. Но это не значит что для каких-то конкретных задач вам не подойдет модель Сугено: всегда можно и нужно экспериментировать, опираясь на базовое понимание нечеткой логики.

#include <Math\Fuzzy\MamdaniFuzzySystem.mqh>
CMamdaniFuzzySystem *OurFuzzy=new CMamdaniFuzzySystem();

Библиотека подключена, объявлена ссылка на класс Мамдани. Это все, что нужно для начала работы. 

Теперь поговорим об основных этапах построения нечеткого вывода. Он занимает центральное место в системах нечеткого моделирования. Процесс нечеткого вывода представляет собой определенную процедуру, или алгоритм получения нечетких заключений на основании нечетких предпосылок с использованием основных операций нечеткой логики. 

Можно выделить 7 этапов построения нечеткого вывода:

  •  Определение структуры системы нечеткого вывода

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

  • Формирование базы правил системы нечеткого вывода

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

  • Фаззификация входных переменных

Установка соответствия между численным значением входной переменной системы нечеткого вывода и значением функции принадлежности соответствующего ей терма лингвистической переменной.

  • Агрегирование

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

  • Активация

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

  • Аккумуляция

Процесс нахождения функции принадлежности для каждой из выходных лингвистических переменных.

  • Дефаззификация
Процесс перехода от функции принадлежности выходной лингвистической переменной к её четкому (числовому) значению. Это значение и будет нашим выходным значением в диапазоне от 0 до 1.

Стоит отметить, что необходимо выполнить только 1 и 2 пункты, все остальное система сделает без нашего вмешательства. Если вам интересны тонкости работы нечеткой логики на всех этапах — можете почитать об этом здесь.

Определение структуры системы нечеткого вывода

Продолжим создание модели. Определим объекты трёх входов и одного выхода, а также вспомогательные объекты словаря для удобной работы с логикой:

CFuzzyVariable *firstInput=new CFuzzyVariable("rsi1",0.0,1.0);
CFuzzyVariable *secondInput=new CFuzzyVariable("rsi2",0.0,1.0);
CFuzzyVariable *thirdInput=new CFuzzyVariable("rsi3",0.0,1.0);
CFuzzyVariable *fuzzyOut=new CFuzzyVariable("out",0.0,1.0);

CDictionary_Obj_Double *firstTerm=new CDictionary_Obj_Double;
CDictionary_Obj_Double *secondTerm=new CDictionary_Obj_Double;
CDictionary_Obj_Double *thirdTerm=new CDictionary_Obj_Double;
CDictionary_Obj_Double *Output;

В качестве входов будем использовать три RSI с разными периодами. Поскольку осциллятор RSI всегда находится в диапазоне 0 —100, то необходимо было бы создать для него переменную такой же размерности. Но мы для удобства нормализуем значения индикатора в диапазон 0 — 1. Просто не забывайте о том, что созданная переменная должна иметь размерность, равную размерности входного вектора, т.е. вмещать все значения. На выходе тоже зададим диапазон от 0 до 1.

Исходя из п.1 создания нечеткой логики, необходимо также определить и настроить функции принадлежности. Сделаем это в обработчике событий OnInit():

firstInput.Terms().Add(new CFuzzyTerm("buy", new CZ_ShapedMembershipFunction(0.0,0.6)));
firstInput.Terms().Add(new CFuzzyTerm("neutral", new CNormalMembershipFunction(0.5, 0.2)));
firstInput.Terms().Add(new CFuzzyTerm("sell", new CS_ShapedMembershipFunction(0.4,1.0)));
OurFuzzy.Input().Add(firstInput);
   
secondInput.Terms().Add(new CFuzzyTerm("buy", new CZ_ShapedMembershipFunction(0.0,0.6)));
secondInput.Terms().Add(new CFuzzyTerm("neutral", new CNormalMembershipFunction(0.5, 0.2)));
secondInput.Terms().Add(new CFuzzyTerm("sell", new CS_ShapedMembershipFunction(0.4,1.0)));
OurFuzzy.Input().Add(secondInput);
   
thirdInput.Terms().Add(new CFuzzyTerm("buy", new CZ_ShapedMembershipFunction(0.0,0.6)));
thirdInput.Terms().Add(new CFuzzyTerm("neutral", new CNormalMembershipFunction(0.5, 0.2)));
thirdInput.Terms().Add(new CFuzzyTerm("sell", new CS_ShapedMembershipFunction(0.4,1.0)));
OurFuzzy.Input().Add(thirdInput);
   
fuzzyOut.Terms().Add(new CFuzzyTerm("buy", new CZ_ShapedMembershipFunction(0.0,0.6)));
fuzzyOut.Terms().Add(new CFuzzyTerm("neutral", new CNormalMembershipFunction(Gposition, Gsigma)));
fuzzyOut.Terms().Add(new CFuzzyTerm("sell", new CS_ShapedMembershipFunction(0.4,1.0)));
OurFuzzy.Output().Add(fuzzyOut);

Теперь попытаемся понять, что же такое функция принадлежности и какую роль она выполняет.

Для каждой входной переменной (и одной выходной) мы создали по 3 терма: "buy", "neutral", "sell", каждый из которых имеет свою функцию принадлежности. Другими словами, теперь мы можем разделить показания осциллятора на 3 нечеткие группы, и каждой группе присвоить диапазон значений при помощи функции принадлежности. Говоря на языке нечеткой логики, мы создали 4 терм-множества, и каждое имеет по 3 терма. Для иллюстрации сказанного напишем простой скрипт, который можно использовать для визуального представления термов и их функций принадлежности:

//+------------------------------------------------------------------+ 
//|                                      Our MembershipFunctions.mq5 | 
//|                        Copyright 2016, MetaQuotes Software Corp. | 
//|                                             https://www.mql5.com | 
//+------------------------------------------------------------------+ 
#include <Math\Fuzzy\membershipfunction.mqh> 
#include <Graphics\Graphic.mqh> 
//--- Create membership functions 
CZ_ShapedMembershipFunction func2(0.0, 0.6);
CNormalMembershipFunction func1(0.5, 0.2); 
CS_ShapedMembershipFunction func3(0.4, 1.0);

//--- Create wrappers for membership functions 
double NormalMembershipFunction1(double x) { return(func1.GetValue(x)); } 
double ZShapedMembershipFunction(double x) { return(func2.GetValue(x)); }
double SShapedMembershipFunction(double x) { return(func3.GetValue(x)); }
 
//+------------------------------------------------------------------+ 
//| Script program start function                                    | 
//+------------------------------------------------------------------+ 
void OnStart() 
  { 
//--- create graphic 
   CGraphic graphic; 
   if(!graphic.Create(0,"Our MembershipFunctions",0,30,30,780,380)) 
     { 
      graphic.Attach(0,"Our MembershipFunctions"); 
     } 
   graphic.HistoryNameWidth(70); 
   graphic.BackgroundMain("Our MembershipFunctions"); 
   graphic.BackgroundMainSize(16); 
//--- create curve 
   graphic.CurveAdd(NormalMembershipFunction1,0.0,1.0,0.01,CURVE_LINES,"[0.5, 0.2]"); 
   graphic.CurveAdd(ZShapedMembershipFunction,0.0,1.0,0.01,CURVE_LINES,"[0.0, 0.6]"); 
   graphic.CurveAdd(SShapedMembershipFunction,0.0,1.0,0.01,CURVE_LINES,"[0.4, 1.0]"); 
//--- sets the X-axis properties 
   graphic.XAxis().AutoScale(false); 
   graphic.XAxis().Min(0.0); 
   graphic.XAxis().Max(1.0); 
   graphic.XAxis().DefaultStep(0.1); 
//--- sets the Y-axis properties 
   graphic.YAxis().AutoScale(false); 
   graphic.YAxis().Min(0.0); 
   graphic.YAxis().Max(1.1); 
   graphic.YAxis().DefaultStep(0.1); 
//--- plot 
   graphic.CurvePlotAll(); 
   graphic.Update(); 
  }

Запустим скрипт на графике:

Изображение 1. Функции принадлежности.

Я выбрал эти функции принадлежности, поскольку в них всего по 2 входных параметра, которые можно оптимизировать (этим мы займемся дальше, на этапе тестирования системы). Также они хорошо описывают крайние и центральное положение системы. Вы можете применять любые функции принадлежности из набора доступных в библиотеке Fuzzy.

Возьмем за правило, что положение осциллятора на экстремумах свидетельствует о скорой смене его направления и, следовательно, о скором развороте тенденции. Поэтому приближение осциллятора к нолю подскажет о возможности начала роста. Движение осциллятора к отметке 0,5 сопровождается постепенным убыванием CZ_ShapedMembershipFunction  или терма "Buy zone". В то же время будет нарастать неопределенность CNormalMembershipFunction "Neutral zone", которая по мере приближения осциллятора к 1, в конечном итоге сменится нарастанием CS_ShapedMembershipFunction  или "Sell zone". По такому принципу устроены все входы и выход, которые будут возвращать принадлежность показания индикатора к той или иной зоне, имеющей нечеткие границы.

Нет никаких ограничений на количество функций принадлежности для каждой переменной. Вы можете задать 5, 7, 15 функций вместо трех, но естественно, в пределах здравого смысла и во имя нечеткой логики.

Формирование базы правил системы нечеткого вывода

На данном этапе добавим в систему базу знаний, на которую она будет ориентироваться в принятии нечетких решений.

   rule1 = OurFuzzy.ParseRule("if (rsi1 is buy) and (rsi2 is buy) and (rsi3 is buy) then (out is buy)");
   rule2 = OurFuzzy.ParseRule("if (rsi1 is sell) and (rsi2 is sell) and (rsi3 is sell) then (out is sell)");
   rule3 = OurFuzzy.ParseRule("if (rsi1 is neutral) and (rsi2 is neutral) and (rsi3 is neutral) then (out is neutral)"); 
   
   rule4 = OurFuzzy.ParseRule("if (rsi1 is buy) and (rsi2 is sell) and (rsi3 is buy) then (out is neutral)");
   rule5 = OurFuzzy.ParseRule("if (rsi1 is sell) and (rsi2 is sell) and (rsi3 is buy) then (out is neutral)");
   rule6 = OurFuzzy.ParseRule("if (rsi1 is buy) and (rsi2 is buy) and (rsi3 is sell) then (out is neutral)"); 
   
   rule7 = OurFuzzy.ParseRule("if (rsi1 is buy) and (rsi2 is buy) and (rsi3 is neutral) then (out is buy)");
   rule8 = OurFuzzy.ParseRule("if (rsi1 is sell) and (rsi2 is sell) and (rsi3 is neutral) then (out is sell)");
   rule9 = OurFuzzy.ParseRule("if (rsi1 is buy) and (rsi2 is neutral) and (rsi3 is buy) then (out is buy)");
   rule10 = OurFuzzy.ParseRule("if (rsi1 is sell) and (rsi2 is neutral) and (rsi3 is sell) then (out is sell)");
   rule11 = OurFuzzy.ParseRule("if (rsi1 is neutral) and (rsi2 is buy) and (rsi3 is buy) then (out is buy)");
   rule12 = OurFuzzy.ParseRule("if (rsi1 is neutral) and (rsi2 is sell) and (rsi3 is sell) then (out is sell)");

   OurFuzzy.Rules().Add(rule1);
   OurFuzzy.Rules().Add(rule2);
   OurFuzzy.Rules().Add(rule3);
   OurFuzzy.Rules().Add(rule4);
   OurFuzzy.Rules().Add(rule5);
   OurFuzzy.Rules().Add(rule6);
   OurFuzzy.Rules().Add(rule7);
   OurFuzzy.Rules().Add(rule8);
   OurFuzzy.Rules().Add(rule9);
   OurFuzzy.Rules().Add(rule10);
   OurFuzzy.Rules().Add(rule11);
   OurFuzzy.Rules().Add(rule12);


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

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

Если все 3 индикатора находятся в нечеткой зоне покупок, то на выходе будет нечеткий сигнал на покупку, то же самое для продаж и нейтрального сигнала. (правила 1-3)

Если 2 индикатора показывают покупку, а один продажу, то выходное значение будет нейтральным, то есть мы не уверены.  (правила 4-6)

Если 2 индикатора показывают покупку или продажу, а один нейтрален, то присваиваем выходу покупку или продажу (правила 7-12)

Это, конечно, не единственный вариант создания базы правил, вы можете экспериментировать. Данная база правил основана лишь на моей "'экспертной" оценке и видении того, как система должна функционировать.

Получение четкого значения выхода после дефаззификации

Осталось посчитать нашу модель и получить результат в виде значения от 0 до 1. Значения, близкие к 0, будут сигнализировать о сильном сигнале на покупку, близкие к 0.5 — о нейтральности, близкие к 1 — о сильном сигнале на продажу.

double CalculateMamdani()

{
 CopyBuffer(hnd1,0,0,1,arr1);
 NormalizeArrays(arr1);
   
 CopyBuffer(hnd2,0,0,1,arr2);
 NormalizeArrays(arr2);
    
 CopyBuffer(hnd3,0,0,1,arr3);
 NormalizeArrays(arr3);
     
 firstTerm.SetAll(firstInput,arr1[0]);
 secondTerm.SetAll(secondInput,arr2[0]);
 thirdTerm.SetAll(thirdInput,arr2[0]);
       
 Inputs.Clear(); 
 Inputs.Add(firstTerm);
 Inputs.Add(secondTerm);
 Inputs.Add(thirdTerm);
      
 CList *FuzzResult=OurFuzzy.Calculate(Inputs);
 Output=FuzzResult.GetNodeAtIndex(0);
 double res = Output.Value();
 delete FuzzResult;

 return(res);
}

В данной функции получаем значения трёх осцилляторов RSI с разными периодами, нормализуем в диапазон от 0 до 1 (можно просто разделить значение на 100), обновляем список с объектами словаря Fuzzy (последними значениями индикаторов), отправляем его на расчеты, создаем список для выходной переменной и принимаем результат в переменную res.

Добавление служебных функций и оптимизация\тестирование полученной системы

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

input string Fuzzy_Setings;        //Fuzzy optimization settings
input double Gsigma = 0.5;         //sigma From 0.05 to 0.5 with 0.05 step
input double Gposition=0.5;        //position From 0.0 to 1.0 with 0.1 step
input double MinNeutralSignal=0.4; //MinNeutralSignal from 0.3 to 0.5 with 0.1 step
input double MaxNeutralSignal=0.6; //MaxNeutralSignal from 0.5 to 0.7 with 0.1 step

Оптимизации подвергнутся параметры гауссианы (функции принадлежности) на выходе из нечеткой логики. Мы будем смещать ее центр по оси Х (параметр Gposition), изменять сигму (сужать и сжимать колокол, параметр Gsigma). Так мы получим более тонкую настройку системы в случае, если сигналы RSI окажутся несимметричными для покупок и продаж.

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

Отобразим обработку сигнала на выходе из нечеткой логики в следующем листинге:

void OnTick()
  {
//---
   if(!isNewBar())
     {  
      return;
     }
     
   double TradeSignal=CalculateMamdani(); 
   
   if(CountOrders(0)!=0 || CountOrders(1)!=0)                                           //если уже есть открытые позиции
      {
       for(int b=OrdersTotal()-1; b>=0; b--)
         {
          if(OrderSelect(b,SELECT_BY_POS)==true)
           {
            if(OrderSymbol()==_Symbol && OrderMagicNumber()==OrderMagic)
             {
              if(OrderType()==OP_BUY && TradeSignal>=MinNeutralSignal)                  //выбран ордер на покупку и торговый сигнал больше, чем левая граница нейтрального сигнала
               {                                                                        //то есть существует либо нейтральный сигнал, либо сигнал на продажу
                if(OrderClose(OrderTicket(),OrderLots(),OrderClosePrice(),0,Red))       //то закрываем позицию на покупку
                 {
                  if(TradeSignal>MaxNeutralSignal)                     //если ордер закрыт и существует сигнал на продажу (вышел за правую границу нейтрального сигнала) то тут же открываем позицию на продажу
                  {
                   lots = LotsOptimized(); 
                   if(OrderSend(Symbol(),OP_SELL,lots,SymbolInfoDouble(_Symbol,SYMBOL_BID),0,0,0,NULL,OrderMagic,Red)){
                     };
                  }
                 }
               }
               if(OrderType()==OP_SELL && TradeSignal<=MaxNeutralSignal)                 //выбран ордер на продажу....///.... то же самое, что для позиций на покупку но все условия зеркальны
               {
                if(OrderClose(OrderTicket(),OrderLots(),OrderClosePrice(),0,Red))
                 {
                  if(TradeSignal<MinNeutralSignal)
                  {
                   lots = LotsOptimized(); 
                   if(OrderSend(Symbol(),OP_BUY,lots,SymbolInfoDouble(_Symbol,SYMBOL_ASK),0,0,0,NULL,OrderMagic,Green)){
                    };
                  }
                 }
               }
             }
            }
          }
         return;
        }
                 //если позиций нет, открываем позиции по сигналам
   lots = LotsOptimized(); 
   if(TradeSignal<MinNeutralSignal && CheckMoneyForTrade(_Symbol,lots,ORDER_TYPE_BUY))
     { 
      if(OrderSend(Symbol(),OP_BUY,lots,SymbolInfoDouble(_Symbol,SYMBOL_ASK),0,0,0,NULL,OrderMagic,Green)){
       };
     }
   else if(TradeSignal>MaxNeutralSignal && CheckMoneyForTrade(_Symbol,lots,ORDER_TYPE_SELL))
     {
      if(OrderSend(Symbol(),OP_SELL,lots,SymbolInfoDouble(_Symbol,SYMBOL_BID),0,0,0,NULL,OrderMagic,Red)){
       };
     }
   return;
     
  }

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

Если есть открытые позиции и сигнал противоречит текущей позиции или не определен, то закрываем ее. Если существует условие на открытие противоположной позиции — открываем.

Стоп-лосс в данной системе не используется, поскольку она переворотная, и закрытие\переоткрытие сделок происходит по сигналам.

В советнике используется библиотека MT4Orders для более простой работы с ордерами и для возможности быстрого переноса кода на язык mql4.

Процесс тестирования

Оптимизируем систему с настройками, изображенными на скриншотах, на таймфрейме М15, за 8 месяцев по ценам открытия, используя fast genetic based algorithm и критерий оптимизации Balance + max Profit.



Выберем наилучший результат оптимизации:

Сравним с тем, что было при тестировании четкой модели:

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

Было изначально:

Стало:


Оптимизируем систему с теми же настройками но уже на М5:

Сравним с тем, что было при тестировании четкой модели:

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

Было изначально:

Стало:

В обоих случаях мы получили смещение гауссианы (нейтральной зоны) в сторону покупок, и количество длинных позиций превалирует над количеством коротких. Это означает, что сигналы на покупку и продажу оказались несимметричными на данном конкретном участке истории, о чем мы не могли узнать, не проведя такой эксперимент. Вероятно, система из трёх индикаторов RSI чаще находилась в зоне перекупленности (в районе 1)  чем в зоне перепроданности (в районе 0), и оптимизация гауссианы помогла сгладить этот дисбаланс. Что же касается самого четкого вывода, то аналитически мне сложно представить, почему такая конфигурация выхода способствовала улучшению показателей торговой системы, потому что процесс дефаззификации по методу центра тяжести, в совокупности со всеми принадлежностями входов нечетким множествам, уже сам по себе является сложной системой.

Система оказалась достаточно устойчивой на протяжении 8 месяцев, хотя оптимизации подверглись всего 4 параметра. Причем их легко можно уменьшить до двух (Gsigma и Gposition), поскольку остальные 2 мало повлияли на результат и всегда находятся в районе 0.5. Будем считать, что это удовлетворительный результат для экспериментальной системы, которая должна показать, как можно уменьшить количество оптимизируемых параметров, введя элемент нечеткой логики в торговую систему. В противовес этому, нам бы пришлось создавать большое количество критериев оптимизации для четких правил, что увеличило бы трудоемкость разработки системы и количество оптимизируемых параметров.

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

Заключение

Нечеткая логика позволяет быстро создавать системы с нечеткими правилами, которые очень просто оптимизируются. При этом сложный процесс подбора параметров торговой системы происходит через генетическую оптимизацию, освобождая разработчика от рутины поиска торговой стратегии, разработки и алгоритмизации многочисленных правил торговой системы. В совокупности с другими способами машинного обучения (например, нейронными сетями) данный подход позволяет достичь внушительных результатов. Он уменьшает вероятность оверфиттинга и размерность входных данных (было 3 индикатора RSI с разными периодами, а стал один сигнал, более обобщенно и полно описывающий рыночную ситуацию, нежели каждый из индикаторов по отдельности).

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

Приведу закрепляющий пример. Допустим, у вас есть 3 желания: пойти на вечеринку, посмотреть фильм дома или спасти мир. Наибольший вес — у терма "посмотреть фильм дома", потому что вы и так дома, и вам не надо прилагать лишних усилий. Пойти на вечеринку можно, если кто-нибудь пригласит и заедет, но поскольку этого пока не произошло, шанс пойти средний. И, наконец, чтобы спасти мир, необходимо мобилизовать все свои сверхъестественные способности, надеть костюм супермена и сразиться с инопланетным монстром. Маловероятно что вы решите сделать это сегодня, а не отложите на завтра, поэтому шансы малы.

Нечеткий вывод будет примерно следующим: скорее всего, я останусь дома, а может быть, пойду на вечеринку, но уж точно сегодня не буду спасать мир. После дефаззификации мы могли бы оценить свои шансы по 10-балльной шкале, где 0 — остаюсь дома, 5 — иду на вечеринку, 10 — сражаюсь с монстром. Понятно, что четкий вывод был бы в пределах от 0 до 3, т.е., скорее всего вы останетесь дома. По такому же принципу работает наша торговая система: она сравнивает показания трёх индикаторов и с помощью логических условий определяет, какой вариант сейчас наиболее предпочтителен — покупка, продажа или посидеть на заборе.

Возможные пути улучшения данного примера (для самостоятельной разработки):

  • Увеличение количества входов и логических условий. Таким образом, мы повышаем емкость системы и даем ей возможность лучше адаптироваться к рынку.
  • Оптимизация не только параметров выходной гауссианы, но всех функций принадлежностей входов и выхода.
  • Оптимизация базы правил.
  • Оптимизация весов логических выражений.
  • Создание комитета из нескольких нечетких моделей, отвечающих за разные аспекты торговой системы.
  • Использование нечетких выводов в качестве предикторов ("фичей") и\или целевых переменных для нейронных сетей.

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (29)
Maxim Dmitrievsky
Maxim Dmitrievsky | 21 ноя 2017 в 09:40
yukoj2004:

HI  Maxim Dmitrievsky

backtester results vary different?


1MIN OLHC and  TICK have many different mainly MDD

TIME 2008.1.1~2017.10.31

Hi I was checked base algorithm (without optimization), the results are equal:


maybe you changed something in algorithm, for example you put stop losses close to price, and results will be different. Because if you use open prices only, the tester does not account for price changes between them.

yukoj2004
yukoj2004 | 21 ноя 2017 в 15:39

HI  Maxim Dmitrievsky

Sorry, my English is poor.

Thanks for your reply

I will try again

and i have interesting tpoic

fuzzy logic and a neural network.

wait you share this the topic

have  You try Reinforcement Learning?


Maxim Dmitrievsky
Maxim Dmitrievsky | 19 мар 2018 в 10:32
yukoj2004:

HI  Maxim Dmitrievsky

Sorry, my English is poor.

Thanks for your reply

I will try again

and i have interesting tpoic

fuzzy logic and a neural network.

wait you share this the topic

have  You try Reinforcement Learning?


Hi, reinforcement it's a next step, I think its important and interesting theme, but need some time for realize it.

Andre Lima
Andre Lima | 22 окт 2018 в 01:17

Hello,


This article has 3 terms: "buy", "sell" and "neutral".

when run Output.Value() we get a double value that correspond to Fuzzy result.

How I can convert this value to corresponding text term? or how I can get the text that correspond to the Fuzzy result?

I need the result like "buy" or "sell" or "neutral" 

Thanks

Maxim Dmitrievsky
Maxim Dmitrievsky | 22 окт 2018 в 08:37
Andre Lima:

Hello,


This article has 3 terms: "buy", "sell" and "neutral".

when run Output.Value() we get a double value that correspond to Fuzzy result.

How I can convert this value to corresponding text term? or how I can get the text that correspond to the Fuzzy result?

I need the result like "buy" or "sell" or "neutral" 

Thanks

Hi, I think you cant do this with fuzzy, only if you compare the double output of this words

Треугольный арбитраж Треугольный арбитраж

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

Создание и тестирование пользовательских символов в MetaTrader 5 Создание и тестирование пользовательских символов в MetaTrader 5

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

Глубокие нейросети (Часть IV). Создание, обучение и тестирование модели нейросети Глубокие нейросети (Часть IV). Создание, обучение и тестирование модели нейросети

В статье рассматриваются новые возможности пакета darch (v.0.12.0). Описаны результаты обучения глубокой нейросети с различными типами данных, структурой и последовательностью обучения. Проанализированы результаты.

Новый подход к интерпретации классической и обратной дивергенции Новый подход к интерпретации классической и обратной дивергенции

В статье рассмотрены классический метод построения дивергенции и отличный от него способ интерпретации. Этот новый метод интерпретации положен в основу торговой стратегии, которая описана в статье.