Ist es möglich, einen Agenten für den Handel an der Börse mit Verstärkung auszubilden? Implementierung der R-Sprache

Lassen Sie uns einen Prototyp des Verstärkungslernagenten (RL) erstellen, der die Handelsfähigkeiten beherrscht.

Da die Implementierung des Prototyps in der R-Sprache funktioniert, ermutige ich R-Benutzer und Programmierer, sich den in diesem Artikel vorgestellten Ideen anzunähern.

Dies ist eine Übersetzung meines englischen Artikels: Kann Reinforcement Learning Trade Stock? Implementierung in R.

Ich möchte die Codejäger warnen, dass es in dieser Notiz nur einen Code für ein neuronales Netzwerk gibt, das für R angepasst ist.

Wenn ich mich nicht in gutem Russisch auszeichnete, weisen Sie auf die Fehler hin (der Text wurde mit Hilfe eines automatischen Übersetzers erstellt).

Bild

Einführung in das Problem


Bild

Ich rate Ihnen, mit diesem Artikel in das Thema einzutauchen : DeepMind

Es führt Sie in die Idee ein, das Deep Q-Network (DQN) zu verwenden, um eine Wertefunktion zu approximieren, die für Markov-Entscheidungsprozesse von entscheidender Bedeutung ist.

Ich empfehle auch, mit dem Vorabdruck dieses Buches von Richard S. Sutton und Andrew J. Barto: Reinforcement Learning tiefer in die Mathematik einzusteigen

Im Folgenden werde ich eine erweiterte Version des ursprünglichen DQN vorstellen, die weitere Ideen enthält, die dem Algorithmus helfen, schnell und effizient zu konvergieren, nämlich:

Deep Double Dueling Noisy NN mit Prioritätsauswahl aus dem Erfahrungswiedergabepuffer.

Was macht diesen Ansatz besser als klassisches DQN?

  • Double: Es gibt zwei Netzwerke, von denen eines trainiert ist und das andere die folgenden Werte von Q auswertet
  • Duell: Es gibt Neuronen, die eindeutig Wert legen und davon profitieren
  • Rauschen: Auf die Gewichte der Zwischenschichten werden Rauschmatrizen angewendet, bei denen der Mittelwert und die Standardabweichung trainierte Gewichte sind
  • Priorität der Probenahme: Beobachtungsstapel aus dem Wiedergabepuffer enthalten Beispiele, aufgrund derer das vorherige Training der Funktionen zu großen Rückständen führte, die im Hilfsarray gespeichert werden können.

Was ist mit dem Handel des DQN-Agenten? Dies ist ein interessantes Thema als solches.


Es gibt Gründe, warum dies interessant ist:

  • Absolute Wahlfreiheit bei der Darstellung von Status, Aktionen, Auszeichnungen und Architektur von NN. Sie können den Einstiegsbereich mit allem bereichern, was Sie für einen Versuch wert halten, von Nachrichten bis zu anderen Aktien und Indizes.
  • Die Entsprechung der Handelslogik mit der Verstärkungslernlogik besteht darin, dass: der Agent diskrete (oder kontinuierliche) Aktionen ausführt, selten belohnt wird (nachdem die Transaktion abgeschlossen wurde oder der Zeitraum abläuft), die Umgebung teilweise beobachtbar ist und Informationen über die nächsten Schritte enthalten kann. Der Handel ist ein episodisches Spiel.
  • Sie können DQN-Ergebnisse mit verschiedenen Benchmarks vergleichen, z. B. mit Indizes und technischen Handelssystemen.
  • Der Agent kann ständig neue Informationen lernen und sich so an die sich ändernden Spielregeln anpassen.

Um das Material nicht zu dehnen, schauen Sie sich den Code dieses NN an, den ich teilen möchte, da dies einer der mysteriösen Teile des gesamten Projekts ist.

R-Code für ein neuronales Wertnetzwerk, das Keras zum Erstellen unseres RL-Agenten verwendet


Code
# configure critic NN ------------ library('keras') library('R6') learning_rate <- 1e-3 state_names_length <- 12 # just for example a_CustomLayer <- R6::R6Class( "CustomLayer" , inherit = KerasLayer , public = list( call = function(x, mask = NULL) { x - k_mean(x, axis = 2, keepdims = T) } ) ) a_normalize_layer <- function(object) { create_layer(a_CustomLayer, object, list(name = 'a_normalize_layer')) } v_CustomLayer <- R6::R6Class( "CustomLayer" , inherit = KerasLayer , public = list( call = function(x, mask = NULL) { k_concatenate(list(x, x, x), axis = 2) } , compute_output_shape = function(input_shape) { output_shape = input_shape output_shape[[2]] <- input_shape[[2]] * 3L output_shape } ) ) v_normalize_layer <- function(object) { create_layer(v_CustomLayer, object, list(name = 'v_normalize_layer')) } noise_CustomLayer <- R6::R6Class( "CustomLayer" , inherit = KerasLayer , lock_objects = FALSE , public = list( initialize = function(output_dim) { self$output_dim <- output_dim } , build = function(input_shape) { self$input_dim <- input_shape[[2]] sqr_inputs <- self$input_dim ** (1/2) self$sigma_initializer <- initializer_constant(.5 / sqr_inputs) self$mu_initializer <- initializer_random_uniform(minval = (-1 / sqr_inputs), maxval = (1 / sqr_inputs)) self$mu_weight <- self$add_weight( name = 'mu_weight', shape = list(self$input_dim, self$output_dim), initializer = self$mu_initializer, trainable = TRUE ) self$sigma_weight <- self$add_weight( name = 'sigma_weight', shape = list(self$input_dim, self$output_dim), initializer = self$sigma_initializer, trainable = TRUE ) self$mu_bias <- self$add_weight( name = 'mu_bias', shape = list(self$output_dim), initializer = self$mu_initializer, trainable = TRUE ) self$sigma_bias <- self$add_weight( name = 'sigma_bias', shape = list(self$output_dim), initializer = self$sigma_initializer, trainable = TRUE ) } , call = function(x, mask = NULL) { #sample from noise distribution e_i = k_random_normal(shape = list(self$input_dim, self$output_dim)) e_j = k_random_normal(shape = list(self$output_dim)) #We use the factorized Gaussian noise variant from Section 3 of Fortunato et al. eW = k_sign(e_i) * (k_sqrt(k_abs(e_i))) * k_sign(e_j) * (k_sqrt(k_abs(e_j))) eB = k_sign(e_j) * (k_abs(e_j) ** (1/2)) #See section 3 of Fortunato et al. noise_injected_weights = k_dot(x, self$mu_weight + (self$sigma_weight * eW)) noise_injected_bias = self$mu_bias + (self$sigma_bias * eB) output = k_bias_add(noise_injected_weights, noise_injected_bias) output } , compute_output_shape = function(input_shape) { output_shape <- input_shape output_shape[[2]] <- self$output_dim output_shape } ) ) noise_add_layer <- function(object, output_dim) { create_layer( noise_CustomLayer , object , list( name = 'noise_add_layer' , output_dim = as.integer(output_dim) , trainable = T ) ) } critic_input <- layer_input( shape = c(as.integer(state_names_length)) , name = 'critic_input' ) common_layer_dense_1 <- layer_dense( units = 20 , activation = "tanh" ) critic_layer_dense_v_1 <- layer_dense( units = 10 , activation = "tanh" ) critic_layer_dense_v_2 <- layer_dense( units = 5 , activation = "tanh" ) critic_layer_dense_v_3 <- layer_dense( units = 1 , name = 'critic_layer_dense_v_3' ) critic_layer_dense_a_1 <- layer_dense( units = 10 , activation = "tanh" ) # critic_layer_dense_a_2 <- layer_dense( # units = 5 # , activation = "tanh" # ) critic_layer_dense_a_3 <- layer_dense( units = length(acts) , name = 'critic_layer_dense_a_3' ) critic_model_v <- critic_input %>% common_layer_dense_1 %>% critic_layer_dense_v_1 %>% critic_layer_dense_v_2 %>% critic_layer_dense_v_3 %>% v_normalize_layer critic_model_a <- critic_input %>% common_layer_dense_1 %>% critic_layer_dense_a_1 %>% #critic_layer_dense_a_2 %>% noise_add_layer(output_dim = 5) %>% critic_layer_dense_a_3 %>% a_normalize_layer critic_output <- layer_add( list( critic_model_v , critic_model_a ) , name = 'critic_output' ) critic_model_1 <- keras_model( inputs = critic_input , outputs = critic_output ) critic_optimizer = optimizer_adam(lr = learning_rate) keras::compile( critic_model_1 , optimizer = critic_optimizer , loss = 'mse' , metrics = 'mse' ) train.x <- rnorm(state_names_length * 10) train.x <- array(train.x, dim = c(10, state_names_length)) predict(critic_model_1, train.x) layer_name <- 'noise_add_layer' intermediate_layer_model <- keras_model(inputs = critic_model_1$input, outputs = get_layer(critic_model_1, layer_name)$output) predict(intermediate_layer_model, train.x)[1,] critic_model_2 <- critic_model_1 


Ich habe diese Quelle verwendet, um Python-Code für den Rauschteil des Netzwerks anzupassen : Github Repo

Dieses neuronale Netzwerk sieht folgendermaßen aus:

Bild

Denken Sie daran, dass wir in der Duellarchitektur Gleichheit verwenden (Gleichung 1):

Q = A '+ V, wobei

A '= A - Durchschnitt (A);

Q = Wert der Zustandsaktion;

V = Zustandswert;

A = Vorteil.

Andere Variablen im Code sprechen für sich. Darüber hinaus eignet sich diese Architektur nur für eine bestimmte Aufgabe. Nehmen Sie sie daher nicht als selbstverständlich an.

Der Rest des Codes wird höchstwahrscheinlich allgemein genug für die Veröffentlichung sein, und es wird für den Programmierer interessant sein, ihn selbst zu schreiben.

Und jetzt - die Experimente. Das Testen der Arbeit des Agenten wurde in einem Sandkasten mit einem echten Makler durchgeführt, weit entfernt von den Realitäten des Handels auf einem Live-Markt.

Phase I.


Wir führen unseren Agenten gegen einen synthetischen Datensatz aus. Unsere Transaktionskosten betragen 0,5:

Bild

Das Ergebnis ist ausgezeichnet. Die maximale durchschnittliche episodische Belohnung in diesem Experiment
sollte 1,5 sein.

Bild

Wir sehen: Verlust der Kritik (das sogenannte Wertnetzwerk im Schauspieler-Kritiker-Ansatz), durchschnittliche Belohnung für eine Episode, akkumulierte Belohnung, Stichprobe der jüngsten Belohnungen.

Phase II


Wir bringen unserem Agenten ein willkürlich ausgewähltes Aktiensymbol bei, das ein interessantes Verhalten zeigt: einen flachen Start, ein schnelles Wachstum in der Mitte und ein trostloses Ende. In unserem Trainingskit ca. 4300 Tage. Die Transaktionskosten betragen 0,1 US-Dollar (absichtlich niedrig). Die Belohnung ist USD Gewinn / Verlust nach Abschluss eines Geschäfts zum Kauf / Verkauf von 1,0 Aktien.

Quelle: Finance.yahoo.com/quote/algn?ltr=1

Bild

NASDAQ: ALGN

Nachdem wir einige Parameter eingestellt hatten (wobei die NN-Architektur unverändert blieb), kamen wir zu folgendem Ergebnis:

Bild

Es stellte sich als nicht schlecht heraus, da der Agent am Ende lernte, durch Drücken von drei Tasten auf seiner Konsole Gewinn zu machen.

Bild

roter Marker = verkaufen, grüner Marker = kaufen, grauer Marker = nichts tun.

Bitte beachten Sie, dass die durchschnittliche Belohnung pro Episode auf ihrem Höhepunkt den realistischen Transaktionswert überstieg, der im realen Handel auftreten kann.

Es ist schade, dass Aktien aufgrund schlechter Nachrichten wie verrückt fallen ...

Schlussbemerkungen


Der Handel mit RL ist nicht nur schwierig, sondern auch nützlich. Wenn Ihr Roboter es besser macht als Sie, ist es Zeit, Ihre persönliche Zeit zu verbringen, um Bildung und Gesundheit zu erlangen.

Ich hoffe das war eine interessante Reise für dich. Wenn Ihnen diese Geschichte gefallen hat, winken Sie mit der Hand. Wenn großes Interesse besteht, kann ich fortfahren und Ihnen zeigen, wie die Richtlinienverlaufsmethoden unter Verwendung der R-Sprache und der Keras-API funktionieren.

Ich möchte auch meinen Freunden, die sich für neuronale Netze interessieren, für ihren Rat danken.

Wenn Sie noch Fragen haben, bin ich immer hier.

Source: https://habr.com/ru/post/de433182/


All Articles