DQNを勉強しているので、以下の記事に触発されて自分もFXでDQNをしてみたくなった。

2年前、OANDAでのシステムトレードで5万円を損した屈辱を今こそ晴らすのだ!

まずは、GMOの記事でも実施しているように、正弦曲線に従う為替の値動きについて、

DQNを適用してバックテストを行ってみた。

今回のJupyter Notebookは Gistにあげました。

正弦曲線

以下のような正弦曲線について、DQNでバックテストを試みる。

MDP

  • 状態
    • (-1.0, 1.0) の範囲の値
    • 売買のステータス(SELL, NONE, BUY)
  • 行動
    • SELL
    • HOLD
    • BUY
  • 報酬
    • 売買の即時報酬

コード

`import enum
class Action(enum.Enum):
    SELL = 2
    HOLD = 0
    BUY  = 1

class Position():
    def __init__(self):
        self.NONE = 0
        self.BUY = 1
        self.SELL = 2

        self.bid = 0.0
        self.pos_state = self.NONE

    def state(self):
        return np.array([self.bid, self.pos_state])

    def change(self, action, bid):
        self.bid = bid
        self.pos_state = action

    def close(self, action, bid):
        if self.pos_state == action:
            return 0

        self.pos_state = self.NONE
        if action == Action.BUY.value:
            reward = (bid - self.bid) * 1000
        else:
            reward = (self.bid - bid) * 1000
        return reward`

keras-rl を利用するために、環境を OpenAI gym ライクで作成する。

`import gym
import gym.spaces

class FXTrade(gym.core.Env):
    def __init__(self):
        self.action_space = gym.spaces.Discrete(3)
            high = np.array([1.0, 1.0])
            self.observation_space = gym.spaces.Box(low=-high, high=high)

            self._position = Position()

            self._sin_list = []
            for t in np.linspace(0, 48, 240):
                self._sin_list.append(np.sin(t))
            self.cur_id = 0

    def _step(self, action):
        bid = self._sin_list[self.cur_id]
        self.cur_id +=1
        done = True if self.cur_id == 240 else False

        if action == Action.HOLD.value:
            reward = 0
        else:
            if self._position.pos_state == self._position.NONE:
                self._position.change(action, bid)
                reward = 0
            else:
                reward = self._position.close(action, bid)
        return np.array([bid, self._position.pos_state]), reward, done ,{}

    def _reset(self):
        self.cur_id = 0
        self._position = Position()
        return np.array([0.0, 0.0])`

keras-rlの登場

強化学習を笑っちゃうほど簡単に実施するために Keras-rlをつかう。

このライブラリは抽象度が高すぎてもはやなにをやっているのかわからない。

exampleを参考にして、それっぽいコードを書いてみる。

`from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten
from keras.optimizers import Adam

from rl.agents.dqn import DQNAgent
from rl.policy import BoltzmannQPolicy
from rl.memory import SequentialMemory

nb_actions = env.action_space.n

model = Sequential()
model.add(Flatten(input_shape=(1,) + env.observation_space.shape))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(nb_actions))
model.add(Activation('linear'))
model.summary()

# DQNに必要な Experience Replayのためのメモリ
memory = SequentialMemory(limit=50000, window_length=1)
# ボルツマン分布に従うポリシー
policy = BoltzmannQPolicy()
# DQN Agentの生成
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory,
               target_model_update=1e-2, policy=policy)
# optimizersはAdam, 損失関数は 平均二乗誤差
dqn.compile(Adam(lr=1e-3), metrics=['mae'])

# トレーニングを開始。同じ正弦曲線を9600 = 240 x 400回 回す。
dqn.fit(env, nb_steps=9600, visualize=False, verbose=1)

# トレーニング結果を確認
dqn.test(env, nb_episodes=5, visualize=False)

`

おお、rewardがプラスだ。。

`Testing for 5 episodes ...
Episode 1: reward: 603.962, steps: 240
Episode 2: reward: 603.962, steps: 240
Episode 3: reward: 603.962, steps: 240
Episode 4: reward: 603.962, steps: 240
Episode 5: reward: 603.962, steps: 240`

おわりに

  • 理論を抑えていないので何をやっているのかがいまいちわからないのだけれども、おそらく上がり調子のときは報酬もたくさんもらえるので、買い注文を多くしているのだと思う。
  • keras-rlは非常に強力なライブラリだけれども、抽象度が高すぎてなにやってるのかよくわからない。理解を深めるために numpyで実装してみるのもありかもしれない。
  • 状態は、その時の値のみを扱ったが、過去5bin分の状態を考慮にいれたらどうなるだろうか?いわゆるwindowの概念を強化学習にいれてみると、結果がよくなるだろうか?