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.
- package/README.md +2 -0
- package/dist/arb/sizing.d.ts +12 -1
- package/dist/arb/sizing.js +38 -5
- package/dist/arb/state.d.ts +6 -0
- package/dist/arb/utils.d.ts +2 -0
- package/dist/arb/utils.js +1 -1
- package/dist/cli-spec.d.ts +0 -4
- package/dist/cli-spec.js +8 -1
- package/dist/commands/agent.js +12 -9
- package/dist/commands/arb/index.js +18 -3
- package/dist/commands/arb-auto.js +885 -67
- package/dist/commands/dex.js +7 -1
- package/dist/commands/funding.d.ts +2 -1
- package/dist/commands/funding.js +211 -4
- package/dist/commands/manage.js +1 -1
- package/dist/commands/market.js +1 -1
- package/dist/commands/multileg.d.ts +16 -0
- package/dist/commands/multileg.js +174 -0
- package/dist/commands/pnl.d.ts +12 -0
- package/dist/commands/pnl.js +243 -0
- package/dist/commands/settings.d.ts +2 -1
- package/dist/commands/settings.js +133 -2
- package/dist/commands/stream.js +132 -55
- package/dist/commands/trade.js +118 -43
- package/dist/commands/wallet.js +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.js +12 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.js +40 -0
- package/dist/dashboard/ws-feeds.js +1 -1
- package/dist/equity-tracker.d.ts +57 -0
- package/dist/equity-tracker.js +209 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +68 -0
- package/dist/exchanges/hyperliquid-spot.js +317 -0
- package/dist/exchanges/hyperliquid.d.ts +26 -5
- package/dist/exchanges/hyperliquid.js +181 -92
- package/dist/exchanges/interface.d.ts +1 -1
- package/dist/exchanges/lighter-spot.d.ts +55 -0
- package/dist/exchanges/lighter-spot.js +417 -0
- package/dist/exchanges/lighter.d.ts +10 -2
- package/dist/exchanges/lighter.js +132 -37
- package/dist/exchanges/pacifica.d.ts +11 -4
- package/dist/exchanges/pacifica.js +52 -37
- package/dist/exchanges/spot-interface.d.ts +45 -0
- package/dist/exchanges/spot-interface.js +22 -0
- package/dist/execution-log.d.ts +2 -2
- package/dist/funding/rates.d.ts +36 -0
- package/dist/funding/rates.js +146 -0
- package/dist/index.js +148 -99
- package/dist/liquidity.d.ts +12 -0
- package/dist/liquidity.js +43 -0
- package/dist/settings.d.ts +6 -0
- package/dist/settings.js +18 -2
- package/dist/signer/evm-local.d.ts +12 -0
- package/dist/signer/evm-local.js +19 -0
- package/dist/signer/index.d.ts +3 -0
- package/dist/signer/index.js +2 -0
- package/dist/signer/interface.d.ts +31 -0
- package/dist/signer/interface.js +7 -0
- package/dist/signer/solana-local.d.ts +13 -0
- package/dist/signer/solana-local.js +19 -0
- package/dist/smart-order.d.ts +44 -0
- package/dist/smart-order.js +145 -0
- package/dist/trade-validator.js +2 -1
- package/dist/utils.js +7 -1
- package/dist/ws/market-feed.d.ts +74 -0
- package/dist/ws/market-feed.js +227 -0
- package/package.json +1 -1
- package/skills/perp-cli/SKILL.md +102 -4
- package/skills/perp-cli/references/commands.md +16 -1
- package/skills/perp-cli/references/strategies.md +1 -1
- package/skills/perp-cli/scripts/arb-monitor.sh +74 -0
- package/skills/perp-cli/scripts/funding-analysis.sh +85 -0
- package/skills/perp-cli/scripts/preflight.sh +98 -0
- package/skills/perp-cli/scripts/spot-perp-scan.sh +83 -0
- package/skills/perp-cli/scripts/validate-arb.sh +112 -0
- package/skills/perp-cli/templates/arb-scan.sh +0 -15
- package/skills/perp-cli/templates/open-position.sh +0 -38
- package/skills/perp-cli/templates/wallet-setup.sh +0 -17
package/README.md
CHANGED
|
@@ -33,6 +33,8 @@ perp wallet show
|
|
|
33
33
|
|
|
34
34
|
Same EVM key works for both Hyperliquid and Lighter.
|
|
35
35
|
|
|
36
|
+
> **Lighter API Key Index:** Indexes 0–3 are reserved by Lighter's frontend (web/mobile). perp-cli defaults to index `4`. Override with `LIGHTER_API_KEY_INDEX` env var or `--key-index` flag on `manage setup-api-key`. Valid range: 4–254.
|
|
37
|
+
|
|
36
38
|
## Core Commands
|
|
37
39
|
|
|
38
40
|
```bash
|
package/dist/arb/sizing.d.ts
CHANGED
|
@@ -5,7 +5,18 @@ import type { ExchangeAdapter } from "../exchanges/interface.js";
|
|
|
5
5
|
*
|
|
6
6
|
* @returns size string that works for both exchanges, or null if not viable
|
|
7
7
|
*/
|
|
8
|
-
export declare function computeMatchedSize(sizeUsd: number, price: number, longExchange: string, shortExchange: string
|
|
8
|
+
export declare function computeMatchedSize(sizeUsd: number, price: number, longExchange: string, shortExchange: string, opts?: {
|
|
9
|
+
longSizeDecimals?: number;
|
|
10
|
+
shortSizeDecimals?: number;
|
|
11
|
+
}): {
|
|
12
|
+
size: string;
|
|
13
|
+
notional: number;
|
|
14
|
+
} | null;
|
|
15
|
+
/**
|
|
16
|
+
* Compute matched size for spot-perp arb.
|
|
17
|
+
* Uses spot exchange's size decimals for the spot leg.
|
|
18
|
+
*/
|
|
19
|
+
export declare function computeSpotPerpMatchedSize(sizeUsd: number, price: number, spotExchange: string, perpExchange: string, spotSizeDecimals?: number): {
|
|
9
20
|
size: string;
|
|
10
21
|
notional: number;
|
|
11
22
|
} | null;
|
package/dist/arb/sizing.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @returns size string that works for both exchanges, or null if not viable
|
|
6
6
|
*/
|
|
7
|
-
export function computeMatchedSize(sizeUsd, price, longExchange, shortExchange) {
|
|
7
|
+
export function computeMatchedSize(sizeUsd, price, longExchange, shortExchange, opts) {
|
|
8
8
|
if (price <= 0)
|
|
9
9
|
return null;
|
|
10
10
|
const rawSize = sizeUsd / price;
|
|
11
|
-
//
|
|
12
|
-
const szDecimals = Math.min(getSizeDecimals(longExchange), getSizeDecimals(shortExchange));
|
|
11
|
+
// Use explicitly provided decimals if available, else conservative defaults
|
|
12
|
+
const szDecimals = Math.min(opts?.longSizeDecimals ?? getSizeDecimals(longExchange), opts?.shortSizeDecimals ?? getSizeDecimals(shortExchange));
|
|
13
13
|
// Round DOWN to the least precise exchange's decimals
|
|
14
14
|
const factor = Math.pow(10, szDecimals);
|
|
15
15
|
const roundedSize = Math.floor(rawSize * factor) / factor;
|
|
@@ -30,12 +30,15 @@ export function computeMatchedSize(sizeUsd, price, longExchange, shortExchange)
|
|
|
30
30
|
}
|
|
31
31
|
return { size: roundedSize.toFixed(szDecimals), notional };
|
|
32
32
|
}
|
|
33
|
-
/** Size decimal precision by exchange */
|
|
33
|
+
/** Size decimal precision by exchange (conservative fallbacks — prefer explicit decimals via opts) */
|
|
34
34
|
function getSizeDecimals(exchange) {
|
|
35
35
|
switch (exchange.toLowerCase()) {
|
|
36
|
-
case "hyperliquid": return
|
|
36
|
+
case "hyperliquid": return 2; // HL perps range 0-5; 2 is safe middle ground
|
|
37
37
|
case "lighter": return 2;
|
|
38
38
|
case "pacifica": return 4;
|
|
39
|
+
// Spot exchanges
|
|
40
|
+
case "spot:hyperliquid": return 2;
|
|
41
|
+
case "spot:lighter": return 2;
|
|
39
42
|
default: return 2;
|
|
40
43
|
}
|
|
41
44
|
}
|
|
@@ -45,9 +48,39 @@ function getMinNotional(exchange) {
|
|
|
45
48
|
case "hyperliquid": return 10;
|
|
46
49
|
case "lighter": return 10;
|
|
47
50
|
case "pacifica": return 1;
|
|
51
|
+
case "spot:hyperliquid": return 10;
|
|
52
|
+
case "spot:lighter": return 10;
|
|
48
53
|
default: return 10;
|
|
49
54
|
}
|
|
50
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Compute matched size for spot-perp arb.
|
|
58
|
+
* Uses spot exchange's size decimals for the spot leg.
|
|
59
|
+
*/
|
|
60
|
+
export function computeSpotPerpMatchedSize(sizeUsd, price, spotExchange, perpExchange, spotSizeDecimals) {
|
|
61
|
+
if (price <= 0)
|
|
62
|
+
return null;
|
|
63
|
+
// If explicit spot decimals provided, use them; otherwise derive from exchange
|
|
64
|
+
const spotDec = spotSizeDecimals ?? getSizeDecimals(`spot:${spotExchange}`);
|
|
65
|
+
const perpDec = getSizeDecimals(perpExchange);
|
|
66
|
+
const szDecimals = Math.min(spotDec, perpDec);
|
|
67
|
+
const rawSize = sizeUsd / price;
|
|
68
|
+
const factor = Math.pow(10, szDecimals);
|
|
69
|
+
const roundedSize = Math.floor(rawSize * factor) / factor;
|
|
70
|
+
if (roundedSize <= 0)
|
|
71
|
+
return null;
|
|
72
|
+
const notional = roundedSize * price;
|
|
73
|
+
const minNotional = Math.max(getMinNotional(`spot:${spotExchange}`), getMinNotional(perpExchange));
|
|
74
|
+
if (notional < minNotional) {
|
|
75
|
+
const roundedUp = Math.ceil(rawSize * factor) / factor;
|
|
76
|
+
const notionalUp = roundedUp * price;
|
|
77
|
+
if (notionalUp <= sizeUsd * 1.2) {
|
|
78
|
+
return { size: roundedUp.toFixed(szDecimals), notional: notionalUp };
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return { size: roundedSize.toFixed(szDecimals), notional };
|
|
83
|
+
}
|
|
51
84
|
/**
|
|
52
85
|
* After both legs are submitted, verify actual fills match.
|
|
53
86
|
* If there's a size mismatch, place a correction order on the larger side.
|
package/dist/arb/state.d.ts
CHANGED
|
@@ -13,6 +13,12 @@ export interface ArbPositionState {
|
|
|
13
13
|
entryShortPrice: number;
|
|
14
14
|
accumulatedFunding: number;
|
|
15
15
|
lastCheckTime: string;
|
|
16
|
+
/** "perp-perp" (default) or "spot-perp" */
|
|
17
|
+
mode?: "perp-perp" | "spot-perp";
|
|
18
|
+
/** Spot leg exchange name (for spot-perp mode) */
|
|
19
|
+
spotExchange?: string;
|
|
20
|
+
/** Spot symbol, e.g. "ETH/USDC" (for spot-perp mode) */
|
|
21
|
+
spotSymbol?: string;
|
|
16
22
|
}
|
|
17
23
|
export interface ArbDaemonState {
|
|
18
24
|
version: 1;
|
package/dist/arb/utils.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Arb utilities: settlement timing strategy, basis risk monitoring, and webhook notifications.
|
|
3
3
|
*/
|
|
4
4
|
import type { ExchangeAdapter } from "../exchanges/interface.js";
|
|
5
|
+
/** Settlement schedules per exchange (UTC hours when settlement occurs) */
|
|
6
|
+
export declare const SETTLEMENT_SCHEDULES: Record<string, number[]>;
|
|
5
7
|
export type SettleStrategy = "block" | "aggressive" | "off";
|
|
6
8
|
/**
|
|
7
9
|
* Get the most recent settlement time for an exchange.
|
package/dist/arb/utils.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
// ── Settlement Timing Strategy ──
|
|
5
5
|
/** Settlement schedules per exchange (UTC hours when settlement occurs) */
|
|
6
|
-
const SETTLEMENT_SCHEDULES = {
|
|
6
|
+
export const SETTLEMENT_SCHEDULES = {
|
|
7
7
|
hyperliquid: Array.from({ length: 24 }, (_, i) => i), // every hour
|
|
8
8
|
pacifica: Array.from({ length: 24 }, (_, i) => i), // every hour
|
|
9
9
|
lighter: Array.from({ length: 24 }, (_, i) => i), // every hour
|
package/dist/cli-spec.d.ts
CHANGED
package/dist/cli-spec.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a structured CLI spec from Commander's program tree.
|
|
3
|
+
* Used by `perp api-spec` so agents can discover all commands programmatically.
|
|
4
|
+
*/
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const { version: pkgVersion } = require("../package.json");
|
|
1
8
|
function extractCommand(cmd) {
|
|
2
9
|
const args = cmd._args ?? [];
|
|
3
10
|
const options = cmd.options.map((opt) => ({
|
|
@@ -27,7 +34,7 @@ export function getCliSpec(program) {
|
|
|
27
34
|
}));
|
|
28
35
|
return {
|
|
29
36
|
name: "perp",
|
|
30
|
-
version:
|
|
37
|
+
version: pkgVersion,
|
|
31
38
|
description: "Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter)",
|
|
32
39
|
globalOptions,
|
|
33
40
|
commands,
|
package/dist/commands/agent.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { printJson, jsonOk } from "../utils.js";
|
|
2
3
|
import { ERROR_CODES } from "../errors.js";
|
|
3
4
|
import chalk from "chalk";
|
|
5
|
+
const _require = createRequire(import.meta.url);
|
|
6
|
+
const _pkg = _require("../../package.json");
|
|
4
7
|
/** Infer parameter type from commander flags/description */
|
|
5
8
|
function inferType(flags, desc) {
|
|
6
9
|
if (flags.includes("[boolean]") || !flags.includes("<") && !flags.includes("["))
|
|
@@ -72,7 +75,7 @@ export function registerAgentCommands(program, getAdapter, isJson) {
|
|
|
72
75
|
}
|
|
73
76
|
const envelope = {
|
|
74
77
|
schemaVersion: "2.0",
|
|
75
|
-
cliVersion:
|
|
78
|
+
cliVersion: _pkg.version,
|
|
76
79
|
generatedAt: new Date().toISOString(),
|
|
77
80
|
exchanges: ["pacifica", "hyperliquid", "lighter"],
|
|
78
81
|
errorCodes: errorCodeDocs,
|
|
@@ -93,7 +96,7 @@ export function registerAgentCommands(program, getAdapter, isJson) {
|
|
|
93
96
|
}
|
|
94
97
|
const envelope = {
|
|
95
98
|
schemaVersion: "2.0",
|
|
96
|
-
cliVersion:
|
|
99
|
+
cliVersion: _pkg.version,
|
|
97
100
|
generatedAt: new Date().toISOString(),
|
|
98
101
|
exchanges: ["pacifica", "hyperliquid", "lighter"],
|
|
99
102
|
errorCodes: errorCodeDocs,
|
|
@@ -110,7 +113,7 @@ export function registerAgentCommands(program, getAdapter, isJson) {
|
|
|
110
113
|
.action(() => {
|
|
111
114
|
printJson(jsonOk({
|
|
112
115
|
name: "perp-cli",
|
|
113
|
-
version:
|
|
116
|
+
version: _pkg.version,
|
|
114
117
|
description: "Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter) with HIP-3 deployed dex support",
|
|
115
118
|
exchanges: ["pacifica", "hyperliquid", "lighter"],
|
|
116
119
|
capabilities: [
|
|
@@ -142,14 +145,14 @@ export function registerAgentCommands(program, getAdapter, isJson) {
|
|
|
142
145
|
category: "trading",
|
|
143
146
|
commands: [
|
|
144
147
|
"perp trade check <symbol> <side> <size> --json",
|
|
145
|
-
"perp trade market <symbol> <buy|sell> <size> --json --client-id <id>",
|
|
148
|
+
"perp trade market <symbol> <buy|sell> <size> --json [--smart] --client-id <id>",
|
|
146
149
|
"perp trade limit <symbol> <buy|sell> <price> <size> --json --client-id <id>",
|
|
147
150
|
"perp trade cancel <symbol> <orderId> --json",
|
|
148
151
|
"perp trade cancel-all --json",
|
|
149
|
-
"perp trade close <symbol> --json",
|
|
150
|
-
"perp trade close-all --json",
|
|
151
|
-
"perp trade flatten --json",
|
|
152
|
-
"perp trade reduce <symbol> <percent> --json",
|
|
152
|
+
"perp trade close <symbol> --json [--smart]",
|
|
153
|
+
"perp trade close-all --json [--smart]",
|
|
154
|
+
"perp trade flatten --json [--smart]",
|
|
155
|
+
"perp trade reduce <symbol> <percent> --json [--smart]",
|
|
153
156
|
"perp trade stop <symbol> <side> <stopPrice> <size> --json",
|
|
154
157
|
"perp trade tpsl <symbol> <side> --tp <price> --sl <price> --json",
|
|
155
158
|
"perp trade twap <symbol> <side> <size> <duration> --json",
|
|
@@ -276,7 +279,7 @@ export function registerAgentCommands(program, getAdapter, isJson) {
|
|
|
276
279
|
.action(async () => {
|
|
277
280
|
const results = {
|
|
278
281
|
timestamp: new Date().toISOString(),
|
|
279
|
-
cli_version:
|
|
282
|
+
cli_version: _pkg.version,
|
|
280
283
|
};
|
|
281
284
|
// Check exchange APIs
|
|
282
285
|
const { pingPacifica, pingHyperliquid } = await import("../shared-api.js");
|
|
@@ -4,12 +4,14 @@ import { readExecutionLog, logExecution } from "../../execution-log.js";
|
|
|
4
4
|
import { computeAnnualSpread } from "../../funding.js";
|
|
5
5
|
import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, fetchLighterFundingRates, } from "../../shared-api.js";
|
|
6
6
|
import { computeBasisRisk } from "../../arb-utils.js";
|
|
7
|
+
import { smartOrder } from "../../smart-order.js";
|
|
8
|
+
import { removePosition as persistRemovePosition } from "../../arb-state.js";
|
|
7
9
|
import { fetchAllBalances, computeRebalancePlan } from "../../rebalance.js";
|
|
8
10
|
import { EXCHANGE_TO_CHAIN, getBestQuote } from "../../bridge-engine.js";
|
|
9
11
|
import { computeEnhancedStats } from "../../arb-history-stats.js";
|
|
10
12
|
// ── Helpers ──
|
|
13
|
+
import { DEFAULT_TAKER_FEE as TAKER_FEE } from "../../constants.js";
|
|
11
14
|
const EXCHANGES = ["hyperliquid", "lighter", "pacifica"];
|
|
12
|
-
const TAKER_FEE = 0.00035; // ~0.035% typical taker fee
|
|
13
15
|
function formatDuration(ms) {
|
|
14
16
|
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
15
17
|
const days = Math.floor(hours / 24);
|
|
@@ -293,6 +295,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
293
295
|
.description("Manually close an arb position on both exchanges")
|
|
294
296
|
.option("--dry-run", "Show what would happen without executing")
|
|
295
297
|
.option("--pair <pair>", "Specify arb pair as longExchange:shortExchange (e.g. pacifica:hyperliquid)")
|
|
298
|
+
.option("--smart", "Smart execution: IOC limit at best bid/ask + 1 tick (reduces slippage)")
|
|
296
299
|
.action(async (symbol, opts) => {
|
|
297
300
|
const sym = symbol.toUpperCase();
|
|
298
301
|
const dryRun = !!opts.dryRun || process.argv.includes("--dry-run");
|
|
@@ -436,10 +439,15 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
436
439
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
437
440
|
try {
|
|
438
441
|
const adapter = await getAdapterForExchange(exchange);
|
|
439
|
-
|
|
442
|
+
if (opts.smart) {
|
|
443
|
+
await smartOrder(adapter, rawSymbol, side, String(size), { reduceOnly: true });
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
await adapter.marketOrder(rawSymbol, side, String(size));
|
|
447
|
+
}
|
|
440
448
|
results.push({ exchange, action: `${side} (close ${label})`, status: "success", retries: attempt });
|
|
441
449
|
if (!isJson())
|
|
442
|
-
console.log(chalk.green(` Closed ${label} on ${exchange}: ${side.toUpperCase()} ${size} ${sym}${attempt > 0 ? ` (retry ${attempt})` : ""}`));
|
|
450
|
+
console.log(chalk.green(` Closed ${label} on ${exchange}: ${side.toUpperCase()} ${size} ${sym}${opts.smart ? " (smart)" : ""}${attempt > 0 ? ` (retry ${attempt})` : ""}`));
|
|
443
451
|
return;
|
|
444
452
|
}
|
|
445
453
|
catch (err) {
|
|
@@ -488,6 +496,13 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
488
496
|
console.log(chalk.yellow(`\n Warning: Position may not be fully closed on: ${notClosed.map(v => v.exchange).join(", ")}`));
|
|
489
497
|
}
|
|
490
498
|
}
|
|
499
|
+
// Remove from persisted daemon state (so daemon doesn't try to re-close)
|
|
500
|
+
if (allSuccess) {
|
|
501
|
+
try {
|
|
502
|
+
persistRemovePosition(sym);
|
|
503
|
+
}
|
|
504
|
+
catch { /* state may not exist */ }
|
|
505
|
+
}
|
|
491
506
|
// Log to execution log
|
|
492
507
|
const arbPairId = `${sym}:${longPos.exchange}:${shortPos.exchange}`;
|
|
493
508
|
logExecution({
|