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
@@ -2,7 +2,8 @@ import { MarketDataCoordinator } from "../../../market-data/coordinator";
2
2
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
3
3
  import { resolveHeadlessInstrument } from "../shared/headless-market-data";
4
4
  import { loadPeBandInputs } from "./client";
5
- import { formatPerShare, projectPeBand } from "./model";
5
+ import { formatPerShare, fxPairQuote, projectPeBand } from "./model";
6
+ import { DAILY_CLOSES } from "../shared/report-freshness";
6
7
 
7
8
  const multiple = (value: unknown) => typeof value === "number" ? `${value.toFixed(1)}x` : "--";
8
9
  const amount = (value: unknown) => typeof value === "number" ? formatPerShare(value) : "--";
@@ -10,18 +11,29 @@ const percent = (value: unknown) => typeof value === "number" ? `${(value * 100)
10
11
 
11
12
  export const peBandHeadless: HeadlessPaneDefinition<"bundle"> = {
12
13
  shape: "bundle", argument: { kind: "ticker", description: "Ticker" },
14
+ freshness: { ...DAILY_CLOSES, basis: "daily closes and reported EPS" },
13
15
  describe: (args) => `PEB ${args.symbols[0] ?? ""}`,
14
- discovery: { screenshotReadiness: "live-dom", limitations: ["Trailing EPS in the listing's own currency; weekly closes."] },
16
+ discovery: { screenshotReadiness: "live-dom", limitations: [
17
+ "Weekly closes. EPS reported in another currency than a dollar price is converted at daily FX closes; other currency pairs are not computed.",
18
+ ] },
15
19
  options: [
16
20
  { key: "lookbackYears", type: "integer", minimum: 0, maximum: 30, defaultValue: 10, description: "Years of history; 0 for all on record" },
17
21
  ],
18
22
  async load(args, ctx) {
19
23
  const instrument = await resolveHeadlessInstrument(ctx, args.symbols[0]!);
20
- const inputs = await loadPeBandInputs({ instrument, signal: ctx.signal }, ctx.marketData, new MarketDataCoordinator(ctx.marketData));
24
+ const inputs = await loadPeBandInputs({ instrument, signal: ctx.signal }, ctx.marketData, new MarketDataCoordinator(ctx.marketData), ctx.apiClient);
21
25
  ctx.signal.throwIfAborted();
22
26
  const lookbackYears = Number(args.options.lookbackYears ?? 10);
23
- const model = projectPeBand(inputs.financials, inputs.history, { symbol: instrument.symbol, lookbackYears });
27
+ const model = projectPeBand(inputs.financials, inputs.history, { symbol: instrument.symbol, lookbackYears, reports: inputs.reports,
28
+ fx: inputs.fx, fxError: inputs.fxError });
24
29
  const current = model.current;
30
+ const conversion = model.conversion;
31
+ const pairQuote = (rate: number) => {
32
+ const quote = fxPairQuote(conversion!.currency, rate);
33
+ return `${quote.pair} ${quote.value.toPrecision(5)}`;
34
+ };
35
+ const reported = current?.step?.eps != null && conversion && current.step.currency === conversion.currency
36
+ ? `, from ${conversion.currency} ${amount(current.step.eps)} at ${pairQuote(conversion.latest.rate)} (${conversion.latest.date} close)` : "";
25
37
  return {
26
38
  sections: [
27
39
  { title: "P/E band", columns: [{ key: "label", header: "" }, { key: "value", header: "Value" }], rows: [
@@ -30,20 +42,26 @@ export const peBandHeadless: HeadlessPaneDefinition<"bundle"> = {
30
42
  { label: "Ranked over", value: model.sample ? `${model.sample.weeks} weeks since ${model.sample.start.toISOString().slice(0, 10)}` : "--" },
31
43
  { label: "P/E low, median, high", value: model.range ? `${multiple(model.range.min)}, ${multiple(model.range.median)}, ${multiple(model.range.max)}` : "--" },
32
44
  { label: "Price", value: `${amount(current?.price)} ${model.currency ?? ""}`.trim() },
33
- { label: "Trailing EPS", value: current?.step ? `${amount(current.eps)} (${current.step.basis === "annual" ? "FY" : "TTM"} ${current.step.periodEnd})` : "--" },
45
+ { label: "Trailing EPS", value: current?.step
46
+ ? `${amount(current.eps)} (${current.step.basis === "annual" ? "FY" : "TTM"} ${current.step.periodEnd})${reported}` : "--" },
34
47
  ] },
35
48
  { title: "Price at each multiple", columns: [{ key: "multiple", header: "Multiple", format: multiple }, { key: "price", header: "Price", format: amount }],
36
49
  rows: model.multiples.map((value) => ({ multiple: value, price: current?.eps != null && current.eps > 0 ? value * current.eps : null })) },
37
50
  { title: "Trailing EPS", columns: [{ key: "periodEnd", header: "Period end" }, { key: "basis", header: "Basis" },
38
- { key: "knownAt", header: "Known" }, { key: "eps", header: "EPS", format: amount }, { key: "yoy", header: "YoY", format: percent },
39
- { key: "price", header: "Price", format: amount }, { key: "pe", header: "P/E", format: multiple }],
51
+ { key: "knownAt", header: "Known" }, { key: "eps", header: conversion ? "EPS reported" : "EPS", format: amount },
52
+ ...(conversion ? [{ key: "currency", header: "Currency" }, { key: "fx", header: "FX close" }] : []),
53
+ { key: "yoy", header: "YoY", format: percent }, { key: "price", header: "Price", format: amount }, { key: "pe", header: "P/E", format: multiple }],
40
54
  rows: model.rows.map((row) => ({ periodEnd: row.periodEnd, basis: row.basis === "annual" ? "FY" : "TTM",
41
- knownAt: row.dated ? row.knownAt.toISOString().slice(0, 10) : "period end", eps: row.eps, yoy: row.yoy, price: row.price, pe: row.pe })) },
55
+ knownAt: row.dated ? row.knownAt.toISOString().slice(0, 10) : "period end", eps: row.eps,
56
+ ...(conversion ? { currency: row.currency, fx: row.fx ? `${pairQuote(row.fx.rate)} (${row.fx.date})` : row.currency === model.currency ? "" : "--" } : {}),
57
+ yoy: row.yoy, price: row.price, pe: row.pe })) },
42
58
  ],
43
59
  complete: !inputs.stale && !inputs.error && !inputs.historyError && !model.error,
44
60
  unavailableSymbols: current?.pe != null ? [] : [instrument.symbol],
45
61
  errors: [inputs.error, inputs.historyError, model.error, model.notice].filter((value): value is string => !!value),
46
62
  metadata: { currency: model.currency, lookbackYears,
63
+ epsConversion: conversion ? { from: conversion.currency, to: model.currency, usdPerUnit: conversion.latest.rate, latestClose: conversion.latest.date,
64
+ basis: "daily FX close on or before each date: a week's bar date for the band, the publication date for each table row, the latest completed close for the current P/E" } : null,
47
65
  sampleStart: model.sample?.start.toISOString().slice(0, 10) ?? null, sampleWeeks: model.sample?.weeks ?? 0, multiples: model.multiples, undatedEpsPeriods: model.undated, unavailableTtmSums: model.unavailable,
48
66
  stale: inputs.stale, fetchedAt: inputs.fetchedAt, methodology: "docs/research-data.md#pe-band" },
49
67
  };
@@ -1,8 +1,12 @@
1
+ import { fxLegForCurrency } from "../../../market-data/coordinator/fx-legs";
2
+ import { fxCloseOnOrBefore, type FxCloses } from "../../../market-data/fx-closes";
1
3
  import { extractFundamentalSeries } from "../../../time-series/fundamentals";
2
4
  import type { SecuritySeriesSource, TimeSeriesPoint } from "../../../time-series/types";
3
5
  import { createValuationCurrencyContext } from "../../../time-series/valuation-currency";
4
6
  import type { FinancialStatement, PricePoint, TickerFinancials } from "../../../types/financials";
7
+ import { resolveCurrencyUnit } from "../../../utils/currency-units";
5
8
  import { areNearbyFinancialPeriodEnds } from "../../../utils/financial-statements";
9
+ import { datedByReport, type ReportDate } from "./report-dates";
6
10
 
7
11
  const DAY_MS = 86_400_000;
8
12
  const YEAR_MS = 365.25 * DAY_MS;
@@ -23,7 +27,7 @@ export interface EpsStep {
23
27
  /** Four reported quarters summed, or a reported fiscal year. */
24
28
  basis: "ttm" | "annual";
25
29
  knownAt: Date;
26
- /** False when no publication date is on record and `knownAt` is the period end. */
30
+ /** False when no publication date or report is on record and `knownAt` is the period end. */
27
31
  dated: boolean;
28
32
  /** Statement currency units; null when unavailable (a declared gap or an incomplete run of quarters). */
29
33
  eps: number | null;
@@ -42,8 +46,24 @@ export interface PeBandRow extends EpsStep {
42
46
  /** The close of the week the figure became known, in price units. */
43
47
  price: number | null;
44
48
  pe: number | null;
45
- /** Change from the figure a year earlier, when both are positive. */
49
+ /** Change from the figure a year earlier in the same currency, when both are positive. */
46
50
  yoy: number | null;
51
+ /** The daily FX close the figure was converted at, when it is in another currency than the price. */
52
+ fx: FxClose | null;
53
+ }
54
+
55
+ /** One completed daily FX close: USD per unit of the statement currency. */
56
+ interface FxClose {
57
+ date: string;
58
+ rate: number;
59
+ }
60
+
61
+ /** EPS reported in another currency than a dollar price, converted at daily FX closes. */
62
+ interface EpsConversion {
63
+ /** The statement currency of today's EPS. */
64
+ currency: string;
65
+ /** The latest completed close, which converts today's EPS. */
66
+ latest: FxClose;
47
67
  }
48
68
 
49
69
  export interface PeBandModel {
@@ -64,10 +84,12 @@ export interface PeBandModel {
64
84
  * the line just above today's P/E when that is higher.
65
85
  */
66
86
  bandCeiling: number | null;
67
- /** Figures in the window dated by period end because no publication date is on record. */
87
+ /** Figures in the window dated by period end because neither a publication date nor a report is on record. */
68
88
  undated: number;
69
89
  /** Trailing sums in the window that are unavailable (a quarter's EPS is withheld or not reported). */
70
90
  unavailable: number;
91
+ /** Set when today's EPS is in another currency than the dollar price. */
92
+ conversion: EpsConversion | null;
71
93
  /** Blocks the pane: nothing can be priced. */
72
94
  error: string | null;
73
95
  /** The pane still draws, but without bands. */
@@ -98,10 +120,13 @@ function epsSeries(financials: TickerFinancials, period: "ttm" | "annual"): Time
98
120
  * by the shared statement series, which keeps declared gaps and broken runs
99
121
  * of quarters unavailable. A reported fiscal year is itself a trailing
100
122
  * twelve-month figure; at a fiscal year end it replaces the quarterly sum.
123
+ * A figure with no publication date of its own is dated by the company's
124
+ * report of that quarter, when `reports` has it.
101
125
  */
102
- export function trailingEpsSteps(financials: TickerFinancials): EpsStep[] {
103
- const adjusted = { ...financials, quarterlyStatements: financials.quarterlyStatements.map(knownWhenFiled),
104
- annualStatements: financials.annualStatements.map(knownWhenFiled) };
126
+ export function trailingEpsSteps(financials: TickerFinancials, reports: readonly ReportDate[] = []): EpsStep[] {
127
+ const known = (row: FinancialStatement) => datedByReport(knownWhenFiled(row), reports);
128
+ const adjusted = { ...financials, quarterlyStatements: financials.quarterlyStatements.map(known),
129
+ annualStatements: financials.annualStatements.map(known) };
105
130
  const toStep = (basis: EpsStep["basis"]) => (point: TimeSeriesPoint): EpsStep => ({
106
131
  periodEnd: point.observedAt.toISOString().slice(0, 10), basis, knownAt: point.date, dated: point.availableAt !== undefined,
107
132
  eps: point.value != null && Number.isFinite(point.value) ? point.value : null, currency: point.provenance?.currency ?? null,
@@ -127,6 +152,37 @@ export function perShareDigits(value: number): number {
127
152
 
128
153
  export const formatPerShare = (value: number) => value.toFixed(perShareDigits(value));
129
154
 
155
+ /**
156
+ * A rate in the pair's market convention: "USD/TWD 31.99" (Taiwan dollars per
157
+ * dollar) or "EUR/USD 1.17" (dollars per euro), from USD per unit.
158
+ */
159
+ export function fxPairQuote(currency: string, usdPerUnit: number): { pair: string; value: number } {
160
+ const perDollar = fxLegForCurrency(currency)?.invert ?? true;
161
+ return perDollar ? { pair: `USD/${currency}`, value: 1 / usdPerUnit } : { pair: `${currency}/USD`, value: usdPerUnit };
162
+ }
163
+
164
+ /** The statement unit of an EPS currency when it is a whole currency other than USD with a daily pair, else null. */
165
+ function fxCurrency(code: string | null | undefined): string | null {
166
+ const unit = resolveCurrencyUnit(code);
167
+ return unit.divisor === 1 && unit.currency !== "USD" && fxLegForCurrency(unit.currency) ? unit.currency : null;
168
+ }
169
+
170
+ const isDollarPrice = (currency: string | null | undefined) => {
171
+ const unit = resolveCurrencyUnit(currency);
172
+ return unit.currency === "USD" && unit.divisor === 1;
173
+ };
174
+
175
+ /**
176
+ * The currencies whose daily FX closes a dollar-priced listing needs: those
177
+ * its EPS is reported in, other than USD. Rows without a declared currency
178
+ * are never converted.
179
+ */
180
+ export function epsFxCurrencies(financials: TickerFinancials): string[] {
181
+ if (!isDollarPrice(financials.quote?.currency)) return [];
182
+ const rows = [...financials.annualStatements, ...financials.quarterlyStatements].filter((row) => typeof row.eps === "number");
183
+ return [...new Set(rows.flatMap((row) => fxCurrency(row.currency?.trim()) ?? []))];
184
+ }
185
+
130
186
  function quantile(sorted: readonly number[], q: number): number {
131
187
  const position = (sorted.length - 1) * q;
132
188
  const low = Math.floor(position), high = Math.ceil(position);
@@ -193,30 +249,47 @@ export function stepAt(steps: readonly EpsStep[], time: number): EpsStep | null
193
249
  export function projectPeBand(
194
250
  financials: TickerFinancials | null,
195
251
  history: readonly PricePoint[],
196
- options: { symbol: string; lookbackYears: number; now?: number },
252
+ options: {
253
+ symbol: string; lookbackYears: number; now?: number; reports?: readonly ReportDate[];
254
+ /** Daily FX closes by statement currency, for a dollar price against EPS in another currency. */
255
+ fx?: ReadonlyMap<string, FxCloses>;
256
+ /** Why a needed FX history is missing, said when it leaves today's EPS unconverted. */
257
+ fxError?: string | null;
258
+ },
197
259
  ): PeBandModel {
198
260
  const currency = financials?.quote?.currency ?? null;
199
261
  const empty: PeBandModel = { symbol: options.symbol, currency, weeks: [], rows: [], multiples: [], current: null, range: null, sample: null, bandCeiling: null,
200
- undated: 0, unavailable: 0, error: null, notice: null };
262
+ undated: 0, unavailable: 0, conversion: null, error: null, notice: null };
201
263
  if (!financials) return { ...empty, error: "Fundamentals unavailable." };
202
- const allSteps = trailingEpsSteps(financials);
264
+ const allSteps = trailingEpsSteps(financials, options.reports);
203
265
  const latest = allSteps.findLast((step) => step.eps != null);
204
266
  if (!latest) return { ...empty, error: "No trailing EPS on record." };
205
267
  const units = createValuationCurrencyContext(financials);
206
268
  // Price units per statement unit: 1 for the same currency, 100 for a pence price against pound statements.
207
- const scale = (step: EpsStep) => {
269
+ const unitScale = (step: EpsStep) => {
208
270
  const factor = units.priceInStatementUnits({ date: step.periodEnd, currency: step.currency ?? undefined }, 1);
209
271
  return factor ? 1 / factor : null;
210
272
  };
211
- // ADRs and cross-listings report in one currency and trade in another; no FX rate is applied.
212
- if (scale(latest) == null) {
213
- return { ...empty, error: currency && latest.currency
214
- ? `EPS is reported in ${latest.currency} and the price is in ${currency}.` : "The price or EPS currency is unknown." };
273
+ // ADRs report in one currency and trade in dollars: their EPS converts at a daily FX close.
274
+ const fxFor = (step: EpsStep) => {
275
+ const code = isDollarPrice(currency) ? fxCurrency(step.currency) : null;
276
+ return code ? options.fx?.get(code) ?? null : null;
277
+ };
278
+ if (unitScale(latest) == null && !fxFor(latest)) {
279
+ const fxCode = isDollarPrice(currency) ? fxCurrency(latest.currency) : null;
280
+ return { ...empty, error: !currency || !latest.currency ? "The price or EPS currency is unknown."
281
+ : `EPS is reported in ${latest.currency} and the price is in ${currency}${fxCode && options.fxError ? `; ${options.fxError}` : ""}.` };
215
282
  }
216
- const priced = (step: EpsStep | null) => {
217
- const factor = step?.eps != null ? scale(step) : null;
218
- return factor == null ? null : step!.eps! * factor;
283
+ /** The EPS in price units on `date` (YYYY-MM-DD), with the FX close used when it was converted. */
284
+ const priced = (step: EpsStep | null, date: string): { eps: number; fx: FxClose | null } | null => {
285
+ if (step?.eps == null) return null;
286
+ const factor = unitScale(step);
287
+ if (factor != null) return { eps: step.eps * factor, fx: null };
288
+ const history = fxFor(step);
289
+ const close = history ? fxCloseOnOrBefore(history, date) : null;
290
+ return close ? { eps: step.eps * close.rate, fx: close } : null;
219
291
  };
292
+ const day = (time: Date | number) => new Date(time).toISOString().slice(0, 10);
220
293
 
221
294
  const now = options.now ?? Date.now();
222
295
  const firstKnown = allSteps.find((step) => step.eps != null)!.knownAt.getTime();
@@ -226,7 +299,9 @@ export function projectPeBand(
226
299
  .filter((point) => Number.isFinite(point.date.getTime()) && Number.isFinite(point.close) && point.close > 0)
227
300
  .sort((a, b) => a.date.getTime() - b.date.getTime());
228
301
  const weeks = sorted.filter((point) => point.date.getTime() >= start).map((point): PeBandWeek => {
229
- const eps = priced(stepAt(allSteps, point.date.getTime()));
302
+ // Converted at the FX close of the bar's date, the same date that picks the EPS in force, so each week's multiple
303
+ // moves with the price and the currency together rather than with the rate of an old publication date.
304
+ const eps = priced(stepAt(allSteps, point.date.getTime()), day(point.date))?.eps ?? null;
230
305
  const positive = eps != null && eps > 0 ? eps : null;
231
306
  return { date: point.date, close: point.close, eps: positive, pe: positive ? point.close / positive : null };
232
307
  });
@@ -235,10 +310,12 @@ export function projectPeBand(
235
310
  const rows = steps.filter((step) => step.eps != null).map((step): PeBandRow => {
236
311
  const time = step.knownAt.getTime();
237
312
  const price = sorted.findLast((point) => point.date.getTime() <= time)?.close ?? null;
238
- const eps = priced(step);
239
- const prior = allSteps.find((other) => other.eps != null
313
+ // Converted at the FX close of the day it became known.
314
+ const converted = priced(step, day(step.knownAt));
315
+ const eps = converted?.eps ?? null;
316
+ const prior = allSteps.find((other) => other.eps != null && other.currency === step.currency
240
317
  && Math.abs(Date.parse(step.periodEnd) - Date.parse(other.periodEnd) - 365 * DAY_MS) <= 20 * DAY_MS);
241
- return { ...step, price, pe: price != null && eps != null && eps > 0 ? price / eps : null,
318
+ return { ...step, price, pe: price != null && eps != null && eps > 0 ? price / eps : null, fx: converted?.fx ?? null,
242
319
  yoy: step.eps != null && prior?.eps != null && step.eps > 0 && prior.eps > 0 ? step.eps / prior.eps - 1 : null };
243
320
  }).reverse();
244
321
 
@@ -248,13 +325,16 @@ export function projectPeBand(
248
325
  const quotePrice = financials.quote?.price;
249
326
  const price = quotePrice != null && Number.isFinite(quotePrice) && quotePrice > 0 ? quotePrice : sorted.at(-1)?.close ?? null;
250
327
  const currentStep = stepAt(allSteps, now);
251
- const currentEps = priced(currentStep);
328
+ // Today's EPS at the latest completed FX close.
329
+ const currentEps = priced(currentStep, day(now))?.eps ?? null;
330
+ const latestFx = unitScale(latest) == null ? fxFor(latest) : null;
252
331
  const currentPe = price != null && currentEps != null && currentEps > 0 ? price / currentEps : null;
253
332
  const multiples = chooseMultiples(pes, currentPe);
254
333
  const above = currentPe == null ? undefined : multiples.find((multiple) => multiple >= currentPe);
255
334
  const highestClose = weeks.reduce((max, week) => Math.max(max, week.close), 0);
256
335
  return {
257
336
  ...empty, weeks, rows, multiples,
337
+ conversion: latestFx ? { currency: latestFx.currency, latest: fxCloseOnOrBefore(latestFx, day(now))! } : null,
258
338
  sample: ranked.length ? { start: ranked[0]!.date, weeks: ranked.length } : null,
259
339
  bandCeiling: highestClose > 0 ? Math.max(highestClose * 1.5, above != null && currentEps != null ? above * currentEps : 0) : null,
260
340
  current: price == null ? null : { price, eps: currentEps, pe: currentPe, step: currentStep,
@@ -14,7 +14,7 @@ import { Box } from "../../../ui";
14
14
  import { formatCurrency, formatPercentileRank } from "../../../utils/format";
15
15
  import { useAssetData } from "../../runtime";
16
16
  import { loadPeBandInputs } from "./client";
17
- import { formatPerShare, perShareDigits, projectPeBand, type PeBandModel, type PeBandRow } from "./model";
17
+ import { formatPerShare, fxPairQuote, perShareDigits, projectPeBand, type PeBandModel, type PeBandRow } from "./model";
18
18
 
19
19
  export const LOOKBACK_OPTIONS = [{ value: "5", label: "5Y" }, { value: "10", label: "10Y" }, { value: "0", label: "Max" }];
20
20
 
@@ -28,14 +28,18 @@ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "
28
28
  const periodLabel = (row: Pick<PeBandRow, "basis" | "periodEnd">) =>
29
29
  `${row.basis === "annual" ? "FY" : "TTM"} ${MONTHS[Number(row.periodEnd.slice(5, 7)) - 1]} ${row.periodEnd.slice(0, 4)}`;
30
30
 
31
- const COLUMNS: DataTableColumn[] = [
32
- { id: "period", label: "Period", width: 13, align: "left" },
33
- { id: "known", label: "Known", width: 11, align: "left" },
34
- { id: "eps", label: "EPS", width: 9, align: "right" },
35
- { id: "yoy", label: "YoY", width: 8, align: "right" },
36
- { id: "price", label: "Price", width: 10, align: "right" },
37
- { id: "pe", label: "P/E", width: 7, align: "right" },
38
- ];
31
+ /** With EPS converted from another currency, the table keeps the reported figure and adds the FX close it was converted at. */
32
+ function tableColumns(conversion: PeBandModel["conversion"]): DataTableColumn[] {
33
+ return [
34
+ { id: "period", label: "Period", width: 13, align: "left" },
35
+ { id: "known", label: "Known", width: 11, align: "left" },
36
+ { id: "eps", label: conversion ? `EPS ${conversion.currency}` : "EPS", width: 9, align: "right" },
37
+ ...(conversion ? [{ id: "fx", label: fxPairQuote(conversion.currency, conversion.latest.rate).pair, width: 9, align: "right" as const }] : []),
38
+ { id: "yoy", label: "YoY", width: 8, align: "right" },
39
+ { id: "price", label: "Price", width: 10, align: "right" },
40
+ { id: "pe", label: "P/E", width: 7, align: "right" },
41
+ ];
42
+ }
39
43
 
40
44
  const rowKey = (row: PeBandRow) => `${row.basis}:${row.periodEnd}`;
41
45
 
@@ -49,6 +53,10 @@ function renderCell(row: PeBandRow, column: DataTableColumn, colors: ReturnType<
49
53
  color: row.eps != null && row.eps <= 0 ? colors.negative : undefined };
50
54
  case "yoy": return { text: pct(row.yoy), value: row.yoy == null ? null : row.yoy * 100,
51
55
  color: row.yoy == null || Math.abs(row.yoy) < 0.0005 ? undefined : row.yoy > 0 ? colors.positive : colors.negative };
56
+ case "fx": {
57
+ const quote = row.fx && row.currency ? fxPairQuote(row.currency, row.fx.rate).value : null;
58
+ return { text: quote == null ? "--" : quote.toPrecision(5), value: quote };
59
+ }
52
60
  case "price": return { text: row.price == null ? "--" : formatPerShare(row.price), value: row.price };
53
61
  default: return { text: multiple(row.pe), value: row.pe };
54
62
  }
@@ -94,9 +102,12 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
94
102
 
95
103
  const lookbackYears = Number(lookback);
96
104
  const model = useMemo(() => inputs.data
97
- ? projectPeBand(inputs.data.financials, inputs.data.history, { symbol: symbol ?? "", lookbackYears: Number.isFinite(lookbackYears) ? lookbackYears : 10 })
105
+ ? projectPeBand(inputs.data.financials, inputs.data.history, { symbol: symbol ?? "", lookbackYears: Number.isFinite(lookbackYears) ? lookbackYears : 10,
106
+ reports: inputs.data.reports, fx: inputs.data.fx, fxError: inputs.data.fxError })
98
107
  : null, [inputs.data, lookbackYears, symbol]);
99
108
  const rows = model?.rows ?? [];
109
+ const conversion = model?.conversion ?? null;
110
+ const columns = useMemo(() => tableColumns(conversion), [conversion?.currency, conversion?.latest.rate]);
100
111
  const series = useMemo(() => model && !model.error ? chartSeries(model, colors) : [], [model, colors]);
101
112
  const currency = model?.currency ?? undefined;
102
113
  const firstWeek = model?.weeks[0]?.date;
@@ -116,7 +127,8 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
116
127
  const unavailableNotice = model?.unavailable
117
128
  ? `${model.unavailable} trailing EPS sum${model.unavailable === 1 ? " is" : "s are"} unavailable because a quarter's EPS is not reported; the previous figure stays in force.` : null;
118
129
  usePaneNoticeFooter({ registrationId: "pe-band-notices", focused, notices: [...new Set([identityError, inputs.error, inputs.data?.historyError,
119
- model?.notice, unavailableNotice, undatedNotice].filter((value): value is string => !!value))] });
130
+ model?.notice, model && !model.error && inputs.data?.fxError ? `EPS in another currency is not converted: ${inputs.data.fxError}.` : null,
131
+ unavailableNotice, undatedNotice].filter((value): value is string => !!value))] });
120
132
  usePaneFooter("pe-band", () => ({ info: [
121
133
  ...(inputs.loading ? [{ id: "loading", parts: [{ text: "loading statements", tone: "muted" as const }] }] : []),
122
134
  ...(inputs.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
@@ -127,15 +139,19 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
127
139
  if (!model?.current || model.error) return [];
128
140
  const { current, range } = model;
129
141
  const step = current.step;
142
+ // Converted EPS says what it was converted from, and at which close.
143
+ const pair = conversion ? fxPairQuote(conversion.currency, conversion.latest.rate) : null;
144
+ const converted = step?.eps != null && conversion && pair && step.currency === conversion.currency
145
+ ? `, from ${conversion.currency} ${formatPerShare(step.eps)} at ${pair.pair} ${pair.value.toPrecision(5)}` : "";
130
146
  return [
131
147
  { id: "pe", label: "P/E", value: multiple(current.pe),
132
148
  detail: current.pe != null ? formatPercentileRank(current.percentile, windowLabel) : current.eps != null ? "EPS not positive" : "EPS unavailable" },
133
149
  { id: "median", label: "Median P/E", value: multiple(range?.median), detail: range ? `${multiple(range.min)} to ${multiple(range.max)}` : undefined },
134
150
  { id: "eps", label: "EPS", value: current.eps == null ? "--" : formatCurrency(current.eps, currency, perShareDigits(current.eps)),
135
- detail: step ? periodLabel(step) : undefined },
151
+ detail: step ? `${periodLabel(step)}${converted}` : undefined },
136
152
  { id: "price", label: "Price", value: formatCurrency(current.price, currency, perShareDigits(current.price)) },
137
153
  ];
138
- }, [model, windowLabel, currency]);
154
+ }, [model, windowLabel, currency, conversion]);
139
155
 
140
156
  const formatPrice = useCallback((value: number) => formatCurrency(value, currency, perShareDigits(value)), [currency]);
141
157
  const formatAxis = useMemo(() => spanAxisFormatter((value, digits) => formatCurrency(value, currency, Math.max(2, digits))), [currency]);
@@ -144,14 +160,16 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
144
160
  if (!symbol) return <EmptyState title="Choose a ticker." />;
145
161
  return <Box width={width} height={height} flexDirection="column" overflow="hidden">
146
162
  <PaneStatusBody subject="P/E band" loading={inputs.loading && !model} error={!model ? inputs.error ?? identityError ?? null : model.error} empty={false}>
147
- {model && !model.error ? <DataTableView<PeBandRow> focused={focused} columns={COLUMNS} items={rows} rootWidth={width} rootHeight={height}
163
+ {model && !model.error ? <DataTableView<PeBandRow> focused={focused} columns={columns} items={rows} rootWidth={width} rootHeight={height}
148
164
  getItemKey={rowKey} emptyStateTitle="No trailing EPS in this window." sortColumnId={null} sortDirection="desc" resetScrollKey={symbol}
149
165
  selection={{ kind: "id", selectedId: selected, getId: rowKey, onChange: (id) => setSelectedId(id) }}
150
166
  selectedTextOverridesCellColor
151
167
  getExportMetadata={() => [["symbol", model.symbol], ["currency", model.currency ?? ""],
152
- ["units", "EPS and price in the listing currency; P/E at the close of the week the EPS became known"]]}
168
+ ["units", conversion
169
+ ? `EPS as reported in ${conversion.currency}; FX close of the day it became known; price in ${model.currency ?? ""}; P/E at that close and that week's close`
170
+ : "EPS and price in the listing currency; P/E at the close of the week the EPS became known"]]}
153
171
  renderCell={(row, column) => renderCell(row, column, colors)}
154
- rootBefore={<ChartTableHeader width={width} height={height} tableRows={rows.length} tableColumns={COLUMNS} query={query} figures={figures}
172
+ rootBefore={<ChartTableHeader width={width} height={height} tableRows={rows.length} tableColumns={columns} query={query} figures={figures}
155
173
  chart={model.weeks.length >= 2 ? { series, formatValue: formatPrice, formatAxisValue: formatAxis, remoteKind: "pe-band", ...link } : null} />} />
156
174
  : null}
157
175
  </PaneStatusBody>
@@ -0,0 +1,78 @@
1
+ import type { EarningsHistoryPayload } from "../../../api-client/earnings";
2
+ import type { FinancialStatement } from "../../../types/financials";
3
+ import { statementFieldAvailability } from "../../../utils/financial-statements";
4
+ import { newYorkToday } from "../earnings/board-model";
5
+
6
+ const DAY_MS = 86_400_000;
7
+ /** A 52/53-week fiscal quarter ends within days of its month end, never a month away. */
8
+ const MONTH_END_TOLERANCE_MS = 10 * DAY_MS;
9
+ /** A company reports after its period ends; later than this the report is another quarter's. */
10
+ const MAX_REPORT_LAG_MS = 180 * DAY_MS;
11
+
12
+ /** One reported quarter, reduced to what dates its EPS. */
13
+ export interface ReportDate {
14
+ /** New York report date, YYYY-MM-DD. */
15
+ date: string;
16
+ /** Fiscal quarter end month, YYYY-MM. */
17
+ fiscalPeriod: string;
18
+ /** Filing time, when on record. */
19
+ reportedAt: string | null;
20
+ }
21
+
22
+ /**
23
+ * The reports that happened, with the quarter they cover. An upcoming report,
24
+ * one whose date is only expected, and one with no fiscal quarter date nothing.
25
+ */
26
+ export function reportDatesFrom(payload: EarningsHistoryPayload): ReportDate[] {
27
+ const asOf = Date.parse(payload.asOf);
28
+ if (!Number.isFinite(asOf)) return [];
29
+ const today = newYorkToday(new Date(asOf));
30
+ return payload.reports.flatMap((report) => (
31
+ report.fiscalPeriod && /^\d{4}-(0[1-9]|1[0-2])$/.test(report.fiscalPeriod) && report.date <= today
32
+ && (report.epsActual != null || report.reportedAt != null)
33
+ ? [{ date: report.date, fiscalPeriod: report.fiscalPeriod, reportedAt: report.reportedAt }] : []
34
+ ));
35
+ }
36
+
37
+ function coversPeriod(periodEnd: number, fiscalPeriod: string): boolean {
38
+ if (new Date(periodEnd).toISOString().slice(0, 7) === fiscalPeriod) return true;
39
+ const monthEnd = Date.UTC(Number(fiscalPeriod.slice(0, 4)), Number(fiscalPeriod.slice(5, 7)), 0);
40
+ return Math.abs(periodEnd - monthEnd) <= MONTH_END_TOLERANCE_MS;
41
+ }
42
+
43
+ /** The filing time when it falls on the report date in both New York and UTC, else the date. */
44
+ function publishedAt(report: ReportDate): string {
45
+ const filed = report.reportedAt ? new Date(report.reportedAt) : null;
46
+ return filed && Number.isFinite(filed.getTime()) && filed.toISOString().slice(0, 10) === report.date
47
+ && newYorkToday(filed) === report.date ? report.reportedAt! : report.date;
48
+ }
49
+
50
+ /**
51
+ * When the quarter ended on `periodEnd` was reported. It is the one report
52
+ * whose fiscal quarter month is the period's own month or sits within days of
53
+ * it, and which comes after the period. No report, or two on different dates,
54
+ * leaves the period undated rather than guessed.
55
+ */
56
+ function reportedWhen(periodEnd: string, reports: readonly ReportDate[]): string | null {
57
+ const end = Date.parse(`${periodEnd}T00:00:00Z`);
58
+ if (!Number.isFinite(end)) return null;
59
+ const matches = reports.filter((report) => {
60
+ const lag = Date.parse(`${report.date}T00:00:00Z`) - end;
61
+ return lag > 0 && lag <= MAX_REPORT_LAG_MS && coversPeriod(end, report.fiscalPeriod);
62
+ });
63
+ if (new Set(matches.map((report) => report.date)).size !== 1) return null;
64
+ return publishedAt(matches.find((report) => report.reportedAt) ?? matches[0]!);
65
+ }
66
+
67
+ /**
68
+ * The statement with its EPS dated by the company's report, only when it has no
69
+ * publication date of its own: a date on record always wins. A fiscal year is
70
+ * dated by the report of its last quarter, which shares its period end. A
71
+ * trailing sum takes the latest of its four quarters from the shared series,
72
+ * so it cannot be known before all four are.
73
+ */
74
+ export function datedByReport(row: FinancialStatement, reports: readonly ReportDate[]): FinancialStatement {
75
+ if (!reports.length || typeof row.eps !== "number" || !Number.isFinite(row.eps) || statementFieldAvailability(row, "eps") !== undefined) return row;
76
+ const known = reportedWhen(row.date, reports);
77
+ return known ? { ...row, fieldAvailability: { ...row.fieldAvailability, eps: known } } : row;
78
+ }
@@ -1,5 +1,6 @@
1
1
  import { apiClient } from "../../../api-client";
2
- import type { PerpBoardPayload, PerpBoardRow, PerpHistoryPayload, PerpHistoryQuery, PerpMarketPayload } from "../../../api-client/perps";
2
+ import type { PerpBoardPayload, PerpBoardQuery, PerpBoardRow, PerpEquityListing, PerpHistoryPayload, PerpHistoryQuery, PerpLongShortPoint, PerpLongShortRatio, PerpMarketPayload,
3
+ PerpRankingsPayload } from "../../../api-client/perps";
3
4
  import { createPluginCache } from "../../../data/plugin-cache";
4
5
  import { cachedCloudResource, loadCloudResource } from "../shared/cloud-resource";
5
6
 
@@ -9,6 +10,12 @@ export const perpsHistoryCache = createPluginCache<PerpHistoryPayload>({ kind: "
9
10
  policy: { staleMs: 60_000, expireMs: 86_400_000 } });
10
11
  const finiteOrNull = (n: unknown) => n === null || typeof n === "number" && Number.isFinite(n);
11
12
  const date = (s: unknown) => typeof s === "string" && Number.isFinite(Date.parse(s));
13
+ const share = (n: unknown) => typeof n === "number" && n >= 0 && n <= 1;
14
+ /** A long/short reading: shares within 0..1, a finite ratio or none. Older servers send no reading at all. */
15
+ const validShares = (value: Pick<PerpLongShortPoint, "longShare" | "shortShare" | "ratio" | "derived" | "definition" | "observedAt">) =>
16
+ share(value.longShare) && share(value.shortShare) && finiteOrNull(value.ratio) && typeof value.derived === "boolean" && typeof value.definition === "string" && date(value.observedAt);
17
+ const validLongShort = (value: PerpLongShortRatio | null | undefined) => value == null
18
+ || typeof value === "object" && validShares(value) && typeof value.venue === "string" && typeof value.definitionText === "string" && date(value.bucketAt) && typeof value.stale === "boolean";
12
19
  function validRow(row: PerpBoardRow) {
13
20
  return !!row && typeof row.marketId === "string" && typeof row.symbol === "string" && typeof row.displayName === "string"
14
21
  && typeof row.baseAsset === "string" && typeof row.quoteCurrency === "string" && typeof row.marginCurrency === "string"
@@ -16,7 +23,8 @@ function validRow(row: PerpBoardRow) {
16
23
  && [row.markPrice, row.oraclePrice, row.fundingRate, row.fundingIntervalHours, row.fundingRate8h, row.fundingApr,
17
24
  row.predictedFundingRate, row.predictedFundingIntervalHours, row.openInterestBase, row.openInterestUsd, row.volume24hUsd,
18
25
  row.premium, row.oiChange24h, row.closedMarketPremium].every(finiteOrNull)
19
- && (row.fundingIntervalHours === null || row.fundingIntervalHours > 0);
26
+ && (row.fundingIntervalHours === null || row.fundingIntervalHours > 0)
27
+ && validLongShort(row.longShortRatio) && (row.longShortRatioReason == null || typeof row.longShortRatioReason === "string");
20
28
  }
21
29
  function envelope(data: { access: string; locked: number | boolean; asOf: string | null }) {
22
30
  return !!data && ["pro", "preview"].includes(data.access) && (typeof data.locked === "boolean" || Number.isInteger(data.locked) && data.locked >= 0) && (data.asOf === null || date(data.asOf));
@@ -30,12 +38,65 @@ export function validatePerpsHistory(data: PerpHistoryPayload, expectedMarketId?
30
38
  if (!envelope(data) || typeof data.marketId !== "string" || expectedMarketId !== undefined && data.marketId !== expectedMarketId || !Array.isArray(data.rows) || !Array.isArray(data.funding) || !Array.isArray(data.candles)
31
39
  || !data.rows.every((row) => date(row.time) && [row.markPrice, row.oraclePrice, row.premium, row.fundingRate, row.fundingIntervalHours, row.openInterestBase, row.openInterestUsd].every(finiteOrNull) && (row.fundingIntervalHours === null || row.fundingIntervalHours > 0))
32
40
  || !data.funding.every((row) => date(row.time) && typeof row.rate === "number" && Number.isFinite(row.rate) && Number.isFinite(row.intervalHours) && row.intervalHours > 0 && row.marketId === data.marketId && date(row.observedAt) && typeof row.sourceUrl === "string")
33
- || !data.candles.every((row) => date(row.time) && [row.open, row.high, row.low, row.close].every((n) => typeof n === "number" && Number.isFinite(n)) && row.marketId === data.marketId && date(row.observedAt) && typeof row.sourceUrl === "string")) throw new Error("The server returned unreadable perpetual history");
41
+ || !data.candles.every((row) => date(row.time) && [row.open, row.high, row.low, row.close].every((n) => typeof n === "number" && Number.isFinite(n)) && row.marketId === data.marketId && date(row.observedAt) && typeof row.sourceUrl === "string")
42
+ || data.longShortRatio != null && !(Array.isArray(data.longShortRatio) && data.longShortRatio.every((point) => !!point && date(point.time) && validShares(point)))) throw new Error("The server returned unreadable perpetual history");
34
43
  return data;
35
44
  }
36
45
  export const fetchPerpsHistory = async (query: PerpHistoryQuery, client: Pick<typeof apiClient, "getCloudPerpsHistory"> = apiClient) => validatePerpsHistory(await client.getCloudPerpsHistory(query), query.marketId);
37
- const fetchPerpsEquity = async (symbol: string, client: Pick<typeof apiClient, "getCloudPerpsEquity"> = apiClient) => validatePerpsBoard(await client.getCloudPerpsEquity(symbol));
38
- export const loadPerpsEquity = (symbol: string, access: string, force = false) => loadCloudResource(perpsCache, `equity:${symbol}:${access}`, () => fetchPerpsEquity(symbol), { force, validate: validatePerpsBoard });
46
+
47
+ const RANKING_KEYS = ["fundingPositive", "fundingNegative", "oiSurges", "premiumDislocations", "closedMarketDislocations"] as const;
48
+ export function validatePerpsRankings(data: PerpRankingsPayload): PerpRankingsPayload {
49
+ // A market can rank in several lists, but only once in each.
50
+ if (!envelope(data) || !RANKING_KEYS.every((key) => Array.isArray(data[key]) && data[key].every(validRow)
51
+ && new Set(data[key].map((row) => row.marketId)).size === data[key].length)) throw new Error("The server returned unreadable perpetual rankings");
52
+ return data;
53
+ }
54
+ /**
55
+ * The server ranks by its own figures and has no long-share order. A full board (Pro) and the fixed preview
56
+ * both arrive whole, so ordering them here is the order the server would give: highest first, none last.
57
+ */
58
+ const LONG_SHORT_SORT = "long-short";
59
+ const byLongShare = (rows: readonly PerpBoardRow[]) => [...rows].sort((a, b) =>
60
+ (b.longShortRatio?.longShare ?? -Infinity) - (a.longShortRatio?.longShare ?? -Infinity) || a.marketId.localeCompare(b.marketId));
61
+ export async function fetchPerpsBoard(query: PerpBoardQuery, client: Pick<typeof apiClient, "getCloudPerpsBoard"> = apiClient) {
62
+ if (query.sort !== LONG_SHORT_SORT) return validatePerpsBoard(await client.getCloudPerpsBoard(query));
63
+ const data = validatePerpsBoard(await client.getCloudPerpsBoard({ ...query, sort: "oi" }));
64
+ return { ...data, rows: byLongShare(data.rows) };
65
+ }
66
+ export const fetchPerpsRankings = async (client: Pick<typeof apiClient, "getCloudPerpsRankings"> = apiClient) => validatePerpsRankings(await client.getCloudPerpsRankings());
67
+ function validatePerpsCompare(data: PerpBoardPayload, baseAsset: string): PerpBoardPayload {
68
+ validatePerpsBoard(data);
69
+ if (!data.rows.every((row) => row.baseAsset.toUpperCase() === baseAsset.toUpperCase())) throw new Error("The server returned contracts for another asset");
70
+ return data;
71
+ }
72
+ export const fetchPerpsCompare = async (baseAsset: string, client: Pick<typeof apiClient, "getCloudPerpsCompare"> = apiClient) => validatePerpsCompare(await client.getCloudPerpsCompare(baseAsset), baseAsset);
73
+ export const perpsRankingsCache = createPluginCache<PerpRankingsPayload>({ kind: "perps-rankings", source: "gloom-cloud", schemaVersion: 1,
74
+ policy: { staleMs: 60_000, expireMs: 86_400_000 } });
75
+ // Board and comparison payloads share the board cache, keyed by the query and the account's plan.
76
+ const boardKey = (query: PerpBoardQuery, access: string) => `board:v1:${query.assetClass ?? "all"}:${query.sort ?? "oi"}:${query.search?.trim().toUpperCase() ?? ""}:${access}`;
77
+ export const cachedPerpsBoard = (query: PerpBoardQuery, access: string) => cachedCloudResource(perpsCache, boardKey(query, access), validatePerpsBoard);
78
+ export const loadPerpsBoard = (query: PerpBoardQuery, access: string, force = false) => loadCloudResource(perpsCache, boardKey(query, access), () => fetchPerpsBoard(query), { force, validate: validatePerpsBoard });
79
+ export const cachedPerpsRankings = (access: string) => cachedCloudResource(perpsRankingsCache, `rankings:v1:${access}`, validatePerpsRankings);
80
+ export const loadPerpsRankings = (access: string, force = false) => loadCloudResource(perpsRankingsCache, `rankings:v1:${access}`, () => fetchPerpsRankings(), { force, validate: validatePerpsRankings });
81
+ const compareKey = (baseAsset: string, access: string) => `compare:v1:${baseAsset.toUpperCase()}:${access}`;
82
+ export const cachedPerpsCompare = (baseAsset: string, access: string) => cachedCloudResource(perpsCache, compareKey(baseAsset, access), (data) => validatePerpsCompare(data, baseAsset));
83
+ export const loadPerpsCompare = (baseAsset: string, access: string, force = false) => loadCloudResource(perpsCache, compareKey(baseAsset, access), () => fetchPerpsCompare(baseAsset), { force, validate: (data) => validatePerpsCompare(data, baseAsset) });
84
+ function validatePerpsEquity(data: PerpBoardPayload, listing: PerpEquityListing): PerpBoardPayload {
85
+ validatePerpsBoard(data);
86
+ const same = (value: PerpEquityListing | null | undefined) => value?.symbol === listing.symbol && value.exchange === listing.exchange;
87
+ if (!same(data.listing) || !data.rows.every((row) => {
88
+ const match = row.equityMatch;
89
+ return same(match?.listing) && row.assetClass === "stocks" && !!match?.underlying.exchange
90
+ && match.underlying.symbol === row.underlyingSymbol
91
+ && (match.basis === "issuer" || match.basis === "listing" && same(match.underlying));
92
+ })) throw new Error("The server returned perpetuals for another listing");
93
+ return data;
94
+ }
95
+ export const loadPerpsEquity = (listing: PerpEquityListing, access: string, force = false) => {
96
+ const validate = (data: PerpBoardPayload) => validatePerpsEquity(data, listing);
97
+ return loadCloudResource(perpsCache, `equity:v2:${listing.symbol}:${listing.exchange}:${access}`,
98
+ async () => validate(await apiClient.getCloudPerpsEquity(listing.symbol, listing.exchange)), { force, validate });
99
+ };
39
100
 
40
101
  function validatePerpsMarket(data: PerpMarketPayload): PerpMarketPayload {
41
102
  validatePerpsBoard(data);
@@ -0,0 +1,16 @@
1
+ import type { PerpEquityListing } from "../../../api-client/perps";
2
+ import { listingSuffixExchange } from "../../../sources/listing-symbols";
3
+ import { canonicalExchange } from "../../../utils/exchanges";
4
+ import { listingIdentity } from "../shared/ticker-request";
5
+
6
+ /** A qualifier owns the listing. Conflicting metadata cannot authorize a comparison. */
7
+ export function perpEquityIdentity(key: string, savedExchange = ""): PerpEquityListing | null {
8
+ const identity = listingIdentity(key, savedExchange);
9
+ if (!identity) return null;
10
+ const suffixExchange = listingSuffixExchange(identity.symbol);
11
+ const exchange = suffixExchange ?? canonicalExchange(identity.exchange);
12
+ if (!exchange || (identity.exchange && canonicalExchange(identity.exchange) !== exchange)
13
+ || (savedExchange && canonicalExchange(savedExchange) !== exchange)) return null;
14
+ const symbol = suffixExchange ? identity.symbol.slice(0, identity.symbol.lastIndexOf(".")) : identity.symbol;
15
+ return /^[A-Z0-9.-]{1,32}$/.test(symbol) ? { symbol, exchange } : null;
16
+ }