@raintree-technology/perps 0.1.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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/adapters/aevo.d.ts +64 -0
- package/dist/adapters/aevo.js +899 -0
- package/dist/adapters/certification.d.ts +33 -0
- package/dist/adapters/certification.js +99 -0
- package/dist/adapters/decibel/order-manager.d.ts +45 -0
- package/dist/adapters/decibel/order-manager.js +140 -0
- package/dist/adapters/decibel/rest-client.d.ts +176 -0
- package/dist/adapters/decibel/rest-client.js +155 -0
- package/dist/adapters/decibel/ws-feed.d.ts +28 -0
- package/dist/adapters/decibel/ws-feed.js +166 -0
- package/dist/adapters/decibel.d.ts +108 -0
- package/dist/adapters/decibel.js +1377 -0
- package/dist/adapters/hyperliquid.d.ts +63 -0
- package/dist/adapters/hyperliquid.js +797 -0
- package/dist/adapters/index.d.ts +11 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/interface.d.ts +310 -0
- package/dist/adapters/interface.js +15 -0
- package/dist/adapters/orderly.d.ts +70 -0
- package/dist/adapters/orderly.js +936 -0
- package/dist/adapters/paradex.d.ts +69 -0
- package/dist/adapters/paradex.js +862 -0
- package/dist/adapters/utils.d.ts +17 -0
- package/dist/adapters/utils.js +122 -0
- package/dist/cli/command-metadata.d.ts +2 -0
- package/dist/cli/command-metadata.js +44 -0
- package/dist/cli/context.d.ts +14 -0
- package/dist/cli/context.js +59 -0
- package/dist/cli/experience.d.ts +48 -0
- package/dist/cli/experience.js +243 -0
- package/dist/cli/ink/app/AppShell.d.ts +12 -0
- package/dist/cli/ink/app/AppShell.js +32 -0
- package/dist/cli/ink/app/MetricStrip.d.ts +6 -0
- package/dist/cli/ink/app/MetricStrip.js +14 -0
- package/dist/cli/ink/app/Panel.d.ts +9 -0
- package/dist/cli/ink/app/Panel.js +7 -0
- package/dist/cli/ink/app/ascii.d.ts +2 -0
- package/dist/cli/ink/app/ascii.js +46 -0
- package/dist/cli/ink/app/index.d.ts +5 -0
- package/dist/cli/ink/app/index.js +4 -0
- package/dist/cli/ink/app/types.d.ts +15 -0
- package/dist/cli/ink/app/types.js +1 -0
- package/dist/cli/ink/components/PnL.d.ts +12 -0
- package/dist/cli/ink/components/PnL.js +23 -0
- package/dist/cli/ink/components/Spinner.d.ts +13 -0
- package/dist/cli/ink/components/Spinner.js +13 -0
- package/dist/cli/ink/components/Table.d.ts +14 -0
- package/dist/cli/ink/components/Table.js +42 -0
- package/dist/cli/ink/components/WatchHeader.d.ts +10 -0
- package/dist/cli/ink/components/WatchHeader.js +18 -0
- package/dist/cli/ink/components/index.d.ts +4 -0
- package/dist/cli/ink/components/index.js +4 -0
- package/dist/cli/ink/index.d.ts +4 -0
- package/dist/cli/ink/index.js +4 -0
- package/dist/cli/ink/render.d.ts +12 -0
- package/dist/cli/ink/render.js +21 -0
- package/dist/cli/ink/theme.d.ts +29 -0
- package/dist/cli/ink/theme.js +40 -0
- package/dist/cli/network-defaults.d.ts +10 -0
- package/dist/cli/network-defaults.js +35 -0
- package/dist/cli/output.d.ts +11 -0
- package/dist/cli/output.js +115 -0
- package/dist/cli/program.d.ts +18 -0
- package/dist/cli/program.js +164 -0
- package/dist/cli/watch.d.ts +19 -0
- package/dist/cli/watch.js +35 -0
- package/dist/client/index.d.ts +55 -0
- package/dist/client/index.js +157 -0
- package/dist/commands/account/add.d.ts +2 -0
- package/dist/commands/account/add.js +510 -0
- package/dist/commands/account/balances-simple.d.ts +5 -0
- package/dist/commands/account/balances-simple.js +63 -0
- package/dist/commands/account/index.d.ts +2 -0
- package/dist/commands/account/index.js +17 -0
- package/dist/commands/account/ls.d.ts +2 -0
- package/dist/commands/account/ls.js +95 -0
- package/dist/commands/account/positions-simple.d.ts +5 -0
- package/dist/commands/account/positions-simple.js +77 -0
- package/dist/commands/account/remove.d.ts +2 -0
- package/dist/commands/account/remove.js +47 -0
- package/dist/commands/account/set-default.d.ts +2 -0
- package/dist/commands/account/set-default.js +47 -0
- package/dist/commands/agent/index.d.ts +2 -0
- package/dist/commands/agent/index.js +126 -0
- package/dist/commands/arb/alert.d.ts +6 -0
- package/dist/commands/arb/alert.js +88 -0
- package/dist/commands/arb/basis-execute.d.ts +6 -0
- package/dist/commands/arb/basis-execute.js +332 -0
- package/dist/commands/arb/basis.d.ts +6 -0
- package/dist/commands/arb/basis.js +181 -0
- package/dist/commands/arb/compare.d.ts +6 -0
- package/dist/commands/arb/compare.js +216 -0
- package/dist/commands/arb/execute.d.ts +6 -0
- package/dist/commands/arb/execute.js +467 -0
- package/dist/commands/arb/funding.d.ts +6 -0
- package/dist/commands/arb/funding.js +201 -0
- package/dist/commands/arb/history.d.ts +6 -0
- package/dist/commands/arb/history.js +153 -0
- package/dist/commands/arb/index.d.ts +6 -0
- package/dist/commands/arb/index.js +29 -0
- package/dist/commands/arb/positions.d.ts +6 -0
- package/dist/commands/arb/positions.js +158 -0
- package/dist/commands/arb/spread.d.ts +6 -0
- package/dist/commands/arb/spread.js +253 -0
- package/dist/commands/arb/track.d.ts +6 -0
- package/dist/commands/arb/track.js +259 -0
- package/dist/commands/asset/book-simple.d.ts +5 -0
- package/dist/commands/asset/book-simple.js +77 -0
- package/dist/commands/asset/index.d.ts +2 -0
- package/dist/commands/asset/index.js +5 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +161 -0
- package/dist/commands/config/index.d.ts +5 -0
- package/dist/commands/config/index.js +109 -0
- package/dist/commands/data/index.d.ts +31 -0
- package/dist/commands/data/index.js +1466 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +201 -0
- package/dist/commands/exchange/index.d.ts +2 -0
- package/dist/commands/exchange/index.js +107 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +48 -0
- package/dist/commands/markets/index.d.ts +2 -0
- package/dist/commands/markets/index.js +5 -0
- package/dist/commands/markets/ls-simple.d.ts +7 -0
- package/dist/commands/markets/ls-simple.js +277 -0
- package/dist/commands/operator/index.d.ts +2 -0
- package/dist/commands/operator/index.js +146 -0
- package/dist/commands/order/cancel-simple.d.ts +5 -0
- package/dist/commands/order/cancel-simple.js +104 -0
- package/dist/commands/order/index.d.ts +2 -0
- package/dist/commands/order/index.js +13 -0
- package/dist/commands/order/limit-simple.d.ts +5 -0
- package/dist/commands/order/limit-simple.js +195 -0
- package/dist/commands/order/market-simple.d.ts +5 -0
- package/dist/commands/order/market-simple.js +190 -0
- package/dist/commands/order/shared.d.ts +17 -0
- package/dist/commands/order/shared.js +51 -0
- package/dist/commands/order/trigger-simple.d.ts +5 -0
- package/dist/commands/order/trigger-simple.js +246 -0
- package/dist/commands/referral/index.d.ts +2 -0
- package/dist/commands/referral/index.js +7 -0
- package/dist/commands/referral/set.d.ts +2 -0
- package/dist/commands/referral/set.js +26 -0
- package/dist/commands/referral/status.d.ts +2 -0
- package/dist/commands/referral/status.js +31 -0
- package/dist/commands/replay/index.d.ts +2 -0
- package/dist/commands/replay/index.js +152 -0
- package/dist/commands/risk/analytics.d.ts +2 -0
- package/dist/commands/risk/analytics.js +64 -0
- package/dist/commands/risk/audit.d.ts +2 -0
- package/dist/commands/risk/audit.js +52 -0
- package/dist/commands/risk/index.d.ts +2 -0
- package/dist/commands/risk/index.js +9 -0
- package/dist/commands/risk/rules.d.ts +2 -0
- package/dist/commands/risk/rules.js +102 -0
- package/dist/commands/server.d.ts +2 -0
- package/dist/commands/server.js +208 -0
- package/dist/commands/setup/index.d.ts +2 -0
- package/dist/commands/setup/index.js +478 -0
- package/dist/commands/signal/index.d.ts +2 -0
- package/dist/commands/signal/index.js +129 -0
- package/dist/commands/state/index.d.ts +2 -0
- package/dist/commands/state/index.js +5 -0
- package/dist/commands/state/show.d.ts +2 -0
- package/dist/commands/state/show.js +105 -0
- package/dist/commands/strategy/index.d.ts +4 -0
- package/dist/commands/strategy/index.js +73 -0
- package/dist/commands/traces/index.d.ts +2 -0
- package/dist/commands/traces/index.js +76 -0
- package/dist/commands/ui/demo.d.ts +9 -0
- package/dist/commands/ui/demo.js +195 -0
- package/dist/commands/ui/index.d.ts +2 -0
- package/dist/commands/ui/index.js +7 -0
- package/dist/commands/ui/terminal.d.ts +2 -0
- package/dist/commands/ui/terminal.js +255 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +98 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/lib/agent/audit.d.ts +12 -0
- package/dist/lib/agent/audit.js +13 -0
- package/dist/lib/agent/gateway.d.ts +13 -0
- package/dist/lib/agent/gateway.js +598 -0
- package/dist/lib/agent/metrics.d.ts +33 -0
- package/dist/lib/agent/metrics.js +175 -0
- package/dist/lib/agent/signature.d.ts +8 -0
- package/dist/lib/agent/signature.js +28 -0
- package/dist/lib/agent/tools.d.ts +28 -0
- package/dist/lib/agent/tools.js +453 -0
- package/dist/lib/agent/x402.d.ts +23 -0
- package/dist/lib/agent/x402.js +62 -0
- package/dist/lib/api-wallet.d.ts +69 -0
- package/dist/lib/api-wallet.js +101 -0
- package/dist/lib/balance-watcher.d.ts +25 -0
- package/dist/lib/balance-watcher.js +83 -0
- package/dist/lib/book-watcher.d.ts +25 -0
- package/dist/lib/book-watcher.js +48 -0
- package/dist/lib/config.d.ts +88 -0
- package/dist/lib/config.js +427 -0
- package/dist/lib/constants.d.ts +50 -0
- package/dist/lib/constants.js +84 -0
- package/dist/lib/contracts.d.ts +7 -0
- package/dist/lib/contracts.js +8 -0
- package/dist/lib/credential-vault.d.ts +22 -0
- package/dist/lib/credential-vault.js +109 -0
- package/dist/lib/db/accounts.d.ts +83 -0
- package/dist/lib/db/accounts.js +203 -0
- package/dist/lib/db/funding-history.d.ts +69 -0
- package/dist/lib/db/funding-history.js +183 -0
- package/dist/lib/db/index.d.ts +11 -0
- package/dist/lib/db/index.js +272 -0
- package/dist/lib/events/bus.d.ts +10 -0
- package/dist/lib/events/bus.js +17 -0
- package/dist/lib/events/types.d.ts +51 -0
- package/dist/lib/events/types.js +1 -0
- package/dist/lib/exchange.d.ts +30 -0
- package/dist/lib/exchange.js +84 -0
- package/dist/lib/execution/journal.d.ts +25 -0
- package/dist/lib/execution/journal.js +158 -0
- package/dist/lib/execution/safety.d.ts +34 -0
- package/dist/lib/execution/safety.js +197 -0
- package/dist/lib/exit-codes.d.ts +18 -0
- package/dist/lib/exit-codes.js +60 -0
- package/dist/lib/fetch.d.ts +18 -0
- package/dist/lib/fetch.js +66 -0
- package/dist/lib/fs-security.d.ts +10 -0
- package/dist/lib/fs-security.js +26 -0
- package/dist/lib/funding-tracker.d.ts +40 -0
- package/dist/lib/funding-tracker.js +118 -0
- package/dist/lib/logger.d.ts +27 -0
- package/dist/lib/logger.js +82 -0
- package/dist/lib/network-model.d.ts +13 -0
- package/dist/lib/network-model.js +30 -0
- package/dist/lib/onboarding.d.ts +133 -0
- package/dist/lib/onboarding.js +1459 -0
- package/dist/lib/operator-state.d.ts +25 -0
- package/dist/lib/operator-state.js +82 -0
- package/dist/lib/orders-watcher.d.ts +24 -0
- package/dist/lib/orders-watcher.js +74 -0
- package/dist/lib/paths.d.ts +20 -0
- package/dist/lib/paths.js +23 -0
- package/dist/lib/portfolio-watcher.d.ts +33 -0
- package/dist/lib/portfolio-watcher.js +95 -0
- package/dist/lib/position-watcher.d.ts +16 -0
- package/dist/lib/position-watcher.js +44 -0
- package/dist/lib/price-watcher.d.ts +15 -0
- package/dist/lib/price-watcher.js +84 -0
- package/dist/lib/prompts.d.ts +32 -0
- package/dist/lib/prompts.js +105 -0
- package/dist/lib/rate-limit.d.ts +32 -0
- package/dist/lib/rate-limit.js +88 -0
- package/dist/lib/risk/analytics.d.ts +39 -0
- package/dist/lib/risk/analytics.js +98 -0
- package/dist/lib/risk/drawdown.d.ts +18 -0
- package/dist/lib/risk/drawdown.js +49 -0
- package/dist/lib/risk/evaluation-log.d.ts +29 -0
- package/dist/lib/risk/evaluation-log.js +61 -0
- package/dist/lib/risk/index.d.ts +4 -0
- package/dist/lib/risk/index.js +4 -0
- package/dist/lib/risk/limits.d.ts +23 -0
- package/dist/lib/risk/limits.js +27 -0
- package/dist/lib/risk/manager.d.ts +32 -0
- package/dist/lib/risk/manager.js +85 -0
- package/dist/lib/risk/policy-middleware.d.ts +33 -0
- package/dist/lib/risk/policy-middleware.js +267 -0
- package/dist/lib/risk/position-sizer.d.ts +9 -0
- package/dist/lib/risk/position-sizer.js +14 -0
- package/dist/lib/risk/rules-store.d.ts +16 -0
- package/dist/lib/risk/rules-store.js +47 -0
- package/dist/lib/schema.d.ts +254 -0
- package/dist/lib/schema.js +199 -0
- package/dist/lib/secrets.d.ts +3 -0
- package/dist/lib/secrets.js +62 -0
- package/dist/lib/settings.d.ts +24 -0
- package/dist/lib/settings.js +86 -0
- package/dist/lib/signals.d.ts +73 -0
- package/dist/lib/signals.js +136 -0
- package/dist/lib/stable-stringify.d.ts +6 -0
- package/dist/lib/stable-stringify.js +17 -0
- package/dist/lib/state-context.d.ts +44 -0
- package/dist/lib/state-context.js +133 -0
- package/dist/lib/strategy/basis-trade.d.ts +2 -0
- package/dist/lib/strategy/basis-trade.js +24 -0
- package/dist/lib/strategy/funding-arb.d.ts +2 -0
- package/dist/lib/strategy/funding-arb.js +23 -0
- package/dist/lib/strategy/interface.d.ts +23 -0
- package/dist/lib/strategy/interface.js +1 -0
- package/dist/lib/strategy/registry.d.ts +4 -0
- package/dist/lib/strategy/registry.js +10 -0
- package/dist/lib/telemetry.d.ts +25 -0
- package/dist/lib/telemetry.js +101 -0
- package/dist/lib/trace-queries.d.ts +20 -0
- package/dist/lib/trace-queries.js +133 -0
- package/dist/lib/trace.d.ts +1 -0
- package/dist/lib/trace.js +4 -0
- package/dist/lib/trade-reputation.d.ts +6 -0
- package/dist/lib/trade-reputation.js +99 -0
- package/dist/lib/ui-tokens.d.ts +21 -0
- package/dist/lib/ui-tokens.js +26 -0
- package/dist/lib/validate.d.ts +39 -0
- package/dist/lib/validate.js +108 -0
- package/dist/lib/validation.d.ts +9 -0
- package/dist/lib/validation.js +64 -0
- package/dist/server/cache.d.ts +38 -0
- package/dist/server/cache.js +56 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +89 -0
- package/dist/server/ipc.d.ts +18 -0
- package/dist/server/ipc.js +159 -0
- package/dist/server/subscriptions.d.ts +18 -0
- package/dist/server/subscriptions.js +114 -0
- package/package.json +124 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { getOutputOptions, getSelectedExchange, getVerbose } from "../cli/program.js";
|
|
2
|
+
import { output } from "../cli/output.js";
|
|
3
|
+
import { parsePositiveInt } from "../lib/validate.js";
|
|
4
|
+
import { loadConfig } from "../lib/config.js";
|
|
5
|
+
import { getAvailableExchanges } from "../lib/exchange.js";
|
|
6
|
+
import { generateAdapterCapabilityReport } from "../adapters/certification.js";
|
|
7
|
+
import { getUIDemoJsonPreview } from "./ui/demo.js";
|
|
8
|
+
import { formatStatusLine, highlighter, startSpinner, } from "../cli/experience.js";
|
|
9
|
+
function getErrorMessage(err) {
|
|
10
|
+
return err instanceof Error ? err.message : String(err);
|
|
11
|
+
}
|
|
12
|
+
function formatCheckMessage(check) {
|
|
13
|
+
const duration = highlighter.dim(`(${check.durationMs}ms)`);
|
|
14
|
+
if (check.ok) {
|
|
15
|
+
if (check.details) {
|
|
16
|
+
return `${check.name} ${duration} ${highlighter.dim(`· ${check.details}`)}`;
|
|
17
|
+
}
|
|
18
|
+
return `${check.name} ${duration}`;
|
|
19
|
+
}
|
|
20
|
+
return `${check.name} ${duration}: ${check.error}`;
|
|
21
|
+
}
|
|
22
|
+
async function runCheck(definition, showProgress) {
|
|
23
|
+
const startedAt = performance.now();
|
|
24
|
+
const spinner = showProgress ? startSpinner(`Checking ${definition.name}...`) : null;
|
|
25
|
+
try {
|
|
26
|
+
const details = await definition.run();
|
|
27
|
+
const result = {
|
|
28
|
+
name: definition.name,
|
|
29
|
+
recommendation: definition.recommendation,
|
|
30
|
+
ok: true,
|
|
31
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
32
|
+
details,
|
|
33
|
+
};
|
|
34
|
+
spinner?.succeed(formatCheckMessage(result));
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const result = {
|
|
39
|
+
name: definition.name,
|
|
40
|
+
recommendation: definition.recommendation,
|
|
41
|
+
ok: false,
|
|
42
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
43
|
+
error: getErrorMessage(err),
|
|
44
|
+
};
|
|
45
|
+
spinner?.fail(formatCheckMessage(result));
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function checkNodeVersion() {
|
|
50
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
51
|
+
if (!Number.isFinite(major) || major < 18) {
|
|
52
|
+
throw new Error(`Node >=18 required, found ${process.versions.node}`);
|
|
53
|
+
}
|
|
54
|
+
return process.versions.node;
|
|
55
|
+
}
|
|
56
|
+
function checkConfigLoad(exchange) {
|
|
57
|
+
const config = loadConfig(false, exchange);
|
|
58
|
+
return config.testnet ? "loaded (testnet)" : "loaded (mainnet)";
|
|
59
|
+
}
|
|
60
|
+
function checkExchangeRegistry() {
|
|
61
|
+
const exchanges = getAvailableExchanges();
|
|
62
|
+
if (exchanges.length === 0) {
|
|
63
|
+
throw new Error("No exchanges registered");
|
|
64
|
+
}
|
|
65
|
+
return `${exchanges.length} exchanges`;
|
|
66
|
+
}
|
|
67
|
+
function checkCapabilityReport() {
|
|
68
|
+
const report = generateAdapterCapabilityReport();
|
|
69
|
+
if (!Array.isArray(report.exchanges) || report.exchanges.length === 0) {
|
|
70
|
+
throw new Error("Capability report is empty");
|
|
71
|
+
}
|
|
72
|
+
const exchangesWithIssues = report.exchanges.filter((exchange) => exchange.errorSemantics.issues.length > 0).length;
|
|
73
|
+
return `${report.exchanges.length} exchanges, ${exchangesWithIssues} issue sets`;
|
|
74
|
+
}
|
|
75
|
+
function checkUIDemoContract() {
|
|
76
|
+
const preview = getUIDemoJsonPreview(500);
|
|
77
|
+
if (preview.command !== "ui demo") {
|
|
78
|
+
throw new Error("Unexpected ui demo command field");
|
|
79
|
+
}
|
|
80
|
+
if (preview.interactive !== false) {
|
|
81
|
+
throw new Error("Expected interactive=false in JSON preview");
|
|
82
|
+
}
|
|
83
|
+
return "ui demo JSON shape OK";
|
|
84
|
+
}
|
|
85
|
+
async function checkNetwork(timeoutMs) {
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
88
|
+
try {
|
|
89
|
+
const response = await fetch("https://api.hyperliquid.xyz/info", {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "content-type": "application/json" },
|
|
92
|
+
body: JSON.stringify({ type: "meta" }),
|
|
93
|
+
signal: controller.signal,
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error(`HTTP ${response.status}`);
|
|
97
|
+
}
|
|
98
|
+
const payload = (await response.json());
|
|
99
|
+
const universe = Array.isArray(payload.universe) ? payload.universe : [];
|
|
100
|
+
if (universe.length === 0) {
|
|
101
|
+
throw new Error("Received empty market universe");
|
|
102
|
+
}
|
|
103
|
+
return `${universe.length} markets`;
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
clearTimeout(timeout);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function printFailureDetails(report) {
|
|
110
|
+
const failedChecks = report.checks.filter((check) => !check.ok);
|
|
111
|
+
if (failedChecks.length === 0)
|
|
112
|
+
return;
|
|
113
|
+
for (const check of failedChecks) {
|
|
114
|
+
console.log(` ${highlighter.error("✗")} ${check.name}: ${check.error}`);
|
|
115
|
+
console.log(` ${highlighter.dim("→")} ${check.recommendation}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function buildChecks(exchange, includeNetwork, timeoutMs) {
|
|
119
|
+
const checks = [
|
|
120
|
+
{
|
|
121
|
+
name: "node version",
|
|
122
|
+
recommendation: "Install Node.js 18+ (Node 20+ recommended).",
|
|
123
|
+
run: () => checkNodeVersion(),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "config load",
|
|
127
|
+
recommendation: "Verify your .env and settings with `perps config show`.",
|
|
128
|
+
run: () => checkConfigLoad(exchange),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "exchange registry",
|
|
132
|
+
recommendation: "Reinstall dependencies so adapters register correctly (`corepack pnpm install`).",
|
|
133
|
+
run: () => checkExchangeRegistry(),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "capability report",
|
|
137
|
+
recommendation: "Run `perps exchange capabilities --json` to inspect adapter health details.",
|
|
138
|
+
run: () => checkCapabilityReport(),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "ui demo contract",
|
|
142
|
+
recommendation: "Rebuild the CLI (`corepack pnpm build`) to restore UI demo output shape.",
|
|
143
|
+
run: () => checkUIDemoContract(),
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
if (includeNetwork) {
|
|
147
|
+
checks.push({
|
|
148
|
+
name: "network reachability",
|
|
149
|
+
recommendation: "Verify outbound HTTPS access to https://api.hyperliquid.xyz/info or retry with a larger --timeout.",
|
|
150
|
+
run: () => checkNetwork(timeoutMs),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return checks;
|
|
154
|
+
}
|
|
155
|
+
export function registerDoctorCommand(program) {
|
|
156
|
+
program
|
|
157
|
+
.command("doctor")
|
|
158
|
+
.description("Run runtime health checks")
|
|
159
|
+
.option("--network", "Include live network reachability check")
|
|
160
|
+
.option("--timeout <ms>", "Network timeout in milliseconds", "15000")
|
|
161
|
+
.action(async function (options) {
|
|
162
|
+
const outputOpts = getOutputOptions(this);
|
|
163
|
+
const verbose = getVerbose(this);
|
|
164
|
+
const includeNetwork = options.network ?? false;
|
|
165
|
+
const timeoutMs = parsePositiveInt(options.timeout, "timeout", 15_000);
|
|
166
|
+
const selectedExchange = getSelectedExchange(this);
|
|
167
|
+
const checks = buildChecks(selectedExchange, includeNetwork, timeoutMs);
|
|
168
|
+
const results = [];
|
|
169
|
+
for (const check of checks) {
|
|
170
|
+
results.push(await runCheck(check, verbose && !outputOpts.json));
|
|
171
|
+
}
|
|
172
|
+
const report = {
|
|
173
|
+
ok: results.every((check) => check.ok),
|
|
174
|
+
generatedAt: new Date().toISOString(),
|
|
175
|
+
includeNetwork,
|
|
176
|
+
nodeVersion: process.versions.node,
|
|
177
|
+
exchange: selectedExchange,
|
|
178
|
+
checks: results,
|
|
179
|
+
};
|
|
180
|
+
if (outputOpts.json) {
|
|
181
|
+
output(report, outputOpts);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const passed = results.filter((c) => c.ok).length;
|
|
185
|
+
const total = results.length;
|
|
186
|
+
if (verbose) {
|
|
187
|
+
console.log("");
|
|
188
|
+
}
|
|
189
|
+
if (report.ok) {
|
|
190
|
+
console.log(formatStatusLine("success", `${passed}/${total} checks passed`));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.log(formatStatusLine("error", `${passed}/${total} checks passed`));
|
|
194
|
+
printFailureDetails(report);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (!report.ok) {
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { getOutputOptions } from "../../cli/program.js";
|
|
4
|
+
import { output } from "../../cli/output.js";
|
|
5
|
+
import { generateAdapterCapabilityReport } from "../../adapters/certification.js";
|
|
6
|
+
import { withJsonContract } from "../../lib/contracts.js";
|
|
7
|
+
function buildCapabilityRows(report) {
|
|
8
|
+
return report.exchanges.map((exchange) => {
|
|
9
|
+
return {
|
|
10
|
+
id: exchange.id,
|
|
11
|
+
name: exchange.name,
|
|
12
|
+
marketData: exchange.capabilities.marketData.level,
|
|
13
|
+
authenticatedReads: exchange.capabilities.authenticatedReads.level,
|
|
14
|
+
orderLifecycle: exchange.capabilities.orderLifecycle.level,
|
|
15
|
+
orderCancellation: exchange.capabilities.orderCancellation.level,
|
|
16
|
+
subscriptions: exchange.capabilities.subscriptions.level,
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function registerExchangeCommands(program) {
|
|
21
|
+
const exchange = program.command("exchange").description("Exchange metadata and capabilities");
|
|
22
|
+
exchange
|
|
23
|
+
.command("capabilities")
|
|
24
|
+
.description("Show machine-readable adapter capability status")
|
|
25
|
+
.action(function () {
|
|
26
|
+
const outputOpts = getOutputOptions(this);
|
|
27
|
+
const report = generateAdapterCapabilityReport();
|
|
28
|
+
const rows = buildCapabilityRows(report);
|
|
29
|
+
if (outputOpts.json) {
|
|
30
|
+
output(withJsonContract("exchange.capabilities.report", report), outputOpts);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log("");
|
|
34
|
+
console.log("Exchange".padEnd(18) + "Market".padEnd(10) + "Auth".padEnd(10) + "Orders".padEnd(10) + "Cancel".padEnd(10) + "Subs");
|
|
35
|
+
console.log("─".repeat(66));
|
|
36
|
+
for (const row of rows) {
|
|
37
|
+
console.log(row.name.padEnd(18) +
|
|
38
|
+
row.marketData.padEnd(10) +
|
|
39
|
+
row.authenticatedReads.padEnd(10) +
|
|
40
|
+
row.orderLifecycle.padEnd(10) +
|
|
41
|
+
row.orderCancellation.padEnd(10) +
|
|
42
|
+
row.subscriptions);
|
|
43
|
+
}
|
|
44
|
+
console.log("");
|
|
45
|
+
});
|
|
46
|
+
exchange
|
|
47
|
+
.command("parity")
|
|
48
|
+
.description("Generate adapter parity dashboard and optionally write reports")
|
|
49
|
+
.option("--write <dir>", "Write markdown + json parity report artifacts to a directory")
|
|
50
|
+
.option("--strict", "Exit non-zero if any exchange is not fully featured")
|
|
51
|
+
.action(function () {
|
|
52
|
+
const outputOpts = getOutputOptions(this);
|
|
53
|
+
const opts = this.opts();
|
|
54
|
+
const report = generateAdapterCapabilityReport();
|
|
55
|
+
const rows = buildCapabilityRows(report);
|
|
56
|
+
const fullCount = rows.filter((row) => row.marketData === "full" &&
|
|
57
|
+
row.authenticatedReads === "full" &&
|
|
58
|
+
row.orderLifecycle === "full" &&
|
|
59
|
+
row.orderCancellation === "full" &&
|
|
60
|
+
row.subscriptions === "full").length;
|
|
61
|
+
const fullyFeatured = fullCount === rows.length;
|
|
62
|
+
if (opts.write) {
|
|
63
|
+
const targetDir = resolve(opts.write);
|
|
64
|
+
mkdirSync(targetDir, { recursive: true });
|
|
65
|
+
const jsonPath = resolve(targetDir, "adapter-parity.json");
|
|
66
|
+
const markdownPath = resolve(targetDir, "adapter-parity.md");
|
|
67
|
+
writeFileSync(jsonPath, `${JSON.stringify(report, null, 2)}\n`);
|
|
68
|
+
writeFileSync(markdownPath, [
|
|
69
|
+
"# Adapter Parity Dashboard",
|
|
70
|
+
"",
|
|
71
|
+
`Generated: ${new Date(report.generatedAt).toISOString()}`,
|
|
72
|
+
"",
|
|
73
|
+
"| exchange | market | auth | order | cancel | subs |",
|
|
74
|
+
"| --- | --- | --- | --- | --- | --- |",
|
|
75
|
+
...rows.map((row) => `| ${row.id} | ${row.marketData} | ${row.authenticatedReads} | ${row.orderLifecycle} | ${row.orderCancellation} | ${row.subscriptions} |`),
|
|
76
|
+
"",
|
|
77
|
+
].join("\n"));
|
|
78
|
+
}
|
|
79
|
+
const payload = {
|
|
80
|
+
generatedAt: report.generatedAt,
|
|
81
|
+
fullyFeatured,
|
|
82
|
+
fullCount,
|
|
83
|
+
total: rows.length,
|
|
84
|
+
rows,
|
|
85
|
+
report,
|
|
86
|
+
};
|
|
87
|
+
if (outputOpts.json) {
|
|
88
|
+
output(withJsonContract("exchange.parity.dashboard", payload), outputOpts);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log("");
|
|
92
|
+
console.log(`Fully featured adapters: ${fullCount}/${rows.length}`);
|
|
93
|
+
for (const row of rows) {
|
|
94
|
+
console.log(` ${row.id.padEnd(12)} market=${row.marketData} auth=${row.authenticatedReads} order=${row.orderLifecycle} cancel=${row.orderCancellation} subs=${row.subscriptions}`);
|
|
95
|
+
}
|
|
96
|
+
if (opts.write) {
|
|
97
|
+
console.log(`\nReport files written to ${resolve(opts.write)}\n`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log("");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (opts.strict && !fullyFeatured) {
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { registerAccountCommands } from "./account/index.js";
|
|
2
|
+
import { registerMarketsCommands } from "./markets/index.js";
|
|
3
|
+
import { registerAssetCommands } from "./asset/index.js";
|
|
4
|
+
import { registerOrderCommands } from "./order/index.js";
|
|
5
|
+
import { registerConfigCommands } from "./config/index.js";
|
|
6
|
+
import { registerServerCommands } from "./server.js";
|
|
7
|
+
import { registerUpgradeCommand } from "./upgrade.js";
|
|
8
|
+
import { registerArbCommands } from "./arb/index.js";
|
|
9
|
+
import { registerExchangeCommands } from "./exchange/index.js";
|
|
10
|
+
import { registerDataCommands } from "./data/index.js";
|
|
11
|
+
import { registerOperatorCommands } from "./operator/index.js";
|
|
12
|
+
import { registerUICommands } from "./ui/index.js";
|
|
13
|
+
import { registerDoctorCommand } from "./doctor.js";
|
|
14
|
+
import { registerSetupCommands } from "./setup/index.js";
|
|
15
|
+
import { registerAgentCommands } from "./agent/index.js";
|
|
16
|
+
import { registerReferralCommands } from "./referral/index.js";
|
|
17
|
+
import { registerStateCommands } from "./state/index.js";
|
|
18
|
+
import { registerRiskCommands } from "./risk/index.js";
|
|
19
|
+
import { registerCompletionCommand } from "./completion.js";
|
|
20
|
+
import { registerStrategyCommands } from "./strategy/index.js";
|
|
21
|
+
import { registerReplayCommands } from "./replay/index.js";
|
|
22
|
+
import { registerTracesCommands } from "./traces/index.js";
|
|
23
|
+
import { registerSignalCommands } from "./signal/index.js";
|
|
24
|
+
export function registerCommands(program) {
|
|
25
|
+
registerAccountCommands(program);
|
|
26
|
+
registerMarketsCommands(program);
|
|
27
|
+
registerAssetCommands(program);
|
|
28
|
+
registerOrderCommands(program);
|
|
29
|
+
registerArbCommands(program);
|
|
30
|
+
registerOperatorCommands(program);
|
|
31
|
+
registerUICommands(program);
|
|
32
|
+
registerSetupCommands(program);
|
|
33
|
+
registerAgentCommands(program);
|
|
34
|
+
registerConfigCommands(program);
|
|
35
|
+
registerExchangeCommands(program);
|
|
36
|
+
registerDataCommands(program);
|
|
37
|
+
registerReferralCommands(program);
|
|
38
|
+
registerStateCommands(program);
|
|
39
|
+
registerRiskCommands(program);
|
|
40
|
+
registerDoctorCommand(program);
|
|
41
|
+
registerServerCommands(program);
|
|
42
|
+
registerUpgradeCommand(program);
|
|
43
|
+
registerStrategyCommands(program);
|
|
44
|
+
registerReplayCommands(program);
|
|
45
|
+
registerTracesCommands(program);
|
|
46
|
+
registerSignalCommands(program);
|
|
47
|
+
registerCompletionCommand(program);
|
|
48
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markets list command
|
|
3
|
+
* Supports single-exchange listing, cross-exchange grouping (--all),
|
|
4
|
+
* and cross-exchange analysis report (--report)
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
export declare function registerMarketsLsSimpleCommand(markets: Command): void;
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markets list command
|
|
3
|
+
* Supports single-exchange listing, cross-exchange grouping (--all),
|
|
4
|
+
* and cross-exchange analysis report (--report)
|
|
5
|
+
*/
|
|
6
|
+
import { output, outputError } from "../../cli/output.js";
|
|
7
|
+
import { getContext, getOutputOptions, getVerbose } from "../../cli/program.js";
|
|
8
|
+
import { getAvailableExchanges, getExchangeAdapter, getExchangeAdapterById, } from "../../lib/exchange.js";
|
|
9
|
+
import { pLimit } from "../../lib/rate-limit.js";
|
|
10
|
+
export function registerMarketsLsSimpleCommand(markets) {
|
|
11
|
+
markets
|
|
12
|
+
.command("ls")
|
|
13
|
+
.description("List available markets")
|
|
14
|
+
.option("--perp", "Show perpetual markets only")
|
|
15
|
+
.option("--spot", "Show spot markets only")
|
|
16
|
+
.option("--all", "List markets across all exchanges, grouped by asset")
|
|
17
|
+
.option("--report", "Show cross-exchange analysis report (implies --all)")
|
|
18
|
+
.option("--limit <n>", "Number of markets to show (0 for all)", "20")
|
|
19
|
+
.action(async function () {
|
|
20
|
+
const ctx = getContext(this);
|
|
21
|
+
const outputOpts = getOutputOptions(this);
|
|
22
|
+
const verbose = getVerbose(this);
|
|
23
|
+
const opts = this.opts();
|
|
24
|
+
const parsed = parseInt(opts.limit ?? "20", 10);
|
|
25
|
+
const limit = verbose ? 0 : (Number.isFinite(parsed) ? Math.max(0, parsed) : 20);
|
|
26
|
+
if (opts.report) {
|
|
27
|
+
await handleReport(ctx, outputOpts, opts);
|
|
28
|
+
}
|
|
29
|
+
else if (opts.all) {
|
|
30
|
+
await handleAllExchanges(ctx, outputOpts, opts);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
await handleSingleExchange(ctx, outputOpts, opts, limit);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function handleSingleExchange(ctx, outputOpts, opts, limit = 0) {
|
|
38
|
+
const adapter = getExchangeAdapter();
|
|
39
|
+
let connected = false;
|
|
40
|
+
try {
|
|
41
|
+
await adapter.connect({ testnet: ctx.config.testnet });
|
|
42
|
+
connected = true;
|
|
43
|
+
const markets = await adapter.getMarkets();
|
|
44
|
+
let filtered = markets;
|
|
45
|
+
if (opts.perp) {
|
|
46
|
+
filtered = markets.filter((m) => m.type === "perp");
|
|
47
|
+
}
|
|
48
|
+
else if (opts.spot) {
|
|
49
|
+
filtered = markets.filter((m) => m.type === "spot");
|
|
50
|
+
}
|
|
51
|
+
if (outputOpts.json) {
|
|
52
|
+
output(filtered, outputOpts);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const showAll = limit === 0 || limit >= filtered.length;
|
|
56
|
+
const display = showAll ? filtered : filtered.slice(0, limit);
|
|
57
|
+
const remaining = filtered.length - display.length;
|
|
58
|
+
console.log(`\n${adapter.info.name} Markets (${filtered.length}):\n`);
|
|
59
|
+
console.log(" Symbol".padEnd(15) + "Base".padEnd(8) + "Max Lev".padEnd(10) + "Min Size");
|
|
60
|
+
console.log(" " + "-".repeat(50));
|
|
61
|
+
for (const m of display) {
|
|
62
|
+
const row = [
|
|
63
|
+
` ${m.symbol}`.padEnd(15),
|
|
64
|
+
m.baseAsset.padEnd(8),
|
|
65
|
+
`${m.maxLeverage}x`.padEnd(10),
|
|
66
|
+
m.minSize,
|
|
67
|
+
].join("");
|
|
68
|
+
console.log(row);
|
|
69
|
+
}
|
|
70
|
+
if (remaining > 0) {
|
|
71
|
+
console.log(`\n ... and ${remaining} more (--limit 0 or -v to show all)`);
|
|
72
|
+
}
|
|
73
|
+
console.log("");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
outputError(err instanceof Error ? err.message : String(err));
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (connected) {
|
|
82
|
+
await adapter.disconnect().catch(() => undefined);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function fetchAllExchangeMarkets(ctx) {
|
|
87
|
+
const exchangeIds = getAvailableExchanges();
|
|
88
|
+
const connectedAdapters = [];
|
|
89
|
+
const tasks = exchangeIds.map((id) => async () => {
|
|
90
|
+
const adapter = getExchangeAdapterById(id);
|
|
91
|
+
await adapter.connect({ testnet: ctx.config.testnet });
|
|
92
|
+
connectedAdapters.push(adapter);
|
|
93
|
+
const markets = await adapter.getMarkets();
|
|
94
|
+
return { id, name: adapter.info.name, markets };
|
|
95
|
+
});
|
|
96
|
+
const results = await pLimit(tasks, 3);
|
|
97
|
+
const exchangeMarkets = [];
|
|
98
|
+
for (const result of results) {
|
|
99
|
+
if (result.status === "fulfilled") {
|
|
100
|
+
exchangeMarkets.push(result.value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { exchangeMarkets, connectedAdapters };
|
|
104
|
+
}
|
|
105
|
+
function groupByAsset(exchangeMarkets, opts) {
|
|
106
|
+
const assetMap = new Map();
|
|
107
|
+
for (const { id, markets } of exchangeMarkets) {
|
|
108
|
+
let filtered = markets;
|
|
109
|
+
if (opts.perp) {
|
|
110
|
+
filtered = markets.filter((m) => m.type === "perp");
|
|
111
|
+
}
|
|
112
|
+
else if (opts.spot) {
|
|
113
|
+
filtered = markets.filter((m) => m.type === "spot");
|
|
114
|
+
}
|
|
115
|
+
for (const m of filtered) {
|
|
116
|
+
const existing = assetMap.get(m.baseAsset);
|
|
117
|
+
if (existing) {
|
|
118
|
+
existing.exchanges.push(id);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
assetMap.set(m.baseAsset, { symbol: m.symbol, exchanges: [id] });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return assetMap;
|
|
126
|
+
}
|
|
127
|
+
function sortedAssets(assetMap) {
|
|
128
|
+
return [...assetMap.entries()].sort((a, b) => {
|
|
129
|
+
const countDiff = b[1].exchanges.length - a[1].exchanges.length;
|
|
130
|
+
if (countDiff !== 0)
|
|
131
|
+
return countDiff;
|
|
132
|
+
return a[0].localeCompare(b[0]);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async function handleAllExchanges(ctx, outputOpts, opts) {
|
|
136
|
+
let connectedAdapters = [];
|
|
137
|
+
try {
|
|
138
|
+
const fetched = await fetchAllExchangeMarkets(ctx);
|
|
139
|
+
connectedAdapters = fetched.connectedAdapters;
|
|
140
|
+
const assetMap = groupByAsset(fetched.exchangeMarkets, opts);
|
|
141
|
+
const sorted = sortedAssets(assetMap);
|
|
142
|
+
if (outputOpts.json) {
|
|
143
|
+
const jsonData = sorted.map(([asset, info]) => ({
|
|
144
|
+
asset,
|
|
145
|
+
symbol: info.symbol,
|
|
146
|
+
exchanges: info.exchanges,
|
|
147
|
+
count: info.exchanges.length,
|
|
148
|
+
}));
|
|
149
|
+
output(jsonData, outputOpts);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const totalExchanges = fetched.exchangeMarkets.length;
|
|
153
|
+
console.log(`\nCross-Exchange Markets (${sorted.length} assets, ${totalExchanges} exchanges):\n`);
|
|
154
|
+
console.log(" " + "Asset".padEnd(10) + "Symbol".padEnd(14) + "Exchanges");
|
|
155
|
+
console.log(" " + "-".repeat(60));
|
|
156
|
+
for (const [asset, info] of sorted) {
|
|
157
|
+
const row = " " + asset.padEnd(10) + info.symbol.padEnd(14) + info.exchanges.join(" ");
|
|
158
|
+
console.log(row);
|
|
159
|
+
}
|
|
160
|
+
console.log("");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
outputError(err instanceof Error ? err.message : String(err));
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
for (const adapter of connectedAdapters) {
|
|
169
|
+
await adapter.disconnect().catch(() => undefined);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function handleReport(ctx, outputOpts, opts) {
|
|
174
|
+
let connectedAdapters = [];
|
|
175
|
+
try {
|
|
176
|
+
const fetched = await fetchAllExchangeMarkets(ctx);
|
|
177
|
+
connectedAdapters = fetched.connectedAdapters;
|
|
178
|
+
const { exchangeMarkets } = fetched;
|
|
179
|
+
const assetMap = groupByAsset(exchangeMarkets, opts);
|
|
180
|
+
const sorted = sortedAssets(assetMap);
|
|
181
|
+
const totalAssets = sorted.length;
|
|
182
|
+
const totalExchanges = exchangeMarkets.length;
|
|
183
|
+
const multiExchange = sorted.filter(([, info]) => info.exchanges.length >= 2);
|
|
184
|
+
const allExchange = sorted.filter(([, info]) => info.exchanges.length === totalExchanges);
|
|
185
|
+
const exclusive = sorted.filter(([, info]) => info.exchanges.length === 1);
|
|
186
|
+
// Per-exchange market counts
|
|
187
|
+
const exchangeCounts = [];
|
|
188
|
+
for (const ex of exchangeMarkets) {
|
|
189
|
+
let filtered = ex.markets;
|
|
190
|
+
if (opts.perp) {
|
|
191
|
+
filtered = ex.markets.filter((m) => m.type === "perp");
|
|
192
|
+
}
|
|
193
|
+
else if (opts.spot) {
|
|
194
|
+
filtered = ex.markets.filter((m) => m.type === "spot");
|
|
195
|
+
}
|
|
196
|
+
exchangeCounts.push({ exchange: ex.id, markets: filtered.length });
|
|
197
|
+
}
|
|
198
|
+
exchangeCounts.sort((a, b) => b.markets - a.markets);
|
|
199
|
+
// Exclusives grouped by exchange
|
|
200
|
+
const exclusivesByExchange = new Map();
|
|
201
|
+
for (const [asset, info] of exclusive) {
|
|
202
|
+
const ex = info.exchanges[0];
|
|
203
|
+
const list = exclusivesByExchange.get(ex) ?? [];
|
|
204
|
+
list.push(asset);
|
|
205
|
+
exclusivesByExchange.set(ex, list);
|
|
206
|
+
}
|
|
207
|
+
if (outputOpts.json) {
|
|
208
|
+
const report = {
|
|
209
|
+
summary: {
|
|
210
|
+
totalAssets,
|
|
211
|
+
totalExchanges,
|
|
212
|
+
arbCandidates: multiExchange.length,
|
|
213
|
+
onAllExchanges: allExchange.length,
|
|
214
|
+
exclusiveToOne: exclusive.length,
|
|
215
|
+
},
|
|
216
|
+
exchanges: exchangeCounts,
|
|
217
|
+
arbOpportunities: multiExchange.map(([asset, info]) => ({
|
|
218
|
+
asset,
|
|
219
|
+
symbol: info.symbol,
|
|
220
|
+
exchanges: info.exchanges,
|
|
221
|
+
count: info.exchanges.length,
|
|
222
|
+
})),
|
|
223
|
+
exclusives: Object.fromEntries(exclusivesByExchange),
|
|
224
|
+
};
|
|
225
|
+
output(report, outputOpts);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
console.log("\n" + "=".repeat(64));
|
|
229
|
+
console.log(" CROSS-EXCHANGE MARKET REPORT");
|
|
230
|
+
console.log("=".repeat(64));
|
|
231
|
+
console.log("\n Summary");
|
|
232
|
+
console.log(" " + "-".repeat(40));
|
|
233
|
+
console.log(` Total assets: ${totalAssets}`);
|
|
234
|
+
console.log(` Exchanges online: ${totalExchanges}`);
|
|
235
|
+
console.log(` Arb candidates: ${multiExchange.length} (listed on 2+ exchanges)`);
|
|
236
|
+
console.log(` On all exchanges: ${allExchange.length}`);
|
|
237
|
+
console.log(` Exclusive to one: ${exclusive.length}`);
|
|
238
|
+
console.log("\n Markets per Exchange");
|
|
239
|
+
console.log(" " + "-".repeat(40));
|
|
240
|
+
for (const { exchange, markets } of exchangeCounts) {
|
|
241
|
+
console.log(` ${exchange.padEnd(20)} ${markets}`);
|
|
242
|
+
}
|
|
243
|
+
if (multiExchange.length > 0) {
|
|
244
|
+
console.log("\n Arb Opportunities (2+ exchanges)");
|
|
245
|
+
console.log(" " + "-".repeat(60));
|
|
246
|
+
console.log(" " + "Asset".padEnd(10) + "Symbol".padEnd(14) + "#".padEnd(4) + "Exchanges");
|
|
247
|
+
console.log(" " + "-".repeat(60));
|
|
248
|
+
for (const [asset, info] of multiExchange) {
|
|
249
|
+
console.log(" " +
|
|
250
|
+
asset.padEnd(10) +
|
|
251
|
+
info.symbol.padEnd(14) +
|
|
252
|
+
String(info.exchanges.length).padEnd(4) +
|
|
253
|
+
info.exchanges.join(" "));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (exclusive.length > 0) {
|
|
257
|
+
console.log("\n Exchange Exclusives");
|
|
258
|
+
console.log(" " + "-".repeat(60));
|
|
259
|
+
for (const [ex, assets] of exclusivesByExchange) {
|
|
260
|
+
const preview = assets.slice(0, 10).join(", ");
|
|
261
|
+
const suffix = assets.length > 10 ? ` (+${assets.length - 10} more)` : "";
|
|
262
|
+
console.log(` ${ex.padEnd(16)} ${assets.length} assets: ${preview}${suffix}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
console.log("\n" + "=".repeat(64) + "\n");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
outputError(err instanceof Error ? err.message : String(err));
|
|
270
|
+
process.exitCode = 1;
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
for (const adapter of connectedAdapters) {
|
|
274
|
+
await adapter.disconnect().catch(() => undefined);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|