El impulso del sector es una estrategia de rotación del sector para impulsar el rendimiento clasificando los sectores según su impulso, comprando los de mejor desempeño y vendiendo los rezagados.
En esta publicación, describo el impulso del sector y por qué funciona, y realizo una prueba retrospectiva de una estrategia de rotación algorítmica del sector en Backtrader. El objetivo no es proporcionar un análisis exhaustivo y estadísticamente significativo de las estrategias de rotación del impulso del sector, sino capacitar a los lectores ambiciosos para que puedan probar sus estrategias algorítmicas. trading estrategias.
¿Qué es la estrategia de rotación del impulso del sector?
Existen 11 sectores bursátiles que agrupan empresas en función de sus productos o servicios. Históricamente, cada sector se desempeña de manera diferente en función de dónde nos encontremos en el ciclo económico. La idea detrás de una estrategia de rotación de impulso es clasificar cada sector, utilizando el impulso, comprar los sectores con mejor rendimiento y, opcionalmente, vender en corto a los rezagados. Esto se denomina estrategia de rotación del sector «N superior» que utiliza el impulso como su señal cuantitativa.
Las principales variables en una estrategia de rotación de impulso superior N son:
- El cálculo del impulso. Utilizo un cálculo de pendiente de 90 días.
- La ventana retrospectiva. Uso el periodo de 20 años 1999-2018
- Él trading estrategia. Solo voy largo
- El porcentaje de clasificación. vamos a optimizar
- El número de activos. vamos a optimizar
La suposición implícita con las estrategias de impulso es que las tendencias persistirán y podemos aprovechar la anomalía del impulso.
¿Por qué funciona el impulso?
La anomalía del impulso está bien documentada y es generalizada. Por lo general, se explica como errores de inversión de comportamiento, como reacción insuficiente, pastoreo de inversores/FOMO y sesgo de confirmación. Es especialmente frecuente en Impulso de la industriatal como lo describen Tobias J. Moskowitz y Mark Grinblatt.
Si no está familiarizado con la anomalía del impulso, le sugiero que vea la charla del Dr. Wes Gray sobre Momentum Investing.
Estrategia de impulso del sector
Para el ejemplo, tomo los datos de precios de cierre diarios de los siguientes índices, ajustados por dividendos y recompras, para los años 1999 a 2018. Eliminé los sectores de Bienes Raíces y Comunicaciones ya que ambos se formaron recientemente. Para una estrategia de producción, es probable que desee analizar las empresas XLC y XLRE existentes y crear un conjunto de datos que reproduzca el rendimiento que habría tenido durante un período de tiempo más prolongado.
Los datos son proporcionados por intrinioque es uno de mis proveedores de datos de valores favoritos. SPDRXLKSSgA Sector selecto de tecnología SPDRXLUSSgA Sector selecto de servicios públicos SPDR
Luego clasifico cada sector según su impulso. En lugar de solo clasificar según los rendimientos, utilizo una regresión lineal basada en la pendiente de la línea durante los 90 días anteriores y la anualizo. El reequilibrio se produce el primer día de cada mes.
Optimización de la estrategia
Hay mucha información en la web sobre el ajuste de curvas y la optimización excesiva. Analizaremos la matriz de períodos de impulso de 50, 100, 150, 200 y 250 días con el número de ETF.
El período de impulso óptimo
Durante los veinte años que terminaron en 2018, el período de impulso de 150 días fue el mejor.
El mejor número de participaciones
Si bien no hablamos de volatilidad y reducciones, nueve fue el único conteo de espera que apareció más de una vez.
Los resultados del impulso del sector
El SPY devolvió el 168 %, lo que significa que 10 000 de capital inicial se convertirían en 26 800.
Solo alrededor del 22%, o 10 de las 45 pruebas anteriores en los 20 años que terminaron en 2018, no superaron el punto de referencia SPY.
Una vez más, aunque muchos artículos académicos sugieren que el impulso del sector funciona, obtener más granularidad muestra un mejor rendimiento. Puede modificar fácilmente el siguiente programa para usar industrias en lugar de sectores.
la optimización
A starting capital of $10,000.Momentum PeriodNumber of HoldingsValue50114025.5350219483.4950335866.5850429206.0550630055.3750728154.9850936940.1450525608.2950835946.76100226911.97100330009.83100115601.72100439984.76100544146.40100633990.44100935862.82100741110.49150125366.22150233968.55150355104.87150442536.01150538116.67100834032.96150640125.33150738413.02150935787.82150832763.17200223756.33200335165.14200133424.86200432359.93200635351.41200535648.14200730516.58200834453.88200936586.48250323196.63250124591.44250734139.63250220700.87250933503 .18250528150.97250422896.26250629603.19250831928.43
Algorítmico Sector Momentum Backtrader Estrategia
Creé esta estrategia en Backtrader usando datos de mi cliente Base de datos de valores de PostgreSQL. El objetivo aquí no es ser un recurso exhaustivo sobre el impulso del sector, sino capacitarlo para que pueda probar los trabajos de investigación y las estrategias que recomiendan.
Si es nuevo en Backtrader, lea mi publicación, Backtrader: Primeros pasos. La documentación y la comunidad de Backtrader son excelentes. La inspiración para la publicación fue agregar alguna funcionalidad a la estrategia de impulso.
Si desea ejecutar una optimización, use cerebro.optstrategy con los parámetros de optimización en lugar de cerebro.addstrategy. En el ejemplo de este post, se ejecutaría lo siguiente:
cerebro.optstrategy(Strategy, momentum_period=range(50,300,50), num_positions=range(1,len(ETF_TICKERS) + 1))
Primero, obtenemos todas las importaciones y modelos de datos. Puede encontrar los módulos personalizados, incluido este código, en la Analizando Alfa Github.
import os, sys
import pandas as pd
import numpy as np
import backtrader as bt
import setup_psql_environment
from models import Security, SecurityPrice
from scipy.stats import linregress
from collections import defaultdict
from tabulate import tabulate
import PyQt5
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
import backtrader.plot
from matplotlib.pyplot import figure
etf_tickers = ['XLB', 'XLE', 'XLF', 'XLI', 'XLK', 'XLP', 'XLU', 'XLV', 'XLY']
Luego defino una función de impulso que devuelve una pendiente anualizada de rendimientos logarítmicos como se describe en Acciones en movimiento por Andreas Clenow. Sin profundizar demasiado, la ecuación de la pendiente favorece pendientes menos volátiles. Creamos un indicador declarativo, que puede ser beneficioso si tenemos mucho código complejo, ya que podemos crearlo como un módulo e importarlo.
def momentum_func(self, price_array):
r = np.log(price_array)
slope, _, rvalue, _, _ = linregress(np.arange(len(r)), r)
annualized = (1 + slope) ** 252
return (annualized * (rvalue ** 2))
class Momentum(bt.ind.OperationN):
lines = ('trend',)
params = dict(period=90)
func = momentum_func
Luego creo nuestra estrategia Backtrader heredando de bt.Strategy y parametrizando donde puedo. Defino los métodos de notificación_temporizador y reequilibrio, que son bastante sencillos. Defino stop como nuestro análisis de optimizaciones que se ejecutará después de cada iteración de la estrategia.
class Strategy(bt.Strategy):
params = dict(
momentum=Momentum,
momentum_period=180,
num_positions=2,
when=bt.timer.SESSION_START,
timer=True,
monthdays=[1],
monthcarry=True,
printlog=True
)
def log(self, txt, dt=None, doprint=False):
''' Logging function fot this strategy'''
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
self.i = 0
self.securities = self.datas[1:]
self.inds = {}
self.add_timer(
when=self.p.when,
monthdays=self.p.monthdays,
monthcarry=self.p.monthcarry
)
for security in self.securities:
self.inds[security] = self.p.momentum(security,
period=self.p.momentum_period)
def notify_timer(self, timer, when, *args, **kwargs):
if self._getminperstatus() < 0:
self.rebalance()
def rebalance(self):
rankings = list(self.securities)
rankings.sort(key=lambda s: self.inds[s][0], reverse=True)
pos_size = 1 / self.p.num_positions
# Sell stocks no longer meeting ranking filter.
for i, d in enumerate(rankings):
if self.getposition(d).size:
if i > self.p.num_positions:
self.close(d)
# Buy and rebalance stocks with remaining cash
for i, d in enumerate(rankings[:self.p.num_positions]):
self.order_target_percent(d, target=pos_size)
def next(self):
self.notify_timer(self, self.p.timer, self.p.when)
def stop(self):
self.log('| %2d | %2d | %.2f |' %
(self.p.momentum_period,
self.p.num_positions,
self.broker.getvalue()),
doprint=True)
if __name__ == '__main__':
cerebro = bt.Cerebro()
# Create an SQLAlchemy conneciton to PostgreSQL and get ETF data
db = setup_psql_environment.get_database()
session = setup_psql_environment.get_session()
query = session.query(SecurityPrice, Security.ticker).join(Security). \
filter(SecurityPrice.date >= START_DATE). \
filter(SecurityPrice.date <= END_DATE). \
filter(Security.code == 'ETF').statement
dataframe = pd.read_sql(query,
db,
index_col=['ticker', 'date'],
parse_dates=['date'])
dataframe.sort_index(inplace=True)
dataframe = dataframe[['adj_open',
'adj_high',
'adj_low',
'adj_close',
'adj_volume']]
dataframe.columns = ['open', 'high', 'low', 'close', 'volume']
# Add Spy as datas0
spy = dataframe.loc['SPY']
benchdata = bt.feeds.PandasData(dataname=spy, name='spy', plot=True)
cerebro.adddata(benchdata)
dataframe.drop('SPY', level='ticker', inplace=True)
Realizo una manipulación básica en pandas en un índice multidimensional para alinear los datos de la manera que espera Backtrader, y luego ejecuto la optimización.
# Add securities as datas1:
for ticker, data in dataframe.groupby(level=0):
if ticker in ETF_TICKERS:
print(f"Adding ticker: {ticker}")
data = bt.feeds.PandasData(dataname=data.droplevel(level=0),
name=ticker,
plot=False)
data.plotinfo.plotmaster = benchdata
cerebro.adddata(data)
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
# Add Strategy
stop = len(ETF_TICKERS) + 1
cerebro.optstrategy(Strategy,
momentum_period=range(50, 300, 50),
num_positions=range(1, len(ETF_TICKERS) + 1))
# Run the strategy. Results will be output from stop.
cerebro.run(stdstats=False, tradehistory=False)