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,96 @@
1
+ /**
2
+ * Calculate the exact same size for both legs of an arb position,
3
+ * respecting both exchanges' minimum order and size precision constraints.
4
+ *
5
+ * @returns size string that works for both exchanges, or null if not viable
6
+ */
7
+ export function computeMatchedSize(sizeUsd, price, longExchange, shortExchange) {
8
+ if (price <= 0)
9
+ return null;
10
+ const rawSize = sizeUsd / price;
11
+ // Size decimals per exchange (conservative defaults)
12
+ const szDecimals = Math.min(getSizeDecimals(longExchange), getSizeDecimals(shortExchange));
13
+ // Round DOWN to the least precise exchange's decimals
14
+ const factor = Math.pow(10, szDecimals);
15
+ const roundedSize = Math.floor(rawSize * factor) / factor;
16
+ if (roundedSize <= 0)
17
+ return null;
18
+ // Verify notional meets minimum for both exchanges
19
+ const notional = roundedSize * price;
20
+ const minNotional = Math.max(getMinNotional(longExchange), getMinNotional(shortExchange));
21
+ if (notional < minNotional) {
22
+ // Try rounding UP instead
23
+ const roundedUp = Math.ceil(rawSize * factor) / factor;
24
+ const notionalUp = roundedUp * price;
25
+ // Only round up if the increase is small (< 20% over requested)
26
+ if (notionalUp <= sizeUsd * 1.2) {
27
+ return { size: roundedUp.toFixed(szDecimals), notional: notionalUp };
28
+ }
29
+ return null; // Can't meet minimum
30
+ }
31
+ return { size: roundedSize.toFixed(szDecimals), notional };
32
+ }
33
+ /** Size decimal precision by exchange */
34
+ function getSizeDecimals(exchange) {
35
+ switch (exchange.toLowerCase()) {
36
+ case "hyperliquid": return 1; // Most HL perps use 1 decimal
37
+ case "lighter": return 2;
38
+ case "pacifica": return 4;
39
+ default: return 2;
40
+ }
41
+ }
42
+ /** Minimum notional (USD) per exchange */
43
+ function getMinNotional(exchange) {
44
+ switch (exchange.toLowerCase()) {
45
+ case "hyperliquid": return 10;
46
+ case "lighter": return 10;
47
+ case "pacifica": return 1;
48
+ default: return 10;
49
+ }
50
+ }
51
+ /**
52
+ * After both legs are submitted, verify actual fills match.
53
+ * If there's a size mismatch, place a correction order on the larger side.
54
+ *
55
+ * @returns the corrected size, or null if already matched
56
+ */
57
+ export async function reconcileArbFills(longAdapter, shortAdapter, symbol, log) {
58
+ const [longPositions, shortPositions] = await Promise.all([
59
+ longAdapter.getPositions(),
60
+ shortAdapter.getPositions(),
61
+ ]);
62
+ const longPos = longPositions.find(p => p.symbol.replace("-PERP", "").toUpperCase() === symbol.toUpperCase() && p.side === "long");
63
+ const shortPos = shortPositions.find(p => p.symbol.replace("-PERP", "").toUpperCase() === symbol.toUpperCase() && p.side === "short");
64
+ const longSize = longPos ? Math.abs(Number(longPos.size)) : 0;
65
+ const shortSize = shortPos ? Math.abs(Number(shortPos.size)) : 0;
66
+ if (longSize === 0 && shortSize === 0) {
67
+ return { matched: true, longSize: 0, shortSize: 0 };
68
+ }
69
+ const diff = Math.abs(longSize - shortSize);
70
+ const maxSize = Math.max(longSize, shortSize);
71
+ const diffPct = maxSize > 0 ? (diff / maxSize) * 100 : 0;
72
+ // Allow 1% tolerance (rounding differences)
73
+ if (diffPct <= 1) {
74
+ return { matched: true, longSize, shortSize };
75
+ }
76
+ log?.(`[ARB] Size mismatch: long ${longSize} vs short ${shortSize} (diff: ${diff.toFixed(4)}, ${diffPct.toFixed(1)}%)`);
77
+ // Correct the larger side by reducing it
78
+ const correctionSize = diff.toFixed(6);
79
+ try {
80
+ if (longSize > shortSize) {
81
+ log?.(`[ARB] Correcting: sell ${correctionSize} ${symbol} on ${longAdapter.name} to match`);
82
+ await longAdapter.marketOrder(symbol, "sell", correctionSize);
83
+ }
84
+ else {
85
+ log?.(`[ARB] Correcting: buy ${correctionSize} ${symbol} on ${shortAdapter.name} to match`);
86
+ await shortAdapter.marketOrder(symbol, "buy", correctionSize);
87
+ }
88
+ log?.(`[ARB] Sizes reconciled`);
89
+ return { matched: true, longSize: Math.min(longSize, shortSize), shortSize: Math.min(longSize, shortSize), correction: correctionSize };
90
+ }
91
+ catch (err) {
92
+ const msg = err instanceof Error ? err.message : String(err);
93
+ log?.(`[ARB] Correction failed: ${msg}`);
94
+ return { matched: false, longSize, shortSize };
95
+ }
96
+ }
@@ -0,0 +1,51 @@
1
+ export declare function setStateFilePath(path: string): void;
2
+ export declare function resetStateFilePath(): void;
3
+ export interface ArbPositionState {
4
+ id: string;
5
+ symbol: string;
6
+ longExchange: string;
7
+ shortExchange: string;
8
+ longSize: number;
9
+ shortSize: number;
10
+ entryTime: string;
11
+ entrySpread: number;
12
+ entryLongPrice: number;
13
+ entryShortPrice: number;
14
+ accumulatedFunding: number;
15
+ lastCheckTime: string;
16
+ }
17
+ export interface ArbDaemonState {
18
+ version: 1;
19
+ lastStartTime: string;
20
+ lastScanTime: string;
21
+ lastSuccessfulScanTime: string;
22
+ positions: ArbPositionState[];
23
+ config: {
24
+ minSpread: number;
25
+ closeSpread: number;
26
+ size: number | "auto";
27
+ holdDays: number;
28
+ bridgeCost: number;
29
+ maxPositions: number;
30
+ settleStrategy: string;
31
+ notifyUrl?: string;
32
+ };
33
+ }
34
+ /** Load daemon state from disk. Falls back to .tmp if main file is corrupted. */
35
+ export declare function loadArbState(): ArbDaemonState | null;
36
+ /**
37
+ * Save daemon state to disk using atomic write pattern.
38
+ * Writes to .tmp first, then renames to main file.
39
+ * If crash occurs mid-write, .tmp is partial but main file is intact.
40
+ */
41
+ export declare function saveArbState(state: ArbDaemonState): void;
42
+ /** Add a position to the persisted state. */
43
+ export declare function addPosition(pos: ArbPositionState): void;
44
+ /** Remove a position by symbol from the persisted state. */
45
+ export declare function removePosition(symbol: string): void;
46
+ /** Update a position by symbol with partial updates. */
47
+ export declare function updatePosition(symbol: string, updates: Partial<ArbPositionState>): void;
48
+ /** Get all persisted positions. */
49
+ export declare function getPositions(): ArbPositionState[];
50
+ /** Create a default empty daemon state with the given config. */
51
+ export declare function createInitialState(config: ArbDaemonState["config"]): ArbDaemonState;
@@ -0,0 +1,112 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "fs";
2
+ import { resolve } from "path";
3
+ const PERP_DIR = resolve(process.env.HOME || "~", ".perp");
4
+ const STATE_FILE = resolve(PERP_DIR, "arb-state.json");
5
+ // Allow overriding the state file path for testing
6
+ let stateFilePath = STATE_FILE;
7
+ export function setStateFilePath(path) {
8
+ stateFilePath = path;
9
+ }
10
+ export function resetStateFilePath() {
11
+ stateFilePath = STATE_FILE;
12
+ }
13
+ function ensureDir() {
14
+ const dir = resolve(stateFilePath, "..");
15
+ if (!existsSync(dir))
16
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
17
+ }
18
+ /** Load daemon state from disk. Falls back to .tmp if main file is corrupted. */
19
+ export function loadArbState() {
20
+ const tmpPath = stateFilePath + ".tmp";
21
+ // Try main file first
22
+ if (existsSync(stateFilePath)) {
23
+ try {
24
+ const raw = readFileSync(stateFilePath, "utf-8");
25
+ const parsed = JSON.parse(raw);
26
+ if (parsed.version === 1)
27
+ return parsed;
28
+ }
29
+ catch {
30
+ // Main file corrupted — try .tmp fallback
31
+ }
32
+ }
33
+ // Fallback: recover from .tmp if main is missing or corrupted
34
+ if (existsSync(tmpPath)) {
35
+ try {
36
+ const raw = readFileSync(tmpPath, "utf-8");
37
+ const parsed = JSON.parse(raw);
38
+ if (parsed.version === 1) {
39
+ // Promote .tmp to main
40
+ try {
41
+ renameSync(tmpPath, stateFilePath);
42
+ }
43
+ catch { /* best effort */ }
44
+ return parsed;
45
+ }
46
+ }
47
+ catch {
48
+ // .tmp also corrupted — give up
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Save daemon state to disk using atomic write pattern.
55
+ * Writes to .tmp first, then renames to main file.
56
+ * If crash occurs mid-write, .tmp is partial but main file is intact.
57
+ */
58
+ export function saveArbState(state) {
59
+ ensureDir();
60
+ const tmpPath = stateFilePath + ".tmp";
61
+ const json = JSON.stringify(state, null, 2);
62
+ writeFileSync(tmpPath, json, { mode: 0o600 });
63
+ renameSync(tmpPath, stateFilePath);
64
+ }
65
+ /** Add a position to the persisted state. */
66
+ export function addPosition(pos) {
67
+ const state = loadArbState();
68
+ if (!state) {
69
+ throw new Error("No daemon state found. Initialize state before adding positions.");
70
+ }
71
+ // Avoid duplicates by symbol
72
+ state.positions = state.positions.filter(p => p.symbol !== pos.symbol);
73
+ state.positions.push(pos);
74
+ saveArbState(state);
75
+ }
76
+ /** Remove a position by symbol from the persisted state. */
77
+ export function removePosition(symbol) {
78
+ const state = loadArbState();
79
+ if (!state)
80
+ return;
81
+ state.positions = state.positions.filter(p => p.symbol !== symbol);
82
+ saveArbState(state);
83
+ }
84
+ /** Update a position by symbol with partial updates. */
85
+ export function updatePosition(symbol, updates) {
86
+ const state = loadArbState();
87
+ if (!state)
88
+ return;
89
+ const idx = state.positions.findIndex(p => p.symbol === symbol);
90
+ if (idx === -1)
91
+ return;
92
+ state.positions[idx] = { ...state.positions[idx], ...updates };
93
+ saveArbState(state);
94
+ }
95
+ /** Get all persisted positions. */
96
+ export function getPositions() {
97
+ const state = loadArbState();
98
+ if (!state)
99
+ return [];
100
+ return state.positions;
101
+ }
102
+ /** Create a default empty daemon state with the given config. */
103
+ export function createInitialState(config) {
104
+ return {
105
+ version: 1,
106
+ lastStartTime: new Date().toISOString(),
107
+ lastScanTime: new Date().toISOString(),
108
+ lastSuccessfulScanTime: new Date().toISOString(),
109
+ positions: [],
110
+ config,
111
+ };
112
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Arb utilities: settlement timing strategy, basis risk monitoring, and webhook notifications.
3
+ */
4
+ import type { ExchangeAdapter } from "../exchanges/interface.js";
5
+ export type SettleStrategy = "block" | "aggressive" | "off";
6
+ /**
7
+ * Get the most recent settlement time for an exchange.
8
+ * @returns Date of the most recent past settlement
9
+ */
10
+ export declare function getLastSettlement(exchange: string, now?: Date): Date;
11
+ /**
12
+ * Get minutes since the most recent settlement for an exchange.
13
+ */
14
+ export declare function getMinutesSinceSettlement(exchange: string, now?: Date): number;
15
+ /**
16
+ * Compute a score boost for aggressive settlement mode.
17
+ * Returns a multiplier > 1.0 if within the post-settlement window, else 1.0.
18
+ * The boost decays linearly from 1.5x (right after settlement) to 1.0x (at windowMinutes).
19
+ */
20
+ export declare function aggressiveSettleBoost(longExchange: string, shortExchange: string, windowMinutes?: number, now?: Date): number;
21
+ /**
22
+ * Estimate cumulative funding between now and the next settlement.
23
+ *
24
+ * Since all three exchanges (HL, PAC, LT) settle every hour, both sides
25
+ * accumulate funding at the same frequency. The function estimates
26
+ * cumulative funding over the given time horizon.
27
+ *
28
+ * @param hlHourlyRate - HL per-hour funding rate (raw, not %)
29
+ * @param pacHourlyRate - PAC per-hour funding rate (raw, not %)
30
+ * @param positionSize - position notional in USD
31
+ * @param hoursUntilSettlement - hours until next settlement
32
+ * @returns { hlCumulative, pacPayment, netFunding } in USD (positive = you receive)
33
+ */
34
+ export declare function estimateFundingUntilSettlement(hlHourlyRate: number, pacHourlyRate: number, positionSize: number, hoursUntilSettlement: number): {
35
+ hlCumulative: number;
36
+ pacPayment: number;
37
+ netFunding: number;
38
+ };
39
+ export interface BasisRisk {
40
+ symbol: string;
41
+ longExchange: string;
42
+ shortExchange: string;
43
+ longMarkPrice: number;
44
+ shortMarkPrice: number;
45
+ divergencePct: number;
46
+ warning: boolean;
47
+ }
48
+ /**
49
+ * Check basis risk (mark price divergence) for open arb positions.
50
+ * Fetches current mark prices from each exchange and computes divergence.
51
+ */
52
+ export declare function checkBasisRisk(positions: Array<{
53
+ symbol: string;
54
+ longExchange: string;
55
+ shortExchange: string;
56
+ }>, adapters: Map<string, ExchangeAdapter>, maxDivergencePct?: number): Promise<BasisRisk[]>;
57
+ /**
58
+ * Compute basis risk from pre-fetched prices (no async, for use in tests and monitor).
59
+ */
60
+ export declare function computeBasisRisk(longPrice: number, shortPrice: number, maxDivergencePct?: number): {
61
+ divergencePct: number;
62
+ warning: boolean;
63
+ };
64
+ export type ArbNotifyEvent = "entry" | "exit" | "reversal" | "margin" | "basis" | "heartbeat";
65
+ /**
66
+ * Format a notification message for an arb event.
67
+ */
68
+ export declare function formatNotifyMessage(event: ArbNotifyEvent, data: Record<string, unknown>): string;
69
+ /**
70
+ * Send a notification to a webhook URL (Discord, Telegram, or generic).
71
+ *
72
+ * @param webhookUrl - The webhook URL
73
+ * @param event - The event type
74
+ * @param data - Event data
75
+ * @param fetchFn - Optional fetch function for testing
76
+ */
77
+ export declare function sendNotification(webhookUrl: string, event: ArbNotifyEvent, data: Record<string, unknown>, fetchFn?: typeof fetch): Promise<void>;
78
+ /**
79
+ * Helper to send notification only if the event is in the allowed list.
80
+ */
81
+ export declare function notifyIfEnabled(webhookUrl: string | undefined, enabledEvents: ArbNotifyEvent[], event: ArbNotifyEvent, data: Record<string, unknown>, fetchFn?: typeof fetch): Promise<void>;
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Arb utilities: settlement timing strategy, basis risk monitoring, and webhook notifications.
3
+ */
4
+ // ── Settlement Timing Strategy ──
5
+ /** Settlement schedules per exchange (UTC hours when settlement occurs) */
6
+ const SETTLEMENT_SCHEDULES = {
7
+ hyperliquid: Array.from({ length: 24 }, (_, i) => i), // every hour
8
+ pacifica: Array.from({ length: 24 }, (_, i) => i), // every hour
9
+ lighter: Array.from({ length: 24 }, (_, i) => i), // every hour
10
+ };
11
+ /**
12
+ * Get the most recent settlement time for an exchange.
13
+ * @returns Date of the most recent past settlement
14
+ */
15
+ export function getLastSettlement(exchange, now = new Date()) {
16
+ const schedule = SETTLEMENT_SCHEDULES[exchange.toLowerCase()];
17
+ if (!schedule || schedule.length === 0) {
18
+ return getLastSettlement("pacifica", now);
19
+ }
20
+ const currentHour = now.getUTCHours();
21
+ const currentMinutes = now.getUTCMinutes();
22
+ const currentSeconds = now.getUTCSeconds();
23
+ // Find the most recent settlement hour at or before now
24
+ for (let i = schedule.length - 1; i >= 0; i--) {
25
+ const hour = schedule[i];
26
+ if (hour < currentHour || (hour === currentHour && (currentMinutes > 0 || currentSeconds > 0))) {
27
+ const last = new Date(now);
28
+ last.setUTCHours(hour, 0, 0, 0);
29
+ return last;
30
+ }
31
+ // Exactly at settlement time
32
+ if (hour === currentHour && currentMinutes === 0 && currentSeconds === 0) {
33
+ const last = new Date(now);
34
+ last.setUTCHours(hour, 0, 0, 0);
35
+ return last;
36
+ }
37
+ }
38
+ // Wrap to previous day's last settlement
39
+ const last = new Date(now);
40
+ last.setUTCDate(last.getUTCDate() - 1);
41
+ last.setUTCHours(schedule[schedule.length - 1], 0, 0, 0);
42
+ return last;
43
+ }
44
+ /**
45
+ * Get minutes since the most recent settlement for an exchange.
46
+ */
47
+ export function getMinutesSinceSettlement(exchange, now = new Date()) {
48
+ const lastSettle = getLastSettlement(exchange, now);
49
+ return (now.getTime() - lastSettle.getTime()) / (1000 * 60);
50
+ }
51
+ /**
52
+ * Compute a score boost for aggressive settlement mode.
53
+ * Returns a multiplier > 1.0 if within the post-settlement window, else 1.0.
54
+ * The boost decays linearly from 1.5x (right after settlement) to 1.0x (at windowMinutes).
55
+ */
56
+ export function aggressiveSettleBoost(longExchange, shortExchange, windowMinutes = 10, now = new Date()) {
57
+ const longMinutes = getMinutesSinceSettlement(longExchange, now);
58
+ const shortMinutes = getMinutesSinceSettlement(shortExchange, now);
59
+ // Use the minimum of the two — we want BOTH exchanges to have recently settled
60
+ const minMinutes = Math.min(longMinutes, shortMinutes);
61
+ if (minMinutes <= windowMinutes) {
62
+ // Linear decay from 1.5 to 1.0
63
+ const factor = 1.0 + 0.5 * (1 - minMinutes / windowMinutes);
64
+ return factor;
65
+ }
66
+ return 1.0;
67
+ }
68
+ /**
69
+ * Estimate cumulative funding between now and the next settlement.
70
+ *
71
+ * Since all three exchanges (HL, PAC, LT) settle every hour, both sides
72
+ * accumulate funding at the same frequency. The function estimates
73
+ * cumulative funding over the given time horizon.
74
+ *
75
+ * @param hlHourlyRate - HL per-hour funding rate (raw, not %)
76
+ * @param pacHourlyRate - PAC per-hour funding rate (raw, not %)
77
+ * @param positionSize - position notional in USD
78
+ * @param hoursUntilSettlement - hours until next settlement
79
+ * @returns { hlCumulative, pacPayment, netFunding } in USD (positive = you receive)
80
+ */
81
+ export function estimateFundingUntilSettlement(hlHourlyRate, pacHourlyRate, positionSize, hoursUntilSettlement) {
82
+ // Both exchanges settle every hour, so we accumulate over the same period
83
+ const hlCumulative = Math.abs(hlHourlyRate) * positionSize * hoursUntilSettlement;
84
+ // PAC also settles every hour, so cumulative over same period
85
+ const pacPayment = Math.abs(pacHourlyRate) * positionSize * hoursUntilSettlement;
86
+ // Net assumes: short high-rate exchange (receive), long low-rate (pay less)
87
+ // In a typical arb, we short the high-rate and long the low-rate
88
+ // The net is the difference we collect
89
+ const netFunding = hlCumulative - pacPayment;
90
+ return { hlCumulative, pacPayment, netFunding };
91
+ }
92
+ /**
93
+ * Check basis risk (mark price divergence) for open arb positions.
94
+ * Fetches current mark prices from each exchange and computes divergence.
95
+ */
96
+ export async function checkBasisRisk(positions, adapters, maxDivergencePct = 3) {
97
+ const results = [];
98
+ for (const pos of positions) {
99
+ const longAdapter = adapters.get(pos.longExchange);
100
+ const shortAdapter = adapters.get(pos.shortExchange);
101
+ if (!longAdapter || !shortAdapter)
102
+ continue;
103
+ try {
104
+ const [longMarkets, shortMarkets] = await Promise.all([
105
+ longAdapter.getMarkets(),
106
+ shortAdapter.getMarkets(),
107
+ ]);
108
+ const longMarket = longMarkets.find(m => m.symbol.replace("-PERP", "").toUpperCase() === pos.symbol.toUpperCase());
109
+ const shortMarket = shortMarkets.find(m => m.symbol.replace("-PERP", "").toUpperCase() === pos.symbol.toUpperCase());
110
+ if (!longMarket || !shortMarket)
111
+ continue;
112
+ const longPrice = Number(longMarket.markPrice);
113
+ const shortPrice = Number(shortMarket.markPrice);
114
+ if (longPrice <= 0 || shortPrice <= 0)
115
+ continue;
116
+ const avgPrice = (longPrice + shortPrice) / 2;
117
+ const divergencePct = Math.abs(longPrice - shortPrice) / avgPrice * 100;
118
+ results.push({
119
+ symbol: pos.symbol,
120
+ longExchange: pos.longExchange,
121
+ shortExchange: pos.shortExchange,
122
+ longMarkPrice: longPrice,
123
+ shortMarkPrice: shortPrice,
124
+ divergencePct,
125
+ warning: divergencePct >= maxDivergencePct,
126
+ });
127
+ }
128
+ catch {
129
+ // Skip positions where we can't fetch prices
130
+ }
131
+ }
132
+ return results;
133
+ }
134
+ /**
135
+ * Compute basis risk from pre-fetched prices (no async, for use in tests and monitor).
136
+ */
137
+ export function computeBasisRisk(longPrice, shortPrice, maxDivergencePct = 3) {
138
+ if (longPrice <= 0 || shortPrice <= 0) {
139
+ return { divergencePct: 0, warning: false };
140
+ }
141
+ const avgPrice = (longPrice + shortPrice) / 2;
142
+ const divergencePct = Math.abs(longPrice - shortPrice) / avgPrice * 100;
143
+ return { divergencePct, warning: divergencePct >= maxDivergencePct };
144
+ }
145
+ /**
146
+ * Format a notification message for an arb event.
147
+ */
148
+ export function formatNotifyMessage(event, data) {
149
+ switch (event) {
150
+ case "entry": {
151
+ const symbol = data.symbol ?? "???";
152
+ const longExch = data.longExchange ?? "?";
153
+ const shortExch = data.shortExchange ?? "?";
154
+ const size = data.size ?? "?";
155
+ const netSpread = typeof data.netSpread === "number" ? data.netSpread.toFixed(1) : "?";
156
+ return `Entered ${symbol} arb: Long ${longExch} / Short ${shortExch}, $${size}/leg, spread ${netSpread}% net`;
157
+ }
158
+ case "exit": {
159
+ const symbol = data.symbol ?? "???";
160
+ const pnl = typeof data.pnl === "number" ? (data.pnl >= 0 ? `+$${data.pnl.toFixed(2)}` : `-$${Math.abs(data.pnl).toFixed(2)}`) : "?";
161
+ const duration = data.duration ?? "?";
162
+ return `Closed ${symbol} arb: ${pnl} net, held ${duration}`;
163
+ }
164
+ case "reversal": {
165
+ const symbol = data.symbol ?? "???";
166
+ return `REVERSAL: ${symbol} spread reversed, emergency close triggered`;
167
+ }
168
+ case "margin": {
169
+ const exchange = data.exchange ?? "?";
170
+ const marginPct = typeof data.marginPct === "number" ? data.marginPct.toFixed(1) : "?";
171
+ const threshold = typeof data.threshold === "number" ? data.threshold.toFixed(1) : "?";
172
+ return `LOW MARGIN: ${exchange} margin at ${marginPct}%, below ${threshold}% threshold`;
173
+ }
174
+ case "basis": {
175
+ const symbol = data.symbol ?? "???";
176
+ const divergence = typeof data.divergencePct === "number" ? data.divergencePct.toFixed(1) : "?";
177
+ const longExch = data.longExchange ?? "?";
178
+ const shortExch = data.shortExchange ?? "?";
179
+ return `BASIS RISK: ${symbol} price divergence ${divergence}% between ${longExch}/${shortExch}`;
180
+ }
181
+ case "heartbeat": {
182
+ const lastScan = data.lastScanTime ?? "unknown";
183
+ const minutesAgo = typeof data.minutesAgo === "number" ? data.minutesAgo.toFixed(0) : "?";
184
+ return `HEARTBEAT: No successful scan in ${minutesAgo} minutes (last: ${lastScan})`;
185
+ }
186
+ default:
187
+ return `Arb event: ${event} - ${JSON.stringify(data)}`;
188
+ }
189
+ }
190
+ /**
191
+ * Detect if a URL is a Discord webhook, Telegram bot API, or generic webhook.
192
+ */
193
+ function detectWebhookType(url) {
194
+ if (url.includes("discord.com/api/webhooks") || url.includes("discordapp.com/api/webhooks")) {
195
+ return "discord";
196
+ }
197
+ if (url.includes("api.telegram.org/bot")) {
198
+ return "telegram";
199
+ }
200
+ return "generic";
201
+ }
202
+ /**
203
+ * Extract Telegram chat_id from a URL like:
204
+ * https://api.telegram.org/bot<TOKEN>/sendMessage?chat_id=<CHAT_ID>
205
+ * or from the data parameter if the URL just has the bot token.
206
+ */
207
+ function parseTelegramUrl(url) {
208
+ const urlObj = new URL(url);
209
+ const chatId = urlObj.searchParams.get("chat_id") || "";
210
+ // Remove chat_id from URL params to build clean API URL
211
+ urlObj.searchParams.delete("chat_id");
212
+ // Ensure path ends with /sendMessage
213
+ if (!urlObj.pathname.endsWith("/sendMessage")) {
214
+ urlObj.pathname = urlObj.pathname.replace(/\/$/, "") + "/sendMessage";
215
+ }
216
+ return { apiUrl: urlObj.toString(), chatId };
217
+ }
218
+ /**
219
+ * Send a notification to a webhook URL (Discord, Telegram, or generic).
220
+ *
221
+ * @param webhookUrl - The webhook URL
222
+ * @param event - The event type
223
+ * @param data - Event data
224
+ * @param fetchFn - Optional fetch function for testing
225
+ */
226
+ export async function sendNotification(webhookUrl, event, data, fetchFn = globalThis.fetch) {
227
+ const message = formatNotifyMessage(event, data);
228
+ const type = detectWebhookType(webhookUrl);
229
+ try {
230
+ if (type === "discord") {
231
+ await fetchFn(webhookUrl, {
232
+ method: "POST",
233
+ headers: { "Content-Type": "application/json" },
234
+ body: JSON.stringify({ content: message }),
235
+ });
236
+ }
237
+ else if (type === "telegram") {
238
+ const { apiUrl, chatId } = parseTelegramUrl(webhookUrl);
239
+ await fetchFn(apiUrl, {
240
+ method: "POST",
241
+ headers: { "Content-Type": "application/json" },
242
+ body: JSON.stringify({ chat_id: chatId, text: message }),
243
+ });
244
+ }
245
+ else {
246
+ // Generic webhook: POST with JSON body
247
+ await fetchFn(webhookUrl, {
248
+ method: "POST",
249
+ headers: { "Content-Type": "application/json" },
250
+ body: JSON.stringify({ event, message, data }),
251
+ });
252
+ }
253
+ }
254
+ catch {
255
+ // Silently fail — notifications should never crash the bot
256
+ }
257
+ }
258
+ /**
259
+ * Helper to send notification only if the event is in the allowed list.
260
+ */
261
+ export async function notifyIfEnabled(webhookUrl, enabledEvents, event, data, fetchFn) {
262
+ if (!webhookUrl)
263
+ return;
264
+ if (enabledEvents.length > 0 && !enabledEvents.includes(event))
265
+ return;
266
+ await sendNotification(webhookUrl, event, data, fetchFn);
267
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/arb/history-stats.ts.
4
+ */
5
+ export * from "./arb/history-stats.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/arb/history-stats.ts.
4
+ */
5
+ export * from "./arb/history-stats.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/arb/sizing.ts.
4
+ */
5
+ export * from "./arb/sizing.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/arb/sizing.ts.
4
+ */
5
+ export * from "./arb/sizing.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/arb/state.ts.
4
+ */
5
+ export * from "./arb/state.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/arb/state.ts.
4
+ */
5
+ export * from "./arb/state.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/arb/utils.ts.
4
+ */
5
+ export * from "./arb/utils.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel re-export wrapper for backward compatibility.
3
+ * Actual implementation moved to src/arb/utils.ts.
4
+ */
5
+ export * from "./arb/utils.js";