Rede Neural e transações quantitativas de moedas digitais série ((2) Curso de aprendizado intensivo em profundidade para treinar estratégias de negociação de Bitcoin

Autora:Ervas daninhas, Criado: 2019-07-31 11:13:15, Atualizado: 2023-10-20 20:10:18

img

1.介绍

Artigo anterior Usando a rede LSTM para prever o preço do Bitcoinhttps://www.fmz.com/digest-topic/4035Como mencionado no artigo, é apenas um pequeno projeto para praticantes para familiarizar-se com RNN e pytorch. Este artigo irá apresentar métodos para usar o aprendizado reforçado para treinar estratégias de negociação diretamente. O modelo de aprendizado reforçado é um PPO de código aberto OpenAI, e o ambiente refere-se ao estilo do ginásio. Para facilitar a compreensão e teste, o modelo de PPO do LSTM e o ambiente do ginásio de retrospecção são diretamente escritos em pacotes que não usam o pronto-a-ser. O PPO, conhecido como Proximal Policy Optimization, é uma melhoria de otimização para o Policy Graident, ou seja, a gradiente de estratégia. Gym também lançado pela OpenAI, pode interagir com a rede de estratégias, feedback sobre o estado e os recompensas do ambiente atual, como o exercício de aprendizagem reforçada, usando o modelo PPO do LSTM para fazer instruções de compra, venda ou não operação diretamente com base nas informações do mercado do Bitcoin. A leitura deste artigo requer uma certa base de aprendizagem intensiva em Python, pytorch e DRL. Mas não importa, o código fornecido neste artigo é fácil de aprender.www.fmz.comO blogueiro também escreveu sobre o assunto:

2.数据和学习参考资料

Os dados do preço do Bitcoin são provenientes da plataforma de negociação quantitativa FMZ inventor:https://www.quantinfo.com/Tools/View/4.htmlUm artigo que usa o DRL+gym para treinar estratégias de negociação:https://towardsdatascience.com/visualizing-stock-trading-agents-using-matplotlib-and-gym-584c992bc6d4O blogueiro também escreveu sobre o tema:https://github.com/yunjey/pytorch-tutorialEste artigo irá usar diretamente uma breve implementação do modelo LSTM-PPO:https://github.com/seungeunrho/minimalRL/blob/master/ppo-lstm.pyO artigo sobre o PPO:https://zhuanlan.zhihu.com/p/38185553Para mais informações sobre o DRL:https://www.zhihu.com/people/flood-sung/postsO artigo não precisa de instalação, mas o aprendizado de reforço é muito comum:https://gym.openai.com/

3.LSTM-PPO

A informação sobre o PPO pode ser estudada em profundidade em referências anteriores, aqui é apenas uma simples introdução do conceito. No último período, a rede LSTM apenas previu um preço, e como, com base nesse preço de previsão, a transação de compra e venda também deve ser realizada de outra forma, é natural pensar que a saída direta do movimento de compra e venda não é mais direta?

O código de origem do LSTM-PPO é apresentado abaixo e pode ser compreendido em combinação com o anterior:


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.比特币回测环境

Em mimetizando o formato do ginásio, há um método de inicialização de reinicialização, passo de entrada de ação, o resultado de retorno é ((próximo estado, ganho de ação, se termina, informações adicionais), o ambiente de retorno inteiro também em 60 linhas, pode ser modificado automaticamente para uma versão mais complexa, o código específico:

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.几个值得注意的细节

Por que as contas iniciais possuem moeda?

A fórmula para calcular o retorno do ambiente de retorno é: retorno atual = valor da conta atual - valor atual da conta inicial. Isso significa que, se o preço do Bitcoin cair e a estratégia fizer uma operação de venda de moeda, mesmo que o valor da conta total diminua, a estratégia também deve ser recompensada. Se o tempo de retorno for longo, a conta inicial pode não ter um grande impacto, mas ainda assim terá um grande impacto no início.

Por que é que as pessoas usam o teste durante o treino?

O total de dados é de mais de dez mil K-strings, o que pode ser mais fácil se o tempo necessário é longo e a estratégia é idêntica em cada situação enfrentada. Extrair 500 bits de dados de cada vez como uma vez de repetição, embora ainda seja possível ultrapassar, a estratégia enfrenta mais de dez mil possibilidades de início.

O que fazer sem moeda ou sem dinheiro?

No contexto da retrospecção, não se considera esta situação, se o dinheiro já foi vendido ou não alcançou o volume mínimo de negociação, a execução de uma operação de venda é, na verdade, equivalente à execução de uma operação de não operação, se o preço cair, de acordo com o cálculo do rendimento relativo, a estratégia ainda é baseada na recompensa positiva. O efeito desta situação é que a estratégia julga que o mercado está caindo e o saldo da conta não pode ser vendido.

Por que é que as informações das contas têm de ser devolvidas ao estado?

O modelo PPO possui uma rede de valor usada para avaliar o valor do estado atual. Obviamente, se a estratégia decidir que o preço vai subir, o estado inteiro terá um valor positivo somente quando a conta atual tiver Bitcoin, e vice-versa. Assim, a informação da conta é uma base importante para o julgamento da rede de valor.

Em que circunstâncias é que o sistema volta a não funcionar?

Quando os ganhos da decisão estratégica de compra e venda não podem cobrir os custos de operação, deve-se voltar a não operar. Embora a descrição anterior tenha usado repetidamente a estratégia para determinar a tendência dos preços, apenas para facilitar a compreensão, o modelo PPO não faz previsões sobre o mercado, mas apenas produz probabilidades de três movimentos.

6.数据的获取和训练

Como no artigo anterior, a forma e o formato de obtenção dos dados são os seguintes:

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)

Como o tempo de treinamento era muito longo usando a rede LSTM, mudei para uma versão da GPU, que era cerca de três vezes mais rápida.

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.训练结果和分析

Depois de uma longa espera:img

Em primeiro lugar, olhe para o mercado de dados de treinamento, em geral, o primeiro semestre é um longo declínio e o segundo semestre é um forte rebote.img

A prática de compras antes do treinamento é muito, e basicamente não há uma rodada lucrativa. Até o treinamento, as operações de compra no meio do treinamento diminuem gradualmente, a probabilidade de lucro também aumenta, mas há uma grande probabilidade de perda.img

O resultado é o seguinte:img

A estratégia rapidamente se libertou da situação de ganhos negativos anteriores, mas oscilações foram grandes, e os ganhos só cresceram rapidamente após 10.000 rodadas, o que geralmente é difícil de treinar.

Após o treinamento final, o modelo executa todos os dados de uma vez para ver como eles funcionam, registrando o valor total da conta, o número de bitcoins mantidos, a proporção de bitcoin valor, o lucro total. A primeira é o valor total do mercado, o lucro total e o semelhante não são incluídos:img
O valor total do mercado aumentou lentamente durante o início do mercado de baixa e também aumentou durante o final do mercado de alta, mas houve ainda prejuízos graduais.

Finalmente, veja a proporção de posicionamento, o eixo esquerdo do gráfico é a proporção de posicionamento, o eixo direito é o mercado. Pode-se julgar preliminarmente que o modelo surgiu de forma excessiva, com baixa frequência de posicionamento no início do mercado de ursos e alta frequência de posicionamento no fundo do mercado.img

8.测试数据分析

O gráfico mostra que o preço caiu de US $ 13.000 no início para US $ 9.000 hoje, o que é um grande teste para o modelo.img

Em primeiro lugar, os ganhos relativos no final, com um desempenho ruim, mas sem prejuízo.img

Olhando novamente para a situação de posicionamento, pode-se supor que o modelo tende a comprar ou vender depois de uma queda acentuada.img

9.总结

O artigo também traz algumas conclusões sobre o treinamento de um robô de negociação automática de Bitcoin por meio do método de aprendizagem reforçada em profundidade (PPO). Como o tempo é limitado, o modelo ainda tem alguns pontos a serem aprimorados, bem-vindos para discussão. A maior lição é que a padronização de dados é uma maneira de fazer, não usar métodos como o escalonamento, caso contrário, o modelo rapidamente se lembra das relações entre preços e mercados, ficando preso ao ajustamento.

O artigo anterior é sobre: Os inventores da FMZ compartilham algumas estratégias públicas na plataforma de quantificação:https://zhuanlan.zhihu.com/p/64961672O curso de negociação quantitativa de moedas digitais da NetEase Cloud Classroom custa apenas 20 dólares:https://study.163.com/course/courseMain.htm?courseId=1006074239&share=2&shareId=400000000602076A estratégia de alta frequência que publiquei e que costumava ser lucrativa:https://www.fmz.com/bbs-topic/1211


Relacionados

Mais.

Lisa20231Por que você quer virar a imagem dos resultados? Por que é que os rendimentos sempre diminuem quando o dólar sobe?

Jackma(self.initial_balance + self.initial_stocks * self.df.iloc[self.current_time,4]) Há um bug Deve ser: profit = self.value - (self.initial_balance + self.initial_stocks * self.df.iloc[self.start,4])

Jackma(self.initial_balance+self.initial_stocks*self.df.iloc[self.current_time,4]) Há um bug Deve ser: profit = self.value - (self.initial_balance+self.initial_stocks*self.df.iloc[self.start,4])

TimoshenkoA primeira edição é muito mais forte do que a primeira.

xw2021Que arrogância!

Eddie, não.Boias de Deus da Erva!

Ervas daninhasEdição GPU Não sei. device = torch.device (('cuda' if torch.cuda.is_available)) else 'cpu') class PPO ((nn.Module): def __init__(self): super ((PPO, self).__init__() self.data = [] O que é isso? 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))) e 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) Proba de retorno, lstm_hidden O que é isso? 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) retorno v O que é isso? def put_data ((self, transition): self.data.append (transição) O que é isso? def make_batch ((self): s_lst, a_lst, r_lst, s_prime_lst, prob_a_lst, hidden_lst, done_lst = [], [], [], [], [], [], [] Para transição em self.data: s, a, r, s_prime, prob_a, hidden, done = transição O que é isso? 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 (assombrado) done_mask = 0 se feito else 1 done_lst.append (([done_mask]) O que é isso? 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 ((device), torch.tensor ((prob_a_lst).to ((device) self.data = [] return s, a, r, s_prime, done_mask, prob_a, hidden_lst[0] O que é isso? 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 + gama * v_prime * done_mask v_s = self.v ((s, first_hidden).squeeze ((1) delta = td_target - v_s delta = delta.cpu (().detach (().numpy (()) Benefício_lst = [] vantagem = 0.0 para item em delta [::-1]: advantage = gamma * lmbda * advantage + item[0] advantage_lst.append (([advantage]) advantage_lst.reverse (em inglês) advantage = torch.tensor ((advantage_lst, dtype=torch.float).to ((device)) 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 = razão * vantagem 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 ((() Não sei.