Обсуждение статьи "Рецепты MQL5 - Создаем кольцевой буфер для быстрого расчета индикаторов в скользящем окне"

 

Опубликована статья Рецепты MQL5 - Создаем кольцевой буфер для быстрого расчета индикаторов в скользящем окне:

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

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

Графическое отображение самого индикатора эквивалентно одноименному стандартному индикатору MovingAverage:

 

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

Автор: Vasiliy Sokolov

 

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Объявление массива на языке MQL4 - не получается задать количество элементов, передавая значение переменной (и даже константы).

fxsaber, 2016.09.16 14:32

void OnStart()
{
  double Index = -345.23;
  double Size = -432.98;
  
  double Array[];
    
  // При любом размере (и не целочисленном) будет работать
  ArrayResize(Array, (int)Size < 0 ? (int)MathAbs(Size) : (int)Size);
  
// При любом индексе (и не целочисленном) ВСЕГДА (кроме нуля - если размер массива ноль) будет выполняться без ошибок.
// Таким образом массив становится бесконечной копией самого себя в обе стороны
  Array[(int)Index < 0 ? ArraySize(Array) + ((int)Index % ArraySize(Array)) : (int)Index % ArraySize(Array)] = 1;
}
С ООП видится некоторый перебор для такой задачи. 
 
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
//---
   bool calc = false;
   for(int i = prev_calculated; i < rates_total; i++)
   {
      Sma.AddValue(price[i]);
      buff[i] = Sma.SMA();
      calc = true;
   }
   if(!calc)
   {
      Sma.ChangeValue(MaPeriod-1, price[rates_total-1]);
      buff[rates_total-1] = Sma.SMA();
   }
   return(rates_total-1);
}
Ситуацию с calc не понял. Вроде, проще можно
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
//---   
   if (prev_calculated < rates_total)
     for(int i = prev_calculated; i < rates_total; i++)
     {
        Sma.AddValue(price[i]);
        buff[i] = Sma.SMA();
     }
   else
   {
      Sma.ChangeValue(MaPeriod-1, price[rates_total-1]);
      buff[rates_total-1] = Sma.SMA();
   }
   return(rates_total-1);
}


ЗЫ Если prev_calculated обнулится, будут ошибки.

 

Очень хорошая статья на очень важную тему.

Кольцевой буфер - важнейший механизм.

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

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


P.S. Было бы здорово, чтобы кто то это реализовал в как самостоятельный механизм. Попробуйте?

 

Спасибо за статью! У меня есть несколько вопросов и едких замечаний )


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

За счет чего получится экономия?
Сгенерированная терминалом тайм-серия Н1 занимает больше памяти, чем такая же тайм-серия, сгенерированная внутри советника?


Однако все равно нам потребовалось почти 3 Гб оперативной памяти. Можно ли как-то еще уменьшить эту цифру? Можно, если оптимизировать количество таймфреймов. Попробуем немного изменить тестировочный код, и вместо 21 таймфрейма будем использовать только один — PERIOD_M1. При этом количество индикаторов останется прежним, просто некоторые из них будут дублироваться:

Теперь те же 504 индикатора в режиме внутреннего расчета занимают уже 548 Мб оперативной памяти.

Этого хода вообще не понял. Как можно сравнивать расчет индикаторов по 21 тайм-фрейму с расчетом по 1 ТФ? Результаты расчетов совсем разные, какая разница, сколько при этом памяти расходуется?


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

Константин Груздев еще в 2012 выкладывал свой класс и несколько примеров. Поиск их находит.


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

 
Andrey Khatimlianskii:

Спасибо за статью! У меня есть несколько вопросов и едких замечаний )

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

За счет чего получится экономия?
Сгенерированная терминалом тайм-серия Н1 занимает больше памяти, чем такая же тайм-серия, сгенерированная внутри советника?

К сожалению да. При том намного. И еще не имеет значения запросили вы один бар, или всю доступную историю. Во внутреннюю память будут скопированы все данные по указанному таймфрейму. Сколько точно будет скопировано я конечно не знаю, но по моим замерам памяти, было видно, что копируется практически все. 

Andrey Khatimlianskii:


Однако все равно нам потребовалось почти 3 Гб оперативной памяти. Можно ли как-то еще уменьшить эту цифру? Можно, если оптимизировать количество таймфреймов. Попробуем немного изменить тестировочный код, и вместо 21 таймфрейма будем использовать только один — PERIOD_M1. При этом количество индикаторов останется прежним, просто некоторые из них будут дублироваться:

Теперь те же 504 индикатора в режиме внутреннего расчета занимают уже 548 Мб оперативной памяти.

Этого хода вообще не понял. Как можно сравнивать расчет индикаторов по 21 тайм-фрейму с расчетом по 1 ТФ? Результаты расчетов совсем разные, какая разница, сколько при этом памяти расходуется?

Если для расчета нескольких индикаторов на разных таймфреймах использовать только один наименьший таймфрейм, память удастся сэкономить. Допустим есть два индикатора, один считает значения на M1 другой на H1. Мы можем загрузить котировки для M1 и для H1. Каждому индикатору подсунуть свои котировки и получить от них значения. Однако только за счет того, что будет загружен H1, использование памяти сильно возрастет. Поэтому если мы запросим M1, затем сконвертируем M1 в H1 и подсуним эти данные индикатору на H1, то память существенно сэкономится. Экономия достигается за счет того, что во внутренних буферах MetaTrader для хранения котировок H1 выделяется гораздо больше памяти, чем если бы эти котировки хранились внутри эксперта.

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

 
Andrey Khatimlianskii:

...


В общем целей при написании статьи было три:

  • Создать быстрые алгоритмы для расчетов индикаторов внутри эксперта (выполнено).
  • Создать удобный интерфейс для расчетов в кольцевом буфере (выполнено).
  • Создать экономный расчет по памяти (не выполнено).

Последний пункт провалился. Памяти пару десятков мегабайт действительно удалось сэкономить, но бессмыслено экономить 10-20 Мбайт памяти, что бы при любом запросе к котировкам увеличивать потребление фактической памяти терминала на 100-200 Мбайт. При загрузке 21 таймфрейма по 6 символам на глубину пару десятков баров уже потребуется более 13 Гбайт внутренней памяти терминала(!)
 
Реter Konow:

Очень хорошая статья на очень важную тему.

Кольцевой буфер - важнейший механизм.

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

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


P.S. Было бы здорово, чтобы кто то это реализовал в как самостоятельный механизм. Попробуйте?


То что Вы описываете это просто индикатор. Создаете класс, допустим CTradeChange. Внутри него разместите два синхронных кольцевых буфера: один запоминает N последних цен, другой N последних значений времени:

CTradeChange change;
...
change.Add(value, TimeCurrent());

Далее в методе Add выполните расчет разницы между текущим и предыдущем значение времени. 

 
Vasiliy Sokolov:


В общем целей при написании статьи было три:

  • Создать быстрые алгоритмы для расчетов индикаторов внутри эксперта (выполнено).
  • Создать удобный интерфейс для расчетов в кольцевом буфере (выполнено).
  • Создать экономный расчет по памяти (не выполнено).

Последний пункт провалился. Памяти пару десятков мегабайт действительно удалось сэкономить, но бессмыслено экономить 10-20 Мбайт памяти, что бы при любом запросе к котировкам увеличивать потребление фактической памяти терминала на 100-200 Мбайт. При загрузке 21 таймфрейма по 6 символам на глубину пару десятков баров уже потребуется более 13 Гбайт внутренней памяти терминала(!)

Спасибо за ответы.

Я при разработке упомянутой в статье панели пришел к необходимости ограничивать кол-во отображаемых на чартах баров (5000). Именно из-за памяти...

В МТ4 с этим было гораздо проще: во-первых, каждый ТФ загружался независимо, а значит не требовал подгрузки М1, а, во-вторых, индикаторные буферы занимали столько места, на сколько их заполняешь (хоть 100 баров, если больше не нужно).

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

 
Vasiliy Sokolov:


То что Вы описываете это просто индикатор. Создаете класс, допустим CTradeChange. Внутри него разместите два синхронных кольцевых буфера: один запоминает N последних цен, другой N последних значений времени:

Далее в методе Add выполните расчет разницы между текущим и предыдущем значение времени. 

Странный ответ Вы дали. Похоже Вы вообще не поняли мысль. Процетирую ее еще раз:

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


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

Предлагаемая Вами в статье область применения - получение конкретных значений по индексам внутри текущего периода.

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

1. Буфера со значениями текущего периода.

2. Буфера с временными промежутками между значениями первого буфера.


Совместив данные можно построть кривую математически (на графике не обязательно) и алгоритмически представить и изучить ее внутри программы. Для этого нужно сканировать оба буфера и рассмотреть сигнатуру изменения параметра.

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


Например, можно будет указать программе:

if(Характер_изменения_значения_параметра_за_период ==  BIG_WAVE)Лот  +=  10;

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

Можно например построить 5 шаблонов сигнатур:

FLAT, RISING, BIG_WAVE, FALLING, SMALL_WAVES. 

Каждая из этих констант - шаблон определенного характера изменения параметра внутри текущего периода.

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

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

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


P.S. Надеюсь на этот раз Вы поняли мою мысль.

 
Реter Konow:

Странный ответ Вы дали. Похоже Вы вообще не поняли мысль. Процетирую ее еще раз:

...

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

P.S. Надеюсь на этот раз Вы поняли мою мысль.

Я прекрасно понял Вашу мысль и в первый раз.

Что такое "сигнатура изменений значений"? Это некоторая величина, изменяемая в динамике. Следовательно это индикатор. Развивать кольцевой буфер для этого не надо, а достаточно просто создать на основе нескольких этих кольцевых индикаторов алгоритм расчета того самого "характера изменения" о котором Вы говорите.

Причина обращения: