Buy & Hold la stretegia vincente

Ho analizzato il comportamento di un paniere di titoli per vederne il comportamento complessivo nel tempo. In particolare già in passato avevo analizzato la strategia buy & hold verificando che è difficile trovare una strategia di trading migliore di quella B&H.

Con l’occasione ho voluto sperimentare backtesting alternativi rispetto a Backtrader. Credo che Backtrader sia una delle piattaforme migliori sul mercato, in grado di simulare in odo molto fedele il comportamento dei broker e anche di agganciarsi a broker reali per algotrading direttamente a mercato. Per fare però delle semplici simulazioni occorre qualcosa di più snello, una volta verificata la bontà di una strategia attraverso un sistema snello si può implementare su backtrader che aggiunge tutti gli orpelli onerosi quali slippage, tobin tax, commissioni e vincoli su ordini in base alla disponibilità economica.

Per implementare un metodo snello ci sono tre framework che ci aiutano:

  • ta-lib: una collezione di funzione per l’analisi tecnica, comprende anche l’identificazione di candlestick e numerosi indicatori
  • pandas-ta, estende in modo semplice le funzioni di pandas aggiungendo diverse funzioni per applciare sul set di dati gli indicatori ta-lib
  • vector-bt: una libreria semplice di backtesting che consente di definire gli entry e exit point di una strategia. In base a questi elementi

Tornando alla strategia B&H, le prime prove fatte coinvolgono la librerie pandas-ta e, dato l’insieme dei titoli, calcolo in guadagno nel tempo per ciascun titolo. Il codice è molto semplice, per prima cosa carico i dati:

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import pandas_ta as ta
df = pd.DataFrame() # Empty DataFrame
assets =["A2A.MI", "AMP.MI", "ATL.MI", "AZM.MI", "BGN.MI", "BMED.MI", "BAMI.MI", "BPE.MI", "BZU.MI",
"CPR.MI", "CNHI.MI", "DIA.MI", "ENEL.MI", "ENI.MI", "EXO.MI", "RACE.MI", "FBK.MI", "G.MI",
"HER.MI", "IP.MI", "ISP.MI", "INW.MI", "IG.MI", "LDO.MI", "MB.MI", "MONC.MI", "NEXI.MI",
"PIRC.MI", "PST.MI", "PRY.MI", "REC.MI", "SPM.MI", "SRG.MI", "STLA.MI", "STM.MI", "TIT.MI",
"TEN.MI", "TRN.MI", "UCG.MI", "UNI.MI"]
dfs = {}
for name in assets:
dfs[name]=pd.read_json(f"{name}_reg_5-60")
dfs[name].set_index(pd.DatetimeIndex(dfs[name]["Date"]), inplace=True)

I dati lì ho in locale per comodità, ma volendoli caricare con pandas-ta è sufficiente sostituire con questo:

dfs[name]=df.ta.ticker(name)

A questo punto calcolo il guadagno nel tempo per ciascun asset e creo una tabella di sintesi:

assets =["A2A.MI", "AMP.MI", "ATL.MI", "AZM.MI", "BGN.MI", "BMED.MI", "BAMI.MI", "BPE.MI", "BZU.MI", 
      "CPR.MI", "CNHI.MI", "DIA.MI", "ENEL.MI", "ENI.MI", "EXO.MI", "RACE.MI", "FBK.MI", "G.MI", 
      "HER.MI", "IP.MI", "ISP.MI", "INW.MI", "IG.MI", "LDO.MI", "MB.MI", "MONC.MI", "NEXI.MI", 
      "PIRC.MI", "PST.MI", "PRY.MI", "REC.MI", "SPM.MI", "SRG.MI", "STLA.MI", "STM.MI", "TIT.MI", 
      "TEN.MI", "TRN.MI", "UCG.MI", "UNI.MI"]

gains = {}
res = pd.DataFrame(columns=["start","end","initial","final","percret","logret"])
total = 0
for name in assets:
    df =dfs[name].copy()
    df.ta.log_return(cumulative=True, append=True)
    df.ta.percent_return(cumulative=True, append=True)
    gains[name] = df["CUMPCTRET_1"][len(df)-1]
    total +=gains[name]
    print(f"Guadagno {name} dal {df.index[0]:%m/%d/%Y} al {df.index[-1]:%m/%d/%Y} di {gains[name]*100:.02f} %")
    p = pd.DataFrame([[df.index[0],df.index[-1],df["Close"][0],df["Close"][len(df)-1],
                             df["CUMPCTRET_1"][len(df)-1],df["CUMLOGRET_1"][len(df)-1]]],
                            columns=["start","end","initial","final","percret","logret"],
                            index=[name])
    
    res = pd.concat([res,p])

print(f"Guadagno medio {total*100/len(gains)} %")



Il risultato mostra come alcuni titoli hanno performato molto, altri molto male:

Guadagno A2A.MI dal 01/03/2000 al 05/05/2022 di -56.15 %
Guadagno AMP.MI dal 10/01/2001 al 05/05/2022 di 2256.41 %
Guadagno ATL.MI dal 09/22/2003 al 05/05/2022 di 88.79 %
Guadagno AZM.MI dal 07/07/2004 al 10/27/2021 di 634.51 %
Guadagno BGN.MI dal 11/15/2006 al 05/05/2022 di 244.51 %
Guadagno BMED.MI dal 01/03/2000 al 05/05/2022 di -45.43 %
Guadagno BAMI.MI dal 01/03/2000 al 05/05/2022 di -97.02 %
Guadagno BPE.MI dal 01/03/2000 al 05/05/2022 di -88.98 %
Guadagno BZU.MI dal 01/03/2000 al 05/05/2022 di 62.49 %
Guadagno CPR.MI dal 07/06/2001 al 05/05/2022 di 6961.02 %
Guadagno CNHI.MI dal 09/30/2013 al 05/05/2022 di 44.78 %
Guadagno DIA.MI dal 07/19/2007 al 05/05/2022 di 868.90 %
Guadagno ENEL.MI dal 01/03/2000 al 05/05/2022 di 1.35 %
Guadagno ENI.MI dal 01/03/2000 al 05/05/2022 di 28.91 %
Guadagno EXO.MI dal 01/03/2000 al 05/05/2022 di 81.08 %
Guadagno RACE.MI dal 01/04/2016 al 05/05/2022 di 339.43 %
Guadagno FBK.MI dal 07/02/2014 al 05/05/2022 di 223.87 %
Guadagno G.MI dal 01/03/2000 al 05/05/2022 di -36.47 %
Guadagno HER.MI dal 03/28/2003 al 05/05/2022 di 95.84 %
Guadagno IP.MI dal 01/03/2000 al 05/05/2022 di 746.27 %
Guadagno ISP.MI dal 01/03/2000 al 05/05/2022 di -44.43 %
Guadagno INW.MI dal 06/22/2015 al 05/05/2022 di 151.35 %
Guadagno IG.MI dal 11/07/2016 al 05/05/2022 di 54.03 %
Guadagno LDO.MI dal 07/18/2005 al 05/05/2022 di -29.44 %
Guadagno MB.MI dal 01/03/2000 al 05/05/2022 di 8.58 %
Guadagno MONC.MI dal 12/16/2013 al 05/05/2022 di 197.93 %
Guadagno NEXI.MI dal 04/18/2019 al 05/05/2022 di 12.75 %
Guadagno PIRC.MI dal 10/04/2017 al 05/05/2022 di -31.25 %
Guadagno PST.MI dal 10/27/2015 al 05/05/2022 di 36.39 %
Guadagno PRY.MI dal 05/03/2007 al 05/05/2022 di 98.21 %
Guadagno REC.MI dal 01/03/2000 al 05/05/2022 di 4034.14 %
Guadagno SPM.MI dal 01/03/2000 al 05/05/2022 di -75.09 %
Guadagno SRG.MI dal 12/06/2001 al 05/05/2022 di 119.76 %
Guadagno STLA.MI dal 01/03/2000 al 05/05/2022 di 58.00 %
Guadagno STM.MI dal 01/03/2000 al 05/05/2022 di -30.69 %
Guadagno TIT.MI dal 01/03/2000 al 05/05/2022 di -93.51 %
Guadagno TEN.MI dal 12/17/2002 al 05/05/2022 di 704.95 %
Guadagno TRN.MI dal 06/23/2004 al 05/05/2022 di 337.61 %
Guadagno UCG.MI dal 01/03/2000 al 05/05/2022 di -93.24 %
Guadagno UNI.MI dal 01/03/2000 al 05/05/2022 di -90.98 %
Guadagno medio 441.97885234421864 %

Il peggiore è stato BAMI con una perdita del 97.02% mentre il migliore è stato CPR con un guadagno del 6961.02%. Per capire l’entità dei due scostamenti, se avessi investito 10.000€ su BAMI oggi avrei 298€, mentre se avessi investito 10.000€ su CPR oggi avrei 706.102€. Sottolineo la precisazione, l’analisi è semplificata, non si considerano un insieme di fattori, oltre ai costi operativi evidenziati sopra (commissioni, ecc. ) non considero i dividendi distribuiti, gli split, gli aumenti di capitale e tutte quelle operazioni di capitale che portano guadagni o perdite all’azionista.

Il punto da capire è se il risultato di 6961.02% è da considerarsi un anomalia o meno. Vediamo altri outperformer come AMP e REC, anche questi sono un’eccezione. Il primo passo per affrontare quest’analisi è il passaggio alla scala logaritmica. Un titolo che perde ogni giorno l’ 1% perde in termini monetari assoluti sempre meno, un titolo che guadagna 1% ogni giorno guadagna in termini monetari assoluti sempre di più. Rispetto alla scala logaritmica i guadagni (e le perdite) seguono un andamento lineare. I risultati visti sopra, calcolati secondo una scala logaritmica diventano:

Guadagno A2A.MI dal 01/03/2000 al 05/05/2022 di -0.82 log
Guadagno AMP.MI dal 10/01/2001 al 05/05/2022 di 3.16 log
Guadagno ATL.MI dal 09/22/2003 al 05/05/2022 di 0.64 log
Guadagno AZM.MI dal 07/07/2004 al 10/27/2021 di 1.99 log
Guadagno BGN.MI dal 11/15/2006 al 05/05/2022 di 1.24 log
Guadagno BMED.MI dal 01/03/2000 al 05/05/2022 di -0.61 log
Guadagno BAMI.MI dal 01/03/2000 al 05/05/2022 di -3.51 log
Guadagno BPE.MI dal 01/03/2000 al 05/05/2022 di -2.21 log
Guadagno BZU.MI dal 01/03/2000 al 05/05/2022 di 0.49 log
Guadagno CPR.MI dal 07/06/2001 al 05/05/2022 di 4.26 log
Guadagno CNHI.MI dal 09/30/2013 al 05/05/2022 di 0.37 log
Guadagno DIA.MI dal 07/19/2007 al 05/05/2022 di 2.27 log
Guadagno ENEL.MI dal 01/03/2000 al 05/05/2022 di 0.01 log
Guadagno ENI.MI dal 01/03/2000 al 05/05/2022 di 0.25 log
Guadagno EXO.MI dal 01/03/2000 al 05/05/2022 di 0.59 log
Guadagno RACE.MI dal 01/04/2016 al 05/05/2022 di 1.48 log
Guadagno FBK.MI dal 07/02/2014 al 05/05/2022 di 1.18 log
Guadagno G.MI dal 01/03/2000 al 05/05/2022 di -0.45 log
Guadagno HER.MI dal 03/28/2003 al 05/05/2022 di 0.67 log
Guadagno IP.MI dal 01/03/2000 al 05/05/2022 di 2.14 log
Guadagno ISP.MI dal 01/03/2000 al 05/05/2022 di -0.59 log
Guadagno INW.MI dal 06/22/2015 al 05/05/2022 di 0.92 log
Guadagno IG.MI dal 11/07/2016 al 05/05/2022 di 0.43 log
Guadagno LDO.MI dal 07/18/2005 al 05/05/2022 di -0.35 log
Guadagno MB.MI dal 01/03/2000 al 05/05/2022 di 0.08 log
Guadagno MONC.MI dal 12/16/2013 al 05/05/2022 di 1.09 log
Guadagno NEXI.MI dal 04/18/2019 al 05/05/2022 di 0.12 log
Guadagno PIRC.MI dal 10/04/2017 al 05/05/2022 di -0.37 log
Guadagno PST.MI dal 10/27/2015 al 05/05/2022 di 0.31 log
Guadagno PRY.MI dal 05/03/2007 al 05/05/2022 di 0.68 log
Guadagno REC.MI dal 01/03/2000 al 05/05/2022 di 3.72 log
Guadagno SPM.MI dal 01/03/2000 al 05/05/2022 di -1.39 log
Guadagno SRG.MI dal 12/06/2001 al 05/05/2022 di 0.79 log
Guadagno STLA.MI dal 01/03/2000 al 05/05/2022 di 0.46 log
Guadagno STM.MI dal 01/03/2000 al 05/05/2022 di -0.37 log
Guadagno TIT.MI dal 01/03/2000 al 05/05/2022 di -2.73 log
Guadagno TEN.MI dal 12/17/2002 al 05/05/2022 di 2.09 log
Guadagno TRN.MI dal 06/23/2004 al 05/05/2022 di 1.48 log
Guadagno UCG.MI dal 01/03/2000 al 05/05/2022 di -2.69 log
Guadagno UNI.MI dal 01/03/2000 al 05/05/2022 di -2.41 log

Il grafico dei guadagni percentuali e logaritmici sono i seguenti:

Osservando il grafico sulle % sembra che i picchi siano delle anomalie, mentre rappresentando i medesimi dati in scala logaritmica sembrano coerenti e seguire un moto Browniano. I dati sono troppo esigui per rappresentarli con un’istogramma sensato, ma facendolo lo stesso otteniamo i seguenti diagrammi:

Istogramma dei guadagni percentuali
Istogramma dei guadagni logaritmici

A occhio, ma servirebbe un matematico, la curva dei guadagni sembra seguire una distribuzione di chi-quadro, mentre la distribuzione dei guadagni logaritmici sembra seguire una distribuzione normale. Ipotesi da approfondire con un set di dati più ampio.

Come ultima simulazione tolgo tutti i titoli che hanno un valore logaritmico elevato fino a ottenere una media di valore logaritmico inferiore a zero. In altre parole sommando i logaritmi di guadagni e perdite prevalgono le perdite. Calcolando il guadagno % otteniamo un guadagno (seppure piccolo) positivo e come si vede il grafico del guadagno logaritmico è stato privato della parte alta con un significativo sbilanciamento verso i numeri negativi.

Guadagno medio -0.0327 log
Guadagno medio 80.35%

In conclusione:

  • Se ci aspettiamo che i guadagni logaritmici seguano una distribuzione normale, il mercato premia sempre un investimento a lungo termine diversificato su un paniere ampio di azioni (B&H)
  • L’andamento nel lungo periodo delle azioni è legato all’analisi fondamentale e alle scelte strategiche operate dalle aziende

Tuning rete neurale

Ho iniziato a sperimentare le reti neurali con il framework pyhton keras e dopo le prime prove mi sono reso conto che l’attività di tuning di una rete neurale è molto delicata.

Quanti nodi servono? Quanti livelli? I batch quanto devono essere grossi? E sopratutto quante epoche di trainng servono?

Come prima attività di tuning mi sono concentrato sulle epoche, un numero eccessivo di epoche porta a un overfit della rete, mentre un numero basso porta a una rete troppo poco addestrata. L’idea che mi sono è che allenare una rete neurale è come allenare un pilota su un circuito di Formula 1. Il pilota sale sulla macchina e inizia a guidare a ogni giro migliora le proprie prestazioni, più giri fa e più migliora. Se però il pilota si allena troppo sul medesimo circuito lo impara a memoria e quando cambia circuito le sue prestazioni saranno scarse. L’obiettivo è quindi allenare il pilota senza fargli imparare a memoria il tracciato. Con l’esempio è evidente che se il pilota si allena su un certo tipo di pista e la pista su cui è chiamato a misurarsi ha caratteristiche diverse i risultati del pilota saranno pessimi. Allenare un pilota su un circuito di Formula 1 e farlo gareggiare su un circuito da rally porterà a pessimi risultati.

Per valutare la bontà dell’allenamento utilizzo l’errore quadratico medio tra il risultato del set di dati di test e il risultato stimato dalla rete neurale. Alleno il mio “pilota” ossia la rete neurale facendogli fare un certo numero di giri di allenamento (epoche) sulla pista di allenamento (training set) e poi lo porto sulla pista di test (test set) per verificare quanto sono buone le sue prestazioni. Ripeto l’esperimento 30 volte a parità di numero di giri di test (epoche) e poi faccio variare il numero di giri (epoche). Eseguire l’esperimento 30 volte serve per avere un campione sufficiente per una statistica di base, ogni esperimento produce pesi diversi e quindi reti neurali che si comportano in modo diverso.

L’asset su cui ho allenato la mia rete neurale è l’azione Telecom, la speranza è di ottenere risultati positivi su un asset in forte calo. Il codice del ciclo di test sulle epoche è il seguente

from pandas import DataFrame
from math import sqrt
import matplotlib
# be able to save images on server
#matplotlib.use('Agg')
from matplotlib import pyplot
def experiment(repeats=5, epochs=2):
    error_scores = list()
    for r in range(repeats):
        lstm = testasset("TIT", epochs=epochs)
        rmse = sqrt(lstm.stats())
        print('%d) Test RMSE: %.3f' % (r+1, rmse))
        error_scores.append(rmse)
    return error_scores
    
repeats = 30
results = DataFrame()
epochs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 50]
for e in epochs:
    results[str(e)] = experiment(repeats, e)
print(results.describe())
results.boxplot()
pyplot.savefig('boxplot_epochs.png')

Per farlo girare c’è voluto diverso tempo, sopratutto per l’apprendimento con 20 e 50 epoche, complessivamente l’esecuzione è durata poco più di due giorni, qui la tabelle dei tempi di esecuzione

	ore	gg
0	0	0
1	0.4375	0.018229167
2	0.875	0.036458333
3	1.3125	0.0546875
4	1.75	0.072916667
5	2.1875	0.091145833
6	2.625	0.109375
7	3.0625	0.127604167
8	3.5	0.145833333
9	3.9375	0.1640625
10	4.375	0.182291667
20	8.75	0.364583333
50	21.875	0.911458333

Nella seguente tabella sono riportati i dati statistici delle misurazioni di esecuzione con le diverse epoche:

              1          2          3          4          5          6  \
count  30.000000  30.000000  30.000000  30.000000  30.000000  30.000000   
mean    0.038587   0.041846   0.034651   0.035500   0.032625   0.032489   
std     0.014706   0.013233   0.009586   0.010110   0.009148   0.009238   
min     0.020083   0.019384   0.022519   0.022931   0.021452   0.021175   
25%     0.026908   0.034563   0.027189   0.027118   0.025462   0.024630   
50%     0.034167   0.039058   0.031203   0.033280   0.031137   0.029748   
75%     0.046000   0.049699   0.040562   0.042590   0.038665   0.036789   
max     0.075115   0.075003   0.060300   0.062060   0.055277   0.053957   

               7          8          9         10         20         50  
count  30.000000  30.000000  30.000000  30.000000  30.000000  30.000000  
mean    0.034010   0.027564   0.029000   0.028845   0.020712   0.018076  
std     0.012303   0.008566   0.010543   0.010209   0.007081   0.006426  
min     0.019001   0.018077   0.017641   0.016518   0.011808   0.009376  
25%     0.023905   0.020850   0.020434   0.021110   0.015664   0.012611  
50%     0.030288   0.024321   0.027368   0.027386   0.018326   0.018031  
75%     0.043142   0.032642   0.033461   0.031322   0.024690   0.022165  
max     0.062863   0.055480   0.063527   0.058419   0.037421   0.031675  

Il set di dati di addestramento non era particolarmente corposo, ca 4700 campioni. Gli stessi dati rappresentati con un grafico a box mostra l’andamento decrescente dell’errore quadratico medio, segno che l’addestramento è efficace e non siamo in una situazione di overfit.

Un ultimo confronto utile è la stima dei valori fatta dale due reti, addestrate agli estremi, 1 epoca e 50 epoche.

Addestramento con 1 epoca
Addestramento con 50 epoche

Il confronto tra i due grafici mette in evidenza il comportamento del “pilota” addestrato bene, rispetto al “pilota” principiante che fatica a star dietro ai rapidi cambi di direzione. La lunghezza del data set coincide anch’esso sull numero di epoche necessarie a un buon addestramento. Tornando alla metafora del pilota, se la pista è lunga, con molti cambi di direzione, accelerazioni, frenate, paraboliche e altro, il pilota riesce ad allenarsi con pochi giri. Se la pista è breve il pilota dovrà ripetere la pista diverse volte per avere un buon allenamento. Maggiore è il numero di epoche necessarie all’addestramento, maggiore è la probabilità di overfit. Benché i risultati non evidenzino l’overfit con il degrado delle performance, dallo studio del grafico con 50 epoche, l’overfit risulta evidente. Lo stesso grafico con 10 epoche risulta più morbido e accompagna bene la curva del titolo.

Addestramento con 10 epoche
Addestramento con 20 epoche

Ciò che però sembra aver appreso la rete neurale è di riproporre in modo molto fedele i movimenti che il titolo ha fatto il giorno precedente. La rete non sembra in grado di anticipare i movimenti del titolo con le sole informazioni disponibili e la miglio strategia che riesce ad attuare è d’inseguimento. Ai fini dello studio di tecniche di AI l’esercizio è stato utile, ai fini di algotrading un po’ meno.

Prove di trailingstop

Uno degli stop spesso suggerito è il Trailing-Stop, ossia uno stop che si attiva se il prezzo scende di una certa % (o tick, in funzione dello strumento) rispetto all’ultimo massimo dall’attivazione del trailing stop.

Non l’ho usato spesso nei backtesting perché la sua implementazione nelle strategie richiede uno sforzo implementativo maggiore. Ho deciso di fare alcune prove con una strategia, la solita che uso basata sulle mie candele. Le prove che faccio sono: senza stop, 5%, 10%, 15%, 20%, 25%, 30%, 35%, 40%.

La strategia ha già uno stop al 9%, gli asset sono le azioni del MIB, il benchmark è la strategia buy&hold sui medesimi titoli, slipage e commissioni incluse. La strategia che uso ha un buon (discreto) punto di ingresso, ma una pessima uscita. L’entrata anticipa spesso dei missili con alti profitti, ma spesso l’uscita cattura la parabola discendente molto in ritardo, posizioni che potenzialmente guadagnano il 50% risultano perdenti perché chiuse troppo tardi. E’ la situazione ideale per l’uso di Trailingstop. Il backtesting viene fatto con backtrader.

Senza Trailingstop

Trailingstop 5%

Trailingstop 10%

Trailingstop 15%

Trailingstop 20%

Trailingstop 25%

trailingstop 30%

Trailingstop 35%

Trailingstop 40%

Risultati

Nei primi 2 grafici, il trailingstop risulta decisamente penalizzante. La strategia viene ‘soffocata’ dagli stop, che non consentono la normale fluttuazione dei titoli all’interno del trend. Anche sui piccoli rintracciamenti o in presenza di spike, viene chiusa la posizione. Visivamente sembra che l’andamento della strategia sia inversamente correlato rispetto al benchmark, aspetto che andrebbe approfondito.

A partire del 20% di trailingstop sembrano portare un benefico, in particolare lo stop al 35% ha un indice di sharpe 1.41 contro l’indice 1.16 della strategia senza trailingstop.

superata la soglia del 30% l’effetto del trailingstop si smorza, segno che è troppo lasco e incide poco sulla strategia.

Il beneficio maggiore sempre esserci con lo stop al 25%, la curva è più fluida e meno spigolosa, ma anche più piatta. Togliendo la parte iniziale con la rampa molto pronunciata nel periodo 2013-2014, si ottengono i grafici sottostanti che mettono a fuoco da magio 2014 fino al 2020. L’indice di sharpe, che prima era superiore con il trailing stop, ora è più basso. L’uso dello stop rende in ogni caso la strategia meno volatile.

Un test che si potrebbe fare è l’applicazione di un trailing stop considerato solo le chiusure, in questo caso non sarebbe da considerare uno stop, ma una condizione d’uscita. In ogni caso questo tipo di uscita eliminerebbe il problema degli spike ( legati hunter stop intraday), rimarrebbe il tema dei rintracciamenti.

NB: ogni strategia è diversa e l’effetto di un trailingstop può essere completamente diversi su un’altra strategia.

Focus trailingstop 25% 05-2014

Focus senza 05-2014

Studio della regressione lineare applicata ai titoli

Tra i diversi indicatori non si trovano spesso indicatori basati sulla regressione lineare, che a mio avviso ha alcuni aspetti interessanti di applicazione.

Dati lineari o logaritmici

La regressione lineare può essere applicata per interpolare i dati di chiusura del mercato e individuare un trend di mercato. L’approccio può essere considerato simile a quelli di una media mobile, con il vantaggio di poter calcolare la pendenza. La prima domanda è se utilizzare i dati lineari o logaritmici. Il dato lineare ha come effetto di essere poco rappresentativo nel momento in cui ci sono grosse variazioni di prezzo. Anche la regressione lineare è sensibile a questa distorsione.

Vediamo a confronto la regressione lineare applicata al medesimo set di dati (TEN ; 2011-10-01 ; 2012-01-01 ) quindi 3 mesi.

Linreg su logaritmo di chiusura con scatter: LinregressResult(slope=0.00877559504812757, intercept=3.338707364650828, rvalue=0.9162344705541173, pvalue=2.5800716530357888e-26, stderr=0.0004873389193687765, intercept_stderr=0.017796213444974496)
Linreg su chiusura con scatter: LinregressResult(slope=0.07284649435853784, intercept=10.049163569395358, rvalue=0.9224407497042941, pvalue=2.605894502505374e-27, stderr=0.0038727335236461253, intercept_stderr=0.14142107199561024)

I due grafici sono molto simili, ma un occhio attento non sfuggono diverse differenze, come ad esempio l’ultimo gruppo in alto a destra di punti, che risulta molto più vicino alla retta di regressione lineare. Considerando che i titoli possono avere scostamenti considerevoli utilizzerò la regressione lineare su dati esponenziali.

Curva di regressione

Utilizzando la regressione lineare, si può stimare il valore punto successivo della sequenza. Nel caso di scala linerare, il punto futuro è semplicemente y = intercept + slope * (period +1), dove period è il perdo di osservazione del campione. Nel caso di scala logaritmica il punto futuro può essere stimato con y = e ^ intercept * e ^ slope ^ (period +1). Rappresentando graficamente i dati si può verificare la bontà del valore stimato.

In rosso la SMA(20) mentre in Blu è rappresetnata la regressione su scala esponneziale con periodo 20 e in verde la regressione con scala lineare e period (20)

Come per la media mobile, anche la regressione mostra un ritardo (come tutti gli indicatori trend following) , coglie con maggior tempestività il cambio di direzione, ma sembra con un ritardo. L’aspetto interessante è che anche con periodi di osservazione ampi mantiene una buona aderenza con la curva del titolo.

In rosso la SMA(50) mentre in Blu è rappresentata la regressione su scala esponenziale con periodo 50 e in verde la regressione con scala lineare e period (50)

La SMA è decisamente più sensibile all’ampliamento del periodo di osservazione rispetto alla regressione lineare.

Slope/ pendenza ed errore della regressione

La regressione lineare restituisce i seguenti valori: slope, intercept, rvalue, pvalue, sterr = linregress(x, returns). I primi due sono quelli che ho utilizzato per disegnare la curva, il primo stabilisce la pendenza della regressione, il secondo il punto del intercetta sull’asse y. La pendenza della regressione lineare non va confusa con la pendenza della curva rappresenta. Per convertire il coefficiente di pendenza in un incremento % giornaliero occorre tornare nel campo lineare da quello esponenziale. Data la pendenza, ,l’incremento % è dato da % = exp(slope)-1. Il grafico mostra il risultato la curva della pendenza trasformata in %.

Curva rossa WAM(20), curva blu SMA(20), curva verde REG(20), grafico inferiore incremento %

Un aspetto interessante nel grafico soprastante è che WMA, SMA, e REG si intersecano nel punto in cui l’incremento % di REG è pari a 0. Infatti per costruzione quando la pendenza della regressione è pari a 0, il valore successivo è apri all’intercetta, che a sua volta è uguale alla media dei punti, che a sua volta è uguale alla media mobile ponderata. Da osservare che la curva, data dalla formula vista in precedenza, cambia direzione prima che cambi la pendenza. Infatti via via che arrivano nuove osservazioni nella nuova direzione. la pendenza inizia a variare, ma lo fa anche l’intercetta. Il coefficiente di correlazione si comporta in modo simile alla pendenza delle retta di regressione. Nei punti in cui la pendenza cambia segno la correlazione è pari a 0.

Curva rossa WAM(20), curva blu SMA(20), curva verde REG(20), grafico inferiore indice di correlazione
TEN ; 2011-03-15 ; 2011-06-01 LinregressResult(slope=0.0001654577957133202, intercept=4.06461066740901, rvalue=0.06658391304605706, pvalue=0.6323851192867931, stderr=0.00034383604540743945, intercept_stderr=0.010570745127746129)

Il grafico a alato mostra il momento di cambio direzione su Tenaris nel periodo, si possono osservare un insieme di aumenti di prezzo, fortemente correlati, seguiti da un insieme di punti non correlati rispetto a quelli iniziali. Sia la pendenza delle rette, che il coefficiente di correlazione si stanno avvicinando allo 0.

Se la correlazione è minima nei punti in cui la retta cambia pendenza, il valore p diventa massimo. Rappresentando in un grafico il valore p-val, otteniamo evidenza della correlazione inversa tra r e p-val.

L’ultimo grafico riporta l’errore standard associato alla curva generata dalla regressione.

Conclusione

Utilizzare la regressione lineare al posto della media può avere un’applicazione pratica concreta per diversi fattori. Il più significativo è a mio avviso la possibilità di confrontare i valori di due diversi strumenti, questo può essere fatto tramite la pendenza della linea di regressione su scala logaritmica. La pendenza stessa può essere utilizzata come indicatore di momento, in modo analogo all’RSI. Potrebbe essere interessante applicare alcuni indicatori come MACD alla regressione lineare. O anche l’incrocio di medie mobili sfruttando la regressione lineare.

Trading stategy Smash Day

La strategia smash day è stata ideata da Larry R. Williams, un trader professionista che ha guadagnato 11.300% nell’arco di un anno durante una gara. Sul sito di Oxford Capital Strategies Ltd ho trovato, i dettagli della strategia e una simulazione su 48 future nell’arco di 32 anni, i risultati delle simulazioni sono stimolanti. Le simulazioni che farò io saranno sul solito set di azioni FTSEMIB nel decennio 2010-2020.

Trovo la strategia interessante, non solo perché ideata da un trader in grado di guadagnare così tanto in così poco tempo, ma anche perché è una strategia basata su pattern e quindi non trend following. La stragia è ben descritta sul sito Oxfordstrat, e mi limito a provare a descriverne la logica. La strategia è simmetrica, ossia prevede ingressi long e short, in modo completamente simmetrico (basta sostituire massimi con minimi e segni). Qui di seguito una simulazione con A2A.

Simulazione 10Y sul titolo A2A

Nel caso di entrate long la condizione di ingresso è Long Trades: Close[i − 1] < Low[i − 2], ossia la barra chiude sotto il minimo della barra precedente, se si verifica questa condizione significa c’è stata una accelerazione del movimento verso il basse. Se questo succede possono succedere due cose, che poi sono le due situazioni che si verificano sempre. O il mercato sale o il mercato scende. Se sale il mercato, l’ipotesi sottostante la strategia, è che ci sia un’inversione e che l’accelerazione sia il preludio del cambio di direzione. Per capire se si verifica un cambio di direzione l’approccio usato è di inserire un buy stop sul massimo della candela “smash” . Il grafico qui riportato mostra come lo scenario di applicazione. La strategia prevede anche un filtro in ingresso, il Close[i − 1] > Close[i − Trend_Index], questo, in modo semplificato, significa che il marcato, rispetto al tempo trend_index, si è spostato verso l’alto. Possiamo vedere lo smash day come il momento di rintracciamento, ossia un minimo relativo al periodo in oggetto, incluso tra il tempo i-1 e i-trend_index, all’interno di un ampio movimento crescita.

Focus entrata smash day su posizione long – si evidenza che l’ipotesi sia di un rintracciamento

La strategia smash day scommette sul proseguimento del trend dato da Close[i − 1] > Close[i − Trend_Index], es entra quando c’è un rintracciamento che si ipotizza stia terminando perché c’è stata un’accelerazione di movimento in discesa dato da Close[i − 1] < Low[i − 2]. In questo scenario lo stop subito sotto il Low[i-1] è quasi doveroso, e in effeti fa parte delle strategia. Sia Williams che Oxford Capital Strategies Ltd hanno applicato la strategia al mercato dei future, in questo caso lo stop si posiziona un tick sotto il minimo, nel caso delle azioni questa possibilità non c’è, quindi posiziono il minimo -1% sotto il Low[i-1] nel caso di long e +1% sopra high[i-1] nel caso posizione short.

Nelle diverse simulazioni che ho fatto trovo coerenza con i dati di Oxfordstart, ossia che la strategia performa meglio con trend_index=80 e time_index=40. Il risultato non è però quello atteso, anche togliendo le commissioni il risultato è deludente. Dopo un po’ di prove mi sono accorto che la condizione di uscita veloce +-1% è troppo stretta e la strategia rimane soffocata.

Simulazione 10Y Smashday vs B&H su FTSEMIB

Anche provando diverse combinazioni il risultato, in assenza di commissioni, è deludente con uno sharpe <0.2 (da panico). Risultati simili sono stati ottenuti da Wpatte15 su tradingview. I dati non sono paragonabili con quelli di Oxfordstart per le evidenti differenze nella simulazione (10Y vs 32Y, 40 azioni vs 42 future). Qui di seguito il codice usato, come sempre la piattaforma du backtesting che uso è backtrader, opportunamente personalizzata.

class SmashDay(mts.MultiTickerStrategy):
'''Trade Setup: Long trades: Close[i − 1] < Low[i − 2]
             Short Trades: Close[i − 1] > High[i − 2].
Filter:    Long Trades: Close[i − 1] > Close[i − Trend_Index]. Trend_Index
        Short Trades: Close[i − 1] < Close[i − Trend_Index].
Trade Entry: Long Trades: A buy stop is placed one tick above the High[i − 1]
             Short Trades: A sell stop is placed one tick below the Low[i − 1]

Time Exit: nth day at the close, n = Time_Index. n=40
Quick Exit: Long Trades: A sell stop is placed one tick below the min(Low[k − 1], Low[k]).  Index: k ~ Entry Bar.
Stop Loss Exit: ATR_Length = 20; ATR_Stop = 6;
'''
params = dict(
    trend_index=80, #Trend_Index = [4, 80], Step = 2;
    time_index=40, #Time_Index = [1, 40], Step = 1;
    atr_lenght=20, #ATR_Length = 20;
    atr_stop=6, #ATR_Stop = 6;
) 
def __init__(self):
    super().__init__()
    self.dts = dict()
    self.stops = dict()
    self.exits = dict()
    self.inds = dict()
    #Carico i dati sharpe

    for i, d in enumerate(self.stocks):
        self.inds[d] = dict()
        self.inds[d]['atr'] = bt.indicators.ATR(d,period=self.p.atr_lenght)
        self.inds[d]['high'] = bt.indicators.Highest(d,period=self.p.trend_index)
        self.inds[d]['low'] = bt.indicators.Lowest(d,period=self.p.trend_index)                    


def next(self):
    for i, d in enumerate(self.stocks):
        dt, dn = self.datetime.date(), d._name

        if dn in self.dts:
            dat = self.dts[dn]
        else:
            dat = 0

        dt, dn = self.datetime.date(), d._name
        pos = self.getposition(d)
        if len(d)<self.p.time_index+1+self.p.trend_index:
            continue

        # prima gestisco il setup long/short se non sono già in posizione
        if not pos:
            #imposto la data operazione

            if d.close[0]<d.low[-1]: #setup long
                if d.close[0] > d.close[-self.p.trend_index]: #filtro long
                    mainorder = self.buy(d,exectype=bt.Order.Stop,price=d.high[0],valid=datetime.timedelta(days=1), transmit=False)
                    if not mainorder == None:
                        sexit = self.sell(d,price=d.low[0]-self.inds[d]['atr']*2, 
                                             size=mainorder.size, exectype=bt.Order.Stop,transmit=False, parent=mainorder, info={"mainorder":""})
                        stoploss = self.sell(d,price=d.high[0]-self.inds[d]['atr']*self.p.atr_stop, 
                                             size=mainorder.size, exectype=bt.Order.Stop,transmit=True, parent=mainorder, info={"mainorder":""})
                        self.stops[d]=stoploss
                        self.exits[d]=sexit
                        self.dts[d._name]=d.datetime[0]

            elif d.close[0]>d.high[-1]: #setup short
                if d.close[0] < d.close[-self.p.trend_index]: #filtro long
                    mainorder = self.sell(d,exectype=bt.Order.Stop,price=d.low[0],valid=datetime.timedelta(days=1), transmit=False)
                    if not mainorder == None:
                        sexit = self.buy(d,price=d.high[0]+self.inds[d]['atr']*2, 
                                             size=mainorder.size, exectype=bt.Order.Stop,transmit=False, parent=mainorder, info={"mainorder":""})
                        stoploss = self.buy(d,price=d.low[0]+self.inds[d]['atr']*self.p.atr_stop, 
                                             size=mainorder.size, exectype=bt.Order.Stop,transmit=True, parent=mainorder)
                        self.stops[d]=stoploss
                        self.exits[d]=sexit
                        self.dts[d._name]=d.datetime[0]
        else:
            if d.datetime[-self.p.time_index]>dat:
                self.close(d)
                if d in self.stops:
                    self.cancel(self.stops[d])
                    del self.stops[d]
                if d in self.exits:
                    self.cancel(self.exits[d])
                    del self.exits[d]
WP to LinkedIn Auto Publish Powered By : XYZScripts.com