ccxt-ir 4.3.46.0.2__py2.py3-none-any.whl → 4.5.0__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 (528) hide show
  1. ccxt/__init__.py +39 -35
  2. ccxt/abantether.py +9 -9
  3. ccxt/abstract/alpaca.py +4 -0
  4. ccxt/abstract/apex.py +31 -0
  5. ccxt/abstract/bigone.py +1 -1
  6. ccxt/abstract/binance.py +106 -48
  7. ccxt/abstract/binancecoinm.py +106 -48
  8. ccxt/abstract/binanceus.py +141 -83
  9. ccxt/abstract/binanceusdm.py +106 -48
  10. ccxt/abstract/bingx.py +50 -1
  11. ccxt/abstract/bitbank.py +5 -0
  12. ccxt/abstract/bitfinex.py +136 -65
  13. ccxt/abstract/bitflyer.py +1 -0
  14. ccxt/abstract/bitget.py +67 -0
  15. ccxt/abstract/bitmart.py +19 -1
  16. ccxt/abstract/bitopro.py +1 -0
  17. ccxt/abstract/bitrue.py +68 -68
  18. ccxt/abstract/bitstamp.py +1 -0
  19. ccxt/abstract/blofin.py +30 -0
  20. ccxt/abstract/btcbox.py +2 -0
  21. ccxt/abstract/bybit.py +28 -13
  22. ccxt/abstract/cex.py +28 -29
  23. ccxt/abstract/coinbaseexchange.py +1 -0
  24. ccxt/abstract/coinbaseinternational.py +1 -1
  25. ccxt/abstract/cryptocom.py +16 -0
  26. ccxt/abstract/cryptomus.py +20 -0
  27. ccxt/abstract/defx.py +69 -0
  28. ccxt/abstract/deribit.py +1 -0
  29. ccxt/abstract/derive.py +117 -0
  30. ccxt/abstract/digifinex.py +1 -0
  31. ccxt/abstract/ellipx.py +25 -0
  32. ccxt/abstract/foxbit.py +26 -0
  33. ccxt/abstract/gate.py +19 -0
  34. ccxt/abstract/gateio.py +19 -0
  35. ccxt/abstract/gemini.py +1 -0
  36. ccxt/abstract/hibachi.py +26 -0
  37. ccxt/abstract/hyperliquid.py +1 -1
  38. ccxt/abstract/independentreserve.py +6 -0
  39. ccxt/abstract/kraken.py +1 -0
  40. ccxt/abstract/krakenfutures.py +4 -0
  41. ccxt/abstract/kucoin.py +10 -0
  42. ccxt/abstract/kucoinfutures.py +18 -0
  43. ccxt/abstract/lbank.py +2 -1
  44. ccxt/abstract/luno.py +1 -0
  45. ccxt/abstract/mexc.py +2 -0
  46. ccxt/abstract/modetrade.py +119 -0
  47. ccxt/abstract/myokx.py +349 -0
  48. ccxt/abstract/oceanex.py +5 -0
  49. ccxt/abstract/okx.py +25 -0
  50. ccxt/abstract/okxus.py +349 -0
  51. ccxt/abstract/onetrading.py +0 -12
  52. ccxt/abstract/paradex.py +23 -0
  53. ccxt/abstract/phemex.py +2 -0
  54. ccxt/abstract/poloniex.py +36 -0
  55. ccxt/abstract/tradeogre.py +3 -1
  56. ccxt/abstract/upbit.py +51 -34
  57. ccxt/abstract/whitebit.py +16 -0
  58. ccxt/abstract/woo.py +64 -6
  59. ccxt/abstract/xt.py +10 -5
  60. ccxt/afratether.py +7 -7
  61. ccxt/alpaca.py +828 -51
  62. ccxt/apex.py +1875 -0
  63. ccxt/arzinja.py +7 -7
  64. ccxt/arzplus.py +9 -9
  65. ccxt/ascendex.py +501 -306
  66. ccxt/async_support/__init__.py +39 -35
  67. ccxt/async_support/abantether.py +10 -10
  68. ccxt/async_support/afratether.py +9 -9
  69. ccxt/async_support/alpaca.py +828 -51
  70. ccxt/async_support/apex.py +1875 -0
  71. ccxt/async_support/arzinja.py +10 -10
  72. ccxt/async_support/arzplus.py +12 -12
  73. ccxt/async_support/ascendex.py +502 -306
  74. ccxt/async_support/base/exchange.py +303 -89
  75. ccxt/async_support/base/ws/cache.py +9 -3
  76. ccxt/async_support/base/ws/client.py +173 -38
  77. ccxt/async_support/base/ws/future.py +25 -37
  78. ccxt/async_support/bequant.py +5 -3
  79. ccxt/async_support/bigone.py +279 -144
  80. ccxt/async_support/binance.py +2347 -1158
  81. ccxt/async_support/binancecoinm.py +9 -3
  82. ccxt/async_support/binanceus.py +17 -3
  83. ccxt/async_support/binanceusdm.py +9 -4
  84. ccxt/async_support/bingx.py +2962 -920
  85. ccxt/async_support/bit2c.py +147 -27
  86. ccxt/async_support/bitbank.py +151 -23
  87. ccxt/async_support/bitbns.py +104 -30
  88. ccxt/async_support/bitfinex.py +3291 -1113
  89. ccxt/async_support/bitflyer.py +202 -27
  90. ccxt/async_support/bitget.py +3683 -1538
  91. ccxt/async_support/bithumb.py +195 -38
  92. ccxt/async_support/bitimen.py +12 -12
  93. ccxt/async_support/bitir.py +38 -38
  94. ccxt/async_support/bitmart.py +1288 -350
  95. ccxt/async_support/bitmex.py +260 -75
  96. ccxt/async_support/bitopro.py +262 -62
  97. ccxt/async_support/bitpin.py +17 -16
  98. ccxt/async_support/bitrue.py +459 -290
  99. ccxt/async_support/bitso.py +199 -54
  100. ccxt/async_support/bitstamp.py +230 -96
  101. ccxt/async_support/bitteam.py +167 -25
  102. ccxt/async_support/{huobijp.py → bittrade.py} +158 -30
  103. ccxt/async_support/bitvavo.py +213 -49
  104. ccxt/async_support/blockchaincom.py +160 -46
  105. ccxt/async_support/blofin.py +502 -120
  106. ccxt/async_support/btcalpha.py +169 -31
  107. ccxt/async_support/btcbox.py +292 -23
  108. ccxt/async_support/btcmarkets.py +211 -58
  109. ccxt/async_support/btcturk.py +161 -38
  110. ccxt/async_support/bybit.py +1775 -1030
  111. ccxt/async_support/cex.py +1440 -1303
  112. ccxt/async_support/coinbase.py +724 -212
  113. ccxt/async_support/coinbaseadvanced.py +2 -1
  114. ccxt/async_support/coinbaseexchange.py +388 -89
  115. ccxt/async_support/coinbaseinternational.py +412 -57
  116. ccxt/async_support/coincatch.py +177 -78
  117. ccxt/async_support/coincheck.py +135 -19
  118. ccxt/async_support/coinex.py +606 -232
  119. ccxt/async_support/coinmate.py +189 -63
  120. ccxt/async_support/coinmetro.py +195 -54
  121. ccxt/async_support/coinone.py +158 -51
  122. ccxt/async_support/coinsph.py +336 -61
  123. ccxt/async_support/coinspot.py +151 -52
  124. ccxt/async_support/cryptocom.py +661 -111
  125. ccxt/async_support/cryptomus.py +1137 -0
  126. ccxt/async_support/defx.py +2071 -0
  127. ccxt/async_support/delta.py +299 -99
  128. ccxt/async_support/deribit.py +348 -126
  129. ccxt/async_support/derive.py +2572 -0
  130. ccxt/async_support/digifinex.py +430 -214
  131. ccxt/async_support/ellipx.py +2029 -0
  132. ccxt/async_support/eterex.py +10 -10
  133. ccxt/async_support/excoino.py +31 -31
  134. ccxt/async_support/exir.py +14 -14
  135. ccxt/async_support/exmo.py +344 -131
  136. ccxt/async_support/exnovin.py +10 -10
  137. ccxt/async_support/farhadexchange.py +12 -12
  138. ccxt/async_support/fmfwio.py +2 -1
  139. ccxt/async_support/foxbit.py +1935 -0
  140. ccxt/async_support/gate.py +1351 -529
  141. ccxt/async_support/gateio.py +2 -1
  142. ccxt/async_support/gemini.py +144 -39
  143. ccxt/async_support/hashkey.py +152 -109
  144. ccxt/async_support/hibachi.py +2080 -0
  145. ccxt/async_support/hitbtc.py +395 -167
  146. ccxt/async_support/hitobit.py +12 -12
  147. ccxt/async_support/hollaex.py +307 -119
  148. ccxt/async_support/htx.py +851 -383
  149. ccxt/async_support/huobi.py +2 -1
  150. ccxt/async_support/hyperliquid.py +1848 -536
  151. ccxt/async_support/independentreserve.py +288 -15
  152. ccxt/async_support/indodax.py +190 -33
  153. ccxt/async_support/jibitex.py +12 -12
  154. ccxt/async_support/kraken.py +795 -351
  155. ccxt/async_support/krakenfutures.py +214 -62
  156. ccxt/async_support/kucoin.py +715 -396
  157. ccxt/async_support/kucoinfutures.py +652 -89
  158. ccxt/async_support/latoken.py +217 -113
  159. ccxt/async_support/lbank.py +425 -97
  160. ccxt/async_support/luno.py +382 -35
  161. ccxt/async_support/mercado.py +113 -6
  162. ccxt/async_support/mexc.py +874 -437
  163. ccxt/async_support/modetrade.py +2818 -0
  164. ccxt/async_support/myokx.py +54 -0
  165. ccxt/async_support/ndax.py +221 -64
  166. ccxt/async_support/nobitex.py +31 -37
  167. ccxt/async_support/novadax.py +190 -34
  168. ccxt/async_support/oceanex.py +217 -28
  169. ccxt/async_support/okcoin.py +253 -145
  170. ccxt/async_support/okexchange.py +11 -11
  171. ccxt/async_support/okx.py +1088 -351
  172. ccxt/async_support/okxus.py +54 -0
  173. ccxt/async_support/ompfinex.py +25 -24
  174. ccxt/async_support/onetrading.py +213 -392
  175. ccxt/async_support/oxfun.py +245 -166
  176. ccxt/async_support/p2b.py +151 -29
  177. ccxt/async_support/paradex.py +562 -49
  178. ccxt/async_support/paymium.py +82 -19
  179. ccxt/async_support/phemex.py +713 -172
  180. ccxt/async_support/poloniex.py +1602 -283
  181. ccxt/async_support/probit.py +224 -95
  182. ccxt/async_support/ramzinex.py +30 -27
  183. ccxt/async_support/sarmayex.py +9 -9
  184. ccxt/async_support/sarrafex.py +13 -13
  185. ccxt/async_support/tabdeal.py +14 -13
  186. ccxt/async_support/tetherland.py +9 -9
  187. ccxt/async_support/timex.py +210 -51
  188. ccxt/async_support/tokocrypto.py +167 -47
  189. ccxt/async_support/tradeogre.py +266 -31
  190. ccxt/async_support/twox.py +9 -9
  191. ccxt/async_support/ubitex.py +12 -12
  192. ccxt/async_support/upbit.py +568 -165
  193. ccxt/async_support/vertex.py +160 -32
  194. ccxt/async_support/wallex.py +12 -12
  195. ccxt/async_support/wavesexchange.py +165 -30
  196. ccxt/async_support/whitebit.py +975 -127
  197. ccxt/async_support/woo.py +1918 -1016
  198. ccxt/async_support/woofipro.py +433 -141
  199. ccxt/async_support/xt.py +649 -193
  200. ccxt/async_support/yobit.py +195 -70
  201. ccxt/async_support/zaif.py +91 -15
  202. ccxt/async_support/zonda.py +151 -36
  203. ccxt/base/decimal_to_precision.py +14 -10
  204. ccxt/base/errors.py +49 -18
  205. ccxt/base/exchange.py +1556 -450
  206. ccxt/base/precise.py +10 -0
  207. ccxt/base/types.py +114 -6
  208. ccxt/bequant.py +5 -3
  209. ccxt/bigone.py +279 -144
  210. ccxt/binance.py +2347 -1158
  211. ccxt/binancecoinm.py +9 -3
  212. ccxt/binanceus.py +17 -3
  213. ccxt/binanceusdm.py +9 -4
  214. ccxt/bingx.py +2962 -920
  215. ccxt/bit2c.py +147 -27
  216. ccxt/bitbank.py +151 -23
  217. ccxt/bitbns.py +104 -30
  218. ccxt/bitfinex.py +3290 -1113
  219. ccxt/bitflyer.py +202 -27
  220. ccxt/bitget.py +3683 -1538
  221. ccxt/bithumb.py +194 -38
  222. ccxt/bitimen.py +9 -9
  223. ccxt/bitir.py +35 -35
  224. ccxt/bitmart.py +1288 -350
  225. ccxt/bitmex.py +260 -75
  226. ccxt/bitopro.py +262 -62
  227. ccxt/bitpin.py +15 -14
  228. ccxt/bitrue.py +459 -290
  229. ccxt/bitso.py +199 -54
  230. ccxt/bitstamp.py +230 -96
  231. ccxt/bitteam.py +167 -25
  232. ccxt/{huobijp.py → bittrade.py} +158 -30
  233. ccxt/bitvavo.py +213 -49
  234. ccxt/blockchaincom.py +160 -46
  235. ccxt/blofin.py +502 -120
  236. ccxt/btcalpha.py +169 -31
  237. ccxt/btcbox.py +291 -23
  238. ccxt/btcmarkets.py +211 -58
  239. ccxt/btcturk.py +161 -38
  240. ccxt/bybit.py +1775 -1030
  241. ccxt/cex.py +1439 -1303
  242. ccxt/coinbase.py +724 -212
  243. ccxt/coinbaseadvanced.py +2 -1
  244. ccxt/coinbaseexchange.py +388 -89
  245. ccxt/coinbaseinternational.py +412 -57
  246. ccxt/coincatch.py +177 -78
  247. ccxt/coincheck.py +135 -19
  248. ccxt/coinex.py +606 -232
  249. ccxt/coinmate.py +189 -63
  250. ccxt/coinmetro.py +194 -54
  251. ccxt/coinone.py +158 -51
  252. ccxt/coinsph.py +336 -61
  253. ccxt/coinspot.py +151 -52
  254. ccxt/cryptocom.py +661 -111
  255. ccxt/cryptomus.py +1137 -0
  256. ccxt/defx.py +2070 -0
  257. ccxt/delta.py +299 -99
  258. ccxt/deribit.py +348 -126
  259. ccxt/derive.py +2571 -0
  260. ccxt/digifinex.py +430 -214
  261. ccxt/ellipx.py +2029 -0
  262. ccxt/eterex.py +7 -7
  263. ccxt/excoino.py +29 -29
  264. ccxt/exir.py +11 -11
  265. ccxt/exmo.py +343 -131
  266. ccxt/exnovin.py +8 -8
  267. ccxt/farhadexchange.py +10 -10
  268. ccxt/fmfwio.py +2 -1
  269. ccxt/foxbit.py +1935 -0
  270. ccxt/gate.py +1351 -529
  271. ccxt/gateio.py +2 -1
  272. ccxt/gemini.py +144 -39
  273. ccxt/hashkey.py +152 -109
  274. ccxt/hibachi.py +2079 -0
  275. ccxt/hitbtc.py +395 -167
  276. ccxt/hitobit.py +9 -9
  277. ccxt/hollaex.py +307 -119
  278. ccxt/htx.py +851 -383
  279. ccxt/huobi.py +2 -1
  280. ccxt/hyperliquid.py +1848 -536
  281. ccxt/independentreserve.py +287 -15
  282. ccxt/indodax.py +190 -33
  283. ccxt/jibitex.py +9 -9
  284. ccxt/kraken.py +794 -351
  285. ccxt/krakenfutures.py +214 -62
  286. ccxt/kucoin.py +715 -396
  287. ccxt/kucoinfutures.py +652 -89
  288. ccxt/latoken.py +217 -113
  289. ccxt/lbank.py +425 -97
  290. ccxt/luno.py +382 -35
  291. ccxt/mercado.py +113 -6
  292. ccxt/mexc.py +873 -437
  293. ccxt/modetrade.py +2818 -0
  294. ccxt/myokx.py +54 -0
  295. ccxt/ndax.py +221 -64
  296. ccxt/nobitex.py +29 -35
  297. ccxt/novadax.py +190 -34
  298. ccxt/oceanex.py +217 -28
  299. ccxt/okcoin.py +253 -145
  300. ccxt/okexchange.py +9 -9
  301. ccxt/okx.py +1088 -351
  302. ccxt/okxus.py +54 -0
  303. ccxt/ompfinex.py +22 -21
  304. ccxt/onetrading.py +213 -392
  305. ccxt/oxfun.py +245 -166
  306. ccxt/p2b.py +151 -29
  307. ccxt/paradex.py +562 -49
  308. ccxt/paymium.py +82 -19
  309. ccxt/phemex.py +712 -172
  310. ccxt/poloniex.py +1601 -283
  311. ccxt/pro/__init__.py +76 -17
  312. ccxt/pro/alpaca.py +21 -6
  313. ccxt/pro/apex.py +984 -0
  314. ccxt/pro/ascendex.py +58 -10
  315. ccxt/pro/bequant.py +6 -1
  316. ccxt/pro/binance.py +728 -156
  317. ccxt/pro/binancecoinm.py +6 -2
  318. ccxt/pro/binanceus.py +8 -4
  319. ccxt/pro/binanceusdm.py +7 -2
  320. ccxt/pro/bingx.py +333 -142
  321. ccxt/pro/bitfinex.py +727 -262
  322. ccxt/pro/bitget.py +570 -79
  323. ccxt/pro/bithumb.py +20 -6
  324. ccxt/pro/bitmart.py +216 -87
  325. ccxt/pro/bitmex.py +47 -9
  326. ccxt/pro/bitopro.py +26 -14
  327. ccxt/pro/bitrue.py +22 -22
  328. ccxt/pro/bitstamp.py +54 -21
  329. ccxt/pro/{huobijp.py → bittrade.py} +7 -6
  330. ccxt/pro/bitvavo.py +191 -67
  331. ccxt/pro/blockchaincom.py +21 -8
  332. ccxt/pro/blofin.py +9 -1
  333. ccxt/pro/bybit.py +632 -245
  334. ccxt/pro/cex.py +59 -24
  335. ccxt/pro/coinbase.py +102 -73
  336. ccxt/pro/coinbaseadvanced.py +2 -1
  337. ccxt/pro/coinbaseexchange.py +8 -8
  338. ccxt/pro/coinbaseinternational.py +181 -25
  339. ccxt/pro/coincatch.py +6 -7
  340. ccxt/pro/coincheck.py +11 -6
  341. ccxt/pro/coinex.py +967 -665
  342. ccxt/pro/coinone.py +16 -9
  343. ccxt/pro/cryptocom.py +448 -45
  344. ccxt/pro/defx.py +831 -0
  345. ccxt/pro/deribit.py +150 -14
  346. ccxt/pro/derive.py +704 -0
  347. ccxt/pro/exmo.py +239 -6
  348. ccxt/pro/gate.py +623 -65
  349. ccxt/pro/gateio.py +2 -1
  350. ccxt/pro/gemini.py +27 -11
  351. ccxt/pro/hashkey.py +2 -2
  352. ccxt/pro/hitbtc.py +196 -91
  353. ccxt/pro/hollaex.py +23 -7
  354. ccxt/pro/htx.py +51 -14
  355. ccxt/pro/huobi.py +2 -1
  356. ccxt/pro/hyperliquid.py +591 -27
  357. ccxt/pro/independentreserve.py +9 -6
  358. ccxt/pro/kraken.py +640 -320
  359. ccxt/pro/krakenfutures.py +62 -35
  360. ccxt/pro/kucoin.py +267 -46
  361. ccxt/pro/kucoinfutures.py +165 -21
  362. ccxt/pro/lbank.py +102 -21
  363. ccxt/pro/luno.py +12 -8
  364. ccxt/pro/mexc.py +877 -111
  365. ccxt/pro/modetrade.py +1271 -0
  366. ccxt/pro/myokx.py +38 -0
  367. ccxt/pro/ndax.py +15 -2
  368. ccxt/pro/okcoin.py +23 -4
  369. ccxt/pro/okx.py +573 -98
  370. ccxt/pro/okxus.py +38 -0
  371. ccxt/pro/onetrading.py +30 -13
  372. ccxt/pro/oxfun.py +131 -27
  373. ccxt/pro/p2b.py +88 -22
  374. ccxt/pro/paradex.py +3 -3
  375. ccxt/pro/phemex.py +75 -21
  376. ccxt/pro/poloniex.py +124 -41
  377. ccxt/pro/probit.py +87 -80
  378. ccxt/pro/tradeogre.py +272 -0
  379. ccxt/pro/upbit.py +152 -12
  380. ccxt/pro/vertex.py +8 -3
  381. ccxt/pro/whitebit.py +58 -5
  382. ccxt/pro/woo.py +228 -37
  383. ccxt/pro/woofipro.py +106 -18
  384. ccxt/pro/xt.py +111 -5
  385. ccxt/probit.py +224 -95
  386. ccxt/protobuf/__init__.py +0 -0
  387. ccxt/protobuf/mexc/PrivateAccountV3Api_pb2.py +37 -0
  388. ccxt/protobuf/mexc/PrivateDealsV3Api_pb2.py +37 -0
  389. ccxt/protobuf/mexc/PrivateOrdersV3Api_pb2.py +37 -0
  390. ccxt/protobuf/mexc/PublicAggreBookTickerV3Api_pb2.py +37 -0
  391. ccxt/protobuf/mexc/PublicAggreDealsV3Api_pb2.py +39 -0
  392. ccxt/protobuf/mexc/PublicAggreDepthsV3Api_pb2.py +39 -0
  393. ccxt/protobuf/mexc/PublicBookTickerBatchV3Api_pb2.py +38 -0
  394. ccxt/protobuf/mexc/PublicBookTickerV3Api_pb2.py +37 -0
  395. ccxt/protobuf/mexc/PublicDealsV3Api_pb2.py +39 -0
  396. ccxt/protobuf/mexc/PublicIncreaseDepthsBatchV3Api_pb2.py +38 -0
  397. ccxt/protobuf/mexc/PublicIncreaseDepthsV3Api_pb2.py +39 -0
  398. ccxt/protobuf/mexc/PublicLimitDepthsV3Api_pb2.py +39 -0
  399. ccxt/protobuf/mexc/PublicMiniTickerV3Api_pb2.py +37 -0
  400. ccxt/protobuf/mexc/PublicMiniTickersV3Api_pb2.py +38 -0
  401. ccxt/protobuf/mexc/PublicSpotKlineV3Api_pb2.py +37 -0
  402. ccxt/protobuf/mexc/PushDataV3ApiWrapper_pb2.py +52 -0
  403. ccxt/protobuf/mexc/__init__.py +0 -0
  404. ccxt/ramzinex.py +28 -25
  405. ccxt/sarmayex.py +7 -7
  406. ccxt/sarrafex.py +10 -10
  407. ccxt/static_dependencies/__init__.py +1 -1
  408. ccxt/static_dependencies/lark/py.typed +0 -0
  409. ccxt/static_dependencies/marshmallow/py.typed +0 -0
  410. ccxt/static_dependencies/marshmallow_dataclass/py.typed +0 -0
  411. ccxt/static_dependencies/marshmallow_oneofschema/py.typed +0 -0
  412. ccxt/tabdeal.py +12 -11
  413. ccxt/test/tests_async.py +261 -57
  414. ccxt/test/tests_helpers.py +1 -3
  415. ccxt/test/tests_init.py +4 -3
  416. ccxt/test/tests_sync.py +261 -57
  417. ccxt/tetherland.py +7 -7
  418. ccxt/timex.py +210 -51
  419. ccxt/tokocrypto.py +167 -47
  420. ccxt/tradeogre.py +266 -31
  421. ccxt/twox.py +7 -7
  422. ccxt/ubitex.py +9 -9
  423. ccxt/upbit.py +568 -165
  424. ccxt/vertex.py +160 -32
  425. ccxt/wallex.py +9 -9
  426. ccxt/wavesexchange.py +165 -30
  427. ccxt/whitebit.py +975 -127
  428. ccxt/woo.py +1917 -1016
  429. ccxt/woofipro.py +432 -141
  430. ccxt/xt.py +649 -193
  431. ccxt/yobit.py +194 -70
  432. ccxt/zaif.py +91 -15
  433. ccxt/zonda.py +151 -36
  434. {ccxt_ir-4.3.46.0.2.dist-info → ccxt_ir-4.5.0.dist-info}/METADATA +225 -73
  435. ccxt_ir-4.5.0.dist-info/RECORD +743 -0
  436. {ccxt_ir-4.3.46.0.2.dist-info → ccxt_ir-4.5.0.dist-info}/WHEEL +1 -1
  437. ccxt/abstract/ace.py +0 -15
  438. ccxt/abstract/bitbay.py +0 -53
  439. ccxt/abstract/bitcoincom.py +0 -115
  440. ccxt/abstract/bitfinex2.py +0 -139
  441. ccxt/abstract/bitpanda.py +0 -35
  442. ccxt/abstract/bl3p.py +0 -19
  443. ccxt/abstract/coinlist.py +0 -54
  444. ccxt/abstract/currencycom.py +0 -68
  445. ccxt/abstract/hitbtc3.py +0 -115
  446. ccxt/abstract/idex.py +0 -26
  447. ccxt/abstract/kuna.py +0 -182
  448. ccxt/abstract/lykke.py +0 -29
  449. ccxt/abstract/poloniexfutures.py +0 -48
  450. ccxt/abstract/wazirx.py +0 -30
  451. ccxt/ace.py +0 -1012
  452. ccxt/async_support/ace.py +0 -1012
  453. ccxt/async_support/base/ws/aiohttp_client.py +0 -125
  454. ccxt/async_support/base/ws/fast_client.py +0 -96
  455. ccxt/async_support/bitbay.py +0 -17
  456. ccxt/async_support/bitcoincom.py +0 -17
  457. ccxt/async_support/bitfinex2.py +0 -3552
  458. ccxt/async_support/bitpanda.py +0 -16
  459. ccxt/async_support/bl3p.py +0 -485
  460. ccxt/async_support/coinlist.py +0 -2243
  461. ccxt/async_support/currencycom.py +0 -1950
  462. ccxt/async_support/hitbtc3.py +0 -16
  463. ccxt/async_support/idex.py +0 -1766
  464. ccxt/async_support/kuna.py +0 -1841
  465. ccxt/async_support/lykke.py +0 -1270
  466. ccxt/async_support/poloniexfutures.py +0 -1717
  467. ccxt/async_support/wazirx.py +0 -1224
  468. ccxt/bitbay.py +0 -17
  469. ccxt/bitcoincom.py +0 -17
  470. ccxt/bitfinex2.py +0 -3552
  471. ccxt/bitpanda.py +0 -16
  472. ccxt/bl3p.py +0 -485
  473. ccxt/coinlist.py +0 -2243
  474. ccxt/currencycom.py +0 -1950
  475. ccxt/hitbtc3.py +0 -16
  476. ccxt/idex.py +0 -1766
  477. ccxt/kuna.py +0 -1841
  478. ccxt/lykke.py +0 -1270
  479. ccxt/poloniexfutures.py +0 -1717
  480. ccxt/pro/bitcoincom.py +0 -34
  481. ccxt/pro/bitfinex2.py +0 -1083
  482. ccxt/pro/bitpanda.py +0 -15
  483. ccxt/pro/currencycom.py +0 -536
  484. ccxt/pro/idex.py +0 -672
  485. ccxt/pro/poloniexfutures.py +0 -990
  486. ccxt/pro/wazirx.py +0 -749
  487. ccxt/test/base/__init__.py +0 -29
  488. ccxt/test/base/test_account.py +0 -26
  489. ccxt/test/base/test_balance.py +0 -56
  490. ccxt/test/base/test_borrow_interest.py +0 -35
  491. ccxt/test/base/test_borrow_rate.py +0 -32
  492. ccxt/test/base/test_calculate_fee.py +0 -51
  493. ccxt/test/base/test_crypto.py +0 -127
  494. ccxt/test/base/test_currency.py +0 -76
  495. ccxt/test/base/test_datetime.py +0 -109
  496. ccxt/test/base/test_decimal_to_precision.py +0 -392
  497. ccxt/test/base/test_deep_extend.py +0 -68
  498. ccxt/test/base/test_deposit_withdrawal.py +0 -50
  499. ccxt/test/base/test_exchange_datetime_functions.py +0 -76
  500. ccxt/test/base/test_funding_rate_history.py +0 -29
  501. ccxt/test/base/test_last_price.py +0 -31
  502. ccxt/test/base/test_ledger_entry.py +0 -45
  503. ccxt/test/base/test_ledger_item.py +0 -48
  504. ccxt/test/base/test_leverage_tier.py +0 -33
  505. ccxt/test/base/test_liquidation.py +0 -50
  506. ccxt/test/base/test_margin_mode.py +0 -24
  507. ccxt/test/base/test_margin_modification.py +0 -35
  508. ccxt/test/base/test_market.py +0 -193
  509. ccxt/test/base/test_number.py +0 -411
  510. ccxt/test/base/test_ohlcv.py +0 -33
  511. ccxt/test/base/test_open_interest.py +0 -32
  512. ccxt/test/base/test_order.py +0 -64
  513. ccxt/test/base/test_order_book.py +0 -69
  514. ccxt/test/base/test_position.py +0 -60
  515. ccxt/test/base/test_shared_methods.py +0 -353
  516. ccxt/test/base/test_status.py +0 -24
  517. ccxt/test/base/test_throttle.py +0 -126
  518. ccxt/test/base/test_ticker.py +0 -92
  519. ccxt/test/base/test_trade.py +0 -47
  520. ccxt/test/base/test_trading_fee.py +0 -26
  521. ccxt/test/base/test_transaction.py +0 -39
  522. ccxt/test/test_async.py +0 -1649
  523. ccxt/test/test_sync.py +0 -1648
  524. ccxt/wazirx.py +0 -1224
  525. ccxt_ir-4.3.46.0.2.dist-info/RECORD +0 -772
  526. /ccxt/abstract/{huobijp.py → bittrade.py} +0 -0
  527. {ccxt_ir-4.3.46.0.2.dist-info → ccxt_ir-4.5.0.dist-info/licenses}/LICENSE.txt +0 -0
  528. {ccxt_ir-4.3.46.0.2.dist-info → ccxt_ir-4.5.0.dist-info}/top_level.txt +0 -0
ccxt/async_support/cex.py CHANGED
@@ -5,76 +5,103 @@
5
5
 
6
6
  from ccxt.async_support.base.exchange import Exchange
7
7
  from ccxt.abstract.cex import ImplicitAPI
8
+ import asyncio
8
9
  import hashlib
9
- import json
10
- from ccxt.base.types import Balances, Currencies, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFees
10
+ from ccxt.base.types import Account, Any, Balances, Currencies, Currency, DepositAddress, Int, LedgerEntry, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, TradingFees, Transaction, TransferEntry
11
11
  from typing import List
12
12
  from ccxt.base.errors import ExchangeError
13
13
  from ccxt.base.errors import AuthenticationError
14
+ from ccxt.base.errors import PermissionDenied
14
15
  from ccxt.base.errors import ArgumentsRequired
15
- from ccxt.base.errors import BadSymbol
16
- from ccxt.base.errors import NullResponse
16
+ from ccxt.base.errors import BadRequest
17
17
  from ccxt.base.errors import InsufficientFunds
18
- from ccxt.base.errors import InvalidOrder
19
- from ccxt.base.errors import OrderNotFound
20
- from ccxt.base.errors import DDoSProtection
21
- from ccxt.base.errors import RateLimitExceeded
22
- from ccxt.base.errors import InvalidNonce
18
+ from ccxt.base.errors import NullResponse
23
19
  from ccxt.base.decimal_to_precision import TICK_SIZE
24
20
  from ccxt.base.precise import Precise
25
21
 
26
22
 
27
23
  class cex(Exchange, ImplicitAPI):
28
24
 
29
- def describe(self):
25
+ def describe(self) -> Any:
30
26
  return self.deep_extend(super(cex, self).describe(), {
31
27
  'id': 'cex',
32
28
  'name': 'CEX.IO',
33
29
  'countries': ['GB', 'EU', 'CY', 'RU'],
34
- 'rateLimit': 1500,
30
+ 'rateLimit': 300, # 200 req/min
35
31
  'pro': True,
36
32
  'has': {
37
33
  'CORS': None,
38
34
  'spot': True,
39
- 'margin': False, # has but not through api
35
+ 'margin': False, # has, but not through api
40
36
  'swap': False,
41
37
  'future': False,
42
38
  'option': False,
43
39
  'addMargin': False,
40
+ 'borrowCrossMargin': False,
41
+ 'borrowIsolatedMargin': False,
42
+ 'borrowMargin': False,
44
43
  'cancelAllOrders': True,
45
44
  'cancelOrder': True,
46
- 'cancelOrders': False,
47
- 'createDepositAddress': False,
48
- 'createMarketBuyOrderWithCost': True,
49
- 'createMarketOrderWithCost': False,
50
- 'createMarketSellOrderWithCost': False,
45
+ 'closeAllPositions': False,
46
+ 'closePosition': False,
51
47
  'createOrder': True,
52
- 'createStopLimitOrder': False,
53
- 'createStopMarketOrder': False,
54
- 'createStopOrder': False,
55
- 'editOrder': True,
48
+ 'createOrderWithTakeProfitAndStopLoss': False,
49
+ 'createOrderWithTakeProfitAndStopLossWs': False,
50
+ 'createPostOnlyOrder': False,
51
+ 'createReduceOnlyOrder': False,
52
+ 'createStopOrder': True,
53
+ 'createTriggerOrder': True,
54
+ 'fetchAccounts': True,
56
55
  'fetchBalance': True,
56
+ 'fetchBorrowInterest': False,
57
+ 'fetchBorrowRate': False,
58
+ 'fetchBorrowRateHistories': False,
59
+ 'fetchBorrowRateHistory': False,
60
+ 'fetchBorrowRates': False,
61
+ 'fetchBorrowRatesPerSymbol': False,
62
+ 'fetchClosedOrder': True,
57
63
  'fetchClosedOrders': True,
64
+ 'fetchCrossBorrowRate': False,
65
+ 'fetchCrossBorrowRates': False,
58
66
  'fetchCurrencies': True,
59
- 'fetchDeposit': False,
60
67
  'fetchDepositAddress': True,
61
- 'fetchDepositAddresses': False,
62
- 'fetchDeposits': False,
63
- 'fetchDepositsWithdrawals': False,
68
+ 'fetchDepositsWithdrawals': True,
64
69
  'fetchFundingHistory': False,
70
+ 'fetchFundingInterval': False,
71
+ 'fetchFundingIntervals': False,
65
72
  'fetchFundingRate': False,
66
73
  'fetchFundingRateHistory': False,
67
74
  'fetchFundingRates': False,
75
+ 'fetchGreeks': False,
68
76
  'fetchIndexOHLCV': False,
77
+ 'fetchIsolatedBorrowRate': False,
78
+ 'fetchIsolatedBorrowRates': False,
79
+ 'fetchIsolatedPositions': False,
80
+ 'fetchLedger': True,
81
+ 'fetchLeverage': False,
82
+ 'fetchLeverages': False,
83
+ 'fetchLeverageTiers': False,
84
+ 'fetchLiquidations': False,
85
+ 'fetchLongShortRatio': False,
86
+ 'fetchLongShortRatioHistory': False,
87
+ 'fetchMarginAdjustmentHistory': False,
69
88
  'fetchMarginMode': False,
89
+ 'fetchMarginModes': False,
90
+ 'fetchMarketLeverageTiers': False,
70
91
  'fetchMarkets': True,
71
92
  'fetchMarkOHLCV': False,
93
+ 'fetchMarkPrices': False,
94
+ 'fetchMyLiquidations': False,
95
+ 'fetchMySettlementHistory': False,
72
96
  'fetchOHLCV': True,
97
+ 'fetchOpenInterest': False,
73
98
  'fetchOpenInterestHistory': False,
99
+ 'fetchOpenInterests': False,
100
+ 'fetchOpenOrder': True,
74
101
  'fetchOpenOrders': True,
75
- 'fetchOrder': True,
102
+ 'fetchOption': False,
103
+ 'fetchOptionChain': False,
76
104
  'fetchOrderBook': True,
77
- 'fetchOrders': True,
78
105
  'fetchPosition': False,
79
106
  'fetchPositionHistory': False,
80
107
  'fetchPositionMode': False,
@@ -83,1523 +110,1633 @@ class cex(Exchange, ImplicitAPI):
83
110
  'fetchPositionsHistory': False,
84
111
  'fetchPositionsRisk': False,
85
112
  'fetchPremiumIndexOHLCV': False,
113
+ 'fetchSettlementHistory': False,
86
114
  'fetchTicker': True,
87
115
  'fetchTickers': True,
116
+ 'fetchTime': True,
88
117
  'fetchTrades': True,
89
- 'fetchTradingFee': False,
90
118
  'fetchTradingFees': True,
91
- 'fetchTransactions': False,
92
- 'fetchTransfer': False,
93
- 'fetchTransfers': False,
94
- 'fetchWithdrawal': False,
95
- 'fetchWithdrawals': False,
96
- 'fetchWithdrawalWhitelist': False,
119
+ 'fetchVolatilityHistory': False,
97
120
  'reduceMargin': False,
121
+ 'repayCrossMargin': False,
122
+ 'repayIsolatedMargin': False,
123
+ 'repayMargin': False,
98
124
  'setLeverage': False,
99
125
  'setMargin': False,
100
126
  'setMarginMode': False,
101
- 'transfer': False,
102
- 'withdraw': False,
103
- },
104
- 'timeframes': {
105
- '1m': '1m',
106
- '1h': '1h',
107
- '1d': '1d',
127
+ 'setPositionMode': False,
128
+ 'transfer': True,
108
129
  },
109
130
  'urls': {
110
131
  'logo': 'https://user-images.githubusercontent.com/1294454/27766442-8ddc33b0-5ed8-11e7-8b98-f786aef0f3c9.jpg',
111
132
  'api': {
112
- 'rest': 'https://cex.io/api',
133
+ 'public': 'https://trade.cex.io/api/spot/rest-public',
134
+ 'private': 'https://trade.cex.io/api/spot/rest',
113
135
  },
114
136
  'www': 'https://cex.io',
115
- 'doc': 'https://cex.io/cex-api',
137
+ 'doc': 'https://trade.cex.io/docs/',
116
138
  'fees': [
117
139
  'https://cex.io/fee-schedule',
118
140
  'https://cex.io/limits-commissions',
119
141
  ],
120
142
  'referral': 'https://cex.io/r/0/up105393824/0/',
121
143
  },
122
- 'requiredCredentials': {
123
- 'apiKey': True,
124
- 'secret': True,
125
- 'uid': True,
126
- },
127
144
  'api': {
128
145
  'public': {
129
- 'get': [
130
- 'currency_profile',
131
- 'currency_limits/',
132
- 'last_price/{pair}/',
133
- 'last_prices/{currencies}/',
134
- 'ohlcv/hd/{yyyymmdd}/{pair}',
135
- 'order_book/{pair}/',
136
- 'ticker/{pair}/',
137
- 'tickers/{currencies}/',
138
- 'trade_history/{pair}/',
139
- ],
140
- 'post': [
141
- 'convert/{pair}',
142
- 'price_stats/{pair}',
143
- ],
146
+ 'get': {},
147
+ 'post': {
148
+ 'get_server_time': 1,
149
+ 'get_pairs_info': 1,
150
+ 'get_currencies_info': 1,
151
+ 'get_processing_info': 10,
152
+ 'get_ticker': 1,
153
+ 'get_trade_history': 1,
154
+ 'get_order_book': 1,
155
+ 'get_candles': 1,
156
+ },
144
157
  },
145
158
  'private': {
146
- 'post': [
147
- 'active_orders_status/',
148
- 'archived_orders/{pair}/',
149
- 'balance/',
150
- 'cancel_order/',
151
- 'cancel_orders/{pair}/',
152
- 'cancel_replace_order/{pair}/',
153
- 'close_position/{pair}/',
154
- 'get_address/',
155
- 'get_crypto_address',
156
- 'get_myfee/',
157
- 'get_order/',
158
- 'get_order_tx/',
159
- 'open_orders/{pair}/',
160
- 'open_orders/',
161
- 'open_position/{pair}/',
162
- 'open_positions/{pair}/',
163
- 'place_order/{pair}/',
164
- 'raw_tx_history',
165
- ],
159
+ 'get': {},
160
+ 'post': {
161
+ 'get_my_current_fee': 5,
162
+ 'get_fee_strategy': 1,
163
+ 'get_my_volume': 5,
164
+ 'do_create_account': 1,
165
+ 'get_my_account_status_v3': 5,
166
+ 'get_my_wallet_balance': 5,
167
+ 'get_my_orders': 5,
168
+ 'do_my_new_order': 1,
169
+ 'do_cancel_my_order': 1,
170
+ 'do_cancel_all_orders': 5,
171
+ 'get_order_book': 1,
172
+ 'get_candles': 1,
173
+ 'get_trade_history': 1,
174
+ 'get_my_transaction_history': 1,
175
+ 'get_my_funding_history': 5,
176
+ 'do_my_internal_transfer': 1,
177
+ 'get_processing_info': 10,
178
+ 'get_deposit_address': 5,
179
+ 'do_deposit_funds_from_wallet': 1,
180
+ 'do_withdrawal_funds_to_wallet': 1,
181
+ },
166
182
  },
167
183
  },
168
- 'fees': {
169
- 'trading': {
170
- 'maker': self.parse_number('0.0016'),
171
- 'taker': self.parse_number('0.0025'),
172
- },
173
- 'funding': {
174
- 'withdraw': {},
175
- 'deposit': {
176
- # 'USD': amount => amount * 0.035 + 0.25,
177
- # 'EUR': amount => amount * 0.035 + 0.24,
178
- # 'RUB': amount => amount * 0.05 + 15.57,
179
- # 'GBP': amount => amount * 0.035 + 0.2,
180
- 'BTC': 0.0,
181
- 'ETH': 0.0,
182
- 'BCH': 0.0,
183
- 'DASH': 0.0,
184
- 'BTG': 0.0,
185
- 'ZEC': 0.0,
186
- 'XRP': 0.0,
187
- 'XLM': 0.0,
184
+ 'features': {
185
+ 'spot': {
186
+ 'sandbox': False,
187
+ 'createOrder': {
188
+ 'marginMode': False,
189
+ 'triggerPrice': True,
190
+ 'triggerPriceType': None,
191
+ 'triggerDirection': False,
192
+ 'stopLossPrice': False, # todo
193
+ 'takeProfitPrice': False, # todo
194
+ 'attachedStopLossTakeProfit': None,
195
+ 'timeInForce': {
196
+ 'IOC': True,
197
+ 'FOK': True,
198
+ 'PO': False, # todo check
199
+ 'GTD': True,
200
+ },
201
+ 'hedged': False,
202
+ 'leverage': False,
203
+ 'marketBuyRequiresPrice': False,
204
+ 'marketBuyByCost': True, # todo check
205
+ 'selfTradePrevention': False,
206
+ 'trailing': False,
207
+ 'iceberg': False,
188
208
  },
209
+ 'createOrders': None,
210
+ 'fetchMyTrades': None,
211
+ 'fetchOrder': None,
212
+ 'fetchOpenOrders': {
213
+ 'marginMode': False,
214
+ 'limit': 1000,
215
+ 'trigger': False,
216
+ 'trailing': False,
217
+ 'symbolRequired': False,
218
+ },
219
+ 'fetchOrders': None,
220
+ 'fetchClosedOrders': {
221
+ 'marginMode': False,
222
+ 'limit': 1000,
223
+ 'daysBack': 100000,
224
+ 'daysBackCanceled': 1,
225
+ 'untilDays': 100000,
226
+ 'trigger': False,
227
+ 'trailing': False,
228
+ 'symbolRequired': False,
229
+ },
230
+ 'fetchOHLCV': {
231
+ 'limit': 1000,
232
+ },
233
+ },
234
+ 'swap': {
235
+ 'linear': None,
236
+ 'inverse': None,
237
+ },
238
+ 'future': {
239
+ 'linear': None,
240
+ 'inverse': None,
189
241
  },
190
242
  },
191
243
  'precisionMode': TICK_SIZE,
192
244
  'exceptions': {
193
245
  'exact': {},
194
246
  'broad': {
247
+ 'You have negative balance on following accounts': InsufficientFunds,
248
+ 'Mandatory parameter side should be one of BUY,SELL': BadRequest,
249
+ 'API orders from Main account are not allowed': BadRequest,
250
+ 'check failed': BadRequest,
195
251
  'Insufficient funds': InsufficientFunds,
196
- 'Nonce must be incremented': InvalidNonce,
197
- 'Invalid Order': InvalidOrder,
198
- 'Order not found': OrderNotFound,
199
- 'limit exceeded': RateLimitExceeded, # {"error":"rate limit exceeded"}
200
- 'Invalid API key': AuthenticationError,
201
- 'There was an error while placing your order': InvalidOrder,
202
- 'Sorry, too many clients already': DDoSProtection,
203
- 'Invalid Symbols Pair': BadSymbol,
204
- 'Wrong currency pair': BadSymbol, # {"error":"There was an error while placing your order: Wrong currency pair.","safe":true}
252
+ 'Get deposit address for main account is not allowed': PermissionDenied,
253
+ 'Market Trigger orders are not allowed': BadRequest, # for some reason, triggerPrice does not work for market orders
254
+ 'key not passed or incorrect': AuthenticationError,
205
255
  },
206
256
  },
257
+ 'timeframes': {
258
+ '1m': '1m',
259
+ '5m': '5m',
260
+ '15m': '15m',
261
+ '30m': '30m',
262
+ '1h': '1h',
263
+ '2h': '2h',
264
+ '4h': '4h',
265
+ '1d': '1d',
266
+ },
207
267
  'options': {
208
- 'fetchOHLCVWarning': True,
209
- 'createMarketBuyOrderRequiresPrice': True,
210
- 'order': {
211
- 'status': {
212
- 'c': 'canceled',
213
- 'd': 'closed',
214
- 'cd': 'canceled',
215
- 'a': 'open',
216
- },
217
- },
218
- 'defaultNetwork': 'ERC20',
219
- 'defaultNetworks': {
220
- 'USDT': 'TRC20',
221
- },
222
268
  'networks': {
223
- 'ERC20': 'Ethereum',
224
- 'BTC': 'BTC',
225
- 'BEP20': 'Binance Smart Chain',
226
- 'TRC20': 'Tron',
269
+ 'BTC': 'bitcoin',
270
+ 'ERC20': 'ERC20',
271
+ 'BSC20': 'binancesmartchain',
272
+ 'DOGE': 'dogecoin',
273
+ 'ALGO': 'algorand',
274
+ 'XLM': 'stellar',
275
+ 'ATOM': 'cosmos',
276
+ 'LTC': 'litecoin',
277
+ 'XRP': 'ripple',
278
+ 'FTM': 'fantom',
279
+ 'MINA': 'mina',
280
+ 'THETA': 'theta',
281
+ 'XTZ': 'tezos',
282
+ 'TIA': 'celestia',
283
+ 'CRONOS': 'cronos', # CRC20
284
+ 'MATIC': 'polygon',
285
+ 'TON': 'ton',
286
+ 'TRC20': 'tron',
287
+ 'SOLANA': 'solana',
288
+ 'SGB': 'songbird',
289
+ 'DYDX': 'dydx',
290
+ 'DASH': 'dash',
291
+ 'ZIL': 'zilliqa',
292
+ 'EOS': 'eos',
293
+ 'AVALANCHEC': 'avalanche',
294
+ 'ETHPOW': 'ethereumpow',
295
+ 'NEAR': 'near',
296
+ 'ARB': 'arbitrum',
297
+ 'DOT': 'polkadot',
298
+ 'OPT': 'optimism',
299
+ 'INJ': 'injective',
300
+ 'ADA': 'cardano',
301
+ 'ONT': 'ontology',
302
+ 'ICP': 'icp',
303
+ 'KAVA': 'kava',
304
+ 'KSM': 'kusama',
305
+ 'SEI': 'sei',
306
+ # 'OSM': 'osmosis',
307
+ 'NEO': 'neo',
308
+ 'NEO3': 'neo3',
309
+ # 'TERRAOLD': 'terra', # tbd
310
+ # 'TERRA': 'terra2', # tbd
311
+ # 'EVER': 'everscale', # tbd
312
+ 'XDC': 'xdc',
227
313
  },
228
314
  },
229
315
  })
230
316
 
231
- async def fetch_currencies_from_cache(self, params={}):
232
- # self method is now redundant
233
- # currencies are now fetched before markets
234
- options = self.safe_value(self.options, 'fetchCurrencies', {})
235
- timestamp = self.safe_integer(options, 'timestamp')
236
- expires = self.safe_integer(options, 'expires', 1000)
237
- now = self.milliseconds()
238
- if (timestamp is None) or ((now - timestamp) > expires):
239
- response = await self.publicGetCurrencyProfile(params)
240
- self.options['fetchCurrencies'] = self.extend(options, {
241
- 'response': response,
242
- 'timestamp': now,
243
- })
244
- return self.safe_value(self.options['fetchCurrencies'], 'response')
245
-
246
317
  async def fetch_currencies(self, params={}) -> Currencies:
247
318
  """
248
319
  fetches all available currencies on an exchange
320
+
321
+ https://trade.cex.io/docs/#rest-public-api-calls-currencies-info
322
+
249
323
  :param dict [params]: extra parameters specific to the exchange API endpoint
250
324
  :returns dict: an associative dictionary of currencies
251
325
  """
252
- response = await self.fetch_currencies_from_cache(params)
253
- self.options['currencies'] = {
254
- 'timestamp': self.milliseconds(),
255
- 'response': response,
256
- }
326
+ promises = []
327
+ promises.append(self.publicPostGetCurrenciesInfo(params))
257
328
  #
258
- # {
259
- # "e":"currency_profile",
260
- # "ok":"ok",
261
- # "data":{
262
- # "symbols":[
263
- # {
264
- # "code":"GHS",
265
- # "contract":true,
266
- # "commodity":true,
267
- # "fiat":false,
268
- # "description":"CEX.IO doesn't provide cloud mining services anymore.",
269
- # "precision":8,
270
- # "scale":0,
271
- # "minimumCurrencyAmount":"0.00000001",
272
- # "minimalWithdrawalAmount":-1
273
- # },
274
- # {
275
- # "code":"BTC",
276
- # "contract":false,
277
- # "commodity":false,
278
- # "fiat":false,
279
- # "description":"",
280
- # "precision":8,
281
- # "scale":0,
282
- # "minimumCurrencyAmount":"0.00000001",
283
- # "minimalWithdrawalAmount":0.002
284
- # },
285
- # {
286
- # "code":"ETH",
287
- # "contract":false,
288
- # "commodity":false,
289
- # "fiat":false,
290
- # "description":"",
291
- # "precision":8,
292
- # "scale":2,
293
- # "minimumCurrencyAmount":"0.00000100",
294
- # "minimalWithdrawalAmount":0.01
295
- # }
296
- # ],
297
- # "pairs":[
298
- # {
299
- # "symbol1":"BTC",
300
- # "symbol2":"USD",
301
- # "pricePrecision":1,
302
- # "priceScale":"/1000000",
303
- # "minLotSize":0.002,
304
- # "minLotSizeS2":20
305
- # },
306
- # {
307
- # "symbol1":"ETH",
308
- # "symbol2":"USD",
309
- # "pricePrecision":2,
310
- # "priceScale":"/10000",
311
- # "minLotSize":0.1,
312
- # "minLotSizeS2":20
313
- # }
314
- # ]
315
- # }
316
- # }
329
+ # {
330
+ # "ok": "ok",
331
+ # "data": [
332
+ # {
333
+ # "currency": "ZAP",
334
+ # "fiat": False,
335
+ # "precision": "8",
336
+ # "walletPrecision": "6",
337
+ # "walletDeposit": True,
338
+ # "walletWithdrawal": True
339
+ # },
340
+ # ...
317
341
  #
318
- data = self.safe_value(response, 'data', [])
319
- currencies = self.safe_value(data, 'symbols', [])
320
- result: dict = {}
321
- for i in range(0, len(currencies)):
322
- currency = currencies[i]
323
- id = self.safe_string(currency, 'code')
324
- code = self.safe_currency_code(id)
325
- active = True
326
- result[code] = {
327
- 'id': id,
328
- 'code': code,
329
- 'name': id,
330
- 'active': active,
331
- 'deposit': None,
332
- 'withdraw': None,
333
- 'precision': self.parse_number(self.parse_precision(self.safe_string(currency, 'precision'))),
334
- 'fee': None,
342
+ promises.append(self.publicPostGetProcessingInfo(params))
343
+ #
344
+ # {
345
+ # "ok": "ok",
346
+ # "data": {
347
+ # "ADA": {
348
+ # "name": "Cardano",
349
+ # "blockchains": {
350
+ # "cardano": {
351
+ # "type": "coin",
352
+ # "deposit": "enabled",
353
+ # "minDeposit": "1",
354
+ # "withdrawal": "enabled",
355
+ # "minWithdrawal": "5",
356
+ # "withdrawalFee": "1",
357
+ # "withdrawalFeePercent": "0",
358
+ # "depositConfirmations": "15"
359
+ # }
360
+ # }
361
+ # },
362
+ # ...
363
+ #
364
+ responses = await asyncio.gather(*promises)
365
+ dataCurrencies = self.safe_list(responses[0], 'data', [])
366
+ dataNetworks = self.safe_dict(responses[1], 'data', {})
367
+ currenciesIndexed = self.index_by(dataCurrencies, 'currency')
368
+ data = self.deep_extend(currenciesIndexed, dataNetworks)
369
+ return self.parse_currencies(self.to_array(data))
370
+
371
+ def parse_currency(self, rawCurrency: dict) -> Currency:
372
+ id = self.safe_string(rawCurrency, 'currency')
373
+ code = self.safe_currency_code(id)
374
+ type = 'fiat' if self.safe_bool(rawCurrency, 'fiat') else 'crypto'
375
+ currencyPrecision = self.parse_number(self.parse_precision(self.safe_string(rawCurrency, 'precision')))
376
+ networks: dict = {}
377
+ rawNetworks = self.safe_dict(rawCurrency, 'blockchains', {})
378
+ keys = list(rawNetworks.keys())
379
+ for j in range(0, len(keys)):
380
+ networkId = keys[j]
381
+ rawNetwork = rawNetworks[networkId]
382
+ networkCode = self.network_id_to_code(networkId)
383
+ deposit = self.safe_string(rawNetwork, 'deposit') == 'enabled'
384
+ withdraw = self.safe_string(rawNetwork, 'withdrawal') == 'enabled'
385
+ networks[networkCode] = {
386
+ 'id': networkId,
387
+ 'network': networkCode,
388
+ 'margin': None,
389
+ 'deposit': deposit,
390
+ 'withdraw': withdraw,
391
+ 'active': None,
392
+ 'fee': self.safe_number(rawNetwork, 'withdrawalFee'),
393
+ 'precision': currencyPrecision,
335
394
  'limits': {
336
- 'amount': {
337
- 'min': self.safe_number(currency, 'minimumCurrencyAmount'),
395
+ 'deposit': {
396
+ 'min': self.safe_number(rawNetwork, 'minDeposit'),
338
397
  'max': None,
339
398
  },
340
399
  'withdraw': {
341
- 'min': self.safe_number(currency, 'minimalWithdrawalAmount'),
400
+ 'min': self.safe_number(rawNetwork, 'minWithdrawal'),
342
401
  'max': None,
343
402
  },
344
403
  },
345
- 'info': currency,
404
+ 'info': rawNetwork,
346
405
  }
347
- return result
406
+ return self.safe_currency_structure({
407
+ 'id': id,
408
+ 'code': code,
409
+ 'name': None,
410
+ 'type': type,
411
+ 'active': None,
412
+ 'deposit': self.safe_bool(rawCurrency, 'walletDeposit'),
413
+ 'withdraw': self.safe_bool(rawCurrency, 'walletWithdrawal'),
414
+ 'fee': None,
415
+ 'precision': currencyPrecision,
416
+ 'limits': {
417
+ 'amount': {
418
+ 'min': None,
419
+ 'max': None,
420
+ },
421
+ 'withdraw': {
422
+ 'min': None,
423
+ 'max': None,
424
+ },
425
+ },
426
+ 'networks': networks,
427
+ 'info': rawCurrency,
428
+ })
348
429
 
349
430
  async def fetch_markets(self, params={}) -> List[Market]:
350
431
  """
351
- retrieves data on all markets for cex
432
+ retrieves data on all markets for ace
433
+
434
+ https://trade.cex.io/docs/#rest-public-api-calls-pairs-info
435
+
352
436
  :param dict [params]: extra parameters specific to the exchange API endpoint
353
437
  :returns dict[]: an array of objects representing market data
354
438
  """
355
- currenciesResponse = await self.fetch_currencies_from_cache(params)
356
- currenciesData = self.safe_value(currenciesResponse, 'data', {})
357
- currencies = self.safe_value(currenciesData, 'symbols', [])
358
- currenciesById = self.index_by(currencies, 'code')
359
- pairs = self.safe_value(currenciesData, 'pairs', [])
360
- response = await self.publicGetCurrencyLimits(params)
439
+ response = await self.publicPostGetPairsInfo(params)
361
440
  #
362
- # {
363
- # "e":"currency_limits",
364
- # "ok":"ok",
365
- # "data": {
366
- # "pairs":[
367
- # {
368
- # "symbol1":"BTC",
369
- # "symbol2":"USD",
370
- # "minLotSize":0.002,
371
- # "minLotSizeS2":20,
372
- # "maxLotSize":30,
373
- # "minPrice":"1500",
374
- # "maxPrice":"35000"
375
- # },
376
- # {
377
- # "symbol1":"BCH",
378
- # "symbol2":"EUR",
379
- # "minLotSize":0.1,
380
- # "minLotSizeS2":20,
381
- # "maxLotSize":null,
382
- # "minPrice":"25",
383
- # "maxPrice":"8192"
384
- # }
385
- # ]
386
- # }
387
- # }
441
+ # {
442
+ # "ok": "ok",
443
+ # "data": [
444
+ # {
445
+ # "base": "AI",
446
+ # "quote": "USD",
447
+ # "baseMin": "30",
448
+ # "baseMax": "2516000",
449
+ # "baseLotSize": "0.000001",
450
+ # "quoteMin": "10",
451
+ # "quoteMax": "1000000",
452
+ # "quoteLotSize": "0.01000000",
453
+ # "basePrecision": "6",
454
+ # "quotePrecision": "8",
455
+ # "pricePrecision": "4",
456
+ # "minPrice": "0.0377",
457
+ # "maxPrice": "19.5000"
458
+ # },
459
+ # ...
388
460
  #
389
- result = []
390
- markets = self.safe_value(response['data'], 'pairs')
391
- for i in range(0, len(markets)):
392
- market = markets[i]
393
- baseId = self.safe_string(market, 'symbol1')
394
- quoteId = self.safe_string(market, 'symbol2')
395
- base = self.safe_currency_code(baseId)
396
- quote = self.safe_currency_code(quoteId)
397
- baseCurrency = self.safe_value(currenciesById, baseId, {})
398
- quoteCurrency = self.safe_value(currenciesById, quoteId, {})
399
- pricePrecisionString = self.safe_string(quoteCurrency, 'precision', '8')
400
- for j in range(0, len(pairs)):
401
- pair = pairs[j]
402
- if (pair['symbol1'] == baseId) and (pair['symbol2'] == quoteId):
403
- # we might need to account for `priceScale` here
404
- pricePrecisionString = self.safe_string(pair, 'pricePrecision', pricePrecisionString)
405
- baseCurrencyPrecision = self.safe_string(baseCurrency, 'precision', '8')
406
- baseCurrencyScale = self.safe_string(baseCurrency, 'scale', '0')
407
- amountPrecisionString = Precise.string_sub(baseCurrencyPrecision, baseCurrencyScale)
408
- result.append({
409
- 'id': baseId + '/' + quoteId,
410
- 'symbol': base + '/' + quote,
411
- 'base': base,
412
- 'quote': quote,
413
- 'settle': None,
414
- 'baseId': baseId,
415
- 'quoteId': quoteId,
416
- 'settleId': None,
417
- 'type': 'spot',
418
- 'spot': True,
419
- 'margin': None,
420
- 'swap': False,
421
- 'future': False,
422
- 'option': False,
423
- 'active': None,
424
- 'contract': False,
425
- 'linear': None,
426
- 'inverse': None,
427
- 'contractSize': None,
428
- 'expiry': None,
429
- 'expiryDatetime': None,
430
- 'strike': None,
431
- 'optionType': None,
432
- 'precision': {
433
- 'amount': self.parse_number(self.parse_precision(amountPrecisionString)),
434
- 'price': self.parse_number(self.parse_precision(pricePrecisionString)),
461
+ data = self.safe_list(response, 'data', [])
462
+ return self.parse_markets(data)
463
+
464
+ def parse_market(self, market: dict) -> Market:
465
+ baseId = self.safe_string(market, 'base')
466
+ base = self.safe_currency_code(baseId)
467
+ quoteId = self.safe_string(market, 'quote')
468
+ quote = self.safe_currency_code(quoteId)
469
+ id = base + '-' + quote # not actual id, but for self exchange we can use self abbreviation, because e.g. tickers have hyphen in between
470
+ symbol = base + '/' + quote
471
+ return self.safe_market_structure({
472
+ 'id': id,
473
+ 'symbol': symbol,
474
+ 'base': base,
475
+ 'baseId': baseId,
476
+ 'quote': quote,
477
+ 'quoteId': quoteId,
478
+ 'settle': None,
479
+ 'settleId': None,
480
+ 'type': 'spot',
481
+ 'spot': True,
482
+ 'margin': False,
483
+ 'swap': False,
484
+ 'future': False,
485
+ 'option': False,
486
+ 'contract': False,
487
+ 'linear': None,
488
+ 'inverse': None,
489
+ 'contractSize': None,
490
+ 'expiry': None,
491
+ 'expiryDatetime': None,
492
+ 'strike': None,
493
+ 'optionType': None,
494
+ 'limits': {
495
+ 'amount': {
496
+ 'min': self.safe_number(market, 'baseMin'),
497
+ 'max': self.safe_number(market, 'baseMax'),
435
498
  },
436
- 'limits': {
437
- 'leverage': {
438
- 'min': None,
439
- 'max': None,
440
- },
441
- 'amount': {
442
- 'min': self.safe_number(market, 'minLotSize'),
443
- 'max': self.safe_number(market, 'maxLotSize'),
444
- },
445
- 'price': {
446
- 'min': self.safe_number(market, 'minPrice'),
447
- 'max': self.safe_number(market, 'maxPrice'),
448
- },
449
- 'cost': {
450
- 'min': self.safe_number(market, 'minLotSizeS2'),
451
- 'max': None,
452
- },
499
+ 'price': {
500
+ 'min': self.safe_number(market, 'minPrice'),
501
+ 'max': self.safe_number(market, 'maxPrice'),
453
502
  },
454
- 'created': None,
455
- 'info': market,
456
- })
457
- return result
458
-
459
- def parse_balance(self, response) -> Balances:
460
- result: dict = {'info': response}
461
- ommited = ['username', 'timestamp']
462
- balances = self.omit(response, ommited)
463
- currencyIds = list(balances.keys())
464
- for i in range(0, len(currencyIds)):
465
- currencyId = currencyIds[i]
466
- balance = self.safe_value(balances, currencyId, {})
467
- account = self.account()
468
- account['free'] = self.safe_string(balance, 'available')
469
- # https://github.com/ccxt/ccxt/issues/5484
470
- account['used'] = self.safe_string(balance, 'orders', '0')
471
- code = self.safe_currency_code(currencyId)
472
- result[code] = account
473
- return self.safe_balance(result)
503
+ 'cost': {
504
+ 'min': self.safe_number(market, 'quoteMin'),
505
+ 'max': self.safe_number(market, 'quoteMax'),
506
+ },
507
+ 'leverage': {
508
+ 'min': None,
509
+ 'max': None,
510
+ },
511
+ },
512
+ 'precision': {
513
+ 'amount': self.safe_string(market, 'baseLotSize'),
514
+ 'price': self.parse_number(self.parse_precision(self.safe_string(market, 'pricePrecision'))),
515
+ # 'cost': self.parse_number(self.parse_precision(self.safe_string(market, 'quoteLotSize'))), # buggy, doesn't reflect their documentation
516
+ 'base': self.parse_number(self.parse_precision(self.safe_string(market, 'basePrecision'))),
517
+ 'quote': self.parse_number(self.parse_precision(self.safe_string(market, 'quotePrecision'))),
518
+ },
519
+ 'active': None,
520
+ 'created': None,
521
+ 'info': market,
522
+ })
474
523
 
475
- async def fetch_balance(self, params={}) -> Balances:
524
+ async def fetch_time(self, params={}) -> Int:
476
525
  """
477
- :see: https://docs.cex.io/#account-balance
478
- query for balance and get the amount of funds available for trading or funds locked in orders
526
+ fetches the current integer timestamp in milliseconds from the exchange server
479
527
  :param dict [params]: extra parameters specific to the exchange API endpoint
480
- :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
528
+ :returns int: the current integer timestamp in milliseconds from the exchange server
481
529
  """
482
- await self.load_markets()
483
- response = await self.privatePostBalance(params)
484
- return self.parse_balance(response)
530
+ response = await self.publicPostGetServerTime(params)
531
+ #
532
+ # {
533
+ # "ok": "ok",
534
+ # "data": {
535
+ # "timestamp": "1728472063472",
536
+ # "ISODate": "2024-10-09T11:07:43.472Z"
537
+ # }
538
+ # }
539
+ #
540
+ data = self.safe_dict(response, 'data')
541
+ timestamp = self.safe_integer(data, 'timestamp')
542
+ return timestamp
485
543
 
486
- async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
544
+ async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
487
545
  """
488
- :see: https://docs.cex.io/#orderbook
489
- fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
490
- :param str symbol: unified symbol of the market to fetch the order book for
491
- :param int [limit]: the maximum amount of order book entries to return
546
+ fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
547
+
548
+ https://trade.cex.io/docs/#rest-public-api-calls-ticker
549
+
550
+ :param str symbol:
492
551
  :param dict [params]: extra parameters specific to the exchange API endpoint
493
- :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
552
+ :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
494
553
  """
495
554
  await self.load_markets()
496
- market = self.market(symbol)
497
- request: dict = {
498
- 'pair': market['id'],
499
- }
500
- if limit is not None:
501
- request['depth'] = limit
502
- response = await self.publicGetOrderBookPair(self.extend(request, params))
503
- timestamp = self.safe_timestamp(response, 'timestamp')
504
- return self.parse_order_book(response, market['symbol'], timestamp)
505
-
506
- def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
507
- #
508
- # [
509
- # 1591403940,
510
- # 0.024972,
511
- # 0.024972,
512
- # 0.024969,
513
- # 0.024969,
514
- # 0.49999900
515
- # ]
516
- #
517
- return [
518
- self.safe_timestamp(ohlcv, 0),
519
- self.safe_number(ohlcv, 1),
520
- self.safe_number(ohlcv, 2),
521
- self.safe_number(ohlcv, 3),
522
- self.safe_number(ohlcv, 4),
523
- self.safe_number(ohlcv, 5),
524
- ]
555
+ response = await self.fetch_tickers([symbol], params)
556
+ return self.safe_dict(response, symbol, {})
525
557
 
526
- async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
558
+ async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
527
559
  """
528
- :see: https://docs.cex.io/#historical-ohlcv-chart
529
- fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
530
- :param str symbol: unified symbol of the market to fetch OHLCV data for
531
- :param str timeframe: the length of time each candle represents
532
- :param int [since]: timestamp in ms of the earliest candle to fetch
533
- :param int [limit]: the maximum amount of candles to fetch
560
+ fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
561
+
562
+ https://trade.cex.io/docs/#rest-public-api-calls-ticker
563
+
564
+ :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
534
565
  :param dict [params]: extra parameters specific to the exchange API endpoint
535
- :returns int[][]: A list of candles ordered, open, high, low, close, volume
566
+ :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
536
567
  """
537
568
  await self.load_markets()
538
- market = self.market(symbol)
539
- if since is None:
540
- since = self.milliseconds() - 86400000 # yesterday
541
- else:
542
- if self.options['fetchOHLCVWarning']:
543
- raise ExchangeError(self.id + " fetchOHLCV warning: CEX can return historical candles for a certain date only, self might produce an empty or None reply. Set exchange.options['fetchOHLCVWarning'] = False or add({'options': {'fetchOHLCVWarning': False}}) to constructor params to suppress self warning message.")
544
- request: dict = {
545
- 'pair': market['id'],
546
- 'yyyymmdd': self.yyyymmdd(since, ''),
547
- }
548
- try:
549
- response = await self.publicGetOhlcvHdYyyymmddPair(self.extend(request, params))
550
- #
551
- # {
552
- # "time":20200606,
553
- # "data1m":"[[1591403940,0.024972,0.024972,0.024969,0.024969,0.49999900]]",
554
- # }
555
- #
556
- key = 'data' + self.safe_string(self.timeframes, timeframe, timeframe)
557
- data = self.safe_string(response, key)
558
- ohlcvs = json.loads(data)
559
- return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
560
- except Exception as e:
561
- if isinstance(e, NullResponse):
562
- return []
563
- return None
569
+ request = {}
570
+ if symbols is not None:
571
+ request['pairs'] = self.market_ids(symbols)
572
+ response = await self.publicPostGetTicker(self.extend(request, params))
573
+ #
574
+ # {
575
+ # "ok": "ok",
576
+ # "data": {
577
+ # "AI-USD": {
578
+ # "bestBid": "0.3917",
579
+ # "bestAsk": "0.3949",
580
+ # "bestBidChange": "0.0035",
581
+ # "bestBidChangePercentage": "0.90",
582
+ # "bestAskChange": "0.0038",
583
+ # "bestAskChangePercentage": "0.97",
584
+ # "low": "0.3787",
585
+ # "high": "0.3925",
586
+ # "volume30d": "2945.722277",
587
+ # "lastTradeDateISO": "2024-10-11T06:18:42.077Z",
588
+ # "volume": "120.736000",
589
+ # "quoteVolume": "46.65654070",
590
+ # "lastTradeVolume": "67.914000",
591
+ # "volumeUSD": "46.65",
592
+ # "last": "0.3949",
593
+ # "lastTradePrice": "0.3925",
594
+ # "priceChange": "0.0038",
595
+ # "priceChangePercentage": "0.97"
596
+ # },
597
+ # ...
598
+ #
599
+ data = self.safe_dict(response, 'data', {})
600
+ return self.parse_tickers(data, symbols)
564
601
 
565
602
  def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
566
- timestamp = self.safe_timestamp(ticker, 'timestamp')
567
- volume = self.safe_string(ticker, 'volume')
568
- high = self.safe_string(ticker, 'high')
569
- low = self.safe_string(ticker, 'low')
570
- bid = self.safe_string(ticker, 'bid')
571
- ask = self.safe_string(ticker, 'ask')
572
- last = self.safe_string(ticker, 'last')
573
- symbol = self.safe_symbol(None, market)
603
+ marketId = self.safe_string(ticker, 'id')
604
+ symbol = self.safe_symbol(marketId, market)
574
605
  return self.safe_ticker({
575
606
  'symbol': symbol,
576
- 'timestamp': timestamp,
577
- 'datetime': self.iso8601(timestamp),
578
- 'high': high,
579
- 'low': low,
580
- 'bid': bid,
607
+ 'timestamp': None,
608
+ 'datetime': None,
609
+ 'high': self.safe_number(ticker, 'high'),
610
+ 'low': self.safe_number(ticker, 'low'),
611
+ 'bid': self.safe_number(ticker, 'bestBid'),
581
612
  'bidVolume': None,
582
- 'ask': ask,
613
+ 'ask': self.safe_number(ticker, 'bestAsk'),
583
614
  'askVolume': None,
584
615
  'vwap': None,
585
616
  'open': None,
586
- 'close': last,
587
- 'last': last,
617
+ 'close': self.safe_string(ticker, 'last'), # last indicative price per api docs(difference also seen here: https://github.com/ccxt/ccxt/actions/runs/14593899575/job/40935513901?pr=25767#step:11:456 )
588
618
  'previousClose': None,
589
- 'change': None,
590
- 'percentage': None,
619
+ 'change': self.safe_number(ticker, 'priceChange'),
620
+ 'percentage': self.safe_number(ticker, 'priceChangePercentage'),
591
621
  'average': None,
592
- 'baseVolume': volume,
593
- 'quoteVolume': None,
622
+ 'baseVolume': self.safe_string(ticker, 'volume'),
623
+ 'quoteVolume': self.safe_string(ticker, 'quoteVolume'),
594
624
  'info': ticker,
595
625
  }, market)
596
626
 
597
- async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
598
- """
599
- fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
600
- :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
601
- :param dict [params]: extra parameters specific to the exchange API endpoint
602
- :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
627
+ async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
603
628
  """
604
- await self.load_markets()
605
- symbols = self.market_symbols(symbols)
606
- currencies = list(self.currencies.keys())
607
- request: dict = {
608
- 'currencies': '/'.join(currencies),
609
- }
610
- response = await self.publicGetTickersCurrencies(self.extend(request, params))
611
- tickers = self.safe_value(response, 'data', [])
612
- result: dict = {}
613
- for t in range(0, len(tickers)):
614
- ticker = tickers[t]
615
- marketId = self.safe_string(ticker, 'pair')
616
- market = self.safe_market(marketId, None, ':')
617
- symbol = market['symbol']
618
- result[symbol] = self.parse_ticker(ticker, market)
619
- return self.filter_by_array_tickers(result, 'symbol', symbols)
629
+ get the list of most recent trades for a particular symbol
620
630
 
621
- async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
622
- """
623
- :see: https://docs.cex.io/#ticker
624
- fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
625
- :param str symbol: unified symbol of the market to fetch the ticker for
631
+ https://trade.cex.io/docs/#rest-public-api-calls-trade-history
632
+
633
+ :param str symbol: unified symbol of the market to fetch trades for
634
+ :param int [since]: timestamp in ms of the earliest trade to fetch
635
+ :param int [limit]: the maximum amount of trades to fetch
626
636
  :param dict [params]: extra parameters specific to the exchange API endpoint
627
- :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
637
+ :param int [params.until]: timestamp in ms of the latest entry
638
+ :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
628
639
  """
629
640
  await self.load_markets()
630
641
  market = self.market(symbol)
631
642
  request: dict = {
632
643
  'pair': market['id'],
633
644
  }
634
- ticker = await self.publicGetTickerPair(self.extend(request, params))
635
- return self.parse_ticker(ticker, market)
645
+ if since is not None:
646
+ request['fromDateISO'] = self.iso8601(since)
647
+ until = None
648
+ until, params = self.handle_param_integer_2(params, 'until', 'till')
649
+ if until is not None:
650
+ request['toDateISO'] = self.iso8601(until)
651
+ if limit is not None:
652
+ request['pageSize'] = min(limit, 10000) # has a bug, still returns more trades
653
+ response = await self.publicPostGetTradeHistory(self.extend(request, params))
654
+ #
655
+ # {
656
+ # "ok": "ok",
657
+ # "data": {
658
+ # "pageSize": "10",
659
+ # "trades": [
660
+ # {
661
+ # "tradeId": "1728630559823-0",
662
+ # "dateISO": "2024-10-11T07:09:19.823Z",
663
+ # "side": "SELL",
664
+ # "price": "60879.5",
665
+ # "amount": "0.00165962"
666
+ # },
667
+ # ... followed by older trades
668
+ #
669
+ data = self.safe_dict(response, 'data', {})
670
+ trades = self.safe_list(data, 'trades', [])
671
+ return self.parse_trades(trades, market, since, limit)
636
672
 
637
673
  def parse_trade(self, trade: dict, market: Market = None) -> Trade:
638
674
  #
639
- # fetchTrades(public)
675
+ # public fetchTrades
640
676
  #
641
- # {
642
- # "type": "sell",
643
- # "date": "1638401878",
644
- # "amount": "0.401000",
645
- # "price": "249",
646
- # "tid": "11922"
647
- # }
677
+ # {
678
+ # "tradeId": "1728630559823-0",
679
+ # "dateISO": "2024-10-11T07:09:19.823Z",
680
+ # "side": "SELL",
681
+ # "price": "60879.5",
682
+ # "amount": "0.00165962"
683
+ # },
648
684
  #
649
- timestamp = self.safe_timestamp(trade, 'date')
650
- id = self.safe_string(trade, 'tid')
651
- type = None
652
- side = self.safe_string(trade, 'type')
653
- priceString = self.safe_string(trade, 'price')
654
- amountString = self.safe_string(trade, 'amount')
685
+ dateStr = self.safe_string(trade, 'dateISO')
686
+ timestamp = self.parse8601(dateStr)
655
687
  market = self.safe_market(None, market)
656
688
  return self.safe_trade({
657
689
  'info': trade,
658
- 'id': id,
659
690
  'timestamp': timestamp,
660
691
  'datetime': self.iso8601(timestamp),
661
692
  'symbol': market['symbol'],
662
- 'type': type,
663
- 'side': side,
693
+ 'id': self.safe_string(trade, 'tradeId'),
664
694
  'order': None,
695
+ 'type': None,
665
696
  'takerOrMaker': None,
666
- 'price': priceString,
667
- 'amount': amountString,
697
+ 'side': self.safe_string_lower(trade, 'side'),
698
+ 'price': self.safe_string(trade, 'price'),
699
+ 'amount': self.safe_string(trade, 'amount'),
668
700
  'cost': None,
669
701
  'fee': None,
670
702
  }, market)
671
703
 
672
- async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
704
+ async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
673
705
  """
674
- :see: https://docs.cex.io/#trade-history
675
- get the list of most recent trades for a particular symbol
676
- :param str symbol: unified symbol of the market to fetch trades for
677
- :param int [since]: timestamp in ms of the earliest trade to fetch
678
- :param int [limit]: the maximum amount of trades to fetch
706
+ fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
707
+
708
+ https://trade.cex.io/docs/#rest-public-api-calls-order-book
709
+
710
+ :param str symbol: unified symbol of the market to fetch the order book for
711
+ :param int [limit]: the maximum amount of order book entries to return
679
712
  :param dict [params]: extra parameters specific to the exchange API endpoint
680
- :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
713
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
714
+ """
715
+ await self.load_markets()
716
+ market = self.market(symbol)
717
+ request: dict = {
718
+ 'pair': market['id'],
719
+ }
720
+ response = await self.publicPostGetOrderBook(self.extend(request, params))
721
+ #
722
+ # {
723
+ # "ok": "ok",
724
+ # "data": {
725
+ # "timestamp": "1728636922648",
726
+ # "currency1": "BTC",
727
+ # "currency2": "USDT",
728
+ # "bids": [
729
+ # [
730
+ # "60694.1",
731
+ # "13.12849761"
732
+ # ],
733
+ # [
734
+ # "60694.0",
735
+ # "0.71829244"
736
+ # ],
737
+ # ...
738
+ #
739
+ orderBook = self.safe_dict(response, 'data', {})
740
+ timestamp = self.safe_integer(orderBook, 'timestamp')
741
+ return self.parse_order_book(orderBook, market['symbol'], timestamp)
742
+
743
+ async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
744
+ """
745
+ fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
746
+
747
+ https://trade.cex.io/docs/#rest-public-api-calls-candles
748
+
749
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
750
+ :param str timeframe: the length of time each candle represents
751
+ :param int [since]: timestamp in ms of the earliest candle to fetch
752
+ :param int [limit]: the maximum amount of candles to fetch
753
+ :param dict [params]: extra parameters specific to the exchange API endpoint
754
+ :param int [params.until]: timestamp in ms of the latest entry
755
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
681
756
  """
757
+ dataType = None
758
+ dataType, params = self.handle_option_and_params(params, 'fetchOHLCV', 'dataType')
759
+ if dataType is None:
760
+ raise ArgumentsRequired(self.id + ' fetchOHLCV requires a parameter "dataType" to be either "bestBid" or "bestAsk"')
682
761
  await self.load_markets()
683
762
  market = self.market(symbol)
684
763
  request: dict = {
685
764
  'pair': market['id'],
765
+ 'resolution': self.timeframes[timeframe],
766
+ 'dataType': dataType,
686
767
  }
687
- response = await self.publicGetTradeHistoryPair(self.extend(request, params))
688
- return self.parse_trades(response, market, since, limit)
768
+ if since is not None:
769
+ request['fromISO'] = self.iso8601(since)
770
+ until = None
771
+ until, params = self.handle_param_integer_2(params, 'until', 'till')
772
+ if until is not None:
773
+ request['toISO'] = self.iso8601(until)
774
+ elif since is None:
775
+ # exchange still requires that we provide one of them
776
+ request['toISO'] = self.iso8601(self.milliseconds())
777
+ if since is not None and until is not None and limit is not None:
778
+ raise ArgumentsRequired(self.id + ' fetchOHLCV does not support fetching candles with both a limit and since/until')
779
+ elif (since is not None or until is not None) and limit is None:
780
+ raise ArgumentsRequired(self.id + ' fetchOHLCV requires a limit parameter when fetching candles with since or until')
781
+ if limit is not None:
782
+ request['limit'] = limit
783
+ response = await self.publicPostGetCandles(self.extend(request, params))
784
+ #
785
+ # {
786
+ # "ok": "ok",
787
+ # "data": [
788
+ # {
789
+ # "timestamp": "1728643320000",
790
+ # "open": "61061",
791
+ # "high": "61095.1",
792
+ # "low": "61048.5",
793
+ # "close": "61087.8",
794
+ # "volume": "0",
795
+ # "resolution": "1m",
796
+ # "isClosed": True,
797
+ # "timestampISO": "2024-10-11T10:42:00.000Z"
798
+ # },
799
+ # ...
800
+ #
801
+ data = self.safe_list(response, 'data', [])
802
+ return self.parse_ohlcvs(data, market, timeframe, since, limit)
803
+
804
+ def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
805
+ return [
806
+ self.safe_integer(ohlcv, 'timestamp'),
807
+ self.safe_number(ohlcv, 'open'),
808
+ self.safe_number(ohlcv, 'high'),
809
+ self.safe_number(ohlcv, 'low'),
810
+ self.safe_number(ohlcv, 'close'),
811
+ self.safe_number(ohlcv, 'volume'),
812
+ ]
689
813
 
690
814
  async def fetch_trading_fees(self, params={}) -> TradingFees:
691
815
  """
692
- :see: https://docs.cex.io/#get-my-fee
693
816
  fetch the trading fees for multiple markets
817
+
818
+ https://trade.cex.io/docs/#rest-public-api-calls-candles
819
+
694
820
  :param dict [params]: extra parameters specific to the exchange API endpoint
695
821
  :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
696
822
  """
697
823
  await self.load_markets()
698
- response = await self.privatePostGetMyfee(params)
824
+ response = await self.privatePostGetMyCurrentFee(params)
699
825
  #
700
- # {
701
- # "e": "get_myfee",
702
- # "ok": "ok",
703
- # "data": {
704
- # 'BTC:USD': {buy: '0.25', sell: '0.25', buyMaker: '0.15', sellMaker: "0.15"},
705
- # 'ETH:USD': {buy: '0.25', sell: '0.25', buyMaker: '0.15', sellMaker: "0.15"},
706
- # ..
707
- # }
708
- # }
826
+ # {
827
+ # "ok": "ok",
828
+ # "data": {
829
+ # "tradingFee": {
830
+ # "AI-USD": {
831
+ # "percent": "0.25"
832
+ # },
833
+ # ...
709
834
  #
710
- data = self.safe_value(response, 'data', {})
835
+ data = self.safe_dict(response, 'data', {})
836
+ fees = self.safe_dict(data, 'tradingFee', {})
837
+ return self.parse_trading_fees(fees, True)
838
+
839
+ def parse_trading_fees(self, response, useKeyAsId=False) -> TradingFees:
711
840
  result: dict = {}
841
+ keys = list(response.keys())
842
+ for i in range(0, len(keys)):
843
+ key = keys[i]
844
+ market = None
845
+ if useKeyAsId:
846
+ market = self.safe_market(key)
847
+ parsed = self.parse_trading_fee(response[key], market)
848
+ result[parsed['symbol']] = parsed
712
849
  for i in range(0, len(self.symbols)):
713
850
  symbol = self.symbols[i]
714
- market = self.market(symbol)
715
- fee = self.safe_value(data, market['id'], {})
716
- makerString = self.safe_string(fee, 'buyMaker')
717
- takerString = self.safe_string(fee, 'buy')
718
- maker = self.parse_number(Precise.string_div(makerString, '100'))
719
- taker = self.parse_number(Precise.string_div(takerString, '100'))
720
- result[symbol] = {
721
- 'info': fee,
722
- 'symbol': symbol,
723
- 'maker': maker,
724
- 'taker': taker,
725
- 'percentage': True,
726
- }
851
+ if not (symbol in result):
852
+ market = self.market(symbol)
853
+ result[symbol] = self.parse_trading_fee(response, market)
727
854
  return result
728
855
 
856
+ def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
857
+ return {
858
+ 'info': fee,
859
+ 'symbol': self.safe_string(market, 'symbol'),
860
+ 'maker': self.safe_number(fee, 'percent'),
861
+ 'taker': self.safe_number(fee, 'percent'),
862
+ 'percentage': None,
863
+ 'tierBased': None,
864
+ }
865
+
866
+ async def fetch_accounts(self, params={}) -> List[Account]:
867
+ await self.load_markets()
868
+ response = await self.privatePostGetMyAccountStatusV3(params)
869
+ #
870
+ # {
871
+ # "ok": "ok",
872
+ # "data": {
873
+ # "convertedCurrency": "USD",
874
+ # "balancesPerAccounts": {
875
+ # "": {
876
+ # "AI": {
877
+ # "balance": "0.000000",
878
+ # "balanceOnHold": "0.000000"
879
+ # },
880
+ # "USDT": {
881
+ # "balance": "0.00000000",
882
+ # "balanceOnHold": "0.00000000"
883
+ # }
884
+ # }
885
+ # }
886
+ # }
887
+ # }
888
+ #
889
+ data = self.safe_dict(response, 'data', {})
890
+ balances = self.safe_dict(data, 'balancesPerAccounts', {})
891
+ arrays = self.to_array(balances)
892
+ return self.parse_accounts(arrays, params)
893
+
894
+ def parse_account(self, account: dict) -> Account:
895
+ return {
896
+ 'id': None,
897
+ 'type': None,
898
+ 'code': None,
899
+ 'info': account,
900
+ }
901
+
902
+ async def fetch_balance(self, params={}) -> Balances:
903
+ """
904
+ query for balance and get the amount of funds available for trading or funds locked in orders
905
+
906
+ https://trade.cex.io/docs/#rest-private-api-calls-account-status-v3
907
+
908
+ :param dict [params]: extra parameters specific to the exchange API endpoint
909
+ :param dict [params.method]: 'privatePostGetMyWalletBalance' or 'privatePostGetMyAccountStatusV3'
910
+ :param dict [params.account]: in case 'privatePostGetMyAccountStatusV3' is chosen, self can specify the account name(default is empty string)
911
+ :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
912
+ """
913
+ accountName = None
914
+ accountName, params = self.handle_param_string(params, 'account', '') # default is empty string
915
+ method = None
916
+ method, params = self.handle_param_string(params, 'method', 'privatePostGetMyWalletBalance')
917
+ accountBalance = None
918
+ if method == 'privatePostGetMyAccountStatusV3':
919
+ response = await self.privatePostGetMyAccountStatusV3(params)
920
+ #
921
+ # {
922
+ # "ok": "ok",
923
+ # "data": {
924
+ # "convertedCurrency": "USD",
925
+ # "balancesPerAccounts": {
926
+ # "": {
927
+ # "AI": {
928
+ # "balance": "0.000000",
929
+ # "balanceOnHold": "0.000000"
930
+ # },
931
+ # ....
932
+ #
933
+ data = self.safe_dict(response, 'data', {})
934
+ balances = self.safe_dict(data, 'balancesPerAccounts', {})
935
+ accountBalance = self.safe_dict(balances, accountName, {})
936
+ else:
937
+ response = await self.privatePostGetMyWalletBalance(params)
938
+ #
939
+ # {
940
+ # "ok": "ok",
941
+ # "data": {
942
+ # "AI": {
943
+ # "balance": "25.606429"
944
+ # },
945
+ # "USDT": {
946
+ # "balance": "7.935449"
947
+ # },
948
+ # ...
949
+ #
950
+ accountBalance = self.safe_dict(response, 'data', {})
951
+ return self.parse_balance(accountBalance)
952
+
953
+ def parse_balance(self, response) -> Balances:
954
+ result: dict = {
955
+ 'info': response,
956
+ }
957
+ keys = list(response.keys())
958
+ for i in range(0, len(keys)):
959
+ key = keys[i]
960
+ balance = self.safe_dict(response, key, {})
961
+ code = self.safe_currency_code(key)
962
+ account: dict = {
963
+ 'used': self.safe_string(balance, 'balanceOnHold'),
964
+ 'total': self.safe_string(balance, 'balance'),
965
+ }
966
+ result[code] = account
967
+ return self.safe_balance(result)
968
+
969
+ async def fetch_orders_by_status(self, status: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
970
+ """
971
+ fetches information on multiple orders made by the user
972
+
973
+ https://trade.cex.io/docs/#rest-private-api-calls-orders
974
+
975
+ :param str status: order status to fetch for
976
+ :param str symbol: unified market symbol of the market orders were made in
977
+ :param int [since]: the earliest time in ms to fetch orders for
978
+ :param int [limit]: the maximum number of order structures to retrieve
979
+ :param dict [params]: extra parameters specific to the exchange API endpoint
980
+ :param int [params.until]: timestamp in ms of the latest entry
981
+ :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
982
+ """
983
+ await self.load_markets()
984
+ request: dict = {}
985
+ isClosedOrders = (status == 'closed')
986
+ if isClosedOrders:
987
+ request['archived'] = True
988
+ market = None
989
+ if symbol is not None:
990
+ market = self.market(symbol)
991
+ request['pair'] = market['id']
992
+ if limit is not None:
993
+ request['pageSize'] = limit
994
+ if since is not None:
995
+ request['serverCreateTimestampFrom'] = since
996
+ elif isClosedOrders:
997
+ # exchange requires a `since` parameter for closed orders, so set default to allowed 365
998
+ request['serverCreateTimestampFrom'] = self.milliseconds() - 364 * 24 * 60 * 60 * 1000
999
+ until = None
1000
+ until, params = self.handle_param_integer_2(params, 'until', 'till')
1001
+ if until is not None:
1002
+ request['serverCreateTimestampTo'] = until
1003
+ response = await self.privatePostGetMyOrders(self.extend(request, params))
1004
+ #
1005
+ # if called without `pair`
1006
+ #
1007
+ # {
1008
+ # "ok": "ok",
1009
+ # "data": [
1010
+ # {
1011
+ # "orderId": "1313003",
1012
+ # "clientOrderId": "037F0AFEB93A",
1013
+ # "clientId": "up421412345",
1014
+ # "accountId": null,
1015
+ # "status": "FILLED",
1016
+ # "statusIsFinal": True,
1017
+ # "currency1": "AI",
1018
+ # "currency2": "USDT",
1019
+ # "side": "BUY",
1020
+ # "orderType": "Market",
1021
+ # "timeInForce": "IOC",
1022
+ # "comment": null,
1023
+ # "rejectCode": null,
1024
+ # "rejectReason": null,
1025
+ # "initialOnHoldAmountCcy1": null,
1026
+ # "initialOnHoldAmountCcy2": "10.23456700",
1027
+ # "executedAmountCcy1": "25.606429",
1028
+ # "executedAmountCcy2": "10.20904439",
1029
+ # "requestedAmountCcy1": null,
1030
+ # "requestedAmountCcy2": "10.20904439",
1031
+ # "originalAmountCcy2": "10.23456700",
1032
+ # "feeAmount": "0.02552261",
1033
+ # "feeCurrency": "USDT",
1034
+ # "price": null,
1035
+ # "averagePrice": "0.3986",
1036
+ # "clientCreateTimestamp": "1728474625320",
1037
+ # "serverCreateTimestamp": "1728474624956",
1038
+ # "lastUpdateTimestamp": "1728474628015",
1039
+ # "expireTime": null,
1040
+ # "effectiveTime": null
1041
+ # },
1042
+ # ...
1043
+ #
1044
+ data = self.safe_list(response, 'data', [])
1045
+ return self.parse_orders(data, market, since, limit)
1046
+
1047
+ async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
1048
+ """
1049
+
1050
+ https://trade.cex.io/docs/#rest-private-api-calls-orders
1051
+
1052
+ fetches information on multiple canceled orders made by the user
1053
+ :param str symbol: unified market symbol of the market orders were made in
1054
+ :param int [since]: timestamp in ms of the earliest order, default is None
1055
+ :param int [limit]: max number of orders to return, default is None
1056
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1057
+ :returns dict: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1058
+ """
1059
+ return await self.fetch_orders_by_status('closed', symbol, since, limit, params)
1060
+
1061
+ async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
1062
+ """
1063
+
1064
+ https://trade.cex.io/docs/#rest-private-api-calls-orders
1065
+
1066
+ fetches information on multiple canceled orders made by the user
1067
+ :param str symbol: unified market symbol of the market orders were made in
1068
+ :param int [since]: timestamp in ms of the earliest order, default is None
1069
+ :param int [limit]: max number of orders to return, default is None
1070
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1071
+ :returns dict: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1072
+ """
1073
+ return await self.fetch_orders_by_status('open', symbol, since, limit, params)
1074
+
1075
+ async def fetch_open_order(self, id: str, symbol: Str = None, params={}):
1076
+ """
1077
+ fetches information on an open order made by the user
1078
+
1079
+ https://trade.cex.io/docs/#rest-private-api-calls-orders
1080
+
1081
+ :param str id: order id
1082
+ :param str [symbol]: unified symbol of the market the order was made in
1083
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1084
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1085
+ """
1086
+ await self.load_markets()
1087
+ request: dict = {
1088
+ 'orderId': int(id),
1089
+ }
1090
+ result = await self.fetch_open_orders(symbol, None, None, self.extend(request, params))
1091
+ return result[0]
1092
+
1093
+ async def fetch_closed_order(self, id: str, symbol: Str = None, params={}):
1094
+ """
1095
+ fetches information on an closed order made by the user
1096
+
1097
+ https://trade.cex.io/docs/#rest-private-api-calls-orders
1098
+
1099
+ :param str id: order id
1100
+ :param str [symbol]: unified symbol of the market the order was made in
1101
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1102
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1103
+ """
1104
+ await self.load_markets()
1105
+ request: dict = {
1106
+ 'orderId': int(id),
1107
+ }
1108
+ result = await self.fetch_closed_orders(symbol, None, None, self.extend(request, params))
1109
+ return result[0]
1110
+
1111
+ def parse_order_status(self, status: Str):
1112
+ statuses: dict = {
1113
+ 'PENDING_NEW': 'open',
1114
+ 'NEW': 'open',
1115
+ 'PARTIALLY_FILLED': 'open',
1116
+ 'FILLED': 'closed',
1117
+ 'EXPIRED': 'expired',
1118
+ 'REJECTED': 'rejected',
1119
+ 'PENDING_CANCEL': 'canceling',
1120
+ 'CANCELLED': 'canceled',
1121
+ }
1122
+ return self.safe_string(statuses, status, status)
1123
+
1124
+ def parse_order(self, order: dict, market: Market = None) -> Order:
1125
+ #
1126
+ # "orderId": "1313003",
1127
+ # "clientOrderId": "037F0AFEB93A",
1128
+ # "clientId": "up421412345",
1129
+ # "accountId": null,
1130
+ # "status": "FILLED",
1131
+ # "statusIsFinal": True,
1132
+ # "currency1": "AI",
1133
+ # "currency2": "USDT",
1134
+ # "side": "BUY",
1135
+ # "orderType": "Market",
1136
+ # "timeInForce": "IOC",
1137
+ # "comment": null,
1138
+ # "rejectCode": null,
1139
+ # "rejectReason": null,
1140
+ # "initialOnHoldAmountCcy1": null,
1141
+ # "initialOnHoldAmountCcy2": "10.23456700",
1142
+ # "executedAmountCcy1": "25.606429",
1143
+ # "executedAmountCcy2": "10.20904439",
1144
+ # "requestedAmountCcy1": null,
1145
+ # "requestedAmountCcy2": "10.20904439",
1146
+ # "originalAmountCcy2": "10.23456700",
1147
+ # "feeAmount": "0.02552261",
1148
+ # "feeCurrency": "USDT",
1149
+ # "price": null,
1150
+ # "averagePrice": "0.3986",
1151
+ # "clientCreateTimestamp": "1728474625320",
1152
+ # "serverCreateTimestamp": "1728474624956",
1153
+ # "lastUpdateTimestamp": "1728474628015",
1154
+ # "expireTime": null,
1155
+ # "effectiveTime": null
1156
+ #
1157
+ currency1 = self.safe_string(order, 'currency1')
1158
+ currency2 = self.safe_string(order, 'currency2')
1159
+ marketId = None
1160
+ if currency1 is not None and currency2 is not None:
1161
+ marketId = currency1 + '-' + currency2
1162
+ market = self.safe_market(marketId, market)
1163
+ symbol = market['symbol']
1164
+ status = self.parse_order_status(self.safe_string(order, 'status'))
1165
+ fee = {}
1166
+ feeAmount = self.safe_number(order, 'feeAmount')
1167
+ if feeAmount is not None:
1168
+ currencyId = self.safe_string(order, 'feeCurrency')
1169
+ feeCode = self.safe_currency_code(currencyId)
1170
+ fee['currency'] = feeCode
1171
+ fee['cost'] = feeAmount
1172
+ timestamp = self.safe_integer(order, 'serverCreateTimestamp')
1173
+ requestedBase = self.safe_number(order, 'requestedAmountCcy1')
1174
+ executedBase = self.safe_number(order, 'executedAmountCcy1')
1175
+ # requestedQuote = self.safe_number(order, 'requestedAmountCcy2')
1176
+ executedQuote = self.safe_number(order, 'executedAmountCcy2')
1177
+ return self.safe_order({
1178
+ 'id': self.safe_string(order, 'orderId'),
1179
+ 'clientOrderId': self.safe_string(order, 'clientOrderId'),
1180
+ 'timestamp': timestamp,
1181
+ 'datetime': self.iso8601(timestamp),
1182
+ 'lastUpdateTimestamp': self.safe_integer(order, 'lastUpdateTimestamp'),
1183
+ 'lastTradeTimestamp': None,
1184
+ 'symbol': symbol,
1185
+ 'type': self.safe_string_lower(order, 'orderType'),
1186
+ 'timeInForce': self.safe_string(order, 'timeInForce'),
1187
+ 'postOnly': None,
1188
+ 'side': self.safe_string_lower(order, 'side'),
1189
+ 'price': self.safe_number(order, 'price'),
1190
+ 'triggerPrice': self.safe_number(order, 'stopPrice'),
1191
+ 'amount': requestedBase,
1192
+ 'cost': executedQuote,
1193
+ 'average': self.safe_number(order, 'averagePrice'),
1194
+ 'filled': executedBase,
1195
+ 'remaining': None,
1196
+ 'status': status,
1197
+ 'fee': fee,
1198
+ 'trades': None,
1199
+ 'info': order,
1200
+ }, market)
1201
+
729
1202
  async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
730
1203
  """
731
- :see: https://docs.cex.io/#place-order
732
1204
  create a trade order
733
- :see: https://cex.io/rest-api#place-order
1205
+
1206
+ https://trade.cex.io/docs/#rest-private-api-calls-new-order
1207
+
734
1208
  :param str symbol: unified symbol of the market to create an order in
735
1209
  :param str type: 'market' or 'limit'
736
1210
  :param str side: 'buy' or 'sell'
737
1211
  :param float amount: how much of currency you want to trade in units of base currency
738
- :param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
1212
+ :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
739
1213
  :param dict [params]: extra parameters specific to the exchange API endpoint
740
- :param float [params.cost]: the quote quantity that can be used alternative for the amount for market buy orders
1214
+ :param str [params.accountId]: account-id to use(default is empty string)
1215
+ :param float [params.triggerPrice]: the price at which a trigger order is triggered at
741
1216
  :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
742
1217
  """
1218
+ accountId = None
1219
+ accountId, params = self.handle_option_and_params(params, 'createOrder', 'accountId')
1220
+ if accountId is None:
1221
+ raise ArgumentsRequired(self.id + ' createOrder() : API trading is now allowed from main account, set params["accountId"] or .options["createOrder"]["accountId"] to the name of your sub-account')
743
1222
  await self.load_markets()
744
1223
  market = self.market(symbol)
745
1224
  request: dict = {
746
- 'pair': market['id'],
747
- 'type': side,
1225
+ 'clientOrderId': self.uuid(),
1226
+ 'currency1': market['baseId'],
1227
+ 'currency2': market['quoteId'],
1228
+ 'accountId': accountId,
1229
+ 'orderType': self.capitalize(type.lower()),
1230
+ 'side': side.upper(),
1231
+ 'timestamp': self.milliseconds(),
1232
+ 'amountCcy1': self.amount_to_precision(symbol, amount),
748
1233
  }
749
- # for market buy it requires the amount of quote currency to spend
750
- if (type == 'market') and (side == 'buy'):
751
- quoteAmount = None
752
- createMarketBuyOrderRequiresPrice = True
753
- createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
754
- cost = self.safe_string(params, 'cost')
755
- params = self.omit(params, 'cost')
756
- if cost is not None:
757
- quoteAmount = self.cost_to_precision(symbol, cost)
758
- elif createMarketBuyOrderRequiresPrice:
759
- if price is None:
760
- raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend in the amount argument')
761
- else:
762
- amountString = self.number_to_string(amount)
763
- priceString = self.number_to_string(price)
764
- costRequest = Precise.string_mul(amountString, priceString)
765
- quoteAmount = self.cost_to_precision(symbol, costRequest)
766
- else:
767
- quoteAmount = self.cost_to_precision(symbol, amount)
768
- request['amount'] = quoteAmount
769
- else:
770
- request['amount'] = self.amount_to_precision(symbol, amount)
1234
+ timeInForce = None
1235
+ timeInForce, params = self.handle_option_and_params(params, 'createOrder', 'timeInForce', 'GTC')
771
1236
  if type == 'limit':
772
- request['price'] = self.number_to_string(price)
773
- else:
774
- request['order_type'] = type
775
- response = await self.privatePostPlaceOrderPair(self.extend(request, params))
1237
+ request['price'] = self.price_to_precision(symbol, price)
1238
+ request['timeInForce'] = timeInForce
1239
+ triggerPrice = None
1240
+ triggerPrice, params = self.handle_param_string(params, 'triggerPrice')
1241
+ if triggerPrice is not None:
1242
+ request['type'] = 'Stop Limit'
1243
+ request['stopPrice'] = triggerPrice
1244
+ response = await self.privatePostDoMyNewOrder(self.extend(request, params))
776
1245
  #
777
- # {
778
- # "id": "12978363524",
779
- # "time": 1586610022259,
780
- # "type": "buy",
781
- # "price": "0.033934",
782
- # "amount": "0.10722802",
783
- # "pending": "0.10722802",
784
- # "complete": False
785
- # }
1246
+ # on success
786
1247
  #
787
- placedAmount = self.safe_string(response, 'amount')
788
- remaining = self.safe_string(response, 'pending')
789
- timestamp = self.safe_value(response, 'time')
790
- complete = self.safe_value(response, 'complete')
791
- status = 'closed' if complete else 'open'
792
- filled = None
793
- if (placedAmount is not None) and (remaining is not None):
794
- filled = Precise.string_max(Precise.string_sub(placedAmount, remaining), '0')
795
- return self.safe_order({
796
- 'id': self.safe_string(response, 'id'),
797
- 'info': response,
798
- 'clientOrderId': None,
799
- 'timestamp': timestamp,
800
- 'datetime': self.iso8601(timestamp),
801
- 'lastTradeTimestamp': None,
802
- 'type': type,
803
- 'side': self.safe_string(response, 'type'),
804
- 'symbol': market['symbol'],
805
- 'status': status,
806
- 'price': self.safe_string(response, 'price'),
807
- 'amount': placedAmount,
808
- 'cost': None,
809
- 'average': None,
810
- 'remaining': remaining,
811
- 'filled': filled,
812
- 'fee': None,
813
- 'trades': None,
814
- })
1248
+ # {
1249
+ # "ok": "ok",
1250
+ # "data": {
1251
+ # "messageType": "executionReport",
1252
+ # "clientId": "up132245425",
1253
+ # "orderId": "1318485",
1254
+ # "clientOrderId": "b5b6cd40-154c-4c1c-bd51-4a442f3d50b9",
1255
+ # "accountId": "sub1",
1256
+ # "status": "FILLED",
1257
+ # "currency1": "LTC",
1258
+ # "currency2": "USDT",
1259
+ # "side": "BUY",
1260
+ # "executedAmountCcy1": "0.23000000",
1261
+ # "executedAmountCcy2": "15.09030000",
1262
+ # "requestedAmountCcy1": "0.23000000",
1263
+ # "requestedAmountCcy2": null,
1264
+ # "orderType": "Market",
1265
+ # "timeInForce": null,
1266
+ # "comment": null,
1267
+ # "executionType": "Trade",
1268
+ # "executionId": "1726747124624_101_41116",
1269
+ # "transactTime": "2024-10-15T15:08:12.794Z",
1270
+ # "expireTime": null,
1271
+ # "effectiveTime": null,
1272
+ # "averagePrice": "65.61",
1273
+ # "lastQuantity": "0.23000000",
1274
+ # "lastAmountCcy1": "0.23000000",
1275
+ # "lastAmountCcy2": "15.09030000",
1276
+ # "lastPrice": "65.61",
1277
+ # "feeAmount": "0.03772575",
1278
+ # "feeCurrency": "USDT",
1279
+ # "clientCreateTimestamp": "1729004892014",
1280
+ # "serverCreateTimestamp": "1729004891628",
1281
+ # "lastUpdateTimestamp": "1729004892786"
1282
+ # }
1283
+ # }
1284
+ #
1285
+ # on failure, there are extra fields
1286
+ #
1287
+ # "status": "REJECTED",
1288
+ # "requestedAmountCcy1": null,
1289
+ # "orderRejectReason": "{\\" code \\ ":405,\\" reason \\ ":\\" Either AmountCcy1(OrderQty)or AmountCcy2(CashOrderQty)should be specified for market order not both \\ "}",
1290
+ # "rejectCode": 405,
1291
+ # "rejectReason": "Either AmountCcy1(OrderQty) or AmountCcy2(CashOrderQty) should be specified for market order not both",
1292
+ #
1293
+ data = self.safe_dict(response, 'data')
1294
+ return self.parse_order(data, market)
815
1295
 
816
1296
  async def cancel_order(self, id: str, symbol: Str = None, params={}):
817
1297
  """
818
- :see: https://docs.cex.io/#cancel-order
819
1298
  cancels an open order
1299
+
1300
+ https://trade.cex.io/docs/#rest-private-api-calls-cancel-order
1301
+
820
1302
  :param str id: order id
821
- :param str symbol: not used by cex cancelOrder()
1303
+ :param str symbol: unified symbol of the market the order was made in
822
1304
  :param dict [params]: extra parameters specific to the exchange API endpoint
823
1305
  :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
824
1306
  """
825
1307
  await self.load_markets()
826
1308
  request: dict = {
827
- 'id': id,
1309
+ 'orderId': int(id),
1310
+ 'cancelRequestId': 'c_' + str((self.milliseconds())),
1311
+ 'timestamp': self.milliseconds(),
828
1312
  }
829
- response = await self.privatePostCancelOrder(self.extend(request, params))
830
- # 'true'
831
- return self.extend(self.parse_order({}), {'info': response, 'type': None, 'id': id, 'status': 'canceled'})
1313
+ response = await self.privatePostDoCancelMyOrder(self.extend(request, params))
1314
+ #
1315
+ # {"ok":"ok","data":{}}
1316
+ #
1317
+ data = self.safe_dict(response, 'data', {})
1318
+ return self.parse_order(data)
832
1319
 
833
1320
  async def cancel_all_orders(self, symbol: Str = None, params={}):
834
1321
  """
835
- :see: https://docs.cex.io/#cancel-all-orders-for-given-pair
836
1322
  cancel all open orders in a market
837
- :param str symbol: unified market symbol of the market to cancel orders in
838
- :param dict [params]: extra parameters specific to the cex api endpoint
839
- :param str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
1323
+
1324
+ https://trade.cex.io/docs/#rest-private-api-calls-cancel-all-orders
1325
+
1326
+ :param str symbol: alpaca cancelAllOrders cannot setting symbol, it will cancel all open orders
1327
+ :param dict [params]: extra parameters specific to the exchange API endpoint
840
1328
  :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
841
1329
  """
842
- if symbol is None:
843
- raise ArgumentsRequired(self.id + ' cancelAllOrders requires a symbol.')
844
1330
  await self.load_markets()
845
- market = self.market(symbol)
846
- request: dict = {
847
- 'pair': market['id'],
848
- }
849
- orders = await self.privatePostCancelOrdersPair(self.extend(request, params))
1331
+ response = await self.privatePostDoCancelAllOrders(params)
850
1332
  #
851
- # {
852
- # "e":"cancel_orders",
853
- # "ok":"ok",
854
- # "data":[
855
- # ]
856
- # }
1333
+ # {
1334
+ # "ok": "ok",
1335
+ # "data": {
1336
+ # "clientOrderIds": [
1337
+ # "3AF77B67109F"
1338
+ # ]
1339
+ # }
1340
+ # }
857
1341
  #
858
- return orders
859
-
860
- def parse_order(self, order: dict, market: Market = None) -> Order:
861
- # Depending on the call, 'time' can be a unix int, unix string or ISO string
862
- # Yes, really
863
- timestamp = self.safe_value(order, 'time')
864
- if isinstance(timestamp, str) and timestamp.find('T') >= 0:
865
- # ISO8601 string
866
- timestamp = self.parse8601(timestamp)
867
- elif timestamp is not None:
868
- # either integer or string integer
869
- timestamp = int(timestamp)
870
- symbol = None
871
- baseId = self.safe_string(order, 'symbol1')
872
- quoteId = self.safe_string(order, 'symbol2')
873
- if market is None and baseId is not None and quoteId is not None:
874
- base = self.safe_currency_code(baseId)
875
- quote = self.safe_currency_code(quoteId)
876
- if (base is not None) and (quote is not None):
877
- symbol = base + '/' + quote
878
- if symbol in self.markets:
879
- market = self.market(symbol)
880
- status = self.parse_order_status(self.safe_string(order, 'status'))
881
- price = self.safe_string(order, 'price')
882
- amount = self.omit_zero(self.safe_string(order, 'amount'))
883
- # sell orders can have a negative amount
884
- # https://github.com/ccxt/ccxt/issues/5338
885
- if amount is not None:
886
- amount = Precise.string_abs(amount)
887
- elif market is not None:
888
- amountKey = 'a:' + market['base'] + 'cds:'
889
- amount = Precise.string_abs(self.safe_string(order, amountKey))
890
- remaining = self.safe_string_2(order, 'pending', 'remains')
891
- filled = Precise.string_sub(amount, remaining)
892
- fee = None
893
- cost = None
894
- if market is not None:
895
- symbol = market['symbol']
896
- taCost = self.safe_string(order, 'ta:' + market['quote'])
897
- ttaCost = self.safe_string(order, 'tta:' + market['quote'])
898
- cost = Precise.string_add(taCost, ttaCost)
899
- baseFee = 'fa:' + market['base']
900
- baseTakerFee = 'tfa:' + market['base']
901
- quoteFee = 'fa:' + market['quote']
902
- quoteTakerFee = 'tfa:' + market['quote']
903
- feeRate = self.safe_string(order, 'tradingFeeMaker')
904
- if not feeRate:
905
- feeRate = self.safe_string(order, 'tradingFeeTaker', feeRate)
906
- if feeRate:
907
- feeRate = Precise.string_div(feeRate, '100') # convert to mathematically-correct percentage coefficients: 1.0 = 100%
908
- if (baseFee in order) or (baseTakerFee in order):
909
- baseFeeCost = self.safe_number_2(order, baseFee, baseTakerFee)
910
- fee = {
911
- 'currency': market['base'],
912
- 'rate': self.parse_number(feeRate),
913
- 'cost': baseFeeCost,
914
- }
915
- elif (quoteFee in order) or (quoteTakerFee in order):
916
- quoteFeeCost = self.safe_number_2(order, quoteFee, quoteTakerFee)
917
- fee = {
918
- 'currency': market['quote'],
919
- 'rate': self.parse_number(feeRate),
920
- 'cost': quoteFeeCost,
921
- }
922
- if not cost:
923
- cost = Precise.string_mul(price, filled)
924
- side = self.safe_string(order, 'type')
925
- trades = None
926
- orderId = self.safe_string(order, 'id')
927
- if 'vtx' in order:
928
- trades = []
929
- for i in range(0, len(order['vtx'])):
930
- item = order['vtx'][i]
931
- tradeSide = self.safe_string(item, 'type')
932
- if tradeSide == 'cancel':
933
- # looks like self might represent the cancelled part of an order
934
- # {"id": "4426729543",
935
- # "type": "cancel",
936
- # "time": "2017-09-22T00:24:30.476Z",
937
- # "user": "up106404164",
938
- # "c": "user:up106404164:a:BCH",
939
- # "d": "order:4426728375:a:BCH",
940
- # "a": "0.09935956",
941
- # "amount": "0.09935956",
942
- # "balance": "0.42580261",
943
- # "symbol": "BCH",
944
- # "order": "4426728375",
945
- # "buy": null,
946
- # "sell": null,
947
- # "pair": null,
948
- # "pos": null,
949
- # "cs": "0.42580261",
950
- # "ds": 0}
951
- continue
952
- tradePrice = self.safe_string(item, 'price')
953
- if tradePrice is None:
954
- # self represents the order
955
- # {
956
- # "a": "0.47000000",
957
- # "c": "user:up106404164:a:EUR",
958
- # "d": "order:6065499239:a:EUR",
959
- # "cs": "1432.93",
960
- # "ds": "476.72",
961
- # "id": "6065499249",
962
- # "buy": null,
963
- # "pos": null,
964
- # "pair": null,
965
- # "sell": null,
966
- # "time": "2018-04-22T13:07:22.152Z",
967
- # "type": "buy",
968
- # "user": "up106404164",
969
- # "order": "6065499239",
970
- # "amount": "-715.97000000",
971
- # "symbol": "EUR",
972
- # "balance": "1432.93000000"}
973
- continue
974
- # todo: deal with these
975
- if tradeSide == 'costsNothing':
976
- continue
977
- # --
978
- # if side != tradeSide:
979
- # raise Error(json.dumps(order, null, 2))
980
- # if orderId != item['order']:
981
- # raise Error(json.dumps(order, null, 2))
982
- # --
983
- # partial buy trade
984
- # {
985
- # "a": "0.01589885",
986
- # "c": "user:up106404164:a:BTC",
987
- # "d": "order:6065499239:a:BTC",
988
- # "cs": "0.36300000",
989
- # "ds": 0,
990
- # "id": "6067991213",
991
- # "buy": "6065499239",
992
- # "pos": null,
993
- # "pair": null,
994
- # "sell": "6067991206",
995
- # "time": "2018-04-22T23:09:11.773Z",
996
- # "type": "buy",
997
- # "user": "up106404164",
998
- # "order": "6065499239",
999
- # "price": 7146.5,
1000
- # "amount": "0.01589885",
1001
- # "symbol": "BTC",
1002
- # "balance": "0.36300000",
1003
- # "symbol2": "EUR",
1004
- # "fee_amount": "0.19"}
1005
- # --
1006
- # trade with zero amount, but non-zero fee
1007
- # {
1008
- # "a": "0.00000000",
1009
- # "c": "user:up106404164:a:EUR",
1010
- # "d": "order:5840654423:a:EUR",
1011
- # "cs": 559744,
1012
- # "ds": 0,
1013
- # "id": "5840654429",
1014
- # "buy": "5807238573",
1015
- # "pos": null,
1016
- # "pair": null,
1017
- # "sell": "5840654423",
1018
- # "time": "2018-03-15T03:20:14.010Z",
1019
- # "type": "sell",
1020
- # "user": "up106404164",
1021
- # "order": "5840654423",
1022
- # "price": 730,
1023
- # "amount": "0.00000000",
1024
- # "symbol": "EUR",
1025
- # "balance": "5597.44000000",
1026
- # "symbol2": "BCH",
1027
- # "fee_amount": "0.01"}
1028
- # --
1029
- # trade which should have an amount of exactly 0.002BTC
1030
- # {
1031
- # "a": "16.70000000",
1032
- # "c": "user:up106404164:a:GBP",
1033
- # "d": "order:9927386681:a:GBP",
1034
- # "cs": "86.90",
1035
- # "ds": 0,
1036
- # "id": "9927401610",
1037
- # "buy": "9927401601",
1038
- # "pos": null,
1039
- # "pair": null,
1040
- # "sell": "9927386681",
1041
- # "time": "2019-08-21T15:25:37.777Z",
1042
- # "type": "sell",
1043
- # "user": "up106404164",
1044
- # "order": "9927386681",
1045
- # "price": 8365,
1046
- # "amount": "16.70000000",
1047
- # "office": "UK",
1048
- # "symbol": "GBP",
1049
- # "balance": "86.90000000",
1050
- # "symbol2": "BTC",
1051
- # "fee_amount": "0.03"
1052
- # }
1053
- tradeTimestamp = self.parse8601(self.safe_string(item, 'time'))
1054
- tradeAmount = self.safe_string(item, 'amount')
1055
- feeCost = self.safe_string(item, 'fee_amount')
1056
- absTradeAmount = Precise.string_abs(tradeAmount)
1057
- tradeCost = None
1058
- if tradeSide == 'sell':
1059
- tradeCost = absTradeAmount
1060
- absTradeAmount = Precise.string_div(Precise.string_add(feeCost, tradeCost), tradePrice)
1061
- else:
1062
- tradeCost = Precise.string_mul(absTradeAmount, tradePrice)
1063
- trades.append({
1064
- 'id': self.safe_string(item, 'id'),
1065
- 'timestamp': tradeTimestamp,
1066
- 'datetime': self.iso8601(tradeTimestamp),
1067
- 'order': orderId,
1068
- 'symbol': symbol,
1069
- 'price': self.parse_number(tradePrice),
1070
- 'amount': self.parse_number(absTradeAmount),
1071
- 'cost': self.parse_number(tradeCost),
1072
- 'side': tradeSide,
1073
- 'fee': {
1074
- 'cost': self.parse_number(feeCost),
1075
- 'currency': market['quote'],
1076
- },
1077
- 'info': item,
1078
- 'type': None,
1079
- 'takerOrMaker': None,
1080
- })
1081
- return self.safe_order({
1082
- 'info': order,
1083
- 'id': orderId,
1084
- 'clientOrderId': None,
1085
- 'datetime': self.iso8601(timestamp),
1086
- 'timestamp': timestamp,
1087
- 'lastTradeTimestamp': None,
1088
- 'status': status,
1089
- 'symbol': symbol,
1090
- 'type': 'market' if (price is None) else 'limit',
1091
- 'timeInForce': None,
1092
- 'postOnly': None,
1093
- 'side': side,
1094
- 'price': price,
1095
- 'stopPrice': None,
1096
- 'triggerPrice': None,
1097
- 'cost': cost,
1098
- 'amount': amount,
1099
- 'filled': filled,
1100
- 'remaining': remaining,
1101
- 'trades': trades,
1102
- 'fee': fee,
1103
- 'average': None,
1104
- })
1342
+ data = self.safe_dict(response, 'data', {})
1343
+ ids = self.safe_list(data, 'clientOrderIds', [])
1344
+ orders = []
1345
+ for i in range(0, len(ids)):
1346
+ id = ids[i]
1347
+ orders.append({'clientOrderId': id})
1348
+ return self.parse_orders(orders)
1105
1349
 
1106
- async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
1350
+ async def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
1107
1351
  """
1108
- :see: https://docs.cex.io/#open-orders
1109
- fetch all unfilled currently open orders
1110
- :param str symbol: unified market symbol
1111
- :param int [since]: the earliest time in ms to fetch open orders for
1112
- :param int [limit]: the maximum number of open orders structures to retrieve
1352
+ fetch the history of changes, actions done by the user or operations that altered the balance of the user
1353
+
1354
+ https://trade.cex.io/docs/#rest-private-api-calls-transaction-history
1355
+
1356
+ :param str [code]: unified currency code
1357
+ :param int [since]: timestamp in ms of the earliest ledger entry
1358
+ :param int [limit]: max number of ledger entries to return
1113
1359
  :param dict [params]: extra parameters specific to the exchange API endpoint
1114
- :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1360
+ :param int [params.until]: timestamp in ms of the latest ledger entry
1361
+ :returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
1115
1362
  """
1116
1363
  await self.load_markets()
1364
+ currency = None
1117
1365
  request: dict = {}
1118
- market = None
1119
- orders = None
1120
- if symbol is not None:
1121
- market = self.market(symbol)
1122
- request['pair'] = market['id']
1123
- orders = await self.privatePostOpenOrdersPair(self.extend(request, params))
1366
+ if code is not None:
1367
+ currency = self.currency(code)
1368
+ request['currency'] = currency['id']
1369
+ if since is not None:
1370
+ request['dateFrom'] = since
1371
+ if limit is not None:
1372
+ request['pageSize'] = limit
1373
+ until = None
1374
+ until, params = self.handle_param_integer_2(params, 'until', 'till')
1375
+ if until is not None:
1376
+ request['dateTo'] = until
1377
+ response = await self.privatePostGetMyTransactionHistory(self.extend(request, params))
1378
+ #
1379
+ # {
1380
+ # "ok": "ok",
1381
+ # "data": [
1382
+ # {
1383
+ # "transactionId": "30367722",
1384
+ # "timestamp": "2024-10-14T14:08:49.987Z",
1385
+ # "accountId": "",
1386
+ # "type": "withdraw",
1387
+ # "amount": "-12.39060600",
1388
+ # "details": "Withdraw fundingId=1235039 clientId=up421412345 walletTxId=76337154166",
1389
+ # "currency": "USDT"
1390
+ # },
1391
+ # ...
1392
+ #
1393
+ data = self.safe_list(response, 'data', [])
1394
+ return self.parse_ledger(data, currency, since, limit)
1395
+
1396
+ def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
1397
+ amount = self.safe_string(item, 'amount')
1398
+ direction = None
1399
+ if Precise.string_le(amount, '0'):
1400
+ direction = 'out'
1401
+ amount = Precise.string_mul('-1', amount)
1124
1402
  else:
1125
- orders = await self.privatePostOpenOrders(self.extend(request, params))
1126
- for i in range(0, len(orders)):
1127
- orders[i] = self.extend(orders[i], {'status': 'open'})
1128
- return self.parse_orders(orders, market, since, limit)
1403
+ direction = 'in'
1404
+ currencyId = self.safe_string(item, 'currency')
1405
+ currency = self.safe_currency(currencyId, currency)
1406
+ code = self.safe_currency_code(currencyId, currency)
1407
+ timestampString = self.safe_string(item, 'timestamp')
1408
+ timestamp = self.parse8601(timestampString)
1409
+ type = self.safe_string(item, 'type')
1410
+ return self.safe_ledger_entry({
1411
+ 'info': item,
1412
+ 'id': self.safe_string(item, 'transactionId'),
1413
+ 'direction': direction,
1414
+ 'account': self.safe_string(item, 'accountId', ''),
1415
+ 'referenceAccount': None,
1416
+ 'referenceId': None,
1417
+ 'type': self.parse_ledger_entry_type(type),
1418
+ 'currency': code,
1419
+ 'amount': self.parse_number(amount),
1420
+ 'timestamp': timestamp,
1421
+ 'datetime': self.iso8601(timestamp),
1422
+ 'before': None,
1423
+ 'after': None,
1424
+ 'status': None,
1425
+ 'fee': None,
1426
+ }, currency)
1129
1427
 
1130
- async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
1428
+ def parse_ledger_entry_type(self, type):
1429
+ ledgerType: dict = {
1430
+ 'deposit': 'deposit',
1431
+ 'withdraw': 'withdrawal',
1432
+ 'commission': 'fee',
1433
+ }
1434
+ return self.safe_string(ledgerType, type, type)
1435
+
1436
+ async def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
1131
1437
  """
1132
- :see: https://docs.cex.io/#archived-orders
1133
- fetches information on multiple closed orders made by the user
1134
- :param str symbol: unified market symbol of the market orders were made in
1135
- :param int [since]: the earliest time in ms to fetch orders for
1136
- :param int [limit]: the maximum number of order structures to retrieve
1438
+ fetch history of deposits and withdrawals
1439
+
1440
+ https://trade.cex.io/docs/#rest-private-api-calls-funding-history
1441
+
1442
+ :param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
1443
+ :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
1444
+ :param int [limit]: max number of deposit/withdrawals to return, default is None
1137
1445
  :param dict [params]: extra parameters specific to the exchange API endpoint
1138
- :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1446
+ :returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
1139
1447
  """
1140
- if symbol is None:
1141
- raise ArgumentsRequired(self.id + ' fetchClosedOrders() requires a symbol argument')
1142
1448
  await self.load_markets()
1143
- market = self.market(symbol)
1144
- request: dict = {'pair': market['id']}
1145
- response = await self.privatePostArchivedOrdersPair(self.extend(request, params))
1146
- return self.parse_orders(response, market, since, limit)
1449
+ request: dict = {}
1450
+ currency = None
1451
+ if code is not None:
1452
+ currency = self.currency(code)
1453
+ if since is not None:
1454
+ request['dateFrom'] = since
1455
+ if limit is not None:
1456
+ request['pageSize'] = limit
1457
+ until = None
1458
+ until, params = self.handle_param_integer_2(params, 'until', 'till')
1459
+ if until is not None:
1460
+ request['dateTo'] = until
1461
+ response = await self.privatePostGetMyFundingHistory(self.extend(request, params))
1462
+ #
1463
+ # {
1464
+ # "ok": "ok",
1465
+ # "data": [
1466
+ # {
1467
+ # "clientId": "up421412345",
1468
+ # "accountId": "",
1469
+ # "currency": "USDT",
1470
+ # "direction": "withdraw",
1471
+ # "amount": "12.39060600",
1472
+ # "commissionAmount": "0.00000000",
1473
+ # "status": "approved",
1474
+ # "updatedAt": "2024-10-14T14:08:50.013Z",
1475
+ # "txId": "30367718",
1476
+ # "details": {}
1477
+ # },
1478
+ # ...
1479
+ #
1480
+ data = self.safe_list(response, 'data', [])
1481
+ return self.parse_transactions(data, currency, since, limit)
1482
+
1483
+ def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
1484
+ currencyId = self.safe_string(transaction, 'currency')
1485
+ direction = self.safe_string(transaction, 'direction')
1486
+ type = 'withdrawal' if (direction == 'withdraw') else 'deposit'
1487
+ code = self.safe_currency_code(currencyId, currency)
1488
+ updatedAt = self.safe_string(transaction, 'updatedAt')
1489
+ timestamp = self.parse8601(updatedAt)
1490
+ return {
1491
+ 'info': transaction,
1492
+ 'id': self.safe_string(transaction, 'txId'),
1493
+ 'txid': None,
1494
+ 'type': type,
1495
+ 'currency': code,
1496
+ 'network': None,
1497
+ 'amount': self.safe_number(transaction, 'amount'),
1498
+ 'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
1499
+ 'timestamp': timestamp,
1500
+ 'datetime': self.iso8601(timestamp),
1501
+ 'address': None,
1502
+ 'addressFrom': None,
1503
+ 'addressTo': None,
1504
+ 'tag': None,
1505
+ 'tagFrom': None,
1506
+ 'tagTo': None,
1507
+ 'updated': None,
1508
+ 'comment': None,
1509
+ 'fee': {
1510
+ 'currency': code,
1511
+ 'cost': self.safe_number(transaction, 'commissionAmount'),
1512
+ },
1513
+ 'internal': None,
1514
+ }
1515
+
1516
+ def parse_transaction_status(self, status: Str):
1517
+ statuses: dict = {
1518
+ 'rejected': 'rejected',
1519
+ 'pending': 'pending',
1520
+ 'approved': 'ok',
1521
+ }
1522
+ return self.safe_string(statuses, status, status)
1147
1523
 
1148
- async def fetch_order(self, id: str, symbol: Str = None, params={}):
1524
+ async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
1149
1525
  """
1150
- :see: https://docs.cex.io/?python#get-order-details
1151
- fetches information on an order made by the user
1152
- :param str symbol: not used by cex fetchOrder
1526
+ transfer currency internally between wallets on the same account
1527
+
1528
+ https://trade.cex.io/docs/#rest-private-api-calls-internal-transfer
1529
+
1530
+ :param str code: unified currency code
1531
+ :param float amount: amount to transfer
1532
+ :param str fromAccount: 'SPOT', 'FUND', or 'CONTRACT'
1533
+ :param str toAccount: 'SPOT', 'FUND', or 'CONTRACT'
1153
1534
  :param dict [params]: extra parameters specific to the exchange API endpoint
1154
- :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1535
+ :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
1155
1536
  """
1537
+ transfer = None
1538
+ if toAccount != '' and fromAccount != '':
1539
+ transfer = await self.transfer_between_sub_accounts(code, amount, fromAccount, toAccount, params)
1540
+ else:
1541
+ transfer = await self.transfer_between_main_and_sub_account(code, amount, fromAccount, toAccount, params)
1542
+ fillResponseFromRequest = self.handle_option('transfer', 'fillResponseFromRequest', True)
1543
+ if fillResponseFromRequest:
1544
+ transfer['fromAccount'] = fromAccount
1545
+ transfer['toAccount'] = toAccount
1546
+ return transfer
1547
+
1548
+ async def transfer_between_main_and_sub_account(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
1156
1549
  await self.load_markets()
1550
+ currency = self.currency(code)
1551
+ fromMain = (fromAccount == '')
1552
+ targetAccount = toAccount if fromMain else fromAccount
1553
+ guid = self.safe_string(params, 'guid', self.uuid())
1157
1554
  request: dict = {
1158
- 'id': str(id),
1555
+ 'currency': currency['id'],
1556
+ 'amount': self.currency_to_precision(code, amount),
1557
+ 'accountId': targetAccount,
1558
+ 'clientTxId': guid,
1159
1559
  }
1160
- response = await self.privatePostGetOrderTx(self.extend(request, params))
1161
- data = self.safe_value(response, 'data', {})
1560
+ response = None
1561
+ if fromMain:
1562
+ response = await self.privatePostDoDepositFundsFromWallet(self.extend(request, params))
1563
+ else:
1564
+ response = await self.privatePostDoWithdrawalFundsToWallet(self.extend(request, params))
1565
+ # both endpoints return the same structure, the only difference is that
1566
+ # the "accountId" is filled with the "subAccount"
1162
1567
  #
1163
1568
  # {
1164
- # "id": "5442731603",
1165
- # "type": "sell",
1166
- # "time": 1516132358071,
1167
- # "lastTxTime": 1516132378452,
1168
- # "lastTx": "5442734452",
1169
- # "pos": null,
1170
- # "user": "up106404164",
1171
- # "status": "d",
1172
- # "symbol1": "ETH",
1173
- # "symbol2": "EUR",
1174
- # "amount": "0.50000000",
1175
- # "kind": "api",
1176
- # "price": "923.3386",
1177
- # "tfacf": "1",
1178
- # "fa:EUR": "0.55",
1179
- # "ta:EUR": "369.77",
1180
- # "remains": "0.00000000",
1181
- # "tfa:EUR": "0.22",
1182
- # "tta:EUR": "91.95",
1183
- # "a:ETH:cds": "0.50000000",
1184
- # "a:EUR:cds": "461.72",
1185
- # "f:EUR:cds": "0.77",
1186
- # "tradingFeeMaker": "0.15",
1187
- # "tradingFeeTaker": "0.23",
1188
- # "tradingFeeStrategy": "userVolumeAmount",
1189
- # "tradingFeeUserVolumeAmount": "2896912572",
1190
- # "orderId": "5442731603",
1191
- # "next": False,
1192
- # "vtx": [
1193
- # {
1194
- # "id": "5442734452",
1195
- # "type": "sell",
1196
- # "time": "2018-01-16T19:52:58.452Z",
1197
- # "user": "up106404164",
1198
- # "c": "user:up106404164:a:EUR",
1199
- # "d": "order:5442731603:a:EUR",
1200
- # "a": "104.53000000",
1201
- # "amount": "104.53000000",
1202
- # "balance": "932.71000000",
1203
- # "symbol": "EUR",
1204
- # "order": "5442731603",
1205
- # "buy": "5442734443",
1206
- # "sell": "5442731603",
1207
- # "pair": null,
1208
- # "pos": null,
1209
- # "office": null,
1210
- # "cs": "932.71",
1211
- # "ds": 0,
1212
- # "price": 923.3386,
1213
- # "symbol2": "ETH",
1214
- # "fee_amount": "0.16"
1215
- # },
1216
- # {
1217
- # "id": "5442731609",
1218
- # "type": "sell",
1219
- # "time": "2018-01-16T19:52:38.071Z",
1220
- # "user": "up106404164",
1221
- # "c": "user:up106404164:a:EUR",
1222
- # "d": "order:5442731603:a:EUR",
1223
- # "a": "91.73000000",
1224
- # "amount": "91.73000000",
1225
- # "balance": "563.49000000",
1226
- # "symbol": "EUR",
1227
- # "order": "5442731603",
1228
- # "buy": "5442618127",
1229
- # "sell": "5442731603",
1230
- # "pair": null,
1231
- # "pos": null,
1232
- # "office": null,
1233
- # "cs": "563.49",
1234
- # "ds": 0,
1235
- # "price": 924.0092,
1236
- # "symbol2": "ETH",
1237
- # "fee_amount": "0.22"
1238
- # },
1239
- # {
1240
- # "id": "5442731604",
1241
- # "type": "sell",
1242
- # "time": "2018-01-16T19:52:38.071Z",
1243
- # "user": "up106404164",
1244
- # "c": "order:5442731603:a:ETH",
1245
- # "d": "user:up106404164:a:ETH",
1246
- # "a": "0.50000000",
1247
- # "amount": "-0.50000000",
1248
- # "balance": "15.80995000",
1249
- # "symbol": "ETH",
1250
- # "order": "5442731603",
1251
- # "buy": null,
1252
- # "sell": null,
1253
- # "pair": null,
1254
- # "pos": null,
1255
- # "office": null,
1256
- # "cs": "0.50000000",
1257
- # "ds": "15.80995000"
1258
- # }
1259
- # ]
1569
+ # "ok": "ok",
1570
+ # "data": {
1571
+ # "accountId": "sub1",
1572
+ # "clientTxId": "27ba8284-67cf-4386-9ec7-80b3871abd45",
1573
+ # "currency": "USDT",
1574
+ # "status": "approved"
1575
+ # }
1260
1576
  # }
1261
1577
  #
1262
- return self.parse_order(data)
1578
+ data = self.safe_dict(response, 'data', {})
1579
+ return self.parse_transfer(data, currency)
1263
1580
 
1264
- async def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
1265
- """
1266
- :see: https://docs.cex.io/#archived-orders
1267
- fetches information on multiple orders made by the user
1268
- :param str symbol: unified market symbol of the market orders were made in
1269
- :param int [since]: the earliest time in ms to fetch orders for
1270
- :param int [limit]: the maximum number of order structures to retrieve
1271
- :param dict [params]: extra parameters specific to the exchange API endpoint
1272
- :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
1273
- """
1581
+ async def transfer_between_sub_accounts(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
1274
1582
  await self.load_markets()
1275
- market = self.market(symbol)
1583
+ currency = self.currency(code)
1276
1584
  request: dict = {
1277
- 'limit': limit,
1278
- 'pair': market['id'],
1279
- 'dateFrom': since,
1585
+ 'currency': currency['id'],
1586
+ 'amount': self.currency_to_precision(code, amount),
1587
+ 'fromAccountId': fromAccount,
1588
+ 'toAccountId': toAccount,
1280
1589
  }
1281
- response = await self.privatePostArchivedOrdersPair(self.extend(request, params))
1282
- results = []
1283
- for i in range(0, len(response)):
1284
- # cancelled(unfilled):
1285
- # {"id": "4005785516",
1286
- # "type": "sell",
1287
- # "time": "2017-07-18T19:08:34.223Z",
1288
- # "lastTxTime": "2017-07-18T19:08:34.396Z",
1289
- # "lastTx": "4005785522",
1290
- # "pos": null,
1291
- # "status": "c",
1292
- # "symbol1": "ETH",
1293
- # "symbol2": "GBP",
1294
- # "amount": "0.20000000",
1295
- # "price": "200.5625",
1296
- # "remains": "0.20000000",
1297
- # 'a:ETH:cds': "0.20000000",
1298
- # "tradingFeeMaker": "0",
1299
- # "tradingFeeTaker": "0.16",
1300
- # "tradingFeeUserVolumeAmount": "10155061217",
1301
- # "orderId": "4005785516"}
1302
- # --
1303
- # cancelled(partially filled buy):
1304
- # {"id": "4084911657",
1305
- # "type": "buy",
1306
- # "time": "2017-08-05T03:18:39.596Z",
1307
- # "lastTxTime": "2019-03-19T17:37:46.404Z",
1308
- # "lastTx": "8459265833",
1309
- # "pos": null,
1310
- # "status": "cd",
1311
- # "symbol1": "BTC",
1312
- # "symbol2": "GBP",
1313
- # "amount": "0.05000000",
1314
- # "price": "2241.4692",
1315
- # "tfacf": "1",
1316
- # "remains": "0.03910535",
1317
- # 'tfa:GBP': "0.04",
1318
- # 'tta:GBP': "24.39",
1319
- # 'a:BTC:cds': "0.01089465",
1320
- # 'a:GBP:cds': "112.26",
1321
- # 'f:GBP:cds': "0.04",
1322
- # "tradingFeeMaker": "0",
1323
- # "tradingFeeTaker": "0.16",
1324
- # "tradingFeeUserVolumeAmount": "13336396963",
1325
- # "orderId": "4084911657"}
1326
- # --
1327
- # cancelled(partially filled sell):
1328
- # {"id": "4426728375",
1329
- # "type": "sell",
1330
- # "time": "2017-09-22T00:24:20.126Z",
1331
- # "lastTxTime": "2017-09-22T00:24:30.476Z",
1332
- # "lastTx": "4426729543",
1333
- # "pos": null,
1334
- # "status": "cd",
1335
- # "symbol1": "BCH",
1336
- # "symbol2": "BTC",
1337
- # "amount": "0.10000000",
1338
- # "price": "0.11757182",
1339
- # "tfacf": "1",
1340
- # "remains": "0.09935956",
1341
- # 'tfa:BTC': "0.00000014",
1342
- # 'tta:BTC': "0.00007537",
1343
- # 'a:BCH:cds': "0.10000000",
1344
- # 'a:BTC:cds': "0.00007537",
1345
- # 'f:BTC:cds': "0.00000014",
1346
- # "tradingFeeMaker": "0",
1347
- # "tradingFeeTaker": "0.18",
1348
- # "tradingFeeUserVolumeAmount": "3466715450",
1349
- # "orderId": "4426728375"}
1350
- # --
1351
- # filled:
1352
- # {"id": "5342275378",
1353
- # "type": "sell",
1354
- # "time": "2018-01-04T00:28:12.992Z",
1355
- # "lastTxTime": "2018-01-04T00:28:12.992Z",
1356
- # "lastTx": "5342275393",
1357
- # "pos": null,
1358
- # "status": "d",
1359
- # "symbol1": "BCH",
1360
- # "symbol2": "BTC",
1361
- # "amount": "0.10000000",
1362
- # "kind": "api",
1363
- # "price": "0.17",
1364
- # "remains": "0.00000000",
1365
- # 'tfa:BTC': "0.00003902",
1366
- # 'tta:BTC': "0.01699999",
1367
- # 'a:BCH:cds': "0.10000000",
1368
- # 'a:BTC:cds': "0.01699999",
1369
- # 'f:BTC:cds': "0.00003902",
1370
- # "tradingFeeMaker": "0.15",
1371
- # "tradingFeeTaker": "0.23",
1372
- # "tradingFeeUserVolumeAmount": "1525951128",
1373
- # "orderId": "5342275378"}
1374
- # --
1375
- # market order(buy):
1376
- # {"id": "6281946200",
1377
- # "pos": null,
1378
- # "time": "2018-05-23T11:55:43.467Z",
1379
- # "type": "buy",
1380
- # "amount": "0.00000000",
1381
- # "lastTx": "6281946210",
1382
- # "status": "d",
1383
- # "amount2": "20.00",
1384
- # "orderId": "6281946200",
1385
- # "remains": "0.00000000",
1386
- # "symbol1": "ETH",
1387
- # "symbol2": "EUR",
1388
- # "tfa:EUR": "0.05",
1389
- # "tta:EUR": "19.94",
1390
- # "a:ETH:cds": "0.03764100",
1391
- # "a:EUR:cds": "20.00",
1392
- # "f:EUR:cds": "0.05",
1393
- # "lastTxTime": "2018-05-23T11:55:43.467Z",
1394
- # "tradingFeeTaker": "0.25",
1395
- # "tradingFeeUserVolumeAmount": "55998097"}
1396
- # --
1397
- # market order(sell):
1398
- # {"id": "6282200948",
1399
- # "pos": null,
1400
- # "time": "2018-05-23T12:42:58.315Z",
1401
- # "type": "sell",
1402
- # "amount": "-0.05000000",
1403
- # "lastTx": "6282200958",
1404
- # "status": "d",
1405
- # "orderId": "6282200948",
1406
- # "remains": "0.00000000",
1407
- # "symbol1": "ETH",
1408
- # "symbol2": "EUR",
1409
- # "tfa:EUR": "0.07",
1410
- # "tta:EUR": "26.49",
1411
- # "a:ETH:cds": "0.05000000",
1412
- # "a:EUR:cds": "26.49",
1413
- # "f:EUR:cds": "0.07",
1414
- # "lastTxTime": "2018-05-23T12:42:58.315Z",
1415
- # "tradingFeeTaker": "0.25",
1416
- # "tradingFeeUserVolumeAmount": "56294576"}
1417
- order = response[i]
1418
- status = self.parse_order_status(self.safe_string(order, 'status'))
1419
- baseId = self.safe_string(order, 'symbol1')
1420
- quoteId = self.safe_string(order, 'symbol2')
1421
- base = self.safe_currency_code(baseId)
1422
- quote = self.safe_currency_code(quoteId)
1423
- symbolInner = base + '/' + quote
1424
- side = self.safe_string(order, 'type')
1425
- baseAmount = self.safe_number(order, 'a:' + baseId + ':cds')
1426
- quoteAmount = self.safe_number(order, 'a:' + quoteId + ':cds')
1427
- fee = self.safe_number(order, 'f:' + quoteId + ':cds')
1428
- amount = self.safe_string(order, 'amount')
1429
- price = self.safe_string(order, 'price')
1430
- remaining = self.safe_string(order, 'remains')
1431
- filled = Precise.string_sub(amount, remaining)
1432
- orderAmount = None
1433
- cost = None
1434
- average = None
1435
- type = None
1436
- if not price:
1437
- type = 'market'
1438
- orderAmount = baseAmount
1439
- cost = quoteAmount
1440
- average = Precise.string_div(orderAmount, cost)
1441
- else:
1442
- ta = self.safe_string(order, 'ta:' + quoteId, '0')
1443
- tta = self.safe_string(order, 'tta:' + quoteId, '0')
1444
- fa = self.safe_string(order, 'fa:' + quoteId, '0')
1445
- tfa = self.safe_string(order, 'tfa:' + quoteId, '0')
1446
- if side == 'sell':
1447
- cost = Precise.string_add(Precise.string_add(ta, tta), Precise.string_add(fa, tfa))
1448
- else:
1449
- cost = Precise.string_sub(Precise.string_add(ta, tta), Precise.string_add(fa, tfa))
1450
- type = 'limit'
1451
- orderAmount = amount
1452
- average = Precise.string_div(cost, filled)
1453
- time = self.safe_string(order, 'time')
1454
- lastTxTime = self.safe_string(order, 'lastTxTime')
1455
- timestamp = self.parse8601(time)
1456
- safeOrder = self.safe_order({
1457
- 'info': order,
1458
- 'id': self.safe_string(order, 'id'),
1459
- 'timestamp': timestamp,
1460
- 'datetime': self.iso8601(timestamp),
1461
- 'lastUpdated': self.parse8601(lastTxTime),
1462
- 'status': status,
1463
- 'symbol': symbolInner,
1464
- 'side': side,
1465
- 'price': price,
1466
- 'amount': orderAmount,
1467
- 'average': average,
1468
- 'type': type,
1469
- 'filled': filled,
1470
- 'cost': cost,
1471
- 'remaining': remaining,
1472
- 'fee': {
1473
- 'cost': fee,
1474
- 'currency': quote,
1475
- },
1476
- })
1477
- results.append(safeOrder)
1478
- return results
1479
-
1480
- def parse_order_status(self, status: Str):
1481
- return self.safe_string(self.options['order']['status'], status, status)
1590
+ response = await self.privatePostDoMyInternalTransfer(self.extend(request, params))
1591
+ #
1592
+ # {
1593
+ # "ok": "ok",
1594
+ # "data": {
1595
+ # "transactionId": "30225415"
1596
+ # }
1597
+ # }
1598
+ #
1599
+ data = self.safe_dict(response, 'data', {})
1600
+ return self.parse_transfer(data, currency)
1482
1601
 
1483
- async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
1484
- """
1485
- edit a trade order
1486
- :see: https://docs.cex.io/#cancel-replace-order
1487
- :param str id: order id
1488
- :param str symbol: unified symbol of the market to create an order in
1489
- :param str type: 'market' or 'limit'
1490
- :param str side: 'buy' or 'sell'
1491
- :param float amount: how much of the currency you want to trade in units of the base currency
1492
- :param float|None [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
1493
- :param dict [params]: extra parameters specific to the cex api endpoint
1494
- :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
1495
- """
1496
- if amount is None:
1497
- raise ArgumentsRequired(self.id + ' editOrder() requires a amount argument')
1498
- if price is None:
1499
- raise ArgumentsRequired(self.id + ' editOrder() requires a price argument')
1500
- await self.load_markets()
1501
- market = self.market(symbol)
1502
- # see: https://cex.io/rest-api#/definitions/CancelReplaceOrderRequest
1503
- request: dict = {
1504
- 'pair': market['id'],
1505
- 'type': side,
1506
- 'amount': amount,
1507
- 'price': price,
1508
- 'order_id': id,
1602
+ def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
1603
+ #
1604
+ # transferBetweenSubAccounts
1605
+ #
1606
+ # {
1607
+ # "ok": "ok",
1608
+ # "data": {
1609
+ # "transactionId": "30225415"
1610
+ # }
1611
+ # }
1612
+ #
1613
+ # transfer between main/sub
1614
+ #
1615
+ # {
1616
+ # "ok": "ok",
1617
+ # "data": {
1618
+ # "accountId": "sub1",
1619
+ # "clientTxId": "27ba8284-67cf-4386-9ec7-80b3871abd45",
1620
+ # "currency": "USDT",
1621
+ # "status": "approved"
1622
+ # }
1623
+ # }
1624
+ #
1625
+ currencyId = self.safe_string(transfer, 'currency')
1626
+ currencyCode = self.safe_currency_code(currencyId, currency)
1627
+ return {
1628
+ 'info': transfer,
1629
+ 'id': self.safe_string_2(transfer, 'transactionId', 'clientTxId'),
1630
+ 'timestamp': None,
1631
+ 'datetime': None,
1632
+ 'currency': currencyCode,
1633
+ 'amount': None,
1634
+ 'fromAccount': None,
1635
+ 'toAccount': None,
1636
+ 'status': self.parse_transaction_status(self.safe_string(transfer, 'status')),
1509
1637
  }
1510
- response = await self.privatePostCancelReplaceOrderPair(self.extend(request, params))
1511
- return self.parse_order(response, market)
1512
1638
 
1513
- async def fetch_deposit_address(self, code: str, params={}):
1639
+ async def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
1514
1640
  """
1515
- :see: https://docs.cex.io/#get-crypto-address
1516
1641
  fetch the deposit address for a currency associated with self account
1642
+
1643
+ https://trade.cex.io/docs/#rest-private-api-calls-deposit-address
1644
+
1517
1645
  :param str code: unified currency code
1518
1646
  :param dict [params]: extra parameters specific to the exchange API endpoint
1647
+ :param str [params.accountId]: account-id(default to empty string) to refer to(at self moment, only sub-accounts allowed by exchange)
1519
1648
  :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
1520
1649
  """
1650
+ accountId = None
1651
+ accountId, params = self.handle_option_and_params(params, 'createOrder', 'accountId')
1652
+ if accountId is None:
1653
+ raise ArgumentsRequired(self.id + ' fetchDepositAddress() : main account is not allowed to fetch deposit address from api, set params["accountId"] or .options["createOrder"]["accountId"] to the name of your sub-account')
1521
1654
  await self.load_markets()
1655
+ networkCode = None
1656
+ networkCode, params = self.handle_network_code_and_params(params)
1522
1657
  currency = self.currency(code)
1523
1658
  request: dict = {
1524
- 'currency': currency['id'],
1659
+ 'accountId': accountId,
1660
+ 'currency': currency['id'], # documentation is wrong about self param
1661
+ 'blockchain': self.network_code_to_id(networkCode),
1525
1662
  }
1526
- networkCode, query = self.handle_network_code_and_params(params)
1527
- # atm, cex doesn't support network in the request
1528
- response = await self.privatePostGetCryptoAddress(self.extend(request, query))
1663
+ response = await self.privatePostGetDepositAddress(self.extend(request, params))
1529
1664
  #
1530
1665
  # {
1531
- # "e": "get_crypto_address",
1532
- # "ok": "ok",
1533
- # "data": {
1534
- # "name": "BTC",
1535
- # "addresses": [
1536
- # {
1537
- # "blockchain": "Bitcoin",
1538
- # "address": "2BvKwe1UwrdTjq2nzhscFYXwqCjCaaHCeq"
1666
+ # "ok": "ok",
1667
+ # "data": {
1668
+ # "address": "TCr..................1AE",
1669
+ # "accountId": "sub1",
1670
+ # "currency": "USDT",
1671
+ # "blockchain": "tron"
1672
+ # }
1673
+ # }
1539
1674
  #
1540
- # # for others coins(i.e. XRP, XLM) other keys are present:
1541
- # # "destination": "rF1sdh25BJX3qFwneeTBwaq3zPEWYcwjp2",
1542
- # # "destinationTag": "7519113655",
1543
- # # "memo": "XLM-memo12345",
1544
- # }
1545
- # ]
1546
- # }
1547
- # }
1548
- #
1549
- data = self.safe_value(response, 'data', {})
1550
- addresses = self.safe_value(data, 'addresses', [])
1551
- chainsIndexedById = self.index_by(addresses, 'blockchain')
1552
- selectedNetworkId = self.select_network_id_from_raw_networks(code, networkCode, chainsIndexedById)
1553
- addressObject = self.safe_value(chainsIndexedById, selectedNetworkId, {})
1554
- address = self.safe_string_2(addressObject, 'address', 'destination')
1675
+ data = self.safe_dict(response, 'data', {})
1676
+ return self.parse_deposit_address(data, currency)
1677
+
1678
+ def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
1679
+ address = self.safe_string(depositAddress, 'address')
1680
+ currencyId = self.safe_string(depositAddress, 'currency')
1681
+ currency = self.safe_currency(currencyId, currency)
1555
1682
  self.check_address(address)
1556
1683
  return {
1557
- 'currency': code,
1684
+ 'info': depositAddress,
1685
+ 'currency': currency['code'],
1686
+ 'network': self.network_id_to_code(self.safe_string(depositAddress, 'blockchain')),
1558
1687
  'address': address,
1559
- 'tag': self.safe_string_2(addressObject, 'destinationTag', 'memo'),
1560
- 'network': self.network_id_to_code(selectedNetworkId),
1561
- 'info': data,
1688
+ 'tag': None,
1562
1689
  }
1563
1690
 
1564
- def nonce(self):
1565
- return self.milliseconds()
1566
-
1567
1691
  def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
1568
- url = self.urls['api']['rest'] + '/' + self.implode_params(path, params)
1692
+ url = self.urls['api'][api] + '/' + self.implode_params(path, params)
1569
1693
  query = self.omit(params, self.extract_params(path))
1570
1694
  if api == 'public':
1571
- if query:
1572
- url += '?' + self.urlencode(query)
1695
+ if method == 'GET':
1696
+ if query:
1697
+ url += '?' + self.urlencode(query)
1698
+ else:
1699
+ body = self.json(query)
1700
+ headers = {
1701
+ 'Content-Type': 'application/json',
1702
+ }
1573
1703
  else:
1574
1704
  self.check_required_credentials()
1575
- nonce = str(self.nonce())
1576
- auth = nonce + self.uid + self.apiKey
1577
- signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
1578
- body = self.json(self.extend({
1579
- 'key': self.apiKey,
1580
- 'signature': signature.upper(),
1581
- 'nonce': nonce,
1582
- }, query))
1705
+ seconds = str(self.seconds())
1706
+ body = self.json(query)
1707
+ auth = path + seconds + body
1708
+ signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256, 'base64')
1583
1709
  headers = {
1584
1710
  'Content-Type': 'application/json',
1711
+ 'X-AGGR-KEY': self.apiKey,
1712
+ 'X-AGGR-TIMESTAMP': seconds,
1713
+ 'X-AGGR-SIGNATURE': signature,
1585
1714
  }
1586
1715
  return {'url': url, 'method': method, 'body': body, 'headers': headers}
1587
1716
 
1588
1717
  def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
1589
- if isinstance(response, list):
1590
- return response # public endpoints may return []-arrays
1591
- if body == 'true':
1592
- return None
1718
+ # in some cases, like from createOrder, exchange returns nested escaped JSON string:
1719
+ # {"ok":"ok","data":{"messageType":"executionReport", "orderRejectReason":"{\"code\":405}"}}
1720
+ # and because of `.parseJson` bug, we need extra fix
1593
1721
  if response is None:
1594
- raise NullResponse(self.id + ' returned ' + self.json(response))
1595
- if 'e' in response:
1596
- if 'ok' in response:
1597
- if response['ok'] == 'ok':
1598
- return None
1599
- if 'error' in response:
1600
- message = self.safe_string(response, 'error')
1722
+ if body is None:
1723
+ raise NullResponse(self.id + ' returned empty response')
1724
+ elif body[0] == '{':
1725
+ fixed = self.fix_stringified_json_members(body)
1726
+ response = self.parse_json(fixed)
1727
+ else:
1728
+ raise NullResponse(self.id + ' returned unparsed response: ' + body)
1729
+ error = self.safe_string(response, 'error')
1730
+ if error is not None:
1601
1731
  feedback = self.id + ' ' + body
1602
- self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
1603
- self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
1732
+ self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
1733
+ self.throw_broadly_matched_exception(self.exceptions['broad'], error, feedback)
1604
1734
  raise ExchangeError(feedback)
1735
+ # check errors in order-engine(the responses are not standard, so we parse here)
1736
+ if url.find('do_my_new_order') >= 0:
1737
+ data = self.safe_dict(response, 'data', {})
1738
+ rejectReason = self.safe_string(data, 'rejectReason')
1739
+ if rejectReason is not None:
1740
+ self.throw_broadly_matched_exception(self.exceptions['broad'], rejectReason, rejectReason)
1741
+ raise ExchangeError(self.id + ' createOrder() ' + rejectReason)
1605
1742
  return None