In questo articolo descriviamo gli ordini bracket con Backtrader e come applicarli alle strategie di trading algoritmico. Un ordine bracket è un tipo speciale di ordine che può semplificare notevolmente l’entrata in posizioni quando desideriamo uno stop loss e un take profit.
Ordini Bracket
L’ordine bracket consente a Backtrader di emulare un ordine del broker dove specifichiamo uno stop loss e un take profit nello stesso momento in cui entriamo. Questo è un modo abbastanza comune per entrare in una posizione con la maggior parte dei broker ed è piuttosto particolare perché:
- Inviamo 3 ordini contemporaneamente. Un ordine per entrare in una posizione e 2 ordini per uscire da una posizione
- Il broker accetta gli ordini di uscita ma non sono attivi finché l’ordine di entrata non viene eseguito. In questo modo impediamo agli ordini di uscita di innescare un trade nella direzione sbagliata!
- Una volta che siamo in una posizione e uno degli ordini di uscita viene eseguito, l’altro viene automaticamente cancellato.
Quando facciamo trading manualmente usando una piattaforma online, di solito abbiamo un modulo con le opzioni per aggiungere uno stop loss e un take profit. In questo modo sembra stiamo inviando un solo ordine ma in realtà ne stiamo inviando tre.
Nelle versioni precedenti di Backtrader, dovevamo simulare l’ordine bracket con più ordini separati. Dalla versione 1.9.37.116
abbiamo uno specifico ordine bracket
che gestisce 3 ordini automaticamente. La documentazione ufficiale descrive dettagliatamente come funziona un ordine bracket:
- I 3 ordini sono inviati insieme per evitare che nessuno di essi venga attivato in modo indipendente
- Gli ordini dai lati basso/alto sono contrassegnati come figli del principale
- I figli non sono attivi finché non viene eseguito l’ordine principale
- La cancellazione dell’ordine principale annulla gli ordini figli
- L’esecuzione dell’ordine principale attiva gli ordini figli
- Dopo essere stato attivo
- L’esecuzione o la cancellazione di uno qualsiasi degli ordini figli annulla automaticamente l’altro
Fonte: https://www.backtrader.com/docu/order-creation-execution/bracket/bracket?#bracket-orders
Nota: confrontato la documentazione di backtrade con l’immagine precedente, l’ordine principale è l’ordine di entrata a mercato. Il lato basso è l’ordine di stop loss e il lato alto è l’ordine di take profit. I neofiti della programmazione possono non capire come si relazionano gli ordini principali e gli ordini figli. È solo un modo conveniente per indicare la gerarchia. Possiamo immaginare questi ordini come le cartelle sul disco rigido. Abbiamo una cartella denominata documents
e all’interno della cartella documents
potremmo avere un’altra cartella denominata work
. In questo scenario, la cartella work
è la cartella figlio della documents
cartella.
Ottenere i dati di test
Per eseguire gli esempi degli ordini bracket con Backtrader, dobbiamo scaricare i seguenti dati di test: TestBrackets.csv. Come accennato negli articolo precedenti su Backtrader, usiamo questi dati artificiali per conoscere esattamente il comportamento dei prezzi e impostare le entrate in modo specifico. In altre parole possiamo controllare i movimenti dei prezzi secondo uno schema prevedibile per semplificare i test e i calcoli. I dati di test in questo esempio contengono solo dati di chiusura e variano semplicemente tra 100$ e 300$.
Obiettivo
Quando conosciamo la sintassi di base, gli ordini bracket sono un gioco da ragazzi. La documentazione ufficiale è solida ma copre solo un semplice esempio che prevede un ordine limite per l’entrata. Pertanto, cercheremo di aggiungere valore descrivendo più tipi di ordine e quando possiamo usarli.
Entrata a mercato, Stop Loss e Take Profit
Un ordine bracket che usa un ordine a mercato per entrare in una posizione (cioè l’ordine principale) è probabilmente l’ordine bracket più comune che i trader retail usano su le piattaforme online. È lo stesso ordine bracket mostrato nell’immagine precedente. Possiamo eseguire questo ordine con la istruzione: self.buy_bracket(limitprice=long_tp, stopprice=long_stop, exectype=bt.Order.Market)
. Generalmente, usiamo questo tipo di ordine quando abbiamo un segnale di entrata e non vogliamo aspettare per entrare in posizione. Effettuiamo un ordine a mercato e veniamo eseguiti rispetto all’ordine migliore presente nell’orderbook.
Ordine Limite, Stop Loss e Take Profit
Se non desideriamo entrare subito, possiamo usare un ordine limite per cercare di ottenere un prezzo migliore. In questo caso, entriamo usando la seguente istruzione: self.sell_bracket(limitprice=short_tp, price=entry, stopprice=short_stop, exectype=bt.Order.Limit)
.
Nel seguente esempio immaginario, siamo fiduciosi che il prezzo rimbalzerà un po’ prima di continuare un trend al ribasso. Quindi posizioniamo un ordine limite appena sopra il picco di un recente rimbalzo per cercare di raggiungere un altro massimo prima del ribasso.
Ordine Stop, Stop Loss e Take Profit
A volte vogliamo inserire una posizione a un prezzo peggiore di quello abbiamo in questo momento. Possiamo farlo quando cerchiamo la conferma che il prezzo continuerà a muoversi in una certa direzione. Un altro motivo è cercare di catturare un breakout. Per entrare con un ordine stop, cambiamo semplicemente il parametro exectype
in questo modo: self.sell_bracket(limitprice=short_tp, price=entry, stopprice=short_stop, exectype=bt.Order.Limit)
.
Nell’esempio seguente, alla barra corrente possiamo vedere che il prezzo è rimbalzato. Abbiamo una visione ribassista, ma vorremmo vedere il prezzo scendere al di sotto del supporto precedente per confermare che il prezzo continuerà a scendere. Pertanto, impostiamo un ordine di ingresso stop leggermente inferiore al minimo prima del rimbalzo.
Se il prezzo non avesse mai raggiunto un nuovo minimo, saremmo al sicuro poiché non verrebbero attivati ordini. Tuttavia, nel nostro esempio, possiamo vedere che il nostro ordine stop viene attivato e alla fine veniamo fermati.
Il codice
import backtrader as bt
from datetime import datetime
class TestStrategy(bt.Strategy):
params = (('percents', 0.9),) # Float: 1 == 100%
def __init__(self):
print('-'*32,' STRATEGY INIT ','-'*32)
self.rsi = bt.indicators.RSI_SMA(self.data.close, period=21)
def next(self):
date = self.data.datetime.date()
close = self.data.close[0]
print('{}: Close: ${}, Position Size: {}'.format(date, close, self.position.size))
if not self.position:
long_tp = close + 50
long_stop = close - 50
if date == datetime(2018,1,11).date():
# Entrata con un ordine a mercato
# Posizionare il TP a 50 dollari sopra
buy_ord = self.buy_bracket(limitprice=long_tp, stopprice=long_stop, exectype=bt.Order.Market)
elif date == datetime(2018,1,21).date():
# Entrata con un ordine limite per andare short e cattuare il prezzo vicino al massimo
entry = 200
short_tp = entry - 50
short_stop = entry + 50
buy_ord = self.sell_bracket(limitprice=short_tp, price=entry, stopprice=short_stop, exectype=bt.Order.Stop)
elif date == datetime(2018,2,11).date():
# Entrata con un ordine stop per andare short e cattuare il prezzo in discesa
entry = 290
short_tp = entry - 50
short_stop = entry + 50
buy_ord = self.sell_bracket(limitprice=short_tp, price=entry, stopprice=short_stop, exectype=bt.Order.Limit)
elif date == datetime(2018,3,23).date():
# Test Ordine non valido
# Tentativo di impostare di nuovo un ordine limite
# 1. Exetype non corretto
# 2. Valore Stop inferiore al prezzo (non corretto per lo short)
# 3. Ordine Limite superiore al prezzo (non corretto per lo short)
entry = 290
short_tp = 300
short_stop = 90
buy_ord = self.sell_bracket(limitprice=short_tp, price=entry, stopprice=short_stop, exectype=bt.Order.Stop)
def notify_order(self, order):
date = self.data.datetime.datetime().date()
if order.status == order.Accepted:
print('-'*32,' NOTIFY ORDER ','-'*32)
print('Order Accepted')
print('{}, Status {}: Ref: {}, Size: {}, Price: {}'.format(
date,
order.status,
order.ref,
order.size,
'NA' if not order.price else round(order.price,5)
))
if order.status == order.Completed:
print('-'*32,' NOTIFY ORDER ','-'*32)
print('Order Completed')
print('{}, Status {}: Ref: {}, Size: {}, Price: {}'.format(
date,
order.status,
order.ref,
order.size,
'NA' if not order.price else round(order.price,5)
))
print('Created: {} Price: {} Size: {}'.format(bt.num2date(order.created.dt), order.created.price,order.created.size))
print('-'*80)
if order.status == order.Canceled:
print('-'*32,' NOTIFY ORDER ','-'*32)
print('Order Canceled')
print('{}, Status {}: Ref: {}, Size: {}, Price: {}'.format(
date,
order.status,
order.ref,
order.size,
'NA' if not order.price else round(order.price,5)
))
if order.status == order.Rejected:
print('-'*32,' NOTIFY ORDER ','-'*32)
print('WARNING! Order Rejected')
print('{}, Status {}: Ref: {}, Size: {}, Price: {}'.format(
date,
order.status,
order.ref,
order.size,
'NA' if not order.price else round(order.price,5)
))
print('-'*80)
def notify_trade(self, trade):
date = self.data.datetime.datetime()
if trade.isclosed:
print('-'*32,' NOTIFY TRADE ','-'*32)
print('{}, Close Price: {}, Profit, Gross {}, Net {}'.format(
date,
trade.price,
round(trade.pnl,2),
round(trade.pnlcomm,2)))
print('-'*80)
startcash = 10000
# Creazione istanza cerebro
cerebro = bt.Cerebro()
# Aggiungere strategia
cerebro.addstrategy(TestStrategy)
# Creazione data feed
data = bt.feeds.GenericCSVData(
timeframe=bt.TimeFrame.Days,
compression=1,
dataname='data/TestBrackets.csv',
dtformat=('%m/%d/%Y'),
datetime=0,
time=-1,
high=1,
low=-1,
open=1,
close=1,
volume=-1,
openinterest=-1 #-1 significa non usato
)
# Aggiungere i dati
cerebro.adddata(data)
# Impostazione del capitale iniziale
cerebro.broker.setcash(startcash)
# Esecuzione del backtest
cerebro.run()
# Valore finale del portafoglio
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash
# Grafico dei risultati
print('Final Portfolio Value: ${}'.format(portvalue))
print('P/L: ${}'.format(pnl))
Commento al codice
Il codice prevede la creazione di ordine bracket per ciascuna delle tipologie descritte in precedenza. Inoltre, poiché è un test, gli ordini sono create ad una data specificata. Non prevediamo nessuna logica per impostare i prezzi target e i livelli di stop loss, mentre prevediamo solamente alcune cifre fisse basate sui valori dei dati di test, che già conosciamo. L’obiettivo è usare gli ordini bracket con backtrader nel modo più semplice possibile invece di implementare nel codice con complessi segnali d’ingresso e calcoli di stop loss. Oltre ai tipi di ordine descritti in precedenza, abbiamo anche creato un ordine non valido per vedere come il motore gestisce tale scenario e aiutarci a individuare facilmente quando abbiamo impostato l’ordine in modo errato.
Esecuzione del codice
Il codice prevede che i dati di test da usare si trovino in una sottocartella denominata data
che si trova nella stessa directory dello script. Se posizioniamo il file dei dati da qualche altra parte o gli assegniamo un nome diverso, dobbiamo assicurarci di modificare la seguente riga: dataname='data/TestBrackets.csv'
.
Vediamo ora alcuni degli output e come questi sono collegati ai nostri scenari di trading.
Entrata a mercato, Stop Loss e Take Profit
Il primo trade concluso produce il seguente output:
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-01-12, Status 2: Ref: 1, Size: 1, Price: NA
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-01-12, Status 2: Ref: 2, Size: -1, Price: 150.0
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-01-12, Status 2: Ref: 3, Size: -1, Price: 250.0
-------------------------------- NOTIFY ORDER --------------------------------
Order Completed
2018-01-12, Status 4: Ref: 1, Size: 1, Price: NA
Created: 2018-01-11 23:59:59.999989 Price: 200.0 Size: 1
--------------------------------------------------------------------------------
2018-01-12: Close: $210.0, Position Size: 1
2018-01-13: Close: $220.0, Position Size: 1
2018-01-14: Close: $230.0, Position Size: 1
2018-01-15: Close: $240.0, Position Size: 1
-------------------------------- NOTIFY ORDER --------------------------------
Order Completed
2018-01-16, Status 4: Ref: 3, Size: -1, Price: 250.0
Created: 2018-01-11 23:59:59.999989 Price: 250.0 Size: -1
--------------------------------------------------------------------------------
-------------------------------- NOTIFY ORDER --------------------------------
Order Canceled
2018-01-16, Status 5: Ref: 2, Size: -1, Price: 150.0
-------------------------------- NOTIFY TRADE --------------------------------
2018-01-16 23:59:59.999989, Close Price: 210.0, Profit, Gross 40.0, Net 40.0
--------------------------------------------------------------------------------
Possiamo vedere che tutti e 3 gli ordini vengono accettati contemporaneamente. Il primo ordine Ref: 1
ha un Price: NA
ed è l’ordine a mercato. Vediamo come questo ordine è l’unico ordine che ha lo status Completed
nella stessa barra degli ordini con status Accepted
. Successivamente, la posizione rimane aperta per alcune barre fino a quando il prezzo non arriva a 250$ e si attiva il take profit. Possiamo vedere che l’ordine Ref: 3
è in status Completed
e l’ordine Ref: 2
è in status Canceled
, cioè è automaticamente cancellato nello stesso momento.
Entrata Stop, Stop Loss e Take Profit
Nel secondo esempio usiamo un ordine stop per l’ingresso ed otteniamo il seguente output:
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-01-22, Status 2: Ref: 4, Size: -1, Price: 200
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-01-22, Status 2: Ref: 5, Size: 1, Price: 250
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-01-22, Status 2: Ref: 6, Size: 1, Price: 150
2018-01-22: Close: $290.0, Position Size: 0
2018-01-23: Close: $280.0, Position Size: 0
2018-01-24: Close: $270.0, Position Size: 0
2018-01-25: Close: $260.0, Position Size: 0
2018-01-26: Close: $250.0, Position Size: 0
2018-01-27: Close: $240.0, Position Size: 0
2018-01-28: Close: $230.0, Position Size: 0
2018-01-29: Close: $220.0, Position Size: 0
2018-01-30: Close: $210.0, Position Size: 0
-------------------------------- NOTIFY ORDER --------------------------------
Order Completed
2018-01-31, Status 4: Ref: 4, Size: -1, Price: 200
Created: 2018-01-21 23:59:59.999989 Price: 200 Size: -1
--------------------------------------------------------------------------------
2018-01-31: Close: $200.0, Position Size: -1
2018-02-01: Close: $190.0, Position Size: -1
2018-02-02: Close: $180.0, Position Size: -1
2018-02-03: Close: $170.0, Position Size: -1
2018-02-04: Close: $160.0, Position Size: -1
-------------------------------- NOTIFY ORDER --------------------------------
Order Completed
2018-02-05, Status 4: Ref: 6, Size: 1, Price: 150
Created: 2018-01-21 23:59:59.999989 Price: 150 Size: 1
--------------------------------------------------------------------------------
-------------------------------- NOTIFY ORDER --------------------------------
Order Canceled
2018-02-05, Status 5: Ref: 5, Size: 1, Price: 250
-------------------------------- NOTIFY TRADE --------------------------------
2018-02-05 23:59:59.999989, Close Price: 200.0, Profit, Gross 50.0, Net 50.0
--------------------------------------------------------------------------------
La differenza fondamentale con l’esempio precedente è che tutti e 3 gli ordini sono in status Accepted
ma nessuno è in status Completed
. Creiamo l’ordine quando il prezzo è piuttosto elevato. Successivamente, il prezzo scende gradualmente fino ad attivare l’ordine di ingresso stop a 200$. Notiamo come il prezzo stopprice
era fissato a 250$ ma non si è attivato quando il prezzo era superiore a 250$ o lo ha attraversato. L’ordine non era valido fino a quando l’ordine mainside
(l’entrata stop) non è stato completato. Questo è il vantaggio degli ordini bracket con backtrader.
Entrata Limit, Stop Loss e Take Profit
Nel seguente esempio usiamo un ordine limite per l’ingresso e otteniamo il seguente output:
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-02-12, Status 2: Ref: 7, Size: -1, Price: 290
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-02-12, Status 2: Ref: 8, Size: 1, Price: 340
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-02-12, Status 2: Ref: 9, Size: 1, Price: 240
2018-02-12: Close: $120.0, Position Size: 0
2018-02-13: Close: $130.0, Position Size: 0
2018-02-14: Close: $140.0, Position Size: 0
2018-02-15: Close: $150.0, Position Size: 0
2018-02-16: Close: $160.0, Position Size: 0
2018-02-17: Close: $170.0, Position Size: 0
2018-02-18: Close: $180.0, Position Size: 0
2018-02-19: Close: $190.0, Position Size: 0
2018-02-20: Close: $200.0, Position Size: 0
2018-02-21: Close: $210.0, Position Size: 0
2018-02-22: Close: $220.0, Position Size: 0
2018-02-23: Close: $230.0, Position Size: 0
2018-02-24: Close: $240.0, Position Size: 0
2018-02-25: Close: $250.0, Position Size: 0
2018-02-26: Close: $260.0, Position Size: 0
2018-02-27: Close: $270.0, Position Size: 0
2018-02-28: Close: $280.0, Position Size: 0
-------------------------------- NOTIFY ORDER --------------------------------
Order Completed
2018-03-01, Status 4: Ref: 7, Size: -1, Price: 290
Created: 2018-02-11 23:59:59.999989 Price: 290 Size: -1
--------------------------------------------------------------------------------
2018-03-01: Close: $290.0, Position Size: -1
2018-03-02: Close: $300.0, Position Size: -1
2018-03-03: Close: $290.0, Position Size: -1
2018-03-04: Close: $280.0, Position Size: -1
2018-03-05: Close: $270.0, Position Size: -1
2018-03-06: Close: $260.0, Position Size: -1
2018-03-07: Close: $250.0, Position Size: -1
-------------------------------- NOTIFY ORDER --------------------------------
Order Completed
2018-03-08, Status 4: Ref: 9, Size: 1, Price: 240
Created: 2018-02-11 23:59:59.999989 Price: 240 Size: 1
--------------------------------------------------------------------------------
-------------------------------- NOTIFY ORDER --------------------------------
Order Canceled
2018-03-08, Status 5: Ref: 8, Size: 1, Price: 340
-------------------------------- NOTIFY TRADE --------------------------------
2018-03-08 23:59:59.999989, Close Price: 290.0, Profit, Gross 50.0, Net 50.0
--------------------------------------------------------------------------------
La sequenza degli eventi quando usiamo un ordine limit
per l’ingresso non cambia rispetto l’ordine stop
. L’unica differenza è che il prezzo deve salire fino al prezzo di entrata (perché vogliamo andare short) invece di scendere ed attraversarlo. In altre parole, il prezzo di entrata deve essere migliore rispetto al prezzo dell’asset al momento della creazione dell’ordine.
Parametri non valide
L’ultimo ordine presente nel codice usa parametri non validi. In particolare:
- Forniamo un
exectype
non valido. Dovrebbe essereLimit
ma abbiamo specificatoStop
. - Il valore
stopprice
è impostato al di sotto del prezzo di entrata (errato per lo short) limitprice
è impostato al di sopra del prezzo di entrata (errato per lo short)
Vediamo cosa ci restituisce lo script:
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-03-24, Status 2: Ref: 10, Size: -1, Price: 290
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-03-24, Status 2: Ref: 11, Size: 1, Price: 90
-------------------------------- NOTIFY ORDER --------------------------------
Order Accepted
2018-03-24, Status 2: Ref: 12, Size: 1, Price: 300
-------------------------------- NOTIFY ORDER --------------------------------
Order Completed
2018-03-24, Status 4: Ref: 10, Size: -1, Price: 290
Created: 2018-03-23 23:59:59.999989 Price: 290 Size: -1
--------------------------------------------------------------------------------
2018-03-24: Close: $120.0, Position Size: -1
-------------------------------- NOTIFY ORDER --------------------------------
Order Completed
2018-03-25, Status 4: Ref: 11, Size: 1, Price: 90
Created: 2018-03-23 23:59:59.999989 Price: 90 Size: 1
--------------------------------------------------------------------------------
-------------------------------- NOTIFY ORDER --------------------------------
Order Canceled
2018-03-25, Status 5: Ref: 12, Size: 1, Price: 300
-------------------------------- NOTIFY TRADE --------------------------------
2018-03-25 23:59:59.999989, Close Price: 120.0, Profit, Gross -10.0, Net -10.0
--------------------------------------------------------------------------------
In questo caso possiamo vedere che l’ordine di entrata è immediatamente Completed
, dato che il prezzo dell’asset è già al di sotto del livello di entrata stop
. Dopo che l’ordine è Completed
, lo stop loss è immediatamente Completed
nella barra successiva. L’ordine è diventato attivo quando l’ordine di entrata Completed
e viene eseguito immediatamente anche perché il prezzo corrente è superiore allo stopprice
che abbiamo impostato. (dato che questa è una posizione short).
Codice completo
In questo articolo abbiamo descritto come usare gli ordini bracket con Backtrader e come applicarli alle 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