KnitPkgを用いたMQL5向けパッケージベース開発手法
はじめに
トレーダー兼開発者は、同じ数学ライブラリやユーティリティコードを共有する複数のMQL5プロジェクト(EA、インジケータ、ユーティリティなど)を保守することが少なくありません。現在、このようなコードの再利用は、プロジェクト間でフォルダをコピーし、#include文を手作業で修正するという従来の方法でおこなわれることが一般的です。しかし、更新を重ねるたびにバージョンの不整合が発生し、一方のプロジェクトには修正が反映されていても、別のプロジェクトには古いファイルが残るという状況が生じます。その結果、「クリーンなMetaTrader環境」でのビルドは、各ターミナルでこれまでにおこなわれた手作業の修正内容に依存してしまいます。本来必要なのは、コンポーネントをGitリポジトリで一度だけ実装し、SemVerによるバージョン管理をおこない、それを複数のプロジェクトから再利用できる仕組みです。そのような仕組みがあれば、インストールやビルドはどの環境でも再現可能となり、依存関係は固定され、成果物(#includeツリーまたは単一のヘッダーファイル)は自動生成されます。また、プロジェクト間でコードを移動するたびにパスを書き換えたり、ファイルをコピー&ペーストしたりする必要もなくなります。
本記事では、このようなワークフローをMQL4/MQL5向けにKnitPkgパッケージを利用して実現する方法を紹介します。それぞれの機能を再利用可能でバージョン管理されたコンポーネントとして定義し、コピー&ペーストではなくマニフェストを通じて各プロジェクトから利用します。本記事を通して、このアプローチがEAやインジケータ間での再利用、ビルド、公開をどのように堅牢なものへと変え、断片的なコードを、依存関係を明示的に管理できる土台へと整理するのかを説明します。
KnitPkgとは
KnitPkgは、Gitを中心に設計されたMQL4/MQL5向けのパッケージおよびプロジェクトマネージャです。MetaTrader開発で長年問題となってきた「コピー&ペースト」「手動による#include管理」「バージョンの不整合」を解決することを目的としています。KnitPkgには、次のような特徴があります。
- 中央レジストリにはメタデータのみを保存し、ソースコード自体は通常のGitリポジトリで管理する
- SemVer(^、~、*など)に対応した依存関係管理により、必要なパッケージのバージョンを明確に指定できる
- 再現可能なビルドを実現するためのロックファイルを生成する
- includeモード(従来型の#includeツリー)とflatモード(MQL5マーケット向け配布に適した単一ヘッダーファイル)の2種類のインストール方式を提供する
つまり、KnitPkgは、依存関係の解決、インストール、コンパイルを自動化することで、開発者がGitでバージョン管理された、保守しやすくテスト可能なMQLコードの作成に集中できる環境を提供します。
ここでは、KnitPkgの主要な概念について概要を説明します。より詳しい内容については、KnitPkgドキュメントおよびKnitPkgホームページをご覧ください。
1. マニフェスト: マニフェストは、KnitPkgプロジェクトの「頭脳」となる設定ファイルです。このファイルには、プロジェクトの情報、ビルド方法、および必要な依存関係を定義します。マニフェストはYAML形式で記述され、knitpkg.yamlまたはknitpkg.ymlという名前で保存します。JSON形式もサポートされており、その場合のファイル名はknitpkg.jsonです。
2. プロジェクトとパッケージ:プロジェクトは、インジケータ、EA、スクリプト、ライブラリ、サービス、あるいはテスト専用リポジトリなどを表す基本的な単位です。KnitPkgプロジェクトでは、通常ソースコードはsrc/に配置され、依存関係から生成される成果物(#includeツリーやフラットファイル)はknitpkg/配下へ自動生成されます。
一方、パッケージとは、他のプロジェクトから依存関係として利用されることを主な目的としたプロジェクトです。そのため、他の開発者や別プロジェクトから#includeしやすいよう、コードを一定の構造で整理しておく必要があります。すべてのプロジェクトがパッケージというわけではありませんが、すべてのパッケージはプロジェクトです。
単体(Single)パッケージと複合(Composite)パッケージ:KnitPkgでは、パッケージは次の2種類に分類されます。
- Singleパッケージ:依存関係を持たない自己完結型のパッケージ
- Compositeパッケージ:1つ以上の他のパッケージに直接または間接的に依存するパッケージ
Compositeパッケージでは、特有の技術的課題を解決する必要があります。開発時には、作成者はIntelliSenseやユニットテストを問題なくコンパイルできる状態を維持したい一方で、そのパッケージはリポジトリ外に存在するコードへ依存しています。さらに、そのパッケージが他のプロジェクトから依存関係としてインストールされる場合には、利用側のプロジェクトは、インストールされた依存パッケージのヘッダーファイルを参照する実際の#includeディレクティブを受け取る必要があります。KnitPkgは、この問題を次の2つの仕組みを組み合わせることで解決しています。
- 開発時用の補助インクルードファイルautocomplete.mqh
- インストール時に使用する@knitpkg:includeディレクティブ。このディレクティブは、kp installコマンドの実行時に、KnitPkgによって実際の#include行へ自動的に変換されます。
さらにKnitPkgには、autocomplete.mqhインクルードファイルを生成するための専用コマンドと、@knitpkg:includeディレクティブが正しく記述されていることを検証するための専用コマンドが用意されています。これにより、パッケージが他のプロジェクトから利用される際にも、インストール処理が確実に正しく動作することを確認できます。
パッケージの使用:パッケージの実装スタイルは、MQL4およびMQL5がサポートする構造化プログラミングでもオブジェクト指向プログラミングでも構いません。本記事では実装スタイル自体については説明しません。なぜなら、それはコンポーネントが解決しようとする問題、性能要件、開発者の好みなどによって異なるためです。
実装スタイルに関係なく、KnitPkgを利用することで、適切に設計されたパッケージはさまざまな方法で利用できます。
- マニフェストに宣言したKnitPkgの依存関係を通じて、コードを直接呼び出す方法。
- MQLライブラリを介して利用する方法。ライブラリはパッケージの機能をカプセル化して公開します。ライブラリ自体もKnitPkgプロジェクトとして作成されます。
- MQLインジケータを介して利用する方法。インジケータは計算処理にパッケージを利用し、その結果をバッファを通じて公開します。インジケータは、その計算結果をチャート上へ描画することでパッケージの「ビュー」として機能するとともに、他のEAが利用できる計算結果も提供します。このように、任意のEAは、ネイティブ関数を呼び出す場合と同様に、インジケータを通じてコンポーネントが実行した計算結果を取得できます。
要約すると、KnitPkgを使用してパッケージを実装することには、次のような利点があります。
- 各機能はGitリポジトリ内で一度だけ実装され、SemVerによってバージョン管理される
- パッケージはコードを重複させることなく、必要に応じて任意のプロジェクトから何度でも再利用できる
- 利用目的に応じて、コードの直接呼び出し、インジケータ、ライブラリなど、最も適した形態でパッケージを再利用できる
3. ユニットテスト:KnitPkgは、パッケージを検証するためにユニットテスト(単体テスト)を利用することを推奨しています。ユニットテストとは、パッケージコードの特定の動作を検証する、小さく繰り返し実行可能なテストです。これらは回帰不具合を早期に発見するのに役立ち、さらに適切な名前を付けることで、公開APIがどのように動作すべきかを示す実行可能なドキュメントとしても機能します。
これはパッケージにとって特に重要です。パッケージは他のプロジェクトから依存関係として再利用されることを前提としているため、新しいバージョンを公開する際には、主要な動作が期待どおり維持されていることを確認したいからです。
ユニットテストは、マニフェストのcompileエントリ内で宣言します。そのため、自動的にコンパイルされ、すぐ実行できるよう準備されます。
4. レジストリ:KnitPkgは、次の2つの要素で構成されています。- CLI:ローカル環境で実行し、直接操作するツール(例:kp install)
- レジストリ:プロジェクトのマニフェストから抽出したメタデータを保存し、そのメタデータをCLIへ提供するWebサービス
レジストリが存在する理由は、CLIがプロジェクトを確実に検索し、依存関係を解決できるようにするためです。一方で、ソースコードそのものは引き続きGitリポジトリ内で管理されます。レジストリには、KnitPkgで管理されているプロジェクトのマニフェストメタデータが保存されます。CLIはこの情報を利用して、公開プロジェクトの検索や問い合わせをおこない、依存関係で指定されたバージョン範囲を具体的なバージョンへ解決し、その解決されたバージョンに対応する正確なソースリビジョン(コミット)を特定します。 これらのGitリポジトリは、MQL5Forge、GitHub、GitLab、Bitbucketなどでホスティングできます。
また、登録されているすべてのプロジェクトは、RegistryのWebインターフェースから閲覧できます。このWebインターフェースは、開発者やトレーダーが必要なパッケージ、EA、インジケータを容易に検索・発見できるよう設計されています。
ケーススタディ:KnitPkgを用いた再利用可能なパッケージの構築
このケーススタディでは、単一の計算処理(SMA)を、パッケージ、インジケータ、EAという3種類の成果物で組み立て、再利用することを主な目的とします。トレードルール自体は意図的にシンプルにしており、重点は同じコンポーネントをコピー&ペーストすることなく、どのように宣言し、バージョン管理し、インストールし、再利用できるかにあります。- 最終的な実行成果物はEAです。このEAは、短期SMA(sma1)が長期SMA(sma2)を上抜けたときに買いポジションを建てます。さらにフィルターとして、エントリーを許可するためには、3本目のSMA(sma3)を含めて、Close > sma1 > sma2 > sma3という条件を満たしている必要があります。ポジションは、sma1がsma2を下抜けたときに決済します。また、このEAはCPU使用率をできるだけ抑えて動作する必要があります。
- このEAを支えるために、現在のチャートに対して単純移動平均(SMA)を計算するKnitPkgSMAインジケータを作成します。SMAはティックごとではなく、新しいバーが形成されたときだけ再計算されるようにします。
- SMAの計算コードは、再利用できるようにパッケージとして実装します。
- SMAパッケージは、TimeSeriesを表す汎用インターフェースを受け取れるようにする必要があります。このインターフェースでは、インデックス0が常に最新の価格を表し、インデックスは「shift」と同じ考え方で動作します。
- SMA実装(および将来追加されるインジケータ群)を含むパッケージは、KnitPkgSMAインジケータだけでなく、他の任意のパッケージやプロジェクトからも再利用できるようにする必要があります。
- また、EAからSMAを効率よく利用するために、ティックごとに新しいバーが形成されたかどうかを判定するための標準化された仕組みも提供します。
- さらに、EAがOHLCVデータへアクセスする必要がある場合も、最新データをインデックス0とする標準的な時系列インターフェースを通じて取得できるようにします。
対象となるパッケージ成果物:これらの要件を満たすために、次のパッケージを作成しました。
- @douglasrechia/bar:TimeSeriesおよびOHLCVバーのインターフェースに加え、ティックごとに新しいバーが生成されたことを検出する機能を提供します。詳細は、barパッケージのGitリポジトリを参照してください。
- @douglasrechia/calc:barパッケージを基盤として構築されており、SMAの実装と、今後追加予定の各種インジケータを含みます。詳細は、calcパッケージのGitリポジトリを参照してください。
注1:KnitPkgでは、プロジェクト名は@<organization>/<repository_name>という形式で表されます。organizationとrepository_nameは、Git URLの最後の2つの要素から取得され、どちらも小文字になります。
注2:本記事の内容が、今後リポジトリに新しいコミットが追加されても一貫性を保てるように、掲載しているリンクは、執筆時点における各パッケージの最新版に対応するコミットを指しています。
1. barパッケージの構成:KnitPkgの規約では、barパッケージのヘッダーファイルは、knitpkg/include/<organization>/<project_name>ディレクトリに配置されます。本ケースでは、配置先はknitpkg/include/douglasrechia/bar/となります。knitpkg/include/配下へコードを配置できるのは、パッケージタイプのプロジェクトだけです。それ以外のプロジェクトタイプでは、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ではスクリプトプロジェクトとして作成されます。ユニットテスト用スクリプトはコンパイル後に実行することで、出力結果が期待どおりであるかどうかを確認できます。それでは、このパッケージをダウンロードし、ユニットテストを実行してみましょう。
2. barパッケージのダウンロードとユニットテストの実行:ここでは、すでにKnitPkgのインストール手順を完了し、新しいMetaTraderがC:\Program Files\MetaTrader 5にインストールされているものとします。
MetaTraderを開き、[ファイル] > [データフォルダを開く]を選択します。その後、開いたフォルダ内で右クリックし、[ターミナルで開く]を選択します。

すると、MetaTraderのDataフォルダでターミナルが開きます。

barパッケージのソースコードをMetaTrader環境へ直接ダウンロードしてコンパイルするには、次のコマンドを実行します。
kp get mql5 @douglasrechia/bar -v 1.1.0
注:kp getコマンドでは-vオプションは必須ではありません。本記事では、説明内容をパッケージのコードバージョンと一致させるため、バージョン1.1.0を明示的に指定しています。-vを省略した場合、kp getは最新の安定版を自動的に取得します。
kp get実行後のターミナルは次のようになります。

この操作により、barバージョン1.1.0のソースコードがローカルへ取得されるだけでなく、そのソースコードからユニットテストの実行ファイルもコンパイルされます。MetaTraderへ戻り、ナビゲータのScriptsセクションを更新してください。Scripts/bar/bin/の下にテストスクリプトが表示されます。UnitTestsBarArray、UnitTestsBarMqlRates、UnitTestsTimeSeriesをそれぞれダブルクリックして実行してください。テスト結果は、ツールボックスの[エキスパート]タブで確認できます。

ダウンロードされたbarプロジェクトはScripts/bar配下にありますので、自由に開いて内容を確認できます。このプロジェクトは、Gitによってバージョン管理されているリポジトリであることにも注目してください。

また、マニフェストファイルknitpkg.yamlが存在していること、そしてknitpkg/include/douglasrechia/bar/内にヘッダーファイルが配置されていることにも注目してください。これらのヘッダーファイルは、このパッケージを利用するすべてのプロジェクトへエクスポートされます。
注:YAMLファイルやその他のMQL以外のソースファイルを確認する場合は、Windows Explorerと任意のエディターを使用してください。エディターとしてはVS Codeを推奨します。MQLコードを編集する場合は、MetaEditorの使用を推奨します。
getコマンド:「内部で何が起きているか」:kp getを実行すると(また、一部の他のコマンドも同様に処理)、KnitPkg CLIはレジストリへ接続し、指定されたパッケージの最新安定版を取得します。レジストリは、そのバージョンに対応するGitリポジトリURLとコミットハッシュを返します。その後、kp getコマンドは、その特定のコミットハッシュを指定してリポジトリをクローンします。これが、先ほどのgit statusでリポジトリがHEAD detached状態になっている理由です。詳細については、kp getリファレンスを参照してください。
barパッケージは、依存関係を持たないSingleパッケージです。それでは次に、より興味深い構成となるcalcパッケージへ進みます。
マイルストーン1 - コピー&ペーストなしでパッケージをインストール:この時点で、@douglasrechia/barパッケージのバージョン1.1.0がMetaTrader 5環境で利用可能になっています。また、ユニットテストによって、期待どおり動作していることも確認されています。これは同時に、このパッケージを実際に開発していく環境でもあります。この環境では、開発ブランチの作成、バグ修正、新機能の実装、変更内容のコミット、そして最終的な新しいパッケージリビジョンの生成までをおこなえます。しかも、barパッケージに依存している他のプロジェクトへ一切変更を加える必要はありません。
3. パッケージcalc:Compositeパッケージの例: すでに説明したように、calcパッケージはSMA計算処理の実装を担当します。ただし、calcは依存関係として@douglasrechia/barを使用します。移動平均計算関数の引数には、TimeSeriesインターフェースが利用されます。この設計の利点は、SMA関数がTimeSeriesの実装方法を完全に抽象化できることです。TimeSeriesが単純な配列によって実装されている場合でも、別のデータ構造によって実装されている場合でも問題ありません。SMA側が必要とするのは、時系列データにおいて最新の要素が位置0にあり、その前の要素が位置1、さらにその前が位置2という形式でアクセスできることだけです。
それでは、同じ手順を繰り返しましょう。calcバージョン1.0.1を取得するには、次のコマンドを実行します。
kp get mql5 @douglasrechia/calc -v 1.0.1
練習として、MetaTraderへ戻り、ナビゲータのScriptsセクションを更新してください。Scripts/calc/bin/の下にテストスクリプトが表示されます。テスト結果は、ツールボックスの[エキスパート]タブで確認できます。
次に、calcプロジェクトと、そのコードに含まれるいくつかの特徴的な部分を確認します。まず、calcのマニフェストknitpkg.yamlを見てみましょう。ここでは新しい要素があります。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
次に、knitpkg/include/douglasrechia/calc/Calc.mqhにあるSMA関数のソースコードを確認してみましょう。以下では、このヘッダーファイルにおける重要なポイントと、Compositeパッケージに特有の仕組みについて説明します。
autocomplete.mqhへの#include:Calc.mqhでは、autocomplete.mqhを#includeすることによって、Compositeパッケージを「苦労なく」記述できるようになります。
//............... //------------------------------------------------------------------ // 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の仕組み。ローカルにインストールされたcalcパッケージ内で、autocomplete.mqhの内容を確認してください。(このヘッダーファイルはリポジトリ内には存在しません。リポジトリにはこのファイルは含まれていません。)
//+------------------------------------------------------------------+ //| 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によってMetaEditorのIntelliSenseがCalc.mqh内で正常に機能し、さらにユニットテストのコンパイルも可能になります。これらのヘッダーファイルは、kp autocompleteを実行した際に作成・解決されます。このコマンドは、マニフェストで宣言された依存パッケージについて、.knitpkg/cache/内でGitクローン(またはフェッチ)を実行します。この仕組みにより、他のパッケージへ依存するパッケージでも、快適に開発を進めることができます。
Compositeパッケージでは、自動生成されるautocomplete.mqhファイルは単なる便利機能ではありません。これは、実際の依存関係の解決とインストールをKnitPkgが管理している間でも、MetaEditorでのコンパイルとIntelliSenseを動作させるための標準的な方法です。この手順がなければ、パッケージ作成者は必然的に手動による#include管理へ戻ることになります。その結果、KnitPkgが解決しようとしているバージョンのずれやコピー&ペーストによる管理問題が再び発生してしまいます。
注:knitpkg/autocomplete/の内容は自動生成されるため、手動で編集してはいけません。
名前空間(MQL5のみ):MQL5では、パッケージのコードを、organization名と同じ名前のnamespace内に配置することが推奨されます(namespaceはMQL4では利用できません)。これにより、異なるorganizationから提供された複数のパッケージを使用する際に、シンボル名の衝突を防止できます。名前空間(namespace)の利用と、knitpkg/include/<organization>/<project name>というヘッダー配置規約を組み合わせることで、名前の競合を防ぎ、インクルードツリーを構築する際にもヘッダーファイルが上書きされないことを保証できます。
SMA関数:SMAは、TimeSeries(ITimeSeriesインターフェース)を引数として受け取り、指定された期間(平均値を計算する要素数)とshift(TimeSeries内で平均を計算する位置)に基づいて平均値を返す関数です。shiftは、TimeSeries内の位置を示します。最新の値は0、1つ前の値は1、そのさらに前の値は2というように扱われます。
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をインジケータのコード内ではなく、このパッケージ内に実装する利点は、このコードを他の任意のプロジェクトでも利用できる点にあります。
Compositeパッケージをインストール可能な状態にする(@knitpkg:include):Calc.mqh内にある次のディレクティブに注目してください。
/* @knitpkg:include "douglasrechia/bar/TimeSeries.mqh" */ /* @knitpkg:include "douglasrechia/bar/Bar.mqh" */
一見すると、これはコンパイラが無視するコメントアウトされたコードのように見えます。しかしKnitPkgにとって、/*と*/のコメント記号で囲まれたこれらのディレクティブには特別な意味があります。これらは、calcパッケージ外部に存在し、Calc.mqhが依存しているヘッダーファイルを参照しています。実際には、これら2つのヘッダーファイルはautocomplete.mqh内でインクルードされています。そのため、開発時にはMetaEditorが通常どおりCalc.mqhをコンパイルでき、さらにユニットテスト用のScriptsもコンパイルできます。
calcが他のプロジェクトで依存関係として利用される場合、Calc.mqhは変換されます。その際、autocomplete.mqhへの#includeは無効化され、代わりに@knitpkg:includeディレクティブが実際の外部ヘッダーを参照する#includeへ解決されます。パッケージ作成者には、@knitpkg:includeディレクティブを常に正しく管理する責任があります。つまり、そのヘッダーが必要とするシンボルを定義しているヘッダーだけを正しく参照するように維持する必要があります。
これらのディレクティブを正しく保つために、KnitPkgにはkp checkinstallコマンドが提供されています。このコマンドは、現在開発中のパッケージをknitpkg/autocomplete/include/配下で解決します。このケースでは、対象パスはknitpkg/autocomplete/knitpkg/include/douglasrechia/calcとなります(このパスは少し繰り返しが多く見えますが、正しい構成です)。これにより、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を使用してプロジェクトをダウンロードした場合、このファイルはすでに生成されています。実際の開発では、パッケージ作成者は@knitpkg:includeディレクティブを変更した場合や、他のプロジェクトによってパッケージが正しくインストールされることを確認したい場合に、いつでもkp checkinstallを実行できます。
@knitpkg:includeディレクティブとkp checkinstallコマンドを組み合わせることで、このワークフローにおける検証段階が形成されます。開発中は、autocomplete.mqhとMetaEditor IntelliSenseを利用して快適に作業できます。一方、インストール時にはKnitPkgがこれらのディレクティブを、正しい依存ヘッダーを参照する実際の#include文へ書き換えます。これにより、ローカル開発環境と、他のプロジェクトへ確実にインストールされる環境との間の一連の流れが完成します。その結果、#includeパスを手動で編集する必要がなくなり、必要なヘッダーが気付かないうちに不足する問題も防止できます。
マイルストーン2 - 共有処理の検証完了とCompositeパッケージの再利用準備完了:calcパッケージがインストールされ、そのユニットテストがコンパイルされ正常に通過したことで、SMAロジックは@douglasrechia/barを基盤とした、テスト済みのコンポーネントとして一度だけ実装されました。Compositeパッケージであるcalcでは、さらに@knitpkg:includeディレクティブがkp checkinstallによって検証されています。これにより、calcを依存関係として利用する際、そのヘッダーが正しく書き換えられ、インストールされたbarヘッダーへ正しく接続されることが確認されます。その結果、@douglasrechia/calcは、他のプロジェクト(インジケータ、EA、ライブラリなど)から利用できる状態になりました。利用側のプロジェクトでは、knitpkg.yamlマニフェスト内で依存関係として宣言するだけで利用でき、手動による#includeパスの調整やコードの重複は一切必要ありません。
4. smaインジケータ:必要な部品がすでに揃ったので、ここではそれらを組み合わせて、インジケータがどのように構築されるかを確認します。まず、@douglasrechia/smaバージョン1.0.1を取得します。
kp get mql5 @douglasrechia/sma -v 1.0.1
注:前のセクションではbarプロジェクトとcalcプロジェクトをそれぞれ個別にkp getしていました。これは、それぞれの構成を単独で確認できるようにするためでした。ただし、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 だけですが、barもsmaの間接的な依存関係として解決され、ダウンロードされます。これは、calcがbarに依存しているためです。
- include_mode: flat:smaではFlatモードが使用されています。また、エントリーポイントとしてsrc/KnitPkgSMA.mqhが指定されています。これは、SMAインジケータが必要とするすべての依存関係が、1つのフラット化されたファイル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); }
ここでは、標準的なMQLの#includeを使用して、フラット化されたファイル../knitpkg/flat/KnitPkgSMA_flat.mqhを参照していることに注目してください。このフラット化されたファイルは、kp installの実行時に、エントリーポイントと解決済みの依存関係を基にKnitPkgによって自動生成されます。そのため、このファイルはリポジトリ内には存在しません(なお、kp installコマンドもkp getのワークフローの一部として実行されます)。
ここまで読めば、パッケージを使用する利点が明確になったはずです。SMA計算の具体的な実装は、インジケータ内部に埋め込まれていません。その結果、インジケータのコードは大幅に簡潔になります。インジケータは、calcで実装された関数を呼び出すだけでよく、OnCalculate()に渡される価格配列から構築したTimeSeriesを引数として渡します。
上記でkp getを実行してsmaをダウンロードした場合、MetaTraderに戻り、ナビゲータのIndicators一覧を更新してください。その後、「Indicators/sma/bin/KnitPkgSMA」に表示されるインジケータを現在のチャートへ追加します。

マイルストーン3 - 再利用可能なFlatな成果物の生成とインジケータのコンパイル完了:kp getを実行すると、KnitPkgはエントリーポイントと解決済みの依存関係から、フラット化されたヘッダーファイルknitpkg/flat/KnitPkgSMA_flat.mqhを生成します。その後、インジケータは正常にコンパイルされます。これは、@douglasrechia/calcパッケージが正しく利用され、SMA計算機能を提供していることを確認するものです。ここでインジケータによって使用されたのと同じように、calcパッケージは他のあらゆるプロジェクトでも再利用できます。EA、別のインジケータ、またはライブラリで利用する場合でも、1つのソースファイルをコピーする必要はありません。
5. expertdemo EA: ここからは、最後の構成要素である、移動平均クロスオーバーシステムを実装する実演用EAについて説明します。expertdemo EAバージョン2.0.1を取得します。
kp get mql5 @douglasrechia/expertdemo -v 2.0.1
まず、マニフェスト内の重要な部分について説明します。expertdemoは@douglasrechia/bar(すでに説明済み)と@douglasrechia/barhelperに依存しています。この記事を簡潔に保つため、barhelperの詳細については説明しません。読者が理解しておくべき点は、barhelperがNewTimeSeriesFromIndicatorという関数を提供していることです。この関数は、任意のインジケータの出力バッファに格納された値を含む、ITimeSeriesの具体的な実装を返します。必要に応じて、バッファ全体を使用することも、一部だけを使用することも可能です。
これは、expertdemoがcalcパッケージ内のSMA関数ではなく、KnitPkgSMAインジケータを使用するために必要になります。注意深い読者であれば、expertdemoのマニフェストにcalcパッケージが記載されていないことに気付くでしょう。これは、パッケージの機能を利用する方法が複数存在することを示しています。KnitPkgSMAはcalcを基盤として構築されていますが、expertdemoはcalcへ直接依存しているのではなく、calcを使用して構築されたインジケータに依存しています。興味深い点です。
expertdemo EAも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; }
このEAは、高レベルの抽象化関数を使用して構築された、整理され理解しやすいコードになっています。OnInit()では、グローバルオブジェクトであるbarWatcherとmyBarがインスタンス化されます。また、3つのSMAのハンドラは、kp getコマンドによって準備されたKnitPkgSMAインジケータへの正確なパスを使用して初期化されます。
注:expertdemoは、OnInit()内のiCustom関数で指定された場所にバイナリファイルKnitPkgSMA.ex5が存在することを前提としています。そのため、expertdemoを実行するユーザーは、事前に一度だけコマンドkp get mql5 @douglasrechia/smaを実行する必要があります。
OnTick()では、@douglasrechia/barのBarWatcherオブジェクトを使用して新しいバーの生成を検出します。新しいバーが出現すると、OnNewBar()が呼び出されます。
EAの中心となる処理はOnNewBar()にあります。@douglasrechia/barhelperのNewTimeSeriesFromIndicator関数を使用することで、各SMAインジケータの最新5つの要素(実際には3つで十分)を簡単に取得できます。その後、同じくbarhelperで実装されているCrossUp関数を利用し、短期SMAが中期SMAを上抜けたかどうかに応じて、EAはポジションを建てる、または決済します。
ポジション確認および取引実行には、MQL標準ライブラリのCTradeクラスとCPositionInfoクラスを使用しています。重要な点として、KnitPkgはMQL標準ライブラリと完全に互換性があり、その利用に制限を設けていません。むしろ、標準ライブラリは成熟した高品質なライブラリであり、積極的な利用が推奨されます。
マイルストーン4 - 「パッケージ → インジケータ → EA」スタックがコピーなしで動作:expertdemoEAは現在、SMAロジックをKnitPkgSMAインジケータ経由で利用しています。そして、そのKnitPkgSMAインジケータは、barおよびcalcパッケージを基盤として構築されています。プロジェクト間でソースファイルをコピーする必要はなく、#includeパスを手動で編集する必要もありません。すべての接続処理はknitpkg.yamlマニフェストに記述され、KnitPkgによって再現可能なインストール形式へ解決されます。
結論
KnitPkgは、MQL4/MQL5コードの再利用方法を大きく変えます。ターミナル間でフォルダをコピーし、パス名を変更し、#include ディレクティブを手動修正する代わりに、各ロジックは一度だけバージョン管理されたパッケージとして実装され、その後マニフェストを通じて宣言的に利用されます。実際には、これは脆弱で手動作業中心だったワークフローを、再現可能なビルドパイプラインへ変換します。依存関係は明示され、バージョンは管理され、アップデート時に複数プロジェクトへ個別対応する「外科手術」のような作業は不要になります。
この記事を読むことで、MQL開発を、パッケージ、インジケータ、ライブラリ、EAなどの再利用可能なコンポーネント群として構成し、一貫した方法で組み立てる方法を理解できました。具体的には、以下の内容を習得しました。
- KnitPkgを理解するための明確な概念モデル: レジストリはメタデータを保存し、Gitリポジトリがソースコードの唯一の基準となります。また、セマンティックバージョニング(^、~、*)によって制御されたアップグレードが可能になります。
- マニフェスト(knitpkg.yaml)の役割:プロジェクト識別情報、ビルド対象、依存関係を1か所で宣言する場所であり、再現可能なビルドを実現します。
- 実用的なインストールワークフロー:Includeモード(従来型のincludeツリー)とFlatモード(単一生成ヘッダー。MQL5マーケット配布にも適しています)の違いを理解しました。
- Compositeパッケージの仕組み: 開発時にはautocomplete.mqhを使用し、インストール時には@knitpkg:includeディレクティブがKnitPkgによって実際のinclude文へ変換されます。
- MQLにおけるユニットテストの重要性:テストはプロジェクトとともにコンパイルされるため、新しいパッケージバージョンによって既存のEAやインジケータが壊れないことを確認できます。
このケーススタディでは、これらの考え方を具体的につなげました。1つのSMA実装をパッケージ化し、それをインジケータで再利用し、さらにEAから利用しました。また、新しいバーの検出によって効率的な動作も実現しています。これは、再利用可能な「パッケージ → インジケータ/ライブラリ → EA」スタックを構築し、MQL開発におけるコピー&ペースト作業を削減するための実践的な設計指針となります。
添付ファイル
この記事で紹介したすべてのプロジェクトのソースコードは、本文中でリンクされているGitリポジトリから取得できます。以下の添付ファイルは、kp getを実行した結果としてKnitPkgによって自動生成されるファイルに対応しています。kp getは内部的にkp autocomplete、kp checkinstall、およびkp installの処理を呼び出します。これらのファイルはGitには保存されませんが、読者は記事内で説明されているものと同じkp getコマンドを実行することで、ローカル環境上で再生成できます。
| ファイル名 | 説明 |
|---|---|
| autocomplete.mqh | calc パッケージ用にkp get の処理の一部として生成されるautocomplete補助ヘッダー(knitpkg/autocomplete/autocomplete.mqh) |
| Calc.mqh | calcパッケージ用に解決されたincludeヘッダー。kp get / kp checkinstallの実行時に生成および検証される(knitpkg/autocomplete/knitpkg/include/douglasrechia/calc/Calc.mqh) |
| KnitPkgSMA_flat.mqh | sma プロジェクトでkp get / kp installを実行した際に生成される、フラット化されたSMAインジケータ用ヘッダー(knitpkg/flat/KnitPkgSMA_flat.mqh) |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/21429
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5取引ツール(第22回):二項分布のヒストグラムと確率質量関数(PMF)の描画
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
取引規律をコードに組み込む(第3回):MQL5でホワイトリストを用いて銘柄単位の取引境界を強制適用する
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
面白そうですが、少し疑問があります。
もし誰かが、KnitPkgによって管理されるはずのMQL5プロジェクト/プログラム/パッケージを含むGitリポジトリ をクローンした場合、リポジトリ内に自動生成ファイル(_flatなど)が欠落しているため、コンパイルできないソースコードが得られる可能性が高いでしょう。 取得方法にかかわらず、リリースがコンパイル可能になるよう、必要な自動生成ソースをすべてGitにコミットするのは実現可能ではないでしょうか?
実際に自動生成されたコードをコンパイルしてデバッグしているため、依存関係に変更を加えるのがどれほど便利になるかは定かではありません。対応するオリジナルコードに簡単に「ジャンプ」できないのですが、何か見落としている点があるのでしょうか? 例えば、インジケーター(あるいはエキスパートアドバイザー)の開発中に、barパッケージを変更する必要が生じたとします。その場合、どのように対応しますか?
最後に、サンプルコードの実装についていくつか疑問があります(おそらく単なるサンプルだからでしょうが)。例えば、各ティックやバーごとに`new`や`delete`が呼び出されている点などです。また、例えば次のようなコードがあります:
price 配列をpriceArrayの コンストラクタ(そこで配列の一部がコピーされる)に渡してから、そのpriceArrayを SMA に渡すのはどのような目的があるのでしょうか?コピーせずに、元のprice 配列を参照渡しでSMA に渡せばよいのではないでしょうか?
実際に自動生成されたコードをコンパイルしてデバッグしているため、依存関係に変更を加えるのがどれほど便利になるかは定かではありません。対応するソースコードに簡単に「ジャンプ」できないのですが、何か見落としている点があるのでしょうか? 例えば、インジケーター(あるいはエキスパートアドバイザー)の開発中に、barパッケージを変更する必要があるとします。その場合、どのように対応しますか?
最後に、サンプル実装のいくつかの点(おそらく単なるサンプルだからでしょうが)について疑問を感じています。例えば、ティックやバーごとに new/delete を行っている点などです。また、例えば次のようなコードがあります:
price 配列をpriceArrayコンストラクタ(そこで配列の一部がコピーされる)に渡してから、そのpriceArrayを SMA に渡すのはどのような目的があるのでしょうか?コピーせずに、元のprice 配列を参照渡しで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に関するフィードバック(ワークフローやサンプルコードについてでも)は大歓迎ですので、これらのアイデアについて遠慮なく異論を唱えたり、発展させたりしてください。