MQL5での取引戦略の自動化(第21回):適応学習率によるニューラルネットワーク取引の強化
はじめに
前回の記事(第20回)では、商品チャンネル指数(CCI: Commodity Channel Index)とオーサムオシレータ(AO)を用いた多銘柄戦略を開発し、MetaQuotes Language 5 (MQL5)で複数通貨ペアにおけるトレンド反転トレードを自動化しました。堅牢なシグナル生成とリスク管理も実装済みです。第21回では、ニューラルネットワーク(英語Wiki)を活用した取引戦略に進み、適応型学習率(英語Wiki)メカニズムによって市場動向の予測精度を最適化します。本記事では以下のトピックを扱います。
本記事を読み終える頃には、学習率の動的調整を活用したニューラルネットワーク取引システムをMQL5で構築できるようになり、さらに改良を加える準備が整うでしょう。それでは、始めましょう。
適応型ニューラルネットワーク学習率戦略の理解
第20回では、商品チャンネル指数(CCI: Commodity Channel Index)とオーサムオシレータ(AO: Awesome Oscillator)を活用し、複数の通貨ペアにわたるトレンド反転取引を自動化できる多通貨取引システムを開発しました。第21回ではさらに進めて、ニューラルネットワークを活用した動的な取引戦略に取り組みます。ニューラルネットワークは、人間の脳内における相互接続されたニューロンの働きを模倣した計算モデルであり、多様な市場指標を処理し、市場のボラティリティに適応して学習プロセスを調整することで、価格変動の予測精度を高めることが可能です。私たちの目標は、柔軟かつ高性能なトレーディングシステムを構築し、ニューラルネットワークを用いて複雑な市場パターンを分析し、適応的学習率(英語Wiki)メカニズムによって精度を最適化した取引実行を実現することです。
ニューラルネットワークは、入力層・隠れ層・出力層というノード(ニューロン)の層構造で動作します。入力層では市場データを取り込み、隠れ層では複雑なパターンを抽出し、出力層では価格の上昇または下降といった売買シグナルを生成します。順伝播では、入力に対して重みやバイアスが適用され、層を通して変換されながら予測が得られます。以下を参照してください。

この過程で重要な要素となるのが活性化関数です。活性化関数は変換に非線形性を導入することで、ネットワークが複雑な関係性をモデル化できるようにします。例えばシグモイド関数は出力を0から1の範囲にマッピングするため、売買のような二値分類タスクに適しています。さらにバックプロパゲーション(誤差逆伝播法)によって予測誤差から逆方向に重みやバイアスを調整し、時間をかけて精度を高めていきます。以下を参照してください。

ネットワークを動的にするために、私たちは適応型学習率戦略を実装する計画です。これは、誤差逆伝播において重みの更新速度を調整するものであり、予測が市場の結果と一致したときには学習を加速させ、誤差が急増したときには学習を減速させることで、安定性を確保します。
私たちは、移動平均やモメンタム指標といった市場インジケーターをニューラルネットワークの入力層に与え、それを隠れ層で処理してパターンを検出し、出力層で信頼性の高い売買シグナルを生成するシステムを設計する計画です。出力層のニューロンの変換にはシグモイド活性化関数を使用し、滑らかで解釈しやすい予測を得ることを目指しています。これを選んでいる理由は、出力を2つの選択肢にできるからです。また、利用可能な他の関数の例についても検討しています。

さらに、学習性能に基づいて学習率を動的に調整し、市場のボラティリティに合わせて隠れ層のニューロン数を適応させることで、複雑さと効率性のバランスを取った柔軟なシステムを構築します。この戦略は、堅牢な実装と徹底したテストの基盤となります。入力としては、2種類の移動平均、相対力指数(RSI: Relative Strength Index)、Average True Range (ATR)を利用し、シグナルが得られた段階でトレードを開始します。以下は、実装後に目指しているイメージの可視化です。

MQL5での実装
MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲータに移動して、Indicatorsフォルダを見つけ、[新規作成]タブをクリックして、表示される手順に従ってファイルを作成します。作成が完了したら、コーディング環境では、まず使用する入力変数や構造体、クラスを宣言します。本戦略では、オブジェクト指向プログラミング(OOP)アプローチを適用したいと考えているためです。
//+------------------------------------------------------------------+ //| Neural Networks Propagation EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #include <Trade/Trade.mqh> CTrade tradeObject; //--- Instantiate trade object for executing trades // Input parameters with clear, meaningful names input double LotSize = 0.1; // Lot Size input int StopLossPoints = 100; // Stop Loss (points) input int TakeProfitPoints = 100; // Take Profit (points) input int MinHiddenNeurons = 10; // Minimum Hidden Neurons input int MaxHiddenNeurons = 50; // Maximum Hidden Neurons input int TrainingBarCount = 1000; // Training Bars input double MinPredictionAccuracy = 0.7; // Minimum Prediction Accuracy input double MinLearningRate = 0.01; // Minimum Learning Rate input double MaxLearningRate = 0.5; // Maximum Learning Rate input string InputToHiddenWeights = "0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1"; // Input-to-Hidden Weights input string HiddenToOutputWeights = "0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1"; // Hidden-to-Output Weights input string HiddenBiases = "0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1,0.1,-0.1"; // Hidden Biases input string OutputBiases = "0.1,-0.1"; // Output Biases // Neural Network Structure Constants const int INPUT_NEURON_COUNT = 10; //--- Define number of input neurons const int OUTPUT_NEURON_COUNT = 2; //--- Define number of output neurons const int MAX_HISTORY_SIZE = 10; //--- Define maximum history size for accuracy and error tracking // Indicator handles int ma20IndicatorHandle; //--- Handle for 20-period moving average int ma50IndicatorHandle; //--- Handle for 50-period moving average int rsiIndicatorHandle; //--- Handle for RSI indicator int atrIndicatorHandle; //--- Handle for ATR indicator // Training related structures struct TrainingData { double inputValues[]; //--- Array to store input values for training double targetValues[]; //--- Array to store target values for training };
ここでは、ニューラルネットワークを用いたトレーディング戦略の基礎を、適応学習率付きで構築するために、取引実行およびデータ処理のための基本的なコンポーネントを初期化します。Trade.mqhライブラリをインクルードし、CTradeクラスのtradeObjectインスタンスを作成することで、取引操作を管理できるようにし、ニューラルネットワークの予測に基づいた売買注文を実行できるようにします。
次に、戦略を設定するための入力パラメータを定義します。LotSizeは取引量を制御し、StopLossPointsとTakeProfitPointsはリスク管理のために設定します。また、MinHiddenNeuronsとMaxHiddenNeuronsでニューラルネットワークの隠れ層ニューロン数の範囲を定義します。さらに、TrainingBarCountは学習に使用する過去バーの数を指定し、MinPredictionAccuracyは精度の閾値として設定します。MinLearningRateとMaxLearningRateは適応学習率の範囲を制限するために用います。加えて、InputToHiddenWeights、HiddenToOutputWeights、HiddenBiases、OutputBiasesといった文字列形式の入力を用いて、ニューラルネットワークの重みやバイアスを初期化でき、事前設定またはデフォルト設定を利用可能にします。
次に、ニューラルネットワークの構造に関する定数を定義します。INPUT_NEURON_COUNTは市場データ入力用に10と設定し、OUTPUT_NEURON_COUNTは売買シグナルの出力として2とします。MAX_HISTORY_SIZEは学習精度や誤差を追跡するために10と設定します。また、20期間および50期間の移動平均線、RSI、ATRを参照するために、インジケーターハンドル「ma20IndicatorHandle」「ma50IndicatorHandle」「rsiIndicatorHandle」「atrIndicatorHandle」を作成し、これらをニューラルネットワークへの市場データ入力として利用します。最後に、TrainingData構造体を定義し、inputValuesとtargetValuesの配列を用いて、学習用の入力特徴量および期待される出力を保存できるようにします。これにより、ニューラルネットワークの学習プロセスにおけるデータ管理を効率的におこなうことができます。以下のようにデータを格納していく予定です。

次に、頻繁に使用する基本的なメンバ変数を保持するクラスを定義する必要があります
// Neural Network Class class CNeuralNetwork { private: int inputNeuronCount; //--- Number of input neurons int hiddenNeuronCount; //--- Number of hidden neurons int outputNeuronCount; //--- Number of output neurons double inputLayer[]; //--- Array for input layer values double hiddenLayer[]; //--- Array for hidden layer values double outputLayer[]; //--- Array for output layer values double inputToHiddenWeights[]; //--- Weights between input and hidden layers double hiddenToOutputWeights[];//--- Weights between hidden and output layers double hiddenLayerBiases[]; //--- Biases for hidden layer double outputLayerBiases[]; //--- Biases for output layer double outputDeltas[]; //--- Delta values for output layer double hiddenDeltas[]; //--- Delta values for hidden layer double trainingError; //--- Current training error double currentLearningRate; //--- Current learning rate double accuracyHistory[]; //--- History of training accuracy double errorHistory[]; //--- History of training errors int historyRecordCount; //--- Number of recorded history entries };
ここでは、CNeuralNetworkクラスを作成して、ニューラルネットワークのコア構造を実装します。ネットワークのアーキテクチャおよび学習プロセスを管理するために、privateメンバー変数を定義します。まず、入力層、隠れ層、および出力層のニューロン数を設定するためにinputNeuronCount、hiddenNeuronCount、outputNeuronCountを定義します。これにより、市場データを処理し、売買シグナルを生成する戦略の設計に沿った構成が可能になります。
層の値を格納するための配列も用意します。市場指標の入力を保持するinputLayer、中間パターンを処理するhiddenLayer、および売買予測を生成するoutputLayerです。ニューラルネットワークの計算をおこなうために、層間の重み接続を保持するinputToHiddenWeightsおよびhiddenToOutputWeights配列、隠れ層および出力層のバイアス調整用にhiddenLayerBiasesおよびoutputLayerBiasesを作成します。バックプロパゲーションを実装するために、誤差勾配を格納するoutputDeltasおよびhiddenDeltasを定義し、学習中の重みおよびバイアスの更新を可能にします。
さらに、現在の誤差を追跡するためのtrainingError、適応学習率を管理するcurrentLearningRate、学習パフォーマンスを時間経過で監視するaccuracyHistoryおよびerrorHistory配列、記録されたエントリ数を保持するhistoryRecordCountを含めています。これにより、ネットワークはパフォーマンスの傾向に応じて学習プロセスを動的に調整できます。このクラスは、順伝播、逆伝播、および適応学習率調整を実装するための基盤を形成します。また、privateアクセス修飾子内で、文字列入力を配列に変換して利用するためのメソッドを定義することも可能です。
// Parse comma-separated string to array bool ParseStringToArray(string inputString, double &output[], int expectedSize) { //--- Check if input string is empty if(inputString == "") return false; string values[]; //--- Initialize array for parsed values ArrayResize(values, 0); //--- Split input string by comma int count = StringSplit(inputString, 44, values); //--- Check if string splitting failed if(count <= 0) { Print("Error: StringSplit failed for input: ", inputString, ". Error code: ", GetLastError()); return false; } //--- Verify correct number of values if(count != expectedSize) { Print("Error: Invalid number of values in input string. Expected: ", expectedSize, ", Got: ", count); return false; } //--- Resize output array to expected size ArrayResize(output, expectedSize); //--- Convert string values to doubles and normalize for(int i = 0; i < count; i++) { output[i] = StringToDouble(values[i]); //--- Clamp values between -1.0 and 1.0 if(MathAbs(output[i]) > 1.0) output[i] = MathMax(-1.0, MathMin(1.0, output[i])); } return true; }
CNeuralNetworkクラス内にParseStringToArray関数を実装し、ニューラルネットワークの重みおよびバイアス用のカンマ区切り文字列を処理します。まず、inputStringが空でないかを確認し、無効な場合はfalseを返します。カンマ区切りでStringSplitを用いて文字列をvaluesに分割します。分割に失敗した場合やcountがexpectedSizeと一致しない場合は、Print関数でエラーを記録し、falseを返します。outputをArrayResizeでリサイズし、valuesをStringToDoubleで数値に変換し、MathMaxおよびMathMinを用いて-1.0から1.0の範囲に正規化します。解析が成功した場合はtrueを返します。その他のヘルパー関数は、以下のようにパブリックアクセス修飾子内で宣言しておき、後で定義することが可能です。
public: CNeuralNetwork(int inputs, int hidden, int outputs); //--- Constructor void InitializeWeights(); //--- Initialize network weights double Sigmoid(double x); //--- Apply sigmoid activation function void ForwardPropagate(); //--- Perform forward propagation void Backpropagate(const double &targets[]); //--- Perform backpropagation void SetInput(double &inputs[]); //--- Set input values void GetOutput(double &outputs[]); //--- Retrieve output values double TrainOnHistoricalData(TrainingData &data[]); //--- Train on historical data void UpdateNetworkWithRecentData(); //--- Update with recent data void InitializeTraining(); //--- Initialize training arrays void ResizeNetwork(int newHiddenNeurons); //--- Resize network void AdjustLearningRate(); //--- Adjust learning rate dynamically double GetRecentAccuracy(); //--- Get recent training accuracy double GetRecentError(); //--- Get recent training error bool ShouldRetrain(); //--- Check if retraining is needed double CalculateDynamicNeurons(); //--- Calculate dynamic neuron count int GetHiddenNeurons() { return hiddenNeuronCount; //--- Get current hidden neuron count }
ここでは、CNeuralNetworkクラスのpublicインターフェースを定義します。CNeuralNetworkコンストラクタを作成し、inputs、hidden、outputsを用いてネットワークを構築します。また、InitializeWeightsで重みおよびバイアスを設定します。活性化関数としてSigmoidを実装し、順伝播処理にはForwardPropagate、目標値targetsに基づく更新にはBackpropagateを使用します。入力および出力を扱うために、SetInputおよびGetOutputを用意します。
さらに、TrainOnHistoricalDataで過去データを用いた学習をおこない、UpdateNetworkWithRecentDataで最新データに対応します。配列初期化用にInitializeTrainingを作成し、隠れ層のニューロン数変更にはResizeNetwork、動的学習率調整にはAdjustLearningRateを実装します。また、GetRecentAccuracy、GetRecentError、ShouldRetrain、CalculateDynamicNeurons、GetHiddenNeuronsを用いて、学習状況の監視やhiddenNeuronCountの適応をおこないます。これで、以下のように関数の実装と定義を開始する準備が整いました。
// Constructor CNeuralNetwork::CNeuralNetwork(int inputs, int hidden, int outputs) { //--- Set input neuron count inputNeuronCount = inputs; //--- Set output neuron count outputNeuronCount = outputs; //--- Initialize learning rate to minimum currentLearningRate = MinLearningRate; //--- Set hidden neuron count hiddenNeuronCount = hidden; //--- Ensure hidden neurons within bounds if(hiddenNeuronCount < MinHiddenNeurons) hiddenNeuronCount = MinHiddenNeurons; if(hiddenNeuronCount > MaxHiddenNeurons) hiddenNeuronCount = MaxHiddenNeurons; //--- Resize input layer array ArrayResize(inputLayer, inputs); //--- Resize hidden layer array ArrayResize(hiddenLayer, hiddenNeuronCount); //--- Resize output layer array ArrayResize(outputLayer, outputs); //--- Resize input-to-hidden weights array ArrayResize(inputToHiddenWeights, inputs * hiddenNeuronCount); //--- Resize hidden-to-output weights array ArrayResize(hiddenToOutputWeights, hiddenNeuronCount * outputs); //--- Resize hidden biases array ArrayResize(hiddenLayerBiases, hiddenNeuronCount); //--- Resize output biases array ArrayResize(outputLayerBiases, outputs); //--- Resize accuracy history array ArrayResize(accuracyHistory, MAX_HISTORY_SIZE); //--- Resize error history array ArrayResize(errorHistory, MAX_HISTORY_SIZE); //--- Initialize history record count historyRecordCount = 0; //--- Initialize training error trainingError = 0.0; //--- Initialize network weights InitializeWeights(); //--- Initialize training arrays InitializeTraining(); } // Initialize training arrays void CNeuralNetwork::InitializeTraining() { //--- Resize output deltas array ArrayResize(outputDeltas, outputNeuronCount); //--- Resize hidden deltas array ArrayResize(hiddenDeltas, hiddenNeuronCount); } // Initialize weights void CNeuralNetwork::InitializeWeights() { //--- Track if weights and biases are set bool isInputToHiddenWeightsSet = false; bool isHiddenToOutputWeightsSet = false; bool isHiddenBiasesSet = false; bool isOutputBiasesSet = false; double tempInputToHiddenWeights[]; double tempHiddenToOutputWeights[]; double tempHiddenBiases[]; double tempOutputBiases[]; //--- Parse and set input-to-hidden weights if provided if(InputToHiddenWeights != "" && ParseStringToArray(InputToHiddenWeights, tempInputToHiddenWeights, inputNeuronCount * hiddenNeuronCount)) { //--- Copy parsed weights to main array ArrayCopy(inputToHiddenWeights, tempInputToHiddenWeights); isInputToHiddenWeightsSet = true; //--- Log weight initialization Print("Initialized input-to-hidden weights from input: ", InputToHiddenWeights); } //--- Parse and set hidden-to-output weights if provided if(HiddenToOutputWeights != "" && ParseStringToArray(HiddenToOutputWeights, tempHiddenToOutputWeights, hiddenNeuronCount * outputNeuronCount)) { //--- Copy parsed weights to main array ArrayCopy(hiddenToOutputWeights, tempHiddenToOutputWeights); isHiddenToOutputWeightsSet = true; //--- Log weight initialization Print("Initialized hidden-to-output weights from input: ", HiddenToOutputWeights); } //--- Parse and set hidden biases if provided if(HiddenBiases != "" && ParseStringToArray(HiddenBiases, tempHiddenBiases, hiddenNeuronCount)) { //--- Copy parsed biases to main array ArrayCopy(hiddenLayerBiases, tempHiddenBiases); isHiddenBiasesSet = true; //--- Log bias initialization Print("Initialized hidden biases from input: ", HiddenBiases); } //--- Parse and set output biases if provided if(OutputBiases != "" && ParseStringToArray(OutputBiases, tempOutputBiases, outputNeuronCount)) { //--- Copy parsed biases to main array ArrayCopy(outputLayerBiases, tempOutputBiases); isOutputBiasesSet = true; //--- Log bias initialization Print("Initialized output biases from input: ", OutputBiases); } //--- Initialize input-to-hidden weights randomly if not set if(!isInputToHiddenWeightsSet) { for(int i = 0; i < ArraySize(inputToHiddenWeights); i++) inputToHiddenWeights[i] = (MathRand() / 32767.0) * 2 - 1; } //--- Initialize hidden-to-output weights randomly if not set if(!isHiddenToOutputWeightsSet) { for(int i = 0; i < ArraySize(hiddenToOutputWeights); i++) hiddenToOutputWeights[i] = (MathRand() / 32767.0) * 2 - 1; } //--- Initialize hidden biases randomly if not set if(!isHiddenBiasesSet) { for(int i = 0; i < ArraySize(hiddenLayerBiases); i++) hiddenLayerBiases[i] = (MathRand() / 32767.0) * 2 - 1; } //--- Initialize output biases randomly if not set if(!isOutputBiasesSet) { for(int i = 0; i < ArraySize(outputLayerBiases); i++) outputLayerBiases[i] = (MathRand() / 32767.0) * 2 - 1; } } // Sigmoid activation function double CNeuralNetwork::Sigmoid(double x) { //--- Compute and return sigmoid value return 1.0 / (1.0 + MathExp(-x)); } // Set input void CNeuralNetwork::SetInput(double &inputs[]) { //--- Check for input array size mismatch if(ArraySize(inputs) != inputNeuronCount) { Print("Error: Input array size mismatch. Expected: ", inputNeuronCount, ", Got: ", ArraySize(inputs)); return; } //--- Copy inputs to input layer ArrayCopy(inputLayer, inputs); } // Forward propagation void CNeuralNetwork::ForwardPropagate() { //--- Compute hidden layer values for(int j = 0; j < hiddenNeuronCount; j++) { double sum = 0; //--- Calculate weighted sum for hidden neuron for(int i = 0; i < inputNeuronCount; i++) sum += inputLayer[i] * inputToHiddenWeights[i * hiddenNeuronCount + j]; //--- Apply sigmoid activation hiddenLayer[j] = Sigmoid(sum + hiddenLayerBiases[j]); } //--- Compute output layer values for(int j = 0; j < outputNeuronCount; j++) { double sum = 0; //--- Calculate weighted sum for output neuron for(int i = 0; i < hiddenNeuronCount; i++) sum += hiddenLayer[i] * hiddenToOutputWeights[i * outputNeuronCount + j]; //--- Apply sigmoid activation outputLayer[j] = Sigmoid(sum + outputLayerBiases[j]); } } // Get output void CNeuralNetwork::GetOutput(double &outputs[]) { //--- Resize output array ArrayResize(outputs, outputNeuronCount); //--- Copy output layer to outputs ArrayCopy(outputs, outputLayer); } // Backpropagation void CNeuralNetwork::Backpropagate(const double &targets[]) { //--- Calculate output layer deltas for(int i = 0; i < outputNeuronCount; i++) { double output = outputLayer[i]; //--- Compute delta for output neuron outputDeltas[i] = output * (1 - output) * (targets[i] - output); } //--- Calculate hidden layer deltas for(int i = 0; i < hiddenNeuronCount; i++) { double error = 0; //--- Sum weighted errors from output layer for(int j = 0; j < outputNeuronCount; j++) error += outputDeltas[j] * hiddenToOutputWeights[i * outputNeuronCount + j]; double output = hiddenLayer[i]; //--- Compute delta for hidden neuron hiddenDeltas[i] = output * (1 - output) * error; } //--- Update hidden-to-output weights for(int i = 0; i < hiddenNeuronCount; i++) { for(int j = 0; j < outputNeuronCount; j++) { int idx = i * outputNeuronCount + j; //--- Adjust weight based on learning rate and delta hiddenToOutputWeights[idx] += currentLearningRate * outputDeltas[j] * hiddenLayer[i]; } } //--- Update input-to-hidden weights for(int i = 0; i < inputNeuronCount; i++) { for(int j = 0; j < hiddenNeuronCount; j++) { int idx = i * hiddenNeuronCount + j; //--- Adjust weight based on learning rate and delta inputToHiddenWeights[idx] += currentLearningRate * hiddenDeltas[j] * inputLayer[i]; } } //--- Update hidden biases for(int i = 0; i < hiddenNeuronCount; i++) //--- Adjust bias based on learning rate and delta hiddenLayerBiases[i] += currentLearningRate * hiddenDeltas[i]; //--- Update output biases for(int i = 0; i < outputNeuronCount; i++) //--- Adjust bias based on learning rate and delta outputLayerBiases[i] += currentLearningRate * outputDeltas[i]; } // Resize network (adjust hidden neurons) void CNeuralNetwork::ResizeNetwork(int newHiddenNeurons) { //--- Clamp new neuron count within bounds newHiddenNeurons = MathMax(MinHiddenNeurons, MathMin(newHiddenNeurons, MaxHiddenNeurons)); //--- Check if resizing is necessary if(newHiddenNeurons == hiddenNeuronCount) return; //--- Log resizing information Print("Resizing network. New hidden neurons: ", newHiddenNeurons, ", Previous: ", hiddenNeuronCount); //--- Update hidden neuron count hiddenNeuronCount = newHiddenNeurons; //--- Resize hidden layer array ArrayResize(hiddenLayer, hiddenNeuronCount); //--- Resize input-to-hidden weights array ArrayResize(inputToHiddenWeights, inputNeuronCount * hiddenNeuronCount); //--- Resize hidden-to-output weights array ArrayResize(hiddenToOutputWeights, hiddenNeuronCount * outputNeuronCount); //--- Resize hidden biases array ArrayResize(hiddenLayerBiases, hiddenNeuronCount); //--- Resize hidden deltas array ArrayResize(hiddenDeltas, hiddenNeuronCount); //--- Reinitialize weights InitializeWeights(); }
ここでは、ニューラルネットワークの重要なコンポーネントを実装します。CNeuralNetworkコンストラクタを作成し、inputs、hidden、outputsを受け取り、inputNeuronCount、outputNeuronCount、hiddenNeuronCountを設定します。hiddenNeuronCountはMinHiddenNeuronsとMaxHiddenNeuronsの範囲内に制限されます。クラスプロトタイプの詳細な説明は前段でおこなっているため、ここでは簡潔に扱います。
次に、currentLearningRateをMinLearningRateに初期化し、inputLayer、hiddenLayer、outputLayer、inputToHiddenWeights、hiddenToOutputWeights、hiddenLayerBiases、outputLayerBiases、accuracyHistory、errorHistoryなどの配列をArrayResizeでリサイズし、InitializeWeightsおよびInitializeTrainingを呼び出してネットワークを設定します。
InitializeTraining関数では、バックプロパゲーション用にoutputDeltasおよびhiddenDeltas配列をリサイズし、誤差勾配の適切な格納を確保します。InitializeWeights関数では、ParseStringToArrayを用いてInputToHiddenWeightsやHiddenToOutputWeights、HiddenLayerBiases、OutputLayerBiasesから重みとバイアスを読み込み、入力がない場合はMathRandで-1から1の範囲でランダムに初期化し、処理内容をPrint関数で記録します。Sigmoid関数は、与えられたxに対するシグモイド活性化値をMathExpを使って計算します。
SetInput関数では、inputsのサイズをArraySizeで確認し、inputLayerにコピーします。サイズが一致しない場合はPrint関数でエラーを記録します。ForwardPropagate関数は、重み付き和とSigmoid活性化関数、hiddenLayerBiasesおよびoutputLayerBiasesを加えてhiddenLayerとoutputLayerの値を計算します。GetOutput関数では、ArrayResizeでoutputsをリサイズし、ArrayCopyでoutputLayerの値をコピーします。
Backpropagate関数では、targetsを基にoutputDeltasおよびhiddenDeltasを計算し、currentLearningRateを用いてhiddenToOutputWeights、inputToHiddenWeights、hiddenLayerBiases、outputLayerBiasesを更新します。最後に、ResizeNetwork関数では、hiddenNeuronCountを範囲内で調整し、hiddenLayerやhiddenDeltasなどの配列をリサイズし、InitializeWeightsで重みを再初期化し、変更内容をPrint関数で記録します。次のステップとして、誤差の傾向に基づいて学習率を調整する必要があるため、ここを慎重に詳しく見ていきます。
// Adjust learning rate based on error trend void CNeuralNetwork::AdjustLearningRate() { //--- Check if enough history exists if(historyRecordCount < 2) return; //--- Get last and previous errors double lastError = errorHistory[historyRecordCount - 1]; double prevError = errorHistory[historyRecordCount - 2]; //--- Calculate error difference double errorDiff = lastError - prevError; //--- Increase learning rate if error decreased if(lastError < prevError) currentLearningRate = MathMin(currentLearningRate * 1.05, MaxLearningRate); //--- Decrease learning rate if error increased significantly else if(lastError > prevError * 1.2) currentLearningRate = MathMax(currentLearningRate * 0.9, MinLearningRate); //--- Slightly decrease learning rate otherwise else currentLearningRate = MathMax(currentLearningRate * 0.99, MinLearningRate); //--- Log learning rate adjustment Print("Adjusted learning rate to: ", currentLearningRate, ", Last Error: ", lastError, ", Prev Error: ", prevError, ", Error Diff: ", errorDiff); }
CNeuralNetworkクラス内にAdjustLearningRate関数を作成し、適応学習率メカニズムを実装します。まず、historyRecordCountが少なくとも2であるかを確認し、errorHistory配列に十分なデータがない場合は無効な調整を避けるために処理を終了します。最新の誤差をlastError、前回の誤差をprevErrorとしてerrorHistoryから取得し、その差分をerrorDiffとして計算し、学習性能を評価します。
学習率の調整には、MathMin関数を用いて、lastErrorがprevErrorより小さい場合はcurrentLearningRateを5%増加させ、上限をMaxLearningRateに制限します。一方、lastErrorがprevErrorを20%以上上回る場合はMathMax関数で10%減少させ、下限をMinLearningRate以上に維持します。それ以外の場合は、MathMaxを用いてcurrentLearningRateをわずかに1%減少させます。最後に、Print関数で調整後のcurrentLearningRate、lastError、prevError、およびerrorDiffを記録し、ニューラルネットワークの学習プロセスを動的に最適化します。さらに、ATRインジケーターを用いて隠れ層のニューロン数を動的に計算し、市場のボラティリティに応じてhiddenNeuronCountを調整できるようにします。
// Calculate dynamic number of hidden neurons based on ATR double CNeuralNetwork::CalculateDynamicNeurons() { double atrValues[]; //--- Set ATR array as series ArraySetAsSeries(atrValues, true); //--- Copy ATR buffer if(CopyBuffer(atrIndicatorHandle, 0, 0, 10, atrValues) < 10) { Print("Error: Failed to copy ATR for dynamic neurons. Using default: ", hiddenNeuronCount); return hiddenNeuronCount; } //--- Calculate average ATR double avgATR = 0; for(int i = 0; i < 10; i++) avgATR += atrValues[i]; avgATR /= 10; //--- Get current close price double closePrice = iClose(_Symbol, PERIOD_CURRENT, 0); //--- Check for valid close price if(MathAbs(closePrice) < 0.000001) { Print("Error: Invalid close price for ATR ratio. Using default: ", hiddenNeuronCount); return hiddenNeuronCount; } //--- Calculate ATR ratio double atrRatio = atrValues[0] / closePrice; //--- Compute new neuron count int newNeurons = MinHiddenNeurons + (int)((MaxHiddenNeurons - MinHiddenNeurons) * MathMin(atrRatio * 100, 1.0)); //--- Return clamped neuron count return MathMax(MinHiddenNeurons, MathMin(newNeurons, MaxHiddenNeurons)); }
市場のボラティリティに応じて隠れ層のニューロン数を動的に調整するために、まずatrValues配列を宣言し、ArraySetAsSeries関数を用いて時系列配列として設定します。次に、CopyBuffer関数を使用して、atrIndicatorHandleから直近10本分のATRデータをatrValuesに取得します。CopyBufferで10本分の値が取得できなかった場合は、Print関数でエラーを記録し、デフォルト値としてhiddenNeuronCountを返します。ここで配列を時系列として設定することで、最新データが配列の先頭インデックスに配置され、最初に使用できるようになります。以下にその表現を示します。

次に、atrValuesの直近10本分を合計して10で割り、平均ATRをavgATRとして算出します。iClose関数を使用して、_SymbolのPERIOD_CURRENTにおける最新の終値をclosePriceに取得します。closePriceがほぼゼロの場合は、Print関数でエラーを記録し、hiddenNeuronCountを返します。ATR比率をatrValues[0]をclosePriceで割ることで算出し、MinHiddenNeuronsとMaxHiddenNeuronsの範囲に基づいてスケーリングし、MathMinで上限を調整してnewNeuronsを計算します。最後にMathMaxおよびMathMinを用いてニューロン数を適切に制限し、市場状況に応じて動的に調整された値を返します。これにより、残りの学習およびネットワーク更新用の関数を以下のように定義する準備が整います。
// Train network on historical data double CNeuralNetwork::TrainOnHistoricalData(TrainingData &data[]) { const int maxEpochs = 100; //--- Maximum training epochs const double targetError = 0.01; //--- Target error threshold double accuracy = 0; //--- Training accuracy //--- Reset learning rate currentLearningRate = MinLearningRate; //--- Iterate through epochs for(int epoch = 0; epoch < maxEpochs; epoch++) { double totalError = 0; //--- Total error for epoch int correctPredictions = 0; //--- Count of correct predictions //--- Process each training sample for(int i = 0; i < ArraySize(data); i++) { //--- Check target array size if(ArraySize(data[i].targetValues) != outputNeuronCount) { Print("Error: Mismatch in targets size for training data at index ", i); continue; } //--- Set input values SetInput(data[i].inputValues); //--- Perform forward propagation ForwardPropagate(); double error = 0; //--- Calculate error for(int j = 0; j < outputNeuronCount; j++) error += MathPow(data[i].targetValues[j] - outputLayer[j], 2); totalError += error; //--- Check prediction correctness if((outputLayer[0] > outputLayer[1] && data[i].targetValues[0] > data[i].targetValues[1]) || (outputLayer[0] < outputLayer[1] && data[i].targetValues[0] < data[i].targetValues[1])) correctPredictions++; //--- Perform backpropagation Backpropagate(data[i].targetValues); } //--- Calculate accuracy accuracy = (double)correctPredictions / ArraySize(data); //--- Update training error trainingError = totalError / ArraySize(data); //--- Update history if(historyRecordCount < MAX_HISTORY_SIZE) { accuracyHistory[historyRecordCount] = accuracy; errorHistory[historyRecordCount] = trainingError; historyRecordCount++; } else { //--- Shift history arrays for(int i = 1; i < MAX_HISTORY_SIZE; i++) { accuracyHistory[i - 1] = accuracyHistory[i]; errorHistory[i - 1] = errorHistory[i]; } //--- Add new values accuracyHistory[MAX_HISTORY_SIZE - 1] = accuracy; errorHistory[MAX_HISTORY_SIZE - 1] = trainingError; } //--- Log error history update Print("Error history updated: ", errorHistory[historyRecordCount - 1]); //--- Adjust learning rate AdjustLearningRate(); //--- Log progress every 10 epochs if(epoch % 10 == 0) Print("Epoch ", epoch, ": Error = ", trainingError, ", Accuracy = ", accuracy); //--- Check for early stopping if(trainingError < targetError && accuracy >= MinPredictionAccuracy) break; } //--- Return final accuracy return accuracy; } // Update network with recent data void CNeuralNetwork::UpdateNetworkWithRecentData() { const int recentBarCount = 10; //--- Number of recent bars to process TrainingData recentData[]; //--- Collect recent training data if(!CollectTrainingData(recentData, recentBarCount)) return; //--- Process each recent data sample for(int i = 0; i < ArraySize(recentData); i++) { //--- Set input values SetInput(recentData[i].inputValues); //--- Perform forward propagation ForwardPropagate(); //--- Perform backpropagation Backpropagate(recentData[i].targetValues); } } // Get recent accuracy double CNeuralNetwork::GetRecentAccuracy() { //--- Check if history exists if(historyRecordCount == 0) return 0.0; //--- Return most recent accuracy return accuracyHistory[historyRecordCount - 1]; } // Get recent error double CNeuralNetwork::GetRecentError() { //--- Check if history exists if(historyRecordCount == 0) return 0.0; //--- Return most recent error return errorHistory[historyRecordCount - 1]; } // Check if retraining is needed bool CNeuralNetwork::ShouldRetrain() { //--- Check if enough history exists if(historyRecordCount < 2) return false; //--- Get recent metrics double recentAccuracy = GetRecentAccuracy(); double recentError = GetRecentError(); double prevError = errorHistory[historyRecordCount - 2]; //--- Determine if retraining is needed return (recentAccuracy < MinPredictionAccuracy || recentError > prevError * 1.5); }
ここでは、ニューラルネットワークの主要な学習および更新メカニズムを実装します。まず、TrainOnHistoricalData関数を作成し、TrainingData構造体配列を用いてネットワークを学習させます。maxEpochsを100、targetErrorを0.01に設定し、currentLearningRateをMinLearningRateにリセットします。各エポックでは、dataを順に処理し、data[i].targetValuesのサイズがoutputNeuronCountと一致するかを確認します。その後、SetInputでdata[i].inputValuesを入力し、ForwardPropagateで予測値を計算します。誤差はoutputLayerとdata[i].targetValuesの差をMathPowで算出し、correctPredictionsを記録したうえで、Backpropagateを呼び出して重み更新をおこないます。
次に、accuracyとtrainingErrorを更新し、historyRecordCountを用いてaccuracyHistoryおよびerrorHistoryに格納します。配列が一杯になった場合はシフト処理をおこないます。更新内容はPrint関数で記録し、AdjustLearningRateを呼び出して学習を最適化します。trainingErrorがtargetError以下、かつaccuracyがMinPredictionAccuracyを満たした場合は、早期終了します。
「UpdateNetworkWithRecentData」関数では、recentBarCountを10に設定し、CollectTrainingData関数を使ってrecentDataを収集します。その後、各サンプルについてSetInput、ForwardPropagate、Backpropagateを順に実行し、ネットワークを最新データに基づいて調整します。GetRecentAccuracy関数は、historyRecordCountがゼロでない場合にaccuracyHistoryの最新値を返し、ゼロであれば0.0を返します。同様にGetRecentError関数は、errorHistoryの最新値を返すか、履歴が存在しない場合は0.0を返します。
ShouldRetrain関数では、historyRecordCountが少なくとも2以上であることを確認し、GetRecentAccuracyとGetRecentErrorを使って、recentAccuracyおよびrecentErrorをそれぞれMinPredictionAccuracyおよび直前のerrorHistory値の1.5倍と比較します。これらの条件を満たした場合にtrueを返し、再学習が必要であると判断します。これで、実際の実装に使用するクラスインスタンスを作成できる準備が整いました。
// Global neural network instance CNeuralNetwork *neuralNetwork; //--- Global neural network object
ニューラルネットワークのグローバルスコープを確立するために、CNeuralNetworkクラスのインスタンスを指すポインタ「neuralNetwork」を作成します。このグローバルオブジェクトにより、学習、順伝播、逆伝播、適応学習率調整といったニューラルネットワークの機能へ集中管理されたアクセスが可能となり、エキスパートアドバイザー(EA)全体の処理にシームレスに統合されます。
neuralNetworkをグローバルに定義することで、初期化関数、ティック処理関数、終了処理関数において活用でき、戦略の予測機能および売買機能を一元的に管理できます。また、モジュール性を高めるために、入力データを初期化する関数や学習用データを収集する関数を定義することが可能です。
// Prepare inputs from market data void PrepareInputs(double &inputs[]) { //--- Resize inputs array if necessary if(ArraySize(inputs) != INPUT_NEURON_COUNT) ArrayResize(inputs, INPUT_NEURON_COUNT); double ma20Values[], ma50Values[], rsiValues[], atrValues[]; //--- Set arrays as series ArraySetAsSeries(ma20Values, true); ArraySetAsSeries(ma50Values, true); ArraySetAsSeries(rsiValues, true); ArraySetAsSeries(atrValues, true); //--- Copy MA20 buffer if(CopyBuffer(ma20IndicatorHandle, 0, 0, 2, ma20Values) <= 0) { Print("Error: Failed to copy MA20 buffer. Error code: ", GetLastError()); return; } //--- Copy MA50 buffer if(CopyBuffer(ma50IndicatorHandle, 0, 0, 2, ma50Values) <= 0) { Print("Error: Failed to copy MA50 buffer. Error code: ", GetLastError()); return; } //--- Copy RSI buffer if(CopyBuffer(rsiIndicatorHandle, 0, 0, 2, rsiValues) <= 0) { Print("Error: Failed to copy RSI buffer. Error code: ", GetLastError()); return; } //--- Copy ATR buffer if(CopyBuffer(atrIndicatorHandle, 0, 0, 2, atrValues) <= 0) { Print("Error: Failed to copy ATR buffer. Error code: ", GetLastError()); return; } //--- Check array sizes if(ArraySize(ma20Values) < 2 || ArraySize(ma50Values) < 2 || ArraySize(rsiValues) < 2 || ArraySize(atrValues) < 2) { Print("Error: Insufficient data in indicator arrays"); return; } //--- Get current market prices double closePrice = iClose(_Symbol, PERIOD_CURRENT, 0); double openPrice = iOpen(_Symbol, PERIOD_CURRENT, 0); double highPrice = iHigh(_Symbol, PERIOD_CURRENT, 0); double lowPrice = iLow(_Symbol, PERIOD_CURRENT, 0); //--- Calculate input features inputs[0] = (MathAbs(openPrice) > 0.000001) ? (closePrice - openPrice) / openPrice : 0; inputs[1] = (MathAbs(lowPrice) > 0.000001) ? (highPrice - lowPrice) / lowPrice : 0; inputs[2] = (MathAbs(ma20Values[0]) > 0.000001) ? (closePrice - ma20Values[0]) / ma20Values[0] : 0; inputs[3] = (MathAbs(ma50Values[0]) > 0.000001) ? (ma20Values[0] - ma50Values[0]) / ma50Values[0] : 0; inputs[4] = rsiValues[0] / 100.0; double highLowRange = highPrice - lowPrice; if(MathAbs(highLowRange) > 0.000001) { inputs[5] = (closePrice - lowPrice) / highLowRange; inputs[7] = MathAbs(closePrice - openPrice) / highLowRange; inputs[8] = (highPrice - closePrice) / highLowRange; inputs[9] = (closePrice - lowPrice) / highLowRange; } else { inputs[5] = 0; inputs[7] = 0; inputs[8] = 0; inputs[9] = 0; } inputs[6] = (MathAbs(closePrice) > 0.000001) ? atrValues[0] / closePrice : 0; //--- Log input preparation Print("Prepared inputs. Size: ", ArraySize(inputs)); } // Collect training data bool CollectTrainingData(TrainingData &data[], int barCount) { //--- Check output neuron count if(OUTPUT_NEURON_COUNT != 2) { Print("Error: OUTPUT_NEURON_COUNT must be 2 for binary classification."); return false; } //--- Resize data array ArrayResize(data, barCount); double ma20Values[], ma50Values[], rsiValues[], atrValues[]; //--- Set arrays as series ArraySetAsSeries(ma20Values, true); ArraySetAsSeries(ma50Values, true); ArraySetAsSeries(rsiValues, true); ArraySetAsSeries(atrValues, true); //--- Copy MA20 buffer if(CopyBuffer(ma20IndicatorHandle, 0, 0, barCount + 1, ma20Values) < barCount + 1) { Print("Error: Failed to copy MA20 buffer for training. Error code: ", GetLastError()); return false; } //--- Copy MA50 buffer if(CopyBuffer(ma50IndicatorHandle, 0, 0, barCount + 1, ma50Values) < barCount + 1) { Print("Error: Failed to copy MA50 buffer for training. Error code: ", GetLastError()); return false; } //--- Copy RSI buffer if(CopyBuffer(rsiIndicatorHandle, 0, 0, barCount + 1, rsiValues) < barCount + 1) { Print("Error: Failed to copy RSI buffer for training. Error code: ", GetLastError()); return false; } //--- Copy ATR buffer if(CopyBuffer(atrIndicatorHandle, 0, 0, barCount + 1, atrValues) < barCount + 1) { Print("Error: Failed to copy ATR buffer for training. Error code: ", GetLastError()); return false; } MqlRates priceData[]; //--- Set rates array as series ArraySetAsSeries(priceData, true); //--- Copy price data if(CopyRates(_Symbol, PERIOD_CURRENT, 0, barCount + 1, priceData) < barCount + 1) { Print("Error: Failed to copy rates for training. Error code: ", GetLastError()); return false; } //--- Process each bar for(int i = 0; i < barCount; i++) { //--- Resize input and target arrays ArrayResize(data[i].inputValues, INPUT_NEURON_COUNT); ArrayResize(data[i].targetValues, OUTPUT_NEURON_COUNT); //--- Get price data double closePrice = priceData[i].close; double openPrice = priceData[i].open; double highPrice = priceData[i].high; double lowPrice = priceData[i].low; double highLowRange = highPrice - lowPrice; //--- Calculate input features data[i].inputValues[0] = (MathAbs(openPrice) > 0.000001) ? (closePrice - openPrice) / openPrice : 0; data[i].inputValues[1] = (MathAbs(lowPrice) > 0.000001) ? (highPrice - lowPrice) / lowPrice : 0; data[i].inputValues[2] = (MathAbs(ma20Values[i]) > 0.000001) ? (closePrice - ma20Values[i]) / ma20Values[i] : 0; data[i].inputValues[3] = (MathAbs(ma50Values[i]) > 0.000001) ? (ma20Values[i] - ma50Values[i]) / ma50Values[i] : 0; data[i].inputValues[4] = rsiValues[i] / 100.0; if(MathAbs(highLowRange) > 0.000001) { data[i].inputValues[5] = (closePrice - lowPrice) / highLowRange; data[i].inputValues[7] = MathAbs(closePrice - openPrice) / highLowRange; data[i].inputValues[8] = (highPrice - closePrice) / highLowRange; data[i].inputValues[9] = (closePrice - lowPrice) / highLowRange; } data[i].inputValues[6] = (MathAbs(closePrice) > 0.000001) ? atrValues[i] / closePrice : 0; //--- Set target values based on price movement if(i < barCount - 1) { double futureClose = priceData[i + 1].close; double priceChange = futureClose - closePrice; if(priceChange > 0) { data[i].targetValues[0] = 1; data[i].targetValues[1] = 0; } else { data[i].targetValues[0] = 0; data[i].targetValues[1] = 1; } } else { data[i].targetValues[0] = 0; data[i].targetValues[1] = 0; } } //--- Return success return true; }
ここでは、ニューラルネットワークの入力データを準備するためにPrepareInputs関数を実装します。まず、ArrayResizeを用いてinputs配列をINPUT_NEURON_COUNTに合わせてリサイズします。次に、ma20Values、ma50Values、rsiValues、atrValues配列を宣言し、ArraySetAsSeriesで時系列配列として設定します。その後、CopyBufferを用いて、ma20IndicatorHandle、ma50IndicatorHandle、rsiIndicatorHandle、およびatrIndicatorHandleから直近2本分のデータを取得し、取得に失敗した場合はPrint関数でエラーログを出力して処理を終了します。
配列サイズをArraySizeで確認し、不足があればPrint関数でエラーを記録します。さらに、iClose、iOpen、iHigh、iLowを用いて、_Symbol とPERIOD_CURRENTから現在の価格をclosePrice、openPrice、highPrice、lowPriceに取得します。これらを基に入力特徴量を計算します。具体的には、正規化した価格差、移動平均からの乖離、0~1にスケーリングしたRSI、closePriceに対するATR比率を算出し、ゼロ除算を避けるためにMathAbsでチェックをおこないます。また、highLowRangeを用いたレンジベースの特徴量も算出します。最後に、Print関数で成功をログに残し、ArraySizeで入力の確保状況を出力します。
さらに、CollectTrainingData関数を作成し、指定したbarCount分のデータをTrainingData構造体配列に準備します。まず、OUTPUT_NEURON_COUNTが2であることを確認し、ArrayResizeでdataをリサイズします。その後、各インジケーターハンドルからのデータをCopyBufferで取得し、価格データはCopyRatesでpriceDataに格納します。取得に失敗した場合はPrint関数でエラーログを出力します。各バーごとに、data[i].inputValuesとdata[i].targetValuesをArrayResizeで確保し、PrepareInputsと同様の手法で入力特徴量を計算します。そして、priceDataの価格変動に基づき、data[i].targetValuesに売買方向を表すターゲット値を設定します。処理が正常に完了した場合はtrueを返します。これにより、収集されたデータを用いてネットワークをトレーニングできる状態となります。
// Train the neural network bool TrainNetwork() { //--- Log training start Print("Starting neural network training..."); TrainingData trainingData[]; //--- Collect training data if(!CollectTrainingData(trainingData, TrainingBarCount)) { Print("Failed to collect training data"); return false; } //--- Train network double accuracy = neuralNetwork.TrainOnHistoricalData(trainingData); //--- Log training completion Print("Training completed. Final accuracy: ", accuracy); //--- Return training success return (accuracy >= MinPredictionAccuracy); }
TrainNetwork関数を作成し、まずPrint関数で学習開始をログに記録します。次に、入力値およびターゲット値を格納するためにTrainingData構造体の配列trainingDataを宣言します。続いてCollectTrainingData関数を呼び出し、TrainingBarCount本分のバーからtrainingDataを生成します。データ収集に失敗した場合はPrint関数でエラーを出力し、falseを返します。
その後、neuralNetworkオブジェクトのTrainOnHistoricalData関数を呼び出してネットワークを学習させ、結果をaccuracyに格納します。学習完了後は、Print関数で完了メッセージを出力し、その際にaccuracyもログに含めます。さらに、accuracyがMinPredictionAccuracy以上であればtrueを返し、ネットワークが十分に学習され取引に利用可能な状態であることを保証します。最後に、生成された予測結果を実際の売買判断に結び付けるための取引シグナル検証関数を新たに作成できる段階となります。
// Validate Stop Loss and Take Profit levels bool CheckStopLossTakeprofit(ENUM_ORDER_TYPE orderType, double price, double stopLoss, double takeProfit) { //--- Get minimum stop level double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point; //--- Validate buy order if(orderType == ORDER_TYPE_BUY) { //--- Check stop loss distance if(MathAbs(price - stopLoss) < stopLevel) { Print("Buy Stop Loss too close. Minimum distance: ", stopLevel); return false; } //--- Check take profit distance if(MathAbs(takeProfit - price) < stopLevel) { Print("Buy Take Profit too close. Minimum distance: ", stopLevel); return false; } } //--- Validate sell order else if(orderType == ORDER_TYPE_SELL) { //--- Check stop loss distance if(MathAbs(stopLoss - price) < stopLevel) { Print("Sell Stop Loss too close. Minimum distance: ", stopLevel); return false; } //--- Check take profit distance if(MathAbs(price - takeProfit) < stopLevel) { Print("Sell Take Profit too close. Minimum distance: ", stopLevel); return false; } } //--- Return validation success return true; }
ここでは、ブローカー制約による潜在的なエラーを回避するため、取引ポイントを検証するブール関数を作成します。その後、OnInitイベントハンドラ内でプログラムを初期化し、インジケーターやニューラルネットワーククラスのインスタンスを初期化して重い処理を担当させます。
// Expert initialization function int OnInit() { //--- Initialize 20-period MA indicator ma20IndicatorHandle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE); //--- Initialize 50-period MA indicator ma50IndicatorHandle = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_SMA, PRICE_CLOSE); //--- Initialize RSI indicator rsiIndicatorHandle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); //--- Initialize ATR indicator atrIndicatorHandle = iATR(_Symbol, PERIOD_CURRENT, 14); //--- Check MA20 handle if(ma20IndicatorHandle == INVALID_HANDLE) Print("Error: Failed to initialize MA20 handle. Error code: ", GetLastError()); //--- Check MA50 handle if(ma50IndicatorHandle == INVALID_HANDLE) Print("Error: Failed to initialize MA50 handle. Error code: ", GetLastError()); //--- Check RSI handle if(rsiIndicatorHandle == INVALID_HANDLE) Print("Error: Failed to initialize RSI handle. Error code: ", GetLastError()); //--- Check ATR handle if(atrIndicatorHandle == INVALID_HANDLE) Print("Error: Failed to initialize ATR handle. Error code: ", GetLastError()); //--- Check for any invalid handles if(ma20IndicatorHandle == INVALID_HANDLE || ma50IndicatorHandle == INVALID_HANDLE || rsiIndicatorHandle == INVALID_HANDLE || atrIndicatorHandle == INVALID_HANDLE) { Print("Error initializing indicators"); return INIT_FAILED; } //--- Create neural network instance neuralNetwork = new CNeuralNetwork(INPUT_NEURON_COUNT, MinHiddenNeurons, OUTPUT_NEURON_COUNT); //--- Check neural network creation if(neuralNetwork == NULL) { Print("Failed to create neural network"); return INIT_FAILED; } //--- Log initialization Print("Initializing neural network..."); //--- Return success return(INIT_SUCCEEDED); }
OnInit関数内に初期化ロジックを実装します。まず、iMA関数を用いて、_SymbolとPERIOD_CURRENTに対して20期間および50期間の単純移動平均を算出するためのma20IndicatorHandleおよびma50IndicatorHandleを初期化します。さらに、iRSI関数およびiATR関数を用いて、14期間のRSIおよびATRを算出するrsiIndicatorHandleとatrIndicatorHandleを初期化します。各ハンドルについてINVALID_HANDLEでないことを確認し、失敗した場合はPrint関数とGetLastErrorでエラーを記録し、INIT_FAILEDを返して初期化を終了します。
次に、ニューラルネットワークの構造を設定するために「CNeuralNetwork」クラスの新しいインスタンスを作成し、INPUT_NEURON_COUNT、MinHiddenNeurons、OUTPUT_NEURON_COUNTを渡してneuralNetworkを初期化します。neuralNetworkがNULLの場合は、Print関数で失敗を記録し、INIT_FAILEDを返します。すべて正常に完了した場合は、Print関数で初期化成功をログに残し、INIT_SUCCEEDEDを返すことで、EAが取引用に正しくセットアップされたことを保証します。プログラムを実行すると、次のような出力が得られます。

出力から、プログラムが正常に初期化されたことを確認できます。そして、ニューラルネットワークのインスタンスを生成したため、プログラムを削除する際にはこれを破棄する必要があります。そのためのロジックを以下に実装します。
// Expert deinitialization function void OnDeinit(const int reason) { //--- Release MA20 indicator handle if(ma20IndicatorHandle != INVALID_HANDLE) IndicatorRelease(ma20IndicatorHandle); //--- Release MA50 indicator handle if(ma50IndicatorHandle != INVALID_HANDLE) IndicatorRelease(ma50IndicatorHandle); //--- Release RSI indicator handle if(rsiIndicatorHandle != INVALID_HANDLE) IndicatorRelease(rsiIndicatorHandle); //--- Release ATR indicator handle if(atrIndicatorHandle != INVALID_HANDLE) IndicatorRelease(atrIndicatorHandle); //--- Delete neural network instance if(neuralNetwork != NULL) delete neuralNetwork; //--- Log deinitialization Print("Expert Advisor deinitialized - ", EnumToString((ENUM_INIT_RETCODE)reason)); }
OnDeinit関数内でクリーンアップ処理を実装します。まず、ma20IndicatorHandle、ma50IndicatorHandle、rsiIndicatorHandle、およびatrIndicatorHandleがINVALID_HANDLEでない場合、それぞれに対してIndicatorRelease関数を呼び出し、移動平均、RSI、ATRインジケーターのリソースを適切に解放します。次に、neuralNetworkがNULLでない場合はdelete演算子を用いてCNeuralNetworkクラスのインスタンスを明示的に破棄し、ニューラルネットワークのリソースをクリーンアップします。最後に、Print関数とEnumToStringを組み合わせて、OnDeinitが呼ばれた理由をログ出力し、EAのリソースが適切に解放されたことを確認します。クラスインスタンスを削除しないまま終了した場合、以下のようにメモリリークが発生する可能性があります。

このメモリリーク問題を解消した後、OnTickイベントハンドラ内で実際のデータ取得、学習処理、推論の実行を実装できるようになります。
// Expert tick function void OnTick() { static datetime lastBarTime = 0; //--- Track last processed bar time //--- Get current bar time datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); //--- Skip if same bar if(lastBarTime == currentBarTime) return; //--- Update last bar time lastBarTime = currentBarTime; //--- Calculate dynamic neuron count int newNeuronCount = (int)neuralNetwork.CalculateDynamicNeurons(); //--- Resize network if necessary if(newNeuronCount != neuralNetwork.GetHiddenNeurons()) neuralNetwork.ResizeNetwork(newNeuronCount); //--- Check if retraining is needed if(TimeCurrent() - iTime(_Symbol, PERIOD_CURRENT, TrainingBarCount) >= 12 * 3600 || neuralNetwork.ShouldRetrain()) { //--- Log training start Print("Starting network training..."); //--- Train network if(!TrainNetwork()) { Print("Training failed or insufficient accuracy"); return; } } //--- Update network with recent data neuralNetwork.UpdateNetworkWithRecentData(); //--- Check for open positions if(PositionsTotal() > 0) { //--- Iterate through positions for(int i = PositionsTotal() - 1; i >= 0; i--) { //--- Skip if position is for current symbol if(PositionGetSymbol(i) == _Symbol) return; } } //--- Prepare input data double currentInputs[]; ArrayResize(currentInputs, INPUT_NEURON_COUNT); PrepareInputs(currentInputs); //--- Verify input array size if(ArraySize(currentInputs) != INPUT_NEURON_COUNT) { Print("Error: Inputs array not properly initialized. Size: ", ArraySize(currentInputs)); return; } //--- Set network inputs neuralNetwork.SetInput(currentInputs); //--- Perform forward propagation neuralNetwork.ForwardPropagate(); double outputValues[]; //--- Resize output array ArrayResize(outputValues, OUTPUT_NEURON_COUNT); //--- Get network outputs neuralNetwork.GetOutput(outputValues); //--- Verify output array size if(ArraySize(outputValues) != OUTPUT_NEURON_COUNT) { Print("Error: Outputs array not properly initialized. Size: ", ArraySize(outputValues)); return; } //--- Get market prices double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bidPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Calculate stop loss and take profit levels double buyStopLoss = NormalizeDouble(askPrice - StopLossPoints * _Point, _Digits); double buyTakeProfit = NormalizeDouble(askPrice + TakeProfitPoints * _Point, _Digits); double sellStopLoss = NormalizeDouble(bidPrice + StopLossPoints * _Point, _Digits); double sellTakeProfit = NormalizeDouble(bidPrice - TakeProfitPoints * _Point, _Digits); //--- Validate stop loss and take profit if(!CheckStopLossTakeprofit(ORDER_TYPE_BUY, askPrice, buyStopLoss, buyTakeProfit) || !CheckStopLossTakeprofit(ORDER_TYPE_SELL, bidPrice, sellStopLoss, sellTakeProfit)) { return; } // Trading logic const double CONFIDENCE_THRESHOLD = 0.8; //--- Confidence threshold for trading //--- Check for buy signal if(outputValues[0] > CONFIDENCE_THRESHOLD && outputValues[1] < (1 - CONFIDENCE_THRESHOLD)) { //--- Set trade magic number tradeObject.SetExpertMagicNumber(123456); //--- Place buy order if(tradeObject.Buy(LotSize, _Symbol, askPrice, buyStopLoss, buyTakeProfit, "Neural Buy")) { //--- Log successful buy order Print("Buy order placed - Signal Strength: ", outputValues[0]); } else { //--- Log buy order failure Print("Buy order failed. Error: ", GetLastError()); } } //--- Check for sell signal else if(outputValues[0] < (1 - CONFIDENCE_THRESHOLD) && outputValues[1] > CONFIDENCE_THRESHOLD) { //--- Set trade magic number tradeObject.SetExpertMagicNumber(123456); //--- Place sell order if(tradeObject.Sell(LotSize, _Symbol, bidPrice, sellStopLoss, sellTakeProfit, "Neural Sell")) { //--- Log successful sell order Print("Sell order placed - Signal Strength: ", outputValues[1]); } else { //--- Log sell order failure Print("Sell order failed. Error: ", GetLastError()); } } } //+------------------------------------------------------------------+
ここでは、OnTick関数内でニューラルネットワーク戦略のコア取引ロジックを実装します。まず、最後に処理したバーを追跡するためにlastBarTimeを宣言し、iTime関数を使用し「_Symbol」とPERIOD_CURRENTのcurrentBarTimeを取得します。バーが更新されていない場合は終了し、新しいバーのみを処理します。その後、lastBarTimeを更新し、CalculateDynamicNeurons関数を用いてnewNeuronCountを計算します。newNeuronCountがGetHiddenNeurons関数の結果と異なる場合は、ResizeNetwork関数を呼び出してネットワークを調整します。
次に、再学習が必要かどうかを確認します。具体的には、TimeCurrentからTrainingBarCountのiTimeを引いた値が12時間を超える場合、またはShouldRetrain関数が再学習を推奨する場合です。その後、TrainNetwork関数を用いて再学習を実行し、失敗した場合は処理を終了します。さらに、UpdateNetworkWithRecentData関数を呼び出してネットワークを最新データで微調整します。PositionsTotalがオープンポジションを示す場合、PositionGetSymbol関数を使用して、現在の_Symbolに関連するポジションが存在する場合は取引をスキップします。次にcurrentInputsを宣言し、ArrayResizeを用いてINPUT_NEURON_COUNTにサイズを変更します。PrepareInputs関数を使用して入力データを設定し、ArraySizeでサイズを確認し、異常があればPrint関数でログを出力します。
その後、SetInput関数でcurrentInputsをロードし、ForwardPropagate関数を呼び出して予測を生成します。GetOutput関数でoutputValuesを取得し、ArrayResizeでOUTPUT_NEURON_COUNTにサイズを変更します。無効な場合はPrint関数でエラーを記録します。続いて、SymbolInfoDoubleでaskPriceとbidPriceを取得し、StopLossPoints、TakeProfitPoints、_Point、_Digitsを用いてbuyStopLoss、buyTakeProfit、sellStopLoss、sellTakeProfitをNormalizeDoubleで計算します。さらにCheckStopLossTakeprofit関数で妥当性を確認します。
取引ロジックについては、CONFIDENCE_THRESHOLDを0.8に設定します。outputValues[0]が閾値を上回り、outputValues[1]がその補数を下回る場合、tradeObject.Buy関数を呼び出して買い注文を実行します。事前にSetExpertMagicNumberでマジックナンバーを設定し、成功・失敗はPrintとGetLastErrorでログに記録します。同様に、売りシグナルの場合はtradeObject.Sell関数を使用します。これにより、堅牢な取引実行を確保しています。コンパイルすると以下の出力が得られます。

画像から分かるように、ニューラルネットワークをトレーニングし、誤差を算出してエポック内で誤差を伝播させ、その誤差の精度に基づいて学習率を調整します。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
学習率調整のテストと最適化
徹底的なバックテストの結果、次の結果が得られました。
バックテストグラフ

バックテストレポート

結論
本記事では、ニューラルネットワークを活用した取引戦略を実装するMQL5プログラムを開発しました。本プログラムは、CNeuralNetworkクラスを用いて市場指標を処理し、誤差に応じた学習率の動的調整やネットワークサイズの最適化を通じて取引を実行します。TrainingData構造体やAdjustLearningRate、TrainNetworkといった関数など、モジュール化されたコンポーネントを活用することで、パラメータの微調整や追加の市場指標の統合など、柔軟に機能を拡張可能なフレームワークを提供します。
免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。
本稿で紹介した概念と実装を活用することで、このニューラルネットワーク取引システムをさらに改善したり、独自の戦略に応用したりすることが可能です。アルゴリズム取引の学習や実践に役立ててください。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18660
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
知っておくべきMQL5ウィザードのテクニック(第72回):教師あり学習でMACDとOBVのパターンを活用する
データサイエンスとML(第45回):FacebookのPROPHETモデルを用いた外国為替時系列予測
プライスアクション分析ツールキットの開発(第30回):コモディティチャンネル指数(CCI)、Zero Line EA
初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(III)-インジケーターインサイト
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索