versión en español disponible aquí
En mi post anterior, empezamos a construir un bot de trading automatico utilizando la API de Binance. Cubrimos la configuración inicial, incluyendo la configuración de la clave API y la importación de las bibliotecas necesarias. implementamos la lógica para el cálculo de medias móviles y la identificación de puntos de cruce. Ahora, vamos a continuar con más código y explorar funcionalidades adicionales.
Descargo de responsabilidad
La información proporcionada en este post es sólo para fines educativos y no debe interpretarse como asesoramiento de inversión. El trading automatizado conlleva un alto riesgo en el que se puede perder el capital invertido. Es importante hacer su propia investigación antes de tomar cualquier decisión de inversión.
Estructura de archivos
Para mantener una estructura modular, hemos dividido el código en tres archivos:
binance_utils.py
: Este archivo contiene funciones auxiliares utilizadas por el bot, incluyendo la consulta de balance, cálculo de cantidad, detección de cruce de las medias moviles, etc.config.py
: Este archivo almacena la API key y secrect, necesarios para acceder a la API de Binance.bot.py
: Este archivo contiene el código principal del bot. Se encarga de la sincronización de datos, inicializa el marco de datos, analiza los datos de las velas, realiza operaciones de trading y ejecuta el bucle infinito.
import numpy as np
# Helper functions
def get_balance(balances, asset):
for bal in balances:
if bal.get('asset') == asset:
return float(bal['free'])
def get_max_float_qty(step_size):
max_float_qty = 0
a = 10
while step_size * a < 1:
a = a*10**max_float_qty
max_float_qty += 1
return max_float_qty
def get_quantity(price, amount, min_qty, max_qty, max_float_qty):
quantity = amount / price
if (quantity < min_qty or quantity > max_qty):
return False
quantity = np.round(quantity, max_float_qty)
return quantity
def crossover(ma_fast, ma_slow):
if (ma_fast[0] < ma_slow[0] and ma_fast[1] >= ma_slow[1]):
return True
return False
API_KEY = "YOUR API KEY"
API_SECRET = "YOUR API SECRET"
Importando Módulos
Tenemos que importar la libreríabinance.enums
que nos permitirá manipular las órdenes OCO para poder colocar órdenes take profit y stop loss.
import time
import datetime as dt
import pandas as pd
import numpy as np
import pytz
from binance.client import Client
from binance.enums import *
import binance_utils as ut
from config import *
Definición de variables y parámetros
Esta vez añadimos una media móvil simple de 200 periodos para calcular la tendencia.
crypto = 'BTC'
ref = 'USDT'
symbol = crypto + ref
amount = 15
# Eligible periods for candlesticks 1m, 3m, 5m, 15m, 1h, etc.
period = '15m'
# Moving Average Periods
ema_f = 15
ema_s = 50
sma = 200
Sincronización de datos
Para asegurar un análisis preciso de los datos, empezamos por sincronizar la hora del bot con el servidor de Binance. La función
synchronize()
recupera los últimos datos de velas y calcula la hora de inicio del bucle del bot.
def synchronize():
candles = client.get_klines(symbol=symbol,interval=period,limit=1)
timer = pd.to_datetime(float(candles[0][0]) * 1000000)
start = timer + dt.timedelta(minutes=step)
print('Synchronizing .....')
while dt.datetime.now(dt.timezone.utc) < pytz.UTC.localize(start):
time.sleep(1)
time.sleep(2)
print('Bot synchronized')
Inicializando el Dataframe
La funcióninitialize_dataframe()
obtiene datos históricos de velas de Binance y crea un DataFrame de pandas.
def initialize_dataframe(limit):
candles = client.get_klines(symbol=symbol,interval=period,limit=limit)
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
Análisis de datos de velas
La funciónparser()
inicializa el marco de datos y lo actualiza con los últimos datos de las velas. Calcula las EMAs, SMA, e identifica la tendencia actual basada en la relación del precio con el SMA.
def parser():
df = initialize_dataframe(sma+1)
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
# get trend
if df['close'].iloc[-1] > df['sma'].iloc[-1]*1.005:
trend = 'up'
elif df['close'].iloc[-1] < df['sma'].iloc[-1]*0.995:
trend = 'down'
else:
trend = None
df['trend'].iloc[-1]= trend
return df
Operaciones
La función
operate()
determina si se debe ejecutar una orden de compra o de venta en función del cruce de las EMAs y de la tendencia actual. Comprueba si hay órdenes abiertas para evitar órdenes duplicadas. Si se detecta una señal de operación, calcula la cantidad, comprueba el saldo disponible y coloca la orden limit adecuada. Además, establece órdenes take profit y stop loss para una mejor gestión del riesgo.
def operate(df):
operation = None
orders = client.get_open_orders(symbol=symbol)
if len(orders) == 0:
price = df['close'].iloc[-1]
if ut.crossover(df.ema_f.values[-2:], df.ema_s.values[-2:]) and df['trend'].iloc[-1] == 'up':
operation = 'BUY'
quantity = ut.get_quantity(price, amount, min_qty, max_qty, max_float_qty)
balances = client.get_account()['balances']
balance = ut.get_balance(balances, ref)
if not quantity:
print('No Quantity available \n')
elif balance <= amount:
print(f'No {ref} to buy {crypto}')
else:
order = client.order_limit_buy(
symbol=symbol,
quantity=quantity,
price=price)
status='NEW'
while status != 'FILLED':
time.sleep(5)
order_id = order.get('orderId')
order = client.get_order(
symbol=symbol,
orderId= order_id)
status=order.get('status')
sell_price = ((price * 1.02) // tick_size) * tick_size
stop_price = ((price*0.991) // tick_size) * tick_size
stop_limit_price = ((price*0.99) // tick_size) * tick_size
order = client.order_oco_sell(
symbol=symbol,
quantity=quantity,
price=sell_price,
stopPrice=stop_price,
stopLimitPrice=stop_limit_price,
stopLimitTimeInForce='GTC')
elif ut.crossover(df.ema_s.values[-2:], df.ema_f.values[-2:])and df['trend'].iloc[-1] == 'down':
operation = 'SELL'
quantity = ut.get_quantity(price, amount, min_qty, max_qty, max_float_qty)
balances = client.get_account()['balances']
balance = ut.get_balance(balances, crypto)
if not quantity:
print('No Quantity available \n')
elif balance < quantity:
print(f'No {crypto} to sell for {ref}')
else:
order = client.order_limit_sell(
symbol=symbol,
quantity=quantity,
price=price)
status='NEW'
while status != 'FILLED':
time.sleep(3)
order_id = order.get('orderId')
order = client.get_order(
symbol=symbol,
orderId= order_id)
status=order.get('status')
buy_price = ((price*0.98) // tick_size) * tick_size
stop_price = ((price*1.009) // tick_size) * tick_size
stop_limit_price = ((price*1.01) // tick_size) * tick_size
order = client.order_oco_buy(
symbol=symbol,
quantity=quantity,
price=buy_price,
stopPrice=stop_price,
stopLimitPrice=stop_limit_price,
stopLimitTimeInForce='GTC')
return operation
Ejecución del bot
El bucle principal while en el bloque
main
del código obtiene y actualiza continuamente los datos. Añade los nuevos datos al marco de datos, ejecuta la operación comercial y guarda el marco de datos en un archivo CSV. Si el marco de datos supera un determinado tamaño, lo recorta para mantener la eficiencia. Si se produce alguna excepción, se registra en un archivo de errores con fines de depuración.
if __name__ == "__main__":
pd.options.mode.chained_assignment = None
client = Client(API_KEY, API_SECRET)
info = client.get_symbol_info(symbol)
min_qty = float(info['filters'][1].get('minQty'))
step_size = float(info['filters'][1].get('stepSize'))
max_qty = float(info['filters'][1].get('maxQty'))
max_float_qty = ut.get_max_float_qty(step_size)
tick_size = float(info['filters'][0].get('tickSize'))
df = initialize_dataframe(1)
step = int(period[:2]) if len(period) > 2 else int(period[0])
if 'h' in period:
step = step * 60
synchronize()
while True:
temp = time.time()
try:
temp_df = parser()
i=1
while df['time'].iloc[-1] != temp_df['time'].iloc[-i] and i < sma:
i+=1
df = pd.concat([df, temp_df.tail(i-1)], ignore_index=True)
df['operation'].iloc[-1] = operate(df.tail(2))
if df.shape[0] > 10000:
df = df.tail(10000)
print(f"{df['time'].iloc[-1]} | Price:{df['close'].iloc[-1]} | Trend:{df['operation'].iloc[-1]} | Operation:{df['operation'].iloc[-1]}")
df.to_csv(f'./{symbol}_{period}.csv')
except Exception as e:
with open('./error.txt', 'a') as file:
file.write(f'Time = {dt.datetime.now(dt.timezone.utc)}\n')
file.write(f'Error = {e}\n')
file.write('----------------------\n')
delay = time.time() - temp
idle = 60 * step - delay
if idle > 0:
time.sleep(idle)
else:
synchronize()
Continuamos construyendo nuestro bot de trading utilizando la API de Binance. Implementamos funcionalidades para sincronizar datos, inicializar el marco de datos, analizar datos de velas y ejecutar operaciones de trading basadas en EMAs, SMAs y análisis de tendencias. También incorporamos la gestión de riesgos mediante órdenes de take profit / stop loss. La estructura modular del código permite un fácil mantenimiento y escalabilidad. En el próximo post, nos centraremos en las estrategias de backtesting y en la implementación de indicadores adicionales para mejorar el rendimiento del bot.
Recuerda manejar el trading automatizado con precaución y realizar pruebas exhaustivas antes de desplegar cualquier estrategia con fondos reales.
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 my previous post, we started building a cryptocurrency trading bot using the Binance API. We covered the initial setup, including API key configuration and importing necessary libraries. We implemented the logic for calculating moving averages and identifying crossover points. Now, let's continue with more code and explore additional functionalities.
Disclaimer
The information provided in this post is for educational purposes only and should not be construed as investment advice. Automated trading carries a high risk in which the invested capital can be lost. It is important to do your own research before making any investment decisions.
File Structure
To maintain a modular structure, we have divided the code into three files:
binance_utils.py
: This file contains utility functions used by the trading bot, including balance retrieval, quantity calculation, crossover detection, and more.config.py
: This file stores the API key and secret, which are required to access the Binance API.bot.py
: This file contains the main trading bot code. It handles data synchronization, initializes the dataframe, parses candlestick data, performs trading operations, and runs the bot loop.
import numpy as np
# Helper functions
def get_balance(balances, asset):
for bal in balances:
if bal.get('asset') == asset:
return float(bal['free'])
def get_max_float_qty(step_size):
max_float_qty = 0
a = 10
while step_size * a < 1:
a = a*10**max_float_qty
max_float_qty += 1
return max_float_qty
def get_quantity(price, amount, min_qty, max_qty, max_float_qty):
quantity = amount / price
if (quantity < min_qty or quantity > max_qty):
return False
quantity = np.round(quantity, max_float_qty)
return quantity
def crossover(ma_fast, ma_slow):
if (ma_fast[0] < ma_slow[0] and ma_fast[1] >= ma_slow[1]):
return True
return False
API_KEY = "YOUR API KEY"
API_SECRET = "YOUR API SECRET"
Importing Modules
We have to import the library
binance.enums
that will allow us to manipulate the OCO orders so that we can place take profit and stop loss orders.
import time
import datetime as dt
import pandas as pd
import numpy as np
import pytz
from binance.client import Client
from binance.enums import *
import binance_utils as ut
from config import *
Defining Variables and Parameters
This time we added a 200-period simple moving average to calculate the trend.
crypto = 'BTC'
ref = 'USDT'
symbol = crypto + ref
amount = 15
# Eligible periods for candlesticks 1m, 3m, 5m, 15m, 1h, etc.
period = '15m'
# Moving Average Periods
ema_f = 15
ema_s = 50
sma = 200
Synchronizing Data
To ensure accurate data analysis, we start by synchronizing the bot's time with the Binance server. The
synchronize()
function retrieves the latest candlestick data and calculates the start time for the bot's loop.
def synchronize():
candles = client.get_klines(symbol=symbol,interval=period,limit=1)
timer = pd.to_datetime(float(candles[0][0]) * 1000000)
start = timer + dt.timedelta(minutes=step)
print('Synchronizing .....')
while dt.datetime.now(dt.timezone.utc) < pytz.UTC.localize(start):
time.sleep(1)
time.sleep(2)
print('Bot synchronized')
Initializing the Dataframe
The
initialize_dataframe()
function retrieves historical candlestick data from Binance and creates a pandas DataFrame.
def initialize_dataframe(limit):
candles = client.get_klines(symbol=symbol,interval=period,limit=limit)
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
Parsing Candlestick Data
The
parser()
function initializes the dataframe and updates it with the latest candlestick data. It calculates the EMA, SMA, and identifies the current trend based on the price's relationship with the SMA.
def parser():
df = initialize_dataframe(sma+1)
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
# get trend
if df['close'].iloc[-1] > df['sma'].iloc[-1]*1.005:
trend = 'up'
elif df['close'].iloc[-1] < df['sma'].iloc[-1]*0.995:
trend = 'down'
else:
trend = None
df['trend'].iloc[-1]= trend
return df
Trading Operations
The
operate()
function determines whether to execute a buy or sell order based on the crossover of the EMAs and the current trend. It checks for open orders to avoid duplicate orders. If a trade signal is detected, it calculates the quantity, checks the available balance, and places the appropriate limit order. Additionally, it sets up take profit and stop loss order for risk management.
def operate(df):
operation = None
orders = client.get_open_orders(symbol=symbol)
if len(orders) == 0:
price = df['close'].iloc[-1]
if ut.crossover(df.ema_f.values[-2:], df.ema_s.values[-2:]) and df['trend'].iloc[-1] == 'up':
operation = 'BUY'
quantity = ut.get_quantity(price, amount, min_qty, max_qty, max_float_qty)
balances = client.get_account()['balances']
balance = ut.get_balance(balances, ref)
if not quantity:
print('No Quantity available \n')
elif balance <= amount:
print(f'No {ref} to buy {crypto}')
else:
order = client.order_limit_buy(
symbol=symbol,
quantity=quantity,
price=price)
status='NEW'
while status != 'FILLED':
time.sleep(5)
order_id = order.get('orderId')
order = client.get_order(
symbol=symbol,
orderId= order_id)
status=order.get('status')
sell_price = ((price * 1.02) // tick_size) * tick_size
stop_price = ((price*0.991) // tick_size) * tick_size
stop_limit_price = ((price*0.99) // tick_size) * tick_size
order = client.order_oco_sell(
symbol=symbol,
quantity=quantity,
price=sell_price,
stopPrice=stop_price,
stopLimitPrice=stop_limit_price,
stopLimitTimeInForce='GTC')
elif ut.crossover(df.ema_s.values[-2:], df.ema_f.values[-2:])and df['trend'].iloc[-1] == 'down':
operation = 'SELL'
quantity = ut.get_quantity(price, amount, min_qty, max_qty, max_float_qty)
balances = client.get_account()['balances']
balance = ut.get_balance(balances, crypto)
if not quantity:
print('No Quantity available \n')
elif balance < quantity:
print(f'No {crypto} to sell for {ref}')
else:
order = client.order_limit_sell(
symbol=symbol,
quantity=quantity,
price=price)
status='NEW'
while status != 'FILLED':
time.sleep(3)
order_id = order.get('orderId')
order = client.get_order(
symbol=symbol,
orderId= order_id)
status=order.get('status')
buy_price = ((price*0.98) // tick_size) * tick_size
stop_price = ((price*1.009) // tick_size) * tick_size
stop_limit_price = ((price*1.01) // tick_size) * tick_size
order = client.order_oco_buy(
symbol=symbol,
quantity=quantity,
price=buy_price,
stopPrice=stop_price,
stopLimitPrice=stop_limit_price,
stopLimitTimeInForce='GTC')
return operation
Bot Execution
The main while loop in the
main
block of the code continuously fetches and updates the data. It appends the new data to the dataframe, executes the trading operation, and saves the dataframe to a CSV file. If the dataframe exceeds a certain size, it trims it to maintain efficiency. If any exceptions occur, they are logged in an error file for debugging purposes.
if __name__ == "__main__":
pd.options.mode.chained_assignment = None
client = Client(API_KEY, API_SECRET)
info = client.get_symbol_info(symbol)
min_qty = float(info['filters'][1].get('minQty'))
step_size = float(info['filters'][1].get('stepSize'))
max_qty = float(info['filters'][1].get('maxQty'))
max_float_qty = ut.get_max_float_qty(step_size)
tick_size = float(info['filters'][0].get('tickSize'))
df = initialize_dataframe(1)
step = int(period[:2]) if len(period) > 2 else int(period[0])
if 'h' in period:
step = step * 60
synchronize()
while True:
temp = time.time()
try:
temp_df = parser()
i=1
while df['time'].iloc[-1] != temp_df['time'].iloc[-i] and i < sma:
i+=1
df = pd.concat([df, temp_df.tail(i-1)], ignore_index=True)
df['operation'].iloc[-1] = operate(df.tail(2))
if df.shape[0] > 10000:
df = df.tail(10000)
print(f"{df['time'].iloc[-1]} | Price:{df['close'].iloc[-1]} | Trend:{df['operation'].iloc[-1]} | Operation:{df['operation'].iloc[-1]}")
df.to_csv(f'./{symbol}_{period}.csv')
except Exception as e:
with open('./error.txt', 'a') as file:
file.write(f'Time = {dt.datetime.now(dt.timezone.utc)}\n')
file.write(f'Error = {e}\n')
file.write('----------------------\n')
delay = time.time() - temp
idle = 60 * step - delay
if idle > 0:
time.sleep(idle)
else:
synchronize()
We continued building our cryptocurrency trading bot using the Binance API. We implemented functionalities to synchronize data, initialize the dataframe, parse candlestick data, and execute trading operations based on EMAs, SMAs, and trend analysis. We also incorporated risk management through stop loss orders. The modular structure of the code allows for easy maintenance and scalability. In the next post, we will focus on backtesting strategies and implementing additional indicators to enhance the bot's performance.
Remember to handle automated trading with caution and perform thorough testing before deploying any strategies with real funds.
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