perp-cli 0.12.17 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +247 -27
  2. package/dist/_stderr-filter.js +0 -1
  3. package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
  4. package/dist/agent-wallet/lighter-keystore.js +92 -10
  5. package/dist/agent-wallet/passphrase.d.ts +1 -1
  6. package/dist/agent-wallet/passphrase.js +16 -3
  7. package/dist/agent-wallet/store.d.ts +37 -4
  8. package/dist/agent-wallet/store.js +132 -0
  9. package/dist/agent-wallet/venue-keystore.d.ts +14 -0
  10. package/dist/agent-wallet/venue-keystore.js +121 -0
  11. package/dist/api/public/hyperliquid.js +4 -4
  12. package/dist/api/public/lighter.js +5 -5
  13. package/dist/api/public/pacifica.d.ts +0 -1
  14. package/dist/api/public/pacifica.js +4 -13
  15. package/dist/api/public/urls.d.ts +9 -1
  16. package/dist/api/public/urls.js +35 -13
  17. package/dist/arb/file-lock.d.ts +11 -0
  18. package/dist/arb/file-lock.js +74 -0
  19. package/dist/arb/index.d.ts +2 -2
  20. package/dist/arb/index.js +2 -2
  21. package/dist/arb/pending-entry.d.ts +32 -0
  22. package/dist/arb/pending-entry.js +77 -0
  23. package/dist/arb/sizing.d.ts +6 -13
  24. package/dist/arb/sizing.js +12 -51
  25. package/dist/arb/state.d.ts +45 -12
  26. package/dist/arb/state.js +214 -42
  27. package/dist/arb/utils.d.ts +20 -6
  28. package/dist/arb/utils.js +43 -39
  29. package/dist/bot/conditions.d.ts +4 -4
  30. package/dist/bot/conditions.js +11 -6
  31. package/dist/bot/config.d.ts +17 -1
  32. package/dist/bot/config.js +48 -12
  33. package/dist/bot/engine.d.ts +61 -2
  34. package/dist/bot/engine.js +610 -144
  35. package/dist/bot/fill-size.d.ts +7 -0
  36. package/dist/bot/fill-size.js +18 -0
  37. package/dist/bot/index.d.ts +1 -1
  38. package/dist/bot/index.js +1 -1
  39. package/dist/bot/perp-order.d.ts +99 -0
  40. package/dist/bot/perp-order.js +299 -0
  41. package/dist/bot/presets.d.ts +7 -0
  42. package/dist/bot/presets.js +17 -0
  43. package/dist/bot/quarantine.d.ts +75 -0
  44. package/dist/bot/quarantine.js +190 -0
  45. package/dist/bot/quote-rules.d.ts +23 -0
  46. package/dist/bot/quote-rules.js +69 -0
  47. package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
  48. package/dist/bot/strategies/avellaneda-mm.js +72 -70
  49. package/dist/bot/strategies/basis-arb.js +2 -0
  50. package/dist/bot/strategies/dca-strategy.d.ts +2 -0
  51. package/dist/bot/strategies/dca-strategy.js +5 -0
  52. package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
  53. package/dist/bot/strategies/funding-arb-strategy.js +571 -380
  54. package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
  55. package/dist/bot/strategies/funding-arb-utils.js +159 -195
  56. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  57. package/dist/bot/strategies/funding-arb-v2.js +258 -47
  58. package/dist/bot/strategies/funding-auto.js +215 -194
  59. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
  60. package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
  61. package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
  62. package/dist/bot/strategies/spot-perp-executor.js +319 -120
  63. package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
  64. package/dist/bot/strategies/spot-perp-state.js +70 -0
  65. package/dist/bot/strategy-types.d.ts +6 -0
  66. package/dist/bot/tui/BotDashboard.d.ts +7 -2
  67. package/dist/bot/tui/BotDashboard.js +6 -3
  68. package/dist/bridge-engine.d.ts +120 -19
  69. package/dist/bridge-engine.js +710 -422
  70. package/dist/bridge-signers.d.ts +54 -0
  71. package/dist/bridge-signers.js +161 -0
  72. package/dist/cache.d.ts +4 -1
  73. package/dist/cache.js +11 -0
  74. package/dist/cli-flags.d.ts +50 -0
  75. package/dist/cli-flags.js +104 -0
  76. package/dist/cli-program.d.ts +35 -0
  77. package/dist/cli-program.js +286 -0
  78. package/dist/cli-spec.js +1 -1
  79. package/dist/client-id-tracker.js +11 -2
  80. package/dist/commands/account.d.ts +7 -1
  81. package/dist/commands/account.js +70 -92
  82. package/dist/commands/agent-import.d.ts +114 -0
  83. package/dist/commands/agent-import.js +794 -0
  84. package/dist/commands/agent.d.ts +232 -2
  85. package/dist/commands/agent.js +1858 -955
  86. package/dist/commands/alerts.d.ts +1 -0
  87. package/dist/commands/alerts.js +31 -28
  88. package/dist/commands/arb/index.d.ts +60 -1
  89. package/dist/commands/arb/index.js +381 -384
  90. package/dist/commands/arb-auto.d.ts +132 -4
  91. package/dist/commands/arb-auto.js +1474 -967
  92. package/dist/commands/arb.d.ts +3 -1
  93. package/dist/commands/arb.js +62 -30
  94. package/dist/commands/bot.js +127 -47
  95. package/dist/commands/bridge.d.ts +2 -1
  96. package/dist/commands/bridge.js +339 -192
  97. package/dist/commands/builder.d.ts +55 -0
  98. package/dist/commands/builder.js +387 -0
  99. package/dist/commands/funds-onchain.d.ts +32 -0
  100. package/dist/commands/funds-onchain.js +286 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +939 -245
  103. package/dist/commands/history.js +72 -45
  104. package/dist/commands/init.d.ts +107 -15
  105. package/dist/commands/init.js +1116 -249
  106. package/dist/commands/jobs.d.ts +2 -1
  107. package/dist/commands/jobs.js +77 -15
  108. package/dist/commands/manage.js +136 -91
  109. package/dist/commands/market.d.ts +4 -1
  110. package/dist/commands/market.js +35 -32
  111. package/dist/commands/outcome.d.ts +10 -0
  112. package/dist/commands/outcome.js +396 -0
  113. package/dist/commands/plan.js +52 -12
  114. package/dist/commands/portfolio.d.ts +1 -0
  115. package/dist/commands/portfolio.js +60 -72
  116. package/dist/commands/rebalance.d.ts +2 -1
  117. package/dist/commands/rebalance.js +33 -43
  118. package/dist/commands/risk.js +46 -19
  119. package/dist/commands/settings.d.ts +1 -0
  120. package/dist/commands/settings.js +246 -51
  121. package/dist/commands/trade.d.ts +50 -0
  122. package/dist/commands/trade.js +1943 -695
  123. package/dist/commands/wallet.d.ts +30 -1
  124. package/dist/commands/wallet.js +316 -337
  125. package/dist/config.d.ts +60 -9
  126. package/dist/config.js +252 -82
  127. package/dist/constants.d.ts +29 -4
  128. package/dist/constants.js +62 -20
  129. package/dist/dashboard/server.d.ts +2 -0
  130. package/dist/dashboard/server.js +9 -6
  131. package/dist/dashboard/ui.js +6 -4
  132. package/dist/dashboard/ws-feeds.js +20 -8
  133. package/dist/dex-asset-map.js +5 -3
  134. package/dist/errors.d.ts +122 -0
  135. package/dist/errors.js +194 -0
  136. package/dist/event-stream.js +75 -19
  137. package/dist/evm-master-tx.d.ts +59 -0
  138. package/dist/evm-master-tx.js +154 -0
  139. package/dist/exchanges/adapter-network.d.ts +10 -0
  140. package/dist/exchanges/adapter-network.js +10 -0
  141. package/dist/exchanges/arcus/adapter.d.ts +196 -0
  142. package/dist/exchanges/arcus/adapter.js +1092 -0
  143. package/dist/exchanges/arcus/agent.d.ts +55 -0
  144. package/dist/exchanges/arcus/agent.js +188 -0
  145. package/dist/exchanges/arcus/client.d.ts +63 -0
  146. package/dist/exchanges/arcus/client.js +285 -0
  147. package/dist/exchanges/arcus/public.d.ts +65 -0
  148. package/dist/exchanges/arcus/public.js +138 -0
  149. package/dist/exchanges/arcus/signing.d.ts +176 -0
  150. package/dist/exchanges/arcus/signing.js +288 -0
  151. package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
  152. package/dist/exchanges/arcus/spot-chain.js +241 -0
  153. package/dist/exchanges/arcus/spot-router.d.ts +177 -0
  154. package/dist/exchanges/arcus/spot-router.js +183 -0
  155. package/dist/exchanges/arcus/spot.d.ts +109 -0
  156. package/dist/exchanges/arcus/spot.js +645 -0
  157. package/dist/exchanges/aster-funds.d.ts +127 -0
  158. package/dist/exchanges/aster-funds.js +252 -0
  159. package/dist/exchanges/aster-nonce.d.ts +2 -0
  160. package/dist/exchanges/aster-nonce.js +107 -0
  161. package/dist/exchanges/aster-spot.d.ts +117 -0
  162. package/dist/exchanges/aster-spot.js +686 -0
  163. package/dist/exchanges/aster-typed-data.d.ts +133 -7
  164. package/dist/exchanges/aster-typed-data.js +121 -7
  165. package/dist/exchanges/aster.d.ts +219 -6
  166. package/dist/exchanges/aster.js +1023 -153
  167. package/dist/exchanges/builder-fee.d.ts +254 -0
  168. package/dist/exchanges/builder-fee.js +421 -0
  169. package/dist/exchanges/canonical-symbols.d.ts +32 -0
  170. package/dist/exchanges/canonical-symbols.js +96 -0
  171. package/dist/exchanges/capabilities.d.ts +7 -1
  172. package/dist/exchanges/capabilities.js +7 -0
  173. package/dist/exchanges/hl-signing.d.ts +165 -0
  174. package/dist/exchanges/hl-signing.js +158 -0
  175. package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
  176. package/dist/exchanges/hyperliquid-outcome.js +646 -0
  177. package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
  178. package/dist/exchanges/hyperliquid-spot.js +308 -117
  179. package/dist/exchanges/hyperliquid.d.ts +462 -57
  180. package/dist/exchanges/hyperliquid.js +1575 -449
  181. package/dist/exchanges/index.d.ts +6 -0
  182. package/dist/exchanges/index.js +4 -0
  183. package/dist/exchanges/interface.d.ts +63 -10
  184. package/dist/exchanges/interface.js +18 -3
  185. package/dist/exchanges/ioc-outcome.d.ts +44 -0
  186. package/dist/exchanges/ioc-outcome.js +57 -0
  187. package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
  188. package/dist/exchanges/legacy-trading-v2.js +940 -0
  189. package/dist/exchanges/lighter-public.d.ts +43 -0
  190. package/dist/exchanges/lighter-public.js +276 -0
  191. package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
  192. package/dist/exchanges/lighter-rh-deposit.js +185 -0
  193. package/dist/exchanges/lighter-signer.d.ts +123 -0
  194. package/dist/exchanges/lighter-signer.js +215 -0
  195. package/dist/exchanges/lighter-spot.d.ts +91 -13
  196. package/dist/exchanges/lighter-spot.js +595 -233
  197. package/dist/exchanges/lighter.d.ts +611 -39
  198. package/dist/exchanges/lighter.js +1890 -649
  199. package/dist/exchanges/ondo/adapter.d.ts +166 -0
  200. package/dist/exchanges/ondo/adapter.js +835 -0
  201. package/dist/exchanges/ondo/agent.d.ts +31 -0
  202. package/dist/exchanges/ondo/agent.js +101 -0
  203. package/dist/exchanges/ondo/client.d.ts +60 -0
  204. package/dist/exchanges/ondo/client.js +297 -0
  205. package/dist/exchanges/ondo/funds.d.ts +122 -0
  206. package/dist/exchanges/ondo/funds.js +290 -0
  207. package/dist/exchanges/ondo/public.d.ts +95 -0
  208. package/dist/exchanges/ondo/public.js +214 -0
  209. package/dist/exchanges/ondo/spot.d.ts +127 -0
  210. package/dist/exchanges/ondo/spot.js +392 -0
  211. package/dist/exchanges/outcome-interface.d.ts +140 -0
  212. package/dist/exchanges/outcome-interface.js +16 -0
  213. package/dist/exchanges/pacifica-spot.d.ts +83 -0
  214. package/dist/exchanges/pacifica-spot.js +324 -0
  215. package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
  216. package/dist/exchanges/pacifica-typed-data.js +57 -9
  217. package/dist/exchanges/pacifica.d.ts +288 -4
  218. package/dist/exchanges/pacifica.js +1013 -76
  219. package/dist/exchanges/precision.d.ts +47 -0
  220. package/dist/exchanges/precision.js +209 -0
  221. package/dist/exchanges/qfex/adapter.d.ts +341 -0
  222. package/dist/exchanges/qfex/adapter.js +1383 -0
  223. package/dist/exchanges/qfex/agent.d.ts +58 -0
  224. package/dist/exchanges/qfex/agent.js +188 -0
  225. package/dist/exchanges/qfex/client.d.ts +54 -0
  226. package/dist/exchanges/qfex/client.js +138 -0
  227. package/dist/exchanges/qfex/errors.d.ts +70 -0
  228. package/dist/exchanges/qfex/errors.js +236 -0
  229. package/dist/exchanges/qfex/public.d.ts +122 -0
  230. package/dist/exchanges/qfex/public.js +268 -0
  231. package/dist/exchanges/qfex/signing.d.ts +160 -0
  232. package/dist/exchanges/qfex/signing.js +283 -0
  233. package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
  234. package/dist/exchanges/qfex/ws-transport.js +798 -0
  235. package/dist/exchanges/rate-limiter.d.ts +12 -0
  236. package/dist/exchanges/rate-limiter.js +64 -0
  237. package/dist/exchanges/registry.d.ts +14 -2
  238. package/dist/exchanges/registry.js +20 -32
  239. package/dist/exchanges/slippage.d.ts +37 -0
  240. package/dist/exchanges/slippage.js +70 -0
  241. package/dist/exchanges/spot-factory.d.ts +166 -0
  242. package/dist/exchanges/spot-factory.js +431 -0
  243. package/dist/exchanges/spot-interface.d.ts +98 -4
  244. package/dist/exchanges/spot-interface.js +27 -1
  245. package/dist/exchanges/spot-result.d.ts +79 -0
  246. package/dist/exchanges/spot-result.js +273 -0
  247. package/dist/exchanges/trading-v2.d.ts +117 -0
  248. package/dist/exchanges/trading-v2.js +301 -0
  249. package/dist/exchanges/venue-types.d.ts +231 -0
  250. package/dist/exchanges/venue-types.js +87 -0
  251. package/dist/exchanges/venue.d.ts +211 -0
  252. package/dist/exchanges/venue.js +132 -0
  253. package/dist/exchanges/venues/arcus.d.ts +8 -0
  254. package/dist/exchanges/venues/arcus.js +109 -0
  255. package/dist/exchanges/venues/aster.d.ts +49 -0
  256. package/dist/exchanges/venues/aster.js +176 -0
  257. package/dist/exchanges/venues/create-helpers.d.ts +18 -0
  258. package/dist/exchanges/venues/create-helpers.js +199 -0
  259. package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
  260. package/dist/exchanges/venues/hyperliquid.js +126 -0
  261. package/dist/exchanges/venues/index.d.ts +16 -0
  262. package/dist/exchanges/venues/index.js +26 -0
  263. package/dist/exchanges/venues/lighter-family.d.ts +92 -0
  264. package/dist/exchanges/venues/lighter-family.js +166 -0
  265. package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
  266. package/dist/exchanges/venues/lighter-rh.js +58 -0
  267. package/dist/exchanges/venues/lighter.d.ts +6 -0
  268. package/dist/exchanges/venues/lighter.js +55 -0
  269. package/dist/exchanges/venues/ondo.d.ts +34 -0
  270. package/dist/exchanges/venues/ondo.js +140 -0
  271. package/dist/exchanges/venues/pacifica.d.ts +8 -0
  272. package/dist/exchanges/venues/pacifica.js +95 -0
  273. package/dist/exchanges/venues/qfex.d.ts +28 -0
  274. package/dist/exchanges/venues/qfex.js +132 -0
  275. package/dist/execution-log.js +10 -2
  276. package/dist/funding/legacy-public.d.ts +31 -0
  277. package/dist/funding/legacy-public.js +369 -0
  278. package/dist/funding/normalize.d.ts +28 -16
  279. package/dist/funding/normalize.js +61 -29
  280. package/dist/funding/rates.d.ts +46 -11
  281. package/dist/funding/rates.js +228 -277
  282. package/dist/guardrail/perp-guardrail.js +0 -0
  283. package/dist/index.js +156 -684
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +7 -2
  287. package/dist/landing.js +19 -3
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +820 -402
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/pacifica/client.d.ts +41 -10
  293. package/dist/pacifica/client.js +167 -63
  294. package/dist/pacifica/constants.d.ts +0 -1
  295. package/dist/pacifica/constants.js +0 -5
  296. package/dist/pacifica/deposit.d.ts +30 -3
  297. package/dist/pacifica/deposit.js +119 -2
  298. package/dist/pacifica/faucet.d.ts +29 -0
  299. package/dist/pacifica/faucet.js +100 -0
  300. package/dist/pacifica/sign-tx.d.ts +15 -0
  301. package/dist/pacifica/sign-tx.js +41 -0
  302. package/dist/pacifica/types/account.d.ts +28 -4
  303. package/dist/pacifica/types/lake.d.ts +20 -5
  304. package/dist/pacifica/types/lake.js +4 -0
  305. package/dist/pacifica/types/market.d.ts +5 -0
  306. package/dist/pacifica/types/order.d.ts +4 -1
  307. package/dist/pacifica/types/position.d.ts +9 -1
  308. package/dist/plan-executor.d.ts +10 -5
  309. package/dist/plan-executor.js +165 -54
  310. package/dist/position-history.js +13 -0
  311. package/dist/rebalance.d.ts +26 -2
  312. package/dist/rebalance.js +86 -7
  313. package/dist/relay-swap.d.ts +232 -0
  314. package/dist/relay-swap.js +630 -0
  315. package/dist/risk.js +5 -1
  316. package/dist/secret-env.d.ts +15 -0
  317. package/dist/secret-env.js +36 -0
  318. package/dist/settings.d.ts +169 -6
  319. package/dist/settings.js +219 -38
  320. package/dist/signer/ows-evm.d.ts +13 -0
  321. package/dist/signer/ows-evm.js +22 -0
  322. package/dist/smart-order.d.ts +9 -0
  323. package/dist/smart-order.js +53 -8
  324. package/dist/strategies/dca.d.ts +6 -2
  325. package/dist/strategies/dca.js +66 -26
  326. package/dist/strategies/funding-arb.d.ts +11 -1
  327. package/dist/strategies/funding-arb.js +202 -89
  328. package/dist/strategies/grid.d.ts +31 -0
  329. package/dist/strategies/grid.js +89 -11
  330. package/dist/strategies/pending-stop.d.ts +8 -0
  331. package/dist/strategies/pending-stop.js +12 -0
  332. package/dist/strategies/split-order.d.ts +45 -17
  333. package/dist/strategies/split-order.js +157 -91
  334. package/dist/strategies/trailing-stop.d.ts +2 -1
  335. package/dist/strategies/trailing-stop.js +52 -6
  336. package/dist/strategies/twap.d.ts +6 -1
  337. package/dist/strategies/twap.js +39 -11
  338. package/dist/trade-validator.d.ts +22 -0
  339. package/dist/trade-validator.js +119 -15
  340. package/dist/utils/numeric.d.ts +46 -0
  341. package/dist/utils/numeric.js +79 -0
  342. package/dist/utils.d.ts +19 -0
  343. package/dist/utils.js +96 -19
  344. package/dist/withdraw-gate.d.ts +49 -0
  345. package/dist/withdraw-gate.js +131 -0
  346. package/package.json +15 -6
  347. package/skills/perp-cli/SKILL.md +142 -55
  348. package/skills/perp-cli/references/agent-operations.md +110 -56
  349. package/skills/perp-cli/references/commands.md +266 -71
  350. package/skills/perp-cli/references/strategies.md +34 -31
  351. package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
  352. package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
  353. package/skills/perp-cli/scripts/preflight.sh +21 -14
  354. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  355. package/skills/perp-cli/scripts/validate-arb.sh +31 -15
  356. package/vendor/lighter-signer/SHA256SUMS +2 -0
  357. package/vendor/lighter-signer/VERSION +8 -0
  358. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  359. package/vendor/lighter-signer/wasm_exec.js +575 -0
  360. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  361. package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Public market data for the legacy venues without a native `getVenueMarkets`
3
+ * (pacifica, hyperliquid core, aster) — design §10.6. Lighter uses
4
+ * exchanges/lighter-public.ts.
5
+ *
6
+ * Network-aware: the REST base comes from `legacyPublicBaseUrl(venue, net)`,
7
+ * never from the mutable `*_API_URL` globals. A network without a known public
8
+ * API throws NOT_IMPLEMENTED.
9
+ *
10
+ * FUNDING BASIS / SETTLEMENT
11
+ * pacifica, hyperliquid: the venue definition (fundingBasisOf / settlementIntervalOf).
12
+ * aster: per symbol from GET /fapi/v3/fundingInfo `fundingIntervalHours`
13
+ * (live 2026-09-30: 1h/2h/4h/8h mixed). One quoted rate covers one interval,
14
+ * so basis hours == settlement hours. A symbol missing from fundingInfo is
15
+ * excluded with a warning (its period would be a guess).
16
+ *
17
+ * ASSET CLASS
18
+ * hyperliquid core: crypto (HIP-3 dexes are not served here).
19
+ * pacifica: crypto-centric; PACIFICA_NON_CRYPTO lists every non-crypto perp
20
+ * listed on 2026-09-30.
21
+ * aster: exchangeInfo `underlyingSubType` (STOCK / ETF / Commodities / pre-launch).
22
+ * AMBIGUOUS tickers are excluded with a warning so they cannot produce a false
23
+ * cross-venue match (SPX = the SPX6900 token, whose ticker equals the S&P 500
24
+ * canonical key).
25
+ *
26
+ * Fields a venue does not publish on this path are null (HL has no fixed tick
27
+ * size; Aster leverage brackets need a signed call; Aster OI is per-symbol only).
28
+ */
29
+ import { HL_MIN_ORDER_VALUE_USD } from "../constants.js";
30
+ import { PerpError } from "../errors.js";
31
+ import { venueRateString } from "../utils/numeric.js";
32
+ import { assertOk } from "../api/public/_http.js";
33
+ import { legacyPublicBaseUrl } from "../api/public/urls.js";
34
+ import { canonicalFor } from "../exchanges/canonical-symbols.js";
35
+ import { fundingBasisOf, settlementIntervalOf } from "../exchanges/venue.js";
36
+ // ── Classification ──
37
+ const AMBIGUOUS = new Set(["SPX"]);
38
+ /** Pacifica non-crypto perps (live /info 2026-09-30). BP / CHIP: ticker collides with a listed equity/ETF. */
39
+ const PACIFICA_NON_CRYPTO = {
40
+ NVDA: "equity", TSLA: "equity", GOOGL: "equity", PLTR: "equity", HOOD: "equity", CRCL: "equity",
41
+ SPCX: "equity", SKHYNIX: "equity", SAMSUNG: "equity", MSTR: "equity", MU: "equity", SNDK: "equity",
42
+ META: "equity",
43
+ DRAM: "etf", URNM: "etf",
44
+ XAU: "commodity", XAG: "commodity", CL: "commodity", COPPER: "commodity", NATGAS: "commodity",
45
+ PLATINUM: "commodity",
46
+ USDJPY: "fx", EURUSD: "fx",
47
+ SP500: "index",
48
+ };
49
+ const PACIFICA_AMBIGUOUS = new Set(["BP", "CHIP"]);
50
+ function classifyPacifica(symbol) {
51
+ const up = symbol.toUpperCase();
52
+ if (AMBIGUOUS.has(up) || PACIFICA_AMBIGUOUS.has(up))
53
+ return null;
54
+ return PACIFICA_NON_CRYPTO[up] ?? "crypto"; // crypto-centric venue: non-crypto is listed explicitly
55
+ }
56
+ function classifyHyperliquid(symbol) {
57
+ return AMBIGUOUS.has(symbol.toUpperCase()) ? null : "crypto";
58
+ }
59
+ function classifyAster(symbol, subTypes) {
60
+ if (AMBIGUOUS.has(symbol.toUpperCase()))
61
+ return null;
62
+ if (subTypes.includes("pre-launch"))
63
+ return "preIpo";
64
+ if (subTypes.includes("ETF"))
65
+ return "etf";
66
+ if (subTypes.includes("STOCK"))
67
+ return "equity";
68
+ if (subTypes.includes("Commodities"))
69
+ return "commodity";
70
+ return "crypto";
71
+ }
72
+ function warnExcluded(venue, what, symbols) {
73
+ if (symbols.length > 0) {
74
+ console.warn(`[${venue}] excluded ${symbols.length} ${what}: ${symbols.join(", ")}`);
75
+ }
76
+ }
77
+ // ── HTTP ──
78
+ async function getJson(venue, url, label, init) {
79
+ const res = await fetch(url, { ...init, signal: AbortSignal.timeout(15_000) });
80
+ await assertOk(res, `${venue} ${label}`);
81
+ const text = await res.text();
82
+ try {
83
+ return JSON.parse(text);
84
+ }
85
+ catch {
86
+ throw new PerpError("EXCHANGE_ERROR", `${venue} ${label}: invalid JSON`, { body: text.slice(0, 500) });
87
+ }
88
+ }
89
+ function arrayField(venue, label, v) {
90
+ if (!Array.isArray(v))
91
+ throw new PerpError("EXCHANGE_ERROR", `${venue} ${label}: expected an array`);
92
+ return v;
93
+ }
94
+ function numStr(v) {
95
+ if (typeof v === "number" && Number.isFinite(v))
96
+ return String(v);
97
+ if (typeof v === "string" && v.length > 0 && Number.isFinite(Number(v)))
98
+ return v;
99
+ return null;
100
+ }
101
+ /** A funding rate field: a JSON number without float noise (venueRateString), a numeric string verbatim; else null. */
102
+ function rateStr(v) {
103
+ if (typeof v === "number" && Number.isFinite(v))
104
+ return venueRateString(v);
105
+ return numStr(v);
106
+ }
107
+ function positiveNumStr(v) {
108
+ const s = numStr(v);
109
+ return s !== null && Number(s) > 0 ? s : null;
110
+ }
111
+ function stepFromDecimals(d) {
112
+ return d === 0 ? "1" : `0.${"0".repeat(d - 1)}1`;
113
+ }
114
+ // ── Pacifica ──
115
+ async function pacificaRows(net) {
116
+ const base = legacyPublicBaseUrl("pacifica", net);
117
+ const [infoJson, pricesJson] = await Promise.all([
118
+ getJson("pacifica", `${base}/info`, "info"),
119
+ getJson("pacifica", `${base}/info/prices`, "info/prices"),
120
+ ]);
121
+ const info = arrayField("pacifica", "info", infoJson.data);
122
+ const prices = arrayField("pacifica", "info/prices", pricesJson.data);
123
+ const priceBySymbol = new Map(prices.map(p => [String(p.symbol), p]));
124
+ const basis = fundingBasisOf("pacifica");
125
+ const settlement = settlementIntervalOf("pacifica");
126
+ const out = [];
127
+ const unclassified = [];
128
+ const unpriced = [];
129
+ for (const m of info) {
130
+ // /info lists spot markets too (e.g. SOL-USDC). A row without the field cannot be classified.
131
+ if (typeof m.instrument_type !== "string") {
132
+ throw new PerpError("EXCHANGE_ERROR", `Pacifica /info row ${String(m.symbol)} has no instrument_type`, { exchange: "pacifica" });
133
+ }
134
+ if (m.instrument_type === "spot")
135
+ continue;
136
+ const symbol = String(m.symbol ?? "");
137
+ const assetClass = classifyPacifica(symbol);
138
+ if (assetClass === null) {
139
+ unclassified.push(symbol);
140
+ continue;
141
+ }
142
+ const p = priceBySymbol.get(symbol);
143
+ const mark = p ? positiveNumStr(p.mark) : null;
144
+ if (!p || mark === null) {
145
+ unpriced.push(symbol);
146
+ continue;
147
+ }
148
+ const maxLeverage = Number(m.max_leverage);
149
+ out.push({
150
+ market: {
151
+ venue: "pacifica",
152
+ venueSymbol: symbol,
153
+ symbol,
154
+ canonical: canonicalFor("pacifica", symbol, assetClass),
155
+ assetClass,
156
+ settle: "USDC",
157
+ quoteAsset: null,
158
+ markPrice: mark,
159
+ indexPrice: positiveNumStr(p.oracle),
160
+ fundingRate: rateStr(p.next_funding),
161
+ fundingRateBasis: basis,
162
+ settlementIntervalHours: settlement,
163
+ openInterest: numStr(p.open_interest),
164
+ volume24h: numStr(p.volume_24h),
165
+ tickSize: positiveNumStr(m.tick_size),
166
+ stepSize: positiveNumStr(m.lot_size),
167
+ minSize: null,
168
+ minNotional: positiveNumStr(m.min_order_size),
169
+ maxLeverage: Number.isFinite(maxLeverage) && maxLeverage > 0 ? maxLeverage : null,
170
+ marginTiers: null,
171
+ session: null,
172
+ tradable: true,
173
+ },
174
+ nextFundingTime: null, // Pacifica publishes no next-funding timestamp
175
+ });
176
+ }
177
+ warnExcluded("pacifica", "unclassified/ambiguous market(s)", unclassified);
178
+ warnExcluded("pacifica", "market(s) without a mark price", unpriced);
179
+ return out;
180
+ }
181
+ // ── Hyperliquid core ──
182
+ async function hyperliquidRows(net) {
183
+ const base = legacyPublicBaseUrl("hyperliquid", net);
184
+ const json = await getJson("hyperliquid", `${base}/info`, "info metaAndAssetCtxs", {
185
+ method: "POST",
186
+ headers: { "Content-Type": "application/json" },
187
+ body: JSON.stringify({ type: "metaAndAssetCtxs" }),
188
+ });
189
+ if (!Array.isArray(json) || json.length < 2) {
190
+ throw new PerpError("EXCHANGE_ERROR", "hyperliquid metaAndAssetCtxs: expected [meta, assetCtxs]");
191
+ }
192
+ const universe = arrayField("hyperliquid", "meta.universe", json[0].universe);
193
+ const ctxs = arrayField("hyperliquid", "assetCtxs", json[1]);
194
+ if (ctxs.length !== universe.length) {
195
+ throw new PerpError("EXCHANGE_ERROR", `hyperliquid metaAndAssetCtxs: ${universe.length} assets vs ${ctxs.length} contexts`);
196
+ }
197
+ const basis = fundingBasisOf("hyperliquid");
198
+ const settlement = settlementIntervalOf("hyperliquid");
199
+ const out = [];
200
+ const unclassified = [];
201
+ const unpriced = [];
202
+ universe.forEach((a, i) => {
203
+ const ctx = ctxs[i];
204
+ const symbol = String(a.name ?? "");
205
+ const assetClass = classifyHyperliquid(symbol);
206
+ if (assetClass === null) {
207
+ unclassified.push(symbol);
208
+ return;
209
+ }
210
+ const mark = positiveNumStr(ctx.markPx);
211
+ if (mark === null) {
212
+ unpriced.push(symbol);
213
+ return;
214
+ }
215
+ const szDecimals = Number(a.szDecimals);
216
+ const maxLeverage = Number(a.maxLeverage);
217
+ out.push({
218
+ market: {
219
+ venue: "hyperliquid",
220
+ venueSymbol: symbol,
221
+ symbol,
222
+ canonical: canonicalFor("hyperliquid", symbol, assetClass),
223
+ assetClass,
224
+ settle: "USDC",
225
+ quoteAsset: null,
226
+ markPrice: mark,
227
+ indexPrice: positiveNumStr(ctx.oraclePx),
228
+ fundingRate: rateStr(ctx.funding),
229
+ fundingRateBasis: basis,
230
+ settlementIntervalHours: settlement,
231
+ openInterest: numStr(ctx.openInterest),
232
+ volume24h: numStr(ctx.dayNtlVlm),
233
+ tickSize: null, // HL prices use a significant-figure rule, not a fixed tick
234
+ stepSize: Number.isInteger(szDecimals) && szDecimals >= 0 ? stepFromDecimals(szDecimals) : null,
235
+ minSize: null,
236
+ minNotional: HL_MIN_ORDER_VALUE_USD,
237
+ maxLeverage: Number.isFinite(maxLeverage) && maxLeverage > 0 ? maxLeverage : null,
238
+ marginTiers: null,
239
+ session: null,
240
+ tradable: a.isDelisted !== true,
241
+ },
242
+ nextFundingTime: null, // not published in metaAndAssetCtxs
243
+ });
244
+ });
245
+ warnExcluded("hyperliquid", "unclassified/ambiguous market(s)", unclassified);
246
+ warnExcluded("hyperliquid", "market(s) without a mark price", unpriced);
247
+ return out;
248
+ }
249
+ // ── Aster ──
250
+ function asterFilter(filters, type, field) {
251
+ if (!Array.isArray(filters))
252
+ return null;
253
+ const f = filters.find(x => x.filterType === type);
254
+ return f ? positiveNumStr(f[field]) : null;
255
+ }
256
+ async function asterRows(net) {
257
+ const base = legacyPublicBaseUrl("aster", net);
258
+ const [infoJson, premiumJson, tickerJson, fundingInfoJson] = await Promise.all([
259
+ getJson("aster", `${base}/fapi/v1/exchangeInfo`, "exchangeInfo"),
260
+ getJson("aster", `${base}/fapi/v1/premiumIndex`, "premiumIndex"),
261
+ getJson("aster", `${base}/fapi/v1/ticker/24hr`, "ticker/24hr"),
262
+ getJson("aster", `${base}/fapi/v3/fundingInfo`, "fundingInfo"),
263
+ ]);
264
+ const symbols = arrayField("aster", "exchangeInfo.symbols", infoJson.symbols);
265
+ const premiumBySymbol = new Map(arrayField("aster", "premiumIndex", premiumJson).map(p => [String(p.symbol), p]));
266
+ const tickerBySymbol = new Map(arrayField("aster", "ticker/24hr", tickerJson).map(t => [String(t.symbol), t]));
267
+ const intervalBySymbol = new Map();
268
+ for (const f of arrayField("aster", "fundingInfo", fundingInfoJson)) {
269
+ const h = Number(f.fundingIntervalHours);
270
+ if (Number.isFinite(h) && h > 0)
271
+ intervalBySymbol.set(String(f.symbol), h);
272
+ }
273
+ const out = [];
274
+ const unclassified = [];
275
+ const unpriced = [];
276
+ const noInterval = [];
277
+ for (const s of symbols) {
278
+ if (s.contractType !== "PERPETUAL" || s.quoteAsset !== "USDT")
279
+ continue;
280
+ const venueSymbol = String(s.symbol);
281
+ const symbol = String(s.baseAsset);
282
+ const subTypes = Array.isArray(s.underlyingSubType) ? s.underlyingSubType.map(String) : [];
283
+ const assetClass = classifyAster(symbol, subTypes);
284
+ if (assetClass === null) {
285
+ unclassified.push(venueSymbol);
286
+ continue;
287
+ }
288
+ const p = premiumBySymbol.get(venueSymbol);
289
+ const mark = p ? positiveNumStr(p.markPrice) : null;
290
+ if (!p || mark === null) {
291
+ unpriced.push(venueSymbol);
292
+ continue;
293
+ }
294
+ const hours = intervalBySymbol.get(venueSymbol);
295
+ if (hours === undefined) {
296
+ noInterval.push(venueSymbol);
297
+ continue;
298
+ }
299
+ if (typeof s.marginAsset !== "string" || s.marginAsset.length === 0) {
300
+ throw new PerpError("EXCHANGE_ERROR", `aster exchangeInfo: ${venueSymbol} has no marginAsset`);
301
+ }
302
+ const next = Number(p.nextFundingTime);
303
+ const t = tickerBySymbol.get(venueSymbol);
304
+ out.push({
305
+ market: {
306
+ venue: "aster",
307
+ venueSymbol,
308
+ symbol,
309
+ canonical: canonicalFor("aster", symbol, assetClass),
310
+ assetClass,
311
+ settle: s.marginAsset,
312
+ quoteAsset: s.quoteAsset,
313
+ markPrice: mark,
314
+ indexPrice: positiveNumStr(p.indexPrice),
315
+ fundingRate: rateStr(p.lastFundingRate),
316
+ fundingRateBasis: { kind: "perHours", hours },
317
+ settlementIntervalHours: hours,
318
+ openInterest: null, // per-symbol endpoint only
319
+ volume24h: t ? numStr(t.quoteVolume) : null,
320
+ tickSize: asterFilter(s.filters, "PRICE_FILTER", "tickSize"),
321
+ stepSize: asterFilter(s.filters, "LOT_SIZE", "stepSize"),
322
+ minSize: asterFilter(s.filters, "LOT_SIZE", "minQty"),
323
+ minNotional: asterFilter(s.filters, "MIN_NOTIONAL", "notional"),
324
+ maxLeverage: null, // leverage brackets require a signed call
325
+ marginTiers: null,
326
+ session: null,
327
+ tradable: s.status === "TRADING",
328
+ },
329
+ nextFundingTime: Number.isFinite(next) && next > 0 ? next : null,
330
+ });
331
+ }
332
+ warnExcluded("aster", "unclassified/ambiguous market(s)", unclassified);
333
+ warnExcluded("aster", "market(s) without a mark price", unpriced);
334
+ warnExcluded("aster", "market(s) missing from fundingInfo", noInterval);
335
+ return out;
336
+ }
337
+ // ── VenuePublicData ──
338
+ function rowsFor(venue, net) {
339
+ switch (venue) {
340
+ case "pacifica": return pacificaRows(net);
341
+ case "hyperliquid": return hyperliquidRows(net);
342
+ case "aster": return asterRows(net);
343
+ }
344
+ }
345
+ export function legacyPublicData(venue) {
346
+ return {
347
+ async fetchMarkets(net) {
348
+ return (await rowsFor(venue, net)).map(r => r.market);
349
+ },
350
+ async fetchFundingRates(net) {
351
+ const out = [];
352
+ for (const { market: m, nextFundingTime } of await rowsFor(venue, net)) {
353
+ if (!m.tradable || m.fundingRate === null)
354
+ continue;
355
+ out.push({
356
+ venue: m.venue,
357
+ venueSymbol: m.venueSymbol,
358
+ symbol: m.symbol,
359
+ canonical: m.canonical,
360
+ rate: m.fundingRate,
361
+ basis: m.fundingRateBasis,
362
+ markPrice: m.markPrice,
363
+ nextFundingTime,
364
+ });
365
+ }
366
+ return out;
367
+ },
368
+ };
369
+ }
@@ -1,26 +1,36 @@
1
1
  /**
2
- * Funding rate normalization.
2
+ * Funding rate normalization (design §10.1, §10.13 #1).
3
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)
4
+ * The number of hours one quoted rate covers (the funding BASIS) comes from the
5
+ * venue definition — `VenueDefinition.fundingRateBasis`, read through
6
+ * `fundingBasisOf(ref)`. Settlement timing is a separate concept (arb/utils.ts).
8
7
  *
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.
8
+ * A venue ref is a venue id/alias, or "hl:<dex>" for Hyperliquid HIP-3 dexes
9
+ * (those resolve to the Hyperliquid core basis — current behavior preserved).
10
+ * Unknown refs throw UNKNOWN_VENUE; per-market venues
11
+ * have no single hours value — use the market's basis with the `*ByBasis`
12
+ * variants below.
13
+ */
14
+ import { type FundingRateBasis } from "../exchanges/venue-types.js";
15
+ /**
16
+ * Funding basis of a venue ref. Unknown refs (e.g. a stale value in persisted
17
+ * arb/bot state) surface as UNKNOWN_VENUE with remediation instead of an
18
+ * anonymous crash deep inside a strategy loop.
13
19
  */
20
+ export declare function venueFundingBasis(ref: string): FundingRateBasis;
14
21
  /**
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.
22
+ * Hours one quoted funding rate covers for a venue ref.
23
+ * Throws INVALID_PARAMS for per-market or annualized venues — use the market's basis.
18
24
  */
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;
25
+ export declare function getFundingHours(ref: string): number;
26
+ /** Convert a raw quoted rate to a per-hour rate using an explicit (market) basis. */
27
+ export declare function toHourlyRateByBasis(rate: number, basis: FundingRateBasis): number;
28
+ /** Convert a raw funding rate to per-hour rate using the venue's basis. */
29
+ export declare function toHourlyRate(rate: number, ref: string): number;
22
30
  /** Annualize a raw rate from a specific exchange */
23
31
  export declare function annualizeRate(rate: number, exchange: string): number;
32
+ /** Annualize a raw rate quoted on an explicit (market) basis */
33
+ export declare function annualizeRateByBasis(rate: number, basis: FundingRateBasis): number;
24
34
  /** Annualize an already-normalized hourly rate (no exchange conversion needed) */
25
35
  export declare function annualizeHourlyRate(hourlyRate: number): number;
26
36
  /**
@@ -39,9 +49,11 @@ export declare function computeSignedAnnualSpread(shortRate: number, shortExchan
39
49
  /**
40
50
  * Estimate hourly funding payment for a position.
41
51
  * @param rate - raw funding rate from the exchange
42
- * @param exchange - exchange name
52
+ * @param exchange - venue ref
43
53
  * @param positionUsd - position notional in USD
44
54
  * @param side - "long" or "short" (longs pay positive rate, shorts receive)
45
55
  * @returns hourly payment in USD (positive = you pay, negative = you receive)
46
56
  */
47
57
  export declare function estimateHourlyFunding(rate: number, exchange: string, positionUsd: number, side: "long" | "short"): number;
58
+ /** Same as estimateHourlyFunding, from an already-normalized hourly rate. */
59
+ export declare function estimateHourlyFundingFromHourly(hourlyRate: number, positionUsd: number, side: "long" | "short"): number;
@@ -1,42 +1,71 @@
1
1
  /**
2
- * Funding rate normalization.
2
+ * Funding rate normalization (design §10.1, §10.13 #1).
3
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)
4
+ * The number of hours one quoted rate covers (the funding BASIS) comes from the
5
+ * venue definition — `VenueDefinition.fundingRateBasis`, read through
6
+ * `fundingBasisOf(ref)`. Settlement timing is a separate concept (arb/utils.ts).
8
7
  *
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.
8
+ * A venue ref is a venue id/alias, or "hl:<dex>" for Hyperliquid HIP-3 dexes
9
+ * (those resolve to the Hyperliquid core basis — current behavior preserved).
10
+ * Unknown refs throw UNKNOWN_VENUE; per-market venues
11
+ * have no single hours value — use the market's basis with the `*ByBasis`
12
+ * variants below.
13
13
  */
14
+ import { PerpError } from "../errors.js";
15
+ import { fundingBasisOf, parseVenueRef } from "../exchanges/venue.js";
16
+ import { rateToHourly } from "../exchanges/venue-types.js";
17
+ import { ensureVenuesRegistered } from "../exchanges/venues/index.js";
14
18
  /** Funding periods per year by convention */
15
19
  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
- aster: 8,
22
- };
23
20
  /**
24
- * Get funding period in hours for an exchange.
25
- * HIP-3 deployed dexes (not in the map) default to 1h (API returns hourly).
26
- * HL and PAC are 1h, Lighter API returns 8h rates.
21
+ * Funding basis of a venue ref. Unknown refs (e.g. a stale value in persisted
22
+ * arb/bot state) surface as UNKNOWN_VENUE with remediation instead of an
23
+ * anonymous crash deep inside a strategy loop.
24
+ */
25
+ export function venueFundingBasis(ref) {
26
+ ensureVenuesRegistered();
27
+ try {
28
+ parseVenueRef(ref);
29
+ }
30
+ catch (err) {
31
+ if (err instanceof PerpError && err.structured.code === "UNKNOWN_VENUE") {
32
+ throw new PerpError("UNKNOWN_VENUE", `Cannot normalize funding for unknown venue reference "${ref}"`, {
33
+ remediation: "Use a registered venue id (or hl:<dex> for HIP-3). If the value came from persisted state "
34
+ + "(~/.perp/arb-state.json or a bot state file), fix or remove that entry.",
35
+ });
36
+ }
37
+ throw err;
38
+ }
39
+ return fundingBasisOf(ref);
40
+ }
41
+ /**
42
+ * Hours one quoted funding rate covers for a venue ref.
43
+ * Throws INVALID_PARAMS for per-market or annualized venues — use the market's basis.
27
44
  */
28
- export function getFundingHours(exchange) {
29
- return EXCHANGE_FUNDING_HOURS[exchange.toLowerCase()] ?? 1;
45
+ export function getFundingHours(ref) {
46
+ const basis = venueFundingBasis(ref);
47
+ if (basis.kind !== "perHours") {
48
+ throw new PerpError("INVALID_PARAMS", `Funding basis of "${ref}" is annualized, not a number of hours`, {
49
+ remediation: "Use the market's fundingRateBasis with toHourlyRateByBasis()",
50
+ });
51
+ }
52
+ return basis.hours;
30
53
  }
31
- /** Convert a raw funding rate to per-hour rate */
32
- export function toHourlyRate(rate, exchange) {
33
- const hours = EXCHANGE_FUNDING_HOURS[exchange.toLowerCase()] ?? 1;
34
- return rate / hours;
54
+ /** Convert a raw quoted rate to a per-hour rate using an explicit (market) basis. */
55
+ export function toHourlyRateByBasis(rate, basis) {
56
+ return rateToHourly(rate, basis);
57
+ }
58
+ /** Convert a raw funding rate to per-hour rate using the venue's basis. */
59
+ export function toHourlyRate(rate, ref) {
60
+ return rateToHourly(rate, venueFundingBasis(ref));
35
61
  }
36
62
  /** Annualize a raw rate from a specific exchange */
37
63
  export function annualizeRate(rate, exchange) {
38
- const hourlyRate = toHourlyRate(rate, exchange);
39
- return hourlyRate * HOURLY_PERIODS * 100; // as percentage
64
+ return annualizeHourlyRate(toHourlyRate(rate, exchange));
65
+ }
66
+ /** Annualize a raw rate quoted on an explicit (market) basis */
67
+ export function annualizeRateByBasis(rate, basis) {
68
+ return annualizeHourlyRate(toHourlyRateByBasis(rate, basis));
40
69
  }
41
70
  /** Annualize an already-normalized hourly rate (no exchange conversion needed) */
42
71
  export function annualizeHourlyRate(hourlyRate) {
@@ -66,13 +95,16 @@ export function computeSignedAnnualSpread(shortRate, shortExchange, longRate, lo
66
95
  /**
67
96
  * Estimate hourly funding payment for a position.
68
97
  * @param rate - raw funding rate from the exchange
69
- * @param exchange - exchange name
98
+ * @param exchange - venue ref
70
99
  * @param positionUsd - position notional in USD
71
100
  * @param side - "long" or "short" (longs pay positive rate, shorts receive)
72
101
  * @returns hourly payment in USD (positive = you pay, negative = you receive)
73
102
  */
74
103
  export function estimateHourlyFunding(rate, exchange, positionUsd, side) {
75
- const hourlyRate = toHourlyRate(rate, exchange);
104
+ return estimateHourlyFundingFromHourly(toHourlyRate(rate, exchange), positionUsd, side);
105
+ }
106
+ /** Same as estimateHourlyFunding, from an already-normalized hourly rate. */
107
+ export function estimateHourlyFundingFromHourly(hourlyRate, positionUsd, side) {
76
108
  // Long pays positive funding, short receives positive funding
77
109
  const multiplier = side === "long" ? 1 : -1;
78
110
  return hourlyRate * positionUsd * multiplier;
@@ -1,22 +1,36 @@
1
1
  /**
2
- * Real-time 3-DEX funding rate comparison.
2
+ * Real-time cross-venue funding rate comparison.
3
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.
4
+ * Fans out over every venue with the "marketData" role (design §10.6) using
5
+ * each venue's `publicData.fetchFundingRates(net)`, normalizes every row to a
6
+ * per-hour rate with the row's own basis (Lighter
7
+ * family 8h, Aster per-symbol), and matches markets across venues by
8
+ * `canonical` (null → excluded, §10.9).
9
+ *
10
+ * A venue that fails is reported in `exchangeStatus` ("error") and
11
+ * `exchangeErrors` (message) — its rows are absent, never silently empty.
7
12
  */
8
13
  import { type HistoricalAverages } from "./history.js";
14
+ import { type VenueDefinition } from "../exchanges/venue.js";
15
+ import { type FundingRateBasis, type Network, type VenueId } from "../exchanges/venue-types.js";
9
16
  export interface ExchangeFundingRate {
10
- exchange: "pacifica" | "hyperliquid" | "lighter" | "aster";
17
+ exchange: VenueId;
18
+ /** Venue display symbol (e.g. "kPEPE", "BTC"). */
11
19
  symbol: string;
20
+ /** Cross-venue matching key (canonical-symbols.ts). */
21
+ canonical: string;
12
22
  fundingRate: number;
23
+ basis: FundingRateBasis;
13
24
  hourlyRate: number;
14
25
  annualizedPct: number;
15
26
  markPrice: number;
16
- nextFundingTime?: number;
17
- historicalAvg?: HistoricalAverages;
27
+ nextFundingTime: number | null;
28
+ historicalAvg: HistoricalAverages | null;
29
+ /** false = the venue's funding rate is unverified (VenueDefinition.fundingVerified) — shown only on explicit request. */
30
+ fundingVerified: boolean;
18
31
  }
19
32
  export interface SymbolFundingComparison {
33
+ /** Canonical symbol shared by every row. */
20
34
  symbol: string;
21
35
  rates: ExchangeFundingRate[];
22
36
  maxSpreadAnnual: number;
@@ -29,20 +43,38 @@ export interface FundingRateSnapshot {
29
43
  timestamp: string;
30
44
  symbols: SymbolFundingComparison[];
31
45
  exchangeStatus: Record<string, "ok" | "error">;
46
+ /** Error message per venue whose status is "error" (§10.13 #3). */
47
+ exchangeErrors: Record<string, string>;
32
48
  }
33
49
  export declare const TOP_SYMBOLS: string[];
34
50
  /**
35
- * Fetch funding rates from all 3 DEXs in parallel, normalize, and compare.
51
+ * Fetch every venue in `venues` concurrently; failures land in status/errors.
52
+ * Venues whose funding is unverified (VenueDefinition.fundingVerified false — QFEX until its
53
+ * rate unit is confirmed) are DROPPED unless `includeUnverified`: every consumer that ranks,
54
+ * alerts on or trades funding spreads (arb scan, MCP, alerts, dashboard, portfolio) leaves it
55
+ * false. Only explicit, labelled reads pass true (`arb scan --include-unverified`).
56
+ */
57
+ export declare function fetchVenuesRates(venues: VenueDefinition[], net: Network, includeUnverified: boolean): Promise<{
58
+ rates: ExchangeFundingRate[];
59
+ exchangeStatus: Record<string, "ok" | "error">;
60
+ exchangeErrors: Record<string, string>;
61
+ }>;
62
+ /**
63
+ * Fetch funding rates from every market-data venue in parallel, normalize, and compare.
36
64
  * Returns rates sorted by max spread (descending).
65
+ * @param opts.network defaults to the network the CLI selected (setSharedApiNetwork)
37
66
  */
38
67
  export declare function fetchAllFundingRates(opts?: {
39
68
  symbols?: string[];
40
69
  minSpread?: number;
70
+ network?: Network;
71
+ /** Also fetch venues whose funding rate is unverified (default false — see fetchVenuesRates). */
72
+ includeUnverified?: boolean;
41
73
  }): Promise<FundingRateSnapshot>;
42
74
  /**
43
- * Fetch rates for a single symbol across all exchanges.
75
+ * Fetch rates for a single (canonical) symbol across all exchanges.
44
76
  */
45
- export declare function fetchSymbolFundingRates(symbol: string): Promise<SymbolFundingComparison | null>;
77
+ export declare function fetchSymbolFundingRates(symbol: string, includeUnverified?: boolean): Promise<SymbolFundingComparison | null>;
46
78
  export interface SpotPerpSpread {
47
79
  symbol: string;
48
80
  /** Perp exchange with the highest absolute funding rate */
@@ -65,7 +97,7 @@ export interface SpotPerpSpread {
65
97
  /**
66
98
  * Fetch spot+perp funding rate spreads.
67
99
  * Since spot has 0 funding cost, the spread is simply |perp funding rate|.
68
- * Only includes symbols available on at least one spot exchange (HL or LT).
100
+ * Only includes symbols available on at least one bot spot venue (def.spot, §12.4 #3).
69
101
  *
70
102
  * Key safety checks:
71
103
  * - U-token mapping: UBTC→BTC, UETH→ETH, USOL→SOL, UFART→FARTCOIN
@@ -75,7 +107,10 @@ export interface SpotPerpSpread {
75
107
  export declare function fetchSpotPerpSpreads(opts?: {
76
108
  symbols?: string[];
77
109
  minSpread?: number;
110
+ network?: Network;
78
111
  }): Promise<{
79
112
  timestamp: string;
80
113
  spreads: SpotPerpSpread[];
114
+ exchangeStatus: Record<string, "ok" | "error">;
115
+ exchangeErrors: Record<string, string>;
81
116
  }>;