Strategia di Mean Reversion per il Pairs Trading Intraday

In questo articolo si vuole descrivere la nostra prima strategia di trading intraday. Si basa su una classica idea di trading, quella delle “coppie di trading”. 

La strategia crea generalmente uno “spread” tra la coppia di asset considerati, andando long su una e short sull’altra. Il rapporto tra long e short può essere definito in molti modi, ad esempio utilizzando tecniche di cointegrazione statistica sulle serie temporali. In questo esempio si calcola il “hedge ratio” (rapporto di copertura) tra gli asset tramite una regressione lineare a rotazione. Questo permette
di creare un “spread” che viene normalizzato in uno z-score. I segnali di trading sono generati quando lo z-score supera una determinata soglia, nella convinzione che lo spread tornerà verso la media.

La logica della strategia prevede che gli asset considerati siano approssimativamente caratterizzate dallo stesso comportamento o andamento. L’idea base consiste nel considerare che lo spread dei prezzi ha un comportamento mean-reverting, dal momento che eventi “locali” (nel tempo) possono influenzare separatamente i singoli asset (come differenze di capitalizzazione, date di ribilanciamento o operazioni di blocco) ma nel lungo termine le serie di prezzi tendono ad essere cointegrate.

La Strategia

Al fine di ottenere Sharpe Ratio più alti, è necessario adottare strategie intraday ad alta frequenza. Il primo importante problema è ottenere dati significativi, dato che i dati intraday di alta qualità non sono solitamente gratuiti. Come descritto negli articoli precedetti, si utilizza il DTN IQFeed per acquisire le barre intraday al minuto e quindi si avrà bisogno di un account DTN per ottenere i dati richiesti per questa strategia. Il secondo problema consiste nel fatto che le simulazioni di backtesting impiegano molto più tempo, specialmente con il modello event-driven, descritto in questa serie di articoli. Se si vuol effettuare il backtesting di un portafolio diversificato con dati al minuto un numero significativo di anni passati, e quindi eseguire qualsiasi ottimizzazione dei parametri, ci si rende rapidamente conto che le simulazioni possono richiedere ore o persino giorni, se effettatu su modermo PC desktop. Questo dovrà essere preso in considerazione nel durante il processo di ricerca e studio della strategia. Il terzo problema è la completa automazione dell’esecuzione nel live trading poiché ci si sta avvicinando al trading ad alta frequenza e quindi il sistema di esecuzione dove essere altamente performante. Questo significa che l’ambiente e il codice di esecuzione devono essere altamente affidabili e privi di errori, altrimenti potrebbe verificarsi significative perdite. Questa strategia espande la precedente strategia multiday al fine di utilizzare i dati intraday. In particolare, si usa barre OHLCV al minuto, a differenza di dati OHLCV giornalieri. Le regole per la strategia sono semplici:
  1. Identificare una coppia di titoli azionari le cui serie temporali hanno statisticamente un comportamento riconducile al mean-reverting. In questo caso, si considerano i due titoli azionari statunitensi con i ticker AREX e WLL.
  2. Creare le serie temporali residue della coppia eseguendo una regressione lineare a rotazione, per una specifica finestra di ricerca, tramite l’algoritmo dei minimi quadrati ordinari (OLS). Questo periodo di ricerca è un parametro da ottimizzare.
  3. Creare un z-score a rotazione delle serie temporali residue per lo stesso periodo di ricerca e utilizzarlo per determinare le soglie di ingresso / uscita per i segnali di trading.
  4. Se la soglia superiore viene superata quando non si è sul mercato, allora si ENTRA a mercato (long o short dipende dalla direzione di rottura della soglia viene). Se invece viene superata la soglia inferiore quando si ha una posizione a mercato, allora si ESCE dal mercato. Anche le soglie superiore e inferiore sono parametri da ottimizzare.
In effetti si potrebbe usare il test Cointegrated Augmented Dickey-Fuller (CADF) per identificare un parametro di copertura ancora più accurato. Questo potrebbe essere un’interessate evoluzione di questa strategia.

Implementazione in Python

Come per tutti i tutorial con Python e Pandas, è necessario aver impostato un ambiente di backtesting con Python, come descritto in questo tutorial. Una volta impostato, il primo passo è importare le necessarie librerie Python. Per questo backtest sono richiesti matplotlib e pandas. In particolare si utilizza metodo rolling_apply, al fine di applicare il calcolo dello z-score ad una finestra di ricerca a rotazione. Si importa statsmodels perché fornisce un mezzo per calcolare l’algoritmo dei minimi quadrati ordinari (OLS) per la regressione lineare, al fine di  ottenere il rapporto di copertura per la costruzione dei residui. Si prevede inoltre un DataHandler e un Portfolio leggermente modificati per effettuare operazioni di trading al minuto sui dati DTN IQFeed. Per creare questi file si può semplicemente copiare tutto il codice di portfolio.py e data.py rispettivamente nei nuovi file hft_portfolio.py e hft_data.py e quindi modificare le sezioni necessarie, che illustrato di seguito.
                    
# intraday_mr.py

import datetime
import numpy as np
import pandas as pd
import statsmodels.api as sm

from strategy import Strategy
from event import SignalEvent
from backtest import Backtest

from hft_data import HistoricCSVDataHandlerHFT
from hft_portfolio import PortfolioHFT

from execution import SimulatedExecutionHandler                    
                
Con il seguente codice si crea la classe IntradayOLSMRStrategy, derivata dalla classe base astratta di Strategy. Il metodo __init__ del costruttore richiede l’accesso al provider di dati storici, alla coda degli eventi, a una soglia zscore_low e a una soglia zscore_high, utilizzate per determinare quando la serie residua tra le due coppie è di tipo mean-reverting. Inoltre, si specifica la finestra di ricerca OLS (impostata su 100), che è un parametro soggetto a potenziale ottimizzazione. All’inizio della simulazione non si è long o short sul mercato, quindi si imposta sia self.long_market che self.short_market uguale a False:
                    
# intraday_mr.py

class IntradayOLSMRStrategy(Strategy):
    """
    Uses ordinary least squares (OLS) to perform a rolling linear
    regression to determine the hedge ratio between a pair of equities.
    The z-score of the residuals time series is then calculated in a
    rolling fashion and if it exceeds an interval of thresholds
    (defaulting to [0.5, 3.0]) then a long/short signal pair are generated
    (for the high threshold) or an exit signal pair are generated (for the
    low threshold).
    """

    def __init__(
        self, bars, events, ols_window=100,
        zscore_low=0.5, zscore_high=3.0
    ):
        """
        Initialises the stat arb strategy.
        Parameters:
        bars - The DataHandler object that provides bar information
        events - The Event Queue object.
        """

        self.bars = bars
        self.symbol_list = self.bars.symbol_list
        self.events = events
        self.ols_window = ols_window
        self.zscore_low = zscore_low
        self.zscore_high = zscore_high
        self.pair = (’AREX’, ’WLL’)
        self.datetime = datetime.datetime.utcnow()
        self.long_market = False
        self.short_market = False                    
                
Il seguente metodo, calculate_xy_signals, prende lo zscore corrente (dal calcolo rolling eseguito di seguito) e determina se è necessario generare nuovi segnali di trading. Questi segnali vengono resi disponibili in output. Ci sono quattro stati potenziali a cui si può essere interessati:
  1. Long sul mercato e sotto la più alta soglia negativa dello zscore
  2. Long il mercato e all’interno del valore assoluto della soglia più alta dello zscore
  3. Short sul mercato e sopra la maggiore soglia positiva dello z-score
  4. Short sul mercato e all’interno tra il valore assoluto del valore assoluto della soglia inferiore più bassa dello zscore.
In tutti i casi è necessario generare due segnali, uno per la prima componente della coppia (AREX) e uno per la seconda componente della coppia (WLL). Se nessuna di queste condizioni viene soddisfatta, si restituisce una coppia di valori None:
                    

# intraday_mr.py

def calculate_xy_signals(self, zscore_last):
    """
    Calculates the actual x, y signal pairings
    to be sent to the signal generator.
    Parameters
    zscore_last - The current zscore to test against
    """

    y_signal = None
    x_signal = None
    p0 = self.pair[0]
    p1 = self.pair[1]
    dt = self.datetime
    hr = abs(self.hedge_ratio)

    # If we’re long the market and below the
    # negative of the high zscore threshold
    if zscore_last <= -self.zscore_high and not self.long_market:
        self.long_market = True
        y_signal = SignalEvent(1, p0, dt, ’LONG’, 1.0)
        x_signal = SignalEvent(1, p1, dt, ’SHORT’, hr)

    # If we’re long the market and between the
    # absolute value of the low zscore threshold
    if abs(zscore_last) <= self.zscore_low and self.long_market: 
        self.long_market = False
        y_signal = SignalEvent(1, p0, dt, ’EXIT’, 1.0) 
        x_signal = SignalEvent(1, p1, dt, ’EXIT’, 1.0) 

    # If we’re short the market and above 
    # the high zscore threshold 
    if zscore_last >= self.zscore_high and not self.short_market:
        self.short_market = True
        y_signal = SignalEvent(1, p0, dt, ’SHORT’, 1.0)
        x_signal = SignalEvent(1, p1, dt, ’LONG’, hr)

    # If we’re short the market and between the
    # absolute value of the low zscore threshold
    if abs(zscore_last) <= self.zscore_low and self.short_market:
        self.short_market = False
        y_signal = SignalEvent(1, p0, dt, ’EXIT’, 1.0)
        x_signal = SignalEvent(1, p1, dt, ’EXIT’, 1.0)

    return y_signal, x_signal                    
                
Il seguente metodo, calculate_signals_for_pairs acquisisce l’ultimo set di barre per ogni componente della coppia (in questo caso 100 barre) e le utilizza per costruire una regressione lineare basata su minimi quadrati ordinari. Ciò consente l’identificazione del rapporto di copertura, necessario per la costruzione delle serie temporali residue. Una volta ricavato il rapporto di copertura, si crea lo spread delle serie di residui. Il passo successivo consiste nel calcolare l’ultimo z-score dalle serie residue sottraendo la loro media e dividendo per la loro deviazione standard nel periodo di ricerca. Infine, y_signal e x_signal sono calcolati sulla base di questo z-score. Se i segnali non sono entrambi None, le istanze SignalEvent vengono inviate alla coda degli eventi:
                    
# intraday_mr.py

def calculate_signals_for_pairs(self):
    """
    Generates a new set of signals based on the mean reversion
    strategy.
    Calculates the hedge ratio between the pair of tickers.
    We use OLS for this, althought we should ideall use CADF.
    """

    # Obtain the latest window of values for each
    # component of the pair of tickers
    y = self.bars.get_latest_bars_values(
        self.pair[0], "close", N=self.ols_window
    )
    x = self.bars.get_latest_bars_values(
        self.pair[1], "close", N=self.ols_window
    )

    if y is not None and x is not None:
        # Check that all window periods are available
        if len(y) >= self.ols_window and len(x) >= self.ols_window:
            # Calculate the current hedge ratio using OLS
            self.hedge_ratio = sm.OLS(y, x).fit().params[0]
            
            # Calculate the current z-score of the residuals
            spread = y - self.hedge_ratio * x
            zscore_last = ((spread - spread.mean())/spread.std())[-1]

            # Calculate signals and add to events queue
            y_signal, x_signal = self.calculate_xy_signals(zscore_last)
            if y_signal is not None and x_signal is not None
                self.events.put(y_signal)
                self.events.put(x_signal)                    
                
Il metodo finale, calculate_signals è sovrascritto dalla classe base e viene utilizzato per verificare se un evento ricevuto dalla coda è in realtà un MarketEvent, nel qual caso viene eseguito il calcolo dei nuovi segnali:
                    
# intraday_mr.py

def calculate_signals(self, event):
    """
    Calculate the SignalEvents based on market data.
    """

    if event.type == ’MARKET’:
        self.calculate_signals_for_pairs()                    
                
La funzione __main__ collega insieme i componenti al fine di eseguire il backtesting di una strategia. Si specifica dove sono archiviati i dati al minuto dei ticker, utilizzando il formato dei simboli IQFeed DTN. Necessariamente i file sono stati modificati in modo tale che iniziano e finiscono sullo stesso minuto. Per questa particolare coppia di AREX e WLL, la data comune di inizio è l’8 novembre 2007 alle 10:41:00. Infine, si costruisce l’oggetto backtest e si inizia a simulare il trading:
                    
# intraday_mr.py

if __name__ == "__main__":
    csv_dir = ’/path/to/your/csv/file’ # CHANGE THIS!
    symbol_list = [’AREX’, ’WLL’]
    initial_capital = 100000.0
    heartbeat = 0.0
    start_date = datetime.datetime(2007, 11, 8, 10, 41, 0)

    backtest = Backtest(
        csv_dir, symbol_list, initial_capital, heartbeat,start_date, 
        HistoricCSVDataHandlerHFT, SimulatedExecutionHandler,
        PortfolioHFT, IntradayOLSMRStrategy
    )
    backtest.simulate_trading()                    
                
Tuttavia, prima di poter eseguire questo codice, è necessario apportare alcune modifiche al gestore dati e all’oggetto portfolio. In particolare, è necessario creare nuovi file hft_data.py e hft_portfolio.py che sono rispettivamente copie di data.py e portfolio.py. In hft_data.py si deve rinominare HistoricCSVDataHandler in HistoricCSVDataHandlerHFT e sostituire l’elenco dei nomi nel metodo _open_convert_csv_files. Il vecchio codice è:
                    
names=[
       ’datetime’, ’open’, ’high’,
       ’low’, ’close’, ’volume’, ’adj_close’
      ]                    
                
Deve essere sostituito con il seguente:
                    
names=[
       ’datetime’, ’open’, ’low’,
       ’high’, ’close’, ’volume’, ’oi’
      ]                    
                
In questo modo si garantisce la compatibilità tra il nuovo formato di DTN IQFeed e il sistema di backtesting. L’altro cambiamento consiste nel rinominare Portfolio in PortfolioHFT all’interno di hft_portfolio.py. Si deve quindi modificare alcune righe per tenere conto della frequenza minima dei dati DTN. In particolare, all’interno del metodo update_timeindex, è necessario modificare il seguente codice:
                    
for s in self.symbol_list:
    # Approximation to the real value
    market_value = self.current_positions[s] * \
        self.bars.get_latest_bar_value(s, "adj_close")
    dh[s] = market_value
    dh[’total’] += market_value                    
                
Per farlo diventare come segue:
                    
for s in self.symbol_list:
    # Approximation to the real value
    market_value = self.current_positions[s] * \
        self.bars.get_latest_bar_value(s, "close")
    dh[s] = market_value
    dh[’total’] += market_value                    
                
Questo assicura di considerare il prezzo di chiusura, piuttosto che il prezzo di adj_close. Quest’ultimo è utilizzato da Yahoo Finance, mentre il primo è di DTN IQFeed. Si deve inoltre prevedere un aggiustamento simile in update_holdings_from_fill. Si deve cambiare il seguente codice:
                    
# Update holdings list with new quantities
    fill_cost = self.bars.get_latest_bar_value(
        fill.symbol, "adj_close"
    )                    
                
come segue:
                    
# Update holdings list with new quantities
    fill_cost = self.bars.get_latest_bar_value(
        fill.symbol, "close"
    )                    
                
La modifica finale si effettua nel metodo output_summary_stats, nella parte inferiore del file. Si deve modificare il metodo di calcolato del Sharpe Ratio per tenere conto del trading con barre al minuto. La seguente riga:
                    
sharpe_ratio = create_sharpe_ratio(returns)                    
                
Viene modificata come:
                    
sharpe_ratio = create_sharpe_ratio(returns, periods=252*6.5*60)                    
                
Dopo aver mandato in esecuzione il file intraday_mr.py si ottiene il seguente output (troncato) dalla simulazione di backtest:
                    
..
..
375072
375073
Creating summary stats...
Creating equity curve...
                    AREX WLL cash commission total returns \
datetime
2014-03-11 15:53:00 2098 -6802 120604.3 9721.4 115900.3 -0.000052
2014-03-11 15:54:00 2101 -6799 120604.3 9721.4 115906.3 0.000052
2014-03-11 15:55:00 2100 -6802 120604.3 9721.4 115902.3 -0.000035
2014-03-11 15:56:00 2097 -6810 120604.3 9721.4 115891.3 -0.000095
2014-03-11 15:57:00 2098 -6801 120604.3 9721.4 115901.3 0.000086
2014-03-11 15:58:00 2098 -6800 120604.3 9721.4 115902.3 0.000009
2014-03-11 15:59:00 2099 -6800 120604.3 9721.4 115903.3 0.000009
2014-03-11 16:00:00 2100 -6801 120604.3 9721.4 115903.3 0.000000
2014-03-11 16:01:00 2100 -6801 120604.3 9721.4 115903.3 0.000000
2014-03-11 16:01:00 2100 -6801 120604.3 9721.4 115903.3 0.000000

                    equity_curve drawdown
datetime
2014-03-11 15:53:00 1.159003 0.003933
2014-03-11 15:54:00 1.159063 0.003873
2014-03-11 15:55:00 1.159023 0.003913
2014-03-11 15:56:00 1.158913 0.004023
2014-03-11 15:57:00 1.159013 0.003923
2014-03-11 15:58:00 1.159023 0.003913
2014-03-11 15:59:00 1.159033 0.003903
2014-03-11 16:00:00 1.159033 0.003903
2014-03-11 16:01:00 1.159033 0.003903
2014-03-11 16:01:00 1.159033 0.003903

[(’Total Return’, ’15.90%’),
 (’Sharpe Ratio’, ’1.89’),
 (’Max Drawdown’, ’3.03%’),
 (’Drawdown Duration’, ’120718’)]
Signals: 7594
Orders: 7478
Fills: 7478                    
                
Si nota facilmente che la strategia si comporta bene durante il periodo sotto esame. Ha un rendimento totale di poco inferiore al 16%. Il Sharpe Ratio ragionevole (se confrontato con una tipica strategia giornaliera), ma data la natura ad alta frequenza della strategia ci si dovrebbe aspettare di più. La migliore caratteristica di questa strategia è il basso drawdown massimo(circa il 3%). Questo suggerisce di poter applicare una leva maggiore per ottenere più profitto.

Visualizzazione grafica delle Performance

Si può facilmente visualizzare il grafico dei rendimenti risposto all’intervallo di ricerca (numero di barre) e tutte le altre misure delle performance usando lo script plot_performance.py. Tale codice può essere utilizzato come base per creare grafici personalizzati delle prestazioni.

È necessario eseguirlo nella stessa directory del file di output dal backtest, ovvero dove risiede equity.csv. Il codice è il seguente:

                    
# plot_performance.py

import os.path
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

if __name__ == "__main__":
    data = pd.io.parsers.read_csv(
        "equity.csv", header=0,
        parse_dates=True, index_col=0
    ).sort()

    # Plot three charts: Equity curve,
    # period returns, drawdowns
    fig = plt.figure()
    
    # Set the outer colour to white
    fig.patch.set_facecolor(’white’)

    # Plot the equity curve
    ax1 = fig.add_subplot(311, ylabel=’Portfolio value, %’)
    data[’equity_curve’].plot(ax=ax1, color="blue", lw=2.)
    plt.grid(True)

    # Plot the returns
    ax2 = fig.add_subplot(312, ylabel=’Period returns, %’)
    data[’returns’].plot(ax=ax2, color="black", lw=2.)
    plt.grid(True)

    # Plot the drawdowns
    ax3 = fig.add_subplot(313, ylabel=’Drawdowns, %’)
    data[’drawdown’].plot(ax=ax3, color="red", lw=2.)
    plt.grid(True)

    # Plot the figure
    plt.show()                    
                
Utilizzando l’output CSV del backtesting della precedente strategia si ottengono i seguenti grafici:
Fig - Curva di Equity, Ritorni Giornalieri e Drawdown per la strategia mean-reversion intraday.

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

from pandas.io.data import DataReader
from sklearn.qda import QDA

from backtest import Strategy, Portfolio
from 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):
    """    
    Requires:
    symbol - A stock symbol on which to form a strategy on.
    bars - A DataFrame of bars for the above symbol."""

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

    def create_periods(self):
        """Create training/test periods."""
        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):
        """Fits a Quadratic Discriminant Analyser to the
        US stock market index (^GPSC in Yahoo)."""
        # Create a lagged series of the S&P500 US stock market index
        snpret = create_lagged_series(self.symbol, self.start_train, 
                                      self.end_period, lags=5) 

        # Use the prior two days of returns as 
        # predictor values, with direction as the response
        X = snpret[["Lag1","Lag2"]]
        y = snpret["Direction"]

        # Create training and test sets
        X_train = X[X.index < self.start_test]
        y_train = y[y.index < self.start_test] # Create the predicting factors for use # in direction forecasting self.predictors = X[X.index >= self.start_test]

        # Create the Quadratic Discriminant Analysis model
        # and the forecasting strategy
        self.model = QDA()
        self.model.fit(X_train, y_train)

    def generate_signals(self):
        """Returns the DataFrame of symbols containing the signals
        to go long, short or hold (1, -1 or 0)."""
        signals = pd.DataFrame(index=self.bars.index)
        signals['signal'] = 0.0       

        # Predict the subsequent period with the QDA model
        signals['signal'] = self.model.predict(self.predictors)

        # Remove the first five signal entries to eliminate
        # NaN issues with the signals DataFrame
        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):
    """Buys or sells 500 shares of an asset at the opening price of
    every bar, depending upon the direction of the forecast, closing 
    out the trade at the close of the bar.

    Requires:
    symbol - A stock symbol which forms the basis of the portfolio.
    bars - A DataFrame of bars for a symbol set.
    signals - A pandas DataFrame of signals (1, 0, -1) for each symbol.
    initial_capital - The amount in cash at the start of the portfolio."""

    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):
        """Generate the positions DataFrame, based on the signals
        provided by the 'signals' DataFrame."""
        positions = pd.DataFrame(index=self.signals.index).fillna(0.0)

        # Long or short 500 shares of SPY based on 
        # directional signal every day
        positions[self.symbol] = 500*self.signals['signal']
        return positions
                    
    def backtest_portfolio(self):
        """Backtest the portfolio and return a DataFrame containing
        the equity curve and the percentage returns."""

        # Set the portfolio object to have the same time period
        # as the positions DataFrame
        portfolio = pd.DataFrame(index=self.positions.index)
        pos_diff = self.positions.diff()

        # Work out the intraday profit of the difference
        # in open and closing prices and then determine
        # the daily profit by longing if an up day is predicted
        # and shorting if a down day is predicted        
        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']

        # Generate the equity curve and percentage returns
        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)

    # Obtain the bars for SPY ETF which tracks the S&P500 index    
    bars = DataReader("SPY", "yahoo", start_test, end_period)
    
    # Create the S&P500 forecasting strategy
    snpf = SNPForecastingStrategy("^GSPC", bars)
    signals = snpf.generate_signals()

    # Create the portfolio based on the forecaster
    portfolio = MarketIntradayPortfolio("SPY", bars, signals,              
                                        initial_capital=100000.0)
    returns = portfolio.backtest_portfolio()

    # Plot results
    fig = plt.figure()
    fig.patch.set_facecolor('white')

    # Plot the price of the SPY ETF
    ax1 = fig.add_subplot(211,  ylabel='SPY ETF price in $')
    bars['Close'].plot(ax=ax1, color='r', lw=2.)

    # Plot the equity curve
    ax2 = fig.add_subplot(212, ylabel='Portfolio value in $')
    returns['total'].plot(ax=ax2, lw=2.)

    fig.show()                    
                
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.

Strategia di Moving Average Crossover in Python con Pandas

Nel precedente articolo relativo allo Sviluppo di ambiente di Backtesting 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
  • Panda – 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):
    """    
    Requires:
    symbol - A stock symbol on which to form a strategy on.
    bars - A DataFrame of bars for the above symbol.
    short_window - Lookback period for short moving average.
    long_window - Lookback period for long moving average."""

    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):
        """Returns the DataFrame of symbols containing the signals
        to go long, short or hold (1, -1 or 0)."""
        signals = pd.DataFrame(index=self.bars.index)
        signals['signal'] = 0.0

        # Create the set of short and long simple moving averages over the 
        # respective periods
        signals['short_mavg'] = pd.rolling_mean(bars['Close'], self.short_window, min_periods=1)
        signals['long_mavg'] = pd.rolling_mean(bars['Close'], self.long_window, min_periods=1)

        # Create a 'signal' (invested or not invested) when the short moving average crosses the long
        # moving average, but only for the period greater than the shortest moving average window
        signals['signal'][self.short_window:] = np.where(signals['short_mavg'][self.short_window:] 
            > signals['long_mavg'][self.short_window:], 1.0, 0.0)   

        # Take the difference of the signals in order to generate actual trading orders
        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):
    """Encapsulates the notion of a portfolio of positions based
    on a set of signals as provided by a Strategy.

    Requires:
    symbol - A stock symbol which forms the basis of the portfolio.
    bars - A DataFrame of bars for a symbol set.
    signals - A pandas DataFrame of signals (1, 0, -1) for each symbol.
    initial_capital - The amount in cash at the start of the portfolio."""

    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)
        positions[self.symbol] = 100*self.signals['signal']   # This strategy buys 100 shares
        return positions
                    
    def backtest_portfolio(self):
        portfolio = self.positions*self.bars['Close']
        pos_diff = self.positions.diff()

        portfolio['holdings'] = (self.positions*self.bars['Close']).sum(axis=1)
        portfolio['cash'] = self.initial_capital - (pos_diff*self.bars['Close']).sum(axis=1).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__":
    # Obtain daily bars of AAPL from Yahoo Finance for the period
    # 1st Jan 1990 to 1st Jan 2002 - This is an example from ZipLine
    symbol = 'AAPL'
    bars = DataReader(symbol, "yahoo", datetime.datetime(1990,1,1), datetime.datetime(2002,1,1))

    # Create a Moving Average Cross Strategy instance with a short moving
    # average window of 100 days and a long window of 400 days
    mac = MovingAverageCrossStrategy(symbol, bars, short_window=100, long_window=400)
    signals = mac.generate_signals()

    # Create a portfolio of AAPL, with $100,000 initial capital
    portfolio = MarketOnClosePortfolio(symbol, bars, signals, initial_capital=100000.0)
    returns = portfolio.backtest_portfolio()

    # Plot two charts to assess trades and equity curve
    fig = plt.figure()
    fig.patch.set_facecolor('white')     # Set the outer colour to white
    ax1 = fig.add_subplot(211,  ylabel='Price in $')
    
    # Plot the AAPL closing price overlaid with the moving averages
    bars['Close'].plot(ax=ax1, color='r', lw=2.)
    signals[['short_mavg', 'long_mavg']].plot(ax=ax1, lw=2.)

    # Plot the "buy" trades against AAPL
    ax1.plot(signals.loc[signals.positions == 1.0].index, 
             signals.short_mavg[signals.positions == 1.0],
             '^', markersize=10, color='m')

    # Plot the "sell" trades against AAPL
    ax1.plot(signals.loc[signals.positions == -1.0].index, 
             signals.short_mavg[signals.positions == -1.0],
             'v', markersize=10, color='k')

    # Plot the equity curve in dollars
    ax2 = fig.add_subplot(212, ylabel='Portfolio value in $')
    returns['total'].plot(ax=ax2, lw=2.)

    # Plot the "buy" and "sell" trades against the equity curve
    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')

    # Plot the figure
    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.

Come identificare le Strategie di Trading Algoritmico

In questo articolo voglio parlare dei metodi con cui io stesso identifico le strategie di trading algoritmico più redditizie. L’obiettivo di oggi è capire in dettaglio come trovare, valutare e selezionare tali sistemi. Descrivere in che modo identificare le migliori strategie a seconda sia del nostro stile di trading che delle prestazioni strategiche, come determinare il tipo e la quantità di dati storici da testare, come valutare nel dettaglio una strategia di trading e infine come procedere verso la fase di backtesting e implementazione di una strategia.

Determinare il proprio stile di trading

Per essere un trader di successo – sia discrezionale che algoritmico – è necessario porsi onestamente alcune domande. Il trading ti offre la possibilità di perdere denaro a un ritmo allarmante, quindi è necessario “conoscerti” per capire quale strategia scegliere.

Direi che la considerazione più importante è essere consapevoli della propria personalità. Il trading, in particolare il trading algoritmico, richiede un grado significativo di disciplina, pazienza e distacco emotivo. Dato che stai lasciando che un algoritmo esegua il trading al tuo posto, è necessario essere determinati ad non interferire MAI con una strategia dopo che viene messa ad operare con denaro reale. Questo può essere estremamente difficile, specialmente nei periodi di forte drawdown. Tuttavia, molte strategie che hanno dimostrato di essere altamente redditizie in un backtest possono essere rovinate da semplici interferenze. Comprendi che se desideri entrare nel mondo del trading algoritmico sarai emotivamente messo alla prova e, che per avere successo, è necessario superare queste difficoltà!

La prossima considerazione riguarda il tempo. Hai un lavoro a tempo pieno? Lavori a tempo parziale? Lavori da casa o hai un lungo tragitto giornaliero? Queste domande aiuteranno a determinare la frequenza delle strategia che dovresti cercare. Per quelli di voi che lavorano a tempo pieno, una strategia di futures infragiornaliero potrebbe non essere appropriata (almeno fino a quando non sarà completamente automatizzata!). I tuoi limiti di tempo dettano anche la tipologia della strategia. Se la tua strategia prevede molti trade e dipende dai feed di notizie (come un terminale Bloomberg), devi chiaramente essere realista riguardo alla tua capacità di gestirlo con successo mentre sei in ufficio! Per quelli di voi con molto tempo a disposizione, o le abilità per automatizzare una strategia, potrebbe essere interessante implementare una strategia di trading ad alta frequenza (HFT).

 

In qualsiasi caso, il trading implica studi e ricerche continui sulle strategie da applicare per mantenere un portafoglio costantemente redditizio. Nessuna strategia rimane “redditizia” per sempre. Quindi parte significativa del tempo assegnato al trading è dedicato al ricerca di nuove strategie. Chiediti se sei pronto a farlo, in quanto può essere la differenza tra una forte redditività o un lento declino verso le perdite.

Devi anche considerare il capitale da dedicare al trading. L’importo minimo ideale generalmente previsto per una strategia quantitativa è di 50.000 USD. Se dovessi ricominciare, inizierei con un importo maggiore, probabilmente più vicino a 100.000 USD. Questo perché i costi di transazione possono essere estremamente costosi per le strategie di media e alta frequenza ed è necessario disporre di capitale sufficiente per assorbirli nei periodi drawdown. Se stai pensando di iniziare con meno di 10.000 USD, dovrai limitarti alle strategie a bassa frequenza, scambiando solo uno o due beni, altrimenti i costi di transazione annullerebbero i profitti.

Interactive Brokers, che è uno dei broker più usati dai trader con competenze di programmazione, grazie alla sua API, prevede un capitale iniziale minimo di 10.000 USD.

 

La programmazione è un fattore importante nella creazione di una strategia di trading algoritmica automatizzata. Essere competenti in un linguaggio di programmazione come C++, Java, C#, Python o R permette di creare autonomamente il gestore di archiviazione dei dati end-to-end, il motore di backtest e il sistema di esecuzione. Questo ha una serie di vantaggi, il principale è la completa comprensione di tutti gli aspetti dell’infrastruttura di trading. Permette anche di esplorare le strategie a più alta frequenza in quanto hai il pieno controllo del tuo “stack tecnologico”. Questo significa che puoi testare il tuo software ed eliminare i bug, ma significa anche dedicare più tempo alla codifica dell’infrastruttura e meno all’attuazione delle strategie, almeno nella prima parte della tua carriera da algotrader. Potresti scoprire di essere a tuo agio nel trading in Excel o MATLAB e puoi esternalizzare lo sviluppo di altri componenti. Cosa che comunque non consiglio, in particolare per i trader che operano ad alta frequenza.

Devi chiederti cosa speri di ottenere con il trading algoritmico. Ti interessa un reddito regolare, per cui speri di ricavare guadagni dal tuo conto di trading? Oppure, sei interessato a un guadagno di capitale a lungo termine e puoi permetterti di fare trading senza la necessità di prelevare i fondi? La dipendenza dal reddito determinerà la frequenza della tua strategia. Prelievi più regolari di reddito richiederanno una strategia di trading a frequenza più alta con una minore volatilità (cioè un SharpeRatio più alto). I trader a lungo termine possono permettersi una frequenza di trading più tranquilla.

Infine, non illuderti di diventare estremamente ricco in un breve lasso di tempo! Il trading di Algo NON è uno schema che permette di arricchirsi rapidamente – semmai può essere uno schema che ti fa diventare povero molto più velocemente. Ci vuole una notevole disciplina, ricerca, diligenza e pazienza per avere successo nel trading algoritmico. Possono essere necessari mesi, se non anni, per generare una redditività costante.

Dove trovare nuove idee di trading algoritmico

Nonostante si pensi il contrario, in realtà è abbastanza semplice individuare strategie di trading redditizie tra quele di pubblico dominio. Mai come ora, le idee di trading sono facilmente disponibili per chiunque. Le riviste di finanza accademica, i blog di trading, i forum di trading, le riviste settimanli di trading e i testi specialistici forniscono migliaia di strategie di trading su cui basare le proprie idee.

Il nostro obiettivo come ricercatori di trading quantitativo è stabilire una pipeline di strategie che ci fornirà un flusso di nuove idee di trading. Idealmente vogliamo creare un approccio metodico per la ricerca, la valutazione e l’implementazione delle strategie che incontriamo. Gli obiettivi della pipeline sono di generare una quantità consistente di nuove idee e di fornirci un quadro per respingere la maggior parte di queste idee con un approccio non emotivo.

E’ necessario stare estremamente attenti a non permettere che i bias cognitivi influenzino la nostra metodologia decisionale. Questo potrebbe essere semplice come avere una preferenza per un asset o un mercato rispetto ad un’altro (ad esempio, l’oro e altri metalli preziosi) perché sono percepiti come più esotici. L’obiettivo deve essere sempre quello di trovare strategie costantemente  redditizie, con aspettative positive. La scelta della classe di attività dovrebbe essere basata su altre considerazioni, quali vincoli di capitale, le commissioni di intermediazione e capacità di leva finanziaria.

Se non conosci il concetto di una strategia di trading, il primo posto da guardare sono i libri di testo. I testi classici offrono una vasta gamma di idee più semplici e dirette con cui familiarizzare con il trading quantitativo. Ecco una selezione che raccomando per coloro che sono nuovi nel trading quantitativo, che gradualmente diventano più sofisticati mentre si procede con la lista:

 

Il passo successivo per trovare idee di strategie più sofisticate consiste nel consultare blog e forum di trading. Tuttavia, una nota di cautela: molti blog di trading si basano sul concetto di analisi tecnica. L’analisi tecnica si basa sull’uso di indicatori stardard e sulla  psicologia del trader che  determina le trendline o schemi di inversione dei prezzi degli asset.

Nonostante sia estremamente popolare nello mondo del trading, l’analisi tecnica è considerata in qualche modo inefficace dal comunity dei trader  quantitativi. Alcuni hanno suggerito che non sia differente dal leggere un oroscopo o studiare i fondi del caffè, in termini di potere predittivo! In realtà ci sono individui di successo che fanno uso dell’analisi tecnica. Tuttavia, come quants abbiamo a disposizione una strumentazione matematica e statistica più sofisticata, possiamo facilmente valutare l’efficacia di tali strategie basate sull’AT e prendere decisioni basate sui dati piuttosto che basarci sulle considerazioni emotive o sui preconcetti.

Ecco una lista dei migliori blog e forum sul trading algoritmico:

Dopo aver testato e valutato alcun delle strategie più semplici, per strategie più sofisticate è tempo di guardare alla letteratura accademica. Alcune riviste accademiche sono difficili da consultare, senza prevedere elevati abbonamenti o costi una tantum, è possibile consultare specifici server, che sono un repository web dei lavori accademici non ancora pubblicati perchè sono oggetto di revisione. Poiché ci interessa solamente le strategie che possiamo replicare, testare e ottenere una discreta redditività, la revisione prima della pubblicazione è un aspetto meno importante. Il principale svantaggio delle strategie accademiche è che possono spesso essere obsolete, richiedere dati storici oscuri e costosi, operate su asset poco liquide o non tenere conto dei costi delle commissioni, slippage o spread. Può anche non essere chiaro se la strategia di trading deve essere eseguita con ordini di mercato, ordini limite o se contiene stop loss ecc. Quindi è assolutamente essenziale replicare la strategia da soli nel miglior modo possibile, effettuare il backtest e aggiungere costi di transazioni realistici, cioè che includa tutti gli aspetti degli strumenti che vogliamo negoziare.

Ecco un elenco dei più noti server e riviste finanziarie da cui è possibile trarre nuove idee:

Trove nuove strategie quantitative richiede generalmente (ma non solo) una buona esperienza in una o più delle seguenti categorie:

  • Market microstructure – In particolare per le strategie ad alta frequenza, è possibile utilizzare la market microstructure, ossia l’analisi delle dinamiche dell’order-book al fine di generare redditività. Mercati diversi avranno vari limiti tecnologici, regolamentazioni, partecipanti al mercato e vincoli, tutti aspetti che possono essere sfruttati a vantaggio del trader tramite strategie specifiche. Questa è una zona molto sofisticata e i trader retail hanno difficoltà ad essere competitivi in ​​questo spazio, in particolare perché i competor sono  hedge fund quantitativi e ben capitalizzati, con forti capacità tecnologiche.
  • Fund structure – I fondi d’investimento, come i fondi pensione, le partnership di investimento private (hedge fund), le società di consulenza e altri tipi di sono sottoposti sia ad una pesante regolamentazione che dall’enorme capitale da dover gestire. Questi vincoli causano un comportamento “prevedile” e possono essere sfruttati da trader più piccoli e quindi più agili. Ad esempio, i fondi di grandi dimensioni sono soggetti a limiti di capacità a causa delle loro dimensioni. Quindi, se hanno bisogno di scaricare rapidamente (vendere) una quantità di titoli, dovranno essere molto cauti altrimenti rischiano di “spostare il mercato”. Algoritmi sofisticati possono trarre vantaggio da questa e da altre inefficiente in un processo generale noto come fund structure arbitrage.
  • Machine learning/artificial intelligence – Negli ultimi anni gli algoritmi di apprendimento automatico si sono diffusi anche nei mercati finanziari. Approcci con reti neurali e algoritmi genetici sono stati tutti usati per prevedere i movimenti dei prezzi o per ottimizzare le strategie di trading. 
    Ci sono, naturalmente, molte altre aree per i quants da investigare. Discuteremo su come elaborare strategie personalizzate in dettaglio in un articolo successivo.

 

Continuando a monitorare questi siti su base settimanale o giornaliera, si inizia a ricevere un elenco quasi costante di strategie da una vasta gamma di fonti. Il prossimo passo consiste nel determinare come rifiutare un ampio sottoinsieme di queste strategie al fine di minimizzare lo spreco di tempo e risorse per il backtesting di strategie che potrebbero non essere redditizie.

 

Valutazione delle Strategie di Trading

La prima, e probabilmente la più ovvia, considerazione l’effettiva comprensione della strategia. Siamo in grado di spiegare la strategia in modo conciso o richiederà una serie di eventi e un elenco di parametri senza fine? Inoltre, la strategia ha buone e solide basi realistiche? Ad esempio, si potrebbe verificare qualche comportamento del mercato o qualche vincolo a cui i fondi sono soggetti che potrebbe causare i pattern che si sta tentando di sfruttare? Questo vincolo sarebbe ancora valido in caso di modifiche strutturali, come un’improvvisa e ampia modifica al contesto normativo? La strategia si basa su complesse regole statistiche o matematiche? Si applica a tutte le serie finanziare o è specifica per un determinato strumento o asset?

Si deve costantemente valutare tutti questi fattori quando si cercano nuovi metodi di trading, altrimenti si rischia di sprecare una notevole quantità di tempo nel tentativo di backtesting  e ottimizzare strategie non redditizie.

Una volta compresi i principi alla base della strategia si deve decidere se si adatta al proprio stile di trading. Questa non è una considerazione vaga come sembra! Le strategie differiranno sostanzialmente nelle loro caratteristiche di rendimento. Vi sono alcuni tipi di persone che possono gestire periodi di drawdown più significativi o che sono disposti ad accettare un rischio maggiore al fine di ottenere un rendimento maggiore. Nonostante il fatto che noi, come quants, proviamo ad eliminare il più possibile i bias cognitivi e dovremmo essere in grado di valutare oggettivamente una strategia, i bias si insinueranno sempre. Quindi abbiamo bisogno di mezzi meccanici e non emotivi attraverso i quali valutare le prestazioni delle strategie . Ecco l’elenco dei criteri che osservo quando valuto una potenziale nuova strategia:

  • Metodologia – La strategia è basata sul momentum, mean-reverting, market-neutral, direzionale? La strategia si basa su sofisticate (o complesse!) tecniche statistiche o machine learning difficili da comprendere e che richiedono un dottorato di ricerca in statistica? Queste tecniche introducono una quantità significativa di parametri, che potrebbero portare a errori di ottimizzazione? La strategia è in grado di resistere a un cambio di regime (vale a dire una potenziale nuova regolamentazione dei mercati finanziari)?
  • SharpeRatio – Caratterizza euristicamente il rapporto reward/risk della strategia. Quantifica quanto profitto è possibile ottenere con un accettabile livello di volatilità della curva equity. Naturalmente, è necessario determinare il periodo e la frequenza nei quali i rendimenti e la volatilità (cioè la deviazione standard) sono misurati. Una strategia di frequenza più alta richiederà una maggiore frequenza di campionamento della deviazione standard, ma, per esempio, un tempo complessivo più breve.
  • Leverage: la strategia richiede una leva significativa per essere redditizia? La strategia richiede l’uso di contratti derivati ​​con leva (futures, opzioni, swap) al fine di ottenere un rendimento? Questi contratti a leva possono avere una forte volatilità e quindi possono facilmente portare a margin call. Si dispone del capitale e del temperamento per sopportare tale volatilità?
  • Frequenza – La frequenza della strategia è intimamente legata allo stack tecnologico (e quindi alla competenza tecnologica), al SharpeRatio e al livello generale dei costi di transazione. In generale, le strategie ad alta frequenza richiedono più capitale, sono più sofisticate e più difficili da implementare. Tuttavia, supponendo che motore di backtesting sia performante e privo di bug, queste strategie hanno SharpeRatio molto alti.
  • Volatilità: la volatilità è strettamente correlata al “rischio” della strategia. E’ contraddistinto dal SharpeRatio lo contraddistingue. Una maggiore volatilità degli asset class sottostanti, se non controllata, causa spesso una maggiore volatilità della curva equity e quindi un SharpeRatio più basso. Naturalmente presumo che la volatilità positiva sia approssimativamente uguale alla volatilità negativa. Alcune strategie possono avere una maggiore volatilità al ribasso. Devi essere consapevole di queste caratteristiche.
  • Win / Loss, Average Profit / Loss – Le strategie differiscono nelle loro caratteristiche per il numero di vincite/perdite e per la media profitti/perdite. Si può avere una strategia molto redditizia, anche se il numero di trade  perdenti supera il numero di operazioni vincenti. Le strategie di momentum tendono ad avere questo schema in quanto si basano su un piccolo numero di “grandi successi” per essere redditizi. Le strategie di inversione della tendenza tendono ad avere profili opposti in cui ci molti trade “vincenti”, ma le operazioni in perdita possono incidere molto negativemente sul capitale.
  • Drawdown massimo: il drawdown massimo è Il drawdown è la discesa, la correzione, da un precedente massimo assoluto fino ad un minimo assoluto nella curva equity della strategia. Le strategie del momentum sono ben note per soffrire di periodi di estesi drawdown (a causa di una serie incrementale di molti trade perdenti). Molti trader si disperano nei periodi di drawdown estesi, nonostante i test storici hanno suggerito che questo comportamento è “tipico” per questa strategia. Si deve stabile quale percentuale di drawdown (e in quale periodo di tempo) sono accettabili prima di interrompere l’esecuzione della strategia. Questa è una decisione altamente personale e quindi deve essere valutata attentamente.
  • Capacità / Liquidità – A livello retail, a meno che non si stia tradando uno strumento altamente illiquido (come uno stock a ridotta capitalizzazione), non dovrete preoccuparvi molto della capacità strategica. La capacità determina la scalabilità della strategia a seconda del capitale. Gli hedge fund più grandi hanno notevoli problemi di capacità man mano che le loro strategie aumentano nella dotazione di capitale da gestire.
  • Parametri – Alcune strategie (in particolare quelle presenti nella comunity del Machine Learning) richiedono una grande quantità di parametri. Ogni parametro introdotto rende una strategia più vulnerabile al bias di ottimizzazione (noto come “curve-fitting”). E’ consigliato individuare le strategie con il minor numero di parametri possibile o assicurarsi  di disporre di una sufficiente quantità di dati con cui testare le strategie.
  • Benchmark – Quasi tutte le strategie (a meno che non siano  “absolute return”) sono misurate rispetto a un benchmark di performance. Il benchmark è solitamente un indice che caratterizza un ampio campione dello strumento o mercato su cui la strategia opera. Se la strategia negozia titoli azionari statunitensi a grande capitalizzazione, l’S&P500 sarebbe un benchmark naturale per misurare la propria strategia. I termini “alpha” e “beta”, applicati a strategie di questo tipo. Discuteremo questi coefficienti in modo approfondito negli articoli successivi.

 

Da notare che non abbiamo parlato dei rendimenti effettivi di una strategia. In realtà, i rendimenti forniscono informazioni limitate sull’efficacia della strategia. Non permettono di valutare la leva finanziaria, la volatilità, i benchmark o i requisiti patrimoniali. Pertanto le strategie vengono giudicate raramente solo in base ai loro rendimenti. Considerare sempre le prestazioni relative al rischio di una strategia, prima di esaminare i rendimenti.

In questa fase molte delle strategie trovate dalla nostra pipeline sono respinte immediatamente, dal momento che non soddisfano i requisiti di capitale, i vincoli di leva, la massima tolleranza di drawdown o la volatilità. Le strategie che rimangono possono ora essere utilizzate per il backtesting. Tuttavia, prima che sia possibile, è necessario considerare un criterio finale di esclusione, quello sulla disponibilità dei dati storici su cui testare queste strategie.

Ottenere i dati storici

Al giorno d’oggi, la complessità dei requisiti tecnici per l’archiviazione dei dati storici di tutti le asset class è notevole. Al fine di rimanere competitivi, sia i buy-side (fondi) che i sell-side (banche d’investimento) investono pesantemente nella loro infrastruttura tecnica. È imperativo considerare la sua importanza. In particolare, siamo interessati alla tempestività, all’accuratezza e ai requisiti di archiviazione. Si descrive ora i concetti basi per ottenere dati storici e come memorizzarli. Purtroppo questo è un argomento molto vasto e tecnico, quindi questo articolo non può essere esaustivo. Tuttavia, ho in programma di approfondire questo tema con articoli futuri. Nella sezione precedente si era impostata una pipeline strategica che permetteva di scartare determinate strategie sulla base di alcuni criteri.

In questa sezione filtreremo maggiormente le strategie in base agli approcci scelti per ottenere i dati storici. Le considerazioni principali (soprattutto a livello di trader retail) sono i costi dei dati, i requisiti di archiviazione e il livello di competenza tecnica. Dobbiamo anche discutere i diversi tipi di dati disponibili e le diverse considerazioni che ogni tipo comporta.

Iniziamo identificando i tipi di dati disponibili e le questioni chiave su cui dovremo riflettere:

  • Dati fondamentali – Include i dati sugli andamenti macroeconomici, come tassi di interesse, i dati sull’inflazione, le dinamiche societarie (dividendi, scissioni), i bilanci societari, dati sugli utili, i rapporti sulle colture, dati meteorologici, ecc. Questi dati vengono spesso usati per valutare le società o altre attività su aspetti di ambito fondamentale, ad esempio attraverso i valori attesi per i futuri flussi di cassa. Non include le serie dei prezzi delle azioni. Alcuni dati fondamentali sono disponibili gratuitamente dai siti web dei governi. Altri dati fondamentali storici a lungo termine possono essere estremamente costosi. I requisiti di archiviazione spesso non sono particolarmente ampi, a meno che non vengano studiate contemporaneamente migliaia di società.
  • Dati delle notizie – Questi dati sono spesso di natura qualitativa. Consiste in articoli, post di blog, post di microblog (“tweet”) ed editoriali. Le tecniche di apprendimento automatico sono spesso usate per interpretare il sentiment. Questi dati sono spesso anche liberamente disponibili o economici, tramite l’abbonamento ai media. I nuovi database di archiviazione dei documenti “NoSQL” sono progettati per archiviare questo tipo di dati qualitativi non strutturati.
  • Dati sui prezzi degli asset – Questo è il dominio dei dati tradizionalmente utilizzato dagli algotrader. Consiste in serie temporali dei prezzi degli strumenti finanziari. Azioni (titoli), prodotti a reddito fisso (obbligazioni), materie prime e valute sono tutti presenti all’interno di questa tipologia. Per gli asset più diffusi i dati storici giornalieri sono spesso semplici da ottenere, come per le azioni. Tuttavia, una volta che accuratezza e pulizia sono state incluse e le distorsioni statistiche rimosse, i dati possono diventare costosi. Inoltre, i dati delle serie temporali hanno spesso requisiti di archiviazione significativi, specialmente quando si considerano i dati intraday.
  • Strumenti finanziari – Le azioni, le obbligazioni, i futures e le opzioni derivate più esotiche hanno caratteristiche e parametri molto diversi. Quindi non esiste una struttura di database “universale” che possa gestirli. È necessario prestare particolare attenzione alla progettazione e alla realizzazione dei database per i vari strumenti finanziari utilizzati. Si approfondisce questo tema negli articoli relativi ai securities master database.
  • Frequenza – Maggiore è la frequenza dei dati, maggiori sono i costi e i requisiti di archiviazione. Per le strategie a bassa frequenza, i dati giornalieri sono spesso sufficienti. Per le strategie ad alta frequenza, potrebbe essere necessario ottenere dati a livello di tick e persino i dati storici di particolari order-book. L’implementazione di un motore di archiviazione per questo tipo di dati è molto intensa dal punto di vista tecnologico e adatta solo a coloro che hanno un forte background tecnico / di programmazione.
  • Benchmark – Le strategie sopra descritte saranno spesso confrontate con un benchmark. Questo di solito si manifesta come serie temporali finanziarie aggiuntive. Per le azioni, questo benchmark è spesso un indice azionario nazionale, come l’S&P500 (USA) o il FTSE100 (Regno Unito). Per un fondo a reddito fisso, è utile confrontarsi con un paniere delle obbligazioni o di prodotti a reddito fisso. Anche il “risk-free rate” (cioè il tasso di interesse appropriato) è un altro benchmark ampiamente accettato. Tutte le categorie di asset class possiedono un relativo benchmark di riferimento, quindi sarà necessario effettuare una ricerca in base alla propria particolare strategia, se si desidera ottenere un ritorno interessate per la propria strategia.
  • Tecnologia – Le tecnologie usate dietro un centro di archiviazione di dati finanziari sono complesse. Questo articolo può solo introdurre tutti gli aspetti coinvolti nella costruzione di centro di archiviazione. Tuttavia, si concentra su un motore di database, come un sistema di gestione di database relazionale (RDBMS), come MySQL, SQL Server, Oracle) e uno schema di archiviazione di documenti (ad esempio “NoSQL”). Questo è accessibile tramite il codice che implementa la  “business logic”, interrogando il database e fornendo  l’accesso a strumenti esterni, come MATLAB, R o Excel. Spesso questa logica è scritta in C ++, C #, Java o Python. Bisogna anche bisogno di ospitare questi dati da qualche parte, sul un personal computer, o su un server remoto. Prodotti come Amazon Web Services hanno reso più semplice ed economico questo lavoro, ma richiede comunque una notevole esperienza tecnica per ottenere risultati solidi.

 

Come si può vedere, una volta che una strategia è stata identificata tramite la pipeline, sarà necessario valutare la disponibilità, i costi, la complessità e i dettagli di implementazione di un particolare insieme di dati storici. Potresti scoprire che è necessario scartare una strategia basandosi esclusivamente su valutazioni dei dati storici. Questo è un tema molto ampio ed è oggetto di lavoro di squadre di ricercatori all’interno dei grandi fondi di investimento, assicurandosi che i prezzi siano accurati e tempestivi. Non sottovalutare le difficoltà di creare un robusto data center per i propri scopi di backtesting!

Voglio sottolineare, tuttavia, che molte piattaforme di backtesting possono fornire questi dati automaticamente, ad un determinato costo. Di conseguenza, si toglie molto lavoro alla gestione dei dati e ci si può concentrare esclusivamente sull’implementazione e l’ottimizzazione della strategia. Strumenti come TradeStation possiedono questa capacità. Tuttavia, la mia opinione personale è quella di implementare internamente il più possibile ed evitare di esternalizzare parti dello stack ai fornitori di software. Preferisco le strategie ad alta frequenza grazie ai loro SharpeRatio più attraenti, ma sono spesso strettamente collegate allo stack tecnologico, dove l’ottimizzazione avanzata è fondamentale.

Ora che abbiamo discusso le questioni relative ai dati storici, è ora di iniziare a implementare le nostre strategie in un motore di backtesting. Questo sarà oggetto di altri articoli, poiché si tratta di un’area di discussione altrettanto ampia!