
Простой пример построения индикатора с использованием нечеткой логики (Fuzzy Logic)
Введение
Применение различных методов для анализа финансовых рынков в последние годы начинает приобретать все большую популярность среди трейдеров. Хочется и мне внести свою лепту, я покажу, как можно получить неплохой индикатор, написав пару десятков строк кода. А заодно постараюсь вкратце рассказать, на чем же основана нечеткая логика.
Всем, кто заинтересуется этим материалом и захочет изучить его поглубже, советую прочитать следующие труды:
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; }
Жмем "Компилировать" и вуаля!
Заключение
Что можно сказать в заключении? В своей статье я затронул самый элементарнейший подход к нечеткой логике.
Пространства для творчества и экспериментов очень много. Например, можно воспользоваться функцией следующего вида:
Думаю, у вас не составит труда написать для нее аналитическое выражение и найти подходящие условия.
Успехов!





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Нечёткой логикой и не пахнет. Мало того, что функцию принадлежности вынесли из "канонического" диапазона [0,1] в [-1,1], так вычисление функции принадлежности представили как нечёткий индикатор, что не соответствует действительности. Этап деффазификации не пройден. Условия покупки/продажи выдуманы. Незачёт. Лучше переименовать статью в "Сила индикатора на основе вероятности" ибо нет тут нечёткой логики, а есть лишь вероятность, которую ещё тоже нужно нормировать
+1
Зашел, чтобы написать то же самое, а тут Ваш исчерпывающий комментарий :)