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