Backtrader Ordini Bracket

Ordini bracket con backtrader

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.

Ordini Bracket con backtrader

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 documentscartella.

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.

Ordini Bracket con backtrader

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.

Ordini Bracket con backtrader

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.

Backtrader-Brack-Order-posizione-stop

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 essere Limit ma abbiamo specificato Stop.
  • 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

Scroll to Top