신경망과 디지털 화폐 양적 거래 시리즈 ((2) 심도 강화 학습 Bitcoin 거래 전략을 훈련

저자:초목, 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, 즉 전략梯度의 최적화 개선이다. OpenAI에서 출시된 또한 운동은 전략 네트워크와 상호 작용하여 현재 환경의 상태와 보상을 피드백 할 수 있으며, 강화 학습 연습과 같이 LSTM의 PPO 모델을 사용하여 비트코인의 시장 정보에 따라 직접 구매, 판매 또는 작동하지 않는 명령을 내리고, 리테스트 환경으로부터 피드백을 제공하여 지속적으로 최적화하는 훈련을 통해 전략 수익을 달성합니다. 이 문서를 읽는 데는 Python,pytorch,DRL의 딥 강화 학습 기초가 필요합니다. 그러나 상관없습니다. 이 문서에서 제공 된 코드와 결합하여 쉽게 배울 수 있습니다. 이 문서는 FMZ의 발명가 디지털 통화 양적 거래 플랫폼을 제공합니다.www.fmz.com), QQ 그룹:863946592에 오신 것을 환영합니다.

2.数据和学习参考资料

비트코인 가격 데이터는 FMZ의 발명가인 양적 거래 플랫폼에서 유래했습니다.https://www.quantinfo.com/Tools/View/4.htmlDRL+gym을 사용하여 거래 전략을 훈련하는 기사:https://towardsdatascience.com/visualizing-stock-trading-agents-using-matplotlib-and-gym-584c992bc6d4토르치에 들어가는 몇 가지 예:https://github.com/yunjey/pytorch-tutorial이 문서에서는 이 LSTM-PPO 모델의 간단한 구현을 직접 사용하겠습니다.https://github.com/seungeunrho/minimalRL/blob/master/ppo-lstm.pyPPO에 대한 기사: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-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.比特币回测环境

짐의 형식을 모방하여, reset 초기화 방법이 있으며, step 입력 동작, 반환 결과는 ((다음 상태, 동작 이득, 종료 여부, 추가 정보) 이다.) 전체 재검토 환경도 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개씩을 한 번 다시 검색하는 데이터로 추출하는 경우에도 오버마칭이 가능하지만 전략은 10만 개 이상의 다른 가능한 시작에 직면한다.

이 모든 것이 어떻게 이루어질까요?

재검토 환경에서는 이러한 상황을 고려하지 않고, 만약 코인이 이미 팔렸거나 최소 거래량에 도달하지 못한다면, 이 때 팔기 동작을 실행하는 것은 실제로 동작하지 않는 것과 같으며, 만약 가격이 떨어지면, 상대적 이익의 계산 방식에 따라, 전략은 여전히 긍정적 보상에 기초한다. 이 상황의 영향은 전략이 시장을 판단하는 데에 있다. 시장이 떨어지고 계정 코인 잔액이 팔 수 없을 때, 판매하는 동작과 동작하지 않는 동작을 구별할 수 없지만, 전략 자체에 대한 시장을 판단하는 데에는 영향을 미치지 않는다.

왜 계정 정보를 상태로 되돌려야 할까요?

PPO 모델은 현재 상태의 가치를 평가하는 데 사용되는 가치 네트워크가 있으며, 분명히 전략이 가격이 상승할 것이라고 판단하면 현재 계정이 비트코인을 보유했을 때만 전체 상태가 긍정적 인 가치를 가지게 되며, 반대로도 마찬가지입니다. 따라서 계정 정보는 가치 네트워크 판단의 중요한 기초입니다. 과거 동작 정보를 상태로 반환하지 않았다는 점에 유의하십시오. 개인은 이것이 가치 판단에 유용하지 않다고 생각합니다.

어떤 상황에서 다시 작동하지 않을까요?

전략 판단 매매 수익이 절차 비용을 충당할 수 없을 때, 비작업으로 돌아가야 한다. 이전 설명에서 전략 판단 가격 추세를 반복적으로 사용했음에도 불구하고, 이해하기 쉽기 때문에, 실제로 이 PPO 모델은 시장을 예측하지 않고, 단지 세 가지 동작의 확률을 내보낸다.

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/06/27까지 비트코인 1시간 시장. 도표에서 가격이 시작에서 13000 달러에서 오늘 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저는 제가 공개한 한 번 큰 수익을 올린 하이프레크 전략 중 하나를 소개합니다.https://www.fmz.com/bbs-topic/1211


관련

더 많은

리사20231왜 테스트 결과물을 뒤집어 놓으십니까? 왜 달러가 상승할 때 항상 수익이 줄어들까요?

잭마profit = self.value - (self.initial_balance+self.initial_stocks * self.df.iloc[self.current_time,4]) 버그가 있습니다 이쪽은: profit = 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]) 버그가 있습니다 이 글은:profit = 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): def __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 = 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, 64) x, lstm_hidden = self.lstm (x, hidden) v = self.fc_v ((x) 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 = [], [], [], [], [], [] 자율.데이터에서 전환을 위해: 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 (도네_마스크) 이 모든 것은 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), \ torch.tensor ((r_lst).to ((device), torch.tensor ((s_prime_lst, dtype=torch.float).to ((device), \ torch.tensor ((done_lst, dtype=torch.float).to ((장치), torch.tensor ((prob_a_lst).to ((장치) 자, 자, 자, 자. 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.to(device).detach(), h2.to(device.detach()) for i in range ((K_epoch)): v_prime = self.v ((s_prime, first_hidden).squeeze))) td_target = r + gamma * v_prime * done_mask v_s = self.v ((s, first_hidden).squeeze ((1)) delta = td_target - v_s 델타 = 델타.CPU ().detach ().numpy ()) 이점_lst = [] 이점 = 0.0 for item in delta [::-1]: advantage = gamma * lmbda * advantage + item[0] 이점_lst.append (이점) 이점_lst.reverse 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)) surr1 = 비율 * 이점 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)) 자.최고화.제로_그라드 loss.mean (().backward ((retain_graph=True) 이 있습니다. 자율성향자.단계 (() ``