perp-cli 0.11.0 → 0.12.1

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 (240) 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/bridge-engine.js +102 -49
  21. package/dist/cli-spec.js +1 -2
  22. package/dist/commands/account.js +2 -500
  23. package/dist/commands/agent.d.ts +8 -2
  24. package/dist/commands/agent.js +1903 -366
  25. package/dist/commands/alerts.js +3 -1
  26. package/dist/commands/arb/index.js +37 -7
  27. package/dist/commands/arb-auto.js +44 -11
  28. package/dist/commands/arb.js +83 -34
  29. package/dist/commands/bot.d.ts +1 -1
  30. package/dist/commands/bot.js +18 -14
  31. package/dist/commands/bridge.d.ts +1 -1
  32. package/dist/commands/bridge.js +4 -4
  33. package/dist/commands/funds.d.ts +1 -1
  34. package/dist/commands/funds.js +18 -68
  35. package/dist/commands/jobs.d.ts +1 -1
  36. package/dist/commands/jobs.js +2 -2
  37. package/dist/commands/manage.d.ts +1 -1
  38. package/dist/commands/manage.js +12 -84
  39. package/dist/commands/plan.d.ts +1 -1
  40. package/dist/commands/plan.js +2 -2
  41. package/dist/commands/portfolio.d.ts +19 -3
  42. package/dist/commands/portfolio.js +747 -163
  43. package/dist/commands/rebalance.d.ts +1 -1
  44. package/dist/commands/rebalance.js +18 -12
  45. package/dist/commands/risk.js +2 -6
  46. package/dist/commands/settings.js +30 -53
  47. package/dist/commands/trade.js +16 -15
  48. package/dist/commands/wallet.d.ts +2 -1
  49. package/dist/commands/wallet.js +756 -28
  50. package/dist/errors.d.ts +64 -0
  51. package/dist/errors.js +82 -2
  52. package/dist/event-stream.js +12 -1
  53. package/dist/exchanges/aster-typed-data.d.ts +156 -0
  54. package/dist/exchanges/aster-typed-data.js +189 -0
  55. package/dist/exchanges/aster.d.ts +60 -11
  56. package/dist/exchanges/aster.js +278 -136
  57. package/dist/exchanges/hyperliquid.d.ts +14 -0
  58. package/dist/exchanges/hyperliquid.js +84 -34
  59. package/dist/exchanges/lighter-spot.js +14 -3
  60. package/dist/exchanges/lighter.d.ts +66 -0
  61. package/dist/exchanges/lighter.js +199 -27
  62. package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
  63. package/dist/exchanges/pacifica-typed-data.js +74 -0
  64. package/dist/exchanges/pacifica.d.ts +29 -1
  65. package/dist/exchanges/pacifica.js +94 -6
  66. package/dist/funding/rates.js +59 -21
  67. package/dist/guardrail/perp-guardrail.js +9 -1
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +268 -360
  70. package/dist/jobs.js +3 -1
  71. package/dist/mcp-server.js +85 -83
  72. package/dist/position-history.js +47 -4
  73. package/dist/settings.d.ts +67 -3
  74. package/dist/settings.js +1 -2
  75. package/dist/signer/ows-evm.d.ts +14 -0
  76. package/dist/signer/ows-evm.js +28 -9
  77. package/dist/smart-order.d.ts +8 -2
  78. package/dist/smart-order.js +46 -11
  79. package/dist/strategies/funding-arb.js +29 -8
  80. package/dist/trade-validator.js +30 -5
  81. package/dist/utils.d.ts +10 -0
  82. package/dist/utils.js +19 -0
  83. package/package.json +2 -2
  84. package/skills/perp-cli/SKILL.md +23 -14
  85. package/skills/perp-cli/references/agent-operations.md +6 -4
  86. package/skills/perp-cli/references/commands.md +55 -34
  87. package/skills/perp-cli/scripts/preflight.sh +4 -4
  88. package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
  89. package/dist/__tests__/alert-logic.test.d.ts +0 -1
  90. package/dist/__tests__/alert-logic.test.js +0 -107
  91. package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
  92. package/dist/__tests__/arb-auto-3dex.test.js +0 -397
  93. package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
  94. package/dist/__tests__/arb-history-stats.test.js +0 -176
  95. package/dist/__tests__/arb-logic.test.d.ts +0 -1
  96. package/dist/__tests__/arb-logic.test.js +0 -84
  97. package/dist/__tests__/arb-manage.test.d.ts +0 -1
  98. package/dist/__tests__/arb-manage.test.js +0 -253
  99. package/dist/__tests__/arb-new-features.test.d.ts +0 -1
  100. package/dist/__tests__/arb-new-features.test.js +0 -457
  101. package/dist/__tests__/arb-sizing.test.d.ts +0 -1
  102. package/dist/__tests__/arb-sizing.test.js +0 -48
  103. package/dist/__tests__/arb-state.test.d.ts +0 -1
  104. package/dist/__tests__/arb-state.test.js +0 -284
  105. package/dist/__tests__/arb-userflow.test.d.ts +0 -1
  106. package/dist/__tests__/arb-userflow.test.js +0 -945
  107. package/dist/__tests__/arb-utils.test.d.ts +0 -1
  108. package/dist/__tests__/arb-utils.test.js +0 -264
  109. package/dist/__tests__/bot-conditions.test.d.ts +0 -1
  110. package/dist/__tests__/bot-conditions.test.js +0 -341
  111. package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
  112. package/dist/__tests__/client-id-tracker.test.js +0 -137
  113. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
  114. package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
  115. package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
  116. package/dist/__tests__/commands/order-intent.test.js +0 -600
  117. package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
  118. package/dist/__tests__/commands/trade-commands.test.js +0 -821
  119. package/dist/__tests__/config.test.d.ts +0 -1
  120. package/dist/__tests__/config.test.js +0 -86
  121. package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
  122. package/dist/__tests__/cross-chain-margin.test.js +0 -287
  123. package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
  124. package/dist/__tests__/dex-asset-map.test.js +0 -191
  125. package/dist/__tests__/errors.test.d.ts +0 -1
  126. package/dist/__tests__/errors.test.js +0 -110
  127. package/dist/__tests__/event-stream.test.d.ts +0 -1
  128. package/dist/__tests__/event-stream.test.js +0 -276
  129. package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
  130. package/dist/__tests__/exchanges/interface.test.js +0 -132
  131. package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
  132. package/dist/__tests__/exchanges/mock-adapter.js +0 -137
  133. package/dist/__tests__/execution-log.test.d.ts +0 -1
  134. package/dist/__tests__/execution-log.test.js +0 -106
  135. package/dist/__tests__/funding-calc.test.d.ts +0 -1
  136. package/dist/__tests__/funding-calc.test.js +0 -71
  137. package/dist/__tests__/funding-history.test.d.ts +0 -1
  138. package/dist/__tests__/funding-history.test.js +0 -343
  139. package/dist/__tests__/funding-rates.test.d.ts +0 -1
  140. package/dist/__tests__/funding-rates.test.js +0 -342
  141. package/dist/__tests__/funding.test.d.ts +0 -1
  142. package/dist/__tests__/funding.test.js +0 -173
  143. package/dist/__tests__/gap-logic.test.d.ts +0 -1
  144. package/dist/__tests__/gap-logic.test.js +0 -43
  145. package/dist/__tests__/hip3-dex.test.d.ts +0 -1
  146. package/dist/__tests__/hip3-dex.test.js +0 -234
  147. package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
  148. package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
  149. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
  150. package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
  151. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
  152. package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
  153. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
  154. package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
  155. package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
  156. package/dist/__tests__/integration/bridge.integration.test.js +0 -309
  157. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
  158. package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
  159. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
  160. package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
  161. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
  162. package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
  163. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
  164. package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
  165. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
  166. package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
  167. package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
  168. package/dist/__tests__/integration/lighter.integration.test.js +0 -53
  169. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
  170. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
  171. package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
  172. package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
  173. package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
  174. package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
  175. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
  176. package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
  177. package/dist/__tests__/liquidity.test.d.ts +0 -1
  178. package/dist/__tests__/liquidity.test.js +0 -225
  179. package/dist/__tests__/plan-executor.test.d.ts +0 -1
  180. package/dist/__tests__/plan-executor.test.js +0 -314
  181. package/dist/__tests__/position-history.test.d.ts +0 -1
  182. package/dist/__tests__/position-history.test.js +0 -367
  183. package/dist/__tests__/retry.test.d.ts +0 -1
  184. package/dist/__tests__/retry.test.js +0 -310
  185. package/dist/__tests__/risk-assessment.test.d.ts +0 -1
  186. package/dist/__tests__/risk-assessment.test.js +0 -288
  187. package/dist/__tests__/security-adversarial.test.d.ts +0 -1
  188. package/dist/__tests__/security-adversarial.test.js +0 -574
  189. package/dist/__tests__/strategies.test.d.ts +0 -1
  190. package/dist/__tests__/strategies.test.js +0 -539
  191. package/dist/__tests__/trade-execution.test.d.ts +0 -1
  192. package/dist/__tests__/trade-execution.test.js +0 -129
  193. package/dist/__tests__/trade-validator.test.d.ts +0 -1
  194. package/dist/__tests__/trade-validator.test.js +0 -655
  195. package/dist/__tests__/utils.test.d.ts +0 -1
  196. package/dist/__tests__/utils.test.js +0 -76
  197. package/dist/arb/reconcile.d.ts +0 -28
  198. package/dist/arb/reconcile.js +0 -162
  199. package/dist/commands/alert.d.ts +0 -2
  200. package/dist/commands/alert.js +0 -421
  201. package/dist/commands/analytics.d.ts +0 -3
  202. package/dist/commands/analytics.js +0 -311
  203. package/dist/commands/arb-manage.d.ts +0 -5
  204. package/dist/commands/arb-manage.js +0 -5
  205. package/dist/commands/dashboard.d.ts +0 -3
  206. package/dist/commands/dashboard.js +0 -176
  207. package/dist/commands/deposit.d.ts +0 -4
  208. package/dist/commands/deposit.js +0 -573
  209. package/dist/commands/dex.d.ts +0 -3
  210. package/dist/commands/dex.js +0 -120
  211. package/dist/commands/env.d.ts +0 -2
  212. package/dist/commands/env.js +0 -136
  213. package/dist/commands/funding.d.ts +0 -3
  214. package/dist/commands/funding.js +0 -554
  215. package/dist/commands/gap.d.ts +0 -2
  216. package/dist/commands/gap.js +0 -305
  217. package/dist/commands/health.d.ts +0 -2
  218. package/dist/commands/health.js +0 -67
  219. package/dist/commands/multileg.d.ts +0 -16
  220. package/dist/commands/multileg.js +0 -174
  221. package/dist/commands/ows.d.ts +0 -2
  222. package/dist/commands/ows.js +0 -887
  223. package/dist/commands/pnl.d.ts +0 -12
  224. package/dist/commands/pnl.js +0 -243
  225. package/dist/commands/run.d.ts +0 -3
  226. package/dist/commands/run.js +0 -202
  227. package/dist/commands/stream.d.ts +0 -5
  228. package/dist/commands/stream.js +0 -200
  229. package/dist/commands/withdraw.d.ts +0 -3
  230. package/dist/commands/withdraw.js +0 -187
  231. package/dist/exchanges/edgex.d.ts +0 -62
  232. package/dist/exchanges/edgex.js +0 -552
  233. package/dist/exchanges/nado.d.ts +0 -52
  234. package/dist/exchanges/nado.js +0 -223
  235. package/dist/funding/index.d.ts +0 -3
  236. package/dist/funding/index.js +0 -3
  237. package/dist/retry.d.ts +0 -74
  238. package/dist/retry.js +0 -129
  239. package/dist/ws/market-feed.d.ts +0 -74
  240. 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;