@gloom-sh/terminal 0.18.5 → 0.19.1

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 (570) 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 +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  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 +19 -7
  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/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  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/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -16,12 +16,12 @@ import { TIME_RANGES, type TimeRange } from "../../../../time-series/range";
16
16
  import type { PaneProps } from "../../../../types/plugin";
17
17
  import type { PricePoint } from "../../../../types/financials";
18
18
  import { colors, priceColor } from "../../../../theme/colors";
19
- import { formatCompact, formatPercent } from "../../../../utils/format";
19
+ import { displayWidth, formatCompact, formatPercent } from "../../../../utils/format";
20
20
  import { isPlainKeyboardEvent } from "../../../../utils/keyboard";
21
21
  import { publicTickerKey } from "../../../../utils/exchanges";
22
- import { quoteFormatOptions } from "../../../../market-data/market/format";
22
+ import { isPenceQuotedLine, penceFigure, quoteFormatOptions } from "../../../../market-data/market/format";
23
23
  import {
24
- barPriceDecimals, formatBarPrice, historyAllStart, historyBarsAfterHole, historyChangeBaseNote, historyMissingPeriodsNote,
24
+ barPriceDecimals, formatBarPrice, historyAllStart, historyBarsAfterHole, historyChangeBaseNote, historyMissingPeriodsNote, quotedPriceDivisor,
25
25
  } from "../../../../cli/history-rows";
26
26
  import {
27
27
  MISSING_VOLUME_TEXT, missingVolumeCount, missingVolumeNotice, pricePointValues, priceHistoryIntegrityNotice,
@@ -89,8 +89,8 @@ function formatMaybePrice(value: number | null | undefined, decimals: number, wi
89
89
  }
90
90
 
91
91
  /** The decimals the table's prices need: the one rule `history` and `fn HP` print by (barPriceDecimals). */
92
- function historicalPriceDecimals(rows: readonly HistoricalPriceRow[], assetCategory?: string): number {
93
- return barPriceDecimals(rows.map(({ point }) => point), assetCategory);
92
+ function historicalPriceDecimals(rows: readonly HistoricalPriceRow[], assetCategory?: string, priceDivisor = 1): number {
93
+ return barPriceDecimals(rows.map(({ point }) => point), assetCategory, priceDivisor);
94
94
  }
95
95
 
96
96
  /** The exported price: the digits the table shows, without the width limit. */
@@ -178,19 +178,27 @@ const HISTORY_NUMERIC_IDS = ["open", "high", "low", "close", "change", "changePe
178
178
  /**
179
179
  * Fixed date and volume columns; the price columns take the rest of the pane
180
180
  * evenly, and give up cells down to a floor when it is narrow, so the volume
181
- * stays on screen.
181
+ * stays on screen. A close with a note beside it (a London line's pence) asks
182
+ * for `closeWidth`, the widest close and note: the other columns give way to
183
+ * it first, down to their minimum, before it gives up its note.
182
184
  */
183
- function buildHistoryColumns(width: number, intraday: boolean, size: HistoryBarSize, newestDate: string): HistoryColumn[] {
185
+ function buildHistoryColumns(width: number, intraday: boolean, size: HistoryBarSize, newestDate: string, closeWidth = 0): HistoryColumn[] {
184
186
  // A month reads as `2026-10`; the newest bar, when its week or month runs on, adds `MTD` or `WTD`.
185
187
  const dateWidth = Math.max(intraday ? 16 : size === "1mo" ? 7 : 10, newestDate.length);
186
- const numeric: number[] = HISTORY_NUMERIC_IDS.map((id) => id === "changePercent" ? HISTORY_PERCENT_WIDTH : HISTORY_PRICE_WIDTH);
188
+ const numeric: number[] = HISTORY_NUMERIC_IDS.map((id) => id === "changePercent" ? HISTORY_PERCENT_WIDTH
189
+ : id === "close" ? Math.max(HISTORY_PRICE_WIDTH, closeWidth) : HISTORY_PRICE_WIDTH);
187
190
  // One gap per column plus the table's inset on both sides.
188
191
  const available = width - dateWidth - HISTORY_VOLUME_WIDTH - 8 - 2;
189
192
  let spare = available - numeric.reduce((sum, value) => sum + value, 0);
190
- for (let index = 0; spare < 0 && numeric.some((value) => value > HISTORY_NUMERIC_MIN_WIDTH); index = (index + 1) % numeric.length) {
191
- if (numeric[index]! > HISTORY_NUMERIC_MIN_WIDTH) {
192
- numeric[index]! -= 1;
193
- spare += 1;
193
+ // The close keeps the width its note asks for while another column can give way, then gives it up too.
194
+ const closeAt = HISTORY_NUMERIC_IDS.indexOf("close");
195
+ for (const closeFloor of [Math.max(HISTORY_NUMERIC_MIN_WIDTH, closeWidth), HISTORY_NUMERIC_MIN_WIDTH]) {
196
+ const floor = (index: number) => index === closeAt ? closeFloor : HISTORY_NUMERIC_MIN_WIDTH;
197
+ for (let index = 0; spare < 0 && numeric.some((value, at) => value > floor(at)); index = (index + 1) % numeric.length) {
198
+ if (numeric[index]! > floor(index)) {
199
+ numeric[index]! -= 1;
200
+ spare += 1;
201
+ }
194
202
  }
195
203
  }
196
204
  if (spare > 0) {
@@ -256,13 +264,25 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
256
264
  // Daily and longer bars are dates; only intraday bars carry a time.
257
265
  const intraday = rows.length > 0 ? rows.some((row) => row.timed) : range === "1D" || range === "1W";
258
266
  const newestRowDate = rows[0]?.date ?? "";
259
- const columns = useMemo(() => buildHistoryColumns(width, intraday, size, newestRowDate), [intraday, newestRowDate, size, width]);
260
267
  const assetCategory = quoteFormatOptions(
261
268
  financials?.quote,
262
269
  ticker?.metadata.assetCategory,
263
270
  financials?.quoteMetadata?.instrumentType,
264
271
  ).assetCategory;
265
- const priceDecimals = useMemo(() => historicalPriceDecimals(rows, assetCategory), [assetCategory, rows]);
272
+ const priceDivisor = quotedPriceDivisor(quote);
273
+ const priceDecimals = useMemo(() => historicalPriceDecimals(rows, assetCategory, priceDivisor), [assetCategory, priceDivisor, rows]);
274
+ // A London line's close reads in pence too (6.385 638.5p): the close column asks for room for the widest.
275
+ const pence = isPenceQuotedLine(quote);
276
+ const closeWidth = useMemo(() => !pence ? 0 : rows.reduce((widest, row) => {
277
+ const close = row.point.close;
278
+ const pence = penceFigure(exportPrice(close, priceDecimals), quote, priceDecimals);
279
+ // A cell more than the text, so the close never runs into the low before it.
280
+ return pence ? Math.max(widest, displayWidth(` ${formatMaybePrice(close, priceDecimals, Infinity)} ${pence}`)) : widest;
281
+ }, 0), [pence, priceDecimals, quote, rows]);
282
+ const columns = useMemo(
283
+ () => buildHistoryColumns(width, intraday, size, newestRowDate, closeWidth),
284
+ [closeWidth, intraday, newestRowDate, size, width],
285
+ );
266
286
  const boundedSelectedIdx = rows.length > 0 ? Math.min(selectedIdx, rows.length - 1) : -1;
267
287
  const cycleRange = useCallback(() => setRange((current) => nextHistoryRange(current)), [setRange]);
268
288
 
@@ -296,12 +316,17 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
296
316
  case "open":
297
317
  case "high":
298
318
  case "low":
299
- case "close":
319
+ case "close": {
320
+ const value = row.point[column.id];
321
+ // A London line's close also reads in the pence it trades in, where the column has room: 6.385 638.5p.
322
+ const pence = column.id === "close" ? penceFigure(exportPrice(value, priceDecimals), quote, priceDecimals) : null;
300
323
  return {
301
- text: formatMaybePrice(row.point[column.id], priceDecimals, column.width),
302
- value: exportPrice(row.point[column.id], priceDecimals),
324
+ text: formatMaybePrice(value, priceDecimals, column.width),
325
+ value: exportPrice(value, priceDecimals),
326
+ ...(pence ? { note: pence } : {}),
303
327
  ...(column.id === "close" ? { color: colors.textBright, attributes: TextAttributes.BOLD } : { color: colors.text }),
304
328
  };
329
+ }
305
330
  case "change":
306
331
  return { text: spanMark(formatMaybePrice(row.change, priceDecimals, column.width - (row.changeSpansGap ? 1 : 0)), row, row.change),
307
332
  value: exportPrice(row.change, priceDecimals), color: priceColor(row.change ?? 0) };
@@ -311,7 +336,7 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
311
336
  case "volume":
312
337
  return { text: formatVolume(row.point.volume, volumeMissing > 0), value: row.point.volume ?? null, color: colors.textDim };
313
338
  }
314
- }, [priceDecimals, volumeMissing]);
339
+ }, [priceDecimals, quote, volumeMissing]);
315
340
 
316
341
  // ALL starts where the source's bars do, when that is well inside the window (BTC-USD's in 2014-09).
317
342
  const oldestDate = rows.at(-1)?.exportDate;
@@ -1,3 +1,4 @@
1
+ import { logFailure } from "../../../../utils/log-failure";
1
2
  import { fetchHistoryResult, InvalidHistoryResultError } from "../../../../sources/history-result";
2
3
  import { historyRetentionNotice, isHistoryRetentionError } from "../../../../sources/history-retention";
3
4
  import {
@@ -74,8 +75,8 @@ async function sessionVenue(provider: DataProvider, symbol: string, exchange: st
74
75
  ? await provider.getQuoteMetadata(symbol, exchange)
75
76
  : await provider.getQuote(symbol, exchange);
76
77
  return metadata?.listingExchangeName ?? "";
77
- } catch {
78
- return "";
78
+ } catch (error) {
79
+ return logFailure("ticker-detail.data-panes.returns-history", "Read return session venue failed", "")(error);
79
80
  }
80
81
  }
81
82
 
@@ -17,7 +17,9 @@ import {
17
17
  import { isRoundTheClockCoin } from "../../../../utils/crypto-pair";
18
18
  import { currentFxClosureStart, isCurrencyPairListing } from "../../../../utils/fx-market-hours";
19
19
  import { pricePointValues } from "../../../../utils/price-history-integrity";
20
+ import { timeZoneCity, zonedClock } from "../../../../utils/utc-time";
20
21
  import { zonedDateKey } from "../../../../utils/zoned-date-time";
22
+ import { historyDateHeader } from "./history-bars";
21
23
 
22
24
  export type ReturnRange = "6H" | "1D" | "5D" | TimeRange;
23
25
 
@@ -347,6 +349,66 @@ export function returnBaseCloseDay(
347
349
  return period ? barLastSession(day, period, venue, now) ?? barCloseDay(day, resolution, venue) : day;
348
350
  }
349
351
 
352
+ /**
353
+ * The clock an intraday bar's time is shown on: its venue's own, where the app
354
+ * knows its sessions, so the open reads 09:30 and not 13:30; null for UTC, which
355
+ * is where a coin, a currency pair or a venue with no known zone keeps its bars.
356
+ */
357
+ function returnClockZone(venue: string | undefined): string | null {
358
+ if (!venue || isAlwaysOpenExchange(venue)) return null;
359
+ return sessionCalendarTimeZone(venue);
360
+ }
361
+
362
+ /**
363
+ * A bar as the table dates it: an intraday bar's open on its venue's clock
364
+ * (`2026-10-23 09:30`), a daily or longer bar its session day.
365
+ */
366
+ export function returnTimeText(timestamp: number, resolution: ManualChartResolution, venue: string | undefined): string {
367
+ if (!isIntradayResolution(resolution)) return barSessionDay(timestamp, venue);
368
+ const zone = returnClockZone(venue);
369
+ if (!zone) return new Date(timestamp).toISOString().slice(0, 16).replace("T", " ");
370
+ const clock = zonedClock(timestamp, zone);
371
+ return `${clock.date} ${clock.time}`;
372
+ }
373
+
374
+ /**
375
+ * What the time column is headed: the clock its times are on (`ET` for New York,
376
+ * as TAS heads it, the venue's city for another zone, else `UTC`), or what a
377
+ * daily or longer bar is dated by.
378
+ */
379
+ export function returnTimeHeader(resolution: ManualChartResolution, venue: string | undefined): string {
380
+ if (!isIntradayResolution(resolution)) return historyDateHeader(resolution)?.toUpperCase() ?? "DATE";
381
+ const zone = returnClockZone(venue);
382
+ return `TIME ${!zone ? "UTC" : zone === "America/New_York" ? "ET" : timeZoneCity(zone).toUpperCase()}`;
383
+ }
384
+
385
+ /** The figures a window's rows add up to: its total, and the bars that moved most each way. */
386
+ export interface ReturnSummary {
387
+ /** The newest row's total return, a fraction. Null when no bar has one. */
388
+ total: number | null;
389
+ best: ReturnRow | null;
390
+ worst: ReturnRow | null;
391
+ }
392
+
393
+ /**
394
+ * The window's total return (the newest priced row's, measured from the base
395
+ * close) with its best and worst interval. A single bar has no interval worth
396
+ * ranking, and a window whose every interval is the same move has no best or
397
+ * worst, so neither is named then.
398
+ */
399
+ export function summarizeReturns(rows: readonly ReturnRow[]): ReturnSummary {
400
+ // Rows are newest first.
401
+ const total = rows.find((row) => row.cumulativePercent !== null)?.cumulativePercent ?? null;
402
+ const moves = rows.filter((row) => row.intervalPercent !== null);
403
+ let best: ReturnRow | null = null;
404
+ let worst: ReturnRow | null = null;
405
+ for (const row of moves) {
406
+ if (!best || row.intervalPercent! > best.intervalPercent!) best = row;
407
+ if (!worst || row.intervalPercent! < worst.intervalPercent!) worst = row;
408
+ }
409
+ return best && worst && best.intervalPercent !== worst.intervalPercent ? { total, best, worst } : { total, best: null, worst: null };
410
+ }
411
+
350
412
  /** Whether the newest daily row is the session still trading at its venue, which its table marks. */
351
413
  export function isTodayReturnRow(row: Pick<ReturnRow, "timestamp"> | undefined, resolution: ManualChartResolution, venue: string | undefined, now: number): boolean {
352
414
  return !!row && resolution === "1d" && isIntradayDailyBar(barSessionDay(row.timestamp, venue), venue, now);
@@ -1,8 +1,10 @@
1
+ import { logFailure } from "../../../../utils/log-failure";
1
2
  import { useCallback, useEffect, useMemo, useState } from "react";
2
3
  import { TextAttributes } from "../../../../ui";
3
4
  import {
4
5
  DataTableView,
5
6
  QueryBar,
7
+ StatGrid,
6
8
  loadingText,
7
9
  unavailableText,
8
10
  usePaneNoticeFooter,
@@ -12,11 +14,13 @@ import {
12
14
  type DataTableColumn,
13
15
  type PaneFooterSegment,
14
16
  type PaneHint,
17
+ type StatItem,
15
18
  } from "../../../../components";
16
19
  import { usePaneRefreshKey } from "../../../../components/data-table/table-pane";
17
20
  import { CHART_RESOLUTIONS } from "../../../../time-series/range";
18
21
  import {
19
22
  getChartResolutionLabel,
23
+ isIntradayResolution,
20
24
  type ChartResolutionSupport,
21
25
  type ManualChartResolution,
22
26
  } from "../../../../time-series/resolution";
@@ -25,7 +29,7 @@ import { cleanFloat32Price, historyPriceDecimals } from "../../../../cli/history
25
29
  import { formatPriceObservation, quoteFormatOptions } from "../../../../market-data/market/format";
26
30
  import { usePaneTicker } from "../../../../state/app/context";
27
31
  import type { PaneProps } from "../../../../types/plugin";
28
- import { colors, priceColor } from "../../../../theme/colors";
32
+ import { colors, shownPriceColor } from "../../../../theme/colors";
29
33
  import { formatPercent } from "../../../../utils/format";
30
34
  import { publicTickerKey } from "../../../../utils/exchanges";
31
35
  import { priceHistoryIntegrityNotice } from "../../../../utils/price-history-integrity";
@@ -39,9 +43,12 @@ import {
39
43
  effectiveReturnResolution,
40
44
  isTodayReturnRow,
41
45
  returnBaseCloseDay,
46
+ returnTimeHeader,
47
+ returnTimeText,
42
48
  RETURN_RANGES,
43
49
  type ReturnRange,
44
50
  type ReturnRow,
51
+ summarizeReturns,
45
52
  } from "./returns-model";
46
53
  import { loadReturnSeries, type ReturnSeries } from "./returns-history";
47
54
  import { formingHistoryBar, formingHistoryBarText, historyBarDate } from "./history-bars";
@@ -64,12 +71,6 @@ const COLUMNS: ReturnColumn[] = [
64
71
  { id: "cumulativePercent", label: "TOTAL %", width: 9, align: "right", flexGrow: 1 },
65
72
  ];
66
73
 
67
- function formatDateTime(date: Date): string {
68
- const iso = date.toISOString();
69
- const hasTime = date.getUTCHours() !== 0 || date.getUTCMinutes() !== 0 || date.getUTCSeconds() !== 0;
70
- return hasTime ? iso.slice(0, 16).replace("T", " ") : iso.slice(0, 10);
71
- }
72
-
73
74
  /** Every price and dollar change at one decimal count, so the columns line up. */
74
75
  function returnPriceDecimals(rows: readonly ReturnRow[], assetCategory?: string): number {
75
76
  return historyPriceDecimals(rows.flatMap((row) => row.price === null ? [] : [{
@@ -95,6 +96,12 @@ function formatMaybePercent(value: number | null): string {
95
96
  return value === null ? "-" : formatPercent(value);
96
97
  }
97
98
 
99
+ /** A move's tone as it is printed: one that rounds to 0.00% is neutral, never red or green. */
100
+ function moveTone(fraction: number): StatItem["tone"] {
101
+ const shown = Number((fraction * 100).toFixed(2));
102
+ return shown > 0 ? "positive" : shown < 0 ? "negative" : "neutral";
103
+ }
104
+
98
105
  function sortValue(row: ReturnRow, id: ReturnColumnId): number | null {
99
106
  switch (id) {
100
107
  case "time":
@@ -127,7 +134,7 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
127
134
  symbol && dataProvider?.getChartResolutionSupport
128
135
  ? dataProvider.getChartResolutionSupport(symbol, exchange)
129
136
  : NO_SUPPORT,
130
- ).catch(() => NO_SUPPORT), [dataProvider, exchange, symbol]);
137
+ ).catch(logFailure("ticker-detail.data-panes.returns-pane", "Read return interval support failed", NO_SUPPORT)), [dataProvider, exchange, symbol]);
131
138
  const [support, setSupport] = useState<readonly ChartResolutionSupport[] | null>(null);
132
139
  useEffect(() => {
133
140
  let cancelled = false;
@@ -162,15 +169,20 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
162
169
  () => data ? buildReturnRows(data.points, data.visibleFrom, data.visibleTo) : [],
163
170
  [data],
164
171
  );
172
+ const barSize = data?.resolution ?? shownResolution;
173
+ const venue = data?.venue || undefined;
174
+ // The times are dated on the venue's own clock, which the column's header names.
175
+ const timeHeader = returnTimeHeader(barSize, venue);
176
+ const columns = useMemo(() => COLUMNS.map((column) => column.id === "time" ? { ...column, label: timeHeader } : column), [timeHeader]);
165
177
  const table = useMemo(() => {
166
178
  const searchable = returnRows.length >= SEARCH_MIN_ROWS;
167
179
  const needle = searchable ? query.trim().toLowerCase() : "";
168
180
  const rows = (needle
169
- ? returnRows.filter((row) => formatDateTime(new Date(row.timestamp)).toLowerCase().includes(needle))
181
+ ? returnRows.filter((row) => returnTimeText(row.timestamp, barSize, venue).includes(needle))
170
182
  : returnRows.slice()
171
183
  ).sort((left, right) => compareSortValues(sortValue(left, sort.columnId), sortValue(right, sort.columnId), sort.direction));
172
184
  return { rows, searchable };
173
- }, [query, returnRows, sort]);
185
+ }, [barSize, query, returnRows, sort, venue]);
174
186
  // The quote knows the instrument type even when the saved ticker does not,
175
187
  // so prices take the same decimals as Historical Prices.
176
188
  const { financials } = usePaneTicker();
@@ -199,7 +211,6 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
199
211
  // Today's daily bar is the session still trading, marked as Historical Prices marks it.
200
212
  const dayToDate = !!data && isTodayReturnRow(returnRows[0], data.resolution, data.venue || undefined, Date.now());
201
213
  const formingKey = forming || dayToDate ? returnRows[0]?.key : undefined;
202
- const barSize = data?.resolution ?? shownResolution;
203
214
  // The close every total is measured from: `from Mon 21 Sep close`.
204
215
  const baseText = useMemo(() => {
205
216
  const day = data && returnRows.length ? returnBaseCloseDay(data.points, data.visibleFrom, data.resolution, data.venue || undefined, Date.now()) : null;
@@ -225,6 +236,29 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
225
236
  title: "Price history data",
226
237
  });
227
238
 
239
+ // The window's total and its best and worst interval, over every bar it holds, whatever the search narrows the table to.
240
+ const summary = useMemo<StatItem[]>(() => {
241
+ const { total, best, worst } = summarizeReturns(returnRows);
242
+ const moveItem = (id: string, label: string, row: ReturnRow | null): StatItem[] => {
243
+ if (row?.intervalPercent == null) return [];
244
+ const when = historyBarDate(returnTimeText(row.timestamp, barSize, venue), barSize, false);
245
+ return [{
246
+ id,
247
+ label,
248
+ value: formatPercent(row.intervalPercent),
249
+ tone: moveTone(row.intervalPercent),
250
+ detail: when,
251
+ // A narrow pane keeps the day without its year, so the three figures stay on one row.
252
+ ...isIntradayResolution(barSize) ? { detailVariants: [when.slice(5)] } : {},
253
+ }];
254
+ };
255
+ return [
256
+ ...total === null ? [] : [{ id: "total", label: "Total", value: formatPercent(total), tone: moveTone(total) }],
257
+ ...moveItem("best", "Best", best),
258
+ ...moveItem("worst", "Worst", worst),
259
+ ];
260
+ }, [barSize, returnRows, venue]);
261
+
228
262
  const chooseSort = useCallback((id: string) => {
229
263
  setSort((current) => nextHeaderSort(current, id as ReturnColumnId, { firstDirection: "desc" }));
230
264
  }, []);
@@ -232,10 +266,10 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
232
266
  const renderCell = useCallback((row: ReturnRow, column: ReturnColumn): DataTableCell => {
233
267
  switch (column.id) {
234
268
  case "time": {
235
- const shown = formatDateTime(new Date(row.timestamp));
269
+ const shown = returnTimeText(row.timestamp, barSize, venue);
236
270
  // A daily or longer bar is a date; a month's reads as the month, and one still running says so.
237
271
  return {
238
- text: shown.length > 10 ? shown : historyBarDate(shown, barSize, row.key === formingKey),
272
+ text: isIntradayResolution(barSize) ? shown : historyBarDate(shown, barSize, row.key === formingKey),
239
273
  value: new Date(row.timestamp).toISOString(),
240
274
  color: colors.textDim,
241
275
  };
@@ -243,15 +277,15 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
243
277
  case "price":
244
278
  return { text: formatMaybePrice(row.price, priceDecimals, column.width), value: exportPrice(row.price, priceDecimals), color: colors.textBright, attributes: TextAttributes.BOLD };
245
279
  case "intervalChange":
246
- return { text: formatMaybePrice(row.intervalChange, priceDecimals, column.width), value: exportPrice(row.intervalChange, priceDecimals), color: priceColor(row.intervalChange ?? 0) };
280
+ return { text: formatMaybePrice(row.intervalChange, priceDecimals, column.width), value: exportPrice(row.intervalChange, priceDecimals), color: shownPriceColor(row.intervalChange ?? 0, priceDecimals) };
247
281
  case "intervalPercent":
248
- return { text: formatMaybePercent(row.intervalPercent), value: row.intervalPercent === null ? null : row.intervalPercent * 100, color: priceColor(row.intervalPercent ?? 0) };
282
+ return { text: formatMaybePercent(row.intervalPercent), value: row.intervalPercent === null ? null : row.intervalPercent * 100, color: shownPriceColor((row.intervalPercent ?? 0) * 100, 2) };
249
283
  case "cumulativeChange":
250
- return { text: formatMaybePrice(row.cumulativeChange, priceDecimals, column.width), value: exportPrice(row.cumulativeChange, priceDecimals), color: priceColor(row.cumulativeChange ?? 0) };
284
+ return { text: formatMaybePrice(row.cumulativeChange, priceDecimals, column.width), value: exportPrice(row.cumulativeChange, priceDecimals), color: shownPriceColor(row.cumulativeChange ?? 0, priceDecimals) };
251
285
  case "cumulativePercent":
252
- return { text: formatMaybePercent(row.cumulativePercent), value: row.cumulativePercent === null ? null : row.cumulativePercent * 100, color: priceColor(row.cumulativePercent ?? 0) };
286
+ return { text: formatMaybePercent(row.cumulativePercent), value: row.cumulativePercent === null ? null : row.cumulativePercent * 100, color: shownPriceColor((row.cumulativePercent ?? 0) * 100, 2) };
253
287
  }
254
- }, [barSize, formingKey, priceDecimals]);
288
+ }, [barSize, formingKey, priceDecimals, venue]);
255
289
 
256
290
  return (
257
291
  <DataTableView<ReturnRow, ReturnColumn>
@@ -259,7 +293,7 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
259
293
  selection={{ kind: "none" }}
260
294
  rootWidth={width}
261
295
  rootHeight={height}
262
- columns={COLUMNS}
296
+ columns={columns}
263
297
  items={table.rows}
264
298
  sortColumnId={sort.columnId}
265
299
  sortDirection={sort.direction}
@@ -268,33 +302,36 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
268
302
  renderCell={renderCell}
269
303
  selectedTextOverridesCellColor
270
304
  rootBefore={(
271
- <QueryBar
272
- width={width}
273
- search={table.searchable ? {
274
- value: query,
275
- onChange: setQuery,
276
- placeholder: "date or time",
277
- focused,
278
- ...searchProps,
279
- } : undefined}
280
- filters={[{
281
- id: "range",
282
- label: "Range",
283
- value: range,
284
- options: RETURN_RANGES.map((value) => ({ value, label: value })),
285
- onChange: setRange,
286
- }, {
287
- id: "interval",
288
- label: "Interval",
289
- value: shownResolution,
290
- options: INTERVAL_OPTIONS.map((value) => ({
291
- value,
292
- label: getChartResolutionLabel(value),
293
- disabled: support !== null && !availableResolutions.has(value),
294
- })),
295
- onChange: setResolution,
296
- }]}
297
- />
305
+ <>
306
+ <QueryBar
307
+ width={width}
308
+ search={table.searchable ? {
309
+ value: query,
310
+ onChange: setQuery,
311
+ placeholder: "date or time",
312
+ focused,
313
+ ...searchProps,
314
+ } : undefined}
315
+ filters={[{
316
+ id: "range",
317
+ label: "Range",
318
+ value: range,
319
+ options: RETURN_RANGES.map((value) => ({ value, label: value })),
320
+ onChange: setRange,
321
+ }, {
322
+ id: "interval",
323
+ label: "Interval",
324
+ value: shownResolution,
325
+ options: INTERVAL_OPTIONS.map((value) => ({
326
+ value,
327
+ label: getChartResolutionLabel(value),
328
+ disabled: support !== null && !availableResolutions.has(value),
329
+ })),
330
+ onChange: setResolution,
331
+ }]}
332
+ />
333
+ <StatGrid items={summary} width={width} />
334
+ </>
298
335
  )}
299
336
  getExportMetadata={() => [
300
337
  ["Ticker", symbol ? publicTickerKey(symbol, exchange) : ""],
@@ -4,7 +4,7 @@ import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, quoteHeadlineTime, type
4
4
  import type { HeadlessPaneColumn, HeadlessPaneDefinition, HeadlessPaneFreshness, HeadlessPaneOptionValues } from "../../../types/headless";
5
5
  import type { TimeRange } from "../../../time-series/range";
6
6
  import { formatNumber, formatPercentRaw } from "../../../utils/format";
7
- import { formatMarketPrice, formatMarketPriceWithCurrency, formatPriceObservation, quoteFormatOptions, withCurrencyMinorDigits, type MarketFormatOptions } from "../../../market-data/market/format";
7
+ import { formatMarketPrice, formatMarketPriceWithCurrency, formatPriceObservation, penceFigure, quoteFormatOptions, withCurrencyMinorDigits, type MarketFormatOptions } from "../../../market-data/market/format";
8
8
  import {
9
9
  MISSING_VOLUME_TEXT, missingVolumeCount, missingVolumeNotice, pricePointValues, priceHistoryIntegrityNotice,
10
10
  } from "../../../utils/price-history-integrity";
@@ -36,7 +36,8 @@ import {
36
36
  } from "../shared/headless-market-data";
37
37
  import { barHistoryFreshness, quoteFreshnessFields, REPORTED_DATA } from "../shared/report-freshness";
38
38
  import {
39
- BAR_INTERVAL_LABELS, barPriceDecimals, cleanFloat32Price, formatBarPrice, historyAllStart, historyAllStartNote, historyMissingPeriodsNote, historyPriceDecimals,
39
+ BAR_INTERVAL_LABELS, barPriceDecimals, cleanBarPrice, cleanFloat32Price, formatBarPrice, historyAllStart, historyAllStartNote, historyMissingPeriodsNote, historyPriceDecimals,
40
+ quotedPriceDivisor,
40
41
  } from "../../../cli/history-rows";
41
42
  import { formatUtcTime } from "../../../utils/utc-time";
42
43
  import { maxDrawdown, maxDrawdownText } from "../../../utils/max-drawdown";
@@ -197,7 +198,7 @@ async function loadRatioReport(
197
198
  const history = oldest && tab.key === "valuation"
198
199
  ? await resolveHeadlessInstrument(ctx, symbol)
199
200
  .then(({ symbol: bare, exchange }) => loadPeriodEndHistory(ctx.marketData, bare, exchange ?? "", oldest))
200
- .catch(() => null)
201
+ .catch(logFailure("ticker-detail.headless", "Load valuation price history failed", null))
201
202
  : null;
202
203
  const table = hasRequestedStatements ? ratioTableForFinancials(financials, tab, period, history) : null;
203
204
  const statementCurrency = financialStatementCurrency(financials, [
@@ -339,12 +340,16 @@ export const quoteComparisonHeadless: HeadlessPaneDefinition<"rows"> = {
339
340
  * report declares before it has bars keep every digit the source sent. A bar without
340
341
  * volume in a history that reports it (`volumeMissing`) prints it as missing, never as 0.
341
342
  */
342
- const historyPriceColumns = (volumeMissing: boolean, decimals?: number): HeadlessPaneColumn[] => ["open", "high", "low", "close", "volume"].map((key) => ({
343
+ const historyPriceColumns = (volumeMissing: boolean, decimals?: number, pence?: (close: number) => string | null): HeadlessPaneColumn[] => ["open", "high", "low", "close", "volume"].map((key) => ({
343
344
  key, header: key, align: "right" as const,
344
- format: (value: unknown) => value == null || !Number.isFinite(Number(value)) ? key === "volume" && volumeMissing ? MISSING_VOLUME_TEXT : "-"
345
- : key === "volume" ? formatNumber(Number(value), 0)
346
- : decimals == null ? formatPriceObservation(Number(value), { minimumFractionDigits: 2 })
347
- : formatBarPrice(Number(value), decimals),
345
+ format: (value: unknown) => {
346
+ if (value == null || !Number.isFinite(Number(value))) return key === "volume" && volumeMissing ? MISSING_VOLUME_TEXT : "-";
347
+ if (key === "volume") return formatNumber(Number(value), 0);
348
+ if (decimals == null) return formatPriceObservation(Number(value), { minimumFractionDigits: 2 });
349
+ // A London line's close also reads in pence, as the pane's close notes it: 6.385 638.5p.
350
+ const note = key === "close" ? pence?.(Number(value)) : null;
351
+ return `${formatBarPrice(Number(value), decimals)}${note ? ` ${note}` : ""}`;
352
+ },
348
353
  }));
349
354
 
350
355
  /**
@@ -353,7 +358,14 @@ const historyPriceColumns = (volumeMissing: boolean, decimals?: number): Headles
353
358
  * whose period runs on (`2026-10 MTD`); the export keeps each bar's own date
354
359
  * and adds a `Partial` column when one is.
355
360
  */
356
- function historyColumns(size: HistoryBarSize, partial: boolean, text: boolean, volumeMissing = false, decimals?: number): HeadlessPaneColumn[] {
361
+ function historyColumns(
362
+ size: HistoryBarSize,
363
+ partial: boolean,
364
+ text: boolean,
365
+ volumeMissing = false,
366
+ decimals?: number,
367
+ pence?: (close: number) => string | null,
368
+ ): HeadlessPaneColumn[] {
357
369
  const daily = isDailyOrLongerBar(size);
358
370
  const date: HeadlessPaneColumn = {
359
371
  key: "date",
@@ -365,7 +377,7 @@ function historyColumns(size: HistoryBarSize, partial: boolean, text: boolean, v
365
377
  const marked: HeadlessPaneColumn[] = partial && !text
366
378
  ? [{ key: "partial", header: "Partial", format: (value) => value === true ? "true" : "" }]
367
379
  : [];
368
- return [date, ...historyPriceColumns(volumeMissing, decimals), ...marked];
380
+ return [date, ...historyPriceColumns(volumeMissing, decimals, text ? pence : undefined), ...marked];
369
381
  }
370
382
 
371
383
  export const historicalPricesHeadless: HeadlessPaneDefinition<"rows"> = {
@@ -386,14 +398,16 @@ export const historicalPricesHeadless: HeadlessPaneDefinition<"rows"> = {
386
398
  window ? loadPriceWindowHistory(ctx.marketData, listing, exchange ?? "", window).then((result) => result.points)
387
399
  : loadHeadlessPriceHistory(ctx, symbol, range),
388
400
  pastWindow || (!window && isIntradayResolution(RANGE_HISTORY_RESOLUTION[range])) ? null
389
- : ctx.marketData.getQuote(listing, exchange ?? "").catch(() => null),
401
+ : ctx.marketData.getQuote(listing, exchange ?? "").catch(logFailure("ticker-detail.headless", "Load historical prices quote failed", null)),
390
402
  ]);
391
403
  // The venue whose sessions say whether a day, week or month is still running: the key's, or for a bare symbol its quote's.
392
404
  const venue = exchange || quote?.listingExchangeName || quote?.exchangeName || undefined;
393
405
  const now = Date.now();
394
406
  const points = withSessionBar(loaded, quote, { now, venue });
395
- // Providers serve float32 prices (4013.39990234 for 4013.4): the rows hold the decimal each is, as `history` does.
396
- const clean = (value: number | null) => value == null ? null : cleanFloat32Price(value);
407
+ // Providers serve float32 prices (4013.39990234 for 4013.4): the rows hold the decimal each is, as `history` does,
408
+ // a London line's cleaned in the pence it was priced in.
409
+ const priceDivisor = quotedPriceDivisor(quote);
410
+ const clean = (value: number | null) => value == null ? null : cleanBarPrice(value, priceDivisor);
397
411
  const rows = points.map((point) => {
398
412
  const date = new Date(point.date);
399
413
  const values = pricePointValues(point);
@@ -417,7 +431,7 @@ export const historicalPricesHeadless: HeadlessPaneDefinition<"rows"> = {
417
431
  && quote.lastUpdated >= Date.parse(`${rows.at(-1)!.date.slice(0, 10)}T00:00:00Z`) ? new Date(quote.lastUpdated).toISOString() : null;
418
432
  const shown = forming ? [...rows.slice(0, -1), { ...rows.at(-1)!, partial: true }] : rows;
419
433
  // One decimal count for the table, by the instrument's display rules when the quote names its kind.
420
- const decimals = barPriceDecimals(rows, quoteFormatOptions(quote).assetCategory);
434
+ const decimals = barPriceDecimals(rows, quoteFormatOptions(quote).assetCategory, priceDivisor);
421
435
  const marked = shown.some((row) => row.partial === true);
422
436
  const volumeMissing = missingVolumeCount(shown);
423
437
  const volumeNote = missingVolumeNotice(volumeMissing, shown.length);
@@ -443,7 +457,8 @@ export const historicalPricesHeadless: HeadlessPaneDefinition<"rows"> = {
443
457
  return {
444
458
  rows: shown,
445
459
  columns: historyColumns(size, marked, false, false, decimals),
446
- textColumns: historyColumns(size, marked, true, volumeMissing > 0, decimals),
460
+ textColumns: historyColumns(size, marked, true, volumeMissing > 0, decimals,
461
+ (close) => penceFigure(Number(close.toFixed(Math.min(20, decimals))), quote, decimals)),
447
462
  freshness,
448
463
  ...(notes.length > 0 ? { notes } : {}),
449
464
  ...(notice ? { complete: false } : {}),
@@ -474,7 +489,7 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
474
489
  // --from 2007 --to 2009 measures those dates' returns in place of the range.
475
490
  const window = readReportWindow(options);
476
491
  const pastWindow = !!window && isPastPriceWindow(window);
477
- const support = await Promise.resolve(ctx.marketData.getChartResolutionSupport?.(symbol, exchange ?? "") ?? []).catch(() => []);
492
+ const support = await Promise.resolve(ctx.marketData.getChartResolutionSupport?.(symbol, exchange ?? "") ?? []).catch(logFailure("ticker-detail.headless", "Read return interval support failed", []));
478
493
  const now = Date.now();
479
494
  const series = await loadReturnSeries(ctx.marketData, {
480
495
  symbol, exchange: exchange ?? "", range, resolution: requested, support, now, forceRefresh: ctx.refresh, window,
@@ -1,9 +1,9 @@
1
1
  import { formatPriceEarnings, NOT_MEANINGFUL } from "../../../../utils/price-earnings";
2
2
  import { convertMarketCapitalization } from "../../../../utils/market-capitalization";
3
3
  import { priceColor } from "../../../../theme/colors";
4
- import type { CompanyProfile, DepositaryPair, NextEarnings, Quote, TickerFinancials } from "../../../../types/financials";
4
+ import type { CedearRatio, CompanyProfile, DepositaryPair, NextEarnings, Quote, TickerFinancials } from "../../../../types/financials";
5
5
  import { adrRatioText } from "../../../../utils/depositary-receipt";
6
- import { ratioCount } from "../../../../market-data/depositary-pair";
6
+ import { cedearRatioText, ratioCount } from "../../../../market-data/depositary-pair";
7
7
  import { ADR_PAIR_TAB_ID, ADR_PAIR_TEMPLATE_ID } from "../adr-pair/model";
8
8
  import type { Portfolio, TickerPosition, TickerRecord } from "../../../../types/ticker";
9
9
  import type { BrokerInstanceConfig } from "../../../../types/config";
@@ -151,6 +151,7 @@ export function buildOverviewStats({
151
151
  nextEarnings,
152
152
  depositaryReceipt = false,
153
153
  depositaryPair = null,
154
+ cedearRatio = null,
154
155
  today,
155
156
  dividend,
156
157
  bond = null,
@@ -169,6 +170,8 @@ export function buildOverviewStats({
169
170
  depositaryReceipt?: boolean;
170
171
  /** The receipt pair the listing is a side of, as Gloom Cloud serves it; `receiptSide` when the listing is the receipt. */
171
172
  depositaryPair?: { pair: DepositaryPair; receiptSide: boolean } | null;
173
+ /** A Buenos Aires CEDEAR's ratio in its company's shares, as Gloom Cloud serves it. */
174
+ cedearRatio?: CedearRatio | null;
172
175
  /** The listing's calendar day, YYYY-MM-DD; dates before it are left out. */
173
176
  today?: string;
174
177
  /** The history the dividend yield reads (useOverviewDividend): a trailing one is measured from its cash, a forward one stops with it; without it the yield stands. */
@@ -221,7 +224,12 @@ export function buildOverviewStats({
221
224
  const count = ratioCount(pair.ratio);
222
225
  stats.push(receiptSide
223
226
  ? { label: "ADR Ratio", value: `1:${count}`, detail: [adrRatioText(pair.ratio)!, `${count} sh per ADR`], link: ADR_PAIR }
224
- : { label: "ADR", value: pair.receipt, detail: [`1 ADR = ${count} of these share${pair.ratio === 1 ? "" : "s"}`, `1:${count}`], link: ADR_PAIR });
227
+ : { label: "ADR", value: pair.receipt, detail: [`1 ADR = ${count} of these shares`, `1:${count}`], link: ADR_PAIR });
228
+ }
229
+ if (cedearRatio) {
230
+ const receipts = ratioCount(cedearRatio.receipts);
231
+ const shares = ratioCount(cedearRatio.shares);
232
+ stats.push({ label: "CEDEAR Ratio", value: `${receipts}:${shares}`, detail: [cedearRatioText(cedearRatio), `${receipts} per ${shares} sh`] });
225
233
  }
226
234
  const trailingPE = liveTrailingPE(quote, fundamentals);
227
235
  if (trailingPE != null) {