English Русский Español Deutsch Português
preview
多通貨エキスパートアドバイザーの開発(第26回):取引商品の情報提供

多通貨エキスパートアドバイザーの開発(第26回):取引商品の情報提供

MetaTrader 5テスター |
24 3
Yuriy Bykov
Yuriy Bykov

内容


はじめに

前回の記事では、私たちはついに一つの到達点とも言える段階に到達しました。それは、単純な取引戦略を自動的に完全なエキスパートアドバイザー(EA)へと変換できる包括的なシステムです。このシステムは、複数の銘柄や時間足に対して同時に動作することを前提としています。また、不利な状況や逆に過度に有利な状況が発生した際に取引を停止することを可能にする、資金管理システムおよびリスク管理機能についても考慮されてきました。

本連載のほぼ全体を通して、一つのシンプルな取引戦略のみを扱ってきました。そして最終段階に近づいた頃になってようやく、新しい戦略を追加し、それを主要戦略として利用する試みが始まりました。このプロセスは、(その戦略に本当に価値があるのであれば)ほぼあらゆる取引戦略の可能性を示すものでした。

しかし現在の地点まで到達したことで、次の段階としてさらに広い選択肢が開かれています。その結果、次にどの方向へ進むべきか判断すること自体が難しくなっています。この課題を整理するために、プロジェクトのソースコードの構成と管理方法そのものの見直しが試みられました。その第一歩は第23回において既に始まっており、コードの大部分を「ライブラリ部分」として分離し、それ以外を「プロジェクト部分」として残す形を取りました。その後、MetaQuotesの新しいコードリポジトリ(MQL5 Algo Forge)の機能に注目し、それについては別記事「MQL5 Algo Forgeへの移行(第1回):メインリポジトリの作成」で初期段階を扱いました。 この新しいリポジトリの活用方針は現在も発展途上にあります。全体としては、ライブラリ部分を複数の方向で同時に発展させることができる環境を構築したいと考えています。それが可能かどうかは、今後の実践次第です。

このプロセスは、実際に実装してみなければ分かりません。つまり、これまでの設計判断が正しかったのかどうかは、実装を通じて初めて明らかになります。そこで今回は、これまで構築してきた「Advisor」というライブラリを用いて、新しいプロジェクトを作成してみます。ここでは、すぐに複雑な取引戦略を用いた大規模なEA開発に入ることはしません。むしろその逆であり、取引EAの開発そのものを目的としないプロジェクトを作成します。


方向性の整理

直近で追加されたストラテジー「 SimpleCandles」に関して、読者の一人から興味深い質問が寄せられました。このストラテジーでは、同一方向の連続ローソク足の本数というパラメータが用いられています。したがって、異なる銘柄や異なる時間足において、そうしたローソク足の連続パターンがどのように分布しているのかを事前に把握できれば望ましい、という指摘です。そうすることで、最適化プロセスにすべてを委ねる必要がなくなります。

実際のところ、自動最適化をおこなう場合であっても、入力パラメータがどの範囲で変化するのかを理解しておくことは依然として重要です。もちろん、単純に広い範囲を設定することも可能ですが、最適化効率が大幅に低下します。パラメータの組み合わせ数が増えれば増えるほど、最適な組み合わせに到達する確率は低下するためです。どの程度低下するかを厳密に見積もることは難しいものの、その傾向を理解するだけでも、効率改善に向けた工夫をおこなう十分な動機になります。

また、異なる銘柄における価格挙動の統計を収集することは、「同一のパラメータレンジを複数の銘柄に適用できるのか」という問いに答える手助けにもなります。もし可能であれば、初期段階の最適化プロセスは大幅に単純化されます。一方で、銘柄ごとに性質が異なる場合には、最適化タスク自体をより複雑に設計する必要が出てきます。

このような背景から、ここでは補助的なEA、あるいはEAの一部として機能するモジュールを作成することを検討します。それは複数の銘柄と時間足に関する統計情報を、何らかの形で可視化できるものです。将来的には、この仕組みがトレーディング戦略そのものの中でも役立つ可能性があります。

ただし、それは次の段階の話です。まず取り組むべきは、将来的な再利用を見据えたソースコードの整理と構造設計です。


プロジェクトの作成

一見すると、この段階にはそれほど複雑な要素はないように思えます。これまでも私たちは何らかの形でコードを保存してきましたし、その延長として同じ方法を続けることも可能です。しかし、そこには明確な違いがあります。これまでの方法は、すべてのプロジェクトコードを一つのフォルダにまとめ、その都度新しいプロジェクトを作成する際には、前回のフォルダをコピーして利用するというものでした。このアプローチは非常にシンプルであり、厳密な後方互換性を気にする必要がない開発においては十分に合理的です。特に開発初期の段階では、頻繁に大きな変更が発生するため、この方法はむしろ適しています。しかしプロジェクトが成長してくると状況は変わります。この段階では、コードは明確に二つの領域へと分かれていきます。頻繁には変更されない部分と、大幅な変更や全面的な作り直しが発生し得る部分です。

このような状況においては、すべてのコードを一つのフォルダに保存する方法の欠点が、次第に利点を上回るようになります。少し前の段階で、私たちはコードの大部分をMQL5/Include/antekov/Advisorフォルダへ移動し、これを「ライブラリ部分」として扱うようにしました。しかし現在の視点から見ると、この配置は必ずしも最適とは言えません。

例えば、同じAdvisorライブラリを利用する二つのプロジェクトを並行して開発している状況を想像してみましょう。この場合、変更の多くはプロジェクト側に集中しますが、同時にライブラリ部分にも修正が加えられることがあります。もし両方のプロジェクトが同じ場所にあるライブラリ(MQL5/Include/antekov/Advisor)を参照している場合、当然ながらコンフリクトが発生しかねません。これを避けるためには、プロジェクトを切り替えるたびに、少なくともライブラリ部分を別のバージョンへ切り替える必要が出てきます。それ自体は難しい作業ではありませんが、このような操作が常に必要になるのは望ましいことではありません。うっかり切り替えを忘れてしまえば、すでにおこなった修正が誤ったブランチに反映されてしまい、それを後から正しいブランチへ移し替えるといった余計な作業が発生しかねません。

そこで、ここではアプローチそのものを変更することを試みます。各プロジェクトは独立したリポジトリとして扱うことにします。そしてプロジェクトフォルダの内部には常にIncludeフォルダを設け、その中にライブラリ部分のフォルダを配置します。ここで重要なのは、「単一のライブラリ」ではなく「複数のライブラリ部分」を個別に配置するという点です。それぞれのライブラリは、独立したコードリポジトリのクローンとして扱われることになります。


ライブラリ側リポジトリ

ライブラリ部分については、MQL5 Algo Forge、もしくはその他の公開Gitリポジトリ上に新しいリポジトリを作成することにします。これまでライブラリに使用していたAdvisorという名称はやや汎用的すぎるため、ここでは、より固有の名前にするためAdwizardへと改名します。今後、この名前を私たちのライブラリとして使用していきます。

このリポジトリには、ライブラリに関するすべてのファイルを配置します。リポジトリ作成直後の状態では、mainという単一ブランチのみが存在しています。ここに新たにdevelopブランチを作成し、今後の記事向けの開発や新機能開発はこのブランチから派生させていくことにします。各機能追加や記事用の作業ブランチは、実装完了後にクローズされ、その変更はdevelop ブランチへマージされます。その後、最終的にはmainへ統合されます。通常、この一連の作業は記事執筆の完了時におこなわれます。

このリポジトリをどのフォルダへクローンしても正しく動作するようにするため、ライブラリ内の一部ファイルに小さな修正を加えました。具体的には、標準ライブラリを参照するために使用していた#includeディレクティブのパスを修正しています。従来はIncludeフォルダ配下の特定パスを前提としていましたが、それを相対パスに置き換えることで、MQL5/Include/antekov/Advisorといった特定のライブラリ配置に依存しない構造へと変更しました。

たとえば、Optimization.mqhファイルでは、次のような置換がおこなわれました。 

#include <antekov/Advisor/Optimization/Optimizer.mqh>
#include "../Optimization/Optimizer.mqh"

また OptimizerTask.mqhでは、外部ライブラリとしてfxsaberの単一ファイルを利用していましたが、それもライブラリ内部へ統合し、Utilsフォルダに移動しました。

#include <antekov/Advisor/Database/Database.mqh>
#include <fxsaber/MultiTester/MTTester.mqh> // https://www.mql5.com/ru/code/26132

#include "../Database/Database.mqh"
#include "../Utils/MTTester.mqh" // https://www.mql5.com/ru/code/26132

これらの変更はすでにライブラリリポジトリへコミットされています。


プロジェクト側リポジトリ

プロジェクト用として、SymbolsInformerリポジトリを新しく作成します。このリポジトリには、mainブランチに加えてdevelop ブランチも用意します。また、もしこのプロジェクトが複数の記事にまたがる場合には、各記事ごとの変更をそれぞれ別のブランチに分割することが望ましいでしょう。これらのブランチはdevelopブランチから作成され、作業が完了次第developおよびmainにマージされます。

まず、プロジェクトを格納するためのフォルダを作成します。たとえば MQL5/Experts/Article/17606.といったディレクトリです。そのフォルダにリポジトリをクローンし、その中にIncludeフォルダを作成します。このIncludeフォルダには、このプロジェクトが依存する他のライブラリのリポジトリを配置します。現時点ではライブラリは1つだけであり、それがAdwizardです。そのためInclude フォルダにはAdwizardのリポジトリをクローンして配置します。もし他のライブラリが必要になれば、同様にこのIncludeフォルダへ追加していくことになります。

これらの操作の結果として、ターミナル上ではおおよそ次のようなフォルダ構成になります。

クローンされたAdwizardリポジトリでは 、developブランチへ切り替えます。このブランチは複数の記事間で共通して使用されるものです。プロジェクト作業中にAdwizardライブラリに変更を加えない場合は、このdevelopブランチ上に留まり、他の記事でおこなわれた更新に応じて随時同期していきます。一方で、現在のプロジェクト作業中にライブラリの修正が必要になった場合には、新しいブランチを作成して対応します。

その後、プロジェクトリポジトリ側でも本記事用の作業ブランチを作成し、そのブランチ上で開発を開始します。以上が新しいプロジェクト作成の概要です。詳細については別記事で説明する予定です。


プロジェクト概要

まずは、必要となるツールの開発に向けて、簡単な技術仕様を整理してみます。このツールはEAとして実装します。このツールが周期的な再計算や、時間経過に応じて変化する値を画面に表示するような処理を必要とするためです。

まず最初に、同一方向のローソク足が連続する数をカウントするためには、一定の時間範囲を定義する必要があります。その範囲内で統計を収集していきます。この方法はいくつか考えられます。たとえば、現在時刻から遡って日数を指定する方法や、開始日と終了日の2点を指定する方法などです。ただし、まずは単純な形として「現在時刻から開始する期間」に限定して統計を計算することにします。この期間の長さは、時間足(例えば日足)とその本数を指定することで定義します。ここではこれを「メイン時間足」と呼ぶことにします。

次に、どの取引銘柄とどの時間足に対して計算するかを指定する必要があります。もちろん、EAを起動した銘柄と時間足だけで計算することも可能です。しかし、将来的な拡張性を考えると、複数の銘柄および複数の時間足に対して同時に計算できるようにしておく方が望ましいでしょう。

以上を踏まえると、EAの入力パラメータは次のようになります。

  • メイン時間足
  • メイン時間足のローソク足本数
  • 銘柄のリスト
  • 時間足のリスト

このパラメータセットは将来的に拡張される可能性があります。銘柄と時間足のリストは、それぞれカンマ区切りの文字列として指定します。時間足の名前は、MQL5ターミナルで使用されている表記(M5、M15、H1など)に合わせます。

銘柄および時間足ごとに、以下の値を計算します。

  • ローソク足の平均サイズ
    • 陽線(終値が始値以上の足)
    • 陰線(終値が始値以下の足)
    • 全体(陽線と陰線)
  • 平均的な連続本数(ここでいう「連続」とは、同じ方向のローソク足が2本以上続くことを指す)
  • 連続本数
    • 2
    • 3
    • ...
    • 8
    • 9

このリストは固定されたものではなく、必要に応じて新しい統計項目を追加できるように設計されています。


初期バージョンの実装

まずは、可能な限りシンプルな形で実装することから始めます。計算結果はグローバル配列に保持し、ログおよびチャート上のコメントとして表示します。この段階では、どのデータが有用で、どのデータが不要なのかはまだ明確ではありません。そのため最初のバージョンは、必要な情報を見極めるための「観測装置」としての役割を持たせます。入力チェックやデータ構造の厳密な設計についても、この時点では最小限にとどめます。

入力パラメータは、先ほど整理した仕様に従い次のように定義します。

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "::: Calculation period"
sinput ENUM_TIMEFRAMES mainTimeframe_  = PERIOD_D1;   // Main timeframe
input int         mainLength_          = 30;          // Number of candles on the main timeframe

input group "::: Symbols and timeframes "
sinput string     symbols_             = "";          // Symbols (comma separated)
sinput string     timeframes_          = "";          // Timeframes (e.g. M5, H1, H4)

複数の銘柄と時間足を1つの文字列として受け取るため、それぞれを分解して保持する配列が必要になります。

また、計算結果を保持するために二次元配列を使用します。第一インデックスは銘柄、第二インデックスは時間足に対応します。MQLでは二次元配列を宣言する際に第二次元のサイズを固定する必要があるため、ここでは TFN という定数を導入します。これは現在利用可能な標準時間足の総数であり、今回は21種類存在します。

// Number of existing timeframes
#define TFN (21)

// Global variables
string g_symbols[];                       // Array of all used symbols
ENUM_TIMEFRAMES g_timeframes[];           // Array of all used timeframes

// Arrays of calculated values.
// The first index is a symbol, the second index is a timeframe
double symbolAvrCandleSizes[][TFN];       // Array of average sizes of all candles
double symbolAvrBuyCandleSizes[][TFN];    // Array of average sizes of bullish candles 
double symbolAvrSellCandleSizes[][TFN];   // Array of average sizes of bearish candles

double symbolAvrSeriesLength[][TFN];      // Array of average series lengths 

int symbolCountSeries2[][TFN];            // Array of the number of series of length 2
int symbolCountSeries3[][TFN];            // Array of the number of series of length 3
int symbolCountSeries4[][TFN];            // Array of the number of series of length 4
int symbolCountSeries5[][TFN];            // Array of the number of series of length 5
int symbolCountSeries6[][TFN];            // Array of the number of series of length 6
int symbolCountSeries7[][TFN];            // Array of the number of series of length 7
int symbolCountSeries8[][TFN];            // Array of the number of series of length 8
int symbolCountSeries9[][TFN];            // Array of the number of series of length 9

時間足の定数(ENUM_TIMEFRAMES)、文字列表記、配列インデックスを相互変換するため、補助関数を用意します。これにより以下が可能になります。

  • 文字列から時間足定数(StringToTimeframe)を取得する
  • 時間足定数から、PERIOD_(TimeframeToString)接頭辞を除いた文字列名を取得する
  • 時間足定数から配列インデックス(TimeframeToIndex)を取得する

// Array of all timeframes
ENUM_TIMEFRAMES tfValues[] = {
   PERIOD_M1, PERIOD_M2, PERIOD_M3, PERIOD_M4, PERIOD_M5, PERIOD_M6,
   PERIOD_M10, PERIOD_M12, PERIOD_M15, PERIOD_M20, PERIOD_M30,
   PERIOD_H1, PERIOD_H2, PERIOD_H3, PERIOD_H4, PERIOD_H6,
   PERIOD_H8, PERIOD_H12, PERIOD_D1, PERIOD_W1, PERIOD_MN1
};

//+------------------------------------------------------------------+
//| Convert a string name to a timeframe                             |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES   StringToTimeframe(string s) {
// If the string contains the "_" symbol, leave only the characters that follow it
   int pos = StringFind(s, "_");
   if(pos != -1) {
      s = StringSubstr(s, pos + 1);
   }

// Convert to uppercase
   StringToUpper(s);

// Arrays of corresponding string names of timeframes
   string keys[] = {"M1", "M2", "M3", "M4", "M5", "M6", "M10", "M12", "M15", "M20", "M30",
                    "H1", "H2", "H3", "H4", "H6", "H8", "H12", "D1", "W1", "MN1"
                   };

// Search for a match and return it if found
   FOREACH(keys) {
      if(keys[i] == s)
         return tfValues[i];
   }

   return PERIOD_CURRENT;
}

//+------------------------------------------------------------------+
//| Convert a timeframe to a string name                             |
//+------------------------------------------------------------------+
string TimeframeToString(ENUM_TIMEFRAMES tf) {
// Get the timeframe name of the 'PERIOD_*' type
   string s = EnumToString(tf);

// Return the part of the name after the '_' symbol
   return StringSubstr(s, StringFind(s, "_") + 1);
}

//+------------------------------------------------------------------+
//| Convert a timeframe to an index in an array of all timeframes    |
//+------------------------------------------------------------------+
int   TimeframeToIndex(ENUM_TIMEFRAMES tf) {
// Search for a match and return it if found
   FOREACH(tfValues) {
      if(tfValues[i] == tf)
         return i;
   }

   return WRONG_VALUE;
}

すべての値の計算はCalculate()関数の内部でおこないます。この関数内では、銘柄と時間足のすべての組み合わせを走査するネストされたループを構成します。その中で、各銘柄・時間足に新しいバーが形成されたかどうかを確認します。もし新しいバーが形成されている場合には、補助的な計算関数を呼び出します。また、新しいバーの到着を待たずにすべての値を即時に計算する必要がある場合は、force パラメータによってその指示を渡すことができます。このモードはEA起動時に使用され、起動直後に結果を確認できるようにするためのものです。

//+------------------------------------------------------------------+
//| Calculate all values                                             |
//+------------------------------------------------------------------+
void Calculate(bool force = false) {
   string symbol;
   ENUM_TIMEFRAMES tf;

// For each symbol and timeframe
   FOREACH_AS(g_symbols, symbol) {
      FOREACH_AS(g_timeframes, tf) {
         // If a new bar has arrived for the given symbol and timeframe, then 
         if(IsNewBar(symbol, tf) || force) {
            // Find the number of candles for calculation
            int n = PeriodSeconds(mainTimeframe_) * mainLength_ / PeriodSeconds(tf);

            // Calculate the average candle sizes
            CalculateAvrSizes(symbol, tf, n);

            // Calculate the lengths of candlestick series
            CalculateSeries(symbol, tf, n);
         }
      }
   }
}

直接的な計算処理は2つの補助関数に分割しています。それぞれの関数は、1つの銘柄と1つの時間足に対する計算のみを担当します。これら2つのパラメータに加えて、もう1つの引数として、その計算対象となるローソク足本数も渡します。

平均値はCalculateAvrSizes()関数で計算されます。 まず銘柄名と時間足から、結果を格納する二次元配列のインデックスstを特定します。ここで、始値と終値が同じローソク足は、陽線としても陰線としても両方に含めて扱います。算出された平均値は最終的にポイント単位の整数値に丸められます。

//+------------------------------------------------------------------+
//| Calculate average candle sizes                                   |
//+------------------------------------------------------------------+
void CalculateAvrSizes(string symbol, ENUM_TIMEFRAMES tf, int n) {
// Find the index used for the desired symbol
   int s;
   FIND(g_symbols, symbol, s);

// Find the index used for the desired timeframe
   int t = TimeframeToIndex(tf);

// Array for candles
   MqlRates rates[];

// Copy the required number of candles into the array
   int res = CopyRates(symbol, tf, 1, n, rates);

// If everything was copied, then
   if(res == n) {
      // Number of up and down candles
      int nBuy = 0, nSell = 0;

      // Zero out the elements for the calculated average values
      symbolAvrCandleSizes[s][t] = 0;
      symbolAvrBuyCandleSizes[s][t] = 0;
      symbolAvrSellCandleSizes[s][t] = 0;

      // For all candles
      FOREACH(rates) {
         // Find the candle size
         double size = rates[i].high - rates[i].low;

         // Add it to the total size of all candles
         symbolAvrCandleSizes[s][t] += size;

         // If this is a bullish candle, then we take it into account
         if(IsBuyRate(rates[i])) {
            symbolAvrBuyCandleSizes[s][t] += size;
            nBuy++;
         }

         // If this is a downward candle, take it into account 
         if(IsSellRate(rates[i])) {
            symbolAvrSellCandleSizes[s][t] += size;
            nSell++;
         }
      }

      // Get the size of one point for a symbol
      double point = SymbolInfoDouble(symbol, SYMBOL_POINT);

      // Find the average values in points
      symbolAvrCandleSizes[s][t] /= n * point;
      symbolAvrBuyCandleSizes[s][t] /= nBuy * point;
      symbolAvrSellCandleSizes[s][t] /= nSell * point;

      // Round them to whole points
      symbolAvrCandleSizes[s][t] = MathRound(symbolAvrCandleSizes[s][t]);
      symbolAvrBuyCandleSizes[s][t] = MathRound(symbolAvrBuyCandleSizes[s][t]);
      symbolAvrSellCandleSizes[s][t] = MathRound(symbolAvrSellCandleSizes[s][t]);
   }
}

連続本数の計算も同様に CalculateSeries()関数内でおこなわれます。この関数では、補助的な配列としてseriesLens(要素数100)を使用します。この配列では、インデックスが連続本数を表し、その要素がその長さの連続の出現回数を表します。このようにすることで、ほとんどの連続本数は100本未満で収まるという前提を置いています。ここでは、ローソク足の連続本数が10本未満の連続数のみを表示します。また実際の表示対象としては、本数が10本未満の連続数のみを扱います。さらに、seriesLensに格納された各連続長の出現回数を、symbolCountSeries*という名前の対応する結果配列へと書き戻します。

//+------------------------------------------------------------------+
//| Calculate the lengths of candlestick series                      |
//+------------------------------------------------------------------+
void CalculateSeries(string symbol, ENUM_TIMEFRAMES tf, int n) {
// Find the index used for the desired symbol
   int s;
   FIND(g_symbols, symbol, s);

// Find the index used for the desired timeframe
   int t = TimeframeToIndex(tf);

// Array for candles
   MqlRates rates[];

// Copy the required number of candles into the array
   int res = CopyRates(symbol, tf, 1, n, rates);

// If everything was copied, then
   if(res == n) {
      // Current series length
      int curLen = 0;

      // Direction of the previous candle
      bool prevIsBuy = false;
      bool prevIsSell = false;

      // Array of numbers of series of different lengths (index = series length)
      int seriesLens[];

      // Set the size and initialize
      ArrayResize(seriesLens, 100);
      ArrayInitialize(seriesLens, 0);

      // For all candles
      FOREACH(rates) {
         // Determine the candle direction
         bool isBuy = IsBuyRate(rates[i]);
         bool isSell = IsSellRate(rates[i]);

         // If the direction is the same as the previous one, then
         if((isBuy && prevIsBuy) || (isSell && prevIsSell)) {
            // Increase the series length
            curLen++;
         } else {
            // Otherwise, if the length is within the required range, then
            if(curLen > 1 && curLen < 100) {
               // Increase the counter of the length series
               seriesLens[curLen]++;
            }
            // Reset the current series length
            curLen = 1;
         }
         // Save the direction of the current candle as the previous one
         prevIsBuy = isBuy;
         prevIsSell = isSell;
      }

      // Initialize the array element for the average series length
      symbolAvrSeriesLength[s][t] = 0;
      int count = 0;

      //  For all series lengths we find the sum and quantity
      FOREACH(seriesLens) {
         symbolAvrSeriesLength[s][t] += seriesLens[i] * i;
         count += seriesLens[i];
      }

      // Calculate the average length of candlestick series
      symbolAvrSeriesLength[s][t] /= (count > 0 ? count : 1);

      // Copy the values of the series lengths into the final arrays
      symbolCountSeries2[s][t] = seriesLens[2];
      symbolCountSeries3[s][t] = seriesLens[3];
      symbolCountSeries4[s][t] = seriesLens[4];
      symbolCountSeries5[s][t] = seriesLens[5];
      symbolCountSeries6[s][t] = seriesLens[6];
      symbolCountSeries7[s][t] = seriesLens[7];
      symbolCountSeries8[s][t] = seriesLens[8];
      symbolCountSeries9[s][t] = seriesLens[9];
   }
}

Show()関数は、結果の表示を担当します。最初のバージョンでは、出力先はターミナルログと、EAを起動したチャート上のコメント表示のみです。そのため、この段階では結果はテキスト形式で提示できれば十分です。この表示処理は、別途用意されたTextComment()関数が担当します。

//+------------------------------------------------------------------+
//| Show results                                                     |
//+------------------------------------------------------------------+
void Show() {
// Get the results as text
   string text = TextComment();

// Show it in the comment and in the log
   Comment(text);
   Print(text);
}

EAの初期化関数では、入力パラメータを処理するだけで十分です。具体的には、指定された銘柄名と時間足をそれぞれ個別の値に分割し、その後、結果を格納するために必要なサイズの配列を準備します。その処理が完了した後に、計算処理と結果表示の関数を呼び出すことができます。

//+------------------------------------------------------------------+
//| Initialize the EA                                                |
//+------------------------------------------------------------------+
int OnInit(void) {
// Fill in the symbol array for calculations from the inputs
   SPLIT(symbols_, g_symbols);

// If no symbols are specified, use the current single symbol
   if(ArraySize(g_symbols) == 0) {
      APPEND(g_symbols, Symbol());
   }
// Number of symbols for calculations
   int nSymbols = ArraySize(g_symbols);

// Initialize arrays for calculated values
   Initialize(nSymbols);

// Fill the array with timeframe names from the inputs
   string strTimeframes[];
   SPLIT(timeframes_, strTimeframes);
   ArrayResize(g_timeframes, 0);

// If timeframes are not specified, use the current one
   if(ArraySize(strTimeframes) == 0) {
      APPEND(strTimeframes, TimeframeToString(Period()));
   }

// Fill the timeframe array from the timeframe names array
   FOREACH(strTimeframes) {
      ENUM_TIMEFRAMES tf = StringToTimeframe(strTimeframes[i]);
      if(tf != PERIOD_CURRENT) {
         APPEND(g_timeframes, tf);
      }
   }

// Perform a forced recalculation
   Calculate(true);

// Show the results
   Show();

   return(INIT_SUCCEEDED);
}

上記の関数では、新しいSPLITマクロを使用しています。これはAdwizardライブラリのUtils/Macros.mqhファイルに追加されたものであり、今回のプロジェクトで必要となったライブラリ側の変更はこれが唯一のものです。

このマクロは、文字列を複数の要素に分割するためのもので、区切り文字としてカンマとセミコロンの両方を扱えるように設計されています。

#define SPLIT(V, A)      { string s=V; StringReplace(s, ";", ","); StringSplit(s, ',', A); }

それでは、開発したEAの動作結果を見てみましょう。


EAテスト

デフォルトパラメータのままEAを任意のチャート上で起動してみます。すると、次のような結果が表示されます。

図1:AUDCAD H1におけるデフォルトパラメータでのEA実行結果

チャート上のコメント表示にはプロポーショナルフォントが使用されるため、得られた数値を視覚的に確認するにはやや見づらい状態です。一方で、ターミナルのログでは等幅フォントが使用されるため、こちらの方が結果は確認しやすくなります。次に、複数の銘柄と時間足での結果を確認してみます。

ここでは、以下の入力パラメータでEAを実行します。

その結果は次のようになります。

図2:複数銘柄・複数時間足でのEA実行結果

複数の銘柄および時間足に対する計算は正常に完了し、結果はテーブル形式で出力されました。現時点ではまだ実用的とは言えませんが、簡易的な予備分析としては十分な情報を得ることができます。



結論

平均ローソク足サイズ(ポイント単位)と、同一方向に進むローソク足の連続長に関する情報を表示する、最初のバージョンの補助インフォーマーEAを作成しました。一見すると、これは自動最適化システムの構築や、複数通貨対応EAの実行といった本プロジェクトの主要テーマとはやや間接的な関係に見えるかもしれません。実際、その指摘は正しく、このEAの開発は本連載の枠を超えて継続されることになります。しかしこの過程で、さまざまな実装手法を試し、それらを検証することができました。これらの知見は、将来的にメインプロジェクトへと応用できることを期待しています。

ここまでで、私たちはすでにかなり多くのことを実現しました。より適切なコード構成へと設計を見直したことで、Adwizardライブラリの開発を複数の領域で並行して進めることが可能になっています。すでに今後の改善方向についてもいくつか検討しています。その一つは、最終的なEAを操作するためのビジュアルインターフェースの構築です。今回扱ったプロジェクトは、複雑な実装に踏み込みすぎることなく、さまざまなアプローチを試すための実験場として機能します。その中でそれぞれの長所と短所を整理し、最も適切な方法を選択することで、最終的にはメインプロジェクトへとより明確な方針のもとで開発を進められるようになります。

お読みいただきありがとうございました。またすぐにお会いしましょう。


重要な注意事項

この記事および連載のこれまでのすべての記事で提示された結果は、過去のテストデータのみに基づいており、将来の利益を保証するものではありません。このプロジェクトでの作業は研究的な性質のものであり、公開された結果はすべて、自己責任で使用されるべきです。


アーカイブ内容

#
 名前
バージョン  詳細  最近の変更
  SymbolsInformer   プロジェクト作業フォルダ  
1 SymbolsInformer.mq5 1.00
一方向ローソク足の連続本数に関する情報を表示するEA
第26回
  SymbolsInformer/Include/Adwizard/Base
  他のプロジェクトクラスが継承する基本クラス    
2 Advisor.mqh 1.04 EA基本クラス 第10回
3 Factorable.mqh
1.05
文字列から作成されたオブジェクトの基本クラス
第24回
4 FactorableCreator.mqh
1.00 名前とCFactorable派生クラスの静的コンストラクタを関連付けるクリエイタークラス 第24回
5 Interface.mqh 1.01
さまざまなオブジェクトを視覚化するための基本クラス
第4回
6 Receiver.mqh
1.04  オープンボリュームを市場ポジションに変換するための基本クラス
第12回
7 Strategy.mqh
1.04
取引戦略基本クラス
第10回
  SymbolsInformer/Include/Adwizard/Database
  プロジェクトEAで使用されるすべての種類のデータベースを扱うファイル
 
8 Database.mqh 1.12 データベースを扱うクラス 第25回
9 db.adv.schema.sql 1.00
最終EAのデータベース構造 第22回
10 db.cut.schema.sql
1.00 簡略化された最適化データベースの構造
第22回
11 db.opt.schema.sql
1.05  最適化データベース構造
第22回
12 Storage.mqh   1.01
EAデータベース内の最終EAのキー値ストレージを扱うクラス
第23回
  SymbolsInformer/Include/Adwizard/Experts
  異なるタイプのEAで使用される共通部分のファイル
 
13 Expert.mqh  1.22 最終EAのライブラリファイル(グループパラメータはEAデータベースから取得)
第23回
14 Optimization.mqh  1.04 最適化タスクの起動を管理するEAのライブラリファイル
第23回
15 Stage1.mqh
1.19 単一インスタンス取引戦略最適化EAのライブラリファイル(第1ステージ)
第23回
16 Stage2.mqh 1.04 取引戦略インスタンスのグループを最適化するEAのライブラリファイル(第2ステージ)   第23回
17 Stage3.mqh
1.04 生成された標準化された戦略グループを、指定された名前のEAデータベースに保存するEAのライブラリファイル 第23回
  SymbolsInformer/Include/Adwizard/Optimization
  自動最適化を担当するクラス
 
18 OptimizationJob.mqh 1.00 最適化プロジェクトステージジョブクラス
第25回
19 OptimizationProject.mqh 1.00 最適化プロジェクトクラス 第25回
20 OptimizationStage.mqh 1.00 最適化プロジェクトステージクラス 第25回
21 OptimizationTask.mqh 1.00 最適化タスククラス(作成) 第25回
22 Optimizer.mqh
1.03  プロジェクト自動最適化マネージャーのクラス
第22回
23 OptimizerTask.mqh
1.03
最適化タスククラス(コンベア)
第22回
  SymbolsInformer/Include/Adwizard/Strategies    プロジェクトの動作を示すために使用される取引戦略の例
 
24 HistoryStrategy.mqh 
1.00 取引履歴を再生するための取引戦略のクラス
第16回
25 SimpleVolumesStrategy.mqh
1.11
ティックボリュームを使用した取引戦略のクラス
第22回
  SymbolsInformer/Include/Adwizard/Utils
  補助ユーティリティ、コード削減用マクロ

26 ExpertHistory.mqh 1.00 取引履歴をファイルにエクスポートするクラス 第16回
27 Macros.mqh 1.07 配列操作に便利なマクロ 第26回
28 NewBarEvent.mqh 1.00  特定の銘柄の新しいバーを定義するクラス  第8回
29 SymbolsMonitor.mqh  1.00 取引商品(銘柄)に関する情報を取得するためのクラス 第21回
  SymbolsInformer/Include/Adwizard/Virtual
  仮想の取引注文やポジションのシステムを用いた各種オブジェクト作成用クラス
 
30 Money.mqh 1.01  基本的なお金の管理クラス
第12回
31 TesterHandler.mqh  1.07 最適化イベント処理クラス  第23回
32 VirtualAdvisor.mqh  1.10  仮想ポジション(注文)を扱うEAのクラス 第24回
33 VirtualChartOrder.mqh  1.01  グラフィカル仮想ポジションクラス 第18回
34 VirtualHistoryAdvisor.mqh 1.00  トレード履歴再生EAクラス  第16回
35 VirtualInterface.mqh  1.00  EAGUIクラス  第4回
36 VirtualOrder.mqh 1.09  仮想注文とポジションのクラス  第22回
37 VirtualReceiver.mqh 1.04 オープンボリュームを市場ポジションに変換するクラス(レシーバー)  第23回
38 VirtualRiskManager.mqh  1.05 リスクマネジメントクラス(リスクマネージャー)  第24回
39 VirtualStrategy.mqh 1.09  仮想ポジションを使った取引戦略のクラス  第23回
40 VirtualStrategyGroup.mqh  1.03  取引戦略グループのクラス 第24回
41 VirtualSymbolReceiver.mqh  1.00 銘柄レシーバークラス  第3回

ソースコードは、 SymbolsInformerおよび Adwizard の公開リポジトリでも入手可能です。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/17606

添付されたファイル |
SymbolsInformer.zip (123.2 KB)
最後のコメント | ディスカッションに移動 (3)
Sergey Chalyshev
Sergey Chalyshev | 25 4月 2025 において 13:36
ただただ残念だ。
Rashid Umarov
Rashid Umarov | 25 4月 2025 において 20:37
Sergey Chalyshev #:
残念だ(

明日、あなたの有益なレビューを拝見します

Arup Nag
Arup Nag | 25 4月 2025 において 21:49

ラシッド・ウマロフ

こんにちは。

このスレッドをよく見ていて、すべてを実装して最適化できていますか?

お願いがあるのですが。

よろしくお願いします。

EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
CatBoost AIによるレンコ足の予測 CatBoost AIによるレンコ足の予測
AIを用いてレンコ足をどのように活用するのでしょうか。本記事では、最大59.27%の予測精度を実現したForex向けレンコ足トレーディングを題材に解説していきます。まず、レンコ足がどのように市場ノイズを除去するのか、その利点を見ていきます。さらに、なぜ価格パターンよりも出来高の方が重要なのかを学び、EURUSDに最適なレンコ足ブロックサイズの設定方法についても掘り下げます。また、CatBoost、Python、MetaTrader 5を組み合わせ、自分自身のレンコ足予測システムを構築する手順をステップごとに解説します。従来のテクニカル分析を超えるアプローチを求めるトレーダーに最適な内容です。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
FX裁定取引:関係性評価パネル FX裁定取引:関係性評価パネル
MQL5における裁定取引分析パネルの開発について説明します。さまざまな方法で、Forexで理論為替レートを導き出すにはどうすればよいでしょうか。市場価格と理論為替レートとの乖離を把握し、ある通貨を別の通貨に交換する裁定取引(三角裁定取引など)の収益機会を評価するためのインジケーターを作成します。