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.
- package/README.md +44 -5
- package/dist/arb/reconcile.d.ts +28 -0
- package/dist/arb/reconcile.js +162 -0
- package/dist/commands/account.d.ts +1 -1
- package/dist/commands/account.js +300 -63
- package/dist/commands/agent.js +9 -5
- package/dist/commands/arb/index.js +243 -49
- package/dist/commands/arb-auto.js +67 -19
- package/dist/commands/arb.d.ts +2 -1
- package/dist/commands/arb.js +535 -131
- package/dist/commands/bot.js +207 -0
- package/dist/commands/funds.d.ts +4 -0
- package/dist/commands/funds.js +741 -0
- package/dist/commands/history.d.ts +2 -1
- package/dist/commands/history.js +508 -5
- package/dist/commands/manage.js +5 -3
- package/dist/commands/market.js +34 -1
- package/dist/commands/pnl.js +1 -1
- package/dist/commands/risk.d.ts +2 -0
- package/dist/commands/risk.js +56 -0
- package/dist/commands/settings.js +147 -0
- package/dist/commands/trade.d.ts +1 -1
- package/dist/commands/trade.js +241 -259
- package/dist/commands/wallet.d.ts +4 -0
- package/dist/commands/wallet.js +10 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +11 -3
- package/dist/exchanges/hyperliquid-spot.d.ts +1 -0
- package/dist/exchanges/hyperliquid-spot.js +13 -21
- package/dist/exchanges/lighter-spot.js +13 -15
- package/dist/exchanges/lighter.js +82 -21
- package/dist/exchanges/pacifica.js +12 -0
- package/dist/execution-log.d.ts +1 -1
- package/dist/index.js +39 -123
- package/dist/mcp-server.js +43 -96
- package/dist/strategies/split-order.d.ts +49 -0
- package/dist/strategies/split-order.js +138 -0
- package/package.json +1 -1
- package/skills/perp-cli/SKILL.md +13 -26
- package/skills/perp-cli/references/agent-operations.md +19 -9
- package/skills/perp-cli/references/commands.md +38 -31
- package/skills/perp-cli/references/strategies.md +26 -16
- package/skills/perp-cli/scripts/funding-analysis.sh +2 -2
- package/skills/perp-cli/scripts/preflight.sh +1 -1
- package/skills/perp-cli/scripts/spot-perp-scan.sh +1 -1
package/dist/commands/bot.js
CHANGED
|
@@ -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;
|