perp-cli 0.14.1 → 0.14.3
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/dist/arb/sizing.js +29 -7
- package/dist/bot/conditions.d.ts +5 -1
- package/dist/bot/conditions.js +16 -11
- package/dist/bot/engine.d.ts +12 -0
- package/dist/bot/engine.js +55 -44
- package/dist/bot/strategies/claude-agent.js +2 -2
- package/dist/bot/strategies/funding-arb-strategy.js +22 -26
- package/dist/bot/strategies/funding-arb-utils.d.ts +33 -1
- package/dist/bot/strategies/funding-arb-utils.js +82 -19
- package/dist/bot/strategies/funding-auto.js +61 -37
- package/dist/bot/strategies/grid-strategy.d.ts +2 -0
- package/dist/bot/strategies/grid-strategy.js +21 -2
- package/dist/bot/strategies/spot-perp-arb-strategy.js +48 -29
- package/dist/bot/tui/BotDashboard.d.ts +13 -2
- package/dist/bot/tui/BotDashboard.js +8 -3
- package/dist/bridge-engine.d.ts +2 -0
- package/dist/bridge-engine.js +68 -31
- package/dist/commands/account.js +2 -2
- package/dist/commands/arb/index.d.ts +20 -0
- package/dist/commands/arb/index.js +121 -36
- package/dist/commands/arb-auto.d.ts +36 -0
- package/dist/commands/arb-auto.js +202 -76
- package/dist/commands/arb.js +51 -35
- package/dist/commands/bot.d.ts +6 -0
- package/dist/commands/bot.js +55 -45
- package/dist/commands/funds-onchain.js +6 -5
- package/dist/commands/funds.js +18 -5
- package/dist/commands/history.js +135 -53
- package/dist/commands/market.js +2 -2
- package/dist/commands/portfolio.d.ts +5 -0
- package/dist/commands/portfolio.js +58 -20
- package/dist/commands/rebalance.js +46 -10
- package/dist/commands/settings.js +2 -1
- package/dist/commands/trade.js +8 -4
- package/dist/commands/wallet.js +1 -1
- package/dist/cross-chain-margin.d.ts +29 -2
- package/dist/cross-chain-margin.js +30 -4
- package/dist/dashboard/server.d.ts +45 -8
- package/dist/dashboard/server.js +113 -42
- package/dist/dashboard/ui.js +47 -28
- package/dist/dashboard/ws-feeds.d.ts +25 -3
- package/dist/dashboard/ws-feeds.js +219 -82
- package/dist/dex-asset-map.d.ts +23 -4
- package/dist/dex-asset-map.js +39 -13
- package/dist/exchanges/arcus/adapter.js +33 -6
- package/dist/exchanges/arcus/public.d.ts +6 -0
- package/dist/exchanges/arcus/public.js +12 -4
- package/dist/exchanges/aster.d.ts +24 -7
- package/dist/exchanges/aster.js +178 -93
- package/dist/exchanges/hyperliquid.d.ts +30 -4
- package/dist/exchanges/hyperliquid.js +159 -55
- package/dist/exchanges/interface.d.ts +3 -2
- package/dist/exchanges/interface.js +3 -2
- package/dist/exchanges/lighter.d.ts +1 -1
- package/dist/exchanges/lighter.js +11 -9
- package/dist/exchanges/ondo/funds.d.ts +0 -1
- package/dist/exchanges/pacifica.d.ts +7 -0
- package/dist/exchanges/pacifica.js +52 -26
- package/dist/rebalance.d.ts +20 -1
- package/dist/rebalance.js +31 -5
- package/dist/strategies/funding-arb.js +38 -7
- package/dist/trade-validator.d.ts +1 -1
- package/dist/trade-validator.js +7 -2
- package/package.json +1 -1
- package/skills/perp-cli/SKILL.md +2 -2
- package/skills/perp-cli/references/commands.md +5 -3
|
@@ -4,7 +4,7 @@ import { symbolMatch, logSettledRejections } from "../utils.js";
|
|
|
4
4
|
import { logExecution } from "../execution-log.js";
|
|
5
5
|
import { updateJobState } from "../jobs.js";
|
|
6
6
|
import { invalidateCache } from "../cache.js";
|
|
7
|
-
import {
|
|
7
|
+
import { fetchBalancesWithFailures, computeRebalancePlan, hasEnoughBalance, marketLeverageOf, requiredArbMargin } from "../rebalance.js";
|
|
8
8
|
import { checkArbLiquidity } from "../liquidity.js";
|
|
9
9
|
import { computeAnnualSpread } from "../funding.js";
|
|
10
10
|
import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, fetchLighterFundingRates, } from "../shared-api.js";
|
|
@@ -15,6 +15,11 @@ import { isQuarantined, quarantineSymbol } from "../bot/quarantine.js";
|
|
|
15
15
|
import { isPositionGone, matchSymbol } from "../bot/strategies/funding-arb-utils.js";
|
|
16
16
|
/** Quarantine source for `perp strategy funding-arb` (src/bot/quarantine.ts). */
|
|
17
17
|
const SOURCE = "funding-arb-cli";
|
|
18
|
+
/** The market's published size decimals on `adapter` (null = not published / market not listed). */
|
|
19
|
+
async function sizeDecimalsOf(adapter, symbol) {
|
|
20
|
+
const m = (await adapter.getMarkets()).find((x) => symbolMatch(x.symbol, symbol));
|
|
21
|
+
return m === undefined ? null : m.sizeDecimals;
|
|
22
|
+
}
|
|
18
23
|
async function fetchAllRates() {
|
|
19
24
|
// SSOT rule #2: each DEX is independent. allSettled keeps the comparison
|
|
20
25
|
// alive when one DEX errors out, while rejection reasons get logged to
|
|
@@ -147,9 +152,17 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
|
|
|
147
152
|
// ── Phase 2: Max drawdown check ──
|
|
148
153
|
if (params.maxDrawdown && params.autoExecute && positions.length > 0) {
|
|
149
154
|
try {
|
|
150
|
-
const snapshots = await
|
|
155
|
+
const { snapshots, failed } = await fetchBalancesWithFailures(adapters);
|
|
151
156
|
const totalPnl = snapshots.reduce((s, e) => s + e.unrealizedPnl, 0);
|
|
152
|
-
|
|
157
|
+
// Only a failed venue that holds one of the bot's legs makes the uPnL sum unknown; a venue
|
|
158
|
+
// without credentials (notConfigured) cannot hold one.
|
|
159
|
+
const legVenues = new Set(positions.flatMap(p => [p.longExchange, p.shortExchange]));
|
|
160
|
+
const failedLegs = failed.filter(f => legVenues.has(f.exchange));
|
|
161
|
+
if (failedLegs.length > 0) {
|
|
162
|
+
// A partial uPnL sum is not the bot's drawdown: never close everything on it.
|
|
163
|
+
log(`[ARB] Drawdown check skipped — balance unavailable on ${failedLegs.map(f => `${f.exchange} (${f.error})`).join(", ")}`);
|
|
164
|
+
}
|
|
165
|
+
else if (totalPnl < -params.maxDrawdown) {
|
|
153
166
|
log(`[ARB] MAX DRAWDOWN hit: uPnL $${totalPnl.toFixed(2)} < -$${params.maxDrawdown}`);
|
|
154
167
|
log(`[ARB] Closing all ${positions.length} positions...`);
|
|
155
168
|
const stillOpen = await closeAllPositions(positions, adapters, log);
|
|
@@ -166,7 +179,11 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
|
|
|
166
179
|
const shouldCheckBalance = params.autoRebalance && (Date.now() - lastRebalanceCheck > 300_000); // every 5 min
|
|
167
180
|
if (shouldCheckBalance || (params.autoExecute && positions.length < maxPos)) {
|
|
168
181
|
try {
|
|
169
|
-
|
|
182
|
+
const read = await fetchBalancesWithFailures(adapters);
|
|
183
|
+
balanceSnapshots = read.snapshots;
|
|
184
|
+
// A venue missing from the snapshots has no balance for entry checks (no entry there).
|
|
185
|
+
for (const f of read.failed)
|
|
186
|
+
log(`[ARB] ${f.exchange} balance unavailable — no entries / rebalance on it this cycle: ${f.error}`);
|
|
170
187
|
lastRebalanceCheck = Date.now();
|
|
171
188
|
if (params.autoRebalance) {
|
|
172
189
|
const threshold = params.rebalanceThreshold ?? 100;
|
|
@@ -229,8 +246,22 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
|
|
|
229
246
|
const liq = await checkArbLiquidity(longAdapter, shortAdapter, symbol, requestedUsd, 0.5, log);
|
|
230
247
|
if (!liq.viable)
|
|
231
248
|
continue;
|
|
232
|
-
// Compute matched size (same for both legs)
|
|
233
|
-
|
|
249
|
+
// Compute matched size (same for both legs) at each venue's published size precision —
|
|
250
|
+
// the per-venue defaults (HL 2 decimals) would reject small BTC legs or over-round.
|
|
251
|
+
let longDec;
|
|
252
|
+
let shortDec;
|
|
253
|
+
try {
|
|
254
|
+
[longDec, shortDec] = await Promise.all([sizeDecimalsOf(longAdapter, symbol), sizeDecimalsOf(shortAdapter, symbol)]);
|
|
255
|
+
}
|
|
256
|
+
catch (e) {
|
|
257
|
+
log(`[ARB] Skip ${symbol}: could not read the market size precision (${e instanceof Error ? e.message : String(e)})`);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (longDec === null || shortDec === null) {
|
|
261
|
+
log(`[ARB] Skip ${symbol}: size precision not published on ${longDec === null ? longEx : shortEx}`);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
let matched = computeMatchedSize(liq.adjustedSizeUsd, highest.markPrice, longEx, shortEx, { longSizeDecimals: longDec, shortSizeDecimals: shortDec });
|
|
234
265
|
if (!matched) {
|
|
235
266
|
log(`[ARB] Skip ${symbol}: can't compute matched size (min notional or precision issue)`);
|
|
236
267
|
continue;
|
|
@@ -266,7 +297,7 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
|
|
|
266
297
|
// The LONG was definitely rejected for lot size (nothing placed): recompute once.
|
|
267
298
|
const lotSize = res.lotSize;
|
|
268
299
|
log(`[ARB] Lot size ${lotSize} on ${longEx} — recalculating (nothing was placed)`);
|
|
269
|
-
matched = computeMatchedSize(liq.adjustedSizeUsd, highest.markPrice, longEx, shortEx, { lotSize });
|
|
300
|
+
matched = computeMatchedSize(liq.adjustedSizeUsd, highest.markPrice, longEx, shortEx, { lotSize, longSizeDecimals: longDec, shortSizeDecimals: shortDec });
|
|
270
301
|
if (!matched) {
|
|
271
302
|
log(`[ARB] Skip ${symbol}: can't meet lot size ${lotSize}`);
|
|
272
303
|
continue;
|
package/dist/trade-validator.js
CHANGED
|
@@ -69,7 +69,12 @@ export async function validateTrade(adapter, params) {
|
|
|
69
69
|
// reject an order sized past a threshold even though the flat value allows it,
|
|
70
70
|
// so resolve the cap AT THIS ORDER'S NOTIONAL.
|
|
71
71
|
const effectiveMax = maxLeverageForNotional(market, notional);
|
|
72
|
-
|
|
72
|
+
// A venue that publishes no max leverage (Aster's public exchangeInfo): the margin estimate
|
|
73
|
+
// uses 1x — the most conservative requirement — and says so; the venue enforces its own cap.
|
|
74
|
+
if (effectiveMax === null && params.leverage === undefined) {
|
|
75
|
+
warnings.push(`Max leverage for ${sym} is not published by ${adapter.name} — margin estimated at 1x; pass --leverage for a tighter estimate`);
|
|
76
|
+
}
|
|
77
|
+
const leverage = params.leverage ?? effectiveMax ?? 1;
|
|
73
78
|
if (!Number.isFinite(leverage) || leverage <= 0) {
|
|
74
79
|
throw new Error(`No usable leverage for ${sym} (resolved=${leverage}); supply params.leverage explicitly.`);
|
|
75
80
|
}
|
|
@@ -197,7 +202,7 @@ export async function validateTrade(adapter, params) {
|
|
|
197
202
|
const fee = notional * DEFAULT_TAKER_FEE;
|
|
198
203
|
const estimatedSlippage = params.type === "limit" ? 0 : notional * 0.001;
|
|
199
204
|
const valid = checks.every(c => c.passed);
|
|
200
|
-
if (leverage > effectiveMax) {
|
|
205
|
+
if (effectiveMax !== null && market.maxLeverage !== null && leverage > effectiveMax) {
|
|
201
206
|
// Compare against the tier that applies at this notional, not the headline
|
|
202
207
|
// cap — otherwise the check is tautological whenever leverage defaulted FROM
|
|
203
208
|
// maxLeverage, and it never fires for the tiered case it exists to catch.
|
package/package.json
CHANGED
package/skills/perp-cli/SKILL.md
CHANGED
|
@@ -6,7 +6,7 @@ allowed-tools: "Bash(perp:*), Bash(npx -y perp-cli:*), Bash(bash scripts/preflig
|
|
|
6
6
|
compatibility: "Requires Node.js 20 or newer and the perp-cli npm package (global install or npx). Network access required for exchange APIs and bridge providers. Mutating commands need a wallet key imported into the OWS vault via 'perp wallet set EXCHANGE' (key on stdin or hidden prompt — never argv). Skill is portable across Claude Code, Claude.ai, Cursor, Codex, and Gemini CLI — no platform-specific features."
|
|
7
7
|
metadata:
|
|
8
8
|
author: hypurrquant
|
|
9
|
-
version: "0.14.
|
|
9
|
+
version: "0.14.3"
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# perp-cli
|
|
@@ -46,7 +46,7 @@ Also bundled under `scripts/`: `preflight.sh`, `arb-monitor.sh`, `funding-analys
|
|
|
46
46
|
### Step 1: Verify install + version
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
perp --version 2>/dev/null || npx -y perp-cli@latest --version # must be >= 0.14.
|
|
49
|
+
perp --version 2>/dev/null || npx -y perp-cli@latest --version # must be >= 0.14.3
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
If it is missing or older, ask the user before installing/upgrading (`npm install -g perp-cli@latest`) — never install on your own. If `perp` is not on PATH, prefix every command with `npx -y perp-cli@latest`.
|
|
@@ -37,6 +37,7 @@ perp --json -e <EX> account margin <SYMBOL> # position margin info
|
|
|
37
37
|
perp --json -e <EX> account pnl # realized + unrealized + funding
|
|
38
38
|
perp --json -e <EX> account twap-orders # active TWAP orders
|
|
39
39
|
perp --json portfolio # cross-exchange unified view
|
|
40
|
+
# funding24h is null (with fundingError) when a venue's funding history is unreadable; totals.fundingIncomplete = true then
|
|
40
41
|
perp --json portfolio --arb # full dashboard with arb top 5
|
|
41
42
|
```
|
|
42
43
|
|
|
@@ -255,7 +256,7 @@ perp --json arb scan --gaps # cross-exchange pri
|
|
|
255
256
|
perp --json arb scan --hip3 # HIP-3 cross-dex spreads
|
|
256
257
|
perp --json arb scan --history [SYMBOL] # historical funding
|
|
257
258
|
perp --json arb scan --compare <SYMBOL> # compare a symbol across exchanges
|
|
258
|
-
perp --json arb scan --positions # position funding impact
|
|
259
|
+
perp --json arb scan --positions # position funding impact; actual24h null + fundingHistoryErrors when a venue's history is unreadable
|
|
259
260
|
perp --json arb scan --live --interval 60 # continuous monitoring
|
|
260
261
|
perp --json arb scan --top 10 --hold-days 7 --bridge-cost 0 --size 100 # cost-aware filtering
|
|
261
262
|
perp --json arb scan --rates --include-unverified # also show QFEX (unverified funding; display only, never ranked)
|
|
@@ -281,6 +282,7 @@ perp --json arb close <SYM> --state-only --yes # drop a stale state
|
|
|
281
282
|
|
|
282
283
|
# Status / history / config / rebalance
|
|
283
284
|
perp --json arb status # open arb positions + PnL + funding; check unconfirmedPairs, staleStateEntries
|
|
285
|
+
# spot-perp entries: fundingSource actual | partial | estimate | recorded (+ fundingError) — only "actual" is complete settled funding
|
|
284
286
|
perp --json arb status --period 7 --symbol BTC # filter funding lookback / symbol
|
|
285
287
|
perp --json arb history # alias: arb log
|
|
286
288
|
perp --json arb history --period 30 # past arb trade performance
|
|
@@ -424,9 +426,9 @@ perp --json history list # execution audit trail
|
|
|
424
426
|
perp --json history positions # position lifetime tracking
|
|
425
427
|
perp --json history prune # purge old entries
|
|
426
428
|
perp --json history summary # trading performance summary (replaces former 'stats')
|
|
427
|
-
perp --json history pnl # P&L breakdown by exchange
|
|
429
|
+
perp --json history pnl # P&L breakdown by exchange; errors + partial name venues whose reads failed (left out, not 0)
|
|
428
430
|
perp --json history funding # funding payment aggregation
|
|
429
|
-
perp --json history report # full performance report (summary + pnl + funding)
|
|
431
|
+
perp --json history report # full performance report (summary + pnl + funding); errors + partial + venuesRead
|
|
430
432
|
perp --json history compare # compare strategies / accounts
|
|
431
433
|
perp --json history snapshot # equity snapshot
|
|
432
434
|
perp --json history track # PnL tracking helper
|