@usherlabs/cex-broker 0.2.24 → 0.2.25
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/dist/commands/cli.js +323 -4
- package/dist/helpers/account-balance-archive-poller.d.ts +19 -0
- package/dist/helpers/broker-execution-archive/index.d.ts +3 -3
- package/dist/helpers/broker-execution-archive/rows.d.ts +18 -0
- package/dist/helpers/broker-execution-archive/types.d.ts +3 -1
- package/dist/helpers/broker-execution-archive/writer.d.ts +3 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +324 -5
- package/dist/index.js.map +9 -8
- package/package.json +1 -1
package/dist/commands/cli.js
CHANGED
|
@@ -315208,6 +315208,9 @@ function compactUndefined(record) {
|
|
|
315208
315208
|
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
315209
315209
|
}
|
|
315210
315210
|
|
|
315211
|
+
// src/helpers/broker-execution-archive/rows.ts
|
|
315212
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
315213
|
+
|
|
315211
315214
|
// src/helpers/broker-execution-archive/redact.ts
|
|
315212
315215
|
import { createHash } from "node:crypto";
|
|
315213
315216
|
var SECRET_KEY_PATTERN = /\b(api[_-]?key|api[_-]?secret|secret|signature|passphrase|password|token|credential)\b/i;
|
|
@@ -315280,6 +315283,8 @@ function hashMarketMetadata(payload) {
|
|
|
315280
315283
|
var BROKER_WRITE_SOURCE = "broker_write";
|
|
315281
315284
|
var ARCHIVE_SCHEMA_VERSION = "1";
|
|
315282
315285
|
var FILL_EVENT_KIND = "trade_history_fill";
|
|
315286
|
+
var ACCOUNT_BALANCE_SCOPE = "spot";
|
|
315287
|
+
var ACCOUNT_BALANCE_PRECISION_BASIS = "ccxt_normalized_number";
|
|
315283
315288
|
|
|
315284
315289
|
// src/helpers/broker-execution-archive/rows.ts
|
|
315285
315290
|
function firstString2(...values2) {
|
|
@@ -315313,6 +315318,151 @@ function normalizeTimestamp2(value) {
|
|
|
315313
315318
|
const date = new Date(ms);
|
|
315314
315319
|
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
315315
315320
|
}
|
|
315321
|
+
function decimalString(value) {
|
|
315322
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
315323
|
+
return;
|
|
315324
|
+
}
|
|
315325
|
+
if (Object.is(value, -0)) {
|
|
315326
|
+
return "0";
|
|
315327
|
+
}
|
|
315328
|
+
const rendered = String(value).toLowerCase();
|
|
315329
|
+
if (!rendered.includes("e")) {
|
|
315330
|
+
return rendered;
|
|
315331
|
+
}
|
|
315332
|
+
const [coefficient = "", rawExponent = "0"] = rendered.split("e");
|
|
315333
|
+
const exponent = Number.parseInt(rawExponent, 10);
|
|
315334
|
+
const negative = coefficient.startsWith("-");
|
|
315335
|
+
const unsigned = negative ? coefficient.slice(1) : coefficient;
|
|
315336
|
+
const [integer = "0", fraction = ""] = unsigned.split(".");
|
|
315337
|
+
const digits = `${integer}${fraction}`;
|
|
315338
|
+
const decimalIndex = integer.length + exponent;
|
|
315339
|
+
let expanded;
|
|
315340
|
+
if (decimalIndex <= 0) {
|
|
315341
|
+
expanded = `0.${"0".repeat(-decimalIndex)}${digits}`;
|
|
315342
|
+
} else if (decimalIndex >= digits.length) {
|
|
315343
|
+
expanded = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
|
|
315344
|
+
} else {
|
|
315345
|
+
expanded = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
|
|
315346
|
+
}
|
|
315347
|
+
return negative ? `-${expanded}` : expanded;
|
|
315348
|
+
}
|
|
315349
|
+
function normalizedBalanceMap(value) {
|
|
315350
|
+
const record = asRecord(value);
|
|
315351
|
+
if (!record) {
|
|
315352
|
+
return {};
|
|
315353
|
+
}
|
|
315354
|
+
const entries = [];
|
|
315355
|
+
for (const [rawAsset, quantity] of Object.entries(record)) {
|
|
315356
|
+
const asset = rawAsset.trim();
|
|
315357
|
+
const normalized = decimalString(quantity);
|
|
315358
|
+
if (asset && normalized !== undefined) {
|
|
315359
|
+
entries.push([asset, normalized]);
|
|
315360
|
+
}
|
|
315361
|
+
}
|
|
315362
|
+
return Object.fromEntries(entries.sort(([left], [right]) => left.localeCompare(right)));
|
|
315363
|
+
}
|
|
315364
|
+
function sortedBalanceMap(map) {
|
|
315365
|
+
return Object.fromEntries(Object.entries(map).sort(([left], [right]) => left.localeCompare(right)));
|
|
315366
|
+
}
|
|
315367
|
+
var BALANCE_METADATA_KEYS = new Set([
|
|
315368
|
+
"info",
|
|
315369
|
+
"timestamp",
|
|
315370
|
+
"datetime",
|
|
315371
|
+
"free",
|
|
315372
|
+
"used",
|
|
315373
|
+
"total",
|
|
315374
|
+
"debt"
|
|
315375
|
+
]);
|
|
315376
|
+
function normalizeCcxtBalanceForArchive(balance) {
|
|
315377
|
+
const record = asRecord(balance) ?? {};
|
|
315378
|
+
const aggregateRecords = {
|
|
315379
|
+
free: asRecord(record.free),
|
|
315380
|
+
used: asRecord(record.used),
|
|
315381
|
+
total: asRecord(record.total)
|
|
315382
|
+
};
|
|
315383
|
+
const maps = {
|
|
315384
|
+
free: normalizedBalanceMap(record.free),
|
|
315385
|
+
used: normalizedBalanceMap(record.used),
|
|
315386
|
+
total: normalizedBalanceMap(record.total)
|
|
315387
|
+
};
|
|
315388
|
+
const assetEntryAssets = new Set;
|
|
315389
|
+
for (const [rawAsset, value] of Object.entries(record)) {
|
|
315390
|
+
if (BALANCE_METADATA_KEYS.has(rawAsset)) {
|
|
315391
|
+
continue;
|
|
315392
|
+
}
|
|
315393
|
+
const asset = rawAsset.trim();
|
|
315394
|
+
const assetEntry = asRecord(value);
|
|
315395
|
+
if (!asset || !assetEntry || !(("free" in assetEntry) || ("used" in assetEntry) || ("total" in assetEntry))) {
|
|
315396
|
+
continue;
|
|
315397
|
+
}
|
|
315398
|
+
assetEntryAssets.add(asset);
|
|
315399
|
+
for (const field of ["free", "used", "total"]) {
|
|
315400
|
+
const normalized = decimalString(assetEntry[field]);
|
|
315401
|
+
if (normalized !== undefined && maps[field][asset] === undefined) {
|
|
315402
|
+
maps[field][asset] = normalized;
|
|
315403
|
+
}
|
|
315404
|
+
}
|
|
315405
|
+
}
|
|
315406
|
+
const reportedAssets = new Set(assetEntryAssets);
|
|
315407
|
+
for (const aggregateRecord of Object.values(aggregateRecords)) {
|
|
315408
|
+
for (const rawAsset of Object.keys(aggregateRecord ?? {})) {
|
|
315409
|
+
const asset = rawAsset.trim();
|
|
315410
|
+
if (asset) {
|
|
315411
|
+
reportedAssets.add(asset);
|
|
315412
|
+
}
|
|
315413
|
+
}
|
|
315414
|
+
}
|
|
315415
|
+
for (const map of Object.values(maps)) {
|
|
315416
|
+
for (const asset of Object.keys(map)) {
|
|
315417
|
+
reportedAssets.add(asset);
|
|
315418
|
+
}
|
|
315419
|
+
}
|
|
315420
|
+
return {
|
|
315421
|
+
exchangeTimestamp: normalizeTimestamp2(record.timestamp ?? record.datetime),
|
|
315422
|
+
reportedAssets: [...reportedAssets].sort(),
|
|
315423
|
+
assetEntryAssets: [...assetEntryAssets].sort(),
|
|
315424
|
+
freeBalances: sortedBalanceMap(maps.free),
|
|
315425
|
+
usedBalances: sortedBalanceMap(maps.used),
|
|
315426
|
+
totalBalances: sortedBalanceMap(maps.total),
|
|
315427
|
+
freeMapPresent: aggregateRecords.free !== undefined,
|
|
315428
|
+
usedMapPresent: aggregateRecords.used !== undefined,
|
|
315429
|
+
totalMapPresent: aggregateRecords.total !== undefined
|
|
315430
|
+
};
|
|
315431
|
+
}
|
|
315432
|
+
function buildAccountBalanceSnapshotRow(input) {
|
|
315433
|
+
const { tags, balance } = input;
|
|
315434
|
+
const observationId = createHash2("sha256").update(JSON.stringify({
|
|
315435
|
+
deployment_id: tags.deployment_id,
|
|
315436
|
+
exchange: tags.exchange,
|
|
315437
|
+
account_selector: tags.account_selector,
|
|
315438
|
+
balance_scope: ACCOUNT_BALANCE_SCOPE,
|
|
315439
|
+
broker_observed_timestamp: tags.broker_observed_timestamp,
|
|
315440
|
+
balance
|
|
315441
|
+
})).digest("hex");
|
|
315442
|
+
return {
|
|
315443
|
+
table: "broker_account.balance_snapshots",
|
|
315444
|
+
row: compactUndefined2({
|
|
315445
|
+
broker_observed_timestamp: tags.broker_observed_timestamp,
|
|
315446
|
+
exchange_timestamp: balance.exchangeTimestamp,
|
|
315447
|
+
source: tags.source,
|
|
315448
|
+
deployment_id: tags.deployment_id,
|
|
315449
|
+
schema_version: ARCHIVE_SCHEMA_VERSION,
|
|
315450
|
+
exchange: tags.exchange,
|
|
315451
|
+
account_selector: tags.account_selector,
|
|
315452
|
+
balance_scope: ACCOUNT_BALANCE_SCOPE,
|
|
315453
|
+
observation_id: observationId,
|
|
315454
|
+
reported_assets: balance.reportedAssets,
|
|
315455
|
+
asset_entry_assets: balance.assetEntryAssets,
|
|
315456
|
+
free_balances: balance.freeBalances,
|
|
315457
|
+
used_balances: balance.usedBalances,
|
|
315458
|
+
total_balances: balance.totalBalances,
|
|
315459
|
+
aggregate_free_map_present: Number(balance.freeMapPresent),
|
|
315460
|
+
aggregate_used_map_present: Number(balance.usedMapPresent),
|
|
315461
|
+
aggregate_total_map_present: Number(balance.totalMapPresent),
|
|
315462
|
+
precision_basis: ACCOUNT_BALANCE_PRECISION_BASIS
|
|
315463
|
+
})
|
|
315464
|
+
};
|
|
315465
|
+
}
|
|
315316
315466
|
function compactUndefined2(record) {
|
|
315317
315467
|
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
315318
315468
|
}
|
|
@@ -315766,6 +315916,15 @@ function isMarketArchiveTable(table) {
|
|
|
315766
315916
|
}
|
|
315767
315917
|
|
|
315768
315918
|
// src/helpers/broker-execution-archive/writer.ts
|
|
315919
|
+
var BROKER_EXECUTION_ARCHIVE_TABLES = new Set([
|
|
315920
|
+
"broker_execution.order_events",
|
|
315921
|
+
"broker_execution.market_metadata_snapshots",
|
|
315922
|
+
"broker_execution.transfer_events",
|
|
315923
|
+
"broker_execution.fill_events"
|
|
315924
|
+
]);
|
|
315925
|
+
function isBrokerExecutionArchiveTable(table) {
|
|
315926
|
+
return BROKER_EXECUTION_ARCHIVE_TABLES.has(table);
|
|
315927
|
+
}
|
|
315769
315928
|
var DEFAULT_MAX_QUEUE_SIZE = 1e4;
|
|
315770
315929
|
var DEFAULT_BATCH_SIZE = 10;
|
|
315771
315930
|
var DEFAULT_FLUSH_INTERVAL_MS = 1000;
|
|
@@ -315850,6 +316009,9 @@ class BrokerExecutionArchiver {
|
|
|
315850
316009
|
canPersistMarketMetadataSnapshot() {
|
|
315851
316010
|
return this.isEnabled() && (Boolean(this.otelLogs?.isOtelEnabled()) || Boolean(this.forwarderUrl));
|
|
315852
316011
|
}
|
|
316012
|
+
canPersistAccountBalanceSnapshots() {
|
|
316013
|
+
return this.isEnabled() && Boolean(this.forwarderUrl);
|
|
316014
|
+
}
|
|
315853
316015
|
enqueue(row) {
|
|
315854
316016
|
if (!this.enabled || this.closed || !this.otelLogs && !this.forwarderUrl) {
|
|
315855
316017
|
return;
|
|
@@ -315861,6 +316023,9 @@ class BrokerExecutionArchiver {
|
|
|
315861
316023
|
}
|
|
315862
316024
|
return;
|
|
315863
316025
|
}
|
|
316026
|
+
if (row.table === "broker_account.balance_snapshots" && !this.forwarderUrl) {
|
|
316027
|
+
return;
|
|
316028
|
+
}
|
|
315864
316029
|
if (this.queue.length >= this.maxQueueSize) {
|
|
315865
316030
|
this.queue.shift();
|
|
315866
316031
|
this.stats.shed += 1;
|
|
@@ -315939,7 +316104,7 @@ class BrokerExecutionArchiver {
|
|
|
315939
316104
|
return true;
|
|
315940
316105
|
}
|
|
315941
316106
|
for (const entry of batch) {
|
|
315942
|
-
if (
|
|
316107
|
+
if (isBrokerExecutionArchiveTable(entry.table)) {
|
|
315943
316108
|
this.emitOtelLog(entry);
|
|
315944
316109
|
}
|
|
315945
316110
|
}
|
|
@@ -316075,8 +316240,138 @@ function parsePositiveInt(value, fallback) {
|
|
|
316075
316240
|
const parsed = Number.parseInt(value, 10);
|
|
316076
316241
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
316077
316242
|
}
|
|
316078
|
-
// src/helpers/
|
|
316243
|
+
// src/helpers/account-balance-archive-poller.ts
|
|
316079
316244
|
var DEFAULT_CONFIG = {
|
|
316245
|
+
pollIntervalMs: 60000
|
|
316246
|
+
};
|
|
316247
|
+
var metricLabels = (target) => ({
|
|
316248
|
+
exchange: target.exchangeId,
|
|
316249
|
+
account_selector: target.account.label,
|
|
316250
|
+
balance_scope: ACCOUNT_BALANCE_SCOPE
|
|
316251
|
+
});
|
|
316252
|
+
|
|
316253
|
+
class AccountBalanceArchivePoller {
|
|
316254
|
+
params;
|
|
316255
|
+
#timer = null;
|
|
316256
|
+
#stopped = false;
|
|
316257
|
+
#running = null;
|
|
316258
|
+
#lastSuccessMs = new Map;
|
|
316259
|
+
#config;
|
|
316260
|
+
constructor(params) {
|
|
316261
|
+
this.params = params;
|
|
316262
|
+
this.#config = { ...DEFAULT_CONFIG, ...params.config };
|
|
316263
|
+
}
|
|
316264
|
+
start() {
|
|
316265
|
+
if (this.#timer || this.#stopped || !this.params.archiver.canPersistAccountBalanceSnapshots()) {
|
|
316266
|
+
return;
|
|
316267
|
+
}
|
|
316268
|
+
log.info("\uD83D\uDCB0 Account balance archive poller started", {
|
|
316269
|
+
balanceScope: ACCOUNT_BALANCE_SCOPE
|
|
316270
|
+
});
|
|
316271
|
+
this.#schedule(0);
|
|
316272
|
+
}
|
|
316273
|
+
async stop() {
|
|
316274
|
+
this.#stopped = true;
|
|
316275
|
+
if (this.#timer) {
|
|
316276
|
+
clearTimeout(this.#timer);
|
|
316277
|
+
this.#timer = null;
|
|
316278
|
+
}
|
|
316279
|
+
await this.#running;
|
|
316280
|
+
}
|
|
316281
|
+
async pollAllOnce() {
|
|
316282
|
+
if (this.#stopped || this.#running || !this.params.archiver.canPersistAccountBalanceSnapshots()) {
|
|
316283
|
+
return false;
|
|
316284
|
+
}
|
|
316285
|
+
this.#running = this.#pollAllSequentially();
|
|
316286
|
+
try {
|
|
316287
|
+
return await this.#running;
|
|
316288
|
+
} finally {
|
|
316289
|
+
this.#running = null;
|
|
316290
|
+
}
|
|
316291
|
+
}
|
|
316292
|
+
#targets() {
|
|
316293
|
+
const targets = [];
|
|
316294
|
+
for (const [exchangeId, pool] of Object.entries(this.params.brokers)) {
|
|
316295
|
+
for (const account of [pool.primary, ...pool.secondaryBrokers]) {
|
|
316296
|
+
targets.push({ exchangeId, account });
|
|
316297
|
+
}
|
|
316298
|
+
}
|
|
316299
|
+
return targets;
|
|
316300
|
+
}
|
|
316301
|
+
async#pollAllSequentially() {
|
|
316302
|
+
for (const target of this.#targets()) {
|
|
316303
|
+
if (this.#stopped) {
|
|
316304
|
+
break;
|
|
316305
|
+
}
|
|
316306
|
+
await this.#pollOne(target);
|
|
316307
|
+
}
|
|
316308
|
+
return true;
|
|
316309
|
+
}
|
|
316310
|
+
async#pollOne(target) {
|
|
316311
|
+
const labels = metricLabels(target);
|
|
316312
|
+
this.params.metrics?.recordCounter("cex_account_balance_poll_attempts_total", 1, labels);
|
|
316313
|
+
try {
|
|
316314
|
+
const exchange = target.account.exchange;
|
|
316315
|
+
const balance = await exchange.fetchBalance({
|
|
316316
|
+
type: ACCOUNT_BALANCE_SCOPE
|
|
316317
|
+
});
|
|
316318
|
+
const observedAt = new Date;
|
|
316319
|
+
const normalized = normalizeCcxtBalanceForArchive(balance);
|
|
316320
|
+
this.params.archiver.enqueue(buildAccountBalanceSnapshotRow({
|
|
316321
|
+
tags: buildCommonArchiveTags({
|
|
316322
|
+
deploymentId: this.params.archiver.getDeploymentId(),
|
|
316323
|
+
accountSelector: target.account.label,
|
|
316324
|
+
exchange: target.exchangeId,
|
|
316325
|
+
brokerObservedTimestamp: observedAt.toISOString()
|
|
316326
|
+
}),
|
|
316327
|
+
balance: normalized
|
|
316328
|
+
}));
|
|
316329
|
+
const successMs = Date.now();
|
|
316330
|
+
this.#lastSuccessMs.set(this.#targetKey(target), successMs);
|
|
316331
|
+
this.params.metrics?.recordCounter("cex_account_balance_poll_successes_total", 1, labels);
|
|
316332
|
+
this.params.metrics?.recordGauge("cex_account_balance_poll_last_success_timestamp_seconds", Math.floor(successMs / 1000), labels);
|
|
316333
|
+
this.#recordFreshness(labels, successMs, successMs);
|
|
316334
|
+
} catch (error) {
|
|
316335
|
+
this.params.metrics?.recordCounter("cex_account_balance_poll_failures_total", 1, labels);
|
|
316336
|
+
const now3 = Date.now();
|
|
316337
|
+
const lastSuccess = this.#lastSuccessMs.get(this.#targetKey(target));
|
|
316338
|
+
if (lastSuccess !== undefined) {
|
|
316339
|
+
this.#recordFreshness(labels, lastSuccess, now3);
|
|
316340
|
+
}
|
|
316341
|
+
log.warn("Account balance archive poll failed", {
|
|
316342
|
+
exchange: target.exchangeId,
|
|
316343
|
+
account: target.account.label,
|
|
316344
|
+
balanceScope: ACCOUNT_BALANCE_SCOPE,
|
|
316345
|
+
errorType: error instanceof Error ? error.name : "unknown"
|
|
316346
|
+
});
|
|
316347
|
+
}
|
|
316348
|
+
}
|
|
316349
|
+
#recordFreshness(labels, lastSuccessMs, nowMs) {
|
|
316350
|
+
this.params.metrics?.recordGauge("cex_account_balance_poll_freshness_seconds", Math.max(0, (nowMs - lastSuccessMs) / 1000), labels);
|
|
316351
|
+
}
|
|
316352
|
+
#targetKey(target) {
|
|
316353
|
+
return `${target.exchangeId}|${target.account.label}|${ACCOUNT_BALANCE_SCOPE}`;
|
|
316354
|
+
}
|
|
316355
|
+
#schedule(delayMs) {
|
|
316356
|
+
this.#timer = setTimeout(() => void this.#tick(), delayMs);
|
|
316357
|
+
this.#timer.unref?.();
|
|
316358
|
+
}
|
|
316359
|
+
async#tick() {
|
|
316360
|
+
this.#timer = null;
|
|
316361
|
+
try {
|
|
316362
|
+
await this.pollAllOnce();
|
|
316363
|
+
} catch (error) {
|
|
316364
|
+
log.error("Account balance archive poller tick failed", error);
|
|
316365
|
+
} finally {
|
|
316366
|
+
if (!this.#stopped) {
|
|
316367
|
+
this.#schedule(this.#config.pollIntervalMs);
|
|
316368
|
+
}
|
|
316369
|
+
}
|
|
316370
|
+
}
|
|
316371
|
+
}
|
|
316372
|
+
|
|
316373
|
+
// src/helpers/fill-archive-poller.ts
|
|
316374
|
+
var DEFAULT_CONFIG2 = {
|
|
316080
316375
|
pollIntervalMs: 60000,
|
|
316081
316376
|
lookbackMs: 24 * 60 * 60 * 1000,
|
|
316082
316377
|
tradesLimit: 500
|
|
@@ -316101,7 +316396,7 @@ class FillArchivePoller {
|
|
|
316101
316396
|
#config;
|
|
316102
316397
|
constructor(params) {
|
|
316103
316398
|
this.params = params;
|
|
316104
|
-
this.#config = { ...
|
|
316399
|
+
this.#config = { ...DEFAULT_CONFIG2, ...params.config };
|
|
316105
316400
|
}
|
|
316106
316401
|
start() {
|
|
316107
316402
|
if (this.#timer || this.#stopped) {
|
|
@@ -332134,6 +332429,7 @@ import { createHmac } from "node:crypto";
|
|
|
332134
332429
|
var BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
|
|
332135
332430
|
var DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
|
|
332136
332431
|
var createWebSocket = (url3) => new wrapper_default(url3);
|
|
332432
|
+
var userDataRequestCounter = 0;
|
|
332137
332433
|
function getExchangeString(exchange, key) {
|
|
332138
332434
|
const value = exchange[key];
|
|
332139
332435
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -332240,7 +332536,7 @@ class BinanceSpotUserDataStream {
|
|
|
332240
332536
|
exchange;
|
|
332241
332537
|
ws;
|
|
332242
332538
|
secretValues;
|
|
332243
|
-
requestId = `user-data-${Date.now()}-${
|
|
332539
|
+
requestId = `user-data-${Date.now()}-${userDataRequestCounter++}`;
|
|
332244
332540
|
maxBufferedEvents;
|
|
332245
332541
|
queue = [];
|
|
332246
332542
|
waiters = [];
|
|
@@ -332318,6 +332614,12 @@ class BinanceSpotUserDataStream {
|
|
|
332318
332614
|
this.subscriptionId = message.result?.subscriptionId ?? null;
|
|
332319
332615
|
return;
|
|
332320
332616
|
}
|
|
332617
|
+
if ("status" in message && typeof message.status === "number" && message.status !== 200) {
|
|
332618
|
+
const errorMessage = message.error?.msg ?? message.error?.message ?? `Binance user-data request failed with status ${message.status}`;
|
|
332619
|
+
const errorCode = message.error?.code;
|
|
332620
|
+
this.fail(new Error(typeof errorCode === "number" ? `${errorMessage} (code ${errorCode})` : errorMessage));
|
|
332621
|
+
return;
|
|
332622
|
+
}
|
|
332321
332623
|
if (!("event" in message) || !message.event) {
|
|
332322
332624
|
return;
|
|
332323
332625
|
}
|
|
@@ -333848,6 +334150,7 @@ class CEXBroker {
|
|
|
333848
334150
|
orderActivityTracker = new OrderActivityTracker;
|
|
333849
334151
|
withdrawalObservationTracker = new WithdrawalObservationTracker;
|
|
333850
334152
|
fillArchivePoller;
|
|
334153
|
+
accountBalanceArchivePoller;
|
|
333851
334154
|
loadEnvConfig() {
|
|
333852
334155
|
log.info("\uD83D\uDD27 Loading CEX_BROKER_ environment variables:");
|
|
333853
334156
|
const configMap = {};
|
|
@@ -333992,6 +334295,10 @@ class CEXBroker {
|
|
|
333992
334295
|
this.fillArchivePoller.stop();
|
|
333993
334296
|
this.fillArchivePoller = undefined;
|
|
333994
334297
|
}
|
|
334298
|
+
if (this.accountBalanceArchivePoller) {
|
|
334299
|
+
await this.accountBalanceArchivePoller.stop();
|
|
334300
|
+
this.accountBalanceArchivePoller = undefined;
|
|
334301
|
+
}
|
|
333995
334302
|
if (this.server) {
|
|
333996
334303
|
await this.server.forceShutdown();
|
|
333997
334304
|
}
|
|
@@ -334017,6 +334324,10 @@ class CEXBroker {
|
|
|
334017
334324
|
this.fillArchivePoller.stop();
|
|
334018
334325
|
this.fillArchivePoller = undefined;
|
|
334019
334326
|
}
|
|
334327
|
+
if (this.accountBalanceArchivePoller) {
|
|
334328
|
+
await this.accountBalanceArchivePoller.stop();
|
|
334329
|
+
this.accountBalanceArchivePoller = undefined;
|
|
334330
|
+
}
|
|
334020
334331
|
log.info(`Running CEXBroker at ${new Date().toISOString()}`);
|
|
334021
334332
|
if (this.otelMetrics?.isOtelEnabled()) {
|
|
334022
334333
|
await this.otelMetrics.initialize();
|
|
@@ -334045,6 +334356,14 @@ class CEXBroker {
|
|
|
334045
334356
|
});
|
|
334046
334357
|
this.fillArchivePoller.start();
|
|
334047
334358
|
}
|
|
334359
|
+
if (this.brokerArchiver?.canPersistAccountBalanceSnapshots()) {
|
|
334360
|
+
this.accountBalanceArchivePoller = new AccountBalanceArchivePoller({
|
|
334361
|
+
brokers: this.brokers,
|
|
334362
|
+
archiver: this.brokerArchiver,
|
|
334363
|
+
metrics: this.otelMetrics
|
|
334364
|
+
});
|
|
334365
|
+
this.accountBalanceArchivePoller.start();
|
|
334366
|
+
}
|
|
334048
334367
|
return this;
|
|
334049
334368
|
}
|
|
334050
334369
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BrokerPoolEntry } from "./broker";
|
|
2
|
+
import { type BrokerExecutionArchiver } from "./broker-execution-archive";
|
|
3
|
+
import type { OtelMetrics } from "./otel";
|
|
4
|
+
export type AccountBalanceArchivePollerConfig = {
|
|
5
|
+
pollIntervalMs: number;
|
|
6
|
+
};
|
|
7
|
+
export declare class AccountBalanceArchivePoller {
|
|
8
|
+
#private;
|
|
9
|
+
private readonly params;
|
|
10
|
+
constructor(params: {
|
|
11
|
+
brokers: Record<string, BrokerPoolEntry>;
|
|
12
|
+
archiver: BrokerExecutionArchiver;
|
|
13
|
+
metrics?: OtelMetrics;
|
|
14
|
+
config?: Partial<AccountBalanceArchivePollerConfig>;
|
|
15
|
+
});
|
|
16
|
+
start(): void;
|
|
17
|
+
stop(): Promise<void>;
|
|
18
|
+
pollAllOnce(): Promise<boolean>;
|
|
19
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { archiveOrderExecutionInBackground, archiveSubscribeStreamInBackground, archiveTransferEventInBackground, archiveWithdrawalObservationsInBackground, captureMarketMetadataSnapshot, captureMarketMetadataSnapshotInBackground, } from "./capture";
|
|
2
2
|
export { hashMarketMetadata, redactErrorForArchive, redactSecretLiterals, redactStreamPayload, } from "./redact";
|
|
3
|
-
export { buildCommonArchiveTags, buildFillEventArchiveRow, buildMarketMetadataSnapshotRow, buildOrderEventArchiveRow, buildSubscribeStreamArchiveRow, buildTransferEventArchiveRow, type FillArchiveFields, type NormalizedCcxtTransfer, normalizeCcxtTradeForArchive, normalizeCcxtTransactionForArchive, type TransferArchiveFields, } from "./rows";
|
|
4
|
-
export { ARCHIVE_SCHEMA_VERSION, BROKER_WRITE_SOURCE, type BrokerArchiveCommonTags, type BrokerArchiveRow, type BrokerArchiveTable, type OrderArchiveAction, type SubscribeArchiveType, type TransferEventKind, type TransferLifecycleAction, } from "./types";
|
|
3
|
+
export { buildAccountBalanceSnapshotRow, buildCommonArchiveTags, buildFillEventArchiveRow, buildMarketMetadataSnapshotRow, buildOrderEventArchiveRow, buildSubscribeStreamArchiveRow, buildTransferEventArchiveRow, type FillArchiveFields, type NormalizedCcxtBalance, type NormalizedCcxtTransfer, normalizeCcxtBalanceForArchive, normalizeCcxtTradeForArchive, normalizeCcxtTransactionForArchive, type TransferArchiveFields, } from "./rows";
|
|
4
|
+
export { ACCOUNT_BALANCE_PRECISION_BASIS, ACCOUNT_BALANCE_SCOPE, ARCHIVE_SCHEMA_VERSION, BROKER_WRITE_SOURCE, type BrokerArchiveCommonTags, type BrokerArchiveRow, type BrokerArchiveTable, type OrderArchiveAction, type SubscribeArchiveType, type TransferEventKind, type TransferLifecycleAction, } from "./types";
|
|
5
5
|
export { DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES, WithdrawalObservationTracker, } from "./withdrawal-observation-tracker";
|
|
6
|
-
export { BrokerExecutionArchiver, type BrokerExecutionArchiverOptions, createBrokerExecutionArchiverFromEnv, isArchiveOtelLogsEnabled, resolveArchiveForwarderUrlFromEnv, } from "./writer";
|
|
6
|
+
export { BrokerExecutionArchiver, type BrokerExecutionArchiverOptions, createBrokerExecutionArchiverFromEnv, isArchiveOtelLogsEnabled, isBrokerExecutionArchiveTable, resolveArchiveForwarderUrlFromEnv, } from "./writer";
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { OrderExecutionTelemetry } from "../order-telemetry";
|
|
2
2
|
import { type BrokerArchiveCommonTags, type BrokerArchiveRow, type OrderArchiveAction, type SubscribeArchiveType, type TransferEventKind, type TransferLifecycleAction } from "./types";
|
|
3
|
+
type BalanceQuantityMap = Record<string, string>;
|
|
4
|
+
export type NormalizedCcxtBalance = {
|
|
5
|
+
exchangeTimestamp?: string;
|
|
6
|
+
reportedAssets: string[];
|
|
7
|
+
assetEntryAssets: string[];
|
|
8
|
+
freeBalances: BalanceQuantityMap;
|
|
9
|
+
usedBalances: BalanceQuantityMap;
|
|
10
|
+
totalBalances: BalanceQuantityMap;
|
|
11
|
+
freeMapPresent: boolean;
|
|
12
|
+
usedMapPresent: boolean;
|
|
13
|
+
totalMapPresent: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare function normalizeCcxtBalanceForArchive(balance: unknown): NormalizedCcxtBalance;
|
|
16
|
+
export declare function buildAccountBalanceSnapshotRow(input: {
|
|
17
|
+
tags: BrokerArchiveCommonTags;
|
|
18
|
+
balance: NormalizedCcxtBalance;
|
|
19
|
+
}): BrokerArchiveRow;
|
|
3
20
|
export declare function buildCommonArchiveTags(input: {
|
|
4
21
|
deploymentId: string;
|
|
5
22
|
accountSelector?: string;
|
|
@@ -83,3 +100,4 @@ export declare function buildMarketMetadataSnapshotRow(input: {
|
|
|
83
100
|
idempotencyId?: string;
|
|
84
101
|
marketSnapshot: unknown;
|
|
85
102
|
}): BrokerArchiveRow;
|
|
103
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const BROKER_WRITE_SOURCE: "broker_write";
|
|
2
2
|
export declare const ARCHIVE_SCHEMA_VERSION: "1";
|
|
3
|
-
export type BrokerArchiveTable = "broker_execution.order_events" | "broker_execution.market_metadata_snapshots" | "broker_execution.transfer_events" | "broker_execution.fill_events" | "market_data.orderbook_snapshots" | "market_data.candles" | "market_data.cex_stream_events" | "market_data.cex_ticker_events" | "market_data.cex_trades";
|
|
3
|
+
export type BrokerArchiveTable = "broker_execution.order_events" | "broker_execution.market_metadata_snapshots" | "broker_execution.transfer_events" | "broker_execution.fill_events" | "broker_account.balance_snapshots" | "market_data.orderbook_snapshots" | "market_data.candles" | "market_data.cex_stream_events" | "market_data.cex_ticker_events" | "market_data.cex_trades";
|
|
4
4
|
export type BrokerArchiveRow = {
|
|
5
5
|
table: BrokerArchiveTable;
|
|
6
6
|
row: Record<string, unknown>;
|
|
@@ -18,3 +18,5 @@ export type SubscribeArchiveType = "ORDERS" | "BALANCE";
|
|
|
18
18
|
export type TransferEventKind = "withdrawal" | "deposit" | "internal_transfer";
|
|
19
19
|
export type TransferLifecycleAction = "submit_withdrawal" | "observe_withdrawal" | "observe_deposit" | "submit_internal_transfer";
|
|
20
20
|
export declare const FILL_EVENT_KIND: "trade_history_fill";
|
|
21
|
+
export declare const ACCOUNT_BALANCE_SCOPE: "spot";
|
|
22
|
+
export declare const ACCOUNT_BALANCE_PRECISION_BASIS: "ccxt_normalized_number";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OtelLogs, OtelMetrics } from "../otel";
|
|
2
|
-
import type { BrokerArchiveRow } from "./types";
|
|
2
|
+
import type { BrokerArchiveRow, BrokerArchiveTable } from "./types";
|
|
3
|
+
export declare function isBrokerExecutionArchiveTable(table: BrokerArchiveTable): boolean;
|
|
3
4
|
export type BrokerExecutionArchiverOptions = {
|
|
4
5
|
deploymentId?: string;
|
|
5
6
|
otelLogs?: OtelLogs;
|
|
@@ -41,6 +42,7 @@ export declare class BrokerExecutionArchiver {
|
|
|
41
42
|
getDeploymentId(): string;
|
|
42
43
|
isEnabled(): boolean;
|
|
43
44
|
canPersistMarketMetadataSnapshot(): boolean;
|
|
45
|
+
canPersistAccountBalanceSnapshots(): boolean;
|
|
44
46
|
enqueue(row: BrokerArchiveRow): void;
|
|
45
47
|
enqueueInBackground(row: BrokerArchiveRow): void;
|
|
46
48
|
flush(): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export default class CEXBroker {
|
|
|
16
16
|
private readonly orderActivityTracker;
|
|
17
17
|
private readonly withdrawalObservationTracker;
|
|
18
18
|
private fillArchivePoller?;
|
|
19
|
+
private accountBalanceArchivePoller?;
|
|
19
20
|
/**
|
|
20
21
|
* Loads environment variables prefixed with CEX_BROKER_
|
|
21
22
|
* Expected format:
|