Trading con il Reinforcement Learning in Python

Trading con il Reinforcement Learning in Python – Parte 2: Applicazione

Sommario

In questo articolo descriviamo un approccio al trading con il reinforcement learning in Python per creare un modello di machine learning per determinare quali operazioni eseguire.

Nel precedente articolo abbiamo descritto il gradient ascent e come possiamo  usarlo per massimizzare una funzione di ricompensa. In questo articolo, invece di usare l’errore quadratico medio come funzione di ricompensa, usiamo lo Sharpe Ratio. Possiamo usare il reinforcement learning per massimizzare lo Sharpe Ratio su un data set di addestramento e provare a creare una strategia con un elevato Sharpe Ratio quando  è testata su dataset fuori  dal campione.

Lo Sharpe Ratio

Lo Sharpe Ratio è un indicatore molto usato per misurare la performance di un investimento nel tempo corretta per il rischio. Supponendo un tasso privo di rischio pari a 0, la formula per calcolare lo Sharpe Ratio è semplicemente il rendimento medio dell’investimento diviso per la deviazione standard dei rendimenti, come segue:

\(S_T = {A \over \sqrt{B – A^2}}\)

dove \(A={1\over T}\sum\limits _{t=1}^{T}R _t\) e \(B={1\over T}\sum\limits _{t=1}^{T}R _t^2\)

Lo Sharpe Ratio può essere codificato in Python in questo modo:

				
					
def sharpe_ratio(rets):
    return rets.mean() / rets.std()
				
			

Funzione di trading

Vogliamo usare lo Sharpe Ratio come funzione di ricompensa, ma come usare queste informazioni per fare trading? Usiamo la seguente funzione per determinare la posizione, F, al tempo T:

\(F_t = \tanh(\theta^T x _t)\)

Questa funzione genere un valore compreso tra -1 e 1, che indica la percentuale del portafoglio da dedicare per acquistare o vendere l’asset, \(θ\). Come nel precedente articolo, sono parametri che vogliamo ottimizzare tramite il gradient ascent, e \(x_t\) è il vettore di input per il tempo T. 

In questo articolo consideriamo il vettore di input come \(x_t = [1, r_{t – M}, … , r_t, F_{t – 1}]\), dove \(R_t\) è la variazione di valore dell’asset tra il tempo \(t\) e il tempo \(t-1\) e è il numero di input della serie temporale. In altre parole, in ogni fase temporale forniamo al modello l’ultima posizione e una serie di variazioni storiche dei prezzi che può usare per calcolare la posizione successiva. Possiamo calcolare tutte le posizioni data la serie di prezzi xtheta con la seguente funzione Python:

				
					
import numpy as np

def positions(x, theta):
    M = len(theta) - 2
    T = len(x)
    Ft = np.zeros(T)
    for t in range(M, T):
        xt = np.concatenate([[1], x[t - M:t], [Ft[t - 1]]])
        Ft[t] = np.tanh(np.dot(theta, xt))
    return Ft
				
			

Calcolo dei rendimenti

Ora che sappiamo quale sarà la nostra posizione in ogni fase temporale, possiamo calcolare i nostri rendimenti \(R\) ad ogni istante temporale tramite la seguente formula:

\(R _t = F _{t-1}r _t – \delta | F _t – F _{t – 1}|\)

In questo caso \(\delta\) è il tasso di costo di transazione. Possiamo codificarlo con una funzione in Python come segue:

				
					
def returns(Ft, x, delta):
    T = len(x)
    rets = Ft[0:T - 1] * x[1:T] - delta * np.abs(Ft[1:T] - Ft[0:T - 1])
    return np.concatenate([[0], rets])
				
			

Questi rendimenti possono essere usati per calcolare lo Sharpe Ratio.

Gradient ascent

Determinare il gradiente

Per eseguire il gradient ascent, dobbiamo calcolare la derivata dello Sharpe Ratio rispetto a theta, o \({dS _T}\over{d\theta}\). Usando la regola della catena e le formule precedenti otteniamo:

\({{dS _T}\over{d\theta}} = \sum\limits_{t=1}^{T} ( {{dS _T}\over{dA}}{{dA}\over{dR _t}} + {{dS _T}\over{dB}}{{dB}\over{dR _t}}) \cdot ({{dR _t}\over{dF _t}}{{dF}\over{d\theta}} + {{dR _t}\over{dF _{t-1}}}{{dF _{t-1}}\over{d\theta}})\)

Per comprendere i passaggi per calcolare la derivata di cui sopra e le derivate parziali, si può consultare l’articolo di Gabriel Molina, Stock Trading with Recurrent Reinforcement Learning (RRL).

Possiamo calcolare questa derivata con la funzione gradient:

				
					

def gradient(x, theta, delta):
    Ft = positions(x, theta)
    R = returns(Ft, x, delta)
    T = len(x)
    M = len(theta) - 2

    A = np.mean(R)
    B = np.mean(np.square(R))
    S = A / np.sqrt(B - A ** 2)

    dSdA = S * (1 + S ** 2) / A
    dSdB = -S ** 3 / 2 / A ** 2
    dAdR = 1. / T
    dBdR = 2. / T * R

    grad = np.zeros(M + 2)  # inizializza il gradiente
    dFpdtheta = np.zeros(M + 2)  # memoriiza i dFdtheta precedenti

    for t in range(M, T):
        xt = np.concatenate([[1], x[t - M:t], [Ft[t - 1]]])
        dRdF = -delta * np.sign(Ft[t] - Ft[t - 1])
        dRdFp = x[t] + delta * np.sign(Ft[t] - Ft[t - 1])
        dFdtheta = (1 - Ft[t] ** 2) * (xt + theta[-1] * dFpdtheta)
        dSdtheta = (dSdA * dAdR + dSdB * dBdR[t]) * (dRdF * dFdtheta + dRdFp * dFpdtheta)
        grad = grad + dSdtheta
        dFpdtheta = dFdtheta

    return grad, S
				
			

Addestramento del modello

Dopo aver definito la funzione gradiente, possiamo ottimizzare i parametri  tramite il gradient ascent. Come nel precedente articolo, aggiorniamo \(\theta\) ogni epoca  con la formula \(\theta = \theta + \alpha{dS _T \over d\theta}\), dove \(\)\alpha[/latex[ è il  learning rate.

				
					

def train(x, epochs=2000, M=8, commission=0.0025, learning_rate=0.3):
    theta = np.random.rand(M + 2)
    sharpes = np.zeros(epochs)  
    for i in range(epochs):
        grad, sharpe = gradient(x, theta, commission)
        theta = theta + grad * learning_rate
        sharpes[i] = sharpe

    print("finished training")
    return theta, sharpes
				
			

Trading sul Bitcoin

Abbiamo ottenuto un modello che possiamo testare usando i dati storici del bitcoin. Usiamo la cronologia di tutti i trade su bitcoin nell’exchange Bitstamp, scaricata da bitcoincharts.com

Iniziamo caricando i dati.

				
					
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (5, 3) # (w, h)
plt.rcParams["figure.dpi"] = 150
import pandas as pd

btc = pd.read_csv("bitstampUSD.csv", names=["utc", "price", "volume"]).set_index('utc')
btc.index = pd.to_datetime(btc.index, unit='s')
rets = btc['price'].diff()[1:]
				
			

Per questa strategia addestriamo il modello con 1000 campioni e e quindi la testiamo con i successivi 200 campioni. Dividiamo i dati nei dataset di training e di test, quindi li normalizziamo con i dati di training.

				
					
x = np.array(rets)

N = 1000
P = 200
x_train = x[-(N+P):-P]
x_test = x[-P:]

std = np.std(x_train)
mean = np.mean(x_train)

x_train = (x_train - mean) / std
x_test = (x_test - mean) / std
				
			

Adesso siamo pronti addestrare il modello. Prevediamo una finestra di look-back di 8 tick per il modello.

				
					
np.random.seed(0)
theta, sharpes = train(x_train, epochs=2000, M=8, commission=0.0025, learning_rate=0.3)
				
			

Per verificare i risultati dell’addestramento, possiamo rappresentare graficamente lo Sharpe Ratio risultante per ogni epoca e vediamo se convergere al massimo.

				
					

plt.plot(sharpes)
plt.xlabel('Epoch Number')
plt.ylabel('Sharpe Ratio')
plt.show()
				
			
Trading con il Reinforcement Learning in Python

Possiamo vedere che mentre si addestra il modello, si converge verso lo Sharpe Ratio massimo. Vediamo come si è comportato il modello sul dataset di training:

				
					
train_returns = returns(positions(x_train, theta), x_train, 0.0025)
plt.plot((train_returns).cumsum(), label="Reinforcement Learning Model", linewidth=1)
plt.plot(x_train.cumsum(), label="Buy and Hold", linewidth=1)
plt.xlabel('Ticks')
plt.ylabel('Cumulative Returns');
plt.legend()
plt.title("RL Model vs. Buy and Hold - Training Data");
				
			
Reinforcement-learning-Bitcoin-rlmodel-train

Possiamo vedere che, rispetto ai dati di training, il nostro modello di reinforcement learning ha ampiamente sovraperformato il semplice  buy&hold dell’asset. Vediamo come si comporta nei prossimi 200 tick, che sono stati tenuti fuori dal modello.

				
					
test_returns = returns(positions(x_test, theta), x_test, 0.0025)
plt.plot((test_returns).cumsum(), label="Reinforcement Learning Model", linewidth=1)
plt.plot(x_test.cumsum(), label="Buy and Hold", linewidth=1)
plt.xlabel('Ticks')
plt.ylabel('Cumulative Returns');
plt.legend()
plt.title("RL Model vs. Buy and Hold - Test Data");
				
			
Reinforcement-learning-Bitcoin-rlmodel-test

Ancora una volta il modello supera l’asset! Questo modello potrebbe essere migliorato progettando più features (input), ma è un ottimo inizio. 

Codice completo

In questo articolo abbiamo descritto un approccio di trading con il reinforcement learning in Python. Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/MachineLearning

Benvenuto su DataTrading!

Sono Gianluca, ingegnere software e data scientist. Sono appassionato di coding, finanza e trading. Leggi la mia storia.

Ho creato DataTrading per aiutare le altre persone ad utilizzare nuovi approcci e nuovi strumenti, ed applicarli correttamente al mondo del trading.

DataTrading vuole essere un punto di ritrovo per scambiare esperienze, opinioni ed idee.

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

Scroll to Top