メタトレーダーでニューラルネットワークを利用する

Mariusz Woloszyn | 22 4月, 2016


はじめに

みなさんの多くはおそらくご自分の EA でニューラルネットワークを利用する可能性について考えたことがおありでしょう。これは、特に「2007年自動売買チャンピオンシップ」後、ニューラルネットワークを基にしたシステム Better がすばらしい勝利を収めてから、ひじょうにホットな話題です。数多くのインターネットフォーラムはニューラルネットワークと Forex 取引に関する話題であふれかえっていました。しかし残念ながら、NN のネイティブの MQL4 への実装を書くことは簡単ではありません。一定のプログラミング技能が必要ですし、特に最終結果を膨大な数のデータについてテスターで検証したい場合、その結果はあまり効率的ではないかもしれません。

本稿では、特定の障害や限界を避けると同時に、みなさんのコードで無料で利用できる(LGPL 下で)著名な高速人口ニューラルネットワーク ライブラリ(FANN) を利用する方法をお見せします。さらに、読者のみなさんが人口ニューラルネットワーク (ann)や、この話題に関連した用語の知識を身に着けたことを前提に、MQL4 言語での特定の実装を行う実用的側面に集中します。



FANN の特徴

FANN 実装の可能性を完全に理解するためには、そのドキュメンテーション や、もっとも一般的に使用される関数の知識が必要です。FANN の一般的な用途は単純なフィードフォワード根とワークを作成し、いくつかのデータでトレーニングし、実行することです。作成、トレーニング済みネットワークはそののちファイルに保存され、さらに利用するため復元されます。ann を作成するには、fann_create_standard() 関数を使用する必要があります。その構文を見ていきましょう。

FANN_EXTERNAL struct fann *FANN_API fann_create_standard(unsigned int num_layers, int lNnum, ... )

ここで num_layers は、入力および出力層を含む層の合計数を表します。lNnum とそれに続く引数は、入力層からスタートして出力層で終わる各層のニューロン数を表します。5 ニューロンの隠れ層を持つネットワークを1つ作成するには、10個の入力と1個の出力層が以下のようにそれを呼び出す必要があります。

fann_create_standard(3,10,5,1);

ひとたび ann が作成されると、次の処理はいくつかの入力および出力データでそれをトレーニングすることです。もっとも簡単なトレーニング方法は、次の関数によって行われるインクリメンタル トレーニングです。

FANN_EXTERNAL void FANN_API fann_train(
        struct  fann    *       ann,
                fann_type       *       input,
                fann_type       *       desired_output  )

この関数は、前にfann_create_standard() によって返された構造体 fann に対するポインターと入力ベクトル、出力ベクトルの両方を取ります。入力、出力ベクトルは fann_type タイプの配列のものです。そのタイプは事実上、double または float タイプで、FANN がコンパイルされる方法によって決まります。この実装では、入力、出力ベクトルは double の配列となります。

がトレーニングされると、次に望まれるのはそのネットワークを実行することでしょう。それを実装する関数は以下のように定義されます。

FANN_EXTERNAL fann_type * FANN_API fann_run(    struct  fann    *       ann,
                fann_type       *       input   )

この関数は、以前に作成されたネットワークと定義されたタイプ(double 配列)の入力ベクトルを表す構造体 fann に対するポインターを取ります。返される値は出力ベクトル配列です。あるアウトプット ネットワークに対して、われわれは常にアウトプット値よりむしろアウトプット値を持つエレメント配列を取得するため、この事実は重要です。

残念ながら、FANN 関数のほとんどはデータタイプとしてストラクチャをサポートしていない MQL4 によって直接処理される ann を表す 構造体 fannに対するポインターを使用します。その限界を避けるために、なんらかの方法でそれをラップし、MQL4 から隠す必要があります。もっとも簡単な方法は、適切な値を持つ 構造体 fann のポインター配列を作成し、int 変数で表されるインデックスでそれらを参照することです。このように、サポートされていない変数タイプをサポートされている変数タイプと置き換え、 MQL4 コードで簡単に統合されるラッパーライブラリを作成することができるのです。



FANN のラッピング

私の知るところでは、MQL4 は変数引数リストを持つ関数をサポートしていないので、それにも対処する必要があります。一方、C 関数(変数引数長)は数多くの引数で呼び出されても、なにも不都合はないため、C ライブラリに渡される MQL4 f関数で固定される引数の最大数を決めることができます。結果のラッパー関数は以下のようなものとなることでしょう。

/* Creates a standard fully connected backpropagation neural network.
* num_layers - The total number of layers including the input and the output layer.
* l1num - number of neurons in 1st layer (inputs)
* l2num, l3num, l4num - number of neurons in hidden and output layers (depending on num_layers).
* Returns:
* handler to ann, -1 on error
*/

int __stdcall f2M_create_standard(unsigned int num_layers, int l1num, int l2num, int l3num, int l4num);

われわれは先導するfann_ with f2M_( FANN TO MQL を表す)を変更し、引数の静的数(4層)を用い、戻り値はここでは ann の内部配列のインデックスで FANN によって操作するのに要求される構造体 fann データを持っています。この方法で、MQL コード内からその関数を簡単に呼び出すことができます。

以下についても同様です。

/* Train one iteration with a set of inputs, and a set of desired outputs.
* This training is always incremental training, since only one pattern is presented.
* ann - network handler returned by f2M_create_*
* *input_vector - array of inputs
* *output_vector - array of outputs
* Returns:
* 0 on success and -1 on error
*/

int __stdcall f2M_train(int ann, double *input_vector, double *output_vector);

また

/* Run fann network
* ann - network handler returned by f2M_create_*
* *input_vector - array of inputs
* Returns:
* 0 on success, negative value on error
* Note:
* To obtain network output use f2M_get_output().
* Any existing output is overwritten
*/

int __stdcall f2M_run(int ann, double *input_vector);

大切なことを1つ言い残しましたが、への呼出しによって一度作成した ann を壊す必要があります。

/* Destroy fann network
* ann - network handler returned by f2M_*
* Returns:
* 0 on success -1 on error
* WARNING: the ann handlers cannot be reused if ann!=(_ann-1)
* Other handlers are reusable only after the last ann is destroyed.
*/

int __stdcall f2M_destroy(int ann);

ハンドルを解放するには、作成されたのと反対の順序でネットワークを壊します。代替として以下が使用できます。

/* Destroy all fann networks
* Returns:
* 0 on success -1 on error
*/
int __stdcall f2M_destroy_all_anns();

ですが、みなさんの中には以下と共に、のちに使用するためトレーニング済みネットワークを保存する方が良い、と思う方もいらっしゃるはずです。

/* Save the entire network to a configuration file.
* ann - network handler returned by f2M_create*
* Returns:
* 0 on success and -1 on failure
*/
int __stdcall f2M_save(int ann,char *path);

もちろん、保存されたネットワークは、以下でのちにロードする(むしろ再作成する)ことができます。

/* Load fann ann from file
* path - path to .net file
* Returns:
* handler to ann, -1 on error
*/
int __stdcall f2M_create_from_file(char *path);

基本的な関数を知ると、自分の EA で使ってみるわけですが、まず Fann2MQL パッケージをインストールする必要があります。

Fann2MQL のインストール

このパッケージを簡単に使用するために、全ソースコードプラスコンパイル済みライブラリ、関数すべてを宣言する Fann2MQL.mqh ヘッダファイルを持つ msi インストーラ を作成しました。

インストール手順はひじょうに明快です。まず、Fann2MQL は GPL ライセンス下にあることが知らされます。


Fann2MQL のインストール ステップ1

そして、パッケージをインストールする先のフォルダを選択します。デフォルトの Program Files\Fann2MQL\ を使用できますし、またはご自分の Meta Trader\experts\ ディレクトリに直接インストールすることも可能です。後者は全ファイルを直接その場所に入れます。そうでないとマニュアルでコピーするはめになります。


Fann2MQL のインストール ステップ2

インストーラはファイルを以下のフォルダに入れます。


include\ folder


libraries\ folder


src\ folder

Fann2MQL 専用フォルダにインストールすることを選択する場合、そのインクルード の内容とライブラリサブフォルダをご自身のの適切なディレクトリにコピーしてください。

インストーラは FANN ライブラリをみなさんのシステムライブラリフォルダ(ほとんどの場合 Windows\system32)にもインストールします。src フォルダには Fann2MQL のソースコードがすべて入っています。内部に関してより詳しい情報が必要な場合は、最終ドキュメンテーションであるソースコードを読むことができます。お望みなら、追加機能を加えてコードを改良することも可能です。何か面白いものを取り入れられたら、みなさんのパッチをお寄せいたくことをお薦めします。



自分の EA でのニューラルネットワークの利用

Fann2MQL がインストールされると、ご自分の EA やインディケータを書き始めることができます。NN には多くの可能な使用法があります。将来の価格変動予測に使用することができますが、そのような予想のクオリティーやそれを実際に活用する可能性には疑問があります。強化学習 テクニックを用いて独自の戦略をプログラミングすることができます。たとえば Q ラーニング や類似のものです。NN をご自分のヒューリスティック EA として使用してみたり、このテクニックすべてプラスなんでもお望みのものを組み合わせてみることができます。みなさんは自分のイマジネーションによって制限されているだけなのです。

ここで、 MACDによって生成されるシグナルに対するフィルターとして NN を使用する例をお見せします。それを価値ある EA としてではなく、Fann2MQL の適用例と考えてください。この方法を説明する間、例の EA:NeuroMACD.mq4 は動作します。Fann2MQL が MQLでどのように効果的に使用されるか説明します。

すべての EA に対して一番最初のことがらはグローバル変数の宣言、セクションの定義とインクルードです。以下はそういう事柄を含むNeuroMACD の冒頭です。

// Include Neural Network package
#include <Fann2MQL.mqh>

// Global defines
#define ANN_PATH "C:\\ANN\\"
// EA Name
#define NAME "NeuroMACD"

//---- input parameters
extern double Lots=0.1;
extern double StopLoss=180.0;
extern double TakeProfit=270.0;
extern int FastMA=18;
extern int SlowMA=36;
extern int SignalMA=21;
extern double Delta=-0.6;
extern int AnnsNumber=16;
extern int AnnInputs=30;
extern bool NeuroFilter=true;
extern bool SaveAnn=false;
extern int DebugLevel=2;
extern double MinimalBalance=100;
extern bool Parallel=true;

// Global variables

// Path to anns folder
string AnnPath;

// Trade magic number
int MagicNumber=65536;

// AnnsArray[ann#] - Array of anns
int AnnsArray[];

// All anns loded properly status
bool AnnsLoaded=true;

// AnnOutputs[ann#] - Array of ann returned returned
double AnnOutputs[];

// InputVector[] - Array of ann input data
double InputVector[];

// Long position ticket
int LongTicket=-1;

// Short position ticket
int ShortTicket=-1;

// Remembered long and short network inputs
double LongInput[];
double ShortInput[];

インクルードコマンドはすべての Fann2MQL 関数宣言を持つ Fann2MQL.mqh ヘッダファイルをロードするように言います。その後、すべての Fann2MQL パッケージ関数はスクリプトで使用できます。ANN_PATH 定数は格納用パスを定義し、トレーニング済み FANN ネットワークを持つファイルをロードします。そのフォルダ、すなわち C:\ANN を作成する必要がありますNAME 定数はこの EA の名前を持ち、それはのちにネットワークファイルをロードし保存するのに使用されます。入力パラメータはひじょうに明白で、そうでないものについてはグローバル変数と共にのちに説明します。


すべての EA のエントリポイントは init() 関数です。

int init()
  {
   int i,ann;

   if(!is_ok_period(PERIOD_M5)) 
     {
      debug(0,"Wrong period!");
      return(-1);
     }

   AnnInputs=(AnnInputs/3)*3; // Make it integer divisible by 3

   if(AnnInputs<3) 
     {
      debug(0,"AnnInputs too low!");
     }
// Compute MagicNumber and AnnPath
   MagicNumber+=(SlowMA+256*FastMA+65536*SignalMA);
   AnnPath=StringConcatenate(ANN_PATH,NAME,"-",MagicNumber);

// Initialize anns
   ArrayResize(AnnsArray,AnnsNumber);
   for(i=0;i<AnnsNumber;i++) 
     {
      if(i%2==0) 
        {
         ann=ann_load(AnnPath+"."+i+"-long.net");
           } else {
         ann=ann_load(AnnPath+"."+i+"-short.net");
        }
      if(ann<0)
         AnnsLoaded=false;
      AnnsArray[i]=ann;
     }
   ArrayResize(AnnOutputs,AnnsNumber);
   ArrayResize(InputVector,AnnInputs);
   ArrayResize(LongInput,AnnInputs);
   ArrayResize(ShortInput,AnnInputs);

// Initialize Intel TBB threads
   f2M_parallel_init();

   return(0);
  }

まず、それはが正しいタイムフレーム期間に適用されているか確認します。 AnnInputs 変数はニューラルネットワークのインプット数を持ちます。ここでは3セットの異なる引数を使用しているため、それを3で割れる数であってほしいと思います。AnnPath は EA NAME および MagicNumberを反映するために計算されます。それは、のちに MACD インディケータのシグナル伝達に使用されるインプット引数 SlowMAFastMASignalMA から計算されるものです。ひとたびそれが AnnPath を知ると、EA は以下で説明する ann_load() 関数を使ってニューラルネットワークをロードしようとします。ロードされたネットワークの半数はロングポジションのフィルターを、残りの半数はショートポジションのフィルターのためのものです。AnnsLoaded 変数はすべてのネットワークのが正常に初期化されたことを示すのためのものです。みなさんは EA が複数のネットワークをロードしようとするこの例におそらくお気づきでしょう。私はそれがこのアプリケーションでほんとうに必要なのか疑問ですが、Fann2MQL の最大の可能性を説明したいのです。それは同時に複数のネットワークを扱い、マルチコアまたは CPU を活用してそれらを並列で処理することができるのです。それを可能にするために、Fann2MQL はインテルl® スレッディング・ビルディング・ブロック 技術を活用します。関数n f2M_parallel_init() がそのインターフェースを初期化するのに使用されます。

以下は私がかつてネットワークを初期化した方法です。

int ann_load(string path)
  {
   int ann=-1;

   /* Load the ANN */
   ann=f2M_create_from_file(path);
   if(ann!=-1) 
     {
      debug(1,"ANN: '"+path+"' loaded successfully with handler "+ann);
     }
   if(ann==-1) 
     {

      /* Create ANN */
      ann=
          f2M_create_standard(4,AnnInputs,AnnInputs,AnnInputs/2+1,1);
      f2M_set_act_function_hidden(ann,FANN_SIGMOID_SYMMETRIC_STEPWISE);
      f2M_set_act_function_output(ann,FANN_SIGMOID_SYMMETRIC_STEPWISE);
      f2M_randomize_weights(ann,-0.4,0.4);
      debug(1,"ANN: '"+path+"' created successfully with handler "+ann);
     }
   if(ann==-1) 
     {
      debug(0,"INITIALIZING NETWORK!");
     }
   return(ann);
  }

ごらんのように、f2M_create_from_file() が失敗すると、それは負の戻り値によって示され、ネットワークは、作成されたネットワークが4層(入力、出力を含み)、 AnnInput 入力、第1の隠れ層に AnnInput ニューロン、第2の隠れ層に AnnInput/2+1 ニューロン、そして出力層に1ニューロンを持つことを示す引数を持つ f2M_create_standard() 関数で作成されます。f2M_set_act_function_hidden() は SIGMOID_SYMMETRIC_STEPWISE( fann_activationfunc_enum の FANNドキュメンテーションを参照ください)に対して隠れ層の関数のアクティブ化を設定するためのもので、出力層に対しても同様です。それから f2m_randomize_weights() に対する呼び出しがあります。それはネットワーク内のニューロン接続ウェイトを初期化するためのものです。ここで私は <-0.4; 0.4> の範囲を取りましたが、みなさんはご自身のアプリケーションに応じてどんな範囲でも取ることができます。

この時点では、みなさんはおそらく私が数回使用したdebug() 関数にお気づきのことでしょう。それは EA の詳細レベルを変更するのにはもっとも簡単な方法の一つです。それと入力パラメータ DebugLevel と共に、コードがデバッグ出力を作り出す方法を調整することができます。

void debug(int level,string text)
  {
   if(DebugLevel>=level) 
     {
      if(level==0)
         text="ERROR: "+text;
      Print(text);
     }
  }

debug() 関数の第1パラメータであるデバッグ レベルDebugLevel より高ければ、関数は出力を作り出しません。それが等しいかまたは低ければ、 テキスト 文字列が表示されます。デバッグレベルが0であれば、文字列『エラー』が初めに表示されます。in. このおうにコードによって作り出されるデバッグを複数レベルに分けることができるのですもっとも重要なのは、たぶん、レベル0に割り当てられるエラーです。そのエラーは DebugLevel を0より低く(お薦めできません)しない限り表示されます。レベル1では、正常なネットワークのロードや作成の確認など、いくつか重要な情報が表示されます。レベル2以上では、表示される情報の重要性は次第に低くなっていきます。

かなり長い start() 関数の詳細説明に入る前に、ネットワークの入力準備や実ネットワークを実行する関数についてもう少し説明する必要があります。

void ann_prepare_input()
  {
   int i;

   for(i=0;i<=AnnInputs-1;i=i+3) 
     {
      InputVector[i]=
         10*iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                  MODE_MAIN,i*3);
      InputVector[i+1]=
         10*iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                  MODE_SIGNAL,i*3);
      InputVector[i+2]=InputVector[i-2]-InputVector[i-1];
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double ann_run(int ann,double &vector[])
  {
   int ret;
   double out;
   ret=f2M_run(ann,vector);
   if(ret<0) 
     {
      debug(0,"Network RUN ERROR! ann="+ann);
      return(FANN_DOUBLE_ERROR);
     }
   out=f2M_get_output(ann,0);
   debug(3,"f2M_get_output("+ann+") returned: "+out);
   return(out);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int anns_run_parallel(int anns_count,int &anns[],double &input_vector[])
  {
   int ret;

   ret=f2M_run_parallel(anns_count,anns,input_vector);

   if(ret<0) 
     {
      debug(0,"f2M_run_parallel("+anns_count+") returned: "+ret);
     }
   return(ret);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void run_anns()
  {
   int i;

   if(Parallel) 
     {
      anns_run_parallel(AnnsNumber,AnnsArray,InputVector);
     }

   for(i=0;i<AnnsNumber;i++) 
     {
      if(Parallel) 
        {
         AnnOutputs[i]=f2M_get_output(AnnsArray[i],0);
           } else {
         AnnOutputs[i]=ann_run(AnnsArray[i],InputVector);
        }
     }
  }
//+------------------------------------------------------------------+

関数 ann_prepare_input() はネットワークの入力名(以降名前)を準備するためのものです。この目的はひじょうに解りやすいものですが、入力データは適切に正規化される必要があることを思い出していただくことが重要です。この場合は、洗練された正規化はありません。私は MACD のメインと計算されたデータの望む範囲を決して超えないシグナル値を使用しました。実例では、みなさんはこの問題にもっと注意を向けることでしょう。疑問をお持ちのように、ネットワーク入力のために適切な入力引数を選択し、それをコード化し、分解し、正規化することはニューラルネットワークの手順で最も重要な要因の一つです。

前にお話したように、 Fann2MQL には MetaTrader の標準的な機能性を拡張する機能があり、それはニューラルネットワークの並列のマルチスレッド処理なのです。グローバル引数Parallel はこの動作を制御します。run_anns() 関数は初期化済みネットワークをすべて実行し、そのアウトプットを取得したら、AnnOutput[] 配列に格納します。anns_run_parallel 関数はマルチスレッド方法でジョブを扱います。それは、第1引数として処理するネットワーク数を取る f2m_run_parallel() を呼びます。その第2引数は入力ベクトルを第3引数として提供しながら、実行したいネットワークすべてに対するハンドルを持つ配列です。ネットワークはすべてまったく同一入力データで実行される必要があります。ネットワークからアウトプットを取得するのは、f2m_get_output()に対する複数呼び出しで行われます。

それではここから start() 関数を見ていきます。

int
start()
  {
   int i;
   bool BuySignal=false;
   bool SellSignal=false;

   double train_output[1];

   /* Is trade allowed? */
   if(!trade_allowed()) 
     {
      return(-1);
     }

   /* Prepare and run neural networks */
   ann_prepare_input();
   run_anns();

   /* Calulate last and previous MACD values.
* Lag one bar as current bar is building up
*/
   double MacdLast=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                         MODE_MAIN,1);
   double MacdPrev=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                         MODE_MAIN,2);

   double SignalLast=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                           MODE_SIGNAL,
                           1);
   double SignalPrev=iMACD(NULL,0,FastMA,SlowMA,SignalMA,PRICE_CLOSE,
                           MODE_SIGNAL,
                           2);

   /* BUY signal */
   if(MacdLast>SignalLast && MacdPrev<SignalPrev) 
     {
      BuySignal=true;
     }
   /* SELL signal */
   if(MacdLast<SignalLast && MacdPrev>SignalPrev) 
     {
      SellSignal=true;
     }

   /* No Long position */
   if(LongTicket==-1) 
     {
      /* BUY signal */
      if(BuySignal) 
        {
         /* If NeuroFilter is set use ann wise to decide :) */
         if(!NeuroFilter || ann_wise_long()>Delta) 
           {
            LongTicket=
          OrderSend(Symbol(),OP_BUY,Lots,Ask,3,
                    Bid-StopLoss*Point,
                    Ask+TakeProfit*Point,
                    NAME+"-"+"L ",MagicNumber,0,Blue);
           }
         /* Remember network input */
         for(i=0;i<AnnInputs;i++) 
           {
            LongInput[i]=InputVector[i];
           }
        }
        } else {
      /* Maintain long position */
      OrderSelect(LongTicket,SELECT_BY_TICKET);
      if(OrderCloseTime()==0) 
        {
         // Order is opened
         if(SellSignal && OrderProfit()>0) 
           {
            OrderClose(LongTicket,Lots,Bid,3);
           }
        }
      if(OrderCloseTime()!=0) 
        {
         // Order is closed
         LongTicket=-1;
         if(OrderProfit()>=0) 
           {
            train_output[0]=1;
              } else {
            train_output[0]=-1;
           }
         for(i=0;i<AnnsNumber;i+=2) 
           {
            ann_train(AnnsArray[i],LongInput,train_output);
           }
        }
     }

   /* No short position */
   if(ShortTicket==-1) 
     {
      if(SellSignal) 
        {
         /* If NeuroFilter is set use ann wise to decide ;) */
         if(!NeuroFilter || ann_wise_short()>Delta) 
           {
            ShortTicket=
          OrderSend(Symbol(),OP_SELL,Lots,Bid,3,
                    Ask+StopLoss*Point,
                    Bid-TakeProfit*Point,NAME+"-"+"S ",
                    MagicNumber,0,Red);
           }
         /* Remember network input */
         for(i=0;i<AnnInputs;i++) 
           {
            ShortInput[i]=InputVector[i];
           }
        }
        } else {
      /* Maintain short position */
      OrderSelect(ShortTicket,SELECT_BY_TICKET);
      if(OrderCloseTime()==0) 
        {
         // Order is opened
         if(BuySignal && OrderProfit()>0) 
           {
            OrderClose(LongTicket,Lots,Bid,3);
           }
        }
      if(OrderCloseTime()!=0) 
        {
         // Order is closed
         ShortTicket=-1;
         if(OrderProfit()>=0) 
           {
            train_output[0]=1;
              } else {
            train_output[0]=-1;
           }
         for(i=1;i<AnnsNumber;i+=2) 
           {
            ann_train(AnnsArray[i],ShortInput,train_output);
           }
        }
     }

   return(0);
  }
//+------------------------------------------------------------------+

詳細にコメントが付けられているので、説明は簡潔にします。trade_allowed() はトレードが許可されているかどうか確認します。基本的にそれは、anns がすべて適切に初期化されていることを示しているAnnsLoaded 変数を確認します。それから適切なタイムフレーム期間、最小のアカウント残高を確認し、一番最後に新規バーの1番目のティックでのみトレードを許可します。ネットワークのインプットを準備し、ネットワーク処理を実行する次の2つの関数は上の数行で説明されました。次に、計算し変数内にのちの処理のためにシグナルと最後に構築されるバーとその前のバーに対するメインラインの MACD 値を入れます。現行バーは、まだ構築されておらず、おそらく再描画されるため、省略されます。SellSignal および BuySignal は MACD シグナルとメインラインの交点に応じて計算されます。どちらのシグナルも、対称なロングポジション、ショートポジションの処理に使われます。よって、ここではロングポジションの場合についてのみ説明します。

LongTicket 変数は現在オープンしているポジションのチケットナンバーを持ちます。それがであれば、ポジションはオープンしておらず、BuySignal が設定されていれば、それはロングポジションをオープンするのに良い機会でしょう。NeuroFilter 変数がセットされていず、ロングポジションがオープンしており、それがニューラルネットワークのシグナルのフィルターなしの場合、オーダーは買いに送信されます。この時点で、 LongInput 変数はのちの使用のため ann_prepare_input() によって準備されたInputVector を記憶するためのものです。

LongTicekt 変数が有効なチケットナンバーを持っていれば、EA はそれがまだオープンしているか、ストップロスやテイクプロフィットによってクローズ済みか確認します。オーダーがクローズされていなければ、何も起こりません。オーダーがクローズされていれば、アウトプットを1つだけ持つ train_output[] ベクトルが次のように値を持つように計算されます。オーダーが損失を伴ってクローズされていれば -1 、利益を伴ってクローズされていれば 1。その値はその後 ann_train() 関数に渡され、ロングポジションに関与するネットワークはすべてそれでトレーニングされます。入力ベクトルとして、変数が使用されます。それはポジションオープンのとき LongInput を保持しています。このように、ネットワークはどのシグナルが利益をもたらし、どれが利益を出さないか教えられます。

ひとたび、トレーニング済みネットワークを取得すると、NeuroFilter に切り替えることはネットワークのフィルター化に変えます。ann_wise_long() は、ロングポジションを処理するためのネットワークすべてによって返される値の平均として計算されるニューラルネットワーク単位を使っています。Delta パラメータはフィルターに欠けられた値が有効化否かを示す閾値として使用されます。その他数多くの値として、それは最適化のプロセスを経て取得されます。

それがどのように動作するかわかったところで、それがどのように使用できるか説明します。検証ペアはもちろん EURUSD です。データはから採り、M5 のタイムフレームに変換しました。トレーニング/最適化期間は 2007.12.31~2009.01.01、検証期間は2009.01.01~2009.03.22 としました。一番最初の実行時、引数 StopLoss、TakeProfit、SlowMA、FastMA に対してもっとも利益の出る値を取得しました。それからそれを NeuroMACD.mq4 ファイルにコード化しました。NeuroFIlter SaveAnn と共にオフにし、ニューラル処理を避けるために AnnsNumber は 0 に設定しました。私は最適化プロセスに遺伝的アルゴリズムを使用しました。値が取得されると、結果レポートは以下のようなものとなります。


基本パラメータ最適化後のトレーニングデータレポート

ごらんのように、私はこの EA をミニ アカウントで 0.01 のロットサイズと初期バランス 200 で実行しました。みなさんはご自身のアカウント設定やお好みでこれらパラメータを調整することが可能です。

この時点で、十分収益性があり、敗北トレードを取得したので、SaveAnn をオンにし、AnnsNumber を30に設定することができました。それを行うと、もう一度テスターを実行しました。結果は、プロセスがずっと遅い(ニューラス処理の結果)こと以外はまったく同様で、以下の画像に示されるようにトレーニング済みネットワークでフォルダ C:\ANN が生成されました。C:\ANN フォルダがこの実行前に存在していることを確認してください!


C:\\ANN\\ フォルダ

ネットワークをトレーニングしたら、今度はそれの動作を検証します。まず、トレーニングデータ上で試します。NeuroFilter に、SaveAnnに変更し、テスターを起動します。私た取得した結果は以下に表示しています。ネットワークの初期化プロセス(この例では、私は ann_load()内でf2M_randomize_weights() に対して明確な呼び出しを行いました)で得たニューロン接続ウェイトにおけるネットワーク内にいくらか無作為性がある場合には、それがわずかに異なる可能性があることに注意します。


シグナルニューラスフィルターをオンにしてトレーニングデータに取得した結果

純利益はわずかに大きくなっています(20.03 vs 16.92)が、プロフィット ファクターはずっと高く(1.25 vs 1.1)なっています。トレード数はずっと少なく(83 vs 1188)、平均連続損失数は7 から 2 に減っています。ニューラルシグナルフィルターが動作していることを示していますが、トレーニング中使用されていないデータに対してそれがどのように処理するかは示していません。以下は私が検証期間(2009.01.01~2009.30.28)から取得した結果です。


ニューラルフィルターをオンにして検証データから取得した結果

操作を行ったトレーダー数は極めて少なく、この戦略のクオリティーを語るのは困難ですが、私が説明したいことは、もっとも収益性あるEA の書き方ではなく、MQL4 コードでのニューラルネットワーク使用法です。この場合にニューラルネットワークを使用する実際の効果は NeuroFilter をオン、オフ状態のテストデータにおいて EA の結果を比較することでのみ確認できるものです。以下はニューラルシグナル フィルターなしで検証データ期間から取得した結果です。


ニューラルフィルターなしでの検証データ結果

違いは非常に明確です。おわかりのように、ニューラルシグナルフィルターは損失 EA を利益 EA に変えたのです!



おわりに

本稿からみなさんが、MetaTrader でのニューラルネットワーク使用法を学習されたことを願っています。簡単、無料、オープンソースのパッケージ Fann2MQL を使用することで、仮想的に任意の Expert Advisor にニューラルネットワーク階層を簡単に追加、または完全にあるいは部分的にニューラルネットワークに基づく独自のものを書くことができるのです。独自のマルチスレッド機能は、お手持ちの CPU コア数に応じて、独に特定のパラメータを最適化するとき、何倍にも処理スピードを上げることが可能です。あるケースでは、4 コアのインテル CPU で、EA 処理ベースの「強化学習」I最適化を 4 日から「たった」28 時間にまで短縮しました。

本稿執筆中、Fann2MQL をそのウェブサイト:http://fann2mql.wordpress.com/、に載せることにしました。そこに Fann2MQL の最新バージョン、可能な今後のバージョンすべて、および全関数のドキュメンテーションがあります。全提供についてこのソフトウェアをライセンス化で維持することをお約束します。みなさんがコメント、機能に関するリクエスト、興味深いパッチをお寄せくだされば、それも次回の公開に必ず含まれています。

本稿は Fann2MQL のごく基本的な用途を説明しているにすぎないことにご注意ください。このパッケージは FANN 向けなので、以下のように FANN ネットワークを管理するために作成されたツールをすべて使用することができます。

FANN については、「高速人工ニューラスネットワークライブラリ」のホームページ: http://leenissen.dk/fann/にもっと情報があります



追記

本稿執筆後、NeuroMACD.mq4 にちょっとしたエラーを発見しました。ショートポジション向け OrderClose() 関数がロングポジションのチケットナンバーで提供されていたのです。それは、ショートポジションを維持し、ロングポジションをクローズする可能性の高い、ゆがんだ戦略につながっていました。

/* Maintain short position */
OrderSelect(ShortTicket,SELECT_BY_TICKET);
if(OrderCloseTime()==0) 
  {
// Order is opened
   if(BuySignal && OrderProfit()>0) 
     {
      OrderClose(LongTicket,Lots,Bid,3);
     }
  }

スクリプトの正しいバージョンで、このエラーを修正し、OrderClose() 戦略を完全に削除しました。これは EA に対するニューラルフィルターの影響の全体像を変えることはありませんが、バランス曲線の形はまったく異なったものとなりました。本稿にはこの EA の両バージョンを添付しています。