perp-cli 0.3.8 → 0.3.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.
@@ -20,6 +20,11 @@ export declare class HyperliquidAdapter implements ExchangeAdapter {
20
20
  init(): Promise<void>;
21
21
  /** Load asset index map — supports native and HIP-3 dex. */
22
22
  private _loadAssetMap;
23
+ /**
24
+ * Resolve a symbol to the canonical name in the asset map.
25
+ * Handles: "ICP" → "ICP-PERP", "BTC-PERP" → "BTC-PERP", "km:GOOGL" → "km:GOOGL"
26
+ */
27
+ resolveSymbol(symbol: string): string;
23
28
  getAssetIndex(symbol: string): number;
24
29
  getMarkets(): Promise<ExchangeMarketInfo[]>;
25
30
  getOrderbook(symbol: string): Promise<{
@@ -93,24 +93,34 @@ export class HyperliquidAdapter {
93
93
  // non-critical
94
94
  }
95
95
  }
96
- getAssetIndex(symbol) {
96
+ /**
97
+ * Resolve a symbol to the canonical name in the asset map.
98
+ * Handles: "ICP" → "ICP-PERP", "BTC-PERP" → "BTC-PERP", "km:GOOGL" → "km:GOOGL"
99
+ */
100
+ resolveSymbol(symbol) {
97
101
  const sym = symbol.toUpperCase();
98
- // Try exact match first (handles both "BTC" for native and "km:GOOGL" for dex)
99
- let idx = this._assetMap.get(sym);
100
- if (idx !== undefined)
101
- return idx;
102
- // For dex: try lowercase prefix (API returns "km:GOOGL" but input may be "KM:GOOGL")
102
+ if (this._assetMap.has(sym))
103
+ return sym;
104
+ if (this._assetMap.has(`${sym}-PERP`))
105
+ return `${sym}-PERP`;
106
+ if (sym.endsWith("-PERP") && this._assetMap.has(sym.replace(/-PERP$/, "")))
107
+ return sym.replace(/-PERP$/, "");
103
108
  if (sym.includes(":")) {
104
109
  const [prefix, base] = sym.split(":");
105
- idx = this._assetMap.get(`${prefix.toLowerCase()}:${base}`);
106
- if (idx !== undefined)
107
- return idx;
108
- // Try base name only (e.g., "GOOGL")
109
- idx = this._assetMap.get(base);
110
- if (idx !== undefined)
111
- return idx;
110
+ const lower = `${prefix.toLowerCase()}:${base}`;
111
+ if (this._assetMap.has(lower))
112
+ return lower;
113
+ if (this._assetMap.has(base))
114
+ return base;
112
115
  }
113
- throw new Error(`Unknown symbol: ${symbol}`);
116
+ return sym; // return as-is, let downstream error
117
+ }
118
+ getAssetIndex(symbol) {
119
+ const resolved = this.resolveSymbol(symbol);
120
+ const idx = this._assetMap.get(resolved);
121
+ if (idx !== undefined)
122
+ return idx;
123
+ throw new Error(`Unknown asset: ${symbol}`);
114
124
  }
115
125
  async getMarkets() {
116
126
  let universe;
@@ -589,14 +599,14 @@ export class HyperliquidAdapter {
589
599
  * Uses SDK's built-in updateLeverage method.
590
600
  */
591
601
  async updateLeverage(symbol, leverage, isCross = true) {
592
- return this.sdk.exchange.updateLeverage(symbol, isCross ? "cross" : "isolated", leverage);
602
+ return this.sdk.exchange.updateLeverage(this.resolveSymbol(symbol), isCross ? "cross" : "isolated", leverage);
593
603
  }
594
604
  /**
595
605
  * Update isolated margin for a position.
596
606
  * amount > 0 to add margin, amount < 0 to remove
597
607
  */
598
608
  async updateIsolatedMargin(symbol, amount) {
599
- return this.sdk.exchange.updateIsolatedMargin(symbol, amount > 0, Math.round(Math.abs(amount) * 1e6));
609
+ return this.sdk.exchange.updateIsolatedMargin(this.resolveSymbol(symbol), amount > 0, Math.round(Math.abs(amount) * 1e6));
600
610
  }
601
611
  /**
602
612
  * Withdraw from Hyperliquid L1 bridge.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter",
5
5
  "bin": {
6
6
  "perp": "./dist/index.js",
@@ -5,7 +5,7 @@ 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.3.8"
8
+ version: "0.3.9"
9
9
  mcp-server: perp-cli
10
10
  ---
11
11
 
@@ -66,6 +66,12 @@ perp --json -e lighter ... # Lighter (Ethereum)
66
66
  ```
67
67
  If a default exchange is set, `-e` can be omitted.
68
68
 
69
+ ### Symbol naming
70
+ Symbols are auto-resolved across exchanges. Use bare symbols (e.g., `BTC`, `SOL`, `ICP`) everywhere — the CLI handles exchange-specific naming:
71
+ - **Hyperliquid**: `ICP` → `ICP-PERP` (auto-resolved, `-PERP` suffix added)
72
+ - **Pacifica / Lighter**: bare symbols as-is
73
+ - `arb scan` returns bare symbols — pass them directly to any exchange command.
74
+
69
75
  ### Common operations
70
76
  ```bash
71
77
  perp --json wallet show # check configured wallets
@@ -73,10 +79,19 @@ perp --json -e hl account info # balance & margin
73
79
  perp --json -e hl account positions # open positions
74
80
  perp --json -e hl market list # available markets
75
81
  perp --json -e hl market mid BTC # BTC mid price
76
- perp --json arb rates # cross-exchange funding rates
82
+ perp --json arb scan --min 5 # find funding arb opportunities (>5bps spread)
77
83
  perp --json portfolio # unified multi-exchange view
78
84
  ```
79
85
 
86
+ ### Funding arb direction (CRITICAL — do NOT reverse)
87
+ ```
88
+ arb scan returns: longExch, shortExch, netSpread
89
+ → ALWAYS follow longExch/shortExch exactly. NEVER reverse the direction.
90
+ → NEVER enter if netSpread ≤ 0 (= loss after fees)
91
+ → Positive funding = longs pay shorts → be SHORT to receive
92
+ → Negative funding = shorts pay longs → be LONG to receive
93
+ ```
94
+
80
95
  ### Trade execution (MANDATORY checklist)
81
96
  ```
82
97
  BEFORE ANY TRADE:
@@ -98,6 +113,43 @@ BEFORE ANY TRADE:
98
113
  9. perp --json -e <EX> account positions → verify result + check liquidation price
99
114
  ```
100
115
 
116
+ ### Exchange-specific constraints
117
+ ```
118
+ Minimum order values (notional, enforced by exchange):
119
+ - Hyperliquid: $10 minimum per order
120
+ - Pacifica: varies by symbol (usually ~$1)
121
+ - Lighter: varies by symbol
122
+
123
+ If your calculated size falls below the minimum, increase to meet it or skip the opportunity.
124
+ trade check returns valid: true/false but is ADVISORY — it does NOT block execution.
125
+ The exchange itself will reject orders below its minimum.
126
+ ```
127
+
128
+ ### Arb order sizing (CRITICAL — both legs MUST match)
129
+ ```
130
+ For funding arb, BOTH legs must have the EXACT SAME SIZE. Size mismatch = directional exposure.
131
+
132
+ 1. Check orderbook depth on BOTH exchanges:
133
+ perp --json -e <LONG_EX> market book <SYM> → asks (you're buying)
134
+ perp --json -e <SHORT_EX> market book <SYM> → bids (you're selling)
135
+
136
+ 2. Compute ORDER_SIZE:
137
+ - fillable_long = sum of ask sizes at best 2-3 levels
138
+ - fillable_short = sum of bid sizes at best 2-3 levels
139
+ - ORDER_SIZE = min(fillable_long, fillable_short, desired_size)
140
+ - Must be ≥ BOTH exchanges' minimum order value (e.g. HL requires ≥$10 notional)
141
+
142
+ 3. Execute BOTH legs with the SAME ORDER_SIZE:
143
+ perp --json -e <LONG_EX> trade market <SYM> buy <ORDER_SIZE>
144
+ → verify fill via account positions
145
+ perp --json -e <SHORT_EX> trade market <SYM> sell <ORDER_SIZE>
146
+ → verify fill via account positions
147
+
148
+ 4. Confirm matched: both positions must show identical size.
149
+ If mismatch (partial fill), adjust the larger to match the smaller.
150
+ ```
151
+ See `references/strategies.md` for detailed execution strategy (chunked orders, limit orders, failure handling).
152
+
101
153
  ### Post-entry monitoring (MANDATORY while positions are open)
102
154
  ```
103
155
  Every 15 minutes:
@@ -106,7 +158,7 @@ Every 15 minutes:
106
158
  perp --json -e <EX> account positions → check each position P&L
107
159
 
108
160
  Every 1 hour (at funding settlement):
109
- perp --json arb rates → is spread still profitable?
161
+ perp --json arb scan --min 5 → is spread still profitable?
110
162
  perp --json portfolio → total equity across exchanges
111
163
  Compare both legs' unrealized P&L — they should roughly offset
112
164
 
@@ -78,7 +78,7 @@ perp --json bridge send --from solana --to arbitrum --amount 500
78
78
  perp --json bridge status <orderId> # wait for completion
79
79
 
80
80
  # 5. Verify both sides have balance, then start arb
81
- perp --json arb rates
81
+ perp --json arb scan --min 5
82
82
  ```
83
83
 
84
84
  ### Lighter API Key Setup
@@ -229,7 +229,7 @@ Error case:
229
229
  - `wallet show`, `wallet balance` — read-only
230
230
  - `account info`, `account positions`, `account orders` — read-only
231
231
  - `market list`, `market mid`, `market book` — read-only
232
- - `arb rates`, `arb scan` — read-only
232
+ - `arb scan` — read-only (`arb rates` is deprecated)
233
233
  - `portfolio`, `risk overview` — read-only
234
234
  - `bridge quote` — read-only
235
235
  - `bridge status` — read-only
@@ -242,6 +242,29 @@ Error case:
242
242
 
243
243
  **For non-idempotent commands:** always verify the result before retrying. Check positions or balances to confirm whether the first attempt succeeded.
244
244
 
245
+ ## Symbol Naming Across Exchanges
246
+
247
+ Symbols are auto-resolved by the CLI. **Always use bare symbols** (e.g., `BTC`, `SOL`, `ICP`) — the CLI handles exchange-specific naming automatically:
248
+
249
+ | Input | Hyperliquid | Pacifica | Lighter |
250
+ |-------|-------------|----------|---------|
251
+ | `ICP` | → `ICP-PERP` | → `ICP` | → `ICP` |
252
+ | `BTC` | → `BTC` | → `BTC` | → `BTC` |
253
+ | `SOL` | → `SOL` | → `SOL` | → `SOL` |
254
+
255
+ - `arb scan` returns bare symbols — pass them directly to trade/leverage commands on any exchange.
256
+ - Do NOT manually add `-PERP` suffix — the CLI resolves this automatically.
257
+
258
+ ## Exchange-Specific Constraints
259
+
260
+ | Exchange | Min Order (notional) | Notes |
261
+ |----------|---------------------|-------|
262
+ | Hyperliquid | **$10** | Rejects orders below $10 notional |
263
+ | Pacifica | ~$1 (varies by symbol) | Lower minimums |
264
+ | Lighter | Varies by symbol | Check market info |
265
+
266
+ **`trade check` is advisory only** — it returns `valid: true/false` but does NOT block execution. The exchange itself enforces minimums and will reject with an error if the order is too small.
267
+
245
268
  ## Common Agent Mistakes
246
269
 
247
270
  1. **Using `perp init`** — interactive, will hang forever. Use `wallet set` instead.
@@ -250,3 +273,5 @@ Error case:
250
273
  4. **Retrying a trade without checking** — leads to double positions. Always check `account positions` after a trade, even if it seemed to fail.
251
274
  5. **Bridging without quoting** — always run `bridge quote` first to show the user fees and estimated time.
252
275
  6. **Assuming deposit is instant** — after `bridge send`, wait for `bridge status` to confirm completion before depositing to the destination exchange.
276
+ 7. **Manually adding `-PERP` suffix** — the CLI auto-resolves symbols. Just use bare names like `ICP`, `SOL`, `BTC`.
277
+ 8. **Order below exchange minimum** — Hyperliquid requires $10+ notional. Compute `size × price` before submitting.
@@ -89,11 +89,11 @@ perp --json bridge status <ORDER_ID>
89
89
 
90
90
  ## Arbitrage
91
91
  ```bash
92
- perp --json arb rates # compare funding rates across exchanges
93
- perp --json arb scan --min <BPS> # find opportunities (>N bps spread)
92
+ perp --json arb scan --min <BPS> # find opportunities (>N bps spread) — PRIMARY command
94
93
  perp --json arb funding # detailed funding analysis
95
94
  perp --json arb dex # HIP-3 cross-dex arb (Hyperliquid)
96
95
  perp --json gap show # cross-exchange price gaps
96
+ # NOTE: 'arb rates' is deprecated — use 'arb scan' instead
97
97
  ```
98
98
 
99
99
  ## Wallet Management
@@ -9,7 +9,7 @@ You are not expected to follow rigid rules — use this as a decision framework
9
9
  - Funding rates settle **every 1 hour** on all supported exchanges
10
10
  - Positive rate = longs pay shorts, negative rate = shorts pay longs
11
11
  - Rates are annualized in display but applied hourly: `hourly = annual / 8760`
12
- - Scan rates: `perp --json arb rates`
12
+ - Scan opportunities: `perp --json arb scan --min 5` (shows spread, longExch, shortExch, netSpread)
13
13
 
14
14
  ### Opportunity Cost Awareness
15
15
 
@@ -56,10 +56,29 @@ During transition, you are **unhedged**. Price can move against you. Factor this
56
56
 
57
57
  ### Discovery Loop
58
58
  ```bash
59
- perp --json arb rates # compare rates across exchanges
60
- perp --json arb scan --min 10 # find spreads > 10 bps
59
+ perp --json arb scan --min 5 # find spreads > 5 bps (shows longExch/shortExch/netSpread)
60
+ # NOTE: 'arb rates' is deprecated use 'arb scan' instead
61
61
  ```
62
62
 
63
+ ### CRITICAL: Reading arb scan Results
64
+
65
+ The `arb scan` output tells you EXACTLY what to do:
66
+
67
+ ```
68
+ longExch: "hyperliquid" → open LONG on this exchange
69
+ shortExch: "pacifica" → open SHORT on this exchange
70
+ netSpread: 12.87 → profit after fees (bps/hour)
71
+ ```
72
+
73
+ **Rules:**
74
+ 1. **ALWAYS follow longExch/shortExch directions exactly.** DO NOT reverse them.
75
+ 2. **NEVER enter if netSpread ≤ 0.** Negative netSpread = loss after fees.
76
+ 3. The direction logic: go LONG where funding is negative (longs receive), go SHORT where funding is positive (shorts receive).
77
+
78
+ **Why these directions?**
79
+ - Positive funding (+) = longs pay shorts → you want to be SHORT to receive
80
+ - Negative funding (-) = shorts pay longs → you want to be LONG to receive
81
+
63
82
  ### Decision Framework
64
83
  When evaluating an arb opportunity:
65
84
 
@@ -118,53 +137,102 @@ Actual hold: 6h | Actual net: ~130 bps
118
137
  perp --json portfolio # unified multi-exchange view
119
138
  perp --json risk overview # cross-exchange risk assessment
120
139
  perp --json -e <EX> account positions # per-exchange positions
121
- perp --json arb rates # are current rates still favorable?
140
+ perp --json arb scan --min 5 # are current rates still favorable?
141
+ ```
142
+
143
+ ### Order Execution: Matched Size, Sequential Legs
144
+
145
+ **The #1 rule of arb execution: BOTH LEGS MUST HAVE THE EXACT SAME SIZE.** A size mismatch means you have net directional exposure — the whole point of arb is to be delta-neutral.
146
+
147
+ #### Step 1: Determine Matched Order Size
148
+
149
+ Before placing ANY order, compute a single `ORDER_SIZE` that BOTH exchanges can fill:
150
+
151
+ ```bash
152
+ # 1. Check orderbook depth on BOTH sides
153
+ perp --json -e <LONG_EX> market book <SYM> # check asks (you're buying)
154
+ perp --json -e <SHORT_EX> market book <SYM> # check bids (you're selling)
155
+
156
+ # 2. Find immediately fillable size at best 2-3 ticks
157
+ # LONG side: sum ask sizes at best 2-3 ask levels → fillable_long
158
+ # SHORT side: sum bid sizes at best 2-3 bid levels → fillable_short
159
+
160
+ # 3. ORDER_SIZE = min(fillable_long, fillable_short, desired_size)
161
+ # The SMALLER side limits your matched size.
162
+ ```
163
+
164
+ **Example:**
165
+ ```
166
+ LONG exchange asks: $85.00 × 0.5, $85.01 × 0.3 → fillable = 0.8
167
+ SHORT exchange bids: $85.10 × 0.4, $85.09 × 0.2 → fillable = 0.6
168
+ Desired size: 1.0
169
+
170
+ → ORDER_SIZE = min(0.8, 0.6, 1.0) = 0.6
171
+ → Both legs get exactly 0.6
122
172
  ```
123
173
 
124
- ### Order Execution: Sequential Leg Management
174
+ **CRITICAL: Each exchange has its own minimum order size and step size.**
175
+ ```bash
176
+ perp --json -e <EX> market info <SYM> # check minOrderSize, stepSize
177
+ ```
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
+
180
+ #### Step 2: Execute Legs Sequentially with Same Size
181
+
182
+ Once you have a single `ORDER_SIZE`, execute both legs using THAT EXACT SIZE:
125
183
 
126
- **NEVER close or open both legs of an arb at once with market orders.** You must manage execution carefully.
184
+ ```bash
185
+ # Leg 1: Open on exchange A
186
+ perp --json -e <LONG_EX> trade market <SYM> buy <ORDER_SIZE>
187
+
188
+ # Verify leg 1 filled
189
+ perp --json -e <LONG_EX> account positions
127
190
 
128
- #### Why This Matters
129
- Orderbooks have limited depth at each price level. A large market order will eat through multiple ticks and suffer heavy slippage. Worse, if you close one leg but fail to close the other (exchange error, rate limit, network issue), you are left with naked directional exposure.
191
+ # Leg 2: Open on exchange B with SAME size
192
+ perp --json -e <SHORT_EX> trade market <SYM> sell <ORDER_SIZE>
130
193
 
131
- #### Pre-Execution: Check Orderbook Depth
132
- Before executing, verify that the orderbook can absorb your size at acceptable prices on BOTH sides:
194
+ # Verify leg 2 filled
195
+ perp --json -e <SHORT_EX> account positions
196
+ ```
197
+
198
+ **After both legs, verify sizes match:**
133
199
  ```bash
134
- perp --json -e <EX_A> market book <SYM> # check bids/asks depth
135
- perp --json -e <EX_B> market book <SYM> # check bids/asks depth
136
- ```
137
-
138
- Look at the size available at the best tick. If your order size exceeds what's available at the best 2-3 ticks, you MUST split the order.
139
-
140
- #### Execution Strategy
141
- 1. **Determine executable chunk size** the largest size both orderbooks can absorb at the best tick without excessive slippage
142
- 2. **Execute in sequential chunks:**
143
- ```
144
- Chunk 1: close X on Exchange A → immediately open X on Exchange B
145
- Chunk 2: close X on Exchange A → immediately open X on Exchange B
146
- ... repeat until full size is executed
147
- ```
148
- 3. **Verify each chunk** before proceeding to the next:
149
- ```bash
150
- perp --json -e <EX_A> account positions # confirm partial close
151
- perp --json -e <EX_B> account positions # confirm partial open
152
- ```
153
- 4. **Re-check the orderbook** between chunks liquidity may have changed
154
-
155
- #### Paired Execution Rule
156
- Each chunk must be a **matched pair**: close on one side, open on the other. Never execute multiple closes without the corresponding opens. If one leg fails:
157
- - STOP immediately
158
- - Assess your current exposure
159
- - Decide whether to retry the failed leg or unwind the completed leg
160
- - Do NOT continue with remaining chunks
200
+ perp --json -e <LONG_EX> account positions # size = X
201
+ perp --json -e <SHORT_EX> account positions # size must = X
202
+ ```
203
+ If sizes differ (partial fill, rounding), immediately adjust the larger position to match the smaller one.
204
+
205
+ #### Step 3: Split Large Orders into Matched Chunks
206
+
207
+ If your desired size exceeds what the orderbooks can absorb at best ticks, split into chunks — but **every chunk must be a matched pair with identical size on both sides**:
208
+
209
+ ```
210
+ Chunk 1: buy 0.3 on Exchange A → sell 0.3 on Exchange B → verify both
211
+ Chunk 2: buy 0.3 on Exchange A → sell 0.3 on Exchange B → verify both
212
+ ... repeat until full size is executed
213
+ ```
214
+
215
+ **Rules:**
216
+ 1. Re-check orderbook depth between chunks liquidity changes
217
+ 2. Each chunk: same size on both sides, no exceptions
218
+ 3. If one leg fails → STOP immediately, do NOT continue
219
+ 4. Assess exposure: retry the failed leg, or unwind the completed leg
220
+ 5. NEVER execute multiple orders on one side without matching the other
161
221
 
162
222
  #### Using Limit Orders for Better Execution
163
- For non-urgent transitions, consider limit orders at the best bid/ask instead of market orders:
223
+ For non-urgent entries, use limit orders at best bid/ask instead of market:
224
+ ```bash
225
+ perp --json -e <EX> trade buy <SYM> <ORDER_SIZE> -p <PRICE> # limit order
226
+ ```
227
+ This avoids crossing the spread but risks not getting filled. Set a timeout and fall back to market if unfilled. **Both legs must still use the same `ORDER_SIZE`.**
228
+
229
+ #### Closing Arb Positions: Same Rules Apply
230
+ When exiting, close BOTH legs with the SAME size:
164
231
  ```bash
165
- perp --json -e <EX> trade sell <SYM> <SIZE> -p <PRICE> # limit order
232
+ perp --json -e <LONG_EX> trade close <SYM> # closes full position
233
+ perp --json -e <SHORT_EX> trade close <SYM> # closes full position
166
234
  ```
167
- This avoids crossing the spread, but you risk not getting filled. Set a reasonable timeout and fall back to market if not filled.
235
+ If positions already have mismatched sizes, close to the smaller size first, then close the remaining delta on the larger side.
168
236
 
169
237
  ### When to Exit
170
238
  - Spread compressed below your breakeven (including fees)
@@ -287,7 +355,7 @@ Check the output:
287
355
  #### Every hour (at funding settlement):
288
356
  ```bash
289
357
  perp --json portfolio # total equity across exchanges
290
- perp --json arb rates # are rates still favorable?
358
+ perp --json arb scan --min 5 # are rates still favorable?
291
359
  perp --json -e <EX_A> account positions # P&L on leg A
292
360
  perp --json -e <EX_B> account positions # P&L on leg B
293
361
  ```
@@ -11,9 +11,5 @@ echo "=== Funding Rate Arbitrage Scanner ==="
11
11
  echo "Minimum spread: ${MIN_SPREAD} bps"
12
12
  echo ""
13
13
 
14
- echo "1. Cross-exchange rates:"
15
- perp --json arb rates
16
-
17
- echo ""
18
- echo "2. Opportunities (>= ${MIN_SPREAD} bps):"
14
+ echo "1. Opportunities (>= ${MIN_SPREAD} bps):"
19
15
  perp --json arb scan --min "$MIN_SPREAD"