En el artículo anterior, se utilizó la red LSTM para predecir el precio de Bitcoin.https://www.fmz.com/digest-topic/4035, como se menciona en el artículo, es solo un pequeño proyecto para practicantes, para familiarizarse con RNN y pytorch. Este artículo se enfoca en el uso de aprendizaje reforzado para entrenar estrategias comerciales directamente. El modelo de aprendizaje reforzado es un PPO de código abierto de OpenAI, y el entorno hace referencia al estilo del gimnasio. Para facilitar la comprensión y la prueba, el modelo de PPO de LSTM y el entorno de gimnasio de retrospección escriben directamente paquetes sin usar. El PPO, conocido como Proximal Policy Optimization, es una mejora de optimización del Policy Graident, es decir, la gradiente estratégica. Gym también publicado por OpenAI, puede interactuar con la red de estrategias, dar retroalimentación sobre el estado y las recompensas del entorno actual, al igual que el ejercicio de aprendizaje reforzado que utiliza el modelo PPO de LSTM para hacer órdenes de compra, venta o no operación directamente en función de la información del mercado de Bitcoin. La lectura de este artículo requiere una cierta base de aprendizaje intensivo en Python, pytorch, DRL. Pero no importa, junto con el código que se da en este artículo, es fácil de aprender.www.fmz.comEn la página de Facebook de la empresa, se puede leer:
Los datos del precio de Bitcoin provienen de la plataforma de intercambio cuantificado de inventores de FMZ:https://www.quantinfo.com/Tools/View/4.htmlUn artículo que usa DRL+gym para entrenar estrategias de trading:https://towardsdatascience.com/visualizing-stock-trading-agents-using-matplotlib-and-gym-584c992bc6d4Algunos ejemplos de pytorch:https://github.com/yunjey/pytorch-tutorialEste artículo utilizará directamente una breve implementación del modelo LSTM-PPO:https://github.com/seungeunrho/minimalRL/blob/master/ppo-lstm.pyEl artículo sobre el PPO:https://zhuanlan.zhihu.com/p/38185553Para más información sobre DRL:https://www.zhihu.com/people/flood-sung/postsEn cuanto a los gimnasios, este artículo no requiere instalación, pero el aprendizaje reforzado es muy común:https://gym.openai.com/
En cuanto a la explicación en profundidad de la PPO, se puede aprender la información de referencia anterior, aquí es sólo una introducción de la idea simple. La red LSTM del último período sólo predijo un precio, ¿cómo se puede realizar una transacción de compra y venta de acuerdo con este precio de predicción, naturalmente, se puede pensar, la salida directa de la acción de compra y venta no es más directa?
A continuación se muestra el código fuente de LSTM-PPO, que se puede entender con la información 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()
Imitando el formato de gimnasia, hay un método de inicialización de reinicio, paso de entrada de la acción, el resultado de retorno es ((el siguiente estado, la acción de ganancia, si termina, información adicional), el entorno de retorno entero también en 60 líneas, que se puede modificar automáticamente a una versión más compleja, el 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)
¿Por qué las cuentas iniciales tienen monedas?
La fórmula para calcular el rendimiento del entorno de repetición es: rendimiento actual = valor de la cuenta actual - valor actual de la cuenta inicial. Esto significa que si el precio de Bitcoin cae y la estrategia realiza una operación de venta de monedas, incluso si el valor de la cuenta total disminuye, en realidad se debe recompensar con la estrategia. Si el tiempo de repetición es largo, la cuenta inicial puede tener un impacto pequeño, pero un gran impacto al principio. El cálculo del rendimiento relativo garantiza una recompensa positiva por cada operación correcta.
¿Por qué se hace la simulación durante el entrenamiento?
El volumen total de datos es de más de diez mil K-rays, y si se ejecuta un ciclo de todo el volumen cada vez, se necesita mucho tiempo y la estrategia es la misma en cada situación que se enfrenta, puede ser más fácil de superajustar. Extraer 500 bits cada vez como datos de repetición, aunque aún es posible superajustar, la estrategia se enfrenta a más de diez mil posibles inicios.
¿Qué hacer sin monedas o sin dinero?
No se tiene en cuenta esta situación en el entorno de retrospección, si el dinero ya se ha vendido o no alcanza el volumen mínimo de operaciones, entonces la ejecución de la operación de venta es en realidad equivalente a la no ejecución de la operación, si el precio cae, según el cálculo de la ganancia relativa, la estrategia sigue basándose en la recompensa positiva. El efecto de esta situación es que la estrategia juzga que el mercado está bajando y el saldo de la cuenta no puede venderse.
¿Por qué devolver la información de la cuenta a su estado?
El modelo de PPO tiene una red de valor para evaluar el valor del estado actual, y obviamente, si la estrategia determina que el precio va a subir, el estado entero tiene un valor positivo solo cuando la cuenta actual tiene Bitcoin, y viceversa. Por lo tanto, la información de la cuenta es una base importante para los juicios de la red de valor.
¿Cuándo regresará a no funcionar?
Cuando la estrategia determina que los beneficios de la compra y venta no pueden cubrir los gastos de tramitación, se debe regresar a la operación. Aunque la descripción anterior utiliza repetidamente la estrategia para determinar la tendencia de los precios, solo para facilitar la comprensión, en realidad el modelo PPO no hace predicciones sobre el mercado, sino que solo produce probabilidades de tres movimientos.
Como en el artículo anterior, la forma y el formato en que se obtienen los datos es el siguiente:
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 el tiempo de entrenamiento era largo con la red LSTM, cambié la versión de la GPU, que fue aproximadamente 3 veces más 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))
Después de una larga espera:
En general, la primera mitad fue una larga caída, mientras que la segunda mitad fue una fuerte recuperación.
Las operaciones de compra en el período previo al entrenamiento son muchas y no hay una ronda de ganancias básicamente. A mediados de entrenamiento, las operaciones de compra disminuyen gradualmente, la probabilidad de ganancias también es cada vez mayor, pero hay una gran probabilidad de pérdida.
Si se suavizan las ganancias por ronda, el resultado es el siguiente:
La estrategia se liberó rápidamente de la situación de rendimiento negativo inicial, pero los vaivenes fueron grandes, y las ganancias no crecieron rápidamente hasta después de 10.000 rondas, en general, el entrenamiento del modelo fue difícil.
Al final de la capacitación, el modelo ejecuta todos los datos una vez más para ver cómo se desempeñan, registrando el valor total de la cuenta, el número de bitcoins en poder, el porcentaje de valor de bitcoins, los beneficios totales durante el período.
En primer lugar, el valor de mercado total, el ingreso total y similares, sin incluir:
El valor de mercado total aumentó lentamente durante el primer mercado bajista, y también aumentó durante el último mercado alcista, pero aún así hubo pérdidas graduales.
Finalmente, vea la proporción de tenencias, el eje izquierdo del gráfico es la proporción de tenencias, el eje derecho es el mercado, se puede juzgar preliminarmente que el modelo ha aparecido sobreajustado, con una baja frecuencia de tenencia en el mercado bajista anterior, con una alta frecuencia de tenencia en el mercado inferior. También se puede ver que el modelo no aprende a mantener a largo plazo, siempre se vende rápidamente.
En el gráfico, se puede ver que el precio cayó desde los 13,000 dólares iniciales hasta más de 9,000 dólares actuales, lo que supone una gran prueba para el modelo.
En primer lugar, en última instancia, las ganancias relativas, que funcionan mal, pero sin pérdidas.
De nuevo, si nos fijamos en la situación de los tenedores, se puede suponer que el modelo tiende a comprar o vender después de una caída brusca, ya que el mercado de Bitcoin ha tenido poca volatilidad recientemente y el modelo ha estado en el estado de tenencia vacía.
Este artículo se basa en el método de aprendizaje profundo de PPO, que entrenó a un robot de negociación automática de Bitcoin, y también obtiene algunas conclusiones. Debido a la limitación del tiempo, el modelo todavía tiene algunos lugares para mejorar, bienvenidos a la discusión. La lección más importante es que el método de estandarización de datos es correcto, no adopte métodos como la escalación, o el modelo se acuerda rápidamente de las relaciones de precios y mercados y se queda en el ajuste.
Los artículos anteriores presentan: Algunas de las estrategias que los inventores de FMZ comparten públicamente en su plataforma de cuantificación:https://zhuanlan.zhihu.com/p/64961672El curso de transacción cuantitativa de monedas digitales de NetEase Cloud Classroom cuesta sólo 20 dólares:https://study.163.com/course/courseMain.htm?courseId=1006074239&share=2&shareId=400000000602076Una estrategia de alta frecuencia que he revelado y que ha sido muy lucrativa:https://www.fmz.com/bbs-topic/1211
- ¿Qué quieres decir?¿Por qué quiere poner la imagen de los resultados de la prueba? ¿Por qué tus ganancias siempre disminuyen cuando el dólar sube?
- ¿ Qué haces?Profit = self.value - (self.initial_balance + self.initial_stocks * self.df.iloc[self.current_time,4]) Hay un error en el resto de las acciones Se debe decir: profit = self.value - (self.initial_balance + self.initial_stocks * self.df.iloc [self.start,4])
- ¿ Qué haces?Profit = self.value - (self.initial_balance+self.initial_stocks*self.df.iloc[self.current_time,4]) Hay un error en el resto de las acciones Se debe decir: profit = self.value - (self.initial_balance + self.initial_stocks*self.df.iloc[self.start,4])
- ¿ Qué es?Es mucho más fuerte que la primera versión.
El año 2021¡Qué arrogante!
¿ Qué quieres decir?¡Las vacas del dios de la hierba!
Las hierbasEdición de GPU ¿Por qué no lo haces? device = torch.device (('cuda' if torch.cuda.is_available)) else 'cpu') class PPO ((nn.Module): Def __init__(self): super (PPO, self).__init__ ()) Se trata de un ejemplo de un sistema de datos. ¿Qué es esto? self.fc1 = nn.Linear ((8,64)) self.lstm = nn.LSTM ((64,32) self.fc_pi = nn.Linear ((32 y 3) self.fc_v = nn.Linear ((32 y 1) Self.optimizer = optim.Adam ((self.parameters))) y lr = learning_rate (tasa de aprendizaje) def pi ((self, x, hidden): x = F.relu ((self.fc1))) x = x.view ((-1, 1, 64) y el valor de x es igual a x.view ((-1, 1, 64) x, lstm_hidden = self. lstm ((x, escondido)) x = self. fc_pi (x) prob = F.softmax ((x, dim = 2) y el valor de la luz es ¿Qué es lo que está pasando? ¿Qué es esto? def v ((self, x, hidden): x = F.relu ((self.fc1))) x = x.view ((-1, 1, 64) y el valor de x es igual a x.view ((-1, 1, 64) x, lstm_hidden = self. lstm (x, oculto) v = self.fc_v ((x) regresar v ¿Qué es esto? Def put_data ((self, transition)): el nombre de la página en el que se encuentra el texto. Se trata de una versión de la página web de Google. ¿Qué es esto? Def make_batch ((sólo): El nombre de la fuente de información es el siguiente: s_lst, a_lst, r_lst, s_prime_lst, prob_a_lst, hidden_lst, done_lst = [], [], [], [], [], [] Para la transición en self.data: s, a, r, s_prime, prob_a, oculto, hecho = transición ¿Qué es esto? Aplicación de las palabras s_lst a_lst.append (([a]) también se puede usar para la aplicación. R_lst.append (([r]) S_prime_lst.append (s_prime) es el nombre de una de las páginas de un sitio web. Prob_a_lst.append (([prob_a]) también está disponible. El nombre de la aplicación es hidden_lst.append. done_mask = 0 si está hecho otro 1 ¿Qué es lo que está haciendo? ¿Qué es esto? 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) y torch.tensor (prob_a_lst). Se trata de un ejemplo de un sistema de datos. return s, a, r, s_prime, done_mask, prob_a, hidden_lst[0] ¿Qué es esto? Def train_net ((sólo): s, a, r, s_prime, done_mask, prob_a, (h1, h2) = self.make_batch ()) first_hidden = (h1.to ((device).detach ((), h2.to ((device).detach (()) Para i en rango (K_epoch): v_prime = self.v ((s_prime, first_hidden).squeeze))) td_target = r + gamma * v_prime * hecho_mask v_s = self.v ((s, first_hidden).squeeze))) delta = td_target - v_s también delta = delta.cpu ().detach ().numpy ().cpu ().cpu ().detach ().numpy ()) ¿Qué es lo que está pasando? ventaja = 0.0 Para el elemento en delta [::-1]: ventaja = gamma * lmbda * ventaja + artículo [0] ¿Qué es lo que está pasando? ¿Qué es lo que está pasando? El tipo de dispositivo que se utiliza es el tipo de dispositivo en el que se utiliza el tipo de dispositivo. pi, _ = self. pi (s, primero oculto) Pi_a = pi.squeeze ((1).gather ((1, a) y pi_a = pi.squeeze ((1).gather ((1, a)) y pi_a = pi.squeeze ((1).gather ((1, a)) y pi_a = pi.squeeze))) ratio = torch.exp ((torch.log ((pi_a) - torch.log ((prob_a)) # a/b == log ((exp)) a) -exp (b)) Surr1 = ratio * ventaja surr2 = torch.clamp ((ratio, 1-eps_clip, 1+eps_clip) * ventaja La pérdida = -torch.min ((surr1, surr2) + F.smooth_l1_loss ((v_s, td_target.detach)) ¿Qué es lo que está pasando? Los gráficos de los gráficos de los gráficos de los gráficos de los gráficos de los gráficos de los gráficos de los gráficos de los gráficos ¿Qué es lo que está pasando? ¿Por qué no lo haces?