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
@@ -1,24 +1,82 @@
1
1
  import { apiClient } from "../../../api-client";
2
2
  import type { DividendSummary } from "../../../api-client/market-discovery";
3
3
  import type { ConnectionHealthRegistry } from "../../../core/connection-health";
4
- import type { DividendMetrics, DividendPayment } from "./types";
4
+ import type { DividendMetrics, DividendPayment, GrowthPayments } from "./types";
5
5
  import { resolveCurrencyUnit } from "../../../utils/currency-units";
6
6
  import { calendarMonthsBefore } from "../../../utils/calendar-date";
7
- import { cashDistribution, dividendReferencePrice, dividendYieldAt, inferCadence, trailingCashAt, trailingDividendRate } from "../../../utils/trailing-dividend";
7
+ import { cashDistribution, dividendReferencePrice, dividendYieldAt, inferCadence, trailingCashAt, trailingDividendRate, trailingPaymentShortfall, unevenRegularPayments } from "../../../utils/trailing-dividend";
8
8
  import { dividendQuotePriceMetadata } from "./reference-price";
9
9
  import { mapQuote } from "../../../sources/gloomberb-cloud/normalizers";
10
10
  import { regularSessionValuationPrice } from "../../../market-data/quotes/regular-session-valuation";
11
11
  import { declaredDividends } from "../../../utils/declared-dividends";
12
12
  import { cashInMajorUnits } from "../../../utils/dividend-cash";
13
13
  import { lapsedDividend } from "../../../utils/forward-dividend";
14
- import type { DeclaredDividendAction } from "../../../types/financials";
14
+ import type { DeclaredCash, DeclaredDividendAction, TickerFinancials } from "../../../types/financials";
15
+ import { logFailure } from "../../../utils/log-failure";
15
16
 
16
17
  export const DIVIDENDS_CONNECTION_ID = "gloom-dividends";
17
- export const INCOMPLETE_DIVIDEND_HISTORY = "Incomplete cash history; totals unavailable.";
18
+
19
+ /** ` (last tried 2026-10-27 15:38 UTC)`, or nothing when the time is not known. */
20
+ function lastTried(at: string | undefined): string {
21
+ const time = at ? Date.parse(at) : Number.NaN;
22
+ if (!Number.isFinite(time)) return "";
23
+ const iso = new Date(time).toISOString();
24
+ return ` (last tried ${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC)`;
25
+ }
26
+
27
+ /** A read with some cash rows, but not all of them usable. */
28
+ function incompleteDividendHistory(triedAt?: string): string {
29
+ return `Incomplete cash history${lastTried(triedAt)}; totals unavailable.`;
30
+ }
31
+ /** A read with no cash rows at all: nothing is incomplete, there is nothing. */
32
+ function unavailableDividendHistory(triedAt?: string): string {
33
+ return `Cash history unavailable${lastTried(triedAt)}; no payments, totals or growth.`;
34
+ }
35
+ /** A read without its summary; `forward` when another source still gave the forward rate. */
36
+ function unavailableDividendSummary(triedAt?: string, { forward = false } = {}): string {
37
+ return `Dividend summary unavailable${lastTried(triedAt)}; no ${forward ? "payout ratio or pay date" : "forward rate, payout ratio or pay date"}.`;
38
+ }
39
+ export const INCOMPLETE_DIVIDEND_HISTORY = incompleteDividendHistory();
18
40
  export const MISSING_DIVIDEND_CURRENCY = "Dividend currency is unavailable; cash amounts cannot be compared safely.";
19
- export const UNAVAILABLE_DIVIDEND_SUMMARY = "Dividend summary unavailable.";
41
+ export const UNAVAILABLE_DIVIDEND_SUMMARY = unavailableDividendSummary();
20
42
  export const INVALID_DIVIDEND_SUMMARY_DATE = "Invalid dividend summary date.";
21
43
 
44
+ /** The forward rate a listing's fundamentals state: the annual rate, in the major unit of `currency`, and the ex-date it annualizes. */
45
+ interface StatedForwardRate {
46
+ rate: number;
47
+ exDate: string;
48
+ currency: string;
49
+ }
50
+
51
+ /**
52
+ * The forward rate fundamentals state for a listing (DUK's 4.34, declared,
53
+ * ex 2026-11-13), in its quote's currency; null when its yield is not a
54
+ * forward one or the rate, its ex-date or the currency is missing.
55
+ */
56
+ export function statedForwardRate(financials: Pick<TickerFinancials, "fundamentals" | "quote"> | null | undefined): StatedForwardRate | null {
57
+ const fundamentals = financials?.fundamentals;
58
+ const currency = resolveCurrencyUnit(financials?.quote?.currency).currency;
59
+ const rate = fundamentals?.dividendRate;
60
+ const exDate = fundamentals?.exDividendDate;
61
+ if (fundamentals?.dividendYieldBasis !== "forward" || !currency || typeof rate !== "number" || !Number.isFinite(rate) || rate <= 0
62
+ || !exDate || !/^\d{4}-\d{2}-\d{2}$/.test(exDate)) return null;
63
+ return { rate, exDate, currency };
64
+ }
65
+
66
+ /** A summary holding only a stated forward rate, dated by its ex-date, for a read whose own summary is missing. */
67
+ function statedRateSummary(stated: StatedForwardRate): QuoteSummaryDividendFields {
68
+ return {
69
+ trailingAnnualDividendRate: null, trailingAnnualDividendYield: null, payoutRatio: null, dividendDate: null,
70
+ forwardAnnualDividendRate: stated.rate, forwardRateAsOf: stated.exDate,
71
+ exDividendDate: Date.parse(`${stated.exDate}T00:00:00Z`) / 1000, currency: stated.currency,
72
+ };
73
+ }
74
+
75
+ interface DividendReadOptions {
76
+ /** The forward rate fundamentals state (statedForwardRate), asked only when the summary is missing. */
77
+ loadStatedForwardRate?: () => Promise<StatedForwardRate | null> | StatedForwardRate | null;
78
+ }
79
+
22
80
  let connectionHealth: ConnectionHealthRegistry | null = null;
23
81
 
24
82
  export function attachDividendYieldHealth(health?: ConnectionHealthRegistry): void {
@@ -55,6 +113,18 @@ export function toDividendPayment(
55
113
  };
56
114
  }
57
115
 
116
+ /**
117
+ * The declared amount of each paid row by its ex-date: a company declaring in
118
+ * another currency than the listing pays. Rows in the listing's own currency,
119
+ * or with no usable amount, are not a declaration in another currency.
120
+ */
121
+ function declaredCashByExDate(declared: DeclaredCash | undefined, cashCurrency: string): Map<string, { amount: number; currency: string }> {
122
+ const unit = resolveCurrencyUnit(declared?.currency);
123
+ if (!declared || !unit.currency || unit.currency === cashCurrency || !Array.isArray(declared.rows)) return new Map();
124
+ return new Map(declared.rows.flatMap((row) => typeof row?.exDate === "string" && Number.isFinite(row.amount) && row.amount > 0
125
+ ? [[row.exDate, { amount: row.amount / unit.divisor, currency: unit.currency }] as const] : []));
126
+ }
127
+
58
128
  export interface DividendData {
59
129
  payments: DividendPayment[];
60
130
  /** Declared and unpaid, in `currency`; never in `payments` or any figure. Absent from older servers. */
@@ -67,6 +137,8 @@ export interface DividendData {
67
137
  historyError?: string;
68
138
  /** Independent summary failures do not invalidate usable cash history. */
69
139
  summaryError?: string;
140
+ /** Set when the summary was missing and the forward rate is the one fundamentals state (statedForwardRate). */
141
+ forwardFromFundamentals?: boolean;
70
142
  notes?: string[];
71
143
  providerId?: string;
72
144
  /** Source fetch time, not the last ex-date or this pane's cache-read time. */
@@ -81,31 +153,41 @@ export async function fetchDividendData(
81
153
  currentPrice: number | null,
82
154
  exchange = "",
83
155
  currentPriceCurrency?: string,
156
+ options: DividendReadOptions = {},
84
157
  ): Promise<DividendData> {
85
158
  const response = await trackRequest("dividends", () => apiClient.getMarketDividends(symbol, exchange));
86
159
  if (!response.data) throw new Error(`No dividend data found for ${symbol}`);
87
- const { actions: sourceActions, quote: rawQuote, summary: quoteFields } = response.data;
160
+ const { actions: sourceActions, quote: rawQuote, summary, triedAt } = response.data;
88
161
  // In the major unit as the history arrives, as every other reader of it gets it.
89
162
  const actions = sourceActions ? cashInMajorUnits(sourceActions) : null;
90
163
  const quote = rawQuote ? mapQuote(rawQuote) : null;
91
- const summaryError = response.data.summaryError ?? (!quoteFields ? UNAVAILABLE_DIVIDEND_SUMMARY
92
- : [quoteFields.exDividendDate, quoteFields.dividendDate]
164
+ const cashCurrency = resolveCurrencyUnit(actions?.currency).currency;
165
+ // Without a summary, the forward rate fundamentals state stands in, dated by the ex-date it annualizes (DUK's declared one).
166
+ const stated = !summary && options.loadStatedForwardRate
167
+ ? await Promise.resolve().then(options.loadStatedForwardRate).catch(logFailure("dividend-yield.client", "Read the stated forward rate failed", null))
168
+ : null;
169
+ const statedFields = !summary && stated && (!cashCurrency || stated.currency === cashCurrency) ? statedRateSummary(stated) : null;
170
+ const quoteFields = summary ?? statedFields;
171
+ const summaryError = !summary ? unavailableDividendSummary(triedAt, { forward: quoteFields != null })
172
+ : response.data.summaryError ?? ([summary.exDividendDate, summary.dividendDate]
93
173
  .some((timestamp) => timestamp != null && reportedDividendDate(timestamp) === null)
94
174
  ? INVALID_DIVIDEND_SUMMARY_DATE : undefined);
95
- const cashCurrency = resolveCurrencyUnit(actions?.currency).currency;
96
175
  const summaryUnit = resolveCurrencyUnit(quoteFields?.currency);
97
176
  const currency = cashCurrency || summaryUnit.currency;
98
- let historyError = response.data.historyError;
177
+ let historyError = !actions && response.data.historyError ? unavailableDividendHistory(triedAt) : response.data.historyError;
99
178
  if (actions && !cashCurrency) historyError = MISSING_DIVIDEND_CURRENCY;
100
179
  if (!currency && (quoteFields?.trailingAnnualDividendRate != null || quoteFields?.forwardAnnualDividendRate != null)) {
101
180
  historyError = MISSING_DIVIDEND_CURRENCY;
102
181
  }
182
+ const declaredCash = cashCurrency ? declaredCashByExDate(actions?.declaredCash, cashCurrency) : new Map();
103
183
  const payments = actions && cashCurrency ? actions.dividends.flatMap(action => {
104
184
  const payment = toDividendPayment(action.exDate, action.amount, cashCurrency);
105
- return payment ? [payment] : [];
185
+ const declared = declaredCash.get(action.exDate);
186
+ return payment ? [declared ? { ...payment, declared } : payment] : [];
106
187
  }).sort((a, b) => b.exDate.getTime() - a.exDate.getTime()) : [];
107
188
  if (!historyError && actions && (actions.coverage?.dividends === "unavailable" || payments.length !== actions.dividends.length)) {
108
- historyError = INCOMPLETE_DIVIDEND_HISTORY;
189
+ // A read with no rows is not an incomplete one.
190
+ historyError = actions.dividends.length === 0 ? unavailableDividendHistory(triedAt) : incompleteDividendHistory(triedAt);
109
191
  }
110
192
  const suppliedPrice = dividendReferencePrice(currentPrice, currentPriceCurrency, currency);
111
193
  // The regular session's price, as the quote headlines it: after the close, the close rather than an after-hours print.
@@ -124,7 +206,9 @@ export async function fetchDividendData(
124
206
  payments, ...(declared.length > 0 ? { declared } : {}), metrics, price, currency: currency || undefined, historyAvailable,
125
207
  ...(suppliedPrice == null && price != null && quote ? dividendQuotePriceMetadata(quote) : {}),
126
208
  ...(historyError ? { historyError } : {}), ...(summaryError ? { summaryError } : {}),
127
- providerId: actions?.providerId ?? "gloom", fetchedAt: actions?.fetchedAt,
209
+ ...(statedFields && metrics.forwardRate != null ? { forwardFromFundamentals: true } : {}),
210
+ // A history standing in for a refresh that could not read it is dated by its own read.
211
+ providerId: actions?.providerId ?? "gloom", fetchedAt: actions?.dividendsAsOf ?? actions?.fetchedAt,
128
212
  stale: response.stale === true || response.data.stale === true || actions?.stale === true,
129
213
  notes: ["Cash yield excludes taxes and reinvestment. SEC yield, tax components and future payments are not modeled."],
130
214
  };
@@ -155,9 +239,6 @@ export function buildDividendMetrics(
155
239
  const statedRate = options.summaryRatesComparable !== false ? quoteFields?.forwardAnnualDividendRate ?? null : null;
156
240
  const statedAsOf = statedRate != null && /^\d{4}-\d{2}-\d{2}$/.test(quoteFields?.forwardRateAsOf ?? "")
157
241
  ? quoteFields!.forwardRateAsOf! : null;
158
- const growth1Y = options.historyAvailable !== false ? computeGrowth(eligible, 1, now) : null;
159
- const growth3Y = options.historyAvailable !== false ? computeGrowth(eligible, 3, now) : null;
160
-
161
242
  // A reported ex-date can be the latest one or an announced one; keep them apart.
162
243
  const reportedExDate = reportedDividendDate(quoteFields?.exDividendDate ?? null);
163
244
  const pastExDates = payments.filter((payment) => payment.exDate <= now).map((payment) => payment.exDate.getTime());
@@ -179,8 +260,17 @@ export function buildDividendMetrics(
179
260
  exDates: [...payments.map((payment) => payment.exDate), ...lastExDividendDate ? [lastExDividendDate] : []], declared }, now)
180
261
  : lapsedDividend([statedAsOf, lastExDividendDate?.toISOString().slice(0, 10)]
181
262
  .filter((day): day is string => !!day).sort().at(-1), { exDates: null, declared }, now);
182
- const forwardRate = lapse ? null : statedRate;
183
- const forwardRateAsOf = lapse ? null : statedAsOf;
263
+ // A year whose regular payments differ widely (HSBC's final beside its interims) has no one payment to annualize: its total stands.
264
+ const uneven = statedRate != null && !lapse && options.historyAvailable !== false ? unevenRegularPayments(eligible, now) : null;
265
+ const forwardRate = lapse ? null : uneven ? uneven.total : statedRate;
266
+ const forwardRateAsOf = lapse ? null : uneven ? uneven.latest.toISOString().slice(0, 10) : statedAsOf;
267
+ const growth1Y = options.historyAvailable !== false ? computeGrowth(eligible, 1, now) : null;
268
+ const growth3Y = options.historyAvailable !== false ? computeGrowth(eligible, 3, now) : null;
269
+
270
+ // A payout over zero or negative earnings (ECC's 2000% beside an EPS of -1.30) is not a payout, whatever the source states.
271
+ const payoutNotMeaningful = typeof quoteFields?.trailingEps === "number" && Number.isFinite(quoteFields.trailingEps) && quoteFields.trailingEps <= 0;
272
+ // A trailing yield over a hole in a regular payer's history says how many payments it counts; a stopped dividend has said so.
273
+ const trailingPayments = options.historyAvailable !== false && !lapse ? trailingPaymentShortfall(eligible, now) : null;
184
274
 
185
275
  return repriceDividendMetrics({
186
276
  trailingYield: null,
@@ -188,10 +278,15 @@ export function buildDividendMetrics(
188
278
  trailingRate,
189
279
  forwardRate,
190
280
  forwardRateAsOf,
281
+ ...(uneven ? { forwardBasis: "uneven" as const } : {}),
191
282
  lapse,
192
- payoutRatio: quoteFields?.payoutRatio ?? null,
193
- growth1Y,
194
- growth3Y,
283
+ payoutRatio: payoutNotMeaningful ? null : quoteFields?.payoutRatio ?? null,
284
+ payoutNotMeaningful,
285
+ trailingPayments,
286
+ growth1Y: growth1Y?.growth ?? null,
287
+ growth3Y: growth3Y?.growth ?? null,
288
+ ...(growth1Y?.shortfall ? { growth1YPayments: growth1Y.shortfall } : {}),
289
+ ...(growth3Y?.shortfall ? { growth3YPayments: growth3Y.shortfall } : {}),
195
290
  paymentFrequency: options.historyAvailable !== false ? inferFrequency(eligible, now) : null,
196
291
  lastExDividendDate,
197
292
  nextExDividendDate,
@@ -207,14 +302,27 @@ export function repriceDividendMetrics(metrics: DividendMetrics, price: number |
207
302
  };
208
303
  }
209
304
 
210
- function computeGrowth(payments: DividendPayment[], years: number, now: Date): number | null {
305
+ /**
306
+ * Cash growth between the trailing year and the one `years` before it, only
307
+ * when each holds every payment the cadence pays: a year with a hole in its
308
+ * history (AZN's missing August interim) measures the hole, not a cut, so it
309
+ * has no growth and says how many payments it holds. A year with none is a
310
+ * suspension, which still reads as a full decline.
311
+ */
312
+ function computeGrowth(payments: DividendPayment[], years: number, now: Date): { growth: number | null; shortfall?: GrowthPayments } | null {
211
313
  const priorEnd = calendarMonthsBefore(now, 12 * years);
212
314
  const priorStart = calendarMonthsBefore(priorEnd, 12);
213
315
  // A new fund's partial first year is not a full-year growth baseline.
214
316
  if (!payments.some((payment) => payment.exDate <= priorStart)) return null;
317
+ for (const end of [now, priorEnd]) {
318
+ const short = trailingPaymentShortfall(payments, end);
319
+ if (short && short.counted > 0) {
320
+ return { growth: null, shortfall: { ...short, ...end === now ? {} : { yearTo: end.toISOString().slice(0, 10) } } };
321
+ }
322
+ }
215
323
  const recent = trailingCashAt(payments, now);
216
324
  const prior = trailingCashAt(payments, priorEnd);
217
- return prior > 0 ? Math.pow(recent / prior, 1 / years) - 1 : null;
325
+ return { growth: prior > 0 ? Math.pow(recent / prior, 1 / years) - 1 : null };
218
326
  }
219
327
 
220
328
  function inferFrequency(payments: DividendPayment[], now: Date): DividendMetrics["paymentFrequency"] {
@@ -4,12 +4,12 @@ import type {
4
4
  HeadlessPaneDefinition,
5
5
  HeadlessPaneLoadArgs,
6
6
  } from "../../../types/plugin";
7
- import { fetchDividendData, type DividendData } from "./client";
7
+ import { fetchDividendData, statedForwardRate, type DividendData } from "./client";
8
8
  import { dividendReferencePrice } from "../../../utils/trailing-dividend";
9
- import type { DividendPayment } from "./types";
10
- import { formatDividendYield, toDividendRows } from "./view";
9
+ import type { DividendPayment, GrowthPayments } from "./types";
10
+ import { formatDividendYield, growthPaymentsText, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText } from "./view";
11
11
  import { formatDistributionAmount, formatPercent } from "../../../utils/format";
12
- import { forwardDividendBasis, noCurrentDividend, TRAILING_DIVIDEND_BASIS } from "../../../utils/forward-dividend";
12
+ import { forwardDividendBasis, noCurrentDividend, TRAILING_DIVIDEND_BASIS, UNEVEN_REGULAR_PAYMENTS } from "../../../utils/forward-dividend";
13
13
  import { currencyPriceMinDigits, formatMarketPriceWithCurrency } from "../../../market-data/market/format";
14
14
  import type { Quote } from "../../../types/financials";
15
15
  import { regularSessionValuationPrice } from "../../../market-data/quotes/regular-session-valuation";
@@ -43,6 +43,11 @@ const PAYMENT_COLUMNS = [
43
43
  { key: "type", header: "Type" },
44
44
  ];
45
45
 
46
+ /** After the amount, for a company declaring in another currency than the listing pays: what it declared. */
47
+ const DECLARED_COLUMN = { key: "declared", header: "Declared", align: "right" as const,
48
+ format: (value: unknown, row: Record<string, unknown>) => typeof value === "number" && typeof row.declaredCurrency === "string"
49
+ ? formatDistributionAmount(value, row.declaredCurrency) : "-" };
50
+
46
51
  export interface DividendYieldHeadlessDependencies {
47
52
  loadData(symbol: string, context: HeadlessPaneContext): Promise<DividendData>;
48
53
  /** A US-listed fund's bond facts (SEC yield, yield to worst, duration); null for any other listing. */
@@ -59,10 +64,12 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
59
64
  let currentPrice: number | null = null;
60
65
  let currentPriceCurrency: string | undefined;
61
66
  let referenceQuote: Quote | undefined;
67
+ // `ULVR:LSE` is ULVR on LSE, as quote and events read it: the history is asked for that listing, not for a symbol `ULVR:LSE`.
62
68
  const instrument = await context.resolveInstrument?.(symbol);
69
+ const listingSymbol = instrument?.symbol ?? symbol;
63
70
  const exchange = instrument?.exchange ?? "";
64
71
  try {
65
- const quote = await context.marketData.getQuote(symbol, exchange);
72
+ const quote = await context.marketData.getQuote(listingSymbol, exchange);
66
73
  // The regular session's price: after the close, the close rather than an after-hours print.
67
74
  currentPrice = regularSessionValuationPrice(quote);
68
75
  currentPriceCurrency = quote.currency;
@@ -71,7 +78,10 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
71
78
  logFailure("dividend-yield.headless", "Read dividend reference quote failed")(error);
72
79
  currentPrice = null;
73
80
  }
74
- const data = await fetchDividendData(symbol, currentPrice, exchange, currentPriceCurrency);
81
+ const data = await fetchDividendData(listingSymbol, currentPrice, exchange, currentPriceCurrency, {
82
+ // Read only when the summary is missing: the forward rate fundamentals state, as `fundamentals` shows it.
83
+ loadStatedForwardRate: async () => statedForwardRate(await context.marketData.getTickerFinancials(listingSymbol, exchange)),
84
+ });
75
85
  if (referenceQuote && dividendReferencePrice(currentPrice, currentPriceCurrency, data.currency ?? "") != null) {
76
86
  Object.assign(data, dividendQuotePriceMetadata(referenceQuote));
77
87
  }
@@ -80,7 +90,7 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
80
90
  async loadBond(symbol, context) {
81
91
  const instrument = await context.resolveInstrument?.(symbol);
82
92
  const exchange = instrument?.exchange ?? "";
83
- const quote = await context.marketData.getQuote(symbol, exchange)
93
+ const quote = await context.marketData.getQuote(instrument?.symbol ?? symbol, exchange)
84
94
  .catch((error) => {
85
95
  logFailure("dividend-yield.headless", "Read bond fund classification quote failed")(error);
86
96
  throw new BondClassificationError("Bond fund quote check failed");
@@ -92,6 +102,11 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
92
102
  loadTreasury: (context) => loadTreasuryTenors(context.apiClient),
93
103
  };
94
104
 
105
+ /** A growth figure, or what its short year holds: "n/a: 1 of 2 payments". */
106
+ function growthText(growth: number | null, payments: GrowthPayments | null | undefined): string {
107
+ return growth == null && payments ? growthPaymentsText(payments) : formatPercent(growth ?? undefined);
108
+ }
109
+
95
110
  function dateEntry(label: string, date: Date | null) {
96
111
  return { label, value: date, formatted: date ? date.toISOString().slice(0, 10) : "—" };
97
112
  }
@@ -129,11 +144,16 @@ export function projectDividendYieldHeadless(
129
144
  paymentDate: payment?.paymentDate?.toISOString() ?? null,
130
145
  declarationDate: payment?.declarationDate?.toISOString() ?? null,
131
146
  type: payment?.type ?? null,
147
+ ...(row.declared ? { declared: row.declared.amount, declaredCurrency: row.declared.currency } : {}),
132
148
  };
133
149
  });
150
+ const declaredColumns = rows.some((row) => row.declared)
151
+ ? [...PAYMENT_COLUMNS.slice(0, 2), DECLARED_COLUMN, ...PAYMENT_COLUMNS.slice(2)] : PAYMENT_COLUMNS;
134
152
  const metrics = data.metrics;
135
153
  const currency = data.currency ?? data.payments[0]?.currency ?? "";
136
154
  const priceStatus = dividendPriceStatus(data.price, data.priceAsOf, data.priceStale);
155
+ // A regular payer's year with fewer payments than its cadence pays: the yield says how many it counts.
156
+ const shortfall = trailingPaymentsText(metrics.trailingPayments, { cadence: true });
137
157
  const errors = [data.historyError, data.summaryError, tax?.checkError].filter((error): error is string => !!error);
138
158
 
139
159
  return {
@@ -143,18 +163,18 @@ export function projectDividendYieldHeadless(
143
163
  title: "Dividend metrics",
144
164
  entries: [
145
165
  { label: "Price", value: data.price, ...(data.price != null && currency ? { formatted: formatMarketPriceWithCurrency(data.price, currency, { minimumFractionDigits: currencyPriceMinDigits(currency) }) } : {}) },
146
- ...(data.stale ? [{ label: "History status", value: "Stale cash history; recent distributions may be missing." }] : []),
166
+ ...(data.stale ? [{ label: "History status", value: staleHistoryText(data.fetchedAt) }] : []),
147
167
  ...(priceStatus ? [{ label: "Price status", value: priceStatus === "stale"
148
168
  ? "Stale reference price; cash yield may be out of date."
149
169
  : "Reference price time unavailable; cash yield may be out of date." }] : []),
150
170
  { label: bond ? `Trailing distribution yield (${NOT_SEC_YIELD})` : "Trailing yield", value: roundedRatio(metrics.trailingYield),
151
171
  formatted: metrics.trailingYield == null ? formatDividendYield(null)
152
- : `${formatDividendYield(metrics.trailingYield)} (${TRAILING_DIVIDEND_BASIS})` },
172
+ : `${formatDividendYield(metrics.trailingYield)} (${TRAILING_DIVIDEND_BASIS}${shortfall ? `; counts ${shortfall}` : ""})` },
153
173
  { label: bond ? `Forward distribution yield (${NOT_SEC_YIELD})` : "Forward yield", value: roundedRatio(metrics.forwardYield),
154
174
  formatted: metrics.lapse ? noCurrentDividend(metrics.lapse)
155
175
  : metrics.forwardYield == null ? formatDividendYield(null)
156
- : `${formatDividendYield(metrics.forwardYield)} (${forwardDividendBasis(metrics.forwardRateAsOf,
157
- { payDate: forwardPayDate(metrics), declared: data.declared, currency })})` },
176
+ : `${formatDividendYield(metrics.forwardYield)} (${metrics.forwardBasis === "uneven" ? UNEVEN_REGULAR_PAYMENTS
177
+ : forwardDividendBasis(metrics.forwardRateAsOf, { payDate: forwardPayDate(metrics), declared: data.declared, currency })})` },
158
178
  ...(bond ? bondFigures(bond).map((figure) => ({
159
179
  label: figure.key === "secYield" ? "30-day SEC yield" : figure.label,
160
180
  value: figure.value,
@@ -162,9 +182,9 @@ export function projectDividendYieldHeadless(
162
182
  })) : []),
163
183
  { label: "Trailing rate", value: metrics.trailingRate, formatted: currency ? formatDistributionAmount(metrics.trailingRate ?? undefined, currency) : "—" },
164
184
  { label: "Forward rate", value: metrics.forwardRate, formatted: currency ? formatDistributionAmount(metrics.forwardRate ?? undefined, currency) : "—" },
165
- { label: "Earnings Payout", value: roundedRatio(metrics.payoutRatio), formatted: formatDividendYield(metrics.payoutRatio) },
166
- { label: "1Y Cash Growth", value: roundedRatio(metrics.growth1Y), formatted: formatPercent(metrics.growth1Y ?? undefined) },
167
- { label: "3Y Cash CAGR", value: roundedRatio(metrics.growth3Y), formatted: formatPercent(metrics.growth3Y ?? undefined) },
185
+ { label: "Earnings Payout", value: roundedRatio(metrics.payoutRatio), formatted: metrics.payoutNotMeaningful ? PAYOUT_NOT_MEANINGFUL : formatDividendYield(metrics.payoutRatio) },
186
+ { label: "1Y Cash Growth", value: roundedRatio(metrics.growth1Y), formatted: growthText(metrics.growth1Y, metrics.growth1YPayments) },
187
+ { label: "3Y Cash CAGR", value: roundedRatio(metrics.growth3Y), formatted: growthText(metrics.growth3Y, metrics.growth3YPayments) },
168
188
  { label: "Frequency", value: metrics.paymentFrequency },
169
189
  dateEntry("Last ex-dividend", metrics.lastExDividendDate),
170
190
  dateEntry("Next ex-dividend", metrics.nextExDividendDate),
@@ -179,7 +199,7 @@ export function projectDividendYieldHeadless(
179
199
  }] : []),
180
200
  {
181
201
  title: "Dividend history",
182
- columns: PAYMENT_COLUMNS,
202
+ columns: declaredColumns,
183
203
  rows,
184
204
  // Each amount is in its own row's currency.
185
205
  rowUnitKey: "currency",
@@ -201,10 +221,18 @@ export function projectDividendYieldHeadless(
201
221
  priceStale: data.priceStale ?? null,
202
222
  limitations: data.notes ?? [],
203
223
  yieldMethod: "Cash distributions with ex-dates in the preceding 12 months, one payment per period for a regular cadence, divided by the reference share price; excludes reinvestment and is not SEC yield or total return.",
224
+ // Set when a regular payer's year holds fewer payments than its cadence pays (a gap in its history): the yield counts only these.
225
+ trailingPayments: metrics.trailingPayments ?? null,
226
+ // Set when a year a growth figure compares holds fewer payments than the cadence pays: that figure is null.
227
+ growth1YPayments: metrics.growth1YPayments ?? null,
228
+ growth3YPayments: metrics.growth3YPayments ?? null,
204
229
  forwardRateAsOf: metrics.forwardRateAsOf,
230
+ // "uneven": the year's regular payments differ too widely for one to be annualized, so the forward rate is their total.
231
+ forwardBasis: metrics.forwardBasis ?? null,
205
232
  // Declared and unpaid, apart from the paid history the table and every figure read.
206
233
  declaredDividends: data.declared ?? [],
207
- forwardRateMethod: "Indicated annual cash rate, the one fundamentals use: the latest regular distribution annualized, or once the next one is declared that one, dated by its ex-date (forwardRateAsOf; a date still ahead is the declared distribution's). Only when its currency units are comparable with the share price.",
234
+ forwardRateMethod: "Indicated annual cash rate, the one fundamentals use: the latest regular distribution annualized, or once the next one is declared that one, dated by its ex-date (forwardRateAsOf; a date still ahead is the declared distribution's). When the trailing year's regular payments differ widely (the largest over 1.5 times the smallest; forwardBasis: uneven), their total instead, dated by the latest. Only when its currency units are comparable with the share price.",
235
+ payoutMethod: "Trailing twelve-month dividends over trailing twelve-month diluted EPS, as the source states it; N/M (not meaningful) when that EPS is zero or negative.",
208
236
  paymentFrequencyMethod: "Cadence inferred from ex-dates in the last two years, unavailable when no cash was reported in the last year; not an announced payment schedule.",
209
237
  // A bond fund's sponsor figures, each dated; its distribution yields above are not its SEC yield.
210
238
  ...(bond ? { bondFund: { ...bond, class: bondClassText(bond) } } : {}),
@@ -2,7 +2,7 @@ import type { DataTableColumn } from "../../../components";
2
2
  import { compareSortValues, type SortDirection } from "../../../utils/sort-values";
3
3
  import type { DividendRow } from "./view";
4
4
 
5
- export type DividendColumnId = "exDate" | "amount" | "currency";
5
+ export type DividendColumnId = "exDate" | "amount" | "declared" | "currency";
6
6
  export type DividendColumn = DataTableColumn & { id: DividendColumnId };
7
7
 
8
8
  export interface DividendSortPreference {
@@ -16,15 +16,17 @@ export const DEFAULT_SORT_PREFERENCE: DividendSortPreference = {
16
16
  };
17
17
 
18
18
  /**
19
- * The amount sits beside its ex-date rather than across the pane. The currency
20
- * column shows only when the payments mix currencies; otherwise the amount's
21
- * own symbol says it.
19
+ * The amount sits beside its ex-date rather than across the pane. The declared
20
+ * amount follows it only for a company declaring in another currency than the
21
+ * listing pays (BP's dollars beside its pence). The currency column shows only
22
+ * when the payments mix currencies; otherwise the amount's own symbol says it.
22
23
  */
23
- export function buildDividendColumns(rows: readonly Pick<DividendRow, "currency">[] = []): DividendColumn[] {
24
+ export function buildDividendColumns(rows: readonly Pick<DividendRow, "currency" | "declared">[] = []): DividendColumn[] {
24
25
  const mixed = new Set(rows.map((row) => row.currency)).size > 1;
25
26
  return [
26
27
  { id: "exDate", label: "EX-DATE", width: 10, align: "left" },
27
28
  { id: "amount", label: "AMOUNT", width: 12, align: "right" },
29
+ ...(rows.some((row) => row.declared) ? [{ id: "declared" as const, label: "DECLARED", width: 10, align: "right" as const }] : []),
28
30
  ...(mixed ? [{ id: "currency" as const, label: "CCY", width: 6, align: "left" as const }] : []),
29
31
  ];
30
32
  }
@@ -33,6 +35,7 @@ function getSortValue(columnId: DividendColumnId, row: DividendRow): string | nu
33
35
  switch (columnId) {
34
36
  case "exDate": return row.exDate;
35
37
  case "amount": return row.amount;
38
+ case "declared": return row.declared?.amount ?? null;
36
39
  case "currency": return row.currency;
37
40
  }
38
41
  }
@@ -22,15 +22,16 @@ import { colors, priceColor } from "../../../theme/colors";
22
22
  import { TextAttributes, useUiCapabilities } from "../../../ui";
23
23
  import { displayWidth, formatCurrency, formatDistributionAmount, formatPercentRaw } from "../../../utils/format";
24
24
  import { resolveCurrencyUnit } from "../../../utils/currency-units";
25
- import { forwardDividendNote, lastPaidNote, NO_CURRENT_DIVIDEND } from "../../../utils/forward-dividend";
25
+ import { parsePublicTickerKey } from "../../../utils/exchanges";
26
+ import { forwardDividendNote, lastPaidNote, NO_CURRENT_DIVIDEND, UNEVEN_REGULAR_PAYMENTS } from "../../../utils/forward-dividend";
26
27
  import { nextHeaderSort } from "../../../utils/sort-values";
27
28
  import { handleRefreshKey, loadingErrorFooterInfo } from "../../../components/data-table/table-pane";
28
29
  import { SignInWall } from "../cloud/auth-actions";
29
30
  import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
30
- import { fetchDividendData, repriceDividendMetrics, type DividendData } from "./client";
31
+ import { fetchDividendData, repriceDividendMetrics, statedForwardRate, type DividendData } from "./client";
31
32
  import { dividendReferencePrice } from "../../../utils/trailing-dividend";
32
33
  import { useTickerQuoteStream } from "../../../state/hooks/live-ticker-financials";
33
- import { buildTrailingCashChartPoints, formatDividendYield, toDividendRows, type DividendRow } from "./view";
34
+ import { buildTrailingCashChartPoints, formatDividendYield, growthPaymentsDetail, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText, type DividendRow } from "./view";
34
35
  import {
35
36
  DEFAULT_SORT_PREFERENCE,
36
37
  buildDividendColumns,
@@ -39,7 +40,7 @@ import {
39
40
  type DividendColumnId,
40
41
  type DividendSortPreference,
41
42
  } from "./model";
42
- import type { DividendMetrics } from "./types";
43
+ import type { DividendMetrics, GrowthPayments } from "./types";
43
44
  import { dividendPriceStatus, dividendPriceTime, dividendQuotePriceMetadata } from "./reference-price";
44
45
  import type { FundBondFacts } from "../../../api-client/fund-facts";
45
46
  import { resolveTickerInstrumentKind } from "../../../tickers/instrument-kind";
@@ -57,6 +58,14 @@ function formatGrowth(value: number | null): string {
57
58
  return formatPercentRaw(value * 100);
58
59
  }
59
60
 
61
+ /** A growth figure, or "n/a" with how many payments its short year holds. */
62
+ function growthItem(id: string, label: string, growth: number | null, payments: GrowthPayments | null | undefined): StatItem {
63
+ if (growth == null && payments) {
64
+ return { id, label, value: "n/a", detail: growthPaymentsDetail(payments) };
65
+ }
66
+ return { id, label, value: formatGrowth(growth), color: priceColor(growth ?? 0) };
67
+ }
68
+
60
69
  function formatDate(date: Date | null): string {
61
70
  if (!date) return "—";
62
71
  return date.toISOString().slice(0, 10);
@@ -86,11 +95,13 @@ function buildMetricItems(metrics: DividendMetrics, currency: string, hasHistory
86
95
  ? [{ id: "next-ex", label: "Next ex", value: formatDate(metrics.nextExDividendDate) }]
87
96
  : hasHistory ? [] : [{ id: "last-ex", label: "Last ex", value: formatDate(metrics.lastExDividendDate) }];
88
97
  const dated = metrics.lapse ? lastPaidNote(metrics.lapse)
89
- : metrics.forwardYield != null ? forwardDividendNote(metrics.forwardRateAsOf, { declared, currency }) : null;
98
+ : metrics.forwardYield == null ? null
99
+ : metrics.forwardBasis === "uneven" ? UNEVEN_REGULAR_PAYMENTS : forwardDividendNote(metrics.forwardRateAsOf, { declared, currency });
90
100
  // "not SEC" in the grid: the SEC yield is the next cell, and the long form costs the header a column.
91
101
  const notSec = (note: string | null) => bond ? [note, "not SEC"].filter(Boolean).join(", ") : note;
92
102
  const forwardNote = notSec(dated);
93
- const trailingNote = notSec(null);
103
+ // A regular payer's year with fewer payments than its cadence pays says how many the yield counts.
104
+ const trailingNote = notSec(trailingPaymentsText(metrics.trailingPayments));
94
105
  const ttmLabel = bond ? (short ? "TTM dist" : "TTM dist yld") : short ? "TTM yld" : "TTM yield";
95
106
  const fwdLabel = bond ? (short ? "Fwd dist" : "Fwd dist yld") : short ? "Fwd yld" : "Fwd yield";
96
107
  const sec = bond ? bondFigures(bond).find((figure) => figure.key === "secYield") : undefined;
@@ -107,9 +118,9 @@ function buildMetricItems(metrics: DividendMetrics, currency: string, hasHistory
107
118
  ...(sec ? [{ id: "sec-yield", label: short ? "SEC yld" : "SEC yield", value: sec.text, ...(sec.date ? { detail: `as of ${sec.date}` } : {}) }] : []),
108
119
  { id: "ttm-rate", label: short ? "TTM/sh" : "TTM/share", value: formatRate(metrics.trailingRate, currency) },
109
120
  { id: "forward-rate", label: short ? "Fwd/sh" : "Fwd/share", value: formatRate(metrics.forwardRate, currency) },
110
- { id: "growth-1y", label: short ? "1Y chg" : "1Y growth", value: formatGrowth(metrics.growth1Y), color: priceColor(metrics.growth1Y ?? 0) },
111
- { id: "cagr-3y", label: "3Y CAGR", value: formatGrowth(metrics.growth3Y), color: priceColor(metrics.growth3Y ?? 0) },
112
- { id: "payout", label: "Payout", value: metrics.payoutRatio != null ? `${(metrics.payoutRatio * 100).toFixed(1)}%` : "—" },
121
+ growthItem("growth-1y", short ? "1Y chg" : "1Y growth", metrics.growth1Y, metrics.growth1YPayments),
122
+ growthItem("cagr-3y", "3Y CAGR", metrics.growth3Y, metrics.growth3YPayments),
123
+ { id: "payout", label: "Payout", value: metrics.payoutNotMeaningful ? PAYOUT_NOT_MEANINGFUL : metrics.payoutRatio != null ? `${(metrics.payoutRatio * 100).toFixed(1)}%` : "—" },
113
124
  { id: "cadence", label: "Cadence", value: formatFrequency(metrics.paymentFrequency) },
114
125
  ...exDate,
115
126
  { id: "next-pay", label: "Next pay", value: formatDate(metrics.nextPayDate) },
@@ -143,6 +154,8 @@ function renderCell(
143
154
  color: colors.textBright,
144
155
  attributes: TextAttributes.BOLD,
145
156
  };
157
+ case "declared":
158
+ return { text: row.declared ? formatDistributionAmount(row.declared.amount, row.declared.currency) : "", color: colors.textDim };
146
159
  case "currency":
147
160
  return { text: row.currency, color: colors.textDim };
148
161
  }
@@ -162,7 +175,11 @@ export function DividendYieldPane({ focused, width, height, loadData = fetchDivi
162
175
  const fundSymbol = ticker && resolveTickerInstrumentKind(ticker, financials) === "fund" && mayBeUsEquityOrFundTicker(ticker) ? ticker.metadata.ticker : null;
163
176
  const bond = useFundFacts(fundSymbol).data?.payload?.bond ?? null;
164
177
  const quoteCurrency = financials?.quote?.currency;
165
- const exchange = ticker?.metadata.exchange ?? "";
178
+ // The listing's own symbol beside its venue: a `ULVR:LSE` key is ULVR on LSE, never a symbol of that name.
179
+ const listingKey = ticker?.metadata.ticker ?? symbol;
180
+ const parsedListing = listingKey ? parsePublicTickerKey(listingKey) : null;
181
+ const listingSymbol = parsedListing?.symbol ?? null;
182
+ const exchange = parsedListing?.exchange ?? ticker?.metadata.exchange ?? "";
166
183
  // The regular session's price: after the close, the close rather than an after-hours print.
167
184
  const quotePrice = regularSessionValuationPrice(financials?.quote);
168
185
 
@@ -173,9 +190,17 @@ export function DividendYieldPane({ focused, width, height, loadData = fetchDivi
173
190
  // quote is read through a ref instead of being an effect dependency.
174
191
  const quoteRef = useRef({ price: quotePrice, currency: quoteCurrency });
175
192
  quoteRef.current = { price: quotePrice, currency: quoteCurrency };
193
+ // The forward rate the ticker's fundamentals state, read only when the summary is missing. Fundamentals can
194
+ // arrive after the read, so a new stated rate reads again; a price tick does not.
195
+ const stated = statedForwardRate(financials);
196
+ const statedKey = stated ? `${stated.rate}|${stated.exDate}|${stated.currency}` : "";
197
+ const statedRef = useRef(stated);
198
+ statedRef.current = stated;
176
199
 
177
- const request = useCallback(() => loadData(symbol!, quoteRef.current.price, exchange, quoteRef.current.currency), [exchange, loadData, symbol, cloudSession.requestKey]);
178
- const { data, loading, error, updatedAt, reload: refresh } = useAsyncResource(symbol ? request : null);
200
+ const request = useCallback(() => loadData(listingSymbol!, quoteRef.current.price, exchange, quoteRef.current.currency, {
201
+ loadStatedForwardRate: () => statedRef.current,
202
+ }), [exchange, loadData, listingSymbol, cloudSession.requestKey, statedKey]);
203
+ const { data, loading, error, updatedAt, reload: refresh } = useAsyncResource(listingSymbol ? request : null);
179
204
  // An integrity response may contain no usable rows. Retain only historical
180
205
  // rows from this resource; its new unavailable metrics remain authoritative.
181
206
  const lastHistory = useRef<{ request: typeof request; data: DividendData } | null>(null);
@@ -211,7 +236,7 @@ export function DividendYieldPane({ focused, width, height, loadData = fetchDivi
211
236
  }, [authWall, currentPrice, historyData?.fetchedAt, data?.historyError, data?.summaryError, error, loading, priceAsOf, priceStatus, width]);
212
237
 
213
238
  const sourceWarnings = [
214
- ...(data?.stale ? ["Stale cash history; recent distributions may be missing."] : []),
239
+ ...(data?.stale ? [staleHistoryText(data.fetchedAt)] : []),
215
240
  ];
216
241
  usePaneNoticeFooter({
217
242
  registrationId: "dividend-yield-notices",