Пакетный подход к разработке на языке MQL5 с помощью KnitPkg
Введение
Трейдеру-разработчику часто приходится сопровождать несколько проектов на языке MQL5 (советники, индикаторы, утилиты), в которых используется общий математический и служебный код. Сегодня код обычно используют повторно по старинке: копируют папки из проекта в проект и вручную редактируют директивы #include. С каждым обновлением возникает рассинхронизация версий: где-то уже есть новое исправление, а где-то остается старый файл. При этом результат сборки только средствами MetaTrader зависит от того, какие исправления уже были вручную внесены в данном терминале. На самом деле нужен способ один раз реализовать компонент в репозитории Git, управлять его версиями по SemVer и подключать к разным проектам так, чтобы установка и сборка были воспроизводимыми на любом компьютере: зависимости зафиксированы, артефакты генерируются автоматически (дерево заголовочных файлов или единый заголовочный файл), а перенос кода между проектами больше не требует редактирования путей или копирования файлов.
В этой статье представлен такой процесс разработки на языках MQL4/MQL5 с использованием пакетов KnitPkg: каждый функциональный блок оформляется как повторно используемый компонент с управлением версиями, а затем подключается к разным проектам через манифест, а не путем копирования кода. В статье мы увидим, как этот подход повышает надежность сборки, повторного использования кода в советниках и индикаторах, а также публикации, превращая разрозненные фрагменты кода в структурированные компоненты с управляемыми зависимостями.
Что такое KnitPkg
KnitPkg — это менеджер пакетов и проектов для языков MQL4/MQL5, ориентированный на использование Git. Он решает давнюю проблему разработки для MetaTrader — копирование кода, ручное управление подключениями и рассинхронизацию версий — благодаря следующим возможностям:
- хранению только метаданных в центральном реестре, тогда как исходный код остается в обычных репозиториях Git;
- поддержке зависимостей с семантическим версионированием (^, ~, * и т.д.), позволяющей точно указать требуемую версию пакета;
- созданию lock-файла для воспроизводимых сборок;
- поддержке двух режимов установки — include (классическое дерево заголовочных файлов) и flat (единый сгенерированный заголовочный файл, удобный для распространения кода в Маркете MQL5).
Иными словами, KnitPkg позволяет сосредоточиться на написании чистого, тестируемого кода на языке MQL с управлением версиями в Git, беря на себя разрешение зависимостей, установку и компиляцию.
В этом разделе мы приводим краткий обзор ключевых идей, лежащих в основе KnitPkg. Более подробное описание см. в документации KnitPkg и на главной странице KnitPkg.
1. Манифест. Манифест — это "мозг" репозитория проекта KnitPkg: он сообщает KnitPkg, что представляет собой этот репозиторий, как собирать проект и какие зависимости ему нужны. Манифест хранится в формате YAML в файле с именем knitpkg.yaml или knitpkg.yml. Также поддерживается формат JSON; в этом случае имя файла — knitpkg.json.
2. Проекты и пакеты. Проект — это стандартный "контейнер": индикатор, советник, скрипт, библиотека, сервис или даже репозиторий тестов. В проектах KnitPkg исходный код обычно хранится в каталоге src/, а KnitPkg автоматически генерирует артефакты зависимостей (дерево заголовочных файлов либо единый заголовочный файл) в каталоге knitpkg/.
Пакет — это проект, основное назначение которого — подключаться к другим проектам в качестве зависимости. Это означает, что код пакета должен иметь предсказуемую структуру, чтобы другие разработчики (или ваши другие проекты) могли подключать его заголовочные файлы с помощью #include. Не каждый проект является пакетом, но каждый пакет — это проект.
Одиночные и составные пакеты. В KnitPkg пакеты бывают двух типов:
- одиночный пакет — автономный пакет, не объявляющий зависимостей;
- составной пакет — пакет, который зависит от одного или нескольких других пакетов (напрямую или косвенно).
Составные пакеты должны решать конкретную задачу. В процессе разработки автору нужно, чтобы IntelliSense и модульные тесты компилировались без проблем, однако пакет зависит от кода, находящегося за пределами репозитория. Кроме того, когда другие разработчики устанавливают пакет как зависимость, использующий его проект должен получать фактические директивы #include, указывающие на заголовочные файлы установленной зависимости. KnitPkg решает эту проблему с помощью сочетания:
- вспомогательного заголовочного файла для разработки (autocomplete.mqh);
- директив @knitpkg:include, которые KnitPkg при установке преобразует в реальные директивы #include во время выполнения команды kp install.
KnitPkg предоставляет специальные команды для генерации заголовочного файла autocomplete.mqh и проверки корректности директив, чтобы пакет правильно устанавливался при использовании в других проектах.
Использование пакетов. При реализации пакета можно использовать структурный или объектно-ориентированный подход — оба поддерживаются языками MQL4 и MQL5. В данной статье не рассматривается стиль реализации, поскольку он зависит от задачи, которую решает компонент, ограничений по производительности, предпочтений разработчиков и т.д.
Благодаря KnitPkg хорошо спроектированный пакет можно использовать несколькими способами независимо от стиля реализации:
- Прямой вызов кода через зависимости KnitPkg, объявленные в манифесте.
- Через библиотеку на языке MQL: библиотека инкапсулирует функциональность пакета и предоставляет к ней доступ. Библиотека также создается как проект KnitPkg.
- Через индикатор на языке MQL: индикатор может использовать пакет для вычислений, а затем предоставлять результаты через свои буферы. Индикатор отображает данные на графике (выступая в роли "представления" для пакета), а также предоставляет информацию (результат вычисления), которую могут использовать другие советники. Таким образом, любой советник может через индикатор получить результат вычисления, выполненного компонентом, — так же, как при использовании нативной функции.
Подводя итог, перечислим преимущества реализации пакетов с помощью KnitPkg:
- каждый функциональный блок реализуется один раз в репозитории Git с управлением версиями по SemVer;
- пакет можно повторно использовать в любом проекте столько раз, сколько потребуется, без дублирования кода;
- пакет можно повторно использовать в наиболее удобной для конкретной задачи форме (прямой вызов кода, индикатор или библиотека).
3. Модульные тесты. KnitPkg рекомендует использовать модульные тесты для проверки пакетов. Модульные тесты — это небольшие повторяемые проверки конкретных аспектов работы кода пакета. Они помогают своевременно выявлять регрессионные ошибки, а при удачно выбранных названиях служат исполняемой документацией, описывающей ожидаемую работу публичного API.
Это особенно важно для пакетов, поскольку они предназначены для повторного использования в качестве зависимостей: при публикации новой версии пакета нужно быть уверенным, что его основные функции по-прежнему работают так, как ожидается.
Модульные тесты объявляются в манифесте в разделе compile, благодаря чему они автоматически компилируются для быстрого запуска.
4. Реестр. KnitPkg состоит из двух частей:- CLI — инструмент командной строки, который вы запускаете локально и с которым взаимодействуете напрямую (например, kp install);
- реестр — веб-служба, которая хранит метаданные, извлеченные из манифестов проектов, и предоставляет их CLI.
Реестр позволяет CLI надежно находить проекты и разрешать зависимости, тогда как исходный код по-прежнему хранится в репозиториях Git. Он хранит метаданные манифестов проектов, управляемых KnitPkg. CLI использует реестр для поиска общедоступных проектов и получения сведений о них, выбора конкретной версии зависимости из заданного диапазона и определения точной ревизии исходного кода (коммита), соответствующей выбранной версии. Репозитории Git могут размещаться на MQL5Forge, GitHub, GitLab или Bitbucket.
Все зарегистрированные проекты также можно просматривать через веб-интерфейс реестра, который упрощает разработчикам и трейдерам поиск нужных пакетов, советников и индикаторов.
Практический пример — создание повторно используемых пакетов с помощью KnitPkg
Основная цель этого практического примера — реализовать один расчет (SMA) и повторно использовать его в трех разных артефактах: пакете, индикаторе и советнике. Торговые правила намеренно просты; основное внимание уделяется тому, как один и тот же компонент объявляется, получает версию, устанавливается и повторно используется без копирования кода.- Итоговый исполняемый артефакт — это советник, который открывает длинную позицию по сигналу двух скользящих средних: когда краткосрочная SMA (sma1) пересекает долгосрочную SMA (sma2) снизу вверх. В качестве фильтра используется третья SMA (sma3): вход в рынок разрешается только при выполнении условия Close > sma1 > sma2 > sma3. Позицию следует закрыть, когда sma1 пересекает sma2 сверху вниз. Советник должен использовать как можно меньше ресурсов процессора.
- Для работы советника мы создадим индикатор KnitPkgSMA, который рассчитывает простую скользящую среднюю для текущего графика. SMA следует (пере)рассчитывать только при появлении нового бара, а не на каждом тике.
- Код для расчета SMA следует разместить в пакете, чтобы его можно было использовать повторно.
- Пакет SMA должен принимать обобщенный интерфейс, представляющий временной ряд (TimeSeries), где индекс 0 всегда соответствует последнему значению цены (индекс работает как "сдвиг").
- Пакет, содержащий реализацию SMA (а в будущем и других индикаторов), должен быть пригоден для повторного использования как индикатором KnitPkgSMA, так и любым другим пакетом или проектом.
- Чтобы эффективно использовать SMA повторно в советнике, необходимо предусмотреть стандартный способ проверки появления нового бара на каждом тике.
- Если советнику требуется доступ к данным OHLCV, он также должен получать их через стандартный интерфейс временных рядов (индекс 0 — для последних значений).
Целевые артефакты пакетов. Для выполнения поставленных требований были созданы следующие пакеты:
- @douglasrechia/bar — содержит интерфейсы для TimeSeries и баров OHLCV, а также средства обнаружения появления нового бара на каждом тике. См. Git-репозиторий пакета bar.
- @douglasrechia/calc — пакет calc, созданный на основе пакета bar и содержащий реализацию SMA; в будущем в него будут добавлены и другие индикаторы. См. Git-репозиторий пакета calc.
Примечание 1. В KnitPkg проект обозначается как @<organization>/<repository_name>, где organization и repository_name берутся из двух последних элементов URL Git-репозитория и приводятся к нижнему регистру.
Примечание 2. Чтобы содержание статьи по-прежнему соответствовало коду, даже если репозитории со временем изменятся (появятся новые коммиты), ссылки ведут на коммит, соответствующий последней версии пакетов на момент написания статьи.
1. Изучение пакета bar. В соответствии с соглашением KnitPkg заголовочные файлы пакета bar находятся в каталоге knitpkg/include/<organization>/<project_name>, в данном случае — knitpkg/include/douglasrechia/bar/.Добавлять код в knitpkg/include/ разрешается только для проектов типа package; для всех остальных типов проектов содержимое каталога knitpkg/ нельзя редактировать вручную.
Заголовочные файлы в knitpkg/include/douglasrechia/bar/ содержат классы и интерфейсы для TimeSeries, Bar (средство доступа к данным OHLCV по принципу временного ряда) и BarWatcher (для обнаружения нового бара на каждом тике). Читателям рекомендуется ознакомиться с репозиторием и изучить код.
В репозитории bar манифест knitpkg.yaml выглядит следующим образом:
target: mql5 type: package organization: douglasrechia name: bar version: 1.1.0 version_description: New constructor for TimeSeriesArray # Search fields description: Simple, fast and reliable bar access with time series and new bar detection. keywords: [ "bar", "array", "timeseries", "demo", "showcase" ] author: Douglas Rechia license: MIT compile: - tests/UnitTestsBarArray.mq5 - tests/UnitTestsBarMqlRates.mq5 - tests/UnitTestsTimeSeries.mq5
В MetaTrader пакет фактически создается как проект типа Script. Скомпилированные скрипты модульных тестов показывают, соответствует ли результат ожидаемому. Теперь загрузим этот пакет и запустим его модульные тесты.
2. Загрузка и запуск модульных тестов пакета bar. Предполагается, что вы уже выполнили инструкции по установке KnitPkg и установили MetaTrader с нуля в каталог `C:\Program Files\MetaTrader 5`.
Откройте MetaTrader и выберите Файл > Открыть каталог данных. Затем щелкните правой кнопкой мыши в окне папки и выберите Открыть в Терминале:

Теперь должно появиться окно терминала, открытого в папке данных MetaTrader:

Чтобы загрузить исходный код пакета bar и скомпилировать его непосредственно в среде MetaTrader, выполните следующую команду:
kp get mql5 @douglasrechia/bar -v 1.1.0
Примечание: параметр -v для kp get необязателен; здесь явно задана версия 1.1.0, чтобы описание в статье соответствовало версии кода пакета. Если параметр -v не указан, команда kp get выбирает последнюю стабильную версию.
Ниже показано, как выглядит окно терминала после выполнения команды kp get:

Таким образом, мы получаем не только локальную копию исходного кода пакета bar версии 1.1.0, но и бинарные файлы модульных тестов, скомпилированные из этого кода. Вернитесь в MetaTrader и обновите раздел "Скрипты" в окне "Навигатор". Вы увидите тестовые скрипты в папке "Скрипты/bar/bin/". Запустите скрипты UnitTestsBarArray, UnitTestsBarMqlRates и UnitTestsTimeSeries (просто дважды щелкните по каждому из них). Результаты тестирования можно посмотреть на вкладке "Эксперты" в панели "Инструменты":

Вы можете открыть и изучить проект bar, который был загружен в каталог "Скрипты/bar". Обратите внимание, что это репозиторий с управлением версиями в Git.

Также обратите внимание на файл манифеста knitpkg.yaml и заголовочные файлы в каталоге knitpkg/include/douglasrechia/bar/, которые будут экспортированы в любые проекты, использующие этот пакет.
Примечание: для просмотра файлов YAML и других исходных файлов, написанных не на языке MQL, используйте Windows Explorer и удобный вам редактор; рекомендуется VS Code. Для редактирования кода на языке MQL рекомендуется использовать MetaEditor.
Команда get: что происходит "под капотом". При выполнении команды kp get интерфейс командной строки KnitPkg обращается к реестру, чтобы получить последнюю стабильную версию запрошенного пакета (некоторые другие команды действуют аналогичным образом). Реестр возвращает URL репозитория Git и хеш коммита для этой версии. Затем команда kp get клонирует репозиторий с фиксацией на коммите с указанным хешем. Поэтому приведенный выше вывод git status показывает, что репозиторий находится в состоянии detached HEAD. Более подробные сведения см. в справке по kp get.
Пакет bar — одиночный пакет без зависимостей. Теперь перейдем к пакету calc — здесь все становится интереснее.
Этап 1 — пакет установлен без копирования кода. На этом этапе в вашей среде MetaTrader 5 доступна версия 1.1.0 пакета @douglasrechia/bar, и модульные тесты подтверждают, что он работает должным образом. В этой же среде ведется активная разработка пакета: создаются ветки разработки, исправляются ошибки, реализуются новые функции, фиксируются изменения и в итоге создается новая ревизия пакета — без изменения проектов, зависящих от bar.
3. Пакет calc — пример составного пакета. Как мы уже знаем, в пакете calc реализуется расчет SMA. Однако пакет calc будет использовать @douglasrechia/bar как зависимость: интерфейс TimeSeries передается в качестве аргумента функции расчета скользящей средней. Преимущество заключается в том, что функция SMA не зависит от конкретной реализации TimeSeries. Не имеет значения, реализован ли TimeSeries на основе простого массива или какой-либо другой структуры данных. Функции SMA достаточно выполнять расчет, исходя из того, что последний по времени элемент временного ряда находится на позиции 0, предыдущий — на позиции 1 и т.д.
Теперь повторим этот процесс. Выполните следующую команду, чтобы загрузить пакет calc версии 1.0.1:
kp get mql5 @douglasrechia/calc -v 1.0.1
В качестве упражнения вернитесь в MetaTrader и обновите раздел "Скрипты" в окне "Навигатор". Вы увидите тестовые скрипты в папке "Скрипты/bar/bin/". Запустите тесты и посмотрите результаты на вкладке "Эксперты" в панели "Инструменты".
Теперь рассмотрим проект calc и некоторые особенности его кода. Сначала рассмотрим манифест knitpkg.yaml проекта calc. Здесь появляется кое-что новое: в разделе dependencies указана зависимость calc от @douglasrechia/bar:
target: mql5 type: package organization: douglasrechia name: calc version: 1.0.1 version_description: 'ATR fix' description: Common indicators built on @douglasrechia/bar (SMA, ATR, etc) keywords: [ "indicator", "sma", "atr", "showcase" ] author: Douglas Rechia license: MIT dependencies: '@douglasrechia/bar': ^1.0.0 compile: - tests/UnitTestSMA.mq5 - tests/UnitTestATR.mq5
Рассмотрим исходный код функции SMA в файле knitpkg/include/douglasrechia/calc/Calc.mqh. Ниже мы отметим несколько важных моментов, касающихся этого заголовочного файла, а также особенности составных пакетов.
Директива #include для autocomplete.mqh. В файле Calc.mqh именно директива #include для autocomplete.mqh позволяет создавать составной пакет "без лишних сложностей".
//............... //------------------------------------------------------------------ // Development autocomplete — resolves dependencies and enables // MetaEditor IntelliSense; automatically neutralized by KnitPkg // installer. // Run `kp autocomplete` to regenerate. //------------------------------------------------------------------ #include "../../../autocomplete/autocomplete.mqh" //------------------------------------------------------------------ // KnitPkg include directives — used by KnitPkg installer at the time // this package is installed as a dependency into another KnitPkg // project. //------------------------------------------------------------------ /* @knitpkg:include "douglasrechia/bar/TimeSeries.mqh" */ /* @knitpkg:include "douglasrechia/bar/Bar.mqh" */ namespace douglasrechia { //...............
Интересно, что файл autocomplete.mqh отсутствует в Git-репозитории. Команда kp autocomplete генерирует этот заголовочный файл по пути knitpkg/autocomplete/autocomplete.mqh. Команда kp get (которую мы использовали выше) автоматически вызывает kp autocomplete в ходе выполнения, поэтому команда get смогла автоматически скомпилировать пакет и его модульные тесты.
Как работает autocomplete. Посмотрите содержимое файла autocomplete.mqh в локально установленном пакете calc (этого заголовочного файла вы не найдете в репозитории; там его нет):
//+------------------------------------------------------------------+ //| autocomplete.mqh | //| Generated by `kp autocomplete` — DO NOT EDIT | //+------------------------------------------------------------------+ #include "knitpkg/include/douglasrechia/bar/Bar.mqh" #include "knitpkg/include/douglasrechia/bar/BarArray.mqh" #include "knitpkg/include/douglasrechia/bar/BarMqlRates.mqh" #include "knitpkg/include/douglasrechia/bar/BarTimeSeries.mqh" #include "knitpkg/include/douglasrechia/bar/BarWatcher.mqh" #include "knitpkg/include/douglasrechia/bar/TimeSeries.mqh" #include "knitpkg/include/douglasrechia/bar/TimeSeriesArray.mqh"
Обратите внимание, что заголовочные файлы пакета bar теперь находятся в каталоге knitpkg/autocomplete/knitpkg/include/douglasrechia/bar (только локально). Благодаря этому файл autocomplete.mqh обеспечивает работу IntelliSense в MetaEditor при редактировании Calc.mqh, а также позволяет компилировать модульные тесты. Эти заголовочные файлы создаются или подбираются при выполнении команды kp autocomplete, которая выполняет git clone (или fetch) в каталоге .knitpkg/cache/ для зависимостей, объявленных в манифесте. Это позволяет удобно разрабатывать пакеты, зависящие от других пакетов.
В составном пакете автоматически сгенерированный файл autocomplete.mqh — не просто удобное дополнение, а стандартное средство обеспечения компиляции в MetaEditor и работы IntelliSense. При этом разрешением и установкой самих зависимостей по-прежнему занимается KnitPkg. Без этого шага авторы пакетов неизбежно вернулись бы к ручному управлению директивами #include, а вместе с ним — к тем самым проблемам рассинхронизации версий и копирования кода, которые KnitPkg призван устранить.
Примечание: содержимое каталога knitpkg/autocomplete/ генерируется автоматически, и его нельзя редактировать.
Пространство имен (только для языка MQL5). В языке MQL5 код пакета рекомендуется помещать в пространство имен с тем же именем, что и у организации (в языке MQL4 пространства имен недоступны). Это позволяет избежать конфликтов имен при использовании пакетов разных организаций. Использование пространств имен вместе с соглашением о размещении заголовочных файлов в knitpkg/include/<organization>/<project name> гарантирует отсутствие конфликтов и предотвращает перезапись заголовочных файлов при построении их дерева.
Функция SMA. SMA — это функция, которая принимает в качестве аргумента временной ряд TimeSeries (интерфейс ITimeSeries) и возвращает среднее значение для заданных периода (количества элементов, по которым рассчитывается среднее) и сдвига (позиции расчета среднего в TimeSeries, где 0 соответствует последнему по времени значению, 1 — предыдущему и т.д.).
double SMA(douglasrechia::ITimeSeries<double> &series, int period, int shift = 0) { if(series.Size() < (period + shift)) return 0.0; double sum = 0.0; for(int i = shift; i < shift + period; i++) sum += series.ValueAtShift(i); return sum / period; }
Как уже упоминалось, преимущество реализации SMA именно здесь, в пакете, а не в коде индикатора, заключается в возможности использовать этот код в любом другом проекте.
Подготовка составного пакета к установке: @knitpkg:include. Обратите внимание на следующие директивы в файле Calc.mqh:
/* @knitpkg:include "douglasrechia/bar/TimeSeries.mqh" */ /* @knitpkg:include "douglasrechia/bar/Bar.mqh" */
На первый взгляд это похоже на закомментированный код, который компилятор проигнорирует. Однако для KnitPkg эти директивы, заключенные между маркерами комментария /* и */, имеют особое значение: они указывают на внешние для calc заголовочные файлы, от которых зависит Calc.mqh. Оба заголовочных файла подключены в autocomplete.mqh, поэтому на этапе разработки MetaEditor может корректно компилировать Calc.mqh и скрипты модульных тестов.
Когда calc используется как зависимость в других проектах, файл Calc.mqh преобразуется: директива #include для autocomplete.mqh отключается, а директивы @knitpkg:include заменяются подключениями соответствующих внешних заголовочных файлов. Автор пакета обязан поддерживать директивы @knitpkg:include в актуальном состоянии, чтобы они указывали только на заголовочные файлы с определениями символов, используемых в данном заголовочном файле.
Чтобы помочь разработчикам проверять корректность этих директив, KnitPkg предоставляет команду kp checkinstall. Эта команда подготавливает разрабатываемый пакет в каталоге knitpkg/autocomplete/include/, в данном случае — knitpkg/autocomplete/knitpkg/include/douglasrechia/calc (да, в пути есть небольшое повторение). Локально вы можете увидеть, как будет выглядеть Calc.mqh после установки пакета в качестве зависимости (этой версии файла в репозитории нет и не будет):
//------------------------------------------------------------------ // Development autocomplete — resolves dependencies and enables // MetaEditor IntelliSense; automatically neutralized by KnitPkg // installer. // Run `kp autocomplete` to regenerate. //------------------------------------------------------------------ // #include "../../../autocomplete/autocomplete.mqh" /*** ← disabled by KnitPkg install (dev helper) ***/ //------------------------------------------------------------------ // KnitPkg include directives — used by KnitPkg installer at the time // this package is installed as a dependency into another KnitPkg // project. //------------------------------------------------------------------ #include "../bar/TimeSeries.mqh" /*** ← dependency added by KnitPkg ***/ #include "../bar/Bar.mqh" /*** ← dependency added by KnitPkg ***/
Команда kp checkinstall выполняется в составе kp get , поэтому этот файл уже сгенерирован, если вы загрузили проект с помощью kp get. На практике автор пакета может запускать kp checkinstall при каждом изменении директив @knitpkg:include или когда хочет убедиться, что пакет правильно установится в других проектах.
Вместе директивы @knitpkg:include и команда kp checkinstall обеспечивают этап проверки в процессе разработки. В процессе разработки они позволяют удобно работать с файлом autocomplete.mqh и IntelliSense в MetaEditor; при установке KnitPkg переписывает эти директивы в фактические директивы #include, указывающие на нужные заголовочные файлы зависимостей. Так обеспечивается связь между локальной разработкой и надежной установкой в другие проекты: не требуется вручную редактировать пути в директивах #include, а пропуск заголовочных файлов не остается незамеченным.
Этап 2 — общая логика проверена, составной пакет готов к повторному использованию. Пакет calc установлен, его модульные тесты скомпилированы и успешно пройдены. Логика SMA реализована один раз в протестированном компоненте на основе @douglasrechia/bar. Поскольку calc — составной пакет, его директивы @knitpkg:include также проверены с помощью kp checkinstall. Это подтверждает, что при использовании calc в качестве зависимости его заголовочные файлы будут корректно преобразованы и подключат установленные заголовочные файлы пакета bar. В результате пакет @douglasrechia/calc готов к использованию в других проектах (индикаторах, советниках, библиотеках): достаточно объявить его в их манифестах knitpkg.yaml, без ручной настройки путей в директивах #include или дублирования кода.
4. Индикатор sma. Теперь, когда все компоненты готовы, объединим их и посмотрим, как собирается индикатор. Для начала загрузим @douglasrechia/sma версии 1.0.1:
kp get mql5 @douglasrechia/sma -v 1.0.1
Примечание. В предыдущих разделах мы запускали kp get для проектов bar и calc по отдельности, чтобы вы могли изучить их независимо друг от друга. Это не требуется для использования индикатора SMA. Достаточно выполнить команду kp get mql5 @douglasrechia/sma: KnitPkg автоматически разрешит и загрузит все необходимые зависимости (включая bar и calc) без каких-либо предварительных действий.
Ниже показано, как выглядит окно терминала после выполнения команды kp get:

На изображении выше в дереве разрешения зависимостей показаны выбранные и загруженные версии обоих пакетов — bar и calc. Рассмотрим манифест проекта sma:
target: mql5 type: indicator organization: douglasrechia name: sma version: 1.0.1 version_description: Dependencies updated # Registry search fields description: KnitPkg for Metatrader - SMA Indicator Demo keywords: [ "indicator", "sma", "showcase" ] author: Douglas Rechia license: MIT # Include mode resolution include_mode: flat # File to be flattened with all the dependencies for this project entrypoints: - src/KnitPkgSMA.mqh compile: - src/KnitPkgSMA.mq5 # Dependencies of the project dependencies: '@douglasrechia/calc': ^1.0.0
Обратите внимание на самые важные части манифеста, выделенные выше; мы их прокомментируем.
- dependencies — sma зависит от @douglasrechia/calc. Хотя в качестве прямой зависимости объявлен только пакет calc, KnitPkg также подбирает и загружает bar как косвенную зависимость sma, поскольку calc зависит от bar.
- include_mode: flat — sma использует режим Flat, при этом файл src/KnitPkgSMA.mqh объявлен точкой входа. Это означает, что все зависимости индикатора SMA будут объединены в единый заголовочный файл knitpkg/flat/KnitPkgSMA_flat.mqh. Ниже приведен фрагмент файла src/KnitPkgSMA.mqh:
/* @knitpkg:include "douglasrechia/calc/Calc.mqh" */ /* @knitpkg:include "douglasrechia/bar/TimeSeriesArray.mqh" */
Заголовочный файл, объявленный как точка входа (в данном случае src/KnitPkgSMA.mqh), используется только в режиме Flat и всегда должен содержать директивы @knitpkg:include, которые ссылаются на внешние заголовочные файлы, необходимые проекту.
Теперь рассмотрим основной исходный файл индикатора src/KnitPkgSMA.mq5. Ниже приведен его фрагмент:
// ..................... #include "../knitpkg/flat/KnitPkgSMA_flat.mqh" // ..................... //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const int32_t begin, const double &price[]) { //--- if (rates_total < InpSMAPeriod) return(0); if (prev_calculated >= rates_total) return rates_total; douglasrechia::TimeSeriesArray<double> priceArray(price, prev_calculated - InpSMAPeriod - 1, rates_total-1, false); // The following loop recalculates the SMA for the most recent bars. // This is necessary because the current bar's data can change with each tick // until its formation is complete. The `shiftStart` variable determines // the initial index for recalculation, ensuring that the SMA is accurately // updated for the latest bar and any preceding bars affected by recent data changes. int shiftStart = rates_total-prev_calculated; if (shiftStart >= rates_total) shiftStart--; for (int shift=shiftStart; shift >= 0 && !IsStopped(); shift--) { SMABuffer[shift] = douglasrechia::SMA(priceArray, InpSMAPeriod, shift); } return(rates_total); }
Обратите внимание, что мы используем стандартную директиву #include языка MQL, указывающую на единый заголовочный файл ../knitpkg/flat/KnitPkgSMA_flat.mqh. Единый заголовочный файл автоматически генерируется KnitPkg на основе точки входа и подобранных зависимостей на этапе kp install, поэтому в репозитории его нет (команда kp install также выполняется в составе kp get).
Теперь должно быть понятно, в чем преимущество использования пакетов: конкретная реализация расчета SMA не включена непосредственно в код индикатора. В результате код индикатора значительно упрощается: достаточно вызвать функцию из пакета calc и передать ей временной ряд (TimeSeries), построенный на основе массива цен, полученного в OnCalculate().
Если ранее вы выполнили kp get для загрузки sma, вернитесь в MetaTrader, обновите список индикаторов в окне "Навигатор" и добавьте на текущий график индикатор из Индикаторы/sma/bin/KnitPkgSMA:

Этап 3 — сгенерирован пригодный для повторного использования артефакт режима Flat и скомпилирован индикатор. После запуска kp get KnitPkg генерирует единый заголовочный файл knitpkg/flat/KnitPkgSMA_flat.mqh на основе точки входа и подобранных для нее зависимостей, а затем успешно компилирует индикатор. Это подтверждает, что пакет @douglasrechia/calc был корректно использован для расчета SMA. Пакет calc, использованный здесь индикатором, можно повторно использовать и в любом другом проекте — советнике, другом индикаторе или библиотеке — без копирования единого исходного файла.
5. Советник expertdemo. Теперь перейдем к последнему компоненту — демонстрационному советнику, реализующему систему на основе пересечения скользящих средних. Загрузите советник expertdemo версии 2.0.1:
kp get mql5 @douglasrechia/expertdemo -v 2.0.1
Для начала разберем наиболее важные части его манифеста. Советник expertdemo зависит от @douglasrechia/bar (который вам уже знаком) и @douglasrechia/barhelper. Чтобы статья не получилась слишком объемной, мы не будем углубляться в детали работы barhelper. Читателю достаточно знать, что barhelper предоставляет функцию NewTimeSeriesFromIndicator. Эта функция возвращает конкретную реализацию ITimeSeries со значениями, рассчитанными любым индикатором и сохраненными в его выходном буфере (в зависимости от потребностей это может быть весь буфер или только его часть).
Это необходимо, поскольку expertdemo использует индикатор KnitPkgSMA, а не функцию SMA из пакета calc. Внимательный читатель заметит, что в манифесте expertdemo пакет calc не указан. Это иллюстрирует разные способы использования функциональности пакета: поскольку индикатор KnitPkgSMA создан на основе calc, советник expertdemo зависит не напрямую от calc, а от индикатора, созданного с его помощью. Интересно, правда?
Советник expertdemo также использует режим Flat, поэтому внешние заголовочные файлы, необходимые проекту, должны быть указаны в файле точки входа src/KnitPkgExpertDemo.mqh.
Наконец перейдем к основному исходному файлу src/KnitPkgExpertDemo.mq5. Ниже приведен его фрагмент:
//................................ //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- barWatcher = new douglasrechia::BarWatcher(); myBar = new douglasrechia::BarMqlRates(); //--- Validate that sma1.period < sma2.period < sma3.period if(!(sma1period < sma2period && sma2period < sma3period)) { Print("Invalid moving average periods"); return(INIT_FAILED); } //--- Handlers for SMA indicators calculated at three different periods sma1handle = iCustom(_Symbol, _Period, "sma/bin/KnitPkgSMA", sma1period); sma2handle = iCustom(_Symbol, _Period, "sma/bin/KnitPkgSMA", sma2period); sma3handle = iCustom(_Symbol, _Period, "sma/bin/KnitPkgSMA", sma3period); if(sma1handle == INVALID_HANDLE || sma2handle == INVALID_HANDLE || sma3handle == INVALID_HANDLE) { Print("Could not initialize KnitPkgSMA indicator"); return(INIT_FAILED); } //--- return(INIT_SUCCEEDED); } //............................... //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- barWatcher.Update(); if(barWatcher.IsFirstTick()) { Print("Hello world! This is KnitPkg's debut EA. I'm mostly here for show, but hey, looking good is half the battle! ;-)"); } else if(barWatcher.IsNewBar()) { OnNewBar(); } } //+------------------------------------------------------------------+ //| OnNewBar — called when a new bar is formed | //+------------------------------------------------------------------+ void OnNewBar() { //--- Get latest 5 bars data myBar.Refresh(5); //--- Get the 5 most recent values of SMA into the TimeSeries. //--- Do it for the three SMAs. douglasrechia::TimeSeriesArray<double>* sma1 = douglasrechia::NewTimeSeriesFromIndicator(sma1handle, 0, 0, 5); douglasrechia::TimeSeriesArray<double>* sma2 = douglasrechia::NewTimeSeriesFromIndicator(sma2handle, 0, 0, 5); douglasrechia::TimeSeriesArray<double>* sma3 = douglasrechia::NewTimeSeriesFromIndicator(sma3handle, 0, 0, 5); if(sma1 != NULL && sma2 != NULL && sma3 != NULL) { if(!positionInfo.Select(_Symbol)) { //--- No position. Check filter conditions. if(!filterEnabled || (myBar.Close(1) > sma1.ValueAtShift(1) && sma1.ValueAtShift(1) > sma2.ValueAtShift(1) && sma2.ValueAtShift(1) > sma3.ValueAtShift(1))) { //--- Open position if Short term sma1 crosses up Mid term sma2 if(douglasrechia::CrossUp(sma1, sma2, 1)) { Print(StringFormat("[%s] Let's do it", TimeToString(myBar.Time(0)))); trade.Buy(1); } } } else { //--- Position is found. Close it if Short term sma1 crosses down Mid term sma2. if(douglasrechia::CrossUp(sma2, sma1, 1)) { Print(StringFormat("[%s] Time to go", TimeToString(myBar.Time(0)))); trade.PositionClose(_Symbol); } } } else { Print("ERROR: could not get TimeSeries from indicators buffers."); } if(sma1 != NULL) delete sma1; if(sma2 != NULL) delete sma2; if(sma3 != NULL) delete sma3; }
Код советника чистый и понятный благодаря использованию функций с высоким уровнем абстракции. В обработчике событий OnInit() создаются глобальные объекты barWatcher и myBar. Хэндлы трех SMA инициализируются с использованием точных путей, подготовленных командой kp get для индикатора KnitPkgSMA.
Примечание. Для работы expertdemo двоичный файл KnitPkgSMA.ex5 должен находиться по пути, указанному в вызове функции iCustom в OnInit(). Поэтому перед запуском expertdemo пользователю необходимо один раз выполнить команду kp get mql5 @douglasrechia/sma.
В обработчике событий OnTick() для обнаружения нового бара используется объект BarWatcher из пакета @douglasrechia/bar; при появлении нового бара вызывается OnNewBar().
Основная логика советника реализована в OnNewBar(). С помощью функции NewTimeSeriesFromIndicator из пакета @douglasrechia/barhelper советник легко получает последние пять значений каждого индикатора SMA (на практике достаточно было бы и трех). Затем с помощью функции CrossUp (также реализованной в barhelper) советник открывает или закрывает позицию в зависимости от того, пересекает ли краткосрочная SMA среднесрочную SMA.
Для проверки позиций и совершения сделок используются классы CTrade и CPositionInfo из Стандартной библиотеки языка MQL. Важно отметить, что KnitPkg полностью совместим со Стандартной библиотекой языка MQL и не накладывает ограничений на ее использование (напротив, Стандартная библиотека — это зрелая высококачественная библиотека, которую мы рекомендуем использовать).
Этап 4 — связка "пакет → индикатор → советник" работает без копирования кода. Советник expertdemo теперь использует логику SMA через индикатор KnitPkgSMA, который, в свою очередь, построен на основе пакетов bar и calc. Исходные файлы не копировались между проектами, а пути в директивах #include не редактировались вручную: все связи описаны в манифестах knitpkg.yaml, а KnitPkg автоматически настраивает их, обеспечивая воспроизводимость установки.
Заключение
KnitPkg меняет подход к повторному использованию кода на языках MQL4/MQL5: вместо копирования папок между терминалами, изменения путей и ручного исправления директив #include каждый функциональный блок реализуется один раз в виде пакета с управлением версиями, а затем декларативно подключается через манифест. На практике ненадежный процесс с ручными операциями превращается в воспроизводимый конвейер сборки, в котором зависимости явно указаны, версии находятся под контролем, а обновления перестают быть "хирургическими операциями" сразу в нескольких проектах.
В этой статье вы узнали, как организовать разработку на языке MQL на основе повторно используемых компонентов — пакетов, индикаторов, библиотек и советников, — которые собираются предсказуемым образом. В частности, вы получаете:
- четкое представление об устройстве KnitPkg: реестр хранит метаданные, Git-репозитории остаются эталонным источником исходного кода, а семантическое версионирование (^, ~, *) обеспечивает контролируемые обновления;
- понимание роли манифеста (knitpkg.yaml): в нем сосредоточены идентификационные данные проекта, цели сборки и зависимости, благодаря чему сборки становятся воспроизводимыми;
- практическую схему установки: понимание различий между режимом include (классическое дерево заголовочных файлов) и режимом flat (единый сгенерированный заголовочный файл, удобный для распространения через Маркет MQL5);
- понимание того, как работают составные пакеты: при разработке используется autocomplete.mqh, а при установке KnitPkg преобразует директивы @knitpkg:include в фактические директивы #include;
- понимание важности модульных тестов в разработке на языке MQL: тесты компилируются вместе с проектом и помогают убедиться, что новые версии пакетов не нарушают работу зависящих от них советников или индикаторов.
Практический пример объединяет эти идеи: одна реализация SMA оформляется как пакет, повторно используется индикатором, а затем через него — советником, который для повышения эффективности отслеживает появление нового бара. Это дает практическую схему построения пригодных для повторного использования связок "пакет → индикатор/библиотека → советник" и сокращения копирования кода при разработке на языке MQL.
Приложения
Исходный код всех проектов, показанных в этой статье, доступен в Git-репозиториях, ссылки на которые приведены по всему тексту. В приведенных ниже приложениях находятся файлы, автоматически сгенерированные KnitPkg при выполнении команды kp get, которая неявно запускает процессы команд kp autocomplete, kp checkinstall и kp install. Эти файлы не хранятся в Git, но читатели могут воспроизвести их локально, выполнив те же команды kp get, которые описаны в статье.
| Имя файла | Описание |
|---|---|
| autocomplete.mqh | Вспомогательный файл для автодополнения, сгенерированный для пакета calc при выполнении kp get (knitpkg/autocomplete/autocomplete.mqh). |
| Calc.mqh | Заголовочный файл пакета calc с подготовленными подключениями зависимостей, сгенерированный и проверенный при выполнении kp get / kp checkinstall (knitpkg/autocomplete/knitpkg/include/douglasrechia/calc/Calc.mqh). |
| KnitPkgSMA_flat.mqh | Единый заголовочный файл индикатора SMA, сгенерированный в проекте sma при выполнении kp get / kp install (knitpkg/flat/KnitPkgSMA_flat.mqh). |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21429
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Пошаговая реализация локальной системы стоп-лоссов на MQL5
От новичка до эксперта: Расширение стратегии на основе ликвидности с помощью трендовых фильтров
Память между перезапусками: Сохранение состояния советника с помощью двоичных файлов в MQL5
Количественный анализ совместной рекуррентности (JRQA) в MQL5: Выявление одновременной рекуррентности в двух временных рядах
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Выглядит интересно, но у меня есть некоторые сомнения.
Если кто-то клонирует репозиторий Git с проектом/программой/пакетом MQL5, который должен управляться KnitPkg, он, скорее всего, получит некомпилируемые исходные коды, поскольку в репозитории отсутствуют автоматически сгенерированные файлы (такие как _flat). Не было бы целесообразно зафиксировать в Git весь необходимый автоматически сгенерированный исходный код, чтобы релиз компилировался независимо от того, как он был получен?
Не уверен, насколько удобно будет вносить изменения в зависимости, ведь мы фактически компилируем и отлаживаем автоматически сгенерированный код и не можем легко «перейти» к соответствующему источнику — может, я что-то упускаю? Например, вам нужно изменить пакет bar при работе над индикатором (если не над советником). Как бы вы это сделали?
Наконец, у меня возникают сомнения по поводу некоторых моментов в реализации примеров (вероятно, потому что это всего лишь примеры), таких как вызов new/delete при каждом тике/баре. Кроме того, например, вы делаете следующее:
В чём смысл передавать массив цен в конструктор priceArray (где создаётся копия части массива), а затем передавать priceArray в SMA — почему бы не передать исходный массив цен по ссылке в SMA без копирования?
Не уверен, насколько удобно будет вносить изменения в зависимости, поскольку мы фактически компилируем и отлаживаем автоматически сгенерированный код и не можем легко «перейти» к соответствующему источнику — может, я что-то упускаю? Например, вам нужно изменить пакет bar при работе над индикатором (если не над советником). Как бы вы это сделали?
Наконец, у меня есть сомнения по поводу некоторых моментов в реализации примеров (вероятно, потому что это всего лишь примеры), таких как вызов new/delete на каждом тике/баре. Кроме того, например, вы делаете так:
В чём смысл передавать массив цен в конструктор priceArray (где создаётся копия части массива), а затем передавать priceArray в SMA — почему бы не передать исходный массив цен по ссылке в SMA без копирования?
Здравствуйте, Станислав,
спасибо, что нашли время прочитать статью и подняли эти вопросы.
Что касается фиксации сгенерированного кода: идея KnitPkg заключается в том, что автоматически сгенерированные артефакты (плоские заголовки, разрешённые включения и т. д.) не должны находиться в репозитории Git, поскольку в этом случае вы фактически дублируете код между репозиториями и рискуете, что он перестанет синхронизироваться. Если какая-то функциональность реализована в пакете/репозитории, это и является единственным достоверным источником; пользователи объявляют зависимость (используя диапазоны SemVer) и позволяют командам `kp install` или `kp autocomplete` перегенерировать производные файлы локально. Таким образом, когда зависимость обновляется в пределах допустимого диапазона SemVer, ваш проект может без проблем перейти на новую версию, вместо того чтобы «застрять» с устаревшим сгенерированным кодом, зафиксированным в собственном репозитории. Конкретный пример обновления версий зависимостей приведён в документации здесь.
Что касается вашего вопроса о том, «насколько удобно будет вносить изменения в зависимости»: я думал об этом при разработке инструмента. KnitPkg поддерживает использование локальных зависимостей, поэтому вы можете напрямую указать проекту пакет, находящийся в локальном каталоге, изменить этот пакет и протестировать изменения в реальном проекте-потребителе во время разработки. Этот рабочий процесс более подробно описан в разделе «Локальные зависимости» документации KnitPkg: https://docs.knitpkg.dev/user-guide/local-dependencies/
Что касается price → TimeSeriesArray → SMA: цель здесь не в том, чтобы показать наиболее эффективную микрооптимизацию, а в том, чтобы продемонстрировать стандартизированный интерфейс. SMA принимает ITimeSeries (а не непосредственно TimeSeriesArray) и обращает внимание только на то, что «самый последний элемент находится по индексу 0, следующий — по индексу 1 и т. д.». TimeSeriesArray — это всего лишь одна из реализаций, использованных в примере, которая, как оказалось, копирует фрагмент массива price. Кто-то другой мог бы реализовать другой ITimeSeries, использующий структуру, более подходящую для кэширования. Суть заключается в отделении вычислений от конкретной схемы хранения; насколько умной или эффективной будет реализация ITimeSeries, становится вопросом дизайна и творческого подхода.
Я всегда рад обсудить эти проектные решения, и с удовольствием приму отзывы о KnitPkg — будь то по рабочему процессу или по примеру кода, — поэтому не стесняйтесь оспаривать или развивать любые из этих идей.