perp-cli 0.9.8 → 0.12.0

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.
Files changed (254) hide show
  1. package/README.md +30 -25
  2. package/dist/_stderr-filter.d.ts +0 -0
  3. package/dist/_stderr-filter.js +30 -0
  4. package/dist/agent-wallet/expiry.d.ts +15 -0
  5. package/dist/agent-wallet/expiry.js +26 -0
  6. package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
  7. package/dist/agent-wallet/lighter-keystore.js +232 -0
  8. package/dist/agent-wallet/passphrase.d.ts +13 -0
  9. package/dist/agent-wallet/passphrase.js +54 -0
  10. package/dist/agent-wallet/signing-strategy.d.ts +67 -0
  11. package/dist/agent-wallet/signing-strategy.js +51 -0
  12. package/dist/agent-wallet/store.d.ts +37 -0
  13. package/dist/agent-wallet/store.js +172 -0
  14. package/dist/api/public/_http.d.ts +15 -0
  15. package/dist/api/public/_http.js +24 -0
  16. package/dist/api/public/hyperliquid.js +36 -24
  17. package/dist/api/public/lighter.d.ts +7 -1
  18. package/dist/api/public/lighter.js +85 -37
  19. package/dist/api/public/pacifica.js +47 -18
  20. package/dist/bot/engine.js +23 -4
  21. package/dist/bot/presets.js +3 -3
  22. package/dist/bot/strategies/funding-arb-strategy.d.ts +0 -23
  23. package/dist/bot/strategies/funding-arb-strategy.js +41 -176
  24. package/dist/bot/strategies/funding-auto.js +2 -2
  25. package/dist/bridge-engine.js +102 -49
  26. package/dist/cli-spec.js +1 -2
  27. package/dist/commands/account.js +2 -500
  28. package/dist/commands/agent.d.ts +8 -2
  29. package/dist/commands/agent.js +1903 -366
  30. package/dist/commands/alerts.js +3 -1
  31. package/dist/commands/arb/index.js +37 -7
  32. package/dist/commands/arb-auto.js +44 -11
  33. package/dist/commands/arb.js +83 -34
  34. package/dist/commands/bot.d.ts +1 -1
  35. package/dist/commands/bot.js +20 -16
  36. package/dist/commands/bridge.d.ts +1 -1
  37. package/dist/commands/bridge.js +4 -4
  38. package/dist/commands/funds.d.ts +1 -1
  39. package/dist/commands/funds.js +18 -68
  40. package/dist/commands/jobs.d.ts +1 -1
  41. package/dist/commands/jobs.js +2 -2
  42. package/dist/commands/manage.d.ts +1 -1
  43. package/dist/commands/manage.js +12 -84
  44. package/dist/commands/plan.d.ts +1 -1
  45. package/dist/commands/plan.js +2 -2
  46. package/dist/commands/portfolio.d.ts +19 -3
  47. package/dist/commands/portfolio.js +747 -163
  48. package/dist/commands/rebalance.d.ts +1 -1
  49. package/dist/commands/rebalance.js +18 -12
  50. package/dist/commands/risk.js +2 -6
  51. package/dist/commands/settings.js +30 -53
  52. package/dist/commands/trade.js +16 -15
  53. package/dist/commands/wallet.d.ts +2 -1
  54. package/dist/commands/wallet.js +1122 -176
  55. package/dist/config.d.ts +9 -0
  56. package/dist/config.js +55 -9
  57. package/dist/errors.d.ts +64 -0
  58. package/dist/errors.js +82 -2
  59. package/dist/event-stream.js +12 -1
  60. package/dist/exchanges/aster-typed-data.d.ts +156 -0
  61. package/dist/exchanges/aster-typed-data.js +189 -0
  62. package/dist/exchanges/aster.d.ts +60 -11
  63. package/dist/exchanges/aster.js +278 -136
  64. package/dist/exchanges/hyperliquid.d.ts +14 -0
  65. package/dist/exchanges/hyperliquid.js +88 -34
  66. package/dist/exchanges/lighter-spot.js +19 -5
  67. package/dist/exchanges/lighter.d.ts +66 -0
  68. package/dist/exchanges/lighter.js +213 -31
  69. package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
  70. package/dist/exchanges/pacifica-typed-data.js +74 -0
  71. package/dist/exchanges/pacifica.d.ts +29 -1
  72. package/dist/exchanges/pacifica.js +94 -6
  73. package/dist/funding/rates.js +59 -21
  74. package/dist/guardrail/contracts.d.ts +14 -0
  75. package/dist/guardrail/contracts.js +72 -0
  76. package/dist/guardrail/perp-guardrail.d.ts +21 -0
  77. package/dist/guardrail/perp-guardrail.js +145 -0
  78. package/dist/index.d.ts +1 -1
  79. package/dist/index.js +307 -362
  80. package/dist/jobs.js +3 -1
  81. package/dist/mcp-server.js +92 -88
  82. package/dist/position-history.js +47 -4
  83. package/dist/settings.d.ts +69 -3
  84. package/dist/settings.js +3 -2
  85. package/dist/signer/index.d.ts +2 -0
  86. package/dist/signer/index.js +2 -0
  87. package/dist/signer/ows-evm.d.ts +32 -0
  88. package/dist/signer/ows-evm.js +86 -0
  89. package/dist/signer/ows-loader.d.ts +2 -0
  90. package/dist/signer/ows-loader.js +6 -0
  91. package/dist/signer/ows-solana.d.ts +25 -0
  92. package/dist/signer/ows-solana.js +56 -0
  93. package/dist/smart-order.d.ts +8 -2
  94. package/dist/smart-order.js +46 -11
  95. package/dist/strategies/funding-arb.js +29 -8
  96. package/dist/trade-validator.js +30 -5
  97. package/dist/utils.d.ts +10 -0
  98. package/dist/utils.js +19 -0
  99. package/package.json +4 -2
  100. package/skills/perp-cli/SKILL.md +23 -14
  101. package/skills/perp-cli/references/agent-operations.md +6 -4
  102. package/skills/perp-cli/references/commands.md +55 -34
  103. package/skills/perp-cli/scripts/preflight.sh +4 -4
  104. package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
  105. package/dist/__tests__/alert-logic.test.d.ts +0 -1
  106. package/dist/__tests__/alert-logic.test.js +0 -107
  107. package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
  108. package/dist/__tests__/arb-auto-3dex.test.js +0 -397
  109. package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
  110. package/dist/__tests__/arb-history-stats.test.js +0 -176
  111. package/dist/__tests__/arb-logic.test.d.ts +0 -1
  112. package/dist/__tests__/arb-logic.test.js +0 -84
  113. package/dist/__tests__/arb-manage.test.d.ts +0 -1
  114. package/dist/__tests__/arb-manage.test.js +0 -253
  115. package/dist/__tests__/arb-new-features.test.d.ts +0 -1
  116. package/dist/__tests__/arb-new-features.test.js +0 -457
  117. package/dist/__tests__/arb-sizing.test.d.ts +0 -1
  118. package/dist/__tests__/arb-sizing.test.js +0 -48
  119. package/dist/__tests__/arb-state.test.d.ts +0 -1
  120. package/dist/__tests__/arb-state.test.js +0 -284
  121. package/dist/__tests__/arb-userflow.test.d.ts +0 -1
  122. package/dist/__tests__/arb-userflow.test.js +0 -945
  123. package/dist/__tests__/arb-utils.test.d.ts +0 -1
  124. package/dist/__tests__/arb-utils.test.js +0 -264
  125. package/dist/__tests__/bot-conditions.test.d.ts +0 -1
  126. package/dist/__tests__/bot-conditions.test.js +0 -341
  127. package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
  128. package/dist/__tests__/client-id-tracker.test.js +0 -137
  129. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
  130. package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
  131. package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
  132. package/dist/__tests__/commands/order-intent.test.js +0 -600
  133. package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
  134. package/dist/__tests__/commands/trade-commands.test.js +0 -821
  135. package/dist/__tests__/config.test.d.ts +0 -1
  136. package/dist/__tests__/config.test.js +0 -86
  137. package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
  138. package/dist/__tests__/cross-chain-margin.test.js +0 -287
  139. package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
  140. package/dist/__tests__/dex-asset-map.test.js +0 -191
  141. package/dist/__tests__/errors.test.d.ts +0 -1
  142. package/dist/__tests__/errors.test.js +0 -110
  143. package/dist/__tests__/event-stream.test.d.ts +0 -1
  144. package/dist/__tests__/event-stream.test.js +0 -276
  145. package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
  146. package/dist/__tests__/exchanges/interface.test.js +0 -132
  147. package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
  148. package/dist/__tests__/exchanges/mock-adapter.js +0 -137
  149. package/dist/__tests__/execution-log.test.d.ts +0 -1
  150. package/dist/__tests__/execution-log.test.js +0 -106
  151. package/dist/__tests__/funding-calc.test.d.ts +0 -1
  152. package/dist/__tests__/funding-calc.test.js +0 -71
  153. package/dist/__tests__/funding-history.test.d.ts +0 -1
  154. package/dist/__tests__/funding-history.test.js +0 -343
  155. package/dist/__tests__/funding-rates.test.d.ts +0 -1
  156. package/dist/__tests__/funding-rates.test.js +0 -342
  157. package/dist/__tests__/funding.test.d.ts +0 -1
  158. package/dist/__tests__/funding.test.js +0 -173
  159. package/dist/__tests__/gap-logic.test.d.ts +0 -1
  160. package/dist/__tests__/gap-logic.test.js +0 -43
  161. package/dist/__tests__/hip3-dex.test.d.ts +0 -1
  162. package/dist/__tests__/hip3-dex.test.js +0 -234
  163. package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
  164. package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
  165. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
  166. package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
  167. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
  168. package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
  169. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
  170. package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
  171. package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
  172. package/dist/__tests__/integration/bridge.integration.test.js +0 -309
  173. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
  174. package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
  175. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
  176. package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
  177. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
  178. package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
  179. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
  180. package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
  181. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
  182. package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
  183. package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
  184. package/dist/__tests__/integration/lighter.integration.test.js +0 -53
  185. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
  186. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
  187. package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
  188. package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
  189. package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
  190. package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
  191. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
  192. package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
  193. package/dist/__tests__/liquidity.test.d.ts +0 -1
  194. package/dist/__tests__/liquidity.test.js +0 -225
  195. package/dist/__tests__/plan-executor.test.d.ts +0 -1
  196. package/dist/__tests__/plan-executor.test.js +0 -314
  197. package/dist/__tests__/position-history.test.d.ts +0 -1
  198. package/dist/__tests__/position-history.test.js +0 -367
  199. package/dist/__tests__/retry.test.d.ts +0 -1
  200. package/dist/__tests__/retry.test.js +0 -310
  201. package/dist/__tests__/risk-assessment.test.d.ts +0 -1
  202. package/dist/__tests__/risk-assessment.test.js +0 -288
  203. package/dist/__tests__/security-adversarial.test.d.ts +0 -1
  204. package/dist/__tests__/security-adversarial.test.js +0 -574
  205. package/dist/__tests__/strategies.test.d.ts +0 -1
  206. package/dist/__tests__/strategies.test.js +0 -539
  207. package/dist/__tests__/trade-execution.test.d.ts +0 -1
  208. package/dist/__tests__/trade-execution.test.js +0 -129
  209. package/dist/__tests__/trade-validator.test.d.ts +0 -1
  210. package/dist/__tests__/trade-validator.test.js +0 -655
  211. package/dist/__tests__/utils.test.d.ts +0 -1
  212. package/dist/__tests__/utils.test.js +0 -76
  213. package/dist/arb/reconcile.d.ts +0 -28
  214. package/dist/arb/reconcile.js +0 -162
  215. package/dist/commands/alert.d.ts +0 -2
  216. package/dist/commands/alert.js +0 -421
  217. package/dist/commands/analytics.d.ts +0 -3
  218. package/dist/commands/analytics.js +0 -311
  219. package/dist/commands/arb-manage.d.ts +0 -5
  220. package/dist/commands/arb-manage.js +0 -5
  221. package/dist/commands/dashboard.d.ts +0 -3
  222. package/dist/commands/dashboard.js +0 -176
  223. package/dist/commands/deposit.d.ts +0 -4
  224. package/dist/commands/deposit.js +0 -573
  225. package/dist/commands/dex.d.ts +0 -3
  226. package/dist/commands/dex.js +0 -120
  227. package/dist/commands/env.d.ts +0 -2
  228. package/dist/commands/env.js +0 -136
  229. package/dist/commands/funding.d.ts +0 -3
  230. package/dist/commands/funding.js +0 -554
  231. package/dist/commands/gap.d.ts +0 -2
  232. package/dist/commands/gap.js +0 -305
  233. package/dist/commands/health.d.ts +0 -2
  234. package/dist/commands/health.js +0 -67
  235. package/dist/commands/multileg.d.ts +0 -16
  236. package/dist/commands/multileg.js +0 -174
  237. package/dist/commands/pnl.d.ts +0 -12
  238. package/dist/commands/pnl.js +0 -243
  239. package/dist/commands/run.d.ts +0 -3
  240. package/dist/commands/run.js +0 -202
  241. package/dist/commands/stream.d.ts +0 -5
  242. package/dist/commands/stream.js +0 -200
  243. package/dist/commands/withdraw.d.ts +0 -3
  244. package/dist/commands/withdraw.js +0 -187
  245. package/dist/exchanges/edgex.d.ts +0 -62
  246. package/dist/exchanges/edgex.js +0 -552
  247. package/dist/exchanges/nado.d.ts +0 -52
  248. package/dist/exchanges/nado.js +0 -223
  249. package/dist/funding/index.d.ts +0 -3
  250. package/dist/funding/index.js +0 -3
  251. package/dist/retry.d.ts +0 -74
  252. package/dist/retry.js +0 -129
  253. package/dist/ws/market-feed.d.ts +0 -74
  254. package/dist/ws/market-feed.js +0 -227
@@ -1,305 +0,0 @@
1
- import chalk from "chalk";
2
- import { printJson, jsonOk } from "../utils.js";
3
- import { fetchPacificaPricesRaw, parsePacificaRaw, fetchHyperliquidAllMidsRaw, fetchLighterOrderBookDetailsRaw, } from "../shared-api.js";
4
- async function fetchAllPrices() {
5
- const [pacRes, hlRes, ltRes] = await Promise.all([
6
- fetchPacificaPricesRaw(),
7
- fetchHyperliquidAllMidsRaw(),
8
- fetchLighterOrderBookDetailsRaw(),
9
- ]);
10
- const { prices: pacPrices } = parsePacificaRaw(pacRes);
11
- const hlPrices = new Map();
12
- if (hlRes && typeof hlRes === "object" && !Array.isArray(hlRes)) {
13
- for (const [symbol, price] of Object.entries(hlRes)) {
14
- const p = Number(price);
15
- if (p > 0)
16
- hlPrices.set(symbol, p);
17
- }
18
- }
19
- const ltPrices = new Map();
20
- if (ltRes) {
21
- const details = (ltRes.order_book_details ?? []);
22
- for (const m of details) {
23
- const sym = String(m.symbol ?? "").replace(/_USDC$/, "");
24
- const price = Number(m.last_trade_price ?? m.mark_price ?? 0);
25
- if (sym && price > 0)
26
- ltPrices.set(sym, price);
27
- }
28
- }
29
- const allSymbols = new Set([...pacPrices.keys(), ...hlPrices.keys(), ...ltPrices.keys()]);
30
- const snapshots = [];
31
- for (const sym of allSymbols) {
32
- const pac = pacPrices.get(sym) ?? null;
33
- const hl = hlPrices.get(sym) ?? null;
34
- const lt = ltPrices.get(sym) ?? null;
35
- // Need at least 2 exchanges
36
- const available = [];
37
- if (pac !== null)
38
- available.push({ ex: "PAC", price: pac });
39
- if (hl !== null)
40
- available.push({ ex: "HL", price: hl });
41
- if (lt !== null)
42
- available.push({ ex: "LT", price: lt });
43
- if (available.length < 2)
44
- continue;
45
- available.sort((a, b) => a.price - b.price);
46
- const cheapest = available[0];
47
- const expensive = available[available.length - 1];
48
- const maxGap = expensive.price - cheapest.price;
49
- const mid = (cheapest.price + expensive.price) / 2;
50
- const maxGapPct = mid > 0 ? (maxGap / mid) * 100 : 0;
51
- snapshots.push({
52
- symbol: sym,
53
- pacPrice: pac,
54
- hlPrice: hl,
55
- ltPrice: lt,
56
- maxGap,
57
- maxGapPct,
58
- cheapest: cheapest.ex,
59
- expensive: expensive.ex,
60
- });
61
- }
62
- return snapshots.sort((a, b) => b.maxGapPct - a.maxGapPct);
63
- }
64
- function formatPrice(p) {
65
- if (p >= 1000)
66
- return p.toFixed(2);
67
- if (p >= 1)
68
- return p.toFixed(4);
69
- return p.toFixed(6);
70
- }
71
- function printGapTable(snapshots, minGap) {
72
- const filtered = snapshots.filter((s) => s.maxGapPct >= minGap);
73
- if (filtered.length === 0) {
74
- console.log(chalk.gray(`\n No gaps above ${minGap}%\n`));
75
- return;
76
- }
77
- console.log(chalk.cyan.bold("\n Symbol Pacifica Hyperliquid Lighter Gap($) Gap(%) Buy/Sell\n"));
78
- for (const s of filtered) {
79
- const gapColor = s.maxGapPct >= 0.5
80
- ? chalk.red.bold
81
- : s.maxGapPct >= 0.1
82
- ? chalk.yellow
83
- : chalk.gray;
84
- const fmtP = (p) => p !== null ? `$${formatPrice(p).padEnd(16)}` : chalk.gray("-".padEnd(17));
85
- console.log(` ${chalk.white.bold(s.symbol.padEnd(10))} ` +
86
- `${fmtP(s.pacPrice)} ` +
87
- `${fmtP(s.hlPrice)} ` +
88
- `${fmtP(s.ltPrice)} ` +
89
- `${gapColor("$" + s.maxGap.toFixed(4).padEnd(10))} ` +
90
- `${gapColor(s.maxGapPct.toFixed(4).padEnd(8) + "%")} ` +
91
- `${chalk.green(s.cheapest)}→${chalk.red(s.expensive)}`);
92
- }
93
- const avgGap = filtered.reduce((sum, s) => sum + s.maxGapPct, 0) / filtered.length;
94
- const top = filtered[0];
95
- console.log(chalk.gray(`\n ${filtered.length} pairs | Avg gap: ${avgGap.toFixed(4)}% | Max: ${top.symbol} ${top.maxGapPct.toFixed(4)}%\n`));
96
- }
97
- export function registerGapCommands(program, isJson) {
98
- const gap = program
99
- .command("gap")
100
- .description("Cross-exchange price gap monitoring (Pacifica vs Hyperliquid vs Lighter)");
101
- // ── gap show (default) ──
102
- gap
103
- .command("show", { isDefault: true })
104
- .description("Show current price gaps between exchanges")
105
- .option("--min <pct>", "Minimum gap % to display", "0.01")
106
- .option("--symbol <sym>", "Filter by symbol (e.g. BTC, ETH)")
107
- .option("--top <n>", "Show top N gaps only")
108
- .action(async (opts) => {
109
- const minGap = parseFloat(opts.min);
110
- if (!isJson())
111
- console.log(chalk.cyan("\n Fetching prices from Pacifica, Hyperliquid & Lighter...\n"));
112
- let snapshots = await fetchAllPrices();
113
- if (opts.symbol) {
114
- const sym = opts.symbol.toUpperCase();
115
- snapshots = snapshots.filter((s) => s.symbol.includes(sym));
116
- }
117
- if (opts.top) {
118
- snapshots = snapshots.slice(0, parseInt(opts.top));
119
- }
120
- if (isJson())
121
- return printJson(jsonOk(snapshots.filter((s) => s.maxGapPct >= minGap)));
122
- printGapTable(snapshots, minGap);
123
- });
124
- // ── gap watch ── (live monitoring)
125
- gap
126
- .command("watch")
127
- .description("Live-monitor price gaps (auto-refresh)")
128
- .option("--min <pct>", "Minimum gap % to display", "0.05")
129
- .option("--interval <seconds>", "Refresh interval", "5")
130
- .option("--symbol <sym>", "Filter by symbol")
131
- .option("--beep", "Beep on gaps above 0.5%")
132
- .action(async (opts) => {
133
- const minGap = parseFloat(opts.min);
134
- const intervalMs = parseInt(opts.interval) * 1000;
135
- const beep = !!opts.beep;
136
- if (!isJson()) {
137
- console.log(chalk.cyan.bold("\n Price Gap Monitor"));
138
- console.log(` Min gap: ${minGap}%`);
139
- console.log(` Interval: ${opts.interval}s`);
140
- if (opts.symbol)
141
- console.log(` Symbol: ${opts.symbol.toUpperCase()}`);
142
- console.log(chalk.gray(" Ctrl+C to stop\n"));
143
- }
144
- const cycle = async () => {
145
- try {
146
- let snapshots = await fetchAllPrices();
147
- if (opts.symbol) {
148
- const sym = opts.symbol.toUpperCase();
149
- snapshots = snapshots.filter((s) => s.symbol.includes(sym));
150
- }
151
- // Clear screen for live view
152
- process.stdout.write("\x1B[2J\x1B[0f");
153
- const now = new Date().toLocaleTimeString();
154
- console.log(chalk.cyan.bold(` Price Gap Monitor`) +
155
- chalk.gray(` ${now} (refresh: ${opts.interval}s)\n`));
156
- printGapTable(snapshots, minGap);
157
- // Alert on large gaps
158
- const bigGaps = snapshots.filter((s) => s.maxGapPct >= 0.5);
159
- if (bigGaps.length > 0) {
160
- console.log(chalk.red.bold(` !! ${bigGaps.length} large gap(s): ` +
161
- bigGaps
162
- .map((s) => `${s.symbol}(${s.maxGapPct.toFixed(3)}%)`)
163
- .join(", ")));
164
- if (beep)
165
- process.stdout.write("\x07");
166
- }
167
- }
168
- catch (err) {
169
- console.error(chalk.gray(` Error: ${err instanceof Error ? err.message : String(err)}`));
170
- }
171
- };
172
- await cycle();
173
- setInterval(cycle, intervalMs);
174
- await new Promise(() => { }); // keep alive
175
- });
176
- // ── gap history ── (track gap over time for a symbol)
177
- gap
178
- .command("track")
179
- .description("Track a symbol's gap over time and print stats")
180
- .requiredOption("-s, --symbol <sym>", "Symbol to track (e.g. BTC)")
181
- .option("--duration <minutes>", "How long to track (minutes)", "10")
182
- .option("--interval <seconds>", "Sample interval", "10")
183
- .action(async (opts) => {
184
- const symbol = opts.symbol.toUpperCase();
185
- const durationMs = parseInt(opts.duration) * 60 * 1000;
186
- const intervalMs = parseInt(opts.interval) * 1000;
187
- const endTime = Date.now() + durationMs;
188
- const samples = [];
189
- if (!isJson()) {
190
- console.log(chalk.cyan.bold(`\n Tracking ${symbol} price gap\n`));
191
- console.log(` Duration: ${opts.duration} min`);
192
- console.log(` Interval: ${opts.interval}s`);
193
- console.log(chalk.gray(" Collecting samples...\n"));
194
- }
195
- const sample = async () => {
196
- const snapshots = await fetchAllPrices();
197
- const s = snapshots.find((x) => x.symbol === symbol);
198
- if (!s) {
199
- console.log(chalk.gray(` ${new Date().toLocaleTimeString()} — ${symbol} not found on both exchanges`));
200
- return;
201
- }
202
- samples.push({
203
- time: new Date().toLocaleTimeString(),
204
- gap: s.maxGap,
205
- gapPct: s.maxGapPct,
206
- direction: `${s.cheapest}→${s.expensive}`,
207
- });
208
- const gapColor = s.maxGapPct >= 0.1 ? chalk.yellow : chalk.gray;
209
- const parts = [`PAC: ${s.pacPrice !== null ? "$" + formatPrice(s.pacPrice) : "-"}`];
210
- parts.push(`HL: ${s.hlPrice !== null ? "$" + formatPrice(s.hlPrice) : "-"}`);
211
- parts.push(`LT: ${s.ltPrice !== null ? "$" + formatPrice(s.ltPrice) : "-"}`);
212
- console.log(` ${chalk.white(s.symbol.padEnd(6))} ` +
213
- `${parts.join(" ")} ` +
214
- `${gapColor(`${s.maxGapPct.toFixed(4)}%`)} ${s.cheapest}→${s.expensive}`);
215
- };
216
- await sample();
217
- const timer = setInterval(async () => {
218
- if (Date.now() >= endTime) {
219
- clearInterval(timer);
220
- printTrackSummary(symbol, samples);
221
- return;
222
- }
223
- await sample();
224
- }, intervalMs);
225
- await new Promise((resolve) => {
226
- setTimeout(resolve, durationMs + 1000);
227
- });
228
- });
229
- // ── gap alert ── (one-shot: notify when gap exceeds threshold)
230
- gap
231
- .command("alert")
232
- .description("Wait until a symbol's gap exceeds a threshold, then exit")
233
- .requiredOption("-s, --symbol <sym>", "Symbol to watch")
234
- .requiredOption("--above <pct>", "Gap % threshold to trigger")
235
- .option("--interval <seconds>", "Check interval", "5")
236
- .action(async (opts) => {
237
- const symbol = opts.symbol.toUpperCase();
238
- const threshold = parseFloat(opts.above);
239
- const intervalMs = parseInt(opts.interval) * 1000;
240
- if (!isJson())
241
- console.log(chalk.cyan(`\n Waiting for ${symbol} gap > ${threshold}%...\n`));
242
- const check = async () => {
243
- const snapshots = await fetchAllPrices();
244
- const s = snapshots.find((x) => x.symbol === symbol);
245
- if (!s)
246
- return false;
247
- const now = new Date().toLocaleTimeString();
248
- if (!isJson())
249
- console.log(chalk.gray(` ${now} ${symbol} gap: ${s.maxGapPct.toFixed(4)}%`));
250
- if (s.maxGapPct >= threshold) {
251
- if (isJson()) {
252
- printJson(jsonOk(s));
253
- }
254
- else {
255
- console.log(chalk.green.bold(`\n TRIGGERED! ${symbol} gap: ${s.maxGapPct.toFixed(4)}% (> ${threshold}%)`));
256
- const lines = [];
257
- if (s.pacPrice !== null)
258
- lines.push(` Pacifica: $${formatPrice(s.pacPrice)}`);
259
- if (s.hlPrice !== null)
260
- lines.push(` Hyperliquid: $${formatPrice(s.hlPrice)}`);
261
- if (s.ltPrice !== null)
262
- lines.push(` Lighter: $${formatPrice(s.ltPrice)}`);
263
- lines.push(` Gap: $${s.maxGap.toFixed(4)} (${s.cheapest}→${s.expensive})`);
264
- console.log(lines.join("\n") + "\n");
265
- }
266
- return true;
267
- }
268
- return false;
269
- };
270
- if (await check())
271
- return;
272
- await new Promise((resolve) => {
273
- const timer = setInterval(async () => {
274
- if (await check()) {
275
- clearInterval(timer);
276
- resolve();
277
- }
278
- }, intervalMs);
279
- });
280
- });
281
- }
282
- function printTrackSummary(symbol, samples) {
283
- if (samples.length === 0) {
284
- console.log(chalk.gray("\n No samples collected.\n"));
285
- return;
286
- }
287
- const gaps = samples.map((s) => s.gapPct);
288
- const avg = gaps.reduce((a, b) => a + b, 0) / gaps.length;
289
- const max = Math.max(...gaps);
290
- const min = Math.min(...gaps);
291
- const maxSample = samples[gaps.indexOf(max)];
292
- // Count direction frequencies
293
- const dirCounts = new Map();
294
- for (const s of samples) {
295
- dirCounts.set(s.direction, (dirCounts.get(s.direction) ?? 0) + 1);
296
- }
297
- console.log(chalk.cyan.bold(`\n ${symbol} Gap Summary (${samples.length} samples)\n`));
298
- console.log(` Avg gap: ${avg.toFixed(4)}%`);
299
- console.log(` Max gap: ${max.toFixed(4)}% at ${maxSample.time}`);
300
- console.log(` Min gap: ${min.toFixed(4)}%`);
301
- for (const [dir, count] of [...dirCounts.entries()].sort((a, b) => b[1] - a[1])) {
302
- console.log(` ${dir.padEnd(10)} ${count} times (${((count / samples.length) * 100).toFixed(0)}%)`);
303
- }
304
- console.log();
305
- }
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerHealthCommands(program: Command, isJson: () => boolean): void;
@@ -1,67 +0,0 @@
1
- import chalk from "chalk";
2
- import { printJson, jsonOk, makeTable } from "../utils.js";
3
- import { pingPacifica, pingHyperliquid, pingLighter } from "../shared-api.js";
4
- async function checkExchangeHealth(name, fn) {
5
- const start = Date.now();
6
- try {
7
- await fn();
8
- return { exchange: name, status: "ok", latency_ms: Date.now() - start };
9
- }
10
- catch (err) {
11
- return {
12
- exchange: name,
13
- status: "down",
14
- latency_ms: Date.now() - start,
15
- error: err instanceof Error ? err.message : String(err),
16
- };
17
- }
18
- }
19
- export function registerHealthCommands(program, isJson) {
20
- program
21
- .command("health")
22
- .description("Check exchange API connectivity and latency")
23
- .action(async () => {
24
- const [pacPing, hlPing, ltPing] = await Promise.all([
25
- pingPacifica(),
26
- pingHyperliquid(),
27
- pingLighter(),
28
- ]);
29
- const toPingResult = (name, p) => ({
30
- exchange: name,
31
- status: p.ok ? "ok" : "down",
32
- latency_ms: p.latencyMs,
33
- error: p.ok ? undefined : `HTTP ${p.status}`,
34
- });
35
- const checks = [
36
- toPingResult("pacifica", pacPing),
37
- toPingResult("hyperliquid", hlPing),
38
- toPingResult("lighter", ltPing),
39
- ];
40
- const allOk = checks.every(c => c.status === "ok");
41
- if (isJson()) {
42
- return printJson(jsonOk({ healthy: allOk, exchanges: checks }));
43
- }
44
- console.log(chalk.cyan.bold("\n Exchange Health Check\n"));
45
- const rows = checks.map(c => {
46
- const statusIcon = c.status === "ok"
47
- ? chalk.green("OK")
48
- : c.status === "degraded"
49
- ? chalk.yellow("DEGRADED")
50
- : chalk.red("DOWN");
51
- const latency = c.latency_ms < 500
52
- ? chalk.green(`${c.latency_ms}ms`)
53
- : c.latency_ms < 2000
54
- ? chalk.yellow(`${c.latency_ms}ms`)
55
- : chalk.red(`${c.latency_ms}ms`);
56
- return [
57
- chalk.white.bold(c.exchange),
58
- statusIcon,
59
- latency,
60
- c.error ? chalk.red(c.error) : chalk.gray("-"),
61
- ];
62
- });
63
- console.log(makeTable(["Exchange", "Status", "Latency", "Error"], rows));
64
- const overall = allOk ? chalk.green("ALL HEALTHY") : chalk.red("ISSUES DETECTED");
65
- console.log(`\n Overall: ${overall}\n`);
66
- });
67
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * Multi-leg order command.
3
- *
4
- * Executes multiple orders across exchanges simultaneously using Promise.allSettled.
5
- * Supports automatic rollback if one leg fails.
6
- *
7
- * Usage:
8
- * perp multi "hl:ETH:buy:0.01" "pac:ETH:sell:0.01"
9
- * perp multi "hl:BTC:buy:0.001" "lighter:BTC:sell:0.001" --smart
10
- * perp multi "hl:ETH:buy:0.01" "pac:SOL:buy:1" "lighter:BTC:sell:0.0001"
11
- *
12
- * Leg format: exchange:symbol:side:size
13
- */
14
- import { Command } from "commander";
15
- import type { ExchangeAdapter } from "../exchanges/interface.js";
16
- export declare function registerMultilegCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
@@ -1,174 +0,0 @@
1
- /**
2
- * Multi-leg order command.
3
- *
4
- * Executes multiple orders across exchanges simultaneously using Promise.allSettled.
5
- * Supports automatic rollback if one leg fails.
6
- *
7
- * Usage:
8
- * perp multi "hl:ETH:buy:0.01" "pac:ETH:sell:0.01"
9
- * perp multi "hl:BTC:buy:0.001" "lighter:BTC:sell:0.001" --smart
10
- * perp multi "hl:ETH:buy:0.01" "pac:SOL:buy:1" "lighter:BTC:sell:0.0001"
11
- *
12
- * Leg format: exchange:symbol:side:size
13
- */
14
- import chalk from "chalk";
15
- import { printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
16
- import { logExecution } from "../execution-log.js";
17
- import { smartOrder } from "../smart-order.js";
18
- function parseLeg(spec) {
19
- const parts = spec.split(":");
20
- if (parts.length !== 4) {
21
- throw new Error(`Invalid leg format "${spec}" — expected exchange:symbol:side:size`);
22
- }
23
- const [exchange, symbol, side, size] = parts;
24
- // Normalize exchange aliases
25
- const exMap = {
26
- hl: "hyperliquid", pac: "pacifica", lit: "lighter",
27
- hyperliquid: "hyperliquid", pacifica: "pacifica", lighter: "lighter",
28
- };
29
- const normalizedExchange = exMap[exchange.toLowerCase()];
30
- if (!normalizedExchange) {
31
- throw new Error(`Unknown exchange "${exchange}" in leg spec`);
32
- }
33
- if (side !== "buy" && side !== "sell") {
34
- throw new Error(`Invalid side "${side}" — must be "buy" or "sell"`);
35
- }
36
- if (isNaN(Number(size)) || Number(size) <= 0) {
37
- throw new Error(`Invalid size "${size}" — must be a positive number`);
38
- }
39
- return { exchange: normalizedExchange, symbol: symbol.toUpperCase(), side, size };
40
- }
41
- export function registerMultilegCommands(program, getAdapterForExchange, isJson) {
42
- program
43
- .command("multi <legs...>")
44
- .description("Execute multi-leg orders simultaneously (exchange:symbol:side:size)")
45
- .option("--smart", "Use smart order (IOC limit + fallback) for each leg")
46
- .option("--rollback", "Auto-rollback filled legs if any leg fails", true)
47
- .option("--no-rollback", "Disable auto-rollback")
48
- .option("--timeout <ms>", "Per-leg timeout in milliseconds", "30000")
49
- .action(async (legSpecs, opts) => {
50
- await withJsonErrors(isJson(), async () => {
51
- // Parse legs
52
- const legs = legSpecs.map(parseLeg);
53
- if (legs.length < 2) {
54
- const err = "Multi-leg requires at least 2 legs";
55
- if (isJson()) {
56
- printJson(jsonError("INVALID_ARGS", err));
57
- return;
58
- }
59
- throw new Error(err);
60
- }
61
- if (!isJson()) {
62
- console.log(chalk.cyan.bold("Multi-Leg Order\n"));
63
- for (const l of legs) {
64
- const color = l.side === "buy" ? chalk.green : chalk.red;
65
- console.log(` ${l.exchange.padEnd(14)} ${color(l.side.toUpperCase())} ${l.symbol} x ${l.size}`);
66
- }
67
- console.log();
68
- }
69
- // Get adapters (deduplicated)
70
- const adapters = new Map();
71
- for (const l of legs) {
72
- if (!adapters.has(l.exchange)) {
73
- adapters.set(l.exchange, await getAdapterForExchange(l.exchange));
74
- }
75
- }
76
- // Execute all legs simultaneously
77
- const timeoutMs = parseInt(opts.timeout);
78
- const results = await Promise.allSettled(legs.map(async (leg) => {
79
- const adapter = adapters.get(leg.exchange);
80
- const orderPromise = opts.smart
81
- ? smartOrder(adapter, leg.symbol, leg.side, leg.size).then(r => r.result)
82
- : adapter.marketOrder(leg.symbol, leg.side, leg.size);
83
- // Apply timeout
84
- const result = await Promise.race([
85
- orderPromise,
86
- new Promise((_, reject) => setTimeout(() => reject(new Error("Leg timeout")), timeoutMs)),
87
- ]);
88
- // Log execution
89
- logExecution({
90
- type: "multi_leg",
91
- exchange: leg.exchange,
92
- symbol: leg.symbol,
93
- side: leg.side,
94
- size: leg.size,
95
- status: "success",
96
- dryRun: false,
97
- meta: { result, smart: opts.smart },
98
- });
99
- return { leg, status: "filled", result };
100
- }));
101
- // Process results
102
- const legResults = results.map((r, i) => {
103
- if (r.status === "fulfilled")
104
- return r.value;
105
- return {
106
- leg: legs[i],
107
- status: "failed",
108
- error: r.reason instanceof Error ? r.reason.message : String(r.reason),
109
- };
110
- });
111
- const filled = legResults.filter(r => r.status === "filled");
112
- const failed = legResults.filter(r => r.status === "failed");
113
- // Rollback if partial fill and rollback enabled
114
- if (failed.length > 0 && filled.length > 0 && opts.rollback) {
115
- if (!isJson()) {
116
- console.log(chalk.yellow(`\n ${failed.length} leg(s) failed — rolling back ${filled.length} filled leg(s)...\n`));
117
- }
118
- for (const fr of filled) {
119
- const adapter = adapters.get(fr.leg.exchange);
120
- const reverseSide = fr.leg.side === "buy" ? "sell" : "buy";
121
- try {
122
- await adapter.marketOrder(fr.leg.symbol, reverseSide, fr.leg.size);
123
- fr.status = "rolled_back";
124
- logExecution({
125
- type: "multi_leg_rollback",
126
- exchange: fr.leg.exchange,
127
- symbol: fr.leg.symbol,
128
- side: reverseSide,
129
- size: fr.leg.size,
130
- status: "success",
131
- dryRun: false,
132
- });
133
- if (!isJson()) {
134
- console.log(chalk.gray(` Rolled back: ${fr.leg.exchange} ${reverseSide} ${fr.leg.symbol} x ${fr.leg.size}`));
135
- }
136
- }
137
- catch (err) {
138
- if (!isJson()) {
139
- console.log(chalk.red(` Rollback failed: ${fr.leg.exchange} ${fr.leg.symbol} — ${err instanceof Error ? err.message : err}`));
140
- }
141
- }
142
- }
143
- }
144
- if (isJson()) {
145
- printJson(jsonOk({
146
- legs: legResults.map(r => ({
147
- exchange: r.leg.exchange,
148
- symbol: r.leg.symbol,
149
- side: r.leg.side,
150
- size: r.leg.size,
151
- status: r.status,
152
- error: r.error,
153
- })),
154
- summary: {
155
- total: legs.length,
156
- filled: legResults.filter(r => r.status === "filled").length,
157
- failed: failed.length,
158
- rolledBack: legResults.filter(r => r.status === "rolled_back").length,
159
- },
160
- }));
161
- }
162
- else {
163
- console.log(chalk.cyan.bold("\nResults:\n"));
164
- for (const r of legResults) {
165
- const icon = r.status === "filled" ? chalk.green("OK") :
166
- r.status === "rolled_back" ? chalk.yellow("ROLLBACK") :
167
- chalk.red("FAIL");
168
- console.log(` ${icon} ${r.leg.exchange.padEnd(14)} ${r.leg.side} ${r.leg.symbol} x ${r.leg.size}${r.error ? ` — ${r.error}` : ""}`);
169
- }
170
- console.log(`\n Total: ${legs.length} Filled: ${filled.length} Failed: ${failed.length} Rolled back: ${legResults.filter(r => r.status === "rolled_back").length}`);
171
- }
172
- });
173
- });
174
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * PnL Tracker CLI commands.
3
- *
4
- * perp pnl snapshot — take one-time equity snapshots across exchanges
5
- * perp pnl track — continuous equity tracking (foreground)
6
- * perp pnl daily — daily PnL breakdown table
7
- * perp pnl weekly — weekly PnL breakdown table
8
- * perp pnl summary — performance summary (Sharpe, drawdown, win rate)
9
- */
10
- import { Command } from "commander";
11
- import type { ExchangeAdapter } from "../exchanges/interface.js";
12
- export declare function registerPnlCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;