Questo è un frammento di codice per un indicatore di Trendline. Come suggerisce il nome, calcola il valore del prezzo in diversi punti di una trendline e, di conseguenza, genera segnali di acquisto e vendita. In alcuni casi mi piace poter adottare un approccio semi-automatico al trading algoritmico. Si potrebbe individuare una bella trendline su Tradingview ma si vuole eseguire operazioni in un ambiente di forward testing, utilizzando Backtrader, quando il prezzo raggiunge la trendline. In quanto tale, considero questo un indicatore a breve termine che può essere utilizzato fino a quando la trendline non viene rotta. L’indicatore di trendline descritto in questo articolo è destinato a essere utilizzato nel framework di Backtrader, ma i calcoli utilizzati per calcolare la trendline possono essere facilmente trasferiti su altri framework.

Background Matematico

Se come me, non sei un mago della matematica, potrebbe essere necessario un po’ di teoria per capire le equazioni contenute nel codice. E’ necessario quindi introdurre quelle che nel mondo della matematica sono conosciute come “equazioni lineari“, al fine di poter sviluppare questo indicatore.

Un tutorial ben scritto e molto utile, anche se in inglese, è il seguente:
mathplanet.com/education/algebra-1/formulating-linear-equations/writing-linear-equations-using-the-slope-intercept-form

Il concetto principale è che per poter calcolare il prezzo della trendline in qualsiasi momento, è necessario calcolare la velocità con cui cambia la pendenza tra due punti temporali. Questi punti temporali sono i due prezzi inseriti come input per l’indicatore. Come identificare questi due punti dipende da te. Come accennato in precedenza, identifico e traccio in modo discrezionale la trendline su Tradingview e quindi prendo nota del punto iniziale e del punto finale da utilizzare con questo indicatore.

L’equazione a cui dobbiamo arrivare è:

\(
\begin{eqnarray}
y = mx + b
\end{eqnarray}
\)

Dove:

y = prezzo

m = pendenza

x = data / ora

b = intersezione con l’asse y

A questo punto, una cosa che ho trovato difficile da spiegare è come la maggior parte dei tutorial presume che tu possa vedere visivamente l’intersezione con l’asse y, cioè il valore di y quando attraversa l’asse y per x=0. In questo modo:

Come sappiamo, i grafici dei prezzi sono leggermente diversi perchè difficilmente si può tornare indietro nel tempo (dove si potrebbe attraversare l’asse y). Non possiamo vedere dove è l’intersezione dell’asse y ma è possibile calcolarla! Per fare questo è necessario capovolgere un po’ l’equazione.

\( \begin{eqnarray} y = mx + b \end{eqnarray} \)
diventa
\( \begin{eqnarray} b = y – m*x \end{eqnarray} \)

Dopo aver risolto l’equazione, si può utilizzare i valori di x e y del punto iniziale e finale della trendline che hai indiviuato. Vedrai come l’ho implementato nel codice seguente.

Le Regole dell'Indicatore

L’indicatore della trendline genera un segnale di acquisto se il prezzo attraversa la trendline verso l’alto, mentre genera un segnale di vendita se il prezzo la attraversa dal basso. Poiché la trendline funge da supporto o resistenza, sarà sempre inferiore al prezzo quando si cercano segnali di acquisto mentre sarà superiore al prezzo quando si cercano segnali di vendita.

Il Codice

class TrendLine(bt.Indicator):
 
    lines = ('signal','trend')
    params = (
        ('x1', None),
        ('y1', None),
        ('x2', None),
        ('y2', None)
    )
 
    def __init__(self):
        self.p.x1 = datetime.datetime.strptime(self.p.x1, "%Y-%m-%d %H:%M:%S")
        self.p.x2 = datetime.datetime.strptime(self.p.x2, "%Y-%m-%d %H:%M:%S")
        x1_time_stamp = time.mktime(self.p.x1.timetuple())
        x2_time_stamp = time.mktime(self.p.x2.timetuple())
        self.m = self.get_slope(x1_time_stamp,x2_time_stamp,self.p.y1,self.p.y2)
        self.B = self.get_y_intercept(self.m, x1_time_stamp, self.p.y1)
        self.plotlines.trend._plotskip = True
 
    def next(self):
        date = self.data0.datetime.datetime()
        date_timestamp = time.mktime(date.timetuple())
        Y = self.get_y(date_timestamp)
        self.lines.trend[0] = Y
 
        #Check if price has crossed up / down into it.
        if self.data0.high[-1] < Y and self.data0.high[0] > Y:
            self.lines.signal[0] = -1
            return
 
        #Check for cross downs (Into support)
        elif self.data0.low[-1] > Y and self.data0.low[0] < Y:
            self.lines.signal[0] = 1
            return
 
        else:
            self.lines.signal[0] = 0
 
    def get_slope(self, x1,x2,y1,y2):
        m = (y2-y1)/(x2-x1)
        return m
 
    def get_y_intercept(self, m, x1, y1):
        b=y1-m*x1
        return b
 
    def get_y(self,ts):
        Y = self.m * ts + self.B
        return Y

Spiegazione del codice

Si generano due lines per questo indicatore. Una line è la trendline e l’altra line è il segnale. Da notare che la trendline non viene tracciata nel grafico perché in backtrader non è possibile tracciare una line sul grafico principale e un’altra sul grafico secondario. Si deve scegliere l’uno o l’altro. Tecnicamente si potrebbe tracciare entrambi sul grafico secondario, ma risulterebbe un po’ strano poiché la trendline può essere qualsiasi numero (dato che il valore dipende dal prezzo dello strumento) mentre il segnale oscilla solamente tra -1 e 1. Una soluzione alternativa è presentata nella sezione dei risultati. Un’altra cosa da notare sono le date, che devono essere convertite in un timestamp in modo che i calcoli possano essere fatti su un numero e non su un oggetto data. Infine, ci si potrebbe domandare perché si sta costruendo la trendline e richiamando get_y() nel metodo next() invece che nel metodo __init__()? Questo perché backtrader genera un IndexError se si prova ad ottenere le informazioni sulla data all’interno di __init__(). Se qualcuno sa come risolvere il problema, lasciatemi un commento! Probabilmente ho trascurato qualcosa all’interno della documentazione.

I metodi chiave

Se si desidera implementare questo codice in un altro framewoek, i tre metodi fondamentali da implementare sono i seguenti:
    def get_slope(self, x1,x2,y1,y2):
        m = (y2-y1)/(x2-x1)
        return m
 
    def get_y_intercept(self, m, x1, y1):
        b=y1-m*x1
        return b
 
    def get_y(self,ts):
        Y = self.m * ts + self.B
        return Y

Come in precedenza, è necessario utilizzare i timestamp per i parametri x e richiamare le funzioni nel seguente ordine:

  1. get_slope()
  2. get_y_intercept(), perché ha bisogno del valore di m, risultato di get_slope()
  3. get_y, per ottenere il prezzo finale per qualsiasi data e ora.

Risultati

Come accennato in precedenza, se si esegue questo codice così com’è, l’indicatore non traccia automaticamente nessuna trendline sul grafico di output. Poiché l’indicatore è progettato per produrre segnali di acquisto e vendita, la priorità è data alla stampa della line del segnale. Come soluzione alternativa, al fine di creare una certa verifica visiva del corretto funzionamento dell’indicatore, è possibile inizializzare un secondo indicatore all’interno della strategia che traccia una media mobile semplice della trendline, con un periodo pari a 1.
self.sma = bt.indicators.MovingAverageSimple(
         self.ind1.lines.trend, period=1,
         plotmaster=self.data0)
Da notare che, nel codice precedenete, è necessario inserire la riga all’interno del metodo __init__() della strategia, supponendo di aver già inizializzato l’indicatore con il nome ind1 (self.ind1.lines.trend).
Come si può vedere, si ha una trendline che attraversa il grafico e i segnali vengono generati secondo le regole implementate nel codice, come rappresentato nel grafico secondario.