perp-cli 0.5.0 → 0.5.2

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 CHANGED
@@ -69,26 +69,48 @@ perp --json portfolio
69
69
  # Market data
70
70
  perp --json -e <EX> market list
71
71
  perp --json -e <EX> market book <SYM>
72
+ perp --json -e <EX> market mid <SYM> # fast mid-price lookup
72
73
  perp --json -e <EX> market funding <SYM>
74
+ perp --json -e <EX> market kline <SYM> 1h # candlestick data
75
+ perp --json -e hl market hip3 # list HIP-3 deployed dexes
73
76
 
74
77
  # Trading
75
- perp --json -e <EX> trade market <SYM> buy <SIZE>
76
- perp --json -e <EX> trade market <SYM> buy <SIZE> --smart # IOC limit at best bid/ask (less slippage)
77
- perp --json -e <EX> trade split <SYM> buy 5000 # orderbook-aware split (large orders)
78
+ perp --json -e <EX> trade buy <SYM> <SIZE> # shortcut for market buy
79
+ perp --json -e <EX> trade sell <SYM> <SIZE> # shortcut for market sell
80
+ perp --json -e <EX> trade market <SYM> buy <SIZE> --smart # IOC limit (less slippage)
81
+ perp --json -e <EX> trade split <SYM> buy 5000 # orderbook-aware split (large orders)
78
82
  perp --json -e <EX> trade close <SYM>
83
+ perp --json -e <EX> trade flatten # close ALL positions on exchange
84
+ perp --json -e <EX> trade reduce <SYM> 50 # reduce position by 50%
85
+ perp --json -e <EX> trade cancel <SYM> # cancel by symbol (or orderId)
86
+ perp --json -e <EX> trade tpsl <SYM> long # set take-profit / stop-loss
79
87
  perp --json -e <EX> trade leverage <SYM> <N>
80
88
 
89
+ # Account
90
+ perp --json -e <EX> account balance
91
+ perp --json -e <EX> account positions
92
+ perp --json -e <EX> account pnl # realized + unrealized + funding
93
+ perp --json -e <EX> account funding # personal funding payment history
94
+ perp --json -e <EX> account settings # per-market leverage & margin mode
95
+
81
96
  # Funding rate arbitrage
82
- perp --json arb scan --min 5 # perp-perp opportunities
83
- perp --json arb scan --mode spot-perp # spot+perp opportunities
84
- perp --json arb exec <SYM> <longEx> <shortEx> <$> --leverage 2 --isolated # execute both legs
85
- perp --json arb spot-exec <SYM> <spotEx> <perpEx> <$> # spot+perp entry
97
+ perp --json arb scan --min 5 # perp-perp opportunities
98
+ perp --json arb scan --mode spot-perp # spot+perp opportunities
99
+ perp --json arb scan --rates # funding rates across all exchanges
100
+ perp --json arb scan --basis # cross-exchange basis opportunities
101
+ perp --json arb scan --gaps # cross-exchange price gaps
102
+ perp --json arb scan --hip3 # HIP-3 cross-dex funding spreads
103
+ perp --json arb scan --live # continuous live monitoring
104
+ perp --json arb exec <SYM> <longEx> <shortEx> <$> # perp-perp dual-leg entry
105
+ perp --json arb exec <SYM> spot:<exch> <perpEx> <$> # spot+perp entry
106
+ perp --json arb config # show arb defaults
107
+ perp --json arb history # past arb trade performance (alias: log)
86
108
 
87
109
  # Funds (deposit, withdraw, transfer)
88
110
  perp --json funds deposit hyperliquid 100
89
111
  perp --json funds withdraw pacifica 50
90
- perp --json funds transfer 100 <ADDRESS> # HL internal transfer
91
- perp --json funds info # all routes & limits
112
+ perp --json funds transfer 100 <ADDRESS> # HL internal transfer
113
+ perp --json funds info # all routes & limits
92
114
 
93
115
  # Risk
94
116
  perp --json risk limits --max-leverage 5
@@ -103,7 +125,9 @@ perp --json bridge quote --from solana --to arbitrum --amount 100
103
125
  perp --json bridge send --from solana --to arbitrum --amount 100
104
126
  ```
105
127
 
106
- Exchange flags: `-e hyperliquid`, `-e pacifica`, `-e lighter` (aliases: `hl`, `pac`, `lt`).
128
+ Exchange flag: `-e hyperliquid` / `-e pacifica` / `-e lighter` (aliases: `hl`, `pac`, `lt`).
129
+
130
+ Global flags: `--json`, `--fields <f>`, `--ndjson`, `--dry-run`, `--dex <name>` (HIP-3), `-w, --wallet <name>`.
107
131
 
108
132
  ## AI Agent Skill
109
133
 
package/dist/cli-spec.js CHANGED
@@ -75,8 +75,8 @@ export function getCliSpec(program) {
75
75
  "Check error.retryable before retrying failed commands",
76
76
  "Use 'perp --json trade check <symbol> <side> <size>' to validate before trading",
77
77
  "Use 'perp --json market mid <symbol>' for fast price lookups",
78
- "Use 'perp --json health' to check exchange connectivity before operations",
79
- "If this CLI is useful, run 'perp settings set referrals true' to support development",
78
+ "Use 'perp agent ping' to check exchange connectivity before operations",
79
+ "If this CLI is useful, run 'perp settings referrals on' to support development",
80
80
  ],
81
81
  };
82
82
  }
@@ -97,7 +97,7 @@ function buildSummary(snapshots) {
97
97
  }
98
98
  function pac(adapter) {
99
99
  if (!(adapter instanceof PacificaAdapter))
100
- throw new Error("This command requires --exchange pacifica");
100
+ throw new Error("Market settings are only available on Pacifica.");
101
101
  return adapter;
102
102
  }
103
103
  export function registerAccountCommands(program, getAdapter, isJson, getAdapterForExchange) {
@@ -622,14 +622,38 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
622
622
  .description("Show per-market account settings (leverage, margin mode)")
623
623
  .action(async () => {
624
624
  const adapter = await getAdapter();
625
- const p = pac(adapter);
626
- const settings = await p.sdk.getAccountSettings(p.publicKey);
627
- if (isJson())
628
- return printJson(jsonOk(settings));
629
- if (!Array.isArray(settings) || settings.length === 0) {
630
- console.log(chalk.gray("\n No market settings configured.\n"));
625
+ // Pacifica: use dedicated SDK endpoint
626
+ if (adapter instanceof PacificaAdapter) {
627
+ const settings = await adapter.sdk.getAccountSettings(adapter.publicKey);
628
+ if (isJson())
629
+ return printJson(jsonOk(settings));
630
+ if (!Array.isArray(settings) || settings.length === 0) {
631
+ console.log(chalk.gray("\n No market settings configured.\n"));
632
+ return;
633
+ }
634
+ const rows = settings.map((s) => [
635
+ chalk.white.bold(s.symbol),
636
+ s.margin_mode,
637
+ `${s.leverage}x`,
638
+ ]);
639
+ console.log(makeTable(["Symbol", "Margin Mode", "Leverage"], rows));
631
640
  return;
632
641
  }
642
+ // HL / Lighter: derive settings from open positions
643
+ const positions = await adapter.getPositions();
644
+ if (positions.length === 0) {
645
+ if (isJson())
646
+ return printJson(jsonOk([]));
647
+ console.log(chalk.gray("\n No open positions — settings shown per active position.\n"));
648
+ return;
649
+ }
650
+ const settings = positions.map(p => ({
651
+ symbol: p.symbol,
652
+ leverage: p.leverage,
653
+ margin_mode: "cross", // HL/Lighter default to cross
654
+ }));
655
+ if (isJson())
656
+ return printJson(jsonOk(settings));
633
657
  const rows = settings.map((s) => [
634
658
  chalk.white.bold(s.symbol),
635
659
  s.margin_mode,
@@ -661,6 +685,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
661
685
  });
662
686
  account
663
687
  .command("funding-history")
688
+ .alias("funding")
664
689
  .description("Personal funding payment history")
665
690
  .action(async () => {
666
691
  const adapter = await getAdapter();
@@ -899,7 +924,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
899
924
  if (getAdapterForExchange) {
900
925
  program
901
926
  .command("portfolio")
902
- .description("Cross-exchange portfolio overview (all exchanges at once)")
927
+ .description("Cross-exchange portfolio overview with risk metrics")
903
928
  .option("--exchange <exchanges>", "Comma-separated exchanges to include (default: all)")
904
929
  .action(async (opts) => {
905
930
  await withJsonErrors(isJson(), async () => {
@@ -914,6 +914,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
914
914
  // ── arb history ──
915
915
  arb
916
916
  .command("history")
917
+ .alias("log")
917
918
  .description("Past arb trade performance and statistics")
918
919
  .option("--period <days>", "Number of days to look back", "30")
919
920
  .action(async (opts) => {
@@ -1177,6 +1178,31 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
1177
1178
  console.log();
1178
1179
  });
1179
1180
  // ── arb rebalance ──
1181
+ arb
1182
+ .command("config")
1183
+ .description("Show arb configuration defaults (use 'arb auto --help' for all options)")
1184
+ .action(async () => {
1185
+ if (isJson()) {
1186
+ return printJson(jsonOk({
1187
+ minSpread: 30, closeSpread: 5, size: 100, holdDays: 7,
1188
+ bridgeCost: 0, interval: 60, cooldown: 30, minMargin: 30,
1189
+ settleStrategy: "block", maxLegs: 5,
1190
+ }));
1191
+ }
1192
+ console.log(chalk.cyan.bold("\n Arb Auto Defaults\n"));
1193
+ console.log(` Min Spread: 30% annual`);
1194
+ console.log(` Close Spread: 5% annual`);
1195
+ console.log(` Size: $100 per leg`);
1196
+ console.log(` Hold Days: 7d (for cost amortization)`);
1197
+ console.log(` Bridge Cost: $0`);
1198
+ console.log(` Check Interval: 60s`);
1199
+ console.log(` Cooldown: 30min (after closing a symbol)`);
1200
+ console.log(` Min Margin: 30%`);
1201
+ console.log(` Settle Strategy: block`);
1202
+ console.log(` Max Legs: 5`);
1203
+ console.log(chalk.gray(`\n Override with 'perp arb auto --min-spread 20 --size 50 ...'\n`));
1204
+ console.log(chalk.gray(` Run 'perp arb auto --help' for all options.\n`));
1205
+ });
1180
1206
  arb
1181
1207
  .command("rebalance")
1182
1208
  .description("Cross-exchange balance rebalancing for arb")
@@ -262,9 +262,12 @@ export async function handleBasisScan(isJson, opts) {
262
262
  console.log(chalk.cyan(" Fetching prices for basis calculation...\n"));
263
263
  const [pacRates, hlRates, ltRates] = await Promise.all([fetchPacificaRates(), fetchHyperliquidRates(), fetchLighterRates()]);
264
264
  const exchangePrices = new Map();
265
+ const filterSymbol = opts.symbol?.toUpperCase();
265
266
  for (const r of [...pacRates, ...hlRates, ...ltRates]) {
266
267
  if (r.markPrice <= 0)
267
268
  continue;
269
+ if (filterSymbol && r.symbol.toUpperCase() !== filterSymbol)
270
+ continue;
268
271
  if (!exchangePrices.has(r.symbol))
269
272
  exchangePrices.set(r.symbol, new Map());
270
273
  exchangePrices.get(r.symbol).set(r.exchange, r.markPrice);
@@ -312,19 +312,33 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
312
312
  });
313
313
  trade
314
314
  .command("cancel <symbolOrOrderId> [orderId]")
315
- .description("Cancel an order by orderId (auto-detects symbol), or by symbol + orderId")
315
+ .description("Cancel order(s) — by orderId, by symbol + orderId, or by symbol name (cancels all orders for that symbol)")
316
316
  .action(async (symbolOrOrderId, orderId) => {
317
317
  const adapter = await getAdapter();
318
- let symbol;
319
- let oid;
320
318
  if (orderId) {
321
319
  // cancel <symbol> <orderId>
322
- symbol = symbolOrOrderId.toUpperCase();
323
- oid = orderId;
320
+ const symbol = symbolOrOrderId.toUpperCase();
321
+ const oid = orderId;
322
+ if (dryRunGuard("cancel", { exchange: adapter.name, symbol, orderId: oid }))
323
+ return;
324
+ try {
325
+ const result = await adapter.cancelOrder(symbol, oid);
326
+ logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "success", dryRun: false, meta: { orderId: oid } });
327
+ if (isJson())
328
+ return printJson(jsonOk(result));
329
+ console.log(chalk.green(`\n Order ${oid} cancelled on ${adapter.name}.\n`));
330
+ }
331
+ catch (err) {
332
+ logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "failed", dryRun: false, error: err instanceof Error ? err.message : String(err), meta: { orderId: oid } });
333
+ throw err;
334
+ }
335
+ return;
324
336
  }
325
- else {
337
+ // Single argument — is it an orderId (numeric) or a symbol name?
338
+ const isNumeric = /^\d+$/.test(symbolOrOrderId);
339
+ if (isNumeric) {
326
340
  // cancel <orderId> — look up symbol from open orders
327
- oid = symbolOrOrderId;
341
+ const oid = symbolOrOrderId;
328
342
  const orders = await adapter.getOpenOrders();
329
343
  const match = orders.find((o) => String(o.orderId) === oid);
330
344
  if (!match) {
@@ -334,20 +348,46 @@ export function registerTradeCommands(program, getAdapter, isJson, isDryRun = ()
334
348
  console.log(chalk.yellow(`\n ${err}\n`));
335
349
  return;
336
350
  }
337
- symbol = match.symbol;
338
- }
339
- if (dryRunGuard("cancel", { exchange: adapter.name, symbol, orderId: oid }))
340
- return;
341
- try {
342
- const result = await adapter.cancelOrder(symbol, oid);
343
- logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "success", dryRun: false, meta: { orderId: oid } });
344
- if (isJson())
345
- return printJson(jsonOk(result));
346
- console.log(chalk.green(`\n Order ${oid} cancelled on ${adapter.name}.\n`));
351
+ const symbol = match.symbol;
352
+ if (dryRunGuard("cancel", { exchange: adapter.name, symbol, orderId: oid }))
353
+ return;
354
+ try {
355
+ const result = await adapter.cancelOrder(symbol, oid);
356
+ logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "success", dryRun: false, meta: { orderId: oid } });
357
+ if (isJson())
358
+ return printJson(jsonOk(result));
359
+ console.log(chalk.green(`\n Order ${oid} cancelled on ${adapter.name}.\n`));
360
+ }
361
+ catch (err) {
362
+ logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "failed", dryRun: false, error: err instanceof Error ? err.message : String(err), meta: { orderId: oid } });
363
+ throw err;
364
+ }
347
365
  }
348
- catch (err) {
349
- logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "failed", dryRun: false, error: err instanceof Error ? err.message : String(err), meta: { orderId: oid } });
350
- throw err;
366
+ else {
367
+ // cancel <symbol> — cancel all open orders for this symbol
368
+ const symbol = symbolOrOrderId.toUpperCase();
369
+ const orders = await adapter.getOpenOrders();
370
+ const matching = orders.filter((o) => symbolMatch(o.symbol, symbol));
371
+ if (matching.length === 0) {
372
+ const err = `No open orders found for ${symbol}`;
373
+ if (isJson())
374
+ return printJson(jsonOk({ cancelled: false, symbol, reason: err }));
375
+ console.log(chalk.yellow(`\n ${err}\n`));
376
+ return;
377
+ }
378
+ if (dryRunGuard("cancel_by_symbol", { exchange: adapter.name, symbol, count: matching.length }))
379
+ return;
380
+ try {
381
+ const result = await adapter.cancelAllOrders(symbol);
382
+ logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "success", dryRun: false, meta: { bySymbol: true, count: matching.length } });
383
+ if (isJson())
384
+ return printJson(jsonOk({ cancelled: true, symbol, count: matching.length, result }));
385
+ console.log(chalk.green(`\n ${matching.length} order(s) for ${symbol} cancelled on ${adapter.name}.\n`));
386
+ }
387
+ catch (err) {
388
+ logExecution({ type: "cancel", exchange: adapter.name, symbol, side: "cancel", size: "0", status: "failed", dryRun: false, error: err instanceof Error ? err.message : String(err), meta: { bySymbol: true } });
389
+ throw err;
390
+ }
351
391
  }
352
392
  });
353
393
  trade
@@ -59,6 +59,15 @@ export declare class HyperliquidAdapter implements ExchangeAdapter {
59
59
  * Bypasses the SDK entirely — works for order, batchModify, twapOrder, etc.
60
60
  */
61
61
  private ensureSigner;
62
+ /**
63
+ * Normalize trailing zeros from price ('p') and size ('s') string fields
64
+ * in an action object, recursively. Matches the SDK's normalizeTrailingZeros.
65
+ *
66
+ * The HL validator normalizes these fields before hashing; if we hash without
67
+ * normalizing, the recovered signer address won't match → "User or API Wallet
68
+ * does not exist" error.
69
+ */
70
+ private static _normalizeAction;
62
71
  private _signAndSendAction;
63
72
  /**
64
73
  * Place an order via raw exchange API with EIP-712 signing.
@@ -430,6 +430,40 @@ export class HyperliquidAdapter {
430
430
  throw new Error("No private key configured. Run: perp init");
431
431
  }
432
432
  }
433
+ /**
434
+ * Normalize trailing zeros from price ('p') and size ('s') string fields
435
+ * in an action object, recursively. Matches the SDK's normalizeTrailingZeros.
436
+ *
437
+ * The HL validator normalizes these fields before hashing; if we hash without
438
+ * normalizing, the recovered signer address won't match → "User or API Wallet
439
+ * does not exist" error.
440
+ */
441
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
442
+ static _normalizeAction(obj) {
443
+ if (!obj || typeof obj !== "object")
444
+ return obj;
445
+ if (Array.isArray(obj)) {
446
+ return obj.map(item => HyperliquidAdapter._normalizeAction(item));
447
+ }
448
+ const result = { ...obj };
449
+ for (const key in result) {
450
+ if (!Object.prototype.hasOwnProperty.call(result, key))
451
+ continue;
452
+ const value = result[key];
453
+ if (value && typeof value === "object") {
454
+ result[key] = HyperliquidAdapter._normalizeAction(value);
455
+ }
456
+ else if ((key === "p" || key === "s") && typeof value === "string") {
457
+ let v = value;
458
+ if (v.includes("."))
459
+ v = v.replace(/\.?0+$/, "");
460
+ if (v === "-0")
461
+ v = "0";
462
+ result[key] = v;
463
+ }
464
+ }
465
+ return result;
466
+ }
433
467
  async _signAndSendAction(action) {
434
468
  this.ensureSigner();
435
469
  const { encode } = await import("@msgpack/msgpack");
@@ -438,9 +472,13 @@ export class HyperliquidAdapter {
438
472
  const baseUrl = isMainnet
439
473
  ? "https://api.hyperliquid.xyz"
440
474
  : "https://api.hyperliquid-testnet.xyz";
475
+ // Normalize trailing zeros from 'p' and 's' fields before hashing.
476
+ // The HL validator does the same; without this, the hash differs and the
477
+ // recovered signer address is wrong → "User or API Wallet does not exist".
478
+ const normalizedAction = HyperliquidAdapter._normalizeAction(action);
441
479
  // Sign L1 action (replicates SDK's signL1Action)
442
480
  const nonce = Date.now();
443
- const msgPackBytes = encode(action);
481
+ const msgPackBytes = encode(normalizedAction);
444
482
  const data = new Uint8Array(msgPackBytes.length + 9); // +8 nonce +1 vault flag
445
483
  data.set(msgPackBytes);
446
484
  const view = new DataView(data.buffer);
@@ -707,8 +745,8 @@ export class HyperliquidAdapter {
707
745
  r: opts?.reduceOnly ?? true,
708
746
  t: {
709
747
  trigger: {
710
- triggerPx: trimZeros(roundedTrigger),
711
748
  isMarket: opts?.isMarket ?? true,
749
+ triggerPx: trimZeros(roundedTrigger),
712
750
  tpsl,
713
751
  },
714
752
  },
@@ -598,7 +598,7 @@ export class LighterAdapter {
598
598
  // /recentTrades returns is_maker_ask: maker was asking → taker bought
599
599
  side: t.is_maker_ask ? "buy" : "sell",
600
600
  price: String(t.price ?? "0"),
601
- size: String(t.base_amount ?? t.amount ?? ""),
601
+ size: String(t.size ?? t.base_amount ?? t.amount ?? ""),
602
602
  fee: String(t.fee ?? "0"),
603
603
  }));
604
604
  }
@@ -339,15 +339,15 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
339
339
  steps.push({ step: 1, command: `perp -e ${ex} --json account orders`, description: "List open orders to find order ID" }, { step: 2, command: `perp -e ${ex} --json trade edit <symbol> <orderId> <newPrice> <newSize>`, description: "Modify the order", dangerous: true });
340
340
  }
341
341
  else if (g.includes("arb") || g.includes("arbitrage")) {
342
- steps.push({ step: 1, command: "perp --json arb scan --min 5", description: "Scan funding rate arbitrage opportunities" }, { step: 2, command: "perp --json arb scan", description: "Find high-spread opportunities" }, { step: 3, command: "perp --json gap show", description: "Check cross-exchange price gaps" }, { step: 4, command: "perp --json arb dex", description: "HIP-3 cross-dex arb opportunities (Hyperliquid)" });
342
+ steps.push({ step: 1, command: "perp --json arb scan --min 5", description: "Scan funding rate arbitrage opportunities" }, { step: 2, command: "perp --json arb scan --gaps", description: "Check cross-exchange price gaps" }, { step: 3, command: "perp --json arb scan --hip3", description: "HIP-3 cross-dex arb opportunities (Hyperliquid)" });
343
343
  }
344
344
  else if (g.includes("funding")) {
345
345
  const symbol = extractSymbol(g);
346
346
  if (symbol) {
347
- steps.push({ step: 1, command: `perp --json arb rates`, description: `Funding rates across all exchanges` }, { step: 2, command: `perp -e ${ex} --json market funding ${symbol}`, description: `${symbol} funding history` });
347
+ steps.push({ step: 1, command: `perp --json arb scan --rates`, description: `Funding rates across all exchanges` }, { step: 2, command: `perp -e ${ex} --json market funding ${symbol}`, description: `${symbol} funding history` });
348
348
  }
349
349
  else {
350
- steps.push({ step: 1, command: "perp --json arb rates", description: "Funding rates across all exchanges" }, { step: 2, command: "perp --json arb scan --min 5", description: "Funding rate arb opportunities" });
350
+ steps.push({ step: 1, command: "perp --json arb scan --rates", description: "Funding rates across all exchanges" }, { step: 2, command: "perp --json arb scan --min 5", description: "Funding rate arb opportunities" });
351
351
  }
352
352
  }
353
353
  else if (g.includes("bridge") || g.includes("transfer") || g.includes("cross-chain")) {
@@ -386,22 +386,22 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
386
386
  steps.push({ step: 1, command: `perp -e ${ex} --json risk status`, description: "Portfolio risk overview" }, { step: 2, command: `perp -e ${ex} --json risk limits`, description: "Current position limits" });
387
387
  }
388
388
  else if (g.includes("analytic") || g.includes("performance") || g.includes("pnl") || g.includes("p&l")) {
389
- steps.push({ step: 1, command: `perp -e ${ex} --json analytics summary`, description: "Trading performance summary" }, { step: 2, command: `perp -e ${ex} --json analytics pnl`, description: "P&L breakdown" }, { step: 3, command: `perp -e ${ex} --json analytics funding`, description: "Funding payment history" });
389
+ steps.push({ step: 1, command: `perp -e ${ex} --json history summary`, description: "Trading performance summary" }, { step: 2, command: `perp -e ${ex} --json history pnl`, description: "P&L breakdown" }, { step: 3, command: `perp -e ${ex} --json history funding`, description: "Funding payment history" });
390
390
  }
391
391
  else if (g.includes("history") || g.includes("log") || g.includes("audit")) {
392
392
  steps.push({ step: 1, command: `perp -e ${ex} --json history list`, description: "Execution audit trail" }, { step: 2, command: `perp -e ${ex} --json history stats`, description: "Execution statistics" });
393
393
  }
394
- else if (g.includes("stream") || g.includes("live") || g.includes("watch") || g.includes("realtime")) {
394
+ else if (g.includes("live") || g.includes("watch") || g.includes("realtime")) {
395
395
  const symbol = extractSymbol(g);
396
396
  if (symbol) {
397
- steps.push({ step: 1, command: `perp -e ${ex} stream book ${symbol}`, description: `Live ${symbol} orderbook` }, { step: 2, command: `perp -e ${ex} stream trades ${symbol}`, description: `Live ${symbol} trades` });
397
+ steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: `${symbol} orderbook` }, { step: 2, command: `perp -e ${ex} --json market trades ${symbol}`, description: `${symbol} recent trades` }, { step: 3, command: `perp --json arb scan --gaps --live`, description: "Live cross-exchange price gap monitor" });
398
398
  }
399
399
  else {
400
- steps.push({ step: 1, command: `perp -e ${ex} stream prices`, description: "Live price stream" }, { step: 2, command: `perp -e ${ex} stream events`, description: "Live account events (fills, liquidations)" });
400
+ steps.push({ step: 1, command: "perp --json arb scan --gaps --live", description: "Live cross-exchange price gap monitor" }, { step: 2, command: `perp -e ${ex} --json market prices`, description: "Current prices across exchanges" });
401
401
  }
402
402
  }
403
403
  else if (g.includes("gap")) {
404
- steps.push({ step: 1, command: "perp --json gap show", description: "Cross-exchange price gaps" }, { step: 2, command: "perp --json gap watch", description: "Live gap monitor" });
404
+ steps.push({ step: 1, command: "perp --json arb scan --gaps", description: "Cross-exchange price gaps" }, { step: 2, command: "perp --json arb scan --gaps --live", description: "Live gap monitor" });
405
405
  }
406
406
  else if (g.includes("rebalance")) {
407
407
  steps.push({ step: 1, command: "perp --json rebalance check", description: "Check balance distribution" }, { step: 2, command: "perp --json rebalance plan", description: "Generate rebalance plan" }, { step: 3, command: "perp --json rebalance execute", description: "Execute rebalance", dangerous: true });
@@ -446,7 +446,7 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
446
446
  steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: `${symbol} orderbook` }, { step: 2, command: `perp -e ${ex} --json market funding ${symbol}`, description: `${symbol} funding history` }, { step: 3, command: `perp -e ${ex} --json market kline ${symbol} 1h`, description: `${symbol} hourly candles` });
447
447
  }
448
448
  else {
449
- steps.push({ step: 1, command: "perp --json market prices", description: "All market prices" }, { step: 2, command: "perp --json gap show", description: "Cross-exchange price gaps" });
449
+ steps.push({ step: 1, command: "perp --json market prices", description: "All market prices" }, { step: 2, command: "perp --json arb scan --gaps", description: "Cross-exchange price gaps" });
450
450
  }
451
451
  }
452
452
  else if (g.includes("setting") || g.includes("config") || g.includes("referral")) {
@@ -708,6 +708,8 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
708
708
  history: "Shows order history (filled, cancelled, etc.)",
709
709
  trades: "Shows trade execution history with prices and fees",
710
710
  "funding-history": "Shows funding payments received/paid",
711
+ funding: "Shows funding payments received/paid (alias for funding-history)",
712
+ "twap-orders": "Shows active TWAP orders",
711
713
  pnl: "Shows profit & loss summary",
712
714
  margin: `Shows margin info for ${args[2] || "a symbol"}`,
713
715
  settings: "Shows account settings (leverage, margin mode per symbol)",
@@ -715,26 +717,26 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
715
717
  parameters: [],
716
718
  risks: [],
717
719
  category: "read",
718
- relatedCommands: ["perp portfolio", "perp portfolio"],
720
+ relatedCommands: ["perp portfolio", "perp account balance"],
719
721
  };
720
722
  }
721
723
  else if (category === "arb") {
722
724
  explanation = {
723
725
  command,
724
726
  description: {
725
- rates: "Compares funding rates across all 3 exchanges",
726
- scan: "Scans for funding rate arbitrage opportunities with the largest spreads",
727
- funding: "Detailed funding rate analysis across exchanges",
728
- dex: "HIP-3 cross-dex arbitrage opportunities on Hyperliquid",
727
+ scan: "Scans for funding rate arbitrage opportunities. Use --rates for funding rates, --gaps for price gaps, --hip3 for cross-dex arb, --positions for funding impact, --basis for basis trading",
729
728
  auto: "Auto-execute funding rate arbitrage (runs as background job)",
729
+ exec: "Execute an arb trade (enter paired positions on two exchanges)",
730
730
  status: "Shows current arb positions and P&L",
731
731
  close: "Closes an arb position on both exchanges",
732
732
  history: "Shows arb execution history",
733
+ config: "View/edit arb configuration",
734
+ rebalance: "Rebalance funds across exchanges for arb positions",
733
735
  }[sub] || `Arbitrage command: ${sub}`,
734
736
  parameters: [],
735
- risks: ["auto" === sub ? "Auto-executes trades on both exchanges" : ""].filter(Boolean),
736
- category: sub === "auto" || sub === "close" ? "write" : "analysis",
737
- relatedCommands: ["perp arb scan", "perp arb scan", "perp gap show"],
737
+ risks: [sub === "auto" || sub === "exec" ? "Executes trades on exchanges" : ""].filter(Boolean),
738
+ category: sub === "auto" || sub === "close" || sub === "exec" || sub === "rebalance" ? "write" : "analysis",
739
+ relatedCommands: ["perp arb scan", "perp arb status", "perp arb scan --gaps"],
738
740
  };
739
741
  }
740
742
  else if (category === "bridge") {
@@ -774,16 +776,6 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
774
776
  relatedCommands: ["perp account positions", "perp portfolio"],
775
777
  };
776
778
  }
777
- else if (category === "alert") {
778
- explanation = {
779
- command,
780
- description: { add: "Add a price, funding rate, PnL, or liquidation alert", list: "List all active alerts", remove: "Remove an alert by ID", daemon: "Start the alert monitoring daemon", test: "Send a test alert notification", config: "Configure alert notification channels (Telegram, Discord)" }[sub] || `Alert command: ${sub}`,
781
- parameters: [],
782
- risks: [],
783
- category: "read",
784
- relatedCommands: ["perp risk status", "perp risk limits"],
785
- };
786
- }
787
779
  else if (category === "bot" || category === "run") {
788
780
  explanation = {
789
781
  command,
@@ -797,9 +789,9 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
797
789
  else if (category === "history") {
798
790
  explanation = {
799
791
  command,
800
- description: { summary: "Trading performance summary", pnl: "P&L breakdown by symbol and time period", funding: "Funding payment history and totals", report: "Detailed performance report", perf: "Performance breakdown (daily/weekly/summary)" }[sub] || `History: ${sub}`,
792
+ description: { list: "Execution audit trail", stats: "Execution statistics", positions: "Position history", summary: "Trading performance summary", pnl: "P&L breakdown by symbol and time period", funding: "Funding payment history and totals", report: "Detailed performance report", snapshot: "Portfolio snapshot", track: "Track position over time", perf: "Performance breakdown (daily/weekly/summary)", prune: "Prune old execution records" }[sub] || `History: ${sub}`,
801
793
  parameters: [],
802
- risks: [],
794
+ risks: sub === "prune" ? ["Permanently removes old execution records"] : [],
803
795
  category: "read",
804
796
  relatedCommands: ["perp history summary", "perp history list"],
805
797
  };
@@ -807,21 +799,11 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
807
799
  else if (category === "wallet") {
808
800
  explanation = {
809
801
  command,
810
- description: { list: "List configured wallets", balance: "Check on-chain USDC/SOL/ETH balances", generate: "Generate a new wallet keypair", use: "Set active wallet" }[sub] || `Wallet: ${sub}`,
802
+ description: { show: "Show configured wallets with public addresses", list: "List configured wallets", balance: "Check on-chain USDC/SOL/ETH balances", generate: "Generate a new wallet keypair", import: "Import an existing private key", set: "Set private key for an exchange", use: "Set active wallet", remove: "Remove a wallet", rename: "Rename a wallet" }[sub] || `Wallet: ${sub}`,
811
803
  parameters: [],
812
- risks: sub === "generate" ? ["Store the private key securely — it cannot be recovered"] : [],
804
+ risks: sub === "generate" ? ["Store the private key securely — it cannot be recovered"] : sub === "remove" ? ["Wallet will be removed — ensure you have the private key backed up"] : [],
813
805
  category: "read",
814
- relatedCommands: ["perp wallet list", "perp wallet balance"],
815
- };
816
- }
817
- else if (category === "gap") {
818
- explanation = {
819
- command,
820
- description: { show: "Shows cross-exchange price gaps for all symbols", watch: "Live monitor for price gaps above threshold", track: "Track gap history", alert: "Alert when gaps exceed threshold" }[sub] || `Gap analysis: ${sub}`,
821
- parameters: [],
822
- risks: [],
823
- category: "analysis",
824
- relatedCommands: ["perp gap show", "perp arb scan"],
806
+ relatedCommands: ["perp wallet show", "perp wallet balance"],
825
807
  };
826
808
  }
827
809
  else if (category === "backtest") {
@@ -976,7 +958,8 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
976
958
  orders: { usage: "perp account orders", description: "Open/pending orders" },
977
959
  history: { usage: "perp account history", description: "Order history" },
978
960
  trades: { usage: "perp account trades", description: "Trade fill history" },
979
- "funding-history": { usage: "perp account funding-history", description: "Funding payments" },
961
+ "funding-history|funding": { usage: "perp account funding-history", description: "Funding payments" },
962
+ "twap-orders": { usage: "perp account twap-orders", description: "Active TWAP orders" },
980
963
  pnl: { usage: "perp account pnl", description: "Profit & loss" },
981
964
  margin: { usage: "perp account margin <symbol>", description: "Position margin info" },
982
965
  settings: { usage: "perp account settings", description: "Account settings (leverage per symbol)" },
@@ -1007,19 +990,23 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
1007
990
  fills: { usage: "perp trade fills [symbol]", description: "Recent fills" },
1008
991
  status: { usage: "perp trade status <orderId>", description: "Check order status" },
1009
992
  "pnl-track": { usage: "perp trade pnl-track", description: "Real-time PnL tracker" },
993
+ split: { usage: "perp trade split <symbol> <side> <usd>", description: "Split large order into depth-based slices" },
994
+ multi: { usage: "perp trade multi <legs...>", description: "Execute multi-leg orders (exchange:symbol:side:size)" },
995
+ "cancel-stop": { usage: "perp trade cancel-stop <symbol> <stopOrderId>", description: "Cancel a stop order" },
996
+ "cancel-twap": { usage: "perp trade cancel-twap <symbol> <twapOrderId>", description: "Cancel a TWAP order" },
1010
997
  },
1011
998
  },
1012
999
  arb: {
1013
1000
  description: "Funding rate arbitrage & basis trading",
1014
1001
  subcommands: {
1015
- rates: { usage: "perp arb scan", description: "Compare funding rates" },
1016
- scan: { usage: "perp arb scan --min <bps>", description: "Find arb opportunities" },
1017
- funding: { usage: "perp arb funding", description: "Detailed funding analysis" },
1018
- dex: { usage: "perp arb dex", description: "HIP-3 cross-dex arb" },
1019
- auto: { usage: "perp arb auto --min-spread <bps>", description: "Auto-execute funding arb" },
1002
+ scan: { usage: "perp arb scan --min <pct>", description: "Find arb opportunities (use --rates, --gaps, --hip3, --positions, --basis, --compare, --history, --live)" },
1003
+ auto: { usage: "perp arb auto --min-spread <pct>", description: "Auto-execute funding arb" },
1004
+ exec: { usage: "perp arb exec <symbol> <longEx> <shortEx> <size>", description: "Execute arb trade on two exchanges" },
1020
1005
  status: { usage: "perp arb status", description: "Current arb positions" },
1021
1006
  close: { usage: "perp arb close <symbol>", description: "Close arb position" },
1022
- history: { usage: "perp arb history", description: "Arb execution history" },
1007
+ "history|log": { usage: "perp arb history", description: "Arb execution history" },
1008
+ config: { usage: "perp arb config", description: "View/edit arb configuration" },
1009
+ rebalance: { usage: "perp arb rebalance", description: "Rebalance funds across exchanges for arb" },
1023
1010
  },
1024
1011
  },
1025
1012
  risk: {
@@ -1043,32 +1030,48 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
1043
1030
  bot: {
1044
1031
  description: "Automated trading bots",
1045
1032
  subcommands: {
1033
+ start: { usage: "perp bot start <config>", description: "Start bot from config file" },
1046
1034
  twap: { usage: "perp bot twap <symbol> <side> <size> <duration>", description: "TWAP execution" },
1047
1035
  grid: { usage: "perp bot grid <symbol> --range <pct> --grids <n> --size <usd>", description: "Grid trading bot" },
1048
1036
  dca: { usage: "perp bot dca <symbol> <side> <amount> <interval>", description: "DCA bot" },
1037
+ "funding-arb": { usage: "perp bot funding-arb", description: "Funding arb bot" },
1049
1038
  "trailing-stop": { usage: "perp bot trailing-stop <symbol>", description: "Trailing stop bot" },
1050
1039
  "quick-grid": { usage: "perp bot quick-grid <symbol>", description: "Quick grid bot" },
1051
1040
  "quick-dca": { usage: "perp bot quick-dca <symbol> <side> <amount> <interval>", description: "Quick DCA bot" },
1052
1041
  "quick-arb": { usage: "perp bot quick-arb", description: "Quick arb bot" },
1042
+ "preset-list": { usage: "perp bot preset-list", description: "List available bot presets" },
1043
+ preset: { usage: "perp bot preset <name> [symbol]", description: "Run a bot preset" },
1044
+ example: { usage: "perp bot example", description: "Show example bot config" },
1053
1045
  },
1054
1046
  },
1055
1047
  wallet: {
1056
1048
  description: "Wallet management",
1057
1049
  subcommands: {
1050
+ show: { usage: "perp wallet show", description: "Show configured wallets with public addresses" },
1058
1051
  list: { usage: "perp wallet list", description: "List wallets" },
1059
- balance: { usage: "perp wallet balance", description: "On-chain balances" },
1052
+ balance: { usage: "perp wallet balance [name]", description: "On-chain balances" },
1060
1053
  generate: { usage: "perp wallet generate solana|evm", description: "Generate new wallet" },
1054
+ import: { usage: "perp wallet import solana|evm <privateKey>", description: "Import existing private key" },
1055
+ set: { usage: "perp wallet set <exchange> <key>", description: "Set private key for exchange" },
1056
+ use: { usage: "perp wallet use <name> [exchange]", description: "Set active wallet" },
1057
+ remove: { usage: "perp wallet remove <name>", description: "Remove a wallet" },
1058
+ rename: { usage: "perp wallet rename <oldName> <newName>", description: "Rename a wallet" },
1061
1059
  },
1062
1060
  },
1063
1061
  history: {
1064
1062
  description: "Execution log, performance & audit trail",
1065
1063
  subcommands: {
1066
1064
  list: { usage: "perp history list", description: "Execution log" },
1065
+ stats: { usage: "perp history stats", description: "Execution statistics" },
1066
+ positions: { usage: "perp history positions", description: "Position history" },
1067
1067
  summary: { usage: "perp history summary", description: "Performance summary" },
1068
1068
  pnl: { usage: "perp history pnl", description: "P&L breakdown" },
1069
1069
  funding: { usage: "perp history funding", description: "Funding payment history" },
1070
1070
  report: { usage: "perp history report", description: "Full performance report" },
1071
+ snapshot: { usage: "perp history snapshot", description: "Portfolio snapshot" },
1072
+ track: { usage: "perp history track", description: "Track position over time" },
1071
1073
  perf: { usage: "perp history perf --period daily|weekly|summary", description: "Performance breakdown" },
1074
+ prune: { usage: "perp history prune", description: "Prune old execution records" },
1072
1075
  },
1073
1076
  },
1074
1077
  backtest: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter",
5
5
  "bin": {
6
6
  "perp": "dist/index.js",
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: perp-cli
3
- description: "Multi-DEX perpetual futures trading CLI for Pacifica (Solana), Hyperliquid (EVM), and Lighter (Ethereum). Use when user asks to: trade perps, check funding rates, scan arbitrage (perp-perp or spot-perp), delta-neutral strategies, bridge USDC, manage positions/orders, deposit/withdraw, spot+perp hedge, or mentions perp-cli, hypurrquant, Pacifica, Hyperliquid, Lighter, HyperEVM, funding arb, U-token (UBTC/UETH/USOL)."
3
+ description: "Multi-DEX perpetual futures trading CLI for Pacifica (Solana), Hyperliquid (EVM), and Lighter (Ethereum). Use when user asks to: trade perps, check funding rates, scan arbitrage (perp-perp or spot-perp), delta-neutral strategies, bridge USDC, manage positions/orders, deposit/withdraw, spot+perp hedge, HIP-3 DEX trading, or mentions perp-cli, hypurrquant, Pacifica, Hyperliquid, Lighter, HyperEVM, funding arb, U-token (UBTC/UETH/USOL)."
4
4
  allowed-tools: "Bash(perp:*), Bash(npx perp-cli:*), Bash(npx -y perp-cli:*)"
5
5
  license: MIT
6
6
  metadata:
7
7
  author: hypurrquant
8
- version: "0.4.9"
8
+ version: "0.5.1"
9
9
  ---
10
10
 
11
11
  # perp-cli Agent Guide
@@ -21,21 +21,18 @@ Multi-DEX perpetual futures CLI — Pacifica (Solana), Hyperliquid (HyperEVM), L
21
21
  5. **NEVER trade without user confirmation.**
22
22
  6. **NEVER read ~/.perp/.env or key files.**
23
23
 
24
- ## Install (run this FIRST)
24
+ ## Install
25
25
 
26
26
  ```bash
27
- # 1. Check if perp exists and is current (must be ≥ 0.3.19)
28
- perp --version 2>/dev/null
29
-
30
- # 2. If not found or outdated:
27
+ perp --version 2>/dev/null # check if exists (must be >= 0.5.1)
31
28
  npm install -g perp-cli@latest 2>/dev/null || npx -y perp-cli@latest --json --version
32
-
33
- # 3. If version is still old (npx cache), clear cache first:
34
- npx -y clear-npx-cache 2>/dev/null; npx -y perp-cli@latest --json --version
35
29
  ```
36
30
 
37
- **IMPORTANT:** Always use `@latest` with npx — without it, npx uses a stale cached version.
38
- Use `perp` if global install works, otherwise `npx -y perp-cli@latest` as prefix for all commands.
31
+ Use `perp` if global install works, otherwise `npx -y perp-cli@latest` as prefix.
32
+
33
+ ## Global Flags
34
+
35
+ `--json` (required) | `-e hl/pac/lt` (exchange) | `--dex <name>` (HIP-3) | `-w <wallet>` | `--dry-run` | `--fields <f1,f2>` | `--ndjson`
39
36
 
40
37
  ## Wallet Setup
41
38
 
@@ -45,227 +42,131 @@ perp --json wallet set pac <SOLANA_KEY> # Pacifica
45
42
  perp --json wallet set lt <EVM_KEY> # Lighter (API key auto-generated)
46
43
  perp --json wallet show # verify
47
44
  ```
48
- Same EVM key works for both HL and Lighter.
49
45
 
50
- ## Key Commands
46
+ ## Core Workflow: Arb
51
47
 
52
- ```bash
53
- # Status (ONE call = balances, positions, risk level/violations/canTrade)
54
- perp --json portfolio
55
-
56
- # Arb workflow
57
- perp --json arb scan --min 5 # find opportunities
58
- perp --json arb exec <SYM> <longEx> <shortEx> <$> --leverage <N> --isolated # execute
59
-
60
- # Single exchange trading (when not doing arb)
61
- perp --json -e <EX> trade market <SYM> buy <SIZE>
62
- perp --json -e <EX> trade market <SYM> buy <SIZE> --smart # IOC limit at best ask + 1 tick (less slippage)
63
- perp --json -e <EX> trade market <SYM> buy <SIZE> --split # orderbook-aware split for large orders
64
- perp --json -e <EX> trade split <SYM> buy 5000 # dedicated split command (USD notional)
65
- perp --json -e <EX> trade close <SYM>
66
- perp --json -e <EX> trade close <SYM> --smart # smart close at best bid/ask
67
-
68
- # Risk (only if you need to set limits — portfolio already includes risk)
69
- perp --json risk limits --max-leverage 5
48
+ ```
49
+ 1. perp --json arb scan # scan opportunities (perp-perp + spot-perp + HIP-3)
50
+ 2. perp --json account balance # check balances across all exchanges
51
+ 3. perp --json --dry-run arb exec <SYM> <longEx> <shortEx> <$> # dry-run (auto-sizes)
52
+ 4. [show result to user, get confirmation]
53
+ 5. perp --json arb exec <SYM> <longEx> <shortEx> <$> # execute
54
+ 6. perp --json arb status # monitor PnL + funding income
55
+ 7. perp --json arb close <SYM> # close both legs
70
56
  ```
71
57
 
72
- Exchange aliases: `hl`, `pac`, `lt`. Symbols auto-resolve (`BTC`, `SOL`, `ICP`).
73
-
74
- ## Agent Tools
75
-
76
- ```bash
77
- # Discover all commands and parameters at runtime (don't guess — query this)
78
- perp --json agent schema
79
-
80
- # Pre-validate a trade before execution (checks balance, liquidity, risk limits)
81
- perp --json -e <EX> trade check <SYM> <SIDE> <SIZE>
82
-
83
- # Dry-run: simulate trade without executing
84
- perp --json --dry-run -e <EX> trade market <SYM> buy <SIZE>
85
-
86
- # Filter output to specific fields (saves tokens)
87
- perp --json --fields totalEquity,positions portfolio
58
+ ### Scan Modes
88
59
 
89
- # Stream large lists as NDJSON (one JSON per line, no buffering)
90
- perp --json --ndjson -e <EX> market list
60
+ | Flag | Description |
61
+ |------|-------------|
62
+ | _(default)_ | All modes combined (perp-perp + spot-perp) |
63
+ | `--rates` | Funding rates across exchanges |
64
+ | `--basis [symbol]` | Cross-exchange basis/price diff |
65
+ | `--gaps` | Price gaps |
66
+ | `--hip3` | HIP-3 on-chain dex opportunities |
67
+ | `--history [symbol]` | Historical funding data |
68
+ | `--compare <symbol>` | Compare symbol across exchanges |
69
+ | `--positions` | Existing position exit signals |
70
+ | `--live` | Continuous monitoring |
91
71
 
92
- # Prevent duplicate orders with client ID
93
- perp --json -e <EX> trade market <SYM> buy <SIZE> --client-id <UNIQUE_ID>
72
+ ### Spot+Perp Arb
94
73
 
95
- # Smart order: IOC limit at best bid/ask + 1 tick instead of raw market order
96
- # Fills only at top-of-book price — no multi-level sweep slippage
97
- perp --json -e <EX> trade market <SYM> buy <SIZE> --smart
98
- perp --json -e <EX> trade close <SYM> --smart
99
- perp --json arb exec <SYM> <longEx> <shortEx> <$> --smart # smart arb entry
100
- perp --json arb close <SYM> --smart # smart arb close
74
+ Spot has 0 funding cost. Use `spot:<exch>` prefix for the long leg:
101
75
 
102
- # Split order: orderbook-aware execution for large orders
103
- # Reads depth before each slice, IOC limit within slippage tolerance
104
- perp --json -e <EX> trade split <SYM> buy 5000 # split $5000 into depth-based slices
105
- perp --json -e <EX> trade split <SYM> sell 10000 --max-slices 5 --delay 2000
106
- perp --json -e <EX> trade market <SYM> buy <SIZE> --split # split via market command flag
76
+ ```bash
77
+ perp --json arb exec ETH spot:hl hl 100 # spot buy(HL) + perp short(HL)
78
+ perp --json arb exec BTC spot:lt pac 50 # spot buy(LT) + perp short(PAC)
79
+ perp --json arb close ETH # sells spot + buys back perp
107
80
  ```
108
81
 
109
- All string outputs are auto-sanitized (control chars stripped, prompt injection patterns blocked).
82
+ Spot exchanges: HL, LT. Pacifica is perp-only.
110
83
 
111
- ## Arb Workflow (Perp-Perp)
84
+ ## Core Workflow: Single Trade
112
85
 
86
+ ```bash
87
+ perp --json -e <EX> trade buy <SYM> <SIZE> # market buy (SIZE = token units)
88
+ perp --json -e <EX> trade sell <SYM> <SIZE> # market sell
89
+ perp --json -e <EX> trade buy <SYM> <SIZE> --smart # IOC limit at best ask
90
+ perp --json -e <EX> trade split <SYM> buy <USD> # orderbook-aware split
91
+ perp --json -e <EX> trade limit <SYM> buy <PRICE> <SIZE>
92
+ perp --json -e <EX> trade tpsl <SYM> buy --tp <P> --sl <P>
93
+ perp --json -e <EX> trade close <SYM>
94
+ perp --json -e <EX> trade flatten # close ALL + cancel ALL
95
+ perp --json -e <EX> trade cancel <SYM> # cancel all orders for symbol
113
96
  ```
114
- 1. perp --json portfolio → check balances across all exchanges
115
- 2. perp --json arb scan --min 5 → find opportunity (longExch, shortExch, netSpread)
116
- 3. [Show opportunity to user, get confirmation]
117
- 4. perp --json arb exec <SYM> <longEx> <shortEx> <$> --leverage 2 --isolated
118
- → validates orderbook depth on both sides
119
- → rounds size to each exchange's lot size
120
- → executes BOTH legs simultaneously
121
- → verifies positions exist after execution
122
- → auto-rollback if one leg fails
123
- 5. perp --json arb status → monitor: PnL, funding income, daily estimate, breakeven
124
- 6. perp --json arb funding-earned → actual funding payments received/paid + APR
125
- 7. perp --json arb close <SYM> → close both legs simultaneously (retry + verify)
126
- → --dry-run: preview without executing
127
- → --pair <longEx>:<shortEx>: specify which pair if multiple
128
- ```
129
-
130
- ## Spot+Perp Arb Workflow
131
-
132
- Spot has 0 funding cost → spread = |perp funding rate|. Only needs 1 exchange with spot + any exchange with perp.
133
97
 
134
- **Spot exchanges:** HL (Hyperliquid), LT (Lighter). Pacifica is perp-only.
135
- **U-token mapping:** HL spot uses Unit protocol bridged tokens — UBTC=BTC, UETH=ETH, USOL=SOL, UFART=FARTCOIN. Resolved automatically.
98
+ ## Agent Tools
136
99
 
137
- ```
138
- 1. perp --json arb scan --mode spot-perp → find spot+perp opportunities
139
- perp --json arb scan --mode all → perp-perp + spot-perp combined
140
- 2. [Show opportunity to user, get confirmation]
141
- 3. perp --json arb spot-exec <SYM> <spotEx> <perpEx> <$>
142
- Examples:
143
- perp --json arb spot-exec ETH hl hl 100 # ETH spot(HL/UETH) + perp(HL)
144
- perp --json arb spot-exec BTC hl lt 50 # BTC spot(HL/UBTC) + perp(LT)
145
- perp --json arb spot-exec LINK lt hl 100 # LINK spot(LT) + perp(HL)
146
- → cross-validates spot vs perp price (>5% deviation = abort, wrong token)
147
- → sequential execution if same exchange (nonce collision avoidance)
148
- → parallel execution if different exchanges
149
- → auto-rollback if one leg fails
150
- Options:
151
- --leverage <N> set perp leverage before entry
152
- --isolated use isolated margin on perp side
153
- 4. perp --json arb status → shows spot-perp positions (spot leg labeled)
154
- 5. perp --json arb spot-close <SYM> --spot-exch <hl|lt>
155
- → sells spot + buys back perp simultaneously
100
+ ```bash
101
+ perp --json agent schema # discover all commands (don't guess)
102
+ perp --json -e <EX> trade check <SYM> <SIDE> <SIZE> # pre-validate trade
103
+ perp --json portfolio # balances + positions + risk level
104
+ perp --json --fields totalEquity,positions portfolio # filtered output
156
105
  ```
157
106
 
158
- **Available spot+perp pairs (13):**
159
- - HL spot: BTC (UBTC), ETH (UETH), SOL (USOL), FARTCOIN (UFART), HYPE, AZTEC, STABLE
160
- - LT spot: ETH, AZTEC, AAVE, LINK, UNI, LIT, LDO, SKY
161
- - All have perps on HL and LT. Pacifica perps available when API is up.
162
-
163
- ## Position Sizing
107
+ ## Position Sizing & Safety
164
108
 
165
109
  - Single position < **80%** of that exchange's available balance
166
110
  - Leverage 1-3x for arb, max 5x
167
111
  - `arb exec` auto-matches both legs to the same size
112
+ - ALWAYS follow `longExch`/`shortExch` from scan — NEVER reverse direction
113
+ - NEVER enter if `netSpread <= 0` or `netSpread < estimated round-trip slippage`
114
+ - Check `portfolio.risk.canTrade` — if `false`, do NOT trade
168
115
 
169
- ## Arb Direction (CRITICAL)
170
-
171
- `arb scan` returns `longExch`, `shortExch`, `netSpread`.
172
- ALWAYS follow exactly. NEVER reverse. NEVER enter if `netSpread <= 0`.
173
-
174
- ## Slippage Check (CRITICAL)
175
-
176
- `arb scan` returns `netSpread` in bps (already fee-adjusted).
177
- Before executing, compare against estimated slippage:
178
- ```
179
- Slippage ≈ 2-5 bps per leg for top pairs (BTC, ETH, SOL)
180
- ≈ 5-15 bps per leg for low-liquidity (altcoins)
181
- Total round-trip slippage = (entry + exit) × 2 legs
182
- ```
183
- **NEVER enter if:** `netSpread < total round-trip slippage`
184
- Example: netSpread=12 bps, estimated slippage=4 bps/leg × 4 = 16 bps → SKIP.
185
-
186
- ## Portfolio Response
187
-
188
- `portfolio` includes `risk: { level, canTrade, violations[] }` — no need for separate `risk status`.
189
- Check `canTrade` before any order. If `false`, do NOT trade.
116
+ ## HIP-3 Support
190
117
 
191
- ## Monitoring (while positions open)
192
-
193
- ```
194
- Every 15 min: perp --json arb status → PnL, daily income, breakeven analysis
195
- Every 1 hour: perp --json arb scan --min 5 → check if spread still profitable
196
- Daily: perp --json arb funding-earned → verify actual funding payments
197
- Exit if: spread < breakeven or one leg closed unexpectedly.
198
- Close: perp --json arb close <SYM> → close both legs with retry + verification
118
+ ```bash
119
+ perp --json -e hl --dex km market list # dex-specific markets
120
+ perp --json market list --hip3 # all HIP-3 markets
121
+ perp --json account positions --hip3 # HIP-3 positions
122
+ perp --json arb scan --hip3 # HIP-3 arb opportunities
199
123
  ```
200
124
 
201
125
  ## Error Handling
202
126
 
203
- Responses: `{ "ok": true, "data": {...} }` or `{ "ok": false, "error": { "code": "...", "retryable": true/false } }`
204
-
205
- If `error.retryable` is `false`, do NOT retry — fix the cause first.
127
+ Response format: `{ "ok": true, "data": {...} }` or `{ "ok": false, "error": { "code": "...", "retryable": true/false } }`
206
128
 
207
129
  | Error | Action |
208
130
  |-------|--------|
209
- | `RATE_LIMITED` | wait 5-10s, retry (max 3). Lighter rate limits are strict — space commands 3s+ apart |
210
- | `EXCHANGE_UNREACHABLE` | wait 10s, retry. 3x fail → skip that exchange |
211
- | `TIMEOUT` | wait 5s, retry (max 3) |
212
- | `INSUFFICIENT_BALANCE` | reduce size or bridge funds to that exchange |
213
- | `SYMBOL_NOT_FOUND` | `perp --json -e <EX> market list` to verify symbol |
214
- | `RISK_VIOLATION` | check `risk limits`, ask user to adjust if needed |
215
- | `SIZE_TOO_SMALL` | `perp --json -e <EX> market info <SYM>` for min order size |
216
- | `MARGIN_INSUFFICIENT` | reduce leverage or close existing positions |
217
- | `DUPLICATE_ORDER` | already submitted — check positions, don't retry |
218
- | Lighter `invalid signature` | check ~/.perp/.env or `perp --json -e lighter manage setup-api-key` |
219
- | Lighter `invalid account index` | rate limit caused init failure — wait 10s and retry the command |
220
- | Lighter `--smart` not filling | Lighter IOC limit orders may not fill — use regular market order (without `--smart`) |
131
+ | `RATE_LIMITED` | wait 5s, retry (max 3). Lighter: space 3s+ apart |
132
+ | `EXCHANGE_UNREACHABLE` | wait 10s, retry. 3x fail → skip exchange |
133
+ | `INSUFFICIENT_BALANCE` | reduce size or bridge funds |
134
+ | `SYMBOL_NOT_FOUND` | `market list` to verify symbol |
135
+ | `SIZE_TOO_SMALL` | `market info <SYM>` for min size |
136
+ | `DUPLICATE_ORDER` | already submitted — check positions |
137
+ | Lighter `invalid signature` | `manage setup-api-key` to regenerate |
221
138
 
222
139
  ## Examples
223
140
 
224
- **User: "펀딩레이트 아비트라지 기회 찾아줘"**
225
- ```bash
226
- perp --json portfolio # check balances
227
- perp --json arb scan --mode all --min 5 # scan perp-perp + spot-perp
228
- # → show results, recommend best opportunity, ask for confirmation
229
- ```
230
-
231
- **User: "ETH spot-perp 아비트라지 실행해줘 $100"**
141
+ **"Find arb opportunities"**
232
142
  ```bash
233
- perp --json arb scan --mode spot-perp # verify opportunity exists
234
- perp --json --dry-run arb spot-exec ETH hl hl 100 # dry-run first
235
- # → show dry-run result, ask for confirmation
236
- perp --json arb spot-exec ETH hl hl 100 # execute after confirmation
237
- perp --json arb status # verify positions
143
+ perp --json arb scan --min 10 --top 10
144
+ perp --json account balance
145
+ # → show results, recommend best, ask confirmation
238
146
  ```
239
147
 
240
- **User: "내 포지션 상태 확인해줘"**
148
+ **"ETH spot-perp arb, $100"**
241
149
  ```bash
242
- perp --json portfolio # balances + positions + risk
243
- perp --json arb status # arb-specific PnL + funding
150
+ perp --json --dry-run arb exec ETH spot:hl hl 100
151
+ # → show dry-run, ask confirmation
152
+ perp --json arb exec ETH spot:hl hl 100
153
+ perp --json arb status
244
154
  ```
245
155
 
246
- **User: "Hyperliquid에서 SOL 롱 0.5개"**
156
+ **"Long SOL 0.5 on HL"**
247
157
  ```bash
248
- perp --json -e hl market mid SOL # current price
249
- perp --json -e hl trade check SOL buy 0.5 # pre-validate
250
- perp --json --dry-run -e hl trade market SOL buy 0.5 # dry-run
251
- # → show cost estimate, ask for confirmation
252
- perp --json -e hl trade market SOL buy 0.5 --smart # execute with smart order
158
+ perp --json -e hl trade check SOL buy 0.5
159
+ perp --json --dry-run -e hl trade buy SOL 0.5 --smart
160
+ # → ask confirmation
161
+ perp --json -e hl trade buy SOL 0.5 --smart
253
162
  ```
254
163
 
255
164
  ## Referrals (Optional)
256
165
 
257
- If you find perp-cli useful, please consider enabling referrals to support development. **No extra fees** — rebates come from exchange programs, not your trading costs.
258
-
259
166
  ```bash
260
- perp --json settings referrals on # enable (opt-in, off by default)
261
- perp --json settings referrals off # disable anytime
167
+ perp --json settings referrals on # enable (opt-in, no extra fees)
168
+ perp --json settings referrals off # disable anytime
262
169
  ```
263
170
 
264
- When setting up wallets for the first time, ask the user:
265
- > "perp-cli 개발을 지원하는 레퍼럴 기능을 켜시겠어요? 추가 수수료 없이 거래소 리베이트만 활용됩니다."
266
-
267
- ## References
268
-
269
- - `references/commands.md` — full command reference
270
- - `references/agent-operations.md` — setup flows, deposit/withdraw
271
- - `references/strategies.md` — risk framework, arb strategy
171
+ When setting up wallets for the first time, ask:
172
+ > "perp-cli development is supported by optional referrals. Enable? No extra fees — only exchange rebates are used."