versión en español disponible aquí
En posts anteriores, empezamos a construir un bot de trading usando la API de Binance. Cubrimos la configuración inicial, implementamos la sincronización de datos y ejecutamos operaciones de trading basadas en medias móviles y análisis de tendencias. En este post, exploraremos estrategias de backtesting para evaluar el rendimiento de nuestro bot de trading utilizando datos históricos.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from binance.client import Client
import binance_utils as utl
from config import *
crypto y ref : Símbolos de criptomoneda y moneda de referencia.
symbol : Concatenación de los símbolos crypto y ref.
ema_f, ema_s, y sma : Las medias móviles utilizadas para la estrategia.
amount : Importe de cada operación en la moneda de referencia.
take_profit y stop_loss : Valores porcentuales para take profit y stop loss.
initial_balance_crypto e initial_balance_ref : Saldos iniciales para criptomonedas y monedas de referencia.
kline_interval, start y finish : Parámetros para la obtención de datos históricos.
client = Client(API_KEY, API_SECRET)
crypto = 'HIVE'
ref = 'USDT'
symbol = f'{crypto}{ref}'
# moving average periods
ema_f = 5
ema_s = 15
sma = 200
# operation size ref
amount = 15
# % on take profit and stop loss
take_profit = 2 /100
stop_loss = 6 /100
# initial balances
initial_balance_crypto = 100
initial_balance_ref = 100
# historic data params
kline_interval = Client.KLINE_INTERVAL_15MINUTE
start = "5 Jul, 2022"
finish = "5 Jul, 2023"
initialize_dataframe() recupera datos históricos de precios usando la API de Binance e inicializa un pandas DataFrame con las columnas requeridas. Esta función se llama dentro de la función parser(), que procesa aún más los datos y añade columnas para medias móviles exponenciales, media móvil simple, tendencia, operación, y balances.
def initialize_dataframe():
candles = client.get_historical_klines(symbol, kline_interval, start, finish)
df = pd.DataFrame(candles)
df = df.drop([6, 7, 8, 9, 10, 11], axis=1)
df.columns = ['time', 'open', 'high', 'low', 'close', 'volume']
df[['time', 'open', 'high', 'low', 'close', 'volume']] = df[['time', 'open', 'high', 'low', 'close', 'volume']].astype(float)
df['time'] = pd.to_datetime(df['time'] * 1000000)
return df
def parser():
df = initialize_dataframe()
df = pd.read_csv(f'./{symbol}_15M.csv')
df['ema_s'] = df['close'].ewm(span=ema_s).mean()
df['ema_f'] = df['close'].ewm(span=ema_f).mean()
df['sma'] = df['close'].rolling(window=sma).mean()
df['trend'] = np.nan
df['operation'] = np.nan
df['balance_crypto']= np.nan
df['balance_ref']= np.nan
df['profit']= np.nan
return df
backtest(df). Utiliza un bucle para iterar a través de las filas del DataFrame y simular operaciones de trading basadas en la estrategia definida. Estos son los pasos principales:
def backtest(df):
balance_ref = initial_balance_ref
balance_crypto = initial_balance_crypto
opened_order = False
i = sma
while i df['sma'].iloc[i-1]*1.005:
df['trend'].iloc[i] = 'up'
elif df['close'].iloc[i-1] = amount:
operation = 'BUY'
balance_ref = balance_ref - (quantity * price)
balance_crypto = balance_crypto + quantity
opened_order = True
order_type = 'SELL'
sell_price = ((price *(1+take_profit)) // tick_size) * tick_size
stop_price = ((price*(1-stop_loss)) // tick_size) * tick_size
stop_limit_price = ((price*(1-stop_loss)) // tick_size) * tick_size
elif utl.crossover((df['ema_s'].iloc[i-1], df['ema_s'].iloc[i]),(df['ema_f'].iloc[i-1], df['ema_f'].iloc[i])) and df['trend'].iloc[i] == 'down':
quantity = utl.get_quantity(price, amount, min_qty, max_qty, max_float_qty)
if quantity = sell_price:
balance_ref = balance_ref + (quantity * sell_price)
balance_crypto = balance_crypto - quantity
operation = 'SELL'
opened_order = False
elif price <= stop_price:
balance_ref = balance_ref + (quantity * stop_limit_price)
balance_crypto = balance_crypto - quantity
operation = 'SELL'
opened_order = False
elif order_type == 'BUY':
if price = stop_price:
balance_crypto = balance_crypto + quantity
balance_ref = balance_ref - (quantity * stop_limit_price)
operation = 'BUY'
opened_order = False
df['operation'].iloc[i]= operation
df['balance_crypto'].iloc[i]= balance_crypto
df['balance_ref'].iloc[i]= balance_ref
profit_crypto = round(balance_crypto - initial_balance_crypto,8)
profit_ref = round(balance_ref - initial_balance_ref,2)
total_profit = round(profit_ref + (profit_crypto * price),2)
df['profit'].iloc[i]= total_profit
i+=1
df.to_csv(f'{symbol}_MF:{ema_f}_MS:{ema_s}_MA:{sma}_TP:{int(take_profit*100)}_SL:{int(stop_loss*100)}.csv')
print("Backtesting Results:")
print(f'EMAF: {ema_f} EMAS: {ema_s} SMA: {sma} TP: {int(take_profit*100)} SL: {int(stop_loss*100)}\n')
print(f"Profit {crypto}: {round(profit_crypto, 8)} = {round(profit_crypto*price,2)} {ref}")
print(f"Profit {ref}: {round(profit_ref,2)}")
print(f"Total Profit : {total_profit} {ref}")
df.plot(x="time", y="profit", figsize=(10,5), legend=False)
plt.title(f'{symbol} EMAF:{ema_f} EMAS:{ema_s} SMA:{sma} TP:{int(take_profit*100)}% SL:{int(stop_loss *100)}%')
plt.xlabel('Time')
plt.ylabel('Profit')
plt.xticks(rotation=45)
plt.show()
|
|
|
Pues bien, hemos explorado la importancia de las estrategias de backtesting en el desarrollo de bots de trading de criptomonedas. Aprendimos cómo obtener datos históricos de precios de Binance e implementamos las modificaciones de código necesarias para permitir el backtesting. Mediante la evaluación de las métricas de rendimiento y la visualización de los resultados, podemos obtener información valiosa sobre la rentabilidad de la estrategia y el perfil de riesgo. Además, el backtesting nos permite optimizar la estrategia probando diferentes parámetros. En un post futuro, nos centraremos en la implementación de indicadores adicionales para mejorar el rendimiento del bot, ya que como podemos ver con solo las medias moviles no conseguimos un ROI atractivo, a pesar de mantener un balance positivo.
Es importante recalcar que los resultados del backtesting se basan en datos históricos y no garantizan el rendimiento futuro. Es esencial validar la estrategia en tiempo real y supervisarla y ajustarla continuamente a medida que cambian las condiciones del mercado.
Recuerda que el código proporcionado es sólo para fines educativos. Operar con criptomonedas implica riesgos, y el rendimiento pasado no es indicativo de resultados futuros. Investiga siempre a fondo y busca asesoramiento profesional antes de tomar cualquier decisión financiera.
Puedes consultar todo este código en mi GitHub y si tienes alguna pregunta o sugerencia no dudes en dejar un comentario.
referencias: python-binance
In previous posts, we started building a trading bot using the Binance API. We covered the initial setup, implemented data synchronization, and performed trading operations based on moving averages and trend analysis. In this post, we will explore backtesting strategies to evaluate the performance of our trading bot using historical data.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from binance.client import Client
import binance_utils as utl
from config import *
crypto and ref : Cryptocurrency and reference currency symbols.symbol : Concatenation of crypto and ref symbols.ema_f, ema_s, and sma : The moving averages used by the strategy.amount : Amount of each trade in the reference currency.take_profit and stop_loss : Take Profit and Stop Loss percentages.initial_balance_crypto and initial_balance_ref : Initial balances for cryptocurrencies and reference currencie.kline_interval, start y finish : Parameters to retrieve historical data.
client = Client(API_KEY, API_SECRET)
crypto = 'HIVE'
ref = 'USDT'
symbol = f'{crypto}{ref}'
# moving average periods
ema_f = 5
ema_s = 15
sma = 200
# operation size ref
amount = 15
# % on take profit and stop loss
take_profit = 2 /100
stop_loss = 6 /100
# initial balances
initial_balance_crypto = 100
initial_balance_ref = 100
# historic data params
kline_interval = Client.KLINE_INTERVAL_15MINUTE
start = "5 Jul, 2022"
finish = "5 Jul, 2023"
The initialize_dataframe() function retrieves historical quote data using the Binance API and initializes a Pandas DataFrame with the required columns. This function is called within the parser() function which further processes the data and adds columns for exponential moving averages, simple moving averages, trend, trade and balances.
def initialize_dataframe():
candles = client.get_historical_klines(symbol, kline_interval, start, finish)
df = pd.DataFrame(candles)
df = df.drop([6, 7, 8, 9, 10, 11], axis=1)
df.columns = ['time', 'open', 'high', 'low', 'close', 'volume']
df[['time', 'open', 'high', 'low', 'close', 'volume']] = df[['time', 'open', 'high', 'low', 'close', 'volume']].astype(float)
df['time'] = pd.to_datetime(df['time'] * 1000000)
return df
def parser():
df = initialize_dataframe()
df = pd.read_csv(f'./{symbol}_15M.csv')
df['ema_s'] = df['close'].ewm(span=ema_s).mean()
df['ema_f'] = df['close'].ewm(span=ema_f).mean()
df['sma'] = df['close'].rolling(window=sma).mean()
df['trend'] = np.nan
df['operation'] = np.nan
df['balance_crypto']= np.nan
df['balance_ref']= np.nan
df['profit']= np.nan
return df
The backtesting logic of our trading strategy is contained in the backtest(df) function. It uses a loop to iterate through the rows of the DataFrame and simulate trading operations based on the defined strategy. Here are the main steps:
def backtest(df):
balance_ref = initial_balance_ref
balance_crypto = initial_balance_crypto
opened_order = False
i = sma
while i df['sma'].iloc[i-1]*1.005:
df['trend'].iloc[i] = 'up'
elif df['close'].iloc[i-1] = amount:
operation = 'BUY'
balance_ref = balance_ref - (quantity * price)
balance_crypto = balance_crypto + quantity
opened_order = True
order_type = 'SELL'
sell_price = ((price *(1+take_profit)) // tick_size) * tick_size
stop_price = ((price*(1-stop_loss)) // tick_size) * tick_size
stop_limit_price = ((price*(1-stop_loss)) // tick_size) * tick_size
elif utl.crossover((df['ema_s'].iloc[i-1], df['ema_s'].iloc[i]),(df['ema_f'].iloc[i-1], df['ema_f'].iloc[i])) and df['trend'].iloc[i] == 'down':
quantity = utl.get_quantity(price, amount, min_qty, max_qty, max_float_qty)
if quantity = sell_price:
balance_ref = balance_ref + (quantity * sell_price)
balance_crypto = balance_crypto - quantity
operation = 'SELL'
opened_order = False
elif price <= stop_price:
balance_ref = balance_ref + (quantity * stop_limit_price)
balance_crypto = balance_crypto - quantity
operation = 'SELL'
opened_order = False
elif order_type == 'BUY':
if price = stop_price:
balance_crypto = balance_crypto + quantity
balance_ref = balance_ref - (quantity * stop_limit_price)
operation = 'BUY'
opened_order = False
df['operation'].iloc[i]= operation
df['balance_crypto'].iloc[i]= balance_crypto
df['balance_ref'].iloc[i]= balance_ref
profit_crypto = round(balance_crypto - initial_balance_crypto,8)
profit_ref = round(balance_ref - initial_balance_ref,2)
total_profit = round(profit_ref + (profit_crypto * price),2)
df['profit'].iloc[i]= total_profit
i+=1
df.to_csv(f'{symbol}_MF:{ema_f}_MS:{ema_s}_MA:{sma}_TP:{int(take_profit*100)}_SL:{int(stop_loss*100)}.csv')
print("Backtesting Results:")
print(f'EMAF: {ema_f} EMAS: {ema_s} SMA: {sma} TP: {int(take_profit*100)} SL: {int(stop_loss*100)}\n')
print(f"Profit {crypto}: {round(profit_crypto, 8)} = {round(profit_crypto*price,2)} {ref}")
print(f"Profit {ref}: {round(profit_ref,2)}")
print(f"Total Profit : {total_profit} {ref}")
df.plot(x="time", y="profit", figsize=(10,5), legend=False)
plt.title(f'{symbol} EMAF:{ema_f} EMAS:{ema_s} SMA:{sma} TP:{int(take_profit*100)}% SL:{int(stop_loss *100)}%')
plt.xlabel('Time')
plt.ylabel('Profit')
plt.xticks(rotation=45)
plt.show()
Backtesting allows us to optimize our trading strategy by testing different parameters and evaluating their performance. By comparing the results, we can identify the most effective parameter settings for our strategy.
|
|
|
Well, we explored the importance of backtesting strategies in the development of cryptocurrency trading bots. We learned how to obtain historical price data from Binance and made the necessary code changes to enable backtesting. By evaluating performance metrics and visualizing the results, we are able to gain valuable information about the profitability and risk profile of the strategy. In addition, backtesting allows us to optimize the strategy by testing different parameters. In a future post, we will focus on implementing additional indicators to improve the performance of the bot, since as we can see, with only moving averages we do not achieve an attractive ROI, despite maintaining a positive balance.
It is important to emphasize that backtesting results are based on historical data and do not guarantee future performance. It is essential to validate the strategy in real time and to continuously monitor and adjust it as market conditions change.
Remember that the code provided is for educational purposes only. Trading cryptocurrencies involves risk and past performance is not indicative of future results. Always do your research and seek professional advice before making any financial decisions.
You can check all this code on my GitHub and if you have any questions or suggestions please feel free to leave a comment.
references: python-binance