perp-cli 0.4.8 → 0.4.9

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 (45) hide show
  1. package/README.md +44 -5
  2. package/dist/arb/reconcile.d.ts +28 -0
  3. package/dist/arb/reconcile.js +162 -0
  4. package/dist/commands/account.d.ts +1 -1
  5. package/dist/commands/account.js +300 -63
  6. package/dist/commands/agent.js +9 -5
  7. package/dist/commands/arb/index.js +243 -49
  8. package/dist/commands/arb-auto.js +67 -19
  9. package/dist/commands/arb.d.ts +2 -1
  10. package/dist/commands/arb.js +535 -131
  11. package/dist/commands/bot.js +207 -0
  12. package/dist/commands/funds.d.ts +4 -0
  13. package/dist/commands/funds.js +741 -0
  14. package/dist/commands/history.d.ts +2 -1
  15. package/dist/commands/history.js +508 -5
  16. package/dist/commands/manage.js +5 -3
  17. package/dist/commands/market.js +34 -1
  18. package/dist/commands/pnl.js +1 -1
  19. package/dist/commands/risk.d.ts +2 -0
  20. package/dist/commands/risk.js +56 -0
  21. package/dist/commands/settings.js +147 -0
  22. package/dist/commands/trade.d.ts +1 -1
  23. package/dist/commands/trade.js +241 -259
  24. package/dist/commands/wallet.d.ts +4 -0
  25. package/dist/commands/wallet.js +10 -0
  26. package/dist/config.d.ts +2 -2
  27. package/dist/config.js +11 -3
  28. package/dist/exchanges/hyperliquid-spot.d.ts +1 -0
  29. package/dist/exchanges/hyperliquid-spot.js +13 -21
  30. package/dist/exchanges/lighter-spot.js +13 -15
  31. package/dist/exchanges/lighter.js +82 -21
  32. package/dist/exchanges/pacifica.js +12 -0
  33. package/dist/execution-log.d.ts +1 -1
  34. package/dist/index.js +39 -123
  35. package/dist/mcp-server.js +43 -96
  36. package/dist/strategies/split-order.d.ts +49 -0
  37. package/dist/strategies/split-order.js +138 -0
  38. package/package.json +1 -1
  39. package/skills/perp-cli/SKILL.md +13 -26
  40. package/skills/perp-cli/references/agent-operations.md +19 -9
  41. package/skills/perp-cli/references/commands.md +38 -31
  42. package/skills/perp-cli/references/strategies.md +26 -16
  43. package/skills/perp-cli/scripts/funding-analysis.sh +2 -2
  44. package/skills/perp-cli/scripts/preflight.sh +1 -1
  45. package/skills/perp-cli/scripts/spot-perp-scan.sh +1 -1
@@ -3,6 +3,12 @@ import { printJson, jsonOk } from "../utils.js";
3
3
  import { loadBotConfig, quickGridConfig, quickDCAConfig } from "../bot/config.js";
4
4
  import { runBot } from "../bot/engine.js";
5
5
  import { PRESETS, getPreset, getPresetsByStrategy } from "../bot/presets.js";
6
+ import { updateJobState } from "../jobs.js";
7
+ import { runTWAP } from "../strategies/twap.js";
8
+ import { runFundingArb } from "../strategies/funding-arb.js";
9
+ import { runGrid } from "../strategies/grid.js";
10
+ import { runDCA } from "../strategies/dca.js";
11
+ import { runTrailingStop } from "../strategies/trailing-stop.js";
6
12
  export function registerBotCommands(program, getAdapter, getAdapterFor, isJson) {
7
13
  const bot = program.command("bot").description("Automated trading bots with condition monitoring & risk management");
8
14
  // ── bot start <config> ──
@@ -326,6 +332,8 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
326
332
  console.log(chalk.gray(` Usage: perp bot start ~/.perp/bots/eth-grid.yaml`));
327
333
  console.log(chalk.gray(` perp bot start ~/.perp/bots/eth-grid.yaml --background\n`));
328
334
  });
335
+ // Strategy subcommands: twap, funding-arb, grid, dca, trailing-stop
336
+ registerRunSubcommands(bot, getAdapter, getAdapterFor, isJson);
329
337
  }
330
338
  function makeLog() {
331
339
  return (msg) => {
@@ -410,3 +418,202 @@ const ARB_EXAMPLE = chalk.gray(` name: funding-arb
410
418
  risk:
411
419
  max_drawdown: 200
412
420
  max_daily_loss: 50`);
421
+ // ── Run subcommands (twap, funding-arb, grid, dca, trailing-stop) ──
422
+ function registerRunSubcommands(parent, getAdapter, getAdapterFor, isJson) {
423
+ const run = parent;
424
+ const twapCmd = run
425
+ .command("twap <symbol> <side> <size> <duration>")
426
+ .description("Run client-side TWAP (splits market orders over time)");
427
+ twapCmd
428
+ .option("-s, --slices <n>", "Number of slices (default: auto)")
429
+ .option("--job-id <id>", "Job ID (set automatically for background jobs)")
430
+ .action(async (symbol, side, size, duration, opts) => {
431
+ const s = side.toLowerCase();
432
+ if (s !== "buy" && s !== "sell") {
433
+ console.error(chalk.red("Side must be buy or sell"));
434
+ process.exit(1);
435
+ }
436
+ const adapter = await getAdapter();
437
+ const params = {
438
+ symbol: symbol.toUpperCase(),
439
+ side: s,
440
+ totalSize: parseFloat(size),
441
+ durationSec: parseInt(duration),
442
+ slices: opts.slices ? parseInt(opts.slices) : undefined,
443
+ };
444
+ const log = (msg) => {
445
+ const ts = new Date().toLocaleTimeString();
446
+ console.log(`${chalk.gray(ts)} ${msg}`);
447
+ };
448
+ const result = await runTWAP(adapter, params, opts.jobId, log);
449
+ if (isJson())
450
+ printJson(jsonOk(result));
451
+ if (opts.jobId) {
452
+ updateJobState(opts.jobId, { status: "done" });
453
+ }
454
+ });
455
+ run
456
+ .command("funding-arb")
457
+ .description("Run funding rate arbitrage monitor/executor")
458
+ .option("--min-spread <pct>", "Min annual spread % to enter", "10")
459
+ .option("--close-spread <pct>", "Close when spread drops below (%)", "5")
460
+ .option("--size <size>", "Position size per leg (base amount)", "0.01")
461
+ .option("--size-usd <usd>", "Position size per leg in USD (alternative)")
462
+ .option("--symbols <list>", "Comma-separated symbol filter")
463
+ .option("--interval <sec>", "Check interval in seconds", "60")
464
+ .option("--auto-execute", "Actually place trades (default: monitor only)")
465
+ .option("--max-positions <n>", "Max simultaneous positions", "3")
466
+ .option("--exchanges <list>", "Comma-separated exchanges to use", "pacifica,hyperliquid,lighter")
467
+ .option("--auto-rebalance", "Trigger rebalance alerts when exchange balance is low")
468
+ .option("--rebalance-threshold <usd>", "Rebalance when available < this ($)", "100")
469
+ .option("--max-drawdown <usd>", "Close all positions if total uPnL exceeds this loss ($)")
470
+ .option("--job-id <id>", "Job ID (set automatically for background jobs)")
471
+ .action(async (opts) => {
472
+ const exchangeNames = opts.exchanges.split(",").map((e) => e.trim());
473
+ const adapters = new Map();
474
+ for (const name of exchangeNames) {
475
+ try {
476
+ const a = await getAdapterFor(name);
477
+ adapters.set(name, a);
478
+ }
479
+ catch {
480
+ if (!isJson())
481
+ console.log(chalk.yellow(` Skipping ${name} (no credentials)`));
482
+ }
483
+ }
484
+ if (adapters.size < 2) {
485
+ console.error(chalk.red("Need at least 2 exchanges for arbitrage. Set credentials for 2+ exchanges."));
486
+ process.exit(1);
487
+ }
488
+ const params = {
489
+ minSpread: parseFloat(opts.minSpread),
490
+ closeSpread: parseFloat(opts.closeSpread),
491
+ size: opts.size,
492
+ sizeUsd: opts.sizeUsd ? parseFloat(opts.sizeUsd) : undefined,
493
+ symbols: opts.symbols ? opts.symbols.split(",").map((s) => s.trim().toUpperCase()) : undefined,
494
+ intervalSec: parseInt(opts.interval),
495
+ autoExecute: opts.autoExecute ?? false,
496
+ maxPositions: parseInt(opts.maxPositions),
497
+ autoRebalance: opts.autoRebalance ?? false,
498
+ rebalanceThreshold: parseFloat(opts.rebalanceThreshold),
499
+ maxDrawdown: opts.maxDrawdown ? parseFloat(opts.maxDrawdown) : undefined,
500
+ };
501
+ const log = (msg) => {
502
+ const ts = new Date().toLocaleTimeString();
503
+ console.log(`${chalk.gray(ts)} ${msg}`);
504
+ };
505
+ await runFundingArb(adapters, params, opts.jobId, log);
506
+ });
507
+ // ── Grid Bot ──
508
+ const gridCmd = run
509
+ .command("grid <symbol>")
510
+ .description("Run grid trading bot (places limit orders across a price range)");
511
+ gridCmd
512
+ .requiredOption("--upper <price>", "Upper price bound")
513
+ .requiredOption("--lower <price>", "Lower price bound")
514
+ .option("--grids <n>", "Number of grid lines", "10")
515
+ .option("--size <size>", "Total position size (base)", "0.1")
516
+ .option("--side <side>", "Grid bias: long, short, neutral", "neutral")
517
+ .option("--leverage <n>", "Leverage to set")
518
+ .option("--interval <sec>", "Check interval in seconds", "10")
519
+ .option("--max-runtime <sec>", "Max runtime in seconds (0 = forever)", "0")
520
+ .option("--trailing-stop <pct>", "Stop if equity drops by this % from peak")
521
+ .option("--job-id <id>", "Job ID (set automatically for background jobs)")
522
+ .action(async (symbol, opts) => {
523
+ const side = opts.side.toLowerCase();
524
+ if (side !== "long" && side !== "short" && side !== "neutral") {
525
+ console.error(chalk.red("Side must be long, short, or neutral"));
526
+ process.exit(1);
527
+ }
528
+ const adapter = await getAdapter();
529
+ const params = {
530
+ symbol: symbol.toUpperCase(),
531
+ side: side,
532
+ upperPrice: parseFloat(opts.upper),
533
+ lowerPrice: parseFloat(opts.lower),
534
+ grids: parseInt(opts.grids),
535
+ totalSize: parseFloat(opts.size),
536
+ leverage: opts.leverage ? parseInt(opts.leverage) : undefined,
537
+ intervalSec: parseInt(opts.interval),
538
+ maxRuntime: parseInt(opts.maxRuntime),
539
+ trailingStop: opts.trailingStop ? parseFloat(opts.trailingStop) : undefined,
540
+ };
541
+ const log = (msg) => {
542
+ const ts = new Date().toLocaleTimeString();
543
+ console.log(`${chalk.gray(ts)} ${msg}`);
544
+ };
545
+ const result = await runGrid(adapter, params, opts.jobId, log);
546
+ if (isJson())
547
+ printJson(jsonOk(result));
548
+ if (opts.jobId) {
549
+ updateJobState(opts.jobId, { status: "done" });
550
+ }
551
+ });
552
+ // ── DCA (Dollar Cost Averaging) ──
553
+ const dcaCmd = run
554
+ .command("dca <symbol> <side> <amount> <interval>")
555
+ .description("Run DCA strategy (periodic market orders at fixed intervals)");
556
+ dcaCmd
557
+ .option("--orders <n>", "Total number of orders (0 = unlimited)", "0")
558
+ .option("--price-limit <price>", "Stop buying above / selling below this price")
559
+ .option("--max-runtime <sec>", "Max runtime in seconds (0 = forever)", "0")
560
+ .option("--job-id <id>", "Job ID (set automatically for background jobs)")
561
+ .action(async (symbol, side, amount, interval, opts) => {
562
+ const s = side.toLowerCase();
563
+ if (s !== "buy" && s !== "sell") {
564
+ console.error(chalk.red("Side must be buy or sell"));
565
+ process.exit(1);
566
+ }
567
+ const adapter = await getAdapter();
568
+ const params = {
569
+ symbol: symbol.toUpperCase(),
570
+ side: s,
571
+ amountPerOrder: parseFloat(amount),
572
+ intervalSec: parseInt(interval),
573
+ totalOrders: parseInt(opts.orders),
574
+ priceLimit: opts.priceLimit ? parseFloat(opts.priceLimit) : undefined,
575
+ maxRuntime: parseInt(opts.maxRuntime),
576
+ };
577
+ const log = (msg) => {
578
+ const ts = new Date().toLocaleTimeString();
579
+ console.log(`${chalk.gray(ts)} ${msg}`);
580
+ };
581
+ const result = await runDCA(adapter, params, opts.jobId, log);
582
+ if (isJson())
583
+ printJson(jsonOk(result));
584
+ if (opts.jobId) {
585
+ updateJobState(opts.jobId, { status: "done" });
586
+ }
587
+ });
588
+ // ── Trailing Stop ──
589
+ const tsCmd = run
590
+ .command("trailing-stop <symbol>")
591
+ .description("Run client-side trailing stop (monitors price, closes position when trail % hit)");
592
+ tsCmd
593
+ .requiredOption("--trail <pct>", "Trail percentage")
594
+ .option("--interval <sec>", "Check interval in seconds", "5")
595
+ .option("--activation <price>", "Only start trailing after price reaches this level")
596
+ .option("--job-id <id>", "Job ID (set automatically for background jobs)")
597
+ .action(async (symbol, opts) => {
598
+ const sym = symbol.toUpperCase();
599
+ const trailPct = parseFloat(opts.trail);
600
+ const intervalSec = parseInt(opts.interval);
601
+ const activationPrice = opts.activation ? parseFloat(opts.activation) : undefined;
602
+ const adapter = await getAdapter();
603
+ const log = (msg) => {
604
+ const ts = new Date().toLocaleTimeString();
605
+ console.log(`${chalk.gray(ts)} ${msg}`);
606
+ };
607
+ const result = await runTrailingStop(adapter, {
608
+ symbol: sym,
609
+ trailPct,
610
+ intervalSec,
611
+ activationPrice,
612
+ }, opts.jobId, log);
613
+ if (isJson())
614
+ printJson(jsonOk(result));
615
+ if (opts.jobId) {
616
+ updateJobState(opts.jobId, { status: "done" });
617
+ }
618
+ });
619
+ }
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ import type { ExchangeAdapter } from "../exchanges/interface.js";
3
+ import type { Network } from "../pacifica/index.js";
4
+ export declare function registerFundsCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getNetwork: () => Network): void;