English Deutsch
preview
MQL5入門(第8回):初心者のためのEA構築ガイド(II)

MQL5入門(第8回):初心者のためのEA構築ガイド(II)

MetaTrader 5トレーディング | 6 9月 2024, 09:56
35 0
Israel Pelumi Abioye
Israel Pelumi Abioye

はじめに

MQL5の基礎をすでに学んだ方は、次のステップとして、アルゴリズム取引における重要なタスクの1つであるエキスパートアドバイザー(EA)の作成に挑戦する準備が整ったことでしょう。本連載では、前回お伝えしたように、プロジェクトベースのアプローチを採用します。この方法は、抽象的な概念の理解を深めるとともに、それらが実際の取引シナリオでどのように活用されるかを体験するのに役立ちます。このガイドを読み終える頃には、ローソク足のパターンと特定の条件に基づいて売買を自動化する方法をしっかりと習得していることでしょう。 

初心者の多くはフォーラムで質問を投稿し、コミュニティのメンバーから素晴らしいアドバイスを得ています。しかし、独学で学んでいる方の中は、得たアドバイスを自身のプログラムに取り入れるのが難しいと感じることもあります。特定の質問に対して答えは得られますが、コード全体の詳細な説明は難しい場合が多いのです。コードスニペットやヒントが提供されても、それらを統合するのに苦労することがあります。本記事では、プロジェクトベースのアプローチを通じて、こうしたよくある質問に対して包括的な解決策を提示し、どのEAにも応用可能な内容を提供します。

この記事では、前日のローソク足分析を使用して売買方向を決定するEAの開発に焦点を当てます。このEAは、直近の日足ローソク足が弱気であれば売りを優先し、強気であれば買いを優先します。また、その日の最初の1時間足の終値を基に売買シグナルを確認し、1度に複数のポジションを持たないように制限します。1日の取引回数も最大2回までに制限し、厳しい取引制限の下で機能します。さらに取引は月曜日から水曜日までの指定時間内にのみおこないます。

このプロジェクトを通じて、MQL5フォーラムでよく寄せられる初心者の疑問に答えます。これらの質問の中には以下のようなものがあります。

  • MQL5で売買するには?
  • ローソク足の始値と終値を取得するには?
  • ティックごとの取引を避けるには?
  • EAが一度に1つの取引しかおこなわないようにするには?
  • EAの取引期間を設定するには?
  • EAが取引できる曜日を指定するには?
  • 取引の利益限度額と損失限度額を設定するには?

このプロジェクトベースのアプローチは、これらの質問に対する具体的で実践的な解答を提供し、EAに直接ソリューションを実装できるようにサポートします。この実践的な方法は、理論的な概念を理この手法により、理論的な理解だけでなく、実際の取引シナリオへの応用力も養えます。さあ、MQL5の世界に足を踏み入れ、取引ツールの開発を始めましょう。


1. プロジェクトの設定

1.1.疑似コード

コードを書き始める前に、EAのロジックを擬似コードで記述することが重要です。擬似コードの重要性については前回の記事で触れましたが、ここでもそのメリットを強調したいと思います。擬似コードを使うことで、アイデアを整理し、明確な計画を立てやすくなるだけでなく、実際のコーディング作業も効率的に進められます。MQL5を学ぶ際には、一度にすべてを学ぶのではなく、プロジェクトベースのアプローチを取ることが効果的です。プロジェクトに取り組むほどスキルが向上していくでしょう。以下はEAの基本的な擬似コードです。 

1. EAの初期化

  • 取引識別のためのマジックナンバーを設定する
  • 取引の開始時間と終了時間を定める
  • 価格を格納するための変数を初期化する

2.ティックごとに

  • 現在時刻を確認し、取引時間内であることを確認する
  • 前日の始値と終値を取得する
  • 前日の始値と終値を取得する
  • 前日の終値が始値より低い場合(弱気)、売り取引をおこなう
  • 前日の終値が始値より高い場合(強気)、買い取引をおこなう
  • 一度に1つの取引しか開いていないことを確認する
  • 取引は月曜日から木曜日まで
  • 最大1つの未決済ポジションを確保する
  • 1日2回まで
  • 1日の利益制限
  • 取引期間終了時に取引を終了する

1.2. 必要なライブラリのインポート

MQL5のライブラリは、あらかじめ用意されたクラスや関数のセットで、コーディングをより効率的におこなうためのツールです。これらを活用することで、基本的な機能を一から書き直す手間を省き、EAの独自性や戦略に集中することができます。前回の記事で「インクルードファイル」の概念に触れましたが、詳細がすべて理解できていなくても問題ありません。この記事は理解できるでしょう。MQL5をマスターするには、一度にすべてを覚える必要はなく、プロジェクトベースの学習を通じて段階的にスキルを向上させていくことができます。コードスニペットと解説に従えば、効率的にプログラムに取り入れることが可能です。

たとえ

コーディングの作業を、広大で魅力的な本棚にたとえてみてください。この本棚には、さまざまな目標を達成するための知識や物語が詰まった本が並んでいます。 MQL5のライブラリも、まさにその本棚にある希少な書物のような存在です。これらの本に書かれた物語(コード)を活用することで、ゼロからすべてを書くことなく、自分の自動売買ロボットを効率的に構築できます。適切な本を選んでそのガイドに従うことで、作業をスムーズに進められるのです。

EAを開発する際、すべての細部を自分で書く必要はありません。これらの本には、コーディングを簡素化するための多くの指針が含まれています。そのおかげで、複雑な作業に手間取ることなく、EAの独自性や興味深い部分に集中できるようになります。

1.2.1. 取引ライブラリをインクルードする

取引操作を効率的に管理するためには、取引ライブラリをEAに組み込むことが重要です。このライブラリには、取引管理をサポートするための便利なクラスや関数が集められています。以下のコードは、そのライブラリをインクルードするためのものです。

たとえ

広大な本棚を持っていると想像してください。その本棚には、多種多様なツールキットとしての本が詰まっており、それぞれが異なる分野での助けとなります。素晴らしい自動売買ロボットのようにユニークなものを作成するには、この本棚から最適な本を取り出すことが必要です。 まるで取引ライブラリの特別な本を本棚から選び出すようなものです。この「本」には、取引操作をスムーズに進めるために必要なすべてのツールが揃っています。この本にはすでにすべての手順が書かれているので、あなた自身がゼロから作る手間を省いてくれます。

この特別な「本」をプロジェクトに取り込むには、以下のコード行を使用します。

#include <Trade/Trade.mqh> // Include the trade library for trading functions

この一行で、自動売買ロボットは「本棚から『Trade.mqh』という本を取り出せ」と指示を受けます。この本には、取引の開始、変更、決済など、取引に関するあらゆる操作を効率化するための強力なツールが含まれています。これにより、プロセスがスムーズに進み、時間と労力を大幅に節約できるのです。

#include <Trade/Trade.mqh> // Include the trade library for trading functions

// Create an instance of the CTrade class for trading operations
CTrade trade;
//magic number
int MagicNumber = 103432;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
// Set the magic number for the EA's trades
   trade.SetExpertMagicNumber(MagicNumber);

// Return initialization success
   return(INIT_SUCCEEDED);
  }

1.2.2.1. CTradeクラスのインスタンスを作成する

取引ライブラリの一部であるCTradeクラスは、取引操作を実行するためのメソッドをまとめたものです。このクラスのインスタンスを作成することで、そのメソッドを活用して取引の実行や管理が可能になります。

たとえ

もう一度、本棚を想像してください。あなたはすでに、取引に必要なすべての手順が書かれている特別な本(Trade.mqh)を選びました。CTradeクラスは、その本の中に登場するスーパーヒーローのような存在です。このスーパーヒーローは、取引に関するすべての知識を持っています。 しかし、このスーパーヒーローを動かすためには、彼を物語に登場させる必要があります。それがCTradeクラスのインスタンスを作成するということです。スーパーヒーローをプロジェクトに招待するようなものです。

次の簡単なコードでスーパーヒーローを「召喚」できます。

CTrade trade;

このコードでは、CTradeはスーパーヒーローであり、取引はあなたの招待です。このtradeというインスタンスインスタンスを作成することで、あなたは「スーパーヒーローCTrade、私のプロジェクトに参加して、取引管理を手伝ってください」と依頼していることになります。 

これで、取引の発注や管理が必要なときにはいつでも、このスーパーヒーローに頼むことができ、あなたの作業がはるかに簡単になり、取引操作が効率的かつ正確におこなわれるようになります。

1.2.2.2. マジックナンバーの設定

マジックナンバーとは、EAがおこなった各取引に割り当てられる一意の識別子です。この識別子を使うことで、取引の追跡や管理が容易になり、EAが自身の取引を、手動でおこなわれた取引や他のEAによって発注された取引と確実に区別できるようになります。 あなたの本棚にある本のそれぞれが取引だと考えてください。中には似たような本(取引)もあるでしょうが、それぞれの本に固有の番号が書かれた特別なシールが貼られているとします。ここのシールのおかげで、どの本がどれなのか一目で分かり、簡単に管理できるようになります。

EAにおける「マジックナンバー」は、この特別なシールに相当します。マジックナンバーを設定することで、EAは自分が発注した取引を識別でき、他のEAや手動取引と混同することなく、管理がスムーズにおこなえます。このユニークな番号によって、EAが実行する取引が誰によっておこなわれたかを明確に追跡できるのです。

int MagicNumber = 103432;
CTradeクラスのコンテキストでは、マジックナンバーを次のように設定します。
trade.SetExpertMagicNumber(MagicNumber);

このコード行は、EAが発注するすべての取引に指定されたマジックナンバーが付与されることを保証し、取引の識別や管理を容易にします。

MQL5で複雑なタスクを効率的に処理するためには、必要なライブラリをインポートすることが重要です。ライブラリは、時間と労力を節約できるツールが揃ったツールキットのようなもので、これらを活用することで、ゼロからすべてのコードを書く必要がなくなります。その結果、EAの独自の機能や戦略に集中することができ、開発がスムーズに進むのです。


2. ローソク足データの取得と分析

ローソク足の始値と終値を取得し、分析することは不可欠です。この情報は、市場の方向性を理解し、売買の意思決定に大いに役立ちます。ローソク足の動きを把握するために、始値と終値を効率的に取得できるようにすることが重要です。MQL5では、CopyOpenやCopyCloseといった関数を使うことで、ローソク足データを迅速に取得できます。これにより、始値と終値を比較し、市場が強気か弱気かを判断することができます

2.1. MQL5のCopyClose関数とCopyOpen関数

指定した銘柄や時間枠に基づいて、ローソク足の終値や始値を取得するには、MQL5のCopyClose関数とCopyOpen関数が非常に有効です。これらの関数は、柔軟な方法で必要なデータをコピーすることができ、効率的な分析をサポートします。

2.1.1. 最初のポジションと必要要素数による呼び出し

たとえ

本棚を価格データのソースと考え、それぞれの本がローソク足の始値と終値を表すとします。左から本を数えて、特定の位置から本をコピーし始めたい場合、その位置を選ぶことができます。たとえば、3冊目から始めたい場合は、3冊目からコピーを始めます。

次に、その位置から始める特定の冊数を決めます。たとえば、3冊目から5冊の本を取り出すことにすれば、合計で5冊の本が手元に残ることになります。このように、棚から最初の本を選び、そこから何冊の本を取り出すかを決めるのと同じように、CopyClose関数を使って価格データの開始位置とコピーする要素(ローソク足)の数を指定します。

構文

CopyClose(symbol_name, timeframe,  start_pos, count, close_array);
CopyOpen(symbol_name, timeframe,  start_pos, count, open_array);

double close_prices[];
double open_prices[]; 
CopyClose(_Symbol, PERIOD_D1, 2, 5, close_prices); // Copy the close prices of 5 daily candlesticks starting from the 3rd candlestick
CopyOpen(_Symbol, PERIOD_D1, 2, 5, open_prices); // Copy the open prices of 5 daily candlesticks starting from the 3rd candlestick

特定の銘柄と時間枠の価格データは、このアナロジーでは本棚にたとえられ、銘柄名と時間枠で管理されます。これは、symbol_nameとtimeframeによって表されます。スタート位置から5冊を取るという決定(カウント)は、本棚の3冊目の本を選ぶことと同じです。開始位置(start_pos)もその決定と同様です。選んだ本を手に取るように、コピーしたデータをターゲットの配列(close_array)に格納します。


2.1.2. 開始日と必要な要素数による呼び出し

たとえ

本棚を価格データとして考えてみましょう。一冊の本が1日分のローソク足のデータに対応しています。特定の日付から終値のコピーを始めたい場合、その日付に対応する本を棚から探します。たとえば、「6月1日」からデータを取得したい場合は、その日付に対応する本を選びます。

次に、その日から始まる特定の終値データを複製するためには、取得したい冊数を決めます。たとえば、「6月1日」から始めて5冊の本の終値データを取りたい場合、その5冊分の終値を取得することができます。この方法は、本棚から開始する本を選び、そこから何冊取り出すかを決めるのと似ています。CopyClose関数を使えば、価格データの開始日とコピーしたい要素(ローソク足)の数を指定できます。

同様に、CopyOpen関数を使用して始値を取得することもできます。指定された日付に対応する本を選び、始値を得るために取りたい本の数量を指定するだけです。

 構文

CopyClose(symbol_name, timeframe,  timeframe, count, close_array[]);
CopyOpen(symbol_name, timeframe,  timeframe, count, open_array[]);

close_prices[];
double open_prices[];
datetime start_date = D'2023.06.01 00:00';  // Starting from June 1st, 2023
// Copy the close prices of 5 daily candlesticks starting from June 1st, 2023
CopyClose(_Symbol, PERIOD_D1, start_date, 5, close_prices);
// Copy the open prices of 5 daily candlesticks starting from June 1st, 2023 CopyOpen(_Symbol, PERIOD_D1, start_date, 5, open_prices);

特定の銘柄と時間枠の価格データは、このアナロジーでは本棚にたとえられ、銘柄名と時間枠で管理されます。これは、symbol_nameとtimeframeによって表されます。「6月1日」に該当する本棚の本を選ぶことは、開始日(start_time)に似ており、その開始日から5冊の本を選ぶことは、要素数(count)に似ています。選んだ本を手に取るように、コピーしたデータは対象の配列(close_arrayとopen_array)に格納します。

このアナロジーを用いると、CopyClose関数とCopyOpen関数が、開始日に基づいて価格データの本棚から特定のデータを組織的かつ直感的に取り出す手助けをしていることが理解できるでしょう。

2.1.3. 必要な時間間隔の開始日と終了日による呼び出し

たとえ

本棚にあるすべての本が、特定の期間の始値と終値を示すローソク足データだと考えてみましょう。特定の時間枠の終値を取得したい場合は、その期間全体をカバーする本を探すことになります。

たとえば、6月1日から6月5日までの終値を複製したいとします。この場合、6月1日の開始点と6月5日の終了点に対応する本を棚から探し、これらの2つの日付の間にあるすべての本を選ぶことで、その期間の終値を取得できます。

本棚から範囲をカバーするために、始まりと終わりの本を選ぶのと同様に、この方法では開始日と終了日を指定してデータを取得できます。同様に、CopyOpen関数を使えば、指定した期間内の始値を取得することも可能です。

構文
CopyClose( symbol_name, timeframe, start_time, stop_time, close_array[]);
CopyOpen(symbol_name, timeframe, start_time, stop_time, open_array[]);
double close_prices[];
double open_prices[];
datetime start_date = D'2023.06.01 00:00'; // Starting from June 1st, 2023
datetime end_date = D'2023.06.05 00:00'; // Ending on June 5th, 2023
// Copy the close prices from June 1st to June 5th
CopyClose(_Symbol, PERIOD_D1, start_date, end_date, close_prices);
// Copy the open prices from June 1st to June 5th
CopyOpen(_Symbol, PERIOD_D1, start_date, end_date, open_prices);

特定の銘柄と時間枠の価格データは、このアナロジーでは本棚にたとえられ、銘柄名と時間枠で管理されます。これは、symbol_nameとtimeframeによって表されます。開始日(start_time)は本棚から「6月1日」に該当する本を選ぶようなもので、終了日(stop_time)は「6月5日」に該当する本を探すようなものです。選んだ本を手に取るように、コピーしたデータは対象の配列(close_arrayとopen_array)に格納します。

このアナロジーを通じて、CopyClose関数とCopyOpen関数が、開始日と終了日に基づいて価格データの本棚から特定のデータを効率的に検索し、データ取得プロセスを直感的かつ整理されたものにする方法が理解できるでしょう。

文字列を時間に変換する

日中の特定の時間帯に稼働するEAを使用する場合、日付や時間の範囲でデータを取得するのが難しくなることがあります。主な課題は、日付は変わるが時間が変わらない点です。自動化されたEAでは、毎日手動で日付を入力するのは現実的ではありません。

たとえ

日付と時間によって棚が整理された図書館を想像してみてください。特定の時間帯に本を読みたい(またはデータを取り出したい)場合、曜日と時間を意識しなければなりません。棚から適切な本を探し出すために、毎日手動で日付を更新するのは手間がかかります。 このため、時間文字列を利用して特定の時間にラベルを付けることで、効率的にデータを検索することができます。

// Declaring time strings
string start_time_str = "00:00";  // Start time
string end_time_str = "20:00";    // End time

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {

// Converting time strings to datetime values
   datetime start_time = StringToTime(start_time_str);
   datetime end_time = StringToTime(end_time_str);

   Print("start_time: ", start_time,"\nend_time: ",end_time);

  }

説明

// Declaring time strings string start_time_str = "00:00"; // Start time string end_time_str = "20:00"; // End time

  • 時間文字列は、データ検索プロセスを効率化するために、特定の時間に対する「ラベル」として機能します。

// Converting time strings to datetime values datetime start_time = StringToTime(start_time_str); datetime end_time = StringToTime(end_time_str);

  • StringToTime関数を使うと、まるで魔法のしおりを持っているかのように、その日その日にどの棚を指すべきか正確に知ることができます。これは日付に関係なく言えることです。この方法なら、毎日日付を変えることに煩わされることはありません。start_timeとend_timeが一貫して棚の現在時刻を参照することを保証することで、手動で更新することなく、適切な本(データ)を簡単に取り出すことができます。

出力

図1:コード出力


EAでの実施

#include <Trade/Trade.mqh>

// Create an instance of the CTrade class for trading operations
CTrade trade;

// Unique identifier for the EA's trades
int MagicNumber = 103432;

// Arrays to store the previous day's open and close prices
double daily_close[];
double daily_open[];

// Arrays to store the first H1 bar's open and close prices of the day
double first_h1_price_close[];

// Arrays to store H1 bars' open and close prices
double H1_price_close[];
double H1_price_open[];

// Strings to define the trading start and end times and the first trade time
string start = "00:00";
string end = "20:00";
string firsttrade  = "02:00";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Convert time strings to datetime format
   datetime start_time = StringToTime(start); // Convert start time string to datetime
   datetime end_time = StringToTime(end); // Convert end time string to datetime
   datetime current_time = TimeCurrent(); // Get the current time
   datetime first_tradetime = StringToTime(firsttrade); // Convert first trade time string to datetime

// Copy daily close and open prices
   CopyClose(_Symbol, PERIOD_D1, 1, 1, daily_close); // Copy the close price of the previous day
   CopyOpen(_Symbol, PERIOD_D1, 1, 1, daily_open); // Copy the open price of the previous day


// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(daily_close, true); // Set daily_close array as series
   ArraySetAsSeries(daily_open, true); // Set daily_open array as series

// Copy close and open prices for the first H1 bar of the day
   CopyClose(_Symbol, PERIOD_H1, start_time, 1, first_h1_price_close); // Copy the close price of the first H1 bar

// Copy close prices for the latest 5 H1 bars
   CopyClose(_Symbol, PERIOD_H1, 0, 5, H1_price_close); // Copy the close prices of the latest 5 H1 bars
   CopyOpen(_Symbol, PERIOD_H1, 0, 5, H1_price_open); // Copy the open prices of the latest 5 H1 bars

// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(H1_price_close, true); // Set H1_price_close array as series
   ArraySetAsSeries(H1_price_open, true); // Set H1_price_open array as series

// If the last daily bar is bearish
   if(daily_close[0] < daily_open[0])
     {
      // Check specific conditions for a sell trade
      if(H1_price_close[2] >= first_h1_price_close[0] && H1_price_close[1] < first_h1_price_close[0] && current_time >= first_tradetime)
        {

         Comment("Its a sell");

        }

     }

// If the last daily bar is bullish
   if(daily_close[0] > daily_open[0])
     {
      // Check specific conditions for a buy trade
      if(H1_price_close[2] <= first_h1_price_close[0] && H1_price_close[1] > first_h1_price_close[0] && current_time >= first_tradetime)
        {
         Comment("Its a buy");
        }
     }

  }

説明

まず、必要なTradeライブラリを「#include <Trade/Trade.mqh>」でインポートし、取引関連の関数を使えるようにします。次に、CTradeクラスのインスタンスを「CTrade trade;」で作成します。また、EAの取引に一意な識別子を「int MagicNumber = 103432;」で定義します。

その後、前日の始値と終値、当日の最初のH1バーの始値と終値、H1バーの始値と終値を格納する配列を宣言します。これらの配列には検索された価格データが格納され、それを分析して売買の意思決定をおこないます。 取引開始時刻と終了時刻、最初の取引時刻は文字列として定義し、後でdatetime形式に変換します。OnInit関数では、取引を識別するためのEAマジックナンバーを設定します。OnTick関数では、時間文字列をdatetime形式に変換し、現在時刻を取得します。その後、CopyClose関数とCopyOpen関数を使用して、日足とH1のローソク足データをそれぞれの配列にコピーします。ArraySetAsSeries関数は、配列が右から左にコピーされるように設定し、最新のデータがインデックス0になるようにします。

最後に、取得したローソク足データを分析して市場の感情を判断します。直近の日足が弱気であれば、特定の条件を確認して売り取引を決定します。逆に、直近の日足が強気であれば、買い取引の条件を確認します。Comment関数を使って、チャート上に売買判断を表示します。 このコード例では、日足と1時間足のローソク足の始値と終値を取得し分析し、その結果に基づいて取引の方向性を決定する仕組みを設定しています。


3. 取引実行の実装

3.1 MQL5での売買方法

このセクションでは、ストップロスとテイクプロフィットのレベルの決定を含め、MQL5で買い注文と売り注文を実行するために必要な基本的な手順について説明します。取引を実行するための基本的なメソッドを提供するTrade.mqhライブラリのCTradeクラスを利用します。

売買注文を出す

売り注文と買い注文を出すにはCTradeクラスのSellメソッドとBuyメソッとを使います。これにより、少ないコードで効率的に取引を実行できます。CTradeクラスは、取引機能に特化した図書館の棚のようなものです。必要な本(手法)を棚から取り出し、取引を実行する際にその本を適用するだけで済みます。

ストップロスとテイクプロフィットの設定

TPとSLの水準はリスク管理にとって非常に重要です。TPは期待する利益の目標を設定し、SLは許容できる最大損失を設定します。これらはいずれも、現在の市場価格に対する具体的な価格ポイントを示しています。SLとTPは、取引が予期しない方向に進んだ場合にどこで損失を限定するか(SL)、または成功した場合にどこで利益を確定するか(TP)を示す「本のしおり」のような役割を果たします。


#include <Trade/Trade.mqh> // Include the trade library for trading functions

// Create an instance of the CTrade class for trading operations
CTrade trade;

// Unique identifier for the EA's trades
int MagicNumber = 103432;

// Arrays to store the previous day's open and close prices
double daily_close[];
double daily_open[];

// Arrays to store the first H1 bar's open and close prices of the day
double first_h1_price_close[];

// Arrays to store H1 bars' open and close prices
double H1_price_close[];
double H1_price_open[];

// Strings to define the trading start and end times and the first trade time
string start = "00:00";
string end = "20:00";
string firsttrade  = "02:00";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Convert time strings to datetime format
   datetime start_time = StringToTime(start); // Convert start time string to datetime
   datetime end_time = StringToTime(end); // Convert end time string to datetime
   datetime current_time = TimeCurrent(); // Get the current time
   datetime first_tradetime = StringToTime(firsttrade); // Convert first trade time string to datetime

// Copy daily close and open prices
   CopyClose(_Symbol, PERIOD_D1, 1, 1, daily_close); // Copy the close price of the previous day
   CopyOpen(_Symbol, PERIOD_D1, 1, 1, daily_open); // Copy the open price of the previous day

// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(daily_close, true); // Set daily_close array as series
   ArraySetAsSeries(daily_open, true); // Set daily_open array as series

// Copy close and open prices for the first H1 bar of the day
   CopyClose(_Symbol, PERIOD_H1, start_time, 1, first_h1_price_close); // Copy the close price of the first H1 bar

// Copy close prices for the latest 5 H1 bars
   CopyClose(_Symbol, PERIOD_H1, 0, 5, H1_price_close); // Copy the close prices of the latest 5 H1 bars
   CopyOpen(_Symbol, PERIOD_H1, 0, 5, H1_price_open); // Copy the open prices of the latest 5 H1 bars

// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(H1_price_close, true); // Set H1_price_close array as series
   ArraySetAsSeries(H1_price_open, true); // Set H1_price_open array as series

// Get the symbol point size
   double symbol_point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

// Get the current Bid price
   double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

// Calculate the stop loss and take profit prices for sell
   double tp_sell = Bid - 400 * symbol_point;
   double sl_sell = Bid + 100 * symbol_point;

// Get the current Ask price
   double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

// Calculate the stop loss and take profit prices for buy
   double tp_buy = Ask + 400 * symbol_point;
   double sl_buy = Ask - 100 * symbol_point;

// If the last daily bar is bearish
   if(daily_close[0] < daily_open[0])
     {
      // Check specific conditions for a sell trade
      if(H1_price_close[2] >= first_h1_price_close[0] && H1_price_close[1] < first_h1_price_close[0] && current_time >= first_tradetime)
        {
         // Execute the sell trade
         trade.Sell(1.0, _Symbol, Bid, sl_sell, tp_sell); // Replace with your lot size
         Comment("It's a sell");
        }
     }

// If the last daily bar is bullish
   if(daily_close[0] > daily_open[0])
     {
      // Check specific conditions for a buy trade
      if(H1_price_close[2] <= first_h1_price_close[0] && H1_price_close[1] > first_h1_price_close[0] && current_time >= first_tradetime)
        {
         // Execute the buy trade
         trade.Buy(1.0, _Symbol, Ask, sl_buy, tp_buy); // Replace with your lot size
         Comment("It's a buy");
        }
     }
  }

説明

  • includeを使用して、取引ライブラリをインクルードします。
  • CTradeクラスの取引インスタンスを作成します。
  • 現在のBidとAskおよび銘柄のポイントサイズに基づいて、SLとTPのレベルを決定します。
  • BuyメソッドとSellメソッドを使い、指定したSLとTPのレベルで注文を出します。

trade.Buyとtrade.Sellのパラメータ

  • lotsize:取引量
  • _Symbol:取引が執行される銘柄(例:EURUSD)
  • Ask/Bid:買い取引の場合は現在の売値、売り取引の場合は買値
  • sl:ストップロス価格水準
  • tp:テイクプロフィット価格水準

この記事では、始値や終値といった特定のローソク足データを取得し、それに基づいて売買注文を発注する方法について解説しました。これは、取引をプログラムに組み込むための基本を理解する上で初心者にとって非常に重要なステップです。しかし、注意が必要なのは、OnTickイベントハンドラを使用している場合、注文がティックごとに繰り返し送信される可能性があることです。

EAでは、リスクを適切に管理し、過剰取引を防ぐために、同時に複数の取引が実行されないよう制限を設けることが重要です。そのためには、新たな取引を開始する前に、既存の未決済ポジションがあるかどうかを確認するロジックを実装する必要があります。この戦略により、必要なときにのみ取引が実行され、過剰取引を回避できるようになります。


すべてのティックで取引を防ぐ

OnTickイベントハンドラは、ティックごとの取引から生じる可能性があります。新しいバーや未決済ポジションを確認することで、必要なときだけ取引がおこなわれるようにしています。新しいバーはすべて、私たちのコレクションに加わった本だと考えてください。現在、他の本(取引)が読まれていないことを確認し、新しい本(バー)が追加されたときだけ「読む」(取引)を選択します。

// Flag to indicate a new bar has formed
bool newBar;

// Variable to store the time of the last bar
datetime lastBarTime;

// Array to store bar data (OHLC)
MqlRates bar[];

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

// Check for a new bar
   CopyRates(_Symbol, PERIOD_H1, 0, 3, bar); // Copy the latest 3 H1 bars
   if(bar[0].time > lastBarTime)  // Check if the latest bar time is greater than the last recorded bar time
     {
      newBar = true; // Set the newBar flag to true
      lastBarTime = bar[0].time; // Update the last bar time
     }
   else
     {
      newBar = false; // Set the newBar flag to false
     }

// If a new bar has formed
   if(newBar == true)
     {
      // If the last daily bar is bearish
      if(daily_close[0] < daily_open[0])
        {
         // Check specific conditions for a sell trade
         if(H1_price_close[2] >= first_h1_price_close[0] && H1_price_close[1] < first_h1_price_close[0] && current_time >= first_tradetime)
           {
            // Execute the sell trade
            trade.Sell(1.0, _Symbol, Bid, sl_sell, tp_sell); // Replace with your lot size
            Comment("It's a sell");
           }
        }

      // If the last daily bar is bullish
      if(daily_close[0] > daily_open[0])
        {
         // Check specific conditions for a buy trade
         if(H1_price_close[2] <= first_h1_price_close[0] && H1_price_close[1] > first_h1_price_close[0] && current_time >= first_tradetime)
           {
            // Execute the buy trade
            trade.Buy(1.0, _Symbol, Ask, sl_buy, tp_buy); // Replace with your lot size
            Comment("It's a buy");
           }
        }

     }

  }

説明

bool newBar;

  • 新しいバー(ローソク足)が形成されたかどうかを示すために、この行ではnewBarというブーリアン変数を宣言しています。

datetime lastBarTime;

  • 直近に処理されたバーの時刻を格納するために、この行ではlastBarTimeというdatetime型の変数を宣言しています。

MqlRates bar[];

  • この行では、MqlRates型の配列barを宣言しています。この配列は、始値、高値、安値、終値(OHLC)などのバーデータを格納するために使用されます。

CopyRates(_Symbol, PERIOD_H1, 0, 3, bar); // 直近3本のH1バーをコピーする

  • 現在の銘柄(_Symbol)の直近の3つの時間足(H1)バーが、この行のCopyRates関数を使用してbar配列にコピーされます。

if(bar[0].time > lastBarTime)

  • この行は、直近のバーの時間(bar[0].time)が直前のバーの時間を超えているかどうかを判定します。それが正確だと仮定すれば、新しいバーが出現したことになります。

newBar = true;

  • 新しいバーが形成された場合、newBarフラグがtrueに設定されます。

lastBarTime = bar[0].time;

  • 直近のバーの時刻はlastBarTime変数に更新されます。

else {

newBar = false;

        }

  • 最新のバーの時間がlastBarTimeよりも大きくない場合、newBarフラグはfalseに設定されます。

if(newBar == true)

{

 // こうする

}

  • 新しいバーが形成されたら、こうします。

このコードでは、if文の中のコードが、ティックごとではなく、新しいバーが形成されたときだけ実行されるようにしています。これは、直近のバーの時間が、最後に記録されたバーの時間より長いかどうかを判断することによっておこなわれます。これに基づいて、新しいバーがいつ形成されたかを判断し、newBarフラグを適切に設定することができます。これにより、「if(newBar==true)」ブロック内のコードが各ティックで繰り返し実行されることがなくなり、EAのパフォーマンスが向上し、無駄なアクションがなくなります。

3.2 EAを一度に1つのポジションに制限する

現在、取引の執行は新しいバーが形成されたときにのみ開始されるため、未決済ポジションの数量を制限することで、EAをさらに改善することができます。こうすることで、EAは他の取引が保留中でない場合にのみ新しい取引を開始します。

これが論理です。

  • ポジションを確認する:新規取引をおこなう前に、未決済ポジションがあるかどうかを確認します。未決済のポジションがない場合、EAは取引をおこないます。
  • すべてのティックで取引を停止する:新しいバーと未決済ポジションの確認を追加することで、絶対に必要なときだけ取引がおこなわれるようにしています。

新しいバーを、私たちのライブラリに追加された1冊の本と考えてみてください。新しい本(バー)が追加された際には、他の本(取引)がまだ読まれていないことを確認してから、初めてその本を「読む」(取引を行う)という判断を下します。

// Initialize the total number of positions being held
int totalPositions = 0;
for(int i = 0; i < PositionsTotal(); i++)
  {
// Get the ticket number for the position
   ulong ticket = PositionGetTicket(i);

// Check if the position's magic number matches
   if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)
     {
      // Increment the total positions count
      totalPositions++;
     }
  }

説明

int totalPositions = 0;

  • この行では、整数変数totalPositionsが宣言され、ゼロに設定されています。EAが現在保有している未決済ポジションの数は、この変数を使用してカウントされます。

for(int i = 0; i < PositionTotal(); i++)

  • すべての未決済ポジションは、このforループの中で繰り返し処理されます。取引端末の未決済ポジションの総数は、PositionsTotal()関数で返されます。
  • i:0から始まり、毎回1ずつ増加する反復ループカウンタ。iがPositionsTotal()より小さい限り、ループは続きます。

  ulong ticket = PositionGetTicket(i);

  • インデックスiのポジションのチケット番号がこの行で検索されます。PositionGetTicket(i)関数により、指定されたインデックスのポジションのチケット番号が返されます。
  • ticket:ulong (unsigned long)型の変数で、現在検査中のポジションのチケット番号を格納します。

if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)

  • このif文は、EAに割り当てられたマジックナンバー(MagicNumber)と現在のポジションのマジックナンバーが一致するかどうかを判定します。
  • PositionGetInteger(POSITION_MAGIC):指定されたプロパティ(この場合はマジックナンバー)の現在の位置の整数値を取得する関数
  • Magic Number:このEA独自の取引識別子となる、あらかじめ決められた定数。EAが建てたポジションのみがカウントされることを保証します。

totalPositions++;

  • この行は、現在のポジションのマジックナンバーがMagicNumberに一致する場合、totalPositionsカウンタを1増やします。要するに、これはこの特定のEAが建てたポジション数のカウントです。

  このコードのブロックは、EAが現在保持しているポジションの数をカウントするために使用されます。それは次のようなものです。

  • カウンタ(totalPositions)をゼロに初期化します。
  • forループを使用してすべての未決済ポジションをループします。
  • 各ポジションについて、チケット番号を取得します。
  • ポジションのマジックナンバーがEAのマジックナンバーと一致するか確認します。
  • 一致すれば、totalPositionsカウンタをインクリメントします。

3.3 EAを1日に最大2回までしか取引できないようにする

EAが新しいバーごとに特定の条件に基づいて取引を実行するようになったので、過剰取引を防ぐために、EAが1日に最大2回しか取引を開かないようにします。

実装の詳細

このセクションでは、HistorySelect関数を使用して、現在の日の取引履歴をフィルタリングします。次に、EAの明確なマジックナンバーを使用して、選択した取引履歴をループし、アルゴリズムがおこなった取引回数をカウントします。各取引の内容を確認し、エントリ取引のみをカウントします。合計2つのエントリ取引がある場合、EAはその日の残りの間、新しい取引を開きません。

本棚を管理するのと同じようなもので、一冊一冊が取引であると考えてください。1日に新しく並べられる本は2冊だけです。その2冊を本棚に収めた後は、翌日まで新しい本を追加しません。こうすることで、本棚が整理され、扱いやすい状態を保てます。過剰に本を詰め込んで乱雑になること(過剰取引)を防ぎ、秩序立った管理(リスク管理)ができるのです。

// Select the trading history within the specified time range
bool success = HistorySelect(start_time, end_time); // Select the trading history

// Initialize the total number of trades for the day
int totalDeal = 0;
if(success)
  {
   for(int i = 0; i < HistoryDealsTotal(); i++)
     {
      // Get the ticket number for the deal
      ulong ticket = HistoryDealGetTicket(i);

      // Check if the deal's magic number matches
      if(HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber)
        {
         // Check if the deal was an entry
         if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
           {
            // Increment the total deal count
            totalDeal++;
           }
        }
     }
  }

説明

  • HistorySelect:その日の取引履歴をフィルタします。
  • totalDeal:取引量を記録するカウンタ
  • 歴史をループする:取引をカウントするには、取引履歴を繰り返し表示します。
  • マジックナンバーを確認する:EAが取引を所有していることを確認します。
  • エントリ取引はカウンタを増やすことでカウントされます。

このコードは、EAが1日に2回までしか取引しないことを保証し、規律ある取引アプローチを維持し、過剰取引のリスクを低減します。

3.4 その日の損益を制限する

このセクションでは、EAが1日の合計損益を超えないようにします。

// Initialize the total profit
double totalProfit = 0;
long dealsMagic = 0;
double profit = 0;
if(success)
  {
   for(int i = 0; i < HistoryDealsTotal(); i++)
     {
      // Get the ticket number for the deal
      ulong ticket = HistoryDealGetTicket(i);

      // Check if the deal was an entry
      if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
        {
         // Get the magic number of the deal
         dealsMagic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
        }

      // Check if the deal was an exit
      if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT)
        {
         // Get the profit of the deal
         profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);

         // Check if the magic number matches
         if(MagicNumber == dealsMagic)
           {
            // Add the profit to the total profit
            totalProfit += profit;
           }
        }
     }
  }

説明

  • HistorySelect:その日の取引履歴をフィルタします。
  • totalProfit:その日の合計損益を追跡するカウンタ
  • 取引履歴をループで繰り返し、損益を合計します。
  • エントリ取引を確認する:すべてのトランザクションのマジックナンバーを確認します。
  • 決済された各取引の利益を計算することにより、出口取引を検証します。
  • 総利益に加える:マジックナンバーが一致した取引の損益を累積します。

あらかじめ決められた利益または損失の閾値に達した後、追加取引を停止することで、この戦略はリスクを軽減します。管理された取引が保証され、リスクを高める過度な取引を避けるのに役立ちます。

3.5 指定された時点ですべてのポジションをクローズする

このセクションでは、MQL5フレームワークを使用して指定された時点ですべてのポジションをクローズする方法を解説します。この機能を実装することで、夜間やオフピークの取引時間帯にポジションが放置されるリスクを減らし、予期せぬ市場の変動に対処しやすくなります。 図書館の閉館時間が訪れた場面を想像してください。司書が一日の終わりに、すべての本(取引)が元の棚に戻っているかを確認するように、私たちも指定された終了時刻にすべてのポジションを確実にクローズし、管理を徹底します。

// Close trades at the specified end time
for(int i = 0; i < PositionsTotal(); i++)
  {
// Get the ticket number for the position
   ulong ticket = PositionGetTicket(i);

// Check if the position's magic number matches and if it's the end time
   if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && current_time == end_time)
     {
      // Close the position
      trade.PositionClose(ticket);
     }
  }

説明

この部分では、未決済のポジションに対して各ポジションのチケット番号を繰り返し検索します。その後、ポジションのマジックナンバーがEAのマジックナンバーと一致し、かつ現在の時刻が指定された終了時刻内であることを確認します。これらの条件が満たされた場合、CTradeクラスのPositionClose関数を使用してポジションをクローズします。

3.6 EAが取引できる曜日を指定する

EAが指定された曜日にのみ取引をおこなうように制御するためには、現在の曜日を取得し、それをあらかじめ定めた取引ルールと照らし合わせて確認する必要があります。

//getting the day of week and month
MqlDateTime day; //Declare an MqlDateTime structure to hold the current time and date
TimeCurrent(day); // Get the current time and fill the MqlDateTime structure
int week_day = day.day_of_week; //Extract the day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday)

//getting the current month
MqlDateTime month; //Declare a structure to hold current month information
TimeCurrent(month); //Get the current date and time
int year_month = month.mon; //Extract the month component (1 for January, 2 for February, ..., 12 for December)

if(week_day == 5)
  {
   Comment("No trades on fridays", "\nday of week: ",week_day);
  }
else
   if(week_day == 4)
     {
      Comment("No trades on Thursdays", "\nday of week: ",week_day);
     }
   else
     {
      Comment(week_day);
     }

説明

EAの取引を特定の曜日に制限するためには、現在の曜日を確認し、あらかじめ設定された取引ルールに照らして判断する必要があります。最初に、現在の日付と時刻を保存するため、MqlDateTime構造体を宣言します。次に、TimeCurrent()関数を使って、この構造体に現在の月と曜日を取得・設定します。

次に、条件文を使用して曜日を確認します。このコードでは、現在の日が金曜日(値5で表示)である場合、金曜日は取引が許可されていないことを示すメッセージが表示されます。同様に、木曜日(値4)は、木曜日は取引が許可されていないことを示します。このコードでは、現在の曜日を表示するだけで、それ以外の日は取引が許可されていることを示しています。

たとえ

曜日ごとに、図書館にあるさまざまな種類の本をイメージしてみましょう。

  • 金曜日(5):金曜日に推理小説を貸し出すことを禁じているように、コードは金曜日の取引を禁じている
  • 木曜日(4):木曜日にSF小説を貸し出すことを禁じているように、コードは木曜日の取引を禁じている
  • その他の日:このコードでは、書籍のジャンルと同じように、他のすべてのジャンル(日)の取引が可能


結論

この記事では、MQL5を使ったアルゴリズム取引の基本を、初心者でも分かりやすいように、プロジェクトベースの学習方法で解説しました。MQL5での売買の実行、ローソク足の始値と終値の取得、ティックごとの売買を防ぐための戦略など、基本的な操作を学びました。また、EAを一度に1つの取引に制限したり、取引日や取引時間を設定したり、利益や損失の限度を管理するなど、自動売買の重要な側面をコントロールする方法についても触れました。

この記事で紹介した内容について、今後の学習の過程で遠慮なく質問してください。特定のコードの実装についての理解を深めたり、これらの概念がさまざまな取引戦略にどのように適用されるかを明確にするために、常に御手伝いします。MQL5を活用したアルゴリズム取引の理解をさらに深める方法について、一緒に考えていきましょう。 最後に、MQL5に限らず、どのプログラミング言語もアルゴリズム取引の道を切り開く鍵となります。すべてを一度に理解するのは難しいかもしれませんが、実際のプロジェクトに取り組みながら、徐々に知識とスキルを磨いていくことが大切です。それぞれのプロジェクトが、スキルと自信を高めるためのステップとなるでしょう。


MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/15299

添付されたファイル |
MQL5Project2.mq5 (10.02 KB)
MQL5でのもみ合いレンジブレイクアウト戦略に基づくエキスパートアドバイザー(EA)の開発 MQL5でのもみ合いレンジブレイクアウト戦略に基づくエキスパートアドバイザー(EA)の開発
この記事では、もみ合い期間後の価格ブレイクアウトを活用したエキスパートアドバイザー(EA)の作成手順を説明します。トレーダーは、もみ合いレンジを特定し、ブレイクアウトレベルを設定することで、この戦略に基づいて取引判断を自動化できます。EAは、誤ったブレイクアウトを回避しつつ、明確なエントリポイントとエグジットポイントを提供することを目的としています。
初心者のためのMQL5におけるファンダメンタル分析とテクニカル分析戦略の組み合わせ 初心者のためのMQL5におけるファンダメンタル分析とテクニカル分析戦略の組み合わせ
この記事では、トレンドフォローとファンダメンタル分析の原則を1つのエキスパートアドバイザー(EA)にシームレスに統合し、より強固な取引戦略を構築する方法について説明します。MQL5を活用して、誰でも簡単にカスタマイズされた取引アルゴリズムを作成できることを紹介します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
PythonとMQL5によるポートフォリオ最適化 PythonとMQL5によるポートフォリオ最適化
この記事では、MetaTrader 5を使ったPythonとMQL5による高度なポートフォリオ最適化技術を紹介します。データ分析、資産配分、売買シグナル生成のためのアルゴリズム開発方法を示し、現代の金融管理やリスク軽減におけるデータ主導の意思決定の重要性を強調します。