@usherlabs/cex-broker 0.2.24 → 0.2.26
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 +415 -8
- package/dist/handlers/subscribe/broker-lifecycle.d.ts +13 -0
- package/dist/handlers/subscribe/handler.d.ts +2 -0
- package/dist/handlers/subscribe/index.d.ts +1 -0
- 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 +416 -9
- package/dist/index.js.map +12 -10
- package/dist/server.d.ts +2 -1
- package/package.json +2 -1
|
@@ -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:
|
package/dist/index.js
CHANGED
|
@@ -274461,6 +274461,9 @@ function compactUndefined(record) {
|
|
|
274461
274461
|
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
274462
274462
|
}
|
|
274463
274463
|
|
|
274464
|
+
// src/helpers/broker-execution-archive/rows.ts
|
|
274465
|
+
import { createHash as createHash2 } from "crypto";
|
|
274466
|
+
|
|
274464
274467
|
// src/helpers/broker-execution-archive/redact.ts
|
|
274465
274468
|
import { createHash } from "crypto";
|
|
274466
274469
|
var SECRET_KEY_PATTERN = /\b(api[_-]?key|api[_-]?secret|secret|signature|passphrase|password|token|credential)\b/i;
|
|
@@ -274533,6 +274536,8 @@ function hashMarketMetadata(payload) {
|
|
|
274533
274536
|
var BROKER_WRITE_SOURCE = "broker_write";
|
|
274534
274537
|
var ARCHIVE_SCHEMA_VERSION = "1";
|
|
274535
274538
|
var FILL_EVENT_KIND = "trade_history_fill";
|
|
274539
|
+
var ACCOUNT_BALANCE_SCOPE = "spot";
|
|
274540
|
+
var ACCOUNT_BALANCE_PRECISION_BASIS = "ccxt_normalized_number";
|
|
274536
274541
|
|
|
274537
274542
|
// src/helpers/broker-execution-archive/rows.ts
|
|
274538
274543
|
function firstString2(...values2) {
|
|
@@ -274566,6 +274571,151 @@ function normalizeTimestamp2(value) {
|
|
|
274566
274571
|
const date = new Date(ms);
|
|
274567
274572
|
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
274568
274573
|
}
|
|
274574
|
+
function decimalString(value) {
|
|
274575
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
274576
|
+
return;
|
|
274577
|
+
}
|
|
274578
|
+
if (Object.is(value, -0)) {
|
|
274579
|
+
return "0";
|
|
274580
|
+
}
|
|
274581
|
+
const rendered = String(value).toLowerCase();
|
|
274582
|
+
if (!rendered.includes("e")) {
|
|
274583
|
+
return rendered;
|
|
274584
|
+
}
|
|
274585
|
+
const [coefficient = "", rawExponent = "0"] = rendered.split("e");
|
|
274586
|
+
const exponent = Number.parseInt(rawExponent, 10);
|
|
274587
|
+
const negative = coefficient.startsWith("-");
|
|
274588
|
+
const unsigned = negative ? coefficient.slice(1) : coefficient;
|
|
274589
|
+
const [integer = "0", fraction = ""] = unsigned.split(".");
|
|
274590
|
+
const digits = `${integer}${fraction}`;
|
|
274591
|
+
const decimalIndex = integer.length + exponent;
|
|
274592
|
+
let expanded;
|
|
274593
|
+
if (decimalIndex <= 0) {
|
|
274594
|
+
expanded = `0.${"0".repeat(-decimalIndex)}${digits}`;
|
|
274595
|
+
} else if (decimalIndex >= digits.length) {
|
|
274596
|
+
expanded = `${digits}${"0".repeat(decimalIndex - digits.length)}`;
|
|
274597
|
+
} else {
|
|
274598
|
+
expanded = `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
|
|
274599
|
+
}
|
|
274600
|
+
return negative ? `-${expanded}` : expanded;
|
|
274601
|
+
}
|
|
274602
|
+
function normalizedBalanceMap(value) {
|
|
274603
|
+
const record = asRecord(value);
|
|
274604
|
+
if (!record) {
|
|
274605
|
+
return {};
|
|
274606
|
+
}
|
|
274607
|
+
const entries = [];
|
|
274608
|
+
for (const [rawAsset, quantity] of Object.entries(record)) {
|
|
274609
|
+
const asset = rawAsset.trim();
|
|
274610
|
+
const normalized = decimalString(quantity);
|
|
274611
|
+
if (asset && normalized !== undefined) {
|
|
274612
|
+
entries.push([asset, normalized]);
|
|
274613
|
+
}
|
|
274614
|
+
}
|
|
274615
|
+
return Object.fromEntries(entries.sort(([left], [right]) => left.localeCompare(right)));
|
|
274616
|
+
}
|
|
274617
|
+
function sortedBalanceMap(map) {
|
|
274618
|
+
return Object.fromEntries(Object.entries(map).sort(([left], [right]) => left.localeCompare(right)));
|
|
274619
|
+
}
|
|
274620
|
+
var BALANCE_METADATA_KEYS = new Set([
|
|
274621
|
+
"info",
|
|
274622
|
+
"timestamp",
|
|
274623
|
+
"datetime",
|
|
274624
|
+
"free",
|
|
274625
|
+
"used",
|
|
274626
|
+
"total",
|
|
274627
|
+
"debt"
|
|
274628
|
+
]);
|
|
274629
|
+
function normalizeCcxtBalanceForArchive(balance) {
|
|
274630
|
+
const record = asRecord(balance) ?? {};
|
|
274631
|
+
const aggregateRecords = {
|
|
274632
|
+
free: asRecord(record.free),
|
|
274633
|
+
used: asRecord(record.used),
|
|
274634
|
+
total: asRecord(record.total)
|
|
274635
|
+
};
|
|
274636
|
+
const maps = {
|
|
274637
|
+
free: normalizedBalanceMap(record.free),
|
|
274638
|
+
used: normalizedBalanceMap(record.used),
|
|
274639
|
+
total: normalizedBalanceMap(record.total)
|
|
274640
|
+
};
|
|
274641
|
+
const assetEntryAssets = new Set;
|
|
274642
|
+
for (const [rawAsset, value] of Object.entries(record)) {
|
|
274643
|
+
if (BALANCE_METADATA_KEYS.has(rawAsset)) {
|
|
274644
|
+
continue;
|
|
274645
|
+
}
|
|
274646
|
+
const asset = rawAsset.trim();
|
|
274647
|
+
const assetEntry = asRecord(value);
|
|
274648
|
+
if (!asset || !assetEntry || !(("free" in assetEntry) || ("used" in assetEntry) || ("total" in assetEntry))) {
|
|
274649
|
+
continue;
|
|
274650
|
+
}
|
|
274651
|
+
assetEntryAssets.add(asset);
|
|
274652
|
+
for (const field of ["free", "used", "total"]) {
|
|
274653
|
+
const normalized = decimalString(assetEntry[field]);
|
|
274654
|
+
if (normalized !== undefined && maps[field][asset] === undefined) {
|
|
274655
|
+
maps[field][asset] = normalized;
|
|
274656
|
+
}
|
|
274657
|
+
}
|
|
274658
|
+
}
|
|
274659
|
+
const reportedAssets = new Set(assetEntryAssets);
|
|
274660
|
+
for (const aggregateRecord of Object.values(aggregateRecords)) {
|
|
274661
|
+
for (const rawAsset of Object.keys(aggregateRecord ?? {})) {
|
|
274662
|
+
const asset = rawAsset.trim();
|
|
274663
|
+
if (asset) {
|
|
274664
|
+
reportedAssets.add(asset);
|
|
274665
|
+
}
|
|
274666
|
+
}
|
|
274667
|
+
}
|
|
274668
|
+
for (const map of Object.values(maps)) {
|
|
274669
|
+
for (const asset of Object.keys(map)) {
|
|
274670
|
+
reportedAssets.add(asset);
|
|
274671
|
+
}
|
|
274672
|
+
}
|
|
274673
|
+
return {
|
|
274674
|
+
exchangeTimestamp: normalizeTimestamp2(record.timestamp ?? record.datetime),
|
|
274675
|
+
reportedAssets: [...reportedAssets].sort(),
|
|
274676
|
+
assetEntryAssets: [...assetEntryAssets].sort(),
|
|
274677
|
+
freeBalances: sortedBalanceMap(maps.free),
|
|
274678
|
+
usedBalances: sortedBalanceMap(maps.used),
|
|
274679
|
+
totalBalances: sortedBalanceMap(maps.total),
|
|
274680
|
+
freeMapPresent: aggregateRecords.free !== undefined,
|
|
274681
|
+
usedMapPresent: aggregateRecords.used !== undefined,
|
|
274682
|
+
totalMapPresent: aggregateRecords.total !== undefined
|
|
274683
|
+
};
|
|
274684
|
+
}
|
|
274685
|
+
function buildAccountBalanceSnapshotRow(input) {
|
|
274686
|
+
const { tags, balance } = input;
|
|
274687
|
+
const observationId = createHash2("sha256").update(JSON.stringify({
|
|
274688
|
+
deployment_id: tags.deployment_id,
|
|
274689
|
+
exchange: tags.exchange,
|
|
274690
|
+
account_selector: tags.account_selector,
|
|
274691
|
+
balance_scope: ACCOUNT_BALANCE_SCOPE,
|
|
274692
|
+
broker_observed_timestamp: tags.broker_observed_timestamp,
|
|
274693
|
+
balance
|
|
274694
|
+
})).digest("hex");
|
|
274695
|
+
return {
|
|
274696
|
+
table: "broker_account.balance_snapshots",
|
|
274697
|
+
row: compactUndefined2({
|
|
274698
|
+
broker_observed_timestamp: tags.broker_observed_timestamp,
|
|
274699
|
+
exchange_timestamp: balance.exchangeTimestamp,
|
|
274700
|
+
source: tags.source,
|
|
274701
|
+
deployment_id: tags.deployment_id,
|
|
274702
|
+
schema_version: ARCHIVE_SCHEMA_VERSION,
|
|
274703
|
+
exchange: tags.exchange,
|
|
274704
|
+
account_selector: tags.account_selector,
|
|
274705
|
+
balance_scope: ACCOUNT_BALANCE_SCOPE,
|
|
274706
|
+
observation_id: observationId,
|
|
274707
|
+
reported_assets: balance.reportedAssets,
|
|
274708
|
+
asset_entry_assets: balance.assetEntryAssets,
|
|
274709
|
+
free_balances: balance.freeBalances,
|
|
274710
|
+
used_balances: balance.usedBalances,
|
|
274711
|
+
total_balances: balance.totalBalances,
|
|
274712
|
+
aggregate_free_map_present: Number(balance.freeMapPresent),
|
|
274713
|
+
aggregate_used_map_present: Number(balance.usedMapPresent),
|
|
274714
|
+
aggregate_total_map_present: Number(balance.totalMapPresent),
|
|
274715
|
+
precision_basis: ACCOUNT_BALANCE_PRECISION_BASIS
|
|
274716
|
+
})
|
|
274717
|
+
};
|
|
274718
|
+
}
|
|
274569
274719
|
function compactUndefined2(record) {
|
|
274570
274720
|
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
274571
274721
|
}
|
|
@@ -275146,6 +275296,15 @@ function isMarketArchiveTable(table) {
|
|
|
275146
275296
|
}
|
|
275147
275297
|
|
|
275148
275298
|
// src/helpers/broker-execution-archive/writer.ts
|
|
275299
|
+
var BROKER_EXECUTION_ARCHIVE_TABLES = new Set([
|
|
275300
|
+
"broker_execution.order_events",
|
|
275301
|
+
"broker_execution.market_metadata_snapshots",
|
|
275302
|
+
"broker_execution.transfer_events",
|
|
275303
|
+
"broker_execution.fill_events"
|
|
275304
|
+
]);
|
|
275305
|
+
function isBrokerExecutionArchiveTable(table) {
|
|
275306
|
+
return BROKER_EXECUTION_ARCHIVE_TABLES.has(table);
|
|
275307
|
+
}
|
|
275149
275308
|
var DEFAULT_MAX_QUEUE_SIZE = 1e4;
|
|
275150
275309
|
var DEFAULT_BATCH_SIZE = 10;
|
|
275151
275310
|
var DEFAULT_FLUSH_INTERVAL_MS = 1000;
|
|
@@ -275230,6 +275389,9 @@ class BrokerExecutionArchiver {
|
|
|
275230
275389
|
canPersistMarketMetadataSnapshot() {
|
|
275231
275390
|
return this.isEnabled() && (Boolean(this.otelLogs?.isOtelEnabled()) || Boolean(this.forwarderUrl));
|
|
275232
275391
|
}
|
|
275392
|
+
canPersistAccountBalanceSnapshots() {
|
|
275393
|
+
return this.isEnabled() && Boolean(this.forwarderUrl);
|
|
275394
|
+
}
|
|
275233
275395
|
enqueue(row) {
|
|
275234
275396
|
if (!this.enabled || this.closed || !this.otelLogs && !this.forwarderUrl) {
|
|
275235
275397
|
return;
|
|
@@ -275241,6 +275403,9 @@ class BrokerExecutionArchiver {
|
|
|
275241
275403
|
}
|
|
275242
275404
|
return;
|
|
275243
275405
|
}
|
|
275406
|
+
if (row.table === "broker_account.balance_snapshots" && !this.forwarderUrl) {
|
|
275407
|
+
return;
|
|
275408
|
+
}
|
|
275244
275409
|
if (this.queue.length >= this.maxQueueSize) {
|
|
275245
275410
|
this.queue.shift();
|
|
275246
275411
|
this.stats.shed += 1;
|
|
@@ -275319,7 +275484,7 @@ class BrokerExecutionArchiver {
|
|
|
275319
275484
|
return true;
|
|
275320
275485
|
}
|
|
275321
275486
|
for (const entry of batch) {
|
|
275322
|
-
if (
|
|
275487
|
+
if (isBrokerExecutionArchiveTable(entry.table)) {
|
|
275323
275488
|
this.emitOtelLog(entry);
|
|
275324
275489
|
}
|
|
275325
275490
|
}
|
|
@@ -275455,8 +275620,138 @@ function parsePositiveInt(value, fallback) {
|
|
|
275455
275620
|
const parsed = Number.parseInt(value, 10);
|
|
275456
275621
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
275457
275622
|
}
|
|
275458
|
-
// src/helpers/
|
|
275623
|
+
// src/helpers/account-balance-archive-poller.ts
|
|
275459
275624
|
var DEFAULT_CONFIG = {
|
|
275625
|
+
pollIntervalMs: 60000
|
|
275626
|
+
};
|
|
275627
|
+
var metricLabels = (target) => ({
|
|
275628
|
+
exchange: target.exchangeId,
|
|
275629
|
+
account_selector: target.account.label,
|
|
275630
|
+
balance_scope: ACCOUNT_BALANCE_SCOPE
|
|
275631
|
+
});
|
|
275632
|
+
|
|
275633
|
+
class AccountBalanceArchivePoller {
|
|
275634
|
+
params;
|
|
275635
|
+
#timer = null;
|
|
275636
|
+
#stopped = false;
|
|
275637
|
+
#running = null;
|
|
275638
|
+
#lastSuccessMs = new Map;
|
|
275639
|
+
#config;
|
|
275640
|
+
constructor(params) {
|
|
275641
|
+
this.params = params;
|
|
275642
|
+
this.#config = { ...DEFAULT_CONFIG, ...params.config };
|
|
275643
|
+
}
|
|
275644
|
+
start() {
|
|
275645
|
+
if (this.#timer || this.#stopped || !this.params.archiver.canPersistAccountBalanceSnapshots()) {
|
|
275646
|
+
return;
|
|
275647
|
+
}
|
|
275648
|
+
log.info("\uD83D\uDCB0 Account balance archive poller started", {
|
|
275649
|
+
balanceScope: ACCOUNT_BALANCE_SCOPE
|
|
275650
|
+
});
|
|
275651
|
+
this.#schedule(0);
|
|
275652
|
+
}
|
|
275653
|
+
async stop() {
|
|
275654
|
+
this.#stopped = true;
|
|
275655
|
+
if (this.#timer) {
|
|
275656
|
+
clearTimeout(this.#timer);
|
|
275657
|
+
this.#timer = null;
|
|
275658
|
+
}
|
|
275659
|
+
await this.#running;
|
|
275660
|
+
}
|
|
275661
|
+
async pollAllOnce() {
|
|
275662
|
+
if (this.#stopped || this.#running || !this.params.archiver.canPersistAccountBalanceSnapshots()) {
|
|
275663
|
+
return false;
|
|
275664
|
+
}
|
|
275665
|
+
this.#running = this.#pollAllSequentially();
|
|
275666
|
+
try {
|
|
275667
|
+
return await this.#running;
|
|
275668
|
+
} finally {
|
|
275669
|
+
this.#running = null;
|
|
275670
|
+
}
|
|
275671
|
+
}
|
|
275672
|
+
#targets() {
|
|
275673
|
+
const targets = [];
|
|
275674
|
+
for (const [exchangeId, pool] of Object.entries(this.params.brokers)) {
|
|
275675
|
+
for (const account of [pool.primary, ...pool.secondaryBrokers]) {
|
|
275676
|
+
targets.push({ exchangeId, account });
|
|
275677
|
+
}
|
|
275678
|
+
}
|
|
275679
|
+
return targets;
|
|
275680
|
+
}
|
|
275681
|
+
async#pollAllSequentially() {
|
|
275682
|
+
for (const target of this.#targets()) {
|
|
275683
|
+
if (this.#stopped) {
|
|
275684
|
+
break;
|
|
275685
|
+
}
|
|
275686
|
+
await this.#pollOne(target);
|
|
275687
|
+
}
|
|
275688
|
+
return true;
|
|
275689
|
+
}
|
|
275690
|
+
async#pollOne(target) {
|
|
275691
|
+
const labels = metricLabels(target);
|
|
275692
|
+
this.params.metrics?.recordCounter("cex_account_balance_poll_attempts_total", 1, labels);
|
|
275693
|
+
try {
|
|
275694
|
+
const exchange = target.account.exchange;
|
|
275695
|
+
const balance = await exchange.fetchBalance({
|
|
275696
|
+
type: ACCOUNT_BALANCE_SCOPE
|
|
275697
|
+
});
|
|
275698
|
+
const observedAt = new Date;
|
|
275699
|
+
const normalized = normalizeCcxtBalanceForArchive(balance);
|
|
275700
|
+
this.params.archiver.enqueue(buildAccountBalanceSnapshotRow({
|
|
275701
|
+
tags: buildCommonArchiveTags({
|
|
275702
|
+
deploymentId: this.params.archiver.getDeploymentId(),
|
|
275703
|
+
accountSelector: target.account.label,
|
|
275704
|
+
exchange: target.exchangeId,
|
|
275705
|
+
brokerObservedTimestamp: observedAt.toISOString()
|
|
275706
|
+
}),
|
|
275707
|
+
balance: normalized
|
|
275708
|
+
}));
|
|
275709
|
+
const successMs = Date.now();
|
|
275710
|
+
this.#lastSuccessMs.set(this.#targetKey(target), successMs);
|
|
275711
|
+
this.params.metrics?.recordCounter("cex_account_balance_poll_successes_total", 1, labels);
|
|
275712
|
+
this.params.metrics?.recordGauge("cex_account_balance_poll_last_success_timestamp_seconds", Math.floor(successMs / 1000), labels);
|
|
275713
|
+
this.#recordFreshness(labels, successMs, successMs);
|
|
275714
|
+
} catch (error) {
|
|
275715
|
+
this.params.metrics?.recordCounter("cex_account_balance_poll_failures_total", 1, labels);
|
|
275716
|
+
const now3 = Date.now();
|
|
275717
|
+
const lastSuccess = this.#lastSuccessMs.get(this.#targetKey(target));
|
|
275718
|
+
if (lastSuccess !== undefined) {
|
|
275719
|
+
this.#recordFreshness(labels, lastSuccess, now3);
|
|
275720
|
+
}
|
|
275721
|
+
log.warn("Account balance archive poll failed", {
|
|
275722
|
+
exchange: target.exchangeId,
|
|
275723
|
+
account: target.account.label,
|
|
275724
|
+
balanceScope: ACCOUNT_BALANCE_SCOPE,
|
|
275725
|
+
errorType: error instanceof Error ? error.name : "unknown"
|
|
275726
|
+
});
|
|
275727
|
+
}
|
|
275728
|
+
}
|
|
275729
|
+
#recordFreshness(labels, lastSuccessMs, nowMs) {
|
|
275730
|
+
this.params.metrics?.recordGauge("cex_account_balance_poll_freshness_seconds", Math.max(0, (nowMs - lastSuccessMs) / 1000), labels);
|
|
275731
|
+
}
|
|
275732
|
+
#targetKey(target) {
|
|
275733
|
+
return `${target.exchangeId}|${target.account.label}|${ACCOUNT_BALANCE_SCOPE}`;
|
|
275734
|
+
}
|
|
275735
|
+
#schedule(delayMs) {
|
|
275736
|
+
this.#timer = setTimeout(() => void this.#tick(), delayMs);
|
|
275737
|
+
this.#timer.unref?.();
|
|
275738
|
+
}
|
|
275739
|
+
async#tick() {
|
|
275740
|
+
this.#timer = null;
|
|
275741
|
+
try {
|
|
275742
|
+
await this.pollAllOnce();
|
|
275743
|
+
} catch (error) {
|
|
275744
|
+
log.error("Account balance archive poller tick failed", error);
|
|
275745
|
+
} finally {
|
|
275746
|
+
if (!this.#stopped) {
|
|
275747
|
+
this.#schedule(this.#config.pollIntervalMs);
|
|
275748
|
+
}
|
|
275749
|
+
}
|
|
275750
|
+
}
|
|
275751
|
+
}
|
|
275752
|
+
|
|
275753
|
+
// src/helpers/fill-archive-poller.ts
|
|
275754
|
+
var DEFAULT_CONFIG2 = {
|
|
275460
275755
|
pollIntervalMs: 60000,
|
|
275461
275756
|
lookbackMs: 24 * 60 * 60 * 1000,
|
|
275462
275757
|
tradesLimit: 500
|
|
@@ -275481,7 +275776,7 @@ class FillArchivePoller {
|
|
|
275481
275776
|
#config;
|
|
275482
275777
|
constructor(params) {
|
|
275483
275778
|
this.params = params;
|
|
275484
|
-
this.#config = { ...
|
|
275779
|
+
this.#config = { ...DEFAULT_CONFIG2, ...params.config };
|
|
275485
275780
|
}
|
|
275486
275781
|
start() {
|
|
275487
275782
|
if (this.#timer || this.#stopped) {
|
|
@@ -295077,6 +295372,59 @@ function createExecuteActionHandler(deps) {
|
|
|
295077
295372
|
}
|
|
295078
295373
|
};
|
|
295079
295374
|
}
|
|
295375
|
+
// src/handlers/subscribe/broker-lifecycle.ts
|
|
295376
|
+
class SubscribeBrokerLifecycle {
|
|
295377
|
+
#brokers = new Map;
|
|
295378
|
+
#closing = new Map;
|
|
295379
|
+
#shuttingDown = false;
|
|
295380
|
+
register(broker, context3) {
|
|
295381
|
+
this.#brokers.set(broker, context3);
|
|
295382
|
+
if (this.#shuttingDown) {
|
|
295383
|
+
this.close(broker);
|
|
295384
|
+
}
|
|
295385
|
+
}
|
|
295386
|
+
close(broker) {
|
|
295387
|
+
const existing = this.#closing.get(broker);
|
|
295388
|
+
if (existing) {
|
|
295389
|
+
return existing;
|
|
295390
|
+
}
|
|
295391
|
+
const context3 = this.#brokers.get(broker) ?? {
|
|
295392
|
+
cex: "unknown",
|
|
295393
|
+
symbol: "unknown"
|
|
295394
|
+
};
|
|
295395
|
+
this.#brokers.delete(broker);
|
|
295396
|
+
const closing = (async () => {
|
|
295397
|
+
try {
|
|
295398
|
+
await broker.close();
|
|
295399
|
+
log.debug("Request-scoped Subscribe broker closed", context3);
|
|
295400
|
+
return "closed";
|
|
295401
|
+
} catch (error48) {
|
|
295402
|
+
log.warn("Failed to close request-scoped Subscribe broker", {
|
|
295403
|
+
...context3,
|
|
295404
|
+
error: error48
|
|
295405
|
+
});
|
|
295406
|
+
return "failed";
|
|
295407
|
+
} finally {
|
|
295408
|
+
this.#closing.delete(broker);
|
|
295409
|
+
}
|
|
295410
|
+
})();
|
|
295411
|
+
this.#closing.set(broker, closing);
|
|
295412
|
+
return closing;
|
|
295413
|
+
}
|
|
295414
|
+
async closeAll() {
|
|
295415
|
+
this.#shuttingDown = true;
|
|
295416
|
+
let failed = 0;
|
|
295417
|
+
while (this.#brokers.size > 0 || this.#closing.size > 0) {
|
|
295418
|
+
const inFlight = [...this.#closing.values()];
|
|
295419
|
+
const fresh = [...this.#brokers.keys()].map((broker) => this.close(broker));
|
|
295420
|
+
const outcomes = await Promise.all([...fresh, ...inFlight]);
|
|
295421
|
+
failed += outcomes.filter((outcome) => outcome === "failed").length;
|
|
295422
|
+
}
|
|
295423
|
+
if (failed > 0) {
|
|
295424
|
+
throw new Error(`${failed} request-scoped Subscribe broker(s) failed to close`);
|
|
295425
|
+
}
|
|
295426
|
+
}
|
|
295427
|
+
}
|
|
295080
295428
|
// src/handlers/subscribe/handler.ts
|
|
295081
295429
|
import * as grpc13 from "@grpc/grpc-js";
|
|
295082
295430
|
|
|
@@ -295087,6 +295435,7 @@ import WebSocket2 from "ws";
|
|
|
295087
295435
|
var BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
|
|
295088
295436
|
var DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
|
|
295089
295437
|
var createWebSocket = (url3) => new WebSocket2(url3);
|
|
295438
|
+
var userDataRequestCounter = 0;
|
|
295090
295439
|
function getExchangeString(exchange, key) {
|
|
295091
295440
|
const value = exchange[key];
|
|
295092
295441
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -295193,7 +295542,7 @@ class BinanceSpotUserDataStream {
|
|
|
295193
295542
|
exchange;
|
|
295194
295543
|
ws;
|
|
295195
295544
|
secretValues;
|
|
295196
|
-
requestId = `user-data-${Date.now()}-${
|
|
295545
|
+
requestId = `user-data-${Date.now()}-${userDataRequestCounter++}`;
|
|
295197
295546
|
maxBufferedEvents;
|
|
295198
295547
|
queue = [];
|
|
295199
295548
|
waiters = [];
|
|
@@ -295271,6 +295620,12 @@ class BinanceSpotUserDataStream {
|
|
|
295271
295620
|
this.subscriptionId = message.result?.subscriptionId ?? null;
|
|
295272
295621
|
return;
|
|
295273
295622
|
}
|
|
295623
|
+
if ("status" in message && typeof message.status === "number" && message.status !== 200) {
|
|
295624
|
+
const errorMessage = message.error?.msg ?? message.error?.message ?? `Binance user-data request failed with status ${message.status}`;
|
|
295625
|
+
const errorCode = message.error?.code;
|
|
295626
|
+
this.fail(new Error(typeof errorCode === "number" ? `${errorMessage} (code ${errorCode})` : errorMessage));
|
|
295627
|
+
return;
|
|
295628
|
+
}
|
|
295274
295629
|
if (!("event" in message) || !message.event) {
|
|
295275
295630
|
return;
|
|
295276
295631
|
}
|
|
@@ -296147,15 +296502,34 @@ async function runCcxtSubscribeLoop(call, isClosed, symbol2, subscriptionType, w
|
|
|
296147
296502
|
}
|
|
296148
296503
|
function createSubscribeHandler(deps) {
|
|
296149
296504
|
const { brokers, whitelistIps, otelMetrics, brokerArchiver } = deps;
|
|
296505
|
+
const brokerLifecycle = deps.brokerLifecycle ?? new SubscribeBrokerLifecycle;
|
|
296150
296506
|
return async (call) => {
|
|
296151
296507
|
const subscribeStartTime = Date.now();
|
|
296152
296508
|
let streamClosed = false;
|
|
296509
|
+
let ownedBroker = null;
|
|
296510
|
+
let ownedBrokerClosePromise;
|
|
296153
296511
|
const markStreamClosed = () => {
|
|
296154
296512
|
streamClosed = true;
|
|
296155
296513
|
};
|
|
296156
|
-
const isStreamClosed = () => streamClosed || call.
|
|
296157
|
-
|
|
296514
|
+
const isStreamClosed = () => streamClosed || call.cancelled || call.writableEnded;
|
|
296515
|
+
const closeOwnedBroker = () => {
|
|
296516
|
+
if (ownedBrokerClosePromise) {
|
|
296517
|
+
return ownedBrokerClosePromise;
|
|
296518
|
+
}
|
|
296519
|
+
if (!ownedBroker) {
|
|
296520
|
+
return Promise.resolve();
|
|
296521
|
+
}
|
|
296522
|
+
const broker = ownedBroker;
|
|
296523
|
+
ownedBroker = null;
|
|
296524
|
+
ownedBrokerClosePromise = brokerLifecycle.close(broker);
|
|
296525
|
+
return ownedBrokerClosePromise;
|
|
296526
|
+
};
|
|
296527
|
+
const closeOwnedBrokerOnCallEnd = () => {
|
|
296528
|
+
closeOwnedBroker();
|
|
296529
|
+
};
|
|
296158
296530
|
call.once("cancelled", markStreamClosed);
|
|
296531
|
+
call.once("cancelled", closeOwnedBrokerOnCallEnd);
|
|
296532
|
+
call.once("error", closeOwnedBrokerOnCallEnd);
|
|
296159
296533
|
call.once("end", () => {
|
|
296160
296534
|
markStreamClosed();
|
|
296161
296535
|
log.info("Subscribe stream ended");
|
|
@@ -296227,6 +296601,17 @@ function createSubscribeHandler(deps) {
|
|
|
296227
296601
|
});
|
|
296228
296602
|
return;
|
|
296229
296603
|
}
|
|
296604
|
+
if (!selectedBrokerAccount) {
|
|
296605
|
+
ownedBroker = broker;
|
|
296606
|
+
brokerLifecycle.register(broker, {
|
|
296607
|
+
cex: normalizedCex,
|
|
296608
|
+
symbol: symbol2
|
|
296609
|
+
});
|
|
296610
|
+
if (isStreamClosed()) {
|
|
296611
|
+
await closeOwnedBroker();
|
|
296612
|
+
return;
|
|
296613
|
+
}
|
|
296614
|
+
}
|
|
296230
296615
|
const resolvedSymbol = await resolveSubscriptionSymbol(broker, symbol2, options?.marketType);
|
|
296231
296616
|
const assetType = parseMarketType(options?.marketType);
|
|
296232
296617
|
const deploymentId = brokerArchiver?.getDeploymentId() ?? "unknown";
|
|
@@ -296491,6 +296876,10 @@ function createSubscribeHandler(deps) {
|
|
|
296491
296876
|
symbol: "",
|
|
296492
296877
|
type: subscriptionType
|
|
296493
296878
|
});
|
|
296879
|
+
} finally {
|
|
296880
|
+
call.off("cancelled", closeOwnedBrokerOnCallEnd);
|
|
296881
|
+
call.off("error", closeOwnedBrokerOnCallEnd);
|
|
296882
|
+
await closeOwnedBroker();
|
|
296494
296883
|
}
|
|
296495
296884
|
};
|
|
296496
296885
|
}
|
|
@@ -296641,7 +297030,7 @@ var CEX_BROKER_PACKAGE_DEFINITION = protoLoader.fromJSON(node_descriptor_default
|
|
|
296641
297030
|
// src/server.ts
|
|
296642
297031
|
var grpcObj = grpc14.loadPackageDefinition(CEX_BROKER_PACKAGE_DEFINITION);
|
|
296643
297032
|
var cexNode = grpcObj.cex_broker;
|
|
296644
|
-
function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver, orderActivityTracker, withdrawalObservationTracker) {
|
|
297033
|
+
function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, otelMetrics, brokerArchiver, orderActivityTracker, withdrawalObservationTracker, subscribeBrokerLifecycle) {
|
|
296645
297034
|
const server = new grpc14.Server;
|
|
296646
297035
|
server.addService(cexNode.cex_service.service, {
|
|
296647
297036
|
ExecuteAction: createExecuteActionHandler({
|
|
@@ -296659,7 +297048,8 @@ function getServer(policy, brokers, whitelistIps, useVerity, verityProverUrl, ot
|
|
|
296659
297048
|
brokers,
|
|
296660
297049
|
whitelistIps,
|
|
296661
297050
|
otelMetrics,
|
|
296662
|
-
brokerArchiver
|
|
297051
|
+
brokerArchiver,
|
|
297052
|
+
brokerLifecycle: subscribeBrokerLifecycle
|
|
296663
297053
|
})
|
|
296664
297054
|
});
|
|
296665
297055
|
return server;
|
|
@@ -296801,6 +297191,7 @@ class CEXBroker {
|
|
|
296801
297191
|
orderActivityTracker = new OrderActivityTracker;
|
|
296802
297192
|
withdrawalObservationTracker = new WithdrawalObservationTracker;
|
|
296803
297193
|
fillArchivePoller;
|
|
297194
|
+
accountBalanceArchivePoller;
|
|
296804
297195
|
loadEnvConfig() {
|
|
296805
297196
|
log.info("\uD83D\uDD27 Loading CEX_BROKER_ environment variables:");
|
|
296806
297197
|
const configMap = {};
|
|
@@ -296945,6 +297336,10 @@ class CEXBroker {
|
|
|
296945
297336
|
this.fillArchivePoller.stop();
|
|
296946
297337
|
this.fillArchivePoller = undefined;
|
|
296947
297338
|
}
|
|
297339
|
+
if (this.accountBalanceArchivePoller) {
|
|
297340
|
+
await this.accountBalanceArchivePoller.stop();
|
|
297341
|
+
this.accountBalanceArchivePoller = undefined;
|
|
297342
|
+
}
|
|
296948
297343
|
if (this.server) {
|
|
296949
297344
|
await this.server.forceShutdown();
|
|
296950
297345
|
}
|
|
@@ -296970,6 +297365,10 @@ class CEXBroker {
|
|
|
296970
297365
|
this.fillArchivePoller.stop();
|
|
296971
297366
|
this.fillArchivePoller = undefined;
|
|
296972
297367
|
}
|
|
297368
|
+
if (this.accountBalanceArchivePoller) {
|
|
297369
|
+
await this.accountBalanceArchivePoller.stop();
|
|
297370
|
+
this.accountBalanceArchivePoller = undefined;
|
|
297371
|
+
}
|
|
296973
297372
|
log.info(`Running CEXBroker at ${new Date().toISOString()}`);
|
|
296974
297373
|
if (this.otelMetrics?.isOtelEnabled()) {
|
|
296975
297374
|
await this.otelMetrics.initialize();
|
|
@@ -296998,6 +297397,14 @@ class CEXBroker {
|
|
|
296998
297397
|
});
|
|
296999
297398
|
this.fillArchivePoller.start();
|
|
297000
297399
|
}
|
|
297400
|
+
if (this.brokerArchiver?.canPersistAccountBalanceSnapshots()) {
|
|
297401
|
+
this.accountBalanceArchivePoller = new AccountBalanceArchivePoller({
|
|
297402
|
+
brokers: this.brokers,
|
|
297403
|
+
archiver: this.brokerArchiver,
|
|
297404
|
+
metrics: this.otelMetrics
|
|
297405
|
+
});
|
|
297406
|
+
this.accountBalanceArchivePoller.start();
|
|
297407
|
+
}
|
|
297001
297408
|
return this;
|
|
297002
297409
|
}
|
|
297003
297410
|
}
|
|
@@ -297005,4 +297412,4 @@ export {
|
|
|
297005
297412
|
CEXBroker as default
|
|
297006
297413
|
};
|
|
297007
297414
|
|
|
297008
|
-
//# debugId=
|
|
297415
|
+
//# debugId=1D08CFF8FA968C4064756E2164756E21
|