@gloom-sh/terminal 0.18.5 → 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 (436) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +140 -7
  3. package/src/api-client/cot.ts +6 -2
  4. package/src/api-client/credit-documents.ts +6 -0
  5. package/src/api-client/data.ts +17 -6
  6. package/src/api-client/equity-screener.ts +24 -0
  7. package/src/api-client/fund-facts.ts +45 -4
  8. package/src/api-client/index.ts +11 -0
  9. package/src/api-client/market-discovery.ts +10 -0
  10. package/src/api-client/market-movers.ts +7 -0
  11. package/src/api-client/members.ts +1 -1
  12. package/src/api-client/perps.ts +43 -0
  13. package/src/api-client/research-activity.ts +3 -2
  14. package/src/api-client/types.ts +70 -2
  15. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  16. package/src/app/runtime/broker-import.ts +0 -2
  17. package/src/app/runtime/plugin-bindings.ts +73 -56
  18. package/src/architecture/pane-tabs-allowlist.json +3 -0
  19. package/src/architecture/ttl-caches-allowlist.json +19 -0
  20. package/src/brokers/sync-broker-instance.ts +2 -8
  21. package/src/capabilities/chart-series.ts +1 -1
  22. package/src/cli/commands/crypto-hints.ts +40 -24
  23. package/src/cli/commands/market.ts +72 -26
  24. package/src/cli/commands/options-chain.ts +48 -7
  25. package/src/cli/commands/options-leaps.ts +9 -3
  26. package/src/cli/commands/overview.ts +61 -12
  27. package/src/cli/commands/search.ts +21 -1
  28. package/src/cli/commands/themes.ts +11 -2
  29. package/src/cli/commands/ticker.ts +55 -34
  30. package/src/cli/company-name-hint.ts +41 -0
  31. package/src/cli/earnings-calendar.ts +1 -1
  32. package/src/cli/errors.ts +8 -0
  33. package/src/cli/glossary.ts +25 -2
  34. package/src/cli/history-rows.ts +57 -9
  35. package/src/cli/index.ts +2 -0
  36. package/src/cli/listing-arg.ts +180 -52
  37. package/src/cli/output-file.ts +68 -0
  38. package/src/cli/pane-functions/capabilities.ts +3 -0
  39. package/src/cli/pane-functions/catalog.ts +80 -7
  40. package/src/cli/pane-functions/dom.ts +13 -3
  41. package/src/cli/pane-functions/freshness.ts +86 -3
  42. package/src/cli/pane-functions/function-help.ts +13 -10
  43. package/src/cli/pane-functions/headless.ts +17 -7
  44. package/src/cli/pane-functions/index.ts +49 -14
  45. package/src/cli/pane-functions/market-session.ts +66 -4
  46. package/src/cli/pane-functions/options.ts +19 -1
  47. package/src/cli/pane-functions/resolver.ts +19 -8
  48. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  49. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  50. package/src/cli/quote-answer.ts +22 -11
  51. package/src/cli/ticker-resolution.ts +3 -2
  52. package/src/components/chart/composite/composite-chart.tsx +4 -0
  53. package/src/components/chart/composite/interactions.ts +6 -0
  54. package/src/components/chart/composite/legend.tsx +45 -7
  55. package/src/components/chart/composite/panel-gestures.ts +33 -0
  56. package/src/components/chart/composite/panel-surface.tsx +52 -3
  57. package/src/components/chart/composite/rasterizer.ts +24 -13
  58. package/src/components/chart/composite/scene.ts +39 -4
  59. package/src/components/chart/composite/series-cues.ts +14 -0
  60. package/src/components/chart/composite/text-renderer.ts +9 -2
  61. package/src/components/chart/composite/tool-catalog.ts +36 -0
  62. package/src/components/chart/composite/tools.ts +203 -44
  63. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  64. package/src/components/command-bar/assist/inventory.ts +2 -1
  65. package/src/components/command-bar/commands/direct/index.ts +28 -13
  66. package/src/components/command-bar/pane-templates/items.ts +3 -2
  67. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  68. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  69. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  70. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  71. package/src/components/onboarding/wizard-positions.ts +3 -3
  72. package/src/components/ticker/list-table-view.tsx +4 -1
  73. package/src/components/ui/choice-dialog.tsx +23 -18
  74. package/src/components/ui/choice-groups.ts +18 -0
  75. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  76. package/src/components/ui/data-table/opentui/model.ts +26 -1
  77. package/src/components/ui/data-table/types.ts +2 -0
  78. package/src/components/ui/list-view.tsx +8 -4
  79. package/src/components/ui/query-bar-keys.tsx +2 -0
  80. package/src/components/ui/query-bar.tsx +1 -0
  81. package/src/components/ui/select-button.tsx +5 -1
  82. package/src/components/ui/select-field.tsx +1 -0
  83. package/src/components/ui/table-layout.ts +8 -5
  84. package/src/core/app-runtime.ts +2 -0
  85. package/src/core/app-services.ts +2 -2
  86. package/src/data/config/chart-settings.ts +1 -1
  87. package/src/data/json-ticker-repository.ts +17 -8
  88. package/src/data/ticker-repository.ts +16 -9
  89. package/src/data/ticker-writes.ts +81 -0
  90. package/src/data/ttl-cache.ts +165 -0
  91. package/src/docs/snapshot.json +71 -137
  92. package/src/i18n/es.ts +18 -2
  93. package/src/i18n/ja.ts +18 -2
  94. package/src/i18n/ko.ts +18 -2
  95. package/src/i18n/zh-cn.ts +18 -2
  96. package/src/i18n/zh-tw.ts +18 -2
  97. package/src/market-data/depositary-pair.ts +2 -2
  98. package/src/market-data/fund-facts.ts +50 -0
  99. package/src/market-data/market/change-basis.ts +16 -3
  100. package/src/market-data/market/format.ts +25 -0
  101. package/src/market-data/market/freshness.ts +2 -1
  102. package/src/market-data/options-alternatives.ts +81 -4
  103. package/src/news/aggregator.ts +113 -9
  104. package/src/news/cloud-session.ts +21 -0
  105. package/src/news/hooks.ts +1 -0
  106. package/src/news/news-model.ts +1 -0
  107. package/src/news/types.ts +11 -0
  108. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  109. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  110. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  111. package/src/plugins/builtin/alerts/cli.ts +336 -77
  112. package/src/plugins/builtin/alerts/command.ts +25 -1
  113. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  114. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  115. package/src/plugins/builtin/alerts/format.ts +75 -5
  116. package/src/plugins/builtin/alerts/headless.ts +24 -6
  117. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  118. package/src/plugins/builtin/alerts/history.ts +1 -1
  119. package/src/plugins/builtin/alerts/index.tsx +79 -16
  120. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  121. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  122. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  123. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  124. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  125. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  126. package/src/plugins/builtin/alerts/storage.ts +12 -8
  127. package/src/plugins/builtin/alerts/types.ts +4 -0
  128. package/src/plugins/builtin/analytics/index.tsx +2 -1
  129. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  130. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  131. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  132. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  134. package/src/plugins/builtin/attention/index.tsx +2 -1
  135. package/src/plugins/builtin/attention/listing.ts +14 -0
  136. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  137. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  138. package/src/plugins/builtin/buildout/format.ts +38 -0
  139. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  140. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  141. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  142. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  143. package/src/plugins/builtin/changelog/index.tsx +9 -6
  144. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  145. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  146. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  147. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  148. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  149. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  150. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  151. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  152. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  153. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  154. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  155. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  156. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  157. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  158. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  159. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  160. package/src/plugins/builtin/company-attention/client.ts +8 -9
  161. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  162. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  163. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  164. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  165. package/src/plugins/builtin/correlation/headless.ts +13 -5
  166. package/src/plugins/builtin/correlation/history.ts +22 -0
  167. package/src/plugins/builtin/cot/client.ts +22 -18
  168. package/src/plugins/builtin/cot/headless.ts +15 -10
  169. package/src/plugins/builtin/cot/model.ts +14 -1
  170. package/src/plugins/builtin/cot/pane.tsx +22 -14
  171. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  172. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  173. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  174. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  175. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  176. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  177. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  178. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  179. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  180. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  181. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  182. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  183. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  184. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  185. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  186. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  187. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  188. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  189. package/src/plugins/builtin/docs/targets.ts +5 -1
  190. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  191. package/src/plugins/builtin/earnings/board.tsx +62 -71
  192. package/src/plugins/builtin/earnings/headless.ts +40 -4
  193. package/src/plugins/builtin/earnings/scope.ts +31 -43
  194. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  195. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  196. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  197. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  198. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  199. package/src/plugins/builtin/econ/types.ts +2 -0
  200. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  201. package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
  202. package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
  203. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  204. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  205. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  206. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  207. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  208. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  209. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  210. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  211. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  212. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  213. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  214. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  215. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  216. package/src/plugins/builtin/gpu/headless.ts +8 -2
  217. package/src/plugins/builtin/help/function-index.ts +4 -1
  218. package/src/plugins/builtin/help/index.tsx +1 -0
  219. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  220. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  221. package/src/plugins/builtin/holders/headless.ts +18 -1
  222. package/src/plugins/builtin/iv-history/client.ts +52 -11
  223. package/src/plugins/builtin/iv-history/format.ts +36 -23
  224. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  225. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  226. package/src/plugins/builtin/iv-history/model.ts +163 -27
  227. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  228. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  229. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  230. package/src/plugins/builtin/jobs/client.ts +25 -29
  231. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  232. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  233. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  234. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  235. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  236. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  237. package/src/plugins/builtin/market-movers/client.ts +25 -7
  238. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  239. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  240. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  241. package/src/plugins/builtin/market-movers/model.ts +4 -1
  242. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  243. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  244. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  245. package/src/plugins/builtin/market-movers/table.ts +17 -5
  246. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  247. package/src/plugins/builtin/news/index.tsx +1 -0
  248. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  249. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  250. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  251. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  252. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  253. package/src/plugins/builtin/openfda/client.ts +14 -2
  254. package/src/plugins/builtin/openfda/index.tsx +3 -2
  255. package/src/plugins/builtin/openfda/model.ts +9 -0
  256. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  257. package/src/plugins/builtin/options/analytics.ts +29 -5
  258. package/src/plugins/builtin/options/enrichment.ts +17 -5
  259. package/src/plugins/builtin/options/table.ts +5 -2
  260. package/src/plugins/builtin/options/types.ts +2 -0
  261. package/src/plugins/builtin/options/view.tsx +17 -4
  262. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  263. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  264. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  265. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  266. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  267. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  268. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  269. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  270. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  271. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  272. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  273. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  274. package/src/plugins/builtin/pe-band/model.ts +12 -49
  275. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  276. package/src/plugins/builtin/perps/cascade.ts +236 -0
  277. package/src/plugins/builtin/perps/client.ts +56 -2
  278. package/src/plugins/builtin/perps/headless.ts +65 -9
  279. package/src/plugins/builtin/perps/index.tsx +7 -5
  280. package/src/plugins/builtin/perps/model.ts +18 -4
  281. package/src/plugins/builtin/perps/pane.tsx +19 -22
  282. package/src/plugins/builtin/perps/status-line.ts +25 -0
  283. package/src/plugins/builtin/plugin-module.ts +5 -1
  284. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  285. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  286. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  287. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  288. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  289. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  290. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  291. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  292. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  293. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  294. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  295. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  296. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  297. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  298. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  299. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  300. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  301. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  302. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  303. package/src/plugins/builtin/research/event-model.ts +10 -8
  304. package/src/plugins/builtin/research/headless.ts +1 -1
  305. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  306. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  307. package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
  308. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  309. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  310. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  311. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  312. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  313. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  314. package/src/plugins/builtin/scanner/headless.ts +204 -35
  315. package/src/plugins/builtin/scanner/index.ts +16 -5
  316. package/src/plugins/builtin/scanner/live-headless.ts +27 -3
  317. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  318. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  319. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  320. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  321. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  322. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  323. package/src/plugins/builtin/sec/headless.ts +2 -1
  324. package/src/plugins/builtin/series-inventory.ts +2 -0
  325. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  326. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  327. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  328. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  329. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  330. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  331. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  332. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  333. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  334. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  335. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  336. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  337. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  338. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  339. package/src/plugins/builtin/themes/model.ts +16 -7
  340. package/src/plugins/builtin/themes/pane.tsx +5 -5
  341. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  342. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  343. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  344. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  345. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  346. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  347. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  348. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  349. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  350. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  351. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  352. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  353. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  354. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  355. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  357. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  358. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  359. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  360. package/src/plugins/builtin/volatility/index.tsx +2 -2
  361. package/src/plugins/builtin/volatility/pane.tsx +2 -2
  362. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  363. package/src/plugins/catalog-manifest.ts +2 -1
  364. package/src/plugins/marketplace/model.ts +18 -5
  365. package/src/portfolio/futures-positions.ts +6 -3
  366. package/src/portfolio/position-quantity.ts +15 -9
  367. package/src/remote/resources.ts +2 -1
  368. package/src/renderers/dom/data-table/row.tsx +26 -0
  369. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  370. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  371. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  372. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  373. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  374. package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
  375. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  376. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  377. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  378. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  379. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  380. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  381. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  382. package/src/renderers/electrobun/view/main.tsx +24 -1
  383. package/src/renderers/electrobun/view/news-source.ts +3 -1
  384. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  385. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  386. package/src/renderers/opentui/start.tsx +5 -0
  387. package/src/series/registry.ts +1 -0
  388. package/src/series/types.ts +6 -0
  389. package/src/series/validate.ts +1 -0
  390. package/src/sources/gloomberb-cloud/index.ts +1 -0
  391. package/src/sources/gloomberb-cloud/news.ts +47 -7
  392. package/src/sources/provider-router/financials.ts +15 -3
  393. package/src/sources/provider-router/index.ts +8 -5
  394. package/src/state/ticker-cleanup.ts +50 -0
  395. package/src/sync/controller.ts +4 -3
  396. package/src/sync/core-contributors.ts +90 -31
  397. package/src/telemetry/stall-reports-dom.ts +38 -0
  398. package/src/telemetry/stall-reports-node.ts +75 -0
  399. package/src/telemetry/stall-reports.ts +214 -0
  400. package/src/test-support/preload.ts +14 -0
  401. package/src/theme/themes.ts +10 -0
  402. package/src/tickers/company-name.ts +44 -3
  403. package/src/tickers/listing-venues.ts +15 -3
  404. package/src/tickers/terminal-codes.ts +19 -0
  405. package/src/time-series/alignment.ts +1 -1
  406. package/src/time-series/economic.ts +18 -4
  407. package/src/time-series/field-catalog.ts +1 -1
  408. package/src/time-series/filed-trailing-eps.ts +197 -0
  409. package/src/time-series/fundamentals.ts +44 -1
  410. package/src/time-series/ice-credit.ts +3 -0
  411. package/src/time-series/price-comparison.ts +30 -2
  412. package/src/time-series/resolve-context.ts +3 -1
  413. package/src/time-series/resolve.ts +56 -4
  414. package/src/time-series/session-bar.ts +11 -0
  415. package/src/time-series/spec.ts +3 -2
  416. package/src/time-series/studies.ts +8 -2
  417. package/src/time-series/total-return.ts +160 -0
  418. package/src/time-series/transforms.ts +2 -3
  419. package/src/time-series/types.ts +22 -1
  420. package/src/types/financials.ts +32 -1
  421. package/src/types/headless.ts +21 -3
  422. package/src/types/news-source.ts +1 -0
  423. package/src/types/plugin.ts +9 -0
  424. package/src/ui/host.tsx +3 -0
  425. package/src/updater/failure.ts +4 -1
  426. package/src/utils/cli-output.ts +42 -1
  427. package/src/utils/crypto-pair.ts +44 -0
  428. package/src/utils/forward-dividend.ts +2 -0
  429. package/src/utils/fundamentals.ts +24 -3
  430. package/src/utils/fx-market-hours.ts +26 -3
  431. package/src/utils/main-thread-stall.ts +35 -2
  432. package/src/utils/market-capitalization.ts +24 -1
  433. package/src/utils/price-history.ts +7 -1
  434. package/src/utils/shell-word.ts +8 -0
  435. package/src/utils/trailing-dividend.ts +53 -4
  436. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -0,0 +1,197 @@
1
+ import type { Fundamentals, FinancialStatement, TickerFinancials } from "../types/financials";
2
+ import { resolveCurrencyUnit } from "../utils/currency-units";
3
+ import { areNearbyFinancialPeriodEnds, statementFieldAvailability } from "../utils/financial-statements";
4
+ import { extractFundamentalSeries, knownWhenFiled } from "./fundamentals";
5
+ import type { SecuritySeriesSource, TimeSeriesPoint } from "./types";
6
+
7
+ const DAY_MS = 86_400_000;
8
+ /**
9
+ * A figure stops pricing this long after its period end. Sixteen months
10
+ * covers a fiscal year plus its filing, so a year of unavailable quarterly
11
+ * sums falls back on the last annual figure but never on one long overdue.
12
+ */
13
+ export const STALE_TRAILING_EPS_MS = 487 * DAY_MS;
14
+ /**
15
+ * The latest period's trailing figure reads statements this far back at most:
16
+ * its four quarters (some 270 days), and for a fiscal fourth quarter read from
17
+ * its year, the three quarters before it. Older rows change nothing.
18
+ */
19
+ const LATEST_WINDOW_MS = 760 * DAY_MS;
20
+
21
+ /** One trailing-twelve-month EPS figure and when it became known. */
22
+ export interface EpsStep {
23
+ /** ISO period end. */
24
+ periodEnd: string;
25
+ /** Four reported quarters summed, or a reported fiscal year. */
26
+ basis: "ttm" | "annual";
27
+ knownAt: Date;
28
+ /** False when no publication date or report is on record and `knownAt` is the period end. */
29
+ dated: boolean;
30
+ /** Statement currency units; null when unavailable (a declared gap or an incomplete run of quarters). */
31
+ eps: number | null;
32
+ currency: string | null;
33
+ }
34
+
35
+ function epsSeries(financials: TickerFinancials, period: "ttm" | "annual"): TimeSeriesPoint[] {
36
+ const source: SecuritySeriesSource = { kind: "security", instrument: { symbol: "" }, fieldId: "fundamental.eps", period, timestampMode: "available-at" };
37
+ return extractFundamentalSeries(financials, source);
38
+ }
39
+
40
+ /**
41
+ * Trailing EPS through time, oldest first. Four reported quarters are summed
42
+ * by the shared statement series, which keeps declared gaps and broken runs
43
+ * of quarters unavailable and reads a fiscal fourth quarter with no EPS of its
44
+ * own from its year. A reported fiscal year is itself a trailing twelve-month
45
+ * figure; at a fiscal year end it replaces the quarterly sum. `known` dates
46
+ * each row's figures (PEB dates them by the company's reports as well).
47
+ */
48
+ export function trailingEpsSteps(
49
+ financials: TickerFinancials,
50
+ known: (row: FinancialStatement) => FinancialStatement = knownWhenFiled,
51
+ ): EpsStep[] {
52
+ const adjusted = { ...financials, quarterlyStatements: financials.quarterlyStatements.map(known),
53
+ annualStatements: financials.annualStatements.map(known) };
54
+ const toStep = (basis: EpsStep["basis"]) => (point: TimeSeriesPoint): EpsStep => ({
55
+ periodEnd: point.observedAt.toISOString().slice(0, 10), basis, knownAt: point.date, dated: point.availableAt !== undefined,
56
+ eps: point.value != null && Number.isFinite(point.value) ? point.value : null, currency: point.provenance?.currency ?? null,
57
+ });
58
+ const steps = epsSeries(adjusted, "annual").map(toStep("annual"));
59
+ for (const step of epsSeries(adjusted, "ttm").map(toStep("ttm"))) {
60
+ const index = steps.findIndex((other) => areNearbyFinancialPeriodEnds(other.periodEnd, step.periodEnd));
61
+ if (index < 0) steps.push(step);
62
+ else if (steps[index]!.eps == null && step.eps != null) steps[index] = step;
63
+ }
64
+ return steps.sort((a, b) => a.knownAt.getTime() - b.knownAt.getTime() || a.periodEnd.localeCompare(b.periodEnd));
65
+ }
66
+
67
+ /** The trailing EPS of the last period filed, with that period's end and the statements' currency. */
68
+ interface FiledTrailingEps {
69
+ eps: number;
70
+ periodEnd: string;
71
+ currency: string;
72
+ }
73
+
74
+ /**
75
+ * How far the filed figure may sit from Gloom Cloud's, either way, before it
76
+ * is taken for another share basis (a receipt or a class the flags miss, as
77
+ * BRK.B's statements are per Class A share) rather than newer or restated
78
+ * quarters: PLTR's 1.17 against 1.13 stands, 4.00 against 0.40 does not.
79
+ */
80
+ const SERVED_EPS_RATIO_LIMIT = 1.5;
81
+
82
+ type TrailingEpsInputs = Pick<TickerFinancials, "quote" | "fundamentals" | "annualStatements" | "quarterlyStatements" | "financialCurrency">;
83
+
84
+ const isReceipt = (financials: TrailingEpsInputs) => !!(
85
+ financials.quote?.isDepositaryReceipt || financials.fundamentals?.isDepositaryReceipt
86
+ || financials.fundamentals?.shareBasis === "depositary_receipt" || financials.quote?.adrRatio || financials.fundamentals?.adrRatio
87
+ );
88
+
89
+ /** `filed` is the day the latest EPS on record was filed (a restated one's restating filing), the period end when none is dated. */
90
+ type LatestStep = { periodEnd: string; eps: number; currency: string; filed: string } | null;
91
+
92
+ /**
93
+ * The last period's step by what it is read from: the router answers the same
94
+ * rows many times, in new arrays. Bounded; the oldest entry leaves first.
95
+ */
96
+ const latestSteps = new Map<string, LatestStep>();
97
+ const LATEST_STEPS_KEPT = 500;
98
+
99
+ /** What the trailing EPS series reads of a row. */
100
+ const rowKey = (row: FinancialStatement) => [
101
+ row.date, row.currency ?? "", row.eps ?? "", statementFieldAvailability(row, "eps") ?? "", row.availableAt ?? "", row.dateSource ?? "",
102
+ Object.keys(row.fieldAvailability ?? {}).length,
103
+ row.unavailableFields?.join("+") ?? "", row.unavailableEarnings?.join("+") ?? "", row.epsBasis?.status ?? "", row.shareBasis ?? "",
104
+ ].join("|");
105
+
106
+ /** The trailing EPS step for the last period on record, null when that period gives none. */
107
+ function latestStep(financials: TrailingEpsInputs): LatestStep {
108
+ const rows = [...financials.quarterlyStatements, ...financials.annualStatements];
109
+ const latest = rows.map((row) => row.date).filter((date) => Number.isFinite(Date.parse(date))).sort().at(-1);
110
+ if (!latest) return null;
111
+ const recent = (row: FinancialStatement) => Date.parse(latest) - Date.parse(row.date) <= LATEST_WINDOW_MS;
112
+ const quarterlyStatements = financials.quarterlyStatements.filter(recent);
113
+ const annualStatements = financials.annualStatements.filter(recent);
114
+ const key = [financials.financialCurrency ?? "", quarterlyStatements.map(rowKey).join(";"), annualStatements.map(rowKey).join(";")].join("#");
115
+ if (latestSteps.has(key)) return latestSteps.get(key)!;
116
+ const found = trailingEpsSteps({ financialCurrency: financials.financialCurrency, priceHistory: [], quarterlyStatements, annualStatements })
117
+ .find((candidate) => areNearbyFinancialPeriodEnds(candidate.periodEnd, latest));
118
+ const filed = [...quarterlyStatements, ...annualStatements]
119
+ .flatMap((row) => typeof row.eps === "number" ? [statementFieldAvailability(row, "eps")?.slice(0, 10)] : [])
120
+ .filter((date): date is string => !!date).sort().at(-1);
121
+ const step = found?.eps != null && found.currency
122
+ ? { periodEnd: found.periodEnd, eps: found.eps, currency: found.currency, filed: filed && filed > found.periodEnd ? filed : found.periodEnd }
123
+ : null;
124
+ if (latestSteps.size >= LATEST_STEPS_KEPT) latestSteps.delete(latestSteps.keys().next().value!);
125
+ latestSteps.set(key, step);
126
+ return step;
127
+ }
128
+
129
+ /**
130
+ * The headline trailing EPS, as PEB and GE read the statements: the diluted
131
+ * EPS of the last four filed quarters, or of the fiscal year when that is the
132
+ * last period filed. Null when that period gives none (a quarter missing or
133
+ * withheld, a split basis unresolved, two share bases), when it is overdue,
134
+ * when its currency is not the price's, for a depositary receipt, whose
135
+ * statements are per ordinary share, when a split went ex after the last of
136
+ * its figures was filed (they are on the old share count until a filing
137
+ * restates them), and when it is more than SERVED_EPS_RATIO_LIMIT times
138
+ * Gloom Cloud's figure either way.
139
+ */
140
+ function filedTrailingEps(financials: TrailingEpsInputs, now = Date.now()): FiledTrailingEps | null {
141
+ const price = resolveCurrencyUnit(financials.quote?.currency);
142
+ if (!financials.quote || price.divisor !== 1 || !/^[A-Z]{3}$/.test(price.currency) || isReceipt(financials)) return null;
143
+ const step = latestStep(financials);
144
+ if (!step || now - Date.parse(step.periodEnd) > STALE_TRAILING_EPS_MS) return null;
145
+ const currency = resolveCurrencyUnit(step.currency);
146
+ if (currency.currency !== price.currency || currency.divisor !== 1) return null;
147
+ // The trailing block's amounts are in another currency: an EPS beside them in this one would be read as theirs.
148
+ const block = financials.fundamentals?.financialCurrency?.trim();
149
+ if (block && resolveCurrencyUnit(block).currency !== price.currency) return null;
150
+ const split = financials.fundamentals?.splitShareBasis?.exDate;
151
+ if (split && split > step.filed) return null;
152
+ const served = financials.fundamentals?.eps;
153
+ if (typeof served === "number" && Number.isFinite(served) && !withinServedRatio(step.eps, served)) return null;
154
+ return { eps: step.eps, periodEnd: step.periodEnd, currency: price.currency };
155
+ }
156
+
157
+ /** Same sign and within SERVED_EPS_RATIO_LIMIT of each other; a figure against a zero is not. */
158
+ function withinServedRatio(filed: number, served: number): boolean {
159
+ const ratio = filed / served;
160
+ return Number.isFinite(ratio) && ratio > 0 && ratio <= SERVED_EPS_RATIO_LIMIT && ratio >= 1 / SERVED_EPS_RATIO_LIMIT;
161
+ }
162
+
163
+ const finite = (value: number | null | undefined): value is number => typeof value === "number" && Number.isFinite(value);
164
+ const positive = (value: number | null | undefined): value is number => finite(value) && value > 0;
165
+
166
+ /**
167
+ * The financials with their headline trailing EPS and P/E on the filed
168
+ * quarters (filedTrailingEps): `fundamentals.eps` is that figure and
169
+ * `trailingPE` the quote's price over it, so every view that moves the P/E to
170
+ * the regular session price (atRegularSessionPrice, liveTrailingPE) reads the
171
+ * multiple PEB and GE read. Without one, Gloom Cloud's trailing EPS and P/E
172
+ * stand, marked `served`, as does a P/E it serves with no EPS (a fund's). A
173
+ * block with neither is left as it is.
174
+ */
175
+ export function withFiledTrailingEps<T extends TrailingEpsInputs>(financials: T, now = Date.now()): T {
176
+ const fundamentals = financials.fundamentals;
177
+ if (!fundamentals) return financials;
178
+ const filed = filedTrailingEps(financials, now);
179
+ if (!filed) {
180
+ const served = finite(fundamentals.eps) || finite(fundamentals.trailingPE);
181
+ if (!served || (fundamentals.trailingEpsSource === "served" && fundamentals.trailingEpsPeriodEnd === undefined)) return financials;
182
+ const { trailingEpsPeriodEnd: _period, ...rest } = fundamentals;
183
+ return { ...financials, fundamentals: { ...rest, trailingEpsSource: "served" } };
184
+ }
185
+ const { trailingPEConversion: _conversion, epsCurrency: _currency, trailingPE: _served, ...rest } = fundamentals;
186
+ // filedTrailingEps reads a quote's currency, so there is a quote; its price is the one atRegularSessionPrice moves from.
187
+ const price = financials.quote!.price;
188
+ const next: Fundamentals = {
189
+ ...rest,
190
+ eps: filed.eps,
191
+ // A loss leaves no multiple: priceEarningsOnEarnings reads the EPS as not meaningful.
192
+ ...(positive(price) && filed.eps > 0 ? { trailingPE: price / filed.eps } : {}),
193
+ trailingEpsSource: "filed-quarters",
194
+ trailingEpsPeriodEnd: filed.periodEnd,
195
+ };
196
+ return { ...financials, fundamentals: next };
197
+ }
@@ -502,6 +502,47 @@ function valuationStatements(financials: TickerFinancials): { quarterly: Financi
502
502
  return { quarterly: financials.quarterlyStatements.map(knownWhenFiled), annual: financials.annualStatements.map(knownWhenFiled) };
503
503
  }
504
504
 
505
+ /**
506
+ * A fiscal fourth quarter's EPS stated without a filing date of its own (a
507
+ * 10-K states the year, the results release the quarter) was public once its
508
+ * year's filing was, so it is dated by that filing. The daily P/E of the
509
+ * quarters after it then sums the same four quarters PEB and the headline EPS
510
+ * (TTM) sum (time-series/filed-trailing-eps), rather than reading the quarter
511
+ * from the year and landing a cent of rounding away from them. The window that
512
+ * ends with the year takes the year's own EPS (fiscalYearEpsAtYearEnd).
513
+ */
514
+ function fourthQuarterEpsAtYearFiling(row: FinancialStatement, annual: readonly FinancialStatement[]): FinancialStatement {
515
+ if (!isFiniteNumber(row.eps) || hasStatementGap(row, "eps") || statementFieldAvailability(row, "eps")) return row;
516
+ const year = annual.find((candidate) => candidate.currency === row.currency && areNearbyFinancialPeriodEnds(candidate.date, row.date));
517
+ const filed = year && isFiniteNumber(year.eps) ? statementFieldAvailability(year, "eps") : undefined;
518
+ return filed ? { ...row, fieldAvailability: { ...row.fieldAvailability, eps: filed } } : row;
519
+ }
520
+
521
+ /**
522
+ * At a fiscal year end the filed year's diluted EPS is the daily trailing
523
+ * figure, as PEB and the headline EPS (TTM) read it (filed-trailing-eps): four
524
+ * quarters need not add up to their year (3.90 for the year beside four
525
+ * quarters of 1.00), so their sum stands only where the year gives no dated EPS.
526
+ */
527
+ function fiscalYearEpsAtYearEnd(statements: readonly InternalStatement[], annual: readonly FinancialStatement[]): InternalStatement[] {
528
+ return statements.map((statement) => {
529
+ const year = annual.find((row) => row.currency === statement.currency && areNearbyFinancialPeriodEnds(row.date, statement.date));
530
+ const eps = year && !hasStatementGap(year, "eps") ? statementNumber(year, "eps") : null;
531
+ if (!year || eps === null) return statement;
532
+ const { epsBasis: _sum, ...rest } = statement;
533
+ const fieldAvailability = { ...statement.fieldAvailability };
534
+ const filed = statementFieldAvailability(year, "eps");
535
+ if (filed) fieldAvailability.eps = filed;
536
+ else delete fieldAvailability.eps;
537
+ const next: InternalStatement = {
538
+ ...rest, eps, fieldAvailability, ...(year.epsBasis ? { epsBasis: year.epsBasis } : {}),
539
+ __timeSeriesDerivedFields: [...new Set([...(statement.__timeSeriesDerivedFields ?? []), "eps" as const])],
540
+ };
541
+ next.availableAt = completeStatementAvailability(next) ?? statement.availableAt;
542
+ return next;
543
+ });
544
+ }
545
+
505
546
  /** An undated vendor value cannot block a derivation from dated filings. */
506
547
  function datedValuationStatement(statement: FinancialStatement): FinancialStatement {
507
548
  const dated = { ...statement };
@@ -530,6 +571,8 @@ function sourceStatements(
530
571
  const venue = financials.quote?.listingExchangeName || financials.quote?.exchangeName || exchange;
531
572
  known = { quarterly: known.quarterly.map(row => statementAtRelease(row, releases, venue)),
532
573
  annual: known.annual.map(row => statementAtRelease(row, releases, venue)) };
574
+ const annualRows = known.annual;
575
+ known = { ...known, quarterly: known.quarterly.map((row) => fourthQuarterEpsAtYearFiling(row, annualRows)) };
533
576
  known = { quarterly: known.quarterly.map(datedValuationStatement), annual: known.annual.map(datedValuationStatement) };
534
577
  // A later comparative quarter may repeat a year-end balance sheet that
535
578
  // the annual filing already published. Only identical, same-currency
@@ -559,7 +602,7 @@ function sourceStatements(
559
602
  // Sparse or loss-making quarters are gaps, not permission to substitute
560
603
  // an older annual denominator. Annual-only issuers retain their years.
561
604
  if (!known.quarterly.length) return { statements: mergeStatementsByPeriod(known.annual), period: "annual" };
562
- const statements = buildTtmStatements(quarterly, known.annual);
605
+ const statements = fiscalYearEpsAtYearEnd(buildTtmStatements(quarterly, known.annual), known.annual);
563
606
  // Capitalization uses the latest reported share count, not an average
564
607
  // of four quarters. Keep TTM average shares for the income/EPS fallback.
565
608
  if (valuationMetric !== "trailingPE") for (const statement of statements) {
@@ -17,6 +17,9 @@ export interface IceCreditSeries {
17
17
  tags: readonly string[];
18
18
  }
19
19
 
20
+ /** What every surface that shows an ICE series says about how far back FRED carries it. */
21
+ export const ICE_HISTORY_LIMIT_NOTICE = "History limited to 3 years by the publisher.";
22
+
20
23
  const HIGH_YIELD = ["high yield", "junk"];
21
24
 
22
25
  export const ICE_CREDIT_SERIES: readonly IceCreditSeries[] = [
@@ -10,6 +10,19 @@ import {
10
10
  } from "../market-data/market/period-return";
11
11
 
12
12
  const PRICE_COMPARISON_BASIS = "Price returns in each listing's currency; cash distributions and FX conversion excluded.";
13
+ const TOTAL_COMPARISON_BASIS = "Total returns in each listing's currency; cash distributions reinvested at the ex-date close, FX conversion excluded.";
14
+
15
+ /**
16
+ * What a comparison's returns are: price returns, or total returns when the
17
+ * chart asked for them, naming any leg whose distributions could not be read
18
+ * (it stays a price return, and the others do not wait for it).
19
+ */
20
+ function comparisonBasis(spec: ChartSpec, legs: ReadonlyArray<{ label: string; entry: ResolvedSeries | undefined }>): string {
21
+ if (spec.viewport.totalReturn !== true) return PRICE_COMPARISON_BASIS;
22
+ const priceOnly = legs.filter(({ entry }) => entry?.returnBasis === "price").map(({ label }) => label);
23
+ if (priceOnly.length === legs.length && legs.length > 0) return PRICE_COMPARISON_BASIS;
24
+ return priceOnly.length ? `${TOTAL_COMPARISON_BASIS} Price return for ${priceOnly.join(", ")}: distribution history unavailable.` : TOTAL_COMPARISON_BASIS;
25
+ }
13
26
 
14
27
  export interface PriceComparison {
15
28
  seriesIds: string[];
@@ -111,6 +124,20 @@ export function priceComparisonSeriesIds(spec: ChartSpec): string[] | null {
111
124
  return visible.map((entry) => entry.id);
112
125
  }
113
126
 
127
+ /**
128
+ * What a total-return chart's price levels are when it is not a comparison (the
129
+ * raw price axis): prices with cash distributions reinvested, not what the
130
+ * listing quotes. Null for a price chart, and when no leg is a total return.
131
+ */
132
+ export function totalReturnLevelsNotice(spec: ChartSpec, series: readonly ResolvedSeries[]): string | null {
133
+ if (spec.viewport.totalReturn !== true) return null;
134
+ const legs = series.filter((entry) => entry.returnBasis !== undefined);
135
+ if (!legs.some((entry) => entry.returnBasis === "total")) return null;
136
+ const priceOnly = legs.filter((entry) => entry.returnBasis === "price").map((entry) => entry.label);
137
+ return `Total return levels in each listing's currency: prices include cash distributions reinvested at the ex-date close, so they are not quoted prices; FX conversion excluded.${
138
+ priceOnly.length ? ` Price only for ${priceOnly.join(", ")}: distribution history unavailable.` : ""}`;
139
+ }
140
+
114
141
  interface ComparedLeg {
115
142
  id: string;
116
143
  label: string;
@@ -152,7 +179,8 @@ export function resolvePriceComparison(
152
179
  return source?.kind === "security" ? source.instrument.symbol : id;
153
180
  };
154
181
  // A leg not loaded yet (a seed from cached history) has not failed: nothing compares until it is.
155
- const unavailable = `${PRICE_COMPARISON_BASIS} Comparison unavailable: need two shared dates and a nonzero baseline.`;
182
+ const basis = comparisonBasis(spec, seriesIds.map((id) => ({ label: symbolOf(id), entry: byId.get(id) })));
183
+ const unavailable = `${basis} Comparison unavailable: need two shared dates and a nonzero baseline.`;
156
184
  if (seriesIds.some((id) => !byId.has(id))) return { seriesIds, start: null, end: null, notice: unavailable };
157
185
  const dropped: NonNullable<PriceComparison["dropped"]> = [];
158
186
  const reported = new Set<string>();
@@ -259,7 +287,7 @@ export function resolvePriceComparison(
259
287
  sourceBounds,
260
288
  legs: legDays,
261
289
  ...(dropped.length ? { dropped } : {}),
262
- notice: `${PRICE_COMPARISON_BASIS} ${span}${droppedText}`,
290
+ notice: `${basis} ${span}${droppedText}`,
263
291
  };
264
292
  }
265
293
 
@@ -3,7 +3,7 @@ import type { PriceHistoryResult } from "../types/price-history";
3
3
  import { LiveBarAccumulator } from "./live-bars";
4
4
  import type { ChartResolutionSupport, ManualChartResolution } from "./resolution";
5
5
  import type { DataProvider, MarketDataRequestContext } from "../types/data-provider";
6
- import type { EarningsAction, Quote, QuoteMetadata, TickerFinancials } from "../types/financials";
6
+ import type { CorporateActionsData, EarningsAction, Quote, QuoteMetadata, TickerFinancials } from "../types/financials";
7
7
  import type { EarningsHistoryPayload } from "../api-client/earnings";
8
8
  import type { FredSeriesLoadResult, FredSeriesRequest } from "../sources/gloomberb-cloud/fred-series";
9
9
  import { chartQuoteOverrideKeyForSource } from "./live-quotes";
@@ -78,6 +78,8 @@ export interface LiveTailRequest {
78
78
  export class ChartResolveCache {
79
79
  readonly financialsByInstrument = new Map<string, Promise<TickerFinancials | null>>();
80
80
  readonly earningsReleasesByInstrument = new Map<string, Promise<EarningsAction[]>>();
81
+ /** Each listing's cash distribution history, for the charts that draw total returns. */
82
+ readonly distributionsByInstrument = new Map<string, Promise<CorporateActionsData | null>>();
81
83
  readonly quoteMetadataByInstrument = new Map<string, Promise<QuoteMetadata | null>>();
82
84
  /** Each futures symbol's contract price unit, asked once per chart once known. */
83
85
  readonly futuresPriceUnits = new Map<string, Promise<string | null>>();
@@ -23,7 +23,7 @@ import {
23
23
  import { aggregatePriceBars, futuresOpenMinuteOf, withBuiltBarSize } from "./bar-aggregation";
24
24
  import { TIME_RANGES, type TimeRange } from "./range";
25
25
  import type { DataProvider } from "../types/data-provider";
26
- import type { EarningsAction, Quote, TickerFinancials } from "../types/financials";
26
+ import type { CorporateActionsData, EarningsAction, Quote, TickerFinancials } from "../types/financials";
27
27
  import { mergeQuoteMetadata, quoteMetadataFromQuote, quoteMetadataMatchesTarget } from "../market-data/quotes/metadata";
28
28
  import type { FredSeriesRequest } from "../sources/gloomberb-cloud/fred-series";
29
29
  import { fredCreditCoverageNotice } from "./economic";
@@ -44,12 +44,14 @@ import { VALUATION_CURRENCY_WARNING_PREFIX } from "./valuation-currency";
44
44
  import { chartPriceHistoryIntegrityNotices } from "./market";
45
45
  import { maxStudyWarmupPoints, resolveStudies } from "./studies";
46
46
  import { clipSeriesToWindow } from "./alignment";
47
+ import { totalReturnSeries } from "./total-return";
47
48
  import {
48
49
  bufferPriceComparison,
49
50
  clipPriceComparison,
50
51
  priceComparisonBoundsForSeries,
51
52
  priceComparisonSeriesIds,
52
53
  resolvePriceComparison,
54
+ totalReturnLevelsNotice,
53
55
  } from "./price-comparison";
54
56
  import { chartQuoteOverrideKeyForSource } from "./live-quotes";
55
57
  import { chartSeriesSourceKey } from "../capabilities/chart-series";
@@ -266,6 +268,13 @@ export async function resolveChartSpecData(
266
268
  return result;
267
269
  }
268
270
 
271
+ /** The price fields whose returns a distribution adds to. */
272
+ function totalReturnFieldId(fieldId: string): boolean {
273
+ const canonical = getTimeSeriesField(fieldId)?.id ?? fieldId;
274
+ return canonical === "market.ohlcv" || canonical === "market.close" || canonical === "market.open"
275
+ || canonical === "market.high" || canonical === "market.low";
276
+ }
277
+
269
278
  async function resolveChartSpecPass(
270
279
  spec: ChartSpec,
271
280
  sources: ChartResolveSources,
@@ -277,6 +286,7 @@ async function resolveChartSpecPass(
277
286
  && entry.source.kind === "security" && isFundamentalFieldId(entry.source.fieldId))
278
287
  ? [FINANCIAL_VINTAGE_NOTICE] : [];
279
288
  const priorityWarnings: string[] = [];
289
+ const coverageNotices: string[] = [];
280
290
  const delistings = new Map<string, { symbol: string; label: string; notice: string }>();
281
291
  const baseSeriesIds = new Set(spec.series.map((entry) => entry.id));
282
292
  const visibleSeriesIds = new Set(spec.series
@@ -357,6 +367,26 @@ async function resolveChartSpecPass(
357
367
  }
358
368
  return pending;
359
369
  };
370
+ // Total returns reinvest the cash a listing paid; each listing's history is read once per chart.
371
+ const loadDistributions = (source: Extract<ChartSeriesSpec["source"], { kind: "security" }>) => {
372
+ const key = instrumentKey(source);
373
+ let pending = cache.distributionsByInstrument.get(key);
374
+ if (!pending) {
375
+ pending = (async (): Promise<CorporateActionsData | null> => {
376
+ const read = sources.dataProvider?.getCorporateActions;
377
+ if (!read) return null;
378
+ try {
379
+ return await read.call(sources.dataProvider, source.instrument.symbol, source.instrument.exchange, requestContext(source));
380
+ } catch (error) {
381
+ cache.distributionsByInstrument.delete(key);
382
+ throw error;
383
+ }
384
+ })();
385
+ cache.distributionsByInstrument.set(key, pending);
386
+ cache.revision += 1;
387
+ }
388
+ return pending;
389
+ };
360
390
  const loadQuoteMetadata = (source: Extract<ChartSeriesSpec["source"], { kind: "security" }>) => {
361
391
  const key = instrumentKey(source);
362
392
  let pending = cache.quoteMetadataByInstrument.get(key);
@@ -527,6 +557,12 @@ async function resolveChartSpecPass(
527
557
  const sourceResolution = dailyValuation ? "1d" : initialResolution;
528
558
  const maxRange = getSupportMaxRange(support, sourceResolution);
529
559
  const ahead = movedWindow ? barsAhead(requestVisibleBounds, maxRange, referenceNow.getTime()) : 0;
560
+ // Bars ahead that reach the present end wherever the clock is on this pass.
561
+ // Such a window is named by its start and the present, so every pass shares
562
+ // one acquisition and the stale-copy refresh below keeps it current. Named
563
+ // by the clock, each live pass fetched and kept another copy of the history.
564
+ const toPresent = ahead > 0 && requestVisibleBounds.end !== null
565
+ && requestVisibleBounds.end + ahead >= referenceNow.getTime();
530
566
  const durationEnd = rangeDurationBounds(spec, initialCalculationBounds).end;
531
567
  const historyBounds = clampHistoryBoundsToSupport(
532
568
  ahead > 0 && initialCalculationBounds.end !== null
@@ -560,7 +596,9 @@ async function resolveChartSpecPass(
560
596
  request.allowProviderDefaultFallback ? "auto" : "manual",
561
597
  isMarketFieldId(source.fieldId) ? source.period ?? "auto" : "auto",
562
598
  ...(request.explicitWindow
563
- ? [request.bounds.start ?? "open", request.bounds.end ?? "open"]
599
+ ? toPresent
600
+ ? [initialCalculationBounds.start ?? "open", "present"]
601
+ : [request.bounds.start ?? "open", request.bounds.end ?? "open"]
564
602
  : []),
565
603
  // A moved view can chart retained bars that the same authored dates cannot.
566
604
  ...(request.explicitWindow && !request.authoredWindow && !request.allowProviderDefaultFallback ? ["moved"] : []),
@@ -803,7 +841,10 @@ async function resolveChartSpecPass(
803
841
  const result = baseEconomicSeries(seriesSpec, fred, index);
804
842
  const coverageNotice = fredCreditCoverageNotice(seriesSpec.source.seriesId, fred.data.info,
805
843
  requestVisibleBounds.start, fred.data.observations);
806
- if (result && coverageNotice) priorityWarnings.push(`${result.label}: ${coverageNotice}`);
844
+ if (result && coverageNotice) {
845
+ priorityWarnings.push(`${result.label}: ${coverageNotice}`);
846
+ coverageNotices.push(`${result.label}: ${coverageNotice}`);
847
+ }
807
848
  const freshnessWarning = staleFredWarning(fred);
808
849
  if (result && freshnessWarning) priorityWarnings.push(`${result.label}: ${freshnessWarning}`);
809
850
  return result;
@@ -927,7 +968,13 @@ async function resolveChartSpecPass(
927
968
  contractUnit,
928
969
  );
929
970
  if (!base) throw new Error(`Unknown field ${source.fieldId}.`);
930
- const result = builtBars ? withBuiltBarSize(base, builtBars) : base;
971
+ let result = builtBars ? withBuiltBarSize(base, builtBars) : base;
972
+ if (spec.viewport.totalReturn === true && totalReturnFieldId(source.fieldId)) {
973
+ const outcome = await totalReturnSeries(result, () => loadDistributions(resolvedSource),
974
+ merged.quote?.currency || quoteMetadata?.currency, referenceNow);
975
+ result = outcome.series;
976
+ if (outcome.notice) warnings.push(`${instrumentLabel(resolvedSource)}: ${outcome.notice}`);
977
+ }
931
978
  if (marketField && history?.historyStart !== undefined && Number.isFinite(history.historyStart)) {
932
979
  result.historyStart = new Date(history.historyStart);
933
980
  }
@@ -1073,6 +1120,9 @@ async function resolveChartSpecPass(
1073
1120
  bounds, comparisonResolution, requestBounds ? null : spec.viewport.dateWindow ?? null,
1074
1121
  );
1075
1122
  if (priceComparison) warnings.push(priceComparison.notice);
1123
+ // A raw-price chart of total returns is no comparison, and its levels say what they are instead.
1124
+ const returnBasisNotice = priceComparison ? null : totalReturnLevelsNotice(spec, rawSeries);
1125
+ if (returnBasisNotice) warnings.push(returnBasisNotice);
1076
1126
  warnings.push(...chartPriceHistoryIntegrityNotices(priceHistoryIntegrity));
1077
1127
  warnings.push(...financialPeriodCoverageWarnings(financialPeriodCoverage(spec, resolved)));
1078
1128
  const resolvedById = new Map(resolved.map((entry) => [entry.id, entry] as const));
@@ -1130,6 +1180,8 @@ async function resolveChartSpecPass(
1130
1180
  loading: false,
1131
1181
  errors,
1132
1182
  warnings: [...new Set([...priorityWarnings, ...warnings])],
1183
+ ...(coverageNotices.length ? { coverageNotices: [...new Set(coverageNotices)] } : {}),
1184
+ ...(returnBasisNotice ? { returnBasisNotice } : {}),
1133
1185
  ...(delistings.size ? { delistings: [...delistings.values()] } : {}),
1134
1186
  viewport,
1135
1187
  resolution,
@@ -136,3 +136,14 @@ export function withSessionBar(
136
136
  }
137
137
  return points;
138
138
  }
139
+
140
+ /**
141
+ * The daily bars `history` shows as final, which alerts on a daily close read
142
+ * too: the bars as served, less the bar of a session still trading
143
+ * (withSessionBar, which `history` rebuilds from the quote and marks partial)
144
+ * and any bar the source marks partial. A close `history` prints without
145
+ * `partial` is final here, and one it prints with it is not.
146
+ */
147
+ export function finalDailyBars(points: PricePoint[], options: SessionBarOptions): PricePoint[] {
148
+ return withSessionBar(points, null, options).filter((point) => !point.partial);
149
+ }
@@ -37,7 +37,7 @@ import { isLeverageFactor } from "./leverage";
37
37
  const RANGE_SET = new Set<TimeRange>(TIME_RANGES);
38
38
  const PERIODS = new Set<SeriesPeriod>(["auto", "daily", "weekly", "monthly", "quarterly", "annual", "ttm"]);
39
39
  // `band` only stays on a capability series: a field's styles and an economic series' leave it out.
40
- const STYLES = new Set<SeriesStyle>(["line", "area", "step", "columns", "points", "candles", "ohlc", "hlc", "band"]);
40
+ const STYLES = new Set<SeriesStyle>(["line", "area", "step", "columns", "points", "candles", "hollow", "heikin-ashi", "ohlc", "hlc", "band"]);
41
41
  const ECONOMIC_STYLES = new Set<SeriesStyle>(["line", "area", "step", "columns", "points"]);
42
42
  const TRANSFORMS = new Set<SeriesTransform>(["raw", "percent", "index100", "yoy", "qoq", "log"]);
43
43
  const AXES = new Set<SeriesAxis>(["auto", "left", "right"]);
@@ -71,7 +71,7 @@ export function isDatasetChart(spec: Pick<ChartSpec, "series">): boolean {
71
71
  }
72
72
 
73
73
  export function isOhlcSeriesStyle(style: SeriesStyle): boolean {
74
- return style === "candles" || style === "ohlc" || style === "hlc";
74
+ return style === "candles" || style === "hollow" || style === "heikin-ashi" || style === "ohlc" || style === "hlc";
75
75
  }
76
76
 
77
77
  export function coerceSeriesTransformForStyle(
@@ -333,6 +333,7 @@ export function normalizeChartSpec(value: unknown, fallback: ChartSpec = DEFAULT
333
333
  ? Math.min(10_000, Math.floor(viewport.maxPoints))
334
334
  : undefined,
335
335
  ...(viewport?.extendedHours === true ? { extendedHours: true } : {}),
336
+ ...(viewport?.totalReturn === true ? { totalReturn: true } : {}),
336
337
  },
337
338
  panels,
338
339
  series,
@@ -400,15 +400,21 @@ function resolveVolume(spec: ChartStudySpec, input: ResolvedSeries, color: strin
400
400
  // No bar has volume to plot (spot FX quotes 0 for every bar; a coin's window
401
401
  // can miss every bar's), so the study adds no panel rather than an empty one.
402
402
  if (input.points.length > 0 && !input.points.some((point) => isFiniteNumber(point.volume) && point.volume !== 0)) return [];
403
+ // Left to its own colour, volume follows the candles: the price's colour for
404
+ // a bar that rose, the falling colour for one that fell. A colour picked for it stays flat.
405
+ const directional = !spec.color;
403
406
  const points = [...input.points]
404
407
  .sort((left, right) => left.date.getTime() - right.date.getTime())
405
408
  .flatMap((point) => isFiniteNumber(point.volume)
406
- ? [derivedPoint({ point, value: point.volume }, point.volume)]
409
+ ? [{
410
+ ...derivedPoint({ point, value: point.volume }, point.volume),
411
+ ...(directional && isFiniteNumber(point.open) && isFiniteNumber(point.close) && point.close < point.open ? { fell: true } : {}),
412
+ }]
407
413
  : []);
408
414
  return [outputSeries(spec, input, {
409
415
  label: nameInput ? `Volume ${input.label}` : "Volume",
410
416
  points,
411
- color,
417
+ color: directional ? input.color : color,
412
418
  unit: input.volumeUnit ?? "",
413
419
  unitGroup: "volume",
414
420
  style: "columns",