English
preview
古典的な戦略をPythonで再構築する:MAクロスオーバー

古典的な戦略をPythonで再構築する:MAクロスオーバー

MetaTrader 5トレーディングシステム | 20 8月 2024, 12:58
25 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

はじめに

今日の取引戦略の多くは、まったく異なる市場環境で考案されたものです。アルゴリズムが支配する現代の市場において、その妥当性を評価することは極めて重要です。この記事では、移動平均クロスオーバー戦略について掘り下げ、今日の金融環境におけるその有効性を評価します。

この記事では、以下のことを取り上げます。

  • 戦略の継続的な使用を裏付ける定量的証拠はあるか
  • 直接価格分析と比較して、この戦略にはどのような利点があるか
  • 現代のアルゴリズム取引の中で、この戦略は今も有効に機能しているのか
  • 戦略の精度を向上させる指標は他にあるか
  • 移動平均クロスオーバーを事前に予測するためにAIを効果的に活用することは可能か

移動平均クロスオーバーの手法は、数十年にわたって幅広く研究されてきました。トレンドと売買シグナルを検出するためにこれらの平均を使用するという基本的な概念は、テクニカル分析の主力でしたが、その正確な起源はまだ不明です。

移動平均クロスオーバー戦略では、通常、期間の異なる2つの移動平均を使用しますが、極めて重要な条件は、一方の期間が他方の期間よりも長いことです。短期間移動平均線が長期間移動平均線を上回ると、強気トレンドの可能性が示唆され、その逆の場合は弱気トレンドの可能性が示唆されます。

テクニカルアナリストは何十年もの間、この戦略を、エントリポイントやエグジットポイントを特定したり、市場のセンチメントを測ったり、その他さまざまな用途に活用してきました。現在の有効性を判断するため、この戦略を最新の定量的テストにかけます。ここでのアプローチの詳細は以下の通りです。


移動平均クロスオーバー

図1:CADJPYペアで適用されている移動平均クロスオーバーの例

概要

私たちは、MetaTrader 5端末とPython環境をリンクさせるエキサイティングな旅に出ようとしています。まず、2020年1月1日から2024年6月25日までのEURUSDペアのM15データを要求します。この広範なデータセットによって、最近の市場の挙動を包括的に見ることができます。

次のステップは、2つの目標を設定することです。1つ目は、ベースラインとなる直接価格変動の予測精度を測定します。このベンチマークは、移動平均クロスオーバーを予測する際に、どの程度うまくいくかを比較するのに役立ちます。その過程で、さらにテクニカル指標を探し、精度を高めていきます。最後に、移動平均クロスオーバーを予測するための重要な変数を特定するために、コンピュータモデルに依頼します。モデルが、私たちが使用した2つの移動平均を優先しないなら、それは私たちの最初の仮定が間違っていたことを示しているかもしれません。

数字に飛び込む前に、考えられる結果を考えてみましょう。

  1. 直接価格予測の優位性:価格変動を直接予測する方が移動平均クロスオーバーよりも精度が高いか同等である場合、クロスオーバーを予測しても余分な利点がない可能性が示唆され、戦略の有効性が疑問視されます。

  2. クロスオーバー予測の優位性:移動平均クロスオーバーの予測精度が高まれば、予測をさらに強化するために、より多くのデータを探し求める動機付けとなり、戦略の潜在的な価値が浮き彫りになるでしょう。

  3. 移動平均の無意味さ:もし私たちのモデルがクロスオーバーを予測する上でどちらの移動平均線が重要であるかを特定できないのであれば、それは他の変数の方がより重要である可能性を示唆しており、2つの移動平均線の間に想定された関係が成立していないことを示唆しています。

  4. 移動平均の妥当性:移動平均の一方または両方がクロスオーバーの予測に重要であると判定された場合、両者の間に実質的な関係があることが確認され、情報に基づいた予測のための信頼できるモデルを構築することができます。

この分析は、取引戦略において移動平均クロスオーバーを使用することの長所と短所を理解し、より効果的な予測方法へと導いてくれるでしょう。


実験:移動平均クロスオーバーはまだ信頼できるか?

まず、必要な標準Pythonライブラリをインポートすることから始めましょう。

import pandas as pd
import pandas_ta as ta
import numpy as np
import MetaTrader5 as mt5
from   datetime import datetime
import seaborn as sns
import time

次に、ログイン情報を入力します。

account = 123436536
password = "Enter Your Password"
server = "Enter Your Broker"

次に、取引口座へのログインを試みます。

if(mt5.initialize(login=account,password=password,server=server)):
    print("Logged in succesfully")
else:
    print("Failed to login")

ログイン成功です。

次に、いくつかのグローバル変数を定義します。

timeframe = mt5.TIMEFRAME_M15
deviation = 1000
volume = 0
lot_multiple = 10
symbol = "EURUSD"

次に、取引したい銘柄の市場データを取得します。

#Setup trading volume
symbols = mt5.symbols_get()
for index,symbol in enumerate(symbols):
    if symbol.name == "EURUSD":
        print(f"{symbol.name} has minimum volume: {symbol.volume_min}")
        volume = symbol.volume_min * lot_multiple

EURUSDの最小出来高は0.01です。

それでは、訓練データを取得する準備をしましょう。

#Specify date range of data to be modelled
date_start = datetime(2020,1,1)
date_end = datetime.now()

次に、どのくらい先の未来を予測したいかを定義します。

#Define how far ahead we are looking
look_ahead = 20

次に、MetaTrader 5端末から市場データを取得し、データにラベルを付けます。ここでのラベル付け方式では、上昇には1を、下降には0を使用します。 

#Fetch market data
market_data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,date_start,date_end))
market_data["time"] = pd.to_datetime(market_data["time"],unit='s')
#Add simple moving average technical indicator
market_data.ta.sma(length=5,append=True)
#Add simple moving average technical indicator
market_data.ta.sma(length=50,append=True)
#Delete missing rows
market_data.dropna(inplace=True)

#Add a column for the target
market_data["target"] = 0
market_data["close_target"] = 0

#Encoding the target
ma_cross_conditions = [
    (market_data["SMA_5"].shift(-look_ahead) > market_data["SMA_50"].shift(-look_ahead)),
    (market_data["SMA_5"].shift(-look_ahead) < market_data["SMA_50"].shift(-look_ahead))
]
#Encoding pattern
ma_cross_choices = [
    #Fast MA above Slow MA
    1,
    #Fast MA below Slow MA
    0
]

price_conditions = [
    (market_data["close"] > market_data["close"].shift(-look_ahead)),
    (market_data["close"] < market_data["close"].shift(-look_ahead))
]

#Encoding pattern
price_choices = [
    #Price fell
    0,
    #Price rose
    1
]

market_data["target"] = np.select(ma_cross_conditions,ma_cross_choices)
market_data["close_target"] = np.select(price_conditions,price_choices)

#The last rows do not have answers
market_data = market_data[:-look_ahead]
market_data


市場データを使用したデータフレーム

図2:市場データを使用した現在のデータフレーム

これから必要な機械学習ライブラリをインポートします。

#XGBoost
from xgboost import XGBClassifier
#Catboost
from catboost import CatBoostClassifier
#Random forest
from sklearn.ensemble import RandomForestClassifier
#LDA and QDA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis , QuadraticDiscriminantAnalysis
#Logistic regression
from sklearn.linear_model import LogisticRegression
#Neural network
from sklearn.neural_network import MLPClassifier
#Time series split
from sklearn.model_selection import TimeSeriesSplit
#Accuracy metrics
from sklearn.metrics import accuracy_score
#Visualising performance
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.model_selection import learning_curve

データセットに対して時系列分割をおこなう準備をします。

#Time series split
splits = 10
gap = look_ahead
models = ["Logistic Regression","Linear Discriminant Analysis","Quadratic Discriminant Analysis","Random Forest Classifier","XGB Classifier","Cat Boost Classifier","Neural Network Small","Neural Network Large"]

多くの異なるモデルの精度を評価し、各モデルから得られた精度を1つのデータフレームに格納します。1つ目のデータフレームは移動平均クロスオーバーを予測するときの精度を保存し、2つ目のデータフレームは価格の変化を直接予測するときの精度を測定します。

error_ma_crossover = pd.DataFrame(index=np.arange(0,splits),columns=models)
error_price = pd.DataFrame(index=np.arange(0,splits),columns=models)

次に、各モデルの精度を測定しますが、その前に、モデルが使用する入力を定義しなければなりません。

predictors = ["open","high","low","close","tick_volume","spread","SMA_5","SMA_50"]

各モデルの精度を測定するために、データセットの一部でモデルを訓練し、訓練中に見なかった残りのデータセットでモデルをテストします。TimeSeriesSplitライブラリはデータフレームを分割してくれるので、このプロセスが簡単になります。

tscv = TimeSeriesSplit(n_splits=splits,gap=gap)
#Training each model to predict changes in the moving average cross over
for i,(train,test) in enumerate(tscv.split(market_data)):
    model = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1,early_stopping=True)
    model.fit( market_data.loc[train[0]:train[-1],predictors] , market_data.loc[train[0]:train[-1],"target"] ) 
    error_ma_crossover.iloc[i,7] = accuracy_score(market_data.loc[test[0]:test[-1],"target"],model.predict(market_data.loc[test[0]:test[-1],predictors]))

#Training each model to predict changes in the close price
for i,(train,test) in enumerate(tscv.split(market_data)):
    model = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1,early_stopping=True)
    model.fit( market_data.loc[train[0]:train[-1],predictors] , market_data.loc[train[0]:train[-1],"close_target"] ) 
    error_price.iloc[i,7] = accuracy_score(market_data.loc[test[0]:test[-1],"close_target"],model.predict(market_data.loc[test[0]:test[-1],predictors]))

まず、価格の変化を直接予測する際の精度を測るデータフレームを見てみましょう。

error_price

価格予測誤差

図3:価格の変動を直接予測する際の精度

次に進む前に、結果を解釈してみましょう。まず言えることは、どのモデルもこのタスクでうまく機能していないということです。いくつかのモデルは、価格を直接予測したときの精度が50%以下でした。この成績はひどいもので、単にランダムに当てるだけで、これらのモデルと同等かそれ以下の成績が出せたことを意味します。モデルは、左が単純なロジスティック回帰、右がディープニューラルネットワークと、複雑さが増す順に並んでいます。観察できるように、モデルの複雑さを増やしても、価格を直接予測するときの精度は上がりませんでした。代わりに移動平均クロスオーバーを予測した場合に改善が見られるかどうかを見てみましょう。

error_ma_crossover

移動平均クロスオーバーの予測誤差

図4:移動平均クロスオーバーの予測精度

上のデータフレームでわかるように、線形判別分析(LDA)はこのタスクで非常に良い結果を出しました。調査したモデルの中では、大差をつけて最高のパフォーマンスでした。さらに、LDAの改善されたモデルパフォーマンスと、最初のタスクのパフォーマンスの低さを対比すると、移動平均クロスオーバーは、価格の直接的な変化よりも予測の信頼性が高い可能性があることがはっきりとわかります。このような場合、移動平均クロスオーバーを予測することの利点に異論はないでしょう。

結果の可視化

上記の結果を可視化してみましょう。 

結果の可視化

図5:得られた結果を可視化する

LDAアルゴリズムの改善は、箱ひげ図に顕著に表れており、私たちのモデルによる学習の重要性を示しています。さらに、ロジスティック回帰のパフォーマンスがわずかではありますが顕著に向上しました。注目すべきは、移動平均クロスオーバーを予測する際、LDAは一貫して箱ひげ図に密にクラスタ化されたスコアを出し、望ましい精度と一貫性を示したことです。このクラスタリングは、モデルの予測が安定していたことを示唆しており、モデルによって学習された信頼できる関係を示す定常残差である可能性が高くなります。

では、私たちのモデルがどのようなエラーを起こしたかを分析してみましょう。私たちの目的は、上向きの動きと下向きの動きのどちらを識別するのに優れているのか、あるいは両方のタスクでバランスが取れているのかを見極めることです。

LDA混同行列

図6:LDAモデルの性能の混同行列

上の混同行列は、左側に真の分類を表示し、下側に私たちのモデルの予測を表示します。このデータから、私たちのモデルは上昇の動きを予測するミスが多く、47%の確率で上昇の動きを下降の動きと誤判定していることがわかります。一方、私たちのモデルは下降の動きを非常によく予測し、真の下降の動きと上昇の動きを混同したのは25%だけでした。したがって、私たちのモデルは上昇を予測するよりも下降を予測する方が得意であることがよくわかります。 

訓練データが増えるにつれて、モデルの学習の進捗状況を可視化することができます。以下のプロットは、私たちのモデルが訓練データに過剰適合しているか過少適合しているかを評価するのに役立ちます。過剰適合は、モデルがデータからノイズを学習し、意味のある関係を捉えることができない場合に発生します。一方、過少適合は、プロット上の訓練精度(青線)と検証精度(オレンジの線)の間に大きなギャップがあることで示されます。現在のプロットでは、訓練スコアと検証スコアの間に顕著ではあるものの、それほど大きくないギャップが見られます。これは、LDAモデルが実際に訓練データに過剰適合していることを示しています。しかし、左側のスケールは、この過剰適合が深刻ではないことを示しています。

線形判別分析の学習曲線

図7:LDA分類器の学習曲線

一方、過少適合の特徴は、訓練と検証の精度が低いことです。例として、パフォーマンスの低いモデルのひとつ、小型ニューラルネットワークの学習曲線を掲載しました。下のプロットでは、モデルの性能と訓練データの量との間に不安定な関係があることがわかります。当初、モデルの検証性能はデータが増えるにつれて悪化しますが、学習サイズが1万サンプルに近づくにつれて、ターニングポイントに達し、改善し始めます。その後、利用可能な訓練データ量が大幅に増加し続けたにもかかわらず、改善は停滞し、わずかな改善しか見られなくなります。

小型ニューラルネットワークの学習曲線

図8:小型ニューラルネットワークの学習曲線

特徴量除去

ほとんどの機械学習プロジェクトでは、すべての入力が目標変数に直接関係することは珍しいです。一般的に、利用可能な入力のうち、目標の予測に関連するのは一部だけです。無関係な入力を排除することで、次のような利点があります。

  1. モデル学習と特徴量エンジニアリングにおける計算効率の向上
  2. 特に除去された特徴量にノイズがある場合のモデルの精度の向上

次に、移動平均の間に意味のある関係があるかどうかを判断する必要があります。この仮定された関係を検証するために、特徴量除去アルゴリズムを採用します。これらのアルゴリズムが入力リストから移動平均を除外できない場合は、意味のある関係が存在することが示唆されます。逆に、これらの特徴量をうまく除去することができれば、移動平均と移動平均クロスオーバーには有意な関係がないことが示唆されます。

後退的選択法として知られる特徴量選択技法を採用します。この手法は、利用可能なすべての入力を使って線形モデルをフィッティングして、モデルの精度を測定することから始まります。その後、一度に1つの特徴量を削除し、モデルの精度に与える影響に注目します。各ステップでは、精度の低下を最も小さくする特徴量が、特徴量が残らなくなるまで排除されます。この段階で、アルゴリズムは識別した最も重要な特徴量を自動的に選択し、使用を推奨します。

特筆すべき特徴量除去の重大な欠点は、ノイズの多い重要でない列がデータセットに存在する場合、重要な列が有益でないように見える可能性があることです。その結果、後退的選択法アルゴリズムは、システム内のノイズのために重要でないように見えるため、不注意に重要な特徴量を排除してしまう可能性があります。

それでは、コンピュータがどの列を重要だと考えているのか見てみましょう。後退的選択法アルゴリズムの実装を含むmlxtendというライブラリをインポートすることから始めます。

from mlxtend.feature_selection import SequentialFeatureSelector

次に、このアルゴリズムを私たちのデータセットに適用します。渡したパラメータのうち、特に3つに注目してみましょう。

  1. 「k_features=」は、いくつの列を選択するかをアルゴリズムに指示します。データセットの列の総数まで、1から始まる区間を渡すことで、必要と思われる列だけを選択するようにアルゴリズムに指示することができます。
  2. 「forward=」は、アルゴリズムが前進的選択法と後退的選択法のどちらを使用するべきかを指示します。ここでは後退的選択法を使用したいので、このパラメータをFalseに設定します。
  3. 「n_jobs=」は、アルゴリズムが並列計算をおこなうべきかどうかを指示します。-1を渡すと、利用可能なすべてのコアを使用する許可をアルゴリズムに与えます。 

backward_feature_selector = SequentialFeatureSelector(LinearDiscriminantAnalysis(),
                                                      k_features=(1,market_data.loc[:,predictors].shape[1]),
                                                      forward=False,
                                                      verbose=2,
                                                      scoring="accuracy",
                                                      cv=5,
						      n_jobs=-1
                                                     ).fit(market_data.loc[:,predictors],market_data.loc[:,"target"])

[Parallel(n_jobs=-1)]:Using backend LokyBackend with 8 concurrent workers.

[Parallel(n_jobs=-1)]:Done   3 out of   8 | elapsed:    8.0s remaining:   13.3s

[Parallel(n_jobs=-1)]:Done   8 out of   8 | elapsed:    8.0s remaining:    0.0s

[Parallel(n_jobs=-1)]:Done   8 out of   8 | elapsed:    8.0s finished

処理が完了したら、以下のコマンドを使って、アルゴリズムが重要だと考える入力のリストを得ることができます。

backward_feature_selector.k_feature_names_

('open', 'high', 'close', 'SMA_5', 'SMA_50')

見てわかるように、後進的選択法アルゴリズムはその重要な特徴量のリストに私たちの2つの移動平均を含んでいます。これは、私たちの取引戦略が単なるスプリアス回帰の結果ではないことを証明するものであり、素晴らしいニュースです。

特徴量エンジニアリング

さて、2つの移動平均線の間にさらなる改善を必要とする重要な関係があることを確認したところで、移動平均線のクロスオーバーを予測する際に、テクニカル指標を追加することで精度を高めることができるかどうかを探ってみましょう。どの入力が有益かを事前に予測することは困難であるため、機械学習が科学よりも芸術に傾くのはこの点です。私たちのアプローチでは、有用と思われるいくつかの機能を追加し、その実際の影響を評価します。

前回と同じ市場から市場データを収集しますが、今回は追加指標を取り入れます。

  1. Moving Average Convergence Divergence(MACD、移動平均収束拡散手法):MACDは非常に強力なトレンド確認テクニカル指標であり、基本的な市場レジームの変化をよりよく観察するのに役立つでしょう。
  2. Awesome Oscillator(オーサムオシレータ)オーサムオシレーターは、非常に信頼性の高いエグジットシグナルを提供することで有名であり、トレンドの勢いが変化したときに明確に示すことができます。
  3. Aroon(アルーン):アルーン指標は、新しいトレンドの始まりを特定するために使用されます。
  4. Chaikins Commodity Index(チャイキンス商品指数)チャイキンス商品指数は、金融証券が買われすぎか売られすぎかを測るバロメーターとして機能します。
  5. Percent Return(パーセントリターン):パーセントリターン指標は、価格の伸びを観察し、それがプラスに伸びているのかマイナスに伸びているのかを観察するのに役立ちます。 
上記の指標を元の移動平均線に追加してみましょう。

#Fetch market data
market_data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,date_start,date_end))
market_data["time"] = pd.to_datetime(market_data["time"],unit='s')
#Add simple moving average technical indicator
market_data.ta.sma(length=5,append=True)
#Add simple moving average technical indicator
market_data.ta.sma(length=50,append=True)
#Add macd
market_data.ta.macd(append=True)
#Add awesome oscilator
market_data.ta.ao(append=True)
#Add aroon
market_data.ta.aroon(append=True)
#Add chaikins comodity index
market_data.ta.cci(append=True)
#Add percent return
market_data.ta.percent_return(append=True)
#Delete missing rows
market_data.dropna(inplace=True)
#Add the target
market_data["target"] = 0
market_data.loc[market_data["SMA_5"].shift(-look_ahead) > market_data["SMA_50"].shift(-look_ahead),"target"] = 1
market_data.loc[market_data["SMA_5"].shift(-look_ahead) < market_data["SMA_50"].shift(-look_ahead),"target"] = 0
#The last rows do not have answers
market_data = market_data[:-look_ahead]
market_data

新しいデータフレーム

図9:データフレームに追加した新しい行の一部


特徴量選択をおこなった結果、後進的選択法アルゴリズムにより、以下の変数が重要であることが判明しました。

backward_feature_selector = SequentialFeatureSelector(LinearDiscriminantAnalysis(),
                                                      k_features=(1,market_data.loc[:,predictors].shape[1]),
                                                      forward=False,
                                                      verbose=2,
                                                      scoring="accuracy",
                                                      cv=5
                                                     ).fit(market_data.iloc[:,1:-1],market_data.loc[:,"target"])
backward_feature_selector.k_feature_names_

('close', 'tick_volume', 'spread', 'SMA_5', 'SMA_50', 'MACDh_12_26_9', 'AO_5_34')

取引戦略の構築

さて、ここまで学んだことをすべて、統合された取引戦略に落とし込む準備が整いました。 

まず、利用可能なすべての訓練データに対して、有用であると判断した列のみを使用してモデルをフィッティングすることから始めます。

predictors = ['close','tick_volume','spread','SMA_5','SMA_50','MACDh_12_26_9','AO_5_34']
model = LinearDiscriminantAnalysis()
model.fit(market_data.loc[:,predictors],market_data.loc[:,"target"])

次に、MetaTrader 5端末から市場データを取得する関数を定義します。

def get_prices():
    start = datetime(2024,6,1)
    end   = datetime.now()
    data  = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,start,end))
    #Add simple moving average technical indicator
    data.ta.sma(length=5,append=True)
    data.ta.sma(length=50,append=True)
    #Add awesome oscilator
    data.ta.ao(append=True)
    #Add macd
    data.ta.macd(append=True)
    #Delete missing rows
    data.dropna(inplace=True)
    data['time'] = pd.to_datetime(data['time'],unit='s')
    data.set_index('time',inplace=True)
    data = data.loc[:,['close','tick_volume','spread','SMA_5','SMA_50','MACDh_12_26_9','AO_5_34']]
    data = data.iloc[-2:,:]
    return(data)

その後、LDAモデルから予測を得るために別の方法が必要になります。

#Get signals LDA model
def ai_signal(input_data,_model):
    #Get a forecast
    forecast = _model.predict(input_data)
    return forecast[1]

これで取引戦略を立てることができます。

#Now we define the main body of our Python Moving Average Crossover Trading Bot
if __name__ == '__main__':
    #We'll use an infinite loop to keep the program running
    while True:
        #Fetching model prediction
        signal = ai_signal(get_prices(),model)
        
        #Decoding model prediction into an action
        if signal == 1:
            direction = 'buy'
        elif signal == 0:
            direction = 'sell'
        
        print(f'AI Forecast: {direction}')
        
        #Opening A Buy Trade
        #But first we need to ensure there are no opposite trades open on the same symbol
        if direction == 'buy':
            #Close any sell positions
            for pos in mt5.positions_get():
                if pos.type == 1:
                    #This is an open sell order, and we need to close it
                    close_order(pos.ticket)
            
            if not mt5.positions_totoal():
                #We have no open positions
                mt5.Buy(symbol,volume)
        
        #Opening A Sell Trade
        elif direction == 'sell':
            #Close any buy positions
            for pos in mt5.positions_get():
                if pos.type == 0:
                    #This is an open buy order, and we need to close it
                    close_order(pos.ticket)
            
            if not mt5.positions_get():
                #We have no open positions
                mt5.sell(symbol,volume)
        
        print('time: ', datetime.now())
        print('-------\n')
        time.sleep(60)

AI Forecast: sell

time:  2024-06-25 14:35:37.954923

-------


取引戦略

図10:取引戦略の実施


MQL5での実装

MQL5 APIを利用して、独自の分類器を一から開発してみましょう。MQL5でカスタム分類器を作成することには多くの利点があります。筆者としては、MQL5のネイティブソリューションが比類ない柔軟性を提供すると確信しています。

モデルをONNXフォーマットにエクスポートするとすれば、取引したい市場ごとに別々のモデルが必要になります。さらに、異なる時間枠をまたぐ取引では、市場ごとに複数のONNXモデルが必要になります。MQL5で直接分類器を構築することで、このような制限を受けることなく、どのような市場でも取引できるようになります。

では、新しいプロジェクトを作成してみましょう。

MQL5 EA

図11:戦略を実行するためのEAを作成する


最初の仕事は、プログラム全体で使用するグローバル変数を定義することです。

//Global variables
int ma_5,ma_50;
double bid, ask;
double min_volume;
double ma_50_reading[],ma_5_reading[];
int size;
double current_prediction;
int state = -1;
matrix ohlc;
vector target;
double b_nort = 0;
double b_one = 0;
double b_two = 0;
long min_distance,atr_stop;

エンドユーザーが調整できる入力も用意します。

//Inputs
int input lot_multiple = 20;
int input positions = 2;
double input sl_width = 0.4;

最後に、ポジション管理に役立つ取引ライブラリをインポートします。

//Libraries
#include <Trade\Trade.mqh>
CTrade Trade;

次に、データの取得、訓練データのラベル付け、モデルの学習、モデルからの予測値の取得を助けるヘルパー関数を定義する必要があります。まず、訓練データを取得し、分類器の目標にラベルを付ける関数を定義することから始めましょう。 

//+----------------------------------------------------------------------+
//|This function is responsible for getting our training data ready      |
//+----------------------------------------------------------------------+
void get_training_data(void)
  {
//How much data are we going to use?
   size = 100;
//Copy price data
   ohlc.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,1,size);
//Get indicator data
   ma_50 = iMA(_Symbol,PERIOD_CURRENT,50,0,MODE_EMA,PRICE_CLOSE);
   ma_5 = iMA(_Symbol,PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE);
   CopyBuffer(ma_50,0,0,size,ma_50_reading);
   CopyBuffer(ma_5,0,0,size,ma_5_reading);
   ArraySetAsSeries(ma_50_reading,true);
   ArraySetAsSeries(ma_5_reading,true);
//Label the target
   target = vector::Zeros(size);
   for(int i = 0; i < size; i++)
     {
      if(ma_5_reading[i] > ma_50_reading[i])
        {
         target[i] = 1;
        }

      else
         if(ma_5_reading[i] < ma_50_reading[i])
           {
            target[i] = 0;
           }
     }

//Feedback
   Print("Done getting training data.");
  }

私たちのモデルには、予測に用いる3つの係数があります。これらの係数を最適化する必要があります。これらの係数を調整するために、初心者にやさしい更新方程式を使用することにしましょう。モデルの予測誤差を測定することで、誤差を最小化し、システムの精度を向上させるために、係数を繰り返し修正します。しかし、モデルの最適化を始める前に、まずモデルがどのように予測をおこなうかを定義する必要があります。 

//+----------------------------------------------------------------------+
//|This function is responsible for making predictions using our model   |
//+----------------------------------------------------------------------+
double model_predict(double input_one,double input_two)
  {
//We simply return the probability that the shorter moving average will rise above the slower moving average
   double prediction = 1 / (1 + MathExp(-(b_nort + (b_one * input_one) + (b_two * input_two))));
   return prediction;
  }

モデルが予測できるようになったので、予測値の誤差を測定し、最適化プロセスを開始することができます。初期状態では、3つの係数はすべて0に設定されます。そして、システムの総誤差を最小化するために、小さなステップで係数を繰り返し調整します。 

//+----------------------------------------------------------------------+
//|This function is responsible for  training our model                  |
//+----------------------------------------------------------------------+
bool train_model(void)
  {
//Update the coefficients
   double learning_rate = 0.3;
   for(int i = 0; i < size; i++)
     {
      //Get a prediction from the model
      current_prediction = model_predict(ma_5_reading[i],ma_50_reading[i]);
      //Update each coefficient
      b_nort = b_nort + learning_rate * (target[i] - current_prediction) * current_prediction * (1 - current_prediction) * 1;
      b_one = b_one + learning_rate * (target[i] - current_prediction) * current_prediction * (1-current_prediction) * ma_5_reading[i];
      b_two = b_two + learning_rate * (target[i] - current_prediction) * current_prediction * (1-current_prediction) * ma_50_reading[i];
      Print(current_prediction);
     }

//Show updated coefficient values
   Print("Updated coefficient values");
   Print(b_nort);
   Print(b_one);
   Print(b_two);
   return(true);
  }

モデルの訓練に成功したら、モデルから予測値を取得する関数があると便利です。この予測が売買シグナルとなります。予測値が1であれば買いシグナルであり、このモデルは短い移動平均が長い期間の移動平均を上回ると予想していることを示しています。逆に、予測値が0であれば売りシグナルであり、このモデルは短い方の移動平均が長い方の移動平均を下回ると予測していることを示しています。

//Get the model's current forecast
void current_forecast()
  {
//Get indicator data
   ma_50 = iMA(_Symbol,PERIOD_CURRENT,50,0,MODE_EMA,PRICE_CLOSE);
   ma_5 = iMA(_Symbol,PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE);
   CopyBuffer(ma_50,0,0,1,ma_50_reading);
   CopyBuffer(ma_5,0,0,1,ma_5_reading);
//Get model forecast
   model_predict(ma_5_reading[0],ma_50_reading[0]);
   interpret_forecast();
  }

エキスパートアドバイザー(EA)には、モデルの予測に基づいて行動してもらいたいです。したがって、モデルの予測を解釈し、適切なアクションを取る関数を書きます。モデルが1を予測したら買い、モデルが0を予測したら売るのです。 

//+----------------------------------------------------------------------+
//|This function is responsible for taking action on our model's forecast|
//+----------------------------------------------------------------------+
void interpret_forecast(void)
  {
   if(current_prediction > 0.5)
     {
      state = 1;
      Trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,min_volume * lot_multiple,ask,0,0,"Volatitlity Doctor AI");
     }

   if(current_prediction < 0.5)
     {
      state = 0;
      Trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,min_volume * lot_multiple,bid,0,0,"Volatitlity Doctor AI");
     }
  }

アプリケーションがデータから学習し、予測を立て、その予測に基づいて行動することができるようになったので、次にポジションを管理するための追加関数を作成します。具体的には、リスクレベルを管理するために、各ポジションにトレーリングストップロスとテイクプロフィットを追加します。リスク限度を定めずにポジションを持つことは避けたいです。ほとんどの取引戦略では、ストップロスサイズを100ピップスに固定することを推奨していますが、私たちはストップロスとテイクプロフィットのレベルを現在の市場のボラティリティに基づいて動的に配置するようにしたいと考えています。したがって、Average True Range(ATR、平均トゥルーレンジ)を使って、ストップの幅を計算します。ATRの倍数を使ってこの水準を決めます。

//+----------------------------------------------------------------------+
//|This function is responsible for calculating our SL & TP values       |
//+----------------------------------------------------------------------+
void CheckAtrStop()
  {

//First we iterate over the total number of open positions
   for(int i = PositionsTotal() -1; i >= 0; i--)
     {

      //Then we fetch the name of the symbol of the open position
      string symbol = PositionGetSymbol(i);

      //Before going any furhter we need to ensure that the symbol of the position matches the symbol we're trading
      if(_Symbol == symbol)
        {
         //Now we get information about the position
         ulong ticket = PositionGetInteger(POSITION_TICKET); //Position Ticket
         double position_price = PositionGetDouble(POSITION_PRICE_OPEN); //Position Open Price
         long type = PositionGetInteger(POSITION_TYPE); //Position Type
         double current_stop_loss = PositionGetDouble(POSITION_SL); //Current Stop loss value

         //If the position is a buy
         if(type == POSITION_TYPE_BUY)
           {

            //The new stop loss value is just the ask price minus the ATR stop we calculated above
            double atr_stop_loss = NormalizeDouble(ask - ((min_distance * sl_width)/2),_Digits);
            //The new take profit is just the ask price plus the ATR stop we calculated above
            double atr_take_profit = NormalizeDouble(ask + (min_distance * sl_width),_Digits);

            //If our current stop loss is less than our calculated ATR stop loss
            //Or if our current stop loss is 0 then we will modify the stop loss and take profit
            if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0))
              {
               Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
              }
           }

         //If the position is a sell
         else
            if(type == POSITION_TYPE_SELL)
              {
               //The new stop loss value is just the ask price minus the ATR stop we calculated above
               double atr_stop_loss = NormalizeDouble(bid + ((min_distance * sl_width)/2),_Digits);
               //The new take profit is just the ask price plus the ATR stop we calculated above
               double atr_take_profit = NormalizeDouble(bid - (min_distance * sl_width),_Digits);

               //If our current stop loss is greater than our calculated ATR stop loss
               //Or if our current stop loss is 0 then we will modify the stop loss and take profit
               if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0))
                 {
                  Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                 }
              }
        }
     }
  }

そして、新しいストップロス値とテイクプロフィット値を計算したいときに呼び出す関数が必要です。

//+------------------------------------------------------------------+
//|This function is responsible for updating our SL&TP values        |
//+------------------------------------------------------------------+
void ManageTrade()
  {
   CheckAtrStop();
  }

ヘルパー関数の定義ができたので、イベントハンドラ内呼び出すことができます。プログラムが初めて読み込まれたら、訓練プロセスを開始します。したがって、OnInitイベントハンドラ内で、EAの訓練を担当するヘルパー関数を呼び出すことにします。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //Define important global variables
   min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   //Train the model
   get_training_data();
   if(train_model())
     {
      interpret_forecast();
     }
   return(INIT_SUCCEEDED);
  }

モデルを訓練した後は、実際の取引を開始することができます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//Get updates bid and ask prices
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);

   if(PositionsTotal() == 0)
     {
      current_forecast();
     }

   if(PositionsTotal() > 0)
     {
      ManageTrade();
     }
  }

モデル出力

図12:EAからの出力サンプル

私たちのEA

図13:実行中のEA


結論

本稿では、移動平均クロスオーバーを予測する方が、価格の変化を直接予測するよりも計算上容易であることを示しました。

他の記事と同様、私は技術的な説明は最後に行い、最初に原理を示すことを好みます。この観察にはいくつかの理由が考えられます。その理由として考えられるのは、選択した期間によっては、価格が不規則に方向転換するため、移動平均線が頻繁にクロスオーバーしない可能性があることです。言い換えれば、過去2時間の間に、価格が上昇し、その後下落した、あるいは2回方向転換した可能性があります。しかし、同じ期間に移動平均線はまったく交差しなかったかもしれません。したがって、移動平均クロスオーバーは、価格そのものが変化するほど急速に方向が変わるわけではないので、予測しやすいかもしれません。これは1つの可能性に過ぎません。自分の頭で考え、自分なりの結論を導き出し、以下のコメントで共有してください。

この手法では、線形モデルを繰り返し学習し、各ステップでモデルの精度に与える影響に基づいて1つの特徴量を除去します。このアプローチは、最も有益な特徴量を特定し保持するのに役立ちますが、ノイズのために有益でないように見える重要な特徴を排除してしまう可能性があります。

2つの移動平均の間に有意な関係があることを検証した後、さらにテクニカル指標(MACD、Awesome Oscillator、Aroon、Chaikins Commodity Index、Percent Return)を統合することを検討しました。これらの指標は、移動平均クロスオーバーを正確に予測する能力を高めることを目的としています。しかし、これらの指標の選択は、モデルの性能に与える影響が予測不可能であるため、いささか難しいです。

全体として、私たちのアプローチは経験的検証と戦略的特徴選択を融合させ、移動平均クロスオーバーが実際に予測可能であることを定量的に証明し、さらにこの取引戦略を改善するために費やされる努力は、決して時間の無駄ではないことを強調しています。

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

EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
母集団最適化アルゴリズム:ボイドアルゴリズム 母集団最適化アルゴリズム:ボイドアルゴリズム
この記事では、動物の群れ行動のユニークな例に基づいたボイドアルゴリズムについて考察しています。その結果、ボイドアルゴリズムは、「群知能(Swarm Intelligence)」の名の下に統合されたアルゴリズム群全体の基礎となった。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
多通貨エキスパートアドバイザーの開発(第6回):インスタンスグループ選択の自動化 多通貨エキスパートアドバイザーの開発(第6回):インスタンスグループ選択の自動化
取引戦略を最適化した後、パラメータのセットを受け取ります。これらを使用して、1つのEAに複数の取引戦略のインスタンスを作成することができます。以前は手動でおこないましたが、ここでは、このプロセスの自動化を試みます。