English Deutsch
preview
古典的な戦略を再構築する(第14回):高確率セットアップ

古典的な戦略を再構築する(第14回):高確率セットアップ

MetaTrader 5 |
120 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

これまでの議論では、移動平均のクロスオーバー戦略を再構築する方法として、2つの移動平均インジケーターの期間を固定することで、戦略に含まれる遅延をある程度制御できることを示しました。さらに、1つの移動平均を始値に、もう1つを終値に適用することで、従来のクロスオーバー戦略よりもはるかに感度の高いバージョンを得ることができました。  この新しいフレームワークによって、従来の戦略では得られなかった一定の保証が得られます。まだその議論を読んでいない読者の方は、以前のその記事がこちらからご覧になれます。

本日はさらに進んで、この再構築した移動平均クロスオーバー戦略から、より高い生産性を引き出すことに意味があるのかを探っていきます。移動平均クロスオーバー戦略とEURUSD市場との関係を慎重にモデル化することにより、私たちの戦略が優れて機能する市場環境と、逆に困難を極める市場環境の違いを明らかにできることを期待しています。最終的な目標は、不利な市場環境を検出した際に取引を停止することを学習する、取引戦略を構築することです。


取引戦略の概要

私たちのコミュニティの多くのメンバーの間では、「高確率セットアップ」を積極的に探して取引するべきだという考えが広く受け入れられています。しかし、実際に「高確率の取引セットアップ」とは何かについての正式な定義はほとんど存在しません。特定の取引セットアップに関連する確率をどのように経験的に測定すればよいのでしょうか。誰に尋ねるかによって、その識別方法や活用方法についての定義は異なるでしょう。

本記事では、これらの問題に対処するために、旧来の定義から脱却し、根拠に基づいた数値的な定義に依拠するアルゴリズム的枠組みを提案します。これにより、取引戦略が自動的に、かつ一貫性を持って、そうした高確率のセットアップを特定し、利益を上げられるようにすることを目的としています。 

私たちが望むのは、自身の取引戦略と、取引対象として選んだ銘柄との関係をモデル化することです。そのためにはまず、MetaTrader 5端末を用いて、市場を完全に記述する市場データと、取引戦略を構成するすべてのパラメータを取得します。
その後、統計モデルを構築し、戦略が利益を生むシグナルを生成するのか、あるいは損失につながる可能性が高いシグナルを生成しているのかを分類します。 

このモデルによって算出された確率が、その特定のシグナルに関連付けられる「確率」となります。こうして、「高確率セットアップ」について、より科学的かつ実証的な観点から語ることができるようになります。それは証拠と関連する市場データに基づいた論理的アプローチです。

この枠組みは、取引戦略に「目的意識」を持たせ、戦略自身が有利だと見込むアクションのみを実行するよう明示的に指示することを可能にします。私たちは、アルゴリズム取引戦略に必要な構成要素を形式化し始めており、それは戦略自身が自らの行動のもっともらしい結果を推定しようとする試みに他なりません。これは、強化学習の理念を、教師あり学習的アプローチで実現しようとする試みとして正しく位置づけられるでしょう。


MQL5を始める

本日の課題は、自分たちの取引戦略と、取引したい銘柄との関係性を学ぶことに焦点を当てています。この目標を達成するために、4つの主要な価格データ(始値、高値、安値、終値)の変化と、2つの移動平均インジケーターの変化を取得します。 

ここで注意すべき点として、データにラベル付けするためには、2つのインジケーターの元の実数値と終値の値も必要になります。最終的に、10列から成るCSVファイルにデータを書き出し、その後、当該銘柄における自分たちの戦略との関係性を学習していきます。各ステップにおいて、この結果を、同じモデルが市場価格を直接予測しようとしたときのパフォーマンスと比較します。

これにより、どのターゲット(予測対象)がより学習しやすいかが分かります。私たちのアプローチの魅力は、どちらのターゲットが予測しやすいかに関わらず、両者ともに「価格がどこへ向かうか」という情報を提供してくれるという点にあります。

/+-------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- Define our moving average indicator
#define MA_PERIOD 3                 //--- Moving Average Period
#define MA_TYPE   MODE_SMA          //--- Type of moving average we have
#define  HORIZON 10

//--- Our handlers for our indicators
int ma_handle,ma_o_handle;

//--- Data structures to store the readings from our indicators
double ma_reading[],ma_o_reading[];

//--- File name
string file_name = Symbol() + " Reward Modelling.csv";

//--- Amount of data requested
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   int fetch = size + (HORIZON * 2);
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);

//---Set the values as series
   CopyBuffer(ma_handle,0,0,fetch,ma_reading);
   ArraySetAsSeries(ma_reading,true);
   CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
   ArraySetAsSeries(ma_o_reading,true);

//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","True Close","True MA C","True MA O","Open","High","Low","Close","MA Close 2","MA Open 2");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   ma_reading[i],
                   ma_o_reading[i],
                   iOpen(_Symbol,PERIOD_CURRENT,i)   - iOpen(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iHigh(_Symbol,PERIOD_CURRENT,i)   - iHigh(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iLow(_Symbol,PERIOD_CURRENT,i)    - iLow(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iClose(_Symbol,PERIOD_CURRENT,i)  - iClose(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   ma_reading[i] - ma_reading[(i + HORIZON)],
                   ma_o_reading[i] - ma_o_reading[(i + HORIZON)]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


データの分析

まず、市場データをJupyter Notebookに読み込み、戦略のパフォーマンスの数値分析を実行できるようにします。

import pandas as pd

どれだけ先の利益や損失を予測するかを定義します。

HORIZON = 10

データを読み込み、必要な列を追加します。最初の列「Target」は従来のマーケットリターンであり、この場合は10日間のEURUSDのマーケットリターンです。「Class」列は、強気の日には1、それ以外は0です。  3番目の「Action」列は、私たちの取引戦略が引き起こしたアクションを示しており、1は買い、-1は売りを意味します。「Reward」列は、「Target」列と「Action」列の要素ごとの積として計算されます。この掛け算は、次の条件を満たす場合にのみ正の報酬を生み出します。

  • Actionが「-1」で、Targetが0未満(戦略が売りを選び、その後価格が下落した)
  • Actionが「1」で、Targetが0を超えた(戦略が買いを選び、その後価格が上昇した)
他のあらゆるActionとTargetの組み合わせは、誤った行動となるため負の報酬を生み出します。もし分類器が報酬が正になるかどうかを予測できれば、好ましくない市場状況を回避し、最も利益が出やすいタイミングで取引をおこなうための非常に信頼性の高いツールを見つけたことになります。最後の列「Trade Signal」は初期値として0に設定されており、報酬が正の場合にのみ1に設定されます。最初は、これら5つの列すべてが0に設定されます。

data = pd.read_csv("..\EURUSD Reward Modelling.csv")

data['Target'] = 0
data['Class']  = 0
data['Action'] = 0
data['Reward'] = 0
data['Trade Signal'] = 0

ここで、従来のターゲットであるEURUSD終値の10日間の変化を入力します。

data['Target'] = data['True Close'].shift(-HORIZON) - data['True Close']
data.dropna(inplace=True)

クラスにラベルを付けて、グラフ内で強気の日と弱気の日を素早く区別できるようにする必要があります。

data.loc[data['Target'] > 0,'Class'] = 1

ここで、私たちの戦略がどのようなアクションをとったかを記入してみましょう。

data.loc[data['True MA C'] > data['True MA O'],'Action'] = 1
data.loc[data['True MA C'] < data['True MA O'],'Action'] = -1

そして、それらの行動をとった場合の私たちの戦略による利益または損失です。

data['Reward'] = data['Target'] * data['Action']

ここで、行動を起こすべきか待つべきかを示す取引シグナルを入力しましょう。

data.loc[((data['Target'] < 0) & (data['Action'] == -1)),'Trade Signal'] = 1
data.loc[((data['Target'] > 0) & (data['Action'] == 1)),'Trade Signal'] = 1

データを確認してみましょう。 市場の動きを明確に分類するのが難しいことがすぐに分かります。オレンジの点は本来取るべきだった売買シグナルを、青の点は無視すべきだったシグナルを表しています。この散布図でオレンジと青の点が重なって見えるという事実は、数学的に見ても、ほぼ同じ条件下で利益の出るシグナルと損失を生むシグナルが形成される可能性があることを示しています。私たち人間では気づけない、あるいは非常に多くの労力を必要とするような微細な違いや共通点を、統計モデルであれば見つけ出せるかもしれません。

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

sns.scatterplot(data=data,x='MA Close 2',y='MA Open 2',hue='Trade Signal')
plt.title('Analyzing How Well Moving Average Cross Overs Separate The Market')
plt.grid()

図1:移動平均のクロスオーバーでは価格の動きをうまく分類できない傾向が見られる

新しいターゲットが従来のターゲットよりも予測しやすいかどうかを簡単に評価してみましょう。 

from sklearn.linear_model import RidgeClassifier
from sklearn.model_selection import TimeSeriesSplit,cross_val_score

線形モデルは、信頼性が高く、かつ低コストで近似を得られる点で特に有用です。ここでは、時系列分割オブジェクトを作成して、線形分類器の交差検証をおこないましょう。

tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)
model = RidgeClassifier()
scores = []

まずは従来のターゲットでモデルを交差検証し、その後、私たちの戦略によって生成される損益を推定する新しいターゲットでモデルを検証します。今回は分類タスクであるため、スコアリング指標は「accuracy」に設定してください。

scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Class'],cv=tscv,scoring='accuracy'))))
scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Trade Signal'],cv=tscv,scoring='accuracy'))))

バーグラフで結果を可視化すると一目瞭然です。戦略の損益を予測するモデルの方が、市場そのものを直接予測しようとするモデルよりも優れた結果を示しています。市場を直接予測するモデルは、50%の正解率を下回っており、一方で新しいターゲットに基づいたモデルは、かろうじてではありますが、50%の基準を超える結果を出しています。

sns.barplot(scores,color='black')
plt.axhline(np.max(scores),linestyle=':',color='red')
plt.title('Forcasting Market Returns vs Forecasting Strategy Reward')
plt.ylabel('Percentage Accuracy Levels %')
plt.xlabel('0: Market Return Forecast | 1: Strategy Profit/Loss Forecast')

図2:線形モデルは、戦略の損益を予測する方が有効である可能性を示唆している

私たちは、得られたパフォーマンスの正確な向上率を計算できます。その結果、市場を直接予測するよりも、戦略と市場の関係を予測することで約7.6%の精度向上が得られていることがわかりました。

scores = (((scores / scores[0]) - 1) * 100)

scores[1]

7.595993322203687

バックテスト期間と重複するすべてのデータを削除して、バックテストが実際の市場状況の実質的なシミュレーションを表すようにします。

#Drop all the data that overlaps with your backtest period
data = data.iloc[:-((365 * 4) + (30 * 5) + 17),:]
data

図3:データフレームの日付がバックテストの対象となる日付と重ならないようにする

線形モデルは、戦略によって生み出される損益を予測するほうが、価格を直接予測するよりも有効である可能性を示してくれました。しかし、取引のバックテストでは、より柔軟な学習モデルを使用して、モデルができる限り多くの有益な情報を捉えられるようにします。

from sklearn.ensemble import GradientBoostingRegressor

model = GradientBoostingRegressor()

入力とターゲットにラベルを付けます。

X = ['Open','High','Low','Close','MA Close 2','MA Open 2']
y = 'Trade Signal'

モデルを適合します。

model.fit(data.loc[:,X],data.loc[:,y])

モデルをONNXにエクスポートする準備をします。

import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

モデルの入力サイズを定義します。

initial_types = [("float input",FloatTensorType([1,6]))]

モデルを保存します。

onnx_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12)
onnx.save(onnx_proto,"EURUSD Reward Model.onnx")


MQL5の始め方

これで、取引アプリケーションの開発を開始する準備が整いました。統計モデルの学習手順に対する変更の効果を評価するために、2つのバージョンの戦略を構築します。両バージョンの取引アルゴリズムは、同一の条件下でバックテストを実施します。重複した情報を避けるために、まずはこれらの条件について理解を深めておきましょう。 

最初の重要な設定は銘柄です。先述の通り、この例ではEURUSDペアを取引します。日足で、2020年1月1日から2025年4月1日までの5年間にわたり取引をおこないます。これにより、アプリケーションの有効性を十分に検証できる長期間のデータを扱うことができます。図3を思い出してください。2019年12月29日以降の市場データは実質的に消去しました。

図4:両方のバージョンの取引戦略のバックテストに必要な日付

最後に、市場をモデル化する際の条件は、実際の取引の予測不可能な性質を模倣するように設定します。そのため、遅延はランダムに設定し、すべてのティックは実際のティックデータに基づくものとすることで、より現実的な市場の再現を目指しています。 

図5:市場の状況を模倣するために使用する設定は非常に重要である

これまで提案してきた学習手法の改良点が有効かどうかを検証するために、バックテスト可能なアプリケーションの構築を始めましょう。まずは、高確率セットアップを識別するために新たに考案したモデリング手法を使わずに、戦略のパフォーマンスを測定します。要するに、最初のバージョンの取引戦略は、移動平均のクロスオーバーが発生した際に裁量的に取引をおこなう、従来のシンプルな戦略を適用するだけです。これにより、提案する報酬モデリング戦略と比較するためのベンチマークを得ることができます。取引ライブラリをインポートすることから始めます。

//+------------------------------------------------------------------+
//|                                             Reward Modelling.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"

//+------------------------------------------------------------------+
//| Libraries we need                                                |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

便利なシステム定数を定義します。これらの定数は、MQL5スクリプトとPythonスクリプトの両方で使用している他の定数と一致しています。

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 3                //--- Moving Average Period
#define MA_TYPE   MODE_SMA         //--- Type of moving average we have
#define HORIZON 10                 //--- How far into the future we should forecast

グローバル変数とテクニカル指標を設定します。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int fetch = HORIZON + 1;

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
int ma_handle,ma_o_handle;
double ma_reading[],ma_o_reading[];
int position_timer;

各イベントハンドラには、それがトリガーされた際に呼び出される対応するメソッドがペアとして紐づけられています。このような設計スタイルを採用することで、将来的に新しいアイデアが浮かんだ際にも、コードベースの保守性や拡張性を高く保つことができます。イベントハンドラ内に直接コードを書いてしまうと、変更時に多くのコードを注意深く読み解く必要があり、意図せず何かを壊してしまうリスクが高まります。一方、私たちの設計パターンでは、開発者は既存の処理の上に自身の関数呼び出しを追加するだけで、簡単に機能を拡張できます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!setup())
      return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }
  
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   release();
  }
  
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

それでは、各メソッドについて順番に見ていきましょう。最初に設定する関数は、テクニカル指標の読み込みと、ポジションタイマーのリセットを担当する関数です。このポジションタイマーは、システム定数であるHORIZENが指定された期間、各取引を保持し続けるために必要となります。

//+------------------------------------------------------------------+
//| Setup the system                                                 |
//+------------------------------------------------------------------+
bool setup(void)
  {
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);
   position_timer = 0;

   return(true);
  }

releaseメソッドは、使用していないテクニカル指標を単に解放するだけです。MQL5では、自分で使用したものを後始末するのが良い習慣です。

//+------------------------------------------------------------------+
//| Release system variables we are no longer using                  |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(ma_handle);
   IndicatorRelease(ma_o_handle);
   return;
  }

updateメソッドは現在の価格レベルを取得し、それらをインジケータバッファにコピーします。さらに、現在のポジションがどれくらいの時間開かれているかを追跡し、適切なタイミングでクローズできるようにします。

//+------------------------------------------------------------------+
//| Update system parameters                                         |
//+------------------------------------------------------------------+
void update(void)
  {
   //--- Time stamps
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_D1,0);

   //--- We are on a new day
   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      if(PositionsTotal() == 0)
        {
         //--- Copy indicator values
         CopyBuffer(ma_handle,0,0,fetch,ma_reading);
         CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
         //---Set the values as series
         ArraySetAsSeries(ma_reading,true);
         ArraySetAsSeries(ma_o_reading,true);
         find_setup();
         position_timer = 0;
        }

      //--- Forecasts are only valid for HORIZON days
      if(PositionsTotal() > 0)
        {
         position_timer += 1;
        }

      //--- Otherwise close the position
      if(position_timer == HORIZON)
         Trade.PositionClose(Symbol());
     }
     return;
  }

そして最後に、find_setup関数です。私たちのセットアップは、移動平均のクロスオーバーが発生したときに識別されます。始値が終値を上回ってクロスした場合は、ショートシグナルとして登録されます。そうでない場合は、ロングシグナルとなります。

//+------------------------------------------------------------------+
//| Find a trading oppurtunity                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
         double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID) , ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         double vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);

         vector ma_o,ma_c;

         ma_o.CopyIndicatorBuffer(ma_o_handle,0,0,1);
         ma_c.CopyIndicatorBuffer(ma_handle,0,0,1);

         if(ma_o[0] > ma_c[0])
           {
            Trade.Sell(vol,Symbol(),ask,0,0,"");
           }

         if(ma_o[0] < ma_c[0])
           {
            Trade.Buy(vol,Symbol(),bid,0,0,"");
           }
     return;
  }    

先ほど定義したシステム定数を未定義にすることを忘れないでください。

//+------------------------------------------------------------------+
//| Undefine system constatns                                        |
//+------------------------------------------------------------------+
#undef HORIZON
#undef MA_PERIOD
#undef MA_TYPE

直前に、バックテストで使用する設定についてはすでに説明しました。私たちの目的は、開発した統計モデリング手法を用いずに、戦略の有効性を観察することです。エキスパートアドバイザー(EA)を読み込み、図4および図5で説明した設定を使ってバックテストを開始します。

図6:ベンチマークの確立

裁量取引戦略は利益を上げてはいるものの、その利益はわずかです。平均利益と平均損失の差はわずか0.9ドルで、全取引のうち利益が出ているのはわずか51%にすぎません。これはあまり心強い結果とは言えません。シャープレシオは0.62であり、システムを見直すことで改善の余地があるかもしれません。

図7:裁量取引戦略のパフォーマンスの詳細な分析

このバージョンのトレーディング戦略によって生成された残高曲線および資産曲線を分析すると、その欠陥は一目瞭然です。戦略は不安定で変動が大きく、実際、バックテスト開始から4年後の2024年2月には、ほぼ初期の残高に戻ってしまっています。2020年後半から始まり、2024年まで4年間も続いた損失の連鎖から抜け出すのに苦しみました。これはアルゴリズムトレーダーとして、魅力的とは言えない結果です。 

図8:裁量的取引戦略によって生成された損益曲線を視覚化する


EAパフォーマンスの向上

さて、次に取引戦略を改善しましょう。アプリケーションに、人間の思考プロセスのように、自分の行動の結果を考慮してから決断する能力を持たせます。 

まずは、ONNXモデルをシステムのファイルディレクトリからリソースとしてアプリケーションにインポートすることから始めます。 

//+------------------------------------------------------------------+
//| System resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Reward Model.onnx" as uchar onnx_proto[];

モデルを活用するために、いくつかの追加のグローバル変数が必要になります。主に、モデルハンドラを表す変数と、取得すべきデータ量を示す変数が必要です。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long onnx_model;
int fetch = HORIZON + 1;

次に、setup関数でONNXモデルを設定する必要があります。以下のコード例では、アプリケーションの両バージョンで変更のないコード部分はあえて省略しています。ONNXモデルはバッファから生成し、モデルの検証をおこない、その後に入力サイズと出力サイズをそれぞれ指定しています。途中のいずれかのステップで失敗した場合は、初期化処理を完全に中止します。

//+------------------------------------------------------------------+
//| Setup the system                                                 |
//+------------------------------------------------------------------+
bool setup(void)
  {
//---Omitted code that hasn't changed

//--- Setup the ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_proto,ONNX_DEFAULT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create ONNX model");
      return(false);
     }

//--- Register the ONNX model I/O parameters
   ulong input_shape[] = {1,6};
   ulong output_shape[] = {1,1};

   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Comment("Failed to set input shape");
      return(false);
     }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Comment("Failed to set output shape");
      return(false);
     }

   return(true);
  }

さらに、アプリケーションを終了するときには、もはや使用していないシステムリソースを解放する必要があります。

//+------------------------------------------------------------------+
//| Release system variables we are no longer using                  |
//+------------------------------------------------------------------+
void release(void)
  {
//--- Omitted code segments that haven't changed
   OnnxRelease(onnx_model);
   return;
  }

アプリケーションには、取引をおこなうかどうか判断する前に、まず取引モデルから予測を取得するよう指示します。モデルの予測値が0.5を超えていれば、戦略によって生成されたシグナルが利益をもたらすとアルゴリズムが期待していることを意味し、取引を許可します。そうでなければ、不利な市場環境が自然に解消するのを待ちます。

//+------------------------------------------------------------------+
//| Find a trading oppurtunity                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
//--- Skipped parts of the code base that haven't changed

//--- Prepare the model's inputs
   vectorf model_input(6);
   model_input[0] = (float)(iOpen(_Symbol,PERIOD_CURRENT,0)   - iOpen(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[1] = (float)(iHigh(_Symbol,PERIOD_CURRENT,0)   - iHigh(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[2] = (float)(iLow(_Symbol,PERIOD_CURRENT,0)    - iLow(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[3] = (float)(iClose(_Symbol,PERIOD_CURRENT,0)  - iClose(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[4] = (float)(ma_reading[0] - ma_reading[(HORIZON)]);
   model_input[5] = (float)(ma_o_reading[0] - ma_o_reading[(HORIZON)]);

//--- Prepare the model's output
   vectorf model_output(1);

//--- We failed to run the model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_input,model_output))
      Comment("Failed to obtain a forecast");

//--- Everything went fine
   else
     {
      Comment("Forecast: ",model_output[0]);

      //--- Our model forecasts that our strategy is likely to be profitable
      if(model_output[0] > 0.5)
        {
         if(ma_o[0] > ma_c[0])
           {
            Trade.Sell(vol,Symbol(),ask,0,0,"");
           }

         if(ma_o[0] < ma_c[0])
           {
            Trade.Buy(vol,Symbol(),bid,0,0,"");
           }
        }
     }
   return;
  }

図4と5に指定された設定を使用してバックテストでアプリケーションを実行し、2つの戦略を公平に比較します。

バックテストの入力

図9:利益と損失をモデル化した改良版取引戦略による第2回バックテストの結果

裁量ベンチマークで同じ戦略を適用した際のシャープレシオは0.62でしたが、現在の改良版では1.07に上昇し、これは72%の増加を示しています。新しいアルゴリズムで定義された「高確率セットアップ」戦略に従った場合、総純利益は117.13ドルから162.75ドルへ38%増加しました。負けトレードの割合は48.78%から40.48%へ17%減少しています。さらに、戦略によっておこなわれた総取引数は164回から126回に減少しました。つまり、新しいシステムは旧システムの取引数の76%でありながら38%多くの利益を上げており、リスクを抑えつつより大きなリターンを得られているため、以前よりも効果的であると言えます。

図10:新しい取引戦略のパフォーマンスの詳細な要約

改良版取引戦略の残高曲線と資産曲線を読者に示し、その下に元の戦略によって生成された曲線も配置しました。これにより、読者は前後にスクロールすることなく両者を比較できます。最初のバックテスト年が終わる前に、元の戦略はほぼ損益分岐点に戻っていたのに対し、新しい戦略はその期間をうまく乗り切っていることが明確に分かります。 

興味深いことに、両方の戦略とも2022年5月から12月の期間にはパフォーマンスに苦戦している点が見られます。これは特に不安定な市場環境の兆候かもしれず、効果的に対処するにはさらなる工夫が必要だと考えられます。

図11:行動の結果を考慮する能力を持つ新しい取引戦略によって生成された損益曲線

図12:元の取引戦略による資産曲線(新しい結果との比較用にコピー)


結論

この記事を読んだ読者は、教師あり統計モデルを用いてアルゴリズム取引に取り組む新しい方法を学びました。読者は自身の取引戦略と取引対象の市場との関係をモデル化するために必要な知識を身につけています。これにより、市場を直接予測しようとする一般的な参加者に対して競争優位を得ることができるのです。私たちは、市場を直接予測することが必ずしも最良の選択肢ではないことを示しました。 

ファイル名  ファイルの説明
Reward Modelling Benchmark.mq5 行動の結果を考慮しようとしない、従来版の取引戦略。すべての取引機会を均等に重み付けし、各取引は必ず利益を生むものと想定します。
Reward Modelling.mq5 取引をおこなう前にその行動の結果を明示的に予測しようとする、改良版の取引戦略
EURUSD Reward Model.onnx 戦略によって生成されたシグナルが利益をもたらす可能性を推定するONNX統計モデル
Reward Modelling.ipynb 過去の市場データを分析し、統計モデルを適合させるために使用したJupyter Notebook

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

MQL5で取引管理者パネルを作成する(第10回):外部リソースベースのインターフェイス MQL5で取引管理者パネルを作成する(第10回):外部リソースベースのインターフェイス
本日は、MQL5の機能を活用して、BMP形式の画像などの外部リソースを利用し、トレーディング管理パネル用に独自のスタイルを持ったホームインターフェイスを作成します。ここで紹介する手法は、画像やサウンドなど複数のリソースを一括でパッケージ化して配布する際に特に有効です。このディスカッションでは、こうした機能をどのように実装し、New_Admin_Panel EAにおいてモダンで視覚的に魅力的なインターフェイスを提供するかを一緒に見ていきましょう。
MQL5経済指標カレンダーを使った取引(第7回):リソースベースのニュースイベント分析による戦略テストの準備 MQL5経済指標カレンダーを使った取引(第7回):リソースベースのニュースイベント分析による戦略テストの準備
この記事では、MQL5の取引システムをストラテジーテスターでの検証に対応するため、経済指標カレンダーのデータをリソースとして埋め込み、ライブ環境ではないテスト分析に活用する方法を解説します。イベントの読み込みと、時間・通貨・影響度に基づくフィルタリングを実装し、最終的にストラテジーテスター内でその動作を検証します。これにより、ニュースに基づいた戦略の効果的なバックテストが可能になります。
MQL5でのカスタム市場レジーム検出システムの構築(第1回):インジケーター MQL5でのカスタム市場レジーム検出システムの構築(第1回):インジケーター
この記事では、自己相関やボラティリティなどの統計手法を用いたMQL5市場レジーム検出システム(Market Regime Detection System)の作成方法を詳述しています。トレンド相場、レンジ相場、ボラティリティの高い相場を分類するためのクラスや、カスタムインジケーターのコードも提供しています。
初心者からエキスパートへ:ローソク足のプログラミング 初心者からエキスパートへ:ローソク足のプログラミング
この記事では、MQL5プログラミングの第一歩を、完全な初心者でも理解できるように解説します。よく知られているローソク足パターンを、実際に機能するカスタムインジケーターへと変換する方法を紹介します。ローソク足パターンは、実際の価格変動を反映し、市場の転換を示唆するため、非常に有用です。チャートを目視で確認してパターンを探す手法ではミスや非効率が生じやすいため、この記事では、パターンを自動的に識別・ラベル付けしてくれるインジケーターを作成する方法を説明します。その過程で、インデックス(索引)、時系列、ATR(市場の変動性に応じた精度向上のため)などの重要な概念についても解説し、今後のプロジェクトで再利用可能なカスタムローソク足パターンライブラリの開発にも触れていきます。