Mean-Reverting sulle bande di Bollinger con Backtrader

Mean-Reverting sulle bande di Bollinger con Backtrader

In questo articolo descriviamo come creare una strategia mean-reverting sulle bande di Bollinger con Backtrader per il trading algoritmico. Per come sono costruite, le bande di Bollinger hanno tutti gli elementi necessari per implementare una completa strategia di ritorno verso la media. La linea mediana di Bollinger è una media mobile semplice adatta per rappresentare la media. Inoltre, le bande superiore e inferiore rappresentano la deviazione standard sopra/sotto la linea mediana. Questo è l’ideale per indicare quando il prezzo si è allontanato dalla media.

I concetti base della Mean Reversion

La mean reversion descrive  semplicemente il comportamento  di un processo che sta ritornando (reversion) verso un valore “medio” (mean). Generalmente, un trader impiega una strategia di mean reversion (o mean-reverting) quando il prezzo dell’asset/azione/valuta si è allontanato dalla media storica e statisticamente tende a tornare indietro verso il valore medio.

Nota di attenzione:

Le strategie di mean-reverting, come descritto nei tutorial sul trading algoritmico, possono essere rischiose se effettuiamo operazioni dalla parte sbagliata quando il mercato  è fortemente in trend. Questa strategia dovrebbe essere impiegata con cura.

Strategia mean-reverting sulle bande di bollinger

La strategia mean-reverting sulle bande di bollinger descritta in questo articolo vuole catturare il ritorno  verso la media (linea mediana) dopo che un movimento direzionale ha spostato il prezzo oltre le bande superiore o inferiore. Per cercare di catturare il più possibile del movimento di ritorno, la strategia  usa ordini stop per entrare in posizione proprio nel momento in cui il prezzo rientra dentro le bande (solo dopo averle superate all’esterno). Questo ha però un potenziale svantaggio. Il prezzo potrebbe tornare dentro le bande di bollinger prima di invertire di nuovo e continuare ad allontanarsi dalla media. Pertanto, si potrebbe preferire aspettare fino a quando il prezzo non chiude una barra all’interno delle bande.

Per uscire dalle posizioni si utilizzano ordini limite con prezzo limite pari alla linea mediana. In questo modo usciamo non appena il prezzo sarà tornato al valore medio.

Il codice

				
					import backtrader as bt
from datetime import datetime


class BOLLStrat(bt.Strategy):
    '''
    Questa è una semplice strategia mean reversion con le banda di bollinger

    Criteri di ingresso:
        - Long:
            - Il prezzo chiude al di sotto della banda inferiore
            - Ordine Stop di entrata quando il prezzo supera di nuovo la banda inferiore
        - Short:
            - Il prezzo chiude al di sopra della banda superiore
            - Ordine Stop di entrata quando il prezzo torna al di sotto della banda superiore
    Criterio di Uscita
        - Long/Short: il prezzo tocca la linea mediana
    '''

    params = (
        ("period", 20),
        ("devfactor", 2),
        ("size", 20),
        ("debug", False)
    )

    def __init__(self):
        self.boll = bt.indicators.BollingerBands(period=self.p.period, devfactor=self.p.devfactor)
        # self.sx = bt.indicators.CrossDown(self.data.close, self.boll.lines.top)
        # self.lx = bt.indicators.CrossUp(self.data.close, self.boll.lines.bot)

    def next(self):

        orders = self.broker.get_orders_open()

        # Cancella gli ordini aperti in modo che possiamo seguire la linea mediana
        if orders:
            for order in orders:
                self.broker.cancel(order)

        if not self.position:

            if self.data.close > self.boll.lines.top:
                self.sell(exectype=bt.Order.Stop, price=self.boll.lines.top[0], size=self.p.size)

            if self.data.close < self.boll.lines.bot:
                self.buy(exectype=bt.Order.Stop, price=self.boll.lines.bot[0], size=self.p.size)


        else:

            if self.position.size > 0:
                self.sell(exectype=bt.Order.Limit, price=self.boll.lines.mid[0], size=self.p.size)

            else:
                self.buy(exectype=bt.Order.Limit, price=self.boll.lines.mid[0], size=self.p.size)

        if self.p.debug:
            print('---------------------------- NEXT ----------------------------------')
            print("1: Data Name:                            {}".format(data._name))
            print("2: Bar Num:                              {}".format(len(data)))
            print("3: Current date:                         {}".format(data.datetime.datetime()))
            print('4: Open:                                 {}'.format(data.open[0]))
            print('5: High:                                 {}'.format(data.high[0]))
            print('6: Low:                                  {}'.format(data.low[0]))
            print('7: Close:                                {}'.format(data.close[0]))
            print('8: Volume:                               {}'.format(data.volume[0]))
            print('9: Position Size:                       {}'.format(self.position.size))
            print('--------------------------------------------------------------------')

    def notify_trade(self, trade):
        if trade.isclosed:
            dt = self.data.datetime.date()

            print('---------------------------- TRADE ---------------------------------')
            print("1: Data Name:                            {}".format(trade.data._name))
            print("2: Bar Num:                              {}".format(len(trade.data)))
            print("3: Current date:                         {}".format(dt))
            print('4: Status:                               Trade Complete')
            print('5: Ref:                                  {}'.format(trade.ref))
            print('6: PnL:                                  {}'.format(round(trade.pnl, 2)))
            print('--------------------------------------------------------------------')


# Capitale iniziale
startcash = 10000

# Creare un'istanza di cerebro
cerebro = bt.Cerebro()

# Aggiungere la strategia
cerebro.addstrategy(BOLLStrat)

data = bt.feeds.Quandl(
    dataname='AMZN',
    fromdate=datetime(2017, 1, 1),
    todate=datetime(2018, 1, 1),
    buffered=True,
)

# Aggiungere i dati storici a Cerebro
cerebro.adddata(data)

# Aggiungere un sizer
cerebro.addsizer(bt.sizers.FixedReverser, stake=10)

# Esecuzione del backtest
cerebro.run()

# Ottenere il valore finale del portafoglio
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

# Stampa dei risultati finali
print('Final Portfolio Value: ${}'.format(round(portvalue, 2)))
print('P/L: ${}'.format(round(pnl, 2)))

# Grafico dei risultati
cerebro.plot(style='candlestick')
				
			

Notiamo che self.broker.get_orders_open() è usato spesso per annullare tutti gli ordini aperti. Questo è necessario perché, al momento in cui scriviamo, Backtrader non dispone di un metodo nativo per modificare gli ordini aperti. Un’altra cosa da  considerare è che la maggior parte dei broker (IB, Oanda ecc.) non supportano il  metodo get_orders_open(). Pertanto, questa strategia non può essere utilizzata nel trading live senza  effettuare alcune modifiche.

Risultati dei backtest

E’ interesse mostrare correttamente i punti di forza e di debolezza di questa strategia, un esempio mostra i risultati della strategia applicata in un ambiente ideale mentre un altro esempio mostra la strategia eseguita in condizioni non ottimali. Nei Sottolineiamo che nei test abbiamo usato una dimensione fissa per le posizione, quindi dobbiamo focalizzare l’attenzione sullo strike rate e sulle differenze tra i trade vincenti e quelli perdenti invece che sul valore effettivo del PnL.

Ford 2016-2017

Un buon esempio di un’equità positiva. Molti trade positivi. Durante il periodo di backtest la volatilità è stata buona e il prezzo  è tornato spesso verso il valore medio.

Mean Reversion Bollinger-Ford-Example-Default-Settings

Amazon 2017-2018

Questo è un buon esempio di un titolo con un forte trend. In questo scenario, la strategia è quasi arrivata al pareggio. Con più dati storici probabilmente sarebbe stata una strategia perdente a lungo termine. In generale, le bande erano piuttosto strette durante il costante trend rialzista del titolo. Questo ha causato una posizione in perdita quando la strategia è entrata short appena il prezzo ha superato la banda superiore, il titolo ha invertito ed è continuato a salire verso l’alto per un periodo piuttosto lungo.

Mean Reversion Bollinger-Amazon-Example-Default-Settings

Codice completo

In questo articolo abbiamo descritto come creare una strategia mean-reverting sulle bande di Bollinger con Backtrader per il 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