def sell_gbp_balance(self): """ To sell GBP e.g.: - BNBGBP market buy BNB with GBP """ pair = self.active_bot["pair"] market = self.find_quoteAsset(pair) new_pair = f"{market}GBP" bo_size = self.active_bot["base_order_size"] book_order = Book_Order(new_pair) price = float(book_order.matching_engine(False, bo_size)) # Precision for balance conversion, not for the deal qty_precision = -( Decimal(str(self.lot_size_by_symbol(new_pair, "stepSize"))) .as_tuple() .exponent ) price_precision = -( Decimal(str(self.price_filter_by_symbol(new_pair, "tickSize"))) .as_tuple() .exponent ) qty = round_numbers( float(bo_size), qty_precision, ) if price: order = { "pair": new_pair, "qty": qty, "price": supress_notation(price, price_precision), } res = requests.post(url=self.bb_buy_order_url, json=order) else: # Matching engine failed - market order order = { "pair": new_pair, "qty": qty, } res = requests.post(url=self.bb_buy_market_order_url, json=order) if isinstance(handle_error(res), Response): resp = jsonResp( { "message": f"Failed to buy {pair} using GBP balance", "botId": str(self.active_bot["_id"]), }, 200, ) return resp return
def close_deals(self): """ Close all deals - Deals should be stored as an array of orderIds - Delete (cancel) endpoint, with symbold and orderId """ deals = self.active_bot["deals"] for d in deals: if "deal_type" in d and ( d["status"] == "NEW" or d["status"] == "PARTIALLY_FILLED" ): order_id = d["order_id"] res = requests.delete( url=f'{self.bb_close_order_url}/{self.active_bot["pair"]}/{order_id}' ) if isinstance(handle_error(res), Response): return handle_error(res) # Sell everything pair = self.active_bot["pair"] base_asset = self.find_baseAsset(pair) balance = self.get_balances().json qty = round_numbers( float(next((s for s in symbols if s["symbol"] == symbol), None)["free"]), self.qty_precision, ) book_order = Book_Order(pair) price = float(book_order.matching_engine(True, qty)) if price: order = { "pair": pair, "qty": qty, "price": supress_notation(price, self.price_precision), } res = requests.post(url=self.bb_sell_order_url, json=order) else: order = { "pair": pair, "qty": qty, } res = requests.post(url=self.bb_sell_market_order_url, json=order) if isinstance(handle_error(res), Response): return handle_error(res) response = jsonResp_message("Deals closed successfully", 200) return response
def short_stop_limit_order(self): """ Part I of Short bot order: Stop loss (sell all) After safety orders are executed, if price keeps going down, execute Stop Loss order """ pair = self.active_bot["pair"] base_asset = self.find_baseAsset(pair) base_order_deal = next( ( bo_deal for bo_deal in self.active_bot["deals"] if bo_deal["deal_type"] == "base_order" ), None, ) price = float(base_order_deal["price"]) stop_loss = price * int(self.active_bot["stop_loss"]) / 100 stop_loss_price = price - stop_loss self.asset_qty = next( (b["free"] for b in self.get_balances().json if b["asset"] == base_asset), None, ) # Validations if price: if price <= float(self.MIN_PRICE): return jsonResp_message("[Short stop loss order] Price too low", 200) # Avoid common rate limits if float(self.asset_qty) <= float(self.MIN_QTY): return jsonResp_message("[Short stop loss order] Quantity too low", 200) if price * float(self.asset_qty) <= float(self.MIN_NOTIONAL): return jsonResp_message( "[Short stop loss order] Price x Quantity too low", 200 ) order = { "pair": pair, "qty": self.asset_qty, "price": supress_notation( stop_loss_price, self.price_precision ), # Theoretically stop_price, as we don't have book orders "stop_price": supress_notation(stop_loss_price, self.price_precision), } res = requests.post(url=self.bb_stop_sell_order_url, json=order) if isinstance(handle_error(res), Response): return handle_error(res) stop_limit_order = res.json() stop_limit_order = { "deal_type": "stop_limit", "order_id": stop_limit_order["orderId"], "strategy": "long", # change accordingly "pair": stop_limit_order["symbol"], "order_side": stop_limit_order["side"], "order_type": stop_limit_order["type"], "price": stop_limit_order["price"], "qty": stop_limit_order["origQty"], "fills": stop_limit_order["fills"], "time_in_force": stop_limit_order["timeInForce"], "status": stop_limit_order["status"], } self.active_bot["deals"].append(stop_limit_order) botId = app.db.bots.update_one( {"_id": self.active_bot["_id"]}, {"$push": {"deals": stop_limit_order}} ) if not botId: resp = jsonResp( { "message": "Failed to save short order stop_limit deal in the bot", "botId": str(self.active_bot["_id"]), }, 200, ) return resp
def long_take_profit_order(self): """ Execute long strategy (buy and sell higher) take profit order (Binance take_profit) - We only have stop_price, because there are no book bids/asks in t0 - Perform validations so we can avoid hitting endpoint errors - take_profit order can ONLY be executed once base order is filled (on Binance) """ pair = self.active_bot["pair"] updated_bot = self.app.db.bots.find_one({"_id": self.active_bot["_id"]}) deal_buy_price = updated_bot["deal"]["buy_price"] buy_total_qty = updated_bot["deal"]["buy_total_qty"] price = (1 + (float(self.active_bot["take_profit"]) / 100)) * float( deal_buy_price ) qty = round_numbers( buy_total_qty, self.qty_precision ) price = round_numbers(price, self.price_precision) order = { "pair": pair, "qty": qty, "price": supress_notation(price, self.price_precision), } res = requests.post(url=self.bb_sell_order_url, json=order) if isinstance(handle_error(res), Response): return handle_error(res) order = res.json() take_profit_order = { "deal_type": "take_profit", "order_id": order["orderId"], "strategy": "long", # change accordingly "pair": order["symbol"], "order_side": order["side"], "order_type": order["type"], "price": order["price"], "qty": order["origQty"], "fills": order["fills"], "time_in_force": order["timeInForce"], "status": order["status"], } self.active_bot["orders"].append(take_profit_order) botId = app.db.bots.update_one( {"_id": self.active_bot["_id"]}, { "$set": {"deal.take_profit_price": order["price"]}, "$push": {"orders": take_profit_order}, }, ) if not botId: resp = jsonResp( { "message": "Failed to save take_profit deal in the bot", "botId": str(self.active_bot["_id"]), }, 200, ) return resp return
def base_order(self): # Transform GBP balance to required market balance # e.g. BNBBTC - sell GBP and buy BTC transformed_balance = self.sell_gbp_balance() if isinstance(transformed_balance, Response): return transformed_balance pair = self.active_bot["pair"] # Long position does not need qty in take_profit # initial price with 1 qty should return first match book_order = Book_Order(pair) initial_price = float(book_order.matching_engine(False, 1)) qty = round_numbers( (float(self.active_bot["base_order_size"]) / float(initial_price)), self.qty_precision, ) price = float(book_order.matching_engine(False, qty)) self.price = price amount = float(qty) * float(price) self.total_amount = amount if price: # Cheaper commissions - limit order order = { "pair": pair, "qty": qty, "price": supress_notation(price, self.price_precision), } res = requests.post(url=self.bb_buy_order_url, json=order) else: # Matching engine failed - market order order = { "pair": pair, "qty": qty, } res = requests.post(url=self.bb_buy_market_order_url, json=order) if isinstance(handle_error(res), Response): return handle_error(res) order = res.json() base_deal = { "order_id": order["orderId"], "deal_type": "base_order", "strategy": "long", # change accordingly "pair": order["symbol"], "order_side": order["side"], "order_type": order["type"], "price": order["price"], "qty": order["origQty"], "fills": order["fills"], "time_in_force": order["timeInForce"], "status": order["status"], } tp_price = float(order["price"]) * 1 + ( float(self.active_bot["take_profit"]) / 100 ) so_prices = {} so_num = 1 for key, value in self.active_bot["safety_orders"].items(): price = float(order["price"]) - ( float(order["price"]) * (float(value["price_deviation_so"]) / 100) ) price = supress_notation(price, self.price_precision) so_prices[str(so_num)] = price so_num += 1 deal = { "last_order_id": order["orderId"], "buy_price": order["price"], "buy_total_qty": order["origQty"], "current_price": self.get_ticker_price(order["symbol"]), "take_profit_price": tp_price, "safety_order_prices": so_prices, "commission": 0, } for chunk in order["fills"]: deal["commission"] += float(chunk["commission"]) botId = app.db.bots.update_one( {"_id": self.active_bot["_id"]}, {"$set": {"deal": deal}, "$push": {"orders": base_deal}}, ) if not botId: resp = jsonResp( { "message": "Failed to save Base order", "botId": str(self.active_bot["_id"]), }, 200, ) return resp return base_deal
def update_take_profit(self, order_id): """ Update take profit after websocket order endpoint triggered - Close current opened take profit order - Create new take profit order - Update database by replacing old take profit deal with new take profit deal """ bot = self.active_bot for deal in bot["deals"]: if deal["order_id"] == order_id: so_deal_price = deal["price"] # Create new take profit order new_tp_price = float(so_deal_price) + ( float(so_deal_price) * float(bot["take_profit"]) / 100) asset = self.find_baseAsset(bot["pair"]) # First cancel old order to unlock balance close_order_params = { "symbol": bot["pair"], "orderId": order_id } cancel_response = requests.post(url=self.bb_close_order_url, params=close_order_params) if cancel_response.status_code != 200: print("Take profit order not found, no need to cancel") else: print("Old take profit order cancelled") qty = round_numbers(self.get_one_balance(asset), self.qty_precision) # Validations if new_tp_price: if new_tp_price <= float(self.MIN_PRICE): return jsonResp_message( "[Take profit order error] Price too low", 200) if qty <= float(self.MIN_QTY): return jsonResp_message( "[Take profit order error] Quantity too low", 200) if new_tp_price * qty <= float(self.MIN_NOTIONAL): return jsonResp_message( "[Take profit order error] Price x Quantity too low", 200) new_tp_order = { "pair": bot["pair"], "qty": qty, "price": supress_notation(new_tp_price, self.price_precision), } res = requests.post(url=self.bb_sell_order_url, json=new_tp_order) if isinstance(handle_error(res), Response): return handle_error(res) # New take profit order successfully created order = res.json() # Replace take_profit order take_profit_order = { "deal_type": "take_profit", "order_id": order["orderId"], "strategy": "long", # change accordingly "pair": order["symbol"], "order_side": order["side"], "order_type": order["type"], "price": order["price"], "qty": order["origQty"], "fills": order["fills"], "time_in_force": order["timeInForce"], "status": order["status"], } # Build new deals list new_deals = [] for d in bot["deals"]: if d["deal_type"] != "take_profit": new_deals.append(d) # Append now new take_profit deal new_deals.append(take_profit_order) self.active_bot["orders"] = new_deals botId = app.db.bots.update_one( {"_id": self.active_bot["_id"]}, {"$push": { "orders": take_profit_order }}, ) if not botId: print(f"Failed to update take_profit deal: {botId}") else: print( f"New take_profit deal successfully updated: {botId}") return
def so_update_deal(self, so_index): """ Executes when - Klines websocket triggers condition price = safety order price - Get qty and price (use trade books so it can sell immediately at limit) - Update deal.price, deal.qty - Cancel old take profit order - Update DB with new deal data - Create new take profit order - Update DB with new take profit deal data """ pair = self.active_bot["pair"] so_qty = list( self.active_bot["safety_orders"].values())[int(so_index) - 1]["so_size"] book_order = Book_Order(pair) price = float(book_order.matching_engine(False, so_qty)) qty = round_numbers( (float(so_qty) / float(price)), self.qty_precision, ) order = { "pair": pair, "qty": supress_notation(qty, self.qty_precision), "price": supress_notation(price, self.price_precision), } res = requests.post(url=self.bb_buy_order_url, json=order) if isinstance(handle_error(res), Response): return handle_error(res) response = res.json() safety_order = { "order_id": response["orderId"], "deal_type": "safety_order", "strategy": "long", # change accordingly "pair": response["symbol"], "order_side": response["side"], "order_type": response["type"], "price": response["price"], "qty": response["origQty"], "fills": response["fills"], "time_in_force": response["timeInForce"], "so_count": so_index, "status": response["status"], } self.active_bot["orders"].append(safety_order) new_tp_price = float(response["price"]) * ( 1 + float(self.active_bot["take_profit"]) / 100) commission = 0 for chunk in response["fills"]: commission += float(chunk["commission"]) if "buy_total_qty" in self.active_bot["deal"]: buy_total_qty = float( self.active_bot["deal"]["buy_total_qty"]) + float( response["origQty"]) else: buy_total_qty = self.active_bot["base_order_size"] new_so_prices = supress_notation( self.active_bot["deal"]["safety_order_prices"][so_index], self.price_precision) del self.active_bot["deal"]["safety_order_prices"][so_index] key_to_remove = list( self.active_bot["safety_orders"].keys())[int(so_index) - 1] del self.active_bot["safety_orders"][key_to_remove] # New take profit order self.active_bot["deal"]["take_profit_price"] = new_tp_price order_id = None for order in self.active_bot["orders"]: if order["deal_type"] == "take_profit": order_id = order["order_id"] self.active_bot["orders"].remove(order) break if order_id: # First cancel old order to unlock balance cancel_response = requests.delete( url= f"{self.bb_close_order_url}/{self.active_bot['pair']}/{order_id}" ) if cancel_response.status_code != 200: print("Take profit order not found, no need to cancel") else: print("Old take profit order cancelled") qty = round_numbers(self.active_bot["deal"]["buy_total_qty"], self.qty_precision) new_tp_order = { "pair": self.active_bot["pair"], "qty": qty, "price": supress_notation(new_tp_price, self.price_precision), } res = requests.post(url=self.bb_sell_order_url, json=new_tp_order) if isinstance(handle_error(res), Response): return handle_error(res) # New take profit order successfully created tp_response = res.json() # Replace take_profit order take_profit_order = { "deal_type": "take_profit", "order_id": tp_response["orderId"], "strategy": "long", # change accordingly "pair": tp_response["symbol"], "order_side": tp_response["side"], "order_type": tp_response["type"], "price": tp_response["price"], "qty": tp_response["origQty"], "fills": tp_response["fills"], "time_in_force": tp_response["timeInForce"], "status": tp_response["status"], } self.active_bot["orders"].append(take_profit_order) botId = self.app.db.bots.update_one( {"_id": self.active_bot["_id"]}, { "$set": { "deal.buy_price": supress_notation(response["price"], self.price_precision), "deal.take_profit_price": supress_notation(new_tp_price, self.price_precision), "deal.buy_total_qty": supress_notation(buy_total_qty, self.qty_precision), "deal.safety_order_prices": new_so_prices, "safety_orders": self.active_bot["safety_orders"], "orders": self.active_bot["orders"] }, "$inc": { "deal.comission": commission }, }, ) if not botId: resp = jsonResp( { "message": "Failed to save safety_order deal in the bot", "botId": str(self.active_bot["_id"]), }, 200, ) return resp return