@blackcube/xgate-sdk 0.21.1 → 0.23.0
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 +239 -12
- package/dist/index.cjs +559 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +294 -2
- package/dist/index.d.ts +294 -2
- package/dist/index.js +554 -137
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.cjs
CHANGED
|
@@ -21,135 +21,12 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
21
21
|
return result;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
// src/enums/timeframe.enums.ts
|
|
25
|
-
var Timeframe = /* @__PURE__ */ ((Timeframe2) => {
|
|
26
|
-
Timeframe2["M1"] = "1m";
|
|
27
|
-
Timeframe2["M5"] = "5m";
|
|
28
|
-
Timeframe2["M15"] = "15m";
|
|
29
|
-
Timeframe2["H1"] = "1h";
|
|
30
|
-
Timeframe2["H4"] = "4h";
|
|
31
|
-
Timeframe2["D"] = "1d";
|
|
32
|
-
Timeframe2["W"] = "1w";
|
|
33
|
-
return Timeframe2;
|
|
34
|
-
})(Timeframe || {});
|
|
35
|
-
var TIMEFRAME_MINUTES = {
|
|
36
|
-
["1m" /* M1 */]: 1,
|
|
37
|
-
["5m" /* M5 */]: 5,
|
|
38
|
-
["15m" /* M15 */]: 15,
|
|
39
|
-
["1h" /* H1 */]: 60,
|
|
40
|
-
["4h" /* H4 */]: 240,
|
|
41
|
-
["1d" /* D */]: 1440,
|
|
42
|
-
["1w" /* W */]: 10080
|
|
43
|
-
};
|
|
44
|
-
function timeframeMs(timeframe) {
|
|
45
|
-
return TIMEFRAME_MINUTES[timeframe] * 6e4;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// src/helpers/decimal.ts
|
|
49
|
-
function parse(value) {
|
|
50
|
-
const trimmed = value.trim();
|
|
51
|
-
if (/^-?\d+(\.\d+)?$/.test(trimmed) === false) {
|
|
52
|
-
throw new Error(`D\xE9cimal illisible : "${value}".`);
|
|
53
|
-
}
|
|
54
|
-
const dot = trimmed.indexOf(".");
|
|
55
|
-
if (dot < 0) {
|
|
56
|
-
return { digits: BigInt(trimmed), scale: 0 };
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
digits: BigInt(trimmed.slice(0, dot) + trimmed.slice(dot + 1)),
|
|
60
|
-
scale: trimmed.length - dot - 1
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
function format(digits, scale) {
|
|
64
|
-
const negative = digits < 0n;
|
|
65
|
-
const plain = (negative === true ? -digits : digits).toString().padStart(scale + 1, "0");
|
|
66
|
-
const sign = negative === true ? "-" : "";
|
|
67
|
-
if (scale === 0) {
|
|
68
|
-
return sign + plain;
|
|
69
|
-
}
|
|
70
|
-
const whole = plain.slice(0, plain.length - scale);
|
|
71
|
-
const fraction = plain.slice(plain.length - scale).replace(/0+$/, "");
|
|
72
|
-
return fraction === "" ? sign + whole : `${sign + whole}.${fraction}`;
|
|
73
|
-
}
|
|
74
|
-
function addDecimals(left, right) {
|
|
75
|
-
const a = parse(left);
|
|
76
|
-
const b = parse(right);
|
|
77
|
-
const scale = Math.max(a.scale, b.scale);
|
|
78
|
-
const digits = a.digits * 10n ** BigInt(scale - a.scale) + b.digits * 10n ** BigInt(scale - b.scale);
|
|
79
|
-
return format(digits, scale);
|
|
80
|
-
}
|
|
81
|
-
function compareDecimals(left, right) {
|
|
82
|
-
const a = parse(left);
|
|
83
|
-
const b = parse(right);
|
|
84
|
-
const scale = Math.max(a.scale, b.scale);
|
|
85
|
-
const x = a.digits * 10n ** BigInt(scale - a.scale);
|
|
86
|
-
const y = b.digits * 10n ** BigInt(scale - b.scale);
|
|
87
|
-
if (x === y) {
|
|
88
|
-
return 0;
|
|
89
|
-
}
|
|
90
|
-
return x > y ? 1 : -1;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// src/helpers/candle-aggregator.ts
|
|
94
|
-
var MONDAY_OFFSET_MS = 4 * 24 * 60 * 60 * 1e3;
|
|
95
|
-
function aggregate(source, target, now = Date.now()) {
|
|
96
|
-
if (source.length === 0) {
|
|
97
|
-
return [];
|
|
98
|
-
}
|
|
99
|
-
const sourceMs = timeframeMs(source[0]?.interval ?? "1m" /* M1 */);
|
|
100
|
-
const targetMs = timeframeMs(target);
|
|
101
|
-
if (targetMs <= sourceMs) {
|
|
102
|
-
return [];
|
|
103
|
-
}
|
|
104
|
-
const offsetMs = target === "1w" /* W */ ? MONDAY_OFFSET_MS : 0;
|
|
105
|
-
const expected = targetMs / sourceMs;
|
|
106
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
107
|
-
const sorted = [...source].sort(
|
|
108
|
-
(left, right) => left.openedAt.getTime() - right.openedAt.getTime()
|
|
109
|
-
);
|
|
110
|
-
for (const candle of sorted) {
|
|
111
|
-
const start = Math.floor((candle.openedAt.getTime() - offsetMs) / targetMs) * targetMs + offsetMs;
|
|
112
|
-
const bucket = buckets.get(start);
|
|
113
|
-
if (bucket === void 0) {
|
|
114
|
-
buckets.set(start, {
|
|
115
|
-
sources: 1,
|
|
116
|
-
candle: {
|
|
117
|
-
...candle,
|
|
118
|
-
interval: target,
|
|
119
|
-
openedAt: new Date(start),
|
|
120
|
-
// Dernière milliseconde du créneau, comme les bougies servies par les venues.
|
|
121
|
-
closedAt: new Date(start + targetMs - 1),
|
|
122
|
-
derived: true
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
} else {
|
|
126
|
-
bucket.sources += 1;
|
|
127
|
-
const built = bucket.candle;
|
|
128
|
-
built.high = compareDecimals(candle.high, built.high) > 0 ? candle.high : built.high;
|
|
129
|
-
built.low = compareDecimals(candle.low, built.low) < 0 ? candle.low : built.low;
|
|
130
|
-
built.close = candle.close;
|
|
131
|
-
built.volume = sum(built.volume, candle.volume);
|
|
132
|
-
built.quoteVolume = sum(built.quoteVolume, candle.quoteVolume);
|
|
133
|
-
built.takerBuyQuoteVolume = sum(built.takerBuyQuoteVolume, candle.takerBuyQuoteVolume);
|
|
134
|
-
built.trades = built.trades === null || candle.trades === null ? null : built.trades + candle.trades;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return [...buckets.keys()].sort((left, right) => left - right).map((start) => buckets.get(start)).filter((bucket) => bucket !== void 0).filter((bucket) => {
|
|
138
|
-
const stillOpen = bucket.candle.openedAt.getTime() + targetMs > now;
|
|
139
|
-
return bucket.sources >= expected || stillOpen === true;
|
|
140
|
-
}).map((bucket) => bucket.candle);
|
|
141
|
-
}
|
|
142
|
-
function sum(left, right) {
|
|
143
|
-
if (left === null || right === null) {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
return addDecimals(left, right);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
24
|
// src/helpers/symbol-naming.ts
|
|
150
25
|
var CONCAT_QUOTE_XEXS = /* @__PURE__ */ new Set(["aster", "binance", "bybit"]);
|
|
151
26
|
var K_MULTIPLIER = /^k(?=[A-Z])/u;
|
|
152
27
|
var NUMERIC_MULTIPLIER = /^10{3,}(?=[A-Z0-9])/u;
|
|
28
|
+
var ABBREV_MULTIPLIER = /^1([MK])(?=[A-Z]{2,})/u;
|
|
29
|
+
var ABBREV_FACTOR = { M: 1e6, K: 1e3 };
|
|
153
30
|
var STOCK_24_5 = /_24_5$/u;
|
|
154
31
|
var TICKER_ALIASES = {
|
|
155
32
|
GOOG: "GOOGL",
|
|
@@ -174,13 +51,26 @@ function canonicalFromXex(rawSymbol, xexId) {
|
|
|
174
51
|
s = s.replace(/-USD-PERP$/u, "").replace(/-PERP$/u, "").replace(/-USDT$/u, "").replace(/-USDC$/u, "").replace(/-USD$/u, "");
|
|
175
52
|
}
|
|
176
53
|
s = s.replace(NUMERIC_MULTIPLIER, "");
|
|
54
|
+
s = s.replace(ABBREV_MULTIPLIER, "");
|
|
177
55
|
s = s.replace(STOCK_24_5, "");
|
|
178
56
|
return TICKER_ALIASES[s] ?? s;
|
|
179
57
|
}
|
|
180
58
|
function canonicalFromBase(base) {
|
|
181
|
-
const s = base.replace(K_MULTIPLIER, "").toUpperCase().replace(NUMERIC_MULTIPLIER, "").replace(STOCK_24_5, "");
|
|
59
|
+
const s = base.replace(K_MULTIPLIER, "").toUpperCase().replace(NUMERIC_MULTIPLIER, "").replace(ABBREV_MULTIPLIER, "").replace(STOCK_24_5, "");
|
|
182
60
|
return TICKER_ALIASES[s] ?? s;
|
|
183
61
|
}
|
|
62
|
+
function multiplierFromXex(rawSymbol) {
|
|
63
|
+
if (K_MULTIPLIER.test(rawSymbol)) {
|
|
64
|
+
return 1e3;
|
|
65
|
+
}
|
|
66
|
+
const upper = rawSymbol.toUpperCase();
|
|
67
|
+
const abbrev = ABBREV_MULTIPLIER.exec(upper);
|
|
68
|
+
if (abbrev !== null) {
|
|
69
|
+
return ABBREV_FACTOR[abbrev[1]] ?? 1;
|
|
70
|
+
}
|
|
71
|
+
const zeros = NUMERIC_MULTIPLIER.exec(upper);
|
|
72
|
+
return zeros === null ? 1 : Number(zeros[0]);
|
|
73
|
+
}
|
|
184
74
|
|
|
185
75
|
// src/helpers/candle-mapper.ts
|
|
186
76
|
function toCandle(wire, xex, interval, quote) {
|
|
@@ -206,15 +96,13 @@ function toCandle(wire, xex, interval, quote) {
|
|
|
206
96
|
};
|
|
207
97
|
}
|
|
208
98
|
|
|
209
|
-
// src/helpers/
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
limit: query.limit
|
|
217
|
-
};
|
|
99
|
+
// src/helpers/ws-limits.ts
|
|
100
|
+
var WS_SUBSCRIPTION_LIMITS = {
|
|
101
|
+
aster: 200,
|
|
102
|
+
hyperliquid: 1e3
|
|
103
|
+
};
|
|
104
|
+
function subscriptionLimitOf(xex) {
|
|
105
|
+
return WS_SUBSCRIPTION_LIMITS[xex];
|
|
218
106
|
}
|
|
219
107
|
|
|
220
108
|
// src/enums/xgate-ex.enums.ts
|
|
@@ -265,6 +153,19 @@ function createAccountXex(xex, access) {
|
|
|
265
153
|
},
|
|
266
154
|
{ default: "a" }
|
|
267
155
|
);
|
|
156
|
+
case "lighter" /* Lighter */:
|
|
157
|
+
return new lighterSdk.Lighter(
|
|
158
|
+
{
|
|
159
|
+
a: {
|
|
160
|
+
apiPrivateKey: access.privateKey ?? NULL_KEY,
|
|
161
|
+
apiKeyIndex: access.apiKeyIndex ?? 0,
|
|
162
|
+
accountIndex: access.accountIndex ?? 0,
|
|
163
|
+
l1Address: adresse(),
|
|
164
|
+
network: access.network ?? "mainnet"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{ default: "a" }
|
|
168
|
+
);
|
|
268
169
|
case "aster" /* Aster */:
|
|
269
170
|
if (access.privateKey === void 0 || access.privateKey === "") {
|
|
270
171
|
throw new Error(
|
|
@@ -305,6 +206,7 @@ var WALLET_XEXES = [
|
|
|
305
206
|
"hyperliquid" /* Hyperliquid */,
|
|
306
207
|
"pacifica" /* Pacifica */,
|
|
307
208
|
"aster" /* Aster */,
|
|
209
|
+
"lighter" /* Lighter */,
|
|
308
210
|
"blofin" /* Blofin */
|
|
309
211
|
];
|
|
310
212
|
function createPublicXex(xex) {
|
|
@@ -348,9 +250,288 @@ function createPublicSpotXex(xex) {
|
|
|
348
250
|
}
|
|
349
251
|
}
|
|
350
252
|
|
|
253
|
+
// src/services/candles-stream.service.ts
|
|
254
|
+
var CandlesStreamService = class {
|
|
255
|
+
constructor(xex, kind = "perp") {
|
|
256
|
+
this.xex = xex;
|
|
257
|
+
this.kind = kind;
|
|
258
|
+
this.logger = new common.Logger(`CandlesStream:${xex}${kind === "spot" ? ":spot" : ""}`);
|
|
259
|
+
this.maxSubscriptions = subscriptionLimitOf(xex) ?? null;
|
|
260
|
+
}
|
|
261
|
+
xex;
|
|
262
|
+
kind;
|
|
263
|
+
logger;
|
|
264
|
+
source = null;
|
|
265
|
+
unsubscribes = [];
|
|
266
|
+
/** Le plafond de souscriptions documenté par la venue, ou `null` si personne ne l'a publié. */
|
|
267
|
+
maxSubscriptions;
|
|
268
|
+
/**
|
|
269
|
+
* Ouvre le flux sur une LISTE de marchés, sur une seule socket.
|
|
270
|
+
*
|
|
271
|
+
* **UNE ERREUR DE FLUX COUPE TOUT, PUIS LÈVE.** Elle n'arrive pas à la souscription : mesuré le
|
|
272
|
+
* 2026-08-07, la venue accepte un symbole inconnu sans broncher, puis ferme la connexion en le
|
|
273
|
+
* découvrant. Le SDK reconnecte, elle referme — cinq cycles en douze secondes, et sur une socket
|
|
274
|
+
* partagée **tous** les marchés valides cessent de recevoir, en silence.
|
|
275
|
+
*
|
|
276
|
+
* On refuse cette boucle : à la première erreur, les souscriptions sont coupées, l'incident est
|
|
277
|
+
* journalisé en `error`, et l'exception part. Un flux à moitié mort qui se tait coûte plus cher
|
|
278
|
+
* qu'un arrêt franc — on ne découvre le premier qu'en constatant l'absence de données.
|
|
279
|
+
*
|
|
280
|
+
* ⚠️ L'exception naît dans un rappel de socket : elle ne remonte pas à l'appelant de `start()`,
|
|
281
|
+
* elle sort en erreur non capturée. C'est délibéré — elle doit être impossible à ignorer.
|
|
282
|
+
*
|
|
283
|
+
* **binance et bybit ne remontent pas encore leurs erreurs** : leur client gère la fermeture en
|
|
284
|
+
* interne sans l'exposer. Sur ces deux venues, la boucle silencieuse reste possible.
|
|
285
|
+
*/
|
|
286
|
+
start(symbolsXex, interval, onCandle) {
|
|
287
|
+
const ws = this.wire();
|
|
288
|
+
if ("onError" in ws) {
|
|
289
|
+
ws.onError = (error) => {
|
|
290
|
+
const raison = lisible(error);
|
|
291
|
+
this.logger.error(`${this.xex} : erreur de flux \u2014 ${raison}. Souscriptions coup\xE9es.`);
|
|
292
|
+
this.stop();
|
|
293
|
+
throw new Error(`stream(${this.xex}) : flux interrompu \u2014 ${raison}`);
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
for (const symbolXex of symbolsXex) {
|
|
297
|
+
this.unsubscribes.push(
|
|
298
|
+
ws.subscribeCandles({ name: symbolXex, interval }, (wire) => {
|
|
299
|
+
onCandle(toCandle(wire, this.xex, interval, this.quoteOf(symbolXex)));
|
|
300
|
+
})
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
this.logger.log(`${this.unsubscribes.length}/${symbolsXex.length} souscription(s) ouvertes`);
|
|
304
|
+
}
|
|
305
|
+
/** Coupe tout. Un désabonnement qui échoue ne doit pas empêcher les autres de se fermer. */
|
|
306
|
+
stop() {
|
|
307
|
+
for (const unsubscribe of this.unsubscribes.splice(0)) {
|
|
308
|
+
try {
|
|
309
|
+
unsubscribe();
|
|
310
|
+
} catch {
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/** Le nombre de souscriptions ouvertes. La liste des désabonnements EST le compteur. */
|
|
315
|
+
subscriptionCount() {
|
|
316
|
+
return this.unsubscribes.length;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Le client temps réel, sur le bon marché.
|
|
320
|
+
*
|
|
321
|
+
* Les deux contrats ne signent pas `ws()` pareil, et c'est voulu : côté comptant le marché est
|
|
322
|
+
* **obligatoire** (`ws('spot')`), parce que les façades ouvrent du perpétuel par défaut — un
|
|
323
|
+
* `ws()` nu y diffuserait des prix de perpétuel à qui croit écouter le comptant.
|
|
324
|
+
*/
|
|
325
|
+
wire() {
|
|
326
|
+
const source = this.instance();
|
|
327
|
+
return this.kind === "spot" ? source.ws("spot") : source.ws();
|
|
328
|
+
}
|
|
329
|
+
/** La façade de la venue, créée une seule fois : c'est elle qui porte la socket partagée. */
|
|
330
|
+
instance() {
|
|
331
|
+
if (this.source === null) {
|
|
332
|
+
if (this.kind === "spot" && servesSpot(this.xex) === false) {
|
|
333
|
+
throw new Error(`stream(${this.xex}) : cette venue ne sert pas de comptant.`);
|
|
334
|
+
}
|
|
335
|
+
this.source = this.kind === "spot" ? createPublicSpotXex(this.xex) : createPublicXex(this.xex);
|
|
336
|
+
}
|
|
337
|
+
return this.source;
|
|
338
|
+
}
|
|
339
|
+
/** Voir `CandlesService.quoteOf` : la cotation se déduit du symbole natif, faute d'être publiée. */
|
|
340
|
+
quoteOf(symbolXex) {
|
|
341
|
+
const parts = symbolXex.split("-");
|
|
342
|
+
const last = parts[parts.length - 1];
|
|
343
|
+
return parts.length > 1 && last !== void 0 ? last : "";
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
exports.CandlesStreamRegistry = class CandlesStreamRegistry {
|
|
347
|
+
streams = /* @__PURE__ */ new Map();
|
|
348
|
+
/** Le flux d'une venue, créé au premier appel puis réutilisé. */
|
|
349
|
+
of(xex, kind = "perp") {
|
|
350
|
+
const cle = `${xex}:${kind}`;
|
|
351
|
+
let stream = this.streams.get(cle);
|
|
352
|
+
if (stream === void 0) {
|
|
353
|
+
stream = new CandlesStreamService(xex, kind);
|
|
354
|
+
this.streams.set(cle, stream);
|
|
355
|
+
}
|
|
356
|
+
return stream;
|
|
357
|
+
}
|
|
358
|
+
/** Coupe tous les flux ouverts, toutes venues confondues. */
|
|
359
|
+
stopAll() {
|
|
360
|
+
for (const stream of this.streams.values()) {
|
|
361
|
+
stream.stop();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/** Ce qui est ouvert, par venue — pour surveiller sans avoir à tenir de compteur soi-même. */
|
|
365
|
+
counts() {
|
|
366
|
+
const total = {};
|
|
367
|
+
for (const [cle, stream] of this.streams) {
|
|
368
|
+
total[cle] = stream.subscriptionCount();
|
|
369
|
+
}
|
|
370
|
+
return total;
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
exports.CandlesStreamRegistry = __decorateClass([
|
|
374
|
+
common.Injectable()
|
|
375
|
+
], exports.CandlesStreamRegistry);
|
|
376
|
+
function lisible(error) {
|
|
377
|
+
if (error instanceof Error) {
|
|
378
|
+
return error.message;
|
|
379
|
+
}
|
|
380
|
+
const event = error;
|
|
381
|
+
if (event !== null && typeof event === "object") {
|
|
382
|
+
const message = event.message ?? event.error?.message;
|
|
383
|
+
if (typeof message === "string" && message !== "") {
|
|
384
|
+
return message;
|
|
385
|
+
}
|
|
386
|
+
if (typeof event.type === "string" && event.type !== "") {
|
|
387
|
+
return `\xE9v\xE9nement \xAB ${event.type} \xBB`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return String(error);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/enums/timeframe.enums.ts
|
|
394
|
+
var Timeframe = /* @__PURE__ */ ((Timeframe2) => {
|
|
395
|
+
Timeframe2["M1"] = "1m";
|
|
396
|
+
Timeframe2["M5"] = "5m";
|
|
397
|
+
Timeframe2["M15"] = "15m";
|
|
398
|
+
Timeframe2["H1"] = "1h";
|
|
399
|
+
Timeframe2["H4"] = "4h";
|
|
400
|
+
Timeframe2["D"] = "1d";
|
|
401
|
+
Timeframe2["W"] = "1w";
|
|
402
|
+
return Timeframe2;
|
|
403
|
+
})(Timeframe || {});
|
|
404
|
+
var TIMEFRAME_MINUTES = {
|
|
405
|
+
["1m" /* M1 */]: 1,
|
|
406
|
+
["5m" /* M5 */]: 5,
|
|
407
|
+
["15m" /* M15 */]: 15,
|
|
408
|
+
["1h" /* H1 */]: 60,
|
|
409
|
+
["4h" /* H4 */]: 240,
|
|
410
|
+
["1d" /* D */]: 1440,
|
|
411
|
+
["1w" /* W */]: 10080
|
|
412
|
+
};
|
|
413
|
+
function timeframeMs(timeframe) {
|
|
414
|
+
return TIMEFRAME_MINUTES[timeframe] * 6e4;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/helpers/decimal.ts
|
|
418
|
+
function parse(value) {
|
|
419
|
+
const trimmed = value.trim();
|
|
420
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed) === false) {
|
|
421
|
+
throw new Error(`D\xE9cimal illisible : "${value}".`);
|
|
422
|
+
}
|
|
423
|
+
const dot = trimmed.indexOf(".");
|
|
424
|
+
if (dot < 0) {
|
|
425
|
+
return { digits: BigInt(trimmed), scale: 0 };
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
digits: BigInt(trimmed.slice(0, dot) + trimmed.slice(dot + 1)),
|
|
429
|
+
scale: trimmed.length - dot - 1
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function format(digits, scale) {
|
|
433
|
+
const negative = digits < 0n;
|
|
434
|
+
const plain = (negative === true ? -digits : digits).toString().padStart(scale + 1, "0");
|
|
435
|
+
const sign = negative === true ? "-" : "";
|
|
436
|
+
if (scale === 0) {
|
|
437
|
+
return sign + plain;
|
|
438
|
+
}
|
|
439
|
+
const whole = plain.slice(0, plain.length - scale);
|
|
440
|
+
const fraction = plain.slice(plain.length - scale).replace(/0+$/, "");
|
|
441
|
+
return fraction === "" ? sign + whole : `${sign + whole}.${fraction}`;
|
|
442
|
+
}
|
|
443
|
+
function addDecimals(left, right) {
|
|
444
|
+
const a = parse(left);
|
|
445
|
+
const b = parse(right);
|
|
446
|
+
const scale = Math.max(a.scale, b.scale);
|
|
447
|
+
const digits = a.digits * 10n ** BigInt(scale - a.scale) + b.digits * 10n ** BigInt(scale - b.scale);
|
|
448
|
+
return format(digits, scale);
|
|
449
|
+
}
|
|
450
|
+
function compareDecimals(left, right) {
|
|
451
|
+
const a = parse(left);
|
|
452
|
+
const b = parse(right);
|
|
453
|
+
const scale = Math.max(a.scale, b.scale);
|
|
454
|
+
const x = a.digits * 10n ** BigInt(scale - a.scale);
|
|
455
|
+
const y = b.digits * 10n ** BigInt(scale - b.scale);
|
|
456
|
+
if (x === y) {
|
|
457
|
+
return 0;
|
|
458
|
+
}
|
|
459
|
+
return x > y ? 1 : -1;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/helpers/candle-aggregator.ts
|
|
463
|
+
var MONDAY_OFFSET_MS = 4 * 24 * 60 * 60 * 1e3;
|
|
464
|
+
function aggregate(source, target, now = Date.now()) {
|
|
465
|
+
if (source.length === 0) {
|
|
466
|
+
return [];
|
|
467
|
+
}
|
|
468
|
+
const sourceMs = timeframeMs(source[0]?.interval ?? "1m" /* M1 */);
|
|
469
|
+
const targetMs = timeframeMs(target);
|
|
470
|
+
if (targetMs <= sourceMs) {
|
|
471
|
+
return [];
|
|
472
|
+
}
|
|
473
|
+
const offsetMs = target === "1w" /* W */ ? MONDAY_OFFSET_MS : 0;
|
|
474
|
+
const expected = targetMs / sourceMs;
|
|
475
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
476
|
+
const sorted = [...source].sort(
|
|
477
|
+
(left, right) => left.openedAt.getTime() - right.openedAt.getTime()
|
|
478
|
+
);
|
|
479
|
+
for (const candle of sorted) {
|
|
480
|
+
const start = Math.floor((candle.openedAt.getTime() - offsetMs) / targetMs) * targetMs + offsetMs;
|
|
481
|
+
const bucket = buckets.get(start);
|
|
482
|
+
if (bucket === void 0) {
|
|
483
|
+
buckets.set(start, {
|
|
484
|
+
sources: 1,
|
|
485
|
+
candle: {
|
|
486
|
+
...candle,
|
|
487
|
+
interval: target,
|
|
488
|
+
openedAt: new Date(start),
|
|
489
|
+
// Dernière milliseconde du créneau, comme les bougies servies par les venues.
|
|
490
|
+
closedAt: new Date(start + targetMs - 1),
|
|
491
|
+
derived: true
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
} else {
|
|
495
|
+
bucket.sources += 1;
|
|
496
|
+
const built = bucket.candle;
|
|
497
|
+
built.high = compareDecimals(candle.high, built.high) > 0 ? candle.high : built.high;
|
|
498
|
+
built.low = compareDecimals(candle.low, built.low) < 0 ? candle.low : built.low;
|
|
499
|
+
built.close = candle.close;
|
|
500
|
+
built.volume = sum(built.volume, candle.volume);
|
|
501
|
+
built.quoteVolume = sum(built.quoteVolume, candle.quoteVolume);
|
|
502
|
+
built.takerBuyQuoteVolume = sum(built.takerBuyQuoteVolume, candle.takerBuyQuoteVolume);
|
|
503
|
+
built.trades = built.trades === null || candle.trades === null ? null : built.trades + candle.trades;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return [...buckets.keys()].sort((left, right) => left - right).map((start) => buckets.get(start)).filter((bucket) => bucket !== void 0).filter((bucket) => {
|
|
507
|
+
const stillOpen = bucket.candle.openedAt.getTime() + targetMs > now;
|
|
508
|
+
return bucket.sources >= expected || stillOpen === true;
|
|
509
|
+
}).map((bucket) => bucket.candle);
|
|
510
|
+
}
|
|
511
|
+
function sum(left, right) {
|
|
512
|
+
if (left === null || right === null) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
return addDecimals(left, right);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/helpers/candle-query.ts
|
|
519
|
+
function toXexQuery(query) {
|
|
520
|
+
return {
|
|
521
|
+
name: query.symbol,
|
|
522
|
+
interval: query.interval,
|
|
523
|
+
startTime: query.startTime,
|
|
524
|
+
endTime: query.endTime,
|
|
525
|
+
limit: query.limit
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
351
529
|
// src/helpers/xex-timeframes.ts
|
|
530
|
+
var HYPERLIQUID_NATIVE = Object.values(Timeframe).filter(
|
|
531
|
+
(interval) => interval !== "1w" /* W */
|
|
532
|
+
);
|
|
352
533
|
var NATIVE = {
|
|
353
|
-
["hyperliquid" /* Hyperliquid */]:
|
|
534
|
+
["hyperliquid" /* Hyperliquid */]: HYPERLIQUID_NATIVE,
|
|
354
535
|
["pacifica" /* Pacifica */]: Object.values(Timeframe),
|
|
355
536
|
["aster" /* Aster */]: Object.values(Timeframe),
|
|
356
537
|
["blofin" /* Blofin */]: Object.values(Timeframe),
|
|
@@ -478,6 +659,43 @@ exports.CandlesService = class CandlesService {
|
|
|
478
659
|
exports.CandlesService = __decorateClass([
|
|
479
660
|
common.Injectable()
|
|
480
661
|
], exports.CandlesService);
|
|
662
|
+
|
|
663
|
+
// src/helpers/contract-expiry.ts
|
|
664
|
+
var DATED_CONTRACT_TYPES = /* @__PURE__ */ new Set([
|
|
665
|
+
// binance : les trimestriels (`BTCUSDT_260925`, `ETHUSDT_261225`).
|
|
666
|
+
"CURRENT_QUARTER",
|
|
667
|
+
"NEXT_QUARTER",
|
|
668
|
+
"CURRENT_QUARTER_DELIVERING",
|
|
669
|
+
"NEXT_QUARTER_DELIVERING",
|
|
670
|
+
"PERPETUAL_DELIVERING",
|
|
671
|
+
// bybit : les datés (`BTCUSDT-25DEC26`), face à `LinearPerpetual`.
|
|
672
|
+
"LinearFutures",
|
|
673
|
+
"InverseFutures"
|
|
674
|
+
]);
|
|
675
|
+
var EXPIRY_FIELDS = ["deliveryTime", "deliveryDate", "expiryTime", "settleTime"];
|
|
676
|
+
function expiryOf(xtras) {
|
|
677
|
+
if (xtras === void 0) {
|
|
678
|
+
return void 0;
|
|
679
|
+
}
|
|
680
|
+
const type = xtras.contractType;
|
|
681
|
+
if (typeof type !== "string" || DATED_CONTRACT_TYPES.has(type) === false) {
|
|
682
|
+
return void 0;
|
|
683
|
+
}
|
|
684
|
+
for (const field of EXPIRY_FIELDS) {
|
|
685
|
+
const brut = xtras[field];
|
|
686
|
+
const ms = typeof brut === "string" ? Number(brut) : brut;
|
|
687
|
+
if (typeof ms === "number" && Number.isFinite(ms) === true && ms > 0) {
|
|
688
|
+
return new Date(ms);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return void 0;
|
|
692
|
+
}
|
|
693
|
+
function isDated(xtras) {
|
|
694
|
+
const type = xtras?.contractType;
|
|
695
|
+
return typeof type === "string" && DATED_CONTRACT_TYPES.has(type);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/services/catalog.service.ts
|
|
481
699
|
var NAMES_ITS_BASE = /* @__PURE__ */ new Set(["binance" /* Binance */, "bybit" /* Bybit */]);
|
|
482
700
|
exports.CatalogService = class CatalogService {
|
|
483
701
|
logger = new common.Logger(exports.CatalogService.name);
|
|
@@ -523,6 +741,8 @@ exports.CatalogService = class CatalogService {
|
|
|
523
741
|
symbolXex: pair.name,
|
|
524
742
|
xex,
|
|
525
743
|
kind: pair.kind,
|
|
744
|
+
multiplier: multiplierFromXex(pair.name),
|
|
745
|
+
expiresAt: expiryOf(pair.xtras),
|
|
526
746
|
ref: pair.ref ?? null,
|
|
527
747
|
szDecimals: pair.szDecimals,
|
|
528
748
|
tickSize: pair.tickSize,
|
|
@@ -597,6 +817,65 @@ exports.PricesService = class PricesService {
|
|
|
597
817
|
exports.PricesService = __decorateClass([
|
|
598
818
|
common.Injectable()
|
|
599
819
|
], exports.PricesService);
|
|
820
|
+
exports.SpotCandlesService = class SpotCandlesService {
|
|
821
|
+
logger = new common.Logger(exports.SpotCandlesService.name);
|
|
822
|
+
/** Les venues dont XGate sert le comptant. */
|
|
823
|
+
venues() {
|
|
824
|
+
return SPOT_XEXES;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Les bougies comptant d'une venue, sur une plage de **dates**.
|
|
828
|
+
*
|
|
829
|
+
* **Lève** si la venue ne sert pas de comptant, plutôt que de rendre une liste vide : « cette
|
|
830
|
+
* venue n'a pas de marché comptant » et « ce marché n'a pas coté » appellent des suites très
|
|
831
|
+
* différentes.
|
|
832
|
+
*/
|
|
833
|
+
async candles(xex, query) {
|
|
834
|
+
if (servesSpot(xex) === false) {
|
|
835
|
+
throw new Error(
|
|
836
|
+
`spotCandles(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(", ")}.`
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
const spot = createPublicSpotXex(xex).spot();
|
|
840
|
+
if (spot.getCandles === void 0) {
|
|
841
|
+
throw new Error(`spotCandles(${xex}) : cette venue ne sert pas de bougies comptant en REST.`);
|
|
842
|
+
}
|
|
843
|
+
const wires = await spot.getCandles(toXexQuery(query));
|
|
844
|
+
return wires.map((wire) => toCandle(wire, xex, query.interval, ""));
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Les bougies comptant d'un même marché chez PLUSIEURS venues, en une liste.
|
|
848
|
+
*
|
|
849
|
+
* Même politique d'échec que partout ailleurs : seule, une venue en échec fait échouer l'appel ;
|
|
850
|
+
* parmi d'autres, elle est journalisée et ignorée. Chaque bougie porte son `xex`.
|
|
851
|
+
*/
|
|
852
|
+
async candlesOf(xexes, query) {
|
|
853
|
+
if (xexes.length === 0) {
|
|
854
|
+
throw new Error("spotCandlesOf() : aucune venue demand\xE9e \u2014 pr\xE9cise au moins un XgateEx.");
|
|
855
|
+
}
|
|
856
|
+
const solo = xexes.length === 1;
|
|
857
|
+
const collected = await Promise.all(
|
|
858
|
+
xexes.map(async (xex) => {
|
|
859
|
+
try {
|
|
860
|
+
return await this.candles(xex, query);
|
|
861
|
+
} catch (error) {
|
|
862
|
+
if (solo === true) {
|
|
863
|
+
throw error;
|
|
864
|
+
}
|
|
865
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
866
|
+
this.logger.error(
|
|
867
|
+
`spotCandles(${xex}, ${query.interval}) a \xE9chou\xE9, venue ignor\xE9e : ${message}`
|
|
868
|
+
);
|
|
869
|
+
return [];
|
|
870
|
+
}
|
|
871
|
+
})
|
|
872
|
+
);
|
|
873
|
+
return collected.flat();
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
exports.SpotCandlesService = __decorateClass([
|
|
877
|
+
common.Injectable()
|
|
878
|
+
], exports.SpotCandlesService);
|
|
600
879
|
exports.SpotCatalogService = class SpotCatalogService {
|
|
601
880
|
logger = new common.Logger(exports.SpotCatalogService.name);
|
|
602
881
|
/** Les venues dont XGate sert le comptant. */
|
|
@@ -648,6 +927,9 @@ exports.SpotCatalogService = class SpotCatalogService {
|
|
|
648
927
|
symbolXex: pair.name,
|
|
649
928
|
xex,
|
|
650
929
|
kind: pair.kind,
|
|
930
|
+
// Le comptant porte les mêmes échelles que le perp : `SATSUSDT` (×1) et `1000SATSUSDT`
|
|
931
|
+
// coexistent, et c'est justement entre eux que la comparaison serait fausse.
|
|
932
|
+
multiplier: multiplierFromXex(pair.name),
|
|
651
933
|
ref: pair.ref ?? null,
|
|
652
934
|
szDecimals: pair.szDecimals,
|
|
653
935
|
tickSize: pair.tickSize,
|
|
@@ -665,6 +947,133 @@ exports.SpotCatalogService = class SpotCatalogService {
|
|
|
665
947
|
exports.SpotCatalogService = __decorateClass([
|
|
666
948
|
common.Injectable()
|
|
667
949
|
], exports.SpotCatalogService);
|
|
950
|
+
exports.SpotPricesService = class SpotPricesService {
|
|
951
|
+
logger = new common.Logger(exports.SpotPricesService.name);
|
|
952
|
+
/** Les venues dont XGate sert les prix comptant. */
|
|
953
|
+
venues() {
|
|
954
|
+
return SPOT_XEXES;
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Les prix comptant d'une ou plusieurs venues, en une seule liste.
|
|
958
|
+
*
|
|
959
|
+
* Même politique d'échec que partout : seule, une venue en échec fait échouer l'appel ; parmi
|
|
960
|
+
* d'autres, elle est journalisée et ignorée. Chaque prix porte son `xex`.
|
|
961
|
+
*/
|
|
962
|
+
async prices(...xexes) {
|
|
963
|
+
if (xexes.length === 0) {
|
|
964
|
+
throw new Error("spot prices() : aucune venue demand\xE9e \u2014 pr\xE9cise au moins un XgateEx.");
|
|
965
|
+
}
|
|
966
|
+
const solo = xexes.length === 1;
|
|
967
|
+
const collected = await Promise.all(
|
|
968
|
+
xexes.map(async (xex) => {
|
|
969
|
+
try {
|
|
970
|
+
return await this.pricesOf(xex);
|
|
971
|
+
} catch (error) {
|
|
972
|
+
if (solo === true) {
|
|
973
|
+
throw error;
|
|
974
|
+
}
|
|
975
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
976
|
+
this.logger.error(`spot prices(${xex}) a \xE9chou\xE9, venue ignor\xE9e : ${message}`);
|
|
977
|
+
return [];
|
|
978
|
+
}
|
|
979
|
+
})
|
|
980
|
+
);
|
|
981
|
+
return collected.flat();
|
|
982
|
+
}
|
|
983
|
+
/** Les prix comptant d'UNE venue, traduits vers {@link IPrice}. */
|
|
984
|
+
async pricesOf(xex) {
|
|
985
|
+
if (servesSpot(xex) === false) {
|
|
986
|
+
throw new Error(
|
|
987
|
+
`spot prices(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(", ")}.`
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
const spot = createPublicSpotXex(xex).spot();
|
|
991
|
+
if (spot.getPrices === void 0) {
|
|
992
|
+
throw new Error(`spot prices(${xex}) : cette venue ne sert pas de prix comptant.`);
|
|
993
|
+
}
|
|
994
|
+
const prices = await spot.getPrices();
|
|
995
|
+
return prices.map((price) => ({
|
|
996
|
+
// `canonicalFromXex`, et non `canonicalFromBase` : les prix ne portent QUE le symbole
|
|
997
|
+
// concaténé (`BTCUSDT`), jamais la base — contrairement au catalogue, où la venue déclare
|
|
998
|
+
// `baseAsset`. Passer le symbole à `canonicalFromBase` rendrait `BTCUSDT` tel quel, puisque
|
|
999
|
+
// cette fonction ne strippe volontairement aucune cotation.
|
|
1000
|
+
//
|
|
1001
|
+
// ⚠️ La limite est connue et assumée : le strip ne retire que `USDT`/`USDC` finaux, donc une
|
|
1002
|
+
// paire cotée en BTC (`ROSEBTC`, `ETHBTC`) garde son nom entier. Le catalogue comptant, lui,
|
|
1003
|
+
// résout ces cas — c'est là qu'il faut aller chercher `symbol` et `quote` justes.
|
|
1004
|
+
symbol: canonicalFromXex(price.name, xex),
|
|
1005
|
+
symbolXex: price.name,
|
|
1006
|
+
xex,
|
|
1007
|
+
kind: price.kind,
|
|
1008
|
+
mark: price.mark,
|
|
1009
|
+
oracle: price.oracle,
|
|
1010
|
+
mid: price.mid,
|
|
1011
|
+
bid: price.bid,
|
|
1012
|
+
ask: price.ask,
|
|
1013
|
+
last: price.last,
|
|
1014
|
+
funding: price.funding,
|
|
1015
|
+
openInterest: price.openInterest,
|
|
1016
|
+
volume24h: price.volume24h,
|
|
1017
|
+
prevDayPrice: price.prevDayPrice,
|
|
1018
|
+
quotedAt: price.quotedAt,
|
|
1019
|
+
xtras: price.xtras
|
|
1020
|
+
}));
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
exports.SpotPricesService = __decorateClass([
|
|
1024
|
+
common.Injectable()
|
|
1025
|
+
], exports.SpotPricesService);
|
|
1026
|
+
exports.SpotWsCandlesService = class SpotWsCandlesService {
|
|
1027
|
+
logger = new common.Logger(exports.SpotWsCandlesService.name);
|
|
1028
|
+
/** Les venues dont XGate diffuse le comptant en temps réel. */
|
|
1029
|
+
venues() {
|
|
1030
|
+
return SPOT_XEXES;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Souscrit aux bougies **comptant** d'un marché. Rend la fonction de désabonnement.
|
|
1034
|
+
*
|
|
1035
|
+
* Le handler reçoit une bougie à la fois, au format unifié ; la bougie en cours est repoussée à
|
|
1036
|
+
* chaque mise à jour tant qu'elle n'est pas close.
|
|
1037
|
+
*/
|
|
1038
|
+
subscribe(xex, query, handler) {
|
|
1039
|
+
if (servesSpot(xex) === false) {
|
|
1040
|
+
throw new Error(
|
|
1041
|
+
`spot subscribe(${xex}) : cette venue ne sert pas de comptant. Disponibles : ${SPOT_XEXES.join(", ")}.`
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
this.logger.log(`souscription bougies COMPTANT ${query.symbol} ${query.interval} sur ${xex}`);
|
|
1045
|
+
return createPublicSpotXex(xex).ws("spot").subscribeCandles({ name: query.symbol, interval: query.interval }, (wire) => {
|
|
1046
|
+
handler(toCandle(wire, xex, query.interval, ""));
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Souscrit au MÊME marché comptant chez plusieurs venues, avec un seul handler.
|
|
1051
|
+
*
|
|
1052
|
+
* Chaque bougie porte son `xex`. La fonction rendue coupe tous les flux d'un coup.
|
|
1053
|
+
*/
|
|
1054
|
+
subscribeAll(xexes, query, handler) {
|
|
1055
|
+
if (xexes.length === 0) {
|
|
1056
|
+
throw new Error("spot subscribeAll() : aucune venue demand\xE9e \u2014 pr\xE9cise au moins un XgateEx.");
|
|
1057
|
+
}
|
|
1058
|
+
const stops = [];
|
|
1059
|
+
for (const xex of xexes) {
|
|
1060
|
+
try {
|
|
1061
|
+
stops.push(this.subscribe(xex, query, handler));
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1064
|
+
this.logger.error(`souscription comptant ${xex} refus\xE9e, venue ignor\xE9e : ${message}`);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return () => {
|
|
1068
|
+
for (const stop of stops) {
|
|
1069
|
+
stop();
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
exports.SpotWsCandlesService = __decorateClass([
|
|
1075
|
+
common.Injectable()
|
|
1076
|
+
], exports.SpotWsCandlesService);
|
|
668
1077
|
|
|
669
1078
|
// src/helpers/protection.ts
|
|
670
1079
|
var SLIPPAGE_DEFAUT = 5e-3;
|
|
@@ -1209,38 +1618,52 @@ exports.XgateModule = __decorateClass([
|
|
|
1209
1618
|
providers: [
|
|
1210
1619
|
exports.CatalogService,
|
|
1211
1620
|
exports.CandlesService,
|
|
1621
|
+
exports.CandlesStreamRegistry,
|
|
1212
1622
|
exports.WsCandlesService,
|
|
1213
1623
|
exports.PricesService,
|
|
1214
1624
|
exports.SpotCatalogService,
|
|
1625
|
+
exports.SpotCandlesService,
|
|
1626
|
+
exports.SpotPricesService,
|
|
1627
|
+
exports.SpotWsCandlesService,
|
|
1215
1628
|
exports.WalletService
|
|
1216
1629
|
],
|
|
1217
1630
|
exports: [
|
|
1218
1631
|
exports.CatalogService,
|
|
1219
1632
|
exports.CandlesService,
|
|
1633
|
+
exports.CandlesStreamRegistry,
|
|
1220
1634
|
exports.WsCandlesService,
|
|
1221
1635
|
exports.PricesService,
|
|
1222
1636
|
exports.SpotCatalogService,
|
|
1637
|
+
exports.SpotCandlesService,
|
|
1638
|
+
exports.SpotPricesService,
|
|
1639
|
+
exports.SpotWsCandlesService,
|
|
1223
1640
|
exports.WalletService
|
|
1224
1641
|
]
|
|
1225
1642
|
})
|
|
1226
1643
|
], exports.XgateModule);
|
|
1227
1644
|
|
|
1645
|
+
exports.CandlesStreamService = CandlesStreamService;
|
|
1228
1646
|
exports.ORDER_STATUSES = ORDER_STATUSES;
|
|
1229
1647
|
exports.SPOT_XEXES = SPOT_XEXES;
|
|
1230
1648
|
exports.TIMEFRAME_MINUTES = TIMEFRAME_MINUTES;
|
|
1231
1649
|
exports.Timeframe = Timeframe;
|
|
1232
1650
|
exports.WALLET_XEXES = WALLET_XEXES;
|
|
1651
|
+
exports.WS_SUBSCRIPTION_LIMITS = WS_SUBSCRIPTION_LIMITS;
|
|
1233
1652
|
exports.XGATE_EXCHANGES = XGATE_EXCHANGES;
|
|
1234
1653
|
exports.XgateEx = XgateEx;
|
|
1235
1654
|
exports.buildLevels = buildLevels;
|
|
1236
1655
|
exports.buildProtection = buildProtection;
|
|
1237
1656
|
exports.canonicalFromBase = canonicalFromBase;
|
|
1238
1657
|
exports.canonicalFromXex = canonicalFromXex;
|
|
1658
|
+
exports.expiryOf = expiryOf;
|
|
1659
|
+
exports.isDated = isDated;
|
|
1660
|
+
exports.multiplierFromXex = multiplierFromXex;
|
|
1239
1661
|
exports.roundPrice = roundPrice;
|
|
1240
1662
|
exports.roundToStep = roundToStep;
|
|
1241
1663
|
exports.servesNatively = servesNatively;
|
|
1242
1664
|
exports.servesSpot = servesSpot;
|
|
1243
1665
|
exports.sourceFor = sourceFor;
|
|
1666
|
+
exports.subscriptionLimitOf = subscriptionLimitOf;
|
|
1244
1667
|
exports.timeframeMs = timeframeMs;
|
|
1245
1668
|
//# sourceMappingURL=index.cjs.map
|
|
1246
1669
|
//# sourceMappingURL=index.cjs.map
|