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
@@ -0,0 +1,197 @@
1
+ import type { Fundamentals, FinancialStatement, TickerFinancials } from "../types/financials";
2
+ import { resolveCurrencyUnit } from "../utils/currency-units";
3
+ import { areNearbyFinancialPeriodEnds, statementFieldAvailability } from "../utils/financial-statements";
4
+ import { extractFundamentalSeries, knownWhenFiled } from "./fundamentals";
5
+ import type { SecuritySeriesSource, TimeSeriesPoint } from "./types";
6
+
7
+ const DAY_MS = 86_400_000;
8
+ /**
9
+ * A figure stops pricing this long after its period end. Sixteen months
10
+ * covers a fiscal year plus its filing, so a year of unavailable quarterly
11
+ * sums falls back on the last annual figure but never on one long overdue.
12
+ */
13
+ export const STALE_TRAILING_EPS_MS = 487 * DAY_MS;
14
+ /**
15
+ * The latest period's trailing figure reads statements this far back at most:
16
+ * its four quarters (some 270 days), and for a fiscal fourth quarter read from
17
+ * its year, the three quarters before it. Older rows change nothing.
18
+ */
19
+ const LATEST_WINDOW_MS = 760 * DAY_MS;
20
+
21
+ /** One trailing-twelve-month EPS figure and when it became known. */
22
+ export interface EpsStep {
23
+ /** ISO period end. */
24
+ periodEnd: string;
25
+ /** Four reported quarters summed, or a reported fiscal year. */
26
+ basis: "ttm" | "annual";
27
+ knownAt: Date;
28
+ /** False when no publication date or report is on record and `knownAt` is the period end. */
29
+ dated: boolean;
30
+ /** Statement currency units; null when unavailable (a declared gap or an incomplete run of quarters). */
31
+ eps: number | null;
32
+ currency: string | null;
33
+ }
34
+
35
+ function epsSeries(financials: TickerFinancials, period: "ttm" | "annual"): TimeSeriesPoint[] {
36
+ const source: SecuritySeriesSource = { kind: "security", instrument: { symbol: "" }, fieldId: "fundamental.eps", period, timestampMode: "available-at" };
37
+ return extractFundamentalSeries(financials, source);
38
+ }
39
+
40
+ /**
41
+ * Trailing EPS through time, oldest first. Four reported quarters are summed
42
+ * by the shared statement series, which keeps declared gaps and broken runs
43
+ * of quarters unavailable and reads a fiscal fourth quarter with no EPS of its
44
+ * own from its year. A reported fiscal year is itself a trailing twelve-month
45
+ * figure; at a fiscal year end it replaces the quarterly sum. `known` dates
46
+ * each row's figures (PEB dates them by the company's reports as well).
47
+ */
48
+ export function trailingEpsSteps(
49
+ financials: TickerFinancials,
50
+ known: (row: FinancialStatement) => FinancialStatement = knownWhenFiled,
51
+ ): EpsStep[] {
52
+ const adjusted = { ...financials, quarterlyStatements: financials.quarterlyStatements.map(known),
53
+ annualStatements: financials.annualStatements.map(known) };
54
+ const toStep = (basis: EpsStep["basis"]) => (point: TimeSeriesPoint): EpsStep => ({
55
+ periodEnd: point.observedAt.toISOString().slice(0, 10), basis, knownAt: point.date, dated: point.availableAt !== undefined,
56
+ eps: point.value != null && Number.isFinite(point.value) ? point.value : null, currency: point.provenance?.currency ?? null,
57
+ });
58
+ const steps = epsSeries(adjusted, "annual").map(toStep("annual"));
59
+ for (const step of epsSeries(adjusted, "ttm").map(toStep("ttm"))) {
60
+ const index = steps.findIndex((other) => areNearbyFinancialPeriodEnds(other.periodEnd, step.periodEnd));
61
+ if (index < 0) steps.push(step);
62
+ else if (steps[index]!.eps == null && step.eps != null) steps[index] = step;
63
+ }
64
+ return steps.sort((a, b) => a.knownAt.getTime() - b.knownAt.getTime() || a.periodEnd.localeCompare(b.periodEnd));
65
+ }
66
+
67
+ /** The trailing EPS of the last period filed, with that period's end and the statements' currency. */
68
+ interface FiledTrailingEps {
69
+ eps: number;
70
+ periodEnd: string;
71
+ currency: string;
72
+ }
73
+
74
+ /**
75
+ * How far the filed figure may sit from Gloom Cloud's, either way, before it
76
+ * is taken for another share basis (a receipt or a class the flags miss, as
77
+ * BRK.B's statements are per Class A share) rather than newer or restated
78
+ * quarters: PLTR's 1.17 against 1.13 stands, 4.00 against 0.40 does not.
79
+ */
80
+ const SERVED_EPS_RATIO_LIMIT = 1.5;
81
+
82
+ type TrailingEpsInputs = Pick<TickerFinancials, "quote" | "fundamentals" | "annualStatements" | "quarterlyStatements" | "financialCurrency">;
83
+
84
+ const isReceipt = (financials: TrailingEpsInputs) => !!(
85
+ financials.quote?.isDepositaryReceipt || financials.fundamentals?.isDepositaryReceipt
86
+ || financials.fundamentals?.shareBasis === "depositary_receipt" || financials.quote?.adrRatio || financials.fundamentals?.adrRatio
87
+ );
88
+
89
+ /** `filed` is the day the latest EPS on record was filed (a restated one's restating filing), the period end when none is dated. */
90
+ type LatestStep = { periodEnd: string; eps: number; currency: string; filed: string } | null;
91
+
92
+ /**
93
+ * The last period's step by what it is read from: the router answers the same
94
+ * rows many times, in new arrays. Bounded; the oldest entry leaves first.
95
+ */
96
+ const latestSteps = new Map<string, LatestStep>();
97
+ const LATEST_STEPS_KEPT = 500;
98
+
99
+ /** What the trailing EPS series reads of a row. */
100
+ const rowKey = (row: FinancialStatement) => [
101
+ row.date, row.currency ?? "", row.eps ?? "", statementFieldAvailability(row, "eps") ?? "", row.availableAt ?? "", row.dateSource ?? "",
102
+ Object.keys(row.fieldAvailability ?? {}).length,
103
+ row.unavailableFields?.join("+") ?? "", row.unavailableEarnings?.join("+") ?? "", row.epsBasis?.status ?? "", row.shareBasis ?? "",
104
+ ].join("|");
105
+
106
+ /** The trailing EPS step for the last period on record, null when that period gives none. */
107
+ function latestStep(financials: TrailingEpsInputs): LatestStep {
108
+ const rows = [...financials.quarterlyStatements, ...financials.annualStatements];
109
+ const latest = rows.map((row) => row.date).filter((date) => Number.isFinite(Date.parse(date))).sort().at(-1);
110
+ if (!latest) return null;
111
+ const recent = (row: FinancialStatement) => Date.parse(latest) - Date.parse(row.date) <= LATEST_WINDOW_MS;
112
+ const quarterlyStatements = financials.quarterlyStatements.filter(recent);
113
+ const annualStatements = financials.annualStatements.filter(recent);
114
+ const key = [financials.financialCurrency ?? "", quarterlyStatements.map(rowKey).join(";"), annualStatements.map(rowKey).join(";")].join("#");
115
+ if (latestSteps.has(key)) return latestSteps.get(key)!;
116
+ const found = trailingEpsSteps({ financialCurrency: financials.financialCurrency, priceHistory: [], quarterlyStatements, annualStatements })
117
+ .find((candidate) => areNearbyFinancialPeriodEnds(candidate.periodEnd, latest));
118
+ const filed = [...quarterlyStatements, ...annualStatements]
119
+ .flatMap((row) => typeof row.eps === "number" ? [statementFieldAvailability(row, "eps")?.slice(0, 10)] : [])
120
+ .filter((date): date is string => !!date).sort().at(-1);
121
+ const step = found?.eps != null && found.currency
122
+ ? { periodEnd: found.periodEnd, eps: found.eps, currency: found.currency, filed: filed && filed > found.periodEnd ? filed : found.periodEnd }
123
+ : null;
124
+ if (latestSteps.size >= LATEST_STEPS_KEPT) latestSteps.delete(latestSteps.keys().next().value!);
125
+ latestSteps.set(key, step);
126
+ return step;
127
+ }
128
+
129
+ /**
130
+ * The headline trailing EPS, as PEB and GE read the statements: the diluted
131
+ * EPS of the last four filed quarters, or of the fiscal year when that is the
132
+ * last period filed. Null when that period gives none (a quarter missing or
133
+ * withheld, a split basis unresolved, two share bases), when it is overdue,
134
+ * when its currency is not the price's, for a depositary receipt, whose
135
+ * statements are per ordinary share, when a split went ex after the last of
136
+ * its figures was filed (they are on the old share count until a filing
137
+ * restates them), and when it is more than SERVED_EPS_RATIO_LIMIT times
138
+ * Gloom Cloud's figure either way.
139
+ */
140
+ function filedTrailingEps(financials: TrailingEpsInputs, now = Date.now()): FiledTrailingEps | null {
141
+ const price = resolveCurrencyUnit(financials.quote?.currency);
142
+ if (!financials.quote || price.divisor !== 1 || !/^[A-Z]{3}$/.test(price.currency) || isReceipt(financials)) return null;
143
+ const step = latestStep(financials);
144
+ if (!step || now - Date.parse(step.periodEnd) > STALE_TRAILING_EPS_MS) return null;
145
+ const currency = resolveCurrencyUnit(step.currency);
146
+ if (currency.currency !== price.currency || currency.divisor !== 1) return null;
147
+ // The trailing block's amounts are in another currency: an EPS beside them in this one would be read as theirs.
148
+ const block = financials.fundamentals?.financialCurrency?.trim();
149
+ if (block && resolveCurrencyUnit(block).currency !== price.currency) return null;
150
+ const split = financials.fundamentals?.splitShareBasis?.exDate;
151
+ if (split && split > step.filed) return null;
152
+ const served = financials.fundamentals?.eps;
153
+ if (typeof served === "number" && Number.isFinite(served) && !withinServedRatio(step.eps, served)) return null;
154
+ return { eps: step.eps, periodEnd: step.periodEnd, currency: price.currency };
155
+ }
156
+
157
+ /** Same sign and within SERVED_EPS_RATIO_LIMIT of each other; a figure against a zero is not. */
158
+ function withinServedRatio(filed: number, served: number): boolean {
159
+ const ratio = filed / served;
160
+ return Number.isFinite(ratio) && ratio > 0 && ratio <= SERVED_EPS_RATIO_LIMIT && ratio >= 1 / SERVED_EPS_RATIO_LIMIT;
161
+ }
162
+
163
+ const finite = (value: number | null | undefined): value is number => typeof value === "number" && Number.isFinite(value);
164
+ const positive = (value: number | null | undefined): value is number => finite(value) && value > 0;
165
+
166
+ /**
167
+ * The financials with their headline trailing EPS and P/E on the filed
168
+ * quarters (filedTrailingEps): `fundamentals.eps` is that figure and
169
+ * `trailingPE` the quote's price over it, so every view that moves the P/E to
170
+ * the regular session price (atRegularSessionPrice, liveTrailingPE) reads the
171
+ * multiple PEB and GE read. Without one, Gloom Cloud's trailing EPS and P/E
172
+ * stand, marked `served`, as does a P/E it serves with no EPS (a fund's). A
173
+ * block with neither is left as it is.
174
+ */
175
+ export function withFiledTrailingEps<T extends TrailingEpsInputs>(financials: T, now = Date.now()): T {
176
+ const fundamentals = financials.fundamentals;
177
+ if (!fundamentals) return financials;
178
+ const filed = filedTrailingEps(financials, now);
179
+ if (!filed) {
180
+ const served = finite(fundamentals.eps) || finite(fundamentals.trailingPE);
181
+ if (!served || (fundamentals.trailingEpsSource === "served" && fundamentals.trailingEpsPeriodEnd === undefined)) return financials;
182
+ const { trailingEpsPeriodEnd: _period, ...rest } = fundamentals;
183
+ return { ...financials, fundamentals: { ...rest, trailingEpsSource: "served" } };
184
+ }
185
+ const { trailingPEConversion: _conversion, epsCurrency: _currency, trailingPE: _served, ...rest } = fundamentals;
186
+ // filedTrailingEps reads a quote's currency, so there is a quote; its price is the one atRegularSessionPrice moves from.
187
+ const price = financials.quote!.price;
188
+ const next: Fundamentals = {
189
+ ...rest,
190
+ eps: filed.eps,
191
+ // A loss leaves no multiple: priceEarningsOnEarnings reads the EPS as not meaningful.
192
+ ...(positive(price) && filed.eps > 0 ? { trailingPE: price / filed.eps } : {}),
193
+ trailingEpsSource: "filed-quarters",
194
+ trailingEpsPeriodEnd: filed.periodEnd,
195
+ };
196
+ return { ...financials, fundamentals: next };
197
+ }
@@ -9,6 +9,7 @@ import type { ValuationCurrencyContext } from "./valuation-currency";
9
9
  import { currentValuationPrice, valuationPriceAtOrBefore } from "./valuation-price";
10
10
  import type { TimeSeriesPoint } from "./types";
11
11
  import { isFiniteNumber } from "../utils/guards";
12
+ import { dailyValuationHistory, type DailyValuationBasis } from "./daily-valuation";
12
13
 
13
14
  /**
14
15
  * Forward multiples over time. No source serves the consensus as it stood on
@@ -236,6 +237,37 @@ export function forwardPeHistory(
236
237
  return points;
237
238
  }
238
239
 
240
+ /** Daily closes over consensus that was actually observed by that date. */
241
+ export function dailyForwardPeHistory(
242
+ financials: TickerFinancials,
243
+ currencies: ValuationCurrencyContext,
244
+ exchange?: string,
245
+ ): TimeSeriesPoint[] {
246
+ const history = financials.epsEstimates;
247
+ if (!history) return [];
248
+ // Pre-report estimates from future quarters are final-vintage figures,
249
+ // not the forecasts investors had on an earlier day. Only dated snapshots
250
+ // establish a historical daily forward denominator.
251
+ const bases: DailyValuationBasis[] = [...snapshotsByDay(history.snapshots)].map(([day, periods]) => {
252
+ const current = periods.get("current year");
253
+ const next = periods.get("next year");
254
+ const eps = blendedNextTwelveMonthsEps(day,
255
+ current ? { periodEnd: current.periodEnd, eps: current.epsAverage } : undefined,
256
+ next ? { eps: next.epsAverage } : undefined);
257
+ const date = new Date(day);
258
+ return {
259
+ availableAt: day,
260
+ observedAt: date,
261
+ periodLabel: `Consensus observed ${day}`,
262
+ atPrice: (price: number) => {
263
+ const comparable = currencies.priceInStatementUnits({ date: day, currency: history.currency ?? financials.financialCurrency }, price);
264
+ return comparable !== null && eps !== null ? comparable / eps : null;
265
+ },
266
+ };
267
+ });
268
+ return dailyValuationHistory(financials.priceHistory, bases, financials.quote?.listingExchangeName || financials.quote?.exchangeName || exchange);
269
+ }
270
+
239
271
  /** Price at each report date over the EPS reported in the following four quarters. */
240
272
  export function realizedNtmPeHistory(
241
273
  financials: TickerFinancials,
@@ -8,7 +8,9 @@ import { fiscalPeriodLabel, fiscalYearEndMonth, ttmPeriodLabel } from "../utils/
8
8
  import { hasStatementGap, mergeStatementGaps } from "../utils/statement-gaps";
9
9
  import { trailingEpsAcrossFiscalYearEnd } from "../utils/trailing-eps";
10
10
  import { canonicalTimeSeriesFieldId, getTimeSeriesField } from "./field-catalog";
11
- import { forwardPeHistory, realizedNtmPeHistory } from "./forward-valuation";
11
+ import { dailyForwardPeHistory, forwardPeHistory, realizedNtmPeHistory } from "./forward-valuation";
12
+ import { dailyValuationHistory, type DailyValuationBasis } from "./daily-valuation";
13
+ import { statementAtRelease } from "./valuation-releases";
12
14
  import { reportingCurrencySeries } from "./reporting-currency";
13
15
  import { createValuationCurrencyContext, type ValuationCurrencyContext } from "./valuation-currency";
14
16
  import { currentValuationPrice, valuationPriceAtOrBefore, valuationQuoteIssue, type ValuationPriceIssue } from "./valuation-price";
@@ -474,7 +476,7 @@ function buildTtmStatements(
474
476
  const availableAt = statementFieldAvailability(latest, field);
475
477
  if (availableAt) ttm.fieldAvailability![field] = availableAt;
476
478
  }
477
- ttm.availableAt = completeStatementAvailability(ttm);
479
+ ttm.availableAt = completeStatementAvailability(ttm) ?? latest.availableAt;
478
480
  result.push(ttm);
479
481
  }
480
482
  return result;
@@ -490,7 +492,7 @@ function buildTtmStatements(
490
492
  export function knownWhenFiled(row: FinancialStatement): FinancialStatement {
491
493
  const filed = row.epsBasis?.status === "split-adjusted" ? row.epsBasis.originalFiled : undefined;
492
494
  if (!filed || !Number.isFinite(Date.parse(filed))) return row;
493
- const current = row.fieldAvailability !== undefined ? row.fieldAvailability.eps : row.availableAt;
495
+ const current = statementFieldAvailability(row, "eps");
494
496
  if (current && Date.parse(current) <= Date.parse(filed)) return row;
495
497
  return { ...row, fieldAvailability: { ...row.fieldAvailability, eps: filed } };
496
498
  }
@@ -500,6 +502,56 @@ function valuationStatements(financials: TickerFinancials): { quarterly: Financi
500
502
  return { quarterly: financials.quarterlyStatements.map(knownWhenFiled), annual: financials.annualStatements.map(knownWhenFiled) };
501
503
  }
502
504
 
505
+ /**
506
+ * A fiscal fourth quarter's EPS stated without a filing date of its own (a
507
+ * 10-K states the year, the results release the quarter) was public once its
508
+ * year's filing was, so it is dated by that filing. The daily P/E of the
509
+ * quarters after it then sums the same four quarters PEB and the headline EPS
510
+ * (TTM) sum (time-series/filed-trailing-eps), rather than reading the quarter
511
+ * from the year and landing a cent of rounding away from them. The window that
512
+ * ends with the year takes the year's own EPS (fiscalYearEpsAtYearEnd).
513
+ */
514
+ function fourthQuarterEpsAtYearFiling(row: FinancialStatement, annual: readonly FinancialStatement[]): FinancialStatement {
515
+ if (!isFiniteNumber(row.eps) || hasStatementGap(row, "eps") || statementFieldAvailability(row, "eps")) return row;
516
+ const year = annual.find((candidate) => candidate.currency === row.currency && areNearbyFinancialPeriodEnds(candidate.date, row.date));
517
+ const filed = year && isFiniteNumber(year.eps) ? statementFieldAvailability(year, "eps") : undefined;
518
+ return filed ? { ...row, fieldAvailability: { ...row.fieldAvailability, eps: filed } } : row;
519
+ }
520
+
521
+ /**
522
+ * At a fiscal year end the filed year's diluted EPS is the daily trailing
523
+ * figure, as PEB and the headline EPS (TTM) read it (filed-trailing-eps): four
524
+ * quarters need not add up to their year (3.90 for the year beside four
525
+ * quarters of 1.00), so their sum stands only where the year gives no dated EPS.
526
+ */
527
+ function fiscalYearEpsAtYearEnd(statements: readonly InternalStatement[], annual: readonly FinancialStatement[]): InternalStatement[] {
528
+ return statements.map((statement) => {
529
+ const year = annual.find((row) => row.currency === statement.currency && areNearbyFinancialPeriodEnds(row.date, statement.date));
530
+ const eps = year && !hasStatementGap(year, "eps") ? statementNumber(year, "eps") : null;
531
+ if (!year || eps === null) return statement;
532
+ const { epsBasis: _sum, ...rest } = statement;
533
+ const fieldAvailability = { ...statement.fieldAvailability };
534
+ const filed = statementFieldAvailability(year, "eps");
535
+ if (filed) fieldAvailability.eps = filed;
536
+ else delete fieldAvailability.eps;
537
+ const next: InternalStatement = {
538
+ ...rest, eps, fieldAvailability, ...(year.epsBasis ? { epsBasis: year.epsBasis } : {}),
539
+ __timeSeriesDerivedFields: [...new Set([...(statement.__timeSeriesDerivedFields ?? []), "eps" as const])],
540
+ };
541
+ next.availableAt = completeStatementAvailability(next) ?? statement.availableAt;
542
+ return next;
543
+ });
544
+ }
545
+
546
+ /** An undated vendor value cannot block a derivation from dated filings. */
547
+ function datedValuationStatement(statement: FinancialStatement): FinancialStatement {
548
+ const dated = { ...statement };
549
+ for (const field of NUMERIC_STATEMENT_FIELDS) {
550
+ if (!statementFieldAvailability(statement, field)) delete dated[field];
551
+ }
552
+ return dated;
553
+ }
554
+
503
555
  function normalizeFundamentalPeriod(period: SeriesPeriod | undefined): "annual" | "quarterly" | "ttm" | "auto" {
504
556
  return period === "annual" || period === "quarterly" || period === "ttm" ? period : "auto";
505
557
  }
@@ -508,12 +560,67 @@ function sourceStatements(
508
560
  financials: TickerFinancials,
509
561
  period: SeriesPeriod | undefined,
510
562
  valuationMetric?: string,
563
+ exchange?: string,
511
564
  ): { statements: InternalStatement[]; period: "annual" | "quarterly" | "ttm" } {
512
565
  const normalized = normalizeFundamentalPeriod(period);
513
- const known = valuationMetric
566
+ let known = valuationMetric
514
567
  ? valuationStatements(financials)
515
568
  : { quarterly: financials.quarterlyStatements, annual: financials.annualStatements };
569
+ if (period === "daily" && valuationMetric) {
570
+ const releases = financials.earningsReleases ?? [];
571
+ const venue = financials.quote?.listingExchangeName || financials.quote?.exchangeName || exchange;
572
+ known = { quarterly: known.quarterly.map(row => statementAtRelease(row, releases, venue)),
573
+ annual: known.annual.map(row => statementAtRelease(row, releases, venue)) };
574
+ const annualRows = known.annual;
575
+ known = { ...known, quarterly: known.quarterly.map((row) => fourthQuarterEpsAtYearFiling(row, annualRows)) };
576
+ known = { quarterly: known.quarterly.map(datedValuationStatement), annual: known.annual.map(datedValuationStatement) };
577
+ // A later comparative quarter may repeat a year-end balance sheet that
578
+ // the annual filing already published. Only identical, same-currency
579
+ // snapshots can inherit that earlier evidence; changed values cannot.
580
+ known.quarterly = known.quarterly.map((statement) => {
581
+ const dated = { ...statement, fieldAvailability: Object.fromEntries(NUMERIC_STATEMENT_FIELDS.flatMap((field) => {
582
+ const availableAt = statementFieldAvailability(statement, field);
583
+ return availableAt ? [[field, availableAt]] : [];
584
+ })) };
585
+ for (const annual of known.annual) {
586
+ if (annual.currency !== dated.currency || !areNearbyFinancialPeriodEnds(annual.date, dated.date)) continue;
587
+ for (const field of QUARTERLY_SNAPSHOT_FIELDS) {
588
+ const value = statementNumber(dated, field);
589
+ const earlier = statementFieldAvailability(annual, field);
590
+ const current = statementFieldAvailability(dated, field);
591
+ if (value !== null && value === statementNumber(annual, field) && earlier
592
+ && (!current || Date.parse(earlier) < Date.parse(current))) {
593
+ dated.fieldAvailability[field] = earlier;
594
+ }
595
+ }
596
+ }
597
+ return dated;
598
+ });
599
+ }
516
600
  const quarterly = deriveQuarterlyStatements(known.quarterly, known.annual);
601
+ if (period === "daily" && valuationMetric) {
602
+ // Sparse or loss-making quarters are gaps, not permission to substitute
603
+ // an older annual denominator. Annual-only issuers retain their years.
604
+ if (!known.quarterly.length) return { statements: mergeStatementsByPeriod(known.annual), period: "annual" };
605
+ const statements = fiscalYearEpsAtYearEnd(buildTtmStatements(quarterly, known.annual), known.annual);
606
+ // Capitalization uses the latest reported share count, not an average
607
+ // of four quarters. Keep TTM average shares for the income/EPS fallback.
608
+ if (valuationMetric !== "trailingPE") for (const statement of statements) {
609
+ const latest = quarterly.find((row) => row.date === statement.date);
610
+ const year = known.annual.find((row) => areNearbyFinancialPeriodEnds(row.date, statement.date));
611
+ const shareRow = latest && selectedShares(latest) ? latest : year;
612
+ for (const field of ["dilutedShares", "basicShares", "ordinarySharesNumber", "shareIssued"] as const) {
613
+ delete statement[field];
614
+ delete statement.fieldAvailability?.[field];
615
+ if (shareRow?.[field] !== undefined) {
616
+ statement[field] = shareRow[field];
617
+ const availableAt = statementFieldAvailability(shareRow, field);
618
+ if (availableAt) (statement.fieldAvailability ??= {})[field] = availableAt;
619
+ }
620
+ }
621
+ }
622
+ return { statements, period: "ttm" };
623
+ }
517
624
  if (normalized === "annual") {
518
625
  return { statements: mergeStatementsByPeriod(known.annual), period: "annual" };
519
626
  }
@@ -727,7 +834,7 @@ function historicalValuation(
727
834
  currencies: ValuationCurrencyContext,
728
835
  ) {
729
836
  // A price failure cannot make absent financial inputs appear available.
730
- if (currencies.priceInStatementUnits(statement, 1) === null || valuationAtPrice(statement, metric, 1) === null) return null;
837
+ if (currencies.priceInStatementUnits(statement, 1) === null || valuationAtPrice(statement, metric, 1, true) === null) return null;
731
838
  const priceDate = metricAvailability(statement, metric) ?? statement.date;
732
839
  const price = valuationPriceAtOrBefore(financials.priceHistory, priceDate);
733
840
  if (!price) return null;
@@ -754,6 +861,7 @@ function valuationAtPrice(
754
861
  statement: FinancialStatement,
755
862
  metric: string,
756
863
  price: number,
864
+ checkingInputs = false,
757
865
  ): number | null {
758
866
  const shares = selectedShares(statement);
759
867
  const marketCap = shares ? price * shares.value : null;
@@ -769,12 +877,83 @@ function valuationAtPrice(
769
877
  return eps && eps > 0 ? price / eps : null;
770
878
  }
771
879
  if (metric === "priceSales") return ratio(marketCap, statement.totalRevenue);
880
+ // The probe at price=1 checks input coverage, not the historical EV sign.
881
+ if (!checkingInputs && enterpriseValue !== null && enterpriseValue < 0 && (metric === "evSales" || metric === "evEbitda")) return null;
772
882
  if (metric === "evSales") return ratio(enterpriseValue, statement.totalRevenue);
773
883
  if (metric === "evEbitda") return ratio(enterpriseValue, statement.ebitda);
774
884
  if (metric === "priceFcf") return ratio(marketCap, freeCashFlow(statement).value);
775
885
  return null;
776
886
  }
777
887
 
888
+ function dailyStatementValuation(financials: TickerFinancials, metric: string, exchange?: string): TimeSeriesPoint[] {
889
+ const earningsMetric = metric === "pegRatio" ? "trailingPE" : metric;
890
+ const selected = sourceStatements(financials, "daily", earningsMetric, exchange);
891
+ const currencies = createValuationCurrencyContext(financials);
892
+ const yearEndMonth = fiscalYearEndMonth(financials.annualStatements);
893
+ const bases: DailyValuationBasis[] = selected.statements.flatMap((statement) => {
894
+ const usable = valuationAtPrice(statement, earningsMetric, 1, true) !== null;
895
+ // Missing dates cannot put information at the period end: until its
896
+ // publication is known there is no historical denominator to use.
897
+ let publication = metricAvailability(statement, earningsMetric)
898
+ ?? (!usable ? statement.availableAt : undefined);
899
+ let growth: number | null = null;
900
+ if (metric === "pegRatio") {
901
+ const prior = selected.statements.find((candidate) => {
902
+ const days = (statementTime(statement) - statementTime(candidate)) / DAY_MS;
903
+ return days >= 330 && days <= 400;
904
+ });
905
+ const currentEps = selectedEps(statement)?.value;
906
+ const previousEps = prior ? selectedEps(prior)?.value : null;
907
+ if (prior && currentEps != null && currentEps > 0 && previousEps != null && previousEps > 0) {
908
+ publication = completeAvailability([publication, metricAvailability(prior, "trailingPE")]);
909
+ const change = (currentEps / previousEps - 1) * 100;
910
+ if (change > 0) growth = change;
911
+ }
912
+ }
913
+ const observedAt = validDate(statement.date);
914
+ if (!observedAt) return [];
915
+ const availableAt = validDate(publication);
916
+ if (availableAt && availableAt < observedAt) publication = undefined;
917
+ const point = pointForStatement(statement, earningsMetric, null, selected.period, "available-at", true, yearEndMonth);
918
+ return [{
919
+ availableAt: publication,
920
+ observedAt,
921
+ periodLabel: point?.periodLabel,
922
+ atPrice: (price: number) => {
923
+ // An undated newer period must stop the previous TTM from continuing.
924
+ // Its period end only bounds the gap; it never publishes a value.
925
+ if (!publication || !availableAt) return null;
926
+ const comparable = currencies.priceInStatementUnits(statement, price);
927
+ if (comparable === null) return null;
928
+ const value = valuationAtPrice(statement, earningsMetric, comparable);
929
+ // Negative cash flow, revenue or EBITDA cannot produce a meaningful
930
+ // multiple. Explicit period views retain their historical behavior.
931
+ const denominator = earningsMetric === "priceFcf" ? freeCashFlow(statement).value
932
+ : earningsMetric === "evEbitda" ? statement.ebitda
933
+ : earningsMetric === "priceSales" || earningsMetric === "evSales" ? statement.totalRevenue : 1;
934
+ if (denominator == null || denominator <= 0 || value === null) return null;
935
+ return metric === "pegRatio" ? growth === null ? null : value / growth : value;
936
+ },
937
+ }];
938
+ });
939
+ const points = dailyValuationHistory(financials.priceHistory, bases, financials.quote?.listingExchangeName || financials.quote?.exchangeName || exchange);
940
+ return points.some((point) => point.value !== null) ? points : [];
941
+ }
942
+
943
+ /** A daily history cannot be dated at all, but the issuer still has reported figures. */
944
+ export function dailyValuationFallbackPeriod(financials: TickerFinancials, source: SecuritySeriesSource): "annual" | "quarterly" | "ttm" | null {
945
+ if (source.period !== "daily") return null;
946
+ const [namespace, metric = ""] = canonicalTimeSeriesFieldId(source.fieldId).split(".");
947
+ const inputMetric = metric === "pegRatio" ? "trailingPE" : metric;
948
+ if (namespace !== "valuation" || !QUOTE_DERIVED_VALUATION_IDS.has(inputMetric)) return null;
949
+ const daily = sourceStatements(financials, "daily", inputMetric, source.instrument.exchange);
950
+ if (daily.statements.some(row => metricAvailability(row, inputMetric))) return null;
951
+ const annual = sourceStatements(financials, "annual", inputMetric);
952
+ if (annual.statements.some(row => valuationAtPrice(row, inputMetric, 1, true) !== null)) return "annual";
953
+ const reported = sourceStatements(financials, "auto", inputMetric);
954
+ return reported.statements.some(row => valuationAtPrice(row, inputMetric, 1, true) !== null) ? reported.period : null;
955
+ }
956
+
778
957
  function providerCurrentValuationPoint(
779
958
  financials: TickerFinancials,
780
959
  metric: string,
@@ -887,9 +1066,9 @@ export function valuationSeriesUsesLiveQuote(fieldId: string): boolean {
887
1066
  }
888
1067
 
889
1068
  /** Whether a valuation field needs the full price history to date its observations. */
890
- export function valuationSeriesUsesPriceHistory(fieldId: string): boolean {
1069
+ export function valuationSeriesUsesPriceHistory(fieldId: string, period?: SeriesPeriod): boolean {
891
1070
  const [namespace, metric = ""] = canonicalTimeSeriesFieldId(fieldId).split(".");
892
- return namespace === "valuation" && PRICE_HISTORY_VALUATION_IDS.has(metric);
1071
+ return namespace === "valuation" && (PRICE_HISTORY_VALUATION_IDS.has(metric) || (metric === "pegRatio" && period === "daily"));
893
1072
  }
894
1073
 
895
1074
  function preferredPeriodPoint(
@@ -983,6 +1162,15 @@ export function extractFundamentalSeries(
983
1162
  }
984
1163
 
985
1164
  if (namespace !== "valuation" || !VALUATION_IDS.has(metric)) return [];
1165
+ const fallback = dailyValuationFallbackPeriod(financials, source);
1166
+ if (fallback) return extractFundamentalSeries(financials, { ...source, period: fallback });
1167
+ if (source.period === "daily" && metric !== "realizedNtmPE") {
1168
+ if (metric !== "forwardPE") return dailyStatementValuation(financials, metric, source.instrument.exchange);
1169
+ const points = dailyForwardPeHistory(financials, createValuationCurrencyContext(financials), source.instrument.exchange);
1170
+ if (points.some((point) => point.value !== null)) return points;
1171
+ const current = providerCurrentValuationPoint(financials, metric);
1172
+ return current ? [current] : [];
1173
+ }
986
1174
  if (metric === "forwardPE") {
987
1175
  const points = forwardPeHistory(financials, createValuationCurrencyContext(financials));
988
1176
  if (points.length > 0) return points;
@@ -1066,6 +1254,9 @@ export function valuationCurrencyWarning(financials: TickerFinancials, source: S
1066
1254
 
1067
1255
  /** Structured failures preserve source values independently of the chart's ratios. */
1068
1256
  export function valuationPriceIssues(financials: TickerFinancials, source: SecuritySeriesSource): ValuationPriceIssue[] {
1257
+ if (source.period === "daily" && canonicalTimeSeriesFieldId(source.fieldId).startsWith("valuation.")) {
1258
+ return extractFundamentalSeries(financials, source).flatMap((point) => point.provenance?.valuationPriceIssues ?? []);
1259
+ }
1069
1260
  if (!valuationSeriesUsesLiveQuote(source.fieldId)) return [];
1070
1261
  const metric = canonicalTimeSeriesFieldId(source.fieldId).split(".")[1]!;
1071
1262
  const currencies = createValuationCurrencyContext(financials);
@@ -79,6 +79,7 @@ export function useResolvedChartSpec(
79
79
  const sources = useMemo(() => ({
80
80
  dataProvider,
81
81
  loadFredSeries: loadFred,
82
+ loadEarningsHistory: (symbol: string) => apiClient.getCloudEarningsHistory(symbol, 100),
82
83
  resolveCapabilitySeries: createChartSeriesResolver(capabilityInvoker),
83
84
  capabilityRefreshMs: registeredSeriesRefreshMs,
84
85
  loadFuturesPriceUnit: (symbol: string) => loadFuturesPriceUnit(symbol),
@@ -17,6 +17,9 @@ export interface IceCreditSeries {
17
17
  tags: readonly string[];
18
18
  }
19
19
 
20
+ /** What every surface that shows an ICE series says about how far back FRED carries it. */
21
+ export const ICE_HISTORY_LIMIT_NOTICE = "History limited to 3 years by the publisher.";
22
+
20
23
  const HIGH_YIELD = ["high yield", "junk"];
21
24
 
22
25
  export const ICE_CREDIT_SERIES: readonly IceCreditSeries[] = [
@@ -10,6 +10,19 @@ import {
10
10
  } from "../market-data/market/period-return";
11
11
 
12
12
  const PRICE_COMPARISON_BASIS = "Price returns in each listing's currency; cash distributions and FX conversion excluded.";
13
+ const TOTAL_COMPARISON_BASIS = "Total returns in each listing's currency; cash distributions reinvested at the ex-date close, FX conversion excluded.";
14
+
15
+ /**
16
+ * What a comparison's returns are: price returns, or total returns when the
17
+ * chart asked for them, naming any leg whose distributions could not be read
18
+ * (it stays a price return, and the others do not wait for it).
19
+ */
20
+ function comparisonBasis(spec: ChartSpec, legs: ReadonlyArray<{ label: string; entry: ResolvedSeries | undefined }>): string {
21
+ if (spec.viewport.totalReturn !== true) return PRICE_COMPARISON_BASIS;
22
+ const priceOnly = legs.filter(({ entry }) => entry?.returnBasis === "price").map(({ label }) => label);
23
+ if (priceOnly.length === legs.length && legs.length > 0) return PRICE_COMPARISON_BASIS;
24
+ return priceOnly.length ? `${TOTAL_COMPARISON_BASIS} Price return for ${priceOnly.join(", ")}: distribution history unavailable.` : TOTAL_COMPARISON_BASIS;
25
+ }
13
26
 
14
27
  export interface PriceComparison {
15
28
  seriesIds: string[];
@@ -111,6 +124,20 @@ export function priceComparisonSeriesIds(spec: ChartSpec): string[] | null {
111
124
  return visible.map((entry) => entry.id);
112
125
  }
113
126
 
127
+ /**
128
+ * What a total-return chart's price levels are when it is not a comparison (the
129
+ * raw price axis): prices with cash distributions reinvested, not what the
130
+ * listing quotes. Null for a price chart, and when no leg is a total return.
131
+ */
132
+ export function totalReturnLevelsNotice(spec: ChartSpec, series: readonly ResolvedSeries[]): string | null {
133
+ if (spec.viewport.totalReturn !== true) return null;
134
+ const legs = series.filter((entry) => entry.returnBasis !== undefined);
135
+ if (!legs.some((entry) => entry.returnBasis === "total")) return null;
136
+ const priceOnly = legs.filter((entry) => entry.returnBasis === "price").map((entry) => entry.label);
137
+ return `Total return levels in each listing's currency: prices include cash distributions reinvested at the ex-date close, so they are not quoted prices; FX conversion excluded.${
138
+ priceOnly.length ? ` Price only for ${priceOnly.join(", ")}: distribution history unavailable.` : ""}`;
139
+ }
140
+
114
141
  interface ComparedLeg {
115
142
  id: string;
116
143
  label: string;
@@ -152,7 +179,8 @@ export function resolvePriceComparison(
152
179
  return source?.kind === "security" ? source.instrument.symbol : id;
153
180
  };
154
181
  // A leg not loaded yet (a seed from cached history) has not failed: nothing compares until it is.
155
- const unavailable = `${PRICE_COMPARISON_BASIS} Comparison unavailable: need two shared dates and a nonzero baseline.`;
182
+ const basis = comparisonBasis(spec, seriesIds.map((id) => ({ label: symbolOf(id), entry: byId.get(id) })));
183
+ const unavailable = `${basis} Comparison unavailable: need two shared dates and a nonzero baseline.`;
156
184
  if (seriesIds.some((id) => !byId.has(id))) return { seriesIds, start: null, end: null, notice: unavailable };
157
185
  const dropped: NonNullable<PriceComparison["dropped"]> = [];
158
186
  const reported = new Set<string>();
@@ -259,7 +287,7 @@ export function resolvePriceComparison(
259
287
  sourceBounds,
260
288
  legs: legDays,
261
289
  ...(dropped.length ? { dropped } : {}),
262
- notice: `${PRICE_COMPARISON_BASIS} ${span}${droppedText}`,
290
+ notice: `${basis} ${span}${droppedText}`,
263
291
  };
264
292
  }
265
293