@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
@@ -1,4 +1,4 @@
1
- import { canonicalExchange, parsePublicTickerKey } from "./exchanges";
1
+ import { canonicalExchange, EXCHANGE_TIME_ZONES, isKnownExchangeCode, parsePublicTickerKey } from "./exchanges";
2
2
 
3
3
  // The fiat codes a pair against USD is read as FX for, never as a coin. The
4
4
  // list Cloud reads: it serves these pairs by the same rule, so a copy judged
@@ -9,8 +9,8 @@ const FIAT_CURRENCY_CODES = new Set([
9
9
  ]);
10
10
 
11
11
  const USD_COIN_PAIR = /^([A-Z0-9]+)[-/]USD$/;
12
- // The other currencies and coins the crypto venue quotes a coin in (BTC-EUR, ETH-BTC).
13
- const QUOTED_COIN_PAIR = /^([A-Z0-9]+)[-/](?:USDT|USDC|EUR|GBP|BTC|ETH)$/;
12
+ // The other currencies and coins the crypto venue quotes a coin in (BTC-EUR, BTC-JPY, ETH-BTC).
13
+ const QUOTED_COIN_PAIR = /^([A-Z0-9]+)[-/](?:USDT|USDC|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|BTC|ETH)$/;
14
14
 
15
15
  /** A coin quoted in dollars (BTC-USD, ETH/USD). EUR-USD is spot FX, not a coin. */
16
16
  function isUsdCoinPair(symbol: string): boolean {
@@ -35,10 +35,18 @@ export function isRoundTheClockCoin(symbol?: string, exchange?: string): boolean
35
35
  const parsed = symbol ? parsePublicTickerKey(symbol) : null;
36
36
  const venue = canonicalExchange(parsed?.exchange ?? exchange) || canonicalExchange(exchange);
37
37
  if (venue === "CCC" || canonicalExchange(exchange) === "CCC") return true;
38
+ if (parsed && exchangeLineCoinListing(parsed.symbol, venue)) return true;
38
39
  return !!parsed && (isUsdCoinPair(parsed.symbol) || (!venue && isQuotedCoinPair(parsed.symbol)));
39
40
  }
40
41
 
41
- const SLASH_COIN_PAIR = /^([A-Z0-9]{1,15})\/(USD|EUR|GBP|USDT|USDC|BTC)$/;
42
+ const SLASH_COIN_PAIR = /^([A-Z0-9]{1,15})\/(USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|ETH)$/;
43
+
44
+ /**
45
+ * What the crypto venue (CCC) quotes a coin in: BTC-GBP, BTC-KRW, LINK-ETH.
46
+ * It has no coin in a stablecoin (BTC-USDT) or in most other currencies
47
+ * (BTC-TRY, BTC-BRL).
48
+ */
49
+ const VENUE_QUOTE_LEGS: ReadonlySet<string> = new Set(["USD", "EUR", "GBP", "JPY", "KRW", "CAD", "INR", "CNY", "AUD", "BTC", "ETH"]);
42
50
 
43
51
  let isoCurrencyCodes: ReadonlySet<string> | null = null;
44
52
 
@@ -58,9 +66,114 @@ export function dashedCoinPair(symbol: string): string | null {
58
66
  return `${match[1]}-${match[2]}`;
59
67
  }
60
68
 
69
+ /**
70
+ * The crypto exchanges whose own lines of a coin pair search lists in a currency the crypto venue quotes coins in,
71
+ * and the venue itself. A slash pair saved on any other venue, or on none, is not read as a coin: on the commodity
72
+ * or currency venue it is spot crude, gold by the gram or a currency (WTI/USD, GAU/USD, RUB/USD).
73
+ */
74
+ const COIN_EXCHANGES: ReadonlySet<string> = new Set([
75
+ "CCC", "BIGONE", "BINANCE", "BITBANK", "BITBNS", "BITFINEX", "BITFLYER", "BITHUMB", "BITMART", "BITRUE", "BITSTAMP",
76
+ "BTC MARKETS", "BYBIT", "CEX.IO", "COINBASE PRO", "COINONE", "CRYPTO.COM EXCHANGE", "FREIEXCHANGE", "GATE.IO", "HITBTC",
77
+ "HUOBI", "INDEPENDENT RESERVE", "KRAKEN", "KUCOIN", "LATOKEN", "MEXC", "MXC", "OKEX", "OKX", "POLONIEX", "SYNTHETIC",
78
+ "UNISWAP", "UPBIT", "WAZIRX", "XT.COM", "YOBIT", "ZAIF",
79
+ ]);
80
+
81
+ /** Venues whose slash pairs are commodities or currencies, never coins, whatever a row's type says. */
82
+ const NOT_COIN_VENUES: ReadonlySet<string> = new Set(["COMMODITY", "CCY", "FX", "FOREX", "CURRENCY", "PHYSICAL CURRENCY"]);
83
+
84
+ /**
85
+ * Spot commodities written as a pair: gold, silver, platinum and palladium, gold and silver by the gram (GAU,
86
+ * XAGG), WTI, Brent (XBR) and Urals crude, and copper (HG1). Never a coin, though tokens share the letters
87
+ * (GAU-USD is Gamer Arena).
88
+ */
89
+ const SPOT_COMMODITY_BASES: ReadonlySet<string> = new Set(["XAU", "GAU", "XAG", "XAGG", "XPT", "XPD", "WTI", "XBR", "URALS", "HG1"]);
90
+
91
+ /** A spot commodity's pair (WTI/USD, GAU/USD) or a pair on the commodity or currency venue, which no coin is. */
92
+ export function isSpotCommodityOrCurrencyPair(symbol: string, exchange?: string): boolean {
93
+ const parsed = parsePublicTickerKey(symbol.trim().toUpperCase());
94
+ const named = (parsed.exchange ?? exchange ?? "").trim().toUpperCase();
95
+ const base = /^([A-Z0-9]{1,15})\//.exec(parsed.symbol)?.[1];
96
+ return !!base && (SPOT_COMMODITY_BASES.has(base) || NOT_COIN_VENUES.has(named) || NOT_COIN_VENUES.has(canonicalExchange(named)));
97
+ }
98
+
99
+ /** The crypto venue's listing of a coin pair written with a slash (BTC/GBP); null for a currency, a spot commodity or another leg. */
100
+ function slashCoinPairListing(symbol: string): { symbol: string; exchange: "CCC"; currency: string } | null {
101
+ const match = SLASH_COIN_PAIR.exec(symbol.trim().toUpperCase());
102
+ if (!match) return null;
103
+ const [, base, quote] = match as unknown as [string, string, string];
104
+ if (isIsoCurrencyCode(base) || SPOT_COMMODITY_BASES.has(base) || !VENUE_QUOTE_LEGS.has(quote)) return null;
105
+ return { symbol: `${base}-${quote}`, exchange: "CCC", currency: quote };
106
+ }
107
+
108
+ /**
109
+ * A crypto exchange's own line of a coin pair (BTC/GBP on COINBASE PRO, LINK/EUR on KRAKEN, DOGE/BTC on BINANCE)
110
+ * as the crypto venue's listing of it, in the currency it is quoted in: BTC-GBP on CCC, in pounds. Search offers
111
+ * those lines, and no source quotes them.
112
+ *
113
+ * Only a pair on a crypto exchange (COIN_EXCHANGES) is one, or a search row whose type says it is a coin
114
+ * (`coinRow`) on a venue that is no stock, commodity or currency venue. Null for a currency pair (EUR/USD,
115
+ * RUB/USD), a spot commodity (XAU/USD, WTI/USD), a pair the venue quotes in no such currency (BTC/USDT), a pair
116
+ * with no venue, and a slash on a stock exchange, which is a share class (BRK/B).
117
+ */
118
+ export function exchangeLineCoinListing(
119
+ symbol: string,
120
+ exchange?: string,
121
+ { coinRow = false }: { coinRow?: boolean } = {},
122
+ ): { symbol: string; exchange: "CCC"; currency: string } | null {
123
+ const parsed = parsePublicTickerKey(symbol);
124
+ const listing = slashCoinPairListing(parsed.symbol);
125
+ if (!listing) return null;
126
+ const named = (parsed.exchange ?? exchange ?? "").trim().toUpperCase();
127
+ const venue = canonicalExchange(named);
128
+ if (COIN_EXCHANGES.has(named) || COIN_EXCHANGES.has(venue)) return listing;
129
+ if (!coinRow || !venue || NOT_COIN_VENUES.has(named) || NOT_COIN_VENUES.has(venue)) return null;
130
+ // The crypto venues trade around the clock; a venue the app knows that does not is a stock exchange.
131
+ return isKnownExchangeCode(venue) && EXCHANGE_TIME_ZONES[venue] !== "UTC" ? null : listing;
132
+ }
133
+
134
+ /**
135
+ * A coin pair typed with a slash (btc/gbp) as the crypto venue's listing of it, BTC-GBP on CCC; with a venue
136
+ * (BTC/GBP:KRAKEN), as that exchange's line reads (exchangeLineCoinListing). Null for a currency pair, a spot
137
+ * commodity (wti/usd) and a pair the venue quotes in no such currency.
138
+ */
139
+ export function typedCoinPairListing(query: string): { symbol: string; exchange: "CCC"; currency: string } | null {
140
+ const parsed = parsePublicTickerKey(query.trim().toUpperCase());
141
+ return parsed.exchange ? exchangeLineCoinListing(parsed.symbol, parsed.exchange) : slashCoinPairListing(parsed.symbol);
142
+ }
143
+
144
+ /**
145
+ * Search rows with each crypto exchange's line of a coin pair (ETH/EUR on KRAKEN, COINBASE PRO, BITSTAMP and
146
+ * BINANCE) as the crypto venue's one listing of it, ETH-EUR on CCC in euros, where the first line stood. A pair
147
+ * already offered on the venue is not offered twice. Newer servers send the listing themselves.
148
+ */
149
+ export function withExchangeLinesAsCoinListings<T extends { symbol: string; exchange: string; type: string; currency?: string; brokerContract?: unknown }>(rows: T[]): T[] {
150
+ const offered = new Set(rows.filter((row) => canonicalExchange(row.exchange) === "CCC").map((row) => row.symbol.trim().toUpperCase()));
151
+ let changed = false;
152
+ const mapped = rows.flatMap((row) => {
153
+ const coin = !row.brokerContract && isCoinRowType(row.type) ? exchangeLineCoinListing(row.symbol, row.exchange, { coinRow: true }) : null;
154
+ if (!coin) return [row];
155
+ changed = true;
156
+ if (offered.has(coin.symbol)) return [];
157
+ offered.add(coin.symbol);
158
+ return [{ ...row, symbol: coin.symbol, exchange: coin.exchange, currency: row.currency || coin.currency }];
159
+ });
160
+ return changed ? mapped : rows;
161
+ }
162
+
163
+ /** Whether a search row's type says it is a coin (CRYPTOCURRENCY, Digital Currency). */
164
+ export function isCoinRowType(type: string | undefined): boolean {
165
+ return /CRYPTO|DIGITAL|COIN/i.test(type ?? "");
166
+ }
167
+
168
+ /** The currency or coin a pair on the crypto venue is quoted in: GBP for BTC-GBP; null for anything else. */
169
+ export function coinPairQuoteCurrency(symbol: string): string | null {
170
+ const match = /^([A-Z0-9]{1,15})-([A-Z]{3})$/.exec(parsePublicTickerKey(symbol).symbol);
171
+ return match && !isIsoCurrencyCode(match[1]!) && VENUE_QUOTE_LEGS.has(match[2]!) ? match[2]! : null;
172
+ }
173
+
61
174
  // A base-quote pair such as BTC-USD or SOL-EUR. Equities with a share-class dash (BRK-B) and
62
175
  // currency pairs (EURUSD=X) do not match.
63
- const CRYPTO_PAIR = /^[A-Z0-9]{1,15}-(?:USD|EUR|GBP|USDT|USDC|BTC)$/;
176
+ const CRYPTO_PAIR = /^[A-Z0-9]{1,15}-(?:USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|ETH)$/;
64
177
 
65
178
  /**
66
179
  * Wider than isUsdCoinPair, for the hint under a pair that will not load: it
@@ -91,9 +204,54 @@ export const MAJOR_COIN_NAMES: ReadonlyMap<string, string> = new Map([
91
204
  ["BNB", "BNB"],
92
205
  ]);
93
206
 
94
- const COIN_PAIR = /^([A-Z0-9]{1,15})[-/](?:USD|EUR|GBP|USDT|USDC|BTC)$/;
207
+ /**
208
+ * The largest coins by market value, stablecoins and gold tokens left out, with
209
+ * how a note names each: the coins whose bare ticker a listed company or fund
210
+ * may hold instead (SOL is a stale Emeren Group, LINK is Interlink Electronics).
211
+ * A hint reads this list; `MAJOR_COIN_NAMES` stays the shorter one stories and
212
+ * company-name lookups use, so a word such as "stellar" is still a company's.
213
+ */
214
+ export const TOP_COIN_NAMES: ReadonlyMap<string, string> = new Map([
215
+ ...MAJOR_COIN_NAMES,
216
+ ["TRX", "tron"],
217
+ ["ZEC", "zcash"],
218
+ ["HYPE", "hyperliquid"],
219
+ ["XMR", "monero"],
220
+ ["XLM", "stellar"],
221
+ ["NEAR", "near protocol"],
222
+ ["TON", "toncoin"],
223
+ ["UNI", "uniswap"],
224
+ ["SUI", "sui"],
225
+ ["HBAR", "hedera"],
226
+ ["TAO", "bittensor"],
227
+ ["SHIB", "shiba inu"],
228
+ ["ENA", "ethena"],
229
+ ["CRO", "cronos"],
230
+ ["AAVE", "aave"],
231
+ ["ONDO", "ondo"],
232
+ ["PEPE", "pepe"],
233
+ ["ICP", "internet computer"],
234
+ ["ETC", "ethereum classic"],
235
+ ["ARB", "arbitrum"],
236
+ ["ALGO", "algorand"],
237
+ ["ATOM", "cosmos"],
238
+ ["APT", "aptos"],
239
+ ["FIL", "filecoin"],
240
+ ["POL", "polygon"],
241
+ ["OP", "optimism"],
242
+ ["WLD", "worldcoin"],
243
+ ["MNT", "mantle"],
244
+ ["KAS", "kaspa"],
245
+ ["QNT", "quant"],
246
+ ["VET", "vechain"],
247
+ ["INJ", "injective"],
248
+ ["RENDER", "render"],
249
+ ]);
250
+
251
+ const COIN_PAIR = /^([A-Z0-9]{1,15})[-/](?:USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|ETH)$/;
95
252
  // What a pair's listing name adds after the coin's: its quote currency, an id in brackets.
96
- const PAIR_NAME_SUFFIX = /(?:\s*\([^)]*\))+$|\s+(?:USD|EUR|GBP|USDT|USDC|BTC|US Dollars?)$/i;
253
+ // Not ETH, which ends coins' own names (Lido Staked ETH).
254
+ const PAIR_NAME_SUFFIX = /(?:\s*\([^)]*\))+$|\s+(?:USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|US Dollars?)$/i;
97
255
 
98
256
  /**
99
257
  * The coin a crypto pair trades, as stories name it: "bitcoin" for BTC-USD
@@ -34,6 +34,6 @@ export function declaredDividends(
34
34
  const at = Date.parse(`${exDate}T00:00:00Z`);
35
35
  if (paid.some((time) => Math.abs(time - at) <= SAME_DISTRIBUTION_DAYS * DAY_MS)) return [];
36
36
  return [{ exDate, amount: row.amount, ...declarationDate ? { declarationDate } : {},
37
- ...recordDate ? { recordDate } : {}, ...payDate ? { payDate } : {} }];
37
+ ...recordDate ? { recordDate } : {}, ...payDate ? { payDate } : {}, ...row.special === true ? { special: true as const } : {} }];
38
38
  }).sort((left, right) => left.exDate.localeCompare(right.exDate));
39
39
  }
@@ -14,6 +14,8 @@ import { formatDistributionAmount } from "./format";
14
14
  * the rate divided back.
15
15
  */
16
16
  export const LATEST_DISTRIBUTION = "latest distribution annualized";
17
+ /** The basis of a forward rate that is the year's regular payments, which differ too widely for one to be annualized (unevenRegularPayments). */
18
+ export const UNEVEN_REGULAR_PAYMENTS = "trailing regular payments: uneven interims";
17
19
 
18
20
  /**
19
21
  * What a trailing dividend yield is everywhere it shows (DVD, a fund's in
@@ -2,20 +2,24 @@ import { formatFxRate } from "../cli/fx-pair";
2
2
  import type { EnterpriseValueCheck, Fundamentals, Quote, TickerFinancials } from "../types/financials";
3
3
  import { quotedFxPair } from "./currency-pair";
4
4
  import { resolveCurrencyUnit } from "./currency-units";
5
+ import { isUsListingExchange } from "./exchanges";
5
6
  import { formatCompact } from "./format";
6
7
  import { formatUtcTime } from "./utc-time";
7
8
 
8
9
  export const RETRACTABLE_VALUATION_FIELDS = ["enterpriseValue", "enterpriseToRevenue"] as const;
10
+ /** Every field a source can retract: the valuation fields, and a receipt's market cap (`marketCapUnavailable`). */
11
+ export const RETRACTED_FUNDAMENTALS_FIELDS = [...RETRACTABLE_VALUATION_FIELDS, "marketCap"] as const;
9
12
 
10
- /** A retraction survives serialization and overrides a contradictory cached number. */
13
+ /** A retraction survives serialization and overrides a contradictory cached number; its reason goes with it. */
11
14
  export function redactUnavailableFundamentals(value: Fundamentals | undefined): Fundamentals | undefined {
12
- if (!value || value.unavailableFields === undefined) return value;
15
+ if (!value || (value.unavailableFields === undefined && value.marketCapUnavailable === undefined)) return value;
13
16
  const unavailableFields = Array.isArray(value.unavailableFields)
14
- ? RETRACTABLE_VALUATION_FIELDS.filter((field) => value.unavailableFields!.includes(field)) : [];
17
+ ? RETRACTED_FUNDAMENTALS_FIELDS.filter((field) => value.unavailableFields!.includes(field)) : [];
15
18
  const result = { ...value };
16
19
  if (unavailableFields.length) result.unavailableFields = unavailableFields;
17
20
  else delete result.unavailableFields;
18
21
  for (const field of unavailableFields) delete result[field];
22
+ if (!unavailableFields.includes("marketCap")) delete result.marketCapUnavailable;
19
23
  return result;
20
24
  }
21
25
 
@@ -116,24 +120,53 @@ export function computedEnterpriseValueNote(fundamentals: Parameters<typeof ente
116
120
  }
117
121
 
118
122
  /** What names the currency of the trailing figures: the fundamentals' own labels, else the statements' on a listing that trades in theirs. */
119
- type FundamentalsCurrencies = Pick<TickerFinancials, "financialCurrency"> & {
123
+ type FundamentalsCurrencies = Pick<TickerFinancials, "financialCurrency" | "statementHistory"> & {
120
124
  fundamentals?: Pick<Fundamentals, "financialCurrency" | "epsCurrency" | "marketCapCurrency"> | null;
121
- quote?: Pick<Quote, "currency"> | null;
125
+ quote?: Pick<Quote, "currency" | "exchangeName" | "listingExchangeName" | "isDepositaryReceipt"> | null;
122
126
  };
123
127
 
128
+ /** What a caller knows of the listing beyond its quote: the day it listed (its IPO's), when known. */
129
+ interface ListingAge {
130
+ listedSince?: string | null;
131
+ now?: Date;
132
+ }
133
+
134
+ /** A listing that started within about a year: its statements may not be served yet. */
135
+ const RECENT_LISTING_DAYS = 372;
136
+
137
+ /**
138
+ * The listing's own currency, standing in for figures with no statements to tell their currency by: only for a
139
+ * listing of the last year or so (a recent IPO such as HAWK) or a US listing in dollars that is no receipt, and
140
+ * never after a statements read that failed. A minor unit (GBp) does not say whether figures are pence or pounds.
141
+ */
142
+ function listingCurrencyFallback(financials: FundamentalsCurrencies | null | undefined, listing: ListingAge): string | undefined {
143
+ const listed = financials?.fundamentals?.marketCapCurrency?.trim() || financials?.quote?.currency?.trim();
144
+ if (!listed || resolveCurrencyUnit(listed).divisor !== 1) return undefined;
145
+ if (financials?.statementHistory?.status === "retryable-failure") return undefined;
146
+ const currency = resolveCurrencyUnit(listed).currency;
147
+ const since = listing.listedSince ? Date.parse(`${listing.listedSince.slice(0, 10)}T00:00:00Z`) : NaN;
148
+ const recent = Number.isFinite(since) && (listing.now ?? new Date()).getTime() - since <= RECENT_LISTING_DAYS * 86_400_000;
149
+ const quote = financials?.quote;
150
+ const home = currency === "USD" && quote?.isDepositaryReceipt !== true
151
+ && isUsListingExchange(quote?.listingExchangeName || quote?.exchangeName);
152
+ return recent || home ? currency : undefined;
153
+ }
154
+
124
155
  /**
125
156
  * The currency of the trailing EPS, revenue, income and cash flows: the one the fundamentals declare for themselves.
126
157
  * A block that declares none is in the statements' currency only when the listing trades in it; on a listing quoted
127
158
  * in another (a receipt in USD beside TWD books, YPFD in ARS beside USD statements) its currency is unknown, since
128
- * Gloom Cloud labels every block whose currency it can tell. Never the quote's.
159
+ * Gloom Cloud labels every block whose currency it can tell. With no statements to tell by, the listing's own
160
+ * currency where that is safe (listingCurrencyFallback).
129
161
  */
130
162
  export function fundamentalsCurrency(
131
163
  financials: FundamentalsCurrencies | null | undefined,
164
+ listing: ListingAge = {},
132
165
  ): string | undefined {
133
166
  const declared = financials?.fundamentals?.financialCurrency?.trim();
134
167
  if (declared) return declared;
135
168
  const statements = financials?.financialCurrency?.trim();
136
- if (!statements) return undefined;
169
+ if (!statements) return listingCurrencyFallback(financials, listing);
137
170
  const listed = financials?.fundamentals?.marketCapCurrency?.trim() || financials?.quote?.currency?.trim();
138
171
  return !listed || resolveCurrencyUnit(listed).currency === resolveCurrencyUnit(statements).currency ? statements : undefined;
139
172
  }
@@ -144,8 +177,9 @@ export function fundamentalsCurrency(
144
177
  */
145
178
  export function fundamentalsEpsCurrency(
146
179
  financials: FundamentalsCurrencies | null | undefined,
180
+ listing: ListingAge = {},
147
181
  ): string | undefined {
148
- return financials?.fundamentals?.epsCurrency?.trim() || fundamentalsCurrency(financials);
182
+ return financials?.fundamentals?.epsCurrency?.trim() || fundamentalsCurrency(financials, listing);
149
183
  }
150
184
 
151
185
  const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
@@ -243,6 +277,24 @@ export function epsConversionNote(fundamentals: Pick<Fundamentals, "financialCur
243
277
  return eps && amounts && eps !== amounts ? `EPS (TTM) in ${eps}, converted from ${amounts} at report-date rates.` : undefined;
244
278
  }
245
279
 
280
+ /** What a trailing EPS that is not the filed quarters' is called where it is shown (EE's TTM row). */
281
+ export const SERVED_TRAILING_EPS = "Gloom Cloud's TTM";
282
+
283
+ /**
284
+ * "EPS (TTM) and P/E (TTM) are Gloom Cloud's trailing figures, not the last four filed quarters'.": the headline EPS
285
+ * and P/E are the filed quarters' (time-series/filed-trailing-eps), so served ones say so, naming those `shown`
286
+ * ("P/E (TTM) is Gloom Cloud's trailing figure, ..." for a fund's P/E with no EPS). Undefined otherwise.
287
+ */
288
+ export function trailingEpsSourceNote(
289
+ fundamentals: Pick<Fundamentals, "trailingEpsSource"> | undefined,
290
+ shown: { eps: boolean; pe: boolean },
291
+ ): string | undefined {
292
+ if (fundamentals?.trailingEpsSource !== "served" || (!shown.eps && !shown.pe)) return undefined;
293
+ const figures = shown.eps && shown.pe ? "EPS (TTM) and P/E (TTM) are Gloom Cloud's trailing figures"
294
+ : `${shown.eps ? "EPS (TTM)" : "P/E (TTM)"} is Gloom Cloud's trailing figure`;
295
+ return `${figures}, not the last four filed quarters'.`;
296
+ }
297
+
246
298
  type PriceEarningsBasis = Pick<Fundamentals, "financialCurrency" | "ttmConversion" | "epsCurrency" | "trailingPEConversion">;
247
299
 
248
300
  /** The trailing EPS in the price's currency: converted at the rate the served P/E used when it says so. */
@@ -30,12 +30,35 @@ export function isFxMarketOpen(time: number): boolean {
30
30
  }
31
31
 
32
32
  /**
33
- * The FX trading day an instant belongs to, as "YYYY-MM-DD": the day rolls
34
- * at 17:00 New York time, so Wednesday 18:00 New York is Thursday's.
33
+ * The FX trading day a print made at `time` belongs to, as "YYYY-MM-DD": the
34
+ * day rolls at 17:00 New York time, so Wednesday 18:00 New York is
35
+ * Thursday's. A print stamped exactly 17:00:00 is that day's close, so it is
36
+ * still that day's.
35
37
  */
36
38
  export function fxTradingDay(time: number): string {
37
39
  // The roll is 17:00, seven hours before New York's midnight, daylight saving or not.
38
- return zonedDateKey(time + 7 * HOUR_MS, NEW_YORK);
40
+ return zonedDateKey(time - 1 + 7 * HOUR_MS, NEW_YORK);
41
+ }
42
+
43
+ /**
44
+ * The FX trading day of a print made at `time` (fxTradingDay), or null for a
45
+ * print made while the market is shut: from just after Friday's 17:00 New
46
+ * York close, whose own print still counts, through Sunday's 17:00 open. A
47
+ * tick over the weekend belongs to no trading day.
48
+ */
49
+ export function fxPrintTradingDay(time: number): string | null {
50
+ return Number.isFinite(time) && isFxMarketOpen(time - 1) ? fxTradingDay(time) : null;
51
+ }
52
+
53
+ /**
54
+ * The FX trading day the market is on at `time`: the day in progress while it
55
+ * trades, or the Friday that closed it while it is shut.
56
+ */
57
+ export function fxTradingDayAt(time: number): string | null {
58
+ const day = fxPrintTradingDay(time);
59
+ if (day != null || !Number.isFinite(time)) return day;
60
+ const shut = currentFxClosureStart(time - 1);
61
+ return shut != null ? fxTradingDay(shut) : null;
39
62
  }
40
63
 
41
64
  /** The latest 17:00 New York on `weekday` (0 is Sunday) at or before `time`. */
@@ -9,13 +9,43 @@ const SAMPLE_INTERVAL_MS = 250;
9
9
  */
10
10
  const STALL_THRESHOLD_MS = 400;
11
11
 
12
+ export interface StallEvent {
13
+ blockedMs: number;
14
+ /** The last measured section before the stall (perf-marks), if any. */
15
+ lastMeasured?: string;
16
+ /** How long before the stall was noticed that section ended. */
17
+ lastMeasuredEndedMsAgo?: number;
18
+ }
19
+
20
+ let stallSink: ((event: StallEvent) => void) | null = null;
21
+
22
+ /**
23
+ * Where this process's stalls go besides the Debug Log: the stall log and its
24
+ * report (telemetry/stall-reports.ts), or, in the desktop window, the Bun
25
+ * process that keeps them.
26
+ */
27
+ export function setStallSink(next: ((event: StallEvent) => void) | null): () => void {
28
+ stallSink = next;
29
+ return () => {
30
+ if (stallSink === next) stallSink = null;
31
+ };
32
+ }
33
+
34
+ function noteStall(event: StallEvent): void {
35
+ try {
36
+ stallSink?.(event);
37
+ } catch {
38
+ // Intentional ignore: reporting a freeze must never become one.
39
+ }
40
+ }
41
+
12
42
  export interface MainThreadStallMonitorOptions {
13
43
  intervalMs?: number;
14
44
  thresholdMs?: number;
15
45
  now?: () => number;
16
46
  schedule?: (callback: () => void, delayMs: number) => unknown;
17
47
  cancel?: (handle: unknown) => void;
18
- report?: (blockedMs: number, context: Record<string, unknown>) => void;
48
+ report?: (blockedMs: number, context: Omit<StallEvent, "blockedMs">) => void;
19
49
  }
20
50
 
21
51
  function defaultNow(): number {
@@ -41,7 +71,10 @@ export function startMainThreadStallMonitor(
41
71
  const schedule = options.schedule ?? ((callback, delayMs) => setInterval(callback, delayMs));
42
72
  const cancel = options.cancel ?? ((handle) => clearInterval(handle as ReturnType<typeof setInterval>));
43
73
  const report = options.report
44
- ?? ((blockedMs, context) => recordPerfSample("runtime.stall", blockedMs, context));
74
+ ?? ((blockedMs, context) => {
75
+ recordPerfSample("runtime.stall", blockedMs, context);
76
+ noteStall({ blockedMs, ...context });
77
+ });
45
78
 
46
79
  let previous = now();
47
80
  const handle = schedule(() => {
@@ -20,11 +20,34 @@ function explicitCurrency(currency: string | undefined): currency is string {
20
20
  return currency != null && /^[A-Z]{3}$/.test(currency);
21
21
  }
22
22
 
23
- /** A lightweight quote can omit capitalization without invalidating the snapshot. */
23
+ /** What every view says for a cap Gloom Cloud withheld, in the words its screener uses. */
24
+ const WITHHELD_MARKET_CAP: Record<NonNullable<Fundamentals["marketCapUnavailable"]>, { gap: string; note: string }> = {
25
+ "receipt-priced-as-ordinary": {
26
+ gap: "receipt, no ratio",
27
+ note: "No market cap: a depositary receipt with no known ratio, whose source's cap is the receipt price x the company's ordinary shares.",
28
+ },
29
+ };
30
+
31
+ /**
32
+ * Why a listing has no market cap when Gloom Cloud withheld it: `gap` for a cell, `note` for a line under a
33
+ * report. Null when the cap was not withheld.
34
+ */
35
+ export function withheldMarketCap(fundamentals: Pick<Fundamentals, "unavailableFields" | "marketCapUnavailable"> | undefined):
36
+ { gap: string; note: string } | null {
37
+ if (!fundamentals?.unavailableFields?.includes("marketCap")) return null;
38
+ const reason = fundamentals.marketCapUnavailable ? WITHHELD_MARKET_CAP[fundamentals.marketCapUnavailable] : undefined;
39
+ return reason ?? { gap: "withheld", note: "No market cap: Gloom Cloud withheld it." };
40
+ }
41
+
42
+ /**
43
+ * A lightweight quote can omit capitalization without invalidating the snapshot. A cap Gloom Cloud withheld
44
+ * (withheldMarketCap) is none, the quote's included.
45
+ */
24
46
  export function selectMarketCapitalization(
25
47
  quote: Quote | undefined,
26
48
  fundamentals: Fundamentals | undefined,
27
49
  ): MarketCapitalization | null {
50
+ if (withheldMarketCap(fundamentals)) return null;
28
51
  if (usableValue(quote?.marketCap) && explicitCurrency(quote?.currency)) {
29
52
  return { value: quote.marketCap, currency: quote.currency,
30
53
  provenance: { kind: "quote", source: quote.providerId } };
@@ -2,7 +2,7 @@ import type { PricePoint, TickerFinancials } from "../types/financials";
2
2
  import { canonicalExchange } from "./exchanges";
3
3
  import { isRoundTheClockCoin } from "./crypto-pair";
4
4
  import { isCurrencyPairListing, isFxMarketOpen, latestFxWeekClose, latestFxWeekOpen } from "./fx-market-hours";
5
- import { hasPublishedSessionCalendar, historySourceWeekendDays, latestRegularSessionClose, latestRegularSessionOpen, sessionCalendarTimeZone } from "../market-data/market/freshness";
5
+ import { hasPublishedSessionCalendar, historySourceWeekendDays, isMiddayBreakPrint, latestRegularSessionClose, latestRegularSessionOpen, sessionCalendarTimeZone } from "../market-data/market/freshness";
6
6
  import { zonedDateTimeParts } from "./zoned-date-time";
7
7
  import { regularHistorySessionStaleness } from "../market-data/history-session";
8
8
  import type { HistorySession } from "../types/price-history";
@@ -234,6 +234,12 @@ export function isPriceHistoryStaleForCurrentWindow(
234
234
  const age = now - latestTime;
235
235
  const allowedLag = allowedBarLagMs(intervalMs);
236
236
  if (age <= allowedLag) return false;
237
+ // A venue paused at midday (SSE/SZSE/BSE, TKO, ...) holds the morning's last
238
+ // bar current through the break, as the quote freshness rule does. A bar is
239
+ // labelled by its start, so an hourly bar ending at the break is judged by
240
+ // when it ends.
241
+ const latestBarEnd = intervalMs != null ? Math.min(latestTime + intervalMs, now) : latestTime;
242
+ if (isMiddayBreakPrint(latestBarEnd, options.exchange, now, 0)) return false;
237
243
  const sessions = listingSessionsAt(now, options);
238
244
  if (!sessions) return age > UNSCHEDULED_HISTORY_MAX_AGE_MS;
239
245
  const fetchedAt = options.fetchedAt ?? Number.NEGATIVE_INFINITY;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * One word of a command line as a shell reads it back: as written when it
3
+ * holds nothing a shell treats specially (`NVDA`, `SAN:EPA`, `BRK.B`), else
4
+ * in single quotes (`'Altria Group'`, `'$nvda'`, `'AT&T'`).
5
+ */
6
+ export function shellWord(word: string): string {
7
+ return /^[\p{L}\p{N}._:@%+,/][\p{L}\p{N}._:@%+,/=-]*$/u.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
8
+ }
@@ -51,7 +51,9 @@ export function inferCadence(payments: readonly Pick<CashDistribution, "exDate">
51
51
  }
52
52
 
53
53
  /**
54
- * Trailing twelve-month cash as known at `asOf`, one payment per regular period.
54
+ * The payments trailing twelve-month cash is made of as known at `asOf`, one
55
+ * per regular period, and how many a year the issuer's cadence pays (null
56
+ * without a regular cadence).
55
57
  *
56
58
  * A fixed one-year ex-date window counts a fifth quarterly (or thirteenth
57
59
  * monthly) payment when this year's ex-date is a few days earlier than last
@@ -61,13 +63,13 @@ export function inferCadence(payments: readonly Pick<CashDistribution, "exDate">
61
63
  * the window holds fewer than one year of payments. Irregular cash uses the
62
64
  * plain window, so special distributions are neither dropped nor extended.
63
65
  */
64
- export function trailingCashAt<Payment extends CashDistribution>(payments: readonly Payment[], asOf: Date): number {
66
+ function trailingPayments<Payment extends CashDistribution>(payments: readonly Payment[], asOf: Date): { counted: Payment[]; perYear: number | null; cadence: DistributionCadence | null } {
65
67
  const known = payments.filter((payment) => payment.exDate <= asOf && Number.isFinite(payment.amount) && payment.amount > 0)
66
68
  .sort((a, b) => a.exDate.getTime() - b.exDate.getTime());
67
69
  const cutoff = calendarMonthsBefore(asOf, 12);
68
70
  const cadence = inferCadence(known, asOf);
69
71
  const regular = cadence ? REGULAR_CADENCE[cadence] : undefined;
70
- if (!regular) return known.filter((payment) => payment.exDate > cutoff).reduce((sum, payment) => sum + payment.amount, 0);
72
+ if (!regular) return { counted: known.filter((payment) => payment.exDate > cutoff), perYear: null, cadence };
71
73
 
72
74
  const halfPeriod = regular.halfPeriodDays * DAY;
73
75
  const graceCutoff = new Date(cutoff.getTime() - Math.min(regular.halfPeriodDays, MAX_LATE_GRACE_DAYS) * DAY);
@@ -94,7 +96,54 @@ export function trailingCashAt<Payment extends CashDistribution>(payments: reado
94
96
  const late = candidates.filter((payment) => payment.exDate <= cutoff && payment.exDate > graceCutoff && !superseded.has(payment))
95
97
  .reverse()
96
98
  .slice(0, Math.max(0, regular.perYear - counted.length));
97
- return [...counted, ...late].reduce((sum, payment) => sum + payment.amount, 0);
99
+ return { counted: [...counted, ...late], perYear: regular.perYear, cadence };
100
+ }
101
+
102
+ /** Trailing twelve-month cash as known at `asOf` (trailingPayments). */
103
+ export function trailingCashAt<Payment extends CashDistribution>(payments: readonly Payment[], asOf: Date): number {
104
+ return trailingPayments(payments, asOf).counted.reduce((sum, payment) => sum + payment.amount, 0);
105
+ }
106
+
107
+ /** How many payments trailing cash counts against how many a year the issuer's cadence pays. */
108
+ export interface TrailingPayments {
109
+ counted: number;
110
+ expected: number;
111
+ cadence: DistributionCadence;
112
+ }
113
+
114
+ /**
115
+ * The payments a regular payer's trailing cash counts when it is fewer than
116
+ * its cadence pays in a year: a hole in the history (ECC's monthly July to
117
+ * September 2026 distributions), a missed or late payment, or a fund under a
118
+ * year old. Null for a full year, an irregular payer and an issuer with no
119
+ * cadence, whose trailing cash is the plain window.
120
+ */
121
+ export function trailingPaymentShortfall(payments: readonly CashDistribution[], asOf: Date): TrailingPayments | null {
122
+ const { counted, perYear, cadence } = trailingPayments(payments, asOf);
123
+ return perYear != null && cadence && counted.length < perYear ? { counted: counted.length, expected: perYear, cadence } : null;
124
+ }
125
+
126
+ /** Over this ratio between a regular payer's largest and smallest payment of a year, no one payment stands for the year. */
127
+ const UNEVEN_PAYMENT_RATIO = 1.5;
128
+
129
+ /**
130
+ * A regular payer's payments of the trailing year when they differ widely:
131
+ * exactly as many as its cadence pays, the largest more than 1.5 times the
132
+ * smallest (HSBC's final beside its interims, AstraZeneca's interim beside its
133
+ * final). Their total is the year's cash, where the latest one annualized is
134
+ * not (HSBA's interim read as 2.17% a year beside the 4.08% it paid). Null for
135
+ * an even year, one with a hole or an extra payment, an annual payer and an
136
+ * irregular one. `latest` is the newest payment's ex-date.
137
+ */
138
+ export function unevenRegularPayments(payments: readonly CashDistribution[], asOf: Date): { total: number; latest: Date } | null {
139
+ const { counted, perYear } = trailingPayments(payments, asOf);
140
+ if (perYear == null || perYear < 2 || counted.length !== perYear) return null;
141
+ const amounts = counted.map((payment) => payment.amount);
142
+ if (Math.max(...amounts) <= UNEVEN_PAYMENT_RATIO * Math.min(...amounts)) return null;
143
+ return {
144
+ total: atSourcePrecision(amounts.reduce((sum, amount) => sum + amount, 0)),
145
+ latest: new Date(Math.max(...counted.map((payment) => payment.exDate.getTime()))),
146
+ };
98
147
  }
99
148
 
100
149
  /** A history row as a paid distribution; null when its date is not a calendar day or its amount is no cash. */
package/src/version.ts CHANGED
@@ -3,7 +3,7 @@ import packageJson from "../package.json" with { type: "json" };
3
3
  export const VERSION = packageJson.version;
4
4
 
5
5
  /**
6
- * The npm package this copy came from: `gloomberb`, or `@gloom-sh/terminal`
7
- * once releases are also published under that name.
6
+ * The npm package this copy came from: `@gloom-sh/terminal` or `gloomberb`,
7
+ * which every release publishes with the same build.
8
8
  */
9
9
  export const PACKAGE_NAME = packageJson.name;
@@ -1,17 +0,0 @@
1
- import type { DataProvider } from "../../../types/data-provider";
2
- import { shortCompanyName } from "../../../tickers/company-name";
3
- import { classifyInstrumentKind } from "../../../tickers/search/ranking";
4
-
5
- /**
6
- * The company a ticker is, by the name a registry files its studies under:
7
- * VKTX is "Viking Therapeutics". Read from the listing's quote, which names
8
- * it; a fund or a contract is no sponsor. Null when no share trades under it.
9
- */
10
- export async function tickerSponsorName(dataProvider: Pick<DataProvider, "getQuote"> | null, ticker: string): Promise<string | null> {
11
- const symbol = ticker.trim().toUpperCase();
12
- if (!dataProvider || !symbol) return null;
13
- const quote = await dataProvider.getQuote(symbol).catch(() => null);
14
- const name = quote?.name?.trim();
15
- const kind = classifyInstrumentKind(quote?.instrumentType);
16
- return name && kind !== "fund" && kind !== "derivative" ? shortCompanyName(name) : null;
17
- }