ccxt 4.1.54__py2.py3-none-any.whl → 4.1.56__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.

Potentially problematic release.


This version of ccxt might be problematic. Click here for more details.

Files changed (251) hide show
  1. ccxt/__init__.py +2 -2
  2. ccxt/abstract/binance.py +1 -0
  3. ccxt/abstract/binancecoinm.py +1 -0
  4. ccxt/abstract/binanceus.py +1 -0
  5. ccxt/abstract/binanceusdm.py +1 -0
  6. ccxt/abstract/bitbank.py +1 -0
  7. ccxt/abstract/coinbase.py +2 -0
  8. ccxt/abstract/htx.py +3 -0
  9. ccxt/abstract/huobi.py +3 -0
  10. ccxt/abstract/huobipro.py +3 -0
  11. ccxt/abstract/okex.py +3 -1
  12. ccxt/abstract/okex5.py +3 -1
  13. ccxt/abstract/okx.py +3 -1
  14. ccxt/ace.py +23 -23
  15. ccxt/alpaca.py +8 -8
  16. ccxt/ascendex.py +26 -26
  17. ccxt/async_support/__init__.py +2 -2
  18. ccxt/async_support/ace.py +23 -23
  19. ccxt/async_support/alpaca.py +8 -8
  20. ccxt/async_support/ascendex.py +26 -26
  21. ccxt/async_support/base/exchange.py +4 -2216
  22. ccxt/async_support/bigone.py +21 -24
  23. ccxt/async_support/binance.py +61 -54
  24. ccxt/async_support/bingx.py +28 -28
  25. ccxt/async_support/bit2c.py +9 -9
  26. ccxt/async_support/bitbank.py +11 -10
  27. ccxt/async_support/bitbns.py +11 -11
  28. ccxt/async_support/bitfinex.py +15 -15
  29. ccxt/async_support/bitfinex2.py +22 -22
  30. ccxt/async_support/bitflyer.py +13 -13
  31. ccxt/async_support/bitforex.py +10 -10
  32. ccxt/async_support/bitget.py +44 -44
  33. ccxt/async_support/bithumb.py +9 -9
  34. ccxt/async_support/bitmart.py +85 -104
  35. ccxt/async_support/bitmex.py +27 -27
  36. ccxt/async_support/bitopro.py +18 -18
  37. ccxt/async_support/bitpanda.py +18 -18
  38. ccxt/async_support/bitrue.py +14 -14
  39. ccxt/async_support/bitso.py +17 -17
  40. ccxt/async_support/bitstamp.py +17 -17
  41. ccxt/async_support/bittrex.py +22 -24
  42. ccxt/async_support/bitvavo.py +15 -15
  43. ccxt/async_support/bl3p.py +4 -4
  44. ccxt/async_support/blockchaincom.py +17 -17
  45. ccxt/async_support/btcalpha.py +14 -14
  46. ccxt/async_support/btcbox.py +9 -9
  47. ccxt/async_support/btcmarkets.py +17 -17
  48. ccxt/async_support/btcturk.py +9 -9
  49. ccxt/async_support/bybit.py +46 -46
  50. ccxt/async_support/cex.py +10 -10
  51. ccxt/async_support/coinbase.py +69 -25
  52. ccxt/async_support/coinbasepro.py +19 -19
  53. ccxt/async_support/coincheck.py +10 -10
  54. ccxt/async_support/coinex.py +57 -66
  55. ccxt/async_support/coinlist.py +22 -22
  56. ccxt/async_support/coinmate.py +10 -10
  57. ccxt/async_support/coinone.py +10 -10
  58. ccxt/async_support/coinsph.py +17 -17
  59. ccxt/async_support/coinspot.py +5 -5
  60. ccxt/async_support/cryptocom.py +27 -27
  61. ccxt/async_support/currencycom.py +18 -18
  62. ccxt/async_support/delta.py +21 -21
  63. ccxt/async_support/deribit.py +24 -24
  64. ccxt/async_support/digifinex.py +35 -35
  65. ccxt/async_support/exmo.py +19 -19
  66. ccxt/async_support/gate.py +38 -38
  67. ccxt/async_support/gemini.py +11 -11
  68. ccxt/async_support/hitbtc.py +27 -27
  69. ccxt/async_support/hollaex.py +19 -19
  70. ccxt/async_support/htx.py +47 -44
  71. ccxt/async_support/huobijp.py +22 -22
  72. ccxt/async_support/idex.py +20 -20
  73. ccxt/async_support/independentreserve.py +9 -9
  74. ccxt/async_support/indodax.py +10 -10
  75. ccxt/async_support/kraken.py +25 -25
  76. ccxt/async_support/krakenfutures.py +17 -17
  77. ccxt/async_support/kucoin.py +27 -27
  78. ccxt/async_support/kucoinfutures.py +20 -20
  79. ccxt/async_support/kuna.py +19 -19
  80. ccxt/async_support/latoken.py +14 -14
  81. ccxt/async_support/lbank.py +18 -18
  82. ccxt/async_support/luno.py +14 -14
  83. ccxt/async_support/lykke.py +12 -12
  84. ccxt/async_support/mercado.py +11 -11
  85. ccxt/async_support/mexc.py +36 -36
  86. ccxt/async_support/ndax.py +18 -18
  87. ccxt/async_support/novadax.py +17 -17
  88. ccxt/async_support/oceanex.py +12 -12
  89. ccxt/async_support/okcoin.py +19 -19
  90. ccxt/async_support/okx.py +48 -45
  91. ccxt/async_support/p2b.py +6 -6
  92. ccxt/async_support/paymium.py +6 -6
  93. ccxt/async_support/phemex.py +57 -57
  94. ccxt/async_support/poloniex.py +31 -30
  95. ccxt/async_support/poloniexfutures.py +16 -16
  96. ccxt/async_support/probit.py +22 -22
  97. ccxt/async_support/tidex.py +15 -15
  98. ccxt/async_support/timex.py +20 -20
  99. ccxt/async_support/tokocrypto.py +16 -16
  100. ccxt/async_support/upbit.py +15 -15
  101. ccxt/async_support/wavesexchange.py +12 -12
  102. ccxt/async_support/wazirx.py +13 -13
  103. ccxt/async_support/whitebit.py +26 -26
  104. ccxt/async_support/woo.py +47 -47
  105. ccxt/async_support/yobit.py +8 -8
  106. ccxt/async_support/zaif.py +10 -10
  107. ccxt/async_support/zonda.py +16 -16
  108. ccxt/base/errors.py +17 -16
  109. ccxt/base/exchange.py +57 -97
  110. ccxt/base/types.py +138 -139
  111. ccxt/bigone.py +21 -24
  112. ccxt/binance.py +61 -54
  113. ccxt/bingx.py +28 -28
  114. ccxt/bit2c.py +9 -9
  115. ccxt/bitbank.py +11 -10
  116. ccxt/bitbns.py +11 -11
  117. ccxt/bitfinex.py +15 -15
  118. ccxt/bitfinex2.py +22 -22
  119. ccxt/bitflyer.py +13 -13
  120. ccxt/bitforex.py +10 -10
  121. ccxt/bitget.py +44 -44
  122. ccxt/bithumb.py +9 -9
  123. ccxt/bitmart.py +85 -104
  124. ccxt/bitmex.py +27 -27
  125. ccxt/bitopro.py +18 -18
  126. ccxt/bitpanda.py +18 -18
  127. ccxt/bitrue.py +14 -14
  128. ccxt/bitso.py +17 -17
  129. ccxt/bitstamp.py +17 -17
  130. ccxt/bittrex.py +22 -24
  131. ccxt/bitvavo.py +15 -15
  132. ccxt/bl3p.py +4 -4
  133. ccxt/blockchaincom.py +17 -17
  134. ccxt/btcalpha.py +14 -14
  135. ccxt/btcbox.py +9 -9
  136. ccxt/btcmarkets.py +17 -17
  137. ccxt/btcturk.py +9 -9
  138. ccxt/bybit.py +46 -46
  139. ccxt/cex.py +10 -10
  140. ccxt/coinbase.py +69 -25
  141. ccxt/coinbasepro.py +19 -19
  142. ccxt/coincheck.py +10 -10
  143. ccxt/coinex.py +57 -66
  144. ccxt/coinlist.py +22 -22
  145. ccxt/coinmate.py +10 -10
  146. ccxt/coinone.py +10 -10
  147. ccxt/coinsph.py +17 -17
  148. ccxt/coinspot.py +5 -5
  149. ccxt/cryptocom.py +27 -27
  150. ccxt/currencycom.py +18 -18
  151. ccxt/delta.py +21 -21
  152. ccxt/deribit.py +24 -24
  153. ccxt/digifinex.py +35 -35
  154. ccxt/exmo.py +19 -19
  155. ccxt/gate.py +38 -38
  156. ccxt/gemini.py +11 -11
  157. ccxt/hitbtc.py +27 -27
  158. ccxt/hollaex.py +19 -19
  159. ccxt/htx.py +47 -44
  160. ccxt/huobijp.py +22 -22
  161. ccxt/idex.py +20 -20
  162. ccxt/independentreserve.py +9 -9
  163. ccxt/indodax.py +10 -10
  164. ccxt/kraken.py +25 -25
  165. ccxt/krakenfutures.py +17 -17
  166. ccxt/kucoin.py +27 -27
  167. ccxt/kucoinfutures.py +20 -20
  168. ccxt/kuna.py +19 -19
  169. ccxt/latoken.py +14 -14
  170. ccxt/lbank.py +18 -18
  171. ccxt/luno.py +14 -14
  172. ccxt/lykke.py +12 -12
  173. ccxt/mercado.py +11 -11
  174. ccxt/mexc.py +36 -36
  175. ccxt/ndax.py +18 -18
  176. ccxt/novadax.py +17 -17
  177. ccxt/oceanex.py +12 -12
  178. ccxt/okcoin.py +19 -19
  179. ccxt/okx.py +48 -45
  180. ccxt/p2b.py +6 -6
  181. ccxt/paymium.py +6 -6
  182. ccxt/phemex.py +57 -57
  183. ccxt/poloniex.py +31 -30
  184. ccxt/poloniexfutures.py +16 -16
  185. ccxt/pro/__init__.py +1 -1
  186. ccxt/pro/alpaca.py +3 -3
  187. ccxt/pro/ascendex.py +2 -2
  188. ccxt/pro/binance.py +9 -9
  189. ccxt/pro/bingx.py +3 -3
  190. ccxt/pro/bitfinex.py +3 -3
  191. ccxt/pro/bitfinex2.py +3 -3
  192. ccxt/pro/bitget.py +3 -3
  193. ccxt/pro/bitmart.py +2 -2
  194. ccxt/pro/bitmex.py +3 -3
  195. ccxt/pro/bitpanda.py +3 -3
  196. ccxt/pro/bitrue.py +2 -2
  197. ccxt/pro/bitstamp.py +2 -2
  198. ccxt/pro/bittrex.py +3 -3
  199. ccxt/pro/bitvavo.py +3 -3
  200. ccxt/pro/blockchaincom.py +2 -2
  201. ccxt/pro/bybit.py +4 -4
  202. ccxt/pro/cex.py +3 -3
  203. ccxt/pro/coinbasepro.py +3 -3
  204. ccxt/pro/coinex.py +2 -2
  205. ccxt/pro/cryptocom.py +5 -5
  206. ccxt/pro/deribit.py +3 -3
  207. ccxt/pro/exmo.py +2 -2
  208. ccxt/pro/gate.py +3 -3
  209. ccxt/pro/gemini.py +2 -2
  210. ccxt/pro/hitbtc.py +4 -4
  211. ccxt/pro/hollaex.py +3 -3
  212. ccxt/pro/htx.py +3 -3
  213. ccxt/pro/idex.py +3 -3
  214. ccxt/pro/kraken.py +7 -7
  215. ccxt/pro/krakenfutures.py +4 -4
  216. ccxt/pro/kucoin.py +3 -3
  217. ccxt/pro/kucoinfutures.py +3 -3
  218. ccxt/pro/mexc.py +3 -3
  219. ccxt/pro/okcoin.py +2 -2
  220. ccxt/pro/okx.py +6 -6
  221. ccxt/pro/phemex.py +3 -3
  222. ccxt/pro/poloniex.py +3 -3
  223. ccxt/pro/poloniexfutures.py +3 -3
  224. ccxt/pro/probit.py +3 -3
  225. ccxt/pro/wazirx.py +3 -3
  226. ccxt/pro/whitebit.py +3 -3
  227. ccxt/pro/woo.py +2 -2
  228. ccxt/probit.py +22 -22
  229. ccxt/test/base/test_shared_methods.py +3 -3
  230. ccxt/test/test_async.py +543 -535
  231. ccxt/test/test_sync.py +542 -534
  232. ccxt/tidex.py +15 -15
  233. ccxt/timex.py +20 -20
  234. ccxt/tokocrypto.py +16 -16
  235. ccxt/upbit.py +15 -15
  236. ccxt/wavesexchange.py +12 -12
  237. ccxt/wazirx.py +13 -13
  238. ccxt/whitebit.py +26 -26
  239. ccxt/woo.py +47 -47
  240. ccxt/yobit.py +8 -8
  241. ccxt/zaif.py +10 -10
  242. ccxt/zonda.py +16 -16
  243. {ccxt-4.1.54.dist-info → ccxt-4.1.56.dist-info}/METADATA +10 -8
  244. ccxt-4.1.56.dist-info/RECORD +449 -0
  245. ccxt/async_support/bitstamp1.py +0 -402
  246. ccxt/async_support/lbank2.py +0 -2620
  247. ccxt/bitstamp1.py +0 -402
  248. ccxt/lbank2.py +0 -2619
  249. ccxt-4.1.54.dist-info/RECORD +0 -453
  250. {ccxt-4.1.54.dist-info → ccxt-4.1.56.dist-info}/WHEEL +0 -0
  251. {ccxt-4.1.54.dist-info → ccxt-4.1.56.dist-info}/top_level.txt +0 -0
ccxt/test/test_sync.py CHANGED
@@ -21,11 +21,21 @@ sys.path.append(root)
21
21
  import ccxt # noqa: E402
22
22
 
23
23
  # ------------------------------------------------------------------------------
24
+ # from typing import Optional
25
+ # from typing import List
26
+ from ccxt.base.errors import NotSupported
27
+ from ccxt.base.errors import NetworkError
28
+ from ccxt.base.errors import ExchangeNotAvailable
29
+ from ccxt.base.errors import OnMaintenance
30
+ from ccxt.base.errors import AuthenticationError
24
31
 
32
+ # ------------------------------------------------------------------------------
25
33
 
26
34
  class Argv(object):
27
- idTests = False
28
- staticTests = False
35
+ id_tests = False
36
+ static_tests = False
37
+ request_tests = False
38
+ response_tests = False
29
39
 
30
40
  sandbox = False
31
41
  privateOnly = False
@@ -48,6 +58,8 @@ parser.add_argument('--verbose', action='store_true', help='enable verbose outpu
48
58
  parser.add_argument('--info', action='store_true', help='enable info output')
49
59
  parser.add_argument('--static', action='store_true', help='run static tests')
50
60
  parser.add_argument('--idTests', action='store_true', help='run brokerId tests')
61
+ parser.add_argument('--responseTests', action='store_true', help='run response tests')
62
+ parser.add_argument('--requestTests', action='store_true', help='run response tests')
51
63
  parser.add_argument('--nonce', type=int, help='integer')
52
64
  parser.add_argument('exchange', type=str, help='exchange id in lowercase', nargs='?')
53
65
  parser.add_argument('symbol', type=str, help='symbol in uppercase', nargs='?')
@@ -81,17 +93,6 @@ sys.excepthook = handle_all_unhandled_exceptions
81
93
 
82
94
  # non-transpiled part, but shared names among langs
83
95
 
84
-
85
- class baseMainTestClass():
86
- lang = 'PY'
87
- staticTestsFailed = False
88
- skippedMethods = {}
89
- checkedPublicTests = {}
90
- testFiles = {}
91
- publicTests = {}
92
- pass
93
-
94
-
95
96
  is_synchronous = 'async' not in os.path.basename(__file__)
96
97
 
97
98
  rootDir = current_dir + '/../../../'
@@ -101,6 +102,22 @@ LOG_CHARS_LENGTH = 10000
101
102
  ext = 'py'
102
103
 
103
104
 
105
+ class baseMainTestClass():
106
+ lang = 'PY'
107
+ request_tests_failed = False
108
+ response_tests_failed = False
109
+ response_tests = False
110
+ skipped_methods = {}
111
+ check_public_tests = {}
112
+ test_files = {}
113
+ public_tests = {}
114
+ root_dir = rootDir
115
+ env_vars = envVars
116
+ ext = ext
117
+ root_dir_for_skips = rootDirForSkips
118
+ pass
119
+
120
+
104
121
  def dump(*args):
105
122
  print(' '.join([str(arg) for arg in args]))
106
123
 
@@ -153,6 +170,9 @@ def call_method(testFiles, methodName, exchange, skippedProperties, args):
153
170
  def call_exchange_method_dynamically(exchange, methodName, args):
154
171
  return getattr(exchange, methodName)(*args)
155
172
 
173
+ def call_overriden_method(exchange, methodName, args):
174
+ # needed for php
175
+ return call_exchange_method_dynamically(exchange, methodName, args)
156
176
 
157
177
  def exception_message(exc):
158
178
  message = '[' + type(exc).__name__ + '] ' + "".join(format_exception(type(exc), exc, exc.__traceback__, limit=6))
@@ -197,195 +217,196 @@ def close(exchange):
197
217
  if (hasattr(exchange, 'close')):
198
218
  exchange.close()
199
219
 
200
- # *********************************
201
- # ***** AUTO-TRANSPILER-START *****
202
- # -*- coding: utf-8 -*-
203
-
204
- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
205
- # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
206
-
207
-
208
- from typing import List
209
- from ccxt.base.errors import NotSupported
210
- from ccxt.base.errors import NetworkError
211
- from ccxt.base.errors import ExchangeNotAvailable
212
- from ccxt.base.errors import OnMaintenance
213
- from ccxt.base.errors import AuthenticationError
220
+ def is_null_value(value):
221
+ return value is None
214
222
 
223
+ def set_fetch_response(exchange: ccxt.Exchange, data):
224
+ def fetch(url, method='GET', headers=None, body=None):
225
+ return data
226
+ exchange.fetch = fetch
227
+ return exchange
215
228
 
229
+ # *********************************
230
+ # ***** AUTO-TRANSPILER-START *****
216
231
  class testMainClass(baseMainTestClass):
217
-
218
232
  def parse_cli_args(self):
219
- self.idTests = get_cli_arg_value('--idTests')
220
- self.staticTests = get_cli_arg_value('--static')
233
+ self.response_tests = get_cli_arg_value('--responseTests')
234
+ self.id_tests = get_cli_arg_value('--idTests')
235
+ self.request_tests = get_cli_arg_value('--requestTests')
221
236
  self.info = get_cli_arg_value('--info')
222
237
  self.verbose = get_cli_arg_value('--verbose')
223
238
  self.debug = get_cli_arg_value('--debug')
224
- self.privateTest = get_cli_arg_value('--private')
225
- self.privateTestOnly = get_cli_arg_value('--privateOnly')
239
+ self.private_test = get_cli_arg_value('--private')
240
+ self.private_test_only = get_cli_arg_value('--privateOnly')
226
241
  self.sandbox = get_cli_arg_value('--sandbox')
227
242
 
228
- def init(self, exchangeId, symbol):
243
+ def init(self, exchange_id, symbol):
229
244
  self.parse_cli_args()
230
- if self.staticTests:
231
- self.run_static_tests(exchangeId, symbol) # symbol here is the testname
245
+ if self.response_tests:
246
+ self.run_static_response_tests(exchange_id, symbol)
247
+ return
248
+ if self.request_tests:
249
+ self.run_static_request_tests(exchange_id, symbol) # symbol here is the testname
232
250
  return
233
- if self.idTests:
251
+ if self.id_tests:
234
252
  self.run_broker_id_tests()
235
253
  return
236
- symbolStr = symbol is not symbol if None else 'all'
237
- dump('\nTESTING ', ext, {'exchange': exchangeId, 'symbol': symbolStr}, '\n')
238
- exchangeArgs = {
254
+ symbol_str = symbol if symbol is not None else 'all'
255
+ dump('\nTESTING ', self.ext, {
256
+ 'exchange': exchange_id,
257
+ 'symbol': symbol_str,
258
+ }, '\n')
259
+ exchange_args = {
239
260
  'verbose': self.verbose,
240
261
  'debug': self.debug,
241
262
  'enableRateLimit': True,
242
263
  'timeout': 30000,
243
264
  }
244
- exchange = init_exchange(exchangeId, exchangeArgs)
265
+ exchange = init_exchange(exchange_id, exchange_args)
245
266
  self.import_files(exchange)
246
267
  self.expand_settings(exchange, symbol)
247
268
  self.start_test(exchange, symbol)
248
269
 
249
270
  def import_files(self, exchange):
250
271
  # exchange tests
251
- self.testFiles = {}
272
+ self.test_files = {}
252
273
  properties = list(exchange.has.keys())
253
274
  properties.append('loadMarkets')
254
275
  set_test_files(self, properties)
255
276
 
256
277
  def expand_settings(self, exchange, symbol):
257
- exchangeId = exchange.id
258
- keysGlobal = rootDir + 'keys.json'
259
- keysLocal = rootDir + 'keys.local.json'
260
- keysGlobalExists = io_file_exists(keysGlobal)
261
- keysLocalExists = io_file_exists(keysLocal)
262
- globalSettings = io_file_read(keysGlobal) if keysGlobalExists else {}
263
- localSettings = io_file_read(keysLocal) if keysLocalExists else {}
264
- allSettings = exchange.deep_extend(globalSettings, localSettings)
265
- exchangeSettings = exchange.safe_value(allSettings, exchangeId, {})
266
- if exchangeSettings:
267
- settingKeys = list(exchangeSettings.keys())
268
- for i in range(0, len(settingKeys)):
269
- key = settingKeys[i]
270
- if exchangeSettings[key]:
271
- finalValue = None
272
- if isinstance(exchangeSettings[key], dict):
278
+ exchange_id = exchange.id
279
+ keys_global = self.root_dir + 'keys.json'
280
+ keys_local = self.root_dir + 'keys.local.json'
281
+ keys_global_exists = io_file_exists(keys_global)
282
+ keys_local_exists = io_file_exists(keys_local)
283
+ global_settings = io_file_read(keys_global) if keys_global_exists else {}
284
+ local_settings = io_file_read(keys_local) if keys_local_exists else {}
285
+ all_settings = exchange.deep_extend(global_settings, local_settings)
286
+ exchange_settings = exchange.safe_value(all_settings, exchange_id, {})
287
+ if exchange_settings:
288
+ setting_keys = list(exchange_settings.keys())
289
+ for i in range(0, len(setting_keys)):
290
+ key = setting_keys[i]
291
+ if exchange_settings[key]:
292
+ final_value = None
293
+ if isinstance(exchange_settings[key], dict):
273
294
  existing = get_exchange_prop(exchange, key, {})
274
- finalValue = exchange.deep_extend(existing, exchangeSettings[key])
295
+ final_value = exchange.deep_extend(existing, exchange_settings[key])
275
296
  else:
276
- finalValue = exchangeSettings[key]
277
- set_exchange_prop(exchange, key, finalValue)
297
+ final_value = exchange_settings[key]
298
+ set_exchange_prop(exchange, key, final_value)
278
299
  # credentials
279
- reqCreds = get_exchange_prop(exchange, 're' + 'quiredCredentials') # dont glue the r-e-q-u-i-r-e phrase, because leads to messed up transpilation
280
- objkeys = list(reqCreds.keys())
300
+ req_creds = get_exchange_prop(exchange, 're' + 'quiredCredentials') # dont glue the r-e-q-u-i-r-e phrase, because leads to messed up transpilation
301
+ objkeys = list(req_creds.keys())
281
302
  for i in range(0, len(objkeys)):
282
303
  credential = objkeys[i]
283
- isRequired = reqCreds[credential]
284
- if isRequired and get_exchange_prop(exchange, credential) is None:
285
- fullKey = exchangeId + '_' + credential
286
- credentialEnvName = fullKey.upper() # example: KRAKEN_APIKEY
287
- credentialValue = envVars[credentialEnvName] if (credentialEnvName in envVars) else None
288
- if credentialValue:
289
- set_exchange_prop(exchange, credential, credentialValue)
304
+ is_required = req_creds[credential]
305
+ if is_required and get_exchange_prop(exchange, credential) is None:
306
+ full_key = exchange_id + '_' + credential
307
+ credential_env_name = full_key.upper() # example: KRAKEN_APIKEY
308
+ credential_value = self.env_vars[credential_env_name] if (credential_env_name in self.env_vars) else None
309
+ if credential_value:
310
+ set_exchange_prop(exchange, credential, credential_value)
290
311
  # skipped tests
291
- skippedFile = rootDirForSkips + 'skip-tests.json'
292
- skippedSettings = io_file_read(skippedFile)
293
- skippedSettingsForExchange = exchange.safe_value(skippedSettings, exchangeId, {})
312
+ skipped_file = self.root_dir_for_skips + 'skip-tests.json'
313
+ skipped_settings = io_file_read(skipped_file)
314
+ skipped_settings_for_exchange = exchange.safe_value(skipped_settings, exchange_id, {})
294
315
  # others
295
- timeout = exchange.safe_value(skippedSettingsForExchange, 'timeout')
316
+ timeout = exchange.safe_value(skipped_settings_for_exchange, 'timeout')
296
317
  if timeout is not None:
297
318
  exchange.timeout = timeout
298
- exchange.httpsProxy = exchange.safe_string(skippedSettingsForExchange, 'httpsProxy')
299
- self.skippedMethods = exchange.safe_value(skippedSettingsForExchange, 'skipMethods', {})
300
- self.checkedPublicTests = {}
319
+ exchange.https_proxy = exchange.safe_string(skipped_settings_for_exchange, 'httpsProxy')
320
+ self.skipped_methods = exchange.safe_value(skipped_settings_for_exchange, 'skipMethods', {})
321
+ self.checked_public_tests = {}
301
322
 
302
323
  def add_padding(self, message, size):
303
324
  # has to be transpilable
304
325
  res = ''
305
- missingSpace = size - len(message) - 0 # - 0 is added just to trick transpile to treat the .length string for php
306
- if missingSpace > 0:
307
- for i in range(0, missingSpace):
326
+ message_length = len(message) # avoid php transpilation issue
327
+ missing_space = size - message_length - 0 # - 0 is added just to trick transpile to treat the .length as a string for php
328
+ if missing_space > 0:
329
+ for i in range(0, missing_space):
308
330
  res += ' '
309
331
  return message + res
310
332
 
311
- def test_method(self, methodName, exchange, args, isPublic):
312
- isLoadMarkets = (methodName == 'loadMarkets')
313
- methodNameInTest = get_test_name(methodName)
314
- # if self is a private test, and the implementation was already tested in public, then no need to re-test it in private test(exception is fetchCurrencies, because our approach in base exchange)
315
- if not isPublic and (methodNameInTest in self.checkedPublicTests) and (methodName != 'fetchCurrencies'):
333
+ def test_method(self, method_name, exchange, args, is_public):
334
+ is_load_markets = (method_name == 'loadMarkets')
335
+ method_name_in_test = get_test_name(method_name)
336
+ # if this is a private test, and the implementation was already tested in public, then no need to re-test it in private test (exception is fetchCurrencies, because our approach in base exchange)
337
+ if not is_public and (method_name_in_test in self.checked_public_tests) and (method_name != 'fetchCurrencies'):
316
338
  return
317
- skipMessage = None
318
- if not isLoadMarkets and (not(methodName in exchange.has) or not exchange.has[methodName]):
319
- skipMessage = '[INFO:UNSUPPORTED_TEST]' # keep it aligned with the longest message
320
- elif (methodName in self.skippedMethods) and (isinstance(self.skippedMethods[methodName], str)):
321
- skipMessage = '[INFO:SKIPPED_TEST]'
322
- elif not (methodNameInTest in self.testFiles):
323
- skipMessage = '[INFO:UNIMPLEMENTED_TEST]'
324
- # exceptionally for `loadMarkets` call, we call it before it's even checked for "skip" need it to be called anyway(but can skip "test.loadMarket" for it)
325
- if isLoadMarkets:
339
+ skip_message = None
340
+ if not is_load_markets and (not (method_name in exchange.has) or not exchange.has[method_name]):
341
+ skip_message = '[INFO:UNSUPPORTED_TEST]' # keep it aligned with the longest message
342
+ elif (method_name in self.skipped_methods) and (isinstance(self.skipped_methods[method_name], str)):
343
+ skip_message = '[INFO:SKIPPED_TEST]'
344
+ elif not (method_name_in_test in self.test_files):
345
+ skip_message = '[INFO:UNIMPLEMENTED_TEST]'
346
+ # exceptionally for `loadMarkets` call, we call it before it's even checked for "skip" as we need it to be called anyway (but can skip "test.loadMarket" for it)
347
+ if is_load_markets:
326
348
  exchange.load_markets(True)
327
- if skipMessage:
349
+ if skip_message:
328
350
  if self.info:
329
- dump(self.add_padding(skipMessage, 25), exchange.id, methodNameInTest)
351
+ dump(self.add_padding(skip_message, 25), exchange.id, method_name_in_test)
330
352
  return
331
353
  if self.info:
332
- argsStringified = '(' + ','.join(args) + ')'
333
- dump(self.add_padding('[INFO:TESTING]', 25), exchange.id, methodNameInTest, argsStringified)
334
- skippedProperties = exchange.safe_value(self.skippedMethods, methodName, {})
335
- call_method(self.testFiles, methodNameInTest, exchange, skippedProperties, args)
354
+ args_stringified = '(' + ','.join(args) + ')'
355
+ dump(self.add_padding('[INFO:TESTING]', 25), exchange.id, method_name_in_test, args_stringified)
356
+ skipped_properties = exchange.safe_value(self.skipped_methods, method_name, {})
357
+ call_method(self.test_files, method_name_in_test, exchange, skipped_properties, args)
336
358
  # if it was passed successfully, add to the list of successfull tests
337
- if isPublic:
338
- self.checkedPublicTests[methodNameInTest] = True
359
+ if is_public:
360
+ self.checked_public_tests[method_name_in_test] = True
339
361
 
340
- def test_safe(self, methodName, exchange, args=[], isPublic=False):
341
- # `testSafe` method does not raise an exception, instead mutes it.
342
- # The reason we mute the thrown exceptions here is because if self test is part
362
+ def test_safe(self, method_name, exchange, args=[], is_public=False):
363
+ # `testSafe` method does not throw an exception, instead mutes it.
364
+ # The reason we mute the thrown exceptions here is because if this test is part
343
365
  # of "runPublicTests", then we don't want to stop the whole test if any single
344
366
  # test-method fails. For example, if "fetchOrderBook" public test fails, we still
345
- # want to run "fetchTickers" and other methods. However, independently self fact,
346
- # from those test-methods we still echo-out(console.log/print...) the exception
367
+ # want to run "fetchTickers" and other methods. However, independently this fact,
368
+ # from those test-methods we still echo-out (console.log/print...) the exception
347
369
  # messages with specific formatted message "[TEST_FAILURE] ..." and that output is
348
370
  # then regex-parsed by run-tests.js, so the exceptions are still printed out to
349
371
  # console from there. So, even if some public tests fail, the script will continue
350
- # doing other things(testing other spot/swap or private tests ...)
351
- maxRetries = 3
352
- argsStringified = exchange.json(args) # args.join() breaks when we provide a list of symbols | "args.toString()" breaks bcz of "array to string conversion"
353
- for i in range(0, maxRetries):
372
+ # doing other things (testing other spot/swap or private tests ...)
373
+ max_retries = 3
374
+ args_stringified = exchange.json(args) # args.join() breaks when we provide a list of symbols | "args.toString()" breaks bcz of "array to string conversion"
375
+ for i in range(0, max_retries):
354
376
  try:
355
- self.test_method(methodName, exchange, args, isPublic)
377
+ self.test_method(method_name, exchange, args, is_public)
356
378
  return True
357
379
  except Exception as e:
358
- isAuthError = (isinstance(e, AuthenticationError))
359
- isNotSupported = (isinstance(e, NotSupported))
360
- isNetworkError = (isinstance(e, NetworkError)) # includes "DDoSProtection", "RateLimitExceeded", "RequestTimeout", "ExchangeNotAvailable", "isOperationFailed", "InvalidNonce", ...
361
- isExchangeNotAvailable = (isinstance(e, ExchangeNotAvailable))
362
- isOnMaintenance = (isinstance(e, OnMaintenance))
363
- tempFailure = isNetworkError and (not isExchangeNotAvailable or isOnMaintenance) # we do not mute specifically "ExchangeNotAvailable" excetpion(but its subtype "OnMaintenance" can be muted)
364
- if tempFailure:
365
- # if last retry was gone with same `tempFailure` error, then let's eventually return False
366
- if i == maxRetries - 1:
367
- dump('[TEST_WARNING]', 'Method could not be tested due to a repeated Network/Availability issues', ' | ', exchange.id, methodName, argsStringified)
380
+ is_auth_error = (isinstance(e, AuthenticationError))
381
+ is_not_supported = (isinstance(e, NotSupported))
382
+ is_network_error = (isinstance(e, NetworkError)) # includes "DDoSProtection", "RateLimitExceeded", "RequestTimeout", "ExchangeNotAvailable", "isOperationFailed", "InvalidNonce", ...
383
+ is_exchange_not_available = (isinstance(e, ExchangeNotAvailable))
384
+ is_on_maintenance = (isinstance(e, OnMaintenance))
385
+ temp_failure = is_network_error and (not is_exchange_not_available or is_on_maintenance) # we do not mute specifically "ExchangeNotAvailable" excetpion (but its subtype "OnMaintenance" can be muted)
386
+ if temp_failure:
387
+ # if last retry was gone with same `tempFailure` error, then let's eventually return false
388
+ if i == max_retries - 1:
389
+ dump('[TEST_WARNING]', 'Method could not be tested due to a repeated Network/Availability issues', ' | ', exchange.id, method_name, args_stringified)
368
390
  else:
369
391
  # wait and retry again
370
392
  exchange.sleep(i * 1000) # increase wait seconds on every retry
371
393
  continue
372
394
  elif isinstance(e, OnMaintenance):
373
- # in case of maintenance, skip exchange(don't fail the test)
395
+ # in case of maintenance, skip exchange (don't fail the test)
374
396
  dump('[TEST_WARNING] Exchange is on maintenance', exchange.id)
375
- # If public test faces authentication error, we don't break(see comments under `testSafe` method)
376
- elif isPublic and isAuthError:
377
- # in case of loadMarkets, it means that "tester"(developer or travis) does not have correct authentication, so it does not have a point to proceed at all
378
- if methodName == 'loadMarkets':
379
- dump('[TEST_WARNING]', 'Exchange can not be tested, because of authentication problems during loadMarkets', exception_message(e), exchange.id, methodName, argsStringified)
397
+ elif is_public and is_auth_error:
398
+ # in case of loadMarkets, it means that "tester" (developer or travis) does not have correct authentication, so it does not have a point to proceed at all
399
+ if method_name == 'loadMarkets':
400
+ dump('[TEST_WARNING]', 'Exchange can not be tested, because of authentication problems during loadMarkets', exception_message(e), exchange.id, method_name, args_stringified)
380
401
  if self.info:
381
- dump('[TEST_WARNING]', 'Authentication problem for public method', exception_message(e), exchange.id, methodName, argsStringified)
402
+ dump('[TEST_WARNING]', 'Authentication problem for public method', exception_message(e), exchange.id, method_name, args_stringified)
382
403
  else:
383
- # if not a temporary connectivity issue, then mark test(no need to re-try)
384
- if isNotSupported:
385
- dump('[NOT_SUPPORTED]', exchange.id, methodName, argsStringified)
386
- return True # why consider not supported failed test?
404
+ # if not a temporary connectivity issue, then mark test as failed (no need to re-try)
405
+ if is_not_supported:
406
+ dump('[NOT_SUPPORTED]', exchange.id, method_name, args_stringified)
407
+ return True # why consider not supported as a failed test?
387
408
  else:
388
- dump('[TEST_FAILURE]', exception_message(e), exchange.id, methodName, argsStringified)
409
+ dump('[TEST_FAILURE]', exception_message(e), exchange.id, method_name, args_stringified)
389
410
  return False
390
411
 
391
412
  def run_public_tests(self, exchange, symbol):
@@ -403,8 +424,8 @@ class testMainClass(baseMainTestClass):
403
424
  'fetchTime': [],
404
425
  }
405
426
  market = exchange.market(symbol)
406
- isSpot = market['spot']
407
- if isSpot:
427
+ is_spot = market['spot']
428
+ if is_spot:
408
429
  tests['fetchCurrencies'] = []
409
430
  else:
410
431
  tests['fetchFundingRates'] = [symbol]
@@ -413,76 +434,54 @@ class testMainClass(baseMainTestClass):
413
434
  tests['fetchIndexOHLCV'] = [symbol]
414
435
  tests['fetchMarkOHLCV'] = [symbol]
415
436
  tests['fetchPremiumIndexOHLCV'] = [symbol]
416
- self.publicTests = tests
417
- testNames = list(tests.keys())
437
+ self.public_tests = tests
438
+ test_names = list(tests.keys())
418
439
  promises = []
419
- for i in range(0, len(testNames)):
420
- testName = testNames[i]
421
- testArgs = tests[testName]
422
- promises.append(self.test_safe(testName, exchange, testArgs, True))
440
+ for i in range(0, len(test_names)):
441
+ test_name = test_names[i]
442
+ test_args = tests[test_name]
443
+ promises.append(self.test_safe(test_name, exchange, test_args, True))
423
444
  # todo - not yet ready in other langs too
424
- # promises.append(testThrottle())
445
+ # promises.push (testThrottle ());
425
446
  results = (promises)
426
447
  # now count which test-methods retuned `false` from "testSafe" and dump that info below
427
448
  if self.info:
428
449
  errors = []
429
- for i in range(0, len(testNames)):
450
+ for i in range(0, len(test_names)):
430
451
  if not results[i]:
431
- errors.append(testNames[i])
432
- # we don't raise exception for public-tests, see comments under 'testSafe' method
433
- errorsInMessage = ''
452
+ errors.append(test_names[i])
453
+ # we don't throw exception for public-tests, see comments under 'testSafe' method
454
+ errors_in_message = ''
434
455
  if errors:
435
- failedMsg = ', '.join(errors)
436
- errorsInMessage = ' | Failed methods : ' + failedMsg
437
- messageContent = '[INFO:PUBLIC_TESTS_END] ' + market['type'] + errorsInMessage
438
- messageWithPadding = self.add_padding(messageContent, 25)
439
- dump(messageWithPadding, exchange.id)
456
+ failed_msg = ', '.join(errors)
457
+ errors_in_message = ' | Failed methods : ' + failed_msg
458
+ message_content = '[INFO:PUBLIC_TESTS_END] ' + market['type'] + errors_in_message
459
+ message_with_padding = self.add_padding(message_content, 25)
460
+ dump(message_with_padding, exchange.id)
440
461
 
441
462
  def load_exchange(self, exchange):
442
463
  result = self.test_safe('loadMarkets', exchange, [], True)
443
464
  if not result:
444
465
  return False
445
- symbols = [
446
- 'BTC/CNY',
447
- 'BTC/USD',
448
- 'BTC/USDT',
449
- 'BTC/EUR',
450
- 'BTC/ETH',
451
- 'ETH/BTC',
452
- 'BTC/JPY',
453
- 'ETH/EUR',
454
- 'ETH/JPY',
455
- 'ETH/CNY',
456
- 'ETH/USD',
457
- 'LTC/CNY',
458
- 'DASH/BTC',
459
- 'DOGE/BTC',
460
- 'BTC/AUD',
461
- 'BTC/PLN',
462
- 'USD/SLL',
463
- 'BTC/RUB',
464
- 'BTC/UAH',
465
- 'LTC/BTC',
466
- 'EUR/USD',
467
- ]
468
- resultSymbols = []
469
- exchangeSpecificSymbols = exchange.symbols
470
- for i in range(0, len(exchangeSpecificSymbols)):
471
- symbol = exchangeSpecificSymbols[i]
466
+ symbols = ['BTC/CNY', 'BTC/USD', 'BTC/USDT', 'BTC/EUR', 'BTC/ETH', 'ETH/BTC', 'BTC/JPY', 'ETH/EUR', 'ETH/JPY', 'ETH/CNY', 'ETH/USD', 'LTC/CNY', 'DASH/BTC', 'DOGE/BTC', 'BTC/AUD', 'BTC/PLN', 'USD/SLL', 'BTC/RUB', 'BTC/UAH', 'LTC/BTC', 'EUR/USD']
467
+ result_symbols = []
468
+ exchange_specific_symbols = exchange.symbols
469
+ for i in range(0, len(exchange_specific_symbols)):
470
+ symbol = exchange_specific_symbols[i]
472
471
  if exchange.in_array(symbol, symbols):
473
- resultSymbols.append(symbol)
474
- resultMsg = ''
475
- resultLength = len(resultSymbols)
476
- exchangeSymbolsLength = len(exchange.symbols)
477
- if resultLength > 0:
478
- if exchangeSymbolsLength > resultLength:
479
- resultMsg = ', '.join(resultSymbols) + ' + more...'
472
+ result_symbols.append(symbol)
473
+ result_msg = ''
474
+ result_length = len(result_symbols)
475
+ exchange_symbols_length = len(exchange.symbols)
476
+ if result_length > 0:
477
+ if exchange_symbols_length > result_length:
478
+ result_msg = ', '.join(result_symbols) + ' + more...'
480
479
  else:
481
- resultMsg = ', '.join(resultSymbols)
482
- dump('Exchange loaded', exchangeSymbolsLength, 'symbols', resultMsg)
480
+ result_msg = ', '.join(result_symbols)
481
+ dump('Exchange loaded', exchange_symbols_length, 'symbols', result_msg)
483
482
  return True
484
483
 
485
- def get_test_symbol(self, exchange, isSpot, symbols):
484
+ def get_test_symbol(self, exchange, is_spot, symbols):
486
485
  symbol = None
487
486
  for i in range(0, len(symbols)):
488
487
  s = symbols[i]
@@ -517,140 +516,86 @@ class testMainClass(baseMainTestClass):
517
516
  return res
518
517
 
519
518
  def get_valid_symbol(self, exchange, spot=True):
520
- currentTypeMarkets = self.get_markets_from_exchange(exchange, spot)
521
- codes = [
522
- 'BTC',
523
- 'ETH',
524
- 'XRP',
525
- 'LTC',
526
- 'BCH',
527
- 'EOS',
528
- 'BNB',
529
- 'BSV',
530
- 'USDT',
531
- 'ATOM',
532
- 'BAT',
533
- 'BTG',
534
- 'DASH',
535
- 'DOGE',
536
- 'ETC',
537
- 'IOTA',
538
- 'LSK',
539
- 'MKR',
540
- 'NEO',
541
- 'PAX',
542
- 'QTUM',
543
- 'TRX',
544
- 'TUSD',
545
- 'USD',
546
- 'USDC',
547
- 'WAVES',
548
- 'XEM',
549
- 'XMR',
550
- 'ZEC',
551
- 'ZRX',
552
- ]
553
- spotSymbols = [
554
- 'BTC/USD',
555
- 'BTC/USDT',
556
- 'BTC/CNY',
557
- 'BTC/EUR',
558
- 'BTC/ETH',
559
- 'ETH/BTC',
560
- 'ETH/USD',
561
- 'ETH/USDT',
562
- 'BTC/JPY',
563
- 'LTC/BTC',
564
- 'ZRX/WETH',
565
- 'EUR/USD',
566
- ]
567
- swapSymbols = [
568
- 'BTC/USDT:USDT',
569
- 'BTC/USD:USD',
570
- 'ETH/USDT:USDT',
571
- 'ETH/USD:USD',
572
- 'LTC/USDT:USDT',
573
- 'DOGE/USDT:USDT',
574
- 'ADA/USDT:USDT',
575
- 'BTC/USD:BTC',
576
- 'ETH/USD:ETH',
577
- ]
578
- targetSymbols = spotSymbols if spot else swapSymbols
579
- symbol = self.get_test_symbol(exchange, spot, targetSymbols)
519
+ current_type_markets = self.get_markets_from_exchange(exchange, spot)
520
+ codes = ['BTC', 'ETH', 'XRP', 'LTC', 'BCH', 'EOS', 'BNB', 'BSV', 'USDT', 'ATOM', 'BAT', 'BTG', 'DASH', 'DOGE', 'ETC', 'IOTA', 'LSK', 'MKR', 'NEO', 'PAX', 'QTUM', 'TRX', 'TUSD', 'USD', 'USDC', 'WAVES', 'XEM', 'XMR', 'ZEC', 'ZRX']
521
+ spot_symbols = ['BTC/USD', 'BTC/USDT', 'BTC/CNY', 'BTC/EUR', 'BTC/ETH', 'ETH/BTC', 'ETH/USD', 'ETH/USDT', 'BTC/JPY', 'LTC/BTC', 'ZRX/WETH', 'EUR/USD']
522
+ swap_symbols = ['BTC/USDT:USDT', 'BTC/USD:USD', 'ETH/USDT:USDT', 'ETH/USD:USD', 'LTC/USDT:USDT', 'DOGE/USDT:USDT', 'ADA/USDT:USDT', 'BTC/USD:BTC', 'ETH/USD:ETH']
523
+ target_symbols = spot_symbols if spot else swap_symbols
524
+ symbol = self.get_test_symbol(exchange, spot, target_symbols)
580
525
  # if symbols wasn't found from above hardcoded list, then try to locate any symbol which has our target hardcoded 'base' code
581
526
  if symbol is None:
582
527
  for i in range(0, len(codes)):
583
- currentCode = codes[i]
584
- marketsArrayForCurrentCode = exchange.filter_by(currentTypeMarkets, 'base', currentCode)
585
- indexedMkts = exchange.index_by(marketsArrayForCurrentCode, 'symbol')
586
- symbolsArrayForCurrentCode = list(indexedMkts.keys())
587
- symbolsLength = len(symbolsArrayForCurrentCode)
588
- if symbolsLength:
589
- symbol = self.get_test_symbol(exchange, spot, symbolsArrayForCurrentCode)
528
+ current_code = codes[i]
529
+ markets_array_for_current_code = exchange.filter_by(current_type_markets, 'base', current_code)
530
+ indexed_mkts = exchange.index_by(markets_array_for_current_code, 'symbol')
531
+ symbols_array_for_current_code = list(indexed_mkts.keys())
532
+ symbols_length = len(symbols_array_for_current_code)
533
+ if symbols_length:
534
+ symbol = self.get_test_symbol(exchange, spot, symbols_array_for_current_code)
590
535
  break
591
536
  # if there wasn't found any symbol with our hardcoded 'base' code, then just try to find symbols that are 'active'
592
537
  if symbol is None:
593
- activeMarkets = exchange.filter_by(currentTypeMarkets, 'active', True)
594
- activeSymbols = []
595
- for i in range(0, len(activeMarkets)):
596
- activeSymbols.append(activeMarkets[i]['symbol'])
597
- symbol = self.get_test_symbol(exchange, spot, activeSymbols)
538
+ active_markets = exchange.filter_by(current_type_markets, 'active', True)
539
+ active_symbols = []
540
+ for i in range(0, len(active_markets)):
541
+ active_symbols.append(active_markets[i]['symbol'])
542
+ symbol = self.get_test_symbol(exchange, spot, active_symbols)
598
543
  if symbol is None:
599
- values = list(currentTypeMarkets.values())
600
- valuesLength = len(values)
601
- if valuesLength > 0:
544
+ values = list(current_type_markets.values())
545
+ values_length = len(values)
546
+ if values_length > 0:
602
547
  first = values[0]
603
548
  if first is not None:
604
549
  symbol = first['symbol']
605
550
  return symbol
606
551
 
607
- def test_exchange(self, exchange, providedSymbol=None):
608
- spotSymbol = None
609
- swapSymbol = None
610
- if providedSymbol is not None:
611
- market = exchange.market(providedSymbol)
552
+ def test_exchange(self, exchange, provided_symbol=None):
553
+ spot_symbol = None
554
+ swap_symbol = None
555
+ if provided_symbol is not None:
556
+ market = exchange.market(provided_symbol)
612
557
  if market['spot']:
613
- spotSymbol = providedSymbol
558
+ spot_symbol = provided_symbol
614
559
  else:
615
- swapSymbol = providedSymbol
560
+ swap_symbol = provided_symbol
616
561
  else:
617
562
  if exchange.has['spot']:
618
- spotSymbol = self.get_valid_symbol(exchange, True)
563
+ spot_symbol = self.get_valid_symbol(exchange, True)
619
564
  if exchange.has['swap']:
620
- swapSymbol = self.get_valid_symbol(exchange, False)
621
- if spotSymbol is not None:
622
- dump('Selected SPOT SYMBOL:', spotSymbol)
623
- if swapSymbol is not None:
624
- dump('Selected SWAP SYMBOL:', swapSymbol)
625
- if not self.privateTestOnly:
626
- if exchange.has['spot'] and spotSymbol is not None:
565
+ swap_symbol = self.get_valid_symbol(exchange, False)
566
+ if spot_symbol is not None:
567
+ dump('Selected SPOT SYMBOL:', spot_symbol)
568
+ if swap_symbol is not None:
569
+ dump('Selected SWAP SYMBOL:', swap_symbol)
570
+ if not self.private_test_only:
571
+ if exchange.has['spot'] and spot_symbol is not None:
627
572
  if self.info:
628
573
  dump('[INFO:SPOT TESTS]')
629
574
  exchange.options['type'] = 'spot'
630
- self.run_public_tests(exchange, spotSymbol)
631
- if exchange.has['swap'] and swapSymbol is not None:
575
+ self.run_public_tests(exchange, spot_symbol)
576
+ if exchange.has['swap'] and swap_symbol is not None:
632
577
  if self.info:
633
578
  dump('[INFO:SWAP TESTS]')
634
579
  exchange.options['type'] = 'swap'
635
- self.run_public_tests(exchange, swapSymbol)
636
- if self.privateTest or self.privateTestOnly:
637
- if exchange.has['spot'] and spotSymbol is not None:
580
+ self.run_public_tests(exchange, swap_symbol)
581
+ if self.private_test or self.private_test_only:
582
+ if exchange.has['spot'] and spot_symbol is not None:
638
583
  exchange.options['defaultType'] = 'spot'
639
- self.run_private_tests(exchange, spotSymbol)
640
- if exchange.has['swap'] and swapSymbol is not None:
584
+ self.run_private_tests(exchange, spot_symbol)
585
+ if exchange.has['swap'] and swap_symbol is not None:
641
586
  exchange.options['defaultType'] = 'swap'
642
- self.run_private_tests(exchange, swapSymbol)
587
+ self.run_private_tests(exchange, swap_symbol)
643
588
 
644
589
  def run_private_tests(self, exchange, symbol):
645
590
  if not exchange.check_required_credentials(False):
646
591
  dump('[Skipping private tests]', 'Keys not found')
647
592
  return
648
593
  code = self.get_exchange_code(exchange)
649
- # if exchange.extendedTest:
650
- # test('InvalidNonce', exchange, symbol)
651
- # test('OrderNotFound', exchange, symbol)
652
- # test('InvalidOrder', exchange, symbol)
653
- # test('InsufficientFunds', exchange, symbol, balance) # danger zone - won't execute with non-empty balance
594
+ # if (exchange.extendedTest) {
595
+ # test ('InvalidNonce', exchange, symbol);
596
+ # test ('OrderNotFound', exchange, symbol);
597
+ # test ('InvalidOrder', exchange, symbol);
598
+ # test ('InsufficientFunds', exchange, symbol, balance); # danger zone - won't execute with non-empty balance
654
599
  # }
655
600
  tests = {
656
601
  'signIn': [],
@@ -671,40 +616,25 @@ class testMainClass(baseMainTestClass):
671
616
  'fetchBorrowRates': [],
672
617
  'fetchBorrowRate': [code],
673
618
  'fetchBorrowInterest': [code, symbol],
674
- # 'addMargin': [],
675
- # 'reduceMargin': [],
676
- # 'setMargin': [],
677
- # 'setMarginMode': [],
678
- # 'setLeverage': [],
679
619
  'cancelAllOrders': [symbol],
680
- # 'cancelOrder': [],
681
- # 'cancelOrders': [],
682
620
  'fetchCanceledOrders': [symbol],
683
- # 'fetchClosedOrder': [],
684
- # 'fetchOpenOrder': [],
685
- # 'fetchOrder': [],
686
- # 'fetchOrderTrades': [],
687
621
  'fetchPosition': [symbol],
688
622
  'fetchDeposit': [code],
689
623
  'createDepositAddress': [code],
690
624
  'fetchDepositAddress': [code],
691
625
  'fetchDepositAddresses': [code],
692
626
  'fetchDepositAddressesByNetwork': [code],
693
- # 'editOrder': [],
694
627
  'fetchBorrowRateHistory': [code],
695
628
  'fetchBorrowRatesPerSymbol': [],
696
629
  'fetchLedgerEntry': [code],
697
- # 'fetchWithdrawal': [],
698
- # 'transfer': [],
699
- # 'withdraw': [],
700
630
  }
701
631
  market = exchange.market(symbol)
702
- isSpot = market['spot']
703
- if isSpot:
632
+ is_spot = market['spot']
633
+ if is_spot:
704
634
  tests['fetchCurrencies'] = []
705
635
  else:
706
636
  # derivatives only
707
- tests['fetchPositions'] = [symbol] # self test fetches all positions for 1 symbol
637
+ tests['fetchPositions'] = [symbol] # this test fetches all positions for 1 symbol
708
638
  tests['fetchPosition'] = [symbol]
709
639
  tests['fetchPositionRisk'] = [symbol]
710
640
  tests['setPositionMode'] = [symbol]
@@ -712,23 +642,23 @@ class testMainClass(baseMainTestClass):
712
642
  tests['fetchOpenInterestHistory'] = [symbol]
713
643
  tests['fetchFundingRateHistory'] = [symbol]
714
644
  tests['fetchFundingHistory'] = [symbol]
715
- combinedPublicPrivateTests = exchange.deep_extend(self.publicTests, tests)
716
- testNames = list(combinedPublicPrivateTests.keys())
645
+ combined_public_private_tests = exchange.deep_extend(self.public_tests, tests)
646
+ test_names = list(combined_public_private_tests.keys())
717
647
  promises = []
718
- for i in range(0, len(testNames)):
719
- testName = testNames[i]
720
- testArgs = combinedPublicPrivateTests[testName]
721
- promises.append(self.test_safe(testName, exchange, testArgs, False))
648
+ for i in range(0, len(test_names)):
649
+ test_name = test_names[i]
650
+ test_args = combined_public_private_tests[test_name]
651
+ promises.append(self.test_safe(test_name, exchange, test_args, False))
722
652
  results = (promises)
723
653
  errors = []
724
- for i in range(0, len(testNames)):
725
- testName = testNames[i]
654
+ for i in range(0, len(test_names)):
655
+ test_name = test_names[i]
726
656
  success = results[i]
727
657
  if not success:
728
- errors.append(testName)
729
- errorsCnt = len(errors) # PHP transpile count($errors)
730
- if errorsCnt > 0:
731
- # raise Error('Failed private tests [' + market['type'] + ']: ' + ', '.join(errors))
658
+ errors.append(test_name)
659
+ errors_cnt = len(errors) # PHP transpile count($errors)
660
+ if errors_cnt > 0:
661
+ # throw new Error ('Failed private tests [' + market['type'] + ']: ' + errors.join (', '));
732
662
  dump('[TEST_FAILURE]', 'Failed private tests [' + market['type'] + ']: ' + ', '.join(errors))
733
663
  else:
734
664
  if self.info:
@@ -740,7 +670,6 @@ class testMainClass(baseMainTestClass):
740
670
  return
741
671
  if self.sandbox or get_exchange_prop(exchange, 'sandbox'):
742
672
  exchange.set_sandbox_mode(True)
743
- # because of python-async, we need proper `.close()` handling
744
673
  try:
745
674
  result = self.load_exchange(exchange)
746
675
  if not result:
@@ -752,296 +681,373 @@ class testMainClass(baseMainTestClass):
752
681
  close(exchange)
753
682
  raise e
754
683
 
755
- def assert_static_error(self, cond: bool, message: str, calculatedOutput, storedOutput):
684
+ def assert_static_error(self, cond, message, calculated_output, stored_output):
756
685
  # -----------------------------------------------------------------------------
757
686
  # --- Init of static tests functions------------------------------------------
758
687
  # -----------------------------------------------------------------------------
759
- calculatedString = json_stringify(calculatedOutput)
760
- outputString = json_stringify(storedOutput)
761
- errorMessage = message + ' expected ' + outputString + ' received: ' + calculatedString
762
- assert cond, errorMessage
688
+ calculated_string = json_stringify(calculated_output)
689
+ output_string = json_stringify(stored_output)
690
+ error_message = message + ' expected ' + output_string + ' received: ' + calculated_string
691
+ assert cond, error_message
763
692
 
764
- def load_markets_from_file(self, id: str):
693
+ def load_markets_from_file(self, id):
765
694
  # load markets from file
766
- # to make self test
695
+ # to make this test as fast as possible
767
696
  # and basically independent from the exchange
768
697
  # so we can run it offline
769
- filename = rootDir + './ts/src/test/static/markets/' + id + '.json'
698
+ filename = self.root_dir + './ts/src/test/static/markets/' + id + '.json'
699
+ content = io_file_read(filename)
700
+ return content
701
+
702
+ def load_currencies_from_file(self, id):
703
+ filename = self.root_dir + './ts/src/test/static/currencies/' + id + '.json'
770
704
  content = io_file_read(filename)
771
705
  return content
772
706
 
773
- def load_static_data(self, targetExchange: str = None):
774
- folder = rootDir + './ts/src/test/static/data/'
707
+ def load_static_data(self, folder, target_exchange=None):
775
708
  result = {}
776
- if targetExchange:
709
+ if target_exchange:
777
710
  # read a single exchange
778
- result[targetExchange] = io_file_read(folder + targetExchange + '.json')
711
+ result[target_exchange] = io_file_read(folder + target_exchange + '.json')
779
712
  return result
780
713
  files = io_dir_read(folder)
781
714
  for i in range(0, len(files)):
782
715
  file = files[i]
783
- exchangeName = file.replace('.json', '')
716
+ exchange_name = file.replace('.json', '')
784
717
  content = io_file_read(folder + file)
785
- result[exchangeName] = content
718
+ result[exchange_name] = content
786
719
  return result
787
720
 
788
- def remove_hostnamefrom_url(self, url: str):
721
+ def remove_hostnamefrom_url(self, url):
789
722
  if url is None:
790
723
  return None
791
- urlParts = url.split('/')
724
+ url_parts = url.split('/')
792
725
  res = ''
793
- for i in range(0, len(urlParts)):
726
+ for i in range(0, len(url_parts)):
794
727
  if i > 2:
795
- current = urlParts[i]
728
+ current = url_parts[i]
796
729
  if current.find('?') > -1:
797
- # handle urls like self: /v1/account/accounts?AccessK
798
- currentParts = current.split('?')
730
+ # handle urls like this: /v1/account/accounts?AccessK
731
+ current_parts = current.split('?')
799
732
  res += '/'
800
- res += currentParts[0]
733
+ res += current_parts[0]
801
734
  break
802
735
  res += '/'
803
736
  res += current
804
737
  return res
805
738
 
806
- def urlencoded_to_dict(self, url: str):
739
+ def urlencoded_to_dict(self, url):
807
740
  result = {}
808
741
  parts = url.split('&')
809
742
  for i in range(0, len(parts)):
810
743
  part = parts[i]
811
- keyValue = part.split('=')
812
- keysLength = len(keyValue)
813
- if keysLength != 2:
744
+ key_value = part.split('=')
745
+ keys_length = len(key_value)
746
+ if keys_length != 2:
814
747
  continue
815
- key = keyValue[0]
816
- value = keyValue[1]
748
+ key = key_value[0]
749
+ value = key_value[1]
817
750
  if (value is not None) and ((value.startswith('[')) or (value.startswith('{'))):
818
- # some exchanges might return something like self: timestamp=1699382693405&batchOrders=[{\"symbol\":\"LTCUSDT\",\"side\":\"BUY\",\"newClientOrderI
751
+ # some exchanges might return something like this: timestamp=1699382693405&batchOrders=[{\"symbol\":\"LTCUSDT\",\"side\":\"BUY\",\"newClientOrderI
819
752
  value = json_parse(value)
820
753
  result[key] = value
821
754
  return result
822
755
 
823
- def assert_new_and_stored_output(self, exchange, skipKeys: List[str], newOutput, storedOutput):
824
- if (isinstance(storedOutput, dict)) and (isinstance(newOutput, dict)):
825
- storedOutputKeys = list(storedOutput.keys())
826
- newOutputKeys = list(newOutput.keys())
827
- storedKeysLength = len(storedOutputKeys)
828
- newKeysLength = len(newOutputKeys)
829
- self.assert_static_error(storedKeysLength == newKeysLength, 'output length mismatch', storedOutput, newOutput)
756
+ def assert_new_and_stored_output(self, exchange, skip_keys, new_output, stored_output, strict_type_check=True):
757
+ if is_null_value(new_output) and is_null_value(stored_output):
758
+ return
759
+ if not new_output and not stored_output:
760
+ return
761
+ if (isinstance(stored_output, dict)) and (isinstance(new_output, dict)):
762
+ stored_output_keys = list(stored_output.keys())
763
+ new_output_keys = list(new_output.keys())
764
+ stored_keys_length = len(stored_output_keys)
765
+ new_keys_length = len(new_output_keys)
766
+ self.assert_static_error(stored_keys_length == new_keys_length, 'output length mismatch', stored_output, new_output)
830
767
  # iterate over the keys
831
- for i in range(0, len(storedOutputKeys)):
832
- key = storedOutputKeys[i]
833
- if exchange.in_array(key, skipKeys):
768
+ for i in range(0, len(stored_output_keys)):
769
+ key = stored_output_keys[i]
770
+ if exchange.in_array(key, skip_keys):
834
771
  continue
835
- if not (exchange.in_array(key, newOutputKeys)):
836
- self.assert_static_error(False, 'output key missing: ' + key, storedOutput, newOutput)
837
- storedValue = storedOutput[key]
838
- newValue = newOutput[key]
839
- self.assert_new_and_stored_output(exchange, skipKeys, newValue, storedValue)
840
- elif isinstance(storedOutput, list) and (isinstance(newOutput, list)):
841
- storedArrayLength = len(storedOutput)
842
- newArrayLength = len(newOutput)
843
- self.assert_static_error(storedArrayLength == newArrayLength, 'output length mismatch', storedOutput, newOutput)
844
- for i in range(0, len(storedOutput)):
845
- storedItem = storedOutput[i]
846
- newItem = newOutput[i]
847
- self.assert_new_and_stored_output(exchange, skipKeys, newItem, storedItem)
772
+ if not (exchange.in_array(key, new_output_keys)):
773
+ self.assert_static_error(False, 'output key missing: ' + key, stored_output, new_output)
774
+ stored_value = stored_output[key]
775
+ new_value = new_output[key]
776
+ self.assert_new_and_stored_output(exchange, skip_keys, new_value, stored_value, strict_type_check)
777
+ elif isinstance(stored_output, list) and (isinstance(new_output, list)):
778
+ stored_array_length = len(stored_output)
779
+ new_array_length = len(new_output)
780
+ self.assert_static_error(stored_array_length == new_array_length, 'output length mismatch', stored_output, new_output)
781
+ for i in range(0, len(stored_output)):
782
+ stored_item = stored_output[i]
783
+ new_item = new_output[i]
784
+ self.assert_new_and_stored_output(exchange, skip_keys, new_item, stored_item, strict_type_check)
848
785
  else:
849
786
  # built-in types like strings, numbers, booleans
850
- messageError = 'output value mismatch:' + str(newOutput) + ' != ' + str(storedOutput)
851
- self.assert_static_error(newOutput == storedOutput, messageError, storedOutput, newOutput)
787
+ sanitized_new_output = None if (not new_output) else new_output # we store undefined as nulls in the json file so we need to convert it back
788
+ sanitized_stored_output = None if (not stored_output) else stored_output
789
+ new_output_string = str(sanitized_new_output) if sanitized_new_output else 'undefined'
790
+ stored_output_string = str(sanitized_stored_output) if sanitized_stored_output else 'undefined'
791
+ message_error = 'output value mismatch:' + new_output_string + ' != ' + stored_output_string
792
+ if strict_type_check:
793
+ # upon building the request we want strict type check to make sure all the types are correct
794
+ # when comparing the response we want to allow some flexibility, because a 50.0 can be equal to 50 after saving it to the json file
795
+ self.assert_static_error(sanitized_new_output == sanitized_stored_output, message_error, stored_output, new_output)
796
+ else:
797
+ is_boolean = (isinstance(sanitized_new_output, bool)) or (isinstance(sanitized_stored_output, bool))
798
+ is_string = (isinstance(sanitized_new_output, str)) or (isinstance(sanitized_stored_output, str))
799
+ is_undefined = (sanitized_new_output is None) or (sanitized_stored_output is None) # undefined is a perfetly valid value
800
+ if is_boolean or is_string or is_undefined:
801
+ self.assert_static_error(new_output_string == stored_output_string, message_error, stored_output, new_output)
802
+ else:
803
+ numeric_new_output = exchange.parse_to_numeric(new_output_string)
804
+ numeric_stored_output = exchange.parse_to_numeric(stored_output_string)
805
+ self.assert_static_error(numeric_new_output == numeric_stored_output, message_error, stored_output, new_output)
852
806
 
853
- def assert_static_output(self, exchange, type: str, skipKeys: List[str], storedUrl: str, requestUrl: str, storedOutput, newOutput):
854
- if storedUrl != requestUrl:
807
+ def assert_static_request_output(self, exchange, type, skip_keys, stored_url, request_url, stored_output, new_output):
808
+ if stored_url != request_url:
855
809
  # remove the host part from the url
856
- firstPath = self.remove_hostnamefrom_url(storedUrl)
857
- secondPath = self.remove_hostnamefrom_url(requestUrl)
858
- self.assert_static_error(firstPath == secondPath, 'url mismatch', firstPath, secondPath)
859
- # body(aka storedOutput and newOutput) is not defined and information is in the url
810
+ first_path = self.remove_hostnamefrom_url(stored_url)
811
+ second_path = self.remove_hostnamefrom_url(request_url)
812
+ self.assert_static_error(first_path == second_path, 'url mismatch', first_path, second_path)
813
+ # body (aka storedOutput and newOutput) is not defined and information is in the url
860
814
  # example: "https://open-api.bingx.com/openApi/spot/v1/trade/order?quoteOrderQty=5&side=BUY&symbol=LTC-USDT&timestamp=1698777135343&type=MARKET&signature=d55a7e4f7f9dbe56c4004c9f3ab340869d3cb004e2f0b5b861e5fbd1762fd9a0
861
- if (storedOutput is None) and (newOutput is None):
862
- if (storedUrl is not None) and (requestUrl is not None):
863
- storedUrlParts = storedUrl.split('?')
864
- newUrlParts = requestUrl.split('?')
865
- storedUrlQuery = exchange.safe_value(storedUrlParts, 1)
866
- newUrlQuery = exchange.safe_value(newUrlParts, 1)
867
- if (storedUrlQuery is None) and (newUrlQuery is None):
815
+ if (stored_output is None) and (new_output is None):
816
+ if (stored_url is not None) and (request_url is not None):
817
+ stored_url_parts = stored_url.split('?')
818
+ new_url_parts = request_url.split('?')
819
+ stored_url_query = exchange.safe_value(stored_url_parts, 1)
820
+ new_url_query = exchange.safe_value(new_url_parts, 1)
821
+ if (stored_url_query is None) and (new_url_query is None):
868
822
  # might be a get request without any query parameters
869
823
  # example: https://api.gateio.ws/api/v4/delivery/usdt/positions
870
824
  return
871
- storedUrlParams = self.urlencoded_to_dict(storedUrlQuery)
872
- newUrlParams = self.urlencoded_to_dict(newUrlQuery)
873
- self.assert_new_and_stored_output(exchange, skipKeys, newUrlParams, storedUrlParams)
825
+ stored_url_params = self.urlencoded_to_dict(stored_url_query)
826
+ new_url_params = self.urlencoded_to_dict(new_url_query)
827
+ self.assert_new_and_stored_output(exchange, skip_keys, new_url_params, stored_url_params)
874
828
  return
875
- # body is defined
876
829
  if type == 'json':
877
- if isinstance(storedOutput, str):
878
- storedOutput = json_parse(storedOutput)
879
- if isinstance(newOutput, str):
880
- newOutput = json_parse(newOutput)
830
+ if isinstance(stored_output, str):
831
+ stored_output = json_parse(stored_output)
832
+ if isinstance(new_output, str):
833
+ new_output = json_parse(new_output)
881
834
  elif type == 'urlencoded':
882
- storedOutput = self.urlencoded_to_dict(storedOutput)
883
- newOutput = self.urlencoded_to_dict(newOutput)
884
- self.assert_new_and_stored_output(exchange, skipKeys, newOutput, storedOutput)
835
+ stored_output = self.urlencoded_to_dict(stored_output)
836
+ new_output = self.urlencoded_to_dict(new_output)
837
+ self.assert_new_and_stored_output(exchange, skip_keys, new_output, stored_output)
838
+
839
+ def assert_static_response_output(self, exchange, skip_keys, computed_result, stored_result):
840
+ self.assert_new_and_stored_output(exchange, skip_keys, computed_result, stored_result, False)
885
841
 
886
842
  def sanitize_data_input(self, input):
887
843
  # remove nulls and replace with unefined instead
888
844
  if input is None:
889
845
  return None
890
- newInput = []
846
+ new_input = []
891
847
  for i in range(0, len(input)):
892
848
  current = input[i]
893
- if current == None: # noqa: E711
894
- newInput.append(None)
849
+ if is_null_value(current):
850
+ new_input.append(None)
895
851
  else:
896
- newInput.append(current)
897
- return newInput
852
+ new_input.append(current)
853
+ return new_input
898
854
 
899
- def test_method_statically(self, exchange, method: str, data: object, type: str, skipKeys: List[str]):
855
+ def test_method_statically(self, exchange, method, data, type, skip_keys):
900
856
  output = None
901
- requestUrl = None
857
+ request_url = None
902
858
  try:
903
859
  call_exchange_method_dynamically(exchange, method, self.sanitize_data_input(data['input']))
904
860
  except Exception as e:
905
861
  if not (isinstance(e, NetworkError)):
906
- # if it's not a network error, it means our request was not created succesfully
907
- # so we might have an error in the request creation
908
862
  raise e
909
863
  output = exchange.last_request_body
910
- requestUrl = exchange.last_request_url
864
+ request_url = exchange.last_request_url
911
865
  try:
912
- callOutput = exchange.safe_value(data, 'output')
913
- self.assert_static_output(exchange, type, skipKeys, data['url'], requestUrl, callOutput, output)
866
+ call_output = exchange.safe_value(data, 'output')
867
+ self.assert_static_request_output(exchange, type, skip_keys, data['url'], request_url, call_output, output)
914
868
  except Exception as e:
915
- self.staticTestsFailed = True
916
- errorMessage = '[' + self.lang + '][STATIC_TEST_FAILURE]' + '[' + exchange.id + ']' + '[' + method + ']' + '[' + data['description'] + ']' + str(e)
917
- dump(errorMessage)
918
-
919
- def init_offline_exchange(self, exchangeName: str):
920
- markets = self.load_markets_from_file(exchangeName)
921
- return init_exchange(exchangeName, {'markets': markets, 'rateLimit': 1, 'httpsProxy': 'http://fake:8080', 'apiKey': 'key', 'secret': 'secretsecret', 'password': 'password', 'uid': 'uid', 'accounts': [{'id': 'myAccount'}], 'options': {'enableUnifiedAccount': True, 'enableUnifiedMargin': False, 'accessToken': 'token', 'expires': 999999999999999, 'leverageBrackets': {}}})
869
+ self.request_tests_failed = True
870
+ error_message = '[' + self.lang + '][STATIC_REQUEST_TEST_FAILURE]' + '[' + exchange.id + ']' + '[' + method + ']' + '[' + data['description'] + ']' + str(e)
871
+ dump(error_message)
922
872
 
923
- def test_exchange_statically(self, exchangeName: str, exchangeData: object, testName: str = None):
873
+ def test_response_statically(self, exchange, method, skip_keys, data):
874
+ expected_result = exchange.safe_value(data, 'parsedResponse')
875
+ mocked_exchange = set_fetch_response(exchange, data['httpResponse'])
876
+ try:
877
+ unified_result = call_exchange_method_dynamically(exchange, method, self.sanitize_data_input(data['input']))
878
+ self.assert_static_response_output(mocked_exchange, skip_keys, unified_result, expected_result)
879
+ except Exception as e:
880
+ self.request_tests_failed = True
881
+ error_message = '[' + self.lang + '][STATIC_RESPONSE_TEST_FAILURE]' + '[' + exchange.id + ']' + '[' + method + ']' + '[' + data['description'] + ']' + str(e)
882
+ dump(error_message)
883
+ set_fetch_response(exchange, None) # reset state
884
+
885
+ def init_offline_exchange(self, exchange_name):
886
+ markets = self.load_markets_from_file(exchange_name)
887
+ currencies = self.load_currencies_from_file(exchange_name)
888
+ exchange = init_exchange(exchange_name, {
889
+ 'markets': markets,
890
+ 'enableRateLimit': False,
891
+ 'rateLimit': 1,
892
+ 'httpsProxy': 'http://fake:8080',
893
+ 'apiKey': 'key',
894
+ 'secret': 'secretsecret',
895
+ 'password': 'password',
896
+ 'uid': 'uid',
897
+ 'accounts': [{
898
+ 'id': 'myAccount',
899
+ }],
900
+ 'options': {
901
+ 'enableUnifiedAccount': True,
902
+ 'enableUnifiedMargin': False,
903
+ 'accessToken': 'token',
904
+ 'expires': 999999999999999,
905
+ 'leverageBrackets': {},
906
+ },
907
+ })
908
+ exchange.currencies = currencies # not working in python if assigned in the config dict
909
+ return exchange
910
+
911
+ def test_exchange_request_statically(self, exchange_name, exchange_data, test_name=None):
924
912
  # instantiate the exchange and make sure that we sink the requests to avoid an actual request
925
- exchange = self.init_offline_exchange(exchangeName)
926
- methods = exchange.safe_value(exchangeData, 'methods', {})
927
- methodsNames = list(methods.keys())
928
- for i in range(0, len(methodsNames)):
929
- method = methodsNames[i]
913
+ exchange = self.init_offline_exchange(exchange_name)
914
+ methods = exchange.safe_value(exchange_data, 'methods', {})
915
+ methods_names = list(methods.keys())
916
+ for i in range(0, len(methods_names)):
917
+ method = methods_names[i]
930
918
  results = methods[method]
931
919
  for j in range(0, len(results)):
932
920
  result = results[j]
933
921
  description = exchange.safe_value(result, 'description')
934
- if (testName is not None) and (testName != description):
922
+ if (test_name is not None) and (test_name != description):
935
923
  continue
936
- type = exchange.safe_string(exchangeData, 'outputType')
937
- skipKeys = exchange.safe_value(exchangeData, 'skipKeys', [])
938
- self.test_method_statically(exchange, method, result, type, skipKeys)
924
+ type = exchange.safe_string(exchange_data, 'outputType')
925
+ skip_keys = exchange.safe_value(exchange_data, 'skipKeys', [])
926
+ self.test_method_statically(exchange, method, result, type, skip_keys)
939
927
  close(exchange)
940
928
 
941
- def get_number_of_tests_from_exchange(self, exchange, exchangeData: object):
929
+ def test_exchange_response_statically(self, exchange_name, exchange_data, test_name=None):
930
+ exchange = self.init_offline_exchange(exchange_name)
931
+ methods = exchange.safe_value(exchange_data, 'methods', {})
932
+ options = exchange.safe_value(exchange_data, 'options', {})
933
+ exchange.options = exchange.deep_extend(exchange.options, options) # custom options to be used in the tests
934
+ methods_names = list(methods.keys())
935
+ for i in range(0, len(methods_names)):
936
+ method = methods_names[i]
937
+ results = methods[method]
938
+ for j in range(0, len(results)):
939
+ result = results[j]
940
+ description = exchange.safe_value(result, 'description')
941
+ if (test_name is not None) and (test_name != description):
942
+ continue
943
+ skip_keys = exchange.safe_value(exchange_data, 'skipKeys', [])
944
+ self.test_response_statically(exchange, method, skip_keys, result)
945
+ close(exchange)
946
+
947
+ def get_number_of_tests_from_exchange(self, exchange, exchange_data):
942
948
  sum = 0
943
- methods = exchangeData['methods']
944
- methodsNames = list(methods.keys())
945
- for i in range(0, len(methodsNames)):
946
- method = methodsNames[i]
949
+ methods = exchange_data['methods']
950
+ methods_names = list(methods.keys())
951
+ for i in range(0, len(methods_names)):
952
+ method = methods_names[i]
947
953
  results = methods[method]
948
- resultsLength = len(results)
949
- sum = exchange.sum(sum, resultsLength)
954
+ results_length = len(results)
955
+ sum = exchange.sum(sum, results_length)
950
956
  return sum
951
957
 
952
- def run_static_tests(self, targetExchange: str = None, testName: str = None):
953
- staticData = self.load_static_data(targetExchange)
954
- exchanges = list(staticData.keys())
955
- exchange = init_exchange('Exchange', {}) # tmp to do the calculations until we have the ast-transpiler transpiling self code
958
+ def run_static_request_tests(self, target_exchange=None, test_name=None):
959
+ self.run_static_tests('request', target_exchange, test_name)
960
+
961
+ def run_static_tests(self, type, target_exchange=None, test_name=None):
962
+ folder = self.root_dir + './ts/src/test/static/' + type + '/'
963
+ static_data = self.load_static_data(folder, target_exchange)
964
+ exchanges = list(static_data.keys())
965
+ exchange = init_exchange('Exchange', {}) # tmp to do the calculations until we have the ast-transpiler transpiling this code
956
966
  promises = []
957
967
  sum = 0
958
- if targetExchange:
959
- dump("Exchange to test: " + targetExchange)
960
- if testName:
961
- dump("Testing only: " + testName)
968
+ if target_exchange:
969
+ dump('Exchange to test: ' + target_exchange)
970
+ if test_name:
971
+ dump('Testing only: ' + test_name)
962
972
  for i in range(0, len(exchanges)):
963
- exchangeName = exchanges[i]
964
- exchangeData = staticData[exchangeName]
965
- numberOfTests = self.get_number_of_tests_from_exchange(exchange, exchangeData)
966
- sum = exchange.sum(sum, numberOfTests)
967
- promises.append(self.test_exchange_statically(exchangeName, exchangeData, testName))
973
+ exchange_name = exchanges[i]
974
+ exchange_data = static_data[exchange_name]
975
+ number_of_tests = self.get_number_of_tests_from_exchange(exchange, exchange_data)
976
+ sum = exchange.sum(sum, number_of_tests)
977
+ if type == 'request':
978
+ promises.append(self.test_exchange_request_statically(exchange_name, exchange_data, test_name))
979
+ else:
980
+ promises.append(self.test_exchange_response_statically(exchange_name, exchange_data, test_name))
968
981
  (promises)
969
- if self.staticTestsFailed:
982
+ if self.request_tests_failed or self.response_tests_failed:
970
983
  exit_script(1)
971
984
  else:
972
- successMessage = '[' + self.lang + '][TEST_SUCCESS] ' + str(sum) + ' static tests passed.'
973
- dump(successMessage)
985
+ success_message = '[' + self.lang + '][TEST_SUCCESS] ' + str(sum) + ' static ' + type + ' tests passed.'
986
+ dump(success_message)
974
987
  exit_script(0)
975
988
 
989
+ def run_static_response_tests(self, exchange_name=None, test=None):
990
+ # -----------------------------------------------------------------------------
991
+ # --- Init of mockResponses tests functions------------------------------------
992
+ # -----------------------------------------------------------------------------
993
+ self.run_static_tests('response', exchange_name, test)
994
+
976
995
  def run_broker_id_tests(self):
977
996
  # -----------------------------------------------------------------------------
978
997
  # --- Init of brokerId tests functions-----------------------------------------
979
998
  # -----------------------------------------------------------------------------
980
- promises = [
981
- self.test_binance(),
982
- self.test_okx(),
983
- self.test_cryptocom(),
984
- self.test_bybit(),
985
- self.test_kucoin(),
986
- self.test_kucoinfutures(),
987
- self.test_bitget(),
988
- self.test_mexc(),
989
- self.test_huobi(),
990
- self.test_woo(),
991
- self.test_bitmart(),
992
- self.test_coinex()
993
- ]
999
+ promises = [self.test_binance(), self.test_okx(), self.test_cryptocom(), self.test_bybit(), self.test_kucoin(), self.test_kucoinfutures(), self.test_bitget(), self.test_mexc(), self.test_huobi(), self.test_woo(), self.test_bitmart(), self.test_coinex()]
994
1000
  (promises)
995
- successMessage = '[' + self.lang + '][TEST_SUCCESS] brokerId tests passed.'
996
- dump(successMessage)
1001
+ success_message = '[' + self.lang + '][TEST_SUCCESS] brokerId tests passed.'
1002
+ dump(success_message)
997
1003
  exit_script(0)
998
1004
 
999
1005
  def test_binance(self):
1000
1006
  binance = self.init_offline_exchange('binance')
1001
- spotId = 'x-R4BD3S82'
1002
- spotOrderRequest = None
1007
+ spot_id = 'x-R4BD3S82'
1008
+ spot_order_request = None
1003
1009
  try:
1004
1010
  binance.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1005
1011
  except Exception as e:
1006
- spotOrderRequest = self.urlencoded_to_dict(binance.last_request_body)
1007
- clientOrderId = spotOrderRequest['newClientOrderId']
1008
- assert clientOrderId.startswith(spotId), 'spot clientOrderId does not start with spotId'
1009
- swapId = 'x-xcKtGhcu'
1010
- swapOrderRequest = None
1012
+ spot_order_request = self.urlencoded_to_dict(binance.last_request_body)
1013
+ client_order_id = spot_order_request['newClientOrderId']
1014
+ assert client_order_id.startswith(spot_id), 'spot clientOrderId does not start with spotId'
1015
+ swap_id = 'x-xcKtGhcu'
1016
+ swap_order_request = None
1011
1017
  try:
1012
1018
  binance.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
1013
1019
  except Exception as e:
1014
- swapOrderRequest = self.urlencoded_to_dict(binance.last_request_body)
1015
- swapInverseOrderRequest = None
1020
+ swap_order_request = self.urlencoded_to_dict(binance.last_request_body)
1021
+ swap_inverse_order_request = None
1016
1022
  try:
1017
1023
  binance.create_order('BTC/USD:BTC', 'limit', 'buy', 1, 20000)
1018
1024
  except Exception as e:
1019
- swapInverseOrderRequest = self.urlencoded_to_dict(binance.last_request_body)
1020
- clientOrderIdSpot = swapOrderRequest['newClientOrderId']
1021
- assert clientOrderIdSpot.startswith(swapId), 'swap clientOrderId does not start with swapId'
1022
- clientOrderIdInverse = swapInverseOrderRequest['newClientOrderId']
1023
- assert clientOrderIdInverse.startswith(swapId), 'swap clientOrderIdInverse does not start with swapId'
1025
+ swap_inverse_order_request = self.urlencoded_to_dict(binance.last_request_body)
1026
+ client_order_id_spot = swap_order_request['newClientOrderId']
1027
+ assert client_order_id_spot.startswith(swap_id), 'swap clientOrderId does not start with swapId'
1028
+ client_order_id_inverse = swap_inverse_order_request['newClientOrderId']
1029
+ assert client_order_id_inverse.startswith(swap_id), 'swap clientOrderIdInverse does not start with swapId'
1024
1030
  close(binance)
1025
1031
 
1026
1032
  def test_okx(self):
1027
1033
  okx = self.init_offline_exchange('okx')
1028
1034
  id = 'e847386590ce4dBC'
1029
- spotOrderRequest = None
1035
+ spot_order_request = None
1030
1036
  try:
1031
1037
  okx.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1032
1038
  except Exception as e:
1033
- spotOrderRequest = json_parse(okx.last_request_body)
1034
- clientOrderId = spotOrderRequest[0]['clOrdId'] # returns order inside array
1035
- assert clientOrderId.startswith(id), 'spot clientOrderId does not start with id'
1036
- assert spotOrderRequest[0]['tag'] == id, 'id different from spot tag'
1037
- swapOrderRequest = None
1039
+ spot_order_request = json_parse(okx.last_request_body)
1040
+ client_order_id = spot_order_request[0]['clOrdId'] # returns order inside array
1041
+ assert client_order_id.startswith(id), 'spot clientOrderId does not start with id'
1042
+ assert spot_order_request[0]['tag'] == id, 'id different from spot tag'
1043
+ swap_order_request = None
1038
1044
  try:
1039
1045
  okx.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
1040
1046
  except Exception as e:
1041
- swapOrderRequest = json_parse(okx.last_request_body)
1042
- clientOrderIdSpot = swapOrderRequest[0]['clOrdId']
1043
- assert clientOrderIdSpot.startswith(id), 'swap clientOrderId does not start with id'
1044
- assert swapOrderRequest[0]['tag'] == id, 'id different from swap tag'
1047
+ swap_order_request = json_parse(okx.last_request_body)
1048
+ client_order_id_spot = swap_order_request[0]['clOrdId']
1049
+ assert client_order_id_spot.startswith(id), 'swap clientOrderId does not start with id'
1050
+ assert swap_order_request[0]['tag'] == id, 'id different from swap tag'
1045
1051
  close(okx)
1046
1052
 
1047
1053
  def test_cryptocom(self):
@@ -1058,142 +1064,144 @@ class testMainClass(baseMainTestClass):
1058
1064
 
1059
1065
  def test_bybit(self):
1060
1066
  bybit = self.init_offline_exchange('bybit')
1061
- reqHeaders = None
1067
+ req_headers = None
1062
1068
  id = 'CCXT'
1063
1069
  assert bybit.options['brokerId'] == id, 'id not in options'
1064
1070
  try:
1065
1071
  bybit.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1066
1072
  except Exception as e:
1067
1073
  # we expect an error here, we're only interested in the headers
1068
- reqHeaders = bybit.last_request_headers
1069
- assert reqHeaders['Referer'] == id, 'id not in headers'
1074
+ req_headers = bybit.last_request_headers
1075
+ assert req_headers['Referer'] == id, 'id not in headers'
1070
1076
  close(bybit)
1071
1077
 
1072
1078
  def test_kucoin(self):
1073
1079
  kucoin = self.init_offline_exchange('kucoin')
1074
- reqHeaders = None
1080
+ req_headers = None
1075
1081
  assert kucoin.options['partner']['spot']['id'] == 'ccxt', 'id not in options'
1076
1082
  assert kucoin.options['partner']['spot']['key'] == '9e58cc35-5b5e-4133-92ec-166e3f077cb8', 'key not in options'
1077
1083
  try:
1078
1084
  kucoin.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1079
1085
  except Exception as e:
1080
1086
  # we expect an error here, we're only interested in the headers
1081
- reqHeaders = kucoin.last_request_headers
1087
+ req_headers = kucoin.last_request_headers
1082
1088
  id = 'ccxt'
1083
- assert reqHeaders['KC-API-PARTNER'] == id, 'id not in headers'
1089
+ assert req_headers['KC-API-PARTNER'] == id, 'id not in headers'
1084
1090
  close(kucoin)
1085
1091
 
1086
1092
  def test_kucoinfutures(self):
1087
1093
  kucoin = self.init_offline_exchange('kucoinfutures')
1088
- reqHeaders = None
1094
+ req_headers = None
1089
1095
  id = 'ccxtfutures'
1090
1096
  assert kucoin.options['partner']['future']['id'] == id, 'id not in options'
1091
1097
  assert kucoin.options['partner']['future']['key'] == '1b327198-f30c-4f14-a0ac-918871282f15', 'key not in options'
1092
1098
  try:
1093
1099
  kucoin.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
1094
1100
  except Exception as e:
1095
- reqHeaders = kucoin.last_request_headers
1096
- assert reqHeaders['KC-API-PARTNER'] == id, 'id not in headers'
1101
+ req_headers = kucoin.last_request_headers
1102
+ assert req_headers['KC-API-PARTNER'] == id, 'id not in headers'
1097
1103
  close(kucoin)
1098
1104
 
1099
1105
  def test_bitget(self):
1100
1106
  bitget = self.init_offline_exchange('bitget')
1101
- reqHeaders = None
1107
+ req_headers = None
1102
1108
  id = 'p4sve'
1103
1109
  assert bitget.options['broker'] == id, 'id not in options'
1104
1110
  try:
1105
1111
  bitget.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1106
1112
  except Exception as e:
1107
- reqHeaders = bitget.last_request_headers
1108
- assert reqHeaders['X-CHANNEL-API-CODE'] == id, 'id not in headers'
1113
+ req_headers = bitget.last_request_headers
1114
+ assert req_headers['X-CHANNEL-API-CODE'] == id, 'id not in headers'
1109
1115
  close(bitget)
1110
1116
 
1111
1117
  def test_mexc(self):
1112
1118
  mexc = self.init_offline_exchange('mexc')
1113
- reqHeaders = None
1119
+ req_headers = None
1114
1120
  id = 'CCXT'
1115
1121
  assert mexc.options['broker'] == id, 'id not in options'
1116
1122
  mexc.load_markets()
1117
1123
  try:
1118
1124
  mexc.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1119
1125
  except Exception as e:
1120
- reqHeaders = mexc.last_request_headers
1121
- assert reqHeaders['source'] == id, 'id not in headers'
1126
+ req_headers = mexc.last_request_headers
1127
+ assert req_headers['source'] == id, 'id not in headers'
1122
1128
  close(mexc)
1123
1129
 
1124
1130
  def test_huobi(self):
1125
1131
  huobi = self.init_offline_exchange('huobi')
1126
1132
  # spot test
1127
1133
  id = 'AA03022abc'
1128
- spotOrderRequest = None
1134
+ spot_order_request = None
1129
1135
  try:
1130
1136
  huobi.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1131
1137
  except Exception as e:
1132
- spotOrderRequest = json_parse(huobi.last_request_body)
1133
- clientOrderId = spotOrderRequest['client-order-id']
1134
- assert clientOrderId.startswith(id), 'spot clientOrderId does not start with id'
1138
+ spot_order_request = json_parse(huobi.last_request_body)
1139
+ client_order_id = spot_order_request['client-order-id']
1140
+ assert client_order_id.startswith(id), 'spot clientOrderId does not start with id'
1135
1141
  # swap test
1136
- swapOrderRequest = None
1142
+ swap_order_request = None
1137
1143
  try:
1138
1144
  huobi.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000)
1139
1145
  except Exception as e:
1140
- swapOrderRequest = json_parse(huobi.last_request_body)
1141
- swapInverseOrderRequest = None
1146
+ swap_order_request = json_parse(huobi.last_request_body)
1147
+ swap_inverse_order_request = None
1142
1148
  try:
1143
1149
  huobi.create_order('BTC/USD:BTC', 'limit', 'buy', 1, 20000)
1144
1150
  except Exception as e:
1145
- swapInverseOrderRequest = json_parse(huobi.last_request_body)
1146
- clientOrderIdSpot = swapOrderRequest['channel_code']
1147
- assert clientOrderIdSpot.startswith(id), 'swap channel_code does not start with id'
1148
- clientOrderIdInverse = swapInverseOrderRequest['channel_code']
1149
- assert clientOrderIdInverse.startswith(id), 'swap inverse channel_code does not start with id'
1151
+ swap_inverse_order_request = json_parse(huobi.last_request_body)
1152
+ client_order_id_spot = swap_order_request['channel_code']
1153
+ assert client_order_id_spot.startswith(id), 'swap channel_code does not start with id'
1154
+ client_order_id_inverse = swap_inverse_order_request['channel_code']
1155
+ assert client_order_id_inverse.startswith(id), 'swap inverse channel_code does not start with id'
1150
1156
  close(huobi)
1151
1157
 
1152
1158
  def test_woo(self):
1153
1159
  woo = self.init_offline_exchange('woo')
1154
1160
  # spot test
1155
1161
  id = 'bc830de7-50f3-460b-9ee0-f430f83f9dad'
1156
- spotOrderRequest = None
1162
+ spot_order_request = None
1157
1163
  try:
1158
1164
  woo.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1159
1165
  except Exception as e:
1160
- spotOrderRequest = self.urlencoded_to_dict(woo.last_request_body)
1161
- brokerId = spotOrderRequest['broker_id']
1162
- assert brokerId.startswith(id), 'broker_id does not start with id'
1166
+ spot_order_request = self.urlencoded_to_dict(woo.last_request_body)
1167
+ broker_id = spot_order_request['broker_id']
1168
+ assert broker_id.startswith(id), 'broker_id does not start with id'
1163
1169
  # swap test
1164
- stopOrderRequest = None
1170
+ stop_order_request = None
1165
1171
  try:
1166
- woo.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000, {'stopPrice': 30000})
1172
+ woo.create_order('BTC/USDT:USDT', 'limit', 'buy', 1, 20000, {
1173
+ 'stopPrice': 30000,
1174
+ })
1167
1175
  except Exception as e:
1168
- stopOrderRequest = json_parse(woo.last_request_body)
1169
- clientOrderIdSpot = stopOrderRequest['brokerId']
1170
- assert clientOrderIdSpot.startswith(id), 'brokerId does not start with id'
1176
+ stop_order_request = json_parse(woo.last_request_body)
1177
+ client_order_id_spot = stop_order_request['brokerId']
1178
+ assert client_order_id_spot.startswith(id), 'brokerId does not start with id'
1171
1179
  close(woo)
1172
1180
 
1173
1181
  def test_bitmart(self):
1174
1182
  bitmart = self.init_offline_exchange('bitmart')
1175
- reqHeaders = None
1183
+ req_headers = None
1176
1184
  id = 'CCXTxBitmart000'
1177
1185
  assert bitmart.options['brokerId'] == id, 'id not in options'
1178
1186
  bitmart.load_markets()
1179
1187
  try:
1180
1188
  bitmart.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1181
1189
  except Exception as e:
1182
- reqHeaders = bitmart.last_request_headers
1183
- assert reqHeaders['X-BM-BROKER-ID'] == id, 'id not in headers'
1190
+ req_headers = bitmart.last_request_headers
1191
+ assert req_headers['X-BM-BROKER-ID'] == id, 'id not in headers'
1184
1192
  close(bitmart)
1185
1193
 
1186
1194
  def test_coinex(self):
1187
1195
  exchange = self.init_offline_exchange('coinex')
1188
1196
  id = 'x-167673045'
1189
1197
  assert exchange.options['brokerId'] == id, 'id not in options'
1190
- spotOrderRequest = None
1198
+ spot_order_request = None
1191
1199
  try:
1192
1200
  exchange.create_order('BTC/USDT', 'limit', 'buy', 1, 20000)
1193
1201
  except Exception as e:
1194
- spotOrderRequest = json_parse(exchange.last_request_body)
1195
- clientOrderId = spotOrderRequest['client_id']
1196
- assert clientOrderId.startswith(id), 'clientOrderId does not start with id'
1202
+ spot_order_request = json_parse(exchange.last_request_body)
1203
+ client_order_id = spot_order_request['client_id']
1204
+ assert client_order_id.startswith(id), 'clientOrderId does not start with id'
1197
1205
  close(exchange)
1198
1206
 
1199
1207
  # ***** AUTO-TRANSPILER-END *****