perp-cli 0.4.2 → 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 +46 -5
- package/dist/arb/reconcile.d.ts +28 -0
- package/dist/arb/reconcile.js +162 -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/cli-spec.d.ts +0 -4
- package/dist/cli-spec.js +8 -1
- package/dist/commands/account.d.ts +1 -1
- package/dist/commands/account.js +300 -63
- package/dist/commands/agent.js +16 -9
- package/dist/commands/arb/index.js +243 -49
- package/dist/commands/arb-auto.js +696 -9
- 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/dex.js +7 -1
- package/dist/commands/funding.d.ts +2 -1
- package/dist/commands/funding.js +211 -4
- 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 +6 -4
- package/dist/commands/market.js +35 -2
- 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/risk.d.ts +2 -0
- package/dist/commands/risk.js +56 -0
- package/dist/commands/settings.js +147 -0
- package/dist/commands/stream.js +132 -55
- package/dist/commands/trade.d.ts +1 -1
- package/dist/commands/trade.js +270 -267
- package/dist/commands/wallet.d.ts +4 -0
- package/dist/commands/wallet.js +11 -1
- package/dist/config.d.ts +6 -1
- package/dist/config.js +21 -1
- 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 +69 -0
- package/dist/exchanges/hyperliquid-spot.js +309 -0
- package/dist/exchanges/hyperliquid.d.ts +26 -5
- package/dist/exchanges/hyperliquid.js +171 -73
- package/dist/exchanges/interface.d.ts +1 -1
- package/dist/exchanges/lighter-spot.d.ts +55 -0
- package/dist/exchanges/lighter-spot.js +415 -0
- package/dist/exchanges/lighter.d.ts +10 -2
- package/dist/exchanges/lighter.js +210 -56
- package/dist/exchanges/pacifica.d.ts +11 -4
- package/dist/exchanges/pacifica.js +64 -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 +126 -161
- package/dist/liquidity.d.ts +12 -0
- package/dist/liquidity.js +43 -0
- package/dist/mcp-server.js +43 -96
- 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.js +14 -3
- package/dist/strategies/split-order.d.ts +49 -0
- package/dist/strategies/split-order.js +138 -0
- 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 +80 -4
- package/skills/perp-cli/references/agent-operations.md +19 -9
- package/skills/perp-cli/references/commands.md +54 -32
- package/skills/perp-cli/references/strategies.md +27 -17
- 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/dist/index.js
CHANGED
|
@@ -7,39 +7,35 @@ config({ path: resolve(process.env.HOME || "~", ".perp", ".env") });
|
|
|
7
7
|
config();
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import chalk from "chalk";
|
|
10
|
-
import {
|
|
10
|
+
import { Keypair } from "@solana/web3.js";
|
|
11
|
+
import { tryLoadPrivateKey, parseSolanaKeypair } from "./config.js";
|
|
11
12
|
import { PacificaAdapter } from "./exchanges/pacifica.js";
|
|
12
13
|
import { HyperliquidAdapter } from "./exchanges/hyperliquid.js";
|
|
13
14
|
import { registerMarketCommands } from "./commands/market.js";
|
|
14
15
|
import { registerAccountCommands } from "./commands/account.js";
|
|
15
16
|
import { registerTradeCommands } from "./commands/trade.js";
|
|
16
17
|
import { registerManageCommands } from "./commands/manage.js";
|
|
17
|
-
|
|
18
|
+
// stream commands removed — WS feeds still used by dashboard/event-stream internally
|
|
18
19
|
import { registerArbCommands } from "./commands/arb.js";
|
|
19
20
|
import { registerWalletCommands } from "./commands/wallet.js";
|
|
20
21
|
import { registerBridgeCommands } from "./commands/bridge.js";
|
|
21
|
-
|
|
22
|
-
import {
|
|
22
|
+
// deposit + withdraw merged into funds
|
|
23
|
+
import { registerFundsCommands } from "./commands/funds.js";
|
|
24
|
+
// alert commands removed
|
|
23
25
|
import { registerArbAutoCommands } from "./commands/arb-auto.js";
|
|
24
|
-
import { registerArbManageCommands } from "./commands/arb
|
|
25
|
-
import { registerGapCommands } from "./commands/gap.js";
|
|
26
|
+
import { registerArbManageCommands } from "./commands/arb/index.js";
|
|
26
27
|
import { registerAgentCommands } from "./commands/agent.js";
|
|
27
|
-
import { registerWithdrawCommands } from "./commands/withdraw.js";
|
|
28
28
|
import { registerRebalanceCommands } from "./commands/rebalance.js";
|
|
29
29
|
import { registerBotCommands } from "./commands/bot.js";
|
|
30
|
-
import { registerHealthCommands } from "./commands/health.js";
|
|
31
|
-
import { registerPortfolioCommands } from "./commands/portfolio.js";
|
|
32
30
|
import { registerRiskCommands } from "./commands/risk.js";
|
|
33
31
|
import { registerHistoryCommands } from "./commands/history.js";
|
|
34
|
-
import { registerAnalyticsCommands } from "./commands/analytics.js";
|
|
35
32
|
import { registerSettingsCommands } from "./commands/settings.js";
|
|
36
|
-
|
|
33
|
+
// dex commands merged into market (hip3) — use --dex flag for markets/balance
|
|
37
34
|
import { registerPlanCommands } from "./commands/plan.js";
|
|
38
|
-
|
|
35
|
+
// funding merged into arb.ts
|
|
39
36
|
import { registerBacktestCommands } from "./commands/backtest.js";
|
|
40
37
|
import { registerDashboardCommands } from "./commands/dashboard.js";
|
|
41
38
|
import { registerInitCommand, EXCHANGE_ENV_MAP, validateKey } from "./commands/init.js";
|
|
42
|
-
import { registerEnvCommands } from "./commands/env.js";
|
|
43
39
|
import { loadSettings, saveSettings } from "./settings.js";
|
|
44
40
|
import { setSharedApiNetwork } from "./shared-api.js";
|
|
45
41
|
const _require = createRequire(import.meta.url);
|
|
@@ -59,6 +55,7 @@ program
|
|
|
59
55
|
.option("--fields <fields>", "Comma-separated fields to include in JSON output (e.g. totalEquity,positions)")
|
|
60
56
|
.option("--ndjson", "Output newline-delimited JSON (one object per line for streaming)")
|
|
61
57
|
.option("--dry-run", "Simulate trades without executing (log as simulated)")
|
|
58
|
+
.option("-w, --wallet <name>", "Use a specific wallet by name (from 'perp wallet list')")
|
|
62
59
|
.option("--dex <name>", "HIP-3 deployed perp dex name (Hyperliquid only)")
|
|
63
60
|
.configureOutput({
|
|
64
61
|
writeErr: (str) => {
|
|
@@ -80,71 +77,83 @@ let _adapter = null;
|
|
|
80
77
|
let _pacificaAdapter = null;
|
|
81
78
|
let _hlAdapter = null;
|
|
82
79
|
let _lighterAdapter = null;
|
|
80
|
+
/** Map short aliases to canonical exchange names. Full names pass through. */
|
|
81
|
+
function resolveExchangeAlias(name) {
|
|
82
|
+
const aliases = {
|
|
83
|
+
hl: "hyperliquid",
|
|
84
|
+
lt: "lighter",
|
|
85
|
+
pac: "pacifica",
|
|
86
|
+
};
|
|
87
|
+
return aliases[name.toLowerCase()] ?? name.toLowerCase();
|
|
88
|
+
}
|
|
83
89
|
function getExchange() {
|
|
84
|
-
return program.opts().exchange;
|
|
90
|
+
return resolveExchangeAlias(program.opts().exchange);
|
|
85
91
|
}
|
|
86
92
|
async function getAdapter() {
|
|
87
|
-
if (_adapter)
|
|
88
|
-
return _adapter;
|
|
89
93
|
const opts = program.opts();
|
|
90
|
-
const
|
|
94
|
+
const walletName = opts.wallet;
|
|
95
|
+
// Skip cache when --wallet is specified (different wallet = different account)
|
|
96
|
+
if (!walletName && _adapter)
|
|
97
|
+
return _adapter;
|
|
98
|
+
const exchange = resolveExchangeAlias(opts.exchange);
|
|
91
99
|
const network = opts.network;
|
|
92
100
|
const isTestnet = network === "testnet";
|
|
101
|
+
// Try to load key — null means no key configured (read-only mode)
|
|
102
|
+
const pk = await tryLoadPrivateKey(exchange, opts.privateKey, walletName);
|
|
93
103
|
switch (exchange) {
|
|
94
104
|
case "pacifica": {
|
|
95
|
-
const
|
|
96
|
-
const keypair = parseSolanaKeypair(pk);
|
|
105
|
+
const keypair = pk ? parseSolanaKeypair(pk) : Keypair.generate();
|
|
97
106
|
const pacNetwork = (isTestnet ? "testnet" : "mainnet");
|
|
98
107
|
const settings = loadSettings();
|
|
99
108
|
const builderCode = process.env.PACIFICA_BUILDER_CODE || settings.referralCodes.pacifica || "PERPCLI";
|
|
100
|
-
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode);
|
|
109
|
+
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode, !!pk);
|
|
101
110
|
_adapter = _pacificaAdapter;
|
|
102
111
|
break;
|
|
103
112
|
}
|
|
104
113
|
case "hyperliquid": {
|
|
105
|
-
|
|
106
|
-
_hlAdapter = new HyperliquidAdapter(pk, isTestnet);
|
|
114
|
+
_hlAdapter = new HyperliquidAdapter(pk ?? undefined, isTestnet);
|
|
107
115
|
if (opts.dex)
|
|
108
116
|
_hlAdapter.setDex(opts.dex);
|
|
109
117
|
await _hlAdapter.init();
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
if (pk) {
|
|
119
|
+
const hlSettings = loadSettings();
|
|
120
|
+
if (hlSettings.referrals && !hlSettings.referralApplied.hyperliquid) {
|
|
121
|
+
const hlRef = process.env.HL_REFERRAL_CODE || hlSettings.referralCodes.hyperliquid;
|
|
122
|
+
if (hlRef) {
|
|
123
|
+
_hlAdapter.autoSetReferrer(hlRef).then(() => {
|
|
124
|
+
const s = loadSettings();
|
|
125
|
+
s.referralApplied.hyperliquid = true;
|
|
126
|
+
saveSettings(s);
|
|
127
|
+
}).catch(() => {
|
|
128
|
+
const s = loadSettings();
|
|
129
|
+
s.referralApplied.hyperliquid = true;
|
|
130
|
+
saveSettings(s);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
135
|
_adapter = _hlAdapter;
|
|
127
136
|
break;
|
|
128
137
|
}
|
|
129
138
|
case "lighter": {
|
|
130
|
-
const pk = await loadPrivateKey("lighter", opts.privateKey);
|
|
131
139
|
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
132
|
-
_lighterAdapter = new LighterAdapter(pk, isTestnet);
|
|
140
|
+
_lighterAdapter = new LighterAdapter(pk ?? "", isTestnet);
|
|
133
141
|
await _lighterAdapter.init();
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
if (pk) {
|
|
143
|
+
const ltSettings = loadSettings();
|
|
144
|
+
if (ltSettings.referrals && !ltSettings.referralApplied.lighter) {
|
|
145
|
+
const ltRef = process.env.LIGHTER_REFERRAL_CODE || ltSettings.referralCodes.lighter;
|
|
146
|
+
if (ltRef) {
|
|
147
|
+
_lighterAdapter.useReferralCode(ltRef).then(() => {
|
|
148
|
+
const s = loadSettings();
|
|
149
|
+
s.referralApplied.lighter = true;
|
|
150
|
+
saveSettings(s);
|
|
151
|
+
}).catch(() => {
|
|
152
|
+
const s = loadSettings();
|
|
153
|
+
s.referralApplied.lighter = true;
|
|
154
|
+
saveSettings(s);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
148
157
|
}
|
|
149
158
|
}
|
|
150
159
|
_adapter = _lighterAdapter;
|
|
@@ -162,7 +171,7 @@ function getAdapterSync() {
|
|
|
162
171
|
return _adapter;
|
|
163
172
|
}
|
|
164
173
|
function isJson() {
|
|
165
|
-
return !!program.opts().json;
|
|
174
|
+
return !!program.opts().json || process.argv.includes("--ndjson");
|
|
166
175
|
}
|
|
167
176
|
function isDryRun() {
|
|
168
177
|
return !!program.opts().dryRun;
|
|
@@ -180,30 +189,32 @@ function getHLAdapter() {
|
|
|
180
189
|
}
|
|
181
190
|
// Register command groups with async adapter getter
|
|
182
191
|
registerMarketCommands(program, getAdapter, isJson);
|
|
183
|
-
registerAccountCommands(program, getAdapter, isJson);
|
|
184
|
-
registerTradeCommands(program, getAdapter, isJson, isDryRun);
|
|
192
|
+
registerAccountCommands(program, getAdapter, isJson, getAdapterForExchange);
|
|
193
|
+
registerTradeCommands(program, getAdapter, isJson, isDryRun, getAdapterForExchange);
|
|
185
194
|
registerManageCommands(program, getAdapter, isJson, getPacificaAdapter);
|
|
186
|
-
|
|
187
|
-
registerArbCommands(program, isJson);
|
|
195
|
+
// stream commands removed
|
|
196
|
+
registerArbCommands(program, isJson, getAdapterForExchange);
|
|
188
197
|
registerWalletCommands(program, isJson);
|
|
189
198
|
registerBridgeCommands(program, isJson);
|
|
190
|
-
|
|
191
|
-
|
|
199
|
+
registerFundsCommands(program, getAdapter, isJson, () => program.opts().network);
|
|
200
|
+
// alert commands removed
|
|
192
201
|
// Helper to get adapter for a specific exchange (used by arb-auto)
|
|
193
|
-
async function getAdapterForExchange(
|
|
202
|
+
async function getAdapterForExchange(rawExchange) {
|
|
203
|
+
const exchange = resolveExchangeAlias(rawExchange);
|
|
194
204
|
const opts = program.opts();
|
|
195
205
|
const network = opts.network;
|
|
196
206
|
const isTestnet = network === "testnet";
|
|
207
|
+
const walletName = opts.wallet;
|
|
208
|
+
const pk = await tryLoadPrivateKey(exchange, opts.privateKey, walletName);
|
|
197
209
|
switch (exchange) {
|
|
198
210
|
case "pacifica": {
|
|
199
211
|
if (_pacificaAdapter)
|
|
200
212
|
return _pacificaAdapter;
|
|
201
|
-
const
|
|
202
|
-
const keypair = parseSolanaKeypair(pk);
|
|
213
|
+
const keypair = pk ? parseSolanaKeypair(pk) : Keypair.generate();
|
|
203
214
|
const pacNetwork = (isTestnet ? "testnet" : "mainnet");
|
|
204
215
|
const s1 = loadSettings();
|
|
205
216
|
const builderCode = process.env.PACIFICA_BUILDER_CODE || s1.referralCodes.pacifica || "PERPCLI";
|
|
206
|
-
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode);
|
|
217
|
+
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode, !!pk);
|
|
207
218
|
if (!_adapter)
|
|
208
219
|
_adapter = _pacificaAdapter;
|
|
209
220
|
return _pacificaAdapter;
|
|
@@ -211,24 +222,25 @@ async function getAdapterForExchange(exchange) {
|
|
|
211
222
|
case "hyperliquid": {
|
|
212
223
|
if (_hlAdapter)
|
|
213
224
|
return _hlAdapter;
|
|
214
|
-
|
|
215
|
-
_hlAdapter = new HyperliquidAdapter(pk, isTestnet);
|
|
225
|
+
_hlAdapter = new HyperliquidAdapter(pk ?? undefined, isTestnet);
|
|
216
226
|
if (opts.dex)
|
|
217
227
|
_hlAdapter.setDex(opts.dex);
|
|
218
228
|
await _hlAdapter.init();
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
if (pk) {
|
|
230
|
+
const s2 = loadSettings();
|
|
231
|
+
if (s2.referrals && !s2.referralApplied.hyperliquid) {
|
|
232
|
+
const hlRef = process.env.HL_REFERRAL_CODE || s2.referralCodes.hyperliquid;
|
|
233
|
+
if (hlRef) {
|
|
234
|
+
_hlAdapter.autoSetReferrer(hlRef).then(() => {
|
|
235
|
+
const s = loadSettings();
|
|
236
|
+
s.referralApplied.hyperliquid = true;
|
|
237
|
+
saveSettings(s);
|
|
238
|
+
}).catch(() => {
|
|
239
|
+
const s = loadSettings();
|
|
240
|
+
s.referralApplied.hyperliquid = true;
|
|
241
|
+
saveSettings(s);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
232
244
|
}
|
|
233
245
|
}
|
|
234
246
|
if (!_adapter)
|
|
@@ -238,23 +250,24 @@ async function getAdapterForExchange(exchange) {
|
|
|
238
250
|
case "lighter": {
|
|
239
251
|
if (_lighterAdapter)
|
|
240
252
|
return _lighterAdapter;
|
|
241
|
-
const pk = await loadPrivateKey("lighter", opts.privateKey);
|
|
242
253
|
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
243
|
-
_lighterAdapter = new LighterAdapter(pk, isTestnet);
|
|
254
|
+
_lighterAdapter = new LighterAdapter(pk ?? "", isTestnet);
|
|
244
255
|
await _lighterAdapter.init();
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
if (pk) {
|
|
257
|
+
const s3 = loadSettings();
|
|
258
|
+
if (s3.referrals && !s3.referralApplied.lighter) {
|
|
259
|
+
const ltRef = process.env.LIGHTER_REFERRAL_CODE || s3.referralCodes.lighter;
|
|
260
|
+
if (ltRef) {
|
|
261
|
+
_lighterAdapter.useReferralCode(ltRef).then(() => {
|
|
262
|
+
const s = loadSettings();
|
|
263
|
+
s.referralApplied.lighter = true;
|
|
264
|
+
saveSettings(s);
|
|
265
|
+
}).catch(() => {
|
|
266
|
+
const s = loadSettings();
|
|
267
|
+
s.referralApplied.lighter = true;
|
|
268
|
+
saveSettings(s);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
258
271
|
}
|
|
259
272
|
}
|
|
260
273
|
if (!_adapter)
|
|
@@ -271,8 +284,9 @@ async function getHLAdapterForDex(dex) {
|
|
|
271
284
|
if (_dexAdapters.has(dex))
|
|
272
285
|
return _dexAdapters.get(dex);
|
|
273
286
|
const opts = program.opts();
|
|
274
|
-
const
|
|
275
|
-
const
|
|
287
|
+
const walletName = opts.wallet;
|
|
288
|
+
const pk = await tryLoadPrivateKey("hyperliquid", opts.privateKey, walletName);
|
|
289
|
+
const adapter = new HyperliquidAdapter(pk ?? undefined, opts.network === "testnet");
|
|
276
290
|
if (dex !== "hl")
|
|
277
291
|
adapter.setDex(dex);
|
|
278
292
|
await adapter.init();
|
|
@@ -281,92 +295,43 @@ async function getHLAdapterForDex(dex) {
|
|
|
281
295
|
}
|
|
282
296
|
registerArbAutoCommands(program, getAdapterForExchange, isJson, getHLAdapterForDex);
|
|
283
297
|
registerArbManageCommands(program, getAdapterForExchange, isJson);
|
|
284
|
-
registerGapCommands(program, isJson);
|
|
285
298
|
registerAgentCommands(program, getAdapter, isJson);
|
|
286
|
-
|
|
299
|
+
// withdraw merged into funds
|
|
287
300
|
registerRebalanceCommands(program, getAdapterForExchange, isJson);
|
|
288
301
|
// Jobs & strategies
|
|
289
302
|
import { registerJobsCommands } from "./commands/jobs.js";
|
|
290
|
-
import { registerRunCommands } from "./commands/run.js";
|
|
291
303
|
registerJobsCommands(program, isJson);
|
|
292
|
-
registerRunCommands(program, getAdapter, getAdapterForExchange, isJson);
|
|
293
304
|
registerBotCommands(program, getAdapter, getAdapterForExchange, isJson);
|
|
294
305
|
// Agent-friendly commands
|
|
295
|
-
registerHealthCommands(program, isJson);
|
|
296
|
-
registerPortfolioCommands(program, getAdapterForExchange, isJson);
|
|
297
306
|
registerRiskCommands(program, getAdapterForExchange, isJson);
|
|
298
|
-
registerHistoryCommands(program, isJson);
|
|
299
|
-
registerAnalyticsCommands(program, getAdapterForExchange, isJson);
|
|
307
|
+
registerHistoryCommands(program, isJson, getAdapterForExchange);
|
|
300
308
|
registerSettingsCommands(program, isJson, getAdapterForExchange);
|
|
301
|
-
|
|
309
|
+
// dex commands removed — use 'market hip3' + --dex flag
|
|
302
310
|
registerPlanCommands(program, getAdapter, isJson);
|
|
303
|
-
registerFundingCommands
|
|
311
|
+
// funding merged into arb — registerFundingCommands removed
|
|
304
312
|
registerBacktestCommands(program, isJson);
|
|
305
313
|
registerDashboardCommands(program, getAdapterForExchange, isJson, getHLAdapterForDex);
|
|
306
314
|
registerInitCommand(program);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
program
|
|
315
|
+
// Agent discovery: perp api-spec — deprecated, use 'perp agent schema'
|
|
316
|
+
const apiSpecCmd = program
|
|
310
317
|
.command("api-spec")
|
|
311
|
-
.description("
|
|
318
|
+
.description("Use 'perp agent schema'")
|
|
312
319
|
.action(async () => {
|
|
313
320
|
const { jsonOk, printJson } = await import("./utils.js");
|
|
314
321
|
const { getCliSpec } = await import("./cli-spec.js");
|
|
315
322
|
printJson(jsonOk(getCliSpec(program)));
|
|
316
323
|
});
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
const [balance, positions, orders] = await Promise.all([
|
|
326
|
-
adapter.getBalance(),
|
|
327
|
-
adapter.getPositions(),
|
|
328
|
-
adapter.getOpenOrders(),
|
|
329
|
-
]);
|
|
330
|
-
if (json) {
|
|
331
|
-
const { jsonOk, printJson } = await import("./utils.js");
|
|
332
|
-
return printJson(jsonOk({ exchange: adapter.name, balance, positions, orders }));
|
|
333
|
-
}
|
|
334
|
-
console.log(chalk.cyan.bold(`\n ${adapter.name.toUpperCase()} Account Status\n`));
|
|
335
|
-
console.log(` Equity: $${Number(balance.equity).toFixed(2)}`);
|
|
336
|
-
console.log(` Available: $${Number(balance.available).toFixed(2)}`);
|
|
337
|
-
console.log(` Margin Used: $${Number(balance.marginUsed).toFixed(2)}`);
|
|
338
|
-
console.log(` Positions: ${positions.length}`);
|
|
339
|
-
console.log(` Open Orders: ${orders.length}`);
|
|
340
|
-
if (positions.length > 0) {
|
|
341
|
-
console.log(chalk.cyan.bold("\n Positions:"));
|
|
342
|
-
positions.forEach((p) => {
|
|
343
|
-
const color = p.side === "long" ? chalk.green : chalk.red;
|
|
344
|
-
const pnlNum = Number(p.unrealizedPnl);
|
|
345
|
-
const pnlColor = pnlNum >= 0 ? chalk.green : chalk.red;
|
|
346
|
-
console.log(` ${color(p.side.toUpperCase().padEnd(5))} ${chalk.white(p.symbol.padEnd(12))} ${p.size.padEnd(10)} entry: $${Number(p.entryPrice).toFixed(2)} pnl: ${pnlColor(pnlNum >= 0 ? "+" : "")}$${pnlNum.toFixed(2)}`);
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
if (orders.length > 0) {
|
|
350
|
-
console.log(chalk.cyan.bold("\n Open Orders:"));
|
|
351
|
-
orders.forEach((o) => {
|
|
352
|
-
const color = o.side === "buy" ? chalk.green : chalk.red;
|
|
353
|
-
console.log(` ${color(o.side.toUpperCase().padEnd(4))} ${chalk.white(o.symbol.padEnd(12))} ${o.type.padEnd(8)} $${Number(o.price).toFixed(2)} x ${o.size}`);
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
console.log();
|
|
357
|
-
}
|
|
358
|
-
catch (err) {
|
|
359
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
360
|
-
if (isJson()) {
|
|
361
|
-
const { jsonError } = await import("./utils.js");
|
|
362
|
-
console.log(JSON.stringify(jsonError("COMMAND_ERROR", msg)));
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
console.error(chalk.red(`Error: ${msg}`));
|
|
366
|
-
}
|
|
367
|
-
process.exit(1);
|
|
324
|
+
apiSpecCmd._hidden = true;
|
|
325
|
+
// Deprecated: perp status → use 'perp portfolio' or 'perp account'
|
|
326
|
+
const statusCmd = program.command("status").description("Use 'perp portfolio'")
|
|
327
|
+
.option("--health", "Check connectivity").action(async (opts) => {
|
|
328
|
+
if (opts.health) {
|
|
329
|
+
const { runHealthCheck } = await import("./commands/risk.js");
|
|
330
|
+
return runHealthCheck(isJson);
|
|
368
331
|
}
|
|
332
|
+
console.log("Use 'perp portfolio' or 'perp account' instead.");
|
|
369
333
|
});
|
|
334
|
+
statusCmd._hidden = true;
|
|
370
335
|
// Switch shared API URLs if --network testnet is used
|
|
371
336
|
program.hook("preAction", () => {
|
|
372
337
|
const network = program.opts().network;
|
|
@@ -375,7 +340,7 @@ program.hook("preAction", () => {
|
|
|
375
340
|
});
|
|
376
341
|
// Smart landing page: `perp` with no subcommand
|
|
377
342
|
const rawArgs = process.argv.slice(2);
|
|
378
|
-
const hasSubcommand = rawArgs.some((a) => !a.startsWith("-") && !["pacifica", "hyperliquid", "lighter", "mainnet", "testnet"].includes(a));
|
|
343
|
+
const hasSubcommand = rawArgs.some((a) => !a.startsWith("-") && !["pacifica", "hyperliquid", "lighter", "hl", "lt", "pac", "mainnet", "testnet"].includes(a));
|
|
379
344
|
if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawArgs.includes("--help") && !rawArgs.includes("-V") && !rawArgs.includes("--version"))) {
|
|
380
345
|
// No subcommand — show smart landing instead of help dump
|
|
381
346
|
(async () => {
|
package/dist/liquidity.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExchangeAdapter } from "./exchanges/interface.js";
|
|
2
|
+
import type { SpotAdapter } from "./exchanges/spot-interface.js";
|
|
2
3
|
export interface LiquidityCheck {
|
|
3
4
|
/** Max executable size (base) within slippage tolerance */
|
|
4
5
|
maxSize: number;
|
|
@@ -32,3 +33,14 @@ export declare function checkArbLiquidity(longAdapter: ExchangeAdapter, shortAda
|
|
|
32
33
|
longSlippage: number;
|
|
33
34
|
shortSlippage: number;
|
|
34
35
|
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Check liquidity for a spot+perp arb pair.
|
|
38
|
+
* Spot side uses asks (for buy) or bids (for sell).
|
|
39
|
+
* Perp side uses bids (for short entry) or asks (for long entry).
|
|
40
|
+
*/
|
|
41
|
+
export declare function checkSpotPerpLiquidity(spotAdapter: SpotAdapter, perpAdapter: ExchangeAdapter, spotSymbol: string, perpSymbol: string, sizeUsd: number, maxSlippagePct?: number, log?: (msg: string) => void): Promise<{
|
|
42
|
+
viable: boolean;
|
|
43
|
+
adjustedSizeUsd: number;
|
|
44
|
+
spotSlippage: number;
|
|
45
|
+
perpSlippage: number;
|
|
46
|
+
}>;
|
package/dist/liquidity.js
CHANGED
|
@@ -98,3 +98,46 @@ export async function checkArbLiquidity(longAdapter, shortAdapter, symbol, sizeU
|
|
|
98
98
|
return { viable: false, adjustedSizeUsd: 0, longSlippage: 0, shortSlippage: 0 };
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Check liquidity for a spot+perp arb pair.
|
|
103
|
+
* Spot side uses asks (for buy) or bids (for sell).
|
|
104
|
+
* Perp side uses bids (for short entry) or asks (for long entry).
|
|
105
|
+
*/
|
|
106
|
+
export async function checkSpotPerpLiquidity(spotAdapter, perpAdapter, spotSymbol, perpSymbol, sizeUsd, maxSlippagePct = 0.5, log) {
|
|
107
|
+
try {
|
|
108
|
+
const [spotOB, perpOB] = await Promise.all([
|
|
109
|
+
spotAdapter.getSpotOrderbook(spotSymbol),
|
|
110
|
+
perpAdapter.getOrderbook(perpSymbol),
|
|
111
|
+
]);
|
|
112
|
+
// Spot buy = consume asks; Perp short = consume bids
|
|
113
|
+
const spotCheck = computeExecutableSize(spotOB.asks, sizeUsd, maxSlippagePct);
|
|
114
|
+
const perpCheck = computeExecutableSize(perpOB.bids, sizeUsd, maxSlippagePct);
|
|
115
|
+
// Cross-market price gap check (spot vs perp)
|
|
116
|
+
if (spotOB.asks.length > 0 && perpOB.bids.length > 0) {
|
|
117
|
+
const bestSpotAsk = Number(spotOB.asks[0][0]);
|
|
118
|
+
const bestPerpBid = Number(perpOB.bids[0][0]);
|
|
119
|
+
const gapPct = Math.abs(bestSpotAsk - bestPerpBid) / Math.min(bestSpotAsk, bestPerpBid) * 100;
|
|
120
|
+
if (gapPct > 2) {
|
|
121
|
+
log?.(`[LIQ] spot-perp ${spotSymbol}: price gap ${gapPct.toFixed(2)}% too wide`);
|
|
122
|
+
return { viable: false, adjustedSizeUsd: 0, spotSlippage: spotCheck.slippagePct, perpSlippage: perpCheck.slippagePct };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const executableUsd = Math.min(spotCheck.maxSize * spotCheck.avgFillPrice, perpCheck.maxSize * perpCheck.avgFillPrice);
|
|
126
|
+
const minViable = sizeUsd * 0.2;
|
|
127
|
+
if (executableUsd < minViable) {
|
|
128
|
+
log?.(`[LIQ] spot-perp ${spotSymbol}: only $${executableUsd.toFixed(0)} executable (need min $${minViable.toFixed(0)})`);
|
|
129
|
+
return { viable: false, adjustedSizeUsd: 0, spotSlippage: spotCheck.slippagePct, perpSlippage: perpCheck.slippagePct };
|
|
130
|
+
}
|
|
131
|
+
const adjustedSizeUsd = Math.min(sizeUsd, executableUsd);
|
|
132
|
+
return {
|
|
133
|
+
viable: true,
|
|
134
|
+
adjustedSizeUsd,
|
|
135
|
+
spotSlippage: spotCheck.slippagePct,
|
|
136
|
+
perpSlippage: perpCheck.slippagePct,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
log?.(`[LIQ] spot-perp ${spotSymbol}: orderbook fetch failed`);
|
|
141
|
+
return { viable: false, adjustedSizeUsd: 0, spotSlippage: 0, perpSlippage: 0 };
|
|
142
|
+
}
|
|
143
|
+
}
|