ccxt 4.4.36 → 4.4.38
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.
- package/README.md +7 -7
- package/dist/ccxt.browser.min.js +2 -2
- package/dist/cjs/ccxt.js +6 -6
- package/dist/cjs/src/abstract/bitfinex1.js +9 -0
- package/dist/cjs/src/base/Exchange.js +10 -1
- package/dist/cjs/src/binance.js +0 -2
- package/dist/cjs/src/bingx.js +93 -8
- package/dist/cjs/src/bitfinex.js +3178 -1161
- package/dist/cjs/src/bitfinex1.js +1760 -0
- package/dist/cjs/src/bitget.js +0 -2
- package/dist/cjs/src/bithumb.js +1 -1
- package/dist/cjs/src/bitmart.js +163 -15
- package/dist/cjs/src/bybit.js +3 -2
- package/dist/cjs/src/coinbase.js +87 -0
- package/dist/cjs/src/gate.js +1 -1
- package/dist/cjs/src/hyperliquid.js +125 -14
- package/dist/cjs/src/kucoin.js +76 -2
- package/dist/cjs/src/kucoinfutures.js +93 -5
- package/dist/cjs/src/ndax.js +7 -2
- package/dist/cjs/src/okx.js +0 -1
- package/dist/cjs/src/paradex.js +4 -2
- package/dist/cjs/src/pro/bitfinex.js +760 -271
- package/dist/cjs/src/pro/bitfinex1.js +675 -0
- package/dist/cjs/src/pro/probit.js +1 -0
- package/dist/cjs/src/probit.js +3 -1
- package/js/ccxt.d.ts +8 -8
- package/js/ccxt.js +6 -6
- package/js/src/abstract/bingx.d.ts +0 -1
- package/js/src/abstract/bitfinex.d.ts +135 -64
- package/js/src/abstract/bitfinex1.d.ts +72 -0
- package/js/src/base/Exchange.js +10 -1
- package/js/src/binance.js +0 -2
- package/js/src/bingx.d.ts +14 -1
- package/js/src/bingx.js +93 -8
- package/js/src/bitfinex.d.ts +316 -106
- package/js/src/bitfinex.js +3179 -1162
- package/js/src/bitfinex1.d.ts +296 -0
- package/js/src/bitfinex1.js +1761 -0
- package/js/src/bitget.js +0 -2
- package/js/src/bithumb.js +1 -1
- package/js/src/bitmart.js +163 -15
- package/js/src/bybit.js +3 -2
- package/js/src/coinbase.d.ts +33 -0
- package/js/src/coinbase.js +87 -0
- package/js/src/gate.js +1 -1
- package/js/src/hyperliquid.d.ts +6 -1
- package/js/src/hyperliquid.js +125 -14
- package/js/src/kucoin.js +76 -2
- package/js/src/kucoinfutures.js +93 -5
- package/js/src/ndax.d.ts +2 -0
- package/js/src/ndax.js +7 -2
- package/js/src/okx.js +0 -1
- package/js/src/paradex.d.ts +2 -0
- package/js/src/paradex.js +4 -2
- package/js/src/pro/bitfinex.d.ts +42 -10
- package/js/src/pro/bitfinex.js +761 -272
- package/js/src/pro/bitfinex1.d.ts +67 -0
- package/js/src/pro/bitfinex1.js +676 -0
- package/js/src/pro/probit.js +1 -0
- package/js/src/probit.js +3 -1
- package/package.json +1 -1
- package/js/src/abstract/bitfinex2.d.ts +0 -143
- /package/js/src/abstract/{bitfinex2.js → bitfinex1.js} +0 -0
package/js/src/pro/bitfinex.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
import bitfinexRest from '../bitfinex.js';
|
|
9
|
-
import { ExchangeError, AuthenticationError } from '../base/errors.js';
|
|
10
|
-
import { ArrayCache, ArrayCacheBySymbolById } from '../base/ws/Cache.js';
|
|
11
9
|
import { Precise } from '../base/Precise.js';
|
|
10
|
+
import { ExchangeError, AuthenticationError, ChecksumError } from '../base/errors.js';
|
|
11
|
+
import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp } from '../base/ws/Cache.js';
|
|
12
12
|
import { sha384 } from '../static_dependencies/noble-hashes/sha512.js';
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
export default class bitfinex extends bitfinexRest {
|
|
@@ -21,14 +21,16 @@ export default class bitfinex extends bitfinexRest {
|
|
|
21
21
|
'watchOrderBook': true,
|
|
22
22
|
'watchTrades': true,
|
|
23
23
|
'watchTradesForSymbols': false,
|
|
24
|
-
'
|
|
25
|
-
'
|
|
24
|
+
'watchMyTrades': true,
|
|
25
|
+
'watchBalance': true,
|
|
26
|
+
'watchOHLCV': true,
|
|
27
|
+
'watchOrders': true,
|
|
26
28
|
},
|
|
27
29
|
'urls': {
|
|
28
30
|
'api': {
|
|
29
31
|
'ws': {
|
|
30
|
-
'public': 'wss://api-pub.bitfinex.com/ws/
|
|
31
|
-
'private': 'wss://api.bitfinex.com/ws/
|
|
32
|
+
'public': 'wss://api-pub.bitfinex.com/ws/2',
|
|
33
|
+
'private': 'wss://api.bitfinex.com/ws/2',
|
|
32
34
|
},
|
|
33
35
|
},
|
|
34
36
|
},
|
|
@@ -36,6 +38,7 @@ export default class bitfinex extends bitfinexRest {
|
|
|
36
38
|
'watchOrderBook': {
|
|
37
39
|
'prec': 'P0',
|
|
38
40
|
'freq': 'F0',
|
|
41
|
+
'checksum': true,
|
|
39
42
|
},
|
|
40
43
|
'ordersLimit': 1000,
|
|
41
44
|
},
|
|
@@ -46,21 +49,149 @@ export default class bitfinex extends bitfinexRest {
|
|
|
46
49
|
const market = this.market(symbol);
|
|
47
50
|
const marketId = market['id'];
|
|
48
51
|
const url = this.urls['api']['ws']['public'];
|
|
52
|
+
const client = this.client(url);
|
|
49
53
|
const messageHash = channel + ':' + marketId;
|
|
50
|
-
// const channel = 'trades';
|
|
51
54
|
const request = {
|
|
52
55
|
'event': 'subscribe',
|
|
53
56
|
'channel': channel,
|
|
54
57
|
'symbol': marketId,
|
|
55
|
-
'messageHash': messageHash,
|
|
56
58
|
};
|
|
57
|
-
|
|
59
|
+
const result = await this.watch(url, messageHash, this.deepExtend(request, params), messageHash, { 'checksum': false });
|
|
60
|
+
const checksum = this.safeBool(this.options, 'checksum', true);
|
|
61
|
+
if (checksum && !client.subscriptions[messageHash]['checksum'] && (channel === 'book')) {
|
|
62
|
+
client.subscriptions[messageHash]['checksum'] = true;
|
|
63
|
+
await client.send({
|
|
64
|
+
'event': 'conf',
|
|
65
|
+
'flags': 131072,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
async subscribePrivate(messageHash) {
|
|
71
|
+
await this.loadMarkets();
|
|
72
|
+
await this.authenticate();
|
|
73
|
+
const url = this.urls['api']['ws']['private'];
|
|
74
|
+
return await this.watch(url, messageHash, undefined, 1);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* @method
|
|
78
|
+
* @name bitfinex#watchOHLCV
|
|
79
|
+
* @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
80
|
+
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
|
|
81
|
+
* @param {string} timeframe the length of time each candle represents
|
|
82
|
+
* @param {int} [since] timestamp in ms of the earliest candle to fetch
|
|
83
|
+
* @param {int} [limit] the maximum amount of candles to fetch
|
|
84
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
85
|
+
* @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
|
|
86
|
+
*/
|
|
87
|
+
async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
|
88
|
+
await this.loadMarkets();
|
|
89
|
+
const market = this.market(symbol);
|
|
90
|
+
symbol = market['symbol'];
|
|
91
|
+
const interval = this.safeString(this.timeframes, timeframe, timeframe);
|
|
92
|
+
const channel = 'candles';
|
|
93
|
+
const key = 'trade:' + interval + ':' + market['id'];
|
|
94
|
+
const messageHash = channel + ':' + interval + ':' + market['id'];
|
|
95
|
+
const request = {
|
|
96
|
+
'event': 'subscribe',
|
|
97
|
+
'channel': channel,
|
|
98
|
+
'key': key,
|
|
99
|
+
};
|
|
100
|
+
const url = this.urls['api']['ws']['public'];
|
|
101
|
+
// not using subscribe here because this message has a different format
|
|
102
|
+
const ohlcv = await this.watch(url, messageHash, this.deepExtend(request, params), messageHash);
|
|
103
|
+
if (this.newUpdates) {
|
|
104
|
+
limit = ohlcv.getLimit(symbol, limit);
|
|
105
|
+
}
|
|
106
|
+
return this.filterBySinceLimit(ohlcv, since, limit, 0, true);
|
|
107
|
+
}
|
|
108
|
+
handleOHLCV(client, message, subscription) {
|
|
109
|
+
//
|
|
110
|
+
// initial snapshot
|
|
111
|
+
// [
|
|
112
|
+
// 341527, // channel id
|
|
113
|
+
// [
|
|
114
|
+
// [
|
|
115
|
+
// 1654705860000, // timestamp
|
|
116
|
+
// 1802.6, // open
|
|
117
|
+
// 1800.3, // close
|
|
118
|
+
// 1802.8, // high
|
|
119
|
+
// 1800.3, // low
|
|
120
|
+
// 86.49588236 // volume
|
|
121
|
+
// ],
|
|
122
|
+
// [
|
|
123
|
+
// 1654705800000,
|
|
124
|
+
// 1803.6,
|
|
125
|
+
// 1802.6,
|
|
126
|
+
// 1804.9,
|
|
127
|
+
// 1802.3,
|
|
128
|
+
// 74.6348086
|
|
129
|
+
// ],
|
|
130
|
+
// [
|
|
131
|
+
// 1654705740000,
|
|
132
|
+
// 1802.5,
|
|
133
|
+
// 1803.2,
|
|
134
|
+
// 1804.4,
|
|
135
|
+
// 1802.4,
|
|
136
|
+
// 23.61801085
|
|
137
|
+
// ]
|
|
138
|
+
// ]
|
|
139
|
+
// ]
|
|
140
|
+
//
|
|
141
|
+
// update
|
|
142
|
+
// [
|
|
143
|
+
// 211171,
|
|
144
|
+
// [
|
|
145
|
+
// 1654705680000,
|
|
146
|
+
// 1801,
|
|
147
|
+
// 1802.4,
|
|
148
|
+
// 1802.9,
|
|
149
|
+
// 1800.4,
|
|
150
|
+
// 23.91911091
|
|
151
|
+
// ]
|
|
152
|
+
// ]
|
|
153
|
+
//
|
|
154
|
+
const data = this.safeValue(message, 1, []);
|
|
155
|
+
let ohlcvs = undefined;
|
|
156
|
+
const first = this.safeValue(data, 0);
|
|
157
|
+
if (Array.isArray(first)) {
|
|
158
|
+
// snapshot
|
|
159
|
+
ohlcvs = data;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// update
|
|
163
|
+
ohlcvs = [data];
|
|
164
|
+
}
|
|
165
|
+
const channel = this.safeValue(subscription, 'channel');
|
|
166
|
+
const key = this.safeString(subscription, 'key');
|
|
167
|
+
const keyParts = key.split(':');
|
|
168
|
+
const interval = this.safeString(keyParts, 1);
|
|
169
|
+
let marketId = key;
|
|
170
|
+
marketId = marketId.replace('trade:', '');
|
|
171
|
+
marketId = marketId.replace(interval + ':', '');
|
|
172
|
+
const market = this.safeMarket(marketId);
|
|
173
|
+
const timeframe = this.findTimeframe(interval);
|
|
174
|
+
const symbol = market['symbol'];
|
|
175
|
+
const messageHash = channel + ':' + interval + ':' + marketId;
|
|
176
|
+
this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {});
|
|
177
|
+
let stored = this.safeValue(this.ohlcvs[symbol], timeframe);
|
|
178
|
+
if (stored === undefined) {
|
|
179
|
+
const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000);
|
|
180
|
+
stored = new ArrayCacheByTimestamp(limit);
|
|
181
|
+
this.ohlcvs[symbol][timeframe] = stored;
|
|
182
|
+
}
|
|
183
|
+
const ohlcvsLength = ohlcvs.length;
|
|
184
|
+
for (let i = 0; i < ohlcvsLength; i++) {
|
|
185
|
+
const ohlcv = ohlcvs[ohlcvsLength - i - 1];
|
|
186
|
+
const parsed = this.parseOHLCV(ohlcv, market);
|
|
187
|
+
stored.append(parsed);
|
|
188
|
+
}
|
|
189
|
+
client.resolve(stored, messageHash);
|
|
58
190
|
}
|
|
59
191
|
/**
|
|
60
192
|
* @method
|
|
61
193
|
* @name bitfinex#watchTrades
|
|
62
194
|
* @description get the list of most recent trades for a particular symbol
|
|
63
|
-
* @see https://docs.bitfinex.com/v1/reference/ws-public-trades
|
|
64
195
|
* @param {string} symbol unified symbol of the market to fetch trades for
|
|
65
196
|
* @param {int} [since] timestamp in ms of the earliest trade to fetch
|
|
66
197
|
* @param {int} [limit] the maximum amount of trades to fetch
|
|
@@ -68,19 +199,39 @@ export default class bitfinex extends bitfinexRest {
|
|
|
68
199
|
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
|
|
69
200
|
*/
|
|
70
201
|
async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
|
|
71
|
-
await this.loadMarkets();
|
|
72
|
-
symbol = this.symbol(symbol);
|
|
73
202
|
const trades = await this.subscribe('trades', symbol, params);
|
|
74
203
|
if (this.newUpdates) {
|
|
75
204
|
limit = trades.getLimit(symbol, limit);
|
|
76
205
|
}
|
|
77
206
|
return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
|
|
78
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* @method
|
|
210
|
+
* @name bitfinex#watchMyTrades
|
|
211
|
+
* @description watches information on multiple trades made by the user
|
|
212
|
+
* @param {string} symbol unified market symbol of the market trades were made in
|
|
213
|
+
* @param {int} [since] the earliest time in ms to fetch trades for
|
|
214
|
+
* @param {int} [limit] the maximum number of trade structures to retrieve
|
|
215
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
216
|
+
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure}
|
|
217
|
+
*/
|
|
218
|
+
async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
|
219
|
+
await this.loadMarkets();
|
|
220
|
+
let messageHash = 'myTrade';
|
|
221
|
+
if (symbol !== undefined) {
|
|
222
|
+
const market = this.market(symbol);
|
|
223
|
+
messageHash += ':' + market['id'];
|
|
224
|
+
}
|
|
225
|
+
const trades = await this.subscribePrivate(messageHash);
|
|
226
|
+
if (this.newUpdates) {
|
|
227
|
+
limit = trades.getLimit(symbol, limit);
|
|
228
|
+
}
|
|
229
|
+
return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true);
|
|
230
|
+
}
|
|
79
231
|
/**
|
|
80
232
|
* @method
|
|
81
233
|
* @name bitfinex#watchTicker
|
|
82
234
|
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
83
|
-
* @see https://docs.bitfinex.com/v1/reference/ws-public-ticker
|
|
84
235
|
* @param {string} symbol unified symbol of the market to fetch the ticker for
|
|
85
236
|
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
86
237
|
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
|
|
@@ -88,100 +239,200 @@ export default class bitfinex extends bitfinexRest {
|
|
|
88
239
|
async watchTicker(symbol, params = {}) {
|
|
89
240
|
return await this.subscribe('ticker', symbol, params);
|
|
90
241
|
}
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
// initial snapshot
|
|
242
|
+
handleMyTrade(client, message, subscription = {}) {
|
|
94
243
|
//
|
|
244
|
+
// trade execution
|
|
245
|
+
// [
|
|
246
|
+
// 0,
|
|
247
|
+
// "te", // or tu
|
|
95
248
|
// [
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
249
|
+
// 1133411090,
|
|
250
|
+
// "tLTCUST",
|
|
251
|
+
// 1655110144598,
|
|
252
|
+
// 97084883506,
|
|
253
|
+
// 0.1,
|
|
254
|
+
// 42.821,
|
|
255
|
+
// "EXCHANGE MARKET",
|
|
256
|
+
// 42.799,
|
|
257
|
+
// -1,
|
|
258
|
+
// null,
|
|
259
|
+
// null,
|
|
260
|
+
// 1655110144596
|
|
102
261
|
// ]
|
|
262
|
+
// ]
|
|
103
263
|
//
|
|
104
|
-
|
|
264
|
+
const name = 'myTrade';
|
|
265
|
+
const data = this.safeValue(message, 2);
|
|
266
|
+
const trade = this.parseWsTrade(data);
|
|
267
|
+
const symbol = trade['symbol'];
|
|
268
|
+
const market = this.market(symbol);
|
|
269
|
+
const messageHash = name + ':' + market['id'];
|
|
270
|
+
if (this.myTrades === undefined) {
|
|
271
|
+
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
|
|
272
|
+
this.myTrades = new ArrayCacheBySymbolById(limit);
|
|
273
|
+
}
|
|
274
|
+
const tradesArray = this.myTrades;
|
|
275
|
+
tradesArray.append(trade);
|
|
276
|
+
this.myTrades = tradesArray;
|
|
277
|
+
// generic subscription
|
|
278
|
+
client.resolve(tradesArray, name);
|
|
279
|
+
// specific subscription
|
|
280
|
+
client.resolve(tradesArray, messageHash);
|
|
281
|
+
}
|
|
282
|
+
handleTrades(client, message, subscription) {
|
|
283
|
+
//
|
|
284
|
+
// initial snapshot
|
|
105
285
|
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
286
|
+
// [
|
|
287
|
+
// 188687, // channel id
|
|
288
|
+
// [
|
|
289
|
+
// [ 1128060675, 1654701572690, 0.00217533, 1815.3 ], // id, mts, amount, price
|
|
290
|
+
// [ 1128060665, 1654701551231, -0.00280472, 1814.1 ],
|
|
291
|
+
// [ 1128060664, 1654701550996, -0.00364444, 1814.1 ],
|
|
292
|
+
// [ 1128060656, 1654701527730, -0.00265203, 1814.2 ],
|
|
293
|
+
// [ 1128060647, 1654701505193, 0.00262395, 1815.2 ],
|
|
294
|
+
// [ 1128060642, 1654701484656, -0.13411443, 1816 ],
|
|
295
|
+
// [ 1128060641, 1654701484656, -0.00088557, 1816 ],
|
|
296
|
+
// [ 1128060639, 1654701478326, -0.002, 1816 ],
|
|
297
|
+
// ]
|
|
298
|
+
// ]
|
|
299
|
+
// update
|
|
108
300
|
//
|
|
109
|
-
//
|
|
301
|
+
// [
|
|
302
|
+
// 360141,
|
|
303
|
+
// "te",
|
|
304
|
+
// [
|
|
305
|
+
// 1128060969, // id
|
|
306
|
+
// 1654702500098, // mts
|
|
307
|
+
// 0.00325131, // amount positive buy, negative sell
|
|
308
|
+
// 1818.5, // price
|
|
309
|
+
// ],
|
|
310
|
+
// ]
|
|
110
311
|
//
|
|
111
|
-
// // channel id, update type, seq, trade id, time, price, amount
|
|
112
|
-
// [ 2, "tu", "28462857-BTCUSD", 413357662, 1580565041, 9374.9, 0.005 ]
|
|
113
312
|
//
|
|
114
313
|
const channel = this.safeValue(subscription, 'channel');
|
|
115
|
-
const marketId = this.safeString(subscription, '
|
|
314
|
+
const marketId = this.safeString(subscription, 'symbol');
|
|
315
|
+
const market = this.safeMarket(marketId);
|
|
116
316
|
const messageHash = channel + ':' + marketId;
|
|
117
317
|
const tradesLimit = this.safeInteger(this.options, 'tradesLimit', 1000);
|
|
118
|
-
const market = this.safeMarket(marketId);
|
|
119
318
|
const symbol = market['symbol'];
|
|
120
|
-
const data = this.safeValue(message, 1);
|
|
121
319
|
let stored = this.safeValue(this.trades, symbol);
|
|
122
320
|
if (stored === undefined) {
|
|
123
321
|
stored = new ArrayCache(tradesLimit);
|
|
124
322
|
this.trades[symbol] = stored;
|
|
125
323
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
324
|
+
const messageLength = message.length;
|
|
325
|
+
if (messageLength === 2) {
|
|
326
|
+
// initial snapshot
|
|
327
|
+
const trades = this.safeList(message, 1, []);
|
|
328
|
+
// needs to be reversed to make chronological order
|
|
329
|
+
const length = trades.length;
|
|
330
|
+
for (let i = 0; i < length; i++) {
|
|
331
|
+
const index = length - i - 1;
|
|
332
|
+
const parsed = this.parseWsTrade(trades[index], market);
|
|
333
|
+
stored.append(parsed);
|
|
130
334
|
}
|
|
131
335
|
}
|
|
132
336
|
else {
|
|
133
|
-
|
|
134
|
-
|
|
337
|
+
// update
|
|
338
|
+
const type = this.safeString(message, 1);
|
|
339
|
+
if (type === 'tu') {
|
|
340
|
+
// don't resolve for a duplicate update
|
|
341
|
+
// since te and tu updates are duplicated on the public stream
|
|
135
342
|
return;
|
|
136
343
|
}
|
|
137
|
-
const trade = this.
|
|
138
|
-
|
|
344
|
+
const trade = this.safeValue(message, 2, []);
|
|
345
|
+
const parsed = this.parseWsTrade(trade, market);
|
|
346
|
+
stored.append(parsed);
|
|
139
347
|
}
|
|
140
348
|
client.resolve(stored, messageHash);
|
|
141
349
|
}
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
// snapshot trade
|
|
350
|
+
parseWsTrade(trade, market = undefined) {
|
|
145
351
|
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
352
|
+
// [
|
|
353
|
+
// 1128060969, // id
|
|
354
|
+
// 1654702500098, // mts
|
|
355
|
+
// 0.00325131, // amount positive buy, negative sell
|
|
356
|
+
// 1818.5, // price
|
|
357
|
+
// ]
|
|
148
358
|
//
|
|
149
|
-
//
|
|
359
|
+
// trade execution
|
|
150
360
|
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
361
|
+
// [
|
|
362
|
+
// 1133411090, // id
|
|
363
|
+
// "tLTCUST", // symbol
|
|
364
|
+
// 1655110144598, // create ms
|
|
365
|
+
// 97084883506, // order id
|
|
366
|
+
// 0.1, // amount
|
|
367
|
+
// 42.821, // price
|
|
368
|
+
// "EXCHANGE MARKET", // order type
|
|
369
|
+
// 42.799, // order price
|
|
370
|
+
// -1, // maker
|
|
371
|
+
// null, // fee
|
|
372
|
+
// null, // fee currency
|
|
373
|
+
// 1655110144596 // cid
|
|
374
|
+
// ]
|
|
153
375
|
//
|
|
154
|
-
//
|
|
376
|
+
// trade update
|
|
155
377
|
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
378
|
+
// [
|
|
379
|
+
// 1133411090,
|
|
380
|
+
// "tLTCUST",
|
|
381
|
+
// 1655110144598,
|
|
382
|
+
// 97084883506,
|
|
383
|
+
// 0.1,
|
|
384
|
+
// 42.821,
|
|
385
|
+
// "EXCHANGE MARKET",
|
|
386
|
+
// 42.799,
|
|
387
|
+
// -1,
|
|
388
|
+
// -0.0002,
|
|
389
|
+
// "LTC",
|
|
390
|
+
// 1655110144596
|
|
391
|
+
// ]
|
|
158
392
|
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
393
|
+
const numFields = trade.length;
|
|
394
|
+
const isPublic = numFields <= 8;
|
|
395
|
+
let marketId = (!isPublic) ? this.safeString(trade, 1) : undefined;
|
|
396
|
+
market = this.safeMarket(marketId, market);
|
|
397
|
+
const createdKey = isPublic ? 1 : 2;
|
|
398
|
+
const priceKey = isPublic ? 3 : 5;
|
|
399
|
+
const amountKey = isPublic ? 2 : 4;
|
|
400
|
+
marketId = market['id'];
|
|
401
|
+
let type = this.safeString(trade, 6);
|
|
402
|
+
if (type !== undefined) {
|
|
403
|
+
if (type.indexOf('LIMIT') > -1) {
|
|
404
|
+
type = 'limit';
|
|
405
|
+
}
|
|
406
|
+
else if (type.indexOf('MARKET') > -1) {
|
|
407
|
+
type = 'market';
|
|
408
|
+
}
|
|
167
409
|
}
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
410
|
+
const orderId = (!isPublic) ? this.safeString(trade, 3) : undefined;
|
|
411
|
+
const id = this.safeString(trade, 0);
|
|
412
|
+
const timestamp = this.safeInteger(trade, createdKey);
|
|
413
|
+
const price = this.safeString(trade, priceKey);
|
|
414
|
+
const amountString = this.safeString(trade, amountKey);
|
|
415
|
+
const amount = this.parseNumber(Precise.stringAbs(amountString));
|
|
171
416
|
let side = undefined;
|
|
172
417
|
if (amount !== undefined) {
|
|
173
|
-
side = Precise.stringGt(
|
|
174
|
-
amount = Precise.stringAbs(amount);
|
|
175
|
-
}
|
|
176
|
-
const seq = this.safeString(trade, 2);
|
|
177
|
-
const parts = seq.split('-');
|
|
178
|
-
let marketId = this.safeString(parts, 1);
|
|
179
|
-
if (marketId !== undefined) {
|
|
180
|
-
marketId = marketId.replace('t', '');
|
|
418
|
+
side = Precise.stringGt(amountString, '0') ? 'buy' : 'sell';
|
|
181
419
|
}
|
|
182
420
|
const symbol = this.safeSymbol(marketId, market);
|
|
183
|
-
const
|
|
184
|
-
|
|
421
|
+
const feeValue = this.safeString(trade, 9);
|
|
422
|
+
let fee = undefined;
|
|
423
|
+
if (feeValue !== undefined) {
|
|
424
|
+
const currencyId = this.safeString(trade, 10);
|
|
425
|
+
const code = this.safeCurrencyCode(currencyId);
|
|
426
|
+
fee = {
|
|
427
|
+
'cost': feeValue,
|
|
428
|
+
'currency': code,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const maker = this.safeInteger(trade, 8);
|
|
432
|
+
let takerOrMaker = undefined;
|
|
433
|
+
if (maker !== undefined) {
|
|
434
|
+
takerOrMaker = (maker === -1) ? 'taker' : 'maker';
|
|
435
|
+
}
|
|
185
436
|
return this.safeTrade({
|
|
186
437
|
'info': trade,
|
|
187
438
|
'timestamp': timestamp,
|
|
@@ -189,19 +440,20 @@ export default class bitfinex extends bitfinexRest {
|
|
|
189
440
|
'symbol': symbol,
|
|
190
441
|
'id': id,
|
|
191
442
|
'order': orderId,
|
|
192
|
-
'type':
|
|
443
|
+
'type': type,
|
|
193
444
|
'takerOrMaker': takerOrMaker,
|
|
194
445
|
'side': side,
|
|
195
446
|
'price': price,
|
|
196
447
|
'amount': amount,
|
|
197
448
|
'cost': undefined,
|
|
198
|
-
'fee':
|
|
199
|
-
});
|
|
449
|
+
'fee': fee,
|
|
450
|
+
}, market);
|
|
200
451
|
}
|
|
201
452
|
handleTicker(client, message, subscription) {
|
|
202
453
|
//
|
|
454
|
+
// [
|
|
455
|
+
// 340432, // channel ID
|
|
203
456
|
// [
|
|
204
|
-
// 2, // 0 CHANNEL_ID integer Channel ID
|
|
205
457
|
// 236.62, // 1 BID float Price of last highest bid
|
|
206
458
|
// 9.0029, // 2 BID_SIZE float Size of the last highest bid
|
|
207
459
|
// 236.88, // 3 ASK float Price of last lowest ask
|
|
@@ -213,47 +465,64 @@ export default class bitfinex extends bitfinexRest {
|
|
|
213
465
|
// 250.01, // 9 HIGH float Daily high
|
|
214
466
|
// 220.05, // 10 LOW float Daily low
|
|
215
467
|
// ]
|
|
468
|
+
// ]
|
|
216
469
|
//
|
|
217
|
-
const
|
|
470
|
+
const ticker = this.safeValue(message, 1);
|
|
471
|
+
const marketId = this.safeString(subscription, 'symbol');
|
|
472
|
+
const market = this.safeMarket(marketId);
|
|
218
473
|
const symbol = this.safeSymbol(marketId);
|
|
474
|
+
const parsed = this.parseWsTicker(ticker, market);
|
|
219
475
|
const channel = 'ticker';
|
|
220
476
|
const messageHash = channel + ':' + marketId;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
477
|
+
this.tickers[symbol] = parsed;
|
|
478
|
+
client.resolve(parsed, messageHash);
|
|
479
|
+
}
|
|
480
|
+
parseWsTicker(ticker, market = undefined) {
|
|
481
|
+
//
|
|
482
|
+
// [
|
|
483
|
+
// 236.62, // 1 BID float Price of last highest bid
|
|
484
|
+
// 9.0029, // 2 BID_SIZE float Size of the last highest bid
|
|
485
|
+
// 236.88, // 3 ASK float Price of last lowest ask
|
|
486
|
+
// 7.1138, // 4 ASK_SIZE float Size of the last lowest ask
|
|
487
|
+
// -1.02, // 5 DAILY_CHANGE float Amount that the last price has changed since yesterday
|
|
488
|
+
// 0, // 6 DAILY_CHANGE_PERC float Amount that the price has changed expressed in percentage terms
|
|
489
|
+
// 236.52, // 7 LAST_PRICE float Price of the last trade.
|
|
490
|
+
// 5191.36754297, // 8 VOLUME float Daily volume
|
|
491
|
+
// 250.01, // 9 HIGH float Daily high
|
|
492
|
+
// 220.05, // 10 LOW float Daily low
|
|
493
|
+
// ]
|
|
494
|
+
//
|
|
495
|
+
market = this.safeMarket(undefined, market);
|
|
496
|
+
const symbol = market['symbol'];
|
|
497
|
+
const last = this.safeString(ticker, 6);
|
|
498
|
+
const change = this.safeString(ticker, 4);
|
|
499
|
+
return this.safeTicker({
|
|
228
500
|
'symbol': symbol,
|
|
229
501
|
'timestamp': undefined,
|
|
230
502
|
'datetime': undefined,
|
|
231
|
-
'high': this.safeString(
|
|
232
|
-
'low': this.safeString(
|
|
233
|
-
'bid': this.safeString(
|
|
234
|
-
'bidVolume':
|
|
235
|
-
'ask': this.safeString(
|
|
236
|
-
'askVolume':
|
|
503
|
+
'high': this.safeString(ticker, 8),
|
|
504
|
+
'low': this.safeString(ticker, 9),
|
|
505
|
+
'bid': this.safeString(ticker, 0),
|
|
506
|
+
'bidVolume': this.safeString(ticker, 1),
|
|
507
|
+
'ask': this.safeString(ticker, 2),
|
|
508
|
+
'askVolume': this.safeString(ticker, 3),
|
|
237
509
|
'vwap': undefined,
|
|
238
|
-
'open':
|
|
239
|
-
'close':
|
|
240
|
-
'last':
|
|
510
|
+
'open': undefined,
|
|
511
|
+
'close': last,
|
|
512
|
+
'last': last,
|
|
241
513
|
'previousClose': undefined,
|
|
242
|
-
'change':
|
|
243
|
-
'percentage': this.safeString(
|
|
514
|
+
'change': change,
|
|
515
|
+
'percentage': this.safeString(ticker, 5),
|
|
244
516
|
'average': undefined,
|
|
245
|
-
'baseVolume': this.safeString(
|
|
517
|
+
'baseVolume': this.safeString(ticker, 7),
|
|
246
518
|
'quoteVolume': undefined,
|
|
247
|
-
'info':
|
|
248
|
-
});
|
|
249
|
-
this.tickers[symbol] = result;
|
|
250
|
-
client.resolve(result, messageHash);
|
|
519
|
+
'info': ticker,
|
|
520
|
+
}, market);
|
|
251
521
|
}
|
|
252
522
|
/**
|
|
253
523
|
* @method
|
|
254
524
|
* @name bitfinex#watchOrderBook
|
|
255
525
|
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
|
|
256
|
-
* @see https://docs.bitfinex.com/v1/reference/ws-public-order-books
|
|
257
526
|
* @param {string} symbol unified symbol of the market to fetch the order book for
|
|
258
527
|
* @param {int} [limit] the maximum amount of order book entries to return
|
|
259
528
|
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
@@ -269,13 +538,12 @@ export default class bitfinex extends bitfinexRest {
|
|
|
269
538
|
const prec = this.safeString(options, 'prec', 'P0');
|
|
270
539
|
const freq = this.safeString(options, 'freq', 'F0');
|
|
271
540
|
const request = {
|
|
272
|
-
// "event": "subscribe", // added in subscribe()
|
|
273
|
-
// "channel": channel, // added in subscribe()
|
|
274
|
-
// "symbol": marketId, // added in subscribe()
|
|
275
541
|
'prec': prec,
|
|
276
|
-
'freq': freq,
|
|
277
|
-
'len': limit, // string, number of price points, '25', '100', default = '25'
|
|
542
|
+
'freq': freq, // string, frequency of updates 'F0' = realtime, 'F1' = 2 seconds, default is 'F0'
|
|
278
543
|
};
|
|
544
|
+
if (limit !== undefined) {
|
|
545
|
+
request['len'] = limit; // string, number of price points, '25', '100', default = '25'
|
|
546
|
+
}
|
|
279
547
|
const orderbook = await this.subscribe('book', symbol, this.deepExtend(request, params));
|
|
280
548
|
return orderbook.limit();
|
|
281
549
|
}
|
|
@@ -298,20 +566,22 @@ export default class bitfinex extends bitfinexRest {
|
|
|
298
566
|
// subsequent updates
|
|
299
567
|
//
|
|
300
568
|
// [
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
569
|
+
// 358169, // channel id
|
|
570
|
+
// [
|
|
571
|
+
// 1807.1, // price
|
|
572
|
+
// 0, // cound
|
|
573
|
+
// 1 // size
|
|
574
|
+
// ]
|
|
305
575
|
// ]
|
|
306
576
|
//
|
|
307
|
-
const marketId = this.safeString(subscription, '
|
|
577
|
+
const marketId = this.safeString(subscription, 'symbol');
|
|
308
578
|
const symbol = this.safeSymbol(marketId);
|
|
309
579
|
const channel = 'book';
|
|
310
580
|
const messageHash = channel + ':' + marketId;
|
|
311
581
|
const prec = this.safeString(subscription, 'prec', 'P0');
|
|
312
582
|
const isRaw = (prec === 'R0');
|
|
313
583
|
// if it is an initial snapshot
|
|
314
|
-
if (
|
|
584
|
+
if (!(symbol in this.orderbooks)) {
|
|
315
585
|
const limit = this.safeInteger(subscription, 'len');
|
|
316
586
|
if (isRaw) {
|
|
317
587
|
// raw order books
|
|
@@ -326,65 +596,232 @@ export default class bitfinex extends bitfinexRest {
|
|
|
326
596
|
const deltas = message[1];
|
|
327
597
|
for (let i = 0; i < deltas.length; i++) {
|
|
328
598
|
const delta = deltas[i];
|
|
329
|
-
const
|
|
330
|
-
const
|
|
331
|
-
const
|
|
332
|
-
const size = (delta2Value < 0) ? -delta2Value : delta2Value;
|
|
333
|
-
const side = (delta2Value < 0) ? 'asks' : 'bids';
|
|
599
|
+
const delta2 = delta[2];
|
|
600
|
+
const size = (delta2 < 0) ? -delta2 : delta2;
|
|
601
|
+
const side = (delta2 < 0) ? 'asks' : 'bids';
|
|
334
602
|
const bookside = orderbook[side];
|
|
335
|
-
|
|
603
|
+
const idString = this.safeString(delta, 0);
|
|
604
|
+
const price = this.safeFloat(delta, 1);
|
|
605
|
+
bookside.storeArray([price, size, idString]);
|
|
336
606
|
}
|
|
337
607
|
}
|
|
338
608
|
else {
|
|
339
609
|
const deltas = message[1];
|
|
340
610
|
for (let i = 0; i < deltas.length; i++) {
|
|
341
611
|
const delta = deltas[i];
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
|
|
612
|
+
const amount = this.safeNumber(delta, 2);
|
|
613
|
+
const counter = this.safeNumber(delta, 1);
|
|
614
|
+
const price = this.safeNumber(delta, 0);
|
|
615
|
+
const size = (amount < 0) ? -amount : amount;
|
|
616
|
+
const side = (amount < 0) ? 'asks' : 'bids';
|
|
617
|
+
const bookside = orderbook[side];
|
|
618
|
+
bookside.storeArray([price, size, counter]);
|
|
347
619
|
}
|
|
348
620
|
}
|
|
621
|
+
orderbook['symbol'] = symbol;
|
|
349
622
|
client.resolve(orderbook, messageHash);
|
|
350
623
|
}
|
|
351
624
|
else {
|
|
352
625
|
const orderbook = this.orderbooks[symbol];
|
|
626
|
+
const deltas = message[1];
|
|
627
|
+
const orderbookItem = this.orderbooks[symbol];
|
|
353
628
|
if (isRaw) {
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
const bookside = orderbook[side];
|
|
629
|
+
const price = this.safeString(deltas, 1);
|
|
630
|
+
const deltas2 = deltas[2];
|
|
631
|
+
const size = (deltas2 < 0) ? -deltas2 : deltas2;
|
|
632
|
+
const side = (deltas2 < 0) ? 'asks' : 'bids';
|
|
633
|
+
const bookside = orderbookItem[side];
|
|
360
634
|
// price = 0 means that you have to remove the order from your book
|
|
361
635
|
const amount = Precise.stringGt(price, '0') ? size : '0';
|
|
362
|
-
|
|
636
|
+
const idString = this.safeString(deltas, 0);
|
|
637
|
+
bookside.storeArray([this.parseNumber(price), this.parseNumber(amount), idString]);
|
|
363
638
|
}
|
|
364
639
|
else {
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
|
|
640
|
+
const amount = this.safeString(deltas, 2);
|
|
641
|
+
const counter = this.safeString(deltas, 1);
|
|
642
|
+
const price = this.safeString(deltas, 0);
|
|
643
|
+
const size = Precise.stringLt(amount, '0') ? Precise.stringNeg(amount) : amount;
|
|
644
|
+
const side = Precise.stringLt(amount, '0') ? 'asks' : 'bids';
|
|
645
|
+
const bookside = orderbookItem[side];
|
|
646
|
+
bookside.storeArray([this.parseNumber(price), this.parseNumber(size), this.parseNumber(counter)]);
|
|
370
647
|
}
|
|
371
648
|
client.resolve(orderbook, messageHash);
|
|
372
649
|
}
|
|
373
650
|
}
|
|
374
|
-
|
|
651
|
+
handleChecksum(client, message, subscription) {
|
|
375
652
|
//
|
|
376
|
-
//
|
|
653
|
+
// [ 173904, "cs", -890884919 ]
|
|
377
654
|
//
|
|
378
|
-
|
|
655
|
+
const marketId = this.safeString(subscription, 'symbol');
|
|
656
|
+
const symbol = this.safeSymbol(marketId);
|
|
657
|
+
const channel = 'book';
|
|
658
|
+
const messageHash = channel + ':' + marketId;
|
|
659
|
+
const book = this.safeValue(this.orderbooks, symbol);
|
|
660
|
+
if (book === undefined) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const depth = 25; // covers the first 25 bids and asks
|
|
664
|
+
const stringArray = [];
|
|
665
|
+
const bids = book['bids'];
|
|
666
|
+
const asks = book['asks'];
|
|
667
|
+
const prec = this.safeString(subscription, 'prec', 'P0');
|
|
668
|
+
const isRaw = (prec === 'R0');
|
|
669
|
+
const idToCheck = isRaw ? 2 : 0;
|
|
670
|
+
// pepperoni pizza from bitfinex
|
|
671
|
+
for (let i = 0; i < depth; i++) {
|
|
672
|
+
const bid = this.safeValue(bids, i);
|
|
673
|
+
const ask = this.safeValue(asks, i);
|
|
674
|
+
if (bid !== undefined) {
|
|
675
|
+
stringArray.push(this.numberToString(bids[i][idToCheck]));
|
|
676
|
+
stringArray.push(this.numberToString(bids[i][1]));
|
|
677
|
+
}
|
|
678
|
+
if (ask !== undefined) {
|
|
679
|
+
stringArray.push(this.numberToString(asks[i][idToCheck]));
|
|
680
|
+
const aski1 = asks[i][1];
|
|
681
|
+
stringArray.push(this.numberToString(-aski1));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const payload = stringArray.join(':');
|
|
685
|
+
const localChecksum = this.crc32(payload, true);
|
|
686
|
+
const responseChecksum = this.safeInteger(message, 2);
|
|
687
|
+
if (responseChecksum !== localChecksum) {
|
|
688
|
+
delete client.subscriptions[messageHash];
|
|
689
|
+
delete this.orderbooks[symbol];
|
|
690
|
+
const checksum = this.handleOption('watchOrderBook', 'checksum', true);
|
|
691
|
+
if (checksum) {
|
|
692
|
+
const error = new ChecksumError(this.id + ' ' + this.orderbookChecksumMessage(symbol));
|
|
693
|
+
client.reject(error, messageHash);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* @method
|
|
699
|
+
* @name bitfinex#watchBalance
|
|
700
|
+
* @description watch balance and get the amount of funds available for trading or funds locked in orders
|
|
701
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
702
|
+
* @param {str} [params.type] spot or contract if not provided this.options['defaultType'] is used
|
|
703
|
+
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
|
|
704
|
+
*/
|
|
705
|
+
async watchBalance(params = {}) {
|
|
706
|
+
await this.loadMarkets();
|
|
707
|
+
const balanceType = this.safeString(params, 'wallet', 'exchange'); // exchange, margin
|
|
708
|
+
params = this.omit(params, 'wallet');
|
|
709
|
+
const messageHash = 'balance:' + balanceType;
|
|
710
|
+
return await this.subscribePrivate(messageHash);
|
|
711
|
+
}
|
|
712
|
+
handleBalance(client, message, subscription) {
|
|
713
|
+
//
|
|
714
|
+
// snapshot (exchange + margin together)
|
|
715
|
+
// [
|
|
716
|
+
// 0,
|
|
717
|
+
// "ws",
|
|
718
|
+
// [
|
|
719
|
+
// [
|
|
720
|
+
// "exchange",
|
|
721
|
+
// "LTC",
|
|
722
|
+
// 0.05479727,
|
|
723
|
+
// 0,
|
|
724
|
+
// null,
|
|
725
|
+
// "Trading fees for 0.05 LTC (LTCUST) @ 51.872 on BFX (0.2%)",
|
|
726
|
+
// null,
|
|
727
|
+
// ]
|
|
728
|
+
// [
|
|
729
|
+
// "margin",
|
|
730
|
+
// "USTF0",
|
|
731
|
+
// 11.960650700086292,
|
|
732
|
+
// 0,
|
|
733
|
+
// null,
|
|
734
|
+
// "Trading fees for 0.1 LTCF0 (LTCF0:USTF0) @ 51.844 on BFX (0.065%)",
|
|
735
|
+
// null,
|
|
736
|
+
// ],
|
|
737
|
+
// ],
|
|
738
|
+
// ]
|
|
379
739
|
//
|
|
380
|
-
|
|
381
|
-
|
|
740
|
+
// spot
|
|
741
|
+
// [
|
|
742
|
+
// 0,
|
|
743
|
+
// "wu",
|
|
744
|
+
// [
|
|
745
|
+
// "exchange",
|
|
746
|
+
// "LTC", // currency
|
|
747
|
+
// 0.06729727, // wallet balance
|
|
748
|
+
// 0, // unsettled balance
|
|
749
|
+
// 0.06729727, // available balance might be null
|
|
750
|
+
// "Exchange 0.4 LTC for UST @ 65.075",
|
|
751
|
+
// {
|
|
752
|
+
// "reason": "TRADE",
|
|
753
|
+
// "order_id": 96596397973,
|
|
754
|
+
// "order_id_oppo": 96596632735,
|
|
755
|
+
// "trade_price": "65.075",
|
|
756
|
+
// "trade_amount": "-0.4",
|
|
757
|
+
// "order_cid": 1654636218766,
|
|
758
|
+
// "order_gid": null
|
|
759
|
+
// }
|
|
760
|
+
// ]
|
|
761
|
+
// ]
|
|
762
|
+
//
|
|
763
|
+
// margin
|
|
764
|
+
//
|
|
765
|
+
// [
|
|
766
|
+
// "margin",
|
|
767
|
+
// "USTF0",
|
|
768
|
+
// 11.960650700086292, // total
|
|
769
|
+
// 0,
|
|
770
|
+
// 6.776250700086292, // available
|
|
771
|
+
// "Trading fees for 0.1 LTCF0 (LTCF0:USTF0) @ 51.844 on BFX (0.065%)",
|
|
772
|
+
// null
|
|
773
|
+
// ]
|
|
774
|
+
//
|
|
775
|
+
const updateType = this.safeValue(message, 1);
|
|
776
|
+
let data = undefined;
|
|
777
|
+
if (updateType === 'ws') {
|
|
778
|
+
data = this.safeValue(message, 2);
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
data = [this.safeValue(message, 2)];
|
|
782
|
+
}
|
|
783
|
+
const updatedTypes = {};
|
|
784
|
+
for (let i = 0; i < data.length; i++) {
|
|
785
|
+
const rawBalance = data[i];
|
|
786
|
+
const currencyId = this.safeString(rawBalance, 1);
|
|
787
|
+
const code = this.safeCurrencyCode(currencyId);
|
|
788
|
+
const balance = this.parseWsBalance(rawBalance);
|
|
789
|
+
const balanceType = this.safeString(rawBalance, 0);
|
|
790
|
+
const oldBalance = this.safeValue(this.balance, balanceType, {});
|
|
791
|
+
oldBalance[code] = balance;
|
|
792
|
+
oldBalance['info'] = message;
|
|
793
|
+
this.balance[balanceType] = this.safeBalance(oldBalance);
|
|
794
|
+
updatedTypes[balanceType] = true;
|
|
795
|
+
}
|
|
796
|
+
const updatesKeys = Object.keys(updatedTypes);
|
|
797
|
+
for (let i = 0; i < updatesKeys.length; i++) {
|
|
798
|
+
const type = updatesKeys[i];
|
|
799
|
+
const messageHash = 'balance:' + type;
|
|
800
|
+
client.resolve(this.balance[type], messageHash);
|
|
801
|
+
}
|
|
382
802
|
}
|
|
383
|
-
|
|
803
|
+
parseWsBalance(balance) {
|
|
384
804
|
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
805
|
+
// [
|
|
806
|
+
// "exchange",
|
|
807
|
+
// "LTC",
|
|
808
|
+
// 0.05479727, // balance
|
|
809
|
+
// 0,
|
|
810
|
+
// null, // available null if not calculated yet
|
|
811
|
+
// "Trading fees for 0.05 LTC (LTCUST) @ 51.872 on BFX (0.2%)",
|
|
812
|
+
// null,
|
|
813
|
+
// ]
|
|
814
|
+
//
|
|
815
|
+
const totalBalance = this.safeString(balance, 2);
|
|
816
|
+
const availableBalance = this.safeString(balance, 4);
|
|
817
|
+
const account = this.account();
|
|
818
|
+
if (availableBalance !== undefined) {
|
|
819
|
+
account['free'] = availableBalance;
|
|
820
|
+
}
|
|
821
|
+
account['total'] = totalBalance;
|
|
822
|
+
return account;
|
|
823
|
+
}
|
|
824
|
+
handleSystemStatus(client, message) {
|
|
388
825
|
//
|
|
389
826
|
// {
|
|
390
827
|
// "event": "info",
|
|
@@ -415,57 +852,47 @@ export default class bitfinex extends bitfinexRest {
|
|
|
415
852
|
async authenticate(params = {}) {
|
|
416
853
|
const url = this.urls['api']['ws']['private'];
|
|
417
854
|
const client = this.client(url);
|
|
418
|
-
const
|
|
419
|
-
const
|
|
420
|
-
const authenticated = this.safeValue(client.subscriptions,
|
|
855
|
+
const messageHash = 'authenticated';
|
|
856
|
+
const future = client.future(messageHash);
|
|
857
|
+
const authenticated = this.safeValue(client.subscriptions, messageHash);
|
|
421
858
|
if (authenticated === undefined) {
|
|
422
859
|
const nonce = this.milliseconds();
|
|
423
860
|
const payload = 'AUTH' + nonce.toString();
|
|
424
861
|
const signature = this.hmac(this.encode(payload), this.encode(this.secret), sha384, 'hex');
|
|
862
|
+
const event = 'auth';
|
|
425
863
|
const request = {
|
|
426
864
|
'apiKey': this.apiKey,
|
|
427
865
|
'authSig': signature,
|
|
428
866
|
'authNonce': nonce,
|
|
429
867
|
'authPayload': payload,
|
|
430
|
-
'event':
|
|
431
|
-
'filter': [
|
|
432
|
-
'trading',
|
|
433
|
-
'wallet',
|
|
434
|
-
],
|
|
868
|
+
'event': event,
|
|
435
869
|
};
|
|
436
|
-
this.
|
|
870
|
+
const message = this.extend(request, params);
|
|
871
|
+
this.watch(url, messageHash, message, messageHash);
|
|
437
872
|
}
|
|
438
873
|
return await future;
|
|
439
874
|
}
|
|
440
875
|
handleAuthenticationMessage(client, message) {
|
|
876
|
+
const messageHash = 'authenticated';
|
|
441
877
|
const status = this.safeString(message, 'status');
|
|
442
878
|
if (status === 'OK') {
|
|
443
879
|
// we resolve the future here permanently so authentication only happens once
|
|
444
|
-
const future = this.safeValue(client.futures,
|
|
880
|
+
const future = this.safeValue(client.futures, messageHash);
|
|
445
881
|
future.resolve(true);
|
|
446
882
|
}
|
|
447
883
|
else {
|
|
448
884
|
const error = new AuthenticationError(this.json(message));
|
|
449
|
-
client.reject(error,
|
|
885
|
+
client.reject(error, messageHash);
|
|
450
886
|
// allows further authentication attempts
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
delete client.subscriptions[method];
|
|
887
|
+
if (messageHash in client.subscriptions) {
|
|
888
|
+
delete client.subscriptions[messageHash];
|
|
454
889
|
}
|
|
455
890
|
}
|
|
456
891
|
}
|
|
457
|
-
async watchOrder(id, symbol = undefined, params = {}) {
|
|
458
|
-
await this.loadMarkets();
|
|
459
|
-
const url = this.urls['api']['ws']['private'];
|
|
460
|
-
await this.authenticate();
|
|
461
|
-
return await this.watch(url, id, undefined, 1);
|
|
462
|
-
}
|
|
463
892
|
/**
|
|
464
893
|
* @method
|
|
465
894
|
* @name bitfinex#watchOrders
|
|
466
895
|
* @description watches information on multiple orders made by the user
|
|
467
|
-
* @see https://docs.bitfinex.com/v1/reference/ws-auth-order-updates
|
|
468
|
-
* @see https://docs.bitfinex.com/v1/reference/ws-auth-order-snapshots
|
|
469
896
|
* @param {string} symbol unified market symbol of the market orders were made in
|
|
470
897
|
* @param {int} [since] the earliest time in ms to fetch orders for
|
|
471
898
|
* @param {int} [limit] the maximum number of order structures to retrieve
|
|
@@ -474,12 +901,12 @@ export default class bitfinex extends bitfinexRest {
|
|
|
474
901
|
*/
|
|
475
902
|
async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
|
476
903
|
await this.loadMarkets();
|
|
477
|
-
|
|
904
|
+
let messageHash = 'orders';
|
|
478
905
|
if (symbol !== undefined) {
|
|
479
|
-
|
|
906
|
+
const market = this.market(symbol);
|
|
907
|
+
messageHash += ':' + market['id'];
|
|
480
908
|
}
|
|
481
|
-
const
|
|
482
|
-
const orders = await this.watch(url, 'os', undefined, 1);
|
|
909
|
+
const orders = await this.subscribePrivate(messageHash);
|
|
483
910
|
if (this.newUpdates) {
|
|
484
911
|
limit = orders.getLimit(symbol, limit);
|
|
485
912
|
}
|
|
@@ -487,111 +914,159 @@ export default class bitfinex extends bitfinexRest {
|
|
|
487
914
|
}
|
|
488
915
|
handleOrders(client, message, subscription) {
|
|
489
916
|
//
|
|
490
|
-
// order
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
//
|
|
508
|
-
//
|
|
509
|
-
//
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
// 0,
|
|
529
|
-
// 0,
|
|
530
|
-
// 0,
|
|
531
|
-
// ]
|
|
532
|
-
// ]
|
|
917
|
+
// limit order
|
|
918
|
+
// [
|
|
919
|
+
// 0,
|
|
920
|
+
// "on", // ou or oc
|
|
921
|
+
// [
|
|
922
|
+
// 96923856256, // order id
|
|
923
|
+
// null, // gid
|
|
924
|
+
// 1655029337026, // cid
|
|
925
|
+
// "tLTCUST", // symbol
|
|
926
|
+
// 1655029337027, // created timestamp
|
|
927
|
+
// 1655029337029, // updated timestamp
|
|
928
|
+
// 0.1, // amount
|
|
929
|
+
// 0.1, // amount_orig
|
|
930
|
+
// "EXCHANGE LIMIT", // order type
|
|
931
|
+
// null, // type_prev
|
|
932
|
+
// null, // mts_tif
|
|
933
|
+
// null, // placeholder
|
|
934
|
+
// 0, // flags
|
|
935
|
+
// "ACTIVE", // status
|
|
936
|
+
// null,
|
|
937
|
+
// null,
|
|
938
|
+
// 30, // price
|
|
939
|
+
// 0, // price average
|
|
940
|
+
// 0, // price_trailling
|
|
941
|
+
// 0, // price_aux_limit
|
|
942
|
+
// null,
|
|
943
|
+
// null,
|
|
944
|
+
// null,
|
|
945
|
+
// 0, // notify
|
|
946
|
+
// 0,
|
|
947
|
+
// null,
|
|
948
|
+
// null,
|
|
949
|
+
// null,
|
|
950
|
+
// "BFX",
|
|
951
|
+
// null,
|
|
952
|
+
// null,
|
|
953
|
+
// ]
|
|
954
|
+
// ]
|
|
533
955
|
//
|
|
534
956
|
const data = this.safeValue(message, 2, []);
|
|
535
957
|
const messageType = this.safeString(message, 1);
|
|
958
|
+
if (this.orders === undefined) {
|
|
959
|
+
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
|
|
960
|
+
this.orders = new ArrayCacheBySymbolById(limit);
|
|
961
|
+
}
|
|
962
|
+
const orders = this.orders;
|
|
963
|
+
const symbolIds = {};
|
|
536
964
|
if (messageType === 'os') {
|
|
965
|
+
const snapshotLength = data.length;
|
|
966
|
+
if (snapshotLength === 0) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
537
969
|
for (let i = 0; i < data.length; i++) {
|
|
538
970
|
const value = data[i];
|
|
539
|
-
this.
|
|
971
|
+
const parsed = this.parseWsOrder(value);
|
|
972
|
+
const symbol = parsed['symbol'];
|
|
973
|
+
symbolIds[symbol] = true;
|
|
974
|
+
orders.append(parsed);
|
|
540
975
|
}
|
|
541
976
|
}
|
|
542
977
|
else {
|
|
543
|
-
this.
|
|
978
|
+
const parsed = this.parseWsOrder(data);
|
|
979
|
+
orders.append(parsed);
|
|
980
|
+
const symbol = parsed['symbol'];
|
|
981
|
+
symbolIds[symbol] = true;
|
|
544
982
|
}
|
|
545
|
-
|
|
546
|
-
|
|
983
|
+
const name = 'orders';
|
|
984
|
+
client.resolve(this.orders, name);
|
|
985
|
+
const keys = Object.keys(symbolIds);
|
|
986
|
+
for (let i = 0; i < keys.length; i++) {
|
|
987
|
+
const symbol = keys[i];
|
|
988
|
+
const market = this.market(symbol);
|
|
989
|
+
const messageHash = name + ':' + market['id'];
|
|
990
|
+
client.resolve(this.orders, messageHash);
|
|
547
991
|
}
|
|
548
992
|
}
|
|
549
993
|
parseWsOrderStatus(status) {
|
|
550
994
|
const statuses = {
|
|
551
995
|
'ACTIVE': 'open',
|
|
552
996
|
'CANCELED': 'canceled',
|
|
997
|
+
'EXECUTED': 'closed',
|
|
998
|
+
'PARTIALLY': 'open',
|
|
553
999
|
};
|
|
554
1000
|
return this.safeString(statuses, status, status);
|
|
555
1001
|
}
|
|
556
|
-
|
|
557
|
-
//
|
|
558
|
-
//
|
|
559
|
-
//
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
//
|
|
567
|
-
//
|
|
568
|
-
//
|
|
1002
|
+
parseWsOrder(order, market = undefined) {
|
|
1003
|
+
//
|
|
1004
|
+
// [
|
|
1005
|
+
// 97084883506, // order id
|
|
1006
|
+
// null,
|
|
1007
|
+
// 1655110144596, // clientOrderId
|
|
1008
|
+
// "tLTCUST", // symbol
|
|
1009
|
+
// 1655110144596, // created timestamp
|
|
1010
|
+
// 1655110144598, // updated timestamp
|
|
1011
|
+
// 0, // amount
|
|
1012
|
+
// 0.1, // amount_orig negative if sell order
|
|
1013
|
+
// "EXCHANGE MARKET", // type
|
|
1014
|
+
// null,
|
|
1015
|
+
// null,
|
|
1016
|
+
// null,
|
|
1017
|
+
// 0,
|
|
1018
|
+
// "EXECUTED @ 42.821(0.1)", // status
|
|
1019
|
+
// null,
|
|
1020
|
+
// null,
|
|
1021
|
+
// 42.799, // price
|
|
1022
|
+
// 42.821, // price average
|
|
1023
|
+
// 0, // price trailling
|
|
1024
|
+
// 0, // price_aux_limit
|
|
1025
|
+
// null,
|
|
1026
|
+
// null,
|
|
1027
|
+
// null,
|
|
1028
|
+
// 0,
|
|
1029
|
+
// 0,
|
|
1030
|
+
// null,
|
|
1031
|
+
// null,
|
|
1032
|
+
// null,
|
|
1033
|
+
// "BFX",
|
|
1034
|
+
// null,
|
|
1035
|
+
// null,
|
|
1036
|
+
// {}
|
|
1037
|
+
// ]
|
|
1038
|
+
//
|
|
569
1039
|
const id = this.safeString(order, 0);
|
|
570
|
-
const
|
|
1040
|
+
const clientOrderId = this.safeString(order, 1);
|
|
1041
|
+
const marketId = this.safeString(order, 3);
|
|
571
1042
|
const symbol = this.safeSymbol(marketId);
|
|
572
|
-
|
|
573
|
-
let
|
|
1043
|
+
market = this.safeMarket(symbol);
|
|
1044
|
+
let amount = this.safeString(order, 7);
|
|
574
1045
|
let side = 'buy';
|
|
575
1046
|
if (Precise.stringLt(amount, '0')) {
|
|
576
1047
|
amount = Precise.stringAbs(amount);
|
|
577
|
-
remaining = Precise.stringAbs(remaining);
|
|
578
1048
|
side = 'sell';
|
|
579
1049
|
}
|
|
580
|
-
|
|
1050
|
+
const remaining = Precise.stringAbs(this.safeString(order, 6));
|
|
1051
|
+
let type = this.safeString(order, 8);
|
|
581
1052
|
if (type.indexOf('LIMIT') > -1) {
|
|
582
1053
|
type = 'limit';
|
|
583
1054
|
}
|
|
584
1055
|
else if (type.indexOf('MARKET') > -1) {
|
|
585
1056
|
type = 'market';
|
|
586
1057
|
}
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
const
|
|
590
|
-
const
|
|
591
|
-
const
|
|
1058
|
+
const rawState = this.safeString(order, 13);
|
|
1059
|
+
const stateParts = rawState.split(' ');
|
|
1060
|
+
const trimmedStatus = this.safeString(stateParts, 0);
|
|
1061
|
+
const status = this.parseWsOrderStatus(trimmedStatus);
|
|
1062
|
+
const price = this.safeString(order, 16);
|
|
1063
|
+
const timestamp = this.safeInteger2(order, 5, 4);
|
|
1064
|
+
const average = this.safeString(order, 17);
|
|
1065
|
+
const stopPrice = this.omitZero(this.safeString(order, 18));
|
|
1066
|
+
return this.safeOrder({
|
|
592
1067
|
'info': order,
|
|
593
1068
|
'id': id,
|
|
594
|
-
'clientOrderId':
|
|
1069
|
+
'clientOrderId': clientOrderId,
|
|
595
1070
|
'timestamp': timestamp,
|
|
596
1071
|
'datetime': this.iso8601(timestamp),
|
|
597
1072
|
'lastTradeTimestamp': undefined,
|
|
@@ -599,9 +1074,9 @@ export default class bitfinex extends bitfinexRest {
|
|
|
599
1074
|
'type': type,
|
|
600
1075
|
'side': side,
|
|
601
1076
|
'price': price,
|
|
602
|
-
'stopPrice':
|
|
603
|
-
'triggerPrice':
|
|
604
|
-
'average':
|
|
1077
|
+
'stopPrice': stopPrice,
|
|
1078
|
+
'triggerPrice': stopPrice,
|
|
1079
|
+
'average': average,
|
|
605
1080
|
'amount': amount,
|
|
606
1081
|
'remaining': remaining,
|
|
607
1082
|
'filled': undefined,
|
|
@@ -609,60 +1084,74 @@ export default class bitfinex extends bitfinexRest {
|
|
|
609
1084
|
'fee': undefined,
|
|
610
1085
|
'cost': undefined,
|
|
611
1086
|
'trades': undefined,
|
|
612
|
-
});
|
|
613
|
-
if (this.orders === undefined) {
|
|
614
|
-
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
|
|
615
|
-
this.orders = new ArrayCacheBySymbolById(limit);
|
|
616
|
-
}
|
|
617
|
-
const orders = this.orders;
|
|
618
|
-
orders.append(parsed);
|
|
619
|
-
client.resolve(parsed, id);
|
|
620
|
-
return parsed;
|
|
1087
|
+
}, market);
|
|
621
1088
|
}
|
|
622
1089
|
handleMessage(client, message) {
|
|
1090
|
+
const channelId = this.safeString(message, 0);
|
|
1091
|
+
//
|
|
1092
|
+
// [
|
|
1093
|
+
// 1231,
|
|
1094
|
+
// "hb",
|
|
1095
|
+
// ]
|
|
1096
|
+
//
|
|
1097
|
+
// auth message
|
|
1098
|
+
// {
|
|
1099
|
+
// "event": "auth",
|
|
1100
|
+
// "status": "OK",
|
|
1101
|
+
// "chanId": 0,
|
|
1102
|
+
// "userId": 3159883,
|
|
1103
|
+
// "auth_id": "ac7108e7-2f26-424d-9982-c24700dc02ca",
|
|
1104
|
+
// "caps": {
|
|
1105
|
+
// "orders": { read: 1, write: 1 },
|
|
1106
|
+
// "account": { read: 1, write: 1 },
|
|
1107
|
+
// "funding": { read: 1, write: 1 },
|
|
1108
|
+
// "history": { read: 1, write: 0 },
|
|
1109
|
+
// "wallets": { read: 1, write: 1 },
|
|
1110
|
+
// "withdraw": { read: 0, write: 1 },
|
|
1111
|
+
// "positions": { read: 1, write: 1 },
|
|
1112
|
+
// "ui_withdraw": { read: 0, write: 0 }
|
|
1113
|
+
// }
|
|
1114
|
+
// }
|
|
1115
|
+
//
|
|
623
1116
|
if (Array.isArray(message)) {
|
|
624
|
-
const channelId = this.safeString(message, 0);
|
|
625
|
-
//
|
|
626
|
-
// [
|
|
627
|
-
// 1231,
|
|
628
|
-
// "hb",
|
|
629
|
-
// ]
|
|
630
|
-
//
|
|
631
1117
|
if (message[1] === 'hb') {
|
|
632
1118
|
return; // skip heartbeats within subscription channels for now
|
|
633
1119
|
}
|
|
634
1120
|
const subscription = this.safeValue(client.subscriptions, channelId, {});
|
|
635
1121
|
const channel = this.safeString(subscription, 'channel');
|
|
636
1122
|
const name = this.safeString(message, 1);
|
|
637
|
-
const
|
|
1123
|
+
const publicMethods = {
|
|
638
1124
|
'book': this.handleOrderBook,
|
|
639
|
-
|
|
1125
|
+
'cs': this.handleChecksum,
|
|
1126
|
+
'candles': this.handleOHLCV,
|
|
640
1127
|
'ticker': this.handleTicker,
|
|
641
1128
|
'trades': this.handleTrades,
|
|
1129
|
+
};
|
|
1130
|
+
const privateMethods = {
|
|
642
1131
|
'os': this.handleOrders,
|
|
1132
|
+
'ou': this.handleOrders,
|
|
643
1133
|
'on': this.handleOrders,
|
|
644
1134
|
'oc': this.handleOrders,
|
|
1135
|
+
'wu': this.handleBalance,
|
|
1136
|
+
'ws': this.handleBalance,
|
|
1137
|
+
'tu': this.handleMyTrade,
|
|
645
1138
|
};
|
|
646
|
-
|
|
1139
|
+
let method = undefined;
|
|
1140
|
+
if (channelId === '0') {
|
|
1141
|
+
method = this.safeValue(privateMethods, name);
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
method = this.safeValue2(publicMethods, name, channel);
|
|
1145
|
+
}
|
|
647
1146
|
if (method !== undefined) {
|
|
648
1147
|
method.call(this, client, message, subscription);
|
|
649
1148
|
}
|
|
650
1149
|
}
|
|
651
1150
|
else {
|
|
652
|
-
// todo add bitfinex handleErrorMessage
|
|
653
|
-
//
|
|
654
|
-
// {
|
|
655
|
-
// "event": "info",
|
|
656
|
-
// "version": 2,
|
|
657
|
-
// "serverId": "e293377e-7bb7-427e-b28c-5db045b2c1d1",
|
|
658
|
-
// "platform": { status: 1 }, // 1 for operative, 0 for maintenance
|
|
659
|
-
// }
|
|
660
|
-
//
|
|
661
1151
|
const event = this.safeString(message, 'event');
|
|
662
1152
|
if (event !== undefined) {
|
|
663
1153
|
const methods = {
|
|
664
1154
|
'info': this.handleSystemStatus,
|
|
665
|
-
// 'book': 'handleOrderBook',
|
|
666
1155
|
'subscribed': this.handleSubscriptionStatus,
|
|
667
1156
|
'auth': this.handleAuthenticationMessage,
|
|
668
1157
|
};
|