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
@@ -9,24 +9,53 @@ import type {
9
9
  import { createPluginCache } from "../../../data/plugin-cache";
10
10
  import { loadCloudResource } from "../shared/cloud-resource";
11
11
  import { canonicalExchange, normalizeSymbol } from "../../../utils/exchanges";
12
+ import { resolveCurrencyUnit } from "../../../utils/currency-units";
12
13
  import type { PricePoint } from "../../../types/financials";
13
14
  import { resolveDatedReturns, type DatedReturn } from "./metrics";
14
15
  import { qualifySharpeCadence } from "./sharpe-cadence";
15
16
  import { evidenceDay } from "./risk-evidence";
17
+ import { FX_LIVE_RATE_MAX_DEVIATION } from "../../../market-data/coordinator/fx-legs";
18
+ import {
19
+ convertClosesToUsd,
20
+ FX_DISAGREES,
21
+ FX_NO_PAIR,
22
+ FX_UNAVAILABLE,
23
+ riskConversion,
24
+ validateFxHistory,
25
+ type FxCloses,
26
+ type RiskConversion,
27
+ } from "./risk-fx";
16
28
 
17
29
  export interface RiskInstrument {
18
30
  symbol: string;
19
31
  exchange: string;
20
32
  }
33
+ /** A holding to load: what ranks it by value before any daily history is requested. */
34
+ export interface RiskHoldingRequest extends RiskInstrument {
35
+ /** Signed quantity; with a USD quote it ranks the holding by current value. */
36
+ quantity?: number;
37
+ /** The currency the position is recorded in, for the rate that values it when it is left out. */
38
+ currency?: string | null;
39
+ /** USD value from the broker's own snapshot, ranking a holding no USD quote values. */
40
+ snapshotValue?: number | null;
41
+ /** Outside the basket model whatever its history says (a short, a derivative): quoted, never history-requested. */
42
+ unsupported?: boolean;
43
+ }
21
44
  interface RiskMarketHistory {
22
45
  instrument: RiskInstrument;
23
46
  currency: string | null;
24
47
  quote: CloudQuotePayload | null;
25
- points: CloudPricePointPayload[];
26
48
  returns: DatedReturn[];
27
49
  asOf: string | null;
28
50
  /** Latest completed close, used as the mark when no current quote arrived. */
29
51
  closeMark?: { price: number; date: string; currency: string } | null;
52
+ /** The listing's own price when it does not quote in USD: it values a left-out holding, never a return. */
53
+ listing?: { price: number; currency: string } | null;
54
+ /**
55
+ * Set when the listing quotes in another currency and its closes were
56
+ * restated in USD at same-date daily FX closes; quote and closeMark are then in USD.
57
+ */
58
+ converted?: { currency: string; fxAsOf: string } | null;
30
59
  error: string | null;
31
60
  }
32
61
  export interface RiskMarketSnapshot {
@@ -36,7 +65,22 @@ export interface RiskMarketSnapshot {
36
65
  fetchedAt: string;
37
66
  warnings: string[];
38
67
  brokerOptions?: import("./risk-options").PortfolioOptionBook;
68
+ /** USD per unit of each listing or position currency of the holdings, at load time. */
69
+ fxRates?: Record<string, number>;
39
70
  }
71
+ /**
72
+ * The most holdings one load requests daily history for, largest current USD
73
+ * value first. Each holding costs one Cloud request of up to 18 months of
74
+ * closes, four in flight, again after every two-minute cache expiry, and the
75
+ * correlation view pairs every basket holding with every other. See
76
+ * docs/research-data.md for the measured cost behind the number.
77
+ */
78
+ export const RISK_HISTORY_LIMIT = 150;
79
+ /** Holdings quoted in one load, 50 to a batch request. */
80
+ const RISK_QUOTE_LIMIT = 500;
81
+ const HISTORY_CONCURRENCY = 4;
82
+ const MAX_FX_CURRENCIES = 24;
83
+ export const BEYOND_SIZE_REASON = `Beyond the supported size: only the ${RISK_HISTORY_LIMIT} largest holdings by value are modelled`;
40
84
  // Listing venues as the quotes report them; MTUM lists on Cboe BZX, not Arca.
41
85
  export const RISK_FACTOR_INSTRUMENTS: RiskInstrument[] = [
42
86
  ["SPY", "ARCA"],
@@ -56,22 +100,23 @@ const message = (error: unknown) =>
56
100
  export const portfolioRiskCache = createPluginCache<RiskMarketSnapshot>({
57
101
  kind: "portfolio-risk",
58
102
  source: "gloom-cloud",
59
- schemaVersion: 2,
103
+ schemaVersion: 4,
60
104
  policy: { staleMs: 2 * 60_000, expireMs: 24 * 60 * 60_000 },
61
105
  });
62
106
  type RiskCloudClient = Pick<
63
107
  typeof apiClient,
64
- "getCloudHistory" | "getCloudQuotesBatch" | "getCloudFredSeries"
108
+ "getCloudHistory" | "getCloudQuotesBatch" | "getCloudFredSeries" | "getCloudExchangeRate"
65
109
  >;
66
110
 
67
111
  function validateRiskQuote(
68
112
  quote: CloudQuotePayload | null,
69
113
  instrument: RiskInstrument,
70
114
  now = new Date(),
115
+ currency = "USD",
71
116
  ): asserts quote is CloudQuotePayload {
72
117
  // A stale quote still establishes listing identity; it is not used as a mark.
73
- if (!quote || quote.currency !== "USD")
74
- throw new Error("Current USD listing identity unavailable");
118
+ if (!quote || quote.currency !== currency)
119
+ throw new Error(currency === "USD" ? "Current USD listing identity unavailable" : "Current listing identity unavailable");
75
120
  if (normalizeSymbol(quote.symbol) !== normalizeSymbol(instrument.symbol))
76
121
  throw new Error("Quote symbol differs from the requested holding");
77
122
  const exchange = canonicalExchange(
@@ -94,17 +139,23 @@ function validateRiskQuote(
94
139
  throw new Error("Current quote price or timestamp unavailable");
95
140
  }
96
141
 
97
- /** Daily Cloud observations, currency and listing metadata must agree before risk math. */
142
+ /**
143
+ * Daily Cloud observations, currency and listing metadata must agree before
144
+ * risk math. With a conversion the listing quotes in that currency and its
145
+ * closes are restated in USD at same-date FX closes on the basket's calendar.
146
+ */
98
147
  export function validateRiskHistory(
99
148
  response: CloudMarketResponse<CloudPricePointPayload[]>,
100
149
  instrument: RiskInstrument,
101
150
  quote: CloudQuotePayload | null,
102
151
  now = new Date(),
152
+ conversion?: { listingCurrency: string; divisor: number; fx: FxCloses },
103
153
  ): {
104
- points: CloudPricePointPayload[];
105
154
  returns: DatedReturn[];
106
155
  asOf: string;
107
156
  closeMark: { price: number; date: string; currency: string } | null;
157
+ /** Latest completed close in the returns' currency, whatever marks the holding. */
158
+ latestClose: { price: number; date: string };
108
159
  } {
109
160
  if (
110
161
  response.status !== "success" ||
@@ -118,9 +169,10 @@ export function validateRiskHistory(
118
169
  response.providerMeta?.currency ??
119
170
  quote?.currency ??
120
171
  (US_LISTING_VENUES.has(canonicalExchange(instrument.exchange)) ? "USD" : null);
121
- if (quote) validateRiskQuote(quote, instrument, now);
122
- else if (historyCurrency !== "USD")
123
- throw new Error("Current USD listing identity unavailable");
172
+ const expectedCurrency = conversion?.listingCurrency ?? "USD";
173
+ if (quote) validateRiskQuote(quote, instrument, now, expectedCurrency);
174
+ else if (historyCurrency !== expectedCurrency)
175
+ throw new Error(conversion ? "Current listing identity unavailable" : "Current USD listing identity unavailable");
124
176
  const exchange = canonicalExchange(
125
177
  instrument.exchange || quote?.listingExchangeName || "",
126
178
  );
@@ -182,24 +234,37 @@ export function validateRiskHistory(
182
234
  ...point,
183
235
  date: new Date(point.date),
184
236
  }));
185
- const resolved = resolveDatedReturns(prices);
186
- if (resolved.integrity)
237
+ const local = resolveDatedReturns(prices);
238
+ if (local.integrity)
187
239
  throw new Error("Daily history has inconsistent OHLC observations");
188
- const cadence = qualifySharpeCadence(resolved.returns, [
189
- { symbol: instrument.symbol, exchange, history: prices },
240
+ // A converted series is on the NYSE calendar the basket and SPY share.
241
+ const series = conversion
242
+ ? convertClosesToUsd(
243
+ points.map((point) => ({ date: new Date(point.date).toISOString().slice(0, 10), close: point.close })),
244
+ conversion.fx,
245
+ conversion.divisor,
246
+ )
247
+ : prices;
248
+ const returns = conversion ? resolveDatedReturns(series).returns : local.returns;
249
+ const cadence = qualifySharpeCadence(returns, [
250
+ { symbol: instrument.symbol, exchange: conversion ? "NYSE" : exchange, history: series },
190
251
  ]);
191
252
  if (!cadence.supported)
192
253
  throw new Error(cadence.reason ?? "Daily session cadence unavailable");
193
- const lastPoint = points.at(-1)!;
254
+ const lastPoint = series.at(-1)!;
255
+ const latestClose = {
256
+ price: lastPoint.close,
257
+ date: lastPoint.date.toISOString().slice(0, 10),
258
+ };
194
259
  return {
195
- points,
196
- returns: resolved.returns,
197
- asOf: last,
260
+ returns,
261
+ asOf: latestClose.date,
198
262
  // A missing or stale quote is not a current mark; the latest dated close is.
199
263
  closeMark:
200
264
  quote && !quote.stale
201
265
  ? null
202
- : { price: lastPoint.close, date: last, currency: historyCurrency! },
266
+ : { ...latestClose, currency: conversion ? "USD" : historyCurrency! },
267
+ latestClose,
203
268
  };
204
269
  }
205
270
  function validateFred(
@@ -229,38 +294,75 @@ function validateFred(
229
294
  return data;
230
295
  }
231
296
 
297
+ /** The USD value a holding ranks by before its history loads; unknown ranks last. */
298
+ function rankValue(request: RiskHoldingRequest, quote: CloudQuotePayload | null): number {
299
+ if (quote && request.quantity != null && Number.isFinite(request.quantity))
300
+ return Math.abs(request.quantity * quote.price);
301
+ const snapshot = request.snapshotValue;
302
+ return snapshot != null && Number.isFinite(snapshot) ? Math.abs(snapshot) : -1;
303
+ }
304
+ const largestFirst = (
305
+ left: { rank: number; order: number },
306
+ right: { rank: number; order: number },
307
+ ) => right.rank - left.rank || left.order - right.order;
308
+
232
309
  /**
233
- * The signal stops the fetch: no request starts once it aborts, the ones in
234
- * flight are cancelled, and the call rejects instead of returning a partial
235
- * snapshot nobody is waiting for.
310
+ * Quotes every holding, then requests daily history only for the factor
311
+ * proxies and the largest holdings by current USD value that can enter the
312
+ * basket, up to RISK_HISTORY_LIMIT. A holding that quotes in another currency
313
+ * competes at its value at the current rate and, when requested, also loads
314
+ * its currency's daily FX history once per currency, so its closes convert to
315
+ * USD. Holdings with no FX pair, positions the caller marks unsupported and
316
+ * holdings past the limit get no history request; their entry says why. The
317
+ * signal stops the fetch: no request starts once it
318
+ * aborts, the ones in flight are cancelled, and the call rejects instead of
319
+ * returning a partial snapshot nobody is waiting for.
236
320
  */
237
321
  export async function fetchPortfolioRiskMarket(
238
- instruments: readonly RiskInstrument[],
322
+ holdings: readonly RiskHoldingRequest[],
239
323
  client: RiskCloudClient = apiClient,
240
324
  now = new Date(),
241
325
  signal?: AbortSignal,
242
326
  ): Promise<RiskMarketSnapshot> {
243
327
  signal?.throwIfAborted();
244
- const unique = [
245
- ...new Map(
246
- [...instruments, ...RISK_FACTOR_INSTRUMENTS].map((row) => [
247
- riskInstrumentId(row),
248
- {
249
- symbol: normalizeSymbol(row.symbol),
250
- exchange: canonicalExchange(row.exchange),
251
- },
252
- ]),
253
- ).values(),
328
+ const normalized = (row: RiskInstrument): RiskInstrument => ({
329
+ symbol: normalizeSymbol(row.symbol),
330
+ exchange: canonicalExchange(row.exchange),
331
+ });
332
+ const factors = new Map(
333
+ RISK_FACTOR_INSTRUMENTS.map((row) => [riskInstrumentId(row), normalized(row)]),
334
+ );
335
+ const entries = new Map<
336
+ string,
337
+ { id: string; instrument: RiskInstrument; request: RiskHoldingRequest; order: number; rank: number }
338
+ >();
339
+ for (const request of holdings) {
340
+ const id = riskInstrumentId(request);
341
+ if (!entries.has(id))
342
+ entries.set(id, {
343
+ id,
344
+ instrument: normalized(request),
345
+ request,
346
+ order: entries.size,
347
+ rank: rankValue(request, null),
348
+ });
349
+ }
350
+ const quoted = [...entries.values()].sort(largestFirst).slice(0, RISK_QUOTE_LIMIT);
351
+ const targets = [
352
+ ...new Map([
353
+ ...quoted.map((row) => [row.id, row.instrument] as const),
354
+ ...factors,
355
+ ]).values(),
254
356
  ];
255
- if (unique.length > 87)
256
- throw new Error(
257
- "Portfolio risk supports at most 80 holdings plus its factor proxies",
258
- );
259
357
  const quotes = new Map<string, CloudQuotePayload>();
358
+ const listings = new Map<string, { price: number; currency: string }>();
359
+ // Listings quoted in another currency that convert to USD, and why the others do not.
360
+ const conversions = new Map<string, RiskConversion>();
361
+ const foreignReasons = new Map<string, string>();
260
362
  const warnings: string[] = [];
261
- for (let start = 0; start < unique.length; start += 50) {
363
+ for (let start = 0; start < targets.length; start += 50) {
262
364
  signal?.throwIfAborted();
263
- const requested = unique.slice(start, start + 50);
365
+ const requested = targets.slice(start, start + 50);
264
366
  try {
265
367
  const response = await client.getCloudQuotesBatch(
266
368
  requested,
@@ -275,17 +377,38 @@ export async function fetchPortfolioRiskMarket(
275
377
  canonicalExchange(item.exchange),
276
378
  );
277
379
  if (
278
- target &&
279
- item.status === "success" &&
280
- item.data &&
281
- normalizeSymbol(item.data.symbol) === normalizeSymbol(target.symbol)
282
- ) {
380
+ !target ||
381
+ item.status !== "success" ||
382
+ !item.data ||
383
+ normalizeSymbol(item.data.symbol) !== normalizeSymbol(target.symbol)
384
+ )
385
+ continue;
386
+ const id = riskInstrumentId(target);
387
+ // A holding quoted in another currency enters the USD basket only
388
+ // through its daily closes restated at same-date FX closes.
389
+ if (!factors.has(id) && item.data.currency && item.data.currency !== "USD") {
390
+ if (Number.isFinite(item.data.price) && item.data.price > 0)
391
+ listings.set(id, { price: item.data.price, currency: item.data.currency });
392
+ if (entries.get(id)?.request.unsupported) continue;
393
+ const conversion = riskConversion(item.data.currency);
394
+ if (!conversion) {
395
+ foreignReasons.set(id, FX_NO_PAIR);
396
+ continue;
397
+ }
283
398
  try {
284
- validateRiskQuote(item.data, target, now);
285
- quotes.set(riskInstrumentId(target), item.data);
399
+ validateRiskQuote(item.data, target, now, item.data.currency);
400
+ quotes.set(id, item.data);
401
+ conversions.set(id, conversion);
286
402
  } catch (error) {
287
- warnings.push(`${target.symbol}: ${message(error)}`);
403
+ foreignReasons.set(id, message(error));
288
404
  }
405
+ continue;
406
+ }
407
+ try {
408
+ validateRiskQuote(item.data, target, now);
409
+ quotes.set(id, item.data);
410
+ } catch (error) {
411
+ warnings.push(`${target.symbol}: ${message(error)}`);
289
412
  }
290
413
  }
291
414
  } catch (error) {
@@ -293,6 +416,63 @@ export async function fetchPortfolioRiskMarket(
293
416
  warnings.push(`Holding marks: ${message(error)}`);
294
417
  }
295
418
  }
419
+ // Current rates value a foreign holding: its rank, its mark, and its size when left out.
420
+ const fxCurrencies = [
421
+ ...new Set(
422
+ [...entries.values()].flatMap((row) => {
423
+ const currency = resolveCurrencyUnit(
424
+ listings.get(row.id)?.currency ?? (quotes.has(row.id) ? null : row.request.currency),
425
+ ).currency;
426
+ return currency && currency !== "USD" ? [currency] : [];
427
+ }),
428
+ ),
429
+ ].slice(0, MAX_FX_CURRENCIES);
430
+ const fx = await Promise.allSettled(
431
+ fxCurrencies.map(async (currency) => {
432
+ const response = await client.getCloudExchangeRate(currency);
433
+ const rate = response.status === "success" || response.status === "partial"
434
+ ? response.data?.rate
435
+ : undefined;
436
+ if (typeof rate !== "number" || !Number.isFinite(rate) || rate <= 0)
437
+ throw new Error(response.reasonCode ?? "Rate unavailable");
438
+ return [currency, rate] as const;
439
+ }),
440
+ );
441
+ signal?.throwIfAborted();
442
+ const fxRates: Record<string, number> = {};
443
+ for (const [index, result] of fx.entries()) {
444
+ if (result.status === "fulfilled") fxRates[result.value[0]] = result.value[1];
445
+ else {
446
+ if (isAccessDenied(result.reason)) throw result.reason;
447
+ warnings.push(`${fxCurrencies[index]} exchange rate: ${message(result.reason)}`);
448
+ }
449
+ }
450
+ /** The listing's quote in USD at the current rate; null when no rate converts it. */
451
+ const usdQuote = (id: string): CloudQuotePayload | null => {
452
+ const quote = quotes.get(id) ?? null,
453
+ conversion = conversions.get(id);
454
+ if (!quote || !conversion) return quote;
455
+ const rate = fxRates[conversion.currency];
456
+ return rate
457
+ ? { ...quote, currency: "USD", price: (quote.price * rate) / conversion.divisor }
458
+ : null;
459
+ };
460
+ const candidates = quoted
461
+ .filter(
462
+ (row) =>
463
+ !factors.has(row.id) &&
464
+ !row.request.unsupported &&
465
+ (!listings.has(row.id) || conversions.has(row.id)),
466
+ )
467
+ .map((row) => ({ ...row, rank: rankValue(row.request, usdQuote(row.id)) }))
468
+ .sort(largestFirst);
469
+ const requestedIds = new Set([
470
+ ...factors.keys(),
471
+ ...candidates.slice(0, RISK_HISTORY_LIMIT).map((row) => row.id),
472
+ ]);
473
+ const instruments = [
474
+ ...new Map([...factors, ...[...entries.values()].map((row) => [row.id, row.instrument] as const)]).entries(),
475
+ ];
296
476
  const start = new Date(now);
297
477
  start.setUTCMonth(start.getUTCMonth() - 18);
298
478
  start.setUTCDate(1);
@@ -301,54 +481,117 @@ export async function fetchPortfolioRiskMarket(
301
481
  endDate = new Date(Date.parse(today) - 86_400_000)
302
482
  .toISOString()
303
483
  .slice(0, 10);
304
- const histories: RiskMarketHistory[] = new Array(unique.length);
484
+ const historyParams = {
485
+ interval: "1day",
486
+ startDate,
487
+ // validateRiskHistory drops today's bar; ending on yesterday
488
+ // loses yesterday's close for most listings.
489
+ endDate: today,
490
+ outputsize: 1000,
491
+ rangeKey: "2Y",
492
+ } as const;
493
+ const histories: RiskMarketHistory[] = instruments.map(([id, instrument]) => {
494
+ const quote = quotes.get(id) ?? null,
495
+ listing = listings.get(id) ?? null;
496
+ return {
497
+ instrument,
498
+ quote,
499
+ listing,
500
+ currency: quote?.currency ?? listing?.currency ?? null,
501
+ returns: [],
502
+ asOf: null,
503
+ error: requestedIds.has(id)
504
+ ? null
505
+ : listing && !conversions.has(id)
506
+ ? (foreignReasons.get(id) ??
507
+ (entries.get(id)?.request.unsupported ? "Outside the long USD equity basket" : FX_UNAVAILABLE))
508
+ : entries.get(id)?.request.unsupported
509
+ ? "Outside the long USD equity basket"
510
+ : BEYOND_SIZE_REASON,
511
+ };
512
+ });
513
+ // One daily FX history per currency, shared by every holding that converts with it.
514
+ const fxHistories = new Map<string, Promise<FxCloses>>();
515
+ const fxHistory = (conversion: RiskConversion) => {
516
+ let loading = fxHistories.get(conversion.currency);
517
+ if (!loading) {
518
+ loading = (async () => {
519
+ const { symbol, exchange } = conversion.leg.instrument;
520
+ let closes: FxCloses;
521
+ try {
522
+ closes = validateFxHistory(
523
+ await client.getCloudHistory(symbol, exchange ?? "", historyParams, signal ? { signal } : undefined),
524
+ conversion.leg,
525
+ now,
526
+ );
527
+ } catch (error) {
528
+ if (isAccessDenied(error) || signal?.aborted) throw error;
529
+ throw new Error(message(error).startsWith("Foreign holdings") ? message(error) : FX_UNAVAILABLE);
530
+ }
531
+ // A latest close far from the current rate is a wrong or inverted pair, not a move.
532
+ const rate = fxRates[conversion.currency];
533
+ const latest = closes.closes.get(closes.asOf)!;
534
+ if (rate && Math.abs(latest / rate - 1) > FX_LIVE_RATE_MAX_DEVIATION)
535
+ throw new Error(FX_DISAGREES);
536
+ return closes;
537
+ })();
538
+ fxHistories.set(conversion.currency, loading);
539
+ }
540
+ return loading;
541
+ };
542
+ const pending = instruments.flatMap(([id], index) =>
543
+ requestedIds.has(id) ? [index] : [],
544
+ );
305
545
  let next = 0;
306
546
  const historyWork = Promise.all(
307
- Array.from({ length: Math.min(4, unique.length) }, async () => {
308
- while (next < unique.length) {
547
+ Array.from({ length: Math.min(HISTORY_CONCURRENCY, pending.length) }, async () => {
548
+ while (next < pending.length) {
309
549
  signal?.throwIfAborted();
310
- const index = next++,
311
- instrument = unique[index]!,
312
- quote = quotes.get(riskInstrumentId(instrument)) ?? null;
550
+ const index = pending[next++]!,
551
+ entry = histories[index]!,
552
+ conversion = conversions.get(instruments[index]![0]),
553
+ { instrument, quote } = entry;
313
554
  try {
555
+ const fxCloses = conversion ? await fxHistory(conversion) : null;
314
556
  const response = await client.getCloudHistory(
315
557
  instrument.symbol,
316
558
  instrument.exchange || quote?.listingExchangeName || "",
317
- {
318
- interval: "1day",
319
- startDate,
320
- // validateRiskHistory drops today's bar; ending on yesterday
321
- // loses yesterday's close for most listings.
322
- endDate: today,
323
- outputsize: 1000,
324
- rangeKey: "2Y",
325
- },
559
+ historyParams,
326
560
  signal ? { signal } : undefined,
327
561
  );
328
- const resolved = validateRiskHistory(
562
+ const { latestClose, ...resolved } = validateRiskHistory(
329
563
  response,
330
564
  instrument,
331
565
  quote,
332
566
  now,
567
+ conversion && fxCloses
568
+ ? { listingCurrency: conversion.listingCurrency, divisor: conversion.divisor, fx: fxCloses }
569
+ : undefined,
333
570
  );
571
+ if (conversion && fxCloses) {
572
+ // A current quote marks at the current rate; without one, or without
573
+ // a rate, the latest close at its own date's FX close does.
574
+ const current = quote && !quote.stale ? usdQuote(instruments[index]![0]) : null;
575
+ histories[index] = {
576
+ ...entry,
577
+ ...resolved,
578
+ quote: current,
579
+ currency: "USD",
580
+ closeMark: current ? null : { ...latestClose, currency: "USD" },
581
+ converted: { currency: conversion.listingCurrency, fxAsOf: fxCloses.asOf },
582
+ error: null,
583
+ };
584
+ continue;
585
+ }
334
586
  histories[index] = {
335
- instrument,
336
- quote,
587
+ ...entry,
337
588
  currency: quote?.currency ?? resolved.closeMark?.currency ?? null,
338
589
  ...resolved,
339
590
  error: null,
340
591
  };
341
592
  } catch (error) {
342
593
  if (isAccessDenied(error) || signal?.aborted) throw error;
343
- histories[index] = {
344
- instrument,
345
- quote,
346
- currency: quote?.currency ?? null,
347
- points: [],
348
- returns: [],
349
- asOf: null,
350
- error: message(error),
351
- };
594
+ histories[index] = { ...entry, error: message(error) };
352
595
  }
353
596
  }
354
597
  }),
@@ -384,18 +627,26 @@ export async function fetchPortfolioRiskMarket(
384
627
  volatility: fred[1]!.status === "fulfilled" ? fred[1]!.value : null,
385
628
  fetchedAt: now.toISOString(),
386
629
  warnings,
630
+ fxRates,
387
631
  };
388
632
  }
389
- const cacheKey = (instruments: readonly RiskInstrument[]) =>
390
- [...new Set(instruments.map(riskInstrumentId))].sort().join(",");
633
+ /** What changes the requests: identity, size and whether a holding can enter the basket. */
634
+ const cacheKey = (holdings: readonly RiskHoldingRequest[]) =>
635
+ [
636
+ ...new Set(
637
+ holdings.map(
638
+ (row) => `${riskInstrumentId(row)}|${row.quantity ?? ""}|${row.currency ?? ""}|${row.unsupported ? 1 : 0}`,
639
+ ),
640
+ ),
641
+ ].sort().join(",");
391
642
  export async function loadPortfolioRiskMarket(
392
- instruments: readonly RiskInstrument[],
643
+ holdings: readonly RiskHoldingRequest[],
393
644
  force = false,
394
645
  ) {
395
646
  const { payload, stale, refreshError } = await loadCloudResource(
396
647
  portfolioRiskCache,
397
- cacheKey(instruments),
398
- () => fetchPortfolioRiskMarket(instruments),
648
+ cacheKey(holdings),
649
+ () => fetchPortfolioRiskMarket(holdings),
399
650
  { force },
400
651
  );
401
652
  return {
@@ -0,0 +1,102 @@
1
+ import type { CloudMarketResponse, CloudPricePointPayload } from "../../../api-client/types";
2
+ import { fxLegForCurrency, type FxLeg } from "../../../market-data/coordinator/fx-legs";
3
+ import { dailyFxCloses, FxHistoryError, type FxCloses } from "../../../market-data/fx-closes";
4
+ import { getPublishedUsEquityCalendarDay } from "../../../market-data/published-us-sessions";
5
+ import type { PricePoint } from "../../../types/financials";
6
+ import { resolveCurrencyUnit } from "../../../utils/currency-units";
7
+
8
+ export type { FxCloses };
9
+
10
+ /** How a listing quoted in another currency is restated in USD. */
11
+ export interface RiskConversion {
12
+ /** The listing currency as quoted, sub-units included (GBp, ZAc). */
13
+ listingCurrency: string;
14
+ /** The currency the FX pair prices (GBP for GBp). */
15
+ currency: string;
16
+ /** Listing units per currency unit: 100 for pence. */
17
+ divisor: number;
18
+ leg: FxLeg;
19
+ }
20
+
21
+ export const FX_UNAVAILABLE = "Foreign holdings: daily FX closes unavailable";
22
+ export const FX_NO_PAIR = "Foreign holdings: no daily FX pair for this currency";
23
+ export const FX_STALE = "Foreign holdings: daily FX history is stale";
24
+ export const FX_GAP = "Foreign holdings: daily FX history has gaps on the holding's dates";
25
+ export const FX_DISAGREES = "Foreign holdings: daily FX closes disagree with the current rate";
26
+ export const LOCAL_GAP = "Foreign holdings: local daily history has a gap longer than a market holiday";
27
+
28
+ /**
29
+ * Longest run of calendar days a listing may go without a close while US
30
+ * sessions continue (Golden Week, Lunar New Year, national holidays). A longer
31
+ * silence is missing data, not a holiday, and is never carried across.
32
+ */
33
+ const MAX_LOCAL_CLOSED_DAYS = 10;
34
+ /** The calendar the basket, SPY and the factor proxies share. */
35
+ const BASKET_CALENDAR = "NYSE";
36
+ const DAY_MS = 86_400_000;
37
+
38
+ /** The pair that restates a listing currency in USD, or null when there is none. */
39
+ export function riskConversion(listingCurrency: string): RiskConversion | null {
40
+ const unit = resolveCurrencyUnit(listingCurrency);
41
+ if (!unit.currency || unit.currency === "USD") return null;
42
+ const leg = fxLegForCurrency(unit.currency);
43
+ return leg ? { listingCurrency, currency: unit.currency, divisor: unit.divisor, leg } : null;
44
+ }
45
+
46
+ /** Completed daily closes of the pair as USD per unit, today's still-trading bar left out. */
47
+ export function validateFxHistory(
48
+ response: CloudMarketResponse<CloudPricePointPayload[]>,
49
+ leg: FxLeg,
50
+ now = new Date(),
51
+ ): FxCloses {
52
+ try {
53
+ return dailyFxCloses(response, leg, now);
54
+ } catch (error) {
55
+ if (error instanceof FxHistoryError) throw new Error(error.problem === "stale" ? FX_STALE : FX_UNAVAILABLE);
56
+ throw error;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Restates a listing's completed local closes in USD on every NYSE session
62
+ * they span, each at the FX close of that same date, so the converted series
63
+ * shares the basket's calendar and its returns pair with SPY and the other
64
+ * holdings interval for interval.
65
+ *
66
+ * On a US session the listing did not trade (its own holiday), the holding is
67
+ * worth its last local close at that day's FX close: its USD value moves with
68
+ * the currency alone, as it would in the account. A US session with no FX
69
+ * close, or a local silence longer than any market holiday, rejects the whole
70
+ * series instead of bridging it. The series ends at the earlier of the last
71
+ * local close and the last FX close, so no close is carried past the data.
72
+ */
73
+ export function convertClosesToUsd(
74
+ local: readonly { date: string; close: number }[],
75
+ fx: FxCloses,
76
+ divisor: number,
77
+ ): PricePoint[] {
78
+ const points = [...local]
79
+ .map((point) => ({ date: point.date.slice(0, 10), close: point.close }))
80
+ .sort((left, right) => left.date.localeCompare(right.date));
81
+ if (points.length < 2) throw new Error("Daily history unavailable");
82
+ const start = points[0]!.date > fx.first ? points[0]!.date : fx.first;
83
+ const end = points.at(-1)!.date < fx.asOf ? points.at(-1)!.date : fx.asOf;
84
+ const converted: PricePoint[] = [];
85
+ let cursor = 0;
86
+ for (let time = Date.parse(`${start}T00:00:00Z`); time <= Date.parse(`${end}T00:00:00Z`); time += DAY_MS) {
87
+ const date = new Date(time).toISOString().slice(0, 10);
88
+ const day = getPublishedUsEquityCalendarDay(BASKET_CALENDAR, date);
89
+ if (day == null) throw new Error("Daily calendar unavailable");
90
+ if (day !== "session") continue;
91
+ while (cursor + 1 < points.length && points[cursor + 1]!.date <= date) cursor += 1;
92
+ const last = points[cursor]!;
93
+ if (last.date > date) continue;
94
+ if (time - Date.parse(`${last.date}T00:00:00Z`) > MAX_LOCAL_CLOSED_DAYS * DAY_MS) throw new Error(LOCAL_GAP);
95
+ const rate = fx.closes.get(date);
96
+ if (rate == null) throw new Error(FX_GAP);
97
+ const close = (last.close * rate) / divisor;
98
+ converted.push({ date: new Date(time), open: close, high: close, low: close, close });
99
+ }
100
+ if (converted.length < 2) throw new Error("Daily history unavailable");
101
+ return converted;
102
+ }