perp-cli 0.4.0 → 0.4.8

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 (79) hide show
  1. package/README.md +2 -0
  2. package/dist/arb/sizing.d.ts +12 -1
  3. package/dist/arb/sizing.js +38 -5
  4. package/dist/arb/state.d.ts +6 -0
  5. package/dist/arb/utils.d.ts +2 -0
  6. package/dist/arb/utils.js +1 -1
  7. package/dist/cli-spec.d.ts +0 -4
  8. package/dist/cli-spec.js +8 -1
  9. package/dist/commands/agent.js +12 -9
  10. package/dist/commands/arb/index.js +18 -3
  11. package/dist/commands/arb-auto.js +885 -67
  12. package/dist/commands/dex.js +7 -1
  13. package/dist/commands/funding.d.ts +2 -1
  14. package/dist/commands/funding.js +211 -4
  15. package/dist/commands/manage.js +1 -1
  16. package/dist/commands/market.js +1 -1
  17. package/dist/commands/multileg.d.ts +16 -0
  18. package/dist/commands/multileg.js +174 -0
  19. package/dist/commands/pnl.d.ts +12 -0
  20. package/dist/commands/pnl.js +243 -0
  21. package/dist/commands/settings.d.ts +2 -1
  22. package/dist/commands/settings.js +133 -2
  23. package/dist/commands/stream.js +132 -55
  24. package/dist/commands/trade.js +118 -43
  25. package/dist/commands/wallet.js +1 -1
  26. package/dist/config.d.ts +5 -0
  27. package/dist/config.js +12 -0
  28. package/dist/constants.d.ts +14 -0
  29. package/dist/constants.js +40 -0
  30. package/dist/dashboard/ws-feeds.js +1 -1
  31. package/dist/equity-tracker.d.ts +57 -0
  32. package/dist/equity-tracker.js +209 -0
  33. package/dist/exchanges/hyperliquid-spot.d.ts +68 -0
  34. package/dist/exchanges/hyperliquid-spot.js +317 -0
  35. package/dist/exchanges/hyperliquid.d.ts +26 -5
  36. package/dist/exchanges/hyperliquid.js +181 -92
  37. package/dist/exchanges/interface.d.ts +1 -1
  38. package/dist/exchanges/lighter-spot.d.ts +55 -0
  39. package/dist/exchanges/lighter-spot.js +417 -0
  40. package/dist/exchanges/lighter.d.ts +10 -2
  41. package/dist/exchanges/lighter.js +132 -37
  42. package/dist/exchanges/pacifica.d.ts +11 -4
  43. package/dist/exchanges/pacifica.js +52 -37
  44. package/dist/exchanges/spot-interface.d.ts +45 -0
  45. package/dist/exchanges/spot-interface.js +22 -0
  46. package/dist/execution-log.d.ts +2 -2
  47. package/dist/funding/rates.d.ts +36 -0
  48. package/dist/funding/rates.js +146 -0
  49. package/dist/index.js +148 -99
  50. package/dist/liquidity.d.ts +12 -0
  51. package/dist/liquidity.js +43 -0
  52. package/dist/settings.d.ts +6 -0
  53. package/dist/settings.js +18 -2
  54. package/dist/signer/evm-local.d.ts +12 -0
  55. package/dist/signer/evm-local.js +19 -0
  56. package/dist/signer/index.d.ts +3 -0
  57. package/dist/signer/index.js +2 -0
  58. package/dist/signer/interface.d.ts +31 -0
  59. package/dist/signer/interface.js +7 -0
  60. package/dist/signer/solana-local.d.ts +13 -0
  61. package/dist/signer/solana-local.js +19 -0
  62. package/dist/smart-order.d.ts +44 -0
  63. package/dist/smart-order.js +145 -0
  64. package/dist/trade-validator.js +2 -1
  65. package/dist/utils.js +7 -1
  66. package/dist/ws/market-feed.d.ts +74 -0
  67. package/dist/ws/market-feed.js +227 -0
  68. package/package.json +1 -1
  69. package/skills/perp-cli/SKILL.md +102 -4
  70. package/skills/perp-cli/references/commands.md +16 -1
  71. package/skills/perp-cli/references/strategies.md +1 -1
  72. package/skills/perp-cli/scripts/arb-monitor.sh +74 -0
  73. package/skills/perp-cli/scripts/funding-analysis.sh +85 -0
  74. package/skills/perp-cli/scripts/preflight.sh +98 -0
  75. package/skills/perp-cli/scripts/spot-perp-scan.sh +83 -0
  76. package/skills/perp-cli/scripts/validate-arb.sh +112 -0
  77. package/skills/perp-cli/templates/arb-scan.sh +0 -15
  78. package/skills/perp-cli/templates/open-position.sh +0 -38
  79. package/skills/perp-cli/templates/wallet-setup.sh +0 -17
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Unified market data feeds for CLI streaming commands.
3
+ *
4
+ * - Hyperliquid: native WebSocket (allMids, l2Book, trades, candle)
5
+ * - Lighter: REST polling (no public market data WS)
6
+ * - Pacifica: handled via existing PacificaWSClient in stream.ts
7
+ */
8
+ import { EventEmitter } from "events";
9
+ // ── Hyperliquid Market Feed (native WebSocket) ──
10
+ export class HyperliquidMarketFeed extends EventEmitter {
11
+ ws = null;
12
+ closed = false;
13
+ reconnectTimer = null;
14
+ subs = [];
15
+ get wsUrl() { return "wss://api.hyperliquid.xyz/ws"; }
16
+ async connect() {
17
+ const { WebSocket } = await import("ws");
18
+ return new Promise((resolve, reject) => {
19
+ this.ws = new WebSocket(this.wsUrl);
20
+ this.ws.on("open", () => {
21
+ // Re-subscribe on reconnect
22
+ for (const sub of this.subs)
23
+ this.send({ method: "subscribe", subscription: sub });
24
+ this.emit("connected");
25
+ resolve();
26
+ });
27
+ this.ws.on("error", (err) => {
28
+ if (!this.closed)
29
+ reject(err);
30
+ });
31
+ this.ws.on("close", () => {
32
+ if (!this.closed) {
33
+ this.emit("disconnected");
34
+ this.scheduleReconnect();
35
+ }
36
+ });
37
+ this.ws.on("message", (raw) => {
38
+ try {
39
+ this.handleMessage(JSON.parse(String(raw)));
40
+ }
41
+ catch { /* ignore */ }
42
+ });
43
+ });
44
+ }
45
+ send(data) {
46
+ if (this.ws?.readyState === 1)
47
+ this.ws.send(JSON.stringify(data));
48
+ }
49
+ sub(subscription) {
50
+ this.subs.push(subscription);
51
+ this.send({ method: "subscribe", subscription });
52
+ }
53
+ scheduleReconnect() {
54
+ if (this.closed)
55
+ return;
56
+ this.reconnectTimer = setTimeout(async () => {
57
+ try {
58
+ await this.connect();
59
+ }
60
+ catch {
61
+ this.scheduleReconnect();
62
+ }
63
+ }, 3000);
64
+ }
65
+ subscribePrices() { this.sub({ type: "allMids" }); }
66
+ subscribeBook(symbol) { this.sub({ type: "l2Book", coin: symbol.toUpperCase() }); }
67
+ subscribeTrades(symbol) { this.sub({ type: "trades", coin: symbol.toUpperCase() }); }
68
+ subscribeCandle(symbol, interval) {
69
+ this.sub({ type: "candle", coin: symbol.toUpperCase(), interval });
70
+ }
71
+ handleMessage(msg) {
72
+ const channel = String(msg.channel ?? "");
73
+ const data = msg.data;
74
+ if (!data)
75
+ return;
76
+ if (channel === "allMids") {
77
+ const mids = (data.mids ?? data);
78
+ const updates = Object.entries(mids).map(([symbol, mid]) => ({
79
+ symbol, mid: Number(mid),
80
+ }));
81
+ this.emit("prices", updates);
82
+ }
83
+ else if (channel === "l2Book") {
84
+ const d = data;
85
+ const levels = d.levels;
86
+ if (levels) {
87
+ this.emit("book", {
88
+ bids: levels[0].map((l) => [l.px, l.sz]),
89
+ asks: levels[1].map((l) => [l.px, l.sz]),
90
+ });
91
+ }
92
+ }
93
+ else if (channel === "trades") {
94
+ const trades = Array.isArray(data) ? data : [];
95
+ for (const t of trades) {
96
+ // HL side: "A" = sell (ask), "B" = buy (bid)
97
+ const rawSide = String(t.side ?? "");
98
+ this.emit("trade", {
99
+ side: rawSide === "A" ? "sell" : rawSide === "B" ? "buy" : rawSide,
100
+ price: String(t.px ?? ""),
101
+ size: String(t.sz ?? ""),
102
+ time: Number(t.time ?? Date.now()),
103
+ });
104
+ }
105
+ }
106
+ else if (channel === "candle") {
107
+ const d = data;
108
+ this.emit("candle", {
109
+ o: String(d.o ?? ""), h: String(d.h ?? ""), l: String(d.l ?? ""),
110
+ c: String(d.c ?? ""), v: String(d.v ?? ""), t: Number(d.t ?? 0),
111
+ });
112
+ }
113
+ }
114
+ close() {
115
+ this.closed = true;
116
+ if (this.reconnectTimer)
117
+ clearTimeout(this.reconnectTimer);
118
+ if (this.ws) {
119
+ this.ws.close();
120
+ this.ws = null;
121
+ }
122
+ }
123
+ }
124
+ // ── Lighter Market Feed (REST polling) ──
125
+ const LIGHTER_BASE = "https://mainnet.zklighter.elliot.ai/api/v1";
126
+ export class LighterMarketFeed extends EventEmitter {
127
+ closed = false;
128
+ timers = [];
129
+ marketMap = new Map();
130
+ pricesMs;
131
+ bookMs;
132
+ candleMs;
133
+ constructor(opts) {
134
+ super();
135
+ this.pricesMs = opts?.pricesIntervalMs ?? 3000;
136
+ this.bookMs = opts?.bookIntervalMs ?? 2000;
137
+ this.candleMs = opts?.candleIntervalMs ?? 5000;
138
+ }
139
+ async connect() {
140
+ const res = await fetch(`${LIGHTER_BASE}/orderBookDetails`);
141
+ const json = await res.json();
142
+ for (const m of json.order_book_details ?? []) {
143
+ if (m.market_type === "perp")
144
+ this.marketMap.set(m.symbol.toUpperCase(), m.market_id);
145
+ }
146
+ this.emit("connected");
147
+ }
148
+ getMarketId(symbol) {
149
+ const id = this.marketMap.get(symbol.toUpperCase());
150
+ if (id === undefined)
151
+ throw new Error(`Unknown Lighter market: ${symbol}`);
152
+ return id;
153
+ }
154
+ poll(fn, intervalMs) {
155
+ fn().catch(() => { });
156
+ this.timers.push(setInterval(() => { if (!this.closed)
157
+ fn().catch(() => { }); }, intervalMs));
158
+ }
159
+ subscribePrices() {
160
+ this.poll(async () => {
161
+ const res = await fetch(`${LIGHTER_BASE}/orderBookDetails`);
162
+ const json = await res.json();
163
+ const updates = (json.order_book_details ?? [])
164
+ .filter(m => m.market_type === "perp")
165
+ .map(m => ({ symbol: m.symbol, mid: m.last_trade_price }));
166
+ this.emit("prices", updates);
167
+ }, this.pricesMs);
168
+ }
169
+ subscribeBook(symbol) {
170
+ const marketId = this.getMarketId(symbol);
171
+ this.poll(async () => {
172
+ const res = await fetch(`${LIGHTER_BASE}/orderBookOrders?market_id=${marketId}&limit=20`);
173
+ const json = await res.json();
174
+ this.emit("book", {
175
+ bids: (json.bids ?? []).map(b => [b.price, b.remaining_base_amount]),
176
+ asks: (json.asks ?? []).map(a => [a.price, a.remaining_base_amount]),
177
+ });
178
+ }, this.bookMs);
179
+ }
180
+ subscribeTrades(symbol) {
181
+ const marketId = this.getMarketId(symbol);
182
+ let lastTid = 0;
183
+ this.poll(async () => {
184
+ const res = await fetch(`${LIGHTER_BASE}/recentTrades?market_id=${marketId}&limit=20`);
185
+ const json = await res.json();
186
+ for (const t of json.trades ?? []) {
187
+ if (t.timestamp > lastTid) {
188
+ lastTid = t.timestamp;
189
+ this.emit("trade", {
190
+ side: t.is_maker_ask ? "buy" : "sell",
191
+ price: t.price,
192
+ size: t.size,
193
+ time: t.timestamp,
194
+ });
195
+ }
196
+ }
197
+ }, this.bookMs);
198
+ }
199
+ subscribeCandle(symbol, interval) {
200
+ const marketId = this.getMarketId(symbol);
201
+ this.poll(async () => {
202
+ const now = Math.floor(Date.now() / 1000);
203
+ const res = await fetch(`${LIGHTER_BASE}/candles?market_id=${marketId}&resolution=${interval}&count_back=1&end_timestamp=${now}`);
204
+ const json = await res.json();
205
+ const c = json.candles?.[0];
206
+ if (c) {
207
+ this.emit("candle", {
208
+ o: c.open, h: c.high, l: c.low, c: c.close, v: c.volume, t: c.timestamp,
209
+ });
210
+ }
211
+ }, this.candleMs);
212
+ }
213
+ close() {
214
+ this.closed = true;
215
+ for (const t of this.timers)
216
+ clearInterval(t);
217
+ this.timers = [];
218
+ }
219
+ }
220
+ // ── Factory ──
221
+ export function createMarketFeed(exchange) {
222
+ if (exchange === "hyperliquid")
223
+ return new HyperliquidMarketFeed();
224
+ if (exchange === "lighter")
225
+ return new LighterMarketFeed();
226
+ throw new Error(`Use PacificaWSClient for pacifica, not createMarketFeed`);
227
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.8",
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, bridge USDC, manage positions, scan arbitrage opportunities, or mentions perp-cli, hypurrquant, Pacifica, Hyperliquid, or Lighter exchanges."
4
- allowed-tools: "Bash(perp:*), Bash(npx perp-cli:*), Bash(npx -y 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)."
4
+ allowed-tools: "Bash(perp:*), Bash(npx perp-cli:*), Bash(npx -y perp-cli:*), Bash(bash scripts/*.sh:*)"
5
5
  license: MIT
6
6
  metadata:
7
7
  author: hypurrquant
8
- version: "0.4.0"
8
+ version: "0.4.7"
9
9
  ---
10
10
 
11
11
  # perp-cli Agent Guide
@@ -59,7 +59,9 @@ perp --json arb exec <SYM> <longEx> <shortEx> <$> --leverage <N> --isolated # e
59
59
 
60
60
  # Single exchange trading (when not doing arb)
61
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)
62
63
  perp --json -e <EX> trade close <SYM>
64
+ perp --json -e <EX> trade close <SYM> --smart # smart close at best bid/ask
63
65
 
64
66
  # Risk (only if you need to set limits — portfolio already includes risk)
65
67
  perp --json risk limits --max-leverage 5
@@ -87,11 +89,18 @@ perp --json --ndjson -e <EX> market list
87
89
 
88
90
  # Prevent duplicate orders with client ID
89
91
  perp --json -e <EX> trade market <SYM> buy <SIZE> --client-id <UNIQUE_ID>
92
+
93
+ # Smart order: IOC limit at best bid/ask + 1 tick instead of raw market order
94
+ # Fills only at top-of-book price — no multi-level sweep slippage
95
+ perp --json -e <EX> trade market <SYM> buy <SIZE> --smart
96
+ perp --json -e <EX> trade close <SYM> --smart
97
+ perp --json arb exec <SYM> <longEx> <shortEx> <$> --smart # smart arb entry
98
+ perp --json arb close <SYM> --smart # smart arb close
90
99
  ```
91
100
 
92
101
  All string outputs are auto-sanitized (control chars stripped, prompt injection patterns blocked).
93
102
 
94
- ## Arb Workflow
103
+ ## Arb Workflow (Perp-Perp)
95
104
 
96
105
  ```
97
106
  1. perp --json portfolio → check balances across all exchanges
@@ -110,6 +119,39 @@ All string outputs are auto-sanitized (control chars stripped, prompt injection
110
119
  → --pair <longEx>:<shortEx>: specify which pair if multiple
111
120
  ```
112
121
 
122
+ ## Spot+Perp Arb Workflow
123
+
124
+ Spot has 0 funding cost → spread = |perp funding rate|. Only needs 1 exchange with spot + any exchange with perp.
125
+
126
+ **Spot exchanges:** HL (Hyperliquid), LT (Lighter). Pacifica is perp-only.
127
+ **U-token mapping:** HL spot uses Unit protocol bridged tokens — UBTC=BTC, UETH=ETH, USOL=SOL, UFART=FARTCOIN. Resolved automatically.
128
+
129
+ ```
130
+ 1. perp --json arb scan --mode spot-perp → find spot+perp opportunities
131
+ perp --json arb scan --mode all → perp-perp + spot-perp combined
132
+ 2. [Show opportunity to user, get confirmation]
133
+ 3. perp --json arb spot-exec <SYM> <spotEx> <perpEx> <$>
134
+ Examples:
135
+ perp --json arb spot-exec ETH hl hl 100 # ETH spot(HL/UETH) + perp(HL)
136
+ perp --json arb spot-exec BTC hl lt 50 # BTC spot(HL/UBTC) + perp(LT)
137
+ perp --json arb spot-exec LINK lt hl 100 # LINK spot(LT) + perp(HL)
138
+ → cross-validates spot vs perp price (>5% deviation = abort, wrong token)
139
+ → sequential execution if same exchange (nonce collision avoidance)
140
+ → parallel execution if different exchanges
141
+ → auto-rollback if one leg fails
142
+ Options:
143
+ --leverage <N> set perp leverage before entry
144
+ --isolated use isolated margin on perp side
145
+ 4. perp --json arb status → shows spot-perp positions (spot leg labeled)
146
+ 5. perp --json arb spot-close <SYM> --spot-exch <hl|lt>
147
+ → sells spot + buys back perp simultaneously
148
+ ```
149
+
150
+ **Available spot+perp pairs (13):**
151
+ - HL spot: BTC (UBTC), ETH (UETH), SOL (USOL), FARTCOIN (UFART), HYPE, AZTEC, STABLE
152
+ - LT spot: ETH, AZTEC, AAVE, LINK, UNI, LIT, LDO, SKY
153
+ - All have perps on HL and LT. Pacifica perps available when API is up.
154
+
113
155
  ## Position Sizing
114
156
 
115
157
  - Single position < **80%** of that exchange's available balance
@@ -167,6 +209,39 @@ If `error.retryable` is `false`, do NOT retry — fix the cause first.
167
209
  | `DUPLICATE_ORDER` | already submitted — check positions, don't retry |
168
210
  | Lighter `invalid signature` | check ~/.perp/.env or `perp --json -e lighter manage setup-api-key` |
169
211
 
212
+ ## Examples
213
+
214
+ **User: "펀딩레이트 아비트라지 기회 찾아줘"**
215
+ ```bash
216
+ perp --json portfolio # check balances
217
+ perp --json arb scan --mode all --min 5 # scan perp-perp + spot-perp
218
+ # → show results, recommend best opportunity, ask for confirmation
219
+ ```
220
+
221
+ **User: "ETH spot-perp 아비트라지 실행해줘 $100"**
222
+ ```bash
223
+ perp --json arb scan --mode spot-perp # verify opportunity exists
224
+ perp --json --dry-run arb spot-exec ETH hl hl 100 # dry-run first
225
+ # → show dry-run result, ask for confirmation
226
+ perp --json arb spot-exec ETH hl hl 100 # execute after confirmation
227
+ perp --json arb status # verify positions
228
+ ```
229
+
230
+ **User: "내 포지션 상태 확인해줘"**
231
+ ```bash
232
+ perp --json portfolio # balances + positions + risk
233
+ perp --json arb status # arb-specific PnL + funding
234
+ ```
235
+
236
+ **User: "Hyperliquid에서 SOL 롱 0.5개"**
237
+ ```bash
238
+ perp --json -e hl market mid SOL # current price
239
+ perp --json -e hl trade check SOL buy 0.5 # pre-validate
240
+ perp --json --dry-run -e hl trade market SOL buy 0.5 # dry-run
241
+ # → show cost estimate, ask for confirmation
242
+ perp --json -e hl trade market SOL buy 0.5 --smart # execute with smart order
243
+ ```
244
+
170
245
  ## Referrals (Optional)
171
246
 
172
247
  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.
@@ -179,6 +254,29 @@ perp --json settings referrals off # disable anytime
179
254
  When setting up wallets for the first time, ask the user:
180
255
  > "perp-cli 개발을 지원하는 레퍼럴 기능을 켜시겠어요? 추가 수수료 없이 거래소 리베이트만 활용됩니다."
181
256
 
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
+
182
280
  ## References
183
281
 
184
282
  - `references/commands.md` — full command reference
@@ -7,7 +7,7 @@ All commands support `--json` for structured output. Always use `--json` when ca
7
7
  perp --json market list # all markets with prices, funding, volume
8
8
  perp --json market prices # cross-exchange price comparison
9
9
  perp --json market mid <SYMBOL> # mid price (fast)
10
- perp --json market info <SYMBOL> # tick size, min order, max leverage
10
+ perp --json market info <SYMBOL> # mark/index price, funding, volume, OI, max leverage
11
11
  perp --json market book <SYMBOL> # orderbook (bids/asks)
12
12
  perp --json market trades <SYMBOL> # recent trades
13
13
  perp --json market funding <SYMBOL> # funding rate history
@@ -89,7 +89,22 @@ perp --json bridge status <ORDER_ID>
89
89
 
90
90
  ## Arbitrage
91
91
  ```bash
92
+ # Perp-Perp arb
92
93
  perp --json arb scan --min <BPS> # find opportunities (>N bps spread) — PRIMARY command
94
+ perp --json arb exec <SYM> <longEx> <shortEx> <$> # execute perp-perp arb
95
+ perp --json arb close <SYM> # close perp-perp arb
96
+ perp --json arb status # monitor open arb positions
97
+ perp --json arb funding-earned # actual funding payments
98
+
99
+ # Spot+Perp arb (spot funding = 0, spread = |perp funding|)
100
+ perp --json arb scan --mode spot-perp # scan spot+perp opportunities
101
+ perp --json arb scan --mode all # both perp-perp + spot-perp
102
+ perp --json arb spot-exec <SYM> <spotEx> <perpEx> <$> # execute spot+perp arb
103
+ perp --json arb spot-close <SYM> --spot-exch <hl|lt> # close spot+perp arb
104
+ # spotEx: hl (Hyperliquid) or lt (Lighter). perpEx: hl, lt, pac.
105
+ # HL spot uses U-tokens: BTC→UBTC, ETH→UETH, SOL→USOL (auto-resolved)
106
+
107
+ # Other arb tools
93
108
  perp --json arb funding # detailed funding analysis
94
109
  perp --json arb dex # HIP-3 cross-dex arb (Hyperliquid)
95
110
  perp --json gap show # cross-exchange price gaps
@@ -173,7 +173,7 @@ Desired size: 1.0
173
173
 
174
174
  **CRITICAL: Each exchange has its own minimum order size and step size.**
175
175
  ```bash
176
- perp --json -e <EX> market info <SYM> # check minOrderSize, stepSize
176
+ perp --json -e <EX> market info <SYM> # check maxLeverage, volume, openInterest
177
177
  ```
178
178
  Round `ORDER_SIZE` to the coarser step size of the two exchanges. If the matched size falls below either exchange's minimum, the arb is NOT executable at this size — reduce target or skip.
179
179
 
@@ -0,0 +1,74 @@
1
+ #!/bin/bash
2
+ # Monitor open arb positions and evaluate exit conditions
3
+ # Usage: ./arb-monitor.sh [--json] [--min-spread 5]
4
+ # Checks: current spread vs entry, funding earned, PnL, liquidation distance
5
+ # Returns JSON with position health + actionable recommendations
6
+
7
+ set -euo pipefail
8
+
9
+ # Auto-detect perp command (supports npx fallback for agents without install permissions)
10
+ if command -v perp &>/dev/null; then
11
+ PERP="perp"
12
+ else
13
+ PERP="npx -y perp-cli@latest"
14
+ fi
15
+
16
+ JSON_MODE=false
17
+ MIN_SPREAD=5
18
+ while [[ $# -gt 0 ]]; do
19
+ case "$1" in
20
+ --json) JSON_MODE=true; shift ;;
21
+ --min-spread) MIN_SPREAD="$2"; shift 2 ;;
22
+ *) shift ;;
23
+ esac
24
+ done
25
+
26
+ # 1. Get current arb status
27
+ ARB_STATUS=$($PERP --json arb status 2>/dev/null || echo '{"ok":false}')
28
+ if ! echo "$ARB_STATUS" | grep -q '"ok":true'; then
29
+ if $JSON_MODE; then
30
+ echo '{"ok":false,"error":"No arb positions or arb status unavailable"}'
31
+ else
32
+ echo "No open arb positions found."
33
+ fi
34
+ exit 0
35
+ fi
36
+
37
+ # 2. Get current spreads
38
+ ARB_SCAN=$($PERP --json arb scan --min 0 2>/dev/null || echo '{"ok":false,"data":{"opportunities":[]}}')
39
+
40
+ # 3. Get funding earned
41
+ FUNDING=$($PERP --json arb funding-earned 2>/dev/null || echo '{"ok":false}')
42
+
43
+ # 4. Get liquidation distances
44
+ LIQ_DIST=$($PERP --json risk liquidation-distance 2>/dev/null || echo '{"ok":false}')
45
+
46
+ # 5. Output combined analysis
47
+ if $JSON_MODE; then
48
+ cat <<EOF
49
+ {
50
+ "ok": true,
51
+ "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
52
+ "positions": $ARB_STATUS,
53
+ "currentSpreads": $ARB_SCAN,
54
+ "fundingEarned": $FUNDING,
55
+ "liquidationDistance": $LIQ_DIST,
56
+ "minSpreadThreshold": $MIN_SPREAD
57
+ }
58
+ EOF
59
+ else
60
+ echo "=== Arb Position Monitor ==="
61
+ echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
62
+ echo ""
63
+ echo "--- Positions ---"
64
+ echo "$ARB_STATUS" | jq -r '.data // empty'
65
+ echo ""
66
+ echo "--- Funding Earned ---"
67
+ echo "$FUNDING" | jq -r '.data // empty'
68
+ echo ""
69
+ echo "--- Liquidation Distance ---"
70
+ echo "$LIQ_DIST" | jq -r '.data // empty'
71
+ echo ""
72
+ echo "Min spread threshold: ${MIN_SPREAD} bps"
73
+ echo "Recommendation: Check if current spreads exceed ${MIN_SPREAD} bps to continue holding."
74
+ fi
@@ -0,0 +1,85 @@
1
+ #!/bin/bash
2
+ # Funding rate analysis across all exchanges
3
+ # Usage: ./funding-analysis.sh [--json] [--symbol ETH] [--top 10]
4
+ # Fetches current + historical funding data for comparison
5
+
6
+ set -euo pipefail
7
+
8
+ # Auto-detect perp command (supports npx fallback for agents without install permissions)
9
+ if command -v perp &>/dev/null; then
10
+ PERP="perp"
11
+ else
12
+ PERP="npx -y perp-cli@latest"
13
+ fi
14
+
15
+ JSON_MODE=false
16
+ SYMBOL=""
17
+ TOP=10
18
+ while [[ $# -gt 0 ]]; do
19
+ case "$1" in
20
+ --json) JSON_MODE=true; shift ;;
21
+ --symbol) SYMBOL="$2"; shift 2 ;;
22
+ --top) TOP="$2"; shift 2 ;;
23
+ *) shift ;;
24
+ esac
25
+ done
26
+
27
+ if [[ -n "$SYMBOL" ]]; then
28
+ # Single symbol detailed analysis
29
+ FUNDING=$($PERP --json market funding "$SYMBOL" 2>/dev/null || echo '{"ok":false}')
30
+ SCAN=$($PERP --json arb scan --min 0 2>/dev/null || echo '{"ok":false}')
31
+ SPOT_SCAN=$($PERP --json arb scan --mode spot-perp --min 0 2>/dev/null || echo '{"ok":false}')
32
+
33
+ if $JSON_MODE; then
34
+ cat <<EOF
35
+ {
36
+ "ok": true,
37
+ "symbol": "$SYMBOL",
38
+ "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
39
+ "funding": $FUNDING,
40
+ "perpPerpArb": $SCAN,
41
+ "spotPerpArb": $SPOT_SCAN
42
+ }
43
+ EOF
44
+ else
45
+ echo "=== Funding Analysis: $SYMBOL ==="
46
+ echo ""
47
+ echo "--- Current Funding Rates ---"
48
+ echo "$FUNDING" | jq -r '.data // empty' 2>/dev/null || echo "(unavailable)"
49
+ echo ""
50
+ echo "--- Perp-Perp Arb ---"
51
+ echo "$SCAN" | jq -r '
52
+ .data // [] | map(select(.symbol == "'"$SYMBOL"'")) | .[] |
53
+ " \(.longExch // .longExchange)→\(.shortExch // .shortExchange): \(.netSpread // .spread)bps"
54
+ ' 2>/dev/null || echo " (no opportunity)"
55
+ echo ""
56
+ echo "--- Spot+Perp Arb ---"
57
+ echo "$SPOT_SCAN" | jq -r '
58
+ .data // [] | map(select(.symbol == "'"$SYMBOL"'")) | .[] |
59
+ " \(.direction): \(.annualSpreadPct // .spread)% annual"
60
+ ' 2>/dev/null || echo " (no opportunity)"
61
+ fi
62
+ else
63
+ # All symbols scan
64
+ SCAN=$($PERP --json arb scan --mode all --min 0 2>/dev/null || echo '{"ok":false}')
65
+ FUNDING_DETAIL=$($PERP --json arb funding 2>/dev/null || echo '{"ok":false}')
66
+
67
+ if $JSON_MODE; then
68
+ cat <<EOF
69
+ {
70
+ "ok": true,
71
+ "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
72
+ "topN": $TOP,
73
+ "allOpportunities": $SCAN,
74
+ "fundingDetail": $FUNDING_DETAIL
75
+ }
76
+ EOF
77
+ else
78
+ echo "=== Funding Rate Overview (top $TOP) ==="
79
+ echo ""
80
+ echo "$SCAN" | jq -r "
81
+ .data // [] | .[0:$TOP] | .[] |
82
+ \" \(.symbol) | \(.mode // \"perp\") | \(.netSpread // .spread)bps | \(.longExch // .longExchange)→\(.shortExch // .shortExchange)\"
83
+ " 2>/dev/null || echo "(no data)"
84
+ fi
85
+ fi