trailing stop su Quantconnect

Trailing Stop con QuantConnect

In questo articolo, descriviamo un frammento di codice che implementa un trailing stop con Quantconnect per il trading algoritmico. Al momento in cui scriviamo, l’API LEAN non supporta questo tipo di stop quindi, se vogliamo usare un trailing stop, dobbiamo crearlo con gli strumenti a nostra disposizione.

Trailing stop

Per implementare la base di un trailing stop su Quantconnect, dobbiamo  semplicemente aggiornare frequentemente l’ordine  di stop loss. In altre parole dobbiamo determinare se lo stop deve salire e, in tal caso, spostarlo più in alto con un aggiornamento  del livello dell’ordine. Questa è una soluzione alternativa perché possiamo aggiornare il livello di stop solo in determinati momenti. Ad esempio, quando si richiama OnData(). Inoltre, se facciamo trading su un timeframe, potrebbe non essere abbastanza frequente.

Tuttavia, questa “soluzione alternativa” ha molto potenziale perchè si possono creare alcuni trailing stop interessanti. Infatti possiamo controllare quando muovere il livello di stop loss, ad esempio se vogliamo seguire solamente le oscillazioni della price action oppure dare respiro allo stop se la volatilità è aumentata.

Trailing stop con Quantconnect

				
					
class ParticleResistanceCircuit(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2018, 11, 20)  # Data Inizio
        self.SetCash(100000)  # Capitale Iniziale
        self.symbol = "SPY"
        self.AddEquity(self.symbol, Resolution.Minute)

        # distanza del trailing in $
        self.trail_dist = 10

        # dichiara un attributo che useremo per memorizzare il nostro stop loss ticket.
        self.sl_order = None

        # Dichiara un attributo che useremo per memorizzare l'ultimo livello
        # di trailing stop usato. Lo useremo per decidere se spostare lo stop
        self.last_trail_level = None

    def OnOrderEvent(self, OrderEvent):
        '''Event when the order is filled. Debug log the order fill. :OrderEvent:'''

        if OrderEvent.FillQuantity == 0:
            return

        # otteniamo l'ordine eseguito
        Order = self.Transactions.GetOrderById(OrderEvent.OrderId)

        # Log dei dettagli dell'ordine eseguito
        self.Log("ORDER NOTIFICATION >> {} >> Status: {} Symbol: {}. Quantity: "
                 "{}. Direction: {}. Fill Price {}".format(str(Order.Tag),
                                                           str(OrderEvent.Status),
                                                           str(OrderEvent.Symbol),
                                                           str(OrderEvent.FillQuantity),
                                                           str(OrderEvent.Direction),
                                                           str(OrderEvent.FillPrice)))

    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
        '''

        # Dobbiamo verificare che il simbolo contengai  dati prima di provare ad
        # accedere a OHLC. In caso contrario, viene sollevata un'eccezione se i dati sono mancanti.
        if data.ContainsKey(self.symbol) and data.HasData:

            # Alias
            # ------------------------------------------
            holdings = self.Portfolio[self.symbol].Quantity
            value = self.Portfolio.TotalPortfolioValue
            cash = self.Portfolio.Cash

            # Eccezione se non ci sono i dati
            try:
                O = round(data[self.symbol].Open, 2)
                H = round(data[self.symbol].High, 2)
                L = round(data[self.symbol].Low, 2)
                C = round(data[self.symbol].Close, 2)
            except AttributeError:
                self.Log('>> {} >> Missing Data')
                return

            # Calcola il nostro livello SL di base. Viene utilizzato per l'inserimento iniziale.
            # Lo useremo anche per il confronto con il livello del trailing precedente.
            base_sl_level = round(C - self.trail_dist, 2)

            # Log OHLC - Questo può essere utile per il debug per vedere dove si sta muovendo il prezzo
            self.Log('>> {}      >> ON DATA >> >> >> >> >> >>'.format(self.symbol))
            self.Log('>> OHLC     >> O:{} H:{} L:{} C:{}'.format(O, H, L, C))
            self.Log('>> SL       >> Base Level:{} Last Trail Level:{}'.format(base_sl_level, self.last_trail_level))
            self.Log('>> Account  >> Cash:{}, Val:{}, Holdings:{}'.format(cash, value, holdings))

            if not self.Portfolio.Invested:
                self.MarketOrder(self.symbol, 10, False, 'Long Entry')
                self.sl_order = self.StopMarketOrder(self.symbol, -10, base_sl_level, 'SL')
                self.last_trail_level = base_sl_level

            else:
                if base_sl_level > self.last_trail_level:
                    self.Log('>> Updating Trailing Stop >>')

                    # Aggiornamento ordine stoploss
                    update_order_fields = UpdateOrderFields()
                    update_order_fields.StopPrice = base_sl_level
                    self.sl_order.Update(update_order_fields)

                    # Log dell'ultimo sl_level
                    self.last_trail_level = base_sl_level
				
			

Commento

Dato che vogliamo modificare e aggiornare lo stop loss man mano che  i prezzi si muovono dobbiamo creare un attributo (variabile) che memorizza il ticket dello stop loss. I ticket vengono restituiti dai metodi API (funzioni) che creano gli ordini e ne abbiamo bisogno per aggiornare l’ordine in un secondo momento. Per questo motivo, una delle prime cose che facciamo è  prevedere una variabile dove memorizzare le informazioni relative all’ordine dello stop loss.

				
					        
        # dichiara un attributo che useremo per memorizzare il nostro stop loss ticket.
        self.sl_order = None

				
			

Allo stesso tempo, creiamo anche un altro attributo per tracciare l’ultimo livello di trailing stop che usiamo ad ogni nuova barra per verificare se l’attuale livello di stop loss è superiore all’ultimo livello memorizzato. In caso affermativo, spostiamo lo stop loss verso l’alto. Sottolineiamo che questa logica può essere implementata tramite una finestra scorrevole. In questo codice non abbiamo usato una finestra scorrevole perché non abbiamo di accedere ad un valore precedente al più recente e possiamo seguirlo senza usare una finestra scorrevole.

I dati

Il resto delle logica è implementata nel metodo  OnData(). Per lo scopo di questo articolo prevediamo di entrare immediatamente  in posizione e contemporaneamente inviamo un ordine di stop loss. Da notare che usiamo la variabile self.sl_order.

				
					
if not self.Portfolio.Invested:
    self.MarketOrder(self.symbol, 10, False, 'Long Entry')
    self.sl_order = self.StopMarketOrder(self.symbol, -10, sl_level, 'SL')
    self.last_trail_level = sl_level

				
			

Successivamente, ricaviamo il trailing stop calcolando semplicemente un valore di stop loss “base” come l’attuale prezzo close meno il trail_dist. Se il  movimento del prezzo è nostro favore, il valore base sarà superiore al  self.last_trail_level. In questo caso, aggiorniamo il ticket con le righe:

				
					
# Aggiornamento ordine stoploss
update_order_fields = UpdateOrderFields()
update_order_fields.StopPrice = sl_level
self.sl_order.Update(update_order_fields)

				
			

Nota: per ulteriori informazioni relative alle funzionalità di UpdateOrderFields() si può consultare la documentazione ufficiale: https://www.quantconnect.com/docs/algorithm-reference/trading-and-orders.

Dopo aver aggiornato l’ordine, allo stesso tempo aggiorniamo il  self.last_trail_level , che corrisponde al nuovo livello da superare per le successive barre. Infine, in caso contrario, ignoriamo questa barra e manteniamo  l’attuale livello di stop loss. Supponendo che il prezzo non si muova più a nostro favore, questo livello rimane costante fino a quanto non viene raggiunto dal prezzo. Questo è proprio come funzionerebbe un vero trailing stop.

Verifica

Dal momento che non ci sono elementi interessanti sul grafico, dobbiamo rivolgerci ai log per verificare cosa sta succedendo. Eseguiamo il backtest e apriamo i log. Dovremmo ottenere qualcosa del genere:

QuantConnect-Trailing-Stop-Log

Vediamo come il trailing stop inizia a 254,66$ e viene aggiornato nella barra successiva quando il prezzo si  muove e si calcola il nuovo livello base viene pari a 254,99$ .

Codice completo

In questo articolo abbiamo descritto come implementare un trailing stop con Quantconnect 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/QuantConnect

Gli altri articoli di questa serie

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

Torna in alto
Scroll to Top