gloomberb 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +169 -5
  5. package/src/account/auth-model.ts +7 -3
  6. package/src/api-client/auth.ts +9 -4
  7. package/src/api-client/catalysts.ts +2 -1
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +31 -6
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/index.ts +171 -11
  14. package/src/api-client/live-scan.ts +91 -0
  15. package/src/api-client/market-discovery.ts +10 -0
  16. package/src/api-client/market-movers.ts +7 -0
  17. package/src/api-client/members.ts +13 -1
  18. package/src/api-client/perps.ts +43 -0
  19. package/src/api-client/request.ts +222 -41
  20. package/src/api-client/research-activity.ts +6 -12
  21. package/src/api-client/types.ts +74 -0
  22. package/src/app/keybindings/index.ts +3 -0
  23. package/src/app/keybindings/labels.ts +18 -1
  24. package/src/app/keybindings/resolve.ts +14 -0
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +42 -27
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +1 -1
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/fred-series.ts +5 -0
  35. package/src/cli/commands/keybindings.ts +23 -0
  36. package/src/cli/commands/market.ts +138 -46
  37. package/src/cli/commands/options-chain.ts +107 -10
  38. package/src/cli/commands/options-leaps.ts +9 -3
  39. package/src/cli/commands/overview.ts +176 -39
  40. package/src/cli/commands/search.ts +39 -3
  41. package/src/cli/commands/themes.ts +11 -2
  42. package/src/cli/commands/ticker.ts +57 -35
  43. package/src/cli/company-name-hint.ts +41 -0
  44. package/src/cli/earnings-calendar.ts +1 -1
  45. package/src/cli/errors.ts +10 -2
  46. package/src/cli/export-source.ts +29 -1
  47. package/src/cli/glossary.ts +73 -1
  48. package/src/cli/history-rows.ts +87 -16
  49. package/src/cli/index.ts +2 -0
  50. package/src/cli/known-gaps.ts +5 -2
  51. package/src/cli/listing-arg.ts +195 -56
  52. package/src/cli/output-file.ts +68 -0
  53. package/src/cli/pane-functions/capabilities.ts +3 -0
  54. package/src/cli/pane-functions/catalog.ts +80 -7
  55. package/src/cli/pane-functions/dom.ts +13 -3
  56. package/src/cli/pane-functions/freshness.ts +111 -16
  57. package/src/cli/pane-functions/function-help.ts +23 -13
  58. package/src/cli/pane-functions/headless.ts +56 -12
  59. package/src/cli/pane-functions/index.ts +58 -16
  60. package/src/cli/pane-functions/market-session.ts +81 -9
  61. package/src/cli/pane-functions/options.ts +19 -1
  62. package/src/cli/pane-functions/report.ts +5 -1
  63. package/src/cli/pane-functions/resolver.ts +35 -8
  64. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  65. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  66. package/src/cli/quote-answer.ts +30 -13
  67. package/src/cli/report-tables.ts +18 -7
  68. package/src/cli/restore-plugins.ts +13 -1
  69. package/src/cli/result.ts +57 -5
  70. package/src/cli/ticker-resolution.ts +3 -2
  71. package/src/components/chart/composite/composite-chart.tsx +26 -4
  72. package/src/components/chart/composite/format.ts +27 -0
  73. package/src/components/chart/composite/interactions.ts +6 -0
  74. package/src/components/chart/composite/legend.tsx +45 -7
  75. package/src/components/chart/composite/levels.ts +2 -0
  76. package/src/components/chart/composite/panel-gestures.ts +33 -0
  77. package/src/components/chart/composite/panel-series.ts +1 -0
  78. package/src/components/chart/composite/panel-surface.tsx +61 -5
  79. package/src/components/chart/composite/range-statistics.ts +62 -0
  80. package/src/components/chart/composite/rasterizer.ts +24 -13
  81. package/src/components/chart/composite/scene.ts +46 -7
  82. package/src/components/chart/composite/series-axis-tags.tsx +16 -9
  83. package/src/components/chart/composite/series-cues.ts +14 -0
  84. package/src/components/chart/composite/text-renderer.ts +9 -2
  85. package/src/components/chart/composite/tool-catalog.ts +36 -0
  86. package/src/components/chart/composite/tools.ts +203 -44
  87. package/src/components/chart/composite/types.ts +4 -0
  88. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  89. package/src/components/command-bar/assist/inventory.ts +2 -1
  90. package/src/components/command-bar/commands/direct/index.ts +28 -13
  91. package/src/components/command-bar/pane-templates/items.ts +3 -2
  92. package/src/components/command-bar/panel/prompt-binding.ts +0 -8
  93. package/src/components/command-bar/workflow/alert.ts +1 -1
  94. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  95. package/src/components/layout/header.tsx +26 -40
  96. package/src/components/layout/pane/footer/index.tsx +1 -0
  97. package/src/components/layout/pane/footer/model.ts +27 -0
  98. package/src/components/onboarding/onboarding-frame.tsx +1 -1
  99. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  100. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
  101. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  102. package/src/components/onboarding/wizard-positions.ts +3 -3
  103. package/src/components/ticker/list-table-view.tsx +4 -1
  104. package/src/components/ui/choice-dialog.tsx +23 -18
  105. package/src/components/ui/choice-groups.ts +18 -0
  106. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  107. package/src/components/ui/data-table/opentui/model.ts +26 -1
  108. package/src/components/ui/data-table/types.ts +2 -0
  109. package/src/components/ui/display.tsx +5 -1
  110. package/src/components/ui/list-view.tsx +8 -4
  111. package/src/components/ui/query-bar-keys.tsx +2 -0
  112. package/src/components/ui/query-bar.tsx +1 -0
  113. package/src/components/ui/select-button.tsx +5 -1
  114. package/src/components/ui/select-field.tsx +1 -0
  115. package/src/components/ui/table-layout.ts +8 -5
  116. package/src/core/app-runtime.ts +2 -0
  117. package/src/core/app-services.ts +2 -2
  118. package/src/core/state/app/layout.ts +2 -0
  119. package/src/core/state/app/state.ts +9 -0
  120. package/src/core/state/app/types.ts +3 -0
  121. package/src/data/config/chart-settings.ts +1 -1
  122. package/src/data/json-ticker-repository.ts +17 -8
  123. package/src/data/saved-copies.ts +55 -16
  124. package/src/data/ticker-repository.ts +16 -9
  125. package/src/data/ticker-writes.ts +81 -0
  126. package/src/data/ttl-cache.ts +165 -0
  127. package/src/docs/snapshot.json +427 -216
  128. package/src/i18n/es.ts +19 -3
  129. package/src/i18n/ja.ts +19 -3
  130. package/src/i18n/ko.ts +19 -3
  131. package/src/i18n/zh-cn.ts +19 -3
  132. package/src/i18n/zh-tw.ts +19 -3
  133. package/src/market-data/coordinator/financials.ts +10 -2
  134. package/src/market-data/coordinator/index.ts +3 -1
  135. package/src/market-data/coordinator/quotes.ts +4 -2
  136. package/src/market-data/depositary-pair.ts +2 -2
  137. package/src/market-data/fund-facts.ts +50 -0
  138. package/src/market-data/market/change-basis.ts +48 -7
  139. package/src/market-data/market/dated-previous-close.ts +203 -0
  140. package/src/market-data/market/format.ts +25 -0
  141. package/src/market-data/market/freshness.ts +2 -1
  142. package/src/market-data/market/futures-week.ts +68 -0
  143. package/src/market-data/market/status.ts +16 -4
  144. package/src/market-data/options-alternatives.ts +141 -4
  145. package/src/market-data/quotes/contributions.ts +20 -2
  146. package/src/market-data/quotes/resolution.ts +3 -0
  147. package/src/market-data/selectors.ts +3 -0
  148. package/src/news/aggregator.ts +113 -9
  149. package/src/news/cloud-session.ts +21 -0
  150. package/src/news/hooks.ts +1 -0
  151. package/src/news/news-model.ts +1 -0
  152. package/src/news/types.ts +11 -0
  153. package/src/notifications/notification-log.ts +6 -4
  154. package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
  155. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  156. package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
  157. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  158. package/src/plugins/builtin/alerts/cli.ts +380 -82
  159. package/src/plugins/builtin/alerts/command.ts +31 -0
  160. package/src/plugins/builtin/alerts/constants.ts +1 -1
  161. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  162. package/src/plugins/builtin/alerts/fired-log.ts +113 -8
  163. package/src/plugins/builtin/alerts/format.ts +80 -4
  164. package/src/plugins/builtin/alerts/headless.ts +30 -7
  165. package/src/plugins/builtin/alerts/history-rows.ts +7 -3
  166. package/src/plugins/builtin/alerts/history.ts +2 -0
  167. package/src/plugins/builtin/alerts/index.tsx +135 -22
  168. package/src/plugins/builtin/alerts/live.ts +3 -3
  169. package/src/plugins/builtin/alerts/metric-command.ts +134 -0
  170. package/src/plugins/builtin/alerts/metric-data.ts +111 -0
  171. package/src/plugins/builtin/alerts/metric-model.ts +324 -0
  172. package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
  173. package/src/plugins/builtin/alerts/pane.tsx +90 -39
  174. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  175. package/src/plugins/builtin/alerts/storage.ts +37 -10
  176. package/src/plugins/builtin/alerts/types.ts +7 -0
  177. package/src/plugins/builtin/analytics/index.tsx +2 -1
  178. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  179. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  180. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  181. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  182. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  183. package/src/plugins/builtin/attention/index.tsx +2 -1
  184. package/src/plugins/builtin/attention/listing.ts +14 -0
  185. package/src/plugins/builtin/backtest/headless.ts +10 -5
  186. package/src/plugins/builtin/backtest/pane.tsx +9 -4
  187. package/src/plugins/builtin/backtest/presets.ts +29 -3
  188. package/src/plugins/builtin/broker-manager/index.tsx +10 -5
  189. package/src/plugins/builtin/broker-manager/model.ts +6 -1
  190. package/src/plugins/builtin/broker-trading/controller.ts +22 -12
  191. package/src/plugins/builtin/broker-trading/index.tsx +13 -1
  192. package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
  193. package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
  194. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  195. package/src/plugins/builtin/buildout/format.ts +38 -0
  196. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  197. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  198. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  199. package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
  200. package/src/plugins/builtin/catalysts/model.ts +2 -1
  201. package/src/plugins/builtin/changelog/index.tsx +9 -6
  202. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
  203. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
  204. package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
  205. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
  206. package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
  207. package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
  208. package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
  209. package/src/plugins/builtin/chart-composer/formula.ts +71 -18
  210. package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
  211. package/src/plugins/builtin/chart-composer/headless.ts +19 -3
  212. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  213. package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
  214. package/src/plugins/builtin/chart-composer/presets.ts +50 -15
  215. package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
  216. package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
  217. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  218. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
  219. package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
  220. package/src/plugins/builtin/chat/content/composer.tsx +13 -6
  221. package/src/plugins/builtin/chat/content/footer.tsx +6 -3
  222. package/src/plugins/builtin/chat/content/index.tsx +50 -21
  223. package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
  224. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
  225. package/src/plugins/builtin/chat/index.tsx +23 -0
  226. package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
  227. package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
  228. package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
  229. package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
  230. package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
  231. package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
  232. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
  233. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  234. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  235. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  236. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  237. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  238. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  239. package/src/plugins/builtin/company-attention/client.ts +8 -9
  240. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  241. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  242. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  243. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  244. package/src/plugins/builtin/correlation/headless.ts +13 -5
  245. package/src/plugins/builtin/correlation/history.ts +22 -0
  246. package/src/plugins/builtin/cot/client.ts +22 -18
  247. package/src/plugins/builtin/cot/headless.ts +15 -10
  248. package/src/plugins/builtin/cot/model.ts +14 -1
  249. package/src/plugins/builtin/cot/pane.tsx +22 -14
  250. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  251. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  252. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  253. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  254. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  255. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  256. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  257. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  258. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  259. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  260. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  261. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  262. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  263. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  265. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  266. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  267. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  268. package/src/plugins/builtin/docs/targets.ts +5 -1
  269. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  270. package/src/plugins/builtin/earnings/board.tsx +62 -71
  271. package/src/plugins/builtin/earnings/headless.ts +40 -4
  272. package/src/plugins/builtin/earnings/scope.ts +31 -43
  273. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  274. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  275. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  276. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  277. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  278. package/src/plugins/builtin/econ/calendar-model.ts +14 -9
  279. package/src/plugins/builtin/econ/figures.ts +72 -0
  280. package/src/plugins/builtin/econ/index.tsx +33 -3
  281. package/src/plugins/builtin/econ/types.ts +4 -0
  282. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  283. package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
  284. package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
  285. package/src/plugins/builtin/econ-statistics/view.ts +3 -0
  286. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  287. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  288. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  289. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  290. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  291. package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
  292. package/src/plugins/builtin/exposure/charts.tsx +2 -2
  293. package/src/plugins/builtin/exposure/model.ts +13 -1
  294. package/src/plugins/builtin/exposure/pane.tsx +4 -4
  295. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  296. package/src/plugins/builtin/futures/index.tsx +1 -0
  297. package/src/plugins/builtin/futures/model.ts +2 -1
  298. package/src/plugins/builtin/futures/table.tsx +2 -2
  299. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  300. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  301. package/src/plugins/builtin/fx-matrix/client.ts +11 -3
  302. package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
  303. package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
  304. package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
  305. package/src/plugins/builtin/gpu/headless.ts +8 -2
  306. package/src/plugins/builtin/help/function-index.ts +4 -1
  307. package/src/plugins/builtin/help/index.tsx +1 -0
  308. package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
  309. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  310. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  311. package/src/plugins/builtin/holders/headless.ts +18 -1
  312. package/src/plugins/builtin/iv-history/client.ts +52 -11
  313. package/src/plugins/builtin/iv-history/format.ts +36 -23
  314. package/src/plugins/builtin/iv-history/headless.ts +36 -18
  315. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  316. package/src/plugins/builtin/iv-history/model.ts +163 -27
  317. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  318. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  319. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  320. package/src/plugins/builtin/jobs/client.ts +25 -29
  321. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  322. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  323. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  324. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  325. package/src/plugins/builtin/macro-day/pane.tsx +7 -5
  326. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  327. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  328. package/src/plugins/builtin/market-movers/client.ts +25 -7
  329. package/src/plugins/builtin/market-movers/footer.ts +2 -2
  330. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  331. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  332. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  333. package/src/plugins/builtin/market-movers/model.ts +4 -1
  334. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  335. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  336. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  337. package/src/plugins/builtin/market-movers/table.ts +17 -5
  338. package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
  339. package/src/plugins/builtin/members/client.ts +19 -4
  340. package/src/plugins/builtin/members/headless.ts +20 -7
  341. package/src/plugins/builtin/members/index.tsx +9 -9
  342. package/src/plugins/builtin/members/model.ts +23 -4
  343. package/src/plugins/builtin/members/pane.tsx +25 -15
  344. package/src/plugins/builtin/news/index.tsx +1 -0
  345. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  346. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  347. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  348. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  349. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  350. package/src/plugins/builtin/openfda/client.ts +14 -2
  351. package/src/plugins/builtin/openfda/index.tsx +3 -2
  352. package/src/plugins/builtin/openfda/model.ts +9 -0
  353. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  354. package/src/plugins/builtin/options/analytics.ts +54 -6
  355. package/src/plugins/builtin/options/enrichment.ts +17 -5
  356. package/src/plugins/builtin/options/model-spot.ts +5 -2
  357. package/src/plugins/builtin/options/strike-window.ts +2 -1
  358. package/src/plugins/builtin/options/table.ts +6 -9
  359. package/src/plugins/builtin/options/types.ts +2 -0
  360. package/src/plugins/builtin/options/view.tsx +42 -12
  361. package/src/plugins/builtin/options-positioning/client.ts +30 -0
  362. package/src/plugins/builtin/options-positioning/headless.ts +74 -21
  363. package/src/plugins/builtin/options-positioning/index.tsx +4 -4
  364. package/src/plugins/builtin/options-positioning/model.ts +122 -14
  365. package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
  366. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  367. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  368. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  369. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  370. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  371. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  372. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  373. package/src/plugins/builtin/pe-band/model.ts +12 -49
  374. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  375. package/src/plugins/builtin/perps/cascade.ts +236 -0
  376. package/src/plugins/builtin/perps/client.ts +56 -2
  377. package/src/plugins/builtin/perps/freshness.ts +12 -10
  378. package/src/plugins/builtin/perps/headless.ts +65 -9
  379. package/src/plugins/builtin/perps/index.tsx +7 -5
  380. package/src/plugins/builtin/perps/live-rows.ts +10 -3
  381. package/src/plugins/builtin/perps/model.ts +18 -4
  382. package/src/plugins/builtin/perps/pane.tsx +32 -31
  383. package/src/plugins/builtin/perps/status-line.ts +25 -0
  384. package/src/plugins/builtin/plugin-module.ts +5 -1
  385. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  386. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  387. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  388. package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
  389. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  390. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  391. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  392. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  393. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  394. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  395. package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
  396. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  397. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  398. package/src/plugins/builtin/rate-path/pane.tsx +2 -2
  399. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  400. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  401. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  402. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  403. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  404. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  405. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  406. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  407. package/src/plugins/builtin/research/event-model.ts +10 -8
  408. package/src/plugins/builtin/research/headless.ts +1 -1
  409. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  410. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  411. package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
  412. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  413. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  414. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  415. package/src/plugins/builtin/scanner/feed.ts +2 -2
  416. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  417. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  418. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  419. package/src/plugins/builtin/scanner/headless.ts +204 -35
  420. package/src/plugins/builtin/scanner/index.ts +40 -6
  421. package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
  422. package/src/plugins/builtin/scanner/live-feed.ts +168 -0
  423. package/src/plugins/builtin/scanner/live-headless.ts +138 -0
  424. package/src/plugins/builtin/scanner/live-model.ts +362 -0
  425. package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
  426. package/src/plugins/builtin/scanner/live-track.ts +186 -0
  427. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  428. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  429. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  430. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  431. package/src/plugins/builtin/sec/headless.ts +2 -1
  432. package/src/plugins/builtin/series-inventory.ts +3 -0
  433. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  434. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  435. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  436. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  437. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  438. package/src/plugins/builtin/shared/macro-releases.ts +65 -0
  439. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  440. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  441. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  442. package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
  443. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  444. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  445. package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
  446. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  447. package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
  448. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  449. package/src/plugins/builtin/themes/headless.ts +6 -4
  450. package/src/plugins/builtin/themes/model.ts +17 -8
  451. package/src/plugins/builtin/themes/pane.tsx +7 -7
  452. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  453. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  456. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  457. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  458. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  459. package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
  460. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  461. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  462. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  463. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  464. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  465. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  466. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  467. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  468. package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
  469. package/src/plugins/builtin/vol-surface/client.ts +9 -17
  470. package/src/plugins/builtin/vol-surface/headless.ts +63 -14
  471. package/src/plugins/builtin/vol-surface/index.tsx +2 -2
  472. package/src/plugins/builtin/vol-surface/model.ts +22 -7
  473. package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
  474. package/src/plugins/builtin/vol-surface/tables.ts +31 -3
  475. package/src/plugins/builtin/volatility/dvol.ts +46 -0
  476. package/src/plugins/builtin/volatility/index.tsx +2 -2
  477. package/src/plugins/builtin/volatility/pane.tsx +4 -4
  478. package/src/plugins/builtin/volatility/series.ts +26 -0
  479. package/src/plugins/builtin/world-indices/footer.ts +1 -1
  480. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  481. package/src/plugins/catalog-browser.ts +13 -8
  482. package/src/plugins/catalog-manifest.ts +2 -1
  483. package/src/plugins/marketplace/model.ts +18 -5
  484. package/src/portfolio/column-values.ts +5 -5
  485. package/src/portfolio/futures-positions.ts +6 -3
  486. package/src/portfolio/position-quantity.ts +15 -9
  487. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  488. package/src/remote/resources.ts +2 -1
  489. package/src/renderers/browser/app-services.ts +1 -7
  490. package/src/renderers/browser/config-host.ts +2 -55
  491. package/src/renderers/browser/main.tsx +7 -57
  492. package/src/renderers/dom/data-table/row.tsx +26 -0
  493. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  494. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  495. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  496. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  497. package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
  498. package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
  499. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  500. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  501. package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
  502. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
  503. package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
  504. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  505. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
  506. package/src/renderers/electrobun/bun/index.ts +25 -1
  507. package/src/renderers/electrobun/shared/app-name.ts +30 -0
  508. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  509. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  510. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  511. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
  512. package/src/renderers/electrobun/view/main.tsx +24 -1
  513. package/src/renderers/electrobun/view/news-source.ts +3 -1
  514. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  515. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  516. package/src/renderers/opentui/start.tsx +5 -0
  517. package/src/renderers/opentui/toast-host.tsx +71 -43
  518. package/src/series/registry.ts +1 -0
  519. package/src/series/types.ts +6 -0
  520. package/src/series/validate.ts +1 -0
  521. package/src/sources/gloomberb-cloud/index.ts +1 -0
  522. package/src/sources/gloomberb-cloud/news.ts +47 -7
  523. package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
  524. package/src/sources/provider-router/batches.ts +10 -2
  525. package/src/sources/provider-router/financial-routes.ts +3 -3
  526. package/src/sources/provider-router/financials.ts +15 -3
  527. package/src/sources/provider-router/history.ts +28 -7
  528. package/src/sources/provider-router/index.ts +149 -10
  529. package/src/sources/provider-router/search.ts +13 -2
  530. package/src/state/app/context/index.tsx +3 -0
  531. package/src/state/ticker-cleanup.ts +50 -0
  532. package/src/sync/auth-transition.ts +19 -9
  533. package/src/sync/baseline.ts +10 -4
  534. package/src/sync/controller.ts +70 -19
  535. package/src/sync/core-contributors.ts +90 -31
  536. package/src/sync/react.ts +4 -1
  537. package/src/sync/types.ts +3 -0
  538. package/src/telemetry/stall-reports-dom.ts +38 -0
  539. package/src/telemetry/stall-reports-node.ts +75 -0
  540. package/src/telemetry/stall-reports.ts +214 -0
  541. package/src/telemetry/usage-counts.ts +0 -18
  542. package/src/test-support/preload.ts +14 -0
  543. package/src/theme/colors.ts +8 -0
  544. package/src/theme/themes.ts +10 -0
  545. package/src/tickers/company-name.ts +103 -3
  546. package/src/tickers/listing-venues.ts +38 -3
  547. package/src/tickers/search/bare-symbol.ts +36 -0
  548. package/src/tickers/search/index.ts +36 -7
  549. package/src/tickers/search/ranking.ts +10 -1
  550. package/src/tickers/search/types.ts +5 -0
  551. package/src/tickers/terminal-codes.ts +19 -0
  552. package/src/time-series/alignment.ts +1 -1
  553. package/src/time-series/base-series.ts +13 -5
  554. package/src/time-series/daily-valuation.ts +62 -0
  555. package/src/time-series/economic.ts +18 -4
  556. package/src/time-series/field-catalog.ts +2 -4
  557. package/src/time-series/filed-trailing-eps.ts +197 -0
  558. package/src/time-series/forward-valuation.ts +32 -0
  559. package/src/time-series/fundamentals.ts +198 -7
  560. package/src/time-series/hooks.ts +1 -0
  561. package/src/time-series/ice-credit.ts +3 -0
  562. package/src/time-series/price-comparison.ts +30 -2
  563. package/src/time-series/price-units.ts +72 -2
  564. package/src/time-series/resolve-context.ts +7 -1
  565. package/src/time-series/resolve.ts +116 -15
  566. package/src/time-series/series-presentation.ts +40 -6
  567. package/src/time-series/session-bar.ts +11 -0
  568. package/src/time-series/spec.ts +5 -3
  569. package/src/time-series/studies.ts +278 -74
  570. package/src/time-series/total-return.ts +160 -0
  571. package/src/time-series/transforms.ts +3 -3
  572. package/src/time-series/types.ts +27 -1
  573. package/src/time-series/valuation-releases.ts +39 -0
  574. package/src/types/data-provider.ts +9 -1
  575. package/src/types/desktop-window.ts +2 -0
  576. package/src/types/financials.ts +73 -2
  577. package/src/types/headless.ts +61 -5
  578. package/src/types/instrument.ts +3 -0
  579. package/src/types/news-source.ts +1 -0
  580. package/src/types/plugin.ts +10 -1
  581. package/src/ui/host.tsx +3 -0
  582. package/src/updater/command-names.ts +12 -7
  583. package/src/updater/failure.ts +4 -1
  584. package/src/utils/cli-output.ts +42 -1
  585. package/src/utils/crypto-pair.ts +44 -0
  586. package/src/utils/financial-statements.ts +6 -1
  587. package/src/utils/forward-dividend.ts +2 -0
  588. package/src/utils/fundamentals.ts +24 -3
  589. package/src/utils/fx-market-hours.ts +26 -3
  590. package/src/utils/main-thread-stall.ts +35 -2
  591. package/src/utils/market-capitalization.ts +24 -1
  592. package/src/utils/network-failure.ts +9 -6
  593. package/src/utils/option-expiry.ts +48 -1
  594. package/src/utils/options.ts +10 -0
  595. package/src/utils/plugin-proxy-hosts.json +1 -0
  596. package/src/utils/price-history.ts +12 -1
  597. package/src/utils/shell-word.ts +8 -0
  598. package/src/utils/trailing-dividend.ts +53 -4
  599. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
  600. package/src/renderers/browser/first-screen.ts +0 -193
@@ -99,12 +99,53 @@ export function companyNameMatch(typed: string, name: string | null | undefined)
99
99
  return { extra: words.size - matched.size, abbreviated };
100
100
  }
101
101
 
102
- /** Items grouped by the company their names are, in the order each company first appears. Unnamed items are left out. */
103
- export function groupByCompany<T>(items: readonly T[], nameOf: (item: T) => string | null | undefined): T[][] {
102
+ // Words one fund's venues name it with or without: "iShares MSCI Japan Index Fund" in New York is "iShares MSCI Japan ETF" in Mexico.
103
+ const FUND_NOISE = new Set(["etf", "etfs", "fund", "funds", "index", "trust", "ucits"]);
104
+
105
+ function instrumentWords(name: string | null | undefined): Set<string> {
106
+ return new Set([...companyWords(name)].filter((word) => !FUND_NOISE.has(word)));
107
+ }
108
+
109
+ /**
110
+ * Whether two venues' names can be one instrument's: one company's
111
+ * (`sameCompany`), or one fund's however a venue words it, with or without
112
+ * "ETF", "Index Fund" or "Trust" ("Technology Select Sector SPDR Fund" in
113
+ * Buenos Aires is State Street's ETF in New York). Two names that are not are
114
+ * another company's or another fund's ("W.A.G Payment Solutions" is not the
115
+ * iShares fund, nor "UBS MSCI India" iShares' India fund). An empty name
116
+ * matches none.
117
+ */
118
+ export function sameListedName(left: string | null | undefined, right: string | null | undefined): boolean {
119
+ if (sameCompany(left, right)) return true;
120
+ const a = instrumentWords(left);
121
+ const b = instrumentWords(right);
122
+ if (a.size === 0 || b.size === 0) return false;
123
+ const [fewer, more] = a.size <= b.size ? [a, b] : [b, a];
124
+ return [...fewer].every((word) => more.has(word));
125
+ }
126
+
127
+ /**
128
+ * Whether a venue names its line a depositary receipt ("Toyota Motor
129
+ * Corporation CEDEAR", "SS SPDR S&P 500 ETF Trust - CEDEAR"): a receipt of a
130
+ * line elsewhere, whose short form of that line's name says little of whose it is.
131
+ */
132
+ export function namesDepositaryReceipt(name: string | null | undefined): boolean {
133
+ return /\b(?:cedears?|adrs?|ads|cdrs?|cdis?|depositary)\b/i.test(name ?? "");
134
+ }
135
+
136
+ /**
137
+ * Items grouped by the company their names are (`same`, `sameCompany` unless
138
+ * given), in the order each company first appears. Unnamed items are left out.
139
+ */
140
+ export function groupByCompany<T>(
141
+ items: readonly T[],
142
+ nameOf: (item: T) => string | null | undefined,
143
+ same: (left: string | null | undefined, right: string | null | undefined) => boolean = sameCompany,
144
+ ): T[][] {
104
145
  const groups: T[][] = [];
105
146
  for (const item of items) {
106
147
  if (companyWords(nameOf(item)).size === 0) continue;
107
- const group = groups.find((members) => members.some((member) => sameCompany(nameOf(member), nameOf(item))));
148
+ const group = groups.find((members) => members.some((member) => same(nameOf(member), nameOf(item))));
108
149
  if (group) group.push(item);
109
150
  else groups.push([item]);
110
151
  }
@@ -129,3 +170,62 @@ export function shortListingTitle(listing: { symbol: string; exchange: string; n
129
170
  const name = listing.name?.trim() ? shortCompanyName(listing.name) : listing.symbol;
130
171
  return listing.exchange ? `${name} (${listing.exchange})` : name;
131
172
  }
173
+
174
+ /** Legal forms a list abbreviates, and short words that are words where short capitals would read as initials. */
175
+ const NAME_WORDS: Readonly<Record<string, string>> = {
176
+ INC: "Inc", CORP: "Corp", CO: "Co", LTD: "Ltd", HLDG: "Hldg", HLDGS: "Hldgs", INTL: "Intl", GRP: "Grp", ST: "St",
177
+ OLD: "Old", NEW: "New", AIR: "Air", OIL: "Oil", GAS: "Gas", FOX: "Fox", ONE: "One", TWO: "Two", NON: "Non", NET: "Net",
178
+ PET: "Pet", BIO: "Bio", SUN: "Sun", SIX: "Six", RED: "Red", BOX: "Box", GAP: "Gap", ELI: "Eli", DOW: "Dow", LAB: "Lab",
179
+ MID: "Mid", SON: "Son", GEN: "Gen", HUB: "Hub", OWL: "Owl", REX: "Rex", LA: "La", LAM: "Lam", JOE: "Joe", CAL: "Cal",
180
+ MET: "Met", GRO: "Gro", BUY: "Buy", COM: "Com", SHS: "Shs",
181
+ };
182
+ /** Words a name keeps in lower case unless it opens with one. */
183
+ const NAME_MINOR_WORDS = new Set(["AND", "OF", "THE", "FOR", "DE", "DEL"]);
184
+ /** Spellings that title case gets wrong, for the largest names; any other name reads in plain title case. */
185
+ const NAME_BRANDS: Readonly<Record<string, string>> = {
186
+ NVIDIA: "NVIDIA", JPMORGAN: "JPMorgan", UNITEDHEALTH: "UnitedHealth", ABBVIE: "AbbVie", EXXONMOBIL: "ExxonMobil",
187
+ PEPSICO: "PepsiCo", BLACKROCK: "BlackRock", SERVICENOW: "ServiceNow", CROWDSTRIKE: "CrowdStrike", FEDEX: "FedEx",
188
+ PAYPAL: "PayPal", EBAY: "eBay", DOORDASH: "DoorDash", DRAFTKINGS: "DraftKings", MONGODB: "MongoDB", HUBSPOT: "HubSpot",
189
+ GODADDY: "GoDaddy", CARMAX: "CarMax", MARKETAXESS: "MarketAxess", AUTOZONE: "AutoZone", AUTONATION: "AutoNation",
190
+ COSTAR: "CoStar", IDACORP: "IDACORP", AECOM: "AECOM", CBRE: "CBRE", MSCI: "MSCI",
191
+ };
192
+ /** Longer than three letters, yet still capitals: a legal form, a share structure or an index. */
193
+ const NAME_INITIALISMS = new Set(["REIT", "USA", "ADR", "ADS", "VIII"]);
194
+ const NAME_DOMAINS = new Set(["COM", "NET", "ORG", "IO", "AI"]);
195
+
196
+ /** One run of letters in a name written in capitals. */
197
+ function nameWord(word: string, opening: boolean, domain: boolean): string {
198
+ if (!/\p{L}/u.test(word)) return word;
199
+ if (domain && NAME_DOMAINS.has(word)) return word.toLowerCase();
200
+ const brand = NAME_BRANDS[word];
201
+ if (brand) return brand;
202
+ if (NAME_MINOR_WORDS.has(word)) return opening ? `${word.charAt(0)}${word.slice(1).toLowerCase()}` : word.toLowerCase();
203
+ const plain = NAME_WORDS[word];
204
+ if (plain) return plain;
205
+ // Initials and tickers read as capitals: up to three letters with no word of their own, or no vowel at all.
206
+ if (word.length <= 3 || NAME_INITIALISMS.has(word) || !/[AEIOUY]/.test(word)) return word;
207
+ if (/^MC[A-Z]{3}/.test(word)) return `Mc${word.charAt(2)}${word.slice(3).toLowerCase()}`;
208
+ return `${word.charAt(0)}${word.slice(1).toLowerCase()}`;
209
+ }
210
+
211
+ /**
212
+ * A company name a source writes in capitals (an ETF's holdings file: "APPLE INC", "MERCK & CO INC") as it is
213
+ * normally written: "Apple Inc", "Merck & Co Inc". Legal forms and short words take their usual case, initials
214
+ * inside a name stay capitals ("GE Aerospace", "AT&T", "L3Harris"). A name with any lower case letter is written
215
+ * the way its source means it and comes back as it is.
216
+ */
217
+ export function companyNameCase(name: string): string {
218
+ if (!/\p{Lu}/u.test(name) || name !== name.toUpperCase()) return name;
219
+ let words = 0;
220
+ return name.replace(/\S+/g, (token) => {
221
+ const opening = words++ === 0;
222
+ // A separator inside a word (Coca-Cola, AT&T, Amazon.com, McDonald's) cuts it into words of its own.
223
+ return token.split(/([-/.&,'\u2019]+)/).map((part, index, parts) => {
224
+ if (index % 2 === 1) return part;
225
+ const before = parts[index - 1];
226
+ if (part === "S" && before && /['\u2019]$/.test(before)) return "s";
227
+ // Digits split a word too: Five9, L3Harris. A run of one or two letters beside a number is an initial.
228
+ return part.split(/(\d+)/).map((run, at) => at % 2 === 1 ? run : nameWord(run, opening && index === 0 && at === 0, before === ".")).join("");
229
+ }).join("");
230
+ });
231
+ }
@@ -12,6 +12,7 @@ import {
12
12
  typedExchangeCode,
13
13
  } from "../utils/exchanges";
14
14
  import { currencyPairSpelling } from "../utils/currency-pair";
15
+ import { savedAbroadForBareSymbol } from "./search/bare-symbol";
15
16
 
16
17
  export interface ListingStore {
17
18
  loadTicker(symbol: string): Promise<TickerRecord | null>;
@@ -27,6 +28,17 @@ export interface ListingVenue {
27
28
  /** Canonical code. */
28
29
  exchange: string;
29
30
  name: string;
31
+ /**
32
+ * The listing's own symbol there, when search lists it under another
33
+ * spelling than the one asked for: BP for `BP.` (London's code for it).
34
+ */
35
+ symbol?: string;
36
+ }
37
+
38
+ /** The venue's own symbol, when it is not `asked` (ignoring case). */
39
+ function ownSymbol(listed: string | undefined, asked: string): { symbol: string } | Record<string, never> {
40
+ const symbol = listed?.trim().toUpperCase();
41
+ return symbol && symbol !== asked.trim().toUpperCase() ? { symbol } : {};
30
42
  }
31
43
 
32
44
  /**
@@ -49,14 +61,15 @@ export async function listingVenues(symbol: string, { store, dataProvider }: Lis
49
61
  : {
50
62
  exchange: resolved.result.exchange === "SMART" ? resolved.result.primaryExchange ?? "" : resolved.result.exchange || resolved.result.primaryExchange || "",
51
63
  name: resolved.result.name,
64
+ ...ownSymbol(parsePublicTickerKey(resolved.result.symbol).symbol, symbol),
52
65
  };
53
- venues = only?.exchange ? [{ exchange: canonicalExchange(only.exchange), name: only.name }] : [];
66
+ venues = only?.exchange ? [{ ...only, exchange: canonicalExchange(only.exchange) }] : [];
54
67
  } catch (error) {
55
68
  // One listing settles the picker; several are its choices.
56
69
  if (!(error instanceof AmbiguousTickerError) || error instanceof AmbiguousContractError) return [];
57
70
  venues = error.listings.flatMap((listing) => {
58
- const exchange = parsePublicTickerKey(listing).exchange;
59
- return exchange ? [{ exchange, name: error.listingNames[listing] ?? "" }] : [];
71
+ const key = parsePublicTickerKey(listing);
72
+ return key.exchange ? [{ exchange: key.exchange, name: error.listingNames[listing] ?? "", ...ownSymbol(key.symbol, symbol) }] : [];
60
73
  });
61
74
  }
62
75
  return venues.filter((venue, index) => venues.findIndex((other) => other.exchange === venue.exchange) === index);
@@ -119,6 +132,28 @@ export async function readAmbiguousCurrencyPair(typed: string, { store, dataProv
119
132
  }
120
133
  }
121
134
 
135
+ /**
136
+ * The saved row a bare symbol stands for, read as the command bar reads it
137
+ * (`savedAbroadForBareSymbol`): a row abroad saved under the bare key (SHEL on
138
+ * LSE, saved from SHEL.L) gives way to the symbol's US listing, the row of it
139
+ * saved under another key, or null when that listing is not saved yet. The
140
+ * row abroad stands when the symbol lists on no US exchange, or search could
141
+ * not say.
142
+ */
143
+ export async function savedRowOfBareSymbol(symbol: string, saved: TickerRecord | null, { store, dataProvider }: ListingDeps): Promise<TickerRecord | null> {
144
+ if (!saved || !savedAbroadForBareSymbol(symbol, saved)) return saved;
145
+ try {
146
+ const tickers = new Map((await store.loadAllTickers()).map((ticker) => [ticker.metadata.ticker.toUpperCase(), ticker] as const));
147
+ const resolved = await resolveTickerSearch({ query: symbol, activeTicker: null, tickers, dataProvider });
148
+ return resolved?.kind === "local" ? resolved.ticker : resolved ? null : saved;
149
+ } catch (error) {
150
+ // US listings no quote settles between: the lookup asks which, as with nothing saved.
151
+ if (error instanceof AmbiguousTickerError && !(error instanceof AmbiguousContractError)) return null;
152
+ logFailure("tickers-listing-venues", "Could not resolve bare symbol")(error);
153
+ return saved;
154
+ }
155
+ }
156
+
122
157
  /** What a typed code that names several venues (7203:TSE) was read as. */
123
158
  export type SharedCodeReading =
124
159
  /** The one of the code's venues the symbol trades on. */
@@ -0,0 +1,36 @@
1
+ import type { TickerRecord } from "../../types/ticker";
2
+ import { tickerHasListingSuffix } from "../../sources/listing-symbols";
3
+ import { canonicalExchange, isUsListingExchange, parsePublicTickerKey } from "../../utils/exchanges";
4
+ import { listingAbroadVenue } from "../../utils/sec";
5
+ import { tickerChangeSymbol } from "../ticker-changes";
6
+
7
+ /**
8
+ * One rule for a bare symbol, whatever the profile saved before: a US-shaped
9
+ * symbol (SHEL, not SHEL.L, SHEL:LSE or 7203) is its US listing whenever it
10
+ * has one. A saved listing abroad (SHEL on LSE, saved from SHEL.L under the
11
+ * bare key) answers for it only when the symbol lists on no US exchange
12
+ * (GARAN, on BIST alone).
13
+ */
14
+ function isBareUsShapedSymbol(symbol: string): boolean {
15
+ return !symbol.includes(":") && !!tickerChangeSymbol(symbol) && !tickerHasListingSuffix(symbol);
16
+ }
17
+
18
+ /** Whether a saved row is a listing abroad that a bare US-shaped symbol settles on only when it has no US listing. */
19
+ export function savedAbroadForBareSymbol(symbol: string, ticker: TickerRecord): boolean {
20
+ if (!isBareUsShapedSymbol(symbol)) return false;
21
+ const venue = savedVenue(ticker);
22
+ return !!venue && !!listingAbroadVenue(`${symbol}:${venue}`);
23
+ }
24
+
25
+ function savedVenue(ticker: TickerRecord): string {
26
+ return canonicalExchange(ticker.metadata.exchange || parsePublicTickerKey(ticker.metadata.ticker).exchange);
27
+ }
28
+
29
+ /** The saved row of a bare symbol's US listing, under any key (SHEL:NYSE beside SHEL on LSE); null when none is saved. */
30
+ export function savedUsListingOf(symbol: string, tickers: Iterable<TickerRecord>): TickerRecord | null {
31
+ for (const ticker of tickers) {
32
+ if (parsePublicTickerKey(ticker.metadata.ticker).symbol !== symbol) continue;
33
+ if (isUsListingExchange(savedVenue(ticker))) return ticker;
34
+ }
35
+ return null;
36
+ }
@@ -39,8 +39,10 @@ import { isProviderBusy, ProviderBusyError, SEARCH_BUSY_MESSAGE } from "../../so
39
39
  import { settleWithin } from "../../utils/async-deadline";
40
40
  import { companyNameExtraWords } from "../company-name";
41
41
  import { withHomeListingLead } from "./home-listing";
42
+ import { savedAbroadForBareSymbol, savedUsListingOf } from "./bare-symbol";
42
43
  import { delistingLabel } from "../../market-data/quotes/delisting";
43
44
  import { hasNonLatinLetters } from "../../utils/typed-text";
45
+ import { isNetworkFailure } from "../../utils/network-failure";
44
46
  export type {
45
47
  ResolvedTickerSearch,
46
48
  TickerSearchCandidate,
@@ -224,6 +226,7 @@ function createProviderTickerSearchCandidates(
224
226
  instrumentClass: classifyInstrumentKind(result.brokerContract?.secType || result.type),
225
227
  instrumentType: result.brokerContract?.secType || result.type,
226
228
  searchAliases: buildSearchResultAliases(result),
229
+ ...(searchResultNames(result) ? { nameAliases: searchResultNames(result) } : {}),
227
230
  ...(searchResultKeywords(result) ? { searchKeywords: searchResultKeywords(result) } : {}),
228
231
  result,
229
232
  }];
@@ -260,7 +263,7 @@ export async function searchTickerCandidates({
260
263
  // A search that hides contracts still shows them when asked: "AAPL OPT".
261
264
  includeOptionContracts: includeOptionContracts || parseAssetClassQuery(query)?.code === "OPT",
262
265
  });
263
- const { results, busy } = await searchProviderResults(
266
+ const { results, busy, unreachable } = await searchProviderResults(
264
267
  dataProvider,
265
268
  query,
266
269
  searchContext,
@@ -268,7 +271,9 @@ export async function searchTickerCandidates({
268
271
  true,
269
272
  );
270
273
  const candidates = assemble(results);
271
- // Saved tickers or another source's rows still answer. Nothing at all, because a source was busy, is no answer.
274
+ // Saved tickers or another source's rows still answer. Nothing at all, because the data service
275
+ // could not be reached or a source was busy, is no answer: not a name nothing lists.
276
+ if (unreachable && candidates.length === 0) throw unreachable;
272
277
  if (busy && candidates.length === 0) throw new ProviderBusyError(SEARCH_BUSY_MESSAGE);
273
278
  return candidates;
274
279
  }
@@ -465,15 +470,27 @@ async function resolveTickerSymbol(
465
470
  if (contracts.size > 1) throw new AmbiguousContractError(symbol,
466
471
  [...contracts.values()].map((contract) => tickerInstrumentLabel(local.metadata.ticker, contract)));
467
472
  }
473
+ // A bare symbol is its US listing whenever it has one (`savedAbroadForBareSymbol`): a row abroad saved under it
474
+ // (SHEL on LSE, from SHEL.L) gives way to the US row saved beside it, else to search's US listing.
475
+ const abroad = !pickerQuery && !!local && savedAbroadForBareSymbol(symbol, local);
476
+ const savedUs = abroad ? savedUsListingOf(symbol, tickers.values()) : null;
477
+ if (savedUs) return { kind: "local", symbol: savedUs.metadata.ticker, ticker: savedUs };
468
478
  // A saved symbol keeps its company. The colon still offers the others.
469
- const saved: ResolvedTickerSearch | null = local ? { kind: "local", symbol: local.metadata.ticker, ticker: local } : null;
470
- if (saved && !pickerQuery) return saved;
479
+ let saved: ResolvedTickerSearch | null = local ? { kind: "local", symbol: local.metadata.ticker, ticker: local } : null;
480
+ if (saved && !pickerQuery && !abroad) return saved;
471
481
 
472
482
  const { results, busy } = await searchProviderResults(dataProvider, symbol, searchContext);
473
483
  const providerItems = createProviderTickerSearchCandidates(results, tickers);
474
484
  const literalMatches = providerItems.filter((item) => normalizeTickerSymbol(item.symbol) === symbol);
475
- const matches = literalMatches.length ? literalMatches
485
+ let matches = literalMatches.length ? literalMatches
476
486
  : providerItems.filter((item) => findExactTickerSearchMatch([item], symbol));
487
+ if (abroad) {
488
+ const us = matches.filter((item) => isUsListingExchange(listingExchange(item.result!)));
489
+ // No US listing: the row abroad is the symbol's.
490
+ if (us.length === 0) return saved;
491
+ matches = us;
492
+ saved = null;
493
+ }
477
494
  let exactMatch = matches[0];
478
495
  if (!exactMatch?.result) {
479
496
  // Nothing matched while a source was too busy to answer: that is no answer, not a miss.
@@ -599,6 +616,8 @@ interface ProviderSearch {
599
616
  results: InstrumentSearchResult[];
600
617
  /** A spelling went unanswered because a source was busy, so an empty list may not mean nothing matches. */
601
618
  busy: boolean;
619
+ /** Why a spelling went unanswered when the data service could not be reached (offline, a hung connection). */
620
+ unreachable: unknown;
602
621
  }
603
622
 
604
623
  async function searchProviderResults(
@@ -651,6 +670,7 @@ async function searchProviderResults(
651
670
  for (const [key, result] of named) byKey.set(key, result);
652
671
  };
653
672
  let busy = false;
673
+ let unreachable: unknown = null;
654
674
  await Promise.all(searchQueries.map(async (searchQuery, index) => {
655
675
  try {
656
676
  const results = await dataProvider.search(searchQuery, {
@@ -669,7 +689,8 @@ async function searchProviderResults(
669
689
  answers[index] = results;
670
690
  merge();
671
691
  } catch (error) {
672
- if (isProviderBusy(error)) busy = true;
692
+ if (isNetworkFailure(error)) unreachable ??= error;
693
+ else if (isProviderBusy(error)) busy = true;
673
694
  else logFailure("tickers-search", "Could not search ticker spelling")(error);
674
695
  }
675
696
  }));
@@ -736,7 +757,7 @@ async function searchProviderResults(
736
757
 
737
758
  // Listings are named in Latin letters, and a source can fail on a name typed in another script (任天堂)
738
759
  // every time it is asked: nothing found for it is no answer to wait for and ask again.
739
- return { results: [...byKey.values()], busy: busy && !hasNonLatinLetters(query) };
760
+ return { results: [...byKey.values()], busy: busy && !hasNonLatinLetters(query), unreachable };
740
761
  }
741
762
 
742
763
  /** Two search texts the server answers alike: its search ignores case and runs of spaces. */
@@ -846,6 +867,14 @@ function getProviderHintScore(result: InstrumentSearchResult, preferredExchange?
846
867
  return getProviderHintRichness(result) + (matchesPreferredExchange ? 10_000 : 0);
847
868
  }
848
869
 
870
+ /** The names the service sent the row for, without the symbol spellings; older servers send none. */
871
+ function searchResultNames(result: InstrumentSearchResult): string[] | undefined {
872
+ const names = Array.isArray(result.searchAliases)
873
+ ? result.searchAliases.filter((alias): alias is string => typeof alias === "string" && !!alias.trim())
874
+ : [];
875
+ return names.length > 0 ? names : undefined;
876
+ }
877
+
849
878
  /** Older servers send none; anything malformed is ignored. */
850
879
  function searchResultKeywords(result: InstrumentSearchResult): string[] | undefined {
851
880
  const keywords = Array.isArray(result.searchKeywords)
@@ -254,7 +254,16 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
254
254
  const explicitIntentScore = scoreAssetPreference(intent, item.instrumentClass)
255
255
  + scoreExchangePreference(intent, item);
256
256
  const priorityScore = explicitIntentScore + scoreListingPriority(item);
257
- const companyMatchRank = scoreCompanyNameMatch(intent, companyNameKey);
257
+ // A company the service found by a name it does not bear in this row (the one it had before it was renamed,
258
+ // a trade name) is that company by the name as much as its listing's own name says so. An index answers to
259
+ // its codes, never as a company.
260
+ const companyAliases = item.instrumentClass === "equity" && !(item.symbol || item.label).startsWith("^")
261
+ ? item.nameAliases ?? []
262
+ : [];
263
+ const companyMatchRank = Math.max(
264
+ scoreCompanyNameMatch(intent, companyNameKey),
265
+ ...companyAliases.map((alias) => scoreCompanyNameMatch(intent, normalizeCompanyName(alias))),
266
+ );
258
267
  return {
259
268
  item,
260
269
  index,
@@ -22,6 +22,11 @@ export interface TickerSearchRankableItem {
22
22
  providerRank?: number;
23
23
  popularity?: number;
24
24
  searchAliases?: string[];
25
+ /**
26
+ * The names the service sent the row for (`InstrumentSearchResult.searchAliases`: a trade name, or the name a
27
+ * renamed company had), apart from the symbol spellings `searchAliases` also holds.
28
+ */
29
+ nameAliases?: string[];
25
30
  /** Themes the row answers to that its name does not say (`InstrumentSearchResult.searchKeywords`). */
26
31
  searchKeywords?: string[];
27
32
  }
@@ -412,6 +412,25 @@ export interface NearMissListing {
412
412
  symbol: string;
413
413
  }
414
414
 
415
+ /**
416
+ * `BP.`, `RR.`: London's code for a company with a two-letter symbol, which
417
+ * the exchange writes with a dot to make three. No other venue writes a
418
+ * trailing dot, though one can trail a US ticker by slip (`GE.`).
419
+ */
420
+ const LONDON_DOTTED = /^([A-Z]{2})\.$/;
421
+
422
+ /**
423
+ * A two-letter symbol written with London's trailing dot (`BP.`): what was
424
+ * typed and the symbol (BP). Whether it is London's line (BP:LSE) or a US
425
+ * ticker with a stray dot (`GE.`) is for where London lists it to say
426
+ * (`resolveCliListing`). Null for anything else.
427
+ */
428
+ export function readLondonDottedSymbol(raw: string): NearMissListing | null {
429
+ const typed = raw.trim();
430
+ const london = LONDON_DOTTED.exec(typed.toUpperCase());
431
+ return london ? { typed, symbol: london[1]! } : null;
432
+ }
433
+
415
434
  /** `BRK/B`: a US share class after a slash, as a broker writes it. One letter before the slash is a ratio or an abbreviation (P/E, N/A). */
416
435
  const SLASH_CLASS = /^([A-Z]{2,5})\/([A-Z])$/;
417
436
  /** `700.HK`: a Hong Kong code without its leading zeros. */
@@ -46,7 +46,7 @@ function isStrictlyOrdered(points: readonly TimeSeriesPoint[]): boolean {
46
46
  }
47
47
 
48
48
  /** Callers only read the result, so ordered input (every market series) is returned as is. */
49
- function sortedUniquePoints(points: readonly TimeSeriesPoint[]): readonly TimeSeriesPoint[] {
49
+ export function sortedUniquePoints(points: readonly TimeSeriesPoint[]): readonly TimeSeriesPoint[] {
50
50
  // Live charts re-resolve on every quote; skip the keyed sort when nothing needs it.
51
51
  if (isStrictlyOrdered(points)) return points;
52
52
  const byObservation = new Map<string, TimeSeriesPoint>();
@@ -10,6 +10,7 @@ import { extractFredSeries } from "./economic";
10
10
  import { getTimeSeriesField, isMarketFieldId } from "./field-catalog";
11
11
  import {
12
12
  unreportedStatementPeriod,
13
+ dailyValuationFallbackPeriod,
13
14
  valuationCurrencyWarning,
14
15
  valuationPriceIssues,
15
16
  } from "./fundamentals";
@@ -20,7 +21,7 @@ import { compareChartQuoteRecency } from "./live-quotes";
20
21
  import { marketSeriesFrequency } from "./market";
21
22
  import { canonicalExchange, resolveExchangeTimeZone } from "../utils/exchanges";
22
23
  import { futuresGenericCaption, futuresGenericListing, futuresListingPriceBasis } from "../utils/futures-generic";
23
- import { contractPriceUnit } from "./price-units";
24
+ import { contractPriceUnit, currencyPairPriceUnit } from "./price-units";
24
25
  import { INDEX_POINTS_UNIT, isIndexLevel } from "../market-data/market/index-level";
25
26
  import { isOhlcSeriesStyle } from "./spec";
26
27
  import { SERIES_COLORS } from "../theme/series-colors";
@@ -118,6 +119,7 @@ export function baseSecuritySeries(
118
119
  const field = getTimeSeriesField(spec.source.fieldId);
119
120
  if (!field) return null;
120
121
  const points = extractSecuritySeries(financials, spec.source);
122
+ const valuationFallback = dailyValuationFallbackPeriod(financials, spec.source);
121
123
  const unreportedPeriod = points.length === 0 ? unreportedStatementPeriod(financials, spec.source) : null;
122
124
  const symbol = instrumentLabel(spec.source);
123
125
  const statementCurrency = field.id.startsWith("fundamental.") && field.unit.startsWith("currency")
@@ -147,8 +149,13 @@ export function baseSecuritySeries(
147
149
  // A future's price in a currency counts what its contract terms say: heating
148
150
  // oil is USD / gallon and crude USD / barrel, which a bare USD does not tell apart.
149
151
  const statedUnit = !genericUnit && unitTemplate === "currency" ? contractPriceUnit(contractUnit, currency) : null;
152
+ // A currency pair's rate is the quote currency one unit of the base buys:
153
+ // EURUSD=X is USD / EUR, so Brent over it reads EUR / barrel.
154
+ const pairUnit = !genericUnit && unitTemplate === "currency" && field.unitGroup === "price" && isMarketFieldId(field.id)
155
+ ? currencyPairPriceUnit(spec.source.instrument.symbol, spec.source.instrument.exchange, currency) : null;
150
156
  const unit = field.id === "market.volume" ? volumeUnit ?? ""
151
- : statedUnit ?? genericUnit ?? (unitTemplate.startsWith("currency") && currency ? unitTemplate.replace("currency", currency) : unitTemplate);
157
+ : statedUnit ?? genericUnit ?? pairUnit
158
+ ?? (unitTemplate.startsWith("currency") && currency ? unitTemplate.replace("currency", currency) : unitTemplate);
152
159
  const currencyUnitGroup = genericUnit ? `${field.unitGroup}:${genericUnit}`
153
160
  : unknownBondBasis && field.unitGroup === "price" ? "price:unknown"
154
161
  : field.unit.startsWith("currency") && currency
@@ -198,10 +205,11 @@ export function baseSecuritySeries(
198
205
  ? financials.quote?.instrumentType || quoteMetadata?.instrumentType : undefined,
199
206
  volumeUnit,
200
207
  ...(priceIssues.length ? { valuationPriceIssues: priceIssues } : {}),
201
- warning: field.id === "market.volume" && !volumeUnit && points.length > 0
202
- ? "Volume unit unknown." : statementCurrency?.warning ?? valuationCurrencyWarning(financials, spec.source),
208
+ warning: [valuationFallback ? "Release dates unavailable; showing reported periods." : undefined,
209
+ field.id === "market.volume" && !volumeUnit && points.length > 0
210
+ ? "Volume unit unknown." : statementCurrency?.warning ?? valuationCurrencyWarning(financials, spec.source)].filter(Boolean).join(" ") || undefined,
203
211
  nativeFrequency: marketField ? marketSeriesFrequency(spec.source, marketResolution)
204
- : spec.source.period && spec.source.period !== "auto" ? spec.source.period : field.nativeFrequency,
212
+ : valuationFallback ?? (spec.source.period && spec.source.period !== "auto" ? spec.source.period : field.nativeFrequency),
205
213
  ...(unreportedPeriod ? { unreportedPeriod } : {}),
206
214
  ...(marketField ? { historyResolution: marketResolution } : {}),
207
215
  timestampMode: spec.source.timestampMode,
@@ -0,0 +1,62 @@
1
+ import type { PricePoint } from "../types/financials";
2
+ import type { TimeSeriesPoint } from "./types";
3
+ import { valuationPriceAtOrBefore } from "./valuation-price";
4
+ import { regularSessionCloseOn } from "../market-data/market/freshness";
5
+ import { financialAvailabilityTime } from "../utils/financial-statements";
6
+
7
+ export interface DailyValuationBasis {
8
+ /** ISO publication date or timestamp. A date without a time takes effect after that session. */
9
+ availableAt?: string;
10
+ /** An undated, unusable statement invalidates the old basis from its period end. */
11
+ observedAt: Date;
12
+ atPrice: (price: number) => number | null;
13
+ periodLabel?: string;
14
+ }
15
+
16
+ /** Reprice a stepped financial figure at each source daily close. */
17
+ export function dailyValuationHistory(
18
+ history: readonly PricePoint[],
19
+ bases: readonly DailyValuationBasis[],
20
+ exchange?: string,
21
+ ): TimeSeriesPoint[] {
22
+ const byDate = new Map<number, PricePoint[]>();
23
+ for (const bar of history) {
24
+ const time = bar.date instanceof Date ? bar.date.getTime() : Date.parse(bar.date);
25
+ if (!Number.isFinite(time) || bar.partial) continue;
26
+ byDate.set(time, [...byDate.get(time) ?? [], bar]);
27
+ }
28
+ const effectiveTime = (basis: DailyValuationBasis) => basis.availableAt
29
+ ? financialAvailabilityTime(basis.availableAt)
30
+ : basis.observedAt.getTime();
31
+ const releases = [...bases].sort((a, b) => effectiveTime(a) - effectiveTime(b)
32
+ || a.observedAt.getTime() - b.observedAt.getTime());
33
+ let next = 0;
34
+ let active: DailyValuationBasis | undefined;
35
+ const points: TimeSeriesPoint[] = [];
36
+ for (const [time, bars] of [...byDate].sort(([a], [b]) => a - b)) {
37
+ // Daily bars name their session at midnight. A timed report belongs to
38
+ // that close only if it was already public, including on early closes.
39
+ const day = new Date(time).toISOString().slice(0, 10);
40
+ const closeTime = regularSessionCloseOn(exchange, day) ?? time;
41
+ while (next < releases.length && effectiveTime(releases[next]!) <= closeTime) {
42
+ const release = releases[next++]!;
43
+ if (!active || release.observedAt >= active.observedAt) active = release;
44
+ }
45
+ const date = new Date(time);
46
+ const price = valuationPriceAtOrBefore(bars, date.toISOString());
47
+ const value = active && price?.price != null ? active.atPrice(price.price) : null;
48
+ points.push({
49
+ date,
50
+ observedAt: date,
51
+ availableAt: active?.availableAt ? new Date(active.availableAt) : undefined,
52
+ value: value !== null && Number.isFinite(value) ? value : null,
53
+ periodLabel: active?.periodLabel,
54
+ provenance: {
55
+ quality: "derived",
56
+ ...(price?.integrity ? { priceHistoryIntegrity: price.integrity } : {}),
57
+ ...(price?.issue ? { valuationPriceIssues: [{ ...price.issue, affectedAt: date.toISOString() }] } : {}),
58
+ },
59
+ });
60
+ }
61
+ return points;
62
+ }
@@ -1,6 +1,6 @@
1
1
  import type { TimeSeriesPoint } from "./types";
2
2
  import type { CloudFredSeriesInfoPayload, CloudFredSeriesPayload } from "../api-client";
3
- import { ICE_CREDIT_SERIES_IDS } from "./ice-credit";
3
+ import { ICE_CREDIT_SERIES_IDS, ICE_HISTORY_LIMIT_NOTICE } from "./ice-credit";
4
4
 
5
5
  function sourceDate(value: string | undefined): number | null {
6
6
  if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
@@ -8,6 +8,21 @@ function sourceDate(value: string | undefined): number | null {
8
8
  return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === value ? time : null;
9
9
  }
10
10
 
11
+ const THREE_YEAR_RETENTION = /Starting in April 2026, this series will only include 3 years of observations\./;
12
+
13
+ /**
14
+ * The publisher's cap on how far back an ICE credit series goes, from the
15
+ * series' own notes, so it lapses if FRED lifts it. Null for any other series.
16
+ */
17
+ export function fredHistoryLimitNotice(
18
+ seriesId: string,
19
+ info: Pick<CloudFredSeriesInfoPayload, "id" | "notes"> | null | undefined,
20
+ ): string | null {
21
+ const id = seriesId.trim().toUpperCase();
22
+ if (!ICE_CREDIT_SERIES_IDS.has(id) || info?.id.trim().toUpperCase() !== id) return null;
23
+ return THREE_YEAR_RETENTION.test(info.notes) ? ICE_HISTORY_LIMIT_NOTICE : null;
24
+ }
25
+
11
26
  /** Source coverage is separate from query limits and calculation buffers. */
12
27
  export function fredCreditCoverageNotice(
13
28
  seriesId: string,
@@ -21,9 +36,8 @@ export function fredCreditCoverageNotice(
21
36
  const end = sourceDate(info.observationEnd);
22
37
  if (start === null || end === null || start > end) return null;
23
38
  if (visibleStart !== null && visibleStart >= start) return null;
24
- const retention = /Starting in April 2026, this series will only include 3 years of observations\./.test(info.notes)
25
- ? " FRED limits this ICE series to 3 years."
26
- : "";
39
+ const limit = fredHistoryLimitNotice(id, info);
40
+ const retention = limit ? ` ${limit}` : "";
27
41
  // Series metadata and observations can refresh independently. An observation
28
42
  // outside the declared span disproves that span; the query's first/last row
29
43
  // cannot establish replacement source coverage either.
@@ -32,7 +32,7 @@ const FIELDS = [
32
32
  unit: "currency",
33
33
  unitGroup: "price",
34
34
  nativeFrequency: "daily",
35
- styles: ["candles", "ohlc", "hlc", "line", "area"],
35
+ styles: ["candles", "hollow", "heikin-ashi", "ohlc", "hlc", "line", "area"],
36
36
  defaultStyle: "candles",
37
37
  transforms: MARKET_TRANSFORMS,
38
38
  defaultInterpolation: "none",
@@ -226,9 +226,7 @@ const FIELDS = [
226
226
  transforms: FUNDAMENTAL_TRANSFORMS,
227
227
  }),
228
228
  ...([
229
- // Trailing twelve-month EPS over the regular session price (the live price in session, the close after it), as
230
- // `valuation` measures it, never an extended print.
231
- ["trailingPE", "Trailing P/E", "P/E", "P/E (TTM, regular session)"],
229
+ ["trailingPE", "Trailing P/E", "P/E"],
232
230
  ["forwardPE", "Forward P/E", "Forward P/E"],
233
231
  ["realizedNtmPE", "Realized NTM P/E", "Realized P/E"],
234
232
  ["pegRatio", "PEG Ratio", "PEG"],