English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Простой пример построения индикатора с использованием нечеткой логики (Fuzzy Logic)

Простой пример построения индикатора с использованием нечеткой логики (Fuzzy Logic)

MetaTrader 5Трейдинг | 7 октября 2010, 18:25
9 507 3
Максим Востров
Максим Востров

Введение

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

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

1.  Леоненков А. "Нечеткое моделирование в среде MATLAB и fuzzyTECH".
2.  Бочарников В."Fuzzy-технология: Математические основы. Практика моделирования в экономике".


1. Основы нечеткой логики

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

Опишем функцию принадлежности для высказывания "горячий кофе": температуру кофе следует рассматривать в диапазоне от 0 до 100 градусов Цельсия по той простой причине, что при температуре меньше 0 градусов это будет лед, а выше 100 градусов - пар. Очевидно, что чашка кофе с температурой 20 градусов никак не может быть названа горячей, то есть функция принадлежности кофе к категории "горячий" равна 0, а вот чашка кофе с температурой 70 градусов уже однозначно принадлежит к категории "горячий" и соответственно значение функции в этом случае равно 1.

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

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


На рисунке, приведенном выше, изображена "кусочно-линейная" функция принадлежности.

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


Функции подобного вида мы и будем использовать для нашего индикатора.


2. Функция принадлежности

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

Для начала нам нужно определиться с граничными условиями. Путь граничным условием для определения «100% Тренд вверх» будет пересечение EMA с периодом 2, построенной по типичной цене (H+L+C)/3, с верхней границей конверта Envelopes  с параметрами 8, 0.08, SMA, Close., а «100% тренд вниз» с нижней границей. Все, что между ними, будем считать флетом.  Для  пущей важности добавим еще один конверт с параметрами 32, 0.15, SMA, Close. 

В итоге мы должны получить две идентичные функции принадлежности.  Сигналом к покупке будет ситуация, когда обе функции будут равны  1, к продаже соответственно -1.  Т.к. графики функции удобно строить в диапазоне от -1 до 1,  результирующий график получим путем среднего арифметического результатов двух функций F(x)= (f1(x)+f2(x))/2.

Вот так это выглядит на чарте:


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


Аналитически его можно записать в следующем виде:

,

где a и b верхняя и нижняя линии конверта соответственно, а х - значение EMA(2).

С функцией определились, теперь переходим к написанию кода индикатора.


3. Составляем программный код

Сначала определимся с тем, что и как будем рисовать.

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

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

Для этого будем использовать стиль рисования DRAW_COLOR_HISTOGRAM.

Для выдачи сигналов на покупку и продажу нарисуем прямоугольники синего и красного цветов над теми полосками гистограммы, значение которых равно 1 или -1.

Запускаем MetaEditor и приступаем. Создать->Пользовательский индикатор->Далее... Заполняем окошко "параметры":


 Создаем буферы:


Жмем кнопку "Готово", получаем исходный код и начинаем его дорабатывать.

Во-первых, определимся с количеством буферов. Семь у нас уже создал мастер (5 для данных, 2 для цвета). Нам понадобится еще 5.

#property indicator_minimum -1.4 // Ставим дробные значения
#property indicator_maximum 1.4  // мастер экспертов почему-то игнорирует дробные части
#property indicator_buffers 12   // Меняем значение с 7 на 12 (добавилось еще 5 буферов)
Редактируем входные параметры:
input string txt1="----------";
input int                  Period_Fast=8;
input ENUM_MA_METHOD        Method_Fast = MODE_SMA; /*Метод усреднения*/ //метод усреднения скользящей 
input ENUM_APPLIED_PRICE    Price_Fast  = PRICE_CLOSE;
input double               Dev_Fast=0.08;
input string txt2="----------";
input int                  Period_Slow=32;
input ENUM_MA_METHOD        Method_Slow = MODE_SMA;
input ENUM_APPLIED_PRICE    Price_Slow  = PRICE_CLOSE;
input double               Dev_Slow=0.15;  /*Параметр отклонения*/
input string txt3="----------";
input int                  Period_Signal=2;
input ENUM_MA_METHOD        Method_Signal = MODE_EMA;
input ENUM_APPLIED_PRICE    Price_Signal  = PRICE_TYPICAL;
input string txt4="----------";

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

Также очень радует возможность создания списков:


Резервируем переменные под хендлы индикаторов и индикаторные буферы:

int Envelopes_Fast;     // Быстрый конверт
int Envelopes_Slow;     // Медленный конверт
int MA_Signal;          // Сигнальная линия

double Env_Fast_Up[];   // Верхняя граница быстрого конверта
double Env_Fast_Dn[];   // Нижняя граница быстрого конверта

double Env_Slow_Up[];   // Верхняя граница медленного конверта
double Env_Slow_Dn[];   // Нижняя граница медленного конверта

double Mov_Sign[];      // Сигнальная линия

Теперь переходим в функцию OnInit().

Наведем немного красоты: зададим имя индикатору и уберем лишние десятичные нули:

IndicatorSetInteger(INDICATOR_DIGITS,1); // зададим точность отображения, большая нам не нужна 
string name;    // имя индикатора 
StringConcatenate(name, "FLE ( ", Period_Fast, " , ", Dev_Fast, " | ", Period_Slow, " , ", Dev_Slow, " | ", Period_Signal, " )"); 
IndicatorSetString(INDICATOR_SHORTNAME,name);

и добавим недостающие буферы:

SetIndexBuffer(7,Env_Fast_Up,INDICATOR_CALCULATIONS);
SetIndexBuffer(8,Env_Fast_Dn,INDICATOR_CALCULATIONS);
SetIndexBuffer(9,Env_Slow_Up,INDICATOR_CALCULATIONS);
SetIndexBuffer(10,Env_Slow_Dn,INDICATOR_CALCULATIONS);
SetIndexBuffer(11,Mov_Sign,INDICATOR_CALCULATIONS); 

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

Обратите внимание на то, как обьявляются индикаторы с буфером цвета:

SetIndexBuffer(4,SignalBuffer1,INDICATOR_DATA);     // Сперва все буферы индикатора 
SetIndexBuffer(5,SignalBuffer2,INDICATOR_DATA);     // т.к это Color Histogram2 то у него 2 буфера данных
SetIndexBuffer(6,SignalColors,INDICATOR_COLOR_INDEX);// а потом идет буфер цвета.

заполняем хендлы:

Envelopes_Fast = iEnvelopes(NULL,0,Period_Fast,0,Method_Fast,Price_Fast,Dev_Fast);
Envelopes_Slow = iEnvelopes(NULL,0,Period_Slow,0,Method_Slow,Price_Slow,Dev_Slow);
MA_Signal      = iMA(NULL,0,Period_Signal,0,Method_Signal,Price_Signal);

С функцией OnInit() все.

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

double Fuzzy(double x,double a, double c)
{
double F;
     if (a<x)          F=1;                // 100% Тренд вверх
else if (x<=a && x>=c)  F=(1-2*(a-x)/(a-c));// Флет
else if (x<c)           F=-1;               // 100% Тренд вниз
return (F);
}

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

Приступаем непосредственно к основной функции OnCalculate().

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

CopyBuffer(Envelopes_Fast,  // Хендл индикатора
           UPPER_LINE,      // Буфер индикатора
           0,              // Откуда начинаем 0 - с самого начала
           rates_total,    // Сколько копируем - Все 
           Env_Fast_Up);   // Буфер в который записываются значения
// - остальные по аналогии
CopyBuffer(Envelopes_Fast,LOWER_LINE,0,rates_total,Env_Fast_Dn);
CopyBuffer(Envelopes_Slow,UPPER_LINE,0,rates_total,Env_Slow_Up);
CopyBuffer(Envelopes_Slow,LOWER_LINE,0,rates_total,Env_Slow_Dn);
CopyBuffer(MA_Signal,0,0,rates_total,Mov_Sign);

 Здесь необходимо добавить код для оптимизации вычислений (пересчет только последнего бара)

// объявляем переменную start, в ней будет храниться индекс бара с которого будет осуществляться
// пересчет индикаторных буферов.

int start;              
if (prev_calculated==0// если ни один бар не просчитан
    {
    start = Period_Slow; // до этого значения не все индикаторы просчитаны поэтому исполнять код не имеет смысла
    }
else start=prev_calculated-1;

for (int i=start;i<rates_total;i++)
      {
      // Здесь будет записан весь оставшийся код
      }

Кода осталось совсем чуть-чуть.

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

double x = Mov_Sign[i]; // Сигнал
// Зададим параметры первой функции принадлежности:
double a1 = Env_Fast_Up[i]; // Верхняя граница
double b1 = Env_Fast_Dn[i];
// вычислим значение первой функции принадлежности и запишем ее в буфер
Rule1Buffer[i] = Fuzzy(x,a1,b1);
// Зададим параметры второй функции принадлежности:
double a2 = Env_Slow_Up[i]; // Верхняя граница
double b2 = Env_Slow_Dn[i];
// вычислим значение второй функции принадлежности и запишем ее в буфер
Rule2Buffer[i] = Fuzzy(x,a2,b2);

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

Теперь рассчитаем результирующее значение.

ResultBuffer[i] = (Rule1Buffer[i]+Rule2Buffer[i])/2;

Далее раскрасим полоски гистограммы соответствующими цветами: т.к. цветов пять, то ResultColors[i] может принимать значение от 0 до 4.

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

for (int ColorIndex=0;ColorIndex<=4;ColorIndex++) 
    { 
    if (MathAbs(ResultBuffer[i])>0.2*ColorIndex && MathAbs(ResultBuffer[i])<=0.2*(ColorIndex+1)) 
        { 
        ResultColors[i] = ColorIndex; 
        break; 
        } 
    }

Далее нарисуем сигнальные прямоугольники, нам понадобится стиль рисования DRAW_COLOR_HISTOGRAM2.

У него два буфера данных, между которыми строится полоска гистограммы и один буфер цвета.

Значения буферов данных всегда будут постоянными: 1.1 и 1.3 для сигнала на покупку, -1.1 и -1.3 для сигнала на продажу соответственно.

Значение EMPTY_VALUE будет соответствовать отсутствию сигнала.

      if (ResultBuffer[i]==1)
        {
        SignalBuffer1[i]=1.1;
        SignalBuffer2[i]=1.3;
        SignalColors[i]=1;
        }
      else if (ResultBuffer[i]==-1)
        {
        SignalBuffer1[i]=-1.1;
        SignalBuffer2[i]=-1.3;
        SignalColors[i]=0;
        }
      else
        {
        SignalBuffer1[i]=EMPTY_VALUE;
        SignalBuffer2[i]=EMPTY_VALUE;
        SignalColors[i]=EMPTY_VALUE;
        }

Жмем "Компилировать" и вуаля!



Заключение

Что можно сказать в заключении? В своей статье я затронул самый элементарнейший подход к нечеткой логике.

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


Думаю, у вас не составит труда написать для нее аналитическое выражение и найти подходящие условия.

Успехов!

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
Valerii Mazurenko
Valerii Mazurenko | 7 окт. 2010 в 19:53
Нечёткой логикой и не пахнет. Мало того, что функцию принадлежности вынесли из "канонического" диапазона [0,1] в [-1,1], так вычисление функции принадлежности представили как нечёткий индикатор, что не соответствует действительности. Этап деффазификации не пройден. Условия покупки/продажи выдуманы. Незачёт. Лучше переименовать статью в "Сила индикатора на основе вероятности" ибо нет тут нечёткой логики, а есть лишь вероятность, которую ещё тоже нужно нормировать
certus
certus | 13 апр. 2014 в 21:04
notused:
Нечёткой логикой и не пахнет. Мало того, что функцию принадлежности вынесли из "канонического" диапазона [0,1] в [-1,1], так вычисление функции принадлежности представили как нечёткий индикатор, что не соответствует действительности. Этап деффазификации не пройден. Условия покупки/продажи выдуманы. Незачёт. Лучше переименовать статью в "Сила индикатора на основе вероятности" ибо нет тут нечёткой логики, а есть лишь вероятность, которую ещё тоже нужно нормировать

+1

Зашел, чтобы написать то же самое, а тут Ваш исчерпывающий комментарий :) 

Rasoul Mojtahedzadeh
Rasoul Mojtahedzadeh | 6 июл. 2014 в 19:55
This indicator in not based on Fuzzy logic (or Fuzzy Inference System) nor an indicator based on probability theory. However, I like the idea of identifying 100% uptrend/downtrend of price action which is presented in this article.
Интервью с Берроном Паркером (ATC 2010) Интервью с Берроном Паркером (ATC 2010)
На протяжении почти всей первой недели Чемпионата эксперт Беррона Паркеразанимал лидирующую позицию, не подпуская к себе конкурентов. Привлекший внимание зрителей Чемпионата разработчик рассказывает о собственном опыте написания экспертов и сложностях перехода на MQL5. Беррон утверждает, что его советник настроен на трендовый рынок и в других условиях будет слаб. Тем не менее он верит, что эксперт покажет неплохой результат по итогам соревнования.
Технический анализ: Как мы анализируем? Технический анализ: Как мы анализируем?
Данная статья в краткой форме отражает отношение автора к таким явлениям, как перерисовывающие индикаторы, индикаторы Multi-timeframe и представление котировок при помощи японских свечей. Техника программирования в статье не затрагивается, статья носит общий характер.
MQL5: Руководство по тестированию и оптимизации советников MQL5: Руководство по тестированию и оптимизации советников
Первая часть статьи посвящена вопросам выявления и исправления различных ошибок в коде программ, написанных на MQL5. Во второй части статьи рассматриваются вопросы практического применения Тестера стратегий клиентского терминала MetaTrader 5. Показано, как пользоваться функционалом оптимизации входных параметров. Предлагаемые советы помогут вам в течении дня провести тестирование и оптимизацию ваших советников.
Обработчик события "новый бар" Обработчик события "новый бар"
Язык программирования MQL5 позволяет решать задачи на совершенно новом уровне. Даже те задачи, которые уже вроде имеют решения, благодаря объектно-ориентированному программированию могут подняться на качественно новый уровень. В данной статье специально взят простой пример проверки появления нового бара на графике, который был преобразован в достаточно мощный и универсальный инструмент. Какой? Читайте в статье.