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,554 +0,0 @@
1
- import chalk from "chalk";
2
- import { makeTable, formatPercent, formatUsd, formatPnl, printJson, jsonOk, jsonError, withJsonErrors } from "../utils.js";
3
- import { fetchAllFundingRates, fetchSymbolFundingRates, TOP_SYMBOLS, } from "../funding-rates.js";
4
- import { estimateHourlyFunding, annualizeHourlyRate, annualizeRate, toHourlyRate } from "../funding.js";
5
- import { saveFundingSnapshot, getHistoricalRates, getCompoundedAnnualReturn, getExchangeCompoundingHours, } from "../funding-history.js";
6
- const ALL_EXCHANGES = ["hyperliquid", "pacifica", "lighter"];
7
- export function registerFundingCommands(program, isJson, getAdapterForExchange) {
8
- const funding = program.command("funding").description("Funding rate comparison across exchanges");
9
- // ── perp funding rates ── (default: show top symbols)
10
- funding
11
- .command("rates")
12
- .description("Show funding rates across all 3 DEXs for top symbols")
13
- .option("-s, --symbol <symbol>", "Filter to a specific symbol")
14
- .option("--symbols <list>", "Comma-separated list of symbols")
15
- .option("--all", "Show all available symbols (not just top ones)")
16
- .option("--min-spread <pct>", "Minimum annual spread % to show", "0")
17
- .action(async (opts) => {
18
- const minSpread = parseFloat(opts.minSpread);
19
- let filterSymbols;
20
- if (opts.symbol) {
21
- filterSymbols = [opts.symbol.toUpperCase()];
22
- }
23
- else if (opts.symbols) {
24
- filterSymbols = opts.symbols.split(",").map(s => s.trim().toUpperCase());
25
- }
26
- else if (!opts.all) {
27
- filterSymbols = TOP_SYMBOLS;
28
- }
29
- if (!isJson())
30
- console.log(chalk.cyan(" Fetching funding rates from all exchanges...\n"));
31
- const snapshot = await fetchAllFundingRates({
32
- symbols: filterSymbols,
33
- minSpread,
34
- });
35
- // Persist snapshot for historical tracking (moved from fetchAllFundingRates to avoid side effect)
36
- try {
37
- const allRates = snapshot.symbols.flatMap(s => s.rates);
38
- if (allRates.length > 0)
39
- saveFundingSnapshot(allRates);
40
- }
41
- catch { /* non-critical */ }
42
- if (isJson()) {
43
- // When --ndjson or --fields is used, return the symbols array directly
44
- // so each symbol gets its own line / fields can be picked per element.
45
- // Otherwise return the full snapshot (includes exchangeStatus metadata).
46
- if (process.argv.includes("--ndjson") || process.argv.includes("--fields")) {
47
- return printJson(jsonOk(snapshot.symbols));
48
- }
49
- return printJson(jsonOk(snapshot));
50
- }
51
- printSnapshotTable(snapshot);
52
- printExchangeStatus(snapshot);
53
- console.log(chalk.gray(" * Rates shown are current predictions. Actual settled rates may differ.\n"));
54
- });
55
- // ── perp funding compare <symbol> ── (detailed single-symbol view)
56
- funding
57
- .command("compare <symbol>")
58
- .description("Detailed funding rate comparison for a single symbol")
59
- .action(async (symbol) => {
60
- if (!isJson())
61
- console.log(chalk.cyan(` Fetching funding rates for ${symbol.toUpperCase()}...\n`));
62
- const comparison = await fetchSymbolFundingRates(symbol);
63
- if (!comparison) {
64
- if (isJson())
65
- return printJson(jsonOk({ symbol: symbol.toUpperCase(), available: false }));
66
- console.log(chalk.gray(` ${symbol.toUpperCase()} not found on at least 2 exchanges.\n`));
67
- return;
68
- }
69
- if (isJson())
70
- return printJson(jsonOk(comparison));
71
- printDetailedComparison(comparison);
72
- });
73
- // ── perp funding spread ── (sorted by spread opportunities)
74
- funding
75
- .command("spread")
76
- .description("Show best funding rate arb opportunities sorted by spread")
77
- .option("--min <pct>", "Min annual spread to show", "10")
78
- .option("--top <n>", "Show top N opportunities", "20")
79
- .action(async (opts) => {
80
- const minSpread = parseFloat(opts.min);
81
- const topN = parseInt(opts.top);
82
- if (!isJson())
83
- console.log(chalk.cyan(" Scanning funding rate spreads across all exchanges...\n"));
84
- const snapshot = await fetchAllFundingRates({ minSpread });
85
- const shown = snapshot.symbols.slice(0, topN);
86
- if (isJson())
87
- return printJson(jsonOk(shown));
88
- if (shown.length === 0) {
89
- console.log(chalk.gray(` No opportunities above ${minSpread}% annual spread.\n`));
90
- return;
91
- }
92
- console.log(chalk.cyan.bold(" Funding Rate Arb Opportunities\n"));
93
- console.log(chalk.gray(" Strategy: Long on low-funding exchange, Short on high-funding exchange\n"));
94
- const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
95
- const rows = shown.map(s => {
96
- const pacRate = s.rates.find(r => r.exchange === "pacifica");
97
- const hlRate = s.rates.find(r => r.exchange === "hyperliquid");
98
- const ltRate = s.rates.find(r => r.exchange === "lighter");
99
- const spreadColor = s.maxSpreadAnnual >= 50 ? chalk.green.bold
100
- : s.maxSpreadAnnual >= 20 ? chalk.green
101
- : chalk.yellow;
102
- return [
103
- chalk.white.bold(s.symbol),
104
- `$${formatUsd(s.bestMarkPrice)}`,
105
- pacRate ? formatPercent(pacRate.fundingRate) : chalk.gray("-"),
106
- hlRate ? formatPercent(hlRate.fundingRate) : chalk.gray("-"),
107
- ltRate ? formatPercent(ltRate.fundingRate) : chalk.gray("-"),
108
- spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`),
109
- getAvgSpread(s, "avg24h"),
110
- `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}`,
111
- `$${s.estHourlyIncomeUsd.toFixed(4)}/hr`,
112
- ];
113
- });
114
- console.log(makeTable(["Symbol", "Price", "Pacifica", "Hyperliquid", "Lighter", "Ann.Spread", "Avg Spread(24h)", "Direction", "Est.Income/$1K"], rows));
115
- console.log(chalk.gray(`\n ${shown.length} opportunities above ${minSpread}% annual spread`));
116
- console.log(chalk.gray(` Income estimated for $1,000 notional per leg`));
117
- console.log(chalk.gray(` Use 'perp arb auto --min-spread ${minSpread}' to auto-trade`));
118
- console.log(chalk.gray("\n * Rates shown are current predictions. Actual settled rates may differ.\n"));
119
- });
120
- // ── perp funding history ── (rate trend over time)
121
- funding
122
- .command("history")
123
- .description("Show funding rate trend over time for a symbol")
124
- .requiredOption("-s, --symbol <symbol>", "Symbol to show history for")
125
- .option("--hours <n>", "Number of hours to look back", "24")
126
- .option("--exchange <ex>", "Filter to a specific exchange")
127
- .action(async (opts) => {
128
- const symbol = opts.symbol.toUpperCase();
129
- const hours = parseInt(opts.hours);
130
- const exchanges = opts.exchange
131
- ? [opts.exchange.toLowerCase()]
132
- : ["hyperliquid", "pacifica", "lighter"];
133
- const endTime = new Date();
134
- const startTime = new Date(endTime.getTime() - hours * 60 * 60 * 1000);
135
- if (isJson()) {
136
- const data = {};
137
- for (const ex of exchanges) {
138
- const rates = getHistoricalRates(symbol, ex, startTime, endTime);
139
- if (rates.length > 0)
140
- data[ex] = rates;
141
- }
142
- return printJson(jsonOk({ symbol, hours, startTime: startTime.toISOString(), endTime: endTime.toISOString(), rates: data }));
143
- }
144
- console.log(chalk.cyan.bold(` ${symbol} Funding Rate History (last ${hours}h)\n`));
145
- const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
146
- let hasData = false;
147
- for (const ex of exchanges) {
148
- const rates = getHistoricalRates(symbol, ex, startTime, endTime);
149
- if (rates.length === 0)
150
- continue;
151
- hasData = true;
152
- console.log(chalk.white.bold(` ${exAbbr(ex)} (${rates.length} snapshots):`));
153
- const rows = rates.map(r => {
154
- const date = new Date(r.ts);
155
- const timeStr = date.toLocaleString();
156
- const hourlyPct = (r.hourlyRate * 100).toFixed(6);
157
- const annualPct = annualizeHourlyRate(r.hourlyRate).toFixed(2);
158
- const color = r.rate > 0 ? chalk.red : r.rate < 0 ? chalk.green : chalk.white;
159
- return [
160
- chalk.gray(timeStr),
161
- color(formatPercent(r.rate)),
162
- `${hourlyPct}%/h`,
163
- `${annualPct}%/yr`,
164
- ];
165
- });
166
- console.log(makeTable(["Time", "Raw Rate", "Hourly", "Annualized"], rows));
167
- console.log();
168
- }
169
- if (!hasData) {
170
- console.log(chalk.gray(` No historical data found for ${symbol} in the last ${hours}h.`));
171
- console.log(chalk.gray(` Run 'perp funding rates' to start collecting data.\n`));
172
- }
173
- });
174
- // ── perp funding monitor ── (live refreshing)
175
- funding
176
- .command("monitor")
177
- .description("Live-monitor funding rates with auto-refresh")
178
- .option("--min <pct>", "Min annual spread to show", "10")
179
- .option("--interval <sec>", "Refresh interval in seconds", "30")
180
- .option("--top <n>", "Show top N", "15")
181
- .option("--symbols <list>", "Comma-separated symbols to watch")
182
- .action(async (opts) => {
183
- const minSpread = parseFloat(opts.min);
184
- const intervalSec = parseInt(opts.interval);
185
- const topN = parseInt(opts.top);
186
- const filterSymbols = opts.symbols?.split(",").map(s => s.trim().toUpperCase());
187
- let cycle = 0;
188
- if (!isJson()) {
189
- console.log(chalk.cyan.bold("\n Funding Rate Monitor"));
190
- console.log(chalk.gray(` Min spread: ${minSpread}% | Refresh: ${intervalSec}s | Top: ${topN}`));
191
- if (filterSymbols)
192
- console.log(chalk.gray(` Symbols: ${filterSymbols.join(", ")}`));
193
- console.log(chalk.gray(` Press Ctrl+C to stop\n`));
194
- }
195
- const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
196
- while (true) {
197
- cycle++;
198
- const ts = new Date().toLocaleTimeString();
199
- try {
200
- const snapshot = await fetchAllFundingRates({
201
- symbols: filterSymbols,
202
- minSpread,
203
- });
204
- const shown = snapshot.symbols.slice(0, topN);
205
- if (isJson()) {
206
- printJson(jsonOk({ cycle, timestamp: ts, opportunities: shown }));
207
- }
208
- else {
209
- // Clear previous output
210
- if (cycle > 1) {
211
- const linesToClear = shown.length + 4;
212
- process.stdout.write(`\x1b[${linesToClear}A\x1b[J`);
213
- }
214
- console.log(chalk.gray(` ${ts} -- Cycle ${cycle} | ${shown.length} opportunities >= ${minSpread}%\n`));
215
- if (shown.length === 0) {
216
- console.log(chalk.gray(` No opportunities found.\n`));
217
- }
218
- else {
219
- for (const s of shown) {
220
- const direction = `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}`;
221
- const spreadColor = s.maxSpreadAnnual >= 50 ? chalk.green.bold
222
- : s.maxSpreadAnnual >= 30 ? chalk.green
223
- : chalk.yellow;
224
- const rateStrs = [];
225
- for (const r of s.rates) {
226
- rateStrs.push(`${exAbbr(r.exchange)}:${(r.fundingRate * 100).toFixed(4)}%`);
227
- }
228
- console.log(` ${chalk.white.bold(s.symbol.padEnd(8))} ` +
229
- `${spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`.padEnd(8))} ` +
230
- `${direction.padEnd(7)} ` +
231
- rateStrs.join(" "));
232
- }
233
- console.log();
234
- }
235
- }
236
- }
237
- catch (err) {
238
- if (!isJson()) {
239
- console.log(chalk.red(` ${ts} Error: ${err instanceof Error ? err.message : err}\n`));
240
- }
241
- }
242
- await new Promise(r => setTimeout(r, intervalSec * 1000));
243
- }
244
- });
245
- // ── perp funding positions ── (funding impact on open positions)
246
- funding
247
- .command("positions")
248
- .description("Show funding rate impact on your current positions")
249
- .option("--exchanges <list>", "Comma-separated exchanges to check (default: all with keys)")
250
- .action(async (opts) => {
251
- if (!getAdapterForExchange) {
252
- const msg = "funding positions requires adapter access";
253
- if (isJson())
254
- return printJson(jsonError("INTERNAL", msg));
255
- console.log(chalk.red(` ${msg}\n`));
256
- return;
257
- }
258
- await withJsonErrors(isJson(), async () => {
259
- const targetExchanges = opts.exchanges
260
- ? opts.exchanges.split(",").map(s => s.trim().toLowerCase())
261
- : [...ALL_EXCHANGES];
262
- if (!isJson())
263
- console.log(chalk.cyan(" Fetching positions and funding rates...\n"));
264
- const results = [];
265
- const exchangeErrors = {};
266
- await Promise.all(targetExchanges.map(async (exchange) => {
267
- try {
268
- const adapter = await getAdapterForExchange(exchange);
269
- const [positions, markets, fundingPayments] = await Promise.all([
270
- adapter.getPositions(),
271
- adapter.getMarkets(),
272
- adapter.getFundingPayments(100).catch(() => []),
273
- ]);
274
- if (positions.length === 0)
275
- return;
276
- // Build symbol → funding rate map
277
- const fundingMap = new Map();
278
- for (const m of markets) {
279
- const sym = m.symbol.toUpperCase().replace(/-PERP$/, "");
280
- fundingMap.set(sym, {
281
- rate: Number(m.fundingRate) || 0,
282
- markPrice: Number(m.markPrice) || 0,
283
- });
284
- }
285
- // Aggregate actual funding payments per symbol (last 24h)
286
- const now = Date.now();
287
- const dayAgo = now - 24 * 60 * 60 * 1000;
288
- const actualFunding = new Map();
289
- for (const fp of fundingPayments) {
290
- if (fp.time < dayAgo)
291
- continue;
292
- const sym = fp.symbol.toUpperCase().replace(/-PERP$/, "");
293
- const amt = Number(fp.payment) || 0;
294
- if (!actualFunding.has(sym))
295
- actualFunding.set(sym, { received: 0, paid: 0 });
296
- const entry = actualFunding.get(sym);
297
- if (amt > 0)
298
- entry.received += amt;
299
- else
300
- entry.paid += Math.abs(amt);
301
- }
302
- for (const pos of positions) {
303
- const sym = pos.symbol.toUpperCase().replace(/-PERP$/, "");
304
- const fdata = fundingMap.get(sym);
305
- const fundingRate = fdata?.rate ?? 0;
306
- const mark = Number(pos.markPrice) || fdata?.markPrice || 0;
307
- const size = Math.abs(Number(pos.size));
308
- const notionalUsd = size * mark;
309
- const hourlyRate = toHourlyRate(fundingRate, exchange);
310
- const annualPct = annualizeRate(fundingRate, exchange);
311
- const hourlyPayment = estimateHourlyFunding(fundingRate, exchange, notionalUsd, pos.side);
312
- const dailyPayment = hourlyPayment * 24;
313
- const actual = actualFunding.get(sym);
314
- const actualReceived24h = actual?.received ?? 0;
315
- const actualPaid24h = actual?.paid ?? 0;
316
- const actualNet24h = actualReceived24h - actualPaid24h;
317
- results.push({
318
- exchange,
319
- symbol: sym,
320
- side: pos.side,
321
- size: pos.size,
322
- entryPrice: pos.entryPrice,
323
- markPrice: pos.markPrice,
324
- unrealizedPnl: pos.unrealizedPnl,
325
- leverage: pos.leverage,
326
- notionalUsd,
327
- fundingRate,
328
- hourlyRate,
329
- annualPct,
330
- hourlyPayment,
331
- dailyPayment,
332
- actualReceived24h,
333
- actualPaid24h,
334
- actualNet24h,
335
- });
336
- }
337
- }
338
- catch (err) {
339
- exchangeErrors[exchange] = err instanceof Error ? err.message : String(err);
340
- }
341
- }));
342
- if (results.length === 0) {
343
- if (isJson()) {
344
- return printJson(jsonOk({
345
- positions: [],
346
- totals: {
347
- predicted: { hourly: 0, daily: 0 },
348
- actual24h: { net: 0 },
349
- notionalUsd: 0,
350
- },
351
- errors: exchangeErrors,
352
- }));
353
- }
354
- console.log(chalk.gray(" No open positions found on any exchange.\n"));
355
- if (Object.keys(exchangeErrors).length > 0) {
356
- for (const [ex, err] of Object.entries(exchangeErrors)) {
357
- console.log(chalk.yellow(` ${ex}: ${err}`));
358
- }
359
- console.log();
360
- }
361
- return;
362
- }
363
- // Sort: biggest daily payment impact first
364
- results.sort((a, b) => Math.abs(b.dailyPayment) - Math.abs(a.dailyPayment));
365
- const totalHourly = results.reduce((s, r) => s + r.hourlyPayment, 0);
366
- const totalDaily = results.reduce((s, r) => s + r.dailyPayment, 0);
367
- const totalNotional = results.reduce((s, r) => s + r.notionalUsd, 0);
368
- const totalActualNet = results.reduce((s, r) => s + r.actualNet24h, 0);
369
- if (isJson()) {
370
- return printJson(jsonOk({
371
- positions: results.map(r => ({
372
- exchange: r.exchange,
373
- symbol: r.symbol,
374
- side: r.side,
375
- size: r.size,
376
- notionalUsd: Number(r.notionalUsd.toFixed(2)),
377
- fundingRate: r.fundingRate,
378
- annualPct: Number(r.annualPct.toFixed(2)),
379
- predicted: {
380
- hourly: Number(r.hourlyPayment.toFixed(6)),
381
- daily: Number(r.dailyPayment.toFixed(4)),
382
- },
383
- actual24h: {
384
- received: Number(r.actualReceived24h.toFixed(6)),
385
- paid: Number(r.actualPaid24h.toFixed(6)),
386
- net: Number(r.actualNet24h.toFixed(6)),
387
- },
388
- unrealizedPnl: r.unrealizedPnl,
389
- })),
390
- totals: {
391
- predicted: {
392
- hourly: Number(totalHourly.toFixed(6)),
393
- daily: Number(totalDaily.toFixed(4)),
394
- },
395
- actual24h: {
396
- net: Number(totalActualNet.toFixed(6)),
397
- },
398
- notionalUsd: Number(totalNotional.toFixed(2)),
399
- },
400
- errors: Object.keys(exchangeErrors).length > 0 ? exchangeErrors : undefined,
401
- }));
402
- }
403
- const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : e === "lighter" ? "LT" : e.toUpperCase();
404
- console.log(chalk.cyan.bold(" Position Funding Impact\n"));
405
- const rows = results.map(r => {
406
- const sideColor = r.side === "long" ? chalk.green : chalk.red;
407
- const rateColor = r.fundingRate > 0 ? chalk.red : r.fundingRate < 0 ? chalk.green : chalk.white;
408
- // actual net: positive = received, negative = paid
409
- const actualColor = r.actualNet24h > 0 ? chalk.green : r.actualNet24h < 0 ? chalk.red : chalk.gray;
410
- const actualStr = r.actualNet24h !== 0
411
- ? actualColor(`${r.actualNet24h > 0 ? "+" : ""}$${r.actualNet24h.toFixed(4)}`)
412
- : chalk.gray("-");
413
- return [
414
- chalk.white.bold(exAbbr(r.exchange)),
415
- chalk.white.bold(r.symbol),
416
- sideColor(r.side.toUpperCase()),
417
- `$${formatUsd(r.notionalUsd)}`,
418
- rateColor(formatPercent(r.fundingRate)),
419
- rateColor(`${r.annualPct.toFixed(1)}%`),
420
- actualStr,
421
- formatPnl(r.unrealizedPnl),
422
- ];
423
- });
424
- console.log(makeTable(["Ex", "Symbol", "Side", "Notional", "Rate(now)", "Annual(now)", "Actual 24h", "uPnL"], rows));
425
- // Summary
426
- const actualNetColor = totalActualNet >= 0 ? chalk.green : chalk.red;
427
- const predictColor = totalDaily >= 0 ? chalk.red : chalk.green;
428
- const predictSign = totalDaily >= 0 ? "-" : "+";
429
- console.log();
430
- console.log(` Total Notional: $${formatUsd(totalNotional)}`);
431
- console.log(` Actual Net (24h): ${actualNetColor(`${totalActualNet >= 0 ? "+" : ""}$${totalActualNet.toFixed(4)}`)}`);
432
- console.log(` Predicted (now rate): ${predictColor(`${predictSign}$${Math.abs(totalDaily).toFixed(4)}/d`)}`);
433
- if (Object.keys(exchangeErrors).length > 0) {
434
- console.log();
435
- for (const [ex, err] of Object.entries(exchangeErrors)) {
436
- console.log(chalk.yellow(` ${exAbbr(ex)}: ${err}`));
437
- }
438
- }
439
- console.log(chalk.gray("\n Actual 24h: real funding received/paid in the last 24 hours."));
440
- console.log(chalk.gray(" Predicted: based on current rate only (changes every hour)."));
441
- console.log(chalk.gray(" + = received, - = paid\n"));
442
- });
443
- });
444
- }
445
- // ── Display helpers ──
446
- function formatAvgRate(rate) {
447
- if (rate == null)
448
- return chalk.gray("-");
449
- const annualPct = annualizeHourlyRate(rate);
450
- const color = annualPct > 0 ? chalk.red : annualPct < 0 ? chalk.green : chalk.white;
451
- return color(`${annualPct.toFixed(1)}%`);
452
- }
453
- /** Compute an "average spread" across rates that have avg24h data. */
454
- function getAvgSpread(s, windowKey) {
455
- const avgs = s.rates
456
- .filter(r => r.historicalAvg?.[windowKey] != null)
457
- .map(r => r.historicalAvg[windowKey]);
458
- if (avgs.length < 2)
459
- return chalk.gray("-");
460
- const maxH = Math.max(...avgs);
461
- const minH = Math.min(...avgs);
462
- const spreadPct = annualizeHourlyRate(maxH - minH);
463
- return `${spreadPct.toFixed(1)}%`;
464
- }
465
- function printSnapshotTable(snapshot) {
466
- const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
467
- if (snapshot.symbols.length === 0) {
468
- console.log(chalk.gray(" No funding rate data available.\n"));
469
- return;
470
- }
471
- const rows = snapshot.symbols.map(s => {
472
- const pacRate = s.rates.find(r => r.exchange === "pacifica");
473
- const hlRate = s.rates.find(r => r.exchange === "hyperliquid");
474
- const ltRate = s.rates.find(r => r.exchange === "lighter");
475
- const spreadColor = s.maxSpreadAnnual >= 30 ? chalk.green
476
- : s.maxSpreadAnnual >= 10 ? chalk.yellow
477
- : chalk.white;
478
- // Use the highest-spread pair's historical averages for the avg columns
479
- // Show the best exchange's avg for quick reference
480
- const bestRate = hlRate ?? pacRate ?? ltRate;
481
- const avg8h = bestRate?.historicalAvg?.avg8h;
482
- const avg24h = bestRate?.historicalAvg?.avg24h;
483
- const avg7d = bestRate?.historicalAvg?.avg7d;
484
- return [
485
- chalk.white.bold(s.symbol),
486
- pacRate ? formatPercent(pacRate.fundingRate) : chalk.gray("-"),
487
- hlRate ? formatPercent(hlRate.fundingRate) : chalk.gray("-"),
488
- ltRate ? formatPercent(ltRate.fundingRate) : chalk.gray("-"),
489
- spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`),
490
- s.maxSpreadAnnual >= 5 ? `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}` : chalk.gray("-"),
491
- formatAvgRate(avg8h),
492
- formatAvgRate(avg24h),
493
- formatAvgRate(avg7d),
494
- ];
495
- });
496
- console.log(makeTable(["Symbol", "Pacifica", "Hyperliquid", "Lighter", "Ann. Spread", "Direction", "Avg 8h", "Avg 24h", "Avg 7d"], rows));
497
- console.log(chalk.gray(`\n ${snapshot.symbols.length} symbols compared across exchanges.`));
498
- console.log(chalk.gray(` Rates: All exchanges per 1h. Spread is normalized.`));
499
- console.log(chalk.gray(` Avg columns show best exchange's historical hourly rate annualized.\n`));
500
- }
501
- function printExchangeStatus(snapshot) {
502
- const statuses = Object.entries(snapshot.exchangeStatus).map(([ex, status]) => {
503
- const indicator = status === "ok" ? chalk.green("OK") : chalk.red("ERR");
504
- return `${ex}: ${indicator}`;
505
- });
506
- console.log(chalk.gray(` Exchange status: ${statuses.join(" ")}\n`));
507
- }
508
- function printDetailedComparison(comparison) {
509
- const exAbbr = (e) => e === "pacifica" ? "PAC" : e === "hyperliquid" ? "HL" : "LT";
510
- console.log(chalk.cyan.bold(` ${comparison.symbol} Funding Rate Comparison\n`));
511
- console.log(` Mark Price: $${formatUsd(comparison.bestMarkPrice)}\n`);
512
- for (const r of comparison.rates) {
513
- const hourlyPct = (r.hourlyRate * 100).toFixed(6);
514
- const annualPct = r.annualizedPct.toFixed(2);
515
- const compHours = getExchangeCompoundingHours(r.exchange);
516
- const compoundedReturn = getCompoundedAnnualReturn(r.hourlyRate, compHours);
517
- const compoundedPct = (compoundedReturn * 100).toFixed(2);
518
- const color = r.fundingRate > 0 ? chalk.red : r.fundingRate < 0 ? chalk.green : chalk.white;
519
- console.log(` ${chalk.white.bold(exAbbr(r.exchange).padEnd(4))} ` +
520
- `Raw: ${color(formatPercent(r.fundingRate).padEnd(14))} ` +
521
- `Hourly: ${hourlyPct}% ` +
522
- `Annual: ${annualPct}% ` +
523
- `APY(compounded): ${compoundedPct}%`);
524
- }
525
- // Show historical averages if available
526
- const hasHistorical = comparison.rates.some(r => r.historicalAvg != null);
527
- if (hasHistorical) {
528
- console.log(chalk.cyan("\n Historical Averages (annualized):"));
529
- for (const r of comparison.rates) {
530
- if (!r.historicalAvg)
531
- continue;
532
- const avg8h = r.historicalAvg.avg8h != null ? `${annualizeHourlyRate(r.historicalAvg.avg8h).toFixed(1)}%` : "-";
533
- const avg24h = r.historicalAvg.avg24h != null ? `${annualizeHourlyRate(r.historicalAvg.avg24h).toFixed(1)}%` : "-";
534
- const avg7d = r.historicalAvg.avg7d != null ? `${annualizeHourlyRate(r.historicalAvg.avg7d).toFixed(1)}%` : "-";
535
- console.log(` ${chalk.white.bold(exAbbr(r.exchange).padEnd(4))} ` +
536
- `8h: ${avg8h.padEnd(10)} 24h: ${avg24h.padEnd(10)} 7d: ${avg7d}`);
537
- }
538
- }
539
- console.log();
540
- const spreadColor = comparison.maxSpreadAnnual >= 30 ? chalk.green.bold
541
- : comparison.maxSpreadAnnual >= 10 ? chalk.yellow
542
- : chalk.white;
543
- console.log(` Max Spread: ${spreadColor(`${comparison.maxSpreadAnnual.toFixed(1)}%`)} annual`);
544
- console.log(` Direction: Long ${exAbbr(comparison.longExchange)} / Short ${exAbbr(comparison.shortExchange)}`);
545
- console.log(` Est. Income: $${comparison.estHourlyIncomeUsd.toFixed(4)}/hr per $1K notional`);
546
- // Show income at different position sizes
547
- const sizes = [1000, 5000, 10000, 50000];
548
- console.log(chalk.gray("\n Estimated daily income by position size:"));
549
- for (const size of sizes) {
550
- const daily = (comparison.estHourlyIncomeUsd / 1000) * size * 24;
551
- console.log(chalk.gray(` $${formatUsd(size)} notional -> $${daily.toFixed(2)}/day`));
552
- }
553
- console.log(chalk.gray("\n * Rates shown are current predictions. Actual settled rates may differ.\n"));
554
- }
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerGapCommands(program: Command, isJson: () => boolean): void;