gloomberb 0.16.1 → 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 (574) 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 +26 -8
  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 +2 -0
  8. package/src/api-client/market-discovery.ts +10 -0
  9. package/src/api-client/paths.ts +30 -1
  10. package/src/api-client/perps.ts +37 -0
  11. package/src/api-client/request.ts +5 -2
  12. package/src/api-client/types.ts +2 -0
  13. package/src/brokers/account-cache.ts +24 -0
  14. package/src/cli/commands/command-utils.ts +8 -0
  15. package/src/cli/commands/crypto-hints.ts +75 -0
  16. package/src/cli/commands/market.ts +414 -106
  17. package/src/cli/commands/overview.ts +77 -32
  18. package/src/cli/commands/ticker.ts +101 -43
  19. package/src/cli/desktop-pane-shot.ts +34 -4
  20. package/src/cli/freshness.ts +61 -0
  21. package/src/cli/help.ts +1 -0
  22. package/src/cli/helpers.ts +4 -8
  23. package/src/cli/index.ts +12 -2
  24. package/src/cli/listing-arg.ts +321 -0
  25. package/src/cli/pane-functions/access-gate.ts +42 -0
  26. package/src/cli/pane-functions/catalog.ts +28 -1
  27. package/src/cli/pane-functions/data.ts +3 -1
  28. package/src/cli/pane-functions/dom.ts +20 -2
  29. package/src/cli/pane-functions/freshness.ts +400 -0
  30. package/src/cli/pane-functions/function-help.ts +27 -20
  31. package/src/cli/pane-functions/headless.ts +39 -11
  32. package/src/cli/pane-functions/index.ts +15 -4
  33. package/src/cli/pane-functions/options.ts +3 -0
  34. package/src/cli/pane-functions/report.ts +4 -0
  35. package/src/cli/pane-functions/resolver.ts +35 -7
  36. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  37. package/src/cli/pane-functions/screenshot.ts +7 -3
  38. package/src/cli/result.ts +41 -27
  39. package/src/cli/ticker-resolution.ts +7 -5
  40. package/src/components/chart/composite/composite-chart.tsx +88 -2536
  41. package/src/components/chart/composite/drawing-store.ts +59 -0
  42. package/src/components/chart/composite/legend.tsx +319 -0
  43. package/src/components/chart/composite/panel-bitmap.ts +139 -0
  44. package/src/components/chart/composite/panel-gestures.ts +59 -0
  45. package/src/components/chart/composite/panel-labels.ts +107 -0
  46. package/src/components/chart/composite/panel-surface.tsx +876 -0
  47. package/src/components/chart/composite/tool-catalog.ts +98 -0
  48. package/src/components/chart/composite/toolbar.tsx +186 -0
  49. package/src/components/chart/composite/tools.ts +8 -0
  50. package/src/components/chart/composite/use-composite-chart-keyboard.ts +703 -0
  51. package/src/components/chart/composite/use-composite-viewport.ts +143 -0
  52. package/src/components/command-bar/list/model.ts +5 -0
  53. package/src/components/command-bar/list/view.tsx +16 -3
  54. package/src/components/command-bar/pane-templates/workflow.ts +1 -0
  55. package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
  56. package/src/components/command-bar/workflow/ops.ts +1 -1
  57. package/src/components/command-bar/workflow/tickers.ts +6 -0
  58. package/src/components/data-table/view.tsx +29 -10
  59. package/src/components/feedback-dialog.tsx +2 -1
  60. package/src/components/form-modal/field-row.tsx +2 -2
  61. package/src/components/layout/detached-pane-shell.tsx +6 -3
  62. package/src/components/layout/pane/content.tsx +23 -10
  63. package/src/components/layout/pane/error-boundary.tsx +103 -0
  64. package/src/components/layout/pane/footer/registration.tsx +68 -23
  65. package/src/components/layout/pane/header.tsx +11 -11
  66. package/src/components/layout/shell/menu.ts +2 -1
  67. package/src/components/layout/shell/pane/actions.ts +2 -1
  68. package/src/components/layout/shell/pane/layers.tsx +14 -3
  69. package/src/components/metric-treemap/heat-surface.tsx +43 -16
  70. package/src/components/metric-treemap/heat-tiles.ts +18 -0
  71. package/src/components/metric-treemap/layout.ts +2 -0
  72. package/src/components/onboarding/onboarding-keyboard.ts +311 -0
  73. package/src/components/onboarding/onboarding-wizard.tsx +101 -788
  74. package/src/components/onboarding/use-onboarding-broker-form.ts +263 -0
  75. package/src/components/onboarding/use-onboarding-progress.ts +298 -0
  76. package/src/components/onboarding/use-onboarding-upgrade.ts +83 -0
  77. package/src/components/ticker/list-table-view.tsx +13 -1
  78. package/src/components/ui/fields.tsx +8 -0
  79. package/src/components/ui/range-track.tsx +10 -3
  80. package/src/components/ui/status.tsx +8 -2
  81. package/src/components/ui/tabs.tsx +7 -4
  82. package/src/components/ui/text-prompt-dialog.tsx +4 -2
  83. package/src/data/config/store/normalize.ts +17 -1
  84. package/src/i18n/es.ts +19 -1
  85. package/src/i18n/ja.ts +19 -1
  86. package/src/i18n/ko.ts +19 -1
  87. package/src/i18n/zh-cn.ts +19 -1
  88. package/src/i18n/zh-tw.ts +19 -1
  89. package/src/market-data/coordinator/chart.ts +1 -1
  90. package/src/market-data/coordinator/quotes.ts +23 -4
  91. package/src/market-data/fx-closes.ts +72 -0
  92. package/src/market-data/market/freshness.ts +78 -6
  93. package/src/market-data/market/session-close-note.ts +57 -0
  94. package/src/market-data/market/status.ts +151 -0
  95. package/src/market-data/published-apac-sessions.ts +66 -0
  96. package/src/market-data/quotes/contributions.ts +2 -0
  97. package/src/market-data/quotes/resolution.ts +2 -0
  98. package/src/market-data/quotes/screener-live-quotes.ts +49 -4
  99. package/src/plugins/builtin/account-management/pane.tsx +4 -0
  100. package/src/plugins/builtin/alerts/cli.ts +10 -5
  101. package/src/plugins/builtin/alerts/index.tsx +1 -0
  102. package/src/plugins/builtin/analytics/metrics.ts +8 -3
  103. package/src/plugins/builtin/analytics/risk-client.ts +191 -66
  104. package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
  105. package/src/plugins/builtin/analytics/risk-headless.ts +19 -13
  106. package/src/plugins/builtin/analytics/risk-model.ts +45 -17
  107. package/src/plugins/builtin/analytics/risk-pane.tsx +1 -0
  108. package/src/plugins/builtin/analytics/risk-test-data.ts +25 -1
  109. package/src/plugins/builtin/attention/headless.ts +6 -4
  110. package/src/plugins/builtin/attention/index.tsx +1 -1
  111. package/src/plugins/builtin/attention/pane.tsx +1 -1
  112. package/src/plugins/builtin/awards/headless.ts +1 -0
  113. package/src/plugins/builtin/awards/pane.tsx +1 -1
  114. package/src/plugins/builtin/backtest/headless.ts +1 -0
  115. package/src/plugins/builtin/bond-calculator/headless.ts +1 -0
  116. package/src/plugins/builtin/buildout/index.tsx +1 -0
  117. package/src/plugins/builtin/catalysts/headless.ts +3 -1
  118. package/src/plugins/builtin/cds/headless.ts +2 -0
  119. package/src/plugins/builtin/central-bank-rates/headless.ts +1 -0
  120. package/src/plugins/builtin/chart-composer/headless.ts +38 -3
  121. package/src/plugins/builtin/chart-composer/index.tsx +3 -1
  122. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -2
  123. package/src/plugins/builtin/chat/channels.ts +19 -3
  124. package/src/plugins/builtin/chat/content/composer-runtime.ts +28 -13
  125. package/src/plugins/builtin/chat/content/footer.tsx +15 -6
  126. package/src/plugins/builtin/chat/content/index.tsx +120 -21
  127. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +261 -113
  128. package/src/plugins/builtin/chat/content/transcript.tsx +20 -2
  129. package/src/plugins/builtin/chat/direct-messages.ts +200 -0
  130. package/src/plugins/builtin/chat/index.tsx +1 -0
  131. package/src/plugins/builtin/chat/message/desktop.tsx +3 -0
  132. package/src/plugins/builtin/chat/message/header.tsx +16 -6
  133. package/src/plugins/builtin/chat/message/inline-tokens.tsx +72 -32
  134. package/src/plugins/builtin/chat/message/profile-popover.tsx +96 -12
  135. package/src/plugins/builtin/chat/message/terminal.tsx +3 -0
  136. package/src/plugins/builtin/chat/message/types.ts +14 -2
  137. package/src/plugins/builtin/chat/profile-popover.ts +18 -13
  138. package/src/plugins/builtin/clinical-trials/index.tsx +1 -0
  139. package/src/plugins/builtin/cloud/askg/executor.ts +2 -1
  140. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  141. package/src/plugins/builtin/cloud/team/module.tsx +1 -0
  142. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
  143. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  144. package/src/plugins/builtin/comment-letters/index.tsx +1 -0
  145. package/src/plugins/builtin/company-attention/headless.ts +5 -1
  146. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  147. package/src/plugins/builtin/company-kpis/headless.ts +1 -0
  148. package/src/plugins/builtin/congress-trades/headless.ts +1 -0
  149. package/src/plugins/builtin/correlation/compute.ts +59 -16
  150. package/src/plugins/builtin/correlation/geo.ts +34 -0
  151. package/src/plugins/builtin/correlation/headless-schema.ts +4 -1
  152. package/src/plugins/builtin/correlation/headless.ts +42 -8
  153. package/src/plugins/builtin/correlation/history.ts +2 -0
  154. package/src/plugins/builtin/correlation/index.tsx +68 -14
  155. package/src/plugins/builtin/correlation/matrix/model.ts +17 -2
  156. package/src/plugins/builtin/correlation/matrix/selection.ts +5 -3
  157. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  158. package/src/plugins/builtin/correlation/relationship/pane.tsx +4 -2
  159. package/src/plugins/builtin/correlation/relationship/view-model.ts +16 -1
  160. package/src/plugins/builtin/correlation/settings.ts +1 -1
  161. package/src/plugins/builtin/cot/headless.ts +6 -3
  162. package/src/plugins/builtin/cot/index.tsx +3 -3
  163. package/src/plugins/builtin/cot/model.ts +36 -3
  164. package/src/plugins/builtin/cpi/headless.ts +4 -0
  165. package/src/plugins/builtin/credit-boards/headless.ts +2 -0
  166. package/src/plugins/builtin/credit-conditions/headless.ts +1 -0
  167. package/src/plugins/builtin/credit-documents/gauge.tsx +5 -8
  168. package/src/plugins/builtin/credit-documents/headless.ts +2 -0
  169. package/src/plugins/builtin/crypto-board/headless.ts +3 -0
  170. package/src/plugins/builtin/custom-view/loader.ts +1 -1
  171. package/src/plugins/builtin/debt-maturities/headless.ts +4 -0
  172. package/src/plugins/builtin/dividend-yield/headless.ts +1 -0
  173. package/src/plugins/builtin/doe/headless.ts +7 -0
  174. package/src/plugins/builtin/doe/range-bar.tsx +7 -37
  175. package/src/plugins/builtin/earnings/headless.ts +5 -0
  176. package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
  177. package/src/plugins/builtin/earnings-ripple/client.ts +91 -18
  178. package/src/plugins/builtin/earnings-ripple/headless.ts +28 -8
  179. package/src/plugins/builtin/earnings-ripple/index.tsx +2 -2
  180. package/src/plugins/builtin/earnings-ripple/model.ts +97 -7
  181. package/src/plugins/builtin/earnings-ripple/pane.tsx +148 -23
  182. package/src/plugins/builtin/econ/index.tsx +1 -0
  183. package/src/plugins/builtin/econ-statistics/headless.ts +2 -0
  184. package/src/plugins/builtin/equity-screener/headless.ts +2 -0
  185. package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
  186. package/src/plugins/builtin/executives/index.tsx +1 -0
  187. package/src/plugins/builtin/exposure/headless.ts +3 -1
  188. package/src/plugins/builtin/fear-greed/headless.ts +2 -0
  189. package/src/plugins/builtin/filing-events/index.tsx +1 -0
  190. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  191. package/src/plugins/builtin/futures-curve/headless.ts +39 -10
  192. package/src/plugins/builtin/futures-curve/index.tsx +3 -3
  193. package/src/plugins/builtin/futures-curve/model.ts +188 -4
  194. package/src/plugins/builtin/futures-curve/pane.tsx +51 -9
  195. package/src/plugins/builtin/gpu/headless.ts +7 -3
  196. package/src/plugins/builtin/gpu/ladder.tsx +26 -30
  197. package/src/plugins/builtin/gpu/model.ts +27 -1
  198. package/src/plugins/builtin/holders/beneficial-client.ts +209 -0
  199. package/src/plugins/builtin/holders/beneficial-model.ts +279 -0
  200. package/src/plugins/builtin/holders/beneficial-report.ts +126 -0
  201. package/src/plugins/builtin/holders/beneficial-view.tsx +241 -0
  202. package/src/plugins/builtin/holders/headless.ts +139 -5
  203. package/src/plugins/builtin/holders/index.tsx +10 -3
  204. package/src/plugins/builtin/holders/pane.tsx +20 -3
  205. package/src/plugins/builtin/holders/report-header.ts +95 -0
  206. package/src/plugins/builtin/holders/table-model.ts +20 -5
  207. package/src/plugins/builtin/holders/thirteenf-match.ts +29 -1
  208. package/src/plugins/builtin/holders/types.ts +2 -1
  209. package/src/plugins/builtin/insider/headless.ts +2 -0
  210. package/src/plugins/builtin/ipo-calendar/headless.ts +1 -0
  211. package/src/plugins/builtin/iv-history/headless.ts +4 -1
  212. package/src/plugins/builtin/jobs/headless.ts +1 -0
  213. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -0
  214. package/src/plugins/builtin/macro-day/headless.ts +2 -0
  215. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  216. package/src/plugins/builtin/market-heatmap/data.ts +2 -0
  217. package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
  218. package/src/plugins/builtin/market-heatmap/index.tsx +39 -46
  219. package/src/plugins/builtin/market-heatmap/model.ts +146 -0
  220. package/src/plugins/builtin/market-heatmap/portfolio.ts +23 -4
  221. package/src/plugins/builtin/market-movers/headless.ts +2 -0
  222. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  223. package/src/plugins/builtin/members/client.ts +20 -3
  224. package/src/plugins/builtin/members/headless.ts +5 -3
  225. package/src/plugins/builtin/members/model.ts +10 -2
  226. package/src/plugins/builtin/members/pane.tsx +13 -6
  227. package/src/plugins/builtin/mna/distress/headless.ts +1 -0
  228. package/src/plugins/builtin/mna/headless.ts +1 -0
  229. package/src/plugins/builtin/money-markets/headless.ts +1 -0
  230. package/src/plugins/builtin/news/headless.ts +2 -0
  231. package/src/plugins/builtin/news/wire/index.tsx +8 -1
  232. package/src/plugins/builtin/notes/cli.ts +14 -6
  233. package/src/plugins/builtin/notes/index.tsx +1 -0
  234. package/src/plugins/builtin/openfda/index.tsx +1 -0
  235. package/src/plugins/builtin/options-calculator/headless.ts +2 -0
  236. package/src/plugins/builtin/options-positioning/headless.ts +3 -0
  237. package/src/plugins/builtin/options-scenario/headless.ts +5 -0
  238. package/src/plugins/builtin/pe-band/client.ts +48 -3
  239. package/src/plugins/builtin/pe-band/headless.ts +25 -7
  240. package/src/plugins/builtin/pe-band/model.ts +92 -16
  241. package/src/plugins/builtin/pe-band/pane.tsx +33 -16
  242. package/src/plugins/builtin/perps/client.ts +50 -3
  243. package/src/plugins/builtin/perps/headless.ts +144 -16
  244. package/src/plugins/builtin/perps/index.tsx +6 -6
  245. package/src/plugins/builtin/perps/model.ts +189 -12
  246. package/src/plugins/builtin/perps/pane.tsx +248 -47
  247. package/src/plugins/builtin/portfolio-list/allocation.ts +245 -0
  248. package/src/plugins/builtin/portfolio-list/cached-account.ts +33 -0
  249. package/src/plugins/builtin/portfolio-list/cell-version.ts +19 -1
  250. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +328 -29
  251. package/src/plugins/builtin/portfolio-list/cli/render.ts +230 -39
  252. package/src/plugins/builtin/portfolio-list/cli/shared.ts +67 -2
  253. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +35 -31
  254. package/src/plugins/builtin/portfolio-list/column-values.ts +124 -47
  255. package/src/plugins/builtin/portfolio-list/headless.ts +98 -16
  256. package/src/plugins/builtin/portfolio-list/index.tsx +14 -3
  257. package/src/plugins/builtin/portfolio-list/live-valuation.ts +7 -5
  258. package/src/plugins/builtin/portfolio-list/mutations.ts +55 -1
  259. package/src/plugins/builtin/portfolio-list/pane/data.ts +6 -0
  260. package/src/plugins/builtin/portfolio-list/pane/index.tsx +47 -13
  261. package/src/plugins/builtin/portfolio-list/settings.ts +33 -2
  262. package/src/plugins/builtin/portfolio-list/summary/index.tsx +24 -1
  263. package/src/plugins/builtin/portfolio-list/summary/totals.ts +26 -7
  264. package/src/plugins/builtin/power/headless.ts +3 -1
  265. package/src/plugins/builtin/rate-path/headless.ts +2 -0
  266. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  267. package/src/plugins/builtin/relative-rotation/headless.ts +2 -0
  268. package/src/plugins/builtin/research/analyst-headless.ts +3 -0
  269. package/src/plugins/builtin/research/events-headless.ts +2 -0
  270. package/src/plugins/builtin/research/headless.ts +2 -0
  271. package/src/plugins/builtin/research/index.tsx +1 -0
  272. package/src/plugins/builtin/research/relative-valuation-headless.ts +10 -5
  273. package/src/plugins/builtin/research/relative-valuation-model.ts +11 -3
  274. package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
  275. package/src/plugins/builtin/research-search/index.tsx +1 -0
  276. package/src/plugins/builtin/revenue-breakdown/headless.ts +2 -0
  277. package/src/plugins/builtin/reverse-dcf/headless.ts +2 -0
  278. package/src/plugins/builtin/risk-factors/headless.ts +2 -0
  279. package/src/plugins/builtin/scanner/flow-history.ts +2 -13
  280. package/src/plugins/builtin/scanner/headless.ts +14 -0
  281. package/src/plugins/builtin/seasonality/headless.ts +2 -0
  282. package/src/plugins/builtin/sec/command-bar-search.ts +14 -4
  283. package/src/plugins/builtin/sec/filing-content.ts +42 -17
  284. package/src/plugins/builtin/sec/forms.ts +12 -0
  285. package/src/plugins/builtin/sec/headless.ts +2 -0
  286. package/src/plugins/builtin/sectors/client.ts +1 -0
  287. package/src/plugins/builtin/sectors/headless.ts +9 -1
  288. package/src/plugins/builtin/sectors/sector-model.ts +2 -0
  289. package/src/plugins/builtin/shared/crypto-pair.ts +14 -0
  290. package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
  291. package/src/plugins/builtin/shared/report-freshness.ts +57 -0
  292. package/src/plugins/builtin/shared/research-cells.tsx +4 -9
  293. package/src/plugins/builtin/short-interest/headless.ts +2 -0
  294. package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
  295. package/src/plugins/builtin/short-volume/headless.ts +1 -0
  296. package/src/plugins/builtin/social-mentions/headless.ts +1 -0
  297. package/src/plugins/builtin/supply-chain/headless.ts +7 -1
  298. package/src/plugins/builtin/supply-chain/index.tsx +10 -1
  299. package/src/plugins/builtin/supply-chain/model.ts +27 -2
  300. package/src/plugins/builtin/supply-chain/pane.tsx +71 -32
  301. package/src/plugins/builtin/themes/headless.ts +3 -0
  302. package/src/plugins/builtin/thirteenf/headless.ts +110 -25
  303. package/src/plugins/builtin/thirteenf/report-notes.ts +149 -0
  304. package/src/plugins/builtin/ticker-detail/headless.ts +7 -1
  305. package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
  306. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +26 -26
  308. package/src/plugins/builtin/time-sales/headless.ts +5 -1
  309. package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
  310. package/src/plugins/builtin/volatility/headless.ts +6 -1
  311. package/src/plugins/builtin/world-indices/headless.ts +2 -0
  312. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +157 -0
  313. package/src/plugins/builtin/world-venue-map/basemap.ts +214 -0
  314. package/src/plugins/builtin/world-venue-map/client.ts +45 -0
  315. package/src/plugins/builtin/world-venue-map/entity-detail.tsx +147 -0
  316. package/src/plugins/builtin/world-venue-map/entity-table.ts +59 -0
  317. package/src/plugins/builtin/world-venue-map/evidence.ts +77 -0
  318. package/src/plugins/builtin/world-venue-map/feed.ts +293 -0
  319. package/src/plugins/builtin/world-venue-map/geo-draw.tsx +248 -0
  320. package/src/plugins/builtin/world-venue-map/geo-series.ts +142 -0
  321. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +447 -0
  322. package/src/plugins/builtin/world-venue-map/headless.ts +146 -0
  323. package/src/plugins/builtin/world-venue-map/ids.ts +1 -0
  324. package/src/plugins/builtin/world-venue-map/index.ts +67 -4
  325. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +552 -0
  326. package/src/plugins/builtin/world-venue-map/layers.ts +348 -0
  327. package/src/plugins/builtin/world-venue-map/map-symbols.ts +220 -0
  328. package/src/plugins/builtin/world-venue-map/map.tsx +381 -45
  329. package/src/plugins/builtin/world-venue-map/model.ts +7 -4
  330. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +199 -0
  331. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n00.chunk.ts +4 -0
  332. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n15.chunk.ts +4 -0
  333. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n30.chunk.ts +4 -0
  334. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n45.chunk.ts +4 -0
  335. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n60.chunk.ts +4 -0
  336. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n75.chunk.ts +4 -0
  337. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s15.chunk.ts +4 -0
  338. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s30.chunk.ts +4 -0
  339. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s60.chunk.ts +4 -0
  340. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n00.chunk.ts +4 -0
  341. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n15.chunk.ts +4 -0
  342. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n30.chunk.ts +4 -0
  343. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n45.chunk.ts +4 -0
  344. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n60.chunk.ts +4 -0
  345. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n75.chunk.ts +4 -0
  346. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s15.chunk.ts +4 -0
  347. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s30.chunk.ts +4 -0
  348. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s45.chunk.ts +4 -0
  349. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n00.chunk.ts +4 -0
  350. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n15.chunk.ts +4 -0
  351. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n30.chunk.ts +4 -0
  352. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n45.chunk.ts +4 -0
  353. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n60.chunk.ts +4 -0
  354. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n75.chunk.ts +4 -0
  355. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s15.chunk.ts +4 -0
  356. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s30.chunk.ts +4 -0
  357. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s45.chunk.ts +4 -0
  358. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s60.chunk.ts +4 -0
  359. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n00.chunk.ts +4 -0
  360. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n15.chunk.ts +4 -0
  361. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n30.chunk.ts +4 -0
  362. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n45.chunk.ts +4 -0
  363. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n60.chunk.ts +4 -0
  364. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n75.chunk.ts +4 -0
  365. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s15.chunk.ts +4 -0
  366. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s30.chunk.ts +4 -0
  367. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s60.chunk.ts +4 -0
  368. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n00.chunk.ts +4 -0
  369. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n15.chunk.ts +4 -0
  370. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n30.chunk.ts +4 -0
  371. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n45.chunk.ts +4 -0
  372. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n60.chunk.ts +4 -0
  373. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n75.chunk.ts +4 -0
  374. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s15.chunk.ts +4 -0
  375. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s30.chunk.ts +4 -0
  376. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s60.chunk.ts +4 -0
  377. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n00.chunk.ts +4 -0
  378. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n15.chunk.ts +4 -0
  379. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n30.chunk.ts +4 -0
  380. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n45.chunk.ts +4 -0
  381. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n60.chunk.ts +4 -0
  382. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n75.chunk.ts +4 -0
  383. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075s45.chunk.ts +4 -0
  384. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n00.chunk.ts +4 -0
  385. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n15.chunk.ts +4 -0
  386. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n30.chunk.ts +4 -0
  387. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n45.chunk.ts +4 -0
  388. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n60.chunk.ts +4 -0
  389. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n75.chunk.ts +4 -0
  390. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090s15.chunk.ts +4 -0
  391. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n00.chunk.ts +4 -0
  392. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n15.chunk.ts +4 -0
  393. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n30.chunk.ts +4 -0
  394. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n45.chunk.ts +4 -0
  395. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n60.chunk.ts +4 -0
  396. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n75.chunk.ts +4 -0
  397. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s15.chunk.ts +4 -0
  398. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s30.chunk.ts +4 -0
  399. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s45.chunk.ts +4 -0
  400. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n00.chunk.ts +4 -0
  401. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n15.chunk.ts +4 -0
  402. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n30.chunk.ts +4 -0
  403. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n45.chunk.ts +4 -0
  404. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n60.chunk.ts +4 -0
  405. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s15.chunk.ts +4 -0
  406. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s30.chunk.ts +4 -0
  407. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s45.chunk.ts +4 -0
  408. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n00.chunk.ts +4 -0
  409. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n15.chunk.ts +4 -0
  410. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n30.chunk.ts +4 -0
  411. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n45.chunk.ts +4 -0
  412. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n60.chunk.ts +4 -0
  413. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n75.chunk.ts +4 -0
  414. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s15.chunk.ts +4 -0
  415. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s30.chunk.ts +4 -0
  416. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s45.chunk.ts +4 -0
  417. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n00.chunk.ts +4 -0
  418. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n15.chunk.ts +4 -0
  419. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n45.chunk.ts +4 -0
  420. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n60.chunk.ts +4 -0
  421. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n75.chunk.ts +4 -0
  422. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s15.chunk.ts +4 -0
  423. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s30.chunk.ts +4 -0
  424. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s45.chunk.ts +4 -0
  425. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s60.chunk.ts +4 -0
  426. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n00.chunk.ts +4 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n15.chunk.ts +4 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n45.chunk.ts +4 -0
  429. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n60.chunk.ts +4 -0
  430. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s15.chunk.ts +4 -0
  431. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s30.chunk.ts +4 -0
  432. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s45.chunk.ts +4 -0
  433. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s60.chunk.ts +4 -0
  434. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n00.chunk.ts +4 -0
  435. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n15.chunk.ts +4 -0
  436. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n30.chunk.ts +4 -0
  437. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n45.chunk.ts +4 -0
  438. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n60.chunk.ts +4 -0
  439. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n75.chunk.ts +4 -0
  440. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s15.chunk.ts +4 -0
  441. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s30.chunk.ts +4 -0
  442. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s45.chunk.ts +4 -0
  443. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n00.chunk.ts +4 -0
  444. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n15.chunk.ts +4 -0
  445. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n30.chunk.ts +4 -0
  446. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n60.chunk.ts +4 -0
  447. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n75.chunk.ts +4 -0
  448. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s30.chunk.ts +4 -0
  449. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s60.chunk.ts +4 -0
  450. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n30.chunk.ts +4 -0
  451. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n45.chunk.ts +4 -0
  452. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n60.chunk.ts +4 -0
  453. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n75.chunk.ts +4 -0
  454. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s15.chunk.ts +4 -0
  455. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s30.chunk.ts +4 -0
  456. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s60.chunk.ts +4 -0
  457. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n00.chunk.ts +4 -0
  458. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n30.chunk.ts +4 -0
  459. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n45.chunk.ts +4 -0
  460. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n60.chunk.ts +4 -0
  461. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n75.chunk.ts +4 -0
  462. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s15.chunk.ts +4 -0
  463. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s30.chunk.ts +4 -0
  464. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s45.chunk.ts +4 -0
  465. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s60.chunk.ts +4 -0
  466. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n00.chunk.ts +4 -0
  467. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n15.chunk.ts +4 -0
  468. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n30.chunk.ts +4 -0
  469. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n45.chunk.ts +4 -0
  470. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n60.chunk.ts +4 -0
  471. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n75.chunk.ts +4 -0
  472. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s15.chunk.ts +4 -0
  473. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s30.chunk.ts +4 -0
  474. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s45.chunk.ts +4 -0
  475. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s60.chunk.ts +4 -0
  476. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n00.chunk.ts +4 -0
  477. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n15.chunk.ts +4 -0
  478. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n30.chunk.ts +4 -0
  479. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n45.chunk.ts +4 -0
  480. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n60.chunk.ts +4 -0
  481. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n75.chunk.ts +4 -0
  482. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s15.chunk.ts +4 -0
  483. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s30.chunk.ts +4 -0
  484. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s45.chunk.ts +4 -0
  485. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s60.chunk.ts +4 -0
  486. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n00.chunk.ts +4 -0
  487. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n15.chunk.ts +4 -0
  488. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n30.chunk.ts +4 -0
  489. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n45.chunk.ts +4 -0
  490. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n60.chunk.ts +4 -0
  491. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n75.chunk.ts +4 -0
  492. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105s15.chunk.ts +4 -0
  493. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n00.chunk.ts +4 -0
  494. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n15.chunk.ts +4 -0
  495. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n30.chunk.ts +4 -0
  496. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n45.chunk.ts +4 -0
  497. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n60.chunk.ts +4 -0
  498. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n75.chunk.ts +4 -0
  499. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120s30.chunk.ts +4 -0
  500. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n30.chunk.ts +4 -0
  501. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n45.chunk.ts +4 -0
  502. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n60.chunk.ts +4 -0
  503. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n75.chunk.ts +4 -0
  504. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135s30.chunk.ts +4 -0
  505. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n45.chunk.ts +4 -0
  506. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n60.chunk.ts +4 -0
  507. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s15.chunk.ts +4 -0
  508. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s30.chunk.ts +4 -0
  509. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n00.chunk.ts +4 -0
  510. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n15.chunk.ts +4 -0
  511. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n45.chunk.ts +4 -0
  512. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n60.chunk.ts +4 -0
  513. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s15.chunk.ts +4 -0
  514. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s30.chunk.ts +4 -0
  515. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n00.chunk.ts +4 -0
  516. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n15.chunk.ts +4 -0
  517. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n45.chunk.ts +4 -0
  518. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n60.chunk.ts +4 -0
  519. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s15.chunk.ts +4 -0
  520. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s30.chunk.ts +4 -0
  521. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s45.chunk.ts +4 -0
  522. package/src/plugins/builtin/world-venue-map/natural-earth/world-50m.chunk.ts +4 -0
  523. package/src/plugins/builtin/world-venue-map/pane.tsx +28 -43
  524. package/src/plugins/builtin/world-venue-map/use-geo.ts +55 -0
  525. package/src/plugins/builtin/world-venue-map/venue-header.tsx +32 -0
  526. package/src/plugins/builtin/yield-curve/headless.ts +16 -0
  527. package/src/plugins/loader.ts +13 -1
  528. package/src/plugins/registry/pane-settings.ts +1 -0
  529. package/src/renderers/dom/build-assets.ts +81 -3
  530. package/src/renderers/dom/data-table/row.tsx +2 -0
  531. package/src/renderers/dom/desktop/controls.tsx +7 -3
  532. package/src/renderers/dom/desktop/list-view.tsx +35 -10
  533. package/src/renderers/dom/desktop/popover.tsx +73 -14
  534. package/src/renderers/dom/host/input.tsx +14 -3
  535. package/src/renderers/dom/styles.css +6 -7
  536. package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
  537. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
  538. package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
  539. package/src/renderers/opentui/host.tsx +5 -0
  540. package/src/sources/gloomberb-cloud/index.ts +25 -4
  541. package/src/sources/gloomberb-cloud/normalizers.ts +27 -3
  542. package/src/sources/provider-router/cached-routes.ts +4 -1
  543. package/src/sources/provider-router/financials.ts +8 -7
  544. package/src/sources/provider-router/history.ts +15 -10
  545. package/src/sources/sec-registrant.ts +75 -0
  546. package/src/state/app/context/index.tsx +24 -8
  547. package/src/state/hooks/quote-streaming.ts +41 -26
  548. package/src/sync/core-contributors.ts +4 -0
  549. package/src/telemetry/crash-reports.ts +4 -1
  550. package/src/test-support/cli-context.ts +10 -2
  551. package/src/theme/heat-colors.ts +266 -84
  552. package/src/time-series/resolve.ts +1 -1
  553. package/src/time-series/use-chart-resolution.ts +1 -1
  554. package/src/types/context-menu.ts +6 -0
  555. package/src/types/data-provider.ts +2 -0
  556. package/src/types/financials.ts +3 -0
  557. package/src/types/headless.ts +79 -2
  558. package/src/types/plugin.ts +20 -1
  559. package/src/types/ticker.ts +15 -0
  560. package/src/ui/host.tsx +14 -0
  561. package/src/updater/index.ts +9 -11
  562. package/src/utils/async-deadline.ts +21 -0
  563. package/src/utils/cli-output.ts +5 -1
  564. package/src/utils/crypto-pair.ts +41 -0
  565. package/src/utils/currency-units.ts +14 -0
  566. package/src/utils/depositary-receipt.ts +26 -0
  567. package/src/utils/exchanges.ts +72 -0
  568. package/src/utils/fire-and-forget.ts +24 -0
  569. package/src/utils/format.ts +13 -0
  570. package/src/utils/price-earnings.ts +70 -3
  571. package/src/utils/price-history.ts +58 -13
  572. package/src/utils/rpc-timeout-error.ts +27 -0
  573. package/src/utils/sec.ts +13 -1
  574. package/src/utils/utc-time.ts +79 -0
@@ -1,5 +1,7 @@
1
1
  import { apiClient } from "../../../api-client";
2
2
  import { getSharedMarketDataCoordinator, MarketDataCoordinator, resolveEntryValue } from "../../../market-data/coordinator";
3
+ import { fxLegForCurrency } from "../../../market-data/coordinator/fx-legs";
4
+ import { dailyFxCloses, FxHistoryError, type FxCloses } from "../../../market-data/fx-closes";
3
5
  import type { InstrumentRef } from "../../../market-data/request-types";
4
6
  import type { ManualChartResolution } from "../../../time-series/resolution";
5
7
  import type { DataProvider } from "../../../types/data-provider";
@@ -8,6 +10,7 @@ import { abortable, abortError, settleWithin } from "../../../utils/async-deadli
8
10
  import { errorMessage } from "../../../utils/errors";
9
11
  import { isUsListingExchange } from "../../../utils/exchanges";
10
12
  import { fetchEarningsHistory } from "../earnings/client";
13
+ import { epsFxCurrencies } from "./model";
11
14
  import { reportDatesFrom, type ReportDate } from "./report-dates";
12
15
 
13
16
  export interface PeBandInputs {
@@ -20,6 +23,10 @@ export interface PeBandInputs {
20
23
  historyError: string | null;
21
24
  /** Report dates for US listings; empty for the rest and when the earnings history is unavailable. */
22
25
  reports: ReportDate[];
26
+ /** Daily FX closes for each currency a dollar-priced listing reports its EPS in. */
27
+ fx: Map<string, FxCloses>;
28
+ /** Why a currency's FX closes are missing; its EPS then stays unconverted. */
29
+ fxError: string | null;
23
30
  fetchedAt: number;
24
31
  }
25
32
 
@@ -31,7 +38,40 @@ const REPORT_LIMIT = 40;
31
38
  /** Report dates only refine the figures' dates, so the pane does not wait longer than this for them. */
32
39
  const REPORT_DEADLINE_MS = 8_000;
33
40
 
34
- type ReportClient = Pick<typeof apiClient, "getCloudEarningsHistory">;
41
+ /** Daily FX closes back this far before the oldest statement, so its weeks find a close. */
42
+ const FX_LEAD_DAYS = 14;
43
+ /** About thirty years of daily closes; a longer reply is not a daily FX history. */
44
+ const FX_MAX_ROWS = 8_000;
45
+
46
+ type ReportClient = Pick<typeof apiClient, "getCloudEarningsHistory" | "getCloudHistory">;
47
+
48
+ /**
49
+ * Completed daily closes of each currency's dollar pair, from just before the
50
+ * oldest statement in that currency. Never rejects but on cancel: a missing
51
+ * or stale history is said in `error`, and that currency's EPS stays unconverted.
52
+ */
53
+ async function loadFxCloses(financials: TickerFinancials, client: ReportClient, signal: AbortSignal | undefined, now: Date):
54
+ Promise<{ fx: Map<string, FxCloses>; error: string | null }> {
55
+ const fx = new Map<string, FxCloses>();
56
+ const errors: string[] = [];
57
+ const rows = [...financials.annualStatements, ...financials.quarterlyStatements];
58
+ await Promise.all(epsFxCurrencies(financials).map(async (currency) => {
59
+ const leg = fxLegForCurrency(currency)!;
60
+ const oldest = rows.filter((row) => row.currency?.trim().toUpperCase() === currency && Number.isFinite(Date.parse(row.date)))
61
+ .reduce((min, row) => Math.min(min, Date.parse(row.date)), now.getTime());
62
+ try {
63
+ const response = await client.getCloudHistory(leg.instrument.symbol, leg.instrument.exchange ?? "", {
64
+ interval: "1day", startDate: new Date(oldest - FX_LEAD_DAYS * 86_400_000).toISOString().slice(0, 10),
65
+ endDate: now.toISOString().slice(0, 10), outputsize: FX_MAX_ROWS,
66
+ }, signal ? { signal } : undefined);
67
+ fx.set(currency, dailyFxCloses(response, leg, now, FX_MAX_ROWS));
68
+ } catch (error) {
69
+ if (signal?.aborted) throw abortError(CANCELLED);
70
+ errors.push(`daily ${currency} FX closes are ${error instanceof FxHistoryError && error.problem === "stale" ? "stale" : "unavailable"}`);
71
+ }
72
+ }));
73
+ return { fx, error: errors.length ? errors.join("; ") : null };
74
+ }
35
75
 
36
76
  /** Never rejects: a listing without reports, an outage or a refused session leaves the figures as they are. */
37
77
  function loadReportDates(symbol: string, client: ReportClient): Promise<ReportDate[]> {
@@ -73,12 +113,17 @@ export async function loadPeBandInputs(
73
113
  const entry = chart.status === "fulfilled" ? chart.value : null;
74
114
  const history = entry ? resolveEntryValue(entry) ?? [] : [];
75
115
  const quote = value?.quote;
76
- const reports = await abortable(early ?? (isUsListingExchange(quote?.listingExchangeName ?? quote?.exchangeName ?? quote?.fullExchangeName)
77
- ? loadReportDates(instrument.symbol, cloud) : Promise.resolve([])), request.signal, CANCELLED);
116
+ const [reports, fx] = await abortable(Promise.all([
117
+ early ?? (isUsListingExchange(quote?.listingExchangeName ?? quote?.exchangeName ?? quote?.fullExchangeName)
118
+ ? loadReportDates(instrument.symbol, cloud) : Promise.resolve([])),
119
+ value ? loadFxCloses(value, cloud, request.signal, new Date(now)) : Promise.resolve({ fx: new Map<string, FxCloses>(), error: null }),
120
+ ]), request.signal, CANCELLED);
78
121
  return {
79
122
  financials: value,
80
123
  history,
81
124
  reports,
125
+ fx: fx.fx,
126
+ fxError: fx.error,
82
127
  stale: !!value?.fundamentals?.stale || !!entry?.error || (entry?.staleAt != null && entry.staleAt <= now),
83
128
  error: financials.status === "rejected" ? errorMessage(financials.reason) : null,
84
129
  historyError: chart.status === "rejected" ? errorMessage(chart.reason)
@@ -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,8 +11,11 @@ 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
  ],
@@ -20,8 +24,16 @@ export const peBandHeadless: HeadlessPaneDefinition<"bundle"> = {
20
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, reports: inputs.reports });
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,7 +1,10 @@
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";
6
9
  import { datedByReport, type ReportDate } from "./report-dates";
7
10
 
@@ -43,8 +46,24 @@ export interface PeBandRow extends EpsStep {
43
46
  /** The close of the week the figure became known, in price units. */
44
47
  price: number | null;
45
48
  pe: number | null;
46
- /** 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. */
47
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;
48
67
  }
49
68
 
50
69
  export interface PeBandModel {
@@ -69,6 +88,8 @@ export interface PeBandModel {
69
88
  undated: number;
70
89
  /** Trailing sums in the window that are unavailable (a quarter's EPS is withheld or not reported). */
71
90
  unavailable: number;
91
+ /** Set when today's EPS is in another currency than the dollar price. */
92
+ conversion: EpsConversion | null;
72
93
  /** Blocks the pane: nothing can be priced. */
73
94
  error: string | null;
74
95
  /** The pane still draws, but without bands. */
@@ -131,6 +152,37 @@ export function perShareDigits(value: number): number {
131
152
 
132
153
  export const formatPerShare = (value: number) => value.toFixed(perShareDigits(value));
133
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
+
134
186
  function quantile(sorted: readonly number[], q: number): number {
135
187
  const position = (sorted.length - 1) * q;
136
188
  const low = Math.floor(position), high = Math.ceil(position);
@@ -197,30 +249,47 @@ export function stepAt(steps: readonly EpsStep[], time: number): EpsStep | null
197
249
  export function projectPeBand(
198
250
  financials: TickerFinancials | null,
199
251
  history: readonly PricePoint[],
200
- options: { symbol: string; lookbackYears: number; now?: number; reports?: readonly ReportDate[] },
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
+ },
201
259
  ): PeBandModel {
202
260
  const currency = financials?.quote?.currency ?? null;
203
261
  const empty: PeBandModel = { symbol: options.symbol, currency, weeks: [], rows: [], multiples: [], current: null, range: null, sample: null, bandCeiling: null,
204
- undated: 0, unavailable: 0, error: null, notice: null };
262
+ undated: 0, unavailable: 0, conversion: null, error: null, notice: null };
205
263
  if (!financials) return { ...empty, error: "Fundamentals unavailable." };
206
264
  const allSteps = trailingEpsSteps(financials, options.reports);
207
265
  const latest = allSteps.findLast((step) => step.eps != null);
208
266
  if (!latest) return { ...empty, error: "No trailing EPS on record." };
209
267
  const units = createValuationCurrencyContext(financials);
210
268
  // Price units per statement unit: 1 for the same currency, 100 for a pence price against pound statements.
211
- const scale = (step: EpsStep) => {
269
+ const unitScale = (step: EpsStep) => {
212
270
  const factor = units.priceInStatementUnits({ date: step.periodEnd, currency: step.currency ?? undefined }, 1);
213
271
  return factor ? 1 / factor : null;
214
272
  };
215
- // ADRs and cross-listings report in one currency and trade in another; no FX rate is applied.
216
- if (scale(latest) == null) {
217
- return { ...empty, error: currency && latest.currency
218
- ? `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}` : ""}.` };
219
282
  }
220
- const priced = (step: EpsStep | null) => {
221
- const factor = step?.eps != null ? scale(step) : null;
222
- 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;
223
291
  };
292
+ const day = (time: Date | number) => new Date(time).toISOString().slice(0, 10);
224
293
 
225
294
  const now = options.now ?? Date.now();
226
295
  const firstKnown = allSteps.find((step) => step.eps != null)!.knownAt.getTime();
@@ -230,7 +299,9 @@ export function projectPeBand(
230
299
  .filter((point) => Number.isFinite(point.date.getTime()) && Number.isFinite(point.close) && point.close > 0)
231
300
  .sort((a, b) => a.date.getTime() - b.date.getTime());
232
301
  const weeks = sorted.filter((point) => point.date.getTime() >= start).map((point): PeBandWeek => {
233
- 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;
234
305
  const positive = eps != null && eps > 0 ? eps : null;
235
306
  return { date: point.date, close: point.close, eps: positive, pe: positive ? point.close / positive : null };
236
307
  });
@@ -239,10 +310,12 @@ export function projectPeBand(
239
310
  const rows = steps.filter((step) => step.eps != null).map((step): PeBandRow => {
240
311
  const time = step.knownAt.getTime();
241
312
  const price = sorted.findLast((point) => point.date.getTime() <= time)?.close ?? null;
242
- const eps = priced(step);
243
- 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
244
317
  && Math.abs(Date.parse(step.periodEnd) - Date.parse(other.periodEnd) - 365 * DAY_MS) <= 20 * DAY_MS);
245
- 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,
246
319
  yoy: step.eps != null && prior?.eps != null && step.eps > 0 && prior.eps > 0 ? step.eps / prior.eps - 1 : null };
247
320
  }).reverse();
248
321
 
@@ -252,13 +325,16 @@ export function projectPeBand(
252
325
  const quotePrice = financials.quote?.price;
253
326
  const price = quotePrice != null && Number.isFinite(quotePrice) && quotePrice > 0 ? quotePrice : sorted.at(-1)?.close ?? null;
254
327
  const currentStep = stepAt(allSteps, now);
255
- 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;
256
331
  const currentPe = price != null && currentEps != null && currentEps > 0 ? price / currentEps : null;
257
332
  const multiples = chooseMultiples(pes, currentPe);
258
333
  const above = currentPe == null ? undefined : multiples.find((multiple) => multiple >= currentPe);
259
334
  const highestClose = weeks.reduce((max, week) => Math.max(max, week.close), 0);
260
335
  return {
261
336
  ...empty, weeks, rows, multiples,
337
+ conversion: latestFx ? { currency: latestFx.currency, latest: fxCloseOnOrBefore(latestFx, day(now))! } : null,
262
338
  sample: ranked.length ? { start: ranked[0]!.date, weeks: ranked.length } : null,
263
339
  bandCeiling: highestClose > 0 ? Math.max(highestClose * 1.5, above != null && currentEps != null ? above * currentEps : 0) : null,
264
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
  }
@@ -95,9 +103,11 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
95
103
  const lookbackYears = Number(lookback);
96
104
  const model = useMemo(() => inputs.data
97
105
  ? projectPeBand(inputs.data.financials, inputs.data.history, { symbol: symbol ?? "", lookbackYears: Number.isFinite(lookbackYears) ? lookbackYears : 10,
98
- reports: inputs.data.reports })
106
+ reports: inputs.data.reports, fx: inputs.data.fx, fxError: inputs.data.fxError })
99
107
  : null, [inputs.data, lookbackYears, symbol]);
100
108
  const rows = model?.rows ?? [];
109
+ const conversion = model?.conversion ?? null;
110
+ const columns = useMemo(() => tableColumns(conversion), [conversion?.currency, conversion?.latest.rate]);
101
111
  const series = useMemo(() => model && !model.error ? chartSeries(model, colors) : [], [model, colors]);
102
112
  const currency = model?.currency ?? undefined;
103
113
  const firstWeek = model?.weeks[0]?.date;
@@ -117,7 +127,8 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
117
127
  const unavailableNotice = model?.unavailable
118
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;
119
129
  usePaneNoticeFooter({ registrationId: "pe-band-notices", focused, notices: [...new Set([identityError, inputs.error, inputs.data?.historyError,
120
- 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))] });
121
132
  usePaneFooter("pe-band", () => ({ info: [
122
133
  ...(inputs.loading ? [{ id: "loading", parts: [{ text: "loading statements", tone: "muted" as const }] }] : []),
123
134
  ...(inputs.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
@@ -128,15 +139,19 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
128
139
  if (!model?.current || model.error) return [];
129
140
  const { current, range } = model;
130
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)}` : "";
131
146
  return [
132
147
  { id: "pe", label: "P/E", value: multiple(current.pe),
133
148
  detail: current.pe != null ? formatPercentileRank(current.percentile, windowLabel) : current.eps != null ? "EPS not positive" : "EPS unavailable" },
134
149
  { id: "median", label: "Median P/E", value: multiple(range?.median), detail: range ? `${multiple(range.min)} to ${multiple(range.max)}` : undefined },
135
150
  { id: "eps", label: "EPS", value: current.eps == null ? "--" : formatCurrency(current.eps, currency, perShareDigits(current.eps)),
136
- detail: step ? periodLabel(step) : undefined },
151
+ detail: step ? `${periodLabel(step)}${converted}` : undefined },
137
152
  { id: "price", label: "Price", value: formatCurrency(current.price, currency, perShareDigits(current.price)) },
138
153
  ];
139
- }, [model, windowLabel, currency]);
154
+ }, [model, windowLabel, currency, conversion]);
140
155
 
141
156
  const formatPrice = useCallback((value: number) => formatCurrency(value, currency, perShareDigits(value)), [currency]);
142
157
  const formatAxis = useMemo(() => spanAxisFormatter((value, digits) => formatCurrency(value, currency, Math.max(2, digits))), [currency]);
@@ -145,14 +160,16 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
145
160
  if (!symbol) return <EmptyState title="Choose a ticker." />;
146
161
  return <Box width={width} height={height} flexDirection="column" overflow="hidden">
147
162
  <PaneStatusBody subject="P/E band" loading={inputs.loading && !model} error={!model ? inputs.error ?? identityError ?? null : model.error} empty={false}>
148
- {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}
149
164
  getItemKey={rowKey} emptyStateTitle="No trailing EPS in this window." sortColumnId={null} sortDirection="desc" resetScrollKey={symbol}
150
165
  selection={{ kind: "id", selectedId: selected, getId: rowKey, onChange: (id) => setSelectedId(id) }}
151
166
  selectedTextOverridesCellColor
152
167
  getExportMetadata={() => [["symbol", model.symbol], ["currency", model.currency ?? ""],
153
- ["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"]]}
154
171
  renderCell={(row, column) => renderCell(row, column, colors)}
155
- 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}
156
173
  chart={model.weeks.length >= 2 ? { series, formatValue: formatPrice, formatAxisValue: formatAxis, remoteKind: "pe-band", ...link } : null} />} />
157
174
  : null}
158
175
  </PaneStatusBody>
@@ -1,5 +1,6 @@
1
1
  import { apiClient } from "../../../api-client";
2
- import type { PerpBoardPayload, PerpBoardRow, PerpEquityListing, 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,10 +38,49 @@ 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);
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) });
37
84
  function validatePerpsEquity(data: PerpBoardPayload, listing: PerpEquityListing): PerpBoardPayload {
38
85
  validatePerpsBoard(data);
39
86
  const same = (value: PerpEquityListing | null | undefined) => value?.symbol === listing.symbol && value.exchange === listing.exchange;