Neuronale Netzwerke und digitale Währung Quantitative Transaktionen Serie ((2))

Schriftsteller:Das Gras, Erstellt: 2019-07-31 11:13:15, aktualisiert: 2023-10-20 20:10:18

img

1.介绍

Vorheriger Artikel über die Verwendung des LSTM-Netzwerks zur Bitcoin-Preisvorhersagehttps://www.fmz.com/digest-topic/4035Wie bereits erwähnt, handelt es sich hierbei nur um ein kleines Projekt für die Praktiker, um sich mit RNN und pytorch vertraut zu machen. In diesem Artikel werden Methoden zur Verwendung von Reinforcement Learning vorgestellt, um Handelsstrategien direkt zu trainieren. Das Reinforcement Learning-Modell ist ein OpenAI-Open-Source-PPO, dessen Umgebung sich auf den Stil des Gyms bezieht. PPO, vollständig bekannt als Proximal Policy Optimization, ist eine Optimierung von Policy Graident, also der Strategie-Gradiale. Gym, auch von OpenAI veröffentlicht, kann mit dem Strategie-Netzwerk interagieren, den Status und die Belohnungen der aktuellen Umgebung feedback. Das Lesen dieses Artikels erfordert eine gewisse Basis für Deep Reinforcement Learning in Python, pytorch und DRL.www.fmz.comSie können sich auch an die Seite der QQ-Gruppe 863946592 wenden.

2.数据和学习参考资料

Bitcoin-Preisdaten stammen von der Quantitative-Trading-Plattform von FMZ:https://www.quantinfo.com/Tools/View/4.htmlEin Artikel, der DRL+gym benutzt, um Trading-Strategien zu trainieren:https://towardsdatascience.com/visualizing-stock-trading-agents-using-matplotlib-and-gym-584c992bc6d4Einige Beispiele für die Einführung von pytorch:https://github.com/yunjey/pytorch-tutorialIn diesem Artikel wird eine kurze Implementierung des LSTM-PPO Modells direkt verwendet:https://github.com/seungeunrho/minimalRL/blob/master/ppo-lstm.pyArtikel über die PPO:https://zhuanlan.zhihu.com/p/38185553Mehr zu DRL:https://www.zhihu.com/people/flood-sung/postsFür Gymnastik benötigen wir keine Installation, aber Reinforcement Learning ist sehr nützlich:https://gym.openai.com/

3.LSTM-PPO

Eine detaillierte Darstellung der PPO kann in den folgenden Referenzen erfahren werden. Hier ist eine einfache Einführung der Konzepte. In der letzten Phase prognostizierte das LSTM-Netzwerk nur einen Preis, und wie kann man auf der Grundlage dieses prognostizierten Preises einen Kauf- oder Verkaufstransaktion auch anders realisieren? Natürlich ist es denkbar, dass die direkte Ausgabe von Kauf- und Verkaufshandlungen nicht direkter ist.

Die folgenden Quellcodes für das LSTM-PPO sind verständlich, wenn sie mit den vorstehenden Informationen kombiniert werden:


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

Das Modell wurde im Gym-Format nachgeahmt und verfügt über eine Reset-Initialisierungsmethode, step-Eingabe von Aktionen, die das Ergebnis als ((nächster Status, Aktionsgewinn, Ende oder nicht, zusätzliche Informationen) zurückgeben), die gesamte Retargeting-Umgebung ist auch in 60 Zeilen, die sich selbst modifizieren lassen, um eine komplexere Version des spezifischen Codes zu erstellen:

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

Warum gibt es Münzen in den ursprünglichen Konten?

Die Formel für die Berechnung des Ertrags durch die Rezensierung der Umgebung lautet: aktuelle Erträge = aktuelle Kontowert - aktuelle Kontowert des Anfangs. Dies bedeutet, dass, wenn der Bitcoin-Preis sinkt und die Strategie eine Verkaufsaktion ausführt, auch wenn der Gesamtkontowert sinkt, in der Tat eine Belohnung mit der Strategie gewährt werden sollte. Wenn die Rezensierung lange dauert, kann das ursprüngliche Konto nur einen geringen Einfluss haben, aber am Anfang hat es einen großen Einfluss. Die Berechnung der relativen Erträge gewährleistet eine positive Belohnung für jede richtige Aktion.

Warum sammeln Sie beim Training?

Die gesamte Datenmenge beträgt mehr als 10.000 K-Zeilen, wenn die gesamte Menge jedes Mal eine Runde läuft, ist es sehr lang, und die Strategie ist jedes Mal im selben Zustand. Es ist möglich, dass es einfacher ist, zu überpassen.

Was tun, wenn man keine Münzen oder kein Geld hat?

In der Rücksichtnahme-Umgebung wird dieser Fall nicht berücksichtigt, wenn die Münze bereits verkauft ist oder nicht das Mindestvolumen erreicht hat, dann ist die Ausführung der Verkaufsoperation in Wirklichkeit gleichbedeutend mit der Ausführung der Nichtoperation, wenn der Preis fällt, basiert die Strategie nach wie vor auf der Berechnung der relativen Erträge auf der Basis einer positiven Belohnung. Die Auswirkungen dieser Situation sind, dass die Strategie die Marktlage nach unten beurteilt und wenn der Konto-Balance nicht verkauft werden kann, kann man nicht zwischen verkaufenden und nicht-operativen Aktionen unterscheiden, aber die Strategie selbst hat keinen Einfluss auf die Marktlage.

Warum sollten wir die Kontoinformationen zurückgeben?

Das PPO-Modell hat ein Wertnetzwerk, mit dem der Wert des aktuellen Zustands bewertet wird. Offensichtlich hat der gesamte Zustand nur einen positiven Wert, wenn die Strategie entscheidet, dass der Preis steigen soll, wenn das aktuelle Konto Bitcoin hält, und umgekehrt.

Was ist der Grund, warum wir zurückkehren, wenn es nicht funktioniert?

Wenn die Gewinne aus dem Kauf und Verkauf nicht abgedeckt werden können, sollte man nicht handeln. Obwohl die vorherige Beschreibung wiederholt die Preisentwicklung mit der Strategie beurteilt, ist es nur zur Erleichterung, dass das PPO-Modell in der Tat keine Prognose für den Markt macht, sondern nur die Wahrscheinlichkeit von drei Bewegungen ausgibt.

6.数据的获取和训练

Wie in dem vorherigen Artikel, wie und in welchem Format die Daten erhalten werden, handelt Bitfinex BTC_USD für den einstündigen Zyklus K-Line von 2018/5/7 bis 2019/6/27:

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)

Da ich mit dem LSTM-Netzwerk lange trainiert habe, habe ich eine weitere Version der GPU geändert, die etwa dreimal schneller ist.

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

Nach langem Warten:img

Wenn man sich zunächst die Entwicklung der Trainingsdaten anschaut, dann sieht man im Allgemeinen einen langen Rückgang in der ersten Hälfte und einen starken Rückgang in der zweiten Hälfte.img

Vorbereitende Kaufoperationen sind viel, grundsätzlich keine profitablen Runden. Bis zur Zwischenbereitstellung werden die Kaufoperationen schrittweise reduziert, die Gewinnchancen werden größer, aber die Verlustchancen sind groß.img

Wenn man die Gewinne pro Runde ausgleicht, ergibt sich folgendes:img

Die Strategie löste sich schnell von der negativen Ertragslage, aber die Schwankungen waren groß, und die Erträge wuchsen erst nach 10.000 Runden rasch an, was im Allgemeinen schwierig war.

Nach dem Abschluss des Trainings wird das Modell alle Daten ausführen, um zu sehen, wie es sich verhält, und den Gesamtmarktwert der Konten, die Anzahl der Bitcoins, den Anteil des Bitcoin-Wertes und die Gesamterträge während des Zeitraums erfassen. Der erste ist der Gesamtmarktwert, der Gesamtgewinn und ähnliches wird nicht berücksichtigt:img
Der Gesamtmarktwert stieg während des ersten Bear Market langsam an und stieg auch während des späteren Bull Market an, aber es gab immer noch schrittweise Verluste.

Schließlich schauen Sie sich den Haltungsanteil an, die linke Achse des Diagramms ist der Haltungsanteil, die rechte Achse ist der Markt. Es kann vorläufig beurteilt werden, dass das Modell eine Überanpassung aufweist.img

8.测试数据分析

Bei der Erfassung der Testdaten vom 27. Juni 2019 bis heute ist der Bitcoin-Stundenmarkt. In der Abbildung kann man sehen, dass der Preis von Anfang an von 13.000 US-Dollar auf heute über 9.000 US-Dollar gefallen ist.img

Zunächst war der Relativgewinn im Endeffekt schlecht, aber auch kein Verlust.img

Wenn man sich die Haltungslage anschaut, kann man vermuten, dass das Modell nach einem starken Rückgang dazu neigt, zu kaufen oder zu verkaufen. Der Bitcoin-Markt ist in letzter Zeit sehr unbeständig.img

9.总结

Die PPO hat einen Bitcoin-Autotrading-Roboter mit Hilfe der Deep Reinforcement Learning-Methode trainiert und einige Schlussfolgerungen gezogen. Da die Zeit begrenzt ist, gibt es noch einige Punkte, an denen das Modell verbessert werden kann. Die größte Lehre darin ist, dass die Daten standardisiert sind und keine Methoden wie Skalierung angewendet werden, sonst erinnert sich das Modell schnell an die Beziehungen zwischen Preis und Markt und gerät in eine Fassung.

Ein früherer Artikel beschreibt: Einige der Strategien, die FMZ-Erfinder öffentlich auf ihrer Quantifizierungsplattform teilen:https://zhuanlan.zhihu.com/p/64961672Der Kurs für die Quantitative Transaktion von digitalen Währungen in der NetEasy Cloud Classroom kostet nur 20 US-Dollar:https://study.163.com/course/courseMain.htm?courseId=1006074239&share=2&shareId=400000000602076Ich habe eine High-Frequency-Strategie veröffentlicht, die einmal sehr viel Geld einbrachte:https://www.fmz.com/bbs-topic/1211


Verwandt

Mehr

Lisa20231Warum wollen Sie die Bilder der Tests umdrehen? Warum sinkt dein Gewinn immer, wenn der Dollar steigt?

JackmaProfit = self.value - (self.initial_balance+self.initial_stocks * self.df.iloc[self.current_time,4]) Es gibt einen Fehler Es sollte sein: 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]) Es gibt einen Fehler Es sollte sein: profit = self.value - (self.initial_balance+self.initial_stocks*self.df.iloc[self.start,4])

- Ich weiß nicht.Es ist viel stärker als die erste Version.

Zwei JahreDas ist ein Schlagzeug!

- Ich weiß.Die Gräsergottesviehmasse!

Das GrasGPU-Version Ich weiß nicht. Device = torch.device (('cuda' if torch.cuda.is_available (() else 'cpu')) 'device' = torch.device (('cuda' if torch.cuda.is_available)) 'device' = torch.device (('cuda' if torch.cuda.is_available)) 'device' = torch.device (('cuda' if torch.cuda.is_available)) 'device' = torch.device)) 'device' = torch.device (('cuda' if torch.cuda.is_available)) 'device' = torch.device)) 'device' = torch.device' class PPO ((nn.Module): Definition von __init__ ((self): Super ((PPO, self).__init__() Selbst.Daten = [] Das ist nicht wahr. Selbst. 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))) und 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) Das ist ein sehr schwieriger Fall. Das ist nicht wahr. def v ((self, x, hidden): x = F.relu ((self.fc1 ((x)) x = x.view ((-1, 1, 64) x, lstm_hidden = self.lstm (x, versteckt) v = self. fc_v (x) zurück v Das ist nicht wahr. Def put_data ((selbst, Übergang): Selbst.Daten.Append (Übergang) Das ist nicht wahr. 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 = Übergang Das ist nicht wahr. 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 (versteckt) done_mask = 0 wenn done else 1 Das ist nicht das Problem. Das ist nicht wahr. 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) Selbst.Daten = [] return s, a, r, s_prime, done_mask, prob_a, hidden_lst[0] Das ist nicht wahr. 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 (()) für 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. Vorteil_lst = [] Vorteil = 0.0 für item in delta [::-1]: Vorteil = gamma * lmbda * Vorteil + Artikel[0] Vorteil_lst.append (([vorteil]) Vorteil_lst.umgekehrt Vorteil = 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 * Vorteil surr2 = torch.clamp ((Ratio, 1-eps_clip, 1+eps_clip) * Vorteil loss = -torch.min ((surr1, surr2) + F.smooth_l1_loss ((v_s, td_target.detach)) Selbstoptimierer.Zero_grad Verlust.mean (().backward ((Retain_graph=True)) Selbstoptimierer.Schritt Ich weiß nicht.