ccxt 4.4.11__py2.py3-none-any.whl → 4.4.13__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.
Files changed (64) hide show
  1. ccxt/__init__.py +1 -1
  2. ccxt/async_support/__init__.py +1 -1
  3. ccxt/async_support/base/exchange.py +4 -1
  4. ccxt/async_support/bigone.py +2 -0
  5. ccxt/async_support/binance.py +54 -3
  6. ccxt/async_support/bingx.py +84 -5
  7. ccxt/async_support/bitget.py +2 -0
  8. ccxt/async_support/bitmex.py +1 -0
  9. ccxt/async_support/bybit.py +4 -1
  10. ccxt/async_support/coinbaseinternational.py +2 -0
  11. ccxt/async_support/coinex.py +2 -0
  12. ccxt/async_support/delta.py +2 -0
  13. ccxt/async_support/deribit.py +2 -0
  14. ccxt/async_support/digifinex.py +2 -0
  15. ccxt/async_support/gate.py +2 -0
  16. ccxt/async_support/htx.py +8 -2
  17. ccxt/async_support/kraken.py +4 -4
  18. ccxt/async_support/krakenfutures.py +2 -0
  19. ccxt/async_support/kucoinfutures.py +2 -0
  20. ccxt/async_support/mexc.py +16 -4
  21. ccxt/async_support/okx.py +51 -20
  22. ccxt/async_support/oxfun.py +1 -0
  23. ccxt/async_support/paradex.py +1 -0
  24. ccxt/async_support/phemex.py +10 -3
  25. ccxt/async_support/poloniex.py +1 -0
  26. ccxt/base/exchange.py +7 -1
  27. ccxt/base/types.py +2 -0
  28. ccxt/bigone.py +2 -0
  29. ccxt/binance.py +54 -3
  30. ccxt/bingx.py +84 -5
  31. ccxt/bitget.py +2 -0
  32. ccxt/bitmex.py +1 -0
  33. ccxt/bybit.py +4 -1
  34. ccxt/coinbaseinternational.py +2 -0
  35. ccxt/coinex.py +2 -0
  36. ccxt/delta.py +2 -0
  37. ccxt/deribit.py +2 -0
  38. ccxt/digifinex.py +2 -0
  39. ccxt/gate.py +2 -0
  40. ccxt/htx.py +8 -2
  41. ccxt/kraken.py +4 -4
  42. ccxt/krakenfutures.py +2 -0
  43. ccxt/kucoinfutures.py +2 -0
  44. ccxt/mexc.py +16 -4
  45. ccxt/okx.py +51 -20
  46. ccxt/oxfun.py +1 -0
  47. ccxt/paradex.py +1 -0
  48. ccxt/phemex.py +10 -3
  49. ccxt/poloniex.py +1 -0
  50. ccxt/pro/__init__.py +1 -1
  51. ccxt/pro/binance.py +72 -5
  52. ccxt/pro/bitfinex.py +8 -8
  53. ccxt/pro/krakenfutures.py +2 -0
  54. ccxt/pro/phemex.py +2 -0
  55. ccxt/pro/woo.py +69 -0
  56. ccxt/test/tests_async.py +31 -3
  57. ccxt/test/tests_helpers.py +13 -36
  58. ccxt/test/tests_init.py +6 -2
  59. ccxt/test/tests_sync.py +31 -3
  60. {ccxt-4.4.11.dist-info → ccxt-4.4.13.dist-info}/METADATA +4 -5
  61. {ccxt-4.4.11.dist-info → ccxt-4.4.13.dist-info}/RECORD +64 -64
  62. {ccxt-4.4.11.dist-info → ccxt-4.4.13.dist-info}/LICENSE.txt +0 -0
  63. {ccxt-4.4.11.dist-info → ccxt-4.4.13.dist-info}/WHEEL +0 -0
  64. {ccxt-4.4.11.dist-info → ccxt-4.4.13.dist-info}/top_level.txt +0 -0
ccxt/kraken.py CHANGED
@@ -1740,7 +1740,7 @@ class kraken(Exchange, ImplicitAPI):
1740
1740
  request['volume'] = self.cost_to_precision(symbol, cost)
1741
1741
  extendedOflags = flags + ',viqc' if (flags is not None) else 'viqc'
1742
1742
  request['oflags'] = extendedOflags
1743
- elif isLimitOrder and not isTrailingAmountOrder:
1743
+ elif isLimitOrder and not isTrailingAmountOrder and not isTrailingPercentOrder:
1744
1744
  request['price'] = self.price_to_precision(symbol, price)
1745
1745
  reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only')
1746
1746
  if isStopLossOrTakeProfitTrigger:
@@ -1760,8 +1760,8 @@ class kraken(Exchange, ImplicitAPI):
1760
1760
  request['price2'] = self.price_to_precision(symbol, price)
1761
1761
  elif isTrailingAmountOrder or isTrailingPercentOrder:
1762
1762
  trailingPercentString = None
1763
- if isTrailingPercentOrder:
1764
- trailingPercentString = trailingPercent if (trailingPercent.endswith('%')) else '+' + (self.number_to_string(trailingPercent) + '%')
1763
+ if trailingPercent is not None:
1764
+ trailingPercentString = ('+' + trailingPercent) if (trailingPercent.endswith('%')) else ('+' + trailingPercent + '%')
1765
1765
  trailingAmountString = '+' + trailingAmount if (trailingAmount is not None) else None # must use + for self
1766
1766
  offset = self.safe_string(params, 'offset', '-') # can use + or - for self
1767
1767
  trailingLimitAmountString = offset + self.number_to_string(trailingLimitAmount) if (trailingLimitAmount is not None) else None
@@ -1770,7 +1770,7 @@ class kraken(Exchange, ImplicitAPI):
1770
1770
  if isLimitOrder or (trailingLimitAmount is not None) or (trailingLimitPercent is not None):
1771
1771
  request['ordertype'] = 'trailing-stop-limit'
1772
1772
  if trailingLimitPercent is not None:
1773
- trailingLimitPercentString = trailingLimitPercent if (trailingLimitPercent.endswith('%')) else (self.number_to_string(trailingLimitPercent) + '%')
1773
+ trailingLimitPercentString = (offset + trailingLimitPercent) if (trailingLimitPercent.endswith('%')) else (offset + trailingLimitPercent + '%')
1774
1774
  request['price'] = trailingPercentString
1775
1775
  request['price2'] = trailingLimitPercentString
1776
1776
  elif trailingLimitAmount is not None:
ccxt/krakenfutures.py CHANGED
@@ -611,6 +611,8 @@ class krakenfutures(Exchange, ImplicitAPI):
611
611
  'average': average,
612
612
  'baseVolume': baseVolume,
613
613
  'quoteVolume': quoteVolume,
614
+ 'markPrice': self.safe_string(ticker, 'markPrice'),
615
+ 'indexPrice': self.safe_string(ticker, 'indexPrice'),
614
616
  'info': ticker,
615
617
  })
616
618
 
ccxt/kucoinfutures.py CHANGED
@@ -944,6 +944,8 @@ class kucoinfutures(kucoin, ImplicitAPI):
944
944
  'average': None,
945
945
  'baseVolume': self.safe_string(ticker, 'volumeOf24h'),
946
946
  'quoteVolume': self.safe_string(ticker, 'turnoverOf24h'),
947
+ 'markPrice': self.safe_string(ticker, 'markPrice'),
948
+ 'indexPrice': self.safe_string(ticker, 'indexPrice'),
947
949
  'info': ticker,
948
950
  }, market)
949
951
 
ccxt/mexc.py CHANGED
@@ -2054,6 +2054,7 @@ class mexc(Exchange, ImplicitAPI):
2054
2054
  :param float [params.triggerPrice]: The price at which a trigger order is triggered at
2055
2055
  :param bool [params.postOnly]: if True, the order will only be posted if it will be a maker order
2056
2056
  :param bool [params.reduceOnly]: *contract only* indicates if self order is to reduce the size of a position
2057
+ :param bool [params.hedged]: *swap only* True for hedged mode, False for one way mode, default is False
2057
2058
  *
2058
2059
  * EXCHANGE SPECIFIC PARAMETERS
2059
2060
  :param int [params.leverage]: *contract only* leverage is necessary on isolated margin
@@ -2179,6 +2180,7 @@ class mexc(Exchange, ImplicitAPI):
2179
2180
  :param float [params.triggerPrice]: The price at which a trigger order is triggered at
2180
2181
  :param bool [params.postOnly]: if True, the order will only be posted if it will be a maker order
2181
2182
  :param bool [params.reduceOnly]: indicates if self order is to reduce the size of a position
2183
+ :param bool [params.hedged]: *swap only* True for hedged mode, False for one way mode, default is False
2182
2184
  *
2183
2185
  * EXCHANGE SPECIFIC PARAMETERS
2184
2186
  :param int [params.leverage]: leverage is necessary on isolated margin
@@ -2246,15 +2248,25 @@ class mexc(Exchange, ImplicitAPI):
2246
2248
  if leverage is None:
2247
2249
  raise ArgumentsRequired(self.id + ' createSwapOrder() requires a leverage parameter for isolated margin orders')
2248
2250
  reduceOnly = self.safe_bool(params, 'reduceOnly', False)
2249
- if reduceOnly:
2250
- request['side'] = 2 if (side == 'buy') else 4
2251
+ hedged = self.safe_bool(params, 'hedged', False)
2252
+ sideInteger = None
2253
+ if hedged:
2254
+ if reduceOnly:
2255
+ params = self.omit(params, 'reduceOnly') # hedged mode does not accept self parameter
2256
+ side = 'sell' if (side == 'buy') else 'buy'
2257
+ sideInteger = 1 if (side == 'buy') else 3
2258
+ request['positionMode'] = 1
2251
2259
  else:
2252
- request['side'] = 1 if (side == 'buy') else 3
2260
+ if reduceOnly:
2261
+ sideInteger = 2 if (side == 'buy') else 4
2262
+ else:
2263
+ sideInteger = 1 if (side == 'buy') else 3
2264
+ request['side'] = sideInteger
2253
2265
  clientOrderId = self.safe_string_2(params, 'clientOrderId', 'externalOid')
2254
2266
  if clientOrderId is not None:
2255
2267
  request['externalOid'] = clientOrderId
2256
2268
  stopPrice = self.safe_number_2(params, 'triggerPrice', 'stopPrice')
2257
- params = self.omit(params, ['clientOrderId', 'externalOid', 'postOnly', 'stopPrice', 'triggerPrice'])
2269
+ params = self.omit(params, ['clientOrderId', 'externalOid', 'postOnly', 'stopPrice', 'triggerPrice', 'hedged'])
2258
2270
  response = None
2259
2271
  if stopPrice:
2260
2272
  request['triggerPrice'] = self.price_to_precision(symbol, stopPrice)
ccxt/okx.py CHANGED
@@ -120,6 +120,7 @@ class okx(Exchange, ImplicitAPI):
120
120
  'fetchMarketLeverageTiers': True,
121
121
  'fetchMarkets': True,
122
122
  'fetchMarkOHLCV': True,
123
+ 'fetchMarkPrices': True,
123
124
  'fetchMySettlementHistory': False,
124
125
  'fetchMyTrades': True,
125
126
  'fetchOHLCV': True,
@@ -1784,6 +1785,13 @@ class okx(Exchange, ImplicitAPI):
1784
1785
  return self.parse_order_book(first, symbol, timestamp)
1785
1786
 
1786
1787
  def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
1788
+ #
1789
+ # {
1790
+ # "instType":"SWAP",
1791
+ # "instId":"BTC-USDT-SWAP",
1792
+ # "markPx":"200",
1793
+ # "ts":"1597026383085"
1794
+ # }
1787
1795
  #
1788
1796
  # {
1789
1797
  # "instType": "SPOT",
@@ -1835,6 +1843,7 @@ class okx(Exchange, ImplicitAPI):
1835
1843
  'average': None,
1836
1844
  'baseVolume': baseVolume,
1837
1845
  'quoteVolume': quoteVolume,
1846
+ 'markPrice': self.safe_string(ticker, 'markPx'),
1838
1847
  'info': ticker,
1839
1848
  }, market)
1840
1849
 
@@ -1935,6 +1944,33 @@ class okx(Exchange, ImplicitAPI):
1935
1944
  tickers = self.safe_list(response, 'data', [])
1936
1945
  return self.parse_tickers(tickers, symbols)
1937
1946
 
1947
+ def fetch_mark_prices(self, symbols: Strings = None, params={}) -> Tickers:
1948
+ """
1949
+ fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
1950
+ :see: https://www.okx.com/docs-v5/en/#public-data-rest-api-get-mark-price
1951
+ :param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
1952
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1953
+ :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
1954
+ """
1955
+ self.load_markets()
1956
+ symbols = self.market_symbols(symbols)
1957
+ market = self.get_market_from_symbols(symbols)
1958
+ marketType = None
1959
+ marketType, params = self.handle_market_type_and_params('fetchTickers', market, params, 'swap')
1960
+ request: dict = {
1961
+ 'instType': self.convert_to_instrument_type(marketType),
1962
+ }
1963
+ if marketType == 'option':
1964
+ defaultUnderlying = self.safe_string(self.options, 'defaultUnderlying', 'BTC-USD')
1965
+ currencyId = self.safe_string_2(params, 'uly', 'marketId', defaultUnderlying)
1966
+ if currencyId is None:
1967
+ raise ArgumentsRequired(self.id + ' fetchTickers() requires an underlying uly or marketId parameter for options markets')
1968
+ else:
1969
+ request['uly'] = currencyId
1970
+ response = self.publicGetPublicMarkPrice(self.extend(request, params))
1971
+ tickers = self.safe_list(response, 'data', [])
1972
+ return self.parse_tickers(tickers, symbols)
1973
+
1938
1974
  def parse_trade(self, trade: dict, market: Market = None) -> Trade:
1939
1975
  #
1940
1976
  # public fetchTrades
@@ -2794,7 +2830,7 @@ class okx(Exchange, ImplicitAPI):
2794
2830
  :param str [params.positionSide]: if position mode is one-way: set to 'net', if position mode is hedge-mode: set to 'long' or 'short'
2795
2831
  :param str [params.trailingPercent]: the percent to trail away from the current market price
2796
2832
  :param str [params.tpOrdKind]: 'condition' or 'limit', the default is 'condition'
2797
- :param str [params.hedged]: True/false, to automatically set exchange-specific params needed when trading in hedge mode
2833
+ :param bool [params.hedged]: *swap and future only* True for hedged mode, False for one way mode
2798
2834
  :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
2799
2835
  """
2800
2836
  self.load_markets()
@@ -4527,32 +4563,27 @@ class okx(Exchange, ImplicitAPI):
4527
4563
  :see: https://www.okx.com/docs-v5/en/#funding-account-rest-api-get-deposit-address
4528
4564
  :param str code: unified currency code
4529
4565
  :param dict [params]: extra parameters specific to the exchange API endpoint
4566
+ :param str [params.network]: the network name for the deposit address
4530
4567
  :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
4531
4568
  """
4569
+ self.load_markets()
4532
4570
  rawNetwork = self.safe_string_upper(params, 'network')
4533
- networks = self.safe_value(self.options, 'networks', {})
4534
- network = self.safe_string(networks, rawNetwork, rawNetwork)
4535
4571
  params = self.omit(params, 'network')
4572
+ code = self.safe_currency_code(code)
4573
+ network = self.network_id_to_code(rawNetwork, code)
4536
4574
  response = self.fetch_deposit_addresses_by_network(code, params)
4537
- result = None
4538
- if network is None:
4539
- result = self.safe_value(response, code)
4575
+ if network is not None:
4576
+ result = self.safe_dict(response, network)
4540
4577
  if result is None:
4541
- alias = self.safe_string(networks, code, code)
4542
- result = self.safe_value(response, alias)
4543
- if result is None:
4544
- defaultNetwork = self.safe_string(self.options, 'defaultNetwork', 'ERC20')
4545
- result = self.safe_value(response, defaultNetwork)
4546
- if result is None:
4547
- values = list(response.values())
4548
- result = self.safe_value(values, 0)
4549
- if result is None:
4550
- raise InvalidAddress(self.id + ' fetchDepositAddress() cannot find deposit address for ' + code)
4578
+ raise InvalidAddress(self.id + ' fetchDepositAddress() cannot find ' + network + ' deposit address for ' + code)
4551
4579
  return result
4552
- result = self.safe_value(response, network)
4553
- if result is None:
4554
- raise InvalidAddress(self.id + ' fetchDepositAddress() cannot find ' + network + ' deposit address for ' + code)
4555
- return result
4580
+ codeNetwork = self.network_id_to_code(code, code)
4581
+ if codeNetwork in response:
4582
+ return response[codeNetwork]
4583
+ # if the network is not specified, return the first address
4584
+ keys = list(response.keys())
4585
+ first = self.safe_string(keys, 0)
4586
+ return self.safe_dict(response, first)
4556
4587
 
4557
4588
  def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
4558
4589
  """
ccxt/oxfun.py CHANGED
@@ -858,6 +858,7 @@ class oxfun(Exchange, ImplicitAPI):
858
858
  'average': None,
859
859
  'baseVolume': self.safe_string(ticker, 'currencyVolume24h'),
860
860
  'quoteVolume': None, # the exchange returns cost in OX
861
+ 'markPrice': self.safe_string(ticker, 'markPrice'),
861
862
  'info': ticker,
862
863
  }, market)
863
864
 
ccxt/paradex.py CHANGED
@@ -652,6 +652,7 @@ class paradex(Exchange, ImplicitAPI):
652
652
  'average': None,
653
653
  'baseVolume': None,
654
654
  'quoteVolume': self.safe_string(ticker, 'volume_24h'),
655
+ 'markPrice': self.safe_string(ticker, 'mark_price'),
655
656
  'info': ticker,
656
657
  }, market)
657
658
 
ccxt/phemex.py CHANGED
@@ -2402,6 +2402,8 @@ class phemex(Exchange, ImplicitAPI):
2402
2402
  :param float [params.takeProfit.triggerPrice]: take profit trigger price
2403
2403
  :param dict [params.stopLoss]: *swap only* *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered(perpetual swap markets only)
2404
2404
  :param float [params.stopLoss.triggerPrice]: stop loss trigger price
2405
+ :param str [params.posSide]: *swap only* "Merged" for one way mode, "Long" for buy side of hedged mode, "Short" for sell side of hedged mode
2406
+ :param bool [params.hedged]: *swap only* True for hedged mode, False for one way mode, default is False
2405
2407
  :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
2406
2408
  """
2407
2409
  self.load_markets()
@@ -2486,13 +2488,18 @@ class phemex(Exchange, ImplicitAPI):
2486
2488
  amountString = self.number_to_string(amount)
2487
2489
  request['baseQtyEv'] = self.to_ev(amountString, market)
2488
2490
  elif market['swap']:
2491
+ hedged = self.safe_bool(params, 'hedged', False)
2492
+ params = self.omit(params, 'hedged')
2489
2493
  posSide = self.safe_string_lower(params, 'posSide')
2490
2494
  if posSide is None:
2491
- posSide = 'Merged'
2495
+ if hedged:
2496
+ if reduceOnly:
2497
+ side = 'sell' if (side == 'buy') else 'buy'
2498
+ posSide = 'Long' if (side == 'buy') else 'Short'
2499
+ else:
2500
+ posSide = 'Merged'
2492
2501
  posSide = self.capitalize(posSide)
2493
2502
  request['posSide'] = posSide
2494
- if reduceOnly is not None:
2495
- request['reduceOnly'] = reduceOnly
2496
2503
  if market['settle'] == 'USDT':
2497
2504
  request['orderQtyRq'] = amount
2498
2505
  else:
ccxt/poloniex.py CHANGED
@@ -633,6 +633,7 @@ class poloniex(Exchange, ImplicitAPI):
633
633
  'average': None,
634
634
  'baseVolume': self.safe_string(ticker, 'quantity'),
635
635
  'quoteVolume': self.safe_string(ticker, 'amount'),
636
+ 'markPrice': self.safe_string(ticker, 'markPrice'),
636
637
  'info': ticker,
637
638
  }, market)
638
639
 
ccxt/pro/__init__.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # ----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.4.11'
7
+ __version__ = '4.4.13'
8
8
 
9
9
  # ----------------------------------------------------------------------------
10
10
 
ccxt/pro/binance.py CHANGED
@@ -43,6 +43,8 @@ class binance(ccxt.async_support.binance):
43
43
  'watchPositions': True,
44
44
  'watchTicker': True,
45
45
  'watchTickers': True,
46
+ 'watchMarkPrices': True,
47
+ 'watchMarkPrice': True,
46
48
  'watchTrades': True,
47
49
  'watchTradesForSymbols': True,
48
50
  'createOrderWs': True,
@@ -159,6 +161,7 @@ class binance(ccxt.async_support.binance):
159
161
  'tickerChannelsMap': {
160
162
  '24hrTicker': 'ticker',
161
163
  '24hrMiniTicker': 'miniTicker',
164
+ 'markPriceUpdate': 'markPrice',
162
165
  # rolling window tickers
163
166
  '1hTicker': 'ticker_1h',
164
167
  '4hTicker': 'ticker_4h',
@@ -1641,6 +1644,39 @@ class binance(ccxt.async_support.binance):
1641
1644
  tickers = await self.watch_tickers([symbol], self.extend(params, {'callerMethodName': 'watchTicker'}))
1642
1645
  return tickers[symbol]
1643
1646
 
1647
+ async def watch_mark_price(self, symbol: str, params={}) -> Ticker:
1648
+ """
1649
+ :see: https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Mark-Price-Stream
1650
+ watches a mark price for a specific market
1651
+ :param str symbol: unified symbol of the market to fetch the ticker for
1652
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1653
+ :param boolean [params.use1sFreq]: *default is True* if set to True, the mark price will be updated every second, otherwise every 3 seconds
1654
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
1655
+ """
1656
+ await self.load_markets()
1657
+ symbol = self.symbol(symbol)
1658
+ tickers = await self.watch_mark_prices([symbol], self.extend(params, {'callerMethodName': 'watchMarkPrice'}))
1659
+ return tickers[symbol]
1660
+
1661
+ async def watch_mark_prices(self, symbols: Strings = None, params={}) -> Tickers:
1662
+ """
1663
+ :see: https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Mark-Price-Stream-for-All-market
1664
+ watches the mark price for all markets
1665
+ :param str[] symbols: unified symbol of the market to fetch the ticker for
1666
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1667
+ :param boolean [params.use1sFreq]: *default is True* if set to True, the mark price will be updated every second, otherwise every 3 seconds
1668
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
1669
+ """
1670
+ channelName = None
1671
+ # for now watchmarkPrice uses the same messageHash
1672
+ # so it's impossible to watch both at the same time
1673
+ # refactor self to use different messageHashes
1674
+ channelName, params = self.handle_option_and_params(params, 'watchMarkPrices', 'name', 'markPrice')
1675
+ newTickers = await self.watch_multi_ticker_helper('watchMarkPrices', channelName, symbols, params)
1676
+ if self.newUpdates:
1677
+ return newTickers
1678
+ return self.filter_by_array(self.tickers, 'symbol', symbols)
1679
+
1644
1680
  async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
1645
1681
  """
1646
1682
  :see: https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-mini-ticker-stream
@@ -1775,12 +1811,16 @@ class binance(ccxt.async_support.binance):
1775
1811
  async def watch_multi_ticker_helper(self, methodName, channelName: str, symbols: Strings = None, params={}):
1776
1812
  await self.load_markets()
1777
1813
  symbols = self.market_symbols(symbols, None, True, False, True)
1814
+ isBidAsk = (channelName == 'bookTicker')
1815
+ isMarkPrice = (channelName == 'markPrice')
1816
+ use1sFreq = self.safe_bool(params, 'use1sFreq', True)
1778
1817
  firstMarket = None
1779
1818
  marketType = None
1780
1819
  symbolsDefined = (symbols is not None)
1781
1820
  if symbolsDefined:
1782
1821
  firstMarket = self.market(symbols[0])
1783
- marketType, params = self.handle_market_type_and_params(methodName, firstMarket, params)
1822
+ defaultMarket = 'swap' if (isMarkPrice) else 'spot'
1823
+ marketType, params = self.handle_market_type_and_params(methodName, firstMarket, params, defaultMarket)
1784
1824
  subType = None
1785
1825
  subType, params = self.handle_sub_type_and_params(methodName, firstMarket, params)
1786
1826
  rawMarketType = None
@@ -1792,20 +1832,24 @@ class binance(ccxt.async_support.binance):
1792
1832
  rawMarketType = marketType
1793
1833
  else:
1794
1834
  raise NotSupported(self.id + ' ' + methodName + '() does not support options markets')
1795
- isBidAsk = (channelName == 'bookTicker')
1796
1835
  subscriptionArgs = []
1797
1836
  messageHashes = []
1837
+ suffix = ''
1838
+ if isMarkPrice:
1839
+ suffix = '@1s' if (use1sFreq) else ''
1798
1840
  if symbolsDefined:
1799
1841
  for i in range(0, len(symbols)):
1800
1842
  symbol = symbols[i]
1801
1843
  market = self.market(symbol)
1802
- subscriptionArgs.append(market['lowercaseId'] + '@' + channelName)
1844
+ subscriptionArgs.append(market['lowercaseId'] + '@' + channelName + suffix)
1803
1845
  messageHashes.append(self.get_message_hash(channelName, market['symbol'], isBidAsk))
1804
1846
  else:
1805
1847
  if isBidAsk:
1806
1848
  if marketType == 'spot':
1807
1849
  raise ArgumentsRequired(self.id + ' ' + methodName + '() requires symbols for self channel for spot markets')
1808
1850
  subscriptionArgs.append('!' + channelName)
1851
+ elif isMarkPrice:
1852
+ subscriptionArgs.append('!' + channelName + '@arr' + suffix)
1809
1853
  else:
1810
1854
  subscriptionArgs.append('!' + channelName + '@arr')
1811
1855
  messageHashes.append(self.get_message_hash(channelName, None, isBidAsk))
@@ -1833,6 +1877,17 @@ class binance(ccxt.async_support.binance):
1833
1877
  return newDict
1834
1878
 
1835
1879
  def parse_ws_ticker(self, message, marketType):
1880
+ # markPrice
1881
+ # {
1882
+ # "e": "markPriceUpdate", # Event type
1883
+ # "E": 1562305380000, # Event time
1884
+ # "s": "BTCUSDT", # Symbol
1885
+ # "p": "11794.15000000", # Mark price
1886
+ # "i": "11784.62659091", # Index price
1887
+ # "P": "11784.25641265", # Estimated Settle Price, only useful in the last hour before the settlement starts
1888
+ # "r": "0.00038167", # Funding rate
1889
+ # "T": 1562306400000 # Next funding time
1890
+ # }
1836
1891
  #
1837
1892
  # ticker
1838
1893
  # {
@@ -1890,9 +1945,21 @@ class binance(ccxt.async_support.binance):
1890
1945
  # "time":1589437530011,
1891
1946
  # }
1892
1947
  #
1948
+ marketId = self.safe_string_2(message, 's', 'symbol')
1949
+ symbol = self.safe_symbol(marketId, None, None, marketType)
1893
1950
  event = self.safe_string(message, 'e', 'bookTicker')
1894
1951
  if event == '24hrTicker':
1895
1952
  event = 'ticker'
1953
+ if event == 'markPriceUpdate':
1954
+ # handle self separately because some fields clash with the ticker fields
1955
+ return self.safe_ticker({
1956
+ 'symbol': symbol,
1957
+ 'timestamp': self.safe_integer(message, 'E'),
1958
+ 'datetime': self.iso8601(self.safe_integer(message, 'E')),
1959
+ 'info': message,
1960
+ 'markPrice': self.safe_string(message, 'p'),
1961
+ 'indexPrice': self.safe_string(message, 'i'),
1962
+ })
1896
1963
  timestamp = None
1897
1964
  if event == 'bookTicker':
1898
1965
  # take the event timestamp, if available, for spot tickers it is not
@@ -1900,8 +1967,6 @@ class binance(ccxt.async_support.binance):
1900
1967
  else:
1901
1968
  # take the timestamp of the closing price for candlestick streams
1902
1969
  timestamp = self.safe_integer_n(message, ['C', 'E', 'time'])
1903
- marketId = self.safe_string_2(message, 's', 'symbol')
1904
- symbol = self.safe_symbol(marketId, None, None, marketType)
1905
1970
  market = self.safe_market(marketId, None, None, marketType)
1906
1971
  last = self.safe_string_2(message, 'c', 'price')
1907
1972
  return self.safe_ticker({
@@ -3864,6 +3929,8 @@ class binance(ccxt.async_support.binance):
3864
3929
  '1dTicker': self.handle_tickers,
3865
3930
  '24hrTicker': self.handle_tickers,
3866
3931
  '24hrMiniTicker': self.handle_tickers,
3932
+ 'markPriceUpdate': self.handle_tickers,
3933
+ 'markPriceUpdate@arr': self.handle_tickers,
3867
3934
  'bookTicker': self.handle_bids_asks, # there is no "bookTicker@arr" endpoint
3868
3935
  'outboundAccountPosition': self.handle_balance,
3869
3936
  'balanceUpdate': self.handle_balance,
ccxt/pro/bitfinex.py CHANGED
@@ -213,15 +213,15 @@ class bitfinex(ccxt.async_support.bitfinex):
213
213
  open = None
214
214
  if (last is not None) and (change is not None):
215
215
  open = Precise.string_sub(last, change)
216
- result = {
216
+ result = self.safe_ticker({
217
217
  'symbol': symbol,
218
218
  'timestamp': None,
219
219
  'datetime': None,
220
- 'high': self.safe_float(message, 9),
221
- 'low': self.safe_float(message, 10),
222
- 'bid': self.safe_float(message, 1),
220
+ 'high': self.safe_string(message, 9),
221
+ 'low': self.safe_string(message, 10),
222
+ 'bid': self.safe_string(message, 1),
223
223
  'bidVolume': None,
224
- 'ask': self.safe_float(message, 3),
224
+ 'ask': self.safe_string(message, 3),
225
225
  'askVolume': None,
226
226
  'vwap': None,
227
227
  'open': self.parse_number(open),
@@ -229,12 +229,12 @@ class bitfinex(ccxt.async_support.bitfinex):
229
229
  'last': self.parse_number(last),
230
230
  'previousClose': None,
231
231
  'change': self.parse_number(change),
232
- 'percentage': self.safe_float(message, 6),
232
+ 'percentage': self.safe_string(message, 6),
233
233
  'average': None,
234
- 'baseVolume': self.safe_float(message, 8),
234
+ 'baseVolume': self.safe_string(message, 8),
235
235
  'quoteVolume': None,
236
236
  'info': message,
237
- }
237
+ })
238
238
  self.tickers[symbol] = result
239
239
  client.resolve(result, messageHash)
240
240
 
ccxt/pro/krakenfutures.py CHANGED
@@ -998,6 +998,8 @@ class krakenfutures(ccxt.async_support.krakenfutures):
998
998
  'average': None,
999
999
  'baseVolume': self.safe_string(ticker, 'volume'),
1000
1000
  'quoteVolume': self.safe_string(ticker, 'volumeQuote'),
1001
+ 'markPrice': self.safe_string(ticker, 'markPrice'),
1002
+ 'indexPrice': self.safe_string(ticker, 'index'),
1001
1003
  })
1002
1004
 
1003
1005
  def handle_order_book_snapshot(self, client: Client, message):
ccxt/pro/phemex.py CHANGED
@@ -132,6 +132,8 @@ class phemex(ccxt.async_support.phemex):
132
132
  'average': average,
133
133
  'baseVolume': baseVolume,
134
134
  'quoteVolume': quoteVolume,
135
+ 'markPrice': self.parse_number(self.from_ep(self.safe_string(ticker, 'markPrice'), market)),
136
+ 'indexPrice': self.parse_number(self.from_ep(self.safe_string(ticker, 'indexPrice'), market)),
135
137
  'info': ticker,
136
138
  }
137
139
  return result
ccxt/pro/woo.py CHANGED
@@ -27,6 +27,7 @@ class woo(ccxt.async_support.woo):
27
27
  'watchOrders': True,
28
28
  'watchTicker': True,
29
29
  'watchTickers': True,
30
+ 'watchBidsAsks': True,
30
31
  'watchTrades': True,
31
32
  'watchTradesForSymbols': False,
32
33
  'watchPositions': True,
@@ -383,6 +384,73 @@ class woo(ccxt.async_support.woo):
383
384
  result.append(ticker)
384
385
  client.resolve(result, topic)
385
386
 
387
+ async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
388
+ """
389
+ :see: https://docs.woox.io/#bbos
390
+ watches best bid & ask for symbols
391
+ :param str[] symbols: unified symbol of the market to fetch the ticker for
392
+ :param dict [params]: extra parameters specific to the exchange API endpoint
393
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
394
+ """
395
+ await self.load_markets()
396
+ symbols = self.market_symbols(symbols, None, False)
397
+ name = 'bbos'
398
+ topic = name
399
+ request: dict = {
400
+ 'event': 'subscribe',
401
+ 'topic': topic,
402
+ }
403
+ message = self.extend(request, params)
404
+ tickers = await self.watch_public(topic, message)
405
+ if self.newUpdates:
406
+ return tickers
407
+ return self.filter_by_array(self.bidsasks, 'symbol', symbols)
408
+
409
+ def handle_bid_ask(self, client: Client, message):
410
+ #
411
+ # {
412
+ # "topic": "bbos",
413
+ # "ts": 1618822376000,
414
+ # "data": [
415
+ # {
416
+ # "symbol": "SPOT_FIL_USDT",
417
+ # "ask": 159.0318,
418
+ # "askSize": 370.43,
419
+ # "bid": 158.9158,
420
+ # "bidSize": 16
421
+ # }
422
+ # ]
423
+ # }
424
+ #
425
+ topic = self.safe_string(message, 'topic')
426
+ data = self.safe_list(message, 'data', [])
427
+ timestamp = self.safe_integer(message, 'ts')
428
+ result: dict = {}
429
+ for i in range(0, len(data)):
430
+ ticker = self.safe_dict(data, i)
431
+ ticker['ts'] = timestamp
432
+ parsedTicker = self.parse_ws_bid_ask(ticker)
433
+ symbol = parsedTicker['symbol']
434
+ self.bidsasks[symbol] = parsedTicker
435
+ result[symbol] = parsedTicker
436
+ client.resolve(result, topic)
437
+
438
+ def parse_ws_bid_ask(self, ticker, market=None):
439
+ marketId = self.safe_string(ticker, 'symbol')
440
+ market = self.safe_market(marketId, market)
441
+ symbol = self.safe_string(market, 'symbol')
442
+ timestamp = self.safe_integer(ticker, 'ts')
443
+ return self.safe_ticker({
444
+ 'symbol': symbol,
445
+ 'timestamp': timestamp,
446
+ 'datetime': self.iso8601(timestamp),
447
+ 'ask': self.safe_string(ticker, 'ask'),
448
+ 'askVolume': self.safe_string(ticker, 'askSize'),
449
+ 'bid': self.safe_string(ticker, 'bid'),
450
+ 'bidVolume': self.safe_string(ticker, 'bidSize'),
451
+ 'info': ticker,
452
+ }, market)
453
+
386
454
  async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
387
455
  """
388
456
  watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
@@ -1101,6 +1169,7 @@ class woo(ccxt.async_support.woo):
1101
1169
  'trade': self.handle_trade,
1102
1170
  'balance': self.handle_balance,
1103
1171
  'position': self.handle_positions,
1172
+ 'bbos': self.handle_bid_ask,
1104
1173
  }
1105
1174
  event = self.safe_string(message, 'event')
1106
1175
  method = self.safe_value(methods, event)
ccxt/test/tests_async.py CHANGED
@@ -3,9 +3,37 @@
3
3
  import asyncio
4
4
 
5
5
 
6
- from tests_helpers import AuthenticationError, NotSupported, InvalidProxySettings, ExchangeNotAvailable, OperationFailed, OnMaintenance, get_cli_arg_value, baseMainTestClass, dump, json_parse, json_stringify, convert_ascii, io_file_exists, io_file_read, io_dir_read, call_method, call_method_sync, call_exchange_method_dynamically, call_exchange_method_dynamically_sync, get_root_exception, exception_message, exit_script, get_exchange_prop, set_exchange_prop, init_exchange, get_test_files_sync, get_test_files, set_fetch_response, is_null_value, close # noqa: F401
6
+ from tests_helpers import AuthenticationError, NotSupported, InvalidProxySettings, ExchangeNotAvailable, OperationFailed, OnMaintenance, get_cli_arg_value, ENV_VARS, NEW_LINE, LANG, EXT, ROOT_DIR, PROXY_TEST_FILE_NAME, IS_SYNCHRONOUS, dump, json_parse, json_stringify, convert_ascii, io_file_exists, io_file_read, io_dir_read, call_method, call_method_sync, call_exchange_method_dynamically, call_exchange_method_dynamically_sync, get_root_exception, exception_message, exit_script, get_exchange_prop, set_exchange_prop, init_exchange, get_test_files_sync, get_test_files, set_fetch_response, is_null_value, close # noqa: F401
7
+
8
+ class testMainClass:
9
+ is_synchronous = IS_SYNCHRONOUS
10
+ id_tests = False
11
+ request_tests_failed = False
12
+ response_tests_failed = False
13
+ request_tests = False
14
+ ws_tests = False
15
+ response_tests = False
16
+ static_tests = False
17
+ info = False
18
+ verbose = False
19
+ debug = False
20
+ private_test = False
21
+ private_test_only = False
22
+ load_keys = False
23
+ sandbox = False
24
+ proxy_test_file_name = PROXY_TEST_FILE_NAME
25
+ only_specific_tests = []
26
+ skipped_settings_for_exchange = {}
27
+ skipped_methods = {}
28
+ checked_public_tests = {}
29
+ test_files = {}
30
+ public_tests = {}
31
+ new_line = NEW_LINE
32
+ root_dir = ROOT_DIR
33
+ env_vars = ENV_VARS
34
+ ext = EXT
35
+ lang = LANG
7
36
 
8
- class testMainClass(baseMainTestClass):
9
37
  def parse_cli_args(self):
10
38
  self.response_tests = get_cli_arg_value('--responseTests')
11
39
  self.id_tests = get_cli_arg_value('--idTests')
@@ -117,7 +145,7 @@ class testMainClass(baseMainTestClass):
117
145
  if self.load_keys:
118
146
  self.load_credentials_from_env(exchange)
119
147
  # skipped tests
120
- skipped_file = self.root_dir_for_skips + 'skip-tests.json'
148
+ skipped_file = self.root_dir + 'skip-tests.json'
121
149
  skipped_settings = io_file_read(skipped_file)
122
150
  self.skipped_settings_for_exchange = exchange.safe_value(skipped_settings, exchange_id, {})
123
151
  skipped_settings_for_exchange = self.skipped_settings_for_exchange