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/mcp-server.js
CHANGED
|
@@ -318,6 +318,12 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
|
|
|
318
318
|
const symbol = extractSymbol(g) || "<symbol>";
|
|
319
319
|
steps.push({ step: 1, command: `perp -e ${ex} --json account positions`, description: "Check current position" }, { step: 2, command: `perp -e ${ex} --json trade stop ${symbol} <side> <stopPrice> <size>`, description: "Place stop order", dangerous: true });
|
|
320
320
|
}
|
|
321
|
+
else if (g.includes("split") || g.includes("depth") || (g.includes("large") && (g.includes("order") || g.includes("buy") || g.includes("sell")))) {
|
|
322
|
+
const symbol = extractSymbol(g) || "<symbol>";
|
|
323
|
+
const amount = extractNumber(g) || "<usd>";
|
|
324
|
+
const side = g.includes("sell") || g.includes("short") ? "sell" : "buy";
|
|
325
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: `Check ${symbol} orderbook depth` }, { step: 2, command: `perp -e ${ex} --json account info`, description: "Check available balance" }, { step: 3, command: `perp -e ${ex} --json trade split ${symbol} ${side} ${amount}`, description: `Split ${side} $${amount} ${symbol} into depth-based slices`, dangerous: true }, { step: 4, command: `perp -e ${ex} --json account positions`, description: "Verify position opened" });
|
|
326
|
+
}
|
|
321
327
|
else if (g.includes("twap")) {
|
|
322
328
|
const symbol = extractSymbol(g) || "<symbol>";
|
|
323
329
|
const size = extractNumber(g) || "<size>";
|
|
@@ -338,19 +344,16 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
|
|
|
338
344
|
else if (g.includes("funding")) {
|
|
339
345
|
const symbol = extractSymbol(g);
|
|
340
346
|
if (symbol) {
|
|
341
|
-
steps.push({ step: 1, command: `perp --json
|
|
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` });
|
|
342
348
|
}
|
|
343
349
|
else {
|
|
344
|
-
steps.push({ step: 1, command: "perp --json
|
|
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" });
|
|
345
351
|
}
|
|
346
352
|
}
|
|
347
353
|
else if (g.includes("bridge") || g.includes("transfer") || g.includes("cross-chain")) {
|
|
348
354
|
const amount = extractNumber(g) || "<amount>";
|
|
349
355
|
steps.push({ step: 1, command: "perp --json bridge chains", description: "List supported chains" }, { step: 2, command: `perp --json bridge quote --from <chain> --to <chain> --amount ${amount}`, description: "Get bridge quote with fees" }, { step: 3, command: `perp --json bridge send --from <chain> --to <chain> --amount ${amount}`, description: "Execute bridge transfer", dangerous: true }, { step: 4, command: "perp --json bridge status <orderId>", description: "Track bridge completion" });
|
|
350
356
|
}
|
|
351
|
-
else if (g.includes("alert") || g.includes("notify") || g.includes("alarm")) {
|
|
352
|
-
steps.push({ step: 1, command: "perp --json alert list", description: "List existing alerts" }, { step: 2, command: "perp --json alert add", description: "Add a new price/funding/pnl/liquidation alert" }, { step: 3, command: "perp --json alert daemon", description: "Start alert monitoring daemon" });
|
|
353
|
-
}
|
|
354
357
|
else if (g.includes("grid") || g.includes("dca") || g.includes("bot")) {
|
|
355
358
|
const symbol = extractSymbol(g) || "<symbol>";
|
|
356
359
|
if (g.includes("grid")) {
|
|
@@ -407,7 +410,7 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
|
|
|
407
410
|
steps.push({ step: 1, command: "perp --json jobs list", description: "List running background jobs" });
|
|
408
411
|
}
|
|
409
412
|
else if (g.includes("dex") || g.includes("hip-3") || g.includes("hip3")) {
|
|
410
|
-
steps.push({ step: 1, command: "perp --json
|
|
413
|
+
steps.push({ step: 1, command: "perp --json -e hl market hip3", description: "List HIP-3 deployed dexes" }, { step: 2, command: "perp --json -e hl --dex <name> market list", description: "Show markets on a specific dex" });
|
|
411
414
|
}
|
|
412
415
|
else if (g.includes("plan") || g.includes("composite") || g.includes("multi-step")) {
|
|
413
416
|
steps.push({ step: 1, command: "perp plan example", description: "Show example execution plan format" }, { step: 2, command: "perp --json plan validate <file>", description: "Validate plan file" }, { step: 3, command: "perp --json plan execute <file> --dry-run", description: "Dry-run the plan", dangerous: true });
|
|
@@ -417,11 +420,11 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
|
|
|
417
420
|
}
|
|
418
421
|
else if (g.includes("deposit")) {
|
|
419
422
|
const amount = extractNumber(g) || "<amount>";
|
|
420
|
-
steps.push({ step: 1, command: "perp --json wallet balance", description: "Check wallet balance" }, { step: 2, command: `perp --json deposit ${ex} ${amount}`, description: `Deposit $${amount} to ${ex}`, dangerous: true }, { step: 3, command: `perp -e ${ex} --json account info`, description: "Verify deposit arrived" });
|
|
423
|
+
steps.push({ step: 1, command: "perp --json wallet balance", description: "Check wallet balance" }, { step: 2, command: `perp --json funds deposit ${ex} ${amount}`, description: `Deposit $${amount} to ${ex}`, dangerous: true }, { step: 3, command: `perp -e ${ex} --json account info`, description: "Verify deposit arrived" });
|
|
421
424
|
}
|
|
422
425
|
else if (g.includes("withdraw")) {
|
|
423
426
|
const amount = extractNumber(g) || "<amount>";
|
|
424
|
-
steps.push({ step: 1, command: `perp -e ${ex} --json account info`, description: "Check available balance" }, { step: 2, command: `perp --json withdraw ${ex} ${amount}`, description: `Withdraw $${amount} from ${ex}`, dangerous: true }, { step: 3, command: "perp --json wallet balance", description: "Verify withdrawal received" });
|
|
427
|
+
steps.push({ step: 1, command: `perp -e ${ex} --json account info`, description: "Check available balance" }, { step: 2, command: `perp --json funds withdraw ${ex} ${amount}`, description: `Withdraw $${amount} from ${ex}`, dangerous: true }, { step: 3, command: "perp --json wallet balance", description: "Verify withdrawal received" });
|
|
425
428
|
}
|
|
426
429
|
else if (g.includes("leverage")) {
|
|
427
430
|
const symbol = extractSymbol(g) || "<symbol>";
|
|
@@ -450,7 +453,7 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
|
|
|
450
453
|
steps.push({ step: 1, command: "perp settings show", description: "Show current settings" }, { step: 2, command: "perp settings referrals on", description: "Enable referral codes (no extra fees, just volume tracking)" });
|
|
451
454
|
}
|
|
452
455
|
else {
|
|
453
|
-
steps.push({ step: 1, command: "perp agent capabilities", description: "List all available CLI capabilities" }, { step: 2, command: `perp -e ${ex} --json status`, description: "Check account status" }, { step: 3, command: "perp --json
|
|
456
|
+
steps.push({ step: 1, command: "perp agent capabilities", description: "List all available CLI capabilities" }, { step: 2, command: `perp -e ${ex} --json status`, description: "Check account status" }, { step: 3, command: "perp --json agent ping", description: "Check exchange connectivity" });
|
|
454
457
|
}
|
|
455
458
|
return {
|
|
456
459
|
content: [{
|
|
@@ -750,11 +753,12 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
|
|
|
750
753
|
relatedCommands: ["perp bridge chains", "perp bridge quote"],
|
|
751
754
|
};
|
|
752
755
|
}
|
|
753
|
-
else if (category === "
|
|
756
|
+
else if (category === "funds") {
|
|
757
|
+
const sub = args[1]; // deposit or withdraw
|
|
754
758
|
explanation = {
|
|
755
759
|
command,
|
|
756
|
-
description:
|
|
757
|
-
parameters: [{ name: "amount", value: args[
|
|
760
|
+
description: sub === "deposit" ? `Deposits USDC into ${args[2] || "an exchange"} account` : sub === "withdraw" ? `Withdraws USDC from ${args[2] || "an exchange"} account` : "Funds management (deposit, withdraw, bridge, transfer)",
|
|
761
|
+
parameters: [{ name: "amount", value: args[3] || args[2], description: "Amount in USDC" }],
|
|
758
762
|
risks: ["Moves real funds — verify amount before executing"],
|
|
759
763
|
category: "write",
|
|
760
764
|
relatedCommands: ["perp wallet balance", "perp account info"],
|
|
@@ -777,7 +781,7 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
|
|
|
777
781
|
parameters: [],
|
|
778
782
|
risks: [],
|
|
779
783
|
category: "read",
|
|
780
|
-
relatedCommands: ["perp
|
|
784
|
+
relatedCommands: ["perp risk status", "perp risk limits"],
|
|
781
785
|
};
|
|
782
786
|
}
|
|
783
787
|
else if (category === "bot" || category === "run") {
|
|
@@ -790,24 +794,14 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
|
|
|
790
794
|
relatedCommands: ["perp jobs list", "perp jobs stop"],
|
|
791
795
|
};
|
|
792
796
|
}
|
|
793
|
-
else if (category === "
|
|
794
|
-
explanation = {
|
|
795
|
-
command,
|
|
796
|
-
description: `Live WebSocket stream: ${sub}. Outputs NDJSON in real-time.`,
|
|
797
|
-
parameters: args.slice(2).map((a, i) => ({ name: `arg${i}`, value: a, description: "See --help" })),
|
|
798
|
-
risks: [],
|
|
799
|
-
category: "read",
|
|
800
|
-
relatedCommands: ["perp stream prices", "perp stream events"],
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
else if (category === "analytics") {
|
|
797
|
+
else if (category === "history") {
|
|
804
798
|
explanation = {
|
|
805
799
|
command,
|
|
806
|
-
description: { summary: "Trading performance summary", pnl: "P&L breakdown by symbol and time period", funding: "Funding payment history and totals", report: "Detailed performance report" }[sub] || `
|
|
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}`,
|
|
807
801
|
parameters: [],
|
|
808
802
|
risks: [],
|
|
809
803
|
category: "read",
|
|
810
|
-
relatedCommands: ["perp
|
|
804
|
+
relatedCommands: ["perp history summary", "perp history list"],
|
|
811
805
|
};
|
|
812
806
|
}
|
|
813
807
|
else if (category === "wallet") {
|
|
@@ -830,16 +824,6 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
|
|
|
830
824
|
relatedCommands: ["perp gap show", "perp arb scan"],
|
|
831
825
|
};
|
|
832
826
|
}
|
|
833
|
-
else if (category === "funding") {
|
|
834
|
-
explanation = {
|
|
835
|
-
command,
|
|
836
|
-
description: { rates: "Compare funding rates across exchanges", compare: "Compare a specific symbol's funding", spread: "Show funding rate spreads", history: "Funding rate history", monitor: "Live funding rate monitor" }[sub] || `Funding: ${sub}`,
|
|
837
|
-
parameters: [],
|
|
838
|
-
risks: [],
|
|
839
|
-
category: "read",
|
|
840
|
-
relatedCommands: ["perp funding rates", "perp arb scan"],
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
827
|
else if (category === "backtest") {
|
|
844
828
|
explanation = {
|
|
845
829
|
command,
|
|
@@ -861,7 +845,7 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
|
|
|
861
845
|
};
|
|
862
846
|
}
|
|
863
847
|
else {
|
|
864
|
-
const writeCommands = new Set(["trade", "
|
|
848
|
+
const writeCommands = new Set(["trade", "funds", "manage", "rebalance"]);
|
|
865
849
|
explanation = {
|
|
866
850
|
command,
|
|
867
851
|
description: `CLI command: ${command}. Run 'perp ${category} --help' for detailed usage.`,
|
|
@@ -1056,32 +1040,16 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
|
|
|
1056
1040
|
status: { usage: "perp bridge status <orderId>", description: "Track bridge status" },
|
|
1057
1041
|
},
|
|
1058
1042
|
},
|
|
1059
|
-
alert: {
|
|
1060
|
-
description: "Price, funding, PnL, and liquidation alerts",
|
|
1061
|
-
subcommands: {
|
|
1062
|
-
add: { usage: "perp alert add", description: "Add alert" },
|
|
1063
|
-
list: { usage: "perp alert list", description: "List alerts" },
|
|
1064
|
-
remove: { usage: "perp alert remove <id>", description: "Remove alert" },
|
|
1065
|
-
daemon: { usage: "perp alert daemon", description: "Start alert monitoring" },
|
|
1066
|
-
},
|
|
1067
|
-
},
|
|
1068
1043
|
bot: {
|
|
1069
1044
|
description: "Automated trading bots",
|
|
1070
1045
|
subcommands: {
|
|
1071
|
-
|
|
1046
|
+
twap: { usage: "perp bot twap <symbol> <side> <size> <duration>", description: "TWAP execution" },
|
|
1047
|
+
grid: { usage: "perp bot grid <symbol> --range <pct> --grids <n> --size <usd>", description: "Grid trading bot" },
|
|
1048
|
+
dca: { usage: "perp bot dca <symbol> <side> <amount> <interval>", description: "DCA bot" },
|
|
1049
|
+
"trailing-stop": { usage: "perp bot trailing-stop <symbol>", description: "Trailing stop bot" },
|
|
1072
1050
|
"quick-grid": { usage: "perp bot quick-grid <symbol>", description: "Quick grid bot" },
|
|
1073
1051
|
"quick-dca": { usage: "perp bot quick-dca <symbol> <side> <amount> <interval>", description: "Quick DCA bot" },
|
|
1074
1052
|
"quick-arb": { usage: "perp bot quick-arb", description: "Quick arb bot" },
|
|
1075
|
-
"preset-list": { usage: "perp bot preset-list", description: "List bot presets" },
|
|
1076
|
-
},
|
|
1077
|
-
},
|
|
1078
|
-
stream: {
|
|
1079
|
-
description: "Live WebSocket streams (NDJSON output)",
|
|
1080
|
-
subcommands: {
|
|
1081
|
-
prices: { usage: "perp stream prices", description: "Live prices" },
|
|
1082
|
-
book: { usage: "perp stream book <symbol>", description: "Live orderbook" },
|
|
1083
|
-
trades: { usage: "perp stream trades <symbol>", description: "Live trades" },
|
|
1084
|
-
events: { usage: "perp stream events", description: "Account events (fills, liquidations)" },
|
|
1085
1053
|
},
|
|
1086
1054
|
},
|
|
1087
1055
|
wallet: {
|
|
@@ -1092,37 +1060,15 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
|
|
|
1092
1060
|
generate: { usage: "perp wallet generate solana|evm", description: "Generate new wallet" },
|
|
1093
1061
|
},
|
|
1094
1062
|
},
|
|
1095
|
-
analytics: {
|
|
1096
|
-
description: "Trading performance analytics",
|
|
1097
|
-
subcommands: {
|
|
1098
|
-
summary: { usage: "perp analytics summary", description: "Performance summary" },
|
|
1099
|
-
pnl: { usage: "perp analytics pnl", description: "P&L breakdown" },
|
|
1100
|
-
funding: { usage: "perp analytics funding", description: "Funding payment history" },
|
|
1101
|
-
report: { usage: "perp analytics report --since <period>", description: "Detailed report" },
|
|
1102
|
-
},
|
|
1103
|
-
},
|
|
1104
1063
|
history: {
|
|
1105
|
-
description: "Execution log & audit trail",
|
|
1064
|
+
description: "Execution log, performance & audit trail",
|
|
1106
1065
|
subcommands: {
|
|
1107
1066
|
list: { usage: "perp history list", description: "Execution log" },
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
description: "Funding rate comparison",
|
|
1114
|
-
subcommands: {
|
|
1115
|
-
rates: { usage: "perp funding rates", description: "Rates across exchanges" },
|
|
1116
|
-
compare: { usage: "perp funding compare <symbol>", description: "Compare symbol's funding" },
|
|
1117
|
-
spread: { usage: "perp funding spread", description: "Funding rate spreads" },
|
|
1118
|
-
monitor: { usage: "perp funding monitor", description: "Live funding monitor" },
|
|
1119
|
-
},
|
|
1120
|
-
},
|
|
1121
|
-
gap: {
|
|
1122
|
-
description: "Cross-exchange price gap analysis",
|
|
1123
|
-
subcommands: {
|
|
1124
|
-
show: { usage: "perp gap show", description: "Current price gaps" },
|
|
1125
|
-
watch: { usage: "perp gap watch --min <pct>", description: "Live gap monitor" },
|
|
1067
|
+
summary: { usage: "perp history summary", description: "Performance summary" },
|
|
1068
|
+
pnl: { usage: "perp history pnl", description: "P&L breakdown" },
|
|
1069
|
+
funding: { usage: "perp history funding", description: "Funding payment history" },
|
|
1070
|
+
report: { usage: "perp history report", description: "Full performance report" },
|
|
1071
|
+
perf: { usage: "perp history perf --period daily|weekly|summary", description: "Performance breakdown" },
|
|
1126
1072
|
},
|
|
1127
1073
|
},
|
|
1128
1074
|
backtest: {
|
|
@@ -1132,14 +1078,6 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
|
|
|
1132
1078
|
grid: { usage: "perp backtest grid", description: "Backtest grid strategy" },
|
|
1133
1079
|
},
|
|
1134
1080
|
},
|
|
1135
|
-
dex: {
|
|
1136
|
-
description: "HIP-3 deployed perp dexes (Hyperliquid)",
|
|
1137
|
-
subcommands: {
|
|
1138
|
-
list: { usage: "perp dex list", description: "List dexes" },
|
|
1139
|
-
markets: { usage: "perp dex markets <name>", description: "Dex markets" },
|
|
1140
|
-
balance: { usage: "perp dex balance <name>", description: "Dex balance" },
|
|
1141
|
-
},
|
|
1142
|
-
},
|
|
1143
1081
|
plan: {
|
|
1144
1082
|
description: "Composite multi-step execution plans",
|
|
1145
1083
|
subcommands: {
|
|
@@ -1164,11 +1102,20 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
|
|
|
1164
1102
|
logs: { usage: "perp jobs logs <id>", description: "View job logs" },
|
|
1165
1103
|
},
|
|
1166
1104
|
},
|
|
1167
|
-
|
|
1168
|
-
|
|
1105
|
+
funds: {
|
|
1106
|
+
usage: "perp funds <deposit|withdraw|transfer|bridge> ...",
|
|
1107
|
+
description: "Deposit, withdraw, bridge & transfer funds",
|
|
1108
|
+
subcommands: {
|
|
1109
|
+
"deposit <exchange> <amount>": "Deposit USDC to exchange",
|
|
1110
|
+
"withdraw <exchange> <amount>": "Withdraw USDC from exchange",
|
|
1111
|
+
"transfer <amount> <address>": "HL internal transfer",
|
|
1112
|
+
"bridge --from <chain> --to <chain> --amount <n> --recipient <addr>": "CCTP bridge",
|
|
1113
|
+
info: "Show all deposit/withdraw routes & limits",
|
|
1114
|
+
},
|
|
1115
|
+
},
|
|
1169
1116
|
portfolio: { usage: "perp portfolio", description: "Cross-exchange portfolio summary" },
|
|
1170
1117
|
status: { usage: "perp portfolio", description: "Full account overview" },
|
|
1171
|
-
health: { usage: "perp
|
|
1118
|
+
health: { usage: "perp agent ping", description: "Exchange connectivity check" },
|
|
1172
1119
|
settings: {
|
|
1173
1120
|
description: "CLI settings",
|
|
1174
1121
|
subcommands: {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orderbook-aware split order execution.
|
|
3
|
+
*
|
|
4
|
+
* Unlike TWAP (time-based splitting), this strategy reads the orderbook
|
|
5
|
+
* before each slice and calculates optimal slice sizes based on available
|
|
6
|
+
* depth. Each slice is placed as an IOC limit order at the worst acceptable
|
|
7
|
+
* price within the slippage tolerance, ensuring no sweep beyond the target.
|
|
8
|
+
*
|
|
9
|
+
* Flow per slice:
|
|
10
|
+
* 1. Fetch fresh orderbook
|
|
11
|
+
* 2. Walk levels via computeExecutableSize to find fillable amount
|
|
12
|
+
* 3. Place IOC limit at best price + slippage tolerance
|
|
13
|
+
* 4. Wait for book to refresh, then repeat
|
|
14
|
+
*/
|
|
15
|
+
import type { ExchangeAdapter } from "../exchanges/interface.js";
|
|
16
|
+
export interface SplitOrderParams {
|
|
17
|
+
symbol: string;
|
|
18
|
+
side: "buy" | "sell";
|
|
19
|
+
totalSizeUsd: number;
|
|
20
|
+
maxSlippagePct?: number;
|
|
21
|
+
maxSlices?: number;
|
|
22
|
+
delayMs?: number;
|
|
23
|
+
minSliceUsd?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface SplitSlice {
|
|
26
|
+
index: number;
|
|
27
|
+
size: string;
|
|
28
|
+
price: string;
|
|
29
|
+
notionalUsd: number;
|
|
30
|
+
slippagePct: number;
|
|
31
|
+
method: "limit_ioc" | "market";
|
|
32
|
+
status: "filled" | "partial" | "failed";
|
|
33
|
+
}
|
|
34
|
+
export interface SplitOrderResult {
|
|
35
|
+
symbol: string;
|
|
36
|
+
side: "buy" | "sell";
|
|
37
|
+
requestedUsd: number;
|
|
38
|
+
filledUsd: number;
|
|
39
|
+
filledSize: number;
|
|
40
|
+
avgPrice: number;
|
|
41
|
+
totalSlippagePct: number;
|
|
42
|
+
slices: SplitSlice[];
|
|
43
|
+
status: "complete" | "partial" | "failed";
|
|
44
|
+
runtime: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Execute a split order: orderbook-aware slicing with IOC limits.
|
|
48
|
+
*/
|
|
49
|
+
export declare function runSplitOrder(adapter: ExchangeAdapter, params: SplitOrderParams, log?: (msg: string) => void): Promise<SplitOrderResult>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orderbook-aware split order execution.
|
|
3
|
+
*
|
|
4
|
+
* Unlike TWAP (time-based splitting), this strategy reads the orderbook
|
|
5
|
+
* before each slice and calculates optimal slice sizes based on available
|
|
6
|
+
* depth. Each slice is placed as an IOC limit order at the worst acceptable
|
|
7
|
+
* price within the slippage tolerance, ensuring no sweep beyond the target.
|
|
8
|
+
*
|
|
9
|
+
* Flow per slice:
|
|
10
|
+
* 1. Fetch fresh orderbook
|
|
11
|
+
* 2. Walk levels via computeExecutableSize to find fillable amount
|
|
12
|
+
* 3. Place IOC limit at best price + slippage tolerance
|
|
13
|
+
* 4. Wait for book to refresh, then repeat
|
|
14
|
+
*/
|
|
15
|
+
import { computeExecutableSize } from "../liquidity.js";
|
|
16
|
+
import { logExecution } from "../execution-log.js";
|
|
17
|
+
/**
|
|
18
|
+
* Infer price precision (decimal places) from an orderbook price string.
|
|
19
|
+
*/
|
|
20
|
+
function pricePrecision(priceStr) {
|
|
21
|
+
return priceStr.includes(".") ? priceStr.split(".")[1].length : 0;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Execute a split order: orderbook-aware slicing with IOC limits.
|
|
25
|
+
*/
|
|
26
|
+
export async function runSplitOrder(adapter, params, log = console.log) {
|
|
27
|
+
const { symbol, side, totalSizeUsd, maxSlippagePct = 0.3, maxSlices = 10, delayMs = 1000, minSliceUsd = 100, } = params;
|
|
28
|
+
const startedAt = Date.now();
|
|
29
|
+
const slices = [];
|
|
30
|
+
let remainingUsd = totalSizeUsd;
|
|
31
|
+
let totalFilledSize = 0;
|
|
32
|
+
let totalFilledNotional = 0;
|
|
33
|
+
// Capture initial best price for overall slippage calculation
|
|
34
|
+
let initialBestPrice = 0;
|
|
35
|
+
try {
|
|
36
|
+
const initialBook = await adapter.getOrderbook(symbol);
|
|
37
|
+
const initialLevels = side === "buy" ? initialBook.asks : initialBook.bids;
|
|
38
|
+
if (initialLevels.length > 0) {
|
|
39
|
+
initialBestPrice = Number(initialLevels[0][0]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Will be recaptured on first slice
|
|
44
|
+
}
|
|
45
|
+
log(`[SPLIT] ${side.toUpperCase()} $${totalSizeUsd.toFixed(0)} of ${symbol} | max slippage: ${maxSlippagePct}% | max slices: ${maxSlices}`);
|
|
46
|
+
for (let i = 0; i < maxSlices && remainingUsd >= minSliceUsd; i++) {
|
|
47
|
+
// 1. Fetch fresh orderbook
|
|
48
|
+
const book = await adapter.getOrderbook(symbol);
|
|
49
|
+
const levels = side === "buy" ? book.asks : book.bids;
|
|
50
|
+
if (levels.length === 0) {
|
|
51
|
+
log(`[SPLIT] No ${side === "buy" ? "asks" : "bids"} in orderbook, stopping`);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
// Capture initial best if we missed it
|
|
55
|
+
if (initialBestPrice === 0) {
|
|
56
|
+
initialBestPrice = Number(levels[0][0]);
|
|
57
|
+
}
|
|
58
|
+
// 2. Calculate how much we can execute within slippage on this depth
|
|
59
|
+
const check = computeExecutableSize(levels, remainingUsd, maxSlippagePct);
|
|
60
|
+
if (check.maxSize <= 0) {
|
|
61
|
+
log(`[SPLIT] Insufficient liquidity within ${maxSlippagePct}% slippage, stopping`);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
// Slice size: what we can fill within slippage, capped by remaining
|
|
65
|
+
const sliceNotional = Math.min(check.maxSize * check.avgFillPrice, remainingUsd);
|
|
66
|
+
const sliceSize = sliceNotional / check.avgFillPrice;
|
|
67
|
+
// 3. Calculate IOC limit price (worst acceptable within slippage)
|
|
68
|
+
const bestPrice = Number(levels[0][0]);
|
|
69
|
+
const limitPrice = side === "buy"
|
|
70
|
+
? bestPrice * (1 + maxSlippagePct / 100)
|
|
71
|
+
: bestPrice * (1 - maxSlippagePct / 100);
|
|
72
|
+
const decimals = pricePrecision(levels[0][0]);
|
|
73
|
+
const formattedPrice = limitPrice.toFixed(decimals);
|
|
74
|
+
const formattedSize = sliceSize.toFixed(6);
|
|
75
|
+
log(`[SPLIT] Slice ${i + 1}: ${formattedSize} @ limit $${formattedPrice} (~$${sliceNotional.toFixed(0)}) | depth: $${check.depthUsd.toFixed(0)}`);
|
|
76
|
+
// 4. Execute IOC limit order
|
|
77
|
+
let slice;
|
|
78
|
+
try {
|
|
79
|
+
await adapter.limitOrder(symbol, side, formattedPrice, formattedSize, { tif: "IOC" });
|
|
80
|
+
slice = {
|
|
81
|
+
index: i,
|
|
82
|
+
size: formattedSize,
|
|
83
|
+
price: formattedPrice,
|
|
84
|
+
notionalUsd: sliceNotional,
|
|
85
|
+
slippagePct: check.slippagePct,
|
|
86
|
+
method: "limit_ioc",
|
|
87
|
+
status: "filled",
|
|
88
|
+
};
|
|
89
|
+
totalFilledSize += sliceSize;
|
|
90
|
+
totalFilledNotional += sliceNotional;
|
|
91
|
+
remainingUsd -= sliceNotional;
|
|
92
|
+
logExecution({
|
|
93
|
+
type: "limit_order", exchange: adapter.name, symbol,
|
|
94
|
+
side, size: formattedSize, price: formattedPrice,
|
|
95
|
+
notional: sliceNotional, status: "success", dryRun: false,
|
|
96
|
+
meta: { action: "split-order", slice: i + 1, totalSlices: maxSlices },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
log(`[SPLIT] Slice ${i + 1} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
101
|
+
slice = {
|
|
102
|
+
index: i, size: formattedSize, price: formattedPrice,
|
|
103
|
+
notionalUsd: 0, slippagePct: 0, method: "limit_ioc", status: "failed",
|
|
104
|
+
};
|
|
105
|
+
logExecution({
|
|
106
|
+
type: "limit_order", exchange: adapter.name, symbol,
|
|
107
|
+
side, size: formattedSize, price: formattedPrice,
|
|
108
|
+
notional: 0, status: "failed", dryRun: false,
|
|
109
|
+
error: err instanceof Error ? err.message : String(err),
|
|
110
|
+
meta: { action: "split-order", slice: i + 1, totalSlices: maxSlices },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
slices.push(slice);
|
|
114
|
+
// 5. Delay before next slice (let the book refresh)
|
|
115
|
+
if (remainingUsd >= minSliceUsd && i < maxSlices - 1) {
|
|
116
|
+
await new Promise(r => setTimeout(r, delayMs));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const avgPrice = totalFilledSize > 0 ? totalFilledNotional / totalFilledSize : 0;
|
|
120
|
+
const totalSlippage = initialBestPrice > 0
|
|
121
|
+
? Math.abs((avgPrice - initialBestPrice) / initialBestPrice) * 100
|
|
122
|
+
: 0;
|
|
123
|
+
const status = totalFilledNotional >= totalSizeUsd * 0.95 ? "complete"
|
|
124
|
+
: totalFilledNotional > 0 ? "partial"
|
|
125
|
+
: "failed";
|
|
126
|
+
log(`[SPLIT] Done: ${slices.length} slices | filled $${totalFilledNotional.toFixed(0)}/$${totalSizeUsd.toFixed(0)} | avg price: $${avgPrice.toFixed(2)} | slippage: ${totalSlippage.toFixed(3)}%`);
|
|
127
|
+
return {
|
|
128
|
+
symbol, side,
|
|
129
|
+
requestedUsd: totalSizeUsd,
|
|
130
|
+
filledUsd: totalFilledNotional,
|
|
131
|
+
filledSize: totalFilledSize,
|
|
132
|
+
avgPrice,
|
|
133
|
+
totalSlippagePct: totalSlippage,
|
|
134
|
+
slices,
|
|
135
|
+
status,
|
|
136
|
+
runtime: (Date.now() - startedAt) / 1000,
|
|
137
|
+
};
|
|
138
|
+
}
|
package/package.json
CHANGED
package/skills/perp-cli/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: perp-cli
|
|
3
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)."
|
|
4
|
-
allowed-tools: "Bash(perp:*), Bash(npx perp-cli:*), Bash(npx -y perp-cli:*)
|
|
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.
|
|
8
|
+
version: "0.4.9"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# perp-cli Agent Guide
|
|
@@ -60,6 +60,8 @@ perp --json arb exec <SYM> <longEx> <shortEx> <$> --leverage <N> --isolated # e
|
|
|
60
60
|
# Single exchange trading (when not doing arb)
|
|
61
61
|
perp --json -e <EX> trade market <SYM> buy <SIZE>
|
|
62
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)
|
|
63
65
|
perp --json -e <EX> trade close <SYM>
|
|
64
66
|
perp --json -e <EX> trade close <SYM> --smart # smart close at best bid/ask
|
|
65
67
|
|
|
@@ -96,6 +98,12 @@ perp --json -e <EX> trade market <SYM> buy <SIZE> --smart
|
|
|
96
98
|
perp --json -e <EX> trade close <SYM> --smart
|
|
97
99
|
perp --json arb exec <SYM> <longEx> <shortEx> <$> --smart # smart arb entry
|
|
98
100
|
perp --json arb close <SYM> --smart # smart arb close
|
|
101
|
+
|
|
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
|
|
99
107
|
```
|
|
100
108
|
|
|
101
109
|
All string outputs are auto-sanitized (control chars stripped, prompt injection patterns blocked).
|
|
@@ -198,7 +206,7 @@ If `error.retryable` is `false`, do NOT retry — fix the cause first.
|
|
|
198
206
|
|
|
199
207
|
| Error | Action |
|
|
200
208
|
|-------|--------|
|
|
201
|
-
| `RATE_LIMITED` | wait
|
|
209
|
+
| `RATE_LIMITED` | wait 5-10s, retry (max 3). Lighter rate limits are strict — space commands 3s+ apart |
|
|
202
210
|
| `EXCHANGE_UNREACHABLE` | wait 10s, retry. 3x fail → skip that exchange |
|
|
203
211
|
| `TIMEOUT` | wait 5s, retry (max 3) |
|
|
204
212
|
| `INSUFFICIENT_BALANCE` | reduce size or bridge funds to that exchange |
|
|
@@ -208,6 +216,8 @@ If `error.retryable` is `false`, do NOT retry — fix the cause first.
|
|
|
208
216
|
| `MARGIN_INSUFFICIENT` | reduce leverage or close existing positions |
|
|
209
217
|
| `DUPLICATE_ORDER` | already submitted — check positions, don't retry |
|
|
210
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`) |
|
|
211
221
|
|
|
212
222
|
## Examples
|
|
213
223
|
|
|
@@ -254,29 +264,6 @@ perp --json settings referrals off # disable anytime
|
|
|
254
264
|
When setting up wallets for the first time, ask the user:
|
|
255
265
|
> "perp-cli 개발을 지원하는 레퍼럴 기능을 켜시겠어요? 추가 수수료 없이 거래소 리베이트만 활용됩니다."
|
|
256
266
|
|
|
257
|
-
## Scripts
|
|
258
|
-
|
|
259
|
-
Executable automation scripts in `scripts/`. Run with `bash scripts/<name>.sh`.
|
|
260
|
-
Scripts auto-detect `perp` (global install) or fall back to `npx -y perp-cli@latest` — no global install required.
|
|
261
|
-
|
|
262
|
-
| Script | Purpose | Usage |
|
|
263
|
-
|--------|---------|-------|
|
|
264
|
-
| `preflight.sh` | Pre-trade validation (install, wallet, connectivity, balance, risk) | `bash scripts/preflight.sh --json` |
|
|
265
|
-
| `validate-arb.sh` | Validate arb opportunity before execution (price, liquidity, balance, risk, dry-run) | `bash scripts/validate-arb.sh ETH hl lt 100 --leverage 2` |
|
|
266
|
-
| `spot-perp-scan.sh` | Combined spot+perp and perp-perp scan with portfolio context | `bash scripts/spot-perp-scan.sh --json --mode all --min 10` |
|
|
267
|
-
| `arb-monitor.sh` | Monitor open arb positions (PnL, funding, liquidation distance) | `bash scripts/arb-monitor.sh --json --min-spread 5` |
|
|
268
|
-
| `funding-analysis.sh` | Funding rate analysis across all exchanges | `bash scripts/funding-analysis.sh --json --symbol ETH` |
|
|
269
|
-
|
|
270
|
-
All scripts support `--json` for structured output. Use in automation pipelines:
|
|
271
|
-
```bash
|
|
272
|
-
# Full arb workflow with validation
|
|
273
|
-
bash scripts/preflight.sh --json # 1. system ready?
|
|
274
|
-
bash scripts/spot-perp-scan.sh --json # 2. find opportunities
|
|
275
|
-
bash scripts/validate-arb.sh ETH hl lt 100 # 3. validate before exec
|
|
276
|
-
# 4. [user confirms] → perp --json arb exec ...
|
|
277
|
-
bash scripts/arb-monitor.sh --json # 5. monitor positions
|
|
278
|
-
```
|
|
279
|
-
|
|
280
267
|
## References
|
|
281
268
|
|
|
282
269
|
- `references/commands.md` — full command reference
|
|
@@ -123,27 +123,28 @@ perp --json -e hl account info # USDC deposited on exchange
|
|
|
123
123
|
### Deposit to Exchange
|
|
124
124
|
```bash
|
|
125
125
|
# Hyperliquid (from Arbitrum wallet)
|
|
126
|
-
perp --json deposit hyperliquid 100
|
|
126
|
+
perp --json funds deposit hyperliquid 100
|
|
127
127
|
|
|
128
128
|
# Pacifica (from Solana wallet)
|
|
129
|
-
perp --json deposit pacifica 100
|
|
129
|
+
perp --json funds deposit pacifica 100
|
|
130
130
|
|
|
131
131
|
# Lighter (multiple routes)
|
|
132
|
-
perp --json deposit lighter info # show all available routes
|
|
133
|
-
perp --json deposit lighter cctp
|
|
132
|
+
perp --json funds deposit lighter info # show all available routes
|
|
133
|
+
perp --json funds deposit lighter cctp arbitrum 100 # via CCTP from Arbitrum
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
### Withdraw from Exchange
|
|
137
137
|
```bash
|
|
138
|
-
perp --json withdraw hyperliquid 100
|
|
139
|
-
perp --json withdraw pacifica 100
|
|
138
|
+
perp --json funds withdraw hyperliquid 100
|
|
139
|
+
perp --json funds withdraw pacifica 100
|
|
140
|
+
perp --json funds withdraw lighter 100
|
|
140
141
|
```
|
|
141
142
|
|
|
142
143
|
### Bridge Between Chains
|
|
143
144
|
When you need to move USDC between exchanges on different chains:
|
|
144
145
|
```bash
|
|
145
146
|
# 1. Withdraw from source exchange
|
|
146
|
-
perp --json withdraw pacifica 500
|
|
147
|
+
perp --json funds withdraw pacifica 500
|
|
147
148
|
|
|
148
149
|
# 2. Quote the bridge
|
|
149
150
|
perp --json bridge quote --from solana --to arbitrum --amount 500
|
|
@@ -155,7 +156,7 @@ perp --json bridge send --from solana --to arbitrum --amount 500
|
|
|
155
156
|
perp --json bridge status <orderId>
|
|
156
157
|
|
|
157
158
|
# 5. Deposit to destination exchange
|
|
158
|
-
perp --json deposit hyperliquid 500
|
|
159
|
+
perp --json funds deposit hyperliquid 500
|
|
159
160
|
```
|
|
160
161
|
|
|
161
162
|
## Order Types & Execution
|
|
@@ -168,6 +169,15 @@ perp --json -e hl trade buy BTC 0.01 # shorthand
|
|
|
168
169
|
perp --json -e hl trade sell BTC 0.01 # shorthand
|
|
169
170
|
```
|
|
170
171
|
|
|
172
|
+
### Split Orders (orderbook-aware, for large orders)
|
|
173
|
+
```bash
|
|
174
|
+
perp --json -e hl trade split BTC buy 5000 # split $5000 into depth-based slices
|
|
175
|
+
perp --json -e hl trade split BTC sell 10000 --max-slippage 0.5 --max-slices 5
|
|
176
|
+
perp --json -e hl trade market BTC buy 0.5 --split # split via market command flag
|
|
177
|
+
```
|
|
178
|
+
Returns `{ slices[], filledUsd, avgPrice, totalSlippagePct, status }`.
|
|
179
|
+
Status: `complete` (all filled), `partial` (some filled), `failed` (none filled).
|
|
180
|
+
|
|
171
181
|
### Limit Orders (execute at specific price)
|
|
172
182
|
```bash
|
|
173
183
|
perp --json -e hl trade buy BTC 0.01 -p 60000 # buy at $60,000
|
|
@@ -238,7 +248,7 @@ Error case:
|
|
|
238
248
|
- `trade market`, `trade buy`, `trade sell` — will open duplicate positions
|
|
239
249
|
- `trade close` — may error if already closed, but harmless
|
|
240
250
|
- `bridge send` — will send duplicate transfers
|
|
241
|
-
- `deposit`, `withdraw` — will move funds twice
|
|
251
|
+
- `funds deposit`, `funds withdraw` — will move funds twice
|
|
242
252
|
|
|
243
253
|
**For non-idempotent commands:** always verify the result before retrying. Check positions or balances to confirm whether the first attempt succeeded.
|
|
244
254
|
|