Skip to content

Commit aca7791

Browse files
committed
[Exchanges] update to ccxt 4.5.28
1 parent 5792417 commit aca7791

File tree

8 files changed

+148
-42
lines changed

8 files changed

+148
-42
lines changed

Trading/Exchange/binance/binance_exchange.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,31 @@ def get_order_additional_params(self, order) -> dict:
286286
params["reduceOnly"] = order.reduce_only
287287
return params
288288

289+
def _order_request_kwargs_factory(
290+
self,
291+
exchange_order_id: str,
292+
order_type: typing.Optional[trading_enums.TraderOrderType] = None,
293+
**kwargs
294+
) -> dict:
295+
params = kwargs or {}
296+
try:
297+
if "stop" not in params:
298+
order_type = (
299+
order_type or
300+
self.exchange_manager.exchange_personal_data.orders_manager.get_order(
301+
None, exchange_order_id=exchange_order_id
302+
).order_type
303+
)
304+
params["stop"] = (
305+
personal_data.is_stop_order(order_type)
306+
or personal_data.is_take_profit_order(order_type)
307+
)
308+
except KeyError as err:
309+
self.logger.warning(
310+
f"Order {exchange_order_id} not found in order manager: considering it a regular (no stop/take profit) order {err}"
311+
)
312+
return params
313+
289314
async def _create_market_sell_order(
290315
self, symbol, quantity, price=None, reduce_only: bool = False, params=None
291316
) -> dict:
@@ -365,11 +390,12 @@ def fix_order(self, raw, symbol=None, **kwargs):
365390
return fixed
366391

367392
def _adapt_order_type(self, fixed):
368-
if order_type := fixed.get(ccxt_enums.ExchangeOrderCCXTColumns.TYPE.value, None):
369-
is_stop = order_type.lower() in self.STOP_ORDERS
370-
is_tp = order_type.lower() in self.TAKE_PROFITS_ORDERS
371-
if is_stop or is_tp:
372-
stop_price = fixed.get(ccxt_enums.ExchangeOrderCCXTColumns.STOP_PRICE.value, None)
393+
order_info = fixed.get(ccxt_enums.ExchangeOrderCCXTColumns.INFO.value, {})
394+
info_order_type = (order_info.get("type", {}) or order_info.get("orderType", None) or "").lower()
395+
is_stop = info_order_type in self.STOP_ORDERS
396+
is_tp = info_order_type in self.TAKE_PROFITS_ORDERS
397+
if is_stop or is_tp:
398+
if trigger_price := fixed.get(ccxt_enums.ExchangeOrderCCXTColumns.TRIGGER_PRICE.value, None):
373399
selling = (
374400
fixed.get(ccxt_enums.ExchangeOrderCCXTColumns.SIDE.value, None)
375401
== trading_enums.TradeOrderSide.SELL.value
@@ -378,13 +404,15 @@ def _adapt_order_type(self, fixed):
378404
trigger_above = False
379405
if is_stop:
380406
updated_type = trading_enums.TradeOrderType.STOP_LOSS.value
407+
# force price to trigger price
408+
fixed[trading_enums.ExchangeConstantsOrderColumns.PRICE.value] = trigger_price
381409
trigger_above = not selling # sell stop loss triggers when price is lower than target
382410
elif is_tp:
383411
# updated_type = trading_enums.TradeOrderType.TAKE_PROFIT.value
384412
# take profits are not yet handled as such: consider them as limit orders
385413
updated_type = trading_enums.TradeOrderType.LIMIT.value # waiting for TP handling
386414
if not fixed[trading_enums.ExchangeConstantsOrderColumns.PRICE.value]:
387-
fixed[trading_enums.ExchangeConstantsOrderColumns.PRICE.value] = stop_price # waiting for TP handling
415+
fixed[trading_enums.ExchangeConstantsOrderColumns.PRICE.value] = trigger_price # waiting for TP handling
388416
trigger_above = selling # sell take profit triggers when price is higher than target
389417
else:
390418
self.logger.error(
@@ -393,6 +421,11 @@ def _adapt_order_type(self, fixed):
393421
# stop loss and take profits are not tagged as such by ccxt, force it
394422
fixed[trading_enums.ExchangeConstantsOrderColumns.TYPE.value] = updated_type
395423
fixed[trading_enums.ExchangeConstantsOrderColumns.TRIGGER_ABOVE.value] = trigger_above
424+
else:
425+
self.logger.error(
426+
f"Unknown [{self.connector.exchange_manager.exchange_name}] order: stop order "
427+
f"with no trigger price, order: {fixed}"
428+
)
396429
return fixed
397430

398431
def fix_trades(self, raw, **kwargs):

Trading/Exchange/bitmart/bitmart_exchange.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
# You should have received a copy of the GNU Lesser General Public
1515
# License along with this library.
1616
import typing
17+
import ccxt.async_support
18+
1719
import octobot_trading.exchanges as exchanges
1820
import octobot_trading.exchanges.connectors.ccxt.constants as ccxt_constants
1921
import octobot_trading.enums as trading_enums
@@ -27,7 +29,43 @@ def _client_factory(
2729
force_unauth,
2830
keys_adapter: typing.Callable[[exchanges.ExchangeCredentialsData], exchanges.ExchangeCredentialsData]=None
2931
) -> tuple:
30-
return super()._client_factory(force_unauth, keys_adapter=self._keys_adapter)
32+
client, is_authenticated = super()._client_factory(force_unauth, keys_adapter=self._keys_adapter)
33+
if client:
34+
client.handle_errors = self._patched_handle_errors_factory(client)
35+
return client, is_authenticated
36+
37+
def _patched_handle_errors_factory(self, client: ccxt.async_support.Exchange):
38+
self = client # set self to the client to use the client methods
39+
def _patched_handle_errors(code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
40+
# temporary patch waiting for CCXT fix (issue in ccxt 4.5.28)
41+
if response is None:
42+
return None
43+
#
44+
# spot
45+
#
46+
# {"message":"Bad Request [to is empty]","code":50000,"trace":"f9d46e1b-4edb-4d07-a06e-4895fb2fc8fc","data":{}}
47+
# {"message":"Bad Request [from is empty]","code":50000,"trace":"579986f7-c93a-4559-926b-06ba9fa79d76","data":{}}
48+
# {"message":"Kline size over 500","code":50004,"trace":"d625caa8-e8ca-4bd2-b77c-958776965819","data":{}}
49+
# {"message":"Balance not enough","code":50020,"trace":"7c709d6a-3292-462c-98c5-32362540aeef","data":{}}
50+
# {"code":40012,"message":"You contract account available balance not enough.","trace":"..."}
51+
#
52+
# contract
53+
#
54+
# {"errno":"OK","message":"INVALID_PARAMETER","code":49998,"trace":"eb5ebb54-23cd-4de2-9064-e090b6c3b2e3","data":null}
55+
#
56+
message = self.safe_string_lower(response, 'message') # PATCH
57+
isErrorMessage = (message is not None) and (message != 'ok') and (message != 'success')
58+
errorCode = self.safe_string(response, 'code')
59+
isErrorCode = (errorCode is not None) and (errorCode != '1000')
60+
if isErrorCode or isErrorMessage:
61+
feedback = self.id + ' ' + body
62+
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
63+
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
64+
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
65+
self.throw_broadly_matched_exception(self.exceptions['broad'], errorCode, feedback)
66+
raise ccxt.ExchangeError(feedback) # unknown message
67+
return None
68+
return _patched_handle_errors
3169

3270
def _keys_adapter(self, creds: exchanges.ExchangeCredentialsData) -> exchanges.ExchangeCredentialsData:
3371
# use password as uid

Trading/Exchange/bybit/bybit_exchange.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,13 @@ async def get_open_orders(self, symbol: str = None, since: int = None,
168168
orders += await super().get_open_orders(symbol=symbol, since=since, limit=limit, **kwargs)
169169
return orders
170170

171-
async def get_order(self, exchange_order_id: str, symbol: str = None, **kwargs: dict) -> dict:
171+
async def get_order(
172+
self,
173+
exchange_order_id: str,
174+
symbol: typing.Optional[str] = None,
175+
order_type: typing.Optional[trading_enums.TraderOrderType] = None,
176+
**kwargs: dict
177+
) -> dict:
172178
# regular get order is not supported
173179
return await self.get_order_from_open_and_closed_orders(exchange_order_id, symbol=symbol, **kwargs)
174180

Trading/Exchange/coinbase/coinbase_exchange.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,9 +496,15 @@ async def get_open_orders(self, symbol=None, since=None, limit=None, **kwargs) -
496496
return await super().get_open_orders(symbol=symbol, since=since, limit=limit, **kwargs)
497497

498498
@_coinbase_retrier
499-
async def get_order(self, exchange_order_id: str, symbol: str = None, **kwargs: dict) -> dict:
499+
async def get_order(
500+
self,
501+
exchange_order_id: str,
502+
symbol: typing.Optional[str] = None,
503+
order_type: typing.Optional[trading_enums.TraderOrderType] = None,
504+
**kwargs: dict
505+
) -> dict:
500506
# override for retrier
501-
return await super().get_order(exchange_order_id, symbol=symbol, **kwargs)
507+
return await super().get_order(exchange_order_id, symbol=symbol, order_type=order_type, **kwargs)
502508

503509
async def _create_market_stop_loss_order(self, symbol, quantity, price, side, current_price, params=None) -> dict:
504510
# warning coinbase only supports stop limit orders, stop markets are not available

Trading/Exchange/kucoin/kucoin_exchange.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,8 +408,14 @@ async def get_open_orders(self, symbol=None, since=None, limit=None, **kwargs) -
408408
return regular_orders + stop_orders
409409

410410
@_kucoin_retrier
411-
async def get_order(self, exchange_order_id: str, symbol: str = None, **kwargs: dict) -> dict:
412-
return await super().get_order(exchange_order_id, symbol=symbol, **kwargs)
411+
async def get_order(
412+
self,
413+
exchange_order_id: str,
414+
symbol: typing.Optional[str] = None,
415+
order_type: typing.Optional[trading_enums.TraderOrderType] = None,
416+
**kwargs: dict
417+
) -> dict:
418+
return await super().get_order(exchange_order_id, symbol=symbol, order_type=order_type, **kwargs)
413419

414420
async def create_order(self, order_type: trading_enums.TraderOrderType, symbol: str, quantity: decimal.Decimal,
415421
price: decimal.Decimal = None, stop_price: decimal.Decimal = None,

Trading/Exchange/mexc/mexc_exchange.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,16 @@ async def get_closed_orders(self, symbol: str = None, since: int = None, limit:
245245
False
246246
)
247247

248-
async def get_order(self, exchange_order_id: str, symbol: str = None, **kwargs: dict) -> dict:
248+
async def get_order(
249+
self,
250+
exchange_order_id: str,
251+
symbol: typing.Optional[str] = None,
252+
order_type: typing.Optional[trading_enums.TraderOrderType] = None,
253+
**kwargs: dict
254+
) -> dict:
249255
try:
250256
return await super().get_order(
251-
exchange_order_id, symbol=symbol, **kwargs
257+
exchange_order_id, symbol=symbol, order_type=order_type, **kwargs
252258
)
253259
except octobot_trading.errors.FailedRequest as err:
254260
if "Order does not exist" in str(err):

Trading/Exchange/okx/okx_exchange.py

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,17 @@ async def get_closed_orders(self, symbol=None, since=None, limit=None, **kwargs)
294294
super().get_closed_orders, symbol=symbol, since=since, limit=limit, **kwargs
295295
)
296296

297-
async def get_order(self, exchange_order_id: str, symbol: str = None, **kwargs: dict) -> dict:
297+
async def get_order(
298+
self,
299+
exchange_order_id: str,
300+
symbol: typing.Optional[str] = None,
301+
order_type: typing.Optional[trading_enums.TraderOrderType] = None,
302+
**kwargs: dict
303+
) -> dict:
298304
try:
299-
kwargs = self._get_okx_order_params(exchange_order_id, **kwargs)
300-
order = await super().get_order(exchange_order_id, symbol=symbol, **kwargs)
305+
order = await super().get_order(
306+
exchange_order_id, symbol=symbol, order_type=order_type, **kwargs
307+
)
301308
return order
302309
except trading_errors.NotSupported:
303310
if kwargs.get("stop", False):
@@ -306,35 +313,31 @@ async def get_order(self, exchange_order_id: str, symbol: str = None, **kwargs:
306313
return await self.get_order_from_open_and_closed_orders(exchange_order_id, symbol=symbol, **kwargs)
307314
raise
308315

309-
async def cancel_order(
310-
self, exchange_order_id: str, symbol: str, order_type: trading_enums.TraderOrderType, **kwargs: dict
311-
) -> trading_enums.OrderStatus:
312-
return await super().cancel_order(
313-
exchange_order_id, symbol, order_type, **self._get_okx_order_params(exchange_order_id, order_type, **kwargs)
314-
)
315-
316-
def _get_okx_order_params(self, exchange_order_id, order_type=None, **kwargs):
316+
def _order_request_kwargs_factory(
317+
self,
318+
exchange_order_id: str,
319+
order_type: typing.Optional[trading_enums.TraderOrderType] = None,
320+
**kwargs
321+
) -> dict:
317322
params = kwargs or {}
318323
try:
319324
if "stop" not in params:
320-
order_type = order_type or \
321-
self.exchange_manager.exchange_personal_data.orders_manager.get_order(
322-
None, exchange_order_id=exchange_order_id
323-
).order_type
324-
params["stop"] = trading_personal_data.is_stop_order(order_type) \
325+
order_type = (
326+
order_type or
327+
self.exchange_manager.exchange_personal_data.orders_manager.get_order(
328+
None, exchange_order_id=exchange_order_id
329+
).order_type
330+
)
331+
params["stop"] = (
332+
trading_personal_data.is_stop_order(order_type)
325333
or trading_personal_data.is_take_profit_order(order_type)
326-
except KeyError:
327-
pass
334+
)
335+
except KeyError as err:
336+
self.logger.warning(
337+
f"Order {exchange_order_id} not found in order manager: considering it a regular (no stop/take profit) order {err}"
338+
)
328339
return params
329340

330-
async def _verify_order(self, created_order, order_type, symbol, price, quantity, side, get_order_params=None):
331-
332-
if trading_personal_data.is_stop_order(order_type) or trading_personal_data.is_take_profit_order(order_type):
333-
get_order_params = get_order_params or {}
334-
get_order_params["stop"] = True
335-
return await super()._verify_order(created_order, order_type, symbol, price, quantity, side,
336-
get_order_params=get_order_params)
337-
338341
def _is_oco_order(self, params):
339342
return all(
340343
oco_order_param in (params or {})

Trading/Exchange/phemex/phemex_exchange.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,16 @@ async def cancel_order(
8686
order_status = trading_enums.OrderStatus.CANCELED
8787
return order_status
8888

89-
async def get_order(self, exchange_order_id: str, symbol: str = None, **kwargs: dict) -> dict:
90-
if order := await self.connector.get_order(symbol=symbol, exchange_order_id=exchange_order_id, **kwargs):
89+
async def get_order(
90+
self,
91+
exchange_order_id: str,
92+
symbol: typing.Optional[str] = None,
93+
order_type: typing.Optional[trading_enums.TraderOrderType] = None,
94+
**kwargs: dict
95+
) -> dict:
96+
if order := await self.connector.get_order(
97+
symbol=symbol, exchange_order_id=exchange_order_id, order_type=order_type, **kwargs
98+
):
9199
return order
92100
# try from closed orders (get_order is not returning filled or cancelled orders)
93101
if order := await self.get_order_from_open_and_closed_orders(exchange_order_id, symbol):

0 commit comments

Comments
 (0)