perp-cli 0.14.3 → 0.15.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 +152 -13
- 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/pacifica.js +83 -13
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- 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/strategies/apex-strategy.js +3 -2
- package/dist/bot/strategies/engine-mm.js +6 -5
- package/dist/bot/strategies/funding-arb-strategy.js +2 -2
- package/dist/bot/strategies/funding-arb-utils.d.ts +1 -1
- package/dist/bot/strategies/funding-arb-utils.js +5 -5
- 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.js +2 -2
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +1 -1
- 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 +125 -5
- 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 +4 -4
- package/dist/commands/arb-auto.js +21 -17
- package/dist/commands/arb.d.ts +12 -0
- package/dist/commands/arb.js +61 -143
- 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.js +167 -35
- package/dist/commands/options.d.ts +9 -0
- package/dist/commands/options.js +134 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +468 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.js +32 -47
- 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 +63 -58
- 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/dashboard/server.js +1 -1
- package/dist/errors.d.ts +24 -1
- package/dist/errors.js +25 -3
- package/dist/exchanges/arcus/adapter.d.ts +7 -7
- package/dist/exchanges/arcus/adapter.js +12 -12
- 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/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 +4 -4
- package/dist/exchanges/aster.js +31 -13
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +1 -2
- package/dist/exchanges/canonical-symbols.js +26 -2
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +68 -0
- package/dist/exchanges/derive/options.js +360 -0
- package/dist/exchanges/extended/adapter.d.ts +82 -0
- package/dist/exchanges/extended/adapter.js +179 -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 +130 -0
- package/dist/exchanges/extended/public.js +429 -0
- package/dist/exchanges/grvt/adapter.d.ts +86 -0
- package/dist/exchanges/grvt/adapter.js +206 -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-spot.d.ts +1 -1
- package/dist/exchanges/hyperliquid-spot.js +5 -5
- package/dist/exchanges/hyperliquid.d.ts +22 -12
- package/dist/exchanges/hyperliquid.js +80 -34
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +94 -6
- package/dist/exchanges/interface.js +16 -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 +2 -2
- package/dist/exchanges/lighter-public.js +2 -2
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +3 -4
- package/dist/exchanges/lighter-spot.js +6 -7
- package/dist/exchanges/lighter.d.ts +11 -11
- package/dist/exchanges/lighter.js +28 -14
- package/dist/exchanges/ondo/adapter.d.ts +5 -6
- package/dist/exchanges/ondo/adapter.js +16 -14
- 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 +8 -1
- package/dist/exchanges/ondo/public.js +13 -2
- 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/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 +2 -2
- package/dist/exchanges/pacifica-spot.js +3 -3
- 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 +121 -0
- package/dist/exchanges/pacifica-vaults.js +264 -0
- package/dist/exchanges/pacifica.d.ts +32 -5
- package/dist/exchanges/pacifica.js +114 -48
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/qfex/adapter.d.ts +18 -5
- package/dist/exchanges/qfex/adapter.js +32 -12
- 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 +3 -3
- package/dist/exchanges/qfex/public.js +3 -3
- 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 +2 -2
- 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 +148 -0
- package/dist/exchanges/risex/adapter.js +796 -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 +7 -7
- 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 +54 -0
- package/dist/exchanges/txflow/adapter.js +118 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +153 -0
- package/dist/exchanges/txflow/public.d.ts +76 -0
- package/dist/exchanges/txflow/public.js +284 -0
- package/dist/exchanges/unknown-symbol.d.ts +29 -0
- package/dist/exchanges/unknown-symbol.js +51 -0
- package/dist/exchanges/variational/adapter.d.ts +71 -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 +6 -7
- package/dist/exchanges/venue-types.js +2 -2
- package/dist/exchanges/venue.d.ts +51 -8
- package/dist/exchanges/venue.js +54 -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 +10 -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 +7 -5
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +5 -3
- 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 +6 -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 +116 -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 +1 -1
- package/dist/funding/legacy-public.js +9 -4
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +44 -4
- package/dist/funding/rates.js +56 -12
- 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/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +58 -0
- package/dist/mcp-server.js +113 -59
- package/dist/pacifica/client.d.ts +67 -4
- package/dist/pacifica/client.js +170 -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/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 +1 -1
- package/dist/trade-validator.js +2 -2
- package/dist/utils.d.ts +12 -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/index.d.ts +959 -0
- package/dist/ws/index.js +4295 -0
- package/dist/ws/node.d.ts +975 -0
- package/dist/ws/node.js +4366 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +58 -10
- package/skills/perp-cli/SKILL.md +62 -9
- package/skills/perp-cli/references/agent-operations.md +7 -0
- package/skills/perp-cli/references/commands.md +76 -5
- package/skills/perp-cli/references/ws-library.md +492 -0
package/dist/commands/market.js
CHANGED
|
@@ -1,32 +1,121 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tapeSkippedOf } from "../exchanges/interface.js";
|
|
2
|
+
import { makeTable, formatUsd, formatPercent, printJson, jsonOk, jsonError, withJsonErrors, normalizeSymbolArg } from "../utils.js";
|
|
2
3
|
import chalk from "chalk";
|
|
3
4
|
import { PerpError } from "../errors.js";
|
|
4
5
|
import { midDecimal } from "../exchanges/precision.js";
|
|
5
6
|
import { hasPacificaSdk, isDexCapable, klinesNeedAuth } from "../exchanges/capabilities.js";
|
|
6
|
-
import { listVenuesWithRole } from "../exchanges/venue.js";
|
|
7
|
+
import { getVenue, listVenuesWithRole, tryResolveVenueId, unofficialApiNote } from "../exchanges/venue.js";
|
|
7
8
|
import "../exchanges/venues/index.js";
|
|
8
9
|
/** Venues with an adapter (market-data-only venues have none — see `perp arb scan`). */
|
|
9
10
|
const EXCHANGES = listVenuesWithRole("account").map(v => v.id);
|
|
10
11
|
/** null = venue does not publish the value → "-" (never rendered as $0). */
|
|
11
12
|
const usdOrDash = (v) => (v === null ? chalk.gray("-") : `$${formatUsd(v)}`);
|
|
13
|
+
/**
|
|
14
|
+
* Funding cell. On a venue whose funding interval is per market (VenueDefinition.fundingRateBasis
|
|
15
|
+
* "perMarket": Variational, Aster, GRVT) the raw rate is labelled with its interval ("+0.0079%/8h"), since
|
|
16
|
+
* the rows of one table cover different periods.
|
|
17
|
+
*/
|
|
18
|
+
function fundingCell(m, perMarket) {
|
|
19
|
+
if (m.fundingRate == null)
|
|
20
|
+
return chalk.gray("N/A");
|
|
21
|
+
const hours = perMarket ? m.fundingHours ?? null : null;
|
|
22
|
+
return hours === null ? formatPercent(m.fundingRate) : `${formatPercent(m.fundingRate)}${chalk.gray(`/${hours}h`)}`;
|
|
23
|
+
}
|
|
24
|
+
/** True when `venueName` is a registered venue whose funding interval is per market. */
|
|
25
|
+
function perMarketFunding(venueName) {
|
|
26
|
+
const id = tryResolveVenueId(venueName);
|
|
27
|
+
return id !== null && getVenue(id).fundingRateBasis === "perMarket";
|
|
28
|
+
}
|
|
29
|
+
/** Base asset of a market symbol: "BTC-PERP" → "BTC", "xyz:XYZ100" → "XYZ100". */
|
|
30
|
+
const baseAssetOf = (symbol) => symbol.split(":").pop().replace(/-PERP$/i, "");
|
|
31
|
+
/**
|
|
32
|
+
* Open interest is published in BASE-asset units by every venue (ExchangeMarketInfo.openInterest),
|
|
33
|
+
* so it is shown as "<n> <BASE>" — never with a "$". `withUsd` adds the notional at the mark
|
|
34
|
+
* price, labelled as such; null (not published) → "-".
|
|
35
|
+
*/
|
|
36
|
+
function formatOpenInterest(m, withUsd) {
|
|
37
|
+
if (m.openInterest === null)
|
|
38
|
+
return chalk.gray("-");
|
|
39
|
+
const oi = Number(m.openInterest);
|
|
40
|
+
const base = `${Number.isFinite(oi) ? oi.toLocaleString("en-US", { maximumFractionDigits: 4 }) : m.openInterest} ${baseAssetOf(m.symbol)}`;
|
|
41
|
+
const mark = Number(m.markPrice);
|
|
42
|
+
if (!withUsd || !Number.isFinite(oi) || !(mark > 0))
|
|
43
|
+
return base;
|
|
44
|
+
return `${base} ${chalk.gray(`(≈ $${formatUsd(oi * mark)} at mark)`)}`;
|
|
45
|
+
}
|
|
46
|
+
function printRfqQuotes(q, json) {
|
|
47
|
+
if (json)
|
|
48
|
+
return printJson(jsonOk(q));
|
|
49
|
+
console.log(chalk.cyan.bold(`\n RFQ quotes: ${q.ticker} (${q.venue}) — not an order book\n`));
|
|
50
|
+
console.log(` Mark: $${formatUsd(q.markPrice)} Base spread: ${q.baseSpreadBps === null ? chalk.gray("-") : `${Number(q.baseSpreadBps).toFixed(2)} bps`} Quoted at: ${q.updatedAt ?? chalk.gray("-")}`);
|
|
51
|
+
if (q.quotes.length === 0) {
|
|
52
|
+
console.log(chalk.gray("\n No quotes published right now (the market may be closed).\n"));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log(makeTable(["Size", "Bid", "Ask", "Spread"], q.quotes.map(t => {
|
|
56
|
+
const bid = Number(t.bid);
|
|
57
|
+
const ask = Number(t.ask);
|
|
58
|
+
const mid = (bid + ask) / 2;
|
|
59
|
+
return [
|
|
60
|
+
t.notionalUsd === null ? "min order" : `$${formatUsd(t.notionalUsd)}`,
|
|
61
|
+
chalk.green(`$${formatUsd(t.bid)}`),
|
|
62
|
+
chalk.red(`$${formatUsd(t.ask)}`),
|
|
63
|
+
mid > 0 ? `${((ask - bid) / mid * 10_000).toFixed(2)} bps` : chalk.gray("-"),
|
|
64
|
+
];
|
|
65
|
+
})));
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.gray(` ${q.note}\n`));
|
|
68
|
+
}
|
|
69
|
+
function printFundingTerms(t, json) {
|
|
70
|
+
if (json)
|
|
71
|
+
return printJson(jsonOk(t));
|
|
72
|
+
console.log(chalk.cyan.bold(`\n Funding: ${t.ticker} (${t.venue}) — current terms, no history published\n`));
|
|
73
|
+
if (t.swap !== null) {
|
|
74
|
+
const row = (label, r) => r === null
|
|
75
|
+
? [label, chalk.gray("-"), chalk.gray("-"), chalk.gray("-"), chalk.gray("-")]
|
|
76
|
+
: [label, formatPercent(r.intervalLong), formatPercent(r.intervalShort), formatPercent(r.annualizedLong), formatPercent(r.annualizedShort)];
|
|
77
|
+
console.log(makeTable(["Payment", "Long / day", "Short / day", "Long APR", "Short APR"], [row("last applied", t.swap.last), row("next", t.swap.next)]));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log(` Rate kind: ${t.rateKind ?? chalk.gray("-")}`);
|
|
81
|
+
console.log(` Interval: ${t.intervalHours === null ? chalk.gray("-") : `${t.intervalHours}h`}`);
|
|
82
|
+
console.log(` Per interval: ${t.intervalRate === null ? chalk.gray("N/A") : formatPercent(t.intervalRate)}`);
|
|
83
|
+
console.log(` Hourly: ${t.hourlyRate === null ? chalk.gray("N/A") : formatPercent(t.hourlyRate)}`);
|
|
84
|
+
console.log(` APR: ${t.annualizedPct === null ? chalk.gray("N/A") : `${t.annualizedPct.toFixed(2)}%`}`);
|
|
85
|
+
}
|
|
86
|
+
console.log(` Next funding: ${t.nextFundingTime === null ? chalk.gray("-") : new Date(t.nextFundingTime).toLocaleString()}`);
|
|
87
|
+
console.log(chalk.gray(` ${t.note}\n`));
|
|
88
|
+
}
|
|
12
89
|
export function registerMarketCommands(program, getAdapter, isJson, getAdapterForExchange,
|
|
13
90
|
/** Credentialed adapter of the selected venue — for market data the venue serves only to an authenticated account. */
|
|
14
91
|
getAccountAdapter) {
|
|
15
92
|
const market = program.command("market").description("Market data commands");
|
|
93
|
+
// Human output for a venue read through an UNOFFICIAL API carries a footnote (JSON rows carry venueApi).
|
|
94
|
+
market.hook("postAction", () => {
|
|
95
|
+
if (isJson())
|
|
96
|
+
return;
|
|
97
|
+
const raw = program.opts().exchange;
|
|
98
|
+
const id = raw === undefined ? null : tryResolveVenueId(raw);
|
|
99
|
+
if (id === null)
|
|
100
|
+
return;
|
|
101
|
+
const note = unofficialApiNote(getVenue(id));
|
|
102
|
+
if (note !== null)
|
|
103
|
+
console.log(chalk.yellow(` * ${note}\n`));
|
|
104
|
+
});
|
|
16
105
|
market
|
|
17
106
|
.command("list")
|
|
18
107
|
.description("List all available markets")
|
|
19
108
|
.option("--hip3", "Include HIP-3 dex markets (Hyperliquid)")
|
|
20
109
|
.action(async (opts) => {
|
|
21
110
|
const explicitExchange = program.getOptionValueSource?.("exchange") === "cli";
|
|
22
|
-
const HEADERS = ["Symbol", "Mark", "Index", "Funding", "24h Vol", "OI", "Max Lev"];
|
|
23
|
-
const marketRow = (m) => [
|
|
111
|
+
const HEADERS = ["Symbol", "Mark", "Index", "Funding", "24h Vol", "OI (base)", "Max Lev"];
|
|
112
|
+
const marketRow = (m, perMarket = false) => [
|
|
24
113
|
chalk.white.bold(m.symbol),
|
|
25
114
|
`$${formatUsd(m.markPrice)}`,
|
|
26
115
|
usdOrDash(m.indexPrice),
|
|
27
|
-
|
|
116
|
+
fundingCell(m, perMarket),
|
|
28
117
|
usdOrDash(m.volume24h),
|
|
29
|
-
|
|
118
|
+
formatOpenInterest(m, false),
|
|
30
119
|
m.maxLeverage === null ? chalk.gray("-") : String(m.maxLeverage) + "x",
|
|
31
120
|
];
|
|
32
121
|
// Multi-exchange mode
|
|
@@ -63,7 +152,7 @@ getAccountAdapter) {
|
|
|
63
152
|
const isHip3 = ex.startsWith("hip3:");
|
|
64
153
|
const label = isHip3 ? chalk.magenta.bold(` HIP-3: ${ex.slice(5)}`) : chalk.cyan.bold(` ${ex.toUpperCase()}`);
|
|
65
154
|
console.log(`\n${label} ${chalk.gray(`(${markets.length} markets)`)}`);
|
|
66
|
-
console.log(makeTable(HEADERS, markets.map(marketRow)));
|
|
155
|
+
console.log(makeTable(HEADERS, markets.map(m => marketRow(m))));
|
|
67
156
|
}
|
|
68
157
|
for (const [ex, msg] of Object.entries(errors)) {
|
|
69
158
|
console.log(chalk.gray(` ${ex}: ${msg}`));
|
|
@@ -91,10 +180,21 @@ getAccountAdapter) {
|
|
|
91
180
|
}
|
|
92
181
|
return printJson(jsonOk(markets));
|
|
93
182
|
}
|
|
94
|
-
|
|
183
|
+
const perMarket = perMarketFunding(adapter.name);
|
|
184
|
+
// Rows that carry an asset class (Extended: crypto / equity / etf / index / commodity / fx / preIpo) show it.
|
|
185
|
+
const classOf = (m) => {
|
|
186
|
+
const c = m.assetClass;
|
|
187
|
+
return typeof c === "string" ? c : null;
|
|
188
|
+
};
|
|
189
|
+
if (markets.length > 0 && markets.every(m => classOf(m) !== null)) {
|
|
190
|
+
console.log(makeTable([...HEADERS, "Class"], markets.map(m => [...marketRow(m, perMarket), classOf(m)])));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.log(makeTable(HEADERS, markets.map(m => marketRow(m, perMarket))));
|
|
194
|
+
}
|
|
95
195
|
for (const [dex, m] of Object.entries(hip3)) {
|
|
96
196
|
console.log(chalk.magenta.bold(`\n HIP-3: ${dex}`) + chalk.gray(` (${m.length} markets)`));
|
|
97
|
-
console.log(makeTable(HEADERS, m.map(marketRow)));
|
|
197
|
+
console.log(makeTable(HEADERS, m.map(x => marketRow(x))));
|
|
98
198
|
}
|
|
99
199
|
});
|
|
100
200
|
market
|
|
@@ -103,11 +203,11 @@ getAccountAdapter) {
|
|
|
103
203
|
.action(async () => {
|
|
104
204
|
const explicitExchange = program.getOptionValueSource?.("exchange") === "cli";
|
|
105
205
|
const PRICE_HEADERS = ["Symbol", "Mark", "Index", "Funding"];
|
|
106
|
-
const priceRow = (m) => [
|
|
206
|
+
const priceRow = (m, perMarket = false) => [
|
|
107
207
|
chalk.white.bold(m.symbol),
|
|
108
208
|
`$${formatUsd(m.markPrice)}`,
|
|
109
209
|
usdOrDash(m.indexPrice),
|
|
110
|
-
|
|
210
|
+
fundingCell(m, perMarket),
|
|
111
211
|
];
|
|
112
212
|
if (!explicitExchange && getAdapterForExchange) {
|
|
113
213
|
const grouped = {};
|
|
@@ -128,7 +228,7 @@ getAccountAdapter) {
|
|
|
128
228
|
if (markets.length === 0)
|
|
129
229
|
continue;
|
|
130
230
|
console.log(chalk.cyan.bold(`\n ${ex.toUpperCase()}`) + chalk.gray(` (${markets.length} markets)`));
|
|
131
|
-
console.log(makeTable(PRICE_HEADERS, markets.map(priceRow)));
|
|
231
|
+
console.log(makeTable(PRICE_HEADERS, markets.map(m => priceRow(m))));
|
|
132
232
|
}
|
|
133
233
|
for (const [ex, msg] of Object.entries(errors))
|
|
134
234
|
console.log(chalk.gray(` ${ex}: ${msg}`));
|
|
@@ -153,7 +253,7 @@ getAccountAdapter) {
|
|
|
153
253
|
const markets = await adapter.getMarkets();
|
|
154
254
|
if (isJson())
|
|
155
255
|
return printJson(jsonOk(markets));
|
|
156
|
-
console.log(makeTable(PRICE_HEADERS, markets.map(priceRow)));
|
|
256
|
+
console.log(makeTable(PRICE_HEADERS, markets.map(m => priceRow(m, perMarketFunding(adapter.name)))));
|
|
157
257
|
}
|
|
158
258
|
});
|
|
159
259
|
market
|
|
@@ -161,6 +261,17 @@ getAccountAdapter) {
|
|
|
161
261
|
.description("Detailed info for a single market")
|
|
162
262
|
.action(async (symbol) => {
|
|
163
263
|
const adapter = await getAdapter();
|
|
264
|
+
// A venue that serves one market directly (GRVT: one ticker + one margin-rules read instead
|
|
265
|
+
// of the whole fan-out) answers here; SYMBOL_NOT_FOUND comes from its own directory.
|
|
266
|
+
const single = adapter.getMarketInfo;
|
|
267
|
+
if (typeof single === "function") {
|
|
268
|
+
return void await withJsonErrors(isJson(), async () => {
|
|
269
|
+
const one = await single.call(adapter, symbol);
|
|
270
|
+
if (isJson())
|
|
271
|
+
return printJson(jsonOk(one));
|
|
272
|
+
printMarketInfo(adapter.name, one);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
164
275
|
const markets = await adapter.getMarkets();
|
|
165
276
|
const sym = symbol.toUpperCase();
|
|
166
277
|
const m = markets.find((mk) => {
|
|
@@ -169,21 +280,20 @@ getAccountAdapter) {
|
|
|
169
280
|
|| s.split(":").pop() === sym; // HIP-3: "km:US500" matches "US500"
|
|
170
281
|
});
|
|
171
282
|
if (!m) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
283
|
+
// Printed (envelope / Error + Remediation) and exit 1 via withJsonErrors.
|
|
284
|
+
return void await withJsonErrors(isJson(), async () => {
|
|
285
|
+
const delisted = typeof adapter.delistedMessage === "function"
|
|
286
|
+
? adapter.delistedMessage(symbol)
|
|
287
|
+
: null;
|
|
288
|
+
throw new PerpError("SYMBOL_NOT_FOUND", delisted ?? `Market ${normalizeSymbolArg(symbol)} not found on ${adapter.name}`, {
|
|
289
|
+
exchange: adapter.name,
|
|
290
|
+
remediation: `List markets: perp -e ${adapter.name} market list`,
|
|
291
|
+
});
|
|
292
|
+
});
|
|
176
293
|
}
|
|
177
294
|
if (isJson())
|
|
178
295
|
return printJson(jsonOk(m));
|
|
179
|
-
|
|
180
|
-
console.log(` Mark Price: $${formatUsd(m.markPrice)}`);
|
|
181
|
-
console.log(` Index Price: ${usdOrDash(m.indexPrice)}`);
|
|
182
|
-
console.log(` Funding Rate: ${m.fundingRate != null ? formatPercent(m.fundingRate) : chalk.gray("N/A")}`);
|
|
183
|
-
console.log(` 24h Volume: ${usdOrDash(m.volume24h)}`);
|
|
184
|
-
console.log(` Open Interest: ${usdOrDash(m.openInterest)}`);
|
|
185
|
-
console.log(` Max Leverage: ${m.maxLeverage === null ? chalk.gray("-") : `${m.maxLeverage}x`}`);
|
|
186
|
-
console.log();
|
|
296
|
+
printMarketInfo(adapter.name, m);
|
|
187
297
|
});
|
|
188
298
|
market
|
|
189
299
|
.command("book <symbol>")
|
|
@@ -191,7 +301,11 @@ getAccountAdapter) {
|
|
|
191
301
|
.option("-d, --depth <n>", "Number of levels", "10")
|
|
192
302
|
.action(async (symbol, opts) => {
|
|
193
303
|
const adapter = await getAdapter();
|
|
194
|
-
const
|
|
304
|
+
const sym = normalizeSymbolArg(symbol);
|
|
305
|
+
// RFQ venue (Variational): no order book — the maker's indicative quotes, labelled as such.
|
|
306
|
+
if (adapter.getRfqQuotes !== undefined)
|
|
307
|
+
return printRfqQuotes(await adapter.getRfqQuotes(sym), isJson());
|
|
308
|
+
const book = await adapter.getOrderbook(sym);
|
|
195
309
|
if (isJson())
|
|
196
310
|
return printJson(jsonOk(book));
|
|
197
311
|
const depth = parseInt(opts.depth);
|
|
@@ -214,7 +328,7 @@ getAccountAdapter) {
|
|
|
214
328
|
const len = maxCum > 0 ? Math.round((cum / maxCum) * BAR_WIDTH) : 0;
|
|
215
329
|
return "█".repeat(len);
|
|
216
330
|
};
|
|
217
|
-
console.log(chalk.cyan.bold(`\n Orderbook: ${
|
|
331
|
+
console.log(chalk.cyan.bold(`\n Orderbook: ${sym} (${adapter.name})\n`));
|
|
218
332
|
console.log(chalk.gray(" Price Size Cum$ Depth"));
|
|
219
333
|
console.log(chalk.gray(" " + "─".repeat(60)));
|
|
220
334
|
asks.forEach(([p, a], i) => {
|
|
@@ -240,9 +354,12 @@ getAccountAdapter) {
|
|
|
240
354
|
.option("--limit <n>", "Number of recent trades to show", "20")
|
|
241
355
|
.action(async (symbol, opts) => {
|
|
242
356
|
const adapter = await getAdapter();
|
|
243
|
-
const trades = await adapter.getRecentTrades(symbol
|
|
357
|
+
const trades = await adapter.getRecentTrades(normalizeSymbolArg(symbol), parseInt(opts.limit));
|
|
358
|
+
const skipped = tapeSkippedOf(trades);
|
|
244
359
|
if (isJson())
|
|
245
|
-
return printJson(jsonOk(trades));
|
|
360
|
+
return printJson(jsonOk(trades, skipped !== null ? { skippedRows: skipped } : undefined));
|
|
361
|
+
if (skipped !== null)
|
|
362
|
+
console.log(chalk.yellow(` ${skipped.count} tape row(s) skipped: ${skipped.reason}`));
|
|
246
363
|
if (trades.length === 0) {
|
|
247
364
|
console.log(chalk.gray("\n No recent trades.\n"));
|
|
248
365
|
return;
|
|
@@ -261,7 +378,10 @@ getAccountAdapter) {
|
|
|
261
378
|
.option("-l, --limit <n>", "Number of records", "10")
|
|
262
379
|
.action(async (symbol, opts) => {
|
|
263
380
|
const adapter = await getAdapter();
|
|
264
|
-
|
|
381
|
+
// A venue without a funding-history API (Variational): its current funding terms, labelled as such.
|
|
382
|
+
if (adapter.getFundingTerms !== undefined)
|
|
383
|
+
return printFundingTerms(await adapter.getFundingTerms(normalizeSymbolArg(symbol)), isJson());
|
|
384
|
+
const history = await adapter.getFundingHistory(normalizeSymbolArg(symbol), parseInt(opts.limit));
|
|
265
385
|
if (isJson())
|
|
266
386
|
return printJson(jsonOk(history));
|
|
267
387
|
if (history.length === 0) {
|
|
@@ -287,7 +407,7 @@ getAccountAdapter) {
|
|
|
287
407
|
.command("mid <symbol>")
|
|
288
408
|
.description("Get mid price for a single symbol (fast)")
|
|
289
409
|
.action(async (symbol) => {
|
|
290
|
-
const sym = symbol
|
|
410
|
+
const sym = normalizeSymbolArg(symbol);
|
|
291
411
|
// withJsonErrors: every failure (incl. an empty book) is an error envelope AND exit code 1.
|
|
292
412
|
await withJsonErrors(isJson(), async () => {
|
|
293
413
|
const adapter = await getAdapter();
|
|
@@ -337,21 +457,22 @@ getAccountAdapter) {
|
|
|
337
457
|
const now = Date.now();
|
|
338
458
|
const startTime = opts.start ? parseInt(opts.start) : now - 24 * 60 * 60 * 1000;
|
|
339
459
|
const endTime = opts.end ? parseInt(opts.end) : now;
|
|
340
|
-
const klines = await adapter.getKlines(symbol
|
|
460
|
+
const klines = await adapter.getKlines(normalizeSymbolArg(symbol), interval, startTime, endTime);
|
|
341
461
|
if (isJson())
|
|
342
462
|
return printJson(jsonOk(klines));
|
|
343
463
|
if (klines.length === 0) {
|
|
344
464
|
console.log(chalk.gray("\n No kline data.\n"));
|
|
345
465
|
return;
|
|
346
466
|
}
|
|
467
|
+
// ISO (UTC) candle open times — one format for every venue, independent of the host locale.
|
|
347
468
|
const rows = klines.slice(-20).map((k) => [
|
|
348
|
-
new Date(k.time).
|
|
469
|
+
new Date(k.time).toISOString(),
|
|
349
470
|
`$${formatUsd(k.open)}`,
|
|
350
471
|
`$${formatUsd(k.high)}`,
|
|
351
472
|
`$${formatUsd(k.low)}`,
|
|
352
473
|
`$${formatUsd(k.close)}`,
|
|
353
|
-
k.volume,
|
|
354
|
-
String(k.trades),
|
|
474
|
+
k.volume === null ? chalk.gray("-") : k.volume,
|
|
475
|
+
k.trades === null ? chalk.gray("-") : String(k.trades),
|
|
355
476
|
]);
|
|
356
477
|
console.log(makeTable(["Time", "Open", "High", "Low", "Close", "Volume", "Trades"], rows));
|
|
357
478
|
});
|
|
@@ -389,3 +510,14 @@ getAccountAdapter) {
|
|
|
389
510
|
});
|
|
390
511
|
});
|
|
391
512
|
}
|
|
513
|
+
function printMarketInfo(venue, m) {
|
|
514
|
+
console.log(chalk.cyan.bold(`\n ${m.symbol} on ${venue}\n`));
|
|
515
|
+
console.log(` Mark Price: $${formatUsd(m.markPrice)}`);
|
|
516
|
+
console.log(` Index Price: ${usdOrDash(m.indexPrice)}`);
|
|
517
|
+
// Per-market funding interval (GRVT 4h / 8h, Variational, Aster): the rate carries its interval.
|
|
518
|
+
console.log(` Funding Rate: ${fundingCell(m, perMarketFunding(venue))}`);
|
|
519
|
+
console.log(` 24h Volume: ${usdOrDash(m.volume24h)}`);
|
|
520
|
+
console.log(` Open Interest: ${formatOpenInterest(m, true)}`);
|
|
521
|
+
console.log(` Max Leverage: ${m.maxLeverage === null ? chalk.gray("-") : `${m.maxLeverage}x`}`);
|
|
522
|
+
console.log();
|
|
523
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `perp options` — read-only options market data (`-e derive`).
|
|
3
|
+
*
|
|
4
|
+
* Public, keyless reads through the options venue registry (src/exchanges/options-venues.ts);
|
|
5
|
+
* no orders, no account. JSON shapes: src/exchanges/options-interface.ts (`schema: 1`).
|
|
6
|
+
* The venue is the root `-e`; a perp venue there (or no `-e`) is CAPABILITY_UNSUPPORTED.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
export declare function registerOptionsCommands(program: Command, isJson: () => boolean): void;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `perp options` — read-only options market data (`-e derive`).
|
|
3
|
+
*
|
|
4
|
+
* Public, keyless reads through the options venue registry (src/exchanges/options-venues.ts);
|
|
5
|
+
* no orders, no account. JSON shapes: src/exchanges/options-interface.ts (`schema: 1`).
|
|
6
|
+
* The venue is the root `-e`; a perp venue there (or no `-e`) is CAPABILITY_UNSUPPORTED.
|
|
7
|
+
*/
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { networkOf } from "../cli-flags.js";
|
|
10
|
+
import { PerpError } from "../errors.js";
|
|
11
|
+
import { getOptionsVenue, listOptionsVenueIds } from "../exchanges/options-venues.js";
|
|
12
|
+
import { makeTable, printJson, jsonOk } from "../utils.js";
|
|
13
|
+
export function registerOptionsCommands(program, isJson) {
|
|
14
|
+
const options = program
|
|
15
|
+
.command("options")
|
|
16
|
+
.description(`Options market data, read-only (-e ${listOptionsVenueIds().join(" | -e ")}): expiries, chain, greeks, IV`);
|
|
17
|
+
async function venue() {
|
|
18
|
+
if (program.getOptionValueSource("exchange") !== "cli") {
|
|
19
|
+
throw new PerpError("CAPABILITY_UNSUPPORTED", "perp options needs an options venue in -e", {
|
|
20
|
+
remediation: `Use -e ${listOptionsVenueIds()[0]} (e.g. perp -e ${listOptionsVenueIds()[0]} options expiries ETH)`,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return getOptionsVenue(String(program.opts().exchange), networkOf(program));
|
|
24
|
+
}
|
|
25
|
+
// ── options underlyings ──────────────────────────────────────────────────
|
|
26
|
+
options
|
|
27
|
+
.command("underlyings")
|
|
28
|
+
.description("List underlyings that have options on the venue")
|
|
29
|
+
.action(async () => {
|
|
30
|
+
const data = await (await venue()).listUnderlyings();
|
|
31
|
+
if (isJson())
|
|
32
|
+
return printJson(jsonOk(data));
|
|
33
|
+
const rows = data.underlyings.map(u => [chalk.white.bold(u.underlying), u.spotPrice === null ? chalk.gray("—") : fmtPx(u.spotPrice)]);
|
|
34
|
+
console.log(`\n ${chalk.white.bold(`${data.venue} options underlyings`)}\n` + makeTable(["underlying", "spot"], rows) + "\n");
|
|
35
|
+
});
|
|
36
|
+
// ── options expiries <underlying> ────────────────────────────────────────
|
|
37
|
+
options
|
|
38
|
+
.command("expiries <underlying>")
|
|
39
|
+
.description("List active option expiries of an underlying (e.g. ETH)")
|
|
40
|
+
.action(async (underlying) => {
|
|
41
|
+
const data = await (await venue()).listExpiries(underlying);
|
|
42
|
+
if (isJson())
|
|
43
|
+
return printJson(jsonOk(data));
|
|
44
|
+
const rows = data.expiries.map(e => [
|
|
45
|
+
chalk.white.bold(e.expiry),
|
|
46
|
+
new Date(e.expiryMs).toISOString().slice(11, 16) + " UTC",
|
|
47
|
+
String(e.strikes),
|
|
48
|
+
String(e.instruments),
|
|
49
|
+
]);
|
|
50
|
+
console.log(`\n ${chalk.white.bold(`${data.underlying} options (${data.venue})`)}\n` + makeTable(["expiry", "time", "strikes", "instruments"], rows) + "\n");
|
|
51
|
+
});
|
|
52
|
+
// ── options chain <underlying> ───────────────────────────────────────────
|
|
53
|
+
options
|
|
54
|
+
.command("chain <underlying>")
|
|
55
|
+
.description("Option chain for one expiry: calls / puts per strike with bid / ask, mark, IV, greeks, OI")
|
|
56
|
+
.option("--expiry <date>", "Expiry (YYYY-MM-DD, UTC); default: the nearest active expiry")
|
|
57
|
+
.option("--strikes <n>", "Only the N strikes closest to the ATM reference (forward, else index)")
|
|
58
|
+
.action(async (underlying, opts) => {
|
|
59
|
+
const strikes = opts.strikes === undefined ? null : parseStrikes(opts.strikes);
|
|
60
|
+
const chain = await (await venue()).getChain({ underlying, expiry: opts.expiry ?? null, strikesAroundAtm: strikes });
|
|
61
|
+
if (isJson())
|
|
62
|
+
return printJson(jsonOk(chain));
|
|
63
|
+
printChain(chain);
|
|
64
|
+
});
|
|
65
|
+
// ── options quote <instrument> ───────────────────────────────────────────
|
|
66
|
+
options
|
|
67
|
+
.command("quote <instrument>")
|
|
68
|
+
.description("One option: bid / ask (price + size), mark, IV, greeks, forward, index, OI, 24h volume (e.g. ETH-20261225-3500-P)")
|
|
69
|
+
.action(async (instrument) => {
|
|
70
|
+
const q = await (await venue()).getQuote(instrument);
|
|
71
|
+
if (isJson())
|
|
72
|
+
return printJson(jsonOk(q));
|
|
73
|
+
printQuote(q);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function parseStrikes(raw) {
|
|
77
|
+
const n = Number(raw);
|
|
78
|
+
if (!/^\d+$/.test(raw.trim()) || !Number.isInteger(n) || n < 1) {
|
|
79
|
+
throw new PerpError("INVALID_PARAMS", `--strikes must be a positive integer (got "${raw}")`, {
|
|
80
|
+
remediation: "Pass a positive integer, e.g. --strikes 10",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return n;
|
|
84
|
+
}
|
|
85
|
+
// ── human output ──
|
|
86
|
+
const dash = chalk.gray("—");
|
|
87
|
+
function fmtPx(v) {
|
|
88
|
+
return v === null ? dash : String(Number(Number(v).toPrecision(8)));
|
|
89
|
+
}
|
|
90
|
+
function fmtPct(v) {
|
|
91
|
+
return v === null ? dash : `${(Number(v) * 100).toFixed(1)}%`;
|
|
92
|
+
}
|
|
93
|
+
function fmtGreek(v) {
|
|
94
|
+
return v === null || v === undefined ? dash : Number(v).toFixed(3);
|
|
95
|
+
}
|
|
96
|
+
function sideCells(q) {
|
|
97
|
+
if (q === null)
|
|
98
|
+
return [dash, dash, dash, dash, dash];
|
|
99
|
+
return [
|
|
100
|
+
q.bid === null ? dash : fmtPx(q.bid.price),
|
|
101
|
+
q.ask === null ? dash : fmtPx(q.ask.price),
|
|
102
|
+
fmtPx(q.mark),
|
|
103
|
+
fmtPct(q.iv.mark),
|
|
104
|
+
fmtGreek(q.greeks?.delta),
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
function printChain(c) {
|
|
108
|
+
const ref = c.atm === null ? "ATM unknown" : `ATM ${c.atm.strike} (${c.atm.reference} ${fmtPx(c.atm.price)})`;
|
|
109
|
+
console.log(chalk.white.bold(`\n ${c.underlying} options — ${c.expiry} (${c.venue})`));
|
|
110
|
+
console.log(chalk.gray(` index ${fmtPx(c.index)} · forward ${fmtPx(c.forward)} · ${ref} · ${c.rows.length}/${c.strikesListed} strikes\n`));
|
|
111
|
+
const rows = c.rows.map(r => {
|
|
112
|
+
const strike = c.atm !== null && r.strike === c.atm.strike ? chalk.yellow.bold(r.strike) : chalk.white.bold(r.strike);
|
|
113
|
+
return [...sideCells(r.call), strike, ...sideCells(r.put).reverse()];
|
|
114
|
+
});
|
|
115
|
+
console.log(makeTable(["C bid", "C ask", "C mark", "C IV", "C Δ", "strike", "P Δ", "P IV", "P mark", "P ask", "P bid"], rows));
|
|
116
|
+
if (c.missingTickers.length > 0)
|
|
117
|
+
console.log(chalk.yellow(` no ticker: ${c.missingTickers.join(", ")}`));
|
|
118
|
+
console.log("");
|
|
119
|
+
}
|
|
120
|
+
function printQuote(q) {
|
|
121
|
+
const side = (s) => (s === null ? dash : `${fmtPx(s.price)} × ${s.size}`);
|
|
122
|
+
console.log(chalk.white.bold(`\n ${q.instrument}`) + chalk.gray(` (${q.venue} · ${q.kind} · strike ${q.strike} · expiry ${new Date(q.expiryMs).toISOString().replace("T", " ").slice(0, 16)} UTC)`));
|
|
123
|
+
const rows = [
|
|
124
|
+
["bid", side(q.bid), "IV", fmtPct(q.iv.bid)],
|
|
125
|
+
["ask", side(q.ask), "IV", fmtPct(q.iv.ask)],
|
|
126
|
+
["mark", fmtPx(q.mark), "IV", fmtPct(q.iv.mark)],
|
|
127
|
+
["index", fmtPx(q.index), "forward", fmtPx(q.forward)],
|
|
128
|
+
["delta", fmtGreek(q.greeks?.delta), "gamma", q.greeks === null ? dash : q.greeks.gamma],
|
|
129
|
+
["theta", fmtGreek(q.greeks?.theta), "vega", fmtGreek(q.greeks?.vega)],
|
|
130
|
+
["rho", fmtGreek(q.greeks?.rho), "OI", q.openInterest],
|
|
131
|
+
["24h contracts", q.stats24h.contracts, "24h trades", String(q.stats24h.trades)],
|
|
132
|
+
];
|
|
133
|
+
console.log(makeTable(["field", "value", "field", "value"], rows) + "\n");
|
|
134
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import type { ExchangeAdapter } from "../exchanges/interface.js";
|
|
3
|
+
type Getter = (exchange: string) => Promise<ExchangeAdapter>;
|
|
4
|
+
export declare function registerPacificaEarnCommands(program: Command, getAdapterForExchange: Getter, isJson: () => boolean): void;
|
|
5
|
+
export {};
|