Las bibliotecas Python maduras como matplotlib, pandas y scikit-learn también reducen la necesidad de escribir código de boilerplate o crear nuestras propias implementaciones de algoritmos bien conocidos.
La estrategia de pronóstico en sí se basa en una técnica de aprendizaje automático conocida como analizador discriminante cuadrático, que está estrechamente relacionada con un analizador discriminante lineal.
El pronosticador utiliza los dos rendimientos diarios anteriores como un conjunto de factores para predecir la dirección del mercado de valores de hoy. Si la probabilidad de que el día sea
Tenga en cuenta que esta no es una estrategia comercial particularmente realista! Es poco probable que logremos un precio de apertura o cierre debido a muchos factores, como la volatilidad excesiva de apertura, el enrutamiento de órdenes por parte de la correduría y los posibles problemas de liquidez alrededor de la apertura / cierre. Además, no hemos incluido los costos de transacción. Estos probablemente serían un porcentaje sustancial de los rendimientos, ya que hay un comercio de ida y vuelta realizado todos los días. Por lo tanto, nuestro pronosticador debe ser relativamente preciso para predecir los rendimientos diarios, de lo contrario, los costos de transacción se comerán todos nuestros rendimientos comerciales.
Al igual que con los otros tutoriales relacionados con Python / pandas he utilizado las siguientes bibliotecas:
La implementación de snp_forecast.py a continuación requierebacktest.pyde este tutorial anterior. Ademásforecast.pyEl primer paso es importar los módulos y objetos necesarios:
# 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 vez que se han incluido todas las bibliotecas y módulos relevantes, es hora de subclasificar la clase base abstracta de Estrategia, como hemos realizado en tutoriales anteriores. SNPForecastingStrategy está diseñado para adaptar un analizador de discriminantes cuadráticos al índice bursátil S&P500 como un medio para predecir su valor futuro.
Los detalles de cómo funciona un analizador de discriminantes cuadráticos, así como la implementación de Python a continuación, se describen en detalle en el artículo anterior sobre pronóstico de series temporales financieras.
# 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
Ahora que el motor de pronóstico ha producido las señales, podemos crear un MarketIntradayPortfolio.
La cartera está diseñada para "ir largo" (comprar) 500 acciones de SPY al precio de apertura si la señal indica que se producirá un día de alza y luego vender al cierre.
Para lograr esto, la diferencia de precio entre los precios de apertura y cierre del mercado se determina todos los días, lo que conduce a un cálculo de la ganancia diaria de las 500 acciones compradas o vendidas. Esto luego conduce naturalmente a una curva de capital mediante la suma acumulada de la ganancia / pérdida de cada día. También tiene la ventaja de permitirnos calcular estadísticas de ganancia / pérdida para cada día.
Este es el listado de la cartera MarketIntraday:
# 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
El paso final consiste en vincular los objetivos de la Estrategia y de la Cartera con unael principalLa función obtiene los datos para el instrumento SPY y luego crea la estrategia de generación de señales en el propio índice S&P500. Esto es proporcionado por el ticker ^GSPC. Luego se genera una cartera MarketIntraday con un capital inicial de 100,000 USD (como en los tutoriales anteriores). Finalmente, se calculan los rendimientos y se traza la curva de renta variable.
Observe cuán poco código se requiere en esta etapa porque todo el cálculo pesado se lleva a cabo en las subclases de Estrategia y Cartera.
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()
El resultado del programa se muestra a continuación. En este período, el mercado de valores devolvió el 4% (suponiendo una estrategia de compra y retención totalmente invertida), mientras que el algoritmo mismo también devolvió el 4%. Tenga en cuenta que los costos de transacción (como las comisiones) no se han agregado a este sistema de backtesting.
Performance de la estrategia de pronóstico del S&P500 desde 2005-01-01 hasta 2006-12-31
En los artículos siguientes añadiremos costes de transacción realistas, utilizaremos motores de pronóstico adicionales, determinaremos métricas de rendimiento y proporcionaremos herramientas de optimización de cartera.