ccxt 4.5.22 → 4.5.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 +6 -5
- package/dist/ccxt.browser.min.js +2 -2
- package/dist/cjs/ccxt.js +6 -1
- package/dist/cjs/src/abstract/bullish.js +11 -0
- package/dist/cjs/src/base/Exchange.js +3 -2
- package/dist/cjs/src/base/ws/WsClient.js +15 -0
- package/dist/cjs/src/binance.js +159 -36
- package/dist/cjs/src/bingx.js +2 -1
- package/dist/cjs/src/bitmart.js +1 -0
- package/dist/cjs/src/bullish.js +2919 -0
- package/dist/cjs/src/bybit.js +34 -37
- package/dist/cjs/src/gate.js +2 -2
- package/dist/cjs/src/htx.js +4 -1
- package/dist/cjs/src/hyperliquid.js +115 -12
- package/dist/cjs/src/kucoin.js +22 -3
- package/dist/cjs/src/mexc.js +7 -0
- package/dist/cjs/src/okx.js +117 -63
- package/dist/cjs/src/paradex.js +78 -3
- package/dist/cjs/src/pro/binance.js +131 -29
- package/dist/cjs/src/pro/bullish.js +781 -0
- package/dist/cjs/src/pro/coinbase.js +2 -2
- package/dist/cjs/src/pro/hyperliquid.js +75 -15
- package/dist/cjs/src/pro/upbit.js +28 -82
- package/js/ccxt.d.ts +8 -2
- package/js/ccxt.js +6 -2
- package/js/src/abstract/binance.d.ts +1 -0
- package/js/src/abstract/binancecoinm.d.ts +1 -0
- package/js/src/abstract/binanceus.d.ts +1 -0
- package/js/src/abstract/binanceusdm.d.ts +1 -0
- package/js/src/abstract/bingx.d.ts +1 -0
- package/js/src/abstract/bullish.d.ts +65 -0
- package/js/src/abstract/bullish.js +5 -0
- package/js/src/abstract/kucoin.d.ts +15 -0
- package/js/src/abstract/kucoinfutures.d.ts +15 -0
- package/js/src/abstract/mexc.d.ts +7 -0
- package/js/src/abstract/myokx.d.ts +90 -39
- package/js/src/abstract/okx.d.ts +90 -39
- package/js/src/abstract/okxus.d.ts +90 -39
- package/js/src/base/Exchange.d.ts +1 -1
- package/js/src/base/Exchange.js +3 -2
- package/js/src/base/ws/Client.d.ts +1 -0
- package/js/src/base/ws/WsClient.js +15 -0
- package/js/src/binance.d.ts +14 -5
- package/js/src/binance.js +159 -36
- package/js/src/bingx.js +2 -1
- package/js/src/bitmart.js +1 -0
- package/js/src/bullish.d.ts +446 -0
- package/js/src/bullish.js +2912 -0
- package/js/src/bybit.js +34 -37
- package/js/src/gate.js +2 -2
- package/js/src/htx.js +4 -1
- package/js/src/hyperliquid.d.ts +24 -0
- package/js/src/hyperliquid.js +115 -12
- package/js/src/kucoin.js +22 -3
- package/js/src/mexc.js +7 -0
- package/js/src/okx.js +117 -63
- package/js/src/paradex.d.ts +15 -1
- package/js/src/paradex.js +78 -3
- package/js/src/pro/binance.d.ts +7 -0
- package/js/src/pro/binance.js +131 -29
- package/js/src/pro/bullish.d.ts +108 -0
- package/js/src/pro/bullish.js +774 -0
- package/js/src/pro/coinbase.js +2 -2
- package/js/src/pro/hyperliquid.d.ts +13 -1
- package/js/src/pro/hyperliquid.js +75 -15
- package/js/src/pro/upbit.d.ts +0 -1
- package/js/src/pro/upbit.js +28 -82
- package/package.json +2 -2
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var bullish$1 = require('../bullish.js');
|
|
6
|
+
var Cache = require('../base/ws/Cache.js');
|
|
7
|
+
var errors = require('../base/errors.js');
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
class bullish extends bullish$1["default"] {
|
|
12
|
+
describe() {
|
|
13
|
+
return this.deepExtend(super.describe(), {
|
|
14
|
+
'has': {
|
|
15
|
+
'ws': true,
|
|
16
|
+
'watchTicker': true,
|
|
17
|
+
'watchTickers': false,
|
|
18
|
+
'watchOrderBook': true,
|
|
19
|
+
'watchOrders': true,
|
|
20
|
+
'watchTrades': true,
|
|
21
|
+
'watchPositions': true,
|
|
22
|
+
'watchMyTrades': true,
|
|
23
|
+
'watchBalance': true,
|
|
24
|
+
'watchOHLCV': false,
|
|
25
|
+
},
|
|
26
|
+
'urls': {
|
|
27
|
+
'api': {
|
|
28
|
+
'ws': {
|
|
29
|
+
'public': 'wss://api.exchange.bullish.com',
|
|
30
|
+
'private': 'wss://api.exchange.bullish.com/trading-api/v1/private-data',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
'test': {
|
|
34
|
+
'ws': {
|
|
35
|
+
'public': 'wss://api.simnext.bullish-test.com',
|
|
36
|
+
'private': 'wss://api.simnext.bullish-test.com/trading-api/v1/private-data',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
'options': {
|
|
41
|
+
'ws': {
|
|
42
|
+
'cookies': {},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
'streaming': {
|
|
46
|
+
'ping': this.ping,
|
|
47
|
+
'keepAlive': 99000, // disconnect after 100 seconds of inactivity
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
requestId() {
|
|
52
|
+
const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1);
|
|
53
|
+
this.options['requestId'] = requestId;
|
|
54
|
+
return requestId;
|
|
55
|
+
}
|
|
56
|
+
ping(client) {
|
|
57
|
+
// bullish does not support built-in ws protocol-level ping-pong
|
|
58
|
+
// https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--keep-websocket-open
|
|
59
|
+
const id = this.requestId().toString();
|
|
60
|
+
return {
|
|
61
|
+
'jsonrpc': '2.0',
|
|
62
|
+
'type': 'command',
|
|
63
|
+
'method': 'keepalivePing',
|
|
64
|
+
'params': {},
|
|
65
|
+
'id': id,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
handlePong(client, message) {
|
|
69
|
+
//
|
|
70
|
+
// {
|
|
71
|
+
// "id": "7",
|
|
72
|
+
// "jsonrpc": "2.0",
|
|
73
|
+
// "result": {
|
|
74
|
+
// "responseCodeName": "OK",
|
|
75
|
+
// "responseCode": "200",
|
|
76
|
+
// "message": "Keep alive pong"
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
79
|
+
//
|
|
80
|
+
client.lastPong = this.milliseconds();
|
|
81
|
+
return message; // current line is for transpilation compatibility
|
|
82
|
+
}
|
|
83
|
+
async watchPublic(url, messageHash, request = {}, params = {}) {
|
|
84
|
+
const id = this.requestId().toString();
|
|
85
|
+
const message = {
|
|
86
|
+
'jsonrpc': '2.0',
|
|
87
|
+
'type': 'command',
|
|
88
|
+
'method': 'subscribe',
|
|
89
|
+
'params': request,
|
|
90
|
+
'id': id,
|
|
91
|
+
};
|
|
92
|
+
const fullUrl = this.urls['api']['ws']['public'] + url;
|
|
93
|
+
return await this.watch(fullUrl, messageHash, this.deepExtend(message, params), messageHash);
|
|
94
|
+
}
|
|
95
|
+
async watchPrivate(messageHash, subscribeHash, request = {}, params = {}) {
|
|
96
|
+
const url = this.urls['api']['ws']['private'];
|
|
97
|
+
const token = await this.handleToken();
|
|
98
|
+
const cookies = {
|
|
99
|
+
'JWT_COOKIE': token,
|
|
100
|
+
};
|
|
101
|
+
this.options['ws']['cookies'] = cookies;
|
|
102
|
+
const id = this.requestId().toString();
|
|
103
|
+
const message = {
|
|
104
|
+
'jsonrpc': '2.0',
|
|
105
|
+
'type': 'command',
|
|
106
|
+
'method': 'subscribe',
|
|
107
|
+
'params': request,
|
|
108
|
+
'id': id,
|
|
109
|
+
};
|
|
110
|
+
const result = await this.watch(url, messageHash, this.deepExtend(message, params), subscribeHash);
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* @method
|
|
115
|
+
* @name bullish#watchTrades
|
|
116
|
+
* @description get the list of most recent trades for a particular symbol
|
|
117
|
+
* @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--unified-anonymous-trades-websocket-unauthenticated
|
|
118
|
+
* @param {string} symbol unified symbol of the market to fetch trades for
|
|
119
|
+
* @param {int} [since] timestamp in ms of the earliest trade to fetch
|
|
120
|
+
* @param {int} [limit] the maximum amount of trades to fetch
|
|
121
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
122
|
+
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
|
|
123
|
+
*/
|
|
124
|
+
async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
|
|
125
|
+
await this.loadMarkets();
|
|
126
|
+
const market = this.market(symbol);
|
|
127
|
+
const messageHash = 'trades::' + market['symbol'];
|
|
128
|
+
const url = '/trading-api/v1/market-data/trades';
|
|
129
|
+
const request = {
|
|
130
|
+
'topic': 'anonymousTrades',
|
|
131
|
+
'symbol': market['id'],
|
|
132
|
+
};
|
|
133
|
+
const trades = await this.watchPublic(url, messageHash, request, params);
|
|
134
|
+
if (this.newUpdates) {
|
|
135
|
+
limit = trades.getLimit(symbol, limit);
|
|
136
|
+
}
|
|
137
|
+
return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
|
|
138
|
+
}
|
|
139
|
+
handleTrades(client, message) {
|
|
140
|
+
//
|
|
141
|
+
// {
|
|
142
|
+
// "type": "snapshot",
|
|
143
|
+
// "dataType": "V1TAAnonymousTradeUpdate",
|
|
144
|
+
// "data": {
|
|
145
|
+
// "trades": [
|
|
146
|
+
// {
|
|
147
|
+
// "tradeId": "100086000000609304",
|
|
148
|
+
// "isTaker": true,
|
|
149
|
+
// "price": "104889.2063",
|
|
150
|
+
// "createdAtTimestamp": "1749124509118",
|
|
151
|
+
// "quantity": "0.01000000",
|
|
152
|
+
// "publishedAtTimestamp": "1749124531466",
|
|
153
|
+
// "side": "BUY",
|
|
154
|
+
// "createdAtDatetime": "2025-06-05T11:55:09.118Z",
|
|
155
|
+
// "symbol": "BTCUSDC"
|
|
156
|
+
// }
|
|
157
|
+
// ],
|
|
158
|
+
// "createdAtTimestamp": "1749124509118",
|
|
159
|
+
// "publishedAtTimestamp": "1749124531466",
|
|
160
|
+
// "symbol": "BTCUSDC"
|
|
161
|
+
// }
|
|
162
|
+
// }
|
|
163
|
+
//
|
|
164
|
+
const data = this.safeDict(message, 'data', {});
|
|
165
|
+
const marketId = this.safeString(data, 'symbol');
|
|
166
|
+
const symbol = this.safeSymbol(marketId);
|
|
167
|
+
const market = this.market(symbol);
|
|
168
|
+
const rawTrades = this.safeList(data, 'trades', []);
|
|
169
|
+
const trades = this.parseTrades(rawTrades, market);
|
|
170
|
+
if (!(symbol in this.trades)) {
|
|
171
|
+
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
|
|
172
|
+
const tradesArrayCache = new Cache.ArrayCache(limit);
|
|
173
|
+
this.trades[symbol] = tradesArrayCache;
|
|
174
|
+
}
|
|
175
|
+
const tradesArray = this.trades[symbol];
|
|
176
|
+
for (let i = 0; i < trades.length; i++) {
|
|
177
|
+
tradesArray.append(trades[i]);
|
|
178
|
+
}
|
|
179
|
+
this.trades[symbol] = tradesArray;
|
|
180
|
+
const messageHash = 'trades::' + market['symbol'];
|
|
181
|
+
client.resolve(tradesArray, messageHash);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* @method
|
|
185
|
+
* @name bullish#watchTicker
|
|
186
|
+
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
187
|
+
* @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--anonymous-market-data-price-tick-unauthenticated
|
|
188
|
+
* @param {string} symbol unified symbol of the market to fetch the ticker for
|
|
189
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
190
|
+
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
|
|
191
|
+
*/
|
|
192
|
+
async watchTicker(symbol, params = {}) {
|
|
193
|
+
await this.loadMarkets();
|
|
194
|
+
const market = this.market(symbol);
|
|
195
|
+
symbol = market['symbol'];
|
|
196
|
+
const url = this.urls['api']['ws']['public'] + '/trading-api/v1/market-data/tick/' + market['id'];
|
|
197
|
+
const messageHash = 'ticker::' + symbol;
|
|
198
|
+
return await this.watch(url, messageHash, params, messageHash); // no need to send a subscribe message, the server sends a ticker update on connect
|
|
199
|
+
}
|
|
200
|
+
handleTicker(client, message) {
|
|
201
|
+
//
|
|
202
|
+
// {
|
|
203
|
+
// "type": "update",
|
|
204
|
+
// "dataType": "V1TATickerResponse",
|
|
205
|
+
// "data": {
|
|
206
|
+
// "askVolume": "0.00100822",
|
|
207
|
+
// "average": "104423.1806",
|
|
208
|
+
// "baseVolume": "472.83799258",
|
|
209
|
+
// "bestAsk": "104324.6000",
|
|
210
|
+
// "bestBid": "104324.5000",
|
|
211
|
+
// "bidVolume": "0.00020146",
|
|
212
|
+
// "change": "-198.4864",
|
|
213
|
+
// "close": "104323.9374",
|
|
214
|
+
// "createdAtTimestamp": "1749132838951",
|
|
215
|
+
// "publishedAtTimestamp": "1749132838955",
|
|
216
|
+
// "high": "105966.6577",
|
|
217
|
+
// "last": "104323.9374",
|
|
218
|
+
// "lastTradeDatetime": "2025-06-05T14:13:56.111Z",
|
|
219
|
+
// "lastTradeSize": "0.02396100",
|
|
220
|
+
// "low": "104246.6662",
|
|
221
|
+
// "open": "104522.4238",
|
|
222
|
+
// "percentage": "-0.19",
|
|
223
|
+
// "quoteVolume": "49662592.6712",
|
|
224
|
+
// "symbol": "BTC-USDC-PERP",
|
|
225
|
+
// "type": "ticker",
|
|
226
|
+
// "vwap": "105030.6996",
|
|
227
|
+
// "currentPrice": "104324.7747",
|
|
228
|
+
// "ammData": [
|
|
229
|
+
// {
|
|
230
|
+
// "feeTierId": "1",
|
|
231
|
+
// "currentPrice": "104324.7747",
|
|
232
|
+
// "baseReservesQuantity": "8.27911366",
|
|
233
|
+
// "quoteReservesQuantity": "1067283.0234",
|
|
234
|
+
// "bidSpreadFee": "0.00000000",
|
|
235
|
+
// "askSpreadFee": "0.00000000"
|
|
236
|
+
// }
|
|
237
|
+
// ],
|
|
238
|
+
// "createdAtDatetime": "2025-06-05T14:13:58.951Z",
|
|
239
|
+
// "markPrice": "104289.6884",
|
|
240
|
+
// "fundingRate": "-0.000192",
|
|
241
|
+
// "openInterest": "92.24146651"
|
|
242
|
+
// }
|
|
243
|
+
// }
|
|
244
|
+
//
|
|
245
|
+
const updateType = this.safeString(message, 'type', '');
|
|
246
|
+
const data = this.safeDict(message, 'data', {});
|
|
247
|
+
const marketId = this.safeString(data, 'symbol');
|
|
248
|
+
const market = this.safeMarket(marketId);
|
|
249
|
+
const symbol = market['symbol'];
|
|
250
|
+
let parsed = undefined;
|
|
251
|
+
if ((updateType === 'snapshot')) {
|
|
252
|
+
parsed = this.parseTicker(data, market);
|
|
253
|
+
}
|
|
254
|
+
else if (updateType === 'update') {
|
|
255
|
+
const ticker = this.safeDict(this.tickers, symbol, {});
|
|
256
|
+
const rawTicker = this.safeDict(ticker, 'info', {});
|
|
257
|
+
const merged = this.extend(rawTicker, data);
|
|
258
|
+
parsed = this.parseTicker(merged, market);
|
|
259
|
+
}
|
|
260
|
+
this.tickers[symbol] = parsed;
|
|
261
|
+
const messageHash = 'ticker::' + symbol;
|
|
262
|
+
client.resolve(this.tickers[symbol], messageHash);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* @method
|
|
266
|
+
* @name bullish#watchOrderBook
|
|
267
|
+
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
|
|
268
|
+
* @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--multi-orderbook-websocket-unauthenticated
|
|
269
|
+
* @param {string} symbol unified symbol of the market to fetch the order book for
|
|
270
|
+
* @param {int} [limit] the maximum amount of order book entries to return
|
|
271
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
272
|
+
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
|
|
273
|
+
*/
|
|
274
|
+
async watchOrderBook(symbol, limit = undefined, params = {}) {
|
|
275
|
+
await this.loadMarkets();
|
|
276
|
+
const market = this.market(symbol);
|
|
277
|
+
const url = '/trading-api/v1/market-data/orderbook';
|
|
278
|
+
const messageHash = 'orderbook::' + market['symbol'];
|
|
279
|
+
const request = {
|
|
280
|
+
'topic': 'l2Orderbook',
|
|
281
|
+
'symbol': market['id'],
|
|
282
|
+
};
|
|
283
|
+
const orderbook = await this.watchPublic(url, messageHash, request, params);
|
|
284
|
+
return orderbook.limit();
|
|
285
|
+
}
|
|
286
|
+
handleOrderBook(client, message) {
|
|
287
|
+
//
|
|
288
|
+
// {
|
|
289
|
+
// "type": "snapshot",
|
|
290
|
+
// "dataType": "V1TALevel2",
|
|
291
|
+
// "data": {
|
|
292
|
+
// "timestamp": "1749372632028",
|
|
293
|
+
// "bids": [
|
|
294
|
+
// "105523.3000",
|
|
295
|
+
// "0.00046045",
|
|
296
|
+
// ],
|
|
297
|
+
// "asks": [
|
|
298
|
+
// "105523.4000",
|
|
299
|
+
// "0.00117112",
|
|
300
|
+
// ],
|
|
301
|
+
// "publishedAtTimestamp": "1749372632073",
|
|
302
|
+
// "datetime": "2025-06-08T08:50:32.028Z",
|
|
303
|
+
// "sequenceNumberRange": [ 1967862061, 1967862062 ],
|
|
304
|
+
// "symbol": "BTCUSDC"
|
|
305
|
+
// }
|
|
306
|
+
// }
|
|
307
|
+
//
|
|
308
|
+
// current channel is 'l2Orderbook' which returns only snapshots
|
|
309
|
+
const data = this.safeDict(message, 'data', {});
|
|
310
|
+
const marketId = this.safeString(data, 'symbol');
|
|
311
|
+
const symbol = this.safeSymbol(marketId);
|
|
312
|
+
const messageHash = 'orderbook::' + symbol;
|
|
313
|
+
const timestamp = this.safeInteger(data, 'timestamp');
|
|
314
|
+
if (!(symbol in this.orderbooks)) {
|
|
315
|
+
this.orderbooks[symbol] = this.orderBook();
|
|
316
|
+
}
|
|
317
|
+
const orderbook = this.orderbooks[symbol];
|
|
318
|
+
const bids = this.separateBidsOrAsks(this.safeList(data, 'bids', []));
|
|
319
|
+
const asks = this.separateBidsOrAsks(this.safeList(data, 'asks', []));
|
|
320
|
+
const snapshot = {
|
|
321
|
+
'bids': bids,
|
|
322
|
+
'asks': asks,
|
|
323
|
+
};
|
|
324
|
+
const parsed = this.parseOrderBook(snapshot, symbol, timestamp);
|
|
325
|
+
const sequenceNumberRange = this.safeList(data, 'sequenceNumberRange', []);
|
|
326
|
+
if (sequenceNumberRange.length > 0) {
|
|
327
|
+
const lastIndex = sequenceNumberRange.length - 1;
|
|
328
|
+
parsed['nonce'] = this.safeInteger(sequenceNumberRange, lastIndex);
|
|
329
|
+
}
|
|
330
|
+
orderbook.reset(parsed);
|
|
331
|
+
this.orderbooks[symbol] = orderbook;
|
|
332
|
+
client.resolve(orderbook, messageHash);
|
|
333
|
+
}
|
|
334
|
+
separateBidsOrAsks(entry) {
|
|
335
|
+
const result = [];
|
|
336
|
+
// 300 = '54885.0000000'
|
|
337
|
+
// 301 = '0.06141566'
|
|
338
|
+
// 302 ='53714.0000000'
|
|
339
|
+
for (let i = 0; i < entry.length; i++) {
|
|
340
|
+
if (i % 2 !== 0) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const price = this.safeString(entry, i);
|
|
344
|
+
const amount = this.safeString(entry, i + 1);
|
|
345
|
+
result.push([price, amount]);
|
|
346
|
+
}
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* @method
|
|
351
|
+
* @name bullish#watchOrders
|
|
352
|
+
* @description watches information on multiple orders made by the user
|
|
353
|
+
* @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--private-data-websocket-authenticated
|
|
354
|
+
* @param {string} symbol unified market symbol of the market orders were made in
|
|
355
|
+
* @param {int} [since] the earliest time in ms to fetch orders for
|
|
356
|
+
* @param {int} [limit] the maximum number of order structures to retrieve
|
|
357
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
358
|
+
* @param {string} [params.tradingAccountId] the trading account id to fetch entries for
|
|
359
|
+
* @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
|
|
360
|
+
*/
|
|
361
|
+
async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
|
362
|
+
await this.loadMarkets();
|
|
363
|
+
const subscribeHash = 'orders';
|
|
364
|
+
let messageHash = subscribeHash;
|
|
365
|
+
if (symbol !== undefined) {
|
|
366
|
+
symbol = this.symbol(symbol);
|
|
367
|
+
messageHash = messageHash + '::' + symbol;
|
|
368
|
+
}
|
|
369
|
+
const request = {
|
|
370
|
+
'topic': 'orders',
|
|
371
|
+
};
|
|
372
|
+
const tradingAccountId = this.safeString(params, 'tradingAccountId');
|
|
373
|
+
if (tradingAccountId !== undefined) {
|
|
374
|
+
request['tradingAccountId'] = tradingAccountId;
|
|
375
|
+
params = this.omit(params, 'tradingAccountId');
|
|
376
|
+
}
|
|
377
|
+
const orders = await this.watchPrivate(messageHash, subscribeHash, request, params);
|
|
378
|
+
if (this.newUpdates) {
|
|
379
|
+
limit = orders.getLimit(symbol, limit);
|
|
380
|
+
}
|
|
381
|
+
return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
|
|
382
|
+
}
|
|
383
|
+
handleOrders(client, message) {
|
|
384
|
+
// snapshot
|
|
385
|
+
// {
|
|
386
|
+
// "type": "snapshot",
|
|
387
|
+
// "tradingAccountId": "111309424211255",
|
|
388
|
+
// "dataType": "V1TAOrder",
|
|
389
|
+
// "data": [ ... ] // could be an empty list or a list of orders
|
|
390
|
+
// }
|
|
391
|
+
//
|
|
392
|
+
// update
|
|
393
|
+
// {
|
|
394
|
+
// "type": "update",
|
|
395
|
+
// "tradingAccountId": "111309424211255",
|
|
396
|
+
// "dataType": "V1TAOrder",
|
|
397
|
+
// "data": {
|
|
398
|
+
// "status": "OPEN",
|
|
399
|
+
// "createdAtTimestamp": "1751893427971",
|
|
400
|
+
// "quoteFee": "0.000000",
|
|
401
|
+
// "stopPrice": null,
|
|
402
|
+
// "quantityFilled": "0.00000000",
|
|
403
|
+
// "handle": null,
|
|
404
|
+
// "clientOrderId": null,
|
|
405
|
+
// "quantity": "0.10000000",
|
|
406
|
+
// "margin": false,
|
|
407
|
+
// "side": "BUY",
|
|
408
|
+
// "createdAtDatetime": "2025-07-07T13:03:47.971Z",
|
|
409
|
+
// "isLiquidation": false,
|
|
410
|
+
// "borrowedQuoteQuantity": null,
|
|
411
|
+
// "borrowedBaseQuantity": null,
|
|
412
|
+
// "timeInForce": "GTC",
|
|
413
|
+
// "borrowedQuantity": null,
|
|
414
|
+
// "baseFee": "0.000000",
|
|
415
|
+
// "quoteAmount": "0.0000000",
|
|
416
|
+
// "price": "0.0000000",
|
|
417
|
+
// "statusReason": "Order accepted",
|
|
418
|
+
// "type": "MKT",
|
|
419
|
+
// "statusReasonCode": 6014,
|
|
420
|
+
// "allowBorrow": false,
|
|
421
|
+
// "orderId": "862317981870850049",
|
|
422
|
+
// "publishedAtTimestamp": "1751893427975",
|
|
423
|
+
// "symbol": "ETHUSDT",
|
|
424
|
+
// "averageFillPrice": null
|
|
425
|
+
// }
|
|
426
|
+
// }
|
|
427
|
+
//
|
|
428
|
+
const type = this.safeString(message, 'type');
|
|
429
|
+
let rawOrders = [];
|
|
430
|
+
if (type === 'update') {
|
|
431
|
+
const data = this.safeDict(message, 'data', {});
|
|
432
|
+
rawOrders.push(data); // update is a single order
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
rawOrders = this.safeList(message, 'data', []); // snapshot is a list of orders
|
|
436
|
+
}
|
|
437
|
+
if (rawOrders.length > 0) {
|
|
438
|
+
if (this.orders === undefined) {
|
|
439
|
+
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
|
|
440
|
+
this.orders = new Cache.ArrayCacheBySymbolById(limit);
|
|
441
|
+
}
|
|
442
|
+
const orders = this.orders;
|
|
443
|
+
const symbols = {};
|
|
444
|
+
for (let i = 0; i < rawOrders.length; i++) {
|
|
445
|
+
const rawOrder = rawOrders[i];
|
|
446
|
+
const parsedOrder = this.parseOrder(rawOrder);
|
|
447
|
+
orders.append(parsedOrder);
|
|
448
|
+
const symbol = this.safeString(parsedOrder, 'symbol');
|
|
449
|
+
symbols[symbol] = true;
|
|
450
|
+
}
|
|
451
|
+
const messageHash = 'orders';
|
|
452
|
+
client.resolve(orders, messageHash);
|
|
453
|
+
const keys = Object.keys(symbols);
|
|
454
|
+
for (let i = 0; i < keys.length; i++) {
|
|
455
|
+
const hashSymbol = keys[i];
|
|
456
|
+
const symbolMessageHash = messageHash + '::' + hashSymbol;
|
|
457
|
+
client.resolve(this.orders, symbolMessageHash);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* @method
|
|
463
|
+
* @name bullish#watchMyTrades
|
|
464
|
+
* @description watches information on multiple trades made by the user
|
|
465
|
+
* @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--private-data-websocket-authenticated
|
|
466
|
+
* @param {string} symbol unified market symbol of the market trades were made in
|
|
467
|
+
* @param {int} [since] the earliest time in ms to fetch trades for
|
|
468
|
+
* @param {int} [limit] the maximum number of trade structures to retrieve
|
|
469
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
470
|
+
* @param {string} [params.tradingAccountId] the trading account id to fetch entries for
|
|
471
|
+
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure}
|
|
472
|
+
*/
|
|
473
|
+
async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
|
|
474
|
+
await this.loadMarkets();
|
|
475
|
+
const subscribeHash = 'myTrades';
|
|
476
|
+
let messageHash = subscribeHash;
|
|
477
|
+
if (symbol !== undefined) {
|
|
478
|
+
symbol = this.symbol(symbol);
|
|
479
|
+
messageHash += '::' + symbol;
|
|
480
|
+
}
|
|
481
|
+
const request = {
|
|
482
|
+
'topic': 'trades',
|
|
483
|
+
};
|
|
484
|
+
const tradingAccountId = this.safeString(params, 'tradingAccountId');
|
|
485
|
+
if (tradingAccountId !== undefined) {
|
|
486
|
+
request['tradingAccountId'] = tradingAccountId;
|
|
487
|
+
params = this.omit(params, 'tradingAccountId');
|
|
488
|
+
}
|
|
489
|
+
const trades = await this.watchPrivate(messageHash, subscribeHash, request, params);
|
|
490
|
+
if (this.newUpdates) {
|
|
491
|
+
limit = trades.getLimit(symbol, limit);
|
|
492
|
+
}
|
|
493
|
+
return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
|
|
494
|
+
}
|
|
495
|
+
handleMyTrades(client, message) {
|
|
496
|
+
//
|
|
497
|
+
// snapshot
|
|
498
|
+
// {
|
|
499
|
+
// "type": "snapshot",
|
|
500
|
+
// "tradingAccountId": "111309424211255",
|
|
501
|
+
// "dataType": "V1TATrade",
|
|
502
|
+
// "data": [ ... ] // could be an empty list or a list of trades
|
|
503
|
+
// }
|
|
504
|
+
//
|
|
505
|
+
// update
|
|
506
|
+
// {
|
|
507
|
+
// "type": "update",
|
|
508
|
+
// "tradingAccountId": "111309424211255",
|
|
509
|
+
// "dataType": "V1TATrade",
|
|
510
|
+
// "data": {
|
|
511
|
+
// "clientOtcTradeId": null,
|
|
512
|
+
// "tradeId": "100203000003940164",
|
|
513
|
+
// "baseFee": "0.00000000",
|
|
514
|
+
// "isTaker": true,
|
|
515
|
+
// "quoteAmount": "253.6012195",
|
|
516
|
+
// "price": "2536.0121950",
|
|
517
|
+
// "createdAtTimestamp": "1751914859840",
|
|
518
|
+
// "quoteFee": "0.0000000",
|
|
519
|
+
// "tradeRebateAmount": null,
|
|
520
|
+
// "tradeRebateAssetSymbol": null,
|
|
521
|
+
// "handle": null,
|
|
522
|
+
// "otcTradeId": null,
|
|
523
|
+
// "otcMatchId": null,
|
|
524
|
+
// "orderId": "862407873644725249",
|
|
525
|
+
// "quantity": "0.10000000",
|
|
526
|
+
// "publishedAtTimestamp": "1751914859843",
|
|
527
|
+
// "side": "SELL",
|
|
528
|
+
// "createdAtDatetime": "2025-07-07T19:00:59.840Z",
|
|
529
|
+
// "symbol": "ETHUSDT"
|
|
530
|
+
// }
|
|
531
|
+
// }
|
|
532
|
+
//
|
|
533
|
+
const type = this.safeString(message, 'type');
|
|
534
|
+
let rawTrades = [];
|
|
535
|
+
if (type === 'update') {
|
|
536
|
+
const data = this.safeDict(message, 'data', {});
|
|
537
|
+
rawTrades.push(data); // update is a single trade
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
rawTrades = this.safeList(message, 'data', []); // snapshot is a list of trades
|
|
541
|
+
}
|
|
542
|
+
if (rawTrades.length > 0) {
|
|
543
|
+
if (this.myTrades === undefined) {
|
|
544
|
+
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
|
|
545
|
+
this.myTrades = new Cache.ArrayCacheBySymbolById(limit);
|
|
546
|
+
}
|
|
547
|
+
const trades = this.myTrades;
|
|
548
|
+
const symbols = {};
|
|
549
|
+
for (let i = 0; i < rawTrades.length; i++) {
|
|
550
|
+
const rawTrade = rawTrades[i];
|
|
551
|
+
const parsedTrade = this.parseTrade(rawTrade);
|
|
552
|
+
trades.append(parsedTrade);
|
|
553
|
+
const symbol = this.safeString(parsedTrade, 'symbol');
|
|
554
|
+
symbols[symbol] = true;
|
|
555
|
+
}
|
|
556
|
+
const messageHash = 'myTrades';
|
|
557
|
+
client.resolve(trades, messageHash);
|
|
558
|
+
const keys = Object.keys(symbols);
|
|
559
|
+
for (let i = 0; i < keys.length; i++) {
|
|
560
|
+
const hashSymbol = keys[i];
|
|
561
|
+
const symbolMessageHash = messageHash + '::' + hashSymbol;
|
|
562
|
+
client.resolve(this.myTrades, symbolMessageHash);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* @method
|
|
568
|
+
* @name bullish#watchBalance
|
|
569
|
+
* @description watch balance and get the amount of funds available for trading or funds locked in orders
|
|
570
|
+
* @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--private-data-websocket-authenticated
|
|
571
|
+
* @param {object} [params] extra parameters specific to the exchange API endpoint
|
|
572
|
+
* @param {string} [params.tradingAccountId] the trading account id to fetch entries for
|
|
573
|
+
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
|
|
574
|
+
*/
|
|
575
|
+
async watchBalance(params = {}) {
|
|
576
|
+
await this.loadMarkets();
|
|
577
|
+
const request = {
|
|
578
|
+
'topic': 'assetAccounts',
|
|
579
|
+
};
|
|
580
|
+
let messageHash = 'balance';
|
|
581
|
+
const tradingAccountId = this.safeString(params, 'tradingAccountId');
|
|
582
|
+
if (tradingAccountId !== undefined) {
|
|
583
|
+
params = this.omit(params, 'tradingAccountId');
|
|
584
|
+
request['tradingAccountId'] = tradingAccountId;
|
|
585
|
+
messageHash += '::' + tradingAccountId;
|
|
586
|
+
}
|
|
587
|
+
return await this.watchPrivate(messageHash, messageHash, request, params);
|
|
588
|
+
}
|
|
589
|
+
handleBalance(client, message) {
|
|
590
|
+
//
|
|
591
|
+
// snapshot
|
|
592
|
+
// {
|
|
593
|
+
// "type": "snapshot",
|
|
594
|
+
// "tradingAccountId": "111309424211255",
|
|
595
|
+
// "dataType": "V1TAAssetAccount",
|
|
596
|
+
// "data": [
|
|
597
|
+
// {
|
|
598
|
+
// "updatedAtTimestamp": "1751989627509",
|
|
599
|
+
// "borrowedQuantity": "0.0000",
|
|
600
|
+
// "tradingAccountId": "111309424211255",
|
|
601
|
+
// "loanedQuantity": "0.0000",
|
|
602
|
+
// "lockedQuantity": "0.0000",
|
|
603
|
+
// "assetId": "5",
|
|
604
|
+
// "assetSymbol": "USDC",
|
|
605
|
+
// "publishedAtTimestamp": "1751989627512",
|
|
606
|
+
// "availableQuantity": "999672939.8767",
|
|
607
|
+
// "updatedAtDatetime": "2025-07-08T15:47:07.509Z"
|
|
608
|
+
// }
|
|
609
|
+
// ]
|
|
610
|
+
// }
|
|
611
|
+
//
|
|
612
|
+
// update
|
|
613
|
+
// {
|
|
614
|
+
// "type": "update",
|
|
615
|
+
// "tradingAccountId": "111309424211255",
|
|
616
|
+
// "dataType": "V1TAAssetAccount",
|
|
617
|
+
// "data": {
|
|
618
|
+
// "updatedAtTimestamp": "1751989627509",
|
|
619
|
+
// "borrowedQuantity": "0.0000",
|
|
620
|
+
// "tradingAccountId": "111309424211255",
|
|
621
|
+
// "loanedQuantity": "0.0000",
|
|
622
|
+
// "lockedQuantity": "0.0000",
|
|
623
|
+
// "assetId": "5",
|
|
624
|
+
// "assetSymbol": "USDC",
|
|
625
|
+
// "publishedAtTimestamp": "1751989627512",
|
|
626
|
+
// "availableQuantity": "999672939.8767",
|
|
627
|
+
// "updatedAtDatetime": "2025-07-08T15:47:07.509Z"
|
|
628
|
+
// }
|
|
629
|
+
// }
|
|
630
|
+
//
|
|
631
|
+
const tradingAccountId = this.safeString(message, 'tradingAccountId');
|
|
632
|
+
if (!(tradingAccountId in this.balance)) {
|
|
633
|
+
this.balance[tradingAccountId] = {};
|
|
634
|
+
}
|
|
635
|
+
const messageType = this.safeString(message, 'type');
|
|
636
|
+
if (messageType === 'snapshot') {
|
|
637
|
+
const data = this.safeList(message, 'data', []);
|
|
638
|
+
this.balance[tradingAccountId] = this.parseBalance(data);
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
const data = this.safeDict(message, 'data', {});
|
|
642
|
+
const assetId = this.safeString(data, 'assetSymbol');
|
|
643
|
+
const account = this.account();
|
|
644
|
+
account['total'] = this.safeString(data, 'availableQuantity');
|
|
645
|
+
account['used'] = this.safeString(data, 'lockedQuantity');
|
|
646
|
+
const code = this.safeCurrencyCode(assetId);
|
|
647
|
+
this.balance[tradingAccountId][code] = account;
|
|
648
|
+
this.balance[tradingAccountId]['info'] = message;
|
|
649
|
+
this.balance[tradingAccountId] = this.safeBalance(this.balance[tradingAccountId]);
|
|
650
|
+
}
|
|
651
|
+
const messageHash = 'balance';
|
|
652
|
+
const tradingAccountIdHash = '::' + tradingAccountId;
|
|
653
|
+
client.resolve(this.balance[tradingAccountId], messageHash);
|
|
654
|
+
client.resolve(this.balance[tradingAccountId], messageHash + tradingAccountIdHash);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* @method
|
|
658
|
+
* @name bullish#watchPositions
|
|
659
|
+
* @see https://api.exchange.bullish.com/docs/api/rest/trading-api/v2/#overview--private-data-websocket-authenticated
|
|
660
|
+
* @description watch all open positions
|
|
661
|
+
* @param {string[]} [symbols] list of unified market symbols
|
|
662
|
+
* @param {int} [since] the earliest time in ms to fetch positions for
|
|
663
|
+
* @param {int} [limit] the maximum number of positions to retrieve
|
|
664
|
+
* @param {object} params extra parameters specific to the exchange API endpoint
|
|
665
|
+
* @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
|
|
666
|
+
*/
|
|
667
|
+
async watchPositions(symbols = undefined, since = undefined, limit = undefined, params = {}) {
|
|
668
|
+
await this.loadMarkets();
|
|
669
|
+
const subscribeHash = 'positions';
|
|
670
|
+
let messageHash = subscribeHash;
|
|
671
|
+
if (!this.isEmpty(symbols)) {
|
|
672
|
+
symbols = this.marketSymbols(symbols);
|
|
673
|
+
messageHash += '::' + symbols.join(',');
|
|
674
|
+
}
|
|
675
|
+
const request = {
|
|
676
|
+
'topic': 'derivativesPositionsV2',
|
|
677
|
+
};
|
|
678
|
+
const positions = await this.watchPrivate(messageHash, subscribeHash, request, params);
|
|
679
|
+
if (this.newUpdates) {
|
|
680
|
+
return positions;
|
|
681
|
+
}
|
|
682
|
+
return this.filterBySymbolsSinceLimit(positions, symbols, since, limit, true);
|
|
683
|
+
}
|
|
684
|
+
handlePositions(client, message) {
|
|
685
|
+
// exchange does not return messages for sandbox mode
|
|
686
|
+
// current method is implemented blindly
|
|
687
|
+
// todo: check if this works with not-sandbox mode
|
|
688
|
+
const messageType = this.safeString(message, 'type');
|
|
689
|
+
let rawPositions = [];
|
|
690
|
+
if (messageType === 'update') {
|
|
691
|
+
const data = this.safeDict(message, 'data', {});
|
|
692
|
+
rawPositions.push(data);
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
rawPositions = this.safeList(message, 'data', []);
|
|
696
|
+
}
|
|
697
|
+
if (this.positions === undefined) {
|
|
698
|
+
this.positions = new Cache.ArrayCacheBySymbolBySide();
|
|
699
|
+
}
|
|
700
|
+
const positions = this.positions;
|
|
701
|
+
const newPositions = [];
|
|
702
|
+
for (let i = 0; i < rawPositions.length; i++) {
|
|
703
|
+
const rawPosition = rawPositions[i];
|
|
704
|
+
const position = this.parsePosition(rawPosition);
|
|
705
|
+
positions.append(position);
|
|
706
|
+
newPositions.push(position);
|
|
707
|
+
}
|
|
708
|
+
const messageHashes = this.findMessageHashes(client, 'positions::');
|
|
709
|
+
for (let i = 0; i < messageHashes.length; i++) {
|
|
710
|
+
const messageHash = messageHashes[i];
|
|
711
|
+
const parts = messageHash.split('::');
|
|
712
|
+
const symbolsString = parts[1];
|
|
713
|
+
const symbols = symbolsString.split(',');
|
|
714
|
+
const symbolPositions = this.filterByArray(newPositions, 'symbol', symbols, false);
|
|
715
|
+
if (!this.isEmpty(symbolPositions)) {
|
|
716
|
+
client.resolve(symbolPositions, messageHash);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
client.resolve(positions, 'positions');
|
|
720
|
+
}
|
|
721
|
+
handleErrorMessage(client, message) {
|
|
722
|
+
//
|
|
723
|
+
// {
|
|
724
|
+
// "data": {
|
|
725
|
+
// "errorCode": 401,
|
|
726
|
+
// "errorCodeName": "UNAUTHORIZED",
|
|
727
|
+
// "message": "Unable to authenticate; JWT is missing/invalid or unauthorised to access account"
|
|
728
|
+
// },
|
|
729
|
+
// "dataType": "V1TAErrorResponse",
|
|
730
|
+
// "type": "error"
|
|
731
|
+
// }
|
|
732
|
+
//
|
|
733
|
+
const data = this.safeDict(message, 'data', {});
|
|
734
|
+
const feedback = this.id + ' ' + this.json(data);
|
|
735
|
+
try {
|
|
736
|
+
const errorCode = this.safeString(data, 'errorCode');
|
|
737
|
+
const errorCodeName = this.safeString(data, 'errorCodeName');
|
|
738
|
+
this.throwExactlyMatchedException(this.exceptions['exact'], errorCode, feedback);
|
|
739
|
+
this.throwBroadlyMatchedException(this.exceptions['broad'], errorCodeName, feedback);
|
|
740
|
+
throw new errors.ExchangeError(feedback); // unknown message
|
|
741
|
+
}
|
|
742
|
+
catch (e) {
|
|
743
|
+
client.reject(e);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
handleMessage(client, message) {
|
|
747
|
+
const dataType = this.safeString(message, 'dataType');
|
|
748
|
+
const result = this.safeDict(message, 'result');
|
|
749
|
+
if (result !== undefined) {
|
|
750
|
+
const response = this.safeString(result, 'message');
|
|
751
|
+
if (response === 'Keep alive pong') {
|
|
752
|
+
this.handlePong(client, message);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
else if (dataType !== undefined) {
|
|
756
|
+
if (dataType === 'V1TAAnonymousTradeUpdate') {
|
|
757
|
+
this.handleTrades(client, message);
|
|
758
|
+
}
|
|
759
|
+
if (dataType === 'V1TATickerResponse') {
|
|
760
|
+
this.handleTicker(client, message);
|
|
761
|
+
}
|
|
762
|
+
if (dataType === 'V1TALevel2') {
|
|
763
|
+
this.handleOrderBook(client, message);
|
|
764
|
+
}
|
|
765
|
+
if (dataType === 'V1TAOrder') {
|
|
766
|
+
this.handleOrders(client, message);
|
|
767
|
+
}
|
|
768
|
+
if (dataType === 'V1TATrade') {
|
|
769
|
+
this.handleMyTrades(client, message);
|
|
770
|
+
}
|
|
771
|
+
if (dataType === 'V1TAAssetAccount') {
|
|
772
|
+
this.handleBalance(client, message);
|
|
773
|
+
}
|
|
774
|
+
if (dataType === 'V1TAErrorResponse') {
|
|
775
|
+
this.handleErrorMessage(client, message);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
exports["default"] = bullish;
|