¿Cómo implementar la estrategia de trading Supertrend en Python para un posible buen rendimiento?

0
322
grafico supertrend btcusd

Este artículo es una guía para la codificación y backtesting de una estrategia con el indicador Supertrend en Python. Es una estrategia que utiliza la acción del precio y la volatilidad para identificar la dirección de la tendencia.

Introducción

El indicador Supertrend es una herramienta diseñada para observar las tendencias y reversiones del mercado. Todo lo que necesitas es tomar buenas decisiones de inversión, por supuesto, algunas pueden fallar, pero es un juego de consistencia: necesitas más victorias que derrotas. Necesitas saber cuándo entrar o salir del mercado.

¿Por qué vamos a usar Python para esta implementación?

Si has trabajado con Python antes, sabes que es un lenguaje de programación muy versátil y completo con excelentes bibliotecas, especialmente diseñadas para el análisis y la visualización de datos. Pandas es una de esas bibliotecas que proporciona formas eficientes de importar, limpiar y preprocesar datos de mercado, dejándolos listos para el análisis. También es excelente para manejar grandes conjuntos de datos, un aspecto esencial cuando se trata de datos de mercado.

También tenemos a Numpy, otra biblioteca útil para el análisis financiero. Es muy poderosa al proporcionar una extensa colección de funciones y herramientas matemáticas que son muy útiles para los cálculos estadísticos. Estas funciones aceleran los cálculos sobre grandes conjuntos de datos, como cuando se trata de datos de mercado, proporcionando así análisis e información más profundos en un corto período de tiempo. Esto, en comparación con revisar manualmente numerosos gráficos, puede consumir mucho tiempo y puede no llevar a un análisis preciso.

Los traders más rentables nunca operan con sesgos y sentimientos emocionales que pueden llevarlos a malas decisiones de inversión. Es por eso que usan estrategias de trading algorítmico como esta, que potencialmente puede llevar a mayores rendimientos.

¿Cómo funciona el indicador Supertrend?

Imaginemos esto: un río a veces fluye suavemente y otras veces se comporta de manera tan brusca que puede desbordarse. Esto es bastante similar a los mercados: los precios pueden moverse suavemente u oscilar de manera salvaje y alcanzar ciertos rangos inesperados. Así que el indicador Supertrend observa estos movimientos, lo que se conoce como volatilidad.

 Lo hace midiendo el Average True Range (ATR), que es básicamente el rango de negociación de un activo dentro de un período especificado. El Supertrend luego usa el ATR para generar la banda superior y la banda inferior, que son más como límites dentro de los cuales los precios de los activos oscilan durante ese período. Si el precio rompe el límite superior, eso señala una tendencia alcista, y si rompe el límite inferior, señala una tendencia bajista. Es un concepto sencillo, ¿verdad?

Cálculo del Supertrend

Primero y, ante todo, calculamos el Average True Range (ATR). Esta es la medida de la volatilidad. Se calcula durante un período de tiempo especificado.

Para esto vamos a comenzar obteniendo el True Range (TR) para cada punto de datos. Un punto de datos puede recopilarse por día, en un marco de tiempo de 4 horas, por hora, o por minuto, etc. Todo depende de la configuración del marco de tiempo.

TR = Máx(Alto — Bajo, Abs(Alto — Cierre Anterior), Abs(Bajo — Cierre Anterior))

Luego calculamos el ATR para el período especificado, digamos que podríamos usar un período de 14, que es el valor más comúnmente usado en el ATR. Esto significa que el ATR se calcula para cada 14 puntos de datos (podemos tomarlo como filas en el dataframe), lo que es básicamente el promedio de los True Ranges (TR) dentro de ese período.

Implementación del Supertrend en Python

Habiendo entendido los conceptos básicos del indicador Supertrend, ahora debemos poner manos a la obra y ejecutarla con Python.

Paso 1: Prepara tu entorno de codificación

Instala la última distribución de Python 3 si aún no la tienes. Puedes verificar la versión de Python en tu máquina ejecutando “python -v” en tu terminal.

Instala todas las bibliotecas de Python necesarias como Pandas, Numpy, Matplotlib, mplfinance y ccxt.

Paso 2: Importar las librerías necesarias

import ccxt
import warnings
from matplotlib.pyplot import fill_between
import pandas as pd
import numpy as np
import pandas_ta as ta
import mplfinance as mpf
import matplotlib.pyplot as plt
warnings.filterwarnings('ignore')

Importa las librerías necesarias para el proyecto. La librería ccxt es el proveedor de datos que se conecta a varios intercambios de criptomonedas; en este caso, lo usaremos para conectarnos a Binance. Las otras bibliotecas, Pandas y Numpy, son para cálculos estadísticos, mientras que mplfinance y matplotlib son para crear gráficos para visualizar los datos.

Paso 3: Obtén los datos del activo de un proveedor (ccxt)

def fetch_asset_data(symbol, start_date, interval, exchange):
   #Convertir fecha de inicio a marca de tiempo en milisegundos
   start_date_ms = exchange.parse8601(start_date)
   ohlcv = exchange.fetch_ohlcv(symbol, interval, since=start_date_ms)
   header = ["date", "open", "high", "low", "close", "volume"]
   df = pd.DataFrame(ohlcv, columns=header)
   df['date'] = pd.to_datetime(df['date'], unit='ms')
   df.set_index("date", inplace=True)
   # Eliminar la última fila que contiene datos en vivo
   df.drop(df.index[-1], inplace=True)
   return df

La función “fetch_asset_data” obtiene los datos históricos de precios necesarios para el cálculo del indicador y la prueba de backtesting. Toma 4 argumentos: el símbolo del ticker, la fecha de inicio, el período de tiempo (como diario, por hora, etc.) y el intercambio en el que se encuentra el instrumento que se quiere analizar.

Primero, convierte la fecha de inicio en un número que la API del intercambio entiende. Luego, se conecta a ese intercambio y solicita todos los precios de apertura, máximo, mínimo y cierre (OHLC) desde esa fecha de inicio hasta ahora para el símbolo del ticker.

Luego coloca todos esos datos de precios en un dataframe ordenado y elimina la última fila de datos. ¿Por qué? Porque esa última fila podría tener datos de precios en vivo que están cambiando en este momento. ¡Y no podemos usar precios futuros cuando estamos haciendo pruebas retrospectivas de lo que sucedió en el pasado! Así que eliminamos esa última fila para darnos solo datos históricos hasta el día de hoy. Luego nos devuelve la tabla de dataframe limpia.

Paso 4: Calcula las líneas de Supertrend

def supertrend(df, atr_multiplier=3):
   # Calcular la banda superior (BS) y la banda inferior (BI)
   # Formula: Supertrend =(Maximo+Minimo)/2 + (Multiplicador)∗(ATR)
   current_average_high_low = (df['high']+df['low'])/2
   df['atr'] = ta.atr(df['high'], df['low'], df['close'], period=15)
   df.dropna(inplace=True)
   df['basicUpperband'] = current_average_high_low + (atr_multiplier * df['atr'])
   df['basicLowerband'] = current_average_high_low - (atr_multiplier * df['atr'])
   first_upperBand_value = df['basicUpperband'].iloc[0]
   first_lowerBand_value = df['basicLowerband'].iloc[0]
   upperBand = [first_upperBand_value]
   lowerBand = [first_lowerBand_value]

   for i in range(1, len(df)):
       if df['basicUpperband'].iloc[i] < upperBand[i-1] or df['close'].iloc[i-1] > upperBand[i-1]:
           upperBand.append(df['basicUpperband'].iloc[i])
       else:
           upperBand.append(upperBand[i-1])

       if df['basicLowerband'].iloc[i] > lowerBand[i-1] or df['close'].iloc[i-1] < lowerBand[i-1]:
           lowerBand.append(df['basicLowerband'].iloc[i])
       else:
           lowerBand.append(lowerBand[i-1])

   df['upperband'] = upperBand
   df['lowerband'] = lowerBand
   df.drop(['basicUpperband', 'basicLowerband',], axis=1, inplace=True)
   return df

Aquí, la función “supertrend” toma el dataframe que obtuvimos de la última función. Para el cálculo del indicador también se necesita otra cosa: el número “atr_multiplier”.

La función calcula el Average True Range o ATR. Esto nos dice cuánto ha estado subiendo y bajando el precio cada día o en cualquier período configurado, para que sepamos cuán “volátil” o variable es el mercado. Utiliza el ATR y el número del multiplicador para calcular la banda superior básica y la banda inferior básica en cada fila.

El código comienza a mirar cada fila, excepto la primera. Para la banda superior: si la banda superior básica es menor que la banda superior final de la fila anterior, o si el precio de cierre está por encima de la banda superior final de la fila anterior, agrega el valor de la banda superior básica a la lista de bandas superiores finales.

Hace lo mismo para la banda inferior, solo verificando si la banda inferior básica es mayor que la banda inferior final de la fila anterior, o si el precio de cierre está por debajo de la banda inferior final de la fila anterior.

Después de revisar cada fila, el código elimina las columnas que ya no necesitamos y devuelve el dataframe. Estas bandas finales (df[‘upperband’] y df[‘lowerband’]) y el precio de cierre nos indicarán las señales que van a indicar cuándo comprar o vender.

Paso 5: Generación de las señales de trading

def generate_signals(df):
   # Intiate a signals list
   signals = [0]

   #Bucle que recorre el marco de datos en bucle
   for i in range(1 , len(df)):
       if df['close'][i] > df['upperband'][i]:
           signals.append(1)
       elif df['close'][i] < df['lowerband'][i]:
           signals.append(-1)
       else:
           signals.append(signals[i-1])

   # Agregar la lista de señales como una nueva columna en el marco de datos
   df['signals'] = signals
   df['signals'] = df["signals"].shift(1) #Eliminar el sesgo de mirar hacia adelante
   return df

La función “generate_signals” comienza creando una lista llamada “signals” y establece la primera señal en 0. Luego recorre todo el dataframe fila por fila para encontrar señales de compra y venta.

Una señal de compra ocurre cuando el precio de cierre de esa fila supera el valor de la banda superior. Una señal de venta ocurre cuando el precio de cierre cae por debajo del valor de la banda inferior.

Si la señal actual es la misma que la anterior, mantenemos la posición igual que en la última fila.

Cada vez que mira una nueva fila, actualiza la lista de señales con la nueva señal para esa fila. Luego agrega toda la lista de señales como una nueva columna en el dataframe llamada “df[‘signals’].

Elimina el sesgo de anticipación para simular la toma de posiciones de una manera similar a como lo harías en un entorno de trading en vivo después de haber visto la señal. De esta manera, no obtenemos señales utilizando información del futuro, por lo que los resultados de las pruebas de backtesting son más realistas y confiables. La función luego devuelve el “df”, que se usará para crear posiciones.

Paso 6: Generar las posiciones de trading

def create_positions(df):
   #Necesitamos apagar los puntos de datos (np.nan) en la banda superior donde la señal no es 1
   df['upperband'][df['signals'] == 1] = np.nan
   #Necesitamos apagar los puntos de datos (np.nan) en la banda inferior donde la señal no es -1
   df['lowerband'][df['signals'] == -1] = np.nan

   #Creación de una lista de posiciones
   buy_positions = [np.nan]
   sell_positions = [np.nan]

   # Recorrer el marco de datos en bucle
   for i in range(1, len(df)):
       # If the current signal is a 1 (Buy) & the it's not equal to the previous signal
       # Then that is a trend reversal, so we BUY at that current market price
       # We take note of the upperband value
       if df['signals'][i] == 1 and df['signals'][i] != df['signals'][i-1]:
           buy_positions.append(df['close'][i])
           sell_positions.append(np.nan)
       #Si la señal actual es -1 (Vender) y no es igual a la señal anterior
       #Entonces se trata de una reversión de tendencia, por lo que VENDEMOS a ese precio de mercado actual
       elif df['signals'][i] == -1 and df['signals'][i] != df['signals'][i-1]:
           sell_positions.append(df['close'][i])
           buy_positions.append(np.nan)
       else:
           buy_positions.append(np.nan)
           sell_positions.append(np.nan)

   #Agregue la lista de posiciones como una nueva columna en el marco de datos
   df['buy_positions'] = buy_positions
   df['sell_positions'] = sell_positions
   return df

La función comienza eliminando algunos números en las bandas superiores e inferiores que no necesitamos ver. ¿Recuerdas cómo se calcularon esas bandas superiores e inferiores antes? Bueno, a veces las bandas seguían mostrando los valores de precios de cierre incluso cuando la señal no indicaba comprar o vender allí. Eso podría causar confusión más tarde cuando sólo queramos ver las bandas donde realmente tenemos una posición abierta (tendencia). Entonces, esas primeras dos líneas en la función simplemente están limpiando las cosas para nosotros. Básicamente, coloca “NaN” en los lugares de la banda superior donde la señal no es “1”, y en la banda inferior donde la señal no es “-1”. Especialmente cuando queremos trazar las bandas, no queremos trazar líneas completas, solo los puntos donde la tendencia está activa. En este caso, “NaN” es un número especial que significa “sin valor”. Esto tendrá más sentido a medida que avancemos.

Luego, inicializamos dos listas vacías: una para nuestros precios de compra y otra para nuestros precios de venta. La función luego recorre el dataframe mirando cada fila, excepto la primera.

Entonces, si la señal es un “1” (significa comprar) y es diferente a la señal anterior, eso es una reversión, así que realizamos una compra. Agrega el precio de cierre de esa fila a la lista de compra. De manera similar, si la señal es un “-1” (vender) y es diferente a la anterior, eso es una venta. Agrega el precio de cierre a la lista de venta. Si nada cambió, simplemente deja esos espacios vacíos (“NaN”) en las listas.

Después de revisar cada fila, agrega las listas finales de compra y venta como nuevas columnas al dataframe. Ahora tenemos un registro claro de exactamente cuándo habríamos comprado y vendido según las señales.

Paso 7: Visualizar los datos

def plot_data(df, symbol):
   #Definir el gráfico de línea de banda inferior
   lowerband_line = mpf.make_addplot(df['lowerband'], label= "lowerband", color='green')
   #Definir el gráfico de línea de banda superior
   upperband_line = mpf.make_addplot(df['upperband'], label= "upperband", color='red')
   #Definir marcadores de compra y venta
   buy_position_makers = mpf.make_addplot(df['buy_positions'], type='scatter', marker='^', label= "Buy", markersize=80, color='#2cf651')
   sell_position_makers = mpf.make_addplot(df['sell_positions'], type='scatter', marker='v', label= "Sell", markersize=80, color='#f50100')
   #Una lista de todos los addplots(apd)
   apd = [lowerband_line, upperband_line, buy_position_makers, sell_position_makers]
   # Crear gráficos de relleno
   lowerband_fill = dict(y1=df['close'].values, y2=df['lowerband'].values, panel=0, alpha=0.3, color="#CCFFCC")
   upperband_fill = dict(y1=df['close'].values, y2=df['upperband'].values, panel=0, alpha=0.3, color="#FFCCCC")
   fills = [lowerband_fill, upperband_fill]
   #Graficado de los datos
   mpf.plot(df, addplot=apd, type='candle', volume=True, style='charles', xrotation=20, title=str(symbol + ' Supertrend Plot'), fill_between=fills)

Ahora queremos visualizar los datos y la función “plot_data crea líneas en el gráfico para mostrar las bandas inferiores y superiores que calculamos antes. La banda inferior será verde y la superior será roja.

También creará marcadores para mostrar exactamente cuándo compramos y vendemos: pequeñas flechas hacia arriba para las compras y hacia abajo para las ventas. Estos serán un poco grandes y coloridos para que destaquen. Luego agrupa todas esas líneas y marcadores juntos para que podamos agregarlos al gráfico. Luego procede a agregar áreas sombreadas entre el precio de cierre y las bandas. Esto hará que sea muy claro visualizar las tendencias al alza y a la baja.

Por último, representa las velas para el ohlv (apertura, máximo, mínimo, cierre), establece el título y las leyendas, y luego muestra el gráfico. Ahora veremos fácilmente cómo se habría desarrollado la estrategia con el tiempo.

Paso 8: Cálculo del rendimiento de la estrategia

def strategy_performance(strategy_df, capital=100, leverage=1):
   #Inicializar las variables de rendimiento
   cumulative_balance = capital
   investment = capital
   pl = 0
   max_drawdown = 0
   max_drawdown_percentage = 0

   # Listas para almacenar valores intermedios para calcular métricas
   balance_list = [capital]
   pnl_list = [0]
   investment_list = [capital]
   peak_balance = capital

   # Bucle desde la segunda fila (índice 1) del DataFrame
   for index in range(1, len(strategy_df)):
       row = strategy_df.iloc[index]

       # Calculate P/L for each trade signal
       if row['signals'] == 1:
           pl = ((row['close'] - row['open']) / row['open']) * \
               investment * leverage
       elif row['signals'] == -1:
           pl = ((row['open'] - row['close']) / row['close']) * \
               investment * leverage
       else:
           pl = 0

       # Actualizar la inversión si hay una reversión de la señal
       if row['signals'] != strategy_df.iloc[index - 1]['signals']:
           investment = cumulative_balance

       # Calcular el nuevo saldo en función del P/L y el apalancamiento
       cumulative_balance += pl

       # Actualizar la lista de inversión
       investment_list.append(investment)

       # Calcular el saldo acumulado y añadirlo al DataFrame
       balance_list.append(cumulative_balance)

       # Calcular el P/L general y agregarlo al DataFrame
       pnl_list.append(pl)

       # Cálculo del drawdown máximo
       drawdown = cumulative_balance - peak_balance
       if drawdown < max_drawdown: max_drawdown = drawdown max_drawdown_percentage = (max_drawdown / peak_balance) * 100 # Update the peak balance if cumulative_balance > peak_balance:
           peak_balance = cumulative_balance

   # Agregar nuevas columnas al DataFrame
   strategy_df['investment'] = investment_list
   strategy_df['cumulative_balance'] = balance_list
   strategy_df['pl'] = pnl_list
   strategy_df['cumPL'] = strategy_df['pl'].cumsum()

   # Calcular otras métricas de rendimiento (reemplazarlas con tus cálculos)
   overall_pl_percentage = (
       strategy_df['cumulative_balance'].iloc[-1] - capital) * 100 / capital
   overall_pl = strategy_df['cumulative_balance'].iloc[-1] - capital
   min_balance = min(strategy_df['cumulative_balance'])
   max_balance = max(strategy_df['cumulative_balance'])

   # Mostrar las métricas de la estrategia
   print("Overall P/L: {:.2f}%".format(overall_pl_percentage))
   print("Overall P/L: {:.2f}".format(overall_pl))
   print("Min balance: {:.2f}".format(min_balance))
   print("Max balance: {:.2f}".format(max_balance))
   print("Maximum Drawdown: {:.2f}".format(max_drawdown))
   print("Maximum Drawdown %: {:.2f}%".format(max_drawdown_percentage))

   # Retornar el marco de datos de estrategia
   return strategy_df

Necesitamos verificar algunas métricas para ver qué tan bien funciona nuestra estrategia de trading. Eso es lo que hace esta función “strategy_performance“. Primero averigua cuánto valdría nuestro dinero si simplemente lo mantuvieramos en una cuenta bancaria sin invertir, por ejemplo, eso es el punto de referencia.

Ahora se configuran algunas listas vacías y variables para rastrear cosas como el balance, las ganancias y pérdidas (P&L), y cuánto dinero invertimos durante cada posición. Luego mira cada fila en el dataframe de la estrategia, una por una.

Para cada señal, calcula el P&L viendo cuánto cambió el precio del activo entre el momento cuando compramos y el momento en que vendimos, y luego actualiza nuestro balance, cantidad de inversión y agrega los nuevos números a las listas. También sigue otras métricas como el balance más alto y más bajo que hemos tenido, la ganancia total, el drawdown máximo, etc. Ten en cuenta que esta función también compone los rendimientos, es decir, la inversión es el saldo disponible incluso de todas las posiciones cerradas.

Paso 9: Visualización del desempeño de la estrategia

def plot_performance_curve(strategy_df):
   # Plot the strategy performace curve
   plt.plot(strategy_df['cumulative_balance'], label='Strategy')
   plt.title('Performance Curve')
   plt.xlabel('Date')
   plt.ylabel('Balance')
   plt.xticks(rotation=70)
   plt.legend()
   plt.show()

Aquí representamos gráficamente los rendimientos de la estrategia y del punto de referencia para comparar qué tan bien habría funcionado la estrategia.

Paso 10: Ejecución del script

if __name__ == '__main__':
    # Inicializar parámetros de obtención de datos
    symbol = "BTC/USDT"
    start_date = "2024-1-1"
    interval = '4h'
    exchange = ccxt.binance()

    # Obtenga datos históricos de OHLC para BTC/USDT
    data = fetch_asset_data(symbol=symbol, start_date=start_date, interval=interval, exchange=exchange)

    volatility = 3

    # Aplicar la fórmula del indicador Supertrend
    supertrend_data = supertrend(df=data, atr_multiplier=volatility)

    # Generar las señales de trading
    supertrend_positions = generate_signals(supertrend_data)

    # Generar las posiciones
    supertrend_positions = create_positions(supertrend_positions)

    # Cálculo del desempeño de la estrategia
    supertrend_df = strategy_performance(supertrend_positions, capital=100, leverage=1)
    print(supertrend_df)

    # Trazado de los datos
    plot_data(supertrend_positions, symbol=symbol)
    
    # Trazado de la curva de desempeño
    plot_performance_curve(supertrend_df)

Configura las variables dentro del constructor “if name == ‘main’:“, y luego ve al punto de la terminal, es decir, al directorio que contiene el archivo y luego ejecuta el script.

En este ejemplo, mi archivo se llama supertrend.py, por lo que es ejecutado en la terminal escribiendo “python supertrend.py“.

Aquí está el dataframe resultante:

dataframe datos precios btcusd

Y estos fueron los indicadores de rendimiento calculados. Ten en cuenta que el rendimiento pasado no garantiza resultados futuros. Por lo tanto, utiliza esta estrategia con precaución. Y, por supuesto, nada en este artículo es un consejo de inversión. Todo es únicamente con fines educativos.

Como puedes ver, durante un período de 3 meses, con una inversión de $100 y sin apalancamiento, BTC/USDT habría generado un retorno de inversión del 40%. Siéntete libre de calcular otros indicadores como el índice de Sortino, el alfa, etc.

datos rendimiento estrategia

Y este fue el gráfico de la estrategia (está un poco alejado, por lo que algunos puntos de datos pueden estar recortados).

grafico supertrend btcusd

Y, por último, aquí está el gráfico resultante para la curva de rendimiento:

curva rendimiento

Limitaciones de la estrategia

Sensibilidad de parámetros: El indicador SuperTrend puede ser sensible a sus parámetros, como el período y el multiplicador de volatilidad ATR. Diferentes activos pueden moverse mucho en un período dado, pero ser bastante tranquilos en otros. Entonces, lo que funcionó bien para una criptomoneda, por ejemplo, puede no funcionar tan bien para otro. Podrías ajustar los parámetros de volatilidad para adaptarlos al par de negociación.

Diferencias de volatilidad: El indicador SuperTrend depende en gran medida del ATR para capturar tendencias, pero si un activo es demasiado volátil o experimenta fluctuaciones de precios bruscas y frecuentes, puede producir señales falsas o no capturar tendencias significativas. Por lo tanto, el indicador de SuperTrend puede no ser adecuado para activos altamente volátiles.

Para concluir, la estrategia con el indicador Supertrend es una herramienta poderosa que puede ayudar a los inversores a tomar decisiones bien informadas respecto a sus actividades en el mercado. Ayuda a identificar las tendencias actuales y las reversiones en el mercado, lo que permite a los operadores ejecutar señales de entrada y salida oportunas.

 


 

Raul Canessa

Leave a reply