ccxt 4.2.76__py2.py3-none-any.whl → 4.4.48__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (546) hide show
  1. ccxt/__init__.py +36 -14
  2. ccxt/abstract/alpaca.py +4 -0
  3. ccxt/abstract/bigone.py +1 -1
  4. ccxt/abstract/binance.py +112 -48
  5. ccxt/abstract/binancecoinm.py +112 -48
  6. ccxt/abstract/binanceus.py +147 -83
  7. ccxt/abstract/binanceusdm.py +112 -48
  8. ccxt/abstract/bingx.py +133 -78
  9. ccxt/abstract/bitbank.py +5 -0
  10. ccxt/abstract/bitfinex.py +136 -65
  11. ccxt/abstract/bitfinex1.py +69 -0
  12. ccxt/abstract/bitflyer.py +1 -0
  13. ccxt/abstract/bitget.py +8 -1
  14. ccxt/abstract/bitmart.py +13 -1
  15. ccxt/abstract/bitopro.py +1 -0
  16. ccxt/abstract/bitpanda.py +0 -12
  17. ccxt/abstract/bitrue.py +3 -3
  18. ccxt/abstract/bitstamp.py +26 -3
  19. ccxt/abstract/blofin.py +24 -0
  20. ccxt/abstract/btcbox.py +1 -0
  21. ccxt/abstract/bybit.py +29 -14
  22. ccxt/abstract/cex.py +28 -29
  23. ccxt/abstract/coinbase.py +6 -0
  24. ccxt/abstract/coinbaseadvanced.py +94 -0
  25. ccxt/abstract/{coinbasepro.py → coinbaseexchange.py} +1 -0
  26. ccxt/abstract/coinbaseinternational.py +1 -1
  27. ccxt/abstract/coincatch.py +94 -0
  28. ccxt/abstract/coinex.py +233 -123
  29. ccxt/abstract/coinmetro.py +1 -0
  30. ccxt/abstract/cryptocom.py +14 -0
  31. ccxt/abstract/defx.py +69 -0
  32. ccxt/abstract/deribit.py +1 -0
  33. ccxt/abstract/digifinex.py +1 -0
  34. ccxt/abstract/ellipx.py +25 -0
  35. ccxt/abstract/gate.py +20 -0
  36. ccxt/abstract/gateio.py +20 -0
  37. ccxt/abstract/gemini.py +1 -0
  38. ccxt/abstract/hashkey.py +67 -0
  39. ccxt/abstract/hyperliquid.py +1 -1
  40. ccxt/abstract/independentreserve.py +6 -0
  41. ccxt/abstract/kraken.py +4 -3
  42. ccxt/abstract/krakenfutures.py +4 -0
  43. ccxt/abstract/kucoin.py +25 -0
  44. ccxt/abstract/kucoinfutures.py +35 -0
  45. ccxt/abstract/luno.py +2 -0
  46. ccxt/abstract/mexc.py +4 -0
  47. ccxt/abstract/myokx.py +340 -0
  48. ccxt/abstract/oceanex.py +5 -0
  49. ccxt/abstract/okx.py +30 -0
  50. ccxt/abstract/onetrading.py +0 -12
  51. ccxt/abstract/oxfun.py +34 -0
  52. ccxt/abstract/paradex.py +40 -0
  53. ccxt/abstract/phemex.py +1 -0
  54. ccxt/abstract/upbit.py +4 -0
  55. ccxt/abstract/vertex.py +19 -0
  56. ccxt/abstract/whitebit.py +31 -1
  57. ccxt/abstract/woo.py +6 -2
  58. ccxt/abstract/woofipro.py +119 -0
  59. ccxt/abstract/xt.py +153 -0
  60. ccxt/abstract/zonda.py +6 -0
  61. ccxt/ace.py +164 -60
  62. ccxt/alpaca.py +727 -63
  63. ccxt/ascendex.py +395 -249
  64. ccxt/async_support/__init__.py +36 -14
  65. ccxt/async_support/ace.py +164 -60
  66. ccxt/async_support/alpaca.py +727 -63
  67. ccxt/async_support/ascendex.py +396 -249
  68. ccxt/async_support/base/exchange.py +531 -155
  69. ccxt/async_support/base/ws/aiohttp_client.py +28 -5
  70. ccxt/async_support/base/ws/cache.py +3 -2
  71. ccxt/async_support/base/ws/client.py +26 -5
  72. ccxt/async_support/base/ws/fast_client.py +4 -3
  73. ccxt/async_support/base/ws/functions.py +1 -1
  74. ccxt/async_support/base/ws/future.py +40 -31
  75. ccxt/async_support/base/ws/order_book_side.py +3 -0
  76. ccxt/async_support/bequant.py +1 -1
  77. ccxt/async_support/bigone.py +329 -202
  78. ccxt/async_support/binance.py +3513 -1511
  79. ccxt/async_support/binancecoinm.py +2 -1
  80. ccxt/async_support/binanceus.py +12 -1
  81. ccxt/async_support/binanceusdm.py +3 -1
  82. ccxt/async_support/bingx.py +3105 -881
  83. ccxt/async_support/bit2c.py +119 -38
  84. ccxt/async_support/bitbank.py +215 -76
  85. ccxt/async_support/bitbns.py +124 -53
  86. ccxt/async_support/bitfinex.py +3236 -1078
  87. ccxt/async_support/bitfinex1.py +1711 -0
  88. ccxt/async_support/bitflyer.py +239 -50
  89. ccxt/async_support/bitget.py +1513 -563
  90. ccxt/async_support/bithumb.py +201 -67
  91. ccxt/async_support/bitmart.py +1320 -435
  92. ccxt/async_support/bitmex.py +308 -111
  93. ccxt/async_support/bitopro.py +256 -96
  94. ccxt/async_support/bitrue.py +365 -233
  95. ccxt/async_support/bitso.py +201 -89
  96. ccxt/async_support/bitstamp.py +438 -269
  97. ccxt/async_support/bitteam.py +179 -73
  98. ccxt/async_support/bitvavo.py +180 -70
  99. ccxt/async_support/bl3p.py +92 -25
  100. ccxt/async_support/blockchaincom.py +193 -79
  101. ccxt/async_support/blofin.py +403 -150
  102. ccxt/async_support/btcalpha.py +161 -55
  103. ccxt/async_support/btcbox.py +250 -34
  104. ccxt/async_support/btcmarkets.py +232 -85
  105. ccxt/async_support/btcturk.py +159 -60
  106. ccxt/async_support/bybit.py +2326 -1255
  107. ccxt/async_support/cex.py +1409 -1329
  108. ccxt/async_support/coinbase.py +1455 -288
  109. ccxt/async_support/coinbaseadvanced.py +17 -0
  110. ccxt/async_support/{coinbasepro.py → coinbaseexchange.py} +233 -99
  111. ccxt/async_support/coinbaseinternational.py +428 -88
  112. ccxt/async_support/coincatch.py +5152 -0
  113. ccxt/async_support/coincheck.py +121 -38
  114. ccxt/async_support/coinex.py +4020 -3339
  115. ccxt/async_support/coinlist.py +273 -116
  116. ccxt/async_support/coinmate.py +204 -97
  117. ccxt/async_support/coinmetro.py +203 -110
  118. ccxt/async_support/coinone.py +142 -68
  119. ccxt/async_support/coinsph.py +206 -89
  120. ccxt/async_support/coinspot.py +137 -62
  121. ccxt/async_support/cryptocom.py +515 -185
  122. ccxt/async_support/currencycom.py +203 -85
  123. ccxt/async_support/defx.py +2066 -0
  124. ccxt/async_support/delta.py +467 -158
  125. ccxt/async_support/deribit.py +558 -324
  126. ccxt/async_support/digifinex.py +340 -223
  127. ccxt/async_support/ellipx.py +1826 -0
  128. ccxt/async_support/exmo.py +259 -128
  129. ccxt/async_support/gate.py +1473 -464
  130. ccxt/async_support/gemini.py +206 -84
  131. ccxt/async_support/hashkey.py +4164 -0
  132. ccxt/async_support/hitbtc.py +334 -178
  133. ccxt/async_support/hollaex.py +134 -83
  134. ccxt/async_support/htx.py +1095 -563
  135. ccxt/async_support/huobijp.py +105 -56
  136. ccxt/async_support/hyperliquid.py +1634 -269
  137. ccxt/async_support/idex.py +148 -95
  138. ccxt/async_support/independentreserve.py +236 -31
  139. ccxt/async_support/indodax.py +165 -62
  140. ccxt/async_support/kraken.py +871 -354
  141. ccxt/async_support/krakenfutures.py +324 -100
  142. ccxt/async_support/kucoin.py +1050 -355
  143. ccxt/async_support/kucoinfutures.py +1004 -149
  144. ccxt/async_support/kuna.py +138 -106
  145. ccxt/async_support/latoken.py +135 -79
  146. ccxt/async_support/lbank.py +290 -113
  147. ccxt/async_support/luno.py +112 -62
  148. ccxt/async_support/lykke.py +104 -55
  149. ccxt/async_support/mercado.py +36 -29
  150. ccxt/async_support/mexc.py +995 -429
  151. ccxt/async_support/myokx.py +43 -0
  152. ccxt/async_support/ndax.py +163 -82
  153. ccxt/async_support/novadax.py +121 -75
  154. ccxt/async_support/oceanex.py +175 -59
  155. ccxt/async_support/okcoin.py +222 -163
  156. ccxt/async_support/okx.py +1777 -455
  157. ccxt/async_support/onetrading.py +132 -414
  158. ccxt/async_support/oxfun.py +2832 -0
  159. ccxt/async_support/p2b.py +79 -51
  160. ccxt/async_support/paradex.py +2017 -0
  161. ccxt/async_support/paymium.py +56 -32
  162. ccxt/async_support/phemex.py +572 -196
  163. ccxt/async_support/poloniex.py +218 -95
  164. ccxt/async_support/poloniexfutures.py +260 -92
  165. ccxt/async_support/probit.py +143 -110
  166. ccxt/async_support/timex.py +123 -70
  167. ccxt/async_support/tokocrypto.py +129 -93
  168. ccxt/async_support/tradeogre.py +39 -25
  169. ccxt/async_support/upbit.py +322 -113
  170. ccxt/async_support/vertex.py +2983 -0
  171. ccxt/async_support/wavesexchange.py +227 -173
  172. ccxt/async_support/wazirx.py +145 -65
  173. ccxt/async_support/whitebit.py +533 -138
  174. ccxt/async_support/woo.py +1155 -295
  175. ccxt/async_support/woofipro.py +2716 -0
  176. ccxt/async_support/xt.py +4628 -0
  177. ccxt/async_support/yobit.py +160 -92
  178. ccxt/async_support/zaif.py +80 -33
  179. ccxt/async_support/zonda.py +140 -69
  180. ccxt/base/errors.py +51 -20
  181. ccxt/base/exchange.py +1729 -482
  182. ccxt/base/precise.py +10 -0
  183. ccxt/base/types.py +223 -4
  184. ccxt/bequant.py +1 -1
  185. ccxt/bigone.py +329 -202
  186. ccxt/binance.py +3513 -1511
  187. ccxt/binancecoinm.py +2 -1
  188. ccxt/binanceus.py +12 -1
  189. ccxt/binanceusdm.py +3 -1
  190. ccxt/bingx.py +3105 -881
  191. ccxt/bit2c.py +119 -38
  192. ccxt/bitbank.py +215 -76
  193. ccxt/bitbns.py +124 -53
  194. ccxt/bitfinex.py +3235 -1078
  195. ccxt/bitfinex1.py +1710 -0
  196. ccxt/bitflyer.py +239 -50
  197. ccxt/bitget.py +1513 -563
  198. ccxt/bithumb.py +200 -67
  199. ccxt/bitmart.py +1320 -435
  200. ccxt/bitmex.py +308 -111
  201. ccxt/bitopro.py +256 -96
  202. ccxt/bitrue.py +365 -233
  203. ccxt/bitso.py +201 -89
  204. ccxt/bitstamp.py +438 -269
  205. ccxt/bitteam.py +179 -73
  206. ccxt/bitvavo.py +180 -70
  207. ccxt/bl3p.py +92 -25
  208. ccxt/blockchaincom.py +193 -79
  209. ccxt/blofin.py +403 -150
  210. ccxt/btcalpha.py +161 -55
  211. ccxt/btcbox.py +250 -34
  212. ccxt/btcmarkets.py +232 -85
  213. ccxt/btcturk.py +159 -60
  214. ccxt/bybit.py +2326 -1255
  215. ccxt/cex.py +1408 -1329
  216. ccxt/coinbase.py +1455 -288
  217. ccxt/coinbaseadvanced.py +17 -0
  218. ccxt/{coinbasepro.py → coinbaseexchange.py} +233 -99
  219. ccxt/coinbaseinternational.py +428 -88
  220. ccxt/coincatch.py +5152 -0
  221. ccxt/coincheck.py +121 -38
  222. ccxt/coinex.py +4020 -3339
  223. ccxt/coinlist.py +273 -116
  224. ccxt/coinmate.py +204 -97
  225. ccxt/coinmetro.py +203 -110
  226. ccxt/coinone.py +142 -68
  227. ccxt/coinsph.py +206 -89
  228. ccxt/coinspot.py +137 -62
  229. ccxt/cryptocom.py +515 -185
  230. ccxt/currencycom.py +203 -85
  231. ccxt/defx.py +2065 -0
  232. ccxt/delta.py +467 -158
  233. ccxt/deribit.py +558 -324
  234. ccxt/digifinex.py +340 -223
  235. ccxt/ellipx.py +1826 -0
  236. ccxt/exmo.py +259 -128
  237. ccxt/gate.py +1473 -464
  238. ccxt/gemini.py +206 -84
  239. ccxt/hashkey.py +4164 -0
  240. ccxt/hitbtc.py +334 -178
  241. ccxt/hollaex.py +134 -83
  242. ccxt/htx.py +1095 -563
  243. ccxt/huobijp.py +105 -56
  244. ccxt/hyperliquid.py +1633 -269
  245. ccxt/idex.py +148 -95
  246. ccxt/independentreserve.py +235 -31
  247. ccxt/indodax.py +165 -62
  248. ccxt/kraken.py +871 -354
  249. ccxt/krakenfutures.py +324 -100
  250. ccxt/kucoin.py +1050 -355
  251. ccxt/kucoinfutures.py +1004 -149
  252. ccxt/kuna.py +138 -106
  253. ccxt/latoken.py +135 -79
  254. ccxt/lbank.py +290 -113
  255. ccxt/luno.py +112 -62
  256. ccxt/lykke.py +104 -55
  257. ccxt/mercado.py +36 -29
  258. ccxt/mexc.py +994 -429
  259. ccxt/myokx.py +43 -0
  260. ccxt/ndax.py +163 -82
  261. ccxt/novadax.py +121 -75
  262. ccxt/oceanex.py +175 -59
  263. ccxt/okcoin.py +222 -163
  264. ccxt/okx.py +1777 -455
  265. ccxt/onetrading.py +132 -414
  266. ccxt/oxfun.py +2831 -0
  267. ccxt/p2b.py +79 -51
  268. ccxt/paradex.py +2017 -0
  269. ccxt/paymium.py +56 -32
  270. ccxt/phemex.py +572 -196
  271. ccxt/poloniex.py +218 -95
  272. ccxt/poloniexfutures.py +260 -92
  273. ccxt/pro/__init__.py +29 -5
  274. ccxt/pro/alpaca.py +32 -17
  275. ccxt/pro/ascendex.py +63 -15
  276. ccxt/pro/bequant.py +4 -0
  277. ccxt/pro/binance.py +1596 -329
  278. ccxt/pro/binancecoinm.py +1 -0
  279. ccxt/pro/binanceus.py +2 -9
  280. ccxt/pro/binanceusdm.py +2 -0
  281. ccxt/pro/bingx.py +527 -134
  282. ccxt/pro/bitcoincom.py +4 -1
  283. ccxt/pro/bitfinex.py +731 -266
  284. ccxt/pro/bitfinex1.py +635 -0
  285. ccxt/pro/bitget.py +726 -357
  286. ccxt/pro/bithumb.py +380 -0
  287. ccxt/pro/bitmart.py +138 -39
  288. ccxt/pro/bitmex.py +199 -40
  289. ccxt/pro/bitopro.py +25 -13
  290. ccxt/pro/bitrue.py +31 -32
  291. ccxt/pro/bitstamp.py +7 -6
  292. ccxt/pro/bitvavo.py +204 -82
  293. ccxt/pro/blockchaincom.py +30 -17
  294. ccxt/pro/blofin.py +692 -0
  295. ccxt/pro/bybit.py +791 -82
  296. ccxt/pro/cex.py +99 -51
  297. ccxt/pro/coinbase.py +220 -30
  298. ccxt/{async_support/hitbtc3.py → pro/coinbaseadvanced.py} +5 -5
  299. ccxt/pro/{coinbasepro.py → coinbaseexchange.py} +19 -19
  300. ccxt/pro/coinbaseinternational.py +193 -30
  301. ccxt/pro/coincatch.py +1464 -0
  302. ccxt/pro/coincheck.py +11 -6
  303. ccxt/pro/coinex.py +967 -661
  304. ccxt/pro/coinone.py +17 -10
  305. ccxt/pro/cryptocom.py +446 -66
  306. ccxt/pro/currencycom.py +11 -10
  307. ccxt/pro/defx.py +832 -0
  308. ccxt/pro/deribit.py +168 -32
  309. ccxt/pro/exmo.py +253 -21
  310. ccxt/pro/gate.py +729 -64
  311. ccxt/pro/gemini.py +44 -26
  312. ccxt/pro/hashkey.py +802 -0
  313. ccxt/pro/hitbtc.py +208 -103
  314. ccxt/pro/hollaex.py +25 -9
  315. ccxt/pro/htx.py +83 -39
  316. ccxt/pro/huobijp.py +17 -16
  317. ccxt/pro/hyperliquid.py +502 -31
  318. ccxt/pro/idex.py +28 -13
  319. ccxt/pro/independentreserve.py +21 -16
  320. ccxt/pro/kraken.py +298 -51
  321. ccxt/pro/krakenfutures.py +166 -75
  322. ccxt/pro/kucoin.py +395 -77
  323. ccxt/pro/kucoinfutures.py +400 -99
  324. ccxt/pro/lbank.py +52 -31
  325. ccxt/pro/luno.py +12 -10
  326. ccxt/pro/mexc.py +400 -50
  327. ccxt/pro/myokx.py +28 -0
  328. ccxt/pro/ndax.py +25 -12
  329. ccxt/pro/okcoin.py +28 -9
  330. ccxt/pro/okx.py +935 -124
  331. ccxt/pro/onetrading.py +41 -24
  332. ccxt/pro/oxfun.py +1054 -0
  333. ccxt/pro/p2b.py +100 -24
  334. ccxt/pro/paradex.py +352 -0
  335. ccxt/pro/phemex.py +93 -34
  336. ccxt/pro/poloniex.py +129 -50
  337. ccxt/pro/poloniexfutures.py +53 -32
  338. ccxt/pro/probit.py +93 -86
  339. ccxt/pro/upbit.py +401 -8
  340. ccxt/pro/vertex.py +943 -0
  341. ccxt/pro/wazirx.py +46 -28
  342. ccxt/pro/whitebit.py +65 -12
  343. ccxt/pro/woo.py +486 -70
  344. ccxt/pro/woofipro.py +1271 -0
  345. ccxt/pro/xt.py +1067 -0
  346. ccxt/probit.py +143 -110
  347. ccxt/static_dependencies/__init__.py +1 -1
  348. ccxt/static_dependencies/lark/__init__.py +38 -0
  349. ccxt/static_dependencies/lark/__pyinstaller/__init__.py +6 -0
  350. ccxt/static_dependencies/lark/__pyinstaller/hook-lark.py +14 -0
  351. ccxt/static_dependencies/lark/ast_utils.py +59 -0
  352. ccxt/static_dependencies/lark/common.py +86 -0
  353. ccxt/static_dependencies/lark/exceptions.py +292 -0
  354. ccxt/static_dependencies/lark/grammar.py +130 -0
  355. ccxt/static_dependencies/lark/grammars/__init__.py +0 -0
  356. ccxt/static_dependencies/lark/indenter.py +143 -0
  357. ccxt/static_dependencies/lark/lark.py +658 -0
  358. ccxt/static_dependencies/lark/lexer.py +678 -0
  359. ccxt/static_dependencies/lark/load_grammar.py +1428 -0
  360. ccxt/static_dependencies/lark/parse_tree_builder.py +391 -0
  361. ccxt/static_dependencies/lark/parser_frontends.py +257 -0
  362. ccxt/static_dependencies/lark/parsers/__init__.py +0 -0
  363. ccxt/static_dependencies/lark/parsers/cyk.py +340 -0
  364. ccxt/static_dependencies/lark/parsers/earley.py +314 -0
  365. ccxt/static_dependencies/lark/parsers/earley_common.py +42 -0
  366. ccxt/static_dependencies/lark/parsers/earley_forest.py +801 -0
  367. ccxt/static_dependencies/lark/parsers/grammar_analysis.py +203 -0
  368. ccxt/static_dependencies/lark/parsers/lalr_analysis.py +332 -0
  369. ccxt/static_dependencies/lark/parsers/lalr_interactive_parser.py +158 -0
  370. ccxt/static_dependencies/lark/parsers/lalr_parser.py +122 -0
  371. ccxt/static_dependencies/lark/parsers/lalr_parser_state.py +110 -0
  372. ccxt/static_dependencies/lark/parsers/xearley.py +165 -0
  373. ccxt/static_dependencies/lark/py.typed +0 -0
  374. ccxt/static_dependencies/lark/reconstruct.py +107 -0
  375. ccxt/static_dependencies/lark/tools/__init__.py +70 -0
  376. ccxt/static_dependencies/lark/tools/nearley.py +202 -0
  377. ccxt/static_dependencies/lark/tools/serialize.py +32 -0
  378. ccxt/static_dependencies/lark/tools/standalone.py +196 -0
  379. ccxt/static_dependencies/lark/tree.py +267 -0
  380. ccxt/static_dependencies/lark/tree_matcher.py +186 -0
  381. ccxt/static_dependencies/lark/tree_templates.py +180 -0
  382. ccxt/static_dependencies/lark/utils.py +343 -0
  383. ccxt/static_dependencies/lark/visitors.py +596 -0
  384. ccxt/static_dependencies/marshmallow/__init__.py +81 -0
  385. ccxt/static_dependencies/marshmallow/base.py +65 -0
  386. ccxt/static_dependencies/marshmallow/class_registry.py +94 -0
  387. ccxt/static_dependencies/marshmallow/decorators.py +231 -0
  388. ccxt/static_dependencies/marshmallow/error_store.py +60 -0
  389. ccxt/static_dependencies/marshmallow/exceptions.py +71 -0
  390. ccxt/static_dependencies/marshmallow/fields.py +2114 -0
  391. ccxt/static_dependencies/marshmallow/orderedset.py +89 -0
  392. ccxt/static_dependencies/marshmallow/py.typed +0 -0
  393. ccxt/static_dependencies/marshmallow/schema.py +1228 -0
  394. ccxt/static_dependencies/marshmallow/types.py +12 -0
  395. ccxt/static_dependencies/marshmallow/utils.py +378 -0
  396. ccxt/static_dependencies/marshmallow/validate.py +678 -0
  397. ccxt/static_dependencies/marshmallow/warnings.py +2 -0
  398. ccxt/static_dependencies/marshmallow_dataclass/__init__.py +1047 -0
  399. ccxt/static_dependencies/marshmallow_dataclass/collection_field.py +51 -0
  400. ccxt/static_dependencies/marshmallow_dataclass/lazy_class_attribute.py +45 -0
  401. ccxt/static_dependencies/marshmallow_dataclass/mypy.py +71 -0
  402. ccxt/static_dependencies/marshmallow_dataclass/py.typed +0 -0
  403. ccxt/static_dependencies/marshmallow_dataclass/typing.py +14 -0
  404. ccxt/static_dependencies/marshmallow_dataclass/union_field.py +82 -0
  405. ccxt/static_dependencies/marshmallow_oneofschema/__init__.py +1 -0
  406. ccxt/static_dependencies/marshmallow_oneofschema/one_of_schema.py +193 -0
  407. ccxt/static_dependencies/marshmallow_oneofschema/py.typed +0 -0
  408. ccxt/static_dependencies/starknet/__init__.py +0 -0
  409. ccxt/static_dependencies/starknet/cairo/__init__.py +0 -0
  410. ccxt/static_dependencies/starknet/cairo/data_types.py +123 -0
  411. ccxt/static_dependencies/starknet/cairo/deprecated_parse/__init__.py +0 -0
  412. ccxt/static_dependencies/starknet/cairo/deprecated_parse/cairo_types.py +77 -0
  413. ccxt/static_dependencies/starknet/cairo/deprecated_parse/parser.py +46 -0
  414. ccxt/static_dependencies/starknet/cairo/deprecated_parse/parser_transformer.py +138 -0
  415. ccxt/static_dependencies/starknet/cairo/felt.py +64 -0
  416. ccxt/static_dependencies/starknet/cairo/type_parser.py +121 -0
  417. ccxt/static_dependencies/starknet/cairo/v1/__init__.py +0 -0
  418. ccxt/static_dependencies/starknet/cairo/v1/type_parser.py +59 -0
  419. ccxt/static_dependencies/starknet/cairo/v2/__init__.py +0 -0
  420. ccxt/static_dependencies/starknet/cairo/v2/type_parser.py +77 -0
  421. ccxt/static_dependencies/starknet/ccxt_utils.py +7 -0
  422. ccxt/static_dependencies/starknet/common.py +15 -0
  423. ccxt/static_dependencies/starknet/constants.py +39 -0
  424. ccxt/static_dependencies/starknet/hash/__init__.py +0 -0
  425. ccxt/static_dependencies/starknet/hash/address.py +79 -0
  426. ccxt/static_dependencies/starknet/hash/compiled_class_hash_objects.py +111 -0
  427. ccxt/static_dependencies/starknet/hash/selector.py +16 -0
  428. ccxt/static_dependencies/starknet/hash/storage.py +12 -0
  429. ccxt/static_dependencies/starknet/hash/utils.py +78 -0
  430. ccxt/static_dependencies/starknet/models/__init__.py +0 -0
  431. ccxt/static_dependencies/starknet/models/typed_data.py +45 -0
  432. ccxt/static_dependencies/starknet/serialization/__init__.py +24 -0
  433. ccxt/static_dependencies/starknet/serialization/_calldata_reader.py +40 -0
  434. ccxt/static_dependencies/starknet/serialization/_context.py +142 -0
  435. ccxt/static_dependencies/starknet/serialization/data_serializers/__init__.py +10 -0
  436. ccxt/static_dependencies/starknet/serialization/data_serializers/_common.py +82 -0
  437. ccxt/static_dependencies/starknet/serialization/data_serializers/array_serializer.py +43 -0
  438. ccxt/static_dependencies/starknet/serialization/data_serializers/bool_serializer.py +37 -0
  439. ccxt/static_dependencies/starknet/serialization/data_serializers/byte_array_serializer.py +66 -0
  440. ccxt/static_dependencies/starknet/serialization/data_serializers/cairo_data_serializer.py +71 -0
  441. ccxt/static_dependencies/starknet/serialization/data_serializers/enum_serializer.py +71 -0
  442. ccxt/static_dependencies/starknet/serialization/data_serializers/felt_serializer.py +50 -0
  443. ccxt/static_dependencies/starknet/serialization/data_serializers/named_tuple_serializer.py +58 -0
  444. ccxt/static_dependencies/starknet/serialization/data_serializers/option_serializer.py +43 -0
  445. ccxt/static_dependencies/starknet/serialization/data_serializers/output_serializer.py +40 -0
  446. ccxt/static_dependencies/starknet/serialization/data_serializers/payload_serializer.py +72 -0
  447. ccxt/static_dependencies/starknet/serialization/data_serializers/struct_serializer.py +36 -0
  448. ccxt/static_dependencies/starknet/serialization/data_serializers/tuple_serializer.py +36 -0
  449. ccxt/static_dependencies/starknet/serialization/data_serializers/uint256_serializer.py +76 -0
  450. ccxt/static_dependencies/starknet/serialization/data_serializers/uint_serializer.py +100 -0
  451. ccxt/static_dependencies/starknet/serialization/data_serializers/unit_serializer.py +32 -0
  452. ccxt/static_dependencies/starknet/serialization/errors.py +10 -0
  453. ccxt/static_dependencies/starknet/serialization/factory.py +229 -0
  454. ccxt/static_dependencies/starknet/serialization/function_serialization_adapter.py +110 -0
  455. ccxt/static_dependencies/starknet/serialization/tuple_dataclass.py +59 -0
  456. ccxt/static_dependencies/starknet/utils/__init__.py +0 -0
  457. ccxt/static_dependencies/starknet/utils/constructor_args_translator.py +86 -0
  458. ccxt/static_dependencies/starknet/utils/iterable.py +13 -0
  459. ccxt/static_dependencies/starknet/utils/schema.py +13 -0
  460. ccxt/static_dependencies/starknet/utils/typed_data.py +182 -0
  461. ccxt/static_dependencies/starkware/__init__.py +0 -0
  462. ccxt/static_dependencies/starkware/crypto/__init__.py +0 -0
  463. ccxt/static_dependencies/starkware/crypto/fast_pedersen_hash.py +50 -0
  464. ccxt/static_dependencies/starkware/crypto/math_utils.py +78 -0
  465. ccxt/static_dependencies/starkware/crypto/signature.py +2344 -0
  466. ccxt/static_dependencies/starkware/crypto/utils.py +63 -0
  467. ccxt/static_dependencies/sympy/__init__.py +0 -0
  468. ccxt/static_dependencies/sympy/core/__init__.py +0 -0
  469. ccxt/static_dependencies/sympy/core/intfunc.py +35 -0
  470. ccxt/static_dependencies/sympy/external/__init__.py +0 -0
  471. ccxt/static_dependencies/sympy/external/gmpy.py +345 -0
  472. ccxt/static_dependencies/sympy/external/importtools.py +187 -0
  473. ccxt/static_dependencies/sympy/external/ntheory.py +637 -0
  474. ccxt/static_dependencies/sympy/external/pythonmpq.py +341 -0
  475. ccxt/static_dependencies/typing_inspect/__init__.py +0 -0
  476. ccxt/static_dependencies/typing_inspect/typing_inspect.py +851 -0
  477. ccxt/test/{test_async.py → tests_async.py} +465 -407
  478. ccxt/test/tests_helpers.py +285 -0
  479. ccxt/test/tests_init.py +39 -0
  480. ccxt/test/{test_sync.py → tests_sync.py} +465 -409
  481. ccxt/timex.py +123 -70
  482. ccxt/tokocrypto.py +129 -93
  483. ccxt/tradeogre.py +39 -25
  484. ccxt/upbit.py +322 -113
  485. ccxt/vertex.py +2983 -0
  486. ccxt/wavesexchange.py +227 -173
  487. ccxt/wazirx.py +145 -65
  488. ccxt/whitebit.py +533 -138
  489. ccxt/woo.py +1155 -295
  490. ccxt/woofipro.py +2716 -0
  491. ccxt/xt.py +4627 -0
  492. ccxt/yobit.py +159 -92
  493. ccxt/zaif.py +80 -33
  494. ccxt/zonda.py +140 -69
  495. ccxt-4.4.48.dist-info/LICENSE.txt +21 -0
  496. ccxt-4.4.48.dist-info/METADATA +646 -0
  497. ccxt-4.4.48.dist-info/RECORD +669 -0
  498. {ccxt-4.2.76.dist-info → ccxt-4.4.48.dist-info}/WHEEL +1 -1
  499. ccxt/abstract/bitbay.py +0 -47
  500. ccxt/abstract/bitfinex2.py +0 -139
  501. ccxt/abstract/hitbtc3.py +0 -115
  502. ccxt/async_support/bitbay.py +0 -17
  503. ccxt/async_support/bitfinex2.py +0 -3496
  504. ccxt/async_support/flowbtc.py +0 -34
  505. ccxt/bitbay.py +0 -17
  506. ccxt/bitfinex2.py +0 -3496
  507. ccxt/flowbtc.py +0 -34
  508. ccxt/hitbtc3.py +0 -16
  509. ccxt/pro/bitfinex2.py +0 -1081
  510. ccxt/test/base/__init__.py +0 -28
  511. ccxt/test/base/test_account.py +0 -26
  512. ccxt/test/base/test_balance.py +0 -56
  513. ccxt/test/base/test_borrow_interest.py +0 -35
  514. ccxt/test/base/test_borrow_rate.py +0 -32
  515. ccxt/test/base/test_calculate_fee.py +0 -51
  516. ccxt/test/base/test_crypto.py +0 -127
  517. ccxt/test/base/test_currency.py +0 -76
  518. ccxt/test/base/test_datetime.py +0 -103
  519. ccxt/test/base/test_decimal_to_precision.py +0 -392
  520. ccxt/test/base/test_deep_extend.py +0 -68
  521. ccxt/test/base/test_deposit_withdrawal.py +0 -50
  522. ccxt/test/base/test_exchange_datetime_functions.py +0 -76
  523. ccxt/test/base/test_funding_rate_history.py +0 -29
  524. ccxt/test/base/test_last_price.py +0 -32
  525. ccxt/test/base/test_ledger_entry.py +0 -45
  526. ccxt/test/base/test_ledger_item.py +0 -48
  527. ccxt/test/base/test_leverage_tier.py +0 -33
  528. ccxt/test/base/test_margin_mode.py +0 -24
  529. ccxt/test/base/test_margin_modification.py +0 -35
  530. ccxt/test/base/test_market.py +0 -190
  531. ccxt/test/base/test_number.py +0 -411
  532. ccxt/test/base/test_ohlcv.py +0 -32
  533. ccxt/test/base/test_open_interest.py +0 -32
  534. ccxt/test/base/test_order.py +0 -64
  535. ccxt/test/base/test_order_book.py +0 -63
  536. ccxt/test/base/test_position.py +0 -60
  537. ccxt/test/base/test_shared_methods.py +0 -345
  538. ccxt/test/base/test_status.py +0 -24
  539. ccxt/test/base/test_throttle.py +0 -126
  540. ccxt/test/base/test_ticker.py +0 -86
  541. ccxt/test/base/test_trade.py +0 -47
  542. ccxt/test/base/test_trading_fee.py +0 -26
  543. ccxt/test/base/test_transaction.py +0 -39
  544. ccxt-4.2.76.dist-info/METADATA +0 -626
  545. ccxt-4.2.76.dist-info/RECORD +0 -534
  546. {ccxt-4.2.76.dist-info → ccxt-4.4.48.dist-info}/top_level.txt +0 -0
ccxt/base/exchange.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.2.76'
7
+ __version__ = '4.4.48'
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
@@ -23,7 +23,8 @@ from ccxt.base.errors import NullResponse
23
23
  from ccxt.base.errors import RateLimitExceeded
24
24
  from ccxt.base.errors import BadRequest
25
25
  from ccxt.base.errors import BadResponse
26
- from ccxt.base.errors import ProxyError
26
+ from ccxt.base.errors import InvalidProxySettings
27
+ from ccxt.base.errors import UnsubscribeError
27
28
 
28
29
  # -----------------------------------------------------------------------------
29
30
 
@@ -31,14 +32,15 @@ from ccxt.base.decimal_to_precision import decimal_to_precision
31
32
  from ccxt.base.decimal_to_precision import DECIMAL_PLACES, TICK_SIZE, NO_PADDING, TRUNCATE, ROUND, ROUND_UP, ROUND_DOWN, SIGNIFICANT_DIGITS
32
33
  from ccxt.base.decimal_to_precision import number_to_string
33
34
  from ccxt.base.precise import Precise
34
- from ccxt.base.types import BalanceAccount, Currency, IndexType, OrderSide, OrderType, Trade, OrderRequest, Market, MarketType, Str, Num
35
+ from ccxt.base.types import BalanceAccount, Currency, IndexType, OrderSide, OrderType, Trade, OrderRequest, Market, MarketType, Str, Num, Strings, CancellationRequest, Bool
35
36
 
36
37
  # -----------------------------------------------------------------------------
37
38
 
38
39
  # rsa jwt signing
39
40
  from cryptography.hazmat import backends
40
41
  from cryptography.hazmat.primitives import hashes
41
- from cryptography.hazmat.primitives.asymmetric import padding
42
+ from cryptography.hazmat.primitives.asymmetric import padding, ed25519
43
+ # from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
42
44
  from cryptography.hazmat.primitives.serialization import load_pem_private_key
43
45
 
44
46
  # -----------------------------------------------------------------------------
@@ -58,6 +60,12 @@ from ccxt.static_dependencies.ethereum import abi
58
60
  from ccxt.static_dependencies.ethereum import account
59
61
  from ccxt.static_dependencies.msgpack import packb
60
62
 
63
+ # starknet
64
+ from ccxt.static_dependencies.starknet.ccxt_utils import get_private_key_from_eth_signature
65
+ from ccxt.static_dependencies.starknet.hash.address import compute_address
66
+ from ccxt.static_dependencies.starknet.hash.selector import get_selector_from_name
67
+ from ccxt.static_dependencies.starknet.hash.utils import message_signature, private_to_stark_key
68
+ from ccxt.static_dependencies.starknet.utils.typed_data import TypedData as TypedDataDataclass
61
69
 
62
70
  # -----------------------------------------------------------------------------
63
71
 
@@ -80,6 +88,14 @@ import gzip
80
88
  import hashlib
81
89
  import hmac
82
90
  import io
91
+
92
+ # load orjson if available, otherwise default to json
93
+ orjson = None
94
+ try:
95
+ import orjson as orjson
96
+ except ImportError:
97
+ pass
98
+
83
99
  import json
84
100
  import math
85
101
  import random
@@ -103,11 +119,20 @@ from ccxt.base.types import Int
103
119
 
104
120
  # -----------------------------------------------------------------------------
105
121
 
122
+ class SafeJSONEncoder(json.JSONEncoder):
123
+ def default(self, obj):
124
+ if isinstance(obj, Exception):
125
+ return {"name": obj.__class__.__name__}
126
+ try:
127
+ return super().default(obj)
128
+ except TypeError:
129
+ return f"TypeError: Object of type {type(obj).__name__} is not JSON serializable"
106
130
 
107
131
  class Exchange(object):
108
132
  """Base exchange class"""
109
133
  id = None
110
134
  name = None
135
+ countries = None
111
136
  version = None
112
137
  certified = False # if certified by the CCXT dev team
113
138
  pro = False # if it is integrated with CCXT Pro for WebSocket support
@@ -123,6 +148,8 @@ class Exchange(object):
123
148
  aiohttp_trust_env = False
124
149
  requests_trust_env = False
125
150
  session = None # Session () by default
151
+ tcp_connector = None # aiohttp.TCPConnector
152
+ aiohttp_socks_connector = None
126
153
  socks_proxy_sessions = None
127
154
  verify = True # SSL verification
128
155
  validateServerSsl = True
@@ -132,12 +159,18 @@ class Exchange(object):
132
159
  markets = None
133
160
  symbols = None
134
161
  codes = None
135
- timeframes = None
162
+ timeframes = {}
163
+
136
164
  fees = {
137
165
  'trading': {
138
- 'percentage': True, # subclasses should rarely have to redefine this
166
+ 'tierBased': None,
167
+ 'percentage': None,
168
+ 'taker': None,
169
+ 'maker': None,
139
170
  },
140
171
  'funding': {
172
+ 'tierBased': None,
173
+ 'percentage': None,
141
174
  'withdraw': {},
142
175
  'deposit': {},
143
176
  },
@@ -198,12 +231,14 @@ class Exchange(object):
198
231
  secret = ''
199
232
  password = ''
200
233
  uid = ''
234
+ accountId = None
201
235
  privateKey = '' # a "0x"-prefixed hexstring private key for a wallet
202
236
  walletAddress = '' # the wallet address "0x"-prefixed hexstring
203
237
  token = '' # reserved for HTTP auth in some cases
204
238
  twofa = None
205
239
  markets_by_id = None
206
240
  currencies_by_id = None
241
+
207
242
  precision = None
208
243
  exceptions = None
209
244
  limits = {
@@ -224,6 +259,7 @@ class Exchange(object):
224
259
  'max': None,
225
260
  },
226
261
  }
262
+
227
263
  httpExceptions = {
228
264
  '422': ExchangeError,
229
265
  '418': DDoSProtection,
@@ -252,26 +288,33 @@ class Exchange(object):
252
288
  '511': AuthenticationError,
253
289
  }
254
290
  balance = None
291
+ liquidations = None
255
292
  orderbooks = None
256
293
  orders = None
257
294
  triggerOrders = None
295
+ myLiquidations = None
258
296
  myTrades = None
259
297
  trades = None
260
298
  transactions = None
261
299
  ohlcvs = None
262
300
  tickers = None
301
+ fundingRates = None
263
302
  bidsasks = None
264
303
  base_currencies = None
265
304
  quote_currencies = None
266
305
  currencies = None
267
306
  options = None # Python does not allow to define properties in run-time with setattr
307
+ isSandboxModeEnabled = False
268
308
  accounts = None
269
309
  positions = None
270
310
 
311
+ status = None
312
+
271
313
  requiredCredentials = {
272
314
  'apiKey': True,
273
315
  'secret': True,
274
316
  'uid': False,
317
+ 'accountId': False,
275
318
  'login': False,
276
319
  'password': False,
277
320
  'twofa': False, # 2-factor authentication (one-time password key)
@@ -281,107 +324,8 @@ class Exchange(object):
281
324
  }
282
325
 
283
326
  # API method metainfo
284
- has = {
285
- 'publicAPI': True,
286
- 'privateAPI': True,
287
- 'CORS': None,
288
- 'spot': None,
289
- 'margin': None,
290
- 'swap': None,
291
- 'future': None,
292
- 'option': None,
293
- 'addMargin': None,
294
- 'cancelAllOrders': None,
295
- 'cancelOrder': True,
296
- 'cancelOrders': None,
297
- 'createDepositAddress': None,
298
- 'createLimitOrder': True,
299
- 'createMarketOrder': True,
300
- 'createOrder': True,
301
- 'createPostOnlyOrder': None,
302
- 'createReduceOnlyOrder': None,
303
- 'createStopOrder': None,
304
- 'createStopLimitOrder': None,
305
- 'createStopMarketOrder': None,
306
- 'editOrder': 'emulated',
307
- 'fetchAccounts': None,
308
- 'fetchBalance': True,
309
- 'fetchBidsAsks': None,
310
- 'fetchBorrowInterest': None,
311
- 'fetchBorrowRate': None,
312
- 'fetchBorrowRateHistory': None,
313
- 'fetchBorrowRatesPerSymbol': None,
314
- 'fetchBorrowRates': None,
315
- 'fetchCanceledOrders': None,
316
- 'fetchClosedOrder': None,
317
- 'fetchClosedOrders': None,
318
- 'fetchCurrencies': 'emulated',
319
- 'fetchDeposit': None,
320
- 'fetchDepositAddress': None,
321
- 'fetchDepositAddresses': None,
322
- 'fetchDepositAddressesByNetwork': None,
323
- 'fetchDeposits': None,
324
- 'fetchFundingFee': None,
325
- 'fetchFundingFees': None,
326
- 'fetchFundingHistory': None,
327
- 'fetchFundingRate': None,
328
- 'fetchFundingRateHistory': None,
329
- 'fetchFundingRates': None,
330
- 'fetchIndexOHLCV': None,
331
- 'fetchLastPrices': None,
332
- 'fetchL2OrderBook': True,
333
- 'fetchLedger': None,
334
- 'fetchLedgerEntry': None,
335
- 'fetchLeverageTiers': None,
336
- 'fetchMarketLeverageTiers': None,
337
- 'fetchMarkets': True,
338
- 'fetchMarkOHLCV': None,
339
- 'fetchMyTrades': None,
340
- 'fetchOHLCV': None,
341
- 'fetchOpenOrder': None,
342
- 'fetchOpenOrders': None,
343
- 'fetchOrder': None,
344
- 'fetchOrderBook': True,
345
- 'fetchOrderBooks': None,
346
- 'fetchOrders': None,
347
- 'fetchOrderTrades': None,
348
- 'fetchPermissions': None,
349
- 'fetchPosition': None,
350
- 'fetchPositions': None,
351
- 'fetchPositionsRisk': None,
352
- 'fetchPremiumIndexOHLCV': None,
353
- 'fetchStatus': None,
354
- 'fetchTicker': True,
355
- 'fetchTickers': None,
356
- 'fetchTime': None,
357
- 'fetchTrades': True,
358
- 'fetchTradingFee': None,
359
- 'fetchTradingFees': None,
360
- 'fetchTradingLimits': None,
361
- 'fetchTransactions': None,
362
- 'fetchTransfers': None,
363
- 'fetchWithdrawal': None,
364
- 'fetchWithdrawals': None,
365
- 'reduceMargin': None,
366
- 'setLeverage': None,
367
- 'setMargin': None,
368
- 'setMarginMode': None,
369
- 'setPositionMode': None,
370
- 'signIn': None,
371
- 'transfer': None,
372
- 'withdraw': None,
373
- 'watchOrderBook': None,
374
- 'watchOrders': None,
375
- 'watchMyTrades': None,
376
- 'watchTickers': None,
377
- 'watchTicker': None,
378
- 'watchTrades': None,
379
- 'watchTradesForSymbols': None,
380
- 'watchOrderBookForSymbols': None,
381
- 'watchOHLCVForSymbols': None,
382
- 'watchBalance': None,
383
- 'watchOHLCV': None,
384
- }
327
+ has = {}
328
+ features = {}
385
329
  precisionMode = DECIMAL_PLACES
386
330
  paddingMode = NO_PADDING
387
331
  minFundingAddressLength = 1 # used in check_address
@@ -399,7 +343,7 @@ class Exchange(object):
399
343
  rateLimitMaxTokens = 16
400
344
  rateLimitUpdateTime = 0
401
345
  enableLastHttpResponse = True
402
- enableLastJsonResponse = True
346
+ enableLastJsonResponse = False
403
347
  enableLastResponseHeaders = True
404
348
  last_http_response = None
405
349
  last_json_response = None
@@ -431,11 +375,14 @@ class Exchange(object):
431
375
  self.headers = dict() if self.headers is None else self.headers
432
376
  self.balance = dict() if self.balance is None else self.balance
433
377
  self.orderbooks = dict() if self.orderbooks is None else self.orderbooks
378
+ self.fundingRates = dict() if self.fundingRates is None else self.fundingRates
434
379
  self.tickers = dict() if self.tickers is None else self.tickers
435
380
  self.bidsasks = dict() if self.bidsasks is None else self.bidsasks
436
381
  self.trades = dict() if self.trades is None else self.trades
437
382
  self.transactions = dict() if self.transactions is None else self.transactions
438
383
  self.ohlcvs = dict() if self.ohlcvs is None else self.ohlcvs
384
+ self.liquidations = dict() if self.liquidations is None else self.liquidations
385
+ self.myLiquidations = dict() if self.myLiquidations is None else self.myLiquidations
439
386
  self.currencies = dict() if self.currencies is None else self.currencies
440
387
  self.options = self.get_default_options() if self.options is None else self.options # Python does not allow to define properties in run-time with setattr
441
388
  self.decimal_to_precision = decimal_to_precision
@@ -462,6 +409,10 @@ class Exchange(object):
462
409
 
463
410
  self.after_construct()
464
411
 
412
+ is_sandbox = self.safe_bool_2(self.options, 'sandbox', 'testnet', False)
413
+ if is_sandbox:
414
+ self.set_sandbox_mode(is_sandbox)
415
+
465
416
  # convert all properties from underscore notation foo_bar to camelcase notation fooBar
466
417
  cls = type(self)
467
418
  for name in dir(self):
@@ -505,9 +456,6 @@ class Exchange(object):
505
456
  def __str__(self):
506
457
  return self.name
507
458
 
508
- def describe(self):
509
- return {}
510
-
511
459
  def throttle(self, cost=None):
512
460
  now = float(self.milliseconds())
513
461
  elapsed = now - self.lastRestRequestTimestamp
@@ -548,9 +496,11 @@ class Exchange(object):
548
496
  return response_body.strip()
549
497
 
550
498
  def on_json_response(self, response_body):
551
- if self.quoteJsonNumbers:
499
+ if self.quoteJsonNumbers and orjson is None:
552
500
  return json.loads(response_body, parse_float=str, parse_int=str)
553
501
  else:
502
+ if orjson:
503
+ return orjson.loads(response_body)
554
504
  return json.loads(response_body)
555
505
 
556
506
  def fetch(self, url, method='GET', headers=None, body=None):
@@ -886,11 +836,13 @@ class Exchange(object):
886
836
 
887
837
  @staticmethod
888
838
  def get_object_value_from_key_list(dictionary_or_list, key_list):
839
+ isDataArray = isinstance(dictionary_or_list, list)
840
+ isDataDict = isinstance(dictionary_or_list, dict)
889
841
  for key in key_list:
890
- if isinstance(key, str):
842
+ if isDataDict:
891
843
  if key in dictionary_or_list and dictionary_or_list[key] is not None and dictionary_or_list[key] != '':
892
844
  return dictionary_or_list[key]
893
- elif key is not None:
845
+ elif isDataArray and not isinstance(key, str):
894
846
  if (key < len(dictionary_or_list)) and (dictionary_or_list[key] is not None) and (dictionary_or_list[key] != ''):
895
847
  return dictionary_or_list[key]
896
848
  return None
@@ -1080,7 +1032,7 @@ class Exchange(object):
1080
1032
  if isinstance(params, dict):
1081
1033
  for key in params:
1082
1034
  _encode_params(params[key], key)
1083
- return _urlencode.urlencode(result)
1035
+ return _urlencode.urlencode(result, quote_via=_urlencode.quote)
1084
1036
 
1085
1037
  @staticmethod
1086
1038
  def rawencode(params={}):
@@ -1171,7 +1123,7 @@ class Exchange(object):
1171
1123
  return None
1172
1124
 
1173
1125
  try:
1174
- utc = datetime.datetime.utcfromtimestamp(timestamp // 1000)
1126
+ utc = datetime.datetime.fromtimestamp(timestamp // 1000, datetime.timezone.utc)
1175
1127
  return utc.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-6] + "{:03d}".format(int(timestamp) % 1000) + 'Z'
1176
1128
  except (TypeError, OverflowError, OSError):
1177
1129
  return None
@@ -1187,13 +1139,13 @@ class Exchange(object):
1187
1139
 
1188
1140
  @staticmethod
1189
1141
  def dmy(timestamp, infix='-'):
1190
- utc_datetime = datetime.datetime.utcfromtimestamp(int(round(timestamp / 1000)))
1142
+ utc_datetime = datetime.datetime.fromtimestamp(int(round(timestamp / 1000)), datetime.timezone.utc)
1191
1143
  return utc_datetime.strftime('%m' + infix + '%d' + infix + '%Y')
1192
1144
 
1193
1145
  @staticmethod
1194
1146
  def ymd(timestamp, infix='-', fullYear=True):
1195
1147
  year_format = '%Y' if fullYear else '%y'
1196
- utc_datetime = datetime.datetime.utcfromtimestamp(int(round(timestamp / 1000)))
1148
+ utc_datetime = datetime.datetime.fromtimestamp(int(round(timestamp / 1000)), datetime.timezone.utc)
1197
1149
  return utc_datetime.strftime(year_format + infix + '%m' + infix + '%d')
1198
1150
 
1199
1151
  @staticmethod
@@ -1206,7 +1158,7 @@ class Exchange(object):
1206
1158
 
1207
1159
  @staticmethod
1208
1160
  def ymdhms(timestamp, infix=' '):
1209
- utc_datetime = datetime.datetime.utcfromtimestamp(int(round(timestamp / 1000)))
1161
+ utc_datetime = datetime.datetime.fromtimestamp(int(round(timestamp / 1000)), datetime.timezone.utc)
1210
1162
  return utc_datetime.strftime('%Y-%m-%d' + infix + '%H:%M:%S')
1211
1163
 
1212
1164
  @staticmethod
@@ -1217,7 +1169,7 @@ class Exchange(object):
1217
1169
  return None
1218
1170
  if 'GMT' in timestamp:
1219
1171
  try:
1220
- string = ''.join([str(value) for value in parsedate(timestamp)[:6]]) + '.000Z'
1172
+ string = ''.join([str(value).zfill(2) for value in parsedate(timestamp)[:6]]) + '.000Z'
1221
1173
  dt = datetime.datetime.strptime(string, "%Y%m%d%H%M%S.%fZ")
1222
1174
  return calendar.timegm(dt.utctimetuple()) * 1000
1223
1175
  except (TypeError, OverflowError, OSError):
@@ -1296,7 +1248,7 @@ class Exchange(object):
1296
1248
  return result
1297
1249
 
1298
1250
  @staticmethod
1299
- def base64urlencode(s):
1251
+ def urlencode_base64(s):
1300
1252
  return Exchange.decode(base64.urlsafe_b64encode(s)).replace('=', '')
1301
1253
 
1302
1254
  @staticmethod
@@ -1316,25 +1268,36 @@ class Exchange(object):
1316
1268
  return Exchange.decode(base64.b64decode(s))
1317
1269
 
1318
1270
  @staticmethod
1319
- def jwt(request, secret, algorithm='sha256', is_rsa=False):
1271
+ def jwt(request, secret, algorithm='sha256', is_rsa=False, opts={}):
1320
1272
  algos = {
1321
1273
  'sha256': hashlib.sha256,
1322
1274
  'sha384': hashlib.sha384,
1323
1275
  'sha512': hashlib.sha512,
1324
1276
  }
1325
1277
  alg = ('RS' if is_rsa else 'HS') + algorithm[3:]
1326
- header = Exchange.encode(Exchange.json({
1278
+ if 'alg' in opts and opts['alg'] is not None:
1279
+ alg = opts['alg']
1280
+ header_opts = {
1327
1281
  'alg': alg,
1328
1282
  'typ': 'JWT',
1329
- }))
1330
- encoded_header = Exchange.base64urlencode(header)
1331
- encoded_data = Exchange.base64urlencode(Exchange.encode(Exchange.json(request)))
1283
+ }
1284
+ if 'kid' in opts and opts['kid'] is not None:
1285
+ header_opts['kid'] = opts['kid']
1286
+ if 'nonce' in opts and opts['nonce'] is not None:
1287
+ header_opts['nonce'] = opts['nonce']
1288
+ header = Exchange.encode(Exchange.json(header_opts))
1289
+ encoded_header = Exchange.urlencode_base64(header)
1290
+ encoded_data = Exchange.urlencode_base64(Exchange.encode(Exchange.json(request)))
1332
1291
  token = encoded_header + '.' + encoded_data
1333
- if is_rsa:
1292
+ algoType = alg[0:2]
1293
+ if is_rsa or algoType == 'RS':
1334
1294
  signature = Exchange.base64_to_binary(Exchange.rsa(token, Exchange.decode(secret), algorithm))
1295
+ elif algoType == 'ES':
1296
+ rawSignature = Exchange.ecdsa(token, secret, 'p256', algorithm)
1297
+ signature = Exchange.base16_to_binary(rawSignature['r'].rjust(64, "0") + rawSignature['s'].rjust(64, "0"))
1335
1298
  else:
1336
1299
  signature = Exchange.hmac(Exchange.encode(token), secret, algos[algorithm], 'binary')
1337
- return token + '.' + Exchange.base64urlencode(signature)
1300
+ return token + '.' + Exchange.urlencode_base64(signature)
1338
1301
 
1339
1302
  @staticmethod
1340
1303
  def rsa(request, secret, alg='sha256'):
@@ -1356,6 +1319,57 @@ class Exchange(object):
1356
1319
  encodedData = account.messages.encode_typed_data(domain, messageTypes, message)
1357
1320
  return Exchange.binary_concat(b"\x19\x01", encodedData.header, encodedData.body)
1358
1321
 
1322
+ @staticmethod
1323
+ def retrieve_stark_account (signature, accountClassHash, accountProxyClassHash):
1324
+ privateKey = get_private_key_from_eth_signature(signature)
1325
+ publicKey = private_to_stark_key(privateKey)
1326
+ calldata = [
1327
+ int(accountClassHash, 16),
1328
+ get_selector_from_name("initialize"),
1329
+ 2,
1330
+ publicKey,
1331
+ 0,
1332
+ ]
1333
+
1334
+ address = compute_address(
1335
+ class_hash=int(accountProxyClassHash, 16),
1336
+ constructor_calldata=calldata,
1337
+ salt=publicKey,
1338
+ )
1339
+ return {
1340
+ 'privateKey': privateKey,
1341
+ 'publicKey': publicKey,
1342
+ 'address': hex(address)
1343
+ }
1344
+
1345
+ @staticmethod
1346
+ def starknet_encode_structured_data (domain, messageTypes, messageData, address):
1347
+ types = list(messageTypes.keys())
1348
+ if len(types) > 1:
1349
+ raise NotSupported('starknetEncodeStructuredData only support single type')
1350
+
1351
+ request = {
1352
+ 'domain': domain,
1353
+ 'primaryType': types[0],
1354
+ 'types': Exchange.extend({
1355
+ 'StarkNetDomain': [
1356
+ {'name': "name", 'type': "felt"},
1357
+ {'name': "chainId", 'type': "felt"},
1358
+ {'name': "version", 'type': "felt"},
1359
+ ],
1360
+ }, messageTypes),
1361
+ 'message': messageData,
1362
+ }
1363
+ typedDataClass = TypedDataDataclass.from_dict(request)
1364
+ msgHash = typedDataClass.message_hash(int(address, 16))
1365
+ return msgHash
1366
+
1367
+ @staticmethod
1368
+ def starknet_sign (hash, pri):
1369
+ # // TODO: unify to ecdsa
1370
+ r, s = message_signature(hash, pri)
1371
+ return Exchange.json([hex(r), hex(s)])
1372
+
1359
1373
  @staticmethod
1360
1374
  def packb(o):
1361
1375
  return packb(o)
@@ -1364,6 +1378,10 @@ class Exchange(object):
1364
1378
  def int_to_base16(num):
1365
1379
  return "%0.2X" % num
1366
1380
 
1381
+ @staticmethod
1382
+ def random_bytes(length):
1383
+ return format(random.getrandbits(length * 8), 'x')
1384
+
1367
1385
  @staticmethod
1368
1386
  def ecdsa(request, secret, algorithm='p256', hash=None, fixed_length=False):
1369
1387
  # your welcome - frosty00
@@ -1384,7 +1402,12 @@ class Exchange(object):
1384
1402
  digest = Exchange.hash(encoded_request, hash, 'binary')
1385
1403
  else:
1386
1404
  digest = base64.b16decode(encoded_request, casefold=True)
1387
- key = ecdsa.SigningKey.from_string(base64.b16decode(Exchange.encode(secret),
1405
+ if isinstance(secret, str):
1406
+ secret = Exchange.encode(secret)
1407
+ if secret.find(b'-----BEGIN EC PRIVATE KEY-----') > -1:
1408
+ key = ecdsa.SigningKey.from_pem(secret, hash_function)
1409
+ else:
1410
+ key = ecdsa.SigningKey.from_string(base64.b16decode(secret,
1388
1411
  casefold=True), curve=curve_info[0])
1389
1412
  r_binary, s_binary, v = key.sign_digest_deterministic(digest, hashfunc=hash_function,
1390
1413
  sigencode=ecdsa.util.sigencode_strings_canonize)
@@ -1407,7 +1430,9 @@ class Exchange(object):
1407
1430
 
1408
1431
  @staticmethod
1409
1432
  def eddsa(request, secret, curve='ed25519'):
1410
- private_key = load_pem_private_key(Exchange.encode(secret), None)
1433
+ if isinstance(secret, str):
1434
+ secret = Exchange.encode(secret)
1435
+ private_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret) if len(secret) == 32 else load_pem_private_key(secret, None)
1411
1436
  return Exchange.binary_to_base64(private_key.sign(request))
1412
1437
 
1413
1438
  @staticmethod
@@ -1420,7 +1445,9 @@ class Exchange(object):
1420
1445
 
1421
1446
  @staticmethod
1422
1447
  def json(data, params=None):
1423
- return json.dumps(data, separators=(',', ':'))
1448
+ if orjson:
1449
+ return orjson.dumps(data).decode('utf-8')
1450
+ return json.dumps(data, separators=(',', ':'), cls=SafeJSONEncoder)
1424
1451
 
1425
1452
  @staticmethod
1426
1453
  def is_json_encoded_object(input):
@@ -1465,18 +1492,10 @@ class Exchange(object):
1465
1492
  return error
1466
1493
  return result
1467
1494
 
1468
- def check_address(self, address):
1469
- """Checks an address is not the same character repeated or an empty sequence"""
1470
- if address is None:
1471
- raise InvalidAddress(self.id + ' address is None')
1472
- if all(letter == address[0] for letter in address) or len(address) < self.minFundingAddressLength or ' ' in address:
1473
- raise InvalidAddress(self.id + ' address is invalid or has less than ' + str(self.minFundingAddressLength) + ' characters: "' + str(address) + '"')
1474
- return address
1475
-
1476
1495
  def precision_from_string(self, str):
1477
1496
  # support string formats like '1e-4'
1478
- if 'e' in str:
1479
- numStr = re.sub(r'\de', '', str)
1497
+ if 'e' in str or 'E' in str:
1498
+ numStr = re.sub(r'\d\.?\d*[eE]', '', str)
1480
1499
  return int(numStr) * -1
1481
1500
  # support integer formats (without dot) like '1', '10' etc [Note: bug in decimalToPrecision, so this should not be used atm]
1482
1501
  # if not ('.' in str):
@@ -1497,13 +1516,6 @@ class Exchange(object):
1497
1516
  markets = self.fetch_markets(params)
1498
1517
  return self.set_markets(markets, currencies)
1499
1518
 
1500
- def load_fees(self, reload=False):
1501
- if not reload:
1502
- if self.loaded_fees != Exchange.loaded_fees:
1503
- return self.loaded_fees
1504
- self.loaded_fees = self.deep_extend(self.loaded_fees, self.fetch_fees())
1505
- return self.loaded_fees
1506
-
1507
1519
  def fetch_markets(self, params={}):
1508
1520
  # markets are returned as a list
1509
1521
  # currencies are returned as a dict
@@ -1659,11 +1671,14 @@ class Exchange(object):
1659
1671
  return default
1660
1672
 
1661
1673
  def omit_zero(self, string_number):
1662
- if string_number is None or string_number == '':
1663
- return None
1664
- if float(string_number) == 0:
1665
- return None
1666
- return string_number
1674
+ try:
1675
+ if string_number is None or string_number == '':
1676
+ return None
1677
+ if float(string_number) == 0:
1678
+ return None
1679
+ return string_number
1680
+ except Exception:
1681
+ return string_number
1667
1682
 
1668
1683
  def check_order_arguments(self, market, type, side, amount, price, params):
1669
1684
  if price is None:
@@ -1703,10 +1718,10 @@ class Exchange(object):
1703
1718
  def string_to_chars_array(self, value):
1704
1719
  return list(value)
1705
1720
 
1706
- def valueIsDefined(self, value):
1721
+ def value_is_defined(self, value):
1707
1722
  return value is not None
1708
1723
 
1709
- def arraySlice(self, array, first, second=None):
1724
+ def array_slice(self, array, first, second=None):
1710
1725
  return array[first:second] if second else array[first:]
1711
1726
 
1712
1727
  def get_property(self, obj, property, defaultValue=None):
@@ -1728,6 +1743,15 @@ class Exchange(object):
1728
1743
  modifiedContent = modifiedContent.replace('}"', '}')
1729
1744
  return modifiedContent
1730
1745
 
1746
+ def extend_exchange_options(self, newOptions):
1747
+ self.options = self.extend(self.options, newOptions)
1748
+
1749
+ def create_safe_dictionary(self):
1750
+ return {}
1751
+
1752
+ def rand_number(self, size):
1753
+ return int(''.join([str(random.randint(0, 9)) for _ in range(size)]))
1754
+
1731
1755
  # ########################################################################
1732
1756
  # ########################################################################
1733
1757
  # ########################################################################
@@ -1767,9 +1791,328 @@ class Exchange(object):
1767
1791
 
1768
1792
  # METHODS BELOW THIS LINE ARE TRANSPILED FROM JAVASCRIPT TO PYTHON AND PHP
1769
1793
 
1794
+ def describe(self):
1795
+ return {
1796
+ 'id': None,
1797
+ 'name': None,
1798
+ 'countries': None,
1799
+ 'enableRateLimit': True,
1800
+ 'rateLimit': 2000, # milliseconds = seconds * 1000
1801
+ 'timeout': self.timeout, # milliseconds = seconds * 1000
1802
+ 'certified': False, # if certified by the CCXT dev team
1803
+ 'pro': False, # if it is integrated with CCXT Pro for WebSocket support
1804
+ 'alias': False, # whether self exchange is an alias to another exchange
1805
+ 'dex': False,
1806
+ 'has': {
1807
+ 'publicAPI': True,
1808
+ 'privateAPI': True,
1809
+ 'CORS': None,
1810
+ 'sandbox': None,
1811
+ 'spot': None,
1812
+ 'margin': None,
1813
+ 'swap': None,
1814
+ 'future': None,
1815
+ 'option': None,
1816
+ 'addMargin': None,
1817
+ 'borrowCrossMargin': None,
1818
+ 'borrowIsolatedMargin': None,
1819
+ 'borrowMargin': None,
1820
+ 'cancelAllOrders': None,
1821
+ 'cancelAllOrdersWs': None,
1822
+ 'cancelOrder': True,
1823
+ 'cancelOrderWs': None,
1824
+ 'cancelOrders': None,
1825
+ 'cancelOrdersWs': None,
1826
+ 'closeAllPositions': None,
1827
+ 'closePosition': None,
1828
+ 'createDepositAddress': None,
1829
+ 'createLimitBuyOrder': None,
1830
+ 'createLimitBuyOrderWs': None,
1831
+ 'createLimitOrder': True,
1832
+ 'createLimitOrderWs': None,
1833
+ 'createLimitSellOrder': None,
1834
+ 'createLimitSellOrderWs': None,
1835
+ 'createMarketBuyOrder': None,
1836
+ 'createMarketBuyOrderWs': None,
1837
+ 'createMarketBuyOrderWithCost': None,
1838
+ 'createMarketBuyOrderWithCostWs': None,
1839
+ 'createMarketOrder': True,
1840
+ 'createMarketOrderWs': True,
1841
+ 'createMarketOrderWithCost': None,
1842
+ 'createMarketOrderWithCostWs': None,
1843
+ 'createMarketSellOrder': None,
1844
+ 'createMarketSellOrderWs': None,
1845
+ 'createMarketSellOrderWithCost': None,
1846
+ 'createMarketSellOrderWithCostWs': None,
1847
+ 'createOrder': True,
1848
+ 'createOrderWs': None,
1849
+ 'createOrders': None,
1850
+ 'createOrderWithTakeProfitAndStopLoss': None,
1851
+ 'createOrderWithTakeProfitAndStopLossWs': None,
1852
+ 'createPostOnlyOrder': None,
1853
+ 'createPostOnlyOrderWs': None,
1854
+ 'createReduceOnlyOrder': None,
1855
+ 'createReduceOnlyOrderWs': None,
1856
+ 'createStopLimitOrder': None,
1857
+ 'createStopLimitOrderWs': None,
1858
+ 'createStopLossOrder': None,
1859
+ 'createStopLossOrderWs': None,
1860
+ 'createStopMarketOrder': None,
1861
+ 'createStopMarketOrderWs': None,
1862
+ 'createStopOrder': None,
1863
+ 'createStopOrderWs': None,
1864
+ 'createTakeProfitOrder': None,
1865
+ 'createTakeProfitOrderWs': None,
1866
+ 'createTrailingAmountOrder': None,
1867
+ 'createTrailingAmountOrderWs': None,
1868
+ 'createTrailingPercentOrder': None,
1869
+ 'createTrailingPercentOrderWs': None,
1870
+ 'createTriggerOrder': None,
1871
+ 'createTriggerOrderWs': None,
1872
+ 'deposit': None,
1873
+ 'editOrder': 'emulated',
1874
+ 'editOrderWs': None,
1875
+ 'fetchAccounts': None,
1876
+ 'fetchBalance': True,
1877
+ 'fetchBalanceWs': None,
1878
+ 'fetchBidsAsks': None,
1879
+ 'fetchBorrowInterest': None,
1880
+ 'fetchBorrowRate': None,
1881
+ 'fetchBorrowRateHistories': None,
1882
+ 'fetchBorrowRateHistory': None,
1883
+ 'fetchBorrowRates': None,
1884
+ 'fetchBorrowRatesPerSymbol': None,
1885
+ 'fetchCanceledAndClosedOrders': None,
1886
+ 'fetchCanceledOrders': None,
1887
+ 'fetchClosedOrder': None,
1888
+ 'fetchClosedOrders': None,
1889
+ 'fetchClosedOrdersWs': None,
1890
+ 'fetchConvertCurrencies': None,
1891
+ 'fetchConvertQuote': None,
1892
+ 'fetchConvertTrade': None,
1893
+ 'fetchConvertTradeHistory': None,
1894
+ 'fetchCrossBorrowRate': None,
1895
+ 'fetchCrossBorrowRates': None,
1896
+ 'fetchCurrencies': 'emulated',
1897
+ 'fetchCurrenciesWs': 'emulated',
1898
+ 'fetchDeposit': None,
1899
+ 'fetchDepositAddress': None,
1900
+ 'fetchDepositAddresses': None,
1901
+ 'fetchDepositAddressesByNetwork': None,
1902
+ 'fetchDeposits': None,
1903
+ 'fetchDepositsWithdrawals': None,
1904
+ 'fetchDepositsWs': None,
1905
+ 'fetchDepositWithdrawFee': None,
1906
+ 'fetchDepositWithdrawFees': None,
1907
+ 'fetchFundingHistory': None,
1908
+ 'fetchFundingRate': None,
1909
+ 'fetchFundingRateHistory': None,
1910
+ 'fetchFundingInterval': None,
1911
+ 'fetchFundingIntervals': None,
1912
+ 'fetchFundingRates': None,
1913
+ 'fetchGreeks': None,
1914
+ 'fetchIndexOHLCV': None,
1915
+ 'fetchIsolatedBorrowRate': None,
1916
+ 'fetchIsolatedBorrowRates': None,
1917
+ 'fetchMarginAdjustmentHistory': None,
1918
+ 'fetchIsolatedPositions': None,
1919
+ 'fetchL2OrderBook': True,
1920
+ 'fetchL3OrderBook': None,
1921
+ 'fetchLastPrices': None,
1922
+ 'fetchLedger': None,
1923
+ 'fetchLedgerEntry': None,
1924
+ 'fetchLeverage': None,
1925
+ 'fetchLeverages': None,
1926
+ 'fetchLeverageTiers': None,
1927
+ 'fetchLiquidations': None,
1928
+ 'fetchLongShortRatio': None,
1929
+ 'fetchLongShortRatioHistory': None,
1930
+ 'fetchMarginMode': None,
1931
+ 'fetchMarginModes': None,
1932
+ 'fetchMarketLeverageTiers': None,
1933
+ 'fetchMarkets': True,
1934
+ 'fetchMarketsWs': None,
1935
+ 'fetchMarkOHLCV': None,
1936
+ 'fetchMyLiquidations': None,
1937
+ 'fetchMySettlementHistory': None,
1938
+ 'fetchMyTrades': None,
1939
+ 'fetchMyTradesWs': None,
1940
+ 'fetchOHLCV': None,
1941
+ 'fetchOHLCVWs': None,
1942
+ 'fetchOpenInterest': None,
1943
+ 'fetchOpenInterests': None,
1944
+ 'fetchOpenInterestHistory': None,
1945
+ 'fetchOpenOrder': None,
1946
+ 'fetchOpenOrders': None,
1947
+ 'fetchOpenOrdersWs': None,
1948
+ 'fetchOption': None,
1949
+ 'fetchOptionChain': None,
1950
+ 'fetchOrder': None,
1951
+ 'fetchOrderBook': True,
1952
+ 'fetchOrderBooks': None,
1953
+ 'fetchOrderBookWs': None,
1954
+ 'fetchOrders': None,
1955
+ 'fetchOrdersByStatus': None,
1956
+ 'fetchOrdersWs': None,
1957
+ 'fetchOrderTrades': None,
1958
+ 'fetchOrderWs': None,
1959
+ 'fetchPosition': None,
1960
+ 'fetchPositionHistory': None,
1961
+ 'fetchPositionsHistory': None,
1962
+ 'fetchPositionWs': None,
1963
+ 'fetchPositionMode': None,
1964
+ 'fetchPositions': None,
1965
+ 'fetchPositionsWs': None,
1966
+ 'fetchPositionsForSymbol': None,
1967
+ 'fetchPositionsForSymbolWs': None,
1968
+ 'fetchPositionsRisk': None,
1969
+ 'fetchPremiumIndexOHLCV': None,
1970
+ 'fetchSettlementHistory': None,
1971
+ 'fetchStatus': None,
1972
+ 'fetchTicker': True,
1973
+ 'fetchTickerWs': None,
1974
+ 'fetchTickers': None,
1975
+ 'fetchMarkPrices': None,
1976
+ 'fetchTickersWs': None,
1977
+ 'fetchTime': None,
1978
+ 'fetchTrades': True,
1979
+ 'fetchTradesWs': None,
1980
+ 'fetchTradingFee': None,
1981
+ 'fetchTradingFees': None,
1982
+ 'fetchTradingFeesWs': None,
1983
+ 'fetchTradingLimits': None,
1984
+ 'fetchTransactionFee': None,
1985
+ 'fetchTransactionFees': None,
1986
+ 'fetchTransactions': None,
1987
+ 'fetchTransfer': None,
1988
+ 'fetchTransfers': None,
1989
+ 'fetchUnderlyingAssets': None,
1990
+ 'fetchVolatilityHistory': None,
1991
+ 'fetchWithdrawAddresses': None,
1992
+ 'fetchWithdrawal': None,
1993
+ 'fetchWithdrawals': None,
1994
+ 'fetchWithdrawalsWs': None,
1995
+ 'fetchWithdrawalWhitelist': None,
1996
+ 'reduceMargin': None,
1997
+ 'repayCrossMargin': None,
1998
+ 'repayIsolatedMargin': None,
1999
+ 'setLeverage': None,
2000
+ 'setMargin': None,
2001
+ 'setMarginMode': None,
2002
+ 'setPositionMode': None,
2003
+ 'signIn': None,
2004
+ 'transfer': None,
2005
+ 'watchBalance': None,
2006
+ 'watchMyTrades': None,
2007
+ 'watchOHLCV': None,
2008
+ 'watchOHLCVForSymbols': None,
2009
+ 'watchOrderBook': None,
2010
+ 'watchOrderBookForSymbols': None,
2011
+ 'watchOrders': None,
2012
+ 'watchOrdersForSymbols': None,
2013
+ 'watchPosition': None,
2014
+ 'watchPositions': None,
2015
+ 'watchStatus': None,
2016
+ 'watchTicker': None,
2017
+ 'watchTickers': None,
2018
+ 'watchTrades': None,
2019
+ 'watchTradesForSymbols': None,
2020
+ 'watchLiquidations': None,
2021
+ 'watchLiquidationsForSymbols': None,
2022
+ 'watchMyLiquidations': None,
2023
+ 'watchMyLiquidationsForSymbols': None,
2024
+ 'withdraw': None,
2025
+ 'ws': None,
2026
+ },
2027
+ 'urls': {
2028
+ 'logo': None,
2029
+ 'api': None,
2030
+ 'www': None,
2031
+ 'doc': None,
2032
+ 'fees': None,
2033
+ },
2034
+ 'api': None,
2035
+ 'requiredCredentials': {
2036
+ 'apiKey': True,
2037
+ 'secret': True,
2038
+ 'uid': False,
2039
+ 'accountId': False,
2040
+ 'login': False,
2041
+ 'password': False,
2042
+ 'twofa': False, # 2-factor authentication(one-time password key)
2043
+ 'privateKey': False, # a "0x"-prefixed hexstring private key for a wallet
2044
+ 'walletAddress': False, # the wallet address "0x"-prefixed hexstring
2045
+ 'token': False, # reserved for HTTP auth in some cases
2046
+ },
2047
+ 'markets': None, # to be filled manually or by fetchMarkets
2048
+ 'currencies': {}, # to be filled manually or by fetchMarkets
2049
+ 'timeframes': None, # redefine if the exchange has.fetchOHLCV
2050
+ 'fees': {
2051
+ 'trading': {
2052
+ 'tierBased': None,
2053
+ 'percentage': None,
2054
+ 'taker': None,
2055
+ 'maker': None,
2056
+ },
2057
+ 'funding': {
2058
+ 'tierBased': None,
2059
+ 'percentage': None,
2060
+ 'withdraw': {},
2061
+ 'deposit': {},
2062
+ },
2063
+ },
2064
+ 'status': {
2065
+ 'status': 'ok',
2066
+ 'updated': None,
2067
+ 'eta': None,
2068
+ 'url': None,
2069
+ },
2070
+ 'exceptions': None,
2071
+ 'httpExceptions': {
2072
+ '422': ExchangeError,
2073
+ '418': DDoSProtection,
2074
+ '429': RateLimitExceeded,
2075
+ '404': ExchangeNotAvailable,
2076
+ '409': ExchangeNotAvailable,
2077
+ '410': ExchangeNotAvailable,
2078
+ '451': ExchangeNotAvailable,
2079
+ '500': ExchangeNotAvailable,
2080
+ '501': ExchangeNotAvailable,
2081
+ '502': ExchangeNotAvailable,
2082
+ '520': ExchangeNotAvailable,
2083
+ '521': ExchangeNotAvailable,
2084
+ '522': ExchangeNotAvailable,
2085
+ '525': ExchangeNotAvailable,
2086
+ '526': ExchangeNotAvailable,
2087
+ '400': ExchangeNotAvailable,
2088
+ '403': ExchangeNotAvailable,
2089
+ '405': ExchangeNotAvailable,
2090
+ '503': ExchangeNotAvailable,
2091
+ '530': ExchangeNotAvailable,
2092
+ '408': RequestTimeout,
2093
+ '504': RequestTimeout,
2094
+ '401': AuthenticationError,
2095
+ '407': AuthenticationError,
2096
+ '511': AuthenticationError,
2097
+ },
2098
+ 'commonCurrencies': {
2099
+ 'XBT': 'BTC',
2100
+ 'BCC': 'BCH',
2101
+ 'BCHSV': 'BSV',
2102
+ },
2103
+ 'precisionMode': TICK_SIZE,
2104
+ 'paddingMode': NO_PADDING,
2105
+ 'limits': {
2106
+ 'leverage': {'min': None, 'max': None},
2107
+ 'amount': {'min': None, 'max': None},
2108
+ 'price': {'min': None, 'max': None},
2109
+ 'cost': {'min': None, 'max': None},
2110
+ },
2111
+ }
2112
+
1770
2113
  def safe_bool_n(self, dictionaryOrList, keys: List[IndexType], defaultValue: bool = None):
1771
2114
  """
1772
- * @ignore
2115
+ @ignore
1773
2116
  safely extract boolean value from dictionary or list
1774
2117
  :returns bool | None:
1775
2118
  """
@@ -1780,7 +2123,7 @@ class Exchange(object):
1780
2123
 
1781
2124
  def safe_bool_2(self, dictionary, key1: IndexType, key2: IndexType, defaultValue: bool = None):
1782
2125
  """
1783
- * @ignore
2126
+ @ignore
1784
2127
  safely extract boolean value from dictionary or list
1785
2128
  :returns bool | None:
1786
2129
  """
@@ -1788,7 +2131,7 @@ class Exchange(object):
1788
2131
 
1789
2132
  def safe_bool(self, dictionary, key: IndexType, defaultValue: bool = None):
1790
2133
  """
1791
- * @ignore
2134
+ @ignore
1792
2135
  safely extract boolean value from dictionary or list
1793
2136
  :returns bool | None:
1794
2137
  """
@@ -1796,20 +2139,21 @@ class Exchange(object):
1796
2139
 
1797
2140
  def safe_dict_n(self, dictionaryOrList, keys: List[IndexType], defaultValue: dict = None):
1798
2141
  """
1799
- * @ignore
2142
+ @ignore
1800
2143
  safely extract a dictionary from dictionary or list
1801
2144
  :returns dict | None:
1802
2145
  """
1803
2146
  value = self.safe_value_n(dictionaryOrList, keys, defaultValue)
1804
2147
  if value is None:
1805
2148
  return defaultValue
1806
- if isinstance(value, dict):
1807
- return value
2149
+ if (isinstance(value, dict)):
2150
+ if not isinstance(value, list):
2151
+ return value
1808
2152
  return defaultValue
1809
2153
 
1810
2154
  def safe_dict(self, dictionary, key: IndexType, defaultValue: dict = None):
1811
2155
  """
1812
- * @ignore
2156
+ @ignore
1813
2157
  safely extract a dictionary from dictionary or list
1814
2158
  :returns dict | None:
1815
2159
  """
@@ -1817,7 +2161,7 @@ class Exchange(object):
1817
2161
 
1818
2162
  def safe_dict_2(self, dictionary, key1: IndexType, key2: str, defaultValue: dict = None):
1819
2163
  """
1820
- * @ignore
2164
+ @ignore
1821
2165
  safely extract a dictionary from dictionary or list
1822
2166
  :returns dict | None:
1823
2167
  """
@@ -1825,7 +2169,7 @@ class Exchange(object):
1825
2169
 
1826
2170
  def safe_list_n(self, dictionaryOrList, keys: List[IndexType], defaultValue: List[Any] = None):
1827
2171
  """
1828
- * @ignore
2172
+ @ignore
1829
2173
  safely extract an Array from dictionary or list
1830
2174
  :returns Array | None:
1831
2175
  """
@@ -1838,7 +2182,7 @@ class Exchange(object):
1838
2182
 
1839
2183
  def safe_list_2(self, dictionaryOrList, key1: IndexType, key2: str, defaultValue: List[Any] = None):
1840
2184
  """
1841
- * @ignore
2185
+ @ignore
1842
2186
  safely extract an Array from dictionary or list
1843
2187
  :returns Array | None:
1844
2188
  """
@@ -1846,7 +2190,7 @@ class Exchange(object):
1846
2190
 
1847
2191
  def safe_list(self, dictionaryOrList, key: IndexType, defaultValue: List[Any] = None):
1848
2192
  """
1849
- * @ignore
2193
+ @ignore
1850
2194
  safely extract an Array from dictionary or list
1851
2195
  :returns Array | None:
1852
2196
  """
@@ -1859,6 +2203,11 @@ class Exchange(object):
1859
2203
  def handle_delta(self, bookside, delta):
1860
2204
  raise NotSupported(self.id + ' handleDelta not supported yet')
1861
2205
 
2206
+ def handle_deltas_with_keys(self, bookSide: Any, deltas, priceKey: IndexType = 0, amountKey: IndexType = 1, countOrIdKey: IndexType = 2):
2207
+ for i in range(0, len(deltas)):
2208
+ bidAsk = self.parse_bid_ask(deltas[i], priceKey, amountKey, countOrIdKey)
2209
+ bookSide.storeArray(bidAsk)
2210
+
1862
2211
  def get_cache_index(self, orderbook, deltas):
1863
2212
  # return the first index of the cache that can be applied to the orderbook or -1 if not possible
1864
2213
  return -1
@@ -1898,7 +2247,7 @@ class Exchange(object):
1898
2247
  length = len(usedProxies)
1899
2248
  if length > 1:
1900
2249
  joinedProxyNames = ','.join(usedProxies)
1901
- raise ProxyError(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from : proxyUrl, proxy_url, proxyUrlCallback, proxy_url_callback')
2250
+ raise InvalidProxySettings(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from : proxyUrl, proxy_url, proxyUrlCallback, proxy_url_callback')
1902
2251
  return proxyUrl
1903
2252
 
1904
2253
  def check_proxy_settings(self, url: Str = None, method: Str = None, headers=None, body=None):
@@ -1907,49 +2256,43 @@ class Exchange(object):
1907
2256
  httpsProxy = None
1908
2257
  socksProxy = None
1909
2258
  # httpProxy
1910
- if self.valueIsDefined(self.httpProxy):
2259
+ isHttpProxyDefined = self.value_is_defined(self.httpProxy)
2260
+ isHttp_proxy_defined = self.value_is_defined(self.http_proxy)
2261
+ if isHttpProxyDefined or isHttp_proxy_defined:
1911
2262
  usedProxies.append('httpProxy')
1912
- httpProxy = self.httpProxy
1913
- if self.valueIsDefined(self.http_proxy):
1914
- usedProxies.append('http_proxy')
1915
- httpProxy = self.http_proxy
1916
- if self.httpProxyCallback is not None:
2263
+ httpProxy = self.httpProxy if isHttpProxyDefined else self.http_proxy
2264
+ ishttpProxyCallbackDefined = self.value_is_defined(self.httpProxyCallback)
2265
+ ishttp_proxy_callback_defined = self.value_is_defined(self.http_proxy_callback)
2266
+ if ishttpProxyCallbackDefined or ishttp_proxy_callback_defined:
1917
2267
  usedProxies.append('httpProxyCallback')
1918
- httpProxy = self.httpProxyCallback(url, method, headers, body)
1919
- if self.http_proxy_callback is not None:
1920
- usedProxies.append('http_proxy_callback')
1921
- httpProxy = self.http_proxy_callback(url, method, headers, body)
2268
+ httpProxy = self.httpProxyCallback(url, method, headers, body) if ishttpProxyCallbackDefined else self.http_proxy_callback(url, method, headers, body)
1922
2269
  # httpsProxy
1923
- if self.valueIsDefined(self.httpsProxy):
2270
+ isHttpsProxyDefined = self.value_is_defined(self.httpsProxy)
2271
+ isHttps_proxy_defined = self.value_is_defined(self.https_proxy)
2272
+ if isHttpsProxyDefined or isHttps_proxy_defined:
1924
2273
  usedProxies.append('httpsProxy')
1925
- httpsProxy = self.httpsProxy
1926
- if self.valueIsDefined(self.https_proxy):
1927
- usedProxies.append('https_proxy')
1928
- httpsProxy = self.https_proxy
1929
- if self.httpsProxyCallback is not None:
2274
+ httpsProxy = self.httpsProxy if isHttpsProxyDefined else self.https_proxy
2275
+ ishttpsProxyCallbackDefined = self.value_is_defined(self.httpsProxyCallback)
2276
+ ishttps_proxy_callback_defined = self.value_is_defined(self.https_proxy_callback)
2277
+ if ishttpsProxyCallbackDefined or ishttps_proxy_callback_defined:
1930
2278
  usedProxies.append('httpsProxyCallback')
1931
- httpsProxy = self.httpsProxyCallback(url, method, headers, body)
1932
- if self.https_proxy_callback is not None:
1933
- usedProxies.append('https_proxy_callback')
1934
- httpsProxy = self.https_proxy_callback(url, method, headers, body)
2279
+ httpsProxy = self.httpsProxyCallback(url, method, headers, body) if ishttpsProxyCallbackDefined else self.https_proxy_callback(url, method, headers, body)
1935
2280
  # socksProxy
1936
- if self.valueIsDefined(self.socksProxy):
2281
+ isSocksProxyDefined = self.value_is_defined(self.socksProxy)
2282
+ isSocks_proxy_defined = self.value_is_defined(self.socks_proxy)
2283
+ if isSocksProxyDefined or isSocks_proxy_defined:
1937
2284
  usedProxies.append('socksProxy')
1938
- socksProxy = self.socksProxy
1939
- if self.valueIsDefined(self.socks_proxy):
1940
- usedProxies.append('socks_proxy')
1941
- socksProxy = self.socks_proxy
1942
- if self.socksProxyCallback is not None:
2285
+ socksProxy = self.socksProxy if isSocksProxyDefined else self.socks_proxy
2286
+ issocksProxyCallbackDefined = self.value_is_defined(self.socksProxyCallback)
2287
+ issocks_proxy_callback_defined = self.value_is_defined(self.socks_proxy_callback)
2288
+ if issocksProxyCallbackDefined or issocks_proxy_callback_defined:
1943
2289
  usedProxies.append('socksProxyCallback')
1944
- socksProxy = self.socksProxyCallback(url, method, headers, body)
1945
- if self.socks_proxy_callback is not None:
1946
- usedProxies.append('socks_proxy_callback')
1947
- socksProxy = self.socks_proxy_callback(url, method, headers, body)
2290
+ socksProxy = self.socksProxyCallback(url, method, headers, body) if issocksProxyCallbackDefined else self.socks_proxy_callback(url, method, headers, body)
1948
2291
  # check
1949
2292
  length = len(usedProxies)
1950
2293
  if length > 1:
1951
2294
  joinedProxyNames = ','.join(usedProxies)
1952
- raise ProxyError(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from: httpProxy, httpsProxy, httpProxyCallback, httpsProxyCallback, socksProxy, socksProxyCallback')
2295
+ raise InvalidProxySettings(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from: httpProxy, httpsProxy, httpProxyCallback, httpsProxyCallback, socksProxy, socksProxyCallback')
1953
2296
  return [httpProxy, httpsProxy, socksProxy]
1954
2297
 
1955
2298
  def check_ws_proxy_settings(self):
@@ -1958,36 +2301,43 @@ class Exchange(object):
1958
2301
  wssProxy = None
1959
2302
  wsSocksProxy = None
1960
2303
  # ws proxy
1961
- if self.valueIsDefined(self.wsProxy):
2304
+ isWsProxyDefined = self.value_is_defined(self.wsProxy)
2305
+ is_ws_proxy_defined = self.value_is_defined(self.ws_proxy)
2306
+ if isWsProxyDefined or is_ws_proxy_defined:
1962
2307
  usedProxies.append('wsProxy')
1963
- wsProxy = self.wsProxy
1964
- if self.valueIsDefined(self.ws_proxy):
1965
- usedProxies.append('ws_proxy')
1966
- wsProxy = self.ws_proxy
2308
+ wsProxy = self.wsProxy if (isWsProxyDefined) else self.ws_proxy
1967
2309
  # wss proxy
1968
- if self.valueIsDefined(self.wssProxy):
2310
+ isWssProxyDefined = self.value_is_defined(self.wssProxy)
2311
+ is_wss_proxy_defined = self.value_is_defined(self.wss_proxy)
2312
+ if isWssProxyDefined or is_wss_proxy_defined:
1969
2313
  usedProxies.append('wssProxy')
1970
- wssProxy = self.wssProxy
1971
- if self.valueIsDefined(self.wss_proxy):
1972
- usedProxies.append('wss_proxy')
1973
- wssProxy = self.wss_proxy
2314
+ wssProxy = self.wssProxy if (isWssProxyDefined) else self.wss_proxy
1974
2315
  # ws socks proxy
1975
- if self.valueIsDefined(self.wsSocksProxy):
2316
+ isWsSocksProxyDefined = self.value_is_defined(self.wsSocksProxy)
2317
+ is_ws_socks_proxy_defined = self.value_is_defined(self.ws_socks_proxy)
2318
+ if isWsSocksProxyDefined or is_ws_socks_proxy_defined:
1976
2319
  usedProxies.append('wsSocksProxy')
1977
- wsSocksProxy = self.wsSocksProxy
1978
- if self.valueIsDefined(self.ws_socks_proxy):
1979
- usedProxies.append('ws_socks_proxy')
1980
- wsSocksProxy = self.ws_socks_proxy
2320
+ wsSocksProxy = self.wsSocksProxy if (isWsSocksProxyDefined) else self.ws_socks_proxy
1981
2321
  # check
1982
2322
  length = len(usedProxies)
1983
2323
  if length > 1:
1984
2324
  joinedProxyNames = ','.join(usedProxies)
1985
- raise ProxyError(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from: wsProxy, wssProxy, wsSocksProxy')
2325
+ raise InvalidProxySettings(self.id + ' you have multiple conflicting proxy settings(' + joinedProxyNames + '), please use only one from: wsProxy, wssProxy, wsSocksProxy')
1986
2326
  return [wsProxy, wssProxy, wsSocksProxy]
1987
2327
 
1988
2328
  def check_conflicting_proxies(self, proxyAgentSet, proxyUrlSet):
1989
2329
  if proxyAgentSet and proxyUrlSet:
1990
- raise ProxyError(self.id + ' you have multiple conflicting proxy settings, please use only one from : proxyUrl, httpProxy, httpsProxy, socksProxy')
2330
+ raise InvalidProxySettings(self.id + ' you have multiple conflicting proxy settings, please use only one from : proxyUrl, httpProxy, httpsProxy, socksProxy')
2331
+
2332
+ def check_address(self, address: Str = None):
2333
+ if address is None:
2334
+ raise InvalidAddress(self.id + ' address is None')
2335
+ # check the address is not the same letter like 'aaaaa' nor too short nor has a space
2336
+ uniqChars = (self.unique(self.string_to_chars_array(address)))
2337
+ length = len(uniqChars) # py transpiler trick
2338
+ if length == 1 or len(address) < self.minFundingAddressLength or address.find(' ') > -1:
2339
+ raise InvalidAddress(self.id + ' address is invalid or has less than ' + str(self.minFundingAddressLength) + ' characters: "' + str(address) + '"')
2340
+ return address
1991
2341
 
1992
2342
  def find_message_hashes(self, client, element: str):
1993
2343
  result = []
@@ -1999,7 +2349,7 @@ class Exchange(object):
1999
2349
  return result
2000
2350
 
2001
2351
  def filter_by_limit(self, array: List[object], limit: Int = None, key: IndexType = 'timestamp', fromStart: bool = False):
2002
- if self.valueIsDefined(limit):
2352
+ if self.value_is_defined(limit):
2003
2353
  arrayLength = len(array)
2004
2354
  if arrayLength > 0:
2005
2355
  ascending = True
@@ -2011,13 +2361,13 @@ class Exchange(object):
2011
2361
  if fromStart:
2012
2362
  if limit > arrayLength:
2013
2363
  limit = arrayLength
2014
- array = self.arraySlice(array, 0, limit) if ascending else self.arraySlice(array, -limit)
2364
+ array = self.array_slice(array, 0, limit) if ascending else self.array_slice(array, -limit)
2015
2365
  else:
2016
- array = self.arraySlice(array, -limit) if ascending else self.arraySlice(array, 0, limit)
2366
+ array = self.array_slice(array, -limit) if ascending else self.array_slice(array, 0, limit)
2017
2367
  return array
2018
2368
 
2019
2369
  def filter_by_since_limit(self, array: List[object], since: Int = None, limit: Int = None, key: IndexType = 'timestamp', tail=False):
2020
- sinceIsDefined = self.valueIsDefined(since)
2370
+ sinceIsDefined = self.value_is_defined(since)
2021
2371
  parsedArray = self.to_array(array)
2022
2372
  result = parsedArray
2023
2373
  if sinceIsDefined:
@@ -2028,15 +2378,15 @@ class Exchange(object):
2028
2378
  if value and (value >= since):
2029
2379
  result.append(entry)
2030
2380
  if tail and limit is not None:
2031
- return self.arraySlice(result, -limit)
2381
+ return self.array_slice(result, -limit)
2032
2382
  # if the user provided a 'since' argument
2033
2383
  # we want to limit the result starting from the 'since'
2034
2384
  shouldFilterFromStart = not tail and sinceIsDefined
2035
2385
  return self.filter_by_limit(result, limit, key, shouldFilterFromStart)
2036
2386
 
2037
2387
  def filter_by_value_since_limit(self, array: List[object], field: IndexType, value=None, since: Int = None, limit: Int = None, key='timestamp', tail=False):
2038
- valueIsDefined = self.valueIsDefined(value)
2039
- sinceIsDefined = self.valueIsDefined(since)
2388
+ valueIsDefined = self.value_is_defined(value)
2389
+ sinceIsDefined = self.value_is_defined(since)
2040
2390
  parsedArray = self.to_array(array)
2041
2391
  result = parsedArray
2042
2392
  # single-pass filter for both symbol and since
@@ -2047,15 +2397,19 @@ class Exchange(object):
2047
2397
  entryFiledEqualValue = entry[field] == value
2048
2398
  firstCondition = entryFiledEqualValue if valueIsDefined else True
2049
2399
  entryKeyValue = self.safe_value(entry, key)
2050
- entryKeyGESince = (entryKeyValue) and since and (entryKeyValue >= since)
2400
+ entryKeyGESince = (entryKeyValue) and (since is not None) and (entryKeyValue >= since)
2051
2401
  secondCondition = entryKeyGESince if sinceIsDefined else True
2052
2402
  if firstCondition and secondCondition:
2053
2403
  result.append(entry)
2054
2404
  if tail and limit is not None:
2055
- return self.arraySlice(result, -limit)
2405
+ return self.array_slice(result, -limit)
2056
2406
  return self.filter_by_limit(result, limit, key, sinceIsDefined)
2057
2407
 
2058
2408
  def set_sandbox_mode(self, enabled: bool):
2409
+ """
2410
+ set the sandbox mode for the exchange
2411
+ :param boolean enabled: True to enable sandbox mode, False to disable it
2412
+ """
2059
2413
  if enabled:
2060
2414
  if 'test' in self.urls:
2061
2415
  if isinstance(self.urls['api'], str):
@@ -2066,6 +2420,8 @@ class Exchange(object):
2066
2420
  self.urls['api'] = self.clone(self.urls['test'])
2067
2421
  else:
2068
2422
  raise NotSupported(self.id + ' does not have a sandbox URL')
2423
+ # set flag
2424
+ self.isSandboxModeEnabled = True
2069
2425
  elif 'apiBackup' in self.urls:
2070
2426
  if isinstance(self.urls['api'], str):
2071
2427
  self.urls['api'] = self.urls['apiBackup']
@@ -2073,6 +2429,8 @@ class Exchange(object):
2073
2429
  self.urls['api'] = self.clone(self.urls['apiBackup'])
2074
2430
  newUrls = self.omit(self.urls, 'apiBackup')
2075
2431
  self.urls = newUrls
2432
+ # set flag
2433
+ self.isSandboxModeEnabled = False
2076
2434
 
2077
2435
  def sign(self, path, api: Any = 'public', method='GET', params={}, headers: Any = None, body: Any = None):
2078
2436
  return {}
@@ -2086,12 +2444,34 @@ class Exchange(object):
2086
2444
  def fetch_trades_ws(self, symbol: str, since: Int = None, limit: Int = None, params={}):
2087
2445
  raise NotSupported(self.id + ' fetchTradesWs() is not supported yet')
2088
2446
 
2447
+ def watch_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}):
2448
+ if self.has['watchLiquidationsForSymbols']:
2449
+ return self.watch_liquidations_for_symbols([symbol], since, limit, params)
2450
+ raise NotSupported(self.id + ' watchLiquidations() is not supported yet')
2451
+
2452
+ def watch_liquidations_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}):
2453
+ raise NotSupported(self.id + ' watchLiquidationsForSymbols() is not supported yet')
2454
+
2455
+ def watch_my_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}):
2456
+ if self.has['watchMyLiquidationsForSymbols']:
2457
+ return self.watch_my_liquidations_for_symbols([symbol], since, limit, params)
2458
+ raise NotSupported(self.id + ' watchMyLiquidations() is not supported yet')
2459
+
2460
+ def watch_my_liquidations_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}):
2461
+ raise NotSupported(self.id + ' watchMyLiquidationsForSymbols() is not supported yet')
2462
+
2089
2463
  def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}):
2090
2464
  raise NotSupported(self.id + ' watchTrades() is not supported yet')
2091
2465
 
2466
+ def un_watch_trades(self, symbol: str, params={}):
2467
+ raise NotSupported(self.id + ' unWatchTrades() is not supported yet')
2468
+
2092
2469
  def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}):
2093
2470
  raise NotSupported(self.id + ' watchTradesForSymbols() is not supported yet')
2094
2471
 
2472
+ def un_watch_trades_for_symbols(self, symbols: List[str], params={}):
2473
+ raise NotSupported(self.id + ' unWatchTradesForSymbols() is not supported yet')
2474
+
2095
2475
  def watch_my_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}):
2096
2476
  raise NotSupported(self.id + ' watchMyTradesForSymbols() is not supported yet')
2097
2477
 
@@ -2101,27 +2481,36 @@ class Exchange(object):
2101
2481
  def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}):
2102
2482
  raise NotSupported(self.id + ' watchOHLCVForSymbols() is not supported yet')
2103
2483
 
2484
+ def un_watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], params={}):
2485
+ raise NotSupported(self.id + ' unWatchOHLCVForSymbols() is not supported yet')
2486
+
2104
2487
  def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}):
2105
2488
  raise NotSupported(self.id + ' watchOrderBookForSymbols() is not supported yet')
2106
2489
 
2107
- def fetch_deposit_addresses(self, codes: List[str] = None, params={}):
2490
+ def un_watch_order_book_for_symbols(self, symbols: List[str], params={}):
2491
+ raise NotSupported(self.id + ' unWatchOrderBookForSymbols() is not supported yet')
2492
+
2493
+ def fetch_deposit_addresses(self, codes: Strings = None, params={}):
2108
2494
  raise NotSupported(self.id + ' fetchDepositAddresses() is not supported yet')
2109
2495
 
2110
2496
  def fetch_order_book(self, symbol: str, limit: Int = None, params={}):
2111
2497
  raise NotSupported(self.id + ' fetchOrderBook() is not supported yet')
2112
2498
 
2499
+ def fetch_order_book_ws(self, symbol: str, limit: Int = None, params={}):
2500
+ raise NotSupported(self.id + ' fetchOrderBookWs() is not supported yet')
2501
+
2113
2502
  def fetch_margin_mode(self, symbol: str, params={}):
2114
2503
  if self.has['fetchMarginModes']:
2115
- marginModes = self.fetchMarginModes([symbol], params)
2504
+ marginModes = self.fetch_margin_modes([symbol], params)
2116
2505
  return self.safe_dict(marginModes, symbol)
2117
2506
  else:
2118
2507
  raise NotSupported(self.id + ' fetchMarginMode() is not supported yet')
2119
2508
 
2120
- def fetch_margin_modes(self, symbols: List[str] = None, params={}):
2509
+ def fetch_margin_modes(self, symbols: Strings = None, params={}):
2121
2510
  raise NotSupported(self.id + ' fetchMarginModes() is not supported yet')
2122
2511
 
2123
2512
  def fetch_rest_order_book_safe(self, symbol, limit=None, params={}):
2124
- fetchSnapshotMaxRetries = self.handleOption('watchOrderBook', 'maxRetries', 3)
2513
+ fetchSnapshotMaxRetries = self.handle_option('watchOrderBook', 'maxRetries', 3)
2125
2514
  for i in range(0, fetchSnapshotMaxRetries):
2126
2515
  try:
2127
2516
  orderBook = self.fetch_order_book(symbol, limit, params)
@@ -2134,13 +2523,28 @@ class Exchange(object):
2134
2523
  def watch_order_book(self, symbol: str, limit: Int = None, params={}):
2135
2524
  raise NotSupported(self.id + ' watchOrderBook() is not supported yet')
2136
2525
 
2526
+ def un_watch_order_book(self, symbol: str, params={}):
2527
+ raise NotSupported(self.id + ' unWatchOrderBook() is not supported yet')
2528
+
2137
2529
  def fetch_time(self, params={}):
2138
2530
  raise NotSupported(self.id + ' fetchTime() is not supported yet')
2139
2531
 
2140
- def fetch_trading_limits(self, symbols: List[str] = None, params={}):
2532
+ def fetch_trading_limits(self, symbols: Strings = None, params={}):
2141
2533
  raise NotSupported(self.id + ' fetchTradingLimits() is not supported yet')
2142
2534
 
2143
- def parse_market(self, market):
2535
+ def parse_currency(self, rawCurrency: dict):
2536
+ raise NotSupported(self.id + ' parseCurrency() is not supported yet')
2537
+
2538
+ def parse_currencies(self, rawCurrencies):
2539
+ result = {}
2540
+ arr = self.to_array(rawCurrencies)
2541
+ for i in range(0, len(arr)):
2542
+ parsed = self.parse_currency(arr[i])
2543
+ code = parsed['code']
2544
+ result[code] = parsed
2545
+ return result
2546
+
2547
+ def parse_market(self, market: dict):
2144
2548
  raise NotSupported(self.id + ' parseMarket() is not supported yet')
2145
2549
 
2146
2550
  def parse_markets(self, markets):
@@ -2149,28 +2553,28 @@ class Exchange(object):
2149
2553
  result.append(self.parse_market(markets[i]))
2150
2554
  return result
2151
2555
 
2152
- def parse_ticker(self, ticker: object, market: Market = None):
2556
+ def parse_ticker(self, ticker: dict, market: Market = None):
2153
2557
  raise NotSupported(self.id + ' parseTicker() is not supported yet')
2154
2558
 
2155
2559
  def parse_deposit_address(self, depositAddress, currency: Currency = None):
2156
2560
  raise NotSupported(self.id + ' parseDepositAddress() is not supported yet')
2157
2561
 
2158
- def parse_trade(self, trade: object, market: Market = None):
2562
+ def parse_trade(self, trade: dict, market: Market = None):
2159
2563
  raise NotSupported(self.id + ' parseTrade() is not supported yet')
2160
2564
 
2161
- def parse_transaction(self, transaction, currency: Currency = None):
2565
+ def parse_transaction(self, transaction: dict, currency: Currency = None):
2162
2566
  raise NotSupported(self.id + ' parseTransaction() is not supported yet')
2163
2567
 
2164
- def parse_transfer(self, transfer, currency: Currency = None):
2568
+ def parse_transfer(self, transfer: dict, currency: Currency = None):
2165
2569
  raise NotSupported(self.id + ' parseTransfer() is not supported yet')
2166
2570
 
2167
- def parse_account(self, account):
2571
+ def parse_account(self, account: dict):
2168
2572
  raise NotSupported(self.id + ' parseAccount() is not supported yet')
2169
2573
 
2170
- def parse_ledger_entry(self, item, currency: Currency = None):
2574
+ def parse_ledger_entry(self, item: dict, currency: Currency = None):
2171
2575
  raise NotSupported(self.id + ' parseLedgerEntry() is not supported yet')
2172
2576
 
2173
- def parse_order(self, order, market: Market = None):
2577
+ def parse_order(self, order: dict, market: Market = None):
2174
2578
  raise NotSupported(self.id + ' parseOrder() is not supported yet')
2175
2579
 
2176
2580
  def fetch_cross_borrow_rates(self, params={}):
@@ -2182,33 +2586,39 @@ class Exchange(object):
2182
2586
  def parse_market_leverage_tiers(self, info, market: Market = None):
2183
2587
  raise NotSupported(self.id + ' parseMarketLeverageTiers() is not supported yet')
2184
2588
 
2185
- def fetch_leverage_tiers(self, symbols: List[str] = None, params={}):
2589
+ def fetch_leverage_tiers(self, symbols: Strings = None, params={}):
2186
2590
  raise NotSupported(self.id + ' fetchLeverageTiers() is not supported yet')
2187
2591
 
2188
- def parse_position(self, position, market: Market = None):
2592
+ def parse_position(self, position: dict, market: Market = None):
2189
2593
  raise NotSupported(self.id + ' parsePosition() is not supported yet')
2190
2594
 
2191
2595
  def parse_funding_rate_history(self, info, market: Market = None):
2192
2596
  raise NotSupported(self.id + ' parseFundingRateHistory() is not supported yet')
2193
2597
 
2194
- def parse_borrow_interest(self, info, market: Market = None):
2598
+ def parse_borrow_interest(self, info: dict, market: Market = None):
2195
2599
  raise NotSupported(self.id + ' parseBorrowInterest() is not supported yet')
2196
2600
 
2197
- def parse_ws_trade(self, trade, market: Market = None):
2601
+ def parse_isolated_borrow_rate(self, info: dict, market: Market = None):
2602
+ raise NotSupported(self.id + ' parseIsolatedBorrowRate() is not supported yet')
2603
+
2604
+ def parse_ws_trade(self, trade: dict, market: Market = None):
2198
2605
  raise NotSupported(self.id + ' parseWsTrade() is not supported yet')
2199
2606
 
2200
- def parse_ws_order(self, order, market: Market = None):
2607
+ def parse_ws_order(self, order: dict, market: Market = None):
2201
2608
  raise NotSupported(self.id + ' parseWsOrder() is not supported yet')
2202
2609
 
2203
- def parse_ws_order_trade(self, trade, market: Market = None):
2610
+ def parse_ws_order_trade(self, trade: dict, market: Market = None):
2204
2611
  raise NotSupported(self.id + ' parseWsOrderTrade() is not supported yet')
2205
2612
 
2206
2613
  def parse_ws_ohlcv(self, ohlcv, market: Market = None):
2207
2614
  return self.parse_ohlcv(ohlcv, market)
2208
2615
 
2209
- def fetch_funding_rates(self, symbols: List[str] = None, params={}):
2616
+ def fetch_funding_rates(self, symbols: Strings = None, params={}):
2210
2617
  raise NotSupported(self.id + ' fetchFundingRates() is not supported yet')
2211
2618
 
2619
+ def fetch_funding_intervals(self, symbols: Strings = None, params={}):
2620
+ raise NotSupported(self.id + ' fetchFundingIntervals() is not supported yet')
2621
+
2212
2622
  def watch_funding_rate(self, symbol: str, params={}):
2213
2623
  raise NotSupported(self.id + ' watchFundingRate() is not supported yet')
2214
2624
 
@@ -2216,7 +2626,7 @@ class Exchange(object):
2216
2626
  raise NotSupported(self.id + ' watchFundingRates() is not supported yet')
2217
2627
 
2218
2628
  def watch_funding_rates_for_symbols(self, symbols: List[str], params={}):
2219
- return self.watchFundingRates(symbols, params)
2629
+ return self.watch_funding_rates(symbols, params)
2220
2630
 
2221
2631
  def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}):
2222
2632
  raise NotSupported(self.id + ' transfer() is not supported yet')
@@ -2232,12 +2642,12 @@ class Exchange(object):
2232
2642
 
2233
2643
  def fetch_leverage(self, symbol: str, params={}):
2234
2644
  if self.has['fetchLeverages']:
2235
- leverages = self.fetchLeverages([symbol], params)
2645
+ leverages = self.fetch_leverages([symbol], params)
2236
2646
  return self.safe_dict(leverages, symbol)
2237
2647
  else:
2238
2648
  raise NotSupported(self.id + ' fetchLeverage() is not supported yet')
2239
2649
 
2240
- def fetch_leverages(self, symbols: List[str] = None, params={}):
2650
+ def fetch_leverages(self, symbols: Strings = None, params={}):
2241
2651
  raise NotSupported(self.id + ' fetchLeverages() is not supported yet')
2242
2652
 
2243
2653
  def set_position_mode(self, hedged: bool, symbol: Str = None, params={}):
@@ -2252,6 +2662,24 @@ class Exchange(object):
2252
2662
  def set_margin(self, symbol: str, amount: float, params={}):
2253
2663
  raise NotSupported(self.id + ' setMargin() is not supported yet')
2254
2664
 
2665
+ def fetch_long_short_ratio(self, symbol: str, timeframe: Str = None, params={}):
2666
+ raise NotSupported(self.id + ' fetchLongShortRatio() is not supported yet')
2667
+
2668
+ def fetch_long_short_ratio_history(self, symbol: Str = None, timeframe: Str = None, since: Int = None, limit: Int = None, params={}):
2669
+ raise NotSupported(self.id + ' fetchLongShortRatioHistory() is not supported yet')
2670
+
2671
+ def fetch_margin_adjustment_history(self, symbol: Str = None, type: Str = None, since: Num = None, limit: Num = None, params={}):
2672
+ """
2673
+ fetches the history of margin added or reduced from contract isolated positions
2674
+ :param str [symbol]: unified market symbol
2675
+ :param str [type]: "add" or "reduce"
2676
+ :param int [since]: timestamp in ms of the earliest change to fetch
2677
+ :param int [limit]: the maximum amount of changes to fetch
2678
+ :param dict params: extra parameters specific to the exchange api endpoint
2679
+ :returns dict[]: a list of `margin structures <https://docs.ccxt.com/#/?id=margin-loan-structure>`
2680
+ """
2681
+ raise NotSupported(self.id + ' fetchMarginAdjustmentHistory() is not supported yet')
2682
+
2255
2683
  def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
2256
2684
  raise NotSupported(self.id + ' setMarginMode() is not supported yet')
2257
2685
 
@@ -2264,6 +2692,9 @@ class Exchange(object):
2264
2692
  def fetch_open_interest(self, symbol: str, params={}):
2265
2693
  raise NotSupported(self.id + ' fetchOpenInterest() is not supported yet')
2266
2694
 
2695
+ def fetch_open_interests(self, symbols: Strings = None, params={}):
2696
+ raise NotSupported(self.id + ' fetchOpenInterests() is not supported yet')
2697
+
2267
2698
  def sign_in(self, params={}):
2268
2699
  raise NotSupported(self.id + ' signIn() is not supported yet')
2269
2700
 
@@ -2273,7 +2704,7 @@ class Exchange(object):
2273
2704
  def parse_to_int(self, number):
2274
2705
  # Solve Common intmisuse ex: int((since / str(1000)))
2275
2706
  # using a number which is not valid in ts
2276
- stringifiedNumber = str(number)
2707
+ stringifiedNumber = self.number_to_string(number)
2277
2708
  convertedNumber = float(stringifiedNumber)
2278
2709
  return int(convertedNumber)
2279
2710
 
@@ -2287,14 +2718,107 @@ class Exchange(object):
2287
2718
  return float(stringVersion)
2288
2719
  return int(stringVersion)
2289
2720
 
2290
- def is_round_number(self, value):
2721
+ def is_round_number(self, value: float):
2291
2722
  # self method is similar to isInteger, but self is more loyal and does not check for types.
2292
2723
  # i.e. isRoundNumber(1.000) returns True, while isInteger(1.000) returns False
2293
2724
  res = self.parse_to_numeric((value % 1))
2294
2725
  return res == 0
2295
2726
 
2727
+ def safe_integer_omit_zero(self, obj: object, key: IndexType, defaultValue: Int = None):
2728
+ timestamp = self.safe_integer(obj, key, defaultValue)
2729
+ if timestamp is None or timestamp == 0:
2730
+ return None
2731
+ return timestamp
2732
+
2296
2733
  def after_construct(self):
2297
2734
  self.create_networks_by_id_object()
2735
+ self.features_generator()
2736
+
2737
+ def features_generator(self):
2738
+ #
2739
+ # the exchange-specific features can be something like self, where we support 'string' aliases too:
2740
+ #
2741
+ # {
2742
+ # 'myItem' : {
2743
+ # 'createOrder' : {...},
2744
+ # 'fetchOrders' : {...},
2745
+ # },
2746
+ # 'swap': {
2747
+ # 'linear': 'myItem',
2748
+ # 'inverse': 'myItem',
2749
+ # },
2750
+ # 'future': {
2751
+ # 'linear': 'myItem',
2752
+ # 'inverse': 'myItem',
2753
+ # }
2754
+ # }
2755
+ #
2756
+ #
2757
+ #
2758
+ # self method would regenerate the blank features tree, eg:
2759
+ #
2760
+ # {
2761
+ # "spot": {
2762
+ # "createOrder": None,
2763
+ # "fetchBalance": None,
2764
+ # ...
2765
+ # },
2766
+ # "swap": {
2767
+ # ...
2768
+ # }
2769
+ # }
2770
+ #
2771
+ if self.features is None:
2772
+ return
2773
+ # reconstruct
2774
+ initialFeatures = self.features
2775
+ self.features = {}
2776
+ unifiedMarketTypes = ['spot', 'swap', 'future', 'option']
2777
+ subTypes = ['linear', 'inverse']
2778
+ # atm only support basic methods, eg: 'createOrder', 'fetchOrder', 'fetchOrders', 'fetchMyTrades'
2779
+ for i in range(0, len(unifiedMarketTypes)):
2780
+ marketType = unifiedMarketTypes[i]
2781
+ # if marketType is not filled for self exchange, don't add that in `features`
2782
+ if not (marketType in initialFeatures):
2783
+ self.features[marketType] = None
2784
+ else:
2785
+ if marketType == 'spot':
2786
+ self.features[marketType] = self.features_mapper(initialFeatures, marketType, None)
2787
+ else:
2788
+ self.features[marketType] = {}
2789
+ for j in range(0, len(subTypes)):
2790
+ subType = subTypes[j]
2791
+ self.features[marketType][subType] = self.features_mapper(initialFeatures, marketType, subType)
2792
+
2793
+ def features_mapper(self, initialFeatures: Any, marketType: Str, subType: Str = None):
2794
+ featuresObj = initialFeatures[marketType][subType] if (subType is not None) else initialFeatures[marketType]
2795
+ # if exchange does not have that market-type(eg. future>inverse)
2796
+ if featuresObj is None:
2797
+ return None
2798
+ extendsStr: Str = self.safe_string(featuresObj, 'extends')
2799
+ if extendsStr is not None:
2800
+ featuresObj = self.omit(featuresObj, 'extends')
2801
+ extendObj = self.features_mapper(initialFeatures, extendsStr)
2802
+ featuresObj = self.deep_extend(extendObj, featuresObj)
2803
+ #
2804
+ # corrections
2805
+ #
2806
+ if 'createOrder' in featuresObj:
2807
+ value = self.safe_dict(featuresObj['createOrder'], 'attachedStopLossTakeProfit')
2808
+ if value is not None:
2809
+ featuresObj['createOrder']['stopLoss'] = value
2810
+ featuresObj['createOrder']['takeProfit'] = value
2811
+ # for spot, default 'hedged' to False
2812
+ if marketType == 'spot':
2813
+ featuresObj['createOrder']['hedged'] = False
2814
+ # default 'GTC' to True
2815
+ gtcValue = self.safe_bool(featuresObj['createOrder']['timeInForce'], 'gtc')
2816
+ if gtcValue is None:
2817
+ featuresObj['createOrder']['timeInForce']['GTC'] = True
2818
+ return featuresObj
2819
+
2820
+ def orderbook_checksum_message(self, symbol: Str):
2821
+ return symbol + ' = False'
2298
2822
 
2299
2823
  def create_networks_by_id_object(self):
2300
2824
  # automatically generate network-id-to-code mappings
@@ -2307,6 +2831,7 @@ class Exchange(object):
2307
2831
  'ETH': {'ERC20': 'ETH'},
2308
2832
  'TRX': {'TRC20': 'TRX'},
2309
2833
  'CRO': {'CRC20': 'CRONOS'},
2834
+ 'BRC20': {'BRC20': 'BTC'},
2310
2835
  },
2311
2836
  }
2312
2837
 
@@ -2377,7 +2902,7 @@ class Exchange(object):
2377
2902
  },
2378
2903
  }, currency)
2379
2904
 
2380
- def safe_market_structure(self, market=None):
2905
+ def safe_market_structure(self, market: dict = None):
2381
2906
  cleanStructure = {
2382
2907
  'id': None,
2383
2908
  'lowercaseId': None,
@@ -2432,6 +2957,10 @@ class Exchange(object):
2432
2957
  'max': None,
2433
2958
  },
2434
2959
  },
2960
+ 'marginModes': {
2961
+ 'cross': None,
2962
+ 'isolated': None,
2963
+ },
2435
2964
  'created': None,
2436
2965
  'info': None,
2437
2966
  }
@@ -2537,7 +3066,7 @@ class Exchange(object):
2537
3066
  superWithRestDescribe = self.deep_extend(extendedRestDescribe, wsBaseDescribe)
2538
3067
  return superWithRestDescribe
2539
3068
 
2540
- def safe_balance(self, balance: object):
3069
+ def safe_balance(self, balance: dict):
2541
3070
  balances = self.omit(balance, ['info', 'timestamp', 'datetime', 'free', 'used', 'total'])
2542
3071
  codes = list(balances.keys())
2543
3072
  balance['free'] = {}
@@ -2571,7 +3100,7 @@ class Exchange(object):
2571
3100
  balance['debt'] = debtBalance
2572
3101
  return balance
2573
3102
 
2574
- def safe_order(self, order: object, market: Market = None):
3103
+ def safe_order(self, order: dict, market: Market = None):
2575
3104
  # parses numbers
2576
3105
  # * it is important pass the trades rawTrades
2577
3106
  amount = self.omit_zero(self.safe_string(order, 'amount'))
@@ -2595,11 +3124,13 @@ class Exchange(object):
2595
3124
  shouldParseFees = parseFee or parseFees
2596
3125
  fees = self.safe_list(order, 'fees', [])
2597
3126
  trades = []
3127
+ isTriggerOrSLTpOrder = ((self.safe_string(order, 'triggerPrice') is not None or (self.safe_string(order, 'stopLossPrice') is not None)) or (self.safe_string(order, 'takeProfitPrice') is not None))
2598
3128
  if parseFilled or parseCost or shouldParseFees:
2599
3129
  rawTrades = self.safe_value(order, 'trades', trades)
2600
- oldNumber = self.number
3130
+ # oldNumber = self.number
2601
3131
  # we parse trades here!
2602
- self.number = str
3132
+ # i don't think self is needed anymore
3133
+ # self.number = str
2603
3134
  firstTrade = self.safe_value(rawTrades, 0)
2604
3135
  # parse trades if they haven't already been parsed
2605
3136
  tradesAreParsed = ((firstTrade is not None) and ('info' in firstTrade) and ('id' in firstTrade))
@@ -2607,7 +3138,7 @@ class Exchange(object):
2607
3138
  trades = self.parse_trades(rawTrades, market)
2608
3139
  else:
2609
3140
  trades = rawTrades
2610
- self.number = oldNumber
3141
+ # self.number = oldNumber; why parse trades if you read the value using `safeString` ?
2611
3142
  tradesLength = 0
2612
3143
  isArray = isinstance(trades, list)
2613
3144
  if isArray:
@@ -2745,7 +3276,7 @@ class Exchange(object):
2745
3276
  postOnly = self.safe_value(order, 'postOnly')
2746
3277
  # timeInForceHandling
2747
3278
  if timeInForce is None:
2748
- if self.safe_string(order, 'type') == 'market':
3279
+ if not isTriggerOrSLTpOrder and (self.safe_string(order, 'type') == 'market'):
2749
3280
  timeInForce = 'IOC'
2750
3281
  # allow postOnly override
2751
3282
  if postOnly:
@@ -2875,7 +3406,7 @@ class Exchange(object):
2875
3406
  'cost': self.parse_number(cost),
2876
3407
  }
2877
3408
 
2878
- def safe_liquidation(self, liquidation: object, market: Market = None):
3409
+ def safe_liquidation(self, liquidation: dict, market: Market = None):
2879
3410
  contracts = self.safe_string(liquidation, 'contracts')
2880
3411
  contractSize = self.safe_string(market, 'contractSize')
2881
3412
  price = self.safe_string(liquidation, 'price')
@@ -2892,7 +3423,7 @@ class Exchange(object):
2892
3423
  liquidation['quoteValue'] = self.parse_number(quoteValue)
2893
3424
  return liquidation
2894
3425
 
2895
- def safe_trade(self, trade: object, market: Market = None):
3426
+ def safe_trade(self, trade: dict, market: Market = None):
2896
3427
  amount = self.safe_string(trade, 'amount')
2897
3428
  price = self.safe_string(trade, 'price')
2898
3429
  cost = self.safe_string(trade, 'cost')
@@ -2906,40 +3437,62 @@ class Exchange(object):
2906
3437
  multiplyPrice = Precise.string_div('1', price)
2907
3438
  multiplyPrice = Precise.string_mul(multiplyPrice, contractSize)
2908
3439
  cost = Precise.string_mul(multiplyPrice, amount)
2909
- parseFee = self.safe_value(trade, 'fee') is None
2910
- parseFees = self.safe_value(trade, 'fees') is None
2911
- shouldParseFees = parseFee or parseFees
2912
- fees = []
2913
- fee = self.safe_value(trade, 'fee')
2914
- if shouldParseFees:
2915
- reducedFees = self.reduce_fees_by_currency(fees) if self.reduceFees else fees
2916
- reducedLength = len(reducedFees)
2917
- for i in range(0, reducedLength):
2918
- reducedFees[i]['cost'] = self.safe_number(reducedFees[i], 'cost')
2919
- if 'rate' in reducedFees[i]:
2920
- reducedFees[i]['rate'] = self.safe_number(reducedFees[i], 'rate')
2921
- if not parseFee and (reducedLength == 0):
2922
- # copy fee to avoid modification by reference
2923
- feeCopy = self.deep_extend(fee)
2924
- feeCopy['cost'] = self.safe_number(feeCopy, 'cost')
2925
- if 'rate' in feeCopy:
2926
- feeCopy['rate'] = self.safe_number(feeCopy, 'rate')
2927
- reducedFees.append(feeCopy)
2928
- if parseFees:
2929
- trade['fees'] = reducedFees
2930
- if parseFee and (reducedLength == 1):
2931
- trade['fee'] = reducedFees[0]
2932
- tradeFee = self.safe_value(trade, 'fee')
2933
- if tradeFee is not None:
2934
- tradeFee['cost'] = self.safe_number(tradeFee, 'cost')
2935
- if 'rate' in tradeFee:
2936
- tradeFee['rate'] = self.safe_number(tradeFee, 'rate')
2937
- trade['fee'] = tradeFee
3440
+ resultFee, resultFees = self.parsed_fee_and_fees(trade)
3441
+ trade['fee'] = resultFee
3442
+ trade['fees'] = resultFees
2938
3443
  trade['amount'] = self.parse_number(amount)
2939
3444
  trade['price'] = self.parse_number(price)
2940
3445
  trade['cost'] = self.parse_number(cost)
2941
3446
  return trade
2942
3447
 
3448
+ def parsed_fee_and_fees(self, container: Any):
3449
+ fee = self.safe_dict(container, 'fee')
3450
+ fees = self.safe_list(container, 'fees')
3451
+ feeDefined = fee is not None
3452
+ feesDefined = fees is not None
3453
+ # parsing only if at least one of them is defined
3454
+ shouldParseFees = (feeDefined or feesDefined)
3455
+ if shouldParseFees:
3456
+ if feeDefined:
3457
+ fee = self.parse_fee_numeric(fee)
3458
+ if not feesDefined:
3459
+ # just set it directly, no further processing needed
3460
+ fees = [fee]
3461
+ # 'fees' were set, so reparse them
3462
+ reducedFees = self.reduce_fees_by_currency(fees) if self.reduceFees else fees
3463
+ reducedLength = len(reducedFees)
3464
+ for i in range(0, reducedLength):
3465
+ reducedFees[i] = self.parse_fee_numeric(reducedFees[i])
3466
+ fees = reducedFees
3467
+ if reducedLength == 1:
3468
+ fee = reducedFees[0]
3469
+ elif reducedLength == 0:
3470
+ fee = None
3471
+ # in case `fee & fees` are None, set `fees` array
3472
+ if fee is None:
3473
+ fee = {
3474
+ 'cost': None,
3475
+ 'currency': None,
3476
+ }
3477
+ if fees is None:
3478
+ fees = []
3479
+ return [fee, fees]
3480
+
3481
+ def parse_fee_numeric(self, fee: Any):
3482
+ fee['cost'] = self.safe_number(fee, 'cost') # ensure numeric
3483
+ if 'rate' in fee:
3484
+ fee['rate'] = self.safe_number(fee, 'rate')
3485
+ return fee
3486
+
3487
+ def find_nearest_ceiling(self, arr: List[float], providedValue: float):
3488
+ # i.e. findNearestCeiling([10, 30, 50], 23) returns 30
3489
+ length = len(arr)
3490
+ for i in range(0, length):
3491
+ current = arr[i]
3492
+ if providedValue <= current:
3493
+ return current
3494
+ return arr[length - 1]
3495
+
2943
3496
  def invert_flat_string_dictionary(self, dict):
2944
3497
  reversed = {}
2945
3498
  keys = list(dict.keys())
@@ -2999,12 +3552,13 @@ class Exchange(object):
2999
3552
  reduced = {}
3000
3553
  for i in range(0, len(fees)):
3001
3554
  fee = fees[i]
3002
- feeCurrencyCode = self.safe_string(fee, 'currency')
3555
+ code = self.safe_string(fee, 'currency')
3556
+ feeCurrencyCode = code is not code if None else str(i)
3003
3557
  if feeCurrencyCode is not None:
3004
3558
  rate = self.safe_string(fee, 'rate')
3005
- cost = self.safe_value(fee, 'cost')
3006
- if Precise.string_eq(cost, '0'):
3007
- # omit zero cost fees
3559
+ cost = self.safe_string(fee, 'cost')
3560
+ if cost is None:
3561
+ # omit None cost, does not make sense, however, don't omit '0' costs, still make sense
3008
3562
  continue
3009
3563
  if not (feeCurrencyCode in reduced):
3010
3564
  reduced[feeCurrencyCode] = {}
@@ -3013,7 +3567,7 @@ class Exchange(object):
3013
3567
  reduced[feeCurrencyCode][rateKey]['cost'] = Precise.string_add(reduced[feeCurrencyCode][rateKey]['cost'], cost)
3014
3568
  else:
3015
3569
  reduced[feeCurrencyCode][rateKey] = {
3016
- 'currency': feeCurrencyCode,
3570
+ 'currency': code,
3017
3571
  'cost': cost,
3018
3572
  }
3019
3573
  if rate is not None:
@@ -3025,7 +3579,7 @@ class Exchange(object):
3025
3579
  result = self.array_concat(result, reducedFeeValues)
3026
3580
  return result
3027
3581
 
3028
- def safe_ticker(self, ticker: object, market: Market = None):
3582
+ def safe_ticker(self, ticker: dict, market: Market = None):
3029
3583
  open = self.omit_zero(self.safe_string(ticker, 'open'))
3030
3584
  close = self.omit_zero(self.safe_string(ticker, 'close'))
3031
3585
  last = self.omit_zero(self.safe_string(ticker, 'last'))
@@ -3045,7 +3599,13 @@ class Exchange(object):
3045
3599
  if change is None:
3046
3600
  change = Precise.string_sub(last, open)
3047
3601
  if average is None:
3048
- average = Precise.string_div(Precise.string_add(last, open), '2')
3602
+ precision = 18
3603
+ if market is not None and self.is_tick_precision():
3604
+ marketPrecision = self.safe_dict(market, 'precision')
3605
+ precisionPrice = self.safe_string(marketPrecision, 'price')
3606
+ if precisionPrice is not None:
3607
+ precision = self.precision_from_string(precisionPrice)
3608
+ average = Precise.string_div(Precise.string_add(last, open), '2', precision)
3049
3609
  if (percentage is None) and (change is not None) and (open is not None) and Precise.string_gt(open, '0'):
3050
3610
  percentage = Precise.string_mul(Precise.string_div(change, open), '100')
3051
3611
  if (change is None) and (percentage is not None) and (open is not None):
@@ -3055,15 +3615,15 @@ class Exchange(object):
3055
3615
  # timestamp and symbol operations don't belong in safeTicker
3056
3616
  # they should be done in the derived classes
3057
3617
  return self.extend(ticker, {
3058
- 'bid': self.parse_number(self.omit_zero(self.safe_number(ticker, 'bid'))),
3618
+ 'bid': self.parse_number(self.omit_zero(self.safe_string(ticker, 'bid'))),
3059
3619
  'bidVolume': self.safe_number(ticker, 'bidVolume'),
3060
- 'ask': self.parse_number(self.omit_zero(self.safe_number(ticker, 'ask'))),
3620
+ 'ask': self.parse_number(self.omit_zero(self.safe_string(ticker, 'ask'))),
3061
3621
  'askVolume': self.safe_number(ticker, 'askVolume'),
3062
3622
  'high': self.parse_number(self.omit_zero(self.safe_string(ticker, 'high'))),
3063
- 'low': self.parse_number(self.omit_zero(self.safe_number(ticker, 'low'))),
3064
- 'open': self.parse_number(self.omit_zero(self.parse_number(open))),
3065
- 'close': self.parse_number(self.omit_zero(self.parse_number(close))),
3066
- 'last': self.parse_number(self.omit_zero(self.parse_number(last))),
3623
+ 'low': self.parse_number(self.omit_zero(self.safe_string(ticker, 'low'))),
3624
+ 'open': self.parse_number(self.omit_zero(open)),
3625
+ 'close': self.parse_number(self.omit_zero(close)),
3626
+ 'last': self.parse_number(self.omit_zero(last)),
3067
3627
  'change': self.parse_number(change),
3068
3628
  'percentage': self.parse_number(percentage),
3069
3629
  'average': self.parse_number(average),
@@ -3071,15 +3631,17 @@ class Exchange(object):
3071
3631
  'baseVolume': self.parse_number(baseVolume),
3072
3632
  'quoteVolume': self.parse_number(quoteVolume),
3073
3633
  'previousClose': self.safe_number(ticker, 'previousClose'),
3634
+ 'indexPrice': self.safe_number(ticker, 'indexPrice'),
3635
+ 'markPrice': self.safe_number(ticker, 'markPrice'),
3074
3636
  })
3075
3637
 
3076
- def fetch_borrow_rate(self, code: str, amount, params={}):
3638
+ def fetch_borrow_rate(self, code: str, amount: float, params={}):
3077
3639
  raise NotSupported(self.id + ' fetchBorrowRate is deprecated, please use fetchCrossBorrowRate or fetchIsolatedBorrowRate instead')
3078
3640
 
3079
- def repay_cross_margin(self, code: str, amount, params={}):
3641
+ def repay_cross_margin(self, code: str, amount: float, params={}):
3080
3642
  raise NotSupported(self.id + ' repayCrossMargin is not support yet')
3081
3643
 
3082
- def repay_isolated_margin(self, symbol: str, code: str, amount, params={}):
3644
+ def repay_isolated_margin(self, symbol: str, code: str, amount: float, params={}):
3083
3645
  raise NotSupported(self.id + ' repayIsolatedMargin is not support yet')
3084
3646
 
3085
3647
  def borrow_cross_margin(self, code: str, amount: float, params={}):
@@ -3088,10 +3650,10 @@ class Exchange(object):
3088
3650
  def borrow_isolated_margin(self, symbol: str, code: str, amount: float, params={}):
3089
3651
  raise NotSupported(self.id + ' borrowIsolatedMargin is not support yet')
3090
3652
 
3091
- def borrow_margin(self, code: str, amount, symbol: Str = None, params={}):
3653
+ def borrow_margin(self, code: str, amount: float, symbol: Str = None, params={}):
3092
3654
  raise NotSupported(self.id + ' borrowMargin is deprecated, please use borrowCrossMargin or borrowIsolatedMargin instead')
3093
3655
 
3094
- def repay_margin(self, code: str, amount, symbol: Str = None, params={}):
3656
+ def repay_margin(self, code: str, amount: float, symbol: Str = None, params={}):
3095
3657
  raise NotSupported(self.id + ' repayMargin is deprecated, please use repayCrossMargin or repayIsolatedMargin instead')
3096
3658
 
3097
3659
  def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
@@ -3109,7 +3671,7 @@ class Exchange(object):
3109
3671
  def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}):
3110
3672
  raise NotSupported(self.id + ' watchOHLCV() is not supported yet')
3111
3673
 
3112
- def convert_trading_view_to_ohlcv(self, ohlcvs, timestamp='t', open='o', high='h', low='l', close='c', volume='v', ms=False):
3674
+ 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):
3113
3675
  result = []
3114
3676
  timestamps = self.safe_list(ohlcvs, timestamp, [])
3115
3677
  opens = self.safe_list(ohlcvs, open, [])
@@ -3128,7 +3690,7 @@ class Exchange(object):
3128
3690
  ])
3129
3691
  return result
3130
3692
 
3131
- def convert_ohlcv_to_trading_view(self, ohlcvs, timestamp='t', open='o', high='h', low='l', close='c', volume='v', ms=False):
3693
+ def convert_ohlcv_to_trading_view(self, ohlcvs: List[List[float]], timestamp='t', open='o', high='h', low='l', close='c', volume='v', ms=False):
3132
3694
  result = {}
3133
3695
  result[timestamp] = []
3134
3696
  result[open] = []
@@ -3137,7 +3699,7 @@ class Exchange(object):
3137
3699
  result[close] = []
3138
3700
  result[volume] = []
3139
3701
  for i in range(0, len(ohlcvs)):
3140
- ts = ohlcvs[i][0] if ms else self.parseToInt(ohlcvs[i][0] / 1000)
3702
+ ts = ohlcvs[i][0] if ms else self.parse_to_int(ohlcvs[i][0] / 1000)
3141
3703
  result[timestamp].append(ts)
3142
3704
  result[open].append(ohlcvs[i][1])
3143
3705
  result[high].append(ohlcvs[i][2])
@@ -3187,7 +3749,7 @@ class Exchange(object):
3187
3749
  else:
3188
3750
  raise BadResponse(errorMessage)
3189
3751
 
3190
- def market_ids(self, symbols):
3752
+ def market_ids(self, symbols: Strings = None):
3191
3753
  if symbols is None:
3192
3754
  return symbols
3193
3755
  result = []
@@ -3195,7 +3757,23 @@ class Exchange(object):
3195
3757
  result.append(self.market_id(symbols[i]))
3196
3758
  return result
3197
3759
 
3198
- def market_symbols(self, symbols, type: Str = None, allowEmpty=True, sameTypeOnly=False, sameSubTypeOnly=False):
3760
+ def currency_ids(self, codes: Strings = None):
3761
+ if codes is None:
3762
+ return codes
3763
+ result = []
3764
+ for i in range(0, len(codes)):
3765
+ result.append(self.currency_id(codes[i]))
3766
+ return result
3767
+
3768
+ def markets_for_symbols(self, symbols: Strings = None):
3769
+ if symbols is None:
3770
+ return symbols
3771
+ result = []
3772
+ for i in range(0, len(symbols)):
3773
+ result.append(self.market(symbols[i]))
3774
+ return result
3775
+
3776
+ def market_symbols(self, symbols: Strings = None, type: Str = None, allowEmpty=True, sameTypeOnly=False, sameSubTypeOnly=False):
3199
3777
  if symbols is None:
3200
3778
  if not allowEmpty:
3201
3779
  raise ArgumentsRequired(self.id + ' empty list of symbols is not supported')
@@ -3225,7 +3803,7 @@ class Exchange(object):
3225
3803
  result.append(symbol)
3226
3804
  return result
3227
3805
 
3228
- def market_codes(self, codes):
3806
+ def market_codes(self, codes: Strings = None):
3229
3807
  if codes is None:
3230
3808
  return codes
3231
3809
  result = []
@@ -3271,7 +3849,7 @@ class Exchange(object):
3271
3849
 
3272
3850
  def network_code_to_id(self, networkCode: str, currencyCode: Str = None):
3273
3851
  """
3274
- * @ignore
3852
+ @ignore
3275
3853
  tries to convert the provided networkCode(which is expected to be an unified network code) to a network id. In order to achieve self, derived class needs to have 'options->networks' defined.
3276
3854
  :param str networkCode: unified network code
3277
3855
  :param str currencyCode: unified currency code, but self argument is not required by default, unless there is an exchange(like huobi) that needs an override of the method to be able to pass currencyCode argument additionally
@@ -3286,7 +3864,7 @@ class Exchange(object):
3286
3864
  if currencyCode is None:
3287
3865
  currencies = list(self.currencies.values())
3288
3866
  for i in range(0, len(currencies)):
3289
- currency = [i]
3867
+ currency = currencies[i]
3290
3868
  networks = self.safe_dict(currency, 'networks')
3291
3869
  network = self.safe_dict(networks, networkCode)
3292
3870
  networkId = self.safe_string(network, 'id')
@@ -3317,9 +3895,9 @@ class Exchange(object):
3317
3895
  networkId = networkCode
3318
3896
  return networkId
3319
3897
 
3320
- def network_id_to_code(self, networkId: str, currencyCode: Str = None):
3898
+ def network_id_to_code(self, networkId: Str = None, currencyCode: Str = None):
3321
3899
  """
3322
- * @ignore
3900
+ @ignore
3323
3901
  tries to convert the provided exchange-specific networkId to an unified network Code. In order to achieve self, derived class needs to have "options['networksById']" defined.
3324
3902
  :param str networkId: exchange specific network id/title, like: TRON, Trc-20, usdt-erc20, etc
3325
3903
  :param str|None currencyCode: unified currency code, but self argument is not required by default, unless there is an exchange(like huobi) that needs an override of the method to be able to pass currencyCode argument additionally
@@ -3352,7 +3930,7 @@ class Exchange(object):
3352
3930
  defaultNetworkCode = defaultNetworks[currencyCode]
3353
3931
  else:
3354
3932
  # otherwise, try to use the global-scope 'defaultNetwork' value(even if that network is not supported by currency, it doesn't make any problem, self will be just used "at first" if currency supports self network at all)
3355
- defaultNetwork = self.safe_dict(self.options, 'defaultNetwork')
3933
+ defaultNetwork = self.safe_string(self.options, 'defaultNetwork')
3356
3934
  if defaultNetwork is not None:
3357
3935
  defaultNetworkCode = defaultNetwork
3358
3936
  return defaultNetworkCode
@@ -3404,28 +3982,43 @@ class Exchange(object):
3404
3982
  'nonce': None,
3405
3983
  }
3406
3984
 
3407
- def parse_ohlcvs(self, ohlcvs: List[object], market: Any = None, timeframe: str = '1m', since: Int = None, limit: Int = None):
3985
+ def parse_ohlcvs(self, ohlcvs: List[object], market: Any = None, timeframe: str = '1m', since: Int = None, limit: Int = None, tail: Bool = False):
3408
3986
  results = []
3409
3987
  for i in range(0, len(ohlcvs)):
3410
3988
  results.append(self.parse_ohlcv(ohlcvs[i], market))
3411
3989
  sorted = self.sort_by(results, 0)
3412
- return self.filter_by_since_limit(sorted, since, limit, 0)
3990
+ return self.filter_by_since_limit(sorted, since, limit, 0, tail)
3413
3991
 
3414
- def parse_leverage_tiers(self, response: List[object], symbols: List[str] = None, marketIdKey=None):
3992
+ def parse_leverage_tiers(self, response: Any, symbols: List[str] = None, marketIdKey=None):
3415
3993
  # marketIdKey should only be None when response is a dictionary
3416
3994
  symbols = self.market_symbols(symbols)
3417
3995
  tiers = {}
3418
- for i in range(0, len(response)):
3419
- item = response[i]
3420
- id = self.safe_string(item, marketIdKey)
3421
- market = self.safe_market(id, None, None, 'swap')
3422
- symbol = market['symbol']
3423
- contract = self.safe_bool(market, 'contract', False)
3424
- if contract and ((symbols is None) or self.in_array(symbol, symbols)):
3425
- tiers[symbol] = self.parse_market_leverage_tiers(item, market)
3996
+ symbolsLength = 0
3997
+ if symbols is not None:
3998
+ symbolsLength = len(symbols)
3999
+ noSymbols = (symbols is None) or (symbolsLength == 0)
4000
+ if isinstance(response, list):
4001
+ for i in range(0, len(response)):
4002
+ item = response[i]
4003
+ id = self.safe_string(item, marketIdKey)
4004
+ market = self.safe_market(id, None, None, 'swap')
4005
+ symbol = market['symbol']
4006
+ contract = self.safe_bool(market, 'contract', False)
4007
+ if contract and (noSymbols or self.in_array(symbol, symbols)):
4008
+ tiers[symbol] = self.parse_market_leverage_tiers(item, market)
4009
+ else:
4010
+ keys = list(response.keys())
4011
+ for i in range(0, len(keys)):
4012
+ marketId = keys[i]
4013
+ item = response[marketId]
4014
+ market = self.safe_market(marketId, None, None, 'swap')
4015
+ symbol = market['symbol']
4016
+ contract = self.safe_bool(market, 'contract', False)
4017
+ if contract and (noSymbols or self.in_array(symbol, symbols)):
4018
+ tiers[symbol] = self.parse_market_leverage_tiers(item, market)
3426
4019
  return tiers
3427
4020
 
3428
- def load_trading_limits(self, symbols: List[str] = None, reload=False, params={}):
4021
+ def load_trading_limits(self, symbols: Strings = None, reload=False, params={}):
3429
4022
  if self.has['fetchTradingLimits']:
3430
4023
  if reload or not ('limitsLoaded' in self.options):
3431
4024
  response = self.fetch_trading_limits(symbols)
@@ -3435,7 +4028,7 @@ class Exchange(object):
3435
4028
  self.options['limitsLoaded'] = self.milliseconds()
3436
4029
  return self.markets
3437
4030
 
3438
- def safe_position(self, position):
4031
+ def safe_position(self, position: dict):
3439
4032
  # simplified version of: /pull/12765/
3440
4033
  unrealizedPnlString = self.safe_string(position, 'unrealisedPnl')
3441
4034
  initialMarginString = self.safe_string(position, 'initialMargin')
@@ -3525,6 +4118,14 @@ class Exchange(object):
3525
4118
  def set_headers(self, headers):
3526
4119
  return headers
3527
4120
 
4121
+ def currency_id(self, code: str):
4122
+ currency = self.safe_dict(self.currencies, code)
4123
+ if currency is None:
4124
+ currency = self.safe_currency(code)
4125
+ if currency is not None:
4126
+ return currency['id']
4127
+ return code
4128
+
3528
4129
  def market_id(self, symbol: str):
3529
4130
  market = self.market(symbol)
3530
4131
  if market is not None:
@@ -3541,12 +4142,36 @@ class Exchange(object):
3541
4142
  params = self.omit(params, paramName)
3542
4143
  return [value, params]
3543
4144
 
4145
+ def handle_param_string_2(self, params: object, paramName1: str, paramName2: str, defaultValue: Str = None):
4146
+ value = self.safe_string_2(params, paramName1, paramName2, defaultValue)
4147
+ if value is not None:
4148
+ params = self.omit(params, [paramName1, paramName2])
4149
+ return [value, params]
4150
+
3544
4151
  def handle_param_integer(self, params: object, paramName: str, defaultValue: Int = None):
3545
4152
  value = self.safe_integer(params, paramName, defaultValue)
3546
4153
  if value is not None:
3547
4154
  params = self.omit(params, paramName)
3548
4155
  return [value, params]
3549
4156
 
4157
+ def handle_param_integer_2(self, params: object, paramName1: str, paramName2: str, defaultValue: Int = None):
4158
+ value = self.safe_integer_2(params, paramName1, paramName2, defaultValue)
4159
+ if value is not None:
4160
+ params = self.omit(params, [paramName1, paramName2])
4161
+ return [value, params]
4162
+
4163
+ def handle_param_bool(self, params: object, paramName: str, defaultValue: Bool = None):
4164
+ value = self.safe_bool(params, paramName, defaultValue)
4165
+ if value is not None:
4166
+ params = self.omit(params, paramName)
4167
+ return [value, params]
4168
+
4169
+ def handle_param_bool_2(self, params: object, paramName1: str, paramName2: str, defaultValue: Bool = None):
4170
+ value = self.safe_bool_2(params, paramName1, paramName2, defaultValue)
4171
+ if value is not None:
4172
+ params = self.omit(params, [paramName1, paramName2])
4173
+ return [value, params]
4174
+
3550
4175
  def resolve_path(self, path, params):
3551
4176
  return [
3552
4177
  self.implode_params(path, params),
@@ -3554,7 +4179,9 @@ class Exchange(object):
3554
4179
  ]
3555
4180
 
3556
4181
  def get_list_from_object_values(self, objects, key: IndexType):
3557
- newArray = self.to_array(objects)
4182
+ newArray = objects
4183
+ if not isinstance(objects, list):
4184
+ newArray = self.to_array(objects)
3558
4185
  results = []
3559
4186
  for i in range(0, len(newArray)):
3560
4187
  results.append(newArray[i][key])
@@ -3595,7 +4222,23 @@ class Exchange(object):
3595
4222
  self.last_request_headers = request['headers']
3596
4223
  self.last_request_body = request['body']
3597
4224
  self.last_request_url = request['url']
3598
- return self.fetch(request['url'], request['method'], request['headers'], request['body'])
4225
+ retries = None
4226
+ retries, params = self.handle_option_and_params(params, path, 'maxRetriesOnFailure', 0)
4227
+ retryDelay = None
4228
+ retryDelay, params = self.handle_option_and_params(params, path, 'maxRetriesOnFailureDelay', 0)
4229
+ for i in range(0, retries + 1):
4230
+ try:
4231
+ return self.fetch(request['url'], request['method'], request['headers'], request['body'])
4232
+ except Exception as e:
4233
+ if isinstance(e, NetworkError):
4234
+ if i < retries:
4235
+ if self.verbose:
4236
+ self.log('Request failed with the error: ' + str(e) + ', retrying ' + (i + str(1)) + ' of ' + str(retries) + '...')
4237
+ if (retryDelay is not None) and (retryDelay != 0):
4238
+ self.sleep(retryDelay)
4239
+ continue
4240
+ raise e
4241
+ return None # self line is never reached, but exists for c# value return requirement
3599
4242
 
3600
4243
  def request(self, path, api: Any = 'public', method='GET', params={}, headers: Any = None, body: Any = None, config={}):
3601
4244
  return self.fetch2(path, api, method, params, headers, body, config)
@@ -3669,27 +4312,27 @@ class Exchange(object):
3669
4312
  return self.edit_order(id, symbol, 'limit', side, amount, price, params)
3670
4313
 
3671
4314
  def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
3672
- self.cancelOrder(id, symbol)
4315
+ self.cancel_order(id, symbol)
3673
4316
  return self.create_order(symbol, type, side, amount, price, params)
3674
4317
 
3675
- def edit_order_ws(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
3676
- self.cancelOrderWs(id, symbol)
3677
- return self.createOrderWs(symbol, type, side, amount, price, params)
3678
-
3679
- def fetch_permissions(self, params={}):
3680
- raise NotSupported(self.id + ' fetchPermissions() is not supported yet')
4318
+ def edit_order_ws(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
4319
+ self.cancel_order_ws(id, symbol)
4320
+ return self.create_order_ws(symbol, type, side, amount, price, params)
3681
4321
 
3682
4322
  def fetch_position(self, symbol: str, params={}):
3683
4323
  raise NotSupported(self.id + ' fetchPosition() is not supported yet')
3684
4324
 
4325
+ def fetch_position_ws(self, symbol: str, params={}):
4326
+ raise NotSupported(self.id + ' fetchPositionWs() is not supported yet')
4327
+
3685
4328
  def watch_position(self, symbol: Str = None, params={}):
3686
4329
  raise NotSupported(self.id + ' watchPosition() is not supported yet')
3687
4330
 
3688
- def watch_positions(self, symbols: List[str] = None, since: Int = None, limit: Int = None, params={}):
4331
+ def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}):
3689
4332
  raise NotSupported(self.id + ' watchPositions() is not supported yet')
3690
4333
 
3691
- def watch_position_for_symbols(self, symbols: List[str] = None, since: Int = None, limit: Int = None, params={}):
3692
- return self.watchPositions(symbols, since, limit, params)
4334
+ def watch_position_for_symbols(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}):
4335
+ return self.watch_positions(symbols, since, limit, params)
3693
4336
 
3694
4337
  def fetch_positions_for_symbol(self, symbol: str, params={}):
3695
4338
  """
@@ -3700,13 +4343,25 @@ class Exchange(object):
3700
4343
  """
3701
4344
  raise NotSupported(self.id + ' fetchPositionsForSymbol() is not supported yet')
3702
4345
 
3703
- def fetch_positions(self, symbols: List[str] = None, params={}):
4346
+ def fetch_positions_for_symbol_ws(self, symbol: str, params={}):
4347
+ """
4348
+ fetches all open positions for specific symbol, unlike fetchPositions(which is designed to work with multiple symbols) so self method might be preffered for one-market position, because of less rate-limit consumption and speed
4349
+ :param str symbol: unified market symbol
4350
+ :param dict params: extra parameters specific to the endpoint
4351
+ :returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>` with maximum 3 items - possible one position for "one-way" mode, and possible two positions(long & short) for "two-way"(a.k.a. hedge) mode
4352
+ """
4353
+ raise NotSupported(self.id + ' fetchPositionsForSymbol() is not supported yet')
4354
+
4355
+ def fetch_positions(self, symbols: Strings = None, params={}):
4356
+ raise NotSupported(self.id + ' fetchPositions() is not supported yet')
4357
+
4358
+ def fetch_positions_ws(self, symbols: Strings = None, params={}):
3704
4359
  raise NotSupported(self.id + ' fetchPositions() is not supported yet')
3705
4360
 
3706
- def fetch_positions_risk(self, symbols: List[str] = None, params={}):
4361
+ def fetch_positions_risk(self, symbols: Strings = None, params={}):
3707
4362
  raise NotSupported(self.id + ' fetchPositionsRisk() is not supported yet')
3708
4363
 
3709
- def fetch_bids_asks(self, symbols: List[str] = None, params={}):
4364
+ def fetch_bids_asks(self, symbols: Strings = None, params={}):
3710
4365
  raise NotSupported(self.id + ' fetchBidsAsks() is not supported yet')
3711
4366
 
3712
4367
  def fetch_borrow_interest(self, code: Str = None, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
@@ -3735,11 +4390,11 @@ class Exchange(object):
3735
4390
  code = currencyId
3736
4391
  if currencyId is not None:
3737
4392
  code = self.common_currency_code(currencyId.upper())
3738
- return {
4393
+ return self.safe_currency_structure({
3739
4394
  'id': currencyId,
3740
4395
  'code': code,
3741
4396
  'precision': None,
3742
- }
4397
+ })
3743
4398
 
3744
4399
  def safe_market(self, marketId: Str, market: Market = None, delimiter: Str = None, marketType: Str = None):
3745
4400
  result = self.safe_market_structure({
@@ -3779,6 +4434,11 @@ class Exchange(object):
3779
4434
  return result
3780
4435
 
3781
4436
  def check_required_credentials(self, error=True):
4437
+ """
4438
+ @ignore
4439
+ :param boolean error: raise an error that a credential is required if True
4440
+ :returns boolean: True if all required credentials have been set, otherwise False or an error is thrown is param error=true
4441
+ """
3782
4442
  keys = list(self.requiredCredentials.keys())
3783
4443
  for i in range(0, len(keys)):
3784
4444
  key = keys[i]
@@ -3823,33 +4483,21 @@ class Exchange(object):
3823
4483
  def fetch_status(self, params={}):
3824
4484
  raise NotSupported(self.id + ' fetchStatus() is not supported yet')
3825
4485
 
3826
- def fetch_funding_fee(self, code: str, params={}):
3827
- warnOnFetchFundingFee = self.safe_bool(self.options, 'warnOnFetchFundingFee', True)
3828
- if warnOnFetchFundingFee:
3829
- raise NotSupported(self.id + ' fetchFundingFee() method is deprecated, it will be removed in July 2022, please, use fetchTransactionFee() or set exchange.options["warnOnFetchFundingFee"] = False to suppress self warning')
3830
- return self.fetch_transaction_fee(code, params)
3831
-
3832
- def fetch_funding_fees(self, codes: List[str] = None, params={}):
3833
- warnOnFetchFundingFees = self.safe_bool(self.options, 'warnOnFetchFundingFees', True)
3834
- if warnOnFetchFundingFees:
3835
- raise NotSupported(self.id + ' fetchFundingFees() method is deprecated, it will be removed in July 2022. Please, use fetchTransactionFees() or set exchange.options["warnOnFetchFundingFees"] = False to suppress self warning')
3836
- return self.fetch_transaction_fees(codes, params)
3837
-
3838
4486
  def fetch_transaction_fee(self, code: str, params={}):
3839
4487
  if not self.has['fetchTransactionFees']:
3840
4488
  raise NotSupported(self.id + ' fetchTransactionFee() is not supported yet')
3841
4489
  return self.fetch_transaction_fees([code], params)
3842
4490
 
3843
- def fetch_transaction_fees(self, codes: List[str] = None, params={}):
4491
+ def fetch_transaction_fees(self, codes: Strings = None, params={}):
3844
4492
  raise NotSupported(self.id + ' fetchTransactionFees() is not supported yet')
3845
4493
 
3846
- def fetch_deposit_withdraw_fees(self, codes: List[str] = None, params={}):
4494
+ def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
3847
4495
  raise NotSupported(self.id + ' fetchDepositWithdrawFees() is not supported yet')
3848
4496
 
3849
4497
  def fetch_deposit_withdraw_fee(self, code: str, params={}):
3850
4498
  if not self.has['fetchDepositWithdrawFees']:
3851
4499
  raise NotSupported(self.id + ' fetchDepositWithdrawFee() is not supported yet')
3852
- fees = self.fetchDepositWithdrawFees([code], params)
4500
+ fees = self.fetch_deposit_withdraw_fees([code], params)
3853
4501
  return self.safe_value(fees, code)
3854
4502
 
3855
4503
  def get_supported_mapping(self, key, mapping={}):
@@ -3862,7 +4510,7 @@ class Exchange(object):
3862
4510
  self.load_markets()
3863
4511
  if not self.has['fetchBorrowRates']:
3864
4512
  raise NotSupported(self.id + ' fetchCrossBorrowRate() is not supported yet')
3865
- borrowRates = self.fetchCrossBorrowRates(params)
4513
+ borrowRates = self.fetch_cross_borrow_rates(params)
3866
4514
  rate = self.safe_value(borrowRates, code)
3867
4515
  if rate is None:
3868
4516
  raise ExchangeError(self.id + ' fetchCrossBorrowRate() could not find the borrow rate for currency code ' + code)
@@ -3900,36 +4548,40 @@ class Exchange(object):
3900
4548
  value = value if (value is not None) else defaultValue
3901
4549
  return [value, params]
3902
4550
 
3903
- def handle_option_and_params_2(self, params: object, methodName: str, methodName2: str, optionName: str, defaultValue=None):
3904
- # This method can be used to obtain method specific properties, i.e: self.handle_option_and_params(params, 'fetchPosition', 'marginMode', 'isolated')
3905
- defaultOptionName = 'default' + self.capitalize(optionName) # we also need to check the 'defaultXyzWhatever'
3906
- # check if params contain the key
3907
- value = self.safe_value_2(params, optionName, defaultOptionName)
4551
+ def handle_option_and_params_2(self, params: object, methodName1: str, optionName1: str, optionName2: str, defaultValue=None):
4552
+ value = None
4553
+ value, params = self.handle_option_and_params(params, methodName1, optionName1)
3908
4554
  if value is not None:
3909
- params = self.omit(params, [optionName, defaultOptionName])
3910
- else:
3911
- # check if exchange has properties for self method
3912
- exchangeWideMethodOptions = self.safe_value_2(self.options, methodName, methodName2)
3913
- if exchangeWideMethodOptions is not None:
3914
- # check if the option is defined inside self method's props
3915
- value = self.safe_value_2(exchangeWideMethodOptions, optionName, defaultOptionName)
3916
- if value is None:
3917
- # if it's still None, check if global exchange-wide option exists
3918
- value = self.safe_value_2(self.options, optionName, defaultOptionName)
3919
- # if it's still None, use the default value
3920
- value = value if (value is not None) else defaultValue
3921
- return [value, params]
4555
+ # omit optionName2 too from params
4556
+ params = self.omit(params, optionName2)
4557
+ return [value, params]
4558
+ # if still None, try optionName2
4559
+ value2 = None
4560
+ value2, params = self.handle_option_and_params(params, methodName1, optionName2, defaultValue)
4561
+ return [value2, params]
3922
4562
 
3923
4563
  def handle_option(self, methodName: str, optionName: str, defaultValue=None):
3924
4564
  # eslint-disable-next-line no-unused-vars
3925
4565
  result, empty = self.handle_option_and_params({}, methodName, optionName, defaultValue)
3926
4566
  return result
3927
4567
 
3928
- def handle_market_type_and_params(self, methodName: str, market: Market = None, params={}):
4568
+ def handle_market_type_and_params(self, methodName: str, market: Market = None, params={}, defaultValue=None):
4569
+ """
4570
+ @ignore
4571
+ @param methodName the method calling handleMarketTypeAndParams
4572
+ :param Market market:
4573
+ :param dict params:
4574
+ :param str [params.type]: type assigned by user
4575
+ :param str [params.defaultType]: same.type
4576
+ :param str [defaultValue]: assigned programatically in the method calling handleMarketTypeAndParams
4577
+ :returns [str, dict]: the market type and params with type and defaultType omitted
4578
+ """
3929
4579
  defaultType = self.safe_string_2(self.options, 'defaultType', 'type', 'spot')
4580
+ if defaultValue is None: # defaultValue takes precendence over exchange wide defaultType
4581
+ defaultValue = defaultType
3930
4582
  methodOptions = self.safe_dict(self.options, methodName)
3931
- methodType = defaultType
3932
- if methodOptions is not None:
4583
+ methodType = defaultValue
4584
+ if methodOptions is not None: # user defined methodType takes precedence over defaultValue
3933
4585
  if isinstance(methodOptions, str):
3934
4586
  methodType = methodOptions
3935
4587
  else:
@@ -3962,7 +4614,7 @@ class Exchange(object):
3962
4614
 
3963
4615
  def handle_margin_mode_and_params(self, methodName: str, params={}, defaultValue=None):
3964
4616
  """
3965
- * @ignore
4617
+ @ignore
3966
4618
  :param dict [params]: extra parameters specific to the exchange API endpoint
3967
4619
  :returns Array: the marginMode in lowercase by params["marginMode"], params["defaultMarginMode"] self.options["marginMode"] or self.options["defaultMarginMode"]
3968
4620
  """
@@ -3989,7 +4641,7 @@ class Exchange(object):
3989
4641
  return key
3990
4642
  return None
3991
4643
 
3992
- def handle_errors(self, statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody):
4644
+ def handle_errors(self, statusCode: int, statusText: str, url: str, method: str, responseHeaders: dict, responseBody: str, response, requestHeaders, requestBody):
3993
4645
  # it is a stub method that must be overrided in the derived exchange classes
3994
4646
  # raise NotSupported(self.id + ' handleErrors() not implemented yet')
3995
4647
  return None
@@ -4011,18 +4663,58 @@ class Exchange(object):
4011
4663
  else:
4012
4664
  raise NotSupported(self.id + ' fetchTicker() is not supported yet')
4013
4665
 
4666
+ def fetch_mark_price(self, symbol: str, params={}):
4667
+ if self.has['fetchMarkPrices']:
4668
+ self.load_markets()
4669
+ market = self.market(symbol)
4670
+ symbol = market['symbol']
4671
+ tickers = self.fetch_mark_prices([symbol], params)
4672
+ ticker = self.safe_dict(tickers, symbol)
4673
+ if ticker is None:
4674
+ raise NullResponse(self.id + ' fetchMarkPrices() could not find a ticker for ' + symbol)
4675
+ else:
4676
+ return ticker
4677
+ else:
4678
+ raise NotSupported(self.id + ' fetchMarkPrices() is not supported yet')
4679
+
4680
+ def fetch_ticker_ws(self, symbol: str, params={}):
4681
+ if self.has['fetchTickersWs']:
4682
+ self.load_markets()
4683
+ market = self.market(symbol)
4684
+ symbol = market['symbol']
4685
+ tickers = self.fetch_tickers_ws([symbol], params)
4686
+ ticker = self.safe_dict(tickers, symbol)
4687
+ if ticker is None:
4688
+ raise NullResponse(self.id + ' fetchTickerWs() could not find a ticker for ' + symbol)
4689
+ else:
4690
+ return ticker
4691
+ else:
4692
+ raise NotSupported(self.id + ' fetchTickerWs() is not supported yet')
4693
+
4014
4694
  def watch_ticker(self, symbol: str, params={}):
4015
4695
  raise NotSupported(self.id + ' watchTicker() is not supported yet')
4016
4696
 
4017
- def fetch_tickers(self, symbols: List[str] = None, params={}):
4697
+ def fetch_tickers(self, symbols: Strings = None, params={}):
4698
+ raise NotSupported(self.id + ' fetchTickers() is not supported yet')
4699
+
4700
+ def fetch_mark_prices(self, symbols: Strings = None, params={}):
4701
+ raise NotSupported(self.id + ' fetchMarkPrices() is not supported yet')
4702
+
4703
+ def fetch_tickers_ws(self, symbols: Strings = None, params={}):
4018
4704
  raise NotSupported(self.id + ' fetchTickers() is not supported yet')
4019
4705
 
4020
- def fetch_order_books(self, symbols: List[str] = None, limit: Int = None, params={}):
4706
+ def fetch_order_books(self, symbols: Strings = None, limit: Int = None, params={}):
4021
4707
  raise NotSupported(self.id + ' fetchOrderBooks() is not supported yet')
4022
4708
 
4023
- def watch_tickers(self, symbols: List[str] = None, params={}):
4709
+ def watch_bids_asks(self, symbols: Strings = None, params={}):
4710
+ raise NotSupported(self.id + ' watchBidsAsks() is not supported yet')
4711
+
4712
+ def watch_tickers(self, symbols: Strings = None, params={}):
4024
4713
  raise NotSupported(self.id + ' watchTickers() is not supported yet')
4025
4714
 
4715
+ def un_watch_tickers(self, symbols: Strings = None, params={}):
4716
+ raise NotSupported(self.id + ' unWatchTickers() is not supported yet')
4717
+
4026
4718
  def fetch_order(self, id: str, symbol: Str = None, params={}):
4027
4719
  raise NotSupported(self.id + ' fetchOrder() is not supported yet')
4028
4720
 
@@ -4063,6 +4755,28 @@ class Exchange(object):
4063
4755
  return self.create_order(symbol, type, side, amount, price, params)
4064
4756
  raise NotSupported(self.id + ' createTrailingAmountOrder() is not supported yet')
4065
4757
 
4758
+ def create_trailing_amount_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingAmount=None, trailingTriggerPrice=None, params={}):
4759
+ """
4760
+ create a trailing order by providing the symbol, type, side, amount, price and trailingAmount
4761
+ :param str symbol: unified symbol of the market to create an order in
4762
+ :param str type: 'market' or 'limit'
4763
+ :param str side: 'buy' or 'sell'
4764
+ :param float amount: how much you want to trade in units of the base currency, or number of contracts
4765
+ :param float [price]: the price for the order to be filled at, in units of the quote currency, ignored in market orders
4766
+ :param float trailingAmount: the quote amount to trail away from the current market price
4767
+ :param float [trailingTriggerPrice]: the price to activate a trailing order, default uses the price argument
4768
+ :param dict [params]: extra parameters specific to the exchange API endpoint
4769
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
4770
+ """
4771
+ if trailingAmount is None:
4772
+ raise ArgumentsRequired(self.id + ' createTrailingAmountOrderWs() requires a trailingAmount argument')
4773
+ params['trailingAmount'] = trailingAmount
4774
+ if trailingTriggerPrice is not None:
4775
+ params['trailingTriggerPrice'] = trailingTriggerPrice
4776
+ if self.has['createTrailingAmountOrderWs']:
4777
+ return self.create_order_ws(symbol, type, side, amount, price, params)
4778
+ raise NotSupported(self.id + ' createTrailingAmountOrderWs() is not supported yet')
4779
+
4066
4780
  def create_trailing_percent_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent=None, trailingTriggerPrice=None, params={}):
4067
4781
  """
4068
4782
  create a trailing order by providing the symbol, type, side, amount, price and trailingPercent
@@ -4085,6 +4799,28 @@ class Exchange(object):
4085
4799
  return self.create_order(symbol, type, side, amount, price, params)
4086
4800
  raise NotSupported(self.id + ' createTrailingPercentOrder() is not supported yet')
4087
4801
 
4802
+ def create_trailing_percent_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent=None, trailingTriggerPrice=None, params={}):
4803
+ """
4804
+ create a trailing order by providing the symbol, type, side, amount, price and trailingPercent
4805
+ :param str symbol: unified symbol of the market to create an order in
4806
+ :param str type: 'market' or 'limit'
4807
+ :param str side: 'buy' or 'sell'
4808
+ :param float amount: how much you want to trade in units of the base currency, or number of contracts
4809
+ :param float [price]: the price for the order to be filled at, in units of the quote currency, ignored in market orders
4810
+ :param float trailingPercent: the percent to trail away from the current market price
4811
+ :param float [trailingTriggerPrice]: the price to activate a trailing order, default uses the price argument
4812
+ :param dict [params]: extra parameters specific to the exchange API endpoint
4813
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
4814
+ """
4815
+ if trailingPercent is None:
4816
+ raise ArgumentsRequired(self.id + ' createTrailingPercentOrderWs() requires a trailingPercent argument')
4817
+ params['trailingPercent'] = trailingPercent
4818
+ if trailingTriggerPrice is not None:
4819
+ params['trailingTriggerPrice'] = trailingTriggerPrice
4820
+ if self.has['createTrailingPercentOrderWs']:
4821
+ return self.create_order_ws(symbol, type, side, amount, price, params)
4822
+ raise NotSupported(self.id + ' createTrailingPercentOrderWs() is not supported yet')
4823
+
4088
4824
  def create_market_order_with_cost(self, symbol: str, side: OrderSide, cost: float, params={}):
4089
4825
  """
4090
4826
  create a market order by providing the symbol, side and cost
@@ -4122,6 +4858,19 @@ class Exchange(object):
4122
4858
  return self.create_order(symbol, 'market', 'sell', cost, 1, params)
4123
4859
  raise NotSupported(self.id + ' createMarketSellOrderWithCost() is not supported yet')
4124
4860
 
4861
+ def create_market_order_with_cost_ws(self, symbol: str, side: OrderSide, cost: float, params={}):
4862
+ """
4863
+ create a market order by providing the symbol, side and cost
4864
+ :param str symbol: unified symbol of the market to create an order in
4865
+ :param str side: 'buy' or 'sell'
4866
+ :param float cost: how much you want to trade in units of the quote currency
4867
+ :param dict [params]: extra parameters specific to the exchange API endpoint
4868
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
4869
+ """
4870
+ if self.has['createMarketOrderWithCostWs'] or (self.has['createMarketBuyOrderWithCostWs'] and self.has['createMarketSellOrderWithCostWs']):
4871
+ return self.create_order_ws(symbol, 'market', side, cost, 1, params)
4872
+ raise NotSupported(self.id + ' createMarketOrderWithCostWs() is not supported yet')
4873
+
4125
4874
  def create_trigger_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, triggerPrice: Num = None, params={}):
4126
4875
  """
4127
4876
  create a trigger stop order(type 1)
@@ -4141,6 +4890,25 @@ class Exchange(object):
4141
4890
  return self.create_order(symbol, type, side, amount, price, params)
4142
4891
  raise NotSupported(self.id + ' createTriggerOrder() is not supported yet')
4143
4892
 
4893
+ def create_trigger_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, triggerPrice: Num = None, params={}):
4894
+ """
4895
+ create a trigger stop order(type 1)
4896
+ :param str symbol: unified symbol of the market to create an order in
4897
+ :param str type: 'market' or 'limit'
4898
+ :param str side: 'buy' or 'sell'
4899
+ :param float amount: how much you want to trade in units of the base currency or the number of contracts
4900
+ :param float [price]: the price to fulfill the order, in units of the quote currency, ignored in market orders
4901
+ :param float triggerPrice: the price to trigger the stop order, in units of the quote currency
4902
+ :param dict [params]: extra parameters specific to the exchange API endpoint
4903
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
4904
+ """
4905
+ if triggerPrice is None:
4906
+ raise ArgumentsRequired(self.id + ' createTriggerOrderWs() requires a triggerPrice argument')
4907
+ params['triggerPrice'] = triggerPrice
4908
+ if self.has['createTriggerOrderWs']:
4909
+ return self.create_order_ws(symbol, type, side, amount, price, params)
4910
+ raise NotSupported(self.id + ' createTriggerOrderWs() is not supported yet')
4911
+
4144
4912
  def create_stop_loss_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, stopLossPrice: Num = None, params={}):
4145
4913
  """
4146
4914
  create a trigger stop loss order(type 2)
@@ -4160,6 +4928,25 @@ class Exchange(object):
4160
4928
  return self.create_order(symbol, type, side, amount, price, params)
4161
4929
  raise NotSupported(self.id + ' createStopLossOrder() is not supported yet')
4162
4930
 
4931
+ def create_stop_loss_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, stopLossPrice: Num = None, params={}):
4932
+ """
4933
+ create a trigger stop loss order(type 2)
4934
+ :param str symbol: unified symbol of the market to create an order in
4935
+ :param str type: 'market' or 'limit'
4936
+ :param str side: 'buy' or 'sell'
4937
+ :param float amount: how much you want to trade in units of the base currency or the number of contracts
4938
+ :param float [price]: the price to fulfill the order, in units of the quote currency, ignored in market orders
4939
+ :param float stopLossPrice: the price to trigger the stop loss order, in units of the quote currency
4940
+ :param dict [params]: extra parameters specific to the exchange API endpoint
4941
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
4942
+ """
4943
+ if stopLossPrice is None:
4944
+ raise ArgumentsRequired(self.id + ' createStopLossOrderWs() requires a stopLossPrice argument')
4945
+ params['stopLossPrice'] = stopLossPrice
4946
+ if self.has['createStopLossOrderWs']:
4947
+ return self.create_order_ws(symbol, type, side, amount, price, params)
4948
+ raise NotSupported(self.id + ' createStopLossOrderWs() is not supported yet')
4949
+
4163
4950
  def create_take_profit_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, takeProfitPrice: Num = None, params={}):
4164
4951
  """
4165
4952
  create a trigger take profit order(type 2)
@@ -4179,6 +4966,25 @@ class Exchange(object):
4179
4966
  return self.create_order(symbol, type, side, amount, price, params)
4180
4967
  raise NotSupported(self.id + ' createTakeProfitOrder() is not supported yet')
4181
4968
 
4969
+ def create_take_profit_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, takeProfitPrice: Num = None, params={}):
4970
+ """
4971
+ create a trigger take profit order(type 2)
4972
+ :param str symbol: unified symbol of the market to create an order in
4973
+ :param str type: 'market' or 'limit'
4974
+ :param str side: 'buy' or 'sell'
4975
+ :param float amount: how much you want to trade in units of the base currency or the number of contracts
4976
+ :param float [price]: the price to fulfill the order, in units of the quote currency, ignored in market orders
4977
+ :param float takeProfitPrice: the price to trigger the take profit order, in units of the quote currency
4978
+ :param dict [params]: extra parameters specific to the exchange API endpoint
4979
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
4980
+ """
4981
+ if takeProfitPrice is None:
4982
+ raise ArgumentsRequired(self.id + ' createTakeProfitOrderWs() requires a takeProfitPrice argument')
4983
+ params['takeProfitPrice'] = takeProfitPrice
4984
+ if self.has['createTakeProfitOrderWs']:
4985
+ return self.create_order_ws(symbol, type, side, amount, price, params)
4986
+ raise NotSupported(self.id + ' createTakeProfitOrderWs() is not supported yet')
4987
+
4182
4988
  def create_order_with_take_profit_and_stop_loss(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, takeProfit: Num = None, stopLoss: Num = None, params={}):
4183
4989
  """
4184
4990
  create an order with a stop loss or take profit attached(type 3)
@@ -4200,6 +5006,12 @@ class Exchange(object):
4200
5006
  :param float [params.stopLossAmount]: *not available on all exchanges* the amount for a stop loss
4201
5007
  :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
4202
5008
  """
5009
+ params = self.set_take_profit_and_stop_loss_params(symbol, type, side, amount, price, takeProfit, stopLoss, params)
5010
+ if self.has['createOrderWithTakeProfitAndStopLoss']:
5011
+ return self.create_order(symbol, type, side, amount, price, params)
5012
+ raise NotSupported(self.id + ' createOrderWithTakeProfitAndStopLoss() is not supported yet')
5013
+
5014
+ def set_take_profit_and_stop_loss_params(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, takeProfit: Num = None, stopLoss: Num = None, params={}):
4203
5015
  if (takeProfit is None) and (stopLoss is None):
4204
5016
  raise ArgumentsRequired(self.id + ' createOrderWithTakeProfitAndStopLoss() requires either a takeProfit or stopLoss argument')
4205
5017
  if takeProfit is not None:
@@ -4235,9 +5047,33 @@ class Exchange(object):
4235
5047
  if stopLossAmount is not None:
4236
5048
  params['stopLoss']['amount'] = self.parse_to_numeric(stopLossAmount)
4237
5049
  params = self.omit(params, ['takeProfitType', 'takeProfitPriceType', 'takeProfitLimitPrice', 'takeProfitAmount', 'stopLossType', 'stopLossPriceType', 'stopLossLimitPrice', 'stopLossAmount'])
4238
- if self.has['createOrderWithTakeProfitAndStopLoss']:
4239
- return self.create_order(symbol, type, side, amount, price, params)
4240
- raise NotSupported(self.id + ' createOrderWithTakeProfitAndStopLoss() is not supported yet')
5050
+ return params
5051
+
5052
+ def create_order_with_take_profit_and_stop_loss_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, takeProfit: Num = None, stopLoss: Num = None, params={}):
5053
+ """
5054
+ create an order with a stop loss or take profit attached(type 3)
5055
+ :param str symbol: unified symbol of the market to create an order in
5056
+ :param str type: 'market' or 'limit'
5057
+ :param str side: 'buy' or 'sell'
5058
+ :param float amount: how much you want to trade in units of the base currency or the number of contracts
5059
+ :param float [price]: the price to fulfill the order, in units of the quote currency, ignored in market orders
5060
+ :param float [takeProfit]: the take profit price, in units of the quote currency
5061
+ :param float [stopLoss]: the stop loss price, in units of the quote currency
5062
+ :param dict [params]: extra parameters specific to the exchange API endpoint
5063
+ :param str [params.takeProfitType]: *not available on all exchanges* 'limit' or 'market'
5064
+ :param str [params.stopLossType]: *not available on all exchanges* 'limit' or 'market'
5065
+ :param str [params.takeProfitPriceType]: *not available on all exchanges* 'last', 'mark' or 'index'
5066
+ :param str [params.stopLossPriceType]: *not available on all exchanges* 'last', 'mark' or 'index'
5067
+ :param float [params.takeProfitLimitPrice]: *not available on all exchanges* limit price for a limit take profit order
5068
+ :param float [params.stopLossLimitPrice]: *not available on all exchanges* stop loss for a limit stop loss order
5069
+ :param float [params.takeProfitAmount]: *not available on all exchanges* the amount for a take profit
5070
+ :param float [params.stopLossAmount]: *not available on all exchanges* the amount for a stop loss
5071
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
5072
+ """
5073
+ params = self.set_take_profit_and_stop_loss_params(symbol, type, side, amount, price, takeProfit, stopLoss, params)
5074
+ if self.has['createOrderWithTakeProfitAndStopLossWs']:
5075
+ return self.create_order_ws(symbol, type, side, amount, price, params)
5076
+ raise NotSupported(self.id + ' createOrderWithTakeProfitAndStopLossWs() is not supported yet')
4241
5077
 
4242
5078
  def create_orders(self, orders: List[OrderRequest], params={}):
4243
5079
  raise NotSupported(self.id + ' createOrders() is not supported yet')
@@ -4257,11 +5093,17 @@ class Exchange(object):
4257
5093
  def cancel_all_orders(self, symbol: Str = None, params={}):
4258
5094
  raise NotSupported(self.id + ' cancelAllOrders() is not supported yet')
4259
5095
 
5096
+ def cancel_all_orders_after(self, timeout: Int, params={}):
5097
+ raise NotSupported(self.id + ' cancelAllOrdersAfter() is not supported yet')
5098
+
5099
+ def cancel_orders_for_symbols(self, orders: List[CancellationRequest], params={}):
5100
+ raise NotSupported(self.id + ' cancelOrdersForSymbols() is not supported yet')
5101
+
4260
5102
  def cancel_all_orders_ws(self, symbol: Str = None, params={}):
4261
5103
  raise NotSupported(self.id + ' cancelAllOrdersWs() is not supported yet')
4262
5104
 
4263
5105
  def cancel_unified_order(self, order, params={}):
4264
- return self.cancelOrder(self.safe_string(order, 'id'), self.safe_string(order, 'symbol'), params)
5106
+ return self.cancel_order(self.safe_string(order, 'id'), self.safe_string(order, 'symbol'), params)
4265
5107
 
4266
5108
  def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
4267
5109
  if self.has['fetchOpenOrders'] and self.has['fetchClosedOrders']:
@@ -4285,7 +5127,7 @@ class Exchange(object):
4285
5127
 
4286
5128
  def fetch_open_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
4287
5129
  if self.has['fetchOrdersWs']:
4288
- orders = self.fetchOrdersWs(symbol, since, limit, params)
5130
+ orders = self.fetch_orders_ws(symbol, since, limit, params)
4289
5131
  return self.filter_by(orders, 'status', 'open')
4290
5132
  raise NotSupported(self.id + ' fetchOpenOrdersWs() is not supported yet')
4291
5133
 
@@ -4300,7 +5142,7 @@ class Exchange(object):
4300
5142
 
4301
5143
  def fetch_closed_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
4302
5144
  if self.has['fetchOrdersWs']:
4303
- orders = self.fetchOrdersWs(symbol, since, limit, params)
5145
+ orders = self.fetch_orders_ws(symbol, since, limit, params)
4304
5146
  return self.filter_by(orders, 'status', 'closed')
4305
5147
  raise NotSupported(self.id + ' fetchClosedOrdersWs() is not supported yet')
4306
5148
 
@@ -4322,6 +5164,15 @@ class Exchange(object):
4322
5164
  def fetch_greeks(self, symbol: str, params={}):
4323
5165
  raise NotSupported(self.id + ' fetchGreeks() is not supported yet')
4324
5166
 
5167
+ def fetch_option_chain(self, code: str, params={}):
5168
+ raise NotSupported(self.id + ' fetchOptionChain() is not supported yet')
5169
+
5170
+ def fetch_option(self, symbol: str, params={}):
5171
+ raise NotSupported(self.id + ' fetchOption() is not supported yet')
5172
+
5173
+ def fetch_convert_quote(self, fromCode: str, toCode: str, amount: Num = None, params={}):
5174
+ raise NotSupported(self.id + ' fetchConvertQuote() is not supported yet')
5175
+
4325
5176
  def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
4326
5177
  """
4327
5178
  fetch history of deposits and withdrawals
@@ -4365,7 +5216,7 @@ class Exchange(object):
4365
5216
 
4366
5217
  def fetch_deposit_address(self, code: str, params={}):
4367
5218
  if self.has['fetchDepositAddresses']:
4368
- depositAddresses = self.fetchDepositAddresses([code], params)
5219
+ depositAddresses = self.fetch_deposit_addresses([code], params)
4369
5220
  depositAddress = self.safe_value(depositAddresses, code)
4370
5221
  if depositAddress is None:
4371
5222
  raise InvalidAddress(self.id + ' fetchDepositAddress() could not find a deposit address for ' + code + ', make sure you have created a corresponding deposit address in your wallet on the exchange website')
@@ -4374,7 +5225,7 @@ class Exchange(object):
4374
5225
  elif self.has['fetchDepositAddressesByNetwork']:
4375
5226
  network = self.safe_string(params, 'network')
4376
5227
  params = self.omit(params, 'network')
4377
- addressStructures = self.fetchDepositAddressesByNetwork(code, params)
5228
+ addressStructures = self.fetch_deposit_addresses_by_network(code, params)
4378
5229
  if network is not None:
4379
5230
  return self.safe_dict(addressStructures, network)
4380
5231
  else:
@@ -4391,10 +5242,10 @@ class Exchange(object):
4391
5242
  'total': None,
4392
5243
  }
4393
5244
 
4394
- def common_currency_code(self, currency: str):
5245
+ def common_currency_code(self, code: str):
4395
5246
  if not self.substituteCommonCurrencyCodes:
4396
- return currency
4397
- return self.safe_string(self.commonCurrencies, currency, currency)
5247
+ return code
5248
+ return self.safe_string(self.commonCurrencies, code, code)
4398
5249
 
4399
5250
  def currency(self, code: str):
4400
5251
  if self.currencies is None:
@@ -4420,7 +5271,7 @@ class Exchange(object):
4420
5271
  return market
4421
5272
  return markets[0]
4422
5273
  elif (symbol.endswith('-C')) or (symbol.endswith('-P')) or (symbol.startswith('C-')) or (symbol.startswith('P-')):
4423
- return self.createExpiredOptionMarket(symbol)
5274
+ return self.create_expired_option_market(symbol)
4424
5275
  raise BadSymbol(self.id + ' does not have market symbol ' + symbol)
4425
5276
 
4426
5277
  def create_expired_option_market(self, symbol: str):
@@ -4439,21 +5290,39 @@ class Exchange(object):
4439
5290
  def create_limit_order(self, symbol: str, side: OrderSide, amount: float, price: float, params={}):
4440
5291
  return self.create_order(symbol, 'limit', side, amount, price, params)
4441
5292
 
5293
+ def create_limit_order_ws(self, symbol: str, side: OrderSide, amount: float, price: float, params={}):
5294
+ return self.create_order_ws(symbol, 'limit', side, amount, price, params)
5295
+
4442
5296
  def create_market_order(self, symbol: str, side: OrderSide, amount: float, price: Num = None, params={}):
4443
5297
  return self.create_order(symbol, 'market', side, amount, price, params)
4444
5298
 
5299
+ def create_market_order_ws(self, symbol: str, side: OrderSide, amount: float, price: Num = None, params={}):
5300
+ return self.create_order_ws(symbol, 'market', side, amount, price, params)
5301
+
4445
5302
  def create_limit_buy_order(self, symbol: str, amount: float, price: float, params={}):
4446
5303
  return self.create_order(symbol, 'limit', 'buy', amount, price, params)
4447
5304
 
5305
+ def create_limit_buy_order_ws(self, symbol: str, amount: float, price: float, params={}):
5306
+ return self.create_order_ws(symbol, 'limit', 'buy', amount, price, params)
5307
+
4448
5308
  def create_limit_sell_order(self, symbol: str, amount: float, price: float, params={}):
4449
5309
  return self.create_order(symbol, 'limit', 'sell', amount, price, params)
4450
5310
 
5311
+ def create_limit_sell_order_ws(self, symbol: str, amount: float, price: float, params={}):
5312
+ return self.create_order_ws(symbol, 'limit', 'sell', amount, price, params)
5313
+
4451
5314
  def create_market_buy_order(self, symbol: str, amount: float, params={}):
4452
5315
  return self.create_order(symbol, 'market', 'buy', amount, None, params)
4453
5316
 
5317
+ def create_market_buy_order_ws(self, symbol: str, amount: float, params={}):
5318
+ return self.create_order_ws(symbol, 'market', 'buy', amount, None, params)
5319
+
4454
5320
  def create_market_sell_order(self, symbol: str, amount: float, params={}):
4455
5321
  return self.create_order(symbol, 'market', 'sell', amount, None, params)
4456
5322
 
5323
+ def create_market_sell_order_ws(self, symbol: str, amount: float, params={}):
5324
+ return self.create_order_ws(symbol, 'market', 'sell', amount, None, params)
5325
+
4457
5326
  def cost_to_precision(self, symbol: str, cost):
4458
5327
  market = self.market(symbol)
4459
5328
  return self.decimal_to_precision(cost, TRUNCATE, market['precision']['price'], self.precisionMode, self.paddingMode)
@@ -4484,9 +5353,10 @@ class Exchange(object):
4484
5353
  networkItem = self.safe_dict(networks, networkCode, {})
4485
5354
  precision = self.safe_value(networkItem, 'precision', precision)
4486
5355
  if precision is None:
4487
- return self.forceString(fee)
5356
+ return self.force_string(fee)
4488
5357
  else:
4489
- return self.decimal_to_precision(fee, ROUND, precision, self.precisionMode, self.paddingMode)
5358
+ roundingMode = self.safe_integer(self.options, 'currencyToPrecisionRoundingMode', ROUND)
5359
+ return self.decimal_to_precision(fee, roundingMode, precision, self.precisionMode, self.paddingMode)
4490
5360
 
4491
5361
  def force_string(self, value):
4492
5362
  if not isinstance(value, str):
@@ -4502,7 +5372,7 @@ class Exchange(object):
4502
5372
  def is_significant_precision(self):
4503
5373
  return self.precisionMode == SIGNIFICANT_DIGITS
4504
5374
 
4505
- def safe_number(self, obj: object, key: IndexType, defaultNumber: Num = None):
5375
+ def safe_number(self, obj, key: IndexType, defaultNumber: Num = None):
4506
5376
  value = self.safe_string(obj, key)
4507
5377
  return self.parse_number(value, defaultNumber)
4508
5378
 
@@ -4512,7 +5382,7 @@ class Exchange(object):
4512
5382
 
4513
5383
  def parse_precision(self, precision: str):
4514
5384
  """
4515
- * @ignore
5385
+ @ignore
4516
5386
  :param str precision: The number of digits to the right of the decimal
4517
5387
  :returns str: a string number equal to 1e-precision
4518
5388
  """
@@ -4526,6 +5396,25 @@ class Exchange(object):
4526
5396
  parsedPrecision = parsedPrecision + '0'
4527
5397
  return parsedPrecision + '1'
4528
5398
 
5399
+ def integer_precision_to_amount(self, precision: Str):
5400
+ """
5401
+ @ignore
5402
+ handles positive & negative numbers too. parsePrecision() does not handle negative numbers, but self method handles
5403
+ :param str precision: The number of digits to the right of the decimal
5404
+ :returns str: a string number equal to 1e-precision
5405
+ """
5406
+ if precision is None:
5407
+ return None
5408
+ if Precise.string_ge(precision, '0'):
5409
+ return self.parse_precision(precision)
5410
+ else:
5411
+ positivePrecisionString = Precise.string_abs(precision)
5412
+ positivePrecision = int(positivePrecisionString)
5413
+ parsedPrecision = '1'
5414
+ for i in range(0, positivePrecision - 1):
5415
+ parsedPrecision = parsedPrecision + '0'
5416
+ return parsedPrecision + '0'
5417
+
4529
5418
  def load_time_difference(self, params={}):
4530
5419
  serverTime = self.fetch_time(params)
4531
5420
  after = self.milliseconds()
@@ -4551,32 +5440,64 @@ class Exchange(object):
4551
5440
  query = self.extend(params, {'postOnly': True})
4552
5441
  return self.create_order(symbol, type, side, amount, price, query)
4553
5442
 
5443
+ def create_post_only_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
5444
+ if not self.has['createPostOnlyOrderWs']:
5445
+ raise NotSupported(self.id + 'createPostOnlyOrderWs() is not supported yet')
5446
+ query = self.extend(params, {'postOnly': True})
5447
+ return self.create_order_ws(symbol, type, side, amount, price, query)
5448
+
4554
5449
  def create_reduce_only_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
4555
5450
  if not self.has['createReduceOnlyOrder']:
4556
5451
  raise NotSupported(self.id + 'createReduceOnlyOrder() is not supported yet')
4557
5452
  query = self.extend(params, {'reduceOnly': True})
4558
5453
  return self.create_order(symbol, type, side, amount, price, query)
4559
5454
 
4560
- def create_stop_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, stopPrice: Num = None, params={}):
5455
+ def create_reduce_only_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
5456
+ if not self.has['createReduceOnlyOrderWs']:
5457
+ raise NotSupported(self.id + 'createReduceOnlyOrderWs() is not supported yet')
5458
+ query = self.extend(params, {'reduceOnly': True})
5459
+ return self.create_order_ws(symbol, type, side, amount, price, query)
5460
+
5461
+ def create_stop_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, triggerPrice: Num = None, params={}):
4561
5462
  if not self.has['createStopOrder']:
4562
5463
  raise NotSupported(self.id + ' createStopOrder() is not supported yet')
4563
- if stopPrice is None:
5464
+ if triggerPrice is None:
4564
5465
  raise ArgumentsRequired(self.id + ' create_stop_order() requires a stopPrice argument')
4565
- query = self.extend(params, {'stopPrice': stopPrice})
5466
+ query = self.extend(params, {'stopPrice': triggerPrice})
4566
5467
  return self.create_order(symbol, type, side, amount, price, query)
4567
5468
 
4568
- def create_stop_limit_order(self, symbol: str, side: OrderSide, amount: float, price: float, stopPrice: float, params={}):
5469
+ def create_stop_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, triggerPrice: Num = None, params={}):
5470
+ if not self.has['createStopOrderWs']:
5471
+ raise NotSupported(self.id + ' createStopOrderWs() is not supported yet')
5472
+ if triggerPrice is None:
5473
+ raise ArgumentsRequired(self.id + ' createStopOrderWs() requires a stopPrice argument')
5474
+ query = self.extend(params, {'stopPrice': triggerPrice})
5475
+ return self.create_order_ws(symbol, type, side, amount, price, query)
5476
+
5477
+ def create_stop_limit_order(self, symbol: str, side: OrderSide, amount: float, price: float, triggerPrice: float, params={}):
4569
5478
  if not self.has['createStopLimitOrder']:
4570
5479
  raise NotSupported(self.id + ' createStopLimitOrder() is not supported yet')
4571
- query = self.extend(params, {'stopPrice': stopPrice})
5480
+ query = self.extend(params, {'stopPrice': triggerPrice})
4572
5481
  return self.create_order(symbol, 'limit', side, amount, price, query)
4573
5482
 
4574
- def create_stop_market_order(self, symbol: str, side: OrderSide, amount: float, stopPrice: float, params={}):
5483
+ def create_stop_limit_order_ws(self, symbol: str, side: OrderSide, amount: float, price: float, triggerPrice: float, params={}):
5484
+ if not self.has['createStopLimitOrderWs']:
5485
+ raise NotSupported(self.id + ' createStopLimitOrderWs() is not supported yet')
5486
+ query = self.extend(params, {'stopPrice': triggerPrice})
5487
+ return self.create_order_ws(symbol, 'limit', side, amount, price, query)
5488
+
5489
+ def create_stop_market_order(self, symbol: str, side: OrderSide, amount: float, triggerPrice: float, params={}):
4575
5490
  if not self.has['createStopMarketOrder']:
4576
5491
  raise NotSupported(self.id + ' createStopMarketOrder() is not supported yet')
4577
- query = self.extend(params, {'stopPrice': stopPrice})
5492
+ query = self.extend(params, {'stopPrice': triggerPrice})
4578
5493
  return self.create_order(symbol, 'market', side, amount, None, query)
4579
5494
 
5495
+ def create_stop_market_order_ws(self, symbol: str, side: OrderSide, amount: float, triggerPrice: float, params={}):
5496
+ if not self.has['createStopMarketOrderWs']:
5497
+ raise NotSupported(self.id + ' createStopMarketOrderWs() is not supported yet')
5498
+ query = self.extend(params, {'stopPrice': triggerPrice})
5499
+ return self.create_order_ws(symbol, 'market', side, amount, None, query)
5500
+
4580
5501
  def safe_currency_code(self, currencyId: Str, currency: Currency = None):
4581
5502
  currency = self.safe_currency(currencyId, currency)
4582
5503
  return currency['code']
@@ -4614,22 +5535,23 @@ class Exchange(object):
4614
5535
  results = []
4615
5536
  if isinstance(pricesData, list):
4616
5537
  for i in range(0, len(pricesData)):
4617
- priceData = self.extend(self.parseLastPrice(pricesData[i]), params)
5538
+ priceData = self.extend(self.parse_last_price(pricesData[i]), params)
4618
5539
  results.append(priceData)
4619
5540
  else:
4620
5541
  marketIds = list(pricesData.keys())
4621
5542
  for i in range(0, len(marketIds)):
4622
5543
  marketId = marketIds[i]
4623
5544
  market = self.safe_market(marketId)
4624
- priceData = self.extend(self.parseLastPrice(pricesData[marketId], market), params)
5545
+ priceData = self.extend(self.parse_last_price(pricesData[marketId], market), params)
4625
5546
  results.append(priceData)
4626
5547
  symbols = self.market_symbols(symbols)
4627
5548
  return self.filter_by_array(results, 'symbol', symbols)
4628
5549
 
4629
- def parse_tickers(self, tickers, symbols: List[str] = None, params={}):
5550
+ def parse_tickers(self, tickers, symbols: Strings = None, params={}):
4630
5551
  #
4631
5552
  # the value of tickers is either a dict or a list
4632
5553
  #
5554
+ #
4633
5555
  # dict
4634
5556
  #
4635
5557
  # {
@@ -4663,7 +5585,7 @@ class Exchange(object):
4663
5585
  symbols = self.market_symbols(symbols)
4664
5586
  return self.filter_by_array(results, 'symbol', symbols)
4665
5587
 
4666
- def parse_deposit_addresses(self, addresses, codes: List[str] = None, indexed=True, params={}):
5588
+ def parse_deposit_addresses(self, addresses, codes: Strings = None, indexed=True, params={}):
4667
5589
  result = []
4668
5590
  for i in range(0, len(addresses)):
4669
5591
  address = self.extend(self.parse_deposit_address(addresses[i]), params)
@@ -4671,7 +5593,7 @@ class Exchange(object):
4671
5593
  if codes is not None:
4672
5594
  result = self.filter_by_array(result, 'currency', codes, False)
4673
5595
  if indexed:
4674
- return self.index_by(result, 'currency')
5596
+ result = self.filter_by_array(result, 'currency', None, indexed)
4675
5597
  return result
4676
5598
 
4677
5599
  def parse_borrow_interests(self, response, market: Market = None):
@@ -4681,6 +5603,27 @@ class Exchange(object):
4681
5603
  interests.append(self.parse_borrow_interest(row, market))
4682
5604
  return interests
4683
5605
 
5606
+ def parse_borrow_rate(self, info, currency: Currency = None):
5607
+ raise NotSupported(self.id + ' parseBorrowRate() is not supported yet')
5608
+
5609
+ def parse_borrow_rate_history(self, response, code: Str, since: Int, limit: Int):
5610
+ result = []
5611
+ for i in range(0, len(response)):
5612
+ item = response[i]
5613
+ borrowRate = self.parse_borrow_rate(item)
5614
+ result.append(borrowRate)
5615
+ sorted = self.sort_by(result, 'timestamp')
5616
+ return self.filter_by_currency_since_limit(sorted, code, since, limit)
5617
+
5618
+ def parse_isolated_borrow_rates(self, info: Any):
5619
+ result = {}
5620
+ for i in range(0, len(info)):
5621
+ item = info[i]
5622
+ borrowRate = self.parse_isolated_borrow_rate(item)
5623
+ symbol = self.safe_string(borrowRate, 'symbol')
5624
+ result[symbol] = borrowRate
5625
+ return result
5626
+
4684
5627
  def parse_funding_rate_histories(self, response, market=None, since: Int = None, limit: Int = None):
4685
5628
  rates = []
4686
5629
  for i in range(0, len(response)):
@@ -4697,22 +5640,39 @@ class Exchange(object):
4697
5640
  def parse_funding_rate(self, contract: str, market: Market = None):
4698
5641
  raise NotSupported(self.id + ' parseFundingRate() is not supported yet')
4699
5642
 
4700
- def parse_funding_rates(self, response, market: Market = None):
4701
- result = {}
5643
+ def parse_funding_rates(self, response, symbols: Strings = None):
5644
+ fundingRates = {}
4702
5645
  for i in range(0, len(response)):
4703
- parsed = self.parse_funding_rate(response[i], market)
4704
- result[parsed['symbol']] = parsed
4705
- return result
5646
+ entry = response[i]
5647
+ parsed = self.parse_funding_rate(entry)
5648
+ fundingRates[parsed['symbol']] = parsed
5649
+ return self.filter_by_array(fundingRates, 'symbol', symbols)
4706
5650
 
4707
- def is_trigger_order(self, params):
5651
+ def parse_long_short_ratio(self, info: dict, market: Market = None):
5652
+ raise NotSupported(self.id + ' parseLongShortRatio() is not supported yet')
5653
+
5654
+ def parse_long_short_ratio_history(self, response, market=None, since: Int = None, limit: Int = None):
5655
+ rates = []
5656
+ for i in range(0, len(response)):
5657
+ entry = response[i]
5658
+ rates.append(self.parse_long_short_ratio(entry, market))
5659
+ sorted = self.sort_by(rates, 'timestamp')
5660
+ symbol = None if (market is None) else market['symbol']
5661
+ return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
5662
+
5663
+ def handle_trigger_and_params(self, params):
4708
5664
  isTrigger = self.safe_bool_2(params, 'trigger', 'stop')
4709
5665
  if isTrigger:
4710
5666
  params = self.omit(params, ['trigger', 'stop'])
4711
5667
  return [isTrigger, params]
4712
5668
 
5669
+ def is_trigger_order(self, params):
5670
+ # for backwards compatibility
5671
+ return self.handle_trigger_and_params(params)
5672
+
4713
5673
  def is_post_only(self, isMarketOrder: bool, exchangeSpecificParam, params={}):
4714
5674
  """
4715
- * @ignore
5675
+ @ignore
4716
5676
  :param str type: Order type
4717
5677
  :param boolean exchangeSpecificParam: exchange specific postOnly
4718
5678
  :param dict [params]: exchange specific params
@@ -4737,7 +5697,7 @@ class Exchange(object):
4737
5697
 
4738
5698
  def handle_post_only(self, isMarketOrder: bool, exchangeSpecificPostOnlyOption: bool, params: Any = {}):
4739
5699
  """
4740
- * @ignore
5700
+ @ignore
4741
5701
  :param str type: Order type
4742
5702
  :param boolean exchangeSpecificBoolean: exchange specific postOnly
4743
5703
  :param dict [params]: exchange specific params
@@ -4761,7 +5721,7 @@ class Exchange(object):
4761
5721
  return [True, params]
4762
5722
  return [False, params]
4763
5723
 
4764
- def fetch_last_prices(self, symbols: List[str] = None, params={}):
5724
+ def fetch_last_prices(self, symbols: Strings = None, params={}):
4765
5725
  raise NotSupported(self.id + ' fetchLastPrices() is not supported yet')
4766
5726
 
4767
5727
  def fetch_trading_fees(self, params={}):
@@ -4773,12 +5733,24 @@ class Exchange(object):
4773
5733
  def fetch_trading_fee(self, symbol: str, params={}):
4774
5734
  if not self.has['fetchTradingFees']:
4775
5735
  raise NotSupported(self.id + ' fetchTradingFee() is not supported yet')
4776
- return self.fetch_trading_fees(params)
5736
+ fees = self.fetch_trading_fees(params)
5737
+ return self.safe_dict(fees, symbol)
5738
+
5739
+ def fetch_convert_currencies(self, params={}):
5740
+ raise NotSupported(self.id + ' fetchConvertCurrencies() is not supported yet')
4777
5741
 
4778
5742
  def parse_open_interest(self, interest, market: Market = None):
4779
5743
  raise NotSupported(self.id + ' parseOpenInterest() is not supported yet')
4780
5744
 
4781
- def parse_open_interests(self, response, market=None, since: Int = None, limit: Int = None):
5745
+ def parse_open_interests(self, response, symbols: Strings = None):
5746
+ result = {}
5747
+ for i in range(0, len(response)):
5748
+ entry = response[i]
5749
+ parsed = self.parse_open_interest(entry)
5750
+ result[parsed['symbol']] = parsed
5751
+ return self.filter_by_array(result, 'symbol', symbols)
5752
+
5753
+ def parse_open_interests_history(self, response, market=None, since: Int = None, limit: Int = None):
4782
5754
  interests = []
4783
5755
  for i in range(0, len(response)):
4784
5756
  entry = response[i]
@@ -4795,7 +5767,7 @@ class Exchange(object):
4795
5767
  symbol = market['symbol']
4796
5768
  if not market['contract']:
4797
5769
  raise BadSymbol(self.id + ' fetchFundingRate() supports contract markets only')
4798
- rates = self.fetchFundingRates([symbol], params)
5770
+ rates = self.fetch_funding_rates([symbol], params)
4799
5771
  rate = self.safe_value(rates, symbol)
4800
5772
  if rate is None:
4801
5773
  raise NullResponse(self.id + ' fetchFundingRate() returned no data for ' + symbol)
@@ -4804,6 +5776,22 @@ class Exchange(object):
4804
5776
  else:
4805
5777
  raise NotSupported(self.id + ' fetchFundingRate() is not supported yet')
4806
5778
 
5779
+ def fetch_funding_interval(self, symbol: str, params={}):
5780
+ if self.has['fetchFundingIntervals']:
5781
+ self.load_markets()
5782
+ market = self.market(symbol)
5783
+ symbol = market['symbol']
5784
+ if not market['contract']:
5785
+ raise BadSymbol(self.id + ' fetchFundingInterval() supports contract markets only')
5786
+ rates = self.fetch_funding_intervals([symbol], params)
5787
+ rate = self.safe_value(rates, symbol)
5788
+ if rate is None:
5789
+ raise NullResponse(self.id + ' fetchFundingInterval() returned no data for ' + symbol)
5790
+ else:
5791
+ return rate
5792
+ else:
5793
+ raise NotSupported(self.id + ' fetchFundingInterval() is not supported yet')
5794
+
4807
5795
  def fetch_mark_ohlcv(self, symbol, timeframe='1m', since: Int = None, limit: Int = None, params={}):
4808
5796
  """
4809
5797
  fetches historical mark price candlestick data containing the open, high, low, and close price of a market
@@ -4815,7 +5803,7 @@ class Exchange(object):
4815
5803
  :returns float[][]: A list of candles ordered, open, high, low, close, None
4816
5804
  """
4817
5805
  if self.has['fetchMarkOHLCV']:
4818
- request = {
5806
+ request: dict = {
4819
5807
  'price': 'mark',
4820
5808
  }
4821
5809
  return self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))
@@ -4830,10 +5818,10 @@ class Exchange(object):
4830
5818
  :param int [since]: timestamp in ms of the earliest candle to fetch
4831
5819
  :param int [limit]: the maximum amount of candles to fetch
4832
5820
  :param dict [params]: extra parameters specific to the exchange API endpoint
4833
- * @returns {} A list of candles ordered, open, high, low, close, None
5821
+ @returns {} A list of candles ordered, open, high, low, close, None
4834
5822
  """
4835
5823
  if self.has['fetchIndexOHLCV']:
4836
- request = {
5824
+ request: dict = {
4837
5825
  'price': 'index',
4838
5826
  }
4839
5827
  return self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))
@@ -4851,7 +5839,7 @@ class Exchange(object):
4851
5839
  :returns float[][]: A list of candles ordered, open, high, low, close, None
4852
5840
  """
4853
5841
  if self.has['fetchPremiumIndexOHLCV']:
4854
- request = {
5842
+ request: dict = {
4855
5843
  'price': 'premiumIndex',
4856
5844
  }
4857
5845
  return self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))
@@ -4860,8 +5848,8 @@ class Exchange(object):
4860
5848
 
4861
5849
  def handle_time_in_force(self, params={}):
4862
5850
  """
4863
- * @ignore
4864
- * Must add timeInForce to self.options to use self method
5851
+ @ignore
5852
+ Must add timeInForce to self.options to use self method
4865
5853
  :returns str: returns the exchange specific value for timeInForce
4866
5854
  """
4867
5855
  timeInForce = self.safe_string_upper(params, 'timeInForce') # supported values GTC, IOC, PO
@@ -4874,8 +5862,8 @@ class Exchange(object):
4874
5862
 
4875
5863
  def convert_type_to_account(self, account):
4876
5864
  """
4877
- * @ignore
4878
- * Must add accountsByType to self.options to use self method
5865
+ @ignore
5866
+ Must add accountsByType to self.options to use self method
4879
5867
  :param str account: key for account name in self.options['accountsByType']
4880
5868
  :returns: the exchange specific account name or the isolated margin id for transfers
4881
5869
  """
@@ -4891,7 +5879,7 @@ class Exchange(object):
4891
5879
 
4892
5880
  def check_required_argument(self, methodName: str, argument, argumentName, options=[]):
4893
5881
  """
4894
- * @ignore
5882
+ @ignore
4895
5883
  :param str methodName: the name of the method that the argument is being checked for
4896
5884
  :param str argument: the argument's actual value provided
4897
5885
  :param str argumentName: the name of the argument being checked(for logging purposes)
@@ -4908,7 +5896,7 @@ class Exchange(object):
4908
5896
 
4909
5897
  def check_required_margin_argument(self, methodName: str, symbol: Str, marginMode: str):
4910
5898
  """
4911
- * @ignore
5899
+ @ignore
4912
5900
  :param str symbol: unified symbol of the market
4913
5901
  :param str methodName: name of the method that requires a symbol
4914
5902
  :param str marginMode: is either 'isolated' or 'cross'
@@ -4918,16 +5906,15 @@ class Exchange(object):
4918
5906
  elif (marginMode == 'cross') and (symbol is not None):
4919
5907
  raise ArgumentsRequired(self.id + ' ' + methodName + '() cannot have a symbol argument for cross margin')
4920
5908
 
4921
- def parse_deposit_withdraw_fees(self, response, codes: List[str] = None, currencyIdKey=None):
5909
+ def parse_deposit_withdraw_fees(self, response, codes: Strings = None, currencyIdKey=None):
4922
5910
  """
4923
- * @ignore
5911
+ @ignore
4924
5912
  :param object[]|dict response: unparsed response from the exchange
4925
5913
  :param str[]|None codes: the unified currency codes to fetch transactions fees for, returns all currencies when None
4926
5914
  :param str currencyIdKey: *should only be None when response is a dictionary* the object key that corresponds to the currency id
4927
5915
  :returns dict: objects with withdraw and deposit fees, indexed by currency codes
4928
5916
  """
4929
5917
  depositWithdrawFees = {}
4930
- codes = self.marketCodes(codes)
4931
5918
  isArray = isinstance(response, list)
4932
5919
  responseKeys = response
4933
5920
  if not isArray:
@@ -4936,10 +5923,10 @@ class Exchange(object):
4936
5923
  entry = responseKeys[i]
4937
5924
  dictionary = entry if isArray else response[entry]
4938
5925
  currencyId = self.safe_string(dictionary, currencyIdKey) if isArray else entry
4939
- currency = self.safe_value(self.currencies_by_id, currencyId)
4940
- code = self.safe_string(currency, 'code', currencyId)
5926
+ currency = self.safe_currency(currencyId)
5927
+ code = self.safe_string(currency, 'code')
4941
5928
  if (codes is None) or (self.in_array(code, codes)):
4942
- depositWithdrawFees[code] = self.parseDepositWithdrawFee(dictionary, currency)
5929
+ depositWithdrawFees[code] = self.parse_deposit_withdraw_fee(dictionary, currency)
4943
5930
  return depositWithdrawFees
4944
5931
 
4945
5932
  def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
@@ -4961,7 +5948,7 @@ class Exchange(object):
4961
5948
 
4962
5949
  def assign_default_deposit_withdraw_fees(self, fee, currency=None):
4963
5950
  """
4964
- * @ignore
5951
+ @ignore
4965
5952
  Takes a depositWithdrawFee structure and assigns the default values for withdraw and deposit
4966
5953
  :param dict fee: A deposit withdraw fee structure
4967
5954
  :param dict currency: A currency structure, the response from self.currency()
@@ -4986,7 +5973,7 @@ class Exchange(object):
4986
5973
 
4987
5974
  def parse_incomes(self, incomes, market=None, since: Int = None, limit: Int = None):
4988
5975
  """
4989
- * @ignore
5976
+ @ignore
4990
5977
  parses funding fee info from exchange response
4991
5978
  :param dict[] incomes: each item describes once instance of currency being received or paid
4992
5979
  :param dict market: ccxt market
@@ -5002,7 +5989,7 @@ class Exchange(object):
5002
5989
  sorted = self.sort_by(result, 'timestamp')
5003
5990
  return self.filter_by_since_limit(sorted, since, limit)
5004
5991
 
5005
- def get_market_from_symbols(self, symbols: List[str] = None):
5992
+ def get_market_from_symbols(self, symbols: Strings = None):
5006
5993
  if symbols is None:
5007
5994
  return None
5008
5995
  firstMarket = self.safe_string(symbols, 0)
@@ -5017,7 +6004,7 @@ class Exchange(object):
5017
6004
 
5018
6005
  def fetch_transactions(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
5019
6006
  """
5020
- * @deprecated
6007
+ @deprecated
5021
6008
  *DEPRECATED* use fetchDepositsWithdrawals instead
5022
6009
  :param str code: unified currency code for the currency of the deposit/withdrawals, default is None
5023
6010
  :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
@@ -5026,20 +6013,20 @@ class Exchange(object):
5026
6013
  :returns dict: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
5027
6014
  """
5028
6015
  if self.has['fetchDepositsWithdrawals']:
5029
- return self.fetchDepositsWithdrawals(code, since, limit, params)
6016
+ return self.fetch_deposits_withdrawals(code, since, limit, params)
5030
6017
  else:
5031
6018
  raise NotSupported(self.id + ' fetchTransactions() is not supported yet')
5032
6019
 
5033
6020
  def filter_by_array_positions(self, objects, key: IndexType, values=None, indexed=True):
5034
6021
  """
5035
- * @ignore
6022
+ @ignore
5036
6023
  Typed wrapper for filterByArray that returns a list of positions
5037
6024
  """
5038
6025
  return self.filter_by_array(objects, key, values, indexed)
5039
6026
 
5040
6027
  def filter_by_array_tickers(self, objects, key: IndexType, values=None, indexed=True):
5041
6028
  """
5042
- * @ignore
6029
+ @ignore
5043
6030
  Typed wrapper for filterByArray that returns a dictionary of tickers
5044
6031
  """
5045
6032
  return self.filter_by_array(objects, key, values, indexed)
@@ -5059,7 +6046,7 @@ class Exchange(object):
5059
6046
  maxEntriesPerRequest = 1000 # default to 1000
5060
6047
  return [maxEntriesPerRequest, params]
5061
6048
 
5062
- def fetch_paginated_call_dynamic(self, method: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}, maxEntriesPerRequest: Int = None):
6049
+ def fetch_paginated_call_dynamic(self, method: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}, maxEntriesPerRequest: Int = None, removeRepeated=True):
5063
6050
  maxCalls = None
5064
6051
  maxCalls, params = self.handle_option_and_params(params, method, 'paginationCalls', 10)
5065
6052
  maxRetries = None
@@ -5067,6 +6054,8 @@ class Exchange(object):
5067
6054
  paginationDirection = None
5068
6055
  paginationDirection, params = self.handle_option_and_params(params, method, 'paginationDirection', 'backward')
5069
6056
  paginationTimestamp = None
6057
+ removeRepeatedOption = removeRepeated
6058
+ removeRepeatedOption, params = self.handle_option_and_params(params, method, 'removeRepeated', removeRepeated)
5070
6059
  calls = 0
5071
6060
  result = []
5072
6061
  errors = 0
@@ -5113,14 +6102,16 @@ class Exchange(object):
5113
6102
  errors = 0
5114
6103
  result = self.array_concat(result, response)
5115
6104
  last = self.safe_value(response, responseLength - 1)
5116
- paginationTimestamp = self.safe_integer(last, 'timestamp') - 1
6105
+ paginationTimestamp = self.safe_integer(last, 'timestamp') + 1
5117
6106
  if (until is not None) and (paginationTimestamp >= until):
5118
6107
  break
5119
6108
  except Exception as e:
5120
6109
  errors += 1
5121
6110
  if errors > maxRetries:
5122
6111
  raise e
5123
- uniqueResults = self.remove_repeated_elements_from_array(result)
6112
+ uniqueResults = result
6113
+ if removeRepeatedOption:
6114
+ uniqueResults = self.remove_repeated_elements_from_array(result)
5124
6115
  key = 0 if (method == 'fetchOHLCV') else 'timestamp'
5125
6116
  return self.filter_by_since_limit(uniqueResults, since, limit, key)
5126
6117
 
@@ -5128,18 +6119,19 @@ class Exchange(object):
5128
6119
  maxRetries = None
5129
6120
  maxRetries, params = self.handle_option_and_params(params, method, 'maxRetries', 3)
5130
6121
  errors = 0
5131
- try:
5132
- if timeframe and method != 'fetchFundingRateHistory':
5133
- return getattr(self, method)(symbol, timeframe, since, limit, params)
5134
- else:
5135
- return getattr(self, method)(symbol, since, limit, params)
5136
- except Exception as e:
5137
- if isinstance(e, RateLimitExceeded):
5138
- raise e # if we are rate limited, we should not retry and fail fast
5139
- errors += 1
5140
- if errors > maxRetries:
5141
- raise e
5142
- return None
6122
+ while(errors <= maxRetries):
6123
+ try:
6124
+ if timeframe and method != 'fetchFundingRateHistory':
6125
+ return getattr(self, method)(symbol, timeframe, since, limit, params)
6126
+ else:
6127
+ return getattr(self, method)(symbol, since, limit, params)
6128
+ except Exception as e:
6129
+ if isinstance(e, RateLimitExceeded):
6130
+ raise e # if we are rate limited, we should not retry and fail fast
6131
+ errors += 1
6132
+ if errors > maxRetries:
6133
+ raise e
6134
+ return []
5143
6135
 
5144
6136
  def fetch_paginated_call_deterministic(self, method: str, symbol: Str = None, since: Int = None, limit: Int = None, timeframe: Str = None, params={}, maxEntriesPerRequest=None):
5145
6137
  maxCalls = None
@@ -5152,6 +6144,8 @@ class Exchange(object):
5152
6144
  currentSince = current - (maxCalls * step) - 1
5153
6145
  if since is not None:
5154
6146
  currentSince = max(currentSince, since)
6147
+ else:
6148
+ currentSince = max(currentSince, 1241440531000) # avoid timestamps older than 2009
5155
6149
  until = self.safe_integer_2(params, 'until', 'till') # do not omit it here
5156
6150
  if until is not None:
5157
6151
  requiredCalls = int(math.ceil((until - since)) / step)
@@ -5160,6 +6154,8 @@ class Exchange(object):
5160
6154
  for i in range(0, maxCalls):
5161
6155
  if (until is not None) and (currentSince >= until):
5162
6156
  break
6157
+ if currentSince >= current:
6158
+ break
5163
6159
  tasks.append(self.safe_deterministic_call(method, symbol, currentSince, maxEntriesPerRequest, timeframe, params))
5164
6160
  currentSince = self.sum(currentSince, step) - 1
5165
6161
  results = tasks
@@ -5180,28 +6176,44 @@ class Exchange(object):
5180
6176
  i = 0
5181
6177
  errors = 0
5182
6178
  result = []
6179
+ timeframe = self.safe_string(params, 'timeframe')
6180
+ params = self.omit(params, 'timeframe') # reading the timeframe from the method arguments to avoid changing the signature
5183
6181
  while(i < maxCalls):
5184
6182
  try:
5185
6183
  if cursorValue is not None:
5186
6184
  if cursorIncrement is not None:
5187
- cursorValue = self.parseToInt(cursorValue) + cursorIncrement
6185
+ cursorValue = self.parse_to_int(cursorValue) + cursorIncrement
5188
6186
  params[cursorSent] = cursorValue
5189
6187
  response = None
5190
6188
  if method == 'fetchAccounts':
5191
6189
  response = getattr(self, method)(params)
6190
+ elif method == 'getLeverageTiersPaginated' or method == 'fetchPositions':
6191
+ response = getattr(self, method)(symbol, params)
6192
+ elif method == 'fetchOpenInterestHistory':
6193
+ response = getattr(self, method)(symbol, timeframe, since, maxEntriesPerRequest, params)
5192
6194
  else:
5193
6195
  response = getattr(self, method)(symbol, since, maxEntriesPerRequest, params)
5194
6196
  errors = 0
5195
6197
  responseLength = len(response)
5196
6198
  if self.verbose:
5197
- iteration = (i + str(1))
5198
- cursorMessage = 'Cursor pagination call ' + iteration + ' method ' + method + ' response length ' + str(responseLength) + ' cursor ' + cursorValue
6199
+ cursorString = '' if (cursorValue is None) else cursorValue
6200
+ iteration = (i + 1)
6201
+ cursorMessage = 'Cursor pagination call ' + str(iteration) + ' method ' + method + ' response length ' + str(responseLength) + ' cursor ' + cursorString
5199
6202
  self.log(cursorMessage)
5200
6203
  if responseLength == 0:
5201
6204
  break
5202
6205
  result = self.array_concat(result, response)
5203
- last = self.safe_value(response, responseLength - 1)
5204
- cursorValue = self.safe_value(last['info'], cursorReceived)
6206
+ last = self.safe_dict(response, responseLength - 1)
6207
+ # cursorValue = self.safe_value(last['info'], cursorReceived)
6208
+ cursorValue = None # search for the cursor
6209
+ for j in range(0, responseLength):
6210
+ index = responseLength - j - 1
6211
+ entry = self.safe_dict(response, index)
6212
+ info = self.safe_dict(entry, 'info')
6213
+ cursor = self.safe_value(info, cursorReceived)
6214
+ if cursor is not None:
6215
+ cursorValue = cursor
6216
+ break
5205
6217
  if cursorValue is None:
5206
6218
  break
5207
6219
  lastTimestamp = self.safe_integer(last, 'timestamp')
@@ -5212,7 +6224,7 @@ class Exchange(object):
5212
6224
  if errors > maxRetries:
5213
6225
  raise e
5214
6226
  i += 1
5215
- sorted = self.sortCursorPaginatedResult(result)
6227
+ sorted = self.sort_cursor_paginated_result(result)
5216
6228
  key = 0 if (method == 'fetchOHLCV') else 'timestamp'
5217
6229
  return self.filter_by_since_limit(sorted, since, limit, key)
5218
6230
 
@@ -5243,7 +6255,7 @@ class Exchange(object):
5243
6255
  if errors > maxRetries:
5244
6256
  raise e
5245
6257
  i += 1
5246
- sorted = self.sortCursorPaginatedResult(result)
6258
+ sorted = self.sort_cursor_paginated_result(result)
5247
6259
  key = 0 if (method == 'fetchOHLCV') else 'timestamp'
5248
6260
  return self.filter_by_since_limit(sorted, since, limit, key)
5249
6261
 
@@ -5278,13 +6290,16 @@ class Exchange(object):
5278
6290
  def handle_until_option(self, key: str, request, params, multiplier=1):
5279
6291
  until = self.safe_integer_2(params, 'until', 'till')
5280
6292
  if until is not None:
5281
- request[key] = self.parseToInt(until * multiplier)
6293
+ request[key] = self.parse_to_int(until * multiplier)
5282
6294
  params = self.omit(params, ['until', 'till'])
5283
6295
  return [request, params]
5284
6296
 
5285
- def safe_open_interest(self, interest, market: Market = None):
6297
+ def safe_open_interest(self, interest: dict, market: Market = None):
6298
+ symbol = self.safe_string(interest, 'symbol')
6299
+ if symbol is None:
6300
+ symbol = self.safe_string(market, 'symbol')
5286
6301
  return self.extend(interest, {
5287
- 'symbol': self.safe_string(market, 'symbol'),
6302
+ 'symbol': symbol,
5288
6303
  'baseVolume': self.safe_number(interest, 'baseVolume'), # deprecated
5289
6304
  'quoteVolume': self.safe_number(interest, 'quoteVolume'), # deprecated
5290
6305
  'openInterestAmount': self.safe_number(interest, 'openInterestAmount'),
@@ -5297,9 +6312,9 @@ class Exchange(object):
5297
6312
  def parse_liquidation(self, liquidation, market: Market = None):
5298
6313
  raise NotSupported(self.id + ' parseLiquidation() is not supported yet')
5299
6314
 
5300
- def parse_liquidations(self, liquidations, market=None, since: Int = None, limit: Int = None):
6315
+ def parse_liquidations(self, liquidations: List[dict], market: Market = None, since: Int = None, limit: Int = None):
5301
6316
  """
5302
- * @ignore
6317
+ @ignore
5303
6318
  parses liquidation info from the exchange response
5304
6319
  :param dict[] liquidations: each item describes an instance of a liquidation event
5305
6320
  :param dict market: ccxt market
@@ -5310,17 +6325,33 @@ class Exchange(object):
5310
6325
  result = []
5311
6326
  for i in range(0, len(liquidations)):
5312
6327
  entry = liquidations[i]
5313
- parsed = self.parseLiquidation(entry, market)
6328
+ parsed = self.parse_liquidation(entry, market)
5314
6329
  result.append(parsed)
5315
6330
  sorted = self.sort_by(result, 'timestamp')
5316
6331
  symbol = self.safe_string(market, 'symbol')
5317
6332
  return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
5318
6333
 
5319
- def parse_greeks(self, greeks, market: Market = None):
6334
+ def parse_greeks(self, greeks: dict, market: Market = None):
5320
6335
  raise NotSupported(self.id + ' parseGreeks() is not supported yet')
5321
6336
 
6337
+ def parse_option(self, chain: dict, currency: Currency = None, market: Market = None):
6338
+ raise NotSupported(self.id + ' parseOption() is not supported yet')
6339
+
6340
+ def parse_option_chain(self, response: List[object], currencyKey: Str = None, symbolKey: Str = None):
6341
+ optionStructures = {}
6342
+ for i in range(0, len(response)):
6343
+ info = response[i]
6344
+ currencyId = self.safe_string(info, currencyKey)
6345
+ currency = self.safe_currency(currencyId)
6346
+ marketId = self.safe_string(info, symbolKey)
6347
+ market = self.safe_market(marketId, None, None, 'option')
6348
+ optionStructures[market['symbol']] = self.parse_option(info, currency, market)
6349
+ return optionStructures
6350
+
5322
6351
  def parse_margin_modes(self, response: List[object], symbols: List[str] = None, symbolKey: Str = None, marketType: MarketType = None):
5323
6352
  marginModeStructures = {}
6353
+ if marketType is None:
6354
+ marketType = 'swap' # default to swap
5324
6355
  for i in range(0, len(response)):
5325
6356
  info = response[i]
5326
6357
  marketId = self.safe_string(info, symbolKey)
@@ -5329,11 +6360,13 @@ class Exchange(object):
5329
6360
  marginModeStructures[market['symbol']] = self.parse_margin_mode(info, market)
5330
6361
  return marginModeStructures
5331
6362
 
5332
- def parse_margin_mode(self, marginMode, market: Market = None):
6363
+ def parse_margin_mode(self, marginMode: dict, market: Market = None):
5333
6364
  raise NotSupported(self.id + ' parseMarginMode() is not supported yet')
5334
6365
 
5335
6366
  def parse_leverages(self, response: List[object], symbols: List[str] = None, symbolKey: Str = None, marketType: MarketType = None):
5336
6367
  leverageStructures = {}
6368
+ if marketType is None:
6369
+ marketType = 'swap' # default to swap
5337
6370
  for i in range(0, len(response)):
5338
6371
  info = response[i]
5339
6372
  marketId = self.safe_string(info, symbolKey)
@@ -5342,5 +6375,219 @@ class Exchange(object):
5342
6375
  leverageStructures[market['symbol']] = self.parse_leverage(info, market)
5343
6376
  return leverageStructures
5344
6377
 
5345
- def parse_leverage(self, leverage, market: Market = None):
6378
+ def parse_leverage(self, leverage: dict, market: Market = None):
5346
6379
  raise NotSupported(self.id + ' parseLeverage() is not supported yet')
6380
+
6381
+ def parse_conversions(self, conversions: List[Any], code: Str = None, fromCurrencyKey: Str = None, toCurrencyKey: Str = None, since: Int = None, limit: Int = None, params={}):
6382
+ conversions = self.to_array(conversions)
6383
+ result = []
6384
+ fromCurrency = None
6385
+ toCurrency = None
6386
+ for i in range(0, len(conversions)):
6387
+ entry = conversions[i]
6388
+ fromId = self.safe_string(entry, fromCurrencyKey)
6389
+ toId = self.safe_string(entry, toCurrencyKey)
6390
+ if fromId is not None:
6391
+ fromCurrency = self.safe_currency(fromId)
6392
+ if toId is not None:
6393
+ toCurrency = self.safe_currency(toId)
6394
+ conversion = self.extend(self.parse_conversion(entry, fromCurrency, toCurrency), params)
6395
+ result.append(conversion)
6396
+ sorted = self.sort_by(result, 'timestamp')
6397
+ currency = None
6398
+ if code is not None:
6399
+ currency = self.safe_currency(code)
6400
+ code = currency['code']
6401
+ if code is None:
6402
+ return self.filter_by_since_limit(sorted, since, limit)
6403
+ fromConversion = self.filter_by(sorted, 'fromCurrency', code)
6404
+ toConversion = self.filter_by(sorted, 'toCurrency', code)
6405
+ both = self.array_concat(fromConversion, toConversion)
6406
+ return self.filter_by_since_limit(both, since, limit)
6407
+
6408
+ def parse_conversion(self, conversion: dict, fromCurrency: Currency = None, toCurrency: Currency = None):
6409
+ raise NotSupported(self.id + ' parseConversion() is not supported yet')
6410
+
6411
+ def convert_expire_date(self, date: str):
6412
+ # parse YYMMDD to datetime string
6413
+ year = date[0:2]
6414
+ month = date[2:4]
6415
+ day = date[4:6]
6416
+ reconstructedDate = '20' + year + '-' + month + '-' + day + 'T00:00:00Z'
6417
+ return reconstructedDate
6418
+
6419
+ def convert_expire_date_to_market_id_date(self, date: str):
6420
+ # parse 240119 to 19JAN24
6421
+ year = date[0:2]
6422
+ monthRaw = date[2:4]
6423
+ month = None
6424
+ day = date[4:6]
6425
+ if monthRaw == '01':
6426
+ month = 'JAN'
6427
+ elif monthRaw == '02':
6428
+ month = 'FEB'
6429
+ elif monthRaw == '03':
6430
+ month = 'MAR'
6431
+ elif monthRaw == '04':
6432
+ month = 'APR'
6433
+ elif monthRaw == '05':
6434
+ month = 'MAY'
6435
+ elif monthRaw == '06':
6436
+ month = 'JUN'
6437
+ elif monthRaw == '07':
6438
+ month = 'JUL'
6439
+ elif monthRaw == '08':
6440
+ month = 'AUG'
6441
+ elif monthRaw == '09':
6442
+ month = 'SEP'
6443
+ elif monthRaw == '10':
6444
+ month = 'OCT'
6445
+ elif monthRaw == '11':
6446
+ month = 'NOV'
6447
+ elif monthRaw == '12':
6448
+ month = 'DEC'
6449
+ reconstructedDate = day + month + year
6450
+ return reconstructedDate
6451
+
6452
+ def convert_market_id_expire_date(self, date: str):
6453
+ # parse 03JAN24 to 240103
6454
+ monthMappping = {
6455
+ 'JAN': '01',
6456
+ 'FEB': '02',
6457
+ 'MAR': '03',
6458
+ 'APR': '04',
6459
+ 'MAY': '05',
6460
+ 'JUN': '06',
6461
+ 'JUL': '07',
6462
+ 'AUG': '08',
6463
+ 'SEP': '09',
6464
+ 'OCT': '10',
6465
+ 'NOV': '11',
6466
+ 'DEC': '12',
6467
+ }
6468
+ # if exchange omits first zero and provides i.e. '3JAN24' instead of '03JAN24'
6469
+ if len(date) == 6:
6470
+ date = '0' + date
6471
+ year = date[0:2]
6472
+ monthName = date[2:5]
6473
+ month = self.safe_string(monthMappping, monthName)
6474
+ day = date[5:7]
6475
+ reconstructedDate = day + month + year
6476
+ return reconstructedDate
6477
+
6478
+ def fetch_position_history(self, symbol: str, since: Int = None, limit: Int = None, params={}):
6479
+ """
6480
+ fetches the history of margin added or reduced from contract isolated positions
6481
+ :param str [symbol]: unified market symbol
6482
+ :param int [since]: timestamp in ms of the position
6483
+ :param int [limit]: the maximum amount of candles to fetch, default=1000
6484
+ :param dict params: extra parameters specific to the exchange api endpoint
6485
+ :returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
6486
+ """
6487
+ if self.has['fetchPositionsHistory']:
6488
+ positions = self.fetch_positions_history([symbol], since, limit, params)
6489
+ return positions
6490
+ else:
6491
+ raise NotSupported(self.id + ' fetchPositionHistory() is not supported yet')
6492
+
6493
+ def fetch_positions_history(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}):
6494
+ """
6495
+ fetches the history of margin added or reduced from contract isolated positions
6496
+ :param str [symbol]: unified market symbol
6497
+ :param int [since]: timestamp in ms of the position
6498
+ :param int [limit]: the maximum amount of candles to fetch, default=1000
6499
+ :param dict params: extra parameters specific to the exchange api endpoint
6500
+ :returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
6501
+ """
6502
+ raise NotSupported(self.id + ' fetchPositionsHistory() is not supported yet')
6503
+
6504
+ def parse_margin_modification(self, data: dict, market: Market = None):
6505
+ raise NotSupported(self.id + ' parseMarginModification() is not supported yet')
6506
+
6507
+ def parse_margin_modifications(self, response: List[object], symbols: Strings = None, symbolKey: Str = None, marketType: MarketType = None):
6508
+ marginModifications = []
6509
+ for i in range(0, len(response)):
6510
+ info = response[i]
6511
+ marketId = self.safe_string(info, symbolKey)
6512
+ market = self.safe_market(marketId, None, None, marketType)
6513
+ if (symbols is None) or self.in_array(market['symbol'], symbols):
6514
+ marginModifications.append(self.parse_margin_modification(info, market))
6515
+ return marginModifications
6516
+
6517
+ def fetch_transfer(self, id: str, code: Str = None, params={}):
6518
+ """
6519
+ fetches a transfer
6520
+ :param str id: transfer id
6521
+ :param [str] code: unified currency code
6522
+ :param dict params: extra parameters specific to the exchange api endpoint
6523
+ :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
6524
+ """
6525
+ raise NotSupported(self.id + ' fetchTransfer() is not supported yet')
6526
+
6527
+ def fetch_transfers(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
6528
+ """
6529
+ fetches a transfer
6530
+ :param str id: transfer id
6531
+ :param int [since]: timestamp in ms of the earliest transfer to fetch
6532
+ :param int [limit]: the maximum amount of transfers to fetch
6533
+ :param dict params: extra parameters specific to the exchange api endpoint
6534
+ :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
6535
+ """
6536
+ raise NotSupported(self.id + ' fetchTransfers() is not supported yet')
6537
+
6538
+ def clean_unsubscription(self, client, subHash: str, unsubHash: str):
6539
+ if unsubHash in client.subscriptions:
6540
+ del client.subscriptions[unsubHash]
6541
+ if subHash in client.subscriptions:
6542
+ del client.subscriptions[subHash]
6543
+ if subHash in client.futures:
6544
+ error = UnsubscribeError(self.id + ' ' + subHash)
6545
+ client.reject(error, subHash)
6546
+ client.resolve(True, unsubHash)
6547
+
6548
+ def clean_cache(self, subscription: dict):
6549
+ topic = self.safe_string(subscription, 'topic')
6550
+ symbols = self.safe_list(subscription, 'symbols', [])
6551
+ symbolsLength = len(symbols)
6552
+ if topic == 'ohlcv':
6553
+ symbolsAndTimeFrames = self.safe_list(subscription, 'symbolsAndTimeframes', [])
6554
+ for i in range(0, len(symbolsAndTimeFrames)):
6555
+ symbolAndTimeFrame = symbolsAndTimeFrames[i]
6556
+ symbol = self.safe_string(symbolAndTimeFrame, 0)
6557
+ timeframe = self.safe_string(symbolAndTimeFrame, 1)
6558
+ if symbol in self.ohlcvs:
6559
+ if timeframe in self.ohlcvs[symbol]:
6560
+ del self.ohlcvs[symbol][timeframe]
6561
+ elif symbolsLength > 0:
6562
+ for i in range(0, len(symbols)):
6563
+ symbol = symbols[i]
6564
+ if topic == 'trades':
6565
+ if symbol in self.trades:
6566
+ del self.trades[symbol]
6567
+ elif topic == 'orderbook':
6568
+ if symbol in self.orderbooks:
6569
+ del self.orderbooks[symbol]
6570
+ elif topic == 'ticker':
6571
+ if symbol in self.tickers:
6572
+ del self.tickers[symbol]
6573
+ else:
6574
+ if topic == 'myTrades':
6575
+ # don't reset self.myTrades directly here
6576
+ # because in c# we need to use a different object(thread-safe dict)
6577
+ keys = list(self.myTrades.keys())
6578
+ for i in range(0, len(keys)):
6579
+ key = keys[i]
6580
+ if key in self.myTrades:
6581
+ del self.myTrades[key]
6582
+ elif topic == 'orders':
6583
+ orderSymbols = list(self.orders.keys())
6584
+ for i in range(0, len(orderSymbols)):
6585
+ orderSymbol = orderSymbols[i]
6586
+ if orderSymbol in self.orders:
6587
+ del self.orders[orderSymbol]
6588
+ elif topic == 'ticker':
6589
+ tickerSymbols = list(self.tickers.keys())
6590
+ for i in range(0, len(tickerSymbols)):
6591
+ tickerSymbol = tickerSymbols[i]
6592
+ if tickerSymbol in self.tickers:
6593
+ del self.tickers[tickerSymbol]