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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Spot-Perp Arbitrage Strategy
|
|
3
3
|
*
|
|
4
|
-
* Focused strategy: buy spot (HL or Lighter) + short perp on the
|
|
4
|
+
* Focused strategy: buy spot (HL or Lighter) + short perp on the best of the configured perp exchanges (`exchanges`).
|
|
5
5
|
* No perp-perp logic. History-based entry/exit with short rotation.
|
|
6
6
|
*
|
|
7
7
|
* Strategy layer handles ONLY decisions (opportunity scanning, scoring, close/rotation decisions).
|
|
@@ -12,7 +12,7 @@ import { registerStrategy } from "../strategy-registry.js";
|
|
|
12
12
|
import { getFundingHours } from "../../funding.js";
|
|
13
13
|
import { annualizeHourlyRate } from "../../funding/normalize.js";
|
|
14
14
|
import { pruneExecutionLog } from "../../execution-log.js";
|
|
15
|
-
import { buildAdapterMap, getPerpSymbol, findRate, cappedLeverage, matchSymbol, fetchRates, scoreFunding, botSpotPerpMatches, assertFundingArbLegs, cancelBotSymbols, collectFundingSince } from "./funding-arb-utils.js";
|
|
15
|
+
import { buildAdapterMap, getPerpSymbol, findRate, cappedLeverage, matchSymbol, fetchRates, scoreFunding, assertFundingHistoryPeriods, botSpotPerpMatches, assertFundingArbLegs, cancelBotSymbols, collectFundingSince } from "./funding-arb-utils.js";
|
|
16
16
|
import { SpotPerpExecutor } from "./spot-perp-executor.js";
|
|
17
17
|
import { findSpotQuoteBalance, getBotSpotAdapter, nonQuoteSpotBalances, spotNetworkOf, spotSharesPerpMargin, spotTokenBase } from "../../exchanges/spot-factory.js";
|
|
18
18
|
import { getVenue } from "../../exchanges/venue.js";
|
|
@@ -80,6 +80,8 @@ export class SpotPerpArbStrategy {
|
|
|
80
80
|
catch { /* non-critical */ }
|
|
81
81
|
this._config = ctx.config;
|
|
82
82
|
const p = this.params;
|
|
83
|
+
// Config-time check: a history_periods above a leg venue's cap would fail every tick.
|
|
84
|
+
assertFundingHistoryPeriods(p.history_periods, p.exchanges);
|
|
83
85
|
ctx.state.set("arbRunning", true);
|
|
84
86
|
ctx.state.set("spaPositions", []);
|
|
85
87
|
ctx.state.set("arbPositions", 0);
|
|
@@ -285,7 +287,7 @@ export class SpotPerpArbStrategy {
|
|
|
285
287
|
ctx.state.set("spaPositions", remaining);
|
|
286
288
|
ctx.state.set("arbPositions", remaining.length);
|
|
287
289
|
}
|
|
288
|
-
// ── Rotation check (only to perp venues that match the spot leg
|
|
290
|
+
// ── Rotation check (only to perp venues that match the spot leg) ──
|
|
289
291
|
const perpMarketsCache = new Map();
|
|
290
292
|
const allowedBySpotVenue = new Map();
|
|
291
293
|
const currentPositions = ctx.state.get("spaPositions");
|
|
@@ -375,7 +377,7 @@ export class SpotPerpArbStrategy {
|
|
|
375
377
|
const spotAdapter = await getBotSpotAdapter(spotExName, spotExAdapter);
|
|
376
378
|
if (!spotAdapter)
|
|
377
379
|
continue;
|
|
378
|
-
// Canonical symbol + price guard
|
|
380
|
+
// Canonical symbol + price guard: base → perp venues it may hedge on.
|
|
379
381
|
const allowedByBase = await this.spotPerpVenues(spotExName, await spotAdapter.getSpotMarkets(), adapters, perpMarketsCache, ctx);
|
|
380
382
|
for (const [base, allowed] of allowedByBase) {
|
|
381
383
|
// HL spot: skip tokens not in whitelist
|
|
@@ -26,7 +26,7 @@ export type ExecutionResult = {
|
|
|
26
26
|
/**
|
|
27
27
|
* closed = position fully closed; remainingSize = partially closed, this size is still open (hedged).
|
|
28
28
|
* manual = the outcome is unknown / unverified or a leg is unhedged: the caller must quarantine
|
|
29
|
-
* the position and never retry it automatically
|
|
29
|
+
* the position and never retry it automatically.
|
|
30
30
|
*/
|
|
31
31
|
export type CloseResult = {
|
|
32
32
|
closed: true;
|
|
@@ -65,8 +65,8 @@ export class SpotPerpExecutor {
|
|
|
65
65
|
}
|
|
66
66
|
// Price estimate
|
|
67
67
|
const price = await getPriceEstimate(perpAdapter, perpSymbol, symbol);
|
|
68
|
-
if (price
|
|
69
|
-
return { success: false, error:
|
|
68
|
+
if (price === null)
|
|
69
|
+
return { success: false, error: `no positive mark price for ${perpSymbol} on ${perpExchange}` };
|
|
70
70
|
// Liquidity check
|
|
71
71
|
const liq = await checkSpotPerpLiquidity(spotAdapter, perpAdapter, symbol, perpSymbol, sizeUsd, 0.5, (msg) => log(` ${msg}`));
|
|
72
72
|
if (!liq.viable)
|
|
@@ -140,7 +140,7 @@ export class SpotPerpExecutor {
|
|
|
140
140
|
await this.returnToPerp(spotAdapter, actualTransferred, spotExchange, log);
|
|
141
141
|
return { success: false, error: `pre-trade balance: ${msg}` };
|
|
142
142
|
}
|
|
143
|
-
// Step 1: Buy spot — the hedge is sized to what actually filled
|
|
143
|
+
// Step 1: Buy spot — the hedge is sized to what actually filled
|
|
144
144
|
let fill;
|
|
145
145
|
try {
|
|
146
146
|
fill = await spotMarketFill(spotAdapter, symbol, "buy", matched.size, spotStep);
|
|
@@ -10,7 +10,8 @@ export interface EnrichedSnapshot extends MarketSnapshot {
|
|
|
10
10
|
bids: [string, string][];
|
|
11
11
|
asks: [string, string][];
|
|
12
12
|
};
|
|
13
|
-
|
|
13
|
+
/** Base-asset open interest; null = not read / not published (never "0" as a stand-in). */
|
|
14
|
+
openInterest: string | null;
|
|
14
15
|
}
|
|
15
16
|
export type StrategyAction = {
|
|
16
17
|
type: "place_order";
|
|
@@ -78,7 +78,7 @@ export function BotDashboard({ initialState, onQuit, onPause, subscribe }) {
|
|
|
78
78
|
const askOrders = state.openOrders.filter((o) => o.side.toLowerCase() === "sell");
|
|
79
79
|
const hasMultiExchange = (state.exchangeBalances?.length ?? 0) > 1;
|
|
80
80
|
const isMultiSymbol = state.symbol.toUpperCase() === "ALL";
|
|
81
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", marginBottom: 1, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, color: "cyan", children: [state.strategy, " | ", state.symbol, " | ", state.exchange] }), _jsxs(Text, { color: "gray", children: ["Runtime: ", formatDuration(state.runtime), " | Tick: ", state.tick] })] }), _jsx(Box, { children: _jsx(Text, { bold: true, color: phaseColor(state.phase), children: phaseIndicator(state.phase) }) })] }), isMultiSymbol ? (_jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { children: ["Exchanges: ", _jsx(Text, { bold: true, color: "white", children: state.exchangeBalances?.length ?? 1 })] }), _jsxs(Text, { color: "gray", children: ["Positions: ", _jsx(Text, { bold: true, color: state.positions.length > 0 ? "yellow" : "white", children: state.positions.length })] }), _jsxs(Text, { color: "gray", children: ["Total Equity: ", _jsx(Text, { bold: true, color: "white", children: usd(totalEquity) })] }), _jsxs(Text, { color: "gray", children: ["PnL: ", _jsxs(Text, { bold: true, color: state.totalPnl >= 0 ? "green" : "red", children: ["$", state.totalPnl.toFixed(2)] })] })] })) : (_jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { children: ["Price: ", _jsxs(Text, { bold: true, color: "white", children: ["$", state.price.toFixed(2)] })] }), _jsxs(Text, { color: "gray", children: ["Vol24h: ", _jsx(Text, { color: "white", children: Number.isFinite(state.volatility24h) ? `${state.volatility24h.toFixed(1)}%` : "unknown" })] }), _jsxs(Text, { color: "gray", children: ["FR: ", _jsxs(Text, { color: state.fundingRate >= 0 ? "green" : "red", children: [(state.fundingRate * 100).toFixed(4), "%"] })] }), _jsxs(Text, { color: "gray", children: ["OI: ", _jsx(Text, { color: "white", children: state.openInterest })] })] })), _jsxs(Box, { marginTop: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Account" }), _jsxs(Text, { children: ["Equity: ", _jsx(Text, { bold: true, color: "white", children: usd(state.equity) }), _jsxs(Text, { color: "gray", children: [" (peak: ", usd(state.peakEquity), ")"] })] }), _jsxs(Text, { children: ["Daily P&L: ", _jsxs(Text, { bold: true, color: pnlColor(state.dailyPnl), children: ["$", state.dailyPnl.toFixed(2)] })] })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsxs(Text, { children: ["Drawdown: ", _jsx(Text, { bold: true, color: drawdown === null ? "gray" : drawdown > 0 ? "red" : "green", children: drawdown === null ? "unknown" : `${usd(drawdown)} (${drawdownPct.toFixed(1)}%)` })] }), _jsxs(Text, { children: ["Orders: ", _jsx(Text, { bold: true, color: "white", children: state.ordersPlaced }), _jsx(Text, { color: "gray", children: " | Fills: " }), _jsx(Text, { bold: true, color: "white", children: state.fills }), _jsx(Text, { color: "gray", children: " | Total P&L: " }), _jsxs(Text, { bold: true, color: pnlColor(state.totalPnl), children: ["$", state.totalPnl.toFixed(2)] })] })] })] }), state.exchangeBalances && state.exchangeBalances.length > 1 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Exchange Balances" }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "gray", children: pad("Exchange", 16) }), _jsx(Text, { color: "gray", children: pad("Equity", 14) }), _jsx(Text, { color: "gray", children: "Available" })] }), state.exchangeBalances.map((eb, i) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "white", children: pad(eb.exchange, 16) }), eb.equity === null ? (_jsxs(Text, { color: "red", children: ["unavailable", eb.error ? ` (${eb.error})` : ""] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "white", children: pad(`$${parseFloat(eb.equity).toFixed(2)}`, 14) }), _jsx(Text, { color: "gray", children: eb.available === null ? "unavailable" : `$${parseFloat(eb.available).toFixed(2)}` })] }))] }, i)))] })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Positions" }), positionErrors.map((pe, i) => (_jsxs(Text, { color: "red", children: [" ", pe.exchange, ": unavailable (", pe.error, ")"] }, `err-${i}`))), state.positions.length === 0 ? (positionErrors.length === 0 && _jsx(Text, { color: "gray", children: " No open positions" })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [hasMultiExchange && _jsx(Text, { color: "gray", children: pad("Exchange", 14) }), _jsx(Text, { color: "gray", children: pad("Symbol", 10) }), _jsx(Text, { color: "gray", children: pad("Side", 6) }), _jsx(Text, { color: "gray", children: pad("Size", 12) }), _jsx(Text, { color: "gray", children: pad("Entry", 12) }), _jsx(Text, { color: "gray", children: pad("Mark", 12) }), _jsx(Text, { color: "gray", children: "uPnL" })] }), state.positions.map((p, i) => {
|
|
81
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", marginBottom: 1, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, color: "cyan", children: [state.strategy, " | ", state.symbol, " | ", state.exchange] }), _jsxs(Text, { color: "gray", children: ["Runtime: ", formatDuration(state.runtime), " | Tick: ", state.tick] })] }), _jsx(Box, { children: _jsx(Text, { bold: true, color: phaseColor(state.phase), children: phaseIndicator(state.phase) }) })] }), isMultiSymbol ? (_jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { children: ["Exchanges: ", _jsx(Text, { bold: true, color: "white", children: state.exchangeBalances?.length ?? 1 })] }), _jsxs(Text, { color: "gray", children: ["Positions: ", _jsx(Text, { bold: true, color: state.positions.length > 0 ? "yellow" : "white", children: state.positions.length })] }), _jsxs(Text, { color: "gray", children: ["Total Equity: ", _jsx(Text, { bold: true, color: "white", children: usd(totalEquity) })] }), _jsxs(Text, { color: "gray", children: ["PnL: ", _jsxs(Text, { bold: true, color: state.totalPnl >= 0 ? "green" : "red", children: ["$", state.totalPnl.toFixed(2)] })] })] })) : (_jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { children: ["Price: ", _jsxs(Text, { bold: true, color: "white", children: ["$", state.price.toFixed(2)] })] }), _jsxs(Text, { color: "gray", children: ["Vol24h: ", _jsx(Text, { color: "white", children: Number.isFinite(state.volatility24h) ? `${state.volatility24h.toFixed(1)}%` : "unknown" })] }), _jsxs(Text, { color: "gray", children: ["FR: ", _jsxs(Text, { color: state.fundingRate >= 0 ? "green" : "red", children: [(state.fundingRate * 100).toFixed(4), "%"] })] }), _jsxs(Text, { color: "gray", children: ["OI: ", _jsx(Text, { color: "white", children: state.openInterest ?? "-" })] })] })), _jsxs(Box, { marginTop: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Account" }), _jsxs(Text, { children: ["Equity: ", _jsx(Text, { bold: true, color: "white", children: usd(state.equity) }), _jsxs(Text, { color: "gray", children: [" (peak: ", usd(state.peakEquity), ")"] })] }), _jsxs(Text, { children: ["Daily P&L: ", _jsxs(Text, { bold: true, color: pnlColor(state.dailyPnl), children: ["$", state.dailyPnl.toFixed(2)] })] })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsxs(Text, { children: ["Drawdown: ", _jsx(Text, { bold: true, color: drawdown === null ? "gray" : drawdown > 0 ? "red" : "green", children: drawdown === null ? "unknown" : `${usd(drawdown)} (${drawdownPct.toFixed(1)}%)` })] }), _jsxs(Text, { children: ["Orders: ", _jsx(Text, { bold: true, color: "white", children: state.ordersPlaced }), _jsx(Text, { color: "gray", children: " | Fills: " }), _jsx(Text, { bold: true, color: "white", children: state.fills }), _jsx(Text, { color: "gray", children: " | Total P&L: " }), _jsxs(Text, { bold: true, color: pnlColor(state.totalPnl), children: ["$", state.totalPnl.toFixed(2)] })] })] })] }), state.exchangeBalances && state.exchangeBalances.length > 1 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Exchange Balances" }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "gray", children: pad("Exchange", 16) }), _jsx(Text, { color: "gray", children: pad("Equity", 14) }), _jsx(Text, { color: "gray", children: "Available" })] }), state.exchangeBalances.map((eb, i) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "white", children: pad(eb.exchange, 16) }), eb.equity === null ? (_jsxs(Text, { color: "red", children: ["unavailable", eb.error ? ` (${eb.error})` : ""] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "white", children: pad(`$${parseFloat(eb.equity).toFixed(2)}`, 14) }), _jsx(Text, { color: "gray", children: eb.available === null ? "unavailable" : `$${parseFloat(eb.available).toFixed(2)}` })] }))] }, i)))] })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Positions" }), positionErrors.map((pe, i) => (_jsxs(Text, { color: "red", children: [" ", pe.exchange, ": unavailable (", pe.error, ")"] }, `err-${i}`))), state.positions.length === 0 ? (positionErrors.length === 0 && _jsx(Text, { color: "gray", children: " No open positions" })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [hasMultiExchange && _jsx(Text, { color: "gray", children: pad("Exchange", 14) }), _jsx(Text, { color: "gray", children: pad("Symbol", 10) }), _jsx(Text, { color: "gray", children: pad("Side", 6) }), _jsx(Text, { color: "gray", children: pad("Size", 12) }), _jsx(Text, { color: "gray", children: pad("Entry", 12) }), _jsx(Text, { color: "gray", children: pad("Mark", 12) }), _jsx(Text, { color: "gray", children: "uPnL" })] }), state.positions.map((p, i) => {
|
|
82
82
|
const upnl = parseFloat(p.unrealizedPnl);
|
|
83
83
|
return (_jsxs(Box, { flexDirection: "row", children: [hasMultiExchange && _jsx(Text, { color: "cyan", children: pad(p.exchange ?? "—", 14) }), _jsx(Text, { color: "white", children: pad(p.symbol, 10) }), _jsx(Text, { color: p.side.toLowerCase() === "long" ? "green" : "red", children: pad(p.side, 6) }), _jsx(Text, { color: "white", children: pad(p.size, 12) }), _jsx(Text, { color: "white", children: pad(p.entryPrice, 12) }), _jsx(Text, { color: "white", children: pad(p.markPrice, 12) }), _jsx(Text, { color: isNaN(upnl) ? "gray" : pnlColor(upnl), children: isNaN(upnl) ? "$—" : `$${upnl.toFixed(2)}` })] }, i));
|
|
84
84
|
})] }))] }), openOrdersError !== null ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Open orders unavailable (", openOrdersError, ")"] }) })) : (_jsxs(Box, { marginTop: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { flexDirection: "column", width: "50%", children: [_jsxs(Text, { color: "green", bold: true, children: ["Bids (", bidOrders.length, ")"] }), bidOrders.length === 0 ? (_jsx(Text, { color: "gray", children: " --" })) : (bidOrders.slice(0, 8).map((o, i) => (_jsxs(Text, { color: "green", children: [pad(o.price, 12), " ", pad(o.size, 10), " ", o.type] }, i))))] }), _jsxs(Box, { flexDirection: "column", width: "50%", children: [_jsxs(Text, { color: "red", bold: true, children: ["Asks (", askOrders.length, ")"] }), askOrders.length === 0 ? (_jsx(Text, { color: "gray", children: " --" })) : (askOrders.slice(0, 8).map((o, i) => (_jsxs(Text, { color: "red", children: [pad(o.price, 12), " ", pad(o.size, 10), " ", o.type] }, i))))] })] })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: state.strategyState.fundingTotal !== undefined ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: "cyan", bold: true, children: "Funding & Arb" }), _jsxs(Box, { flexDirection: "row", marginLeft: 2, children: [_jsx(Text, { color: "gray", children: "Funding Income: " }), _jsx(Text, { color: "white", children: String(state.strategyState.fundingTotal) }), _jsx(Text, { color: "gray", children: " Positions: " }), _jsx(Text, { color: "white", children: String(state.strategyState.arbPositions ?? 0) })] }), Array.isArray(state.strategyState.arbPositionDetails) &&
|
package/dist/cache.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { mkdirSync, readFileSync, writeFileSync, renameSync, readdirSync, unlinkSync } from "fs";
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
import { tmpdir } from "os";
|
|
10
|
+
import { randomBytes } from "crypto";
|
|
10
11
|
const CACHE_DIR = join(tmpdir(), "perp-cli-cache");
|
|
11
12
|
// TTL presets
|
|
12
13
|
export const TTL_ACCOUNT = 15_000; // 15s — balance, positions, orders (ticks are 30s; 15s ensures same-tick dedup)
|
|
@@ -41,16 +42,21 @@ export function getCached(key) {
|
|
|
41
42
|
}
|
|
42
43
|
export function setCached(key, data, ttlMs) {
|
|
43
44
|
ensureDir();
|
|
45
|
+
const file = join(CACHE_DIR, `${safeKey(key)}.json`);
|
|
46
|
+
// Unique per writer: concurrent processes writing the same key must not share one tmp file
|
|
47
|
+
// (a rename of another writer's half-written tmp would publish a partial entry).
|
|
48
|
+
const tmp = `${file}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
44
49
|
try {
|
|
45
50
|
const entry = { ts: Date.now(), ttl: ttlMs, data };
|
|
46
|
-
|
|
47
|
-
const file = join(CACHE_DIR, `${safeKey(key)}.json`);
|
|
48
|
-
const tmp = file + ".tmp";
|
|
49
|
-
writeFileSync(tmp, content);
|
|
51
|
+
writeFileSync(tmp, JSON.stringify(entry));
|
|
50
52
|
renameSync(tmp, file);
|
|
51
53
|
}
|
|
52
54
|
catch {
|
|
53
|
-
// cache write failure is non-fatal
|
|
55
|
+
// cache write failure is non-fatal; never leave this writer's tmp file behind
|
|
56
|
+
try {
|
|
57
|
+
unlinkSync(tmp);
|
|
58
|
+
}
|
|
59
|
+
catch { /* not written */ }
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
/**
|
|
@@ -94,7 +100,7 @@ export function invalidateCache(keyPrefix) {
|
|
|
94
100
|
const files = readdirSync(CACHE_DIR);
|
|
95
101
|
for (const f of files) {
|
|
96
102
|
if (!f.endsWith(".json"))
|
|
97
|
-
continue;
|
|
103
|
+
continue; // also skips in-flight "<key>.json.<pid>.<rand>.tmp" files
|
|
98
104
|
if (!prefix || f.startsWith(prefix)) {
|
|
99
105
|
try {
|
|
100
106
|
unlinkSync(join(CACHE_DIR, f));
|
package/dist/cli-program.d.ts
CHANGED
|
@@ -28,8 +28,23 @@ export declare function landingData(program: Command, version: string): {
|
|
|
28
28
|
name: string;
|
|
29
29
|
description: string;
|
|
30
30
|
}[];
|
|
31
|
+
venues: {
|
|
32
|
+
id: string;
|
|
33
|
+
displayName: string;
|
|
34
|
+
roles: string[];
|
|
35
|
+
apiStatus: "official" | "unofficial";
|
|
36
|
+
note: string | null;
|
|
37
|
+
}[];
|
|
31
38
|
};
|
|
32
39
|
export declare function buildProgram(opts: {
|
|
33
40
|
defaultExchange: string;
|
|
34
41
|
version: string;
|
|
35
42
|
}): CliProgram;
|
|
43
|
+
/**
|
|
44
|
+
* The trading-role gate at command resolution: `perp -e <venue> trade <write>`, every
|
|
45
|
+
* `perp trade multi <venue>:…` leg and `perp wallet agent approve|import|revoke|rotate <venue>`
|
|
46
|
+
* (revoke --force / --local-only excepted: local cleanup only) and every `strategy … --exchanges` leg refuse with roleUnsupported() (CAPABILITY_UNSUPPORTED) when the venue has no trading role. Runs
|
|
47
|
+
* before the dry-run guard and before any action, so a dry-run plan can never present a gated
|
|
48
|
+
* venue as tradable. Unknown venue names are left to the command's own UNKNOWN_VENUE handling.
|
|
49
|
+
*/
|
|
50
|
+
export declare function assertCommandVenueRole(actionCommand: Command, exchangeOpt: string): void;
|
package/dist/cli-program.js
CHANGED
|
@@ -6,13 +6,15 @@ import { Command } from "commander";
|
|
|
6
6
|
import { cliCredentialOptsFrom, hasCredentials, resolveCredentials } from "./config.js";
|
|
7
7
|
import { PacificaAdapter } from "./exchanges/pacifica.js";
|
|
8
8
|
import { HyperliquidAdapter } from "./exchanges/hyperliquid.js";
|
|
9
|
-
import { getVenue, listVenues, resolveVenueId, READ_ONLY_CREDENTIALS } from "./exchanges/venue.js";
|
|
9
|
+
import { getVenue, listVenues, resolveVenueId, roleUnsupported, tryResolveVenueId, READ_ONLY_CREDENTIALS } from "./exchanges/venue.js";
|
|
10
10
|
import { registerAdapterNetwork } from "./exchanges/adapter-network.js";
|
|
11
11
|
import "./exchanges/venues/index.js";
|
|
12
12
|
import { PerpError } from "./errors.js";
|
|
13
13
|
import { registerMarketCommands } from "./commands/market.js";
|
|
14
14
|
import { registerAccountCommands } from "./commands/account.js";
|
|
15
15
|
import { registerOutcomeCommands } from "./commands/outcome.js";
|
|
16
|
+
import { registerOptionsCommands } from "./commands/options.js";
|
|
17
|
+
import { registerPacificaEarnCommands } from "./commands/pacifica-earn.js";
|
|
16
18
|
import { registerTradeCommands } from "./commands/trade.js";
|
|
17
19
|
// manage commands now register under `wallet manage ...` via registerWalletCommands.
|
|
18
20
|
// stream commands removed — WS feeds still used by dashboard/event-stream internally
|
|
@@ -86,6 +88,11 @@ export function landingData(program, version) {
|
|
|
86
88
|
return {
|
|
87
89
|
name: program.name(),
|
|
88
90
|
version,
|
|
91
|
+
// Registry facts only (no reads): an "unofficial" venue is read through an undocumented API.
|
|
92
|
+
venues: listVenues().map(v => ({
|
|
93
|
+
id: v.id, displayName: v.displayName, roles: [...v.roles], apiStatus: v.apiStatus.kind,
|
|
94
|
+
note: v.apiStatus.kind === "unofficial" ? v.apiStatus.note : null,
|
|
95
|
+
})),
|
|
89
96
|
usage: `${program.name()} [options] <command> [args] (add --json for one JSON document on stdout)`,
|
|
90
97
|
help: `${program.name()} <command> --help`,
|
|
91
98
|
commands: program.commands
|
|
@@ -104,7 +111,7 @@ export function buildProgram(opts) {
|
|
|
104
111
|
.option("-e, --exchange <exchange>", `Exchange: ${listVenues().map(v => v.id).join(", ")} (default: ${defaultExchange})`, defaultExchange)
|
|
105
112
|
.option("-k, --private-key <key>", "REJECTED — a raw key on argv leaks via ps / shell history. Import it instead: perp wallet set <exchange> (stdin)")
|
|
106
113
|
.option("--json", "Output raw JSON (for piping)")
|
|
107
|
-
.option("--fields <fields>", "Comma-separated fields to include in JSON output (e.g.
|
|
114
|
+
.option("--fields <fields>", "Comma-separated fields to include in JSON output (dot paths allowed, e.g. totals.equity,exchanges)")
|
|
108
115
|
.option("--ndjson", "Output newline-delimited JSON (one object per line for streaming)")
|
|
109
116
|
.option("-w, --wallet <name>", "Use a specific wallet by name (from 'perp wallet list')")
|
|
110
117
|
.option("--ows <name>", "Use an OWS (Open Wallet Standard) wallet by name")
|
|
@@ -131,9 +138,9 @@ export function buildProgram(opts) {
|
|
|
131
138
|
});
|
|
132
139
|
let _adapter = null;
|
|
133
140
|
/**
|
|
134
|
-
* One adapter per `${venueId}:${network}:${wallet}:${hlDex}
|
|
141
|
+
* One adapter per `${venueId}:${network}:${wallet}:${hlDex}`.
|
|
135
142
|
* Every adapter is built by the venue definition's create(ctx) — the single
|
|
136
|
-
* creation path shared with the MCP server
|
|
143
|
+
* creation path shared with the MCP server.
|
|
137
144
|
*/
|
|
138
145
|
const _adapters = new Map();
|
|
139
146
|
function cliCredentialOpts() {
|
|
@@ -147,7 +154,7 @@ export function buildProgram(opts) {
|
|
|
147
154
|
const wallet = opts.ows ? `ows:${opts.ows}` : (opts.wallet ?? "default");
|
|
148
155
|
return `${venue}:${network}:${wallet}:${hlDex ?? "-"}`;
|
|
149
156
|
}
|
|
150
|
-
/** restrictedRegions notice: stderr, once per venue per process (
|
|
157
|
+
/** restrictedRegions notice: stderr, once per venue per process (warn, never block). */
|
|
151
158
|
const _restrictedWarned = new Set();
|
|
152
159
|
function warnRestrictedRegions(def) {
|
|
153
160
|
if (def.restrictedRegions.length === 0 || _restrictedWarned.has(def.id))
|
|
@@ -167,6 +174,10 @@ export function buildProgram(opts) {
|
|
|
167
174
|
remediation: `${def.id} is market-data only — use the funding/market public commands (e.g. perp arb scan)`,
|
|
168
175
|
});
|
|
169
176
|
}
|
|
177
|
+
// A market-data-only venue (TxFlow: unofficial read-only API) never gets a credentialed adapter:
|
|
178
|
+
// every account / trading / funds / bot path refuses here, before any credential or request.
|
|
179
|
+
if (!publicOnly && !def.roles.includes("account"))
|
|
180
|
+
throw roleUnsupported(def, "account");
|
|
170
181
|
const network = currentNetwork();
|
|
171
182
|
const hlDex = def.id === "hyperliquid" ? dex : null;
|
|
172
183
|
const key = publicOnly ? `${def.id}:${network}:public:${hlDex ?? "-"}` : adapterCacheKey(def.id, network, hlDex);
|
|
@@ -246,6 +257,8 @@ export function buildProgram(opts) {
|
|
|
246
257
|
registerMarketCommands(program, getPublicAdapter, isJson, getPublicAdapterForExchange, getAdapter);
|
|
247
258
|
registerAccountCommands(program, getAdapter, isJson, getAdapterForExchange);
|
|
248
259
|
registerOutcomeCommands(program, getAdapterForExchange, isJson);
|
|
260
|
+
registerOptionsCommands(program, isJson);
|
|
261
|
+
registerPacificaEarnCommands(program, getAdapterForExchange, isJson);
|
|
249
262
|
registerTradeCommands(program, getAdapter, isJson, isDryRunFlag, getAdapterForExchange);
|
|
250
263
|
// manage tree wired inside registerWalletCommands below.
|
|
251
264
|
// stream commands removed
|
|
@@ -273,7 +286,15 @@ export function buildProgram(opts) {
|
|
|
273
286
|
// registerFundsCommands above.
|
|
274
287
|
// Background processes & strategies
|
|
275
288
|
registerBackgroundCommands(program, isJson, getAdapterForExchange);
|
|
276
|
-
|
|
289
|
+
// Every strategy command (bots, plans, TWAP / DCA / grid, `strategy run`, `--exchanges` legs,
|
|
290
|
+
// config-file venues) builds its adapters through these: a venue without the trading role
|
|
291
|
+
// (TxFlow, release-gated RISEx) is refused here, before any key is opened — also under --dry-run.
|
|
292
|
+
const strategyVenueGate = (venue) => {
|
|
293
|
+
const def = getVenue(venue);
|
|
294
|
+
if (!def.roles.includes("trading"))
|
|
295
|
+
throw roleUnsupported(def, "automation");
|
|
296
|
+
};
|
|
297
|
+
registerStrategyCommands(program, async () => { strategyVenueGate(getExchange()); return getAdapter(); }, async (exchange) => { strategyVenueGate(exchange); return getAdapterForExchange(exchange); }, isJson);
|
|
277
298
|
// Agent-friendly commands
|
|
278
299
|
registerRiskCommands(program, getAdapterForExchange, isJson);
|
|
279
300
|
registerHistoryCommands(program, isJson, getAdapterForExchange);
|
|
@@ -286,7 +307,12 @@ export function buildProgram(opts) {
|
|
|
286
307
|
registerPortfolioCommand(program, getAdapterForExchange, isJson, version, getHLAdapterForDex);
|
|
287
308
|
registerInitCommand(program);
|
|
288
309
|
registerAlertCommands(program, isJson);
|
|
310
|
+
installNestedHelp(program);
|
|
311
|
+
installMissingSubcommandError(program);
|
|
289
312
|
// Switch shared API URLs if --network testnet is used
|
|
313
|
+
// A venue without the trading role (market-data-only TxFlow, release-gated RISEx) refuses every
|
|
314
|
+
// trading command before anything else — also under --dry-run, whose plans skip the adapter.
|
|
315
|
+
program.hook("preAction", (_root, actionCommand) => assertCommandVenueRole(actionCommand, program.opts().exchange));
|
|
290
316
|
// Validates -n and enforces default-deny --dry-run before any action runs.
|
|
291
317
|
installDryRunGuard(program);
|
|
292
318
|
// Secrets on argv (review L10): -k is rejected outright; --passphrase still works but warns loudly.
|
|
@@ -301,3 +327,97 @@ export function buildProgram(opts) {
|
|
|
301
327
|
program.hook("preAction", () => setQuarantineNetwork(networkOf(program)));
|
|
302
328
|
return { program, getAdapterForExchange, isJson };
|
|
303
329
|
}
|
|
330
|
+
/** `perp trade <leaf>` commands that only read (order status, fills, P&L tracking, spot reads). */
|
|
331
|
+
const TRADE_READ_COMMANDS = new Set(["status", "fills", "pnl-track", "spot-orders", "spot-balance"]);
|
|
332
|
+
/**
|
|
333
|
+
* The trading-role gate at command resolution: `perp -e <venue> trade <write>`, every
|
|
334
|
+
* `perp trade multi <venue>:…` leg and `perp wallet agent approve|import|revoke|rotate <venue>`
|
|
335
|
+
* (revoke --force / --local-only excepted: local cleanup only) and every `strategy … --exchanges` leg refuse with roleUnsupported() (CAPABILITY_UNSUPPORTED) when the venue has no trading role. Runs
|
|
336
|
+
* before the dry-run guard and before any action, so a dry-run plan can never present a gated
|
|
337
|
+
* venue as tradable. Unknown venue names are left to the command's own UNKNOWN_VENUE handling.
|
|
338
|
+
*/
|
|
339
|
+
export function assertCommandVenueRole(actionCommand, exchangeOpt) {
|
|
340
|
+
const path = commandPath(actionCommand).split(" ");
|
|
341
|
+
const leaf = path[path.length - 1];
|
|
342
|
+
let venueArgs;
|
|
343
|
+
let feature;
|
|
344
|
+
if (leaf === "multi" && (path[0] === "trade" || path.length === 1)) {
|
|
345
|
+
venueArgs = actionCommand.args.map(leg => leg.split(":")[0]);
|
|
346
|
+
feature = "trading";
|
|
347
|
+
}
|
|
348
|
+
else if (path[0] === "strategy" && typeof actionCommand.opts().exchanges === "string") {
|
|
349
|
+
// `--exchanges` legs (strategy run / funding-arb / quick-arb / delta-neutral, incl. their defaults):
|
|
350
|
+
// refused before the first leg's adapter is built. The -e venue is gated by the strategy adapter factory.
|
|
351
|
+
venueArgs = String(actionCommand.opts().exchanges).split(",").map(v => v.trim()).filter(v => v !== "");
|
|
352
|
+
feature = "automation";
|
|
353
|
+
}
|
|
354
|
+
else if (path[0] === "trade" && path.length === 2 && !TRADE_READ_COMMANDS.has(leaf)) {
|
|
355
|
+
venueArgs = [exchangeOpt];
|
|
356
|
+
feature = "trading";
|
|
357
|
+
}
|
|
358
|
+
else if (path[0] === "wallet" && path[1] === "agent" && ["approve", "import", "revoke", "rotate"].includes(path[2] ?? "")) {
|
|
359
|
+
// `revoke --force` / `--local-only` only clears local records (nothing signed or sent): allowed.
|
|
360
|
+
const o = actionCommand.opts();
|
|
361
|
+
if (path[2] === "revoke" && (o.force === true || o.localOnly === true))
|
|
362
|
+
return;
|
|
363
|
+
venueArgs = actionCommand.args.slice(0, 1);
|
|
364
|
+
feature = path[2] === "import" ? "agentImport" : `agent ${path[2]}`;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
for (const v of venueArgs) {
|
|
370
|
+
const id = tryResolveVenueId(v);
|
|
371
|
+
if (id !== null && !getVenue(id).roles.includes("trading"))
|
|
372
|
+
throw roleUnsupported(getVenue(id), feature);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/** `perp a b c` path of a command (root name excluded). */
|
|
376
|
+
function commandPath(cmd) {
|
|
377
|
+
const names = [];
|
|
378
|
+
for (let c = cmd; c !== null && c.parent !== null; c = c.parent)
|
|
379
|
+
names.unshift(c.name());
|
|
380
|
+
return names.join(" ");
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* `perp help <group> <leaf> ...` shows the help of the deepest named command. Commander's built-in
|
|
384
|
+
* root help command resolves only its first operand, so `perp help market list` printed the
|
|
385
|
+
* `market` group help. Groups keep their own built-in `help` subcommand (`perp market help list`).
|
|
386
|
+
*/
|
|
387
|
+
function installNestedHelp(program) {
|
|
388
|
+
program.helpCommand(false);
|
|
389
|
+
program
|
|
390
|
+
.command("help [command...]")
|
|
391
|
+
.description("display help for command (any depth: perp help trade limit)")
|
|
392
|
+
.action((names) => {
|
|
393
|
+
let cmd = program;
|
|
394
|
+
for (const name of names) {
|
|
395
|
+
const sub = cmd.commands.find(c => c.name() === name || c.aliases().includes(name));
|
|
396
|
+
if (sub === undefined) {
|
|
397
|
+
const where = cmd === program ? "perp" : `perp ${commandPath(cmd)}`;
|
|
398
|
+
throw new PerpError("INVALID_PARAMS", `Unknown command '${name}' under '${where}'`, {
|
|
399
|
+
remediation: `Available: ${cmd.commands.map(c => c.name()).join(", ")} — see: ${where} --help`,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
cmd = sub;
|
|
403
|
+
}
|
|
404
|
+
cmd.help();
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* A command group run without a subcommand (`perp history`): Commander prints the group's help as
|
|
409
|
+
* an error. In --json mode that reached stdout as a CLI_ERROR whose message was the usage text;
|
|
410
|
+
* it is now one INVALID_PARAMS document naming the subcommands, with remediation (exit 1).
|
|
411
|
+
* Human mode keeps the usage listing on stderr.
|
|
412
|
+
*/
|
|
413
|
+
function installMissingSubcommandError(program) {
|
|
414
|
+
program.on("beforeAllHelp", (ctx) => {
|
|
415
|
+
if (!ctx.error || program.opts().json !== true)
|
|
416
|
+
return;
|
|
417
|
+
const path = `perp ${commandPath(ctx.command)}`.trim();
|
|
418
|
+
const subs = ctx.command.commands.map(c => c.name()).filter(n => n !== "help");
|
|
419
|
+
throw new PerpError("INVALID_PARAMS", `'${path}' needs a subcommand: ${subs.join(", ")}`, {
|
|
420
|
+
remediation: `Run one of them, e.g. ${path} ${subs[0]} — list them with: ${path} --help`,
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
}
|