gate-io-api 0.0.65__py3-none-any.whl → 0.0.100__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.

Potentially problematic release.


This version of gate-io-api might be problematic. Click here for more details.

@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.4.86'
7
+ __version__ = '4.5.15'
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
@@ -33,7 +33,7 @@ from ccxt.base.decimal_to_precision import decimal_to_precision
33
33
  from ccxt.base.decimal_to_precision import DECIMAL_PLACES, TICK_SIZE, NO_PADDING, TRUNCATE, ROUND, ROUND_UP, ROUND_DOWN, SIGNIFICANT_DIGITS
34
34
  from ccxt.base.decimal_to_precision import number_to_string
35
35
  from ccxt.base.precise import Precise
36
- from ccxt.base.types import ConstructorArgs, BalanceAccount, Currency, IndexType, OrderSide, OrderType, Trade, OrderRequest, Market, MarketType, Str, Num, Strings, CancellationRequest, Bool
36
+ from ccxt.base.types import ConstructorArgs, BalanceAccount, Currency, IndexType, OrderSide, OrderType, Trade, OrderRequest, Market, MarketType, Str, Num, Strings, CancellationRequest, Bool, Order
37
37
 
38
38
  # -----------------------------------------------------------------------------
39
39
 
@@ -50,6 +50,11 @@ from cryptography.hazmat.primitives.serialization import load_pem_private_key
50
50
  from ccxt.static_dependencies import ecdsa
51
51
  from ccxt.static_dependencies import keccak
52
52
 
53
+ try:
54
+ import coincurve
55
+ except ImportError:
56
+ coincurve = None
57
+
53
58
  # eddsa signing
54
59
  try:
55
60
  import axolotl_curve25519 as eddsa
@@ -229,6 +234,7 @@ class Exchange(object):
229
234
  'chrome100': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36',
230
235
  }
231
236
  headers = None
237
+ returnResponseHeaders = False
232
238
  origin = '*' # CORS origin
233
239
  MAX_VALUE = float('inf')
234
240
  #
@@ -310,7 +316,7 @@ class Exchange(object):
310
316
  bidsasks = None
311
317
  base_currencies = None
312
318
  quote_currencies = None
313
- currencies = None
319
+ currencies = {}
314
320
  options = None # Python does not allow to define properties in run-time with setattr
315
321
  isSandboxModeEnabled = False
316
322
  accounts = None
@@ -578,6 +584,8 @@ class Exchange(object):
578
584
  if self.verbose:
579
585
  self.log("\nfetch Response:", self.id, method, url, http_status_code, "ResponseHeaders:", headers, "ResponseBody:", http_response)
580
586
  self.logger.debug("%s %s, Response: %s %s %s", method, url, http_status_code, headers, http_response)
587
+ if json_response and not isinstance(json_response, list) and self.returnResponseHeaders:
588
+ json_response['responseHeaders'] = headers
581
589
  response.raise_for_status()
582
590
 
583
591
  except Timeout as e:
@@ -905,6 +913,10 @@ class Exchange(object):
905
913
  def keysort(dictionary):
906
914
  return collections.OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
907
915
 
916
+ @staticmethod
917
+ def sort(array):
918
+ return sorted(array)
919
+
908
920
  @staticmethod
909
921
  def extend(*args):
910
922
  if args is not None:
@@ -955,6 +967,11 @@ class Exchange(object):
955
967
  def groupBy(array, key):
956
968
  return Exchange.group_by(array, key)
957
969
 
970
+
971
+ @staticmethod
972
+ def index_by_safe(array, key):
973
+ return Exchange.index_by(array, key) # wrapper for go
974
+
958
975
  @staticmethod
959
976
  def index_by(array, key):
960
977
  result = {}
@@ -1036,7 +1053,7 @@ class Exchange(object):
1036
1053
  return _urlencode.urlencode(result, quote_via=_urlencode.quote)
1037
1054
 
1038
1055
  @staticmethod
1039
- def rawencode(params={}):
1056
+ def rawencode(params={}, sort=False):
1040
1057
  return _urlencode.unquote(Exchange.urlencode(params))
1041
1058
 
1042
1059
  @staticmethod
@@ -1296,6 +1313,10 @@ class Exchange(object):
1296
1313
  elif algoType == 'ES':
1297
1314
  rawSignature = Exchange.ecdsa(token, secret, 'p256', algorithm)
1298
1315
  signature = Exchange.base16_to_binary(rawSignature['r'].rjust(64, "0") + rawSignature['s'].rjust(64, "0"))
1316
+ elif algoType == 'Ed':
1317
+ signature = Exchange.eddsa(token.encode('utf-8'), secret, 'ed25519', True)
1318
+ # here the signature is already a urlencoded base64-encoded string
1319
+ return token + '.' + signature
1299
1320
  else:
1300
1321
  signature = Exchange.hmac(Exchange.encode(token), secret, algos[algorithm], 'binary')
1301
1322
  return token + '.' + Exchange.urlencode_base64(signature)
@@ -1366,9 +1387,9 @@ class Exchange(object):
1366
1387
  return msgHash
1367
1388
 
1368
1389
  @staticmethod
1369
- def starknet_sign (hash, pri):
1390
+ def starknet_sign (msg_hash, pri):
1370
1391
  # // TODO: unify to ecdsa
1371
- r, s = message_signature(hash, pri)
1392
+ r, s = message_signature(msg_hash, pri)
1372
1393
  return Exchange.json([hex(r), hex(s)])
1373
1394
 
1374
1395
  @staticmethod
@@ -1385,6 +1406,21 @@ class Exchange(object):
1385
1406
 
1386
1407
  @staticmethod
1387
1408
  def ecdsa(request, secret, algorithm='p256', hash=None, fixed_length=False):
1409
+ """
1410
+ ECDSA signing with support for multiple algorithms and coincurve for SECP256K1.
1411
+ Args:
1412
+ request: The message to sign
1413
+ secret: The private key (hex string or PEM format)
1414
+ algorithm: The elliptic curve algorithm ('p192', 'p224', 'p256', 'p384', 'p521', 'secp256k1')
1415
+ hash: The hash function to use (defaults to algorithm-specific hash)
1416
+ fixed_length: Whether to ensure fixed-length signatures (for deterministic signing)
1417
+ Note: coincurve produces non-deterministic signatures
1418
+ Returns:
1419
+ dict: {'r': r_value, 's': s_value, 'v': v_value}
1420
+ Note:
1421
+ If coincurve is not available or fails for SECP256K1, the method automatically
1422
+ falls back to the standard ecdsa implementation.
1423
+ """
1388
1424
  # your welcome - frosty00
1389
1425
  algorithms = {
1390
1426
  'p192': [ecdsa.NIST192p, 'sha256'],
@@ -1396,6 +1432,14 @@ class Exchange(object):
1396
1432
  }
1397
1433
  if algorithm not in algorithms:
1398
1434
  raise ArgumentsRequired(algorithm + ' is not a supported algorithm')
1435
+ # Use coincurve for SECP256K1 if available
1436
+ if algorithm == 'secp256k1' and coincurve is not None:
1437
+ try:
1438
+ return Exchange._ecdsa_secp256k1_coincurve(request, secret, hash, fixed_length)
1439
+ except Exception:
1440
+ # If coincurve fails, fall back to ecdsa implementation
1441
+ pass
1442
+ # Fall back to original ecdsa implementation for other algorithms or when deterministic signing is needed
1399
1443
  curve_info = algorithms[algorithm]
1400
1444
  hash_function = getattr(hashlib, curve_info[1])
1401
1445
  encoded_request = Exchange.encode(request)
@@ -1429,12 +1473,69 @@ class Exchange(object):
1429
1473
  'v': v,
1430
1474
  }
1431
1475
 
1476
+
1432
1477
  @staticmethod
1433
- def eddsa(request, secret, curve='ed25519'):
1478
+ def _ecdsa_secp256k1_coincurve(request, secret, hash=None, fixed_length=False):
1479
+ """
1480
+ Use coincurve library for SECP256K1 ECDSA signing.
1481
+ This method provides faster SECP256K1 signing using the coincurve library,
1482
+ which is a Python binding to libsecp256k1. This implementation produces
1483
+ deterministic signatures (RFC 6979) using coincurve's sign_recoverable method.
1484
+ Args:
1485
+ request: The message to sign
1486
+ secret: The private key (hex string or PEM format)
1487
+ hash: The hash function to use
1488
+ fixed_length: Not used in coincurve implementation (signatures are always fixed length)
1489
+ Returns:
1490
+ dict: {'r': r_value, 's': s_value, 'v': v_value}
1491
+ Raises:
1492
+ ArgumentsRequired: If coincurve library is not available
1493
+ """
1494
+ encoded_request = Exchange.encode(request)
1495
+ if hash is not None:
1496
+ digest = Exchange.hash(encoded_request, hash, 'binary')
1497
+ else:
1498
+ digest = base64.b16decode(encoded_request, casefold=True)
1499
+ if isinstance(secret, str):
1500
+ secret = Exchange.encode(secret)
1501
+ # Handle PEM format
1502
+ if secret.find(b'-----BEGIN EC PRIVATE KEY-----') > -1:
1503
+ secret = base64.b16decode(secret.replace(b'-----BEGIN EC PRIVATE KEY-----', b'').replace(b'-----END EC PRIVATE KEY-----', b'').replace(b'\n', b'').replace(b'\r', b''), casefold=True)
1504
+ else:
1505
+ # Assume hex format
1506
+ secret = base64.b16decode(secret, casefold=True)
1507
+ # Create coincurve PrivateKey
1508
+ private_key = coincurve.PrivateKey(secret)
1509
+ # Sign the digest using sign_recoverable which produces deterministic signatures (RFC 6979)
1510
+ # The signature format is: 32 bytes r + 32 bytes s + 1 byte recovery_id (v)
1511
+ signature = private_key.sign_recoverable(digest, hasher=None)
1512
+ # Extract r, s, and v from the recoverable signature (65 bytes total)
1513
+ r_binary = signature[:32]
1514
+ s_binary = signature[32:64]
1515
+ v = signature[64]
1516
+ # Convert to hex strings
1517
+ r = Exchange.decode(base64.b16encode(r_binary)).lower()
1518
+ s = Exchange.decode(base64.b16encode(s_binary)).lower()
1519
+ return {
1520
+ 'r': r,
1521
+ 's': s,
1522
+ 'v': v,
1523
+ }
1524
+
1525
+ def binary_to_urlencoded_base64(data: bytes) -> str:
1526
+ encoded = base64.urlsafe_b64encode(data).decode("utf-8")
1527
+ return encoded.rstrip("=")
1528
+
1529
+ @staticmethod
1530
+ def eddsa(request, secret, curve='ed25519', url_encode=False):
1434
1531
  if isinstance(secret, str):
1435
1532
  secret = Exchange.encode(secret)
1436
1533
  private_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret) if len(secret) == 32 else load_pem_private_key(secret, None)
1437
- return Exchange.binary_to_base64(private_key.sign(request))
1534
+ signature = private_key.sign(request)
1535
+ if url_encode:
1536
+ return Exchange.binary_to_urlencoded_base64(signature)
1537
+
1538
+ return Exchange.binary_to_base64(signature)
1438
1539
 
1439
1540
  @staticmethod
1440
1541
  def axolotl(request, secret, curve='ed25519'):
@@ -1505,6 +1606,12 @@ class Exchange(object):
1505
1606
  parts = re.sub(r'0+$', '', str).split('.')
1506
1607
  return len(parts[1]) if len(parts) > 1 else 0
1507
1608
 
1609
+ def map_to_safe_map(self, dictionary):
1610
+ return dictionary # wrapper for go
1611
+
1612
+ def safe_map_to_map(self, dictionary):
1613
+ return dictionary # wrapper for go
1614
+
1508
1615
  def load_markets(self, reload=False, params={}):
1509
1616
  """
1510
1617
  Loads and prepares the markets for trading.
@@ -1533,7 +1640,10 @@ class Exchange(object):
1533
1640
  currencies = None
1534
1641
  if self.has['fetchCurrencies'] is True:
1535
1642
  currencies = self.fetch_currencies()
1643
+ self.options['cachedCurrencies'] = currencies
1536
1644
  markets = self.fetch_markets(params)
1645
+ if 'cachedCurrencies' in self.options:
1646
+ del self.options['cachedCurrencies']
1537
1647
  return self.set_markets(markets, currencies)
1538
1648
 
1539
1649
  def fetch_markets(self, params={}):
@@ -1840,6 +1950,8 @@ class Exchange(object):
1840
1950
  signature = auth_data.signature
1841
1951
  return signature
1842
1952
 
1953
+ def is_binary_message(self, message):
1954
+ return isinstance(message, bytes) or isinstance(message, bytearray)
1843
1955
 
1844
1956
  # ########################################################################
1845
1957
  # ########################################################################
@@ -1878,7 +1990,7 @@ class Exchange(object):
1878
1990
  # ########################################################################
1879
1991
  # ########################################################################
1880
1992
 
1881
- # METHODS BELOW THIS LINE ARE TRANSPILED FROM JAVASCRIPT TO PYTHON AND PHP
1993
+ # METHODS BELOW THIS LINE ARE TRANSPILED FROM TYPESCRIPT
1882
1994
 
1883
1995
  def describe(self) -> Any:
1884
1996
  return {
@@ -2111,6 +2223,17 @@ class Exchange(object):
2111
2223
  'watchLiquidations': None,
2112
2224
  'watchLiquidationsForSymbols': None,
2113
2225
  'watchMyLiquidations': None,
2226
+ 'unWatchOrders': None,
2227
+ 'unWatchTrades': None,
2228
+ 'unWatchTradesForSymbols': None,
2229
+ 'unWatchOHLCVForSymbols': None,
2230
+ 'unWatchOrderBookForSymbols': None,
2231
+ 'unWatchPositions': None,
2232
+ 'unWatchOrderBook': None,
2233
+ 'unWatchTickers': None,
2234
+ 'unWatchMyTrades': None,
2235
+ 'unWatchTicker': None,
2236
+ 'unWatchOHLCV': None,
2114
2237
  'watchMyLiquidationsForSymbols': None,
2115
2238
  'withdraw': None,
2116
2239
  'ws': None,
@@ -2302,6 +2425,12 @@ class Exchange(object):
2302
2425
  # return the first index of the cache that can be applied to the orderbook or -1 if not possible
2303
2426
  return -1
2304
2427
 
2428
+ def arrays_concat(self, arraysOfArrays: List[Any]):
2429
+ result = []
2430
+ for i in range(0, len(arraysOfArrays)):
2431
+ result = self.array_concat(result, arraysOfArrays[i])
2432
+ return result
2433
+
2305
2434
  def find_timeframe(self, timeframe, timeframes=None):
2306
2435
  if timeframes is None:
2307
2436
  timeframes = self.timeframes
@@ -2536,6 +2665,22 @@ class Exchange(object):
2536
2665
  # set flag
2537
2666
  self.isSandboxModeEnabled = False
2538
2667
 
2668
+ def enable_demo_trading(self, enable: bool):
2669
+ """
2670
+ enables or disables demo trading mode
2671
+ :param boolean [enable]: True if demo trading should be enabled, False otherwise
2672
+ """
2673
+ if self.isSandboxModeEnabled:
2674
+ raise NotSupported(self.id + ' demo trading does not support in sandbox environment. Please check https://www.binance.com/en/support/faq/detail/9be58f73e5e14338809e3b705b9687dd to see the differences')
2675
+ if enable:
2676
+ self.urls['apiBackupDemoTrading'] = self.urls['api']
2677
+ self.urls['api'] = self.urls['demo']
2678
+ elif 'apiBackupDemoTrading' in self.urls:
2679
+ self.urls['api'] = self.urls['apiBackupDemoTrading']
2680
+ newUrls = self.omit(self.urls, 'apiBackupDemoTrading')
2681
+ self.urls = newUrls
2682
+ self.options['enableDemoTrading'] = enable
2683
+
2539
2684
  def sign(self, path, api: Any = 'public', method='GET', params={}, headers: Any = None, body: Any = None):
2540
2685
  return {}
2541
2686
 
@@ -2567,6 +2712,9 @@ class Exchange(object):
2567
2712
  def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}):
2568
2713
  raise NotSupported(self.id + ' watchTrades() is not supported yet')
2569
2714
 
2715
+ def un_watch_orders(self, symbol: Str = None, params={}):
2716
+ raise NotSupported(self.id + ' unWatchOrders() is not supported yet')
2717
+
2570
2718
  def un_watch_trades(self, symbol: str, params={}):
2571
2719
  raise NotSupported(self.id + ' unWatchTrades() is not supported yet')
2572
2720
 
@@ -2594,6 +2742,18 @@ class Exchange(object):
2594
2742
  def un_watch_order_book_for_symbols(self, symbols: List[str], params={}):
2595
2743
  raise NotSupported(self.id + ' unWatchOrderBookForSymbols() is not supported yet')
2596
2744
 
2745
+ def un_watch_positions(self, symbols: Strings = None, params={}):
2746
+ raise NotSupported(self.id + ' unWatchPositions() is not supported yet')
2747
+
2748
+ def un_watch_ticker(self, symbol: str, params={}):
2749
+ raise NotSupported(self.id + ' unWatchTicker() is not supported yet')
2750
+
2751
+ def un_watch_mark_price(self, symbol: str, params={}):
2752
+ raise NotSupported(self.id + ' unWatchMarkPrice() is not supported yet')
2753
+
2754
+ def un_watch_mark_prices(self, symbols: Strings = None, params={}):
2755
+ raise NotSupported(self.id + ' unWatchMarkPrices() is not supported yet')
2756
+
2597
2757
  def fetch_deposit_addresses(self, codes: Strings = None, params={}):
2598
2758
  raise NotSupported(self.id + ' fetchDepositAddresses() is not supported yet')
2599
2759
 
@@ -2735,13 +2895,13 @@ class Exchange(object):
2735
2895
  def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}):
2736
2896
  raise NotSupported(self.id + ' transfer() is not supported yet')
2737
2897
 
2738
- def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
2898
+ def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}):
2739
2899
  raise NotSupported(self.id + ' withdraw() is not supported yet')
2740
2900
 
2741
2901
  def create_deposit_address(self, code: str, params={}):
2742
2902
  raise NotSupported(self.id + ' createDepositAddress() is not supported yet')
2743
2903
 
2744
- def set_leverage(self, leverage: Int, symbol: Str = None, params={}):
2904
+ def set_leverage(self, leverage: int, symbol: Str = None, params={}):
2745
2905
  raise NotSupported(self.id + ' setLeverage() is not supported yet')
2746
2906
 
2747
2907
  def fetch_leverage(self, symbol: str, params={}):
@@ -2790,7 +2950,7 @@ class Exchange(object):
2790
2950
  def fetch_deposit_addresses_by_network(self, code: str, params={}):
2791
2951
  raise NotSupported(self.id + ' fetchDepositAddressesByNetwork() is not supported yet')
2792
2952
 
2793
- def fetch_open_interest_history(self, symbol: str, timeframe='1h', since: Int = None, limit: Int = None, params={}):
2953
+ def fetch_open_interest_history(self, symbol: str, timeframe: str = '1h', since: Int = None, limit: Int = None, params={}):
2794
2954
  raise NotSupported(self.id + ' fetchOpenInterestHistory() is not supported yet')
2795
2955
 
2796
2956
  def fetch_open_interest(self, symbol: str, params={}):
@@ -2815,9 +2975,9 @@ class Exchange(object):
2815
2975
  def parse_to_numeric(self, number):
2816
2976
  stringVersion = self.number_to_string(number) # self will convert 1.0 and 1 to "1" and 1.1 to "1.1"
2817
2977
  # keep self in mind:
2818
- # in JS: 1 == 1.0 is True; 1 == 1.0 is True
2978
+ # in JS: 1 == 1.0 is True
2819
2979
  # in Python: 1 == 1.0 is True
2820
- # in PHP 1 == 1.0 is True, but 1 == 1.0 is False
2980
+ # in PHP: 1 == 1.0 is True, but 1 == 1.0 is False.
2821
2981
  if stringVersion.find('.') >= 0:
2822
2982
  return float(stringVersion)
2823
2983
  return int(stringVersion)
@@ -2863,7 +3023,7 @@ class Exchange(object):
2863
3023
  'delay': 0.001,
2864
3024
  'capacity': 1,
2865
3025
  'cost': 1,
2866
- 'maxCapacity': 1000,
3026
+ 'maxCapacity': self.safe_integer(self.options, 'maxRequestsQueue', 1000),
2867
3027
  'refillRate': refillRate,
2868
3028
  }
2869
3029
  existingBucket = {} if (self.tokenBucket is None) else self.tokenBucket
@@ -2945,6 +3105,79 @@ class Exchange(object):
2945
3105
  featureBlock['symbolRequired'] = self.in_array(key, ['createOrder', 'createOrders', 'fetchOHLCV'])
2946
3106
  return featuresObj
2947
3107
 
3108
+ def feature_value(self, symbol: str, methodName: Str = None, paramName: Str = None, defaultValue: Any = None):
3109
+ """
3110
+ self method is a very deterministic to help users to know what feature is supported by the exchange
3111
+ :param str [symbol]: unified symbol
3112
+ :param str [methodName]: view currently supported methods: https://docs.ccxt.com/#/README?id=features
3113
+ :param str [paramName]: unified param value, like: `triggerPrice`, `stopLoss.triggerPrice`(check docs for supported param names)
3114
+ :param dict [defaultValue]: return default value if no result found
3115
+ :returns dict: returns feature value
3116
+ """
3117
+ market = self.market(symbol)
3118
+ return self.feature_value_by_type(market['type'], market['subType'], methodName, paramName, defaultValue)
3119
+
3120
+ def feature_value_by_type(self, marketType: str, subType: Str, methodName: Str = None, paramName: Str = None, defaultValue: Any = None):
3121
+ """
3122
+ self method is a very deterministic to help users to know what feature is supported by the exchange
3123
+ :param str [marketType]: supported only: "spot", "swap", "future"
3124
+ :param str [subType]: supported only: "linear", "inverse"
3125
+ :param str [methodName]: view currently supported methods: https://docs.ccxt.com/#/README?id=features
3126
+ :param str [paramName]: unified param value(check docs for supported param names)
3127
+ :param dict [defaultValue]: return default value if no result found
3128
+ :returns dict: returns feature value
3129
+ """
3130
+ # if exchange does not yet have features manually implemented
3131
+ if self.features is None:
3132
+ return defaultValue
3133
+ if marketType is None:
3134
+ return defaultValue # marketType is required
3135
+ # if marketType(e.g. 'option') does not exist in features
3136
+ if not (marketType in self.features):
3137
+ return defaultValue # unsupported marketType, check "exchange.features" for details
3138
+ # if marketType dict None
3139
+ if self.features[marketType] is None:
3140
+ return defaultValue
3141
+ methodsContainer = self.features[marketType]
3142
+ if subType is None:
3143
+ if marketType != 'spot':
3144
+ return defaultValue # subType is required for non-spot markets
3145
+ else:
3146
+ if not (subType in self.features[marketType]):
3147
+ return defaultValue # unsupported subType, check "exchange.features" for details
3148
+ # if subType dict None
3149
+ if self.features[marketType][subType] is None:
3150
+ return defaultValue
3151
+ methodsContainer = self.features[marketType][subType]
3152
+ # if user wanted only marketType and didn't provide methodName, eg: featureIsSupported('spot')
3153
+ if methodName is None:
3154
+ return defaultValue if (defaultValue is not None) else methodsContainer
3155
+ if not (methodName in methodsContainer):
3156
+ return defaultValue # unsupported method, check "exchange.features" for details')
3157
+ methodDict = methodsContainer[methodName]
3158
+ if methodDict is None:
3159
+ return defaultValue
3160
+ # if user wanted only method and didn't provide `paramName`, eg: featureIsSupported('swap', 'linear', 'createOrder')
3161
+ if paramName is None:
3162
+ return defaultValue if (defaultValue is not None) else methodDict
3163
+ splited = paramName.split('.') # can be only parent key(`stopLoss`) or with child(`stopLoss.triggerPrice`)
3164
+ parentKey = splited[0]
3165
+ subKey = self.safe_string(splited, 1)
3166
+ if not (parentKey in methodDict):
3167
+ return defaultValue # unsupported paramName, check "exchange.features" for details')
3168
+ dictionary = self.safe_dict(methodDict, parentKey)
3169
+ if dictionary is None:
3170
+ # if the value is not dictionary but a scalar value(or None), return
3171
+ return methodDict[parentKey]
3172
+ else:
3173
+ # return, when calling without subKey eg: featureValueByType('spot', None, 'createOrder', 'stopLoss')
3174
+ if subKey is None:
3175
+ return methodDict[parentKey]
3176
+ # raise an exception for unsupported subKey
3177
+ if not (subKey in methodDict[parentKey]):
3178
+ return defaultValue # unsupported subKey, check "exchange.features" for details
3179
+ return methodDict[parentKey][subKey]
3180
+
2948
3181
  def orderbook_checksum_message(self, symbol: Str):
2949
3182
  return symbol + ' = False'
2950
3183
 
@@ -3183,7 +3416,7 @@ class Exchange(object):
3183
3416
 
3184
3417
  def set_markets(self, markets, currencies=None):
3185
3418
  values = []
3186
- self.markets_by_id = {}
3419
+ self.markets_by_id = self.create_safe_dictionary()
3187
3420
  # handle marketId conflicts
3188
3421
  # we insert spot markets first
3189
3422
  marketValues = self.sort_by(self.to_array(markets), 'spot', True, True)
@@ -3206,14 +3439,18 @@ class Exchange(object):
3206
3439
  else:
3207
3440
  market['subType'] = None
3208
3441
  values.append(market)
3209
- self.markets = self.index_by(values, 'symbol')
3442
+ self.markets = self.map_to_safe_map(self.index_by(values, 'symbol'))
3210
3443
  marketsSortedBySymbol = self.keysort(self.markets)
3211
3444
  marketsSortedById = self.keysort(self.markets_by_id)
3212
3445
  self.symbols = list(marketsSortedBySymbol.keys())
3213
3446
  self.ids = list(marketsSortedById.keys())
3447
+ numCurrencies = 0
3214
3448
  if currencies is not None:
3449
+ keys = list(currencies.keys())
3450
+ numCurrencies = len(keys)
3451
+ if numCurrencies > 0:
3215
3452
  # currencies is always None when called in constructor but not when called from loadMarkets
3216
- self.currencies = self.deep_extend(self.currencies, currencies)
3453
+ self.currencies = self.map_to_safe_map(self.deep_extend(self.currencies, currencies))
3217
3454
  else:
3218
3455
  baseCurrencies = []
3219
3456
  quoteCurrencies = []
@@ -3239,8 +3476,8 @@ class Exchange(object):
3239
3476
  quoteCurrencies.append(currency)
3240
3477
  baseCurrencies = self.sort_by(baseCurrencies, 'code', False, '')
3241
3478
  quoteCurrencies = self.sort_by(quoteCurrencies, 'code', False, '')
3242
- self.baseCurrencies = self.index_by(baseCurrencies, 'code')
3243
- self.quoteCurrencies = self.index_by(quoteCurrencies, 'code')
3479
+ self.baseCurrencies = self.map_to_safe_map(self.index_by(baseCurrencies, 'code'))
3480
+ self.quoteCurrencies = self.map_to_safe_map(self.index_by(quoteCurrencies, 'code'))
3244
3481
  allCurrencies = self.array_concat(baseCurrencies, quoteCurrencies)
3245
3482
  groupedCurrencies = self.group_by(allCurrencies, 'code')
3246
3483
  codes = list(groupedCurrencies.keys())
@@ -3257,12 +3494,36 @@ class Exchange(object):
3257
3494
  highestPrecisionCurrency = currentCurrency if (currentCurrency['precision'] > highestPrecisionCurrency['precision']) else highestPrecisionCurrency
3258
3495
  resultingCurrencies.append(highestPrecisionCurrency)
3259
3496
  sortedCurrencies = self.sort_by(resultingCurrencies, 'code')
3260
- self.currencies = self.deep_extend(self.currencies, self.index_by(sortedCurrencies, 'code'))
3261
- self.currencies_by_id = self.index_by(self.currencies, 'id')
3497
+ self.currencies = self.map_to_safe_map(self.deep_extend(self.currencies, self.index_by(sortedCurrencies, 'code')))
3498
+ self.currencies_by_id = self.index_by_safe(self.currencies, 'id')
3262
3499
  currenciesSortedByCode = self.keysort(self.currencies)
3263
3500
  self.codes = list(currenciesSortedByCode.keys())
3264
3501
  return self.markets
3265
3502
 
3503
+ def set_markets_from_exchange(self, sourceExchange):
3504
+ # Validate that both exchanges are of the same type
3505
+ if self.id != sourceExchange.id:
3506
+ raise ArgumentsRequired(self.id + ' shareMarkets() can only share markets with exchanges of the same type(got ' + sourceExchange['id'] + ')')
3507
+ # Validate that source exchange has loaded markets
3508
+ if not sourceExchange.markets:
3509
+ raise ExchangeError('setMarketsFromExchange() source exchange must have loaded markets first. Can call by using loadMarkets function')
3510
+ # Set all market-related data
3511
+ self.markets = sourceExchange.markets
3512
+ self.markets_by_id = sourceExchange.markets_by_id
3513
+ self.symbols = sourceExchange.symbols
3514
+ self.ids = sourceExchange.ids
3515
+ self.currencies = sourceExchange.currencies
3516
+ self.baseCurrencies = sourceExchange.baseCurrencies
3517
+ self.quoteCurrencies = sourceExchange.quoteCurrencies
3518
+ self.codes = sourceExchange.codes
3519
+ # check marketHelperProps
3520
+ sourceExchangeHelpers = self.safe_list(sourceExchange.options, 'marketHelperProps', [])
3521
+ for i in range(0, len(sourceExchangeHelpers)):
3522
+ helper = sourceExchangeHelpers[i]
3523
+ if sourceExchange.options[helper] is not None:
3524
+ self.options[helper] = sourceExchange.options[helper]
3525
+ return self
3526
+
3266
3527
  def get_describe_for_extended_ws_exchange(self, currentRestInstance: Any, parentRestInstance: Any, wsBaseDescribe: dict):
3267
3528
  extendedRestDescribe = self.deep_extend(parentRestInstance.describe(), currentRestInstance.describe())
3268
3529
  superWithRestDescribe = self.deep_extend(extendedRestDescribe, wsBaseDescribe)
@@ -3562,18 +3823,7 @@ class Exchange(object):
3562
3823
  symbol = market['symbol'] if (market is not None) else None
3563
3824
  return self.filter_by_symbol_since_limit(results, symbol, since, limit)
3564
3825
 
3565
- def calculate_fee(self, symbol: str, type: str, side: str, amount: float, price: float, takerOrMaker='taker', params={}):
3566
- """
3567
- calculates the presumptive fee that would be charged for an order
3568
- :param str symbol: unified market symbol
3569
- :param str type: 'market' or 'limit'
3570
- :param str side: 'buy' or 'sell'
3571
- :param float amount: how much you want to trade, in units of the base currency on most exchanges, or number of contracts
3572
- :param float price: the price for the order to be filled at, in units of the quote currency
3573
- :param str takerOrMaker: 'taker' or 'maker'
3574
- :param dict params:
3575
- :returns dict: contains the rate, the percentage multiplied to the order amount to obtain the fee amount, and cost, the total value of the fee in units of the quote currency, for the order
3576
- """
3826
+ def calculate_fee_with_rate(self, symbol: str, type: str, side: str, amount: float, price: float, takerOrMaker='taker', feeRate: Num = None, params={}):
3577
3827
  if type == 'market' and takerOrMaker == 'maker':
3578
3828
  raise ArgumentsRequired(self.id + ' calculateFee() - you have provided incompatible arguments - "market" type order can not be "maker". Change either the "type" or the "takerOrMaker" argument to calculate the fee.')
3579
3829
  market = self.markets[symbol]
@@ -3602,7 +3852,7 @@ class Exchange(object):
3602
3852
  # even if `takerOrMaker` argument was set to 'maker', for 'market' orders we should forcefully override it to 'taker'
3603
3853
  if type == 'market':
3604
3854
  takerOrMaker = 'taker'
3605
- rate = self.safe_string(market, takerOrMaker)
3855
+ rate = self.number_to_string(feeRate) if (feeRate is not None) else self.safe_string(market, takerOrMaker)
3606
3856
  cost = Precise.string_mul(cost, rate)
3607
3857
  return {
3608
3858
  'type': takerOrMaker,
@@ -3611,6 +3861,20 @@ class Exchange(object):
3611
3861
  'cost': self.parse_number(cost),
3612
3862
  }
3613
3863
 
3864
+ def calculate_fee(self, symbol: str, type: str, side: str, amount: float, price: float, takerOrMaker='taker', params={}):
3865
+ """
3866
+ calculates the presumptive fee that would be charged for an order
3867
+ :param str symbol: unified market symbol
3868
+ :param str type: 'market' or 'limit'
3869
+ :param str side: 'buy' or 'sell'
3870
+ :param float amount: how much you want to trade, in units of the base currency on most exchanges, or number of contracts
3871
+ :param float price: the price for the order to be filled at, in units of the quote currency
3872
+ :param str takerOrMaker: 'taker' or 'maker'
3873
+ :param dict params:
3874
+ :returns dict: contains the rate, the percentage multiplied to the order amount to obtain the fee amount, and cost, the total value of the fee in units of the quote currency, for the order
3875
+ """
3876
+ return self.calculate_fee_with_rate(symbol, type, side, amount, price, takerOrMaker, None, params)
3877
+
3614
3878
  def safe_liquidation(self, liquidation: dict, market: Market = None):
3615
3879
  contracts = self.safe_string(liquidation, 'contracts')
3616
3880
  contractSize = self.safe_string(market, 'contractSize')
@@ -3650,6 +3914,21 @@ class Exchange(object):
3650
3914
  trade['cost'] = self.parse_number(cost)
3651
3915
  return trade
3652
3916
 
3917
+ def create_ccxt_trade_id(self, timestamp=None, side=None, amount=None, price=None, takerOrMaker=None):
3918
+ # self approach is being used by multiple exchanges(mexc, woo, coinsbit, dydx, ...)
3919
+ id = None
3920
+ if timestamp is not None:
3921
+ id = self.number_to_string(timestamp)
3922
+ if side is not None:
3923
+ id += '-' + side
3924
+ if amount is not None:
3925
+ id += '-' + self.number_to_string(amount)
3926
+ if price is not None:
3927
+ id += '-' + self.number_to_string(price)
3928
+ if takerOrMaker is not None:
3929
+ id += '-' + takerOrMaker
3930
+ return id
3931
+
3653
3932
  def parsed_fee_and_fees(self, container: Any):
3654
3933
  fee = self.safe_dict(container, 'fee')
3655
3934
  fees = self.safe_list(container, 'fees')
@@ -3758,7 +4037,7 @@ class Exchange(object):
3758
4037
  for i in range(0, len(fees)):
3759
4038
  fee = fees[i]
3760
4039
  code = self.safe_string(fee, 'currency')
3761
- feeCurrencyCode = code is not code if None else str(i)
4040
+ feeCurrencyCode = code if (code is not None) else str(i)
3762
4041
  if feeCurrencyCode is not None:
3763
4042
  rate = self.safe_string(fee, 'rate')
3764
4043
  cost = self.safe_string(fee, 'cost')
@@ -3786,8 +4065,7 @@ class Exchange(object):
3786
4065
 
3787
4066
  def safe_ticker(self, ticker: dict, market: Market = None):
3788
4067
  open = self.omit_zero(self.safe_string(ticker, 'open'))
3789
- close = self.omit_zero(self.safe_string(ticker, 'close'))
3790
- last = self.omit_zero(self.safe_string(ticker, 'last'))
4068
+ close = self.omit_zero(self.safe_string_2(ticker, 'close', 'last'))
3791
4069
  change = self.omit_zero(self.safe_string(ticker, 'change'))
3792
4070
  percentage = self.omit_zero(self.safe_string(ticker, 'percentage'))
3793
4071
  average = self.omit_zero(self.safe_string(ticker, 'average'))
@@ -3796,29 +4074,52 @@ class Exchange(object):
3796
4074
  quoteVolume = self.safe_string(ticker, 'quoteVolume')
3797
4075
  if vwap is None:
3798
4076
  vwap = Precise.string_div(self.omit_zero(quoteVolume), baseVolume)
3799
- if (last is not None) and (close is None):
3800
- close = last
3801
- elif (last is None) and (close is not None):
3802
- last = close
3803
- if (last is not None) and (open is not None):
3804
- if change is None:
3805
- change = Precise.string_sub(last, open)
3806
- if average is None:
4077
+ # calculate open
4078
+ if change is not None:
4079
+ if close is None and average is not None:
4080
+ close = Precise.string_add(average, Precise.string_div(change, '2'))
4081
+ if open is None and close is not None:
4082
+ open = Precise.string_sub(close, change)
4083
+ elif percentage is not None:
4084
+ if close is None and average is not None:
4085
+ openAddClose = Precise.string_mul(average, '2')
4086
+ # openAddClose = open * (1 + (100 + percentage)/100)
4087
+ denominator = Precise.string_add('2', Precise.string_div(percentage, '100'))
4088
+ calcOpen = open if (open is not None) else Precise.string_div(openAddClose, denominator)
4089
+ close = Precise.string_mul(calcOpen, Precise.string_add('1', Precise.string_div(percentage, '100')))
4090
+ if open is None and close is not None:
4091
+ open = Precise.string_div(close, Precise.string_add('1', Precise.string_div(percentage, '100')))
4092
+ # change
4093
+ if change is None:
4094
+ if close is not None and open is not None:
4095
+ change = Precise.string_sub(close, open)
4096
+ elif close is not None and percentage is not None:
4097
+ change = Precise.string_mul(Precise.string_div(percentage, '100'), Precise.string_div(close, '100'))
4098
+ elif open is not None and percentage is not None:
4099
+ change = Precise.string_mul(open, Precise.string_div(percentage, '100'))
4100
+ # calculate things according to "open"(similar can be done with "close")
4101
+ if open is not None:
4102
+ # percentage(using change)
4103
+ if percentage is None and change is not None:
4104
+ percentage = Precise.string_mul(Precise.string_div(change, open), '100')
4105
+ # close(using change)
4106
+ if close is None and change is not None:
4107
+ close = Precise.string_add(open, change)
4108
+ # close(using average)
4109
+ if close is None and average is not None:
4110
+ close = Precise.string_mul(average, '2')
4111
+ # average
4112
+ if average is None and close is not None:
3807
4113
  precision = 18
3808
4114
  if market is not None and self.is_tick_precision():
3809
4115
  marketPrecision = self.safe_dict(market, 'precision')
3810
4116
  precisionPrice = self.safe_string(marketPrecision, 'price')
3811
4117
  if precisionPrice is not None:
3812
4118
  precision = self.precision_from_string(precisionPrice)
3813
- average = Precise.string_div(Precise.string_add(last, open), '2', precision)
3814
- if (percentage is None) and (change is not None) and (open is not None) and Precise.string_gt(open, '0'):
3815
- percentage = Precise.string_mul(Precise.string_div(change, open), '100')
3816
- if (change is None) and (percentage is not None) and (open is not None):
3817
- change = Precise.string_div(Precise.string_mul(percentage, open), '100')
3818
- if (open is None) and (last is not None) and (change is not None):
3819
- open = Precise.string_sub(last, change)
4119
+ average = Precise.string_div(Precise.string_add(open, close), '2', precision)
3820
4120
  # timestamp and symbol operations don't belong in safeTicker
3821
4121
  # they should be done in the derived classes
4122
+ closeParsed = self.parse_number(self.omit_zero(close))
3822
4123
  return self.extend(ticker, {
3823
4124
  'bid': self.parse_number(self.omit_zero(self.safe_string(ticker, 'bid'))),
3824
4125
  'bidVolume': self.safe_number(ticker, 'bidVolume'),
@@ -3827,8 +4128,8 @@ class Exchange(object):
3827
4128
  'high': self.parse_number(self.omit_zero(self.safe_string(ticker, 'high'))),
3828
4129
  'low': self.parse_number(self.omit_zero(self.safe_string(ticker, 'low'))),
3829
4130
  'open': self.parse_number(self.omit_zero(open)),
3830
- 'close': self.parse_number(self.omit_zero(close)),
3831
- 'last': self.parse_number(self.omit_zero(last)),
4131
+ 'close': closeParsed,
4132
+ 'last': closeParsed,
3832
4133
  'change': self.parse_number(change),
3833
4134
  'percentage': self.parse_number(percentage),
3834
4135
  'average': self.parse_number(average),
@@ -3861,19 +4162,19 @@ class Exchange(object):
3861
4162
  def repay_margin(self, code: str, amount: float, symbol: Str = None, params={}):
3862
4163
  raise NotSupported(self.id + ' repayMargin is deprecated, please use repayCrossMargin or repayIsolatedMargin instead')
3863
4164
 
3864
- def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
4165
+ def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}):
3865
4166
  message = ''
3866
4167
  if self.has['fetchTrades']:
3867
4168
  message = '. If you want to build OHLCV candles from trade executions data, visit https://github.com/ccxt/ccxt/tree/master/examples/ and see "build-ohlcv-bars" file'
3868
4169
  raise NotSupported(self.id + ' fetchOHLCV() is not supported yet' + message)
3869
4170
 
3870
- def fetch_ohlcv_ws(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
4171
+ def fetch_ohlcv_ws(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}):
3871
4172
  message = ''
3872
4173
  if self.has['fetchTradesWs']:
3873
4174
  message = '. If you want to build OHLCV candles from trade executions data, visit https://github.com/ccxt/ccxt/tree/master/examples/ and see "build-ohlcv-bars" file'
3874
4175
  raise NotSupported(self.id + ' fetchOHLCVWs() is not supported yet. Try using fetchOHLCV instead.' + message)
3875
4176
 
3876
- def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
4177
+ def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}):
3877
4178
  raise NotSupported(self.id + ' watchOHLCV() is not supported yet')
3878
4179
 
3879
4180
  def convert_trading_view_to_ohlcv(self, ohlcvs: List[List[float]], timestamp='t', open='o', high='h', low='l', close='c', volume='v', ms=False):
@@ -4166,11 +4467,11 @@ class Exchange(object):
4166
4467
  raise NotSupported(self.id + ' - ' + networkCode + ' network did not return any result for ' + currencyCode)
4167
4468
  else:
4168
4469
  # if networkCode was provided by user, we should check it after response, referenced exchange doesn't support network-code during request
4169
- networkId = networkCode if isIndexedByUnifiedNetworkCode else self.network_code_to_id(networkCode, currencyCode)
4170
- if networkId in indexedNetworkEntries:
4171
- chosenNetworkId = networkId
4470
+ networkIdOrCode = networkCode if isIndexedByUnifiedNetworkCode else self.network_code_to_id(networkCode, currencyCode)
4471
+ if networkIdOrCode in indexedNetworkEntries:
4472
+ chosenNetworkId = networkIdOrCode
4172
4473
  else:
4173
- raise NotSupported(self.id + ' - ' + networkId + ' network was not found for ' + currencyCode + ', use one of ' + ', '.join(availableNetworkIds))
4474
+ raise NotSupported(self.id + ' - ' + networkIdOrCode + ' network was not found for ' + currencyCode + ', use one of ' + ', '.join(availableNetworkIds))
4174
4475
  else:
4175
4476
  if responseNetworksLength == 0:
4176
4477
  raise NotSupported(self.id + ' - no networks were returned for ' + currencyCode)
@@ -4207,7 +4508,7 @@ class Exchange(object):
4207
4508
  return self.filter_by_since_limit(sorted, since, limit, 0, tail)
4208
4509
 
4209
4510
  def parse_leverage_tiers(self, response: Any, symbols: List[str] = None, marketIdKey=None):
4210
- # marketIdKey should only be None when response is a dictionary
4511
+ # marketIdKey should only be None when response is a dictionary.
4211
4512
  symbols = self.market_symbols(symbols)
4212
4513
  tiers = {}
4213
4514
  symbolsLength = 0
@@ -4285,16 +4586,27 @@ class Exchange(object):
4285
4586
  result.append(account)
4286
4587
  return result
4287
4588
 
4288
- def parse_trades(self, trades: List[Any], market: Market = None, since: Int = None, limit: Int = None, params={}):
4589
+ def parse_trades_helper(self, isWs: bool, trades: List[Any], market: Market = None, since: Int = None, limit: Int = None, params={}):
4289
4590
  trades = self.to_array(trades)
4290
4591
  result = []
4291
4592
  for i in range(0, len(trades)):
4292
- trade = self.extend(self.parse_trade(trades[i], market), params)
4593
+ parsed = None
4594
+ if isWs:
4595
+ parsed = self.parse_ws_trade(trades[i], market)
4596
+ else:
4597
+ parsed = self.parse_trade(trades[i], market)
4598
+ trade = self.extend(parsed, params)
4293
4599
  result.append(trade)
4294
4600
  result = self.sort_by_2(result, 'timestamp', 'id')
4295
4601
  symbol = market['symbol'] if (market is not None) else None
4296
4602
  return self.filter_by_symbol_since_limit(result, symbol, since, limit)
4297
4603
 
4604
+ def parse_trades(self, trades: List[Any], market: Market = None, since: Int = None, limit: Int = None, params={}):
4605
+ return self.parse_trades_helper(False, trades, market, since, limit, params)
4606
+
4607
+ def parse_ws_trades(self, trades: List[Any], market: Market = None, since: Int = None, limit: Int = None, params={}):
4608
+ return self.parse_trades_helper(True, trades, market, since, limit, params)
4609
+
4298
4610
  def parse_transactions(self, transactions: List[Any], currency: Currency = None, since: Int = None, limit: Int = None, params={}):
4299
4611
  transactions = self.to_array(transactions)
4300
4612
  result = []
@@ -4458,15 +4770,15 @@ class Exchange(object):
4458
4770
  if self.enableRateLimit:
4459
4771
  cost = self.calculate_rate_limiter_cost(api, method, path, params, config)
4460
4772
  self.throttle(cost)
4773
+ retries = None
4774
+ retries, params = self.handle_option_and_params(params, path, 'maxRetriesOnFailure', 0)
4775
+ retryDelay = None
4776
+ retryDelay, params = self.handle_option_and_params(params, path, 'maxRetriesOnFailureDelay', 0)
4461
4777
  self.lastRestRequestTimestamp = self.milliseconds()
4462
4778
  request = self.sign(path, api, method, params, headers, body)
4463
4779
  self.last_request_headers = request['headers']
4464
4780
  self.last_request_body = request['body']
4465
4781
  self.last_request_url = request['url']
4466
- retries = None
4467
- retries, params = self.handle_option_and_params(params, path, 'maxRetriesOnFailure', 0)
4468
- retryDelay = None
4469
- retryDelay, params = self.handle_option_and_params(params, path, 'maxRetriesOnFailureDelay', 0)
4470
4782
  for i in range(0, retries + 1):
4471
4783
  try:
4472
4784
  return self.fetch(request['url'], request['method'], request['headers'], request['body'])
@@ -4511,9 +4823,12 @@ class Exchange(object):
4511
4823
  i_count = 6
4512
4824
  tradesLength = len(trades)
4513
4825
  oldest = min(tradesLength, limit)
4826
+ options = self.safe_dict(self.options, 'buildOHLCVC', {})
4827
+ skipZeroPrices = self.safe_bool(options, 'skipZeroPrices', True)
4514
4828
  for i in range(0, oldest):
4515
4829
  trade = trades[i]
4516
4830
  ts = trade['timestamp']
4831
+ price = trade['price']
4517
4832
  if ts < since:
4518
4833
  continue
4519
4834
  openingTime = int(math.floor(ts / ms)) * ms # shift to the edge of m/h/d(but not M)
@@ -4521,22 +4836,25 @@ class Exchange(object):
4521
4836
  continue
4522
4837
  ohlcv_length = len(ohlcvs)
4523
4838
  candle = ohlcv_length - 1
4524
- if (candle == -1) or (openingTime >= self.sum(ohlcvs[candle][i_timestamp], ms)):
4839
+ if skipZeroPrices and not (price > 0) and not (price < 0):
4840
+ continue
4841
+ isFirstCandle = candle == -1
4842
+ if isFirstCandle or openingTime >= self.sum(ohlcvs[candle][i_timestamp], ms):
4525
4843
  # moved to a new timeframe -> create a new candle from opening trade
4526
4844
  ohlcvs.append([
4527
4845
  openingTime, # timestamp
4528
- trade['price'], # O
4529
- trade['price'], # H
4530
- trade['price'], # L
4531
- trade['price'], # C
4846
+ price, # O
4847
+ price, # H
4848
+ price, # L
4849
+ price, # C
4532
4850
  trade['amount'], # V
4533
4851
  1, # count
4534
4852
  ])
4535
4853
  else:
4536
4854
  # still processing the same timeframe -> update opening trade
4537
- ohlcvs[candle][i_high] = max(ohlcvs[candle][i_high], trade['price'])
4538
- ohlcvs[candle][i_low] = min(ohlcvs[candle][i_low], trade['price'])
4539
- ohlcvs[candle][i_close] = trade['price']
4855
+ ohlcvs[candle][i_high] = max(ohlcvs[candle][i_high], price)
4856
+ ohlcvs[candle][i_low] = min(ohlcvs[candle][i_low], price)
4857
+ ohlcvs[candle][i_close] = price
4540
4858
  ohlcvs[candle][i_volume] = self.sum(ohlcvs[candle][i_volume], trade['amount'])
4541
4859
  ohlcvs[candle][i_count] = self.sum(ohlcvs[candle][i_count], 1)
4542
4860
  return ohlcvs
@@ -4676,6 +4994,11 @@ class Exchange(object):
4676
4994
  return market
4677
4995
  return result
4678
4996
 
4997
+ def market_or_null(self, symbol: str):
4998
+ if symbol is None:
4999
+ return None
5000
+ return self.market(symbol)
5001
+
4679
5002
  def check_required_credentials(self, error=True):
4680
5003
  """
4681
5004
  @ignore
@@ -4994,7 +5317,7 @@ class Exchange(object):
4994
5317
  def fetch_position_mode(self, symbol: Str = None, params={}):
4995
5318
  raise NotSupported(self.id + ' fetchPositionMode() is not supported yet')
4996
5319
 
4997
- def create_trailing_amount_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingAmount=None, trailingTriggerPrice=None, params={}):
5320
+ def create_trailing_amount_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingAmount: Num = None, trailingTriggerPrice: Num = None, params={}):
4998
5321
  """
4999
5322
  create a trailing order by providing the symbol, type, side, amount, price and trailingAmount
5000
5323
  :param str symbol: unified symbol of the market to create an order in
@@ -5016,7 +5339,7 @@ class Exchange(object):
5016
5339
  return self.create_order(symbol, type, side, amount, price, params)
5017
5340
  raise NotSupported(self.id + ' createTrailingAmountOrder() is not supported yet')
5018
5341
 
5019
- def create_trailing_amount_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingAmount=None, trailingTriggerPrice=None, params={}):
5342
+ def create_trailing_amount_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingAmount: Num = None, trailingTriggerPrice: Num = None, params={}):
5020
5343
  """
5021
5344
  create a trailing order by providing the symbol, type, side, amount, price and trailingAmount
5022
5345
  :param str symbol: unified symbol of the market to create an order in
@@ -5038,7 +5361,7 @@ class Exchange(object):
5038
5361
  return self.create_order_ws(symbol, type, side, amount, price, params)
5039
5362
  raise NotSupported(self.id + ' createTrailingAmountOrderWs() is not supported yet')
5040
5363
 
5041
- def create_trailing_percent_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent=None, trailingTriggerPrice=None, params={}):
5364
+ def create_trailing_percent_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent: Num = None, trailingTriggerPrice: Num = None, params={}):
5042
5365
  """
5043
5366
  create a trailing order by providing the symbol, type, side, amount, price and trailingPercent
5044
5367
  :param str symbol: unified symbol of the market to create an order in
@@ -5060,7 +5383,7 @@ class Exchange(object):
5060
5383
  return self.create_order(symbol, type, side, amount, price, params)
5061
5384
  raise NotSupported(self.id + ' createTrailingPercentOrder() is not supported yet')
5062
5385
 
5063
- def create_trailing_percent_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent=None, trailingTriggerPrice=None, params={}):
5386
+ def create_trailing_percent_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent: Num = None, trailingTriggerPrice: Num = None, params={}):
5064
5387
  """
5065
5388
  create a trailing order by providing the symbol, type, side, amount, price and trailingPercent
5066
5389
  :param str symbol: unified symbol of the market to create an order in
@@ -5351,6 +5674,9 @@ class Exchange(object):
5351
5674
  def cancel_order_ws(self, id: str, symbol: Str = None, params={}):
5352
5675
  raise NotSupported(self.id + ' cancelOrderWs() is not supported yet')
5353
5676
 
5677
+ def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
5678
+ raise NotSupported(self.id + ' cancelOrders() is not supported yet')
5679
+
5354
5680
  def cancel_orders_ws(self, ids: List[str], symbol: Str = None, params={}):
5355
5681
  raise NotSupported(self.id + ' cancelOrdersWs() is not supported yet')
5356
5682
 
@@ -5366,7 +5692,7 @@ class Exchange(object):
5366
5692
  def cancel_all_orders_ws(self, symbol: Str = None, params={}):
5367
5693
  raise NotSupported(self.id + ' cancelAllOrdersWs() is not supported yet')
5368
5694
 
5369
- def cancel_unified_order(self, order, params={}):
5695
+ def cancel_unified_order(self, order: Order, params={}):
5370
5696
  return self.cancel_order(self.safe_string(order, 'id'), self.safe_string(order, 'symbol'), params)
5371
5697
 
5372
5698
  def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
@@ -5428,6 +5754,9 @@ class Exchange(object):
5428
5754
  def fetch_greeks(self, symbol: str, params={}):
5429
5755
  raise NotSupported(self.id + ' fetchGreeks() is not supported yet')
5430
5756
 
5757
+ def fetch_all_greeks(self, symbols: Strings = None, params={}):
5758
+ raise NotSupported(self.id + ' fetchAllGreeks() is not supported yet')
5759
+
5431
5760
  def fetch_option_chain(self, code: str, params={}):
5432
5761
  raise NotSupported(self.id + ' fetchOptionChain() is not supported yet')
5433
5762
 
@@ -5448,10 +5777,10 @@ class Exchange(object):
5448
5777
  """
5449
5778
  raise NotSupported(self.id + ' fetchDepositsWithdrawals() is not supported yet')
5450
5779
 
5451
- def fetch_deposits(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
5780
+ def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
5452
5781
  raise NotSupported(self.id + ' fetchDeposits() is not supported yet')
5453
5782
 
5454
- def fetch_withdrawals(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
5783
+ def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
5455
5784
  raise NotSupported(self.id + ' fetchWithdrawals() is not supported yet')
5456
5785
 
5457
5786
  def fetch_deposits_ws(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
@@ -5512,7 +5841,9 @@ class Exchange(object):
5512
5841
  return self.safe_string(self.commonCurrencies, code, code)
5513
5842
 
5514
5843
  def currency(self, code: str):
5515
- if self.currencies is None:
5844
+ keys = list(self.currencies.keys())
5845
+ numCurrencies = len(keys)
5846
+ if numCurrencies == 0:
5516
5847
  raise ExchangeError(self.id + ' currencies not loaded')
5517
5848
  if isinstance(code, str):
5518
5849
  if code in self.currencies:
@@ -5681,10 +6012,16 @@ class Exchange(object):
5681
6012
  precisionNumber = int(precision)
5682
6013
  if precisionNumber == 0:
5683
6014
  return '1'
5684
- parsedPrecision = '0.'
5685
- for i in range(0, precisionNumber - 1):
5686
- parsedPrecision = parsedPrecision + '0'
5687
- return parsedPrecision + '1'
6015
+ if precisionNumber > 0:
6016
+ parsedPrecision = '0.'
6017
+ for i in range(0, precisionNumber - 1):
6018
+ parsedPrecision = parsedPrecision + '0'
6019
+ return parsedPrecision + '1'
6020
+ else:
6021
+ parsedPrecision = '1'
6022
+ for i in range(0, precisionNumber * -1 - 1):
6023
+ parsedPrecision = parsedPrecision + '0'
6024
+ return parsedPrecision + '0'
5688
6025
 
5689
6026
  def integer_precision_to_amount(self, precision: Str):
5690
6027
  """
@@ -5952,6 +6289,29 @@ class Exchange(object):
5952
6289
  symbol = None if (market is None) else market['symbol']
5953
6290
  return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
5954
6291
 
6292
+ def handle_trigger_prices_and_params(self, symbol, params, omitParams=True):
6293
+ #
6294
+ triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
6295
+ triggerPriceStr: Str = None
6296
+ stopLossPrice = self.safe_string(params, 'stopLossPrice')
6297
+ stopLossPriceStr: Str = None
6298
+ takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
6299
+ takeProfitPriceStr: Str = None
6300
+ #
6301
+ if triggerPrice is not None:
6302
+ if omitParams:
6303
+ params = self.omit(params, ['triggerPrice', 'stopPrice'])
6304
+ triggerPriceStr = self.price_to_precision(symbol, float(triggerPrice))
6305
+ if stopLossPrice is not None:
6306
+ if omitParams:
6307
+ params = self.omit(params, 'stopLossPrice')
6308
+ stopLossPriceStr = self.price_to_precision(symbol, float(stopLossPrice))
6309
+ if takeProfitPrice is not None:
6310
+ if omitParams:
6311
+ params = self.omit(params, 'takeProfitPrice')
6312
+ takeProfitPriceStr = self.price_to_precision(symbol, float(takeProfitPrice))
6313
+ return [triggerPriceStr, stopLossPriceStr, takeProfitPriceStr, params]
6314
+
5955
6315
  def handle_trigger_direction_and_params(self, params, exchangeSpecificKey: Str = None, allowEmpty: Bool = False):
5956
6316
  """
5957
6317
  @ignore
@@ -6105,7 +6465,7 @@ class Exchange(object):
6105
6465
  else:
6106
6466
  raise NotSupported(self.id + ' fetchFundingInterval() is not supported yet')
6107
6467
 
6108
- def fetch_mark_ohlcv(self, symbol, timeframe='1m', since: Int = None, limit: Int = None, params={}):
6468
+ def fetch_mark_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}):
6109
6469
  """
6110
6470
  fetches historical mark price candlestick data containing the open, high, low, and close price of a market
6111
6471
  :param str symbol: unified symbol of the market to fetch OHLCV data for
@@ -6123,7 +6483,7 @@ class Exchange(object):
6123
6483
  else:
6124
6484
  raise NotSupported(self.id + ' fetchMarkOHLCV() is not supported yet')
6125
6485
 
6126
- def fetch_index_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
6486
+ def fetch_index_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}):
6127
6487
  """
6128
6488
  fetches historical index price candlestick data containing the open, high, low, and close price of a market
6129
6489
  :param str symbol: unified symbol of the market to fetch OHLCV data for
@@ -6141,7 +6501,7 @@ class Exchange(object):
6141
6501
  else:
6142
6502
  raise NotSupported(self.id + ' fetchIndexOHLCV() is not supported yet')
6143
6503
 
6144
- def fetch_premium_index_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
6504
+ def fetch_premium_index_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}):
6145
6505
  """
6146
6506
  fetches historical premium index price candlestick data containing the open, high, low, and close price of a market
6147
6507
  :param str symbol: unified symbol of the market to fetch OHLCV data for
@@ -6373,7 +6733,7 @@ class Exchange(object):
6373
6733
  calls = 0
6374
6734
  result = []
6375
6735
  errors = 0
6376
- until = self.safe_integer_2(params, 'untill', 'till') # do not omit it from params here
6736
+ until = self.safe_integer_n(params, ['until', 'untill', 'till']) # do not omit it from params here
6377
6737
  maxEntriesPerRequest, params = self.handle_max_entries_per_request_and_params(method, maxEntriesPerRequest, params)
6378
6738
  if (paginationDirection == 'forward'):
6379
6739
  if since is None:
@@ -6613,6 +6973,15 @@ class Exchange(object):
6613
6973
  values = list(uniqueResult.values())
6614
6974
  return values
6615
6975
 
6976
+ def remove_keys_from_dict(self, dict: dict, removeKeys: List[str]):
6977
+ keys = list(dict.keys())
6978
+ newDict = {}
6979
+ for i in range(0, len(keys)):
6980
+ key = keys[i]
6981
+ if not self.in_array(key, removeKeys):
6982
+ newDict[key] = dict[key]
6983
+ return newDict
6984
+
6616
6985
  def handle_until_option(self, key: str, request, params, multiplier=1):
6617
6986
  until = self.safe_integer_2(params, 'until', 'till')
6618
6987
  if until is not None:
@@ -6660,6 +7029,27 @@ class Exchange(object):
6660
7029
  def parse_greeks(self, greeks: dict, market: Market = None):
6661
7030
  raise NotSupported(self.id + ' parseGreeks() is not supported yet')
6662
7031
 
7032
+ def parse_all_greeks(self, greeks, symbols: Strings = None, params={}):
7033
+ #
7034
+ # the value of greeks is either a dict or a list
7035
+ #
7036
+ results = []
7037
+ if isinstance(greeks, list):
7038
+ for i in range(0, len(greeks)):
7039
+ parsedTicker = self.parse_greeks(greeks[i])
7040
+ greek = self.extend(parsedTicker, params)
7041
+ results.append(greek)
7042
+ else:
7043
+ marketIds = list(greeks.keys())
7044
+ for i in range(0, len(marketIds)):
7045
+ marketId = marketIds[i]
7046
+ market = self.safe_market(marketId)
7047
+ parsed = self.parse_greeks(greeks[marketId], market)
7048
+ greek = self.extend(parsed, params)
7049
+ results.append(greek)
7050
+ symbols = self.market_symbols(symbols)
7051
+ return self.filter_by_array(results, 'symbol', symbols)
7052
+
6663
7053
  def parse_option(self, chain: dict, currency: Currency = None, market: Market = None):
6664
7054
  raise NotSupported(self.id + ' parseOption() is not supported yet')
6665
7055
 
@@ -6776,7 +7166,7 @@ class Exchange(object):
6776
7166
  return reconstructedDate
6777
7167
 
6778
7168
  def convert_market_id_expire_date(self, date: str):
6779
- # parse 03JAN24 to 240103
7169
+ # parse 03JAN24 to 240103.
6780
7170
  monthMappping = {
6781
7171
  'JAN': '01',
6782
7172
  'FEB': '02',
@@ -6861,14 +7251,104 @@ class Exchange(object):
6861
7251
  """
6862
7252
  raise NotSupported(self.id + ' fetchTransfers() is not supported yet')
6863
7253
 
6864
- def clean_unsubscription(self, client, subHash: str, unsubHash: str):
7254
+ def un_watch_ohlcv(self, symbol: str, timeframe: str = '1m', params={}):
7255
+ """
7256
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
7257
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
7258
+ :param str timeframe: the length of time each candle represents
7259
+ :param dict [params]: extra parameters specific to the exchange API endpoint
7260
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
7261
+ """
7262
+ raise NotSupported(self.id + ' unWatchOHLCV() is not supported yet')
7263
+
7264
+ def watch_mark_price(self, symbol: str, params={}):
7265
+ """
7266
+ watches a mark price for a specific market
7267
+ :param str symbol: unified symbol of the market to fetch the ticker for
7268
+ :param dict [params]: extra parameters specific to the exchange API endpoint
7269
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
7270
+ """
7271
+ raise NotSupported(self.id + ' watchMarkPrice() is not supported yet')
7272
+
7273
+ def watch_mark_prices(self, symbols: Strings = None, params={}):
7274
+ """
7275
+ watches the mark price for all markets
7276
+ :param str[] symbols: unified symbol of the market to fetch the ticker for
7277
+ :param dict [params]: extra parameters specific to the exchange API endpoint
7278
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
7279
+ """
7280
+ raise NotSupported(self.id + ' watchMarkPrices() is not supported yet')
7281
+
7282
+ def withdraw_ws(self, code: str, amount: float, address: str, tag: Str = None, params={}):
7283
+ """
7284
+ make a withdrawal
7285
+ :param str code: unified currency code
7286
+ :param float amount: the amount to withdraw
7287
+ :param str address: the address to withdraw to
7288
+ :param str tag:
7289
+ :param dict [params]: extra parameters specific to the bitvavo api endpoint
7290
+ :returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
7291
+ """
7292
+ raise NotSupported(self.id + ' withdrawWs() is not supported yet')
7293
+
7294
+ def un_watch_my_trades(self, symbol: Str = None, params={}):
7295
+ """
7296
+ unWatches information on multiple trades made by the user
7297
+ :param str symbol: unified market symbol of the market orders were made in
7298
+ :param dict [params]: extra parameters specific to the exchange API endpoint
7299
+ :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
7300
+ """
7301
+ raise NotSupported(self.id + ' unWatchMyTrades() is not supported yet')
7302
+
7303
+ def create_orders_ws(self, orders: List[OrderRequest], params={}):
7304
+ """
7305
+ create a list of trade orders
7306
+ :param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
7307
+ :param dict [params]: extra parameters specific to the exchange API endpoint
7308
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
7309
+ """
7310
+ raise NotSupported(self.id + ' createOrdersWs() is not supported yet')
7311
+
7312
+ def fetch_orders_by_status_ws(self, status: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
7313
+ """
7314
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
7315
+ :param str symbol: unified symbol of the market to fetch the order book for
7316
+ :param int [limit]: the maximum amount of order book entries to return
7317
+ :param dict [params]: extra parameters specific to the exchange API endpoint
7318
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
7319
+ """
7320
+ raise NotSupported(self.id + ' fetchOrdersByStatusWs() is not supported yet')
7321
+
7322
+ def un_watch_bids_asks(self, symbols: Strings = None, params={}):
7323
+ """
7324
+ unWatches best bid & ask for symbols
7325
+ :param str[] symbols: unified symbol of the market to fetch the ticker for
7326
+ :param dict [params]: extra parameters specific to the exchange API endpoint
7327
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
7328
+ """
7329
+ raise NotSupported(self.id + ' unWatchBidsAsks() is not supported yet')
7330
+
7331
+ def clean_unsubscription(self, client, subHash: str, unsubHash: str, subHashIsPrefix=False):
6865
7332
  if unsubHash in client.subscriptions:
6866
7333
  del client.subscriptions[unsubHash]
6867
- if subHash in client.subscriptions:
6868
- del client.subscriptions[subHash]
6869
- if subHash in client.futures:
6870
- error = UnsubscribeError(self.id + ' ' + subHash)
6871
- client.reject(error, subHash)
7334
+ if not subHashIsPrefix:
7335
+ if subHash in client.subscriptions:
7336
+ del client.subscriptions[subHash]
7337
+ if subHash in client.futures:
7338
+ error = UnsubscribeError(self.id + ' ' + subHash)
7339
+ client.reject(error, subHash)
7340
+ else:
7341
+ clientSubscriptions = list(client.subscriptions.keys())
7342
+ for i in range(0, len(clientSubscriptions)):
7343
+ sub = clientSubscriptions[i]
7344
+ if sub.startswith(subHash):
7345
+ del client.subscriptions[sub]
7346
+ clientFutures = list(client.futures.keys())
7347
+ for i in range(0, len(clientFutures)):
7348
+ future = clientFutures[i]
7349
+ if future.startswith(subHash):
7350
+ error = UnsubscribeError(self.id + ' ' + future)
7351
+ client.reject(error, future)
6872
7352
  client.resolve(True, unsubHash)
6873
7353
 
6874
7354
  def clean_cache(self, subscription: dict):
@@ -6876,12 +7356,12 @@ class Exchange(object):
6876
7356
  symbols = self.safe_list(subscription, 'symbols', [])
6877
7357
  symbolsLength = len(symbols)
6878
7358
  if topic == 'ohlcv':
6879
- symbolsAndTimeFrames = self.safe_list(subscription, 'symbolsAndTimeframes', [])
6880
- for i in range(0, len(symbolsAndTimeFrames)):
6881
- symbolAndTimeFrame = symbolsAndTimeFrames[i]
7359
+ symbolsAndTimeframes = self.safe_list(subscription, 'symbolsAndTimeframes', [])
7360
+ for i in range(0, len(symbolsAndTimeframes)):
7361
+ symbolAndTimeFrame = symbolsAndTimeframes[i]
6882
7362
  symbol = self.safe_string(symbolAndTimeFrame, 0)
6883
7363
  timeframe = self.safe_string(symbolAndTimeFrame, 1)
6884
- if symbol in self.ohlcvs:
7364
+ if (self.ohlcvs is not None) and (symbol in self.ohlcvs):
6885
7365
  if timeframe in self.ohlcvs[symbol]:
6886
7366
  del self.ohlcvs[symbol][timeframe]
6887
7367
  elif symbolsLength > 0:
@@ -6896,24 +7376,31 @@ class Exchange(object):
6896
7376
  elif topic == 'ticker':
6897
7377
  if symbol in self.tickers:
6898
7378
  del self.tickers[symbol]
7379
+ elif topic == 'bidsasks':
7380
+ if symbol in self.bidsasks:
7381
+ del self.bidsasks[symbol]
6899
7382
  else:
6900
- if topic == 'myTrades':
6901
- # don't reset self.myTrades directly here
6902
- # because in c# we need to use a different object(thread-safe dict)
6903
- keys = list(self.myTrades.keys())
6904
- for i in range(0, len(keys)):
6905
- key = keys[i]
6906
- if key in self.myTrades:
6907
- del self.myTrades[key]
6908
- elif topic == 'orders':
6909
- orderSymbols = list(self.orders.keys())
6910
- for i in range(0, len(orderSymbols)):
6911
- orderSymbol = orderSymbols[i]
6912
- if orderSymbol in self.orders:
6913
- del self.orders[orderSymbol]
6914
- elif topic == 'ticker':
7383
+ if topic == 'myTrades' and (self.myTrades is not None):
7384
+ self.myTrades = None
7385
+ elif topic == 'orders' and (self.orders is not None):
7386
+ self.orders = None
7387
+ elif topic == 'positions' and (self.positions is not None):
7388
+ self.positions = None
7389
+ clients = list(self.clients.values())
7390
+ for i in range(0, len(clients)):
7391
+ client = clients[i]
7392
+ futures = client.futures
7393
+ if (futures is not None) and ('fetchPositionsSnapshot' in futures):
7394
+ del futures['fetchPositionsSnapshot']
7395
+ elif topic == 'ticker' and (self.tickers is not None):
6915
7396
  tickerSymbols = list(self.tickers.keys())
6916
7397
  for i in range(0, len(tickerSymbols)):
6917
7398
  tickerSymbol = tickerSymbols[i]
6918
7399
  if tickerSymbol in self.tickers:
6919
7400
  del self.tickers[tickerSymbol]
7401
+ elif topic == 'bidsasks' and (self.bidsasks is not None):
7402
+ bidsaskSymbols = list(self.bidsasks.keys())
7403
+ for i in range(0, len(bidsaskSymbols)):
7404
+ bidsaskSymbol = bidsaskSymbols[i]
7405
+ if bidsaskSymbol in self.bidsasks:
7406
+ del self.bidsasks[bidsaskSymbol]