Backtesting uma estratégia de previsão para o S & P500 em Python com pandas

Autora:Bem-estar, Criado: 2019-03-29 14:24:50, Atualizado:

Bibliotecas Python maduras como matplotlib, pandas e scikit-learn também reduzem a necessidade de escrever código de boilerplate ou criar nossas próprias implementações de algoritmos bem conhecidos.

A estratégia de previsão

A estratégia de previsão em si é baseada em uma técnica de aprendizado de máquina conhecida como analisador discriminante quadrático, que está intimamente relacionada a um analisador discriminante linear.

O prognosticador usa os dois retornos diários anteriores como um conjunto de fatores para prever a direção atual do mercado de ações. Se a probabilidade de o dia ser up exceder 50%, a estratégia compra 500 ações do SPY ETF e as vende no final do dia. se a probabilidade de um dia de queda exceder 50%, a estratégia vende 500 ações do SPY ETF e depois compra novamente no fechamento. Assim, é o nosso primeiro exemplo de uma estratégia de negociação intradiária.

Observe que esta não é uma estratégia de negociação particularmente realista! É improvável que alcancemos um preço de abertura ou fechamento devido a muitos fatores, como a volatilidade excessiva de abertura, o encaminhamento de ordens pela corretora e possíveis problemas de liquidez em torno da abertura / fechamento. Além disso, não incluímos os custos de transação. Estes provavelmente seriam uma porcentagem substancial dos retornos, pois há um comércio de ida e volta realizado todos os dias. Assim, nosso previsor precisa ser relativamente preciso na previsão dos retornos diários, caso contrário, os custos de transação comerão todos os nossos retornos de negociação.

Implementação

Como com os outros tutoriais relacionados ao Python / pandas, eu usei as seguintes bibliotecas:

  • Python - 2.7.3
  • NumPy - 1.8.0
  • Pandas - 0.12.0
  • matplotlib - 1.1.0
  • Sikit-learn - 0,14.1

A implementação de snp_forecast.py abaixo requerbacktest.pyde este tutorial anterior.forecast.pyO primeiro passo é importar os módulos e objetos necessários:

# 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

Uma vez que todas as bibliotecas e módulos relevantes foram incluídos, é hora de subclassificar a classe básica abstrata da Estratégia, como fizemos em tutoriais anteriores. A Estratégia SNPForecasting é projetada para ajustar um Analisador de Discriminação Quádrática ao índice de ações S&P500 como um meio de prever seu valor futuro. A ajuste do modelo é realizada no método fit_model abaixo, enquanto os sinais reais são gerados a partir do método generate_signals. Isso corresponde à interface de uma classe de Estratégia.

Os detalhes de como um analisador discriminante quadrático funciona, bem como a implementação do Python abaixo, são descritos em detalhes no artigo anterior sobre previsão de séries temporais financeiras.

# 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

Agora que o motor de previsão produziu os sinais, podemos criar um MarketIntradayPortfolio.

A carteira é projetada para far long (comprar) 500 ações da SPY no preço de abertura se o sinal indicar que um dia de alta ocorrerá e depois vender no fechamento.

Para isso, a diferença de preço entre os preços de mercado aberto e de fechamento do mercado é determinada todos os dias, levando a um cálculo do lucro diário das 500 ações compradas ou vendidas. Isso, então, leva naturalmente a uma curva de equidade, somando cumulativamente o lucro/perda para cada dia.

Esta é a lista do 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

O último passo consiste em ligar os objectivos da Estratégia e do Portfólio a umprincipalA função obtém os dados para o instrumento SPY e, em seguida, cria a estratégia de geração de sinal no próprio índice S&P500. Isso é fornecido pelo ticker ^GSPC. Em seguida, um MarketIntradayPortfolio é gerado com um capital inicial de 100.000 USD (como nos tutoriais anteriores). Finalmente, os retornos são calculados e a curva de ações é traçada.

Observe quão pouco código é necessário nesta fase porque toda a computação pesada é realizada nas subclasses Estratégia e Portfólio.

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()

O resultado do programa é dado abaixo. Neste período, o mercado de ações retornou 4% (assumindo uma estratégia de compra e retenção totalmente investida), enquanto o próprio algoritmo também retornou 4%. Observe que os custos de transação (como taxas de comissão) não foram adicionados a este sistema de backtesting.

Performance da Estratégia de Previsão do S&P500 de 1 de janeiro de 2005 a 31 de dezembro de 2006

Nos artigos seguintes, adicionaremos custos de transacção realistas, utilizaremos motores de previsão adicionais, determinaremos métricas de desempenho e forneceremos ferramentas de otimização de carteira.


Mais.