ccxt 4.2.95__py2.py3-none-any.whl → 4.2.97__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ccxt/deribit.py CHANGED
@@ -1184,13 +1184,17 @@ class deribit(Exchange, ImplicitAPI):
1184
1184
  """
1185
1185
  fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
1186
1186
  :see: https://docs.deribit.com/#public-get_book_summary_by_currency
1187
- :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
1187
+ :param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
1188
1188
  :param dict [params]: extra parameters specific to the exchange API endpoint
1189
+ :param str [params.code]: *required* the currency code to fetch the tickers for, eg. 'BTC', 'ETH'
1189
1190
  :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
1190
1191
  """
1191
1192
  self.load_markets()
1192
1193
  symbols = self.market_symbols(symbols)
1193
- code = self.code_from_options('fetchTickers', params)
1194
+ code = self.safe_string_2(params, 'code', 'currency')
1195
+ params = self.omit(params, ['code'])
1196
+ if code is None:
1197
+ raise ArgumentsRequired(self.id + ' fetchTickers requires a currency/code(eg: BTC/ETH/USDT) parameter to fetch tickers for')
1194
1198
  currency = self.currency(code)
1195
1199
  request = {
1196
1200
  'currency': currency['id'],
@@ -1226,7 +1230,7 @@ class deribit(Exchange, ImplicitAPI):
1226
1230
  # "testnet": False
1227
1231
  # }
1228
1232
  #
1229
- result = self.safe_value(response, 'result', [])
1233
+ result = self.safe_list(response, 'result', [])
1230
1234
  tickers = {}
1231
1235
  for i in range(0, len(result)):
1232
1236
  ticker = self.parse_ticker(result[i])
ccxt/gemini.py CHANGED
@@ -299,7 +299,13 @@ class gemini(Exchange, ImplicitAPI):
299
299
  'ATOM': 'cosmos',
300
300
  'DOT': 'polkadot',
301
301
  },
302
- 'nonce': 'milliseconds', # if getting a Network 400 error change to seconds
302
+ 'nonce': 'milliseconds', # if getting a Network 400 error change to seconds,
303
+ 'conflictingMarkets': {
304
+ 'paxgusd': {
305
+ 'base': 'PAXG',
306
+ 'quote': 'USD',
307
+ },
308
+ },
303
309
  },
304
310
  })
305
311
 
@@ -661,16 +667,25 @@ class gemini(Exchange, ImplicitAPI):
661
667
  marketIdUpper = marketId.upper()
662
668
  isPerp = (marketIdUpper.find('PERP') >= 0)
663
669
  marketIdWithoutPerp = marketIdUpper.replace('PERP', '')
664
- quoteQurrencies = self.handle_option('fetchMarketsFromAPI', 'quoteCurrencies', [])
665
- for i in range(0, len(quoteQurrencies)):
666
- quoteCurrency = quoteQurrencies[i]
667
- if marketIdWithoutPerp.endswith(quoteCurrency):
668
- quoteLength = self.parse_to_int(-1 * len(quoteCurrency))
669
- baseId = marketIdWithoutPerp[0:quoteLength]
670
- quoteId = quoteCurrency
671
- if isPerp:
672
- settleId = quoteCurrency # always same
673
- break
670
+ conflictingMarkets = self.safe_dict(self.options, 'conflictingMarkets', {})
671
+ lowerCaseId = marketIdWithoutPerp.lower()
672
+ if lowerCaseId in conflictingMarkets:
673
+ conflictingMarket = conflictingMarkets[lowerCaseId]
674
+ baseId = conflictingMarket['base']
675
+ quoteId = conflictingMarket['quote']
676
+ if isPerp:
677
+ settleId = conflictingMarket['quote']
678
+ else:
679
+ quoteCurrencies = self.handle_option('fetchMarketsFromAPI', 'quoteCurrencies', [])
680
+ for i in range(0, len(quoteCurrencies)):
681
+ quoteCurrency = quoteCurrencies[i]
682
+ if marketIdWithoutPerp.endswith(quoteCurrency):
683
+ quoteLength = self.parse_to_int(-1 * len(quoteCurrency))
684
+ baseId = marketIdWithoutPerp[0:quoteLength]
685
+ quoteId = quoteCurrency
686
+ if isPerp:
687
+ settleId = quoteCurrency # always same
688
+ break
674
689
  base = self.safe_currency_code(baseId)
675
690
  quote = self.safe_currency_code(quoteId)
676
691
  settle = self.safe_currency_code(settleId)
ccxt/okx.py CHANGED
@@ -8,6 +8,7 @@ from ccxt.abstract.okx import ImplicitAPI
8
8
  import hashlib
9
9
  from ccxt.base.types import Account, Balances, Conversion, Currencies, Currency, Greeks, Int, Leverage, MarginModification, Market, MarketInterface, Num, Option, OptionChain, Order, OrderBook, OrderRequest, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, Transaction, TransferEntry
10
10
  from typing import List
11
+ from typing import Any
11
12
  from ccxt.base.errors import ExchangeError
12
13
  from ccxt.base.errors import AuthenticationError
13
14
  from ccxt.base.errors import PermissionDenied
@@ -1129,13 +1130,13 @@ class okx(Exchange, ImplicitAPI):
1129
1130
  },
1130
1131
  })
1131
1132
 
1132
- def handle_market_type_and_params(self, methodName, market=None, params={}):
1133
+ def handle_market_type_and_params(self, methodName: str, market: Market = None, params={}, defaultValue=None) -> Any:
1133
1134
  instType = self.safe_string(params, 'instType')
1134
1135
  params = self.omit(params, 'instType')
1135
1136
  type = self.safe_string(params, 'type')
1136
1137
  if (type is None) and (instType is not None):
1137
1138
  params['type'] = instType
1138
- return super(okx, self).handle_market_type_and_params(methodName, market, params)
1139
+ return super(okx, self).handle_market_type_and_params(methodName, market, params, defaultValue)
1139
1140
 
1140
1141
  def convert_to_instrument_type(self, type):
1141
1142
  exchangeTypes = self.safe_dict(self.options, 'exchangeType', {})
ccxt/poloniexfutures.py CHANGED
@@ -382,7 +382,10 @@ class poloniexfutures(Exchange, ImplicitAPI):
382
382
  #
383
383
  marketId = self.safe_string(ticker, 'symbol')
384
384
  symbol = self.safe_symbol(marketId, market)
385
- timestamp = self.safe_integer_product(ticker, 'ts', 0.000001)
385
+ timestampString = self.safe_string(ticker, 'ts')
386
+ # check timestamp bcz bug: https://app.travis-ci.com/github/ccxt/ccxt/builds/269959181#L4011
387
+ multiplier = 0.00001 if (len(timestampString) == 18) else 0.000001
388
+ timestamp = self.safe_integer_product(ticker, 'ts', multiplier)
386
389
  last = self.safe_string_2(ticker, 'price', 'lastPrice')
387
390
  percentage = Precise.string_mul(self.safe_string(ticker, 'priceChgPct'), '100')
388
391
  return self.safe_ticker({
ccxt/pro/__init__.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # ----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.2.95'
7
+ __version__ = '4.2.97'
8
8
 
9
9
  # ----------------------------------------------------------------------------
10
10
 
ccxt/pro/binance.py CHANGED
@@ -9,10 +9,10 @@ import hashlib
9
9
  from ccxt.base.types import Balances, Int, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, Trade
10
10
  from ccxt.async_support.base.ws.client import Client
11
11
  from typing import List
12
- from ccxt.base.errors import ExchangeError
13
12
  from ccxt.base.errors import ArgumentsRequired
14
13
  from ccxt.base.errors import BadRequest
15
14
  from ccxt.base.errors import NotSupported
15
+ from ccxt.base.errors import InvalidNonce
16
16
  from ccxt.base.precise import Precise
17
17
 
18
18
 
@@ -400,8 +400,10 @@ class binance(ccxt.async_support.binance):
400
400
  if nonce < orderbook['nonce']:
401
401
  client.resolve(orderbook, messageHash)
402
402
  else:
403
- # todo: client.reject from handleOrderBookMessage properly
404
- raise ExchangeError(self.id + ' handleOrderBook received an out-of-order nonce')
403
+ checksum = self.safe_bool(self.options, 'checksum', True)
404
+ if checksum:
405
+ # todo: client.reject from handleOrderBookMessage properly
406
+ raise InvalidNonce(self.id + ' handleOrderBook received an out-of-order nonce')
405
407
  else:
406
408
  # future
407
409
  # 4. Drop any event where u is < lastUpdateId in the snapshot
@@ -413,8 +415,10 @@ class binance(ccxt.async_support.binance):
413
415
  if nonce <= orderbook['nonce']:
414
416
  client.resolve(orderbook, messageHash)
415
417
  else:
416
- # todo: client.reject from handleOrderBookMessage properly
417
- raise ExchangeError(self.id + ' handleOrderBook received an out-of-order nonce')
418
+ checksum = self.safe_bool(self.options, 'checksum', True)
419
+ if checksum:
420
+ # todo: client.reject from handleOrderBookMessage properly
421
+ raise InvalidNonce(self.id + ' handleOrderBook received an out-of-order nonce')
418
422
  except Exception as e:
419
423
  del self.orderbooks[symbol]
420
424
  del client.subscriptions[messageHash]
ccxt/pro/coinbase.py CHANGED
@@ -136,6 +136,11 @@ class coinbase(ccxt.async_support.coinbase):
136
136
  # "low_52_w": "15460",
137
137
  # "high_52_w": "48240",
138
138
  # "price_percent_chg_24_h": "-4.15775596190603"
139
+ # new 2024-04-12
140
+ # "best_bid":"21835.29",
141
+ # "best_bid_quantity": "0.02000000",
142
+ # "best_ask":"23011.18",
143
+ # "best_ask_quantity": "0.01500000"
139
144
  # }
140
145
  # ]
141
146
  # }
@@ -161,6 +166,11 @@ class coinbase(ccxt.async_support.coinbase):
161
166
  # "low_52_w": "0.04908",
162
167
  # "high_52_w": "0.1801",
163
168
  # "price_percent_chg_24_h": "0.50177456859626"
169
+ # new 2024-04-12
170
+ # "best_bid":"0.07989",
171
+ # "best_bid_quantity": "500.0",
172
+ # "best_ask":"0.08308",
173
+ # "best_ask_quantity": "300.0"
164
174
  # }
165
175
  # ]
166
176
  # }
@@ -209,6 +219,11 @@ class coinbase(ccxt.async_support.coinbase):
209
219
  # "low_52_w": "0.04908",
210
220
  # "high_52_w": "0.1801",
211
221
  # "price_percent_chg_24_h": "0.50177456859626"
222
+ # new 2024-04-12
223
+ # "best_bid":"0.07989",
224
+ # "best_bid_quantity": "500.0",
225
+ # "best_ask":"0.08308",
226
+ # "best_ask_quantity": "300.0"
212
227
  # }
213
228
  #
214
229
  marketId = self.safe_string(ticker, 'product_id')
@@ -221,10 +236,10 @@ class coinbase(ccxt.async_support.coinbase):
221
236
  'datetime': self.iso8601(timestamp),
222
237
  'high': self.safe_string(ticker, 'high_24_h'),
223
238
  'low': self.safe_string(ticker, 'low_24_h'),
224
- 'bid': None,
225
- 'bidVolume': None,
226
- 'ask': None,
227
- 'askVolume': None,
239
+ 'bid': self.safe_string(ticker, 'best_bid'),
240
+ 'bidVolume': self.safe_string(ticker, 'best_bid_quantity'),
241
+ 'ask': self.safe_string(ticker, 'best_ask'),
242
+ 'askVolume': self.safe_string(ticker, 'best_ask_quantity'),
228
243
  'vwap': None,
229
244
  'open': None,
230
245
  'close': last,
@@ -8,9 +8,9 @@ from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById
8
8
  from ccxt.base.types import Balances, Int, Order, OrderBook, Str, Ticker, Trade
9
9
  from ccxt.async_support.base.ws.client import Client
10
10
  from typing import List
11
- from ccxt.base.errors import ExchangeError
12
11
  from ccxt.base.errors import AuthenticationError
13
12
  from ccxt.base.errors import BadRequest
13
+ from ccxt.base.errors import InvalidNonce
14
14
 
15
15
 
16
16
  class poloniexfutures(ccxt.async_support.poloniexfutures):
@@ -817,7 +817,10 @@ class poloniexfutures(ccxt.async_support.poloniexfutures):
817
817
  sequence = self.safe_integer(delta, 'sequence')
818
818
  nonce = self.safe_integer(orderbook, 'nonce')
819
819
  if nonce != sequence - 1:
820
- raise ExchangeError(self.id + ' watchOrderBook received an out-of-order nonce')
820
+ checksum = self.safe_bool(self.options, 'checksum', True)
821
+ if checksum:
822
+ # todo: client.reject from handleOrderBookMessage properly
823
+ raise InvalidNonce(self.id + ' watchOrderBook received an out-of-order nonce')
821
824
  change = self.safe_string(delta, 'change')
822
825
  splitChange = change.split(',')
823
826
  price = self.safe_number(splitChange, 0)
@@ -24,7 +24,9 @@ def test_order_book(exchange, skipped_properties, method, orderbook, symbol):
24
24
  'datetime': '2017-09-01T00:00:00',
25
25
  'nonce': 134234234,
26
26
  }
27
- empty_allowed_for = ['symbol', 'nonce', 'datetime', 'timestamp'] # todo: make timestamp required
27
+ empty_allowed_for = ['nonce']
28
+ # turn into copy: https://discord.com/channels/690203284119617602/921046068555313202/1220626834887282728
29
+ orderbook = exchange.deep_extend({}, orderbook)
28
30
  test_shared_methods.assert_structure(exchange, skipped_properties, method, orderbook, format, empty_allowed_for)
29
31
  test_shared_methods.assert_timestamp_and_datetime(exchange, skipped_properties, method, orderbook)
30
32
  test_shared_methods.assert_symbol(exchange, skipped_properties, method, orderbook, 'symbol', symbol)
@@ -37,24 +39,28 @@ def test_order_book(exchange, skipped_properties, method, orderbook, symbol):
37
39
  bids_length = len(bids)
38
40
  for i in range(0, bids_length):
39
41
  current_bid_string = exchange.safe_string(bids[i], 0)
40
- next_i = i + 1
41
- if bids_length > next_i:
42
- next_bid_string = exchange.safe_string(bids[next_i], 0)
43
- has_correct_order = Precise.string_gt(current_bid_string, next_bid_string)
44
- assert has_correct_order, 'current bid should be > than the next one: ' + current_bid_string + '>' + next_bid_string + log_text
45
- test_shared_methods.assert_greater(exchange, skipped_properties, method, bids[i], 0, '0')
46
- test_shared_methods.assert_greater(exchange, skipped_properties, method, bids[i], 1, '0')
42
+ if not ('compareToNextItem' in skipped_properties):
43
+ next_i = i + 1
44
+ if bids_length > next_i:
45
+ next_bid_string = exchange.safe_string(bids[next_i], 0)
46
+ assert Precise.string_gt(current_bid_string, next_bid_string), 'current bid should be > than the next one: ' + current_bid_string + '>' + next_bid_string + log_text
47
+ if not ('compareToZero' in skipped_properties):
48
+ # compare price & volume to zero
49
+ test_shared_methods.assert_greater(exchange, skipped_properties, method, bids[i], 0, '0')
50
+ test_shared_methods.assert_greater(exchange, skipped_properties, method, bids[i], 1, '0')
47
51
  asks = orderbook['asks']
48
52
  asks_length = len(asks)
49
53
  for i in range(0, asks_length):
50
54
  current_ask_string = exchange.safe_string(asks[i], 0)
51
- next_i = i + 1
52
- if asks_length > next_i:
53
- next_ask_string = exchange.safe_string(asks[next_i], 0)
54
- has_correct_order = Precise.string_lt(current_ask_string, next_ask_string)
55
- assert has_correct_order, 'current ask should be < than the next one: ' + current_ask_string + '<' + next_ask_string + log_text
56
- test_shared_methods.assert_greater(exchange, skipped_properties, method, asks[i], 0, '0')
57
- test_shared_methods.assert_greater(exchange, skipped_properties, method, asks[i], 1, '0')
55
+ if not ('compareToNextItem' in skipped_properties):
56
+ next_i = i + 1
57
+ if asks_length > next_i:
58
+ next_ask_string = exchange.safe_string(asks[next_i], 0)
59
+ assert Precise.string_lt(current_ask_string, next_ask_string), 'current ask should be < than the next one: ' + current_ask_string + '<' + next_ask_string + log_text
60
+ if not ('compareToZero' in skipped_properties):
61
+ # compare price & volume to zero
62
+ test_shared_methods.assert_greater(exchange, skipped_properties, method, asks[i], 0, '0')
63
+ test_shared_methods.assert_greater(exchange, skipped_properties, method, asks[i], 1, '0')
58
64
  if not ('spread' in skipped_properties):
59
65
  if bids_length and asks_length:
60
66
  first_bid = exchange.safe_string(bids[0], 0)
ccxt/test/test_async.py CHANGED
@@ -422,9 +422,13 @@ class testMainClass(baseMainTestClass):
422
422
  return result
423
423
 
424
424
  async def test_method(self, method_name, exchange, args, is_public):
425
+ # todo: temporary skip for c#
426
+ if 'OrderBook' in method_name and self.ext == 'cs':
427
+ exchange.options['checksum'] = False
425
428
  # todo: temporary skip for php
426
429
  if 'OrderBook' in method_name and self.ext == 'php':
427
430
  return
431
+ skipped_properties_for_method = self.get_skips(exchange, method_name)
428
432
  is_load_markets = (method_name == 'loadMarkets')
429
433
  is_fetch_currencies = (method_name == 'fetchCurrencies')
430
434
  is_proxy_test = (method_name == self.proxy_test_file_name)
@@ -437,7 +441,7 @@ class testMainClass(baseMainTestClass):
437
441
  skip_message = '[INFO] IGNORED_TEST'
438
442
  elif not is_load_markets and not supported_by_exchange and not is_proxy_test:
439
443
  skip_message = '[INFO] UNSUPPORTED_TEST' # keep it aligned with the longest message
440
- elif (method_name in self.skipped_methods) and (isinstance(self.skipped_methods[method_name], str)):
444
+ elif isinstance(skipped_properties_for_method, str):
441
445
  skip_message = '[INFO] SKIPPED_TEST'
442
446
  elif not (method_name in self.test_files):
443
447
  skip_message = '[INFO] UNIMPLEMENTED_TEST'
@@ -451,15 +455,24 @@ class testMainClass(baseMainTestClass):
451
455
  if self.info:
452
456
  args_stringified = '(' + ','.join(args) + ')'
453
457
  dump(self.add_padding('[INFO] TESTING', 25), self.exchange_hint(exchange), method_name, args_stringified)
454
- await call_method(self.test_files, method_name, exchange, self.get_skips(exchange, method_name), args)
458
+ await call_method(self.test_files, method_name, exchange, skipped_properties_for_method, args)
455
459
  # if it was passed successfully, add to the list of successfull tests
456
460
  if is_public:
457
461
  self.checked_public_tests[method_name] = True
458
462
  return
459
463
 
460
464
  def get_skips(self, exchange, method_name):
461
- # get "method-specific" skips
462
- skips_for_method = exchange.safe_value(self.skipped_methods, method_name, {})
465
+ final_skips = {}
466
+ # check the exact method (i.e. `fetchTrades`) and language-specific (i.e. `fetchTrades.php`)
467
+ method_names = [method_name, method_name + '.' + self.ext]
468
+ for i in range(0, len(method_names)):
469
+ m_name = method_names[i]
470
+ if m_name in self.skipped_methods:
471
+ # if whole method is skipped, by assigning a string to it, i.e. "fetchOrders":"blabla"
472
+ if isinstance(self.skipped_methods[m_name], str):
473
+ return self.skipped_methods[m_name]
474
+ else:
475
+ final_skips = exchange.deep_extend(final_skips, self.skipped_methods[m_name])
463
476
  # get "object-specific" skips
464
477
  object_skips = {
465
478
  'orderBook': ['fetchOrderBook', 'fetchOrderBooks', 'fetchL2OrderBook', 'watchOrderBook', 'watchOrderBookForSymbols'],
@@ -475,9 +488,19 @@ class testMainClass(baseMainTestClass):
475
488
  object_name = object_names[i]
476
489
  object_methods = object_skips[object_name]
477
490
  if exchange.in_array(method_name, object_methods):
491
+ # if whole object is skipped, by assigning a string to it, i.e. "orderBook":"blabla"
492
+ if (object_name in self.skipped_methods) and (isinstance(self.skipped_methods[object_name], str)):
493
+ return self.skipped_methods[object_name]
478
494
  extra_skips = exchange.safe_dict(self.skipped_methods, object_name, {})
479
- return exchange.deep_extend(skips_for_method, extra_skips)
480
- return skips_for_method
495
+ final_skips = exchange.deep_extend(final_skips, extra_skips)
496
+ # extend related skips
497
+ # - if 'timestamp' is skipped, we should do so for 'datetime' too
498
+ # - if 'bid' is skipped, skip 'ask' too
499
+ if ('timestamp' in final_skips) and not ('datetime' in final_skips):
500
+ final_skips['datetime'] = final_skips['timestamp']
501
+ if ('bid' in final_skips) and not ('ask' in final_skips):
502
+ final_skips['ask'] = final_skips['bid']
503
+ return final_skips
481
504
 
482
505
  async def test_safe(self, method_name, exchange, args=[], is_public=False):
483
506
  # `testSafe` method does not throw an exception, instead mutes it. The reason we
@@ -569,11 +592,14 @@ class testMainClass(baseMainTestClass):
569
592
  if self.ws_tests:
570
593
  tests = {
571
594
  'watchOHLCV': [symbol],
595
+ 'watchOHLCVForSymbols': [symbol],
572
596
  'watchTicker': [symbol],
573
597
  'watchTickers': [symbol],
574
598
  'watchBidsAsks': [symbol],
575
599
  'watchOrderBook': [symbol],
600
+ 'watchOrderBookForSymbols': [[symbol]],
576
601
  'watchTrades': [symbol],
602
+ 'watchTradesForSymbols': [[symbol]],
577
603
  }
578
604
  market = exchange.market(symbol)
579
605
  is_spot = market['spot']
ccxt/test/test_sync.py CHANGED
@@ -421,9 +421,13 @@ class testMainClass(baseMainTestClass):
421
421
  return result
422
422
 
423
423
  def test_method(self, method_name, exchange, args, is_public):
424
+ # todo: temporary skip for c#
425
+ if 'OrderBook' in method_name and self.ext == 'cs':
426
+ exchange.options['checksum'] = False
424
427
  # todo: temporary skip for php
425
428
  if 'OrderBook' in method_name and self.ext == 'php':
426
429
  return
430
+ skipped_properties_for_method = self.get_skips(exchange, method_name)
427
431
  is_load_markets = (method_name == 'loadMarkets')
428
432
  is_fetch_currencies = (method_name == 'fetchCurrencies')
429
433
  is_proxy_test = (method_name == self.proxy_test_file_name)
@@ -436,7 +440,7 @@ class testMainClass(baseMainTestClass):
436
440
  skip_message = '[INFO] IGNORED_TEST'
437
441
  elif not is_load_markets and not supported_by_exchange and not is_proxy_test:
438
442
  skip_message = '[INFO] UNSUPPORTED_TEST' # keep it aligned with the longest message
439
- elif (method_name in self.skipped_methods) and (isinstance(self.skipped_methods[method_name], str)):
443
+ elif isinstance(skipped_properties_for_method, str):
440
444
  skip_message = '[INFO] SKIPPED_TEST'
441
445
  elif not (method_name in self.test_files):
442
446
  skip_message = '[INFO] UNIMPLEMENTED_TEST'
@@ -450,15 +454,24 @@ class testMainClass(baseMainTestClass):
450
454
  if self.info:
451
455
  args_stringified = '(' + ','.join(args) + ')'
452
456
  dump(self.add_padding('[INFO] TESTING', 25), self.exchange_hint(exchange), method_name, args_stringified)
453
- call_method(self.test_files, method_name, exchange, self.get_skips(exchange, method_name), args)
457
+ call_method(self.test_files, method_name, exchange, skipped_properties_for_method, args)
454
458
  # if it was passed successfully, add to the list of successfull tests
455
459
  if is_public:
456
460
  self.checked_public_tests[method_name] = True
457
461
  return
458
462
 
459
463
  def get_skips(self, exchange, method_name):
460
- # get "method-specific" skips
461
- skips_for_method = exchange.safe_value(self.skipped_methods, method_name, {})
464
+ final_skips = {}
465
+ # check the exact method (i.e. `fetchTrades`) and language-specific (i.e. `fetchTrades.php`)
466
+ method_names = [method_name, method_name + '.' + self.ext]
467
+ for i in range(0, len(method_names)):
468
+ m_name = method_names[i]
469
+ if m_name in self.skipped_methods:
470
+ # if whole method is skipped, by assigning a string to it, i.e. "fetchOrders":"blabla"
471
+ if isinstance(self.skipped_methods[m_name], str):
472
+ return self.skipped_methods[m_name]
473
+ else:
474
+ final_skips = exchange.deep_extend(final_skips, self.skipped_methods[m_name])
462
475
  # get "object-specific" skips
463
476
  object_skips = {
464
477
  'orderBook': ['fetchOrderBook', 'fetchOrderBooks', 'fetchL2OrderBook', 'watchOrderBook', 'watchOrderBookForSymbols'],
@@ -474,9 +487,19 @@ class testMainClass(baseMainTestClass):
474
487
  object_name = object_names[i]
475
488
  object_methods = object_skips[object_name]
476
489
  if exchange.in_array(method_name, object_methods):
490
+ # if whole object is skipped, by assigning a string to it, i.e. "orderBook":"blabla"
491
+ if (object_name in self.skipped_methods) and (isinstance(self.skipped_methods[object_name], str)):
492
+ return self.skipped_methods[object_name]
477
493
  extra_skips = exchange.safe_dict(self.skipped_methods, object_name, {})
478
- return exchange.deep_extend(skips_for_method, extra_skips)
479
- return skips_for_method
494
+ final_skips = exchange.deep_extend(final_skips, extra_skips)
495
+ # extend related skips
496
+ # - if 'timestamp' is skipped, we should do so for 'datetime' too
497
+ # - if 'bid' is skipped, skip 'ask' too
498
+ if ('timestamp' in final_skips) and not ('datetime' in final_skips):
499
+ final_skips['datetime'] = final_skips['timestamp']
500
+ if ('bid' in final_skips) and not ('ask' in final_skips):
501
+ final_skips['ask'] = final_skips['bid']
502
+ return final_skips
480
503
 
481
504
  def test_safe(self, method_name, exchange, args=[], is_public=False):
482
505
  # `testSafe` method does not throw an exception, instead mutes it. The reason we
@@ -568,11 +591,14 @@ class testMainClass(baseMainTestClass):
568
591
  if self.ws_tests:
569
592
  tests = {
570
593
  'watchOHLCV': [symbol],
594
+ 'watchOHLCVForSymbols': [symbol],
571
595
  'watchTicker': [symbol],
572
596
  'watchTickers': [symbol],
573
597
  'watchBidsAsks': [symbol],
574
598
  'watchOrderBook': [symbol],
599
+ 'watchOrderBookForSymbols': [[symbol]],
575
600
  'watchTrades': [symbol],
601
+ 'watchTradesForSymbols': [[symbol]],
576
602
  }
577
603
  market = exchange.market(symbol)
578
604
  is_spot = market['spot']
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ccxt
3
- Version: 4.2.95
3
+ Version: 4.2.97
4
4
  Summary: A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 100+ exchanges
5
5
  Home-page: https://ccxt.com
6
6
  Author: Igor Kroitor
@@ -261,13 +261,13 @@ console.log(version, Object.keys(exchanges));
261
261
 
262
262
  All-in-one browser bundle (dependencies included), served from a CDN of your choice:
263
263
 
264
- * jsDelivr: https://cdn.jsdelivr.net/npm/ccxt@4.2.95/dist/ccxt.browser.js
265
- * unpkg: https://unpkg.com/ccxt@4.2.95/dist/ccxt.browser.js
264
+ * jsDelivr: https://cdn.jsdelivr.net/npm/ccxt@4.2.97/dist/ccxt.browser.js
265
+ * unpkg: https://unpkg.com/ccxt@4.2.97/dist/ccxt.browser.js
266
266
 
267
267
  CDNs are not updated in real-time and may have delays. Defaulting to the most recent version without specifying the version number is not recommended. Please, keep in mind that we are not responsible for the correct operation of those CDN servers.
268
268
 
269
269
  ```HTML
270
- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/ccxt@4.2.95/dist/ccxt.browser.js"></script>
270
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/ccxt@4.2.97/dist/ccxt.browser.js"></script>
271
271
  ```
272
272
 
273
273
  Creates a global `ccxt` object: