diff --git a/additional_tests/exchanges_tests/abstract_authenticated_exchange_tester.py b/additional_tests/exchanges_tests/abstract_authenticated_exchange_tester.py index fe555754d..0f83ef8c0 100644 --- a/additional_tests/exchanges_tests/abstract_authenticated_exchange_tester.py +++ b/additional_tests/exchanges_tests/abstract_authenticated_exchange_tester.py @@ -16,6 +16,7 @@ import asyncio import contextlib import decimal +import random import time import typing import ccxt @@ -53,6 +54,7 @@ class AbstractAuthenticatedExchangeTester: ORDER_CURRENCY = "BTC" SETTLEMENT_CURRENCY = "USDT" SYMBOL = f"{ORDER_CURRENCY}/{SETTLEMENT_CURRENCY}" + TIME_FRAME = "1h" VALID_ORDER_ID = "8bb80a81-27f7-4415-aa50-911ea46d841c" SPECIAL_ORDER_TYPES_BY_EXCHANGE_ID: dict[ str, ( @@ -104,6 +106,8 @@ class AbstractAuthenticatedExchangeTester: CHECK_EMPTY_ACCOUNT = False # set True when the account to check has no funds. Warning: does not check order # parse/create/fill/cancel or portfolio & trades parsing IS_BROKER_ENABLED_ACCOUNT = True # set False when this test account can't generate broker fees + # set True when this exchange used to have symbols that can't be traded through API (ex: MEXC) + USED_TO_HAVE_UNTRADABLE_SYMBOL = False # Implement all "test_[name]" methods, call super() to run the test, pass to ignore it. # Override the "inner_test_[name]" method to override a test content. @@ -158,6 +162,98 @@ def check_portfolio_content(self, portfolio): at_least_one_value = True assert at_least_one_value + async def test_untradable_symbols(self): + await self.inner_test_untradable_symbols() + + async def inner_test_untradable_symbols(self): + if not self.USED_TO_HAVE_UNTRADABLE_SYMBOL: + # nothing to do + return + async with self.local_exchange_manager(): + all_symbols = self.exchange_manager.exchange.get_all_available_symbols() + tradable_symbols = await self.exchange_manager.exchange.get_all_tradable_symbols() + assert len(all_symbols) > len(tradable_symbols) + untradable_symbols = [ + symbol + for symbol in all_symbols + if symbol not in tradable_symbols + and symbol.endswith(f"/{self.SETTLEMENT_CURRENCY}") + and ( + symbols.parse_symbol(symbol).is_spot() + if self.EXCHANGE_TYPE == trading_enums.ExchangeTypes.SPOT.value + else symbols.parse_symbol(symbol).is_future() + ) + ] + tradable_symbols = [ + symbol + for symbol in all_symbols + if symbol in tradable_symbols + and symbol.endswith(f"/{self.SETTLEMENT_CURRENCY}") + and ( + symbols.parse_symbol(symbol).is_spot() + if self.EXCHANGE_TYPE == trading_enums.ExchangeTypes.SPOT.value + else symbols.parse_symbol(symbol).is_future() + ) + ] + # has untradable symbols of this trading type + assert len(untradable_symbols) > 0 + first_untradable_symbol = untradable_symbols[0] + # Public data + # market status is available + assert ccxt_constants.CCXT_INFO in self.exchange_manager.exchange.get_market_status(first_untradable_symbol) + # fetching ohlcv is ok + assert len( + await self.exchange_manager.exchange.get_symbol_prices( + first_untradable_symbol, commons_enums.TimeFrames(self.TIME_FRAME) + ) + ) > 5 + # fetching kline is ok + kline = await self.exchange_manager.exchange.get_kline_price( + first_untradable_symbol, commons_enums.TimeFrames(self.TIME_FRAME) + ) + assert len(kline) == 1 + assert len(kline[0]) == 6 + # fetching ticker is ok + ticker = await self.exchange_manager.exchange.get_price_ticker(first_untradable_symbol) + assert ticker + price = ticker[trading_enums.ExchangeConstantsTickersColumns.CLOSE.value] + assert price > 0 + # fetching recent trades is ok + recent_trades = await self.exchange_manager.exchange.get_recent_trades(first_untradable_symbol) + assert len(recent_trades) > 1 + # fetching order book is ok + order_book = await self.exchange_manager.exchange.get_order_book(first_untradable_symbol) + assert len(order_book[trading_enums.ExchangeConstantsOrderBookInfoColumns.ASKS.value]) > 0 + # is in all tickers + all_tickers = await self.exchange_manager.exchange.get_all_currencies_price_ticker() + assert all_tickers[first_untradable_symbol][trading_enums.ExchangeConstantsTickersColumns.CLOSE.value] > 0 + # Orders + # try creating & cancelling orders on 5 random tradable and untradable symbols + symbols_to_test = 5 + tradable_stepper = random.randint(1, len(tradable_symbols) - 2) + tradable_indexes = [tradable_stepper * i for i in range(0, symbols_to_test)] + untradable_stepper = random.randint(1, len(untradable_symbols) - 2) + untradable_indexes = [untradable_stepper * i for i in range(0, symbols_to_test)] + to_test_symbols = [ + tradable_symbols[i % (len(tradable_symbols) - 1)] for i in tradable_indexes + ] + [ + untradable_symbols[i % (len(untradable_symbols) - 1)] for i in untradable_indexes + ] + for i, symbol in enumerate(to_test_symbols): + ticker = await self.exchange_manager.exchange.get_price_ticker(symbol) + price = ticker[trading_enums.ExchangeConstantsTickersColumns.CLOSE.value] + price = self.get_order_price(decimal.Decimal(str(price)), False, symbol=symbol) + size = self.get_order_size( + await self.get_portfolio(), price, symbol=symbol, + settlement_currency=self.SETTLEMENT_CURRENCY + ) + buy_limit = await self.create_limit_order( + price, size, trading_enums.TradeOrderSide.BUY, + symbol=symbol + ) + await self.cancel_order(buy_limit) + print(f"{i+1}/{len(to_test_symbols)} : {symbol} Create & cancel order OK") + async def test_get_account_id(self): async with self.local_exchange_manager(): await self.inner_test_get_account_id() @@ -1432,7 +1528,7 @@ def get_exchange_data(self, symbol=None, all_symbols=None) -> exchange_data_impo exchange_details={"name": self.exchange_manager.exchange_name}, markets=[ { - "id": s, "symbol": s, "info": {}, "time_frame": "1h", + "id": s, "symbol": s, "info": {}, "time_frame": self.TIME_FRAME, "close": [0], "open": [0], "high": [0], "low": [0], "volume": [0], "time": [0] # todo } for s in _symbols diff --git a/additional_tests/exchanges_tests/test_ascendex.py b/additional_tests/exchanges_tests/test_ascendex.py index 10d90d2fd..845e55cdc 100644 --- a/additional_tests/exchanges_tests/test_ascendex.py +++ b/additional_tests/exchanges_tests/test_ascendex.py @@ -40,6 +40,9 @@ async def test_get_portfolio_with_market_filter(self): # pass if not implemented pass + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): # pass if not implemented pass diff --git a/additional_tests/exchanges_tests/test_binance.py b/additional_tests/exchanges_tests/test_binance.py index afef1004d..f93ea0a30 100644 --- a/additional_tests/exchanges_tests/test_binance.py +++ b/additional_tests/exchanges_tests/test_binance.py @@ -81,6 +81,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): await super().test_get_account_id() diff --git a/additional_tests/exchanges_tests/test_binance_futures.py b/additional_tests/exchanges_tests/test_binance_futures.py index c7a2580c6..f3ff05e8c 100644 --- a/additional_tests/exchanges_tests/test_binance_futures.py +++ b/additional_tests/exchanges_tests/test_binance_futures.py @@ -81,6 +81,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() # can have small variations failing the test when positions are open + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): await super().test_get_account_id() diff --git a/additional_tests/exchanges_tests/test_bingx.py b/additional_tests/exchanges_tests/test_bingx.py index e40a9b160..dce470c2e 100644 --- a/additional_tests/exchanges_tests/test_bingx.py +++ b/additional_tests/exchanges_tests/test_bingx.py @@ -77,6 +77,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): await super().test_get_account_id() diff --git a/additional_tests/exchanges_tests/test_bitget.py b/additional_tests/exchanges_tests/test_bitget.py index e62de2920..63f46c6c3 100644 --- a/additional_tests/exchanges_tests/test_bitget.py +++ b/additional_tests/exchanges_tests/test_bitget.py @@ -42,6 +42,9 @@ async def test_get_portfolio_with_market_filter(self): # pass if not implemented pass + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): # pass if not implemented pass diff --git a/additional_tests/exchanges_tests/test_bitmart.py b/additional_tests/exchanges_tests/test_bitmart.py index 58146e68a..7c6700ac2 100644 --- a/additional_tests/exchanges_tests/test_bitmart.py +++ b/additional_tests/exchanges_tests/test_bitmart.py @@ -40,6 +40,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): # pass if not implemented pass diff --git a/additional_tests/exchanges_tests/test_bybit.py b/additional_tests/exchanges_tests/test_bybit.py index 6bce71e67..b893070f4 100644 --- a/additional_tests/exchanges_tests/test_bybit.py +++ b/additional_tests/exchanges_tests/test_bybit.py @@ -44,6 +44,9 @@ async def test_get_portfolio_with_market_filter(self): # pass if not implemented pass + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): # pass if not implemented pass diff --git a/additional_tests/exchanges_tests/test_bybit_futures.py b/additional_tests/exchanges_tests/test_bybit_futures.py index 98678b74c..a90efa93a 100644 --- a/additional_tests/exchanges_tests/test_bybit_futures.py +++ b/additional_tests/exchanges_tests/test_bybit_futures.py @@ -47,6 +47,9 @@ async def test_get_portfolio_with_market_filter(self): # pass if not implemented pass + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): # pass if not implemented pass diff --git a/additional_tests/exchanges_tests/test_coinbase.py b/additional_tests/exchanges_tests/test_coinbase.py index 6176f14f8..6e484f29a 100644 --- a/additional_tests/exchanges_tests/test_coinbase.py +++ b/additional_tests/exchanges_tests/test_coinbase.py @@ -72,6 +72,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): await super().test_get_account_id() diff --git a/additional_tests/exchanges_tests/test_coinex.py b/additional_tests/exchanges_tests/test_coinex.py index 3df57e583..8fa9c5abe 100644 --- a/additional_tests/exchanges_tests/test_coinex.py +++ b/additional_tests/exchanges_tests/test_coinex.py @@ -39,6 +39,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): # pass if not implemented pass diff --git a/additional_tests/exchanges_tests/test_cryptocom.py b/additional_tests/exchanges_tests/test_cryptocom.py index efbbe4994..23ca404de 100644 --- a/additional_tests/exchanges_tests/test_cryptocom.py +++ b/additional_tests/exchanges_tests/test_cryptocom.py @@ -40,6 +40,9 @@ async def test_get_portfolio_with_market_filter(self): # pass if not implemented pass + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): # pass if not implemented pass diff --git a/additional_tests/exchanges_tests/test_gateio.py b/additional_tests/exchanges_tests/test_gateio.py index c9abe4f3b..009813fee 100644 --- a/additional_tests/exchanges_tests/test_gateio.py +++ b/additional_tests/exchanges_tests/test_gateio.py @@ -41,6 +41,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): # pass if not implemented pass diff --git a/additional_tests/exchanges_tests/test_hollaex.py b/additional_tests/exchanges_tests/test_hollaex.py index c2e9c3027..07f31c107 100644 --- a/additional_tests/exchanges_tests/test_hollaex.py +++ b/additional_tests/exchanges_tests/test_hollaex.py @@ -44,6 +44,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): await super().test_get_account_id() diff --git a/additional_tests/exchanges_tests/test_htx.py b/additional_tests/exchanges_tests/test_htx.py index ab2f68b44..43923ff28 100644 --- a/additional_tests/exchanges_tests/test_htx.py +++ b/additional_tests/exchanges_tests/test_htx.py @@ -41,6 +41,9 @@ async def test_get_portfolio_with_market_filter(self): # pass if not implemented pass + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): # pass if not implemented pass diff --git a/additional_tests/exchanges_tests/test_kucoin.py b/additional_tests/exchanges_tests/test_kucoin.py index a42eb3f55..3295f5392 100644 --- a/additional_tests/exchanges_tests/test_kucoin.py +++ b/additional_tests/exchanges_tests/test_kucoin.py @@ -75,6 +75,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_is_valid_account(self): await super().test_is_valid_account() diff --git a/additional_tests/exchanges_tests/test_kucoin_futures.py b/additional_tests/exchanges_tests/test_kucoin_futures.py index d6a9f5a65..8727b6f9e 100644 --- a/additional_tests/exchanges_tests/test_kucoin_futures.py +++ b/additional_tests/exchanges_tests/test_kucoin_futures.py @@ -79,6 +79,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() # can have small variations failing the test when positions are open + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): await super().test_get_account_id() diff --git a/additional_tests/exchanges_tests/test_mexc.py b/additional_tests/exchanges_tests/test_mexc.py index 042903d0b..14975241b 100644 --- a/additional_tests/exchanges_tests/test_mexc.py +++ b/additional_tests/exchanges_tests/test_mexc.py @@ -28,7 +28,7 @@ class TestMEXCAuthenticatedExchange( EXCHANGE_NAME = "mexc" EXCHANGE_TENTACLE_NAME = "MEXC" ORDER_CURRENCY = "BTC" - SETTLEMENT_CURRENCY = "USDC" + SETTLEMENT_CURRENCY = "USDT" SYMBOL = f"{ORDER_CURRENCY}/{SETTLEMENT_CURRENCY}" ORDER_SIZE = 30 # % of portfolio to include in test orders CONVERTS_ORDER_SIZE_BEFORE_PUSHING_TO_EXCHANGES = True @@ -36,6 +36,7 @@ class TestMEXCAuthenticatedExchange( EXPECT_MISSING_FEE_IN_CANCELLED_ORDERS = False IS_ACCOUNT_ID_AVAILABLE = False USE_ORDER_OPERATION_TO_CHECK_API_KEY_RIGHTS = True + USED_TO_HAVE_UNTRADABLE_SYMBOL = True async def test_get_portfolio(self): await super().test_get_portfolio() @@ -43,6 +44,9 @@ async def test_get_portfolio(self): async def test_get_portfolio_with_market_filter(self): await super().test_get_portfolio_with_market_filter() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): await super().test_get_account_id() diff --git a/additional_tests/exchanges_tests/test_okx.py b/additional_tests/exchanges_tests/test_okx.py index 0737b6fa1..db4f85c1c 100644 --- a/additional_tests/exchanges_tests/test_okx.py +++ b/additional_tests/exchanges_tests/test_okx.py @@ -54,6 +54,9 @@ async def test_create_and_cancel_limit_orders(self): async def test_get_account_id(self): await super().test_get_account_id() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_is_authenticated_request(self): await super().test_is_authenticated_request() diff --git a/additional_tests/exchanges_tests/test_okx_futures.py b/additional_tests/exchanges_tests/test_okx_futures.py index 104969d42..fd47c3936 100644 --- a/additional_tests/exchanges_tests/test_okx_futures.py +++ b/additional_tests/exchanges_tests/test_okx_futures.py @@ -46,6 +46,9 @@ async def test_get_portfolio_with_market_filter(self): async def test_get_account_id(self): await super().test_get_account_id() + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_is_authenticated_request(self): await super().test_is_authenticated_request() diff --git a/additional_tests/exchanges_tests/test_phemex.py b/additional_tests/exchanges_tests/test_phemex.py index 83fbce10c..6d2ba7f84 100644 --- a/additional_tests/exchanges_tests/test_phemex.py +++ b/additional_tests/exchanges_tests/test_phemex.py @@ -39,6 +39,9 @@ async def test_get_portfolio_with_market_filter(self): # pass if not implemented pass + async def test_untradable_symbols(self): + await super().test_untradable_symbols() + async def test_get_account_id(self): # pass if not implemented pass diff --git a/requirements.txt b/requirements.txt index 0ca473ac5..e34ca6933 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Drakkar-Software requirements OctoBot-Commons==1.9.70 -OctoBot-Trading==2.4.145 +OctoBot-Trading==2.4.146 OctoBot-Evaluators==1.9.7 OctoBot-Tentacles-Manager==2.9.16 OctoBot-Services==1.6.23