English Русский 中文 Español Deutsch Português
preview
データサイエンスとML(第32回):AIモデルを最新の状態に保つ、オンライン学習

データサイエンスとML(第32回):AIモデルを最新の状態に保つ、オンライン学習

MetaTrader 5統計と分析 |
355 3
Omega J Msigwa
Omega J Msigwa

内容


オンライン学習について

オンライン機械学習とは、モデルがリアルタイムでデータストリームから継続的に学習する機械学習手法です。これは、時間の経過とともに予測アルゴリズムを適応させ、新しいデータが追加されるたびにモデルが更新される動的なプロセスです。この手法は、取引データのような急速に変化するデータが豊富な環境において、タイムリーで正確な予測を提供できるため、非常に重要です。

取取引データを扱う際、モデルをいつ、どの頻度で更新すべきかを判断するのは常に難しい課題です。 たとえば、過去1年間のビットコインデータで訓練されたAIモデルがある場合、最近の情報が外れ値と見なされる可能性があります。特に、この暗号通貨が先週、新たな最高値を更新したことを考慮すると、その影響は大きいでしょう。

通常、外国為替市場の通貨ペアは歴史的に特定の範囲内で上下動する傾向がありますが、NASDAQ100やS&P500といった指数、さらには個別株式は、長期的に上昇し続け、新たな最高値を更新する傾向があります。

オンライン学習は、過去の訓練データが陳腐化することを防ぐだけでなく、市場の最新動向に影響を与える可能性のある新しい情報を取り入れ、モデルを常に最新の状態に保つためにも重要です。


オンライン学習のメリット

  • 適応性

    サイクリストが走りながら学習するように、オンライン機械学習もデータの新しいパターンに適応し、時間の経過とともにパフォーマンスを向上させることができます。

  • スケーラビリティ

    一部のモデルでは、オンライン学習手法によってデータを1つずつ順番に処理することができます。このため、計算リソースが限られている環境でも安全に運用できる手法となり、最終的にはビッグデータを活用するモデルのスケーリングにも貢献します。

  • リアルタイム予測 

    バッチ学習では実装が完了する頃にはデータが古くなっている可能性がありますが、オンライン学習はリアルタイムのインサイトを提供し、多くの取引アプリケーションにおいて重要な役割を果たします。

  • 効率

    増分学習により、モデルは継続的に学習・更新されるため、より迅速かつコスト効率の高い訓練プロセスを実現できます。

この手法の利点を理解したところで、MetaTrader 5で効果的なオンライン学習を実現するために必要なインフラストラクチャについて見ていきましょう。


MetaTrader 5のオンライン学習インフラストラクチャ 

私たちの最終目標は、MetaTrader 5での取引にAIモデルを活用することです。そのため、Pythonベースのアプリケーションで一般的に使用されるものとは異なるオンライン学習インフラストラクチャが求められます。

MetaTrader5のオンライン学習インフラストラクチャ


手順01:Pythonクライアント

Pythonクライアント(スクリプト)内で、MetaTrader 5から受信した取引データに基づいてAIモデルを構築します。

MetaTrader 5(Pythonライブラリ)を使用して、プラットフォームを初期化することから始めます。

import pandas as pd
import numpy as np
import MetaTrader5 as mt5
from datetime import datetime

if not mt5.initialize(): # Initialize the MetaTrader 5 platform
    print("initialize() failed")
    mt5.shutdown()

MetaTrader 5プラットフォームを初期化した後は、copy_rates_from_posメソッドを使用してそこから取引情報を取得できます。 

def getData(start = 1, bars = 1000):

    rates = mt5.copy_rates_from_pos("EURUSD", mt5.TIMEFRAME_H1, start, bars)
  
   if len(rates) < bars: # if the received information is less than specified
        print("Failed to copy rates from MetaTrader 5, error = ",mt5.last_error())

    # create a pnadas DataFrame out of the obtained data

    df_rates = pd.DataFrame(rates)
                                                
    return df_rates

取得した情報を出力して確認することができます。

print("Trading info:\n",getData(1, 100)) # get 100 bars starting at the recent closed bar

以下が出力です。

           time     open     high      low    close  tick_volume  spread  real_volume
0   1731351600  1.06520  1.06564  1.06451  1.06491         1688       0            0
1   1731355200  1.06491  1.06519  1.06460  1.06505         1607       0            0
2   1731358800  1.06505  1.06573  1.06495  1.06512         1157       0            0
3   1731362400  1.06512  1.06564  1.06512  1.06557         1112       0            0
4   1731366000  1.06557  1.06579  1.06553  1.06557          776       0            0
..         ...      ...      ...      ...      ...          ...     ...          ...
95  1731693600  1.05354  1.05516  1.05333  1.05513         5125       0            0
96  1731697200  1.05513  1.05600  1.05472  1.05486         3966       0            0
97  1731700800  1.05487  1.05547  1.05386  1.05515         2919       0            0
98  1731704400  1.05515  1.05522  1.05359  1.05372         2651       0            0
99  1731708000  1.05372  1.05379  1.05164  1.05279         2977       0            0

[100 rows x 8 columns]

copy_rates_from_posメソッドを使用すると、インデックス1に配置された最近閉じたバーにアクセスできるため、固定された日付を使用してアクセスする場合に比べて非常に便利です。

インデックス1にあるバーからコピーすることで、常に、最近クローズしたバーから、必要な指定数のバーまでの情報を取得できることが保証されます。

この情報を受け取った後、このデータに対して一般的な機械学習をおこなうことができます。

モデルごとに個別のファイルを作成し、各モデルを個別のファイルに配置することで、すべての主要なプロセスと関数が実装されているmain.pyファイルでこれらのモデルを簡単に呼び出すことができます。

[catboost_models.py]

from catboost import CatBoostClassifier
from sklearn.metrics import accuracy_score

from onnx.helper import get_attribute_value
from skl2onnx import convert_sklearn, update_registered_converter
from sklearn.pipeline import Pipeline
from skl2onnx.common.shape_calculator import (
    calculate_linear_classifier_output_shapes,
)  # noqa
from skl2onnx.common.data_types import (
    FloatTensorType,
    Int64TensorType,
    guess_tensor_type,
)
from skl2onnx._parse import _apply_zipmap, _get_sklearn_operator_name
from catboost.utils import convert_to_onnx_object

# Example initial data (X_initial, y_initial are your initial feature matrix and target)

class CatBoostClassifierModel():
    def __init__(self, X_train, X_test, y_train, y_test):

        self.X_train = X_train
        self.X_test = X_test
        self.y_train = y_train
        self.y_test = y_test
        self.model = None

    def train(self, iterations=100, depth=6, learning_rate=0.1, loss_function="CrossEntropy", use_best_model=True):
        # Initialize the CatBoost model

        params = {
            "iterations": iterations,
            "depth": depth,
            "learning_rate": learning_rate,
            "loss_function": loss_function,
            "use_best_model": use_best_model
        }

        self.model = Pipeline([ # wrap a catboost classifier in sklearn pipeline | good practice (not necessary tho :))
            ("catboost", CatBoostClassifier(**params))
        ])

        # Testing the model
        
        self.model.fit(X=self.X_train, y=self.y_train, catboost__eval_set=(self.X_test, self.y_test))

        y_pred = self.model.predict(self.X_test)
        print("Model's accuracy on out-of-sample data = ",accuracy_score(self.y_test, y_pred))

    # a function for saving the trained CatBoost model to ONNX format

    def to_onnx(self, model_name):

        update_registered_converter(
            CatBoostClassifier,
            "CatBoostCatBoostClassifier",
            calculate_linear_classifier_output_shapes,
            self.skl2onnx_convert_catboost,
            parser=self.skl2onnx_parser_castboost_classifier,
            options={"nocl": [True, False], "zipmap": [True, False, "columns"]},
        )

        model_onnx = convert_sklearn(
            self.model,
            "pipeline_catboost",
            [("input", FloatTensorType([None, self.X_train.shape[1]]))],
            target_opset={"": 12, "ai.onnx.ml": 2},
        )

        # And save.
        with open(model_name, "wb") as f:
            f.write(model_onnx.SerializeToString())

このCatBoostモデルの実装の詳細については、こちらの記事を参照してください。  ここではCatBoostモデルを例として使用しましたが、お好みのモデルを自由に使用してください。

これで、catboostモデルの初期化、訓練、保存に役立つこのクラスができました。このモデルをmain.pyファイルに実装してみましょう。

[main.py]

ここでも、MetaTrader 5デスクトップアプリからデータを受信することから始めます。

data = getData(start=1, bars=1000)

CatBoostモデルをよく見ると、それが分類モデルであることがわかります。この分類器の目的変数はまだないので、作成してみましょう。

# Preparing the target variable

data["future_open"] = data["open"].shift(-1) # shift one bar into the future
data["future_close"] = data["close"].shift(-1)

target = []
for row in range(data.shape[0]):
    if data["future_close"].iloc[row] > data["future_open"].iloc[row]: # bullish signal
        target.append(1)
    else: # bearish signal
        target.append(0)

data["target"] = target # add the target variable to the dataframe

data = data.dropna() # drop empty rows

2D配列「X」から、ゼロ値が多数含まれるすべての将来の変数とその他の機能を削除し、target変数を1D配列「y」に割り当てることができます。

X = data.drop(columns = ["spread","real_volume","future_close","future_open","target"])
y = data["target"]

次に、情報を訓練サンプルと検証サンプルに分割し、市場のデータを使用してCatBoostモデルを初期化し、訓練します。

X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state=42)

catboost_model = catboost_models.CatBoostClassifierModel(X_train, X_test, y_train, y_test)
catboost_model.train()

最後に、このモデルをONNX形式で MetaTrader 5のCommonディレクトリに保存します。

手順02:Commonフォルダ

MetaTrader 5 Pythonを使用すると、Commonのパスに関する情報を取得できます。

terminal_info_dict = mt5.terminal_info()._asdict()
common_path = terminal_info_dict["commondata_path"]

このPythonクライアントから訓練されたすべてのAIモデルをここに保存します。

MQL5を使用してCommonフォルダにアクセスする場合、通常はCommonフォルダの下にあるFilesサブフォルダを参照します。MQL5の観点からこれらのファイルに簡単にアクセスできるようにするには、そのサブフォルダにモデルを保存する必要があります。

# Save models in a specific location under the common parent folder

models_path = os.path.join(common_path, "Files")

if not os.path.exists(models_path): #if the folder exists
    os.makedirs(models_path) # Create the folder if it doesn't exist

catboost_model.to_onnx(model_name=os.path.join(models_path, "catboost.H1.onnx"))

最後に、これらすべてのコード行を1つの関数にまとめて、必要なときにいつでもさまざまなプロセスを簡単に実行できるようにする必要があります。

def trainAndSaveCatBoost():

    data = getData(start=1, bars=1000)

    # Check if we were able to receive some data

    if (len(data)<=0):
        print("Failed to obtain data from Metatrader5, error = ",mt5.last_error())
        mt5.shutdown()

    # Preparing the target variable

    data["future_open"] = data["open"].shift(-1) # shift one bar into the future
    data["future_close"] = data["close"].shift(-1)

    target = []
    for row in range(data.shape[0]):
        if data["future_close"].iloc[row] > data["future_open"].iloc[row]: # bullish signal
            target.append(1)
        else: # bearish signal
            target.append(0)

    data["target"] = target # add the target variable to the dataframe

    data = data.dropna() # drop empty rows

    X = data.drop(columns = ["spread","real_volume","future_close","future_open","target"])
    y = data["target"]

    X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state=42)

    catboost_model = catboost_models.CatBoostClassifierModel(X_train, X_test, y_train, y_test)
    catboost_model.train()

    # Save models in a specific location under the common parent folder

    models_path = os.path.join(common_path, "Files")

    if not os.path.exists(models_path): #if the folder exists
        os.makedirs(models_path) # Create the folder if it doesn't exist

    catboost_model.to_onnx(model_name=os.path.join(models_path, "catboost.H1.onnx"))

それでは、この関数を呼び出して、何がおこなわれるか見てみましょう。

trainAndSaveCatBoost()
exit() # stop the script

結果

0:      learn: 0.6916088        test: 0.6934968 best: 0.6934968 (0)     total: 163ms    remaining: 16.1s
1:      learn: 0.6901684        test: 0.6936087 best: 0.6934968 (0)     total: 168ms    remaining: 8.22s
2:      learn: 0.6888965        test: 0.6931576 best: 0.6931576 (2)     total: 175ms    remaining: 5.65s
3:      learn: 0.6856524        test: 0.6927187 best: 0.6927187 (3)     total: 184ms    remaining: 4.41s
4:      learn: 0.6843646        test: 0.6927737 best: 0.6927187 (3)     total: 196ms    remaining: 3.72s
...
...
...
96:     learn: 0.5992419        test: 0.6995323 best: 0.6927187 (3)     total: 915ms    remaining: 28.3ms
97:     learn: 0.5985751        test: 0.7002011 best: 0.6927187 (3)     total: 924ms    remaining: 18.9ms
98:     learn: 0.5978617        test: 0.7003299 best: 0.6927187 (3)     total: 928ms    remaining: 9.37ms
99:     learn: 0.5968786        test: 0.7010596 best: 0.6927187 (3)     total: 932ms    remaining: 0us

bestTest = 0.6927187021
bestIteration = 3

Shrink model to first 4 iterations.
Model's accuracy on out-of-sample data =  0.5

.onnxファイルはCommon\Filesの下になります。

手順03:MetaTrader 5

ここで、MetaTrader 5で、ONNX形式で保存されたこのモデルをロードする必要があります。

まず、このタスクに役立つライブラリをインポートします。

[Online Learning Catboost.mq5]

#include <CatBoost.mqh>
CCatBoost *catboost;

input string model_name = "catboost.H1.onnx";
input string symbol = "EURUSD";
input ENUM_TIMEFRAMES timeframe = PERIOD_H1;

string common_path;

Oninit関数内で最初におこなうことは、Commonフォルダにファイルが存在するかどうかを確認することです。存在しない場合は、モデルが訓練されていないことを示している可能性があります。

その後、ONNX_COMMON_FOLDERフラグを渡してONNXモデルを初期化し、Commonフォルダからモデルを明示的に読み込みます。

int OnInit()
  {
//--- Check if the model file exists
  
   if (!FileIsExist(model_name, FILE_COMMON))
     {
       printf("%s Onnx file doesn't exist",__FUNCTION__);
       return INIT_FAILED;
     }
     
//--- Initialize a catboost model
   
  catboost = new CCatBoost(); 
  if (!catboost.Init(model_name, ONNX_COMMON_FOLDER))
    {
      printf("%s failed to initialize the catboost model, error = %d",__FUNCTION__,GetLastError());      
      return INIT_FAILED;
    }
      
//---
}

この読み込まれたモデルを使用して予測をおこなうには、Pythonスクリプトに戻って、いくつかの特徴量が削除された後に訓練に使用された特徴量を確認します。

MQL5では同じ特徴量を同じ順序で収集する必要があります。

[main.pyファイルのPythonコード]

X = data.drop(columns = ["spread","real_volume","future_close","future_open","target"])
y = data["target"]

print(X.head())

結果

         time     open     high      low    close  tick_volume
0  1726772400  1.11469  1.11584  1.11453  1.11556         3315
1  1726776000  1.11556  1.11615  1.11525  1.11606         2812
2  1726779600  1.11606  1.11680  1.11606  1.11656         2309
3  1726783200  1.11656  1.11668  1.11590  1.11622         2667
4  1726786800  1.11622  1.11644  1.11605  1.11615         1166

ここで、OnTick関数内でこの情報を取得し、クラスを予測するpredict_bin関数を呼び出します。

この関数は、Pythonクライアントで準備した目的変数に表示された2つのクラス(  0(強気)、1(弱気))を予測します。

void OnTick()
  {
//---
     MqlRates rates[];
     CopyRates(symbol, timeframe, 1, 1, rates); //copy the recent closed bar information
     
     vector x = {
                 (double)rates[0].time, 
                 rates[0].open, 
                 rates[0].high, 
                 rates[0].low, 
                 rates[0].close, 
                 (double)rates[0].tick_volume};
     
     Comment(TimeCurrent(),"\nPredicted signal: ",catboost.predict_bin(x)==0?"Bearish":"Bullish");// if the predicted signal is 0 it means a bearish signal, otherwise it is a bullish signal
  }

結果


訓練と導入のプロセスの自動化

MetaTrader 5でモデルを訓練して導入することはできましたが、これは本来の目的ではありません。私たちの主な目標は、プロセス全体を自動化することです。 

Python仮想環境内に、スケジュールライブラリをインストールする必要があります。

$ pip install schedule

この小さなモジュールは、特定の関数を実行するスケジュールを設定するのに役立ちます。データの収集、訓練、モデルの保存をおこなうコードをすでに1つの関数にラップしているので、この関数が1分ごとに呼び出されるようにスケジュールしましょう。

schedule.every(1).minute.do(trainAndSaveCatBoost) #schedule catboost training

# Keep the script running to execute the scheduled tasks
while True:
    schedule.run_pending()
    time.sleep(60)  # Wait for 1 minute before checking again

このスケジュール機能はうまく機能します :)

メインのエキスパートアドバイザー(EA)では、EAがCommonディレクトリからモデルをロードするタイミングと頻度もスケジュールします。これにより、自動売買ロボットのモデルが効果的に更新されます。

OnTimer関数も使用できますが、これも非常にうまく機能します :)

int OnInit()
  {
//--- Check if the model file exists
  
  ....
     
//--- Initialize a catboost model
   
....
      
//---

   if (!EventSetTimer(60)) //Execute the OnTimer function after every 60 seconds
     {
       printf("%s failed to set the event timer, error = %d",__FUNCTION__,GetLastError());
       return INIT_FAILED;
     }
    
    
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
    if (CheckPointer(catboost) != POINTER_INVALID)
      delete catboost;
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
     ....
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
    if (CheckPointer(catboost) != POINTER_INVALID)
      delete catboost; 
      
//--- Load the new model after deleting the prior one from memory

     catboost = new CCatBoost(); 
     if (!catboost.Init(model_name, ONNX_COMMON_FOLDER))
       {
         printf("%s failed to initialize the catboost model, error = %d",__FUNCTION__,GetLastError());      
         return;
       }
       
     printf("%s New model loaded",TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES));
  }

結果

HO      0       13:14:00.648    Online Learning Catboost (EURUSD,D1)    2024.11.18 12:14 New model loaded
FK      0       13:15:55.388    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:15 New model loaded
JG      0       13:16:55.380    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:16 New model loaded
MP      0       13:17:55.376    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:17 New model loaded
JM      0       13:18:55.377    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:18 New model loaded
PF      0       13:19:55.368    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:19 New model loaded
CR      0       13:20:55.387    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:20 New model loaded
NO      0       13:21:55.377    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:21 New model loaded
LH      0       13:22:55.379    Online Learning Catboost (GBPUSD,H1)    2024.11.18 12:22 New model loaded

ここまで、訓練プロセスをスケジュールし、MetaTrader 5のEAと新しいモデルを同期させる方法を見てきました。このプロセスは、シンプルな機械学習手法を展開する場合は比較的容易ですが、再帰型ニューラルネットワーク(RNN)などのディープラーニングモデルを扱う場合は、より困難になる可能性があります。これは、RNNがSklearnパイプラインに収めることができず、さまざまな機械学習モデルを扱う際に便利なワークフローが使えなくなるためです。

再帰型ニューラルネットワークの特殊な形式であゲート付き回帰型ユニット(GRU)を操作するときに、この手法をどのように適用できるかを見てみましょう。


ディープラーニングAIモデルのオンライン学習

Pythonクライアントの場合

GRUClassifierクラス内で典型的な機械学習を適用します。GRUの詳細については、こちらの記事を参照してください。

モデルを訓練した後、それをONNXに保存します。今回は、StandardScalerの情報もバイナリ ファイルに保存します。これにより、Pythonで現在使用しているのと同じ方法で、MQL5内でも新しいデータを正しく正規化できるようになります。

[gru_models.py]

import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import GRU, Dense, Input, Dropout
from keras.callbacks import EarlyStopping
from keras.optimizers import Adam
import tf2onnx


class GRUClassifier():
    def __init__(self, time_step, X_train, X_test, y_train, y_test):

        self.X_train = X_train
        self.X_test = X_test
        self.y_train = y_train
        self.y_test = y_test
        self.model = None
        self.time_step = time_step
        self.classes_in_y = np.unique(self.y_train)


    def train(self, learning_rate=0.001, layers=2, neurons = 50, activation="relu", batch_size=32, epochs=100, loss="binary_crossentropy", verbose=0):

        self.model = Sequential()
        self.model.add(Input(shape=(self.time_step, self.X_train.shape[2]))) 
        self.model.add(GRU(units=neurons, activation=activation)) # input layer


        for layer in range(layers): # dynamically adjusting the number of hidden layers

            self.model.add(Dense(units=neurons, activation=activation))
            self.model.add(Dropout(0.5))

        self.model.add(Dense(units=len(self.classes_in_y), activation='softmax', name='output_layer')) # the output layer

        # Compile the model
        adam_optimizer = Adam(learning_rate=learning_rate)
        self.model.compile(optimizer=adam_optimizer, loss=loss, metrics=['accuracy'])
        

        early_stopping = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)

        history = self.model.fit(self.X_train, self.y_train, epochs=epochs, batch_size=batch_size,
                                 validation_data=(self.X_test, self.y_test),
                                 callbacks=[early_stopping], verbose=verbose)

        val_loss, val_accuracy = self.model.evaluate(self.X_test, self.y_test, verbose=verbose)

        print("Gru accuracy on validation sample = ",val_accuracy)

            
    def to_onnx(self, model_name, standard_scaler):

        # Convert the Keras model to ONNX
        spec = (tf.TensorSpec((None, self.time_step, self.X_train.shape[2]), tf.float16, name="input"),)
        self.model.output_names = ['outputs']

        onnx_model, _ = tf2onnx.convert.from_keras(self.model, input_signature=spec, opset=13)

        # Save the ONNX model to a file
        with open(model_name, "wb") as f:
            f.write(onnx_model.SerializeToString())

        # Save the mean and scale parameters to binary files
        standard_scaler.mean_.tofile(f"{model_name.replace('.onnx','')}.standard_scaler_mean.bin")
        standard_scaler.scale_.tofile(f"{model_name.replace('.onnx','')}.standard_scaler_scale.bin")

main.pyファイル内に、GRUモデルで実行したいすべての処理を担当する関数を作成します。 

def trainAndSaveGRU():

    data = getData(start=1, bars=1000)

    # Preparing the target variable

    data["future_open"] = data["open"].shift(-1)
    data["future_close"] = data["close"].shift(-1)

    target = []
    for row in range(data.shape[0]):
        if data["future_close"].iloc[row] > data["future_open"].iloc[row]:
            target.append(1)
        else:
            target.append(0)

    data["target"] = target

    data = data.dropna()

    # Check if we were able to receive some data

    if (len(data)<=0):
        print("Failed to obtain data from Metatrader5, error = ",mt5.last_error())
        mt5.shutdown()

    X = data.drop(columns = ["spread","real_volume","future_close","future_open","target"])
    y = data["target"]

    X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=False)

    ########### Preparing data for timeseries forecasting ###############

    time_step = 10 

    scaler = StandardScaler()

    X_train = scaler.fit_transform(X_train)
    X_test = scaler.transform(X_test)

    x_train_seq, y_train_seq = create_sequences(X_train, y_train, time_step)
    x_test_seq, y_test_seq = create_sequences(X_test, y_test, time_step)

    ###### One HOt encoding #######

    y_train_encoded = to_categorical(y_train_seq)
    y_test_encoded = to_categorical(y_test_seq)

    gru = gru_models.GRUClassifier(time_step=time_step,
                                    X_train= x_train_seq, 
                                    y_train= y_train_encoded, 
                                    X_test= x_test_seq, 
                                    y_test= y_test_encoded
                                    )

    gru.train(
        batch_size=64, 
        learning_rate=0.001, 
        activation = "relu",
        epochs=1000,
        loss="binary_crossentropy",
        layers = 2,
        neurons = 50,
        verbose=1
        )
    
    # Save models in a specific location under the common parent folder

    models_path = os.path.join(common_path, "Files")

    if not os.path.exists(models_path): #if the folder exists
        os.makedirs(models_path) # Create the folder if it doesn't exist

    gru.to_onnx(model_name=os.path.join(models_path, "gru.H1.onnx"), standard_scaler=scaler)

最後に、CatBoost関数をスケジュールしたのと同様に、このtrainAndSaveGRU関数を呼び出す頻度をスケジュールできます。

schedule.every(1).minute.do(trainAndSaveGRU) #scheduled GRU training

結果

Epoch 1/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 7s 87ms/step - accuracy: 0.4930 - loss: 0.6985 - val_accuracy: 0.5000 - val_loss: 0.6958
Epoch 2/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4847 - loss: 0.6957 - val_accuracy: 0.4931 - val_loss: 0.6936
Epoch 3/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.5500 - loss: 0.6915 - val_accuracy: 0.4897 - val_loss: 0.6934
Epoch 4/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4910 - loss: 0.6923 - val_accuracy: 0.4690 - val_loss: 0.6938
Epoch 5/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.5538 - loss: 0.6910 - val_accuracy: 0.4897 - val_loss: 0.6935
Epoch 6/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.5037 - loss: 0.6953 - val_accuracy: 0.4931 - val_loss: 0.6937
Epoch 7/1000
...
...
...
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 22ms/step - accuracy: 0.4964 - loss: 0.6952 - val_accuracy: 0.4793 - val_loss: 0.6940
Epoch 20/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 19ms/step - accuracy: 0.5285 - loss: 0.6914 - val_accuracy: 0.4793 - val_loss: 0.6949
Epoch 21/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.5224 - loss: 0.6935 - val_accuracy: 0.4966 - val_loss: 0.6942
Epoch 22/1000
11/11 ━━━━━━━━━━━━━━━━━━━━ 0s 21ms/step - accuracy: 0.5009 - loss: 0.6936 - val_accuracy: 0.5103 - val_loss: 0.6933
10/10 ━━━━━━━━━━━━━━━━━━━━ 0s 19ms/step - accuracy: 0.4925 - loss: 0.6938
Gru accuracy on validation sample =  0.5103448033332825

MetaTrader 5

まず、GRUモデルと標準スケーラーをロードするタスクを支援するライブラリをロードします。

#include <preprocessing.mqh>
#include <GRU.mqh>

CGRU *gru;
StandardizationScaler *scaler;

//--- Arrays for temporary storage of the scaler values
double scaler_mean[], scaler_std[];

input string model_name = "gru.H1.onnx";

string mean_file;
string std_file;

OnInit関数で最初におこなうことは、スケーラーバイナリファイルの名前を取得することです。これらのファイルを作成するときに、同じ原則を適用しました。

 string base_name__ = model_name;
   
 if (StringReplace(base_name__,".onnx","")<0)
   {
     printf("%s Failed to obtain the parent name for the scaler files, error = %d",__FUNCTION__,GetLastError());
     return INIT_FAILED;
   }
  
  mean_file = base_name__ + ".standard_scaler_mean.bin";
  std_file = base_name__ + ".standard_scaler_scale.bin";

最後に、Commonフォルダから ONNX形式のGRUモデルをロードし、scaler_mean配列とscaler_std配列に値を割り当てることで、バイナリ形式のスケーラーファイルも読み取ります。

int OnInit()
  {
   
   string base_name__ = model_name;
   
   if (StringReplace(base_name__,".onnx","")<0) //we followed this same file patterns while saving the binary files in python client
     {
       printf("%s Failed to obtain the parent name for the scaler files, error = %d",__FUNCTION__,GetLastError());
       return INIT_FAILED;
     }
        
   mean_file = base_name__ + ".standard_scaler_mean.bin";
   std_file = base_name__ + ".standard_scaler_scale.bin";
   
//--- Check if the model file exists

   if (!FileIsExist(model_name, FILE_COMMON))
     {
       printf("%s Onnx file doesn't exist",__FUNCTION__);
       return INIT_FAILED;
     }
  
//--- Initialize the GRU model from the common folder

     gru = new CGRU(); 
     if (!gru.Init(model_name, ONNX_COMMON_FOLDER))
       {
         printf("%s failed to initialize the gru model, error = %d",__FUNCTION__,GetLastError());      
         return INIT_FAILED;
       }

//--- Read the scaler files
   
   if (!readArray(mean_file, scaler_mean) || !readArray(std_file, scaler_std))
     {
       printf("%s failed to read scaler information",__FUNCTION__);
       return INIT_FAILED;
     }  
      
   scaler = new StandardizationScaler(scaler_mean, scaler_std); //Load the scaler class by populating it with values
   
//--- Set the timer

   if (!EventSetTimer(60))
     {
       printf("%s failed to set the event timer, error = %d",__FUNCTION__,GetLastError());
       return INIT_FAILED;
     }
    
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
    if (CheckPointer(gru) != POINTER_INVALID)
      delete gru;
    if (CheckPointer(scaler) != POINTER_INVALID)
      delete scaler;
  }

OnTimer関数で、Commonフォルダからスケーラーファイルとモデルファイルを読み取るプロセスをスケジュールします。

void OnTimer(void)
  {
//--- Delete the existing pointers in memory as the new ones are about to be created

    if (CheckPointer(gru) != POINTER_INVALID)
      delete gru;
    if (CheckPointer(scaler) != POINTER_INVALID)
      delete scaler;
      
//---
      
   if (!readArray(mean_file, scaler_mean) || !readArray(std_file, scaler_std))
     {
       printf("%s failed to read scaler information",__FUNCTION__);
       return;
     }  
      
   scaler = new StandardizationScaler(scaler_mean, scaler_std);
   
     gru = new CGRU(); 
     if (!gru.Init(model_name, ONNX_COMMON_FOLDER))
       {
         printf("%s failed to initialize the gru model, error = %d",__FUNCTION__,GetLastError());      
         return;
         
       }
     printf("%s New model loaded",TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES));
  }

結果

II      0       14:49:35.920    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:49 New model loaded
QP      0       14:50:35.886    Online Learning GRU (GBPUSD,H1) Initilaizing ONNX model...
MF      0       14:50:35.919    Online Learning GRU (GBPUSD,H1) ONNX model Initialized
IJ      0       14:50:35.919    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:50 New model loaded
EN      0       14:51:35.894    Online Learning GRU (GBPUSD,H1) Initilaizing ONNX model...
JD      0       14:51:35.913    Online Learning GRU (GBPUSD,H1) ONNX model Initialized
EL      0       14:51:35.913    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:51 New model loaded
NM      0       14:52:35.885    Online Learning GRU (GBPUSD,H1) Initilaizing ONNX model...
KK      0       14:52:35.915    Online Learning GRU (GBPUSD,H1) ONNX model Initialized
QQ      0       14:52:35.915    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:52 New model loaded
DK      0       14:53:35.899    Online Learning GRU (GBPUSD,H1) Initilaizing ONNX model...
HI      0       14:53:35.935    Online Learning GRU (GBPUSD,H1) ONNX model Initialized
MS      0       14:53:35.935    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:53 New model loaded
DI      0       14:54:35.885    Online Learning GRU (GBPUSD,H1) Initilaizing ONNX model...
IL      0       14:54:35.908    Online Learning GRU (GBPUSD,H1) ONNX model Initialized
QE      0       14:54:35.908    Online Learning GRU (GBPUSD,H1) 2024.11.18 13:54 New model loaded

GRUモデルから予測を受け取るには、再帰型ニューラルネットワーク(RNN)がデータ内の時間的依存性を理解するのに役立つタイムステップ値を考慮する必要があります。

関数trainAndSaveGRU内では、タイムステップ値として10を使用しました。

def trainAndSaveGRU():

    data = getData(start=1, bars=1000)

     ....
     ....

    time_step = 10 

MQL5で最近閉じたバーから始めて、履歴から最後の10バー(タイムステップ)を収集してみましょう(これが本来のやり方です)

input int time_step = 10;
void OnTick()
  {
//---
     MqlRates rates[];
     CopyRates(symbol, timeframe, 1, time_step, rates); //copy the recent closed bar information
     
     vector classes = {0,1}; //Beware of how classes are organized in the target variable. use numpy.unique(y) to determine this array
     
     matrix X = matrix::Zeros(time_step, 6); // 6 columns
     for (int i=0; i<time_step; i++)
       {         
         vector row = {
                 (double)rates[i].time, 
                 rates[i].open, 
                 rates[i].high, 
                 rates[i].low, 
                 rates[i].close, 
                 (double)rates[i].tick_volume};
         
         X.Row(row, i);
       }     
     
     X = scaler.transform(X); //it's important to normalize the data  
     Comment(TimeCurrent(),"\nPredicted signal: ",gru.predict_bin(X, classes)==0?"Bearish":"Bullish");// if the predicted signal is 0 it means a bearish signal, otherwise it is a bullish signal
  }

結果


増分機械学習

訓練方法に関しては、一部のモデルは他のモデルよりも優れており、堅牢です。インターネットで「オンライン機械学習」を検索すると、ほとんどの人が、これは、より大きな訓練目標のために、少量のデータをモデルに再訓練するプロセスであると言います。

これに関する問題は、多くのモデルが、少量のデータサンプルが与えられた場合にはサポートされないか、適切に機能しないことです。

CatBoostのような最新の機械学習技術は、増分学習を念頭に置いています。この訓練方法はオンライン学習に使用でき、データを小さなチャンクに分割して初期モデルに再訓練できるため、ビッグデータで作業するときに大量のメモリを節約できます。

def getData(start = 1, bars = 1000):

    rates = mt5.copy_rates_from_pos("EURUSD", mt5.TIMEFRAME_H1, start, bars)

    df_rates = pd.DataFrame(rates)
                                                
    return df_rates

def trainIncrementally():

    # CatBoost model
    clf = CatBoostClassifier(
        task_type="CPU",
        iterations=2000,
        learning_rate=0.2,
        max_depth=1,
        verbose=0,
    )
    
    # Get big data
    big_data = getData(1, 10000)

    # Split into chunks of 1000 samples
    chunk_size = 1000
    chunks = [big_data[i:i + chunk_size].copy() for i in range(0, len(big_data), chunk_size)]  # Use .copy() here    

    for i, chunk in enumerate(chunks):
            
        # Preparing the target variable

        chunk["future_open"] = chunk["open"].shift(-1)
        chunk["future_close"] = chunk["close"].shift(-1)

        target = []
        for row in range(chunk.shape[0]):
            if chunk["future_close"].iloc[row] > chunk["future_open"].iloc[row]:
                target.append(1)
            else:
                target.append(0)

        chunk["target"] = target

        chunk = chunk.dropna()

        # Check if we were able to receive some data

        if (len(chunk)<=0):
            print("Failed to obtain chunk from Metatrader5, error = ",mt5.last_error())
            mt5.shutdown()

        X = chunk.drop(columns = ["spread","real_volume","future_close","future_open","target"])
        y = chunk["target"]

        X_train, X_val, y_train, y_val = train_test_split(X, y, train_size=0.8, random_state=42)

        if i == 0:
            # Initial training, training the model for the first time
            clf.fit(X_train, y_train, eval_set=(X_val, y_val))

            y_pred = clf.predict(X_val)
            print(f"---> Acc score: {accuracy_score(y_pred=y_pred, y_true=y_val)}")
        else:
            # Incremental training by using the intial trained model
            clf.fit(X_train, y_train, init_model="model.cbm", eval_set=(X_val, y_val))

            y_pred = clf.predict(X_val)
            print(f"---> Acc score: {accuracy_score(y_pred=y_pred, y_true=y_val)}")
        
        # Save the model
        clf.save_model("model.cbm")
        print(f"Chunk {i + 1}/{len(chunks)} processed and model saved.")

結果

---> Acc score: 0.555
Chunk 1/10 processed and model saved.
---> Acc score: 0.505
Chunk 2/10 processed and model saved.
---> Acc score: 0.55
Chunk 3/10 processed and model saved.
---> Acc score: 0.565
Chunk 4/10 processed and model saved.
---> Acc score: 0.495
Chunk 5/10 processed and model saved.
---> Acc score: 0.55
Chunk 6/10 processed and model saved.
---> Acc score: 0.555
Chunk 7/10 processed and model saved.
---> Acc score: 0.52
Chunk 8/10 processed and model saved.
---> Acc score: 0.455
Chunk 9/10 processed and model saved.
---> Acc score: 0.535
Chunk 10/10 processed and model saved.

モデルを段階的に構築しながら同じオンライン学習アーキテクチャに従い、最終モデルを MetaTrader 5 で使用するためにCommonフォルダに ONNX 形式で保存できます。


最後に

オンライン学習は、手動による介入を最小限に抑えながらモデルを継続的に更新するための優れたアプローチです。このインフラストラクチャを実装することで、モデルが最新の市場動向に沿った状態を維持し、新しい情報に迅速に適応できるようになります。ただし、オンライン学習では、モデルがデータの処理順序に非常に敏感になる場合があり、モデルと訓練情報が人間の視点から論理的に意味を成すかどうかを確認するために、人間による監視が必要になることが非常に多いことに注意することが重要です。

すべてが期待どおりに進むようにするには、学習プロセスの自動化とモデルの定期的な評価の間で適切なバランスを見つける必要があります。


添付ファイルの表


インフラストラクチャ(フォルダ)

ファイル 説明と使用法

 Pythonクライアント


catboost_models.py
gru_models.py
main.py
incremental_learning.py


CatBoostモデルのファイル
GRUモデルのファイル
すべてをまとめるためのメインのPythonファイル
CatBoostモデルの増分学習の実装


Commonフォルダ



catboost.H1.onnx
gru.H1.onnx
gru.H1.standard_scaler_mean.bin
gru.H1.standard_scaler_scale.bin

 ONNX形式のすべてのAIモデルとバイナリ形式のスケーラーファイルを含むフォルダ
 
MetaTrader 5 (MQL5)


 Experts\Online Learning Catboost.mq5
 Experts\Online Learning GRU.mq5
 Include\CatBoost.mqh
 Include\GRU.mqh
 Include\preprocessing.mqh
 
MQL5でのCatBoostモデルの実装
MQL5でのGRUモデルの実装
ONNX形式でCatBoostモデルを初期化および導入するためのライブラリファイル
ONNX形式でGRUモデルを初期化および導入するためのライブラリファイル
MLモデルの使用のためにデータを正規化するためのStandardScalerを含むライブラリファイル

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

添付されたファイル |
Attachments.zip (475 KB)
最後のコメント | ディスカッションに移動 (3)
Phuc Hung
Phuc Hung | 14 3月 2025 において 08:49

オメガ・J・ミシグワ

この記事のために、あなたが使用しているpythonのバージョンを尋ねました。私はそれをインストールし、ライブラリの競合があります。

コンフリクトの原因は

ユーザは protobuf==3.20.3 を要求しました。

onnx 1.17.0 は protobuf>=3.20.2 に依存しています。

onnxconverter-common 1.14.0はprotobuf==3.20.2に依存します。


その後、提案されたとおりにバージョンを編集したところ、別のインストールエラーが発生しました。


これを解決するには、次のことを試してみてください:

1. 指定したパッケージバージョンの範囲を緩める。

2. パッケージのバージョンを削除して、pipが依存関係の衝突を解決できるようにする。


コンフリクトの原因

ユーザが指定した protobuf==3.20.2

onnx 1.17.0 は protobuf>=3.20.2 に依存しています。

onnxconverter-common 1.14.0 は protobuf==3.20.2 に依存します。

tensorboard 2.18.0 は protobuf!=4.24.0 および >=3.19.6 に依存します

tensorflow-intel 2.18.0 は protobuf!=4.21.0, !=4.21.1, !=4.21.2, !=4.21.3, !=4.21.4, !=4.21.5, <6.0.0dev and >=3.20.3 に依存します。


これを解決するには、次のことを試してみてください:

1. 指定したパッケージバージョンの範囲を緩める。

2. パッケージのバージョンを削除して、pip が依存関係の衝突を解決できるようにする。



より詳しい指示をお願いします。

panovq
panovq | 13 8月 2025 において 15:48
何がそうさせるのか、はっきりさせてもいいですか?
Miguel Angel Vico Alba
Miguel Angel Vico Alba | 13 8月 2025 において 19:02
panovq # 何がそうさせるのか、はっきりさせてもいいですか?
ご自由にどうぞ。
段階的特徴量選択の基準としての相互情報量 段階的特徴量選択の基準としての相互情報量
この記事では、最適な予測変数セットと目的変数との相互情報量に基づく段階的特徴量選択のMQL5実装を紹介します。
MQL5経済指標カレンダーを使った取引(第3回):通貨、重要度、時間フィルターの追加 MQL5経済指標カレンダーを使った取引(第3回):通貨、重要度、時間フィルターの追加
この記事では、MQL5経済カレンダーダッシュボードにフィルターを実装し、通貨、重要度、時間ごとにニュースイベントの表示を絞り込みます。まず、各カテゴリのフィルター基準を設定し、それをダッシュボードに組み込むことで、関連するイベントのみが表示されるようにします。最後に、各フィルターが動的に更新され、トレーダーにとって必要な、焦点を絞ったリアルタイムの経済情報が提供されるようにします。
プライスアクション分析ツールキットの開発(第3回):Analytics Master EA プライスアクション分析ツールキットの開発(第3回):Analytics Master EA
シンプルな取引スクリプトから完全に機能するエキスパートアドバイザー(EA)に移行することで、取引エクスペリエンスが大幅に向上します。チャートを自動で監視し、バックグラウンドで重要な計算を実行し、さらに2時間ごとに定期的な更新を提供するシステムを想像してみてください。このEAは、的確な取引判断を下すために不可欠な主要指標を分析し、常に最新の情報を取得して戦略を効果的に調整できるようにします。
MQL5で取引管理者パネルを作成する(第7回):信頼できるユーザー、回復、暗号化 MQL5で取引管理者パネルを作成する(第7回):信頼できるユーザー、回復、暗号化
チャートの更新や管理パネル(Admin Panel) EAとのチャットに新しいペアを追加する際、または端末を再起動するたびにトリガーされるセキュリティプロンプトは、時に煩わしく感じられることがあります。このディスカッションでは、ログイン試行回数を追跡して信頼できるユーザーを識別する機能を検討し、実装します。一定回数の試行に失敗した場合、アプリケーションは高度なログイン手続きに移行し、パスコードを忘れたユーザーが回復できるようにします。さらに、管理パネルに暗号化を効果的に統合してセキュリティを強化する方法についても取り上げます。