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,266 @@
1
+ /**
2
+ * Historical funding rate tracking.
3
+ *
4
+ * Stores funding rate snapshots as JSONL files in ~/.perp/funding-rates/
5
+ * organized by month (YYYY-MM.jsonl). Provides averaging and trend analysis
6
+ * over configurable time windows.
7
+ */
8
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync, existsSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ // ── Constants ──
12
+ const DATA_DIR = join(homedir(), ".perp", "funding-rates");
13
+ const DEDUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
14
+ const CLEANUP_MAX_AGE_DAYS = 30;
15
+ // ── Internal helpers ──
16
+ function ensureDataDir() {
17
+ mkdirSync(DATA_DIR, { recursive: true });
18
+ }
19
+ function getMonthKey(date) {
20
+ const y = date.getFullYear();
21
+ const m = String(date.getMonth() + 1).padStart(2, "0");
22
+ return `${y}-${m}`;
23
+ }
24
+ function getFilePath(monthKey) {
25
+ return join(DATA_DIR, `${monthKey}.jsonl`);
26
+ }
27
+ /** Read all entries from a JSONL file, returning [] if file doesn't exist. */
28
+ function readJsonlFile(filePath) {
29
+ if (!existsSync(filePath))
30
+ return [];
31
+ try {
32
+ const content = readFileSync(filePath, "utf-8");
33
+ const entries = [];
34
+ for (const line of content.split("\n")) {
35
+ const trimmed = line.trim();
36
+ if (!trimmed)
37
+ continue;
38
+ try {
39
+ entries.push(JSON.parse(trimmed));
40
+ }
41
+ catch {
42
+ // skip malformed lines
43
+ }
44
+ }
45
+ return entries;
46
+ }
47
+ catch {
48
+ return [];
49
+ }
50
+ }
51
+ /** Append entries to a JSONL file. */
52
+ function appendToJsonl(filePath, entries) {
53
+ if (entries.length === 0)
54
+ return;
55
+ const lines = entries.map(e => JSON.stringify(e)).join("\n") + "\n";
56
+ try {
57
+ // Append to existing file or create new
58
+ const existing = existsSync(filePath) ? readFileSync(filePath, "utf-8") : "";
59
+ const needsNewline = existing.length > 0 && !existing.endsWith("\n");
60
+ writeFileSync(filePath, existing + (needsNewline ? "\n" : "") + lines);
61
+ }
62
+ catch {
63
+ // If append fails, try writing fresh
64
+ writeFileSync(filePath, lines);
65
+ }
66
+ }
67
+ /** Get entries across all relevant month files for a time range. */
68
+ function getEntriesInRange(startTime, endTime) {
69
+ ensureDataDir();
70
+ // Determine which month files to read
71
+ const monthKeys = new Set();
72
+ const cur = new Date(startTime);
73
+ while (cur <= endTime) {
74
+ monthKeys.add(getMonthKey(cur));
75
+ cur.setMonth(cur.getMonth() + 1);
76
+ cur.setDate(1); // reset to 1st to avoid day overflow
77
+ }
78
+ // Always include the end month
79
+ monthKeys.add(getMonthKey(endTime));
80
+ const allEntries = [];
81
+ for (const key of monthKeys) {
82
+ const filePath = getFilePath(key);
83
+ const entries = readJsonlFile(filePath);
84
+ for (const entry of entries) {
85
+ const entryTime = new Date(entry.ts).getTime();
86
+ if (entryTime >= startTime.getTime() && entryTime <= endTime.getTime()) {
87
+ allEntries.push(entry);
88
+ }
89
+ }
90
+ }
91
+ return allEntries;
92
+ }
93
+ // ── Public API ──
94
+ /**
95
+ * Clean up JSONL files older than 30 days.
96
+ * Called automatically on startup / first save.
97
+ */
98
+ let _cleanupDone = false;
99
+ export function cleanupOldFiles() {
100
+ if (_cleanupDone)
101
+ return;
102
+ _cleanupDone = true;
103
+ ensureDataDir();
104
+ const cutoff = new Date();
105
+ cutoff.setDate(cutoff.getDate() - CLEANUP_MAX_AGE_DAYS);
106
+ const cutoffKey = getMonthKey(cutoff);
107
+ try {
108
+ const files = readdirSync(DATA_DIR);
109
+ for (const file of files) {
110
+ if (!file.endsWith(".jsonl"))
111
+ continue;
112
+ const monthKey = file.replace(".jsonl", "");
113
+ // Compare YYYY-MM strings lexicographically
114
+ if (monthKey < cutoffKey) {
115
+ try {
116
+ unlinkSync(join(DATA_DIR, file));
117
+ }
118
+ catch {
119
+ // ignore deletion errors
120
+ }
121
+ }
122
+ }
123
+ }
124
+ catch {
125
+ // ignore
126
+ }
127
+ }
128
+ // Export for testing
129
+ export function _resetCleanupFlag() {
130
+ _cleanupDone = false;
131
+ }
132
+ /**
133
+ * Save current funding rates as a historical snapshot.
134
+ * Deduplicates: skips entries if same symbol+exchange was saved within 5 minutes.
135
+ */
136
+ export function saveFundingSnapshot(rates) {
137
+ cleanupOldFiles();
138
+ ensureDataDir();
139
+ const now = new Date();
140
+ const monthKey = getMonthKey(now);
141
+ const filePath = getFilePath(monthKey);
142
+ const ts = now.toISOString();
143
+ // Read existing entries for dedup check (only current month file)
144
+ const existing = readJsonlFile(filePath);
145
+ // Build a map of latest timestamps for each symbol+exchange
146
+ const latestTs = new Map();
147
+ for (const entry of existing) {
148
+ const key = `${entry.symbol}:${entry.exchange}`;
149
+ const entryTime = new Date(entry.ts).getTime();
150
+ const current = latestTs.get(key) ?? 0;
151
+ if (entryTime > current)
152
+ latestTs.set(key, entryTime);
153
+ }
154
+ // Filter out rates that would be duplicates (within 5 min)
155
+ const nowMs = now.getTime();
156
+ const toSave = [];
157
+ for (const r of rates) {
158
+ if (!r.symbol)
159
+ continue;
160
+ const key = `${r.symbol.toUpperCase()}:${r.exchange}`;
161
+ const lastSaved = latestTs.get(key) ?? 0;
162
+ if (nowMs - lastSaved < DEDUP_INTERVAL_MS)
163
+ continue;
164
+ toSave.push({
165
+ ts,
166
+ symbol: r.symbol.toUpperCase(),
167
+ exchange: r.exchange,
168
+ rate: r.fundingRate,
169
+ hourlyRate: r.hourlyRate,
170
+ });
171
+ }
172
+ appendToJsonl(filePath, toSave);
173
+ }
174
+ /**
175
+ * Get average funding rate for a symbol+exchange over the last N hours.
176
+ * Returns null if no data available.
177
+ */
178
+ export function getAvgFundingRate(symbol, exchange, hours) {
179
+ const endTime = new Date();
180
+ const startTime = new Date(endTime.getTime() - hours * 60 * 60 * 1000);
181
+ const entries = getEntriesInRange(startTime, endTime);
182
+ const filtered = entries.filter(e => e.symbol === symbol.toUpperCase() && e.exchange === exchange.toLowerCase());
183
+ if (filtered.length === 0)
184
+ return null;
185
+ const sum = filtered.reduce((acc, e) => acc + e.hourlyRate, 0);
186
+ return sum / filtered.length;
187
+ }
188
+ /**
189
+ * Get all historical rates for a symbol+exchange in a time range.
190
+ */
191
+ export function getHistoricalRates(symbol, exchange, startTime, endTime) {
192
+ const entries = getEntriesInRange(startTime, endTime);
193
+ return entries
194
+ .filter(e => e.symbol === symbol.toUpperCase() && e.exchange === exchange.toLowerCase())
195
+ .map(e => ({ ts: e.ts, rate: e.rate, hourlyRate: e.hourlyRate }))
196
+ .sort((a, b) => new Date(a.ts).getTime() - new Date(b.ts).getTime());
197
+ }
198
+ /**
199
+ * Get averaged rates for all symbols across time windows.
200
+ * Returns a Map with key format "SYMBOL:exchange".
201
+ */
202
+ export function getHistoricalAverages(symbols, exchanges) {
203
+ const result = new Map();
204
+ const now = new Date();
205
+ // Read all entries from the last 7 days (covers all windows)
206
+ const startTime = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
207
+ const allEntries = getEntriesInRange(startTime, now);
208
+ // Group entries by symbol:exchange
209
+ const grouped = new Map();
210
+ for (const entry of allEntries) {
211
+ const key = `${entry.symbol}:${entry.exchange}`;
212
+ if (!grouped.has(key))
213
+ grouped.set(key, []);
214
+ grouped.get(key).push(entry);
215
+ }
216
+ const windows = [
217
+ { key: "avg1h", ms: 1 * 60 * 60 * 1000 },
218
+ { key: "avg8h", ms: 8 * 60 * 60 * 1000 },
219
+ { key: "avg24h", ms: 24 * 60 * 60 * 1000 },
220
+ { key: "avg7d", ms: 7 * 24 * 60 * 60 * 1000 },
221
+ ];
222
+ for (const symbol of symbols) {
223
+ for (const exchange of exchanges) {
224
+ const key = `${symbol.toUpperCase()}:${exchange.toLowerCase()}`;
225
+ const entries = grouped.get(key) ?? [];
226
+ const avgs = { avg1h: null, avg8h: null, avg24h: null, avg7d: null };
227
+ for (const w of windows) {
228
+ const cutoff = now.getTime() - w.ms;
229
+ const inWindow = entries.filter(e => new Date(e.ts).getTime() >= cutoff);
230
+ if (inWindow.length > 0) {
231
+ const sum = inWindow.reduce((acc, e) => acc + e.hourlyRate, 0);
232
+ avgs[w.key] = sum / inWindow.length;
233
+ }
234
+ }
235
+ result.set(key, avgs);
236
+ }
237
+ }
238
+ return result;
239
+ }
240
+ /**
241
+ * Calculate effective annualized return considering compounding frequency.
242
+ *
243
+ * All three main exchanges (HL, PAC, LT) compound every 1h (8760 times/year).
244
+ *
245
+ * Formula: (1 + rate)^(8760/compoundingHours) - 1
246
+ *
247
+ * @param hourlyRate - the per-hour funding rate
248
+ * @param compoundingHours - how often the exchange compounds (1 for all main exchanges)
249
+ * @returns effective annualized return as a decimal (not percentage)
250
+ */
251
+ export function getCompoundedAnnualReturn(hourlyRate, compoundingHours) {
252
+ // The rate per compounding period
253
+ const periodRate = hourlyRate * compoundingHours;
254
+ // Number of compounding periods per year
255
+ const periodsPerYear = 8760 / compoundingHours;
256
+ // Compounded annual return
257
+ return Math.pow(1 + periodRate, periodsPerYear) - 1;
258
+ }
259
+ /**
260
+ * Get the compounding hours for an exchange.
261
+ * All main exchanges (HL, PAC, LT) compound every 1h.
262
+ */
263
+ export function getExchangeCompoundingHours(exchange) {
264
+ // All main exchanges compound hourly
265
+ return 1;
266
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./normalize.js";
2
+ export * from "./rates.js";
3
+ export * from "./history.js";
@@ -0,0 +1,3 @@
1
+ export * from "./normalize.js";
2
+ export * from "./rates.js";
3
+ export * from "./history.js";
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Funding rate normalization.
3
+ *
4
+ * Exchange funding rate periods:
5
+ * - Hyperliquid: per 1 HOUR (settles every 1h)
6
+ * - Pacifica: per 1 HOUR (settles every 1h)
7
+ * - Lighter: per 8 HOURS (API returns 8h rate, settles every 1h)
8
+ *
9
+ * HIP-3 deployed dexes on Hyperliquid use 8h funding periods.
10
+ *
11
+ * To compare rates across exchanges, we normalize everything to
12
+ * a per-hour basis, then annualize from there.
13
+ */
14
+ /**
15
+ * Get funding period in hours for an exchange.
16
+ * HIP-3 deployed dexes (not in the map) default to 1h (API returns hourly).
17
+ * HL and PAC are 1h, Lighter API returns 8h rates.
18
+ */
19
+ export declare function getFundingHours(exchange: string): number;
20
+ /** Convert a raw funding rate to per-hour rate */
21
+ export declare function toHourlyRate(rate: number, exchange: string): number;
22
+ /** Annualize a raw rate from a specific exchange */
23
+ export declare function annualizeRate(rate: number, exchange: string): number;
24
+ /** Annualize an already-normalized hourly rate (no exchange conversion needed) */
25
+ export declare function annualizeHourlyRate(hourlyRate: number): number;
26
+ /**
27
+ * Compute annualized spread between two exchange rates.
28
+ * Normalizes both to hourly before comparing.
29
+ */
30
+ export declare function computeAnnualSpread(rateA: number, exchangeA: string, rateB: number, exchangeB: string): number;
31
+ /**
32
+ * Estimate hourly funding payment for a position.
33
+ * @param rate - raw funding rate from the exchange
34
+ * @param exchange - exchange name
35
+ * @param positionUsd - position notional in USD
36
+ * @param side - "long" or "short" (longs pay positive rate, shorts receive)
37
+ * @returns hourly payment in USD (positive = you pay, negative = you receive)
38
+ */
39
+ export declare function estimateHourlyFunding(rate: number, exchange: string, positionUsd: number, side: "long" | "short"): number;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Funding rate normalization.
3
+ *
4
+ * Exchange funding rate periods:
5
+ * - Hyperliquid: per 1 HOUR (settles every 1h)
6
+ * - Pacifica: per 1 HOUR (settles every 1h)
7
+ * - Lighter: per 8 HOURS (API returns 8h rate, settles every 1h)
8
+ *
9
+ * HIP-3 deployed dexes on Hyperliquid use 8h funding periods.
10
+ *
11
+ * To compare rates across exchanges, we normalize everything to
12
+ * a per-hour basis, then annualize from there.
13
+ */
14
+ /** Funding periods per year by convention */
15
+ const HOURLY_PERIODS = 24 * 365; // 8760
16
+ /** How many hours each exchange's rate covers */
17
+ const EXCHANGE_FUNDING_HOURS = {
18
+ hyperliquid: 1,
19
+ pacifica: 1,
20
+ lighter: 8,
21
+ };
22
+ /**
23
+ * Get funding period in hours for an exchange.
24
+ * HIP-3 deployed dexes (not in the map) default to 1h (API returns hourly).
25
+ * HL and PAC are 1h, Lighter API returns 8h rates.
26
+ */
27
+ export function getFundingHours(exchange) {
28
+ return EXCHANGE_FUNDING_HOURS[exchange.toLowerCase()] ?? 1;
29
+ }
30
+ /** Convert a raw funding rate to per-hour rate */
31
+ export function toHourlyRate(rate, exchange) {
32
+ const hours = EXCHANGE_FUNDING_HOURS[exchange.toLowerCase()] ?? 1;
33
+ return rate / hours;
34
+ }
35
+ /** Annualize a raw rate from a specific exchange */
36
+ export function annualizeRate(rate, exchange) {
37
+ const hourlyRate = toHourlyRate(rate, exchange);
38
+ return hourlyRate * HOURLY_PERIODS * 100; // as percentage
39
+ }
40
+ /** Annualize an already-normalized hourly rate (no exchange conversion needed) */
41
+ export function annualizeHourlyRate(hourlyRate) {
42
+ return hourlyRate * HOURLY_PERIODS * 100; // as percentage
43
+ }
44
+ /**
45
+ * Compute annualized spread between two exchange rates.
46
+ * Normalizes both to hourly before comparing.
47
+ */
48
+ export function computeAnnualSpread(rateA, exchangeA, rateB, exchangeB) {
49
+ const hourlyA = toHourlyRate(rateA, exchangeA);
50
+ const hourlyB = toHourlyRate(rateB, exchangeB);
51
+ return Math.abs(hourlyA - hourlyB) * HOURLY_PERIODS * 100;
52
+ }
53
+ /**
54
+ * Estimate hourly funding payment for a position.
55
+ * @param rate - raw funding rate from the exchange
56
+ * @param exchange - exchange name
57
+ * @param positionUsd - position notional in USD
58
+ * @param side - "long" or "short" (longs pay positive rate, shorts receive)
59
+ * @returns hourly payment in USD (positive = you pay, negative = you receive)
60
+ */
61
+ export function estimateHourlyFunding(rate, exchange, positionUsd, side) {
62
+ const hourlyRate = toHourlyRate(rate, exchange);
63
+ // Long pays positive funding, short receives positive funding
64
+ const multiplier = side === "long" ? 1 : -1;
65
+ return hourlyRate * positionUsd * multiplier;
66
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Real-time 3-DEX funding rate comparison.
3
+ *
4
+ * Fetches funding rates from Pacifica, Hyperliquid, and Lighter in parallel,
5
+ * normalizes them to comparable hourly rates, and identifies arbitrage
6
+ * opportunities across exchanges.
7
+ */
8
+ import { type HistoricalAverages } from "./history.js";
9
+ export interface ExchangeFundingRate {
10
+ exchange: "pacifica" | "hyperliquid" | "lighter";
11
+ symbol: string;
12
+ fundingRate: number;
13
+ hourlyRate: number;
14
+ annualizedPct: number;
15
+ markPrice: number;
16
+ nextFundingTime?: number;
17
+ historicalAvg?: HistoricalAverages;
18
+ }
19
+ export interface SymbolFundingComparison {
20
+ symbol: string;
21
+ rates: ExchangeFundingRate[];
22
+ maxSpreadAnnual: number;
23
+ longExchange: string;
24
+ shortExchange: string;
25
+ bestMarkPrice: number;
26
+ estHourlyIncomeUsd: number;
27
+ }
28
+ export interface FundingRateSnapshot {
29
+ timestamp: string;
30
+ symbols: SymbolFundingComparison[];
31
+ exchangeStatus: Record<string, "ok" | "error">;
32
+ }
33
+ export declare const TOP_SYMBOLS: string[];
34
+ /**
35
+ * Fetch funding rates from all 3 DEXs in parallel, normalize, and compare.
36
+ * Returns rates sorted by max spread (descending).
37
+ */
38
+ export declare function fetchAllFundingRates(opts?: {
39
+ symbols?: string[];
40
+ minSpread?: number;
41
+ }): Promise<FundingRateSnapshot>;
42
+ /**
43
+ * Fetch rates for a single symbol across all exchanges.
44
+ */
45
+ export declare function fetchSymbolFundingRates(symbol: string): Promise<SymbolFundingComparison | null>;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Real-time 3-DEX funding rate comparison.
3
+ *
4
+ * Fetches funding rates from Pacifica, Hyperliquid, and Lighter in parallel,
5
+ * normalizes them to comparable hourly rates, and identifies arbitrage
6
+ * opportunities across exchanges.
7
+ */
8
+ import { toHourlyRate, annualizeRate, computeAnnualSpread, estimateHourlyFunding } from "./normalize.js";
9
+ import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, fetchLighterFundingRates as fetchLtFundingRates, } from "../shared-api.js";
10
+ import { getHistoricalAverages } from "./history.js";
11
+ // ── Default top symbols to track ──
12
+ export const TOP_SYMBOLS = [
13
+ "BTC", "ETH", "SOL", "DOGE", "SUI", "AVAX", "LINK", "ARB",
14
+ "WIF", "PEPE", "ONDO", "SEI", "TIA", "INJ", "NEAR",
15
+ "APT", "OP", "FIL", "AAVE", "MKR",
16
+ ];
17
+ // ── Fetchers (using shared-api.ts) ──
18
+ async function fetchPacificaRates() {
19
+ try {
20
+ const assets = await fetchPacificaPrices();
21
+ return assets.map(p => {
22
+ const hourly = toHourlyRate(p.funding, "pacifica");
23
+ return {
24
+ exchange: "pacifica",
25
+ symbol: p.symbol,
26
+ fundingRate: p.funding,
27
+ hourlyRate: hourly,
28
+ annualizedPct: annualizeRate(p.funding, "pacifica"),
29
+ markPrice: p.mark,
30
+ nextFundingTime: p.nextFunding,
31
+ };
32
+ });
33
+ }
34
+ catch {
35
+ return [];
36
+ }
37
+ }
38
+ async function fetchHyperliquidRates() {
39
+ try {
40
+ const assets = await fetchHyperliquidMeta();
41
+ return assets.map(a => {
42
+ const hourly = toHourlyRate(a.funding, "hyperliquid");
43
+ return {
44
+ exchange: "hyperliquid",
45
+ symbol: a.symbol,
46
+ fundingRate: a.funding,
47
+ hourlyRate: hourly,
48
+ annualizedPct: annualizeRate(a.funding, "hyperliquid"),
49
+ markPrice: a.markPx,
50
+ };
51
+ });
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ }
57
+ async function fetchLighterRates() {
58
+ try {
59
+ const [details, funding] = await Promise.all([
60
+ fetchLighterOrderBookDetails(),
61
+ fetchLtFundingRates(),
62
+ ]);
63
+ const priceMap = new Map(details.map(d => [d.marketId, d.lastTradePrice]));
64
+ const symMap = new Map(details.map(d => [d.marketId, d.symbol]));
65
+ return funding.map(fr => {
66
+ const symbol = fr.symbol || symMap.get(fr.marketId) || "";
67
+ const rate = fr.rate;
68
+ const hourly = toHourlyRate(rate, "lighter");
69
+ return {
70
+ exchange: "lighter",
71
+ symbol,
72
+ fundingRate: rate,
73
+ hourlyRate: hourly,
74
+ annualizedPct: annualizeRate(rate, "lighter"),
75
+ markPrice: fr.markPrice || priceMap.get(fr.marketId) || 0,
76
+ };
77
+ });
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ }
83
+ // ── Core comparison logic ──
84
+ /**
85
+ * Fetch funding rates from all 3 DEXs in parallel, normalize, and compare.
86
+ * Returns rates sorted by max spread (descending).
87
+ */
88
+ export async function fetchAllFundingRates(opts) {
89
+ const exchangeStatus = {};
90
+ const [pacRates, hlRates, ltRates] = await Promise.all([
91
+ fetchPacificaRates().then(r => { exchangeStatus.pacifica = r.length > 0 ? "ok" : "error"; return r; }),
92
+ fetchHyperliquidRates().then(r => { exchangeStatus.hyperliquid = r.length > 0 ? "ok" : "error"; return r; }),
93
+ fetchLighterRates().then(r => { exchangeStatus.lighter = r.length > 0 ? "ok" : "error"; return r; }),
94
+ ]);
95
+ const allRates = [...pacRates, ...hlRates, ...ltRates];
96
+ // Build per-symbol rate map
97
+ const rateMap = new Map();
98
+ for (const r of allRates) {
99
+ if (!r.symbol)
100
+ continue;
101
+ if (opts?.symbols && !opts.symbols.includes(r.symbol.toUpperCase()))
102
+ continue;
103
+ const key = r.symbol.toUpperCase();
104
+ if (!rateMap.has(key))
105
+ rateMap.set(key, []);
106
+ rateMap.get(key).push(r);
107
+ }
108
+ // Attach historical averages when available
109
+ try {
110
+ const symbols = Array.from(rateMap.keys());
111
+ const exchanges = ["pacifica", "hyperliquid", "lighter"];
112
+ const historicals = getHistoricalAverages(symbols, exchanges);
113
+ for (const [, rates] of rateMap) {
114
+ for (const r of rates) {
115
+ const key = `${r.symbol.toUpperCase()}:${r.exchange}`;
116
+ const avg = historicals.get(key);
117
+ if (avg)
118
+ r.historicalAvg = avg;
119
+ }
120
+ }
121
+ }
122
+ catch {
123
+ // Non-critical
124
+ }
125
+ const comparisons = [];
126
+ const minSpread = opts?.minSpread ?? 0;
127
+ for (const [symbol, rates] of rateMap) {
128
+ // Need at least 2 exchanges to compare
129
+ if (rates.length < 2)
130
+ continue;
131
+ // Sort by hourly rate (ascending)
132
+ rates.sort((a, b) => a.hourlyRate - b.hourlyRate);
133
+ const lowest = rates[0];
134
+ const highest = rates[rates.length - 1];
135
+ const maxSpreadAnnual = computeAnnualSpread(highest.fundingRate, highest.exchange, lowest.fundingRate, lowest.exchange);
136
+ if (maxSpreadAnnual < minSpread)
137
+ continue;
138
+ // Best mark price: prefer HL (most liquid), then PAC, then LT
139
+ const hlRate = rates.find(r => r.exchange === "hyperliquid");
140
+ const pacRate = rates.find(r => r.exchange === "pacifica");
141
+ const ltRate = rates.find(r => r.exchange === "lighter");
142
+ const bestMarkPrice = hlRate?.markPrice || pacRate?.markPrice || ltRate?.markPrice || 0;
143
+ // Estimate hourly income for $1000 notional arb
144
+ const notional = 1000;
145
+ const longIncome = estimateHourlyFunding(lowest.fundingRate, lowest.exchange, notional, "long");
146
+ const shortIncome = estimateHourlyFunding(highest.fundingRate, highest.exchange, notional, "short");
147
+ const estHourlyIncomeUsd = -(longIncome + shortIncome); // negate because income = -cost
148
+ comparisons.push({
149
+ symbol,
150
+ rates,
151
+ maxSpreadAnnual,
152
+ longExchange: lowest.exchange, // long where funding is lowest
153
+ shortExchange: highest.exchange, // short where funding is highest
154
+ bestMarkPrice,
155
+ estHourlyIncomeUsd,
156
+ });
157
+ }
158
+ // Sort by spread descending
159
+ comparisons.sort((a, b) => b.maxSpreadAnnual - a.maxSpreadAnnual);
160
+ return {
161
+ timestamp: new Date().toISOString(),
162
+ symbols: comparisons,
163
+ exchangeStatus,
164
+ };
165
+ }
166
+ /**
167
+ * Fetch rates for a single symbol across all exchanges.
168
+ */
169
+ export async function fetchSymbolFundingRates(symbol) {
170
+ const snapshot = await fetchAllFundingRates({ symbols: [symbol.toUpperCase()] });
171
+ return snapshot.symbols[0] ?? null;
172
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/funding/history.ts.
4
+ */
5
+ export * from "./funding/history.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/funding/history.ts.
4
+ */
5
+ export * from "./funding/history.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/funding/rates.ts.
4
+ */
5
+ export * from "./funding/rates.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/funding/rates.ts.
4
+ */
5
+ export * from "./funding/rates.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/funding/normalize.ts.
4
+ */
5
+ export * from "./funding/normalize.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/funding/normalize.ts.
4
+ */
5
+ export * from "./funding/normalize.js";
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};