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,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/commands/arb/index.ts.
4
+ */
5
+ export * from "./arb/index.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/commands/arb/index.ts.
4
+ */
5
+ export * from "./arb/index.js";
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerArbCommands(program: Command, isJson: () => boolean): void;
@@ -0,0 +1,347 @@
1
+ import { makeTable, formatUsd, formatPercent, printJson, jsonOk } from "../utils.js";
2
+ import chalk from "chalk";
3
+ import { computeAnnualSpread, toHourlyRate } from "../funding.js";
4
+ import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, fetchLighterFundingRates, } from "../shared-api.js";
5
+ import { scanDexArb } from "../dex-asset-map.js";
6
+ async function fetchPacificaRates() {
7
+ const assets = await fetchPacificaPrices();
8
+ return assets.map(p => ({
9
+ exchange: "pacifica",
10
+ symbol: p.symbol,
11
+ fundingRate: p.funding,
12
+ markPrice: p.mark,
13
+ nextFunding: p.nextFunding ?? 0,
14
+ }));
15
+ }
16
+ async function fetchHyperliquidRates() {
17
+ const assets = await fetchHyperliquidMeta();
18
+ return assets.map(a => ({
19
+ exchange: "hyperliquid",
20
+ symbol: a.symbol,
21
+ fundingRate: a.funding,
22
+ markPrice: a.markPx,
23
+ }));
24
+ }
25
+ async function fetchLighterRates() {
26
+ try {
27
+ const [details, funding] = await Promise.all([
28
+ fetchLighterOrderBookDetails(),
29
+ fetchLighterFundingRates(),
30
+ ]);
31
+ const priceMap = new Map(details.map(d => [d.marketId, d.lastTradePrice]));
32
+ const symMap = new Map(details.map(d => [d.marketId, d.symbol]));
33
+ const rates = [];
34
+ for (const fr of funding) {
35
+ const symbol = fr.symbol || symMap.get(fr.marketId);
36
+ if (!symbol)
37
+ continue;
38
+ rates.push({
39
+ exchange: "lighter",
40
+ symbol,
41
+ fundingRate: fr.rate,
42
+ markPrice: fr.markPrice || priceMap.get(fr.marketId) || 0,
43
+ });
44
+ }
45
+ return rates;
46
+ }
47
+ catch {
48
+ return [];
49
+ }
50
+ }
51
+ // annualize moved to ../funding.ts — use annualizeRate() / computeAnnualSpread()
52
+ export function registerArbCommands(program, isJson) {
53
+ const arb = program.command("arb").description("Funding rate arbitrage & basis trading");
54
+ arb
55
+ .command("funding")
56
+ .description("Compare funding rates across exchanges")
57
+ .option("-s, --symbol <symbol>", "Filter by symbol")
58
+ .option("--min-spread <pct>", "Minimum annual spread % to show", "5")
59
+ .action(async (opts) => {
60
+ if (!isJson())
61
+ console.log(chalk.cyan(" Fetching funding rates from all exchanges...\n"));
62
+ const [pacRates, hlRates, ltRates] = await Promise.all([
63
+ fetchPacificaRates(),
64
+ fetchHyperliquidRates(),
65
+ fetchLighterRates(),
66
+ ]);
67
+ // Build rate map by symbol
68
+ const rateMap = new Map();
69
+ for (const r of [...pacRates, ...hlRates, ...ltRates]) {
70
+ if (opts.symbol && r.symbol.toUpperCase() !== opts.symbol.toUpperCase())
71
+ continue;
72
+ if (!rateMap.has(r.symbol))
73
+ rateMap.set(r.symbol, []);
74
+ rateMap.get(r.symbol).push(r);
75
+ }
76
+ const opportunities = [];
77
+ for (const [symbol, rates] of rateMap.entries()) {
78
+ if (rates.length < 2)
79
+ continue;
80
+ // Find min and max funding rate exchange (normalize to hourly for comparison)
81
+ rates.sort((a, b) => toHourlyRate(a.fundingRate, a.exchange) - toHourlyRate(b.fundingRate, b.exchange));
82
+ const lowest = rates[0];
83
+ const highest = rates[rates.length - 1];
84
+ if (lowest.exchange === highest.exchange)
85
+ continue;
86
+ const annualSpread = computeAnnualSpread(highest.fundingRate, highest.exchange, lowest.fundingRate, lowest.exchange);
87
+ if (annualSpread >= parseFloat(opts.minSpread)) {
88
+ opportunities.push({
89
+ symbol,
90
+ longExchange: lowest.exchange, // long where funding is lowest (you pay less / receive more)
91
+ shortExchange: highest.exchange, // short where funding is highest (you receive more / pay less)
92
+ longRate: lowest.fundingRate,
93
+ shortRate: highest.fundingRate,
94
+ spread: toHourlyRate(highest.fundingRate, highest.exchange) - toHourlyRate(lowest.fundingRate, lowest.exchange),
95
+ annualSpread,
96
+ markPrice: highest.markPrice || lowest.markPrice,
97
+ });
98
+ }
99
+ }
100
+ opportunities.sort((a, b) => b.annualSpread - a.annualSpread);
101
+ if (isJson())
102
+ return printJson(jsonOk(opportunities));
103
+ if (opportunities.length === 0) {
104
+ console.log(chalk.gray(` No funding arb opportunities above ${opts.minSpread}% annual spread.\n`));
105
+ return;
106
+ }
107
+ console.log(chalk.cyan.bold(" Funding Rate Arbitrage Opportunities\n"));
108
+ console.log(chalk.gray(` Strategy: Long on low-funding exchange, Short on high-funding exchange\n`));
109
+ const rows = opportunities.map((o) => [
110
+ chalk.white.bold(o.symbol),
111
+ `$${formatUsd(o.markPrice)}`,
112
+ `${o.longExchange}`,
113
+ formatPercent(o.longRate),
114
+ `${o.shortExchange}`,
115
+ formatPercent(o.shortRate),
116
+ chalk.yellow(`${o.annualSpread.toFixed(1)}%`),
117
+ ]);
118
+ console.log(makeTable(["Symbol", "Price", "Long On", "L.Fund", "Short On", "S.Fund", "Ann. Spread"], rows));
119
+ console.log(chalk.gray("\n Note: All rates are hourly. Normalized before annualizing.\n"));
120
+ });
121
+ arb
122
+ .command("rates")
123
+ .description("[Deprecated] Use 'perp funding rates' instead. Shows all funding rates.")
124
+ .option("-s, --symbol <symbol>", "Filter by symbol")
125
+ .action(async (opts) => {
126
+ if (!isJson()) {
127
+ console.log(chalk.yellow("\n [Deprecated] 'perp arb rates' is deprecated. Use 'perp funding rates' instead.\n"));
128
+ }
129
+ // Delegate to the same data source as funding rates
130
+ const [pacRates, hlRates, ltRates] = await Promise.all([
131
+ fetchPacificaRates(),
132
+ fetchHyperliquidRates(),
133
+ fetchLighterRates(),
134
+ ]);
135
+ const pacMap = new Map(pacRates.map((r) => [r.symbol, r]));
136
+ const hlMap = new Map(hlRates.map((r) => [r.symbol, r]));
137
+ const ltMap = new Map(ltRates.map((r) => [r.symbol, r]));
138
+ const allSymbols = new Set([...pacMap.keys(), ...hlMap.keys(), ...ltMap.keys()]);
139
+ const symbols = opts.symbol
140
+ ? [opts.symbol.toUpperCase()]
141
+ : [...allSymbols].sort();
142
+ if (isJson()) {
143
+ const data = symbols.map((s) => ({
144
+ symbol: s,
145
+ pacifica: pacMap.get(s)?.fundingRate ?? null,
146
+ hyperliquid: hlMap.get(s)?.fundingRate ?? null,
147
+ lighter: ltMap.get(s)?.fundingRate ?? null,
148
+ _deprecated: "Use 'perp funding rates' instead",
149
+ }));
150
+ return printJson(jsonOk(data));
151
+ }
152
+ const rows = symbols
153
+ .filter((s) => pacMap.has(s) || hlMap.has(s) || ltMap.has(s))
154
+ .map((s) => {
155
+ const pac = pacMap.get(s);
156
+ const hl = hlMap.get(s);
157
+ const lt = ltMap.get(s);
158
+ const pacRate = pac ? formatPercent(pac.fundingRate) : chalk.gray("-");
159
+ const hlRate = hl ? formatPercent(hl.fundingRate) : chalk.gray("-");
160
+ const ltRate = lt ? formatPercent(lt.fundingRate) : chalk.gray("-");
161
+ const withEx = [
162
+ pac ? { rate: pac.fundingRate, ex: "pacifica" } : null,
163
+ hl ? { rate: hl.fundingRate, ex: "hyperliquid" } : null,
164
+ lt ? { rate: lt.fundingRate, ex: "lighter" } : null,
165
+ ].filter(Boolean);
166
+ let annDiff = 0;
167
+ if (withEx.length >= 2) {
168
+ withEx.sort((a, b) => a.rate - b.rate);
169
+ annDiff = computeAnnualSpread(withEx[0].rate, withEx[0].ex, withEx[withEx.length - 1].rate, withEx[withEx.length - 1].ex);
170
+ }
171
+ return [
172
+ chalk.white.bold(s),
173
+ pacRate,
174
+ hlRate,
175
+ ltRate,
176
+ annDiff > 5 ? chalk.yellow(`${annDiff.toFixed(1)}%`) : `${annDiff.toFixed(1)}%`,
177
+ ];
178
+ });
179
+ console.log(makeTable(["Symbol", "Pacifica", "Hyperliquid", "Lighter", "Ann. Spread"], rows));
180
+ });
181
+ arb
182
+ .command("basis")
183
+ .description("Show basis (spot vs perp price) opportunities across exchanges")
184
+ .option("--min-basis <pct>", "Minimum annual basis % to show", "3")
185
+ .action(async (opts) => {
186
+ if (!isJson())
187
+ console.log(chalk.cyan(" Fetching prices for basis calculation...\n"));
188
+ const [pacRates, hlRates, ltRates] = await Promise.all([
189
+ fetchPacificaRates(),
190
+ fetchHyperliquidRates(),
191
+ fetchLighterRates(),
192
+ ]);
193
+ // Compare mark prices across all exchanges for cross-exchange basis
194
+ const exchangePrices = new Map();
195
+ for (const r of [...pacRates, ...hlRates, ...ltRates]) {
196
+ if (r.markPrice <= 0)
197
+ continue;
198
+ if (!exchangePrices.has(r.symbol))
199
+ exchangePrices.set(r.symbol, new Map());
200
+ exchangePrices.get(r.symbol).set(r.exchange, r.markPrice);
201
+ }
202
+ const opps = [];
203
+ for (const [symbol, prices] of exchangePrices.entries()) {
204
+ if (prices.size < 2)
205
+ continue;
206
+ // Find cheapest and most expensive exchange
207
+ let minEx = "", maxEx = "", minPrice = Infinity, maxPrice = 0;
208
+ for (const [ex, price] of prices) {
209
+ if (price < minPrice) {
210
+ minPrice = price;
211
+ minEx = ex;
212
+ }
213
+ if (price > maxPrice) {
214
+ maxPrice = price;
215
+ maxEx = ex;
216
+ }
217
+ }
218
+ if (minEx === maxEx)
219
+ continue;
220
+ const diff = maxPrice - minPrice;
221
+ const avgPrice = (maxPrice + minPrice) / 2;
222
+ const pctDiff = (diff / avgPrice) * 100;
223
+ if (pctDiff * 365 >= parseFloat(opts.minBasis)) {
224
+ const priceObj = {};
225
+ for (const [ex, p] of prices)
226
+ priceObj[ex] = p;
227
+ opps.push({
228
+ symbol,
229
+ prices: priceObj,
230
+ priceDiff: diff,
231
+ pctDiff,
232
+ buyOn: minEx,
233
+ sellOn: maxEx,
234
+ });
235
+ }
236
+ }
237
+ opps.sort((a, b) => b.pctDiff - a.pctDiff);
238
+ if (isJson())
239
+ return printJson(jsonOk(opps));
240
+ if (opps.length === 0) {
241
+ console.log(chalk.gray(` No basis opportunities above ${opts.minBasis}% threshold.\n`));
242
+ return;
243
+ }
244
+ console.log(chalk.cyan.bold(" Cross-Exchange Basis Opportunities\n"));
245
+ console.log(chalk.gray(" Strategy: Buy cheap, sell expensive, profit from convergence.\n"));
246
+ const rows = opps.map((o) => [
247
+ chalk.white.bold(o.symbol),
248
+ o.prices.pacifica ? `$${formatUsd(o.prices.pacifica)}` : chalk.gray("-"),
249
+ o.prices.hyperliquid ? `$${formatUsd(o.prices.hyperliquid)}` : chalk.gray("-"),
250
+ o.prices.lighter ? `$${formatUsd(o.prices.lighter)}` : chalk.gray("-"),
251
+ `$${formatUsd(o.priceDiff)}`,
252
+ `${o.pctDiff.toFixed(4)}%`,
253
+ chalk.green(o.buyOn),
254
+ chalk.red(o.sellOn),
255
+ ]);
256
+ console.log(makeTable(["Symbol", "Pacifica", "Hyperliquid", "Lighter", "Diff", "Basis %", "Buy On", "Sell On"], rows));
257
+ });
258
+ // ── arb dex ── (HIP-3 cross-dex funding arb scan)
259
+ arb
260
+ .command("dex")
261
+ .description("Scan HIP-3 cross-dex funding arb opportunities on Hyperliquid")
262
+ .option("--min-spread <pct>", "Min annual spread % to show", "5")
263
+ .option("--max-gap <pct>", "Max price gap % to treat as same asset", "5")
264
+ .option("--include-native", "Include native HL perps in comparison", true)
265
+ .option("--no-include-native", "Exclude native HL perps")
266
+ .option("--top <n>", "Show top N opportunities", "30")
267
+ .action(async (opts) => {
268
+ if (!isJson())
269
+ console.log(chalk.cyan(" Scanning HIP-3 deployed dex funding spreads...\n"));
270
+ const pairs = await scanDexArb({
271
+ minAnnualSpread: parseFloat(opts.minSpread),
272
+ maxPriceGapPct: parseFloat(opts.maxGap),
273
+ includeNative: opts.includeNative,
274
+ });
275
+ const topN = parseInt(opts.top);
276
+ const shown = pairs.slice(0, topN);
277
+ if (isJson())
278
+ return printJson(jsonOk(shown.map(p => ({
279
+ underlying: p.underlying,
280
+ longDex: p.long.dex,
281
+ longSymbol: p.long.raw,
282
+ longFunding: p.long.fundingRate,
283
+ longPrice: p.long.markPrice,
284
+ longOiUsd: p.long.openInterest * p.long.markPrice,
285
+ longVolume24h: p.long.volume24h,
286
+ shortDex: p.short.dex,
287
+ shortSymbol: p.short.raw,
288
+ shortFunding: p.short.fundingRate,
289
+ shortPrice: p.short.markPrice,
290
+ shortOiUsd: p.short.openInterest * p.short.markPrice,
291
+ shortVolume24h: p.short.volume24h,
292
+ annualSpread: p.annualSpread,
293
+ priceGapPct: p.priceGapPct,
294
+ minOiUsd: p.minOiUsd,
295
+ minVolume24hUsd: p.minVolume24hUsd,
296
+ viability: p.viability,
297
+ }))));
298
+ if (shown.length === 0) {
299
+ console.log(chalk.gray(` No cross-dex opportunities above ${opts.minSpread}% annual spread.\n`));
300
+ return;
301
+ }
302
+ console.log(chalk.cyan.bold(" HIP-3 Cross-Dex Funding Arb Opportunities\n"));
303
+ console.log(chalk.gray(" Strategy: Long on low-funding dex, Short on high-funding dex (same underlying)\n"));
304
+ const rows = shown.map((p) => {
305
+ const spreadColor = p.annualSpread >= 50 ? chalk.green.bold
306
+ : p.annualSpread >= 20 ? chalk.green
307
+ : chalk.yellow;
308
+ const viabilityColor = p.viability === "A" ? chalk.green.bold
309
+ : p.viability === "B" ? chalk.green
310
+ : p.viability === "C" ? chalk.yellow
311
+ : chalk.red;
312
+ const fmtOi = p.minOiUsd >= 1_000_000 ? `$${(p.minOiUsd / 1_000_000).toFixed(1)}M`
313
+ : p.minOiUsd >= 1_000 ? `$${(p.minOiUsd / 1_000).toFixed(0)}K`
314
+ : `$${p.minOiUsd.toFixed(0)}`;
315
+ const fmtVol = p.minVolume24hUsd >= 1_000_000 ? `$${(p.minVolume24hUsd / 1_000_000).toFixed(1)}M`
316
+ : p.minVolume24hUsd >= 1_000 ? `$${(p.minVolume24hUsd / 1_000).toFixed(0)}K`
317
+ : `$${p.minVolume24hUsd.toFixed(0)}`;
318
+ return [
319
+ chalk.white.bold(p.underlying),
320
+ `${p.long.dex}:${p.long.base}`,
321
+ formatPercent(p.long.fundingRate),
322
+ `${p.short.dex}:${p.short.base}`,
323
+ formatPercent(p.short.fundingRate),
324
+ `$${formatUsd(p.long.markPrice)}`,
325
+ spreadColor(`${p.annualSpread.toFixed(1)}%`),
326
+ p.priceGapPct < 0.1 ? chalk.green(`${p.priceGapPct.toFixed(3)}%`) : chalk.yellow(`${p.priceGapPct.toFixed(3)}%`),
327
+ viabilityColor(p.viability),
328
+ viabilityColor(fmtOi),
329
+ chalk.gray(fmtVol),
330
+ ];
331
+ });
332
+ console.log(makeTable(["Asset", "Long On", "L.Fund", "Short On", "S.Fund", "Price", "Ann.Spread", "Gap", "Grade", "MinOI", "MinVol"], rows));
333
+ // Summary
334
+ const dexes = new Set();
335
+ for (const p of shown) {
336
+ dexes.add(p.long.dex);
337
+ dexes.add(p.short.dex);
338
+ }
339
+ const gradeCount = { A: 0, B: 0, C: 0, D: 0 };
340
+ for (const p of shown)
341
+ gradeCount[p.viability]++;
342
+ console.log(chalk.gray(`\n ${shown.length}/${pairs.length} opportunities | Dexes: ${[...dexes].sort().join(", ")}`));
343
+ console.log(chalk.gray(` Grade: ${chalk.green.bold(`A(${gradeCount.A})`)} ${chalk.green(`B(${gradeCount.B})`)} ${chalk.yellow(`C(${gradeCount.C})`)} ${chalk.red(`D(${gradeCount.D})`)}`));
344
+ console.log(chalk.gray(` A: OI>$1M B: OI>$100K C: OI>$10K D: OI<$10K (thin — high risk)`));
345
+ console.log(chalk.gray(` Note: HIP-3 dexes charge 2x fees. Factor into net profitability.\n`));
346
+ });
347
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerBacktestCommands(program: Command, isJson: () => boolean): void;