perp-cli 0.12.15 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -31
- package/dist/_stderr-filter.js +0 -1
- package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
- package/dist/agent-wallet/lighter-keystore.js +92 -10
- package/dist/agent-wallet/passphrase.d.ts +1 -1
- package/dist/agent-wallet/passphrase.js +16 -3
- package/dist/agent-wallet/store.d.ts +37 -4
- package/dist/agent-wallet/store.js +132 -0
- package/dist/agent-wallet/venue-keystore.d.ts +14 -0
- package/dist/agent-wallet/venue-keystore.js +121 -0
- package/dist/api/public/hyperliquid.js +4 -4
- package/dist/api/public/lighter.js +5 -5
- package/dist/api/public/pacifica.d.ts +0 -1
- package/dist/api/public/pacifica.js +4 -13
- package/dist/api/public/urls.d.ts +9 -1
- package/dist/api/public/urls.js +35 -13
- package/dist/arb/file-lock.d.ts +11 -0
- package/dist/arb/file-lock.js +74 -0
- package/dist/arb/index.d.ts +2 -2
- package/dist/arb/index.js +2 -2
- package/dist/arb/pending-entry.d.ts +32 -0
- package/dist/arb/pending-entry.js +77 -0
- package/dist/arb/sizing.d.ts +6 -13
- package/dist/arb/sizing.js +12 -51
- package/dist/arb/state.d.ts +45 -12
- package/dist/arb/state.js +214 -42
- package/dist/arb/utils.d.ts +20 -6
- package/dist/arb/utils.js +43 -39
- package/dist/bot/conditions.d.ts +4 -4
- package/dist/bot/conditions.js +11 -6
- package/dist/bot/config.d.ts +17 -1
- package/dist/bot/config.js +48 -12
- package/dist/bot/engine.d.ts +61 -2
- package/dist/bot/engine.js +610 -144
- package/dist/bot/fill-size.d.ts +7 -0
- package/dist/bot/fill-size.js +18 -0
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/perp-order.d.ts +99 -0
- package/dist/bot/perp-order.js +299 -0
- package/dist/bot/presets.d.ts +7 -0
- package/dist/bot/presets.js +17 -0
- package/dist/bot/quarantine.d.ts +75 -0
- package/dist/bot/quarantine.js +190 -0
- package/dist/bot/quote-rules.d.ts +23 -0
- package/dist/bot/quote-rules.js +69 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
- package/dist/bot/strategies/avellaneda-mm.js +72 -70
- package/dist/bot/strategies/basis-arb.js +2 -0
- package/dist/bot/strategies/dca-strategy.d.ts +2 -0
- package/dist/bot/strategies/dca-strategy.js +5 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
- package/dist/bot/strategies/funding-arb-strategy.js +571 -380
- package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
- package/dist/bot/strategies/funding-arb-utils.js +159 -195
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +258 -47
- package/dist/bot/strategies/funding-auto.js +215 -194
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
- package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
- package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
- package/dist/bot/strategies/spot-perp-executor.js +319 -120
- package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
- package/dist/bot/strategies/spot-perp-state.js +70 -0
- package/dist/bot/strategy-types.d.ts +6 -0
- package/dist/bot/tui/BotDashboard.d.ts +7 -2
- package/dist/bot/tui/BotDashboard.js +6 -3
- package/dist/bridge-engine.d.ts +120 -19
- package/dist/bridge-engine.js +710 -422
- package/dist/bridge-signers.d.ts +54 -0
- package/dist/bridge-signers.js +161 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +11 -0
- package/dist/cli-flags.d.ts +50 -0
- package/dist/cli-flags.js +104 -0
- package/dist/cli-program.d.ts +35 -0
- package/dist/cli-program.js +286 -0
- package/dist/cli-spec.js +1 -1
- package/dist/client-id-tracker.js +11 -2
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +74 -84
- package/dist/commands/agent-import.d.ts +114 -0
- package/dist/commands/agent-import.js +794 -0
- package/dist/commands/agent.d.ts +232 -2
- package/dist/commands/agent.js +1858 -955
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +31 -28
- package/dist/commands/arb/index.d.ts +60 -1
- package/dist/commands/arb/index.js +381 -384
- package/dist/commands/arb-auto.d.ts +132 -4
- package/dist/commands/arb-auto.js +1474 -967
- package/dist/commands/arb.d.ts +3 -1
- package/dist/commands/arb.js +62 -30
- package/dist/commands/bot.js +127 -47
- package/dist/commands/bridge.d.ts +2 -1
- package/dist/commands/bridge.js +339 -192
- package/dist/commands/builder.d.ts +55 -0
- package/dist/commands/builder.js +387 -0
- package/dist/commands/funds-onchain.d.ts +32 -0
- package/dist/commands/funds-onchain.js +286 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +939 -245
- package/dist/commands/history.js +72 -45
- package/dist/commands/init.d.ts +107 -15
- package/dist/commands/init.js +1116 -249
- package/dist/commands/jobs.d.ts +2 -1
- package/dist/commands/jobs.js +77 -15
- package/dist/commands/manage.js +136 -91
- package/dist/commands/market.d.ts +4 -1
- package/dist/commands/market.js +35 -32
- package/dist/commands/outcome.d.ts +10 -0
- package/dist/commands/outcome.js +396 -0
- package/dist/commands/plan.js +52 -12
- package/dist/commands/portfolio.d.ts +1 -0
- package/dist/commands/portfolio.js +60 -72
- package/dist/commands/rebalance.d.ts +2 -1
- package/dist/commands/rebalance.js +33 -43
- package/dist/commands/risk.js +46 -19
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +246 -51
- package/dist/commands/trade.d.ts +50 -0
- package/dist/commands/trade.js +1943 -695
- package/dist/commands/wallet.d.ts +30 -1
- package/dist/commands/wallet.js +316 -337
- package/dist/config.d.ts +60 -9
- package/dist/config.js +252 -82
- package/dist/constants.d.ts +29 -4
- package/dist/constants.js +62 -20
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.js +9 -6
- package/dist/dashboard/ui.js +6 -4
- package/dist/dashboard/ws-feeds.js +20 -8
- package/dist/dex-asset-map.js +5 -3
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +194 -0
- package/dist/event-stream.js +75 -19
- package/dist/evm-master-tx.d.ts +59 -0
- package/dist/evm-master-tx.js +154 -0
- package/dist/exchanges/adapter-network.d.ts +10 -0
- package/dist/exchanges/adapter-network.js +10 -0
- package/dist/exchanges/arcus/adapter.d.ts +196 -0
- package/dist/exchanges/arcus/adapter.js +1092 -0
- package/dist/exchanges/arcus/agent.d.ts +55 -0
- package/dist/exchanges/arcus/agent.js +188 -0
- package/dist/exchanges/arcus/client.d.ts +63 -0
- package/dist/exchanges/arcus/client.js +285 -0
- package/dist/exchanges/arcus/public.d.ts +65 -0
- package/dist/exchanges/arcus/public.js +138 -0
- package/dist/exchanges/arcus/signing.d.ts +176 -0
- package/dist/exchanges/arcus/signing.js +288 -0
- package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
- package/dist/exchanges/arcus/spot-chain.js +241 -0
- package/dist/exchanges/arcus/spot-router.d.ts +177 -0
- package/dist/exchanges/arcus/spot-router.js +183 -0
- package/dist/exchanges/arcus/spot.d.ts +109 -0
- package/dist/exchanges/arcus/spot.js +645 -0
- package/dist/exchanges/aster-funds.d.ts +127 -0
- package/dist/exchanges/aster-funds.js +252 -0
- package/dist/exchanges/aster-nonce.d.ts +2 -0
- package/dist/exchanges/aster-nonce.js +107 -0
- package/dist/exchanges/aster-spot.d.ts +117 -0
- package/dist/exchanges/aster-spot.js +686 -0
- package/dist/exchanges/aster-typed-data.d.ts +133 -7
- package/dist/exchanges/aster-typed-data.js +121 -7
- package/dist/exchanges/aster.d.ts +219 -6
- package/dist/exchanges/aster.js +1023 -153
- package/dist/exchanges/builder-fee.d.ts +254 -0
- package/dist/exchanges/builder-fee.js +421 -0
- package/dist/exchanges/canonical-symbols.d.ts +32 -0
- package/dist/exchanges/canonical-symbols.js +96 -0
- package/dist/exchanges/capabilities.d.ts +7 -1
- package/dist/exchanges/capabilities.js +7 -0
- package/dist/exchanges/hl-signing.d.ts +165 -0
- package/dist/exchanges/hl-signing.js +158 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
- package/dist/exchanges/hyperliquid-outcome.js +646 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
- package/dist/exchanges/hyperliquid-spot.js +308 -117
- package/dist/exchanges/hyperliquid.d.ts +462 -57
- package/dist/exchanges/hyperliquid.js +1575 -449
- package/dist/exchanges/index.d.ts +6 -0
- package/dist/exchanges/index.js +4 -0
- package/dist/exchanges/interface.d.ts +63 -10
- package/dist/exchanges/interface.js +18 -3
- package/dist/exchanges/ioc-outcome.d.ts +44 -0
- package/dist/exchanges/ioc-outcome.js +57 -0
- package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
- package/dist/exchanges/legacy-trading-v2.js +940 -0
- package/dist/exchanges/lighter-public.d.ts +43 -0
- package/dist/exchanges/lighter-public.js +276 -0
- package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
- package/dist/exchanges/lighter-rh-deposit.js +185 -0
- package/dist/exchanges/lighter-signer.d.ts +123 -0
- package/dist/exchanges/lighter-signer.js +215 -0
- package/dist/exchanges/lighter-spot.d.ts +91 -13
- package/dist/exchanges/lighter-spot.js +595 -233
- package/dist/exchanges/lighter.d.ts +611 -39
- package/dist/exchanges/lighter.js +1890 -649
- package/dist/exchanges/ondo/adapter.d.ts +166 -0
- package/dist/exchanges/ondo/adapter.js +835 -0
- package/dist/exchanges/ondo/agent.d.ts +31 -0
- package/dist/exchanges/ondo/agent.js +101 -0
- package/dist/exchanges/ondo/client.d.ts +60 -0
- package/dist/exchanges/ondo/client.js +297 -0
- package/dist/exchanges/ondo/funds.d.ts +122 -0
- package/dist/exchanges/ondo/funds.js +290 -0
- package/dist/exchanges/ondo/public.d.ts +95 -0
- package/dist/exchanges/ondo/public.js +214 -0
- package/dist/exchanges/ondo/spot.d.ts +127 -0
- package/dist/exchanges/ondo/spot.js +392 -0
- package/dist/exchanges/outcome-interface.d.ts +140 -0
- package/dist/exchanges/outcome-interface.js +16 -0
- package/dist/exchanges/pacifica-spot.d.ts +83 -0
- package/dist/exchanges/pacifica-spot.js +324 -0
- package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
- package/dist/exchanges/pacifica-typed-data.js +57 -9
- package/dist/exchanges/pacifica.d.ts +288 -4
- package/dist/exchanges/pacifica.js +1013 -76
- package/dist/exchanges/precision.d.ts +47 -0
- package/dist/exchanges/precision.js +209 -0
- package/dist/exchanges/qfex/adapter.d.ts +341 -0
- package/dist/exchanges/qfex/adapter.js +1383 -0
- package/dist/exchanges/qfex/agent.d.ts +58 -0
- package/dist/exchanges/qfex/agent.js +188 -0
- package/dist/exchanges/qfex/client.d.ts +54 -0
- package/dist/exchanges/qfex/client.js +138 -0
- package/dist/exchanges/qfex/errors.d.ts +70 -0
- package/dist/exchanges/qfex/errors.js +236 -0
- package/dist/exchanges/qfex/public.d.ts +122 -0
- package/dist/exchanges/qfex/public.js +268 -0
- package/dist/exchanges/qfex/signing.d.ts +160 -0
- package/dist/exchanges/qfex/signing.js +283 -0
- package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
- package/dist/exchanges/qfex/ws-transport.js +798 -0
- package/dist/exchanges/rate-limiter.d.ts +12 -0
- package/dist/exchanges/rate-limiter.js +64 -0
- package/dist/exchanges/registry.d.ts +14 -2
- package/dist/exchanges/registry.js +20 -32
- package/dist/exchanges/slippage.d.ts +37 -0
- package/dist/exchanges/slippage.js +70 -0
- package/dist/exchanges/spot-factory.d.ts +166 -0
- package/dist/exchanges/spot-factory.js +431 -0
- package/dist/exchanges/spot-interface.d.ts +98 -4
- package/dist/exchanges/spot-interface.js +27 -1
- package/dist/exchanges/spot-result.d.ts +79 -0
- package/dist/exchanges/spot-result.js +273 -0
- package/dist/exchanges/trading-v2.d.ts +117 -0
- package/dist/exchanges/trading-v2.js +301 -0
- package/dist/exchanges/venue-types.d.ts +231 -0
- package/dist/exchanges/venue-types.js +87 -0
- package/dist/exchanges/venue.d.ts +211 -0
- package/dist/exchanges/venue.js +132 -0
- package/dist/exchanges/venues/arcus.d.ts +8 -0
- package/dist/exchanges/venues/arcus.js +109 -0
- package/dist/exchanges/venues/aster.d.ts +49 -0
- package/dist/exchanges/venues/aster.js +176 -0
- package/dist/exchanges/venues/create-helpers.d.ts +18 -0
- package/dist/exchanges/venues/create-helpers.js +199 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
- package/dist/exchanges/venues/hyperliquid.js +126 -0
- package/dist/exchanges/venues/index.d.ts +16 -0
- package/dist/exchanges/venues/index.js +26 -0
- package/dist/exchanges/venues/lighter-family.d.ts +92 -0
- package/dist/exchanges/venues/lighter-family.js +166 -0
- package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
- package/dist/exchanges/venues/lighter-rh.js +58 -0
- package/dist/exchanges/venues/lighter.d.ts +6 -0
- package/dist/exchanges/venues/lighter.js +55 -0
- package/dist/exchanges/venues/ondo.d.ts +34 -0
- package/dist/exchanges/venues/ondo.js +140 -0
- package/dist/exchanges/venues/pacifica.d.ts +8 -0
- package/dist/exchanges/venues/pacifica.js +95 -0
- package/dist/exchanges/venues/qfex.d.ts +28 -0
- package/dist/exchanges/venues/qfex.js +132 -0
- package/dist/execution-log.js +10 -2
- package/dist/funding/legacy-public.d.ts +31 -0
- package/dist/funding/legacy-public.js +369 -0
- package/dist/funding/normalize.d.ts +28 -16
- package/dist/funding/normalize.js +61 -29
- package/dist/funding/rates.d.ts +46 -11
- package/dist/funding/rates.js +228 -277
- package/dist/guardrail/perp-guardrail.js +0 -0
- package/dist/index.js +157 -682
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +14 -0
- package/dist/landing.js +36 -0
- package/dist/mcp-server.d.ts +14 -4
- package/dist/mcp-server.js +820 -402
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -0
- package/dist/pacifica/client.d.ts +41 -10
- package/dist/pacifica/client.js +167 -63
- package/dist/pacifica/constants.d.ts +0 -1
- package/dist/pacifica/constants.js +0 -5
- package/dist/pacifica/deposit.d.ts +30 -3
- package/dist/pacifica/deposit.js +119 -2
- package/dist/pacifica/faucet.d.ts +29 -0
- package/dist/pacifica/faucet.js +100 -0
- package/dist/pacifica/sign-tx.d.ts +15 -0
- package/dist/pacifica/sign-tx.js +41 -0
- package/dist/pacifica/types/account.d.ts +28 -4
- package/dist/pacifica/types/lake.d.ts +20 -5
- package/dist/pacifica/types/lake.js +4 -0
- package/dist/pacifica/types/market.d.ts +5 -0
- package/dist/pacifica/types/order.d.ts +4 -1
- package/dist/pacifica/types/position.d.ts +9 -1
- package/dist/plan-executor.d.ts +10 -5
- package/dist/plan-executor.js +165 -54
- package/dist/position-history.js +13 -0
- package/dist/rebalance.d.ts +26 -2
- package/dist/rebalance.js +86 -7
- package/dist/relay-swap.d.ts +232 -0
- package/dist/relay-swap.js +630 -0
- package/dist/risk.js +5 -1
- package/dist/secret-env.d.ts +15 -0
- package/dist/secret-env.js +36 -0
- package/dist/settings.d.ts +169 -6
- package/dist/settings.js +219 -38
- package/dist/signer/ows-evm.d.ts +13 -0
- package/dist/signer/ows-evm.js +22 -0
- package/dist/smart-order.d.ts +9 -0
- package/dist/smart-order.js +53 -8
- package/dist/strategies/dca.d.ts +6 -2
- package/dist/strategies/dca.js +66 -26
- package/dist/strategies/funding-arb.d.ts +11 -1
- package/dist/strategies/funding-arb.js +202 -89
- package/dist/strategies/grid.d.ts +31 -0
- package/dist/strategies/grid.js +89 -11
- package/dist/strategies/pending-stop.d.ts +8 -0
- package/dist/strategies/pending-stop.js +12 -0
- package/dist/strategies/split-order.d.ts +45 -17
- package/dist/strategies/split-order.js +157 -91
- package/dist/strategies/trailing-stop.d.ts +2 -1
- package/dist/strategies/trailing-stop.js +52 -6
- package/dist/strategies/twap.d.ts +6 -1
- package/dist/strategies/twap.js +39 -11
- package/dist/trade-validator.d.ts +22 -0
- package/dist/trade-validator.js +119 -15
- package/dist/utils/numeric.d.ts +46 -0
- package/dist/utils/numeric.js +79 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +96 -19
- package/dist/withdraw-gate.d.ts +49 -0
- package/dist/withdraw-gate.js +131 -0
- package/package.json +15 -6
- package/skills/perp-cli/SKILL.md +338 -134
- package/skills/perp-cli/references/agent-operations.md +181 -92
- package/skills/perp-cli/references/commands.md +475 -132
- package/skills/perp-cli/references/strategies.md +44 -38
- package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
- package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
- package/skills/perp-cli/scripts/preflight.sh +21 -14
- package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
- package/skills/perp-cli/scripts/validate-arb.sh +33 -16
- package/vendor/lighter-signer/SHA256SUMS +2 -0
- package/vendor/lighter-signer/VERSION +8 -0
- package/vendor/lighter-signer/lighter-signer.wasm +0 -0
- package/vendor/lighter-signer/wasm_exec.js +575 -0
- package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
- package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { makeTable, formatUsd, formatPnl, printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
|
|
3
3
|
import { isDexCapable } from "../exchanges/capabilities.js";
|
|
4
|
+
import { getVenue, listVenuesWithRole, tryResolveVenueId } from "../exchanges/venue.js";
|
|
5
|
+
import { PerpError } from "../errors.js";
|
|
6
|
+
import "../exchanges/venues/index.js";
|
|
7
|
+
import { listConfiguredAccountVenues } from "../config.js";
|
|
8
|
+
import { HYPERLIQUID_API_URL } from "../api/public/urls.js";
|
|
9
|
+
import { fetchValuedSpotBalances, getSpotAdapter, spotSharesPerpMargin, withSpotTimeout } from "../exchanges/spot-factory.js";
|
|
10
|
+
import { scrubSecretEnv } from "../secret-env.js";
|
|
11
|
+
import { networkOf } from "../cli-flags.js";
|
|
4
12
|
// ── Types ──────────────────────────────────────────────────────────────────
|
|
5
|
-
|
|
13
|
+
/** Venues with an account API (market-data-only venues are excluded). */
|
|
14
|
+
function accountVenueIds() {
|
|
15
|
+
return listVenuesWithRole("account").map(v => v.id);
|
|
16
|
+
}
|
|
6
17
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
7
18
|
function exAbbr(e) {
|
|
8
19
|
return e === "pacifica" ? "PAC"
|
|
@@ -37,12 +48,14 @@ function parseList(input) {
|
|
|
37
48
|
return input.split(",").map(s => s.trim()).filter(Boolean);
|
|
38
49
|
}
|
|
39
50
|
function validateExchanges(list) {
|
|
40
|
-
const
|
|
41
|
-
|
|
51
|
+
const valid = accountVenueIds();
|
|
52
|
+
// Aliases (hl, pac, lt, lrh, ast, …) resolve through the venue registry, like every other command.
|
|
53
|
+
const ids = list.map(s => tryResolveVenueId(s) ?? s.toLowerCase());
|
|
54
|
+
const unknown = ids.filter(e => !valid.includes(e));
|
|
42
55
|
if (unknown.length > 0) {
|
|
43
|
-
throw new
|
|
56
|
+
throw new PerpError("INVALID_PARAMS", `Unknown exchange: ${unknown.join(", ")}`, { remediation: `Use one of: ${valid.join(", ")} (or an alias such as hl, pac, lt, lrh, ast)` });
|
|
44
57
|
}
|
|
45
|
-
return
|
|
58
|
+
return ids;
|
|
46
59
|
}
|
|
47
60
|
/** Resolve global -e flag from program (only counts when the user typed it). */
|
|
48
61
|
function explicitExchange(program) {
|
|
@@ -52,8 +65,8 @@ function explicitExchange(program) {
|
|
|
52
65
|
}
|
|
53
66
|
return undefined;
|
|
54
67
|
}
|
|
55
|
-
function resolveOptions(program, flags, stdinOpts) {
|
|
56
|
-
// 1) Build exchange list. Precedence: stdin > --exchanges > -e > all.
|
|
68
|
+
function resolveOptions(program, flags, stdinOpts, defaultExchanges) {
|
|
69
|
+
// 1) Build exchange list. Precedence: stdin > --exchanges > -e > all configured.
|
|
57
70
|
let exchangeList;
|
|
58
71
|
let scope = "all";
|
|
59
72
|
if (stdinOpts.exchanges && stdinOpts.exchanges.length > 0) {
|
|
@@ -73,7 +86,7 @@ function resolveOptions(program, flags, stdinOpts) {
|
|
|
73
86
|
scope = "single";
|
|
74
87
|
}
|
|
75
88
|
else {
|
|
76
|
-
exchangeList = [...
|
|
89
|
+
exchangeList = [...defaultExchanges];
|
|
77
90
|
}
|
|
78
91
|
}
|
|
79
92
|
// If only 1 entry and we got there via -e or stdin.exchange, scope is single.
|
|
@@ -135,7 +148,10 @@ function resolveOptions(program, flags, stdinOpts) {
|
|
|
135
148
|
watchIntervalMs = n;
|
|
136
149
|
}
|
|
137
150
|
}
|
|
138
|
-
|
|
151
|
+
// --dexes, or the root --dex when typed on the command line (backward compatible: `portfolio --serve --dex xyz`
|
|
152
|
+
// reaches the root option, which used to swallow a subcommand --dex silently).
|
|
153
|
+
const rootDex = program.getOptionValueSource("dex") === "cli" ? String(program.opts().dex) : undefined;
|
|
154
|
+
const dexList = stdinOpts.dex ?? parseList(flags.dexes ?? rootDex);
|
|
139
155
|
const autoDex = stdinOpts.autoDex ?? (flags.autoDex !== false);
|
|
140
156
|
return {
|
|
141
157
|
exchangeList,
|
|
@@ -149,6 +165,7 @@ function resolveOptions(program, flags, stdinOpts) {
|
|
|
149
165
|
pollIntervalMs,
|
|
150
166
|
dexList,
|
|
151
167
|
autoDex,
|
|
168
|
+
network: networkOf(program),
|
|
152
169
|
};
|
|
153
170
|
}
|
|
154
171
|
// ── Per-exchange snapshot fetch ────────────────────────────────────────────
|
|
@@ -161,64 +178,32 @@ async function fetchExchangeEntry(exName, getAdapter) {
|
|
|
161
178
|
adapter.getOpenOrders(),
|
|
162
179
|
adapter.getFundingPayments(200).catch(() => []),
|
|
163
180
|
]);
|
|
164
|
-
// Spot balances
|
|
181
|
+
// Spot balances for every venue with def.spot (§12). Failures are surfaced on
|
|
182
|
+
// stderr (the perp snapshot is still reported); unpriced tokens are excluded + warned.
|
|
165
183
|
let spot = [];
|
|
166
184
|
let isUnified = false;
|
|
185
|
+
const def = getVenue(exName);
|
|
186
|
+
const warn = (msg) => process.stderr.write(`[warn] ${msg}\n`);
|
|
167
187
|
try {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const [raw, markets] = await Promise.all([hlSpot.getSpotBalances(), hlSpot.getSpotMarkets()]);
|
|
174
|
-
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
175
|
-
const strip = (t) => t.replace(/-SPOT$/i, "").toUpperCase();
|
|
176
|
-
spot = raw.filter(b => Number(b.total) > 0).map(b => {
|
|
177
|
-
const base = strip(b.token);
|
|
178
|
-
return {
|
|
179
|
-
token: b.token,
|
|
180
|
-
total: b.total,
|
|
181
|
-
available: b.available,
|
|
182
|
-
held: b.held,
|
|
183
|
-
valueUsd: base === "USDC" ? Number(b.total) : (priceMap.get(base) ?? 0) * Number(b.total),
|
|
184
|
-
};
|
|
185
|
-
});
|
|
186
|
-
// Read the actual abstraction mode (populated during init()) instead
|
|
187
|
-
// of assuming all main HL accounts are unified. Standard/default mode
|
|
188
|
-
// accounts must include spot USDC in totalAccountValueUsd because
|
|
189
|
-
// perp equity does NOT contain it. HIP-3 dex accounts always run
|
|
190
|
-
// standard semantics. Codex v0.12.12 final QA #1.
|
|
191
|
-
const hlAdapter = adapter;
|
|
192
|
-
const dexScoped = isDexCapable(adapter) && !!adapter.dex;
|
|
193
|
-
isUnified = !dexScoped && hlAdapter.isUnifiedAccount === true;
|
|
194
|
-
}
|
|
195
|
-
else if (exName === "lighter") {
|
|
196
|
-
const { LighterAdapter } = await import("../exchanges/lighter.js");
|
|
197
|
-
const { LighterSpotAdapter } = await import("../exchanges/lighter-spot.js");
|
|
198
|
-
const ltSpot = new LighterSpotAdapter(adapter);
|
|
199
|
-
await ltSpot.init();
|
|
200
|
-
const [raw, markets] = await Promise.all([ltSpot.getSpotBalances(), ltSpot.getSpotMarkets()]);
|
|
201
|
-
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
202
|
-
spot = raw.filter(b => Number(b.total) > 0).map(b => ({
|
|
203
|
-
token: b.token,
|
|
204
|
-
total: b.total,
|
|
205
|
-
available: b.available,
|
|
206
|
-
held: b.held,
|
|
207
|
-
valueUsd: b.token === "USDC" || b.token === "USDC_SPOT"
|
|
208
|
-
? Number(b.total)
|
|
209
|
-
: (priceMap.get(b.token.toUpperCase()) ?? 0) * Number(b.total),
|
|
188
|
+
const spotAdapter = await withSpotTimeout(getSpotAdapter(def.id, adapter), `${def.displayName} spot adapter`);
|
|
189
|
+
if (spotAdapter !== null) {
|
|
190
|
+
// Wallet-model venues (Arcus) price only held tokens; bounded by SPOT_VALUATION_TIMEOUT_MS.
|
|
191
|
+
spot = (await fetchValuedSpotBalances(def, spotAdapter, warn)).map(b => ({
|
|
192
|
+
token: b.token, total: b.total, available: b.available, held: b.held, valueUsd: b.valueUsd, shared: b.shared,
|
|
210
193
|
}));
|
|
194
|
+
// HIP-3 dex accounts always run standard (non-unified) semantics. Codex v0.12.12 final QA #1.
|
|
195
|
+
const dexScoped = isDexCapable(adapter) && !!adapter.dex;
|
|
196
|
+
isUnified = !dexScoped && spotSharesPerpMargin(def, adapter);
|
|
211
197
|
}
|
|
212
198
|
}
|
|
213
|
-
catch {
|
|
199
|
+
catch (e) {
|
|
200
|
+
warn(`${def.displayName} spot balances unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
201
|
+
}
|
|
214
202
|
const cutoff24h = Date.now() - 24 * 60 * 60 * 1000;
|
|
215
203
|
const recent = fundingPayments.filter(f => f.time >= cutoff24h);
|
|
216
204
|
const funding24h = recent.reduce((sum, f) => sum + Number(f.payment), 0);
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
const nonUsdcSpotValue = isUnified
|
|
220
|
-
? spot.filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC").reduce((s, b) => s + b.valueUsd, 0)
|
|
221
|
-
: spot.reduce((s, b) => s + b.valueUsd, 0);
|
|
205
|
+
// Shared balances (Pacifica USDC, HL unified USDC) are already in perp equity (§12.4 #8).
|
|
206
|
+
const nonUsdcSpotValue = spot.filter(b => !b.shared).reduce((s, b) => s + b.valueUsd, 0);
|
|
222
207
|
return {
|
|
223
208
|
name: exName,
|
|
224
209
|
connected: true,
|
|
@@ -236,6 +221,7 @@ async function fetchExchangeEntry(exName, getAdapter) {
|
|
|
236
221
|
size: p.size,
|
|
237
222
|
entryPrice: p.entryPrice,
|
|
238
223
|
markPrice: p.markPrice,
|
|
224
|
+
liquidationPrice: p.liquidationPrice,
|
|
239
225
|
unrealizedPnl: p.unrealizedPnl,
|
|
240
226
|
leverage: p.leverage,
|
|
241
227
|
})),
|
|
@@ -291,7 +277,7 @@ function buildTotals(entries) {
|
|
|
291
277
|
};
|
|
292
278
|
}
|
|
293
279
|
// ── Health check (no balance fetches) ──────────────────────────────────────
|
|
294
|
-
async function runHealth(exchangeList) {
|
|
280
|
+
async function runHealth(exchangeList, network) {
|
|
295
281
|
const { pingPacifica, pingHyperliquid, pingLighter } = await import("../shared-api.js");
|
|
296
282
|
const pingAster = async () => {
|
|
297
283
|
const start = Date.now();
|
|
@@ -312,11 +298,13 @@ async function runHealth(exchangeList) {
|
|
|
312
298
|
const exchanges = {};
|
|
313
299
|
await Promise.all(exchangeList.map(async (ex) => {
|
|
314
300
|
const fn = pingMap[ex];
|
|
315
|
-
if (!fn) {
|
|
316
|
-
exchanges[ex] = "error";
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
301
|
try {
|
|
302
|
+
if (!fn) {
|
|
303
|
+
// No dedicated ping helper: probe the venue's public market-data path.
|
|
304
|
+
await getVenue(ex).publicData.fetchMarkets(network);
|
|
305
|
+
exchanges[ex] = "ok";
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
320
308
|
const r = await fn();
|
|
321
309
|
exchanges[ex] = r.ok ? "ok" : "error";
|
|
322
310
|
}
|
|
@@ -542,17 +530,17 @@ async function runWatch(resolved, version, getAdapterForExchange, isJsonMode) {
|
|
|
542
530
|
process.stdout.write("[2J[H");
|
|
543
531
|
renderSnapshotText(data);
|
|
544
532
|
if (resolved.health)
|
|
545
|
-
renderHealthText(await runHealth(resolved.exchangeList));
|
|
533
|
+
renderHealthText(await runHealth(resolved.exchangeList, resolved.network));
|
|
546
534
|
}
|
|
547
535
|
await new Promise(r => setTimeout(r, resolved.watchIntervalMs));
|
|
548
536
|
}
|
|
549
537
|
}
|
|
550
538
|
// ── Serve mode (delegates to dashboard infra) ──────────────────────────────
|
|
551
539
|
const KNOWN_HIP3_DEXES = ["xyz", "flx", "hyna", "km", "cash", "vntl"];
|
|
552
|
-
const HL_INFO_URL = "https://api.hyperliquid.xyz/info";
|
|
553
540
|
async function checkDexPositions(address, dex) {
|
|
554
541
|
try {
|
|
555
|
-
|
|
542
|
+
// Selected network (review L7): follows -n via setSharedApiNetwork (live binding).
|
|
543
|
+
const res = await fetch(HYPERLIQUID_API_URL, {
|
|
556
544
|
method: "POST",
|
|
557
545
|
headers: { "Content-Type": "application/json" },
|
|
558
546
|
body: JSON.stringify({ type: "clearinghouseState", user: address, dex }),
|
|
@@ -689,9 +677,9 @@ async function runServe(resolved, getAdapterForExchange, isJsonMode, getHLAdapte
|
|
|
689
677
|
const { exec } = await import("child_process");
|
|
690
678
|
const url = `http://localhost:${dashboard.port}`;
|
|
691
679
|
if (process.platform === "darwin")
|
|
692
|
-
exec(`open ${url}
|
|
680
|
+
exec(`open ${url}`, { env: scrubSecretEnv(process.env) });
|
|
693
681
|
else if (process.platform === "linux")
|
|
694
|
-
exec(`xdg-open ${url}
|
|
682
|
+
exec(`xdg-open ${url}`, { env: scrubSecretEnv(process.env) });
|
|
695
683
|
}
|
|
696
684
|
catch { /* user can open manually */ }
|
|
697
685
|
const ac = new AbortController();
|
|
@@ -713,13 +701,13 @@ export function registerPortfolioCommand(program, getAdapterForExchange, isJson,
|
|
|
713
701
|
program
|
|
714
702
|
.command("portfolio")
|
|
715
703
|
.description("Unified account view: balances, positions, risk (replaces account balance / status / dashboard)")
|
|
716
|
-
.option("--exchanges <list>", "Comma-separated exchanges to include (default: all). Plural to avoid collision with global -e/--exchange.")
|
|
704
|
+
.option("--exchanges <list>", "Comma-separated exchanges to include (default: all with configured credentials). Plural to avoid collision with global -e/--exchange.")
|
|
717
705
|
.option("--arb", "Include top arb opportunities (replaces 'perp status')")
|
|
718
706
|
.option("--health", "Connectivity-only mode (replaces 'perp status --health')")
|
|
719
707
|
.option("--watch [interval]", "Poll mode: clear screen + redraw each tick (ms; default 5000)")
|
|
720
708
|
.option("--serve [port]", "Serve web dashboard (replaces 'perp dashboard')")
|
|
721
709
|
.option("--interval <ms>", "Poll interval for --watch / --serve (default 5000)")
|
|
722
|
-
.option("--
|
|
710
|
+
.option("--dexes <list>", "Comma-separated HIP-3 dex names (only with --serve; the root --dex <name> is also accepted)")
|
|
723
711
|
.option("--no-auto-dex", "Disable HIP-3 dex auto-detection")
|
|
724
712
|
.option("--stdin", "Read JSON args from stdin (overrides flag args)")
|
|
725
713
|
.action(async (flags) => {
|
|
@@ -742,7 +730,7 @@ export function registerPortfolioCommand(program, getAdapterForExchange, isJson,
|
|
|
742
730
|
}
|
|
743
731
|
let resolved;
|
|
744
732
|
try {
|
|
745
|
-
resolved = resolveOptions(program, flags, stdinOpts);
|
|
733
|
+
resolved = resolveOptions(program, flags, stdinOpts, await listConfiguredAccountVenues());
|
|
746
734
|
}
|
|
747
735
|
catch (err) {
|
|
748
736
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -763,7 +751,7 @@ export function registerPortfolioCommand(program, getAdapterForExchange, isJson,
|
|
|
763
751
|
}
|
|
764
752
|
await withJsonErrors(json, async () => {
|
|
765
753
|
if (resolved.health) {
|
|
766
|
-
const health = await runHealth(resolved.exchangeList);
|
|
754
|
+
const health = await runHealth(resolved.exchangeList, resolved.network);
|
|
767
755
|
const data = {
|
|
768
756
|
version,
|
|
769
757
|
scope: resolved.scope,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
3
|
-
|
|
3
|
+
import type { Network } from "../exchanges/venue-types.js";
|
|
4
|
+
export declare function registerFundsRebalanceCommands(parent: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, getNetwork: () => Network): void;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { isDryRun, supportsDryRun } from "../cli-flags.js";
|
|
1
2
|
import chalk from "chalk";
|
|
2
3
|
import { formatUsd, makeTable, printJson, jsonOk } from "../utils.js";
|
|
3
4
|
import { fetchAllBalances, computeRebalancePlan, describeBridgeRoute, estimateMoveTime, } from "../rebalance.js";
|
|
4
|
-
import { EXCHANGE_TO_CHAIN, executeBestBridge } from "../bridge-engine.js";
|
|
5
|
-
import {
|
|
6
|
-
export function registerFundsRebalanceCommands(parent, getAdapterForExchange, isJson) {
|
|
5
|
+
import { EXCHANGE_TO_CHAIN, assertBridgeNetwork, executeBestBridge } from "../bridge-engine.js";
|
|
6
|
+
import { cliCredentialOptsFrom } from "../config.js";
|
|
7
|
+
export function registerFundsRebalanceCommands(parent, getAdapterForExchange, isJson, getNetwork) {
|
|
7
8
|
const rebalance = parent.command("rebalance").description("Cross-exchange balance management");
|
|
8
9
|
// ── rebalance check ──
|
|
9
10
|
rebalance
|
|
@@ -118,16 +119,19 @@ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, is
|
|
|
118
119
|
console.log(chalk.yellow(` To execute: perp funds rebalance execute --exchanges ${opts.exchanges}\n`));
|
|
119
120
|
});
|
|
120
121
|
// ── rebalance execute ──
|
|
121
|
-
rebalance
|
|
122
|
-
.
|
|
123
|
-
.description("Execute rebalancing (withdraw → bridge → deposit)")
|
|
122
|
+
supportsDryRun(rebalance.command("execute"))
|
|
123
|
+
.description("Execute rebalancing (withdraw → bridge → deposit; supports --dry-run)")
|
|
124
124
|
.option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter,aster")
|
|
125
125
|
.option("--min-move <usd>", "Minimum move amount ($)", "50")
|
|
126
126
|
.option("--reserve <usd>", "Keep at least this much per exchange ($)", "20")
|
|
127
|
-
.option("--dry-run", "Show what would happen without executing")
|
|
128
127
|
.option("--withdraw-only", "Only execute withdrawals (manual bridge + deposit)")
|
|
129
128
|
.option("--auto-bridge", "Auto-bridge via deBridge DLN (withdraw → bridge in one step)")
|
|
130
|
-
.action(async (opts) => {
|
|
129
|
+
.action(async (opts, cmd) => {
|
|
130
|
+
const dryRun = isDryRun(cmd);
|
|
131
|
+
// --auto-bridge signs mainnet-only bridge routes: refuse -n testnet up front,
|
|
132
|
+
// before any withdrawal is sent (not after, as a per-move bridge_failed).
|
|
133
|
+
if (opts.autoBridge && !dryRun)
|
|
134
|
+
assertBridgeNetwork(getNetwork());
|
|
131
135
|
const exchangeNames = opts.exchanges.split(",").map((e) => e.trim());
|
|
132
136
|
const adapters = new Map();
|
|
133
137
|
for (const name of exchangeNames) {
|
|
@@ -159,7 +163,7 @@ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, is
|
|
|
159
163
|
for (const move of plan.moves) {
|
|
160
164
|
if (!isJson())
|
|
161
165
|
console.log(chalk.white.bold(` $${move.amount} ${move.from} → ${move.to}`));
|
|
162
|
-
if (
|
|
166
|
+
if (dryRun) {
|
|
163
167
|
if (!isJson())
|
|
164
168
|
console.log(chalk.yellow(" [DRY RUN] Skipped.\n"));
|
|
165
169
|
moveResults.push({ from: move.from, to: move.to, amount: move.amount, status: "dry_run" });
|
|
@@ -202,8 +206,11 @@ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, is
|
|
|
202
206
|
if (!isJson())
|
|
203
207
|
console.log(chalk.gray(` Step 2: Bridging $${move.amount} (${srcChain} → ${dstChain})...`));
|
|
204
208
|
// Load keys for bridge
|
|
205
|
-
|
|
206
|
-
|
|
209
|
+
let root = cmd;
|
|
210
|
+
while (root.parent)
|
|
211
|
+
root = root.parent;
|
|
212
|
+
const { senderAddress, recipientAddress, signer, dstSigner } = await loadBridgeKeys(move.from, move.to, srcChain, dstChain, cliCredentialOptsFrom(root.opts()));
|
|
213
|
+
const result = await executeBestBridge(getNetwork(), srcChain, dstChain, move.amount, signer, senderAddress, recipientAddress, dstSigner);
|
|
207
214
|
if (!isJson()) {
|
|
208
215
|
console.log(chalk.green(` Bridge submitted! TX: ${result.txHash}`));
|
|
209
216
|
console.log(chalk.gray(` Provider: ${result.provider} | ~$${result.amountOut} arriving on ${dstChain}\n`));
|
|
@@ -227,7 +234,7 @@ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, is
|
|
|
227
234
|
}
|
|
228
235
|
}
|
|
229
236
|
if (isJson())
|
|
230
|
-
return printJson(jsonOk({ status: "executed", moves: moveResults }));
|
|
237
|
+
return printJson(jsonOk({ status: dryRun ? "simulated" : "executed", dryRun, sent: !dryRun, moves: moveResults }));
|
|
231
238
|
console.log(chalk.cyan(" Rebalance initiated. Monitor progress with 'perp funds rebalance check'.\n"));
|
|
232
239
|
});
|
|
233
240
|
}
|
|
@@ -244,7 +251,8 @@ async function executeWithdraw(move, adapters) {
|
|
|
244
251
|
if (!hasPacificaSdk(adapter))
|
|
245
252
|
throw new Error("Invalid adapter");
|
|
246
253
|
const sdk = adapter.sdk;
|
|
247
|
-
|
|
254
|
+
// Pacifica withdraws to the account owner's own wallet (no destination field).
|
|
255
|
+
await sdk.withdraw({ amount: String(move.amount) }, adapter.publicKey, adapter.signer);
|
|
248
256
|
break;
|
|
249
257
|
}
|
|
250
258
|
case "hyperliquid": {
|
|
@@ -264,35 +272,17 @@ async function executeWithdraw(move, adapters) {
|
|
|
264
272
|
}
|
|
265
273
|
}
|
|
266
274
|
/**
|
|
267
|
-
*
|
|
275
|
+
* Sender / recipient (the master wallet's own public addresses) and master signers for a bridge
|
|
276
|
+
* move: OWS vault signers for an OWS master, raw-key signers for a raw-key master.
|
|
268
277
|
*/
|
|
269
|
-
async function loadBridgeKeys(srcExchange, dstExchange, srcChain, dstChain) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
const pk = await loadPrivateKey(srcExchange);
|
|
282
|
-
const { ethers } = await import("ethers");
|
|
283
|
-
senderAddress = new ethers.Wallet(pk).address;
|
|
284
|
-
signerKey = pk;
|
|
285
|
-
}
|
|
286
|
-
// Destination
|
|
287
|
-
if (dstChain === "solana") {
|
|
288
|
-
const pk = await loadPrivateKey("pacifica");
|
|
289
|
-
recipientAddress = parseSolanaKeypair(pk).publicKey.toBase58();
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
const pk = await loadPrivateKey(dstExchange);
|
|
293
|
-
const { ethers } = await import("ethers");
|
|
294
|
-
recipientAddress = new ethers.Wallet(pk).address;
|
|
295
|
-
dstSignerKey = pk; // For auto receiveMessage
|
|
296
|
-
}
|
|
297
|
-
return { senderAddress, recipientAddress, signerKey, dstSignerKey };
|
|
278
|
+
async function loadBridgeKeys(srcExchange, dstExchange, srcChain, dstChain, credOpts) {
|
|
279
|
+
const { bridgeAddress, bridgeVenueForChain, loadBridgeSigner } = await import("../bridge-signers.js");
|
|
280
|
+
const srcVenue = bridgeVenueForChain(srcChain, srcExchange);
|
|
281
|
+
const dstVenue = bridgeVenueForChain(dstChain, dstExchange);
|
|
282
|
+
return {
|
|
283
|
+
senderAddress: await bridgeAddress(srcChain, srcVenue, credOpts),
|
|
284
|
+
recipientAddress: await bridgeAddress(dstChain, dstVenue, credOpts),
|
|
285
|
+
signer: await loadBridgeSigner(srcChain, srcVenue, credOpts),
|
|
286
|
+
dstSigner: await loadBridgeSigner(dstChain, dstVenue, credOpts),
|
|
287
|
+
};
|
|
298
288
|
}
|
package/dist/commands/risk.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { listConfiguredAccountVenues } from "../config.js";
|
|
1
2
|
import chalk from "chalk";
|
|
2
3
|
import { printJson, jsonOk, makeTable, formatUsd, withJsonErrors } from "../utils.js";
|
|
3
4
|
import { loadRiskLimits, saveRiskLimits, assessRisk, getLiquidationDistances } from "../risk.js";
|
|
@@ -59,19 +60,26 @@ export async function runHealthCheck(isJson) {
|
|
|
59
60
|
const overall = allOk ? chalk.green("ALL HEALTHY") : chalk.red("ISSUES DETECTED");
|
|
60
61
|
console.log(`\n Overall: ${overall}\n`);
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Exchange filter (comma-separated) of a risk subcommand: the ROOT `-e, --exchange`, only when given
|
|
65
|
+
* on the command line. Commander hands `--exchange` to the root wherever it appears, so a subcommand
|
|
66
|
+
* copy was never set (the filter was silently dropped); the root's default venue is never a filter.
|
|
67
|
+
*/
|
|
68
|
+
function cliExchangeFilter(root) {
|
|
69
|
+
return root.getOptionValueSource("exchange") === "cli" ? String(root.opts().exchange) : undefined;
|
|
70
|
+
}
|
|
63
71
|
export function registerRiskCommands(program, getAdapterForExchange, isJson) {
|
|
64
72
|
const risk = program.command("risk").description("Risk management and guardrails");
|
|
65
73
|
// ── risk status ──
|
|
66
74
|
risk
|
|
67
75
|
.command("status")
|
|
68
|
-
.description("Assess current risk across all exchanges")
|
|
69
|
-
.
|
|
70
|
-
|
|
76
|
+
.description("Assess current risk across all exchanges — -e <ex1,ex2> limits the exchanges (default: all with configured credentials)")
|
|
77
|
+
.action(async () => {
|
|
78
|
+
const exchangeFilter = cliExchangeFilter(program);
|
|
71
79
|
await withJsonErrors(isJson(), async () => {
|
|
72
|
-
const exchanges =
|
|
73
|
-
?
|
|
74
|
-
:
|
|
80
|
+
const exchanges = exchangeFilter
|
|
81
|
+
? exchangeFilter.split(",").map(e => e.trim())
|
|
82
|
+
: await listConfiguredAccountVenues();
|
|
75
83
|
const balances = [];
|
|
76
84
|
const positions = [];
|
|
77
85
|
const results = await Promise.allSettled(exchanges.map(async (ex) => {
|
|
@@ -120,6 +128,9 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
|
|
|
120
128
|
: chalk.green;
|
|
121
129
|
console.log(` Min Liq Distance: ${ldColor(`${assessment.metrics.minLiquidationDistancePct.toFixed(1)}%`)}`);
|
|
122
130
|
}
|
|
131
|
+
for (const { exchange, position: p } of positions.filter(x => x.position.liquidationPrice === null)) {
|
|
132
|
+
console.log(` Liq Price ${exchange}:${p.symbol}: ${chalk.yellow("unknown (venue does not report)")} — not in Min Liq Distance`);
|
|
133
|
+
}
|
|
123
134
|
if (assessment.violations.length > 0) {
|
|
124
135
|
console.log(chalk.red.bold("\n Violations"));
|
|
125
136
|
const vRows = assessment.violations.map(v => {
|
|
@@ -222,15 +233,15 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
|
|
|
222
233
|
// ── risk check ── (pre-trade check, for agent use)
|
|
223
234
|
risk
|
|
224
235
|
.command("check")
|
|
225
|
-
.description("Pre-trade risk check (for agents)")
|
|
236
|
+
.description("Pre-trade risk check (for agents) — -e <ex1,ex2> limits the exchanges (default: all with configured credentials)")
|
|
226
237
|
.requiredOption("--notional <usd>", "Order notional value in USD")
|
|
227
238
|
.requiredOption("--leverage <n>", "Order leverage")
|
|
228
|
-
.option("--exchange <exchanges>", "Comma-separated exchanges (default: all)")
|
|
229
239
|
.action(async (opts) => {
|
|
240
|
+
const exchangeFilter = cliExchangeFilter(program);
|
|
230
241
|
await withJsonErrors(isJson(), async () => {
|
|
231
|
-
const exchanges =
|
|
232
|
-
?
|
|
233
|
-
:
|
|
242
|
+
const exchanges = exchangeFilter
|
|
243
|
+
? exchangeFilter.split(",").map(e => e.trim())
|
|
244
|
+
: await listConfiguredAccountVenues();
|
|
234
245
|
const balances = [];
|
|
235
246
|
const positions = [];
|
|
236
247
|
await Promise.allSettled(exchanges.map(async (ex) => {
|
|
@@ -260,14 +271,14 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
|
|
|
260
271
|
// ── risk liquidation-distance ──
|
|
261
272
|
risk
|
|
262
273
|
.command("liquidation-distance")
|
|
263
|
-
.description("Show % distance from liquidation price for all positions")
|
|
274
|
+
.description("Show % distance from liquidation price for all positions — -e <ex1,ex2> limits the exchanges (default: all with configured credentials)")
|
|
264
275
|
.alias("liq-dist")
|
|
265
|
-
.
|
|
266
|
-
|
|
276
|
+
.action(async () => {
|
|
277
|
+
const exchangeFilter = cliExchangeFilter(program);
|
|
267
278
|
await withJsonErrors(isJson(), async () => {
|
|
268
|
-
const exchanges =
|
|
269
|
-
?
|
|
270
|
-
:
|
|
279
|
+
const exchanges = exchangeFilter
|
|
280
|
+
? exchangeFilter.split(",").map(e => e.trim())
|
|
281
|
+
: await listConfiguredAccountVenues();
|
|
271
282
|
const positions = [];
|
|
272
283
|
const results = await Promise.allSettled(exchanges.map(async (ex) => {
|
|
273
284
|
const adapter = await getAdapterForExchange(ex);
|
|
@@ -291,9 +302,14 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
|
|
|
291
302
|
}
|
|
292
303
|
const limits = loadRiskLimits();
|
|
293
304
|
const distances = getLiquidationDistances(positions, limits);
|
|
305
|
+
// Positions whose venue reports no liquidation price are listed explicitly, never dropped.
|
|
306
|
+
const unknownLiq = positions
|
|
307
|
+
.filter(({ position: p }) => p.liquidationPrice === null)
|
|
308
|
+
.map(({ exchange, position: p }) => ({ exchange, symbol: p.symbol, side: p.side, markPrice: Number(p.markPrice), liquidationPrice: null }));
|
|
294
309
|
if (isJson()) {
|
|
295
310
|
return printJson(jsonOk({
|
|
296
311
|
positions: distances,
|
|
312
|
+
unknownLiquidation: unknownLiq,
|
|
297
313
|
limits: {
|
|
298
314
|
minLiquidationDistance: limits.minLiquidationDistance,
|
|
299
315
|
},
|
|
@@ -318,6 +334,17 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
|
|
|
318
334
|
statusColor(d.status.toUpperCase()),
|
|
319
335
|
];
|
|
320
336
|
});
|
|
337
|
+
for (const u of unknownLiq) {
|
|
338
|
+
rows.push([
|
|
339
|
+
chalk.white(u.exchange),
|
|
340
|
+
chalk.white.bold(u.symbol),
|
|
341
|
+
u.side === "long" ? chalk.green("LONG") : chalk.red("SHORT"),
|
|
342
|
+
`$${formatUsd(u.markPrice)}`,
|
|
343
|
+
chalk.yellow("unknown (venue does not report)"),
|
|
344
|
+
chalk.gray("n/a"),
|
|
345
|
+
chalk.yellow("UNKNOWN"),
|
|
346
|
+
]);
|
|
347
|
+
}
|
|
321
348
|
console.log(makeTable(["Exchange", "Symbol", "Side", "Mark Price", "Liq Price", "Distance", "Status"], rows));
|
|
322
349
|
// Summary warnings
|
|
323
350
|
const critical = distances.filter(d => d.status === "critical");
|
|
@@ -338,7 +365,7 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
|
|
|
338
365
|
});
|
|
339
366
|
program
|
|
340
367
|
.command("health")
|
|
341
|
-
.description("Adapter health check
|
|
368
|
+
.description("Adapter health check: ping venue REST endpoints (connectivity + latency)")
|
|
342
369
|
.action(async () => {
|
|
343
370
|
await runHealthCheck(isJson);
|
|
344
371
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
3
|
+
import "../exchanges/venues/index.js";
|
|
3
4
|
export declare function registerSettingsCommands(program: Command, isJson: () => boolean, getAdapterForExchange?: (exchange: string) => Promise<ExchangeAdapter>): void;
|