Нейронные сети и цифровые валюты Количественная торговля Серия ((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, а среда ссылается на стиль гимна. PPO, или Proximal Policy Optimization, - это оптимизация Policy Graident, то есть стратегического градиента. Gym, также выпущенный OpenAI, может взаимодействовать с стратегической сетью, давать отзывы о состоянии и вознаграждениях текущей среды. Для чтения данной статьи необходимы определенные основы глубокого реинтеграционного обучения Python, pytorch, DRL.www.fmz.comВ этом году мы получили более 10 миллионов долларов США, и мы хотим поделиться своими мыслями.

2.数据和学习参考资料

Данные о ценах на биткоин получены на квантовой платформе FMZ:https://www.quantinfo.com/Tools/View/4.htmlВ статье, в которой используется DRL+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Статья о PPO:https://zhuanlan.zhihu.com/p/38185553Больше статей о DRL:https://www.zhihu.com/people/flood-sung/postsПо поводу тренажерных заведений, эта статья не требует установки, но усиливающее обучение часто используется:https://gym.openai.com/

3.LSTM-PPO

Политический 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.比特币回测环境

Подражая формату гимна, существует метод резета инициализации, 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.几个值得注意的细节

Почему в первоначальном счете были монеты?

Формула для исчисления прибыли от рекурсионной среды: текущий доход = текущая стоимость счета - текущая стоимость первоначального счета. Это означает, что если цена биткоина падает, а стратегия совершает операции по продаже, даже если общая стоимость счета уменьшается, она фактически должна быть вознаграждена с помощью стратегии. Если время рекурсионного периода длинное, первоначальный счет может иметь небольшой эффект, но вначале он имеет большой эффект.

Почему в тренировках пробки?

Общий объем данных составляет более 10 000 K-линий. Если каждый раз весь объем будет выполняться в одном цикле, то потребуется много времени, и стратегия будет иметь одинаковые условия при каждой встрече.

Как вы относитесь к тому, что у вас нет денег или денег?

В условиях рекурсионной оценки не учитывается, что если монета уже проданная или не достигла минимального объема торгов, то выполнение операции продажи фактически равносильно невыполнению операции, а если цена упала, то по-прежнему основывается на стратегическом положительном вознаграждении, согласно методу расчета относительной прибыли.

Почему мы должны возвращать информацию об аккаунте в состояние, в котором она была?

В модели PPO имеется сеть ценностей, используемая для оценки стоимости текущего состояния. Очевидно, что если стратегия решает, что цена должна подняться, то только тогда, когда текущий аккаунт держит биткоин, весь состояние имеет положительное значение, и наоборот.

В каких случаях он возвращается в нерабочее положение?

Если прибыль от покупки и продажи не может покрыть расходы на процедуру, следует вернуться к бездействию. Хотя в предыдущем описании неоднократно использовались стратегии для определения ценовых тенденций, для удобства понимания модель PPO не делает прогнозов на рынке, а просто выводит вероятность трех действий.

6.数据的获取和训练

Как и в предыдущей статье, способы и формат получения данных следуют:

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)

Так как обучение занимало много времени, я сменил версию GPU, которая была примерно в три раза быстрее.

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

Стратегия быстро избавилась от первоначальной отрицательной прибыли, но колебания были большими, и прибыль быстро росла только после 10000 оборотов, и в целом было трудно тренировать модели.

После окончания обучения модель выполняет полный анализ данных, чтобы увидеть, как они работают, и записывает общую стоимость аккаунта, количество биткоинов, долю стоимости биткойнов, общую прибыль. В первую очередь, общая рыночная стоимость, общая прибыль и тому подобное, без учета:img
Общая рыночная стоимость увеличилась медленно в начале медвежьего рынка, а затем выросла в начале бычьего рынка, но все же имела поэтапный убыток.

Наконец, посмотрите на соотношение хранения, левая ось диаграммы - соотношение хранения, правая - рынок, можно предварительно судить, что модель возникла сверхспособной, в начале периода медвежьего рынка частота хранения была низкой, а в начале рынка - высокой.img

8.测试数据分析

В диаграмме можно увидеть, что цена упала с 13000 долларов до более чем 9000 долларов сегодня, что является большим испытанием для модели.img

Первый - это относительная прибыль, которая в конечном итоге показывает плохую динамику, но не приносит убытков.img

Если посмотреть на состояние хранения, можно предположить, что модель имеет тенденцию к рецессионным покупкам и продажам, а в последнее время рынок биткоина имеет небольшие колебания.img

9.总结

В этой статье используется метод глубокого усиления обучения PPO, который обучает автоторговый робот биткойна, и приводит к некоторым выводам. Из-за ограниченного времени модель имеет еще несколько мест для улучшения, и вы можете обсудить. Самый большой урок заключается в том, что данные стандартизированы.

В статье рассказывается: Некоторые из открытых стратегий, которыми FMZ делится на своей квантовой платформе:https://zhuanlan.zhihu.com/p/64961672Курс по количественным операциям с цифровыми валютами в классах NetEase Cloud стоит всего 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.data = [] Что вы думаете? 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 = [], [], [], [], [], [], [] Для перехода в 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 если сделано 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), \ 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] Что вы думаете? 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 + гамма * v_prime * done_mask v_s = self.v ((s, first_hidden).squeeze ((1)) delta = td_target - v_s delta = delta.cpu (().detach (().numpy (()) advantage_lst = [] преимущество = 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).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))) self.optimizer.zero_grad (англ.) русский loss.mean (().backward ((retain_graph=True) Определение потерь: self.optimizer.step (Серф. оптимизатор. шаг)) ``