diff --git a/api/bots/controllers.py b/api/bots/controllers.py index 34c38d4c5..989970504 100644 --- a/api/bots/controllers.py +++ b/api/bots/controllers.py @@ -6,10 +6,9 @@ from fastapi.exceptions import RequestValidationError from account.account import Account -from deals.margin import MarginShortError from deals.controllers import CreateDealController from tools.enum_definitions import BinbotEnums -from tools.exceptions import OpenDealError, NotEnoughFunds, QuantityTooLow, IsolateBalanceError +from tools.exceptions import BinanceErrors, BinbotErrors, QuantityTooLow from tools.handle_error import ( handle_binance_errors, json_response, @@ -184,14 +183,11 @@ def activate(self, botId: str): bot, db_collection=self.db_collection.name ).open_deal() return json_response_message("Successfully activated bot!") - except OpenDealError as error: - return json_response_error(error.args[0]) - except NotEnoughFunds as e: - return json_response_error(e.args[0]) - except MarginShortError as error: - message = str("Unable to create margin_short bot: ".join(error.args)) - return json_response_error(message) - except IsolateBalanceError as error: + except BinanceErrors as error: + self.post_errors_by_id(botId, error.message) + return json_response_error(error.message) + except BinbotErrors as error: + self.post_errors_by_id(botId, error.message) return json_response_error(error.message) except Exception as error: resp = json_response_error(f"Unable to activate bot: {error}") @@ -355,3 +351,20 @@ def put_archive(self, botId): resp = json_response({"message": f"Failed to archive bot {error}"}) return resp + + def post_errors_by_id(self, bot_id: str, reported_error: str): + """ + Directly post errors to Bot + which should show in the BotForm page in Web + """ + try: + self.db_collection.update_one( + {"id": bot_id}, {"$push": {"errors": reported_error}} + ) + return json_response( + {"message": "Successfully submitted bot errors", "botId": bot_id} + ) + except Exception as error: + resp = json_response({"message": f"Failed to submit bot errors: {error}"}) + + return resp diff --git a/api/bots/routes.py b/api/bots/routes.py index 1f10812b3..db1efce08 100644 --- a/api/bots/routes.py +++ b/api/bots/routes.py @@ -63,3 +63,8 @@ def deactivate(id: str): @bot_blueprint.put("/bot/archive/{id}", tags=["bots"]) def archive(id: str): return Bot(collection_name="bots").put_archive(id) + + +@bot_blueprint.post("/bot/errors/{bot_id}", tags=["bots"]) +def bot_errors(bot_id: str, bot_errors: str): + return Bot(collection_name="bots").post_errors_by_id(bot_id, bot_errors) diff --git a/api/bots/schemas.py b/api/bots/schemas.py index 6aa81c8da..e69eb8dde 100644 --- a/api/bots/schemas.py +++ b/api/bots/schemas.py @@ -1,5 +1,5 @@ from time import time -from typing import Literal +from typing import List, Literal from bson.objectid import ObjectId from tools.enum_definitions import Status @@ -40,7 +40,7 @@ class BotSchema(BaseModel): created_at: float = time() * 1000 deal: DealSchema = Field(default_factory=DealSchema) dynamic_trailling: bool = False - errors: list[str] = [] + errors: list[str] = [""] # Event logs locked_so_funds: float = 0 # funds locked by Safety orders mode: str = "manual" # Manual is triggered by the terminal dashboard, autotrade by research app name: str = "Default bot" @@ -90,12 +90,18 @@ def check_trailling(cls, v: str | bool): return False return True + @validator("errors") + def check_errors_format(cls, v: list[str]): + if isinstance(v, list): + return [] + return [] + class Config: use_enum_values = True arbitrary_types_allowed = True json_encoders = {ObjectId: str} schema_extra = { - "description": "Most fields are optional. Deal field is generated internally, orders are filled up by Binance and", + "description": "Most fields are optional. Deal field is generated internally, orders are filled up by Binance", "example": { "pair": "BNBUSDT", "balance_size_to_use": 0, diff --git a/api/deals/controllers.py b/api/deals/controllers.py index f61e167c9..2f6de1eb0 100644 --- a/api/deals/controllers.py +++ b/api/deals/controllers.py @@ -8,7 +8,6 @@ from pymongo import ReturnDocument from tools.enum_definitions import Status from tools.exceptions import ( - ShortStrategyError, TakeProfitError, ) from tools.handle_error import ( @@ -90,7 +89,11 @@ def base_order(self): pair, supress_notation(price, self.price_precision), qty, "BUY" ) else: - res = self.buy_order(symbol=pair, qty=qty, price=supress_notation(price, self.price_precision)) + res = self.buy_order( + symbol=pair, + qty=qty, + price=supress_notation(price, self.price_precision), + ) order_data = OrderSchema( timestamp=res["transactTime"], @@ -280,7 +283,11 @@ def update_take_profit(self, order_id): print("Old take profit order cancelled") qty = round_numbers(self.get_one_balance(asset), self.qty_precision) - res = self.sell_order(symbol=self.active_bot.pair, qty=qty, price=supress_notation(new_tp_price, self.price_precision)) + res = self.sell_order( + symbol=self.active_bot.pair, + qty=qty, + price=supress_notation(new_tp_price, self.price_precision), + ) # New take profit order successfully created order = handle_binance_errors(res) @@ -320,7 +327,6 @@ def update_take_profit(self, order_id): else: self.update_deal_logs("Error: Bot does not contain a base order deal") - def open_deal(self): """ Mandatory deals section @@ -382,13 +388,10 @@ def open_deal(self): # Keep trailling_stop_loss_price up to date in case of failure to update in autotrade # if we don't do this, the trailling stop loss will trigger - if ( - self.active_bot.deal - and ( - self.active_bot.deal.trailling_stop_loss_price > 0 - or self.active_bot.deal.trailling_stop_loss_price - < self.active_bot.deal.buy_price - ) + if self.active_bot.deal and ( + self.active_bot.deal.trailling_stop_loss_price > 0 + or self.active_bot.deal.trailling_stop_loss_price + < self.active_bot.deal.buy_price ): take_profit_price = float(self.active_bot.deal.buy_price) * ( 1 + (float(self.active_bot.take_profit) / 100) diff --git a/api/deals/margin.py b/api/deals/margin.py index c72e3a2fe..6a6fec183 100644 --- a/api/deals/margin.py +++ b/api/deals/margin.py @@ -156,8 +156,13 @@ def init_margin_short(self, initial_price): self.qty_precision, ) + # transfer quantity is base order size (what we want to invest) + stop loss to cover losses + stop_loss_price_inc = float(initial_price) * ( + 1 + (self.active_bot.stop_loss / 100) + ) + final_qty = float(stop_loss_price_inc * qty) transfer_qty = round_numbers_ceiling( - float(qty), + final_qty, self.qty_precision, ) @@ -185,41 +190,32 @@ def init_margin_short(self, initial_price): raise MarginShortError("Isolated margin not available") asset = self.active_bot.pair.replace(self.active_bot.balance_to_use, "") - try: - self.create_margin_loan( - asset=asset, symbol=self.active_bot.pair, amount=qty - ) - loan_details = self.get_margin_loan_details( - asset=asset, isolatedSymbol=self.active_bot.pair - ) + self.create_margin_loan( + asset=asset, symbol=self.active_bot.pair, amount=qty + ) + loan_details = self.get_margin_loan_details( + asset=asset, isolatedSymbol=self.active_bot.pair + ) - self.active_bot.deal.margin_short_loan_timestamp = loan_details["rows"][0][ - "timestamp" - ] - self.active_bot.deal.margin_short_loan_principal = loan_details["rows"][0][ - "principal" - ] - self.active_bot.deal.margin_loan_id = loan_details["rows"][0]["txId"] - self.active_bot.deal.margin_short_base_order = qty - - # Estimate interest to add to total cost - asset = self.active_bot.pair.replace(self.active_bot.balance_to_use, "") - # This interest rate is much more accurate than any of the others - hourly_fees = self.signed_request( - url=self.isolated_hourly_interest, - payload={"assets": asset, "isIsolated": "TRUE"}, - ) - self.active_bot.deal.hourly_interest_rate = float( - hourly_fees[0]["nextHourlyInterestRate"] - ) + self.active_bot.deal.margin_short_loan_timestamp = loan_details["rows"][0][ + "timestamp" + ] + self.active_bot.deal.margin_short_loan_principal = loan_details["rows"][0][ + "principal" + ] + self.active_bot.deal.margin_loan_id = loan_details["rows"][0]["txId"] + self.active_bot.deal.margin_short_base_order = qty - except BinanceErrors as error: - if error.args[1] == -3045: - self._append_errors(error.message) - self.terminate_failed_transactions() - raise MarginShortError(error.message) - except Exception as error: - logging.error(error) + # Estimate interest to add to total cost + asset = self.active_bot.pair.replace(self.active_bot.balance_to_use, "") + # This interest rate is much more accurate than any of the others + hourly_fees = self.signed_request( + url=self.isolated_hourly_interest, + payload={"assets": asset, "isIsolated": "TRUE"}, + ) + self.active_bot.deal.hourly_interest_rate = float( + hourly_fees[0]["nextHourlyInterestRate"] + ) return @@ -389,7 +385,7 @@ def margin_short_base_order(self): self.init_margin_short(initial_price) order_res = self.sell_margin_order( symbol=self.active_bot.pair, - qty=self.active_bot.deal.margin_short_base_order, + qty=self.active_bot.base_order_size, ) else: # Simulate Margin sell diff --git a/web/src/components/LogsInfo.jsx b/web/src/components/LogsInfo.jsx index c17d4ddce..eca157c5b 100644 --- a/web/src/components/LogsInfo.jsx +++ b/web/src/components/LogsInfo.jsx @@ -1,14 +1,14 @@ import React from "react"; import { Card, CardBody, CardHeader, CardTitle } from "reactstrap"; -export default function LogInfo({ info }) { +export default function LogInfo({ events }) { return ( - Logs + Event logs - {info.map((item, i) => ( + {events.map((item, i) => (

{item}


diff --git a/web/src/pages/bots/BotForm.jsx b/web/src/pages/bots/BotForm.jsx index 147bbfb8d..a131a3071 100644 --- a/web/src/pages/bots/BotForm.jsx +++ b/web/src/pages/bots/BotForm.jsx @@ -555,11 +555,7 @@ class BotForm extends React.Component { - {this.props.bot.errors.length > 0 && ( - - - - )} + ) : ( "" @@ -754,6 +750,9 @@ class BotForm extends React.Component { {this.props.balance_estimate && ( )} + {this.props.bot.errors.length > 0 && ( + + )} diff --git a/web/src/state/bots/actions.js b/web/src/state/bots/actions.js index 7607c6d79..df0738668 100644 --- a/web/src/state/bots/actions.js +++ b/web/src/state/bots/actions.js @@ -20,6 +20,7 @@ export const bot = { baseOrderSizeError: false, balance_to_use: "USDT", bot_profit: 0, + errors: [], mode: "manual", max_so_count: "0", maxSOCountError: false,