perp-cli 0.14.3 → 0.16.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.ja.md +243 -0
- package/README.ko.md +243 -0
- package/README.md +138 -405
- package/README.zh.md +243 -0
- package/dist/agent-wallet/store.d.ts +3 -2
- package/dist/agent-wallet/store.js +2 -2
- package/dist/api/public/hyperliquid.js +4 -0
- package/dist/api/public/lighter.d.ts +5 -0
- package/dist/api/public/lighter.js +5 -0
- package/dist/api/public/pacifica.js +85 -15
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/sizing.js +4 -2
- package/dist/arb/state.d.ts +6 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- package/dist/balance-by-address.d.ts +39 -0
- package/dist/balance-by-address.js +44 -0
- package/dist/bot/apex/pulse.js +3 -0
- package/dist/bot/apex/radar.js +2 -1
- package/dist/bot/engine.d.ts +10 -1
- package/dist/bot/engine.js +14 -6
- package/dist/bot/indicators.d.ts +7 -0
- package/dist/bot/indicators.js +16 -0
- package/dist/bot/perp-order.d.ts +4 -4
- package/dist/bot/perp-order.js +5 -5
- package/dist/bot/quarantine.d.ts +1 -1
- package/dist/bot/quarantine.js +1 -1
- package/dist/bot/quote-rules.js +1 -1
- package/dist/bot/strategies/apex-strategy.js +3 -2
- package/dist/bot/strategies/engine-mm.js +6 -5
- package/dist/bot/strategies/funding-arb-strategy.js +12 -5
- package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
- package/dist/bot/strategies/funding-arb-utils.js +53 -20
- package/dist/bot/strategies/funding-auto.js +2 -2
- package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
- package/dist/bot/strategies/liquidation-mm.js +8 -2
- package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
- package/dist/bot/strategies/momentum-breakout.js +11 -3
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +3 -3
- package/dist/bot/strategy-types.d.ts +2 -1
- package/dist/bot/tui/BotDashboard.d.ts +1 -1
- package/dist/bot/tui/BotDashboard.js +1 -1
- package/dist/cache.js +12 -6
- package/dist/cli-program.d.ts +15 -0
- package/dist/cli-program.js +126 -6
- package/dist/commands/account.js +168 -24
- package/dist/commands/agent-import.d.ts +7 -0
- package/dist/commands/agent-import.js +30 -1
- package/dist/commands/agent.d.ts +6 -3
- package/dist/commands/agent.js +160 -12
- package/dist/commands/arb/index.js +23 -81
- package/dist/commands/arb-auto.d.ts +24 -0
- package/dist/commands/arb-auto.js +175 -97
- package/dist/commands/arb.d.ts +20 -0
- package/dist/commands/arb.js +107 -163
- package/dist/commands/backtest.js +23 -21
- package/dist/commands/bot.js +1 -1
- package/dist/commands/builder.js +1 -1
- package/dist/commands/funds.js +113 -3
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +27 -7
- package/dist/commands/manage.js +23 -21
- package/dist/commands/market.d.ts +48 -0
- package/dist/commands/market.js +395 -100
- package/dist/commands/options.d.ts +14 -0
- package/dist/commands/options.js +151 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +455 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.d.ts +16 -0
- package/dist/commands/portfolio.js +83 -55
- package/dist/commands/rebalance.d.ts +1 -0
- package/dist/commands/rebalance.js +15 -0
- package/dist/commands/risk.d.ts +11 -2
- package/dist/commands/risk.js +39 -44
- package/dist/commands/trade.d.ts +1 -0
- package/dist/commands/trade.js +67 -62
- package/dist/commands/wallet.js +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +15 -4
- package/dist/constants.js +6 -0
- package/dist/cross-chain-margin.d.ts +3 -1
- package/dist/cross-chain-margin.js +4 -2
- package/dist/dashboard/server.js +3 -3
- package/dist/dashboard/ui.js +9 -6
- package/dist/errors.d.ts +29 -1
- package/dist/errors.js +35 -3
- package/dist/exchanges/address.d.ts +8 -0
- package/dist/exchanges/address.js +47 -0
- package/dist/exchanges/arcus/adapter.d.ts +16 -12
- package/dist/exchanges/arcus/adapter.js +45 -18
- package/dist/exchanges/arcus/agent.d.ts +1 -1
- package/dist/exchanges/arcus/agent.js +1 -1
- package/dist/exchanges/arcus/client.js +1 -1
- package/dist/exchanges/arcus/public.js +3 -2
- package/dist/exchanges/arcus/signing.d.ts +2 -3
- package/dist/exchanges/arcus/signing.js +1 -1
- package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
- package/dist/exchanges/arcus/spot-chain.js +1 -1
- package/dist/exchanges/arcus/spot-router.d.ts +1 -1
- package/dist/exchanges/arcus/spot-router.js +2 -2
- package/dist/exchanges/arcus/spot.d.ts +2 -3
- package/dist/exchanges/arcus/spot.js +2 -3
- package/dist/exchanges/aster-spot.d.ts +3 -3
- package/dist/exchanges/aster-spot.js +6 -9
- package/dist/exchanges/aster-typed-data.d.ts +1 -1
- package/dist/exchanges/aster-typed-data.js +1 -1
- package/dist/exchanges/aster.d.ts +30 -10
- package/dist/exchanges/aster.js +153 -53
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +8 -2
- package/dist/exchanges/canonical-symbols.js +46 -2
- package/dist/exchanges/capabilities.d.ts +2 -0
- package/dist/exchanges/capabilities.js +2 -1
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +73 -0
- package/dist/exchanges/derive/options.js +379 -0
- package/dist/exchanges/extended/adapter.d.ts +96 -0
- package/dist/exchanges/extended/adapter.js +241 -0
- package/dist/exchanges/extended/client.d.ts +38 -0
- package/dist/exchanges/extended/client.js +88 -0
- package/dist/exchanges/extended/public.d.ts +133 -0
- package/dist/exchanges/extended/public.js +436 -0
- package/dist/exchanges/grvt/adapter.d.ts +100 -0
- package/dist/exchanges/grvt/adapter.js +283 -0
- package/dist/exchanges/grvt/client.d.ts +25 -0
- package/dist/exchanges/grvt/client.js +86 -0
- package/dist/exchanges/grvt/public.d.ts +151 -0
- package/dist/exchanges/grvt/public.js +438 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
- package/dist/exchanges/hyperliquid-outcome.js +29 -1
- package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
- package/dist/exchanges/hyperliquid-spot.js +24 -16
- package/dist/exchanges/hyperliquid.d.ts +199 -22
- package/dist/exchanges/hyperliquid.js +550 -202
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +157 -13
- package/dist/exchanges/interface.js +47 -0
- package/dist/exchanges/ioc-outcome.d.ts +1 -1
- package/dist/exchanges/ioc-outcome.js +1 -1
- package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
- package/dist/exchanges/legacy-trading-v2.js +7 -2
- package/dist/exchanges/lighter-public.d.ts +67 -12
- package/dist/exchanges/lighter-public.js +250 -82
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +10 -5
- package/dist/exchanges/lighter-spot.js +105 -97
- package/dist/exchanges/lighter.d.ts +80 -21
- package/dist/exchanges/lighter.js +312 -105
- package/dist/exchanges/ondo/adapter.d.ts +38 -13
- package/dist/exchanges/ondo/adapter.js +86 -27
- package/dist/exchanges/ondo/agent.d.ts +1 -1
- package/dist/exchanges/ondo/agent.js +1 -1
- package/dist/exchanges/ondo/client.d.ts +1 -1
- package/dist/exchanges/ondo/client.js +2 -2
- package/dist/exchanges/ondo/public.d.ts +35 -3
- package/dist/exchanges/ondo/public.js +75 -32
- package/dist/exchanges/ondo/spot.d.ts +2 -2
- package/dist/exchanges/ondo/spot.js +3 -3
- package/dist/exchanges/options-interface.d.ts +158 -0
- package/dist/exchanges/options-interface.js +17 -0
- package/dist/exchanges/options-venues.d.ts +12 -0
- package/dist/exchanges/options-venues.js +35 -0
- package/dist/exchanges/outcome-interface.d.ts +4 -0
- package/dist/exchanges/pacifica-atomic.d.ts +211 -0
- package/dist/exchanges/pacifica-atomic.js +440 -0
- package/dist/exchanges/pacifica-decimal.d.ts +10 -0
- package/dist/exchanges/pacifica-decimal.js +51 -0
- package/dist/exchanges/pacifica-presigned.d.ts +53 -0
- package/dist/exchanges/pacifica-presigned.js +141 -0
- package/dist/exchanges/pacifica-print.d.ts +200 -0
- package/dist/exchanges/pacifica-print.js +417 -0
- package/dist/exchanges/pacifica-spot.d.ts +8 -2
- package/dist/exchanges/pacifica-spot.js +26 -18
- package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
- package/dist/exchanges/pacifica-typed-data.js +1 -1
- package/dist/exchanges/pacifica-vaults.d.ts +130 -0
- package/dist/exchanges/pacifica-vaults.js +277 -0
- package/dist/exchanges/pacifica.d.ts +108 -10
- package/dist/exchanges/pacifica.js +428 -171
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/public-balances.d.ts +25 -0
- package/dist/exchanges/public-balances.js +158 -0
- package/dist/exchanges/public-positions.d.ts +43 -0
- package/dist/exchanges/public-positions.js +154 -0
- package/dist/exchanges/qfex/adapter.d.ts +69 -13
- package/dist/exchanges/qfex/adapter.js +102 -17
- package/dist/exchanges/qfex/agent.d.ts +1 -1
- package/dist/exchanges/qfex/agent.js +1 -1
- package/dist/exchanges/qfex/client.d.ts +1 -1
- package/dist/exchanges/qfex/client.js +1 -1
- package/dist/exchanges/qfex/errors.d.ts +1 -1
- package/dist/exchanges/qfex/errors.js +2 -2
- package/dist/exchanges/qfex/public.d.ts +65 -12
- package/dist/exchanges/qfex/public.js +131 -25
- package/dist/exchanges/qfex/signing.d.ts +2 -2
- package/dist/exchanges/qfex/signing.js +4 -5
- package/dist/exchanges/qfex/ws-transport.d.ts +3 -3
- package/dist/exchanges/qfex/ws-transport.js +3 -3
- package/dist/exchanges/rate-limiter.d.ts +1 -1
- package/dist/exchanges/rate-limiter.js +5 -2
- package/dist/exchanges/registry.d.ts +1 -1
- package/dist/exchanges/registry.js +1 -1
- package/dist/exchanges/risex/adapter.d.ts +160 -0
- package/dist/exchanges/risex/adapter.js +842 -0
- package/dist/exchanges/risex/agent.d.ts +154 -0
- package/dist/exchanges/risex/agent.js +384 -0
- package/dist/exchanges/risex/client.d.ts +47 -0
- package/dist/exchanges/risex/client.js +174 -0
- package/dist/exchanges/risex/nonce.d.ts +43 -0
- package/dist/exchanges/risex/nonce.js +212 -0
- package/dist/exchanges/risex/public.d.ts +64 -0
- package/dist/exchanges/risex/public.js +150 -0
- package/dist/exchanges/risex/session.d.ts +71 -0
- package/dist/exchanges/risex/session.js +171 -0
- package/dist/exchanges/risex/typed-data.d.ts +173 -0
- package/dist/exchanges/risex/typed-data.js +240 -0
- package/dist/exchanges/risex/units.d.ts +29 -0
- package/dist/exchanges/risex/units.js +104 -0
- package/dist/exchanges/slippage.d.ts +1 -1
- package/dist/exchanges/slippage.js +1 -1
- package/dist/exchanges/spot-factory.d.ts +3 -3
- package/dist/exchanges/spot-factory.js +8 -8
- package/dist/exchanges/spot-interface.d.ts +5 -5
- package/dist/exchanges/spot-interface.js +2 -2
- package/dist/exchanges/spot-result.d.ts +3 -3
- package/dist/exchanges/spot-result.js +3 -3
- package/dist/exchanges/trading-v2.d.ts +3 -3
- package/dist/exchanges/trading-v2.js +9 -6
- package/dist/exchanges/txflow/adapter.d.ts +61 -0
- package/dist/exchanges/txflow/adapter.js +150 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +155 -0
- package/dist/exchanges/txflow/public.d.ts +87 -0
- package/dist/exchanges/txflow/public.js +355 -0
- package/dist/exchanges/unknown-symbol.d.ts +34 -0
- package/dist/exchanges/unknown-symbol.js +56 -0
- package/dist/exchanges/variational/adapter.d.ts +67 -0
- package/dist/exchanges/variational/adapter.js +137 -0
- package/dist/exchanges/variational/client.d.ts +21 -0
- package/dist/exchanges/variational/client.js +66 -0
- package/dist/exchanges/variational/public.d.ts +126 -0
- package/dist/exchanges/variational/public.js +430 -0
- package/dist/exchanges/venue-types.d.ts +52 -10
- package/dist/exchanges/venue-types.js +82 -11
- package/dist/exchanges/venue.d.ts +150 -11
- package/dist/exchanges/venue.js +126 -4
- package/dist/exchanges/venues/arcus.d.ts +1 -2
- package/dist/exchanges/venues/arcus.js +5 -4
- package/dist/exchanges/venues/aster.d.ts +2 -2
- package/dist/exchanges/venues/aster.js +6 -4
- package/dist/exchanges/venues/create-helpers.d.ts +1 -1
- package/dist/exchanges/venues/create-helpers.js +3 -3
- package/dist/exchanges/venues/extended.d.ts +21 -0
- package/dist/exchanges/venues/extended.js +96 -0
- package/dist/exchanges/venues/grvt.d.ts +30 -0
- package/dist/exchanges/venues/grvt.js +98 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
- package/dist/exchanges/venues/hyperliquid.js +19 -8
- package/dist/exchanges/venues/index.d.ts +7 -2
- package/dist/exchanges/venues/index.js +8 -2
- package/dist/exchanges/venues/lighter-family.d.ts +7 -9
- package/dist/exchanges/venues/lighter-family.js +9 -11
- package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
- package/dist/exchanges/venues/lighter-rh.js +20 -8
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +18 -6
- package/dist/exchanges/venues/ondo.d.ts +1 -1
- package/dist/exchanges/venues/ondo.js +5 -3
- package/dist/exchanges/venues/pacifica.d.ts +2 -2
- package/dist/exchanges/venues/pacifica.js +15 -4
- package/dist/exchanges/venues/qfex.d.ts +1 -1
- package/dist/exchanges/venues/qfex.js +4 -2
- package/dist/exchanges/venues/risex-release.d.ts +14 -0
- package/dist/exchanges/venues/risex-release.js +14 -0
- package/dist/exchanges/venues/risex.d.ts +31 -0
- package/dist/exchanges/venues/risex.js +131 -0
- package/dist/exchanges/venues/txflow.d.ts +18 -0
- package/dist/exchanges/venues/txflow.js +77 -0
- package/dist/exchanges/venues/variational.d.ts +21 -0
- package/dist/exchanges/venues/variational.js +85 -0
- package/dist/execution-log.d.ts +1 -1
- package/dist/funding/legacy-public.d.ts +6 -4
- package/dist/funding/legacy-public.js +42 -41
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +95 -6
- package/dist/funding/rates.js +99 -19
- package/dist/index.js +7 -6
- package/dist/jobs.d.ts +1 -0
- package/dist/jobs.js +25 -1
- package/dist/landing.d.ts +2 -0
- package/dist/landing.js +1 -1
- package/dist/liquidity.d.ts +3 -1
- package/dist/liquidity.js +8 -4
- package/dist/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +64 -0
- package/dist/mcp-server.js +211 -68
- package/dist/pacifica/client.d.ts +69 -5
- package/dist/pacifica/client.js +181 -8
- package/dist/pacifica/rate-limit.d.ts +144 -0
- package/dist/pacifica/rate-limit.js +267 -0
- package/dist/pacifica/types/order.d.ts +2 -1
- package/dist/plan-executor.js +7 -2
- package/dist/positions-by-address.d.ts +34 -0
- package/dist/positions-by-address.js +46 -0
- package/dist/rebalance.js +1 -1
- package/dist/risk.d.ts +2 -1
- package/dist/risk.js +2 -4
- package/dist/settings.d.ts +32 -3
- package/dist/signer/ows-evm.d.ts +1 -1
- package/dist/signer/ows-evm.js +1 -1
- package/dist/smart-order.d.ts +1 -1
- package/dist/smart-order.js +1 -1
- package/dist/strategies/funding-arb.d.ts +1 -1
- package/dist/strategies/funding-arb.js +36 -63
- package/dist/trade-validator.js +2 -2
- package/dist/utils.d.ts +22 -3
- package/dist/utils.js +11 -3
- package/dist/venue-health.d.ts +40 -0
- package/dist/venue-health.js +75 -0
- package/dist/withdraw-gate.d.ts +1 -1
- package/dist/withdraw-gate.js +1 -1
- package/dist/ws/arcus-market.d.ts +9 -0
- package/dist/ws/arcus-market.js +9 -0
- package/dist/ws/aster-market.d.ts +33 -0
- package/dist/ws/aster-market.js +30 -0
- package/dist/ws/index.d.ts +980 -0
- package/dist/ws/index.js +4460 -0
- package/dist/ws/lighter-market.d.ts +14 -0
- package/dist/ws/lighter-market.js +13 -0
- package/dist/ws/node.d.ts +996 -0
- package/dist/ws/node.js +4554 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws/unclassified.d.ts +16 -0
- package/dist/ws/unclassified.js +43 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +71 -10
- package/skills/perp-cli/SKILL.md +153 -36
- package/skills/perp-cli/package.json +15 -2
- package/skills/perp-cli/references/agent-operations.md +22 -12
- package/skills/perp-cli/references/commands.md +143 -17
- package/skills/perp-cli/references/strategies.md +38 -3
- package/skills/perp-cli/references/ws-library.md +523 -0
- package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
- package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
- package/skills/perp-cli/scripts/preflight.sh +27 -19
- package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
- package/skills/perp-cli/scripts/validate-arb.sh +56 -39
- package/dist/cli-spec.d.ts +0 -46
- package/dist/cli-spec.js +0 -81
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pacifica-specific order types exposed through `perp -e pacifica trade …`.
|
|
3
|
+
*
|
|
4
|
+
* Of Pacifica's strategy order types (docs trading-on-pacifica/order-types/strategies) only Atomic
|
|
5
|
+
* has an API path: it is a batch of ordinary market orders (POST /orders/batch). Chase, OCO,
|
|
6
|
+
* Trailing Stop, Swarm, Trigger Swarm and Conditional Market / Limit have no documented API / SDK
|
|
7
|
+
* path (as of 2026-10-19): no REST endpoint, no signing operation type (api/signing/operation-types),
|
|
8
|
+
* no batch action type and no order-type code; the official python-sdk and Pacifica MCP server
|
|
9
|
+
* expose none. Nearest perp-cli equivalents: `trade twap` (≈ Swarm: a split market execution) and
|
|
10
|
+
* `trade tpsl` (≈ OCO bracket exit: position take-profit + stop-loss).
|
|
11
|
+
*/
|
|
12
|
+
import type { Command } from "commander";
|
|
13
|
+
import type { ExchangeAdapter } from "../exchanges/interface.js";
|
|
14
|
+
export declare function registerPacificaOrderCommands(trade: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, isDryRun: () => boolean): void;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pacifica-specific order types exposed through `perp -e pacifica trade …`.
|
|
3
|
+
*
|
|
4
|
+
* Of Pacifica's strategy order types (docs trading-on-pacifica/order-types/strategies) only Atomic
|
|
5
|
+
* has an API path: it is a batch of ordinary market orders (POST /orders/batch). Chase, OCO,
|
|
6
|
+
* Trailing Stop, Swarm, Trigger Swarm and Conditional Market / Limit have no documented API / SDK
|
|
7
|
+
* path (as of 2026-10-19): no REST endpoint, no signing operation type (api/signing/operation-types),
|
|
8
|
+
* no batch action type and no order-type code; the official python-sdk and Pacifica MCP server
|
|
9
|
+
* expose none. Nearest perp-cli equivalents: `trade twap` (≈ Swarm: a split market execution) and
|
|
10
|
+
* `trade tpsl` (≈ OCO bracket exit: position take-profit + stop-loss).
|
|
11
|
+
*/
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import { supportsDryRun } from "../cli-flags.js";
|
|
14
|
+
import { PerpError, extractErrorMessage, parsePositiveDecimal } from "../errors.js";
|
|
15
|
+
import { atomicBasketRiskCheck, atomicDryRunPlan, atomicIncompleteError, atomicLegClientOrderId, atomicLegPayload, atomicMarketsOf, executeAtomic, parseAtomicLegs, requireAtomicReduceOnly, sizeAtomicLegs, } from "../exchanges/pacifica-atomic.js";
|
|
16
|
+
import { resolveSlippage } from "../exchanges/slippage.js";
|
|
17
|
+
import { parseSlippageBpsFlag } from "./trade.js";
|
|
18
|
+
import { reduceOnlyGateOf, unsupported } from "../exchanges/trading-v2.js";
|
|
19
|
+
import { getVenue } from "../exchanges/venue.js";
|
|
20
|
+
import { quarantineEntryFor, quarantineSymbol } from "../bot/quarantine.js";
|
|
21
|
+
import { enforceOrderRisk } from "../trade-validator.js";
|
|
22
|
+
import { assessRisk, loadRiskLimits } from "../risk.js";
|
|
23
|
+
import { logExecution } from "../execution-log.js";
|
|
24
|
+
import { invalidateCache } from "../cache.js";
|
|
25
|
+
import { printJson, jsonOk } from "../utils.js";
|
|
26
|
+
export function registerPacificaOrderCommands(trade, getAdapter, isJson, isDryRun) {
|
|
27
|
+
supportsDryRun(trade.command("atomic"))
|
|
28
|
+
.description("Pacifica only: weighted basket of market orders across markets, sent in ONE batch request. Each leg fills or fails on its own (not all-or-nothing). "
|
|
29
|
+
+ "An unconfirmed leg → ORDER_PENDING and its symbol is quarantined; a batch failure other than HTTP 400 / 429 (timeout, 5xx, unreadable answer) quarantines EVERY leg. "
|
|
30
|
+
+ "Pacifica Chase / OCO / Trailing / Swarm have no documented API (as of 2026-10-19) — nearest here: trade twap (≈ Swarm), trade tpsl (≈ OCO)")
|
|
31
|
+
.requiredOption("--usd <total>", "Total basket notional in USD, split across the legs by weight at each mark price")
|
|
32
|
+
.option("--long <legs...>", "Buy legs: SYMBOL[:WEIGHT] (up to 5; no weight = 1)")
|
|
33
|
+
.option("--short <legs...>", "Sell legs: SYMBOL[:WEIGHT] (up to 5; no weight = 1)")
|
|
34
|
+
.option("--reduce-only", "Every leg reduce-only: --long legs must close shorts, --short legs must close longs")
|
|
35
|
+
.option("--slippage-bps <n>", "Per-leg slippage bound in bps (default: settings slippageBps.pacifica → defaultSlippageBps)")
|
|
36
|
+
.option("--force", "Bypass local risk-limit checks (maxPosition / total exposure) for every leg")
|
|
37
|
+
.action(async (opts) => {
|
|
38
|
+
const adapter = await getAdapter();
|
|
39
|
+
if (adapter.name !== "pacifica") {
|
|
40
|
+
const def = getVenue(adapter.name);
|
|
41
|
+
throw unsupported(def.displayName, "atomicOrder", "Atomic is a Pacifica basket order (POST /orders/batch of market orders)", "Run it on Pacifica (perp -e pacifica trade atomic …), or use perp trade multi for a cross-venue multi-leg order");
|
|
42
|
+
}
|
|
43
|
+
const pac = adapter;
|
|
44
|
+
const specs = parseAtomicLegs(opts.long ?? [], opts.short ?? []);
|
|
45
|
+
const usd = parsePositiveDecimal(opts.usd, "--usd");
|
|
46
|
+
const slippage = resolveSlippage("pacifica", parseSlippageBpsFlag(opts.slippageBps));
|
|
47
|
+
const reduceOnly = opts.reduceOnly === true;
|
|
48
|
+
const [info, prices] = await Promise.all([pac.sdk.getInfo(), pac.sdk.getPrices()]);
|
|
49
|
+
const legs = sizeAtomicLegs(specs, usd, atomicMarketsOf(specs, info, prices), slippage.bps);
|
|
50
|
+
// Pre-send gates (read only), before the dry-run return so a dry run reports the same refusal.
|
|
51
|
+
if (reduceOnly) {
|
|
52
|
+
const gate = reduceOnlyGateOf(adapter);
|
|
53
|
+
if (gate !== null)
|
|
54
|
+
throw gate;
|
|
55
|
+
invalidateCache("acct");
|
|
56
|
+
requireAtomicReduceOnly(legs, await adapter.getPositions());
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
for (const l of legs) {
|
|
60
|
+
const q = quarantineEntryFor(l.symbol, (m) => { if (!isJson())
|
|
61
|
+
console.error(m); });
|
|
62
|
+
if (q !== null) {
|
|
63
|
+
throw new PerpError("RISK_VIOLATION", `${l.symbol} is quarantined by ${q.strategy}: ${q.reason}`, {
|
|
64
|
+
exchange: "pacifica",
|
|
65
|
+
remediation: `${q.remediation} — only reduce-only orders are accepted until a human clears it (perp strategy quarantine-clear ${l.symbol})`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
await enforceOrderRisk(adapter, { symbol: l.symbol, size: Number(l.size), price: Number(l.mark), force: opts.force });
|
|
69
|
+
}
|
|
70
|
+
// The basket as one order: total notional vs total exposure, new markets vs max positions.
|
|
71
|
+
if (opts.force !== true) {
|
|
72
|
+
const [balance, positions] = await Promise.all([adapter.getBalance(), adapter.getPositions()]);
|
|
73
|
+
const assessment = assessRisk([{ exchange: adapter.name, balance }], positions.map(position => ({ exchange: adapter.name, position })), loadRiskLimits());
|
|
74
|
+
atomicBasketRiskCheck(assessment, legs, positions);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const builder = await pac.builderDecision();
|
|
78
|
+
const builderCode = builder.applied === null ? null : builder.applied.identity;
|
|
79
|
+
const payloads = legs.map(l => atomicLegPayload(l, { reduceOnly, slippageBps: slippage.bps, clientOrderId: atomicLegClientOrderId(), builderCode }));
|
|
80
|
+
const legView = legs.map((l, i) => ({
|
|
81
|
+
symbol: l.symbol, side: l.side, weight: l.weight, share: Number(l.share.toFixed(6)), mark: l.mark, lotSize: l.lotSize,
|
|
82
|
+
size: l.size, notionalUsd: Number(l.notionalUsd.toFixed(2)), clientOrderId: payloads[i].client_order_id,
|
|
83
|
+
}));
|
|
84
|
+
const summary = { exchange: "pacifica", totalUsd: usd, reduceOnly, slippageBps: slippage.bps, legs: legView, builderFee: builder.applied, ...(builder.note !== null ? { builderNote: builder.note } : {}) };
|
|
85
|
+
if (isDryRun()) {
|
|
86
|
+
const tier = pac.activeSignerTier;
|
|
87
|
+
const plan = atomicDryRunPlan(payloads, {
|
|
88
|
+
account: pac.isReadOnly ? "<no signer configured: a live run needs a Pacifica key>" : pac.publicKey,
|
|
89
|
+
agentWallet: tier === "agent" ? "<agent wallet public key>" : null,
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
});
|
|
92
|
+
for (const l of legs) {
|
|
93
|
+
logExecution({ type: "multi_leg", exchange: "pacifica", symbol: l.symbol, side: l.side, size: l.size, status: "simulated", dryRun: true, meta: { atomic: true, reduceOnly } });
|
|
94
|
+
}
|
|
95
|
+
if (isJson())
|
|
96
|
+
return printJson(jsonOk({ dryRun: true, sent: false, action: "atomic_order", ...summary, request: plan }));
|
|
97
|
+
console.log(chalk.yellow("\n [DRY RUN] Would send a Pacifica Atomic basket (nothing signed or sent):"));
|
|
98
|
+
for (const l of legView)
|
|
99
|
+
console.log(chalk.gray(` ${l.side.toUpperCase().padEnd(4)} ${l.size} ${l.symbol} ≈ $${l.notionalUsd} (weight ${l.weight}, mark ${l.mark})`));
|
|
100
|
+
console.log(chalk.gray(` reduce-only: ${reduceOnly} · slippage: ${slippage.bps} bps per leg · builder: ${builder.applied === null ? "none" : builder.applied.identity}`));
|
|
101
|
+
console.log(chalk.gray(` ${plan.endpoint}\n${JSON.stringify(plan.body, null, 2).split("\n").map(s => ` ${s}`).join("\n")}`));
|
|
102
|
+
console.log(chalk.gray(` signed messages (create_market_order, one per leg):\n${plan.signedMessages.map(s => ` ${s}`).join("\n")}\n`));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let outcomes;
|
|
106
|
+
let venueRaw;
|
|
107
|
+
try {
|
|
108
|
+
({ outcomes, venueRaw } = await executeAtomic(pac, legs, payloads));
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
for (const l of legs) {
|
|
112
|
+
logExecution({ type: "multi_leg", exchange: "pacifica", symbol: l.symbol, side: l.side, size: l.size, status: e instanceof PerpError && e.structured.code === "ORDER_PENDING" ? "unverified" : "failed", dryRun: false, error: extractErrorMessage(e), meta: { atomic: true, reduceOnly } });
|
|
113
|
+
if (!reduceOnly && e instanceof PerpError && e.structured.code === "ORDER_PENDING")
|
|
114
|
+
quarantineAtomicLeg(l.symbol, extractErrorMessage(e), isJson);
|
|
115
|
+
}
|
|
116
|
+
throw e;
|
|
117
|
+
}
|
|
118
|
+
for (const o of outcomes) {
|
|
119
|
+
logExecution({
|
|
120
|
+
type: "multi_leg", exchange: "pacifica", symbol: o.symbol, side: o.side, size: o.size,
|
|
121
|
+
status: o.status === "unknown" ? "unverified" : o.status === "filled" || o.status === "partiallyFilled" ? "success" : "failed",
|
|
122
|
+
dryRun: false, ...(o.detail !== null && o.status !== "filled" ? { error: o.detail } : {}),
|
|
123
|
+
meta: { atomic: true, reduceOnly, orderId: o.orderId, clientOrderId: o.clientOrderId, filledSize: o.filledSize },
|
|
124
|
+
});
|
|
125
|
+
if (o.status === "unknown" && !reduceOnly)
|
|
126
|
+
quarantineAtomicLeg(o.symbol, o.detail ?? "outcome unknown", isJson);
|
|
127
|
+
}
|
|
128
|
+
const incomplete = atomicIncompleteError(outcomes);
|
|
129
|
+
if (incomplete !== null)
|
|
130
|
+
throw incomplete;
|
|
131
|
+
if (isJson())
|
|
132
|
+
return printJson(jsonOk({ ...summary, outcomes, venueRaw }));
|
|
133
|
+
console.log(chalk.green(`\n Atomic basket filled on Pacifica ($${usd}):`));
|
|
134
|
+
for (const o of outcomes)
|
|
135
|
+
console.log(` ${chalk.green("FILLED")} ${o.side.toUpperCase().padEnd(4)} ${o.filledSize ?? o.size} ${o.symbol} order ${o.orderId ?? "-"}`);
|
|
136
|
+
console.log();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function quarantineAtomicLeg(symbol, reason, isJson) {
|
|
140
|
+
quarantineSymbol("trade-atomic", symbol, `atomic leg on ${symbol}: outcome unknown (${reason})`, `Check the position and fills: perp -e pacifica account positions / trade fills ${symbol}`, (m) => { if (!isJson())
|
|
141
|
+
console.error(m); });
|
|
142
|
+
}
|
|
@@ -16,10 +16,26 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { Command } from "commander";
|
|
18
18
|
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
19
|
+
import type { DashboardExchange } from "../dashboard/index.js";
|
|
19
20
|
import "../exchanges/venues/index.js";
|
|
21
|
+
/**
|
|
22
|
+
* HIP-3 dex names, read live from info `perpDexs` on the selected network (index 0 = the native
|
|
23
|
+
* dex, null). Throws on a failed or malformed read — never substitutes a fixed list (Rule #2):
|
|
24
|
+
* a stale list hides positions on any dex Hyperliquid adds later.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fetchHip3DexNames(): Promise<string[]>;
|
|
20
27
|
/**
|
|
21
28
|
* Open-position count on a HIP-3 dex; null = the read failed (unknown — NOT "no positions").
|
|
22
29
|
* The failure is warned on stderr; callers must treat null as "may hold positions".
|
|
23
30
|
*/
|
|
24
31
|
export declare function checkDexPositions(address: string, dex: string): Promise<number | null>;
|
|
32
|
+
/**
|
|
33
|
+
* HIP-3 dexes holding positions (or whose read failed) for `address`, scanning every dex of the
|
|
34
|
+
* live `perpDexs` list. `error` is set when the dex list itself could not be read: no dex is
|
|
35
|
+
* scanned and the caller must surface it (positions on HIP-3 dexes are unknown, not absent).
|
|
36
|
+
*/
|
|
37
|
+
export declare function autoDetectHip3Dexes(address: string, getHLAdapterForDex: (dex: string) => Promise<ExchangeAdapter>, verbose: boolean): Promise<{
|
|
38
|
+
exchanges: DashboardExchange[];
|
|
39
|
+
error: string | null;
|
|
40
|
+
}>;
|
|
25
41
|
export declare function registerPortfolioCommand(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, version: string, getHLAdapterForDex?: (dex: string) => Promise<ExchangeAdapter>): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
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";
|
|
4
|
+
import { getVenue, listVenuesWithRole, roleUnsupported, tryResolveVenueId } from "../exchanges/venue.js";
|
|
5
5
|
import { PerpError } from "../errors.js";
|
|
6
6
|
import "../exchanges/venues/index.js";
|
|
7
7
|
import { listConfiguredAccountVenues } from "../config.js";
|
|
@@ -9,6 +9,7 @@ import { HYPERLIQUID_API_URL } from "../api/public/urls.js";
|
|
|
9
9
|
import { fetchValuedSpotBalances, getSpotAdapter, spotSharesPerpMargin, withSpotTimeout } from "../exchanges/spot-factory.js";
|
|
10
10
|
import { scrubSecretEnv } from "../secret-env.js";
|
|
11
11
|
import { networkOf } from "../cli-flags.js";
|
|
12
|
+
import { probeVenueHealth } from "../venue-health.js";
|
|
12
13
|
// ── Types ──────────────────────────────────────────────────────────────────
|
|
13
14
|
/** Venues with an account API (market-data-only venues are excluded). */
|
|
14
15
|
function accountVenueIds() {
|
|
@@ -51,6 +52,12 @@ function validateExchanges(list) {
|
|
|
51
52
|
const valid = accountVenueIds();
|
|
52
53
|
// Aliases (hl, pac, lt, lrh, ast, …) resolve through the venue registry, like every other command.
|
|
53
54
|
const ids = list.map(s => tryResolveVenueId(s) ?? s.toLowerCase());
|
|
55
|
+
// A registered market-data-only venue (TxFlow) is a capability refusal, not an unknown name.
|
|
56
|
+
for (const id of ids) {
|
|
57
|
+
const known = tryResolveVenueId(id);
|
|
58
|
+
if (known !== null && !valid.includes(known))
|
|
59
|
+
throw roleUnsupported(getVenue(known), "account");
|
|
60
|
+
}
|
|
54
61
|
const unknown = ids.filter(e => !valid.includes(e));
|
|
55
62
|
if (unknown.length > 0) {
|
|
56
63
|
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)` });
|
|
@@ -178,7 +185,7 @@ async function fetchExchangeEntry(exName, getAdapter) {
|
|
|
178
185
|
adapter.getOpenOrders(),
|
|
179
186
|
adapter.getFundingPayments(200).then((rows) => ({ rows, error: null }), (e) => ({ rows: null, error: e instanceof Error ? e.message : String(e) })),
|
|
180
187
|
]);
|
|
181
|
-
// Spot balances for every venue with def.spot
|
|
188
|
+
// Spot balances for every venue with def.spot. Failures are surfaced on
|
|
182
189
|
// stderr (the perp snapshot is still reported); unpriced tokens are excluded + warned.
|
|
183
190
|
let spot = [];
|
|
184
191
|
let isUnified = false;
|
|
@@ -205,7 +212,7 @@ async function fetchExchangeEntry(exName, getAdapter) {
|
|
|
205
212
|
const funding24h = fundingPayments.rows === null
|
|
206
213
|
? null
|
|
207
214
|
: fundingPayments.rows.filter(f => f.time >= cutoff24h).reduce((sum, f) => sum + Number(f.payment), 0);
|
|
208
|
-
// Shared balances (Pacifica USDC, HL unified USDC) are already in perp equity
|
|
215
|
+
// Shared balances (Pacifica USDC, HL unified USDC) are already in perp equity.
|
|
209
216
|
const nonUsdcSpotValue = spot.filter(b => !b.shared).reduce((s, b) => s + b.valueUsd, 0);
|
|
210
217
|
return {
|
|
211
218
|
name: exName,
|
|
@@ -288,46 +295,16 @@ function buildTotals(entries) {
|
|
|
288
295
|
}
|
|
289
296
|
// ── Health check (no balance fetches) ──────────────────────────────────────
|
|
290
297
|
async function runHealth(exchangeList, network) {
|
|
291
|
-
|
|
292
|
-
const
|
|
293
|
-
const start = Date.now();
|
|
294
|
-
try {
|
|
295
|
-
const res = await fetch("https://fapi.asterdex.com/fapi/v1/time");
|
|
296
|
-
return { ok: res.ok, latencyMs: Date.now() - start, status: res.status };
|
|
297
|
-
}
|
|
298
|
-
catch {
|
|
299
|
-
return { ok: false, latencyMs: Date.now() - start, status: 0 };
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
const pingMap = {
|
|
303
|
-
pacifica: pingPacifica,
|
|
304
|
-
hyperliquid: pingHyperliquid,
|
|
305
|
-
lighter: pingLighter,
|
|
306
|
-
aster: pingAster,
|
|
307
|
-
};
|
|
298
|
+
// The same probe as `perp health` and the MCP health_check, over the selected venues.
|
|
299
|
+
const h = await probeVenueHealth(network, exchangeList);
|
|
308
300
|
const exchanges = {};
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (!fn) {
|
|
313
|
-
// No dedicated ping helper: probe the venue's public market-data path.
|
|
314
|
-
await getVenue(ex).publicData.fetchMarkets(network);
|
|
315
|
-
exchanges[ex] = "ok";
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
const r = await fn();
|
|
319
|
-
exchanges[ex] = r.ok ? "ok" : "error";
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
exchanges[ex] = "error";
|
|
323
|
-
}
|
|
324
|
-
}));
|
|
325
|
-
const anyDown = Object.values(exchanges).some(v => v === "error");
|
|
326
|
-
return { exchanges, anyDown };
|
|
301
|
+
for (const p of h.probes)
|
|
302
|
+
exchanges[p.venue] = p.ok ? "ok" : "error";
|
|
303
|
+
return { exchanges, anyDown: !h.allHealthy, probes: h.probes, skipped: h.skipped };
|
|
327
304
|
}
|
|
328
305
|
// ── Arb top-5 ──────────────────────────────────────────────────────────────
|
|
329
306
|
async function fetchArbTop() {
|
|
330
|
-
const { fetchAllFundingRates, TOP_SYMBOLS } = await import("../funding-rates.js");
|
|
307
|
+
const { fetchAllFundingRates, isRankedComparison, TOP_SYMBOLS } = await import("../funding-rates.js");
|
|
331
308
|
const { saveFundingSnapshot, getHistoricalAverages } = await import("../funding-history.js");
|
|
332
309
|
let snapshot;
|
|
333
310
|
try {
|
|
@@ -346,6 +323,7 @@ async function fetchArbTop() {
|
|
|
346
323
|
}
|
|
347
324
|
catch { /* ignore */ }
|
|
348
325
|
const top = snapshot.symbols
|
|
326
|
+
.filter(isRankedComparison)
|
|
349
327
|
.filter(s => s.maxSpreadAnnual >= 5)
|
|
350
328
|
.sort((a, b) => b.maxSpreadAnnual - a.maxSpreadAnnual)
|
|
351
329
|
.slice(0, 5);
|
|
@@ -514,11 +492,15 @@ function renderSnapshotText(data) {
|
|
|
514
492
|
}
|
|
515
493
|
function renderHealthText(h) {
|
|
516
494
|
console.log(chalk.cyan.bold("\n Exchange Health Check\n"));
|
|
517
|
-
const rows =
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
495
|
+
const rows = h.probes.map((p) => [
|
|
496
|
+
chalk.white.bold(p.venue),
|
|
497
|
+
p.ok ? chalk.green("OK") : chalk.red("DOWN"),
|
|
498
|
+
`${p.latencyMs}ms`,
|
|
499
|
+
p.error !== null ? chalk.red(p.error) : chalk.gray("-"),
|
|
500
|
+
]);
|
|
501
|
+
console.log(makeTable(["Exchange", "Status", "Latency", "Error"], rows));
|
|
502
|
+
for (const s of h.skipped)
|
|
503
|
+
console.log(chalk.gray(` ${s.venue}: not probed — ${s.reason}`));
|
|
522
504
|
const overall = h.anyDown ? chalk.red("ISSUES DETECTED") : chalk.green("ALL HEALTHY");
|
|
523
505
|
console.log(`\n Overall: ${overall}\n`);
|
|
524
506
|
}
|
|
@@ -546,7 +528,31 @@ async function runWatch(resolved, version, getAdapterForExchange, isJsonMode) {
|
|
|
546
528
|
}
|
|
547
529
|
}
|
|
548
530
|
// ── Serve mode (delegates to dashboard infra) ──────────────────────────────
|
|
549
|
-
|
|
531
|
+
/**
|
|
532
|
+
* HIP-3 dex names, read live from info `perpDexs` on the selected network (index 0 = the native
|
|
533
|
+
* dex, null). Throws on a failed or malformed read — never substitutes a fixed list (Rule #2):
|
|
534
|
+
* a stale list hides positions on any dex Hyperliquid adds later.
|
|
535
|
+
*/
|
|
536
|
+
export async function fetchHip3DexNames() {
|
|
537
|
+
const res = await fetch(HYPERLIQUID_API_URL, {
|
|
538
|
+
method: "POST",
|
|
539
|
+
headers: { "Content-Type": "application/json" },
|
|
540
|
+
body: JSON.stringify({ type: "perpDexs" }),
|
|
541
|
+
});
|
|
542
|
+
if (!res.ok)
|
|
543
|
+
throw new Error(`info perpDexs HTTP ${res.status}`);
|
|
544
|
+
const list = await res.json();
|
|
545
|
+
if (!Array.isArray(list))
|
|
546
|
+
throw new Error("info perpDexs: expected an array");
|
|
547
|
+
return list.flatMap((d, i) => {
|
|
548
|
+
if (d === null)
|
|
549
|
+
return [];
|
|
550
|
+
const name = d?.name;
|
|
551
|
+
if (typeof name !== "string" || name === "")
|
|
552
|
+
throw new Error(`info perpDexs: entry ${i} has no dex name`);
|
|
553
|
+
return [name];
|
|
554
|
+
});
|
|
555
|
+
}
|
|
550
556
|
/**
|
|
551
557
|
* Open-position count on a HIP-3 dex; null = the read failed (unknown — NOT "no positions").
|
|
552
558
|
* The failure is warned on stderr; callers must treat null as "may hold positions".
|
|
@@ -577,8 +583,22 @@ export async function checkDexPositions(address, dex) {
|
|
|
577
583
|
return null;
|
|
578
584
|
}
|
|
579
585
|
}
|
|
580
|
-
|
|
581
|
-
|
|
586
|
+
/**
|
|
587
|
+
* HIP-3 dexes holding positions (or whose read failed) for `address`, scanning every dex of the
|
|
588
|
+
* live `perpDexs` list. `error` is set when the dex list itself could not be read: no dex is
|
|
589
|
+
* scanned and the caller must surface it (positions on HIP-3 dexes are unknown, not absent).
|
|
590
|
+
*/
|
|
591
|
+
export async function autoDetectHip3Dexes(address, getHLAdapterForDex, verbose) {
|
|
592
|
+
let dexes;
|
|
593
|
+
try {
|
|
594
|
+
dexes = await fetchHip3DexNames();
|
|
595
|
+
}
|
|
596
|
+
catch (e) {
|
|
597
|
+
const error = `HIP-3 dex list unavailable — HIP-3 positions unknown, pass --dexes to monitor specific dexes: ${e instanceof Error ? e.message : String(e)}`;
|
|
598
|
+
process.stderr.write(`[warn] hl:hip3 ${error}\n`);
|
|
599
|
+
return { exchanges: [], error };
|
|
600
|
+
}
|
|
601
|
+
const checks = await Promise.all(dexes.map(async (dex) => ({
|
|
582
602
|
dex,
|
|
583
603
|
count: await checkDexPositions(address, dex),
|
|
584
604
|
})));
|
|
@@ -599,7 +619,7 @@ async function autoDetectHip3Dexes(address, getHLAdapterForDex, verbose) {
|
|
|
599
619
|
process.stderr.write(`[warn] hl:${dex} left out of the dashboard (adapter unavailable${count === null ? ", positions unknown" : `, ${count} position(s)`}): ${e instanceof Error ? e.message : String(e)}\n`);
|
|
600
620
|
}
|
|
601
621
|
}
|
|
602
|
-
return results;
|
|
622
|
+
return { exchanges: results, error: null };
|
|
603
623
|
}
|
|
604
624
|
async function runServe(resolved, getAdapterForExchange, isJsonMode, getHLAdapterForDex) {
|
|
605
625
|
const { startDashboard } = await import("../dashboard/index.js");
|
|
@@ -635,7 +655,9 @@ async function runServe(resolved, getAdapterForExchange, isJsonMode, getHLAdapte
|
|
|
635
655
|
const hlAddr = hlEx ? hlEx.adapter.address ?? "" : "";
|
|
636
656
|
if (hlAddr) {
|
|
637
657
|
const detected = await autoDetectHip3Dexes(hlAddr, getHLAdapterForDex, false);
|
|
638
|
-
exchanges.push(...detected);
|
|
658
|
+
exchanges.push(...detected.exchanges);
|
|
659
|
+
if (detected.error !== null)
|
|
660
|
+
errors["hl:hip3"] = detected.error;
|
|
639
661
|
}
|
|
640
662
|
}
|
|
641
663
|
}
|
|
@@ -694,8 +716,10 @@ async function runServe(resolved, getAdapterForExchange, isJsonMode, getHLAdapte
|
|
|
694
716
|
if (hlAddr) {
|
|
695
717
|
console.log(chalk.gray(" Scanning HIP-3 dexes for active positions..."));
|
|
696
718
|
const detected = await autoDetectHip3Dexes(hlAddr, getHLAdapterForDex, true);
|
|
697
|
-
exchanges.push(...detected);
|
|
698
|
-
if (
|
|
719
|
+
exchanges.push(...detected.exchanges);
|
|
720
|
+
if (detected.error !== null)
|
|
721
|
+
console.log(chalk.yellow(` ✗ hl:hip3: ${detected.error.slice(0, 120)}`));
|
|
722
|
+
else if (!detected.exchanges.length)
|
|
699
723
|
console.log(chalk.gray(" No active HIP-3 dex positions found"));
|
|
700
724
|
}
|
|
701
725
|
}
|
|
@@ -773,7 +797,11 @@ export function registerPortfolioCommand(program, getAdapterForExchange, isJson,
|
|
|
773
797
|
catch (err) {
|
|
774
798
|
const msg = err instanceof Error ? err.message : String(err);
|
|
775
799
|
if (json) {
|
|
776
|
-
|
|
800
|
+
// A typed refusal (CAPABILITY_UNSUPPORTED for a market-data-only venue) keeps its code.
|
|
801
|
+
const s = err instanceof PerpError ? err.structured : null;
|
|
802
|
+
console.log(JSON.stringify(s !== null
|
|
803
|
+
? jsonError(s.code, msg, { ...(s.details !== undefined ? { details: s.details } : {}), ...(s.remediation !== undefined ? { remediation: s.remediation } : {}) })
|
|
804
|
+
: jsonError("INVALID_PARAMS", msg)));
|
|
777
805
|
}
|
|
778
806
|
else {
|
|
779
807
|
console.error(chalk.red(`Error: ${msg}`));
|
|
@@ -794,9 +822,9 @@ export function registerPortfolioCommand(program, getAdapterForExchange, isJson,
|
|
|
794
822
|
version,
|
|
795
823
|
scope: resolved.scope,
|
|
796
824
|
mode: "health",
|
|
797
|
-
exchanges:
|
|
798
|
-
totals:
|
|
799
|
-
risk:
|
|
825
|
+
exchanges: null,
|
|
826
|
+
totals: null,
|
|
827
|
+
risk: null,
|
|
800
828
|
health,
|
|
801
829
|
};
|
|
802
830
|
if (json)
|
|
@@ -1,4 +1,5 @@
|
|
|
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
|
+
import "../exchanges/venues/index.js";
|
|
4
5
|
export declare function registerFundsRebalanceCommands(parent: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, getNetwork: () => Network): void;
|
|
@@ -5,6 +5,10 @@ import { PerpError } from "../errors.js";
|
|
|
5
5
|
import { fetchBalancesWithFailures, computeRebalancePlan, describeBridgeRoute, estimateMoveTime, } from "../rebalance.js";
|
|
6
6
|
import { EXCHANGE_TO_CHAIN, assertBridgeNetwork, executeBestBridge } from "../bridge-engine.js";
|
|
7
7
|
import { cliCredentialOptsFrom } from "../config.js";
|
|
8
|
+
import { getVenue, roleUnsupported, tryResolveVenueId } from "../exchanges/venue.js";
|
|
9
|
+
import { unsupported } from "../exchanges/trading-v2.js";
|
|
10
|
+
import { RISEX_FUNDS_APP_ONLY } from "../exchanges/venues/risex.js";
|
|
11
|
+
import "../exchanges/venues/index.js";
|
|
8
12
|
export function registerFundsRebalanceCommands(parent, getAdapterForExchange, isJson, getNetwork) {
|
|
9
13
|
const rebalance = parent.command("rebalance").description("Cross-exchange balance management");
|
|
10
14
|
// ── rebalance check ──
|
|
@@ -149,6 +153,17 @@ export function registerFundsRebalanceCommands(parent, getAdapterForExchange, is
|
|
|
149
153
|
if (opts.autoBridge && !dryRun)
|
|
150
154
|
assertBridgeNetwork(getNetwork());
|
|
151
155
|
const exchangeNames = opts.exchanges.split(",").map((e) => e.trim());
|
|
156
|
+
// Venues whose funds perp-cli cannot move are refused before any read (no partial plan).
|
|
157
|
+
for (const name of exchangeNames) {
|
|
158
|
+
const id = tryResolveVenueId(name);
|
|
159
|
+
if (id === null)
|
|
160
|
+
continue;
|
|
161
|
+
const def = getVenue(id);
|
|
162
|
+
if (!def.roles.includes("account"))
|
|
163
|
+
throw roleUnsupported(def, "rebalance");
|
|
164
|
+
if (id === "risex")
|
|
165
|
+
throw unsupported("risex", "rebalance", RISEX_FUNDS_APP_ONLY.reason, RISEX_FUNDS_APP_ONLY.remediation);
|
|
166
|
+
}
|
|
152
167
|
const adapters = new Map();
|
|
153
168
|
const unavailable = [];
|
|
154
169
|
for (const name of exchangeNames) {
|
package/dist/commands/risk.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import type { Network } from "../exchanges/venue-types.js";
|
|
4
|
+
/**
|
|
5
|
+
* `perp health`: probe EVERY registered venue on the selected network — the same probe as
|
|
6
|
+
* `perp portfolio --health` and the MCP health_check (src/venue-health.ts). A venue with no
|
|
7
|
+
* environment on that network is listed under `skipped`, never silently dropped.
|
|
8
|
+
*
|
|
9
|
+
* JSON: `exchanges` = official-API venues, `unofficial` = venues read through an undocumented API
|
|
10
|
+
* (TxFlow), each entry with `error` (null when ok). `healthy` = every OFFICIAL venue is up — an
|
|
11
|
+
* unofficial API being down does not make the CLI unhealthy; `allHealthy` includes them.
|
|
12
|
+
*/
|
|
13
|
+
export declare function runHealthCheck(isJson: () => boolean, network: Network): Promise<void>;
|
|
5
14
|
export declare function registerRiskCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
|
package/dist/commands/risk.js
CHANGED
|
@@ -2,61 +2,55 @@ import { listConfiguredAccountVenues } from "../config.js";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { printJson, jsonOk, makeTable, formatUsd, withJsonErrors } from "../utils.js";
|
|
4
4
|
import { loadRiskLimits, saveRiskLimits, assessRisk, getLiquidationDistances } from "../risk.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
pingHyperliquid(),
|
|
21
|
-
pingLighter(),
|
|
22
|
-
pingAster(),
|
|
23
|
-
]);
|
|
24
|
-
const toPingResult = (name, p) => ({
|
|
25
|
-
exchange: name,
|
|
5
|
+
import { probeVenueHealth } from "../venue-health.js";
|
|
6
|
+
import { networkOf } from "../cli-flags.js";
|
|
7
|
+
/**
|
|
8
|
+
* `perp health`: probe EVERY registered venue on the selected network — the same probe as
|
|
9
|
+
* `perp portfolio --health` and the MCP health_check (src/venue-health.ts). A venue with no
|
|
10
|
+
* environment on that network is listed under `skipped`, never silently dropped.
|
|
11
|
+
*
|
|
12
|
+
* JSON: `exchanges` = official-API venues, `unofficial` = venues read through an undocumented API
|
|
13
|
+
* (TxFlow), each entry with `error` (null when ok). `healthy` = every OFFICIAL venue is up — an
|
|
14
|
+
* unofficial API being down does not make the CLI unhealthy; `allHealthy` includes them.
|
|
15
|
+
*/
|
|
16
|
+
export async function runHealthCheck(isJson, network) {
|
|
17
|
+
const health = await probeVenueHealth(network);
|
|
18
|
+
const checks = health.probes.map(p => ({
|
|
19
|
+
exchange: p.venue,
|
|
26
20
|
status: p.ok ? "ok" : "down",
|
|
27
21
|
latency_ms: p.latencyMs,
|
|
28
|
-
error: p.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
];
|
|
36
|
-
const allOk = checks.every(c => c.status === "ok");
|
|
22
|
+
error: p.error,
|
|
23
|
+
probe: p.probe,
|
|
24
|
+
apiStatus: p.apiStatus,
|
|
25
|
+
}));
|
|
26
|
+
const official = checks.filter(c => c.apiStatus === "official");
|
|
27
|
+
const unofficial = checks.filter(c => c.apiStatus === "unofficial");
|
|
28
|
+
const allOk = official.every(c => c.status === "ok");
|
|
37
29
|
if (isJson()) {
|
|
38
|
-
return printJson(jsonOk({ healthy: allOk, exchanges:
|
|
30
|
+
return printJson(jsonOk({ healthy: allOk, allHealthy: health.allHealthy, network, exchanges: official, unofficial, skipped: health.skipped }));
|
|
39
31
|
}
|
|
40
|
-
console.log(chalk.cyan.bold(
|
|
32
|
+
console.log(chalk.cyan.bold(`\n Exchange Health Check (${network})\n`));
|
|
41
33
|
const rows = checks.map(c => {
|
|
42
|
-
const statusIcon = c.status === "ok"
|
|
43
|
-
? chalk.green("OK")
|
|
44
|
-
: c.status === "degraded"
|
|
45
|
-
? chalk.yellow("DEGRADED")
|
|
46
|
-
: chalk.red("DOWN");
|
|
34
|
+
const statusIcon = c.status === "ok" ? chalk.green("OK") : chalk.red("DOWN");
|
|
47
35
|
const latency = c.latency_ms < 500
|
|
48
36
|
? chalk.green(`${c.latency_ms}ms`)
|
|
49
37
|
: c.latency_ms < 2000
|
|
50
38
|
? chalk.yellow(`${c.latency_ms}ms`)
|
|
51
39
|
: chalk.red(`${c.latency_ms}ms`);
|
|
52
40
|
return [
|
|
53
|
-
chalk.white.bold(c.exchange),
|
|
41
|
+
chalk.white.bold(c.exchange) + (c.apiStatus === "unofficial" ? chalk.yellow(" *") : ""),
|
|
54
42
|
statusIcon,
|
|
55
43
|
latency,
|
|
56
|
-
c.
|
|
44
|
+
c.probe,
|
|
45
|
+
c.error !== null ? chalk.red(c.error) : chalk.gray("-"),
|
|
57
46
|
];
|
|
58
47
|
});
|
|
59
|
-
console.log(makeTable(["Exchange", "Status", "Latency", "Error"], rows));
|
|
48
|
+
console.log(makeTable(["Exchange", "Status", "Latency", "Probe", "Error"], rows));
|
|
49
|
+
if (unofficial.length > 0) {
|
|
50
|
+
console.log(chalk.yellow(" * unofficial (undocumented) venue API — may change without notice; not counted in Overall"));
|
|
51
|
+
}
|
|
52
|
+
for (const s of health.skipped)
|
|
53
|
+
console.log(chalk.gray(` ${s.venue}: not probed — ${s.reason}`));
|
|
60
54
|
const overall = allOk ? chalk.green("ALL HEALTHY") : chalk.red("ISSUES DETECTED");
|
|
61
55
|
console.log(`\n Overall: ${overall}\n`);
|
|
62
56
|
}
|
|
@@ -122,11 +116,12 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
|
|
|
122
116
|
console.log(` Positions: ${assessment.metrics.positionCount}`);
|
|
123
117
|
console.log(` Largest Position: $${formatUsd(assessment.metrics.largestPositionUsd)}`);
|
|
124
118
|
console.log(` Max Leverage Used: ${assessment.metrics.maxLeverageUsed}x`);
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
const minLiq = assessment.metrics.minLiquidationDistancePct;
|
|
120
|
+
if (minLiq !== null) {
|
|
121
|
+
const ldColor = minLiq < assessment.limits.minLiquidationDistance
|
|
127
122
|
? chalk.red
|
|
128
123
|
: chalk.green;
|
|
129
|
-
console.log(` Min Liq Distance: ${ldColor(`${
|
|
124
|
+
console.log(` Min Liq Distance: ${ldColor(`${minLiq.toFixed(1)}%`)}`);
|
|
130
125
|
}
|
|
131
126
|
for (const { exchange, position: p } of positions.filter(x => x.position.liquidationPrice === null)) {
|
|
132
127
|
console.log(` Liq Price ${exchange}:${p.symbol}: ${chalk.yellow("unknown (venue does not report)")} — not in Min Liq Distance`);
|
|
@@ -367,6 +362,6 @@ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
|
|
|
367
362
|
.command("health")
|
|
368
363
|
.description("Adapter health check: ping venue REST endpoints (connectivity + latency)")
|
|
369
364
|
.action(async () => {
|
|
370
|
-
await runHealthCheck(isJson);
|
|
365
|
+
await runHealthCheck(isJson, networkOf(program));
|
|
371
366
|
});
|
|
372
367
|
}
|
package/dist/commands/trade.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
|
3
3
|
import { PerpError } from "../errors.js";
|
|
4
4
|
import "../exchanges/venues/index.js";
|
|
5
5
|
import { type CancelResult } from "../exchanges/venue-types.js";
|
|
6
|
+
export declare function parseSlippageBpsFlag(raw: string | undefined): number | null;
|
|
6
7
|
/**
|
|
7
8
|
* A cancel with anything not proven cancelled (failed, or pending / still listed open after the
|
|
8
9
|
* verifying re-list) → ORDER_PARTIAL carrying the whole result (non-zero exit, also in --json);
|