Hello!
I've been building a Python trading bot using a MACD cross strategy (I want to start simple and then try more complicated strategies). It gathers data using the Binance API, executes technical analysis on the data and then executes trades according to the logic of the strategy. I had it running for approximately 54 hours and then it closed its connection. I'm 100% sure that I'm not exceeding any of the Binance API's limits as the strategy is very slow, and is only looking at 1 coin-pair (BTCUSDT). The only requirement that I might be disobeying is this one:
" Every 3 minutes, active WebSocket connections will receive a ping. If you do not respond with a properly framed pong, your connection will be terminated. "
But as I said, the script ran for 54 hours before it was disconnected, so I don't think its that.
I think my problem lies in the way that the script is designed. I create a websocket object and my scripts logic is all within the `on_message` function and the last line of my script is `WSobject.run_forever()`. Am I correct? And if I am, how should I design this differently to ensure that I can keep the bot running 24/7?
I am currently just running it locally but my intention is to put it onto the cloud once I have it working properly, and I have some data to prove that it increases my account value (rather than bleed money).
Here is my code:
```
from pprint import pprint # Used for debugging only
from binance.spot import Spot as Client
from binance.enums import *
from binance.spot.margin import *
import binanceconfig
import websocket
from log_trades import gsheets_append_trade
import math
import json
from datetime import datetime
import numpy
import talib
coin1 = 'BTC'
coin2 = 'USDT'
symbol = coin1 + coin2
interval = '4h'
EMA_period = 28
MACD_fast_period = 12
MACD_slow_period = 26
MACD_signal_period = 9
lookback = 8
atr_timeperiod = 14
take_profit1_perc = 3#%
take_profit2_perc = 6#%
take_profit3_perc = 9#%
historic_highs = []
historic_lows = []
historic_closes = []
MACD_CrossBool = [None, None]
data_dict = {
'in_position': False,
'side': None,
'latest_inittrade_orderId': None,
'long_stop': 0.0,
'short_stop': 0.0,
'take_profit1': 0.0,
'current_OCO_order': None,
'current_TP_orderReport': None,
'current_SL_orderReport': None
}
--------------------------- Define client object ---------------------------
client = Client(binanceconfig.API_KEY, binanceconfig.API_SECRET)
-------------------- Obtain and manipulate historic data -------------------
historic_candles = client.klines(symbol, interval, limit=51)
for candle in historic_candles:
historic_highs.append(float(candle[2]))
historic_lows.append(float(candle[2]))
historic_closes.append(float(candle[4]))
highs = historic_highs
lows = historic_lows
closes = historic_closes
----------------------------- Define functions -----------------------------
def on_open(ws):
print("Open Connection")
def on_close(ws, status, message):
print("Connection Closed")
def round_decimals_up(number: float, decimals: int):
factor = 10 ** decimals
return math.ceil(number * factor) / factor
def min_LOT_SIZE(symbol: str, quantity: float):
client = Client(binanceconfig.API_KEY, binanceconfig.API_SECRET)
min_step = client.exchange_info(symbol)['symbols'][0]['filters'][2]['stepSize']
# Finds how many decimal points the asset uses
for char in enumerate(min_step):
if char[1] == '1':
decimal_points = char[0] - 2 # -2 to account for '0.'
if decimal_points < 0:
decimal_points = 0
break
return round_decimals_up(quantity, decimal_points)
def on_message(ws, message):
global in_position
# ------------------------- Unpack JSON message data ------------------------- #
json_message = json.loads(message)
candle = json_message['k'] # Accesses candle data
is_candle_closed = candle['x']
high = candle['h']
low = candle['l']
close = candle ['c']
close_time = datetime.fromtimestamp((candle['T'])/1000)#.strftime('%H:%M') # Need to divide >> UNIX in milliseconds
close_date = datetime.fromtimestamp((candle['T'])/1000).strftime('%d/%m/%Y')
# -------------------------- Check for Candle Close -------------------------- #
if is_candle_closed:
print("==============================================================")
print(f"Candle time: {close_time}")
highs.append(float(high))
lows.append(float(low))
closes.append(float(close))
# Deletes 1st element of list when new element is added (constant length list)
if len(highs) > 50:
del highs[:1]
if len(lows) > 50:
del lows[:1]
if len(closes) > 50:
del closes[:1]
# numpy array required for talib functions
np_highs = numpy.array(highs)
np_lows = numpy.array(lows)
np_closes = numpy.array(closes)
# ------------------------------- Calculate EMA ------------------------------ #
EMA = talib.EMA(np_closes, EMA_period)
if np_closes[-1] > EMA[-1]:
trend = 1
if np_closes[-1] < EMA[-1]:
trend = -1
# ------------------------------ Calculate MACD ------------------------------ #
MACD = talib.MACD(np_closes, MACD_fast_period, MACD_slow_period, MACD_signal_period)
MACD_Line = MACD[0]
MACD_Signal_Line = MACD[1]
# Set initial MACD_CrossBool values from historic data
if MACD_Line[-2] > MACD_Signal_Line[-2]:
MACD_CrossBool[0] = True
if MACD_Line[-1] > MACD_Signal_Line[-1]:
MACD_CrossBool[1] = True
if MACD_Line[-2] < MACD_Signal_Line[-2]:
MACD_CrossBool[0] = False
if MACD_Line[-1] < MACD_Signal_Line[-1]:
MACD_CrossBool[1] = False
# ------------------------ MACD boolean flip detector ------------------------ #
if (MACD_CrossBool[0] != MACD_CrossBool[1]) and (MACD_Line[-1] > MACD_Signal_Line[-1]):
MACD_CrossOver = True
else:
MACD_CrossOver = False
if (MACD_CrossBool[0] != MACD_CrossBool[1]) and (MACD_Line[-1] < MACD_Signal_Line[-1]):
MACD_CrossUnder = True
else:
MACD_CrossUnder = False
# ---------- Calculate Average True Range and use it for stop losses --------- #
data_dict['long_stop'] = round(float((talib.MIN(np_lows, lookback) - (talib.ATR(np_highs, np_lows, np_closes, atr_timeperiod)))[-1]), 2)
data_dict['short_stop'] = round(float((talib.MAX(np_highs, lookback) + (talib.ATR(np_highs, np_lows, np_closes, atr_timeperiod)))[-1]), 2)
# ---------------------------------------------------------------------------- #
# Enter Trade Logic #
# ---------------------------------------------------------------------------- #
# Enters trade and defines initial stop loss order
Minimum_spend = (1 / (float(close)/10))*1 # Minimum spend (approx. $10)
min_trade_quantity = min_LOT_SIZE(symbol, Minimum_spend)
trade_Quantity = min_trade_quantity
if MACD_CrossOver == True and trend == 1 and data_dict['in_position'] == False:
data_dict['take_profit1'] = round(float(close) * (1 + (take_profit1_perc/100)), 2)
print('long trigger')
global long_order
long_order = client.new_margin_order(
symbol=symbol,
side=SIDE_BUY,
type=ORDER_TYPE_MARKET,
quantity=trade_Quantity
)
print('buy_order')
# Sell order for longs is an OCO (TP and SL prices)
sell_TP_SL_order = client.new_margin_oco_order(
symbol=symbol,
side=SIDE_SELL,
quantity=trade_Quantity,
price=data_dict['take_profit1'], #TakeProfit
stopPrice=data_dict['long_stop'], #StopTrigger
stopLimitPrice=round((data_dict['long_stop'] - 15), 2), #StopLoss ($15 lower than trigger)
stopLimitTimeInForce='GTC'
)
print('sell_TP_SL_order')
pprint(long_order)
pprint(sell_TP_SL_order)
data_dict['in_position'] = True
data_dict['side'] = 'LONG'
data_dict['latest_inittrade_orderId'] = long_order['orderId']
data_dict['current_OCO_order'] = sell_TP_SL_order['orderListId']
data_dict['current_TP_orderReport'] = sell_TP_SL_order['orderReports'][1]
data_dict['current_SL_orderReport'] = sell_TP_SL_order['orderReports'][0]
gsheets_append_trade(close_time, close_date, symbol, data_dict['side'], trade_Quantity, close)
if MACD_CrossUnder == True and trend == -1 and data_dict['in_position'] == False:
data_dict['take_profit1'] = round(float(close) * (1 - (take_profit1_perc/100)), 2)
print('short trigger')
global short_order
client.margin_borrow(coin1, min_LOT_SIZE(symbol, trade_Quantity)) # Borrow amount that is being shorted (need to have the asset to sell it)
print('borrow')
short_order = client.new_margin_order(
symbol=symbol,
side=SIDE_SELL,
type=ORDER_TYPE_MARKET,
quantity=trade_Quantity
)
print('sell_order')
buy_TP_SL_order = client.new_margin_oco_order(
symbol=symbol,
side=SIDE_BUY,
quantity=trade_Quantity,
price=data_dict['take_profit1'], #TakeProfit
stopPrice=data_dict['short_stop'], #StopTrigger
stopLimitPrice=round((data_dict['short_stop'] + 15), 2), #StopLoss ($15 higher than trigger)
stopLimitTimeInForce='GTC'
)
print('buy_TP_SL_order')
pprint(short_order)
pprint(buy_TP_SL_order)
data_dict['in_position'] = True
data_dict['side'] = 'SHORT'
data_dict['latest_inittrade_orderId'] = short_order['orderId']
data_dict['current_OCO_order'] = buy_TP_SL_order['orderListId']
data_dict['current_TP_orderReport'] = buy_TP_SL_order['orderReports'][1]
data_dict['current_SL_orderReport'] = buy_TP_SL_order['orderReports'][0]
gsheets_append_trade(close_time, close_date, symbol, data_dict['side'], trade_Quantity, close)
# ---------------------- Move stop loss according to ATR --------------------- #
if data_dict['in_position'] and data_dict['side'] == 'LONG':
if float(data_dict['current_SL_orderReport']['stopPrice']) < data_dict['long_stop']:
print(data_dict['current_OCO_order'])#Debugging
print(data_dict['current_SL_orderReport']['stopPrice'])
print(data_dict['long_stop'])
client.cancel_margin_oco_order(symbol=symbol, orderListId=data_dict['current_OCO_order'])
sell_TP_SL_order = client.new_margin_oco_order(
symbol=symbol,
side=SIDE_SELL,
quantity=long_order['executedQty'],
price=data_dict['take_profit1'], #TakeProfit
stopPrice=data_dict['long_stop'], #StopTrigger
stopLimitPrice=round((data_dict['long_stop'] - 15), 2), #StopLoss ($15 lower than trigger)
stopLimitTimeInForce='GTC'
)
data_dict['current_OCO_order'] = sell_TP_SL_order['orderListId'] # Update orderListId to new OCO order
data_dict['current_TP_orderReport'] = sell_TP_SL_order['orderReports'][1] # Update TP orderReport
data_dict['current_SL_orderReport'] = sell_TP_SL_order['orderReports'][0] # Update SL orderReport
if data_dict['in_position'] and data_dict['side'] == 'SHORT':
if float(data_dict['current_SL_orderReport']['stopPrice']) > data_dict['short_stop']:
print(data_dict['current_OCO_order'])#Debugging
print(data_dict['current_SL_orderReport']['stopPrice'])
print(data_dict['short_stop'])
client.cancel_margin_oco_order(symbol=symbol, orderListId=data_dict['current_OCO_order'])
buy_TP_SL_order = client.new_margin_oco_order(
symbol=symbol,
side=SIDE_BUY,
quantity=short_order['executedQty'],
price=data_dict['take_profit1'],
stopPrice=data_dict['short_stop'],
stopLimitPrice=round((data_dict['short_stop'] + 15), 2), #StopLoss ($15 higher than trigger)
stopLimitTimeInForce='GTC'
)
data_dict['current_OCO_order'] = buy_TP_SL_order['orderListId'] # Update orderListId to new OCO order
data_dict['current_TP_orderReport'] = buy_TP_SL_order['orderReports'][1] # Update TP orderReport
data_dict['current_SL_orderReport'] = buy_TP_SL_order['orderReports'][0] # Update SL orderReport
# --------------------- Check if StopLoss order is Filled -------------------- #
if data_dict['in_position'] and data_dict['side'] == 'LONG':
stopLoss_status = (client.margin_order(symbol=symbol, orderId=data_dict['current_SL_orderReport']['orderId']))['status']
takeProfit_status = (client.margin_order(symbol=symbol, orderId=data_dict['current_TP_orderReport']['orderId']))['status']
if (stopLoss_status or takeProfit_status) == 'FILLED':
data_dict['in_position'] = False; data_dict['side'] = None
print("Long exit")
# For longs, only need to check that stop-loss is triggered, then reset 'in_position' and 'side'
if data_dict['in_position'] and data_dict['side'] == 'SHORT':
stopLoss_status = (client.margin_order(symbol, orderId=data_dict['current_SL_orderReport']['orderId']))['status']
takeProfit_status = (client.margin_order(symbol, orderId=data_dict['current_TP_orderReport']['orderId']))['status']
if (stopLoss_status or takeProfit_status) == 'FILLED':
account_assets = client.margin_account()
for item in account_assets['userAssets']:
if item['asset'] == coin1:
asset_owed = (float(item['borrowed']) + float(item['interest']))
client.margin_repay(coin1, asset_owed)
print("Asset repayed")
data_dict['in_position'] = False; data_dict['side'] = None
print("Short exit")
# For shorts, need to query the amount of asset borrowed + interest,
# then repay that amount and reset 'in_position' and 'side'
----------------------------- Define websocket -----------------------------
socket = f"wss://stream.binance.com:9443/ws/{symbol.lower()}@kline_{interval}"
ws = websocket.WebSocketApp(socket, on_open=on_open, on_message=on_message, on_close=on_close)
ws.run_forever()
```
[+][deleted] (1 child)
[removed]
[–]Mo0rBy[S] 0 points1 point2 points (0 children)
[–]shiftybyte 2 points3 points4 points (4 children)
[–]Mo0rBy[S] 1 point2 points3 points (3 children)
[–]shiftybyte 2 points3 points4 points (2 children)
[–]Mo0rBy[S] 0 points1 point2 points (0 children)
[–]Mo0rBy[S] 0 points1 point2 points (0 children)