예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
    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
예제 #7
0
    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