RRLをMT4で試してみる
目次
はじめに
3月はじめから書いているRRLシリーズも今回で5回目になりました。
今回はRRLをMetatrader4のEAに実装してみようと思います。
いきなりこの記事を読んだ人には何のことかさっぱりわからないと思うので、一応背景説明も。
再帰型強化学習(RRL)というアルゴリズムを株取引に適応すると結構いいらしいです。そこでRRLでFX取引をしたらどうなるのかという試みです。
詳細は過去記事を参照の事。
RRLを実装したEA
最近のEAはクラスを作れるんですね~。前々回RRLをC++で実装しましたが、mqlは文法がほぼC++でして、C++で書いたクラスが殆どそのままコピペでいけました。
追加で内積を求めるdot関数を作ったり、ハイパブリックタンジェントが無かったのでtanh関数を作ったりしてます。
細かく言うと、2次元配列の初期化ができなかったので、重みにかけるを1次元配列にしたり、実数で出力されるエージェントの行動をクォンタライザーに入れて、1、0、-1に振り分けたりもしてます。
でもまあ、殆どC++で実装した時のコピペです。
注意点としては、グローバル変数とクラス内のメンバ変数が同じ名前だとコンストラクターでの初期化時に、変な値が入ってしまうのでグローバル変数と同じ名前のメンバ変数はクラス内から除去しておきました。
以下RRLを実装したEAです。USDJPYの30分足チャートで動かすことを想定しています。
コードの解説
パラメータ説明
パラメータ名は参考にしたRRLの論文と合わせてありますが、今までのRRL実装でもパラメータの説明を全然書いていなかったので、一応ここで確認しておきます。
EAで設定する以下のパラメータは、RRLの学習に使用します。
T
: 学習の時間区間M
: パーセプトロンに食わせる過去の価格差データの個数mu
: 報酬算出時の取引量(デフォルトの10000は0.1ロットに相当)sigma
: 報酬算出時のスプレット(0.04円)rho
: 学習率q_threshold
: エージェントのアクションを1、0、-1に量子化するクォンタライザーの閾値。n_epoch
: 学習回数n_tick_update_w
: 学習を実施して重みをアップデートする周期write_log
: 学習結果のログ(シャープレシオの学習過程と最適化後の重み)を出力するかどうか。
基本的に変更するのは、T
とM
の値です。またn_tick_update_w
はデフォルトではT
と同じ値にしてますがもっと早い周期にしてもいいと思います。
報酬算出時のスプレッドsigma
は通過ペアをUSDJPYから変更する場合は、それなりの値を設定してください。
以下のパラメータは、エージェントの行動を反映した実際のFX取引に使います。
slippage
: 注文時に許容するスリッページlots
: 注文ロット数stop_flag
: ストップを入れるかどうかlimit_flag
: リミットを入れるかどうかstop_points
: ストップを入れた時の設定ポイントlimit_points
: リミットを入れた時の設定ポイント
これらは特にいじらなくていいですが、実運用時に注文ロット数を増やしたり、ストップやリミットを入れたくなったら入れてください。(実運用できる様なパフォーマンスはまだ確認できてませんが。。希望を込めて一応書いておきました。)
コードの流れ
最初にエージェントであるTradingRRL
クラスの定義をザーッと書いて、グローバル変数としてポインタで宣言しておきます。(34行目~330行目)
//+------------------------------------------------------------------+ //| RRL agent | //+------------------------------------------------------------------+ class TradingRRL{ public: /* int T; int M; double mu; ... ... ... */ } //--- Create TradingRRL object as global. TradingRRL *rrl;
EAを稼働した時に最初に実行されるOnInit()
関数内でnew
で生成して、EAを外した時に呼ばれるOnDeinit()
関数内でdelete
で解放します。(332行目~347行目)
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ n_tick = 0; init_flag = True; Fp = 0.0; rrl = new TradingRRL(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ delete rrl; }
オーダーのクローズ、カウントまたストップとリミットを入れる関数です。(349行目~428行目)
//+------------------------------------------------------------------+ //| Market functions | //+------------------------------------------------------------------+ void closeBuyPos(){ int res; for(int i=OrdersTotal()-1; i>=0; i--){ ... ... ...
次にTick毎に呼ばれるOnTick()
関数内での処理です。
フラグがTrueでポジションを持っているのにストップ、リミットが入っていない場合にストップ、リミットを入れます。(436行目~439行目)
// --- Place stop and limit. if(stop_flag || limit_flag){ orderStopLimit(stop_flag, stop_points, limit_flag, limit_points); }
Volumeが1以上の時はそのまま抜けます。つまり時間足が生成された最初のTickでしかそれ以降の処理は行いません。(441行目~442行目)
//---Evaluate only the first tick. if(Volume[0]>1 || IsTradeAllowed()==false) return;
バーの数がT+M
より少ない場合は学習ができません。バーの数がT+M
になるまで待ちます。バックテストでは最初のバーの数は1000個がデフォルト設定みたいなので入れました。(444行目~450行目)
//--- Wait until the number of bars become T+M. n_tick +=1; if(init_flag){ if(Bars <= T+M) return; init_flag = False; n_tick = 0; }
エージェントの学習を実施します。バーがn_tick_update_w
個生成される度に実施します。(452行目~458行目)
//--- Training agent. if(n_tick % n_tick_update_w == 0){ Print("Update weights"); rrl.set_r(); rrl.fit(); if(write_log) rrl.save_weight(); }
現在の価格におけるエージェントのアクションF
を決定します。F
は実数なのでクォンタライザーに入れて量子化した値qF
(qはquantizedのqです)にします。現在のアクションF
を一つ前のアクションFp
(pはpreviousのpです)に代入してループを継続します。(460行目~466行目)
//--- The agent decide action with optimized weight. double F; int qF; rrl.set_r(); F = rrl.calc_F(0, Fp); qF = rrl.quant(F); Fp = F;
量子化されたアクションqF
に従いオーダーを出します。(477行目~495行目)
//--- long if(qF == 1){ if(n_buy_pos == 0){ closeSellPos(); res = OrderSend(Symbol(),OP_BUY,lots,Ask,slippage,0,0,"",MAGIC,0,Blue); } } //--- short else if(qF == -1){ if(n_sell_pos == 0){ closeBuyPos(); res = OrderSend(Symbol(),OP_SELL,lots,Bid,slippage,0,0,"",MAGIC,0,Red); } } //--- neutral else{ closeBuyPos(); closeSellPos(); }
バックテスト結果
肝心のバックテスト結果ですが、まぁあまり良く無いというか全然ダメですね。以下、2016/3/25から2017/3/25までの一年間のバックテスト結果です。
学習時間が結構長いので、1回のバックテストで1晩ぐらいかかっちゃいますね。
1日2ケース流したとして、100ケース流すと50日か。。パラメータの最適化は電気代がかかりそうなのであまりやりたく無いなぁ。。
とりあえず放置です。
でもまぁ、バックテスト中のボットの取引をチャートで見ると結構面白いですね。
上がると思ってロングを入れるのですが、逆に下がって慌ててショートに切り替えるみたいな。まるで私の様な感じです。(笑)
今はお馬鹿ですが、いつか賢くなってくれるんでしょうかね。