Neste artigo, utilizaremos o mecanismo que introduzimos para realizar a investigação de uma estratégia real, a saber, o cruzamento da média móvel em AAPL.
A técnica de cruzamento da média móvel é uma estratégia de impulso simplista extremamente conhecida.
A estratégia descrita aqui é apenas longa. Dois filtros simples móveis separados são criados, com períodos de retrospectiva variáveis, de uma série de tempo particular. Os sinais para comprar o ativo ocorrem quando a média móvel de retrospectiva mais curta excede a média móvel de retrospectiva mais longa. Se a média mais longa subsequentemente exceder a média mais curta, o ativo é vendido de volta. A estratégia funciona bem quando uma série de tempo entra em um período de forte tendência e, em seguida, lentamente reverte a tendência.
Para este exemplo, eu escolhi a Apple, Inc. (AAPL) como a série temporal, com um lookback curto de 100 dias e um lookback longo de 400 dias. Este é o exemplo fornecido pela biblioteca de negociação algorítmica zipline.
Certifique-se de seguir o tutorial anterior aqui, que descreve como a hierarquia de objetos inicial para o backtester é construída, caso contrário o código abaixo não funcionará.
A implementação do ma_cross.py requerbacktest.pyO primeiro passo é importar os módulos e objetos necessários:
# ma_cross.py
import datetime
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from pandas.io.data import DataReader
from backtest import Strategy, Portfolio
Como no tutorial anterior, vamos subclasse a estratégia abstract base classe para produzir MovingAverageCrossStrategy, que contém todos os detalhes sobre como gerar os sinais quando as médias móveis de AAPL cruzar um sobre o outro.
O objeto requer uma short_window e uma long_window para operar. Os valores foram definidos para padrões de 100 dias e 400 dias, respectivamente, que são os mesmos parâmetros usados no exemplo principal de zipline.
As médias móveis são criadas usando a função panda rolling_mean nas barras [
# 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
O MarketOnClosePortfolio é subclassificado do Portfolio, que se encontra embacktest.pyÉ quase idêntico à implementação descrita no tutorial anterior, com a exceção de que os negócios agora são realizados em uma base Close-to-Close, em vez de uma base Open-to-Open. Para detalhes sobre como o objeto Portfolio é definido, consulte o tutorial anterior.
# 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=signals.index).fillna(0.0)
positions[self.symbol] = 100*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
Agora que as classes MovingAverageCrossStrategy e MarketOnClosePortfolio foram definidas, umaprincipalA estratégia de desempenho será examinada através de um gráfico da curva de equidade.
O objeto Panda DataReader faz o download dos preços OHLCV das ações da AAPL para o período de 1o de janeiro de 1990 a 1o de janeiro de 2002, momento em que os sinais DataFrame são criados para gerar os sinais de longo prazo.
O passo final é usar o matplotlib para traçar um gráfico de dois dígitos de ambos os preços AAPL, sobrepostos com as médias móveis e sinais de compra / venda, bem como a curva de ações com os mesmos sinais de compra / venda.
# 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.ix[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.ix[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.ix[signals.positions == 1.0].index,
returns.total[signals.positions == 1.0],
'^', markersize=10, color='m')
ax2.plot(returns.ix[signals.positions == -1.0].index,
returns.total[signals.positions == -1.0],
'v', markersize=10, color='k')
# Plot the figure
fig.show()
A saída gráfica do código é a seguinte. Eu usei o comando IPython %paste para colocá-lo diretamente no console IPython enquanto estava no Ubuntu, para que a saída gráfica permanecesse visível. Os upticks rosas representam a compra do estoque, enquanto os downticks pretos representam vendê-lo de volta:Performance cruzada média móvel da AAPL de 1990-01-01 a 2002-01-01
Como se pode ver, a estratégia perde dinheiro ao longo do período, com cinco operações de ida e volta. Isto não é surpreendente, dado o comportamento da AAPL durante o período, que teve uma ligeira tendência de queda, seguida de um aumento significativo a partir de 1998.
Nos artigos seguintes, criaremos um meio mais sofisticado de analisar o desempenho, bem como descreveremos como otimizar os períodos de retrospectiva dos sinais individuais da média móvel.