ccxt 4.2.77__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 +24 -0
  44. ccxt/abstract/kucoinfutures.py +34 -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 +3030 -1087
  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 +3104 -880
  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 +238 -49
  89. ccxt/async_support/bitget.py +1513 -563
  90. ccxt/async_support/bithumb.py +199 -65
  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 +392 -148
  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 +2231 -1193
  107. ccxt/async_support/cex.py +1409 -1329
  108. ccxt/async_support/coinbase.py +1454 -287
  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 +404 -109
  125. ccxt/async_support/deribit.py +557 -323
  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 +1472 -463
  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 +1633 -268
  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 +917 -357
  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 +1776 -454
  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 +1137 -296
  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 +1722 -480
  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 +3030 -1087
  187. ccxt/binancecoinm.py +2 -1
  188. ccxt/binanceus.py +12 -1
  189. ccxt/binanceusdm.py +3 -1
  190. ccxt/bingx.py +3104 -880
  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 +238 -49
  197. ccxt/bitget.py +1513 -563
  198. ccxt/bithumb.py +198 -65
  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 +392 -148
  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 +2231 -1193
  215. ccxt/cex.py +1408 -1329
  216. ccxt/coinbase.py +1454 -287
  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 +404 -109
  233. ccxt/deribit.py +557 -323
  234. ccxt/digifinex.py +340 -223
  235. ccxt/ellipx.py +1826 -0
  236. ccxt/exmo.py +259 -128
  237. ccxt/gate.py +1472 -463
  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 +1632 -268
  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 +917 -357
  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 +1776 -454
  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 +62 -14
  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 +203 -81
  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 +965 -665
  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 +167 -31
  309. ccxt/pro/exmo.py +252 -20
  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 +92 -33
  336. ccxt/pro/poloniex.py +128 -49
  337. ccxt/pro/poloniexfutures.py +53 -32
  338. ccxt/pro/probit.py +92 -85
  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 +437 -65
  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} +456 -391
  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} +456 -393
  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 +1137 -296
  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.77.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.77.dist-info/METADATA +0 -626
  545. ccxt-4.2.77.dist-info/RECORD +0 -534
  546. {ccxt-4.2.77.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.77'
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
 
@@ -2293,8 +2724,101 @@ class Exchange(object):
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={}):
@@ -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={}):
3704
4356
  raise NotSupported(self.id + ' fetchPositions() is not supported yet')
3705
4357
 
3706
- def fetch_positions_risk(self, symbols: List[str] = None, params={}):
4358
+ def fetch_positions_ws(self, symbols: Strings = None, params={}):
4359
+ raise NotSupported(self.id + ' fetchPositions() is not supported yet')
4360
+
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({
@@ -3780,7 +4435,7 @@ class Exchange(object):
3780
4435
 
3781
4436
  def check_required_credentials(self, error=True):
3782
4437
  """
3783
- * @ignore
4438
+ @ignore
3784
4439
  :param boolean error: raise an error that a credential is required if True
3785
4440
  :returns boolean: True if all required credentials have been set, otherwise False or an error is thrown is param error=true
3786
4441
  """
@@ -3828,33 +4483,21 @@ class Exchange(object):
3828
4483
  def fetch_status(self, params={}):
3829
4484
  raise NotSupported(self.id + ' fetchStatus() is not supported yet')
3830
4485
 
3831
- def fetch_funding_fee(self, code: str, params={}):
3832
- warnOnFetchFundingFee = self.safe_bool(self.options, 'warnOnFetchFundingFee', True)
3833
- if warnOnFetchFundingFee:
3834
- 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')
3835
- return self.fetch_transaction_fee(code, params)
3836
-
3837
- def fetch_funding_fees(self, codes: List[str] = None, params={}):
3838
- warnOnFetchFundingFees = self.safe_bool(self.options, 'warnOnFetchFundingFees', True)
3839
- if warnOnFetchFundingFees:
3840
- 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')
3841
- return self.fetch_transaction_fees(codes, params)
3842
-
3843
4486
  def fetch_transaction_fee(self, code: str, params={}):
3844
4487
  if not self.has['fetchTransactionFees']:
3845
4488
  raise NotSupported(self.id + ' fetchTransactionFee() is not supported yet')
3846
4489
  return self.fetch_transaction_fees([code], params)
3847
4490
 
3848
- def fetch_transaction_fees(self, codes: List[str] = None, params={}):
4491
+ def fetch_transaction_fees(self, codes: Strings = None, params={}):
3849
4492
  raise NotSupported(self.id + ' fetchTransactionFees() is not supported yet')
3850
4493
 
3851
- def fetch_deposit_withdraw_fees(self, codes: List[str] = None, params={}):
4494
+ def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
3852
4495
  raise NotSupported(self.id + ' fetchDepositWithdrawFees() is not supported yet')
3853
4496
 
3854
4497
  def fetch_deposit_withdraw_fee(self, code: str, params={}):
3855
4498
  if not self.has['fetchDepositWithdrawFees']:
3856
4499
  raise NotSupported(self.id + ' fetchDepositWithdrawFee() is not supported yet')
3857
- fees = self.fetchDepositWithdrawFees([code], params)
4500
+ fees = self.fetch_deposit_withdraw_fees([code], params)
3858
4501
  return self.safe_value(fees, code)
3859
4502
 
3860
4503
  def get_supported_mapping(self, key, mapping={}):
@@ -3867,7 +4510,7 @@ class Exchange(object):
3867
4510
  self.load_markets()
3868
4511
  if not self.has['fetchBorrowRates']:
3869
4512
  raise NotSupported(self.id + ' fetchCrossBorrowRate() is not supported yet')
3870
- borrowRates = self.fetchCrossBorrowRates(params)
4513
+ borrowRates = self.fetch_cross_borrow_rates(params)
3871
4514
  rate = self.safe_value(borrowRates, code)
3872
4515
  if rate is None:
3873
4516
  raise ExchangeError(self.id + ' fetchCrossBorrowRate() could not find the borrow rate for currency code ' + code)
@@ -3905,36 +4548,40 @@ class Exchange(object):
3905
4548
  value = value if (value is not None) else defaultValue
3906
4549
  return [value, params]
3907
4550
 
3908
- def handle_option_and_params_2(self, params: object, methodName: str, methodName2: str, optionName: str, defaultValue=None):
3909
- # This method can be used to obtain method specific properties, i.e: self.handle_option_and_params(params, 'fetchPosition', 'marginMode', 'isolated')
3910
- defaultOptionName = 'default' + self.capitalize(optionName) # we also need to check the 'defaultXyzWhatever'
3911
- # check if params contain the key
3912
- 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)
3913
4554
  if value is not None:
3914
- params = self.omit(params, [optionName, defaultOptionName])
3915
- else:
3916
- # check if exchange has properties for self method
3917
- exchangeWideMethodOptions = self.safe_value_2(self.options, methodName, methodName2)
3918
- if exchangeWideMethodOptions is not None:
3919
- # check if the option is defined inside self method's props
3920
- value = self.safe_value_2(exchangeWideMethodOptions, optionName, defaultOptionName)
3921
- if value is None:
3922
- # if it's still None, check if global exchange-wide option exists
3923
- value = self.safe_value_2(self.options, optionName, defaultOptionName)
3924
- # if it's still None, use the default value
3925
- value = value if (value is not None) else defaultValue
3926
- 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]
3927
4562
 
3928
4563
  def handle_option(self, methodName: str, optionName: str, defaultValue=None):
3929
4564
  # eslint-disable-next-line no-unused-vars
3930
4565
  result, empty = self.handle_option_and_params({}, methodName, optionName, defaultValue)
3931
4566
  return result
3932
4567
 
3933
- 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
+ """
3934
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
3935
4582
  methodOptions = self.safe_dict(self.options, methodName)
3936
- methodType = defaultType
3937
- if methodOptions is not None:
4583
+ methodType = defaultValue
4584
+ if methodOptions is not None: # user defined methodType takes precedence over defaultValue
3938
4585
  if isinstance(methodOptions, str):
3939
4586
  methodType = methodOptions
3940
4587
  else:
@@ -3967,7 +4614,7 @@ class Exchange(object):
3967
4614
 
3968
4615
  def handle_margin_mode_and_params(self, methodName: str, params={}, defaultValue=None):
3969
4616
  """
3970
- * @ignore
4617
+ @ignore
3971
4618
  :param dict [params]: extra parameters specific to the exchange API endpoint
3972
4619
  :returns Array: the marginMode in lowercase by params["marginMode"], params["defaultMarginMode"] self.options["marginMode"] or self.options["defaultMarginMode"]
3973
4620
  """
@@ -3994,7 +4641,7 @@ class Exchange(object):
3994
4641
  return key
3995
4642
  return None
3996
4643
 
3997
- 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):
3998
4645
  # it is a stub method that must be overrided in the derived exchange classes
3999
4646
  # raise NotSupported(self.id + ' handleErrors() not implemented yet')
4000
4647
  return None
@@ -4016,18 +4663,58 @@ class Exchange(object):
4016
4663
  else:
4017
4664
  raise NotSupported(self.id + ' fetchTicker() is not supported yet')
4018
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
+
4019
4694
  def watch_ticker(self, symbol: str, params={}):
4020
4695
  raise NotSupported(self.id + ' watchTicker() is not supported yet')
4021
4696
 
4022
- 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={}):
4023
4704
  raise NotSupported(self.id + ' fetchTickers() is not supported yet')
4024
4705
 
4025
- 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={}):
4026
4707
  raise NotSupported(self.id + ' fetchOrderBooks() is not supported yet')
4027
4708
 
4028
- 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={}):
4029
4713
  raise NotSupported(self.id + ' watchTickers() is not supported yet')
4030
4714
 
4715
+ def un_watch_tickers(self, symbols: Strings = None, params={}):
4716
+ raise NotSupported(self.id + ' unWatchTickers() is not supported yet')
4717
+
4031
4718
  def fetch_order(self, id: str, symbol: Str = None, params={}):
4032
4719
  raise NotSupported(self.id + ' fetchOrder() is not supported yet')
4033
4720
 
@@ -4068,6 +4755,28 @@ class Exchange(object):
4068
4755
  return self.create_order(symbol, type, side, amount, price, params)
4069
4756
  raise NotSupported(self.id + ' createTrailingAmountOrder() is not supported yet')
4070
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
+
4071
4780
  def create_trailing_percent_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, trailingPercent=None, trailingTriggerPrice=None, params={}):
4072
4781
  """
4073
4782
  create a trailing order by providing the symbol, type, side, amount, price and trailingPercent
@@ -4090,6 +4799,28 @@ class Exchange(object):
4090
4799
  return self.create_order(symbol, type, side, amount, price, params)
4091
4800
  raise NotSupported(self.id + ' createTrailingPercentOrder() is not supported yet')
4092
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
+
4093
4824
  def create_market_order_with_cost(self, symbol: str, side: OrderSide, cost: float, params={}):
4094
4825
  """
4095
4826
  create a market order by providing the symbol, side and cost
@@ -4127,6 +4858,19 @@ class Exchange(object):
4127
4858
  return self.create_order(symbol, 'market', 'sell', cost, 1, params)
4128
4859
  raise NotSupported(self.id + ' createMarketSellOrderWithCost() is not supported yet')
4129
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
+
4130
4874
  def create_trigger_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, triggerPrice: Num = None, params={}):
4131
4875
  """
4132
4876
  create a trigger stop order(type 1)
@@ -4146,6 +4890,25 @@ class Exchange(object):
4146
4890
  return self.create_order(symbol, type, side, amount, price, params)
4147
4891
  raise NotSupported(self.id + ' createTriggerOrder() is not supported yet')
4148
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
+
4149
4912
  def create_stop_loss_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, stopLossPrice: Num = None, params={}):
4150
4913
  """
4151
4914
  create a trigger stop loss order(type 2)
@@ -4165,6 +4928,25 @@ class Exchange(object):
4165
4928
  return self.create_order(symbol, type, side, amount, price, params)
4166
4929
  raise NotSupported(self.id + ' createStopLossOrder() is not supported yet')
4167
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
+
4168
4950
  def create_take_profit_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, takeProfitPrice: Num = None, params={}):
4169
4951
  """
4170
4952
  create a trigger take profit order(type 2)
@@ -4184,6 +4966,25 @@ class Exchange(object):
4184
4966
  return self.create_order(symbol, type, side, amount, price, params)
4185
4967
  raise NotSupported(self.id + ' createTakeProfitOrder() is not supported yet')
4186
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
+
4187
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={}):
4188
4989
  """
4189
4990
  create an order with a stop loss or take profit attached(type 3)
@@ -4205,6 +5006,12 @@ class Exchange(object):
4205
5006
  :param float [params.stopLossAmount]: *not available on all exchanges* the amount for a stop loss
4206
5007
  :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
4207
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={}):
4208
5015
  if (takeProfit is None) and (stopLoss is None):
4209
5016
  raise ArgumentsRequired(self.id + ' createOrderWithTakeProfitAndStopLoss() requires either a takeProfit or stopLoss argument')
4210
5017
  if takeProfit is not None:
@@ -4240,9 +5047,33 @@ class Exchange(object):
4240
5047
  if stopLossAmount is not None:
4241
5048
  params['stopLoss']['amount'] = self.parse_to_numeric(stopLossAmount)
4242
5049
  params = self.omit(params, ['takeProfitType', 'takeProfitPriceType', 'takeProfitLimitPrice', 'takeProfitAmount', 'stopLossType', 'stopLossPriceType', 'stopLossLimitPrice', 'stopLossAmount'])
4243
- if self.has['createOrderWithTakeProfitAndStopLoss']:
4244
- return self.create_order(symbol, type, side, amount, price, params)
4245
- 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')
4246
5077
 
4247
5078
  def create_orders(self, orders: List[OrderRequest], params={}):
4248
5079
  raise NotSupported(self.id + ' createOrders() is not supported yet')
@@ -4262,11 +5093,17 @@ class Exchange(object):
4262
5093
  def cancel_all_orders(self, symbol: Str = None, params={}):
4263
5094
  raise NotSupported(self.id + ' cancelAllOrders() is not supported yet')
4264
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
+
4265
5102
  def cancel_all_orders_ws(self, symbol: Str = None, params={}):
4266
5103
  raise NotSupported(self.id + ' cancelAllOrdersWs() is not supported yet')
4267
5104
 
4268
5105
  def cancel_unified_order(self, order, params={}):
4269
- 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)
4270
5107
 
4271
5108
  def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
4272
5109
  if self.has['fetchOpenOrders'] and self.has['fetchClosedOrders']:
@@ -4290,7 +5127,7 @@ class Exchange(object):
4290
5127
 
4291
5128
  def fetch_open_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
4292
5129
  if self.has['fetchOrdersWs']:
4293
- orders = self.fetchOrdersWs(symbol, since, limit, params)
5130
+ orders = self.fetch_orders_ws(symbol, since, limit, params)
4294
5131
  return self.filter_by(orders, 'status', 'open')
4295
5132
  raise NotSupported(self.id + ' fetchOpenOrdersWs() is not supported yet')
4296
5133
 
@@ -4305,7 +5142,7 @@ class Exchange(object):
4305
5142
 
4306
5143
  def fetch_closed_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
4307
5144
  if self.has['fetchOrdersWs']:
4308
- orders = self.fetchOrdersWs(symbol, since, limit, params)
5145
+ orders = self.fetch_orders_ws(symbol, since, limit, params)
4309
5146
  return self.filter_by(orders, 'status', 'closed')
4310
5147
  raise NotSupported(self.id + ' fetchClosedOrdersWs() is not supported yet')
4311
5148
 
@@ -4327,6 +5164,15 @@ class Exchange(object):
4327
5164
  def fetch_greeks(self, symbol: str, params={}):
4328
5165
  raise NotSupported(self.id + ' fetchGreeks() is not supported yet')
4329
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
+
4330
5176
  def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
4331
5177
  """
4332
5178
  fetch history of deposits and withdrawals
@@ -4370,7 +5216,7 @@ class Exchange(object):
4370
5216
 
4371
5217
  def fetch_deposit_address(self, code: str, params={}):
4372
5218
  if self.has['fetchDepositAddresses']:
4373
- depositAddresses = self.fetchDepositAddresses([code], params)
5219
+ depositAddresses = self.fetch_deposit_addresses([code], params)
4374
5220
  depositAddress = self.safe_value(depositAddresses, code)
4375
5221
  if depositAddress is None:
4376
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')
@@ -4379,7 +5225,7 @@ class Exchange(object):
4379
5225
  elif self.has['fetchDepositAddressesByNetwork']:
4380
5226
  network = self.safe_string(params, 'network')
4381
5227
  params = self.omit(params, 'network')
4382
- addressStructures = self.fetchDepositAddressesByNetwork(code, params)
5228
+ addressStructures = self.fetch_deposit_addresses_by_network(code, params)
4383
5229
  if network is not None:
4384
5230
  return self.safe_dict(addressStructures, network)
4385
5231
  else:
@@ -4396,10 +5242,10 @@ class Exchange(object):
4396
5242
  'total': None,
4397
5243
  }
4398
5244
 
4399
- def common_currency_code(self, currency: str):
5245
+ def common_currency_code(self, code: str):
4400
5246
  if not self.substituteCommonCurrencyCodes:
4401
- return currency
4402
- return self.safe_string(self.commonCurrencies, currency, currency)
5247
+ return code
5248
+ return self.safe_string(self.commonCurrencies, code, code)
4403
5249
 
4404
5250
  def currency(self, code: str):
4405
5251
  if self.currencies is None:
@@ -4425,7 +5271,7 @@ class Exchange(object):
4425
5271
  return market
4426
5272
  return markets[0]
4427
5273
  elif (symbol.endswith('-C')) or (symbol.endswith('-P')) or (symbol.startswith('C-')) or (symbol.startswith('P-')):
4428
- return self.createExpiredOptionMarket(symbol)
5274
+ return self.create_expired_option_market(symbol)
4429
5275
  raise BadSymbol(self.id + ' does not have market symbol ' + symbol)
4430
5276
 
4431
5277
  def create_expired_option_market(self, symbol: str):
@@ -4444,21 +5290,39 @@ class Exchange(object):
4444
5290
  def create_limit_order(self, symbol: str, side: OrderSide, amount: float, price: float, params={}):
4445
5291
  return self.create_order(symbol, 'limit', side, amount, price, params)
4446
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
+
4447
5296
  def create_market_order(self, symbol: str, side: OrderSide, amount: float, price: Num = None, params={}):
4448
5297
  return self.create_order(symbol, 'market', side, amount, price, params)
4449
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
+
4450
5302
  def create_limit_buy_order(self, symbol: str, amount: float, price: float, params={}):
4451
5303
  return self.create_order(symbol, 'limit', 'buy', amount, price, params)
4452
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
+
4453
5308
  def create_limit_sell_order(self, symbol: str, amount: float, price: float, params={}):
4454
5309
  return self.create_order(symbol, 'limit', 'sell', amount, price, params)
4455
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
+
4456
5314
  def create_market_buy_order(self, symbol: str, amount: float, params={}):
4457
5315
  return self.create_order(symbol, 'market', 'buy', amount, None, params)
4458
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
+
4459
5320
  def create_market_sell_order(self, symbol: str, amount: float, params={}):
4460
5321
  return self.create_order(symbol, 'market', 'sell', amount, None, params)
4461
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
+
4462
5326
  def cost_to_precision(self, symbol: str, cost):
4463
5327
  market = self.market(symbol)
4464
5328
  return self.decimal_to_precision(cost, TRUNCATE, market['precision']['price'], self.precisionMode, self.paddingMode)
@@ -4489,9 +5353,10 @@ class Exchange(object):
4489
5353
  networkItem = self.safe_dict(networks, networkCode, {})
4490
5354
  precision = self.safe_value(networkItem, 'precision', precision)
4491
5355
  if precision is None:
4492
- return self.forceString(fee)
5356
+ return self.force_string(fee)
4493
5357
  else:
4494
- 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)
4495
5360
 
4496
5361
  def force_string(self, value):
4497
5362
  if not isinstance(value, str):
@@ -4507,7 +5372,7 @@ class Exchange(object):
4507
5372
  def is_significant_precision(self):
4508
5373
  return self.precisionMode == SIGNIFICANT_DIGITS
4509
5374
 
4510
- def safe_number(self, obj: object, key: IndexType, defaultNumber: Num = None):
5375
+ def safe_number(self, obj, key: IndexType, defaultNumber: Num = None):
4511
5376
  value = self.safe_string(obj, key)
4512
5377
  return self.parse_number(value, defaultNumber)
4513
5378
 
@@ -4517,7 +5382,7 @@ class Exchange(object):
4517
5382
 
4518
5383
  def parse_precision(self, precision: str):
4519
5384
  """
4520
- * @ignore
5385
+ @ignore
4521
5386
  :param str precision: The number of digits to the right of the decimal
4522
5387
  :returns str: a string number equal to 1e-precision
4523
5388
  """
@@ -4531,6 +5396,25 @@ class Exchange(object):
4531
5396
  parsedPrecision = parsedPrecision + '0'
4532
5397
  return parsedPrecision + '1'
4533
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
+
4534
5418
  def load_time_difference(self, params={}):
4535
5419
  serverTime = self.fetch_time(params)
4536
5420
  after = self.milliseconds()
@@ -4556,32 +5440,64 @@ class Exchange(object):
4556
5440
  query = self.extend(params, {'postOnly': True})
4557
5441
  return self.create_order(symbol, type, side, amount, price, query)
4558
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
+
4559
5449
  def create_reduce_only_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
4560
5450
  if not self.has['createReduceOnlyOrder']:
4561
5451
  raise NotSupported(self.id + 'createReduceOnlyOrder() is not supported yet')
4562
5452
  query = self.extend(params, {'reduceOnly': True})
4563
5453
  return self.create_order(symbol, type, side, amount, price, query)
4564
5454
 
4565
- 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={}):
4566
5462
  if not self.has['createStopOrder']:
4567
5463
  raise NotSupported(self.id + ' createStopOrder() is not supported yet')
4568
- if stopPrice is None:
5464
+ if triggerPrice is None:
4569
5465
  raise ArgumentsRequired(self.id + ' create_stop_order() requires a stopPrice argument')
4570
- query = self.extend(params, {'stopPrice': stopPrice})
5466
+ query = self.extend(params, {'stopPrice': triggerPrice})
4571
5467
  return self.create_order(symbol, type, side, amount, price, query)
4572
5468
 
4573
- 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={}):
4574
5478
  if not self.has['createStopLimitOrder']:
4575
5479
  raise NotSupported(self.id + ' createStopLimitOrder() is not supported yet')
4576
- query = self.extend(params, {'stopPrice': stopPrice})
5480
+ query = self.extend(params, {'stopPrice': triggerPrice})
4577
5481
  return self.create_order(symbol, 'limit', side, amount, price, query)
4578
5482
 
4579
- 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={}):
4580
5490
  if not self.has['createStopMarketOrder']:
4581
5491
  raise NotSupported(self.id + ' createStopMarketOrder() is not supported yet')
4582
- query = self.extend(params, {'stopPrice': stopPrice})
5492
+ query = self.extend(params, {'stopPrice': triggerPrice})
4583
5493
  return self.create_order(symbol, 'market', side, amount, None, query)
4584
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
+
4585
5501
  def safe_currency_code(self, currencyId: Str, currency: Currency = None):
4586
5502
  currency = self.safe_currency(currencyId, currency)
4587
5503
  return currency['code']
@@ -4619,22 +5535,23 @@ class Exchange(object):
4619
5535
  results = []
4620
5536
  if isinstance(pricesData, list):
4621
5537
  for i in range(0, len(pricesData)):
4622
- priceData = self.extend(self.parseLastPrice(pricesData[i]), params)
5538
+ priceData = self.extend(self.parse_last_price(pricesData[i]), params)
4623
5539
  results.append(priceData)
4624
5540
  else:
4625
5541
  marketIds = list(pricesData.keys())
4626
5542
  for i in range(0, len(marketIds)):
4627
5543
  marketId = marketIds[i]
4628
5544
  market = self.safe_market(marketId)
4629
- priceData = self.extend(self.parseLastPrice(pricesData[marketId], market), params)
5545
+ priceData = self.extend(self.parse_last_price(pricesData[marketId], market), params)
4630
5546
  results.append(priceData)
4631
5547
  symbols = self.market_symbols(symbols)
4632
5548
  return self.filter_by_array(results, 'symbol', symbols)
4633
5549
 
4634
- def parse_tickers(self, tickers, symbols: List[str] = None, params={}):
5550
+ def parse_tickers(self, tickers, symbols: Strings = None, params={}):
4635
5551
  #
4636
5552
  # the value of tickers is either a dict or a list
4637
5553
  #
5554
+ #
4638
5555
  # dict
4639
5556
  #
4640
5557
  # {
@@ -4668,7 +5585,7 @@ class Exchange(object):
4668
5585
  symbols = self.market_symbols(symbols)
4669
5586
  return self.filter_by_array(results, 'symbol', symbols)
4670
5587
 
4671
- 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={}):
4672
5589
  result = []
4673
5590
  for i in range(0, len(addresses)):
4674
5591
  address = self.extend(self.parse_deposit_address(addresses[i]), params)
@@ -4676,7 +5593,7 @@ class Exchange(object):
4676
5593
  if codes is not None:
4677
5594
  result = self.filter_by_array(result, 'currency', codes, False)
4678
5595
  if indexed:
4679
- return self.index_by(result, 'currency')
5596
+ result = self.filter_by_array(result, 'currency', None, indexed)
4680
5597
  return result
4681
5598
 
4682
5599
  def parse_borrow_interests(self, response, market: Market = None):
@@ -4686,6 +5603,27 @@ class Exchange(object):
4686
5603
  interests.append(self.parse_borrow_interest(row, market))
4687
5604
  return interests
4688
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
+
4689
5627
  def parse_funding_rate_histories(self, response, market=None, since: Int = None, limit: Int = None):
4690
5628
  rates = []
4691
5629
  for i in range(0, len(response)):
@@ -4702,22 +5640,39 @@ class Exchange(object):
4702
5640
  def parse_funding_rate(self, contract: str, market: Market = None):
4703
5641
  raise NotSupported(self.id + ' parseFundingRate() is not supported yet')
4704
5642
 
4705
- def parse_funding_rates(self, response, market: Market = None):
4706
- result = {}
5643
+ def parse_funding_rates(self, response, symbols: Strings = None):
5644
+ fundingRates = {}
4707
5645
  for i in range(0, len(response)):
4708
- parsed = self.parse_funding_rate(response[i], market)
4709
- result[parsed['symbol']] = parsed
4710
- 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)
4711
5650
 
4712
- 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):
4713
5664
  isTrigger = self.safe_bool_2(params, 'trigger', 'stop')
4714
5665
  if isTrigger:
4715
5666
  params = self.omit(params, ['trigger', 'stop'])
4716
5667
  return [isTrigger, params]
4717
5668
 
5669
+ def is_trigger_order(self, params):
5670
+ # for backwards compatibility
5671
+ return self.handle_trigger_and_params(params)
5672
+
4718
5673
  def is_post_only(self, isMarketOrder: bool, exchangeSpecificParam, params={}):
4719
5674
  """
4720
- * @ignore
5675
+ @ignore
4721
5676
  :param str type: Order type
4722
5677
  :param boolean exchangeSpecificParam: exchange specific postOnly
4723
5678
  :param dict [params]: exchange specific params
@@ -4742,7 +5697,7 @@ class Exchange(object):
4742
5697
 
4743
5698
  def handle_post_only(self, isMarketOrder: bool, exchangeSpecificPostOnlyOption: bool, params: Any = {}):
4744
5699
  """
4745
- * @ignore
5700
+ @ignore
4746
5701
  :param str type: Order type
4747
5702
  :param boolean exchangeSpecificBoolean: exchange specific postOnly
4748
5703
  :param dict [params]: exchange specific params
@@ -4766,7 +5721,7 @@ class Exchange(object):
4766
5721
  return [True, params]
4767
5722
  return [False, params]
4768
5723
 
4769
- def fetch_last_prices(self, symbols: List[str] = None, params={}):
5724
+ def fetch_last_prices(self, symbols: Strings = None, params={}):
4770
5725
  raise NotSupported(self.id + ' fetchLastPrices() is not supported yet')
4771
5726
 
4772
5727
  def fetch_trading_fees(self, params={}):
@@ -4778,12 +5733,24 @@ class Exchange(object):
4778
5733
  def fetch_trading_fee(self, symbol: str, params={}):
4779
5734
  if not self.has['fetchTradingFees']:
4780
5735
  raise NotSupported(self.id + ' fetchTradingFee() is not supported yet')
4781
- 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')
4782
5741
 
4783
5742
  def parse_open_interest(self, interest, market: Market = None):
4784
5743
  raise NotSupported(self.id + ' parseOpenInterest() is not supported yet')
4785
5744
 
4786
- 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):
4787
5754
  interests = []
4788
5755
  for i in range(0, len(response)):
4789
5756
  entry = response[i]
@@ -4800,7 +5767,7 @@ class Exchange(object):
4800
5767
  symbol = market['symbol']
4801
5768
  if not market['contract']:
4802
5769
  raise BadSymbol(self.id + ' fetchFundingRate() supports contract markets only')
4803
- rates = self.fetchFundingRates([symbol], params)
5770
+ rates = self.fetch_funding_rates([symbol], params)
4804
5771
  rate = self.safe_value(rates, symbol)
4805
5772
  if rate is None:
4806
5773
  raise NullResponse(self.id + ' fetchFundingRate() returned no data for ' + symbol)
@@ -4809,6 +5776,22 @@ class Exchange(object):
4809
5776
  else:
4810
5777
  raise NotSupported(self.id + ' fetchFundingRate() is not supported yet')
4811
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
+
4812
5795
  def fetch_mark_ohlcv(self, symbol, timeframe='1m', since: Int = None, limit: Int = None, params={}):
4813
5796
  """
4814
5797
  fetches historical mark price candlestick data containing the open, high, low, and close price of a market
@@ -4820,7 +5803,7 @@ class Exchange(object):
4820
5803
  :returns float[][]: A list of candles ordered, open, high, low, close, None
4821
5804
  """
4822
5805
  if self.has['fetchMarkOHLCV']:
4823
- request = {
5806
+ request: dict = {
4824
5807
  'price': 'mark',
4825
5808
  }
4826
5809
  return self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))
@@ -4835,10 +5818,10 @@ class Exchange(object):
4835
5818
  :param int [since]: timestamp in ms of the earliest candle to fetch
4836
5819
  :param int [limit]: the maximum amount of candles to fetch
4837
5820
  :param dict [params]: extra parameters specific to the exchange API endpoint
4838
- * @returns {} A list of candles ordered, open, high, low, close, None
5821
+ @returns {} A list of candles ordered, open, high, low, close, None
4839
5822
  """
4840
5823
  if self.has['fetchIndexOHLCV']:
4841
- request = {
5824
+ request: dict = {
4842
5825
  'price': 'index',
4843
5826
  }
4844
5827
  return self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))
@@ -4856,7 +5839,7 @@ class Exchange(object):
4856
5839
  :returns float[][]: A list of candles ordered, open, high, low, close, None
4857
5840
  """
4858
5841
  if self.has['fetchPremiumIndexOHLCV']:
4859
- request = {
5842
+ request: dict = {
4860
5843
  'price': 'premiumIndex',
4861
5844
  }
4862
5845
  return self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))
@@ -4865,8 +5848,8 @@ class Exchange(object):
4865
5848
 
4866
5849
  def handle_time_in_force(self, params={}):
4867
5850
  """
4868
- * @ignore
4869
- * Must add timeInForce to self.options to use self method
5851
+ @ignore
5852
+ Must add timeInForce to self.options to use self method
4870
5853
  :returns str: returns the exchange specific value for timeInForce
4871
5854
  """
4872
5855
  timeInForce = self.safe_string_upper(params, 'timeInForce') # supported values GTC, IOC, PO
@@ -4879,8 +5862,8 @@ class Exchange(object):
4879
5862
 
4880
5863
  def convert_type_to_account(self, account):
4881
5864
  """
4882
- * @ignore
4883
- * Must add accountsByType to self.options to use self method
5865
+ @ignore
5866
+ Must add accountsByType to self.options to use self method
4884
5867
  :param str account: key for account name in self.options['accountsByType']
4885
5868
  :returns: the exchange specific account name or the isolated margin id for transfers
4886
5869
  """
@@ -4896,7 +5879,7 @@ class Exchange(object):
4896
5879
 
4897
5880
  def check_required_argument(self, methodName: str, argument, argumentName, options=[]):
4898
5881
  """
4899
- * @ignore
5882
+ @ignore
4900
5883
  :param str methodName: the name of the method that the argument is being checked for
4901
5884
  :param str argument: the argument's actual value provided
4902
5885
  :param str argumentName: the name of the argument being checked(for logging purposes)
@@ -4913,7 +5896,7 @@ class Exchange(object):
4913
5896
 
4914
5897
  def check_required_margin_argument(self, methodName: str, symbol: Str, marginMode: str):
4915
5898
  """
4916
- * @ignore
5899
+ @ignore
4917
5900
  :param str symbol: unified symbol of the market
4918
5901
  :param str methodName: name of the method that requires a symbol
4919
5902
  :param str marginMode: is either 'isolated' or 'cross'
@@ -4923,16 +5906,15 @@ class Exchange(object):
4923
5906
  elif (marginMode == 'cross') and (symbol is not None):
4924
5907
  raise ArgumentsRequired(self.id + ' ' + methodName + '() cannot have a symbol argument for cross margin')
4925
5908
 
4926
- 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):
4927
5910
  """
4928
- * @ignore
5911
+ @ignore
4929
5912
  :param object[]|dict response: unparsed response from the exchange
4930
5913
  :param str[]|None codes: the unified currency codes to fetch transactions fees for, returns all currencies when None
4931
5914
  :param str currencyIdKey: *should only be None when response is a dictionary* the object key that corresponds to the currency id
4932
5915
  :returns dict: objects with withdraw and deposit fees, indexed by currency codes
4933
5916
  """
4934
5917
  depositWithdrawFees = {}
4935
- codes = self.marketCodes(codes)
4936
5918
  isArray = isinstance(response, list)
4937
5919
  responseKeys = response
4938
5920
  if not isArray:
@@ -4941,10 +5923,10 @@ class Exchange(object):
4941
5923
  entry = responseKeys[i]
4942
5924
  dictionary = entry if isArray else response[entry]
4943
5925
  currencyId = self.safe_string(dictionary, currencyIdKey) if isArray else entry
4944
- currency = self.safe_value(self.currencies_by_id, currencyId)
4945
- code = self.safe_string(currency, 'code', currencyId)
5926
+ currency = self.safe_currency(currencyId)
5927
+ code = self.safe_string(currency, 'code')
4946
5928
  if (codes is None) or (self.in_array(code, codes)):
4947
- depositWithdrawFees[code] = self.parseDepositWithdrawFee(dictionary, currency)
5929
+ depositWithdrawFees[code] = self.parse_deposit_withdraw_fee(dictionary, currency)
4948
5930
  return depositWithdrawFees
4949
5931
 
4950
5932
  def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
@@ -4966,7 +5948,7 @@ class Exchange(object):
4966
5948
 
4967
5949
  def assign_default_deposit_withdraw_fees(self, fee, currency=None):
4968
5950
  """
4969
- * @ignore
5951
+ @ignore
4970
5952
  Takes a depositWithdrawFee structure and assigns the default values for withdraw and deposit
4971
5953
  :param dict fee: A deposit withdraw fee structure
4972
5954
  :param dict currency: A currency structure, the response from self.currency()
@@ -4991,7 +5973,7 @@ class Exchange(object):
4991
5973
 
4992
5974
  def parse_incomes(self, incomes, market=None, since: Int = None, limit: Int = None):
4993
5975
  """
4994
- * @ignore
5976
+ @ignore
4995
5977
  parses funding fee info from exchange response
4996
5978
  :param dict[] incomes: each item describes once instance of currency being received or paid
4997
5979
  :param dict market: ccxt market
@@ -5007,7 +5989,7 @@ class Exchange(object):
5007
5989
  sorted = self.sort_by(result, 'timestamp')
5008
5990
  return self.filter_by_since_limit(sorted, since, limit)
5009
5991
 
5010
- def get_market_from_symbols(self, symbols: List[str] = None):
5992
+ def get_market_from_symbols(self, symbols: Strings = None):
5011
5993
  if symbols is None:
5012
5994
  return None
5013
5995
  firstMarket = self.safe_string(symbols, 0)
@@ -5022,7 +6004,7 @@ class Exchange(object):
5022
6004
 
5023
6005
  def fetch_transactions(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
5024
6006
  """
5025
- * @deprecated
6007
+ @deprecated
5026
6008
  *DEPRECATED* use fetchDepositsWithdrawals instead
5027
6009
  :param str code: unified currency code for the currency of the deposit/withdrawals, default is None
5028
6010
  :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
@@ -5031,20 +6013,20 @@ class Exchange(object):
5031
6013
  :returns dict: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
5032
6014
  """
5033
6015
  if self.has['fetchDepositsWithdrawals']:
5034
- return self.fetchDepositsWithdrawals(code, since, limit, params)
6016
+ return self.fetch_deposits_withdrawals(code, since, limit, params)
5035
6017
  else:
5036
6018
  raise NotSupported(self.id + ' fetchTransactions() is not supported yet')
5037
6019
 
5038
6020
  def filter_by_array_positions(self, objects, key: IndexType, values=None, indexed=True):
5039
6021
  """
5040
- * @ignore
6022
+ @ignore
5041
6023
  Typed wrapper for filterByArray that returns a list of positions
5042
6024
  """
5043
6025
  return self.filter_by_array(objects, key, values, indexed)
5044
6026
 
5045
6027
  def filter_by_array_tickers(self, objects, key: IndexType, values=None, indexed=True):
5046
6028
  """
5047
- * @ignore
6029
+ @ignore
5048
6030
  Typed wrapper for filterByArray that returns a dictionary of tickers
5049
6031
  """
5050
6032
  return self.filter_by_array(objects, key, values, indexed)
@@ -5064,7 +6046,7 @@ class Exchange(object):
5064
6046
  maxEntriesPerRequest = 1000 # default to 1000
5065
6047
  return [maxEntriesPerRequest, params]
5066
6048
 
5067
- 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):
5068
6050
  maxCalls = None
5069
6051
  maxCalls, params = self.handle_option_and_params(params, method, 'paginationCalls', 10)
5070
6052
  maxRetries = None
@@ -5072,6 +6054,8 @@ class Exchange(object):
5072
6054
  paginationDirection = None
5073
6055
  paginationDirection, params = self.handle_option_and_params(params, method, 'paginationDirection', 'backward')
5074
6056
  paginationTimestamp = None
6057
+ removeRepeatedOption = removeRepeated
6058
+ removeRepeatedOption, params = self.handle_option_and_params(params, method, 'removeRepeated', removeRepeated)
5075
6059
  calls = 0
5076
6060
  result = []
5077
6061
  errors = 0
@@ -5118,14 +6102,16 @@ class Exchange(object):
5118
6102
  errors = 0
5119
6103
  result = self.array_concat(result, response)
5120
6104
  last = self.safe_value(response, responseLength - 1)
5121
- paginationTimestamp = self.safe_integer(last, 'timestamp') - 1
6105
+ paginationTimestamp = self.safe_integer(last, 'timestamp') + 1
5122
6106
  if (until is not None) and (paginationTimestamp >= until):
5123
6107
  break
5124
6108
  except Exception as e:
5125
6109
  errors += 1
5126
6110
  if errors > maxRetries:
5127
6111
  raise e
5128
- uniqueResults = self.remove_repeated_elements_from_array(result)
6112
+ uniqueResults = result
6113
+ if removeRepeatedOption:
6114
+ uniqueResults = self.remove_repeated_elements_from_array(result)
5129
6115
  key = 0 if (method == 'fetchOHLCV') else 'timestamp'
5130
6116
  return self.filter_by_since_limit(uniqueResults, since, limit, key)
5131
6117
 
@@ -5133,18 +6119,19 @@ class Exchange(object):
5133
6119
  maxRetries = None
5134
6120
  maxRetries, params = self.handle_option_and_params(params, method, 'maxRetries', 3)
5135
6121
  errors = 0
5136
- try:
5137
- if timeframe and method != 'fetchFundingRateHistory':
5138
- return getattr(self, method)(symbol, timeframe, since, limit, params)
5139
- else:
5140
- return getattr(self, method)(symbol, since, limit, params)
5141
- except Exception as e:
5142
- if isinstance(e, RateLimitExceeded):
5143
- raise e # if we are rate limited, we should not retry and fail fast
5144
- errors += 1
5145
- if errors > maxRetries:
5146
- raise e
5147
- 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 []
5148
6135
 
5149
6136
  def fetch_paginated_call_deterministic(self, method: str, symbol: Str = None, since: Int = None, limit: Int = None, timeframe: Str = None, params={}, maxEntriesPerRequest=None):
5150
6137
  maxCalls = None
@@ -5157,6 +6144,8 @@ class Exchange(object):
5157
6144
  currentSince = current - (maxCalls * step) - 1
5158
6145
  if since is not None:
5159
6146
  currentSince = max(currentSince, since)
6147
+ else:
6148
+ currentSince = max(currentSince, 1241440531000) # avoid timestamps older than 2009
5160
6149
  until = self.safe_integer_2(params, 'until', 'till') # do not omit it here
5161
6150
  if until is not None:
5162
6151
  requiredCalls = int(math.ceil((until - since)) / step)
@@ -5165,6 +6154,8 @@ class Exchange(object):
5165
6154
  for i in range(0, maxCalls):
5166
6155
  if (until is not None) and (currentSince >= until):
5167
6156
  break
6157
+ if currentSince >= current:
6158
+ break
5168
6159
  tasks.append(self.safe_deterministic_call(method, symbol, currentSince, maxEntriesPerRequest, timeframe, params))
5169
6160
  currentSince = self.sum(currentSince, step) - 1
5170
6161
  results = tasks
@@ -5185,28 +6176,44 @@ class Exchange(object):
5185
6176
  i = 0
5186
6177
  errors = 0
5187
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
5188
6181
  while(i < maxCalls):
5189
6182
  try:
5190
6183
  if cursorValue is not None:
5191
6184
  if cursorIncrement is not None:
5192
- cursorValue = self.parseToInt(cursorValue) + cursorIncrement
6185
+ cursorValue = self.parse_to_int(cursorValue) + cursorIncrement
5193
6186
  params[cursorSent] = cursorValue
5194
6187
  response = None
5195
6188
  if method == 'fetchAccounts':
5196
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)
5197
6194
  else:
5198
6195
  response = getattr(self, method)(symbol, since, maxEntriesPerRequest, params)
5199
6196
  errors = 0
5200
6197
  responseLength = len(response)
5201
6198
  if self.verbose:
5202
- iteration = (i + str(1))
5203
- 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
5204
6202
  self.log(cursorMessage)
5205
6203
  if responseLength == 0:
5206
6204
  break
5207
6205
  result = self.array_concat(result, response)
5208
- last = self.safe_value(response, responseLength - 1)
5209
- 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
5210
6217
  if cursorValue is None:
5211
6218
  break
5212
6219
  lastTimestamp = self.safe_integer(last, 'timestamp')
@@ -5217,7 +6224,7 @@ class Exchange(object):
5217
6224
  if errors > maxRetries:
5218
6225
  raise e
5219
6226
  i += 1
5220
- sorted = self.sortCursorPaginatedResult(result)
6227
+ sorted = self.sort_cursor_paginated_result(result)
5221
6228
  key = 0 if (method == 'fetchOHLCV') else 'timestamp'
5222
6229
  return self.filter_by_since_limit(sorted, since, limit, key)
5223
6230
 
@@ -5248,7 +6255,7 @@ class Exchange(object):
5248
6255
  if errors > maxRetries:
5249
6256
  raise e
5250
6257
  i += 1
5251
- sorted = self.sortCursorPaginatedResult(result)
6258
+ sorted = self.sort_cursor_paginated_result(result)
5252
6259
  key = 0 if (method == 'fetchOHLCV') else 'timestamp'
5253
6260
  return self.filter_by_since_limit(sorted, since, limit, key)
5254
6261
 
@@ -5283,13 +6290,16 @@ class Exchange(object):
5283
6290
  def handle_until_option(self, key: str, request, params, multiplier=1):
5284
6291
  until = self.safe_integer_2(params, 'until', 'till')
5285
6292
  if until is not None:
5286
- request[key] = self.parseToInt(until * multiplier)
6293
+ request[key] = self.parse_to_int(until * multiplier)
5287
6294
  params = self.omit(params, ['until', 'till'])
5288
6295
  return [request, params]
5289
6296
 
5290
- 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')
5291
6301
  return self.extend(interest, {
5292
- 'symbol': self.safe_string(market, 'symbol'),
6302
+ 'symbol': symbol,
5293
6303
  'baseVolume': self.safe_number(interest, 'baseVolume'), # deprecated
5294
6304
  'quoteVolume': self.safe_number(interest, 'quoteVolume'), # deprecated
5295
6305
  'openInterestAmount': self.safe_number(interest, 'openInterestAmount'),
@@ -5302,9 +6312,9 @@ class Exchange(object):
5302
6312
  def parse_liquidation(self, liquidation, market: Market = None):
5303
6313
  raise NotSupported(self.id + ' parseLiquidation() is not supported yet')
5304
6314
 
5305
- 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):
5306
6316
  """
5307
- * @ignore
6317
+ @ignore
5308
6318
  parses liquidation info from the exchange response
5309
6319
  :param dict[] liquidations: each item describes an instance of a liquidation event
5310
6320
  :param dict market: ccxt market
@@ -5315,17 +6325,33 @@ class Exchange(object):
5315
6325
  result = []
5316
6326
  for i in range(0, len(liquidations)):
5317
6327
  entry = liquidations[i]
5318
- parsed = self.parseLiquidation(entry, market)
6328
+ parsed = self.parse_liquidation(entry, market)
5319
6329
  result.append(parsed)
5320
6330
  sorted = self.sort_by(result, 'timestamp')
5321
6331
  symbol = self.safe_string(market, 'symbol')
5322
6332
  return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
5323
6333
 
5324
- def parse_greeks(self, greeks, market: Market = None):
6334
+ def parse_greeks(self, greeks: dict, market: Market = None):
5325
6335
  raise NotSupported(self.id + ' parseGreeks() is not supported yet')
5326
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
+
5327
6351
  def parse_margin_modes(self, response: List[object], symbols: List[str] = None, symbolKey: Str = None, marketType: MarketType = None):
5328
6352
  marginModeStructures = {}
6353
+ if marketType is None:
6354
+ marketType = 'swap' # default to swap
5329
6355
  for i in range(0, len(response)):
5330
6356
  info = response[i]
5331
6357
  marketId = self.safe_string(info, symbolKey)
@@ -5334,11 +6360,13 @@ class Exchange(object):
5334
6360
  marginModeStructures[market['symbol']] = self.parse_margin_mode(info, market)
5335
6361
  return marginModeStructures
5336
6362
 
5337
- def parse_margin_mode(self, marginMode, market: Market = None):
6363
+ def parse_margin_mode(self, marginMode: dict, market: Market = None):
5338
6364
  raise NotSupported(self.id + ' parseMarginMode() is not supported yet')
5339
6365
 
5340
6366
  def parse_leverages(self, response: List[object], symbols: List[str] = None, symbolKey: Str = None, marketType: MarketType = None):
5341
6367
  leverageStructures = {}
6368
+ if marketType is None:
6369
+ marketType = 'swap' # default to swap
5342
6370
  for i in range(0, len(response)):
5343
6371
  info = response[i]
5344
6372
  marketId = self.safe_string(info, symbolKey)
@@ -5347,5 +6375,219 @@ class Exchange(object):
5347
6375
  leverageStructures[market['symbol']] = self.parse_leverage(info, market)
5348
6376
  return leverageStructures
5349
6377
 
5350
- def parse_leverage(self, leverage, market: Market = None):
6378
+ def parse_leverage(self, leverage: dict, market: Market = None):
5351
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]