読者です 読者をやめる 読者になる 読者になる

Pythonと機械学習

Pythonも機械学習も初心者ですが、頑張ってこのブログで勉強してこうと思います。

RRLをMT4で試してみる

強化学習 Metatrader4

目次

はじめに

3月はじめから書いているRRLシリーズも今回で5回目になりました。

今回はRRLをMetatrader4のEAに実装してみようと思います。

いきなりこの記事を読んだ人には何のことかさっぱりわからないと思うので、一応背景説明も。

再帰型強化学習(RRL)というアルゴリズムを株取引に適応すると結構いいらしいです。そこでRRLでFX取引をしたらどうなるのかという試みです。

詳細は過去記事を参照の事。

RRLを実装したEA

最近のEAはクラスを作れるんですね~。前々回RRLをC++で実装しましたが、mqlは文法がほぼC++でして、C++で書いたクラスが殆どそのままコピペでいけました。

追加で内積を求めるdot関数を作ったり、ハイパブリックタンジェントが無かったのでtanh関数を作ったりしてます。

細かく言うと、2次元配列の初期化ができなかったので、重みにかける{\mathbf{x}}を1次元配列にしたり、実数で出力されるエージェントの行動{F_t}をクォンタライザーに入れて、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 : 学習結果のログ(シャープレシオの学習過程と最適化後の重み)を出力するかどうか。

基本的に変更するのは、TMの値です。また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までの一年間のバックテスト結果です。

f:id:darden:20170325131239p:plain

学習時間が結構長いので、1回のバックテストで1晩ぐらいかかっちゃいますね。

1日2ケース流したとして、100ケース流すと50日か。。パラメータの最適化は電気代がかかりそうなのであまりやりたく無いなぁ。。

とりあえず放置です。

f:id:darden:20170325131248p:plain

でもまぁ、バックテスト中のボットの取引をチャートで見ると結構面白いですね。

上がると思ってロングを入れるのですが、逆に下がって慌ててショートに切り替えるみたいな。まるで私の様な感じです。(笑)

今はお馬鹿ですが、いつか賢くなってくれるんでしょうかね。