ccxt 4.4.88__py2.py3-none-any.whl → 4.4.91__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccxt/__init__.py +1 -3
- ccxt/abstract/bitget.py +58 -0
- ccxt/abstract/bitrue.py +65 -65
- ccxt/abstract/cryptocom.py +2 -0
- ccxt/abstract/luno.py +1 -0
- ccxt/async_support/__init__.py +1 -3
- ccxt/async_support/base/exchange.py +6 -3
- ccxt/async_support/base/ws/client.py +173 -64
- ccxt/async_support/base/ws/future.py +23 -50
- ccxt/async_support/binance.py +2 -2
- ccxt/async_support/bingx.py +55 -29
- ccxt/async_support/bitget.py +469 -147
- ccxt/async_support/bitmex.py +2 -1
- ccxt/async_support/bitrue.py +72 -66
- ccxt/async_support/bitvavo.py +34 -0
- ccxt/async_support/btcalpha.py +35 -0
- ccxt/async_support/btcbox.py +35 -0
- ccxt/async_support/btcmarkets.py +35 -0
- ccxt/async_support/btcturk.py +35 -0
- ccxt/async_support/bybit.py +9 -3
- ccxt/async_support/cex.py +61 -0
- ccxt/async_support/coinbase.py +1 -3
- ccxt/async_support/cryptocom.py +66 -2
- ccxt/async_support/cryptomus.py +1 -1
- ccxt/async_support/delta.py +2 -2
- ccxt/async_support/digifinex.py +39 -99
- ccxt/async_support/exmo.py +14 -7
- ccxt/async_support/gate.py +14 -7
- ccxt/async_support/hashkey.py +15 -28
- ccxt/async_support/hollaex.py +27 -22
- ccxt/async_support/hyperliquid.py +104 -53
- ccxt/async_support/kraken.py +54 -50
- ccxt/async_support/luno.py +87 -1
- ccxt/async_support/mexc.py +1 -0
- ccxt/async_support/modetrade.py +2 -2
- ccxt/async_support/okx.py +2 -1
- ccxt/async_support/paradex.py +1 -1
- ccxt/async_support/phemex.py +16 -8
- ccxt/async_support/tradeogre.py +3 -3
- ccxt/async_support/xt.py +1 -1
- ccxt/base/exchange.py +20 -8
- ccxt/binance.py +2 -2
- ccxt/bingx.py +55 -29
- ccxt/bitget.py +469 -147
- ccxt/bitmex.py +2 -1
- ccxt/bitrue.py +72 -66
- ccxt/bitvavo.py +34 -0
- ccxt/btcalpha.py +35 -0
- ccxt/btcbox.py +35 -0
- ccxt/btcmarkets.py +35 -0
- ccxt/btcturk.py +35 -0
- ccxt/bybit.py +9 -3
- ccxt/cex.py +61 -0
- ccxt/coinbase.py +1 -3
- ccxt/cryptocom.py +66 -2
- ccxt/cryptomus.py +1 -1
- ccxt/delta.py +2 -2
- ccxt/digifinex.py +39 -99
- ccxt/exmo.py +13 -7
- ccxt/gate.py +14 -7
- ccxt/hashkey.py +15 -28
- ccxt/hollaex.py +27 -22
- ccxt/hyperliquid.py +104 -53
- ccxt/kraken.py +53 -50
- ccxt/luno.py +87 -1
- ccxt/mexc.py +1 -0
- ccxt/modetrade.py +2 -2
- ccxt/okx.py +2 -1
- ccxt/paradex.py +1 -1
- ccxt/phemex.py +16 -8
- ccxt/pro/__init__.py +1 -127
- ccxt/pro/bitstamp.py +1 -1
- ccxt/pro/bybit.py +6 -136
- ccxt/pro/coinbase.py +2 -0
- ccxt/pro/cryptocom.py +27 -0
- ccxt/pro/kraken.py +249 -267
- ccxt/pro/mexc.py +0 -1
- ccxt/tradeogre.py +3 -3
- ccxt/xt.py +1 -1
- {ccxt-4.4.88.dist-info → ccxt-4.4.91.dist-info}/METADATA +64 -23
- {ccxt-4.4.88.dist-info → ccxt-4.4.91.dist-info}/RECORD +84 -101
- ccxt/abstract/coinlist.py +0 -57
- ccxt/async_support/base/ws/aiohttp_client.py +0 -147
- ccxt/async_support/bitcoincom.py +0 -18
- ccxt/async_support/bitfinex1.py +0 -1711
- ccxt/async_support/bitpanda.py +0 -17
- ccxt/async_support/coinlist.py +0 -2542
- ccxt/async_support/poloniexfutures.py +0 -1875
- ccxt/bitcoincom.py +0 -18
- ccxt/bitfinex1.py +0 -1710
- ccxt/bitpanda.py +0 -17
- ccxt/coinlist.py +0 -2542
- ccxt/poloniexfutures.py +0 -1875
- ccxt/pro/bitcoincom.py +0 -35
- ccxt/pro/bitfinex1.py +0 -635
- ccxt/pro/bitpanda.py +0 -16
- ccxt/pro/poloniexfutures.py +0 -1004
- ccxt/pro/wazirx.py +0 -766
- {ccxt-4.4.88.dist-info → ccxt-4.4.91.dist-info}/LICENSE.txt +0 -0
- {ccxt-4.4.88.dist-info → ccxt-4.4.91.dist-info}/WHEEL +0 -0
- {ccxt-4.4.88.dist-info → ccxt-4.4.91.dist-info}/top_level.txt +0 -0
ccxt/bitfinex1.py
DELETED
@@ -1,1710 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
|
-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
4
|
-
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
5
|
-
|
6
|
-
from ccxt.base.exchange import Exchange
|
7
|
-
from ccxt.abstract.bitfinex1 import ImplicitAPI
|
8
|
-
import hashlib
|
9
|
-
from ccxt.base.types import Any, Balances, Currency, DepositAddress, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFees, Transaction, TransferEntry
|
10
|
-
from typing import List
|
11
|
-
from ccxt.base.errors import ExchangeError
|
12
|
-
from ccxt.base.errors import AuthenticationError
|
13
|
-
from ccxt.base.errors import PermissionDenied
|
14
|
-
from ccxt.base.errors import ArgumentsRequired
|
15
|
-
from ccxt.base.errors import BadSymbol
|
16
|
-
from ccxt.base.errors import InsufficientFunds
|
17
|
-
from ccxt.base.errors import InvalidOrder
|
18
|
-
from ccxt.base.errors import OrderNotFound
|
19
|
-
from ccxt.base.errors import NotSupported
|
20
|
-
from ccxt.base.errors import RateLimitExceeded
|
21
|
-
from ccxt.base.errors import ExchangeNotAvailable
|
22
|
-
from ccxt.base.errors import InvalidNonce
|
23
|
-
from ccxt.base.decimal_to_precision import ROUND
|
24
|
-
from ccxt.base.decimal_to_precision import TRUNCATE
|
25
|
-
from ccxt.base.decimal_to_precision import DECIMAL_PLACES
|
26
|
-
from ccxt.base.decimal_to_precision import SIGNIFICANT_DIGITS
|
27
|
-
from ccxt.base.precise import Precise
|
28
|
-
|
29
|
-
|
30
|
-
class bitfinex1(Exchange, ImplicitAPI):
|
31
|
-
|
32
|
-
def describe(self) -> Any:
|
33
|
-
return self.deep_extend(super(bitfinex1, self).describe(), {
|
34
|
-
'id': 'bitfinex1',
|
35
|
-
'name': 'Bitfinex',
|
36
|
-
'countries': ['VG'],
|
37
|
-
'version': 'v1',
|
38
|
-
# cheapest is 90 requests a minute = 1.5 requests per second on average =>( 1000ms / 1.5) = 666.666 ms between requests on average
|
39
|
-
'rateLimit': 666.666,
|
40
|
-
'pro': True,
|
41
|
-
# new metainfo interface
|
42
|
-
'has': {
|
43
|
-
'CORS': None,
|
44
|
-
'spot': True,
|
45
|
-
'margin': None, # has but unimplemented
|
46
|
-
'swap': None, # has but unimplemented
|
47
|
-
'future': None,
|
48
|
-
'option': None,
|
49
|
-
'cancelAllOrders': True,
|
50
|
-
'cancelOrder': True,
|
51
|
-
'createDepositAddress': True,
|
52
|
-
'createOrder': True,
|
53
|
-
'editOrder': True,
|
54
|
-
'fetchBalance': True,
|
55
|
-
'fetchClosedOrders': True,
|
56
|
-
'fetchDepositAddress': True,
|
57
|
-
'fetchDepositAddresses': False,
|
58
|
-
'fetchDepositAddressesByNetwork': False,
|
59
|
-
'fetchDeposits': False,
|
60
|
-
'fetchDepositsWithdrawals': True,
|
61
|
-
'fetchDepositWithdrawFee': 'emulated',
|
62
|
-
'fetchDepositWithdrawFees': True,
|
63
|
-
'fetchFundingHistory': False,
|
64
|
-
'fetchFundingRate': False, # Endpoint 'lendbook/{currency}' is related to interest rates on spot margin lending
|
65
|
-
'fetchFundingRateHistory': False,
|
66
|
-
'fetchFundingRates': False,
|
67
|
-
'fetchIndexOHLCV': False,
|
68
|
-
'fetchLeverageTiers': False,
|
69
|
-
'fetchMarginMode': False,
|
70
|
-
'fetchMarkets': True,
|
71
|
-
'fetchMarkOHLCV': False,
|
72
|
-
'fetchMyTrades': True,
|
73
|
-
'fetchOHLCV': True,
|
74
|
-
'fetchOpenOrders': True,
|
75
|
-
'fetchOrder': True,
|
76
|
-
'fetchOrderBook': True,
|
77
|
-
'fetchPositionMode': False,
|
78
|
-
'fetchPositions': True,
|
79
|
-
'fetchPremiumIndexOHLCV': False,
|
80
|
-
'fetchTicker': True,
|
81
|
-
'fetchTickers': True,
|
82
|
-
'fetchTime': False,
|
83
|
-
'fetchTrades': True,
|
84
|
-
'fetchTradingFee': False,
|
85
|
-
'fetchTradingFees': True,
|
86
|
-
'fetchTransactionFees': True,
|
87
|
-
'fetchTransactions': 'emulated',
|
88
|
-
'transfer': True,
|
89
|
-
'withdraw': True,
|
90
|
-
},
|
91
|
-
'timeframes': {
|
92
|
-
'1m': '1m',
|
93
|
-
'5m': '5m',
|
94
|
-
'15m': '15m',
|
95
|
-
'30m': '30m',
|
96
|
-
'1h': '1h',
|
97
|
-
'3h': '3h',
|
98
|
-
'4h': '4h',
|
99
|
-
'6h': '6h',
|
100
|
-
'12h': '12h',
|
101
|
-
'1d': '1D',
|
102
|
-
'1w': '7D',
|
103
|
-
'2w': '14D',
|
104
|
-
'1M': '1M',
|
105
|
-
},
|
106
|
-
'urls': {
|
107
|
-
'logo': 'https://github.com/user-attachments/assets/9147c6c5-7197-481e-827b-7483672bb0e9',
|
108
|
-
'api': {
|
109
|
-
'v2': 'https://api-pub.bitfinex.com', # https://github.com/ccxt/ccxt/issues/5109
|
110
|
-
'public': 'https://api.bitfinex.com',
|
111
|
-
'private': 'https://api.bitfinex.com',
|
112
|
-
},
|
113
|
-
'www': 'https://www.bitfinex.com',
|
114
|
-
'referral': 'https://www.bitfinex.com/?refcode=P61eYxFL',
|
115
|
-
'doc': [
|
116
|
-
'https://docs.bitfinex.com/v1/docs',
|
117
|
-
'https://github.com/bitfinexcom/bitfinex-api-node',
|
118
|
-
],
|
119
|
-
},
|
120
|
-
'api': {
|
121
|
-
# v2 symbol ids require a 't' prefix
|
122
|
-
# just the public part of it(use bitfinex2 for everything else)
|
123
|
-
'v2': {
|
124
|
-
'get': {
|
125
|
-
'platform/status': 3, # 30 requests per minute
|
126
|
-
'tickers': 1, # 90 requests a minute
|
127
|
-
'ticker/{symbol}': 1,
|
128
|
-
'tickers/hist': 1,
|
129
|
-
'trades/{symbol}/hist': 1,
|
130
|
-
'book/{symbol}/{precision}': 0.375, # 240 requests per minute = 4 requests per second(1000ms / rateLimit) / 4 = 0.37500375
|
131
|
-
'book/{symbol}/P0': 0.375,
|
132
|
-
'book/{symbol}/P1': 0.375,
|
133
|
-
'book/{symbol}/P2': 0.375,
|
134
|
-
'book/{symbol}/P3': 0.375,
|
135
|
-
'book/{symbol}/R0': 0.375,
|
136
|
-
'stats1/{key}:{size}:{symbol}:{side}/{section}': 1, # 90 requests a minute
|
137
|
-
'stats1/{key}:{size}:{symbol}/{section}': 1,
|
138
|
-
'stats1/{key}:{size}:{symbol}:long/last': 1,
|
139
|
-
'stats1/{key}:{size}:{symbol}:long/hist': 1,
|
140
|
-
'stats1/{key}:{size}:{symbol}:short/last': 1,
|
141
|
-
'stats1/{key}:{size}:{symbol}:short/hist': 1,
|
142
|
-
'candles/trade:{timeframe}:{symbol}/{section}': 1, # 90 requests a minute
|
143
|
-
'candles/trade:{timeframe}:{symbol}/last': 1,
|
144
|
-
'candles/trade:{timeframe}:{symbol}/hist': 1,
|
145
|
-
},
|
146
|
-
},
|
147
|
-
'public': {
|
148
|
-
'get': {
|
149
|
-
'book/{symbol}': 1, # 90 requests a minute
|
150
|
-
# 'candles/{symbol}':0,
|
151
|
-
'lendbook/{currency}': 6, # 15 requests a minute
|
152
|
-
'lends/{currency}': 3, # 30 requests a minute
|
153
|
-
'pubticker/{symbol}': 3, # 30 requests a minute = 0.5 requests per second =>(1000ms / rateLimit) / 0.5 = 3.00003
|
154
|
-
'stats/{symbol}': 6, # 15 requests a minute = 0.25 requests per second =>(1000ms / rateLimit ) /0.25 = 6.00006(endpoint returns red html... or 'unknown symbol')
|
155
|
-
'symbols': 18, # 5 requests a minute = 0.08333 requests per second =>(1000ms / rateLimit) / 0.08333 = 18.0009
|
156
|
-
'symbols_details': 18, # 5 requests a minute
|
157
|
-
'tickers': 1, # endpoint not mentioned in v1 docs... but still responds
|
158
|
-
'trades/{symbol}': 3, # 60 requests a minute = 1 request per second =>(1000ms / rateLimit) / 1 = 1.5 ... but only works if set to 3
|
159
|
-
},
|
160
|
-
},
|
161
|
-
'private': {
|
162
|
-
'post': {
|
163
|
-
'account_fees': 18,
|
164
|
-
'account_infos': 6,
|
165
|
-
'balances': 9.036, # 10 requests a minute = 0.166 requests per second =>(1000ms / rateLimit) / 0.166 = 9.036
|
166
|
-
'basket_manage': 6,
|
167
|
-
'credits': 6,
|
168
|
-
'deposit/new': 18,
|
169
|
-
'funding/close': 6,
|
170
|
-
'history': 6, # 15 requests a minute
|
171
|
-
'history/movements': 6,
|
172
|
-
'key_info': 6,
|
173
|
-
'margin_infos': 3, # 30 requests a minute
|
174
|
-
'mytrades': 3,
|
175
|
-
'mytrades_funding': 6,
|
176
|
-
'offer/cancel': 6,
|
177
|
-
'offer/new': 6,
|
178
|
-
'offer/status': 6,
|
179
|
-
'offers': 6,
|
180
|
-
'offers/hist': 90.03, # one request per minute
|
181
|
-
'order/cancel': 0.2,
|
182
|
-
'order/cancel/all': 0.2,
|
183
|
-
'order/cancel/multi': 0.2,
|
184
|
-
'order/cancel/replace': 0.2,
|
185
|
-
'order/new': 0.2, # 450 requests a minute = 7.5 request a second =>(1000ms / rateLimit) / 7.5 = 0.2000002
|
186
|
-
'order/new/multi': 0.2,
|
187
|
-
'order/status': 0.2,
|
188
|
-
'orders': 0.2,
|
189
|
-
'orders/hist': 90.03, # one request per minute = 0.1666 =>(1000ms / rateLimit) / 0.01666 = 90.03
|
190
|
-
'position/claim': 18,
|
191
|
-
'position/close': 18,
|
192
|
-
'positions': 18,
|
193
|
-
'summary': 18,
|
194
|
-
'taken_funds': 6,
|
195
|
-
'total_taken_funds': 6,
|
196
|
-
'transfer': 18,
|
197
|
-
'unused_taken_funds': 6,
|
198
|
-
'withdraw': 18,
|
199
|
-
},
|
200
|
-
},
|
201
|
-
},
|
202
|
-
'fees': {
|
203
|
-
'trading': {
|
204
|
-
'feeSide': 'get',
|
205
|
-
'tierBased': True,
|
206
|
-
'percentage': True,
|
207
|
-
'maker': self.parse_number('0.001'),
|
208
|
-
'taker': self.parse_number('0.002'),
|
209
|
-
'tiers': {
|
210
|
-
'taker': [
|
211
|
-
[self.parse_number('0'), self.parse_number('0.002')],
|
212
|
-
[self.parse_number('500000'), self.parse_number('0.002')],
|
213
|
-
[self.parse_number('1000000'), self.parse_number('0.002')],
|
214
|
-
[self.parse_number('2500000'), self.parse_number('0.002')],
|
215
|
-
[self.parse_number('5000000'), self.parse_number('0.002')],
|
216
|
-
[self.parse_number('7500000'), self.parse_number('0.002')],
|
217
|
-
[self.parse_number('10000000'), self.parse_number('0.0018')],
|
218
|
-
[self.parse_number('15000000'), self.parse_number('0.0016')],
|
219
|
-
[self.parse_number('20000000'), self.parse_number('0.0014')],
|
220
|
-
[self.parse_number('25000000'), self.parse_number('0.0012')],
|
221
|
-
[self.parse_number('30000000'), self.parse_number('0.001')],
|
222
|
-
],
|
223
|
-
'maker': [
|
224
|
-
[self.parse_number('0'), self.parse_number('0.001')],
|
225
|
-
[self.parse_number('500000'), self.parse_number('0.0008')],
|
226
|
-
[self.parse_number('1000000'), self.parse_number('0.0006')],
|
227
|
-
[self.parse_number('2500000'), self.parse_number('0.0004')],
|
228
|
-
[self.parse_number('5000000'), self.parse_number('0.0002')],
|
229
|
-
[self.parse_number('7500000'), self.parse_number('0')],
|
230
|
-
[self.parse_number('10000000'), self.parse_number('0')],
|
231
|
-
[self.parse_number('15000000'), self.parse_number('0')],
|
232
|
-
[self.parse_number('20000000'), self.parse_number('0')],
|
233
|
-
[self.parse_number('25000000'), self.parse_number('0')],
|
234
|
-
[self.parse_number('30000000'), self.parse_number('0')],
|
235
|
-
],
|
236
|
-
},
|
237
|
-
},
|
238
|
-
'funding': {
|
239
|
-
'tierBased': False, # True for tier-based/progressive
|
240
|
-
'percentage': False, # fixed commission
|
241
|
-
# Actually deposit fees are free for larger deposits(> $1000 USD equivalent)
|
242
|
-
# these values below are deprecated, we should not hardcode fees and limits anymore
|
243
|
-
# to be reimplemented with bitfinex funding fees from their API or web endpoints
|
244
|
-
'deposit': {},
|
245
|
-
'withdraw': {},
|
246
|
-
},
|
247
|
-
},
|
248
|
-
# todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
|
249
|
-
'commonCurrencies': {
|
250
|
-
'ALG': 'ALGO', # https://github.com/ccxt/ccxt/issues/6034
|
251
|
-
'AMP': 'AMPL',
|
252
|
-
'ATO': 'ATOM', # https://github.com/ccxt/ccxt/issues/5118
|
253
|
-
'BCHABC': 'XEC',
|
254
|
-
'BCHN': 'BCH',
|
255
|
-
'DAT': 'DATA',
|
256
|
-
'DOG': 'MDOGE',
|
257
|
-
'DSH': 'DASH',
|
258
|
-
# https://github.com/ccxt/ccxt/issues/7399
|
259
|
-
# https://coinmarketcap.com/currencies/pnetwork/
|
260
|
-
# https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/
|
261
|
-
'EDO': 'PNT',
|
262
|
-
'EUS': 'EURS',
|
263
|
-
'EUT': 'EURT',
|
264
|
-
'IDX': 'ID',
|
265
|
-
'IOT': 'IOTA',
|
266
|
-
'IQX': 'IQ',
|
267
|
-
'LUNA': 'LUNC',
|
268
|
-
'LUNA2': 'LUNA',
|
269
|
-
'MNA': 'MANA',
|
270
|
-
'ORS': 'ORS Group', # conflict with Origin Sport #3230
|
271
|
-
'PAS': 'PASS',
|
272
|
-
'QSH': 'QASH',
|
273
|
-
'QTM': 'QTUM',
|
274
|
-
'RBT': 'RBTC',
|
275
|
-
'SNG': 'SNGLS',
|
276
|
-
'STJ': 'STORJ',
|
277
|
-
'TERRAUST': 'USTC',
|
278
|
-
'TSD': 'TUSD',
|
279
|
-
'YGG': 'YEED', # conflict with Yield Guild Games
|
280
|
-
'YYW': 'YOYOW',
|
281
|
-
'UDC': 'USDC',
|
282
|
-
'UST': 'USDT',
|
283
|
-
'VSY': 'VSYS',
|
284
|
-
'WAX': 'WAXP',
|
285
|
-
'XCH': 'XCHF',
|
286
|
-
'ZBT': 'ZB',
|
287
|
-
},
|
288
|
-
'exceptions': {
|
289
|
-
'exact': {
|
290
|
-
'temporarily_unavailable': ExchangeNotAvailable, # Sorry, the service is temporarily unavailable. See https://www.bitfinex.com/ for more info.
|
291
|
-
'Order could not be cancelled.': OrderNotFound, # non-existent order
|
292
|
-
'No such order found.': OrderNotFound, # ?
|
293
|
-
'Order price must be positive.': InvalidOrder, # on price <= 0
|
294
|
-
'Could not find a key matching the given X-BFX-APIKEY.': AuthenticationError,
|
295
|
-
'Key price should be a decimal number, e.g. "123.456"': InvalidOrder, # on isNaN(price)
|
296
|
-
'Key amount should be a decimal number, e.g. "123.456"': InvalidOrder, # on isNaN(amount)
|
297
|
-
'ERR_RATE_LIMIT': RateLimitExceeded,
|
298
|
-
'Ratelimit': RateLimitExceeded,
|
299
|
-
'Nonce is too small.': InvalidNonce,
|
300
|
-
'No summary found.': ExchangeError, # fetchTradingFees(summary) endpoint can give self vague error message
|
301
|
-
'Cannot evaluate your available balance, please try again': ExchangeNotAvailable,
|
302
|
-
'Unknown symbol': BadSymbol,
|
303
|
-
'Cannot complete transfer. Exchange balance insufficient.': InsufficientFunds,
|
304
|
-
'Momentary balance check. Please wait few seconds and try the transfer again.': ExchangeError,
|
305
|
-
},
|
306
|
-
'broad': {
|
307
|
-
'Invalid X-BFX-SIGNATURE': AuthenticationError,
|
308
|
-
'This API key does not have permission': PermissionDenied, # authenticated but not authorized
|
309
|
-
'not enough exchange balance for ': InsufficientFunds, # when buying cost is greater than the available quote currency
|
310
|
-
'minimum size for ': InvalidOrder, # when amount below limits.amount.min
|
311
|
-
'Invalid order': InvalidOrder, # ?
|
312
|
-
'The available balance is only': InsufficientFunds, # {"status":"error","message":"Cannot withdraw 1.0027 ETH from your exchange wallet. The available balance is only 0.0 ETH. If you have limit orders, open positions, unused or active margin funding, self will decrease your available balance. To increase it, you can cancel limit orders or reduce/close your positions.","withdrawal_id":0,"fees":"0.0027"}
|
313
|
-
},
|
314
|
-
},
|
315
|
-
'precisionMode': SIGNIFICANT_DIGITS,
|
316
|
-
'options': {
|
317
|
-
'currencyNames': {
|
318
|
-
'AGI': 'agi',
|
319
|
-
'AID': 'aid',
|
320
|
-
'AIO': 'aio',
|
321
|
-
'ANT': 'ant',
|
322
|
-
'AVT': 'aventus', # #1811
|
323
|
-
'BAT': 'bat',
|
324
|
-
# https://github.com/ccxt/ccxt/issues/5833
|
325
|
-
'BCH': 'bab', # undocumented
|
326
|
-
# 'BCH': 'bcash', # undocumented
|
327
|
-
'BCI': 'bci',
|
328
|
-
'BFT': 'bft',
|
329
|
-
'BSV': 'bsv',
|
330
|
-
'BTC': 'bitcoin',
|
331
|
-
'BTG': 'bgold',
|
332
|
-
'CFI': 'cfi',
|
333
|
-
'COMP': 'comp',
|
334
|
-
'DAI': 'dai',
|
335
|
-
'DADI': 'dad',
|
336
|
-
'DASH': 'dash',
|
337
|
-
'DATA': 'datacoin',
|
338
|
-
'DTH': 'dth',
|
339
|
-
'EDO': 'eidoo', # #1811
|
340
|
-
'ELF': 'elf',
|
341
|
-
'EOS': 'eos',
|
342
|
-
'ETC': 'ethereumc',
|
343
|
-
'ETH': 'ethereum',
|
344
|
-
'ETP': 'metaverse',
|
345
|
-
'FUN': 'fun',
|
346
|
-
'GNT': 'golem',
|
347
|
-
'IOST': 'ios',
|
348
|
-
'IOTA': 'iota',
|
349
|
-
# https://github.com/ccxt/ccxt/issues/5833
|
350
|
-
'LEO': 'let', # ETH chain
|
351
|
-
# 'LEO': 'les', # EOS chain
|
352
|
-
'LINK': 'link',
|
353
|
-
'LRC': 'lrc',
|
354
|
-
'LTC': 'litecoin',
|
355
|
-
'LYM': 'lym',
|
356
|
-
'MANA': 'mna',
|
357
|
-
'MIT': 'mit',
|
358
|
-
'MKR': 'mkr',
|
359
|
-
'MTN': 'mtn',
|
360
|
-
'NEO': 'neo',
|
361
|
-
'ODE': 'ode',
|
362
|
-
'OMG': 'omisego',
|
363
|
-
'OMNI': 'mastercoin',
|
364
|
-
'QASH': 'qash',
|
365
|
-
'QTUM': 'qtum', # #1811
|
366
|
-
'RCN': 'rcn',
|
367
|
-
'RDN': 'rdn',
|
368
|
-
'REP': 'rep',
|
369
|
-
'REQ': 'req',
|
370
|
-
'RLC': 'rlc',
|
371
|
-
'SAN': 'santiment',
|
372
|
-
'SNGLS': 'sng',
|
373
|
-
'SNT': 'status',
|
374
|
-
'SPANK': 'spk',
|
375
|
-
'STORJ': 'stj',
|
376
|
-
'TNB': 'tnb',
|
377
|
-
'TRX': 'trx',
|
378
|
-
'TUSD': 'tsd',
|
379
|
-
'USD': 'wire',
|
380
|
-
'USDC': 'udc', # https://github.com/ccxt/ccxt/issues/5833
|
381
|
-
'UTK': 'utk',
|
382
|
-
'USDT': 'tetheruso', # Tether on Omni
|
383
|
-
# 'USDT': 'tetheruse', # Tether on ERC20
|
384
|
-
# 'USDT': 'tetherusl', # Tether on Liquid
|
385
|
-
# 'USDT': 'tetherusx', # Tether on Tron
|
386
|
-
# 'USDT': 'tetheruss', # Tether on EOS
|
387
|
-
'VEE': 'vee',
|
388
|
-
'WAX': 'wax',
|
389
|
-
'XLM': 'xlm',
|
390
|
-
'XMR': 'monero',
|
391
|
-
'XRP': 'ripple',
|
392
|
-
'XVG': 'xvg',
|
393
|
-
'YOYOW': 'yoyow',
|
394
|
-
'ZEC': 'zcash',
|
395
|
-
'ZRX': 'zrx',
|
396
|
-
'XTZ': 'xtz',
|
397
|
-
},
|
398
|
-
'orderTypes': {
|
399
|
-
'limit': 'exchange limit',
|
400
|
-
'market': 'exchange market',
|
401
|
-
},
|
402
|
-
'fiat': {
|
403
|
-
'USD': 'USD',
|
404
|
-
'EUR': 'EUR',
|
405
|
-
'JPY': 'JPY',
|
406
|
-
'GBP': 'GBP',
|
407
|
-
'CNH': 'CNH',
|
408
|
-
},
|
409
|
-
'accountsByType': {
|
410
|
-
'spot': 'exchange',
|
411
|
-
'margin': 'trading',
|
412
|
-
'funding': 'deposit',
|
413
|
-
'swap': 'trading',
|
414
|
-
},
|
415
|
-
},
|
416
|
-
})
|
417
|
-
|
418
|
-
def fetch_transaction_fees(self, codes: Strings = None, params={}):
|
419
|
-
"""
|
420
|
-
@deprecated
|
421
|
-
please use fetchDepositWithdrawFees instead
|
422
|
-
|
423
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-fees
|
424
|
-
|
425
|
-
:param str[]|None codes: list of unified currency codes
|
426
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
427
|
-
:returns dict[]: a list of `fees structures <https://docs.ccxt.com/#/?id=fee-structure>`
|
428
|
-
"""
|
429
|
-
self.load_markets()
|
430
|
-
result: dict = {}
|
431
|
-
response = self.privatePostAccountFees(params)
|
432
|
-
#
|
433
|
-
# {
|
434
|
-
# "withdraw": {
|
435
|
-
# "BTC": "0.0004",
|
436
|
-
# }
|
437
|
-
# }
|
438
|
-
#
|
439
|
-
fees = self.safe_dict(response, 'withdraw', {})
|
440
|
-
ids = list(fees.keys())
|
441
|
-
for i in range(0, len(ids)):
|
442
|
-
id = ids[i]
|
443
|
-
code = self.safe_currency_code(id)
|
444
|
-
if (codes is not None) and not self.in_array(code, codes):
|
445
|
-
continue
|
446
|
-
result[code] = {
|
447
|
-
'withdraw': self.safe_number(fees, id),
|
448
|
-
'deposit': {},
|
449
|
-
'info': self.safe_number(fees, id),
|
450
|
-
}
|
451
|
-
return result
|
452
|
-
|
453
|
-
def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
|
454
|
-
"""
|
455
|
-
fetch deposit and withdraw fees
|
456
|
-
|
457
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-fees
|
458
|
-
|
459
|
-
:param str[]|None codes: list of unified currency codes
|
460
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
461
|
-
:returns dict[]: a list of `fees structures <https://docs.ccxt.com/#/?id=fee-structure>`
|
462
|
-
"""
|
463
|
-
self.load_markets()
|
464
|
-
response = self.privatePostAccountFees(params)
|
465
|
-
#
|
466
|
-
# {
|
467
|
-
# "withdraw": {
|
468
|
-
# "BTC": "0.0004",
|
469
|
-
# ...
|
470
|
-
# }
|
471
|
-
# }
|
472
|
-
#
|
473
|
-
withdraw = self.safe_list(response, 'withdraw')
|
474
|
-
return self.parse_deposit_withdraw_fees(withdraw, codes)
|
475
|
-
|
476
|
-
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
|
477
|
-
#
|
478
|
-
# '0.0004'
|
479
|
-
#
|
480
|
-
return {
|
481
|
-
'withdraw': {
|
482
|
-
'fee': self.parse_number(fee),
|
483
|
-
'percentage': None,
|
484
|
-
},
|
485
|
-
'deposit': {
|
486
|
-
'fee': None,
|
487
|
-
'percentage': None,
|
488
|
-
},
|
489
|
-
'networks': {},
|
490
|
-
'info': fee,
|
491
|
-
}
|
492
|
-
|
493
|
-
def fetch_trading_fees(self, params={}) -> TradingFees:
|
494
|
-
"""
|
495
|
-
fetch the trading fees for multiple markets
|
496
|
-
|
497
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-summary
|
498
|
-
|
499
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
500
|
-
:returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
|
501
|
-
"""
|
502
|
-
self.load_markets()
|
503
|
-
response = self.privatePostSummary(params)
|
504
|
-
#
|
505
|
-
# {
|
506
|
-
# "time": "2022-02-23T16:05:47.659000Z",
|
507
|
-
# "status": {resid_hint: null, login_last: "2022-02-23T16:05:48Z"},
|
508
|
-
# "is_locked": False,
|
509
|
-
# "leo_lev": "0",
|
510
|
-
# "leo_amount_avg": "0.0",
|
511
|
-
# "trade_vol_30d": [
|
512
|
-
# {
|
513
|
-
# "curr": "Total(USD)",
|
514
|
-
# "vol": "0.0",
|
515
|
-
# "vol_safe": "0.0",
|
516
|
-
# "vol_maker": "0.0",
|
517
|
-
# "vol_BFX": "0.0",
|
518
|
-
# "vol_BFX_safe": "0.0",
|
519
|
-
# "vol_BFX_maker": "0.0"
|
520
|
-
# }
|
521
|
-
# ],
|
522
|
-
# "fees_funding_30d": {},
|
523
|
-
# "fees_funding_total_30d": "0",
|
524
|
-
# "fees_trading_30d": {},
|
525
|
-
# "fees_trading_total_30d": "0",
|
526
|
-
# "rebates_trading_30d": {},
|
527
|
-
# "rebates_trading_total_30d": "0",
|
528
|
-
# "maker_fee": "0.001",
|
529
|
-
# "taker_fee": "0.002",
|
530
|
-
# "maker_fee_2crypto": "0.001",
|
531
|
-
# "maker_fee_2stablecoin": "0.001",
|
532
|
-
# "maker_fee_2fiat": "0.001",
|
533
|
-
# "maker_fee_2deriv": "0.0002",
|
534
|
-
# "taker_fee_2crypto": "0.002",
|
535
|
-
# "taker_fee_2stablecoin": "0.002",
|
536
|
-
# "taker_fee_2fiat": "0.002",
|
537
|
-
# "taker_fee_2deriv": "0.00065",
|
538
|
-
# "deriv_maker_rebate": "0.0002",
|
539
|
-
# "deriv_taker_fee": "0.00065",
|
540
|
-
# "trade_last": null
|
541
|
-
# }
|
542
|
-
#
|
543
|
-
result: dict = {}
|
544
|
-
fiat = self.safe_dict(self.options, 'fiat', {})
|
545
|
-
makerFee = self.safe_number(response, 'maker_fee')
|
546
|
-
takerFee = self.safe_number(response, 'taker_fee')
|
547
|
-
makerFee2Fiat = self.safe_number(response, 'maker_fee_2fiat')
|
548
|
-
takerFee2Fiat = self.safe_number(response, 'taker_fee_2fiat')
|
549
|
-
makerFee2Deriv = self.safe_number(response, 'maker_fee_2deriv')
|
550
|
-
takerFee2Deriv = self.safe_number(response, 'taker_fee_2deriv')
|
551
|
-
for i in range(0, len(self.symbols)):
|
552
|
-
symbol = self.symbols[i]
|
553
|
-
market = self.market(symbol)
|
554
|
-
fee = {
|
555
|
-
'info': response,
|
556
|
-
'symbol': symbol,
|
557
|
-
'percentage': True,
|
558
|
-
'tierBased': True,
|
559
|
-
}
|
560
|
-
if market['quote'] in fiat:
|
561
|
-
fee['maker'] = makerFee2Fiat
|
562
|
-
fee['taker'] = takerFee2Fiat
|
563
|
-
elif market['contract']:
|
564
|
-
fee['maker'] = makerFee2Deriv
|
565
|
-
fee['taker'] = takerFee2Deriv
|
566
|
-
else:
|
567
|
-
fee['maker'] = makerFee
|
568
|
-
fee['taker'] = takerFee
|
569
|
-
result[symbol] = fee
|
570
|
-
return result
|
571
|
-
|
572
|
-
def fetch_markets(self, params={}) -> List[Market]:
|
573
|
-
"""
|
574
|
-
retrieves data on all markets for bitfinex
|
575
|
-
|
576
|
-
https://docs.bitfinex.com/v1/reference/rest-public-symbols
|
577
|
-
https://docs.bitfinex.com/v1/reference/rest-public-symbol-details
|
578
|
-
|
579
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
580
|
-
:returns dict[]: an array of objects representing market data
|
581
|
-
"""
|
582
|
-
idsPromise = self.publicGetSymbols()
|
583
|
-
#
|
584
|
-
# ["btcusd", "ltcusd", "ltcbtc"]
|
585
|
-
#
|
586
|
-
detailsPromise = self.publicGetSymbolsDetails()
|
587
|
-
#
|
588
|
-
# [
|
589
|
-
# {
|
590
|
-
# "pair":"btcusd",
|
591
|
-
# "price_precision":5,
|
592
|
-
# "initial_margin":"10.0",
|
593
|
-
# "minimum_margin":"5.0",
|
594
|
-
# "maximum_order_size":"2000.0",
|
595
|
-
# "minimum_order_size":"0.0002",
|
596
|
-
# "expiration":"NA",
|
597
|
-
# "margin":true
|
598
|
-
# },
|
599
|
-
# ]
|
600
|
-
#
|
601
|
-
ids, details = [idsPromise, detailsPromise]
|
602
|
-
result = []
|
603
|
-
for i in range(0, len(details)):
|
604
|
-
market = details[i]
|
605
|
-
id = self.safe_string(market, 'pair')
|
606
|
-
if not self.in_array(id, ids):
|
607
|
-
continue
|
608
|
-
id = id.upper()
|
609
|
-
baseId = None
|
610
|
-
quoteId = None
|
611
|
-
if id.find(':') >= 0:
|
612
|
-
parts = id.split(':')
|
613
|
-
baseId = parts[0]
|
614
|
-
quoteId = parts[1]
|
615
|
-
else:
|
616
|
-
baseId = id[0:3]
|
617
|
-
quoteId = id[3:6]
|
618
|
-
base = self.safe_currency_code(baseId)
|
619
|
-
quote = self.safe_currency_code(quoteId)
|
620
|
-
symbol = base + '/' + quote
|
621
|
-
type = 'spot'
|
622
|
-
if id.find('F0') > -1:
|
623
|
-
type = 'swap'
|
624
|
-
result.append({
|
625
|
-
'id': id,
|
626
|
-
'symbol': symbol,
|
627
|
-
'base': base,
|
628
|
-
'quote': quote,
|
629
|
-
'settle': None,
|
630
|
-
'baseId': baseId,
|
631
|
-
'quoteId': quoteId,
|
632
|
-
'settleId': None,
|
633
|
-
'type': type,
|
634
|
-
'spot': (type == 'spot'),
|
635
|
-
'margin': self.safe_bool(market, 'margin'),
|
636
|
-
'swap': (type == 'swap'),
|
637
|
-
'future': False,
|
638
|
-
'option': False,
|
639
|
-
'active': True,
|
640
|
-
'contract': (type == 'swap'),
|
641
|
-
'linear': None,
|
642
|
-
'inverse': None,
|
643
|
-
'contractSize': None,
|
644
|
-
'expiry': None,
|
645
|
-
'expiryDatetime': None,
|
646
|
-
'strike': None,
|
647
|
-
'optionType': None,
|
648
|
-
'precision': {
|
649
|
-
# https://docs.bitfinex.com/docs/introduction#amount-precision
|
650
|
-
# The amount field allows up to 8 decimals.
|
651
|
-
# Anything exceeding self will be rounded to the 8th decimal.
|
652
|
-
'amount': int('8'),
|
653
|
-
'price': self.safe_integer(market, 'price_precision'),
|
654
|
-
},
|
655
|
-
'limits': {
|
656
|
-
'leverage': {
|
657
|
-
'min': None,
|
658
|
-
'max': None,
|
659
|
-
},
|
660
|
-
'amount': {
|
661
|
-
'min': self.safe_number(market, 'minimum_order_size'),
|
662
|
-
'max': self.safe_number(market, 'maximum_order_size'),
|
663
|
-
},
|
664
|
-
'price': {
|
665
|
-
'min': self.parse_number('1e-8'),
|
666
|
-
'max': None,
|
667
|
-
},
|
668
|
-
'cost': {
|
669
|
-
'min': None,
|
670
|
-
'max': None,
|
671
|
-
},
|
672
|
-
},
|
673
|
-
'created': None,
|
674
|
-
'info': market,
|
675
|
-
})
|
676
|
-
return result
|
677
|
-
|
678
|
-
def amount_to_precision(self, symbol, amount):
|
679
|
-
# https://docs.bitfinex.com/docs/introduction#amount-precision
|
680
|
-
# The amount field allows up to 8 decimals.
|
681
|
-
# Anything exceeding self will be rounded to the 8th decimal.
|
682
|
-
symbol = self.safe_symbol(symbol)
|
683
|
-
return self.decimal_to_precision(amount, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)
|
684
|
-
|
685
|
-
def price_to_precision(self, symbol, price):
|
686
|
-
symbol = self.safe_symbol(symbol)
|
687
|
-
price = self.decimal_to_precision(price, ROUND, self.markets[symbol]['precision']['price'], self.precisionMode)
|
688
|
-
# https://docs.bitfinex.com/docs/introduction#price-precision
|
689
|
-
# The precision level of all trading prices is based on significant figures.
|
690
|
-
# All pairs on Bitfinex use up to 5 significant digits and up to 8 decimals(e.g. 1.2345, 123.45, 1234.5, 0.00012345).
|
691
|
-
# Prices submit with a precision larger than 5 will be cut by the API.
|
692
|
-
return self.decimal_to_precision(price, TRUNCATE, 8, DECIMAL_PLACES)
|
693
|
-
|
694
|
-
def fetch_balance(self, params={}) -> Balances:
|
695
|
-
"""
|
696
|
-
query for balance and get the amount of funds available for trading or funds locked in orders
|
697
|
-
|
698
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-wallet-balances
|
699
|
-
|
700
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
701
|
-
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
702
|
-
"""
|
703
|
-
self.load_markets()
|
704
|
-
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
|
705
|
-
requestedType = self.safe_string(params, 'type', 'exchange')
|
706
|
-
accountType = self.safe_string(accountsByType, requestedType, requestedType)
|
707
|
-
if accountType is None:
|
708
|
-
keys = list(accountsByType.keys())
|
709
|
-
raise ExchangeError(self.id + ' fetchBalance() type parameter must be one of ' + ', '.join(keys))
|
710
|
-
query = self.omit(params, 'type')
|
711
|
-
response = self.privatePostBalances(query)
|
712
|
-
# [{type: "deposit",
|
713
|
-
# "currency": "btc",
|
714
|
-
# "amount": "0.00116721",
|
715
|
-
# "available": "0.00116721"},
|
716
|
-
# {type: "exchange",
|
717
|
-
# "currency": "ust",
|
718
|
-
# "amount": "0.0000002",
|
719
|
-
# "available": "0.0000002"},
|
720
|
-
# {type: "trading",
|
721
|
-
# "currency": "btc",
|
722
|
-
# "amount": "0.0005",
|
723
|
-
# "available": "0.0005"}],
|
724
|
-
result: dict = {'info': response}
|
725
|
-
isDerivative = requestedType == 'derivatives'
|
726
|
-
for i in range(0, len(response)):
|
727
|
-
balance = response[i]
|
728
|
-
type = self.safe_string(balance, 'type')
|
729
|
-
currencyId = self.safe_string_lower(balance, 'currency', '')
|
730
|
-
start = len(currencyId) - 2
|
731
|
-
isDerivativeCode = currencyId[start:] == 'f0'
|
732
|
-
# self will only filter the derivative codes if the requestedType is 'derivatives'
|
733
|
-
derivativeCondition = (not isDerivative or isDerivativeCode)
|
734
|
-
if (accountType == type) and derivativeCondition:
|
735
|
-
code = self.safe_currency_code(currencyId)
|
736
|
-
# bitfinex had BCH previously, now it's BAB, but the old
|
737
|
-
# BCH symbol is kept for backward-compatibility
|
738
|
-
# we need a workaround here so that the old BCH balance
|
739
|
-
# would not override the new BAB balance(BAB is unified to BCH)
|
740
|
-
# https://github.com/ccxt/ccxt/issues/4989
|
741
|
-
if not (code in result):
|
742
|
-
account = self.account()
|
743
|
-
account['free'] = self.safe_string(balance, 'available')
|
744
|
-
account['total'] = self.safe_string(balance, 'amount')
|
745
|
-
result[code] = account
|
746
|
-
return self.safe_balance(result)
|
747
|
-
|
748
|
-
def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
749
|
-
"""
|
750
|
-
transfer currency internally between wallets on the same account
|
751
|
-
|
752
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-transfer-between-wallets
|
753
|
-
|
754
|
-
:param str code: unified currency code
|
755
|
-
:param float amount: amount to transfer
|
756
|
-
:param str fromAccount: account to transfer from
|
757
|
-
:param str toAccount: account to transfer to
|
758
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
759
|
-
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
|
760
|
-
"""
|
761
|
-
# transferring between derivatives wallet and regular wallet is not documented in their API
|
762
|
-
# however we support it in CCXT(from just looking at web inspector)
|
763
|
-
self.load_markets()
|
764
|
-
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
|
765
|
-
fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
|
766
|
-
toId = self.safe_string(accountsByType, toAccount, toAccount)
|
767
|
-
currency = self.currency(code)
|
768
|
-
fromCurrencyId = self.convert_derivatives_id(currency['id'], fromAccount)
|
769
|
-
toCurrencyId = self.convert_derivatives_id(currency['id'], toAccount)
|
770
|
-
requestedAmount = self.currency_to_precision(code, amount)
|
771
|
-
request: dict = {
|
772
|
-
'amount': requestedAmount,
|
773
|
-
'currency': fromCurrencyId,
|
774
|
-
'currency_to': toCurrencyId,
|
775
|
-
'walletfrom': fromId,
|
776
|
-
'walletto': toId,
|
777
|
-
}
|
778
|
-
response = self.privatePostTransfer(self.extend(request, params))
|
779
|
-
#
|
780
|
-
# [
|
781
|
-
# {
|
782
|
-
# "status": "success",
|
783
|
-
# "message": "0.0001 Bitcoin transfered from Margin to Exchange"
|
784
|
-
# }
|
785
|
-
# ]
|
786
|
-
#
|
787
|
-
result = self.safe_value(response, 0)
|
788
|
-
message = self.safe_string(result, 'message')
|
789
|
-
if message is None:
|
790
|
-
raise ExchangeError(self.id + ' transfer failed')
|
791
|
-
return self.extend(self.parse_transfer(result, currency), {
|
792
|
-
'fromAccount': fromAccount,
|
793
|
-
'toAccount': toAccount,
|
794
|
-
'amount': self.parse_number(requestedAmount),
|
795
|
-
})
|
796
|
-
|
797
|
-
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
|
798
|
-
#
|
799
|
-
# {
|
800
|
-
# "status": "success",
|
801
|
-
# "message": "0.0001 Bitcoin transfered from Margin to Exchange"
|
802
|
-
# }
|
803
|
-
#
|
804
|
-
return {
|
805
|
-
'info': transfer,
|
806
|
-
'id': None,
|
807
|
-
'timestamp': None,
|
808
|
-
'datetime': None,
|
809
|
-
'currency': self.safe_currency_code(None, currency),
|
810
|
-
'amount': None,
|
811
|
-
'fromAccount': None,
|
812
|
-
'toAccount': None,
|
813
|
-
'status': self.parse_transfer_status(self.safe_string(transfer, 'status')),
|
814
|
-
}
|
815
|
-
|
816
|
-
def parse_transfer_status(self, status: Str) -> Str:
|
817
|
-
statuses: dict = {
|
818
|
-
'SUCCESS': 'ok',
|
819
|
-
}
|
820
|
-
return self.safe_string(statuses, status, status)
|
821
|
-
|
822
|
-
def convert_derivatives_id(self, currencyId, type):
|
823
|
-
start = len(currencyId) - 2
|
824
|
-
isDerivativeCode = currencyId[start:] == 'F0'
|
825
|
-
if (type != 'derivatives' and type != 'trading' and type != 'margin') and isDerivativeCode:
|
826
|
-
currencyId = currencyId[0:start]
|
827
|
-
elif type == 'derivatives' and not isDerivativeCode:
|
828
|
-
currencyId = currencyId + 'F0'
|
829
|
-
return currencyId
|
830
|
-
|
831
|
-
def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
832
|
-
"""
|
833
|
-
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
834
|
-
|
835
|
-
https://docs.bitfinex.com/v1/reference/rest-public-orderbook
|
836
|
-
|
837
|
-
:param str symbol: unified symbol of the market to fetch the order book for
|
838
|
-
:param int [limit]: the maximum amount of order book entries to return
|
839
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
840
|
-
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
841
|
-
"""
|
842
|
-
self.load_markets()
|
843
|
-
market = self.market(symbol)
|
844
|
-
request: dict = {
|
845
|
-
'symbol': market['id'],
|
846
|
-
}
|
847
|
-
if limit is not None:
|
848
|
-
request['limit_bids'] = limit
|
849
|
-
request['limit_asks'] = limit
|
850
|
-
response = self.publicGetBookSymbol(self.extend(request, params))
|
851
|
-
return self.parse_order_book(response, market['symbol'], None, 'bids', 'asks', 'price', 'amount')
|
852
|
-
|
853
|
-
def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
854
|
-
"""
|
855
|
-
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
856
|
-
:param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
857
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
858
|
-
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
859
|
-
"""
|
860
|
-
self.load_markets()
|
861
|
-
symbols = self.market_symbols(symbols)
|
862
|
-
response = self.publicGetTickers(params)
|
863
|
-
result: dict = {}
|
864
|
-
for i in range(0, len(response)):
|
865
|
-
ticker = self.parse_ticker(response[i])
|
866
|
-
symbol = ticker['symbol']
|
867
|
-
result[symbol] = ticker
|
868
|
-
return self.filter_by_array_tickers(result, 'symbol', symbols)
|
869
|
-
|
870
|
-
def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
871
|
-
"""
|
872
|
-
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
873
|
-
|
874
|
-
https://docs.bitfinex.com/v1/reference/rest-public-ticker
|
875
|
-
|
876
|
-
:param str symbol: unified symbol of the market to fetch the ticker for
|
877
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
878
|
-
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
879
|
-
"""
|
880
|
-
self.load_markets()
|
881
|
-
market = self.market(symbol)
|
882
|
-
request: dict = {
|
883
|
-
'symbol': market['id'],
|
884
|
-
}
|
885
|
-
ticker = self.publicGetPubtickerSymbol(self.extend(request, params))
|
886
|
-
#
|
887
|
-
# {
|
888
|
-
# mid: '63560.5',
|
889
|
-
# bid: '63560.0',
|
890
|
-
# ask: '63561.0',
|
891
|
-
# last_price: '63547.0',
|
892
|
-
# low: '62812.0',
|
893
|
-
# high: '64480.0',
|
894
|
-
# volume: '517.25634977',
|
895
|
-
# timestamp: '1715102384.9849467'
|
896
|
-
# }
|
897
|
-
#
|
898
|
-
return self.parse_ticker(ticker, market)
|
899
|
-
|
900
|
-
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
901
|
-
#
|
902
|
-
# {
|
903
|
-
# mid: '63560.5',
|
904
|
-
# bid: '63560.0',
|
905
|
-
# ask: '63561.0',
|
906
|
-
# last_price: '63547.0',
|
907
|
-
# low: '62812.0',
|
908
|
-
# high: '64480.0',
|
909
|
-
# volume: '517.25634977',
|
910
|
-
# timestamp: '1715102384.9849467'
|
911
|
-
# }
|
912
|
-
#
|
913
|
-
timestamp = self.safe_timestamp(ticker, 'timestamp')
|
914
|
-
marketId = self.safe_string(ticker, 'pair')
|
915
|
-
market = self.safe_market(marketId, market)
|
916
|
-
symbol = market['symbol']
|
917
|
-
last = self.safe_string(ticker, 'last_price')
|
918
|
-
return self.safe_ticker({
|
919
|
-
'symbol': symbol,
|
920
|
-
'timestamp': timestamp,
|
921
|
-
'datetime': self.iso8601(timestamp),
|
922
|
-
'high': self.safe_string(ticker, 'high'),
|
923
|
-
'low': self.safe_string(ticker, 'low'),
|
924
|
-
'bid': self.safe_string(ticker, 'bid'),
|
925
|
-
'bidVolume': None,
|
926
|
-
'ask': self.safe_string(ticker, 'ask'),
|
927
|
-
'askVolume': None,
|
928
|
-
'vwap': None,
|
929
|
-
'open': None,
|
930
|
-
'close': last,
|
931
|
-
'last': last,
|
932
|
-
'previousClose': None,
|
933
|
-
'change': None,
|
934
|
-
'percentage': None,
|
935
|
-
'average': self.safe_string(ticker, 'mid'),
|
936
|
-
'baseVolume': self.safe_string(ticker, 'volume'),
|
937
|
-
'quoteVolume': None,
|
938
|
-
'info': ticker,
|
939
|
-
}, market)
|
940
|
-
|
941
|
-
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
942
|
-
#
|
943
|
-
# fetchTrades(public) v1
|
944
|
-
#
|
945
|
-
# {
|
946
|
-
# "timestamp":1637258380,
|
947
|
-
# "tid":894452833,
|
948
|
-
# "price":"0.99941",
|
949
|
-
# "amount":"261.38",
|
950
|
-
# "exchange":"bitfinex",
|
951
|
-
# "type":"sell"
|
952
|
-
# }
|
953
|
-
#
|
954
|
-
# fetchMyTrades(private) v1
|
955
|
-
#
|
956
|
-
# {
|
957
|
-
# "price":"0.99941",
|
958
|
-
# "amount":"261.38",
|
959
|
-
# "timestamp":"1637258380.0",
|
960
|
-
# "type":"Sell",
|
961
|
-
# "fee_currency":"UST",
|
962
|
-
# "fee_amount":"-0.52245157",
|
963
|
-
# "tid":894452833,
|
964
|
-
# "order_id":78819731373
|
965
|
-
# }
|
966
|
-
#
|
967
|
-
# {
|
968
|
-
# "price":"0.99958",
|
969
|
-
# "amount":"261.90514",
|
970
|
-
# "timestamp":"1637258238.0",
|
971
|
-
# "type":"Buy",
|
972
|
-
# "fee_currency":"UDC",
|
973
|
-
# "fee_amount":"-0.52381028",
|
974
|
-
# "tid":894452800,
|
975
|
-
# "order_id":78819504838
|
976
|
-
# }
|
977
|
-
#
|
978
|
-
id = self.safe_string(trade, 'tid')
|
979
|
-
timestamp = self.safe_timestamp(trade, 'timestamp')
|
980
|
-
type = None
|
981
|
-
side = self.safe_string_lower(trade, 'type')
|
982
|
-
orderId = self.safe_string(trade, 'order_id')
|
983
|
-
priceString = self.safe_string(trade, 'price')
|
984
|
-
amountString = self.safe_string(trade, 'amount')
|
985
|
-
fee = None
|
986
|
-
if 'fee_amount' in trade:
|
987
|
-
feeCostString = Precise.string_neg(self.safe_string(trade, 'fee_amount'))
|
988
|
-
feeCurrencyId = self.safe_string(trade, 'fee_currency')
|
989
|
-
feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
|
990
|
-
fee = {
|
991
|
-
'cost': feeCostString,
|
992
|
-
'currency': feeCurrencyCode,
|
993
|
-
}
|
994
|
-
return self.safe_trade({
|
995
|
-
'id': id,
|
996
|
-
'info': trade,
|
997
|
-
'timestamp': timestamp,
|
998
|
-
'datetime': self.iso8601(timestamp),
|
999
|
-
'symbol': market['symbol'],
|
1000
|
-
'type': type,
|
1001
|
-
'order': orderId,
|
1002
|
-
'side': side,
|
1003
|
-
'takerOrMaker': None,
|
1004
|
-
'price': priceString,
|
1005
|
-
'amount': amountString,
|
1006
|
-
'cost': None,
|
1007
|
-
'fee': fee,
|
1008
|
-
}, market)
|
1009
|
-
|
1010
|
-
def fetch_trades(self, symbol: str, since: Int = None, limit: Int = 50, params={}) -> List[Trade]:
|
1011
|
-
"""
|
1012
|
-
get the list of most recent trades for a particular symbol
|
1013
|
-
|
1014
|
-
https://docs.bitfinex.com/v1/reference/rest-public-trades
|
1015
|
-
|
1016
|
-
:param str symbol: unified symbol of the market to fetch trades for
|
1017
|
-
:param int [since]: timestamp in ms of the earliest trade to fetch
|
1018
|
-
:param int [limit]: the maximum amount of trades to fetch
|
1019
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1020
|
-
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
1021
|
-
"""
|
1022
|
-
self.load_markets()
|
1023
|
-
market = self.market(symbol)
|
1024
|
-
request: dict = {
|
1025
|
-
'symbol': market['id'],
|
1026
|
-
'limit_trades': limit,
|
1027
|
-
}
|
1028
|
-
if since is not None:
|
1029
|
-
request['timestamp'] = self.parse_to_int(since / 1000)
|
1030
|
-
response = self.publicGetTradesSymbol(self.extend(request, params))
|
1031
|
-
#
|
1032
|
-
# [
|
1033
|
-
# {
|
1034
|
-
# "timestamp": "1694284565",
|
1035
|
-
# "tid": "1415415034",
|
1036
|
-
# "price": "25862.0",
|
1037
|
-
# "amount": "0.00020685",
|
1038
|
-
# "exchange": "bitfinex",
|
1039
|
-
# "type": "buy"
|
1040
|
-
# },
|
1041
|
-
# ]
|
1042
|
-
#
|
1043
|
-
return self.parse_trades(response, market, since, limit)
|
1044
|
-
|
1045
|
-
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
1046
|
-
"""
|
1047
|
-
fetch all trades made by the user
|
1048
|
-
|
1049
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-past-trades
|
1050
|
-
|
1051
|
-
:param str symbol: unified market symbol
|
1052
|
-
:param int [since]: the earliest time in ms to fetch trades for
|
1053
|
-
:param int [limit]: the maximum number of trades structures to retrieve
|
1054
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1055
|
-
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
1056
|
-
"""
|
1057
|
-
if symbol is None:
|
1058
|
-
raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
|
1059
|
-
self.load_markets()
|
1060
|
-
market = self.market(symbol)
|
1061
|
-
request: dict = {
|
1062
|
-
'symbol': market['id'],
|
1063
|
-
}
|
1064
|
-
if limit is not None:
|
1065
|
-
request['limit_trades'] = limit
|
1066
|
-
if since is not None:
|
1067
|
-
request['timestamp'] = self.parse_to_int(since / 1000)
|
1068
|
-
response = self.privatePostMytrades(self.extend(request, params))
|
1069
|
-
return self.parse_trades(response, market, since, limit)
|
1070
|
-
|
1071
|
-
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
1072
|
-
"""
|
1073
|
-
create a trade order
|
1074
|
-
|
1075
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-new-order
|
1076
|
-
|
1077
|
-
:param str symbol: unified symbol of the market to create an order in
|
1078
|
-
:param str type: 'market' or 'limit'
|
1079
|
-
:param str side: 'buy' or 'sell'
|
1080
|
-
:param float amount: how much of currency you want to trade in units of base currency
|
1081
|
-
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
1082
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1083
|
-
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
1084
|
-
"""
|
1085
|
-
self.load_markets()
|
1086
|
-
market = self.market(symbol)
|
1087
|
-
postOnly = self.safe_bool(params, 'postOnly', False)
|
1088
|
-
type = type.lower()
|
1089
|
-
params = self.omit(params, ['postOnly'])
|
1090
|
-
if market['spot']:
|
1091
|
-
# although they claim that type needs to be 'exchange limit' or 'exchange market'
|
1092
|
-
# in fact that's not the case for swap markets
|
1093
|
-
type = self.safe_string_lower(self.options['orderTypes'], type, type)
|
1094
|
-
request: dict = {
|
1095
|
-
'symbol': market['id'],
|
1096
|
-
'side': side,
|
1097
|
-
'amount': self.amount_to_precision(symbol, amount),
|
1098
|
-
'type': type,
|
1099
|
-
'ocoorder': False,
|
1100
|
-
'buy_price_oco': 0,
|
1101
|
-
'sell_price_oco': 0,
|
1102
|
-
}
|
1103
|
-
if type.find('market') > -1:
|
1104
|
-
request['price'] = str(self.nonce())
|
1105
|
-
else:
|
1106
|
-
request['price'] = self.price_to_precision(symbol, price)
|
1107
|
-
if postOnly:
|
1108
|
-
request['is_postonly'] = True
|
1109
|
-
response = self.privatePostOrderNew(self.extend(request, params))
|
1110
|
-
return self.parse_order(response, market)
|
1111
|
-
|
1112
|
-
def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
|
1113
|
-
self.load_markets()
|
1114
|
-
order: dict = {
|
1115
|
-
'order_id': int(id),
|
1116
|
-
}
|
1117
|
-
if price is not None:
|
1118
|
-
order['price'] = self.price_to_precision(symbol, price)
|
1119
|
-
if amount is not None:
|
1120
|
-
order['amount'] = self.number_to_string(amount)
|
1121
|
-
if symbol is not None:
|
1122
|
-
order['symbol'] = self.market_id(symbol)
|
1123
|
-
if side is not None:
|
1124
|
-
order['side'] = side
|
1125
|
-
if type is not None:
|
1126
|
-
order['type'] = self.safe_string(self.options['orderTypes'], type, type)
|
1127
|
-
response = self.privatePostOrderCancelReplace(self.extend(order, params))
|
1128
|
-
return self.parse_order(response)
|
1129
|
-
|
1130
|
-
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
1131
|
-
"""
|
1132
|
-
cancels an open order
|
1133
|
-
|
1134
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-cancel-order
|
1135
|
-
|
1136
|
-
:param str id: order id
|
1137
|
-
:param str symbol: not used by bitfinex cancelOrder()
|
1138
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1139
|
-
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
1140
|
-
"""
|
1141
|
-
self.load_markets()
|
1142
|
-
request: dict = {
|
1143
|
-
'order_id': int(id),
|
1144
|
-
}
|
1145
|
-
response = self.privatePostOrderCancel(self.extend(request, params))
|
1146
|
-
#
|
1147
|
-
# {
|
1148
|
-
# id: '161236928925',
|
1149
|
-
# cid: '1720172026812',
|
1150
|
-
# cid_date: '2024-07-05',
|
1151
|
-
# gid: null,
|
1152
|
-
# symbol: 'adaust',
|
1153
|
-
# exchange: 'bitfinex',
|
1154
|
-
# price: '0.33',
|
1155
|
-
# avg_execution_price: '0.0',
|
1156
|
-
# side: 'buy',
|
1157
|
-
# type: 'exchange limit',
|
1158
|
-
# timestamp: '1720172026.813',
|
1159
|
-
# is_live: True,
|
1160
|
-
# is_cancelled: False,
|
1161
|
-
# is_hidden: False,
|
1162
|
-
# oco_order: null,
|
1163
|
-
# was_forced: False,
|
1164
|
-
# original_amount: '10.0',
|
1165
|
-
# remaining_amount: '10.0',
|
1166
|
-
# executed_amount: '0.0',
|
1167
|
-
# src: 'api',
|
1168
|
-
# meta: {}
|
1169
|
-
# }
|
1170
|
-
#
|
1171
|
-
return self.parse_order(response)
|
1172
|
-
|
1173
|
-
def cancel_all_orders(self, symbol: Str = None, params={}):
|
1174
|
-
"""
|
1175
|
-
cancel all open orders
|
1176
|
-
|
1177
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-cancel-all-orders
|
1178
|
-
|
1179
|
-
:param str symbol: not used by bitfinex cancelAllOrders
|
1180
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1181
|
-
:returns dict: response from exchange
|
1182
|
-
"""
|
1183
|
-
response = self.privatePostOrderCancelAll(params)
|
1184
|
-
#
|
1185
|
-
# {result: 'Submitting 1 order cancellations.'}
|
1186
|
-
#
|
1187
|
-
return [
|
1188
|
-
self.safe_order({
|
1189
|
-
'info': response,
|
1190
|
-
}),
|
1191
|
-
]
|
1192
|
-
|
1193
|
-
def parse_order(self, order: dict, market: Market = None) -> Order:
|
1194
|
-
#
|
1195
|
-
# {
|
1196
|
-
# "id": 57334010955,
|
1197
|
-
# "cid": 1611584840966,
|
1198
|
-
# "cid_date": null,
|
1199
|
-
# "gid": null,
|
1200
|
-
# "symbol": "ltcbtc",
|
1201
|
-
# "exchange": null,
|
1202
|
-
# "price": "0.0042125",
|
1203
|
-
# "avg_execution_price": "0.0042097",
|
1204
|
-
# "side": "sell",
|
1205
|
-
# "type": "exchange market",
|
1206
|
-
# "timestamp": "1611584841.0",
|
1207
|
-
# "is_live": False,
|
1208
|
-
# "is_cancelled": False,
|
1209
|
-
# "is_hidden": 0,
|
1210
|
-
# "oco_order": 0,
|
1211
|
-
# "was_forced": False,
|
1212
|
-
# "original_amount": "0.205176",
|
1213
|
-
# "remaining_amount": "0.0",
|
1214
|
-
# "executed_amount": "0.205176",
|
1215
|
-
# "src": "web"
|
1216
|
-
# }
|
1217
|
-
#
|
1218
|
-
side = self.safe_string(order, 'side')
|
1219
|
-
open = self.safe_bool(order, 'is_live')
|
1220
|
-
canceled = self.safe_bool(order, 'is_cancelled')
|
1221
|
-
status = None
|
1222
|
-
if open:
|
1223
|
-
status = 'open'
|
1224
|
-
elif canceled:
|
1225
|
-
status = 'canceled'
|
1226
|
-
else:
|
1227
|
-
status = 'closed'
|
1228
|
-
marketId = self.safe_string_upper(order, 'symbol')
|
1229
|
-
symbol = self.safe_symbol(marketId, market)
|
1230
|
-
orderType = self.safe_string(order, 'type', '')
|
1231
|
-
exchange = orderType.find('exchange ') >= 0
|
1232
|
-
if exchange:
|
1233
|
-
parts = order['type'].split(' ')
|
1234
|
-
orderType = parts[1]
|
1235
|
-
timestamp = self.safe_timestamp(order, 'timestamp')
|
1236
|
-
id = self.safe_string(order, 'id')
|
1237
|
-
return self.safe_order({
|
1238
|
-
'info': order,
|
1239
|
-
'id': id,
|
1240
|
-
'clientOrderId': None,
|
1241
|
-
'timestamp': timestamp,
|
1242
|
-
'datetime': self.iso8601(timestamp),
|
1243
|
-
'lastTradeTimestamp': None,
|
1244
|
-
'symbol': symbol,
|
1245
|
-
'type': orderType,
|
1246
|
-
'timeInForce': None,
|
1247
|
-
'postOnly': None,
|
1248
|
-
'side': side,
|
1249
|
-
'price': self.safe_string(order, 'price'),
|
1250
|
-
'triggerPrice': None,
|
1251
|
-
'average': self.safe_string(order, 'avg_execution_price'),
|
1252
|
-
'amount': self.safe_string(order, 'original_amount'),
|
1253
|
-
'remaining': self.safe_string(order, 'remaining_amount'),
|
1254
|
-
'filled': self.safe_string(order, 'executed_amount'),
|
1255
|
-
'status': status,
|
1256
|
-
'fee': None,
|
1257
|
-
'cost': None,
|
1258
|
-
'trades': None,
|
1259
|
-
}, market)
|
1260
|
-
|
1261
|
-
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
1262
|
-
"""
|
1263
|
-
fetch all unfilled currently open orders
|
1264
|
-
|
1265
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-active-orders
|
1266
|
-
|
1267
|
-
:param str symbol: unified market symbol
|
1268
|
-
:param int [since]: the earliest time in ms to fetch open orders for
|
1269
|
-
:param int [limit]: the maximum number of open orders structures to retrieve
|
1270
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1271
|
-
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
1272
|
-
"""
|
1273
|
-
self.load_markets()
|
1274
|
-
if symbol is not None:
|
1275
|
-
if not (symbol in self.markets):
|
1276
|
-
raise ExchangeError(self.id + ' has no symbol ' + symbol)
|
1277
|
-
response = self.privatePostOrders(params)
|
1278
|
-
orders = self.parse_orders(response, None, since, limit)
|
1279
|
-
if symbol is not None:
|
1280
|
-
orders = self.filter_by(orders, 'symbol', symbol)
|
1281
|
-
return orders
|
1282
|
-
|
1283
|
-
def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
1284
|
-
"""
|
1285
|
-
fetches information on multiple closed orders made by the user
|
1286
|
-
|
1287
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-orders-history
|
1288
|
-
|
1289
|
-
:param str symbol: unified market symbol of the market orders were made in
|
1290
|
-
:param int [since]: the earliest time in ms to fetch orders for
|
1291
|
-
:param int [limit]: the maximum number of order structures to retrieve
|
1292
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1293
|
-
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
1294
|
-
"""
|
1295
|
-
self.load_markets()
|
1296
|
-
symbol = self.symbol(symbol)
|
1297
|
-
request: dict = {}
|
1298
|
-
if limit is not None:
|
1299
|
-
request['limit'] = limit
|
1300
|
-
response = self.privatePostOrdersHist(self.extend(request, params))
|
1301
|
-
orders = self.parse_orders(response, None, since, limit)
|
1302
|
-
if symbol is not None:
|
1303
|
-
orders = self.filter_by(orders, 'symbol', symbol)
|
1304
|
-
orders = self.filter_by_array(orders, 'status', ['closed', 'canceled'], False)
|
1305
|
-
return orders
|
1306
|
-
|
1307
|
-
def fetch_order(self, id: str, symbol: Str = None, params={}):
|
1308
|
-
"""
|
1309
|
-
fetches information on an order made by the user
|
1310
|
-
|
1311
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-order-status
|
1312
|
-
|
1313
|
-
:param str id: the order id
|
1314
|
-
:param str symbol: not used by bitfinex fetchOrder
|
1315
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1316
|
-
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
1317
|
-
"""
|
1318
|
-
self.load_markets()
|
1319
|
-
request: dict = {
|
1320
|
-
'order_id': int(id),
|
1321
|
-
}
|
1322
|
-
response = self.privatePostOrderStatus(self.extend(request, params))
|
1323
|
-
return self.parse_order(response)
|
1324
|
-
|
1325
|
-
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
1326
|
-
#
|
1327
|
-
# [
|
1328
|
-
# 1457539800000,
|
1329
|
-
# 0.02594,
|
1330
|
-
# 0.02594,
|
1331
|
-
# 0.02594,
|
1332
|
-
# 0.02594,
|
1333
|
-
# 0.1
|
1334
|
-
# ]
|
1335
|
-
#
|
1336
|
-
return [
|
1337
|
-
self.safe_integer(ohlcv, 0),
|
1338
|
-
self.safe_number(ohlcv, 1),
|
1339
|
-
self.safe_number(ohlcv, 3),
|
1340
|
-
self.safe_number(ohlcv, 4),
|
1341
|
-
self.safe_number(ohlcv, 2),
|
1342
|
-
self.safe_number(ohlcv, 5),
|
1343
|
-
]
|
1344
|
-
|
1345
|
-
def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
1346
|
-
"""
|
1347
|
-
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
1348
|
-
|
1349
|
-
https://docs.bitfinex.com/reference/rest-public-candles#aggregate-funding-currency-candles
|
1350
|
-
|
1351
|
-
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
1352
|
-
:param str timeframe: the length of time each candle represents
|
1353
|
-
:param int [since]: timestamp in ms of the earliest candle to fetch
|
1354
|
-
:param int [limit]: the maximum amount of candles to fetch
|
1355
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1356
|
-
:param int [params.until]: timestamp in ms of the latest candle to fetch
|
1357
|
-
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
1358
|
-
"""
|
1359
|
-
self.load_markets()
|
1360
|
-
if limit is None:
|
1361
|
-
limit = 100
|
1362
|
-
else:
|
1363
|
-
limit = min(limit, 10000)
|
1364
|
-
market = self.market(symbol)
|
1365
|
-
v2id = 't' + market['id']
|
1366
|
-
request: dict = {
|
1367
|
-
'symbol': v2id,
|
1368
|
-
'timeframe': self.safe_string(self.timeframes, timeframe, timeframe),
|
1369
|
-
'sort': 1,
|
1370
|
-
'limit': limit,
|
1371
|
-
}
|
1372
|
-
until = self.safe_integer(params, 'until')
|
1373
|
-
if since is not None:
|
1374
|
-
request['start'] = since
|
1375
|
-
elif until is not None:
|
1376
|
-
duration = self.parse_timeframe(timeframe)
|
1377
|
-
request['start'] = until - ((limit - 1) * duration * 1000)
|
1378
|
-
if until is not None:
|
1379
|
-
request['end'] = until
|
1380
|
-
params = self.omit(params, 'until')
|
1381
|
-
response = self.v2GetCandlesTradeTimeframeSymbolHist(self.extend(request, params))
|
1382
|
-
#
|
1383
|
-
# [
|
1384
|
-
# [1457539800000,0.02594,0.02594,0.02594,0.02594,0.1],
|
1385
|
-
# [1457547300000,0.02577,0.02577,0.02577,0.02577,0.01],
|
1386
|
-
# [1457550240000,0.0255,0.0253,0.0255,0.0252,3.2640000000000002],
|
1387
|
-
# ]
|
1388
|
-
#
|
1389
|
-
return self.parse_ohlcvs(response, market, timeframe, since, limit)
|
1390
|
-
|
1391
|
-
def get_currency_name(self, code):
|
1392
|
-
# todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
|
1393
|
-
if code in self.options['currencyNames']:
|
1394
|
-
return self.options['currencyNames'][code]
|
1395
|
-
raise NotSupported(self.id + ' ' + code + ' not supported for withdrawal')
|
1396
|
-
|
1397
|
-
def create_deposit_address(self, code: str, params={}) -> DepositAddress:
|
1398
|
-
"""
|
1399
|
-
create a currency deposit address
|
1400
|
-
|
1401
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-deposit
|
1402
|
-
|
1403
|
-
:param str code: unified currency code of the currency for the deposit address
|
1404
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1405
|
-
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
1406
|
-
"""
|
1407
|
-
self.load_markets()
|
1408
|
-
request: dict = {
|
1409
|
-
'renew': 1,
|
1410
|
-
}
|
1411
|
-
return self.fetch_deposit_address(code, self.extend(request, params))
|
1412
|
-
|
1413
|
-
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
1414
|
-
"""
|
1415
|
-
fetch the deposit address for a currency associated with self account
|
1416
|
-
|
1417
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-deposit
|
1418
|
-
|
1419
|
-
:param str code: unified currency code
|
1420
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1421
|
-
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
1422
|
-
"""
|
1423
|
-
self.load_markets()
|
1424
|
-
# todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
|
1425
|
-
name = self.get_currency_name(code)
|
1426
|
-
request: dict = {
|
1427
|
-
'method': name,
|
1428
|
-
'wallet_name': 'exchange',
|
1429
|
-
'renew': 0, # a value of 1 will generate a new address
|
1430
|
-
}
|
1431
|
-
response = self.privatePostDepositNew(self.extend(request, params))
|
1432
|
-
address = self.safe_value(response, 'address')
|
1433
|
-
tag = None
|
1434
|
-
if 'address_pool' in response:
|
1435
|
-
tag = address
|
1436
|
-
address = response['address_pool']
|
1437
|
-
self.check_address(address)
|
1438
|
-
return {
|
1439
|
-
'currency': code,
|
1440
|
-
'address': address,
|
1441
|
-
'tag': tag,
|
1442
|
-
'network': None,
|
1443
|
-
'info': response,
|
1444
|
-
}
|
1445
|
-
|
1446
|
-
def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
1447
|
-
"""
|
1448
|
-
fetch history of deposits and withdrawals
|
1449
|
-
|
1450
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-deposit-withdrawal-history
|
1451
|
-
|
1452
|
-
:param str code: unified currency code for the currency of the deposit/withdrawals
|
1453
|
-
:param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
|
1454
|
-
:param int [limit]: max number of deposit/withdrawals to return, default is None
|
1455
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1456
|
-
:returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
1457
|
-
"""
|
1458
|
-
self.load_markets()
|
1459
|
-
currencyId = self.safe_string(params, 'currency')
|
1460
|
-
query = self.omit(params, 'currency')
|
1461
|
-
currency = None
|
1462
|
-
if currencyId is None:
|
1463
|
-
if code is None:
|
1464
|
-
raise ArgumentsRequired(self.id + ' fetchDepositsWithdrawals() requires a currency `code` argument or a `currency` parameter')
|
1465
|
-
else:
|
1466
|
-
currency = self.currency(code)
|
1467
|
-
currencyId = currency['id']
|
1468
|
-
query['currency'] = currencyId
|
1469
|
-
if since is not None:
|
1470
|
-
query['since'] = self.parse_to_int(since / 1000)
|
1471
|
-
response = self.privatePostHistoryMovements(self.extend(query, params))
|
1472
|
-
#
|
1473
|
-
# [
|
1474
|
-
# {
|
1475
|
-
# "id": 581183,
|
1476
|
-
# "txid": 123456,
|
1477
|
-
# "currency": "BTC",
|
1478
|
-
# "method": "BITCOIN",
|
1479
|
-
# "type": "WITHDRAWAL",
|
1480
|
-
# "amount": ".01",
|
1481
|
-
# "description": "3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ, offchain transfer ",
|
1482
|
-
# "address": "3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ",
|
1483
|
-
# "status": "COMPLETED",
|
1484
|
-
# "timestamp": "1443833327.0",
|
1485
|
-
# "timestamp_created": "1443833327.1",
|
1486
|
-
# "fee": 0.1,
|
1487
|
-
# }
|
1488
|
-
# ]
|
1489
|
-
#
|
1490
|
-
return self.parse_transactions(response, currency, since, limit)
|
1491
|
-
|
1492
|
-
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
1493
|
-
#
|
1494
|
-
# crypto
|
1495
|
-
#
|
1496
|
-
# {
|
1497
|
-
# "id": 12042490,
|
1498
|
-
# "fee": "-0.02",
|
1499
|
-
# "txid": "EA5B5A66000B66855865EFF2494D7C8D1921FCBE996482157EBD749F2C85E13D",
|
1500
|
-
# "type": "DEPOSIT",
|
1501
|
-
# "amount": "2099.849999",
|
1502
|
-
# "method": "RIPPLE",
|
1503
|
-
# "status": "COMPLETED",
|
1504
|
-
# "address": "2505189261",
|
1505
|
-
# "currency": "XRP",
|
1506
|
-
# "timestamp": "1551730524.0",
|
1507
|
-
# "description": "EA5B5A66000B66855865EFF2494D7C8D1921FCBE996482157EBD749F2C85E13D",
|
1508
|
-
# "timestamp_created": "1551730523.0"
|
1509
|
-
# }
|
1510
|
-
#
|
1511
|
-
# fiat
|
1512
|
-
#
|
1513
|
-
# {
|
1514
|
-
# "id": 12725095,
|
1515
|
-
# "fee": "-60.0",
|
1516
|
-
# "txid": null,
|
1517
|
-
# "type": "WITHDRAWAL",
|
1518
|
-
# "amount": "9943.0",
|
1519
|
-
# "method": "WIRE",
|
1520
|
-
# "status": "SENDING",
|
1521
|
-
# "address": null,
|
1522
|
-
# "currency": "EUR",
|
1523
|
-
# "timestamp": "1561802484.0",
|
1524
|
-
# "description": "Name: bob, AccountAddress: some address, Account: someaccountno, Bank: bank address, SWIFT: foo, Country: UK, Details of Payment: withdrawal name, Intermediary Bank Name: , Intermediary Bank Address: , Intermediary Bank City: , Intermediary Bank Country: , Intermediary Bank Account: , Intermediary Bank SWIFT: , Fee: -60.0",
|
1525
|
-
# "timestamp_created": "1561716066.0"
|
1526
|
-
# }
|
1527
|
-
#
|
1528
|
-
# withdraw
|
1529
|
-
#
|
1530
|
-
# {
|
1531
|
-
# "status": "success",
|
1532
|
-
# "message": "Your withdrawal request has been successfully submitted.",
|
1533
|
-
# "withdrawal_id": 586829
|
1534
|
-
# }
|
1535
|
-
#
|
1536
|
-
timestamp = self.safe_timestamp(transaction, 'timestamp_created')
|
1537
|
-
currencyId = self.safe_string(transaction, 'currency')
|
1538
|
-
code = self.safe_currency_code(currencyId, currency)
|
1539
|
-
feeCost = self.safe_string(transaction, 'fee')
|
1540
|
-
if feeCost is not None:
|
1541
|
-
feeCost = Precise.string_abs(feeCost)
|
1542
|
-
return {
|
1543
|
-
'info': transaction,
|
1544
|
-
'id': self.safe_string_2(transaction, 'id', 'withdrawal_id'),
|
1545
|
-
'txid': self.safe_string(transaction, 'txid'),
|
1546
|
-
'type': self.safe_string_lower(transaction, 'type'), # DEPOSIT or WITHDRAWAL,
|
1547
|
-
'currency': code,
|
1548
|
-
'network': None,
|
1549
|
-
'amount': self.safe_number(transaction, 'amount'),
|
1550
|
-
'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
|
1551
|
-
'timestamp': timestamp,
|
1552
|
-
'datetime': self.iso8601(timestamp),
|
1553
|
-
'address': self.safe_string(transaction, 'address'), # todo: self is actually the tag for XRP transfers(the address is missing)
|
1554
|
-
'addressFrom': None,
|
1555
|
-
'addressTo': None,
|
1556
|
-
'tag': self.safe_string(transaction, 'description'),
|
1557
|
-
'tagFrom': None,
|
1558
|
-
'tagTo': None,
|
1559
|
-
'updated': self.safe_timestamp(transaction, 'timestamp'),
|
1560
|
-
'comment': None,
|
1561
|
-
'internal': None,
|
1562
|
-
'fee': {
|
1563
|
-
'currency': code,
|
1564
|
-
'cost': self.parse_number(feeCost),
|
1565
|
-
'rate': None,
|
1566
|
-
},
|
1567
|
-
}
|
1568
|
-
|
1569
|
-
def parse_transaction_status(self, status: Str):
|
1570
|
-
statuses: dict = {
|
1571
|
-
'SENDING': 'pending',
|
1572
|
-
'CANCELED': 'canceled',
|
1573
|
-
'ZEROCONFIRMED': 'failed', # ZEROCONFIRMED happens e.g. in a double spend attempt(I had one in my movementsnot )
|
1574
|
-
'COMPLETED': 'ok',
|
1575
|
-
}
|
1576
|
-
return self.safe_string(statuses, status, status)
|
1577
|
-
|
1578
|
-
def withdraw(self, code: str, amount: float, address: str, tag=None, params={}) -> Transaction:
|
1579
|
-
"""
|
1580
|
-
make a withdrawal
|
1581
|
-
|
1582
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-withdrawal
|
1583
|
-
|
1584
|
-
:param str code: unified currency code
|
1585
|
-
:param float amount: the amount to withdraw
|
1586
|
-
:param str address: the address to withdraw to
|
1587
|
-
:param str tag:
|
1588
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1589
|
-
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
1590
|
-
"""
|
1591
|
-
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
1592
|
-
self.check_address(address)
|
1593
|
-
self.load_markets()
|
1594
|
-
# todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
|
1595
|
-
name = self.get_currency_name(code)
|
1596
|
-
currency = self.currency(code)
|
1597
|
-
request: dict = {
|
1598
|
-
'withdraw_type': name,
|
1599
|
-
'walletselected': 'exchange',
|
1600
|
-
'amount': self.number_to_string(amount),
|
1601
|
-
'address': address,
|
1602
|
-
}
|
1603
|
-
if tag is not None:
|
1604
|
-
request['payment_id'] = tag
|
1605
|
-
responses = self.privatePostWithdraw(self.extend(request, params))
|
1606
|
-
#
|
1607
|
-
# [
|
1608
|
-
# {
|
1609
|
-
# "status":"success",
|
1610
|
-
# "message":"Your withdrawal request has been successfully submitted.",
|
1611
|
-
# "withdrawal_id":586829
|
1612
|
-
# }
|
1613
|
-
# ]
|
1614
|
-
#
|
1615
|
-
response = self.safe_dict(responses, 0, {})
|
1616
|
-
id = self.safe_integer(response, 'withdrawal_id')
|
1617
|
-
message = self.safe_string(response, 'message')
|
1618
|
-
errorMessage = self.find_broadly_matched_key(self.exceptions['broad'], message)
|
1619
|
-
if id == 0:
|
1620
|
-
if errorMessage is not None:
|
1621
|
-
ExceptionClass = self.exceptions['broad'][errorMessage]
|
1622
|
-
raise ExceptionClass(self.id + ' ' + message)
|
1623
|
-
raise ExchangeError(self.id + ' withdraw returned an id of zero: ' + self.json(response))
|
1624
|
-
return self.parse_transaction(response, currency)
|
1625
|
-
|
1626
|
-
def fetch_positions(self, symbols: Strings = None, params={}):
|
1627
|
-
"""
|
1628
|
-
fetch all open positions
|
1629
|
-
|
1630
|
-
https://docs.bitfinex.com/v1/reference/rest-auth-active-positions
|
1631
|
-
|
1632
|
-
:param str[]|None symbols: list of unified market symbols
|
1633
|
-
:param dict [params]: extra parameters specific to the exchange API endpoint
|
1634
|
-
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
1635
|
-
"""
|
1636
|
-
self.load_markets()
|
1637
|
-
response = self.privatePostPositions(params)
|
1638
|
-
#
|
1639
|
-
# [
|
1640
|
-
# {
|
1641
|
-
# "id":943715,
|
1642
|
-
# "symbol":"btcusd",
|
1643
|
-
# "status":"ACTIVE",
|
1644
|
-
# "base":"246.94",
|
1645
|
-
# "amount":"1.0",
|
1646
|
-
# "timestamp":"1444141857.0",
|
1647
|
-
# "swap":"0.0",
|
1648
|
-
# "pl":"-2.22042"
|
1649
|
-
# }
|
1650
|
-
# ]
|
1651
|
-
#
|
1652
|
-
# todo unify parsePosition/parsePositions
|
1653
|
-
return response
|
1654
|
-
|
1655
|
-
def nonce(self):
|
1656
|
-
return self.microseconds()
|
1657
|
-
|
1658
|
-
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
1659
|
-
request = '/' + self.implode_params(path, params)
|
1660
|
-
if api == 'v2':
|
1661
|
-
request = '/' + api + request
|
1662
|
-
else:
|
1663
|
-
request = '/' + self.version + request
|
1664
|
-
query = self.omit(params, self.extract_params(path))
|
1665
|
-
url = self.urls['api'][api] + request
|
1666
|
-
if (api == 'public') or (path.find('/hist') >= 0):
|
1667
|
-
if query:
|
1668
|
-
suffix = '?' + self.urlencode(query)
|
1669
|
-
url += suffix
|
1670
|
-
request += suffix
|
1671
|
-
if api == 'private':
|
1672
|
-
self.check_required_credentials()
|
1673
|
-
nonce = self.nonce()
|
1674
|
-
query = self.extend({
|
1675
|
-
'nonce': str(nonce),
|
1676
|
-
'request': request,
|
1677
|
-
}, query)
|
1678
|
-
body = self.json(query)
|
1679
|
-
payload = self.string_to_base64(body)
|
1680
|
-
secret = self.encode(self.secret)
|
1681
|
-
signature = self.hmac(self.encode(payload), secret, hashlib.sha384)
|
1682
|
-
headers = {
|
1683
|
-
'X-BFX-APIKEY': self.apiKey,
|
1684
|
-
'X-BFX-PAYLOAD': payload,
|
1685
|
-
'X-BFX-SIGNATURE': signature,
|
1686
|
-
'Content-Type': 'application/json',
|
1687
|
-
}
|
1688
|
-
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
1689
|
-
|
1690
|
-
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
1691
|
-
if response is None:
|
1692
|
-
return None
|
1693
|
-
throwError = False
|
1694
|
-
if code >= 400:
|
1695
|
-
if body[0] == '{':
|
1696
|
-
throwError = True
|
1697
|
-
else:
|
1698
|
-
# json response with error, i.e:
|
1699
|
-
# [{"status":"error","message":"Momentary balance check. Please wait few seconds and try the transfer again."}]
|
1700
|
-
responseObject = self.safe_dict(response, 0, {})
|
1701
|
-
status = self.safe_string(responseObject, 'status', '')
|
1702
|
-
if status == 'error':
|
1703
|
-
throwError = True
|
1704
|
-
if throwError:
|
1705
|
-
feedback = self.id + ' ' + body
|
1706
|
-
message = self.safe_string_2(response, 'message', 'error')
|
1707
|
-
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
1708
|
-
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
1709
|
-
raise ExchangeError(feedback) # unknown message
|
1710
|
-
return None
|