perp-cli 0.12.17 → 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 +247 -27
- 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 +70 -92
- 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 +156 -684
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +7 -2
- package/dist/landing.js +19 -3
- 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 +142 -55
- package/skills/perp-cli/references/agent-operations.md +110 -56
- package/skills/perp-cli/references/commands.md +266 -71
- package/skills/perp-cli/references/strategies.md +34 -31
- package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
- package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
- 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 +31 -15
- 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
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `perp outcome` — Hyperliquid Outcome markets (HIP-4).
|
|
3
|
+
*
|
|
4
|
+
* Currently HL-only. Asset class is fully collateralized binary/range
|
|
5
|
+
* contracts, quoted per outcome (outcomeMeta quoteToken, e.g. USDC). No leverage, no liquidation. Settlement is
|
|
6
|
+
* dated per-outcome.
|
|
7
|
+
*/
|
|
8
|
+
import { isDryRun, supportsDryRun } from "../cli-flags.js";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import { HyperliquidAdapter } from "../exchanges/hyperliquid.js";
|
|
11
|
+
import { HyperliquidOutcomeAdapter } from "../exchanges/hyperliquid-outcome.js";
|
|
12
|
+
import { makeTable, printJson, jsonOk, formatUsd } from "../utils.js";
|
|
13
|
+
import { PerpError } from "../errors.js";
|
|
14
|
+
export function registerOutcomeCommands(program, getAdapterForExchange, isJson) {
|
|
15
|
+
const outcome = program
|
|
16
|
+
.command("outcome")
|
|
17
|
+
.description("Hyperliquid Outcome markets (HIP-4) — binary/range contracts");
|
|
18
|
+
async function getOutcomeAdapter() {
|
|
19
|
+
const adapter = await getAdapterForExchange("hyperliquid");
|
|
20
|
+
if (!(adapter instanceof HyperliquidAdapter)) {
|
|
21
|
+
throw new PerpError("NOT_IMPLEMENTED", "Outcome markets are only available on Hyperliquid.", {
|
|
22
|
+
exchange: "hyperliquid",
|
|
23
|
+
remediation: "Run with -e hyperliquid (or omit -e to use the default).",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const out = new HyperliquidOutcomeAdapter(adapter);
|
|
27
|
+
await out.init();
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
// ── outcome list ─────────────────────────────────────────────────────────
|
|
31
|
+
outcome
|
|
32
|
+
.command("list")
|
|
33
|
+
.alias("markets")
|
|
34
|
+
.description("List active outcome markets")
|
|
35
|
+
.action(async () => {
|
|
36
|
+
const adapter = await getOutcomeAdapter();
|
|
37
|
+
const markets = await adapter.getMarkets();
|
|
38
|
+
if (isJson())
|
|
39
|
+
return printJson(jsonOk(markets));
|
|
40
|
+
if (markets.length === 0) {
|
|
41
|
+
console.log(chalk.gray("\n No active outcome markets.\n"));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
console.log(chalk.white.bold(`\n Outcome Markets (Hyperliquid HIP-4)`));
|
|
45
|
+
const quotes = [...new Set(markets.map((m) => m.quoteToken))].join(" / ");
|
|
46
|
+
console.log(chalk.gray(` Quote: ${quotes} (per outcome) · No leverage · Min order 10 quote tokens\n`));
|
|
47
|
+
for (const m of markets) {
|
|
48
|
+
const expiry = m.expiryMs ? new Date(m.expiryMs).toISOString().replace("T", " ").slice(0, 16) + " UTC" : "—";
|
|
49
|
+
console.log(` ${chalk.cyan(`#${m.outcome}`)} ${chalk.white.bold(m.name)} ${chalk.gray(`(${m.class ?? "?"} · ${m.underlying ?? "?"} · ${m.period ?? "?"})`)}`);
|
|
50
|
+
if (m.targetPrice !== undefined)
|
|
51
|
+
console.log(` target: $${m.targetPrice.toLocaleString()} expiry: ${expiry}`);
|
|
52
|
+
const sideRows = m.sides.map((s) => [
|
|
53
|
+
String(s.side),
|
|
54
|
+
chalk.white.bold(s.name),
|
|
55
|
+
s.mid ? `$${Number(s.mid).toFixed(4)}` : chalk.gray("—"),
|
|
56
|
+
chalk.gray(`assetId=${s.assetId}`),
|
|
57
|
+
]);
|
|
58
|
+
console.log(makeTable(["#", "side", "mid", ""], sideRows));
|
|
59
|
+
console.log("");
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
// ── outcome book <outcome> <side> ────────────────────────────────────────
|
|
63
|
+
outcome
|
|
64
|
+
.command("book <outcome> <side>")
|
|
65
|
+
.description("Show orderbook for one outcome side (e.g., 'outcome book 1 0' or '1 yes')")
|
|
66
|
+
.option("--depth <n>", "Number of levels per side", "10")
|
|
67
|
+
.action(async (outcomeArg, sideArg, opts) => {
|
|
68
|
+
const adapter = await getOutcomeAdapter();
|
|
69
|
+
const { outcome: outcomeId, side } = await resolveOutcomeSide(adapter, outcomeArg, sideArg);
|
|
70
|
+
const book = await adapter.getOrderbook(outcomeId, side);
|
|
71
|
+
const depth = Math.max(1, Number(opts.depth ?? "10"));
|
|
72
|
+
if (isJson())
|
|
73
|
+
return printJson(jsonOk(book));
|
|
74
|
+
printOutcomeBook(book, depth);
|
|
75
|
+
});
|
|
76
|
+
// ── outcome view <outcome> ───────────────────────────────────────────────
|
|
77
|
+
// Symmetric Yes/No book + underlying gap + time to expiry. Single
|
|
78
|
+
// round-trip view for binary markets.
|
|
79
|
+
outcome
|
|
80
|
+
.command("view <outcome>")
|
|
81
|
+
.alias("status")
|
|
82
|
+
.description("Combined view: Yes/No books side-by-side + underlying mark gap + expiry")
|
|
83
|
+
.option("--depth <n>", "Number of levels per side", "10")
|
|
84
|
+
.action(async (outcomeArg, opts) => {
|
|
85
|
+
const adapter = await getOutcomeAdapter();
|
|
86
|
+
const outcomeId = Number(outcomeArg);
|
|
87
|
+
if (!Number.isInteger(outcomeId) || outcomeId < 0) {
|
|
88
|
+
throw new PerpError("INVALID_PARAMS", `Invalid outcome id: ${outcomeArg}`, {});
|
|
89
|
+
}
|
|
90
|
+
const depth = Math.max(1, Number(opts.depth ?? "10"));
|
|
91
|
+
const view = await adapter.getView(outcomeId, depth);
|
|
92
|
+
if (isJson())
|
|
93
|
+
return printJson(jsonOk(view));
|
|
94
|
+
printOutcomeView(view);
|
|
95
|
+
});
|
|
96
|
+
// ── outcome positions ────────────────────────────────────────────────────
|
|
97
|
+
outcome
|
|
98
|
+
.command("positions")
|
|
99
|
+
.description("Show open outcome positions")
|
|
100
|
+
.action(async () => {
|
|
101
|
+
const adapter = await getOutcomeAdapter();
|
|
102
|
+
const positions = await adapter.getPositions();
|
|
103
|
+
if (isJson())
|
|
104
|
+
return printJson(jsonOk(positions));
|
|
105
|
+
if (positions.length === 0) {
|
|
106
|
+
console.log(chalk.gray("\n No open outcome positions.\n"));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const rows = positions.map((p) => [
|
|
110
|
+
chalk.cyan(`#${p.outcome}`),
|
|
111
|
+
chalk.white.bold(p.sideName),
|
|
112
|
+
p.size,
|
|
113
|
+
`$${formatUsd(p.entryNotional)}`,
|
|
114
|
+
p.markPrice ? `$${Number(p.markPrice).toFixed(4)}` : chalk.gray("—"),
|
|
115
|
+
formatPnlCell(p.unrealizedPnl),
|
|
116
|
+
]);
|
|
117
|
+
console.log("\n" + makeTable(["outcome", "side", "size", "entry$", "mark", "uPnL"], rows) + "\n");
|
|
118
|
+
});
|
|
119
|
+
// ── outcome orders ───────────────────────────────────────────────────────
|
|
120
|
+
outcome
|
|
121
|
+
.command("orders")
|
|
122
|
+
.description("Show open outcome orders")
|
|
123
|
+
.action(async () => {
|
|
124
|
+
const adapter = await getOutcomeAdapter();
|
|
125
|
+
const orders = await adapter.getOpenOrders();
|
|
126
|
+
if (isJson()) {
|
|
127
|
+
return printJson(jsonOk(orders.map((o) => ({ ...o, outcome: adapter.describeCoin(String(o.symbol ?? "")) }))));
|
|
128
|
+
}
|
|
129
|
+
if (orders.length === 0) {
|
|
130
|
+
console.log(chalk.gray("\n No open outcome orders.\n"));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// HL adapter normalises open-order sides to "buy"/"sell" (see
|
|
134
|
+
// HyperliquidAdapter.getOpenOrders → ExchangeOrder shape). Earlier
|
|
135
|
+
// versions checked the raw "B"/"A" form, which mislabelled every buy
|
|
136
|
+
// as SELL.
|
|
137
|
+
const rows = orders.map((o) => {
|
|
138
|
+
const side = String(o.side ?? "").toLowerCase();
|
|
139
|
+
const sideLabel = side === "buy" ? chalk.green("BUY") : side === "sell" ? chalk.red("SELL") : chalk.gray(String(o.side ?? ""));
|
|
140
|
+
const coin = String(o.symbol ?? "");
|
|
141
|
+
const d = adapter.describeCoin(coin);
|
|
142
|
+
const strip = (n) => n.replace(/^template:/, "");
|
|
143
|
+
return [
|
|
144
|
+
d !== null ? `#${d.outcome} ${strip(d.name)} · ${strip(d.sideName)} (side ${d.side}, ${coin})` : coin,
|
|
145
|
+
sideLabel,
|
|
146
|
+
String(o.size ?? ""),
|
|
147
|
+
String(o.price ?? ""),
|
|
148
|
+
String(o.orderId ?? ""),
|
|
149
|
+
];
|
|
150
|
+
});
|
|
151
|
+
console.log("\n" + makeTable(["outcome · side", "dir", "size", "price", "oid"], rows) + "\n");
|
|
152
|
+
});
|
|
153
|
+
// ── outcome buy / sell ───────────────────────────────────────────────────
|
|
154
|
+
for (const action of ["buy", "sell"]) {
|
|
155
|
+
const cmd = supportsDryRun(outcome.command(`${action} <outcome> <side> [usd]`))
|
|
156
|
+
.description(`${action === "buy" ? "Buy" : "Sell"} a side of an outcome: [usd] notional in the outcome's quote token (outcomeMeta quoteToken, e.g. USDC), or --size <shares>${action === "sell" ? " / --all (the whole position)" : ""}; supports --dry-run`)
|
|
157
|
+
.option("--limit <px>", "Limit price (default: aggressive Ioc at top of book)")
|
|
158
|
+
.option("--tif <tif>", "Time-in-force: gtc | ioc | alo (default: ioc for market, gtc for limit)")
|
|
159
|
+
.option("--size <shares>", "Number of shares (integer) instead of a notional");
|
|
160
|
+
if (action === "sell")
|
|
161
|
+
cmd.option("--all", "Sell the whole position of this outcome side (shares read from the account)");
|
|
162
|
+
cmd
|
|
163
|
+
.action(async (outcomeArg, sideArg, usdArg, opts, command) => {
|
|
164
|
+
// --dry-run is the root flag (never re-declared here — the root would swallow it).
|
|
165
|
+
const merged = { ...opts, dryRun: isDryRun(command) };
|
|
166
|
+
const given = [usdArg !== undefined, opts.size !== undefined, opts.all === true].filter(Boolean).length;
|
|
167
|
+
if (given !== 1) {
|
|
168
|
+
throw new PerpError("INVALID_PARAMS", `outcome ${action}: give exactly one of <usd>, --size <shares>${action === "sell" ? ", --all" : ""}`, {
|
|
169
|
+
remediation: `e.g. perp outcome ${action} <outcome> <side> 12 | --size 13${action === "sell" ? " | --all" : ""}`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const adapter = await getOutcomeAdapter();
|
|
173
|
+
const { outcome: outcomeId, side } = await resolveOutcomeSide(adapter, outcomeArg, sideArg);
|
|
174
|
+
const usd = usdArg !== undefined ? Number(usdArg) : null;
|
|
175
|
+
if (usd !== null && (!Number.isFinite(usd) || usd <= 0)) {
|
|
176
|
+
throw new PerpError("INVALID_PARAMS", `Invalid notional: ${usdArg}`, {});
|
|
177
|
+
}
|
|
178
|
+
if (opts.size !== undefined && !/^[1-9]\d*$/.test(opts.size)) {
|
|
179
|
+
throw new PerpError("INVALID_PARAMS", `--size must be a positive whole number of shares (got ${opts.size})`, {});
|
|
180
|
+
}
|
|
181
|
+
// Shares held on this side now (spot outcome balance); every path reads it for the remainder check.
|
|
182
|
+
const held = Number((await adapter.getPositions()).find(p => p.outcome === outcomeId && p.side === side)?.size ?? "0");
|
|
183
|
+
if (opts.all === true && !(Math.floor(held) >= 1)) {
|
|
184
|
+
throw new PerpError("POSITION_NOT_FOUND", `No whole share of outcome ${outcomeId} side ${side} to sell (held ${held})`, { remediation: "See: perp outcome positions" });
|
|
185
|
+
}
|
|
186
|
+
// Resolve price + size from notional
|
|
187
|
+
let price = merged.limit;
|
|
188
|
+
const tif = (merged.tif?.toUpperCase() ?? (price ? "GTC" : "IOC"));
|
|
189
|
+
if (!price) {
|
|
190
|
+
const book = await adapter.getOrderbook(outcomeId, side);
|
|
191
|
+
if (action === "buy") {
|
|
192
|
+
const bestAsk = book.asks[0]?.[0];
|
|
193
|
+
if (!bestAsk)
|
|
194
|
+
throw new PerpError("EXCHANGE_ERROR", `No asks available for outcome ${outcomeId} side ${side}`, {});
|
|
195
|
+
// Aggressive Ioc: pay 5% above best ask, capped at $1
|
|
196
|
+
price = Math.min(1, Number(bestAsk) * 1.05).toFixed(5);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
const bestBid = book.bids[0]?.[0];
|
|
200
|
+
if (!bestBid)
|
|
201
|
+
throw new PerpError("EXCHANGE_ERROR", `No bids available for outcome ${outcomeId} side ${side}`, {});
|
|
202
|
+
// Aggressive Ioc: sell 5% below best bid, floored at $0
|
|
203
|
+
price = Math.max(0.0001, Number(bestBid) * 0.95).toFixed(5);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Outcome sizes are integer shares.
|
|
207
|
+
const size = opts.all === true ? String(Math.floor(held)) : opts.size !== undefined ? opts.size : String(Math.floor(usd / Number(price)));
|
|
208
|
+
if (action === "sell" && Number(size) > held) {
|
|
209
|
+
throw new PerpError("SIZE_TOO_LARGE", `outcome sell ${size} shares exceeds the ${held} held on outcome ${outcomeId} side ${side}; nothing was sent`, { remediation: "Use --all, or a smaller --size" });
|
|
210
|
+
}
|
|
211
|
+
const tifMap = { GTC: "Gtc", IOC: "Ioc", ALO: "Alo" };
|
|
212
|
+
const tifNorm = tifMap[tif];
|
|
213
|
+
const orderInfo = {
|
|
214
|
+
outcome: outcomeId,
|
|
215
|
+
side,
|
|
216
|
+
isBuy: action === "buy",
|
|
217
|
+
price,
|
|
218
|
+
size,
|
|
219
|
+
tif: tifNorm,
|
|
220
|
+
notional: (Number(price) * Number(size)).toFixed(4),
|
|
221
|
+
};
|
|
222
|
+
// Validate min-notional client-side so --dry-run surfaces it too
|
|
223
|
+
// (placeOrder enforces but is bypassed by --dry-run early return).
|
|
224
|
+
const quote = adapter.quoteTokenOf(outcomeId);
|
|
225
|
+
// A position worth less than the 10-quote minimum can never be sold (every order must be >= 10):
|
|
226
|
+
// warn before an order that would leave such a remainder.
|
|
227
|
+
const after = action === "buy" ? held + Number(size) : held - Number(size);
|
|
228
|
+
if (after > 0 && after * Number(price) < 10) {
|
|
229
|
+
const msg = `this ${action} leaves ${after} share(s) ≈ ${(after * Number(price)).toFixed(2)} ${quote} on outcome ${outcomeId} side ${side} — below the 10 ${quote} minimum order value, so that remainder cannot be sold on its own${action === "sell" ? " (use --all to sell everything)" : ""}`;
|
|
230
|
+
process.stderr.write(chalk.yellow(` WARNING: ${msg}\n`));
|
|
231
|
+
}
|
|
232
|
+
if (Number(orderInfo.notional) < 10) {
|
|
233
|
+
throw new PerpError("INVALID_PARAMS", `Outcome order value must be at least 10 ${quote} (got price=${price} * size=${size} = ${orderInfo.notional})`, { exchange: "hyperliquid", remediation: "Increase --usd or pass --limit so price*size >= 10" });
|
|
234
|
+
}
|
|
235
|
+
if (merged.dryRun) {
|
|
236
|
+
if (isJson())
|
|
237
|
+
return printJson(jsonOk({ dryRun: true, sent: false, order: orderInfo }));
|
|
238
|
+
console.log(chalk.yellow(`\n [dry-run] ${action.toUpperCase()} outcome=${outcomeId} side=${side} ${size}@${price} (notional ~${orderInfo.notional} ${quote}, tif=${tifNorm})\n`));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const result = await adapter.placeOrder({
|
|
242
|
+
outcome: outcomeId,
|
|
243
|
+
side,
|
|
244
|
+
isBuy: action === "buy",
|
|
245
|
+
price,
|
|
246
|
+
size,
|
|
247
|
+
tif: tifNorm,
|
|
248
|
+
});
|
|
249
|
+
if (isJson())
|
|
250
|
+
return printJson(jsonOk({ order: orderInfo, response: result }));
|
|
251
|
+
console.log(chalk.green(`\n ${action.toUpperCase()} placed: outcome=${outcomeId} side=${side} ${size}@${price}\n`));
|
|
252
|
+
console.log(` ${chalk.gray("response:")} ${JSON.stringify(result)}\n`);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
// ── outcome cancel <outcome> <side> <oid> ────────────────────────────────
|
|
256
|
+
outcome
|
|
257
|
+
.command("cancel <outcome> <side> <oid>")
|
|
258
|
+
.description("Cancel an open outcome order")
|
|
259
|
+
.action(async (outcomeArg, sideArg, oidArg) => {
|
|
260
|
+
const adapter = await getOutcomeAdapter();
|
|
261
|
+
const { outcome: outcomeId, side } = await resolveOutcomeSide(adapter, outcomeArg, sideArg);
|
|
262
|
+
const oid = Number(oidArg);
|
|
263
|
+
if (!Number.isInteger(oid) || oid <= 0) {
|
|
264
|
+
throw new PerpError("INVALID_PARAMS", `Invalid oid: ${oidArg}`, {});
|
|
265
|
+
}
|
|
266
|
+
const result = await adapter.cancelOrder(outcomeId, side, oid);
|
|
267
|
+
if (isJson())
|
|
268
|
+
return printJson(jsonOk(result));
|
|
269
|
+
console.log(chalk.green(`\n Cancelled order ${oid} on outcome=${outcomeId} side=${side}\n`));
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
273
|
+
async function resolveOutcomeSide(adapter, outcomeArg, sideArg) {
|
|
274
|
+
const outcomeId = Number(outcomeArg);
|
|
275
|
+
if (!Number.isInteger(outcomeId) || outcomeId < 0) {
|
|
276
|
+
throw new PerpError("INVALID_PARAMS", `Invalid outcome id: ${outcomeArg}`, {});
|
|
277
|
+
}
|
|
278
|
+
// side may be: integer (0/1), name (Yes/No), or `#<enc>`/`+<enc>` where
|
|
279
|
+
// enc encodes BOTH outcome and side. The encoded form is verified against
|
|
280
|
+
// outcomeArg — silently picking the side digit while ignoring the outcome
|
|
281
|
+
// would route the trade to the wrong market.
|
|
282
|
+
let side;
|
|
283
|
+
if (/^\d+$/.test(sideArg)) {
|
|
284
|
+
side = Number(sideArg);
|
|
285
|
+
}
|
|
286
|
+
else if (/^[#+]\d+$/.test(sideArg)) {
|
|
287
|
+
const enc = Number(sideArg.slice(1));
|
|
288
|
+
const encodedOutcome = Math.floor(enc / 10);
|
|
289
|
+
const encodedSide = enc % 10;
|
|
290
|
+
if (encodedOutcome !== outcomeId) {
|
|
291
|
+
throw new PerpError("INVALID_PARAMS", `Side reference '${sideArg}' encodes outcome=${encodedOutcome} but the outcome arg is ${outcomeId}. ` +
|
|
292
|
+
`Either use plain side (0/1/Yes/No) or pass outcome=${encodedOutcome}.`, {});
|
|
293
|
+
}
|
|
294
|
+
side = encodedSide;
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
const markets = await adapter.getMarkets();
|
|
298
|
+
const market = markets.find((m) => m.outcome === outcomeId);
|
|
299
|
+
const match = market?.sides.find((s) => s.name.toLowerCase() === sideArg.toLowerCase());
|
|
300
|
+
side = match?.side;
|
|
301
|
+
}
|
|
302
|
+
if (side === undefined || !Number.isInteger(side)) {
|
|
303
|
+
throw new PerpError("INVALID_PARAMS", `Invalid side: ${sideArg} (use 0/1, Yes/No, or #<enc>)`, {});
|
|
304
|
+
}
|
|
305
|
+
return { outcome: outcomeId, side };
|
|
306
|
+
}
|
|
307
|
+
function printOutcomeView(view) {
|
|
308
|
+
console.log(chalk.white.bold(`\n Outcome #${view.outcome} — ${view.name}`));
|
|
309
|
+
console.log(chalk.gray(` ${view.description}`));
|
|
310
|
+
// Header line: target / underlying mark / gap / expiry
|
|
311
|
+
if (view.underlying) {
|
|
312
|
+
const u = view.underlying;
|
|
313
|
+
const target = u.targetPrice !== undefined ? `$${u.targetPrice.toLocaleString()}` : "—";
|
|
314
|
+
const mark = u.markPrice !== undefined ? `$${Number(u.markPrice).toLocaleString()}` : chalk.gray("—");
|
|
315
|
+
const gapStr = u.gap !== undefined && u.gapPct !== undefined
|
|
316
|
+
? (u.gap >= 0
|
|
317
|
+
? chalk.green(`+$${Math.abs(u.gap).toFixed(2)} (+${u.gapPct.toFixed(2)}%)`)
|
|
318
|
+
: chalk.red(`-$${Math.abs(u.gap).toFixed(2)} (${u.gapPct.toFixed(2)}%)`))
|
|
319
|
+
: chalk.gray("—");
|
|
320
|
+
const itm = u.inTheMoney === "yes" ? chalk.green("Yes ITM")
|
|
321
|
+
: u.inTheMoney === "no" ? chalk.red("No ITM")
|
|
322
|
+
: chalk.gray("—");
|
|
323
|
+
console.log(` ${u.symbol} target ${target} current ${mark} gap ${gapStr} ${itm}`);
|
|
324
|
+
}
|
|
325
|
+
if (view.expiryMs !== undefined) {
|
|
326
|
+
const expiryStr = new Date(view.expiryMs).toISOString().replace("T", " ").slice(0, 16) + " UTC";
|
|
327
|
+
const ttx = view.msToExpiry !== undefined ? formatDuration(view.msToExpiry) : "—";
|
|
328
|
+
console.log(` Expires: ${expiryStr} (${ttx})`);
|
|
329
|
+
}
|
|
330
|
+
if (view.midSum !== undefined) {
|
|
331
|
+
const sumColor = Math.abs(view.midSum - 1) < 0.01 ? chalk.gray : chalk.yellow;
|
|
332
|
+
console.log(` Implied probabilities (sum ${sumColor(view.midSum.toFixed(4))}):`);
|
|
333
|
+
for (const s of view.sides) {
|
|
334
|
+
const p = s.impliedProb !== undefined ? `${(s.impliedProb * 100).toFixed(1)}%` : "—";
|
|
335
|
+
console.log(` ${s.name.padEnd(6)} ${p}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// One table per side. Stacked layout — readable on any terminal width
|
|
339
|
+
// and avoids the alignment quirks that come from padding ANSI strings.
|
|
340
|
+
console.log("");
|
|
341
|
+
for (const s of view.sides) {
|
|
342
|
+
console.log(` ${chalk.white.bold(s.name)} book`);
|
|
343
|
+
const maxLevels = Math.max(s.bids.length, s.asks.length);
|
|
344
|
+
const rows = [];
|
|
345
|
+
for (let i = 0; i < maxLevels; i++) {
|
|
346
|
+
const b = s.bids[i];
|
|
347
|
+
const a = s.asks[i];
|
|
348
|
+
rows.push([
|
|
349
|
+
b ? chalk.green(b[0]) : "",
|
|
350
|
+
b ? b[1] : "",
|
|
351
|
+
a ? chalk.red(a[0]) : "",
|
|
352
|
+
a ? a[1] : "",
|
|
353
|
+
]);
|
|
354
|
+
}
|
|
355
|
+
console.log(makeTable(["bid", "size", "ask", "size"], rows));
|
|
356
|
+
console.log("");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function formatDuration(ms) {
|
|
360
|
+
if (ms < 0)
|
|
361
|
+
return "expired";
|
|
362
|
+
const s = Math.floor(ms / 1000);
|
|
363
|
+
const h = Math.floor(s / 3600);
|
|
364
|
+
const m = Math.floor((s % 3600) / 60);
|
|
365
|
+
if (h > 24)
|
|
366
|
+
return `${Math.floor(h / 24)}d ${h % 24}h ${m}m left`;
|
|
367
|
+
if (h > 0)
|
|
368
|
+
return `${h}h ${m}m left`;
|
|
369
|
+
if (m > 0)
|
|
370
|
+
return `${m}m ${s % 60}s left`;
|
|
371
|
+
return `${s}s left`;
|
|
372
|
+
}
|
|
373
|
+
function printOutcomeBook(book, depth) {
|
|
374
|
+
console.log(chalk.white.bold(`\n outcome=${book.outcome} side=${book.side}`));
|
|
375
|
+
const bids = book.bids.slice(0, depth);
|
|
376
|
+
const asks = book.asks.slice(0, depth);
|
|
377
|
+
const rows = [];
|
|
378
|
+
const max = Math.max(bids.length, asks.length);
|
|
379
|
+
for (let i = 0; i < max; i++) {
|
|
380
|
+
const b = bids[i];
|
|
381
|
+
const a = asks[i];
|
|
382
|
+
rows.push([
|
|
383
|
+
b ? `${chalk.green(b[0])} (${b[1]})` : "",
|
|
384
|
+
a ? `${chalk.red(a[0])} (${a[1]})` : "",
|
|
385
|
+
]);
|
|
386
|
+
}
|
|
387
|
+
console.log("\n" + makeTable(["bid (size)", "ask (size)"], rows) + "\n");
|
|
388
|
+
}
|
|
389
|
+
function formatPnlCell(pnl) {
|
|
390
|
+
if (pnl === undefined)
|
|
391
|
+
return chalk.gray("—");
|
|
392
|
+
const n = Number(pnl);
|
|
393
|
+
return n >= 0
|
|
394
|
+
? chalk.green(`+$${formatUsd(String(n.toFixed(4)))}`)
|
|
395
|
+
: chalk.red(`-$${formatUsd(String(Math.abs(n).toFixed(4)))}`);
|
|
396
|
+
}
|
package/dist/commands/plan.js
CHANGED
|
@@ -1,7 +1,30 @@
|
|
|
1
|
+
import { isDryRun, supportsDryRun } from "../cli-flags.js";
|
|
1
2
|
import { readFileSync } from "fs";
|
|
2
3
|
import { printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
|
|
3
4
|
import { validatePlan, executePlan } from "../plan-executor.js";
|
|
5
|
+
import { PerpError, extractErrorMessage } from "../errors.js";
|
|
4
6
|
import chalk from "chalk";
|
|
7
|
+
/** Read and JSON-parse a plan file (`-` = stdin). A missing / unreadable / malformed file is an input error. */
|
|
8
|
+
function readPlanFile(file) {
|
|
9
|
+
const source = file === "-" ? "stdin" : `'${file}'`;
|
|
10
|
+
let raw;
|
|
11
|
+
try {
|
|
12
|
+
raw = file === "-" ? readFileSync(0, "utf-8") : readFileSync(file, "utf-8");
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
throw new PerpError("INVALID_PARAMS", `Cannot read plan file ${source}: ${extractErrorMessage(err)}`, {
|
|
16
|
+
remediation: "Check the path (or pass - to read the plan from stdin). Generate a template with: perp strategy plan example",
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
throw new PerpError("INVALID_PARAMS", `Plan file ${source} is not valid JSON: ${extractErrorMessage(err)}`, {
|
|
24
|
+
remediation: "Fix the JSON. Generate a template with: perp strategy plan example",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
5
28
|
export function registerStrategyPlanCommands(parent, getAdapter, isJson) {
|
|
6
29
|
const plan = parent.command("plan").description("Composite execution plans (multi-step atomic operations)");
|
|
7
30
|
// ── plan validate ──
|
|
@@ -10,8 +33,7 @@ export function registerStrategyPlanCommands(parent, getAdapter, isJson) {
|
|
|
10
33
|
.description("Validate a JSON execution plan without executing")
|
|
11
34
|
.action(async (file) => {
|
|
12
35
|
await withJsonErrors(isJson(), async () => {
|
|
13
|
-
const
|
|
14
|
-
const planData = JSON.parse(raw);
|
|
36
|
+
const planData = readPlanFile(file);
|
|
15
37
|
const result = validatePlan(planData);
|
|
16
38
|
if (isJson())
|
|
17
39
|
return printJson(jsonOk(result));
|
|
@@ -28,17 +50,16 @@ export function registerStrategyPlanCommands(parent, getAdapter, isJson) {
|
|
|
28
50
|
});
|
|
29
51
|
});
|
|
30
52
|
// ── plan execute ──
|
|
31
|
-
plan
|
|
32
|
-
.
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
.action(async (file, opts) => {
|
|
53
|
+
supportsDryRun(plan.command("execute <file>"))
|
|
54
|
+
.description("Execute a JSON plan (use - for stdin; supports --dry-run)")
|
|
55
|
+
.action(async (file, _opts, cmd) => {
|
|
56
|
+
const dryRun = isDryRun(cmd);
|
|
36
57
|
await withJsonErrors(isJson(), async () => {
|
|
37
|
-
const
|
|
38
|
-
const planData = JSON.parse(raw);
|
|
58
|
+
const planData = readPlanFile(file);
|
|
39
59
|
// Validate first
|
|
40
60
|
const validation = validatePlan(planData);
|
|
41
61
|
if (!validation.valid) {
|
|
62
|
+
process.exitCode = 1;
|
|
42
63
|
if (isJson())
|
|
43
64
|
return printJson(jsonError("INVALID_PARAMS", `Plan validation failed: ${validation.errors.join("; ")}`));
|
|
44
65
|
console.error(chalk.red(`\n Plan validation failed:`));
|
|
@@ -49,14 +70,29 @@ export function registerStrategyPlanCommands(parent, getAdapter, isJson) {
|
|
|
49
70
|
const adapter = await getAdapter();
|
|
50
71
|
const log = isJson() ? () => { } : (msg) => console.log(chalk.gray(` ${msg}`));
|
|
51
72
|
if (!isJson()) {
|
|
52
|
-
console.log(chalk.cyan.bold(`\n Executing plan (${planData.steps.length} steps)${
|
|
73
|
+
console.log(chalk.cyan.bold(`\n Executing plan (${planData.steps.length} steps)${dryRun ? " [DRY RUN]" : ""}...\n`));
|
|
53
74
|
}
|
|
54
75
|
const result = await executePlan(adapter, planData, {
|
|
55
|
-
dryRun
|
|
76
|
+
dryRun,
|
|
56
77
|
log,
|
|
57
78
|
});
|
|
58
|
-
|
|
79
|
+
// Unknown / quarantined / failed outcomes must not look like success to scripts.
|
|
80
|
+
const bad = result.status === "pending" || result.status === "quarantined" || result.status === "failed" || result.status === "partial";
|
|
81
|
+
const badCode = result.status === "pending" ? "ORDER_PENDING"
|
|
82
|
+
: result.status === "quarantined" ? "ORDER_PARTIAL"
|
|
83
|
+
: result.status === "partial" ? "ORDER_PARTIAL"
|
|
84
|
+
: "ORDER_REJECTED";
|
|
85
|
+
const badRemediation = result.status === "pending" || result.status === "quarantined"
|
|
86
|
+
? "An order outcome is unknown or a symbol was quarantined — do NOT re-run the plan. Check positions, then: perp strategy quarantine"
|
|
87
|
+
: "Check the failed step and current positions before re-running";
|
|
88
|
+
if (isJson()) {
|
|
89
|
+
if (bad) {
|
|
90
|
+
printJson(jsonError(badCode, `Plan ended with status "${result.status}"`, { details: { result }, remediation: badRemediation }));
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
59
94
|
return printJson(jsonOk(result));
|
|
95
|
+
}
|
|
60
96
|
// Pretty print results
|
|
61
97
|
for (const step of result.steps) {
|
|
62
98
|
const icon = step.status === "success" ? chalk.green("✓")
|
|
@@ -72,6 +108,10 @@ export function registerStrategyPlanCommands(parent, getAdapter, isJson) {
|
|
|
72
108
|
: result.status === "partial" ? chalk.yellow
|
|
73
109
|
: chalk.red;
|
|
74
110
|
console.log(`\n Status: ${statusColor(result.status)} | ${result.totalDurationMs}ms\n`);
|
|
111
|
+
if (bad) {
|
|
112
|
+
console.log(chalk.red(` ${badRemediation}\n`));
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
}
|
|
75
115
|
});
|
|
76
116
|
});
|
|
77
117
|
// ── plan example ──
|
|
@@ -16,4 +16,5 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { Command } from "commander";
|
|
18
18
|
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
19
|
+
import "../exchanges/venues/index.js";
|
|
19
20
|
export declare function registerPortfolioCommand(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, version: string, getHLAdapterForDex?: (dex: string) => Promise<ExchangeAdapter>): void;
|