gloomberb 0.16.0 → 0.16.2

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 (691) hide show
  1. package/README.md +2 -1
  2. package/package.json +2 -1
  3. package/src/api-client/beneficial-owners.ts +92 -0
  4. package/src/api-client/data.ts +44 -11
  5. package/src/api-client/errors.ts +12 -0
  6. package/src/api-client/geo.ts +363 -0
  7. package/src/api-client/index.ts +5 -0
  8. package/src/api-client/market-discovery.ts +10 -0
  9. package/src/api-client/members.ts +26 -0
  10. package/src/api-client/paths.ts +30 -1
  11. package/src/api-client/perps.ts +41 -0
  12. package/src/api-client/request.ts +5 -2
  13. package/src/api-client/types.ts +9 -1
  14. package/src/app/global-shortcuts.ts +14 -6
  15. package/src/app/keybindings/actions.ts +1 -1
  16. package/src/app/runtime/update.ts +38 -2
  17. package/src/app.tsx +10 -6
  18. package/src/brokers/account-cache.ts +24 -0
  19. package/src/cli/commands/command-utils.ts +8 -0
  20. package/src/cli/commands/crypto-hints.ts +75 -0
  21. package/src/cli/commands/market.ts +414 -106
  22. package/src/cli/commands/overview.ts +77 -32
  23. package/src/cli/commands/ticker.ts +101 -43
  24. package/src/cli/desktop-pane-shot.ts +34 -4
  25. package/src/cli/freshness.ts +61 -0
  26. package/src/cli/help.ts +1 -0
  27. package/src/cli/helpers.ts +4 -8
  28. package/src/cli/index.ts +12 -2
  29. package/src/cli/listing-arg.ts +321 -0
  30. package/src/cli/pane-functions/access-gate.ts +42 -0
  31. package/src/cli/pane-functions/catalog.ts +28 -1
  32. package/src/cli/pane-functions/data.ts +3 -1
  33. package/src/cli/pane-functions/dom.ts +20 -2
  34. package/src/cli/pane-functions/freshness.ts +400 -0
  35. package/src/cli/pane-functions/function-help.ts +36 -22
  36. package/src/cli/pane-functions/headless.ts +39 -11
  37. package/src/cli/pane-functions/index.ts +15 -4
  38. package/src/cli/pane-functions/options.ts +3 -0
  39. package/src/cli/pane-functions/report.ts +4 -0
  40. package/src/cli/pane-functions/resolver.ts +35 -7
  41. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  42. package/src/cli/pane-functions/screenshot.ts +7 -3
  43. package/src/cli/result.ts +41 -27
  44. package/src/cli/ticker-resolution.ts +7 -5
  45. package/src/components/chart/composite/composite-chart.tsx +88 -2536
  46. package/src/components/chart/composite/drawing-store.ts +59 -0
  47. package/src/components/chart/composite/legend.tsx +319 -0
  48. package/src/components/chart/composite/panel-bitmap.ts +139 -0
  49. package/src/components/chart/composite/panel-gestures.ts +59 -0
  50. package/src/components/chart/composite/panel-labels.ts +107 -0
  51. package/src/components/chart/composite/panel-surface.tsx +876 -0
  52. package/src/components/chart/composite/tool-catalog.ts +98 -0
  53. package/src/components/chart/composite/toolbar.tsx +186 -0
  54. package/src/components/chart/composite/tools.ts +8 -0
  55. package/src/components/chart/composite/use-composite-chart-keyboard.ts +703 -0
  56. package/src/components/chart/composite/use-composite-viewport.ts +143 -0
  57. package/src/components/command-bar/commands/collection.ts +5 -1
  58. package/src/components/command-bar/list/model.ts +5 -0
  59. package/src/components/command-bar/list/view.tsx +16 -3
  60. package/src/components/command-bar/pane-templates/workflow.ts +1 -0
  61. package/src/components/command-bar/routes/root/command-items.ts +9 -2
  62. package/src/components/command-bar/routes/root/plugin-install.ts +2 -2
  63. package/src/components/command-bar/routes/root/provider-search.ts +19 -5
  64. package/src/components/command-bar/routes/root/runtime.ts +14 -5
  65. package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
  66. package/src/components/command-bar/routes/ticker-search/results.ts +31 -5
  67. package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
  68. package/src/components/command-bar/routing/actions.ts +2 -0
  69. package/src/components/command-bar/selection-runtime.ts +38 -0
  70. package/src/components/command-bar/surface/index.tsx +7 -2
  71. package/src/components/command-bar/workflow/ops.ts +1 -1
  72. package/src/components/command-bar/workflow/tickers.ts +6 -0
  73. package/src/components/data-table/view.tsx +29 -10
  74. package/src/components/feedback-dialog.tsx +2 -1
  75. package/src/components/form-modal/field-row.tsx +2 -2
  76. package/src/components/index.ts +2 -2
  77. package/src/components/layout/detached-pane-shell.tsx +6 -3
  78. package/src/components/layout/floating-pane.tsx +6 -0
  79. package/src/components/layout/header.tsx +24 -7
  80. package/src/components/layout/missing-pane.tsx +2 -2
  81. package/src/components/layout/pane/content.tsx +23 -10
  82. package/src/components/layout/pane/error-boundary.tsx +103 -0
  83. package/src/components/layout/pane/footer/registration.tsx +68 -23
  84. package/src/components/layout/pane/header.tsx +61 -28
  85. package/src/components/layout/pane/index.tsx +6 -0
  86. package/src/components/layout/pane/title.ts +31 -7
  87. package/src/components/layout/shell/drag/index.ts +6 -5
  88. package/src/components/layout/shell/drag/runtime.ts +3 -0
  89. package/src/components/layout/shell/index.tsx +6 -0
  90. package/src/components/layout/shell/menu.ts +2 -1
  91. package/src/components/layout/shell/pane/actions.ts +2 -1
  92. package/src/components/layout/shell/pane/layers.tsx +29 -10
  93. package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -3
  94. package/src/components/layout/shell/ticker-link-menu.ts +23 -9
  95. package/src/components/metric-treemap/heat-surface.tsx +745 -0
  96. package/src/components/metric-treemap/heat-tiles.ts +228 -0
  97. package/src/components/metric-treemap/index.tsx +2 -0
  98. package/src/components/metric-treemap/layout.ts +8 -0
  99. package/src/components/metric-treemap/scene.ts +432 -0
  100. package/src/components/onboarding/onboarding-keyboard.ts +311 -0
  101. package/src/components/onboarding/onboarding-wizard.tsx +101 -788
  102. package/src/components/onboarding/use-onboarding-broker-form.ts +263 -0
  103. package/src/components/onboarding/use-onboarding-progress.ts +298 -0
  104. package/src/components/onboarding/use-onboarding-upgrade.ts +83 -0
  105. package/src/components/pane-settings-dialog/field-dialogs.tsx +1 -0
  106. package/src/components/ticker/list-table-view.tsx +13 -1
  107. package/src/components/toggle-list.tsx +38 -12
  108. package/src/components/ui/country-flag-art.tsx +449 -0
  109. package/src/components/ui/country-flag.tsx +41 -0
  110. package/src/components/ui/data-table/types.ts +13 -0
  111. package/src/components/ui/fields.tsx +8 -0
  112. package/src/components/ui/icon.tsx +2 -0
  113. package/src/components/ui/index.ts +4 -0
  114. package/src/components/ui/multi-select/dialog.tsx +108 -22
  115. package/src/components/ui/range-track.tsx +53 -0
  116. package/src/components/ui/status.tsx +8 -2
  117. package/src/components/ui/tabs.tsx +11 -4
  118. package/src/components/ui/text-prompt-dialog.tsx +4 -2
  119. package/src/core/state/app/types.ts +10 -1
  120. package/src/data/config/store/normalize.ts +17 -1
  121. package/src/i18n/es.ts +24 -4
  122. package/src/i18n/ja.ts +24 -4
  123. package/src/i18n/ko.ts +24 -4
  124. package/src/i18n/zh-cn.ts +24 -4
  125. package/src/i18n/zh-tw.ts +24 -4
  126. package/src/layout/ticker-navigation.ts +28 -2
  127. package/src/market-data/coordinator/chart.ts +1 -1
  128. package/src/market-data/coordinator/quotes.ts +23 -4
  129. package/src/market-data/fx-closes.ts +72 -0
  130. package/src/market-data/market/freshness.ts +78 -6
  131. package/src/market-data/market/session-close-note.ts +57 -0
  132. package/src/market-data/market/status.ts +151 -0
  133. package/src/market-data/published-apac-sessions.ts +66 -0
  134. package/src/market-data/quotes/contributions.ts +8 -0
  135. package/src/market-data/quotes/freshness.ts +36 -0
  136. package/src/market-data/quotes/resolution.ts +24 -8
  137. package/src/market-data/quotes/screener-live-quotes.ts +49 -4
  138. package/src/market-data/quotes/time.ts +16 -0
  139. package/src/plugins/builtin/account-management/pane.tsx +4 -0
  140. package/src/plugins/builtin/alerts/cli.ts +10 -5
  141. package/src/plugins/builtin/alerts/index.tsx +1 -0
  142. package/src/plugins/builtin/analytics/metrics.ts +8 -3
  143. package/src/plugins/builtin/analytics/risk-client.ts +330 -79
  144. package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
  145. package/src/plugins/builtin/analytics/risk-headless.ts +61 -49
  146. package/src/plugins/builtin/analytics/risk-math.ts +33 -13
  147. package/src/plugins/builtin/analytics/risk-model.ts +282 -64
  148. package/src/plugins/builtin/analytics/risk-pane.tsx +62 -30
  149. package/src/plugins/builtin/analytics/risk-test-data.ts +206 -0
  150. package/src/plugins/builtin/attention/headless.ts +6 -4
  151. package/src/plugins/builtin/attention/index.tsx +1 -1
  152. package/src/plugins/builtin/attention/pane.tsx +1 -1
  153. package/src/plugins/builtin/awards/headless.ts +1 -0
  154. package/src/plugins/builtin/awards/pane.tsx +1 -1
  155. package/src/plugins/builtin/backtest/headless.ts +1 -0
  156. package/src/plugins/builtin/bond-calculator/headless.ts +1 -0
  157. package/src/plugins/builtin/buildout/cells.tsx +15 -12
  158. package/src/plugins/builtin/buildout/detail/company.tsx +3 -3
  159. package/src/plugins/builtin/buildout/detail/index.tsx +0 -1
  160. package/src/plugins/builtin/buildout/detail/intel.tsx +6 -6
  161. package/src/plugins/builtin/buildout/detail/site.tsx +4 -7
  162. package/src/plugins/builtin/buildout/detail/ui.tsx +0 -19
  163. package/src/plugins/builtin/buildout/index.tsx +1 -0
  164. package/src/plugins/builtin/catalysts/headless.ts +3 -1
  165. package/src/plugins/builtin/catalysts/index.tsx +2 -2
  166. package/src/plugins/builtin/catalysts/pane.tsx +3 -1
  167. package/src/plugins/builtin/cds/headless.ts +2 -0
  168. package/src/plugins/builtin/cds/index.tsx +1 -0
  169. package/src/plugins/builtin/central-bank-rates/headless.ts +1 -0
  170. package/src/plugins/builtin/chart-composer/headless.ts +38 -3
  171. package/src/plugins/builtin/chart-composer/index.tsx +3 -1
  172. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -2
  173. package/src/plugins/builtin/chat/channels.ts +19 -3
  174. package/src/plugins/builtin/chat/content/composer-runtime.ts +28 -13
  175. package/src/plugins/builtin/chat/content/footer.tsx +15 -6
  176. package/src/plugins/builtin/chat/content/index.tsx +120 -21
  177. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +261 -113
  178. package/src/plugins/builtin/chat/content/transcript.tsx +20 -2
  179. package/src/plugins/builtin/chat/direct-messages.ts +200 -0
  180. package/src/plugins/builtin/chat/index.tsx +1 -0
  181. package/src/plugins/builtin/chat/message/desktop.tsx +3 -0
  182. package/src/plugins/builtin/chat/message/header.tsx +16 -6
  183. package/src/plugins/builtin/chat/message/inline-tokens.tsx +72 -32
  184. package/src/plugins/builtin/chat/message/profile-popover.tsx +96 -12
  185. package/src/plugins/builtin/chat/message/terminal.tsx +3 -0
  186. package/src/plugins/builtin/chat/message/types.ts +14 -2
  187. package/src/plugins/builtin/chat/profile-popover.ts +18 -13
  188. package/src/plugins/builtin/clinical-trials/index.tsx +1 -0
  189. package/src/plugins/builtin/cloud/askg/executor.ts +2 -1
  190. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  191. package/src/plugins/builtin/cloud/team/module.tsx +1 -0
  192. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
  193. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  194. package/src/plugins/builtin/comment-letters/index.tsx +1 -0
  195. package/src/plugins/builtin/company-attention/headless.ts +5 -1
  196. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  197. package/src/plugins/builtin/company-kpis/headless.ts +1 -0
  198. package/src/plugins/builtin/congress-trades/headless.ts +1 -0
  199. package/src/plugins/builtin/correlation/compute.ts +59 -16
  200. package/src/plugins/builtin/correlation/geo.ts +34 -0
  201. package/src/plugins/builtin/correlation/headless-schema.ts +4 -1
  202. package/src/plugins/builtin/correlation/headless.ts +42 -8
  203. package/src/plugins/builtin/correlation/history.ts +2 -0
  204. package/src/plugins/builtin/correlation/index.tsx +68 -14
  205. package/src/plugins/builtin/correlation/matrix/model.ts +17 -2
  206. package/src/plugins/builtin/correlation/matrix/selection.ts +5 -3
  207. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  208. package/src/plugins/builtin/correlation/relationship/pane.tsx +4 -2
  209. package/src/plugins/builtin/correlation/relationship/view-model.ts +16 -1
  210. package/src/plugins/builtin/correlation/settings.ts +1 -1
  211. package/src/plugins/builtin/cot/headless.ts +6 -3
  212. package/src/plugins/builtin/cot/index.tsx +3 -3
  213. package/src/plugins/builtin/cot/model.ts +36 -3
  214. package/src/plugins/builtin/cpi/headless.ts +4 -0
  215. package/src/plugins/builtin/credit-boards/headless.ts +2 -0
  216. package/src/plugins/builtin/credit-conditions/headless.ts +1 -0
  217. package/src/plugins/builtin/credit-documents/gauge.tsx +5 -8
  218. package/src/plugins/builtin/credit-documents/headless.ts +2 -0
  219. package/src/plugins/builtin/crypto-board/headless.ts +3 -0
  220. package/src/plugins/builtin/custom-view/loader.ts +1 -1
  221. package/src/plugins/builtin/debt-maturities/headless.ts +4 -0
  222. package/src/plugins/builtin/debt-maturities/wall-bar.tsx +14 -41
  223. package/src/plugins/builtin/dividend-yield/client.ts +4 -2
  224. package/src/plugins/builtin/dividend-yield/headless.ts +1 -0
  225. package/src/plugins/builtin/doe/headless.ts +7 -0
  226. package/src/plugins/builtin/doe/range-bar.tsx +7 -37
  227. package/src/plugins/builtin/earnings/client.ts +2 -2
  228. package/src/plugins/builtin/earnings/headless.ts +5 -0
  229. package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
  230. package/src/plugins/builtin/earnings-calls/index.tsx +1 -0
  231. package/src/plugins/builtin/earnings-ripple/client.ts +91 -18
  232. package/src/plugins/builtin/earnings-ripple/headless.ts +28 -8
  233. package/src/plugins/builtin/earnings-ripple/index.tsx +2 -2
  234. package/src/plugins/builtin/earnings-ripple/model.ts +97 -7
  235. package/src/plugins/builtin/earnings-ripple/pane.tsx +148 -23
  236. package/src/plugins/builtin/econ/index.tsx +1 -0
  237. package/src/plugins/builtin/econ-statistics/headless.ts +2 -0
  238. package/src/plugins/builtin/equity-screener/headless.ts +2 -0
  239. package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
  240. package/src/plugins/builtin/executives/index.tsx +1 -0
  241. package/src/plugins/builtin/exposure/headless.ts +3 -1
  242. package/src/plugins/builtin/fear-greed/headless.ts +2 -0
  243. package/src/plugins/builtin/filing-events/index.tsx +1 -0
  244. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  245. package/src/plugins/builtin/futures-curve/headless.ts +39 -10
  246. package/src/plugins/builtin/futures-curve/index.tsx +3 -3
  247. package/src/plugins/builtin/futures-curve/model.ts +188 -4
  248. package/src/plugins/builtin/futures-curve/pane.tsx +51 -9
  249. package/src/plugins/builtin/fx-matrix/direction.ts +81 -0
  250. package/src/plugins/builtin/fx-matrix/index.tsx +71 -33
  251. package/src/plugins/builtin/fx-matrix/live-legs.ts +17 -0
  252. package/src/plugins/builtin/fx-matrix/pairs.ts +68 -14
  253. package/src/plugins/builtin/fx-matrix/settings.ts +62 -0
  254. package/src/plugins/builtin/gpu/headless.ts +7 -3
  255. package/src/plugins/builtin/gpu/ladder.tsx +26 -30
  256. package/src/plugins/builtin/gpu/model.ts +27 -1
  257. package/src/plugins/builtin/holders/beneficial-client.ts +209 -0
  258. package/src/plugins/builtin/holders/beneficial-model.ts +279 -0
  259. package/src/plugins/builtin/holders/beneficial-report.ts +126 -0
  260. package/src/plugins/builtin/holders/beneficial-view.tsx +241 -0
  261. package/src/plugins/builtin/holders/headless.ts +139 -5
  262. package/src/plugins/builtin/holders/index.tsx +10 -3
  263. package/src/plugins/builtin/holders/pane.tsx +20 -3
  264. package/src/plugins/builtin/holders/report-header.ts +95 -0
  265. package/src/plugins/builtin/holders/table-model.ts +20 -5
  266. package/src/plugins/builtin/holders/thirteenf-match.ts +29 -1
  267. package/src/plugins/builtin/holders/types.ts +2 -1
  268. package/src/plugins/builtin/insider/headless.ts +2 -0
  269. package/src/plugins/builtin/ipo-calendar/headless.ts +1 -0
  270. package/src/plugins/builtin/iv-history/headless.ts +4 -1
  271. package/src/plugins/builtin/jobs/headless.ts +1 -0
  272. package/src/plugins/builtin/jobs/index.tsx +1 -0
  273. package/src/plugins/builtin/jobs/share-bars.tsx +5 -28
  274. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -0
  275. package/src/plugins/builtin/macro-day/headless.ts +2 -0
  276. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  277. package/src/plugins/builtin/market-heatmap/data.ts +30 -2
  278. package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
  279. package/src/plugins/builtin/market-heatmap/index.tsx +245 -81
  280. package/src/plugins/builtin/market-heatmap/live.ts +88 -0
  281. package/src/plugins/builtin/market-heatmap/model.ts +146 -0
  282. package/src/plugins/builtin/market-heatmap/portfolio.ts +50 -11
  283. package/src/plugins/builtin/market-movers/client.ts +3 -2
  284. package/src/plugins/builtin/market-movers/headless.ts +10 -1
  285. package/src/plugins/builtin/market-movers/screener.ts +18 -5
  286. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  287. package/src/plugins/builtin/members/client.ts +53 -0
  288. package/src/plugins/builtin/members/headless.ts +35 -0
  289. package/src/plugins/builtin/members/index.tsx +20 -0
  290. package/src/plugins/builtin/members/model.ts +68 -0
  291. package/src/plugins/builtin/members/pane.tsx +175 -0
  292. package/src/plugins/builtin/mna/distress/headless.ts +1 -0
  293. package/src/plugins/builtin/mna/headless.ts +1 -0
  294. package/src/plugins/builtin/mna/index.tsx +2 -1
  295. package/src/plugins/builtin/mna/pane.tsx +5 -2
  296. package/src/plugins/builtin/money-markets/headless.ts +1 -0
  297. package/src/plugins/builtin/news/headless.ts +2 -0
  298. package/src/plugins/builtin/news/wire/index.tsx +8 -1
  299. package/src/plugins/builtin/notes/cli.ts +14 -6
  300. package/src/plugins/builtin/notes/index.tsx +1 -0
  301. package/src/plugins/builtin/openfda/index.tsx +1 -0
  302. package/src/plugins/builtin/options/index.tsx +2 -1
  303. package/src/plugins/builtin/options-calculator/headless.ts +2 -0
  304. package/src/plugins/builtin/options-positioning/headless.ts +3 -0
  305. package/src/plugins/builtin/options-scenario/headless.ts +5 -0
  306. package/src/plugins/builtin/pe-band/client.ts +74 -2
  307. package/src/plugins/builtin/pe-band/headless.ts +26 -8
  308. package/src/plugins/builtin/pe-band/model.ts +102 -22
  309. package/src/plugins/builtin/pe-band/pane.tsx +34 -16
  310. package/src/plugins/builtin/pe-band/report-dates.ts +78 -0
  311. package/src/plugins/builtin/perps/client.ts +66 -5
  312. package/src/plugins/builtin/perps/equity-identity.ts +16 -0
  313. package/src/plugins/builtin/perps/equity-row.tsx +6 -4
  314. package/src/plugins/builtin/perps/headless.ts +144 -16
  315. package/src/plugins/builtin/perps/index.tsx +6 -6
  316. package/src/plugins/builtin/perps/model.ts +189 -12
  317. package/src/plugins/builtin/perps/pane.tsx +248 -47
  318. package/src/plugins/builtin/plugin-marketplace/activation.ts +19 -0
  319. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -2
  320. package/src/plugins/builtin/plugin-marketplace/store.ts +2 -0
  321. package/src/plugins/builtin/portfolio-list/allocation.ts +245 -0
  322. package/src/plugins/builtin/portfolio-list/cached-account.ts +33 -0
  323. package/src/plugins/builtin/portfolio-list/cell-version.ts +19 -1
  324. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +328 -29
  325. package/src/plugins/builtin/portfolio-list/cli/render.ts +230 -39
  326. package/src/plugins/builtin/portfolio-list/cli/shared.ts +67 -2
  327. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +35 -31
  328. package/src/plugins/builtin/portfolio-list/column-values.ts +124 -47
  329. package/src/plugins/builtin/portfolio-list/headless.ts +98 -16
  330. package/src/plugins/builtin/portfolio-list/index.tsx +14 -3
  331. package/src/plugins/builtin/portfolio-list/live-valuation.ts +7 -5
  332. package/src/plugins/builtin/portfolio-list/mutations.ts +55 -1
  333. package/src/plugins/builtin/portfolio-list/pane/data.ts +6 -0
  334. package/src/plugins/builtin/portfolio-list/pane/index.tsx +47 -13
  335. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +43 -2
  336. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +48 -5
  337. package/src/plugins/builtin/portfolio-list/settings.ts +33 -2
  338. package/src/plugins/builtin/portfolio-list/summary/index.tsx +24 -1
  339. package/src/plugins/builtin/portfolio-list/summary/totals.ts +26 -7
  340. package/src/plugins/builtin/power/headless.ts +3 -1
  341. package/src/plugins/builtin/rate-path/headless.ts +2 -0
  342. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  343. package/src/plugins/builtin/relative-rotation/headless.ts +2 -0
  344. package/src/plugins/builtin/research/analyst-headless.ts +3 -0
  345. package/src/plugins/builtin/research/events-headless.ts +2 -0
  346. package/src/plugins/builtin/research/headless.ts +2 -0
  347. package/src/plugins/builtin/research/index.tsx +1 -0
  348. package/src/plugins/builtin/research/relative-valuation-headless.ts +10 -5
  349. package/src/plugins/builtin/research/relative-valuation-model.ts +11 -3
  350. package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
  351. package/src/plugins/builtin/research-search/index.tsx +1 -0
  352. package/src/plugins/builtin/revenue-breakdown/headless.ts +2 -0
  353. package/src/plugins/builtin/reverse-dcf/headless.ts +2 -0
  354. package/src/plugins/builtin/risk-factors/headless.ts +2 -0
  355. package/src/plugins/builtin/scanner/flow-history.ts +2 -13
  356. package/src/plugins/builtin/scanner/headless.ts +14 -0
  357. package/src/plugins/builtin/scanner/hilo-bars.tsx +12 -70
  358. package/src/plugins/builtin/scanner/hilo-model.ts +0 -18
  359. package/src/plugins/builtin/seasonality/headless.ts +2 -0
  360. package/src/plugins/builtin/sec/command-bar-search.ts +14 -4
  361. package/src/plugins/builtin/sec/filing-content.ts +42 -17
  362. package/src/plugins/builtin/sec/forms.ts +12 -0
  363. package/src/plugins/builtin/sec/headless.ts +2 -0
  364. package/src/plugins/builtin/sectors/client.ts +1 -0
  365. package/src/plugins/builtin/sectors/headless.ts +9 -1
  366. package/src/plugins/builtin/sectors/move-bar.tsx +10 -32
  367. package/src/plugins/builtin/sectors/sector-model.ts +2 -0
  368. package/src/plugins/builtin/shared/crypto-pair.ts +14 -0
  369. package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
  370. package/src/plugins/builtin/shared/report-freshness.ts +57 -0
  371. package/src/plugins/builtin/shared/research-cells.tsx +4 -9
  372. package/src/plugins/builtin/short-interest/headless.ts +2 -0
  373. package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
  374. package/src/plugins/builtin/short-volume/headless.ts +1 -0
  375. package/src/plugins/builtin/social-mentions/headless.ts +1 -0
  376. package/src/plugins/builtin/social-mentions/index.tsx +1 -1
  377. package/src/plugins/builtin/supply-chain/headless.ts +7 -1
  378. package/src/plugins/builtin/supply-chain/index.tsx +10 -1
  379. package/src/plugins/builtin/supply-chain/model.ts +27 -2
  380. package/src/plugins/builtin/supply-chain/pane.tsx +71 -32
  381. package/src/plugins/builtin/themes/headless.ts +3 -0
  382. package/src/plugins/builtin/thirteenf/headless.ts +110 -25
  383. package/src/plugins/builtin/thirteenf/index.tsx +2 -1
  384. package/src/plugins/builtin/thirteenf/report-notes.ts +149 -0
  385. package/src/plugins/builtin/ticker-detail/headless.ts +7 -1
  386. package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
  387. package/src/plugins/builtin/ticker-detail/overview/components.tsx +5 -60
  388. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
  389. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +1 -1
  390. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +26 -26
  391. package/src/plugins/builtin/ticker-detail/pane.tsx +1 -1
  392. package/src/plugins/builtin/ticker-detail/quote-footer.ts +7 -1
  393. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +20 -4
  394. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -2
  395. package/src/plugins/builtin/time-sales/headless.ts +5 -1
  396. package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
  397. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  398. package/src/plugins/builtin/volatility/headless.ts +6 -1
  399. package/src/plugins/builtin/world-indices/headless.ts +2 -0
  400. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +157 -0
  401. package/src/plugins/builtin/world-venue-map/basemap.ts +214 -0
  402. package/src/plugins/builtin/world-venue-map/client.ts +45 -0
  403. package/src/plugins/builtin/world-venue-map/entity-detail.tsx +147 -0
  404. package/src/plugins/builtin/world-venue-map/entity-table.ts +59 -0
  405. package/src/plugins/builtin/world-venue-map/evidence.ts +77 -0
  406. package/src/plugins/builtin/world-venue-map/feed.ts +293 -0
  407. package/src/plugins/builtin/world-venue-map/geo-draw.tsx +248 -0
  408. package/src/plugins/builtin/world-venue-map/geo-series.ts +142 -0
  409. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +447 -0
  410. package/src/plugins/builtin/world-venue-map/headless.ts +146 -0
  411. package/src/plugins/builtin/world-venue-map/ids.ts +1 -0
  412. package/src/plugins/builtin/world-venue-map/index.ts +67 -4
  413. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +552 -0
  414. package/src/plugins/builtin/world-venue-map/layers.ts +348 -0
  415. package/src/plugins/builtin/world-venue-map/map-symbols.ts +220 -0
  416. package/src/plugins/builtin/world-venue-map/map.tsx +381 -45
  417. package/src/plugins/builtin/world-venue-map/model.ts +7 -4
  418. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +199 -0
  419. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n00.chunk.ts +4 -0
  420. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n15.chunk.ts +4 -0
  421. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n30.chunk.ts +4 -0
  422. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n45.chunk.ts +4 -0
  423. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n60.chunk.ts +4 -0
  424. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n75.chunk.ts +4 -0
  425. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s15.chunk.ts +4 -0
  426. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s30.chunk.ts +4 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s60.chunk.ts +4 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n00.chunk.ts +4 -0
  429. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n15.chunk.ts +4 -0
  430. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n30.chunk.ts +4 -0
  431. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n45.chunk.ts +4 -0
  432. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n60.chunk.ts +4 -0
  433. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n75.chunk.ts +4 -0
  434. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s15.chunk.ts +4 -0
  435. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s30.chunk.ts +4 -0
  436. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s45.chunk.ts +4 -0
  437. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n00.chunk.ts +4 -0
  438. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n15.chunk.ts +4 -0
  439. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n30.chunk.ts +4 -0
  440. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n45.chunk.ts +4 -0
  441. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n60.chunk.ts +4 -0
  442. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n75.chunk.ts +4 -0
  443. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s15.chunk.ts +4 -0
  444. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s30.chunk.ts +4 -0
  445. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s45.chunk.ts +4 -0
  446. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s60.chunk.ts +4 -0
  447. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n00.chunk.ts +4 -0
  448. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n15.chunk.ts +4 -0
  449. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n30.chunk.ts +4 -0
  450. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n45.chunk.ts +4 -0
  451. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n60.chunk.ts +4 -0
  452. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n75.chunk.ts +4 -0
  453. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s15.chunk.ts +4 -0
  454. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s30.chunk.ts +4 -0
  455. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s60.chunk.ts +4 -0
  456. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n00.chunk.ts +4 -0
  457. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n15.chunk.ts +4 -0
  458. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n30.chunk.ts +4 -0
  459. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n45.chunk.ts +4 -0
  460. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n60.chunk.ts +4 -0
  461. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n75.chunk.ts +4 -0
  462. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s15.chunk.ts +4 -0
  463. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s30.chunk.ts +4 -0
  464. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s60.chunk.ts +4 -0
  465. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n00.chunk.ts +4 -0
  466. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n15.chunk.ts +4 -0
  467. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n30.chunk.ts +4 -0
  468. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n45.chunk.ts +4 -0
  469. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n60.chunk.ts +4 -0
  470. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n75.chunk.ts +4 -0
  471. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075s45.chunk.ts +4 -0
  472. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n00.chunk.ts +4 -0
  473. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n15.chunk.ts +4 -0
  474. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n30.chunk.ts +4 -0
  475. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n45.chunk.ts +4 -0
  476. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n60.chunk.ts +4 -0
  477. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n75.chunk.ts +4 -0
  478. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090s15.chunk.ts +4 -0
  479. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n00.chunk.ts +4 -0
  480. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n15.chunk.ts +4 -0
  481. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n30.chunk.ts +4 -0
  482. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n45.chunk.ts +4 -0
  483. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n60.chunk.ts +4 -0
  484. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n75.chunk.ts +4 -0
  485. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s15.chunk.ts +4 -0
  486. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s30.chunk.ts +4 -0
  487. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s45.chunk.ts +4 -0
  488. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n00.chunk.ts +4 -0
  489. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n15.chunk.ts +4 -0
  490. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n30.chunk.ts +4 -0
  491. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n45.chunk.ts +4 -0
  492. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n60.chunk.ts +4 -0
  493. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s15.chunk.ts +4 -0
  494. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s30.chunk.ts +4 -0
  495. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s45.chunk.ts +4 -0
  496. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n00.chunk.ts +4 -0
  497. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n15.chunk.ts +4 -0
  498. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n30.chunk.ts +4 -0
  499. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n45.chunk.ts +4 -0
  500. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n60.chunk.ts +4 -0
  501. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n75.chunk.ts +4 -0
  502. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s15.chunk.ts +4 -0
  503. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s30.chunk.ts +4 -0
  504. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s45.chunk.ts +4 -0
  505. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n00.chunk.ts +4 -0
  506. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n15.chunk.ts +4 -0
  507. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n45.chunk.ts +4 -0
  508. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n60.chunk.ts +4 -0
  509. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n75.chunk.ts +4 -0
  510. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s15.chunk.ts +4 -0
  511. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s30.chunk.ts +4 -0
  512. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s45.chunk.ts +4 -0
  513. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s60.chunk.ts +4 -0
  514. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n00.chunk.ts +4 -0
  515. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n15.chunk.ts +4 -0
  516. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n45.chunk.ts +4 -0
  517. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n60.chunk.ts +4 -0
  518. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s15.chunk.ts +4 -0
  519. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s30.chunk.ts +4 -0
  520. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s45.chunk.ts +4 -0
  521. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s60.chunk.ts +4 -0
  522. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n00.chunk.ts +4 -0
  523. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n15.chunk.ts +4 -0
  524. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n30.chunk.ts +4 -0
  525. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n45.chunk.ts +4 -0
  526. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n60.chunk.ts +4 -0
  527. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n75.chunk.ts +4 -0
  528. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s15.chunk.ts +4 -0
  529. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s30.chunk.ts +4 -0
  530. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s45.chunk.ts +4 -0
  531. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n00.chunk.ts +4 -0
  532. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n15.chunk.ts +4 -0
  533. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n30.chunk.ts +4 -0
  534. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n60.chunk.ts +4 -0
  535. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n75.chunk.ts +4 -0
  536. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s30.chunk.ts +4 -0
  537. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s60.chunk.ts +4 -0
  538. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n30.chunk.ts +4 -0
  539. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n45.chunk.ts +4 -0
  540. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n60.chunk.ts +4 -0
  541. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n75.chunk.ts +4 -0
  542. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s15.chunk.ts +4 -0
  543. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s30.chunk.ts +4 -0
  544. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s60.chunk.ts +4 -0
  545. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n00.chunk.ts +4 -0
  546. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n30.chunk.ts +4 -0
  547. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n45.chunk.ts +4 -0
  548. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n60.chunk.ts +4 -0
  549. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n75.chunk.ts +4 -0
  550. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s15.chunk.ts +4 -0
  551. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s30.chunk.ts +4 -0
  552. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s45.chunk.ts +4 -0
  553. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s60.chunk.ts +4 -0
  554. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n00.chunk.ts +4 -0
  555. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n15.chunk.ts +4 -0
  556. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n30.chunk.ts +4 -0
  557. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n45.chunk.ts +4 -0
  558. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n60.chunk.ts +4 -0
  559. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n75.chunk.ts +4 -0
  560. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s15.chunk.ts +4 -0
  561. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s30.chunk.ts +4 -0
  562. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s45.chunk.ts +4 -0
  563. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s60.chunk.ts +4 -0
  564. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n00.chunk.ts +4 -0
  565. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n15.chunk.ts +4 -0
  566. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n30.chunk.ts +4 -0
  567. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n45.chunk.ts +4 -0
  568. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n60.chunk.ts +4 -0
  569. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n75.chunk.ts +4 -0
  570. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s15.chunk.ts +4 -0
  571. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s30.chunk.ts +4 -0
  572. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s45.chunk.ts +4 -0
  573. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s60.chunk.ts +4 -0
  574. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n00.chunk.ts +4 -0
  575. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n15.chunk.ts +4 -0
  576. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n30.chunk.ts +4 -0
  577. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n45.chunk.ts +4 -0
  578. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n60.chunk.ts +4 -0
  579. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n75.chunk.ts +4 -0
  580. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105s15.chunk.ts +4 -0
  581. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n00.chunk.ts +4 -0
  582. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n15.chunk.ts +4 -0
  583. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n30.chunk.ts +4 -0
  584. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n45.chunk.ts +4 -0
  585. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n60.chunk.ts +4 -0
  586. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n75.chunk.ts +4 -0
  587. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120s30.chunk.ts +4 -0
  588. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n30.chunk.ts +4 -0
  589. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n45.chunk.ts +4 -0
  590. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n60.chunk.ts +4 -0
  591. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n75.chunk.ts +4 -0
  592. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135s30.chunk.ts +4 -0
  593. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n45.chunk.ts +4 -0
  594. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n60.chunk.ts +4 -0
  595. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s15.chunk.ts +4 -0
  596. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s30.chunk.ts +4 -0
  597. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n00.chunk.ts +4 -0
  598. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n15.chunk.ts +4 -0
  599. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n45.chunk.ts +4 -0
  600. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n60.chunk.ts +4 -0
  601. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s15.chunk.ts +4 -0
  602. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s30.chunk.ts +4 -0
  603. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n00.chunk.ts +4 -0
  604. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n15.chunk.ts +4 -0
  605. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n45.chunk.ts +4 -0
  606. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n60.chunk.ts +4 -0
  607. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s15.chunk.ts +4 -0
  608. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s30.chunk.ts +4 -0
  609. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s45.chunk.ts +4 -0
  610. package/src/plugins/builtin/world-venue-map/natural-earth/world-50m.chunk.ts +4 -0
  611. package/src/plugins/builtin/world-venue-map/pane.tsx +28 -43
  612. package/src/plugins/builtin/world-venue-map/use-geo.ts +55 -0
  613. package/src/plugins/builtin/world-venue-map/venue-header.tsx +32 -0
  614. package/src/plugins/builtin/yield-curve/headless.ts +16 -0
  615. package/src/plugins/catalog-browser.ts +2 -0
  616. package/src/plugins/catalog-manifest.ts +1 -0
  617. package/src/plugins/catalog-ui.ts +3 -1
  618. package/src/plugins/catalog.ts +1 -1
  619. package/src/plugins/host-link.ts +33 -1
  620. package/src/plugins/installer.ts +48 -12
  621. package/src/plugins/loader.ts +30 -3
  622. package/src/plugins/manager-node.ts +15 -5
  623. package/src/plugins/registry/index.ts +1 -1
  624. package/src/plugins/registry/pane-settings.ts +26 -7
  625. package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
  626. package/src/renderers/dom/build-assets.ts +81 -3
  627. package/src/renderers/dom/data-table/row.tsx +52 -16
  628. package/src/renderers/dom/desktop/controls.tsx +7 -3
  629. package/src/renderers/dom/desktop/icons.tsx +1 -0
  630. package/src/renderers/dom/desktop/list-view.tsx +35 -10
  631. package/src/renderers/dom/desktop/popover.tsx +73 -14
  632. package/src/renderers/dom/host/box.tsx +69 -26
  633. package/src/renderers/dom/host/input.tsx +14 -3
  634. package/src/renderers/dom/host/tabs.tsx +37 -1
  635. package/src/renderers/dom/styles.css +41 -7
  636. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +11 -6
  637. package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
  638. package/src/renderers/electrobun/bun/desktop/update.ts +181 -87
  639. package/src/renderers/electrobun/bun/index.ts +73 -6
  640. package/src/renderers/electrobun/bun/window/remembered-frame.ts +122 -0
  641. package/src/renderers/electrobun/shared/protocol.ts +3 -0
  642. package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
  643. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +3 -1
  644. package/src/renderers/electrobun/view/main.tsx +7 -1
  645. package/src/renderers/electrobun/view/renderer-window-error.ts +18 -0
  646. package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
  647. package/src/renderers/electrobun/view/update-host.ts +45 -21
  648. package/src/renderers/opentui/host.tsx +5 -0
  649. package/src/sources/gloomberb-cloud/index.ts +25 -4
  650. package/src/sources/gloomberb-cloud/normalizers.ts +36 -5
  651. package/src/sources/provider-router/cached-routes.ts +4 -1
  652. package/src/sources/provider-router/financials.ts +11 -8
  653. package/src/sources/provider-router/history.ts +15 -10
  654. package/src/sources/sec-registrant.ts +75 -0
  655. package/src/state/app/context/index.tsx +24 -8
  656. package/src/state/hooks/quote-streaming.ts +41 -26
  657. package/src/sync/core-contributors.ts +4 -0
  658. package/src/telemetry/benign-window-error.ts +7 -0
  659. package/src/telemetry/crash-reports-dom.ts +2 -0
  660. package/src/telemetry/crash-reports.ts +4 -1
  661. package/src/test-support/cli-context.ts +10 -2
  662. package/src/test-support/daily-nav.ts +25 -0
  663. package/src/theme/heat-colors.ts +357 -2
  664. package/src/tickers/instrument-kind.ts +34 -7
  665. package/src/tickers/search/asset-classes.ts +24 -9
  666. package/src/tickers/search/index.ts +44 -11
  667. package/src/tickers/search/ranking.ts +23 -3
  668. package/src/time-series/resolve.ts +1 -1
  669. package/src/time-series/use-chart-resolution.ts +1 -1
  670. package/src/types/context-menu.ts +6 -0
  671. package/src/types/data-provider.ts +2 -0
  672. package/src/types/financials.ts +5 -0
  673. package/src/types/headless.ts +79 -2
  674. package/src/types/plugin.ts +38 -6
  675. package/src/types/ticker.ts +15 -0
  676. package/src/ui/context-menu.tsx +1 -0
  677. package/src/ui/host.tsx +16 -0
  678. package/src/updater/index.ts +38 -13
  679. package/src/utils/async-deadline.ts +21 -0
  680. package/src/utils/cli-output.ts +5 -1
  681. package/src/utils/crypto-pair.ts +41 -0
  682. package/src/utils/currency-units.ts +14 -0
  683. package/src/utils/depositary-receipt.ts +26 -0
  684. package/src/utils/exchanges.ts +72 -0
  685. package/src/utils/fire-and-forget.ts +24 -0
  686. package/src/utils/format.ts +13 -0
  687. package/src/utils/price-earnings.ts +70 -3
  688. package/src/utils/price-history.ts +58 -13
  689. package/src/utils/rpc-timeout-error.ts +27 -0
  690. package/src/utils/sec.ts +13 -1
  691. package/src/utils/utc-time.ts +79 -0
@@ -4,19 +4,21 @@ import { usePlanAccess } from "../../../api-client/plan-access";
4
4
  import { ActionRow } from "../../../components";
5
5
  import { useAsyncResource, useAutoRefresh, usePluginAppActions } from "../../../public/react";
6
6
  import { useResearchCloudSession } from "../shared/research-cloud-session";
7
- import { listingIdentity } from "../shared/ticker-request";
7
+ import { perpEquityIdentity } from "./equity-identity";
8
8
  import { loadPerpsEquity } from "./client";
9
9
  import { compact, marketLabel, percent, price } from "./model";
10
10
 
11
11
  /** A failed optional market comparison never blocks a stock's quote. */
12
- export function PerpEquityRow({ symbol, instrumentType, maxRows = 3 }: { symbol: string; instrumentType?: string; maxRows?: number }) {
12
+ export function PerpEquityRow({ symbol, exchange, instrumentType, maxRows = 3 }: { symbol: string; exchange?: string; instrumentType?: string; maxRows?: number }) {
13
13
  const session = useResearchCloudSession();
14
14
  const plan = usePlanAccess();
15
15
  const { createPaneFromTemplate } = usePluginAppActions();
16
- const identity = listingIdentity(symbol)?.symbol;
16
+ const identity = perpEquityIdentity(symbol, exchange);
17
+ const listingSymbol = identity?.symbol;
18
+ const listingExchange = identity?.exchange;
17
19
  const enabled = !!identity && ["STK", "EQUITY", "STOCK", "COMMON STOCK"].includes(instrumentType?.toUpperCase() ?? "");
18
20
  const accessKey = `${session.requestKey}:${plan.hasProAccess ? "pro" : "preview"}`;
19
- const loader = useCallback((force: boolean) => loadPerpsEquity(identity!, accessKey, force), [identity, accessKey]);
21
+ const loader = useCallback((force: boolean) => loadPerpsEquity({ symbol: listingSymbol!, exchange: listingExchange! }, accessKey, force), [listingSymbol, listingExchange, accessKey]);
20
22
  const resource = useAsyncResource(enabled ? loader : null, { clearOnError: isAccessDenied });
21
23
  useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
22
24
  const rows = resource.data?.payload.rows.filter((row) => !row.delisted) ?? [];
@@ -1,32 +1,160 @@
1
+ import type { PerpBoardPayload, PerpBoardRow } from "../../../api-client/perps";
2
+ import type { HeadlessBundleSection, HeadlessPaneColumn, HeadlessPaneLoadArgs, HeadlessPaneRow } from "../../../types/headless";
1
3
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
- import { fetchPerpSelection, fetchPerpsHistory } from "./client";
3
- import { PERP_TABS } from "./model";
4
+ import { fetchPerpSelection, fetchPerpsBoard, fetchPerpsCompare, fetchPerpsHistory, fetchPerpsRankings } from "./client";
5
+ import { ASSET_FILTERS, BOARD_COLUMNS, BOARD_SORTS, COMPARE_COLUMNS, compact, fundingSpread, HISTORY_METRICS, label, openingTab, PERP_TABS, perpCellText, perpColumnLabel, percent,
6
+ positioningFields, price, RANKING_COLUMNS, rankingRows, RANKINGS, ratioText, share, time, VENUES, venueLabel, venueName, venuesOf, type PerpColumnId } from "./model";
7
+
8
+ // Text columns only: the text report shows these, JSON keeps every field of every row. Rates, premiums and
9
+ // changes stay fractions in the rows; `percent` multiplies by 100 so the text agrees with the desktop pane.
10
+ const num = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value : null;
11
+ const text = (value: unknown) => typeof value === "string" ? value : null;
12
+ const pct = (digits: number): HeadlessPaneColumn["format"] => (value) => percent(num(value), digits);
13
+ const rateOver = (rate: unknown, interval: string | null, digits = 5) => num(rate) === null ? "--" : `${percent(num(rate), digits)} /${interval ?? "--"}`;
14
+ const hours = (value: unknown) => num(value) ? `${num(value)}h` : null;
15
+ const timeColumn = (key: string, header: string): HeadlessPaneColumn => ({ key, header, format: (value) => time(text(value)) });
16
+ const right = (key: string, header: string, format: HeadlessPaneColumn["format"]): HeadlessPaneColumn => ({ key, header, align: "right", format });
17
+ const priceColumn = (key: string, header: string) => right(key, header, (value) => price(num(value)));
18
+ const oiColumn = right("openInterestUsd", "OI USD", (value) => compact(num(value)));
19
+
20
+ const asMarket = (row: HeadlessPaneRow) => row as unknown as PerpBoardRow;
21
+ // The board, comparison and ranking columns print what the pane's cells show, through the same formatter.
22
+ const RIGHT = new Set<PerpColumnId>(["markPrice", "fundingRate", "fundingRate8h", "fundingApr", "premium", "openInterestUsd", "oiChange24h", "longShare", "priceChange24h", "value"]);
23
+ const KEYS: Partial<Record<PerpColumnId, string>> = { market: "baseAsset", longShare: "longShortRatio" };
24
+ const DESCRIPTIONS: Partial<Record<PerpColumnId, string>> = { longShare: "Share of accounts net long on the contract, by each venue's own definition and hourly bucket" };
25
+ const perpColumn = (id: PerpColumnId): HeadlessPaneColumn => ({ key: KEYS[id] ?? id, header: perpColumnLabel(id), ...(RIGHT.has(id) ? { align: "right" as const } : {}),
26
+ ...(DESCRIPTIONS[id] ? { description: DESCRIPTIONS[id] } : {}), format: (_, row) => perpCellText(asMarket(row), id) });
27
+ // One market's row keeps its full observation time, as it always printed.
28
+ const MARKET_COLUMNS: HeadlessPaneColumn[] = BOARD_COLUMNS.filter((id) => id !== "oiChange24h").map((id) => id === "observedAt" ? timeColumn("observedAt", "Observed") : perpColumn(id));
29
+ const BOARD_TEXT_COLUMNS = BOARD_COLUMNS.map(perpColumn);
30
+ const COMPARE_TEXT_COLUMNS = COMPARE_COLUMNS.map(perpColumn);
31
+ const rankingTextColumns = (ranking: typeof RANKINGS[number]): HeadlessPaneColumn[] => RANKING_COLUMNS.map((id) => id === "value"
32
+ ? right(ranking.field, ranking.header, pct(ranking.digits)) : perpColumn(id));
33
+ const observationColumns = (currency: string): HeadlessPaneColumn[] => [
34
+ timeColumn("time", "Time"),
35
+ { key: "resolution", header: "Resolution", format: (value) => text(value) ?? "--" },
36
+ priceColumn("markPrice", `Mark ${currency}`),
37
+ right("premium", "Premium %", pct(3)),
38
+ right("fundingRate", "Funding %/int", (value, row) => rateOver(value, hours(row.fundingIntervalHours))),
39
+ oiColumn,
40
+ right("sampleCount", "Samples", (value) => `${num(value) ?? "--"}`),
41
+ ];
42
+ const FUNDING_COLUMNS: HeadlessPaneColumn[] = [
43
+ timeColumn("time", "Time"),
44
+ right("rate", "Paid %/int", (value, row) => rateOver(value, hours(row.intervalHours))),
45
+ right("rate", "Funding 8h %", (value, row) => num(value) !== null && num(row.intervalHours) ? percent(num(value)! * 8 / num(row.intervalHours)!, 4) : "--"),
46
+ right("premium", "Premium %", pct(3)),
47
+ timeColumn("observedAt", "Observed"),
48
+ ];
49
+ const candleColumns = (currency: string): HeadlessPaneColumn[] => [
50
+ timeColumn("time", "Time"),
51
+ priceColumn("open", "Open"), priceColumn("high", "High"), priceColumn("low", "Low"), priceColumn("close", `Close ${currency}`),
52
+ right("volumeBase", "Volume (base)", (value) => compact(num(value))),
53
+ right("trades", "Trades", (value) => num(value)?.toLocaleString("en-US") ?? "--"),
54
+ ];
55
+ const LONG_SHORT_COLUMNS: HeadlessPaneColumn[] = [
56
+ timeColumn("time", "Bucket"),
57
+ right("longShare", "Long %", (value) => share(num(value), 2)),
58
+ right("shortShare", "Short %", (value) => share(num(value), 2)),
59
+ right("ratio", "Ratio", (value) => ratioText(num(value))),
60
+ { key: "derived", header: "Shares", format: (value) => value === true ? "From ratio" : "Published" },
61
+ timeColumn("observedAt", "Observed"),
62
+ ];
63
+ const REVISION_COLUMNS: HeadlessPaneColumn[] = [
64
+ { key: "kind", header: "Kind", format: (value) => text(value) ? label(text(value)!) : "--" },
65
+ timeColumn("period_at", "Period"),
66
+ timeColumn("received_at", "Received"),
67
+ { key: "superseded_at", header: "Status", format: (value) => text(value) ? `Superseded ${time(text(value))}` : "Current" },
68
+ { key: "fingerprint", header: "Fingerprint", format: (value) => text(value)?.slice(0, 8) || "--" },
69
+ ];
70
+
71
+ // With no market the report shows the first; a market nothing matches names the others as examples.
72
+ const EXAMPLE_MARKETS = ["BTC", "ETH", "SOL"] as const;
73
+ const DEFAULT_MARKET = EXAMPLE_MARKETS[0];
74
+
75
+ const requestedOf = (args: HeadlessPaneLoadArgs) => String(Array.isArray(args.argument) ? args.argument.join(" ") : args.argument ?? "").trim();
76
+ // No tab named: a market opens its history, nothing opens the board, as the pane does.
77
+ const tabOf = (args: HeadlessPaneLoadArgs) => PERP_TABS.find((tab) => tab === args.options.tab) ?? openingTab(requestedOf(args));
78
+ const TITLES: Record<typeof PERP_TABS[number], string> = { board: "Perpetual markets", rankings: "Perpetual rankings", compare: "Perpetual contracts across venues",
79
+ history: "Perpetual history (Pro)", evidence: "Perpetual evidence (Pro)" };
80
+ const copy = (rows: readonly PerpBoardRow[]) => rows.map((row) => ({ ...row }));
81
+ const more = (count: number, noun: string) => `${count.toLocaleString("en-US")} more ${noun} need Gloom Pro`;
82
+ // Many venues' markets, each as last polled: cited by venue, stale after an hour without a poll.
83
+ const listFreshness = (rows: readonly PerpBoardRow[], asOf: string | null, venue = "all") => {
84
+ const source = rows.length ? venuesOf(rows).map(venueName).join(", ") : venue !== "all" ? venueName(venue) : null;
85
+ return { ...(source ? { source } : {}), status: "delayed" as const, asOf, maxAgeMinutes: 60 };
86
+ };
87
+ const listMetadata = (data: Pick<PerpBoardPayload, "asOf" | "access" | "status"> & { locked: number | boolean }) => ({ asOf: data.asOf, access: data.access, locked: data.locked, status: data.status, rates: "fraction", oiCurrency: "USD" });
4
88
 
5
89
  export const perpsHeadless: HeadlessPaneDefinition<"bundle"> = {
6
- shape: "bundle", argument: { kind: "free-text", optional: true, placeholder: "market", description: "Underlying or canonical market identity, such as BTC or hyperliquid:xyz:TSLA." },
90
+ shape: "bundle", argument: { kind: "free-text", optional: true, placeholder: "market",
91
+ description: "A base asset, underlying or canonical market identity, such as BTC or hyperliquid:xyz:TSLA. Opens its history; with --tab board it searches the board, with --tab compare it is the base asset. Without one, the board." },
7
92
  options: [
8
- { key: "tab", type: "enum", settingKey: "tab", values: PERP_TABS.map((value) => ({ value })), defaultValue: "history", description: "Per-market history or evidence." },
93
+ { key: "tab", type: "enum", settingKey: "tab", values: PERP_TABS.map((value) => ({ value })), description: "Board, rankings, compare, history or evidence. Board without a market, history with one." },
94
+ { key: "asset-class", type: "enum", settingKey: "assetClass", values: ASSET_FILTERS.map((value) => ({ value })), defaultValue: "all", description: "Board asset class." },
95
+ { key: "venue", type: "enum", settingKey: "venue", values: ["all", ...VENUES].map((value) => ({ value })), defaultValue: "all", description: "Board venue." },
96
+ { key: "sort", type: "enum", settingKey: "sort", values: BOARD_SORTS.map((value) => ({ value })), defaultValue: "oi", description: "Board order: open interest, funding, open-interest change, premium or long share of accounts." },
9
97
  { key: "days", type: "enum", settingKey: "days", values: ["1", "7", "30", "90", "365"].map((value) => ({ value })), defaultValue: "7", description: "History lookback in days." },
10
- { key: "metric", type: "enum", settingKey: "metric", values: ["funding", "oi", "premium", "price"].map((value) => ({ value })), defaultValue: "funding", description: "Displayed history chart; JSON retains every series." },
98
+ { key: "metric", type: "enum", settingKey: "metric", values: HISTORY_METRICS.map((value) => ({ value })), defaultValue: "funding", description: "Displayed history chart; JSON retains every series." },
11
99
  ],
12
- discovery: { aliases: ["PERP"], dataRequirements: ["Perpetual market observations"], limitations: ["Pro history with latest-value free previews", "Rates, premiums and changes are fractions in JSON", "Open-interest history begins at first collection", "Funding APR is a simple annualization, not a forecast"] },
13
- describe: "Perpetual history and evidence (Pro)",
100
+ discovery: { aliases: ["PERP"], dataRequirements: ["Perpetual market observations"], limitations: ["Free accounts see a fixed preview of the board, rankings and comparisons, and latest values for one market", "Pro unlocks every market, full rankings and history", "Rankings list up to ten markets each", "Rates, premiums and changes are fractions in JSON", "Open-interest history begins at first collection", "Funding APR is a simple annualization, not a forecast",
101
+ "Long/short account shares: venue definitions differ; history begins at first collection"] },
102
+ describe: (args) => TITLES[tabOf(args)],
14
103
  async load(args, ctx) {
15
- const search = String(Array.isArray(args.argument) ? args.argument.join(" ") : args.argument ?? "BTC");
104
+ const requested = requestedOf(args);
105
+ const tab = tabOf(args);
106
+ if (tab === "board") {
107
+ const assetClass = String(args.options["asset-class"] ?? "all"), venue = String(args.options.venue ?? "all"), sort = String(args.options.sort ?? "oi");
108
+ const board = await fetchPerpsBoard({ ...(assetClass !== "all" ? { assetClass } : {}), ...(requested ? { search: requested } : {}), sort }, ctx.apiClient);
109
+ const rows = venue === "all" ? board.rows : board.rows.filter((row) => row.venue === venue);
110
+ return { sections: [{ title: "Markets", columns: BOARD_TEXT_COLUMNS, rows: copy(rows) }], complete: !board.locked && board.status === "ok",
111
+ errors: board.locked ? [more(board.locked, "markets")] : [], freshness: listFreshness(rows, board.asOf, venue),
112
+ metadata: { ...listMetadata(board), total: board.total, assetClass, venue, sort, ...(requested ? { search: requested } : {}) } };
113
+ }
114
+ if (tab === "rankings") {
115
+ const rankings = await fetchPerpsRankings(ctx.apiClient);
116
+ const sections: HeadlessBundleSection[] = RANKINGS.map((ranking) => ({ title: ranking.label, columns: rankingTextColumns(ranking), rows: copy(rankingRows(rankings, ranking.key)) }));
117
+ return { sections, complete: !rankings.locked && rankings.status === "ok", errors: rankings.locked ? ["Full rankings need Gloom Pro"] : [],
118
+ freshness: listFreshness(RANKINGS.flatMap((ranking) => rankingRows(rankings, ranking.key)), rankings.asOf), metadata: listMetadata(rankings) };
119
+ }
120
+ if (tab === "compare") {
121
+ // A canonical identity names one contract; its base asset is what the venues share.
122
+ const base = requested.includes(":") ? (await fetchPerpSelection(requested, ctx.apiClient)).rows[0]?.baseAsset ?? requested : (requested || DEFAULT_MARKET).toUpperCase();
123
+ const compare = await fetchPerpsCompare(base, ctx.apiClient);
124
+ if (!compare.rows.length && !compare.locked) throw new Error(`No perpetual market matches "${requested || base}". Try ${EXAMPLE_MARKETS.join(", ")}.`);
125
+ const spread = fundingSpread(compare.rows);
126
+ return { sections: [{ title: "Contracts", columns: COMPARE_TEXT_COLUMNS, rows: copy(compare.rows) },
127
+ ...(spread ? [{ title: "Funding spread", entries: [{ key: "fundingSpread8h", label: "Funding spread 8h", value: spread.value, formatted: `${spread.text} ${spread.detail}` }] }] : [])],
128
+ complete: !compare.locked && compare.status === "ok", errors: compare.locked ? [more(compare.locked, compare.locked === 1 ? "contract" : "contracts")] : [],
129
+ freshness: listFreshness(compare.rows, compare.asOf),
130
+ metadata: { ...(requested ? {} : { defaultArgument: DEFAULT_MARKET, notices: [`Showing ${DEFAULT_MARKET}. Try fn PERP ${EXAMPLE_MARKETS[1]} --tab compare.`] }),
131
+ ...listMetadata(compare), total: compare.total, baseAsset: base,
132
+ ...(spread ? { fundingSpread8h: spread.value, fundingSpreadHigh: spread.high.marketId, fundingSpreadLow: spread.low.marketId } : {}) } };
133
+ }
134
+ const search = requested || DEFAULT_MARKET;
16
135
  const market = await fetchPerpSelection(search, ctx.apiClient);
17
136
  const selected = market.rows[0];
18
- if (!selected) return { sections: [{ title: "Market", rows: [] }], errors: ["No market matches the selection"], complete: false };
19
- const metadata = { marketId: selected.marketId, sourceUrl: selected.sourceUrl, observedAt: selected.observedAt, sourceAsOf: selected.sourceAsOf,
137
+ if (!selected) throw new Error(`No perpetual market matches "${search}". Try ${EXAMPLE_MARKETS.join(", ")}.`);
138
+ const defaulted = requested ? {} : { defaultArgument: DEFAULT_MARKET, notices: [`Showing ${DEFAULT_MARKET}. Try fn PERP ${EXAMPLE_MARKETS[1]}.`] };
139
+ const metadata = { ...defaulted, marketId: selected.marketId, sourceUrl: selected.sourceUrl, observedAt: selected.observedAt, sourceAsOf: selected.sourceAsOf,
20
140
  rates: "fraction", quoteCurrency: selected.quoteCurrency, oiCurrency: "USD", fundingIntervalHours: selected.fundingIntervalHours };
21
- if (args.options.tab === "evidence") return {
22
- sections: [{ title: "Market evidence", rows: market.rows.map((row) => ({ ...row })) }, { title: "Observation revisions", rows: market.evidence.map((row) => ({ ...row })) }],
23
- complete: market.access === "pro" && market.status === "ok", errors: market.access !== "pro" ? ["Full evidence requires Gloom Pro"] : [],
141
+ // The venue's market, as last polled: not a stream, and a venue trading around the clock is stale after an hour without a poll.
142
+ const freshness = { source: venueLabel(selected), status: "delayed" as const, asOf: selected.observedAt, maxAgeMinutes: 60 };
143
+ // The same Positioning lines the pane's Evidence shows; JSON keeps the raw reading on the market row.
144
+ const positioning = positioningFields(selected);
145
+ if (tab === "evidence") return {
146
+ sections: [{ title: "Market evidence", columns: MARKET_COLUMNS, rows: market.rows.map((row) => ({ ...row })) },
147
+ ...(positioning ? [{ title: "Positioning", entries: positioning.map((field) => ({ key: field.key, label: field.label, value: field.value,
148
+ formatted: field.detail ? `${field.text} · ${field.detail}` : field.text })) }] : []),
149
+ { title: "Observation revisions", columns: REVISION_COLUMNS, rows: market.evidence.map((row) => ({ ...row })) }],
150
+ complete: market.access === "pro" && market.status === "ok", errors: market.access !== "pro" ? ["Full evidence requires Gloom Pro"] : [], freshness,
24
151
  metadata: { ...metadata, asOf: market.asOf, access: market.access, locked: market.locked, status: market.status },
25
152
  };
26
153
  const history = await fetchPerpsHistory({ marketId: selected.marketId, from: new Date(Date.now() - Number(args.options.days ?? 7) * 86_400_000).toISOString(), resolution: "auto", limit: 5000 }, ctx.apiClient);
27
- return { sections: [{ title: "Latest market", rows: [{ ...selected }] }, { title: "Own observations", rows: history.rows.map((row) => ({ ...row })) },
28
- { title: "Paid funding", rows: history.funding.map((row) => ({ ...row })) }, { title: "Hourly candles", rows: history.candles.map((row) => ({ ...row })) }],
29
- complete: !history.locked && !history.truncated && history.status === "ok",
154
+ return { sections: [{ title: "Latest market", columns: MARKET_COLUMNS, rows: [{ ...selected }] }, { title: "Own observations", columns: observationColumns(selected.quoteCurrency), rows: history.rows.map((row) => ({ ...row })) },
155
+ { title: "Paid funding", columns: FUNDING_COLUMNS, rows: history.funding.map((row) => ({ ...row })) }, { title: "Hourly candles", columns: candleColumns(selected.quoteCurrency), rows: history.candles.map((row) => ({ ...row })) },
156
+ { title: "Long/short history", columns: LONG_SHORT_COLUMNS, rows: (history.longShortRatio ?? []).map((row) => ({ ...row })) }],
157
+ complete: !history.locked && !history.truncated && history.status === "ok", freshness,
30
158
  errors: history.locked ? ["Full history requires Gloom Pro"] : history.truncated ? ["History reached the 5,000-observation limit; narrow the range"] : [],
31
159
  metadata: { ...metadata, asOf: history.asOf, access: history.access, locked: history.locked, status: history.status, from: history.from, to: history.to, resolution: history.resolution, truncated: history.truncated ?? false },
32
160
  };
@@ -1,14 +1,14 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
- import { perpsCache, perpsHistoryCache, perpsMarketCache } from "./client";
2
+ import { perpsCache, perpsHistoryCache, perpsMarketCache, perpsRankingsCache } from "./client";
3
3
  import { perpsHeadless } from "./headless";
4
4
  import { PerpsPane } from "./pane";
5
5
  export const perpsModule: PluginModule = {
6
- panes: [{ id: "perps", name: "Perpetual History", icon: "P", component: PerpsPane, defaultPosition: "right", defaultMode: "floating",
6
+ panes: [{ id: "perps", name: "Perpetuals", icon: "P", component: PerpsPane, defaultPosition: "right", defaultMode: "floating",
7
7
  defaultFloatingSize: { width: 138, height: 34 }, tableExport: true, headless: perpsHeadless }],
8
- paneTemplates: [{ id: "perps-pane", paneId: "perps", label: "Perpetual History", description: "Pro per-market funding, open interest, premium history and evidence.",
9
- keywords: ["perp", "perpetual", "funding", "open interest", "hip-3", "stock perps"],
8
+ paneTemplates: [{ id: "perps-pane", paneId: "perps", label: "Perpetuals", description: "Perpetual funding, open interest and premiums by market and venue, with rankings, a venue comparison and Pro history.",
9
+ keywords: ["perp", "perpetual", "perpetuals", "funding", "funding rate", "open interest", "hip-3", "stock perps", "basis", "venue", "funding spread", "long short ratio", "long/short", "positioning"],
10
10
  shortcut: { prefix: "PERP", argKind: "text", argOptional: true, argPlaceholder: "market" }, headless: perpsHeadless,
11
11
  createInstance: (_ctx, options) => ({ placement: "floating", settings: { market: options?.arg?.trim() ?? "" } }) }],
12
- setup(ctx) { perpsCache.attach(ctx.persistence); perpsHistoryCache.attach(ctx.persistence); perpsMarketCache.attach(ctx.persistence); },
13
- dispose() { perpsCache.reset(); perpsHistoryCache.reset(); perpsMarketCache.reset(); },
12
+ setup(ctx) { perpsCache.attach(ctx.persistence); perpsHistoryCache.attach(ctx.persistence); perpsMarketCache.attach(ctx.persistence); perpsRankingsCache.attach(ctx.persistence); },
13
+ dispose() { perpsCache.reset(); perpsHistoryCache.reset(); perpsMarketCache.reset(); perpsRankingsCache.reset(); },
14
14
  };
@@ -1,21 +1,38 @@
1
- import type { PerpBoardRow, PerpHistoryPayload } from "../../../api-client/perps";
1
+ import type { PerpBoardRow, PerpHistoryPayload, PerpLongShortRatio, PerpRankingsPayload } from "../../../api-client/perps";
2
2
  import type { DataTableCell, DataTableColumn } from "../../../components";
3
3
  import { scalarPoint, staticSeries } from "../../../components/chart/static/series";
4
+ import { getTableWidth } from "../../../components/ui/table-layout";
4
5
  import { formatCompact } from "../../../utils/format";
5
6
  import type { ThemeColors } from "../../../theme/colors";
6
- export const PERP_TABS = ["history", "evidence"] as const;
7
+ export const PERP_TABS = ["board", "rankings", "compare", "history", "evidence"] as const;
7
8
  export type PerpTab = typeof PERP_TABS[number];
9
+ /** One market's tabs; the others list many markets. */
10
+ export const isMarketTab = (tab: string) => tab === "history" || tab === "evidence";
11
+ /** A named market opens its history; nothing named opens the board. */
12
+ export const openingTab = (market: string | null | undefined): PerpTab => market?.trim() ? "history" : "board";
13
+ export const ASSET_FILTERS = ["all", "stocks", "indices", "energy", "metals", "fx", "crypto"] as const;
14
+ export const VENUES = ["hyperliquid", "binance", "bybit", "okx", "deribit", "coinbase", "kraken", "dydx"] as const;
15
+ export const BOARD_SORTS = ["oi", "funding", "oi-change", "premium", "long-short"] as const;
16
+ export const sortLabel = (sort: string) => ({ oi: "OI", funding: "Funding", "oi-change": "OI change", premium: "Premium", "long-short": "Long %" })[sort] ?? sort;
8
17
  export const label = (s: string) => s === "fx" ? "FX" : s.charAt(0).toUpperCase() + s.slice(1);
9
18
  export const percent = (n: number | null | undefined, digits = 3) => n == null ? "--" : `${n > 0 ? "+" : ""}${(n * 100).toFixed(digits)}%`;
10
19
  export const price = (n: number | null | undefined) => n == null ? "--" : n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: n < 1 ? 6 : 2 });
11
20
  export const compact = (n: number | null | undefined) => n == null ? "--" : formatCompact(n, { fixedDecimals: true });
12
21
  export const time = (s: string | null | undefined) => s ? `${s.slice(0, 10)} ${s.slice(11, 16)} UTC` : "--";
22
+ /** A list row's observation, month to minute: the header names UTC and the footer the full as-of. */
23
+ const observed = (s: string | null | undefined) => s ? `${s.slice(5, 10)} ${s.slice(11, 16)}` : "--";
13
24
  export const marketLabel = (row: PerpBoardRow) => row.dex && row.dex !== "default" ? `${row.baseAsset} · ${row.dex.toUpperCase()}` : row.baseAsset;
14
25
  export const fundingInterval = (row: Pick<PerpBoardRow, "fundingIntervalHours" | "fundingKind">) => row.fundingIntervalHours ? `${row.fundingIntervalHours}h${row.fundingKind === "last-paid" ? " paid" : row.fundingKind === "continuous" ? " continuous" : ""}` : "--";
15
- export type HistoryMetric = "funding" | "oi" | "premium" | "price";
16
- export function historyRows(data: PerpHistoryPayload | null | undefined, metric: HistoryMetric) {
26
+ export const HISTORY_METRICS = ["funding", "oi", "premium", "price", "long-short"] as const;
27
+ export type HistoryMetric = typeof HISTORY_METRICS[number];
28
+ export const metricLabel = (metric: HistoryMetric) => metric === "oi" ? "OI" : metric === "long-short" ? "L/S" : label(metric);
29
+ /** One point of a history series; a long/short point also carries the short share and the ratio. */
30
+ export interface HistoryRow { time: string; value: number | null; basis: string; change: number | null; shortShare?: number; ratio?: number | null }
31
+ export function historyRows(data: PerpHistoryPayload | null | undefined, metric: HistoryMetric): HistoryRow[] {
17
32
  if (!data) return [];
18
- const points = metric === "funding" && data.funding.length ? data.funding.map((row) => ({ time: row.time, value: row.rate * 8 / row.intervalHours, basis: `Paid · ${row.intervalHours}h → 8h` }))
33
+ const points: Array<Omit<HistoryRow, "change">> = metric === "long-short" ? (data.longShortRatio ?? []).map((row) => ({ time: row.time, value: row.longShare,
34
+ shortShare: row.shortShare, ratio: row.ratio, basis: row.derived ? "1h bucket · shares from ratio" : "1h bucket" }))
35
+ : metric === "funding" && data.funding.length ? data.funding.map((row) => ({ time: row.time, value: row.rate * 8 / row.intervalHours, basis: `Paid · ${row.intervalHours}h → 8h` }))
19
36
  : metric === "price" && data.candles.length ? data.candles.map((row) => ({ time: row.time, value: row.close, basis: "1h close" }))
20
37
  : data.rows.map((row) => ({ time: row.time,
21
38
  value: metric === "oi" ? row.openInterestUsd : metric === "premium" ? row.premium : metric === "price" ? row.markPrice
@@ -24,35 +41,195 @@ export function historyRows(data: PerpHistoryPayload | null | undefined, metric:
24
41
  return points.sort((a, b) => a.time.localeCompare(b.time)).map((row, i, all) => ({ ...row,
25
42
  change: i && row.value !== null && all[i - 1]!.value !== null ? row.value - all[i - 1]!.value! : null }));
26
43
  }
27
- export const historyValue = (n: number | null | undefined, metric: HistoryMetric) => metric === "oi" ? compact(n) : metric === "price" ? price(n) : percent(n, metric === "funding" ? 4 : 3);
44
+ /** The move since the newest point a day or more before the latest; null until history reaches back that far. */
45
+ export function dayChange(rows: readonly HistoryRow[]) {
46
+ const last = rows.at(-1);
47
+ if (last?.value == null) return null;
48
+ const before = rows.findLast((row) => row.value != null && Date.parse(row.time) <= Date.parse(last.time) - 86_400_000);
49
+ return before ? last.value - before.value! : null;
50
+ }
51
+ export const historyValue = (n: number | null | undefined, metric: HistoryMetric) => metric === "oi" ? compact(n) : metric === "price" ? price(n) : metric === "long-short" ? share(n, 2) : percent(n, metric === "funding" ? 4 : 3);
28
52
  export function historySeries(data: PerpHistoryPayload | null | undefined, metric: HistoryMetric, name: string, color: string) {
29
53
  const rows = historyRows(data, metric);
30
54
  return [staticSeries(rows.map((row) => scalarPoint(new Date(row.time), row.value)), { id: `perp-${metric}`, label: `${name} · ${historyCaption(data, metric)}`, color, calendarSpaced: true })];
31
55
  }
32
56
 
33
- export const venueLabel = (row: PerpBoardRow) => ({ hyperliquid: "Hyperliquid", binance: "Binance", bybit: "Bybit", okx: "OKX", deribit: "Deribit", coinbase: "Coinbase International", kraken: "Kraken", dydx: "dYdX" })[row.venue] ?? row.venue;
57
+ export const venueName = (venue: string) => ({ hyperliquid: "Hyperliquid", binance: "Binance", bybit: "Bybit", okx: "OKX", deribit: "Deribit", coinbase: "Coinbase International", kraken: "Kraken", dydx: "dYdX" } as Record<string, string>)[venue] ?? venue;
58
+ export const venueLabel = (row: Pick<PerpBoardRow, "venue">) => venueName(row.venue);
59
+ /** The venues behind some rows, in a stable order: the citation for a report over many markets. */
60
+ export const venuesOf = (rows: readonly Pick<PerpBoardRow, "venue">[]): string[] => [...new Set<string>(rows.map((row) => row.venue))].sort((a, b) => venueName(a).localeCompare(venueName(b)));
61
+
62
+ /** A share of accounts, unsigned: 0.6183 reads 61.8%. */
63
+ export const share = (n: number | null | undefined, digits = 1) => n == null ? "--" : `${(n * 100).toFixed(digits)}%`;
64
+ /** Long accounts per short account, as the venue publishes it. */
65
+ export const ratioText = (n: number | null | undefined, digits = 2) => n == null ? "--" : n.toFixed(digits);
66
+ /** What a long/short reading counts, short enough for a legend; `definitionText` says it in full. */
67
+ const longShortDefinition = (definition: string | null | undefined) => definition === "accounts_all" || !definition ? "All accounts" : label(definition.replaceAll("_", " "));
68
+ const LONG_SHORT_REASONS: Record<string, string> = { unsupported: "This venue publishes no per-contract account ratio", off: "Not collected yet", collecting: "Collecting" };
69
+ /** Why a row has no long/short reading, in words; null when it has one, or the server predates the series. */
70
+ export function longShortReason(row: Pick<PerpBoardRow, "longShortRatio" | "longShortRatioReason">) {
71
+ if (row.longShortRatio || !row.longShortRatioReason) return null;
72
+ return LONG_SHORT_REASONS[row.longShortRatioReason] ?? "Not available";
73
+ }
74
+ /**
75
+ * The long/short reading as Evidence lists it, shared by the pane and the text report. The Market group already
76
+ * names the venue, so the bucket row carries the venue's period instead of a second Venue row.
77
+ */
78
+ export interface PositioningField { key: string; label: string; /** The raw value JSON keeps. */ value: unknown; text: string; detail?: string }
79
+ export function positioningFields(row: Pick<PerpBoardRow, "longShortRatio" | "longShortRatioReason">): PositioningField[] | null {
80
+ const reading: PerpLongShortRatio | null = row.longShortRatio ?? null;
81
+ const reason = longShortReason(row);
82
+ if (!reading) return reason ? [{ key: "longShortRatioReason", label: "Long accounts", value: row.longShortRatioReason, text: reason }] : null;
83
+ return [
84
+ { key: "longShare", label: "Long accounts", value: reading.longShare, text: share(reading.longShare, 2) },
85
+ { key: "shortShare", label: "Short accounts", value: reading.shortShare, text: share(reading.shortShare, 2) },
86
+ { key: "ratio", label: "Long / short ratio", value: reading.ratio, text: ratioText(reading.ratio, 4) },
87
+ ...(reading.derived ? [{ key: "derived", label: "Shares", value: true, text: "Derived from the published ratio" }] : []),
88
+ { key: "definition", label: "Definition", value: reading.definition, text: reading.definitionText || longShortDefinition(reading.definition) },
89
+ { key: "bucketAt", label: "Bucket", value: reading.bucketAt, text: time(reading.bucketAt), detail: `${venueName(reading.venue)} ${reading.period}${reading.stale ? " · stale" : ""}` },
90
+ { key: "observedAt", label: "Observed", value: reading.observedAt, text: time(reading.observedAt) },
91
+ ];
92
+ }
93
+
94
+ // One text per figure, shared by the pane cells and the text report so the two always agree.
95
+ const markText = (row: Pick<PerpBoardRow, "markPrice" | "quoteCurrency">) => row.markPrice == null ? "--" : `${price(row.markPrice)} ${row.quoteCurrency}`;
96
+ const fundingPerInterval = (row: Pick<PerpBoardRow, "fundingRate" | "fundingIntervalHours" | "fundingKind">) => row.fundingRate == null ? "--" : `${percent(row.fundingRate, 5)} /${fundingInterval(row)}`;
97
+ /** Percentage points between two fractions: 0.00003 reads 0.0030pp. */
98
+ const points = (n: number | null | undefined, digits = 4) => n == null ? "--" : `${(n * 100).toFixed(digits)}pp`;
99
+
100
+ export type PerpColumnId = "market" | "venue" | "symbol" | "markPrice" | "fundingRate" | "fundingRate8h" | "fundingApr" | "premium"
101
+ | "openInterestUsd" | "oiChange24h" | "longShare" | "priceChange24h" | "observedAt" | "value";
102
+ const PERP_COLUMNS: Record<PerpColumnId, DataTableColumn> = {
103
+ market: { id: "market", label: "Market", width: 14, align: "left" },
104
+ venue: { id: "venue", label: "Venue", width: 11, align: "left" },
105
+ symbol: { id: "symbol", label: "Contract", width: 14, align: "left" },
106
+ markPrice: { id: "markPrice", label: "Mark", width: 16, align: "right" },
107
+ fundingRate: { id: "fundingRate", label: "Funding %/int", width: 14, align: "right" },
108
+ fundingRate8h: { id: "fundingRate8h", label: "Funding 8h %", width: 12, align: "right" },
109
+ fundingApr: { id: "fundingApr", label: "APR %", width: 9, align: "right" },
110
+ premium: { id: "premium", label: "Premium %", width: 9, align: "right" },
111
+ openInterestUsd: { id: "openInterestUsd", label: "OI USD", width: 8, align: "right" },
112
+ oiChange24h: { id: "oiChange24h", label: "OI 24h %", width: 8, align: "right" },
113
+ longShare: { id: "longShare", label: "Long %", width: 6, align: "right" },
114
+ priceChange24h: { id: "priceChange24h", label: "24h %", width: 7, align: "right" },
115
+ observedAt: { id: "observedAt", label: "Observed UTC", width: 12, align: "left" },
116
+ value: { id: "value", label: "Value %", width: 10, align: "right" },
117
+ };
118
+ // Columns in reading order, then the order a narrow pane gives them up in.
119
+ export const BOARD_COLUMNS: PerpColumnId[] = ["market", "venue", "markPrice", "fundingRate", "fundingApr", "premium", "openInterestUsd", "oiChange24h", "longShare", "priceChange24h", "observedAt"];
120
+ // The venue goes last: the same market trades on several, and only the venue tells those rows apart. The long share
121
+ // gives way before every figure a 100-column pane already showed, so it never pushes one of those off.
122
+ const BOARD_DROPS: PerpColumnId[] = ["observedAt", "priceChange24h", "fundingApr", "longShare", "oiChange24h", "premium", "markPrice", "openInterestUsd", "venue"];
123
+ export const COMPARE_COLUMNS: PerpColumnId[] = ["venue", "symbol", "markPrice", "fundingRate", "fundingRate8h", "fundingApr", "premium", "openInterestUsd", "oiChange24h", "longShare", "observedAt"];
124
+ const COMPARE_DROPS: PerpColumnId[] = ["observedAt", "oiChange24h", "fundingApr", "longShare", "fundingRate", "premium", "markPrice", "openInterestUsd", "symbol"];
125
+ export const RANKING_COLUMNS: PerpColumnId[] = ["market", "venue", "value", "openInterestUsd"];
126
+
127
+ /** The columns that fit the pane, the least read going first; before the last of them goes, a long market name gives up its tail. */
128
+ function fitPerpColumns(ids: readonly PerpColumnId[], drops: readonly PerpColumnId[], width: number): DataTableColumn[] {
129
+ let kept = ids.map((id) => PERP_COLUMNS[id]);
130
+ const fits = () => getTableWidth(kept) <= width;
131
+ for (const [index, drop] of drops.entries()) {
132
+ if (fits()) break;
133
+ if (index === drops.length - 1) {
134
+ kept = kept.map((column) => column.id === "market" ? { ...column, width: 10 } : column);
135
+ if (fits()) break;
136
+ }
137
+ kept = kept.filter((column) => column.id !== drop);
138
+ }
139
+ return kept;
140
+ }
141
+ export const boardColumns = (width: number) => fitPerpColumns(BOARD_COLUMNS, BOARD_DROPS, width);
142
+ export const compareColumns = (width: number) => fitPerpColumns(COMPARE_COLUMNS, COMPARE_DROPS, width);
143
+ export const rankingColumns = (width: number) => fitPerpColumns(RANKING_COLUMNS, ["openInterestUsd", "venue"], width);
144
+ export const perpColumnLabel = (id: PerpColumnId) => PERP_COLUMNS[id].label;
145
+
146
+ const SIGNED: Partial<Record<PerpColumnId, number>> = { fundingRate8h: 4, fundingApr: 2, premium: 3, oiChange24h: 2, priceChange24h: 2 };
147
+ /** A board or comparison cell: the text the report prints, the number CSV exports, and the sign's tone. */
148
+ export function perpCellText(row: PerpBoardRow, id: PerpColumnId): string {
149
+ if (id === "market") return marketLabel(row);
150
+ if (id === "venue") return venueLabel(row);
151
+ if (id === "symbol") return row.symbol;
152
+ if (id === "markPrice") return markText(row);
153
+ if (id === "fundingRate") return fundingPerInterval(row);
154
+ if (id === "openInterestUsd") return compact(row.openInterestUsd);
155
+ if (id === "observedAt") return observed(row.observedAt);
156
+ if (id === "longShare") return share(row.longShortRatio?.longShare);
157
+ const digits = SIGNED[id];
158
+ return digits === undefined ? "--" : percent(row[id as keyof PerpBoardRow] as number | null, digits);
159
+ }
160
+ export function perpCell(row: PerpBoardRow, id: PerpColumnId, colors: Pick<ThemeColors, "positive" | "negative" | "text" | "textBright" | "textMuted" | "textDim">): DataTableCell {
161
+ const text = perpCellText(row, id);
162
+ if (id === "market") return { text, color: row.stale ? colors.textDim : colors.textBright };
163
+ if (id === "venue" || id === "symbol") return { text, color: colors.textMuted };
164
+ if (id === "observedAt") return { text, value: row.observedAt, color: row.stale ? colors.textMuted : colors.textDim };
165
+ if (id === "markPrice") return { text, value: row.markPrice };
166
+ if (id === "openInterestUsd") return { text, value: row.openInterestUsd };
167
+ // A share, not a signed move: no tone, dimmed like a stale market when its bucket is old.
168
+ if (id === "longShare") {
169
+ const reading = row.longShortRatio;
170
+ return { text, value: reading ? reading.longShare * 100 : null, color: !reading ? colors.textMuted : reading.stale ? colors.textDim : colors.text };
171
+ }
172
+ const value = id === "fundingRate" ? row.fundingRate : row[id as keyof PerpBoardRow] as number | null;
173
+ return { text, value: value == null ? null : value * 100, color: value == null || value === 0 ? colors.textMuted : value > 0 ? colors.positive : colors.negative };
174
+ }
175
+
176
+ /** The five ranked lists, each with the figure it ranks by. */
177
+ export type RankingKey = Exclude<keyof PerpRankingsPayload, "status" | "asOf" | "access" | "locked">;
178
+ export const RANKINGS: Array<{ key: RankingKey; label: string; header: string; field: "fundingRate8h" | "oiChange24h" | "premium" | "closedMarketPremium"; digits: number }> = [
179
+ { key: "fundingPositive", label: "Highest Funding 8h", header: "Funding 8h %", field: "fundingRate8h", digits: 4 },
180
+ { key: "fundingNegative", label: "Lowest Funding 8h", header: "Funding 8h %", field: "fundingRate8h", digits: 4 },
181
+ { key: "oiSurges", label: "OI Surges 24h", header: "OI 24h %", field: "oiChange24h", digits: 2 },
182
+ { key: "premiumDislocations", label: "Premium Dislocations", header: "Premium %", field: "premium", digits: 3 },
183
+ { key: "closedMarketDislocations", label: "Closed-Market Dislocations", header: "Closed-market premium %", field: "closedMarketPremium", digits: 3 },
184
+ ];
185
+ /** Rows per list: the preview's three, or the first ten of the full ranking. */
186
+ const RANKING_ROWS = 10;
187
+ export const rankingRows = (data: PerpRankingsPayload | null | undefined, key: RankingKey) => (data?.[key] ?? []).slice(0, RANKING_ROWS);
188
+
189
+ /**
190
+ * Highest minus lowest 8h funding across the shown contracts, in percentage points. Each end is named by its
191
+ * venue, with the contract symbol when that venue lists more than one.
192
+ */
193
+ export function fundingSpread(rows: readonly PerpBoardRow[]) {
194
+ const rated = rows.filter((row) => row.fundingRate8h != null);
195
+ if (rated.length < 2) return null;
196
+ const high = rated.reduce((best, row) => row.fundingRate8h! > best.fundingRate8h! ? row : best);
197
+ const low = rated.reduce((best, row) => row.fundingRate8h! < best.fundingRate8h! ? row : best);
198
+ const name = (row: PerpBoardRow) => rated.filter((other) => other.venue === row.venue).length > 1 ? `${venueLabel(row)} ${row.symbol}` : venueLabel(row);
199
+ return { value: high.fundingRate8h! - low.fundingRate8h!, high, low, text: points(high.fundingRate8h! - low.fundingRate8h!), detail: `${name(high)} over ${name(low)}` };
200
+ }
34
201
 
35
202
 
203
+ /** Series read in percent, whose changes are percentage points. */
204
+ export const inPoints = (metric: HistoryMetric) => metric === "funding" || metric === "premium" || metric === "long-short";
36
205
  export function historyColumns(metric: HistoryMetric, currency: string, hasCandles = true): DataTableColumn[] {
37
206
  return [
38
207
  { id: "time", label: "Time (UTC)", width: 19, flexGrow: 1, align: "left" },
39
- { id: "value", label: metric === "funding" ? "Funding 8h %" : metric === "premium" ? "Oracle premium %" : metric === "oi" ? "OI USD" : `${hasCandles ? "Close" : "Mark"} ${currency}`, width: 17, align: "right" },
40
- { id: "change", label: metric === "funding" || metric === "premium" ? "Change pp" : `Change ${metric === "oi" ? "USD" : currency}`, width: 17, align: "right" },
208
+ { id: "value", label: metric === "funding" ? "Funding 8h %" : metric === "premium" ? "Oracle premium %" : metric === "oi" ? "OI USD" : metric === "long-short" ? "Long %" : `${hasCandles ? "Close" : "Mark"} ${currency}`, width: 17, align: "right" },
209
+ { id: "change", label: inPoints(metric) ? "Change pp" : `Change ${metric === "oi" ? "USD" : currency}`, width: 17, align: "right" },
210
+ ...(metric === "long-short" ? [{ id: "short", label: "Short %", width: 9, align: "right" as const }, { id: "ratio", label: "Ratio", width: 7, align: "right" as const }] : []),
41
211
  { id: "basis", label: "Observation basis", width: 25, align: "left" },
42
212
  ];
43
213
  }
44
- export const historyChange = (value: number | null | undefined, metric: HistoryMetric) => value == null ? "--" : metric === "funding" || metric === "premium" ? `${value > 0 ? "+" : ""}${(value * 100).toFixed(metric === "funding" ? 4 : 3)}pp` : historyValue(value, metric);
214
+ export const historyChange = (value: number | null | undefined, metric: HistoryMetric) => value == null ? "--"
215
+ : inPoints(metric) ? `${value > 0 ? "+" : ""}${(value * 100).toFixed(metric === "funding" ? 4 : metric === "long-short" ? 2 : 3)}pp` : historyValue(value, metric);
45
216
  export function historyCaption(data: PerpHistoryPayload | null | undefined, metric: HistoryMetric) {
217
+ if (metric === "long-short") {
218
+ const points = data?.longShortRatio ?? [];
219
+ return `${longShortDefinition(points.at(-1)?.definition)} net long${points.length && points.every((point) => point.derived) ? ", from ratio" : ""}`;
220
+ }
46
221
  return metric === "funding" ? data?.funding.length ? "Paid funding / 8h" : "Observed funding / 8h"
47
222
  : metric === "price" && data?.candles.length ? "Hourly trade closes" : metric === "oi" ? "Collected open interest" : metric === "premium" ? "Collected oracle premium" : "Collected mark";
48
223
  }
49
224
 
50
- export function historyCell(row: ReturnType<typeof historyRows>[number], column: string, metric: HistoryMetric, colors?: Pick<ThemeColors, "positive" | "negative" | "textMuted" | "textDim" | "textBright">): DataTableCell {
225
+ export function historyCell(row: HistoryRow, column: string, metric: HistoryMetric, colors?: Pick<ThemeColors, "positive" | "negative" | "text" | "textMuted" | "textDim" | "textBright">): DataTableCell {
51
226
  // The header says UTC; each row only needs the day and the hour.
52
227
  if (column === "time") return { text: row.time.slice(0, 16).replace("T", " "), value: row.time };
53
228
  if (column === "basis") return { text: row.basis, color: colors?.textDim };
229
+ if (column === "short") return { text: share(row.shortShare, 2), value: row.shortShare == null ? null : row.shortShare * 100, color: colors?.text };
230
+ if (column === "ratio") return { text: ratioText(row.ratio), value: row.ratio ?? null, color: colors?.text };
54
231
  const value = column === "change" ? row.change : row.value;
55
232
  return { text: column === "change" ? historyChange(value, metric) : historyValue(value, metric),
56
- value: value === null ? null : value * (metric === "funding" || metric === "premium" ? 100 : 1),
233
+ value: value === null ? null : value * (inPoints(metric) ? 100 : 1),
57
234
  color: !colors ? undefined : column === "change" ? value == null || value === 0 ? colors.textMuted : value > 0 ? colors.positive : colors.negative : colors.textBright };
58
235
  }