@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
@@ -9,6 +9,8 @@ import type { CloudCdsHistoryParams } from "../../../api-client/paths";
9
9
  import type { InstrumentSearchResult } from "../../../types/instrument";
10
10
  import {
11
11
  cdsReferenceEntity,
12
+ closestCoveredIssuers,
13
+ coveredIssuerMatch,
12
14
  issuerNameOf,
13
15
  normalizeCdsTrades,
14
16
  spreadLevelsOf,
@@ -29,6 +31,12 @@ export interface CdsActivity {
29
31
  query: string | null;
30
32
  /** The issuer name actually sent, after any symbol lookup. */
31
33
  issuer: string | null;
34
+ /**
35
+ * Set when a name matched no covered issuer and was sent as typed or as its
36
+ * listing's name: the covered issuers closest to what was typed, for the
37
+ * view to offer when the tape has nothing under it either.
38
+ */
39
+ unmatched?: { nearest: string[] } | null;
32
40
  /** How many days of the tape the trades cover. */
33
41
  days: number;
34
42
  trades: CdsTrade[];
@@ -38,61 +46,164 @@ export type CdsActivityLoader = (issuer: string | null) => Promise<CdsActivity>;
38
46
 
39
47
  export type CdsFetch = (params: { issuer?: string; days?: number; limit?: number }) => Promise<CloudCdsResponse>;
40
48
  export type InstrumentSearch = (query: string, limit?: number) => Promise<InstrumentSearchResult[]>;
49
+ /** The names of the issuers whose 5Y line has a level, as the tape spells them. */
50
+ export type CoveredIssuers = () => Promise<readonly string[]>;
41
51
 
42
52
  /**
43
- * A bare ticker with normal symbol punctuation and no spaces. Anything longer or
44
- * containing a space is already a company name and is sent as the tape would
45
- * name it.
53
+ * A ticker as typed: symbol punctuation, no spaces, and no lowercase letter.
54
+ * `WHR` and `BRK.B` are tickers; `goodyear` and `Paramount` are names.
46
55
  */
47
- const TICKER_LIKE = /^[A-Za-z0-9][A-Za-z0-9.^:-]{0,11}$/;
56
+ const TICKER_LIKE = /^[A-Z0-9][A-Z0-9.^:-]{0,11}$/;
48
57
  const US_PRIMARY_EXCHANGES = new Set(["AMEX", "NASDAQ", "NYSE", "NYSEAMERICAN", "NYSEARCA"]);
49
58
 
59
+ /** The US venues besides the primary exchanges a listing can trade on: a receipt's or a delisted company's line. */
60
+ const US_OTHER_EXCHANGES = new Set(["ARCA", "BATS", "IEX", "OTC", "OTCMKTS", "PNK"]);
61
+
62
+ const listingExchange = (result: InstrumentSearchResult) => (result.primaryExchange || result.exchange).toUpperCase().replace(/[^A-Z]/g, "");
63
+
50
64
  function isPrimaryUsCommonStock(result: InstrumentSearchResult): boolean {
51
- const exchange = (result.primaryExchange || result.exchange).toUpperCase().replace(/[^A-Z]/g, "");
52
65
  const type = result.type.toLowerCase();
53
66
  return result.currency === "USD"
54
- && US_PRIMARY_EXCHANGES.has(exchange)
67
+ && US_PRIMARY_EXCHANGES.has(listingExchange(result))
55
68
  && (type === "stk" || type === "equity" || type.includes("common stock"));
56
69
  }
57
70
 
71
+ /** A line on a US venue in dollars (a receipt, a line over the counter); a listing abroad is not one. */
72
+ function isUsLine(result: InstrumentSearchResult): boolean {
73
+ const exchange = listingExchange(result);
74
+ return (!result.currency || result.currency === "USD") && (US_PRIMARY_EXCHANGES.has(exchange) || US_OTHER_EXCHANGES.has(exchange));
75
+ }
76
+
58
77
  /**
59
- * The backend matches DTCC issuer names, so a ticker has to become a company
60
- * name first: its reference entity when the debt sits with another entity,
61
- * else the listed company's name without its share class ("Alphabet Inc.",
62
- * not "Alphabet Inc. Class A Common Stock"). An untracked symbol has no local
63
- * metadata to expand it, and its quote name never arrives for a fixed
64
- * binding, so the shared instrument search is the only reliable expansion. A
65
- * search outage falls back to the raw input rather than taking the CDS
66
- * request down with it.
78
+ * Whether a listing carries what was typed: the words in its name or a name
79
+ * search knows it by (`searchAliases`), or a ticker it had (`formerIssuer`:
80
+ * BRCM on Broadcom Inc.). The first US hit for a name that does not is
81
+ * another company.
67
82
  */
83
+ function carriesTyped(result: InstrumentSearchResult, typed: string): boolean {
84
+ const symbol = typed.toUpperCase();
85
+ if (result.formerIssuer?.symbol?.toUpperCase() === symbol) return true;
86
+ return [result.name, ...result.searchAliases ?? [], result.formerIssuer?.name]
87
+ .some((name) => !!name && coveredIssuerMatch(typed, [name]) != null);
88
+ }
89
+
90
+ /** The covered issuers' names, from the same request as their levels. */
91
+ export function coveredIssuersFrom(fetchLevels: CdsLevelsFetch): CoveredIssuers {
92
+ return async () => ((await fetchLevels()).levels ?? []).flatMap((level) => (typeof level.issuer === "string" ? [level.issuer] : []));
93
+ }
94
+
95
+ const defaultCoveredIssuers = coveredIssuersFrom(() => apiClient.getCloudCdsLevels());
96
+
97
+ async function searchOrNone(searchInstruments: InstrumentSearch, query: string): Promise<InstrumentSearchResult[]> {
98
+ try {
99
+ return await searchInstruments(query);
100
+ } catch {
101
+ // Intentional ignore: a search outage sends the name as typed instead of failing the CDS request.
102
+ return [];
103
+ }
104
+ }
105
+
106
+ async function coveredOrNone(coveredIssuers: CoveredIssuers): Promise<readonly string[]> {
107
+ try {
108
+ return await coveredIssuers();
109
+ } catch {
110
+ // Intentional ignore: without the covered list the name goes on to the listing and the tape.
111
+ return [];
112
+ }
113
+ }
114
+
115
+ interface CdsIssuerResolution {
116
+ /** The name the tape is asked for. */
117
+ issuer: string;
118
+ /** Set when a name matched no covered issuer; see `CdsActivity.unmatched`. */
119
+ unmatched: { nearest: string[] } | null;
120
+ }
121
+
122
+ /**
123
+ * The backend matches DTCC issuer names, so what was typed has to become one.
124
+ *
125
+ * Any one-word input, whatever its case (`CHTR`, `chtr`), is first looked up
126
+ * among the tickers whose debt sits with another entity: CHTR is CCO
127
+ * Holdings, LLC.
128
+ *
129
+ * A ticker (`WHR`) becomes the listed company's name without its share class
130
+ * ("Alphabet Inc.", not "Alphabet Inc. Class A Common Stock"): its primary US
131
+ * common stock where exchanges reuse the symbol, else its US line. An
132
+ * untracked symbol has no local metadata to expand it, and its quote name
133
+ * never arrives for a fixed binding, so the shared instrument search is the
134
+ * only reliable expansion.
135
+ *
136
+ * Then the names of the covered issuers (`goodyear`, `GOODYEAR`, `paramount `
137
+ * are The Goodyear Tire & Rubber Company and Paramount Global), by words of
138
+ * three letters or more: `f` and `ge` go to the symbol they are. Only then a
139
+ * listing abroad with that exact symbol, and only when it names a covered
140
+ * issuer: Goodyear India, the first hit for `goodyear`, is no CDS name. Last,
141
+ * the primary US common stock with the symbol, or the first one whose name
142
+ * carries what was typed. Nothing matched, the input goes to the tape as
143
+ * typed, with the covered issuers closest to it.
144
+ *
145
+ * A longer name in capitals and lowercase is already a company name and is
146
+ * sent as written. A search outage falls back to the raw input rather than
147
+ * taking the CDS request down with it.
148
+ */
149
+ async function resolveCdsIssuer(
150
+ query: string,
151
+ searchInstruments: InstrumentSearch,
152
+ coveredIssuers: CoveredIssuers = defaultCoveredIssuers,
153
+ ): Promise<CdsIssuerResolution> {
154
+ const ticker = TICKER_LIKE.test(query);
155
+ const oneWord = !/\s/.test(query);
156
+ if (!oneWord && query !== query.toLowerCase()) return { issuer: issuerNameOf(query), unmatched: null };
157
+ const referenceEntity = oneWord ? cdsReferenceEntity(query) : null;
158
+ if (referenceEntity) return { issuer: referenceEntity, unmatched: null };
159
+ const symbol = query.toUpperCase();
160
+ let results: InstrumentSearchResult[] | null = null;
161
+ let abroad: InstrumentSearchResult | undefined;
162
+ if (ticker) {
163
+ results = await searchOrNone(searchInstruments, query);
164
+ const exactMatches = results.filter((result) => result.symbol.toUpperCase() === symbol);
165
+ const name = (exactMatches.find(isPrimaryUsCommonStock) ?? exactMatches.find(isUsLine))?.name?.trim();
166
+ if (name) return { issuer: issuerNameOf(name), unmatched: null };
167
+ abroad = exactMatches.find((result) => !!result.name?.trim());
168
+ }
169
+ const covered = await coveredOrNone(coveredIssuers);
170
+ const named = coveredIssuerMatch(query, covered);
171
+ if (named) return { issuer: named, unmatched: null };
172
+ // A line abroad never wins on its own: only as the covered issuer it names.
173
+ const abroadIssuer = abroad ? coveredIssuerMatch(issuerNameOf(abroad.name.trim()), covered) : null;
174
+ if (abroadIssuer) return { issuer: abroadIssuer, unmatched: null };
175
+ results ??= await searchOrNone(searchInstruments, query);
176
+ const primary = results.filter(isPrimaryUsCommonStock);
177
+ const listing = (primary.find((result) => result.symbol.toUpperCase() === symbol) ?? primary.find((result) => carriesTyped(result, query)))?.name?.trim();
178
+ if (listing) {
179
+ const issuer = issuerNameOf(listing);
180
+ const listed = coveredIssuerMatch(issuer, covered);
181
+ return listed
182
+ ? { issuer: listed, unmatched: null }
183
+ : { issuer, unmatched: { nearest: closestCoveredIssuers(query, covered) } };
184
+ }
185
+ return { issuer: ticker ? query : issuerNameOf(query), unmatched: { nearest: closestCoveredIssuers(query, covered) } };
186
+ }
187
+
188
+ /** The name `resolveCdsIssuer` sends to the tape, for a caller that only needs that. */
68
189
  export async function resolveIssuerName(
69
190
  issuer: string,
70
191
  searchInstruments: InstrumentSearch,
192
+ coveredIssuers?: CoveredIssuers,
71
193
  ): Promise<string> {
72
- if (!TICKER_LIKE.test(issuer)) return issuerNameOf(issuer);
73
- const referenceEntity = cdsReferenceEntity(issuer);
74
- if (referenceEntity) return referenceEntity;
75
- try {
76
- const results = await searchInstruments(issuer);
77
- const symbol = issuer.toUpperCase();
78
- const exactMatches = results.filter((result) => result.symbol.toUpperCase() === symbol);
79
- const match = exactMatches.find(isPrimaryUsCommonStock) ?? exactMatches[0] ?? results[0];
80
- const name = match?.name?.trim();
81
- return name ? issuerNameOf(name) : issuer;
82
- } catch {
83
- return issuer;
84
- }
194
+ return (await resolveCdsIssuer(issuer, searchInstruments, coveredIssuers)).issuer;
85
195
  }
86
196
 
87
197
  export async function loadCdsActivity(
88
198
  issuer: string | null,
89
199
  fetchCds: CdsFetch = (params) => apiClient.getCloudCds(params),
90
200
  searchInstruments: InstrumentSearch = (query, limit) => apiClient.searchInstruments(query, limit),
201
+ coveredIssuers: CoveredIssuers = defaultCoveredIssuers,
91
202
  ): Promise<CdsActivity> {
92
203
  const query = issuer?.trim() || null;
93
- const resolved = query ? await resolveIssuerName(query, searchInstruments) : null;
204
+ const resolved = query ? await resolveCdsIssuer(query, searchInstruments, coveredIssuers) : null;
94
205
  const response = await fetchCds({
95
- ...(resolved ? { issuer: resolved } : {}),
206
+ ...(resolved ? { issuer: resolved.issuer } : {}),
96
207
  days: CDS_TRADE_DAYS,
97
208
  limit: CDS_TRADE_LIMIT,
98
209
  });
@@ -100,7 +211,8 @@ export async function loadCdsActivity(
100
211
  source: response.source,
101
212
  asOf: response.asOf,
102
213
  query,
103
- issuer: resolved,
214
+ issuer: resolved?.issuer ?? null,
215
+ ...(resolved?.unmatched ? { unmatched: resolved.unmatched } : {}),
104
216
  days: CDS_TRADE_DAYS,
105
217
  trades: normalizeCdsTrades(response.trades ?? []),
106
218
  };
@@ -9,6 +9,7 @@ import type {
9
9
  } from "../../../types/plugin";
10
10
  import {
11
11
  CDS_TRADE_DAYS,
12
+ coveredIssuersFrom,
12
13
  loadCdsActivity,
13
14
  loadCdsSpreadHistory,
14
15
  loadCdsSpreadLevels,
@@ -17,6 +18,7 @@ import {
17
18
  type CdsHistoryFetch,
18
19
  type CdsLevelsFetch,
19
20
  type CdsSpreadHistory,
21
+ type CoveredIssuers,
20
22
  type InstrumentSearch,
21
23
  } from "./client";
22
24
  import { formatContract } from "../credit-boards/model";
@@ -35,6 +37,7 @@ import {
35
37
  formatSpreadMove,
36
38
  formatSpreadRange,
37
39
  formatUpfront,
40
+ hasPointsQuotes,
38
41
  issuerListSort,
39
42
  quotedLevel,
40
43
  lastTradeOf,
@@ -154,7 +157,7 @@ function tradeForFormatting(row: Record<string, unknown>): CdsTrade {
154
157
  return row as unknown as CdsTrade;
155
158
  }
156
159
 
157
- const TRADE_COLUMNS = [
160
+ const TRADE_COLUMNS: HeadlessPaneColumn[] = [
158
161
  { key: "eventAt", header: "Time UTC", shrink: false, format: (value: unknown) => formatEventTime(Number(value)) },
159
162
  { key: "issuer", header: "Issuer" },
160
163
  { key: "maturity", header: "Maturity" },
@@ -175,6 +178,22 @@ const TRADE_COLUMNS = [
175
178
  },
176
179
  ];
177
180
 
181
+ /**
182
+ * A print that quoted points upfront in its spread field reads in a Points
183
+ * column, shown when one did; exports write the number as quoted.
184
+ */
185
+ const POINTS_COLUMN: HeadlessPaneColumn = {
186
+ key: "pointsUpfront",
187
+ header: "Points",
188
+ align: "right",
189
+ format: (value: unknown) => (typeof value === "number" ? String(value) : "--"),
190
+ textFormat: (value: unknown) => formatPoints(numberOrNull(value)),
191
+ };
192
+
193
+ const tradeColumns = (points: boolean): HeadlessPaneColumn[] => (
194
+ points ? [...TRADE_COLUMNS.slice(0, -1), POINTS_COLUMN, TRADE_COLUMNS.at(-1)!] : TRADE_COLUMNS
195
+ );
196
+
178
197
  /**
179
198
  * The pane's figures over its 5Y chart, as label and value lines: the level,
180
199
  * its date, the 1W and 1M moves through the roll and the range charted, in
@@ -227,24 +246,29 @@ interface CdsReportInputs {
227
246
  function projectCdsHeadless({ activity, history, levels, sort, days = DAILY_DAYS.defaultValue }: CdsReportInputs): HeadlessBundleResult {
228
247
  const errors: string[] = [];
229
248
  const notes: string[] = [];
230
- const empty = noTradesMessage({ query: activity.query, issuer: activity.issuer, days: activity.days });
231
249
  const metadata = { issuer: activity.issuer, days: activity.days };
232
250
  if (activity.query) {
233
251
  const line = history instanceof Error || !history ? null : history;
234
252
  if (history instanceof Error) errors.push("5Y spread history unavailable.");
253
+ // A name that matched no covered issuer, with no trades and no line under it on the tape either.
254
+ const unmatched = activity.unmatched && !activity.trades.length && line && !line.issuer ? activity.unmatched : null;
255
+ const empty = noTradesMessage({ query: activity.query, issuer: activity.issuer, days: activity.days, unmatched });
235
256
  const entries = line ? spreadEntries(line) : [];
236
- // The name the tape was queried for heads the figures, as in the pane, so a ticker says what it became.
237
- const name = displayIssuerName(activity.issuer ?? line?.issuer ?? activity.query);
257
+ // The name the tape was queried for heads the figures, as in the pane, so a ticker says what it became;
258
+ // a name sent as typed takes the one the tape answered with.
259
+ const name = displayIssuerName((activity.unmatched ? line?.issuer : null) ?? activity.issuer ?? line?.issuer ?? activity.query);
238
260
  const why = line ? noLevelMessage(line.noLevelReason, line.points.at(-1)?.date ?? null) : null;
239
261
  if (why) notes.push(`${why}.`);
240
- else if (line && entries.length === 0) notes.push(`No 5Y spread level for ${name} in the last two years.`);
262
+ else if (unmatched) {
263
+ if (!unmatched.nearest.length) notes.push("fn CDS lists the covered issuers.");
264
+ } else if (line && entries.length === 0) notes.push(`No 5Y spread level for ${name} in the last two years.`);
241
265
  // The 5Y figures are dated by their level's day, each trade and each daily level by its own.
242
266
  const levelDate = line ? spreadSummary(line)?.date : undefined;
243
267
  const sections: HeadlessBundleSection[] = [
244
268
  ...(entries.length ? [{ title: name, entries, symbol: name, ...(levelDate ? { asOf: levelDate } : {}) }] : []),
245
269
  {
246
270
  title: "Trades",
247
- columns: TRADE_COLUMNS,
271
+ columns: tradeColumns(hasPointsQuotes(activity.trades)),
248
272
  rows: sortTrades(activity.trades, DEFAULT_TRADE_SORT).map((trade) => ({ ...trade })),
249
273
  empty,
250
274
  rowSymbolKey: "issuer",
@@ -265,6 +289,7 @@ function projectCdsHeadless({ activity, history, levels, sort, days = DAILY_DAYS
265
289
  ...(notes.length ? { notes } : {}),
266
290
  };
267
291
  }
292
+ const empty = noTradesMessage({ query: null, issuer: null, days: activity.days });
268
293
  if (levels instanceof Error) errors.push("5Y spreads unavailable.");
269
294
  const issuers = withSpreadLevels(summarizeIssuers(activity.trades), levels instanceof Error ? null : levels ?? null);
270
295
  return {
@@ -294,14 +319,15 @@ export interface CdsHeadlessDependencies {
294
319
  issuer: string | null,
295
320
  fetchCds: CdsFetch,
296
321
  searchInstruments: InstrumentSearch,
322
+ coveredIssuers: CoveredIssuers,
297
323
  ): Promise<CdsActivity>;
298
324
  loadHistory?(issuer: string, fetchHistory: CdsHistoryFetch): Promise<CdsSpreadHistory>;
299
325
  loadLevels?: typeof loadCdsSpreadLevels;
300
326
  }
301
327
 
302
328
  const defaultDependencies: CdsHeadlessDependencies = {
303
- load: (_args, issuer, fetchCds, searchInstruments) => (
304
- loadCdsActivity(issuer, fetchCds, searchInstruments)
329
+ load: (_args, issuer, fetchCds, searchInstruments, coveredIssuers) => (
330
+ loadCdsActivity(issuer, fetchCds, searchInstruments, coveredIssuers)
305
331
  ),
306
332
  loadHistory: (issuer, fetchHistory) => loadCdsSpreadHistory(issuer, fetchHistory),
307
333
  loadLevels: loadCdsSpreadLevels,
@@ -353,6 +379,7 @@ export function createCdsHeadless(
353
379
  issuer,
354
380
  (params) => ctx.apiClient.getCloudCds(params),
355
381
  (query, limit) => ctx.apiClient.searchInstruments(query, limit),
382
+ coveredIssuersFrom(fetchLevels),
356
383
  );
357
384
  if (activity.query) {
358
385
  // The line follows the name the tape was queried for, as the pane's does.
@@ -5,6 +5,7 @@ import type { HeadlessPaneFreshness } from "../../../types/headless";
5
5
  import type { TickerRecord } from "../../../types/ticker";
6
6
  import { formatCompact } from "../../../utils/format";
7
7
  import { compareSortValues, type SortPreference } from "../../../utils/sort-values";
8
+ import { typoDistance } from "../../../utils/typo-distance";
8
9
  import { formatMove } from "../credit-boards/model";
9
10
 
10
11
  export const CDS_PANE_ID = "cds";
@@ -20,7 +21,7 @@ const DECIMAL_TO_BP = 10_000;
20
21
 
21
22
  export interface CdsTrade {
22
23
  id: string;
23
- /** Reported spelling, kept verbatim so a display name can be chosen later. */
24
+ /** The issuer's best spelling on the tape, the same on every print of it (see `normalizeCdsTrades`). */
24
25
  issuer: string;
25
26
  /** Alias-collapsed grouping key. Internal only; never shown or sent. */
26
27
  issuerKey: string;
@@ -31,8 +32,13 @@ export interface CdsTrade {
31
32
  notionalCapped: boolean;
32
33
  currency: string | null;
33
34
  couponBp: number | null;
34
- /** Only what the report carried. Never derived from upfront or coupon. */
35
+ /**
36
+ * Only what the report carried. Never derived from upfront or coupon, and
37
+ * empty when the field held points upfront (`pointsUpfront`).
38
+ */
35
39
  spreadBp: number | null;
40
+ /** The quote of a print whose spread field held points upfront, not bp. */
41
+ pointsUpfront: number | null;
36
42
  /** Source inputs retained even when their notation cannot supply a bp rate. */
37
43
  reportedSpread: number | null;
38
44
  spreadNotation: string | null;
@@ -143,6 +149,66 @@ export function issuerGroupKey(name: string): string {
143
149
  return (meaningful.length > 0 ? meaningful : tokens).join(" ") || name.trim().toLowerCase();
144
150
  }
145
151
 
152
+ /** Fewer letters than this are a symbol, not words of a name: `f` is Ford's ticker, not "F&G Annuities & Life". */
153
+ const MIN_NAME_LETTERS = 3;
154
+
155
+ /**
156
+ * The covered issuer a name stands for, by its words: the issuer of that very
157
+ * name, else one whose name holds those words in a row (`goodyear` is "The
158
+ * Goodyear Tire & Rubber Company"), one that starts with them first, then the
159
+ * shortest, so `ford` is Ford Motor Company and not Ford Motor Credit. Null
160
+ * when none does, and for fewer than three letters (`f`, `ge`), which are a
161
+ * symbol to look up.
162
+ */
163
+ export function coveredIssuerMatch(name: string, covered: readonly string[]): string | null {
164
+ const wanted = issuerGroupKey(displayIssuerName(name));
165
+ if (wanted.replace(/[^\p{L}\p{N}]/gu, "").length < MIN_NAME_LETTERS) return null;
166
+ let best: { issuer: string; leading: boolean; words: number } | null = null;
167
+ for (const issuer of covered) {
168
+ const key = issuerGroupKey(displayIssuerName(issuer));
169
+ if (key === wanted) return displayIssuerName(issuer);
170
+ if (!` ${key} `.includes(` ${wanted} `)) continue;
171
+ const candidate = { issuer: displayIssuerName(issuer), leading: key.startsWith(`${wanted} `), words: key.split(" ").length };
172
+ if (!best
173
+ || (candidate.leading !== best.leading ? candidate.leading
174
+ : candidate.words !== best.words ? candidate.words < best.words
175
+ : candidate.issuer.localeCompare(best.issuer) < 0)) best = candidate;
176
+ }
177
+ return best?.issuer ?? null;
178
+ }
179
+
180
+ /** How many covered issuers an unmatched name offers. */
181
+ const CLOSEST_ISSUERS = 3;
182
+
183
+ /**
184
+ * How near a word is to one of a name's: a slip of the keys, or the same first
185
+ * letters (`goodyer`, `paramo`). Short words never count, or every name with
186
+ * "co" in it would be close.
187
+ */
188
+ function wordCloseness(typed: string, word: string): number {
189
+ if (typed.length < 3 || word.length < 3) return 0;
190
+ if (typed.length >= 4 && typoDistance(typed, word) <= 1) return typed.length;
191
+ let shared = 0;
192
+ while (shared < Math.min(typed.length, word.length) && typed[shared] === word[shared]) shared += 1;
193
+ return shared >= 4 || (shared >= 3 && shared === typed.length) ? shared : 0;
194
+ }
195
+
196
+ /** The covered issuers closest to a name that matched none, closest first; none when nothing is near. */
197
+ export function closestCoveredIssuers(name: string, covered: readonly string[]): string[] {
198
+ const typed = issuerGroupKey(displayIssuerName(name)).split(" ").filter(Boolean);
199
+ const scored = new Map<string, number>();
200
+ for (const issuer of covered) {
201
+ const words = issuerGroupKey(displayIssuerName(issuer)).split(" ");
202
+ const score = typed.reduce((sum, word) => sum + Math.max(0, ...words.map((other) => wordCloseness(word, other))), 0);
203
+ const shown = displayIssuerName(issuer);
204
+ if (score > 0 && score > (scored.get(shown) ?? 0)) scored.set(shown, score);
205
+ }
206
+ return [...scored]
207
+ .sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
208
+ .slice(0, CLOSEST_ISSUERS)
209
+ .map(([issuer]) => issuer);
210
+ }
211
+
146
212
  /**
147
213
  * DTCC writes a comma inside a field as `;` ("CCO Holdings;LLC",
148
214
  * "Amazon.com;Inc."), so a name gets its comma back before it is shown,
@@ -207,6 +273,37 @@ function couponToBasisPoints(fixedRate: number | null): number | null {
207
273
  return fixedRate * DECIMAL_TO_BP;
208
274
  }
209
275
 
276
+ /**
277
+ * How far a reported quote may sit from the points its upfront works out to
278
+ * and still be read as those points: accrued and rounding move the amount by
279
+ * about a tenth of a point.
280
+ */
281
+ const POINTS_MATCH_PTS = 0.5;
282
+ const POINTS_MATCH_SHARE = 0.05;
283
+
284
+ /**
285
+ * Some desks key a distressed name's points upfront into the spread field and
286
+ * leave the coupon blank: Whirlpool's Dec 2028 contract printed `0.00065`,
287
+ * 6.5 read as bp, beside 319,861 upfront on 5M, which is 6.4 points. Without a
288
+ * coupon nothing makes the field a running spread, and when the amount over
289
+ * the notional reads as the same number in points, it is that many points.
290
+ * A real spread that low on a running coupon would need the upfront to land on
291
+ * the same number by chance, below 5bp.
292
+ */
293
+ function pointsUpfrontOf(
294
+ spreadBp: number | null,
295
+ couponBp: number | null,
296
+ upfront: number | null,
297
+ notional: number | null,
298
+ ): number | null {
299
+ if (couponBp != null || spreadBp == null || !(spreadBp > 0)) return null;
300
+ if (upfront == null || !Number.isFinite(upfront) || notional == null || !(notional > 0)) return null;
301
+ const impliedPts = (Math.abs(upfront) / notional) * 100;
302
+ const tolerance = Math.max(POINTS_MATCH_PTS, impliedPts * POINTS_MATCH_SHARE);
303
+ // The decimal the field holds carries float noise (0.001525 is 15.249999...).
304
+ return Math.abs(spreadBp - impliedPts) <= tolerance ? Math.round(spreadBp * 1e6) / 1e6 : null;
305
+ }
306
+
210
307
  /**
211
308
  * When the trade was struck, not when the tape carried it. A lifecycle
212
309
  * correction is disseminated long after the fact, so using the event time would
@@ -232,32 +329,46 @@ function issuerOf(trade: CloudCdsTradePayload): string | null {
232
329
  return underlier && identifiesIssuer(underlier) ? displayIssuerName(underlier) : null;
233
330
  }
234
331
 
332
+ /**
333
+ * The tape's prints as rows. One issuer reads as one name on every print:
334
+ * the tape spells Whirlpool three ways in a week ("WHIRLPOOL CORPORATION",
335
+ * "Whirlpool Corp", "Whirlpool Corporation"), and each row takes the spelling
336
+ * the issuer list shows for them all.
337
+ */
235
338
  export function normalizeCdsTrades(trades: readonly CloudCdsTradePayload[]): CdsTrade[] {
236
339
  const rows: CdsTrade[] = [];
340
+ const names = new Map<string, string>();
237
341
  for (const trade of trades) {
238
342
  const eventAt = tapeTime(trade);
239
343
  if (eventAt == null) continue;
240
344
  const issuer = issuerOf(trade);
241
345
  // Nothing names the reference entity, so the row would invent one.
242
346
  if (!issuer) continue;
347
+ const issuerKey = issuerGroupKey(issuer);
348
+ const named = names.get(issuerKey);
349
+ names.set(issuerKey, named ? preferredIssuerName(named, issuer) : issuer);
350
+ const couponBp = couponToBasisPoints(trade.fixedRate);
351
+ const reportedBp = spreadToBasisPoints(trade.reportedSpread, trade.spreadNotation);
352
+ const pointsUpfront = pointsUpfrontOf(reportedBp, couponBp, trade.upfrontAmount, trade.notionalAmount);
243
353
  rows.push({
244
354
  id: trade.disseminationId,
245
355
  issuer,
246
- issuerKey: issuerGroupKey(issuer),
356
+ issuerKey,
247
357
  eventAt,
248
358
  maturity: trade.maturityDate ?? trade.expirationDate,
249
359
  notional: trade.notionalAmount,
250
360
  notionalCapped: trade.notionalCapped,
251
361
  currency: trade.notionalCurrency,
252
- couponBp: couponToBasisPoints(trade.fixedRate),
253
- spreadBp: spreadToBasisPoints(trade.reportedSpread, trade.spreadNotation),
362
+ couponBp,
363
+ spreadBp: pointsUpfront == null ? reportedBp : null,
364
+ pointsUpfront,
254
365
  reportedSpread: trade.reportedSpread,
255
366
  spreadNotation: trade.spreadNotation,
256
367
  upfront: trade.upfrontAmount,
257
368
  upfrontCurrency: trade.upfrontCurrency,
258
369
  });
259
370
  }
260
- return rows;
371
+ return rows.map((row) => ({ ...row, issuer: names.get(row.issuerKey) ?? row.issuer }));
261
372
  }
262
373
 
263
374
  /**
@@ -405,13 +516,23 @@ export function cdsFreshness(lastTrade: number | string | null): HeadlessPaneFre
405
516
  * What an empty view says: the window searched and, for one name, the
406
517
  * reference entity the query became, with what was typed when that differs.
407
518
  */
408
- export function noTradesMessage({ query, issuer, days }: {
519
+ export function noTradesMessage({ query, issuer, days, unmatched = null }: {
409
520
  query: string | null;
410
521
  issuer: string | null;
411
522
  days: number;
523
+ /**
524
+ * A name that matched no covered issuer, once the tape has no trades and no
525
+ * line under it either: what the view says instead is that no issuer
526
+ * matches, with the covered ones closest to what was typed.
527
+ */
528
+ unmatched?: { nearest: readonly string[] } | null;
412
529
  }): string {
413
530
  const window = `in the last ${days} days`;
414
531
  if (!query) return `No single-name CDS trades ${window}.`;
532
+ if (unmatched) {
533
+ const nearest = unmatched.nearest.length ? ` Closest covered issuers: ${unmatched.nearest.join(", ")}.` : "";
534
+ return `No CDS issuer matches ${query}.${nearest}`;
535
+ }
415
536
  const name = issuer && issuer.toUpperCase() !== query.toUpperCase() ? `${issuer} (${query})` : issuer ?? query;
416
537
  return `No CDS trades for ${name} ${window}.`;
417
538
  }
@@ -610,22 +731,25 @@ export function sortIssuers(
610
731
  });
611
732
  }
612
733
 
613
- export type TradeColumnId = "time" | "maturity" | "notional" | "currency" | "coupon" | "spread" | "upfront";
734
+ export type TradeColumnId = "time" | "maturity" | "notional" | "currency" | "coupon" | "spread" | "points" | "upfront";
614
735
  export type TradeColumn = DataTableColumn & { id: TradeColumnId };
615
736
  export type TradeSortPreference = SortPreference<TradeColumnId>;
616
737
 
617
- export const TRADE_SORT_COLUMN_IDS: readonly TradeColumnId[] = [
618
- "time",
619
- "maturity",
620
- "notional",
621
- "coupon",
622
- "spread",
623
- "upfront",
624
- ];
625
738
  export const DEFAULT_TRADE_SORT: TradeSortPreference = { columnId: "time", direction: "desc" };
626
739
 
627
- export function buildTradeColumns(width: number): TradeColumn[] {
628
- const maturityWidth = Math.max(10, Math.min(12, width - 59));
740
+ /** Whether any print was quoted in points upfront, which is when the trades show a Points column. */
741
+ export function hasPointsQuotes(trades: readonly Pick<CdsTrade, "pointsUpfront">[]): boolean {
742
+ return trades.some((trade) => trade.pointsUpfront != null);
743
+ }
744
+
745
+ /** The columns `]` and `[` cycle the trades' sort through: the ones shown. */
746
+ export function tradeSortColumnIds(points: boolean): readonly TradeColumnId[] {
747
+ return ["time", "maturity", "notional", "coupon", "spread", ...(points ? ["points" as const] : []), "upfront"];
748
+ }
749
+
750
+ /** A Points column sits beside Spread only when a print was quoted in points. */
751
+ export function buildTradeColumns(width: number, points = false): TradeColumn[] {
752
+ const maturityWidth = Math.max(10, Math.min(12, width - 59 - (points ? 9 : 0)));
629
753
  return [
630
754
  { id: "time", label: "TIME UTC", width: 16, align: "left" },
631
755
  { id: "maturity", label: "MATURITY", width: maturityWidth, align: "left" },
@@ -633,6 +757,7 @@ export function buildTradeColumns(width: number): TradeColumn[] {
633
757
  { id: "currency", label: "CCY", width: 5, align: "left" },
634
758
  { id: "coupon", label: "COUPON", width: 9, align: "right" },
635
759
  { id: "spread", label: "SPREAD", width: 10, align: "right" },
760
+ ...(points ? [{ id: "points" as const, label: "POINTS", width: 9, align: "right" as const }] : []),
636
761
  { id: "upfront", label: "ABS UPFRONT", width: 13, align: "right" },
637
762
  ];
638
763
  }
@@ -651,6 +776,8 @@ function tradeSortValue(columnId: TradeColumnId, row: CdsTrade): string | number
651
776
  return row.couponBp;
652
777
  case "spread":
653
778
  return row.spreadBp;
779
+ case "points":
780
+ return row.pointsUpfront;
654
781
  case "upfront":
655
782
  return row.upfront;
656
783
  }