Renko внутри советника: построение по тикам без offline-графика
Renko внутри советника: строим кирпичи без offline-графика
Разработчик советника может получать тики без ошибок и всё же видеть разные Renko-кирпичи в тестере и при подключении к текущему рынку. Размер кирпича одинаков, но после перезапуска меняется точка отсчёта; при резком движении пропадают промежуточные кирпичи, а сигнал иногда появляется раньше, чем закончился сам кирпич. С такими расхождениями трудно проверять торговую логику: непонятно, изменилась стратегия или построение Renko.
В этой статье разберём построение Renko непосредственно внутри советника MQL5 — без offline-графика и пользовательского символа. Сначала зафиксируем правила: какую цену принимать, как выбрать начальную точку, когда подтверждать продолжение и разворот и как обработать несколько уровней за один тик. Затем соберём эти правила в CRenkoBuilder и подключим его к демонстрационному советнику, который не совершает сделок.
В результате вы получите исходный код класса CRenkoBuilder, демонстрационный советник и скрипт самопроверки. Вы сможете отдельно проверить заданные правила на коротких примерах, сравнить два прохода по одному массиву и посмотреть кирпичи в CSV. Для повторения приложен фиксированный искусственный набор входных данных с ожидаемым результатом. Он позволяет проверить конкретные случаи, но не доказывает правильность алгоритма на любых котировках и не заменяет проверку работы в терминале.
Renko внутри советника

Рис. 1. Советник получает тики, строит Renko внутри себя и передаёт торговой логике только завершённые кирпичи.
Сначала зафиксируем правила построения
В обычном описании Renko достаточно сказать, что новый кирпич появляется после движения цены на заданное расстояние. Для кода этого мало: разные источники цены и разные способы подтверждения разворота дают разные последовательности при одном и том же размере кирпича. Поэтому прежде, чем писать обработчик тиков, нужно определить, какая именно последовательность должна считаться правильной для нашего примера.
Начнём с источника цены и привязки сетки, затем разберём развороты и скачки через несколько уровней. Эти решения задают ожидаемую последовательность, с которой мы будем сравнивать результат программы.
Какую цену использовать
Структура MqlTick содержит Bid и Ask, а для части инструментов — ещё и Last. Демонстрационный советник позволяет выбрать один из четырёх вариантов:
- Bid;
- Ask;
- Mid — среднюю цену между Bid и Ask;
- Last, а если Last отсутствует — Bid.
В режимах Ask и Mid запись без необходимой положительной котировки пропускается. В режиме Last функция TickPrice() использует last, когда он положителен, а иначе возвращает bid. Это смешанный источник "Last с резервным Bid", а не поток только торговых цен: переход между сделкой и котировкой способен изменить кирпичи. Для проверки построения ниже используется Bid. Для исследования именно сделок политику Last потребуется изменить отдельно, без скрытой подстановки другой цены.
После выбора цены метод PriceUnits() выполняет units=(long)MathRound(price/m_point). Дробная часть участвует в округлении к ближайшему целому; после него сохраняется целое число points. Поэтому половина point в Mid не отбрасывается заранее и может изменить округлённый уровень. Первичный расчёт Mid и деление на point выполняются в double; целочисленными становятся последующие сравнения порогов. Это правило одинаково для истории и новых тиков.
Источник цены выбирается в демонстрационном советнике, а не внутри CRenkoBuilder: класс получает готовое число. Два прогона следует сравнивать при одинаковых источнике, параметрах и входных записях. Сам ExportBricks() эти настройки в CSV не добавляет; их нужно сохранять отдельно. Ниже для фиксированного примера предусмотрен отдельный файл параметров.
Размер кирпича
Следующее правило — размер кирпича. В параметрах он задаётся в points, а при выводе может быть представлен ценовым расстоянием:
//--- Переводим размер кирпича из points в единицы цены.
m_box=box_points*point_size; Например, при _Point = 0.00001 кирпич размером 100 points соответствует расстоянию 0.00100. Но пороги внутри CRenkoBuilder хранятся целыми числами points: 1.10000 соответствует 110000, следующий уровень — 110100. Так достижение границы определяется целочисленным сравнением и не зависит от небольших погрешностей сложения double.
Размер в 100 points приведён только для иллюстрации и не является торговой рекомендацией. Класс не подбирает его под инструмент и не оценивает прибыльность: он лишь выполняет заранее согласованное правило. Теперь нужно решить, от какого уровня отсчитывать первый кирпич.
С чего начинается Renko
Начальной опорой становится первая пригодная цена, округлённая до points. Если она равна 1.1000, а размер кирпича — 0.0010, первое движение не создаёт кирпич, пока округлённая цена строго внутри интервала от 1.0990 до 1.1010. Достижение любой из границ уже подтверждает первый кирпич соответствующего направления.
После первого кирпича вверх до 1.1010 следующий появится на 1.1020. Можно выбрать другую привязку — например, фиксированную ценовую сетку, — но это будет уже другое правило. В нашем демо начальная цена берётся из выбранного окна истории; при изменении окна опора и последующие уровни могут сдвинуться.
Почему правила разворота нужно задать отдельно
После появления первого направления остаётся вопрос: сколько цена должна пройти в обратную сторону, чтобы появился кирпич разворота? В примере реализованы два режима — на один и на два размера кирпича. Оба задают последовательные правила, но дают разные результаты на одном потоке цен.
Разворот на один кирпич
Пусть последний завершённый кирпич направлен вверх, его close равен 110, а размер — 10. В режиме разворота на один кирпич снижение до 100 уже достаточно для создания обратного кирпича. Условие можно записать в сокращённом виде:
//--- Упрощённая схема: для разворота достаточно одного размера кирпича. if(price<=m_last_close-m_box_units) create_down_reversal();
В условии price, m_last_close и m_box_units измеряются целым числом points. create_down_reversal() здесь — поясняющее имя операции, а не функция из приложенного класса; в приложенном исходнике используются методы ReverseDownOneBox() и AppendBrick().
Разворот на два кирпича
При развороте на два кирпича тех же десяти пунктов недостаточно. После close 110 цена 100 ещё не подтверждает новое направление; порог находится на 90. Тогда первый обратный кирпич имеет тело:
Open = 100 Close = 90
Следующий фрагмент показывает правило two-box в той же условной шкале:
//--- Упрощённая схема two-box: ждём два размера от последнего закрытия. //--- Новый кирпич открывается на один размер ниже последнего закрытия. if(price<=m_last_close-2*m_box_units) { long reversal_open =m_last_close-m_box_units; long reversal_close=m_last_close-2*m_box_units; create_down_reversal(reversal_open,reversal_close); }
Разница видна на пути 100 → 110 → 100 → 90: первый режим подтверждает разворот на уровне 100, второй ждёт 90. При этом тело каждого кирпича имеет размер 10. Режим разворота следует записывать в параметры эксперимента наравне с размером кирпича; в самом CSV с кирпичами отдельного поля для этого режима нет.
Разворот на один и два кирпича

Рис. 2. Цена проходит 100 → 110 → 100 → 90. Слева разворот на один кирпич: после роста появляются два кирпича вниз. Справа разворот на два кирпича: цена 100 ещё ничего не подтверждает, а при 90 появляется кирпич 100 → 90. Размер каждого тела — 10.
Что делать, если один тик перескочил несколько уровней
Переход к следующему уровню не обязательно происходит отдельным тиком. Представим, что последний close растущего Renko — 1.1000, размер кирпича — 0.0010, а новая пригодная цена сразу равна 1.1034. Между этими значениями находятся три полных уровня: 1.1010, 1.1020 и 1.1030.
Если создать один кирпич и ждать следующего события, два оставшихся уровня окажутся необработанными. При быстром возврате цены это уже изменит итоговую последовательность. Поэтому функция обновления должна завершить все кирпичи, пороги которых пересёк поступивший тик, сохранив их порядок.
Для этого в классе используется цикл. Один вызов Update() может вернуть не единицу, а количество завершённых кирпичей; остаток движения, не достигший полного уровня, остаётся незавершённым. В приведённом примере это 0.0004. При движении вниз действует симметричная логика.
Внутри алгоритма значения price и m_last_close уже приведены к целым points:
//--- Один тик может пересечь несколько уровней — добавляем все готовые кирпичи. //--- При ошибке прекращаем обработку: часть кирпичей уже могла попасть в массив. while(price>=m_last_close+m_box_units) { long next_close=m_last_close+m_box_units; if(!AppendBrick(m_last_close,next_close,+1,source_time_msc,false)) return -1; }
Такой цикл обрабатывает скачок между соседними входными ценами, в том числе на накопленной истории. Все кирпичи, добавленные одним вызовом Update(), становятся известны после обработки одного и того же тика. Их расчётный порядок не означает, что на промежуточных уровнях были отдельные моменты для исполнения ордеров.
Несколько кирпичей за один тик

Рис. 3. Продолжаем движение вверх от close 1.1000. Новый тик приходит по 1.1034 и подтверждает три кирпича до 1.1030. Остаток 0.0004 не становится четвёртым кирпичом. Это схема уровней, а не три известные сделки по промежуточным ценам.
Торговая логика получает только завершённые кирпичи
Ещё одна причина расхождений — преждевременное подтверждение движения. Если последний close равен 1.1000, размер кирпича — 0.0010, а текущая цена поднялась лишь до 1.1007, новый кирпич ещё не появился. Хотя пройдено 70% расстояния, это всё ещё незавершённое движение.
CRenkoBuilder добавляет запись только при достижении или пересечении полного порога. До этого текущая цена остаётся входом Update(), а не готовым кирпичом. Торговая логика должна соблюдать ту же границу: использование впоследствии достроенного кирпича в более раннем решении создаёт заглядывание в будущее. Само наличие массива завершённых кирпичей не освобождает стратегию от проверки порядка доступа к данным.
Почему для воспроизведения нужен порядок тиков
Историю Renko можно попытаться восстановить по обычным OHLC-барам, и для предварительного исследования это иногда быстрее. Однако High и Low не содержат порядка движения внутри бара. Если за минуту рынок успел пересечь порог продолжения и порог разворота, по четырём ценам бара нельзя однозначно установить, какой кирпич появился первым.
Для повторяемой проверки нужен упорядоченный поток тиков. В MQL5 его можно запросить через CopyTicksRange():
//--- Берём все записи диапазона, чтобы replay сохранил порядок изменений цены. //--- Возвращённое количество и ошибку проверяем до обработки массива. int copied=CopyTicksRange(_Symbol, ticks, COPY_TICKS_ALL, from_msc, to_msc);
Записи обрабатываются от старых к новым, без произвольной перестановки тиков с одинаковым time_msc. Поле flags показывает, какие данные изменились; остальные поля MqlTick также заполнены последними известными значениями. В нашем демо выбрана обработка снимков цены: TickPrice() читает нужное поле в каждой записи и не фильтрует её по flags. Повтор той же цены сам по себе не добавляет кирпич, однако считать каждую такую запись отдельной сделкой нельзя. При диагностике нужно сохранять и флаги, и исходный порядок записей.
Есть и ограничение на интерпретацию теста. Название режима "Все тики" не гарантирует, что поток состоит из записей реального рынка. Для максимально близкой к истории проверки выбирают "Каждый тик на основе реальных тиков" и отдельно убеждаются, что нужные данные доступны. При пробелах тестер может создавать недостающие тики; сам вызов CopyTicksRange() не делает любой полученный поток "реальным". Если результаты расходятся, следует сравнивать исходные записи, а не только построенные кирпичи.
Реализуем правила в CRenkoBuilder
Теперь правила можно отделить от торгового кода. Класс CRenkoBuilder не открывает ордера и не вычисляет сигналы BUY или SELL. Его задача — принять цену, определить пересечённые уровни, создать все подтверждённые кирпичи и предоставить их советнику в исходной последовательности.
В примере используются положительные цены и десятичная сетка _Point/_Digits. Поля high и low описывают границы тела кирпича (максимум и минимум между open и close), а не экстремумы всех тиков между подтверждениями. Это важно учитывать при сравнении с Renko-графиками, на которых дополнительно рисуются тени.
Обычная последовательность вызовов — Init() → Update(). Если опора ещё не задана, первый Update() сам вызывает Seed(price), сохраняет округлённую цену и возвращает ноль кирпичей. Явный Seed() нужен только тогда, когда опору задают отдельно перед обработкой потока. Повторный Seed без Reset отклоняется; Reset очищает последовательность и опору, сохраняя настройки класса.
Update(price, source_time_msc) возвращает число новых кирпичей или −1 при ошибке. Например, после отказа выделения памяти часть записей могла уже добавиться, поэтому продолжать с таким состоянием нельзя: потребуется сброс и повторное воспроизведение входных данных.
Класс хранит все завершённые кирпичи в памяти и рассчитан на учебный интервал. Ограничение памяти и сохранение состояния между запусками в нём не реализованы. Полный исходный код находится во вложенном CRenkoBuilder.mqh; в статье приведены только фрагменты, необходимые для объяснения.
Демонстрационный советник: от истории к новым тикам
Файл InternalRenkoBuilderDemo.mq5 показывает, как подключить класс к обычному графику MetaTrader 5 без создания специального символа. Это учебный советник: он строит кирпичи, печатает сообщения в журнал и при включённом экспорте записывает CSV, но торговые операции не выполняет.
При инициализации демо получает недавний участок тиков, дважды строит по нему Renko в отдельных экземплярах класса и сравнивает результаты. После этого создаётся рабочее состояние, а OnTick() дочитывает новые записи. CSV содержит кирпичи, а не исходные тики. При штатном снятии советника он перезаписывается с учётом уже обработанного текущего потока; это не архив всех состояний одного запуска.
Событие OnTick() не гарантирует отдельного вызова для каждого рыночного тика: если NewTick уже ожидает обработки или обрабатывается, новый NewTick не добавляется в очередь. Поэтому SymbolInfoTick() определяет верхнюю границу, а записи загружаются через CopyTicksRange(). При ошибке копирования демо не продвигает границу даже тогда, когда вернулась часть массива. Следующий вызов может повторить запрос.
Полный код демо находится во вложении InternalRenkoBuilderDemo.mq5. Проверять его удобнее после коротких тестов класса, когда уже понятно, какой результат должен получиться из известных входных цен.
Как установить файлы и повторить проверку
В MetaTrader 5 выберите "Файл → Открыть каталог данных" и распакуйте туда MQL5.zip с сохранением папок. В корне архива уже есть папка MQL5, поэтому распаковывать его внутрь существующей MQL5 не нужно. Демонстрационный советник находится в MQL5\Experts\GDS_Internal_Renko, оба проверочных скрипта — в MQL5\Scripts\GDS_Internal_Renko, а общие заголовки CRenkoBuilder.mqh и ReferenceFixture.mqh — в MQL5\Include\GDS_Internal_Renko. Подключения вида #include <GDS_Internal_Renko/CRenkoBuilder.mqh> используют этот общий каталог; копировать заголовки рядом с каждой программой не требуется. В MetaEditor скомпилируйте демо и оба скрипта.
Сначала запустите InternalRenkoBuilderSelfTest и посмотрите вкладку "Эксперты". Он не обращается к брокерской истории. Затем выполните InternalRenkoReferenceRun: этот дополнительный скрипт создаёт комплект одного фиксированного искусственного примера, описанного ниже. После этого можно прикрепить демо к обычному графику с InpReplayHours=1 или 2 и включённым InpExportCsv. Отсутствие кирпичей на коротком тихом участке не означает ошибку; при смене периода или размера запишите новые параметры.
Если InpReplayHours > 0 и история дала завершённые кирпичи, демо сообщает результат двух проходов в таком формате:
Determinism check: PASS | bricks=... | hash1=... | hash2=...
PASS означает, что два прохода по одному массиву дали одинаковые кирпичи, FAIL — расхождение, которое нужно разобрать до подключения торговых сигналов. Сообщение INCONCLUSIVE при отсутствии завершённых кирпичей не является ни успехом, ни ошибкой построения. При InpReplayHours=0 исторический replay и эта проверка не выполняются. Числа в строке выше — пример формата, а не результаты нового запуска.
Файлы обычного запуска находятся в MQL5\Files; в тестере используется одноимённая папка соответствующего агента. FILE_COMMON в примере не применяется. Демо открывает CSV с FILE_WRITE, то есть прежнее содержимое файла с тем же именем заменяется. Перед следующим прогоном сохраните нужную выгрузку отдельно и задайте другое InpCsvName. Автоматического архива запусков, экспорта сырых брокерских тиков и полного набора метаданных в демо нет.
Повторяемость и проверка правил — разные задачи
Два прохода по одному массиву показывают, повторяется ли результат при одинаковых входах и настройках. Они не устанавливают, правильно ли реализованы правила Renko: одна ошибка может повториться в обоих экземплярах. Поэтому нужны два вида проверки — сравнение проходов и сравнение с заранее заданным ответом.
В демо SameBricks() сравнивает количество записей и все поля каждой пары кирпичей, включая sequence и source_time_msc. Fingerprint() последовательно смешивает open, close, направление и признак разворота; время в подпись не включено. High и low здесь выводятся из границ тела, а порядок учитывается порядком смешивания записей. Подпись удобна для журнала, но не заменяет сравнение полей.
В исходном демо условие записано как SameBricks(first,second) && hash1==hash2. После полного равенства записей добавочное совпадение хешей логически избыточно: оно не усиливает доказательство. Статус PASS следует понимать только как совпадение двух результатов на проверенном массиве.
Точное сравнение double в SameBricks рассчитано на два прохода одного кода внутри одного процесса с одинаковой сеткой. При сравнении внешних CSV удобнее читать десятичные значения точно и переводить их в целые points по общей конфигурации. Сопоставлять нужно все поля кирпичей, включая номер и время; различие на один point нельзя скрывать произвольным допуском.
Что действительно проверяет self-test
Полный InternalRenkoBuilderSelfTest.mq5 приложен отдельным файлом. Его функция Check считает выполненные утверждения, а не независимые рыночные сценарии. При успешном прохождении текущего исходника получается 4061 утверждение: 17 для one-box, 16 для two-box, 4015 в TestDecimalLevels и 13 в TestResetAndInputs. Из них 3998 — шаги двух циклов по точным ценовым границам. Это не означает "4061 различную стратегию" или полное покрытие всех ошибок.
TestReversal() проверяет оба режима разворота и резкий обратный ход. TestDecimalLevels() — незавершённое движение, несколько кирпичей за тик, повтор цены, разные записи одной миллисекунды и попадание на порог. TestResetAndInputs() — неверные настройки, явную и автоматическую опору, запрет повторного Seed и сброс. Один из фрагментов теста выглядит так:
CRenkoBuilder b; Check(b.Init(0.00001,5,100,RENKO_REVERSAL_TWO_BOX),"decimal init"); Check(b.Update(1.10000,1000)==0,"decimal seed"); Check(b.Update(1.10070,1001)==0,"unfinished"); Check(b.Update(1.10340,1002)==3,"three bricks from one tick"); CheckBrick(b,0,1.10000,1.10100,1,false,1002); CheckBrick(b,1,1.10100,1.10200,1,false,1002); CheckBrick(b,2,1.10200,1.10300,1,false,1002); Check(b.Update(1.10340,1002)==0,"repeated identical quote"); Check(b.Update(1.10400,1002)==1,"new quote in same millisecond"); Check(b.Update(1.10499,1003)==0,"one point below boundary"); Check(b.Update(1.10500,1004)==1,"exact boundary");
Здесь результат задан заранее: из перехода 1.10070 → 1.10340 должны получиться три кирпича. CheckBrick сравнивает их цены, границы тела, направление, признак разворота, номер и время подтверждающего тика. Это содержательная проверка выбранного случая, в отличие от простого повторения одного расчёта. Доставка данных терминалом, изменение брокерской истории, работа файлового API и восстановление после перезапуска этим скриптом не проверяются.
Один пример: входные записи, параметры и результат
Чтобы проверку можно было повторить без доступа к чужой брокерской истории, используем один фиксированный искусственный поток. Он продолжает пример с рисунка 3. Это не котировки EURUSD и не результат реальной торговли; временные метки условные. Параметры: источник Bid, point 0.00001, digits 5, размер 100 points, разворот two-box, начальная опора 1.10000 из первого Update. Порядок строк — часть входа.
| Строка | time_msc | Bid | Новых кирпичей | Проверяемый случай |
|---|---|---|---|---|
| 0 | 1000 | 1.10000 | 0 | Начальная опора |
| 1 | 1001 | 1.10070 | 0 | Незавершённое движение |
| 2 | 1002 | 1.10340 | 3 | Три кирпича вверх |
| 3 | 1002 | 1.10340 | 0 | Повтор той же цены |
| 4 | 1002 | 1.10400 | 1 | Ещё один тик в той же миллисекунде |
| 5 | 1003 | 1.10300 | 0 | Одного размера для разворота мало |
| 6 | 1004 | 1.10200 | 1 | Разворот вниз на два размера |
| 7 | 1005 | 1.10100 | 1 | Продолжение вниз |
После строки 2 должны существовать три кирпича до 1.10300. Строка 3 ничего не добавляет, а строка 4 создаёт ещё один кирпич до 1.10400. Время у этих четырёх кирпичей одинаковое, хотя их подтвердили разные входные записи. Поэтому одинаковый time_msc сам по себе не позволяет сгруппировать кирпичи по исходным тикам — нужно сохранять порядок входа и результат каждого Update.
Снижения до 1.10300 недостаточно для two-box-разворота от 1.10400. На 1.10200 появляется обратное тело 1.10300 → 1.10200; затем — продолжение до 1.10100. Полная ожидаемая последовательность:
sequence;time_msc;open;high;low;close;direction;reversal 0;1002;1.10000;1.10100;1.10000;1.10100;1;0 1;1002;1.10100;1.10200;1.10100;1.10200;1;0 2;1002;1.10200;1.10300;1.10200;1.10300;1;0 3;1002;1.10300;1.10400;1.10300;1.10400;1;0 4;1004;1.10300;1.10300;1.10200;1.10200;-1;1 5;1005;1.10200;1.10200;1.10100;1.10100;-1;0
Этот эталон записан отдельно от вычисленного результата. В ReferenceFixture.mqh фиксированы восемь входных записей, ожидаемое число кирпичей после каждого Update и поля шести готовых тел. InternalRenkoReferenceRun.mq5 строит последовательность дважды, проверяет каждый проход по эталону и отдельно сравнивает проходы между собой.
Как сохранить один согласованный запуск
Для ReferenceRun задайте ещё не использованный InpRunId, например reference_002. В MQL5\Files\GDS_Internal_Renko\reference_002 скрипт запишет ticks.csv, parameters.txt, journal.txt, bricks.csv и bricks_second.csv, а также служебный маркер run.started. Параметры включают источник, размер, сетку, режим разворота, опору, границы набора, число входных записей и сборку терминала. Настройки point/digits фиксированы для искусственного примера и не берутся с открытого графика. В CSV.zip приложены три CSV-файла выполненного запуска reference_001; его параметры и строки журнала приведены ниже.
При обнаружении файлов с тем же идентификатором скрипт прекращает запуск без их перезаписи. Для повторной попытки используйте другое имя; одновременный запуск нескольких экземпляров с одинаковым ID не предусмотрен. После успешного выполнения сопоставьте обе выгрузки с шестью эталонными строками CSV, приведёнными выше, и сохраните свой журнал "Эксперты" вместе с исходниками и параметрами. Это комплект проверки фиксированного потока, а не сохранённое состояние непрерывно работающего советника.
Результат проверки в MetaTrader 5
26 октября 2026 года фиксированный пример выполнен в MetaTrader 5, сборка 6230. В сохранённых параметрах указаны идентификатор примера RENKO_REFERENCE_V1 и запуск reference_001. Использованы описанные выше условия: Bid, point 0.00001, digits 5, кирпич 100 points, разворот two-box и опора 1.10000 из первого Update. Восемь искусственных записей с time_msc от 1000 до 1005 обрабатывались в порядке строк без фильтрации по flags; настройки открытого графика не использовались. Получены шесть кирпичей. В журнале этого запуска записано:
runtime=MetaTrader5; case_id=RENKO_REFERENCE_V1; run_id=reference_001 Reference: ticks=8 | bricks=6 | expected=PASS | repeat=PASS Geometry only: hash1=5386730456815558540 | hash2=5386730456815558540 Files: ticks.csv; parameters.txt; bricks.csv; bricks_second.csv Reference run: PASS | synthetic fixture only
expected=PASS означает совпадение с заранее заданными полями эталона, а repeat=PASS — равенство двух проходов. Дополнительно обе сохранённые выгрузки сопоставлены с ожидаемыми строками: совпали все восемь полей каждого из шести кирпичей. Файлы bricks.csv и bricks_second.csv также побайтно одинаковы. По этим строкам заново рассчитана геометрическая подпись 5386730456815558540; она совпала с журналом, но не использовалась вместо полного сравнения.
В приложенном CSV.zip находятся только три неизменённых файла этого запуска: ticks.csv с восемью входными записями, bricks.csv с первым результатом и bricks_second.csv со вторым. Параметры, эталон и строки журнала приведены в статье, а исходники — в MQL5.zip. Входные CSV-строки сохраняют полный порядок, цены, временные метки и flags. Это искусственные данные, а не выгрузка брокерской истории; символ открытого графика не меняет point, digits и цены примера.
Отдельным запуском в тот же день выполнен InternalRenkoBuilderSelfTest. В переданной строке вкладки "Эксперты" записано:
Renko self-test: PASS | checks=4061 | failed=0
Эта строка относится к отдельному запуску самопроверки, а не к CSV фиксированного примера. Число 4061 означает количество выполненных утверждений, а не полноту тестирования. Полного отчёта компиляции в опубликованные материалы не включено; приведённые строки относятся к выполнению программ.
Что меняется при перезапуске и на границе history/live
При переходе от загруженной истории к текущему потоку последний исторический тик может встретиться повторно. Кажется естественным запоминать только time_msc и пропускать все записи с такой же или более ранней меткой, но в одной миллисекунде могут находиться несколько разных тиков. Вместе с дублем такой фильтр отбросил бы часть ещё не обработанного движения.
Поэтому демо хранит время последней принятой записи и количество обработанных записей с этой меткой. Следующий CopyTicksRange() снова включает пограничную миллисекунду, а ConsumeTicks() пропускает прочитанное начало группы. Это работает при неизменности состава и порядка уже принятых записей. Их ценовые поля функция не сравнивает: при подмене содержимого с сохранением количества ошибка может остаться незамеченной.
Например, после двух записей с time_msc=1001 следующий запрос возвращает те же две, ещё одну с временем 1001 и одну с 1002. Первые две пропускаются, остальные обрабатываются. Но при перестановке или исправлении старых записей одного счётчика недостаточно. Для защищённого продолжения потребовалась бы проверка сохранённого префикса по содержимому и перестроение при расхождении; в учебном демо такой проверки нет.
Курсор тиков существует только в памяти текущего запуска. Демо не сохраняет на диск начальную опору, последний close, направление, номер кирпича и границу чтения. После нового старта окно "последние два часа" сдвигается, и сетка может оказаться другой. Поэтому результат проверки на фиксированном массиве нельзя переносить на непрерывность между перезапусками.
Для полноценного торгового советника придётся выбрать отдельную политику восстановления: сохранять настройки, начальную опору, последний close и направление, номер кирпича и точную границу уже прочитанных тиков либо каждый раз воспроизводить поток с одной фиксированной отправной точки. Если брокер исправит старую историю или изменится порядок записей, состояние понадобится перестроить. Эти механизмы не стоит считать реализованными только потому, что учебное демо умеет подгружать новые тики.
Какие ошибки проверять в первую очередь
Пропущенные кирпичи после скачка цены. Если обработчик создаёт не более одного кирпича из тика, переход через несколько уровней теряется или растягивается на следующие события. Проверьте ситуацию, когда за один тик цена пересекает три размера кирпича: последовательность должна содержать все три подтверждённых тела, а не только первое.
Разные входные цены. Исторический участок, построенный по Bid, нельзя напрямую сравнивать с обработкой новых тиков по Mid или Last. Сначала убедитесь, что источник цены, правила пропуска отсутствующих котировок и перевод в points одинаковы на обеих частях потока.
Неявный разворот. Даже одинаковый Brick Size не обеспечивает одинакового Renko, если одна реализация подтверждает разворот на один размер, а другая — на два. Проверять лучше на короткой последовательности с заранее известным порогом, а затем сверить в CSV значение reversal.
Незавершённое движение выдано за сигнал. Текущая цена может пройти почти всё расстояние до следующего кирпича и развернуться. Если стратегия успела обработать такой кирпич как готовый, результат теста уже отличается от логики, работающей только с завершёнными записями.
Неточный порядок истории. OHLC не сообщает, что произошло раньше внутри бара, а произвольная сортировка тиков с одинаковым time_msc способна изменить разворот. Если последовательность не сходится, проверьте сами записи, из которых она строилась, до исправления формул Renko.
Сдвиг после перезапуска. Если начальная цена выбирается из скользящего окна недавней истории, после нового запуска опорный уровень может оказаться другим. Для непрерывной работы потребуется фиксированная точка воспроизведения или сохранённое и проверяемое состояние; данное демо такой устойчивости не обещает.
Оценка только по прибыли. После изменения builder итог теста может улучшиться просто потому, что кирпичи появились в другом порядке. Прежде чем делать вывод о стратегии, найдите первый расходящийся sequence в CSV и определите причину. Иначе ошибка построения будет ошибочно выглядеть как торговое преимущество.
Как передать готовые кирпичи торговой логике
Завершённые кирпичи можно последовательно передавать расчётной части стратегии: например, обновлять её индикатор или состояние. Но вызов для каждого кирпича не должен означать отдельное исполнение ордера на его close. В следующей схеме UpdateSignalState() только обновляет расчётное состояние и не обращается к торговым функциям:
//--- Учебная схема: код исполнения ордеров здесь не вызывается. if(g_failed) return; int first_new=renko.Count(); int added=renko.Update(price,(ulong)tick.time_msc); if(added<0) { g_failed=true; return; } for(int i=first_new;i<renko.Count();i++) { SRenkoBrick brick; if(!renko.Get(i,brick)) { g_failed=true; return; } UpdateSignalState(brick); // Только обновление расчётного состояния. } //--- Решение об ордере — отдельно, после обработки пакета, //--- с проверкой свежести данных и доступных текущих котировок.
g_failed объявляется на уровне советника и первоначально равен false. После ошибки он блокирует дальнейшие обновления до восстановления состояния. UpdateSignalState(const SRenkoBrick &brick) — условная функция вашей стратегии, а не часть приложенного демо; в её пошаговом расчёте нельзя читать ещё не переданные кирпичи из уже заполненного массива.
Все кирпичи одного вызова Update подтверждаются одним входным событием. Последовательное обновление индикатора внутри такого набора не создаёт дополнительных рыночных моментов. Если модель принимает решение только по итоговому состоянию пакета, это правило нужно одинаково применять при проверке и при работе с текущими данными.
Close кирпича — расчётный уровень, не гарантированная цена сделки. После скачка через три уровня нельзя открыть три ордера задним числом по промежуточным close. Догруженная после задержки история тоже обновляет только расчётное состояние; перед отправкой заявки необходимо отдельно проверить свежесть сигнала, актуальные котировки и условия исполнения. В демонстрационном советнике заявок нет.
Разделение обязанностей остаётся полезным и после подключения торговли: builder отвечает за последовательность кирпичей, стратегия — за решения, а модуль исполнения — за реальные заявки. Благодаря этому изменение входного алгоритма можно проверить отдельно от результатов сделок.
Файлы примера
| Файл | Назначение |
|---|---|
| CRenkoBuilder.mqh | Построение завершённых кирпичей по выбранным правилам. |
| InternalRenkoBuilderDemo.mq5 | История, текущие тики, сравнение проходов и экспорт кирпичей; без торговли. |
| InternalRenkoBuilderSelfTest.mq5 | Полный исходник проверок правил, числовых границ и сброса. |
| ReferenceFixture.mqh / InternalRenkoReferenceRun.mq5 | Фиксированный искусственный вход, эталон и сохранение одного проверочного запуска. |
| MQL5.zip | Исходники с готовой структурой папок для каталога данных терминала. |
| CSV.zip | Входные записи и две выгрузки кирпичей одного запуска reference_001 в MetaTrader 5; только три CSV-файла. |
Что получилось и что ещё нужно проверить
Теперь у вас есть класс для построения Renko внутри советника и способы проверить его отдельно от торговли. Источник цены выбирает демо, а CRenkoBuilder отвечает за перевод уровней в points, начальную опору, продолжение, разворот и несколько кирпичей из одного тика. Поля high/low описывают тела без теней; незавершённое движение не добавляется в последовательность.
Self-test сопоставляет выбранные случаи с заранее заданными ответами. Два прохода проверяют повторяемость, а входные записи и обе выгрузки из CSV.zip вместе с приведёнными в статье параметрами и журналом позволяют разобрать результат одного запуска. В выполненном терминальном примере восемь искусственных записей дали шесть кирпичей, совпавших с эталоном. Самопроверка завершилась без ошибок на 4061 утверждении. Эти результаты относятся к указанным случаям: ни совпавший хеш, ни большое число утверждений не доказывают правильность на любых данных.
Учебное демо не является готовым механизмом непрерывного торгового состояния: оно не сохраняет Renko между перезапусками и не проверяет содержимое повторно прочитанных тиков. Эти задачи, ограничение памяти и исполнение ордеров остаются за пределами примера. Перед проверкой торговой идеи сначала убедитесь, что на выбранных входных данных выполняются ваши правила построения, и только затем оценивайте сигналы и доступные цены исполнения.
Документация MQL5
- MathRound: округление входной цены
- MqlTick: заполнение ценовых полей и флаги изменений
- CopyTicksRange: порядок, включённые границы и ошибки копирования
- CopyTicks: снимки цен и типы запрашиваемых тиков
- OnTick: обработка событий NewTick
- FileOpen: папки файлов терминала и тестера
- Флаги файлов: перезапись при FILE_WRITE
- Особенности режима тестера на реальных тиках
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Создание системы торговой аналитики (Часть 3): Сохранение данных о сделках MetaTrader 5 в SQLite
Создание объектно-ориентированного механизма статистического арбитража на основе Z-показателя в MQL5
Разработка объектно-ориентированного сканера разрывов справедливой стоимости (FVG) на MQL5
От новичка до эксперта: Создание индикатора-оверлея CRT для нескольких таймфреймов в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования