diff --git a/additional_tests/exchanges_tests/abstract_authenticated_exchange_tester.py b/additional_tests/exchanges_tests/abstract_authenticated_exchange_tester.py
index d318ad943..8c8576edf 100644
--- a/additional_tests/exchanges_tests/abstract_authenticated_exchange_tester.py
+++ b/additional_tests/exchanges_tests/abstract_authenticated_exchange_tester.py
@@ -29,6 +29,7 @@
import octobot_trading.enums as trading_enums
import octobot_trading.constants as trading_constants
import octobot_trading.exchanges as trading_exchanges
+import octobot_trading.exchanges.connectors.ccxt.constants as ccxt_constants
import octobot_trading.personal_data as personal_data
import octobot_trading.personal_data.orders as personal_data_orders
import octobot_trading.util.test_tools.exchanges_test_tools as exchanges_test_tools
@@ -52,6 +53,18 @@ class AbstractAuthenticatedExchangeTester:
SETTLEMENT_CURRENCY = "USDT"
SYMBOL = f"{ORDER_CURRENCY}/{SETTLEMENT_CURRENCY}"
VALID_ORDER_ID = "8bb80a81-27f7-4415-aa50-911ea46d841c"
+ SPECIAL_ORDER_TYPES_BY_EXCHANGE_ID: dict[
+ str, (
+ str, # symbol
+ str, # order type key in 'info' dict
+ str, # order type found in 'info' dict
+ str, # parsed trading_enums.TradeOrderType
+ str, # parsed trading_enums.TradeOrderSide
+ bool, # trigger above (on higher price than order price)
+ )
+ ] = {} # stop loss / take profit and other special order types to be successfully parsed
+ # details of an order that exists but can"t be cancelled
+ UNCANCELLABLE_ORDER_ID_SYMBOL_TYPE: tuple[str, str, trading_enums.TraderOrderType] = None
ORDER_SIZE = 10 # % of portfolio to include in test orders
PORTFOLIO_TYPE_FOR_SIZE = trading_constants.CONFIG_PORTFOLIO_FREE
CONVERTS_ORDER_SIZE_BEFORE_PUSHING_TO_EXCHANGES = False
@@ -327,8 +340,53 @@ async def inner_test_is_valid_account(self):
assert isinstance(error, str)
assert len(error) > 0
+
+ async def test_get_special_orders(self):
+ if self.SPECIAL_ORDER_TYPES_BY_EXCHANGE_ID:
+ async with self.local_exchange_manager():
+ await self.inner_test_get_special_orders()
+
+ async def inner_test_get_special_orders(self):
+ # open_orders = await self.get_open_orders(_symbols=["TAO/USDT"])
+ # print(open_orders) # to print special orders when they are open
+ # return
+ for exchange_id, order_details in self.SPECIAL_ORDER_TYPES_BY_EXCHANGE_ID.items():
+ symbol, info_key, info_type, expected_type, expected_side, expected_trigger_above = order_details
+ fetched_order = await self.exchange_manager.exchange.get_order(exchange_id, symbol=symbol)
+ assert fetched_order is not None
+ self._check_fetched_order_dicts([fetched_order])
+ # ensure parsing order doesn't crash
+ parsed = personal_data.create_order_instance_from_raw(self.exchange_manager.trader, fetched_order)
+ assert isinstance(parsed, personal_data.Order)
+
+ assert fetched_order[trading_enums.ExchangeConstantsOrderColumns.SYMBOL.value] == symbol
+ found_type = fetched_order[ccxt_constants.CCXT_INFO][info_key]
+ assert found_type == info_type, f"[{exchange_id}]: {found_type} != {info_type}"
+ parsed_type = fetched_order[trading_enums.ExchangeConstantsOrderColumns.TYPE.value]
+ assert parsed_type == expected_type, f"[{exchange_id}]: {parsed_type} != {expected_type}"
+ assert fetched_order[trading_enums.ExchangeConstantsOrderColumns.SIDE.value] == expected_side
+ if expected_trigger_above is None:
+ assert trading_enums.ExchangeConstantsOrderColumns.TRIGGER_ABOVE.value not in fetched_order
+ else:
+ parsed_trigger_above = fetched_order[trading_enums.ExchangeConstantsOrderColumns.TRIGGER_ABOVE.value]
+ assert parsed_trigger_above == expected_trigger_above, (
+ f"[{exchange_id}]: {parsed_trigger_above} != {expected_trigger_above}"
+ )
+ if isinstance(parsed, personal_data.LimitOrder):
+ assert parsed.trigger_above == parsed_trigger_above
+ if expected_type == trading_enums.TradeOrderType.LIMIT.value:
+ assert fetched_order[trading_enums.ExchangeConstantsOrderColumns.PRICE.value] > 0
+ elif expected_type == trading_enums.TradeOrderType.STOP_LOSS.value:
+ assert fetched_order[trading_enums.ExchangeConstantsOrderColumns.STOP_PRICE.value] > 0
+ elif expected_type == trading_enums.TradeOrderType.UNSUPPORTED.value:
+ assert isinstance(parsed, personal_data.UnsupportedOrder)
+ else:
+ # ensure all cases are covered, otherwise there is a problem in order type parsing
+ assert expected_type == trading_enums.TradeOrderType.MARKET
+
async def test_create_and_cancel_limit_orders(self):
async with self.local_exchange_manager():
+ await self.inner_test_cancel_uncancellable_order()
await self.inner_test_create_and_cancel_limit_orders()
async def inner_test_create_and_cancel_limit_orders(self, symbol=None, settlement_currency=None, margin_type=None):
@@ -396,6 +454,12 @@ async def inner_test_create_and_cancel_limit_orders(self, symbol=None, settlemen
assert await self.order_not_in_open_orders(open_orders, buy_limit, symbol=symbol)
assert await self.order_in_cancelled_orders(cancelled_orders, buy_limit, symbol=symbol)
+ async def inner_test_cancel_uncancellable_order(self):
+ if self.UNCANCELLABLE_ORDER_ID_SYMBOL_TYPE:
+ order_id, symbol, order_type = self.UNCANCELLABLE_ORDER_ID_SYMBOL_TYPE
+ with pytest.raises(trading_errors.ExchangeOrderCancelError):
+ await self.exchange_manager.exchange.cancel_order(order_id, symbol, order_type)
+
async def test_create_and_fill_market_orders(self):
async with self.local_exchange_manager():
await self.inner_test_create_and_fill_market_orders()
@@ -747,12 +811,12 @@ async def get_my_recent_trades(self, exchange_data=None):
async def get_closed_orders(self, symbol=None):
return await self.exchange_manager.exchange.get_closed_orders(symbol or self.SYMBOL)
- async def get_cancelled_orders(self, exchange_data=None, force_fetch=False):
+ async def get_cancelled_orders(self, exchange_data=None, force_fetch=False, _symbols=None):
if not force_fetch and not self.exchange_manager.exchange.SUPPORT_FETCHING_CANCELLED_ORDERS:
# skipped
return []
exchange_data = exchange_data or self.get_exchange_data()
- return await exchanges_test_tools.get_cancelled_orders(self.exchange_manager, exchange_data)
+ return await exchanges_test_tools.get_cancelled_orders(self.exchange_manager, exchange_data, symbols=_symbols)
async def check_require_closed_orders_from_recent_trades(self, symbol=None):
if self.exchange_manager.exchange.REQUIRE_CLOSED_ORDERS_FROM_RECENT_TRADES:
@@ -1088,9 +1152,9 @@ def get_order_price(self, price, is_above_price, symbol=None, price_diff=None):
price * (decimal.Decimal(str(multiplier)))
)
- async def get_open_orders(self, exchange_data=None):
+ async def get_open_orders(self, exchange_data=None, _symbols=None):
exchange_data = exchange_data or self.get_exchange_data()
- orders = await exchanges_test_tools.get_open_orders(self.exchange_manager, exchange_data)
+ orders = await exchanges_test_tools.get_open_orders(self.exchange_manager, exchange_data, symbols=_symbols)
self.check_duplicate(orders)
self._check_fetched_order_dicts(orders)
return orders
diff --git a/additional_tests/exchanges_tests/test_ascendex.py b/additional_tests/exchanges_tests/test_ascendex.py
index f43b2800d..10d90d2fd 100644
--- a/additional_tests/exchanges_tests/test_ascendex.py
+++ b/additional_tests/exchanges_tests/test_ascendex.py
@@ -63,6 +63,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_binance.py b/additional_tests/exchanges_tests/test_binance.py
index 7755dcc6a..afef1004d 100644
--- a/additional_tests/exchanges_tests/test_binance.py
+++ b/additional_tests/exchanges_tests/test_binance.py
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public
# License along with OctoBot. If not, see .
import pytest
+import octobot_trading.enums
from additional_tests.exchanges_tests import abstract_authenticated_exchange_tester
@@ -41,6 +42,38 @@ class TestBinanceAuthenticatedExchange(
IS_BROKER_ENABLED_ACCOUNT = False
IS_AUTHENTICATED_REQUEST_CHECK_AVAILABLE = True # set True when is_authenticated_request is implemented
+ SPECIAL_ORDER_TYPES_BY_EXCHANGE_ID: dict[
+ str, (
+ str, # symbol
+ str, # order type key in 'info' dict
+ str, # order type found in 'info' dict
+ str, # parsed trading_enums.TradeOrderType
+ str, # parsed trading_enums.TradeOrderSide
+ bool, # trigger above (on higher price than order price)
+ )
+ ] = {
+ "6799804660": (
+ "BNB/USDT", "type", "TAKE_PROFIT",
+ octobot_trading.enums.TradeOrderType.LIMIT.value, octobot_trading.enums.TradeOrderSide.SELL.value, True
+ ),
+ '6799810041': (
+ "BNB/USDT", "type", "STOP_LOSS",
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.SELL.value, False
+ ),
+ '6799798838': (
+ "BNB/USDT", "type", "TAKE_PROFIT_LIMIT",
+ octobot_trading.enums.TradeOrderType.LIMIT.value, octobot_trading.enums.TradeOrderSide.SELL.value, True
+ ),
+ '6799795001': (
+ "BNB/USDT", "type", "STOP_LOSS_LIMIT",
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.SELL.value, False
+ ),
+ } # stop loss / take profit and other special order types to be successfully parsed
+ # details of an order that exists but can"t be cancelled
+ UNCANCELLABLE_ORDER_ID_SYMBOL_TYPE: tuple[str, str, octobot_trading.enums.TraderOrderType] = (
+ "6799798838", "BNB/USDT", octobot_trading.enums.TraderOrderType.BUY_LIMIT.value
+ )
+
async def test_get_portfolio(self):
await super().test_get_portfolio()
@@ -69,6 +102,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_binance_futures.py b/additional_tests/exchanges_tests/test_binance_futures.py
index 57cc09cde..c7a2580c6 100644
--- a/additional_tests/exchanges_tests/test_binance_futures.py
+++ b/additional_tests/exchanges_tests/test_binance_futures.py
@@ -15,6 +15,7 @@
# License along with OctoBot. If not, see .
import pytest
+import octobot_trading.enums
from additional_tests.exchanges_tests import abstract_authenticated_future_exchange_tester, \
abstract_authenticated_exchange_tester
@@ -38,6 +39,38 @@ class TestBinanceFuturesAuthenticatedExchange(
EXPECTED_QUOTE_MIN_ORDER_SIZE = 200 # min quote value of orders to create (used to check market status parsing)
IS_AUTHENTICATED_REQUEST_CHECK_AVAILABLE = True # set True when is_authenticated_request is implemented
+ SPECIAL_ORDER_TYPES_BY_EXCHANGE_ID: dict[
+ str, (
+ str, # symbol
+ str, # order type key in 'info' dict
+ str, # order type found in 'info' dict
+ str, # parsed trading_enums.TradeOrderType
+ str, # parsed trading_enums.TradeOrderSide
+ bool, # trigger above (on higher price than order price)
+ )
+ ] = {
+ "4075521283": (
+ "BTC/USDT:USDT", "type", "TAKE_PROFIT_MARKET",
+ octobot_trading.enums.TradeOrderType.LIMIT.value, octobot_trading.enums.TradeOrderSide.SELL.value, True
+ ),
+ '622529': (
+ "BTC/USDC:USDC", "type", "STOP_MARKET",
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.SELL.value, False
+ ),
+ '4076521927': (
+ "BTC/USDT:USDT", "type", "TAKE_PROFIT",
+ octobot_trading.enums.TradeOrderType.LIMIT.value, octobot_trading.enums.TradeOrderSide.BUY.value, False
+ ),
+ '4076521976': (
+ "BTC/USDT:USDT", "type", "STOP",
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.SELL.value, False
+ ),
+ } # stop loss / take profit and other special order types to be successfully parsed
+ # details of an order that exists but can"t be cancelled
+ UNCANCELLABLE_ORDER_ID_SYMBOL_TYPE: tuple[str, str, octobot_trading.enums.TraderOrderType] = (
+ "4076521927", "BTC/USDT:USDT", octobot_trading.enums.TraderOrderType.BUY_LIMIT.value
+ )
+
async def _set_account_types(self, account_types):
# todo remove this and use both types when exchange-side multi portfolio is enabled
self.exchange_manager.exchange._futures_account_types = account_types
@@ -86,6 +119,9 @@ async def test_get_and_set_leverage(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_bingx.py b/additional_tests/exchanges_tests/test_bingx.py
index 8d402a4d1..e40a9b160 100644
--- a/additional_tests/exchanges_tests/test_bingx.py
+++ b/additional_tests/exchanges_tests/test_bingx.py
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public
# License along with OctoBot. If not, see .
import pytest
+import octobot_trading.enums
from additional_tests.exchanges_tests import abstract_authenticated_exchange_tester
@@ -38,6 +39,38 @@ class TestBingxAuthenticatedExchange(
VALID_ORDER_ID = "1812980957928929280"
+ SPECIAL_ORDER_TYPES_BY_EXCHANGE_ID: dict[
+ str, (
+ str, # symbol
+ str, # order type key in 'info' dict
+ str, # order type found in 'info' dict
+ str, # parsed trading_enums.TradeOrderType
+ str, # parsed trading_enums.TradeOrderSide
+ bool, # trigger above (on higher price than order price)
+ )
+ ] = {
+ "1877004154170146816": (
+ "TAO/USDT", "type", "TAKE_STOP_MARKET",
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.SELL.value, False
+ ),
+ '1877004191864356864': (
+ "TAO/USDT", "type", "TAKE_STOP_MARKET",
+ octobot_trading.enums.TradeOrderType.LIMIT.value, octobot_trading.enums.TradeOrderSide.SELL.value, True
+ ),
+ '1877004220704391168': (
+ "TAO/USDT", "type", "TAKE_STOP_LIMIT",
+ octobot_trading.enums.TradeOrderType.UNSUPPORTED.value, octobot_trading.enums.TradeOrderSide.SELL.value, None
+ ),
+ '1877004292053696512': (
+ "TAO/USDT", "type", "TAKE_STOP_LIMIT",
+ octobot_trading.enums.TradeOrderType.UNSUPPORTED.value, octobot_trading.enums.TradeOrderSide.SELL.value, None
+ ),
+ } # stop loss / take profit and other special order types to be successfully parsed
+ # details of an order that exists but can"t be cancelled
+ UNCANCELLABLE_ORDER_ID_SYMBOL_TYPE: tuple[str, str, octobot_trading.enums.TraderOrderType] = (
+ "1877004292053696512", "TAO/USDT", octobot_trading.enums.TraderOrderType.SELL_LIMIT.value
+ )
+
async def test_get_portfolio(self):
await super().test_get_portfolio()
@@ -65,6 +98,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_bitget.py b/additional_tests/exchanges_tests/test_bitget.py
index 33c03e91d..e62de2920 100644
--- a/additional_tests/exchanges_tests/test_bitget.py
+++ b/additional_tests/exchanges_tests/test_bitget.py
@@ -65,6 +65,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_bitmart.py b/additional_tests/exchanges_tests/test_bitmart.py
index f1eacab34..58146e68a 100644
--- a/additional_tests/exchanges_tests/test_bitmart.py
+++ b/additional_tests/exchanges_tests/test_bitmart.py
@@ -63,6 +63,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_bybit.py b/additional_tests/exchanges_tests/test_bybit.py
index 1eb4dde3c..6bce71e67 100644
--- a/additional_tests/exchanges_tests/test_bybit.py
+++ b/additional_tests/exchanges_tests/test_bybit.py
@@ -67,6 +67,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_bybit_futures.py b/additional_tests/exchanges_tests/test_bybit_futures.py
index 950b07c0b..98678b74c 100644
--- a/additional_tests/exchanges_tests/test_bybit_futures.py
+++ b/additional_tests/exchanges_tests/test_bybit_futures.py
@@ -79,6 +79,9 @@ async def test_get_and_set_leverage(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_coinbase.py b/additional_tests/exchanges_tests/test_coinbase.py
index 7b3790f06..6176f14f8 100644
--- a/additional_tests/exchanges_tests/test_coinbase.py
+++ b/additional_tests/exchanges_tests/test_coinbase.py
@@ -15,6 +15,7 @@
# License along with OctoBot. If not, see .
import pytest
+import octobot_trading.enums
from additional_tests.exchanges_tests import abstract_authenticated_exchange_tester
# All test coroutines will be treated as marked.
@@ -29,7 +30,7 @@ class TestCoinbaseAuthenticatedExchange(
ORDER_CURRENCY = "ADA"
SETTLEMENT_CURRENCY = "BTC"
SYMBOL = f"{ORDER_CURRENCY}/{SETTLEMENT_CURRENCY}"
- ORDER_SIZE = 5 # % of portfolio to include in test orders
+ ORDER_SIZE = 25 # % of portfolio to include in test orders
CONVERTS_ORDER_SIZE_BEFORE_PUSHING_TO_EXCHANGES = True
VALID_ORDER_ID = "8bb80a81-27f7-4415-aa50-911ea46d841c"
USE_ORDER_OPERATION_TO_CHECK_API_KEY_RIGHTS = True # set True when api key rights can't be checked using a
@@ -37,6 +38,34 @@ class TestCoinbaseAuthenticatedExchange(
IS_BROKER_ENABLED_ACCOUNT = False
IS_AUTHENTICATED_REQUEST_CHECK_AVAILABLE = True # set True when is_authenticated_request is implemented
+ SPECIAL_ORDER_TYPES_BY_EXCHANGE_ID: dict[
+ str, (
+ str, # symbol
+ str, # order type key in 'info' dict
+ str, # order type found in 'info' dict
+ str, # parsed trading_enums.TradeOrderType
+ str, # parsed trading_enums.TradeOrderSide
+ bool, # trigger above (on higher price than order price)
+ )
+ ] = {
+ '7e03c745-7340-49ef-8af1-b8f7fe431c8a': (
+ "BTC/EUR", "order_type", "STOP_LIMIT", # sell at a lower price
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.SELL.value, False
+ ),
+ '1e2f0918-5728-4c68-b8f4-6fd804396248': (
+ "ETH/BTC", "order_type", "STOP_LIMIT", # buy at a higher price
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.BUY.value, True
+ ),
+ 'f4016e50-1f0b-4caa-abe5-1ec00af18be9': (
+ "ETH/BTC", "order_type", "STOP_LIMIT", # buy at a lower price
+ octobot_trading.enums.TradeOrderType.LIMIT.value, octobot_trading.enums.TradeOrderSide.BUY.value, False
+ ),
+ } # stop loss / take profit and other special order types to be successfully parsed
+ # details of an order that exists but can"t be cancelled
+ UNCANCELLABLE_ORDER_ID_SYMBOL_TYPE: tuple[str, str, octobot_trading.enums.TraderOrderType] = (
+ "f4016e50-1f0b-4caa-abe5-1ec00af18be9", "ETH/BTC", octobot_trading.enums.TraderOrderType.BUY_LIMIT.value
+ )
+
async def test_get_portfolio(self):
await super().test_get_portfolio()
@@ -64,6 +93,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_coinex.py b/additional_tests/exchanges_tests/test_coinex.py
index d4fa0a9c1..3df57e583 100644
--- a/additional_tests/exchanges_tests/test_coinex.py
+++ b/additional_tests/exchanges_tests/test_coinex.py
@@ -62,6 +62,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_cryptocom.py b/additional_tests/exchanges_tests/test_cryptocom.py
index 7963937b9..efbbe4994 100644
--- a/additional_tests/exchanges_tests/test_cryptocom.py
+++ b/additional_tests/exchanges_tests/test_cryptocom.py
@@ -63,6 +63,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_gateio.py b/additional_tests/exchanges_tests/test_gateio.py
index d5cf41b7e..c9abe4f3b 100644
--- a/additional_tests/exchanges_tests/test_gateio.py
+++ b/additional_tests/exchanges_tests/test_gateio.py
@@ -65,6 +65,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_hollaex.py b/additional_tests/exchanges_tests/test_hollaex.py
index be63620b9..c2e9c3027 100644
--- a/additional_tests/exchanges_tests/test_hollaex.py
+++ b/additional_tests/exchanges_tests/test_hollaex.py
@@ -65,6 +65,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_htx.py b/additional_tests/exchanges_tests/test_htx.py
index a22c17725..ab2f68b44 100644
--- a/additional_tests/exchanges_tests/test_htx.py
+++ b/additional_tests/exchanges_tests/test_htx.py
@@ -64,6 +64,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_kucoin.py b/additional_tests/exchanges_tests/test_kucoin.py
index ef48cfbe7..a42eb3f55 100644
--- a/additional_tests/exchanges_tests/test_kucoin.py
+++ b/additional_tests/exchanges_tests/test_kucoin.py
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public
# License along with OctoBot. If not, see .
import pytest
+import octobot_trading.enums
from additional_tests.exchanges_tests import abstract_authenticated_exchange_tester
@@ -36,6 +37,38 @@ class TestKucoinAuthenticatedExchange(
VALID_ORDER_ID = "6617e84c5c1e0000083c71f7"
IS_AUTHENTICATED_REQUEST_CHECK_AVAILABLE = True # set True when is_authenticated_request is implemented
+ SPECIAL_ORDER_TYPES_BY_EXCHANGE_ID: dict[
+ str, (
+ str, # symbol
+ str, # order type key in 'info' dict
+ str, # order type found in 'info' dict
+ str, # parsed trading_enums.TradeOrderType
+ str, # parsed trading_enums.TradeOrderSide
+ bool, # trigger above (on higher price than order price)
+ )
+ ] = {
+ "vs93gpruc6ikekiv003o48ci": (
+ "BTC/USDT", "type", "market",
+ octobot_trading.enums.TradeOrderType.LIMIT.value, octobot_trading.enums.TradeOrderSide.BUY.value, False
+ ),
+ 'vs93gpruc6n45taf003lr546': (
+ "BTC/USDT", "type", "market",
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.BUY.value, True
+ ),
+ 'vs93gpruc69s00cs003tat0g': (
+ "BTC/USDT", "type", "limit",
+ octobot_trading.enums.TradeOrderType.LIMIT.value, octobot_trading.enums.TradeOrderSide.BUY.value, False
+ ),
+ 'vs93gpruc5q45taf003lr545': (
+ "BTC/USDT", "type", "limit",
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.BUY.value, True
+ ),
+ } # stop loss / take profit and other special order types to be successfully parsed
+ # details of an order that exists but can"t be cancelled
+ UNCANCELLABLE_ORDER_ID_SYMBOL_TYPE: tuple[str, str, octobot_trading.enums.TraderOrderType] = (
+ "vs93gpruc69s00cs003tat0g", "BTC/USDT", octobot_trading.enums.TraderOrderType.BUY_LIMIT.value
+ )
+
async def test_get_portfolio(self):
await super().test_get_portfolio()
@@ -45,6 +78,9 @@ async def test_get_portfolio_with_market_filter(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_kucoin_futures.py b/additional_tests/exchanges_tests/test_kucoin_futures.py
index ef6ed0e18..d6a9f5a65 100644
--- a/additional_tests/exchanges_tests/test_kucoin_futures.py
+++ b/additional_tests/exchanges_tests/test_kucoin_futures.py
@@ -15,6 +15,7 @@
# License along with OctoBot. If not, see .
import pytest
+import octobot_trading.enums
from additional_tests.exchanges_tests import abstract_authenticated_future_exchange_tester
# All test coroutines will be treated as marked.
@@ -40,6 +41,38 @@ class TestKucoinFuturesAuthenticatedExchange(
EXPECTED_QUOTE_MIN_ORDER_SIZE = 40
EXPECT_BALANCE_FILTER_BY_MARKET_STATUS = True
+ SPECIAL_ORDER_TYPES_BY_EXCHANGE_ID: dict[
+ str, (
+ str, # symbol
+ str, # order type key in 'info' dict
+ str, # order type found in 'info' dict
+ str, # parsed trading_enums.TradeOrderType
+ str, # parsed trading_enums.TradeOrderSide
+ bool, # trigger above (on higher price than order price)
+ )
+ ] = {
+ "266424660906831872": (
+ "ETH/USDT:USDT", "type", "market",
+ octobot_trading.enums.TradeOrderType.LIMIT.value, octobot_trading.enums.TradeOrderSide.BUY.value, False
+ ),
+ '266424746172764160': (
+ "ETH/USDT:USDT", "type", "market",
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.SELL.value, False
+ ),
+ '266424798085668865': (
+ "ETH/USDT:USDT", "type", "limit",
+ octobot_trading.enums.TradeOrderType.LIMIT.value, octobot_trading.enums.TradeOrderSide.BUY.value, False
+ ),
+ '266424826044899328': (
+ "ETH/USDT:USDT", "type", "limit",
+ octobot_trading.enums.TradeOrderType.STOP_LOSS.value, octobot_trading.enums.TradeOrderSide.SELL.value, False
+ ),
+ } # stop loss / take profit and other special order types to be successfully parsed
+ # details of an order that exists but can"t be cancelled
+ UNCANCELLABLE_ORDER_ID_SYMBOL_TYPE: tuple[str, str, octobot_trading.enums.TraderOrderType] = (
+ "266424798085668865", "ETH/USDT:USDT", octobot_trading.enums.TraderOrderType.BUY_LIMIT.value
+ )
+
async def test_get_portfolio(self):
await super().test_get_portfolio()
@@ -76,6 +109,9 @@ async def test_get_and_set_leverage(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
# todo test cross position order creation (kucoin param) at next ccxt update (will support set margin type)
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_mexc.py b/additional_tests/exchanges_tests/test_mexc.py
index a47659703..e14b77102 100644
--- a/additional_tests/exchanges_tests/test_mexc.py
+++ b/additional_tests/exchanges_tests/test_mexc.py
@@ -64,6 +64,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_okx.py b/additional_tests/exchanges_tests/test_okx.py
index 632cceaf2..0737b6fa1 100644
--- a/additional_tests/exchanges_tests/test_okx.py
+++ b/additional_tests/exchanges_tests/test_okx.py
@@ -45,6 +45,9 @@ async def test_get_portfolio_with_market_filter(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_okx_futures.py b/additional_tests/exchanges_tests/test_okx_futures.py
index 656efb7a9..104969d42 100644
--- a/additional_tests/exchanges_tests/test_okx_futures.py
+++ b/additional_tests/exchanges_tests/test_okx_futures.py
@@ -73,6 +73,9 @@ async def test_get_and_set_leverage(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/additional_tests/exchanges_tests/test_phemex.py b/additional_tests/exchanges_tests/test_phemex.py
index b570e3bd8..83fbce10c 100644
--- a/additional_tests/exchanges_tests/test_phemex.py
+++ b/additional_tests/exchanges_tests/test_phemex.py
@@ -62,6 +62,9 @@ async def test_get_not_found_order(self):
async def test_is_valid_account(self):
await super().test_is_valid_account()
+ async def test_get_special_orders(self):
+ await super().test_get_special_orders()
+
async def test_create_and_cancel_limit_orders(self):
await super().test_create_and_cancel_limit_orders()
diff --git a/octobot/community/models/formatters.py b/octobot/community/models/formatters.py
index e7bff3f88..092029c14 100644
--- a/octobot/community/models/formatters.py
+++ b/octobot/community/models/formatters.py
@@ -123,6 +123,9 @@ def format_orders(orders: list, exchange_name: str) -> list:
trading_enums.ExchangeConstantsOrderColumns.AMOUNT.value],
backend_enums.OrderKeys.SIDE.value: storage_order[trading_constants.STORAGE_ORIGIN_VALUE][
trading_enums.ExchangeConstantsOrderColumns.SIDE.value],
+ backend_enums.OrderKeys.TRIGGER_ABOVE.value: storage_order[trading_constants.STORAGE_ORIGIN_VALUE].get(
+ trading_enums.ExchangeConstantsOrderColumns.TRIGGER_ABOVE.value
+ ),
backend_enums.OrderKeys.EXCHANGE_ID.value: storage_order[trading_constants.STORAGE_ORIGIN_VALUE][
trading_enums.ExchangeConstantsOrderColumns.EXCHANGE_ID.value],
backend_enums.OrderKeys.CHAINED.value: format_orders(
diff --git a/octobot/community/supabase_backend/enums.py b/octobot/community/supabase_backend/enums.py
index 2c7dcfa84..c421db22c 100644
--- a/octobot/community/supabase_backend/enums.py
+++ b/octobot/community/supabase_backend/enums.py
@@ -173,6 +173,7 @@ class OrderKeys(enum.Enum):
TYPE = "type"
CHAINED = "chained"
SIDE = "side"
+ TRIGGER_ABOVE = "trigger_above"
class PositionKeys(enum.Enum):
diff --git a/requirements.txt b/requirements.txt
index eca2c3d86..476a90d05 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
# Drakkar-Software requirements
OctoBot-Commons==1.9.70
-OctoBot-Trading==2.4.142
+OctoBot-Trading==2.4.143
OctoBot-Evaluators==1.9.7
OctoBot-Tentacles-Manager==2.9.16
OctoBot-Services==1.6.21