Применение нечеткой логики в трейдинге средствами MQL4

Alexander Fedosov | 8 октября, 2015

Введение

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

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


Описание функций принадлежности

Для ознакомления с общими понятиями теории нечеткой логики рекомендуется прочесть статью Введение в теорию нечеткой логики и изучить основы работы библиотеки FuzzyNet для MQL4, так как она будет использована для реализации примеров.

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


Треугольная функция принадлежности

Функция принадлежности в форме треугольника. Эта простая и наиболее часто применяемая функция задается следующей аналитической формулой:

Формула треугольной функции

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

Рис. 1. Треугольная функция принадлежности


Трапециевидная функция принадлежности

Функция принадлежности в форме трапеции. Задается в виде следующей формулы:

Трапецевидная функция принадлежности

Параметры трапециевидной функции принадлежности интерпретируются следующим образом:

Рис. 2. Трапециевидная функция принадлежности


Колоколообразная функция принадлежности

Функция принадлежности в виде симметричной кривой в форме колокола. Данная функция задается формулой:

Колокообразная функция принадлежности

Значения параметров которой интерпретируются так:

Рис. 3. Колоколообразная функция принадлежности


Сигмоидная функция принадлежности

Задается следующей формулой и применяется для задания монотонных функций принадлежности:

Сигмоидная функция принадлежности

Ее параметры следует интерпретировать следующим образом:

Рис. 4. Сигмоидная функция принадлежности


Пример реализации индикатора средствами библиотеки FuzzyNet для MQL4

В качестве примера я решил использовать индекс Среднего Направления Движения (Average Directional Movement Index) или ADX. Это трендовый индикатор, показывающий силу текущего тренда (зеленая толстая линия). Для начала введем четкие категории силы тренда (рис. 5), а именно:

Рис. 5. Пример работы и выбранная градация по силе тренда

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

Что же предлагает нечеткая логика для исправления этих недостатков?

Происходит следующее: заданные границы размываются, становятся нечеткими. То есть в приграничных зонах жестко заданных категорий появляется двойственность — сила тренда относится сразу к двум понятиям, но с разной степенью принадлежности. Это можно описать так: сила тренда в данный момент в меньшей степени (30%) похожа на слабый тренд, но в большей (70%) —  на средний. Человек охарактеризовал бы это так: тренд скорее средний, чем слабый. Я считаю, именно в этом состоит главное преимущество нечеткой логики — гибкость и вариативность оценки какого-либо изначально жестко заданного параметра. Для нашего примера с ADX я выбрал следующие описанные выше функции принадлежности:

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


Рис. 6. Описание силы тренда средствами нечеткой логики

Как видно на графике, теперь мы видим области, в которых одновременно присутствуют 2 категории тренда. В зоне 50-60 это одновременно слабый и средний тренд, а в области 60-70 — средний и сильный. Таким образом, для трех категорий мы определили терм-множество с заданными функциями принадлежности. Теперь, имея входные значения ADX, описанные функциями принадлежности, нам нужно определиться с тем, что же будет выходным значением, результатом дефаззификации, и выбрать алгоритм нечеткого логического вывода.

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

Так же, как и для силы тренда, введем три четкие категории по степени риска:

По аналогии с силой тренда опишем категории риска с помощью функций принадлежности:

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


Рис. 7. Описание степени риска средствами нечеткой логики


Реализуем описанные данные с помощью библиотеки FuzzyNet для MQL4:

//+------------------------------------------------------------------+
//|                                                    ADX_Fuzzy.mq4 |
//|                                                Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Alexander Fedosov"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
#property strict
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_color1 Green
#property indicator_color2 Red
#property indicator_minimum 0
#property indicator_maximum 10
//+------------------------------------------------------------------+
//| Connecting libraries                                             |
//+------------------------------------------------------------------+
#include <Math\FuzzyNet\MamdaniFuzzySystem.mqh>
//--- input parameters
input string  p1="==== Parameters ====";
input int    visual=100;             // Visual Period
input int    adx_period=10;          // ADX Period
//---
double Buffer1[],Buffer2[],adx,adx_di_minus,adx_di_plus;
int limit;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexStyle(0,DRAW_HISTOGRAM,0,2);
   SetIndexBuffer(0,Buffer1);
   SetIndexEmptyValue(0,0.0);
//---
   SetIndexStyle(1,DRAW_HISTOGRAM,0,2);
   SetIndexBuffer(1,Buffer2);
   SetIndexEmptyValue(1,0.0);
//---
   ArrayResize(Buffer1,visual);
   ArrayResize(Buffer2,visual);
   ArrayInitialize(Buffer1,0.0);
   ArrayInitialize(Buffer2,0.0);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   int count_bars=IndicatorCounted();
//---
   if(count_bars<0)
      return(-1);
//---
   if(Bars-1<adx_period)
      return(0);
//---
   for(int i=0; i<visual;i++)
     {
      adx=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_MAIN,i),_Digits);
      adx_di_plus=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_PLUSDI,i),_Digits);
      adx_di_minus=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_MINUSDI,i),_Digits);
      //---
      double r=(adx_di_plus-adx_di_minus);
      if(MathAbs(r)>10 && adx>=30.0)
         if(r>0)
            Buffer1[i]=mamdani(adx);
      else if(r<0)
         Buffer2[i]=mamdani(adx);
     }
   return(rates_total);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double mamdani(double v)
  {
   double res=0;
//--- Mamdani Fuzzy System  
   MamdaniFuzzySystem *fsRisk=new MamdaniFuzzySystem();
//--- Create input variables for the system
   FuzzyVariable *fsTrend=new FuzzyVariable("trend",30.0,100.0);
//---
   fsTrend.Terms().Add(new FuzzyTerm("weak", new TrapezoidMembershipFunction(20.0, 30.0, 50.0, 60.0)));
   fsTrend.Terms().Add(new FuzzyTerm("average", new GeneralizedBellShapedMembershipFunction(2.5,2.0,60.0)));
   fsTrend.Terms().Add(new FuzzyTerm("strong",new SigmoidalMembershipFunction(0.4,75.0)));
   fsRisk.Input().Add(fsTrend);
//--- Create Output
   FuzzyVariable *fvRisk=new FuzzyVariable("risk",2.0,10.0);
   fvRisk.Terms().Add(new FuzzyTerm("low", new TrapezoidMembershipFunction(1.0, 2.0, 3.0, 4.0)));
   fvRisk.Terms().Add(new FuzzyTerm("normal", new TriangularMembershipFunction(3.0, 4.0, 5.0)));
   fvRisk.Terms().Add(new FuzzyTerm("high", new SigmoidalMembershipFunction(6.0,5.0)));
   fsRisk.Output().Add(fvRisk);
//--- Create three Mamdani fuzzy rules
   MamdaniFuzzyRule *rule1 = fsRisk.ParseRule("if (trend is weak) then risk is low");
   MamdaniFuzzyRule *rule2 = fsRisk.ParseRule("if (trend is average) then risk is normal");
   MamdaniFuzzyRule *rule3 = fsRisk.ParseRule("if (trend is strong) then risk is high");
//--- Add three Mamdani fuzzy rules in the system
   fsRisk.Rules().Add(rule1);
   fsRisk.Rules().Add(rule2);
   fsRisk.Rules().Add(rule3);
//--- Set input value
   CList *in=new CList;
   Dictionary_Obj_Double *p_od_in=new Dictionary_Obj_Double;
   p_od_in.SetAll(fsTrend,v);
   in.Add(p_od_in);
//--- Get result
   CList *result;
   Dictionary_Obj_Double *p_od_out;
   result=fsRisk.Calculate(in);
   p_od_out=result.GetNodeAtIndex(0);
   res=NormalizeDouble(p_od_out.Value(),_Digits);
//---
   delete in;
   delete result;
   delete fsRisk;
   return res;
  }
//+------------------------------------------------------------------+

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

Вначале установим 2 буфера для применяемых гистограмм, их цвет и диапазон по оси ординат от нуля до максимально заданного значения риска, равного 10%.

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_color1 Green
#property indicator_color2 Red
#property indicator_minimum 0
#property indicator_maximum 10

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

//+------------------------------------------------------------------+
//| Connecting libraries FuzzyNet                                    |
//+------------------------------------------------------------------+
#include <Math\FuzzyNet\MamdaniFuzzySystem.mqh>
//--- input parameters
input string  p1="==== Parameters ====";
input int    visual=100;             // Visual Period
input int    adx_period=10;          // ADX Period

При инициализации устанавливаем, что наш индикатор будет в виде гистограмм.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexStyle(0,DRAW_HISTOGRAM,0,2);
   SetIndexBuffer(0,Buffer1);
   SetIndexEmptyValue(0,0.0);
//---
   SetIndexStyle(1,DRAW_HISTOGRAM,0,2);
   SetIndexBuffer(1,Buffer2);
   SetIndexEmptyValue(1,0.0);
//---
   ArrayResize(Buffer1,visual);
   ArrayResize(Buffer2,visual);
   ArrayInitialize(Buffer1,0.0);
   ArrayInitialize(Buffer2,0.0);
//---
   return(INIT_SUCCEEDED);
  }

В основном коде определяем основные показания индикатора ADX, а также в переменной r находим разницу между двумя индикаторами направленности тренда +DI и -DI. Вводим фильтр присутствия тренда как разницу по модулю значений +DI и -DI выше 10 и основное значение силы тренда выше 30 (нижняя граница слабого тренда). Далее определяем направление тренда исходя из знака переменной r и помещаем значение функции mamdani() в заданный заранее массив.

int count_bars=IndicatorCounted();
//---
   if(count_bars<0)
      return(-1);
//---
   if(Bars-1<adx_period)
      return(0);
//---
   for(int i=0; i<visual;i++)
     {
      adx=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_MAIN,i),_Digits);
      adx_di_plus=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_PLUSDI,i),_Digits);
      adx_di_minus=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_MINUSDI,i),_Digits);
      //---
      double r=(adx_di_plus-adx_di_minus);
      if(MathAbs(r)>10 && adx>=30.0)
         if(r>0)
            Buffer1[i]=mamdani(adx);
      else if(r<0)
         Buffer2[i]=mamdani(adx);
     }

Описание функции mamdani:

1. Создаем новую систему нечеткой логики типа Мамдани *fsRisk.

2. Добавляем в нее входную переменнную *fsTrend с установленным именем trend, минимальным и максимальным значениями 30 и 100.

//--- Mamdani Fuzzy System  
   MamdaniFuzzySystem *fsRisk=new MamdaniFuzzySystem();
//--- Create input variables for the system
   FuzzyVariable *fsTrend=new FuzzyVariable("trend",30.0,100.0);

3. Далее добавляем в нее нечеткие термы, описанные выше (рис. 6), с выбранными для каждой категории функциями принадлежности.

fsTrend.Terms().Add(new FuzzyTerm("weak", new TrapezoidMembershipFunction(30.0, 40.0, 50.0, 60.0)));
   fsTrend.Terms().Add(new FuzzyTerm("average", new GeneralizedBellShapedMembershipFunction(2.5,2.0,60.0)));
   fsTrend.Terms().Add(new FuzzyTerm("strong",new SigmoidalMembershipFunction(0.4,75.0)));
   fsRisk.Input().Add(fsTrend);

4. Проделаем пункты 2-3 для выходного значения: создаем переменную *fvRisk с именем risk и минимальными и максимальными значениями риска 2% и 10%.

//--- Create Output
   FuzzyVariable *fvRisk=new FuzzyVariable("risk",2.0,10.0);
   fvRisk.Terms().Add(new FuzzyTerm("low", new TriangularMembershipFunction(2.0, 3.0, 4.0)));
   fvRisk.Terms().Add(new FuzzyTerm("normal", new TriangularMembershipFunction(3.0, 4.0, 5.0)));
   fvRisk.Terms().Add(new FuzzyTerm("high", new SigmoidalMembershipFunction(6.0,5.0)));
   fsRisk.Output().Add(fvRisk);

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

//--- Create three Mamdani fuzzy rules
   MamdaniFuzzyRule *rule1 = fsRisk.ParseRule("if (trend is weak) then risk is low");
   MamdaniFuzzyRule *rule2 = fsRisk.ParseRule("if (trend is average) then risk is normal");
   MamdaniFuzzyRule *rule3 = fsRisk.ParseRule("if (trend is strong) then risk is high");

6. Добавляем наши правила в систему:

//--- Add three Mamdani fuzzy rules in the system
   fsRisk.Rules().Add(rule1);
   fsRisk.Rules().Add(rule2);
   fsRisk.Rules().Add(rule3);

7. Создаем списки для входных и выходных переменных и добавляем входной параметр v, который служит аргументом функции mamdani. То есть для всей функции mamdani создается система нечеткой логики с установленными входными и выходными нечеткими перемененными, а на вход подается значение индикатора ADX.

//--- Set input value
   CList *in=new CList;
   Dictionary_Obj_Double *p_od_in=new Dictionary_Obj_Double;
   p_od_in.SetAll(fsTrend,v);
   in.Add(p_od_in);
//--- Get result
   CList *result=new CList;
   Dictionary_Obj_Double *p_od_out=new Dictionary_Obj_Double;
   result=fsRisk.Calculate(in);
   p_od_out=result.GetNodeAtIndex(0);
   res=NormalizeDouble(p_od_out.Value(),_Digits);

8. Значением функции и результатом является переменная res, на основе которой и строится гистограмма.

adx=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_MAIN,i),_Digits);
      adx_di_plus=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_PLUSDI,i),_Digits);
      adx_di_minus=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_MINUSDI,i),_Digits);
      //---
      double r=(adx_di_plus-adx_di_minus);
      if(MathAbs(r)>10 && adx>=30.0)
         if(r>0)
            Buffer1[i]=mamdani(adx);
      else if(r<0)
         Buffer2[i]=mamdani(adx);

В итоге мы сможем наблюдать следующий визуальный результат работы индикатора:

Рис. 8. Работа индикатора

Как видно из его работы, при наличии тренда индикатор в виде гистограммы определенного цвета указывает на это, а высота показывает рекомендуемый процент риска от депозита. Вполне очевиден вопрос — а в чем же была бы разница, если бы этот же индикатор был реализован как обычно, с четкими интервалами? Для этого рассмотрим более детально следующий участок (рис. 9). На нем зеленой стрелкой указан столбец гистограммы, а слева — его численное значение и значение силы тренда ADX. Как определено нами ранее, значение ADX больше 70 — это сильный тренд, а при сильном тренде значение риска предлагается выше 5%. Но на рис. 9 мы прекрасно видим, что ADX = 69.7923, т.е. по строгим условиям это был бы еще средний тренд, и риск обязан быть ниже 5%, а здесь — 5,6406, т.е. выше.


Рис. 9. Демонстрация отличия нечеткой логики от стандартной

Это и есть работа нечеткой логики в действии — она определила, что хоть значение и ниже 70, но тренд в этой области больше похож на сильный, чем средний. В этом можно убедиться, посмотрев на рис. 6, где отлично видно, что при значении на оси абсцисс равном 69,7923 функция принадлежности сильного тренда имеет значение выше, чем функция среднего тренда. Поэтому наша система и предложила риск выше 5%, подойдя к оценке на границе категорий сильного и умеренного тренда более гибко, чем это сделала бы система с четкой логикой.


Пример реализации торгового эксперта средствами библиотеки FuzzyNet для MQL4

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

Смысл связи достаточно прост. Если RSI и AC — это параметры движения, то чем выше скорость, тем выше будет инерция данного движения, а значит, логично поставить тейк-профит побольше. Соответственно, при низкой скорости, т.е. несильном движении, нужно делать цели для прибыли скромнее, чтобы в стремлении получить больше, получить откат или смену тенденции и получить убыток. На рис. 10 представлена блок-схема для более ясного понимания применения нечеткой логики в данном торговом эксперте.


Рис. 10. Блок-схема применения нечеткой логики в советнике

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

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

Визуальное представление таково:


Рис. 11. Описание функциями принадлежности категорий значений RSI

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

В итоге мы получим следующую визуализацию:


Рис. 12. Описание функциями принадлежности категорий значений индекса RSI

Далее опишем вторую нечеткую модель из рис. 10 — нечеткую модель вычисления тейк-профита. Входные параметры этой модели уже описаны как выходные первой модели, а именно — нечеткого индекса RSI. Выходным же будет значение самого тейк-профита. Определим для него четкие категории:

Теперь опишем функциями принадлежности:

Соответственно, графическая реализация будет следующая:


Рис. 13. Описание функциями принадлежности категорий значений тейк-профита

Итак, мы определились со всеми параметрами, осталось реализовать это в торговом эксперте, в который мы добавим две нечеткие модели последовательного расчета значения тейк-профита и стоп-лосса, исходя из показаний индикатора RSI.

//+------------------------------------------------------------------+
//|                                                       tester.mq4 |
//|                                                Alexander Fedosov |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Alexander Fedosov"
#property strict
#include "trading.mqh" //вспомогательная библиотека для торговых операций
//+------------------------------------------------------------------+
//| Connecting libraries                                             |
//+------------------------------------------------------------------+
#include <Math\FuzzyNet\MamdaniFuzzySystem.mqh>
//+------------------------------------------------------------------+
//| Параметры советника                                              |
//+------------------------------------------------------------------+
input bool           Lot_perm=false;               // Лот от баланса?
input double         Risk = 2;                     // Риск депозита, %
input double         lt=0.01;                      // Лот
input int            magic=2356;                   // Маджик
input int            period=14;                    // Период индикатора RSI
input ENUM_TIMEFRAMES tf=PERIOD_CURRENT;           // Рабочий таймфрейм
//---
int index_rsi,index_ac;
double tkp,stl;
double rs,mdm;
CTrading tr;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   tr.Trading(magic,5,lt,Lot_perm,Risk);
   tr.exp_name="Tester Fuzzy Logic";
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Главная функция расчета                                          |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- проверка на то, что нет уже открытых ордеров
   if(!tr.isOpened(magic))
     {
      depth_trend();
      speed_ac();
      rs=tr.ND(iRSI(_Symbol,tf,period,PRICE_CLOSE,0),2);
      //--- проверка условий на покупку
      if(Buy() && rs<=85.0)
        {
         mdm = mamdani_rsi(rs);
         tkp = MathCeil(mamdani_tp(mdm));
         stl = MathCeil(tkp*0.43);
         if(tr.OpnOrd(OP_BUY,lt,(int)tkp,(int)stl))
            Print("RSI равно ",rs," TP равно ",tkp," SL равно ",stl);
        }
      //--- проверка условий на продажу
      if(Sell() && rs>=15.0)
        {
         mdm = mamdani_rsi(100-rs);
         tkp = MathCeil(mamdani_tp(mdm));
         stl = MathCeil(tkp*0.43);
         if(tr.OpnOrd(OP_SELL,lt,(int)tkp,(int)stl))
            Print("RSI равно ",rs," TP равно ",tkp," SL равно ",stl);
        }
     }
//--- есть открытые ордера?
   if(tr.isOpened(magic))
     {
      //--- проверяем и закрываем те ордера на продажу, которые удовлетворяют условиям закрытия
      if(Sell_close())
         tr.ClosePosAll(OP_SELL);
      //--- проверяем и закрываем те ордера на покупку, которые удовлетворяют условиям закрытия
      if(Buy_close())
         tr.ClosePosAll(OP_BUY);
     }
  }
//+------------------------------------------------------------------+
//| Функция определения глубины тренда                               |
//+------------------------------------------------------------------+
void depth_trend()
  {
//--- определение индекса на вход в рынок
   double rsi=iRSI(_Symbol,tf,period,PRICE_CLOSE,0);
//---
   index_rsi=0;
   if(rsi>90.0)
      index_rsi=4;
   else if(rsi>80.0)
      index_rsi=3;
   else if(rsi>70.0)
      index_rsi=2;
   else if(rsi>60.0)
      index_rsi=1;
   else if(rsi<10.0)
      index_rsi=-4;
   else if(rsi<20.0)
      index_rsi=-3;
   else if(rsi<30.0)
      index_rsi=-2;
   else if(rsi<40.0)
      index_rsi=-1;
  }
//+------------------------------------------------------------------+
//| Функция определения скорости тренда                              |
//+------------------------------------------------------------------+
void speed_ac()
  {
   double ac[];
   ArrayResize(ac,5);
   ArrayInitialize(ac,0.0);
   for(int i=0; i<5; i++)
      ac[i]=iAC(_Symbol,tf,i);
//---
   index_ac=0;
//--- индексы на покупку
   if(ac[0]>ac[1])
      index_ac=1;
   else if(ac[0]>ac[1] && ac[1]>ac[2])
      index_ac=2;
   else if(ac[0]>ac[1] && ac[1]>ac[2] && ac[2]>ac[3])
      index_ac=3;
   else if(ac[0]>ac[1] && ac[1]>ac[2] && ac[2]>ac[3] && ac[3]>ac[4])
      index_ac=4;
//--- индексы на продажу
   else if(ac[0]<ac[1])
      index_ac=-1;
   else if(ac[0]<ac[1] && ac[1]<ac[2])
      index_ac=-2;
   else if(ac[0]<ac[1] && ac[1]<ac[2] && ac[2]<ac[3])
      index_ac=-3;
   else if(ac[0]<ac[1] && ac[1]<ac[2] && ac[2]<ac[3] && ac[3]<ac[4])
      index_ac=-4;
  }
//+------------------------------------------------------------------+
//| Функция проверки условия на покупку                              |
//+------------------------------------------------------------------+
bool Buy()
  {
   return (((index_rsi==2 && index_ac>=1) || (index_rsi==3 && index_ac==1))?true:false);
  }
//+------------------------------------------------------------------+
//| Функция проверки условия на продажу                              |
//+------------------------------------------------------------------+
bool Sell()
  {
   return (((index_rsi==-2 && index_ac<=-1) || (index_rsi==-3 && index_ac==-1))?true:false);
  }
//+------------------------------------------------------------------+
//| Функция проверки условия закрытия позиции на покупку             |
//+------------------------------------------------------------------+
bool Buy_close()
  {
   return ((index_rsi>2 && index_ac<0)?true:false);
  }
//+------------------------------------------------------------------+
//| Функция проверки условия закрытия позиции на продажу             |
//+------------------------------------------------------------------+
bool Sell_close()
  {
   return ((index_rsi<-2 && index_ac>0)?true:false);
  }
//+------------------------------------------------------------------+
//| Функция нечеткой модели вычисления индекса RSI                   |
//+------------------------------------------------------------------+
double mamdani_rsi(double rsi)
  {
   double res=0;
//--- Mamdani Fuzzy System  
   MamdaniFuzzySystem *fsRSI=new MamdaniFuzzySystem();
//--- создание входных переменных для системы и определение термов
   FuzzyVariable *fsTrend=new FuzzyVariable("rsi",60.0,85.0);
//---
   fsTrend.Terms().Add(new FuzzyTerm("weak", new SigmoidalMembershipFunction(-0.75,67.5)));
   fsTrend.Terms().Add(new FuzzyTerm("average", new NormalMembershipFunction(72.5,2.2)));
   fsTrend.Terms().Add(new FuzzyTerm("strong", new NormalMembershipFunction(80.0,1.4)));
   fsRSI.Input().Add(fsTrend);
//--- создание выходных переменных для системы и определение термов
   FuzzyVariable *fsIndex=new FuzzyVariable("index",1.0,4.0);
   fsIndex.Terms().Add(new FuzzyTerm("low", new SigmoidalMembershipFunction(-11.0,1.5)));
   fsIndex.Terms().Add(new FuzzyTerm("normal", new NormalMembershipFunction(2.0,0.3)));
   fsIndex.Terms().Add(new FuzzyTerm("high", new SigmoidalMembershipFunction(6.0,3.0)));
   fsRSI.Output().Add(fsIndex);
//--- создание нечетких правил и добавление их в систему
   MamdaniFuzzyRule *rule1 = fsRSI.ParseRule("if (rsi is weak) then (index is low)");
   MamdaniFuzzyRule *rule2 = fsRSI.ParseRule("if (rsi is average) then (index is normal)");
   MamdaniFuzzyRule *rule3 = fsRSI.ParseRule("if (rsi is strong) then (index is high)");
   fsRSI.Rules().Add(rule1);
   fsRSI.Rules().Add(rule2);
   fsRSI.Rules().Add(rule3);
//--- настройка входных значений
   CList *in=new CList;
   Dictionary_Obj_Double *p_od_in=new Dictionary_Obj_Double;
   p_od_in.SetAll(fsTrend,rsi);
   in.Add(p_od_in);
//--- вывод результата
   CList *result=new CList;
   Dictionary_Obj_Double *p_od_out=new Dictionary_Obj_Double;
   result=fsRSI.Calculate(in);
   p_od_out=result.GetNodeAtIndex(0);
   res=NormalizeDouble(p_od_out.Value(),_Digits);
//---
   delete in;
   delete result;
   delete fsRSI;
   return res;
  }
//+------------------------------------------------------------------+
//| Функция нечеткой модели вычисления индекса Тейк-Профита          |
//+------------------------------------------------------------------+
double mamdani_tp(double ind_rsi)
  {
   double res=0;
//--- Mamdani Fuzzy System  
   MamdaniFuzzySystem *fsTP=new MamdaniFuzzySystem();
//--- создание входных переменных для системы и определение термов
   FuzzyVariable *fsIndex=new FuzzyVariable("index",1.0,4.0);
   fsIndex.Terms().Add(new FuzzyTerm("low", new SigmoidalMembershipFunction(-11.0,1.5)));
   fsIndex.Terms().Add(new FuzzyTerm("normal", new NormalMembershipFunction(2.0,0.3)));
   fsIndex.Terms().Add(new FuzzyTerm("high", new SigmoidalMembershipFunction(6.0,3.0)));
   fsTP.Input().Add(fsIndex);
//--- создание выходных переменных для системы и определение термов
   FuzzyVariable *fsProfit=new FuzzyVariable("TP",30.0,70.0);
   fsProfit.Terms().Add(new FuzzyTerm("minimal", new SigmoidalMembershipFunction(-0.8,37.5)));
   fsProfit.Terms().Add(new FuzzyTerm("average", new NormalMembershipFunction(50.0,3.0)));
   fsProfit.Terms().Add(new FuzzyTerm("maximal", new SigmoidalMembershipFunction(0.8,62.5)));
   fsTP.Output().Add(fsProfit);
//--- создание нечетких правил и добавление их в систему
   MamdaniFuzzyRule *rule1 = fsTP.ParseRule("if (index is low) then (TP is minimal)");
   MamdaniFuzzyRule *rule2 = fsTP.ParseRule("if (index is normal) then (TP is average)");
   MamdaniFuzzyRule *rule3 = fsTP.ParseRule("if (index is high) then (TP is maximal)");
   fsTP.Rules().Add(rule1);
   fsTP.Rules().Add(rule2);
   fsTP.Rules().Add(rule3);
//--- настройка входных значений
   CList *in=new CList;
   Dictionary_Obj_Double *p_od_in=new Dictionary_Obj_Double;
   p_od_in.SetAll(fsIndex,ind_rsi);
   in.Add(p_od_in);
//--- вывод результата
   CList *result=new CList;
   Dictionary_Obj_Double *p_od_out=new Dictionary_Obj_Double;
   result=fsTP.Calculate(in);
   p_od_out=result.GetNodeAtIndex(0);
   res=NormalizeDouble(p_od_out.Value(),_Digits);
//---
   delete in;
   delete result;
   delete fsTP;
   return res;
  }
//+------------------------------------------------------------------+

Теперь разберем основные изменения, которые были внесены в советник:

if(OrdersTotal()<1)
     {
      depth_trend();
      speed_ac();
      rs=tr.ND(iRSI(_Symbol,tf,period,PRICE_CLOSE,0),2);
      //--- проверка условий на покупку
      if(Buy() && rs<=85.0)
        {
         mdm = mamdani_rsi(rs);
         tkp = MathCeil(mamdani_tp(mdm));
         stl = MathCeil(tkp*0.43);
         if(tr.OpnOrd(OP_BUY,lt,tkp,stl))
            Print("RSI равно ",rs," TP равно ",tkp," SL равно ",stl);
        }
      //--- проверка условий на продажу
      if(Sell() && rs>=15.0)
        {
         mdm = mamdani_rsi(100-rs);
         tkp = MathCeil(mamdani_tp(mdm));
         stl = MathCeil(tkp*0.43);
         if(tr.OpnOrd(OP_SELL,lt,tkp,stl))
            Print("RSI равно ",rs," TP равно ",tkp," SL равно ",stl);
        }
     }

Если нет открытых ордеров с маджиком советника, то система с помощью функций depth_trend() и speed_ac() отслеживает параметры движения на рынке и при соответствии Buy() или Sell() входит. Далее, в момент удовлетворения условиям переменной mdm присваивается значение результата работы нечеткой модели, у которой на входе — текущее значение RSI, а на выходе — нечеткий индекс. В свою очередь, значение нечеткого индекса подается на вход второй нашей системы, выходом которой является тейк-профит в пунктах. Значение тейк-профита присваивается переменной tkp.

Коэффицинет 0,43 я взял исходя из максимального значения профита в 70 пунктов и взял стоп-лосс для него в 30. При успешном открытии ордера наш советник также показывает значения RSI, при котором он был открыт, а также вычисленные от его значения параметры стоп-лосс и тейк-профит. Это сделано лишь для удобства тестирования.

Стоит пояснить следущие моменты:

  1. При условии на продажу переменной mdm присваивается mamdani_rsi(100-rs). Сделано это потому, что диапазоны и их границы относительно минимума и максимума значения RSI (0 и 100) зеркальны.
  2. Второй момент — это два дополнительных условия: при покупке rs<=85 и аналогично при продаже rs>=15. Это сделано потому, что при создании входных переменных нечеткой модели вычисления индекса RSI границы установлены в 60-85, соответственно, зеркально для продажи крайнее значение как раз 15.

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


Рис. 14. Результаты работы торгового эксперта


Заключение

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