ccxt-look 1.81.50

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 (264) hide show
  1. package/.cache/eslintcache +1 -0
  2. package/.dockerignore +6 -0
  3. package/.eslintignore +1 -0
  4. package/.gitattributes +5 -0
  5. package/.readthedocs.yaml +16 -0
  6. package/CONTRIBUTING.md +1049 -0
  7. package/LICENSE.txt +21 -0
  8. package/README.md +537 -0
  9. package/SECURITY.md +5 -0
  10. package/build/cleanup-old-tags.js +94 -0
  11. package/build/countries.js +256 -0
  12. package/build/export-exchanges.js +520 -0
  13. package/build/fs.js +51 -0
  14. package/build/transpile.js +1772 -0
  15. package/build/vss.js +78 -0
  16. package/ccxt.browser.js +7 -0
  17. package/ccxt.d.ts +692 -0
  18. package/ccxt.js +171 -0
  19. package/cleanup.sh +2 -0
  20. package/composer-install.sh +20 -0
  21. package/dist/ccxt.browser.js +208383 -0
  22. package/gource.sh +3 -0
  23. package/index.html +7 -0
  24. package/js/.eslintrc +87 -0
  25. package/js/aax.js +2686 -0
  26. package/js/ascendex.js +2584 -0
  27. package/js/base/.eslintrc.js +43 -0
  28. package/js/base/Exchange.js +2371 -0
  29. package/js/base/Precise.js +283 -0
  30. package/js/base/errorHierarchy.js +47 -0
  31. package/js/base/errors.js +55 -0
  32. package/js/base/functions/crypto.js +158 -0
  33. package/js/base/functions/encode.js +118 -0
  34. package/js/base/functions/generic.js +270 -0
  35. package/js/base/functions/misc.js +138 -0
  36. package/js/base/functions/number.js +329 -0
  37. package/js/base/functions/platform.js +38 -0
  38. package/js/base/functions/string.js +21 -0
  39. package/js/base/functions/throttle.js +79 -0
  40. package/js/base/functions/time.js +210 -0
  41. package/js/base/functions/type.js +66 -0
  42. package/js/base/functions.js +28 -0
  43. package/js/bequant.js +32 -0
  44. package/js/bibox.js +1407 -0
  45. package/js/bigone.js +1366 -0
  46. package/js/binance.js +5652 -0
  47. package/js/binancecoinm.js +46 -0
  48. package/js/binanceus.js +46 -0
  49. package/js/binanceusdm.js +49 -0
  50. package/js/bit2c.js +535 -0
  51. package/js/bitbank.js +842 -0
  52. package/js/bitbay.js +16 -0
  53. package/js/bitbns.js +1073 -0
  54. package/js/bitcoincom.js +15 -0
  55. package/js/bitfinex.js +1433 -0
  56. package/js/bitfinex2.js +2025 -0
  57. package/js/bitflyer.js +840 -0
  58. package/js/bitforex.js +614 -0
  59. package/js/bitget.js +2397 -0
  60. package/js/bithumb.js +980 -0
  61. package/js/bitmart.js +2516 -0
  62. package/js/bitmex.js +1809 -0
  63. package/js/bitopro.js +1443 -0
  64. package/js/bitpanda.js +1782 -0
  65. package/js/bitrue.js +1747 -0
  66. package/js/bitso.js +1062 -0
  67. package/js/bitstamp.js +1757 -0
  68. package/js/bitstamp1.js +343 -0
  69. package/js/bittrex.js +1876 -0
  70. package/js/bitvavo.js +1579 -0
  71. package/js/bkex.js +1233 -0
  72. package/js/bl3p.js +346 -0
  73. package/js/blockchaincom.js +969 -0
  74. package/js/btcalpha.js +680 -0
  75. package/js/btcbox.js +477 -0
  76. package/js/btcmarkets.js +1022 -0
  77. package/js/btctradeua.js +466 -0
  78. package/js/btcturk.js +734 -0
  79. package/js/buda.js +946 -0
  80. package/js/bw.js +1265 -0
  81. package/js/bybit.js +3372 -0
  82. package/js/bytetrade.js +1336 -0
  83. package/js/cdax.js +1646 -0
  84. package/js/cex.js +1410 -0
  85. package/js/coinbase.js +1342 -0
  86. package/js/coinbaseprime.js +31 -0
  87. package/js/coinbasepro.js +1466 -0
  88. package/js/coincheck.js +755 -0
  89. package/js/coinex.js +3400 -0
  90. package/js/coinfalcon.js +880 -0
  91. package/js/coinmate.js +794 -0
  92. package/js/coinone.js +816 -0
  93. package/js/coinspot.js +345 -0
  94. package/js/crex24.js +1636 -0
  95. package/js/cryptocom.js +1832 -0
  96. package/js/currencycom.js +1748 -0
  97. package/js/delta.js +1547 -0
  98. package/js/deribit.js +2148 -0
  99. package/js/digifinex.js +1585 -0
  100. package/js/eqonex.js +1660 -0
  101. package/js/exmo.js +1670 -0
  102. package/js/fairdesk.js +1231 -0
  103. package/js/flowbtc.js +35 -0
  104. package/js/fmfwio.js +34 -0
  105. package/js/ftx.js +2751 -0
  106. package/js/ftxus.js +38 -0
  107. package/js/gateio.js +4174 -0
  108. package/js/gemini.js +1397 -0
  109. package/js/hitbtc.js +1343 -0
  110. package/js/hitbtc3.js +2329 -0
  111. package/js/hollaex.js +1486 -0
  112. package/js/huobi.js +5706 -0
  113. package/js/huobijp.js +1710 -0
  114. package/js/huobipro.js +18 -0
  115. package/js/idex.js +1439 -0
  116. package/js/independentreserve.js +649 -0
  117. package/js/indodax.js +742 -0
  118. package/js/itbit.js +722 -0
  119. package/js/kraken.js +2179 -0
  120. package/js/kucoin.js +2571 -0
  121. package/js/kucoinfutures.js +1771 -0
  122. package/js/kuna.js +809 -0
  123. package/js/latoken.js +1445 -0
  124. package/js/lbank.js +760 -0
  125. package/js/liquid.js +1432 -0
  126. package/js/luno.js +873 -0
  127. package/js/lykke.js +1147 -0
  128. package/js/mercado.js +771 -0
  129. package/js/mexc.js +3151 -0
  130. package/js/ndax.js +2233 -0
  131. package/js/novadax.js +1318 -0
  132. package/js/oceanex.js +816 -0
  133. package/js/okcoin.js +3841 -0
  134. package/js/okex.js +16 -0
  135. package/js/okex5.js +16 -0
  136. package/js/okx.js +4795 -0
  137. package/js/paymium.js +498 -0
  138. package/js/phemex.js +2957 -0
  139. package/js/poloniex.js +1674 -0
  140. package/js/probit.js +1346 -0
  141. package/js/qtrade.js +1588 -0
  142. package/js/ripio.js +1061 -0
  143. package/js/static_dependencies/BN/bn.js +3526 -0
  144. package/js/static_dependencies/README.md +1 -0
  145. package/js/static_dependencies/crypto-js/crypto-js.js +5988 -0
  146. package/js/static_dependencies/elliptic/lib/elliptic/curve/base.js +375 -0
  147. package/js/static_dependencies/elliptic/lib/elliptic/curve/edwards.js +433 -0
  148. package/js/static_dependencies/elliptic/lib/elliptic/curve/index.js +8 -0
  149. package/js/static_dependencies/elliptic/lib/elliptic/curve/mont.js +180 -0
  150. package/js/static_dependencies/elliptic/lib/elliptic/curve/short.js +938 -0
  151. package/js/static_dependencies/elliptic/lib/elliptic/curves.js +204 -0
  152. package/js/static_dependencies/elliptic/lib/elliptic/ec/index.js +240 -0
  153. package/js/static_dependencies/elliptic/lib/elliptic/ec/key.js +119 -0
  154. package/js/static_dependencies/elliptic/lib/elliptic/ec/signature.js +24 -0
  155. package/js/static_dependencies/elliptic/lib/elliptic/eddsa/index.js +145 -0
  156. package/js/static_dependencies/elliptic/lib/elliptic/eddsa/key.js +100 -0
  157. package/js/static_dependencies/elliptic/lib/elliptic/eddsa/signature.js +65 -0
  158. package/js/static_dependencies/elliptic/lib/elliptic/precomputed/secp256k1.js +780 -0
  159. package/js/static_dependencies/elliptic/lib/elliptic/utils.js +214 -0
  160. package/js/static_dependencies/elliptic/lib/elliptic.js +22 -0
  161. package/js/static_dependencies/elliptic/lib/hmac-drbg/hmac-drbg.js +114 -0
  162. package/js/static_dependencies/fetch-ponyfill/fetch-node.js +39 -0
  163. package/js/static_dependencies/node-fetch/index.js +1564 -0
  164. package/js/static_dependencies/node-rsa/NodeRSA.js +223 -0
  165. package/js/static_dependencies/node-rsa/asn1/ber/errors.js +13 -0
  166. package/js/static_dependencies/node-rsa/asn1/ber/index.js +21 -0
  167. package/js/static_dependencies/node-rsa/asn1/ber/reader.js +262 -0
  168. package/js/static_dependencies/node-rsa/asn1/ber/types.js +36 -0
  169. package/js/static_dependencies/node-rsa/asn1/index.js +17 -0
  170. package/js/static_dependencies/node-rsa/encryptEngines/js.js +34 -0
  171. package/js/static_dependencies/node-rsa/formats/components.js +71 -0
  172. package/js/static_dependencies/node-rsa/formats/formats.js +31 -0
  173. package/js/static_dependencies/node-rsa/formats/pkcs1.js +148 -0
  174. package/js/static_dependencies/node-rsa/formats/pkcs8.js +187 -0
  175. package/js/static_dependencies/node-rsa/libs/jsbn.js +1252 -0
  176. package/js/static_dependencies/node-rsa/libs/rsa.js +147 -0
  177. package/js/static_dependencies/node-rsa/schemes/pkcs1.js +176 -0
  178. package/js/static_dependencies/node-rsa/schemes/schemes.js +21 -0
  179. package/js/static_dependencies/node-rsa/utils.js +98 -0
  180. package/js/static_dependencies/qs/formats.js +18 -0
  181. package/js/static_dependencies/qs/index.js +11 -0
  182. package/js/static_dependencies/qs/parse.js +242 -0
  183. package/js/static_dependencies/qs/stringify.js +269 -0
  184. package/js/static_dependencies/qs/utils.js +230 -0
  185. package/js/stex.js +1925 -0
  186. package/js/test/.eslintrc.js +42 -0
  187. package/js/test/Exchange/test.balance.js +61 -0
  188. package/js/test/Exchange/test.borrowRate.js +32 -0
  189. package/js/test/Exchange/test.currency.js +52 -0
  190. package/js/test/Exchange/test.fetchBalance.js +23 -0
  191. package/js/test/Exchange/test.fetchBorrowInterest.js +59 -0
  192. package/js/test/Exchange/test.fetchBorrowRate.js +32 -0
  193. package/js/test/Exchange/test.fetchBorrowRates.js +28 -0
  194. package/js/test/Exchange/test.fetchClosedOrders.js +32 -0
  195. package/js/test/Exchange/test.fetchCurrencies.js +35 -0
  196. package/js/test/Exchange/test.fetchDeposits.js +31 -0
  197. package/js/test/Exchange/test.fetchFundingFees.js +19 -0
  198. package/js/test/Exchange/test.fetchFundingRateHistory.js +40 -0
  199. package/js/test/Exchange/test.fetchL2OrderBook.js +23 -0
  200. package/js/test/Exchange/test.fetchLedger.js +42 -0
  201. package/js/test/Exchange/test.fetchLeverageTiers.js +33 -0
  202. package/js/test/Exchange/test.fetchMarketLeverageTiers.js +22 -0
  203. package/js/test/Exchange/test.fetchMarkets.js +33 -0
  204. package/js/test/Exchange/test.fetchMyTrades.js +42 -0
  205. package/js/test/Exchange/test.fetchOHLCV.js +46 -0
  206. package/js/test/Exchange/test.fetchOpenOrders.js +36 -0
  207. package/js/test/Exchange/test.fetchOrderBook.js +25 -0
  208. package/js/test/Exchange/test.fetchOrderBooks.js +35 -0
  209. package/js/test/Exchange/test.fetchOrders.js +41 -0
  210. package/js/test/Exchange/test.fetchPositions.js +47 -0
  211. package/js/test/Exchange/test.fetchStatus.js +35 -0
  212. package/js/test/Exchange/test.fetchTicker.js +38 -0
  213. package/js/test/Exchange/test.fetchTickers.js +49 -0
  214. package/js/test/Exchange/test.fetchTrades.js +39 -0
  215. package/js/test/Exchange/test.fetchTradingFee.js +18 -0
  216. package/js/test/Exchange/test.fetchTradingFees.js +22 -0
  217. package/js/test/Exchange/test.fetchTransactions.js +31 -0
  218. package/js/test/Exchange/test.fetchWithdrawals.js +31 -0
  219. package/js/test/Exchange/test.ledgerItem.js +46 -0
  220. package/js/test/Exchange/test.leverageTier.js +33 -0
  221. package/js/test/Exchange/test.loadMarkets.js +35 -0
  222. package/js/test/Exchange/test.market.js +129 -0
  223. package/js/test/Exchange/test.ohlcv.js +33 -0
  224. package/js/test/Exchange/test.order.js +62 -0
  225. package/js/test/Exchange/test.orderbook.js +61 -0
  226. package/js/test/Exchange/test.position.js +21 -0
  227. package/js/test/Exchange/test.throttle.js +94 -0
  228. package/js/test/Exchange/test.ticker.js +95 -0
  229. package/js/test/Exchange/test.trade.js +68 -0
  230. package/js/test/Exchange/test.tradingFee.js +34 -0
  231. package/js/test/Exchange/test.transaction.js +35 -0
  232. package/js/test/base/.eslintrc +38 -0
  233. package/js/test/base/functions/test.crypto.js +110 -0
  234. package/js/test/base/functions/test.datetime.js +62 -0
  235. package/js/test/base/functions/test.generic.js +152 -0
  236. package/js/test/base/functions/test.number.js +362 -0
  237. package/js/test/base/functions/test.time.js +56 -0
  238. package/js/test/base/functions/test.type.js +53 -0
  239. package/js/test/base/test.base.js +193 -0
  240. package/js/test/errors/test.InsufficientFunds.js +86 -0
  241. package/js/test/errors/test.InvalidNonce.js +64 -0
  242. package/js/test/errors/test.InvalidOrder.js +35 -0
  243. package/js/test/errors/test.OrderNotFound.js +39 -0
  244. package/js/test/test.js +426 -0
  245. package/js/test/test.timeout_hang.js +12 -0
  246. package/js/therock.js +1431 -0
  247. package/js/tidebit.js +632 -0
  248. package/js/tidex.js +939 -0
  249. package/js/timex.js +1283 -0
  250. package/js/upbit.js +1622 -0
  251. package/js/vcc.js +1353 -0
  252. package/js/wavesexchange.js +2185 -0
  253. package/js/wazirx.js +732 -0
  254. package/js/whitebit.js +1352 -0
  255. package/js/woo.js +1577 -0
  256. package/js/xena.js +1948 -0
  257. package/js/yobit.js +1129 -0
  258. package/js/zaif.js +647 -0
  259. package/js/zb.js +4088 -0
  260. package/js/zipmex.js +40 -0
  261. package/js/zonda.js +1497 -0
  262. package/multilang.sh +159 -0
  263. package/package.json +591 -0
  264. package/postinstall.js +103 -0
package/js/ftx.js ADDED
@@ -0,0 +1,2751 @@
1
+ 'use strict';
2
+
3
+ // ---------------------------------------------------------------------------
4
+
5
+ const Exchange = require ('./base/Exchange');
6
+ const { TICK_SIZE } = require ('./base/functions/number');
7
+ const { ExchangeError, InvalidOrder, BadRequest, InsufficientFunds, OrderNotFound, AuthenticationError, RateLimitExceeded, ExchangeNotAvailable, CancelPending, ArgumentsRequired, PermissionDenied, BadSymbol, DuplicateOrderId, BadResponse } = require ('./base/errors');
8
+ const Precise = require ('./base/Precise');
9
+
10
+ // ---------------------------------------------------------------------------
11
+
12
+ module.exports = class ftx extends Exchange {
13
+ describe () {
14
+ return this.deepExtend (super.describe (), {
15
+ 'id': 'ftx',
16
+ 'name': 'FTX',
17
+ 'countries': [ 'BS' ], // Bahamas
18
+ // hard limit of 7 requests per 200ms => 35 requests per 1000ms => 1000ms / 35 = 28.5714 ms between requests
19
+ // 10 withdrawal requests per 30 seconds = (1000ms / rateLimit) / (1/3) = 90.1
20
+ // cancels do not count towards rateLimit
21
+ // only 'order-making' requests count towards ratelimit
22
+ 'rateLimit': 28.57,
23
+ 'certified': true,
24
+ 'pro': true,
25
+ 'hostname': 'ftx.com', // or ftx.us
26
+ 'urls': {
27
+ 'logo': 'https://user-images.githubusercontent.com/1294454/67149189-df896480-f2b0-11e9-8816-41593e17f9ec.jpg',
28
+ 'www': 'https://ftx.com',
29
+ 'api': {
30
+ 'public': 'https://{hostname}',
31
+ 'private': 'https://{hostname}',
32
+ },
33
+ 'doc': 'https://github.com/ftexchange/ftx',
34
+ 'fees': 'https://ftexchange.zendesk.com/hc/en-us/articles/360024479432-Fees',
35
+ 'referral': {
36
+ 'url': 'https://ftx.com/#a=ccxt',
37
+ 'discount': 0.05,
38
+ },
39
+ },
40
+ 'has': {
41
+ 'CORS': undefined,
42
+ 'spot': true,
43
+ 'margin': true,
44
+ 'swap': true,
45
+ 'future': true,
46
+ 'option': false,
47
+ 'cancelAllOrders': true,
48
+ 'cancelOrder': true,
49
+ 'createOrder': true,
50
+ 'createReduceOnlyOrder': true,
51
+ 'createStopLimitOrder': true,
52
+ 'createStopMarketOrder': true,
53
+ 'createStopOrder': true,
54
+ 'editOrder': true,
55
+ 'fetchBalance': true,
56
+ 'fetchBorrowInterest': true,
57
+ 'fetchBorrowRate': true,
58
+ 'fetchBorrowRateHistories': true,
59
+ 'fetchBorrowRateHistory': true,
60
+ 'fetchBorrowRates': true,
61
+ 'fetchClosedOrders': undefined,
62
+ 'fetchCurrencies': true,
63
+ 'fetchDepositAddress': true,
64
+ 'fetchDeposits': true,
65
+ 'fetchFundingFees': undefined,
66
+ 'fetchFundingHistory': true,
67
+ 'fetchFundingRate': true,
68
+ 'fetchFundingRateHistory': true,
69
+ 'fetchFundingRates': false,
70
+ 'fetchIndexOHLCV': true,
71
+ 'fetchLeverageTiers': false,
72
+ 'fetchMarketLeverageTiers': false,
73
+ 'fetchMarkets': true,
74
+ 'fetchMarkOHLCV': false,
75
+ 'fetchMyTrades': true,
76
+ 'fetchOHLCV': true,
77
+ 'fetchOpenOrders': true,
78
+ 'fetchOrder': true,
79
+ 'fetchOrderBook': true,
80
+ 'fetchOrders': true,
81
+ 'fetchOrderTrades': true,
82
+ 'fetchPosition': false,
83
+ 'fetchPositions': true,
84
+ 'fetchPositionsRisk': false,
85
+ 'fetchPremiumIndexOHLCV': false,
86
+ 'fetchTicker': true,
87
+ 'fetchTickers': true,
88
+ 'fetchTime': false,
89
+ 'fetchTrades': true,
90
+ 'fetchTradingFee': false,
91
+ 'fetchTradingFees': true,
92
+ 'fetchTransfer': undefined,
93
+ 'fetchTransfers': undefined,
94
+ 'fetchWithdrawals': true,
95
+ 'reduceMargin': false,
96
+ 'setLeverage': true,
97
+ 'setMarginMode': false, // FTX only supports cross margin
98
+ 'setPositionMode': false,
99
+ 'transfer': true,
100
+ 'withdraw': true,
101
+ },
102
+ 'timeframes': {
103
+ '15s': '15',
104
+ '1m': '60',
105
+ '5m': '300',
106
+ '15m': '900',
107
+ '1h': '3600',
108
+ '4h': '14400',
109
+ '1d': '86400',
110
+ '3d': '259200',
111
+ '1w': '604800',
112
+ '2w': '1209600',
113
+ // the exchange does not align candles to the start of the month
114
+ // it can only fetch candles in fixed intervals of multiples of whole days
115
+ // that works for all timeframes, except the monthly timeframe
116
+ // because months have varying numbers of days
117
+ '1M': '2592000',
118
+ },
119
+ 'api': {
120
+ 'public': {
121
+ 'get': {
122
+ 'coins': 1,
123
+ // markets
124
+ 'markets': 1,
125
+ 'markets/{market_name}': 1,
126
+ 'markets/{market_name}/orderbook': 1, // ?depth={depth}
127
+ 'markets/{market_name}/trades': 1, // ?limit={limit}&start_time={start_time}&end_time={end_time}
128
+ 'markets/{market_name}/candles': 1, // ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time}
129
+ // futures
130
+ 'futures': 1,
131
+ 'futures/{future_name}': 1,
132
+ 'futures/{future_name}/stats': 1,
133
+ 'funding_rates': 1,
134
+ 'indexes/{index_name}/weights': 1,
135
+ 'expired_futures': 1,
136
+ 'indexes/{market_name}/candles': 1, // ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time}
137
+ // wallet
138
+ 'wallet/coins': 1,
139
+ // leverage tokens
140
+ 'lt/tokens': 1,
141
+ 'lt/{token_name}': 1,
142
+ // etfs
143
+ 'etfs/rebalance_info': 1,
144
+ // options
145
+ 'options/requests': 1,
146
+ 'options/trades': 1,
147
+ 'options/historical_volumes/BTC': 1,
148
+ 'stats/24h_options_volume': 1,
149
+ 'options/open_interest/BTC': 1,
150
+ 'options/historical_open_interest/BTC': 1,
151
+ // spot margin
152
+ 'spot_margin/history': 1,
153
+ 'spot_margin/borrow_summary': 1,
154
+ // nfts
155
+ 'nft/nfts': 1,
156
+ 'nft/{nft_id}': 1,
157
+ 'nft/{nft_id}/trades': 1,
158
+ 'nft/all_trades': 1,
159
+ 'nft/{nft_id}/account_info': 1,
160
+ 'nft/collections': 1,
161
+ // ftx pay
162
+ 'ftxpay/apps/{user_specific_id}/details': 1,
163
+ },
164
+ 'post': {
165
+ 'ftxpay/apps/{user_specific_id}/orders': 1,
166
+ },
167
+ },
168
+ 'private': {
169
+ 'get': {
170
+ // subaccounts
171
+ 'subaccounts': 1,
172
+ 'subaccounts/{nickname}/balances': 1,
173
+ // account
174
+ 'account': 1,
175
+ 'positions': 1,
176
+ // wallet
177
+ 'wallet/balances': 1,
178
+ 'wallet/all_balances': 1,
179
+ 'wallet/deposit_address/{coin}': 1, // ?method={method}
180
+ 'wallet/deposits': 1,
181
+ 'wallet/withdrawals': 1,
182
+ 'wallet/airdrops': 1,
183
+ 'wallet/withdrawal_fee': 1,
184
+ 'wallet/saved_addresses': 1,
185
+ // orders
186
+ 'orders': 1, // ?market={market}
187
+ 'orders/history': 1, // ?market={market}
188
+ 'orders/{order_id}': 1,
189
+ 'orders/by_client_id/{client_order_id}': 1,
190
+ // conditional orders
191
+ 'conditional_orders': 1, // ?market={market}
192
+ 'conditional_orders/{conditional_order_id}/triggers': 1,
193
+ 'conditional_orders/history': 1, // ?market={market}
194
+ 'fills': 1, // ?market={market}
195
+ 'funding_payments': 1,
196
+ // leverage tokens
197
+ 'lt/balances': 1,
198
+ 'lt/creations': 1,
199
+ 'lt/redemptions': 1,
200
+ // options
201
+ 'options/my_requests': 1,
202
+ 'options/requests/{request_id}/quotes': 1,
203
+ 'options/my_quotes': 1,
204
+ 'options/account_info': 1,
205
+ 'options/positions': 1,
206
+ 'options/fills': 1,
207
+ // staking
208
+ 'staking/stakes': 1,
209
+ 'staking/unstake_requests': 1,
210
+ 'staking/balances': 1,
211
+ 'staking/staking_rewards': 1,
212
+ // otc
213
+ 'otc/quotes/{quoteId}': 1,
214
+ // spot margin
215
+ 'spot_margin/borrow_rates': 1,
216
+ 'spot_margin/lending_rates': 1,
217
+ 'spot_margin/market_info': 1, // ?market={market}
218
+ 'spot_margin/borrow_history': 1,
219
+ 'spot_margin/lending_history': 1,
220
+ 'spot_margin/offers': 1,
221
+ 'spot_margin/lending_info': 1,
222
+ // nfts
223
+ 'nft/balances': 1,
224
+ 'nft/bids': 1,
225
+ 'nft/deposits': 1,
226
+ 'nft/withdrawals': 1,
227
+ 'nft/fills': 1,
228
+ 'nft/gallery/{gallery_id}': 1,
229
+ 'nft/gallery_settings': 1,
230
+ // latency statistics
231
+ 'stats/latency_stats': 1,
232
+ // pnl
233
+ 'pnl/historical_changes': 1,
234
+ },
235
+ 'post': {
236
+ // subaccounts
237
+ 'subaccounts': 1,
238
+ 'subaccounts/update_name': 1,
239
+ 'subaccounts/transfer': 1,
240
+ // account
241
+ 'account/leverage': 1,
242
+ // wallet
243
+ 'wallet/deposit_address/list': 1,
244
+ 'wallet/withdrawals': 90,
245
+ 'wallet/saved_addresses': 1,
246
+ // orders
247
+ 'orders': 1,
248
+ 'conditional_orders': 1,
249
+ 'orders/{order_id}/modify': 1,
250
+ 'orders/by_client_id/{client_order_id}/modify': 1,
251
+ 'conditional_orders/{order_id}/modify': 1,
252
+ // leverage tokens
253
+ 'lt/{token_name}/create': 1,
254
+ 'lt/{token_name}/redeem': 1,
255
+ // options
256
+ 'options/requests': 1,
257
+ 'options/requests/{request_id}/quotes': 1,
258
+ 'options/quotes/{quote_id}/accept': 1,
259
+ // staking
260
+ 'staking/unstake_requests': 1,
261
+ 'srm_stakes/stakes': 1,
262
+ // otc
263
+ 'otc/quotes/{quote_id}/accept': 1,
264
+ 'otc/quotes': 1,
265
+ // spot margin
266
+ 'spot_margin/offers': 1,
267
+ // nfts
268
+ 'nft/offer': 1,
269
+ 'nft/buy': 1,
270
+ 'nft/auction': 1,
271
+ 'nft/edit_auction': 1,
272
+ 'nft/cancel_auction': 1,
273
+ 'nft/bids': 1,
274
+ 'nft/redeem': 1,
275
+ 'nft/gallery_settings': 1,
276
+ // ftx pay
277
+ 'ftxpay/apps/{user_specific_id}/orders': 1,
278
+ },
279
+ 'delete': {
280
+ // subaccounts
281
+ 'subaccounts': 1,
282
+ // wallet
283
+ 'wallet/saved_addresses/{saved_address_id}': 1,
284
+ // orders
285
+ 'orders/{order_id}': 1,
286
+ 'orders/by_client_id/{client_order_id}': 1,
287
+ 'orders': 1,
288
+ 'conditional_orders/{order_id}': 1,
289
+ // options
290
+ 'options/requests/{request_id}': 1,
291
+ 'options/quotes/{quote_id}': 1,
292
+ // staking
293
+ 'staking/unstake_requests/{request_id}': 1,
294
+ },
295
+ },
296
+ },
297
+ 'fees': {
298
+ 'trading': {
299
+ 'tierBased': true,
300
+ 'percentage': true,
301
+ 'maker': this.parseNumber ('0.0002'),
302
+ 'taker': this.parseNumber ('0.0007'),
303
+ 'tiers': {
304
+ 'taker': [
305
+ [ this.parseNumber ('0'), this.parseNumber ('0.0007') ],
306
+ [ this.parseNumber ('2000000'), this.parseNumber ('0.0006') ],
307
+ [ this.parseNumber ('5000000'), this.parseNumber ('0.00055') ],
308
+ [ this.parseNumber ('10000000'), this.parseNumber ('0.0005') ],
309
+ [ this.parseNumber ('25000000'), this.parseNumber ('0.0045') ],
310
+ [ this.parseNumber ('50000000'), this.parseNumber ('0.0004') ],
311
+ ],
312
+ 'maker': [
313
+ [ this.parseNumber ('0'), this.parseNumber ('0.0002') ],
314
+ [ this.parseNumber ('2000000'), this.parseNumber ('0.00015') ],
315
+ [ this.parseNumber ('5000000'), this.parseNumber ('0.0001') ],
316
+ [ this.parseNumber ('10000000'), this.parseNumber ('0.00005') ],
317
+ [ this.parseNumber ('25000000'), this.parseNumber ('0') ],
318
+ [ this.parseNumber ('50000000'), this.parseNumber ('0') ],
319
+ ],
320
+ },
321
+ },
322
+ 'funding': {
323
+ 'withdraw': {},
324
+ },
325
+ },
326
+ 'exceptions': {
327
+ 'exact': {
328
+ 'Slow down': RateLimitExceeded, // {"error":"Slow down","success":false}
329
+ 'Size too small for provide': InvalidOrder, // {"error":"Size too small for provide","success":false}
330
+ 'Not enough balances': InsufficientFunds, // {"error":"Not enough balances","success":false}
331
+ 'InvalidPrice': InvalidOrder, // {"error":"Invalid price","success":false}
332
+ 'Size too small': InvalidOrder, // {"error":"Size too small","success":false}
333
+ 'Size too large': InvalidOrder, // {"error":"Size too large","success":false}
334
+ 'Invalid price': InvalidOrder, // {"success":false,"error":"Invalid price"}
335
+ 'Missing parameter price': InvalidOrder, // {"error":"Missing parameter price","success":false}
336
+ 'Order not found': OrderNotFound, // {"error":"Order not found","success":false}
337
+ 'Order already closed': InvalidOrder, // {"error":"Order already closed","success":false}
338
+ 'Trigger price too high': InvalidOrder, // {"error":"Trigger price too high","success":false}
339
+ 'Trigger price too low': InvalidOrder, // {"error":"Trigger price too low","success":false}
340
+ 'Order already queued for cancellation': CancelPending, // {"error":"Order already queued for cancellation","success":false}
341
+ 'Duplicate client order ID': DuplicateOrderId, // {"error":"Duplicate client order ID","success":false}
342
+ 'Spot orders cannot be reduce-only': InvalidOrder, // {"error":"Spot orders cannot be reduce-only","success":false}
343
+ 'Invalid reduce-only order': InvalidOrder, // {"error":"Invalid reduce-only order","success":false}
344
+ 'Account does not have enough balances': InsufficientFunds, // {"success":false,"error":"Account does not have enough balances"}
345
+ 'Not authorized for subaccount-specific access': PermissionDenied, // {"success":false,"error":"Not authorized for subaccount-specific access"}
346
+ 'Not approved to trade this product': PermissionDenied, // {"success":false,"error":"Not approved to trade this product"}
347
+ },
348
+ 'broad': {
349
+ // {"error":"Not logged in","success":false}
350
+ // {"error":"Not logged in: Invalid API key","success":false}
351
+ 'Not logged in': AuthenticationError,
352
+ 'Account does not have enough margin for order': InsufficientFunds,
353
+ 'Invalid parameter': BadRequest, // {"error":"Invalid parameter start_time","success":false}
354
+ 'The requested URL was not found on the server': BadRequest,
355
+ 'No such coin': BadRequest,
356
+ 'No such subaccount': AuthenticationError,
357
+ 'No such future': BadSymbol,
358
+ 'No such market': BadSymbol,
359
+ 'Do not send more than': RateLimitExceeded,
360
+ 'An unexpected error occurred': ExchangeNotAvailable, // {"error":"An unexpected error occurred, please try again later (58BC21C795).","success":false}
361
+ 'Please retry request': ExchangeNotAvailable, // {"error":"Please retry request","success":false}
362
+ 'Please try again': ExchangeNotAvailable, // {"error":"Please try again","success":false}
363
+ 'Try again': ExchangeNotAvailable, // {"error":"Try again","success":false}
364
+ 'Only have permissions for subaccount': PermissionDenied, // {"success":false,"error":"Only have permissions for subaccount *sub_name*"}
365
+ },
366
+ },
367
+ 'precisionMode': TICK_SIZE,
368
+ 'options': {
369
+ // support for canceling conditional orders
370
+ // https://github.com/ccxt/ccxt/issues/6669
371
+ 'fetchMarkets': {
372
+ // the expiry datetime may be undefined for expiring futures, https://github.com/ccxt/ccxt/pull/12692
373
+ 'throwOnUndefinedExpiry': false,
374
+ },
375
+ 'cancelOrder': {
376
+ 'method': 'privateDeleteOrdersOrderId', // privateDeleteConditionalOrdersOrderId
377
+ },
378
+ 'fetchOpenOrders': {
379
+ 'method': 'privateGetOrders', // privateGetConditionalOrders
380
+ },
381
+ 'fetchOrders': {
382
+ 'method': 'privateGetOrdersHistory', // privateGetConditionalOrdersHistory
383
+ },
384
+ 'sign': {
385
+ 'ftx.com': 'FTX',
386
+ 'ftx.us': 'FTXUS',
387
+ },
388
+ 'networks': {
389
+ 'SOL': 'sol',
390
+ 'SPL': 'sol',
391
+ 'TRX': 'trx',
392
+ 'TRC20': 'trx',
393
+ 'ETH': 'erc20',
394
+ 'ERC20': 'erc20',
395
+ 'OMNI': 'omni',
396
+ 'BEP2': 'bep2',
397
+ 'BNB': 'bep2',
398
+ 'BEP20': 'bsc',
399
+ 'BSC': 'bsc',
400
+ },
401
+ },
402
+ 'commonCurrencies': {
403
+ 'AMC': 'AMC Entertainment Holdings',
404
+ 'STARS': 'StarLaunch',
405
+ },
406
+ });
407
+ }
408
+
409
+ async fetchCurrencies (params = {}) {
410
+ const response = await this.publicGetCoins (params);
411
+ const currencies = this.safeValue (response, 'result', []);
412
+ //
413
+ // {
414
+ // "success":true,
415
+ // "result": [
416
+ // {"id":"BTC","name":"Bitcoin"},
417
+ // {"id":"ETH","name":"Ethereum"},
418
+ // {"id":"ETHMOON","name":"10X Long Ethereum Token","underlying":"ETH"},
419
+ // {"id":"EOSBULL","name":"3X Long EOS Token","underlying":"EOS"},
420
+ // ],
421
+ // }
422
+ //
423
+ const result = {};
424
+ for (let i = 0; i < currencies.length; i++) {
425
+ const currency = currencies[i];
426
+ const id = this.safeString (currency, 'id');
427
+ const code = this.safeCurrencyCode (id);
428
+ const name = this.safeString (currency, 'name');
429
+ result[code] = {
430
+ 'id': id,
431
+ 'code': code,
432
+ 'info': currency,
433
+ 'type': undefined,
434
+ 'name': name,
435
+ 'active': undefined,
436
+ 'deposit': undefined,
437
+ 'withdraw': undefined,
438
+ 'fee': undefined,
439
+ 'precision': undefined,
440
+ 'limits': {
441
+ 'withdraw': { 'min': undefined, 'max': undefined },
442
+ 'amount': { 'min': undefined, 'max': undefined },
443
+ },
444
+ };
445
+ }
446
+ return result;
447
+ }
448
+
449
+ async fetchMarkets (params = {}) {
450
+ const response = await this.publicGetMarkets (params);
451
+ //
452
+ // {
453
+ // 'success': true,
454
+ // "result": [
455
+ // {
456
+ // "ask":170.37,
457
+ // "baseCurrency":null,
458
+ // "bid":170.31,
459
+ // "change1h":-0.019001554672655036,
460
+ // "change24h":-0.024841165359738997,
461
+ // "changeBod":-0.03816406029469881,
462
+ // "enabled":true,
463
+ // "last":170.37,
464
+ // "name":"ETH-PERP",
465
+ // "price":170.37,
466
+ // "priceIncrement":0.01,
467
+ // "quoteCurrency":null,
468
+ // "quoteVolume24h":7742164.59889,
469
+ // "sizeIncrement":0.001,
470
+ // "type":"future",
471
+ // "underlying":"ETH",
472
+ // "volumeUsd24h":7742164.59889
473
+ // },
474
+ // {
475
+ // "ask":170.44,
476
+ // "baseCurrency":"ETH",
477
+ // "bid":170.41,
478
+ // "change1h":-0.018485459257126403,
479
+ // "change24h":-0.023825887743413515,
480
+ // "changeBod":-0.037605872388481086,
481
+ // "enabled":true,
482
+ // "last":172.72,
483
+ // "name":"ETH/USD",
484
+ // "price":170.44,
485
+ // "priceIncrement":0.01,
486
+ // "quoteCurrency":"USD",
487
+ // "quoteVolume24h":382802.0252,
488
+ // "sizeIncrement":0.001,
489
+ // "type":"spot",
490
+ // "underlying":null,
491
+ // "volumeUsd24h":382802.0252
492
+ // },
493
+ // ],
494
+ // }
495
+ //
496
+ // {
497
+ // name: "BTC-PERP",
498
+ // enabled: true,
499
+ // postOnly: false,
500
+ // priceIncrement: "1.0",
501
+ // sizeIncrement: "0.0001",
502
+ // minProvideSize: "0.001",
503
+ // last: "60397.0",
504
+ // bid: "60387.0",
505
+ // ask: "60388.0",
506
+ // price: "60388.0",
507
+ // type: "future",
508
+ // baseCurrency: null,
509
+ // quoteCurrency: null,
510
+ // underlying: "BTC",
511
+ // restricted: false,
512
+ // highLeverageFeeExempt: true,
513
+ // change1h: "-0.0036463231533270636",
514
+ // change24h: "-0.01844838515677064",
515
+ // changeBod: "-0.010130151132675475",
516
+ // quoteVolume24h: "2892083192.6099",
517
+ // volumeUsd24h: "2892083192.6099"
518
+ // }
519
+ //
520
+ let allFuturesResponse = undefined;
521
+ if (this.has['future'] && (this.hostname !== 'ftx.us')) {
522
+ allFuturesResponse = await this.publicGetFutures ();
523
+ }
524
+ //
525
+ // {
526
+ // success: true,
527
+ // result: [
528
+ // {
529
+ // name: "1INCH-PERP",
530
+ // underlying: "1INCH",
531
+ // description: "1INCH Token Perpetual Futures",
532
+ // type: "perpetual",
533
+ // expiry: null,
534
+ // perpetual: true,
535
+ // expired: false,
536
+ // enabled: true,
537
+ // postOnly: false,
538
+ // priceIncrement: "0.0001",
539
+ // sizeIncrement: "1.0",
540
+ // last: "2.5556",
541
+ // bid: "2.5555",
542
+ // ask: "2.5563",
543
+ // index: "2.5612449804010833",
544
+ // mark: "2.5587",
545
+ // imfFactor: "0.0005",
546
+ // lowerBound: "2.4315",
547
+ // upperBound: "2.6893",
548
+ // underlyingDescription: "1INCH Token",
549
+ // expiryDescription: "Perpetual",
550
+ // moveStart: null,
551
+ // marginPrice: "2.5587",
552
+ // positionLimitWeight: "20.0",
553
+ // group: "perpetual",
554
+ // change1h: "0.00799716356760164",
555
+ // change24h: "0.004909276569004792",
556
+ // changeBod: "0.008394419484511705",
557
+ // volumeUsd24h: "17834492.0818",
558
+ // volume: "7224898.0",
559
+ // openInterest: "5597917.0",
560
+ // openInterestUsd: "14323390.2279",
561
+ // },
562
+ // ...
563
+ // ],
564
+ // }
565
+ //
566
+ const result = [];
567
+ const markets = this.safeValue (response, 'result', []);
568
+ const allFutures = this.safeValue (allFuturesResponse, 'result', []);
569
+ const allFuturesDict = this.indexBy (allFutures, 'name');
570
+ for (let i = 0; i < markets.length; i++) {
571
+ const market = markets[i];
572
+ const id = this.safeString (market, 'name');
573
+ const future = this.safeValue (allFuturesDict, id);
574
+ const marketType = this.safeString (market, 'type');
575
+ const contract = (marketType === 'future');
576
+ const baseId = this.safeString2 (market, 'baseCurrency', 'underlying');
577
+ const quoteId = this.safeString (market, 'quoteCurrency', 'USD');
578
+ const settleId = contract ? 'USD' : undefined;
579
+ let base = this.safeCurrencyCode (baseId);
580
+ const quote = this.safeCurrencyCode (quoteId);
581
+ const settle = this.safeCurrencyCode (settleId);
582
+ const spot = !contract;
583
+ const margin = !contract;
584
+ const perpetual = this.safeValue (future, 'perpetual', false);
585
+ const swap = perpetual;
586
+ const option = false;
587
+ const isFuture = contract && !swap;
588
+ let expiry = undefined;
589
+ const expiryDatetime = this.safeString (future, 'expiry');
590
+ let type = 'spot';
591
+ let symbol = base + '/' + quote;
592
+ if (swap) {
593
+ type = 'swap';
594
+ symbol = base + '/' + quote + ':' + settle;
595
+ } else if (isFuture) {
596
+ type = 'future';
597
+ expiry = this.parse8601 (expiryDatetime);
598
+ if (expiry === undefined) {
599
+ // it is likely a future that is expiring in this moment
600
+ const options = this.safeValue (this.options, 'fetchMarkets', {});
601
+ const throwOnUndefinedExpiry = this.safeValue (options, 'throwOnUndefinedExpiry', false);
602
+ if (throwOnUndefinedExpiry) {
603
+ throw new BadResponse (this.id + " symbol '" + id + "' is a future contract with an invalid expiry datetime.");
604
+ } else {
605
+ continue;
606
+ }
607
+ }
608
+ const parsedId = id.split ('-');
609
+ const length = parsedId.length;
610
+ if (length > 2) {
611
+ // handling for MOVE contracts
612
+ // BTC-MOVE-2022Q1
613
+ // BTC-MOVE-0106
614
+ // BTC-MOVE-WK-0121
615
+ parsedId.pop ();
616
+ // remove expiry
617
+ // [ 'BTC', 'MOVE' ]
618
+ // [ 'BTC', 'MOVE' ]
619
+ // [ 'BTC', 'MOVE', 'WK' ]
620
+ base = parsedId.join ('-');
621
+ }
622
+ symbol = base + '/' + quote + ':' + settle + '-' + this.yymmdd (expiry, '');
623
+ }
624
+ // check if a market is a spot or future market
625
+ const sizeIncrement = this.safeString (market, 'sizeIncrement');
626
+ const minProvideSize = this.safeString (market, 'minProvideSize');
627
+ let minAmountString = sizeIncrement;
628
+ if (minProvideSize !== undefined) {
629
+ minAmountString = Precise.stringGt (minProvideSize, sizeIncrement) ? sizeIncrement : minProvideSize;
630
+ }
631
+ result.push ({
632
+ 'id': id,
633
+ 'symbol': symbol,
634
+ 'base': base,
635
+ 'quote': quote,
636
+ 'settle': settle,
637
+ 'baseId': baseId,
638
+ 'quoteId': quoteId,
639
+ 'settleId': settleId,
640
+ 'type': type,
641
+ 'spot': spot,
642
+ 'margin': margin,
643
+ 'swap': swap,
644
+ 'future': isFuture,
645
+ 'option': option,
646
+ 'active': this.safeValue (market, 'enabled'),
647
+ 'contract': contract,
648
+ 'linear': contract ? true : undefined,
649
+ 'inverse': contract ? false : undefined,
650
+ 'contractSize': this.parseNumber ('1'),
651
+ 'expiry': expiry,
652
+ 'expiryDatetime': this.iso8601 (expiry),
653
+ 'strike': undefined,
654
+ 'optionType': undefined,
655
+ 'precision': {
656
+ 'amount': this.parseNumber (sizeIncrement),
657
+ 'price': this.safeNumber (market, 'priceIncrement'),
658
+ },
659
+ 'limits': {
660
+ 'leverage': {
661
+ 'min': this.parseNumber ('1'),
662
+ 'max': this.parseNumber ('20'),
663
+ },
664
+ 'amount': {
665
+ 'min': this.parseNumber (minAmountString),
666
+ 'max': undefined,
667
+ },
668
+ 'price': {
669
+ 'min': undefined,
670
+ 'max': undefined,
671
+ },
672
+ 'cost': {
673
+ 'min': undefined,
674
+ 'max': undefined,
675
+ },
676
+ },
677
+ 'info': market,
678
+ });
679
+ }
680
+ return result;
681
+ }
682
+
683
+ parseTicker (ticker, market = undefined) {
684
+ //
685
+ // {
686
+ // "ask":171.29,
687
+ // "baseCurrency":null, // base currency for spot markets
688
+ // "bid":171.24,
689
+ // "change1h":-0.0012244897959183673,
690
+ // "change24h":-0.031603346901854366,
691
+ // "changeBod":-0.03297013492914808,
692
+ // "enabled":true,
693
+ // "last":171.44,
694
+ // "name":"ETH-PERP",
695
+ // "price":171.29,
696
+ // "priceIncrement":0.01,
697
+ // "quoteCurrency":null, // quote currency for spot markets
698
+ // "quoteVolume24h":8570651.12113,
699
+ // "sizeIncrement":0.001,
700
+ // "type":"future",
701
+ // "underlying":"ETH", // null for spot markets
702
+ // "volumeUsd24h":8570651.12113,
703
+ // }
704
+ //
705
+ const marketId = this.safeString (ticker, 'name');
706
+ if (marketId in this.markets_by_id) {
707
+ market = this.markets_by_id[marketId];
708
+ }
709
+ const symbol = this.safeSymbol (marketId, market);
710
+ const last = this.safeString (ticker, 'last');
711
+ const timestamp = this.safeTimestamp (ticker, 'time', this.milliseconds ());
712
+ let percentage = this.safeString (ticker, 'change24h');
713
+ if (percentage !== undefined) {
714
+ percentage = Precise.stringMul (percentage, '100');
715
+ }
716
+ return this.safeTicker ({
717
+ 'symbol': symbol,
718
+ 'timestamp': timestamp,
719
+ 'datetime': this.iso8601 (timestamp),
720
+ 'high': this.safeString (ticker, 'high'),
721
+ 'low': this.safeString (ticker, 'low'),
722
+ 'bid': this.safeString (ticker, 'bid'),
723
+ 'bidVolume': this.safeString (ticker, 'bidSize'),
724
+ 'ask': this.safeString (ticker, 'ask'),
725
+ 'askVolume': this.safeString (ticker, 'askSize'),
726
+ 'vwap': undefined,
727
+ 'open': undefined,
728
+ 'close': last,
729
+ 'last': last,
730
+ 'previousClose': undefined,
731
+ 'change': undefined,
732
+ 'percentage': percentage,
733
+ 'average': undefined,
734
+ 'baseVolume': undefined,
735
+ 'quoteVolume': this.safeString (ticker, 'quoteVolume24h'),
736
+ 'info': ticker,
737
+ }, market, false);
738
+ }
739
+
740
+ async fetchTicker (symbol, params = {}) {
741
+ await this.loadMarkets ();
742
+ const market = this.market (symbol);
743
+ const request = {
744
+ 'market_name': market['id'],
745
+ };
746
+ const response = await this.publicGetMarketsMarketName (this.extend (request, params));
747
+ //
748
+ // {
749
+ // "success":true,
750
+ // "result":{
751
+ // "ask":171.29,
752
+ // "baseCurrency":null, // base currency for spot markets
753
+ // "bid":171.24,
754
+ // "change1h":-0.0012244897959183673,
755
+ // "change24h":-0.031603346901854366,
756
+ // "changeBod":-0.03297013492914808,
757
+ // "enabled":true,
758
+ // "last":171.44,
759
+ // "name":"ETH-PERP",
760
+ // "price":171.29,
761
+ // "priceIncrement":0.01,
762
+ // "quoteCurrency":null, // quote currency for spot markets
763
+ // "quoteVolume24h":8570651.12113,
764
+ // "sizeIncrement":0.001,
765
+ // "type":"future",
766
+ // "underlying":"ETH", // null for spot markets
767
+ // "volumeUsd24h":8570651.12113,
768
+ // }
769
+ // }
770
+ //
771
+ const result = this.safeValue (response, 'result', {});
772
+ return this.parseTicker (result, market);
773
+ }
774
+
775
+ async fetchTickers (symbols = undefined, params = {}) {
776
+ await this.loadMarkets ();
777
+ const response = await this.publicGetMarkets (params);
778
+ //
779
+ // {
780
+ // 'success': true,
781
+ // "result": [
782
+ // {
783
+ // "ask":170.44,
784
+ // "baseCurrency":"ETH",
785
+ // "bid":170.41,
786
+ // "change1h":-0.018485459257126403,
787
+ // "change24h":-0.023825887743413515,
788
+ // "changeBod":-0.037605872388481086,
789
+ // "enabled":true,
790
+ // "last":172.72,
791
+ // "name":"ETH/USD",
792
+ // "price":170.44,
793
+ // "priceIncrement":0.01,
794
+ // "quoteCurrency":"USD",
795
+ // "quoteVolume24h":382802.0252,
796
+ // "sizeIncrement":0.001,
797
+ // "type":"spot",
798
+ // "underlying":null,
799
+ // "volumeUsd24h":382802.0252
800
+ // },
801
+ // ],
802
+ // }
803
+ //
804
+ const tickers = this.safeValue (response, 'result', []);
805
+ return this.parseTickers (tickers, symbols);
806
+ }
807
+
808
+ async fetchOrderBook (symbol, limit = undefined, params = {}) {
809
+ await this.loadMarkets ();
810
+ const market = this.market (symbol);
811
+ const request = {
812
+ 'market_name': market['id'],
813
+ };
814
+ if (limit !== undefined) {
815
+ request['depth'] = limit; // max 100, default 20
816
+ }
817
+ const response = await this.publicGetMarketsMarketNameOrderbook (this.extend (request, params));
818
+ //
819
+ // {
820
+ // "success":true,
821
+ // "result":{
822
+ // "asks":[
823
+ // [171.95,279.865],
824
+ // [171.98,102.42],
825
+ // [171.99,124.11],
826
+ // ],
827
+ // "bids":[
828
+ // [171.93,69.749],
829
+ // [171.9,288.325],
830
+ // [171.88,87.47],
831
+ // ],
832
+ // }
833
+ // }
834
+ //
835
+ const result = this.safeValue (response, 'result', {});
836
+ return this.parseOrderBook (result, symbol);
837
+ }
838
+
839
+ parseOHLCV (ohlcv, market = undefined) {
840
+ //
841
+ // {
842
+ // "close":177.23,
843
+ // "high":177.45,
844
+ // "low":177.2,
845
+ // "open":177.43,
846
+ // "startTime":"2019-10-17T13:27:00+00:00",
847
+ // "time":1571318820000.0,
848
+ // "volume":0.0
849
+ // }
850
+ //
851
+ return [
852
+ this.safeInteger (ohlcv, 'time'),
853
+ this.safeNumber (ohlcv, 'open'),
854
+ this.safeNumber (ohlcv, 'high'),
855
+ this.safeNumber (ohlcv, 'low'),
856
+ this.safeNumber (ohlcv, 'close'),
857
+ this.safeNumber (ohlcv, 'volume'),
858
+ ];
859
+ }
860
+
861
+ getMarketId (symbol, key, params = {}) {
862
+ const parts = this.getMarketParams (symbol, key, params);
863
+ return this.safeString (parts, 1, symbol);
864
+ }
865
+
866
+ getMarketParams (symbol, key, params = {}) {
867
+ let market = undefined;
868
+ let marketId = undefined;
869
+ if (symbol in this.markets) {
870
+ market = this.market (symbol);
871
+ marketId = market['id'];
872
+ } else {
873
+ marketId = this.safeString (params, key, symbol);
874
+ }
875
+ return [ market, marketId ];
876
+ }
877
+
878
+ async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
879
+ await this.loadMarkets ();
880
+ const [ market, marketId ] = this.getMarketParams (symbol, 'market_name', params);
881
+ // max 1501 candles, including the current candle when since is not specified
882
+ const maxLimit = 5000;
883
+ const defaultLimit = 1500;
884
+ limit = (limit === undefined) ? defaultLimit : Math.min (limit, maxLimit);
885
+ const request = {
886
+ 'resolution': this.timeframes[timeframe],
887
+ 'market_name': marketId,
888
+ // 'start_time': parseInt (since / 1000),
889
+ // 'end_time': this.seconds (),
890
+ 'limit': limit,
891
+ };
892
+ const price = this.safeString (params, 'price');
893
+ params = this.omit (params, 'price');
894
+ if (since !== undefined) {
895
+ const startTime = parseInt (since / 1000);
896
+ request['start_time'] = startTime;
897
+ const duration = this.parseTimeframe (timeframe);
898
+ const endTime = this.sum (startTime, limit * duration);
899
+ request['end_time'] = Math.min (endTime, this.seconds ());
900
+ if (duration > 86400) {
901
+ const wholeDaysInTimeframe = parseInt (duration / 86400);
902
+ request['limit'] = Math.min (limit * wholeDaysInTimeframe, maxLimit);
903
+ }
904
+ }
905
+ let method = 'publicGetMarketsMarketNameCandles';
906
+ if (price === 'index') {
907
+ if (symbol in this.markets) {
908
+ request['market_name'] = market['baseId'];
909
+ }
910
+ method = 'publicGetIndexesMarketNameCandles';
911
+ }
912
+ const response = await this[method] (this.extend (request, params));
913
+ //
914
+ // {
915
+ // "success": true,
916
+ // "result":[
917
+ // {
918
+ // "close":177.23,
919
+ // "high":177.45,
920
+ // "low":177.2,
921
+ // "open":177.43,
922
+ // "startTime":"2019-10-17T13:27:00+00:00",
923
+ // "time":1571318820000.0,
924
+ // "volume":0.0
925
+ // },
926
+ // {
927
+ // "close":177.26,
928
+ // "high":177.33,
929
+ // "low":177.23,
930
+ // "open":177.23,
931
+ // "startTime":"2019-10-17T13:28:00+00:00",
932
+ // "time":1571318880000.0,
933
+ // "volume":0.0
934
+ // },
935
+ // ],
936
+ // }
937
+ //
938
+ const result = this.safeValue (response, 'result', []);
939
+ return this.parseOHLCVs (result, market, timeframe, since, limit);
940
+ }
941
+
942
+ async fetchIndexOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
943
+ const request = {
944
+ 'price': 'index',
945
+ };
946
+ return await this.fetchOHLCV (symbol, timeframe, since, limit, this.extend (request, params));
947
+ }
948
+
949
+ parseTrade (trade, market = undefined) {
950
+ //
951
+ // fetchTrades (public)
952
+ //
953
+ // {
954
+ // "id":1715826,
955
+ // "liquidation":false,
956
+ // "price":171.62,
957
+ // "side":"buy",
958
+ // "size":2.095,
959
+ // "time":"2019-10-18T12:59:54.288166+00:00"
960
+ // }
961
+ //
962
+ // fetchMyTrades (private)
963
+ //
964
+ // {
965
+ // "fee": 20.1374935,
966
+ // "feeRate": 0.0005,
967
+ // "feeCurrency": "USD",
968
+ // "future": "EOS-0329",
969
+ // "id": 11215,
970
+ // "liquidity": "taker",
971
+ // "market": "EOS-0329",
972
+ // "baseCurrency": null,
973
+ // "quoteCurrency": null,
974
+ // "orderId": 8436981,
975
+ // "price": 4.201,
976
+ // "side": "buy",
977
+ // "size": 9587,
978
+ // "time": "2019-03-27T19:15:10.204619+00:00",
979
+ // "type": "order"
980
+ // }
981
+ //
982
+ // {
983
+ // "baseCurrency": "BTC",
984
+ // "fee": 0,
985
+ // "feeCurrency": "USD",
986
+ // "feeRate": 0,
987
+ // "future": null,
988
+ // "id": 664079556,
989
+ // "liquidity": "taker",
990
+ // "market": null,
991
+ // "orderId": null,
992
+ // "price": 34830.61359,
993
+ // "quoteCurrency": "USD",
994
+ // "side": "sell",
995
+ // "size": 0.0005996,
996
+ // "time": "2021-01-15T16:05:29.246135+00:00",
997
+ // "tradeId": null,
998
+ // "type": "otc"
999
+ // }
1000
+ //
1001
+ // with -ve fee
1002
+ // {
1003
+ // "id": 1171258927,
1004
+ // "fee": -0.0000713875,
1005
+ // "side": "sell",
1006
+ // "size": 1,
1007
+ // "time": "2021-03-11T13:34:35.523627+00:00",
1008
+ // "type": "order",
1009
+ // "price": 14.2775,
1010
+ // "future": null,
1011
+ // "market": "SOL/USD",
1012
+ // "feeRate": -0.000005,
1013
+ // "orderId": 33182929044,
1014
+ // "tradeId": 582936801,
1015
+ // "liquidity": "maker",
1016
+ // "feeCurrency": "USD",
1017
+ // "baseCurrency": "SOL",
1018
+ // "quoteCurrency": "USD"
1019
+ // }
1020
+ //
1021
+ // // from OTC order
1022
+ // {
1023
+ // "id": 1172129651,
1024
+ // "fee": 0,
1025
+ // "side": "sell",
1026
+ // "size": 1.47568846,
1027
+ // "time": "2021-03-11T15:04:46.893383+00:00",
1028
+ // "type": "otc",
1029
+ // "price": 14.60932598,
1030
+ // "future": null,
1031
+ // "market": null,
1032
+ // "feeRate": 0,
1033
+ // "orderId": null,
1034
+ // "tradeId": null,
1035
+ // "liquidity": "taker",
1036
+ // "feeCurrency": "USD",
1037
+ // "baseCurrency": "BCHA",
1038
+ // "quoteCurrency": "USD"
1039
+ // }
1040
+ //
1041
+ const id = this.safeString (trade, 'id');
1042
+ const takerOrMaker = this.safeString (trade, 'liquidity');
1043
+ // a workaround for the OTC trades, they don't have a symbol
1044
+ const baseId = this.safeString (trade, 'baseCurrency');
1045
+ const quoteId = this.safeString (trade, 'quoteCurrency');
1046
+ let defaultMarketId = undefined;
1047
+ if ((baseId !== undefined) && (quoteId !== undefined)) {
1048
+ defaultMarketId = baseId + '/' + quoteId;
1049
+ }
1050
+ const marketId = this.safeString (trade, 'market', defaultMarketId);
1051
+ market = this.safeMarket (marketId, market, '/');
1052
+ const symbol = market['symbol'];
1053
+ const timestamp = this.parse8601 (this.safeString (trade, 'time'));
1054
+ const priceString = this.safeString (trade, 'price');
1055
+ const amountString = this.safeString (trade, 'size');
1056
+ const side = this.safeString (trade, 'side');
1057
+ let fee = undefined;
1058
+ const feeCostString = this.safeString (trade, 'fee');
1059
+ if (feeCostString !== undefined) {
1060
+ const feeCurrencyId = this.safeString (trade, 'feeCurrency');
1061
+ const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId);
1062
+ fee = {
1063
+ 'cost': feeCostString,
1064
+ 'currency': feeCurrencyCode,
1065
+ 'rate': this.safeString (trade, 'feeRate'),
1066
+ };
1067
+ }
1068
+ const orderId = this.safeString (trade, 'orderId');
1069
+ return this.safeTrade ({
1070
+ 'info': trade,
1071
+ 'timestamp': timestamp,
1072
+ 'datetime': this.iso8601 (timestamp),
1073
+ 'symbol': symbol,
1074
+ 'id': id,
1075
+ 'order': orderId,
1076
+ 'type': undefined,
1077
+ 'takerOrMaker': takerOrMaker,
1078
+ 'side': side,
1079
+ 'price': priceString,
1080
+ 'amount': amountString,
1081
+ 'cost': undefined,
1082
+ 'fee': fee,
1083
+ }, market);
1084
+ }
1085
+
1086
+ async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
1087
+ await this.loadMarkets ();
1088
+ const [ market, marketId ] = this.getMarketParams (symbol, 'market_name', params);
1089
+ const request = {
1090
+ 'market_name': marketId,
1091
+ };
1092
+ if (since !== undefined) {
1093
+ // the exchange aligns results to end_time returning 5000 trades max
1094
+ // the user must set the end_time (in seconds) close enough to start_time
1095
+ // for a proper pagination, fetch the most recent trades first
1096
+ // then set the end_time parameter to the timestamp of the last trade
1097
+ // start_time and end_time must be in seconds, divided by a thousand
1098
+ request['start_time'] = parseInt (since / 1000);
1099
+ // start_time doesn't work without end_time
1100
+ request['end_time'] = this.seconds ();
1101
+ }
1102
+ if (limit !== undefined) {
1103
+ request['limit'] = limit;
1104
+ }
1105
+ const response = await this.publicGetMarketsMarketNameTrades (this.extend (request, params));
1106
+ //
1107
+ // {
1108
+ // "success":true,
1109
+ // "result":[
1110
+ // {
1111
+ // "id":1715826,
1112
+ // "liquidation":false,
1113
+ // "price":171.62,
1114
+ // "side":"buy",
1115
+ // "size":2.095,
1116
+ // "time":"2019-10-18T12:59:54.288166+00:00"
1117
+ // },
1118
+ // {
1119
+ // "id":1715763,
1120
+ // "liquidation":false,
1121
+ // "price":171.89,
1122
+ // "side":"sell",
1123
+ // "size":1.477,
1124
+ // "time":"2019-10-18T12:58:38.443734+00:00"
1125
+ // },
1126
+ // ],
1127
+ // }
1128
+ //
1129
+ const result = this.safeValue (response, 'result', []);
1130
+ return this.parseTrades (result, market, since, limit);
1131
+ }
1132
+
1133
+ async fetchTradingFees (params = {}) {
1134
+ await this.loadMarkets ();
1135
+ const response = await this.privateGetAccount (params);
1136
+ //
1137
+ // {
1138
+ // "success": true,
1139
+ // "result": {
1140
+ // "backstopProvider": true,
1141
+ // "collateral": 3568181.02691129,
1142
+ // "freeCollateral": 1786071.456884368,
1143
+ // "initialMarginRequirement": 0.12222384240257728,
1144
+ // "liquidating": false,
1145
+ // "maintenanceMarginRequirement": 0.07177992558058484,
1146
+ // "makerFee": 0.0002,
1147
+ // "marginFraction": 0.5588433331419503,
1148
+ // "openMarginFraction": 0.2447194090423075,
1149
+ // "takerFee": 0.0005,
1150
+ // "totalAccountValue": 3568180.98341129,
1151
+ // "totalPositionSize": 6384939.6992,
1152
+ // "username": "user@domain.com",
1153
+ // "positions": [
1154
+ // {
1155
+ // "cost": -31.7906,
1156
+ // "entryPrice": 138.22,
1157
+ // "future": "ETH-PERP",
1158
+ // "initialMarginRequirement": 0.1,
1159
+ // "longOrderSize": 1744.55,
1160
+ // "maintenanceMarginRequirement": 0.04,
1161
+ // "netSize": -0.23,
1162
+ // "openSize": 1744.32,
1163
+ // "realizedPnl": 3.39441714,
1164
+ // "shortOrderSize": 1732.09,
1165
+ // "side": "sell",
1166
+ // "size": 0.23,
1167
+ // "unrealizedPnl": 0,
1168
+ // },
1169
+ // ],
1170
+ // },
1171
+ // }
1172
+ //
1173
+ const result = this.safeValue (response, 'result', {});
1174
+ const maker = this.safeNumber (result, 'makerFee');
1175
+ const taker = this.safeNumber (result, 'takerFee');
1176
+ const tradingFees = {};
1177
+ for (let i = 0; i < this.symbols.length; i++) {
1178
+ const symbol = this.symbols[i];
1179
+ tradingFees[symbol] = {
1180
+ 'info': response,
1181
+ 'symbol': symbol,
1182
+ 'maker': maker,
1183
+ 'taker': taker,
1184
+ 'percentage': true,
1185
+ 'tierBased': true,
1186
+ };
1187
+ }
1188
+ return tradingFees;
1189
+ }
1190
+
1191
+ async fetchFundingRateHistory (symbol = undefined, since = undefined, limit = undefined, params = {}) {
1192
+ //
1193
+ // Gets a history of funding rates with their timestamps
1194
+ // (param) symbol: Future currency pair (e.g. "BTC-PERP")
1195
+ // (param) limit: Not used by ftx
1196
+ // (param) since: Unix timestamp in miliseconds for the time of the earliest requested funding rate
1197
+ // (param) params: Object containing more params for the request
1198
+ // - until: Unix timestamp in miliseconds for the time of the earliest requested funding rate
1199
+ // return: [{symbol, fundingRate, timestamp}]
1200
+ //
1201
+ await this.loadMarkets ();
1202
+ const request = {};
1203
+ if (symbol !== undefined) {
1204
+ const market = this.market (symbol);
1205
+ symbol = market['symbol'];
1206
+ request['future'] = market['id'];
1207
+ }
1208
+ if (since !== undefined) {
1209
+ request['start_time'] = parseInt (since / 1000);
1210
+ }
1211
+ const till = this.safeInteger (params, 'till'); // unified in milliseconds
1212
+ const endTime = this.safeString (params, 'end_time'); // exchange-specific in seconds
1213
+ params = this.omit (params, [ 'end_time', 'till' ]);
1214
+ if (till !== undefined) {
1215
+ request['end_time'] = parseInt (till / 1000);
1216
+ } else if (endTime !== undefined) {
1217
+ request['end_time'] = endTime;
1218
+ }
1219
+ const response = await this.publicGetFundingRates (this.extend (request, params));
1220
+ //
1221
+ // {
1222
+ // "success": true,
1223
+ // "result": [
1224
+ // {
1225
+ // "future": "BTC-PERP",
1226
+ // "rate": 0.0025,
1227
+ // "time": "2019-06-02T08:00:00+00:00"
1228
+ // }
1229
+ // ]
1230
+ // }
1231
+ //
1232
+ const result = this.safeValue (response, 'result');
1233
+ const rates = [];
1234
+ for (let i = 0; i < result.length; i++) {
1235
+ const entry = result[i];
1236
+ const marketId = this.safeString (entry, 'future');
1237
+ const timestamp = this.parse8601 (this.safeString (result[i], 'time'));
1238
+ rates.push ({
1239
+ 'info': entry,
1240
+ 'symbol': this.safeSymbol (marketId),
1241
+ 'fundingRate': this.safeNumber (entry, 'rate'),
1242
+ 'timestamp': timestamp,
1243
+ 'datetime': this.iso8601 (timestamp),
1244
+ });
1245
+ }
1246
+ const sorted = this.sortBy (rates, 'timestamp');
1247
+ return this.filterBySymbolSinceLimit (sorted, symbol, since, limit);
1248
+ }
1249
+
1250
+ parseBalance (response) {
1251
+ const result = {
1252
+ 'info': response,
1253
+ };
1254
+ const balances = this.safeValue (response, 'result', []);
1255
+ for (let i = 0; i < balances.length; i++) {
1256
+ const balance = balances[i];
1257
+ const code = this.safeCurrencyCode (this.safeString (balance, 'coin'));
1258
+ const account = this.account ();
1259
+ account['free'] = this.safeString2 (balance, 'availableWithoutBorrow', 'free');
1260
+ account['total'] = this.safeString (balance, 'total');
1261
+ result[code] = account;
1262
+ }
1263
+ return this.safeBalance (result);
1264
+ }
1265
+
1266
+ async fetchBalance (params = {}) {
1267
+ await this.loadMarkets ();
1268
+ const response = await this.privateGetWalletBalances (params);
1269
+ //
1270
+ // {
1271
+ // "success": true,
1272
+ // "result": [
1273
+ // {
1274
+ // "coin": "USDTBEAR",
1275
+ // "free": 2320.2,
1276
+ // "total": 2340.2
1277
+ // },
1278
+ // ],
1279
+ // }
1280
+ //
1281
+ return this.parseBalance (response);
1282
+ }
1283
+
1284
+ parseOrderStatus (status) {
1285
+ const statuses = {
1286
+ 'new': 'open',
1287
+ 'open': 'open',
1288
+ 'closed': 'closed', // filled or canceled
1289
+ 'triggered': 'closed',
1290
+ };
1291
+ return this.safeString (statuses, status, status);
1292
+ }
1293
+
1294
+ parseOrder (order, market = undefined) {
1295
+ //
1296
+ // limit orders - fetchOrder, fetchOrders, fetchOpenOrders, createOrder, editOrder
1297
+ //
1298
+ // {
1299
+ // "createdAt": "2019-03-05T09:56:55.728933+00:00",
1300
+ // "filledSize": 0,
1301
+ // "future": "XRP-PERP",
1302
+ // "id": 9596912,
1303
+ // "market": "XRP-PERP",
1304
+ // "price": 0.306525,
1305
+ // "remainingSize": 31431,
1306
+ // "side": "sell",
1307
+ // "size": 31431,
1308
+ // "status": "open",
1309
+ // "type": "limit",
1310
+ // "reduceOnly": false,
1311
+ // "ioc": false,
1312
+ // "postOnly": false,
1313
+ // "clientId": null,
1314
+ // }
1315
+ //
1316
+ // market orders - fetchOrder, fetchOrders, fetchOpenOrders, createOrder
1317
+ //
1318
+ // {
1319
+ // "avgFillPrice": 2666.0,
1320
+ // "clientId": None,
1321
+ // "createdAt": "2020-02-12T00: 53: 49.009726+00: 00",
1322
+ // "filledSize": 0.0007,
1323
+ // "future": None,
1324
+ // "id": 3109208514,
1325
+ // "ioc": True,
1326
+ // "market": "BNBBULL/USD",
1327
+ // "postOnly": False,
1328
+ // "price": None,
1329
+ // "reduceOnly": False,
1330
+ // "remainingSize": 0.0,
1331
+ // "side": "buy",
1332
+ // "size": 0.0007,
1333
+ // "status": "closed",
1334
+ // "type": "market"
1335
+ // }
1336
+ //
1337
+ // createOrder (conditional, "stop", "trailingStop", or "takeProfit")
1338
+ //
1339
+ // {
1340
+ // "createdAt": "2019-03-05T09:56:55.728933+00:00",
1341
+ // "future": "XRP-PERP",
1342
+ // "id": 9596912,
1343
+ // "market": "XRP-PERP",
1344
+ // "triggerPrice": 0.306525,
1345
+ // "orderId": null,
1346
+ // "side": "sell",
1347
+ // "size": 31431,
1348
+ // "status": "open",
1349
+ // "type": "stop",
1350
+ // "orderPrice": null,
1351
+ // "error": null,
1352
+ // "triggeredAt": null,
1353
+ // "reduceOnly": false
1354
+ // }
1355
+ //
1356
+ // editOrder (conditional, stop, trailing stop, take profit)
1357
+ //
1358
+ // {
1359
+ // "createdAt": "2019-03-05T09:56:55.728933+00:00",
1360
+ // "future": "XRP-PERP",
1361
+ // "id": 9596912,
1362
+ // "market": "XRP-PERP",
1363
+ // "triggerPrice": 0.306225,
1364
+ // "orderId": null,
1365
+ // "side": "sell",
1366
+ // "size": 31431,
1367
+ // "status": "open",
1368
+ // "type": "stop",
1369
+ // "orderPrice": null,
1370
+ // "error": null,
1371
+ // "triggeredAt": null,
1372
+ // "reduceOnly": false,
1373
+ // "orderType": "market",
1374
+ // "filledSize": 0,
1375
+ // "avgFillPrice": null,
1376
+ // "retryUntilFilled": false
1377
+ // }
1378
+ //
1379
+ // canceled order with a closed status
1380
+ //
1381
+ // {
1382
+ // "avgFillPrice":null,
1383
+ // "clientId":null,
1384
+ // "createdAt":"2020-09-01T13:45:57.119695+00:00",
1385
+ // "filledSize":0.0,
1386
+ // "future":null,
1387
+ // "id":8553541288,
1388
+ // "ioc":false,
1389
+ // "liquidation":false,
1390
+ // "market":"XRP/USDT",
1391
+ // "postOnly":false,
1392
+ // "price":0.5,
1393
+ // "reduceOnly":false,
1394
+ // "remainingSize":0.0,
1395
+ // "side":"sell",
1396
+ // "size":46.0,
1397
+ // "status":"closed",
1398
+ // "type":"limit"
1399
+ // }
1400
+ //
1401
+ const id = this.safeString (order, 'id');
1402
+ const timestamp = this.parse8601 (this.safeString (order, 'createdAt'));
1403
+ let status = this.parseOrderStatus (this.safeString (order, 'status'));
1404
+ const amount = this.safeString (order, 'size');
1405
+ const filled = this.safeString (order, 'filledSize');
1406
+ let remaining = this.safeString (order, 'remainingSize');
1407
+ if (Precise.stringEquals (remaining, '0')) {
1408
+ remaining = Precise.stringSub (amount, filled);
1409
+ if (Precise.stringGt (remaining, '0')) {
1410
+ status = 'canceled';
1411
+ }
1412
+ }
1413
+ const marketId = this.safeString (order, 'market');
1414
+ market = this.safeMarket (marketId, market);
1415
+ let symbol = market['symbol'];
1416
+ if (symbol === undefined) {
1417
+ // support for delisted market ids
1418
+ // https://github.com/ccxt/ccxt/issues/7113
1419
+ symbol = marketId;
1420
+ }
1421
+ const side = this.safeString (order, 'side');
1422
+ const type = this.safeString (order, 'type');
1423
+ const average = this.safeString (order, 'avgFillPrice');
1424
+ const price = this.safeString2 (order, 'price', 'triggerPrice', average);
1425
+ const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'triggeredAt'));
1426
+ const clientOrderId = this.safeString (order, 'clientId');
1427
+ const stopPrice = this.safeNumber (order, 'triggerPrice');
1428
+ const postOnly = this.safeValue (order, 'postOnly');
1429
+ return this.safeOrder ({
1430
+ 'info': order,
1431
+ 'id': id,
1432
+ 'clientOrderId': clientOrderId,
1433
+ 'timestamp': timestamp,
1434
+ 'datetime': this.iso8601 (timestamp),
1435
+ 'lastTradeTimestamp': lastTradeTimestamp,
1436
+ 'symbol': symbol,
1437
+ 'type': type,
1438
+ 'timeInForce': undefined,
1439
+ 'postOnly': postOnly,
1440
+ 'side': side,
1441
+ 'price': price,
1442
+ 'stopPrice': stopPrice,
1443
+ 'amount': amount,
1444
+ 'cost': undefined,
1445
+ 'average': average,
1446
+ 'filled': filled,
1447
+ 'remaining': remaining,
1448
+ 'status': status,
1449
+ 'fee': undefined,
1450
+ 'trades': undefined,
1451
+ }, market);
1452
+ }
1453
+
1454
+ async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
1455
+ await this.loadMarkets ();
1456
+ const market = this.market (symbol);
1457
+ const request = {
1458
+ 'market': market['id'],
1459
+ 'side': side, // "buy" or "sell"
1460
+ // 'price': 0.306525, // send null for market orders
1461
+ 'type': type, // "limit", "market", "stop", "trailingStop", or "takeProfit"
1462
+ 'size': parseFloat (this.amountToPrecision (symbol, amount)),
1463
+ // 'reduceOnly': false, // optional, default is false
1464
+ // 'ioc': false, // optional, default is false, limit or market orders only
1465
+ // 'postOnly': false, // optional, default is false, limit or market orders only
1466
+ // 'clientId': 'abcdef0123456789', // string, optional, client order id, limit or market orders only
1467
+ };
1468
+ const clientOrderId = this.safeString2 (params, 'clientId', 'clientOrderId');
1469
+ if (clientOrderId !== undefined) {
1470
+ request['clientId'] = clientOrderId;
1471
+ params = this.omit (params, [ 'clientId', 'clientOrderId' ]);
1472
+ }
1473
+ let method = undefined;
1474
+ if (type === 'limit') {
1475
+ method = 'privatePostOrders';
1476
+ request['price'] = parseFloat (this.priceToPrecision (symbol, price));
1477
+ } else if (type === 'market') {
1478
+ method = 'privatePostOrders';
1479
+ request['price'] = null;
1480
+ } else if ((type === 'stop') || (type === 'takeProfit')) {
1481
+ method = 'privatePostConditionalOrders';
1482
+ const stopPrice = this.safeNumber2 (params, 'stopPrice', 'triggerPrice');
1483
+ if (stopPrice === undefined) {
1484
+ throw new ArgumentsRequired (this.id + ' createOrder() requires a stopPrice parameter or a triggerPrice parameter for ' + type + ' orders');
1485
+ } else {
1486
+ params = this.omit (params, [ 'stopPrice', 'triggerPrice' ]);
1487
+ request['triggerPrice'] = parseFloat (this.priceToPrecision (symbol, stopPrice));
1488
+ }
1489
+ if (price !== undefined) {
1490
+ request['orderPrice'] = parseFloat (this.priceToPrecision (symbol, price)); // optional, order type is limit if this is specified, otherwise market
1491
+ }
1492
+ } else if (type === 'trailingStop') {
1493
+ const trailValue = this.safeNumber (params, 'trailValue', price);
1494
+ if (trailValue === undefined) {
1495
+ throw new ArgumentsRequired (this.id + ' createOrder() requires a trailValue parameter or a price argument (negative or positive) for a ' + type + ' order');
1496
+ }
1497
+ method = 'privatePostConditionalOrders';
1498
+ request['trailValue'] = parseFloat (this.priceToPrecision (symbol, trailValue)); // negative for "sell", positive for "buy"
1499
+ } else {
1500
+ throw new InvalidOrder (this.id + ' createOrder() does not support order type ' + type + ', only limit, market, stop, trailingStop, or takeProfit orders are supported');
1501
+ }
1502
+ const response = await this[method] (this.extend (request, params));
1503
+ //
1504
+ // orders
1505
+ //
1506
+ // {
1507
+ // "success": true,
1508
+ // "result": [
1509
+ // {
1510
+ // "createdAt": "2019-03-05T09:56:55.728933+00:00",
1511
+ // "filledSize": 0,
1512
+ // "future": "XRP-PERP",
1513
+ // "id": 9596912,
1514
+ // "market": "XRP-PERP",
1515
+ // "price": 0.306525,
1516
+ // "remainingSize": 31431,
1517
+ // "side": "sell",
1518
+ // "size": 31431,
1519
+ // "status": "open",
1520
+ // "type": "limit",
1521
+ // "reduceOnly": false,
1522
+ // "ioc": false,
1523
+ // "postOnly": false,
1524
+ // "clientId": null,
1525
+ // }
1526
+ // ]
1527
+ // }
1528
+ //
1529
+ // conditional orders
1530
+ //
1531
+ // {
1532
+ // "success": true,
1533
+ // "result": [
1534
+ // {
1535
+ // "createdAt": "2019-03-05T09:56:55.728933+00:00",
1536
+ // "future": "XRP-PERP",
1537
+ // "id": 9596912,
1538
+ // "market": "XRP-PERP",
1539
+ // "triggerPrice": 0.306525,
1540
+ // "orderId": null,
1541
+ // "side": "sell",
1542
+ // "size": 31431,
1543
+ // "status": "open",
1544
+ // "type": "stop",
1545
+ // "orderPrice": null,
1546
+ // "error": null,
1547
+ // "triggeredAt": null,
1548
+ // "reduceOnly": false
1549
+ // }
1550
+ // ]
1551
+ // }
1552
+ //
1553
+ //
1554
+ const result = this.safeValue (response, 'result', []);
1555
+ return this.parseOrder (result, market);
1556
+ }
1557
+
1558
+ async createReduceOnlyOrder (symbol, type, side, amount, price = undefined, params = {}) {
1559
+ const request = {
1560
+ 'reduceOnly': true,
1561
+ };
1562
+ return await this.createOrder (symbol, type, side, amount, price, this.extend (request, params));
1563
+ }
1564
+
1565
+ async editOrder (id, symbol, type, side, amount, price = undefined, params = {}) {
1566
+ await this.loadMarkets ();
1567
+ const market = this.market (symbol);
1568
+ const request = {};
1569
+ let method = undefined;
1570
+ const clientOrderId = this.safeString2 (params, 'client_order_id', 'clientOrderId');
1571
+ const triggerPrice = this.safeNumber (params, 'triggerPrice');
1572
+ const orderPrice = this.safeNumber (params, 'orderPrice');
1573
+ const trailValue = this.safeNumber (params, 'trailValue');
1574
+ params = this.omit (params, [ 'client_order_id', 'clientOrderId', 'triggerPrice', 'orderPrice', 'trailValue' ]);
1575
+ const triggerPriceIsDefined = (triggerPrice !== undefined);
1576
+ const orderPriceIsDefined = (orderPrice !== undefined);
1577
+ const trailValueIsDefined = (trailValue !== undefined);
1578
+ if (triggerPriceIsDefined || orderPriceIsDefined || trailValueIsDefined) {
1579
+ method = 'privatePostConditionalOrdersOrderIdModify';
1580
+ request['order_id'] = id;
1581
+ if (triggerPriceIsDefined) {
1582
+ request['triggerPrice'] = parseFloat (this.priceToPrecision (symbol, triggerPrice));
1583
+ }
1584
+ if (orderPriceIsDefined) {
1585
+ // only for stop limit or take profit limit orders
1586
+ request['orderPrice'] = parseFloat (this.priceToPrecision (symbol, orderPrice));
1587
+ }
1588
+ if (trailValueIsDefined) {
1589
+ // negative for sell orders, positive for buy orders
1590
+ request['trailValue'] = parseFloat (this.priceToPrecision (symbol, trailValue));
1591
+ }
1592
+ } else {
1593
+ if (clientOrderId === undefined) {
1594
+ method = 'privatePostOrdersOrderIdModify';
1595
+ request['order_id'] = id;
1596
+ } else {
1597
+ method = 'privatePostOrdersByClientIdClientOrderIdModify';
1598
+ request['client_order_id'] = clientOrderId;
1599
+ // request['clientId'] = clientOrderId;
1600
+ }
1601
+ if (price !== undefined) {
1602
+ request['price'] = parseFloat (this.priceToPrecision (symbol, price));
1603
+ }
1604
+ }
1605
+ if (amount !== undefined) {
1606
+ request['size'] = parseFloat (this.amountToPrecision (symbol, amount));
1607
+ }
1608
+ const response = await this[method] (this.extend (request, params));
1609
+ //
1610
+ // regular order
1611
+ //
1612
+ // {
1613
+ // "success": true,
1614
+ // "result": {
1615
+ // "createdAt": "2019-03-05T11:56:55.728933+00:00",
1616
+ // "filledSize": 0,
1617
+ // "future": "XRP-PERP",
1618
+ // "id": 9596932,
1619
+ // "market": "XRP-PERP",
1620
+ // "price": 0.326525,
1621
+ // "remainingSize": 31431,
1622
+ // "side": "sell",
1623
+ // "size": 31431,
1624
+ // "status": "open",
1625
+ // "type": "limit",
1626
+ // "reduceOnly": false,
1627
+ // "ioc": false,
1628
+ // "postOnly": false,
1629
+ // "clientId": null,
1630
+ // }
1631
+ // }
1632
+ //
1633
+ // conditional trigger order
1634
+ //
1635
+ // {
1636
+ // "success": true,
1637
+ // "result": {
1638
+ // "createdAt": "2019-03-05T09:56:55.728933+00:00",
1639
+ // "future": "XRP-PERP",
1640
+ // "id": 9596912,
1641
+ // "market": "XRP-PERP",
1642
+ // "triggerPrice": 0.306225,
1643
+ // "orderId": null,
1644
+ // "side": "sell",
1645
+ // "size": 31431,
1646
+ // "status": "open",
1647
+ // "type": "stop",
1648
+ // "orderPrice": null,
1649
+ // "error": null,
1650
+ // "triggeredAt": null,
1651
+ // "reduceOnly": false,
1652
+ // "orderType": "market",
1653
+ // "filledSize": 0,
1654
+ // "avgFillPrice": null,
1655
+ // "retryUntilFilled": false
1656
+ // }
1657
+ // }
1658
+ //
1659
+ const result = this.safeValue (response, 'result', {});
1660
+ return this.parseOrder (result, market);
1661
+ }
1662
+
1663
+ async cancelOrder (id, symbol = undefined, params = {}) {
1664
+ await this.loadMarkets ();
1665
+ const request = {};
1666
+ // support for canceling conditional orders
1667
+ // https://github.com/ccxt/ccxt/issues/6669
1668
+ const options = this.safeValue (this.options, 'cancelOrder', {});
1669
+ const defaultMethod = this.safeString (options, 'method', 'privateDeleteOrdersOrderId');
1670
+ let method = this.safeString (params, 'method', defaultMethod);
1671
+ const type = this.safeValue (params, 'type');
1672
+ const clientOrderId = this.safeValue2 (params, 'client_order_id', 'clientOrderId');
1673
+ if (clientOrderId === undefined) {
1674
+ request['order_id'] = parseInt (id);
1675
+ if ((type === 'stop') || (type === 'trailingStop') || (type === 'takeProfit')) {
1676
+ method = 'privateDeleteConditionalOrdersOrderId';
1677
+ }
1678
+ } else {
1679
+ request['client_order_id'] = clientOrderId;
1680
+ method = 'privateDeleteOrdersByClientIdClientOrderId';
1681
+ }
1682
+ const query = this.omit (params, [ 'method', 'type', 'client_order_id', 'clientOrderId' ]);
1683
+ const response = await this[method] (this.extend (request, query));
1684
+ //
1685
+ // {
1686
+ // "success": true,
1687
+ // "result": "Order queued for cancelation"
1688
+ // }
1689
+ //
1690
+ const result = this.safeValue (response, 'result', {});
1691
+ return result;
1692
+ }
1693
+
1694
+ async cancelAllOrders (symbol = undefined, params = {}) {
1695
+ await this.loadMarkets ();
1696
+ const request = {
1697
+ // 'market': market['id'], // optional
1698
+ // 'conditionalOrdersOnly': false, // cancel conditional orders only
1699
+ // 'limitOrdersOnly': false, // cancel existing limit orders (non-conditional orders) only
1700
+ };
1701
+ const marketId = this.getMarketId (symbol, 'market', params);
1702
+ if (marketId !== undefined) {
1703
+ request['market'] = marketId;
1704
+ }
1705
+ const response = await this.privateDeleteOrders (this.extend (request, params));
1706
+ const result = this.safeValue (response, 'result', {});
1707
+ //
1708
+ // {
1709
+ // "success": true,
1710
+ // "result": "Orders queued for cancelation"
1711
+ // }
1712
+ //
1713
+ return result;
1714
+ }
1715
+
1716
+ async fetchOrder (id, symbol = undefined, params = {}) {
1717
+ await this.loadMarkets ();
1718
+ const request = {};
1719
+ const clientOrderId = this.safeValue2 (params, 'client_order_id', 'clientOrderId');
1720
+ let method = 'privateGetOrdersOrderId';
1721
+ if (clientOrderId === undefined) {
1722
+ request['order_id'] = id;
1723
+ } else {
1724
+ request['client_order_id'] = clientOrderId;
1725
+ params = this.omit (params, [ 'client_order_id', 'clientOrderId' ]);
1726
+ method = 'privateGetOrdersByClientIdClientOrderId';
1727
+ }
1728
+ const response = await this[method] (this.extend (request, params));
1729
+ //
1730
+ // {
1731
+ // "success": true,
1732
+ // "result": {
1733
+ // "createdAt": "2019-03-05T09:56:55.728933+00:00",
1734
+ // "filledSize": 10,
1735
+ // "future": "XRP-PERP",
1736
+ // "id": 9596912,
1737
+ // "market": "XRP-PERP",
1738
+ // "price": 0.306525,
1739
+ // "avgFillPrice": 0.306526,
1740
+ // "remainingSize": 31421,
1741
+ // "side": "sell",
1742
+ // "size": 31431,
1743
+ // "status": "open",
1744
+ // "type": "limit",
1745
+ // "reduceOnly": false,
1746
+ // "ioc": false,
1747
+ // "postOnly": false,
1748
+ // "clientId": null
1749
+ // }
1750
+ // }
1751
+ //
1752
+ const result = this.safeValue (response, 'result', {});
1753
+ return this.parseOrder (result);
1754
+ }
1755
+
1756
+ async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
1757
+ await this.loadMarkets ();
1758
+ const request = {};
1759
+ const [ market, marketId ] = this.getMarketParams (symbol, 'market', params);
1760
+ if (marketId !== undefined) {
1761
+ request['market'] = marketId;
1762
+ }
1763
+ // support for canceling conditional orders
1764
+ // https://github.com/ccxt/ccxt/issues/6669
1765
+ const options = this.safeValue (this.options, 'fetchOpenOrders', {});
1766
+ const defaultMethod = this.safeString (options, 'method', 'privateGetOrders');
1767
+ let method = this.safeString (params, 'method', defaultMethod);
1768
+ const type = this.safeValue (params, 'type');
1769
+ if ((type === 'stop') || (type === 'trailingStop') || (type === 'takeProfit')) {
1770
+ method = 'privateGetConditionalOrders';
1771
+ }
1772
+ const query = this.omit (params, [ 'method', 'type' ]);
1773
+ const response = await this[method] (this.extend (request, query));
1774
+ //
1775
+ // {
1776
+ // "success": true,
1777
+ // "result": [
1778
+ // {
1779
+ // "createdAt": "2019-03-05T09:56:55.728933+00:00",
1780
+ // "filledSize": 10,
1781
+ // "future": "XRP-PERP",
1782
+ // "id": 9596912,
1783
+ // "market": "XRP-PERP",
1784
+ // "price": 0.306525,
1785
+ // "avgFillPrice": 0.306526,
1786
+ // "remainingSize": 31421,
1787
+ // "side": "sell",
1788
+ // "size": 31431,
1789
+ // "status": "open",
1790
+ // "type": "limit",
1791
+ // "reduceOnly": false,
1792
+ // "ioc": false,
1793
+ // "postOnly": false,
1794
+ // "clientId": null
1795
+ // }
1796
+ // ]
1797
+ // }
1798
+ //
1799
+ const result = this.safeValue (response, 'result', []);
1800
+ return this.parseOrders (result, market, since, limit);
1801
+ }
1802
+
1803
+ async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
1804
+ await this.loadMarkets ();
1805
+ const request = {};
1806
+ const [ market, marketId ] = this.getMarketParams (symbol, 'market', params);
1807
+ if (marketId !== undefined) {
1808
+ request['market'] = marketId;
1809
+ }
1810
+ if (limit !== undefined) {
1811
+ request['limit'] = limit; // default 100, max 100
1812
+ }
1813
+ if (since !== undefined) {
1814
+ request['start_time'] = parseInt (since / 1000);
1815
+ }
1816
+ // support for canceling conditional orders
1817
+ // https://github.com/ccxt/ccxt/issues/6669
1818
+ const options = this.safeValue (this.options, 'fetchOrders', {});
1819
+ const defaultMethod = this.safeString (options, 'method', 'privateGetOrdersHistory');
1820
+ let method = this.safeString (params, 'method', defaultMethod);
1821
+ const type = this.safeValue (params, 'type');
1822
+ if ((type === 'stop') || (type === 'trailingStop') || (type === 'takeProfit')) {
1823
+ method = 'privateGetConditionalOrdersHistory';
1824
+ }
1825
+ const query = this.omit (params, [ 'method', 'type' ]);
1826
+ const response = await this[method] (this.extend (request, query));
1827
+ //
1828
+ // {
1829
+ // "success": true,
1830
+ // "result": [
1831
+ // {
1832
+ // "createdAt": "2019-03-05T09:56:55.728933+00:00",
1833
+ // "filledSize": 10,
1834
+ // "future": "XRP-PERP",
1835
+ // "id": 9596912,
1836
+ // "market": "XRP-PERP",
1837
+ // "price": 0.306525,
1838
+ // "avgFillPrice": 0.306526,
1839
+ // "remainingSize": 31421,
1840
+ // "side": "sell",
1841
+ // "size": 31431,
1842
+ // "status": "open",
1843
+ // "type": "limit",
1844
+ // "reduceOnly": false,
1845
+ // "ioc": false,
1846
+ // "postOnly": false,
1847
+ // "clientId": null
1848
+ // }
1849
+ // ]
1850
+ // }
1851
+ //
1852
+ const result = this.safeValue (response, 'result', []);
1853
+ return this.parseOrders (result, market, since, limit);
1854
+ }
1855
+
1856
+ async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) {
1857
+ const request = {
1858
+ 'orderId': id,
1859
+ };
1860
+ return await this.fetchMyTrades (symbol, since, limit, this.extend (request, params));
1861
+ }
1862
+
1863
+ async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
1864
+ await this.loadMarkets ();
1865
+ const [ market, marketId ] = this.getMarketParams (symbol, 'market', params);
1866
+ const request = {};
1867
+ if (marketId !== undefined) {
1868
+ request['market'] = marketId;
1869
+ }
1870
+ if (since !== undefined) {
1871
+ request['start_time'] = parseInt (since / 1000);
1872
+ request['end_time'] = this.seconds ();
1873
+ }
1874
+ const response = await this.privateGetFills (this.extend (request, params));
1875
+ //
1876
+ // {
1877
+ // "success": true,
1878
+ // "result": [
1879
+ // {
1880
+ // "fee": 20.1374935,
1881
+ // "feeRate": 0.0005,
1882
+ // "future": "EOS-0329",
1883
+ // "id": 11215,
1884
+ // "liquidity": "taker",
1885
+ // "market": "EOS-0329",
1886
+ // "baseCurrency": null,
1887
+ // "quoteCurrency": null,
1888
+ // "orderId": 8436981,
1889
+ // "price": 4.201,
1890
+ // "side": "buy",
1891
+ // "size": 9587,
1892
+ // "time": "2019-03-27T19:15:10.204619+00:00",
1893
+ // "type": "order"
1894
+ // }
1895
+ // ]
1896
+ // }
1897
+ //
1898
+ const trades = this.safeValue (response, 'result', []);
1899
+ return this.parseTrades (trades, market, since, limit);
1900
+ }
1901
+
1902
+ async transfer (code, amount, fromAccount, toAccount, params = {}) {
1903
+ await this.loadMarkets ();
1904
+ const currency = this.currency (code);
1905
+ const request = {
1906
+ 'coin': currency['id'],
1907
+ 'source': fromAccount,
1908
+ 'destination': toAccount,
1909
+ 'size': amount,
1910
+ };
1911
+ const response = await this.privatePostSubaccountsTransfer (this.extend (request, params));
1912
+ //
1913
+ // {
1914
+ // success: true,
1915
+ // result: {
1916
+ // id: '31222278',
1917
+ // coin: 'USDT',
1918
+ // size: '1.0',
1919
+ // time: '2022-04-01T11:18:27.194188+00:00',
1920
+ // notes: 'Transfer from main account to testSubaccount',
1921
+ // status: 'complete'
1922
+ // }
1923
+ // }
1924
+ //
1925
+ const result = this.safeValue (response, 'result', {});
1926
+ return this.parseTransfer (result, currency);
1927
+ }
1928
+
1929
+ parseTransfer (transfer, currency = undefined) {
1930
+ //
1931
+ // {
1932
+ // id: '31222278',
1933
+ // coin: 'USDT',
1934
+ // size: '1.0',
1935
+ // time: '2022-04-01T11:18:27.194188+00:00',
1936
+ // notes: 'Transfer from main account to testSubaccount',
1937
+ // status: 'complete'
1938
+ // }
1939
+ //
1940
+ const currencyId = this.safeString (transfer, 'coin');
1941
+ const notes = this.safeString (transfer, 'notes', '');
1942
+ const status = this.safeString (transfer, 'status');
1943
+ const fromTo = notes.replace ('Transfer from ', '');
1944
+ const parts = fromTo.split (' to ');
1945
+ let fromAccount = this.safeString (parts, 0);
1946
+ fromAccount = fromAccount.replace (' account', '');
1947
+ let toAccount = this.safeString (parts, 1);
1948
+ toAccount = toAccount.replace (' account', '');
1949
+ return {
1950
+ 'info': transfer,
1951
+ 'id': this.safeString (transfer, 'id'),
1952
+ 'timestamp': undefined,
1953
+ 'datetime': this.safeString (transfer, 'time'),
1954
+ 'currency': this.safeCurrencyCode (currencyId, currency),
1955
+ 'amount': this.safeNumber (transfer, 'size'),
1956
+ 'fromAccount': fromAccount,
1957
+ 'toAccount': toAccount,
1958
+ 'status': this.parseTransferStatus (status),
1959
+ };
1960
+ }
1961
+
1962
+ parseTransferStatus (status) {
1963
+ const statuses = {
1964
+ 'complete': 'ok',
1965
+ };
1966
+ return this.safeString (statuses, status, status);
1967
+ }
1968
+
1969
+ async withdraw (code, amount, address, tag = undefined, params = {}) {
1970
+ [ tag, params ] = this.handleWithdrawTagAndParams (tag, params);
1971
+ await this.loadMarkets ();
1972
+ this.checkAddress (address);
1973
+ const currency = this.currency (code);
1974
+ const request = {
1975
+ 'coin': currency['id'],
1976
+ 'size': amount,
1977
+ 'address': address,
1978
+ // 'password': 'string', // optional withdrawal password if it is required for your account
1979
+ // 'code': '192837', // optional 2fa code if it is required for your account
1980
+ };
1981
+ if (this.password !== undefined) {
1982
+ request['password'] = this.password;
1983
+ }
1984
+ if (tag !== undefined) {
1985
+ request['tag'] = tag;
1986
+ }
1987
+ const networks = this.safeValue (this.options, 'networks', {});
1988
+ let network = this.safeStringUpper (params, 'network'); // this line allows the user to specify either ERC20 or ETH
1989
+ network = this.safeStringLower (networks, network, network); // handle ERC20>ETH alias
1990
+ if (network !== undefined) {
1991
+ request['method'] = network;
1992
+ params = this.omit (params, 'network');
1993
+ }
1994
+ const response = await this.privatePostWalletWithdrawals (this.extend (request, params));
1995
+ //
1996
+ // {
1997
+ // "success": true,
1998
+ // "result": {
1999
+ // "coin": "USDTBEAR",
2000
+ // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
2001
+ // "tag": "null",
2002
+ // "fee": 0,
2003
+ // "id": 1,
2004
+ // "size": "20.2",
2005
+ // "status": "requested",
2006
+ // "time": "2019-03-05T09:56:55.728933+00:00",
2007
+ // "txid": "null"
2008
+ // }
2009
+ // }
2010
+ //
2011
+ const result = this.safeValue (response, 'result', {});
2012
+ return this.parseTransaction (result, currency);
2013
+ }
2014
+
2015
+ async fetchPositions (symbols = undefined, params = {}) {
2016
+ await this.loadMarkets ();
2017
+ const request = {
2018
+ 'showAvgPrice': true,
2019
+ };
2020
+ const response = await this.privateGetPositions (this.extend (request, params));
2021
+ //
2022
+ // {
2023
+ // "success": true,
2024
+ // "result": [
2025
+ // {
2026
+ // "cost": -31.7906,
2027
+ // "entryPrice": 138.22,
2028
+ // "estimatedLiquidationPrice": 152.1,
2029
+ // "future": "ETH-PERP",
2030
+ // "initialMarginRequirement": 0.1,
2031
+ // "longOrderSize": 1744.55,
2032
+ // "maintenanceMarginRequirement": 0.04,
2033
+ // "netSize": -0.23,
2034
+ // "openSize": 1744.32,
2035
+ // "realizedPnl": 3.39441714,
2036
+ // "shortOrderSize": 1732.09,
2037
+ // "recentAverageOpenPrice": 278.98,
2038
+ // "recentPnl": 2.44,
2039
+ // "recentBreakEvenPrice": 278.98,
2040
+ // "side": "sell",
2041
+ // "size": 0.23,
2042
+ // "unrealizedPnl": 0,
2043
+ // "collateralUsed": 3.17906
2044
+ // }
2045
+ // ]
2046
+ // }
2047
+ //
2048
+ const result = this.safeValue (response, 'result', []);
2049
+ const results = [];
2050
+ for (let i = 0; i < result.length; i++) {
2051
+ results.push (this.parsePosition (result[i]));
2052
+ }
2053
+ return this.filterByArray (results, 'symbol', symbols, false);
2054
+ }
2055
+
2056
+ parsePosition (position, market = undefined) {
2057
+ //
2058
+ // {
2059
+ // "future": "XMR-PERP",
2060
+ // "size": "0.0",
2061
+ // "side": "buy",
2062
+ // "netSize": "0.0",
2063
+ // "longOrderSize": "0.0",
2064
+ // "shortOrderSize": "0.0",
2065
+ // "cost": "0.0",
2066
+ // "entryPrice": null,
2067
+ // "unrealizedPnl": "0.0",
2068
+ // "realizedPnl": "0.0",
2069
+ // "initialMarginRequirement": "0.02",
2070
+ // "maintenanceMarginRequirement": "0.006",
2071
+ // "openSize": "0.0",
2072
+ // "collateralUsed": "0.0",
2073
+ // "estimatedLiquidationPrice": null
2074
+ // }
2075
+ //
2076
+ const contractsString = this.safeString (position, 'size');
2077
+ const rawSide = this.safeString (position, 'side');
2078
+ const side = (rawSide === 'buy') ? 'long' : 'short';
2079
+ const marketId = this.safeString (position, 'future');
2080
+ const symbol = this.safeSymbol (marketId, market);
2081
+ const liquidationPriceString = this.safeString (position, 'estimatedLiquidationPrice');
2082
+ const initialMarginPercentage = this.safeString (position, 'initialMarginRequirement');
2083
+ const leverage = parseInt (Precise.stringDiv ('1', initialMarginPercentage, 0));
2084
+ // on ftx the entryPrice is actually the mark price
2085
+ const markPriceString = this.safeString (position, 'entryPrice');
2086
+ const notionalString = Precise.stringMul (contractsString, markPriceString);
2087
+ const initialMargin = Precise.stringMul (notionalString, initialMarginPercentage);
2088
+ const maintenanceMarginPercentageString = this.safeString (position, 'maintenanceMarginRequirement');
2089
+ const maintenanceMarginString = Precise.stringMul (notionalString, maintenanceMarginPercentageString);
2090
+ const unrealizedPnlString = this.safeString (position, 'recentPnl');
2091
+ const percentage = this.parseNumber (Precise.stringMul (Precise.stringDiv (unrealizedPnlString, initialMargin, 4), '100'));
2092
+ const entryPriceString = this.safeString (position, 'recentAverageOpenPrice');
2093
+ let difference = undefined;
2094
+ let collateral = undefined;
2095
+ let marginRatio = undefined;
2096
+ if ((entryPriceString !== undefined) && (Precise.stringGt (liquidationPriceString, '0'))) {
2097
+ // collateral = maintenanceMargin ± ((markPrice - liquidationPrice) * size)
2098
+ if (side === 'long') {
2099
+ difference = Precise.stringSub (markPriceString, liquidationPriceString);
2100
+ } else {
2101
+ difference = Precise.stringSub (liquidationPriceString, markPriceString);
2102
+ }
2103
+ const loss = Precise.stringMul (difference, contractsString);
2104
+ collateral = Precise.stringAdd (loss, maintenanceMarginString);
2105
+ marginRatio = this.parseNumber (Precise.stringDiv (maintenanceMarginString, collateral, 4));
2106
+ }
2107
+ // ftx has a weird definition of realizedPnl
2108
+ // it keeps the historical record of the realizedPnl per contract forever
2109
+ // so we cannot use this data
2110
+ return {
2111
+ 'info': position,
2112
+ 'symbol': symbol,
2113
+ 'timestamp': undefined,
2114
+ 'datetime': undefined,
2115
+ 'initialMargin': this.parseNumber (initialMargin),
2116
+ 'initialMarginPercentage': this.parseNumber (initialMarginPercentage),
2117
+ 'maintenanceMargin': this.parseNumber (maintenanceMarginString),
2118
+ 'maintenanceMarginPercentage': this.parseNumber (maintenanceMarginPercentageString),
2119
+ 'entryPrice': this.parseNumber (entryPriceString),
2120
+ 'notional': this.parseNumber (notionalString),
2121
+ 'leverage': leverage,
2122
+ 'unrealizedPnl': this.parseNumber (unrealizedPnlString),
2123
+ 'contracts': this.parseNumber (contractsString),
2124
+ 'contractSize': this.safeValue (market, 'contractSize'),
2125
+ 'marginRatio': marginRatio,
2126
+ 'liquidationPrice': this.parseNumber (liquidationPriceString),
2127
+ 'markPrice': this.parseNumber (markPriceString),
2128
+ 'collateral': this.parseNumber (collateral),
2129
+ 'marginType': 'cross',
2130
+ 'side': side,
2131
+ 'percentage': percentage,
2132
+ };
2133
+ }
2134
+
2135
+ async fetchDepositAddress (code, params = {}) {
2136
+ await this.loadMarkets ();
2137
+ const currency = this.currency (code);
2138
+ const request = {
2139
+ 'coin': currency['id'],
2140
+ };
2141
+ const networks = this.safeValue (this.options, 'networks', {});
2142
+ let network = this.safeStringUpper (params, 'network'); // this line allows the user to specify either ERC20 or ETH
2143
+ network = this.safeStringLower (networks, network, network); // handle ERC20>ETH alias
2144
+ if (network !== undefined) {
2145
+ request['method'] = network;
2146
+ params = this.omit (params, 'network');
2147
+ }
2148
+ const response = await this.privateGetWalletDepositAddressCoin (this.extend (request, params));
2149
+ //
2150
+ // {
2151
+ // "success": true,
2152
+ // "result": {
2153
+ // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
2154
+ // "tag": null,
2155
+ // "method": "erc20",
2156
+ // "coin": null
2157
+ // }
2158
+ // }
2159
+ //
2160
+ const result = this.safeValue (response, 'result', {});
2161
+ const networkId = this.safeString (result, 'method');
2162
+ const address = this.safeString (result, 'address');
2163
+ this.checkAddress (address);
2164
+ return {
2165
+ 'currency': code,
2166
+ 'address': address,
2167
+ 'tag': this.safeString (result, 'tag'),
2168
+ 'network': this.safeNetwork (networkId),
2169
+ 'info': response,
2170
+ };
2171
+ }
2172
+
2173
+ safeNetwork (networkId) {
2174
+ const networksById = {
2175
+ 'trx': 'TRC20',
2176
+ 'erc20': 'ERC20',
2177
+ 'sol': 'SOL',
2178
+ 'bsc': 'BEP20',
2179
+ 'bep2': 'BEP2',
2180
+ };
2181
+ return this.safeString (networksById, networkId, networkId);
2182
+ }
2183
+
2184
+ parseTransactionStatus (status) {
2185
+ const statuses = {
2186
+ // what are other statuses here?
2187
+ 'confirmed': 'ok', // deposits
2188
+ 'complete': 'ok', // withdrawals
2189
+ 'cancelled': 'canceled', // deposits
2190
+ };
2191
+ return this.safeString (statuses, status, status);
2192
+ }
2193
+
2194
+ parseTransaction (transaction, currency = undefined) {
2195
+ //
2196
+ // fetchDeposits
2197
+ //
2198
+ // airdrop
2199
+ //
2200
+ // {
2201
+ // "id": 9147072,
2202
+ // "coin": "SRM_LOCKED",
2203
+ // "size": 3.12,
2204
+ // "time": "2021-04-27T23:59:03.565983+00:00",
2205
+ // "notes": "SRM Airdrop for FTT holdings",
2206
+ // "status": "complete"
2207
+ // }
2208
+ //
2209
+ // regular deposits
2210
+ //
2211
+ // {
2212
+ // "coin": "TUSD",
2213
+ // "confirmations": 64,
2214
+ // "confirmedTime": "2019-03-05T09:56:55.728933+00:00",
2215
+ // "fee": 0,
2216
+ // "id": 1,
2217
+ // "sentTime": "2019-03-05T09:56:55.735929+00:00",
2218
+ // "size": "99.0",
2219
+ // "status": "confirmed",
2220
+ // "time": "2019-03-05T09:56:55.728933+00:00",
2221
+ // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
2222
+ // }
2223
+ //
2224
+ // fetchWithdrawals
2225
+ //
2226
+ // {
2227
+ // "coin": "TUSD",
2228
+ // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
2229
+ // "tag": "null",
2230
+ // "fee": 0,
2231
+ // "id": 1,
2232
+ // "size": "99.0",
2233
+ // "status": "complete",
2234
+ // "time": "2019-03-05T09:56:55.728933+00:00",
2235
+ // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
2236
+ // }
2237
+ //
2238
+ // {
2239
+ // "coin": 'BTC',
2240
+ // "id": 1969806,
2241
+ // "notes": 'Transfer to Dd6gi7m2Eg4zzBbPAxuwfEaHs6tYvyUX5hbPpsTcNPXo',
2242
+ // "size": 0.003,
2243
+ // "status": 'complete',
2244
+ // "time": '2021-02-03T20:28:54.918146+00:00'
2245
+ // }
2246
+ //
2247
+ const code = this.safeCurrencyCode (this.safeString (transaction, 'coin'));
2248
+ const id = this.safeString (transaction, 'id');
2249
+ const amount = this.safeNumber (transaction, 'size');
2250
+ const status = this.parseTransactionStatus (this.safeString (transaction, 'status'));
2251
+ const timestamp = this.parse8601 (this.safeString (transaction, 'time'));
2252
+ const txid = this.safeString (transaction, 'txid');
2253
+ let tag = undefined;
2254
+ let address = this.safeValue (transaction, 'address');
2255
+ if (typeof address !== 'string') {
2256
+ tag = this.safeString (address, 'tag');
2257
+ address = this.safeString (address, 'address');
2258
+ }
2259
+ if (address === undefined) {
2260
+ // parse address from internal transfer
2261
+ const notes = this.safeString (transaction, 'notes');
2262
+ if ((notes !== undefined) && (notes.indexOf ('Transfer to') >= 0)) {
2263
+ address = notes.slice (12);
2264
+ }
2265
+ }
2266
+ const fee = this.safeNumber (transaction, 'fee');
2267
+ return {
2268
+ 'info': transaction,
2269
+ 'id': id,
2270
+ 'txid': txid,
2271
+ 'timestamp': timestamp,
2272
+ 'datetime': this.iso8601 (timestamp),
2273
+ 'network': undefined,
2274
+ 'addressFrom': undefined,
2275
+ 'address': address,
2276
+ 'addressTo': address,
2277
+ 'tagFrom': undefined,
2278
+ 'tag': tag,
2279
+ 'tagTo': tag,
2280
+ 'type': undefined,
2281
+ 'amount': amount,
2282
+ 'currency': code,
2283
+ 'status': status,
2284
+ 'updated': undefined,
2285
+ 'fee': {
2286
+ 'currency': code,
2287
+ 'cost': fee,
2288
+ 'rate': undefined,
2289
+ },
2290
+ };
2291
+ }
2292
+
2293
+ async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
2294
+ await this.loadMarkets ();
2295
+ const response = await this.privateGetWalletDeposits (params);
2296
+ //
2297
+ // {
2298
+ // "success": true,
2299
+ // "result": {
2300
+ // "coin": "TUSD",
2301
+ // "confirmations": 64,
2302
+ // "confirmedTime": "2019-03-05T09:56:55.728933+00:00",
2303
+ // "fee": 0,
2304
+ // "id": 1,
2305
+ // "sentTime": "2019-03-05T09:56:55.735929+00:00",
2306
+ // "size": "99.0",
2307
+ // "status": "confirmed",
2308
+ // "time": "2019-03-05T09:56:55.728933+00:00",
2309
+ // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
2310
+ // }
2311
+ // }
2312
+ //
2313
+ const result = this.safeValue (response, 'result', []);
2314
+ let currency = undefined;
2315
+ if (code !== undefined) {
2316
+ currency = this.currency (code);
2317
+ }
2318
+ return this.parseTransactions (result, currency, since, limit, { 'type': 'deposit' });
2319
+ }
2320
+
2321
+ async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
2322
+ await this.loadMarkets ();
2323
+ const response = await this.privateGetWalletWithdrawals (params);
2324
+ //
2325
+ // {
2326
+ // "success": true,
2327
+ // "result": {
2328
+ // "coin": "TUSD",
2329
+ // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
2330
+ // "tag": "null",
2331
+ // "fee": 0,
2332
+ // "id": 1,
2333
+ // "size": "99.0",
2334
+ // "status": "complete",
2335
+ // "time": "2019-03-05T09:56:55.728933+00:00",
2336
+ // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
2337
+ // }
2338
+ // }
2339
+ //
2340
+ const result = this.safeValue (response, 'result', []);
2341
+ let currency = undefined;
2342
+ if (code !== undefined) {
2343
+ currency = this.currency (code);
2344
+ }
2345
+ return this.parseTransactions (result, currency, since, limit, { 'type': 'withdrawal' });
2346
+ }
2347
+
2348
+ sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
2349
+ let request = '/api/' + this.implodeParams (path, params);
2350
+ const signOptions = this.safeValue (this.options, 'sign', {});
2351
+ const headerPrefix = this.safeString (signOptions, this.hostname, 'FTX');
2352
+ const subaccountField = headerPrefix + '-SUBACCOUNT';
2353
+ const chosenSubaccount = this.safeString2 (params, subaccountField, 'subaccount');
2354
+ if (chosenSubaccount !== undefined) {
2355
+ params = this.omit (params, [ subaccountField, 'subaccount' ]);
2356
+ }
2357
+ const query = this.omit (params, this.extractParams (path));
2358
+ const baseUrl = this.implodeHostname (this.urls['api'][api]);
2359
+ let url = baseUrl + request;
2360
+ if (method !== 'POST') {
2361
+ if (Object.keys (query).length) {
2362
+ const suffix = '?' + this.urlencode (query);
2363
+ url += suffix;
2364
+ request += suffix;
2365
+ }
2366
+ }
2367
+ if (api === 'private') {
2368
+ this.checkRequiredCredentials ();
2369
+ const timestamp = this.milliseconds ().toString ();
2370
+ let auth = timestamp + method + request;
2371
+ headers = {};
2372
+ if ((method === 'POST') || (method === 'DELETE')) {
2373
+ body = this.json (query);
2374
+ auth += body;
2375
+ headers['Content-Type'] = 'application/json';
2376
+ }
2377
+ const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256');
2378
+ headers[headerPrefix + '-KEY'] = this.apiKey;
2379
+ headers[headerPrefix + '-TS'] = timestamp;
2380
+ headers[headerPrefix + '-SIGN'] = signature;
2381
+ if (chosenSubaccount !== undefined) {
2382
+ headers[subaccountField] = chosenSubaccount;
2383
+ }
2384
+ }
2385
+ return { 'url': url, 'method': method, 'body': body, 'headers': headers };
2386
+ }
2387
+
2388
+ handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
2389
+ if (response === undefined) {
2390
+ return; // fallback to the default error handler
2391
+ }
2392
+ //
2393
+ // {"error":"Invalid parameter start_time","success":false}
2394
+ // {"error":"Not enough balances","success":false}
2395
+ //
2396
+ const success = this.safeValue (response, 'success');
2397
+ if (!success) {
2398
+ const feedback = this.id + ' ' + body;
2399
+ const error = this.safeString (response, 'error');
2400
+ this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback);
2401
+ this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback);
2402
+ throw new ExchangeError (feedback); // unknown message
2403
+ }
2404
+ }
2405
+
2406
+ async setLeverage (leverage, symbol = undefined, params = {}) {
2407
+ // WARNING: THIS WILL INCREASE LIQUIDATION PRICE FOR OPEN ISOLATED LONG POSITIONS
2408
+ // AND DECREASE LIQUIDATION PRICE FOR OPEN ISOLATED SHORT POSITIONS
2409
+ if ((leverage < 1) || (leverage > 20)) {
2410
+ throw new BadRequest (this.id + ' setLeverage() leverage should be between 1 and 20');
2411
+ }
2412
+ const request = {
2413
+ 'leverage': leverage,
2414
+ };
2415
+ return await this.privatePostAccountLeverage (this.extend (request, params));
2416
+ }
2417
+
2418
+ parseIncome (income, market = undefined) {
2419
+ //
2420
+ // {
2421
+ // "future": "ETH-PERP",
2422
+ // "id": 33830,
2423
+ // "payment": 0.0441342,
2424
+ // "time": "2019-05-15T18:00:00+00:00",
2425
+ // "rate": 0.0001
2426
+ // }
2427
+ //
2428
+ const marketId = this.safeString (income, 'future');
2429
+ const symbol = this.safeSymbol (marketId, market);
2430
+ const amount = this.safeNumber (income, 'payment');
2431
+ const code = this.safeCurrencyCode ('USD');
2432
+ const id = this.safeString (income, 'id');
2433
+ const time = this.safeString (income, 'time');
2434
+ const timestamp = this.parse8601 (time);
2435
+ const rate = this.safe_number (income, 'rate');
2436
+ return {
2437
+ 'info': income,
2438
+ 'symbol': symbol,
2439
+ 'code': code,
2440
+ 'timestamp': timestamp,
2441
+ 'datetime': this.iso8601 (timestamp),
2442
+ 'id': id,
2443
+ 'amount': amount,
2444
+ 'rate': rate,
2445
+ };
2446
+ }
2447
+
2448
+ parseIncomes (incomes, market = undefined, since = undefined, limit = undefined) {
2449
+ const result = [];
2450
+ for (let i = 0; i < incomes.length; i++) {
2451
+ const entry = incomes[i];
2452
+ const parsed = this.parseIncome (entry, market);
2453
+ result.push (parsed);
2454
+ }
2455
+ const sorted = this.sortBy (result, 'timestamp');
2456
+ return this.filterBySinceLimit (sorted, since, limit, 'timestamp');
2457
+ }
2458
+
2459
+ async fetchFundingHistory (symbol = undefined, since = undefined, limit = undefined, params = {}) {
2460
+ await this.loadMarkets ();
2461
+ const request = {};
2462
+ let market = undefined;
2463
+ if (symbol !== undefined) {
2464
+ market = this.market (symbol);
2465
+ request['future'] = market['id'];
2466
+ }
2467
+ if (since !== undefined) {
2468
+ request['startTime'] = since;
2469
+ }
2470
+ const response = await this.privateGetFundingPayments (this.extend (request, params));
2471
+ const result = this.safeValue (response, 'result', []);
2472
+ return this.parseIncomes (result, market, since, limit);
2473
+ }
2474
+
2475
+ parseFundingRate (fundingRate, market = undefined) {
2476
+ //
2477
+ // perp
2478
+ // {
2479
+ // "volume": "71294.7636",
2480
+ // "nextFundingRate": "0.000033",
2481
+ // "nextFundingTime": "2021-10-14T20:00:00+00:00",
2482
+ // "openInterest": "47142.994"
2483
+ // }
2484
+ //
2485
+ // delivery
2486
+ // {
2487
+ // "volume": "4998.727",
2488
+ // "predictedExpirationPrice": "3798.820141757",
2489
+ // "openInterest": "48307.96"
2490
+ // }
2491
+ //
2492
+ const fundingRateDatetimeRaw = this.safeString (fundingRate, 'nextFundingTime');
2493
+ const fundingRateTimestamp = this.parse8601 (fundingRateDatetimeRaw);
2494
+ const estimatedSettlePrice = this.safeNumber (fundingRate, 'predictedExpirationPrice');
2495
+ return {
2496
+ 'info': fundingRate,
2497
+ 'symbol': market['symbol'],
2498
+ 'markPrice': undefined,
2499
+ 'indexPrice': undefined,
2500
+ 'interestRate': this.parseNumber ('0'),
2501
+ 'estimatedSettlePrice': estimatedSettlePrice,
2502
+ 'timestamp': undefined,
2503
+ 'datetime': undefined,
2504
+ 'fundingRate': this.safeNumber (fundingRate, 'nextFundingRate'),
2505
+ 'fundingTimestamp': fundingRateTimestamp,
2506
+ 'fundingDatetime': this.iso8601 (fundingRateTimestamp),
2507
+ 'nextFundingRate': undefined,
2508
+ 'nextFundingTimestamp': undefined,
2509
+ 'nextFundingDatetime': undefined,
2510
+ 'previousFundingRate': undefined,
2511
+ 'previousFundingTimestamp': undefined,
2512
+ 'previousFundingDatetime': undefined,
2513
+ };
2514
+ }
2515
+
2516
+ async fetchFundingRate (symbol, params = {}) {
2517
+ await this.loadMarkets ();
2518
+ const market = this.market (symbol);
2519
+ const request = {
2520
+ 'future_name': market['id'],
2521
+ };
2522
+ const response = await this.publicGetFuturesFutureNameStats (this.extend (request, params));
2523
+ //
2524
+ // {
2525
+ // "success": true,
2526
+ // "result": {
2527
+ // "volume": "71294.7636",
2528
+ // "nextFundingRate": "0.000033",
2529
+ // "nextFundingTime": "2021-10-14T20:00:00+00:00",
2530
+ // "openInterest": "47142.994"
2531
+ // }
2532
+ // }
2533
+ //
2534
+ const result = this.safeValue (response, 'result', {});
2535
+ return this.parseFundingRate (result, market);
2536
+ }
2537
+
2538
+ async fetchBorrowRates (params = {}) {
2539
+ await this.loadMarkets ();
2540
+ const response = await this.privateGetSpotMarginBorrowRates (params);
2541
+ //
2542
+ // {
2543
+ // "success":true,
2544
+ // "result":[
2545
+ // {"coin":"1INCH","previous":4.8763e-6,"estimate":4.8048e-6},
2546
+ // {"coin":"AAPL","previous":0.0000326469,"estimate":0.0000326469},
2547
+ // {"coin":"AAVE","previous":1.43e-6,"estimate":1.43e-6},
2548
+ // ]
2549
+ // }
2550
+ //
2551
+ const result = this.safeValue (response, 'result');
2552
+ return this.parseBorrowRates (result, 'coin');
2553
+ }
2554
+
2555
+ async fetchBorrowRateHistories (codes = undefined, since = undefined, limit = undefined, params = {}) {
2556
+ /**
2557
+ * @method
2558
+ * @name ftx#fetchBorrowRateHistory
2559
+ * @description Gets the history of the borrow rate for mutiple currencies
2560
+ * @param {str} code Unified currency code
2561
+ * @param {int} since Timestamp in ms of the earliest time to fetch the borrow rate
2562
+ * @param {int} limit Max number of [borrow rate structures]{@link https://docs.ccxt.com/en/latest/manual.html#borrow-rate-structure} to return per currency, max=48 for multiple currencies, max=5000 for a single currency
2563
+ * @param {dict} params Exchange specific parameters
2564
+ * @param {dict} params.till Timestamp in ms of the latest time to fetch the borrow rate
2565
+ * @returns A dictionary of [borrow rate structures]{@link https://docs.ccxt.com/en/latest/manual.html#borrow-rate-structure} with unified currency codes as keys
2566
+ */
2567
+ await this.loadMarkets ();
2568
+ const request = {};
2569
+ let numCodes = 0;
2570
+ let endTime = this.safeNumber2 (params, 'till', 'end_time');
2571
+ if (codes !== undefined) {
2572
+ numCodes = codes.length;
2573
+ }
2574
+ if (numCodes === 1) {
2575
+ const millisecondsPer5000Hours = 18000000000;
2576
+ if ((limit !== undefined) && (limit > 5000)) {
2577
+ throw new BadRequest (this.id + ' fetchBorrowRateHistories() limit cannot exceed 5000 for a single currency');
2578
+ }
2579
+ if ((endTime !== undefined) && (since !== undefined) && ((endTime - since) > millisecondsPer5000Hours)) {
2580
+ throw new BadRequest (this.id + ' fetchBorrowRateHistories() requires the time range between the since time and the end time to be less than 5000 hours for a single currency');
2581
+ }
2582
+ const currency = this.currency (codes[0]);
2583
+ request['coin'] = currency['id'];
2584
+ } else {
2585
+ const millisecondsPer2Days = 172800000;
2586
+ if ((limit !== undefined) && (limit > 48)) {
2587
+ throw new BadRequest (this.id + ' fetchBorrowRateHistories() limit cannot exceed 48 for multiple currencies');
2588
+ }
2589
+ if ((endTime !== undefined) && (since !== undefined) && ((endTime - since) > millisecondsPer2Days)) {
2590
+ throw new BadRequest (this.id + ' fetchBorrowRateHistories() requires the time range between the since time and the end time to be less than 48 hours for multiple currencies');
2591
+ }
2592
+ }
2593
+ const millisecondsPerHour = 3600000;
2594
+ if (since !== undefined) {
2595
+ request['start_time'] = parseInt (since / 1000);
2596
+ if (endTime === undefined) {
2597
+ const now = this.milliseconds ();
2598
+ const sinceLimit = (limit === undefined) ? 2 : limit;
2599
+ endTime = this.sum (since, millisecondsPerHour * (sinceLimit - 1));
2600
+ endTime = Math.min (endTime, now);
2601
+ }
2602
+ } else {
2603
+ if (limit !== undefined) {
2604
+ if (endTime === undefined) {
2605
+ endTime = this.milliseconds ();
2606
+ }
2607
+ const startTime = this.sum ((endTime - millisecondsPerHour * limit), 1000);
2608
+ request['start_time'] = parseInt (startTime / 1000);
2609
+ }
2610
+ }
2611
+ if (endTime !== undefined) {
2612
+ request['end_time'] = parseInt (endTime / 1000);
2613
+ }
2614
+ const response = await this.publicGetSpotMarginHistory (this.extend (request, params));
2615
+ //
2616
+ // {
2617
+ // "success": true,
2618
+ // "result": [
2619
+ // {
2620
+ // "coin": "PYPL",
2621
+ // "time": "2022-01-24T13:00:00+00:00",
2622
+ // "size": 0.00500172,
2623
+ // "rate": 1e-6
2624
+ // },
2625
+ // ...
2626
+ // ]
2627
+ // }
2628
+ //
2629
+ const result = this.safeValue (response, 'result');
2630
+ return this.parseBorrowRateHistories (result, codes, since, limit);
2631
+ }
2632
+
2633
+ async fetchBorrowRateHistory (code, since = undefined, limit = undefined, params = {}) {
2634
+ /**
2635
+ * @method
2636
+ * @name ftx#fetchBorrowRateHistory
2637
+ * @description Gets the history of the borrow rate for a currency
2638
+ * @param {str} code Unified currency code
2639
+ * @param {int} since Timestamp in ms of the earliest time to fetch the borrow rate
2640
+ * @param {int} limit Max number of [borrow rate structures]{@link https://docs.ccxt.com/en/latest/manual.html#borrow-rate-structure} to return, max=5000
2641
+ * @param {dict} params Exchange specific parameters
2642
+ * @param {dict} params.till Timestamp in ms of the latest time to fetch the borrow rate
2643
+ * @returns An array of [borrow rate structures]{@link https://docs.ccxt.com/en/latest/manual.html#borrow-rate-structure}
2644
+ */
2645
+ const histories = await this.fetchBorrowRateHistories ([ code ], since, limit, params);
2646
+ const borrowRateHistory = this.safeValue (histories, code);
2647
+ if (borrowRateHistory === undefined) {
2648
+ throw new BadRequest (this.id + ' fetchBorrowRateHistory() returned no data for ' + code);
2649
+ }
2650
+ return borrowRateHistory;
2651
+ }
2652
+
2653
+ parseBorrowRateHistories (response, codes, since, limit) {
2654
+ // How to calculate borrow rate
2655
+ // https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer
2656
+ const takerFee = this.fees['trading']['taker'].toString ();
2657
+ const spotMarginBorrowRate = Precise.stringMul ('500', takerFee);
2658
+ const borrowRateHistories = {};
2659
+ for (let i = 0; i < response.length; i++) {
2660
+ const item = response[i];
2661
+ const code = this.safeCurrencyCode (this.safeString (item, 'coin'));
2662
+ if (codes === undefined || this.inArray (code, codes)) {
2663
+ if (!(code in borrowRateHistories)) {
2664
+ borrowRateHistories[code] = [];
2665
+ }
2666
+ const lendingRate = this.safeString (item, 'rate');
2667
+ const borrowRate = Precise.stringMul (lendingRate, Precise.stringAdd ('1', spotMarginBorrowRate));
2668
+ const borrowRateStructure = this.extend (this.parseBorrowRate (item), { 'rate': borrowRate });
2669
+ borrowRateHistories[code].push (borrowRateStructure);
2670
+ }
2671
+ }
2672
+ const keys = Object.keys (borrowRateHistories);
2673
+ for (let i = 0; i < keys.length; i++) {
2674
+ const code = keys[i];
2675
+ borrowRateHistories[code] = this.filterByCurrencySinceLimit (borrowRateHistories[code], code, since, limit);
2676
+ }
2677
+ return borrowRateHistories;
2678
+ }
2679
+
2680
+ parseBorrowRates (response, codeKey) {
2681
+ const result = {};
2682
+ for (let i = 0; i < response.length; i++) {
2683
+ const item = response[i];
2684
+ const currency = this.safeString (item, codeKey);
2685
+ const code = this.safeCurrencyCode (currency);
2686
+ const borrowRate = this.parseBorrowRate (item);
2687
+ result[code] = borrowRate;
2688
+ }
2689
+ return result;
2690
+ }
2691
+
2692
+ parseBorrowRate (info, currency = undefined) {
2693
+ //
2694
+ // {
2695
+ // "coin": "1INCH",
2696
+ // "previous": 0.0000462375,
2697
+ // "estimate": 0.0000462375
2698
+ // }
2699
+ //
2700
+ const coin = this.safeString (info, 'coin');
2701
+ const datetime = this.safeString (info, 'time');
2702
+ const timestamp = this.parse8601 (datetime);
2703
+ return {
2704
+ 'currency': this.safeCurrencyCode (coin),
2705
+ 'rate': this.safeNumber (info, 'previous'),
2706
+ 'period': 3600000,
2707
+ 'timestamp': timestamp,
2708
+ 'datetime': this.iso8601 (timestamp),
2709
+ 'info': info,
2710
+ };
2711
+ }
2712
+
2713
+ async fetchBorrowInterest (code = undefined, symbol = undefined, since = undefined, limit = undefined, params = {}) {
2714
+ await this.loadMarkets ();
2715
+ const request = {};
2716
+ if (since !== undefined) {
2717
+ request['start_time'] = parseInt (since / 1000);
2718
+ }
2719
+ const response = await this.privateGetSpotMarginBorrowHistory (this.extend (request, params));
2720
+ //
2721
+ // {
2722
+ // "success":true,
2723
+ // "result":[
2724
+ // {"coin":"USDT","time":"2021-12-26T01:00:00+00:00","size":4593.74214725,"rate":3.3003e-6,"cost":0.0151607272085692,"feeUsd":0.0151683341034461},
2725
+ // {"coin":"USDT","time":"2021-12-26T00:00:00+00:00","size":4593.97110361,"rate":3.3003e-6,"cost":0.0151614828332441,"feeUsd":0.015169697173028324},
2726
+ // {"coin":"USDT","time":"2021-12-25T23:00:00+00:00","size":4594.20005922,"rate":3.3003e-6,"cost":0.0151622384554438,"feeUsd":0.015170200298479137},
2727
+ // ]
2728
+ // }
2729
+ //
2730
+ const result = this.safeValue (response, 'result');
2731
+ const interest = this.parseBorrowInterests (result);
2732
+ return this.filterByCurrencySinceLimit (interest, code, since, limit);
2733
+ }
2734
+
2735
+ parseBorrowInterest (info, market = undefined) {
2736
+ const coin = this.safeString (info, 'coin');
2737
+ const datetime = this.safeString (info, 'time');
2738
+ return {
2739
+ 'account': 'cross',
2740
+ 'symbol': undefined,
2741
+ 'marginType': 'cross',
2742
+ 'currency': this.safeCurrencyCode (coin),
2743
+ 'interest': this.safeNumber (info, 'cost'),
2744
+ 'interestRate': this.safeNumber (info, 'rate'),
2745
+ 'amountBorrowed': this.safeNumber (info, 'size'),
2746
+ 'timestamp': this.parse8601 (datetime),
2747
+ 'datetime': datetime,
2748
+ 'info': info,
2749
+ };
2750
+ }
2751
+ };