ccxt 4.4.21 → 4.4.23
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 +112 -111
- package/dist/ccxt.browser.min.js +2 -2
- package/dist/cjs/ccxt.js +6 -1
- package/dist/cjs/src/abstract/coincatch.js +9 -0
- package/dist/cjs/src/alpaca.js +1 -0
- package/dist/cjs/src/base/Exchange.js +21 -0
- package/dist/cjs/src/bigone.js +3 -0
- package/dist/cjs/src/binance.js +172 -44
- package/dist/cjs/src/bitfinex.js +4 -0
- package/dist/cjs/src/bitflyer.js +58 -0
- package/dist/cjs/src/bitget.js +77 -0
- package/dist/cjs/src/bitrue.js +3 -0
- package/dist/cjs/src/bybit.js +80 -2
- package/dist/cjs/src/cex.js +1307 -1381
- package/dist/cjs/src/coinbase.js +1 -1
- package/dist/cjs/src/coinbaseexchange.js +3 -0
- package/dist/cjs/src/coincatch.js +5370 -0
- package/dist/cjs/src/coinex.js +63 -1
- package/dist/cjs/src/cryptocom.js +1 -1
- package/dist/cjs/src/gate.js +103 -3
- package/dist/cjs/src/htx.js +1 -7
- package/dist/cjs/src/hyperliquid.js +10 -8
- package/dist/cjs/src/kucoin.js +27 -59
- package/dist/cjs/src/latoken.js +6 -0
- package/dist/cjs/src/mexc.js +1 -1
- package/dist/cjs/src/oceanex.js +2 -0
- package/dist/cjs/src/okcoin.js +1 -0
- package/dist/cjs/src/okx.js +74 -0
- package/dist/cjs/src/poloniex.js +5 -0
- package/dist/cjs/src/pro/coincatch.js +1554 -0
- package/js/ccxt.d.ts +9 -3
- package/js/ccxt.js +6 -2
- package/js/src/abstract/binance.d.ts +21 -0
- package/js/src/abstract/binancecoinm.d.ts +21 -0
- package/js/src/abstract/binanceus.d.ts +21 -0
- package/js/src/abstract/binanceusdm.d.ts +21 -0
- package/js/src/abstract/bitflyer.d.ts +1 -0
- package/js/src/abstract/bitget.d.ts +3 -0
- package/js/src/abstract/cex.d.ts +28 -29
- package/js/src/abstract/coincatch.d.ts +97 -0
- package/js/src/abstract/coincatch.js +11 -0
- package/js/src/abstract/gate.d.ts +5 -0
- package/js/src/abstract/gateio.d.ts +5 -0
- package/js/src/abstract/kucoin.d.ts +1 -0
- package/js/src/abstract/kucoinfutures.d.ts +1 -0
- package/js/src/abstract/okx.d.ts +1 -0
- package/js/src/alpaca.js +1 -0
- package/js/src/base/Exchange.d.ts +8 -2
- package/js/src/base/Exchange.js +21 -0
- package/js/src/base/types.d.ts +8 -0
- package/js/src/bigone.js +3 -0
- package/js/src/binance.d.ts +3 -1
- package/js/src/binance.js +172 -44
- package/js/src/bitfinex.js +4 -0
- package/js/src/bitflyer.d.ts +3 -1
- package/js/src/bitflyer.js +58 -0
- package/js/src/bitget.d.ts +3 -1
- package/js/src/bitget.js +77 -0
- package/js/src/bitrue.js +3 -0
- package/js/src/bybit.d.ts +3 -1
- package/js/src/bybit.js +80 -2
- package/js/src/cex.d.ts +34 -20
- package/js/src/cex.js +1308 -1382
- package/js/src/coinbase.js +1 -1
- package/js/src/coinbaseexchange.js +3 -0
- package/js/src/coincatch.d.ts +130 -0
- package/js/src/coincatch.js +5371 -0
- package/js/src/coinex.d.ts +1 -0
- package/js/src/coinex.js +63 -1
- package/js/src/cryptocom.js +1 -1
- package/js/src/gate.d.ts +2 -0
- package/js/src/gate.js +103 -3
- package/js/src/htx.js +1 -7
- package/js/src/hyperliquid.js +10 -8
- package/js/src/kucoin.d.ts +0 -1
- package/js/src/kucoin.js +27 -59
- package/js/src/latoken.js +6 -0
- package/js/src/mexc.js +1 -1
- package/js/src/oceanex.js +2 -0
- package/js/src/okcoin.js +1 -0
- package/js/src/okx.d.ts +3 -1
- package/js/src/okx.js +74 -0
- package/js/src/poloniex.js +5 -0
- package/js/src/pro/coincatch.d.ts +57 -0
- package/js/src/pro/coincatch.js +1555 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1555 @@
|
|
|
1
|
+
// ----------------------------------------------------------------------------
|
|
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
|
+
// EDIT THE CORRESPONDENT .ts FILE INSTEAD
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
import coincatchRest from '../coincatch.js';
|
|
9
|
+
import { ArgumentsRequired, AuthenticationError, BadRequest, ChecksumError, ExchangeError, NotSupported, RateLimitExceeded, UnsubscribeError } from '../base/errors.js';
|
|
10
|
+
import { Precise } from '../base/Precise.js';
|
|
11
|
+
import { sha256 } from '../static_dependencies/noble-hashes/sha256.js';
|
|
12
|
+
import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp } from '../base/ws/Cache.js';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
export default class coincatch extends coincatchRest {
|
|
15
|
+
describe() {
|
|
16
|
+
return this.deepExtend(super.describe(), {
|
|
17
|
+
'has': {
|
|
18
|
+
'ws': true,
|
|
19
|
+
'watchTrades': true,
|
|
20
|
+
'watchTradesForSymbols': true,
|
|
21
|
+
'watchOrderBook': true,
|
|
22
|
+
'watchOrderBookForSymbols': true,
|
|
23
|
+
'watchOHLCV': true,
|
|
24
|
+
'watchOHLCVForSymbols': false,
|
|
25
|
+
'watchOrders': true,
|
|
26
|
+
'watchMyTrades': false,
|
|
27
|
+
'watchTicker': true,
|
|
28
|
+
'watchTickers': true,
|
|
29
|
+
'watchBalance': true,
|
|
30
|
+
'watchPositions': true,
|
|
31
|
+
},
|
|
32
|
+
'urls': {
|
|
33
|
+
'api': {
|
|
34
|
+
'ws': {
|
|
35
|
+
'public': 'wss://ws.coincatch.com/public/v1/stream',
|
|
36
|
+
'private': 'wss://ws.coincatch.com/private/v1/stream',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
'options': {
|
|
41
|
+
'tradesLimit': 1000,
|
|
42
|
+
'OHLCVLimit': 200,
|
|
43
|
+
'timeframesForWs': {
|
|
44
|
+
'1m': '1m',
|
|
45
|
+
'5m': '5m',
|
|
46
|
+
'15m': '15m',
|
|
47
|
+
'30m': '30m',
|
|
48
|
+
'1h': '1H',
|
|
49
|
+
'4h': '4H',
|
|
50
|
+
'12h': '12H',
|
|
51
|
+
'1d': '1D',
|
|
52
|
+
'1w': '1W',
|
|
53
|
+
},
|
|
54
|
+
'watchOrderBook': {
|
|
55
|
+
'checksum': true,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
'streaming': {
|
|
59
|
+
'ping': this.ping,
|
|
60
|
+
},
|
|
61
|
+
'exceptions': {
|
|
62
|
+
'ws': {
|
|
63
|
+
'exact': {
|
|
64
|
+
'30001': BadRequest,
|
|
65
|
+
'30002': AuthenticationError,
|
|
66
|
+
'30003': BadRequest,
|
|
67
|
+
'30004': AuthenticationError,
|
|
68
|
+
'30005': AuthenticationError,
|
|
69
|
+
'30006': RateLimitExceeded,
|
|
70
|
+
'30007': RateLimitExceeded,
|
|
71
|
+
'30011': AuthenticationError,
|
|
72
|
+
'30012': AuthenticationError,
|
|
73
|
+
'30013': AuthenticationError,
|
|
74
|
+
'30014': BadRequest,
|
|
75
|
+
'30015': AuthenticationError, // { event: 'error', code: 30015, msg: 'Invalid sign' }
|
|
76
|
+
},
|
|
77
|
+
'broad': {},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
getMarketFromArg(entry) {
|
|
83
|
+
const instId = this.safeString(entry, 'instId');
|
|
84
|
+
const instType = this.safeString(entry, 'instType');
|
|
85
|
+
const baseAndQuote = this.parseSpotMarketId(instId);
|
|
86
|
+
const baseId = baseAndQuote['baseId'];
|
|
87
|
+
const quoteId = baseAndQuote['quoteId'];
|
|
88
|
+
let suffix = '_SPBL'; // spot suffix
|
|
89
|
+
if (instType === 'mc') {
|
|
90
|
+
if (quoteId === 'USD') {
|
|
91
|
+
suffix = '_DMCBL';
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
suffix = '_UMCBL';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const marketId = this.safeCurrencyCode(baseId) + this.safeCurrencyCode(quoteId) + suffix;
|
|
98
|
+
return this.safeMarketCustom(marketId);
|
|
99
|
+
}
|
|
100
|
+
async authenticate(params = {}) {
|
|
101
|
+
this.checkRequiredCredentials();
|
|
102
|
+
const url = this.urls['api']['ws']['private'];
|
|
103
|
+
const client = this.client(url);
|
|
104
|
+
const messageHash = 'authenticated';
|
|
105
|
+
const future = client.future(messageHash);
|
|
106
|
+
const authenticated = this.safeValue(client.subscriptions, messageHash);
|
|
107
|
+
if (authenticated === undefined) {
|
|
108
|
+
const timestamp = this.seconds().toString();
|
|
109
|
+
const auth = timestamp + 'GET' + '/user/verify';
|
|
110
|
+
const signature = this.hmac(this.encode(auth), this.encode(this.secret), sha256, 'base64');
|
|
111
|
+
const operation = 'login';
|
|
112
|
+
const request = {
|
|
113
|
+
'op': operation,
|
|
114
|
+
'args': [
|
|
115
|
+
{
|
|
116
|
+
'apiKey': this.apiKey,
|
|
117
|
+
'passphrase': this.password,
|
|
118
|
+
'timestamp': timestamp,
|
|
119
|
+
'sign': signature,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
const message = this.extend(request, params);
|
|
124
|
+
this.watch(url, messageHash, message, messageHash);
|
|
125
|
+
}
|
|
126
|
+
return await future;
|
|
127
|
+
}
|
|
128
|
+
async watchPublic(messageHash, subscribeHash, args, params = {}) {
|
|
129
|
+
const url = this.urls['api']['ws']['public'];
|
|
130
|
+
const request = {
|
|
131
|
+
'op': 'subscribe',
|
|
132
|
+
'args': [args],
|
|
133
|
+
};
|
|
134
|
+
const message = this.extend(request, params);
|
|
135
|
+
return await this.watch(url, messageHash, message, subscribeHash);
|
|
136
|
+
}
|
|
137
|
+
async unWatchPublic(messageHash, args, params = {}) {
|
|
138
|
+
const url = this.urls['api']['ws']['public'];
|
|
139
|
+
const request = {
|
|
140
|
+
'op': 'unsubscribe',
|
|
141
|
+
'args': [args],
|
|
142
|
+
};
|
|
143
|
+
const message = this.extend(request, params);
|
|
144
|
+
return await this.watch(url, messageHash, message, messageHash);
|
|
145
|
+
}
|
|
146
|
+
async watchPrivate(messageHash, subscribeHash, args, params = {}) {
|
|
147
|
+
await this.authenticate();
|
|
148
|
+
const url = this.urls['api']['ws']['private'];
|
|
149
|
+
const request = {
|
|
150
|
+
'op': 'subscribe',
|
|
151
|
+
'args': [args],
|
|
152
|
+
};
|
|
153
|
+
const message = this.extend(request, params);
|
|
154
|
+
return await this.watch(url, messageHash, message, subscribeHash);
|
|
155
|
+
}
|
|
156
|
+
async watchPrivateMultiple(messageHashes, subscribeHashes, args, params = {}) {
|
|
157
|
+
await this.authenticate();
|
|
158
|
+
const url = this.urls['api']['ws']['private'];
|
|
159
|
+
const request = {
|
|
160
|
+
'op': 'subscribe',
|
|
161
|
+
'args': args,
|
|
162
|
+
};
|
|
163
|
+
const message = this.extend(request, params);
|
|
164
|
+
return await this.watchMultiple(url, messageHashes, message, subscribeHashes);
|
|
165
|
+
}
|
|
166
|
+
handleAuthenticate(client, message) {
|
|
167
|
+
//
|
|
168
|
+
// { event: "login", code: 0 }
|
|
169
|
+
//
|
|
170
|
+
const messageHash = 'authenticated';
|
|
171
|
+
const future = this.safeValue(client.futures, messageHash);
|
|
172
|
+
future.resolve(true);
|
|
173
|
+
}
|
|
174
|
+
async watchPublicMultiple(messageHashes, subscribeHashes, argsArray, params = {}) {
|
|
175
|
+
const url = this.urls['api']['ws']['public'];
|
|
176
|
+
const request = {
|
|
177
|
+
'op': 'subscribe',
|
|
178
|
+
'args': argsArray,
|
|
179
|
+
};
|
|
180
|
+
const message = this.extend(request, params);
|
|
181
|
+
return await this.watchMultiple(url, messageHashes, message, subscribeHashes);
|
|
182
|
+
}
|
|
183
|
+
async unWatchChannel(symbol, channel, messageHashTopic, params = {}) {
|
|
184
|
+
await this.loadMarkets();
|
|
185
|
+
const market = this.market(symbol);
|
|
186
|
+
const [instType, instId] = this.getPublicInstTypeAndId(market);
|
|
187
|
+
const messageHash = 'unsubscribe:' + messageHashTopic + ':' + symbol;
|
|
188
|
+
const args = {
|
|
189
|
+
'instType': instType,
|
|
190
|
+
'channel': channel,
|
|
191
|
+
'instId': instId,
|
|
192
|
+
};
|
|
193
|
+
return await this.unWatchPublic(messageHash, args, params);
|
|
194
|
+
}
|
|
195
|
+
getPublicInstTypeAndId(market) {
|
|
196
|
+
const instId = market['baseId'] + market['quoteId'];
|
|
197
|
+
let instType = undefined;
|
|
198
|
+
if (market['spot']) {
|
|
199
|
+
instType = 'SP';
|
|
200
|
+
}
|
|
201
|
+
else if (market['swap']) {
|
|
202
|
+
instType = 'MC';
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
throw new NotSupported(this.id + ' supports only spot and swap markets');
|
|
206
|
+
}
|
|
207
|
+
return [instType, instId];
|
|
208
|
+
}
|
|
209
|
+
handleDMCBLMarketByMessageHashes(market, hash, client, timeframe = undefined) {
|
|
210
|
+
const marketId = market['id'];
|
|
211
|
+
const messageHashes = this.findMessageHashes(client, hash);
|
|
212
|
+
// the exchange counts DMCBL markets as the same market with different quote currencies
|
|
213
|
+
// for example symbols ETHUSD:ETH and ETH/USD:BTC both have the same marketId ETHUSD_DMCBL
|
|
214
|
+
// we need to check all markets with the same marketId to find the correct market that is in messageHashes
|
|
215
|
+
const marketsWithCurrentId = this.safeList(this.markets_by_id, marketId, []);
|
|
216
|
+
let suffix = '';
|
|
217
|
+
if (timeframe !== undefined) {
|
|
218
|
+
suffix = ':' + timeframe;
|
|
219
|
+
}
|
|
220
|
+
for (let i = 0; i < marketsWithCurrentId.length; i++) {
|
|
221
|
+
market = marketsWithCurrentId[i];
|
|
222
|
+
const symbol = market['symbol'];
|
|
223
|
+
const messageHash = hash + symbol + suffix;
|
|
224
|
+
if (this.inArray(messageHash, messageHashes)) {
|
|
225
|
+
return market;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return market;
|
|
229
|
+
}
|
|
230
|
+
async watchTicker(symbol, params = {}) {
|
|
231
|
+
/**
|
|
232
|
+
* @method
|
|
233
|
+
* @name coincatch#watchTicker
|
|
234
|
+
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
235
|
+
* @see https://coincatch.github.io/github.io/en/spot/#tickers-channel
|
|
236
|
+
* @param {string} symbol unified symbol of the market to fetch the ticker for
|
|
237
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
238
|
+
* @param {string} [params.instType] the type of the instrument to fetch the ticker for, 'SP' for spot markets, 'MC' for futures markets (default is 'SP')
|
|
239
|
+
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
|
|
240
|
+
*/
|
|
241
|
+
await this.loadMarkets();
|
|
242
|
+
const market = this.market(symbol);
|
|
243
|
+
const [instType, instId] = this.getPublicInstTypeAndId(market);
|
|
244
|
+
const channel = 'ticker';
|
|
245
|
+
const messageHash = channel + ':' + symbol;
|
|
246
|
+
const args = {
|
|
247
|
+
'instType': instType,
|
|
248
|
+
'channel': channel,
|
|
249
|
+
'instId': instId,
|
|
250
|
+
};
|
|
251
|
+
return await this.watchPublic(messageHash, messageHash, args, params);
|
|
252
|
+
}
|
|
253
|
+
async unWatchTicker(symbol, params = {}) {
|
|
254
|
+
/**
|
|
255
|
+
* @method
|
|
256
|
+
* @name coinctach#unWatchTicker
|
|
257
|
+
* @description unsubscribe from the ticker channel
|
|
258
|
+
* @see https://coincatch.github.io/github.io/en/mix/#tickers-channel
|
|
259
|
+
* @param {string} symbol unified symbol of the market to unwatch the ticker for
|
|
260
|
+
* @returns {any} status of the unwatch request
|
|
261
|
+
*/
|
|
262
|
+
await this.loadMarkets();
|
|
263
|
+
return await this.unWatchChannel(symbol, 'ticker', 'ticker', params);
|
|
264
|
+
}
|
|
265
|
+
async watchTickers(symbols = undefined, params = {}) {
|
|
266
|
+
/**
|
|
267
|
+
* @method
|
|
268
|
+
* @name coincatch#watchTickers
|
|
269
|
+
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
|
|
270
|
+
* @see https://coincatch.github.io/github.io/en/mix/#tickers-channel
|
|
271
|
+
* @param {string[]} symbols unified symbol of the market to watch the tickers for
|
|
272
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
273
|
+
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
|
|
274
|
+
*/
|
|
275
|
+
await this.loadMarkets();
|
|
276
|
+
if (symbols === undefined) {
|
|
277
|
+
symbols = this.symbols;
|
|
278
|
+
}
|
|
279
|
+
const topics = [];
|
|
280
|
+
const messageHashes = [];
|
|
281
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
282
|
+
const symbol = symbols[i];
|
|
283
|
+
const market = this.market(symbol);
|
|
284
|
+
const [instType, instId] = this.getPublicInstTypeAndId(market);
|
|
285
|
+
const args = {
|
|
286
|
+
'instType': instType,
|
|
287
|
+
'channel': 'ticker',
|
|
288
|
+
'instId': instId,
|
|
289
|
+
};
|
|
290
|
+
topics.push(args);
|
|
291
|
+
messageHashes.push('ticker:' + symbol);
|
|
292
|
+
}
|
|
293
|
+
const tickers = await this.watchPublicMultiple(messageHashes, messageHashes, topics, params);
|
|
294
|
+
if (this.newUpdates) {
|
|
295
|
+
const result = {};
|
|
296
|
+
result[tickers['symbol']] = tickers;
|
|
297
|
+
return result;
|
|
298
|
+
}
|
|
299
|
+
return this.filterByArray(this.tickers, 'symbol', symbols);
|
|
300
|
+
}
|
|
301
|
+
handleTicker(client, message) {
|
|
302
|
+
//
|
|
303
|
+
// action: 'snapshot',
|
|
304
|
+
// arg: { instType: 'sp', channel: 'ticker', instId: 'ETHUSDT' },
|
|
305
|
+
// data: [
|
|
306
|
+
// {
|
|
307
|
+
// instId: 'ETHUSDT',
|
|
308
|
+
// last: '2421.06',
|
|
309
|
+
// open24h: '2416.93',
|
|
310
|
+
// high24h: '2441.47',
|
|
311
|
+
// low24h: '2352.99',
|
|
312
|
+
// bestBid: '2421.03',
|
|
313
|
+
// bestAsk: '2421.06',
|
|
314
|
+
// baseVolume: '9445.2043',
|
|
315
|
+
// quoteVolume: '22807159.1148',
|
|
316
|
+
// ts: 1728131730687,
|
|
317
|
+
// labeId: 0,
|
|
318
|
+
// openUtc: '2414.50',
|
|
319
|
+
// chgUTC: '0.00272',
|
|
320
|
+
// bidSz: '3.866',
|
|
321
|
+
// askSz: '0.124'
|
|
322
|
+
// }
|
|
323
|
+
// ],
|
|
324
|
+
// ts: 1728131730688
|
|
325
|
+
//
|
|
326
|
+
const arg = this.safeDict(message, 'arg', {});
|
|
327
|
+
let market = this.getMarketFromArg(arg);
|
|
328
|
+
const marketId = market['id'];
|
|
329
|
+
const hash = 'ticker:';
|
|
330
|
+
if (marketId.indexOf('_DMCBL') >= 0) {
|
|
331
|
+
market = this.handleDMCBLMarketByMessageHashes(market, hash, client);
|
|
332
|
+
}
|
|
333
|
+
const data = this.safeList(message, 'data', []);
|
|
334
|
+
const ticker = this.parseWsTicker(this.safeDict(data, 0, {}), market);
|
|
335
|
+
const symbol = market['symbol'];
|
|
336
|
+
this.tickers[symbol] = ticker;
|
|
337
|
+
const messageHash = hash + symbol;
|
|
338
|
+
client.resolve(this.tickers[symbol], messageHash);
|
|
339
|
+
}
|
|
340
|
+
parseWsTicker(ticker, market = undefined) {
|
|
341
|
+
//
|
|
342
|
+
// spot
|
|
343
|
+
// {
|
|
344
|
+
// instId: 'ETHUSDT',
|
|
345
|
+
// last: '2421.06',
|
|
346
|
+
// open24h: '2416.93',
|
|
347
|
+
// high24h: '2441.47',
|
|
348
|
+
// low24h: '2352.99',
|
|
349
|
+
// bestBid: '2421.03',
|
|
350
|
+
// bestAsk: '2421.06',
|
|
351
|
+
// baseVolume: '9445.2043',
|
|
352
|
+
// quoteVolume: '22807159.1148',
|
|
353
|
+
// ts: 1728131730687,
|
|
354
|
+
// labeId: 0,
|
|
355
|
+
// openUtc: '2414.50',
|
|
356
|
+
// chgUTC: '0.00272',
|
|
357
|
+
// bidSz: '3.866',
|
|
358
|
+
// askSz: '0.124'
|
|
359
|
+
// }
|
|
360
|
+
//
|
|
361
|
+
// swap
|
|
362
|
+
// {
|
|
363
|
+
// instId: 'ETHUSDT',
|
|
364
|
+
// last: '2434.47',
|
|
365
|
+
// bestAsk: '2434.48',
|
|
366
|
+
// bestBid: '2434.47',
|
|
367
|
+
// high24h: '2471.68',
|
|
368
|
+
// low24h: '2400.01',
|
|
369
|
+
// priceChangePercent: '0.00674',
|
|
370
|
+
// capitalRate: '0.000082',
|
|
371
|
+
// nextSettleTime: 1728489600000,
|
|
372
|
+
// systemTime: 1728471993602,
|
|
373
|
+
// markPrice: '2434.46',
|
|
374
|
+
// indexPrice: '2435.44',
|
|
375
|
+
// holding: '171450.25',
|
|
376
|
+
// baseVolume: '1699298.91',
|
|
377
|
+
// quoteVolume: '4144522832.32',
|
|
378
|
+
// openUtc: '2439.67',
|
|
379
|
+
// chgUTC: '-0.00213',
|
|
380
|
+
// symbolType: 1,
|
|
381
|
+
// symbolId: 'ETHUSDT_UMCBL',
|
|
382
|
+
// deliveryPrice: '0',
|
|
383
|
+
// bidSz: '26.12',
|
|
384
|
+
// askSz: '49.6'
|
|
385
|
+
// }
|
|
386
|
+
//
|
|
387
|
+
const last = this.safeString(ticker, 'last');
|
|
388
|
+
const timestamp = this.safeInteger2(ticker, 'ts', 'systemTime');
|
|
389
|
+
return this.safeTicker({
|
|
390
|
+
'symbol': market['symbol'],
|
|
391
|
+
'timestamp': timestamp,
|
|
392
|
+
'datetime': this.iso8601(timestamp),
|
|
393
|
+
'high': this.safeString(ticker, 'high24h'),
|
|
394
|
+
'low': this.safeString(ticker, 'low24h'),
|
|
395
|
+
'bid': this.safeString(ticker, 'bestBid'),
|
|
396
|
+
'bidVolume': this.safeString(ticker, 'bidSz'),
|
|
397
|
+
'ask': this.safeString(ticker, 'bestAsk'),
|
|
398
|
+
'askVolume': this.safeString(ticker, 'askSz'),
|
|
399
|
+
'vwap': undefined,
|
|
400
|
+
'open': this.safeString2(ticker, 'open24h', 'openUtc'),
|
|
401
|
+
'close': last,
|
|
402
|
+
'last': last,
|
|
403
|
+
'previousClose': undefined,
|
|
404
|
+
'change': undefined,
|
|
405
|
+
'percentage': Precise.stringMul(this.safeString(ticker, 'chgUTC'), '100'),
|
|
406
|
+
'average': undefined,
|
|
407
|
+
'baseVolume': this.safeNumber(ticker, 'baseVolume'),
|
|
408
|
+
'quoteVolume': this.safeNumber(ticker, 'quoteVolume'),
|
|
409
|
+
'indexPrice': this.safeString(ticker, 'indexPrice'),
|
|
410
|
+
'markPrice': this.safeString(ticker, 'markPrice'),
|
|
411
|
+
'info': ticker,
|
|
412
|
+
}, market);
|
|
413
|
+
}
|
|
414
|
+
async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
|
|
415
|
+
/**
|
|
416
|
+
* @method
|
|
417
|
+
* @name coincatch#watchOHLCV
|
|
418
|
+
* @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
419
|
+
* @see https://coincatch.github.io/github.io/en/spot/#candlesticks-channel
|
|
420
|
+
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
|
|
421
|
+
* @param {string} timeframe the length of time each candle represents
|
|
422
|
+
* @param {int} [since] timestamp in ms of the earliest candle to fetch (not including)
|
|
423
|
+
* @param {int} [limit] the maximum amount of candles to fetch (not including)
|
|
424
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
425
|
+
* @param {bool} [params.instType] the type of the instrument to fetch the OHLCV data for, 'SP' for spot markets, 'MC' for futures markets (default is 'SP')
|
|
426
|
+
* @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
|
|
427
|
+
*/
|
|
428
|
+
await this.loadMarkets();
|
|
429
|
+
const market = this.market(symbol);
|
|
430
|
+
const timeframes = this.options['timeframesForWs'];
|
|
431
|
+
const channel = 'candle' + this.safeString(timeframes, timeframe);
|
|
432
|
+
const [instType, instId] = this.getPublicInstTypeAndId(market);
|
|
433
|
+
const args = {
|
|
434
|
+
'instType': instType,
|
|
435
|
+
'channel': channel,
|
|
436
|
+
'instId': instId,
|
|
437
|
+
};
|
|
438
|
+
const messageHash = 'ohlcv:' + symbol + ':' + timeframe;
|
|
439
|
+
const ohlcv = await this.watchPublic(messageHash, messageHash, args, params);
|
|
440
|
+
if (this.newUpdates) {
|
|
441
|
+
limit = ohlcv.getLimit(symbol, limit);
|
|
442
|
+
}
|
|
443
|
+
return this.filterBySinceLimit(ohlcv, since, limit, 0, true);
|
|
444
|
+
}
|
|
445
|
+
async unWatchOHLCV(symbol, timeframe = '1m', params = {}) {
|
|
446
|
+
/**
|
|
447
|
+
* @method
|
|
448
|
+
* @name coincatch#unWatchOHLCV
|
|
449
|
+
* @description unsubscribe from the ohlcv channel
|
|
450
|
+
* @see https://www.bitget.com/api-doc/spot/websocket/public/Candlesticks-Channel
|
|
451
|
+
* @param {string} symbol unified symbol of the market to unwatch the ohlcv for
|
|
452
|
+
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
|
|
453
|
+
*/
|
|
454
|
+
await this.loadMarkets();
|
|
455
|
+
const timeframes = this.options['timeframesForWs'];
|
|
456
|
+
const interval = this.safeString(timeframes, timeframe);
|
|
457
|
+
const channel = 'candle' + interval;
|
|
458
|
+
return await this.unWatchChannel(symbol, channel, 'ohlcv:' + interval, params);
|
|
459
|
+
}
|
|
460
|
+
handleOHLCV(client, message) {
|
|
461
|
+
//
|
|
462
|
+
// {
|
|
463
|
+
// action: 'update',
|
|
464
|
+
// arg: { instType: 'sp', channel: 'candle1D', instId: 'ETHUSDT' },
|
|
465
|
+
// data: [
|
|
466
|
+
// [
|
|
467
|
+
// '1728316800000',
|
|
468
|
+
// '2474.5',
|
|
469
|
+
// '2478.21',
|
|
470
|
+
// '2459.8',
|
|
471
|
+
// '2463.51',
|
|
472
|
+
// '86.0551'
|
|
473
|
+
// ]
|
|
474
|
+
// ],
|
|
475
|
+
// ts: 1728317607657
|
|
476
|
+
// }
|
|
477
|
+
//
|
|
478
|
+
const arg = this.safeDict(message, 'arg', {});
|
|
479
|
+
let market = this.getMarketFromArg(arg);
|
|
480
|
+
const marketId = market['id'];
|
|
481
|
+
const hash = 'ohlcv:';
|
|
482
|
+
const data = this.safeList(message, 'data', []);
|
|
483
|
+
const channel = this.safeString(arg, 'channel');
|
|
484
|
+
const klineType = channel.slice(6);
|
|
485
|
+
const timeframe = this.findTimeframe(klineType);
|
|
486
|
+
if (marketId.indexOf('_DMCBL') >= 0) {
|
|
487
|
+
market = this.handleDMCBLMarketByMessageHashes(market, hash, client, timeframe);
|
|
488
|
+
}
|
|
489
|
+
const symbol = market['symbol'];
|
|
490
|
+
if (!(symbol in this.ohlcvs)) {
|
|
491
|
+
this.ohlcvs[symbol] = {};
|
|
492
|
+
}
|
|
493
|
+
if (!(timeframe in this.ohlcvs[symbol])) {
|
|
494
|
+
const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000);
|
|
495
|
+
this.ohlcvs[symbol][timeframe] = new ArrayCacheByTimestamp(limit);
|
|
496
|
+
}
|
|
497
|
+
const stored = this.ohlcvs[symbol][timeframe];
|
|
498
|
+
for (let i = 0; i < data.length; i++) {
|
|
499
|
+
const candle = this.safeList(data, i, []);
|
|
500
|
+
const parsed = this.parseWsOHLCV(candle, market);
|
|
501
|
+
stored.append(parsed);
|
|
502
|
+
}
|
|
503
|
+
const messageHash = hash + symbol + ':' + timeframe;
|
|
504
|
+
client.resolve(stored, messageHash);
|
|
505
|
+
}
|
|
506
|
+
parseWsOHLCV(ohlcv, market = undefined) {
|
|
507
|
+
//
|
|
508
|
+
// [
|
|
509
|
+
// '1728316800000',
|
|
510
|
+
// '2474.5',
|
|
511
|
+
// '2478.21',
|
|
512
|
+
// '2459.8',
|
|
513
|
+
// '2463.51',
|
|
514
|
+
// '86.0551'
|
|
515
|
+
// ]
|
|
516
|
+
//
|
|
517
|
+
return [
|
|
518
|
+
this.safeInteger(ohlcv, 0),
|
|
519
|
+
this.safeNumber(ohlcv, 1),
|
|
520
|
+
this.safeNumber(ohlcv, 2),
|
|
521
|
+
this.safeNumber(ohlcv, 3),
|
|
522
|
+
this.safeNumber(ohlcv, 4),
|
|
523
|
+
this.safeNumber(ohlcv, 5),
|
|
524
|
+
];
|
|
525
|
+
}
|
|
526
|
+
async watchOrderBook(symbol, limit = undefined, params = {}) {
|
|
527
|
+
/**
|
|
528
|
+
* @method
|
|
529
|
+
* @name coincatch#watchOrderBook
|
|
530
|
+
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
|
|
531
|
+
* @see https://coincatch.github.io/github.io/en/spot/#depth-channel
|
|
532
|
+
* @param {string} symbol unified symbol of the market to fetch the order book for
|
|
533
|
+
* @param {int} [limit] the maximum amount of order book entries to return
|
|
534
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
535
|
+
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
|
|
536
|
+
*/
|
|
537
|
+
return await this.watchOrderBookForSymbols([symbol], limit, params);
|
|
538
|
+
}
|
|
539
|
+
async unWatchOrderBook(symbol, params = {}) {
|
|
540
|
+
/**
|
|
541
|
+
* @method
|
|
542
|
+
* @name coincatch#unWatchOrderBook
|
|
543
|
+
* @description unsubscribe from the orderbook channel
|
|
544
|
+
* @see https://coincatch.github.io/github.io/en/spot/#depth-channel
|
|
545
|
+
* @param {string} symbol unified symbol of the market to fetch the order book for
|
|
546
|
+
* @param {int} [params.limit] orderbook limit, default is undefined
|
|
547
|
+
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
|
|
548
|
+
*/
|
|
549
|
+
await this.loadMarkets();
|
|
550
|
+
let channel = 'books';
|
|
551
|
+
const limit = this.safeInteger(params, 'limit');
|
|
552
|
+
if ((limit === 5) || (limit === 15)) {
|
|
553
|
+
params = this.omit(params, 'limit');
|
|
554
|
+
channel += limit.toString();
|
|
555
|
+
}
|
|
556
|
+
return await this.unWatchChannel(symbol, channel, channel, params);
|
|
557
|
+
}
|
|
558
|
+
async watchOrderBookForSymbols(symbols, limit = undefined, params = {}) {
|
|
559
|
+
/**
|
|
560
|
+
* @method
|
|
561
|
+
* @name coincatch#watchOrderBook
|
|
562
|
+
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
|
|
563
|
+
* @see https://coincatch.github.io/github.io/en/spot/#depth-channel
|
|
564
|
+
* @param {string} symbol unified symbol of the market to fetch the order book for
|
|
565
|
+
* @param {int} [limit] the maximum amount of order book entries to return
|
|
566
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
567
|
+
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
|
|
568
|
+
*/
|
|
569
|
+
await this.loadMarkets();
|
|
570
|
+
symbols = this.marketSymbols(symbols);
|
|
571
|
+
const channel = 'books';
|
|
572
|
+
const topics = [];
|
|
573
|
+
const messageHashes = [];
|
|
574
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
575
|
+
const symbol = symbols[i];
|
|
576
|
+
const market = this.market(symbol);
|
|
577
|
+
const [instType, instId] = this.getPublicInstTypeAndId(market);
|
|
578
|
+
const args = {
|
|
579
|
+
'instType': instType,
|
|
580
|
+
'channel': channel,
|
|
581
|
+
'instId': instId,
|
|
582
|
+
};
|
|
583
|
+
topics.push(args);
|
|
584
|
+
messageHashes.push(channel + ':' + symbol);
|
|
585
|
+
}
|
|
586
|
+
const orderbook = await this.watchPublicMultiple(messageHashes, messageHashes, topics, params);
|
|
587
|
+
return orderbook.limit();
|
|
588
|
+
}
|
|
589
|
+
handleOrderBook(client, message) {
|
|
590
|
+
//
|
|
591
|
+
// {
|
|
592
|
+
// action: 'update',
|
|
593
|
+
// arg: { instType: 'sp', channel: 'books', instId: 'ETHUSDT' },
|
|
594
|
+
// data: [
|
|
595
|
+
// {
|
|
596
|
+
// asks: [ [ 2507.07, 0.4248 ] ],
|
|
597
|
+
// bids: [ [ 2507.05, 0.1198 ] ],
|
|
598
|
+
// checksum: -1400923312,
|
|
599
|
+
// ts: '1728339446908'
|
|
600
|
+
// }
|
|
601
|
+
// ],
|
|
602
|
+
// ts: 1728339446908
|
|
603
|
+
// }
|
|
604
|
+
//
|
|
605
|
+
const arg = this.safeDict(message, 'arg', {});
|
|
606
|
+
let market = this.getMarketFromArg(arg);
|
|
607
|
+
const marketId = market['id'];
|
|
608
|
+
const hash = 'books:';
|
|
609
|
+
if (marketId.indexOf('_DMCBL') >= 0) {
|
|
610
|
+
market = this.handleDMCBLMarketByMessageHashes(market, hash, client);
|
|
611
|
+
}
|
|
612
|
+
const symbol = market['symbol'];
|
|
613
|
+
const channel = this.safeString(arg, 'channel');
|
|
614
|
+
const messageHash = hash + symbol;
|
|
615
|
+
const data = this.safeList(message, 'data', []);
|
|
616
|
+
const rawOrderBook = this.safeDict(data, 0);
|
|
617
|
+
const timestamp = this.safeInteger(rawOrderBook, 'ts');
|
|
618
|
+
const incrementalBook = channel;
|
|
619
|
+
if (incrementalBook) {
|
|
620
|
+
if (!(symbol in this.orderbooks)) {
|
|
621
|
+
const ob = this.countedOrderBook({});
|
|
622
|
+
ob['symbol'] = symbol;
|
|
623
|
+
this.orderbooks[symbol] = ob;
|
|
624
|
+
}
|
|
625
|
+
const storedOrderBook = this.orderbooks[symbol];
|
|
626
|
+
const asks = this.safeList(rawOrderBook, 'asks', []);
|
|
627
|
+
const bids = this.safeList(rawOrderBook, 'bids', []);
|
|
628
|
+
this.handleDeltas(storedOrderBook['asks'], asks);
|
|
629
|
+
this.handleDeltas(storedOrderBook['bids'], bids);
|
|
630
|
+
storedOrderBook['timestamp'] = timestamp;
|
|
631
|
+
storedOrderBook['datetime'] = this.iso8601(timestamp);
|
|
632
|
+
const checksum = this.safeBool(this.options, 'checksum', true);
|
|
633
|
+
const isSnapshot = this.safeString(message, 'action') === 'snapshot';
|
|
634
|
+
if (!isSnapshot && checksum) {
|
|
635
|
+
const storedAsks = storedOrderBook['asks'];
|
|
636
|
+
const storedBids = storedOrderBook['bids'];
|
|
637
|
+
const asksLength = storedAsks.length;
|
|
638
|
+
const bidsLength = storedBids.length;
|
|
639
|
+
const payloadArray = [];
|
|
640
|
+
for (let i = 0; i < 25; i++) {
|
|
641
|
+
if (i < bidsLength) {
|
|
642
|
+
payloadArray.push(storedBids[i][2][0]);
|
|
643
|
+
payloadArray.push(storedBids[i][2][1]);
|
|
644
|
+
}
|
|
645
|
+
if (i < asksLength) {
|
|
646
|
+
payloadArray.push(storedAsks[i][2][0]);
|
|
647
|
+
payloadArray.push(storedAsks[i][2][1]);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
const payload = payloadArray.join(':');
|
|
651
|
+
const calculatedChecksum = this.crc32(payload, true);
|
|
652
|
+
const responseChecksum = this.safeInteger(rawOrderBook, 'checksum');
|
|
653
|
+
if (calculatedChecksum !== responseChecksum) {
|
|
654
|
+
this.spawn(this.handleCheckSumError, client, symbol, messageHash);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
const orderbook = this.orderBook({});
|
|
661
|
+
const parsedOrderbook = this.parseOrderBook(rawOrderBook, symbol, timestamp);
|
|
662
|
+
orderbook.reset(parsedOrderbook);
|
|
663
|
+
this.orderbooks[symbol] = orderbook;
|
|
664
|
+
}
|
|
665
|
+
client.resolve(this.orderbooks[symbol], messageHash);
|
|
666
|
+
}
|
|
667
|
+
async handleCheckSumError(client, symbol, messageHash) {
|
|
668
|
+
await this.unWatchOrderBook(symbol);
|
|
669
|
+
const error = new ChecksumError(this.id + ' ' + this.orderbookChecksumMessage(symbol));
|
|
670
|
+
client.reject(error, messageHash);
|
|
671
|
+
}
|
|
672
|
+
handleDelta(bookside, delta) {
|
|
673
|
+
const bidAsk = this.parseBidAsk(delta, 0, 1);
|
|
674
|
+
bidAsk.push(delta);
|
|
675
|
+
bookside.storeArray(bidAsk);
|
|
676
|
+
}
|
|
677
|
+
handleDeltas(bookside, deltas) {
|
|
678
|
+
for (let i = 0; i < deltas.length; i++) {
|
|
679
|
+
this.handleDelta(bookside, deltas[i]);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
|
|
683
|
+
/**
|
|
684
|
+
* @method
|
|
685
|
+
* @name coincatch#watchTrades
|
|
686
|
+
* @description get the list of most recent trades for a particular symbol
|
|
687
|
+
* @see https://coincatch.github.io/github.io/en/spot/#trades-channel
|
|
688
|
+
* @param {string} symbol unified symbol of the market to fetch trades for
|
|
689
|
+
* @param {int} [since] timestamp in ms of the earliest trade to fetch
|
|
690
|
+
* @param {int} [limit] the maximum amount of trades to fetch
|
|
691
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
692
|
+
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
|
|
693
|
+
*/
|
|
694
|
+
return await this.watchTradesForSymbols([symbol], since, limit, params);
|
|
695
|
+
}
|
|
696
|
+
async watchTradesForSymbols(symbols, since = undefined, limit = undefined, params = {}) {
|
|
697
|
+
/**
|
|
698
|
+
* @method
|
|
699
|
+
* @name coincatch#watchTrades
|
|
700
|
+
* @description watches information on multiple trades made in a market
|
|
701
|
+
* @see https://coincatch.github.io/github.io/en/spot/#trades-channel
|
|
702
|
+
* @param {string} symbol unified market symbol of the market trades were made in
|
|
703
|
+
* @param {int} [since] the earliest time in ms to fetch orders for
|
|
704
|
+
* @param {int} [limit] the maximum number of trade structures to retrieve
|
|
705
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
706
|
+
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure}
|
|
707
|
+
*/
|
|
708
|
+
const symbolsLength = symbols.length;
|
|
709
|
+
if (symbolsLength === 0) {
|
|
710
|
+
throw new ArgumentsRequired(this.id + ' watchTradesForSymbols() requires a non-empty array of symbols');
|
|
711
|
+
}
|
|
712
|
+
await this.loadMarkets();
|
|
713
|
+
symbols = this.marketSymbols(symbols);
|
|
714
|
+
const topics = [];
|
|
715
|
+
const messageHashes = [];
|
|
716
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
717
|
+
const symbol = symbols[i];
|
|
718
|
+
const market = this.market(symbol);
|
|
719
|
+
const [instType, instId] = this.getPublicInstTypeAndId(market);
|
|
720
|
+
const args = {
|
|
721
|
+
'instType': instType,
|
|
722
|
+
'channel': 'trade',
|
|
723
|
+
'instId': instId,
|
|
724
|
+
};
|
|
725
|
+
topics.push(args);
|
|
726
|
+
messageHashes.push('trade:' + symbol);
|
|
727
|
+
}
|
|
728
|
+
const trades = await this.watchPublicMultiple(messageHashes, messageHashes, topics, params);
|
|
729
|
+
if (this.newUpdates) {
|
|
730
|
+
const first = this.safeDict(trades, 0);
|
|
731
|
+
const tradeSymbol = this.safeString(first, 'symbol');
|
|
732
|
+
limit = trades.getLimit(tradeSymbol, limit);
|
|
733
|
+
}
|
|
734
|
+
return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
|
|
735
|
+
}
|
|
736
|
+
async unWatchTrades(symbol, params = {}) {
|
|
737
|
+
/**
|
|
738
|
+
* @method
|
|
739
|
+
* @name coincatch#unWatchTrades
|
|
740
|
+
* @description unsubscribe from the trades channel
|
|
741
|
+
* @see https://coincatch.github.io/github.io/en/spot/#trades-channel
|
|
742
|
+
* @param {string} symbol unified symbol of the market to unwatch the trades for
|
|
743
|
+
* @returns {any} status of the unwatch request
|
|
744
|
+
*/
|
|
745
|
+
await this.loadMarkets();
|
|
746
|
+
return await this.unWatchChannel(symbol, 'trade', 'trade', params);
|
|
747
|
+
}
|
|
748
|
+
handleTrades(client, message) {
|
|
749
|
+
//
|
|
750
|
+
// {
|
|
751
|
+
// action: 'update',
|
|
752
|
+
// arg: { instType: 'sp', channel: 'trade', instId: 'ETHUSDT' },
|
|
753
|
+
// data: [ [ '1728341807469', '2421.41', '0.478', 'sell' ] ],
|
|
754
|
+
// ts: 1728341807482
|
|
755
|
+
// }
|
|
756
|
+
//
|
|
757
|
+
const arg = this.safeDict(message, 'arg', {});
|
|
758
|
+
let market = this.getMarketFromArg(arg);
|
|
759
|
+
const marketId = market['id'];
|
|
760
|
+
const hash = 'trade:';
|
|
761
|
+
if (marketId.indexOf('_DMCBL') >= 0) {
|
|
762
|
+
market = this.handleDMCBLMarketByMessageHashes(market, hash, client);
|
|
763
|
+
}
|
|
764
|
+
const symbol = market['symbol'];
|
|
765
|
+
if (!(symbol in this.trades)) {
|
|
766
|
+
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
|
|
767
|
+
this.trades[symbol] = new ArrayCache(limit);
|
|
768
|
+
}
|
|
769
|
+
const stored = this.trades[symbol];
|
|
770
|
+
let data = this.safeList(message, 'data', []);
|
|
771
|
+
if (data !== undefined) {
|
|
772
|
+
data = this.sortBy(data, 0);
|
|
773
|
+
for (let i = 0; i < data.length; i++) {
|
|
774
|
+
const trade = this.safeList(data, i);
|
|
775
|
+
const parsed = this.parseWsTrade(trade, market);
|
|
776
|
+
stored.append(parsed);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const messageHash = hash + symbol;
|
|
780
|
+
client.resolve(stored, messageHash);
|
|
781
|
+
}
|
|
782
|
+
parseWsTrade(trade, market = undefined) {
|
|
783
|
+
//
|
|
784
|
+
// [
|
|
785
|
+
// '1728341807469',
|
|
786
|
+
// '2421.41',
|
|
787
|
+
// '0.478',
|
|
788
|
+
// 'sell'
|
|
789
|
+
// ]
|
|
790
|
+
//
|
|
791
|
+
const timestamp = this.safeInteger(trade, 0);
|
|
792
|
+
return this.safeTrade({
|
|
793
|
+
'id': undefined,
|
|
794
|
+
'timestamp': timestamp,
|
|
795
|
+
'datetime': this.iso8601(timestamp),
|
|
796
|
+
'symbol': market['symbol'],
|
|
797
|
+
'side': this.safeStringLower(trade, 3),
|
|
798
|
+
'price': this.safeString(trade, 1),
|
|
799
|
+
'amount': this.safeString(trade, 2),
|
|
800
|
+
'cost': undefined,
|
|
801
|
+
'takerOrMaker': undefined,
|
|
802
|
+
'type': undefined,
|
|
803
|
+
'order': undefined,
|
|
804
|
+
'fee': undefined,
|
|
805
|
+
'info': trade,
|
|
806
|
+
}, market);
|
|
807
|
+
}
|
|
808
|
+
async watchBalance(params = {}) {
|
|
809
|
+
/**
|
|
810
|
+
* @method
|
|
811
|
+
* @name coincatch#watchBalance
|
|
812
|
+
* @description watch balance and get the amount of funds available for trading or funds locked in orders
|
|
813
|
+
* @see https://coincatch.github.io/github.io/en/spot/#account-channel
|
|
814
|
+
* @see https://coincatch.github.io/github.io/en/mix/#account-channel
|
|
815
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
816
|
+
* @param {str} [params.type] 'spot' or 'swap' (default is 'spot')
|
|
817
|
+
* @param {string} [params.instType] *swap only* 'umcbl' or 'dmcbl' (default is 'umcbl')
|
|
818
|
+
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
|
|
819
|
+
*/
|
|
820
|
+
let type = undefined;
|
|
821
|
+
[type, params] = this.handleMarketTypeAndParams('watchBalance', undefined, params);
|
|
822
|
+
let instType = 'spbl'; // must be lower case for spot
|
|
823
|
+
if (type === 'swap') {
|
|
824
|
+
instType = 'umcbl';
|
|
825
|
+
}
|
|
826
|
+
const channel = 'account';
|
|
827
|
+
[instType, params] = this.handleOptionAndParams(params, 'watchBalance', 'instType', instType);
|
|
828
|
+
const args = {
|
|
829
|
+
'instType': instType,
|
|
830
|
+
'channel': channel,
|
|
831
|
+
'instId': 'default',
|
|
832
|
+
};
|
|
833
|
+
const messageHash = 'balance:' + instType.toLowerCase();
|
|
834
|
+
return await this.watchPrivate(messageHash, messageHash, args, params);
|
|
835
|
+
}
|
|
836
|
+
handleBalance(client, message) {
|
|
837
|
+
//
|
|
838
|
+
// spot
|
|
839
|
+
// {
|
|
840
|
+
// action: 'snapshot',
|
|
841
|
+
// arg: { instType: 'spbl', channel: 'account', instId: 'default' },
|
|
842
|
+
// data: [
|
|
843
|
+
// {
|
|
844
|
+
// coinId: '3',
|
|
845
|
+
// coinName: 'ETH',
|
|
846
|
+
// available: '0.0000832',
|
|
847
|
+
// frozen: '0',
|
|
848
|
+
// lock: '0'
|
|
849
|
+
// }
|
|
850
|
+
// ],
|
|
851
|
+
// ts: 1728464548725
|
|
852
|
+
// }
|
|
853
|
+
//
|
|
854
|
+
// // swap
|
|
855
|
+
// {
|
|
856
|
+
// action: 'snapshot',
|
|
857
|
+
// arg: { instType: 'dmcbl', channel: 'account', instId: 'default' },
|
|
858
|
+
// data: [
|
|
859
|
+
// {
|
|
860
|
+
// marginCoin: 'ETH',
|
|
861
|
+
// locked: '0.00000000',
|
|
862
|
+
// available: '0.00001203',
|
|
863
|
+
// maxOpenPosAvailable: '0.00001203',
|
|
864
|
+
// maxTransferOut: '0.00001203',
|
|
865
|
+
// equity: '0.00001203',
|
|
866
|
+
// usdtEquity: '0.029092328738',
|
|
867
|
+
// coinDisplayName: 'ETH'
|
|
868
|
+
// }
|
|
869
|
+
// ],
|
|
870
|
+
// ts: 1728650777643
|
|
871
|
+
// }
|
|
872
|
+
//
|
|
873
|
+
const data = this.safeList(message, 'data', []);
|
|
874
|
+
for (let i = 0; i < data.length; i++) {
|
|
875
|
+
const rawBalance = data[i];
|
|
876
|
+
const currencyId = this.safeString2(rawBalance, 'coinName', 'marginCoin');
|
|
877
|
+
const code = this.safeCurrencyCode(currencyId);
|
|
878
|
+
const account = (code in this.balance) ? this.balance[code] : this.account();
|
|
879
|
+
const freeQuery = ('maxTransferOut' in rawBalance) ? 'maxTransferOut' : 'available';
|
|
880
|
+
account['free'] = this.safeString(rawBalance, freeQuery);
|
|
881
|
+
account['total'] = this.safeString(rawBalance, 'equity');
|
|
882
|
+
account['used'] = this.safeString(rawBalance, 'frozen');
|
|
883
|
+
this.balance[code] = account;
|
|
884
|
+
}
|
|
885
|
+
this.balance = this.safeBalance(this.balance);
|
|
886
|
+
const arg = this.safeDict(message, 'arg');
|
|
887
|
+
const instType = this.safeStringLower(arg, 'instType');
|
|
888
|
+
const messageHash = 'balance:' + instType;
|
|
889
|
+
client.resolve(this.balance, messageHash);
|
|
890
|
+
}
|
|
891
|
+
async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
|
892
|
+
/**
|
|
893
|
+
* @method
|
|
894
|
+
* @name coincatch#watchOrders
|
|
895
|
+
* @description watches information on multiple orders made by the user
|
|
896
|
+
* @see https://coincatch.github.io/github.io/en/spot/#order-channel
|
|
897
|
+
* @see https://coincatch.github.io/github.io/en/mix/#order-channel
|
|
898
|
+
* @see https://coincatch.github.io/github.io/en/mix/#plan-order-channel
|
|
899
|
+
* @param {string} symbol unified market symbol of the market orders were made in
|
|
900
|
+
* @param {int} [since] the earliest time in ms to fetch orders for
|
|
901
|
+
* @param {int} [limit] the maximum number of order structures to retrieve
|
|
902
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
903
|
+
* @param {string} [params.type] 'spot' or 'swap'
|
|
904
|
+
* @param {string} [params.instType] *swap only* 'umcbl' or 'dmcbl' (default is 'umcbl')
|
|
905
|
+
* @param {bool} [params.trigger] *swap only* whether to watch trigger orders (default is false)
|
|
906
|
+
* @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
|
|
907
|
+
*/
|
|
908
|
+
const methodName = 'watchOrders';
|
|
909
|
+
await this.loadMarkets();
|
|
910
|
+
let market = undefined;
|
|
911
|
+
let marketId = undefined;
|
|
912
|
+
if (symbol !== undefined) {
|
|
913
|
+
market = this.market(symbol);
|
|
914
|
+
symbol = market['symbol'];
|
|
915
|
+
marketId = market['id'];
|
|
916
|
+
}
|
|
917
|
+
let marketType = undefined;
|
|
918
|
+
[marketType, params] = this.handleMarketTypeAndParams(methodName, market, params);
|
|
919
|
+
let instType = 'spbl';
|
|
920
|
+
let instId = marketId;
|
|
921
|
+
if (marketType === 'spot') {
|
|
922
|
+
if (symbol === undefined) {
|
|
923
|
+
throw new ArgumentsRequired(this.id + ' ' + methodName + '() requires a symbol argument for ' + marketType + ' markets.');
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
instId = 'default';
|
|
928
|
+
instType = 'umcbl';
|
|
929
|
+
if (symbol === undefined) {
|
|
930
|
+
[instType, params] = this.handleOptionAndParams(params, methodName, 'instType', instType);
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
if (marketId.indexOf('_DMCBL') >= 0) {
|
|
934
|
+
instType = 'dmcbl';
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
let channel = 'orders';
|
|
939
|
+
const isTrigger = this.safeBool(params, 'trigger');
|
|
940
|
+
if (isTrigger) {
|
|
941
|
+
channel = 'ordersAlgo'; // channel does not return any data
|
|
942
|
+
params = this.omit(params, 'trigger');
|
|
943
|
+
}
|
|
944
|
+
const args = {
|
|
945
|
+
'instType': instType,
|
|
946
|
+
'channel': channel,
|
|
947
|
+
'instId': instId,
|
|
948
|
+
};
|
|
949
|
+
let messageHash = 'orders';
|
|
950
|
+
if (symbol !== undefined) {
|
|
951
|
+
messageHash += ':' + symbol;
|
|
952
|
+
}
|
|
953
|
+
const orders = await this.watchPrivate(messageHash, messageHash, args, params);
|
|
954
|
+
if (this.newUpdates) {
|
|
955
|
+
limit = orders.getLimit(symbol, limit);
|
|
956
|
+
}
|
|
957
|
+
return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
|
|
958
|
+
}
|
|
959
|
+
handleOrder(client, message) {
|
|
960
|
+
//
|
|
961
|
+
// spot
|
|
962
|
+
//
|
|
963
|
+
// {
|
|
964
|
+
// action: 'snapshot',
|
|
965
|
+
// arg: { instType: 'spbl', channel: 'orders', instId: 'ETHUSDT_SPBL' },
|
|
966
|
+
// data: [
|
|
967
|
+
// {
|
|
968
|
+
// instId: 'ETHUSDT_SPBL',
|
|
969
|
+
// ordId: '1228627925964996608',
|
|
970
|
+
// clOrdId: 'f0cccf74-c535-4523-a53d-dbe3b9958559',
|
|
971
|
+
// px: '2000',
|
|
972
|
+
// sz: '0.001',
|
|
973
|
+
// notional: '2',
|
|
974
|
+
// ordType: 'limit',
|
|
975
|
+
// force: 'normal',
|
|
976
|
+
// side: 'buy',
|
|
977
|
+
// accFillSz: '0',
|
|
978
|
+
// avgPx: '0',
|
|
979
|
+
// status: 'new',
|
|
980
|
+
// cTime: 1728653645030,
|
|
981
|
+
// uTime: 1728653645030,
|
|
982
|
+
// orderFee: [],
|
|
983
|
+
// eps: 'API'
|
|
984
|
+
// }
|
|
985
|
+
// ],
|
|
986
|
+
// ts: 1728653645046
|
|
987
|
+
// }
|
|
988
|
+
//
|
|
989
|
+
// swap
|
|
990
|
+
//
|
|
991
|
+
// {
|
|
992
|
+
// action: 'snapshot',
|
|
993
|
+
// arg: { instType: 'umcbl', channel: 'orders', instId: 'default' },
|
|
994
|
+
// data: [
|
|
995
|
+
// {
|
|
996
|
+
// accFillSz: '0',
|
|
997
|
+
// cTime: 1728653796976,
|
|
998
|
+
// clOrdId: '1228628563272753152',
|
|
999
|
+
// eps: 'API',
|
|
1000
|
+
// force: 'normal',
|
|
1001
|
+
// hM: 'single_hold',
|
|
1002
|
+
// instId: 'ETHUSDT_UMCBL',
|
|
1003
|
+
// lever: '5',
|
|
1004
|
+
// low: false,
|
|
1005
|
+
// notionalUsd: '20',
|
|
1006
|
+
// ordId: '1228628563188867072',
|
|
1007
|
+
// ordType: 'limit',
|
|
1008
|
+
// orderFee: [],
|
|
1009
|
+
// posSide: 'net',
|
|
1010
|
+
// px: '2000',
|
|
1011
|
+
// side: 'buy',
|
|
1012
|
+
// status: 'new',
|
|
1013
|
+
// sz: '0.01',
|
|
1014
|
+
// tS: 'buy_single',
|
|
1015
|
+
// tdMode: 'cross',
|
|
1016
|
+
// tgtCcy: 'USDT',
|
|
1017
|
+
// uTime: 1728653796976
|
|
1018
|
+
// }
|
|
1019
|
+
// ],
|
|
1020
|
+
// ts: 1728653797002
|
|
1021
|
+
// }
|
|
1022
|
+
//
|
|
1023
|
+
//
|
|
1024
|
+
const arg = this.safeDict(message, 'arg', {});
|
|
1025
|
+
const instType = this.safeString(arg, 'instType');
|
|
1026
|
+
const argInstId = this.safeString(arg, 'instId');
|
|
1027
|
+
let marketType = undefined;
|
|
1028
|
+
if (instType === 'spbl') {
|
|
1029
|
+
marketType = 'spot';
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
marketType = 'swap';
|
|
1033
|
+
}
|
|
1034
|
+
const data = this.safeList(message, 'data', []);
|
|
1035
|
+
if (this.orders === undefined) {
|
|
1036
|
+
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
|
|
1037
|
+
this.orders = new ArrayCacheBySymbolById(limit);
|
|
1038
|
+
}
|
|
1039
|
+
const hash = 'orders';
|
|
1040
|
+
const stored = this.orders;
|
|
1041
|
+
let symbol = undefined;
|
|
1042
|
+
for (let i = 0; i < data.length; i++) {
|
|
1043
|
+
const order = data[i];
|
|
1044
|
+
const marketId = this.safeString(order, 'instId', argInstId);
|
|
1045
|
+
const market = this.safeMarket(marketId, undefined, undefined, marketType);
|
|
1046
|
+
const parsed = this.parseWsOrder(order, market);
|
|
1047
|
+
stored.append(parsed);
|
|
1048
|
+
symbol = parsed['symbol'];
|
|
1049
|
+
const messageHash = 'orders:' + symbol;
|
|
1050
|
+
client.resolve(stored, messageHash);
|
|
1051
|
+
}
|
|
1052
|
+
client.resolve(stored, hash);
|
|
1053
|
+
}
|
|
1054
|
+
parseWsOrder(order, market = undefined) {
|
|
1055
|
+
//
|
|
1056
|
+
// spot
|
|
1057
|
+
// {
|
|
1058
|
+
// instId: 'ETHUSDT_SPBL',
|
|
1059
|
+
// ordId: '1228627925964996608',
|
|
1060
|
+
// clOrdId: 'f0cccf74-c535-4523-a53d-dbe3b9958559',
|
|
1061
|
+
// px: '2000',
|
|
1062
|
+
// sz: '0.001',
|
|
1063
|
+
// notional: '2',
|
|
1064
|
+
// ordType: 'limit',
|
|
1065
|
+
// force: 'normal',
|
|
1066
|
+
// side: 'buy',
|
|
1067
|
+
// accFillSz: '0',
|
|
1068
|
+
// avgPx: '0',
|
|
1069
|
+
// status: 'new',
|
|
1070
|
+
// cTime: 1728653645030,
|
|
1071
|
+
// uTime: 1728653645030,
|
|
1072
|
+
// orderFee: orderFee: [ { fee: '0', feeCcy: 'USDT' } ],
|
|
1073
|
+
// eps: 'API'
|
|
1074
|
+
// }
|
|
1075
|
+
//
|
|
1076
|
+
// swap
|
|
1077
|
+
// {
|
|
1078
|
+
// accFillSz: '0',
|
|
1079
|
+
// cTime: 1728653796976,
|
|
1080
|
+
// clOrdId: '1228628563272753152',
|
|
1081
|
+
// eps: 'API',
|
|
1082
|
+
// force: 'normal',
|
|
1083
|
+
// hM: 'single_hold',
|
|
1084
|
+
// instId: 'ETHUSDT_UMCBL',
|
|
1085
|
+
// lever: '5',
|
|
1086
|
+
// low: false,
|
|
1087
|
+
// notionalUsd: '20',
|
|
1088
|
+
// ordId: '1228628563188867072',
|
|
1089
|
+
// ordType: 'limit',
|
|
1090
|
+
// orderFee: [ { fee: '0', feeCcy: 'USDT' } ],
|
|
1091
|
+
// posSide: 'net',
|
|
1092
|
+
// px: '2000',
|
|
1093
|
+
// side: 'buy',
|
|
1094
|
+
// status: 'new',
|
|
1095
|
+
// sz: '0.01',
|
|
1096
|
+
// tS: 'buy_single',
|
|
1097
|
+
// tdMode: 'cross',
|
|
1098
|
+
// tgtCcy: 'USDT',
|
|
1099
|
+
// uTime: 1728653796976
|
|
1100
|
+
// }
|
|
1101
|
+
//
|
|
1102
|
+
const marketId = this.safeString(order, 'instId');
|
|
1103
|
+
const settleId = this.safeString(order, 'tgtCcy');
|
|
1104
|
+
market = this.safeMarketCustom(marketId, market, settleId);
|
|
1105
|
+
const timestamp = this.safeInteger(order, 'cTime');
|
|
1106
|
+
const symbol = market['symbol'];
|
|
1107
|
+
const rawStatus = this.safeString(order, 'status');
|
|
1108
|
+
const orderFee = this.safeList(order, 'orderFee', []);
|
|
1109
|
+
const fee = this.safeDict(orderFee, 0);
|
|
1110
|
+
const feeCost = Precise.stringMul(this.safeString(fee, 'fee'), '-1');
|
|
1111
|
+
const feeCurrency = this.safeString(fee, 'feeCcy');
|
|
1112
|
+
let price = this.omitZero(this.safeString(order, 'px'));
|
|
1113
|
+
const priceAvg = this.omitZero(this.safeString(order, 'avgPx'));
|
|
1114
|
+
if (price === undefined) {
|
|
1115
|
+
price = priceAvg;
|
|
1116
|
+
}
|
|
1117
|
+
const type = this.safeStringLower(order, 'ordType');
|
|
1118
|
+
return this.safeOrder({
|
|
1119
|
+
'id': this.safeString(order, 'ordId'),
|
|
1120
|
+
'clientOrderId': this.safeString(order, 'clOrdId'),
|
|
1121
|
+
'timestamp': timestamp,
|
|
1122
|
+
'datetime': this.iso8601(timestamp),
|
|
1123
|
+
'lastTradeTimestamp': undefined,
|
|
1124
|
+
'lastUpdateTimestamp': this.safeInteger(order, 'uTime'),
|
|
1125
|
+
'status': this.parseOrderStatus(rawStatus),
|
|
1126
|
+
'symbol': symbol,
|
|
1127
|
+
'type': type,
|
|
1128
|
+
'timeInForce': this.parseOrderTimeInForce(this.safeStringLower(order, 'force')),
|
|
1129
|
+
'side': this.safeStringLower(order, 'side'),
|
|
1130
|
+
'price': price,
|
|
1131
|
+
'average': this.safeString(order, 'avgPx'),
|
|
1132
|
+
'amount': this.safeString(order, 'sz'),
|
|
1133
|
+
'filled': this.safeString(order, 'accFillSz'),
|
|
1134
|
+
'remaining': undefined,
|
|
1135
|
+
'triggerPrice': undefined,
|
|
1136
|
+
'takeProfitPrice': undefined,
|
|
1137
|
+
'stopLossPrice': undefined,
|
|
1138
|
+
'cost': this.safeString(order, 'notional'),
|
|
1139
|
+
'trades': undefined,
|
|
1140
|
+
'fee': {
|
|
1141
|
+
'currency': feeCurrency,
|
|
1142
|
+
'cost': feeCost,
|
|
1143
|
+
},
|
|
1144
|
+
'reduceOnly': this.safeBool(order, 'low'),
|
|
1145
|
+
'postOnly': undefined,
|
|
1146
|
+
'info': order,
|
|
1147
|
+
}, market);
|
|
1148
|
+
}
|
|
1149
|
+
async watchPositions(symbols = undefined, since = undefined, limit = undefined, params = {}) {
|
|
1150
|
+
/**
|
|
1151
|
+
* @method
|
|
1152
|
+
* @name coincatch#watchPositions
|
|
1153
|
+
* @description watch all open positions
|
|
1154
|
+
* @see https://coincatch.github.io/github.io/en/mix/#positions-channel
|
|
1155
|
+
* @param {string[]|undefined} symbols list of unified market symbols
|
|
1156
|
+
* @param {object} params extra parameters specific to the exchange API endpoint
|
|
1157
|
+
* @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
|
|
1158
|
+
*/
|
|
1159
|
+
await this.loadMarkets();
|
|
1160
|
+
symbols = this.marketSymbols(symbols, 'swap');
|
|
1161
|
+
const messageHashes = [];
|
|
1162
|
+
const hash = 'positions';
|
|
1163
|
+
let instTypes = [];
|
|
1164
|
+
if (symbols !== undefined) {
|
|
1165
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
1166
|
+
const symbol = symbols[i];
|
|
1167
|
+
const market = this.market(symbol);
|
|
1168
|
+
const instType = this.getPrivateInstType(market);
|
|
1169
|
+
if (!this.inArray(instType, instTypes)) {
|
|
1170
|
+
instTypes.push(instType);
|
|
1171
|
+
}
|
|
1172
|
+
messageHashes.push(hash + '::' + symbol);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
instTypes = ['umcbl', 'dmcbl'];
|
|
1177
|
+
messageHashes.push(hash);
|
|
1178
|
+
}
|
|
1179
|
+
const args = [];
|
|
1180
|
+
const subscribeHashes = [];
|
|
1181
|
+
for (let i = 0; i < instTypes.length; i++) {
|
|
1182
|
+
const instType = instTypes[i];
|
|
1183
|
+
const arg = {
|
|
1184
|
+
'instType': instType,
|
|
1185
|
+
'channel': hash,
|
|
1186
|
+
'instId': 'default',
|
|
1187
|
+
};
|
|
1188
|
+
subscribeHashes.push(hash + '::' + instType);
|
|
1189
|
+
args.push(arg);
|
|
1190
|
+
}
|
|
1191
|
+
const newPositions = await this.watchPrivateMultiple(messageHashes, subscribeHashes, args, params);
|
|
1192
|
+
if (this.newUpdates) {
|
|
1193
|
+
return newPositions;
|
|
1194
|
+
}
|
|
1195
|
+
return this.filterBySymbolsSinceLimit(newPositions, symbols, since, limit, true);
|
|
1196
|
+
}
|
|
1197
|
+
getPrivateInstType(market) {
|
|
1198
|
+
const marketId = market['id'];
|
|
1199
|
+
if (marketId.indexOf('_DMCBL') >= 0) {
|
|
1200
|
+
return 'dmcbl';
|
|
1201
|
+
}
|
|
1202
|
+
return 'umcbl';
|
|
1203
|
+
}
|
|
1204
|
+
handlePositions(client, message) {
|
|
1205
|
+
//
|
|
1206
|
+
// {
|
|
1207
|
+
// action: 'snapshot',
|
|
1208
|
+
// arg: { instType: 'umcbl', channel: 'positions', instId: 'default' },
|
|
1209
|
+
// data: [
|
|
1210
|
+
// {
|
|
1211
|
+
// posId: '1221355728745619456',
|
|
1212
|
+
// instId: 'ETHUSDT_UMCBL',
|
|
1213
|
+
// instName: 'ETHUSDT',
|
|
1214
|
+
// marginCoin: 'USDT',
|
|
1215
|
+
// margin: '5.27182',
|
|
1216
|
+
// marginMode: 'crossed',
|
|
1217
|
+
// holdSide: 'long',
|
|
1218
|
+
// holdMode: 'single_hold',
|
|
1219
|
+
// total: '0.01',
|
|
1220
|
+
// available: '0.01',
|
|
1221
|
+
// locked: '0',
|
|
1222
|
+
// averageOpenPrice: '2635.91',
|
|
1223
|
+
// leverage: 5,
|
|
1224
|
+
// achievedProfits: '0',
|
|
1225
|
+
// upl: '-0.0267',
|
|
1226
|
+
// uplRate: '-0.005064664576',
|
|
1227
|
+
// liqPx: '-3110.66866033',
|
|
1228
|
+
// keepMarginRate: '0.0033',
|
|
1229
|
+
// marginRate: '0.002460827254',
|
|
1230
|
+
// cTime: '1726919818102',
|
|
1231
|
+
// uTime: '1728919604312',
|
|
1232
|
+
// markPrice: '2633.24',
|
|
1233
|
+
// autoMargin: 'off'
|
|
1234
|
+
// }
|
|
1235
|
+
// ],
|
|
1236
|
+
// ts: 1728919604329
|
|
1237
|
+
// }
|
|
1238
|
+
//
|
|
1239
|
+
if (this.positions === undefined) {
|
|
1240
|
+
this.positions = new ArrayCacheBySymbolBySide();
|
|
1241
|
+
}
|
|
1242
|
+
const cache = this.positions;
|
|
1243
|
+
const rawPositions = this.safeValue(message, 'data', []);
|
|
1244
|
+
const dataLength = rawPositions.length;
|
|
1245
|
+
if (dataLength === 0) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const newPositions = [];
|
|
1249
|
+
const symbols = [];
|
|
1250
|
+
for (let i = 0; i < rawPositions.length; i++) {
|
|
1251
|
+
const rawPosition = rawPositions[i];
|
|
1252
|
+
const position = this.parseWsPosition(rawPosition);
|
|
1253
|
+
symbols.push(position['symbol']);
|
|
1254
|
+
newPositions.push(position);
|
|
1255
|
+
cache.append(position);
|
|
1256
|
+
}
|
|
1257
|
+
const hash = 'positions';
|
|
1258
|
+
const messageHashes = this.findMessageHashes(client, hash);
|
|
1259
|
+
for (let i = 0; i < messageHashes.length; i++) {
|
|
1260
|
+
const messageHash = messageHashes[i];
|
|
1261
|
+
const parts = messageHash.split('::');
|
|
1262
|
+
const symbol = parts[1];
|
|
1263
|
+
if (this.inArray(symbol, symbols)) {
|
|
1264
|
+
const positionsForSymbol = [];
|
|
1265
|
+
for (let j = 0; j < newPositions.length; j++) {
|
|
1266
|
+
const position = newPositions[j];
|
|
1267
|
+
if (position['symbol'] === symbol) {
|
|
1268
|
+
positionsForSymbol.push(position);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
client.resolve(positionsForSymbol, messageHash);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
client.resolve(newPositions, hash);
|
|
1275
|
+
}
|
|
1276
|
+
parseWsPosition(position, market = undefined) {
|
|
1277
|
+
//
|
|
1278
|
+
// {
|
|
1279
|
+
// posId: '1221355728745619456',
|
|
1280
|
+
// instId: 'ETHUSDT_UMCBL',
|
|
1281
|
+
// instName: 'ETHUSDT',
|
|
1282
|
+
// marginCoin: 'USDT',
|
|
1283
|
+
// margin: '5.27182',
|
|
1284
|
+
// marginMode: 'crossed',
|
|
1285
|
+
// holdSide: 'long',
|
|
1286
|
+
// holdMode: 'single_hold',
|
|
1287
|
+
// total: '0.01',
|
|
1288
|
+
// available: '0.01',
|
|
1289
|
+
// locked: '0',
|
|
1290
|
+
// averageOpenPrice: '2635.91',
|
|
1291
|
+
// leverage: 5,
|
|
1292
|
+
// achievedProfits: '0',
|
|
1293
|
+
// upl: '-0.0267',
|
|
1294
|
+
// uplRate: '-0.005064664576',
|
|
1295
|
+
// liqPx: '-3110.66866033',
|
|
1296
|
+
// keepMarginRate: '0.0033',
|
|
1297
|
+
// marginRate: '0.002460827254',
|
|
1298
|
+
// cTime: '1726919818102',
|
|
1299
|
+
// uTime: '1728919604312',
|
|
1300
|
+
// markPrice: '2633.24',
|
|
1301
|
+
// autoMargin: 'off'
|
|
1302
|
+
// }
|
|
1303
|
+
//
|
|
1304
|
+
const marketId = this.safeString(position, 'symbol');
|
|
1305
|
+
const settleId = this.safeString(position, 'marginCoin');
|
|
1306
|
+
market = this.safeMarketCustom(marketId, market, settleId);
|
|
1307
|
+
const timestamp = this.safeInteger(position, 'cTime');
|
|
1308
|
+
const marginModeId = this.safeString(position, 'marginMode');
|
|
1309
|
+
const marginMode = this.getSupportedMapping(marginModeId, {
|
|
1310
|
+
'crossed': 'cross',
|
|
1311
|
+
'isolated': 'isolated',
|
|
1312
|
+
});
|
|
1313
|
+
let isHedged = undefined;
|
|
1314
|
+
const holdMode = this.safeString(position, 'holdMode');
|
|
1315
|
+
if (holdMode === 'double_hold') {
|
|
1316
|
+
isHedged = true;
|
|
1317
|
+
}
|
|
1318
|
+
else if (holdMode === 'single_hold') {
|
|
1319
|
+
isHedged = false;
|
|
1320
|
+
}
|
|
1321
|
+
const percentageDecimal = this.safeString(position, 'uplRate');
|
|
1322
|
+
const percentage = Precise.stringMul(percentageDecimal, '100');
|
|
1323
|
+
const margin = this.safeNumber(position, 'margin');
|
|
1324
|
+
return this.safePosition({
|
|
1325
|
+
'symbol': market['symbol'],
|
|
1326
|
+
'id': undefined,
|
|
1327
|
+
'timestamp': timestamp,
|
|
1328
|
+
'datetime': this.iso8601(timestamp),
|
|
1329
|
+
'contracts': this.safeNumber(position, 'total'),
|
|
1330
|
+
'contractSize': undefined,
|
|
1331
|
+
'side': this.safeStringLower(position, 'holdSide'),
|
|
1332
|
+
'notional': margin,
|
|
1333
|
+
'leverage': this.safeInteger(position, 'leverage'),
|
|
1334
|
+
'unrealizedPnl': this.safeNumber(position, 'upl'),
|
|
1335
|
+
'realizedPnl': this.safeNumber(position, 'achievedProfits'),
|
|
1336
|
+
'collateral': undefined,
|
|
1337
|
+
'entryPrice': this.safeNumber(position, 'averageOpenPrice'),
|
|
1338
|
+
'markPrice': this.safeNumber(position, 'markPrice'),
|
|
1339
|
+
'liquidationPrice': this.safeNumber(position, 'liqPx'),
|
|
1340
|
+
'marginMode': marginMode,
|
|
1341
|
+
'hedged': isHedged,
|
|
1342
|
+
'maintenanceMargin': undefined,
|
|
1343
|
+
'maintenanceMarginPercentage': this.safeNumber(position, 'keepMarginRate'),
|
|
1344
|
+
'initialMargin': margin,
|
|
1345
|
+
'initialMarginPercentage': undefined,
|
|
1346
|
+
'marginRatio': this.safeNumber(position, 'marginRate'),
|
|
1347
|
+
'lastUpdateTimestamp': this.safeInteger(position, 'uTime'),
|
|
1348
|
+
'lastPrice': undefined,
|
|
1349
|
+
'stopLossPrice': undefined,
|
|
1350
|
+
'takeProfitPrice': undefined,
|
|
1351
|
+
'percentage': percentage,
|
|
1352
|
+
'info': position,
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
handleErrorMessage(client, message) {
|
|
1356
|
+
//
|
|
1357
|
+
// { event: "error", code: 30001, msg: "Channel does not exist" }
|
|
1358
|
+
//
|
|
1359
|
+
const event = this.safeString(message, 'event');
|
|
1360
|
+
try {
|
|
1361
|
+
if (event === 'error') {
|
|
1362
|
+
const code = this.safeString(message, 'code');
|
|
1363
|
+
const feedback = this.id + ' ' + this.json(message);
|
|
1364
|
+
this.throwExactlyMatchedException(this.exceptions['ws']['exact'], code, feedback);
|
|
1365
|
+
const msg = this.safeString(message, 'msg', '');
|
|
1366
|
+
this.throwBroadlyMatchedException(this.exceptions['ws']['broad'], msg, feedback);
|
|
1367
|
+
throw new ExchangeError(feedback);
|
|
1368
|
+
}
|
|
1369
|
+
return false;
|
|
1370
|
+
}
|
|
1371
|
+
catch (e) {
|
|
1372
|
+
if (e instanceof AuthenticationError) {
|
|
1373
|
+
const messageHash = 'authenticated';
|
|
1374
|
+
client.reject(e, messageHash);
|
|
1375
|
+
if (messageHash in client.subscriptions) {
|
|
1376
|
+
delete client.subscriptions[messageHash];
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
else {
|
|
1380
|
+
client.reject(e);
|
|
1381
|
+
}
|
|
1382
|
+
return true;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
handleMessage(client, message) {
|
|
1386
|
+
// todo handle with subscribe and unsubscribe
|
|
1387
|
+
if (this.handleErrorMessage(client, message)) {
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const content = this.safeString(message, 'message');
|
|
1391
|
+
if (content === 'pong') {
|
|
1392
|
+
this.handlePong(client, message);
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
if (message === 'pong') {
|
|
1396
|
+
this.handlePong(client, message);
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
const event = this.safeString(message, 'event');
|
|
1400
|
+
if (event === 'login') {
|
|
1401
|
+
this.handleAuthenticate(client, message);
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
if (event === 'subscribe') {
|
|
1405
|
+
this.handleSubscriptionStatus(client, message);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (event === 'unsubscribe') {
|
|
1409
|
+
this.handleUnSubscriptionStatus(client, message);
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
const data = this.safeDict(message, 'arg', {});
|
|
1413
|
+
const channel = this.safeString(data, 'channel');
|
|
1414
|
+
if (channel === 'ticker') {
|
|
1415
|
+
this.handleTicker(client, message);
|
|
1416
|
+
}
|
|
1417
|
+
if (channel.indexOf('candle') >= 0) {
|
|
1418
|
+
this.handleOHLCV(client, message);
|
|
1419
|
+
}
|
|
1420
|
+
if (channel.indexOf('books') >= 0) {
|
|
1421
|
+
this.handleOrderBook(client, message);
|
|
1422
|
+
}
|
|
1423
|
+
if (channel === 'trade') {
|
|
1424
|
+
this.handleTrades(client, message);
|
|
1425
|
+
}
|
|
1426
|
+
if (channel === 'account') {
|
|
1427
|
+
this.handleBalance(client, message);
|
|
1428
|
+
}
|
|
1429
|
+
if ((channel === 'orders') || (channel === 'ordersAlgo')) {
|
|
1430
|
+
this.handleOrder(client, message);
|
|
1431
|
+
}
|
|
1432
|
+
if (channel === 'positions') {
|
|
1433
|
+
this.handlePositions(client, message);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
ping(client) {
|
|
1437
|
+
return 'ping';
|
|
1438
|
+
}
|
|
1439
|
+
handlePong(client, message) {
|
|
1440
|
+
client.lastPong = this.milliseconds();
|
|
1441
|
+
return message;
|
|
1442
|
+
}
|
|
1443
|
+
handleSubscriptionStatus(client, message) {
|
|
1444
|
+
return message;
|
|
1445
|
+
}
|
|
1446
|
+
handleUnSubscriptionStatus(client, message) {
|
|
1447
|
+
let argsList = this.safeList(message, 'args');
|
|
1448
|
+
if (argsList === undefined) {
|
|
1449
|
+
argsList = [this.safeDict(message, 'arg', {})];
|
|
1450
|
+
}
|
|
1451
|
+
for (let i = 0; i < argsList.length; i++) {
|
|
1452
|
+
const arg = argsList[i];
|
|
1453
|
+
const channel = this.safeString(arg, 'channel');
|
|
1454
|
+
if (channel === 'books') {
|
|
1455
|
+
this.handleOrderBookUnSubscription(client, message);
|
|
1456
|
+
}
|
|
1457
|
+
else if (channel === 'trade') {
|
|
1458
|
+
this.handleTradesUnSubscription(client, message);
|
|
1459
|
+
}
|
|
1460
|
+
else if (channel === 'ticker') {
|
|
1461
|
+
this.handleTickerUnSubscription(client, message);
|
|
1462
|
+
}
|
|
1463
|
+
else if (channel.startsWith('candle')) {
|
|
1464
|
+
this.handleOHLCVUnSubscription(client, message);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return message;
|
|
1468
|
+
}
|
|
1469
|
+
handleOrderBookUnSubscription(client, message) {
|
|
1470
|
+
const arg = this.safeDict(message, 'arg', {});
|
|
1471
|
+
const instType = this.safeStringLower(arg, 'instType');
|
|
1472
|
+
const type = (instType === 'sp') ? 'spot' : 'swap';
|
|
1473
|
+
const instId = this.safeString(arg, 'instId');
|
|
1474
|
+
const market = this.safeMarket(instId, undefined, undefined, type);
|
|
1475
|
+
const symbol = market['symbol'];
|
|
1476
|
+
const messageHash = 'unsubscribe:orderbook:' + market['symbol'];
|
|
1477
|
+
const subMessageHash = 'orderbook:' + symbol;
|
|
1478
|
+
if (symbol in this.orderbooks) {
|
|
1479
|
+
delete this.orderbooks[symbol];
|
|
1480
|
+
}
|
|
1481
|
+
if (subMessageHash in client.subscriptions) {
|
|
1482
|
+
delete client.subscriptions[subMessageHash];
|
|
1483
|
+
}
|
|
1484
|
+
if (messageHash in client.subscriptions) {
|
|
1485
|
+
delete client.subscriptions[messageHash];
|
|
1486
|
+
}
|
|
1487
|
+
const error = new UnsubscribeError(this.id + 'orderbook ' + symbol);
|
|
1488
|
+
client.reject(error, subMessageHash);
|
|
1489
|
+
client.resolve(true, messageHash);
|
|
1490
|
+
}
|
|
1491
|
+
handleTradesUnSubscription(client, message) {
|
|
1492
|
+
const arg = this.safeDict(message, 'arg', {});
|
|
1493
|
+
const instType = this.safeStringLower(arg, 'instType');
|
|
1494
|
+
const type = (instType === 'sp') ? 'spot' : 'swap';
|
|
1495
|
+
const instId = this.safeString(arg, 'instId');
|
|
1496
|
+
const market = this.safeMarket(instId, undefined, undefined, type);
|
|
1497
|
+
const symbol = market['symbol'];
|
|
1498
|
+
const messageHash = 'unsubscribe:trade:' + market['symbol'];
|
|
1499
|
+
const subMessageHash = 'trade:' + symbol;
|
|
1500
|
+
if (symbol in this.trades) {
|
|
1501
|
+
delete this.trades[symbol];
|
|
1502
|
+
}
|
|
1503
|
+
if (subMessageHash in client.subscriptions) {
|
|
1504
|
+
delete client.subscriptions[subMessageHash];
|
|
1505
|
+
}
|
|
1506
|
+
if (messageHash in client.subscriptions) {
|
|
1507
|
+
delete client.subscriptions[messageHash];
|
|
1508
|
+
}
|
|
1509
|
+
const error = new UnsubscribeError(this.id + 'trades ' + symbol);
|
|
1510
|
+
client.reject(error, subMessageHash);
|
|
1511
|
+
client.resolve(true, messageHash);
|
|
1512
|
+
}
|
|
1513
|
+
handleTickerUnSubscription(client, message) {
|
|
1514
|
+
const arg = this.safeDict(message, 'arg', {});
|
|
1515
|
+
const instType = this.safeStringLower(arg, 'instType');
|
|
1516
|
+
const type = (instType === 'sp') ? 'spot' : 'swap';
|
|
1517
|
+
const instId = this.safeString(arg, 'instId');
|
|
1518
|
+
const market = this.safeMarket(instId, undefined, undefined, type);
|
|
1519
|
+
const symbol = market['symbol'];
|
|
1520
|
+
const messageHash = 'unsubscribe:ticker:' + market['symbol'];
|
|
1521
|
+
const subMessageHash = 'ticker:' + symbol;
|
|
1522
|
+
if (symbol in this.tickers) {
|
|
1523
|
+
delete this.tickers[symbol];
|
|
1524
|
+
}
|
|
1525
|
+
if (subMessageHash in client.subscriptions) {
|
|
1526
|
+
delete client.subscriptions[subMessageHash];
|
|
1527
|
+
}
|
|
1528
|
+
if (messageHash in client.subscriptions) {
|
|
1529
|
+
delete client.subscriptions[messageHash];
|
|
1530
|
+
}
|
|
1531
|
+
const error = new UnsubscribeError(this.id + 'ticker ' + symbol);
|
|
1532
|
+
client.reject(error, subMessageHash);
|
|
1533
|
+
client.resolve(true, messageHash);
|
|
1534
|
+
}
|
|
1535
|
+
handleOHLCVUnSubscription(client, message) {
|
|
1536
|
+
const arg = this.safeDict(message, 'arg', {});
|
|
1537
|
+
const instType = this.safeStringLower(arg, 'instType');
|
|
1538
|
+
const type = (instType === 'sp') ? 'spot' : 'swap';
|
|
1539
|
+
const instId = this.safeString(arg, 'instId');
|
|
1540
|
+
const channel = this.safeString(arg, 'channel');
|
|
1541
|
+
const interval = channel.replace('candle', '');
|
|
1542
|
+
const timeframes = this.safeDict(this.options, 'timeframesForWs');
|
|
1543
|
+
const timeframe = this.findTimeframe(interval, timeframes);
|
|
1544
|
+
const market = this.safeMarket(instId, undefined, undefined, type);
|
|
1545
|
+
const symbol = market['symbol'];
|
|
1546
|
+
const messageHash = 'unsubscribe:ohlcv:' + timeframe + ':' + market['symbol'];
|
|
1547
|
+
const subMessageHash = 'ohlcv:' + symbol + ':' + timeframe;
|
|
1548
|
+
if (symbol in this.ohlcvs) {
|
|
1549
|
+
if (timeframe in this.ohlcvs[symbol]) {
|
|
1550
|
+
delete this.ohlcvs[symbol][timeframe];
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
this.cleanUnsubscription(client, subMessageHash, messageHash);
|
|
1554
|
+
}
|
|
1555
|
+
}
|