Mạng thần kinh và số tiền kỹ thuật số giao dịch định lượng series ((2) Deep Reinforcement Learning training Bitcoin trading strategies

Tác giả:Cỏ nhỏ, Tạo: 2019-07-31 11:13:15, Cập nhật: 2023-10-20 20:10:18

img

1.介绍

Bài trước về việc sử dụng mạng LSTM để dự đoán giá Bitcoinhttps://www.fmz.com/digest-topic/4035, như đã đề cập trong bài viết, chỉ là một dự án nhỏ cho người thực hành để làm quen với RNN và pytorch. Bài viết này sẽ giới thiệu phương pháp sử dụng học tăng cường, trực tiếp đào tạo chiến lược giao dịch. Mô hình học tăng cường là PPO nguồn mở của OpenAI, môi trường dựa trên phong cách của phòng tập thể dục. Để dễ dàng hiểu và thử nghiệm, mô hình PPO của LSTM và môi trường phòng tập thể dục được kiểm tra đều viết trực tiếp các gói chưa sử dụng sẵn. PPO, được gọi là Proximal Policy Optimization, là một cải tiến tối ưu hóa cho Policy Graident, tức là các chiến lược gradient. Gym cũng được phát hành bởi OpenAI, có thể tương tác với mạng chiến lược, phản hồi trạng thái và phần thưởng của môi trường hiện tại, giống như bài tập học tăng cường sử dụng mô hình PPO của LSTM để chỉ thị mua, bán hoặc không hoạt động trực tiếp dựa trên thông tin thị trường của Bitcoin, được phản hồi bởi môi trường kiểm tra lại và đạt được mục tiêu chiến lược lợi nhuận thông qua việc đào tạo mô hình tối ưu hóa liên tục. Đọc bài viết này đòi hỏi một số nền tảng học tập tăng cường sâu Python, pytorch, DRL. Nhưng không quan trọng, kết hợp với mã được cung cấp trong bài viết này, rất dễ dàng để tìm hiểu.www.fmz.com), chào mừng bạn đến với nhóm QQ: 863946592.

2.数据和学习参考资料

Dữ liệu giá Bitcoin được lấy từ nền tảng giao dịch định lượng của FMZ:https://www.quantinfo.com/Tools/View/4.htmlMột bài viết sử dụng DRL+gym để đào tạo chiến lược giao dịch:https://towardsdatascience.com/visualizing-stock-trading-agents-using-matplotlib-and-gym-584c992bc6d4Một số ví dụ về pytorch:https://github.com/yunjey/pytorch-tutorialBài viết này sẽ sử dụng một thực hiện ngắn của mô hình LSTM-PPO trực tiếp:https://github.com/seungeunrho/minimalRL/blob/master/ppo-lstm.pyBài viết về PPO:https://zhuanlan.zhihu.com/p/38185553Các bài viết khác về DRL:https://www.zhihu.com/people/flood-sung/postsVề phòng tập thể dục, bài viết này không cần cài đặt, nhưng học tăng cường rất phổ biến:https://gym.openai.com/

3.LSTM-PPO

Các thông tin tham khảo sâu về PPO, có thể học được ở trên, đây chỉ là một giới thiệu về khái niệm đơn giản. Trong giai đoạn trước, mạng lưới LSTM chỉ dự đoán một giá, làm thế nào để thực hiện giao dịch mua và bán theo giá dự đoán này, bạn có thể tự nhiên nghĩ rằng, đầu ra mua và bán hành động trực tiếp không phải là trực tiếp hơn?

Dưới đây là mã nguồn của LSTM-PPO, kết hợp với các thông tin trước đây có thể hiểu được:


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

Mô phỏng định dạng của gym, có một phương pháp khởi tạo reset, step nhập hành động, trả về kết quả là ((tiếp theo trạng thái, hành động thu nhập, có kết thúc, thông tin bổ sung), toàn bộ môi trường reset cũng có 60 dòng, tự sửa đổi cho phiên bản phức tạp hơn, mã cụ thể:

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

Tại sao tài khoản ban đầu có đồng xu?

Công thức tính toán lợi nhuận của môi trường xem xét lại là: lợi nhuận hiện tại = giá trị tài khoản hiện tại - giá trị tài khoản ban đầu. Điều này có nghĩa là nếu giá Bitcoin giảm, và chiến lược thực hiện các hoạt động bán tiền, ngay cả khi tổng giá trị tài khoản giảm, thực tế cũng nên được thưởng cho chiến lược. Nếu thời gian xem xét lại là dài, tài khoản ban đầu có thể không có tác động lớn, nhưng vẫn có tác động lớn ngay từ đầu. Tính toán lợi nhuận tương đối đảm bảo mỗi hành động đúng sẽ nhận được phần thưởng tích cực.

Tại sao các bạn nên thử nghiệm trong khi tập luyện?

Tổng số dữ liệu là hơn 10.000 dòng K, nếu mỗi lần chạy một vòng lặp toàn bộ, thì sẽ mất nhiều thời gian, và chiến lược sẽ giống hệt nhau trong mọi trường hợp. Có thể dễ dàng quá phù hợp hơn.

Không có đồng xu hay không có tiền thì làm gì?

Trong môi trường đánh giá lại, không tính đến tình huống này, nếu đồng tiền đã bán ra hoặc không đạt được khối lượng giao dịch tối thiểu, thì thực hiện hoạt động bán là thực hiện không hoạt động, nếu giá giảm, theo cách tính toán lợi nhuận tương đối, chiến lược vẫn dựa trên phần thưởng tích cực.

Tại sao bạn lại muốn trả lại thông tin tài khoản của mình?

Mô hình PPO có một mạng lưới giá trị để đánh giá giá giá trị của trạng thái hiện tại, rõ ràng là nếu chiến lược quyết định giá sẽ tăng, toàn bộ trạng thái sẽ có giá trị tích cực chỉ khi tài khoản hiện tại nắm giữ Bitcoin, và ngược lại. Vì vậy, thông tin tài khoản là một cơ sở quan trọng để đánh giá giá trị mạng lưới.

Trong trường hợp nào thì nó sẽ trở lại không hoạt động?

Khi chiến lược phán đoán lợi nhuận từ việc mua bán không thể trang trải chi phí thủ tục, nên quay lại không hoạt động. Mặc dù mô tả trước đây đã sử dụng nhiều lần chiến lược phán đoán xu hướng giá, nhưng chỉ để dễ hiểu, thực tế mô hình PPO không dự đoán thị trường, chỉ đưa ra xác suất ba động thái.

6.数据的获取和训练

Như trong bài viết trước, cách thức và định dạng thu thập dữ liệu như sau, sàn giao dịch Bitfinex BTC_USD giao dịch trên dòng K cho chu kỳ một giờ từ ngày 5/7/2018 đến ngày 27/6/2019:

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)

Do sử dụng mạng LSTM, thời gian đào tạo rất dài, tôi đã thay đổi một phiên bản GPU khác, nhanh hơn khoảng 3 lần.

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

Sau một thời gian dài chờ đợi:img

Trước tiên, hãy nhìn vào thị trường dữ liệu huấn luyện, nói chung, nửa đầu là một đợt giảm dài, nửa sau là một đợt tăng mạnh.img

Các giao dịch mua mua trước khi đào tạo rất nhiều, về cơ bản không có vòng lợi nhuận. Đến giữa thời gian đào tạo, các giao dịch mua mua dần giảm, khả năng kiếm được lợi nhuận cũng ngày càng lớn, nhưng vẫn có khả năng mất mát lớn.img

Sau khi làm cho lợi nhuận của mỗi vòng được làm mịn, kết quả là:img

Chiến lược này nhanh chóng thoát khỏi tình trạng lợi nhuận tiêu cực trước đó, nhưng có những biến động lớn, và lợi nhuận chỉ tăng nhanh chóng sau 10.000 vòng, nói chung là rất khó để đào tạo mô hình.

Sau khi hoàn thành, mô hình sẽ chạy tất cả dữ liệu một lần nữa để xem nó hoạt động như thế nào, ghi lại tổng giá trị thị trường của tài khoản, số lượng Bitcoin nắm giữ, tỷ lệ giá trị Bitcoin, tổng lợi nhuận. Đầu tiên là tổng giá trị thị trường, tổng lợi nhuận và tương tự, không tính:img
Tổng giá trị thị trường tăng chậm trong thời kỳ thị trường gấu đầu tiên, tăng theo sau đó trong thời kỳ thị trường bò cuối cùng, nhưng vẫn có những khoản lỗ từng giai đoạn.

Cuối cùng, hãy nhìn vào tỷ lệ nắm giữ, trục trái của biểu đồ là tỷ lệ nắm giữ, trục phải là thị trường, bạn có thể phán đoán ban đầu mô hình xuất hiện quá phù hợp, trong thời gian đầu thị trường gấu có tần suất nắm giữ thấp, trong thời gian đáy thị trường có tần suất nắm giữ cao.img

8.测试数据分析

Khi dữ liệu thử nghiệm được thu thập, Bitcoin đã có một thị trường hàng giờ từ ngày 27/6/2019 đến nay.img

Trước tiên, cuối cùng, lợi nhuận tương đối, hoạt động kém nhưng không có lỗ.img

Xem lại tình hình nắm giữ, bạn có thể đoán mô hình có xu hướng mua hoặc bán trở lại sau khi giảm mạnh. Trong thời gian gần đây, thị trường Bitcoin có sự biến động rất nhỏ và mô hình đã ở trong tình trạng nắm giữ trống.img

9.总结

Bài viết này đã đào tạo một robot giao dịch tự động Bitcoin bằng cách sử dụng phương pháp học tập tăng cường sâu (PPO) và cũng đưa ra một số kết luận. Vì thời gian hạn chế, mô hình vẫn còn một số điểm để cải thiện, hãy vui lòng thảo luận. Bài học lớn nhất trong số đó là phương pháp chuẩn hóa dữ liệu, không sử dụng phương pháp quy mô, nếu không mô hình sẽ nhanh chóng ghi nhớ mối quan hệ giá và thị trường, rơi vào sự phù hợp.

Những bài viết trước đây: Một số chiến lược công khai được chia sẻ trên nền tảng đo lường của các nhà phát minh FMZ:https://zhuanlan.zhihu.com/p/64961672Chương trình giao dịch định lượng tiền kỹ thuật số của lớp học đám mây NetEase chỉ có 20 đô la:https://study.163.com/course/courseMain.htm?courseId=1006074239&share=2&shareId=400000000602076Một trong những chiến lược tần số cao mà tôi đã công bố và từng rất có lợi là:https://www.fmz.com/bbs-topic/1211


Có liên quan

Thêm nữa

Lisa20231Tại sao bạn lại lật ngược hình ảnh của kết quả thử nghiệm? Tại sao lợi nhuận của bạn luôn giảm khi đồng đô la tăng?

Jackmaprofit = self.value - (self.initial_balance+self.initial_stocks * self.df.iloc[self.current_time,4]) có lỗi Nó nên là: profit = self.value - (self.initial_balance+self.initial_stocks * self.df.iloc[self.start,4])

Jackmaprofit = self.value - (self.initial_balance+self.initial_stocks*self.df.iloc[self.current_time,4]) có lỗi Nó nên là: profit = self.value - (self.initial_balance+self.initial_stocks*self.df.iloc[self.start,4])

TimoshenkoNó mạnh mẽ hơn so với phiên bản đầu tiên.

xw2021Bọn chúng ta sẽ bị giết.

Eddie.Bọn bò cỏ!

Cỏ nhỏPhiên bản 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 = [] Không có gì đâu. 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 Không có gì đâu. 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)) return v Không có gì đâu. Def put_data ((self, transition): self.data.append ((transition)) Không có gì đâu. 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 = chuyển đổi Không có gì đâu. 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 (tạm dịch: ẩn) done_mask = 0 if done else 1 done_lst.append (([done_mask]) Không có gì đâu. 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] Không có gì đâu. 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 delta = delta.cpu (().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 (tạm dịch: lợi thế_lst.reverse) 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 = tỷ lệ * lợi thế 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 (tạm dịch: loss.mean (().backward ((retain_graph=True)) self.optimizer.step (tạm dịch: ``