In questo articolo descriviamo come gestire il position sizing in base allo stop-loss con backtrader per gestire il rischio nelle strategie di trading algoritmico, In questo modo abbiamo un drawdown pari ad una percentuale fissa del capitale se si raggiunge lo stop loss. In altre parole, valutiamo una posizione sulla base di uno stop loss. Ad esempio, ridimensioniamo la posizione in modo da perdere il 20% della liquidità disponibile quando lo stop loss è distante il 5% dal prezzo di entrata.
Sottolineiamo che questa tecnica prevede principalmente l’uso della leva finanziaria perché spesso abbiamo bisogno di avere dimensioni così grandi che superano il totale del denaro disponibile nel conto. Pertanto, questa tecnica è comunemente usata nei mercati con leva finanziaria come il Forex.
Nota: questo articolo non considera il margine disponibile nel conto e i requisiti di margine necessari per aprire e mantenere posizioni con leva finanziaria. Ulteriori informazioni sul margine nei mercati Forex sono descritte nell’articolo che descrive come gestire il margine e la leva di Oanda con Backtrader
Introduzione
Il codice descritto in questo articolo è eseguito su dati di test appositamente creati per verificare che il nostro codice sia corretto. È possibile scaricare una copia dei dati al seguente link: Stop Loss Position Sizing Test Data
I dati di test contengono un breve set di candele giornaliere. È tutto ciò di cui abbiamo bisogno per eseguire i test. Per iniziare, i dati apriranno e chiuderanno a 100 USD. Gli stessi prezzi saranno costanti per 10 giorni, in questo modo possiamo entrare esattamente a 100 USD (perché ci piace la matematica facile!). Successivamente i prezzi scendono a 90 per altri 10 giorni e quindi a 50 per gli ultimi 10 giorni. Questi casi forniscono un range di prezzi sufficiente per testare ampi stop loss.
La Formula Magica
Per dimensionare la posizione dobbiamo semplicemente applicare la seguente formula:
DIMENSIONE = IMPORTO A RISCHIO / (PREZZO DI ENTRATA – PREZZO TARGET DI USCITA)
Ad esempio, se vogliamo rischiare il 10% di 10000$, l’importo a rischio in dollari corrisponde a 1000$. Supponiamo di voler acquistare un asset per 100$ e avere uno stop loss a 95$. Applicando la formula con questi valori otteniamo:
- DIMENSIONE = 1000 / (100 – 95)
- DIMENSIONE = 1000 / 5
- DIMENSIONE = 200
Verifichiamo che la formula è corretta. Supponendo di aver acquistato l’asset per 100$, il costo/valore totale del nostro investimento è di 100 x 200 $ che equivale a 20.000$. Nota: possiamo vedere perché questa tecnica richiede un conto con leva finanziaria. Abbiamo iniziato solo con 10.000$! Se poi il prezzo scende a 95$, il valore del nostro investimento vale 95 * 200$, che equivale a 19.000$. Supponendo che usciamo a questo prezzo, risulterebbe in una perdita di 1000$, che è il 10% del nostro conto.
Naturalmente, questo è un esempio ideale. Potremmo non essere sempre eseguiti al livello desiderato a causa di gap o slippage. Inoltre, dobbiamo arrotondare per difetto quando i numeri non sono multipli della quota minima dell’asset che vogliamo negoziare. Pertanto, non dovremmo aspettarci un drawdown perfetto su ogni operazione, ma almeno dovremmo essere molto vicini!
Ora, passiamo a ricreare nel codice il trade con le stesse cifre dell’esempio!
Dimensionare una posizione in base allo Stop Loss
import backtrader as bt
from datetime import datetime
import math
class TestStrategy(bt.Strategy):
params = (('risk', 0.1), #risk 10%
('stop_dist', 0.05)) #stoploss distance 5%
def next(self):
date = self.data.datetime.date()
bar = len(self)
cash = self.broker.get_cash()
if bar == 7:
stop_price = (self.data.close[0] * (1 - self.p.stop_dist))
qty = math.floor((cash * self.p.risk) / (self.data.close[0] - stop_price))
self.buy(size=qty)
self.sell(exectype=bt.Order.Stop, size=qty, price=stop_price)
def notify_trade(self, trade):
date = self.data.datetime.datetime()
if trade.isclosed:
print('-'*32,' NOTIFY TRADE ','-'*32)
print('{}, Avg Price: {}, Profit, Gross {}, Net {}'.format(
date,
trade.price,
round(trade.pnl,2),
round(trade.pnlcomm,2)))
print('-'*80)
startcash = 10000
# Creare un'istanza di celebro
cerebro = bt.Cerebro()
# Aggiungere la strategia
cerebro.addstrategy(TestStrategy)
# Impostare le commissioni
cerebro.broker.setcommission(leverage=20)
# Creare il datafeed
data = bt.feeds.GenericCSVData(
timeframe=bt.TimeFrame.Days,
compression=1,
dataname='data/PositionSizingTestData.csv',
nullvalue=0.0,
dtformat=('%m/%d/%Y'),
datetime=0,
time=-1,
high=2,
low=3,
open=1,
close=4,
volume=-1,
openinterest=-1 #-1 means not used
)
# Aggiungere i dati
cerebro.adddata(data)
# Imposto il capitale inziale
cerebro.broker.setcash(startcash)
# Esecuzione del backtest
cerebro.run()
# Valore finale del portafoglio
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash
# Stampa dei risultati finali
print('Final Portfolio Value: ${}'.format(portvalue))
print('P/L: ${}'.format(pnl))
print('P/L: {}%'.format(((portvalue - startcash)/startcash)*100))
# Grafico dei risultati
cerebro.plot(style='candlestick')
Commento al codice
Questo articolo che descrivere come dimensionare una posizione in base allo stop loss, non è per neofiti. Pertanto, non descriviamo il funzionamento di ogni parte del codice. Per maggiori dettagli si può leggere gli altri articoli su Backtrader.
In tutta onestà, non c’è molto da scrivere su questa strategia. In effetti, ci sono solo 3 parti davvero degne di nota!
- Non dimentichiamoci di modificare il percorso dei dati, cioè dobbiamo modificare
dataname='data/PositionSizingTestData.csv'
se si ha una diversa struttura di cartelle o si salva il csv con un nome diverso. - Abbiamo definito la leva durante l’impostazione delle commissioni in cerebro. In questo modo siamo sicuri di poter aprire la posizione nel trade. Possiamo modificare la quantità di leva da usare tramite l’istruzione
cerebro.broker.setcommission(leverage=50)
- La formula per determinare la quantità
qty
è leggermente diversa dalla formula nell’esempio all’inizio dell’articolo:qty = math.floor((cash * self.p.risk) / (self.data.close[0] - stop_price))
. In questo caso calcoliamo l’importo del rischio in dollari moltiplicando il capitale per la percentuale che vogliamo rischiare. Lo mettiamo tra parentesi()
in modo che sia e viene calcolato prima di dividerlo. Usiamo il prezzoclose
come presunto livello di ingresso. Sappiamo che verremo eseguiti a questo livello perché stiamo usando i dati di test, ma in pratica, il prezzo potrebbe variare leggermente tra ilclose
e il prezzoopen
della barra successiva. Sfortunatamente, non abbiamo una formula che ci aiuti a fare il calcolo perfetto!
Riferimenti:
- Backtrader: documentazione sugli schemi della commissione (inclusa l’impostazione della leva finanziaria)
Risultati
Eseguendo lo script otteniamo un grafico simile a questo:
Con il seguente output:
-------------------------------- NOTIFY TRADE --------------------------------
2018-01-11 23:59:59.999989, Avg Price: 100.0, Profit, Gross -1000.0, Net -1000.0
--------------------------------------------------------------------------------
Final Portfolio Value: $9000.0
P/L: $-1000.0
P/L: -10.0%
Da notare che l’importo perso è esattamente il 10% del conto. Possiamo continuare e sperimentare diversi livelli di rischio e stop loss. Per fare questo basta modificare i parametri all’inizio della strategia.
params = (('risk', 0.1), # rischio del 10%
('stop_dist', 0.05)) # distanza stoploss del 5%
Codice completo
In questo articolo abbiamo descritto come gestire il position sizing in base allo stop-loss con backtrader per gestire il rischio nelle strategie di trading algoritmico, Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/BackTrader