gloomberb 0.17.4 → 0.17.6

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 (384) hide show
  1. package/package.json +3 -1
  2. package/patches/electrobun@1.18.1.patch +80 -0
  3. package/src/api-client/chat.ts +9 -0
  4. package/src/api-client/commodity-balances.ts +77 -0
  5. package/src/api-client/data.ts +35 -3
  6. package/src/api-client/earnings.ts +10 -0
  7. package/src/api-client/index.ts +5 -0
  8. package/src/api-client/insider-signals.ts +75 -0
  9. package/src/api-client/market-discovery.ts +1 -1
  10. package/src/api-client/normalizers.ts +1 -0
  11. package/src/api-client/paths.ts +9 -0
  12. package/src/api-client/research-activity.ts +10 -3
  13. package/src/api-client/socket.ts +10 -0
  14. package/src/api-client/types.ts +25 -1
  15. package/src/app/pane-runtime/ticker-open-runtime.ts +2 -1
  16. package/src/app/runtime/broker-import.ts +23 -21
  17. package/src/app/runtime/plugin-bindings.ts +13 -7
  18. package/src/app/runtime/update.ts +64 -4
  19. package/src/app.tsx +2 -0
  20. package/src/architecture/core-builtin-allowlist.json +2 -10
  21. package/src/ask-gloom/pane-context.ts +11 -1
  22. package/src/brokers/broker-portfolio-sync.ts +66 -0
  23. package/src/brokers/sync-broker-instance.ts +75 -41
  24. package/src/cli/commands/market.ts +101 -34
  25. package/src/cli/commands/options-chain.ts +20 -6
  26. package/src/cli/commands/overview.ts +54 -13
  27. package/src/cli/commands/search.ts +1 -1
  28. package/src/cli/commands/system.ts +19 -1
  29. package/src/cli/commands/ticker.ts +24 -4
  30. package/src/cli/context.ts +13 -0
  31. package/src/cli/errors.ts +15 -2
  32. package/src/cli/fx-pair.ts +77 -0
  33. package/src/cli/glossary.ts +34 -4
  34. package/src/cli/history-rows.ts +25 -11
  35. package/src/cli/index.ts +30 -12
  36. package/src/cli/listing-arg.ts +61 -138
  37. package/src/cli/listing-suggestion.ts +3 -21
  38. package/src/cli/pane-functions/catalog.ts +50 -0
  39. package/src/cli/pane-functions/freshness.ts +44 -9
  40. package/src/cli/pane-functions/function-help.ts +9 -9
  41. package/src/cli/pane-functions/headless.ts +19 -7
  42. package/src/cli/pane-functions/index.ts +63 -5
  43. package/src/cli/pane-functions/market-session.ts +18 -3
  44. package/src/cli/pane-functions/resolver.ts +29 -8
  45. package/src/cli/pane-functions/screenshot.ts +2 -0
  46. package/src/cli/report-tables.ts +11 -4
  47. package/src/cli/result.ts +2 -0
  48. package/src/cli/series-search.ts +6 -1
  49. package/src/cli/ticker-resolution.ts +1 -2
  50. package/src/cli/unknown-command.ts +18 -0
  51. package/src/components/chart/composite/composite-chart.tsx +80 -19
  52. package/src/components/chart/composite/format.ts +17 -0
  53. package/src/components/chart/composite/legend.tsx +26 -15
  54. package/src/components/chart/composite/panel-labels.ts +38 -0
  55. package/src/components/chart/composite/panel-series.ts +1 -0
  56. package/src/components/chart/composite/panel-surface.tsx +25 -7
  57. package/src/components/chart/composite/price-axis-labels.tsx +16 -3
  58. package/src/components/chart/composite/scene.ts +60 -2
  59. package/src/components/chart/composite/series-axis-tags.tsx +67 -0
  60. package/src/components/chart/composite/tools.ts +2 -2
  61. package/src/components/chart/composite/types.ts +28 -0
  62. package/src/components/chart/core/view-reset.ts +46 -0
  63. package/src/components/command-bar/commands/collection.ts +1 -1
  64. package/src/components/command-bar/commands/direct/index.ts +48 -1
  65. package/src/components/command-bar/commands/registry.ts +8 -0
  66. package/src/components/command-bar/list/model.ts +7 -0
  67. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  68. package/src/components/command-bar/panel/prompt-binding.ts +8 -0
  69. package/src/components/command-bar/routes/root/command-items.ts +17 -2
  70. package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
  71. package/src/components/command-bar/routes/root/provider-search.ts +6 -0
  72. package/src/components/command-bar/routes/root/runtime.ts +3 -0
  73. package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
  74. package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
  75. package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
  76. package/src/components/command-bar/surface/index.tsx +7 -1
  77. package/src/components/command-bar/workflow/collection-actions.ts +6 -5
  78. package/src/components/command-bar/workflow/ops.ts +8 -5
  79. package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
  80. package/src/components/command-bar/workflow/tickers.ts +1 -1
  81. package/src/components/form-modal/confirm.tsx +1 -0
  82. package/src/components/form-modal/request.ts +2 -0
  83. package/src/components/layout/detached-pane-shell.tsx +6 -1
  84. package/src/components/layout/pane/content.tsx +11 -8
  85. package/src/components/layout/pane/sidebar.tsx +24 -11
  86. package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
  87. package/src/components/layout/shell/index.tsx +5 -1
  88. package/src/components/layout/shell/menu.ts +8 -0
  89. package/src/components/layout/shell/pane/capture.ts +29 -12
  90. package/src/components/onboarding/wizard-model.ts +1 -1
  91. package/src/components/onboarding/wizard-positions.ts +18 -10
  92. package/src/components/ticker/badge/format.ts +64 -15
  93. package/src/components/ticker/badge/index.tsx +32 -11
  94. package/src/components/ticker/badge/list.tsx +4 -3
  95. package/src/components/ticker/badge/text.tsx +2 -2
  96. package/src/components/ticker/list-table-view.tsx +8 -1
  97. package/src/components/ui/confirm-dialog.tsx +30 -4
  98. package/src/components/ui/data-table/opentui/index.tsx +61 -20
  99. package/src/components/ui/data-table/opentui/model.ts +104 -0
  100. package/src/components/ui/fields.tsx +5 -0
  101. package/src/components/ui/inline-quick-add.tsx +4 -1
  102. package/src/components/ui/query-bar-keys.tsx +29 -5
  103. package/src/components/ui/query-bar.tsx +29 -4
  104. package/src/components/ui/windowed-list.tsx +27 -0
  105. package/src/data/config/store/normalize.ts +8 -0
  106. package/src/docs/snapshot.json +248 -5
  107. package/src/i18n/es.ts +3 -1
  108. package/src/i18n/ja.ts +3 -1
  109. package/src/i18n/ko.ts +3 -1
  110. package/src/i18n/zh-cn.ts +3 -1
  111. package/src/i18n/zh-tw.ts +3 -1
  112. package/src/layout-marketplace/cloud.ts +16 -5
  113. package/src/layout-marketplace/gallery.tsx +4 -4
  114. package/src/layout-marketplace/linked-sync.ts +9 -3
  115. package/src/layout-marketplace/linked.ts +24 -3
  116. package/src/layout-marketplace/model.ts +39 -4
  117. package/src/market-data/coordinator/events.ts +5 -1
  118. package/src/market-data/coordinator/index.ts +38 -1
  119. package/src/market-data/coordinator/quotes.ts +4 -0
  120. package/src/market-data/futures-contract.ts +53 -0
  121. package/src/market-data/hooks.tsx +225 -30
  122. package/src/market-data/market/bar-period.ts +71 -0
  123. package/src/market-data/market/format.ts +14 -2
  124. package/src/market-data/market/freshness.ts +28 -1
  125. package/src/market-data/market/status.ts +152 -13
  126. package/src/market-data/model-dividend-yield.ts +76 -0
  127. package/src/market-data/quotes/freshness.ts +30 -1
  128. package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
  129. package/src/market-data/quotes/regular-session-valuation.ts +32 -1
  130. package/src/plugins/builtin/analytics/metrics.ts +8 -4
  131. package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
  132. package/src/plugins/builtin/analytics/risk-model.ts +74 -15
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
  134. package/src/plugins/builtin/broker-manager/detail.tsx +2 -2
  135. package/src/plugins/builtin/broker-manager/model.ts +2 -2
  136. package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
  137. package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
  138. package/src/plugins/builtin/chart-composer/formula.ts +233 -0
  139. package/src/plugins/builtin/chart-composer/headless.ts +27 -2
  140. package/src/plugins/builtin/chart-composer/index.tsx +3 -2
  141. package/src/plugins/builtin/chart-composer/pane.tsx +41 -10
  142. package/src/plugins/builtin/chart-composer/presets.ts +91 -2
  143. package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
  144. package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
  145. package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
  146. package/src/plugins/builtin/chart-composer/settings.ts +16 -4
  147. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  148. package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
  149. package/src/plugins/builtin/chat/content/footer.tsx +5 -2
  150. package/src/plugins/builtin/chat/content/index.tsx +12 -5
  151. package/src/plugins/builtin/chat/content/mentions.ts +50 -21
  152. package/src/plugins/builtin/chat/content/scroll.ts +12 -5
  153. package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
  154. package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
  155. package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
  156. package/src/plugins/builtin/chat/controller/index.ts +88 -17
  157. package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
  158. package/src/plugins/builtin/chat/controller/messages.ts +14 -2
  159. package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
  160. package/src/plugins/builtin/chat/controller/state.ts +25 -0
  161. package/src/plugins/builtin/chat/controller/unread-people.ts +240 -0
  162. package/src/plugins/builtin/chat/controller/view.ts +4 -0
  163. package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
  164. package/src/plugins/builtin/chat/conversations.ts +12 -5
  165. package/src/plugins/builtin/chat/index.tsx +1 -1
  166. package/src/plugins/builtin/chat/layout.ts +23 -5
  167. package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
  168. package/src/plugins/builtin/chat/share/flow.tsx +18 -9
  169. package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
  170. package/src/plugins/builtin/chat/sidebar.tsx +6 -1
  171. package/src/plugins/builtin/chat/unread-badge.ts +38 -7
  172. package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
  173. package/src/plugins/builtin/cloud/askg/pane-attachment.ts +2 -3
  174. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
  175. package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
  176. package/src/plugins/builtin/commodity-balances/client.ts +51 -193
  177. package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
  178. package/src/plugins/builtin/commodity-balances/model.ts +5 -180
  179. package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
  180. package/src/plugins/builtin/custom-view/index.tsx +1 -1
  181. package/src/plugins/builtin/custom-view/loader.ts +57 -7
  182. package/src/plugins/builtin/custom-view/sources.ts +146 -0
  183. package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
  184. package/src/plugins/builtin/earnings/board-model.ts +33 -3
  185. package/src/plugins/builtin/earnings/board-table.ts +59 -14
  186. package/src/plugins/builtin/earnings/board.tsx +36 -3
  187. package/src/plugins/builtin/earnings/headless.ts +24 -5
  188. package/src/plugins/builtin/earnings/history-model.ts +9 -1
  189. package/src/plugins/builtin/earnings/history-table.ts +16 -5
  190. package/src/plugins/builtin/earnings/history.tsx +40 -10
  191. package/src/plugins/builtin/earnings/index.tsx +3 -1
  192. package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
  193. package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
  194. package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
  195. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  196. package/src/plugins/builtin/econ-statistics/view.ts +4 -0
  197. package/src/plugins/builtin/futures/model.ts +5 -4
  198. package/src/plugins/builtin/futures/series.ts +38 -1
  199. package/src/plugins/builtin/futures-curve/client.ts +4 -4
  200. package/src/plugins/builtin/futures-curve/headless.ts +33 -8
  201. package/src/plugins/builtin/futures-curve/index.tsx +4 -4
  202. package/src/plugins/builtin/futures-curve/model.ts +109 -6
  203. package/src/plugins/builtin/futures-curve/pane.tsx +24 -13
  204. package/src/plugins/builtin/insider/headless.ts +74 -4
  205. package/src/plugins/builtin/insider/index.tsx +99 -5
  206. package/src/plugins/builtin/insider/signals-client.ts +69 -0
  207. package/src/plugins/builtin/insider/signals-model.ts +146 -0
  208. package/src/plugins/builtin/insider/signals.tsx +176 -0
  209. package/src/plugins/builtin/iv-history/client.ts +41 -2
  210. package/src/plugins/builtin/iv-history/headless.ts +24 -16
  211. package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
  212. package/src/plugins/builtin/jodi/client.ts +42 -205
  213. package/src/plugins/builtin/jodi/headless.ts +13 -4
  214. package/src/plugins/builtin/jodi/model.ts +18 -43
  215. package/src/plugins/builtin/jodi/pane.tsx +5 -0
  216. package/src/plugins/builtin/kelly-sizer/headless.ts +1 -1
  217. package/src/plugins/builtin/kelly-sizer/history.ts +5 -3
  218. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  219. package/src/plugins/builtin/macro-day/release-table.ts +3 -3
  220. package/src/plugins/builtin/market-movers/client.ts +32 -6
  221. package/src/plugins/builtin/market-movers/headless.ts +7 -2
  222. package/src/plugins/builtin/market-movers/model.ts +64 -3
  223. package/src/plugins/builtin/market-movers/screener.ts +20 -9
  224. package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
  225. package/src/plugins/builtin/market-valuation/headless.ts +17 -4
  226. package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
  227. package/src/plugins/builtin/market-valuation/view.ts +10 -0
  228. package/src/plugins/builtin/mna/model.ts +2 -1
  229. package/src/plugins/builtin/options-positioning/client.ts +10 -0
  230. package/src/plugins/builtin/options-positioning/headless.ts +88 -13
  231. package/src/plugins/builtin/options-positioning/index.tsx +2 -2
  232. package/src/plugins/builtin/options-positioning/model.ts +47 -0
  233. package/src/plugins/builtin/options-positioning/pane.tsx +55 -26
  234. package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
  235. package/src/plugins/builtin/options-scenario/client.ts +111 -17
  236. package/src/plugins/builtin/options-scenario/collar.ts +141 -0
  237. package/src/plugins/builtin/options-scenario/csp.ts +1 -1
  238. package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
  239. package/src/plugins/builtin/options-scenario/headless.ts +28 -12
  240. package/src/plugins/builtin/options-scenario/index.tsx +1 -1
  241. package/src/plugins/builtin/options-scenario/model.ts +55 -5
  242. package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
  243. package/src/plugins/builtin/perps/client.ts +77 -6
  244. package/src/plugins/builtin/perps/headless.ts +25 -14
  245. package/src/plugins/builtin/perps/index.tsx +1 -1
  246. package/src/plugins/builtin/perps/model.ts +32 -0
  247. package/src/plugins/builtin/perps/pane.tsx +5 -4
  248. package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
  249. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
  250. package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
  251. package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
  252. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
  253. package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
  254. package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
  255. package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
  256. package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
  257. package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
  258. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
  259. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
  260. package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
  261. package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
  262. package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
  263. package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
  264. package/src/plugins/builtin/rate-path/model.ts +1 -1
  265. package/src/plugins/builtin/rate-path/pane.tsx +2 -1
  266. package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
  267. package/src/plugins/builtin/research/event-model.ts +41 -49
  268. package/src/plugins/builtin/research/event-sources.ts +1 -1
  269. package/src/plugins/builtin/research/headless.ts +1 -1
  270. package/src/plugins/builtin/research/relative-valuation-model.ts +6 -0
  271. package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
  272. package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
  273. package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
  274. package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
  275. package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
  276. package/src/plugins/builtin/sectors/client.ts +46 -19
  277. package/src/plugins/builtin/sectors/sector-model.ts +22 -10
  278. package/src/plugins/builtin/shared/earnings-report.ts +48 -1
  279. package/src/plugins/builtin/shared/history-span.ts +55 -0
  280. package/src/plugins/builtin/shared/report-freshness.ts +20 -11
  281. package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
  282. package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
  283. package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
  284. package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
  285. package/src/plugins/builtin/themes/headless.ts +15 -2
  286. package/src/plugins/builtin/thirteenf/api.ts +60 -43
  287. package/src/plugins/builtin/thirteenf/data.ts +76 -35
  288. package/src/plugins/builtin/thirteenf/headless.ts +2 -0
  289. package/src/plugins/builtin/thirteenf/pane.tsx +10 -2
  290. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
  291. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
  292. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
  293. package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
  294. package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
  295. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  296. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  297. package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
  298. package/src/plugins/builtin/yield-curve/curves.ts +15 -1
  299. package/src/plugins/builtin/yield-curve/headless.ts +17 -7
  300. package/src/plugins/builtin/yield-curve/history.ts +2 -0
  301. package/src/plugins/builtin/yield-curve/index.tsx +23 -18
  302. package/src/plugins/builtin/yield-curve/series.ts +19 -9
  303. package/src/plugins/builtin/yield-curve/treasury-data.ts +2 -0
  304. package/src/plugins/builtin/yield-curve/world.ts +4 -0
  305. package/src/plugins/registry/host-actions.ts +3 -0
  306. package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
  307. package/src/portfolio/valuation-basis.ts +4 -21
  308. package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
  309. package/src/remote/controller.ts +27 -7
  310. package/src/remote/schema.ts +25 -9
  311. package/src/renderers/browser/app-services.ts +7 -1
  312. package/src/renderers/browser/config-host.ts +55 -2
  313. package/src/renderers/browser/first-screen.ts +193 -0
  314. package/src/renderers/browser/main.tsx +59 -7
  315. package/src/renderers/cloudflare/http-proxy.ts +21 -13
  316. package/src/renderers/dom/desktop/controls.tsx +2 -0
  317. package/src/renderers/dom/desktop/query-bar.tsx +1 -1
  318. package/src/renderers/dom/dom-ui-host.tsx +2 -0
  319. package/src/renderers/dom/windowed-list.tsx +201 -0
  320. package/src/renderers/dom/windowed-rows.ts +65 -0
  321. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -0
  322. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -7
  323. package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
  324. package/src/renderers/electrobun/bun/desktop/update.ts +132 -14
  325. package/src/renderers/electrobun/shared/protocol.ts +7 -4
  326. package/src/renderers/electrobun/view/http-fetch.ts +12 -1
  327. package/src/renderers/electrobun/view/update-host.ts +2 -2
  328. package/src/shares/bug-report-dialog.tsx +162 -0
  329. package/src/shares/bug-report-flow.tsx +84 -0
  330. package/src/shares/bug-report.ts +42 -0
  331. package/src/shares/capture-flow.ts +19 -0
  332. package/src/shares/capture-preview.tsx +129 -0
  333. package/src/shares/chat.ts +10 -1
  334. package/src/shares/dialog.tsx +16 -91
  335. package/src/shares/flow.tsx +12 -16
  336. package/src/shares/personal-pane.ts +35 -1
  337. package/src/sources/listing-symbols.ts +8 -4
  338. package/src/state/app/context/index.tsx +13 -0
  339. package/src/state/app/tab-cursor.ts +85 -0
  340. package/src/sync/core-contributors.ts +206 -59
  341. package/src/telemetry/usage-counts.ts +16 -0
  342. package/src/test-support/config-store.ts +31 -0
  343. package/src/test-support/moved-symbols.ts +64 -0
  344. package/src/theme/colors.ts +30 -0
  345. package/src/tickers/listing-fate.ts +242 -0
  346. package/src/tickers/open-target.ts +61 -11
  347. package/src/tickers/search/index.ts +45 -3
  348. package/src/tickers/search/ranking.ts +19 -2
  349. package/src/tickers/search/types.ts +3 -0
  350. package/src/tickers/search/upsert.ts +22 -7
  351. package/src/tickers/ticker-changes.ts +26 -9
  352. package/src/time-series/bar-spacing.ts +19 -0
  353. package/src/time-series/chart-data.ts +37 -13
  354. package/src/time-series/field-catalog.ts +5 -2
  355. package/src/time-series/forward-valuation.ts +10 -7
  356. package/src/time-series/fundamentals.ts +49 -11
  357. package/src/time-series/hooks.ts +2 -0
  358. package/src/time-series/live-bars.ts +3 -1
  359. package/src/time-series/parsed-history-cache.ts +2 -0
  360. package/src/time-series/price-units.ts +69 -0
  361. package/src/time-series/resolve.ts +71 -11
  362. package/src/time-series/session-bar.ts +138 -0
  363. package/src/time-series/spec.ts +20 -3
  364. package/src/time-series/studies.ts +211 -12
  365. package/src/time-series/transforms.ts +2 -0
  366. package/src/time-series/types.ts +11 -1
  367. package/src/time-series/valuation-price.ts +13 -0
  368. package/src/types/config.ts +24 -1
  369. package/src/types/financials.ts +2 -0
  370. package/src/types/headless.ts +27 -0
  371. package/src/types/plugin.ts +7 -0
  372. package/src/ui/host.tsx +5 -0
  373. package/src/updater/channel.ts +51 -0
  374. package/src/updater/github-releases.ts +85 -16
  375. package/src/updater/index.ts +128 -17
  376. package/src/utils/asset-category.ts +20 -0
  377. package/src/utils/datetime-format.ts +7 -1
  378. package/src/utils/decimal-comma.ts +83 -3
  379. package/src/utils/fundamentals.ts +7 -2
  380. package/src/utils/http-proxy-response.ts +66 -9
  381. package/src/utils/plugin-proxy-hosts.json +1 -5
  382. package/src/utils/semver.ts +47 -2
  383. package/src/utils/typo-distance.ts +22 -0
  384. package/src/plugins/builtin/shared/zip-text.ts +0 -46
@@ -1,6 +1,6 @@
1
1
  import { FINANCIAL_VINTAGE_NOTICE, statementHistoryNotes } from "../../../utils/financial-statements";
2
2
  import { hasValidQuoteObservationTime } from "../../../market-data/quotes/freshness";
3
- import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, type ExtendedSession } from "../../../market-data/market/status";
3
+ import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, quoteHeadlineTime, type ExtendedSession } from "../../../market-data/market/status";
4
4
  import type { HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/headless";
5
5
  import type { TimeRange } from "../../../time-series/range";
6
6
  import { formatNumber, formatPercentRaw } from "../../../utils/format";
@@ -29,12 +29,16 @@ import {
29
29
  import {
30
30
  loadHeadlessFinancials, loadHeadlessPriceHistory, loadHeadlessSymbols, resolveHeadlessInstrument,
31
31
  } from "../shared/headless-market-data";
32
- import { barHistoryFreshness, barResolutionFromDates, quoteFreshnessFields, REPORTED_DATA } from "../shared/report-freshness";
32
+ import { barHistoryFreshness, quoteFreshnessFields, REPORTED_DATA } from "../shared/report-freshness";
33
33
  import { cleanFloat32Price, historyPriceDecimals } from "../../../cli/history-rows";
34
34
  import { historyCoverageNotice } from "../../../sources/history-coverage";
35
- import { getChartResolutionLabel, isIntradayResolution, type ManualChartResolution } from "../../../time-series/resolution";
35
+ import { getChartResolutionLabel, isIntradayResolution, RANGE_HISTORY_RESOLUTION, type ManualChartResolution } from "../../../time-series/resolution";
36
+ import { DAY_SO_FAR_TEXT, withSessionBar } from "../../../time-series/session-bar";
36
37
  import { buildReturnRows, closedMarketNote, type ReturnRange } from "./data-panes/returns-model";
37
38
  import { loadReturnSeries } from "./data-panes/returns-history";
39
+ import {
40
+ formingHistoryBar, HISTORY_DEFAULT_RANGE, historyBarDate, historyBarSize, historyDateHeader, isDailyOrLongerBar, type HistoryBarSize,
41
+ } from "./data-panes/history-bars";
38
42
 
39
43
  export const financialStatementsHeadless: HeadlessPaneDefinition<"rows"> = {
40
44
  ...paneSchemas["financial-analysis-pane"],
@@ -247,7 +251,7 @@ export const quoteComparisonHeadless: HeadlessPaneDefinition<"rows"> = {
247
251
  ...(quote.instrumentType ? { instrumentType: quote.instrumentType } : {}),
248
252
  ...(quote.priceBasis ? { priceBasis: quote.priceBasis } : {}),
249
253
  change, changePercent, ...extended,
250
- marketCap: quote.marketCap ?? null, updatedAt: quote.lastUpdated,
254
+ marketCap: quote.marketCap ?? null, updatedAt: quoteHeadlineTime(quote),
251
255
  ...quoteFreshnessFields(quote),
252
256
  };
253
257
  });
@@ -263,35 +267,86 @@ export const quoteComparisonHeadless: HeadlessPaneDefinition<"rows"> = {
263
267
  },
264
268
  };
265
269
 
270
+ /** The price columns of HP, which `fn HP` prints and exports with the date column of the bars' size. */
271
+ const HISTORY_PRICE_COLUMNS: HeadlessPaneColumn[] = ["open", "high", "low", "close", "volume"].map((key) => ({
272
+ key, header: key, align: "right" as const,
273
+ format: (value: unknown) => value == null || !Number.isFinite(Number(value)) ? "-"
274
+ : key === "volume" ? formatNumber(Number(value), 0)
275
+ : formatPriceObservation(Number(value), { minimumFractionDigits: 2 }),
276
+ }));
277
+
278
+ /**
279
+ * The columns of HP's rows. A day or longer bar is a date and an intraday one a
280
+ * UTC time. The text table dates a month's bar as the month and marks a bar
281
+ * whose period runs on (`2026-10 MTD`); the export keeps each bar's own date
282
+ * and adds a `Partial` column when one is.
283
+ */
284
+ function historyColumns(size: HistoryBarSize, partial: boolean, text: boolean): HeadlessPaneColumn[] {
285
+ const daily = isDailyOrLongerBar(size);
286
+ const date: HeadlessPaneColumn = {
287
+ key: "date",
288
+ header: daily ? historyDateHeader(size) ?? "Date" : "Time (UTC)",
289
+ format: (value, row) => daily
290
+ ? (text ? historyBarDate(String(value).slice(0, 10), size, row.partial === true) : String(value).slice(0, 10))
291
+ : String(value).slice(0, 16).replace("T", " "),
292
+ };
293
+ const marked: HeadlessPaneColumn[] = partial && !text
294
+ ? [{ key: "partial", header: "Partial", format: (value) => value === true ? "true" : "" }]
295
+ : [];
296
+ return [date, ...HISTORY_PRICE_COLUMNS, ...marked];
297
+ }
298
+
266
299
  export const historicalPricesHeadless: HeadlessPaneDefinition<"rows"> = {
267
300
  ...paneSchemas["historical-prices-pane"],
268
301
  shape: "rows",
269
- description: "Daily OHLCV rows (date, open, high, low, close, volume) for one ticker over a range.",
270
- describe: ({ symbols, options }) => `Historical Prices | ${symbols[0]} | ${options.range ?? "ALL"}`,
271
- columns: [
272
- { key: "date", header: "Date", format: (value) => String(value).slice(0, 10) },
273
- ...["open", "high", "low", "close", "volume"].map((key) => ({
274
- key, header: key, align: "right" as const,
275
- format: (value: unknown) => value == null || !Number.isFinite(Number(value)) ? "-"
276
- : key === "volume" ? formatNumber(Number(value), 0)
277
- : formatPriceObservation(Number(value), { minimumFractionDigits: 2 }),
278
- })),
279
- ],
302
+ description: "OHLCV bars (date, open, high, low, close, volume) for one ticker over a range: daily to start, weekly for 5Y and monthly for ALL. The newest bar is marked partial while its day, week or month runs on; a day's bar then comes from the live quote.",
303
+ describe: ({ symbols, options }) => `Historical Prices | ${symbols[0]} | ${options.range ?? HISTORY_DEFAULT_RANGE}`,
304
+ columns: historyColumns("1d", false, false),
280
305
  async load({ symbols, options }, ctx) {
281
306
  const symbol = symbols[0]!;
282
- const range = (options.range ?? "ALL") as TimeRange;
283
- const points = await loadHeadlessPriceHistory(ctx, symbol, range);
307
+ const range = (options.range ?? HISTORY_DEFAULT_RANGE) as TimeRange;
308
+ // A range of daily or longer bars reaches into the session now trading, whose bar is the quote's (withSessionBar).
309
+ const { symbol: listing, exchange } = await resolveHeadlessInstrument(ctx, symbol);
310
+ const [loaded, quote] = await Promise.all([
311
+ loadHeadlessPriceHistory(ctx, symbol, range),
312
+ isIntradayResolution(RANGE_HISTORY_RESOLUTION[range]) ? null : ctx.marketData.getQuote(listing, exchange ?? "").catch(() => null),
313
+ ]);
314
+ // The venue whose sessions say whether a day, week or month is still running: the key's, or for a bare symbol its quote's.
315
+ const venue = exchange || quote?.listingExchangeName || quote?.exchangeName || undefined;
316
+ const now = Date.now();
317
+ const points = withSessionBar(loaded, quote, { now, venue });
284
318
  const rows = points.map((point) => {
285
319
  const date = new Date(point.date);
286
320
  return {
287
321
  date: Number.isFinite(date.getTime()) ? date.toISOString() : String(point.date),
288
322
  ...pricePointValues(point),
323
+ ...(point.partial ? { partial: true } : {}),
289
324
  };
290
325
  }).sort((left, right) => left.date.localeCompare(right.date));
291
326
  const notice = priceHistoryIntegrityNotice(rows.filter((row) => row.integrity).length);
327
+ const size = historyBarSize(rows.map((row) => Date.parse(row.date)));
328
+ // A weekly or monthly bar whose period runs on is dated by how far it runs, as `history` dates it.
329
+ const forming = formingHistoryBar(size, rows.at(-1)?.date, venue, now);
330
+ const shown = forming ? [...rows.slice(0, -1), { ...rows.at(-1)!, partial: true }] : rows;
331
+ const marked = shown.some((row) => row.partial === true);
332
+ // The day's bar built from the quote is not a close; a coin's day in progress, as its source flags it, keeps its line.
333
+ const dayToDate = points !== loaded && size === "1d" && shown.at(-1)?.partial === true;
292
334
  // A bar history: dated by its last bar, stale once bars of its size stop arriving.
293
- const freshness = { ...barHistoryFreshness(barResolutionFromDates(rows.map((row) => row.date))), observedKey: "date", oldest: null };
294
- return { rows, freshness, ...(notice ? { complete: false } : {}), unavailableSymbols: rows.some((row) => row.close !== null) ? [] : [symbol], metadata: { symbol, range, ...(notice ? { notices: [notice] } : {}) } };
335
+ const freshness = {
336
+ ...barHistoryFreshness(isDailyOrLongerBar(size) ? size : null),
337
+ observedKey: "date", oldest: null,
338
+ ...(forming ? { periodToDate: forming } : {}),
339
+ ...(dayToDate ? { basis: "daily bars", note: `the newest bar is ${DAY_SO_FAR_TEXT}`, ...(quote ? { asOf: new Date(quote.lastUpdated).toISOString() } : {}) } : {}),
340
+ };
341
+ return {
342
+ rows: shown,
343
+ columns: historyColumns(size, marked, false),
344
+ textColumns: historyColumns(size, marked, true),
345
+ freshness,
346
+ ...(notice ? { complete: false } : {}),
347
+ unavailableSymbols: rows.some((row) => row.close !== null) ? [] : [symbol],
348
+ metadata: { symbol, range, ...(size === "intraday" ? {} : { interval: size }), ...(notice ? { notices: [notice] } : {}) },
349
+ };
295
350
  },
296
351
  };
297
352
 
@@ -323,7 +378,7 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
323
378
  const percent = (value: number | null) => value === null ? null : value * 100;
324
379
  const intraday = isIntradayResolution(series.resolution);
325
380
  // A daily or longer bar is a trading date; an intraday one its UTC open.
326
- const rows = returns.map((row) => ({
381
+ const unmarked = returns.map((row) => ({
327
382
  time: intraday ? new Date(row.timestamp).toISOString() : new Date(row.timestamp).toISOString().slice(0, 10),
328
383
  close: rounded(row.price),
329
384
  intervalChange: rounded(row.intervalChange),
@@ -332,15 +387,31 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
332
387
  totalReturnPct: percent(row.cumulativePercent),
333
388
  ...(row.inconsistent ? { inconsistent: true } : {}),
334
389
  }));
390
+ // A weekly or monthly bar whose period runs on is the period so far: marked partial, and dated by how far it runs.
391
+ const forming = formingHistoryBar(series.resolution, unmarked.at(-1)?.time, series.venue || undefined, Date.now());
392
+ const rows = forming ? [...unmarked.slice(0, -1), { ...unmarked.at(-1)!, partial: true }] : unmarked;
335
393
  const price = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value.toFixed(decimals) : "-";
336
- const columns: HeadlessPaneColumn[] = [
337
- { key: "time", header: intraday ? "Time (UTC)" : "Date", format: (value) => String(value).slice(0, 16).replace("T", " ") },
394
+ const figures: HeadlessPaneColumn[] = [
338
395
  { key: "close", header: "Price", align: "right", format: price },
339
396
  { key: "intervalChange", header: "Int $", align: "right", format: price },
340
397
  { key: "intervalReturnPct", header: "Int %", align: "right", format: signedPercentCell },
341
398
  { key: "totalChange", header: "Total $", align: "right", format: price },
342
399
  { key: "totalReturnPct", header: "Total %", align: "right", format: signedPercentCell },
343
400
  ];
401
+ // The text table dates a month's bar as the month and marks one still running (`2026-10 MTD`); the export keeps the day and says which bar is partial.
402
+ const timeColumn = (text: boolean): HeadlessPaneColumn => ({
403
+ key: "time",
404
+ header: intraday ? "Time (UTC)" : "Date",
405
+ format: (value, row) => text && !intraday
406
+ ? historyBarDate(String(value).slice(0, 10), series.resolution, row.partial === true)
407
+ : String(value).slice(0, 16).replace("T", " "),
408
+ });
409
+ const columns: HeadlessPaneColumn[] = [
410
+ timeColumn(false),
411
+ ...figures,
412
+ ...forming ? [{ key: "partial", header: "Partial", format: (value: unknown) => value === true ? "true" : "" }] : [],
413
+ ];
414
+ const textColumns: HeadlessPaneColumn[] = [timeColumn(true), ...figures];
344
415
  // An interval the range is too long for is served in the nearest longer bar; say so rather than switch silently.
345
416
  const notices = [
346
417
  series.resolution !== requested
@@ -352,12 +423,14 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
352
423
  return {
353
424
  rows,
354
425
  columns,
426
+ textColumns,
355
427
  unavailableSymbols: rows.length ? [] : [key],
356
428
  ...(rows.length ? {} : { errors: [`${key}: No bars in this window.`] }),
357
429
  freshness: {
358
430
  ...barHistoryFreshness(intraday ? null : series.resolution),
359
431
  observedKey: "time",
360
432
  oldest: null,
433
+ ...(forming ? { periodToDate: forming } : {}),
361
434
  ...(series.venue ? { cadenceExchange: series.venue } : {}),
362
435
  // The window ends at the last session while the market is shut, which the status line says.
363
436
  ...(series.lastSession ? { note: closedMarketNote(series.lastSession, series.visibleTo) } : {}),
@@ -20,7 +20,7 @@ import {
20
20
  } from "../../../../market-data/market/format";
21
21
  import type { OverviewFunctionLink, PositionTableRow, StatField } from "./types";
22
22
  import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue, resolvePortfolioPositionPnl, portfolioPnlPercent, signedPositionDirection } from "../../../../portfolio/position-metrics";
23
- import { liveDividendYield, liveForwardPE, liveMarketCapitalization, liveTrailingPE } from "../../portfolio-list/live-valuation";
23
+ import { liveDividendYield, liveForwardPE, liveMarketCapitalization, liveTrailingPE } from "../../../../market-data/quotes/live-valuation";
24
24
  import { atRegularSessionPrice } from "../../../../market-data/quotes/regular-session-valuation";
25
25
  import { formatReportedMoney } from "../../../../utils/reported-money";
26
26
  import { enterpriseValueCheck, fundamentalsCurrency, fundamentalsEpsCurrency, reportedEnterpriseValue } from "../../../../utils/fundamentals";
@@ -44,7 +44,7 @@ import { useFuturesContractTerms } from "./overview/futures-contract";
44
44
  import { buildOverviewStats, buildPositionRows, buildProfileFields } from "./overview/model";
45
45
  import type { OverviewFunctionLink, StatField } from "./overview/types";
46
46
  import { describeFundamentalMarketCap } from "../../../utils/market-capitalization";
47
- import { liveFiftyTwoWeekRange, liveMarketCapitalization } from "../portfolio-list/live-valuation";
47
+ import { liveFiftyTwoWeekRange, liveMarketCapitalization } from "../../../market-data/quotes/live-valuation";
48
48
  import { instrumentTypeLabel } from "../../../tickers/instrument-kind";
49
49
  import { ClippedText } from "../../../components/ui/display";
50
50
 
@@ -78,6 +78,21 @@ function layerColumns(layer: GeoLayerInfo): HeadlessPaneColumn[] {
78
78
  ];
79
79
  }
80
80
 
81
+ /**
82
+ * What a search that finds nothing says. A search outside a one-country layer
83
+ * finds nothing because the layer has nothing there; ships are found by name,
84
+ * not by the ticker of the company that runs them, so a ticker that links no
85
+ * fleet (or a server that links none) gets a name to try.
86
+ */
87
+ function emptySearchNote(layer: GeoLayerInfo, query: string): string | null {
88
+ const typed = query.trim();
89
+ if (!typed) return null;
90
+ const coverage = layerCoverageNote(layer);
91
+ if (coverage) return `No ${layer.name.toLowerCase()} match "${typed}". ${coverage}`;
92
+ if (layer.id === "vessels") return `No ${layer.name.toLowerCase()} match "${typed}"; try a ship-name prefix, such as --query Norwegian.`;
93
+ return null;
94
+ }
95
+
81
96
  export const mapHeadless = {
82
97
  shape: "rows",
83
98
  description: "Trading venues with open status and local time, or one map layer's entities (ships, chokepoints, ports, airports, pipelines, fields, terminals) with their fields and linked tickers.",
@@ -126,12 +141,11 @@ export const mapHeadless = {
126
141
  lon: row.lon,
127
142
  lat: row.lat,
128
143
  }));
129
- const coverage = layerCoverageNote(layer);
144
+ const empty = emptySearchNote(layer, query);
130
145
  return {
131
146
  columns: layerColumns({ ...layer, columns }),
132
147
  rows,
133
- // A search outside a one-country layer finds nothing because the layer has nothing there.
134
- ...(query.trim() && coverage ? { empty: `No ${layer.name.toLowerCase()} match "${query.trim()}". ${coverage}` } : {}),
148
+ ...(empty ? { empty } : {}),
135
149
  freshness: layerFreshness(layer),
136
150
  complete: layer.status === "ok",
137
151
  errors: layer.status === "unavailable" ? [`${layer.name}: ${layer.statusNote ?? "unavailable"}`] : [],
@@ -73,6 +73,19 @@ export interface CurveData {
73
73
  lookbacks: YieldLookbackCurves | null;
74
74
  /** Null for the FRED fallback: its spreads follow from the points and look-backs. */
75
75
  spreads: CurveSpreadFigure[] | null;
76
+ /**
77
+ * FRED `info.source` when these points are the constant-maturity fallback.
78
+ * Null when that fallback came back without series info. Absent on a stored curve.
79
+ */
80
+ seriesSource?: string | null;
81
+ }
82
+
83
+ function fredSeriesSource(points: readonly YieldPoint[]): string | null {
84
+ const sources = [...new Set(points.flatMap((point) => {
85
+ const source = point.source?.trim();
86
+ return source ? [source] : [];
87
+ }))];
88
+ return sources.length ? sources.join(", ") : null;
76
89
  }
77
90
 
78
91
  type CurveClient = Pick<typeof apiClient, "getCloudCurve" | "getCloudYieldCurve" | "getCloudFredSeries">;
@@ -105,7 +118,8 @@ async function loadFredCurve(requestedDate: string, client: CurveClient): Promis
105
118
  const points = completeYieldCurve(requestedDate
106
119
  ? await loadHistoricalYieldCurve(requestedDate, (id, options) => client.getCloudFredSeries(id, options))
107
120
  : await loadYieldCurve(() => client.getCloudYieldCurve()));
108
- return { curve: "us", requestedDate, basis: "par", couponsPerYear: 2, points, lookbacks: null, spreads: null };
121
+ return { curve: "us", requestedDate, basis: "par", couponsPerYear: 2, points, lookbacks: null, spreads: null,
122
+ seriesSource: fredSeriesSource(points) };
109
123
  }
110
124
 
111
125
  /** The spreads GC shows: the stored curve's, or for FRED's curve its own with no percentile. */
@@ -153,20 +153,31 @@ export function createYieldCurveHeadless(
153
153
  const compareAsOf = comparePoints ? curveAsOf(comparePoints) : null;
154
154
  const compareAt = (maturity: string) => comparePoints?.find((point) => point.maturity === maturity)?.yield ?? null;
155
155
  // The stored curve brings its look-backs; the FRED fallback's load after it, and a failure leaves the move blank.
156
- const lookbacks = data.lookbacks ?? (session ? await loadLookbacks(session, context).catch(() => ({})) : {});
156
+ const lookbacks: YieldLookbackCurves = data.lookbacks ?? (session ? await loadLookbacks(session, context).catch(() => ({})) : {});
157
157
  const change1d = new Map(yieldTenorRows(points, lookbacks).map((row) => [row.id, row.change1d]));
158
158
  const rows = [...points].sort((a, b) => a.maturityYears - b.maturityYears);
159
159
  const missing = points.filter((point) => point.yield == null);
160
160
  const missingTenors = missing.map((point) => point.maturity);
161
- // On a past date a tenor the publisher has no yield for was most likely not issued yet: a note, not an error.
162
- const notIssued = requestedDate && missing.length < points.length ? missing.filter((point) => !point.error).map((point) => point.maturity) : [];
163
- const unavailable = missingTenors.filter((maturity) => !notIssued.includes(maturity));
161
+ // A blank on a past date is not evidence the tenor was unissued. Leave the cell empty.
162
+ const pastBlanks = requestedDate && missing.length < points.length
163
+ ? missing.filter((point) => !point.error).map((point) => point.maturity) : [];
164
+ const unavailable = missingTenors.filter((maturity) => !pastBlanks.includes(maturity));
164
165
  const spreads = curveSpreadFigures(data, lookbacks);
165
166
  const spread = (id: string) => bp(spreads.find((entry) => entry.id === id)?.value ?? null);
166
167
  const twosTens = spread("2s10s");
168
+ const fredNames = new Set<string>();
169
+ for (const curvePoints of [points, ...Object.values(lookbacks)]) {
170
+ for (const point of curvePoints ?? []) {
171
+ const source = point.source?.trim();
172
+ if (source) fredNames.add(source);
173
+ }
174
+ }
175
+ const seriesSource = fredNames.size > 0 ? [...fredNames].join(", ")
176
+ : data.seriesSource ? data.seriesSource
177
+ : data.seriesSource === null ? "FRED" : CURVE_PUBLISHERS[curve];
167
178
  return {
168
- // Each curve is its official publisher's daily close; a past date is a historical curve, not a feed.
169
- freshness: { source: CURVE_PUBLISHERS[curve], status: "not-a-feed", tradingDayMarket: CURVE_MARKETS[curve],
179
+ // A stored curve cites its publisher. FRED's fallback cites info.source, not that publisher.
180
+ freshness: { source: seriesSource, status: "not-a-feed", tradingDayMarket: CURVE_MARKETS[curve],
170
181
  ...(requestedDate ? { basis: "historical curve" } : { basis: "daily curve", cadence: "daily" }) },
171
182
  ...(comparePoints ? { columns: compareColumns(compareAsOf) } : {}),
172
183
  rows: rows.map((point) => {
@@ -180,7 +191,6 @@ export function createYieldCurveHeadless(
180
191
  : {}),
181
192
  };
182
193
  }),
183
- ...(notIssued.length ? { notes: [`Not issued then: ${notIssued.join(", ")}`] } : {}),
184
194
  errors: [
185
195
  ...yieldCurveErrors(points),
186
196
  ...(unavailable.length ? [`Tenors unavailable: ${unavailable.join(", ")}`] : []),
@@ -88,11 +88,13 @@ function curveOnOrBefore(results: readonly PromiseSettledResult<TreasurySeriesRe
88
88
  const result = results[index]!;
89
89
  const data = result.status === "fulfilled" ? result.value : null;
90
90
  const point = data?.observations.find((entry) => entry.date === asOf);
91
+ const source = data?.payload.info?.source?.trim();
91
92
  return {
92
93
  maturity,
93
94
  maturityYears: years,
94
95
  yield: point?.value ?? null,
95
96
  asOf: point ? asOf : null,
97
+ ...(source ? { source } : {}),
96
98
  stale: data?.payload.stale ?? false,
97
99
  fetchedAt: data?.payload.fetchedAt,
98
100
  error: result.status === "rejected"
@@ -30,7 +30,7 @@ import {
30
30
  compareDate, curveIdOf, CURVE_OPTIONS, curveSpreadFigures, loadComparePoints, loadCurveData, parseCompareInput,
31
31
  parseCurveArgument, type CurveId,
32
32
  } from "./curves";
33
- import { forwardCurve } from "./forward";
33
+ import { forwardCurve, type CurveBasis } from "./forward";
34
34
  import { loadYieldCurveLookbacks, yieldCurveDate } from "./history";
35
35
  import { curveAsOf, isYieldObservationDate, type YieldPoint, yieldCurveErrors } from "./treasury-data";
36
36
  import { loadWorldRows, type WorldRow } from "./world";
@@ -38,15 +38,17 @@ import { loadWorldRows, type WorldRow } from "./world";
38
38
  type GcTab = "curve" | "world";
39
39
  const TABS = [{ label: "Curve", value: "curve" }, { label: "World", value: "world" }];
40
40
  const EMPTY_POINTS: YieldPoint[] = [];
41
- /** What each curve plots, in the legend's first words. */
42
- const CAPTIONS: Partial<Record<CurveId, string>> = {
43
- "us-real": "Real yield % by maturity",
44
- "us-breakeven": "Breakeven % by maturity",
45
- gb: "Zero-coupon yield % by maturity",
41
+ /** Axis word for the loaded curve's basis. */
42
+ const BASIS_LABEL: Record<CurveBasis, string> = {
43
+ par: "Par yield",
44
+ zero: "Zero rate",
45
+ benchmark: "Benchmark yield",
46
+ spread: "Spread",
46
47
  };
47
- const YIELD_CAPTION = "Yield % by maturity";
48
- /** A breakeven is not a yield, so its curve and column say what it is. */
49
- const valueName = (curve: CurveId) => curve === "us-breakeven" ? "Breakeven" : "Yield";
48
+ const basisText = (basis: CurveBasis | undefined): string | null => basis ? BASIS_LABEL[basis] : null;
49
+ /** TIPS par yields are real yields, and the breakeven curve is the spread readers call a breakeven. */
50
+ const CURVE_LEVEL: Partial<Record<CurveId, string>> = { "us-real": "Real par yield", "us-breakeven": "Breakeven" };
51
+ const curveLevel = (curve: CurveId, basis: CurveBasis | undefined): string | null => CURVE_LEVEL[curve] ?? basisText(basis);
50
52
  const TENOR: DataTableColumn = { id: "tenor", label: "Tenor", width: 7, align: "left" };
51
53
  const YIELD: DataTableColumn = { id: "yield", label: "Yield", width: 8, align: "right" };
52
54
  const CHANGE_1D: DataTableColumn = { id: "change1d", label: "1D chg", width: 8, align: "right" };
@@ -268,29 +270,31 @@ function CurveTab({ curve, onCurveChange, focused, width, height }: {
268
270
  useChartTableSelection({ rows, getId: tenorKey, getDate: tenorPosition, selectedId, onSelect: setSelectedTenor,
269
271
  focused: focused && !editing });
270
272
  const compareName = curveLookbackLabel(compareLabel);
273
+ const level = curveLevel(curve, current?.basis);
274
+ const levelName = level ?? "Yield";
271
275
  const series = useMemo(() => differenceView
272
276
  ? [yieldDifferenceSeries(rows, compareName, { positive: colors.positive, negative: colors.negative })]
273
277
  : yieldCurveChartSeries(points, lookbackCurves,
274
278
  { current: colors.positive, ghosts: curveGhostColors(colors), compare: colors.warning, forward: colors.borderFocused },
275
- { compare: comparing ? { label: compareLabel, points: comparePoints ?? [] } : null, forward, label: valueName(curve) }),
276
- [colors, compareLabel, compareName, comparePoints, comparing, curve, differenceView, forward, lookbackCurves, points, rows]);
279
+ { compare: comparing ? { label: compareLabel, points: comparePoints ?? [] } : null, forward, label: levelName }),
280
+ [colors, compareLabel, compareName, comparePoints, comparing, differenceView, forward, levelName, lookbackCurves, points, rows]);
277
281
  // Mixed tenor dates still draw the curve: the As of column and the footer
278
282
  // warning say which tenors differ.
279
283
  const mixedDates = new Set(rows.flatMap((row) => row.yield == null ? [] : [row.asOf])).size > 1;
280
284
  const columns = useMemo<DataTableColumn[]>(() => [
281
- TENOR, curve === "us-breakeven" ? { ...YIELD, label: valueName(curve), width: 10 } : YIELD,
285
+ TENOR, { ...YIELD, label: levelName, width: Math.max(8, levelName.length) },
282
286
  ...(comparing
283
287
  ? [{ id: "compare", label: compareName || "vs", width: Math.max(8, compareName.length + 1), align: "right" as const },
284
288
  { id: "changeCompare", label: "Chg", width: 8, align: "right" as const }, CHANGE_1D]
285
289
  : LOOKBACK_COLUMNS),
286
290
  ...(forward.length ? [FORWARD] : []),
287
291
  ...(mixedDates ? [AS_OF_COLUMN] : []),
288
- ], [compareName, comparing, curve, forward.length, mixedDates]);
289
- const caption = differenceView ? `Change vs ${compareName}` : CAPTIONS[curve] ?? YIELD_CAPTION;
292
+ ], [compareName, comparing, forward.length, levelName, mixedDates]);
293
+ const caption = differenceView ? `Change vs ${compareName}` : level ? `${level} % by maturity` : "Yield % by maturity";
290
294
  const formatValue = differenceView ? formatBp : formatYield;
291
295
  // The strip's label shares its row with the curve's shape and the point. A
292
296
  // curve with fewer than two yields has neither, so the tenors take the band.
293
- const strip = curveStrip(series, formatValue, { caption: differenceView ? "Change" : "Yield %", selectedPointId: selectedId });
297
+ const strip = curveStrip(series, formatValue, { caption: differenceView ? "Change" : level ? `${level} %` : "Yield %", selectedPointId: selectedId });
294
298
  const renderCell = useCallback((row: YieldTenorRow, column: DataTableColumn) => {
295
299
  if (column.id === "tenor") return { text: row.id };
296
300
  if (column.id === "asOf") return { text: row.asOf ?? "--", color: colors.textMuted };
@@ -403,7 +407,8 @@ function WorldTab({ onOpen, focused, width, height }: {
403
407
  points: row.points.filter((point) => point.years > 0)
404
408
  .map((point) => ({ id: point.tenor, label: point.tenor, x: point.years, value: point.yield })),
405
409
  })), [rows, selectedId]);
406
- const strip = curveStrip(series, formatYield, { caption: "Yield %" });
410
+ const level = basisText(rows.find((row) => row.id === selectedId)?.basis);
411
+ const strip = curveStrip(series, formatYield, { caption: level ? `${level} %` : "Yield %" });
407
412
  const renderCell = useCallback((row: WorldRow, column: DataTableColumn) => {
408
413
  if (column.id === "market") return { text: row.market };
409
414
  if (column.id === "asOf") return { text: row.asOf, color: colors.textMuted };
@@ -428,9 +433,9 @@ function WorldTab({ onOpen, focused, width, height }: {
428
433
  rootBefore={<ChartTableHeader width={width} height={height} tableRows={rows.length} tableColumns={WORLD_COLUMNS}
429
434
  chart={strip ? {
430
435
  render: (size) => <CurveSurface series={series} width={size.width} height={size.height} display="chart"
431
- caption={YIELD_CAPTION} xScale="log" formatValue={formatYield} formatChange={formatYieldChange}
436
+ caption={level ? `${level} % by maturity` : "Yield % by maturity"} xScale="log" formatValue={formatYield} formatChange={formatYieldChange}
432
437
  formatAxisValue={formatPercentAxis} primarySeriesId={selectedId ?? undefined} />,
433
- minRows: curveSurfaceMinRows({ series, width, caption: YIELD_CAPTION }),
438
+ minRows: curveSurfaceMinRows({ series, width, caption: level ? `${level} % by maturity` : "Yield % by maturity" }),
434
439
  strip,
435
440
  } : null} />}
436
441
  />
@@ -5,20 +5,26 @@ import type { SeriesDefinition } from "../../../series/types";
5
5
  import { unavailableOnServer } from "../shared/cloud-resource";
6
6
  import { TREASURY_MATURITIES } from "./treasury-data";
7
7
 
8
- /** Each Treasury constant maturity the curve draws, as `UST:10Y`. */
8
+ /**
9
+ * FRED constant-maturity yields, charted as `UST:10Y` (DGS10). These ids stay
10
+ * on that series. The curve pane plots the Treasury par curve, below.
11
+ */
9
12
  export const TREASURY_SERIES: readonly SeriesDefinition[] = TREASURY_MATURITIES.map((entry) => ({
10
13
  id: `ust:${entry.maturity}`,
11
- name: `${entry.maturity} Treasury yield`,
14
+ name: `${entry.maturity} Treasury constant maturity`,
15
+ description: `FRED ${entry.seriesId}, the constant-maturity yield, not the Treasury par curve.`,
12
16
  unit: "%",
13
17
  frequency: "daily",
14
18
  group: "Rates",
15
- tags: ["treasury", "yield curve", entry.seriesId],
16
- publisher: "FRED",
19
+ tags: ["treasury", "yield curve", "constant maturity", entry.seriesId],
20
+ publisher: "U.S. Treasury",
17
21
  url: fredSeriesUrl(entry.seriesId),
18
22
  expression: `UST:${entry.maturity}`,
19
23
  }));
20
24
 
21
25
  /** The government curves stored beside the Treasury's, with the tenors each publisher prints. */
26
+ const BILL_TENORS = new Set(["1M", "3M", "6M", "1Y"]);
27
+
22
28
  const CURVES: ReadonlyArray<{
23
29
  curve: CloudCurveId;
24
30
  name: (tenor: string) => string;
@@ -26,6 +32,11 @@ const CURVES: ReadonlyArray<{
26
32
  tags: readonly string[];
27
33
  tenors: readonly string[];
28
34
  }> = [
35
+ {
36
+ curve: "us", publisher: "U.S. Treasury", tags: ["Treasury", "par yield", "UST"],
37
+ name: (tenor) => `US Treasury ${tenor} par yield`,
38
+ tenors: ["1M", "6W", "2M", "3M", "4M", "6M", "1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "20Y", "30Y"],
39
+ },
29
40
  {
30
41
  curve: "de", publisher: "Bundesbank", tags: ["Germany", "Bund", "euro"],
31
42
  name: (tenor) => `Germany ${tenor} Bund yield`,
@@ -42,8 +53,8 @@ const CURVES: ReadonlyArray<{
42
53
  tenors: ["1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "15Y", "20Y", "25Y", "30Y", "40Y"],
43
54
  },
44
55
  {
45
- curve: "ca", publisher: "Bank of Canada", tags: ["Canada", "benchmark"],
46
- name: (tenor) => `Canada ${tenor} benchmark yield`,
56
+ curve: "ca", publisher: "Bank of Canada", tags: ["Canada", "benchmark", "bill"],
57
+ name: (tenor) => BILL_TENORS.has(tenor) ? `Canada ${tenor} Treasury bill yield` : `Canada ${tenor} benchmark yield`,
47
58
  tenors: ["1M", "3M", "6M", "1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "30Y"],
48
59
  },
49
60
  {
@@ -52,15 +63,14 @@ const CURVES: ReadonlyArray<{
52
63
  tenors: ["3M", "6M", "1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "15Y", "20Y", "30Y"],
53
64
  },
54
65
  {
55
- // FRED lists the 5Y and 10Y; the curve has the rest.
56
66
  curve: "us-real", publisher: "U.S. Treasury", tags: ["TIPS", "real yield", "treasury"],
57
67
  name: (tenor) => `${tenor} TIPS real yield`,
58
- tenors: ["7Y", "20Y", "30Y"],
68
+ tenors: ["5Y", "7Y", "10Y", "20Y", "30Y"],
59
69
  },
60
70
  {
61
71
  curve: "us-breakeven", publisher: "U.S. Treasury", tags: ["breakeven", "inflation expectations", "treasury"],
62
72
  name: (tenor) => `${tenor} breakeven inflation (nominal less TIPS)`,
63
- tenors: ["7Y", "20Y", "30Y"],
73
+ tenors: ["5Y", "7Y", "10Y", "20Y", "30Y"],
64
74
  },
65
75
  ];
66
76
 
@@ -4,6 +4,8 @@ export interface YieldPoint {
4
4
  maturity: string; // "1M", "3M", "6M", "1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "20Y", "30Y"
5
5
  maturityYears: number; // 0.083, 0.25, 0.5, 1, 2, 3, 5, 7, 10, 20, 30
6
6
  yield: number | null; // percent, e.g., 4.29
7
+ /** FRED `info.source` when this point came from a FRED series. Absent on a stored curve. */
8
+ source?: string;
7
9
  asOf?: string | null; // FRED observation date, absent on older servers
8
10
  stale?: boolean;
9
11
  fetchedAt?: string;
@@ -1,6 +1,7 @@
1
1
  import { apiClient } from "../../../api-client";
2
2
  import type { CloudCurvePoint, CloudWorldCurves } from "../../../api-client/yield-curves";
3
3
  import { isCurveId, type CurveId } from "./curves";
4
+ import type { CurveBasis } from "./forward";
4
5
 
5
6
  export interface WorldRow {
6
7
  id: CurveId;
@@ -17,6 +18,8 @@ export interface WorldRow {
17
18
  thirtyYearChange: number | null;
18
19
  /** 10Y minus 2Y. */
19
20
  twosTens: number | null;
21
+ /** The basis the publisher printed: par, zero, benchmark, or spread. */
22
+ basis: CurveBasis;
20
23
  twosTensChange: number | null;
21
24
  /** The whole latest curve, for the chart. */
22
25
  points: CloudCurvePoint[];
@@ -43,6 +46,7 @@ function worldRows(payload: CloudWorldCurves): WorldRow[] {
43
46
  twoYearChange: minus(now("2Y"), before("2Y")), tenYearChange: minus(now("10Y"), before("10Y")),
44
47
  thirtyYearChange: minus(now("30Y"), before("30Y")),
45
48
  twosTens, twosTensChange: minus(twosTens, minus(before("10Y"), before("2Y"))),
49
+ basis: entry.curve.basis,
46
50
  points: entry.points,
47
51
  }];
48
52
  });
@@ -44,6 +44,8 @@ export interface PluginHostActions {
44
44
  /** `options.fieldKey` starts the cursor on that setting, as a setting picked in the command bar does. */
45
45
  openPaneSettings(paneId?: string, options?: { fieldKey?: string }): void;
46
46
  sharePane(paneId?: string): void;
47
+ /** Report a Bug for a pane (the focused one when omitted): its picture and a line, posted to #bugs. Not on the plugin API. */
48
+ reportPaneBug(paneId?: string): void;
47
49
  /**
48
50
  * A picture of a pane (the focused one when omitted) as it looks now, its
49
51
  * footer reading as a dated status line. Not on the plugin API.
@@ -114,6 +116,7 @@ export function createDefaultHostActions(current?: () => Pick<PluginHostActions,
114
116
  openBuiltInWorkflow: () => {},
115
117
  openPaneSettings: () => {},
116
118
  sharePane: () => {},
119
+ reportPaneBug: () => {},
117
120
  capturePaneImage: async () => { throw new Error("Pictures of panes are not available here."); },
118
121
  promptDialog: async () => undefined,
119
122
  openWindowMode: () => {},
@@ -1,9 +1,9 @@
1
- import type { AppConfig } from "../../../types/config";
2
- import type { Portfolio, PortfolioCash, TickerPosition, TickerRecord, Watchlist } from "../../../types/ticker";
3
- import { resolveCurrencyUnit } from "../../../utils/currency-units";
4
- import { slugifyName } from "../../../utils/slugify";
5
- import { parseCollectionRef } from "../../../team/collections";
6
- import { findNamedCollection } from "../../../portfolio/collection-name";
1
+ import type { AppConfig } from "../types/config";
2
+ import type { Portfolio, PortfolioCash, TickerPosition, TickerRecord, Watchlist } from "../types/ticker";
3
+ import { resolveCurrencyUnit } from "../utils/currency-units";
4
+ import { slugifyName } from "../utils/slugify";
5
+ import { parseCollectionRef } from "../team/collections";
6
+ import { findNamedCollection } from "./collection-name";
7
7
 
8
8
  export interface DeleteWatchlistResult {
9
9
  config: AppConfig;