ccxt 4.4.92__py2.py3-none-any.whl → 4.4.94__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 (58) hide show
  1. ccxt/__init__.py +1 -1
  2. ccxt/abstract/lbank.py +1 -1
  3. ccxt/ascendex.py +9 -8
  4. ccxt/async_support/__init__.py +1 -1
  5. ccxt/async_support/ascendex.py +9 -8
  6. ccxt/async_support/base/exchange.py +4 -1
  7. ccxt/async_support/base/ws/client.py +3 -0
  8. ccxt/async_support/binance.py +42 -1
  9. ccxt/async_support/bitmex.py +3 -3
  10. ccxt/async_support/bybit.py +83 -10
  11. ccxt/async_support/coinbase.py +4 -1
  12. ccxt/async_support/coinbaseexchange.py +53 -0
  13. ccxt/async_support/coincheck.py +45 -4
  14. ccxt/async_support/coinex.py +16 -12
  15. ccxt/async_support/coinmetro.py +15 -3
  16. ccxt/async_support/cryptomus.py +30 -52
  17. ccxt/async_support/deribit.py +6 -6
  18. ccxt/async_support/exmo.py +64 -52
  19. ccxt/async_support/htx.py +5 -1
  20. ccxt/async_support/hyperliquid.py +126 -33
  21. ccxt/async_support/kucoin.py +12 -14
  22. ccxt/async_support/latoken.py +19 -71
  23. ccxt/async_support/lbank.py +2 -2
  24. ccxt/async_support/okx.py +159 -3
  25. ccxt/async_support/paradex.py +54 -0
  26. ccxt/async_support/phemex.py +3 -3
  27. ccxt/async_support/wavesexchange.py +12 -2
  28. ccxt/base/exchange.py +96 -31
  29. ccxt/binance.py +42 -1
  30. ccxt/bitmex.py +3 -3
  31. ccxt/bybit.py +83 -10
  32. ccxt/coinbase.py +4 -1
  33. ccxt/coinbaseexchange.py +53 -0
  34. ccxt/coincheck.py +45 -4
  35. ccxt/coinex.py +16 -12
  36. ccxt/coinmetro.py +14 -3
  37. ccxt/cryptomus.py +30 -52
  38. ccxt/deribit.py +6 -6
  39. ccxt/exmo.py +64 -52
  40. ccxt/htx.py +5 -1
  41. ccxt/hyperliquid.py +126 -33
  42. ccxt/kucoin.py +12 -14
  43. ccxt/latoken.py +19 -71
  44. ccxt/lbank.py +2 -2
  45. ccxt/okx.py +159 -3
  46. ccxt/paradex.py +54 -0
  47. ccxt/phemex.py +3 -3
  48. ccxt/pro/__init__.py +1 -1
  49. ccxt/pro/bitstamp.py +48 -16
  50. ccxt/pro/bybit.py +2 -1
  51. ccxt/test/tests_async.py +17 -15
  52. ccxt/test/tests_sync.py +17 -15
  53. ccxt/wavesexchange.py +12 -2
  54. {ccxt-4.4.92.dist-info → ccxt-4.4.94.dist-info}/METADATA +4 -4
  55. {ccxt-4.4.92.dist-info → ccxt-4.4.94.dist-info}/RECORD +58 -58
  56. {ccxt-4.4.92.dist-info → ccxt-4.4.94.dist-info}/LICENSE.txt +0 -0
  57. {ccxt-4.4.92.dist-info → ccxt-4.4.94.dist-info}/WHEEL +0 -0
  58. {ccxt-4.4.92.dist-info → ccxt-4.4.94.dist-info}/top_level.txt +0 -0
@@ -138,7 +138,7 @@ class lbank(Exchange, ImplicitAPI):
138
138
  'accuracy': 2.5,
139
139
  'usdToCny': 2.5,
140
140
  'assetConfigs': 2.5,
141
- 'withdrawConfigs': 2.5,
141
+ 'withdrawConfigs': 2.5 * 1.5, # frequently rate-limits, so increase self endpoint RL
142
142
  'timestamp': 2.5,
143
143
  'ticker/24hr': 2.5,
144
144
  'ticker': 2.5,
@@ -675,7 +675,7 @@ class lbank(Exchange, ImplicitAPI):
675
675
  'active': True,
676
676
  'contract': True,
677
677
  'linear': True,
678
- 'inverse': None,
678
+ 'inverse': False,
679
679
  'contractSize': self.safe_number(market, 'volumeMultiple'),
680
680
  'expiry': None,
681
681
  'expiryDatetime': None,
ccxt/async_support/okx.py CHANGED
@@ -81,6 +81,7 @@ class okx(Exchange, ImplicitAPI):
81
81
  'createTriggerOrder': True,
82
82
  'editOrder': True,
83
83
  'fetchAccounts': True,
84
+ 'fetchAllGreeks': True,
84
85
  'fetchBalance': True,
85
86
  'fetchBidsAsks': None,
86
87
  'fetchBorrowInterest': True,
@@ -2556,11 +2557,11 @@ class okx(Exchange, ImplicitAPI):
2556
2557
  # it may be incorrect to use total, free and used for swap accounts
2557
2558
  eq = self.safe_string(balance, 'eq')
2558
2559
  availEq = self.safe_string(balance, 'availEq')
2559
- if (eq is None) or (availEq is None):
2560
+ account['total'] = eq
2561
+ if availEq is None:
2560
2562
  account['free'] = self.safe_string(balance, 'availBal')
2561
2563
  account['used'] = self.safe_string(balance, 'frozenBal')
2562
2564
  else:
2563
- account['total'] = eq
2564
2565
  account['free'] = availEq
2565
2566
  result[code] = account
2566
2567
  result['timestamp'] = timestamp
@@ -2958,7 +2959,8 @@ class okx(Exchange, ImplicitAPI):
2958
2959
  stopLossTriggerPrice = self.safe_value_n(stopLoss, ['triggerPrice', 'stopPrice', 'slTriggerPx'])
2959
2960
  if stopLossTriggerPrice is None:
2960
2961
  raise InvalidOrder(self.id + ' createOrder() requires a trigger price in params["stopLoss"]["triggerPrice"], or params["stopLoss"]["stopPrice"], or params["stopLoss"]["slTriggerPx"] for a stop loss order')
2961
- request['slTriggerPx'] = self.price_to_precision(symbol, stopLossTriggerPrice)
2962
+ slTriggerPx = self.price_to_precision(symbol, stopLossTriggerPrice)
2963
+ request['slTriggerPx'] = slTriggerPx
2962
2964
  stopLossLimitPrice = self.safe_value_n(stopLoss, ['price', 'stopLossPrice', 'slOrdPx'])
2963
2965
  stopLossOrderType = self.safe_string(stopLoss, 'type')
2964
2966
  if stopLossOrderType is not None:
@@ -3024,6 +3026,12 @@ class okx(Exchange, ImplicitAPI):
3024
3026
  # tpOrdKind is 'condition' which is the default
3025
3027
  if twoWayCondition:
3026
3028
  request['ordType'] = 'oco'
3029
+ if side == 'sell':
3030
+ request = self.omit(request, 'tgtCcy')
3031
+ if self.safe_string(request, 'tdMode') == 'cash':
3032
+ # for some reason tdMode = cash throws
3033
+ # {"code":"1","data":[{"algoClOrdId":"","algoId":"","clOrdId":"","sCode":"51000","sMsg":"Parameter tdMode error ","tag":""}],"msg":""}
3034
+ request['tdMode'] = marginMode
3027
3035
  if takeProfitPrice is not None:
3028
3036
  request['tpTriggerPx'] = self.price_to_precision(symbol, takeProfitPrice)
3029
3037
  tpOrdPxReq = '-1'
@@ -3614,6 +3622,84 @@ class okx(Exchange, ImplicitAPI):
3614
3622
  # "uTime": "1621910749815"
3615
3623
  # }
3616
3624
  #
3625
+ # watchOrders & fetchClosedOrders
3626
+ #
3627
+ # {
3628
+ # "algoClOrdId": "",
3629
+ # "algoId": "",
3630
+ # "attachAlgoClOrdId": "",
3631
+ # "attachAlgoOrds": [],
3632
+ # "cancelSource": "",
3633
+ # "cancelSourceReason": "", # not present in WS, but present in fetchClosedOrders
3634
+ # "category": "normal",
3635
+ # "ccy": "", # empty in WS, but eg. `USDT` in fetchClosedOrders
3636
+ # "clOrdId": "",
3637
+ # "cTime": "1751705801423",
3638
+ # "feeCcy": "USDT",
3639
+ # "instId": "LINK-USDT-SWAP",
3640
+ # "instType": "SWAP",
3641
+ # "isTpLimit": "false",
3642
+ # "lever": "3",
3643
+ # "linkedAlgoOrd": {"algoId": ""},
3644
+ # "ordId": "2657625147249614848",
3645
+ # "ordType": "limit",
3646
+ # "posSide": "net",
3647
+ # "px": "13.142",
3648
+ # "pxType": "",
3649
+ # "pxUsd": "",
3650
+ # "pxVol": "",
3651
+ # "quickMgnType": "",
3652
+ # "rebate": "0",
3653
+ # "rebateCcy": "USDT",
3654
+ # "reduceOnly": "true",
3655
+ # "side": "sell",
3656
+ # "slOrdPx": "",
3657
+ # "slTriggerPx": "",
3658
+ # "slTriggerPxType": "",
3659
+ # "source": "",
3660
+ # "stpId": "",
3661
+ # "stpMode": "cancel_maker",
3662
+ # "sz": "0.1",
3663
+ # "tag": "",
3664
+ # "tdMode": "isolated",
3665
+ # "tgtCcy": "",
3666
+ # "tpOrdPx": "",
3667
+ # "tpTriggerPx": "",
3668
+ # "tpTriggerPxType": "",
3669
+ # "uTime": "1751705807467",
3670
+ # "reqId": "", # field present only in WS
3671
+ # "msg": "", # field present only in WS
3672
+ # "amendResult": "", # field present only in WS
3673
+ # "amendSource": "", # field present only in WS
3674
+ # "code": "0", # field present only in WS
3675
+ # "fillFwdPx": "", # field present only in WS
3676
+ # "fillMarkVol": "", # field present only in WS
3677
+ # "fillPxUsd": "", # field present only in WS
3678
+ # "fillPxVol": "", # field present only in WS
3679
+ # "lastPx": "13.142", # field present only in WS
3680
+ # "notionalUsd": "1.314515408", # field present only in WS
3681
+ #
3682
+ # #### these below fields are empty on first omit from websocket, because of "creation" event. however, if order is executed, it also immediately sends another update with these fields filled ###
3683
+ #
3684
+ # "pnl": "-0.0001",
3685
+ # "accFillSz": "0.1",
3686
+ # "avgPx": "13.142",
3687
+ # "state": "filled",
3688
+ # "fee": "-0.00026284",
3689
+ # "fillPx": "13.142",
3690
+ # "tradeId": "293429690",
3691
+ # "fillSz": "0.1",
3692
+ # "fillTime": "1751705807467",
3693
+ # "fillNotionalUsd": "1.314515408", # field present only in WS
3694
+ # "fillPnl": "-0.0001", # field present only in WS
3695
+ # "fillFee": "-0.00026284", # field present only in WS
3696
+ # "fillFeeCcy": "USDT", # field present only in WS
3697
+ # "execType": "M", # field present only in WS
3698
+ # "fillMarkPx": "13.141", # field present only in WS
3699
+ # "fillIdxPx": "13.147" # field present only in WS
3700
+ # }
3701
+ #
3702
+ #
3617
3703
  # Algo Order fetchOpenOrders, fetchCanceledOrders, fetchClosedOrders
3618
3704
  #
3619
3705
  # {
@@ -7548,6 +7634,76 @@ class okx(Exchange, ImplicitAPI):
7548
7634
  return self.parse_greeks(entry, market)
7549
7635
  return None
7550
7636
 
7637
+ async def fetch_all_greeks(self, symbols: Strings = None, params={}) -> List[Greeks]:
7638
+ """
7639
+ fetches all option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
7640
+
7641
+ https://www.okx.com/docs-v5/en/#public-data-rest-api-get-option-market-data
7642
+
7643
+ :param str[] [symbols]: unified symbols of the markets to fetch greeks for, all markets are returned if not assigned
7644
+ :param dict [params]: extra parameters specific to the exchange API endpoint
7645
+ :param str params['uly']: Underlying, either uly or instFamily is required
7646
+ :param str params['instFamily']: Instrument family, either uly or instFamily is required
7647
+ :returns dict: a `greeks structure <https://docs.ccxt.com/#/?id=greeks-structure>`
7648
+ """
7649
+ await self.load_markets()
7650
+ request: dict = {}
7651
+ symbols = self.market_symbols(symbols, None, True, True, True)
7652
+ symbolsLength = None
7653
+ if symbols is not None:
7654
+ symbolsLength = len(symbols)
7655
+ if (symbols is None) or (symbolsLength != 1):
7656
+ uly = self.safe_string(params, 'uly')
7657
+ if uly is not None:
7658
+ request['uly'] = uly
7659
+ instFamily = self.safe_string(params, 'instFamily')
7660
+ if instFamily is not None:
7661
+ request['instFamily'] = instFamily
7662
+ if (uly is None) and (instFamily is None):
7663
+ raise BadRequest(self.id + ' fetchAllGreeks() requires either a uly or instFamily parameter')
7664
+ market = None
7665
+ if symbols is not None:
7666
+ if symbolsLength == 1:
7667
+ market = self.market(symbols[0])
7668
+ marketId = market['id']
7669
+ optionParts = marketId.split('-')
7670
+ request['uly'] = market['info']['uly']
7671
+ request['instFamily'] = market['info']['instFamily']
7672
+ request['expTime'] = self.safe_string(optionParts, 2)
7673
+ params = self.omit(params, ['uly', 'instFamily'])
7674
+ response = await self.publicGetPublicOptSummary(self.extend(request, params))
7675
+ #
7676
+ # {
7677
+ # "code": "0",
7678
+ # "data": [
7679
+ # {
7680
+ # "askVol": "0",
7681
+ # "bidVol": "0",
7682
+ # "delta": "0.5105464486882039",
7683
+ # "deltaBS": "0.7325502184143025",
7684
+ # "fwdPx": "37675.80158694987186",
7685
+ # "gamma": "-0.13183515090501083",
7686
+ # "gammaBS": "0.000024139685826358558",
7687
+ # "instId": "BTC-USD-240329-32000-C",
7688
+ # "instType": "OPTION",
7689
+ # "lever": "4.504428015946619",
7690
+ # "markVol": "0.5916253554539876",
7691
+ # "realVol": "0",
7692
+ # "theta": "-0.0004202992014012855",
7693
+ # "thetaBS": "-18.52354631567909",
7694
+ # "ts": "1699586421976",
7695
+ # "uly": "BTC-USD",
7696
+ # "vega": "0.0020207455080045846",
7697
+ # "vegaBS": "74.44022302387287",
7698
+ # "volLv": "0.5948549730405797"
7699
+ # },
7700
+ # ],
7701
+ # "msg": ""
7702
+ # }
7703
+ #
7704
+ data = self.safe_list(response, 'data', [])
7705
+ return self.parse_all_greeks(data, symbols)
7706
+
7551
7707
  def parse_greeks(self, greeks: dict, market: Market = None) -> Greeks:
7552
7708
  #
7553
7709
  # {
@@ -57,6 +57,7 @@ class paradex(Exchange, ImplicitAPI):
57
57
  'createTriggerOrder': True,
58
58
  'editOrder': False,
59
59
  'fetchAccounts': False,
60
+ 'fetchAllGreeks': True,
60
61
  'fetchBalance': True,
61
62
  'fetchBorrowInterest': False,
62
63
  'fetchBorrowRateHistories': False,
@@ -2346,6 +2347,59 @@ class paradex(Exchange, ImplicitAPI):
2346
2347
  greeks = self.safe_dict(data, 0, {})
2347
2348
  return self.parse_greeks(greeks, market)
2348
2349
 
2350
+ async def fetch_all_greeks(self, symbols: Strings = None, params={}) -> List[Greeks]:
2351
+ """
2352
+ fetches all option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
2353
+
2354
+ https://docs.api.testnet.paradex.trade/#list-available-markets-summary
2355
+
2356
+ :param str[] [symbols]: unified symbols of the markets to fetch greeks for, all markets are returned if not assigned
2357
+ :param dict [params]: extra parameters specific to the exchange API endpoint
2358
+ :returns dict: a `greeks structure <https://docs.ccxt.com/#/?id=greeks-structure>`
2359
+ """
2360
+ await self.load_markets()
2361
+ symbols = self.market_symbols(symbols, None, True, True, True)
2362
+ request: dict = {
2363
+ 'market': 'ALL',
2364
+ }
2365
+ response = await self.publicGetMarketsSummary(self.extend(request, params))
2366
+ #
2367
+ # {
2368
+ # "results": [
2369
+ # {
2370
+ # "symbol": "BTC-USD-114000-P",
2371
+ # "mark_price": "10835.66892602",
2372
+ # "mark_iv": "0.71781855",
2373
+ # "delta": "-0.98726024",
2374
+ # "greeks": {
2375
+ # "delta": "-0.9872602390817709",
2376
+ # "gamma": "0.000004560958862297231",
2377
+ # "vega": "227.11344863639806",
2378
+ # "rho": "-302.0617972461581",
2379
+ # "vanna": "0.06609830491614832",
2380
+ # "volga": "925.9501532805552"
2381
+ # },
2382
+ # "last_traded_price": "10551.5",
2383
+ # "bid": "10794.9",
2384
+ # "bid_iv": "0.05",
2385
+ # "ask": "10887.3",
2386
+ # "ask_iv": "0.8783283",
2387
+ # "last_iv": "0.05",
2388
+ # "volume_24h": "0",
2389
+ # "total_volume": "195240.72672261014",
2390
+ # "created_at": 1747644009995,
2391
+ # "underlying_price": "103164.79162649",
2392
+ # "open_interest": "0",
2393
+ # "funding_rate": "0.000004464241170536191",
2394
+ # "price_change_rate_24h": "0.074915",
2395
+ # "future_funding_rate": "0.0001"
2396
+ # }
2397
+ # ]
2398
+ # }
2399
+ #
2400
+ results = self.safe_list(response, 'results', [])
2401
+ return self.parse_all_greeks(results, symbols)
2402
+
2349
2403
  def parse_greeks(self, greeks: dict, market: Market = None) -> Greeks:
2350
2404
  #
2351
2405
  # {
@@ -2659,14 +2659,14 @@ class phemex(Exchange, ImplicitAPI):
2659
2659
  triggerDirection = None
2660
2660
  triggerDirection, params = self.handle_param_string(params, 'triggerDirection')
2661
2661
  if triggerDirection is None:
2662
- raise ArgumentsRequired(self.id + " createOrder() also requires a 'triggerDirection' parameter with either 'up' or 'down' value")
2662
+ raise ArgumentsRequired(self.id + " createOrder() also requires a 'triggerDirection' parameter with either 'ascending' or 'descending' value")
2663
2663
  # the flow defined per https://phemex-docs.github.io/#more-order-type-examples
2664
- if triggerDirection == 'up':
2664
+ if triggerDirection == 'ascending' or triggerDirection == 'up':
2665
2665
  if side == 'sell':
2666
2666
  request['ordType'] = 'MarketIfTouched' if (type == 'Market') else 'LimitIfTouched'
2667
2667
  elif side == 'buy':
2668
2668
  request['ordType'] = 'Stop' if (type == 'Market') else 'StopLimit'
2669
- elif triggerDirection == 'down':
2669
+ elif triggerDirection == 'descending' or triggerDirection == 'down':
2670
2670
  if side == 'sell':
2671
2671
  request['ordType'] = 'Stop' if (type == 'Market') else 'StopLimit'
2672
2672
  elif side == 'buy':
@@ -2215,11 +2215,21 @@ class wavesexchange(Exchange, ImplicitAPI):
2215
2215
  order1 = self.safe_value(data, 'order1')
2216
2216
  order2 = self.safe_value(data, 'order2')
2217
2217
  order = None
2218
- # order2 arrived after order1
2218
+ # at first, detect if response is from `fetch_my_trades`
2219
2219
  if self.safe_string(order1, 'senderPublicKey') == self.apiKey:
2220
2220
  order = order1
2221
- else:
2221
+ elif self.safe_string(order2, 'senderPublicKey') == self.apiKey:
2222
2222
  order = order2
2223
+ else:
2224
+ # response is from `fetch_trades`, so find only taker order
2225
+ date1 = self.safe_string(order1, 'timestamp')
2226
+ date2 = self.safe_string(order2, 'timestamp')
2227
+ ts1 = self.parse8601(date1)
2228
+ ts2 = self.parse8601(date2)
2229
+ if ts1 > ts2:
2230
+ order = order1
2231
+ else:
2232
+ order = order2
2223
2233
  symbol = None
2224
2234
  assetPair = self.safe_value(order, 'assetPair')
2225
2235
  if assetPair is not None:
ccxt/base/exchange.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.4.92'
7
+ __version__ = '4.4.94'
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
@@ -3808,8 +3808,7 @@ class Exchange(object):
3808
3808
 
3809
3809
  def safe_ticker(self, ticker: dict, market: Market = None):
3810
3810
  open = self.omit_zero(self.safe_string(ticker, 'open'))
3811
- close = self.omit_zero(self.safe_string(ticker, 'close'))
3812
- last = self.omit_zero(self.safe_string(ticker, 'last'))
3811
+ close = self.omit_zero(self.safe_string_2(ticker, 'close', 'last'))
3813
3812
  change = self.omit_zero(self.safe_string(ticker, 'change'))
3814
3813
  percentage = self.omit_zero(self.safe_string(ticker, 'percentage'))
3815
3814
  average = self.omit_zero(self.safe_string(ticker, 'average'))
@@ -3818,29 +3817,52 @@ class Exchange(object):
3818
3817
  quoteVolume = self.safe_string(ticker, 'quoteVolume')
3819
3818
  if vwap is None:
3820
3819
  vwap = Precise.string_div(self.omit_zero(quoteVolume), baseVolume)
3821
- if (last is not None) and (close is None):
3822
- close = last
3823
- elif (last is None) and (close is not None):
3824
- last = close
3825
- if (last is not None) and (open is not None):
3826
- if change is None:
3827
- change = Precise.string_sub(last, open)
3828
- if average is None:
3820
+ # calculate open
3821
+ if change is not None:
3822
+ if close is None and average is not None:
3823
+ close = Precise.string_add(average, Precise.string_div(change, '2'))
3824
+ if open is None and close is not None:
3825
+ open = Precise.string_sub(close, change)
3826
+ elif percentage is not None:
3827
+ if close is None and average is not None:
3828
+ openAddClose = Precise.string_mul(average, '2')
3829
+ # openAddClose = open * (1 + (100 + percentage)/100)
3830
+ denominator = Precise.string_add('2', Precise.string_div(percentage, '100'))
3831
+ calcOpen = open if (open is not None) else Precise.string_div(openAddClose, denominator)
3832
+ close = Precise.string_mul(calcOpen, Precise.string_add('1', Precise.string_div(percentage, '100')))
3833
+ if open is None and close is not None:
3834
+ open = Precise.string_div(close, Precise.string_add('1', Precise.string_div(percentage, '100')))
3835
+ # change
3836
+ if change is None:
3837
+ if close is not None and open is not None:
3838
+ change = Precise.string_sub(close, open)
3839
+ elif close is not None and percentage is not None:
3840
+ change = Precise.string_mul(Precise.string_div(percentage, '100'), Precise.string_div(close, '100'))
3841
+ elif open is not None and percentage is not None:
3842
+ change = Precise.string_mul(open, Precise.string_div(percentage, '100'))
3843
+ # calculate things according to "open"(similar can be done with "close")
3844
+ if open is not None:
3845
+ # percentage(using change)
3846
+ if percentage is None and change is not None:
3847
+ percentage = Precise.string_mul(Precise.string_div(change, open), '100')
3848
+ # close(using change)
3849
+ if close is None and change is not None:
3850
+ close = Precise.string_add(open, change)
3851
+ # close(using average)
3852
+ if close is None and average is not None:
3853
+ close = Precise.string_mul(average, '2')
3854
+ # average
3855
+ if average is None and close is not None:
3829
3856
  precision = 18
3830
3857
  if market is not None and self.is_tick_precision():
3831
3858
  marketPrecision = self.safe_dict(market, 'precision')
3832
3859
  precisionPrice = self.safe_string(marketPrecision, 'price')
3833
3860
  if precisionPrice is not None:
3834
3861
  precision = self.precision_from_string(precisionPrice)
3835
- average = Precise.string_div(Precise.string_add(last, open), '2', precision)
3836
- if (percentage is None) and (change is not None) and (open is not None) and Precise.string_gt(open, '0'):
3837
- percentage = Precise.string_mul(Precise.string_div(change, open), '100')
3838
- if (change is None) and (percentage is not None) and (open is not None):
3839
- change = Precise.string_div(Precise.string_mul(percentage, open), '100')
3840
- if (open is None) and (last is not None) and (change is not None):
3841
- open = Precise.string_sub(last, change)
3862
+ average = Precise.string_div(Precise.string_add(open, close), '2', precision)
3842
3863
  # timestamp and symbol operations don't belong in safeTicker
3843
3864
  # they should be done in the derived classes
3865
+ closeParsed = self.parse_number(self.omit_zero(close))
3844
3866
  return self.extend(ticker, {
3845
3867
  'bid': self.parse_number(self.omit_zero(self.safe_string(ticker, 'bid'))),
3846
3868
  'bidVolume': self.safe_number(ticker, 'bidVolume'),
@@ -3849,8 +3871,8 @@ class Exchange(object):
3849
3871
  'high': self.parse_number(self.omit_zero(self.safe_string(ticker, 'high'))),
3850
3872
  'low': self.parse_number(self.omit_zero(self.safe_string(ticker, 'low'))),
3851
3873
  'open': self.parse_number(self.omit_zero(open)),
3852
- 'close': self.parse_number(self.omit_zero(close)),
3853
- 'last': self.parse_number(self.omit_zero(last)),
3874
+ 'close': closeParsed,
3875
+ 'last': closeParsed,
3854
3876
  'change': self.parse_number(change),
3855
3877
  'percentage': self.parse_number(percentage),
3856
3878
  'average': self.parse_number(average),
@@ -4229,7 +4251,7 @@ class Exchange(object):
4229
4251
  return self.filter_by_since_limit(sorted, since, limit, 0, tail)
4230
4252
 
4231
4253
  def parse_leverage_tiers(self, response: Any, symbols: List[str] = None, marketIdKey=None):
4232
- # marketIdKey should only be None when response is a dictionary
4254
+ # marketIdKey should only be None when response is a dictionary.
4233
4255
  symbols = self.market_symbols(symbols)
4234
4256
  tiers = {}
4235
4257
  symbolsLength = 0
@@ -5450,6 +5472,9 @@ class Exchange(object):
5450
5472
  def fetch_greeks(self, symbol: str, params={}):
5451
5473
  raise NotSupported(self.id + ' fetchGreeks() is not supported yet')
5452
5474
 
5475
+ def fetch_all_greeks(self, symbols: Strings = None, params={}):
5476
+ raise NotSupported(self.id + ' fetchAllGreeks() is not supported yet')
5477
+
5453
5478
  def fetch_option_chain(self, code: str, params={}):
5454
5479
  raise NotSupported(self.id + ' fetchOptionChain() is not supported yet')
5455
5480
 
@@ -5703,10 +5728,16 @@ class Exchange(object):
5703
5728
  precisionNumber = int(precision)
5704
5729
  if precisionNumber == 0:
5705
5730
  return '1'
5706
- parsedPrecision = '0.'
5707
- for i in range(0, precisionNumber - 1):
5708
- parsedPrecision = parsedPrecision + '0'
5709
- return parsedPrecision + '1'
5731
+ if precisionNumber > 0:
5732
+ parsedPrecision = '0.'
5733
+ for i in range(0, precisionNumber - 1):
5734
+ parsedPrecision = parsedPrecision + '0'
5735
+ return parsedPrecision + '1'
5736
+ else:
5737
+ parsedPrecision = '1'
5738
+ for i in range(0, precisionNumber * -1 - 1):
5739
+ parsedPrecision = parsedPrecision + '0'
5740
+ return parsedPrecision + '0'
5710
5741
 
5711
5742
  def integer_precision_to_amount(self, precision: Str):
5712
5743
  """
@@ -6682,6 +6713,27 @@ class Exchange(object):
6682
6713
  def parse_greeks(self, greeks: dict, market: Market = None):
6683
6714
  raise NotSupported(self.id + ' parseGreeks() is not supported yet')
6684
6715
 
6716
+ def parse_all_greeks(self, greeks, symbols: Strings = None, params={}):
6717
+ #
6718
+ # the value of greeks is either a dict or a list
6719
+ #
6720
+ results = []
6721
+ if isinstance(greeks, list):
6722
+ for i in range(0, len(greeks)):
6723
+ parsedTicker = self.parse_greeks(greeks[i])
6724
+ greek = self.extend(parsedTicker, params)
6725
+ results.append(greek)
6726
+ else:
6727
+ marketIds = list(greeks.keys())
6728
+ for i in range(0, len(marketIds)):
6729
+ marketId = marketIds[i]
6730
+ market = self.safe_market(marketId)
6731
+ parsed = self.parse_greeks(greeks[marketId], market)
6732
+ greek = self.extend(parsed, params)
6733
+ results.append(greek)
6734
+ symbols = self.market_symbols(symbols)
6735
+ return self.filter_by_array(results, 'symbol', symbols)
6736
+
6685
6737
  def parse_option(self, chain: dict, currency: Currency = None, market: Market = None):
6686
6738
  raise NotSupported(self.id + ' parseOption() is not supported yet')
6687
6739
 
@@ -6883,14 +6935,27 @@ class Exchange(object):
6883
6935
  """
6884
6936
  raise NotSupported(self.id + ' fetchTransfers() is not supported yet')
6885
6937
 
6886
- def clean_unsubscription(self, client, subHash: str, unsubHash: str):
6938
+ def clean_unsubscription(self, client, subHash: str, unsubHash: str, subHashIsPrefix=False):
6887
6939
  if unsubHash in client.subscriptions:
6888
6940
  del client.subscriptions[unsubHash]
6889
- if subHash in client.subscriptions:
6890
- del client.subscriptions[subHash]
6891
- if subHash in client.futures:
6892
- error = UnsubscribeError(self.id + ' ' + subHash)
6893
- client.reject(error, subHash)
6941
+ if not subHashIsPrefix:
6942
+ if subHash in client.subscriptions:
6943
+ del client.subscriptions[subHash]
6944
+ if subHash in client.futures:
6945
+ error = UnsubscribeError(self.id + ' ' + subHash)
6946
+ client.reject(error, subHash)
6947
+ else:
6948
+ clientSubscriptions = list(client.subscriptions.keys())
6949
+ for i in range(0, len(clientSubscriptions)):
6950
+ sub = clientSubscriptions[i]
6951
+ if sub.startswith(subHash):
6952
+ del client.subscriptions[sub]
6953
+ clientFutures = list(client.futures.keys())
6954
+ for i in range(0, len(clientFutures)):
6955
+ future = clientFutures[i]
6956
+ if future.startswith(subHash):
6957
+ error = UnsubscribeError(self.id + ' ' + future)
6958
+ client.reject(error, future)
6894
6959
  client.resolve(True, unsubHash)
6895
6960
 
6896
6961
  def clean_cache(self, subscription: dict):
ccxt/binance.py CHANGED
@@ -87,6 +87,7 @@ class binance(Exchange, ImplicitAPI):
87
87
  'editOrder': True,
88
88
  'editOrders': True,
89
89
  'fetchAccounts': None,
90
+ 'fetchAllGreeks': True,
90
91
  'fetchBalance': True,
91
92
  'fetchBidsAsks': True,
92
93
  'fetchBorrowInterest': True,
@@ -10777,6 +10778,7 @@ class binance(Exchange, ImplicitAPI):
10777
10778
  request: dict = {}
10778
10779
  if symbol is not None:
10779
10780
  request['symbol'] = market['id']
10781
+ symbol = market['symbol']
10780
10782
  if since is not None:
10781
10783
  request['startTime'] = since
10782
10784
  if limit is not None:
@@ -10804,7 +10806,7 @@ class binance(Exchange, ImplicitAPI):
10804
10806
  #
10805
10807
  settlements = self.parse_settlements(response, market)
10806
10808
  sorted = self.sort_by(settlements, 'timestamp')
10807
- return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit)
10809
+ return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
10808
10810
 
10809
10811
  def parse_settlement(self, settlement, market):
10810
10812
  #
@@ -12422,6 +12424,45 @@ class binance(Exchange, ImplicitAPI):
12422
12424
  #
12423
12425
  return self.parse_greeks(response[0], market)
12424
12426
 
12427
+ def fetch_all_greeks(self, symbols: Strings = None, params={}) -> List[Greeks]:
12428
+ """
12429
+ fetches all option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
12430
+
12431
+ https://developers.binance.com/docs/derivatives/option/market-data/Option-Mark-Price
12432
+
12433
+ :param str[] [symbols]: unified symbols of the markets to fetch greeks for, all markets are returned if not assigned
12434
+ :param dict [params]: extra parameters specific to the exchange API endpoint
12435
+ :returns dict: a `greeks structure <https://docs.ccxt.com/#/?id=greeks-structure>`
12436
+ """
12437
+ self.load_markets()
12438
+ symbols = self.market_symbols(symbols, None, True, True, True)
12439
+ request: dict = {}
12440
+ market = None
12441
+ if symbols is not None:
12442
+ symbolsLength = len(symbols)
12443
+ if symbolsLength == 1:
12444
+ market = self.market(symbols[0])
12445
+ request['symbol'] = market['id']
12446
+ response = self.eapiPublicGetMark(self.extend(request, params))
12447
+ #
12448
+ # [
12449
+ # {
12450
+ # "symbol": "BTC-231229-40000-C",
12451
+ # "markPrice": "2012",
12452
+ # "bidIV": "0.60236275",
12453
+ # "askIV": "0.62267244",
12454
+ # "markIV": "0.6125176",
12455
+ # "delta": "0.39111646",
12456
+ # "theta": "-32.13948531",
12457
+ # "gamma": "0.00004656",
12458
+ # "vega": "51.70062218",
12459
+ # "highPriceLimit": "6474",
12460
+ # "lowPriceLimit": "5"
12461
+ # }
12462
+ # ]
12463
+ #
12464
+ return self.parse_all_greeks(response, symbols)
12465
+
12425
12466
  def parse_greeks(self, greeks: dict, market: Market = None) -> Greeks:
12426
12467
  #
12427
12468
  # {
ccxt/bitmex.py CHANGED
@@ -1924,7 +1924,7 @@ class bitmex(Exchange, ImplicitAPI):
1924
1924
  :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
1925
1925
  :param dict [params]: extra parameters specific to the exchange API endpoint
1926
1926
  :param dict [params.triggerPrice]: the price at which a trigger order is triggered at
1927
- :param dict [params.triggerDirection]: the direction whenever the trigger happens with relation to price - 'above' or 'below'
1927
+ :param dict [params.triggerDirection]: the direction whenever the trigger happens with relation to price - 'ascending' or 'descending'
1928
1928
  :param float [params.trailingAmount]: the quote amount to trail away from the current market price
1929
1929
  :returns dict: an `order structure <https://github.com/ccxt/ccxt/wiki/Manual#order-structure>`
1930
1930
  """
@@ -1951,7 +1951,7 @@ class bitmex(Exchange, ImplicitAPI):
1951
1951
  isTrailingAmountOrder = trailingAmount is not None
1952
1952
  if isTriggerOrder or isTrailingAmountOrder:
1953
1953
  triggerDirection = self.safe_string(params, 'triggerDirection')
1954
- triggerAbove = (triggerDirection == 'above')
1954
+ triggerAbove = ((triggerDirection == 'ascending') or (triggerDirection == 'above'))
1955
1955
  if (type == 'limit') or (type == 'market'):
1956
1956
  self.check_required_argument('createOrder', triggerDirection, 'triggerDirection', ['above', 'below'])
1957
1957
  if type == 'limit':
@@ -1994,7 +1994,7 @@ class bitmex(Exchange, ImplicitAPI):
1994
1994
  isTrailingAmountOrder = trailingAmount is not None
1995
1995
  if isTrailingAmountOrder:
1996
1996
  triggerDirection = self.safe_string(params, 'triggerDirection')
1997
- triggerAbove = (triggerDirection == 'above')
1997
+ triggerAbove = ((triggerDirection == 'ascending') or (triggerDirection == 'above'))
1998
1998
  if (type == 'limit') or (type == 'market'):
1999
1999
  self.check_required_argument('createOrder', triggerDirection, 'triggerDirection', ['above', 'below'])
2000
2000
  orderType = None