English Deutsch
preview
MQL5で自己最適化エキスパートアドバイザーを構築する(第4回):動的なポジションサイズ調整

MQL5で自己最適化エキスパートアドバイザーを構築する(第4回):動的なポジションサイズ調整

MetaTrader 5 |
158 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

電子デジタルコンピューターは1950年代から存在していますが、金融市場自体は何世紀も前から存在しています。人間のトレーダーは、歴史的に高度な計算ツールを用いることなく成功を収めてきました。このことは、現代の取引ソフトウェアの設計において、計算能力を最大限に活用すべきか、それとも人間の成功体験に基づく原則に合わせるべきかという問いを投げかけます。本記事では、シンプルさと最新技術のバランスを取ることの重要性を提唱いたします。現代の高度なツールが存在するにもかかわらず、多くのトレーダーが、MQL5 APIのような強力なソフトウェアを使用せずとも、複雑で分散的なシステムの中で成功を収めてきたのです。 

私たち人間が日常的におこなっている意思決定の多くは、コンピューターに正確に伝えるのが難しいものです。たとえば、取引の現場では「今回は自信があったので、ロットを増やした」といった発言をよく耳にします。では、取引アプリケーションにも「自信がある」と感じたときに、同様にロットを増やすよう指示するには、どのようにすれば良いのでしょうか。

このような目標を達成するには、コンピューターがどの程度「自信がある」と感じているかを測定するための複雑さを、システムに導入しなければならないことは明らかです。一つのアプローチとして、確率的なモデルを構築し、取引の「信頼度」を定量化する方法があります。本記事では、単純なロジスティック回帰モデルを構築することで、取引の信頼度を測定し、アプリケーションが自立的にポジションサイズを調整できるようにしていきます。

今回の実装では、ジョン・ボリンジャー氏が提唱したボリンジャーバンド戦略に焦点を当てます。この戦略の本質を損なうことなく、その欠点を改善し、より洗練された形にすることを目指します。 

私たちの取引アプリケーションが目指す目標は以下の通りです。

  1. モデルが取引に対して高い信頼度を示した場合、ロットを増やして追加取引をおこなう
  2. モデルの信頼度が低い場合は、ロットを小さくして単一の取引をおこなう

ボリンジャー氏のオリジナル戦略は、私たちのバックテストにおいて493回の取引をおこない、そのうち62%が利益を上げました。これは健全な勝率ではありますが、収益性の高い戦略としては不十分であり、テスト期間中の損益はマイナス813ドル、シャープレシオは-0.33でした。一方、私たちが改良を加えたアルゴリズムでは、合計495回の取引をおこない、そのうち63%が利益を上げました。結果として、同じ期間での最終利益は2,427ドルに増加し、シャープレシオも0.74に改善されました。 

本記事の目的は、DNN(ディープニューラルネットワーク)や強化学習のような高度な計算ツールの価値を否定することではありません。むしろ、こうした技術がもたらす可能性には非常に期待しています。しかし、複雑さを導入することそれ自体が、常に優れた成果をもたらすわけではないことを認識することも重要です。

アルゴリズム取引のコミュニティに新しく加わる方々が直面する課題について、私はよく理解しています。私自身、かつては大きな野心を持ちながらも、どこから始めればよいのか分からず、膨大な数のツールや手法に圧倒されていました。
本記事は、そうした方々に向けたロードマップとして執筆しています。シンプルな方法から始めることで、応用に対する理解を深め、将来的には自信を持って複雑な問題に取り組む力を養うことができます。 私たちは、ボリンジャー氏が提唱したオリジナルでシンプルな取引ルールを維持しつつ、無意味な複雑さを導入するのではなく、人間の意思決定プロセスを模倣するために必要な複雑さを補完的に加えることで、本記事で紹介した成果を得ることができました。


取引戦略の概要

取引戦略の可視化

図1:ボリンジャーバンド戦略のイメージ

私たちの取引戦略は、ボリンジャー氏が提唱した取引シグナルに従うことを基本としています。戦略の元々のルールでは、価格がボリンジャーバンドの上限を超えた場合に売り、価格が下限を下回った場合に買うという条件で取引をおこないます。

一般的に言えば、これらのルールは決済条件としても拡張することができます。つまり、価格が上部バンドを上回ったときには、新たに売りポジションを開始するだけでなく、すでに保有している買いポジションも決済するということです。このようなルールを用いることで、ポジションの開閉を自動で判断できる自己管理型の取引システムを構築することが可能になります。 

この取引戦略をGBPUSD(ポンド/米ドル)通貨ペアに対して、2022年1月1日から2024年12月30日までの期間、15分足(M15)チャートでテストします。 


MQL5の始め方

MQL5で取引を開始するには、まずシステム定数を定義することから始めます。たとえば、取引対象のペア、使用するロットサイズ、その他ユーザーには変更してほしくない定数などです。

//+------------------------------------------------------------------+
//|                                 GBPUSD BB Breakout Benchmark.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/ja/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/ja/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define BB_SHIFT  0                         // Our bollinger band should not be shifted
#define SYMBOL    "GBPUSD"                  // The intended pair for our trading system
#define BB_PRICE  PRICE_CLOSE               // The price our bollinger band should work on
#define LOT       0.1                       // Our intended lot size

そこからTradeライブラリをロードします。

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

取引戦略の一部の要素については、最終的にユーザーが設定を調整できるようになっています。たとえば、テクニカル指標の計算に使用する時間足や、ボリンジャーバンドの期間などをユーザーが選択できるようにすることが可能です。

//+------------------------------------------------------------------+
//| User inputs                                                      |
//+------------------------------------------------------------------+
input group "Technical Indicators"
input ENUM_TIMEFRAMES TF = PERIOD_M15;   // Intended time frame
input int    BB_PERIOD   = 30;               // The period for our bollinger bands
input double BB_SD       = 2.0;              // The standard deviation for our bollinger bands

また、プログラム全体で使用するグローバル変数も定義する必要があります。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
int    bb_handler;
double bb_u[],bb_m[],bb_l[];

//+------------------------------------------------------------------+
//| System variables                                                 |
//+------------------------------------------------------------------+
int state;
double o,h,l,c,bid,ask;

取引アプリケーションが初めてロードされたとき、初期化関数を呼び出します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup our system
   if(!setup())
      return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }

私たちのアプリケーションが使用されなくなった場合は、使用していないテクニカル指標を解放しなければなりません。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Release resources we no longer need
   release();
  }

最新の価格情報を受信した際には、その新しい価格データを保存し、取引判断を行うために処理をおこなう必要があります。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Update our system variables
   update();
  }
//+------------------------------------------------------------------+

この関数は、テクニカル指標の設定を担当します。

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Setup our technical indicators and other variables               |
//+------------------------------------------------------------------+
bool setup(void)
  {
//--- Setup our system
   bb_handler = iBands(SYMBOL,TF,BB_PERIOD,BB_SHIFT,BB_SD,BB_PRICE);
   state = 0;

//--- Validate our system has been setup correctly
   if((bb_handler != INVALID_HANDLE) && (Symbol() == SYMBOL))
      return(true);

//--- Something went wrong!
   return(false);
  }

取引アプリケーションを使用しなくなった場合は、選択したテクニカル指標に関連するメモリを解放します。

//+------------------------------------------------------------------+
//| Release the resources we no longer need                          |
//+------------------------------------------------------------------+
void release(void)
  {
//--- Free up system resources for our end user
   IndicatorRelease(bb_handler);
  }

市場から最新の価格情報を受信した際には、グローバル変数を更新し、ポジションを保有していない場合に限り、有効な取引セットアップが存在するかどうかを確認します。

//+------------------------------------------------------------------+
//| Update our system variables                                      |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime timestamp;
   datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0);

   if(timestamp != current_time)
     {
      timestamp = current_time;
      //--- Update our system
      CopyBuffer(bb_handler,0,1,1,bb_m);
      CopyBuffer(bb_handler,1,1,1,bb_u);
      CopyBuffer(bb_handler,2,1,1,bb_l);
      Comment("U: ",bb_u[0],"\nM: ",bb_m[0],"\nL: ",bb_l[0]);

      //--- Market prices
      o = iOpen(SYMBOL,PERIOD_CURRENT,1);
      c = iClose(SYMBOL,PERIOD_CURRENT,1);
      h = iHigh(SYMBOL,PERIOD_CURRENT,1);
      l = iLow(SYMBOL,PERIOD_CURRENT,1);
      bid = SymbolInfoDouble(SYMBOL,SYMBOL_BID);
      ask = SymbolInfoDouble(SYMBOL,SYMBOL_ASK);

      //--- Should we reset our system state?
      if(PositionsTotal() == 0)
        {
         state = 0;
         find_setup();
        }

      if(PositionsTotal() == 1)
        {
         manage_setup();
        }

     }
  }

取引エントリーを見つけるためのルールは、ボリンジャー氏が提唱したオリジナルのルールに基づいています。

//+------------------------------------------------------------------+
//| Find an oppurtunity to trade                                     |
//+------------------------------------------------------------------+
void find_setup(void)
  {
//--- Check if we have breached the bollinger bands
   if(c > bb_u[0])
     {
      Trade.Sell(LOT,SYMBOL,bid);
      state = -1;
      return;
     }

   if(c < bb_l[0])
     {
      Trade.Buy(LOT,SYMBOL,ask);
      state = 1;
     }
  }

先に述べたように、ボリンジャー氏が提供したルールは、ポジションをいつ閉じるべきかを明確に定義する決済ルールとしても活用できます。

//+------------------------------------------------------------------+
//| Manage our open trades                                           |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   if(((c < bb_l[0]) && (state == -1))||((c > bb_u[0]) && (state == 1)))
      Trade.PositionClose(SYMBOL);
  }
//+------------------------------------------------------------------+

まず、M15時間枠を選択することから始めます。こうした短い時間足は、私たちのように金融市場で日々形成されるパターンを利用するスキャルピング戦略に非常に適しています。銘柄はGBPUSDペアを選択し、2022年1月1日から2024年12月30日までテストをおこないます。 

図2:バックテストの時間枠の選択

それでは、テストのパラメータを微調整していきます。「ランダム遅延」を選択すると、市場環境が不安定な状況で取引システムの信頼性を検証できます。また、「実ティックに基づくすべてのティック」を選択したのは、過去の市場データを最も現実的にシミュレーションできるためです。このモデリングモードでは、MetaTrader 5ターミナルが当日にブローカーから送られた実際のティックデータをすべて取得します。この処理はインターネットの速度によって時間がかかることがありますが、最終的にはより現実に近い結果が得られる可能性が高いです。

図3:バックテスト条件の選択

最後に、アプリケーションの動作を制御する設定を定義します。なお、2回目のテストでは、図4で選択した設定をシステム変数で一定に保ちます。したがって、今回テストする現在のバージョンに対して、第2バージョンに不公平な有利さを与えることはありません。

図4:このシングルバックテストにおけるエキスパートアドバイザー(EA)の入力パラメータ

現在のアルゴリズムによって生成された利益曲線は、本質的に不安定です。急激な成長期と過度な損失期を予測できずに繰り返しています。現在の取引戦略のバージョンは、利益を積み上げるというよりも、むしろドローダウンから回復する期間に多くの時間を費やしており、たまに損失を出すという状況です。  これは決して理想的とは言えません。テスト終了時には、アルゴリズムは資本を失う結果となりました。このアルゴリズムを実用化するには、さらなる改良が必要であることは明らかです。 

最初の取引アルゴリズムによるエクイティカーブ

図5:現在のオリジナル取引戦略のバージョンによって生成されたエクイティカーブ

バックテストの結果を詳しく検証すると、取引のうち63%が利益を上げており、勝率は健全であることがわかります。しかし問題は、利益の大きさが損失のほぼ半分にとどまっている点です。オリジナルの取引ルールは変更したくないため、新たな目標は、平均利益の成長を最大限に近づけつつ、損失取引の増加を抑えることにあります。この繊細なバランス調整によって、望ましい結果を得られると考えています。

初期結果の詳細

図6:オリジナルバージョンの取引戦略で得られた結果の詳細分析


初期結果の改善

ご覧のとおり、初期の結果はあまり芳しくありません。しかし、ボリンジャーバンドを考案し、これらの取引ルールを提案した人間のトレーダー、ボリンジャー氏は、いかなる基準で見ても成功したトレーダーでした。では、ボリンジャー氏が作成したルールと、私たちがそのルールに基づいてアルゴリズム的に得た結果との間に、どこにギャップがあるのでしょうか。 

図7:ボリンジャーバンドの発明者、ジョン・ボリンジャー氏

この違いの一部は、ルールの人間による適用方法にあるかもしれません。おそらく、ボリンジャー氏は長年の経験を通じて、自身の戦略がうまく機能する市場状況と、失敗しやすい状況について直感を身につけていたのでしょう。現在の私たちのアプリケーションは、すべての取引において同じリスク量を常にかけており、すべての取引機会を同じように扱っています。しかし、人間のトレーダーは学習した期待値や信頼度の度合いに応じて、リスク量を増減させる裁量を持っています。 

人間のトレーダーは、報われる可能性が最も高いと信じるときにリスクを取ろうとし、与えられたルールに厳格に従うわけではありません。私たちは、元の戦略に加えて、コンピューターにもこうした柔軟性のレベルを持たせたいと考えています。この目標を実現することで、私たちが期待した結果と、これまでに得た結果とのギャップが説明できるかもしれません。そのため、単に将来の価格水準を予測するだけでなく、プロのトレーダーが日々行っている意思決定に近づけるために、複雑さを導入していきます。 

そこで、ロジスティック回帰モデルを構築し、アプリケーションに「信頼度」の感覚を持たせます。モデルのパラメータは、MetaTrader 5ターミナルから取得する過去の市場データを用いて最適化します。MQL5でネイティブ実装しているため、十分なデータがある時間足であれば、どの時間枠でもEAを動作させることが可能です。 

ロジスティック回帰モデルは、現時点で構築できる最もシンプルなモデルの一つです。ロジスティック回帰モデルには様々な形がありますが、ここで扱うモデルは2つのクラスのみを分類するものです。2クラス以上の分類を行いたい読者は、ロジスティック回帰モデルに関する文献をさらに参照されることをお勧めします。

私たちの望む変化を実装し、アプリケーションの意思決定プロセスを人間の意思決定に近づけるために、現在の取引システムにいくつか重要な変更を加えていきます。

提案する変更項目 目的・意図
追加のシステム定数 構築予定の確率モデルやその他新しいシステム部分に対応するため、新たなシステム定数を作成します。
補助的なテクニカル分析  2つの戦略を同時に使うことで、システムの収益性向上を目指します。また、取引開始前にストキャスティクスで確認をおこない、利益の出る取引の確率を高めます。
新たなユーザー入力 新機能の操作をユーザーがコントロールできるように、新たなユーザー入力項目を作成します。
カスタム関数の修正・拡張 これまで構築したカスタム関数を見直し、新たな変数やタスクに対応できるよう修正・拡張します。


はじめに

改良版の取引アプリケーションを構築するにあたり、まずは新しいシステム定数を作成し、提案するすべてのアルゴリズムのバージョン間でテストの一貫性を保つ必要があります。

//+------------------------------------------------------------------+
//|                                 GBPUSD BB Breakout Benchmark.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/ja/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/ja/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define  BB_SHIFT   0                              // Our bollinger band should not be shifted
#define  SYMBOL     "GBPUSD"                       // The intended pair for our trading system
#define  BB_PRICE   PRICE_CLOSE                    // The price our bollinger band should work on
#define  BB_PERIOD  90                             // The period for our bollinger bands
#define  BB_SD      2.0                            // The standard deviation for our bollinger bands
#define  LOT        0.1                            // Our intended lot size
#define  TF    PERIOD_M15                          // Our intended time frame
#define  ATR_MULTIPLE 20                           // ATR Multiple
#define  ATR_PERIOD 14                             // ATR Period
#define  K_PERIOD 12                               // Stochastic K period
#define  D_PERIOD 20                               // Stochastic D period
#define  STO_SMOOTHING 12                          // Stochastic smoothing
#define  LOGISTIC_MODEL_PARAMS 5                   // Total inputs to our logistic model

さらに、ユーザーにロジスティック回帰モデルの機能を操作できるようにしたいと考えています。「fetch」入力は、モデル構築に使用するデータ量を決定します。一般的に、ユーザーが使用したい時間枠が大きくなるほど、利用可能なデータ量は少なくなることに注意してください。一方で、「look_ahead」はモデルがどの程度未来を予測しようとするかを決めます。

//+------------------------------------------------------------------+
//| User inputs                                                      |
//+------------------------------------------------------------------+
input int fetch = 5;                               // How many historical bars of data should we fetch?
input int look_ahead = 10;                         // How far ahead into the future should we forecast?

さらに、アプリケーションには新たなグローバル変数が必要になります。これらの変数は、新しいテクニカル指標のハンドラや、ロジスティック回帰モデルの可変部分を管理する役割を果たします。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
int    bb_handler,atr_handler,stoch_handler;
double bb_u[],bb_m[],bb_l[],atr[],stoch[];
double logistic_prediction;
double learning_rate = 5E-3;

vector open_price  = vector::Zeros(fetch);
vector open_price_old = vector::Zeros(fetch);
vector close_price = vector::Zeros(fetch);
vector close_price_old = vector::Zeros(fetch);
vector high_price  = vector::Zeros(fetch);
vector high_price_old = vector::Zeros(fetch);
vector low_price = vector::Zeros(fetch);
vector low_price_old = vector::Zeros(fetch);

vector target      = vector::Zeros(fetch);
vector coef        = vector::Zeros(LOGISTIC_MODEL_PARAMS);
double max_forecast = 0;
double min_forecast = 0;
double baseline_forecast = 0;

取引システムのほとんどの部分は変わりませんが、拡張が必要な関数や新たに定義すべき関数がいくつかあります。まず最初に編集するのは初期化関数です。取引を開始する前に追加でおこなうべき処理があります。具体的には、ATRとストキャスティクスモデルのセットアップをおこない、さらにsetup_logistic_model()関数を定義する必要があります。

//+------------------------------------------------------------------+
//| Setup our technical indicators and other variables               |
//+------------------------------------------------------------------+
bool setup(void)
  {
//--- Setup our system
   bb_handler = iBands(SYMBOL,TF,BB_PERIOD,BB_SHIFT,BB_SD,BB_PRICE);
   atr_handler = iATR(SYMBOL,TF,ATR_PERIOD);
   stoch_handler = iStochastic(SYMBOL,TF,K_PERIOD,D_PERIOD,STO_SMOOTHING,MODE_EMA,STO_LOWHIGH);
   state = 0;
   higher_state = 0;
   setup_logistic_model();

//--- Validate our system has been setup correctly
   if((bb_handler != INVALID_HANDLE) && (Symbol() == SYMBOL))
      return(true);

//--- Something went wrong!
   return(false);
  }

私たちのロジスティック回帰モデルは、一連の入力を受け取り、現在のxの値に基づいて目的変数がデフォルトクラスに属する確率(0から1の間)を予測します。モデルは、以下の図8に示すシグモイド関数を用いてこれらの確率を計算します。 

たとえば、「ある人の体重と身長から、その人が男性である確率はどれくらいか?」という問題を解くことに興味があるとします。この例では、男性であることがデフォルトクラスです。確率が0.5を超えればその人は男性と判断され、0.5未満であれば女性とみなされます。これが最も単純なロジスティック回帰モデルのバージョンです。ロジスティック回帰モデルには2つ以上のクラスを分類できるバージョンもありますが、今回は扱いません。

上記の図8に示された一般化されたシグモイド関数は、任意の x の値を変換し、図9に示すように0から1の間の出力値を返します。

図9:シグモイド関数の変換の可視化

シグモイド関数を慎重に調整することで、学習データのクラス1に属するすべての観測値に対しては1に近い推定値を、クラス0に属するすべての観測値に対しては0に近い推定値を出せるようになります。このアルゴリズムは最尤推定法として知られています。これらの結果は、勾配降下法というより単純なアルゴリズムを用いて近似することができます。 

以下のコードでは、まず入力データの準備から始めます。始値、高値、安値、終値の変化量を求め、これらをモデルの入力とします。その後、関連する将来の価格変動を記録します。価格が下落した場合はクラス0として記録します。クラス0はデフォルトクラスです。予測値がカットオフポイントを上回る場合、モデルは将来の価格が下落すると予測していることを意味します。逆に予測値がカットオフポイントを下回る場合は、デフォルトクラスが当てはまらない、つまり価格が上昇すると予測していることになります。一般的に、カットオフポイントは0.5が好まれます。

データにラベル付けをした後、すべてのモデル係数を0で初期化し、まずはこれらの係数を使って最初の予測をおこないます。その後、予測と実際のラベルの差を使って係数を修正します。この処理を取得したすべてのバーに対して繰り返します。 

最後に、先ほども述べたように、クラシックな方法ではカットオフポイントとして0.5が好まれますが、金融市場は必ずしも秩序立った環境ではありません。従来の方法ではトレーダーにとって有用な確率を得られなかったため、私はこのクラシックなアルゴリズムを拡張し、さらに調整しました。

具体的には、モデルが予測した最大および最小のオッズを記録し、それらの真の範囲を二分探索することで最適なカットオフポイントを算出する追加のステップを導入しました。金融市場はノイズが多いため、モデルの学習は困難であり、新たな解釈方法を工夫する必要があるかもしれません。この動的カットオフポイントは、私たちの固有のバイアスに依存せずにモデルが判断を下すのに役立ちます。

図10:カットオフポイントを動的に設定する方法を視覚化する

したがって、本ケースにおいては、動的カットオフポイントを上回る確率はデフォルトクラスと解釈され、モデルは「売り」を推奨していることを意味します。一方で、動的カットオフポイントを下回る予測値はその逆であると判断されます。

//+------------------------------------------------------------------+
//| Setup our logistic regression model                              |
//+------------------------------------------------------------------+
void setup_logistic_model(void)
  {
   open_price.CopyRates(SYMBOL,TF,COPY_RATES_OPEN,(fetch + look_ahead),fetch);
   open_price_old.CopyRates(SYMBOL,TF,COPY_RATES_OPEN,(fetch + (look_ahead * 2)),fetch);

   high_price.CopyRates(SYMBOL,TF,COPY_RATES_HIGH,(fetch + look_ahead),fetch);
   high_price_old.CopyRates(SYMBOL,TF,COPY_RATES_HIGH,(fetch + (look_ahead * 2)),fetch);

   low_price.CopyRates(SYMBOL,TF,COPY_RATES_LOW,(fetch + look_ahead),fetch);
   low_price_old.CopyRates(SYMBOL,TF,COPY_RATES_LOW,(fetch + (look_ahead * 2)),fetch);

   close_price.CopyRates(SYMBOL,TF,COPY_RATES_CLOSE,(fetch + look_ahead),fetch);
   close_price_old.CopyRates(SYMBOL,TF,COPY_RATES_CLOSE,(fetch + (look_ahead * 2)),fetch);

   open_price = open_price - open_price_old;
   high_price = high_price - high_price_old;
   low_price = low_price - low_price_old;
   close_price = close_price - close_price_old;

   CopyBuffer(atr_handler,0,0,fetch,atr);

   for(int i = (fetch + look_ahead); i > look_ahead; i--)
     {
      if(iClose(SYMBOL,TF,i) > iClose(SYMBOL,TF,i - look_ahead))
         target[i-look_ahead-1] = 0;
      if(iClose(SYMBOL,TF,i) < iClose(SYMBOL,TF,i - look_ahead))
         target[i-look_ahead-1] = 1;
     }

//Fitting our coefficients
   coef[0] = 0;
   coef[1] = 0;
   coef[2] = 0;
   coef[3] = 0;
   coef[4] = 0;

   for(int i =0; i < fetch; i++)
     {
      double prediction = 1 / (1 + MathExp(-(coef[0] + (coef[1] * open_price[i]) + (coef[2] * high_price[i]) + (coef[3] * low_price[i]) + (coef[4] * close_price[i]))));
      coef[0] = coef[0] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * 1.0;
      coef[1] = coef[1] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * open_price[i];
      coef[2] = coef[2] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * high_price[i];
      coef[3] = coef[3] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * low_price[i];
      coef[4] = coef[4] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * close_price[i];
     }

   for(int i =0; i < fetch; i++)
     {
      double prediction = 1 / (1 + MathExp(-(coef[0] + (coef[1] * open_price[i]) + (coef[2] * high_price[i]) + (coef[3] * low_price[i]) + (coef[4] * close_price[i]))));
      if(i == 0)
        {
         max_forecast = prediction;
         min_forecast = prediction;
        }
      max_forecast = (prediction > max_forecast) ? (prediction) : max_forecast;
      min_forecast = (prediction < min_forecast) ? (prediction) : min_forecast;
     }

   baseline_forecast = ((max_forecast + min_forecast) / 2);

   Print(coef);
   Print("Baseline: ",baseline_forecast);
  }

EAを使用しない場合、リリースする必要があるテクニカル指標がいくつかあります。

//+------------------------------------------------------------------+
//| Release the resources we no longer need                          |
//+------------------------------------------------------------------+
void release(void)
  {
//--- Free up system resources for our end user
   IndicatorRelease(bb_handler);
   IndicatorRelease(atr_handler);
   IndicatorRelease(stoch_handler);
  }

ポジションを設定する条件は基本的にこれまでとほぼ同じですが、当モデルの予測がボリンジャー氏の提唱した取引ルールと一致する場合に限り、その機会に対して倍のリスクを取るように指示します。つまり、その条件下でのみ、ポジションサイズを増やして積極的に取引をおこないます。

//+------------------------------------------------------------------+
//| Find an oppurtunity to trade                                     |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   double open_input = iOpen(SYMBOL,TF,0)   - iOpen(SYMBOL,TF,look_ahead);
   double close_input = iClose(SYMBOL,TF,0) - iClose(SYMBOL,TF,look_ahead);
   double high_input = iHigh(SYMBOL,TF,0)   - iHigh(SYMBOL,TF,look_ahead);
   double low_input = iLow(SYMBOL,TF,0)     - iLow(SYMBOL,TF,look_ahead);
   double prediction = 1 / (1 + MathExp(-(coef[0] + (coef[1] * open_input) + (coef[2] * high_input) + (coef[3] * low_input) + (coef[4] * close_input))));
   Print("Odds: ",prediction - baseline_forecast);
   
//--- Check if we have breached the bollinger bands
   if((c > bb_u[0]) && (stoch[0] < 50))
     {
      
      Trade.Sell(LOT,SYMBOL,bid);
      state = -1;

      if(((prediction - baseline_forecast) > 0))
        {
         Trade.Sell((LOT * 2),SYMBOL,bid);
         Trade.Sell((LOT * 2),SYMBOL,bid);
         state = -1;
        }

      return;
     }

   if((c < bb_l[0]) && (stoch[0] > 50))
     {
      
      Trade.Buy(LOT,SYMBOL,ask);
      state = 1;
      
      if(((prediction - baseline_forecast) < 0))
        {
         Trade.Buy((LOT * 2),SYMBOL,ask);
         Trade.Buy((LOT * 2),SYMBOL,ask);
         state = 1;
        }

      return;
     }
  }

また、利益が出ている場合はストップロスをトレイル(追従)させ、そうでない場合はそのまま維持する仕組みを設けたいと考えています。これにより、利益が出ている際にリスクを減らすことができ、これは人間のトレーダーが常に行っている賢明な手法です。

//+------------------------------------------------------------------+
//| Manage our open positions                                        |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   if(((c < bb_l[0]) && (state == -1))||((c > bb_u[0]) && (state == 1)))
      Trade.PositionClose(SYMBOL);


//--- Update the stop loss
   for(int i = PositionsTotal() -1; i >= 0; i--)
     {
      string symbol = PositionGetSymbol(i);
      if(_Symbol == symbol)
        {
         double position_size = PositionGetDouble(POSITION_VOLUME);
         double risk_factor = 1;
         if(position_size == (LOT * 2))
            risk_factor = 2;
         double atr_stop = atr[0] * ATR_MULTIPLE * risk_factor;
         ulong ticket = PositionGetInteger(POSITION_TICKET);
         double position_price = PositionGetDouble(POSITION_PRICE_OPEN);
         long type = PositionGetInteger(POSITION_TYPE);
         double current_take_profit = PositionGetDouble(POSITION_TP);
         double current_stop_loss = PositionGetDouble(POSITION_SL);
         if(type == POSITION_TYPE_BUY)
           {
            double atr_stop_loss = (bid - (atr_stop));
            double atr_take_profit = (bid + (atr_stop));

            if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0))
              {
               Trade.PositionModify(ticket,atr_stop_loss,current_take_profit);
              }
           }
         else+
            if(type == POSITION_TYPE_SELL)
              {
               double atr_stop_loss = (ask + (atr_stop));
               double atr_take_profit = (ask - (atr_stop));
               if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0))
                 {
                  Trade.PositionModify(ticket,atr_stop_loss,current_take_profit);
                 }
              }
        }
     }
  }
//+------------------------------------------------------------------+

バックテストの期間や時間枠を制御する設定はそのまま維持し、変更すべき変数は選択するEAのみです。この記事の前のセクションで一緒に改良した新しいバージョンのアプリケーションを選択してください。新バージョンを選ぶ際は、設定も同様に変更せずに維持するようにしてください。

図11:選択した設定の有効性を評価するため、2回目のバックテストの時間枠と期間を選択する

いつも通り、ブローカーとの契約に合ったレバレッジ設定を選択するよう注意してください。レバレッジ設定を誤って指定すると、取引アプリケーションの収益性について非現実的な期待を持ってしまう可能性があります。さらに悪いことに、実際の口座のレバレッジ設定がバックテストで使っている設定と異なると、バックテストで得られた結果を再現するのが難しくなります。これはバックテスト時によく見落とされがちな誤りの原因ですので、十分に時間をかけて慎重に設定してください。

図12:バックテストは、バックテスト起動時に選択された設定に敏感である。初回から確実に成功させるように

次に、私たちの取引アプリケーションがロジスティック回帰モデルのパラメータ推定に使用するデータ量と、モデルの予測期間を設定します。ブローカーが提供するデータ量を超えて取得しようとしないでください。そうしないと、アプリケーションが意図した通りに動作しません。また、ご自身のリスク許容度に合った予測期間を設定してください。

例えば、アプリケーションに将来2000ステップ先まで予測させたい場合を考えてみましょう。しかし、M15の時間軸で2000ステップ先は約20日分に相当します。もし人間として、実際にそのくらい先まで見越して取引をおこなっていないのであれば、無理にアプリケーションにそうさせる必要はありません。私たちの目標は、あなたが毎日おこなっているトレーダーとしての行動を模倣するアプリケーションを作ることだということを思い出してください。

2番目のアルゴリズムの入力パラメータ

図13:取引アプリケーションとロジスティック回帰モデルの動作を制御するパラメータ

いよいよテスト結果の最も重要な部分に到達しました。新しいシステムは平均利益が79ドルとなりました。最初に期待していた平均利益は45ドルでした。つまり、現在の期待利益(79ドル)と以前の期待利益(45ドル)の差は34ドルであり、これは元の期待利益に対して約75%の成長を意味します。

一方で、新しい期待損失は-122ドル、初期の期待損失は-81ドルでした。この差は41ドルであり、平均損失の大きさが約50%増加したことに相当します。 したがって、私たちは目標を見事に達成したと言えます。

新しい設定により、利益の伸び率が損失の伸び率を上回ることを確保できました。これこそが、シャープレシオや期待収益率が改善された理由でもあります。初期の取引戦略バージョンでは-791ドルの損失を積み重ねていたのに対し、新しいシステムはアルゴリズムのルールやバックテスト期間を変更することなく、2,274ドルの利益を積み上げました。


図14: 理想を言えば、損失の成長率が0であることが望ましいが、現実の世界は理想的ではない

現在、アルゴリズムが描くエクイティカーブを観察すると、以前に比べて明らかに安定していることがわかります。どの取引戦略も必ずドローダウン期間を経験しますが、重要なのは損失から回復し、最終的に利益を守る能力です。リスクを避けすぎる戦略はほとんど利益を上げられない一方で、リスクを過度に取る戦略は稼いだ利益をあっという間に失ってしまう可能性があります。そこで、私たちはその中間にうまくバランスを取ることに成功しました。


図15:新バージョンの取引アルゴリズムが生み出すエクイティカーブは、当初の結果よりも望ましいものとなっている


結論

取引アプリケーションにおけるリスク管理は、収益性と持続可能な取引を実現するために非常に重要です。この記事では、取引の成功確率が高いと判断した場合に、アプリケーションが自動的にロットを大きくする設計方法を紹介しました。逆に、取引がうまくいかない可能性がある場合は、できるだけ小さなリスクで取引をおこなうようにしています。このような動的なポジションサイズ調整は、利益機会を最大限に活かしつつ、リスクを適切に管理するために不可欠です。確率的なロジスティック回帰モデルを構築することで、アプリケーションが市場の状況を学習し、その情報に基づいて最適なポジションサイズを選択できる一つの有効な方法を示しました。
添付ファイル 詳細
GBPUSD BB Breakout Benchmark 私たちの取引アプリケーションの初期バージョンで、最初のテストでは利益が出なかった
GBPUSD BB Breakout Benchmark V2 同じ取引ルールに基づく洗練されたアルゴリズムだが、勝算があると判断した場合は、インテリジェントにポジションサイズを増やすように設計されている

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

ログレコードをマスターする(第4回):ログをファイルに保存する ログレコードをマスターする(第4回):ログをファイルに保存する
この記事では、基本的なファイル操作と、カスタマイズに対応した柔軟なハンドラの設定方法について紹介します。CLogifyHandlerFileクラスを更新し、ログをファイルに直接書き込むようにします。また、EURUSDで1週間にわたるストラテジーをシミュレーションし、各ティックごとにログを生成して、合計5分11秒のパフォーマンステストを実施します。この結果は今後の記事で比較し、パフォーマンス向上のためにキャッシュシステムの導入もおこなう予定です。
MQL5とMetaTrader 5のインジケーターの再定義 MQL5とMetaTrader 5のインジケーターの再定義
MQL5でインジケーター情報を収集する革新的なアプローチにより、開発者がカスタム入力をインジケーターに渡して即座に計算をおこなえるようになり、より柔軟で効率的なデータ分析が可能になります。この方法は、従来の制約を超えてインジケーターで処理される情報に対する制御性を高めるため、アルゴリズム取引において特に有用です。
逆フェアバリューギャップ取引戦略 逆フェアバリューギャップ取引戦略
逆フェアバリューギャップ(IFVG)とは、価格が過去に特定されたフェアバリューギャップ(FVG)へ回帰した際に、通常想定されるサポートまたはレジスタンスとしての反応を示さず、その水準を無視して通過してしまう現象を指します。このような失敗は、市場の方向性の変調を示すサインである可能性があり、逆張り志向の取引アプローチにおいて優位性をもたらすシグナルとなることがあります。本記事では、MetaTrader 5エキスパートアドバイザー(EA)の戦略として、この逆フェアバリューギャップを定量的に捉え、取引ロジックに組み込むために私が独自に開発したアプローチを紹介します。
プライスアクション分析ツールキットの開発(第9回):External Flow プライスアクション分析ツールキットの開発(第9回):External Flow
本稿では、高度な分析手法として外部ライブラリを活用する、新たなアプローチを紹介します。pandasのようなライブラリは、複雑なデータを処理・解釈するための強力なツールを提供し、トレーダーが市場の動向についてより深い洞察を得られるようにします。このようなテクノロジーを統合することで、生のデータと実用的な戦略との間にあるギャップを埋めることができます。この革新的なアプローチの基盤を築き、テクノロジーと取引の専門知識を融合させる可能性を引き出すために、ぜひご一緒に取り組んでいきましょう。