perp-cli 0.14.3 → 0.16.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 (363) hide show
  1. package/README.ja.md +243 -0
  2. package/README.ko.md +243 -0
  3. package/README.md +138 -405
  4. package/README.zh.md +243 -0
  5. package/dist/agent-wallet/store.d.ts +3 -2
  6. package/dist/agent-wallet/store.js +2 -2
  7. package/dist/api/public/hyperliquid.js +4 -0
  8. package/dist/api/public/lighter.d.ts +5 -0
  9. package/dist/api/public/lighter.js +5 -0
  10. package/dist/api/public/pacifica.js +85 -15
  11. package/dist/api/public/urls.d.ts +2 -0
  12. package/dist/api/public/urls.js +3 -0
  13. package/dist/arb/sizing.js +4 -2
  14. package/dist/arb/state.d.ts +6 -0
  15. package/dist/arb/utils.d.ts +1 -2
  16. package/dist/arb/utils.js +1 -2
  17. package/dist/balance-by-address.d.ts +39 -0
  18. package/dist/balance-by-address.js +44 -0
  19. package/dist/bot/apex/pulse.js +3 -0
  20. package/dist/bot/apex/radar.js +2 -1
  21. package/dist/bot/engine.d.ts +10 -1
  22. package/dist/bot/engine.js +14 -6
  23. package/dist/bot/indicators.d.ts +7 -0
  24. package/dist/bot/indicators.js +16 -0
  25. package/dist/bot/perp-order.d.ts +4 -4
  26. package/dist/bot/perp-order.js +5 -5
  27. package/dist/bot/quarantine.d.ts +1 -1
  28. package/dist/bot/quarantine.js +1 -1
  29. package/dist/bot/quote-rules.js +1 -1
  30. package/dist/bot/strategies/apex-strategy.js +3 -2
  31. package/dist/bot/strategies/engine-mm.js +6 -5
  32. package/dist/bot/strategies/funding-arb-strategy.js +12 -5
  33. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
  34. package/dist/bot/strategies/funding-arb-utils.js +53 -20
  35. package/dist/bot/strategies/funding-auto.js +2 -2
  36. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  37. package/dist/bot/strategies/liquidation-mm.js +8 -2
  38. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  39. package/dist/bot/strategies/momentum-breakout.js +11 -3
  40. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
  41. package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
  42. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  43. package/dist/bot/strategies/spot-perp-executor.js +3 -3
  44. package/dist/bot/strategy-types.d.ts +2 -1
  45. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  46. package/dist/bot/tui/BotDashboard.js +1 -1
  47. package/dist/cache.js +12 -6
  48. package/dist/cli-program.d.ts +15 -0
  49. package/dist/cli-program.js +126 -6
  50. package/dist/commands/account.js +168 -24
  51. package/dist/commands/agent-import.d.ts +7 -0
  52. package/dist/commands/agent-import.js +30 -1
  53. package/dist/commands/agent.d.ts +6 -3
  54. package/dist/commands/agent.js +160 -12
  55. package/dist/commands/arb/index.js +23 -81
  56. package/dist/commands/arb-auto.d.ts +24 -0
  57. package/dist/commands/arb-auto.js +175 -97
  58. package/dist/commands/arb.d.ts +20 -0
  59. package/dist/commands/arb.js +107 -163
  60. package/dist/commands/backtest.js +23 -21
  61. package/dist/commands/bot.js +1 -1
  62. package/dist/commands/builder.js +1 -1
  63. package/dist/commands/funds.js +113 -3
  64. package/dist/commands/init.d.ts +1 -1
  65. package/dist/commands/init.js +27 -7
  66. package/dist/commands/manage.js +23 -21
  67. package/dist/commands/market.d.ts +48 -0
  68. package/dist/commands/market.js +395 -100
  69. package/dist/commands/options.d.ts +14 -0
  70. package/dist/commands/options.js +151 -0
  71. package/dist/commands/pacifica-earn.d.ts +5 -0
  72. package/dist/commands/pacifica-earn.js +455 -0
  73. package/dist/commands/pacifica-orders.d.ts +14 -0
  74. package/dist/commands/pacifica-orders.js +142 -0
  75. package/dist/commands/portfolio.d.ts +16 -0
  76. package/dist/commands/portfolio.js +83 -55
  77. package/dist/commands/rebalance.d.ts +1 -0
  78. package/dist/commands/rebalance.js +15 -0
  79. package/dist/commands/risk.d.ts +11 -2
  80. package/dist/commands/risk.js +39 -44
  81. package/dist/commands/trade.d.ts +1 -0
  82. package/dist/commands/trade.js +67 -62
  83. package/dist/commands/wallet.js +1 -1
  84. package/dist/config.d.ts +2 -2
  85. package/dist/config.js +15 -4
  86. package/dist/constants.js +6 -0
  87. package/dist/cross-chain-margin.d.ts +3 -1
  88. package/dist/cross-chain-margin.js +4 -2
  89. package/dist/dashboard/server.js +3 -3
  90. package/dist/dashboard/ui.js +9 -6
  91. package/dist/errors.d.ts +29 -1
  92. package/dist/errors.js +35 -3
  93. package/dist/exchanges/address.d.ts +8 -0
  94. package/dist/exchanges/address.js +47 -0
  95. package/dist/exchanges/arcus/adapter.d.ts +16 -12
  96. package/dist/exchanges/arcus/adapter.js +45 -18
  97. package/dist/exchanges/arcus/agent.d.ts +1 -1
  98. package/dist/exchanges/arcus/agent.js +1 -1
  99. package/dist/exchanges/arcus/client.js +1 -1
  100. package/dist/exchanges/arcus/public.js +3 -2
  101. package/dist/exchanges/arcus/signing.d.ts +2 -3
  102. package/dist/exchanges/arcus/signing.js +1 -1
  103. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  104. package/dist/exchanges/arcus/spot-chain.js +1 -1
  105. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  106. package/dist/exchanges/arcus/spot-router.js +2 -2
  107. package/dist/exchanges/arcus/spot.d.ts +2 -3
  108. package/dist/exchanges/arcus/spot.js +2 -3
  109. package/dist/exchanges/aster-spot.d.ts +3 -3
  110. package/dist/exchanges/aster-spot.js +6 -9
  111. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  112. package/dist/exchanges/aster-typed-data.js +1 -1
  113. package/dist/exchanges/aster.d.ts +30 -10
  114. package/dist/exchanges/aster.js +153 -53
  115. package/dist/exchanges/builder-fee.d.ts +1 -1
  116. package/dist/exchanges/builder-fee.js +13 -2
  117. package/dist/exchanges/canonical-symbols.d.ts +8 -2
  118. package/dist/exchanges/canonical-symbols.js +46 -2
  119. package/dist/exchanges/capabilities.d.ts +2 -0
  120. package/dist/exchanges/capabilities.js +2 -1
  121. package/dist/exchanges/derive/client.d.ts +37 -0
  122. package/dist/exchanges/derive/client.js +135 -0
  123. package/dist/exchanges/derive/options.d.ts +73 -0
  124. package/dist/exchanges/derive/options.js +379 -0
  125. package/dist/exchanges/extended/adapter.d.ts +96 -0
  126. package/dist/exchanges/extended/adapter.js +241 -0
  127. package/dist/exchanges/extended/client.d.ts +38 -0
  128. package/dist/exchanges/extended/client.js +88 -0
  129. package/dist/exchanges/extended/public.d.ts +133 -0
  130. package/dist/exchanges/extended/public.js +436 -0
  131. package/dist/exchanges/grvt/adapter.d.ts +100 -0
  132. package/dist/exchanges/grvt/adapter.js +283 -0
  133. package/dist/exchanges/grvt/client.d.ts +25 -0
  134. package/dist/exchanges/grvt/client.js +86 -0
  135. package/dist/exchanges/grvt/public.d.ts +151 -0
  136. package/dist/exchanges/grvt/public.js +438 -0
  137. package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
  138. package/dist/exchanges/hyperliquid-outcome.js +29 -1
  139. package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
  140. package/dist/exchanges/hyperliquid-spot.js +24 -16
  141. package/dist/exchanges/hyperliquid.d.ts +199 -22
  142. package/dist/exchanges/hyperliquid.js +550 -202
  143. package/dist/exchanges/index.d.ts +1 -1
  144. package/dist/exchanges/index.js +1 -1
  145. package/dist/exchanges/interface.d.ts +157 -13
  146. package/dist/exchanges/interface.js +47 -0
  147. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  148. package/dist/exchanges/ioc-outcome.js +1 -1
  149. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  150. package/dist/exchanges/legacy-trading-v2.js +7 -2
  151. package/dist/exchanges/lighter-public.d.ts +67 -12
  152. package/dist/exchanges/lighter-public.js +250 -82
  153. package/dist/exchanges/lighter-signer.d.ts +1 -1
  154. package/dist/exchanges/lighter-signer.js +1 -1
  155. package/dist/exchanges/lighter-spot.d.ts +10 -5
  156. package/dist/exchanges/lighter-spot.js +105 -97
  157. package/dist/exchanges/lighter.d.ts +80 -21
  158. package/dist/exchanges/lighter.js +312 -105
  159. package/dist/exchanges/ondo/adapter.d.ts +38 -13
  160. package/dist/exchanges/ondo/adapter.js +86 -27
  161. package/dist/exchanges/ondo/agent.d.ts +1 -1
  162. package/dist/exchanges/ondo/agent.js +1 -1
  163. package/dist/exchanges/ondo/client.d.ts +1 -1
  164. package/dist/exchanges/ondo/client.js +2 -2
  165. package/dist/exchanges/ondo/public.d.ts +35 -3
  166. package/dist/exchanges/ondo/public.js +75 -32
  167. package/dist/exchanges/ondo/spot.d.ts +2 -2
  168. package/dist/exchanges/ondo/spot.js +3 -3
  169. package/dist/exchanges/options-interface.d.ts +158 -0
  170. package/dist/exchanges/options-interface.js +17 -0
  171. package/dist/exchanges/options-venues.d.ts +12 -0
  172. package/dist/exchanges/options-venues.js +35 -0
  173. package/dist/exchanges/outcome-interface.d.ts +4 -0
  174. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  175. package/dist/exchanges/pacifica-atomic.js +440 -0
  176. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  177. package/dist/exchanges/pacifica-decimal.js +51 -0
  178. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  179. package/dist/exchanges/pacifica-presigned.js +141 -0
  180. package/dist/exchanges/pacifica-print.d.ts +200 -0
  181. package/dist/exchanges/pacifica-print.js +417 -0
  182. package/dist/exchanges/pacifica-spot.d.ts +8 -2
  183. package/dist/exchanges/pacifica-spot.js +26 -18
  184. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  185. package/dist/exchanges/pacifica-typed-data.js +1 -1
  186. package/dist/exchanges/pacifica-vaults.d.ts +130 -0
  187. package/dist/exchanges/pacifica-vaults.js +277 -0
  188. package/dist/exchanges/pacifica.d.ts +108 -10
  189. package/dist/exchanges/pacifica.js +428 -171
  190. package/dist/exchanges/precision.d.ts +3 -1
  191. package/dist/exchanges/precision.js +9 -2
  192. package/dist/exchanges/public-balances.d.ts +25 -0
  193. package/dist/exchanges/public-balances.js +158 -0
  194. package/dist/exchanges/public-positions.d.ts +43 -0
  195. package/dist/exchanges/public-positions.js +154 -0
  196. package/dist/exchanges/qfex/adapter.d.ts +69 -13
  197. package/dist/exchanges/qfex/adapter.js +102 -17
  198. package/dist/exchanges/qfex/agent.d.ts +1 -1
  199. package/dist/exchanges/qfex/agent.js +1 -1
  200. package/dist/exchanges/qfex/client.d.ts +1 -1
  201. package/dist/exchanges/qfex/client.js +1 -1
  202. package/dist/exchanges/qfex/errors.d.ts +1 -1
  203. package/dist/exchanges/qfex/errors.js +2 -2
  204. package/dist/exchanges/qfex/public.d.ts +65 -12
  205. package/dist/exchanges/qfex/public.js +131 -25
  206. package/dist/exchanges/qfex/signing.d.ts +2 -2
  207. package/dist/exchanges/qfex/signing.js +4 -5
  208. package/dist/exchanges/qfex/ws-transport.d.ts +3 -3
  209. package/dist/exchanges/qfex/ws-transport.js +3 -3
  210. package/dist/exchanges/rate-limiter.d.ts +1 -1
  211. package/dist/exchanges/rate-limiter.js +5 -2
  212. package/dist/exchanges/registry.d.ts +1 -1
  213. package/dist/exchanges/registry.js +1 -1
  214. package/dist/exchanges/risex/adapter.d.ts +160 -0
  215. package/dist/exchanges/risex/adapter.js +842 -0
  216. package/dist/exchanges/risex/agent.d.ts +154 -0
  217. package/dist/exchanges/risex/agent.js +384 -0
  218. package/dist/exchanges/risex/client.d.ts +47 -0
  219. package/dist/exchanges/risex/client.js +174 -0
  220. package/dist/exchanges/risex/nonce.d.ts +43 -0
  221. package/dist/exchanges/risex/nonce.js +212 -0
  222. package/dist/exchanges/risex/public.d.ts +64 -0
  223. package/dist/exchanges/risex/public.js +150 -0
  224. package/dist/exchanges/risex/session.d.ts +71 -0
  225. package/dist/exchanges/risex/session.js +171 -0
  226. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  227. package/dist/exchanges/risex/typed-data.js +240 -0
  228. package/dist/exchanges/risex/units.d.ts +29 -0
  229. package/dist/exchanges/risex/units.js +104 -0
  230. package/dist/exchanges/slippage.d.ts +1 -1
  231. package/dist/exchanges/slippage.js +1 -1
  232. package/dist/exchanges/spot-factory.d.ts +3 -3
  233. package/dist/exchanges/spot-factory.js +8 -8
  234. package/dist/exchanges/spot-interface.d.ts +5 -5
  235. package/dist/exchanges/spot-interface.js +2 -2
  236. package/dist/exchanges/spot-result.d.ts +3 -3
  237. package/dist/exchanges/spot-result.js +3 -3
  238. package/dist/exchanges/trading-v2.d.ts +3 -3
  239. package/dist/exchanges/trading-v2.js +9 -6
  240. package/dist/exchanges/txflow/adapter.d.ts +61 -0
  241. package/dist/exchanges/txflow/adapter.js +150 -0
  242. package/dist/exchanges/txflow/client.d.ts +47 -0
  243. package/dist/exchanges/txflow/client.js +155 -0
  244. package/dist/exchanges/txflow/public.d.ts +87 -0
  245. package/dist/exchanges/txflow/public.js +355 -0
  246. package/dist/exchanges/unknown-symbol.d.ts +34 -0
  247. package/dist/exchanges/unknown-symbol.js +56 -0
  248. package/dist/exchanges/variational/adapter.d.ts +67 -0
  249. package/dist/exchanges/variational/adapter.js +137 -0
  250. package/dist/exchanges/variational/client.d.ts +21 -0
  251. package/dist/exchanges/variational/client.js +66 -0
  252. package/dist/exchanges/variational/public.d.ts +126 -0
  253. package/dist/exchanges/variational/public.js +430 -0
  254. package/dist/exchanges/venue-types.d.ts +52 -10
  255. package/dist/exchanges/venue-types.js +82 -11
  256. package/dist/exchanges/venue.d.ts +150 -11
  257. package/dist/exchanges/venue.js +126 -4
  258. package/dist/exchanges/venues/arcus.d.ts +1 -2
  259. package/dist/exchanges/venues/arcus.js +5 -4
  260. package/dist/exchanges/venues/aster.d.ts +2 -2
  261. package/dist/exchanges/venues/aster.js +6 -4
  262. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  263. package/dist/exchanges/venues/create-helpers.js +3 -3
  264. package/dist/exchanges/venues/extended.d.ts +21 -0
  265. package/dist/exchanges/venues/extended.js +96 -0
  266. package/dist/exchanges/venues/grvt.d.ts +30 -0
  267. package/dist/exchanges/venues/grvt.js +98 -0
  268. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  269. package/dist/exchanges/venues/hyperliquid.js +19 -8
  270. package/dist/exchanges/venues/index.d.ts +7 -2
  271. package/dist/exchanges/venues/index.js +8 -2
  272. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  273. package/dist/exchanges/venues/lighter-family.js +9 -11
  274. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  275. package/dist/exchanges/venues/lighter-rh.js +20 -8
  276. package/dist/exchanges/venues/lighter.d.ts +1 -1
  277. package/dist/exchanges/venues/lighter.js +18 -6
  278. package/dist/exchanges/venues/ondo.d.ts +1 -1
  279. package/dist/exchanges/venues/ondo.js +5 -3
  280. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  281. package/dist/exchanges/venues/pacifica.js +15 -4
  282. package/dist/exchanges/venues/qfex.d.ts +1 -1
  283. package/dist/exchanges/venues/qfex.js +4 -2
  284. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  285. package/dist/exchanges/venues/risex-release.js +14 -0
  286. package/dist/exchanges/venues/risex.d.ts +31 -0
  287. package/dist/exchanges/venues/risex.js +131 -0
  288. package/dist/exchanges/venues/txflow.d.ts +18 -0
  289. package/dist/exchanges/venues/txflow.js +77 -0
  290. package/dist/exchanges/venues/variational.d.ts +21 -0
  291. package/dist/exchanges/venues/variational.js +85 -0
  292. package/dist/execution-log.d.ts +1 -1
  293. package/dist/funding/legacy-public.d.ts +6 -4
  294. package/dist/funding/legacy-public.js +42 -41
  295. package/dist/funding/normalize.d.ts +1 -1
  296. package/dist/funding/normalize.js +1 -1
  297. package/dist/funding/rates.d.ts +95 -6
  298. package/dist/funding/rates.js +99 -19
  299. package/dist/index.js +7 -6
  300. package/dist/jobs.d.ts +1 -0
  301. package/dist/jobs.js +25 -1
  302. package/dist/landing.d.ts +2 -0
  303. package/dist/landing.js +1 -1
  304. package/dist/liquidity.d.ts +3 -1
  305. package/dist/liquidity.js +8 -4
  306. package/dist/mcp-pacifica-earn.d.ts +9 -0
  307. package/dist/mcp-pacifica-earn.js +64 -0
  308. package/dist/mcp-server.js +211 -68
  309. package/dist/pacifica/client.d.ts +69 -5
  310. package/dist/pacifica/client.js +181 -8
  311. package/dist/pacifica/rate-limit.d.ts +144 -0
  312. package/dist/pacifica/rate-limit.js +267 -0
  313. package/dist/pacifica/types/order.d.ts +2 -1
  314. package/dist/plan-executor.js +7 -2
  315. package/dist/positions-by-address.d.ts +34 -0
  316. package/dist/positions-by-address.js +46 -0
  317. package/dist/rebalance.js +1 -1
  318. package/dist/risk.d.ts +2 -1
  319. package/dist/risk.js +2 -4
  320. package/dist/settings.d.ts +32 -3
  321. package/dist/signer/ows-evm.d.ts +1 -1
  322. package/dist/signer/ows-evm.js +1 -1
  323. package/dist/smart-order.d.ts +1 -1
  324. package/dist/smart-order.js +1 -1
  325. package/dist/strategies/funding-arb.d.ts +1 -1
  326. package/dist/strategies/funding-arb.js +36 -63
  327. package/dist/trade-validator.js +2 -2
  328. package/dist/utils.d.ts +22 -3
  329. package/dist/utils.js +11 -3
  330. package/dist/venue-health.d.ts +40 -0
  331. package/dist/venue-health.js +75 -0
  332. package/dist/withdraw-gate.d.ts +1 -1
  333. package/dist/withdraw-gate.js +1 -1
  334. package/dist/ws/arcus-market.d.ts +9 -0
  335. package/dist/ws/arcus-market.js +9 -0
  336. package/dist/ws/aster-market.d.ts +33 -0
  337. package/dist/ws/aster-market.js +30 -0
  338. package/dist/ws/index.d.ts +980 -0
  339. package/dist/ws/index.js +4460 -0
  340. package/dist/ws/lighter-market.d.ts +14 -0
  341. package/dist/ws/lighter-market.js +13 -0
  342. package/dist/ws/node.d.ts +996 -0
  343. package/dist/ws/node.js +4554 -0
  344. package/dist/ws/pacifica-instrument.d.ts +8 -0
  345. package/dist/ws/pacifica-instrument.js +8 -0
  346. package/dist/ws/unclassified.d.ts +16 -0
  347. package/dist/ws/unclassified.js +43 -0
  348. package/dist/ws-cli/credentials.d.ts +62 -0
  349. package/dist/ws-cli/credentials.js +126 -0
  350. package/package.json +71 -10
  351. package/skills/perp-cli/SKILL.md +153 -36
  352. package/skills/perp-cli/package.json +15 -2
  353. package/skills/perp-cli/references/agent-operations.md +22 -12
  354. package/skills/perp-cli/references/commands.md +143 -17
  355. package/skills/perp-cli/references/strategies.md +38 -3
  356. package/skills/perp-cli/references/ws-library.md +523 -0
  357. package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
  358. package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
  359. package/skills/perp-cli/scripts/preflight.sh +27 -19
  360. package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
  361. package/skills/perp-cli/scripts/validate-arb.sh +56 -39
  362. package/dist/cli-spec.d.ts +0 -46
  363. package/dist/cli-spec.js +0 -81
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Variational public market data — the OFFICIAL read-only API (venues/variational.ts).
3
+ *
4
+ * Source: `GET /metadata/v2/stats` (docs.variational.io/technical-documentation/api). One call
5
+ * returns every listing; it is shared by every reader for VARIATIONAL_STATS_TTL_MS through the
6
+ * file cache (cache.ts, cross-process, raw JSON body only) plus an in-process in-flight promise,
7
+ * so `market list` + `arb scan` + `perp health` inside the window cost ONE request.
8
+ *
9
+ * Listings (575 on 2026-10-19: 557 perpetuals, 18 swaps):
10
+ * - ticker "BTC-PERP" / "XAU-SWAP", contract_type "perpetual" | "swap". Perp display symbol =
11
+ * the base ("BTC"); a swap keeps its suffix ("XAU-SWAP") so it never collides with "XAU" perp.
12
+ * - mark_price, volume_24h (USDC), open_interest.{long,short}_open_interest (USDC, single-sided:
13
+ * trader positions only — the OLP is the other side). Base-unit OI is NOT published, so the
14
+ * legacy openInterest (base units) is null; the USD figures are carried separately.
15
+ * - quotes.{base,size_1k,size_100k,size_1m}.{bid,ask}: the OLP's INDICATIVE RFQ quotes at fixed
16
+ * notionals (may be cached up to 600 s; quotes.updated_at). Omitted while a market is closed.
17
+ *
18
+ * Funding (docs.variational.io/omni/trading/funding-rates):
19
+ * - perpetual: funding.last.interval_rate is a decimal fraction for ONE interval_seconds window
20
+ * (1h / 4h / 8h, per market), positive = longs pay. annualized_rate is the same rate × 365 d /
21
+ * interval — the identity is checked per row; a row that breaks it gets no rate (warned).
22
+ * - rateKind "predicted": `last` is recomputed during the window (live samples 2026-10-19: 175
23
+ * of 557 perps changed within 2 min under the same next_timestamp) and `next` ("predicted
24
+ * funding rate for the next interval") equals it. It is the rate the OLP will charge at
25
+ * next_timestamp, not a settled print.
26
+ * - swap: separate long / short rates paid once per trading day — there is no single funding
27
+ * rate, so fundingRate is null and swaps are never matched across venues (canonical null);
28
+ * the long / short rates are shown on the Variational rows only.
29
+ */
30
+ import { getCached, setCached } from "../../cache.js";
31
+ import { canonicalFor } from "../canonical-symbols.js";
32
+ import { networkConfig } from "../venue.js";
33
+ import { variationalVenue } from "../venues/variational.js";
34
+ import { variationalShapeError, VariationalClient } from "./client.js";
35
+ import { symbolNotFound } from "../unknown-symbol.js";
36
+ /** How long one stats payload serves every reader (the venue itself sends max-age=30). */
37
+ export const VARIATIONAL_STATS_TTL_MS = 5_000;
38
+ const YEAR_SECONDS = 365 * 86_400;
39
+ /** interval_rate vs annualized_rate × interval / 365 d: both are rounded to ~4 significant digits. */
40
+ const RATE_IDENTITY_REL_TOL = 0.005;
41
+ const RATE_IDENTITY_ABS_TOL = 1e-9;
42
+ /**
43
+ * TradFi perpetuals by class (the API publishes no asset class). Equities / ETFs match by ticker
44
+ * identity; commodities / pre-IPO only through EXPLICIT_CANONICAL. A perp whose `name` reads like
45
+ * a company or fund but is missing here is listed as equity with NO canonical symbol (never matched
46
+ * across venues) until it is classified — see looksTradFi().
47
+ */
48
+ const TRADFI_PERP_CLASS = {
49
+ // Commodities
50
+ XAU: "commodity", XAG: "commodity", XPD: "commodity", XPT: "commodity", CL: "commodity", BZ: "commodity",
51
+ COPPER: "commodity", NATGAS: "commodity",
52
+ // Pre-IPO
53
+ ANTHROPIC: "preIpo", OPENAI: "preIpo",
54
+ // ETFs
55
+ DRAM: "etf", EWJ: "etf", EWT: "etf", EWY: "etf", EWZ: "etf", IWM: "etf", KSTR: "etf", QQQ: "etf", SOXL: "etf",
56
+ SOXS: "etf", SPCX: "etf", TMF: "etf", TZA: "etf", URNM: "etf", UVXY: "etf", XBI: "etf", XLE: "etf", US500: "etf",
57
+ // Equities
58
+ AAOI: "equity", AAPL: "equity", ADBE: "equity", ALAB: "equity", AMAT: "equity", AMD: "equity", AMZN: "equity",
59
+ APP: "equity", ARM: "equity", ASML: "equity", AVGO: "equity", BABA: "equity", BBX: "equity", BE: "equity",
60
+ BMNR: "equity", BNC: "equity", BOT: "equity", BRKB: "equity", BX: "equity", CAT: "equity", CBRS: "equity",
61
+ CIEN: "equity", COIN: "equity", COST: "equity", CRCL: "equity", CRDO: "equity", CRM: "equity", CRWD: "equity",
62
+ CRWV: "equity", CSCO: "equity", DELL: "equity", DIS: "equity", DJT: "equity", DKNG: "equity", EBAY: "equity",
63
+ FLNC: "equity", FWDI: "equity", GLW: "equity", GME: "equity", GOOGL: "equity", GPRO: "equity", HD: "equity",
64
+ HIMS: "equity", HOOD: "equity", HPE: "equity", IBM: "equity", INTC: "equity", IREN: "equity", JPM: "equity",
65
+ KLAC: "equity", KO: "equity", LITE: "equity", LLY: "equity", META: "equity", MRNA: "equity", MRVL: "equity",
66
+ MSFT: "equity", MSTR: "equity", MU: "equity", NBIS: "equity", NFLX: "equity", NOK: "equity", NOW: "equity",
67
+ NVDA: "equity", NVO: "equity", ONDS: "equity", ORCL: "equity", PAYP: "equity", PENG: "equity", PLTR: "equity",
68
+ QCOM: "equity", QNTX: "equity", RDDT: "equity", RIVN: "equity", RKLB: "equity", SHAZ: "equity", SHOP: "equity",
69
+ SKHY: "equity", SMCI: "equity", SNDK: "equity", SNOW: "equity", SOFI: "equity", SONY: "equity", STRC: "equity",
70
+ STXX: "equity", TER: "equity", TSLA: "equity", TSM: "equity", TTWO: "equity", TXN: "equity", UBER: "equity",
71
+ USAR: "equity", VISA: "equity", WEN: "equity", WMT: "equity", ZM: "equity",
72
+ };
73
+ /**
74
+ * Variational tickers whose canonical symbol is NOT the ticker:
75
+ * - US500-PERP is "State Street SPDR S&P 500 ETF Trust" (SPY, mark ≈ $760), not the S&P 500 index
76
+ * that other venues list as US500 (→ SPX).
77
+ * `null` = never matched across venues — the same exclusions the other venues apply:
78
+ * - SPX-PERP is the SPX6900 token; "SPX" is the canonical key of the S&P 500 index, and
79
+ * Hyperliquid / Aster / Lighter all exclude their SPX6900 (legacy-public.ts AMBIGUOUS,
80
+ * lighter-public.ts LIGHTER_AMBIGUOUS), so it could only ever pair with the index.
81
+ * - QNT-PERP is Quant (crypto); QNT is Quantinuum on Arcus / QFEX (LIGHTER_AMBIGUOUS too).
82
+ *
83
+ * Live cross-venue survey (2026-10-19, 440 Variational canonicals shared with another venue):
84
+ * ZETA (ZetaChain vs QFEX Zeta Global), DIA (DIA oracle vs Lighter DIA ETF) and BEAM (Beam vs QFEX
85
+ * Beam Global) also collide with an equity / ETF elsewhere but are the same token on the crypto
86
+ * venues; the wrong pairing is dropped, with a stderr warning, by the cross-venue price guard
87
+ * (rates.ts dropPriceOutliers / pair gap).
88
+ */
89
+ const CANONICAL_OVERRIDE = {
90
+ US500: "SPY",
91
+ SPX: null,
92
+ QNT: null,
93
+ };
94
+ /**
95
+ * Hint for a cross-venue lookup by a Variational ticker whose canonical symbol differs (arb scan
96
+ * --compare US500): where it is compared instead, or that it is never matched. null = no override.
97
+ */
98
+ export function canonicalHintFor(symbol) {
99
+ const up = symbol.trim().toUpperCase().replace(/-PERP$/, "");
100
+ if (!(up in CANONICAL_OVERRIDE))
101
+ return null;
102
+ const canonical = CANONICAL_OVERRIDE[up];
103
+ return canonical === null
104
+ ? `Variational's ${up}-PERP is never matched across venues (the ticker means a different asset elsewhere)`
105
+ : `Variational's ${up}-PERP is compared as ${canonical}: perp arb scan --compare ${canonical} --include-unverified`;
106
+ }
107
+ /** Swaps (daily-funded TradFi swaps) by class — display only: swaps never get a canonical symbol. */
108
+ const SWAP_CLASS = {
109
+ XAU: "commodity", XAG: "commodity", UKOIL: "commodity", USOIL: "commodity", HOIL: "commodity", GASOL: "commodity",
110
+ COCOA: "commodity", COPP: "commodity",
111
+ US30: "index", US100: "index", US500: "index", US400: "index", US2K: "index", CNA50: "index", TWI: "index",
112
+ USFNG: "index", VXI: "index",
113
+ DXI: "fx",
114
+ };
115
+ /** A listing name that reads like a company / fund (an unclassified TradFi perp). */
116
+ const TRADFI_NAME = /\b(Inc|Corp|Corporation|Company|Holdings?|Ltd|Limited|plc|N\.V|A\/S|Oyj|ETF|Common Stock|Class [A-Z])\b\.?/i;
117
+ export function looksTradFi(name) {
118
+ return name !== null && TRADFI_NAME.test(name);
119
+ }
120
+ /** Each distinct warning once per process (the stats body is re-parsed on every read). */
121
+ const warned = new Set();
122
+ export function warnOnce(warn, msg) {
123
+ if (warned.has(msg))
124
+ return;
125
+ warned.add(msg);
126
+ warn(msg);
127
+ }
128
+ export const QUOTE_TIERS = ["base", "size_1k", "size_100k", "size_1m"];
129
+ /** USD notional of each quote tier; "base" = the market's minimum order size (notional not published). */
130
+ export const QUOTE_TIER_NOTIONAL_USD = {
131
+ base: null, size_1k: "1000", size_100k: "100000", size_1m: "1000000",
132
+ };
133
+ const DEC = /^-?\d+(\.\d+)?$/;
134
+ const isDec = (v) => typeof v === "string" && DEC.test(v);
135
+ class RowError extends Error {
136
+ }
137
+ const bad = (what) => { throw new RowError(what); };
138
+ const dec = (v, what) => (isDec(v) ? v : bad(`${what} is not a decimal string`));
139
+ const obj = (v, what) => (v !== null && typeof v === "object" && !Array.isArray(v) ? v : bad(`${what} is not an object`));
140
+ /** Unix seconds → ms; null stays null (documented for swaps). */
141
+ function tsMs(v, what, nullable) {
142
+ if (v === null && nullable)
143
+ return null;
144
+ if (typeof v !== "number" || !Number.isInteger(v) || v <= 0)
145
+ return bad(`${what} is not a unix-seconds integer`);
146
+ return v * 1000;
147
+ }
148
+ function perpRate(v, what) {
149
+ const r = obj(v, what);
150
+ return { annualized: dec(r.annualized_rate, `${what}.annualized_rate`), interval: dec(r.interval_rate, `${what}.interval_rate`) };
151
+ }
152
+ function swapRate(v, what) {
153
+ if (v === null)
154
+ return null;
155
+ const r = obj(v, what);
156
+ return {
157
+ annualizedLong: dec(r.annualized_long_rate, `${what}.annualized_long_rate`),
158
+ annualizedShort: dec(r.annualized_short_rate, `${what}.annualized_short_rate`),
159
+ intervalLong: dec(r.interval_long_rate, `${what}.interval_long_rate`),
160
+ intervalShort: dec(r.interval_short_rate, `${what}.interval_short_rate`),
161
+ };
162
+ }
163
+ function funding(v, kind) {
164
+ if (v === null)
165
+ return null;
166
+ const f = obj(v, "funding");
167
+ const intervalSeconds = f.interval_seconds;
168
+ if (typeof intervalSeconds !== "number" || !Number.isInteger(intervalSeconds) || intervalSeconds <= 0)
169
+ return bad("funding.interval_seconds is not a positive integer");
170
+ if (kind === "perpetual") {
171
+ return {
172
+ kind, intervalSeconds,
173
+ nextFundingTime: tsMs(f.next_timestamp, "funding.next_timestamp", false),
174
+ last: perpRate(f.last, "funding.last"),
175
+ next: f.next === null ? null : perpRate(f.next, "funding.next"),
176
+ };
177
+ }
178
+ return {
179
+ kind, intervalSeconds,
180
+ nextFundingTime: tsMs(f.next_timestamp, "funding.next_timestamp", true),
181
+ last: swapRate(f.last, "funding.last"),
182
+ next: swapRate(f.next, "funding.next"),
183
+ };
184
+ }
185
+ function quotes(v) {
186
+ if (v === undefined)
187
+ return null; // documented: omitted when unavailable (e.g. a closed swap)
188
+ const q = obj(v, "quotes");
189
+ if (typeof q.updated_at !== "string" || Number.isNaN(Date.parse(q.updated_at)))
190
+ return bad("quotes.updated_at is not an ISO time");
191
+ const tiers = [];
192
+ for (const tier of QUOTE_TIERS) {
193
+ if (q[tier] === undefined)
194
+ continue; // documented: individual sizes are omitted when unavailable
195
+ const t = obj(q[tier], `quotes.${tier}`);
196
+ tiers.push({ tier, notionalUsd: QUOTE_TIER_NOTIONAL_USD[tier], bid: dec(t.bid, `quotes.${tier}.bid`), ask: dec(t.ask, `quotes.${tier}.ask`) });
197
+ }
198
+ return { updatedAt: q.updated_at, tiers };
199
+ }
200
+ function classify(base, contractType, name) {
201
+ if (contractType === "swap") {
202
+ const assetClass = SWAP_CLASS[base];
203
+ return assetClass === undefined ? null : { assetClass, canonical: null };
204
+ }
205
+ const known = TRADFI_PERP_CLASS[base];
206
+ const assetClass = known ?? (looksTradFi(name) ? "equity" : "crypto");
207
+ if (known === undefined && assetClass === "equity")
208
+ return { assetClass, canonical: null }; // unclassified TradFi
209
+ if (base in CANONICAL_OVERRIDE)
210
+ return { assetClass, canonical: CANONICAL_OVERRIDE[base] };
211
+ return { assetClass, canonical: canonicalFor("variational", base, assetClass) };
212
+ }
213
+ /** Parse one listing. Throws RowError (caught per row by parseStats) on any shape drift. */
214
+ function parseListing(raw) {
215
+ const r = obj(raw, "listing");
216
+ if (typeof r.ticker !== "string")
217
+ return bad("ticker is not a string");
218
+ const m = /^(.+)-(PERP|SWAP)$/.exec(r.ticker);
219
+ if (m === null)
220
+ return bad(`ticker ${r.ticker} has no -PERP / -SWAP suffix`);
221
+ const contractType = r.contract_type;
222
+ if (contractType !== "perpetual" && contractType !== "swap")
223
+ return { skip: `unknown contract_type ${String(contractType)}` };
224
+ if ((contractType === "perpetual") !== (m[2] === "PERP"))
225
+ return bad(`ticker suffix ${m[2]} disagrees with contract_type ${contractType}`);
226
+ const base = m[1].toUpperCase();
227
+ const name = r.name === undefined ? null : typeof r.name === "string" ? r.name.replace(/​/g, "").trim() : bad("name is not a string");
228
+ const markPrice = dec(r.mark_price, "mark_price");
229
+ if (!(Number(markPrice) > 0))
230
+ return { skip: "no positive mark price" };
231
+ const oi = obj(r.open_interest, "open_interest");
232
+ if (!("funding" in r))
233
+ return bad("funding is missing (documented as always present)");
234
+ const cls = classify(base, contractType, name);
235
+ if (cls === null)
236
+ return { skip: "unclassified swap market" };
237
+ return {
238
+ ticker: r.ticker,
239
+ contractType,
240
+ symbol: contractType === "perpetual" ? base : r.ticker,
241
+ base,
242
+ name,
243
+ markPrice,
244
+ volume24h: dec(r.volume_24h, "volume_24h"),
245
+ longOpenInterestUsd: dec(oi.long_open_interest, "open_interest.long_open_interest"),
246
+ shortOpenInterestUsd: dec(oi.short_open_interest, "open_interest.short_open_interest"),
247
+ funding: funding(r.funding, contractType),
248
+ baseSpreadBps: r.base_spread_bps === undefined ? null : dec(r.base_spread_bps, "base_spread_bps"),
249
+ quotes: quotes(r.quotes),
250
+ assetClass: cls.assetClass,
251
+ canonical: cls.canonical,
252
+ };
253
+ }
254
+ /**
255
+ * Parse the stats payload. A non-object payload / missing listings array is EXCHANGE_ERROR; a
256
+ * single malformed or unclassifiable listing is dropped with a warning naming it (never guessed).
257
+ */
258
+ export function parseVariationalStats(body, warn) {
259
+ if (body === null || typeof body !== "object" || Array.isArray(body))
260
+ throw variationalShapeError("payload is not an object");
261
+ const listings = body.listings;
262
+ if (!Array.isArray(listings))
263
+ throw variationalShapeError("payload has no listings array");
264
+ const out = [];
265
+ const dropped = [];
266
+ const seen = new Set();
267
+ for (const raw of listings) {
268
+ const ticker = typeof raw?.ticker === "string" ? raw.ticker : "?";
269
+ try {
270
+ const l = parseListing(raw);
271
+ if ("skip" in l) {
272
+ dropped.push(`${ticker} (${l.skip})`);
273
+ continue;
274
+ }
275
+ if (seen.has(l.symbol)) {
276
+ dropped.push(`${ticker} (listed twice)`);
277
+ continue;
278
+ }
279
+ seen.add(l.symbol);
280
+ out.push(l);
281
+ }
282
+ catch (e) {
283
+ if (!(e instanceof RowError))
284
+ throw e;
285
+ dropped.push(`${ticker} (${e.message})`);
286
+ }
287
+ }
288
+ if (dropped.length > 0)
289
+ warnOnce(warn, `[variational] dropped ${dropped.length} listing(s): ${dropped.join(", ")}`);
290
+ return out;
291
+ }
292
+ // ── Funding ──
293
+ /** interval_rate agrees with annualized_rate × interval / 365 d (both published, both rounded). */
294
+ export function perpRateConsistent(rate, intervalSeconds) {
295
+ const interval = Number(rate.interval);
296
+ const expected = Number(rate.annualized) * intervalSeconds / YEAR_SECONDS;
297
+ return Math.abs(interval - expected) <= Math.max(RATE_IDENTITY_ABS_TOL, Math.abs(expected) * RATE_IDENTITY_REL_TOL);
298
+ }
299
+ /** Basis of one perp's interval_rate: perHours(interval_seconds / 3600). */
300
+ export function fundingBasisOf(f) {
301
+ return { kind: "perHours", hours: f.intervalSeconds / 3600 };
302
+ }
303
+ /**
304
+ * The current (predicted) funding rate per interval, or null: swaps (no single rate), no funding
305
+ * terms, or a perp whose two published representations disagree (unit drift — warned).
306
+ */
307
+ export function currentPerpRate(l, warn) {
308
+ const f = l.funding;
309
+ if (f === null || f.kind !== "perpetual")
310
+ return null;
311
+ if (!perpRateConsistent(f.last, f.intervalSeconds)) {
312
+ warnOnce(warn, `[variational] ${l.ticker}: interval_rate ${f.last.interval} disagrees with annualized_rate ${f.last.annualized} over ${f.intervalSeconds}s — funding not shown`);
313
+ return null;
314
+ }
315
+ return f.last.interval;
316
+ }
317
+ function clientFor(net) {
318
+ return new VariationalClient(networkConfig(variationalVenue, net).restUrl, variationalVenue.rateLimit); // testnet → NOT_IMPLEMENTED
319
+ }
320
+ const DEFAULT_DEPS = {
321
+ stats: net => clientFor(net).stats(),
322
+ cache: { get: key => getCached(key), set: (key, value, ttl) => setCached(key, value, ttl) },
323
+ warn: msg => process.stderr.write(`${msg}\n`),
324
+ };
325
+ const inflight = new Map();
326
+ export const statsCacheKey = (net) => `pub:variational:${net}:stats`;
327
+ /**
328
+ * The raw stats body: from the cache when fresh, else ONE request shared by every concurrent
329
+ * caller in this process. Only the raw JSON body is cached (JSON-safe); parsing runs per read.
330
+ */
331
+ export function fetchVariationalStatsRaw(net, deps = DEFAULT_DEPS) {
332
+ const key = statsCacheKey(net);
333
+ const pending = inflight.get(key);
334
+ if (pending !== undefined)
335
+ return pending;
336
+ const p = (async () => {
337
+ const hit = deps.cache.get(key);
338
+ if (hit !== null && hit !== undefined)
339
+ return hit;
340
+ const body = await deps.stats(net);
341
+ // Only a body of the documented shape is shared; drift fails this read and is never cached.
342
+ if (!Array.isArray(body?.listings))
343
+ throw variationalShapeError("payload has no listings array");
344
+ deps.cache.set(key, body, VARIATIONAL_STATS_TTL_MS);
345
+ return body;
346
+ })().finally(() => { inflight.delete(key); });
347
+ inflight.set(key, p);
348
+ return p;
349
+ }
350
+ export async function fetchVariationalListings(net, deps = DEFAULT_DEPS) {
351
+ return parseVariationalStats(await fetchVariationalStatsRaw(net, deps), deps.warn);
352
+ }
353
+ // ── Mapping ──
354
+ /** null when the listing has no funding terms (its interval — part of VenueMarket — is unknown). */
355
+ export function toVariationalVenueMarket(l, warn) {
356
+ if (l.funding === null)
357
+ return null;
358
+ return {
359
+ venue: "variational",
360
+ venueSymbol: l.ticker,
361
+ symbol: l.symbol,
362
+ canonical: l.canonical,
363
+ assetClass: l.assetClass,
364
+ settle: "USDC",
365
+ quoteAsset: "USDC",
366
+ markPrice: l.markPrice,
367
+ // No index / oracle price is published.
368
+ indexPrice: null,
369
+ fundingRate: currentPerpRate(l, warn),
370
+ fundingRateBasis: fundingBasisOf(l.funding),
371
+ settlementIntervalHours: l.funding.intervalSeconds / 3600,
372
+ // Published in USD and single-sided only; base-unit OI is unknown.
373
+ openInterest: null,
374
+ volume24h: l.volume24h,
375
+ tickSize: null,
376
+ stepSize: null,
377
+ minSize: null,
378
+ minNotional: null,
379
+ maxLeverage: null,
380
+ marginTiers: null,
381
+ session: null,
382
+ // Quotes are omitted while a market is closed: no quote → not tradable right now.
383
+ tradable: l.quotes !== null && l.quotes.tiers.length > 0,
384
+ };
385
+ }
386
+ export async function fetchVariationalMarkets(net, deps = DEFAULT_DEPS) {
387
+ const out = [];
388
+ const noFunding = [];
389
+ for (const l of await fetchVariationalListings(net, deps)) {
390
+ const m = toVariationalVenueMarket(l, deps.warn);
391
+ if (m === null)
392
+ noFunding.push(l.ticker);
393
+ else
394
+ out.push(m);
395
+ }
396
+ if (noFunding.length > 0)
397
+ warnOnce(deps.warn, `[variational] ${noFunding.length} market(s) publish no funding terms (interval unknown) and are not shown here: ${noFunding.join(", ")}`);
398
+ return out;
399
+ }
400
+ export async function fetchVariationalFundingRates(net, deps = DEFAULT_DEPS) {
401
+ const out = [];
402
+ for (const l of await fetchVariationalListings(net, deps)) {
403
+ const m = toVariationalVenueMarket(l, deps.warn);
404
+ if (m === null || !m.tradable || m.fundingRate === null || l.funding === null)
405
+ continue;
406
+ out.push({
407
+ venue: "variational",
408
+ venueSymbol: m.venueSymbol,
409
+ symbol: m.symbol,
410
+ canonical: m.canonical,
411
+ rate: m.fundingRate,
412
+ basis: m.fundingRateBasis,
413
+ markPrice: m.markPrice,
414
+ nextFundingTime: l.funding.nextFundingTime,
415
+ });
416
+ }
417
+ return out;
418
+ }
419
+ export const variationalPublicData = {
420
+ fetchMarkets: net => fetchVariationalMarkets(net),
421
+ fetchFundingRates: net => fetchVariationalFundingRates(net),
422
+ };
423
+ /** Resolve a CLI symbol ("btc", "BTC-PERP", "xau-swap") against the parsed listings. */
424
+ export function resolveVariationalListing(listings, input) {
425
+ const up = input.trim().toUpperCase();
426
+ const hit = listings.find(l => l.ticker === up || l.symbol === up);
427
+ if (hit === undefined)
428
+ throw symbolNotFound("variational", input, `Variational has no listed market ${input}`);
429
+ return hit;
430
+ }
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Venue-level data contracts (docs/DESIGN-venue-abstraction.md §10).
2
+ * Venue-level data contracts.
3
3
  *
4
4
  * SSOT Rule #2: every "unknown" value is `T | null`, never optional, never a
5
5
  * silently substituted default.
6
6
  */
7
- import type { ExchangeMarketInfo } from "./interface.js";
7
+ import type { ExchangeMarketInfo, FundingRateKind } from "./interface.js";
8
8
  import type { ResolvedSlippage, SlippageSource } from "./slippage.js";
9
9
  import type { AppliedBuilderFee } from "./builder-fee.js";
10
- export type VenueId = "pacifica" | "hyperliquid" | "lighter" | "aster" | "lighter-rh" | "ondo" | "arcus" | "qfex";
10
+ export type VenueId = "pacifica" | "hyperliquid" | "lighter" | "aster" | "lighter-rh" | "ondo" | "arcus" | "qfex" | "risex" | "txflow" | "variational" | "grvt" | "extended";
11
11
  export type LegacyVenueId = "pacifica" | "hyperliquid" | "lighter" | "aster";
12
12
  export declare const VENUE_IDS: readonly VenueId[];
13
13
  export declare const LEGACY_VENUE_IDS: readonly LegacyVenueId[];
@@ -23,7 +23,12 @@ export type FundingRateBasis = {
23
23
  };
24
24
  /** Convert a raw quoted rate to a per-hour rate. */
25
25
  export declare function rateToHourly(rate: number, basis: FundingRateBasis): number;
26
- export type AssetClass = "crypto" | "equity" | "index" | "etf" | "commodity" | "fx" | "preIpo";
26
+ /**
27
+ * "unclassified": listed, but its class is ambiguous (a ticker that names different instruments
28
+ * on different venues: SPX6900 vs the S&P 500, Pacifica BP / CHIP, Lighter BB / BOT / WEN / QNT,
29
+ * Lighter RH USELESS). Its canonical is always null, so it is never matched across venues.
30
+ */
31
+ export type AssetClass = "crypto" | "equity" | "index" | "etf" | "commodity" | "fx" | "preIpo" | "unclassified";
27
32
  export interface MarketSession {
28
33
  /** Whether the underlying (e.g. US equity) market is open right now. */
29
34
  underlyingOpen: boolean;
@@ -48,7 +53,7 @@ export interface VenueMarket {
48
53
  /**
49
54
  * Currency prices are quoted in ("USD", "KRW", ...); null = this venue's data path does not
50
55
  * record it. A non-USD quote is never traded through perp-cli (sizing, risk limits and
51
- * notionals assume USD) — see QFEX, design §14.12 #7.
56
+ * notionals assume USD) — see QFEX,.
52
57
  */
53
58
  quoteAsset: string | null;
54
59
  markPrice: string;
@@ -73,7 +78,18 @@ export interface VenueMarket {
73
78
  session: MarketSession | null;
74
79
  /** false = listed but not tradable right now (offline / paused / corporate action). */
75
80
  tradable: boolean;
81
+ /**
82
+ * The venue's listing status as the CLI market row shows it (Extended "ACTIVE" / "REDUCE_ONLY",
83
+ * Ondo "active" / …, QFEX "active" / "nonUsdQuote"); absent = the venue states none.
84
+ */
85
+ status?: string | null;
76
86
  }
87
+ /**
88
+ * Quantity decimals of a lot step, or null when the step is not a power of ten ≤ 1 ("0.001" → 3,
89
+ * "1" → 0; TxFlow "10" / "100" and a "0.5" step → null): a decimals count cannot express them, and
90
+ * 0 next to stepSize "10" read as "any integer". `stepSize` is the authoritative grid.
91
+ */
92
+ export declare function decimalsOf(step: string): number | null;
77
93
  /** Pure mapping used by new-venue adapters to serve the legacy getMarkets(). */
78
94
  /** Tick/step/leverage required for order entry. Throws when the venue did not publish them. */
79
95
  export declare function requireTradingPrecision(m: VenueMarket): {
@@ -81,16 +97,35 @@ export declare function requireTradingPrecision(m: VenueMarket): {
81
97
  stepSize: string;
82
98
  maxLeverage: number;
83
99
  };
100
+ export declare function fundingRateKindOf(venue: VenueMarket["venue"], fundingRate: string | null): FundingRateKind | null;
101
+ /**
102
+ * THE market-row shape (`perp market list` / `info` / `prices`, ExchangeMarketInfo) of a
103
+ * VenueMarket — the one mapper for the CLI adapters of VenueMarket venues and the keyless MCP
104
+ * `get_markets` public directory rows. It does not require trading precision: a public row
105
+ * without a published tick / lot / leverage maps those to null.
106
+ */
107
+ export declare function venueMarketRow(m: VenueMarket): ExchangeMarketInfo & {
108
+ tradable: boolean;
109
+ quoteAsset: string | null;
110
+ status: string | null;
111
+ };
112
+ /** venueMarketRow for an adapter that trades the market: tick / lot / leverage must be published. */
84
113
  export declare function toLegacyMarketInfo(m: VenueMarket): ExchangeMarketInfo;
85
114
  export interface VenueFundingRate {
86
115
  venue: VenueId;
87
116
  venueSymbol: string;
88
117
  symbol: string;
89
118
  canonical: string | null;
90
- rate: string;
119
+ /** null = no funding right now (`marketClosed` true: outside the market's funding hours). */
120
+ rate: string | null;
91
121
  basis: FundingRateBasis;
92
122
  markPrice: string | null;
93
123
  nextFundingTime: number | null;
124
+ /**
125
+ * true = the current funding window is outside the market's funding hours (QFEX market_hours):
126
+ * `rate` is null — the venue's 0 is "no funding", not a 0 rate. Absent = funds around the clock.
127
+ */
128
+ marketClosed?: boolean;
94
129
  }
95
130
  /** CLI-level TIF inputs. "TOB" = Pacifica top-of-book, "GTX" = Aster post-only. */
96
131
  export type TimeInForce = "GTC" | "GTT" | "IOC" | "FOK" | "ALO" | "TOB" | "GTX";
@@ -111,7 +146,7 @@ export interface OrderRequest {
111
146
  /** Required when the venue's orderAck is "async". */
112
147
  clientOrderId: string | null;
113
148
  /**
114
- * Resolved slippage bound (resolveSlippage(), design §13.10 #1). Required for
149
+ * Resolved slippage bound (resolveSlippage()). Required for
115
150
  * type "market"; adapters whose stopMarket / TP-SL legs are protection-priced
116
151
  * (Arcus) require it there too. null only for orders that carry no market leg.
117
152
  */
@@ -138,13 +173,12 @@ export interface OrderResult {
138
173
  /** Filled base size; null = the venue response does not report it (never "0" as a stand-in). */
139
174
  filledSize: string | null;
140
175
  avgPrice: string | null;
141
- /** How the venue adapter actually executed the request (design §13.1 #3, §13.10 #14). */
176
+ /** How the venue adapter actually executed the request. */
142
177
  execution: OrderExecution;
143
178
  venueRaw: unknown;
144
179
  }
145
180
  /**
146
- * Transparent record of the venue-specific handling behind a uniform order
147
- * (design §13.1 #3, §13.2, §13.10 #14). Every field is required; a value the
181
+ * Transparent record of the venue-specific handling behind a uniform order. Every field is required; a value the
148
182
  * venue / adapter did not report is null (SSOT Rule #2), never a guess.
149
183
  */
150
184
  export interface OrderExecution {
@@ -229,3 +263,11 @@ export interface LeverageResult {
229
263
  export declare function orderRefOf(r: Pick<OrderResult, "orderId" | "clientOrderId">): OrderRef;
230
264
  /** Parse a CLI order reference: "c:<id>" → clientOrderId, otherwise orderId. */
231
265
  export declare function parseOrderRef(s: string): OrderRef;
266
+ /**
267
+ * Next funding settlement of a venue that documents hourly settlement on the hour (UTC): the next
268
+ * top of the hour after `now`. Used only where the venue's docs say so — Hyperliquid ("paid every
269
+ * hour"), Lighter ("at each hour mark"), Pacifica ("at the end of each 1-hour interval") — and
270
+ * their settled history is stamped on the hour (live 2026-10-19: HL 18:00:00.139 … 21:00:00.032,
271
+ * Pacifica 18:00:02.674 … 21:00:02.574). Other venues keep null unless they publish the time.
272
+ */
273
+ export declare function nextTopOfHourMs(now?: number): number;