English Deutsch
preview
MQL5入門(第13回):初心者のためのカスタムインジケーター作成ガイド(II)

MQL5入門(第13回):初心者のためのカスタムインジケーター作成ガイド(II)

MetaTrader 5エキスパートアドバイザー |
115 2
Israel Pelumi Abioye
Israel Pelumi Abioye

はじめに

MQL5の連載にようこそ。本稿第12回では、MQL5におけるカスタムインジケーター作成の基本について学びました。組み込み関数に頼らず、移動平均インジケーターを一から手動で実装しました。その後、この知識を活かして、ローソク足形式の移動平均インジケーターへと拡張し、インジケーター内でグラフィカル要素を操作する方法を紹介しました。

今回の記事では、これまでの基礎を活かし、より興味深いインジケーター開発の概念に取り組みます。今回もプロジェクトベースのアプローチを用いて、実践を通じて理解を深めていきます。主な目標は、平均足インジケーターの作成と、そのデータを使用した移動平均の計算です。これらのインジケーターが完成したら、それらを組み込んだエキスパートアドバイザー(EA)を開発します。この記事は初心者にも分かりやすく書かれているため、MQL5が初めての方でも安心して読み進めることができます。コードの各行に対して、「どのように動作するのか」だけでなく、「なぜそのステップが必要なのか」という理由まで丁寧に解説していきます。

この記事で紹介する戦略は教育目的のみに限定されており、成功を保証する売買戦略や投資アドバイスではありません。実際の取引に使用する前に、必ずリスクのない環境でテストをおこなってください。

平均足(HA)とHA移動平均線

図1:平均足とMAインジケーター

この記事では、次の内容を学びます。

  • MQL5でカスタムの平均足インジケーターを一から作成する方法
  • 平均足のデータを活用して、平均足移動平均を計算する方法
  • iCustom()関数を使用して、組み込みではないカスタムインジケーターにアクセスし、そのデータを売買戦略に組み込む方法
  • 平均足と移動平均のクロスを利用してエントリー条件を定義する方法
  • 平均足ベースの計算を用いて、ストップロスおよびテイクプロフィットを動的に設定し、効果的にリスクを管理する方法
  • トレンドの進行に応じて利益を確保するために、平均足のパターンを利用したトレーリングストップ機能を適用する方法

1. 平均足インジケーター

1.1. 平均足インジケーターを理解する

平均足(HA)インジケーターは、トレンドを視覚的に捉えやすくするための手法です。通常のローソク足チャートが、各時間足ごとの正確な始値、高値、安値、終値を表示するのに対して、平均足は過去の価格データを平均化して新しい値を算出するという独自の計算方法を用います。これにより、マーケットのノイズを除去し、重要な動きに集中できる、より明瞭で理解しやすいチャートを描き出します。

通常のローソク足チャートでは、1本のローソク足が特定の時間における価格の動きを示します。緑色(陽線)のローソク足は、終値が始値より高かったことを意味し、赤色(陰線)のローソク足はその逆であることを示します。また、ローソク足の上下に伸びる髭は、その時間帯に到達した最高値および最安値を示し、市場のボラティリティを可視化します。

一方、平均足はこの動きとは異なり、価格変動をそのまま表示するのではなく、トレンドをスムーズに描写するための特別な計算をおこないます。たとえば、長い陽線(緑)でヒゲが少ない場合は、上昇トレンドが力強く続いていることを示します。同様に、下落トレンド時には赤いローソク足が明確に現れ、下降の勢いを視覚的に強調します。上下に髭がある小さなローソク足は、相場がレンジ状態にあるか、方向感が乏しく、トレーダーが迷っている状態を表します。

平均足インジケーターの最大の特徴は、伝統的なローソク足の計算方法を修正し、「平均化」によって価格の動きを滑らかに描く点にあります。市場の始値・高値・安値・終値を直接プロットするのではなく、新たに算出された値を用いることで、細かい価格変動による「ノイズ」を取り除き、トレーダーがより明確にトレンドを見極め、より良い判断ができるようサポートします。

平均足の終値 

平均足の終値は、現在の期間の始値・高値・安値・終値の平均値によって計算されます。標準的なローソク足が終値のみを使用して価格の変動を示すのに対し、平均足はこれら4つの値を平均することで、よりバランスの取れた価格の動きの見方を提供します。

計算式は次のとおりです。

図2:HA_Close式

平均足の終値は、これら4つの値を平均することで価格の変動を滑らかにし、視覚的にパターンをより明確にします。

平均足の始値

平均足の始値は、実際の市場の始値ではなく、前の平均足のデータを基に算出されます。具体的には、前の平均足の始値と終値の平均を使って計算されます。

図3:HA_Open式

平均足は、新しいローソク足の始値を前のローソク足に連結させることで、価格の動きに連続性を持たせ、従来のローソク足チャートで頻繁に発生する予測困難なギャップを減少させます。

平均足の高値

平均足の高値は、その期間中に達した最も高い値ですが、単に実際の高値を使うのではなく、現在の期間の高値、平均足の始値、平均足の終値の3つの値を考慮します。これらのうち、最も高い値が採用されます。

図4:HA_High式

平均足の安値

同様に、平均足の安値は、平均足の終値、平均足の始値、実際の安値の中から最も低い値を選ぶことで算出されます。

図5:HA_Lowの式

このアプローチは、平滑化手法との整合性を保ちながら、平均足の安値が価格変動の最も低いポイントを捉えることを保証します。平均足はこれらの計算方法を用いることで、小さな価格の振れを除去し、市場の方向性をより正確に表現します。次のセクションでは、この理論を基に、MQL5で独自の平均足インジケーターを構築していきます。

1.2. 平均足を使用するメリット

小さな価格変動を取り除くことができるため、平均足インジケーターはトレーダーに人気があり、トレンドの特定が非常に簡単になります。従来のローソク足チャートは色の変化が頻繁で、相場が上昇しているのか下降しているのか判断しづらく、トレーダーを迷わせることがよくあります。平均足は、価格データの平均を用いてチャートを滑らかにすることで、この問題に対処し、細部にとらわれることなく全体像を把握しやすくします。

赤いローソク足が連続していれば下降トレンドを示し、緑のローソク足が続けば、通常は力強い上昇トレンドを示します。この明確さにより、一時的な下落と実際の長期的な市場の動きを見分けやすくなります。  平均足は、短期的な価格変動によって引き起こされる不要な売買を回避し、騙しを減らすのに役立ちます。過去のデータを用いることで市場のノイズを除去し、より信頼性の高いトレンドの確認が可能になります。多くのトレーダーは、RSIや移動平均などのツールと組み合わせて戦略を強化しています。価格の動きをより明確に視覚化できるため、平均足はエントリーやエグジットの判断を容易にしてくれます。

1.3. MQL5での平均足の実装

平均足インジケーターの仕組みを理解した後は、それをMQL5で実装するステップへ進みます。MetaTrader 5には平均足インジケーターが標準搭載されていないため、ここでは一から自分で作成します。そのためには、平均足の数式をコードに落とし込み、価格データに適用し、チャート上に表示させる必要があります。

通常どおり、開発はロジックを整理した疑似コードの作成から始めます。これにより、各ステップを理解し、プログラムの構造を適切に整えることができます。

擬似コード

// インジケーターをセットアップする

  • インジケーターを別ウィンドウに表示するように設定する
  • 平均足の描画設定をおこなう
  • 平均足の値(Open、High、Low、Close)を格納するためのバッファを4つ定義する
// バッファを定義する

計算された値を格納するためのバッファを作成する。

  • 平均足の始値
  • 平均足の高値
  • 平均足の安値
  • 平均足の終値

平均足の値を計算する 

過去の価格データをループし、以下の数式に基づいて平均足の値を計算する。

  • HA終値= (始値 + 高値 + 安値 + 終値) / 4
  • HA始値= (前回のHA始値 + 前回のHA終値) / 2
  • HA高値=(高値、HA始値、HA終値)の最大値
  • HA安値=(安値、HA 始値、HA 終値)の最小値

6.平均足の作成とカスタマイズ

疑似コードを作成した後は、プログラミングをさらに進めていく必要があります。前回の記事でも説明したとおり、インジケーターを設計・調整する最初のステップは、チャート上でどのように表示されるべきかをイメージすることです。インジケーターの描画方法、平均足の表示、そして上昇・下降トレンドを示す色などの追加要素を、コードを書く前にあらかじめ決めておく必要があります。

平均足はローソク足の見た目を変更するため、カスタムインジケーターが通常のローソク足チャートを自分自身の計算値で正しく置き換えるようにしなければなりません。これには、始値・高値・安値・終値用のバッファを設定し、上昇トレンドと下降トレンドに応じて色が動的に変化するようにすることが含まれます。こうした視覚的な構成が明確になったら、インジケーターの構造を定義し、コードの記述を開始できます。まずは、平均足インジケーターのプロパティ設定を定義しなければなりません。これらの設定は、描画される要素(平均足など)のスタイル、使用するバッファの数、そしてインジケーターがチャート上でどのように表示されるかを決定します。

// PROPERTY SETTINGS
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1

// PLOT SETTINGS FOR HEIKIN ASHI CANDLES
#property indicator_label1  "Heikin Ashi"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrGreen, clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

解説

プロパティ設定
#property indicator_separate_window

これはMetaTrader 5に対して、「インジケーターをメインチャートの上に重ねて表示するのではなく、別ウィンドウに表示するべきである」と指示します。このインジケーターを価格チャート上に直接表示したい場合は、この行は削除することになります。

比喩的な説明

取引チャートを、市場の動きを観察する作業机だと考えてみましょう。価格のローソク足や一般的なインジケーターといった基本的なツールは、机の上(メインチャート)に置かれています。さて、何か細かく集中して進めたい特別な作業をする場合、机の隣にある小さなホワイトボードに移すとしましょう。ホワイトボードに移せば、メインの作業スペースを煩雑にせずに、別個に集中して取り組むことができます。

同様に、#property indicator_separate_windowは、平均足インジケーターをメインの価格チャートとは別のウィンドウに表示させることで、通常のローソク足データを妨げることなく、トレンドを個別に観察しやすくしてくれるのです。

#property indicator_buffers 5

これは、インジケーターが使用するバッファの数を指定します。この例では、合計5つのバッファを使っており、そのうち1つは色の表示用、残りの4つは計算された平均足の値(始値、高値、安値、終値)を格納するためのものです。

比喩的な説明

先ほど、作業机の隣に別のホワイトボードがあると想像しましたが、今度はそのホワイトボードでの作業を管理するために、5つの異なるトレイが必要だと考えてみてください。スケッチや寸法、メモなどがそれぞれ別のトレイに整理されていることで、必要な情報をすぐに取り出せるようになります。

このトレイと同じように、#property indicator_buffers 5は、様々な平均足のデータポイントを分けて管理できるようにします。ここでは、色の表示用のバッファが1つと、平均足の始値、高値、安値、終値を格納するための4つのバッファの合計5つです。これらのバッファによってインジケーターの計算が整理され、チャート上に正しいデータを表示しやすくなるのです。まさにトレイが作業スペースを整頓するのと同じ役割を果たします。

#property indicator_plots 1

これは、インジケーターが表示するチャートの数を指定します。平均足を一つのまとまりとして描画するため、ここではプロットは1つだけで十分です。

比喩的な説明

トレイに道具を整理し、ホワイトボードの作業場を整えた後は、次にどのように作業内容を表示するかを決めます。複数の別々の図を作るのではなく、すべてのデータを1つにまとめた分かりやすい図を作成するとイメージしてください。

同じように、MetaTrader 5では#property indicator_plots 1によって、平均足インジケーターは1つの描画要素として表示されることが伝えられます。ホワイトボード上の単一の図のように、複数のバッファ(始値、高値、安値、終値、色)がそれぞれ異なるデータを持っていますが、すべて組み合わさって1つのローソク足セットを形成しています。平均足を描画するだけなので、チャート上に表示するプロットは1つで十分です。

描画設定

#property indicator_label1  "Heikin Ashi"
#property indicator_type1   DRAW_COLOR_CANDLES  
#property indicator_color1  clrGreen, clrRed    
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

比喩的な説明

材料を整理しホワイトボードの作業場を整えた後は、自分の成果を明確かつ分かりやすく伝えることが重要です。トレンドをよりわかりやすくするために、単なる文字や抽象的なデザインを使う代わりに、色分けされたシンボルを使うことにしました。ホワイトボードを見る人が図の意味をすぐに理解できるよう、「Heikin Ashi」とラベルをつけてはっきり示します。同様に、#property indicator_label1 "Heikin Ashi"はインジケーターに名前を付け、MetaTrader 5のインジケーターリストに表示されるようにします。これにより、トレーダーは他のインジケーターの中からこのインジケーターを素早く識別できるようになります。

#property indicator_type1 DRAW_COLOR_CANDLESは、MetaTrader 5にラインやヒストグラムではなく、色分けされたローソク足を使うよう指示します。色は #property indicator_color1 clrGreen, clrRedで定義され、緑が強気ローソク足、赤が弱気ローソク足を表します。この視覚的な明快さにより、トレンドを一目で把握しやすくなります。 ホワイトボードを見やすく整えるために、破線や点線ではなく実線で描画することにしました。

同様に、#property indicator_style1 STYLE_SOLIDは平均足を塗りつぶしの実線で表示させ、視覚的に判別しやすくします。最後に、線を太くしすぎて図が煩雑にならないように、#property indicator_width1 1はローソク足の輪郭の幅を適度に保ち、チャートが見やすくなるようにします。 こうして平均足インジケーターの設定をおこなうことで、整理された直感的にわかりやすいトレンド表示を実現できます。これは、きちんと整頓されたホワイトボード作業場と同じ考え方です。

インジケーターのプロパティや描画設定をおこなったら、次は平均足の価格を格納するバッファを定義します。バッファはインジケーターの計算値を保存するコンテナの役割を果たし、MetaTrader 5がチャート上にそれらを表示できるようにします。今回の場合、平均足の始値、高値、安値、終値を格納するバッファと、色の表現用のバッファが必要です。それぞれのバッファに対応するインデックスも設定し、正しいデータが正しいバッファに割り当てられるようにします。

// PROPERTY SETTINGS
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1

// PLOT SETTINGS FOR HEIKIN ASHI CANDLES
#property indicator_label1  "Heikin Ashi"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrGreen, clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

// INDICATOR BUFFERS
double HA_Open[];
double HA_High[];
double HA_Low[];
double HA_Close[];
double ColorBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
// SET BUFFERS
   SetIndexBuffer(0, HA_Open, INDICATOR_DATA);
   SetIndexBuffer(1, HA_High, INDICATOR_DATA);
   SetIndexBuffer(2, HA_Low, INDICATOR_DATA);
   SetIndexBuffer(3, HA_Close, INDICATOR_DATA);
   SetIndexBuffer(4, ColorBuffer, INDICATOR_COLOR_INDEX);

   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {

   return(rates_total);

  }

説明

平均足インジケーターで計算された値を保存するために、修正されたローソク足の始値、高値、安値、終値を保持する配列(バッファ)を作成します。また、各ローソク足の色は別のバッファを使って管理します。

double HA_Open[];
double HA_High[];
double HA_Low[];
double HA_Close[];
double ColorBuffer[];

HA_Open[]バッファには各ローソク足の平均足始値が格納され、HA_High[]には平均足の最高値が入ります。同様に、HA_Close[]には平均足の終値が保存され、HA_Low[]には最安値が記録されます。加えて、ColorBuffer[]は各ローソク足の色を決定するために使用され、強気ローソク足(緑)と弱気ローソク足(赤)を区別します。これらのバッファが連携して動作することで、チャート上に更新された平均足を保存し表示することが可能になります。

SetIndexBuffer(0, HA_Open, INDICATOR_DATA);
SetIndexBuffer(1, HA_High, INDICATOR_DATA);
SetIndexBuffer(2, HA_Low, INDICATOR_DATA);
SetIndexBuffer(3, HA_Close, INDICATOR_DATA);
SetIndexBuffer(4, ColorBuffer, INDICATOR_COLOR_INDEX);     

MetaTrader 5のSetIndexBuffer関数は、特定のバッファを対応するインデックスに結び付けることで、平均足データが正確に処理・表示されるようにします。プラットフォームのローソク足構造に従い、始値は常にインデックス0に割り当てられ、高値・安値・終値はそれぞれインデックス1、2、3にマッピングされます。適切なインデックス付けがなければ、MetaTrader 5はそのデータを正規のローソク足として認識せず、チャートの表示不具合や要素の欠落が生じる可能性があります。

SetIndexBuffer(4, ColorBuffer, INDICATOR_COLOR_INDEX)は、各ローソク足の色を指定し、強気(緑)または弱気(赤)の動きを視覚的に区別してトレンドを明確に示します。これらのバッファを正しくインデックス付けすることで、平均足インジケーターは価格の正確な表現と視覚的なスタイリングを保証し、トレーダーが迅速にトレンドを分析し、適切な判断を下せるようにします。

インジケーターのプロパティ設定、バッファの定義、そして適切なインデックスへの接続が完了したので、次は平均足の値を計算する処理を実装していきます。

// PROPERTY SETTINGS
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1

// PLOT SETTINGS FOR HEIKIN ASHI CANDLES
#property indicator_label1  "Heikin Ashi"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrGreen, clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

// INDICATOR BUFFERS
double HA_Open[];
double HA_High[];
double HA_Low[];
double HA_Close[];
double ColorBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
// SET BUFFERS
   SetIndexBuffer(0, HA_Open, INDICATOR_DATA);
   SetIndexBuffer(1, HA_High, INDICATOR_DATA);
   SetIndexBuffer(2, HA_Low, INDICATOR_DATA);
   SetIndexBuffer(3, HA_Close, INDICATOR_DATA);
   SetIndexBuffer(4, ColorBuffer, INDICATOR_COLOR_INDEX);

   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {

   if(rates_total < 2)
      return 0; // ENSURE ENOUGH DATA

   for(int i = 1; i < rates_total; i++)  // START FROM SECOND BAR
     {
      // HEIKIN ASHI CLOSE FORMULA
      HA_Close[i] = (open[i] + high[i] + low[i] + close[i]) / 4.0;

      // HEIKIN ASHI OPEN FORMULA
      HA_Open[i] = (HA_Open[i - 1] + HA_Close[i - 1]) / 2.0;

      // HEIKIN ASHI HIGH FORMULA
      HA_High[i] = MathMax(high[i], MathMax(HA_Open[i], HA_Close[i]));

      // HEIKIN ASHI LOW FORMULA
      HA_Low[i] = MathMin(low[i], MathMin(HA_Open[i], HA_Close[i]));

      // SET COLOR: GREEN FOR BULLISH, RED FOR BEARISH
      ColorBuffer[i] = (HA_Close[i] >= HA_Open[i]) ? 0 : 1;
     }

   return(rates_total);

  }

最初のバーには前のデータが存在しないため、この方法では平均足の値は2本目のバーから計算を開始します。価格の変動を滑らかにするために、終値は現在のバーの始値、高値、安値、終値の平均値として求められます。始値は、前のバーの平均足の始値と終値の平均を取り、スムーズな連続性を確保します。高値と安値は、現在のバーの高値・安値と平均足の始値・終値の中から最も高い値と最も低い値を選んで計算されます。最後に、ローソク足は、終値が始値以下の場合は赤(弱気)、それ以外の場合は緑(強気)に色付けされます。これにより市場のノイズが減り、トレーダーはトレンドをより見やすくなります。

図6:HAインジケーター


2. 平均足データから移動平均を作成する

平均足を正しく生成できたので、次は通常の価格データではなく平均足の値をもとに移動平均(MA)を作成します。

擬似コード

インジケーターのプロパティを修正する

  • 平均足移動平均用にバッファ数を5から6に増やす 

  • ローソク足と平均足移動平均の両方を表示できるように、プロット数を1から2に変更する

平均足移動平均用のバッファを定義する

  • 平均足移動平均の値を格納するバッファを作成する
  • 移動平均の期間(例:20)を入力変数として定義する

平均足移動平均用のバッファを設定する

  • 計算した移動平均値を格納するためにバッファをインデックスに結び付ける
  • 表示を正しくするため、移動平均の期間からプロットを開始するように設定する

平均足移動平均を計算する

  • 十分なデータを確保するため、ループは(期間 - 1)から開始する
  • 直近n本の平均足終値の合計を計算する
  • 合計を期間で割り、結果をバッファに保存する

// PROPERTY SETTINGS
#property indicator_separate_window
#property indicator_buffers 6
#property indicator_plots   2

// PLOT SETTINGS FOR HEIKIN ASHI CANDLES
#property indicator_label1  "Heikin Ashi"
#property indicator_type1   DRAW_COLOR_CANDLES  
#property indicator_color1  clrGreen, clrRed    
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//PROPERTIES OF THE Heikin MA
#property indicator_label2  "Heikin MA"   
#property indicator_type2   DRAW_LINE  
#property indicator_style2  STYLE_DASH 
#property indicator_width2  1          
#property indicator_color2  clrBrown   

// INDICATOR BUFFERS
double HA_Open[];
double HA_High[];
double HA_Low[];
double HA_Close[];
double ColorBuffer[]; 

double Heikin_MA_Buffer[];
int input  heikin_ma_period = 20;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
    // SET BUFFERS
    SetIndexBuffer(0, HA_Open, INDICATOR_DATA);
    SetIndexBuffer(1, HA_High, INDICATOR_DATA);
    SetIndexBuffer(2, HA_Low, INDICATOR_DATA);
    SetIndexBuffer(3, HA_Close, INDICATOR_DATA);
    SetIndexBuffer(4, ColorBuffer, INDICATOR_COLOR_INDEX);
    
    SetIndexBuffer(5, Heikin_MA_Buffer, INDICATOR_DATA);  
    PlotIndexSetInteger(5, PLOT_DRAW_BEGIN, heikin_ma_period);
     
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
    if (rates_total < 2) return 0; // ENSURE ENOUGH DATA
      
    for (int i = 1; i < rates_total; i++) // START FROM SECOND BAR
    {
        // HEIKIN ASHI CLOSE FORMULA
        HA_Close[i] = (open[i] + high[i] + low[i] + close[i]) / 4.0;

        // HEIKIN ASHI OPEN FORMULA
        HA_Open[i] = (HA_Open[i - 1] + HA_Close[i - 1]) / 2.0;

        // HEIKIN ASHI HIGH FORMULA
        HA_High[i] = MathMax(high[i], MathMax(HA_Open[i], HA_Close[i]));

        // HEIKIN ASHI LOW FORMULA
        HA_Low[i] = MathMin(low[i], MathMin(HA_Open[i], HA_Close[i]));

        // SET COLOR: GREEN FOR BULLISH, RED FOR BEARISH
        ColorBuffer[i] = (HA_Close[i] >= HA_Open[i]) ? 0 : 1;
    }    
    
     for(int i = heikin_ma_period - 1; i < rates_total; i++)
     {

      double sum = 0.0;
      for(int j = 0; j < heikin_ma_period; j++)
        {
         sum += HA_Close[i - j];
        }

      Heikin_MA_Buffer[i] = sum / heikin_ma_period;

     }

    return rates_total;
}

解説

まず、平均足移動平均(HA MA)をインジケーターに組み込むために、バッファとプロットの設定を修正する必要があります。#property indicator_buffers 6により、バッファ数を5から6に増やし、平均足移動平均のデータを格納する余分なバッファを確保します。また、#property indicator_plots 2によってプロット数を1から2に変更し、平均足と平均足移動平均の両方をチャート上に表示できるようにします。これにより、インジケーターは両方のデータセットを正しく処理可能となります。

次に、平均足移動平均のプロットに関するプロパティを設定します。#property indicator_label2でラベルを指定し、「Heikin MA」という名前をつけることで、インジケーター一覧で識別しやすくします。移動平均をローソク足ではなく線で表示するために、#property indicator_type2 DRAW_LINEを設定します。線を破線にするために#property indicator_style2 STYLE_DASH、見やすくするために線の太さを#property indicator_width2 1に設定します。さらに、#property indicator_color2 clrBrownによって色を茶色に指定し、平均足との視認性を高めます。

プロパティ設定の後、ユーザーが期間を変更できるように入力パラメータint input heikin_ma_period = 20;を定義し、移動平均のデータを格納する配列double Heikin_MA_Buffer[];を宣言します。SetIndexBuffer(5, Heikin_MA_Buffer, INDICATOR_DATA); によって、このバッファをインジケーターに紐づけ、MetaTrader 5が値を適切に管理・表示できるようにします。さらに、PlotIndexSetInteger(5, PLOT_DRAW_BEGIN, heikin_ma_period);により、十分なバー数が揃うまで移動平均がプロットされないように設定します。

最後に、平均足移動平均の計算をおこないます。計算に十分なデータを確保するために、ループはheikin_ma_period - 1から開始します。ループ内で直近n本の平均足終値を合計し、その合計を期間で割って移動平均を算出します。計算結果はHeikin_MA_Buffer[i]に格納され、平均足と共に描画されます。これにより、トレーダーはトレンドを滑らかに追うツールを手に入れることができます。

図7:HAインジケーターとHA MA


3. カスタムインジケーターをEAに統合する

ここまででカスタムインジケーターを作成しましたが、これをどのようにしてEAに活用するのか疑問に思うかもしれません。本章では、前章で作成した平均足インジケーターを使い、引き続きプロジェクトベースの手法でEAを設計します。このEAは平均足のシグナルを利用して取引判断を自動化し、実際に動作する自動売買ボットとして機能します。

取引戦略は、平均足移動平均(HA MA)と平均足(HA)ローソク足の単純なクロスオーバー戦略です。 この手法は、平均足の終値と平均足移動平均の関係から、トレンドの反転や継続の兆候を判断します。平均足が平均足移動平均を上回って終われば上昇トレンドおよび買いのチャンスを示します。逆に、平均足が平均足移動平均を下回って終わる場合は売りシグナルとなり、下降トレンドの反転や売りの機会を示唆します。

図8:売買ロジック

3.1. インジケーターデータの取得

カスタムインジケーターをEAに統合する際にまず考えるべきことは、インジケーターのデータをどのようにEAに取り込むかという点です。インジケーターの値が利用できなければ、EAはそのデータをもとに取引判断をおこなうことができません。たとえば、平均足を構成する4つの値を、平均足移動平均のクロスオーバー戦略に取り込む必要があります。

  • HA始値
  • HA高値
  • HA安値
  • HA終値

私たちの取引シグナルはこれらの値に基づいており、平均足が平均足移動平均を上抜けたか下抜けたかを判断するために使用されます。

// Declare arrays to store Heikin Ashi data from the custom indicator
double heikin_open[];   // Stores Heikin Ashi Open prices
double heikin_close[];  // Stores Heikin Ashi Close prices
double heikin_low[];    // Stores Heikin Ashi Low prices
double heikin_high[];   // Stores Heikin Ashi High prices
double heikin_ma[];     // Stores Heikin Ashi Moving Average values

int heikin_handle;      // Handle for the custom Heikin Ashi indicator

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
// Ensure arrays store data in a time series format (most recent data first)
   ArraySetAsSeries(heikin_open, true);
   ArraySetAsSeries(heikin_close, true);
   ArraySetAsSeries(heikin_low, true);
   ArraySetAsSeries(heikin_high, true);
   ArraySetAsSeries(heikin_ma, true);

// Load the custom Heikin Ashi indicator and get its handle
   heikin_handle = iCustom(_Symbol, PERIOD_CURRENT, "Project7 Heikin Ashi Indicator.ex5");

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
// Nothing to clean up in this case, but can be used for resource management if needed
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Copy the latest 3 values of each buffer from the Heikin Ashi indicator
   CopyBuffer(heikin_handle, 0, 0, 3, heikin_open);  // Get HA Open values
   CopyBuffer(heikin_handle, 1, 0, 3, heikin_high);  // Get HA High values
   CopyBuffer(heikin_handle, 2, 0, 3, heikin_low);   // Get HA Low values
   CopyBuffer(heikin_handle, 3, 0, 3, heikin_close); // Get HA Close values
   CopyBuffer(heikin_handle, 5, 0, 3, heikin_ma);    // Get HA Moving Average values

// Print index 0 values to the terminal
   Print("HA Open: ", heikin_open[0],
         "\nHA High: ", heikin_high[0],
         "\nHA Low: ", heikin_low[0],
         "\nHA Close: ", heikin_close[0],
         "\nHA MA: ", heikin_ma[0]);
  }

解説

平均足データを格納する配列の宣言

カスタムインジケーターをEAに統合する最初のステップは、インジケーターから取得したデータを格納する配列を宣言することです。ここでは、平均足インジケーターの各構成要素を保存するために5つの配列を宣言します。

  • heikin_open[]:平均足の始値を格納
  • heikin_close[]:平均足の終値を格納
  • heikin_low[]:平均足の安値を格納
  • heikin_high[]:平均足の高値を格納
  • heikin_ma[]:平均足移動平均値を格納

これらの配列は過去および現在の平均足の値にEAがアクセスできるようにするため重要です。EAはこれらのデータを用いて過去の価格動向を評価し、取引判断を調整します。これらの配列はカスタムインジケーターからのデータで動的に更新されるため、初期化時に固定値を設定する必要はありません。

インジケーターハンドルの宣言

カスタム平均足インジケーターのハンドルはheikin_handle変数に格納されます。MQL5におけるハンドルとは、バックグラウンドで動作するインジケーターの一意の参照です。このハンドルを通じてEAはインジケーターとやり取りし、必要なデータを取得できます。ハンドルがなければEAは平均足の値にアクセスできません。後ほどiCustom()関数でこのハンドルに値が割り当てられます。もしハンドルが無効(-1を返す)であれば、インジケーターが正しく読み込まれていないことを意味し、EAは必要なデータを取得できません。

時系列形式での配列の初期化

配列は宣言時に、常に最新データがインデックス0にくるように構築しなければなりません。そのためにArraySetAsSeries()関数を使い、配列要素を降順(新しい順)に並べ替えます。

この関数は以下の5つの配列すべてに適用されます。

 ArraySetAsSeries(heikin_open, true);
 ArraySetAsSeries(heikin_close, true);
 ArraySetAsSeries(heikin_low, true);
 ArraySetAsSeries(heikin_high, true);
 ArraySetAsSeries(heikin_ma, true);

これらの配列を時系列形式に変換することで、インデックス0が常に最新の平均足データをEAがアクセスできるようになります。これは、取引手法を実践する際に特に役立ち、EAが過去のデータではなく現在の市場動向に基づいて反応することを保証します。

iCustom()でカスタム平均足インジケーターを読み込む

カスタムインジケーターをEAに組み込むためには、インジケーターのデータにアクセスする方法が必要です。iCustom()関数を使うことで、カスタムインジケーターを読み込み、EAがインジケーターの値を要求する際に使用するハンドルを取得できます。このハンドルがなければ、EAはインジケーターのデータにアクセスできません。

heikin_handle = iCustom(_Symbol, PERIOD_CURRENT, "Project7 Heikin Ashi Indicator.ex5");

  • _Symbol:現在の取引銘柄(例:EUR/USD、GBP/JPYなど)に対してインジケーターを適用することを指定します。これにより、EAが動作している資産と同じデータを処理します。
  • PERIOD_CURRENT:EAと同じ時間軸にインジケーターを適用します。たとえば、EAがH1チャートで動作している場合、インジケーターもH1に適用されます。
  • Project7 Heikin Ashi Indicator.ex5:EAが使用すべきカスタムインジケーターのファイル名を指定します。.ex5拡張子はコンパイル済みのMQL5インジケーターファイルであることを示します。

インジケーターのファイルがMetaTrader 5の適切なディレクトリに保存されていることが重要です。インジケーターはMQL5フォルダのIndicatorsフォルダ内に配置する必要があります。このディレクトリの完全なパスは次のとおりです。

MQL5/Indicators/

まとめると、カスタムインジケーターとEAをつなぐ主な関数はiCustom()です。これによりEAは動的にインジケーターの値を取得できるハンドルを得られます。インジケーターがIndicatorsフォルダ(またはそのサブフォルダ)に正しく配置されていないと、関数は正常に動作しません。

インジケーターデータを配列にコピーする

ハンドルを取得した後、EAはCopyBuffer()関数を使ってインジケーターから最新の平均足データを取得できます。CopyBuffer()関数はインジケーターの内部バッファからEAの配列にデータをコピーします。このEAでは、5種類のデータそれぞれについて5回この関数を呼び出します。

CopyBuffer(heikin_handle, 0, 0, 3, heikin_open);  // Get HA Open values
CopyBuffer(heikin_handle, 1, 0, 3, heikin_high);  // Get HA High values
CopyBuffer(heikin_handle, 2, 0, 3, heikin_low);   // Get HA Low values
CopyBuffer(heikin_handle, 3, 0, 3, heikin_close); // Get HA Close values
CopyBuffer(heikin_handle, 5, 0, 3, heikin_ma);    // Get HA Moving Average values

CopyBuffer()関数は、カスタムインジケーターからデータを取得する際、常に同じ構造で呼び出されます。iCustom()関数から取得したハンドルであるheikin_handleが最初のパラメータです。このハンドルを参照として使用することで、EAはカスタム平均足インジケーターのデータにアクセスできます。ハンドルがなければ、EAはインジケーターの値を要求することができません。 平均足インジケーターの各コンポーネントに対応するバッファのインデックスは、パラメータ0、1、2、3、5で指定されます。MQL5では、インジケーターはデータをバッファに格納し、それぞれ固有のインデックスが割り当てられます。この例では、バッファ0が平均足の始値、バッファ1が高値、バッファ2が安値、バッファ3が終値、そしてバッファ5が平均足移動平均を表します。これらのインデックスを指定することで、正しいデータをインジケーターから取得できます。

3番目のパラメータ0は、最新のバー(現在のローソク足)からデータをコピーし始めることを示しています。これにより、EAは常に最新の市場データを利用でき、リアルタイムの取引判断に必要な情報が確保されます。4番目のパラメータ3は、3つのデータポイントを複数取得することを意味します。複数の値を取得することで、EAは過去と現在の平均足データの両方を分析でき、パターンの発見やトレンドの検証に役立ちます。 

最後に、取得したデータはそれぞれ対応する配列heikin_open[]、heikin_high[]、heikin_low[]、heikin_close[]、heikin_ma[]に格納されます。これらの配列に値を保持することで、EAは取引ロジックでデータを処理し活用可能となります。取引判断を適切におこなうには、平均足インジケーターの最新データを持つことが不可欠です。

3.2. 取引戦略における指標データの活用

カスタムインジケーターからデータを取得する方法を理解した後は、そのデータをどのように取引戦略に適用するかを学ぶ必要があります。これにより、平均足インジケーターをEAで効果的に活用し、取得した数値に基づいて取引判断を下す方法を示します。さらに、この過程で重要なMQL5の概念を深く理解できる機会にもなり、この記事を読み進めるうえで理解がより深まります。 

しかしながら、EAの重要な要素を考慮せずに単純に理論を適用することはできません。適切な取引管理をおこなうためには、いくつかの機能を統合する必要があります。これには、ポジション数の管理、トレーリングストップの設定、リスクパラメータの制御、そしてEAが体系的に取引を実行することが含まれます。これらの重要な要素を含めることで、実践的に機能する信頼性の高い取引システムを構築できます。

3.2.1. EAにおける買い・売りのオープンポジションの管理

取引手法が正しく機能するためには、同時に買いポジションと売りポジションがそれぞれ1つずつだけ開かれている状態を維持する必要があります。これにより、同じ方向の複数ポジションによる不要なリスクを軽減し、ポジション管理のコントロールがしやすくなります。EAはポジション数を監視し、新規ポジションを建てるべきか、既存ポジションが決済されるのを待つべきかを判断します。この方法によって、戦略が計画通りに機能し、ポジション管理が向上します。

int totalPositions = 0;
int position_type_buy = 0;
int position_type_sell = 0;

for(int i = 0; i < PositionsTotal(); i++)
  {
   ulong ticket = PositionGetTicket(i);

   if(PositionSelectByTicket(ticket))
     {
      if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(ChartID())
     {
      totalPositions++;
      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
           {
            position_type_buy++;
           }
         if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
           {
            position_type_sell++;
           }
        }
     }
  }

EAはポジションの管理のために、買いポジション、売りポジション、そして総取引数をカウントします。ポジションを順に調べ、それぞれを買いまたは売りに分類し、マジックナンバーとチャートの銘柄を使って戦略に合致しているかを確認します。これにより、同時に買いポジションと売りポジションがそれぞれ1つずつのみ存在することを許可し、ポジション管理を適切におこなうことが保証されます。

3.2.2. EAにおける日次取引制限の管理

このセクションの主題は、一定の取引セッション内でおこなわれた取引数の合計です。EAは、あらかじめ定めた時間範囲内で買い・売りの取引数を集計し、戦略が許容される日次取引制限を超えないように管理します。これにより、設定された時間内での過剰取引を防ぎ、コントロールされた取引行動の維持に役立ちます。

input int daily_trades = 6; // Total Daily Trades

// Start and end time for
string start = "00:00";
string end = "23:50";

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

   datetime start_time = StringToTime(start);
   datetime end_time = StringToTime(end);
   bool success = HistorySelect(start_time, end_time);

// Getting total trades
   int totalDeal = 0;
   int deal_type_buy = 0;
   int deal_type_sell = 0;
   if(success)
     {
      for(int i = 0; i < HistoryDealsTotal(); i++)
        {
         ulong ticket = HistoryDealGetTicket(i);

         if(HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber && HistoryDealGetString(ticket,DEAL_SYMBOL) == ChartSymbol(chart_id))
           {
            if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
              {
               totalDeal++;
               if(HistoryDealGetInteger(ticket, DEAL_TYPE) == DEAL_TYPE_BUY)
                 {

                  deal_type_buy++;

                 }
               if(HistoryDealGetInteger(ticket, DEAL_TYPE) == DEAL_TYPE_SELL)
                 {

                  deal_type_sell++;

                 }
              }
           }
        }
     }
  }

解説

EAは、まず指定された開始時刻と終了時刻をdatetime形式に変換し、特定の取引セッション内でおこなわれた取引の総数を管理します。次に、HistorySelect()関数を使って、その時間範囲内に該当する取引履歴を選択します。選択に成功した場合、EAは実行された取引の総数と、買い・売り取引の件数を別々に記録するためのカウンタを設定します。

その後、EAは取引履歴を順に処理して各取引のチケット番号を取得します。関連する取引のみをカウントするために、取引が現在のチャートのマジックナンバーおよび銘柄と一致するかを確認します。取引が新規エントリーのものであると判断された場合、取引の総数が増加します。取引の種類を判定した後、EAは対応するカウンタを更新します。これにより、戦略は実行された取引を正確に把握し、日次の取引制限を超えないよう管理できます。

3.2.3. 同じローソク足内での繰り返し取引の防止

場合によっては、EAが既存のポジションを決済した直後に新しい取引を開始することがあります。特に、新規取引は新しいバーの始値でのみおこないたい場合に、取引条件が変わらず継続していると、このようなことが起こりやすいです。これにより、同じローソク足の中で意図せず連続した取引が発生する可能性があります。この節では、取引が次のバーの始値でのみ実行されるようにするなどの対策を取り上げ、取引制御を強化して不要な再エントリーを防ぐ方法を説明します。

// Declare an array to store time data
datetime time[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
// Ensure the time array is structured as a time series (most recent data first)
   ArraySetAsSeries(time, true);

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Retrieve the latest three time values from the current symbol and timeframe
   CopyTime(_Symbol, PERIOD_CURRENT, 0, 3, time);

// Select trading history from the earliest recorded time to the current time
   bool trade_control = HistorySelect(time[0], TimeCurrent());

// Variable to count the number of closed trades
   int total_deals_out = 0;

// If the trading history selection is successful, process the data
   if(trade_control)
     {
      // Loop through all closed trades in the history
      for(int i = 0; i < HistoryDealsTotal(); i++)
        {
         // Get the ticket number of the historical trade
         ulong ticket = HistoryDealGetTicket(i);

         // Check if the trade matches the current EA's Magic Number and symbol
         if(HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber && HistoryDealGetString(ticket, DEAL_SYMBOL) == ChartSymbol(chart_id))
           {
            // If the trade was an exit trade, increment the counter
            if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT)
              {
               total_deals_out++;
              }
           }
        }
     }
  }

解説

この機能は、EAがポジション決済後に同じローソク足内で新規取引を開くのを防ぎます。まず、CopyTime()関数を使ってチャート上の最新バーのタイムスタンプを取得します。次に、最も古いバーの時刻(time[0])から現在の市場時間(TimeCurrent())までの間の取引履歴を選択します。

続いて、EAのマジックナンバーと銘柄に一致する決済取引を見つけるために、取引履歴を順に調査します。決済取引、特に「取引終了(DEAL_ENTRY_OUT)」が見つかると、total_deals_outカウンタが増加します。同一ローソク足内で最近決済された取引があるかどうかをこのカウンタで把握できます。これにより、新しいローソク足が始まるまでEAは新規取引を開始せず、不要な即時再エントリーを防ぎます。

3.2.4. 平均足を用いたリスク管理および取引執行の実装

このセクションでは、買い・売りの取引が平均足のシグナルに従って正しく実行されるための条件を定めます。さらに、1取引あたりのリスク許容額(ドルベース)とリスクリワード比(RRR)に基づきロットサイズを計算することでリスク管理を組み込みます。この戦略により、過度なリスクを避けつつ、安定した取引を維持することが可能になります。

input  double dollar_risk = 12.0; // How Many Dollars($) Per Trade?
input double RRR = 3;

double ask_price;
double lot_size;
double point_risk;
double take_profit;

// Variable to store the time of the last executed trade
datetime lastTradeBarTime = 0;

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

// Get the opening time of the current bar
   datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);

// Check conditions for opening a buy position
// Ensures Heikin Ashi candle crosses above the moving average
// Limits trades per day and prevents multiple trades in the same candlestick
   if(heikin_open[1] < heikin_ma[1] && heikin_close[1] > heikin_ma[1] &&
      deal_type_buy < (daily_trades / 2) && total_deals_out < 1 &&
      totalPositions < 1 && currentBarTime != lastTradeBarTime)
     {
      // Calculate risk in points (distance from entry to stop loss)
      point_risk = ask_price - heikin_low[1];

      // Calculate take profit based on risk-to-reward ratio (RRR)
      take_profit = ((ask_price - heikin_low[1]) * RRR) + ask_price;

      // Determine lot size based on the dollar risk per trade
      lot_size = CalculateLotSize(_Symbol, dollar_risk, point_risk);

      // Execute a buy trade
      trade.Buy(lot_size, _Symbol, ask_price, heikin_low[1], take_profit);

      // Store the current bar time to prevent multiple trades in the same candle
      lastTradeBarTime = currentBarTime;
     }

// Check conditions for opening a sell position
// Ensures Heikin Ashi candle crosses below the moving average
// Limits trades per day and prevents multiple trades in the same candlestick
   if(heikin_open[1] > heikin_ma[1] && heikin_close[1] < heikin_ma[1] &&
      deal_type_sell < (daily_trades / 2) && total_deals_out < 1 &&
      totalPositions < 1 && currentBarTime != lastTradeBarTime)
     {
      // Calculate risk in points (distance from entry to stop loss)
      point_risk = heikin_high[1] - ask_price;

      // Calculate take profit based on risk-to-reward ratio (RRR)
      take_profit = MathAbs(((heikin_high[1] - ask_price) * RRR) - ask_price);

      // Determine lot size based on the dollar risk per trade
      lot_size = CalculateLotSize(_Symbol, dollar_risk, point_risk);

      // Execute a sell trade
      trade.Sell(lot_size, _Symbol, ask_price, heikin_high[1], take_profit);

      // Store the current bar time to prevent multiple trades in the same candle
      lastTradeBarTime = currentBarTime;
     }
  }

//+------------------------------------------------------------------+
//| Function to calculate the lot size based on risk amount and stop loss
//+------------------------------------------------------------------+
double CalculateLotSize(string symbol, double riskAmount, double stopLossPips)
  {
// Get symbol information
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
   double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);

// Calculate pip value per lot
   double pipValuePerLot = tickValue / point;

// Calculate the stop loss value in currency
   double stopLossValue = stopLossPips * pipValuePerLot;

// Calculate the lot size
   double lotSize = riskAmount / stopLossValue;

// Round the lot size to the nearest acceptable lot step
   double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   lotSize = MathFloor(lotSize / lotStep) * lotStep;

   return lotSize;
  }

解説

取引執行のロジックは、平均足が移動平均線(MA)をクロスすることを基にトレンドの確認をおこないます。始値がMAより下で終値が上回った場合は強気の反転(買いシグナル)、その逆の場合は売りシグナルと判断します。システムは日次取引回数を制限し、ポジションがないことを確認し、新しいローソク足が形成された際のみ取引をおこなうことで過剰取引を防止します。リスク管理の一環として、ストップロスは直近の平均足の高値または安値に設定し、リスクリワード比(RRR)に基づく利益確定や、ロットサイズの動的調整もおこないます。

3.2.5. 平均足を用いたトレーリングストップの実装

このセクションの主な目的は、平均足を利用したトレーリングストップ機能の実装です。これは、平均足価格の変動に応じてストップロスレベルを動的に調整する仕組みを提供します。

input bool    allow_trailing  = false; // Do you Allow Trailing Stop?

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

   // Check if trailing stop is enabled
   if(allow_trailing == true)
     {
      // Variables to store trade-related information
      double positionProfit = 0;
      double positionopen = 0;
      double positionTP = 0;
      double positionSL = 0;

      // Loop through all open positions
      for(int i = 0; i < PositionsTotal(); i++)
        {
         // Get the ticket number of the position
         ulong ticket = PositionGetTicket(i);

         // Select the position using its ticket number
         if(PositionSelectByTicket(ticket))
           {
            // Check if the position belongs to the EA by verifying the magic number and symbol
            if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id))
              {
               // Retrieve trade details: open price, take profit, profit, and stop loss
               positionopen = PositionGetDouble(POSITION_PRICE_OPEN);
               positionTP = PositionGetDouble(POSITION_TP);
               positionProfit = PositionGetDouble(POSITION_PROFIT);
               positionSL = PositionGetDouble(POSITION_SL);

               // Apply trailing stop logic for buy positions
               if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                 {
                  // Adjust stop loss if Heikin Ashi low is above the entry price and the candle is bullish
                  if(heikin_low[1] > positionopen && heikin_close[1] > heikin_open[1])
                    {
                     trade.PositionModify(ticket, heikin_low[1], positionTP);
                    }
                 }

               // Apply trailing stop logic for sell positions
               if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
                 {
                  // Adjust stop loss if Heikin Ashi high is below the entry price and the candle is bearish
                  if(heikin_high[1] < positionopen && heikin_close[1] < heikin_open[1])
                    {
                     trade.PositionModify(ticket, heikin_high[1], positionTP);
                    }
                 }
              }
           }
        }
     }
  }

解説

利益を確保し、ストップロスレベルを動的に調整するために、このセクションでは平均足を用いてトレーリングストップシステムを実装します。allow_trailing入力変数でトレーリングストップ機能の有効・無効を制御し、trueに設定されている場合、システムはすべてのポジションをループで処理し、マジックナンバーと銘柄を確認してEAに属しているかどうかを判定します。トレーリングストップロジックを簡潔にするために、オープン価格、テイクプロフィット、利益、ストップロスといった主要な取引情報を取得します。

システムは、過去の平均足の安値がエントリー価格より高く、かつ平均足の終値が始値を上回っていることを確認することで、ポジションの買いトレンドを検証します。利益を守りつつさらなる上昇余地を許容するために、もし条件を満たせばストップロスは平均足の安値に移動されます。一方、売り取引の場合は、過去の平均足の高値がエントリー価格より低く、かつ平均足の終値が始値を下回っていることを確認して下降トレンドを検証します。これらの条件を満たす場合は、取引を保護しさらなる市場変動を取り込むために、ストップロスを平均足の高値に設定します。


結論

本記事では、一から平均足インジケーターを作成し、平均足のデータを用いた移動平均と統合しました。また、カスタムインジケーターをEAに組み込み、自動売買へのスムーズな連携方法を解説しました。日次取引回数の制限や同一ローソク足内での複数決済取引の防止、ロットサイズの動的計算といったリスク管理手法も実装しています。さらに、平均足を活用したトレーリングストップ機能を導入し、ストップロスレベルの自動調整をおこないました。これらの内容は、MQL5における自動売買戦略の構築と改善に役立つ堅固な基盤を提供します。

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

最後のコメント | ディスカッションに移動 (2)
dhermanus
dhermanus | 31 5月 2025 において 11:08

こんにちは、イスラエル、

ブログ、お時間と労力をありがとうございます。


あなたのHeikin Ashiカスタムインジケータコードについて お聞きしたいのですが。

ハイキンアシ・オープンの計算式について:

// HEIKIN ASHI OPEN FORMULA
HA_Open[i] = (HA_Open[i - 1] + HA_Close[i - 1]) / 2.0;

HA_Open[i-1]は計算されていません。これは0ではないでしょうか?


私の提案は


if (i == 1){

HA_Open[i] = (open[i - 1] + close[i - 1])/2.0; // HA最初のバーでは、通常のオープン/クローズのデータを使用する。

}

else{

// ヘイキン・アシ・オープンの計算式

HA_Open[i] = (HA_Open[i - 1] + HA_Close[i - 1]) / 2.0;


}

Israel Pelumi Abioye
Israel Pelumi Abioye | 31 5月 2025 において 12:28
dhermanus カスタムインジケーターコードについて お聞きしたいのですが。

ハイキンアシ・オープンの計算式について:

//
HA_Open[i] = (HA_Open[i - 1] + HA_Close[i - 1]) / 2.0;

HA_Open[i-1]は計算されていません。これは0ではないでしょうか?


私の提案


if (i == 1){

HA_Open[i] = (open[i - 1] + close[i - 1])/2.0; // HA最初のバーでは、通常のオープン/クローズのデータを使用する。

}

else{

// ヘイキン・アシ・オープンの公式

HA_Open[i] = (HA_Open[i - 1] + HA_Close[i - 1]) / 2.0;


}

ありがとう。調べてみます。
HarmonyOS NEXTデバイスにMetaTrader 5などのMetaQuotesアプリをインストールする HarmonyOS NEXTデバイスにMetaTrader 5などのMetaQuotesアプリをインストールする
HarmonyOS NEXTデバイスでMetaTrader 5やその他のMetaQuotesアプリをDroiTong(卓易通)を使って簡単にインストールできます。スマートフォンやノートパソコン向けの詳細なステップバイステップガイドです。
PythonとMQL5による多銘柄分析(第3回):三角為替レート PythonとMQL5による多銘柄分析(第3回):三角為替レート
トレーダーは、誤ったシグナルによるドローダウンに直面することが多い一方で、確認を待ちすぎることで、有望な機会を逃すこともあります。本稿では、ドル建て銀価格(XAGUSD)、ユーロ建て銀価格(XAGEUR)、およびEURUSD為替レートを用いた三角裁定取引戦略を紹介し、市場のノイズをフィルタリングする方法を解説します。市場間の相関関係を活用することで、隠れた市場センチメントをリアルタイムで捉え、エントリータイミングをより洗練させることが可能になります。
知っておくべきMQL5ウィザードのテクニック(第56回):ビル・ウィリアムズフラクタル 知っておくべきMQL5ウィザードのテクニック(第56回):ビル・ウィリアムズフラクタル
ビル・ウィリアムズによるフラクタルは、最初にチャート上で目にしたときには見落とされがちな強力なインジケーターです。一見するとチャートが煩雑に見え、鋭さに欠けるように思えるかもしれません。この記事では、このインジケーターの覆いを取り払い、そのさまざまなパターンがどのように機能するのかを、MQL5ウィザードで組み上げたエキスパートアドバイザー(EA)によるフォワードウォークテストを通じて検証していきます。
外国為替平均回帰戦略のためのカルマンフィルター 外国為替平均回帰戦略のためのカルマンフィルター
カルマンフィルターは、価格変動のノイズを除去して金融時系列の真の状態を推定するために、アルゴリズム取引で用いられる再帰的なアルゴリズムです。新しい市場データに基づいて予測を動的に更新するため、平均回帰のような適応型戦略において非常に有用です。本記事ではまず、カルマンフィルターの計算方法と実装について紹介します。次に、このフィルターをクラシックな平均回帰型の外国為替(FX)戦略に適用する例を示します。最後に、異なる通貨ペアにおいてカルマンフィルターと移動平均を比較し、さまざまな統計分析をおこないます。