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.

Distribuzione delle chiusure di mercato

Una domanda che mi sono spesso posto è se le chiusure dei titoli seguano una distribuzione normale. Ho fatto alcune prove per verificare se è corretto assumere un andamento normale. Nello studio ho usato l’indice di borsa FTSMIB, ma ripetendo le prove con diversi titoli sembra che il comportamento sia analogo anche per i diversi titoli.

Il codice è scritto per Jupyter, strumento di analisi abbastanza standard, facie da usare e molto diffuso. Primo importo le librerie.

#Import vari
import yfinance as yf
from datetime import datetime
from datetime import timedelta
import scipy.stats as sp
import pylab
import numpy as np
from matplotlib import pyplot as plt

Poi carico i dati da yahoo, poi li normalizzo attraverso un’operazione, anch’essa comune, logaritmo sui dati, e differenza tra i valori logaritmici.

#carico i dati, uso il FTSEMIB
start = '2015-01-01' 
#ticker = "A2A.MI"
ticker = "FTSEMIB.MI"
sp_list = [ticker]
end = '2020-12-31'
print(f'{ticker} Stock download')
spy = yf.download(sp_list, start,end)

log_spy = np.log(spy.Close)
log_return = (np.diff(log_spy ,axis=0))
spy = spy.iloc[1:,:]
spy.loc[:,('log_return')] = log_return
data = spy.log_return.values

E ora i grafici e l’analisi.

Distribuzione normale

Faccio un fittin dei dati con la curva normale.

xmin, xmax = plt.xlim([-0.2,0.2])
x = np.linspace(xmin, xmax, len(data))
print(f'lunghezza dati {len(data)}')
plt.hist(data, bins=100, density=True, alpha=0.5)

r = sp.norm.fit(data)
print(f'fit normale: {r}')
p1 = sp.norm.pdf( x, r[0], r[1])
plt.plot(x, p1, 'r' ,linewidth=2,label='norm Returns')

# Plot the PDF.
title = f"Fitting Distributione Normale {ticker} " 
plt.title(title)
plt.legend()
plt.show()

sp.probplot(data, dist="norm", plot=pylab)
pylab.show()

Il risultato è quello mostrato in figura, il fitting è solamente discreto, si nota un picco nella parte centrale che supera la curva normale e il grafico delle probabilità conferma che le code sono distanti dai valori della distribuzione normale.

Distribuzione t-Student

Il picco centrale suggerisce che una distribuzione t-student con un basso grado di libertà (< 30) possa avere un fitting migliore.

Una distribuzione di t-student sembra in effetti avere un fitting migliore, rimangono delle asimmetrie sulle code e allontanamento dalla norma, con conseguente skewness (asimmetria) e kurtosi.

Approfondimenti da fare: per quali ragioni la distribuzione è t-student?

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