どうも、ryo_gridです。
おはこんばんちわ。
以下の記事の通り(追記部分も含めて)やってみたのですが、残念ながらこの記事の内容だけではうまくいきませんでした。
深層強化学習でのFX自動トレード(のシミュレーション)がうまくいかないのでオレオレ手法を考えた - Qiita
が、上記リンク先のオレオレ手法(追記部分は除く)に更なる改良を加えることで、学習データではようやくまともに学習したであろう結果を得ることができました。
本記事ではその、更なる改良の部分(差分)+ 省略するとあまりに読みにくくなる部分、について記述したいと思います。
それ以外については上のリンク先をご参照下さい。
実装について
-
学習処理全体について
- 学習データでまともに動くことを確認するのが先決なので、テストデータの量は減らし3年分(5分足)から、半年分に変更した
- Q関数を表現するNNの重みを更新する際の教師シグナル算出における時間割引率は、0.3としていたが、0.9に変更した。これは、各 state x action の reward を過去20アクションの獲得pipsの総計としたので(後述)、未来に得られる報酬を最大化することを考慮した上で各rewardを求めていくというQ学習の考え方を素直に受け入れた方が良いと判断したため。ただし、一般的に用いられる0.99といった値だと、上述の20アクションという数字と合わない(0.99の累乗は20乗を超えてもかなりおおきな値となる)ため、0.9まで落としている
- 学習が進むにつれランダムなaction選択が減っていくグリーディー法を用いているが、変更前の実装だと、最適な行動が大半になる状態には、かなりのイテレーションが回らないと至らないようになっていたが、そもそも、どの程度のイテレーションが必要なのか良くわかっていない上に、変更前の実装でイテレーション数を減らすと序盤でのランダムな選択の確率が減って学習が進みにくくなるように見えたため、折衷案として、2イテレーションおきに、最適行動のみをとるイテレーションを挟むという、なんちゃってグリーディー法に変更した(最適行動のみをとるイテレーション以外は全てランダムというわけではなく、これまで同様、徐々にランダム選択の確率が減っていく計算式をそのまま用いている)
-
reward
- BUY
- 現在と過去19アクションでの獲得pipsの総計(マイナスの場合ももちろんあり)。ただし、現在の値はactionをとった時点では不定であるため、CLOSEされた時に確定した値で、更新される(後述)
- CLOSE
- 過去19アクションでの獲得pipsの総計
- CLOSEアクションは保有ポジション全てを決済するが、その際に保有していたポジション個別での獲得pipsを求めて、各ポジションの識別子とともに返す(environment側の挙動)
- agent側は、CLOSEアクションをとった場合、返ってきたポジション個別の獲得pipsで、memory に記録してある対応するBUYアクションのエピソード情報の reward を変更する(※)
- このようにすることで、BUYアクションのrewardを後追いで求める。NNの重みの更新はランダムreplayでのみ更新されるので、そのうち変更されたrewardが NNで表現されるQ関数に反映される、という目論見
- CLOSEした場合は、BUYのrewardが更新されるが、それに伴い、最後に行われたCLOSE以降のDONOTアクション時のrewardも更新されなければならないため、そのように上記の仕組みを修正した
- また、※のようなことをするため、CLOSE自体のrewardも、actionをとった直後だが更新するようにした
- DONOT
- 過去19アクションでの獲得pipsの総計
- BUY
-
state
-
現在価格、前の足からの変化率(符号あり)、各種テクニカル指標。加えて、過去の20アクションを数値にエンコードしたスカラ値。エンコード方法は、各アクションは0,1,2の対応する数字を持っているので、それらを時系列の逆に並べて、3進数として整数化し、全てが2(DONOTに対応)の場合を最大値として、0~1に正規化するというもの。時系列の逆に並べているのは、より近い過去のstateが数値化した際に大きな変化として現れるようにするため
-
最後の一つ以外は、同じ為替データであればかならず同じ値(テクニカル指標算出のパラメータを変えたりしない限り)であるため、テスト期間の為替データをなめながら何周も回すとした場合、同じstate、遷移先stateで異なるrewardを得るということになってしまうため、それを回避するために過去のアクションの情報をstateに加えている
-
action
- タスクをできるだけ単純にするため、ポジションはロングのみ扱うようにしている
- BUY
- BUYのアクションをした場合、設定した分割数(保有可能な取引通貨の塊の数)でポジションの状態になっているものも含めた総資産を分割した額の分、ロングポジションを購入する(厳密には記述されたような値にはならないが、それに近い額になるようにはしてある)
- 保有している塊の数が規定数(今のところ100にしている)を超えると、BUYのアクションをとっても購入は行われない
- 学習を進めていった結果、設定した塊を保有したままずっとHOLDするという挙動が発生したため、Q学習の手法とは本来相いれない手段ではあるが、BUYアクションの結果100の塊に到達した場合は、次のactionで必ずCLOSEするよう environment がagentに通知を返すようにした
- CLOSE
- 全てのポジションを決済する
- DONOT
- 何もしない
-
ソースコード
-
リポジトリ(の作業ブランチ): github
-
ソースファイル: agent, environment
※:
stateに保有ポジションや過去のアクションの情報を含めない場合、agentがとったactionに関わらず、stateの遷移は一本道になってしまうため、同じテスト期間を何周もさせる場合、同じstateで異なるrewardを得るということが発生する。
その場合、同じstate、遷移先stateというパラメータで、異なるrewardが教師信号の算出に用いられるということが発生するが、それは、(私の理解したと思っている)Q学習の考え方としておかしいのではないかと考えた。また、memoryの記憶可能容量に制限を設けている場合を試していた際に、直近の何回のrewardのみがreplayで用いられるのもまずいのではないかと考えた。
そこで、state に為替データから算出されるもの以外の何か、が含まれない場合、イテレーション(周回)を跨いで、過去に同じ state x action で得た全てのrewardの平均値を求められるようにして、上記のCLOSEアクション時の reward の更新において用いるようにした。
そして、その処理はstateに直近20アクションの情報が含まれる今も残してある。しかしながら、この記事で記述している実装ではstateの遷移は一本道ではないので、この仕組みは残してはあるが、重複することはほとんどなく、あまり意味がない、というか、余計な処理コストを発生させているだけのような気もする
結果(テストデータ期間でのバックテスト)
7イテレーションの途中
14イテレーションの途中
18イテレーションの途中
33イテレーションの途中
41イテレーションの途中
結果の考察
- 14イテレーションまでは、最終的なパフォーマンスは低いものの、常勝という、テストデータではあるべき結果(?)に向かっているようであったが、18イテレーションあたりから大きな2つの谷のあるグラフとなってしまい、その後もあまり変化が見られなくなってしまった。
- 私の中でのテストデータにおける成功は(ほぼほぼ)単調増加という結果なので、成功とはいいがたい
- コードロジックから分析すると、過去の何アクションを見るか、gammaの値、NNのunit数(数が少なく、表現力が足りない、など)が適切でないのではないかと考えている
以上です!