perp-cli 0.14.1 → 0.14.3

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.
Files changed (66) hide show
  1. package/dist/arb/sizing.js +29 -7
  2. package/dist/bot/conditions.d.ts +5 -1
  3. package/dist/bot/conditions.js +16 -11
  4. package/dist/bot/engine.d.ts +12 -0
  5. package/dist/bot/engine.js +55 -44
  6. package/dist/bot/strategies/claude-agent.js +2 -2
  7. package/dist/bot/strategies/funding-arb-strategy.js +22 -26
  8. package/dist/bot/strategies/funding-arb-utils.d.ts +33 -1
  9. package/dist/bot/strategies/funding-arb-utils.js +82 -19
  10. package/dist/bot/strategies/funding-auto.js +61 -37
  11. package/dist/bot/strategies/grid-strategy.d.ts +2 -0
  12. package/dist/bot/strategies/grid-strategy.js +21 -2
  13. package/dist/bot/strategies/spot-perp-arb-strategy.js +48 -29
  14. package/dist/bot/tui/BotDashboard.d.ts +13 -2
  15. package/dist/bot/tui/BotDashboard.js +8 -3
  16. package/dist/bridge-engine.d.ts +2 -0
  17. package/dist/bridge-engine.js +68 -31
  18. package/dist/commands/account.js +2 -2
  19. package/dist/commands/arb/index.d.ts +20 -0
  20. package/dist/commands/arb/index.js +121 -36
  21. package/dist/commands/arb-auto.d.ts +36 -0
  22. package/dist/commands/arb-auto.js +202 -76
  23. package/dist/commands/arb.js +51 -35
  24. package/dist/commands/bot.d.ts +6 -0
  25. package/dist/commands/bot.js +55 -45
  26. package/dist/commands/funds-onchain.js +6 -5
  27. package/dist/commands/funds.js +18 -5
  28. package/dist/commands/history.js +135 -53
  29. package/dist/commands/market.js +2 -2
  30. package/dist/commands/portfolio.d.ts +5 -0
  31. package/dist/commands/portfolio.js +58 -20
  32. package/dist/commands/rebalance.js +46 -10
  33. package/dist/commands/settings.js +2 -1
  34. package/dist/commands/trade.js +8 -4
  35. package/dist/commands/wallet.js +1 -1
  36. package/dist/cross-chain-margin.d.ts +29 -2
  37. package/dist/cross-chain-margin.js +30 -4
  38. package/dist/dashboard/server.d.ts +45 -8
  39. package/dist/dashboard/server.js +113 -42
  40. package/dist/dashboard/ui.js +47 -28
  41. package/dist/dashboard/ws-feeds.d.ts +25 -3
  42. package/dist/dashboard/ws-feeds.js +219 -82
  43. package/dist/dex-asset-map.d.ts +23 -4
  44. package/dist/dex-asset-map.js +39 -13
  45. package/dist/exchanges/arcus/adapter.js +33 -6
  46. package/dist/exchanges/arcus/public.d.ts +6 -0
  47. package/dist/exchanges/arcus/public.js +12 -4
  48. package/dist/exchanges/aster.d.ts +24 -7
  49. package/dist/exchanges/aster.js +178 -93
  50. package/dist/exchanges/hyperliquid.d.ts +30 -4
  51. package/dist/exchanges/hyperliquid.js +159 -55
  52. package/dist/exchanges/interface.d.ts +3 -2
  53. package/dist/exchanges/interface.js +3 -2
  54. package/dist/exchanges/lighter.d.ts +1 -1
  55. package/dist/exchanges/lighter.js +11 -9
  56. package/dist/exchanges/ondo/funds.d.ts +0 -1
  57. package/dist/exchanges/pacifica.d.ts +7 -0
  58. package/dist/exchanges/pacifica.js +52 -26
  59. package/dist/rebalance.d.ts +20 -1
  60. package/dist/rebalance.js +31 -5
  61. package/dist/strategies/funding-arb.js +38 -7
  62. package/dist/trade-validator.d.ts +1 -1
  63. package/dist/trade-validator.js +7 -2
  64. package/package.json +1 -1
  65. package/skills/perp-cli/SKILL.md +2 -2
  66. package/skills/perp-cli/references/commands.md +5 -3
@@ -4,7 +4,7 @@ import { symbolMatch, logSettledRejections } from "../utils.js";
4
4
  import { logExecution } from "../execution-log.js";
5
5
  import { updateJobState } from "../jobs.js";
6
6
  import { invalidateCache } from "../cache.js";
7
- import { fetchAllBalances, computeRebalancePlan, hasEnoughBalance, marketLeverageOf, requiredArbMargin } from "../rebalance.js";
7
+ import { fetchBalancesWithFailures, computeRebalancePlan, hasEnoughBalance, marketLeverageOf, requiredArbMargin } from "../rebalance.js";
8
8
  import { checkArbLiquidity } from "../liquidity.js";
9
9
  import { computeAnnualSpread } from "../funding.js";
10
10
  import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, fetchLighterFundingRates, } from "../shared-api.js";
@@ -15,6 +15,11 @@ import { isQuarantined, quarantineSymbol } from "../bot/quarantine.js";
15
15
  import { isPositionGone, matchSymbol } from "../bot/strategies/funding-arb-utils.js";
16
16
  /** Quarantine source for `perp strategy funding-arb` (src/bot/quarantine.ts). */
17
17
  const SOURCE = "funding-arb-cli";
18
+ /** The market's published size decimals on `adapter` (null = not published / market not listed). */
19
+ async function sizeDecimalsOf(adapter, symbol) {
20
+ const m = (await adapter.getMarkets()).find((x) => symbolMatch(x.symbol, symbol));
21
+ return m === undefined ? null : m.sizeDecimals;
22
+ }
18
23
  async function fetchAllRates() {
19
24
  // SSOT rule #2: each DEX is independent. allSettled keeps the comparison
20
25
  // alive when one DEX errors out, while rejection reasons get logged to
@@ -147,9 +152,17 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
147
152
  // ── Phase 2: Max drawdown check ──
148
153
  if (params.maxDrawdown && params.autoExecute && positions.length > 0) {
149
154
  try {
150
- const snapshots = await fetchAllBalances(adapters);
155
+ const { snapshots, failed } = await fetchBalancesWithFailures(adapters);
151
156
  const totalPnl = snapshots.reduce((s, e) => s + e.unrealizedPnl, 0);
152
- if (totalPnl < -params.maxDrawdown) {
157
+ // Only a failed venue that holds one of the bot's legs makes the uPnL sum unknown; a venue
158
+ // without credentials (notConfigured) cannot hold one.
159
+ const legVenues = new Set(positions.flatMap(p => [p.longExchange, p.shortExchange]));
160
+ const failedLegs = failed.filter(f => legVenues.has(f.exchange));
161
+ if (failedLegs.length > 0) {
162
+ // A partial uPnL sum is not the bot's drawdown: never close everything on it.
163
+ log(`[ARB] Drawdown check skipped — balance unavailable on ${failedLegs.map(f => `${f.exchange} (${f.error})`).join(", ")}`);
164
+ }
165
+ else if (totalPnl < -params.maxDrawdown) {
153
166
  log(`[ARB] MAX DRAWDOWN hit: uPnL $${totalPnl.toFixed(2)} < -$${params.maxDrawdown}`);
154
167
  log(`[ARB] Closing all ${positions.length} positions...`);
155
168
  const stillOpen = await closeAllPositions(positions, adapters, log);
@@ -166,7 +179,11 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
166
179
  const shouldCheckBalance = params.autoRebalance && (Date.now() - lastRebalanceCheck > 300_000); // every 5 min
167
180
  if (shouldCheckBalance || (params.autoExecute && positions.length < maxPos)) {
168
181
  try {
169
- balanceSnapshots = await fetchAllBalances(adapters);
182
+ const read = await fetchBalancesWithFailures(adapters);
183
+ balanceSnapshots = read.snapshots;
184
+ // A venue missing from the snapshots has no balance for entry checks (no entry there).
185
+ for (const f of read.failed)
186
+ log(`[ARB] ${f.exchange} balance unavailable — no entries / rebalance on it this cycle: ${f.error}`);
170
187
  lastRebalanceCheck = Date.now();
171
188
  if (params.autoRebalance) {
172
189
  const threshold = params.rebalanceThreshold ?? 100;
@@ -229,8 +246,22 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
229
246
  const liq = await checkArbLiquidity(longAdapter, shortAdapter, symbol, requestedUsd, 0.5, log);
230
247
  if (!liq.viable)
231
248
  continue;
232
- // Compute matched size (same for both legs)
233
- let matched = computeMatchedSize(liq.adjustedSizeUsd, highest.markPrice, longEx, shortEx);
249
+ // Compute matched size (same for both legs) at each venue's published size precision —
250
+ // the per-venue defaults (HL 2 decimals) would reject small BTC legs or over-round.
251
+ let longDec;
252
+ let shortDec;
253
+ try {
254
+ [longDec, shortDec] = await Promise.all([sizeDecimalsOf(longAdapter, symbol), sizeDecimalsOf(shortAdapter, symbol)]);
255
+ }
256
+ catch (e) {
257
+ log(`[ARB] Skip ${symbol}: could not read the market size precision (${e instanceof Error ? e.message : String(e)})`);
258
+ continue;
259
+ }
260
+ if (longDec === null || shortDec === null) {
261
+ log(`[ARB] Skip ${symbol}: size precision not published on ${longDec === null ? longEx : shortEx}`);
262
+ continue;
263
+ }
264
+ let matched = computeMatchedSize(liq.adjustedSizeUsd, highest.markPrice, longEx, shortEx, { longSizeDecimals: longDec, shortSizeDecimals: shortDec });
234
265
  if (!matched) {
235
266
  log(`[ARB] Skip ${symbol}: can't compute matched size (min notional or precision issue)`);
236
267
  continue;
@@ -266,7 +297,7 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
266
297
  // The LONG was definitely rejected for lot size (nothing placed): recompute once.
267
298
  const lotSize = res.lotSize;
268
299
  log(`[ARB] Lot size ${lotSize} on ${longEx} — recalculating (nothing was placed)`);
269
- matched = computeMatchedSize(liq.adjustedSizeUsd, highest.markPrice, longEx, shortEx, { lotSize });
300
+ matched = computeMatchedSize(liq.adjustedSizeUsd, highest.markPrice, longEx, shortEx, { lotSize, longSizeDecimals: longDec, shortSizeDecimals: shortDec });
270
301
  if (!matched) {
271
302
  log(`[ARB] Skip ${symbol}: can't meet lot size ${lotSize}`);
272
303
  continue;
@@ -19,7 +19,7 @@ export interface TradeValidation {
19
19
  symbol: string;
20
20
  markPrice: number;
21
21
  fundingRate: number;
22
- maxLeverage: number;
22
+ maxLeverage: number | null;
23
23
  };
24
24
  timestamp: string;
25
25
  }
@@ -69,7 +69,12 @@ export async function validateTrade(adapter, params) {
69
69
  // reject an order sized past a threshold even though the flat value allows it,
70
70
  // so resolve the cap AT THIS ORDER'S NOTIONAL.
71
71
  const effectiveMax = maxLeverageForNotional(market, notional);
72
- const leverage = params.leverage ?? effectiveMax;
72
+ // A venue that publishes no max leverage (Aster's public exchangeInfo): the margin estimate
73
+ // uses 1x — the most conservative requirement — and says so; the venue enforces its own cap.
74
+ if (effectiveMax === null && params.leverage === undefined) {
75
+ warnings.push(`Max leverage for ${sym} is not published by ${adapter.name} — margin estimated at 1x; pass --leverage for a tighter estimate`);
76
+ }
77
+ const leverage = params.leverage ?? effectiveMax ?? 1;
73
78
  if (!Number.isFinite(leverage) || leverage <= 0) {
74
79
  throw new Error(`No usable leverage for ${sym} (resolved=${leverage}); supply params.leverage explicitly.`);
75
80
  }
@@ -197,7 +202,7 @@ export async function validateTrade(adapter, params) {
197
202
  const fee = notional * DEFAULT_TAKER_FEE;
198
203
  const estimatedSlippage = params.type === "limit" ? 0 : notional * 0.001;
199
204
  const valid = checks.every(c => c.passed);
200
- if (leverage > effectiveMax) {
205
+ if (effectiveMax !== null && market.maxLeverage !== null && leverage > effectiveMax) {
201
206
  // Compare against the tier that applies at this notional, not the headline
202
207
  // cap — otherwise the check is tautological whenever leverage defaulted FROM
203
208
  // maxLeverage, and it never fires for the tiered case it exists to catch.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster, Lighter RH, Ondo Perps, Arcus",
5
5
  "bin": {
6
6
  "perp": "dist/index.js",
@@ -6,7 +6,7 @@ allowed-tools: "Bash(perp:*), Bash(npx -y perp-cli:*), Bash(bash scripts/preflig
6
6
  compatibility: "Requires Node.js 20 or newer and the perp-cli npm package (global install or npx). Network access required for exchange APIs and bridge providers. Mutating commands need a wallet key imported into the OWS vault via 'perp wallet set EXCHANGE' (key on stdin or hidden prompt — never argv). Skill is portable across Claude Code, Claude.ai, Cursor, Codex, and Gemini CLI — no platform-specific features."
7
7
  metadata:
8
8
  author: hypurrquant
9
- version: "0.14.1"
9
+ version: "0.14.3"
10
10
  ---
11
11
 
12
12
  # perp-cli
@@ -46,7 +46,7 @@ Also bundled under `scripts/`: `preflight.sh`, `arb-monitor.sh`, `funding-analys
46
46
  ### Step 1: Verify install + version
47
47
 
48
48
  ```bash
49
- perp --version 2>/dev/null || npx -y perp-cli@latest --version # must be >= 0.14.1
49
+ perp --version 2>/dev/null || npx -y perp-cli@latest --version # must be >= 0.14.3
50
50
  ```
51
51
 
52
52
  If it is missing or older, ask the user before installing/upgrading (`npm install -g perp-cli@latest`) — never install on your own. If `perp` is not on PATH, prefix every command with `npx -y perp-cli@latest`.
@@ -37,6 +37,7 @@ perp --json -e <EX> account margin <SYMBOL> # position margin info
37
37
  perp --json -e <EX> account pnl # realized + unrealized + funding
38
38
  perp --json -e <EX> account twap-orders # active TWAP orders
39
39
  perp --json portfolio # cross-exchange unified view
40
+ # funding24h is null (with fundingError) when a venue's funding history is unreadable; totals.fundingIncomplete = true then
40
41
  perp --json portfolio --arb # full dashboard with arb top 5
41
42
  ```
42
43
 
@@ -255,7 +256,7 @@ perp --json arb scan --gaps # cross-exchange pri
255
256
  perp --json arb scan --hip3 # HIP-3 cross-dex spreads
256
257
  perp --json arb scan --history [SYMBOL] # historical funding
257
258
  perp --json arb scan --compare <SYMBOL> # compare a symbol across exchanges
258
- perp --json arb scan --positions # position funding impact
259
+ perp --json arb scan --positions # position funding impact; actual24h null + fundingHistoryErrors when a venue's history is unreadable
259
260
  perp --json arb scan --live --interval 60 # continuous monitoring
260
261
  perp --json arb scan --top 10 --hold-days 7 --bridge-cost 0 --size 100 # cost-aware filtering
261
262
  perp --json arb scan --rates --include-unverified # also show QFEX (unverified funding; display only, never ranked)
@@ -281,6 +282,7 @@ perp --json arb close <SYM> --state-only --yes # drop a stale state
281
282
 
282
283
  # Status / history / config / rebalance
283
284
  perp --json arb status # open arb positions + PnL + funding; check unconfirmedPairs, staleStateEntries
285
+ # spot-perp entries: fundingSource actual | partial | estimate | recorded (+ fundingError) — only "actual" is complete settled funding
284
286
  perp --json arb status --period 7 --symbol BTC # filter funding lookback / symbol
285
287
  perp --json arb history # alias: arb log
286
288
  perp --json arb history --period 30 # past arb trade performance
@@ -424,9 +426,9 @@ perp --json history list # execution audit trail
424
426
  perp --json history positions # position lifetime tracking
425
427
  perp --json history prune # purge old entries
426
428
  perp --json history summary # trading performance summary (replaces former 'stats')
427
- perp --json history pnl # P&L breakdown by exchange
429
+ perp --json history pnl # P&L breakdown by exchange; errors + partial name venues whose reads failed (left out, not 0)
428
430
  perp --json history funding # funding payment aggregation
429
- perp --json history report # full performance report (summary + pnl + funding)
431
+ perp --json history report # full performance report (summary + pnl + funding); errors + partial + venuesRead
430
432
  perp --json history compare # compare strategies / accounts
431
433
  perp --json history snapshot # equity snapshot
432
434
  perp --json history track # PnL tracking helper