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
@@ -446,6 +446,7 @@ export function CorporateActionsView({
446
446
  const financialCurrency = tickerFinancials?.financialCurrency;
447
447
  const trailingEps = tickerFinancials?.fundamentals?.eps;
448
448
  const trailingEpsCurrency = tickerFinancials?.fundamentals?.epsCurrency;
449
+ const trailingEpsSource = tickerFinancials?.fundamentals?.trailingEpsSource;
449
450
  const fundamentalsCurrency = tickerFinancials?.fundamentals?.financialCurrency;
450
451
  const marketCapCurrency = tickerFinancials?.fundamentals?.marketCapCurrency;
451
452
  const quoteCurrency = tickerFinancials?.quote?.currency;
@@ -453,10 +454,10 @@ export function CorporateActionsView({
453
454
  () => (quarterlyStatements || financialCurrency || trailingEps != null ? {
454
455
  quarterlyStatements: quarterlyStatements ?? [],
455
456
  financialCurrency,
456
- fundamentals: { eps: trailingEps, epsCurrency: trailingEpsCurrency, financialCurrency: fundamentalsCurrency, marketCapCurrency },
457
+ fundamentals: { eps: trailingEps, epsCurrency: trailingEpsCurrency, financialCurrency: fundamentalsCurrency, marketCapCurrency, trailingEpsSource },
457
458
  quote: quoteCurrency ? { currency: quoteCurrency } : null,
458
459
  } : null),
459
- [financialCurrency, fundamentalsCurrency, marketCapCurrency, quarterlyStatements, quoteCurrency, trailingEps, trailingEpsCurrency],
460
+ [financialCurrency, fundamentalsCurrency, marketCapCurrency, quarterlyStatements, quoteCurrency, trailingEps, trailingEpsCurrency, trailingEpsSource],
460
461
  );
461
462
  const actionsLoader = useCallback((nextSymbol: string, nextExchange: string, forceRefresh: boolean) => {
462
463
  if (!dataProvider?.getCorporateActions) throw new Error("Corporate actions source unavailable");
@@ -15,7 +15,7 @@ import {
15
15
  } from "../../../utils/format";
16
16
  import { computeTTM } from "../ticker-detail/financials/aggregation";
17
17
  import { declaredDividends } from "../../../utils/declared-dividends";
18
- import { fundamentalsEpsCurrency } from "../../../utils/fundamentals";
18
+ import { fundamentalsEpsCurrency, SERVED_TRAILING_EPS } from "../../../utils/fundamentals";
19
19
  import {
20
20
  earningsDateStatus,
21
21
  earningsDetail,
@@ -175,11 +175,11 @@ function estimateValue(pair: EstimatePair): string {
175
175
  }
176
176
 
177
177
  /**
178
- * What the TTM row reads: the statements for its four quarters and revenue, and the trailing EPS Gloom Cloud serves
179
- * with the fundamentals, the figure `ticker` prints as EPS (TTM).
178
+ * What the TTM row reads: the statements for its four quarters and revenue, and the trailing EPS of the fundamentals,
179
+ * the figure `ticker` prints as EPS (TTM) (the filed quarters' when they give one, see time-series/filed-trailing-eps).
180
180
  */
181
181
  export type EventFinancials = Pick<TickerFinancials, "quarterlyStatements" | "financialCurrency"> & {
182
- fundamentals?: Pick<NonNullable<TickerFinancials["fundamentals"]>, "eps" | "epsCurrency" | "financialCurrency" | "marketCapCurrency"> | null;
182
+ fundamentals?: Pick<NonNullable<TickerFinancials["fundamentals"]>, "eps" | "epsCurrency" | "financialCurrency" | "marketCapCurrency" | "trailingEpsSource"> | null;
183
183
  /** The listing's currency, which says whether an unlabelled EPS can be in the statements' (fundamentalsCurrency). */
184
184
  quote?: Pick<NonNullable<TickerFinancials["quote"]>, "currency"> | null;
185
185
  };
@@ -204,9 +204,10 @@ function adjustedQuarterSum(
204
204
  }
205
205
 
206
206
  /**
207
- * The TTM row's EPS is the GAAP diluted trailing figure, the one `ticker` prints as EPS (TTM). Without it, the four
208
- * statement quarters' diluted EPS summed. The reported rows above it are often adjusted, so they are never added to
209
- * it: only when all four are adjusted does the detail also give their sum.
207
+ * The TTM row's EPS is the GAAP diluted trailing figure `ticker` prints as EPS (TTM): the last four filed quarters',
208
+ * or Gloom Cloud's trailing figure, named so, when they give none. Without either, the four statement quarters'
209
+ * diluted EPS summed. The reported rows above it are often adjusted, so they are never added to it: only when all
210
+ * four are adjusted does the detail also give their sum.
210
211
  */
211
212
  function ttmRow(
212
213
  quarterlyStatements: readonly FinancialStatement[],
@@ -226,7 +227,8 @@ function ttmRow(
226
227
  date: latest?.date ?? "",
227
228
  status: "TTM",
228
229
  period: "4 qtrs",
229
- detail: [eps == null ? "statement sum" : served ? "GAAP diluted" : "GAAP diluted (statements)",
230
+ detail: [eps == null ? "statement sum" : !served ? "GAAP diluted (statements)"
231
+ : financials?.fundamentals?.trailingEpsSource === "served" ? `GAAP diluted (${SERVED_TRAILING_EPS})` : "GAAP diluted",
230
232
  adjusted == null ? null : `adj sum ${formatNumber(adjusted, 2)}`].filter(Boolean).join(", "),
231
233
  epsCurrency: served ? fundamentalsEpsCurrency(financials) : ttm.currency ?? financials?.financialCurrency,
232
234
  revenueCurrency: ttm.currency ?? financials?.financialCurrency,
@@ -83,7 +83,7 @@ function projectEarningsEstimatesHeadless(
83
83
  returned: rows.length,
84
84
  truncated: rows.length < matching.length,
85
85
  coverageNote: notice?.text,
86
- epsBasis: `${EPS_BASIS_NOTE} The TTM row's EPS is the GAAP diluted trailing figure ticker shows as EPS (TTM), or the four statement quarters' diluted EPS when none is served; reported quarters of different bases are never summed, and the row adds an adjusted sum only when all four quarters are adjusted.`,
86
+ epsBasis: `${EPS_BASIS_NOTE} The TTM row's EPS is the GAAP diluted trailing figure ticker shows as EPS (TTM): the last four filed quarters', or Gloom Cloud's TTM (named so) when they give none, or the four statement quarters' diluted EPS when neither is there; reported quarters of different bases are never summed, and the row adds an adjusted sum only when all four quarters are adjusted.`,
87
87
  dateNote: "Fiscal period dates are not announcement dates. Revenue is attached only to an identified statement period.",
88
88
  unmatchedReportedPeriods,
89
89
  },
@@ -96,7 +96,10 @@ const VALUATION_COLUMNS: HeadlessPaneColumn[] = [
96
96
  ...([["fcfYield", "FCF Yield"], ["revenueGrowth", REVENUE_GROWTH_HEADER], ["operatingMargin", "TTM Op Margin"]] as const).map(([key, header]) => ({
97
97
  key, header, align: "right" as const,
98
98
  format: (value: unknown, row: Record<string, unknown>) => value == null ? gapText(row, key)
99
- : key === "revenueGrowth" ? formatPercent(Number(value)) : formatLevelPercent(Number(value)),
99
+ : key === "revenueGrowth" ? formatPercent(Number(value))
100
+ // A margin of the latest fiscal year, where no trailing quarters were served, says which year.
101
+ : key === "operatingMargin" && typeof row.operatingMarginPeriod === "string"
102
+ ? `${formatLevelPercent(Number(value))} ${row.operatingMarginPeriod}` : formatLevelPercent(Number(value)),
100
103
  })),
101
104
  ];
102
105
 
@@ -226,6 +229,7 @@ export const relativeValuationHeadless: HeadlessPaneDefinition<"rows"> = {
226
229
  ...rows.flatMap((row) => row.evSalesUnavailable ? [`${row.symbol}: ${row.evSalesUnavailable}`] : []),
227
230
  ...rows.flatMap((row) => row.evSalesNote ? [`${row.symbol}: ${row.evSalesNote}`] : []),
228
231
  ...rows.flatMap((row) => row.marketCapNote ? [`${row.symbol}: ${row.marketCapNote}`] : []),
232
+ ...rows.flatMap((row) => row.marketCapWithheld ? [`${row.symbol}: ${row.marketCapWithheld}`] : []),
229
233
  ],
230
234
  multipleBasis: "Comparable P/E fields require a positive finite multiple over positive EPS, and evSales an enterprise value above zero; notMeaningful says why one is null, and reportedMultiples preserves the finite provider P/E values.",
231
235
  marketCapBasis: "Market caps retain their own currency and source. Fundamentals retrieval time is not a valuation date; quote timestamps do not date fallback caps.",
@@ -1,9 +1,10 @@
1
1
  import { formatCurrency } from "../../../utils/format";
2
2
  import { comparablePriceEarnings, notMeaningfulMultiples } from "../../../utils/price-earnings";
3
- import { marketCapBasisNote, viewMarketCapitalization } from "../../../utils/market-capitalization";
3
+ import { marketCapBasisNote, viewMarketCapitalization, withheldMarketCap } from "../../../utils/market-capitalization";
4
4
  import { enterpriseValueNotMeaningful, enterpriseValueReconciliationNote, enterpriseValueWithdrawn, reportedEnterpriseValue } from "../../../utils/fundamentals";
5
5
  import { getExtendedSessionDisplay, getQuoteSessionFields } from "../../../market-data/market/status";
6
6
  import { atRegularSessionPrice, movePriceDerivedFigures, regularSessionValuationPrice } from "../../../market-data/quotes/regular-session-valuation";
7
+ import { fiscalPeriodLabel, fiscalYearEndMonth } from "../../../utils/fiscal-period";
7
8
  import { latestFinancialPeriod, latestReportedPeriod } from "../../../utils/latest-financial-period";
8
9
  import type { Quote, TickerFinancials } from "../../../types/financials";
9
10
 
@@ -50,6 +51,25 @@ function fcfYieldGap(fundamentals: TickerFinancials["fundamentals"], capCurrency
50
51
  }
51
52
  export const RELATIVE_VALUATION_STALE_FUNDAMENTALS_NOTICE = "Fundamentals stale: retained values may be out of date";
52
53
 
54
+ /**
55
+ * The operating margin of the latest fiscal year, for a row whose trailing
56
+ * twelve months are not whole (a quarter the source has not carried yet):
57
+ * that year's operating income over its revenue, named for the year ("FY25")
58
+ * so a cell never passes it off as trailing. Null when the latest year lacks
59
+ * either figure; an older year is never stood in for it.
60
+ */
61
+ function fiscalYearOperatingMargin(annualStatements: TickerFinancials["annualStatements"] | undefined) {
62
+ const latest = latestFinancialPeriod(annualStatements, (row) => row.date);
63
+ if (!latest || !finiteNumber(latest.operatingIncome) || !finiteNumber(latest.totalRevenue) || latest.totalRevenue <= 0) return null;
64
+ const year = /^FY\d{2}(\d{2})$/.exec(fiscalPeriodLabel(latest.date, "annual", fiscalYearEndMonth(annualStatements ?? [])))?.[1];
65
+ return {
66
+ value: latest.operatingIncome / latest.totalRevenue,
67
+ // The fiscal year's own name; the calendar year of its end where the year cannot be named.
68
+ label: `FY${year ?? latest.date.slice(2, 4)}`,
69
+ periodEnd: latest.date.slice(0, 10),
70
+ };
71
+ }
72
+
53
73
  /**
54
74
  * A peer's row. Last, its change and the extended print are the quote's
55
75
  * sessions as `quote` reads them (getQuoteSessionFields): after the close,
@@ -97,14 +117,17 @@ export function relativeValuationValues(snapshot: TickerFinancials | null) {
97
117
  && fundamentals.freeCashFlow != null && Number.isFinite(fundamentals.freeCashFlow) && capitalization.value > 0
98
118
  ? fundamentals.freeCashFlow / capitalization.value : null;
99
119
  const revenueGrowth = fundamentals?.revenueGrowth ?? fundamentals?.lastQuarterGrowth ?? null;
100
- const operatingMargin = fundamentals?.operatingMargin ?? null;
120
+ const trailingOperatingMargin = fundamentals?.operatingMargin ?? null;
121
+ const fiscalYearMargin = trailingOperatingMargin == null ? fiscalYearOperatingMargin(financials?.annualStatements) : null;
122
+ const operatingMargin = trailingOperatingMargin ?? fiscalYearMargin?.value ?? null;
101
123
  const trailingPE = trailingNotMeaningful ? null : comparablePriceEarnings(reportedMultiples.trailingPE);
102
124
  const forwardPE = forwardNotMeaningful ? null : comparablePriceEarnings(reportedMultiples.forwardPE);
103
125
  // Every figure a row lacks says why, so an export's blank cell is never silent: N/M multiples say so themselves.
104
126
  const gaps: Partial<Record<RelativeValuationGapKey, string>> = {};
105
127
  if (session.price == null) gaps.price = quoteStale ? QUOTE_STALE : NOT_REPORTED;
106
128
  if (session.changePercent == null) gaps.changePercent = quoteStale ? QUOTE_STALE : NOT_REPORTED;
107
- if (capitalization == null) gaps.marketCap = quoteStale && !fundamentals?.marketCap ? QUOTE_STALE : NOT_REPORTED;
129
+ const withheld = withheldMarketCap(fundamentals);
130
+ if (capitalization == null) gaps.marketCap = withheld?.gap ?? (quoteStale && !fundamentals?.marketCap ? QUOTE_STALE : NOT_REPORTED);
108
131
  if (trailingPE == null && !trailingNotMeaningful && reportedMultiples.trailingPE == null) gaps.trailingPE = NOT_REPORTED;
109
132
  if (forwardPE == null && !forwardNotMeaningful && reportedMultiples.forwardPE == null) gaps.forwardPE = NOT_REPORTED;
110
133
  if (evSales == null && !evSalesNotMeaningful) gaps.evSales = evSalesGap(fundamentals, quote?.currency, evSalesWithdrawn, enterpriseValueGap);
@@ -136,6 +159,8 @@ export function relativeValuationValues(snapshot: TickerFinancials | null) {
136
159
  marketCapProvenance: capitalization?.provenance ?? null,
137
160
  /** valuation's note when the cap is the share count times the price: the provider's figure it replaced. */
138
161
  marketCapNote: marketCapBasisNote(capitalization) ?? null,
162
+ /** Why Gloom Cloud withheld the cap (a receipt priced as ordinary shares), as `valuation` and EQS say it; null otherwise. */
163
+ marketCapWithheld: withheld?.note ?? null,
139
164
  trailingPE,
140
165
  forwardPE,
141
166
  reportedMultiples,
@@ -151,6 +176,9 @@ export function relativeValuationValues(snapshot: TickerFinancials | null) {
151
176
  fcfYield,
152
177
  revenueGrowth,
153
178
  operatingMargin,
179
+ /** The fiscal year the operating margin is of ("FY25") when no trailing figure was served; null when it is trailing twelve months. */
180
+ operatingMarginPeriod: fiscalYearMargin?.label ?? null,
181
+ operatingMarginPeriodEnd: fiscalYearMargin?.periodEnd ?? null,
154
182
  /** Why each missing figure is missing, in a few words the row draws in its place. */
155
183
  gaps,
156
184
  };
@@ -19,7 +19,7 @@ import { useSampledValue } from "../../../state/hooks/live-ticker-financials";
19
19
  import type { QuoteSubscriptionTarget } from "../../../types/data-provider";
20
20
  import type { TickerFinancials } from "../../../types/financials";
21
21
  import { normalizeSymbol } from "../../../utils/exchanges";
22
- import { colors, priceColor } from "../../../theme/colors";
22
+ import { colors, priceColor, shownPriceColor } from "../../../theme/colors";
23
23
  import { extendedSessionStateLabel, type ExtendedSession } from "../../../market-data/market/status";
24
24
  import { compareSortValues, nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
25
25
  import { formatCompact, formatLevelPercent, formatNumber, formatPercent, formatPercentRaw } from "../../../utils/format";
@@ -97,7 +97,7 @@ function extendedMove(row: RelativeRow, columnId: ExtendedColumnId): number | nu
97
97
  * AFTER-HRS or OVERNIGHT column, named as BI and MOST name it, follows CHG%
98
98
  * for each session in `extendedSessions`.
99
99
  */
100
- function buildRelativeColumns(baseCurrency: string, extendedSessions: readonly ExtendedSession[]): RelativeColumn[] {
100
+ function buildRelativeColumns(baseCurrency: string, extendedSessions: readonly ExtendedSession[], marginNamesYear: boolean): RelativeColumn[] {
101
101
  const symbolWidth = 8;
102
102
  const priceWidth = 10;
103
103
  const pctWidth = 8;
@@ -117,7 +117,8 @@ function buildRelativeColumns(baseCurrency: string, extendedSessions: readonly E
117
117
  { id: "fcfYield", label: "FCF%", width: metricWidth, align: "right" },
118
118
  // Latest quarter against the year before, and a trailing-twelve-month margin.
119
119
  { id: "revenueGrowth", label: REVENUE_GROWTH_LABEL, width: metricWidth, align: "right" },
120
- { id: "operatingMargin", label: "TTM OP%", width: metricWidth, align: "right" },
120
+ // Wider while a peer's margin is of its latest fiscal year and names it ("16.83% FY25").
121
+ { id: "operatingMargin", label: "TTM OP%", width: marginNamesYear ? 12 : metricWidth, align: "right" },
121
122
  ];
122
123
  }
123
124
 
@@ -217,9 +218,10 @@ export function RelativeValuationPane({ focused, width, height }: PaneProps) {
217
218
  const fxRates = useFxRatesMap([baseCurrency, ...rows.map((row) => row.marketCapCurrency)]);
218
219
  // A pre-market or after-hours print gets its own column when a peer has one.
219
220
  const extendedSessionsKey = extendedSessionsOf(rows).join(",");
221
+ const marginNamesYear = rows.some((row) => row.operatingMarginPeriod != null);
220
222
  const columns = useMemo(
221
- () => buildRelativeColumns(baseCurrency, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : []),
222
- [baseCurrency, extendedSessionsKey],
223
+ () => buildRelativeColumns(baseCurrency, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : [], marginNamesYear),
224
+ [baseCurrency, extendedSessionsKey, marginNamesYear],
223
225
  );
224
226
 
225
227
  const comparableRows = useMemo(() => rows.map((row) => ({
@@ -295,12 +297,12 @@ export function RelativeValuationPane({ focused, width, height }: PaneProps) {
295
297
  case "price":
296
298
  return { text: formatRelativePrice(row.price, row.currency), color: colors.text };
297
299
  case "changePercent":
298
- return { text: row.changePercent != null ? formatPercentRaw(row.changePercent) : "-", color: priceColor(row.changePercent ?? 0) };
300
+ return { text: row.changePercent != null ? formatPercentRaw(row.changePercent) : "-", color: shownPriceColor(row.changePercent ?? 0, 2) };
299
301
  case "preMarket":
300
302
  case "afterHours":
301
303
  case "overnight": {
302
304
  const move = extendedMove(row, column.id);
303
- return { text: move != null ? formatPercentRaw(move) : "-", value: move, color: move == null ? colors.textDim : priceColor(move) };
305
+ return { text: move != null ? formatPercentRaw(move) : "-", value: move, color: move == null ? colors.textDim : shownPriceColor(move, 2) };
304
306
  }
305
307
  case "marketCap":
306
308
  return { text: formatCompact(row.marketCap ?? undefined, { fixedDecimals: true }), color: colors.textDim };
@@ -317,9 +319,9 @@ export function RelativeValuationPane({ focused, width, height }: PaneProps) {
317
319
  case "fcfYield":
318
320
  return { text: formatLevelPercent(row.fcfYield ?? undefined), color: priceColor(row.fcfYield ?? 0) };
319
321
  case "revenueGrowth":
320
- return { text: formatPercent(row.revenueGrowth ?? undefined), color: priceColor(row.revenueGrowth ?? 0) };
322
+ return { text: formatPercent(row.revenueGrowth ?? undefined), color: shownPriceColor((row.revenueGrowth ?? 0) * 100, 2) };
321
323
  case "operatingMargin":
322
- return { text: formatLevelPercent(row.operatingMargin ?? undefined), color: colors.text };
324
+ return { text: `${formatLevelPercent(row.operatingMargin ?? undefined)}${row.operatingMargin != null && row.operatingMarginPeriod ? ` ${row.operatingMarginPeriod}` : ""}`, color: colors.text };
323
325
  }
324
326
  }, []);
325
327
 
@@ -4,7 +4,8 @@ import type { HeadlessPaneDefinition } from "../../../types/plugin";
4
4
  import { formatCompactCurrency } from "../../../utils/format";
5
5
  import { resolveHeadlessInstrument } from "../shared/headless-market-data";
6
6
  import { loadReverseDcfInputs } from "./client";
7
- import { describeFcfMargin, DISCOUNT_RATES, projectReverseDcf, TERMINAL_GROWTH, TERMINAL_GROWTHS, type ImpliedGrowth } from "./model";
7
+ import { describeFcfMargin, DISCOUNT_RATES, pastGrowthBasis, projectReverseDcf, TERMINAL_GROWTH, TERMINAL_GROWTHS, ttmBaseNote,
8
+ type ImpliedGrowth } from "./model";
8
9
 
9
10
  const percent = (value: unknown) => typeof value === "number" ? `${(value * 100).toFixed(1)}%` : "--";
10
11
  /** An assumption as it is set: 9%, 2.5%. */
@@ -35,6 +36,7 @@ export const reverseDcfHeadless: HeadlessPaneDefinition<"bundle"> = {
35
36
  const quote = inputs.financials?.quote;
36
37
  // The enterprise value is measured at the regular session price, so the report is dated by it: after the close, the close.
37
38
  const valuedAt = regularSessionValuationTime(quote);
39
+ const notes = [model.enterpriseValueNote, ttmBaseNote(model)].filter((note): note is string => !!note);
38
40
  return {
39
41
  sections: [
40
42
  { title: "Reverse DCF", columns: [{ key: "label", header: "" }, { key: "value", header: "Value" }], rows: [
@@ -42,7 +44,7 @@ export const reverseDcfHeadless: HeadlessPaneDefinition<"bundle"> = {
42
44
  : `${typeof growth === "number" ? percent(growth) : growth} at ${rate(discountRate)} discount, ${rate(TERMINAL_GROWTH)} terminal` },
43
45
  { label: "Implied revenue, year 10", value: model.impliedRevenue == null ? "--"
44
46
  : `${formatCompactCurrency(model.impliedRevenue, currency)} at ${describeFcfMargin(fcfMargin, fcfMarginBasis)}` },
45
- { label: "Past FCF growth", value: model.pastGrowth ? `${percent(model.pastGrowth.rate)} over ${model.pastGrowth.years}y` : "--" },
47
+ { label: "Past FCF growth", value: model.pastGrowth ? `${percent(model.pastGrowth.rate)} ${pastGrowthBasis(model.pastGrowth)}` : "--" },
46
48
  { label: "FCF yield", value: percent(model.fcfYield) },
47
49
  { label: "Enterprise value", value: `${formatCompactCurrency(model.enterpriseValue ?? undefined, currency)}${model.enterpriseValueNote ? " (provider)" : ""}` },
48
50
  { label: "Free cash flow, TTM", value: formatCompactCurrency(model.freeCashFlow ?? undefined, currency) },
@@ -60,7 +62,7 @@ export const reverseDcfHeadless: HeadlessPaneDefinition<"bundle"> = {
60
62
  },
61
63
  unavailableSymbols: model.implied ? [] : [instrument.symbol],
62
64
  errors: [inputs.error, model.error].filter((value): value is string => !!value),
63
- ...(model.enterpriseValueNote ? { notes: [model.enterpriseValueNote] } : {}),
65
+ ...notes.length > 0 ? { notes } : {},
64
66
  metadata: { currency: model.currency, discountRate, terminalGrowth: TERMINAL_GROWTH, discountRates: DISCOUNT_RATES, fcfMargin,
65
67
  fcfMarginSource: fcfMarginBasis.source, companyFcfMargin: fcfMarginBasis.company, fcfMarginGap: fcfMarginBasis.gap,
66
68
  impliedRevenue: model.impliedRevenue, stale: inputs.stale, fetchedAt: inputs.fetchedAt, methodology: "docs/research-data.md#reverse-dcf" },
@@ -1,6 +1,8 @@
1
1
  import { unavailableText } from "../../../components/ui/status";
2
2
  import { atRegularSessionPrice } from "../../../market-data/quotes/regular-session-valuation";
3
3
  import type { Fundamentals, TickerFinancials } from "../../../types/financials";
4
+ import { fiscalPeriodLabel, fiscalYearEndMonth } from "../../../utils/fiscal-period";
5
+ import { formatCompactCurrency } from "../../../utils/format";
4
6
  import { enterpriseValueCheck, enterpriseValueLabel, enterpriseValueNotMeaningful, enterpriseValueReconciliationNote,
5
7
  enterpriseValueWithdrawn, reportedEnterpriseValue } from "../../../utils/fundamentals";
6
8
 
@@ -50,8 +52,10 @@ export interface ReverseDcfModel {
50
52
  fcfMarginBasis: FcfMarginBasis;
51
53
  /** Year-ten revenue at `fcfMargin`: trailing free cash flow grown at the implied rate for ten years, over the margin. */
52
54
  impliedRevenue: number | null;
53
- /** Annual FCF growth between the latest annual statement and one up to five years earlier, when both are positive. */
54
- pastGrowth: { rate: number; years: number } | null;
55
+ /** Annual FCF growth between the latest annual statement and one up to five years earlier, when both are positive. `period` names the two years: FY2021–FY2025. */
56
+ pastGrowth: { rate: number; years: number; period: string } | null;
57
+ /** The latest annual statement's free cash flow, which the past growth ends at, and the year it is of (FY2025). */
58
+ fiscalYearFcf: { value: number; label: string } | null;
55
59
  /** Implied growth for each discount rate (rows) at each terminal growth (columns). */
56
60
  sensitivity: { discountRate: number; implied: (ImpliedGrowth | null)[] }[];
57
61
  error: string | null;
@@ -82,15 +86,65 @@ export function solveImpliedGrowth(enterpriseValue: number, fcf: number, discoun
82
86
  return { kind: "rate", value: (low + high) / 2 };
83
87
  }
84
88
 
85
- function pastFcfGrowth(financials: TickerFinancials): ReverseDcfModel["pastGrowth"] {
86
- const annual = financials.annualStatements
89
+ /** The latest annual statements with a free cash flow, oldest first, as many as the past growth looks back over. */
90
+ function annualFreeCashFlows(financials: TickerFinancials) {
91
+ return financials.annualStatements
87
92
  .filter((row) => typeof row.freeCashFlow === "number" && Number.isFinite(row.freeCashFlow))
88
93
  .sort((a, b) => a.date.localeCompare(b.date))
89
94
  .slice(-6);
95
+ }
96
+
97
+ /** The two years a past growth spans, as issuers name them: `FY2021–FY2025`; period end dates where a year is off the fiscal calendar. */
98
+ function fiscalSpan(financials: TickerFinancials, from: string, to: string): string {
99
+ const month = fiscalYearEndMonth(financials.annualStatements);
100
+ const [start, end] = [from, to].map((date) => fiscalPeriodLabel(date, "annual", month)) as [string, string];
101
+ return start.startsWith("FY") && end.startsWith("FY") ? `${start}–${end}` : `${from.slice(0, 10)} to ${to.slice(0, 10)}`;
102
+ }
103
+
104
+ function pastFcfGrowth(financials: TickerFinancials): ReverseDcfModel["pastGrowth"] {
105
+ const annual = annualFreeCashFlows(financials);
90
106
  const first = annual[0], last = annual.at(-1);
91
107
  if (!first || !last || first === last || !(first.freeCashFlow! > 0) || !(last.freeCashFlow! > 0)) return null;
92
108
  const years = Math.round((Date.parse(last.date) - Date.parse(first.date)) / (365.25 * 86_400_000));
93
- return years > 0 ? { rate: (last.freeCashFlow! / first.freeCashFlow!) ** (1 / years) - 1, years } : null;
109
+ return years > 0
110
+ ? { rate: (last.freeCashFlow! / first.freeCashFlow!) ** (1 / years) - 1, years, period: fiscalSpan(financials, first.date, last.date) }
111
+ : null;
112
+ }
113
+
114
+ function latestFiscalYearFcf(financials: TickerFinancials): ReverseDcfModel["fiscalYearFcf"] {
115
+ const last = annualFreeCashFlows(financials).at(-1);
116
+ return last ? { value: last.freeCashFlow!, label: fiscalPeriodLabel(last.date, "annual", fiscalYearEndMonth(financials.annualStatements)) } : null;
117
+ }
118
+
119
+ /** How far above the latest fiscal year's free cash flow the trailing twelve months' must be to say the two bases differ. */
120
+ const TTM_BASE_RATIO = 1.5;
121
+
122
+ /**
123
+ * How a trailing free cash flow more than 1.5 times the latest fiscal year's
124
+ * differs from it (`2.7x` FY2025's `5.3B USD`): the growth priced in starts
125
+ * from the trailing figure, the past growth ends at the year's. Null when they
126
+ * are near each other, the year's is not positive, or the model has no growth
127
+ * to say.
128
+ */
129
+ export function ttmBaseGap(model: Pick<ReverseDcfModel, "freeCashFlow" | "fiscalYearFcf" | "currency" | "error">): { multiple: string; year: string; fcf: string } | null {
130
+ const { freeCashFlow, fiscalYearFcf } = model;
131
+ if (model.error || freeCashFlow == null || !fiscalYearFcf || !(fiscalYearFcf.value > 0) || !(freeCashFlow > fiscalYearFcf.value * TTM_BASE_RATIO)) return null;
132
+ return {
133
+ multiple: `${(freeCashFlow / fiscalYearFcf.value).toFixed(1)}x`,
134
+ year: fiscalYearFcf.label,
135
+ fcf: formatCompactCurrency(fiscalYearFcf.value, model.currency ?? undefined),
136
+ };
137
+ }
138
+
139
+ /** "The TTM base differs from FY2025: TTM FCF is 2.7x that year's 5.3B USD.", the one line a report adds when `ttmBaseGap` finds a gap. */
140
+ export function ttmBaseNote(model: Parameters<typeof ttmBaseGap>[0]): string | null {
141
+ const gap = ttmBaseGap(model);
142
+ return gap ? `The TTM base differs from ${gap.year}: TTM FCF is ${gap.multiple} that year's ${gap.fcf}.` : null;
143
+ }
144
+
145
+ /** What the past growth rate is over, after the rate: `a year, FY2021–FY2025`. */
146
+ export function pastGrowthBasis(growth: NonNullable<ReverseDcfModel["pastGrowth"]>): string {
147
+ return `a year, ${growth.period}`;
94
148
  }
95
149
 
96
150
  /**
@@ -145,7 +199,8 @@ export function projectReverseDcf(
145
199
  const empty: ReverseDcfModel = { symbol: options.symbol, currency, discountRate: options.discountRate, terminalGrowth: TERMINAL_GROWTH,
146
200
  enterpriseValue, computedEnterpriseValue: check?.computed ?? null, enterpriseValueNote: check ? enterpriseValueReconciliationNote(fundamentals) ?? null : null,
147
201
  freeCashFlow, fcfYield: null, implied: null, fcfMargin, fcfMarginBasis, impliedRevenue: null,
148
- pastGrowth: financials ? pastFcfGrowth(financials) : null, sensitivity: [], error: null };
202
+ pastGrowth: financials ? pastFcfGrowth(financials) : null, fiscalYearFcf: financials ? latestFiscalYearFcf(financials) : null,
203
+ sensitivity: [], error: null };
149
204
  const valueCurrency = fundamentals?.marketCapCurrency;
150
205
  // ADRs and cross-listings can report cash flows in one currency and trade in another.
151
206
  if (currency && valueCurrency && currency !== valueCurrency) {
@@ -10,8 +10,8 @@ import type { PaneProps } from "../../../types/plugin";
10
10
  import { Box } from "../../../ui";
11
11
  import { formatCompact, formatCompactCurrency } from "../../../utils/format";
12
12
  import { loadReverseDcfInputs } from "./client";
13
- import { describeFcfMargin, DISCOUNT_RATES, FORECAST_YEARS, formatFcfMargin, projectReverseDcf, TERMINAL_GROWTH, TERMINAL_GROWTHS,
14
- type ImpliedGrowth, type ReverseDcfModel } from "./model";
13
+ import { describeFcfMargin, DISCOUNT_RATES, FORECAST_YEARS, formatFcfMargin, pastGrowthBasis, projectReverseDcf, TERMINAL_GROWTH, TERMINAL_GROWTHS,
14
+ ttmBaseGap, type ImpliedGrowth, type ReverseDcfModel } from "./model";
15
15
 
16
16
  export const DISCOUNT_OPTIONS = DISCOUNT_RATES.map((rate) => ({ value: String(Math.round(rate * 100)), label: `${Math.round(rate * 100)}%` }));
17
17
  /** The margin setting that follows the company's own trailing margin, the pane's default. */
@@ -91,15 +91,18 @@ export function ReverseDcfPane({ width, height, focused }: PaneProps) {
91
91
  const currency = model.currency ?? undefined;
92
92
  const implied = model.implied?.kind === "rate" ? model.implied.value : null;
93
93
  const past = model.pastGrowth;
94
+ const gap = ttmBaseGap(model);
94
95
  return [
95
96
  { id: "implied", label: "Priced-in growth", value: impliedText(model.implied),
96
97
  tone: implied == null || !past ? undefined : implied > past.rate ? "negative" : "positive",
97
98
  detail: `a year for ${FORECAST_YEARS}y, then ${pct(TERMINAL_GROWTH, false)}` },
98
99
  { id: "revenue", label: `Revenue, yr ${FORECAST_YEARS}`, value: formatCompactCurrency(model.impliedRevenue ?? undefined, currency),
99
100
  detail: `at ${describeFcfMargin(model.fcfMargin, model.fcfMarginBasis)}` },
100
- { id: "past", label: "Past FCF growth", value: past ? pct(past.rate) : "--", detail: past ? `per year over ${past.years}y` : undefined },
101
+ { id: "past", label: "Past FCF growth", value: past ? pct(past.rate) : "--", detail: past ? pastGrowthBasis(past) : undefined },
101
102
  { id: "yield", label: "FCF yield", value: pct(model.fcfYield, false) },
102
- { id: "fcf", label: "FCF", value: formatCompactCurrency(model.freeCashFlow ?? undefined, currency) },
103
+ // A trailing figure far above the fiscal year the past growth ends at says so beside itself.
104
+ { id: "fcf", label: "FCF", value: formatCompactCurrency(model.freeCashFlow ?? undefined, currency),
105
+ detail: gap ? `TTM base, ${gap.multiple} ${gap.year}'s ${gap.fcf}` : undefined },
103
106
  { id: "ev", label: "EV", value: formatCompactCurrency(model.enterpriseValue ?? undefined, currency),
104
107
  // More than 5% from market cap + debt - cash: the provider's, with the computed figure where it fits.
105
108
  detail: model.computedEnterpriseValue != null ? `provider vs ${formatCompact(model.computedEnterpriseValue)}` : undefined },
@@ -14,7 +14,7 @@ import { useAppVisible } from "../../../state/app/activity";
14
14
  import { ScannerClientContext } from "../shared/scanner-capture";
15
15
  import { delayedFooterSegment } from "../../../components/layout/pane/footer/model";
16
16
 
17
- export interface ScannerFeedState<T extends ScannerPayload> {
17
+ export interface ScannerFeedState<T extends Pick<ScannerPayload, "status" | "access" | "delayMinutes">> {
18
18
  payload: T | null;
19
19
  /** True once the server refused the subscription (not signed in, or not Pro). */
20
20
  denied: boolean;
@@ -63,7 +63,7 @@ export function useFlowFeed(): ScannerFeedState<ScannerFlowPayload> {
63
63
  */
64
64
  export function useScannerStatusFooter(
65
65
  registrationId: string,
66
- state: ScannerFeedState<ScannerPayload>,
66
+ state: ScannerFeedState<Pick<ScannerPayload, "status" | "access" | "delayMinutes">>,
67
67
  focused: boolean,
68
68
  extra: PaneFooterSegment | null = null,
69
69
  ): void {
@@ -81,6 +81,22 @@ function printRoot(underlying: string): string {
81
81
  return underlying.toUpperCase().replace(/\d+$/, "");
82
82
  }
83
83
 
84
+ /**
85
+ * Whose prints the pane shows: one name (`FLOW NVDA`), which takes the place
86
+ * of the universe filter, or the market, narrowed to the user's own tickers
87
+ * when the universe filter says so.
88
+ */
89
+ export interface FlowScope {
90
+ symbol?: string | null;
91
+ watchlist: ReadonlySet<string>;
92
+ }
93
+
94
+ /** The listed symbol a `FLOW <ticker>` pane is about, as the pane setting or report argument names it. */
95
+ export function flowScopeSymbol(value: unknown): string | null {
96
+ const symbol = typeof value === "string" ? value.trim().toUpperCase() : "";
97
+ return symbol || null;
98
+ }
99
+
84
100
  /**
85
101
  * The shared feed is published once for everyone, so every user preference is a
86
102
  * local predicate over the same events. Never push these upstream.
@@ -88,13 +104,14 @@ function printRoot(underlying: string): string {
88
104
  export function filterFlowEvents(
89
105
  events: readonly ScannerFlowEvent[] | undefined,
90
106
  filters: FlowFilters,
91
- watchlist: ReadonlySet<string>,
107
+ scope: FlowScope,
92
108
  now = Date.now(),
93
109
  ): ScannerFlowEvent[] {
94
110
  const minPremium = Number(filters.minPremium);
95
111
  const minVolOi = filters.volOi === "off" ? null : Number(filters.volOi);
96
112
  const maxExpiryDays = filters.expiry === "all" ? null : Number(filters.expiry);
97
- const roots = filters.universe === "watchlist" ? new Set([...watchlist].map(flowOptionRoot)) : null;
113
+ const roots = scope.symbol ? new Set([flowOptionRoot(scope.symbol)])
114
+ : filters.universe === "watchlist" ? new Set([...scope.watchlist].map(flowOptionRoot)) : null;
98
115
 
99
116
  return (events ?? []).filter((event) => {
100
117
  if (!(event.premium >= minPremium)) return false;
@@ -121,7 +138,15 @@ export function flowEmptyState(
121
138
  received: number,
122
139
  visible: number,
123
140
  status: ScannerStatus | undefined,
141
+ symbol: string | null = null,
124
142
  ): { title: string; hint: string } {
143
+ // One name's prints are mostly recorded ones, and a quiet name has none at all.
144
+ if (symbol) {
145
+ return {
146
+ title: `No ${symbol} prints under these filters.`,
147
+ hint: "Flow is recorded for the most active names, the day's movers and names with a flow alert.",
148
+ };
149
+ }
125
150
  if (received > visible) {
126
151
  return {
127
152
  title: `${received - visible} ${received - visible === 1 ? "print" : "prints"} hidden by filters.`,
@@ -219,22 +244,43 @@ export function mergeFlowRows(
219
244
  /** The pane's filters as a query for recorded prints, so each page is rows it will show. */
220
245
  export function flowHistoryQuery(
221
246
  filters: FlowFilters,
222
- watchlist: ReadonlySet<string>,
247
+ scope: FlowScope,
223
248
  before?: { at: number; id: string },
224
249
  limit = FLOW_HISTORY_PAGE,
225
250
  ): ScannerFlowHistoryQuery {
226
251
  return {
227
252
  ...(before ? { before } : {}),
228
253
  limit,
254
+ ...flowFilterQuery(filters),
255
+ ...(scope.symbol ? { symbols: [scope.symbol.toUpperCase()] }
256
+ : filters.universe === "watchlist" ? { symbols: [...scope.watchlist].map((symbol) => symbol.toUpperCase()).sort() } : {}),
257
+ };
258
+ }
259
+
260
+ function flowFilterQuery(filters: FlowFilters): Omit<ScannerFlowHistoryQuery, "before" | "limit" | "symbols"> {
261
+ return {
229
262
  minPremium: Number(filters.minPremium),
230
263
  ...(filters.side === "calls" ? { right: "C" as const } : filters.side === "puts" ? { right: "P" as const } : {}),
231
264
  ...(filters.kind === "sweeps" ? { kind: "sweep" as const } : filters.kind === "blocks" ? { kind: "block" as const } : {}),
232
265
  ...(filters.volOi === "off" ? {} : { minVolOi: Number(filters.volOi) }),
233
266
  ...(filters.expiry === "all" ? {} : { maxExpiryDays: Number(filters.expiry) }),
234
- ...(filters.universe === "watchlist" ? { symbols: [...watchlist].map((symbol) => symbol.toUpperCase()).sort() } : {}),
235
267
  };
236
268
  }
237
269
 
270
+ /**
271
+ * The session summary's query as the Cloud route reads it: the same filters as
272
+ * the recorded prints, with one name as `symbol`, which also asks for its strikes.
273
+ */
274
+ export function flowSessionSearch(filters: FlowFilters, scope: FlowScope): string {
275
+ const params = new URLSearchParams(flowHistorySearch({
276
+ ...flowFilterQuery(filters),
277
+ ...(!scope.symbol && filters.universe === "watchlist"
278
+ ? { symbols: [...scope.watchlist].map((symbol) => symbol.toUpperCase()).sort() } : {}),
279
+ }));
280
+ if (scope.symbol) params.set("symbol", scope.symbol.toUpperCase());
281
+ return params.toString();
282
+ }
283
+
238
284
  function localDay(at: number): string {
239
285
  const date = new Date(at);
240
286
  return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;