神経ネットワークとデジタル通貨量化取引シリーズ (2) ディープ強化学習訓練ビットコイン取引戦略

作者: リン・ハーン小草作成日:2019年07月31日 11:13:15 更新日:2023年10月20日 20時10分18秒

img

1.介绍

前回の記事では,LSTMネットワークを使ってビットコインの価格を予測した.https://www.fmz.com/digest-topic/4035この記事では,RNNとpytorchを熟知するための練習者の小さなプロジェクトである. この記事では,強化学習を使用して取引戦略を直接訓練する方法について説明します. 強化学習モデルはOpenAIのオープンソースPPOであり,環境はジムのスタイルを参照しています. 理解とテストの便利のために,LSTMのPPOモデルと復習されたジムの環境は,未使用の現成パッケージを直接記述しています. PPO (Proximal Policy Optimization) は,Policy Graident,つまり戦略梯子の最適化の一種である.gymもOpenAIによってリリースされている. 戦略ネットワークと相互作用し,現在の環境の状態と報酬をフィードバックすることができる. この記事を読むには,Python、pytorch、DRLの深層学習の基礎が必要である.しかし,関係ありません.この記事で与えられたコードと組み合わせて,簡単に学ぶことができます.この記事ではFMZの発明者 デジタル通貨量化取引プラットフォームを紹介します.www.fmz.comQQグループ:863946592へようこそ.

2.数据和学习参考资料

ビットコインの価格データは,FMZの発明者による量化取引プラットフォームから得られたものです.https://www.quantinfo.com/Tools/View/4.htmlDRL+gymを使って取引戦略を訓練した記事:https://towardsdatascience.com/visualizing-stock-trading-agents-using-matplotlib-and-gym-584c992bc6d4投稿者:Pytorch さんhttps://github.com/yunjey/pytorch-tutorialこの記事では,このLSTM-PPOモデルの簡単な実装を直接使用します.https://github.com/seungeunrho/minimalRL/blob/master/ppo-lstm.pyこの記事へのトラックバック一覧です.https://zhuanlan.zhihu.com/p/38185553DRLに関する他の記事:https://www.zhihu.com/people/flood-sung/posts健身器については,この記事では設置は必要ありませんが,補強学習はよく使われます:https://gym.openai.com/

3.LSTM-PPO

詳細なPPOの説明については,前回の参考資料で学ぶことができます.ここは単純な概念の紹介です. 前回のLSTMネットワークは,単に価格を予測し,この予測価格に基づいて売買取引をどのように実現するか,直接輸出する売買行動がより直接的ではないのか,考えられるでしょう. Policy Graidentは,入力された環境情報に基づいて様々なアクションアクションの確率を与えることができます. LSTMの損失は予測価格と実際の価格の違いであり,PGの損失は-log§*Qであり,pは出力された特定の動作の確率であり,Qはこの動作の価格である.

以下はLSTM-PPOのソースコードを示し,上記の情報と組み合わせて理解できるものとする.


import time
import requests
import json
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical
from itertools import count

#模型的超参数
learning_rate = 0.0005
gamma         = 0.98
lmbda         = 0.95
eps_clip      = 0.1
K_epoch       = 3

device = torch.device('cpu') # 也可以改为GPU版本

class PPO(nn.Module):
    def __init__(self, state_size, action_size):
        super(PPO, self).__init__()
        self.data = []
        self.fc1   = nn.Linear(state_size,10)
        self.lstm  = nn.LSTM(10,10)
        self.fc_pi = nn.Linear(10,action_size)
        self.fc_v  = nn.Linear(10,1)
        self.optimizer = optim.Adam(self.parameters(), lr=learning_rate)

    def pi(self, x, hidden):
        #输出各个动作的概率,由于是LSTM网络还要包含hidden层的信息,可以参考上一期文章
        x = F.relu(self.fc1(x))
        x = x.view(-1, 1, 10)
        x, lstm_hidden = self.lstm(x, hidden)
        x = self.fc_pi(x)
        prob = F.softmax(x, dim=2)
        return prob, lstm_hidden
    
    def v(self, x, hidden):
        #价值函数,用于评价当前局面的好坏,所以只有一个输出
        x = F.relu(self.fc1(x))
        x = x.view(-1, 1, 10)
        x, lstm_hidden = self.lstm(x, hidden)
        v = self.fc_v(x)
        return v
      
    def put_data(self, transition):
        self.data.append(transition)
        
    def make_batch(self):
        #准备训练数据
        s_lst, a_lst, r_lst, s_prime_lst, prob_a_lst, hidden_lst, done_lst = [], [], [], [], [], [], []
        for transition in self.data:
            s, a, r, s_prime, prob_a, hidden, done = transition
            s_lst.append(s)
            a_lst.append([a])
            r_lst.append([r])
            s_prime_lst.append(s_prime)
            prob_a_lst.append([prob_a])
            hidden_lst.append(hidden)
            done_mask = 0 if done else 1
            done_lst.append([done_mask])
            
        s,a,r,s_prime,done_mask,prob_a = torch.tensor(s_lst, dtype=torch.float), torch.tensor(a_lst), \
                                         torch.tensor(r_lst), torch.tensor(s_prime_lst, dtype=torch.float), \
                                         torch.tensor(done_lst, dtype=torch.float), torch.tensor(prob_a_lst)
        self.data = []
        return s,a,r,s_prime, done_mask, prob_a, hidden_lst[0]
        
    def train_net(self):
        s,a,r,s_prime,done_mask, prob_a, (h1,h2) = self.make_batch()
        first_hidden = (h1.detach(), h2.detach())

        for i in range(K_epoch):
            v_prime = self.v(s_prime, first_hidden).squeeze(1)
            td_target = r + gamma * v_prime * done_mask
            v_s = self.v(s, first_hidden).squeeze(1)
            delta = td_target - v_s
            delta = delta.detach().numpy()
            
            advantage_lst = []
            advantage = 0.0
            for item in delta[::-1]:
                advantage = gamma * lmbda * advantage + item[0]
                advantage_lst.append([advantage])
            advantage_lst.reverse()
            advantage = torch.tensor(advantage_lst, dtype=torch.float)

            pi, _ = self.pi(s, first_hidden)
            pi_a = pi.squeeze(1).gather(1,a)
            ratio = torch.exp(torch.log(pi_a) - torch.log(prob_a))  # a/b == log(exp(a)-exp(b))

            surr1 = ratio * advantage
            surr2 = torch.clamp(ratio, 1-eps_clip, 1+eps_clip) * advantage
            loss = -torch.min(surr1, surr2) + F.smooth_l1_loss(v_s, td_target.detach()) #同时训练了价值网络和决策网络

            self.optimizer.zero_grad()
            loss.mean().backward(retain_graph=True)
            self.optimizer.step()

4.比特币回测环境

ジムの形式を真似,リセット初期化方法があり,ステップ入力アクションが,結果として返ってくる ((次の状態,アクションの利益,終了かどうか,追加の情報).) 全体のリセット環境も60行で,より複雑なバージョン,特定のコードを自律的に修正することができます:

class BitcoinTradingEnv:
    def __init__(self, df, commission=0.00075,  initial_balance=10000, initial_stocks=1, all_data = False, sample_length= 500):
        self.initial_stocks = initial_stocks #初始的比特币数量
        self.initial_balance = initial_balance #初始的资产
        self.current_time = 0 #回测的时间位置
        self.commission = commission #易手续费
        self.done = False #回测是否结束
        self.df = df
        self.norm_df = 100*(self.df/self.df.shift(1)-1).fillna(0) #标准化方法,简单的收益率标准化
        self.mode = all_data # 是否为抽样回测模式
        self.sample_length = 500 # 抽样长度
        
    def reset(self):
        self.balance = self.initial_balance
        self.stocks = self.initial_stocks
        self.last_profit = 0
        
        if self.mode:
            self.start = 0
            self.end = self.df.shape[0]-1
        else:
            self.start = np.random.randint(0,self.df.shape[0]-self.sample_length)
            self.end = self.start + self.sample_length
            
        self.initial_value = self.initial_balance + self.initial_stocks*self.df.iloc[self.start,4]
        self.stocks_value = self.initial_stocks*self.df.iloc[self.start,4]
        self.stocks_pct = self.stocks_value/self.initial_value
        self.value = self.initial_value
        
        self.current_time = self.start
        return np.concatenate([self.norm_df[['o','h','l','c','v']].iloc[self.start].values , [self.balance/10000, self.stocks/1]])
    
    def step(self, action):
        #action即策略采取的动作,这里将更新账户和计算reward
        done = False
        if action == 0: #持有
            pass
        elif action == 1: #买入
            buy_value = self.balance*0.5
            if buy_value > 1: #余钱不足,不操作账户
                self.balance -= buy_value
                self.stocks += (1-self.commission)*buy_value/self.df.iloc[self.current_time,4]
        elif action == 2: #卖出
            sell_amount = self.stocks*0.5
            if sell_amount > 0.0001:
                self.stocks -= sell_amount
                self.balance += (1-self.commission)*sell_amount*self.df.iloc[self.current_time,4]
                
        self.current_time += 1
        if self.current_time == self.end:
            done = True
        self.value = self.balance + self.stocks*self.df.iloc[self.current_time,4]
        self.stocks_value = self.stocks*self.df.iloc[self.current_time,4]
        self.stocks_pct = self.stocks_value/self.value
        if self.value < 0.1*self.initial_value:
            done = True
            
        profit = self.value - (self.initial_balance+self.initial_stocks*self.df.iloc[self.current_time,4])
        reward = profit - self.last_profit # 每回合的reward是新增收益
        self.last_profit = profit
        next_state = np.concatenate([self.norm_df[['o','h','l','c','v']].iloc[self.current_time].values , [self.balance/10000, self.stocks/1]])
        return (next_state, reward, done, profit)

5.几个值得注意的细节

初期口座にはなぜコインがあるのか?

復習環境の計算収益の公式は:現在の収益 = 現在の口座価値 - 最初の口座の現在の価値である. つまり,ビットコインの価格が下がった場合,戦略がコインを売却する操作を行った場合,総口座価値が減少したとしても,実際には戦略と報酬を与えられるべきである. 復習時間が長い場合,初期口座はほとんど影響しないかもしれませんが,最初は大きな影響を与える可能性があります. 相対的な収益を計算すると,正しい操作ごとにポジティブな報酬を得ることが保証されます.

なぜ訓練中にサンプリングするのか?

総データ量は1万以上のK線であり,全量で一回を回転させる場合,時間が長くなり,戦略が直面する状況が毎回同じである場合,過適化が容易になる可能性がある. 500個のデータを一回抽出すると,過適化の可能性はあるが,戦略は1万以上の異なる可能性の開始に直面する.

銀貨やお金がないとどうする?

再評価環境では,この状況が考慮されていない.もしコインが売り切れているか,最小取引量に達していない場合,売り切る操作を実行することは,実際には操作をしないことと同等である.価格が下がった場合,相対的利益の計算方法に基づいて,戦略は依然としてポジティブな報酬に基づいている.この状況の影響は,戦略が市場を判断すると,市場が落ちて,口座のコインの残高が売れない場合,売り切る動作と操作しない動作を区別することができないが,戦略そのものの市場状況判断には影響しない.

なぜアカウント情報を元に戻す必要があるのか?

PPOモデルには,現在の状態の価値を評価するための価値ネットワークがある.明らかに,戦略が価格を上昇させると判断した場合,現在のアカウントがビットコインを保持しているときにのみ,全体的な状態がポジティブな価値を持つこと,そしてその逆である.したがって,アカウント情報は価値ネットワークの判断の重要な基盤である. 過去の動作情報を状態として戻さないことに注意し,個人は価値判断に役に立たないと考えます.

機能しない状態に戻る場合は?

策略判断で買い買いで得られる利益が手続費をカバーできない場合,不動に戻るべきである.前述の記述は繰り返し策略判断で価格傾向を判断しているが,理解の便宜のために,実際にはこのPPOモデルは市場を予測していないが,わずか3つの動きの確率を出力している.

6.数据的获取和训练

前回の記事と同様に,データ取得の方法と形式は以下の通りです. Bitfinexの取引所BTC_USDは,2018/5/7から2019/6/27までの1時間周期K線を取引しています.

resp = requests.get('https://www.quantinfo.com/API/m/chart/history?symbol=BTC_USD_BITFINEX&resolution=60&from=1525622626&to=1561607596')
data = resp.json()
df = pd.DataFrame(data,columns = ['t','o','h','l','c','v'])
df.index = df['t']
df = df.dropna()
df = df.astype(np.float32)

LSTM ネットワークを使用したため,トレーニング時間が長くなり,GPU バージョンを3倍ほど速く変更しました.

env = BitcoinTradingEnv(df)
model = PPO()

total_profit = 0 #记录总收益
profit_list = [] #记录每次训练收益
for n_epi in range(10000):
    hidden = (torch.zeros([1, 1, 32], dtype=torch.float).to(device), torch.zeros([1, 1, 32], dtype=torch.float).to(device))
    s = env.reset()
    done = False
    buy_action = 0
    sell_action = 0
    while not done:
        h_input = hidden
        prob, hidden = model.pi(torch.from_numpy(s).float().to(device), h_input)
        prob = prob.view(-1)
        m = Categorical(prob)
        a = m.sample().item()
        if a==1:
            buy_action += 1
        if a==2:
            sell_action += 1
        s_prime, r, done, profit = env.step(a)

        model.put_data((s, a, r/10.0, s_prime, prob[a].item(), h_input, done))
        s = s_prime

    model.train_net()
    profit_list.append(profit)
    total_profit += profit
    if n_epi%10==0:
        print("# of episode :{:<5}, profit : {:<8.1f}, buy :{:<3}, sell :{:<3}, total profit: {:<20.1f}".format(n_epi, profit, buy_action, sell_action, total_profit))

7.训练结果和分析

長い間待っていた後:img

まず,トレーニングデータを見てみましょう. 概して,前半は長い落ち込みで,後半は強い反転です.img

トレーニング前半期の買い買い操作は多く,基本的には利益の回路はありません. トレーニング中期間の買い買い操作は徐々に減少し,利益の確率もますます高くなっていますが,損失の確率も大きいです.img

ロープごとに収益を平ら化する結果,以下のような結果になります.img

戦略は,初期収益が負の状況からすぐに抜け出したが,波動は大きく,収益は1万回後に急激に成長するまでは,一般的にモデル訓練は困難である.

最終的なトレーニングが終わると,モデルがすべてのデータを一度実行して,その間に口座の総市場価値,持っていたビットコインの数,ビットコイン価値比率,総収益を記録します. 総市場価値は,総利益やそれと似たものを表さない.img
総市場価値は,前期の熊市に緩やかに上昇し,後期の牛市に上昇したが,段階的な損失が発生した.

最後に,保有比率を見てください. 図の左軸は保有比率であり,右軸は市場です. モデルが過剰に適合していることを初期判断することができます. 前期熊市では保有頻度が低く,市場の底部では保有頻度が高くなります. また,モデルが長期的に保有することを学んでいないことも見ることができます.img

8.测试数据分析

2019年6月27日までのテストデータを取得した時点で,ビットコインの1時間市場. 図では,価格が13,000ドルから今日9000ドル以上に下がったことが示されています. これはモデルにとって大きなテストです.img

投資家は,まず,相対的な利益で,負の業績を示したが,損はしなかった.img

また,保有状況を見てみると,モデルは急落後買い買いなどの反転売りを傾向していると推測することができます. 最近,ビットコイン市場は波動がほとんどなく,モデルは空売り状態にあります.img

9.总结

この記事では,深層強化学習方法PPOによるビットコイン自動取引ロボットの訓練によって,いくつかの結論が得られる. 時間が限られているため,モデルにはいくつかの改善点があります. 大事な教訓は,データ標準化が正しい方法であり,スケーリングなどの方法を使用しないこと. そうでなければ,モデルは価格と市場の関係をすぐに記憶し,適合に陥ります. 変動率標準化が相対的なデータなので,モデルが変動率と変動率の関係を覚えることが難しくなり,変動率と暴跌率の関係を探すことになります.

この記事へのトラックバック一覧です. FMZの発明者は,数値化プラットフォームで公開した戦略をいくつか共有しています.https://zhuanlan.zhihu.com/p/64961672デジタル通貨の定量化取引のコースは,20ドルで:https://study.163.com/course/courseMain.htm?courseId=1006074239&share=2&shareId=400000000602076メディアの報道によると,この2つの戦略は,インターネットの利用者にとって非常に有益なものでした.https://www.fmz.com/bbs-topic/1211


関連性

もっと

リサ20231テスト結果の画像を 逆にしてみませんか? なぜドルが上がると利益が下がるのでしょう?

ジャックマprofit = self.value - (self.initial_balance+self.initial_stocks * self.df.iloc[self.current_time,4]) バグがある 利益 = self.value - (self.initial_balance+self.initial_stocks * self.df.iloc[self.start,4]) となるべきでした

ジャックマprofit = self.value - (self.initial_balance+self.initial_stocks*self.df.iloc[self.current_time,4]) バグがある 利益 = self.value - (self.initial_balance+self.initial_stocks*self.df.iloc[self.start,4]) となるべきでした

ティモシェンコ出版社では,

xw2021 年暴力を振るう!

エディ草神牛群!

小草GPU版 `` device = torch.device (('cuda' if torch.cuda.is_available (() else 'cpu') について) class PPO ((nn.Module)): 定義 __init__ ((self): super ((PPO,self).__init__ ((() について セルフ.データ = [] ありがとうございました. self.fc1 = nn.Linear ((8,64) について self.lstm = nn.LSTM ((64,32) について self.fc_pi = nn.Linear ((32,3) について self.fc_v = nn.Linear ((32,1) について self.optimizer = optim.Adam ((self.parameters ((), lr=learning_rate) は,学習率について説明しています. def pi ((self,x,hidden)): 隠している x = F.relu ((self.fc1 ((x)) x = x.view ((−1, 1, 64) について x,lstm_hidden = self.lstm ((x,hidden)) は,x,lstm_hidden を表示する. x = self.fc_pi (x) prob = F.softmax ((x, dim=2) について return prob, lstm_hidden 返信プロブ, lstm_hidden 返信プロブ, lstm_hidden 返信プロブ, lstm_hidden ありがとうございました. def v ((self,x,hidden)): 隠している x = F.relu ((self.fc1 ((x)) x = x.view ((−1, 1, 64) について x,lstm_hidden = self.lstm (x,hidden) について fc_v (x) は,fc_v (x) を表します. return v について ありがとうございました. def put_data ((self, transition)): このページは, self.data.append (トランジション) ありがとうございました. def make_batch ((self)): 設定する s_lst, a_lst, r_lst, s_prime_lst, prob_a_lst, hidden_lst, done_lst = [], [], [], [], [], [], [] for transition in self.data: このページのページのページのページのページのページのページのページのページのページ s, a, r, s_prime, prob_a, hidden, done = トランジション ありがとうございました. s_lst.append (s) について a_lst.append (([a]) について r_lst.append (([r]) について s_prime_lst.append (s_prime) について prob_a_lst.append (([prob_a]) について hidden_lst.append (隠された) done_mask = 0 if done else 1 で,このマスクを削除する場合は, done_lst.append (([done_mask]) をクリックしてください ありがとうございました. s,a,r,s_prime,done_mask,prob_a = torch.tensor ((s_lst,dtype=torch.float).to ((device),torch.tensor ((a_lst).to ((device).to ((device), \ トルチ・テンサー (a_lst).to ((device).to ((device) について torch.tensor ((r_lst).to ((device), torch.tensor ((s_prime_lst, dtype=torch.float).to ((device), \ トルチ・テンサー (s_prime_lst, dtype=torch.float).to (r_lst), トルチ・テンサー (s_prime_lst), トルチ・テンサー (s_prime_lst), トルチ・テンサー (s_prime_lst), トルチ・テンサー (s_prime_lst), トルチ・テンサー (s_prime_lst), トルチ・テンサー (s_prime_lst), トルチ・テンサー (s_prime_lst), トルチ・テンサー (t_type=torch.float).to (to)) torch.tensor ((done_lst, dtype=torch.float).to ((device), torch.tensor ((prob_a_lst).to ((device) について) セルフ.データ = [] return s,a,r,s_prime, done_mask, prob_a, hidden_lst[0] 試し読みした結果 ありがとうございました. カーストの位置を表示する s,a,r,s_prime,done_mask,prob_a, (h1,h2) = self.make_batch ()) first_hidden = (h1.to(device).detach(), h2.to(device).detach()) for i in range (K_epoch) は,次のように表示されます. v_prime = self.v ((s_prime, first_hidden).squeeze ((1)) について td_target = r + ガンマ * v_prime * done_mask v_s = self.v(s, first_hidden).squeeze ((1) について デルタ = td_target - v_s デルタ = デルタ.CPU.Detach.Numpy. ポイントは, 優位性 = 0.0 for item in delta [:: - 1]: は,次のとおりです advantage = gamma * lmbda * advantage + item[0] ポイントは,そのポイントが,そのポイントが, advantage_lst.append (アドバンテージ) advantage_lst.reverse (アデテインテージ_lst.リバース) advantage = torch.tensor ((advantage_lst, dtype=torch.float).to ((デバイス) について) pi, _ = self.pi (s, first_hidden) pi_a = pi.squeeze ((1).gather ((1,a) について ratio = torch.exp ((torch.log ((pi_a) - torch.log ((prob_a)) # a/b == log ((exp ((a) -exp ((b)) この式は,この式を surr2 = torch.clamp ((ratio, 1-eps_clip, 1+eps_clip) * advantage トーチを固定する loss = -torch.min ((surr1, surr2) + F.smooth_l1_loss ((v_s, td_target.detach()) セルフ.オプティマイザー.ゼロ_グラード このグラフは,このグラフの右辺に位置しています. セルフ.オプティマイザー.ステップ ``