Estrategia: Cruce de medias + Backtest

Gerard Sánchez - 31 de Mayo de 2021 a las 13:02 - Estrategias




Las medias móviles suavizan los datos de los precios para formar un indicador de seguimiento de tendencia. No predicen la dirección de los precios, sino que definen la dirección actual, aunque son indicadores retrasados debido a que se basan en precios históricos. Los periodos antiguos se eliminan a medida que se dispone de nuevos datos, lo que hace que el promedio se mueva a lo largo de la escala de tiempo que hayamos escogido.

 A pesar de esto, las medias móviles ayudan a suavizar la acción del precio y filtrar el ruido.

En este ejemplo vamos a programar una estrategia básica de cruce de medias, una corta y una larga.

  • Un cruce alcista ocurre cuando la media móvil corta cruza al alza con respecto a la media móvil larga. 
  • Un cruce bajista ocurre cuando la media móvil corta cruza a la baja con respecto a la media móvil larga.

Los cruces de medias móviles producen señales relativamente tardías, el sistema emplea dos indicadores rezagados. Cuanto más largos sean los períodos de medias, mayor será el retraso en las señales. Estas señales funcionan muy bien cuando se establece una buena tendencia. Sin embargo, el sistema no es tan efectivo para mercados laterales, ya que producirá numerosas señales falsas.

Vamos a programar la estrategia:

En primer lugar nos descargamos los precios de cierre o cierre ajustado para un activo (de momento no nos preocupa el número de datos de la muestra)

import matplotlib.pyplot as plt
import pandas as pd
import pandas_datareader.data as wb
import numpy as np
import seaborn as sns

assets = ['BAC']
data = wb.DataReader(assets, 'yahoo', '2018-1-1')['Adj Close']

Declaramos 2 variables que van a ser nuestros periodos de tiempo para las medias móviles, configurables por el usuario:

short_window = 50
long_window = 150

Creamos un DataFrame con pandas y le pasamos el argumento opcional index para que contenga las fechas. Después programamos estas medias móviles en columnas:

signal = pd.DataFrame(index=data.index)
signal['short'] = data.rolling(short_window).mean()
signal['long'] = data.rolling(long_window).mean()

A continuación modificamos la columna de la media móvil corta para que coincida con el inicio de la larga:

signal['short'] = signal['short'].iloc[long_window:]

Creamos una columna con una variable discreta (0,1) para cuando la media móvil corta está por encima o por debajo:

signal['signals'] = np.where(signal['short'] > signal['long'], 1, 0)

Finalmente, con .diff(), hacemos otra columna en la que nos detecte el momento exacto del cruce y nos lo marque:

signal['positions'] = signal['signals'].diff()

Empezamos la graficación con plt.figure() y add_subplot():

fig = plt.figure(figsize=(20,10))
ax1 = fig.add_subplot(121, ylabel=assets)
ax1.set_title("Estrategia cruce de medias con: " + str(assets))

Renombramos data para que empiece a dibujar el precio a partir del periodo que tengamos en long_window, (así queda más limpio), y le especificamos el primer subplot (ax=ax1)

data.iloc[long_window:].plot(ax=ax1, color='k', lw=1.9)

Dibujamos las medias móviles y los momentos de cruce (1,-1) con '^' y 'v' en color verde y rojo respectivamente:

signal[['short', 'long']].plot(ax=ax1, lw=1.3)
ax1.plot(signal['short'][signal['positions'] == 1], '^', markersize=8, color='g')
ax1.plot(signal['short'][signal['positions'] == -1], 'v', markersize=8, color='r')

El gráfico sale bien, tenemos nuestras señales de entrada y salida bien definidas. Cada operación ocurriría en el precio de cierre del periodo.

A continuación procederemos a programar los resultados de nuestra estrategia.

Debemos tener en cuenta que los resultados no son reales ni significativos, ya que ni los precios de ejecución serán correctos, ni tendremos tampoco un control sobre las comisiones, pero sí pueden servirnos para dar una idea de cómo hacer un "backtest". Para esta comprobación vamos a programar que para la operación de compra se adquieran 300 acciones, y cuando el cruce sea a la baja se venda todo. Sin infraponderar o sobreponderar.

Primero declaramos el capital inicial y el número de acciones en dos variables configurables por el usuario:

capital = int(100000)
stocks = int(300)

Generamos un DataFrame y creamos una columna para tener en cuenta en qué momento se tienen esas 300 acciones:

positions = pd.DataFrame(index=signal.index)
positions['BAC'] = stocks*signal['signals']

Creamos una variable portfolio en la que se calculara el precio de esas 300 acciones en cada momento de la cotización:

portfolio = positions.multiply(data)

Generamos una variable para tener esas diferencias con .diff() para saber en qué momento se producen las compras y ventas:

pos_diff = positions.diff()

Pasamos la variable portfolio a una columna llamada "Cartera", calculamos el efectivo que tenemos en cada momento teniendo en cuenta las compras y ventas realizadas y también una columna para tener el total, que será lo que acabaremos graficando.

portfolio['Cartera'] = portfolio
portfolio['Cash'] = capital - (pos_diff.multiply(data).cumsum())
portfolio['total'] = portfolio['Cash'] + portfolio['Cartera']

A continuación calculamos los retornos de la estrategia y eliminamos los periodos en los que la estrategia está parada (0.0) para poder tener un histograma limpio:

portfolio['Returns'] = portfolio['total'].pct_change()[1:]
portfolio['Returns'] = portfolio['Returns'][portfolio['Returns'] != 0]

Vamos a graficar el backtest, seguimos con ax2 y añadiendo subplots para graficar la columna del total y las señales con '^' y 'v'

ax2= fig.add_subplot(222, ylabel='Valor de la cartera')
portfolio['total'].plot(ax=ax2, lw=2., label="Total de la cartera")
ax2.plot(portfolio['total'][signal['positions'] == 1], '^', markersize=8, color='g')
ax2.plot(portfolio['total'][signal['positions'] == -1], 'v', markersize=8, color='r')

Añadimos un ax3 para el gráfico de los retornos de la estrategia y lo mostramos en un histograma con la librería seaborn y procedemos a mostrarlo con plt.show()

ax3= fig.add_subplot(224)
sns.histplot(portfolio['Returns'], kde=True, ax=ax3)
plt.show()

 

El código entero:

import matplotlib.pyplot as plt
import pandas as pd
import pandas_datareader.data as wb
import numpy as np
import seaborn as sns

assets = ['BAC']
data = wb.DataReader(assets, 'yahoo', '2018-1-1')['Adj Close']

short_window = 50
long_window = 150

signal = pd.DataFrame(index=data.index)

signal['short']= data.rolling(short_window).mean()
signal['long']= data.rolling(long_window).mean()
signal['short'] = signal['short'].iloc[long_window:]

signal['signals'] = np.where(signal['short'] > signal['long'], 1, 0)

signal['positions'] = signal['signals'].diff()

fig = plt.figure(figsize=(20,10))
ax1 = fig.add_subplot(121, ylabel=assets)
ax1.set_title("Estrategia cruce de medias con: " + str(assets))
data.iloc[long_window:].plot(ax=ax1, color='k', lw=1.9)
signal[['short', 'long']].plot(ax=ax1, lw=1.3)
ax1.plot(signal['short'][signal['positions'] == 1], '^', markersize=8, color='g')
ax1.plot(signal['short'][signal['positions'] == -1], 'v', markersize=8, color='r')


#############################################################################################################
#############################################################################################################
#############################################################################################################


capital = int(100000)
stocks = int(300)

positions = pd.DataFrame(index=signal.index)
positions['BAC'] = stocks*signal['signals']
portfolio = positions.multiply(data)

pos_diff = positions.diff()

portfolio['Cartera'] = portfolio
portfolio['Cash'] = capital - (pos_diff.multiply(data).cumsum())
portfolio['total'] = portfolio['Cash'] + portfolio['Cartera']
portfolio['Returns'] = portfolio['total'].pct_change()[1:]

portfolio['Returns'] = portfolio['Returns'][portfolio['Returns'] != 0]

ax2= fig.add_subplot(222, ylabel='Valor de la cartera')
portfolio['total'].plot(ax=ax2, lw=2., label="Total de la cartera")
ax2.plot(portfolio['total'][signal['positions'] == 1], '^', markersize=8, color='g')
ax2.plot(portfolio['total'][signal['positions'] == -1], 'v', markersize=8, color='r')

ax3= fig.add_subplot(224)
sns.histplot(portfolio['Returns'], kde=True, ax=ax3)
plt.show()

 

 


707 visitas
6    Login to like
Categorías:
 Estrategias   Estadísticas   Random   Gestión pasiva   Análisis técnico   Modelos   CEO   Mapas mentales   Liberalismo   Python   Growth   Niusleta   Ahorro   Recursos humanos   Inmobiliario   Fiscalidad   Value investing   Dividendos   Contabilidad   Marketing   Riesgo   IF   Cursos   Opciones   Bolsa