学生時代にも ML 系について勉強していましたが、久々に 1 から始めてみようと思い立ちました。
しかし勉強したところで業務に利用することもないし、もちろんお金になりません。
だったら、ビットコインの価格予想で儲けることができればモチベも上がるんじゃないかと。 ということで頑張ってみます。
先に結果を記載しますと、 一見すると予想はいい感じだけど、いざ取引してみると爆損です。
一見するといい感じのグラフ
少しでも興味のある方への手助けになれば幸いですが、私自身かなり浅い所にいるのは自覚しているのでご承知おきください。
使用するもの
今回は keras
と keras-rl
を利用します。なぜなら”私の”学習コストが安いからです。
エージェントにはたくさん学習させますが、私の学習時間は減らしたかったので。社会人は時間がないのです。勉強しながら働ける職場を熱望します。
価格予想には強化学習を利用します。
Keras Keras-rl
Keras は簡単に機械学習できるライブラリです。機械学習についての基礎は、良くも悪くもいらないです。
Keras-rl はそれをさらに簡単にしたライブラリです。「なんでもいいから機械学習させてくれ」って方におすすめです。
keras.io
ビットコイン
ビットコイン FX は 24 時間稼働していますし、ボラティリティが大きいので成果が確認しやすいため、今回の実験対象としました。
私自身は少しだけアカウント取ってみただけでトレード知識はほとんどありません。
環境構築
keras を用意したところで、まだ実験を始めることはできません。
機械学習には学習させるためのデータが必要です。
データ取得
FX 予想に使われるデータとしては、時系列データである OHLC が一般的ですね。
OHLC は過去のデータを簡単に取得できるので、学習データを集めやすいのが大きいメリットです。
しかし、私は下記仮定を立てました。
- 価格情報は学習データの中でユニークな値になりやすいのではないか
- ユニークな値で学習すると Agent が安易に学習してしまうのではないか
その結果、 OHLC で学習しても使い物にならないのではないか (過学習になりやすいのではないか)と予想を立てました。
※過学習=データの丸暗記みたいなもの。汎用性がない。
対策として OHLC を値そのままで使わず、増減率にするなどのアイデアもありましたが、今回は板情報から”板の厚み”を学習データにすることを考えました。
そこに価格情報は含めず、「midprice から x 円離れたところの厚み」を利用します。
これがいい選択である確信なんてありません。ですが、何事もトライアル&エラーです。
板情報の取得
bitFlyer API で板情報を取得して sqlite
で保存します。
保存方法は下記を参考にしました。
https://qiita.com/onhrs/items/b64144cd63cf7484a35a
https://note.mu/akagami/n/nda6159c7b8ad
コード全文は上記有料 note を改変したものですので載せるのは控えます。
累積方法は以下のコードを参考にしてください。
# 欲しい深さによって変更
DEPTH = [10, 20, 30, 50, 80, 130, 210, 340, 550, 890, 1440, 2330, 3770, 6100, 9870]
askvol = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
bidvol = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
for i in range(len(DEPTH)):
for ask in board['asks']:
if DEPTH[i] < ask['price'] - board['mid_price']:
break
else:
askvol[i] += ask['size']
for bid in board['bids']:
if DEPTH[i] < board['mid_price'] - bid['price']:
break
else:
bidvol[i] += bid['size']
私はだいたい 1 週間を 1 つのファイルに保存していましたが、データベースが大きくなっていくと徐々に処理が重くなっていくので注意が必要です。
計算リソースの確保
効率的に ML を進めるには計算能力のある環境が必要です。
そこで今回は AWS の EC2 インスタンス p2-xlarge
と、AMI Deep Learning AMI (Ubuntu)
の組み合わせで環境を作ります。
AWS アカウントについての詳細は割愛しますが、インスタンスの作成で下記 AMI を選びましょう。
インスタンスに接続すると「環境を選べ」みたいなメッセージが出ますが、source activate tensorflow_p36
を実行しましょう。
p2.xlarge
のコスパはマシンのスペックから考えるといいんですが、消し忘れるととても痛いです。
価格予想で一攫千金を狙うより、素直に節約した方がお金は貯まると思います。
もちろん、モデルが複雑でない限りはハイスペックはいらないです。
私は途中からモデルをシンプルにしたので、t2.midium の CPU で計算させていました。お手元の mac book でもいけますが、他の作業ができなくなるのでやめておきましょう。
実装と実験
とにかく改変 & 改変で作ったので、色々と非効率だったりすると思います。
整備できてなくてお恥ずかしいですが、GitHub ならこちら。
github.com
DQNBot
- Agent には 3 つの選択肢 “BUY/SELL/STAY(何もしない)” を与える
- 与える状況は “一定間隔の板の厚さ”
- BUY -> (STAY) -> SELL もしくはその逆の順番で action が選択された時、その差額を報酬として与える
実装 1
import sys | |
from time import sleep | |
import random | |
from datetime import datetime | |
from sklearn import preprocessing | |
import gym | |
import gym.spaces | |
import numpy as np | |
from keras.models import Sequential | |
from keras.layers import Dense, Activation, Flatten, LSTM | |
from keras.optimizers import Adam | |
from keras.models import model_from_json | |
from rl.agents.dqn import DQNAgent | |
from rl.policy import EpsGreedyQPolicy | |
from rl.memory import SequentialMemory | |
from keras.initializers import TruncatedNormal | |
import rl.callbacks | |
import matplotlib.pyplot as plt | |
import pandas as pd | |
import pandas.io.sql as psql | |
import sqlite3 | |
class DQNBot(gym.core.Env): | |
def __init__(self): | |
# Agentにさせる行動はBUY/SELL/STAY(何もしない) | |
self.BUY = 0 | |
self.SELL = 1 | |
self.STAY = 2 | |
self.action_space = gym.spaces.Discrete(3) | |
self.con = sqlite3.connect(sys.argv[1]) | |
# ask/bidの観測範囲 | |
# 0番目は BUY SELL STAYで0-2 | |
# それ以外は0-1で正規化する | |
low_list = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | |
high_list = [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] | |
low = np.array(low_list) | |
high = np.array(high_list) | |
# DBからask/bidのpandasデータフレームを取得 | |
board_df = psql.read_sql('SELECT ask_price_100, ask_price_200, ask_price_300, ask_price_500, ask_price_800, ask_price_1300, ask_price_2100, ask_price_3400, ask_price_5500, ask_price_8900, \ | |
bid_price_100, bid_price_200, bid_price_300, bid_price_500, bid_price_800, bid_price_1300, bid_price_2100, bid_price_3400, bid_price_5500, bid_price_8900 FROM boards;', self.con) # DBからPandasデータフレーム取得 | |
# DBからmidpriceのpandasデータフレームを取得 | |
board_mid_df = psql.read_sql('SELECT mid_price FROM boards;', self.con) | |
# 観測範囲を定義 | |
self.observation_space = gym.spaces.Box(low=low, high=high) | |
# 0-1で正規化して配列取得 | |
self.board_array = preprocessing.minmax_scale(board_df.values, axis=1) | |
# DBの長さを取得 | |
self.board_array_rows = len(self.board_array) | |
# midpriceの配列を用意 | |
self.board_mid = board_mid_df.values | |
self.step_count = 0 | |
# 指定stepのask/bidを取得 | |
def get_state(self, count): | |
return self.board_array[count].flatten() | |
# 指定stepのmidpriceを取得 | |
def get_midprice(self, count): | |
return self.board_mid[count].flatten()[0] | |
# 視覚化の時にだけ使用 本来は多分いらない | |
def get_midprice_list(self): | |
return self.board_mid.flatten() | |
# 各stepごとに呼ばれる | |
def step(self, action): | |
# stepのカウントアップ | |
self.step_count += 1 | |
# 学習データが終わりそうならdoneをTrueにしてstepを0に戻す | |
done = self.board_array_rows - 20 < self.step_count | |
if done: | |
self.step_count = 0 | |
reward = 0 | |
if action == self.BUY: | |
if self.pos[0] == self.STAY: | |
self.pos = [self.BUY, self.get_midprice(self.step_count)] | |
elif self.pos[0] == self.SELL: | |
reward = self.pos[1] - self.get_midprice(self.step_count) | |
self.pos = [self.STAY, 0] | |
# 学習時は売買が成立した時点で区切る(=結果を学習させる) | |
if sys.argv[2] == 'train': | |
done = True | |
elif action == self.SELL: | |
if self.pos[0] == self.STAY: | |
self.pos = [self.SELL, self.get_midprice(self.step_count)] | |
elif self.pos[0] == self.BUY: | |
reward = self.get_midprice(self.step_count) - self.pos[1] | |
self.pos = [self.STAY, 0] | |
if sys.argv[2] == 'train': | |
done = True | |
# 次のstate、reward、終了したかどうか、追加情報の順に返す | |
# ask/bidの情報 + ポジション情報を渡す | |
# 追加情報は特にないので空dict | |
return np.insert(self.get_state(self.step_count), 0, self.pos[0]), reward, done, {} | |
# 各episodeの開始時に呼ばれる | |
# 初期情報を渡す | |
def reset(self): | |
self.pos = [self.STAY, 0] | |
self.profit = 0 | |
if sys.argv[2] == 'train': | |
pass | |
# randomにしてもいいかもしれないですね | |
# self.step_count = random.randint(0, self.board_array_rows - 2000) | |
else: | |
self.step_count = 0 | |
return np.insert(self.get_state(self.step_count), 0, self.pos[0]) | |
# 学習状況の保存 | |
class EpisodeLogger(rl.callbacks.Callback): | |
def __init__(self): | |
self.observations = {} | |
self.rewards = {} | |
self.actions = {} | |
def on_episode_begin(self, episode, logs): | |
self.observations[episode] = [] | |
self.rewards[episode] = [] | |
self.actions[episode] = [] | |
def on_step_end(self, step, logs): | |
episode = logs['episode'] | |
self.observations[episode].append(logs['observation']) | |
self.rewards[episode].append(logs['reward']) | |
self.actions[episode].append(logs['action']) | |
if __name__ == "__main__": | |
env = DQNBot() | |
nb_actions = env.action_space.n | |
if sys.argv[2] == 'train': | |
input_shape = (1,) + env.observation_space.shape | |
# DQNのネットワーク定義 | |
# とりあえずオプションはデフォルト | |
model = Sequential() | |
model.add(Flatten(input_shape=(1,) + env.observation_space.shape)) | |
model.add(Dense(512)) | |
model.add(Dense(512)) | |
model.add(Dense(nb_actions)) | |
print(model.summary()) | |
# experience replay用のmemory | |
# 各ステップごと順番に学習させるわけではく、一度メモリに保存してからランダムに抽出と学習するとか | |
# 正直、完全には理解できていません | |
memory = SequentialMemory(limit=40000, window_length=1) | |
# 行動方策はオーソドックスなepsilon-greedyです。 | |
policy = EpsGreedyQPolicy(eps=0.1) | |
# warmup = 文字通り準備運動のイメージ いきなり学習させずにある程度メモリに貯めると思ってる | |
# update = 学習率 小さくすると時間がかかるし、高くすると過学習しやすくなる | |
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory, nb_steps_warmup=100, | |
target_model_update=1e-2, policy=policy) | |
dqn.compile(Adam(lr=0.001)) | |
# nb_steps = 何ステップ学習させるか 数値をめちゃくちゃ大きくして、一晩経ったらCtrl+Cで止めるとかでも別にいい | |
# max_episode_steps = 1エピソードの最大ステップ | |
history = dqn.fit(env, nb_steps=400000, visualize=False, verbose=2, nb_max_episode_steps=1440) | |
# modelとweightの保存 | |
now = datetime.now().strftime("%Y%m%d%H%M%S") | |
dqn.save_weights('weight_' + str(now) + '.h5') | |
model_json = model.to_json() | |
with open('model_' + str(now) + '.json', "w") as json_file: | |
json_file.write(model_json) | |
elif sys.argv[2] == 'test': | |
# modelのロード | |
json_file = open(sys.argv[3], 'r') | |
loaded_model_json = json_file.read() | |
json_file.close() | |
model = model_from_json(loaded_model_json) | |
print(model.summary()) | |
# 学習後のテストをしたいだけなのに以下宣言が必要なのかは不明 一応同じようにdqnを設定していく | |
memory = SequentialMemory(limit=2000000, window_length=1) | |
policy = EpsGreedyQPolicy(eps=0.1) | |
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory, nb_steps_warmup=100, | |
target_model_update=1e-2, policy=policy) | |
dqn.compile(Adam(lr=0.001)) | |
# weighのロード | |
dqn.load_weights(sys.argv[4]) | |
cb_ep = EpisodeLogger() | |
# テストを実行 | |
# データベースで一通り売買してもらう | |
# 時間がかかるので、consoleに状況を出すようにstepメソッド内で実装してもいいかも | |
dqn.test(env, nb_episodes=1, visualize=False, callbacks=[cb_ep]) | |
# 結果の視覚化 | |
print("COUNT BUY : " + str(list(cb_ep.actions.values())[0].count(0))) | |
print("COUNT SELL : " + str(list(cb_ep.actions.values())[0].count(1))) | |
print("COUNT STAY : " + str(list(cb_ep.actions.values())[0].count(2))) | |
plt.subplot(211) | |
plt.plot(env.get_midprice_list(), linewidth=0.1) | |
plt.subplot(212) | |
rw_list = [] | |
reward = 0 | |
for ep_reward in list(cb_ep.rewards.values())[0]: | |
reward += ep_reward | |
rw_list.append(reward) | |
print("\rReward: " + str(len(rw_list)), end='') | |
print("") | |
plt.plot(rw_list) | |
plt.xlabel("step") | |
plt.ylabel("price") | |
# dpiが低いと荒すぎる | |
plt.savefig("figure.png",format = 'png', dpi=1200) |
結果 1
「Agent はノーポジとガチホを覚えた!!!」
何十万ステップもトレーニングさせると、「これ取引するより何もしないほうがいいよね」「最初に買ってガチホが一番だよね」と考えるようになってしまいました。
逆に学習を少なくしても、ひたすらに負け続けていました。
実装 2 LSTM を導入する
モデルを変えて LSTM を導入してみます。
「実はこの板の厚みの変化って長期的に見る必要があるんじゃないか」と仮説を立て、LSTM を選定しました。
ネットワーク定義を以下に変えてみます。
# DQNのネットワーク定義
model = Sequential()
model.add(LSTM(units=512, return_sequences=True, input_shape=input_shape))
model.add(Dropout(dropout))
model.add(LSTM(units=512, return_sequences=False))
model.add(Dropout(dropout))
model.add(Dense(units=nb_actions))
結果 2
「Agent はノーポジとガチホを覚えた!!!」
変わらないですね。
反省と仮説
-
1step(1sec)毎に 3 択を与えるって Agent にとってとても難しいことではないのか?
-> ゲームを攻略できる DQN があるくらいだからいけると思いますが… -
シンプルにパラメータや考え方が悪いんじゃないか?
-
一定期間後に上か下か予想させる方が効率よく学習できるんじゃないか?
-> やってみよう
DQNBot_Label
- Agent には 3 つの選択肢 “BUY/SELL/STAY(何もしない)” を与える
- 与える状況は “一定間隔の板の厚さ”
- BUY or SELL が選択された時、一定期間後の midprice との差額を報酬として与える
- Label って名前に特に意味はない
報酬の与え方も少し工夫します。
報酬をシンプルにすることで、汎用性を高めることが目的です。
- 一定額以上のプラス差額 “報酬 1”
- 一定額未満のプラス差額 “報酬 0”
- マイナス差額 “報酬-1”
実装
import sys | |
from time import sleep | |
import random | |
from datetime import datetime | |
from sklearn import preprocessing | |
import gym | |
import gym.spaces | |
import pickle | |
import numpy as np | |
from keras.models import Sequential | |
from keras.layers import Dense, Activation, Flatten, LSTM, Dropout | |
from keras.optimizers import Adam | |
from keras.models import model_from_json | |
from keras.callbacks import TensorBoard | |
from keras.initializers import TruncatedNormal | |
from rl.agents.dqn import DQNAgent | |
from rl.policy import EpsGreedyQPolicy | |
from rl.policy import BoltzmannQPolicy | |
from rl.policy import GreedyQPolicy | |
from rl.policy import BoltzmannGumbelQPolicy | |
from rl.memory import SequentialMemory | |
import rl.callbacks | |
import matplotlib.pyplot as plt | |
import pandas as pd | |
import pandas.io.sql as psql | |
import sqlite3 | |
class DQNBot(gym.core.Env): | |
def __init__(self): | |
self.term = 20 | |
self.margin = 10 | |
self.BUY = 0 | |
self.SELL = 1 | |
self.STAY = 2 | |
self.action_space = gym.spaces.Discrete(3) | |
self.con = sqlite3.connect(sys.argv[2]) | |
low_list = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | |
high_list = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] | |
low = np.array(low_list) | |
high = np.array(high_list) | |
board_df = psql.read_sql('SELECT ask_price_100, ask_price_200, ask_price_300, ask_price_500, ask_price_800, ask_price_1300, ask_price_2100, ask_price_3400, ask_price_5500, ask_price_8900, \ | |
bid_price_100, bid_price_200, bid_price_300, bid_price_500, bid_price_800, bid_price_1300, bid_price_2100, bid_price_3400, bid_price_5500, bid_price_8900 FROM boards;', self.con) # DBからPandasデータフレーム取得 | |
board_mid_df = psql.read_sql('SELECT mid_price FROM boards;', self.con) | |
self.observation_space = gym.spaces.Box(low=low, high=high) | |
self.board_array = preprocessing.minmax_scale(board_df.values, axis=1) | |
self.board_array_rows = len(self.board_array) | |
self.board_mid = board_mid_df.values | |
self.step_count = 0 | |
def get_state(self, count): | |
return self.board_array[count].flatten() | |
def get_midprice_list(self): | |
return self.board_mid.flatten() | |
def get_midprice(self, count): | |
return self.board_mid[count].flatten()[0] | |
# 各stepごとに呼ばれる | |
def step(self, action): | |
self.step_count += 1 | |
done = self.board_array_rows - 400 < self.step_count | |
reward = 0 | |
if action == self.BUY: | |
if sys.argv[1] == 'train': | |
pl = self.get_midprice(self.step_count + self.term) - self.get_midprice(self.step_count) | |
if self.margin < pl: | |
reward = 1 | |
elif pl < 0: | |
reward = -1 | |
done = True | |
else: | |
reward = self.get_midprice(self.step_count + self.term) - self.get_midprice(self.step_count) | |
# self.step_count += 60 | |
elif action == self.SELL: | |
if sys.argv[1] == 'train': | |
pl = self.get_midprice(self.step_count) - self.get_midprice(self.step_count + self.term) | |
if self.margin < pl: | |
reward = 1 | |
elif pl < 0: | |
reward = -1 | |
done = True | |
else: | |
reward = self.get_midprice(self.step_count) - self.get_midprice(self.step_count + self.term) | |
# self.step_count += 60 | |
if sys.argv[1] == 'test': | |
print('\r' + "step: " + str(self.step_count), end='') | |
if done: | |
print("") | |
return self.get_state(self.step_count), reward, done, {} | |
def reset(self): | |
self.pos = [self.STAY, 0] | |
self.profit = 0 | |
if sys.argv[1] == 'train': | |
self.step_count = random.randint(0, self.board_array_rows - 2000) | |
else: | |
self.step_count = 0 | |
return self.get_state(self.step_count) | |
class EpisodeLogger(rl.callbacks.Callback): | |
def __init__(self): | |
self.observations = {} | |
self.rewards = {} | |
self.actions = {} | |
def on_episode_begin(self, episode, logs): | |
self.observations[episode] = [] | |
self.rewards[episode] = [] | |
self.actions[episode] = [] | |
def on_step_end(self, step, logs): | |
episode = logs['episode'] | |
self.observations[episode].append(logs['observation']) | |
self.rewards[episode].append(logs['reward']) | |
self.actions[episode].append(logs['action']) | |
if __name__ == "__main__": | |
env = DQNBot() | |
nb_actions = env.action_space.n | |
memory = SequentialMemory(limit=200000, window_length=1) | |
warmup = 1000 | |
model_update = 1e-2 | |
policy = EpsGreedyQPolicy(eps=0.1) | |
if sys.argv[1] == 'train': | |
input_shape = (1,) + env.observation_space.shape | |
dropout = 0.5 | |
# DQNのネットワーク定義 | |
model = Sequential() | |
model.add(LSTM(units=512, return_sequences=True, input_shape=input_shape)) | |
model.add(Dropout(dropout)) | |
model.add(LSTM(units=512, return_sequences=False)) | |
model.add(Dropout(dropout)) | |
model.add(Dense(units=nb_actions)) | |
print(model.summary()) | |
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory, nb_steps_warmup=warmup, | |
target_model_update=model_update, policy=policy) | |
dqn.compile(Adam(lr=0.001)) | |
tbcb = TensorBoard(log_dir='./graph', histogram_freq=0, write_grads=True) | |
history = dqn.fit(env, nb_steps=5000000, verbose=2, nb_max_episode_steps=60, callbacks=[tbcb]) | |
now = datetime.now().strftime("%Y%m%d%H%M%S") | |
dqn.save_weights('weight1_' + str(now) + '.h5') | |
model_json = model.to_json() | |
with open('model1_' + str(now) + '.json', "w") as json_file: | |
json_file.write(model_json) | |
with open("history.pickle", mode='wb') as f: | |
pickle.dump(history.history, f) | |
elif sys.argv[1] == 'test': | |
json_file = open(sys.argv[3], 'r') | |
loaded_model_json = json_file.read() | |
json_file.close() | |
model = model_from_json(loaded_model_json) | |
print(model.summary()) | |
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory, nb_steps_warmup=warmup, | |
target_model_update=model_update, policy=policy) | |
dqn.compile(Adam(lr=0.002)) | |
dqn.load_weights(sys.argv[4]) | |
cb_ep = EpisodeLogger() | |
dqn.test(env, nb_episodes=1, visualize=False, callbacks=[cb_ep]) | |
print("COUNT BUY : " + str(list(cb_ep.actions.values())[0].count(0))) | |
print("COUNT SELL : " + str(list(cb_ep.actions.values())[0].count(1))) | |
print("COUNT STAY : " + str(list(cb_ep.actions.values())[0].count(2))) | |
plt.subplot(211) | |
plt.plot(env.get_midprice_list(), linewidth=0.1) | |
plt.subplot(212) | |
rw_list = [] | |
reward = 0 | |
for ep_reward in list(cb_ep.rewards.values())[0]: | |
reward += ep_reward | |
rw_list.append(reward) | |
print("\rStep: " + str(len(rw_list)), end='') | |
print("") | |
plt.plot(rw_list) | |
plt.xlabel("step") | |
plt.ylabel("price") | |
plt.savefig("figure.png",format = 'png', dpi=1200) |
結果
train データそのままを test してたところ、かなり儲けているようです。(パラメータや学習回数は記録できていません。。)
青が SELL 赤が BUY
しかし他のデータセットで test したところ、惨敗です。
このモデルで bitFlyer の Bot も作ってみましたが、取引の回数が少なく、十分なデータを集められませんでした。(STAY ばかり選択される)
反省
- 汎用性がない どうやら過学習になっているように見受けられる
- 急騰/急落時にリバを期待して逆張りしていくっぽい
- もっとガンガン取引してスキャって欲しい
対策
以下の案で考えています。まだ検証は進められていません。
-
汎用性がない
-> モデルをシンプルにする dropout を高めに設定する 学習データをもっと増やす -
急騰/急落時にリバを期待して逆張りしていくようだ
-> モデルをシンプルにする -
もっとガンガン取引して欲しい
-> モデルをシンプルにする action を BUY/SELL の 2 択にする
まとめ
「DQNBot で儲けられたのか」については、
「今の所、儲けられていません」 が回答になります。
バックテストでこの状況では、確実に実環境では大損します。実環境ではスプレッドなども考慮しなければならず、それを賄えるほどの報酬を安定して出さなければ行けません。
過学習を乗り越えた先に何かがあると信じて、暇を見つけて進めたいと思います。上手くいった日には、続編として書くかもしれません。
最初のグラフ
最初に載せたグラフは action を BUY/SELL の 2 択にして 1 秒毎 1 分先の価格予想した結果です。
めちゃくちゃいい感じに伸びていますが、これも過学習でした。
参考にさせていただいたサイト
DQN で機械学習した人工知能が Bitcoin をシストレして月 700 万円儲けるまでの話(失敗) - Qiita
Deep Q-Learning で FX してみた
機械学習やディープラーニングで FX 予測をする際に超参考になる記事まとめ
https://ai-kenkyujo.com/2018/10/16/kagakushu/
[Python] Keras-RL で簡単に強化学習(DQN)を試す - Qiita