perp-cli 0.3.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/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/__tests__/alert-logic.test.d.ts +1 -0
- package/dist/__tests__/alert-logic.test.js +107 -0
- package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
- package/dist/__tests__/arb-auto-3dex.test.js +397 -0
- package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
- package/dist/__tests__/arb-history-stats.test.js +176 -0
- package/dist/__tests__/arb-logic.test.d.ts +1 -0
- package/dist/__tests__/arb-logic.test.js +84 -0
- package/dist/__tests__/arb-manage.test.d.ts +1 -0
- package/dist/__tests__/arb-manage.test.js +253 -0
- package/dist/__tests__/arb-new-features.test.d.ts +1 -0
- package/dist/__tests__/arb-new-features.test.js +457 -0
- package/dist/__tests__/arb-sizing.test.d.ts +1 -0
- package/dist/__tests__/arb-sizing.test.js +48 -0
- package/dist/__tests__/arb-state.test.d.ts +1 -0
- package/dist/__tests__/arb-state.test.js +284 -0
- package/dist/__tests__/arb-userflow.test.d.ts +1 -0
- package/dist/__tests__/arb-userflow.test.js +945 -0
- package/dist/__tests__/arb-utils.test.d.ts +1 -0
- package/dist/__tests__/arb-utils.test.js +264 -0
- package/dist/__tests__/bot-conditions.test.d.ts +1 -0
- package/dist/__tests__/bot-conditions.test.js +341 -0
- package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
- package/dist/__tests__/client-id-tracker.test.js +137 -0
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
- package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
- package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
- package/dist/__tests__/commands/order-intent.test.js +600 -0
- package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
- package/dist/__tests__/commands/trade-commands.test.js +821 -0
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/config.test.js +86 -0
- package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
- package/dist/__tests__/cross-chain-margin.test.js +287 -0
- package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
- package/dist/__tests__/dex-asset-map.test.js +191 -0
- package/dist/__tests__/errors.test.d.ts +1 -0
- package/dist/__tests__/errors.test.js +110 -0
- package/dist/__tests__/event-stream.test.d.ts +1 -0
- package/dist/__tests__/event-stream.test.js +276 -0
- package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
- package/dist/__tests__/exchanges/interface.test.js +132 -0
- package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
- package/dist/__tests__/exchanges/mock-adapter.js +137 -0
- package/dist/__tests__/execution-log.test.d.ts +1 -0
- package/dist/__tests__/execution-log.test.js +106 -0
- package/dist/__tests__/funding-calc.test.d.ts +1 -0
- package/dist/__tests__/funding-calc.test.js +71 -0
- package/dist/__tests__/funding-history.test.d.ts +1 -0
- package/dist/__tests__/funding-history.test.js +343 -0
- package/dist/__tests__/funding-rates.test.d.ts +1 -0
- package/dist/__tests__/funding-rates.test.js +342 -0
- package/dist/__tests__/funding.test.d.ts +1 -0
- package/dist/__tests__/funding.test.js +173 -0
- package/dist/__tests__/gap-logic.test.d.ts +1 -0
- package/dist/__tests__/gap-logic.test.js +43 -0
- package/dist/__tests__/hip3-dex.test.d.ts +1 -0
- package/dist/__tests__/hip3-dex.test.js +234 -0
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
- package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
- package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge.integration.test.js +309 -0
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
- package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/lighter.integration.test.js +53 -0
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
- package/dist/__tests__/liquidity.test.d.ts +1 -0
- package/dist/__tests__/liquidity.test.js +225 -0
- package/dist/__tests__/plan-executor.test.d.ts +1 -0
- package/dist/__tests__/plan-executor.test.js +314 -0
- package/dist/__tests__/position-history.test.d.ts +1 -0
- package/dist/__tests__/position-history.test.js +367 -0
- package/dist/__tests__/retry.test.d.ts +1 -0
- package/dist/__tests__/retry.test.js +310 -0
- package/dist/__tests__/risk-assessment.test.d.ts +1 -0
- package/dist/__tests__/risk-assessment.test.js +145 -0
- package/dist/__tests__/security-adversarial.test.d.ts +1 -0
- package/dist/__tests__/security-adversarial.test.js +574 -0
- package/dist/__tests__/strategies.test.d.ts +1 -0
- package/dist/__tests__/strategies.test.js +539 -0
- package/dist/__tests__/trade-execution.test.d.ts +1 -0
- package/dist/__tests__/trade-execution.test.js +129 -0
- package/dist/__tests__/trade-validator.test.d.ts +1 -0
- package/dist/__tests__/trade-validator.test.js +655 -0
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +76 -0
- package/dist/api/public/hyperliquid.d.ts +18 -0
- package/dist/api/public/hyperliquid.js +82 -0
- package/dist/api/public/index.d.ts +8 -0
- package/dist/api/public/index.js +8 -0
- package/dist/api/public/lighter.d.ts +24 -0
- package/dist/api/public/lighter.js +100 -0
- package/dist/api/public/pacifica.d.ts +17 -0
- package/dist/api/public/pacifica.js +54 -0
- package/dist/api/public/urls.d.ts +12 -0
- package/dist/api/public/urls.js +33 -0
- package/dist/arb/history-stats.d.ts +44 -0
- package/dist/arb/history-stats.js +135 -0
- package/dist/arb/index.d.ts +4 -0
- package/dist/arb/index.js +4 -0
- package/dist/arb/sizing.d.ts +23 -0
- package/dist/arb/sizing.js +96 -0
- package/dist/arb/state.d.ts +51 -0
- package/dist/arb/state.js +112 -0
- package/dist/arb/utils.d.ts +81 -0
- package/dist/arb/utils.js +267 -0
- package/dist/arb-history-stats.d.ts +5 -0
- package/dist/arb-history-stats.js +5 -0
- package/dist/arb-sizing.d.ts +5 -0
- package/dist/arb-sizing.js +5 -0
- package/dist/arb-state.d.ts +5 -0
- package/dist/arb-state.js +5 -0
- package/dist/arb-utils.d.ts +5 -0
- package/dist/arb-utils.js +5 -0
- package/dist/bot/conditions.d.ts +32 -0
- package/dist/bot/conditions.js +141 -0
- package/dist/bot/config.d.ts +76 -0
- package/dist/bot/config.js +160 -0
- package/dist/bot/engine.d.ts +8 -0
- package/dist/bot/engine.js +519 -0
- package/dist/bot/presets.d.ts +11 -0
- package/dist/bot/presets.js +296 -0
- package/dist/bridge-engine.d.ts +133 -0
- package/dist/bridge-engine.js +1487 -0
- package/dist/cache.d.ts +25 -0
- package/dist/cache.js +99 -0
- package/dist/cli-spec.d.ts +50 -0
- package/dist/cli-spec.js +75 -0
- package/dist/client-id-tracker.d.ts +25 -0
- package/dist/client-id-tracker.js +76 -0
- package/dist/commands/account.d.ts +3 -0
- package/dist/commands/account.js +425 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.js +386 -0
- package/dist/commands/alert.d.ts +2 -0
- package/dist/commands/alert.js +421 -0
- package/dist/commands/analytics.d.ts +3 -0
- package/dist/commands/analytics.js +311 -0
- package/dist/commands/arb/index.d.ts +3 -0
- package/dist/commands/arb/index.js +921 -0
- package/dist/commands/arb-auto.d.ts +54 -0
- package/dist/commands/arb-auto.js +1328 -0
- package/dist/commands/arb-manage.d.ts +5 -0
- package/dist/commands/arb-manage.js +5 -0
- package/dist/commands/arb.d.ts +2 -0
- package/dist/commands/arb.js +347 -0
- package/dist/commands/backtest.d.ts +2 -0
- package/dist/commands/backtest.js +327 -0
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +412 -0
- package/dist/commands/bridge.d.ts +2 -0
- package/dist/commands/bridge.js +396 -0
- package/dist/commands/dashboard.d.ts +3 -0
- package/dist/commands/dashboard.js +176 -0
- package/dist/commands/deposit.d.ts +4 -0
- package/dist/commands/deposit.js +573 -0
- package/dist/commands/dex.d.ts +3 -0
- package/dist/commands/dex.js +114 -0
- package/dist/commands/env.d.ts +2 -0
- package/dist/commands/env.js +136 -0
- package/dist/commands/funding.d.ts +2 -0
- package/dist/commands/funding.js +347 -0
- package/dist/commands/gap.d.ts +2 -0
- package/dist/commands/gap.js +305 -0
- package/dist/commands/health.d.ts +2 -0
- package/dist/commands/health.js +67 -0
- package/dist/commands/history.d.ts +2 -0
- package/dist/commands/history.js +235 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +266 -0
- package/dist/commands/jobs.d.ts +2 -0
- package/dist/commands/jobs.js +133 -0
- package/dist/commands/manage.d.ts +4 -0
- package/dist/commands/manage.js +309 -0
- package/dist/commands/market.d.ts +3 -0
- package/dist/commands/market.js +225 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +95 -0
- package/dist/commands/portfolio.d.ts +3 -0
- package/dist/commands/portfolio.js +169 -0
- package/dist/commands/rebalance.d.ts +3 -0
- package/dist/commands/rebalance.js +293 -0
- package/dist/commands/risk.d.ts +3 -0
- package/dist/commands/risk.js +169 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.js +202 -0
- package/dist/commands/settings.d.ts +2 -0
- package/dist/commands/settings.js +102 -0
- package/dist/commands/stream.d.ts +5 -0
- package/dist/commands/stream.js +123 -0
- package/dist/commands/trade.d.ts +3 -0
- package/dist/commands/trade.js +1273 -0
- package/dist/commands/wallet.d.ts +14 -0
- package/dist/commands/wallet.js +602 -0
- package/dist/commands/withdraw.d.ts +3 -0
- package/dist/commands/withdraw.js +187 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +68 -0
- package/dist/cross-chain-margin.d.ts +46 -0
- package/dist/cross-chain-margin.js +107 -0
- package/dist/dashboard/server.d.ts +80 -0
- package/dist/dashboard/server.js +340 -0
- package/dist/dashboard/ui.d.ts +4 -0
- package/dist/dashboard/ui.js +538 -0
- package/dist/dashboard/ws-feeds.d.ts +29 -0
- package/dist/dashboard/ws-feeds.js +660 -0
- package/dist/dex-asset-map.d.ts +80 -0
- package/dist/dex-asset-map.js +201 -0
- package/dist/errors.d.ts +109 -0
- package/dist/errors.js +84 -0
- package/dist/event-stream.d.ts +25 -0
- package/dist/event-stream.js +168 -0
- package/dist/exchanges/hyperliquid.d.ts +212 -0
- package/dist/exchanges/hyperliquid.js +931 -0
- package/dist/exchanges/interface.d.ts +95 -0
- package/dist/exchanges/interface.js +5 -0
- package/dist/exchanges/lighter.d.ts +159 -0
- package/dist/exchanges/lighter.js +793 -0
- package/dist/exchanges/pacifica.d.ts +51 -0
- package/dist/exchanges/pacifica.js +248 -0
- package/dist/execution-log.d.ts +36 -0
- package/dist/execution-log.js +102 -0
- package/dist/funding/history.d.ts +63 -0
- package/dist/funding/history.js +266 -0
- package/dist/funding/index.d.ts +3 -0
- package/dist/funding/index.js +3 -0
- package/dist/funding/normalize.d.ts +39 -0
- package/dist/funding/normalize.js +66 -0
- package/dist/funding/rates.d.ts +45 -0
- package/dist/funding/rates.js +172 -0
- package/dist/funding-history.d.ts +5 -0
- package/dist/funding-history.js +5 -0
- package/dist/funding-rates.d.ts +5 -0
- package/dist/funding-rates.js +5 -0
- package/dist/funding.d.ts +5 -0
- package/dist/funding.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +458 -0
- package/dist/jobs.d.ts +37 -0
- package/dist/jobs.js +152 -0
- package/dist/liquidity.d.ts +34 -0
- package/dist/liquidity.js +100 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.js +1206 -0
- package/dist/pacifica/client.d.ts +111 -0
- package/dist/pacifica/client.js +310 -0
- package/dist/pacifica/constants.d.ts +27 -0
- package/dist/pacifica/constants.js +47 -0
- package/dist/pacifica/deposit.d.ts +14 -0
- package/dist/pacifica/deposit.js +78 -0
- package/dist/pacifica/index.d.ts +6 -0
- package/dist/pacifica/index.js +11 -0
- package/dist/pacifica/signing.d.ts +49 -0
- package/dist/pacifica/signing.js +97 -0
- package/dist/pacifica/types/account.d.ts +42 -0
- package/dist/pacifica/types/account.js +1 -0
- package/dist/pacifica/types/index.d.ts +6 -0
- package/dist/pacifica/types/index.js +6 -0
- package/dist/pacifica/types/lake.d.ts +18 -0
- package/dist/pacifica/types/lake.js +1 -0
- package/dist/pacifica/types/market.d.ts +64 -0
- package/dist/pacifica/types/market.js +1 -0
- package/dist/pacifica/types/order.d.ts +92 -0
- package/dist/pacifica/types/order.js +1 -0
- package/dist/pacifica/types/position.d.ts +25 -0
- package/dist/pacifica/types/position.js +1 -0
- package/dist/pacifica/types/ws.d.ts +34 -0
- package/dist/pacifica/types/ws.js +41 -0
- package/dist/pacifica/ws-client.d.ts +42 -0
- package/dist/pacifica/ws-client.js +180 -0
- package/dist/plan-executor.d.ts +48 -0
- package/dist/plan-executor.js +280 -0
- package/dist/position-history.d.ts +68 -0
- package/dist/position-history.js +222 -0
- package/dist/rebalance.d.ts +64 -0
- package/dist/rebalance.js +142 -0
- package/dist/retry.d.ts +74 -0
- package/dist/retry.js +129 -0
- package/dist/risk.d.ts +48 -0
- package/dist/risk.js +156 -0
- package/dist/settings.d.ts +19 -0
- package/dist/settings.js +45 -0
- package/dist/shared-api.d.ts +5 -0
- package/dist/shared-api.js +5 -0
- package/dist/strategies/dca.d.ts +25 -0
- package/dist/strategies/dca.js +114 -0
- package/dist/strategies/funding-arb.d.ts +15 -0
- package/dist/strategies/funding-arb.js +281 -0
- package/dist/strategies/grid.d.ts +34 -0
- package/dist/strategies/grid.js +185 -0
- package/dist/strategies/trailing-stop.d.ts +17 -0
- package/dist/strategies/trailing-stop.js +121 -0
- package/dist/strategies/twap.d.ts +20 -0
- package/dist/strategies/twap.js +78 -0
- package/dist/trade-validator.d.ts +39 -0
- package/dist/trade-validator.js +154 -0
- package/dist/utils.d.ts +38 -0
- package/dist/utils.js +110 -0
- package/package.json +63 -0
- package/skills/perp-cli/SKILL.md +149 -0
- package/skills/perp-cli/references/commands.md +143 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { config } from "dotenv";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
// Load ~/.perp/.env first (global), then CWD .env (overrides)
|
|
5
|
+
config({ path: resolve(process.env.HOME || "~", ".perp", ".env") });
|
|
6
|
+
config();
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { loadPrivateKey, parseSolanaKeypair } from "./config.js";
|
|
10
|
+
import { PacificaAdapter } from "./exchanges/pacifica.js";
|
|
11
|
+
import { HyperliquidAdapter } from "./exchanges/hyperliquid.js";
|
|
12
|
+
import { registerMarketCommands } from "./commands/market.js";
|
|
13
|
+
import { registerAccountCommands } from "./commands/account.js";
|
|
14
|
+
import { registerTradeCommands } from "./commands/trade.js";
|
|
15
|
+
import { registerManageCommands } from "./commands/manage.js";
|
|
16
|
+
import { registerStreamCommands } from "./commands/stream.js";
|
|
17
|
+
import { registerArbCommands } from "./commands/arb.js";
|
|
18
|
+
import { registerWalletCommands } from "./commands/wallet.js";
|
|
19
|
+
import { registerBridgeCommands } from "./commands/bridge.js";
|
|
20
|
+
import { registerDepositCommands } from "./commands/deposit.js";
|
|
21
|
+
import { registerAlertCommands } from "./commands/alert.js";
|
|
22
|
+
import { registerArbAutoCommands } from "./commands/arb-auto.js";
|
|
23
|
+
import { registerArbManageCommands } from "./commands/arb-manage.js";
|
|
24
|
+
import { registerGapCommands } from "./commands/gap.js";
|
|
25
|
+
import { registerAgentCommands } from "./commands/agent.js";
|
|
26
|
+
import { registerWithdrawCommands } from "./commands/withdraw.js";
|
|
27
|
+
import { registerRebalanceCommands } from "./commands/rebalance.js";
|
|
28
|
+
import { registerBotCommands } from "./commands/bot.js";
|
|
29
|
+
import { registerHealthCommands } from "./commands/health.js";
|
|
30
|
+
import { registerPortfolioCommands } from "./commands/portfolio.js";
|
|
31
|
+
import { registerRiskCommands } from "./commands/risk.js";
|
|
32
|
+
import { registerHistoryCommands } from "./commands/history.js";
|
|
33
|
+
import { registerAnalyticsCommands } from "./commands/analytics.js";
|
|
34
|
+
import { registerSettingsCommands } from "./commands/settings.js";
|
|
35
|
+
import { registerDexCommands } from "./commands/dex.js";
|
|
36
|
+
import { registerPlanCommands } from "./commands/plan.js";
|
|
37
|
+
import { registerFundingCommands } from "./commands/funding.js";
|
|
38
|
+
import { registerBacktestCommands } from "./commands/backtest.js";
|
|
39
|
+
import { registerDashboardCommands } from "./commands/dashboard.js";
|
|
40
|
+
import { registerInitCommand, EXCHANGE_ENV_MAP, validateKey } from "./commands/init.js";
|
|
41
|
+
import { registerEnvCommands } from "./commands/env.js";
|
|
42
|
+
import { loadSettings, saveSettings } from "./settings.js";
|
|
43
|
+
import { setSharedApiNetwork } from "./shared-api.js";
|
|
44
|
+
const program = new Command();
|
|
45
|
+
// Resolve default exchange from settings (fallback: "pacifica")
|
|
46
|
+
const _settings = loadSettings();
|
|
47
|
+
const _defaultExchange = _settings.defaultExchange || "pacifica";
|
|
48
|
+
program
|
|
49
|
+
.name("perp")
|
|
50
|
+
.description("Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter)")
|
|
51
|
+
.version("0.3.3")
|
|
52
|
+
.option("-e, --exchange <exchange>", `Exchange: pacifica, hyperliquid, lighter (default: ${_defaultExchange})`, _defaultExchange)
|
|
53
|
+
.option("-n, --network <network>", "Network: mainnet or testnet", "mainnet")
|
|
54
|
+
.option("-k, --private-key <key>", "Private key")
|
|
55
|
+
.option("--json", "Output raw JSON (for piping)")
|
|
56
|
+
.option("--dry-run", "Simulate trades without executing (log as simulated)")
|
|
57
|
+
.option("--dex <name>", "HIP-3 deployed perp dex name (Hyperliquid only)")
|
|
58
|
+
.configureOutput({
|
|
59
|
+
writeErr: (str) => {
|
|
60
|
+
if (process.argv.includes("--json")) {
|
|
61
|
+
const msg = str.replace(/^error:\s*/i, "").trim();
|
|
62
|
+
// Use inline envelope (jsonError import not available in sync context)
|
|
63
|
+
console.log(JSON.stringify({
|
|
64
|
+
ok: false,
|
|
65
|
+
error: { code: "CLI_ERROR", message: msg },
|
|
66
|
+
meta: { timestamp: new Date().toISOString() },
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
process.stderr.write(str);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
let _adapter = null;
|
|
75
|
+
let _pacificaAdapter = null;
|
|
76
|
+
let _hlAdapter = null;
|
|
77
|
+
let _lighterAdapter = null;
|
|
78
|
+
function getExchange() {
|
|
79
|
+
return program.opts().exchange;
|
|
80
|
+
}
|
|
81
|
+
async function getAdapter() {
|
|
82
|
+
if (_adapter)
|
|
83
|
+
return _adapter;
|
|
84
|
+
const opts = program.opts();
|
|
85
|
+
const exchange = opts.exchange;
|
|
86
|
+
const network = opts.network;
|
|
87
|
+
const isTestnet = network === "testnet";
|
|
88
|
+
switch (exchange) {
|
|
89
|
+
case "pacifica": {
|
|
90
|
+
const pk = await loadPrivateKey("pacifica", opts.privateKey);
|
|
91
|
+
const keypair = parseSolanaKeypair(pk);
|
|
92
|
+
const pacNetwork = (isTestnet ? "testnet" : "mainnet");
|
|
93
|
+
const settings = loadSettings();
|
|
94
|
+
const builderCode = process.env.PACIFICA_BUILDER_CODE || settings.referralCodes.pacifica || "PERPCLI";
|
|
95
|
+
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode);
|
|
96
|
+
_adapter = _pacificaAdapter;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "hyperliquid": {
|
|
100
|
+
const pk = await loadPrivateKey("hyperliquid", opts.privateKey);
|
|
101
|
+
_hlAdapter = new HyperliquidAdapter(pk, isTestnet);
|
|
102
|
+
if (opts.dex)
|
|
103
|
+
_hlAdapter.setDex(opts.dex);
|
|
104
|
+
await _hlAdapter.init();
|
|
105
|
+
const hlSettings = loadSettings();
|
|
106
|
+
if (hlSettings.referrals && !hlSettings.referralApplied.hyperliquid) {
|
|
107
|
+
const hlRef = process.env.HL_REFERRAL_CODE || hlSettings.referralCodes.hyperliquid;
|
|
108
|
+
if (hlRef) {
|
|
109
|
+
_hlAdapter.autoSetReferrer(hlRef).then(() => {
|
|
110
|
+
const s = loadSettings();
|
|
111
|
+
s.referralApplied.hyperliquid = true;
|
|
112
|
+
saveSettings(s);
|
|
113
|
+
}).catch(() => {
|
|
114
|
+
// Already referred or API error — mark as done either way
|
|
115
|
+
const s = loadSettings();
|
|
116
|
+
s.referralApplied.hyperliquid = true;
|
|
117
|
+
saveSettings(s);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
_adapter = _hlAdapter;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case "lighter": {
|
|
125
|
+
const pk = await loadPrivateKey("lighter", opts.privateKey);
|
|
126
|
+
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
127
|
+
_lighterAdapter = new LighterAdapter(pk, isTestnet);
|
|
128
|
+
await _lighterAdapter.init();
|
|
129
|
+
const ltSettings = loadSettings();
|
|
130
|
+
if (ltSettings.referrals && !ltSettings.referralApplied.lighter) {
|
|
131
|
+
const ltRef = process.env.LIGHTER_REFERRAL_CODE || ltSettings.referralCodes.lighter;
|
|
132
|
+
if (ltRef) {
|
|
133
|
+
_lighterAdapter.useReferralCode(ltRef).then(() => {
|
|
134
|
+
const s = loadSettings();
|
|
135
|
+
s.referralApplied.lighter = true;
|
|
136
|
+
saveSettings(s);
|
|
137
|
+
}).catch(() => {
|
|
138
|
+
// Already referred or API error — mark as done either way
|
|
139
|
+
const s = loadSettings();
|
|
140
|
+
s.referralApplied.lighter = true;
|
|
141
|
+
saveSettings(s);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
_adapter = _lighterAdapter;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
default:
|
|
149
|
+
throw new Error(`Unknown exchange: ${exchange}`);
|
|
150
|
+
}
|
|
151
|
+
return _adapter;
|
|
152
|
+
}
|
|
153
|
+
// Sync wrapper for commands that need adapter (lazy init)
|
|
154
|
+
function getAdapterSync() {
|
|
155
|
+
if (!_adapter)
|
|
156
|
+
throw new Error("Adapter not initialized");
|
|
157
|
+
return _adapter;
|
|
158
|
+
}
|
|
159
|
+
function isJson() {
|
|
160
|
+
return !!program.opts().json;
|
|
161
|
+
}
|
|
162
|
+
function isDryRun() {
|
|
163
|
+
return !!program.opts().dryRun;
|
|
164
|
+
}
|
|
165
|
+
// Helper to get PacificaAdapter specifically (for Pacifica-only commands)
|
|
166
|
+
function getPacificaAdapter() {
|
|
167
|
+
if (!_pacificaAdapter)
|
|
168
|
+
throw new Error("This command requires --exchange pacifica");
|
|
169
|
+
return _pacificaAdapter;
|
|
170
|
+
}
|
|
171
|
+
function getHLAdapter() {
|
|
172
|
+
if (!_hlAdapter)
|
|
173
|
+
throw new Error("This command requires --exchange hyperliquid");
|
|
174
|
+
return _hlAdapter;
|
|
175
|
+
}
|
|
176
|
+
// Register command groups with async adapter getter
|
|
177
|
+
registerMarketCommands(program, getAdapter, isJson);
|
|
178
|
+
registerAccountCommands(program, getAdapter, isJson);
|
|
179
|
+
registerTradeCommands(program, getAdapter, isJson, isDryRun);
|
|
180
|
+
registerManageCommands(program, getAdapter, isJson, getPacificaAdapter);
|
|
181
|
+
registerStreamCommands(program, () => program.opts().network, getExchange, getAdapter);
|
|
182
|
+
registerArbCommands(program, isJson);
|
|
183
|
+
registerWalletCommands(program, isJson);
|
|
184
|
+
registerBridgeCommands(program, isJson);
|
|
185
|
+
registerDepositCommands(program, getAdapter, isJson, () => program.opts().network);
|
|
186
|
+
registerAlertCommands(program, isJson, getAdapterForExchange);
|
|
187
|
+
// Helper to get adapter for a specific exchange (used by arb-auto)
|
|
188
|
+
async function getAdapterForExchange(exchange) {
|
|
189
|
+
const opts = program.opts();
|
|
190
|
+
const network = opts.network;
|
|
191
|
+
const isTestnet = network === "testnet";
|
|
192
|
+
switch (exchange) {
|
|
193
|
+
case "pacifica": {
|
|
194
|
+
if (_pacificaAdapter)
|
|
195
|
+
return _pacificaAdapter;
|
|
196
|
+
const pk = await loadPrivateKey("pacifica", opts.privateKey);
|
|
197
|
+
const keypair = parseSolanaKeypair(pk);
|
|
198
|
+
const pacNetwork = (isTestnet ? "testnet" : "mainnet");
|
|
199
|
+
const s1 = loadSettings();
|
|
200
|
+
const builderCode = process.env.PACIFICA_BUILDER_CODE || s1.referralCodes.pacifica || "PERPCLI";
|
|
201
|
+
_pacificaAdapter = new PacificaAdapter(keypair, pacNetwork, builderCode);
|
|
202
|
+
if (!_adapter)
|
|
203
|
+
_adapter = _pacificaAdapter;
|
|
204
|
+
return _pacificaAdapter;
|
|
205
|
+
}
|
|
206
|
+
case "hyperliquid": {
|
|
207
|
+
if (_hlAdapter)
|
|
208
|
+
return _hlAdapter;
|
|
209
|
+
const pk = await loadPrivateKey("hyperliquid", opts.privateKey);
|
|
210
|
+
_hlAdapter = new HyperliquidAdapter(pk, isTestnet);
|
|
211
|
+
if (opts.dex)
|
|
212
|
+
_hlAdapter.setDex(opts.dex);
|
|
213
|
+
await _hlAdapter.init();
|
|
214
|
+
const s2 = loadSettings();
|
|
215
|
+
if (s2.referrals && !s2.referralApplied.hyperliquid) {
|
|
216
|
+
const hlRef = process.env.HL_REFERRAL_CODE || s2.referralCodes.hyperliquid;
|
|
217
|
+
if (hlRef) {
|
|
218
|
+
_hlAdapter.autoSetReferrer(hlRef).then(() => {
|
|
219
|
+
const s = loadSettings();
|
|
220
|
+
s.referralApplied.hyperliquid = true;
|
|
221
|
+
saveSettings(s);
|
|
222
|
+
}).catch(() => {
|
|
223
|
+
const s = loadSettings();
|
|
224
|
+
s.referralApplied.hyperliquid = true;
|
|
225
|
+
saveSettings(s);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (!_adapter)
|
|
230
|
+
_adapter = _hlAdapter;
|
|
231
|
+
return _hlAdapter;
|
|
232
|
+
}
|
|
233
|
+
case "lighter": {
|
|
234
|
+
if (_lighterAdapter)
|
|
235
|
+
return _lighterAdapter;
|
|
236
|
+
const pk = await loadPrivateKey("lighter", opts.privateKey);
|
|
237
|
+
const { LighterAdapter } = await import("./exchanges/lighter.js");
|
|
238
|
+
_lighterAdapter = new LighterAdapter(pk, isTestnet);
|
|
239
|
+
await _lighterAdapter.init();
|
|
240
|
+
const s3 = loadSettings();
|
|
241
|
+
if (s3.referrals && !s3.referralApplied.lighter) {
|
|
242
|
+
const ltRef = process.env.LIGHTER_REFERRAL_CODE || s3.referralCodes.lighter;
|
|
243
|
+
if (ltRef) {
|
|
244
|
+
_lighterAdapter.useReferralCode(ltRef).then(() => {
|
|
245
|
+
const s = loadSettings();
|
|
246
|
+
s.referralApplied.lighter = true;
|
|
247
|
+
saveSettings(s);
|
|
248
|
+
}).catch(() => {
|
|
249
|
+
const s = loadSettings();
|
|
250
|
+
s.referralApplied.lighter = true;
|
|
251
|
+
saveSettings(s);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (!_adapter)
|
|
256
|
+
_adapter = _lighterAdapter;
|
|
257
|
+
return _lighterAdapter;
|
|
258
|
+
}
|
|
259
|
+
default:
|
|
260
|
+
throw new Error(`Unknown exchange: ${exchange}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Helper to get an HL adapter configured for a specific HIP-3 dex
|
|
264
|
+
const _dexAdapters = new Map();
|
|
265
|
+
async function getHLAdapterForDex(dex) {
|
|
266
|
+
if (_dexAdapters.has(dex))
|
|
267
|
+
return _dexAdapters.get(dex);
|
|
268
|
+
const opts = program.opts();
|
|
269
|
+
const pk = await loadPrivateKey("hyperliquid", opts.privateKey);
|
|
270
|
+
const adapter = new HyperliquidAdapter(pk, opts.network === "testnet");
|
|
271
|
+
if (dex !== "hl")
|
|
272
|
+
adapter.setDex(dex);
|
|
273
|
+
await adapter.init();
|
|
274
|
+
_dexAdapters.set(dex, adapter);
|
|
275
|
+
return adapter;
|
|
276
|
+
}
|
|
277
|
+
registerArbAutoCommands(program, getAdapterForExchange, isJson, getHLAdapterForDex);
|
|
278
|
+
registerArbManageCommands(program, getAdapterForExchange, isJson);
|
|
279
|
+
registerGapCommands(program, isJson);
|
|
280
|
+
registerAgentCommands(program, getAdapter, isJson);
|
|
281
|
+
registerWithdrawCommands(program, getAdapter, isJson);
|
|
282
|
+
registerRebalanceCommands(program, getAdapterForExchange, isJson);
|
|
283
|
+
// Jobs & strategies
|
|
284
|
+
import { registerJobsCommands } from "./commands/jobs.js";
|
|
285
|
+
import { registerRunCommands } from "./commands/run.js";
|
|
286
|
+
registerJobsCommands(program, isJson);
|
|
287
|
+
registerRunCommands(program, getAdapter, getAdapterForExchange, isJson);
|
|
288
|
+
registerBotCommands(program, getAdapter, getAdapterForExchange, isJson);
|
|
289
|
+
// Agent-friendly commands
|
|
290
|
+
registerHealthCommands(program, isJson);
|
|
291
|
+
registerPortfolioCommands(program, getAdapterForExchange, isJson);
|
|
292
|
+
registerRiskCommands(program, getAdapterForExchange, isJson);
|
|
293
|
+
registerHistoryCommands(program, isJson);
|
|
294
|
+
registerAnalyticsCommands(program, getAdapterForExchange, isJson);
|
|
295
|
+
registerSettingsCommands(program, isJson);
|
|
296
|
+
registerDexCommands(program, getAdapter, isJson);
|
|
297
|
+
registerPlanCommands(program, getAdapter, isJson);
|
|
298
|
+
registerFundingCommands(program, isJson);
|
|
299
|
+
registerBacktestCommands(program, isJson);
|
|
300
|
+
registerDashboardCommands(program, getAdapterForExchange, isJson, getHLAdapterForDex);
|
|
301
|
+
registerInitCommand(program);
|
|
302
|
+
registerEnvCommands(program, isJson);
|
|
303
|
+
// Agent discovery: perp api-spec — returns full CLI spec as JSON
|
|
304
|
+
program
|
|
305
|
+
.command("api-spec")
|
|
306
|
+
.description("Return full CLI command spec as JSON (for agent discovery)")
|
|
307
|
+
.action(async () => {
|
|
308
|
+
const { jsonOk, printJson } = await import("./utils.js");
|
|
309
|
+
const { getCliSpec } = await import("./cli-spec.js");
|
|
310
|
+
printJson(jsonOk(getCliSpec(program)));
|
|
311
|
+
});
|
|
312
|
+
// Status command
|
|
313
|
+
program
|
|
314
|
+
.command("status")
|
|
315
|
+
.description("Quick overview: account + positions + open orders")
|
|
316
|
+
.action(async () => {
|
|
317
|
+
const adapter = await getAdapter();
|
|
318
|
+
const json = isJson();
|
|
319
|
+
try {
|
|
320
|
+
const [balance, positions, orders] = await Promise.all([
|
|
321
|
+
adapter.getBalance(),
|
|
322
|
+
adapter.getPositions(),
|
|
323
|
+
adapter.getOpenOrders(),
|
|
324
|
+
]);
|
|
325
|
+
if (json) {
|
|
326
|
+
const { jsonOk, printJson } = await import("./utils.js");
|
|
327
|
+
return printJson(jsonOk({ exchange: adapter.name, balance, positions, orders }));
|
|
328
|
+
}
|
|
329
|
+
console.log(chalk.cyan.bold(`\n ${adapter.name.toUpperCase()} Account Status\n`));
|
|
330
|
+
console.log(` Equity: $${Number(balance.equity).toFixed(2)}`);
|
|
331
|
+
console.log(` Available: $${Number(balance.available).toFixed(2)}`);
|
|
332
|
+
console.log(` Margin Used: $${Number(balance.marginUsed).toFixed(2)}`);
|
|
333
|
+
console.log(` Positions: ${positions.length}`);
|
|
334
|
+
console.log(` Open Orders: ${orders.length}`);
|
|
335
|
+
if (positions.length > 0) {
|
|
336
|
+
console.log(chalk.cyan.bold("\n Positions:"));
|
|
337
|
+
positions.forEach((p) => {
|
|
338
|
+
const color = p.side === "long" ? chalk.green : chalk.red;
|
|
339
|
+
const pnlNum = Number(p.unrealizedPnl);
|
|
340
|
+
const pnlColor = pnlNum >= 0 ? chalk.green : chalk.red;
|
|
341
|
+
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)}`);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (orders.length > 0) {
|
|
345
|
+
console.log(chalk.cyan.bold("\n Open Orders:"));
|
|
346
|
+
orders.forEach((o) => {
|
|
347
|
+
const color = o.side === "buy" ? chalk.green : chalk.red;
|
|
348
|
+
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}`);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
console.log();
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
355
|
+
if (isJson()) {
|
|
356
|
+
const { jsonError } = await import("./utils.js");
|
|
357
|
+
console.log(JSON.stringify(jsonError("COMMAND_ERROR", msg)));
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
361
|
+
}
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
// Switch shared API URLs if --network testnet is used
|
|
366
|
+
program.hook("preAction", () => {
|
|
367
|
+
const network = program.opts().network;
|
|
368
|
+
if (network === "testnet")
|
|
369
|
+
setSharedApiNetwork("testnet");
|
|
370
|
+
});
|
|
371
|
+
// Smart landing page: `perp` with no subcommand
|
|
372
|
+
const rawArgs = process.argv.slice(2);
|
|
373
|
+
const hasSubcommand = rawArgs.some((a) => !a.startsWith("-") && !["pacifica", "hyperliquid", "lighter", "mainnet", "testnet"].includes(a));
|
|
374
|
+
if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawArgs.includes("--help") && !rawArgs.includes("-V") && !rawArgs.includes("--version"))) {
|
|
375
|
+
// No subcommand — show smart landing instead of help dump
|
|
376
|
+
(async () => {
|
|
377
|
+
try {
|
|
378
|
+
const { getWalletSetupStatus } = await import("./commands/wallet.js");
|
|
379
|
+
const status = getWalletSetupStatus();
|
|
380
|
+
const settings = loadSettings();
|
|
381
|
+
const hasEnvKey = !!(process.env.PRIVATE_KEY || process.env.PACIFICA_PRIVATE_KEY ||
|
|
382
|
+
process.env.HL_PRIVATE_KEY || process.env.HYPERLIQUID_PRIVATE_KEY ||
|
|
383
|
+
process.env.LIGHTER_PRIVATE_KEY);
|
|
384
|
+
if (!status.hasWallets && !hasEnvKey && !settings.defaultExchange) {
|
|
385
|
+
// Fresh install — onboarding
|
|
386
|
+
console.log(chalk.cyan.bold("\n Welcome to perp-cli!") + chalk.gray(" v0.3.3\n"));
|
|
387
|
+
console.log(" Multi-DEX perpetual futures CLI for Pacifica, Hyperliquid, and Lighter.\n");
|
|
388
|
+
console.log(` Get started: ${chalk.cyan("perp init")}`);
|
|
389
|
+
console.log(chalk.gray(`\n Or explore without a wallet:`));
|
|
390
|
+
console.log(` ${chalk.green("perp market list")} available markets`);
|
|
391
|
+
console.log(` ${chalk.green("perp -e hyperliquid market list")} Hyperliquid markets`);
|
|
392
|
+
console.log(` ${chalk.green("perp arb rates")} funding rate comparison`);
|
|
393
|
+
console.log(` ${chalk.green("perp --help")} all commands\n`);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
// Configured — show status overview
|
|
397
|
+
const defaultEx = settings.defaultExchange || "pacifica";
|
|
398
|
+
const activeEntries = Object.entries(status.active);
|
|
399
|
+
console.log(chalk.cyan.bold("\n perp-cli") + chalk.gray(" v0.3.3\n"));
|
|
400
|
+
console.log(` Default exchange: ${chalk.cyan(defaultEx)}`);
|
|
401
|
+
if (activeEntries.length > 0) {
|
|
402
|
+
console.log(chalk.white.bold("\n Wallets:"));
|
|
403
|
+
for (const [exchange, walletName] of activeEntries) {
|
|
404
|
+
const w = status.wallets[walletName];
|
|
405
|
+
if (w) {
|
|
406
|
+
const addr = w.address.length > 20
|
|
407
|
+
? w.address.slice(0, 6) + "..." + w.address.slice(-4)
|
|
408
|
+
: w.address;
|
|
409
|
+
console.log(` ${chalk.cyan(exchange.padEnd(14))} ${chalk.white(walletName)} ${chalk.gray(addr)}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
else if (hasEnvKey) {
|
|
414
|
+
console.log(chalk.white.bold("\n Configured:"));
|
|
415
|
+
for (const [exchange, info] of Object.entries(EXCHANGE_ENV_MAP)) {
|
|
416
|
+
const key = process.env[info.envKey];
|
|
417
|
+
if (key) {
|
|
418
|
+
try {
|
|
419
|
+
const { valid, address } = await validateKey(info.chain, key);
|
|
420
|
+
const addr = valid ? address : "(invalid key)";
|
|
421
|
+
console.log(` ${chalk.cyan(exchange.padEnd(14))} ${chalk.green(addr)}`);
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
console.log(` ${chalk.cyan(exchange.padEnd(14))} ${chalk.gray("(error reading key)")}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
console.log(chalk.white.bold("\n Quick commands:"));
|
|
430
|
+
console.log(` ${chalk.green("perp status")} account overview`);
|
|
431
|
+
console.log(` ${chalk.green("perp market list")} available markets`);
|
|
432
|
+
console.log(` ${chalk.green("perp dashboard")} live monitoring`);
|
|
433
|
+
console.log(` ${chalk.green("perp arb rates")} funding rate comparison`);
|
|
434
|
+
console.log(` ${chalk.green("perp --help")} all commands\n`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
program.help();
|
|
439
|
+
}
|
|
440
|
+
})();
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
program.parseAsync().then(() => {
|
|
444
|
+
// Allow a short delay for any pending output, then exit cleanly.
|
|
445
|
+
// Without this, HL SDK's WebSocket keeps the process alive indefinitely.
|
|
446
|
+
setTimeout(() => process.exit(0), 500);
|
|
447
|
+
}).catch(async (err) => {
|
|
448
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
449
|
+
if (isJson()) {
|
|
450
|
+
const { jsonError } = await import("./utils.js");
|
|
451
|
+
console.log(JSON.stringify(jsonError("FATAL", msg)));
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
console.error(chalk.red(msg));
|
|
455
|
+
}
|
|
456
|
+
process.exit(1);
|
|
457
|
+
});
|
|
458
|
+
}
|
package/dist/jobs.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface JobEntry {
|
|
2
|
+
id: string;
|
|
3
|
+
strategy: string;
|
|
4
|
+
exchange: string;
|
|
5
|
+
params: Record<string, unknown>;
|
|
6
|
+
tmuxSession: string;
|
|
7
|
+
pid?: number;
|
|
8
|
+
startedAt: string;
|
|
9
|
+
status: "running" | "stopped" | "done" | "error";
|
|
10
|
+
result?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export declare function logFile(id: string): string;
|
|
13
|
+
export declare function saveJob(job: JobEntry): void;
|
|
14
|
+
export declare function loadJob(id: string): JobEntry | null;
|
|
15
|
+
export declare function listJobs(): JobEntry[];
|
|
16
|
+
/**
|
|
17
|
+
* Start a job in a tmux background session.
|
|
18
|
+
* Returns the job entry.
|
|
19
|
+
*/
|
|
20
|
+
export declare function startJob(opts: {
|
|
21
|
+
strategy: string;
|
|
22
|
+
exchange: string;
|
|
23
|
+
params: Record<string, unknown>;
|
|
24
|
+
cliArgs: string[];
|
|
25
|
+
}): JobEntry;
|
|
26
|
+
/**
|
|
27
|
+
* Stop a running job.
|
|
28
|
+
*/
|
|
29
|
+
export declare function stopJob(id: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Update job state file (called from within the running job).
|
|
32
|
+
*/
|
|
33
|
+
export declare function updateJobState(id: string, data: Partial<JobEntry>): void;
|
|
34
|
+
/**
|
|
35
|
+
* Remove a job entry.
|
|
36
|
+
*/
|
|
37
|
+
export declare function removeJob(id: string): boolean;
|
package/dist/jobs.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
const JOBS_DIR = join(homedir(), ".perp", "jobs");
|
|
7
|
+
const LOGS_DIR = join(homedir(), ".perp", "logs");
|
|
8
|
+
function ensureDirs() {
|
|
9
|
+
mkdirSync(JOBS_DIR, { recursive: true });
|
|
10
|
+
mkdirSync(LOGS_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
function jobFile(id) {
|
|
13
|
+
return join(JOBS_DIR, `${id}.json`);
|
|
14
|
+
}
|
|
15
|
+
export function logFile(id) {
|
|
16
|
+
return join(LOGS_DIR, `${id}.log`);
|
|
17
|
+
}
|
|
18
|
+
function genId() {
|
|
19
|
+
return randomBytes(4).toString("hex");
|
|
20
|
+
}
|
|
21
|
+
function hasTmux() {
|
|
22
|
+
try {
|
|
23
|
+
execSync("which tmux", { stdio: "ignore" });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function tmuxSessionExists(name) {
|
|
31
|
+
try {
|
|
32
|
+
execSync(`tmux has-session -t ${name} 2>/dev/null`, { stdio: "ignore" });
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function saveJob(job) {
|
|
40
|
+
ensureDirs();
|
|
41
|
+
writeFileSync(jobFile(job.id), JSON.stringify(job, null, 2));
|
|
42
|
+
}
|
|
43
|
+
export function loadJob(id) {
|
|
44
|
+
const path = jobFile(id);
|
|
45
|
+
if (!existsSync(path))
|
|
46
|
+
return null;
|
|
47
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
48
|
+
}
|
|
49
|
+
export function listJobs() {
|
|
50
|
+
ensureDirs();
|
|
51
|
+
const files = readdirSync(JOBS_DIR).filter((f) => f.endsWith(".json"));
|
|
52
|
+
return files.map((f) => {
|
|
53
|
+
const job = JSON.parse(readFileSync(join(JOBS_DIR, f), "utf-8"));
|
|
54
|
+
// Check if tmux session is still alive
|
|
55
|
+
if (job.status === "running" && !tmuxSessionExists(job.tmuxSession)) {
|
|
56
|
+
job.status = "done";
|
|
57
|
+
saveJob(job);
|
|
58
|
+
}
|
|
59
|
+
return job;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
import { readdirSync } from "node:fs";
|
|
63
|
+
/**
|
|
64
|
+
* Start a job in a tmux background session.
|
|
65
|
+
* Returns the job entry.
|
|
66
|
+
*/
|
|
67
|
+
export function startJob(opts) {
|
|
68
|
+
if (!hasTmux()) {
|
|
69
|
+
throw new Error("tmux is required for background jobs. Install with: brew install tmux");
|
|
70
|
+
}
|
|
71
|
+
ensureDirs();
|
|
72
|
+
const id = genId();
|
|
73
|
+
const session = `perp-${id}`;
|
|
74
|
+
const log = logFile(id);
|
|
75
|
+
// Build the command — re-invoke ourselves with `run` subcommand
|
|
76
|
+
const nodeCmd = process.argv[0];
|
|
77
|
+
const cliPath = process.argv[1];
|
|
78
|
+
const envVars = buildEnvString();
|
|
79
|
+
const args = opts.cliArgs.join(" ");
|
|
80
|
+
const cmd = `${envVars} ${nodeCmd} ${cliPath} run ${opts.strategy} ${args} --job-id ${id} 2>&1 | tee -a ${log}`;
|
|
81
|
+
// Create tmux session
|
|
82
|
+
execSync(`tmux new-session -d -s ${session} '${cmd.replace(/'/g, "'\\''")}'`);
|
|
83
|
+
const job = {
|
|
84
|
+
id,
|
|
85
|
+
strategy: opts.strategy,
|
|
86
|
+
exchange: opts.exchange,
|
|
87
|
+
params: opts.params,
|
|
88
|
+
tmuxSession: session,
|
|
89
|
+
startedAt: new Date().toISOString(),
|
|
90
|
+
status: "running",
|
|
91
|
+
};
|
|
92
|
+
saveJob(job);
|
|
93
|
+
return job;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Stop a running job.
|
|
97
|
+
*/
|
|
98
|
+
export function stopJob(id) {
|
|
99
|
+
const job = loadJob(id);
|
|
100
|
+
if (!job)
|
|
101
|
+
return false;
|
|
102
|
+
if (tmuxSessionExists(job.tmuxSession)) {
|
|
103
|
+
try {
|
|
104
|
+
execSync(`tmux kill-session -t ${job.tmuxSession}`);
|
|
105
|
+
}
|
|
106
|
+
catch { /* already dead */ }
|
|
107
|
+
}
|
|
108
|
+
job.status = "stopped";
|
|
109
|
+
saveJob(job);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Update job state file (called from within the running job).
|
|
114
|
+
*/
|
|
115
|
+
export function updateJobState(id, data) {
|
|
116
|
+
const job = loadJob(id);
|
|
117
|
+
if (!job)
|
|
118
|
+
return;
|
|
119
|
+
Object.assign(job, data);
|
|
120
|
+
saveJob(job);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Remove a job entry.
|
|
124
|
+
*/
|
|
125
|
+
export function removeJob(id) {
|
|
126
|
+
const path = jobFile(id);
|
|
127
|
+
if (!existsSync(path))
|
|
128
|
+
return false;
|
|
129
|
+
unlinkSync(path);
|
|
130
|
+
// Also remove log
|
|
131
|
+
const log = logFile(id);
|
|
132
|
+
if (existsSync(log))
|
|
133
|
+
unlinkSync(log);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Build env string to pass through to tmux session.
|
|
138
|
+
*/
|
|
139
|
+
function buildEnvString() {
|
|
140
|
+
const keys = [
|
|
141
|
+
"LIGHTER_PRIVATE_KEY", "LIGHTER_API_KEY", "LIGHTER_ACCOUNT_INDEX", "LIGHTER_API_KEY_INDEX",
|
|
142
|
+
"PRIVATE_KEY", "pk",
|
|
143
|
+
"PACIFICA_BUILDER_CODE", "NEXT_PUBLIC_BUILDER_CODE",
|
|
144
|
+
"HL_REFERRAL_CODE", "LIGHTER_REFERRAL_CODE",
|
|
145
|
+
];
|
|
146
|
+
const parts = [];
|
|
147
|
+
for (const k of keys) {
|
|
148
|
+
if (process.env[k])
|
|
149
|
+
parts.push(`${k}='${process.env[k]}'`);
|
|
150
|
+
}
|
|
151
|
+
return parts.join(" ");
|
|
152
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ExchangeAdapter } from "./exchanges/interface.js";
|
|
2
|
+
export interface LiquidityCheck {
|
|
3
|
+
/** Max executable size (base) within slippage tolerance */
|
|
4
|
+
maxSize: number;
|
|
5
|
+
/** Estimated avg fill price for the given size */
|
|
6
|
+
avgFillPrice: number;
|
|
7
|
+
/** Estimated slippage % vs mid price */
|
|
8
|
+
slippagePct: number;
|
|
9
|
+
/** Total depth in USD on the relevant side */
|
|
10
|
+
depthUsd: number;
|
|
11
|
+
/** Whether the full requested size can be filled */
|
|
12
|
+
canFillFull: boolean;
|
|
13
|
+
/** Recommended size (capped by liquidity) */
|
|
14
|
+
recommendedSize: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Walk through orderbook levels and compute how much can be filled
|
|
18
|
+
* within the given slippage tolerance.
|
|
19
|
+
*
|
|
20
|
+
* @param levels - [[price, size], ...] from getOrderbook (asks for buy, bids for sell)
|
|
21
|
+
* @param maxSlippagePct - max acceptable slippage from best price (default 0.5%)
|
|
22
|
+
* @param requestedSizeUsd - desired position size in USD
|
|
23
|
+
*/
|
|
24
|
+
export declare function computeExecutableSize(levels: [string, string][], requestedSizeUsd: number, maxSlippagePct?: number): LiquidityCheck;
|
|
25
|
+
/**
|
|
26
|
+
* Check liquidity on both sides for an arb entry.
|
|
27
|
+
* Returns adjusted size or 0 if not viable.
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkArbLiquidity(longAdapter: ExchangeAdapter, shortAdapter: ExchangeAdapter, symbol: string, sizeUsd: number, maxSlippagePct?: number, log?: (msg: string) => void): Promise<{
|
|
30
|
+
viable: boolean;
|
|
31
|
+
adjustedSizeUsd: number;
|
|
32
|
+
longSlippage: number;
|
|
33
|
+
shortSlippage: number;
|
|
34
|
+
}>;
|