English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Быстрое тестирование торговых идей на графике

Быстрое тестирование торговых идей на графике

MetaTrader 5Торговые системы | 22 октября 2012, 10:59
10 477 17
Vladimir Kustikov
Vladimir Kustikov

Введение

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

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

Что популярно на ATC 2012:

  • торговля по различным графическим построениям (важные ценовые уровни, уровни поддержки/сопротивления, каналы) - 55;
  • анализ движения цены (в том числе и на разных таймфреймах) - 33;
  • системы слежения за трендом (думаю, что эти громкие слова скрывают какую-то сверхоптимизированную комбинацию скользящих средних, но вдруг это все-таки не так? :) ) - 31;
  • статистические ценовые паттерны - 10:
  • арбитраж, анализ корреляции валютных пар - 8;
  • анализ волатильности - 8;
  • нейросети - 7;
  • свечной анализ - 5;
  • усреднители - 5;
  • портфели стратегий - 5;
  • торговля по времени торговых сессий - 4;
  • торговля по ГСЧ - 4;
  • торговля на новостях - 3,
  • волны Эллиота - 2.

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

  • Moving Average - 75;
  • MACD - 54;
  • Stochastic Oscillator - 25;
  • RSI - 23;
  • Bollinger Bands - 19;
  • Fractals - 8;
  • CCI, ATR - по 7;
  • Zigzag, Parabolic SAR - по 6;
  • ADX - 5;
  • Momentum - 4;
  • собственные уникальные индикаторы (какая интрига :) ) - 4;
  • Ichimoku, AO - по 3;
  • ROC, WPR, StdDev, Volumes - по 2.

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

Вот тебе правила использования MACD, вот сигналы - иди коси капусту, только параметры оптимизируй. Думать? А зачем? Есть же классика, все уже придумано до нас. Только почему-то часто забывают, что те индикаторы, которыми мы сейчас пользуемся, придумали такие же люди, как мы с вами, и в их время были свои классики и авторитеты. Но кто знает, вдруг лет через десять стандартом станет индикатор, названный вашим именем?

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


Описание метода

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

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

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

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

Но, допустим, вы нашли новую закономерность, которая, как вам показалось, работает в вашу пользу. И что дальше? Самый простой способ - это написать советник, прогнать его по истории и убедиться в том, что ваше предположение верно. Но если это не так, обычный путь заключается в оптимизации параметров. Самое плохое в этом - мы не ответили на вопрос "Почему?" Почему советник сливает/наращивает депозит? Почему была такая огромная просадка? Без ответа на эти вопросы ваша идея так и останется сырой.

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

  1. Создаю или изменяю интересующий меня индикатор таким образом, чтобы он выдавал сигнал: -1 на продажу и 1 на покупку.
  2. Подключаю к графику индикатор баланса, который отображает точки входов и выходов из позиции, а также изменение баланса и эквити (в пунктах) при обработке этого сигнала.
  3. Анализирую, когда и как работают и не работают мои предположения.

У метода есть несомненные преимущества.

  • Во-первых, индикатор баланса целиком рассчитывается в методе OnCalculate, что обеспечивает максимальную скорость расчета и автоматическую доступность исторических данных во входных расчетных массивах.
  • Во-вторых, добавление сигнала к существующему индикатору является промежуточной ступенью между созданием эксперта в мастере и самостоятельным написанием советника.
  • В-третьих, идея и результат видны сразу на одном графике. Естественно, у этого метода есть свои ограничения - сигнал привязан к цене закрытия бара, баланс рассчитывается для постоянного лота, отсутствуют варианты торговли отложенными ордерами. Но все это легко исправить/дополнить.


Практическая реализация

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

Рисунок 1 - Свечные модели "молот" и "падающая звезда"

Рисунок 1. Свечные модели "молот" и "падающая звезда".

Теперь определимся с правилами входа в рынок при появлении модели "молот".

  1. Минимум свечи должен быть ниже, чем минимумы пяти предыдущих свечей;
  2. Тело свечи должно составлять не более 50% от всей высоты свечи;
  3. Верхняя тень свечи должна быть не более 0% от всей высоты свечи;
  4. Высота свечи должна быть не менее 100% от средней высоты пяти свеч до нее;
  5. Цена закрытия модели должна быть ниже скользящей средней с периодом 10.

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

  1. Максимум свечи должен быть выше, чем максимумы пяти предыдущих свечей;
  2. Тело свечи должно составлять не более 50% от всей высоты свечи;
  3. Нижняя тень свечи должна быть не более 0% от всей высоты свечи;
  4. Высота свечи должна быть не менее 100% от средней высоты пяти свеч до нее;
  5. Цена закрытия модели должна быть выше скользящей средней с периодом 10.

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

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

Теперь настало время немного попрограммировать. В Мастере MQL5 создадим новый пользовательский индикатор, назовем его PivotCandles и опишем логику его работы. Для того чтобы можно было подключить к нему индикатор баланса, определим следующие возвращаемые значения:

  • -1 - открытие позиции на продажу;
  • -2 - закрытие позиции на покупку;
  • 0 - нет сигнала;
  • 1 - открытие позиции на покупку;
  • 2 - закрытие позиции на продажу.

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

//+------------------------------------------------------------------+
//|                                            PivotCandlesClass.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input int      iMaxBodySize            = 50;  // Максимальное тело свечи, %
input int      iMaxShadowSize          = 0;   // Максимально допустимая малая тень свечи, %
input int      iVolatilityCandlesCount = 5;   // Количество предыдущих баров для расчета средней волатильности
input int      iPrevCandlesCount       = 5;   // Количество предыдущих баров, для которых текущая свеча должна быть экстремумом
input int      iVolatilityPercent      = 100; // Отношение сигнальной свечи к предыдущей волатильности, %
input int      iMAPeriod               = 10;  // Период сигнальной простой скользящей средней
//+------------------------------------------------------------------+
//| Class definition                                                 |
//+------------------------------------------------------------------+
class CPivotCandlesClass
  {
private:
   MqlRates          m_candles[];              // Массив для хранения необходимой для расчетов истории
   int               m_history_depth;          // Длина массива для хранения истории
   int               m_handled_candles_count;  // Количество уже обработанных свечей
   
   double            m_ma_value;               // Текущее рассчитанное значение скользящей средней
   double            m_prev_ma_value;          // Предыдущее рассчитанное значение скользящей средней
   bool              m_is_highest;             // Является ли текущая свеча максимальной
   bool              m_is_lowest;              // Является ли текущая свеча минимальной
   double            m_volatility;             // Средняя волатильность
   int               m_candle_pattern;         // Текущий распознанный паттерн
   
   void              PrepareArrayForNewCandle();        // Подготовка массива для приема новой свечи
   int               CheckCandleSize(MqlRates &candle); // Проверка на соответствие свечи моделям
   void              PrepareCalculation();
protected:
   int               DoAnalizeNewCandle();              // Расчетная функция
public:
   void              CPivotCandlesClass(); 
   
   void              CleanupHistory();                  // Очистка всех расчетных переменных  
   double            MAValue() {return m_ma_value;}     // Текущее значение скользящей средней
   int               AnalizeNewCandle(MqlRates& candle);
   int               AnalizeNewCandle( 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 );
  };
//+------------------------------------------------------------------+
//| CPivotCandlesClass                                               |
//+------------------------------------------------------------------+
//| Инициализация класса                                             |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::CPivotCandlesClass()
  {
   // Глубины истории должно хватить на все расчеты
   m_history_depth = (int)MathMax(MathMax(
      iVolatilityCandlesCount + 1, iPrevCandlesCount + 1), iMAPeriod);
   m_handled_candles_count = 0;
   m_prev_ma_value = 0;
   m_ma_value = 0;
   
   ArrayResize(m_candles, m_history_depth);
  }  
//+------------------------------------------------------------------+
//| CleanupHistory                                                   |
//+------------------------------------------------------------------+
//| Очистка свечного буфера для перерасчета                          |
//+------------------------------------------------------------------+
void CPivotCandlesClass::CleanupHistory()
  {
   // Очистка массива
   ArrayFree(m_candles);
   ArrayResize(m_candles, m_history_depth);
   
   // Обнуление расчетных переменных
   m_handled_candles_count = 0;
   m_prev_ma_value = 0;
   m_ma_value = 0;   
  }
//+------------------------------------------------------------------+
//| AnalizeNewCandle                                                 |
//+------------------------------------------------------------------+
//| Подготовка к анализу новой свечи и ее анализ                     |
//| на основе отдельных значений характеристик свечи                 |
//+------------------------------------------------------------------+
int CPivotCandlesClass::AnalizeNewCandle( 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 )
  {
   // Подготавливаем массив для записи новой свечи
   PrepareArrayForNewCandle();

   // Заполняем текущее значение свечи
   m_candles[0].time          = time;
   m_candles[0].open          = open;
   m_candles[0].high          = high;
   m_candles[0].low           = low;
   m_candles[0].close         = close;
   m_candles[0].tick_volume   = tick_volume;
   m_candles[0].real_volume   = volume;
   m_candles[0].spread        = spread;

   // Хватает ли данных для расчета?
   if (m_handled_candles_count < m_history_depth)
      return 0;
   else
      return DoAnalizeNewCandle();
  }  
//+------------------------------------------------------------------+
//| AnalizeNewCandle                                                 |
//+------------------------------------------------------------------+
//| Подготовка к анализу новой свечи и ее анализ                     |
//| на основе полученной свечи                                       |
//+------------------------------------------------------------------+
int CPivotCandlesClass::AnalizeNewCandle(MqlRates& candle)
  {
   // Подготавливаем массив для записи новой свечи   
   PrepareArrayForNewCandle();

   // Добавляем свечу 
   m_candles[0] = candle;

   // Хватает ли данных для расчета?
   if (m_handled_candles_count < m_history_depth)
      return 0;
   else
      return DoAnalizeNewCandle();
  }
//+------------------------------------------------------------------+
//| PrepareArrayForNewCandle                                         |
//+------------------------------------------------------------------+ 
//| Подготовка массива к приему новой свечи                          |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::PrepareArrayForNewCandle()
  {
   // Сдвигаем массив на одну позицию, чтобы записать туда новое значение
   ArrayCopy(m_candles, m_candles, 1, 0, m_history_depth-1);
   
   // Увеличиваем счетчик добавленных свеч
   m_handled_candles_count++;
  }
//+------------------------------------------------------------------+
//| CalcMAValue                                                      |
//+------------------------------------------------------------------+ 
//| Расчет текущих значений скользящей средней, волатильности        |
//|   и экстремальности значения                                     |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::PrepareCalculation()
  {
   // Запоминаем предыдущее значение
   m_prev_ma_value = m_ma_value;
   m_ma_value = 0;
   
   m_is_highest = true; 	// является ли текущая свеча максимальной
   m_is_lowest = true;  	// является ли текущая свеча минимальной
   m_volatility = 0;  	// средняя волатильность
   
   double price_sum = 0; // Переменная для запоминания суммы
   for (int i=0; i<m_history_depth; i++)
     {
      if (i<iMAPeriod)
         price_sum += m_candles[i].close;
      if (i>0 && i<=iVolatilityCandlesCount)
         m_volatility += m_candles[i].high - m_candles[i].low;
      if (i>0 && i<=iPrevCandlesCount)
        {
         m_is_highest = m_is_highest && (m_candles[0].high > m_candles[i].high);
         m_is_lowest = m_is_lowest && (m_candles[0].low < m_candles[i].low);
        }
     }
   m_ma_value = price_sum / iMAPeriod;
   m_volatility /= iVolatilityCandlesCount;
   
   m_candle_pattern = CheckCandleSize(m_candles[0]);
  }
//+------------------------------------------------------------------+
//| CheckCandleSize                                                  |
//+------------------------------------------------------------------+
//| Проверка, соответствуют ли размеры свечи паттернам               |
//| Функция должна вернуть:                                          |
//|   0 - свеча не соответствует данным моделям                      |
//|   1 - найдена модель "молот"                                     |
//|   -1 - найдена модель "падающая звезда"                          |
//+------------------------------------------------------------------+ 
int CPivotCandlesClass::CheckCandleSize(MqlRates &candle)
  {
   double candle_height=candle.high-candle.low;          // полная высота свечи
   double candle_body=MathAbs(candle.close-candle.open); // высота тела свечи

   // Проверка на то, что свеча имеет маленькое тело
   if(candle_body/candle_height*100.0>iMaxBodySize)
      return 0;

   double candle_top_shadow=candle.high-MathMax(candle.open,candle.close);   // высота верхнего тела свечи
   double candle_bottom_shadow=MathMin(candle.open,candle.close)-candle.low; // высота нижнего тела свечи

   // Если верхняя тень очень маленькая, это - модель "молот"
   if(candle_top_shadow/candle_height*100.0<=iMaxShadowSize)
      return 1;
   // Если нижняя тень очень маленькая, это - модель "падающая звезда"
   else if(candle_bottom_shadow/candle_height*100.0<=iMaxShadowSize)
      return -1;
   else
      return 0;
  }
//+------------------------------------------------------------------+
//| DoAnalizeNewCandle                                               |
//+------------------------------------------------------------------+
//| Реальный анализ соответствия моделям                             |
//+------------------------------------------------------------------+ 
int CPivotCandlesClass::DoAnalizeNewCandle()
  {
   // Подготовим данные для анализа текущей ситуации
   PrepareCalculation();
   
   // Обработаем подготовленные данные и установим выходной сигнал
   int signal = 0;
   
   ///////////////////////////////////////////////////////////////////
   // СИГНАЛЫ ВЫХОДА ИЗ ПОЗИЦИИ                                     //
   ///////////////////////////////////////////////////////////////////
   // Если цена пересекла скользящую среднюю сверху вниз, закрываем сделку на продажу
   if(m_candles[1].close > m_prev_ma_value && m_candles[0].close < m_ma_value)
      signal = 2;
   // Если цена пересекла скользящую среднюю снизу вверх, закрываем сделку на покупку 
   else if (m_candles[1].close < m_prev_ma_value && m_candles[0].close > m_ma_value)
      signal = -2;
      
   ///////////////////////////////////////////////////////////////////
   // СИГНАЛЫ ВХОДА В ПОЗИЦИЮ                                       //
   ///////////////////////////////////////////////////////////////////
   // Проверка, что соблюдается условие минимальной волатильности
   if (m_candles[0].high - m_candles[0].low >= iVolatilityPercent / 100.0 * m_volatility)
     {
      // Проверки для модели "падающая звезда"
      if (m_candle_pattern < 0 && m_is_highest && m_candles[0].close > m_ma_value)
         signal = -1;
      // Проверки для модели "молот"
      else if (m_candle_pattern > 0 && m_is_lowest && m_candles[0].close < m_ma_value)
         signal = 1;
     }
     
   return signal;
  }
//+------------------------------------------------------------------+

Видно, что вся расчетная часть обеспечивается классом CPivotCandlesClass. Считается хорошим тоном программирования отделять расчетную часть от визуальной, и я по мере своих сил стараюсь следовать этому правилу. И дивиденды не заставляют себя ждать - вот каким получился код самого индикатора:

//+------------------------------------------------------------------+
//|                                                 PivotCandles.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window

// Будем использовать четыре буфера, а рисовать два
#property indicator_buffers 4
#property indicator_plots   2
//--- plot SlowMA
#property indicator_label1  "SlowMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAliceBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot ChartSignal
#property indicator_label2  "ChartSignal"
#property indicator_type2   DRAW_COLOR_ARROW
#property indicator_color2  clrLightSalmon,clrOrangeRed,clrBlack,clrSteelBlue,clrLightBlue
#property indicator_style2  STYLE_SOLID
#property indicator_width2  3

#include <PivotCandlesClass.mqh>
//+------------------------------------------------------------------+
//| Common arrays and structures                                     |
//+------------------------------------------------------------------+
//--- Indicator buffers                                                
double   SMA[];            // Значения скользящей средней
double   Signal[];         // Значения сигналов
double   ChartSignal[];    // Положение сигналов на графике
double   SignalColor[];    // Массив цветов сигналов
//--- Calculation class
CPivotCandlesClass PivotCandlesClass;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,SMA,INDICATOR_DATA);
   SetIndexBuffer(1,ChartSignal,INDICATOR_DATA);
   SetIndexBuffer(2,SignalColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(3,Signal,INDICATOR_CALCULATIONS);

//--- установим в качестве пустого значения 0
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);

   return(0);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
   // Если расчетов еще не было или (!) подгрузилась новая история, очищаем расчетный объект
   if (prev_calculated == 0)
      PivotCandlesClass.CleanupHistory();
   
   int end_calc_edge = rates_total-1;   
   if (prev_calculated >= end_calc_edge)
      return end_calc_edge;
   
   for(int i=prev_calculated; i<end_calc_edge; i++)
     {
      int signal = PivotCandlesClass.AnalizeNewCandle(time[i],open[i],high[i],low[i],close[i],tick_volume[i],volume[i],spread[i]);
      Signal[i] = signal;
      SMA[i] = PivotCandlesClass.MAValue();
      
      // Сигналы обработаны, осталось вывести их на график
      // Установим положение наших сигналов..
      if (signal < 0)
         ChartSignal[i]=high[i];
      else if (signal > 0)
         ChartSignal[i]=low[i];
      else
         ChartSignal[i]=0;
      // .. а также их цвет
      // Сигналы имеют диапазон [-2..2], а цветовые индексы [0..4]. Выравниваем их 
      SignalColor[i]=signal+2;
     }
   
   // Для того, чтобы график средней не уходил в подземелье, установим его значение как предыдущее
   SMA[end_calc_edge] = SMA[end_calc_edge-1];

//--- return value of prev_calculated for next call
   return(end_calc_edge);
  }
//+------------------------------------------------------------------+

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

Рисунок 2. Индикатор свечных паттернов "молот" и "падающая звезда".

Рисунок 2. Индикатор свечных паттернов "молот" и "падающая звезда".

Цветными точками индикатор показывает возможные входы и выходы из рынка. Цвета выбраны следующим образом:

  • темно-красный - продажа;
  • темно-синий - покупка;
  • светло-красный - закрытие позиции на покупку;
  • светло-красный - закрытие позиции на продажу.

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

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

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

//+------------------------------------------------------------------+
//|                                                 BalanceClass.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Common structures                                                |
//+------------------------------------------------------------------+
// Структура для того, чтобы результаты расчета можно было вернуть 
// одной командой return;
struct BalanceResults
  {
   double balance;
   double equity;
  };
//+------------------------------------------------------------------+
//| Common function                                                  |
//+------------------------------------------------------------------+
//  Функция поиска хэндла индикатора по его имени
int FindIndicatorHandle(string _name)
  {
   // Получаем количество открытых графиков
   int windowsCount = (int)ChartGetInteger(0,CHART_WINDOWS_TOTAL);
   
   // Пройдемся по ним всем
   for(int w=windowsCount-1; w>=0; w--)
     {
      // Сколько индикаторов привязано к текущему чарту
      int indicatorsCount = ChartIndicatorsTotal(0,w);

      // Поиск по всем индикаторам чарта
      for(int i=0;i<indicatorsCount;i++)
        {
         string name = ChartIndicatorName(0,w,i);
         // Если такой индикатор найден, возвращаем его хэндл
         if (name == _name)
            return ChartIndicatorGet(0,w,name);
        }
     }  
     
   // Если такого индикатора не нашлось, возвращаем некорректный хэндл 
   return -1;
  }
//+------------------------------------------------------------------+
//| Base calculation class                                           |
//+------------------------------------------------------------------+
class CBaseBalanceCalculator
  {
private:
   double            m_position_volume; // Текущий открытый объем позиции
   double            m_position_price;  // Цена, по которой позиция была открыта
   double            m_symbol_points;   // Значение одного пункта для текущего символа
   BalanceResults    m_results;         // Результаты расчета
public:
   void              CBaseBalanceCalculator(string symbol_name = "");
   void              Cleanup();
   BalanceResults    Calculate( const double _prev_balance, 
                                const int    _signal,
                                const double _next_open,
                                const double _next_spread );
  };
//+------------------------------------------------------------------+
//| CBaseBalanceCalculator                                |
//+------------------------------------------------------------------+
void CBaseBalanceCalculator::CBaseBalanceCalculator(string symbol_name = "")
  {
   // Очистка переменных состояния
   Cleanup();
   
   // Определение размера пункта (так как прибыль мы будем считать в пунктах)
   if (symbol_name == "")
      m_symbol_points = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   else 
      m_symbol_points = SymbolInfoDouble(symbol_name, SYMBOL_POINT);
  }
//+------------------------------------------------------------------+
//| Cleanup                                                          |
//+------------------------------------------------------------------+
//| Очистка информации о позициях и ценах                            |
//+------------------------------------------------------------------+
void CBaseBalanceCalculator::Cleanup()
  {
   m_position_volume = 0;
   m_position_price = 0;  
  }
//+------------------------------------------------------------------+
//| Calculate                                                        |
//+------------------------------------------------------------------+
//| Основной расчетный блок                                          |
//+------------------------------------------------------------------+
BalanceResults CBaseBalanceCalculator::Calculate(
                                       const double _prev_balance,
                                       const int _signal,
                                       const double _next_open,
                                       const double _next_spread )
  {
   // Очищаем выходную структуру от пр
   ZeroMemory(m_results);
   
   // Инициализация вспомогательных переменных
   double current_price = 0; // текущая цена (bid или ask в зависимости от направления позиции)
   double profit = 0;        // расчетное значение прибыли
   
   // Если сигнала не было, баланс остается тем же самым 
   if (_signal == 0)
      m_results.balance = _prev_balance;
   // сигнал совпадает по направлению или позиций еще не было открыто
   else if (_signal * m_position_volume >= 0)
     {
      // Позиция уже есть, игнорируем сигнал
      if (m_position_volume != 0)
         // Баланс не меняется
         m_results.balance = _prev_balance;
      // Позиции еще нет, сигнал на покупку

      else if (_signal == 1)
        {
         // Рассчитываем текущую цену ASK, пересчитываем цену, объем и баланс
         current_price = _next_open + _next_spread * m_symbol_points;
         m_position_price = (m_position_volume * m_position_price + current_price) / (m_position_volume + 1);
         m_position_volume = m_position_volume + 1;
         m_results.balance = _prev_balance;
        }
      // Позиции еще нет, сигнал на продажу
      else if (_signal == -1) 
        {
         // Рассчитываем текущую цену BID, пересчитываем цену, объем и баланс
         current_price = _next_open;
         m_position_price = (-m_position_volume * m_position_price + current_price) / (-m_position_volume + 1);
         m_position_volume = m_position_volume - 1; 
         m_results.balance = _prev_balance;      
        }
      else
         m_results.balance = _prev_balance;
     }
   // Позиция уже установлена, получен сигнал, противоположный по направлению
   else 
     {
      // сигнал на покупку/закрытие продажи
      if (_signal > 0)
        {
         // Закрываем позицию по цене ASK, пересчитываем профит и баланс
         current_price = _next_open + _next_spread * m_symbol_points;
         profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
         m_results.balance = _prev_balance + profit;
          
         // Если это сигнал на открытие новой позиции, сразу же и открываемся
         if (_signal == 1)
           {
            m_position_price = current_price;
            m_position_volume = 1;
           }
         else
            m_position_volume = 0;
        }
      // сигнал на продажу/закрытие покупки
      else 
        {
         // Закрываем позицию по цене BID, пересчитываем профит и баланс
         current_price = _next_open;
         profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
         m_results.balance = _prev_balance + profit;
         
         // Если это сигнал на открытие новой позиции, сразу же и открываемся
         if (_signal == -1)
           {
            m_position_price = current_price;
            m_position_volume = -1;
           }
         else 
           m_position_volume = 0;
        }
     }
    
   // Расчет текущей эквити
   if (m_position_volume > 0)
     {
      current_price = _next_open;
      profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
      m_results.equity = m_results.balance + profit;
     }
   else if (m_position_volume < 0)
     {
      current_price = _next_open + _next_spread * m_symbol_points;
      profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
      m_results.equity = m_results.balance + profit;
     }
   else
      m_results.equity = m_results.balance;    
   
   return m_results;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//|                                                      Balance.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window

#property indicator_buffers 4
#property indicator_plots   3
#property indicator_level1  0.0 
#property indicator_levelcolor Silver 
#property indicator_levelstyle STYLE_DOT
#property indicator_levelwidth 1  
//--- plot Balance
#property indicator_label1  "Balance"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrBlue,clrRed
#property indicator_style1  STYLE_DOT
#property indicator_width1  1
//--- plot Equity
#property indicator_label2  "Equity"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrLime
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- plot Zero
#property indicator_label3  "Zero"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrGray
#property indicator_style3  STYLE_DOT
#property indicator_width3  1

#include <BalanceClass.mqh>
//+------------------------------------------------------------------+
//| Input and global variables                                       |
//+------------------------------------------------------------------+
input string   iParentName        = "";            // Имя индикатора для расчета баланса
input int      iSignalBufferIndex = -1;            // Порядковый номер буфера сигнала
input datetime iStartTime         = D'01.01.2012'; // Дата начала расчета
input datetime iEndTime           = 0;             // Дата окончания расчета
//--- Indicator buffers 
double   Balance[];       // Значения баланса
double   BalanceColor[];  // Цветовой индекс для отрисовки баланса
double   Equity[];        // Значения эквити
double   Zero[];          // Нулевой уровень, для правильного отображения гистограммы
//--- Global variables
double   Signal[1];       // Массив для получения текущего сигнала
int      parent_handle;   // Хэндл индикатора, сигналы которого планируем использовать 

CBaseBalanceCalculator calculator; // Объект для расчета баланса и эквити
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {     
   // Привязка индикаторных буферов
   SetIndexBuffer(0,Balance,INDICATOR_DATA);
   SetIndexBuffer(1,BalanceColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2,Equity,INDICATOR_DATA);
   SetIndexBuffer(3,Zero,INDICATOR_DATA);
  
   // Поиск хэндла индикатора по его имени
   parent_handle = FindIndicatorHandle(iParentName);
   if (parent_handle < 0)
     {
      Print("Ошибка! Не найден родительский индикатор");
      return -1;
     } 
   
   return(0);
  }
//+------------------------------------------------------------------+
//| 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 start_index = prev_calculated;
   int end_index = rates_total-1;
   
   // Рассчитываем значения баланса и эквити
   for(int i=start_index; i<end_index; i++)
     {
      // Проверка на соответствие интервалу расчета баланса
      if (time[i] < iStartTime)
        {
         Balance[i] = 0;
         Equity[i] = 0; 
         continue;
        }
      if (time[i] > iEndTime && iEndTime != 0)
        {
         Equity[i] = (i==0) ? 0 : Equity[i-1];
         Balance[i] = Equity[i]; 
         continue;
        }
      
      // Запрос сигнала от родительского индикатора
      if(CopyBuffer(parent_handle,iSignalBufferIndex,time[i],1,Signal)==-1) // Копирования данных главной линии индикатора
        {
         Print("Ошибка копирования данных: " + IntegerToString(GetLastError()));
         return(0);  // Завершаем работу функции и отправляем индикатор на полный пересчет
        }
      
      // Инициируем расчет баланса и эквити
      // Так как сигнал у нас формируется на закрытии свечи, совершить какую-либо операцию 
      //   мы сможем только по цене открытия следующей свечи
      BalanceResults results = calculator.Calculate(i==0?0:Balance[i-1], (int)Signal[0], open[i+1], spread[1+1]);
      
      // Заполнение всех индикаторных буферов
      Balance[i] = results.balance;
      Equity[i] = results.equity; 
      Zero[i] = 0;
      if (Balance[i] >= 0)
         BalanceColor[i] = 0;
      else
         BalanceColor[i] = 1;
     }
     
   // Заполнение буферов для последней свечи 
   Balance[end_index] = Balance[end_index-1];
   Equity[end_index] = Equity[end_index-1]; 
   BalanceColor[end_index] = BalanceColor[end_index-1];
   Zero[end_index] = 0;
     
   return rates_total;
  }
//+------------------------------------------------------------------+

Уфф, ну теперь точно все! Давайте скомпилируем его и попытаемся посмотреть, что у нас получилось в итоге.


Порядок использования

Для того чтобы увидеть, как работает наш свеженаписанный индикатор, нужно просто бросить его на график, который содержит хотя бы один сигнальный индикатор. Если вы делали все вместе со мной, то такой индикатор у нас уже есть, это PivotCandles. Итак, нам предлагается настроить входные параметры. Давайте посмотрим, что нам предлагается ввести:

  • Имя индикатора для расчета баланса (строковое) - нужно иметь в виду, что привязка индикатора баланса производится по имени, поэтому это поле является обязательным к заполнению.
  • Порядковый номер буфера сигнала (целое) - еще один обязательный параметр. Сигнальный индикатор может выдавать сразу несколько сигналов по определенной нами выше схеме, поэтому индикатору баланса обязательно нужно знать, сигнал какого буфера ему следует обсчитать.
  • Дата начала расчета (дата/время) - дата, начиная с которой будет вестись подсчет баланса.
  • Дата окончания расчета (дата/время) - дата, до которой будет вестись подсчет баланса. Если эта дата не выбрана (равна нулю), расчет будет вестись до последнего бара.

На рисунке 3 показано, как настроить первые два параметра для прикрепления индикатора баланса к третьему буферу индикатора PivotCandles. Оставшиеся два параметра можно установить на свой вкус.

Рисунок 3. Параметры индикатора Balance.

Рисунок 3. Параметры индикатора Balance.

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

 Рисунок 4. Отображение кривых баланса и эквити по сигналам с индикатора PivotCandles.

Рисунок 4. Отображение кривых баланса и эквити по сигналам с индикатора PivotCandles.

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

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


Заключение

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

  • необходима предварительная подготовка сигнального буфера у анализируемого индикатора;
  • сигналы привязаны ко времени открытия нового бара;
  • отсутствует ММ при расчете баланса;

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

Прикрепленные файлы |
balanceclass.mqh (8.08 KB)
balance.mq5 (5.58 KB)
pivotcandles.mq5 (4.15 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (17)
astrohelper
astrohelper | 12 нояб. 2012 в 21:47

Как ни компилировал, всё время выдаёт ошибку. И непонятно как исправить. А потому и не обнаруживается файл в индикаторах. Ошибка в картинке.

---
--- | 13 нояб. 2012 в 07:26
astrohelper:

Как ни компилировал, всё время выдаёт ошибку. И непонятно как исправить. А потому и не обнаруживается файл в индикаторах. Ошибка в картинке.

вероятно у вас файл открыт другой программой

или запись в папку запрещена

astrohelper
astrohelper | 13 нояб. 2012 в 21:24

astrohelper:

 

А вот как выглядит в другой программе - уже 9 ошибок!!! 


Как ни компилировал, всё время выдаёт ошибку. И непонятно как исправить. А потому и не обнаруживается файл в индикаторах. Ошибка в картинке.

sergeev:

вероятно у вас файл открыт другой программой

или запись в папку запрещена

Sergey Petruk
Sergey Petruk | 13 нояб. 2012 в 21:36
astrohelper:
так бывает, когда несколько платформ на одном компе,- вначале из открытой платформы открыть отладчик и уже в нем найти файл и компилировать.
astrohelper
astrohelper | 15 нояб. 2012 в 00:54

Спасибо, всё получилось.

Интервью с Эгидиюсом Бочкусом (ATC 2012) Интервью с Эгидиюсом Бочкусом (ATC 2012)
"Пришлось проанализировать работу многих индикаторов, чтобы понять, что вообще-то они для зарабатывания на Forex не нужны" - смело заявил нам герой сегодняшнего интервью Эгидиюс Бочкус (Egidijus). И у нас есть повод задуматься о смысле этих слов, ведь на третьей неделе Automated Trading Championship 2012 его советник укрепился на третьем месте с результатом более $32 000 по балансу.
Интервью с Александром Прищенко (ATC 2012) Интервью с Александром Прищенко (ATC 2012)
Что может быть сложнее мультивалютного торгового робота? Наверняка, это автоматизированная стратегия на основе волновой теории Эллиотта. А что будет сложнее такой торговой стратегии? Определенно, мультивалютник, торгующий по Эллиотту на каждой валютной паре! Александр Прищенко (Crucian) считает, что освоить правила может даже неподкованный читатель.
Преимущества MQL5 Сигналов Преимущества MQL5 Сигналов
Недавно появившийся в MetaTrader 5 сервис «Торговые сигналы» позволил трейдерам копировать торговые операции любого поставщика сигналов. Пользователь выбирает понравившийся ему сигнал, подписывается на него, и все сделки теперь повторяются на его счете. Не остается внакладе и поставщик, ведь он может установить свою цену на подписку и получать ежемесячно фиксированную плату со своих клиентов.
Как подготовить описание продукта для Маркета Как подготовить описание продукта для Маркета
В MQL5 Маркете представлено много продуктов, однако их описания оставляют желать лучшего. Многие тексты непонятны обычному трейдеру и нуждаются в улучшении. Данная статья поможет вам представить свой продукт в выгодном свете. Воспользуйтесь ею и создайте хорошее описание, которое доходчиво объяснит вашим покупателям, что именно вы продаете.