Пример #1
0
    def __init__(self, app: PyCryptoBot, account: TradingAccount) -> None:
        if app.getExchange() == Exchange.BINANCE:
            self.api = BAuthAPI(
                app.getAPIKey(),
                app.getAPISecret(),
                app.getAPIURL(),
                recv_window=app.getRecvWindow(),
            )
        elif app.getExchange() == Exchange.COINBASEPRO:
            self.api = CAuthAPI(
                app.getAPIKey(),
                app.getAPISecret(),
                app.getAPIPassphrase(),
                app.getAPIURL(),
            )
        elif app.getExchange() == Exchange.KUCOIN:
            self.api = KAuthAPI(
                app.getAPIKey(),
                app.getAPISecret(),
                app.getAPIPassphrase(),
                app.getAPIURL(),
            )
        else:
            self.api = None

        self.app = app
        self.account = account

        self.action = "WAIT"
        self.buy_count = 0
        self.buy_state = ""
        self.buy_sum = 0
        self.eri_text = ""
        self.fib_high = 0
        self.fib_low = 0
        self.first_buy_size = 0
        self.iterations = 0
        self.last_action = "WAIT"
        self.last_buy_size = 0
        self.last_buy_price = 0
        self.last_buy_filled = 0
        self.last_buy_fee = 0
        self.last_buy_high = 0
        self.last_sell_size = 0
        self.last_df_index = ""
        self.sell_count = 0
        self.sell_sum = 0

        self.margintracker = 0
        self.profitlosstracker = 0
        self.feetracker = 0
        self.buy_tracker = 0

        self.last_api_call_datetime = datetime.datetime.now(
        ) - datetime.timedelta(minutes=2)
        self.exchange_last_buy = None
Пример #2
0
    def getBalance(self, currency=""):
        """Retrieves balance either live or simulation

        Parameters
        ----------
        currency: str, optional
            Filters orders by currency
        """

        if self.app.getExchange() == Exchange.KUCOIN:
            if self.mode == 'live':
                model = KAuthAPI(
                    self.app.getAPIKey(),
                    self.app.getAPISecret(),
                    self.app.getAPIPassphrase(),
                    self.app.getAPIURL(),
                    use_cache=self.app.useKucoinCache(),
                )
                trycnt, maxretry = (0, 5)
                while trycnt <= maxretry:
                    df = model.getAccounts()

                    if isinstance(df, pd.DataFrame) and len(df) > 0:
                        if currency == '':
                            # retrieve all balances
                            return df
                        else:
                            # retrieve balance of specified currency
                            df_filtered = df[df['currency'] ==
                                             currency]['available']
                            if len(df_filtered) == 0:
                                # return nil balance if no positive balance was found
                                return 0.0
                            else:
                                # return balance of specified currency (if positive)
                                if currency in ['EUR', 'GBP', 'USD']:
                                    return float(
                                        truncate(
                                            float(
                                                df[df['currency'] == currency]
                                                ['available'].values[0]), 2))
                                else:
                                    return float(
                                        truncate(
                                            float(
                                                df[df['currency'] == currency]
                                                ['available'].values[0]), 4))
                    else:
                        time.sleep(5)
                        trycnt += 1
                else:
                    return 0.0

            else:
                # return dummy balances
                if currency == '':
                    # retrieve all balances
                    return self.balance
                else:
                    self.balance = self.balance.replace('QUOTE', currency)

                    if self.balance.currency[self.balance.currency.isin(
                        [currency])].empty:
                        self.balance.loc[len(
                            self.balance)] = [currency, 0, 0, 0]

                    # retrieve balance of specified currency
                    df = self.balance
                    df_filtered = df[df['currency'] == currency]['available']

                    if len(df_filtered) == 0:
                        # return nil balance if no positive balance was found
                        return 0.0
                    else:
                        # return balance of specified currency (if positive)
                        if currency in ['EUR', 'GBP', 'USD']:
                            return float(
                                truncate(
                                    float(df[df['currency'] == currency]
                                          ['available'].values[0]), 2))
                        else:
                            return float(
                                truncate(
                                    float(df[df['currency'] == currency]
                                          ['available'].values[0]), 4))

        elif self.app.getExchange() == Exchange.BINANCE:
            if self.mode == "live":
                model = BAuthAPI(
                    self.app.getAPIKey(),
                    self.app.getAPISecret(),
                    self.app.getAPIURL(),
                    recv_window=self.app.getRecvWindow(),
                )
                df = model.getAccount()
                if isinstance(df, pd.DataFrame):
                    if currency == "":
                        # retrieve all balances
                        return df
                    else:
                        # return nil if dataframe is empty
                        if len(df) == 0:
                            return 0.0

                        # retrieve balance of specified currency
                        df_filtered = df[df["currency"] ==
                                         currency]["available"]
                        if len(df_filtered) == 0:
                            # return nil balance if no positive balance was found
                            return 0.0
                        else:
                            # return balance of specified currency (if positive)
                            if currency in ["EUR", "GBP", "USD"]:
                                return float(
                                    truncate(
                                        float(df[df['currency'] == currency]
                                              ['available'].values[0]), 2))
                            else:
                                return float(
                                    truncate(
                                        float(df[df['currency'] == currency]
                                              ['available'].values[0]), 4))
                else:
                    return 0.0
            else:
                # return dummy balances
                if currency == "":
                    # retrieve all balances
                    return self.balance
                else:
                    if self.app.getExchange() == Exchange.BINANCE:
                        self.balance = self.balance.replace("QUOTE", currency)
                    else:
                        # replace QUOTE and BASE placeholders
                        if currency in ["EUR", "GBP", "USD"]:
                            self.balance = self.balance.replace(
                                "QUOTE", currency)
                        else:
                            self.balance = self.balance.replace(
                                "BASE", currency)

                    if self.balance.currency[self.balance.currency.isin(
                        [currency])].empty:
                        self.balance.loc[len(
                            self.balance)] = [currency, 0, 0, 0]

                    # retrieve balance of specified currency
                    df = self.balance
                    df_filtered = df[df["currency"] == currency]["available"]

                    if len(df_filtered) == 0:
                        # return nil balance if no positive balance was found
                        return 0.0
                    else:
                        # return balance of specified currency (if positive)
                        if currency in ["EUR", "GBP", "USD"]:
                            return float(
                                truncate(
                                    float(df[df['currency'] == currency]
                                          ['available'].values[0]), 2))
                        else:
                            return float(
                                truncate(
                                    float(df[df['currency'] == currency]
                                          ['available'].values[0]), 4))

        elif self.app.getExchange() == Exchange.COINBASEPRO:
            if self.mode == "live":
                # if config is provided and live connect to Coinbase Pro account portfolio
                model = CBAuthAPI(
                    self.app.getAPIKey(),
                    self.app.getAPISecret(),
                    self.app.getAPIPassphrase(),
                    self.app.getAPIURL(),
                )
                trycnt, maxretry = (0, 5)
                while trycnt <= maxretry:
                    df = model.getAccounts()

                    if len(df) > 0:
                        # retrieve all balances, but check the resp
                        if currency == "" and "balance" not in df:
                            time.sleep(5)
                            trycnt += 1
                        # retrieve all balances and return
                        elif currency == "":
                            return df
                        else:
                            # retrieve balance of specified currency
                            df_filtered = df[df["currency"] ==
                                             currency]["available"]
                            if len(df_filtered) == 0:
                                # return nil balance if no positive balance was found
                                return 0.0
                            else:
                                # return balance of specified currency (if positive)
                                if currency in ["EUR", "GBP", "USD"]:
                                    return float(
                                        truncate(
                                            float(
                                                df[df['currency'] == currency]
                                                ['available'].values[0]), 2))
                                else:
                                    return float(
                                        truncate(
                                            float(
                                                df[df['currency'] == currency]
                                                ['available'].values[0]), 4))
                    else:
                        time.sleep(5)
                        trycnt += 1
                else:
                    return 0.0

            else:
                # return dummy balances
                if currency == "":
                    # retrieve all balances
                    return self.balance
                else:
                    # replace QUOTE and BASE placeholders
                    if currency in ["EUR", "GBP", "USD"]:
                        self.balance = self.balance.replace("QUOTE", currency)
                    elif currency in ["BCH", "BTC", "ETH", "LTC", "XLM"]:
                        self.balance = self.balance.replace("BASE", currency)

                    if (self.balance.currency[self.balance.currency.isin(
                        [currency])].empty == True):
                        self.balance.loc[len(
                            self.balance)] = [currency, 0, 0, 0]

                    # retrieve balance of specified currency
                    df = self.balance
                    df_filtered = df[df["currency"] == currency]["available"]

                    if len(df_filtered) == 0:
                        # return nil balance if no positive balance was found
                        return 0.0
                    else:
                        # return balance of specified currency (if positive)
                        if currency in ["EUR", "GBP", "USD"]:
                            return float(
                                truncate(
                                    float(df[df['currency'] == currency]
                                          ['available'].values[0]), 2))
                        else:
                            return float(
                                truncate(
                                    float(df[df['currency'] == currency]
                                          ['available'].values[0]), 4))
        else:
            # dummy account

            if currency == "":
                # retrieve all balances
                return self.balance
            else:
                # retrieve balance of specified currency
                df = self.balance
                df_filtered = df[df["currency"] == currency]["available"]

                if len(df_filtered) == 0:
                    # return nil balance if no positive balance was found
                    return 0.0
                else:
                    # return balance of specified currency (if positive)
                    return float(
                        df[df["currency"] == currency]["available"].values[0])
Пример #3
0
class AppState:
    def __init__(self, app: PyCryptoBot, account: TradingAccount) -> None:
        if app.getExchange() == Exchange.BINANCE:
            self.api = BAuthAPI(
                app.getAPIKey(),
                app.getAPISecret(),
                app.getAPIURL(),
                recv_window=app.getRecvWindow(),
            )
        elif app.getExchange() == Exchange.COINBASEPRO:
            self.api = CAuthAPI(
                app.getAPIKey(),
                app.getAPISecret(),
                app.getAPIPassphrase(),
                app.getAPIURL(),
            )
        elif app.getExchange() == Exchange.KUCOIN:
            self.api = KAuthAPI(
                app.getAPIKey(),
                app.getAPISecret(),
                app.getAPIPassphrase(),
                app.getAPIURL(),
            )
        else:
            self.api = None

        self.app = app
        self.account = account

        self.action = "WAIT"
        self.buy_count = 0
        self.buy_state = ""
        self.buy_sum = 0
        self.eri_text = ""
        self.fib_high = 0
        self.fib_low = 0
        self.first_buy_size = 0
        self.iterations = 0
        self.last_action = "WAIT"
        self.last_buy_size = 0
        self.last_buy_price = 0
        self.last_buy_filled = 0
        self.last_buy_fee = 0
        self.last_buy_high = 0
        self.last_sell_size = 0
        self.last_df_index = ""
        self.sell_count = 0
        self.sell_sum = 0

        self.margintracker = 0
        self.profitlosstracker = 0
        self.feetracker = 0
        self.buy_tracker = 0

        self.last_api_call_datetime = datetime.datetime.now(
        ) - datetime.timedelta(minutes=2)
        self.exchange_last_buy = None

    def minimumOrderBase(self, base, actionchk: bool = False):
        self.app.insufficientfunds = False
        if self.app.getExchange() == Exchange.BINANCE:
            df = self.api.getMarketInfoFilters(self.app.getMarket())

            if len(df) > 0:
                try:
                    base_min = float(df[df["filterType"] == "LOT_SIZE"][[
                        "minQty"
                    ]].values[0][0])
                    base = float(base)
                except:
                    return

                if base < base_min:
                    if self.app.enableinsufficientfundslogging:
                        self.app.insufficientfunds = True
                        Logger.warning(
                            f"Insufficient Base Funds! (Actual: {base}, Minimum: {base_min})"
                        )
                        return

                    sys.tracebacklimit = 0
                    raise Exception(
                        f"Insufficient Base Funds! (Actual: {base}, Minimum: {base_min})"
                    )

                return

        elif self.app.getExchange() == Exchange.COINBASEPRO:
            product = self.api.authAPI("GET",
                                       f"products/{self.app.getMarket()}")
            if len(product) == 0:
                sys.tracebacklimit = 0
                raise Exception(f"Market not found! ({self.app.getMarket()})")

            base = float(base)
            base_min = float(product["base_min_size"])

        elif self.app.getExchange() == Exchange.KUCOIN:
            resp = self.api.authAPI('GET', f'api/v1/symbols')
            product = resp[resp['symbol'] == self.app.getMarket()]
            if len(product) == 0:
                sys.tracebacklimit = 0
                raise Exception(f'Market not found! ({self.app.getMarket()})')

            base = float(base)
            base_min = float(product['baseMinSize'])
            # additional check for last order type
            if base > base_min:
                return True

        if actionchk:
            return
        elif base < base_min:
            if self.app.enableinsufficientfundslogging:
                self.app.insufficientfunds = True
                Logger.warning(
                    f"Insufficient Base Funds! (Actual: {base}, Minimum: {base_min})"
                )
                return

            sys.tracebacklimit = 0
            raise Exception(
                f"Insufficient Base Funds! (Actual: {base}, Minimum: {base_min})"
            )

    def minimumOrderQuote(self, quote, actionchk: bool = False):
        self.app.insufficientfunds = False
        if self.app.getExchange() == Exchange.BINANCE:
            df = self.api.getMarketInfoFilters(self.app.getMarket())

            if len(df) > 0:
                try:
                    quote_min = float(df[df["filterType"] == "MIN_NOTIONAL"][[
                        "minNotional"
                    ]].values[0][0])
                    quote = float(quote)
                except:
                    return

                if quote < quote_min:
                    if self.app.enableinsufficientfundslogging:
                        self.app.insufficientfunds = True
                        Logger.warning(
                            f"Insufficient Quote Funds! (Actual: {quote}, Minimum: {quote_min})"
                        )
                        return

                    sys.tracebacklimit = 0
                    raise Exception(
                        f"Insufficient Quote Funds! (Actual: {quote}, Minimum: {quote_min})"
                    )
                return
            else:
                sys.tracebacklimit = 0
                raise Exception(f"Market not found! ({self.app.getMarket()})")

        elif self.app.getExchange() == Exchange.COINBASEPRO:
            product = self.api.authAPI("GET",
                                       f"products/{self.app.getMarket()}")
            if len(product) == 0:
                sys.tracebacklimit = 0
                raise Exception(f"Market not found! ({self.app.getMarket()})")

            ticker = self.api.authAPI(
                "GET", f"products/{self.app.getMarket()}/ticker")
            price = float(ticker["price"])

            quote = float(quote)
            base_min = float(product["base_min_size"])

        elif self.app.getExchange() == Exchange.KUCOIN:
            resp = self.api.authAPI('GET', 'api/v1/symbols')
            product = resp[resp['symbol'] == self.app.getMarket()]
            if len(product) == 0:
                sys.tracebacklimit = 0
                raise Exception(f'Market not found! ({self.app.getMarket()})')

            ticker = self.api.authAPI(
                "GET",
                f"api/v1/market/orderbook/level1?symbol={self.app.getMarket()}"
            )

            price = float(ticker["price"])
            quote = float(quote)
            base_min = float(product['baseMinSize'])
            # additional check for last order type
            if (quote / price) > base_min:
                return True

        if actionchk:
            return
        elif (quote / price) < base_min:
            if self.app.enableinsufficientfundslogging:
                self.app.insufficientfunds = True
                Logger.warning(
                    f'Insufficient Quote Funds! (Actual: {"{:.8f}".format((quote / price))}, Minimum: {base_min})'
                )
                return

            sys.tracebacklimit = 0
            raise Exception(
                f'Insufficient Quote Funds! (Actual: {"{:.8f}".format((quote / price))}, Minimum: {base_min})'
            )

    def getLastOrder(self):
        # if not live
        if not self.app.isLive():
            self.last_action = "SELL"
            return

        ac = self.account.getBalance()

        df_base = ac[ac["currency"] == self.app.getBaseCurrency()]["available"]
        base = 0.0 if len(df_base) == 0 else float(df_base.values[0])

        df_quote = ac[ac["currency"] ==
                      self.app.getQuoteCurrency()]["available"]
        quote = 0.0 if len(df_quote) == 0 else float(df_quote.values[0])

        orders = self.account.getOrders(self.app.getMarket(), "", "done")
        if len(orders) > 0:
            last_order = orders[-1:]

            # if orders exist and last order is a buy
            if str(last_order.action.values[0]) == "buy" and base > 0.0:
                self.last_buy_size = float(
                    last_order[last_order.action == "buy"]["size"])
                self.last_buy_filled = float(
                    last_order[last_order.action == "buy"]["filled"])
                self.last_buy_price = float(
                    last_order[last_order.action == "buy"]["price"])

                # binance orders do not show fees
                if self.app.getExchange(
                ) == Exchange.COINBASEPRO or self.app.getExchange(
                ) == Exchange.KUCOIN:
                    self.last_buy_fee = float(
                        last_order[last_order.action == "buy"]["fees"])

                self.last_action = "BUY"
                return
            else:
                self.minimumOrderQuote(quote)
                self.last_action = "SELL"
                self.last_buy_price = 0.0
                return
        else:
            # nil base or quote funds
            if base == 0.0 and quote == 0.0:
                if self.app.enableinsufficientfundslogging:
                    self.app.insufficientfunds = True
                    Logger.warning(
                        f"Insufficient Funds! ({self.app.getBaseCurrency()}={str(base)}, {self.app.getQuoteCurrency()}={str(base)})"
                    )
                    self.last_action = "WAIT"
                    return

                sys.tracebacklimit = 0
                raise Exception(
                    f"Insufficient Funds! ({self.app.getBaseCurrency()}={str(base)}, {self.app.getQuoteCurrency()}={str(base)})"
                )

            # determine last action by comparing normalised [0,1] base and quote balances
            order_pairs = np_array([base, quote])
            order_pairs_normalised = (
                order_pairs - np_min(order_pairs)) / np_ptp(order_pairs)

            # If Kucoin returns empty response, on a shared trading account, could multiple buy same pair
            if self.app.getExchange(
            ) == Exchange.KUCOIN and self.minimumOrderBase(
                    base, actionchk=True) and self.minimumOrderQuote(
                        quote, actionchk=True):
                self.last_action = "BUY"
            elif order_pairs_normalised[0] < order_pairs_normalised[1]:
                self.minimumOrderQuote(quote)
                self.last_action = "SELL"
            elif order_pairs_normalised[0] > order_pairs_normalised[1]:
                self.minimumOrderBase(base)
                self.last_action = "BUY"

            else:
                self.last_action = "WAIT"

            return

    def initLastAction(self):
        # ignore if manually set
        if self.app.getLastAction() is not None:
            self.last_action = self.app.getLastAction()
            return

        self.getLastOrder()

    def pollLastAction(self):
        self.getLastOrder()
Пример #4
0
class AppState:
    def __init__(self, app: PyCryptoBot, account: TradingAccount) -> None:
        if app.getExchange() == Exchange.BINANCE:
            self.api = BAuthAPI(
                app.getAPIKey(),
                app.getAPISecret(),
                app.getAPIURL(),
                recv_window=app.getRecvWindow(),
            )
        elif app.getExchange() == Exchange.COINBASEPRO:
            self.api = CAuthAPI(
                app.getAPIKey(),
                app.getAPISecret(),
                app.getAPIPassphrase(),
                app.getAPIURL(),
            )
        elif app.getExchange() == Exchange.KUCOIN:
            self.api = KAuthAPI(
                app.getAPIKey(),
                app.getAPISecret(),
                app.getAPIPassphrase(),
                app.getAPIURL(),
                use_cache=app.useKucoinCache(),
            )
        else:
            self.api = None

        self.app = app
        self.account = account

        self.action = "WAIT"
        self.buy_count = 0
        self.buy_state = ""
        self.buy_sum = 0
        self.eri_text = ""
        self.fib_high = 0
        self.fib_low = 0
        self.first_buy_size = 0
        self.iterations = 0
        self.last_action = "WAIT"
        self.last_buy_size = 0
        self.last_buy_price = 0
        self.last_buy_filled = 0
        self.last_buy_fee = 0
        self.last_buy_high = 0
        self.last_sell_size = 0
        self.trailing_buy = 0
        self.waiting_buy_price = 0
        self.previous_buy_size = 0
        self.open_trade_margin = 0
        self.in_open_trade = False
        self.last_df_index = ""
        self.sell_count = 0
        self.sell_sum = 0
        self.prevent_loss = 0
        self.tsl_triggered = 0
        self.margintracker = 0
        self.profitlosstracker = 0
        self.feetracker = 0
        self.buy_tracker = 0
        self.trade_error_cnt = 0

        self.last_api_call_datetime = datetime.datetime.now() - datetime.timedelta(
            minutes=2
        )
        self.exchange_last_buy = None

    def minimumOrderBase(self, base, balancechk: bool = False):
        self.app.insufficientfunds = False
        if self.app.getExchange() == Exchange.BINANCE:
            df = self.api.getMarketInfoFilters(self.app.getMarket())
            if len(df) > 0:
                base_min = float(
                    df[df["filterType"] == "LOT_SIZE"][["minQty"]].values[0][0]
                )
                base = float(base)

        elif self.app.getExchange() == Exchange.COINBASEPRO:
            product = self.api.authAPI("GET", f"products/{self.app.getMarket()}")
            if len(product) == 0:
                sys.tracebacklimit = 0
                raise Exception(f"Market not found! ({self.app.getMarket()})")

            base = float(base)
            base_min = float(product["base_min_size"])

        elif self.app.getExchange() == Exchange.KUCOIN:
            resp = self.api.authAPI("GET", "api/v1/symbols")
            product = resp[resp["symbol"] == self.app.getMarket()]
            if len(product) == 0:
                sys.tracebacklimit = 0
                raise Exception(f"Market not found! ({self.app.getMarket()})")

            base = float(base)
            base_min = float(product["baseMinSize"])

        # additional check for last order type
        if balancechk:
            if base > base_min:
                return True
            else:
                return
        elif base < base_min:
            if self.app.enableinsufficientfundslogging:
                self.app.insufficientfunds = True
                Logger.warning(
                    f"Insufficient Base Funds! (Actual: {base}, Minimum: {base_min})"
                )
                return

            sys.tracebacklimit = 0
            raise Exception(
                f"Insufficient Base Funds! (Actual: {base}, Minimum: {base_min})"
            )
        else:
            return

    def minimumOrderQuote(self, quote, balancechk: bool = False):
        self.app.insufficientfunds = False
        if self.app.getExchange() == Exchange.BINANCE:
            df = self.api.getMarketInfoFilters(self.app.getMarket())

            if len(df) > 0:
                quote_min = float(
                    df[df["filterType"] == "MIN_NOTIONAL"][["minNotional"]].values[0][0]
                )
                quote = float(quote)

                if quote < quote_min:
                    if self.app.enableinsufficientfundslogging:
                        self.app.insufficientfunds = True

                        Logger.warning(f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | Insufficient Quote Funds! (Actual: {quote}, Minimum: {quote_min})")
                        return

                    sys.tracebacklimit = 0
                    raise Exception(
                        f"Insufficient Quote Funds! (Actual: {quote}, Minimum: {quote_min})"
                    )
                elif balancechk and quote > quote_min:
                    return True
                else:
                    return
            else:
                sys.tracebacklimit = 0
                raise Exception(f"Market not found! ({self.app.getMarket()})")

        elif self.app.getExchange() == Exchange.COINBASEPRO:
            product = self.api.authAPI("GET", f"products/{self.app.getMarket()}")
            if len(product) == 0:
                sys.tracebacklimit = 0
                raise Exception(f"Market not found! ({self.app.getMarket()})")

            ticker = self.api.authAPI("GET", f"products/{self.app.getMarket()}/ticker")
            price = float(ticker["price"])

            quote = float(quote)
            base_min = float(product["base_min_size"])

        elif self.app.getExchange() == Exchange.KUCOIN:
            resp = self.api.authAPI("GET", "api/v1/symbols")
            product = resp[resp["symbol"] == self.app.getMarket()]
            if len(product) == 0:
                sys.tracebacklimit = 0
                raise Exception(f"Market not found! ({self.app.getMarket()})")

            ticker = self.api.authAPI(
                "GET", f"api/v1/market/orderbook/level1?symbol={self.app.getMarket()}"
            )

            price = float(ticker["price"])
            quote = float(quote)
            base_min = float(product["baseMinSize"])

        # additional check for last order type
        if balancechk:
            if (quote / price) > base_min:
                return True
            else:
                return
        elif quote < self.app.buyminsize:
            if self.app.enableinsufficientfundslogging:
                self.app.insufficientfunds = True
        elif (quote / price) < base_min:
            if self.app.enableinsufficientfundslogging:
                self.app.insufficientfunds = True
                Logger.warning(
                    f'Insufficient Quote Funds! (Actual: {"{:.8f}".format((quote / price))}, Minimum: {base_min})'
                )
                return

            sys.tracebacklimit = 0
            raise Exception(
                f'Insufficient Quote Funds! (Actual: {"{:.8f}".format((quote / price))}, Minimum: {base_min})'
            )

    def getLastOrder(self):
        # if not live
        if not self.app.isLive():
            self.last_action = "SELL"
            return

        base = 0.0
        quote = 0.0

        ac = self.account.getBalance()
        try:
            df_base = ac[ac["currency"] == self.app.getBaseCurrency()]["available"]
            base = 0.0 if len(df_base) == 0 else float(df_base.values[0])

            df_quote = ac[ac["currency"] == self.app.getQuoteCurrency()]["available"]
            quote = 0.0 if len(df_quote) == 0 else float(df_quote.values[0])
        except:
            pass
        orders = self.account.getOrders(self.app.getMarket(), "", "done")
        if len(orders) > 0:
            last_order = orders[-1:]

            # if orders exist and last order is a buy
            if str(last_order.action.values[0]) == "buy" and base > 0.0:
                self.last_buy_size = float(
                    last_order[last_order.action == "buy"]["size"]
                )
                self.last_buy_filled = float(
                    last_order[last_order.action == "buy"]["filled"]
                )
                self.last_buy_price = float(
                    last_order[last_order.action == "buy"]["price"]
                )
                self.in_open_trade = True

                # binance orders do not show fees
                if (
                    self.app.getExchange() == Exchange.COINBASEPRO
                    or self.app.getExchange() == Exchange.KUCOIN
                ):
                    self.last_buy_fee = float(
                        last_order[last_order.action == "buy"]["fees"]
                    )

                self.last_action = "BUY"
                return
            else:
                # get last Sell order filled to use as next buy size
                if str(last_order.action.values[0]) == "sell" and quote > 0.0:
                    self.last_sell_size = float(
                        last_order[last_order.action == "sell"]["filled"]
                    ) * float(last_order[last_order.action == "sell"]["price"])
                self.minimumOrderQuote(quote)
                self.last_action = "SELL"
                self.last_buy_price = 0.0
                self.in_open_trade = False
                return
        else:
            if quote > 0.0:
                self.minimumOrderQuote(quote)
            # nil base or quote funds
            if base == 0.0 and quote == 0.0:
                if self.app.enableinsufficientfundslogging:
                    self.app.insufficientfunds = True
                    Logger.warning(
                        f"Insufficient Funds! ({self.app.getBaseCurrency()}={str(base)}, {self.app.getQuoteCurrency()}={str(base)})"
                    )
                    self.last_action = "WAIT"
                    return

                sys.tracebacklimit = 0
                raise Exception(
                    f"Insufficient Funds! ({self.app.getBaseCurrency()}={str(base)}, {self.app.getQuoteCurrency()}={str(base)})"
                )

            # determine last action by comparing normalised [0,1] base and quote balances
            order_pairs = np_array([base, quote])
            order_pairs_normalised = (order_pairs - np_min(order_pairs)) / np_ptp(
                order_pairs
            )

            # If multi buy check enabled for pair, check balances to prevent multiple buys
            if (
                self.app.marketMultiBuyCheck()
                and self.minimumOrderBase(base, balancechk=True)
                and self.minimumOrderQuote(quote, balancechk=True)
            ):
                self.last_action = "BUY"
                Logger.warning(
                    f"Market - {self.app.getMarket()} did not return order info, but looks like there was a already a buy. Set last action to buy"
                )
            elif order_pairs_normalised[0] < order_pairs_normalised[1]:
                self.minimumOrderQuote(quote)
                self.last_action = "SELL"
            elif order_pairs_normalised[0] > order_pairs_normalised[1]:
                self.minimumOrderBase(base)
                self.last_action = "BUY"
            else:
                self.last_action = "WAIT"

            return

    def initLastAction(self):
        # ignore if manually set
        if self.app.getLastAction() is not None:
            self.last_action = self.app.getLastAction()
            return

        self.getLastOrder()

    def pollLastAction(self):
        self.getLastOrder()