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.
Files changed (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/dist/__tests__/alert-logic.test.d.ts +1 -0
  4. package/dist/__tests__/alert-logic.test.js +107 -0
  5. package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
  6. package/dist/__tests__/arb-auto-3dex.test.js +397 -0
  7. package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
  8. package/dist/__tests__/arb-history-stats.test.js +176 -0
  9. package/dist/__tests__/arb-logic.test.d.ts +1 -0
  10. package/dist/__tests__/arb-logic.test.js +84 -0
  11. package/dist/__tests__/arb-manage.test.d.ts +1 -0
  12. package/dist/__tests__/arb-manage.test.js +253 -0
  13. package/dist/__tests__/arb-new-features.test.d.ts +1 -0
  14. package/dist/__tests__/arb-new-features.test.js +457 -0
  15. package/dist/__tests__/arb-sizing.test.d.ts +1 -0
  16. package/dist/__tests__/arb-sizing.test.js +48 -0
  17. package/dist/__tests__/arb-state.test.d.ts +1 -0
  18. package/dist/__tests__/arb-state.test.js +284 -0
  19. package/dist/__tests__/arb-userflow.test.d.ts +1 -0
  20. package/dist/__tests__/arb-userflow.test.js +945 -0
  21. package/dist/__tests__/arb-utils.test.d.ts +1 -0
  22. package/dist/__tests__/arb-utils.test.js +264 -0
  23. package/dist/__tests__/bot-conditions.test.d.ts +1 -0
  24. package/dist/__tests__/bot-conditions.test.js +341 -0
  25. package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
  26. package/dist/__tests__/client-id-tracker.test.js +137 -0
  27. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
  28. package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
  29. package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
  30. package/dist/__tests__/commands/order-intent.test.js +600 -0
  31. package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
  32. package/dist/__tests__/commands/trade-commands.test.js +821 -0
  33. package/dist/__tests__/config.test.d.ts +1 -0
  34. package/dist/__tests__/config.test.js +86 -0
  35. package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
  36. package/dist/__tests__/cross-chain-margin.test.js +287 -0
  37. package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
  38. package/dist/__tests__/dex-asset-map.test.js +191 -0
  39. package/dist/__tests__/errors.test.d.ts +1 -0
  40. package/dist/__tests__/errors.test.js +110 -0
  41. package/dist/__tests__/event-stream.test.d.ts +1 -0
  42. package/dist/__tests__/event-stream.test.js +276 -0
  43. package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
  44. package/dist/__tests__/exchanges/interface.test.js +132 -0
  45. package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
  46. package/dist/__tests__/exchanges/mock-adapter.js +137 -0
  47. package/dist/__tests__/execution-log.test.d.ts +1 -0
  48. package/dist/__tests__/execution-log.test.js +106 -0
  49. package/dist/__tests__/funding-calc.test.d.ts +1 -0
  50. package/dist/__tests__/funding-calc.test.js +71 -0
  51. package/dist/__tests__/funding-history.test.d.ts +1 -0
  52. package/dist/__tests__/funding-history.test.js +343 -0
  53. package/dist/__tests__/funding-rates.test.d.ts +1 -0
  54. package/dist/__tests__/funding-rates.test.js +342 -0
  55. package/dist/__tests__/funding.test.d.ts +1 -0
  56. package/dist/__tests__/funding.test.js +173 -0
  57. package/dist/__tests__/gap-logic.test.d.ts +1 -0
  58. package/dist/__tests__/gap-logic.test.js +43 -0
  59. package/dist/__tests__/hip3-dex.test.d.ts +1 -0
  60. package/dist/__tests__/hip3-dex.test.js +234 -0
  61. package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
  62. package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
  63. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
  64. package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
  65. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
  66. package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
  67. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
  68. package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
  69. package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
  70. package/dist/__tests__/integration/bridge.integration.test.js +309 -0
  71. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
  72. package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
  73. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
  74. package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
  75. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
  76. package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
  77. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
  78. package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
  79. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
  80. package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
  81. package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
  82. package/dist/__tests__/integration/lighter.integration.test.js +53 -0
  83. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
  84. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
  85. package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
  86. package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
  87. package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
  88. package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
  89. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
  90. package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
  91. package/dist/__tests__/liquidity.test.d.ts +1 -0
  92. package/dist/__tests__/liquidity.test.js +225 -0
  93. package/dist/__tests__/plan-executor.test.d.ts +1 -0
  94. package/dist/__tests__/plan-executor.test.js +314 -0
  95. package/dist/__tests__/position-history.test.d.ts +1 -0
  96. package/dist/__tests__/position-history.test.js +367 -0
  97. package/dist/__tests__/retry.test.d.ts +1 -0
  98. package/dist/__tests__/retry.test.js +310 -0
  99. package/dist/__tests__/risk-assessment.test.d.ts +1 -0
  100. package/dist/__tests__/risk-assessment.test.js +145 -0
  101. package/dist/__tests__/security-adversarial.test.d.ts +1 -0
  102. package/dist/__tests__/security-adversarial.test.js +574 -0
  103. package/dist/__tests__/strategies.test.d.ts +1 -0
  104. package/dist/__tests__/strategies.test.js +539 -0
  105. package/dist/__tests__/trade-execution.test.d.ts +1 -0
  106. package/dist/__tests__/trade-execution.test.js +129 -0
  107. package/dist/__tests__/trade-validator.test.d.ts +1 -0
  108. package/dist/__tests__/trade-validator.test.js +655 -0
  109. package/dist/__tests__/utils.test.d.ts +1 -0
  110. package/dist/__tests__/utils.test.js +76 -0
  111. package/dist/api/public/hyperliquid.d.ts +18 -0
  112. package/dist/api/public/hyperliquid.js +82 -0
  113. package/dist/api/public/index.d.ts +8 -0
  114. package/dist/api/public/index.js +8 -0
  115. package/dist/api/public/lighter.d.ts +24 -0
  116. package/dist/api/public/lighter.js +100 -0
  117. package/dist/api/public/pacifica.d.ts +17 -0
  118. package/dist/api/public/pacifica.js +54 -0
  119. package/dist/api/public/urls.d.ts +12 -0
  120. package/dist/api/public/urls.js +33 -0
  121. package/dist/arb/history-stats.d.ts +44 -0
  122. package/dist/arb/history-stats.js +135 -0
  123. package/dist/arb/index.d.ts +4 -0
  124. package/dist/arb/index.js +4 -0
  125. package/dist/arb/sizing.d.ts +23 -0
  126. package/dist/arb/sizing.js +96 -0
  127. package/dist/arb/state.d.ts +51 -0
  128. package/dist/arb/state.js +112 -0
  129. package/dist/arb/utils.d.ts +81 -0
  130. package/dist/arb/utils.js +267 -0
  131. package/dist/arb-history-stats.d.ts +5 -0
  132. package/dist/arb-history-stats.js +5 -0
  133. package/dist/arb-sizing.d.ts +5 -0
  134. package/dist/arb-sizing.js +5 -0
  135. package/dist/arb-state.d.ts +5 -0
  136. package/dist/arb-state.js +5 -0
  137. package/dist/arb-utils.d.ts +5 -0
  138. package/dist/arb-utils.js +5 -0
  139. package/dist/bot/conditions.d.ts +32 -0
  140. package/dist/bot/conditions.js +141 -0
  141. package/dist/bot/config.d.ts +76 -0
  142. package/dist/bot/config.js +160 -0
  143. package/dist/bot/engine.d.ts +8 -0
  144. package/dist/bot/engine.js +519 -0
  145. package/dist/bot/presets.d.ts +11 -0
  146. package/dist/bot/presets.js +296 -0
  147. package/dist/bridge-engine.d.ts +133 -0
  148. package/dist/bridge-engine.js +1487 -0
  149. package/dist/cache.d.ts +25 -0
  150. package/dist/cache.js +99 -0
  151. package/dist/cli-spec.d.ts +50 -0
  152. package/dist/cli-spec.js +75 -0
  153. package/dist/client-id-tracker.d.ts +25 -0
  154. package/dist/client-id-tracker.js +76 -0
  155. package/dist/commands/account.d.ts +3 -0
  156. package/dist/commands/account.js +425 -0
  157. package/dist/commands/agent.d.ts +3 -0
  158. package/dist/commands/agent.js +386 -0
  159. package/dist/commands/alert.d.ts +2 -0
  160. package/dist/commands/alert.js +421 -0
  161. package/dist/commands/analytics.d.ts +3 -0
  162. package/dist/commands/analytics.js +311 -0
  163. package/dist/commands/arb/index.d.ts +3 -0
  164. package/dist/commands/arb/index.js +921 -0
  165. package/dist/commands/arb-auto.d.ts +54 -0
  166. package/dist/commands/arb-auto.js +1328 -0
  167. package/dist/commands/arb-manage.d.ts +5 -0
  168. package/dist/commands/arb-manage.js +5 -0
  169. package/dist/commands/arb.d.ts +2 -0
  170. package/dist/commands/arb.js +347 -0
  171. package/dist/commands/backtest.d.ts +2 -0
  172. package/dist/commands/backtest.js +327 -0
  173. package/dist/commands/bot.d.ts +3 -0
  174. package/dist/commands/bot.js +412 -0
  175. package/dist/commands/bridge.d.ts +2 -0
  176. package/dist/commands/bridge.js +396 -0
  177. package/dist/commands/dashboard.d.ts +3 -0
  178. package/dist/commands/dashboard.js +176 -0
  179. package/dist/commands/deposit.d.ts +4 -0
  180. package/dist/commands/deposit.js +573 -0
  181. package/dist/commands/dex.d.ts +3 -0
  182. package/dist/commands/dex.js +114 -0
  183. package/dist/commands/env.d.ts +2 -0
  184. package/dist/commands/env.js +136 -0
  185. package/dist/commands/funding.d.ts +2 -0
  186. package/dist/commands/funding.js +347 -0
  187. package/dist/commands/gap.d.ts +2 -0
  188. package/dist/commands/gap.js +305 -0
  189. package/dist/commands/health.d.ts +2 -0
  190. package/dist/commands/health.js +67 -0
  191. package/dist/commands/history.d.ts +2 -0
  192. package/dist/commands/history.js +235 -0
  193. package/dist/commands/init.d.ts +15 -0
  194. package/dist/commands/init.js +266 -0
  195. package/dist/commands/jobs.d.ts +2 -0
  196. package/dist/commands/jobs.js +133 -0
  197. package/dist/commands/manage.d.ts +4 -0
  198. package/dist/commands/manage.js +309 -0
  199. package/dist/commands/market.d.ts +3 -0
  200. package/dist/commands/market.js +225 -0
  201. package/dist/commands/plan.d.ts +3 -0
  202. package/dist/commands/plan.js +95 -0
  203. package/dist/commands/portfolio.d.ts +3 -0
  204. package/dist/commands/portfolio.js +169 -0
  205. package/dist/commands/rebalance.d.ts +3 -0
  206. package/dist/commands/rebalance.js +293 -0
  207. package/dist/commands/risk.d.ts +3 -0
  208. package/dist/commands/risk.js +169 -0
  209. package/dist/commands/run.d.ts +3 -0
  210. package/dist/commands/run.js +202 -0
  211. package/dist/commands/settings.d.ts +2 -0
  212. package/dist/commands/settings.js +102 -0
  213. package/dist/commands/stream.d.ts +5 -0
  214. package/dist/commands/stream.js +123 -0
  215. package/dist/commands/trade.d.ts +3 -0
  216. package/dist/commands/trade.js +1273 -0
  217. package/dist/commands/wallet.d.ts +14 -0
  218. package/dist/commands/wallet.js +602 -0
  219. package/dist/commands/withdraw.d.ts +3 -0
  220. package/dist/commands/withdraw.js +187 -0
  221. package/dist/config.d.ts +5 -0
  222. package/dist/config.js +68 -0
  223. package/dist/cross-chain-margin.d.ts +46 -0
  224. package/dist/cross-chain-margin.js +107 -0
  225. package/dist/dashboard/server.d.ts +80 -0
  226. package/dist/dashboard/server.js +340 -0
  227. package/dist/dashboard/ui.d.ts +4 -0
  228. package/dist/dashboard/ui.js +538 -0
  229. package/dist/dashboard/ws-feeds.d.ts +29 -0
  230. package/dist/dashboard/ws-feeds.js +660 -0
  231. package/dist/dex-asset-map.d.ts +80 -0
  232. package/dist/dex-asset-map.js +201 -0
  233. package/dist/errors.d.ts +109 -0
  234. package/dist/errors.js +84 -0
  235. package/dist/event-stream.d.ts +25 -0
  236. package/dist/event-stream.js +168 -0
  237. package/dist/exchanges/hyperliquid.d.ts +212 -0
  238. package/dist/exchanges/hyperliquid.js +931 -0
  239. package/dist/exchanges/interface.d.ts +95 -0
  240. package/dist/exchanges/interface.js +5 -0
  241. package/dist/exchanges/lighter.d.ts +159 -0
  242. package/dist/exchanges/lighter.js +793 -0
  243. package/dist/exchanges/pacifica.d.ts +51 -0
  244. package/dist/exchanges/pacifica.js +248 -0
  245. package/dist/execution-log.d.ts +36 -0
  246. package/dist/execution-log.js +102 -0
  247. package/dist/funding/history.d.ts +63 -0
  248. package/dist/funding/history.js +266 -0
  249. package/dist/funding/index.d.ts +3 -0
  250. package/dist/funding/index.js +3 -0
  251. package/dist/funding/normalize.d.ts +39 -0
  252. package/dist/funding/normalize.js +66 -0
  253. package/dist/funding/rates.d.ts +45 -0
  254. package/dist/funding/rates.js +172 -0
  255. package/dist/funding-history.d.ts +5 -0
  256. package/dist/funding-history.js +5 -0
  257. package/dist/funding-rates.d.ts +5 -0
  258. package/dist/funding-rates.js +5 -0
  259. package/dist/funding.d.ts +5 -0
  260. package/dist/funding.js +5 -0
  261. package/dist/index.d.ts +2 -0
  262. package/dist/index.js +458 -0
  263. package/dist/jobs.d.ts +37 -0
  264. package/dist/jobs.js +152 -0
  265. package/dist/liquidity.d.ts +34 -0
  266. package/dist/liquidity.js +100 -0
  267. package/dist/mcp-server.d.ts +9 -0
  268. package/dist/mcp-server.js +1206 -0
  269. package/dist/pacifica/client.d.ts +111 -0
  270. package/dist/pacifica/client.js +310 -0
  271. package/dist/pacifica/constants.d.ts +27 -0
  272. package/dist/pacifica/constants.js +47 -0
  273. package/dist/pacifica/deposit.d.ts +14 -0
  274. package/dist/pacifica/deposit.js +78 -0
  275. package/dist/pacifica/index.d.ts +6 -0
  276. package/dist/pacifica/index.js +11 -0
  277. package/dist/pacifica/signing.d.ts +49 -0
  278. package/dist/pacifica/signing.js +97 -0
  279. package/dist/pacifica/types/account.d.ts +42 -0
  280. package/dist/pacifica/types/account.js +1 -0
  281. package/dist/pacifica/types/index.d.ts +6 -0
  282. package/dist/pacifica/types/index.js +6 -0
  283. package/dist/pacifica/types/lake.d.ts +18 -0
  284. package/dist/pacifica/types/lake.js +1 -0
  285. package/dist/pacifica/types/market.d.ts +64 -0
  286. package/dist/pacifica/types/market.js +1 -0
  287. package/dist/pacifica/types/order.d.ts +92 -0
  288. package/dist/pacifica/types/order.js +1 -0
  289. package/dist/pacifica/types/position.d.ts +25 -0
  290. package/dist/pacifica/types/position.js +1 -0
  291. package/dist/pacifica/types/ws.d.ts +34 -0
  292. package/dist/pacifica/types/ws.js +41 -0
  293. package/dist/pacifica/ws-client.d.ts +42 -0
  294. package/dist/pacifica/ws-client.js +180 -0
  295. package/dist/plan-executor.d.ts +48 -0
  296. package/dist/plan-executor.js +280 -0
  297. package/dist/position-history.d.ts +68 -0
  298. package/dist/position-history.js +222 -0
  299. package/dist/rebalance.d.ts +64 -0
  300. package/dist/rebalance.js +142 -0
  301. package/dist/retry.d.ts +74 -0
  302. package/dist/retry.js +129 -0
  303. package/dist/risk.d.ts +48 -0
  304. package/dist/risk.js +156 -0
  305. package/dist/settings.d.ts +19 -0
  306. package/dist/settings.js +45 -0
  307. package/dist/shared-api.d.ts +5 -0
  308. package/dist/shared-api.js +5 -0
  309. package/dist/strategies/dca.d.ts +25 -0
  310. package/dist/strategies/dca.js +114 -0
  311. package/dist/strategies/funding-arb.d.ts +15 -0
  312. package/dist/strategies/funding-arb.js +281 -0
  313. package/dist/strategies/grid.d.ts +34 -0
  314. package/dist/strategies/grid.js +185 -0
  315. package/dist/strategies/trailing-stop.d.ts +17 -0
  316. package/dist/strategies/trailing-stop.js +121 -0
  317. package/dist/strategies/twap.d.ts +20 -0
  318. package/dist/strategies/twap.js +78 -0
  319. package/dist/trade-validator.d.ts +39 -0
  320. package/dist/trade-validator.js +154 -0
  321. package/dist/utils.d.ts +38 -0
  322. package/dist/utils.js +110 -0
  323. package/package.json +63 -0
  324. package/skills/perp-cli/SKILL.md +149 -0
  325. package/skills/perp-cli/references/commands.md +143 -0
@@ -0,0 +1,114 @@
1
+ import chalk from "chalk";
2
+ import { HyperliquidAdapter } from "../exchanges/hyperliquid.js";
3
+ import { printJson, jsonOk, makeTable, formatUsd, withJsonErrors } from "../utils.js";
4
+ export function registerDexCommands(program, getAdapter, isJson) {
5
+ const dex = program.command("dex").description("HIP-3 deployed perp dex commands (Hyperliquid)");
6
+ // ── dex list ──
7
+ dex
8
+ .command("list")
9
+ .description("List all available HIP-3 deployed perp dexes")
10
+ .action(async () => {
11
+ await withJsonErrors(isJson(), async () => {
12
+ const adapter = await getAdapter();
13
+ if (!(adapter instanceof HyperliquidAdapter)) {
14
+ console.error(chalk.red("\n HIP-3 dexes are only available on Hyperliquid. Use -e hyperliquid.\n"));
15
+ return;
16
+ }
17
+ const dexes = await adapter.listDeployedDexes();
18
+ if (isJson())
19
+ return printJson(jsonOk(dexes));
20
+ if (dexes.length === 0) {
21
+ console.log(chalk.gray("\n No deployed dexes found.\n"));
22
+ return;
23
+ }
24
+ console.log(chalk.cyan.bold("\n HIP-3 Deployed Perp DEXes\n"));
25
+ const rows = dexes.map(d => [
26
+ chalk.white.bold(d.name),
27
+ chalk.gray(d.deployer.slice(0, 10) + "..."),
28
+ String(d.assets.length),
29
+ d.assets.slice(0, 5).join(", ") + (d.assets.length > 5 ? ` +${d.assets.length - 5}` : ""),
30
+ ]);
31
+ console.log(makeTable(["DEX", "Deployer", "Assets", "Markets"], rows));
32
+ console.log(chalk.gray(`\n Use --dex <name> to trade on a deployed dex.\n`));
33
+ console.log(chalk.gray(` Example: perp -e hyperliquid --dex xyz market list\n`));
34
+ });
35
+ });
36
+ // ── dex markets <dex-name> ──
37
+ dex
38
+ .command("markets <dexName>")
39
+ .description("List all markets on a specific HIP-3 dex")
40
+ .action(async (dexName) => {
41
+ await withJsonErrors(isJson(), async () => {
42
+ const adapter = await getAdapter();
43
+ if (!(adapter instanceof HyperliquidAdapter)) {
44
+ console.error(chalk.red("\n HIP-3 dexes are only available on Hyperliquid. Use -e hyperliquid.\n"));
45
+ return;
46
+ }
47
+ // Temporarily switch dex, fetch markets, then restore
48
+ const prevDex = adapter.dex;
49
+ adapter.setDex(dexName);
50
+ try {
51
+ const markets = await adapter.getMarkets();
52
+ if (isJson())
53
+ return printJson(jsonOk({ dex: dexName, markets }));
54
+ if (markets.length === 0) {
55
+ console.log(chalk.gray(`\n No markets found on dex "${dexName}".\n`));
56
+ return;
57
+ }
58
+ console.log(chalk.cyan.bold(`\n ${dexName.toUpperCase()} DEX Markets\n`));
59
+ const rows = markets.map(m => [
60
+ chalk.white.bold(m.symbol),
61
+ `$${formatUsd(m.markPrice)}`,
62
+ m.fundingRate !== "0" ? `${(Number(m.fundingRate) * 100).toFixed(4)}%` : chalk.gray("-"),
63
+ `$${formatUsd(m.volume24h)}`,
64
+ `$${formatUsd(m.openInterest)}`,
65
+ `${m.maxLeverage}x`,
66
+ ]);
67
+ console.log(makeTable(["Symbol", "Mark Price", "Funding", "24h Volume", "OI", "Max Lev"], rows));
68
+ console.log(chalk.gray(`\n Trade: perp -e hyperliquid --dex ${dexName} trade market <symbol> <side> <size>\n`));
69
+ }
70
+ finally {
71
+ adapter.setDex(prevDex);
72
+ }
73
+ });
74
+ });
75
+ // ── dex balance <dex-name> ──
76
+ dex
77
+ .command("balance <dexName>")
78
+ .description("Show balance on a specific HIP-3 dex")
79
+ .action(async (dexName) => {
80
+ await withJsonErrors(isJson(), async () => {
81
+ const adapter = await getAdapter();
82
+ if (!(adapter instanceof HyperliquidAdapter)) {
83
+ console.error(chalk.red("\n HIP-3 dexes are only available on Hyperliquid. Use -e hyperliquid.\n"));
84
+ return;
85
+ }
86
+ const prevDex = adapter.dex;
87
+ adapter.setDex(dexName);
88
+ try {
89
+ const [balance, positions] = await Promise.all([
90
+ adapter.getBalance(),
91
+ adapter.getPositions(),
92
+ ]);
93
+ if (isJson())
94
+ return printJson(jsonOk({ dex: dexName, balance, positions }));
95
+ console.log(chalk.cyan.bold(`\n ${dexName.toUpperCase()} DEX Balance\n`));
96
+ console.log(` Equity: $${formatUsd(balance.equity)}`);
97
+ console.log(` Available: $${formatUsd(balance.available)}`);
98
+ console.log(` Margin Used: $${formatUsd(balance.marginUsed)}`);
99
+ console.log(` uPnL: $${formatUsd(balance.unrealizedPnl)}`);
100
+ if (positions.length > 0) {
101
+ console.log(chalk.white.bold("\n Positions:"));
102
+ for (const p of positions) {
103
+ const color = p.side === "long" ? chalk.green : chalk.red;
104
+ console.log(` ${color(p.side.toUpperCase().padEnd(5))} ${chalk.white(p.symbol.padEnd(16))} ${p.size.padEnd(10)} entry: $${Number(p.entryPrice).toFixed(2)} pnl: $${Number(p.unrealizedPnl).toFixed(2)}`);
105
+ }
106
+ }
107
+ console.log();
108
+ }
109
+ finally {
110
+ adapter.setDex(prevDex);
111
+ }
112
+ });
113
+ });
114
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerEnvCommands(program: Command, isJson: () => boolean): void;
@@ -0,0 +1,136 @@
1
+ import chalk from "chalk";
2
+ import { printJson, jsonOk } from "../utils.js";
3
+ import { ENV_FILE, loadEnvFile, setEnvVar, EXCHANGE_ENV_MAP, validateKey } from "./init.js";
4
+ // Resolve exchange alias → env var key
5
+ function resolveEnvKey(nameOrKey) {
6
+ // Direct env var name
7
+ const upper = nameOrKey.toUpperCase();
8
+ for (const info of Object.values(EXCHANGE_ENV_MAP)) {
9
+ if (info.envKey === upper)
10
+ return { envKey: info.envKey, chain: info.chain };
11
+ }
12
+ // Exchange name alias
13
+ const info = EXCHANGE_ENV_MAP[nameOrKey.toLowerCase()];
14
+ if (info)
15
+ return { envKey: info.envKey, chain: info.chain };
16
+ // Common aliases
17
+ const aliases = { hl: "hyperliquid", pac: "pacifica", lt: "lighter" };
18
+ const aliased = aliases[nameOrKey.toLowerCase()];
19
+ if (aliased)
20
+ return resolveEnvKey(aliased);
21
+ return null;
22
+ }
23
+ export function registerEnvCommands(program, isJson) {
24
+ const env = program.command("env").description("Manage ~/.perp/.env configuration");
25
+ // ── perp env show ──
26
+ env
27
+ .command("show")
28
+ .description("Show current configuration")
29
+ .action(async () => {
30
+ const stored = loadEnvFile();
31
+ const entries = [];
32
+ for (const [exchange, info] of Object.entries(EXCHANGE_ENV_MAP)) {
33
+ const fromFile = stored[info.envKey];
34
+ const fromEnv = process.env[info.envKey];
35
+ if (fromFile) {
36
+ entries.push({ name: exchange, chain: info.chain, key: fromFile, source: "~/.perp/.env" });
37
+ }
38
+ else if (fromEnv) {
39
+ entries.push({ name: exchange, chain: info.chain, key: fromEnv, source: "environment" });
40
+ }
41
+ }
42
+ // Derive addresses
43
+ const results = [];
44
+ for (const entry of entries) {
45
+ const { valid, address } = await validateKey(entry.chain, entry.key);
46
+ results.push({ name: entry.name, address: valid ? address : "(invalid key)", source: entry.source });
47
+ }
48
+ if (isJson()) {
49
+ const data = results.map((r) => ({ exchange: r.name, address: r.address, source: r.source }));
50
+ return printJson(jsonOk({ envFile: ENV_FILE, exchanges: data }));
51
+ }
52
+ console.log(chalk.cyan.bold("\n perp-cli Configuration\n"));
53
+ console.log(` File: ${chalk.gray(ENV_FILE)}\n`);
54
+ if (results.length === 0) {
55
+ console.log(chalk.gray(" No keys configured. Run 'perp init' or 'perp env set <exchange> <key>'\n"));
56
+ return;
57
+ }
58
+ for (const { name, address, source } of results) {
59
+ console.log(` ${chalk.cyan(name.padEnd(14))} ${chalk.green(address)} ${chalk.gray(source)}`);
60
+ }
61
+ console.log();
62
+ });
63
+ // ── perp env set <exchange|key> <value> ──
64
+ env
65
+ .command("set <name> <value>")
66
+ .description("Set a key (exchange name or env var name)")
67
+ .action(async (name, value) => {
68
+ const resolved = resolveEnvKey(name);
69
+ if (resolved) {
70
+ // Validate the key
71
+ const { valid, address } = await validateKey(resolved.chain, value);
72
+ if (!valid) {
73
+ if (isJson()) {
74
+ const { jsonError } = await import("../utils.js");
75
+ return printJson(jsonError("INVALID_PARAMS", `Invalid ${resolved.chain} private key`));
76
+ }
77
+ console.error(chalk.red(`\n Invalid ${resolved.chain} private key.\n`));
78
+ process.exit(1);
79
+ }
80
+ const normalized = resolved.chain === "evm"
81
+ ? (value.startsWith("0x") ? value : `0x${value}`)
82
+ : value;
83
+ setEnvVar(resolved.envKey, normalized);
84
+ if (isJson())
85
+ return printJson(jsonOk({ key: resolved.envKey, address, file: ENV_FILE }));
86
+ console.log(chalk.green(`\n ${resolved.envKey} set.`));
87
+ console.log(` Address: ${chalk.gray(address)}`);
88
+ console.log(` File: ${chalk.gray("~/.perp/.env")}\n`);
89
+ }
90
+ else {
91
+ // Raw env var (e.g. LIGHTER_API_KEY, custom vars)
92
+ setEnvVar(name, value);
93
+ if (isJson())
94
+ return printJson(jsonOk({ key: name, file: ENV_FILE }));
95
+ console.log(chalk.green(`\n ${name} set.`));
96
+ console.log(` File: ${chalk.gray("~/.perp/.env")}\n`);
97
+ }
98
+ });
99
+ // ── perp env remove <name> ──
100
+ env
101
+ .command("remove <name>")
102
+ .description("Remove a key from ~/.perp/.env")
103
+ .action(async (name) => {
104
+ const resolved = resolveEnvKey(name);
105
+ const envKey = resolved?.envKey || name;
106
+ const env = loadEnvFile();
107
+ if (!(envKey in env)) {
108
+ if (isJson()) {
109
+ const { jsonError } = await import("../utils.js");
110
+ return printJson(jsonError("NOT_FOUND", `${envKey} not found in ~/.perp/.env`));
111
+ }
112
+ console.log(chalk.gray(`\n ${envKey} not found in ~/.perp/.env\n`));
113
+ return;
114
+ }
115
+ delete env[envKey];
116
+ // Rewrite file
117
+ const { writeFileSync } = await import("fs");
118
+ const lines = ["# perp-cli configuration", "# Generated by 'perp init' — edit freely", ""];
119
+ for (const [k, v] of Object.entries(env))
120
+ lines.push(`${k}=${v}`);
121
+ lines.push("");
122
+ writeFileSync(ENV_FILE, lines.join("\n"), { mode: 0o600 });
123
+ if (isJson())
124
+ return printJson(jsonOk({ removed: envKey }));
125
+ console.log(chalk.yellow(`\n ${envKey} removed from ~/.perp/.env\n`));
126
+ });
127
+ // ── perp env path ──
128
+ env
129
+ .command("path")
130
+ .description("Print env file path")
131
+ .action(() => {
132
+ if (isJson())
133
+ return printJson(jsonOk({ path: ENV_FILE }));
134
+ console.log(ENV_FILE);
135
+ });
136
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerFundingCommands(program: Command, isJson: () => boolean): void;
@@ -0,0 +1,347 @@
1
+ import chalk from "chalk";
2
+ import { makeTable, formatPercent, formatUsd, printJson, jsonOk } from "../utils.js";
3
+ import { fetchAllFundingRates, fetchSymbolFundingRates, TOP_SYMBOLS, } from "../funding-rates.js";
4
+ import { annualizeHourlyRate } from "../funding.js";
5
+ import { saveFundingSnapshot, getHistoricalRates, getCompoundedAnnualReturn, getExchangeCompoundingHours, } from "../funding-history.js";
6
+ export function registerFundingCommands(program, isJson) {
7
+ const funding = program.command("funding").description("Funding rate comparison across exchanges");
8
+ // ── perp funding rates ── (default: show top symbols)
9
+ funding
10
+ .command("rates")
11
+ .description("Show funding rates across all 3 DEXs for top symbols")
12
+ .option("-s, --symbol <symbol>", "Filter to a specific symbol")
13
+ .option("--symbols <list>", "Comma-separated list of symbols")
14
+ .option("--all", "Show all available symbols (not just top ones)")
15
+ .option("--min-spread <pct>", "Minimum annual spread % to show", "0")
16
+ .action(async (opts) => {
17
+ const minSpread = parseFloat(opts.minSpread);
18
+ let filterSymbols;
19
+ if (opts.symbol) {
20
+ filterSymbols = [opts.symbol.toUpperCase()];
21
+ }
22
+ else if (opts.symbols) {
23
+ filterSymbols = opts.symbols.split(",").map(s => s.trim().toUpperCase());
24
+ }
25
+ else if (!opts.all) {
26
+ filterSymbols = TOP_SYMBOLS;
27
+ }
28
+ if (!isJson())
29
+ console.log(chalk.cyan(" Fetching funding rates from all exchanges...\n"));
30
+ const snapshot = await fetchAllFundingRates({
31
+ symbols: filterSymbols,
32
+ minSpread,
33
+ });
34
+ // Persist snapshot for historical tracking (moved from fetchAllFundingRates to avoid side effect)
35
+ try {
36
+ const allRates = snapshot.symbols.flatMap(s => s.rates);
37
+ if (allRates.length > 0)
38
+ saveFundingSnapshot(allRates);
39
+ }
40
+ catch { /* non-critical */ }
41
+ if (isJson())
42
+ return printJson(jsonOk(snapshot));
43
+ printSnapshotTable(snapshot);
44
+ printExchangeStatus(snapshot);
45
+ console.log(chalk.gray(" * Rates shown are current predictions. Actual settled rates may differ.\n"));
46
+ });
47
+ // ── perp funding compare <symbol> ── (detailed single-symbol view)
48
+ funding
49
+ .command("compare <symbol>")
50
+ .description("Detailed funding rate comparison for a single symbol")
51
+ .action(async (symbol) => {
52
+ if (!isJson())
53
+ console.log(chalk.cyan(` Fetching funding rates for ${symbol.toUpperCase()}...\n`));
54
+ const comparison = await fetchSymbolFundingRates(symbol);
55
+ if (!comparison) {
56
+ if (isJson())
57
+ return printJson(jsonOk({ symbol: symbol.toUpperCase(), available: false }));
58
+ console.log(chalk.gray(` ${symbol.toUpperCase()} not found on at least 2 exchanges.\n`));
59
+ return;
60
+ }
61
+ if (isJson())
62
+ return printJson(jsonOk(comparison));
63
+ printDetailedComparison(comparison);
64
+ });
65
+ // ── perp funding spread ── (sorted by spread opportunities)
66
+ funding
67
+ .command("spread")
68
+ .description("Show best funding rate arb opportunities sorted by spread")
69
+ .option("--min <pct>", "Min annual spread to show", "10")
70
+ .option("--top <n>", "Show top N opportunities", "20")
71
+ .action(async (opts) => {
72
+ const minSpread = parseFloat(opts.min);
73
+ const topN = parseInt(opts.top);
74
+ if (!isJson())
75
+ console.log(chalk.cyan(" Scanning funding rate spreads across all exchanges...\n"));
76
+ const snapshot = await fetchAllFundingRates({ minSpread });
77
+ const shown = snapshot.symbols.slice(0, topN);
78
+ if (isJson())
79
+ return printJson(jsonOk(shown));
80
+ if (shown.length === 0) {
81
+ console.log(chalk.gray(` No opportunities above ${minSpread}% annual spread.\n`));
82
+ return;
83
+ }
84
+ console.log(chalk.cyan.bold(" Funding Rate Arb Opportunities\n"));
85
+ console.log(chalk.gray(" Strategy: Long on low-funding exchange, Short on high-funding exchange\n"));
86
+ const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
87
+ const rows = shown.map(s => {
88
+ const pacRate = s.rates.find(r => r.exchange === "pacifica");
89
+ const hlRate = s.rates.find(r => r.exchange === "hyperliquid");
90
+ const ltRate = s.rates.find(r => r.exchange === "lighter");
91
+ const spreadColor = s.maxSpreadAnnual >= 50 ? chalk.green.bold
92
+ : s.maxSpreadAnnual >= 20 ? chalk.green
93
+ : chalk.yellow;
94
+ return [
95
+ chalk.white.bold(s.symbol),
96
+ `$${formatUsd(s.bestMarkPrice)}`,
97
+ pacRate ? formatPercent(pacRate.fundingRate) : chalk.gray("-"),
98
+ hlRate ? formatPercent(hlRate.fundingRate) : chalk.gray("-"),
99
+ ltRate ? formatPercent(ltRate.fundingRate) : chalk.gray("-"),
100
+ spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`),
101
+ getAvgSpread(s, "avg24h"),
102
+ `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}`,
103
+ `$${s.estHourlyIncomeUsd.toFixed(4)}/hr`,
104
+ ];
105
+ });
106
+ console.log(makeTable(["Symbol", "Price", "Pacifica", "Hyperliquid", "Lighter", "Ann.Spread", "Avg Spread(24h)", "Direction", "Est.Income/$1K"], rows));
107
+ console.log(chalk.gray(`\n ${shown.length} opportunities above ${minSpread}% annual spread`));
108
+ console.log(chalk.gray(` Income estimated for $1,000 notional per leg`));
109
+ console.log(chalk.gray(` Use 'perp arb auto --min-spread ${minSpread}' to auto-trade`));
110
+ console.log(chalk.gray("\n * Rates shown are current predictions. Actual settled rates may differ.\n"));
111
+ });
112
+ // ── perp funding history ── (rate trend over time)
113
+ funding
114
+ .command("history")
115
+ .description("Show funding rate trend over time for a symbol")
116
+ .requiredOption("-s, --symbol <symbol>", "Symbol to show history for")
117
+ .option("--hours <n>", "Number of hours to look back", "24")
118
+ .option("--exchange <ex>", "Filter to a specific exchange")
119
+ .action(async (opts) => {
120
+ const symbol = opts.symbol.toUpperCase();
121
+ const hours = parseInt(opts.hours);
122
+ const exchanges = opts.exchange
123
+ ? [opts.exchange.toLowerCase()]
124
+ : ["hyperliquid", "pacifica", "lighter"];
125
+ const endTime = new Date();
126
+ const startTime = new Date(endTime.getTime() - hours * 60 * 60 * 1000);
127
+ if (isJson()) {
128
+ const data = {};
129
+ for (const ex of exchanges) {
130
+ const rates = getHistoricalRates(symbol, ex, startTime, endTime);
131
+ if (rates.length > 0)
132
+ data[ex] = rates;
133
+ }
134
+ return printJson(jsonOk({ symbol, hours, startTime: startTime.toISOString(), endTime: endTime.toISOString(), rates: data }));
135
+ }
136
+ console.log(chalk.cyan.bold(` ${symbol} Funding Rate History (last ${hours}h)\n`));
137
+ const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
138
+ let hasData = false;
139
+ for (const ex of exchanges) {
140
+ const rates = getHistoricalRates(symbol, ex, startTime, endTime);
141
+ if (rates.length === 0)
142
+ continue;
143
+ hasData = true;
144
+ console.log(chalk.white.bold(` ${exAbbr(ex)} (${rates.length} snapshots):`));
145
+ const rows = rates.map(r => {
146
+ const date = new Date(r.ts);
147
+ const timeStr = date.toLocaleString();
148
+ const hourlyPct = (r.hourlyRate * 100).toFixed(6);
149
+ const annualPct = annualizeHourlyRate(r.hourlyRate).toFixed(2);
150
+ const color = r.rate > 0 ? chalk.red : r.rate < 0 ? chalk.green : chalk.white;
151
+ return [
152
+ chalk.gray(timeStr),
153
+ color(formatPercent(r.rate)),
154
+ `${hourlyPct}%/h`,
155
+ `${annualPct}%/yr`,
156
+ ];
157
+ });
158
+ console.log(makeTable(["Time", "Raw Rate", "Hourly", "Annualized"], rows));
159
+ console.log();
160
+ }
161
+ if (!hasData) {
162
+ console.log(chalk.gray(` No historical data found for ${symbol} in the last ${hours}h.`));
163
+ console.log(chalk.gray(` Run 'perp funding rates' to start collecting data.\n`));
164
+ }
165
+ });
166
+ // ── perp funding monitor ── (live refreshing)
167
+ funding
168
+ .command("monitor")
169
+ .description("Live-monitor funding rates with auto-refresh")
170
+ .option("--min <pct>", "Min annual spread to show", "10")
171
+ .option("--interval <sec>", "Refresh interval in seconds", "30")
172
+ .option("--top <n>", "Show top N", "15")
173
+ .option("--symbols <list>", "Comma-separated symbols to watch")
174
+ .action(async (opts) => {
175
+ const minSpread = parseFloat(opts.min);
176
+ const intervalSec = parseInt(opts.interval);
177
+ const topN = parseInt(opts.top);
178
+ const filterSymbols = opts.symbols?.split(",").map(s => s.trim().toUpperCase());
179
+ let cycle = 0;
180
+ if (!isJson()) {
181
+ console.log(chalk.cyan.bold("\n Funding Rate Monitor"));
182
+ console.log(chalk.gray(` Min spread: ${minSpread}% | Refresh: ${intervalSec}s | Top: ${topN}`));
183
+ if (filterSymbols)
184
+ console.log(chalk.gray(` Symbols: ${filterSymbols.join(", ")}`));
185
+ console.log(chalk.gray(` Press Ctrl+C to stop\n`));
186
+ }
187
+ const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
188
+ while (true) {
189
+ cycle++;
190
+ const ts = new Date().toLocaleTimeString();
191
+ try {
192
+ const snapshot = await fetchAllFundingRates({
193
+ symbols: filterSymbols,
194
+ minSpread,
195
+ });
196
+ const shown = snapshot.symbols.slice(0, topN);
197
+ if (isJson()) {
198
+ printJson(jsonOk({ cycle, timestamp: ts, opportunities: shown }));
199
+ }
200
+ else {
201
+ // Clear previous output
202
+ if (cycle > 1) {
203
+ const linesToClear = shown.length + 4;
204
+ process.stdout.write(`\x1b[${linesToClear}A\x1b[J`);
205
+ }
206
+ console.log(chalk.gray(` ${ts} -- Cycle ${cycle} | ${shown.length} opportunities >= ${minSpread}%\n`));
207
+ if (shown.length === 0) {
208
+ console.log(chalk.gray(` No opportunities found.\n`));
209
+ }
210
+ else {
211
+ for (const s of shown) {
212
+ const direction = `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}`;
213
+ const spreadColor = s.maxSpreadAnnual >= 50 ? chalk.green.bold
214
+ : s.maxSpreadAnnual >= 30 ? chalk.green
215
+ : chalk.yellow;
216
+ const rateStrs = [];
217
+ for (const r of s.rates) {
218
+ rateStrs.push(`${exAbbr(r.exchange)}:${(r.fundingRate * 100).toFixed(4)}%`);
219
+ }
220
+ console.log(` ${chalk.white.bold(s.symbol.padEnd(8))} ` +
221
+ `${spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`.padEnd(8))} ` +
222
+ `${direction.padEnd(7)} ` +
223
+ rateStrs.join(" "));
224
+ }
225
+ console.log();
226
+ }
227
+ }
228
+ }
229
+ catch (err) {
230
+ if (!isJson()) {
231
+ console.log(chalk.red(` ${ts} Error: ${err instanceof Error ? err.message : err}\n`));
232
+ }
233
+ }
234
+ await new Promise(r => setTimeout(r, intervalSec * 1000));
235
+ }
236
+ });
237
+ }
238
+ // ── Display helpers ──
239
+ function formatAvgRate(rate) {
240
+ if (rate == null)
241
+ return chalk.gray("-");
242
+ const annualPct = annualizeHourlyRate(rate);
243
+ const color = annualPct > 0 ? chalk.red : annualPct < 0 ? chalk.green : chalk.white;
244
+ return color(`${annualPct.toFixed(1)}%`);
245
+ }
246
+ /** Compute an "average spread" across rates that have avg24h data. */
247
+ function getAvgSpread(s, windowKey) {
248
+ const avgs = s.rates
249
+ .filter(r => r.historicalAvg?.[windowKey] != null)
250
+ .map(r => r.historicalAvg[windowKey]);
251
+ if (avgs.length < 2)
252
+ return chalk.gray("-");
253
+ const maxH = Math.max(...avgs);
254
+ const minH = Math.min(...avgs);
255
+ const spreadPct = annualizeHourlyRate(maxH - minH);
256
+ return `${spreadPct.toFixed(1)}%`;
257
+ }
258
+ function printSnapshotTable(snapshot) {
259
+ const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
260
+ if (snapshot.symbols.length === 0) {
261
+ console.log(chalk.gray(" No funding rate data available.\n"));
262
+ return;
263
+ }
264
+ const rows = snapshot.symbols.map(s => {
265
+ const pacRate = s.rates.find(r => r.exchange === "pacifica");
266
+ const hlRate = s.rates.find(r => r.exchange === "hyperliquid");
267
+ const ltRate = s.rates.find(r => r.exchange === "lighter");
268
+ const spreadColor = s.maxSpreadAnnual >= 30 ? chalk.green
269
+ : s.maxSpreadAnnual >= 10 ? chalk.yellow
270
+ : chalk.white;
271
+ // Use the highest-spread pair's historical averages for the avg columns
272
+ // Show the best exchange's avg for quick reference
273
+ const bestRate = hlRate ?? pacRate ?? ltRate;
274
+ const avg8h = bestRate?.historicalAvg?.avg8h;
275
+ const avg24h = bestRate?.historicalAvg?.avg24h;
276
+ const avg7d = bestRate?.historicalAvg?.avg7d;
277
+ return [
278
+ chalk.white.bold(s.symbol),
279
+ pacRate ? formatPercent(pacRate.fundingRate) : chalk.gray("-"),
280
+ hlRate ? formatPercent(hlRate.fundingRate) : chalk.gray("-"),
281
+ ltRate ? formatPercent(ltRate.fundingRate) : chalk.gray("-"),
282
+ spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`),
283
+ s.maxSpreadAnnual >= 5 ? `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}` : chalk.gray("-"),
284
+ formatAvgRate(avg8h),
285
+ formatAvgRate(avg24h),
286
+ formatAvgRate(avg7d),
287
+ ];
288
+ });
289
+ console.log(makeTable(["Symbol", "Pacifica", "Hyperliquid", "Lighter", "Ann. Spread", "Direction", "Avg 8h", "Avg 24h", "Avg 7d"], rows));
290
+ console.log(chalk.gray(`\n ${snapshot.symbols.length} symbols compared across exchanges.`));
291
+ console.log(chalk.gray(` Rates: All exchanges per 1h. Spread is normalized.`));
292
+ console.log(chalk.gray(` Avg columns show best exchange's historical hourly rate annualized.\n`));
293
+ }
294
+ function printExchangeStatus(snapshot) {
295
+ const statuses = Object.entries(snapshot.exchangeStatus).map(([ex, status]) => {
296
+ const indicator = status === "ok" ? chalk.green("OK") : chalk.red("ERR");
297
+ return `${ex}: ${indicator}`;
298
+ });
299
+ console.log(chalk.gray(` Exchange status: ${statuses.join(" ")}\n`));
300
+ }
301
+ function printDetailedComparison(comparison) {
302
+ const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
303
+ console.log(chalk.cyan.bold(` ${comparison.symbol} Funding Rate Comparison\n`));
304
+ console.log(` Mark Price: $${formatUsd(comparison.bestMarkPrice)}\n`);
305
+ for (const r of comparison.rates) {
306
+ const hourlyPct = (r.hourlyRate * 100).toFixed(6);
307
+ const annualPct = r.annualizedPct.toFixed(2);
308
+ const compHours = getExchangeCompoundingHours(r.exchange);
309
+ const compoundedReturn = getCompoundedAnnualReturn(r.hourlyRate, compHours);
310
+ const compoundedPct = (compoundedReturn * 100).toFixed(2);
311
+ const color = r.fundingRate > 0 ? chalk.red : r.fundingRate < 0 ? chalk.green : chalk.white;
312
+ console.log(` ${chalk.white.bold(exAbbr(r.exchange).padEnd(4))} ` +
313
+ `Raw: ${color(formatPercent(r.fundingRate).padEnd(14))} ` +
314
+ `Hourly: ${hourlyPct}% ` +
315
+ `Annual: ${annualPct}% ` +
316
+ `APY(compounded): ${compoundedPct}%`);
317
+ }
318
+ // Show historical averages if available
319
+ const hasHistorical = comparison.rates.some(r => r.historicalAvg != null);
320
+ if (hasHistorical) {
321
+ console.log(chalk.cyan("\n Historical Averages (annualized):"));
322
+ for (const r of comparison.rates) {
323
+ if (!r.historicalAvg)
324
+ continue;
325
+ const avg8h = r.historicalAvg.avg8h != null ? `${annualizeHourlyRate(r.historicalAvg.avg8h).toFixed(1)}%` : "-";
326
+ const avg24h = r.historicalAvg.avg24h != null ? `${annualizeHourlyRate(r.historicalAvg.avg24h).toFixed(1)}%` : "-";
327
+ const avg7d = r.historicalAvg.avg7d != null ? `${annualizeHourlyRate(r.historicalAvg.avg7d).toFixed(1)}%` : "-";
328
+ console.log(` ${chalk.white.bold(exAbbr(r.exchange).padEnd(4))} ` +
329
+ `8h: ${avg8h.padEnd(10)} 24h: ${avg24h.padEnd(10)} 7d: ${avg7d}`);
330
+ }
331
+ }
332
+ console.log();
333
+ const spreadColor = comparison.maxSpreadAnnual >= 30 ? chalk.green.bold
334
+ : comparison.maxSpreadAnnual >= 10 ? chalk.yellow
335
+ : chalk.white;
336
+ console.log(` Max Spread: ${spreadColor(`${comparison.maxSpreadAnnual.toFixed(1)}%`)} annual`);
337
+ console.log(` Direction: Long ${exAbbr(comparison.longExchange)} / Short ${exAbbr(comparison.shortExchange)}`);
338
+ console.log(` Est. Income: $${comparison.estHourlyIncomeUsd.toFixed(4)}/hr per $1K notional`);
339
+ // Show income at different position sizes
340
+ const sizes = [1000, 5000, 10000, 50000];
341
+ console.log(chalk.gray("\n Estimated daily income by position size:"));
342
+ for (const size of sizes) {
343
+ const daily = (comparison.estHourlyIncomeUsd / 1000) * size * 24;
344
+ console.log(chalk.gray(` $${formatUsd(size)} notional -> $${daily.toFixed(2)}/day`));
345
+ }
346
+ console.log(chalk.gray("\n * Rates shown are current predictions. Actual settled rates may differ.\n"));
347
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerGapCommands(program: Command, isJson: () => boolean): void;