Strategia di Forecasting sul S&P500, backtesting con Python e Pandas

Recentemente su DataTrading abbiamo introdotto il machine learning, il forecasting e la progettazione e l’implementazione del backtesting di una strategia. In questo articolo si combinano tutti questi strumenti al fine di testare un algoritmo di previsione finanziaria per l’indice azionario statunitense S&P500 tramite lo strumento l’ETF (SPY).

Questo articolo si basa per la maggior parte sul software che abbiamo già sviluppato negli articoli menzionati sopra, incluso un motore di backtesting orientato agli oggetti e il generatore di segnali. La  programmazione orientata agli oggetti permette Permette di estendere una classe, facendole ereditare le proprietà di un’altra classe e ridefinendone altri (overriding). L’ereditarietà ci permette di vedere le relazioni di parentela tra classi che ereditano dalla stessa superclasse, come un albero radicato.

Inoltre le librerie Python come matplotlib, pandas e scikit-learn riducono la necessità di scrivere codice da zero o effettuare nuove implementazioni di algoritmi già noti e ampiamente testati.

La Strategia di Forecasting

Questa strategia di previsione si basa su una tecnica di apprendimento automatico nota come Analisi Discriminante Quadratica, che è strettamente correlata all’Analisi Discriminante Lineare. Entrambi questi modelli sono descritti nell’articolo relativo alla previsione delle serie temporali finanziarie.

Il forecaster utilizza i dati dei deu giorni precedenti come un set di fattori per prevedere la direzione odierna del mercato azionario. Se la probabilità che il giorno sia “Up” è maggiore del 50%, la strategia acquista 500 shares dell’ETF SPY e vende a fine della giornata, mentre se la probabilità di un giorno “Down” è maggiore del 50%, la strategia vende 500 azioni dell’ETF SPY e poi riacquista alla chiusura. Quindi è un semplice esempio di una strategia di trading intraday.

Nota che questa non è una strategia di trading particolarmente realistica! È improbabile che riusciremo mai a raggiungere un prezzo di apertura o di chiusura a causa di molti fattori quali l’eccessiva volatilità di apertura, il routing degli ordini da parte dell’intermediario e le potenziali criticità di liquidità durante l’apertura / chiusura. Inoltre non abbiamo incluso i costi di transazione. Questi sarebbero probabilmente una percentuale sostanziale dei rendimenti in quanto ci sono operazioni di apertura e chiusura posizioni ogni giorno. Pertanto, il nostro forecaster deve essere relativamente preciso nel prevedere i rendimenti giornalieri, altrimenti i costi di transazione mangeranno tutti i nostri profitti.

Implementazione

Come per gli altri esempi relativi a Python/Pandas, si usano le seguenti librerie:
  • Python
  • NumPy
  • Pandas
  • Matplotlib
  • Scikit-learn
L’implementazione di snp_forecast.py prevede di utilizzare backtest.py, descritto in questo tutorial. Inoltre è necessario importare forecast.py (che contiene principalmente la funzione create_lagged_series), implementato in questo precedente tutorial. Il primo passo è importare i necessari moduli ed oggetti:
            # snp_forecast.py

import datetime
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import sklearn

import pandas_datareader as pdr
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis as QDA

from backtest.backtest import Strategy, Portfolio
from model.forecast import create_lagged_series
        

Una volta incluse tutte le librerie e i moduli pertinenti, è tempo di creare una sottoclasse della classe base astratta Strategy, come abbiamo fatto in precedenti tutorial. SNPForecastingStrategy è progettata per implementare un Analizzatore Discriminante Quadratico per l’indice azionario S&P500, come metodo per predire il suo valore futuro. L’addestramento del modello viene eseguito tramite il metodo fit_model, mentre i segnali effettivi vengono generati dal metodo generate_signals. Questo corrisponde all’interfaccia di una classe Strategy.

I dettagli su come funziona un analizzatore discriminante quadratico, così come la seguente l’implementazione in Python, sono descritti in dettaglio nel precedente articolo relativo alla previsione delle serie temporali finanziarie. I commenti nel seguente codice sorgente descrivono ampiamente le funzionalità del programma:

            # snp_forecast.py

class SNPForecastingStrategy(Strategy):
    """
    Richiede:
    symbol - simbolo di un'azione per il quale applicare la strategia.
    bars - DataFrame delle barre del simbolo precedente.
    """

    def __init__(self, symbol, bars):
        self.symbol = symbol
        self.bars = bars
        self.create_periods()
        self.fit_model()

    def create_periods(self):
        """Crea i periodi di training/test."""
        self.start_train = datetime.datetime(2001,1,10)
        self.start_test = datetime.datetime(2005,1,1)
        self.end_period = datetime.datetime(2005,12,31)

    def fit_model(self):
        """Applica il Quadratic Discriminant Analyser al indice
        del mercato azionario US (^GPSC in Yahoo).
        """
        # Crea la serie ritardata dell'indice S&P500 del mercato azionario US
        snpret = create_lagged_series(self.symbol, self.start_train,
                                      self.end_period, lags=5)

        # Use i rendimenti dei 2 giorni precedenti come
        # valori di predizione, con la direzione come risposta
        X = snpret[["Lag1","Lag2"]]
        y = snpret["Direction"]

        # Crea i dataset di training e di test
        X_train = X[X.index < self.start_test]
        y_train = y[y.index < self.start_test]

        # Crea i fattori di predizioni per usare
        # la direzione predetta
        self.predictors = X[X.index >= self.start_test]

        # Crea il modello di Quadratic Discriminant Analysis
        # e la strategia previsionale
        self.model = QDA()
        self.model.fit(X_train, y_train)

    def generate_signals(self):
        """Restituisce il DataFrame dei simboli che contiene i segnali
        per andare long, short o flat (1, -1 or 0).
        """
        signals = pd.DataFrame(index=self.bars.index)
        signals['signal'] = 0.0

        # Predizione del periodo successivo con il modello QDA
        signals['signal'] = self.model.predict(self.predictors)

        # Rimuove i primi 5 segnali per eliminare gli elementi
        # NaN nel DataFrame dei segnali
        signals['signal'][0:5] = 0.0
        signals['positions'] = signals['signal'].diff()

        return signals
        

Ora che il motore di previsione è in grado di produrre i segnali, è necessario creare MarketIntradayPortfolio. Questo oggetto si differenzia dall’esempio fornito nell’articolo “Backtesting di una Strategia di Moving Average Crossover in Python con Pandas” in quanto svolge operazioni su base intraday.

Il portafoglio è progettato per “andare long” (acquistare) 500 azioni di SPY al prezzo di apertura se il segnale indica che si verificherà un giorno UP e poi vendere alla chiusura. Viceversa, il portafoglio è progettato per “andare short” (vendere) 500 azioni di SPY se il segnale indica che si verificherà un giorno DOWN e successivamente chiudere la posizione (riacquistare) al prezzo di chiusura.

Per raggiungere questo obiettivo, è necessario calcolare ogni giorno la differenza tra i prezzi di apertura del mercato aperto e i prezzi di chiusura del mercato, determinando il calcolo del profitto giornaliero sulle 500 azioni acquistate o vendute. Questo comporta quindi la costruzione di una curva equity formata dalla somma cumulata dei profitti/perditi per ogni giorno. Ha anche il vantaggio di poter facilmente calcolare le statistiche relative ai profitti / perdite di ogni giorno.

Di seguito il codice per la classe MarketIntradayPortfolio:

            # snp_forecast.py
class MarketIntradayPortfolio(Portfolio):
    """
    Acquista o vende 500 azioni di un'asset al prezzo di apertura di
    ogni barra, a seconda della direzione della previsione, chiude
    il trade alla chiusura della barra.

    Richiede:
    symbol - Un simbolo azionario che costituisce la base del portafoglio.
    bars - Un DataFrame di barre per un set di simboli.
    signals - Un DataFrame panda di segnali (1, 0, -1) per ogni simbolo.
    initial_capital - L'importo in contanti all'inizio del portafoglio.
    """

    def __init__(self, symbol, bars, signals, initial_capital=100000.0):
        self.symbol = symbol
        self.bars = bars
        self.signals = signals
        self.initial_capital = float(initial_capital)
        self.positions = self.generate_positions()

    def generate_positions(self):
        """Genera il DataFrame delle posizioni, basate sui segnali
        forniti dal DataFrame 'signals'.
        """
        positions = pd.DataFrame(index=self.signals.index).fillna(0.0)

        # Long o short di 500 azioni dello SPY basate sui
        # segnali direzionali giornalieri
        positions[self.symbol] = 500 * self.signals['signal']
        return positions

    def backtest_portfolio(self):
        """
        Backtest del portafoglio e restituisce un DataFrame contenente
        la curva equity e i precentuali dei rendimenti."""

        # Imposta l'oggetto Portfolio per avere lo stesso periodo
        # del DataFrame delle posizioni
        portfolio = pd.DataFrame(index=self.positions.index)
        pos_diff = self.positions.diff()

        # Calcola il profitto infragiornaliero della differenza tra 
        # i prezzi di apertura e chiusura e quindi determina il 
        # profitto giornaliero andando long se è previsto un giorno 
        # positivo e short se è previsto un giorno negativo
        portfolio['price_diff'] = self.bars['Close'] - self.bars['Open']
        portfolio['price_diff'][0:5] = 0.0
        portfolio['profit'] = self.positions[self.symbol] * portfolio['price_diff']

        # Genera la curva equity e la percentuale dei rendimenti
        portfolio['total'] = self.initial_capital + portfolio['profit'].cumsum()
        portfolio['returns'] = portfolio['total'].pct_change()
        return portfolio
        

Il passaggio finale consiste nel legare gli oggetti Strategy e Portfolio tramite una funzione __main__. La funzione ottiene i dati per lo strumento SPY e quindi crea la strategia per la generazione del segnale sull’indice S&P500. Questa viene effettuato tramite il simbolo ^ GSPC. Inoltre si crea un’instanza di un MarketIntradayPortfolio con un capitale iniziale di 100.000 USD (come nei tutorial precedenti). Infine, si calcolano i rendimenti e si traccia la curva di equity.

Da notare che il codice richiesto in questa fase è abbastanza ridotto perché gran parte del lavoro viene svolto dalle sottoclassi di Strategy e Portfolio. Ciò rende estremamente semplice creare nuove strategie di trading e testarle rapidamente per l’utilizzo nella “strategy pipeline”.

            if __name__ == "__main__":
    start_test = datetime.datetime(2005, 1, 1)
    end_period = datetime.datetime(2005, 12, 31)

    # Download le barre dello ETF SPY che rispecchia l'indice S&P500
    bars = pdr.DataReader("SPY", "yahoo", start_test, end_period)

    # Crea la strategia di previsione dell'S&P500
    snpf = SNPForecastingStrategy("^GSPC", bars)
    signals = snpf.generate_signals()

    # Crea il portafoglio basate sul predittore
    portfolio = MarketIntradayPortfolio("SPY", bars, signals,
                                        initial_capital=100000.0)
    returns = portfolio.backtest_portfolio()

    # Stampa il grafico dei risultati
    fig = plt.figure()
    fig.patch.set_facecolor('white')

    # Stampa il prezzo dell'ETF dello SPY
    ax1 = fig.add_subplot(211, ylabel='SPY ETF price in $')
    bars['Close'].plot(ax=ax1, color='r', lw=2.)

    # Stampa la curva di equity
    ax2 = fig.add_subplot(212, ylabel='Portfolio value in $')
    returns['total'].plot(ax=ax2, lw=2.)

    fig.show()
    print("")
        
Di seguito è riportato l’output del programma. Nel periodo sotto esame, il mercato azionario ha guadagnato il 4% (ipotizzando una strategia di “buy and hold” di investimento), e anche lo stesso l’algoritmo ha generato un rendimento del 4%. Si noti che i costi di transazione (come le commissioni) non sono stati considerati in questo sistema di backtesting. Dal momento che la strategia effettua due operazioni ogni giorno, è probabile che tali commissioni riducano significativamente i rendimenti.
Performance della Strategia di Forecasting sull'S&P500 dal 01-01-2005 al 31-12-2006
Negli articoli successivi vedremo come migliorare questo algoritmo implementando costi di transazione più realistici, utilizzando motori di previsione avanzati, e fornire strumenti per l’ottimizzazione del portafoglio.

 

Per il codice completo riportato in questo articolo utilizzando il modulo di backtesting vettoriale VectorBacktest si può consultare il seguente repository di github:
https://github.com/datatrading-info/VectorBacktest

Strategia di Moving Average Crossover in Python con Pandas

Nel precedente articolo relativo allo Sviluppo di un BackTesting Vettoriale con Python e Pandas abbiamo creato un ambiente di backtesting orientato agli oggetti e testato su una strategia di previsione casuale. In questo articolo utilizzeremo gli strumenti che abbiamo introdotto per condurre ricerche su una strategia reale, ovvero il Moving Average Crossover su AAPL.

Strategia di Moving Average Crossover

La strategia di Moving Average Crossover (ovvero incrocio della media mobile) è una semplice strategia di momentum estremamente nota. È spesso considerata come l’esempio di “Hello World” per il trading quantitativo.

La strategia qui descritta è solo long. Si considerano due simple moving average separate, costruite su diversi periodi, di una particolare serie storica. I segnali per l’acquisto dell’asset si verificano quando la media mobile semplice più breve incrocia dal basso, cioè supera, la media mobile semplice più lunga. Se successivamente la media più lunga supera la media più breve, l’asset viene venduto. La strategia funziona bene quando una serie temporale entra in un periodo di forte trend e poi rallenta lentamente.

Per questo esempio, ho scelto Apple, Inc. (AAPL) come serie temporali, con una media mobile breve di 100 giorni e una media mobile lunga di 400 giorni. Questo è l’esempio presente nella libreria di trading algoritmico “zipline“. Quindi, se vogliamo implementare il nostro backtester, dobbiamo assicurarci che corrisponda ai risultati ottenuti da zipline, come metodo base per la convalidare il test.

Implementazione

Assicurati di aver letto il precedente tutorial, che descrive come viene costruita la gerarchia dell’oggetto iniziale del nostro backtester, altrimenti il codice sottostante non potrà funzionerà. Per questa particolare implementazione ho usato le seguenti librerie:

  • Python – 3.7
  • NumPy – 1.16.2
  • Pandas – 0.24.2
  • Matplotlib – 3.0.3

L’implementazione di ma_cross.py richiede il file backtest.py del precedente tutorial. Il primo passaggio consiste nell’importare i moduli e gli oggetti necessari:

            # ma_cross.py

import datetime
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from pandas_datareader.data import DataReader
from backtest import Strategy, Portfolio
        

Come nel precedente tutorial, si eredità la classe astratta della strategia (Strategy) per produrre la classe MovingAverageCrossStrategy, che contiene tutti i dettagli per generare  segnali quando le medie mobili di AAPL si incrociano l’una sull’altra.

L’oggetto richiede una short_window e una long_window su cui operare. I valori sono stati impostati com valori predefiniti, rispettivamente pari a 100 giorni e 400 giorni, che sono gli stessi parametri utilizzati nell’esempio di zipline.

Le medie mobili vengono create tramite la funzione rolling_mean applicata a bars['Close'], cioè i prezzi di chiusura del titolo AAPL. Una volta che le singole medie mobili sono state calcolate, la serie di segnali viene generata impostando la colonna uguale a 1,0 quando la media mobile breve è maggiore della media mobile lunga o 0,0 in caso contrario. Da queste informazioni si può generare gli ordini positions per rappresentare i segnali di trading.

            # ma_cross.py

class MovingAverageCrossStrategy(Strategy):
    """
    Richiede:
    symbol - Un simbolo di un titolo azionario su cui formare una strategia.
    bars - Un DataFrame di barre per il simbolo.
    short_window - Periodo di ricerca per media mobile breve.
    long_window - Periodo di ricerca per media mobile lunga.
    """

    def __init__(self, symbol, bars, short_window=100, long_window=400):
        self.symbol = symbol
        self.bars = bars

        self.short_window = short_window
        self.long_window = long_window

    def generate_signals(self):
        """
        Restituisce il DataFrame dei simboli che contiene i segnali
        per andare long, short o flat (1, -1 o 0).
        """
        signals = pd.DataFrame(index=self.bars.index)
        signals['signal'] = 0.0

        # Crea l'insieme di medie mobili semplici di breve e di
        # lungo periodo
        signals['short_mavg'] = pd.rolling_mean(self.bars['Close'], self.short_window, min_periods=1)
        signals['long_mavg'] = pd.rolling_mean(self.bars['Close'], self.long_window, min_periods=1)

        # Crea un "segnale" (investito o non investito) quando la media mobile corta incrocia la media
        # mobile lunga, ma solo per il periodo maggiore della finestra della media mobile più breve
        signals['signal'][self.short_window:] = np.where(signals['short_mavg'][self.short_window:]
            > signals['long_mavg'][self.short_window:], 1.0, 0.0)

        # Si calcola la differenza dei segnali per generare gli effettivi ordini di trading
        signals['positions'] = signals['signal'].diff()

        return signals
        

La classe MarketOnClosePortfolio è una classe derivata dalla classe astratta Portfolio, presente in backtest.py. È quasi identico all’implementazione descritta nel tutorial precedente, con l’eccezione che le operazioni vengono ora eseguite su base Close-to-Close, piuttosto che Open-to-Open. Ho trascritto tutto il codice completo per rendere autonomo questo tutorial:

            # ma_cross.py

class MarketOnClosePortfolio(Portfolio):
    """
    Incapsula la nozione di un portafoglio di posizioni basato
    su una serie di segnali forniti da una strategia.

    Richiede:
    symbol - Un simbolo di un titolo azionario che costituisce la base del portafoglio.
    bars - Un DataFrame di barre per un set di simboli.
    signals - Un DataFrame panda di segnali (1, 0, -1) per ogni simbolo.
    initial_capital - L'importo in contanti all'inizio del portafoglio.
    """

    def __init__(self, symbol, bars, signals, initial_capital=100000.0):
        self.symbol = symbol
        self.bars = bars
        self.signals = signals
        self.initial_capital = float(initial_capital)
        self.positions = self.generate_positions()

    def generate_positions(self):
        positions = pd.DataFrame(index=self.signals.index).fillna(0.0)
        # Questa strategia compra 100 azioni
        positions[self.symbol] = 100 * self.signals['signal']  
        return positions

    def backtest_portfolio(self):
        portfolio = pd.DataFrame(index=self.signals.index).fillna(0.0)
        pos_diff = self.positions[self.symbol].diff()

        portfolio['holdings'] = (self.positions[self.symbol] * self.bars['Close'])
        portfolio['cash'] = self.initial_capital - (pos_diff * self.bars['Close']).cumsum()

        portfolio['total'] = portfolio['cash'] + portfolio['holdings']
        portfolio['returns'] = portfolio['total'].pct_change()
        return portfolio
        

Ora che sono state definite le classi MovingAverageCrossStrategy e MarketOnClosePortfolio, verrà chiamata una funzione __main__ per collegare insieme le funzionalità delle due classi. Inoltre, la performance della strategia sarà esaminato attraverso un grafico della curva equity.

L’oggetto DataReader di pandas scarica i prezzi OHLCV del titolo AAPL per il periodo che va dal 1 gennaio 1990 al 1 gennaio 2002, in seguito di crea il DataFrame signals per generare i segnali long-only. Successivamente il portafoglio è generato con una base di capitale iniziale di 100.000 USD e i rendimenti sono calcolati sulla curva equity.

Il passaggio finale consiste nell’utilizzare matplotlib per tracciare un grafico a due figure con i prezzi di AAPL, sovrapposti con le medie mobili e i segnali di acquisto / vendita, nonché la curva equity con gli stessi segnali di acquisto / vendita. Il codice di plotting è stato preso (e modificato) dall‘esempio di zipline.

            # ma_cross.py

if __name__ == "__main__":
    # Download delle barre giornaliere di AAPL da Yahoo Finance per il periodo
    # Dal 1 ° gennaio 1990 al 1 ° gennaio 2002 - Questo è un esempio tratto da ZipLine
    symbol = 'AAPL'
    bars = DataReader(symbol, "yahoo", datetime.datetime(1990, 1, 1), datetime.datetime(2002, 1, 1))

    # Crea un'istanza della classe MovingAverageCrossStrategy con un periodo della media
    # mobile breve pari a 100 giorni e un periodo per la media lunga pari a 400 giorni
    mac = MovingAverageCrossStrategy(symbol, bars, short_window=100, long_window=400)
    signals = mac.generate_signals()

    # Crea un portofoglio per AAPL, con $100,000 di capitale iniziale
    portfolio = MarketOnClosePortfolio(symbol, bars, signals, initial_capital=100000.0)
    returns = portfolio.backtest_portfolio()

    # Visualizza 2 grafici per i trade e la curva di equity
    fig = plt.figure()
    fig.patch.set_facecolor('white')  # Imposta il colore di fondo a bianco
    ax1 = fig.add_subplot(211, ylabel='Price in $')

    # Visualizza il grafico dei prezzi di chiusura di AAPL con la media mobile
    bars['Close'].plot(ax=ax1, color='r', lw=2.)
    signals[['short_mavg', 'long_mavg']].plot(ax=ax1, lw=2.)

    # Visualizza i trade "buy" su AAPL
    ax1.plot(signals.loc[signals.positions == 1.0].index,
             signals.short_mavg[signals.positions == 1.0],
             '^', markersize=10, color='m')

    # Visualizza i trade "sell" su AAPL
    ax1.plot(signals.loc[signals.positions == -1.0].index,
             signals.short_mavg[signals.positions == -1.0],
             'v', markersize=10, color='k')

    # Visualizza la curva di equity in dollari
    ax2 = fig.add_subplot(212, ylabel='Portfolio value in $')
    returns['total'].plot(ax=ax2, lw=2.)

    # Visualizza i trade "buy" e "sell" su la curva di equity
    ax2.plot(returns.loc[signals.positions == 1.0].index,
             returns.total[signals.positions == 1.0],
             '^', markersize=10, color='m')
    ax2.plot(returns.loc[signals.positions == -1.0].index,
             returns.total[signals.positions == -1.0],
             'v', markersize=10, color='k')

    # Stampa il grafico
    fig.show()
        
Il grafico output del codice è il seguente. Ho fatto uso del comando IPython %paste per inserirlo direttamente nella console IPython di Ubuntu, in modo che l’output grafico rimanesse visibile. Gli uptick rosa rappresentano l’acquisto del titolo, mentre i downtick neri rappresentano la vendita:
Performance di un Moving Average Crossover su AAPL tra il 01/01/1990 e il 01/01/2002
Come si può vedere la strategia perde denaro lungo questo periodo, con cinque trade di apertura e chiusra della posizione. Ciò non sorprende, tenuto conto del comportamento dell’AAPL nel periodo in esame, che era leggermente in calo, seguito da un significativo aumento a partire dal 1998. Il periodo di take profit dei segnali della media mobile è piuttosto ampio e ha influito sul profitto finale dell’operazione. , che altrimenti potrebbe l’avrebbe resa una strategia redditizia. Negli articoli successivi creeremo uno strumento più sofisticato per analizzare le prestazioni, oltre a descrivere come ottimizzare i take profit dei singoli segnali della media mobile.

 

Per il codice completo riportato in questo articolo utilizzando il modulo di backtesting vettoriale VectorBacktest si può consultare il seguente repository di github:
https://github.com/datatrading-info/VectorBacktest

Come Elaborare i Dati Finanziari

In un precedente precedente articolo abbiamo descritto come come creare un Securities Master Database.
Questo articolo introduce un argomento che spesso non è molto considerato dalla maggior parte dei libri sul trading, cioè quello di elaborare i dati del mercato finanziario prima del loro utilizzo nel backtesting di strategia.

Si introducono inizialmente i diversi tipi di dati a cui i trader algoritmici sono interessati. Si prenderà quindi in considerazione la frequenza dei dati, dai dati trimestrali (come i report SEC) fino ai tick e l’order-book sulla scala del millisecondo. Le fonti di tali dati (sia gratuiti che commerciali) saranno quindi delineati insieme al codice per ottenere i dati. Infine, si descrive la pulizia e la preparazione dei dati per l’utilizzo nelle strategie.

Classificazione dei Mercati e degli Strumenti

Come trader algoritmici siamo spesso interessati ai dati su una vasta gamma di mercati finanziari. I dati possono spaziare dalle serie temporali dei prezzi di uno strumento, a dati testuali non strutturati (come feed di notizie) fino alle informazioni sugli utili societari. In questo articolo ci concentriamo prevalentemente sui dati delle serie temporali finanziarie.

Mercati

Le azioni statunitensi e internazionali, le valute estere, le materie prime e il reddito passivi sono le principali fonti di dati di mercato che sono di interesse per un trader algoritmico. Nel mercato azionario è ancora estremamente comune acquistare direttamente l’attività sottostante, mentre negli ultimi tre mercati gli strumenti derivati ​​altamente liquidi (come futures, opzioni o strumenti più esotici) sono più comunemente utilizzati per il trading.
Questa ampia categorizzazione rende essenzialmente relativamente semplice trattare i mercati azionari, sebbene con problemi relativi alla gestione dei dati delle azioni societarie (vedi sotto). Quindi gran parte del panorama di trading algoritmico retail è basato sulle azioni, come le azioni societarie dirette o gli Exchange Traded Funds (ETF). Anche i mercati delle valute estere (“forex”) sono molto apprezzati dal momento che i broker permetteranno il margin trading su movimenti percentuali in punti (PIP).
Un pip è un’unità del quarto decimale in un tasso di cambio. Per le valute denominate in dollari statunitensi questo è equivalente a 1/100 di centesimo.
Le materie prime e i mercati dei titoli a reddito fisso sono più difficili da scambiare direttamente con il sottostante. Un trader algoritmico retail non è spesso interessato a consegnare barili di petrolio a un deposito di petrolio! Invece, i contratti futures sull’attività sottostante sono utilizzati a fini speculativi. Ancora una volta, è permetto il margin trading consentendo ampia leva su tali contratti.

Strumenti

Per il trader algoritmico è disponibile una vasta gamma di sottostanti e di derivati.
Mercato Strumenti
Azioni / Indici Stock, ETFs, Futures, Options
Foreign Exchange (FOREX) Margin/Spot, ETFs, Futures, Options
Commodities Futures, Options
Obbligazioni Futures, Options

Al fine di semplificare l’implementazioni, in questo articolo ci concetriamo sul mercato azionario e ETF

Dati Fondamentali

Sebbene i trader algoritmici eseguano principalmente analisi sulle serie temporali dei prezzi finanziari, anche i dati fondamentali (con diverse frequenze) sono spesso aggiunti all’analisi. Le cosiddette strategie di Quantitative Value (QV) fanno molto affidamento sull’acquisizione e sull’analisi dei dati fondamentali, come ad esempio le informazioni macroeconomiche, i dati storici degli utili societari, gli indici di inflazione, i rapporti sui salari, i tassi di interesse e depositi SEC. Tali dati sono spesso disponibili anche in formato temporale, sebbene su frequenze molto grandi, come mesi, trimestri o anni.

Questo articolo non approfondisce l’elaborazione dei dati fondamentali per le strategie QV o strategie su timeframe molto elevati, mentre mi voglio concentrare su strategie daily o più frequenti (intraday) derivate principalmente dall’azione dei prezzi (price action).

Dati non Strutturati

I dati non strutturati sono costituiti da documenti come news, post di blog, articoli o relazioni. L’analisi di tali dati può essere complicata poiché si basa sulle tecniche di Natural Language Processing (NLP). Uno di questi è l’analisi dei dati non strutturati nel tentativo di determinare il livello di sentiment. Questo può essere utile per guidare una strategia di trading. Ad esempio, classificando i testi come “bullish”, “bearish” o “neutral”, potrebbe essere generato un insieme di segnali di trading. Il termine per questo processo è la Sentiment Analysis.

Python fornisce una libreria estremamente completa per l’analisi di dati di testo, conosciuta come Natural Language Toolkit (NLTK).

Dati Testuali

Esistono numerose fonti di dati testuali che possono essere utili per generare una strategia di trading. Le popolari fonti di news finanziarie come Bloomberg e il Financial Times, così come blog di commenti finanziari come Seeking Alpha e ZeroHedge, forniscono importanti fonti di testo da analizzare. Inoltre, i feed di notizie proprietari forniti dai fornitori di dati sono anch’esse buone fonti di dati.

Per ottenere dati su una scala più ampia, è necessario utilizzare strumenti di “web scraping”, progettati per automatizzare il download di massa di siti Web. In questo caso è necessario prestare la massima attenzione perché gli strumenti automatici di web scraping possono violano i Termini di servizio di questi siti. Assicurati di controllare prima di iniziare a scaricare questo tipo di dati. Uno strumento particolarmente utile per il web scraping, che rende il processo efficiente e strutturato, è la libreria ScraPy di Python.

Dati dai Social Media

Negli ultimi anni c’è stato un significativo interesse per ottenere le informazioni sul sentiment dai dati dei social media, in particolare tramite il servizio di microblogging di Twitter. Nel 2011, un hedge fund è stato creato intorno al sentiment di Twitter, noto come Derwent Capital. In effetti, studi accademici hanno dimostrato che è possibile generare un buion livello di capacità predittiva basato su tale sull’analisi del sentimento. Nel caso si desideri effettuare ricerche approfondite sul sentiment, allora ci sono due libri di Matt Russell relativi all’acquisizione dei dati sui social media tramite le API pubbliche fornite da questi servizi web.

Frequenza dei Dati

La frequenza dei dati è un fattore fondamentale quando si progetta un sistema di trading algoritmico. Influisce su ogni decisione progettuale riguardante l’archiviazione dei dati, il backtesting di una strategia e l’esecuzione di un algoritmo. È probabile che le strategie ad alta frequenza portino a un’analisi statisticamente più solida, semplicemente a causa del maggior numero di punti di dati (e quindi di trade) che verranno utilizzati. Le strategie HFT richiedono spesso un investimento significativo in termini di tempo e capitale per lo sviluppo del software necessario per eseguirle. Le strategie a bassa frequenza sono più facili da sviluppare e implementare, poiché richiedono meno automazione. Tuttavia, spesso generano molti meno trade rispetto ad una strategia ad alta frequenza che porta a un’analisi  statisticamente meno solida.

Dati Mensili e Settimanali

I dati fondamentali sono spesso riportati su base settimanale, mensile, trimestrale o anche annuale. Tali dati includono dati sui salari, report sulle prestazioni dei fondi hedge, depositi SEC, indici basati sull’inflazione (come l’indice dei prezzi al consumo), la crescita economica e i conti aziendali. Per l’archiviazione di tali dati è più idoneo utilizzare database non strutturati, come MongoDB, che può gestire dati nidificati gerarchicamente e quindi consentire un ragionevole grado di capacità di interrogazione. L’alternativa è archiviare il testo di file flat in un RDBMS, che è meno appropriato, dal momento che le interrogazioni full-text sono più complicate.

Dati Giornalieri

La maggior parte dei trader algoritmici retail fa uso di serie giornaliere di dati finanziari (“end of day” / EOD), in particolare per le azioni e il forex. Tali dati sono liberamente disponibili (vedi sotto), ma spesso sono di qualità discutibile e soggetti a determinati bias. I dati di fine giornata sono spesso archiviati in RDBMS, poiché la natura della mappatura ticker / simboli si applica perfettamente al modello relazionale.

I dati EOD non comportano requisiti di archiviazione particolarmente ampi. Ci sono 252 giorni di trading in un anno per il mercato degli Stati Uniti e quindi in un decennio ci saranno 2.520 record per ogni titolo azionario. Anche con un mercato di 10.000 simboli si avrà 25.200.000 record, che possono essere facilmente gestiti all’interno di un database relazionale.

Dati Intraday

Le strategie infragiornaliere utilizzano spesso le barre OHLCV di ogni ora, quindici, cinque, un minuto o addirittura secondi. I provider di feed intraday come QuantQuote e DTN IQFeed forniscono spesso dati al minuto o al secondo in base ai loro dati di tick. I dati a tali frequenze possono presentare molte barre “mancanti” semplicemente perché non sono state effettuate transazioni in quel periodo di tempo. La libreria Pandas può essere utilizzata per ottimizzare questi dati, a discapito della precisione dei dati. Inoltre, Pandas può essere utilizzati per creare dati su scale temporali meno granulari, quando necessario.

Per un periodo di dieci anni, i dati al minuto genereranno quasi un milione di barre per ogni titolo. Analogamente per i dati al secondo il numero di punti dati nello stesso periodo ammonterà a quasi sessanta milioni per ogni titolo. Pertanto, per immagazzinare un migliaio di tali titoli si otterranno 60 miliardi di barre di dati. Si tratta di una grande quantità di dati da conservare in un RDBMS e di conseguenza sono necessari approcci più sofisticati.

Dati di Tick e dell'Order-Book

Quando un trade viene eseguito all’interno di un exchange, o in un’altra sede, viene generato un tick. I feed di tick sono costituiti da tutte le transazioni di un exchange. Ogni tick retail è memorizzato all’interno del feed con un timestamp accurato al millisecondo. I dati di tick spesso includono anche il miglior prezzo di domanda/offerta (bid/ask). L’archiviazione dei dati tick è ben oltre lo scopo di un trader retail, ma inutile dire che i volumi di tali dati sono notevoli. I comuni meccanismi di archiviazione comprendono HDF5, kdb e semplicemente flat-file / CSV.

Più ordini limite in un exchange portano al concetto dell’order book. Questo è essenzialmente l’elenco di tutti gli ordini limite di acquisto e di vendita a determinati volumi per ogni partecipante al mercato. Porta alla definizione dello spread bid-ask (o semplicemente dello “spread”), che è la più piccola differenza tra i prezzi bid e ask per gli ordini “top of book”. La creazione di una rappresentazione storica, o un simulatore di mercato, di un order book è solitamente necessario per realizzare strategie di trading a frequenze ultra alte (UHFT).

Fonti di Dati

Esistono numerose fonti e venditori di dati finanziari. Variano sostanzialmente in ampiezza, tempestività, qualità e prezzo.
In generale, i dati sui mercati finanziari forniti su una frequenza giornaliera (con un leggero ritardo), o più ampie sono disponibili gratuitamente, anche se non sono di buona qualità e presentano bias di sopravvivenza. Per ottenere i dati intraday è di solito necessario acquistare un feed di dati commerciale. I fornitori di tali feed variano enormemente nella loro capacità di servizio al cliente, nella qualità complessiva dei feed e nell’ampiezza degli strumenti.

Fonti gratuite

I dati gratuiti di fine giornata, che consistono in prezzi Open-High-Low-Close-Volume (OHLCV) degli  strumenti, sono disponibili per un’ampia gamma di titoli azionari e futures statunitensi e internazionali tramite i servizi come Yahoo Finance, Google Finance e Quandl.

Yahoo Finance

Yahoo Finance è la risorsa “base” quando si costituisce un database dei dati EOD dei titoli azionari statunitensi. L’ampiezza dei dati è estremamente completa, e contiene migliaia di azioni scambiate. Inoltre, le scissioni di azioni e i dividendi vengono gestiti utilizzando il metodo di back-adjustment, il cui risultato è contenuto nella colonna “Adj Close” nell’output CSV dell’API (che discuteremo di seguito). Pertanto, questi dati permettono ai trader algoritmici di iniziare rapidamente e a costo zero. Personalmente ho avuto molta esperienza nella pulizia dei dati di Yahoo. Devo dire che i dati possono essere abbastanza errati. Innanzitutto, è soggetto a un problema noto come backfilling. Questo problema si verifica quando i dati storici passati vengono corretti in una data futura, causando dei backtesting di scarsa qualità che cambiano man mano che il proprio database viene aggiornato. Per gestire questo problema, un record di registrazione viene generalmente aggiunto al securities master (in una apposita tabella) ogni volta che viene modificato un dato storico. In secondo luogo, il feed di Yahoo aggrega solo i prezzi di poche fonti per formare i punti OHLCV. Ciò significa che i valori possono essere ingannevoli, poiché altre fonti potrebbero aver eseguito prezzi diversi rispetto ai valori. In terzo luogo, ho notato che quando si ottengono i dati finanziari in massa, gli errori si insinuano nell’API. Ad esempio, più chiamate all’API con identici parametri data / ticker occasionalmente portano a set di risultati diversi. Questo è chiaramente un problema sostanziale e deve essere attentamente controllato. In sintesi, preparatevi a eseguire una vasta pulizia dei dati sui dati di Yahoo Finance, se scegliete di utilizzarlo per popolare un grande master di titoli e avete bisogno di dati altamente accurati.

Quandl

Quandl è un servizio che si propone come “il modo più semplice per trovare e utilizzare i dati finanziari”. Credo che siano sulla buona strada per raggiungere questo obiettivo! Il servizio fornisce una serie di dati giornalieri per i titoli azionari statunitensi e internazionali, i tassi di interesse, le materie prime / futures, le valute estere e altri dati economici. Inoltre, il database viene continuamente ampliato e il progetto viene attivamente mantenuto.
Tutti i dati sono accessibili tramite una moderna API HTTP (CSV, JSON, XML o HTML), con plugin per una vasta gamma di linguaggi di programmazione tra cui R, Python, Matlab, Excel, Stata, Maple, C#, EViews, Java , C/C++, .NET, Clojure e Julia. Senza un account sono consentite 50 chiamate all’API al giorno, ma questo può essere aumentato a 500 se si registra un account. In effetti, le chiamate possono essere aggiornate a 5.000 all’ora, se lo si desidera, contattando il team.
Non ho avuto molta esperienza con Quandl “su larga scala” e quindi non posso commentare il livello di errori presenti all’interno del set di dati, ma la mia sensazione è che è probabile che tutti gli errori vengano costantemente segnalati e corretti. Pertanto, vale la pena considerarlo come fonte di dati primaria per un master di titoli di fine giornata.

Fonti a pagamento

Per effettuare trading algoritmico intraday è solitamente necessario acquistare un feed commerciale. I prezzi possono variare dai $30 al mese fino a circa $500 al mese per il “livello retail”. I feed di qualità istituzionali saranno spesso nel range di quattro zeri al mese e come tali non sono considerati in questo articolo.

EODData.

Ho utilizzato EODData solo con dati giornalieri e prevalentemente per il mercato forex. Nonostante il loro nome, forniscono un certo grado di dati intraday. Il costo è $25 al mese per il loro pacchetto “platino”. La risorsa è molto utile per trovare un elenco completo dei simboli scambiati sulle borse globali, ma ricorda che questo è soggetto al bias di sopravvivenza poiché ritengo che la lista rappresenti le attuali entità quotate. Sfortunatamente ho scoperto che il feed di divisione azionaria era in qualche modo inaccurato (almeno se confrontato con le informazioni di Morningstar). Ciò ha portato a notevoli problemi di picco (vedi sotto) nei dati, che hanno aumentato l’attrito nel processo di pulizia dei dati.

DTN IQFeed

DTN IQFeed è uno dei feed di dati più popolari per i trader algoritmici retail di fascia alta. Dicono di avere oltre 80.000 clienti. Forniscono dati tick-by-tick in tempo reale non filtrati dall’exchange e una grande quantità di dati storici. Il prezzo inizia a $50 al mese, ma in realtà sarà compreso tra $150 e $200 al mese una volta selezionati determinati servizi. Io uso DTN IQFeed per tutte le mie strategie intraday sul mercato azionario e dei futures.

In termini di dati storici, IQFeed fornisce i dati per le azioni, futures e opzioni:

  • 180 giorni di calendario di tick (ogni trade)
  • 7+ anni di dati storici da 1 minuto
  • Oltre 15 anni di dati storici giornalieri

Il principale svantaggio è che il software DTN IQFeed (il mini-server, non gli strumenti per la creazione di grafici) funzionerà solo su Windows. Questo potrebbe non essere un problema se tutto il tuo trading algoritmico viene eseguito in questo sistema operativo, ma personalmente sviluppo tutte le mie strategie in Ubuntu Linux. Tuttavia, anche se non l’ho testato attivamente, ho sentito che è possibile eseguire DTN IQFeed con l’emulatore WINE.

QuantQuote

QuantQuote fornisce a prezzi ragionevoli dati storici al minuto, al secondo e a livello di tick per il mercato azionario statunitense, a partire dal 1998. Inoltre, forniscono a livello istituzionale il feed dei tick in tempo reale, sebbene questo sia di minore interesse per i trader algoritmici retail. Uno dei principali vantaggi di QuantQuote è che i loro dati sono forniti senza bias di sopravvivenza, grazie al loro software di abbinamento simbolico TickMap e all’inclusione di tutti i titoli entro un determinato indice nel tempo. Ad esempio, per acquistare l’intera cronologia dello S&P500 risalente al 1998 in barre al minuto, comprensivo dei simboli delistati, il costo al momento della scrittura di questo articolo era di $895.

QuantQuote è attualmente il principale fornitore di dati per il servizio di backtesting basato sul web QuantConnect. QuantQuote fa di tutto per garantire la minimizzazione degli errori, quindi se stai cercando titoli azionari statunitensi ad alta risoluzione, dovresti considerare di usare il loro servizio.

Automatizzare i Trade con l’API di Interactive Brokers tramite Python e IBPy

Interactive Brokers è uno dei principali broker utilizzati dai trader algoritmici retail a causa dei suoi relativamente bassi requisiti minimi di capitale sul conto (10.000 USD) e la robusta API. In questo articolo faremo uso di un account demo per automatizzare gli ordini con l’API di Interactive Brokers, tramite Python e il package IBPy.

Nota: non ho alcuna affiliazione con Interactive Brokers. Ho avuto modo di usarlo in molti contesti e quindi ho familiarità con il loro software.

L' API di Interactive Brokers

Interactive Brokers è una grande azienda e in quanto tale si rivolge a una vasta gamma di operatori, che vanno dai trader retail discrezionali agli istituzionali automatizzati. Questo a fatto in modo che questo broker abbia sviluppato un’interfaccia GUI completa e robusta, la Trader Workstation (TWS).

Oltre a TWS c’è anche un semplice componente chiamato IB Gateway, che fornisce lo stesso accesso ai server di IB ma senza le funzionalità extra della GUI. Per i nostri scopi di trading automatico non avremo realmente bisogno della GUI TWS, ma penso che possa essere utile utilizzarla per questo tutorial dimostrativo.

L’architettura sottostante si basa su un modello client / server che offre sia feed di dati di esecuzione e di mercato (storici e in tempo reale) tramite un’API. Utilizzeremo questa API  per inviare ordini automatici, tramite IBPy.

IBPy è stato scritto per “avvolgere” l’API Java nativa e renderla semplice da essere chiamata tramite Python. Le due principali librerie a cui siamo interessati all’interno di IBPy sono ib.extib.opt. Quest’ultimo è di livello superiore e fa uso della funzionalità nel primo.

Nella seguente implementazione creeremo un esempio estremamente semplice, che invierà semplicemente un singolo ordine di mercato per acquistare 100 unità di azioni Google, utilizzando il routing intelligente degli ordini. Quest’ultimo è progettato per ottenere il miglior prezzo, anche se in determinate situazioni può non essere ottimale. Tuttavia, sarà sufficiente per gli scopi di questo tutorial.

Implementazione in Python

Prima di iniziare è necessario aver attivato un account su Interactive Brokers. Inoltre è necessario disporre di uno ambiente di lavoro in in modo da poter installare IBPy, che ti consentirà di legare insieme altri aspetti del tuo codice. Per creare un ambiente Python puoi leggere il mio precedente articolo su come installare di un ambiente di backtesting in Python.

Installare IBPy

IBPy è un wrapper Python sviluppato attorno all’API Interactive Brokers basata su Java. Rende un po ‘meno problematico lo sviluppo dei sistemi di trading algoritmico in Python. Sarà utilizzato come base per tutte le successive comunicazioni con Interactive Brokers finché non considereremo il protocollo FIX in un secondo momento. Poiché IBPy è gestito su GitHub come repository git, sarà necessario installare git. Su un sistema Ubuntu questo è effettuato con il comando:
            sudo apt-get install git-core
        
Una volta installato git, è possibile creare una sottodirectory per memorizzare IBPy. Sul mio sistema l’ho semplicemente collocato sotto la mia home directory:
            mkdir ~/ibapi
        
Lo step successivo è scaricare IBPy tramite git clone
Assicurarsi di entrare nella directory IbPy ed effettuare l’installazione con l’ambiente virtuale Python preferito:
            cd ~/ibapi/IbPy
python setup.py.in install
        
Questo completa l’installazione di IBPy. Il prossimo passo è aprire TWS
Schermata del TWS Portfolio

Trading Automatico

Il seguente codice implementa una semplice gestione degli ordine basato sulle API. Il codice è lontano dall’essere usato in produzione, ma dimostra la funzionalità essenziale dell’API di Interactive Brokers e come utilizzarla per l’esecuzione degli ordini. Tutto il seguente codice è contenuto nel file ib_api_demo.py. Il primo passaggio consiste nell’importare gli oggetti Contract e Order dalla libreria ib.ext. Inoltre importiamo gli oggetti Connection e message dalla libreria ib.opt.
            # ib_api_demo.py

from ib.ext.Contract import Contract
from ib.ext.Order import Order
from ib.opt import Connection, message
        

 

IB ci offre la possibilità di gestire gli errori e le risposte del server tramite un meccanismo di callback. Le seguenti due funzioni non fanno altro che stampare il contenuto dei messaggi restituiti dal server. Un sistema di produzione più sofisticato dovrebbe implementare la logica per assicurare il funzionamento continuo del sistema nel caso di generazione di eccezioni:

            # ib_api_demo.py

def error_handler(msg):
    """Gestione per la cattura dei messagi di errori"""
    print("Server Error: %s" % msg)

def reply_handler(msg):
    """Gestione delle risposte dal server"""
    print("Server Response: %s, %s" % (msg.typeName, msg))
        

 

Le seguenti due funzioni completano la creazione degli oggetti ContractOrder, impostando i rispettivi parametri:

            # ib_api_demo.py

def create_contract(symbol, sec_type, exch, prim_exch, curr):
    """
    Crea un oggetto Contract definendo cosa sarà
    acquistato, in quale exchange e in quale valuta.

    symbol - Il simbolo del ticker per il contratto
    sec_type - Il tipo di asset per il contratto ("STK" è "stock")
    exch - La borsa su cui eseguire il contratto
    prim_exch - Lo scambio principale su cui eseguire il contratto
    curr - La valuta in cui acquistare il contratto
    """
    contract = Contract()
    contract.m_symbol = symbol
    contract.m_secType = sec_type
    contract.m_exchange = exch
    contract.m_primaryExch = prim_exch
    contract.m_currency = curr
    return contract

def create_order(order_type, quantity, action):
    """
    Crea un oggetto Ordine (Market/Limit) per andare long/short.

    order_type - "MKT", "LMT" per ordini a mercato o limite
    quantity - Numero intero di asset dell'ordine
    action - 'BUY' o 'SELL'    
    """
    order = Order()
    order.m_orderType = order_type
    order.m_totalQuantity = quantity
    order.m_action = action
    return order

        
La funzione __main__ inizialmente crea un oggetto Connection verso Trader Workstation, che deve essere in esecuzione affinché il codice funzioni. Le funzioni di gestione degli errori e di risposta vengono quindi registrate con l’oggetto di connessione. Successivamente viene definita una variabile order_id. In un sistema di produzione questo deve essere incrementato per ogni ordine. I passi successivi sono la creazione di un Contract e di un Order che rappresenta un market-order per acquistare 100 unità di azioni Google. L’ultimo compito è quello di piazzare quell’ordine tramite il metodo placeOrder dell’oggetto Connection. Quindi ci disconnettiamo da TWS:
            # ib_api_demo.py

if __name__ == "__main__":
    # Collegarsi alla Trader Workstation (TWS) in esecuzione
    # sulla solita porta 7496, con un clientId di 100
    # (Il clientId è scelto da noi e avremo bisogno di
    # ID separati sia per la connessione di esecuzione che per
    # connessione dati di mercato)
    tws_conn = Connection.create(port=7496, clientId=100)
    tws_conn.connect()

    # Assegna la funzione di gestione degli errori definita
    # sopra alla connessione TWS
    tws_conn.register(error_handler, 'Error')

    # Assegna tutti i messaggi di risposta del server alla
    # funzione reply_handler definita sopra
    tws_conn.registerAll(reply_handler)

    # Crea un ordine ID che sia "globale" per questa sessione. Questo
    # dovrà essere incrementato una volta inviati nuovi ordini.
    order_id = 1

    # Crea un contratto in stock GOOG tramite il routing degli ordini SMART
    goog_contract = create_contract('GOOG', 'STK', 'SMART', 'SMART', 'USD')

    # si va long di 100 azioni di Google
    goog_order = create_order('MKT', 100, 'BUY')

    # Uso della connessione per inviare l'ordine a IB
    tws_conn.placeOrder(order_id, goog_contract, goog_order)

    # Disconnessione da TWS
    tws_conn.disconnect()
        
Infine è sufficiente lanciare lo script Python:
            python ib_api_demo.py
        

Immediatamente si può vedere che si apre un tab “API” su Trader Workstation, mostrando il market order per andare long di 100 azioni su Google

Schermata del Tab API di TWS dopo l'ordine su Google

 

Per il codice completo riportato in questo articolo utilizzando il modulo di backtesting vettoriale VectorBacktest si può consultare il seguente repository di github:
https://github.com/datatrading-info/VectorBacktest

Storico Intraday per l’Azionario Statunitenze da DTN IQFeed con Python

In questo articolo voglio presentare un approccio per scaricare i dati storici intraday delle azioni statunitensi  dal fornitore di dati DTN IQFeed. È possibile ottenere i dati tramite una connessione socket al server locale IQLink che viene fornito quando si crea un account. In questo articolo utilizzeremo una connessione di streaming socket con Python per bufferizzare questi dati e creare file CSV di dati infragiornalieri il mercato  delle azioni statunitensi.

Nota: non ho alcuna affiliazione con DTN IQFeed oltre ad essere un loro cliente. Sto semplicemente scrivendo questo articolo per aiutare coloro che hanno un account IQFeed (o stanno prendendo in considerazione l’idea di acquistarne uno) per scaricare i dati senza la necessità di un software GUI.

Connessioni Socket in Python per IQFeed

Ipotizziamo che tu abbia già un account con IQFeed. In caso contrario, è possibile ottenere una prova gratuita di due settimane al momento dell’iscrizione.

Dopo esserti iscritto agli  exchange e al livello di granularità dei dati che si desidera, ti verrà chiesto di scaricare il launcher IQLink. Questo strumento funziona solo in modo nativo su Windows ma può essere eseguito anche su un Mac o una macchina Linux con WINE e un po ‘di lavoro!

L’avvio di IQLink avvia la finestra di dialogo di connessione:

Facendo clic su “Avvia IQLink” verrà avviato il server. Ti verrà richiesto il nome utente e la password. Una volta che il server è in esecuzione, è necessario creare una connessione Stream Socket  a una porta locale (9100 è l’impostazione predefinita). È quindi possibile inviare messaggi attraverso questo socket e ricevere i dati. Il primo compito è creare il file iqfeed.py e importare sia il sistema che le librerie di socket:
            # iqfeed.py

import sys
import socket
        
Il buffering dei dati viene gestito dalla funzione read_historical_data_socket, che richiede in input un oggetto socket e il numero di byte da prelevare per ogni. La funzione aggiunge semplicemente l’ultima serie di dati prelevati ad una stringa e la restituisce una volta che trova la stringa “! ENDMSG!” all’interno dei dati (cioè il buffer ha raggiunto la fine!):
            # iqfeed.py

def read_historical_data_socket(sock, recv_buffer=4096):
    """
    Lettura delle informazioni dal socket, in un buffer
    su misura, ricevendo solo 4096 byte alla volta.

    Parametri:
    sock - L'oggetto socket
    recv_buffer - Quantità in byte da ricevere per lettura
    """

    ibuffer = ""
    data = ""
    while True:
        data = sock.recv(recv_buffer)
        ibuffer += data

        # Controllo se è arrivata la stringa di fine messaggio
        if "!ENDMSG!" in ibuffer:
            break

    # Rimuovere la stringa di fine messaggio
    ibuffer = ibuffer[:-12]
    return ibuffer
        

Il socket deve connettersi al computer locale sulla porta 9100. In questo esempio, scarichiamo quattro simboli di azioni: SPY, AAPL, GOOG e AMZN dall’inizio del 2014 ad oggi.

IQFeed accetta messaggi nel seguente formato: CMD,SYM,[options]\n. Si noti il carattere “newline”, che deve aggiunta altrimenti il messaggio non funzionerà. Le opzioni fornite sono [bars in seconds], [beginning date: CCYYMMDD HHmmSS], [ending date: CCYYMMDD HHmmSS], [empty],[beginning time filter: HHmmSS], [ending time filter: HHmmSS], [old or new: 0 or 1], [empty] ,[queue data points per second].

Ad esempio il messaggio avrà un formato simile al seguente: "HIT,GOOG,60,20140101 075000,,,093000,160000,1\n". Questo messaggio ci restituisce i dati storici (HIT) per il simbolo del ticker GOOG, ad frequenza pari a una volta ogni 60 secondi (ad es. Timeframe ad un minuto), dalle 07:50:00 del 1 ° gennaio 2014 fino ad oggi. I dati vengono filtrati per essere presenti solo dalle 09:30:00 alle 16:00:00, che è l’orario di apertura del mercato NYSE.

Il primo compito è definire l’host, la porta e i simboli per il download. Ciascuno dei quattro simboli viene ripetuto e viene creato il messaggio per i dati storici. Quindi viene aperto un socket. AF_INET specifica che una tupla (host, port) deve essere prevista durante la connessione. SOCK_STREAM afferma che il socket deve essere un socket di streaming.

Una volta aperto il socket, il messaggio viene inviato e i dati cronologici vengono memorizzati nel buffer, dopodiché il socket viene chiuso. Tutte le terminazioni di riga vengono rimosse e i dati vengono scritti in un file chiamato “sym.csv” archiviato nella stessa directory del codice Python, dove “sym” è il simbolo ticker:

            # iqfeed.py

if __name__ == "__main__":
    # Definizione dell'host del server, la porta e il simbolo da scaricare
    host = "127.0.0.1"  # Localhost
    port = 9100  # porta del socket per i dati storici
    syms = ["SPY", "AAPL", "GOOG", "AMZN"]

    # Download ogni simbolo su disco
    for sym in syms:
        print "Downloading symbol: %s..." % sym

        # Costruzione del messaggio previsto da IQFeed per ricevere i dati
        message = "HIT,%s,60,20140101 075000,,,093000,160000,1\n" % sym

        # Apertura di un socket streaming localmente verso un server IQFeed
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((host, port))

        # Invia i messaggi per le richieste di dati 
        # storici e immagazziona i dati
        sock.sendall(message)
        data = read_historical_data_socket(sock)
        sock.close

        # Rimuovere tutte le linee finali e le virgole di fine
        # linea che delimitano ogni record
        data = "".join(data.split("\r"))
        data = data.replace(",\n","\n")[:-1]

        # Scrive il flusso dei deti sul disco
        f = open("%s.csv" % sym, "w")
        f.write(data)
        f.close()
        

I dati restituite hanno il seguente formato:

[YYYY-MM-DD HH:mm:SS],[OPEN],[LOW],[HIGH],[CLOSE],[VOLUME],[OPEN INTEREST]

Quindi le righe all’interno del file dovrebbero essere come segue:

2012-01-03 09:31:00,30.6400,30.5000,30.6400,30.5100,6128,6128
2012-01-03 09:32:00,30.5600,30.4900,30.4900,30.5600,6528,400
2012-01-03 09:33:00,30.5000,30.5000,30.5000,30.5000,6672,144
2012-01-03 09:34:00,30.3800,30.3400,30.3400,30.3500,8423,1751
2012-01-03 09:35:00,30.5300,30.5300,30.5300,30.5300,8623,200
2012-01-03 09:36:00,30.6400,30.5500,30.5500,30.6400,9423,800
2012-01-03 09:37:00,30.6500,30.6500,30.6500,30.6500,10329,906
2012-01-03 09:38:00,30.6900,30.6600,30.6900,30.6600,12329,2000
2012-01-03 09:39:00,30.7200,30.6400,30.6500,30.7200,13729,1400
2012-01-03 09:40:00,30.7500,30.6900,30.7200,30.7500,17029,3300

I dati disponibili su IQFeed possono risalire anche a molti anni indietro. Tuttavia, può essere necessario del tempo per scaricarli una volta che si inizia a considerare i dati di molti simboli, risalenti a cinque o più anni di storico. Se desideri scaricare tipi di dati diversi dalle azioni, puoi consultare la guida di IQFeed. Ovviamente è necessario essere abbonati all’exchange appropriato affinché il download del simbolo funzioni.

Tieni presente che i dati intraday forniti da IQFeed non sono “aggiustati”. Ciò significa che non tiene conto degli eventi societarie come i  dividendi o le scissioni di azioni. Dovrai personalmente seguire la correzione dei prezzi delle azioni.

 

Per il codice completo riportato in questo articolo utilizzando il modulo di backtesting vettoriale VectorBacktest si può consultare il seguente repository di github:
https://github.com/datatrading-info/VectorBacktest

Utilizzare i Futures Continuous per scopi di Backtesting

In questo articolo discuteremo le caratteristiche dei contratti future che rappresentano un’interessate sfida dal punto di vista di backtesting. In particolare, i concetti di “continuous contract” e”roll returns”. Descriveremo le principali difficoltà dei futures e forniremo un’implementazione in Python con pandas in grado di risolvere parzialmente questi problemi.

Breve panoramica sui Futures

I futures sono una forma di contratto stipulato tra due parti per l’acquisto o la vendita di una certa quantità di un sottostante in una specifica data specifica. Questa data è conosciuta come  scadenza. Quando questa data viene raggiunta, l’acquirente deve consegnare il sottostante fisico (o equivalente in contanti) al venditore per il prezzo concordato alla data di costituzione del contratto.

In pratica i futures sono negoziati in borsa (al contrario del trading Over The Counter – OTC) per quantità e qualità standardizzate del sottostante. I prezzi sono  marked to market ogni giorno. I futures sono incredibilmente liquidi e sono utilizzati pesantemente per scopi speculativi. In passato i futures venivano utilizzati principalmente per i prezzi dei prodotti agricoli o industriali, ora è possibile stipulare un contratto futures su qualsiasi sottostante tangibile o intangibile, come indici azionari, tassi di interesse dei tassi di cambio.

Un elenco dettagliato di tutti i codici e simboli utilizzati per i contratti futures in varie borse può essere consultato dal sul sito del CSI Data: Futures Factsheet.

La principale differenza tra un contratto futures e la partecipazione azionaria è il fatto che un contratto futures ha una finestra di disponibilità limitata in virtù della data di scadenza. In qualsiasi momento ci sarà una varietà di contratti futures sullo stesso sottostante, tutti con diverse date di scadenza. Il contratto con la data di scadenza più vicina è noto come near contract. Il problema che affrontiamo come trader quantitativi è la possibilità di scegliere in qualsiasi momento con molti contratti con cui fare trading. Quindi abbiamo a che fare con un insieme di serie temporali sovrapposte piuttosto che un flusso continuo come nel caso di azioni o valute.

L’obiettivo di questo articolo è di delineare vari approcci per costruire un flusso continuo di contratti a partire da questo insieme di serie multiple e per evidenziare i compromessi associati a ciascuna tecnica.

I contratti di Futures continui

La principale difficoltà nel cercare di generare un contratto continuo dai contratti sottostanti con differenti scadenze è causata dalla variazione dei prezzi con cui vengono scambiati. Quindi si verificano casi dove i prezzi non sono lineari tra un contratto a scadenza e il successivo. Ciò è dovuto agli effetti di Contango (i prezzi delle scadenze più lontane sono più alti di quelli delle scadenze più vicine) o in Backwardation (esattamente l’opposto).

Esistono diversi approcci per affrontare questo problema. Sfortunatamente non esiste un unico metodo “standard” per unire i contratti futures. In definitiva, il metodo scelto dipenderà in gran parte dalla strategia e dal metodo di esecuzione. Nonostante non esista un unico metodo, ci sono alcuni approcci comuni:

Aggiustamento Back/Forward ("Panama")

Questo metodo riduce il “divario” tra più contratti spostando ciascun contratto in modo tale che le singole scadenze si uniscano in modo agevole con contratti adiacenti. Pertanto, il prezzo di apertura / chiusura tra i contratti coincide.

Il problema chiave con il metodo Panama consiste nell’introduzione di un bias di trend, che causa una forte deriva dei prezzi, soprattutto con orizzonti temporali di lungo periodo (e anche la possibilità di prezzi negativi). Inoltre, vi è una perdita delle differenze di prezzo relative a causa di uno spostamento assoluto dei valori. Ciò significa che i rendimenti sono più complicati da calcolare (o semplicemente errati).

Aggiustamento Proporzionale

Questo approccio è simile al metodo del “handling stock splits” per le azioni (gestione dei frazionamenti azionari). In questo caso, il rapporto tra il vecchio prezzo di chiusura e il nuovo prezzo di apertura è utilizzato per adeguare proporzionalmente i prezzi dei contratti storici. Ciò consente un flusso continuo senza un’interruzione del calcolo dei ritorni percentuali. Il problema principale con l’adeguamento proporzionale è la necessità di applicare la stessa logica a qualsiasi trading basata su un livello di prezzo assoluto, al fine di eseguire il segnale corretto. Questo è un processo problematico e soggetto a errori. Quindi questo tipo di flusso continuo è spesso utile solo per una analisi statistica sommaria, al contrario della ricerca diretta di backtesting.

Rollover e serie Perpetual

La base di questo approccio consiste nel creare un contratto continuo come successione di contratti, considerano una percentuale ponderata linearmente di ciascun contratto su uno specifico numero di giorni, in modo da assicurare una transizione più fluida tra i contratti.

Per esempio, consideriamo 5 giorni di transizione. Il prezzo al giorno1, P1 è pari all’80% del prezzo del contratto più lontano (F1) e il 20% del prezzo del contratto più vicino (N1).

In modo analogo, al giorno 2 il prezzo è pari a: P2=0.6×F2+0.4×N2.

Al giorno 5 si ha: P5=0.0×F5+1.0×N5=N5

e quindi il contratto diventa continuo al prezzo più vicino. Dopo 5 giorni il contratto è transitato dal prezzo più lontano a quello più vicino.

Il problema con il metodo rollover consiste nel dovre effettuare operazioni tutti e 5 i giorni e quindi si ha un incremento dei costi di transazione days.

Implementazione del Roll-Return in Python e Pandas

Il resto dell’articolo si concentra sull’implementazione del metodo delle serie perpetue poiché è più appropriato per la fase di backtesting. È un modo utile per condurre ricerche sulle pipeline strategiche.

Vogliamo collegare  i contratti futures del WTI Crude Oil “near” e “lontano” (simbolo CL) per generare una serie di prezzi continui. Ad esempio possiamo considerare il contratto a breve termine è CLF2014 (gennaio) e il contratto lontano è CLG2014 (febbraio).

Per effettuare il download dei dati relativi ai Futures, ho utilizzato la lbreria Quandl. Assicurati di impostare il corretto ambiente virtuale di Python sul tuo sistema e installa il pacchetto Quandl digitando quanto segue nel terminale:

            pip install quandl
        

Ora che il pacchetto quandl è installato, dobbiamo usare NumPy e Pandas per eseguire la creazione dei roll-return. Se non hai installato NumPy o Pandas, ti consiglio di seguire il mio tutorial. Crea un nuovo file e inserisci le seguenti istruzioni:

            import datetime
import numpy as np
import panda as pd
import quandl
        
Il lavoro principale è svolto nella funzione futures_rollover_weights. Richiede una data di inizio (la prima data del contratto vicino), un dizionario delle date di regolamentazione del contratto (expiry_dates), i simboli dei contratti e il numero di giorni per il rinnovo del contratto (cinque, come default). Di seguito il codice di questa logica:
            def futures_rollover_weights(start_date, expiry_dates, contracts, rollover_days=5):
    """
    Si costruisce un DataFrame pandas che contiene pesi (tra 0,0 e 1,0)
    di posizioni contrattuali da mantenere per eseguire un rollover di rollover_days
    prima della scadenza del primo contratto. La matrice può quindi essere
    'moltiplicato' con un altro DataFrame contenente i prezzi di settle di ciascuno
    contratto al fine di produrre una serie temporali per un contratto future 
    continuo.
    """

    # Costruisci una sequenza di date a partire dalla data inizio del primo contratto
    # alla data di fine del contratto finale
    dates = pd.date_range(start_date, expiry_dates[-1], freq='B')

    # Crea il DataFrame 'roll weights' che memorizzerà i moltiplicatori per
    # ogni contratto (tra 0,0 e 1,0)
    roll_weights = pd.DataFrame(np.zeros((len(dates), len(contracts))),
                                index=dates, columns=contracts)
    prev_date = roll_weights.index[0]

    # Si scorre ogni contratto e si crea i pesi specifiche per ogni
    # contratto che dipende dalla data di settlement e dai rollover_days
    for i, (item, ex_date) in enumerate(expiry_dates.iteritems()):
        if i < len(expiry_dates) - 1:
            roll_weights.ix[prev_date:ex_date - pd.offsets.BDay(), item] = 1
            roll_rng = pd.date_range(end=ex_date - pd.offsets.BDay(),
                                     periods=rollover_days + 1, freq='B')

            # Crea una sequenza di pesi a finesta mobile (cioè [0.0,0.2, ..., 
            # 0.8,1.0] e si usano per regolare i pesi di ogni future
            decay_weights = np.linspace(0, 1, rollover_days + 1)
            roll_weights.ix[roll_rng, item] = 1 - decay_weights
            roll_weights.ix[roll_rng, expiry_dates.index[i+1]] = decay_weights
        else:
            roll_weights.ix[prev_date:, item] = 1
        prev_date = ex_date
    return roll_weights
        

Ora che la matrice pesata è è stata prodotta, è possibile applicarla alle singole serie temporali. La funzione principale scarica i contratti vicini e lontani, crea un singolo DataFrame per entrambi, costruisce la matrice del rollover ed infine produce una serie continua di entrambi i prezzi, opportunamente ponderata:

            if __name__ == "__main__":
    # Scarica gli attuali contratti future Front e Back (vicino e lontano)
    # per il petrolio WTI, negoziato al NYMEX, da Quandl.com. Avrai bisogno di
    # aggiustare i contratti per riflettere gli attuali contratti vicini / lontani
    # a seconda del punto in cui leggi questo!
    wti_near = quandl.get("OFDP/FUTURE_CLF2014")
    wti_far = quandl.get("OFDP/FUTURE_CLG2014")
    wti = pd.DataFrame({'CLF2014': wti_near['Settle'],
                        'CLG2014': wti_far['Settle']}, index=wti_far.index)

    # Crea un dizionario delle date di scadenza di ogni contratto
    expiry_dates = pd.Series({'CLF2014': datetime.datetime(2013, 12, 19),
                              'CLG2014': datetime.datetime(2014, 2, 21)}).order()

    # Calcolare la matrice (Dataframe) dei pesi di rollover
    weights = futures_rollover_weights(wti_near.index[0], expiry_dates, wti.columns)

    # Costruzione del future continuo dei contratti del petrolio WTI (CL)
    wti_cts = (wti * weights).sum(1).dropna()

    # Stammpa delle serie aggregate dei prezzi di settle dei contratti
    wti_cts.tail(60)
        

Eseguendo questo script si ottiene il seguente output:

Da notare come ora la serie è continua tra i due contratti. Il prossimo passo è eseguire questo per scadenze multiple per un buon numero di anni, a seconda delle vostre esigenze di backtesting.

 

Per il codice completo riportato in questo articolo utilizzando il modulo di backtesting vettoriale VectorBacktest si può consultare il seguente repository di github:
https://github.com/datatrading-info/VectorBacktest

Sviluppo di un BackTesting Vettoriale con Python e Pandas

Il backtesting è un  processo di ricerca che prevede di applicare l’idea di strategia di trading ai dati storici di uno o più sottostanti, al fine di verificare le performance della strategia nel  passato. In particolare, un backtest non fornisce alcuna garanzia circa le prestazioni future della strategia. Sono tuttavia una componente essenziale nel processo di ricerca della pipeline delle strategie, che consente di filtrare le strategie prima di essere inserite in produzione.

In questo articolo (e in quelli che seguono) voglio descrivere un semplice sistema di backtesting, orientato agli oggetti e scritto in Python. Questo sistema di base sarà principalmente un “sussidio didattico”, utilizzato per dimostrare i vari componenti di un sistema di backtesting. Man mano che procediamo attraverso gli articoli, verranno aggiunte funzionalità più sofisticate.

Panoramica di un Sistema di Backtesting

Il processo di progettazione di un robusto sistema di backtesting è estremamente difficile. Una efficace simulazione di tutte le componenti che influiscono sulle prestazioni di un sistema di trading algoritmico è molto impegnativa. La scarsa granularità dei dati, l’opacità del routing degli ordini all’interno di un broker, la latenza degli ordini e una miriade di altri fattori contribuiscono a modificare le prestazioni “reali” di una strategia rispetto alle prestazioni di backtesting.

Quando si implementa un sistema di backtesting si è tentati di voler costantemente “riscriverlo da zero” poiché sempre più fattori si rivelano cruciali nella valutazione delle prestazioni. Nessun sistema di backtesting si può dire completato, cioè è un sistema in continua evoluzione, e deve constantemente verificare che un numero sufficiente di fattori sia stato correttamente implementato nel sistema.

Tenendo presente questi vincoli, il sistema di backtesting descritto in questo articolo contiene delle semplificazioni. Durante l’esplorazione di aspetti avanzati (ottimizzazione del portafoglio, gestione del rischio, gestione dei costi di transazione), il sistema diventerà più solido.

Tipologie di Sistemi di Backtesting

Ci sono due principali tipologie di sistemi di backtest. Il primo è basato sulla ricerca, utilizzato principalmente nelle fasi iniziali, dove molte strategie saranno testate per selezionare quelle che meritano una valutazione più seria. Questi sistemi di backtesting di ricerca, chiamati  Explorers, sono spesso scritti in Python, R o MatLab poiché in questa fase la velocità e facilita di sviluppo del codice è più importante della velocità di esecuzione.

Il secondo tipo di sistema di backtest è basato sugli Event-based. Cioè, esegue il processo di backtest in un ciclo di esecuzione simile (se non identico) allo stesso sistema di esecuzione del trading. Modellerà realisticamente i dati di mercato e il processo di esecuzione degli ordini per fornire una valutazione più rigorosa di una strategia.

Questi ultimi sistemi sono spesso scritti in un linguaggio ad alte prestazioni come C ++ o Java, dove la velocità di esecuzione è essenziale. Per le strategie a bassa frequenza (anche se ancora intraday), Python è più che sufficiente per essere utilizzato anche in questo contesto.

Sistema di Backtesting Object-Oriented in Python

Vediamo ora la progettazione e l’implementazione di un ambiente di backtesting Explorer. Si vuole usare un paradigma di  programmazione orientata agli oggetti perchè permette di definire oggetti software in grado di interagire gli uni con gli altri attraverso lo scambio di messaggi.In particolare, questo approccio permette di:

  • specificare le interfacce di ciascun componente in anticipo, mentre le parti interne di ciascun componente possono essere modificate (o sostituite) durante l’evoluzione del progetto;
  • testare efficacemente come si comporta ciascun componente (tramite un unit test);
  • creare  nuovi componenti  sopra o in aggiunta agli altri, tramite l’ereditarietà e l’incaspulamento.

Il sistema è progettato per semplificare l’implementazione ed avere un ragionevole grado di flessibilità, a scapito della precisione. In particolare, questo backtester sarà in grado di gestire strategie che agiscono su un singolo strumento. Successivamente il backtester verrà modificato per gestire più strumenti.

Le componenti fondamentali di questo sistema sono le seguenti:

  • Strategia – è una classe che prevede in input un DataFrame pandas di barre, ovvero un elenco di dati Open-High-Low-Close-Volume (OHLCV) ad uno specifico timeframe. La strategia produrrà una lista di segnali, che consistono in un timestamp e un elemento dell’insieme {1,0, -1} che indica rispettivamente un segnale long, hold o short.
  • Portafoglio – La maggior parte del lavoro di backtesting avviene in questa classe. Riceve in input un insieme di segnali (come descritto sopra) e creerà una serie di posizioni, o ordini. Il compito delle Portfolio è di produrre una curva equity, incorporare i costi di transazione di base e tenere traccia delle operazioni.
    Prestazioni – prende un oggetto portfolio e produce una serie di statistiche sulle sue prestazioni. In particolare, produrrà caratteristiche di rischio / rendimento, metriche di trade / profit ed informazioni sui drawdown.

 

Come si può vedere, questo backtester non include alcun riferimento alla gestione del portafoglio o del  rischio, alla gestione dell’esecuzione (ad esempio non si gestisce i limit order ) né fornirà una sofisticata modellizzazione dei costi di transazione. Questo non è un grosso problema in questa fase. Ci consente di acquisire dimestichezza con il processo di creazione del di questo sistema e delle librerie Pandas / NumPy. Col tempo sarà poi ampliato con nuove componenti.

 

Implementazione

Vediamo ora l’implementazione di ciascuna delle componenti del sistema

Strategia

In questa fase, è necessario prevede un oggetto sufficientemente generico da poter gestire diverse tipologie di strategie, come mean-reversion, momentum e volatilità. Le strategie che vogliamo gestire devono essere basate sulle serie temporali, cioè “price driven“. Un requisito iniziale per questo tipo di  backtester è che le classi di strategie derivate dovranno accettere in input un elenco di DataFrame OHLCV, di tick (prezzi trade-by-trade) o i  dati degli order-book. Si prevede un limite inferiore di frenquenza dei trader ad 1 secondo.

Inoltre la classe Strategia  deve produrre avvisi sui segnali. Ciò significa che ‘avvisa’ un’istanza Portfolio della possibilità di andare long / short o mantenere una posizione. Questa flessibilità ci consentirà di creare più “explorers” di strategie che forniscono una serie di segnali, che una classe di Portfolio più avanzata può accettare per determinare quali ordini possono essere effettivamente immessi a mercato.

L’interfaccia delle classi è sviluppata utilizzando la metodologia abstract base class . Una classe base astratta è un oggetto che non può essere istanziato e quindi è possibile creare solo classi derivate. Il codice Python è riportato di seguito in un file chiamato backtest.py. La classe Strategy richiede che qualsiasi classe figlia implementi il metodo generate_signals.

Al fine di prevenire la possibilità che la classe Strategy sia inizializzata direttamente (dato che è astratta) è necessario usare gli oggetti ABCMeta e abstractmethod del modulo abc. In  particolare si introduce  una proprietà alla classe, chiamata __metaclass__ che corrisponde a ABCMeta e si applica il  decorate al metodo generate_signals con il decorator abstractmethod.

            # backtest.py

from abc import ABCMeta, abstractmethod

class Strategy(object):
    """
    Strategy è una classe base astratta che fornisce un'interfaccia per
    tutte le strategie di trading successive (ereditate).

    L'obiettivo di un oggetto Strategy (derivato) è produrre un elenco di segnali,
    che ha la forma di un DataFrame pandas indicizzato di serie temporale.

    In questo caso è gestito solo un singolo simbolo / strumento.
    """

    __metaclass__ = ABCMeta

    @abstractmethod
    def generate_signals(self):
        """
        È necessaria un'implementazione per restituire il DataFrame dei simboli
        contenente i segnali per andare long, short o flat (1, -1 o 0)
        """
        raise NotImplementedError("Should implement generate_signals()!")
        

Sebbene l’interfaccia di cui sopra sia molto semplice, diventerà più complicata quando questa classe verrà ereditata da ogni specifica tipologia di strategia. In definitiva, l’obiettivo della classe Strategy, in questa fase, è fornire un elenco di segnali long / short / hold per ogni strumento e poi inviare l’elenco da inviare a un portfolio.

Portafoglio

La classe Portfolio contiene la maggior parte della logica di trading. Per questo sistema di backtesting, il portafoglio è incaricato di determinare il dimensionamento delle posizioni, l’analisi dei rischi, la gestione dei costi di transazione e la gestione delle esecuzioni (vale a dire gli ordini market-on-open, market-on-close). In una fase successiva queste attività verranno suddivise in componenti separati. Per il momento, sono inserite all’interno di in una sola classe.

Questa classe fa ampio uso dei funzionalità offerte dalla libreria Pandas e fornisce un ottimo esempio di come questa libreria permette di risparmiare una quantità enorme di tempo, in particolare per quanto riguarda la trasformazione dei dati storici in un formato “standard”. Per  principale vantaggio di Pandas e NumPy consiste di evitare di accedere a qualsiasi set di dati usando la sintassi for d in .... Questo perché NumPy (che è alla base di Pandas) ottimizza il loop tramite operazioni vettorializzate.

L’obiettivo della classe Portfolio è di produrre una sequenza di ordini e una curva equity, che saranno analizzati dalla classe Performance. Per raggiungere questo obiettivo, è necessario fornire un elenco di ‘segnali’ da un oggetto Strategia. Più tardi, questo sarà un gruppo di oggetti strategici.

La classe del Portfolio deve prevedere un logica per determinare quanto capitale possa essere utilizzato per uno specifico sottinsieme di segnali, prevedere la gestione dei costi di transazione e determinare quale tipo di ordine utilizzare. Devono essere previste logiche per utilizzare il set di dati forniti dalla Strategia (barre OHLCV) per determinare il prezzo di esecuzione di un ordine. Poiché i prezzi high/low di ogni barra sono sconosciuti a priori, è possibile utilizzare solo i prezzi di apertura e chiusura per effettuare il trade. In realtà è impossibile garantire che un ordine sarà eseguito esattamente ad uno specifico prezzo quando si utilizza un ordine market, quindi sarà sempre un’approssimazione della realtà.

Oltre ai vincoli sull’esecuzione degli ordini, questo backtester ignorerà tutti i concetti di margin/brokerage e presumerà che sia possibile andare long o short con qualsiasi strumento, senza vincoli di liquidità. Questa è chiaramente un’ipotesi molto irrealistica, ma è una funzionalitò che può essere implementata in una seconda fase.

Di seguito, il codice da aggiungere al nostro backtester.py

            # backtest.py

class Portfolio(object):
    """
    Una classe base astratta che rappresenta un portfolio di
    posizioni (inclusi strumenti e contanti), determinate
    sulla base di una serie di segnali forniti da una Strategy
    """

    __metaclass__ = ABCMeta

    @abstractmethod
    def generate_positions(self):
        """
        Fornisce la logica per determinare come le posizioni del 
        portafoglio sono allocate sulla base dei segnali 
        previsionali e dei contanti disponibili
        """
        raise NotImplementedError("Should implement generate_positions()!")

    @abstractmethod
    def backtest_portfolio(self):
        """
        Fornisce la logica per generare gli ordini di trading
        e la successiva curva del patrimonio netto (ovvero la 
        crescita del patrimonio netto totale), come somma 
        di partecipazioni e contanti, e il periodo delle barre
        associato a questa curva in base al DataFrame delle "posizioni".

        Produce un oggetto portfolio che può essere esaminato da
        altre classi / funzioni.
        """
        raise NotImplementedError("Should implement backtest_portfolio()!")
        
A questo punto, dopo aver introdotto le classi astratte Strategy e Portfolio abstract, possiamo ora creare alcuni concrete classi derivate da queste due, in modo da implementare una strategia funzionante. Iniziano con il definire una classe RandomForecastStrategy, derivata da Strategy, che prevedere di produrre segnali semplicemente scegliendo a caso (random) long o short! Chiaramente questa strategia non può funzionare nel mercato, ma è utile per scopi dimostrativi. Si crea quindi un nuovo file, chiamato random_forecast.py, con il codice per implementa la logica random della strategia:
            # random_forecast.py

import numpy as np
import pandas as pd
import quandl   # Necessary for obtaining financial data easily

from backtest import Strategy, Portfolio

class RandomForecastingStrategy(Strategy):
    """
    Classe derivata da Strategy per produrre un insieme di segnali che
    sono long / short generati casualmente. Chiaramente una strategia non 
    corretta, ma perfettamente accettabile per dimostrare il
    infrastruttura di backtest!
    """    
    
    def __init__(self, symbol, bars):
    	"""Necessita del ticker del simbolo e il dataframe delle barre"""
        self.symbol = symbol
        self.bars = bars

    def generate_signals(self):
        """Creazione del DataFrame pandas dei segnali random."""
        signals = pd.DataFrame(index=self.bars.index)
        signals['signal'] = np.sign(np.random.randn(len(signals)))

        # I primi cinque elementi sono impostati a zero in modo da minimizzare
        # la generazione di errori NaN nella previsione.
        signals['signal'][0:5] = 0.0
        return signals
        

Ora che abbiamo una  strategia “concreta”, dobbiamo creare un’implementazione dell’oggetto Portfolio. Questo oggetto comprenderà la maggior parte del codice di backtesting. È progettato per creare due DataFram separati, il primo, chiamato positions, utilizzato per memorizzare la quantità detenuta di ogni strumento ad ogni specifica barra. Il secondo, portfolio, contiene in realtà il prezzo market di tutte le posizioni per ciascuna barra, nonché un conteggio del denaro contante, a partire da uno specifico capitale iniziale. Questo alla fine fornisce una curva equity su cui valutare la performance della strategia.

L’oggetto Portfolio, sebbene estremamente flessibile nella sua interfaccia, richiede scelte specifiche su come gestire i costi di transazione, gli ordini a mercato, ecc. In questo esempio di base ho considerato la possibilità di andare facilmente long/short su uno strumento, senza restrizioni o margine, di acquistare o vendere direttamente al prezzo di apertura della barra, senza costi di transazione (compresi slippage e commisioni) e di specificare direttamente la quantità di azioni da acquistare ad ogni operazione.

Di seguito il codice da aggiungere a random_forecast.py:

            # random_forecast.py

class MarketOnOpenPortfolio(Portfolio):
    """
    Eredita la classe Portfolio per creare un sistema che acquista 100 unità di
    uno specifico simbolo per un segnale long / short, utilizzando il prezzo 
    open di una barra.

    Inoltre, non ci sono costi di transazione e il denaro può essere immediatamente
    preso in prestito per vendita allo scoperto (nessuna registrazione di 
    margini o requisiti di interesse).

    Richiede:
    symbol - Un simbolo di una azione che costituisce la base del portafoglio.
    bars - Un DataFrame di barre per un simbolo.
    signals - Un DataFrame panda di segnali (1, 0, -1) per ogni simbolo.
    initial_capital - L'importo in contanti all'inizio del portafoglio.
    """

    def __init__(self, symbol, bars, signals, initial_capital=100000.0):
        self.symbol = symbol        
        self.bars = bars
        self.signals = signals
        self.initial_capital = float(initial_capital)
        self.positions = self.generate_positions()
        
    def generate_positions(self):
    	"""
        Crea un DataFrame "positions" che semplicemente è long o short 
        per 100 pezzi di uno specfico simbolo basato sui segnali di 
        previsione di {1, 0, -1} dal DataFrame dei segnali.
        """
        positions = pd.DataFrame(index=self.signals.index).fillna(0.0)
        positions[self.symbol] = 100*self.signals['signal']
        return positions
                    
    def backtest_portfolio(self):
    	"""
        Costruisce un portafoglio a partire dal Dataframe delle posizioni 
        assumendo la capacità di negoziare all'esatto prezzo open di ogni 
        barra (un'ipotesi irrealistica!).

        Calcola il totale della liquidità e delle partecipazioni (prezzo 
        di mercato di ogni posizione per barra), al fine di generare una 
        curva di equity ('totale') e una serie di rendimenti basati sulle
        barre ('ritorni').

        Restituisce l'oggetto portfolio da utilizzare altrove.
        """

        # Costruzione di un DataFrame 'portfolio' che usa lo stesso indice
        # delle "posizioni" e con una serie di "ordini di trading" 
        # nell'oggetto 'pos_diff', assumendo prezzi open.
        portfolio = self.positions*self.bars['Open']
        pos_diff = self.positions.diff()

        # Crea le serie "holding" e "cash" scorrendo il dataframe 
        # delle operazioni e aggiungendo / sottraendo la relativa quantità di
        # ogni colonna
        portfolio['holdings'] = (self.positions*self.bars['Open']).sum(axis=1)
        portfolio['cash'] = self.initial_capital - 
                          (pos_diff*self.bars['Open']).sum(axis=1).cumsum()

        # Finalizza i rendimenti totali e basati su barre in base al "contante"
        # e dati sulle "partecipazioni" per il portafoglio        
        portfolio['total'] = portfolio['cash'] + portfolio['holdings']
        portfolio['returns'] = portfolio['total'].pct_change()
        return portfolio
        

 

Questo di permette di generare una curva equity del sistema.

Lo step finale prevede di collegare tutto insieme con una funzione  __main__:

            if __name__ == "__main__":
    # Ottenere le barre giornaliere di SPY (ETF che generalmente
    # segue l'S&P500) da Quandl (richiede 'pip install Quandl'
    # sulla riga di comando)
    symbol = 'SPY'
    bars = quandl.get("GOOG/NYSE_%s" % symbol, collapse="daily")

    # Crea un insieme di segnali randome per SPY
    rfs = RandomForecastingStrategy(symbol, bars)
    signals = rfs.generate_signals()

    # Crea un portfolio di SPY
    portfolio = MarketOnOpenPortfolio(symbol, bars, signals, 
                                      initial_capital=100000.0)
    returns = portfolio.backtest_portfolio()

    print(returns.tail(10))
        

The output dello script è riportato di seguito. Questo output dipende ovviamente dal range di tempo considerato e il generatore random utilizzato:

In questo caso la strategia ha perso denaro, il che non sorprende vista la natura stocastica del generatore di segnali! Il  passo successivo consiste nel creare un oggetto Performance che accetta un’istanza Portfolio e fornisce un report delle metriche sul rendimento su cui basare le valutazioni su come filtrare la strategia. Possiamo anche migliorare l’oggetto portfolio per avere una gestione più realistica dei costi di transazione (come le commissioni di Interactive Brokers e lo slippage). Possiamo anche includere direttamente una logica ‘più realistica’ in una strategia, che (si spera) produrrà risultati migliori. Nei prossimi articoli esploreremo questi concetti in modo più approfondito.

 

Per il codice completo riportato in questo articolo utilizzando il modulo di backtesting vettoriale VectorBacktest si può consultare il seguente repository di github:
https://github.com/datatrading-info/VectorBacktest

Implementazione di un Database di Securities Master con MySQL e Python

Ora aver introdotto l’idea alla base di un securities master database, vediamo è ora come implementarne uno. Per questo faremo uso di due tecnologie open source: il database MySQL e il linguaggio di programmazione Python. Alla fine di questo articolo avrete un vero e proprio database master di titoli azionari con cui condurre ulteriori analisi alla ricerca di una strategia di trading quantitativo.

I vantaggi di un Securities Master Database

Prima di iniziare, ricapitoliamo i vantaggi che si ottengo grazie all’introduzione di un securities master database nel nostro sistema di trading quantitativo:

  • Velocità – con i dati azionari memorazzati su un rigido locale, qualsiasi applicazione di analisi dati (come pandas) può accedere rapidamente ai dati senza dover eseguire lente operazioni di input / output (I / O) attraverso un collegamento di rete latente.
  • Fonti Multiple: i securities master database consentono la memorizzazione diretta da più sorgenti dati per lo stesso ticker. Quindi possiamo aggiungere un codice personalizzato per la correzione degli errori e / o tracciare i dati che devono essere controllati manualmente.
  • Tempo di inattività – Se ci affidiamo a una connessione Internet per i nostri dati, nel caso il fornitore (per qualsiasi motivo) stia affrontando un  periodo di inattività, non sarà possibile effettuare ricerche. Un database locale, con un sistema di backup, è sempre disponibile.
  • Meta-dati: un securities master database permette di memorizzare metadati sulle informazioni del nostro ticker. Possiamo includere tabelle degli exchange, fornitori e simboli di corrispondenza, aiutandoci a minimizzare gli errori dalle sorgenti dati.
 Esistono molti altri motivi per archiviare i dati localmente (o almeno su un server remoto) anziché fare affidamento sulle connessioni API ad un fornitore di dati. Un securities master fornisce il modello su cui costruire l’intero archivio dati delle nostre applicazioni di trading algoritmico. Tuttavia, ai fini di questo articolo ci concentreremo sulla memorizzazione dei dati storici giornalieri.
 

MySQL per i Securities Master Database

Per costruire ed interagire con un securities master database si può utilizzare MySQL e Python/pandas. Non mi soffermerò sulle specifiche di installazione di ciascuno di questi strumenti, poiché la procedura di installazione è piuttosto specifica per ogni piattaforma. Tuttavia, ti indicherò alcune guide che ti saranno sicuramente di aiuto per installare il software.

Installare MySQL

Per installare MySQL, è necessario selezionare la piattaforma appropriata:

  • Windows – Per le informazioni sulla procedura di installazione  di MySQL su Microsoft Windows, si può consultare la documentazione di MySQL. Se vuoi scaricare i file binari disponibili per Windows, puoi consultare questa pagina.
  • Mac OSX: è possibile scaricare i file binari per Mac OSX nella pagina dei download di MySQL. In alternativa, puoi installare MySQL tramite homebrew.
  • Linux / UNIX: puoi scegliere se scaricare un file binario dalla tua distribuzione o compilare dai codici sorgente. Su un sistema Debian / Ubuntu puoi digitare sudo apt-get install mysql-server. Se si utilizza una distribuzione basata su RPM come Fedora o Cent OS, è possibile digitare yum install mysql-server.

Creare un nuovo database e utente

Ora che MySQL è installato sul tuo sistema, possiamo creare un nuovo database e un utente per interagire con esso. All’installazione ti verrà richiesta una password di root. Per accedere a MySQL dalla riga di comando, si può utilizzare il seguente comando e inserire la password:

            $ mysql -u root -p
        

Una volta effettuato l’accesso a MySQL, puoi creare un nuovo database chiamato securities_master e selezionarlo:

            mysql> CREATE DATABASE securities_master; 
mysql> USE securities_master;
        
I database documentali/NoSQL, sebbene non siano un nuovo concetto, negli ultimi anni hanno acquisito una notevole importanza grazie a loro utilizzo da parte dei giganti del web come Google, Facebook e Twitter. Differiscono sostanzialmente dai sistemi RDBMS in quanto non esiste alcun concetto di schemi di tabelle. Invece, ci sono collezioni e documenti, che sono le analogie più vicine, rispettivamente, alle tabelle e ai record. Esiste un’ampia tassonomia di archivi documentali, la cui discussione è ben al di fuori di questo articolo! Tuttavia, alcuni delle soluzioni più popolari sono MongoDBCassandra e CouchDB. I database documentali, nelle applicazioni finanziarie, sono adatti principalmente ai dati fondamentali o ai metadati. I dati fondamentali per le attività finanziarie sono disponibili in molte forme, come azioni aziendali, dichiarazioni di guadagni, archivi SEC ecc. Pertanto, la natura senza schema dei DB NoSQL è particolarmente adatta. Tuttavia, i DB NoSQL non sono ben progettati per le serie temporali come i dati sui prezzi ad alta risoluzione e quindi non li prenderemo in considerazione per tale scopo.

Una volta creato un database è necessario aggiungere un nuovo utente per interagire con il database. Anche se è possibile utilizzare l’utente root, è considerato una cattiva pratica dal punto di vista della sicurezza, in quanto garantisce troppe autorizzazioni e può portare a un sistema compromesso. Su una macchina locale questo è per lo più irrilevante, ma in un ambiente di produzione remoto sarà certamente necessario creare un utente con autorizzazioni ridotte. In questo caso il nostro utente verrà chiamato sec_user. Ricordarsi di sostituire ‘password’ con una password sicura:

            mysql> CREATE USER 'sec_user'@'localhost' IDENTIFIED BY 'password'; 
mysql> GRANT ALL PRIVILEGES ON securities_master.* TO 'sec_user'@'localhost'; 
mysql> FLUSH PRIVILEGES;
        

Con le tre righe precedenti si crea e si autorizza l’utente ad usare il db ‘securities_master’ e si applica tali privilegi. D’ora in poi qualsiasi interazione che si verifica con il database si dovrà far uso dell’utente ‘sec_user’.

Progettare lo schema per i Securities Master Azionari

Dopo aver installato MySQL e configurato un utente con cui interagire con il nostro database. In questa fase siamo pronti a costruire le tabelle necessarie per memorizzare i nostri dati finanziari. Per un semplice e diretto securities master azionario sono sufficienti quattro tabelle:

  • Exchange – La tabella elenca gli exchange da cui vogliamo ottenere le informazioni sui prezzi delle azioni. In questo caso sarà quasi esclusivamente la Borsa di New York (NYSE) e la National Association of Securities Dealers Automated Quotations (NASDAQ).
  • DataVendor – questa tabella elenca le informazioni sui fornitori di dati dei prezzi storici. Useremo Yahoo Finance per trovare i nostri dati di fine giornata (EOD). Introducendo questa tabella, rendiamo semplice aggiungere altri fornitori, se necessario, come ad esempio Google Finance.
  • Symbol: la tabella dei simboli memorizza l’elenco dei simboli dei ticker e delle informazioni aziendali. In questo momento eviteremo problemi come classi di azioni differenti e nomi di simboli multipli. Tratteremo tali questioni in articoli successivi!
  • DailyPrice – Questa tabella memorizza le informazioni dei prezzo giornaliero per ogni security. Può diventare molto grande se vengono aggiunti molti titoli. Quindi è necessario ottimizzare le prestazioni.

MySQL è un database estremamente flessibile in quanto consente di personalizzare la modalità di archiviazione dei dati tramite vari tipi di tabelle.  Le più usate sono senza dubbio le MyISAM e InnoDB. Anche se non entrerò nei dettagli dei vari tipi di tabelle gestite da MySQL (ce ne sono molte!), si può dire che MyISAM è più utile per una rapida lettura (come l’interrogazione sulle grandi quantità di informazioni relative ai prezzi), ma non supporta le transazioni (necessarie per il rollback completo di un’operazione a più passaggi che può fallire durante l’elaborazione). InnoDB permette la gestione delle foreign key e della transazionalità, con le quali è possibile creare una base di dati relazionale e transazionale, ma sono più lente a causa delle funzionalità aggiuntive di cui dispongono.

InnoDB consente inoltre il blocco a livello di riga durante le operazioni di scrittura, mentre MyISAM blocca l’intera tabella durante la scrittura. Questo può avere problemi di prestazioni quando si scrivono molte informazioni su punti arbitrari nella tabella (come con le istruzioni UPDATE). Questo è un argomento molto vasto, quindi rimando la discussione per un’altro articolo.

Utilizzeremo InnoDB poiché è nativamente più sicuro per le transazioni e offre il blocco a livello di riga. Nel caso una tabella fosse lenta da leggere, possiamo in prima analisi creare degli indici e solo nel caso le prestazioni fossero ancora poco soddisfacenti si procederebbe alla modifica del tipo di tabella. Tutte le nostre tabelle utilizzeranno il set di caratteri UTF-8, poiché desideriamo supportare gli exchange internazionali. Puoi leggere ulteriori informazioni sulla codifica UTF-8 in questa pagina di Wikipedia.

Iniziamo con schema e l’istruzione CREATE TABLE del linguaggio SQL per creazione della tabella exchange. Questa tabella memorizza la sigla e il nome dello exchange (ad esempio NYSE – New York Stock Exchange) così come la posizione geografica. Prevede anche una valuta e una differenza di fuso orario da UTC. Per scopi interni memorizziamo anche una data di creazione e dell’ultimo aggiornamento. Infine, impostiamo la chiave dell’indice primario come un numero intero a incremento automatico (che è sufficiente per gestire i record 232):

            CREATE TABLE `exchange` (
  `id` int NOT NULL AUTO_INCREMENT,
  `abbrev` varchar(32) NOT NULL,
  `name` varchar(255) NOT NULL,
  `city` varchar(255) NULL,
  `country` varchar(255) NULL,
  `currency` varchar(64) NULL,
  `timezone_offset` time NULL,
  `created_date` datetime NOT NULL,
  `last_updated_date` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
        
Di seguito lo schema e l’istruzione CREATE TABLE di SQL per la tabelladata_vendor. Questa memorizza il nome, il sito web e l’email del fornitore dei dati. In caso di necessità potremmo aggiungere più informazioni, come l’API endpoint URL:
            CREATE TABLE `data_vendor` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL,
  `website_url` varchar(255) NULL,
  `support_email` varchar(255) NULL,
  `created_date` datetime NOT NULL,
  `last_updated_date` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
        

Successivamente si passa allo schema e l’istruzione CREATE TABLE di SQL per la tabella symbol. Questa tabella contiene un link alla foreign key di un exchange (in questo articolo ci focalizziamo solamente sui strumenti tradati degli exchange), un simbolo di  ticker (ad esempio GOOG), il tipo di strumento (‘azione’ o ‘indice’), il nome del titolo azionario o dell’indici del mercato, il settore e la valuta di riferimento.

            CREATE TABLE `symbol` (
  `id` int NOT NULL AUTO_INCREMENT,
  `exchange_id` int NULL,
  `ticker` varchar(32) NOT NULL,
  `instrument` varchar(64) NOT NULL,
  `name` varchar(255) NULL,
  `sector` varchar(255) NULL,
  `currency` varchar(32) NULL,
  `created_date` datetime NOT NULL,
  `last_updated_date` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_exchange_id` (`exchange_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
        

Infine si riporta lo schema e l’istruzione CREATE TABLE di SQL per la tabella daily_price. In questa tabella vengono memorizzati i dati storici dei prezzi. Il nome della tabella ha il prefisso daily_ poiché potremmo voler creare dati con diverse risoluzioni temporali (come minuti o secondi) e quindi gestirli in tabelle separate, in caso successivamente si vuole implementare strategie con frequenze più elevate. La tabella contiene due chiavi esterne: una verso il fornitore di dati e l’altra verso un simbolo. In questo modo si identifica in modo univoco il dato e ci consente di memorizzare nella stessa tabella gli stessi dati di prezzo per più fornitori. Per i nostri scopi memorizziamo anche la data del prezzo (vale a dire il periodo giornaliero su cui i dati OHLC sono validi) e le date di creazione e di ‘ultima aggiornamento.

I campi rimanenti memorizzano i prezzi di apertura-massimo-minimo-chiusura ti-alti-bassi e il prezzo di chiusura aggiustato. Alcuni fornitori di dati calcola anche i dividendi e scissioni, e quindi memorizziamo il prezzo corretto nella colonna adj_close_price. Da notare che il tipo di dato corrisponde a decimal(19,4). Quando si tratta di dati finanziari è assolutamente necessario essere precisi. Se avessimo usato il tipo di dati float, avremmo riscontrato errori di arrotondamento dovuti alle modalità con le quali i dati float sono archiviati internamente. Il campo finale memorizza il volume di scambi giornalieri. Questo usa il tipo di dati bigint in modo da non troncare accidentalmente giorni di volumi estremamente elevati.

            CREATE TABLE `daily_price` (
  `id` int NOT NULL AUTO_INCREMENT,
  `data_vendor_id` int NOT NULL,
  `symbol_id` int NOT NULL,
  `price_date` datetime NOT NULL,
  `created_date` datetime NOT NULL,
  `last_updated_date` datetime NOT NULL,
  `open_price` decimal(19,4) NULL,
  `high_price` decimal(19,4) NULL,
  `low_price` decimal(19,4) NULL,
  `close_price` decimal(19,4) NULL,
  `adj_close_price` decimal(19,4) NULL,
  `volume` bigint NULL,
  PRIMARY KEY (`id`),
  KEY `index_data_vendor_id` (`data_vendor_id`),
  KEY `index_synbol_id` (`symbol_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
        

Inserendo tutti i precedenti comandi SQL nella riga di comando MySQL verranno create le quattro tabelle previste.

Usare Python/Pandas per l'interazione con i Securities Master

Per iniziare a popolare il nostro database è necessario installare Python e Panda.

Installare Python/Pandas

Il modo migliore per installare Python consiste nell’utilizzare lo strumento dell’ambiente virtuale virtualenv e il gestore di pacchetti pip. Per installare Python in questo modo, è necessario attenersi alla seguente procedura:
  • Windows: visita la pagina Download Python per scaricare una versione di Python. Raccomando di usare l’ultima versione stabile disponibile di Python3. Una volta installato Python, è necessario scaricare setuptools. I passaggi finali consistono nell’eseguire easy_install pip e pip install virtualenv nella shell dei comandi.
  • Mac OSX – Il modo migliore per installare Python su Mac è usare homebrew. Quindi puoi installare Python tramite brew install python. Quindi è necessario eseguire pip install virtualenv per installare virtualenv.
  • Linux / UNIX – Per le distribuzioni di tipo Debian / Ubuntu sudo apt-get install python3-pip python3-dev per installare pip e le librerie di sviluppo Python. Quindi eseguire pip install virtualenv per installare virtualenv a livello globale.

Una volta installato virtualenv, è possibile creare un nuovo ambiente virtuale Python in una directory separata e quindi installare pandas (comandi per un ambiente UNIX):

            $ cd ~
$ mkdir -p python-apps/trading
$ cd python-apps/trading
$ virtualenv .
$ source bin/activate
$ pip install pandas
        
Il passaggio finale è installare la libreria Python-MySQL. Sulle macchine Debian/Ubuntu bisogna eseguire i seguenti comandi:
            sudo apt-get install default-libmysqlclient-dev
pip install mysqlclient
        
Ora siamo pronti per iniziare a interagire con il nostro database MySQL tramite Python e Pandas.

Usare un Object-Relational Mapper

Quelli di voi che hanno un background tecnico e sono familiari con lo sviluppo e la gestione di database, si potranno chiedere se sia più ragionevole utilizzare un Object-Relational Mapper (ORM). Un ORM consente agli oggetti all’interno di un linguaggio di programmazione di essere mappati direttamente nelle tabelle nei database in modo tale che il codice del programma sia completamente inconsapevole del motore di archiviazione sottostante. Tali software non sono esenti da errori, ma sono comunque in grado di far risparmiare molto tempo. Tuttavia, il risparmio di tempo viene generalmente a discapito delle prestazioni.

Un ORM popolare per Python è SQLAlchemy. Permette di specificare lo schema del database all’interno di Python stesso e quindi genera automaticamente il codice CREATE TABLE. Poiché abbiamo scelto specificamente MySQL e sono interessato alle prestazioni, ho scelto di non utilizzare un ORM per questo articolo.

Ottenere i dati dei simboli listati

Iniziamo con il recuperare i simboli associati all’elenco di Standard & Poor’s dei 500 titoli a grande capitalizzazione, ad esempio S&P500. Naturalmente, questo è semplicemente un esempio. Se stai operando sul mercato italiano e desideri utilizzare gli indici domestici dell’Italia, puoi anche ottenere l’elenco delle società FTSE MIB quotate alla Borsa di Milano (LSE).

Inoltre Wikipedia elenca le componenti del S&P500. Analizzeremo questo sito web usando la libreria lxml di Python ed aggiungeremo direttamente il contenuto direttamente al database in MySQL. Innanzitutto assicurati che la libreria sia installata:

            pip install lxml
        

Il seguente codice utilizzerà la libreria lxml e aggiungerà i simboli direttamente al database MySQL creato in precedenza. Ricordarsi di sostituire “password” con la password scelta durante la creazione dell’utente del db:

            #!/usr/bin/python
# -*- coding: utf-8 -*-
#!/usr/bin/python
# -*- coding: utf-8 -*-

import datetime
import lxml.html
import PyMySQL as mdb

from math import ceil


def obtain_parse_wiki_snp500():
    """
    Scarica e analizza l'elenco dei costituenti
    dell'S&P500 da Wikipedia utilizzando le librerie
    requests e libxml.

    Restituisce un elenco di tuple da aggiungere a al
    database MySQL.
    """

    # Memorizza l'ora corrente, per il record created_at
    now = datetime.datetime.utcnow()

    # Usa libxml per scaricare l'elenco delle società S&P500 e ottenere
    # la tabella dei simboli
    page = lxml.html
               .parse('http://en.wikipedia.org/wiki/List_of_S%26P_500_companies')
    symbolslist = page.xpath('//table[1]/tr')[1:]

    # Ottenere le informazioni sui simboli per ogni riga nella tabella 
    # dei componenti S&P500
    symbols = []
    for symbol in symbolslist:
        tds = symbol.getchildren()
        sd = {'ticker': tds[0].getchildren()[0].text,
            'name': tds[1].getchildren()[0].text,
            'sector': tds[3].text}

        # Crea una tupla (per il formato DB) e la aggiunge alla lista
        symbols.append( (sd['ticker'], 'stock', sd['name'],
          sd['sector'], 'USD', now, now) )
    return symbols

def insert_snp500_symbols(symbols):
    """Inserimento dei simboli dell'S&P500 nel database MySQL."""

    # Connessione all'instanza di MySQL
    db_host = 'localhost'
    db_user = 'sec_user'
    db_pass = 'password'
    db_name = 'securities_master'
    con = mdb.connect(host=db_host, user=db_user, passwd=db_pass, db=db_name)

    # Creazione delle stringe per l'insert
    column_str = 
    'ticker, instrument, name, sector, currency, created_date, last_updated_date'
    insert_str = ("%s, " * 7)[:-2]
    final_str = "INSERT INTO symbol (%s) VALUES (%s)" % (column_str, insert_str)
    print(final_str, len(symbols))

    # Usando la connessione MySQL, si effettua un INSERT INTO per ogni simbolo
    with con:
        cur = con.cursor()
        # Questa riga evita MySQL MAX_PACKET_SIZE
        # Anche se ovviamente potrebbe essere impostato più grande!
        for i in range(0, int(ceil(len(symbols) / 100.0))):
            cur.executemany(final_str, symbols[i*100:(i+1)*100-1])

if __name__ == "__main__":
    symbols = obtain_parse_wiki_snp500()
    insert_snp500_symbols(symbols)
        

A questo punto tutti gli attuali 500 simboli che compongono l’indice S&P500 sono inseriti nel database. Il nostro prossimo obiettivo sarà quello di ottenere lo storico dei prezzi da diverse fonti e collegarli ai simboli appena salvati.

Recuperare i dati storici

Per ottenere i dati storici degli attuali titoli che compongono l’S&P500, dobbiamo prima interrogare il database per farci restituire l’elenco di tutti i simboli. Una volta ottenuto l’elenco dei simboli (insieme agli ID dei simboli), è possibile richiamare l’API di Yahoo Finance e scaricare lo storico dei prezzi da ciascun simbolo. Quindi possiamo inserire i dati nel database per ogni simboli ottenuto. Ecco il codice Python che effettua queste operazioni:

            #!/usr/bin/python
# -*- coding: utf-8 -*-

import datetime
import pymysql as mdb
from urllib.request import urlopen

# create una connessione ad un'instanza del database MySQL
db_host = 'localhost'
db_user = 'sec_user'
db_pass = 'password'
db_name = 'securities_master'
con = mdb.connect(db_host, db_user, db_pass, db_name)


def obtain_list_of_db_tickers():
    """Ottenere una lista di ticker dalla tabella Symbols del database."""
    with con:
        cur = con.cursor()
        cur.execute("SELECT id, ticker FROM symbol")
        data = cur.fetchall()
        return [(d[0], d[1]) for d in data]


def get_daily_historic_data_yahoo(ticker,
                                  start_date=(2000, 1, 1),
                                  end_date=datetime.date.today().timetuple()[0:3]):
    """
    Ricavare i dati da Yahoo Finance e restituisce una lista di tuple.

    ticker: simbolo di un ticker di Yahoo Finance, e.g. "GOOG" for Google, Inc.
    start_date: data iniziale nel formato (YYYY, M, D)
    end_date: data finale nel formato (YYYY, M, D)
    """

    # Construzione del URL di Yahoo con la corretta query di parametri integer
    # per le date di inizio e fine. Da notare che alcuni parametri sono base zero!
    yahoo_url = "http://ichart.finance.yahoo.com/table.csv?s=%s&a=%s&b=%s&c=%s&d=%s&e=%s&f=%s" % \
                (ticker, start_date[1] - 1, start_date[2], start_date[0], end_date[1] - 1, end_date[2], end_date[0])

    # Prova di connessione a Yahoo Finance e ottenere i dati
    # In caso di mancata ricezione si stampa un messaggio di errore.
    try:
        yf_data = urlopen(yahoo_url).readlines()[1:]  # Ignora l'header
        prices = []
        for y in yf_data:
            p = y.strip().split(',')
            prices.append((datetime.datetime.strptime(p[0], '%Y-%m-%d'),
                           p[1], p[2], p[3], p[4], p[5], p[6]))
    except Exception as e:
        print("Could not download Yahoo data: %s" % e)
    return prices


def insert_daily_data_into_db(data_vendor_id, symbol_id, daily_data):
    """
    Prende una lista di tuples di dati giornalieri e il inserisce nel 
    database MySQL. Si aggiunge un vendor ID e un symbol ID nei dati.

    daily_data: Lista di tuples di dati OHLC (con adj_close e volume)
    """

    # Ottenere l'ora attuale
    now = datetime.datetime.utcnow()

    # Creazione dei dati giornalieri con vendor ID e symbol ID
    daily_data = [(data_vendor_id, symbol_id, d[0], now, now,
                   d[1], d[2], d[3], d[4], d[5], d[6]) for d in daily_data]

    # Creazione delle stringhe di insert
    column_str = """data_vendor_id, symbol_id, price_date, created_date, 
          last_updated_date, open_price, high_price, low_price, 
          close_price, volume, adj_close_price"""
    insert_str = ("%s, " * 11)[:-2]
    final_str = "INSERT INTO daily_price (%s) VALUES (%s)" % (column_str, insert_str)

    # Uso della connessione di MySQL per eseguire un INSERT INTO per ogni simbolo
    with con:
        cur = con.cursor()
        cur.executemany(final_str, daily_data)


if __name__ == "__main__":
    # Ciclo su tutti i ticker e inserimento dei dati storici
    # giornalieri nel database
    tickers = obtain_list_of_db_tickers()
    for t in tickers:
        print("Adding data for %s" % t[1])
        yf_data = get_daily_historic_data_yahoo(t[1])
        insert_daily_data_into_db('1', t[0], yf_data)
        

Da notare che ci sono sicuramente molti per ottimizzare questo codice. Ad esempio si può utilizzare la libreria Python ScraPy per ottenere dei download ad elevata concorrenza, dato che ScraPy è basato sul framework ad eventi chiamato Twisted

Nel nostro codice ogni download viene eseguito in sequenza.

Interfaccia Python/Pandas per i dati dei prezzi

Ora che abbiamo scaricato lo storico dei prezzi per tutti i titoli che compongono l’S&P500, vogliamo poter accedere a questi dati tramite strutture logiche implementante in Python. La libreria pandas rende passaggio questo estremamente semplice. Ecco uno script per ottiene i dati OHLC del titolo  Google in un determinato periodo di tempo dal nostro database master dei titoli e restituisce il tail del dataset:

            #!/usr/bin/python
# -*- coding: utf-8 -*-

import pandas as pd
import pandas.io.sql as psql
import pymysql as mdb


# Connessione all'instanza di MySQL
db_host = 'localhost'
db_user = 'sec_user'
db_pass = 'password'
db_name = 'securities_master'
con = mdb.connect(db_host, db_user, db_pass, db_name)

# Selezione di tutti i dati storici di Google con il campo "adjusted close"
sql = """SELECT dp.price_date, dp.adj_close_price
         FROM symbol AS sym
         INNER JOIN daily_price AS dp
         ON dp.symbol_id = sym.id
         WHERE sym.ticker = 'GOOG'
         ORDER BY dp.price_date ASC;"""

# Creazione di un dataframe pandas dalla query SQL
goog = psql.frame_query(sql, con=con, index_col='price_date')

# Stampa della coda del dataframe
print(goog.tail())
        

L’output dello script è il seguente:

price_date              adj_close_price
2013-05-20            908.53
2013-05-21            906.97
2013-05-22            889.42
2013-05-23            882.79
2013-05-24            873.32

Ovviamente questo è solo un semplice script, ma mostra tutte le potenzialità di un securities master archiviato localmente. Con tale approccio è possibile testare in modo estremamente rapido alcune strategie, poiché l’I / O dal database sarà notevolmente più veloce rispetto a quello effettuato tramite una connessione Internet.

Il prossimo passo è quello di automatizzare la raccolta di dati in modo che ogni simbolo abbia i dati OHLC aggiornati dopo la chiusura di ogni giorno di negoziazione. Utilizzando un programma di pianificazione delle attività come Windows Task Scheduler o crontab, questo processo può essere programmato per essere eseguito in background. Ci porterà un passo avanti verso la creazione di un sistema di trading completamente automatizzato.

 

Per il codice completo riportato in questo articolo utilizzando il modulo di backtesting vettoriale VectorBacktest si può consultare il seguente repository di github:
https://github.com/datatrading-info/VectorBacktest

Base di Dati di Securities Master per il Trading Algoritmico

Solitamente, nel trading algoritmico i riflettori sono puntati sul componente del completo sistema di trading che implementa il modello alpha. Questa è la parte del sistema che genera i segnali di trading, prima della filtrazione tramite un sistema di gestione dei rischi o di costruzione del portafoglio. Di conseguenza, gli algotrader spendono una parte significativa del loro tempo alla ricerca di ottimizzazioni del modello alpha, in modo da generare un maggiore Shape Ratio durante la fase di backtesting prima di mettere in produzione il proprio sistema.

Tuttavia, un modello alpha è accurato solo quando viene alimentato da dati di ottima qualità. Per questo motivo è fondamentale utilizzare dati accurati e tempestivi come input per il modello alpha, altrimenti i risultati saranno scarsi nel migliore dei casi o addirittura completamente errati nel peggiore dei casi, con conseguenti ingenti perdite se il sistema verrà messo in produzione.

In questo articolo desidero discutere questioni relative all’acquisizione e alla fornitura di dati tempestivi e accurati per il backtesting di una strategia algoritmica e, in definitiva, per il motore di esecuzione di trading. In particolare voglio evidenziare come ottenere dati finanziari, come conservarli, come pulirli e come esportarli. Nel settore finanziario questo tipo di servizio dati è noto come securities master database.

Cos'è una Securities Master Database?

Un Securities Master è un database che memorizza i dati fondamentali, i prezzi e le transazioni per una varietà di strumenti finanziari in tutte le classi di attività. Fornisce l’accesso a queste informazioni in modo coerente per essere utilizzato da molti processi, come la gestione dei rischi, la compensazione / liquidazione e il trading proprietario.

Ecco alcuni degli strumenti che potrebbero comporre un securities master database:

  • Azioni
  • Opzioni azionarie
  • Indici
  • Foreign Exchange
  • Tassi di interesse
  • Futures
  • Commodities
  • Obbligazioni – Governo e società
  • Derivati – Caps, Floors, Swaps

 

La basi dati di Securities Master sono gestite da un team di sviluppatori e da specialisti di dati che garantiscono un elevato livello di disponibilità dei dati all’interno di un’azienda o istituzione.

Mentre questo è necessario nelle grandi aziende, a livello di un trader retail o in un piccolo fondo, un securities master può essere molto più semplice. Infatti, mentre i securities master dei grandi fondi fanno uso di costosi database aziendali e sistemi di analisi, a livello retail è possibile utilizzare software open source per fornire lo stesso livello di funzionalità, presupponendo che un sistema ben ottimizzato.

 

Quale database scegliere?

Per il trader algoritmico al dettaglio o il piccolo fondo quantitativo, i set di dati più comuni per azioni, indici, futures (principalmente materie prime o reddito fisso) e cambi (forex) sono i prezzi storici di fine giornata (EOD) e i dati  intraday . Al fine di semplificare questa discussione ci concentreremo esclusivamente sui dati di fine giornata (EOD) per titoli azionari, ETF e indici azionari. Gli articoli successivi discuteranno di aggiungere dati ad alta frequenza, classi di assets aggiuntivi e dati derivati, che hanno requisiti più avanzati.

I dati EOD per le azioni sono facili da ottenere. Esistono numerosi servizi che forniscono l’accesso gratuito tramite API disponibili sul Web:

È semplice scaricare manualmente i dati storici per singoli titoli, ma diventa molto dispendioso in termini di tempo se si devono scaricare giornalmente molti titoli. Pertanto un componente importante del nostro securities master si occuperà di aggiornare automaticamente il set di dati.

Un altro problema è il periodo di riferimento. Fino a che punto in passato abbiamo bisogno di andare con i nostri dati? Questo dipenderà dai requisiti della tua strategia di trading, ma ci sono alcuni problemi che riguardano tutte le strategie. Il più comune è il cambio di regime, che è spesso caratterizzato da un nuovo contesto normativo, periodi di volatilità superiore / inferiore o mercati con lunghe fasi di trend. Ad esempio, una strategia trend-following/momentum per movimenti corti nel lungo termine potrebbe performare molto bene tra il 2000-2003 e il 2007-2009. Tuttavia avrebbe avuto un periodo difficile dal 2003 al 2007 o dal 2009 ad oggi.

La mia regola generale consiste nell’ottenere quanti più dati possibili, specialmente per i dati EOD, che sono economici da archiviare (necessitano di poco spazio sul disco fisso). Solo perché i dati esistono nel tuo securities master, non significa che debbano essere utilizzati. Ci sono avvertenze circa le prestazioni, in quanto tabelle di database più grandi significano tempi di interrogazione più lunghi (vedi sotto), ma i benefici di avere più punti di campionamento generalmente superano qualsiasi problema di prestazione.

 

Come per tutti i dati finanziari, è imperativo essere consapevoli degli errori, come i prezzi massimi/minimi non corretti o il bias di sopravvivenza, di cui ho discusso a lungo in questo articolo (vedi qui).

 

Quali strumenti usare per memorizzare i dati?

Esistono tre principali modalità per archiviare i dati finanziari. Ognumo possiede diverse caratteristiche per l’accesso ai dati, le prestazioni e le capacità strutturali. Vediamo singolarmente.

Flat-File

Il modo più semplice per memorizzare i dati finanziari, che è anche il principale “formato” in cui si ricevono i dati da qualsiasi fornitore, è un file flat. I file flat utilizzano spesso il formato CSV (Comma-separated values), che memorizza una matrice bidimensionale di dati come una serie di righe, con i dati della colonna separati da un delimitatore (spesso una virgola, ma può essere anche uno spazio bianco, come ad esempio uno spazio o tab). Per i dati sui prezzi EOD, ogni riga rappresenta un giorno di negoziazione tramite il paradigma OHLC (vale a dire i prezzi di aperta , massimo, minimo, e di chiusura).

Il vantaggio dei file flat è la loro semplicità e la capacità di essere efficientemente  compressi per l’archiviazione o il download. Gli svantaggi principali risiedono nella mancanza di capacità di interrogazione e le scarse prestazioni per l’iterazione su set di dati di grandi dimensioni. SQLite ed Excel attenuano alcuni di questi problemi fornendo alcune funzionalità base di interrogazione.

 

Documentale / NoSQL

I database documentali/NoSQL, sebbene non siano un nuovo concetto, negli ultimi anni hanno acquisito una notevole importanza grazie a loro utilizzo da parte dei giganti del web come Google, Facebook e Twitter. Differiscono sostanzialmente dai sistemi RDBMS in quanto non esiste alcun concetto di schemi di tabelle. Invece, ci sono collezioni e documenti, che sono le analogie più vicine, rispettivamente, alle tabelle e ai record. Esiste un’ampia tassonomia di archivi documentali, la cui discussione è ben al di fuori di questo articolo! Tuttavia, alcuni delle soluzioni più popolari sono MongoDBCassandra e CouchDB.

I database documentali, nelle applicazioni finanziarie, sono adatti principalmente ai dati fondamentali o ai metadati. I dati fondamentali per le attività finanziarie sono disponibili in molte forme, come azioni aziendali, dichiarazioni di guadagni, archivi SEC ecc. Pertanto, la natura senza schema dei DB NoSQL è particolarmente adatta. Tuttavia, i DB NoSQL non sono ben progettati per le serie temporali come i dati sui prezzi ad alta risoluzione e quindi non li prenderemo in considerazione per tale scopo.

 

Relational Database Management Systems

Un Relational Database Management Systems utilizza il modello relazionale per archiviare i dati. Questi database sono particolarmente adatti ai dati finanziari perché diversi “oggetti” (come exchanges, sorgenti dati, prezzi) possono essere separati in tabelle con specifiche relazioni tra di loro.

Un RDBMS fa uso del Structured Query Language (SQL) per eseguire complesse query sui dati finanziari. Esempi di RDBMS includono Oracle, MySQL, SQLServer e PostgreSQL.

I principali vantaggi di unRDBMS sono la semplicità di installazione, l’indipendenza dalla piattaforma, la facilità di interrogazione, la semplicità di integrazione con i principali software di backtest e la capacità ad alte prestazioni su grandi set di dati. I loro svantaggi sono spesso dovuti alla complessità ddi effettuare personalizzazioni e alle difficoltà nel raggiungere tali prestazioni senza una conoscenza di base del modo in cui i dati RDBMS sono archiviati. Inoltre, possiedono schemi semirigidi e quindi i dati devono essere modificati per adattarsi a tali schemi. Questo è diverso dagli archivi dati NoSQL, dove non esiste uno schema.

Per tutti i futuri articoli di implementazione dei prezzi storici su DataTrading, utilizzeremo MySQL o PostgreSQL, che sono entrambi gratuiti, open source, multipiattaforma, estremamente robusti e le prestazioni sono ben documentate, che li rendono una scelta ottimale per il lavoro degli algotrader.

 

Come sono strutturati i dati storici?

Nel campo dell’informatica, esiste moltissimo lavoro teorico e di ricerca accademica relativo al design ottimale per gli archivi di dati. Tuttavia, non entreremo troppo nei dettagli in quanto è facile perdersi nei dettagli! In questo paragrafo voglio introdurre un modello comune per la costruzione di un securities master azionario, che è possibile modificare a seconda delle specifiche delle proprie applicazioni.

Il primo compito consiste nel definire le nostre entità, che corrispondono ad elementi dei dati finanziari mappati con le tabelle del database. Per una banca dati di titoli azionari si prevedono le seguenti entità:

  • Exchange – Qual è l’ultima fonte originale dei dati?
  • Fornitore: da dove viene ottenuto un particolare dataset?
  • Strumento / Ticker – Il ticker / simbolo per l’equity o l’indice, insieme alle informazioni aziendali, della società o del fondo sottostante.
  • Prezzo – il prezzo effettivo per un determinato titolo in un determinato giorno.
  • Azioni Aziendali: l’elenco di tutte le suddivisioni di azioni o gli aggiustamenti dei dividendi (ciò potrebbe portare a una o più tabelle), necessarie per adeguare i dati del prezzo.
  • Festività nazionali – Per evitare di classificare erroneamente le vacanze come errori per dati mancanti, può essere utile memorizzare festività nazionali e i riferimenti incrociati.

Ci sono problemi significativi riguardo alla memorizzazione dei ticker canonici. Posso affermare questo grazie ad esperienze dirette. Diversi fornitori utilizzano metodi diversi per risolvere i ticker e quindi è necessario combinare più fonti per avere una precisione dei dati soddisfaciante. Inoltre, le società falliscono, sono esposte all’attività di fusione e acquisizione (ad esempio acquisiscono e cambiano nomi / simboli) e possono avere più classi di azioni quotate in borsa. Molti di voi non dovranno preoccuparsi di ciò perché il vostro universo di ticker sarà limitato ai componenti dei grandi indici(come S&P500 o FTSE350).

Come si valuta l'accuratezza dei dati?

I dati storici dei forniti da società terze sono soggetti a molte forme di errore:

  • Azioni societarie – Errata gestione delle scissioni azionari e gli aggiustamenti dei dividendi. Bisogna essere assolutamente sicuri che le formule siano state implementate correttamente.
  • Spike: i picchi di prezzo che superano di gran lunga determinati livelli storici di volatilità. Bisogna stare molto attenti a questi picchi quando si verificano. I picchi possono anche essere causati dal non prendere in considerazione le divisioni azionarie, quando si verificano. Gli script di spike-filter vengono utilizzati per informare i trader di tali situazioni.
  • Aggregazione OHLC – I dati OHLC gratuiti, come quelli di Yahoo / Google, sono particolarmente soggetti a situazioni di “cattiva aggregazione di ticker” in cui le piccole borse trattano piccoli scambi ben al di sopra dei prezzi di scambio “principali” per un singolo giorno, portando così a massimi / minimi eccessivi una volta aggregati questi dati. Questo non è un vero e proprio ‘errore’ in quanto tale, ma al più  un problema di cui stare attenti.
  • Dati mancanti: i dati mancanti possono essere causati dalla mancanza di scambi in un particolare periodo di tempo (comune nei dati con timeframe al minuti o secondi in azioni small-cap ed illiquidi), dalle giornate di chiusura dei mercati o semplicemente da un errore nel sistema dell’exchange. I dati mancanti possono essere riempiti (cioè riempiti con il valore precedente), interpolati (linearmente o in altro modo) o ignorati, a seconda del sistema di trading.

Molti di questi errori si basano sul giudizio manuale per decidere come procedere. È possibile automatizzare la notifica di tali errori, ma è molto più difficile automatizzare la loro soluzione. Ad esempio, si deve scegliere la soglia per essere informati sui picchi: quante deviazioni standard usare e su quale periodo di ritorno? Un livello troppo alto di stdev salterà alcuni picchi, mentre un livello troppo basso produce molti falsi positivi (come molti annunci di notizie insolite). Tutti questi problemi richiedono un giudizio da parte dell’algotrader.

È anche necessario decidere come correggere gli errori. Gli errori devono essere corretti non appena sono individuati e, in caso affermativo, deve essere eseguita una procedura di controllo? Ciò richiederà una tabella aggiuntiva nel DB. Questo ci porta al tema del back-filling, che è un problema particolarmente insidioso per il backtesting. Riguarda la correzione automatica dei dati errati a monte. Ad esempio se il fornitore di dati inizia a corregge un errore storico, ma la nostra strategia di trading in produzione è stata ottimizzata (backtested)in base alla ricerca dei precedenti dati non validi, è necessario prendere decisioni in merito all’efficacia della strategia. Questo problema può essere un po’ attenuato grazie ad uno studio approfondito delle metriche sul rendimento della strategia (in particolare la variazione delle caratteristiche di vincita / perdita per ogni trade). Le strategie dovrebbero essere scelte o progettate in modo tale che un singolo dato non possa distorcere le prestazioni della strategia.

 

Come sono automatizzati questi processi?

Il vantaggio di scrivere il software per eseguire il download, l’archiviazione e la pulizia dei dati consiste che tali software possono essere automatizzati tramite gli strumenti forniti dal sistema operativo. Nei sistemi basati su UNIX (come Mac OSX o Linux), si può fare uso di crontab, che è un processo in esecuzione continua che consente di eseguire script specifici in periodi definiti dall’utente o periodi regolari. Esiste un processo equivalente su MS Windows noto come Utilità di pianificazione.

Un processo di produzione, ad esempio, potrebbe automatizzare il download di tutti i prezzi di fine giornata del S&P500 non appena vengono pubblicati tramite da fornitore di dati. Avvierà quindi automaticamente gli algoritmi per la verifica dei dati mancanti e gli script per il filtraggio degli spike, avvisando il trader via e-mail, SMS o qualche altra forma di notifica. A questo punto, qualsiasi strumento di backtesting avrà automaticamente accesso ai dati recenti, senza che il trader debba alzare un dito! A seconda che il tuo sistema di trading si trovi su un desktop o su un server remoto, puoi scegliere di avere un processo semi-automatico o completamente automatico per queste attività.

Come vengono forniti i dati al software di backtesting?

Una volta che i dati vengono aggiornati automaticamente e risiedono nell’RDBMS, è necessario acquisirlo ed includerlo  nel software di backtesting. Questo processo dipenderà in larga misura dal modo in cui il database è installato e dal fatto che il tuo sistema di trading sia locale (cioè su un computer desktop) o remoto (come con un server co-localizzato).

Una delle considerazioni più importanti è quella di ridurre al minimo l’input / output (I / O) eccessivo in quanto può essere estremamente costoso sia in termini di tempo che di denaro, presupponendo connessioni remote in cui la larghezza di banda è costosa. Il modo migliore per affrontare questo problema è trasferire i dati attraverso una connessione di rete solo quando ne hai bisogno (tramite query selettiva) o esportando e comprimendo i dati.

Molti RDBMS supportano la tecnologia di replicazione, che consente di clonare un database su un altro sistema remoto, solitamente con un certo grado di latenza. A seconda della configurazione e della quantità di dati, questo processo può impiegare alcuni secondi o minuti. Un semplice approccio consiste nel replicare un database remoto su un desktop locale. Tuttavia, tieni presente che si possono presentare problemi di sincronizzazione e richiedono molto tempo per essere risolti!

 

Di seguito descrivo alcuni casi di esempio, ma ci sono molti modi per affrontare questo problema e saranno altamente dipendenti dalla tua specifica configurazione:

MySQL
Se si utilizza MySQL, è possibile utilizzare un linguaggio di scripting open source come Python (tramite la libreria  MySQLdb o l’ORM di SQLAlchemy) per connettersi al database ed eseguire query su di esso.

Le più recenti librerie di analisi dei dati, come Pandas , consentono l’accesso diretto a MySQL (si consulti questo thread per un esempio).

È inoltre possibile utilizzare il linguaggio / ambiente preferiti (C ++, C #, Matlab) e un collegamento ODBC per connettersi ad un’istanza MySQL.

MS SQLServer
SQLServer è progettato per essere facilmente connesso ai linguaggi MS .NET, come C # e Visual Basic tramite l’ORM LINQ. È anche possibile connettersi a SQLServer con Python, tramite pyODBC.

Esistono chiaramente molte altre combinazioni di database e ambiente di backtesting. Tuttavia, tratterò la discussione di questi setup in successivi articoli!

 

Conclusioni

Nei prossimi articoli introdurrò i dettagli tecnici per l’implementazione di securities master.i. In particolare, installeremo MySQL, lo configureremo per i dati sui prezzi e otterremo i dati EOD da Yahoo / Google finance ed li esploreremo tramite la libreria di analisi dati pandas.

Ambiente di sviluppo per il Trading Algoritmico con Ubuntu/Linux e Python

In questo articolo voglio discutere sulle attività necessarie al fine di impostare un ambiente di sviluppo robusto, efficiente ed interattivo per il testing di strategie di trading algoritmico, utilizzando la distribuzione Linux molto conosciuta, come Ubuntu Desktop e il linguaggio di programmazione Python. Utilizzeremo questo ambiente per quasi tutti i successivi articoli sul trading algoritmico.

Per creare questo ambiente di sviluppo installeremo i seguenti strumenti software, tutti open-source e gratuiti da scaricare:

Oracle VirtualBox – Per la virtualizzazione del sistema operativo

Ubuntu Desktop Linux – Come nostro sistema operativo virtuale

Python – l’ambiente principale per la programmazione.

NumPy / SciPy – Per elaborare il calcolo di array / matrice in modo veloce ed efficiente

IPython – Per lo sviluppo interattivo visivo con Python

Matplotlib – Per la visualizzazione grafica dei dati

Pandas – Per il “wrangling” dei dati e analisi delle serie temporali

Scikit-learn – Per il machine learning e gli algoritmi di intelligenza artificiale

Questi strumenti (abbinati ad un adeguato database di Securities Master) ci permetteranno di creare un ambiente efficiente ed interattivo per la ricerca di nuove strategie. Pandas è progettato per la “manipolazione” dei dati, potendo importare e ripulire i dati delle serie temporali in modo molto efficiente. Utilizzando NumPy / SciPy si può mantiene il sistema ottimizzato e performante. IPython / matplotlib (e la qtconsole descritta di seguito) consentono la visualizzazione interattiva dei risultati e una rapida iterazione. Scikit-learn ci consente di applicare tecniche di machine learning alle nostre strategie per migliorare ulteriormente le prestazioni.

Inoltre ho scritto un tutorial in modo che gli utenti Windows o Mac OSX che non vogliono o non siano in grado di installare direttamente Ubuntu Linux possano comunque utilizzarlo tramite VirtualBox. VirtualBox ci consente di creare una “Macchina Virtuale” all’interno del sistema host in grado di emulare un sistema operativo guest senza influenzare l’host in alcun modo. Ciò consente la sperimentazione di Ubuntu e degli strumenti Python prima di eseguire l’installazione completa.

Per coloro che hanno già installato Ubuntu, possono direttamente iniziare dalla sezione “Installare i pacchetti di Python nel sistema Ubuntu“.

Installare VirtualBox e Ubuntu-Linux

Questa sezione del tutorial si concentra sull’installazione di VirtualBox ed è stata scritta per un sistema Mac OSX, ma è adatta anche per ambiente Windows. Una volta installato VirtualBox, la procedura sarà la stessa per qualsiasi sistema operativo host sottostante.

Prima di iniziare l’installazione del software, dobbiamo scaricare sia Ubuntu che VirtualBox.

 

Scaricare l'immagine ISO di Ubuntu-Linux

Apri il tuo browser web preferito e vai alla homepage di Ubuntu Desktop, e seleziona “Download Ubuntu”:

Scarica Ubuntu Desktop (32-bit o 64-bit)

Scarica l’ultima versione stabile di Ubuntu. Una volta raggiunta la pagina di download, assicurati di selezionarella versione LTS di Ubuntu. Dovrai scegliere se vuoi versione a 32 o 64 bit. È probabile che tu abbia un sistema a 64 bit, ma nel dubbio, scegli 32 bit. Su un sistema Mac OSX l’immagine del disco ISO di Ubuntu Desktop verrà memorizzata nella directory Download. Lo useremo successivamente, dopo aver installato la  VirtualBox.

Scaricare l'immagine ISO di Ubuntu-Linux

Ora che abbiamo scaricato Ubuntu, dobbiamo procurarci l’ultima versione del software VirtualBox di Oracle. Clicca qui per visitare il sito web e selezionare la versione relativa al tuo specifico sistema host (in questo tutorial abbiamo bisogno della versione per Mac OSX):

Pagina di download di Orcacle VirtualBox

Una volta scaricato il file, è necessario eseguirlo e fare clic sull’icona del pacchetto (questo varia leggermente in ambiente Windows ma è un processo simile):

Doppio click sull'icona "pacchetto" per installare Oracle VirtualBox

Dopo che il pacchetto è stato aperto, seguiamo le istruzioni di installazione, mantenendo le impostazioni predefinite (a meno che non sentiate la necessità di cambiarle!). Ora che VirtualBox è stato installato possiamo aprirlo dalla cartella Applicazioni (che può essere trovata con Finder). Questo inserisce VirtualBox sul dock delle icone durante l’esecuzione, quindi puoi fissarlo in questa posizione in modo permanente se vuoi esamire e provare Ubuntu Linux Live  prima di eseguire l’installazione completa:

VirtualBox senza l'immagine ISO di Ubuntu

A questo punto si procede a creare una nuova “scatola virtuale” (cioè un sistema operativo virtualizzato) facendo clic sull’icona Nuova, che sembra un ingranaggio. Ho chiamato la mia scatola virtuale come “Ubuntu Desktop Algorithmic Trading” (quindi potresti usare qualcosa di simile anche tu!):

Impostanzione di un nuovo ambiente virtuale

Scegli la quantità di RAM che desideri allocare al sistema virtuale. Personamente ho impostato 512 Mb poiché questo è solo un sistema di “test”. Per motivi di performance, un vero ambiente per il backtesting deve necessariamente prevedere un’installazione nativa (e quindi allocerà tutta la memoria disponibile per l’hardware utilizzato):

Scegliere la quantità di memoria da allocare per il sistema virtuale

Creare un disco rigido virtuale (si consiglia di utilizzare 8Gb) con una VirtualBox Image Box, dinamicamente assegnata, che abbia lo stesso nome dell’immagine virtuale impostata in precedenza:

Scegliere il tipo di disco rigido da utilizzare per l'immagine ISO

A questo punto si ottiene un sistema completo, con il seguente elenco delle caratteristiche hardware e software: 

Schermata riassuntiva dopo la creazione di un sistema virtuale

Ora dobbiamo dire a VirtualBox di includere un “CD drive” virtuale per la nuova immagine del disco, in modo da poter simulare l’avvio della nostra immagine del sistema Ubuntu da questa unità CD.

Vai alla sezione Impostazioni, fai clic sulla scheda “Archiviazione” e aggiungi un disco. A questo punto è necessario selezionare il file ISO dell’immagine del sistema Ubuntu, memorizzato direttamente nei tuoi download (o ovunque tu abbia scaricato Ubuntu). Selezionalo e quindi salva le impostazioni:

Selezionare l'ISO di Ubuntu Desktop per il primo avvio

 

A questo punto siamo pronti per avviare la nostra immagine di Ubuntu ed effettuare l’installazione del sistema operativo. E’ sufficiente fare clic su “Start” e quindi su “OK”.

Verrà quindi visualizzato la schermata di Ubuntu Desktop. Clicca su “Installa Ubuntu”:

Clicca su "Install Ubuntu" per avviare l'installazione

Assicurati di selezionare le opzioni corrette per installare i driver delle periferiche come la scheda grafica e il Wi-Fi:

Installa i specifici drivers per la scheda grafica e il Wi-Fi

Successivamente viene visualizzata una schermata in cui viene chiesto come si desidera memorizzare i dati creati per il sistema operativo. Non aver paura dell’opzione “Cancella disco e Installa Ubuntu”. Questo NON significa che cancellerà il tuo normale disco fisso!

In realtà si riferisce al disco virtuale che stai usando per avviare Ubuntu, che deve essere sicuramente cancellato e formattato (e comunque non ci sono dati al suo interno dato che lo abbiamo appena creato). Continua con l’installazione e ti verrà presentata una schermata che ti chiederà la tua posizione e, successivamente, il layout della tua tastiera:

Seleziona la tua zona geografica

Inserisci le tue credenziali ed assicurati di ricordare la password scelta perchè ne avrai bisogno ogni volta che accedi al sistema e per installare nuovi software e pacchetti:

Inserisci il tuo Username e Password (questa sarà la password utilizzata dall'amministratore)

Dopo aver inserito tutti i dati richiesti, Ubuntu installerà i tutti i file del sistema operativo. Dovrebbe essere un processo relativamente veloce dato che copia i dati dal disco fisso al disco fisso!

Quando questo processo sarà concluso, VirtualBox si riavvierà. Se non si riavvia da solo, puoi andare sul menu e forzare l’arresto. Al riavvio verrà visualizzata la schermata di login di Ubuntu:

Schermata di login di Ubuntu Desktop

Effettua il login con il tuo username e password (che hai inserito in precedenza) e entra nel tuo nuovo ambiente Ubuntu:

L'interfaccia Unity di Ubuntu Desktop dopo il login

Ora che abbiamo installato Ubuntu Desktop, possiamo iniziare ad installare i pacchetti per l’ambiente di ricerca e sviluppo del trading algoritmico.

Installare su i pacchetti Python per il Trading Algoritmo

Clicca sul bottone “Search” nell’angolo in alto a sinistra dello schermo e digita “Terminal” nella casella in modo da individuare l’interfaccia per la linea di comandi.

Doppio click sull’icona terminal per avviare il Terminal:

Il Terminal a linea di comando di Ubuntu Desktop

Tutti i comandi successivi dovranno essere digitati in questo terminale.

La prima cosa da fare su qualsiasi nuovo ambiente Ubuntu Linux è fare l’update e l’upgrade dei pacchetti. Il primo ci informa se ci sono aggiornamenti disponibili per Ubuntu, mentre il secondo esegue effettivamente il processo di aggiornamento dei vecchi pacchetti con le versioni più recenti.

Esegui i seguenti comandi (ti verrà richiesta la password):

            sudo apt -y update 
sudo apt -y upgrade
        

Da notare che il prefisso -y specifica ad Ubuntu che tu vuoi rispondere ‘yes’ a tutte le domande yes/no del sistema. “sudo” è un comando degli ambienti Ubuntu/Debian Linux che permette di eseguire altri comandi con i privilegi di amministatrore. Dato che staimo installando dei pacchetti per tutto il sistema, abbiamo bisogno dell’accesso ‘root’ alla macchina e per questo dobbiamo usare il comando ‘sudo’.

Dopo che entrambi i comandi sono stati eseguiti e il sistema è stato aggiornato, è necessario installare Python, NumPy / SciPy, matplotlib, panda, scikit-learn e IPython. Inizieremo installando i pacchetti di sviluppo Python e gli strumenti necessari per compilare tutto il software:

            sudo apt install python3-pip python-dev python3-dev build-essential liblapack-dev libblas-dev
        

Una volta installati i pacchetti necessari, possiamo procedere e installare NumPy tramite pip, il gestore di pacchetti Python.
Pip scaricherà un file zip del pacchetto e compilerà il codice sorgente per noi. Ricorda che ci vorrà un po ‘di tempo per compilare, probabilmente 10 minuti!

            sudo pip install numpy
        

Terminata l’installazione di  NumPy, è necessario verificare che tutto funzioni correttamente prima di procedere.

Se osservi il terminale vedrai il tuo nome utente seguito dal nome del tuo computer. Nel mio caso è “[email protected]”, seguito dal prompt. Nel prompt digitare python e quindi provare a importare NumPy. Verificheremo che funziona calcolando la media aritmetica di una lista di valori:

            [email protected]:~$ python 
Python 3.6.0 (default, Jan 12 2017, 03:20:26) 
[GCC 4.7.3] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import numpy 
>>> from numpy import mean 
>>> mean([1,2,3]) 
2.0
>>> exit()
        

Ora che NumPy è stato installato con successo, vogliamo installare la libreria Python Scientific conosciuta come SciPy. 

Questo pacchetto ha alcune dipendenze tra cui la libreria ATLAS e il compilatore GNU Fortran:

            sudo apt install libatlas-base-dev gfortran
        

Ora siamo pronti per installare SciPy tramite il gestore. Ci vorrà molto tempo (circa 10 minuti, a seconda del computer), quindi potrebbe valere la pena andare a prendere un caffè:

            sudo pip install scipy
        

Perfetto! SciPy è stato installato.
Proviamolo calcolando la deviazione standard di un elenco di numeri interi:

            [email protected]:~$ python 
Python 3.6.0 (default, Jan 12 2017, 03:25:17) 
[GCC 4.7.3] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import scipy
>>> from scipy import std
>>> std([1,2,3])
0.81649658092772603
>>> exit()
        

Successivamente dobbiamo installare i pacchetti necessari al corretto funzionamento di matplotlib, la libreria di grafi di Python. Dato che matplotlib è un pacchetto Python, non possiamo usare pip per installare le librerie sottostanti per lavorare con PNG, JPEG e font freetype, quindi abbiamo bisogno di Ubuntu per installarli per noi:

            sudo apt install libpng-dev libjpeg8-dev libfreetype6-dev
        

Ora possiamo installare matplotlib:

            sudo apt install matplotlib
        

Passiamo ora ad installare le librerie per l’analisi dei dati e per l’apprendimento automatico, Pandas e Scikit-learn. Non abbiamo bisogno di ulteriori dipendenze in questa fase in quanto sono coperti da NumPy e SciPy:

            sudo pip install -U scikit-learn 
sudo pip install pandas
        
Per effettuare il test di Scikit-learn si può procedere come segue:
            [email protected]:~$ python 
Python 3.6.0 (default, Jan 12 2017, 03:33:46) 
[GCC 4.7.3] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> from sklearn import datasets
>>> iris = datasets.load_iris()
>>> iris
..
..
'petal width (cm)']}
>>>
        

Inoltre possiamo testare il funzionamento di Pandas:

            >>> from pandas import DataFrame
>>> pd = DataFrame()
>>> pd
Empty DataFrame
Columns: []
Index: []
>>> exit()
        
Infine, vogliamo installare IPython. Si tratta di un interprete Python interattivo che offre un flusso di lavoro molto più snello, rispetto all’uso della console Python standard. Nei tutorial successivi illustrerò la piena utilità di Jupiter per lo sviluppo del trading algoritmico:
            sudo pip install ipython
        

Nonostante IPython è sufficientemente funzionale anche da solo, può essere reso ancora più potente aggiungendo la qtconsole, che fornisce la possibilità di visualizzare matplotlib in linea. Tuttavia, ci vuole un po ‘più di lavoro per farlo funzionare.

Per prima cosa, dobbiamo installare la libreria Qt. Per questo potrebbe essere necessario aggiornare nuovamente i pacchetti:

            sudo apt update
        

Adesso possiamo installare Qt:

            sudo apt-get install libqt4-core libqt4-gui libqt4-dev
        

La qtconsole ha alcuni pacchetti addizionali, nello specifico le librerie ZMQ e Pygments:

            sudo apt-get install libzmq-dev
sudo pip install pyzmq
sudo pip install pygments
        

A questo punto siamo pronti ad avviare IPython con la qtconsole:

            ipython qtconsole --pylab=inline
        

A questo punto siamo pronti ad avviare IPython con la qtconsole:

Quindi possiamo costruire un semplice grafico tramite i seguenti comandi (ho incluso anche i numeri input/output di IPython che non hai bisogno di digitare):

            In [1]: x=np.array([1,2,3])

In [2]: plot(x)
Out[2]: []
        
Che produce il seguente grafico:
un grafico prodotto con IPython e Qtconsole

Qui si conclude la procedura di installazione. Ora abbiamo a portata di mano di un ambiente di sviluppo per il trading algoritmico estremamente robusto, efficiente e interattivo.

Negli articoli successivi descriverò in dettaglio come IPython, matplotlib, panda e scikit-learn possano essere combinati per trovare ed effettuare il backtesting di nuove strategie di trading quantitative in modo diretto.