def __init_client(self): """ initialization of client """ if self.client is not None: return api_key = conf['binance_keys'][self.account]['API_KEY'] api_secret = conf['binance_keys'][self.account]['SECRET_KEY'] self.client = Client(api_key=api_key, api_secret=api_secret)
def __keep_alive_user_datastream(self, listenKey): """ keep alive user data stream, needs to ping every 60m """ api_key = conf['binance_keys'][self.account]['API_KEY'] api_secret = conf['binance_keys'][self.account]['SECRET_KEY'] client = Client(api_key, api_secret) def loop_function(): while self.is_running: client.stream_keepalive() time.sleep(3480) timer = threading.Timer(10, loop_function) timer.daemon = True if listenKey is not None: timer.start() else: self.__get_auth_user_data_streams() timer.start()
class BinanceFutures: # Account account = '' # Pair pair = 'BTCUSDT' # wallet wallet = None # Price market_price = 0 # Order update order_update = None # Position position = None # Position size position_size = None # Entry price entry_price = None # Margin margin = None # Account information account_information = None # Time Frame bin_size = '1h' # Binance futures client client = None # Is bot running is_running = True # Bar crawler crawler = None # Strategy strategy = None # Enable log output enable_trade_log = True # OHLCV length ohlcv_len = 100 # OHLCV data data = None # Profit target long and short for a simple limit exit strategy sltp_values = { 'profit_long': 0, 'profit_short': 0, 'stop_long': 0, 'stop_short': 0, 'eval_tp_next_candle': False } # Round decimals round_decimals = 2 # Profit, Loss and Trail Offset exit_order = {'profit': 0, 'loss': 0, 'trail_offset': 0} # Trailing Stop trail_price = 0 # Last strategy execution time last_action_time = None # best bid price best_bid_price = None # best ask price best_ask_price = None def __init__(self, account, pair, demo=False, threading=True): """ constructor :account: :pair: :param demo: :param run: """ self.account = account self.pair = pair self.demo = demo self.is_running = threading def __init_client(self): """ initialization of client """ if self.client is not None: return api_key = conf['binance_keys'][self.account]['API_KEY'] api_secret = conf['binance_keys'][self.account]['SECRET_KEY'] self.client = Client(api_key=api_key, api_secret=api_secret) def now_time(self): """ current time """ return datetime.now().astimezone(UTC) def get_retain_rate(self): """ maintenance margin :return: """ return 0.8 def lot_leverage(self): """ get leverage for lot calculation :return: """ return 20 def get_lot(self, round_decimals=3): """ lot calculation :param round_decimals: round decimals :return: """ account_information = self.get_account_information() return round( float(account_information['totalMarginBalance']) / self.get_market_price() * self.lot_leverage(), round_decimals) def get_balance(self): """ get balance :return: """ self.__init_client() return float(self.get_margin()[0]["balance"]) def get_margin(self): """ get margin :return: """ self.__init_client() if self.margin is not None: return self.margin else: # when the WebSocket cant get it self.margin = retry( lambda: self.client.futures_account_balance_v2()) return self.margin def get_leverage(self): """ get leverage :return: """ self.__init_client() return float(self.get_position()["leverage"]) def get_account_information(self): """ get account information about all types of margin balances, assets and positions https://binance-docs.github.io/apidocs/futures/en/#account-information-v2-user_data """ self.account_information = retry( lambda: self.client.futures_account_v2()) return self.account_information def get_position(self): """ get current position :return: """ self.__init_client() #Unfortunately we cannot rely just on the WebSocket updates (for instance PnL) since binance is not pushing updates for the ACCOUNT_UPDATE stream often enough #read more here https://binance-docs.github.io/apidocs/futures/en/#event-balance-and-position-update # if self.position is not None: # return self.position[0] # else: # when the WebSocket cant get it ret = retry(lambda: self.client.futures_position_information()) if len(ret) > 0: self.position = [ p for p in ret if p["symbol"].startswith(self.pair) ] return self.position[0] else: return None def get_position_size(self): """ get current position size。 :return: """ self.__init_client() if self.position_size is not None: #and self.position_size == 0: return self.position_size position = self.get_position() if position['symbol'] == self.pair: return float(position['positionAmt']) else: return 0 def get_position_avg_price(self): """ get average price of the current position :return: """ self.__init_client() return float(self.get_position()['entryPrice']) def get_market_price(self): """ get current price :return: """ self.__init_client() if self.market_price != 0: return self.market_price else: # when the WebSocket cant get it self.market_price = retry( lambda: self.client.futures_symbol_ticker(symbol=self.pair)) return float(self.market_price['price']) def get_pnl(self): """ get profit and loss calculation in % :return: """ # PnL calculation in % pnl = (self.market_price - self.entry_price) * 100 / self.entry_price return pnl def get_trail_price(self): """ Trail Price :return: """ return self.trail_price def set_trail_price(self, value): """ set Trail Price :return: """ self.trail_price = value def get_commission(self): """ get commission :return: """ return 0.04 / 100 def cancel_all(self): """ cancel all orders """ self.__init_client() res = retry(lambda: self.client.futures_cancel_all_open_orders( symbol=self.pair)) #for order in orders: logger.info(f"Cancel all open orders: {res}") def close_all(self): """ market close opened position for this pair """ self.__init_client() position_size = self.get_position_size() if position_size == 0: return side = False if position_size > 0 else True self.order("Close", side, abs(position_size)) position_size = self.get_position_size() if position_size == 0: logger.info(f"Closed {self.pair} position") else: logger.info( f"Failed to close all {self.pair} position, still {position_size} amount remaining" ) def cancel(self, id): """ cancel a specific order by id :param id: id of the order :return: result """ self.__init_client() order = self.get_open_order(id) if order is None: return False try: retry(lambda: self.client.futures_cancel_order( symbol=self.pair, origClientOrderId=order['clientOrderId'])) except HTTPNotFound: return False logger.info( f"Cancel Order : (clientOrderId, type, side, quantity, price, stop) = " f"({order['clientOrderId']}, {order['type']}, {order['side']}, {order['origQty']}, " f"{order['price']}, {order['stopPrice']})") return True def __new_order(self, ord_id, side, ord_qty, limit=0, stop=0, post_only=False, reduce_only=False, trailing_stop=0, activationPrice=0): """ create an order """ #removes "+" from order suffix, because of the new regular expression rule for newClientOrderId updated as ^[\.A-Z\:/a-z0-9_-]{1,36}$ (2021-01-26) ord_id = ord_id.replace("+", "k") if trailing_stop > 0 and activationPrice > 0: ord_type = "TRAILING_STOP_MARKET" retry(lambda: self.client.futures_create_order( symbol=self.pair, type=ord_type, newClientOrderId=ord_id, side=side, quantity=ord_qty, activationPrice=activationPrice, callbackRate=trailing_stop)) elif trailing_stop > 0: ord_type = "TRAILING_STOP_MARKET" retry(lambda: self.client.futures_create_order( symbol=self.pair, type=ord_type, newClientOrderId=ord_id, side=side, quantity=ord_qty, callbackRate=trailing_stop)) elif limit > 0 and post_only: ord_type = "LIMIT" retry(lambda: self.client.futures_create_order(symbol=self.pair, type=ord_type, newClientOrderId= ord_id, side=side, quantity=ord_qty, price=limit, timeInForce="GTX")) elif limit > 0 and stop > 0 and reduce_only: ord_type = "STOP" retry(lambda: self.client.futures_create_order(symbol=self.pair, type=ord_type, newClientOrderId= ord_id, side=side, quantity=ord_qty, price=limit, stopPrice=stop, reduceOnly="true")) elif limit > 0 and reduce_only: ord_type = "LIMIT" retry(lambda: self.client.futures_create_order(symbol=self.pair, type=ord_type, newClientOrderId= ord_id, side=side, quantity=ord_qty, price=limit, reduceOnly="true", timeInForce="GTC")) elif limit > 0 and stop > 0: ord_type = "STOP" retry(lambda: self.client.futures_create_order(symbol=self.pair, type=ord_type, newClientOrderId= ord_id, side=side, quantity=ord_qty, price=limit, stopPrice=stop)) elif limit > 0: ord_type = "LIMIT" retry(lambda: self.client.futures_create_order(symbol=self.pair, type=ord_type, newClientOrderId= ord_id, side=side, quantity=ord_qty, price=limit, timeInForce="GTC")) elif stop > 0 and reduce_only: ord_type = "STOP_MARKET" retry(lambda: self.client.futures_create_order(symbol=self.pair, type=ord_type, newClientOrderId= ord_id, side=side, quantity=ord_qty, stopPrice=stop, reduceOnly="true")) elif stop > 0: ord_type = "STOP" retry(lambda: self.client.futures_create_order(symbol=self.pair, type=ord_type, newClientOrderId= ord_id, side=side, quantity=ord_qty, stopPrice=stop)) elif post_only: # limit order with post only ord_type = "LIMIT" i = 0 while True: prices = self.get_orderbook_ticker() limit = float(prices['bidPrice']) if side == "Buy" else float( prices['askPrice']) retry(lambda: self.client.futures_create_order( symbol=self.pair, type=ord_type, newClientOrderId=ord_id, side=side, quantity=ord_qty, price=limit, timeInForce="GTX")) time.sleep(4) self.cancel(ord_id) if float(self.get_position()['positionAmt']) > 0: break i += 1 if i > 10: notify(f"Order retry count exceed") break self.cancel_all() else: ord_type = "MARKET" retry(lambda: self.client.futures_create_order(symbol=self.pair, type=ord_type, newClientOrderId= ord_id, side=side, quantity=ord_qty)) if self.enable_trade_log: logger.info(f"========= New Order ==============") logger.info(f"ID : {ord_id}") logger.info(f"Type : {ord_type}") logger.info(f"Side : {side}") logger.info(f"Qty : {ord_qty}") logger.info(f"Limit : {limit}") logger.info(f"Stop : {stop}") logger.info(f"======================================") notify( f"New Order\nType: {ord_type}\nSide: {side}\nQty: {ord_qty}\nLimit: {limit}\nStop: {stop}" ) # def __amend_order(self, ord_id, side, ord_qty, limit=0, stop=0, post_only=False): # """ # amend order # """ # todo, unfortunately binance ecosystem doesnt provide us with amend order functionality so we have to implement our own mechanism # if self.enable_trade_log: # logger.info(f"========= Amend Order ==============") # logger.info(f"ID : {ord_id}") # logger.info(f"Type : {ord_type}") # logger.info(f"Side : {side}") # logger.info(f"Qty : {ord_qty}") # logger.info(f"Limit : {limit}") # logger.info(f"Stop : {stop}") # logger.info(f"======================================") # notify(f"Amend Order\nType: {ord_type}\nSide: {side}\nQty: {ord_qty}\nLimit: {limit}\nStop: {stop}") def entry(self, id, long, qty, limit=0, stop=0, post_only=False, reduce_only=False, when=True): """ places an entry order, works as equivalent to tradingview pine script implementation https://tradingview.com/study-script-reference/#fun_strategy{dot}entry :param id: Order id :param long: Long or Short :param qty: Quantity :param limit: Limit price :param stop: Stop limit :param post_only: Post only :param reduce_only: Reduce Only means that your existing position cannot be increased only reduced by this order :param when: Do you want to execute the order or not - True for live trading :return: """ self.__init_client() # if self.get_margin()['excessMargin'] <= 0 or qty <= 0: # return if not when: return pos_size = self.get_position_size() logger.info(f"pos_size: {pos_size}") if long and pos_size > 0: return if not long and pos_size < 0: return ord_qty = qty + abs(pos_size) trailing_stop = 0 activationPrice = 0 self.order(id, long, ord_qty, limit, stop, post_only, reduce_only, trailing_stop, activationPrice, when) def order(self, id, long, qty, limit=0, stop=0, post_only=False, reduce_only=False, trailing_stop=0, activationPrice=0, when=True): """ places an order, works as equivalent to tradingview pine script implementation https://www.tradingview.com/pine-script-reference/#fun_strategy{dot}order :param id: Order id :param long: Long or Short :param qty: Quantity :param limit: Limit price :param stop: Stop limit :param post_only: Post only :param reduce_only: Reduce Only means that your existing position cannot be increased only reduced by this order :param trailing_stop: Binance futures built in implementation of trailing stop in % :param activationPrice: price that triggers Binance futures built in trailing stop :param when: Do you want to execute the order or not - True for live trading :return: """ self.__init_client() # if self.get_margin()['excessMargin'] <= 0 or qty <= 0: # return if not when: return side = "BUY" if long else "SELL" ord_qty = qty order = self.get_open_order(id) ord_id = id + ord_suffix( ) #if order is None else order["clientOrderId"] if order is None: self.__new_order(ord_id, side, ord_qty, limit, stop, post_only, reduce_only, trailing_stop, activationPrice) else: self.__new_order(ord_id, side, ord_qty, limit, stop, post_only, reduce_only, trailing_stop, activationPrice) #self.__amend_order(ord_id, side, ord_qty, limit, stop, post_only) return def entry_pyramiding(self, id, long, qty, limit=0, stop=0, trailValue=0, post_only=False, reduce_only=False, cancel_all=False, pyramiding=2, when=True): """ places an entry order, works as equivalent to tradingview pine script implementation with pyramiding https://tradingview.com/study-script-reference/#fun_strategy{dot}entry :param id: Order id :param long: Long or Short :param qty: Quantity :param limit: Limit price :param stop: Stop limit :param post_only: Post only :param reduce_only: Reduce Only means that your existing position cannot be increased only reduced by this order :param cancell_all: cancell all open order before sending the entry order? :param pyramiding: number of entries you want in pyramiding :param when: Do you want to execute the order or not - True for live trading :return: """ # if self.get_margin()['excessMargin'] <= 0 or qty <= 0: # return if qty <= 0: return if not when: return pos_size = self.get_position_size() if long and pos_size >= pyramiding * qty: return if not long and pos_size <= -(pyramiding * qty): return if cancel_all: self.cancel_all() if long and pos_size < 0: ord_qty = qty + abs(pos_size) elif not long and pos_size > 0: ord_qty = qty + abs(pos_size) else: ord_qty = qty if long and (pos_size + qty > pyramiding * qty): ord_qty = pyramiding * qty - abs(pos_size) if not long and (pos_size - qty < -(pyramiding * qty)): ord_qty = pyramiding * qty - abs(pos_size) # make sure it doesnt spam small entries, which in most cases would trigger risk management orders evaluation, you can make this less than 2% if needed if ord_qty < ((pyramiding * qty) / 100) * 2: return trailing_stop = 0 activationPrice = 0 self.order(id, long, ord_qty, limit, stop, post_only, reduce_only, trailing_stop, activationPrice, when) def get_open_order(self, id): """ Get open order by id :param id: Order id for this pair :return: """ self.__init_client() open_orders = retry( lambda: self.client.futures_get_open_orders(symbol=self.pair)) open_orders = [ o for o in open_orders if o["clientOrderId"].startswith(id) ] if len(open_orders) > 0: return open_orders[0] else: return None def get_open_orders(self, id): """ Get open orders for this pair by id :param id: Order id :return: """ self.__init_client() open_orders = retry( lambda: self.client.futures_get_open_orders(symbol=self.pair)) open_orders = [ o for o in open_orders if o["clientOrderId"].startswith(id) ] if len(open_orders) > 0: return open_orders else: return None def get_all_open_orders(self): """ Get all open orders for this pair :param id: Order id :return: """ self.__init_client() open_orders = retry( lambda: self.client.futures_get_open_orders(symbol=self.pair)) if len(open_orders) > 0: return open_orders else: return None def get_orderbook_ticker(self): orderbook_ticker = retry( lambda: self.client.futures_orderbook_ticker(symbol=self.pair)) return orderbook_ticker def exit(self, profit=0, loss=0, trail_offset=0): """ profit taking and stop loss and trailing, if both stop loss and trailing offset are set trailing_offset takes precedence :param profit: Profit (specified in ticks) :param loss: Stop loss (specified in ticks) :param trail_offset: Trailing stop price (specified in ticks) """ self.exit_order = { 'profit': profit, 'loss': loss, 'trail_offset': trail_offset } def sltp(self, profit_long=0, profit_short=0, stop_long=0, stop_short=0, eval_tp_next_candle=False, round_decimals=2): """ simple profit target triggered upon entering a position :param profit_long: profit target value in % for longs :param profit_short: profit target value in % for shorts :param stop_long: stop loss value for long position in % :param stop_short: stop loss value for short position in % :param round_decimals: round decimals """ self.sltp_values = { 'profit_long': profit_long / 100, 'profit_short': profit_short / 100, 'stop_long': stop_long / 100, 'stop_short': stop_short / 100, 'eval_tp_next_candle': eval_tp_next_candle } self.round_decimals = round_decimals def get_exit_order(self): """ get profit take and stop loss and trailing settings """ return self.exit_order def get_sltp_values(self): """ get values for the simple profit target/stop loss in % """ return self.sltp_values def eval_exit(self): """ evalution of profit target and stop loss and trailing """ if self.get_position_size() == 0: return unrealised_pnl = float(self.get_position()['unRealizedProfit']) # trail asset if self.get_exit_order()['trail_offset'] > 0 and self.get_trail_price( ) > 0: if self.get_position_size() > 0 and \ self.get_market_price() - self.get_exit_order()['trail_offset'] < self.get_trail_price(): logger.info( f"Loss cut by trailing stop: {self.get_exit_order()['trail_offset']}" ) self.close_all() elif self.get_position_size() < 0 and \ self.get_market_price() + self.get_exit_order()['trail_offset'] > self.get_trail_price(): logger.info( f"Loss cut by trailing stop: {self.get_exit_order()['trail_offset']}" ) self.close_all() #stop loss if unrealised_pnl < 0 and \ 0 < self.get_exit_order()['loss'] < abs(unrealised_pnl): logger.info( f"Loss cut by stop loss: {self.get_exit_order()['loss']}") self.close_all() # profit take if unrealised_pnl > 0 and \ 0 < self.get_exit_order()['profit'] < abs(unrealised_pnl): logger.info( f"Take profit by stop profit: {self.get_exit_order()['profit']}" ) self.close_all() # simple TP implementation def eval_sltp(self): """ evaluate simple profit target and stop loss """ pos_size = float(self.get_position()['positionAmt']) if pos_size == 0: return # tp tp_order = self.get_open_order('TP') is_tp_full_size = False is_sl_full_size = False if tp_order is not None: origQty = float(tp_order['origQty']) is_tp_full_size = origQty == abs(pos_size) if True else False #pos_size = pos_size - origQty tp_percent_long = self.get_sltp_values()['profit_long'] tp_percent_short = self.get_sltp_values()['profit_short'] avg_entry = self.get_position_avg_price() # tp execution logic if tp_percent_long > 0 and is_tp_full_size == False: if pos_size > 0: tp_price_long = round( avg_entry + (avg_entry * tp_percent_long), self.round_decimals) if tp_order is not None: time.sleep(2) self.cancel(id=tp_order['clientOrderId']) time.sleep(2) self.order("TP", False, abs(pos_size), limit=tp_price_long, reduce_only=True) else: self.order("TP", False, abs(pos_size), limit=tp_price_long, reduce_only=True) if tp_percent_short > 0 and is_tp_full_size == False: if pos_size < 0: tp_price_short = round( avg_entry - (avg_entry * tp_percent_short), self.round_decimals) if tp_order is not None: time.sleep(2) self.cancel(id=tp_order['clientOrderId']) time.sleep(2) self.order("TP", True, abs(pos_size), limit=tp_price_short, reduce_only=True) else: self.order("TP", True, abs(pos_size), limit=tp_price_short, reduce_only=True) #sl sl_order = self.get_open_order('SL') if sl_order is not None: origQty = float(sl_order['origQty']) orig_side = sl_order['side'] == "BUY" if True else False if orig_side == False: origQty = -origQty is_sl_full_size = origQty == -pos_size if True else False sl_percent_long = self.get_sltp_values()['stop_long'] sl_percent_short = self.get_sltp_values()['stop_short'] # sl execution logic if sl_percent_long > 0 and is_sl_full_size == False: if pos_size > 0: sl_price_long = round( avg_entry - (avg_entry * sl_percent_long), self.round_decimals) if sl_order is not None: time.sleep(2) self.cancel(id=sl_order['clientOrderId']) time.sleep(2) self.order("SL", False, abs(pos_size), stop=sl_price_long, reduce_only=True) else: self.order("SL", False, abs(pos_size), stop=sl_price_long, reduce_only=True) if sl_percent_short > 0 and is_sl_full_size == False: if pos_size < 0: sl_price_short = round( avg_entry + (avg_entry * sl_percent_short), self.round_decimals) if sl_order is not None: time.sleep(2) self.cancel(id=sl_order['clientOrderId']) time.sleep(2) self.order("SL", True, abs(pos_size), stop=sl_price_short, reduce_only=True) else: self.order("SL", True, abs(pos_size), stop=sl_price_short, reduce_only=True) def fetch_ohlcv(self, bin_size, start_time, end_time): """ fetch OHLCV data :param start_time: start time :param end_time: end time :return: """ self.__init_client() fetch_bin_size = allowed_range[bin_size][0] left_time = start_time right_time = end_time data = to_data_frame([]) while True: if left_time > right_time: break logger.info(f"fetching OHLCV data") left_time_to_timestamp = int(datetime.timestamp(left_time) * 1000) right_time_to_timestamp = int( datetime.timestamp(right_time) * 1000) source = retry(lambda: self.client.futures_klines( symbol=self.pair, interval=fetch_bin_size, startTime=left_time_to_timestamp, endTime=right_time_to_timestamp, limit=1500)) if len(source) == 0: break source_to_object_list = [] for s in source: timestamp_to_datetime = datetime.fromtimestamp( s[6] / 1000).astimezone(UTC) source_to_object_list.append({ "timestamp": timestamp_to_datetime, "high": float(s[2]), "low": float(s[3]), "open": float(s[1]), "close": float(s[4]), "volume": float(s[5]) }) source = to_data_frame(source_to_object_list) data = pd.concat([data, source]) if right_time > source.iloc[-1].name + delta(fetch_bin_size): left_time = source.iloc[-1].name + delta(fetch_bin_size) time.sleep(2) else: break return resample(data, bin_size) def security(self, bin_size): """ Recalculate and obtain different time frame data """ return resample(self.data, bin_size)[:-1] def __update_ohlcv(self, action, new_data): """ get OHLCV data and execute the strategy """ if self.data is None: end_time = datetime.now(timezone.utc) start_time = end_time - self.ohlcv_len * delta(self.bin_size) #logger.info(f"start time fetch ohlcv: {start_time}") #logger.info(f"end time fetch ohlcv: {end_time}") d1 = self.fetch_ohlcv(self.bin_size, start_time, end_time) if len(d1) > 0: d2 = self.fetch_ohlcv( allowed_range[self.bin_size][0], d1.iloc[-1].name + delta(allowed_range[self.bin_size][0]), end_time) self.data = pd.concat([d1, d2]) else: self.data = d1 else: self.data = pd.concat([self.data, new_data]) # exclude current candle data re_sample_data = resample(self.data, self.bin_size)[:-1] if self.data.iloc[-1].name == re_sample_data.iloc[-1].name: self.data = re_sample_data.iloc[-1 * self.ohlcv_len:, :] if self.last_action_time is not None and \ self.last_action_time == re_sample_data.iloc[-1].name: return open = re_sample_data['open'].values close = re_sample_data['close'].values high = re_sample_data['high'].values low = re_sample_data['low'].values volume = re_sample_data['volume'].values try: if self.strategy is not None: self.strategy(open, close, high, low, volume) self.last_action_time = re_sample_data.iloc[-1].name except FatalError as e: # Fatal error logger.error(f"Fatal error. {e}") logger.error(traceback.format_exc()) notify(f"Fatal error occurred. Stopping Bot. {e}") notify(traceback.format_exc()) self.stop() except Exception as e: logger.error(f"An error occurred. {e}") logger.error(traceback.format_exc()) notify(f"An error occurred. {e}") notify(traceback.format_exc()) def __on_update_instrument(self, action, instrument): """ Update instrument price """ if 'c' in instrument: self.market_price = float(instrument['c']) position_size = self.position_size if position_size == None: #position_size = self.get_position_size() return if position_size == 0: return # trail price update if self.position_size > 0 and \ self.market_price > self.get_trail_price(): self.set_trail_price(self.market_price) if self.position_size < 0 and \ self.market_price < self.get_trail_price(): self.set_trail_price(self.market_price) #Get PnL calculation in % self.pnl = self.get_pnl() def __on_update_wallet(self, action, wallet): """ update wallet """ self.wallet = wallet #{**self.wallet, **wallet} if self.wallet is not None else self.wallet def __on_update_order(self, action, order): """ Update order status https://binance-docs.github.io/apidocs/futures/en/#event-order-update """ self.order_update = order # Evaluation of profit and loss self.eval_exit() #self.eval_sltp() def __on_update_position(self, action, position): """ Update position """ if len(position) > 0: position = [p for p in position if p["s"].startswith(self.pair)] # Was the position size changed? is_update_pos_size = self.get_position_size != float(position[0]['pa']) # Reset trail to current price if position size changes if is_update_pos_size and float(position[0]['pa']) != 0: self.set_trail_price(self.market_price) if is_update_pos_size: logger.info( f"Updated Position\n" f"Price: {self.position[0]['entryPrice']} => {position[0]['ep']}\n" f"Qty: {self.position[0]['positionAmt']} => {position[0]['pa']}\n" f"Balance: {self.get_balance()} USDT") notify( f"Updated Position\n" f"Price: {self.position[0]['entryPrice']} => {position[0]['ep']}\n" f"Qty: {self.position[0]['positionAmt']} => {position[0]['pa']}\n" f"Balance: {self.get_balance()} USDT") self.position[0] = { "entryPrice": position[0]['ep'], "marginType": position[0]['mt'], "positionAmt": position[0]['pa'], "symbol": position[0]['s'], "unRealizedProfit": position[0]['up'], "positionSide": position[0]['ps'], } if self.position is not None else self.position self.position_size = float(self.position[0]['positionAmt']) self.entry_price = float(self.position[0]['entryPrice']) # Evaluation of profit and loss self.eval_exit() self.eval_sltp() def __on_update_margin(self, action, margin): """ Update margin """ if self.margin is not None: self.margin[0] = { "balance": float(margin['wb']), "crossWalletBalance": float(margin['cw']) } else: self.get_margin() def __on_update_bookticker(self, action, bookticker): """ best bid and best ask price """ self.best_bid_price = float(bookticker['b']) self.best_ask_price = float(bookticker['a']) def on_update(self, bin_size, strategy): """ Register the strategy function bind functions with webosocket data streams :param strategy: """ logger.info(f"pair: {self.pair}") self.bin_size = bin_size self.strategy = strategy if self.is_running: self.ws = BinanceFuturesWs(account=self.account, pair=self.pair, test=self.demo) self.ws.bind(allowed_range[bin_size][0], self.__update_ohlcv) self.ws.bind('instrument', self.__on_update_instrument) self.ws.bind('wallet', self.__on_update_wallet) self.ws.bind('position', self.__on_update_position) self.ws.bind('order', self.__on_update_order) self.ws.bind('margin', self.__on_update_margin) self.ws.bind('IndividualSymbolBookTickerStreams', self.__on_update_bookticker) #todo orderbook #self.ob = OrderBook(self.ws) logger.info(f" on_update(self, bin_size, strategy)") def stop(self): """ Stop the crawler """ self.is_running = False self.ws.close() def show_result(self): """ Show results """ pass def plot(self, name, value, color, overlay=True): """ Draw the graph """ pass
def get_listenkey(api_key, api_secret): client = Client(api_key=api_key, api_secret=api_secret) listenKey = client.stream_get_listen_key() return listenKey