gloomberb 0.16.4 → 0.16.5

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 (554) hide show
  1. package/README.md +14 -91
  2. package/package.json +4 -1
  3. package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
  4. package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
  5. package/src/account/billing.ts +57 -0
  6. package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
  7. package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
  8. package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
  9. package/src/account/session.ts +16 -0
  10. package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
  11. package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
  12. package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
  13. package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
  14. package/src/api-client/data.ts +24 -2
  15. package/src/api-client/earnings.ts +6 -0
  16. package/src/api-client/futures-curve.ts +56 -0
  17. package/src/api-client/index.ts +4 -1
  18. package/src/api-client/market-discovery.ts +10 -1
  19. package/src/api-client/paths.ts +7 -0
  20. package/src/api-client/research-activity.ts +15 -10
  21. package/src/api-client/types.ts +17 -1
  22. package/src/api-client/yield-curves.ts +8 -0
  23. package/src/app/global-shortcuts.ts +15 -1
  24. package/src/app/pane-runtime/ticker-open-runtime.ts +7 -3
  25. package/src/architecture/core-builtin-allowlist.json +4 -13
  26. package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
  27. package/src/capabilities/chart-series.ts +11 -1
  28. package/src/cli/commands/crypto-hints.ts +7 -0
  29. package/src/cli/commands/fred-series.ts +61 -0
  30. package/src/cli/commands/market.ts +407 -104
  31. package/src/cli/commands/news-hints.ts +62 -0
  32. package/src/cli/commands/news-search.ts +53 -0
  33. package/src/cli/commands/options-leaps.ts +5 -2
  34. package/src/cli/commands/overview.ts +93 -20
  35. package/src/cli/commands/remote.ts +2 -1
  36. package/src/cli/commands/search.ts +82 -9
  37. package/src/cli/commands/system.ts +55 -8
  38. package/src/cli/commands/ticker.ts +88 -11
  39. package/src/cli/context.ts +45 -14
  40. package/src/cli/desktop-pane-shot.ts +59 -7
  41. package/src/cli/earnings-calendar.ts +71 -0
  42. package/src/cli/freshness.ts +18 -7
  43. package/src/cli/glossary.ts +429 -0
  44. package/src/cli/history-rows.ts +48 -7
  45. package/src/cli/index-roots.ts +7 -0
  46. package/src/cli/index.ts +21 -7
  47. package/src/cli/known-gaps.ts +27 -0
  48. package/src/cli/listing-arg.ts +417 -56
  49. package/src/cli/not-a-ticker.ts +12 -5
  50. package/src/cli/pane-functions/access-gate.ts +5 -0
  51. package/src/cli/pane-functions/capabilities.ts +3 -2
  52. package/src/cli/pane-functions/catalog.ts +35 -5
  53. package/src/cli/pane-functions/data.ts +12 -8
  54. package/src/cli/pane-functions/discovery.ts +1 -0
  55. package/src/cli/pane-functions/dom.ts +20 -6
  56. package/src/cli/pane-functions/freshness.ts +363 -43
  57. package/src/cli/pane-functions/function-help.ts +12 -5
  58. package/src/cli/pane-functions/headless.ts +45 -17
  59. package/src/cli/pane-functions/index.ts +63 -10
  60. package/src/cli/pane-functions/market-session.ts +22 -8
  61. package/src/cli/pane-functions/options.ts +63 -22
  62. package/src/cli/pane-functions/report-notices.ts +25 -7
  63. package/src/cli/pane-functions/report.ts +15 -8
  64. package/src/cli/pane-functions/resolver.ts +49 -12
  65. package/src/cli/pane-functions/screenshot.ts +110 -23
  66. package/src/cli/report-tables.ts +3 -0
  67. package/src/cli/result.ts +4 -1
  68. package/src/cli/staged-tickers.ts +52 -0
  69. package/src/cli/symbol-args.ts +9 -0
  70. package/src/cli/symbol-list.ts +172 -0
  71. package/src/cli/ticker-resolution.ts +223 -30
  72. package/src/cli/types.ts +5 -0
  73. package/src/components/chart/composite/axis-overlays.tsx +6 -2
  74. package/src/components/chart/composite/composite-chart.tsx +40 -6
  75. package/src/components/chart/composite/legend.tsx +49 -2
  76. package/src/components/chart/composite/panel-surface.tsx +44 -17
  77. package/src/components/chart/composite/price-axis-labels.tsx +3 -1
  78. package/src/components/command-bar/panel/index.tsx +11 -3
  79. package/src/components/command-bar/panel/runtime.ts +3 -0
  80. package/src/components/command-bar/panel/types.ts +2 -0
  81. package/src/components/command-bar/routes/root/provider-search.ts +4 -3
  82. package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
  83. package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
  84. package/src/components/command-bar/workflow/tickers.ts +23 -4
  85. package/src/components/layout/detached-pane-shell.tsx +9 -0
  86. package/src/components/layout/floating-pane.tsx +3 -0
  87. package/src/components/layout/header.tsx +20 -5
  88. package/src/components/layout/pane/content.tsx +7 -13
  89. package/src/components/layout/pane/footer/index.tsx +6 -2
  90. package/src/components/layout/pane/footer/model.ts +35 -1
  91. package/src/components/layout/pane/header.tsx +24 -1
  92. package/src/components/layout/pane/index.tsx +12 -2
  93. package/src/components/layout/pane/sidebar.tsx +78 -10
  94. package/src/components/layout/shell/active-drag.ts +21 -5
  95. package/src/components/layout/shell/drag/index.ts +39 -9
  96. package/src/components/layout/shell/drag/live.ts +61 -51
  97. package/src/components/layout/shell/drag/runtime.ts +54 -12
  98. package/src/components/layout/shell/index.tsx +25 -7
  99. package/src/components/layout/shell/menu.ts +5 -0
  100. package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
  101. package/src/components/layout/shell/pane/layers.tsx +192 -45
  102. package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
  103. package/src/components/markdown-text.tsx +62 -20
  104. package/src/components/metric-treemap/heat-surface.tsx +10 -1
  105. package/src/components/onboarding/account-step/form-panel.tsx +1 -1
  106. package/src/components/onboarding/account-step/index.tsx +1 -1
  107. package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
  108. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  109. package/src/components/onboarding/onboarding-keyboard.ts +1 -1
  110. package/src/components/onboarding/onboarding-wizard.tsx +2 -2
  111. package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
  112. package/src/components/onboarding/wizard-account.ts +2 -2
  113. package/src/components/pane-template-wizard.tsx +9 -1
  114. package/src/components/price-selector-dialog.tsx +11 -2
  115. package/src/components/sign-in-gate.tsx +3 -3
  116. package/src/components/ticker/list-table-view.tsx +24 -1
  117. package/src/components/ui/action-row.tsx +2 -1
  118. package/src/components/ui/button.tsx +7 -1
  119. package/src/components/ui/choice-dialog.tsx +6 -4
  120. package/src/components/ui/confirm-dialog.tsx +28 -18
  121. package/src/components/ui/data-table/index.tsx +17 -6
  122. package/src/components/ui/data-table/opentui/index.tsx +37 -6
  123. package/src/components/ui/data-table/types.ts +12 -0
  124. package/src/components/ui/dialog-actions.tsx +64 -0
  125. package/src/components/ui/display.tsx +17 -3
  126. package/src/components/ui/frame.tsx +35 -2
  127. package/src/components/ui/icon.tsx +2 -0
  128. package/src/components/ui/text-prompt-dialog.tsx +11 -9
  129. package/src/data/cached-query.ts +2 -0
  130. package/src/data/config/store/normalize.ts +7 -2
  131. package/src/data/ticker-repository.ts +5 -0
  132. package/src/data/ticker-store.ts +15 -0
  133. package/src/docs/content.ts +215 -0
  134. package/src/docs/load.ts +139 -0
  135. package/src/docs/requests.ts +39 -0
  136. package/src/docs/snapshot.chunk.ts +8 -0
  137. package/src/docs/snapshot.json +7416 -0
  138. package/src/docs/topics.ts +216 -0
  139. package/src/i18n/es.ts +61 -4
  140. package/src/i18n/ja.ts +61 -4
  141. package/src/i18n/ko.ts +61 -4
  142. package/src/i18n/zh-cn.ts +61 -4
  143. package/src/i18n/zh-tw.ts +61 -4
  144. package/src/layout/desks.ts +1 -1
  145. package/src/layout/pane-manager/dock-tree.ts +18 -0
  146. package/src/layout/pane-manager/index.ts +1 -0
  147. package/src/market-data/coordinator/chart.ts +10 -4
  148. package/src/market-data/coordinator/index.ts +6 -3
  149. package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
  150. package/src/market-data/futures-contract.ts +151 -0
  151. package/src/market-data/hooks.tsx +10 -2
  152. package/src/market-data/market/format.ts +39 -21
  153. package/src/market-data/market/freshness.ts +81 -18
  154. package/src/market-data/market/status.ts +19 -23
  155. package/src/market-data/published-apac-sessions.ts +23 -1
  156. package/src/market-data/quotes/contributions.ts +3 -0
  157. package/src/market-data/quotes/metadata.ts +7 -24
  158. package/src/market-data/quotes/resolution.ts +2 -0
  159. package/src/market-data/quotes/screener-live-quotes.ts +5 -4
  160. package/src/market-data/result-types.ts +2 -0
  161. package/src/plugins/builtin/account-management/model.ts +1 -56
  162. package/src/plugins/builtin/account-management/pane.tsx +6 -4
  163. package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
  164. package/src/plugins/builtin/alerts/cli.ts +313 -47
  165. package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
  166. package/src/plugins/builtin/alerts/events.ts +23 -2
  167. package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
  168. package/src/plugins/builtin/alerts/history.ts +2 -0
  169. package/src/plugins/builtin/alerts/index.tsx +41 -9
  170. package/src/plugins/builtin/alerts/pane.tsx +8 -0
  171. package/src/plugins/builtin/alerts/research-builder.ts +8 -3
  172. package/src/plugins/builtin/alerts/research-rules.ts +18 -2
  173. package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
  174. package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
  175. package/src/plugins/builtin/alerts/scheduled.ts +254 -0
  176. package/src/plugins/builtin/alerts/targets.ts +92 -0
  177. package/src/plugins/builtin/analytics/index.tsx +4 -2
  178. package/src/plugins/builtin/analytics/risk-headless.ts +30 -12
  179. package/src/plugins/builtin/analytics/risk-math.ts +91 -27
  180. package/src/plugins/builtin/analytics/risk-model.ts +35 -5
  181. package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
  182. package/src/plugins/builtin/attention/index.tsx +2 -0
  183. package/src/plugins/builtin/attention/pane.tsx +3 -1
  184. package/src/plugins/builtin/attention/series.ts +26 -0
  185. package/src/plugins/builtin/awards/index.tsx +2 -0
  186. package/src/plugins/builtin/awards/pane.tsx +3 -1
  187. package/src/plugins/builtin/awards/series.ts +29 -0
  188. package/src/plugins/builtin/bond-calculator/model.ts +2 -1
  189. package/src/plugins/builtin/catalysts/pane.tsx +3 -1
  190. package/src/plugins/builtin/cds/client.ts +70 -13
  191. package/src/plugins/builtin/cds/headless.ts +116 -19
  192. package/src/plugins/builtin/cds/index.tsx +3 -1
  193. package/src/plugins/builtin/cds/model.ts +193 -31
  194. package/src/plugins/builtin/cds/pane.tsx +56 -14
  195. package/src/plugins/builtin/cds/series.ts +19 -0
  196. package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
  197. package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
  198. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
  199. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
  200. package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
  201. package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
  202. package/src/plugins/builtin/chart-composer/headless.ts +8 -3
  203. package/src/plugins/builtin/chart-composer/index.tsx +9 -2
  204. package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
  205. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
  206. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
  207. package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
  208. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
  209. package/src/plugins/builtin/chat/controller/index.ts +2 -0
  210. package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
  211. package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
  212. package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
  213. package/src/plugins/builtin/cloud/askg/model.ts +27 -8
  214. package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
  215. package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
  216. package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
  217. package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
  218. package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
  219. package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
  220. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  221. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
  222. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
  223. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
  224. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
  225. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
  226. package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
  227. package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
  228. package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
  229. package/src/plugins/builtin/company-attention/headless.ts +19 -5
  230. package/src/plugins/builtin/company-attention/index.tsx +2 -0
  231. package/src/plugins/builtin/company-attention/model.ts +26 -7
  232. package/src/plugins/builtin/company-attention/pane.tsx +12 -9
  233. package/src/plugins/builtin/company-attention/series.ts +52 -0
  234. package/src/plugins/builtin/company-kpis/pane.tsx +4 -2
  235. package/src/plugins/builtin/composite-plugins.ts +19 -13
  236. package/src/plugins/builtin/cot/index.tsx +3 -1
  237. package/src/plugins/builtin/cot/model.ts +40 -11
  238. package/src/plugins/builtin/cot/series.ts +27 -0
  239. package/src/plugins/builtin/credit-boards/index.tsx +2 -0
  240. package/src/plugins/builtin/credit-boards/series.ts +66 -0
  241. package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
  242. package/src/plugins/builtin/credit-conditions/series.ts +26 -0
  243. package/src/plugins/builtin/credit-documents/headless.ts +31 -1
  244. package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
  245. package/src/plugins/builtin/crypto-board/headless.ts +2 -3
  246. package/src/plugins/builtin/crypto-board/index.tsx +2 -0
  247. package/src/plugins/builtin/crypto-board/model.ts +12 -0
  248. package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
  249. package/src/plugins/builtin/crypto-board/series.ts +48 -0
  250. package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
  251. package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
  252. package/src/plugins/builtin/debt-maturities/series.ts +45 -0
  253. package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
  254. package/src/plugins/builtin/dividend-yield/index.tsx +2 -0
  255. package/src/plugins/builtin/dividend-yield/series.ts +20 -0
  256. package/src/plugins/builtin/docs/blocks.tsx +181 -0
  257. package/src/plugins/builtin/docs/index.tsx +68 -0
  258. package/src/plugins/builtin/docs/pane.tsx +426 -0
  259. package/src/plugins/builtin/docs/targets.ts +72 -0
  260. package/src/plugins/builtin/docs/use-docs.ts +60 -0
  261. package/src/plugins/builtin/doe/index.tsx +2 -0
  262. package/src/plugins/builtin/doe/series.ts +62 -0
  263. package/src/plugins/builtin/earnings/client.ts +3 -2
  264. package/src/plugins/builtin/earnings/format.ts +1 -5
  265. package/src/plugins/builtin/earnings/headless.ts +58 -6
  266. package/src/plugins/builtin/earnings/history-model.ts +15 -1
  267. package/src/plugins/builtin/earnings/history-table.ts +13 -2
  268. package/src/plugins/builtin/earnings/history.tsx +7 -3
  269. package/src/plugins/builtin/econ/fred-catalog.ts +189 -0
  270. package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
  271. package/src/plugins/builtin/econ/index.tsx +2 -0
  272. package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
  273. package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
  274. package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
  276. package/src/plugins/builtin/exposure/holdings.ts +1 -1
  277. package/src/plugins/builtin/exposure/pane.tsx +3 -1
  278. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  279. package/src/plugins/builtin/fear-greed/series.ts +52 -0
  280. package/src/plugins/builtin/futures/index.tsx +43 -3
  281. package/src/plugins/builtin/futures/model.ts +1 -1
  282. package/src/plugins/builtin/futures/returns.ts +1 -1
  283. package/src/plugins/builtin/futures/series.ts +13 -0
  284. package/src/plugins/builtin/futures/table.tsx +1 -1
  285. package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
  286. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  287. package/src/plugins/builtin/futures-curve/headless.ts +70 -9
  288. package/src/plugins/builtin/futures-curve/index.tsx +1 -1
  289. package/src/plugins/builtin/futures-curve/model.ts +66 -11
  290. package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
  291. package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
  292. package/src/plugins/builtin/gpu/board.tsx +13 -10
  293. package/src/plugins/builtin/gpu/changes.tsx +4 -4
  294. package/src/plugins/builtin/gpu/client.ts +19 -1
  295. package/src/plugins/builtin/gpu/equities.tsx +14 -10
  296. package/src/plugins/builtin/gpu/headless.ts +56 -25
  297. package/src/plugins/builtin/gpu/history.tsx +3 -3
  298. package/src/plugins/builtin/gpu/index.tsx +2 -0
  299. package/src/plugins/builtin/gpu/model.ts +101 -16
  300. package/src/plugins/builtin/gpu/pane.tsx +28 -12
  301. package/src/plugins/builtin/gpu/series.ts +38 -0
  302. package/src/plugins/builtin/help/function-card.tsx +25 -6
  303. package/src/plugins/builtin/help/index.tsx +17 -8
  304. package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
  305. package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
  306. package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
  307. package/src/plugins/builtin/holders/client.ts +22 -3
  308. package/src/plugins/builtin/holders/format.ts +18 -3
  309. package/src/plugins/builtin/holders/headless.ts +36 -133
  310. package/src/plugins/builtin/holders/pane.tsx +11 -4
  311. package/src/plugins/builtin/holders/report-header.ts +40 -5
  312. package/src/plugins/builtin/holders/report-rows.ts +240 -0
  313. package/src/plugins/builtin/insider/client.ts +35 -20
  314. package/src/plugins/builtin/insider/headless.ts +41 -11
  315. package/src/plugins/builtin/insider/index.tsx +26 -9
  316. package/src/plugins/builtin/insider/insider-data.ts +10 -0
  317. package/src/plugins/builtin/insider/issuer.ts +49 -0
  318. package/src/plugins/builtin/insider/model.ts +3 -0
  319. package/src/plugins/builtin/iv-history/client.ts +14 -4
  320. package/src/plugins/builtin/iv-history/headless.ts +7 -4
  321. package/src/plugins/builtin/iv-history/index.tsx +2 -0
  322. package/src/plugins/builtin/iv-history/model.ts +111 -7
  323. package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
  324. package/src/plugins/builtin/iv-history/series.ts +29 -0
  325. package/src/plugins/builtin/jobs/headless.ts +28 -3
  326. package/src/plugins/builtin/jobs/index.tsx +3 -1
  327. package/src/plugins/builtin/jobs/series.ts +19 -0
  328. package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
  329. package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
  330. package/src/plugins/builtin/macro-day/client.ts +44 -3
  331. package/src/plugins/builtin/macro-day/headless.ts +13 -10
  332. package/src/plugins/builtin/macro-day/model.ts +56 -10
  333. package/src/plugins/builtin/macro-day/pane.tsx +30 -20
  334. package/src/plugins/builtin/market-heatmap/model.ts +19 -11
  335. package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
  336. package/src/plugins/builtin/market-movers/client.ts +7 -5
  337. package/src/plugins/builtin/market-movers/index.tsx +3 -2
  338. package/src/plugins/builtin/market-movers/model.ts +30 -0
  339. package/src/plugins/builtin/market-movers/screener.ts +31 -0
  340. package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
  341. package/src/plugins/builtin/market-valuation/index.tsx +3 -3
  342. package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
  343. package/src/plugins/builtin/members/headless.ts +7 -2
  344. package/src/plugins/builtin/members/model.ts +31 -3
  345. package/src/plugins/builtin/members/pane.tsx +3 -6
  346. package/src/plugins/builtin/mna/headless.ts +15 -3
  347. package/src/plugins/builtin/money-markets/index.tsx +2 -0
  348. package/src/plugins/builtin/money-markets/series.ts +41 -0
  349. package/src/plugins/builtin/notes/cli.ts +12 -7
  350. package/src/plugins/builtin/options/view.tsx +1 -1
  351. package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
  352. package/src/plugins/builtin/options-positioning/client.ts +7 -3
  353. package/src/plugins/builtin/options-positioning/headless.ts +1 -0
  354. package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
  355. package/src/plugins/builtin/options-scenario/client.ts +2 -1
  356. package/src/plugins/builtin/options-scenario/headless.ts +1 -0
  357. package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
  358. package/src/plugins/builtin/perps/client.ts +40 -6
  359. package/src/plugins/builtin/perps/equity-row.tsx +33 -11
  360. package/src/plugins/builtin/perps/freshness.ts +66 -0
  361. package/src/plugins/builtin/perps/headless.ts +45 -30
  362. package/src/plugins/builtin/perps/index.tsx +18 -1
  363. package/src/plugins/builtin/perps/model.ts +20 -5
  364. package/src/plugins/builtin/perps/pane.tsx +76 -20
  365. package/src/plugins/builtin/perps/series.ts +76 -0
  366. package/src/plugins/builtin/plugin-marketplace/pane.tsx +67 -61
  367. package/src/plugins/builtin/plugin-module.ts +3 -0
  368. package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
  369. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
  370. package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
  371. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +331 -65
  372. package/src/plugins/builtin/portfolio-list/cli/render.ts +200 -31
  373. package/src/plugins/builtin/portfolio-list/cli/shared.ts +24 -0
  374. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
  375. package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
  376. package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
  377. package/src/plugins/builtin/portfolio-list/headless.ts +34 -4
  378. package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
  379. package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
  380. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
  381. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
  382. package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
  383. package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
  384. package/src/plugins/builtin/power/client.ts +16 -0
  385. package/src/plugins/builtin/power/evidence.ts +6 -2
  386. package/src/plugins/builtin/power/headless.ts +71 -7
  387. package/src/plugins/builtin/power/index.tsx +4 -1
  388. package/src/plugins/builtin/power/model.ts +72 -1
  389. package/src/plugins/builtin/power/pane.tsx +32 -9
  390. package/src/plugins/builtin/rate-path/headless.ts +11 -3
  391. package/src/plugins/builtin/realized-vol/headless.ts +5 -0
  392. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
  393. package/src/plugins/builtin/research/event-model.ts +68 -24
  394. package/src/plugins/builtin/research/events-headless.ts +2 -1
  395. package/src/plugins/builtin/research/headless.ts +2 -1
  396. package/src/plugins/builtin/seasonality/client.ts +15 -7
  397. package/src/plugins/builtin/seasonality/headless.ts +36 -12
  398. package/src/plugins/builtin/seasonality/index.tsx +2 -2
  399. package/src/plugins/builtin/seasonality/model.ts +119 -0
  400. package/src/plugins/builtin/seasonality/pane.tsx +108 -37
  401. package/src/plugins/builtin/series-inventory.ts +91 -0
  402. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
  403. package/src/plugins/builtin/shared/command-tab.ts +30 -12
  404. package/src/plugins/builtin/shared/earnings-report.ts +55 -0
  405. package/src/plugins/builtin/shared/fred-points.ts +20 -0
  406. package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
  407. package/src/plugins/builtin/shared/open-description.ts +29 -0
  408. package/src/plugins/builtin/shared/report-freshness.ts +9 -0
  409. package/src/plugins/builtin/shared/series-warning.ts +5 -0
  410. package/src/plugins/builtin/short-interest/headless.ts +1 -0
  411. package/src/plugins/builtin/short-interest/index.tsx +2 -0
  412. package/src/plugins/builtin/short-interest/series.ts +36 -0
  413. package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
  414. package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
  415. package/src/plugins/builtin/short-volume/headless.ts +1 -0
  416. package/src/plugins/builtin/short-volume/index.tsx +2 -0
  417. package/src/plugins/builtin/short-volume/series.ts +23 -0
  418. package/src/plugins/builtin/social-mentions/index.tsx +2 -0
  419. package/src/plugins/builtin/social-mentions/series.ts +23 -0
  420. package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
  421. package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
  422. package/src/plugins/builtin/thirteenf/api.ts +1 -0
  423. package/src/plugins/builtin/thirteenf/data.ts +6 -4
  424. package/src/plugins/builtin/thirteenf/headless.ts +217 -36
  425. package/src/plugins/builtin/thirteenf/model.ts +39 -1
  426. package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
  427. package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
  428. package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
  429. package/src/plugins/builtin/thirteenf/types.ts +2 -0
  430. package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
  431. package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
  432. package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
  433. package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
  434. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
  435. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
  436. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
  437. package/src/plugins/builtin/time-sales/headless.ts +1 -0
  438. package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
  439. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  440. package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
  441. package/src/plugins/builtin/vol-surface/headless.ts +1 -0
  442. package/src/plugins/builtin/volatility/index.tsx +2 -0
  443. package/src/plugins/builtin/volatility/series.ts +84 -0
  444. package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
  445. package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
  446. package/src/plugins/builtin/world-venue-map/index.ts +2 -1
  447. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
  448. package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
  449. package/src/plugins/builtin/yield-curve/chart.ts +2 -2
  450. package/src/plugins/builtin/yield-curve/curves.ts +21 -8
  451. package/src/plugins/builtin/yield-curve/headless.ts +71 -18
  452. package/src/plugins/builtin/yield-curve/index.tsx +3 -1
  453. package/src/plugins/builtin/yield-curve/series.ts +86 -0
  454. package/src/plugins/catalog-browser.ts +2 -1
  455. package/src/plugins/registry/index.ts +89 -4
  456. package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
  457. package/src/portfolio/position-value.ts +40 -0
  458. package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
  459. package/src/public/time-series.ts +17 -0
  460. package/src/react/auto-refresh.ts +13 -3
  461. package/src/remote/app-host.tsx +20 -4
  462. package/src/remote/confirm-change.tsx +85 -0
  463. package/src/remote/controller-utils.ts +8 -1
  464. package/src/remote/controller.ts +123 -6
  465. package/src/remote/pane-screenshot.ts +47 -0
  466. package/src/remote/schema.ts +62 -0
  467. package/src/remote/types.ts +30 -0
  468. package/src/remote/watchlist.ts +306 -0
  469. package/src/renderers/browser/main.tsx +3 -2
  470. package/src/renderers/cloudflare/worker.ts +1 -1
  471. package/src/renderers/dom/data-table/frozen-column.ts +3 -1
  472. package/src/renderers/dom/data-table/index.tsx +33 -6
  473. package/src/renderers/dom/data-table/row.tsx +27 -1
  474. package/src/renderers/dom/desktop/control-styles.ts +63 -1
  475. package/src/renderers/dom/desktop/controls.tsx +30 -23
  476. package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
  477. package/src/renderers/dom/desktop/icons.tsx +7 -0
  478. package/src/renderers/dom/dom-ui-host.tsx +3 -0
  479. package/src/renderers/dom/host/box.tsx +3 -0
  480. package/src/renderers/dom/host/chart-surface.tsx +6 -2
  481. package/src/renderers/dom/host/live-frame.ts +6 -2
  482. package/src/renderers/dom/host/overflow-fade.ts +14 -1
  483. package/src/renderers/dom/host/size-observer.ts +26 -0
  484. package/src/renderers/dom/host/surface-3d.tsx +7 -2
  485. package/src/renderers/dom/host/text.tsx +1 -1
  486. package/src/renderers/dom/styles.css +61 -0
  487. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +17 -2
  488. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +19 -11
  489. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
  490. package/src/renderers/electrobun/view/main.tsx +2 -0
  491. package/src/series/capability.ts +75 -0
  492. package/src/series/csv.ts +160 -0
  493. package/src/series/files.ts +107 -0
  494. package/src/series/registry.ts +198 -0
  495. package/src/series/resolve.ts +107 -0
  496. package/src/series/standalone.ts +48 -0
  497. package/src/series/types.ts +145 -0
  498. package/src/series/validate.ts +171 -0
  499. package/src/sources/gloomberb-cloud/index.ts +7 -7
  500. package/src/sources/gloomberb-cloud/normalizers.ts +1 -0
  501. package/src/sources/history-result.ts +1 -0
  502. package/src/sources/listing-symbols.ts +4 -1
  503. package/src/sources/provider-router/batches.ts +4 -1
  504. package/src/sources/provider-router/cache.ts +4 -2
  505. package/src/sources/provider-router/cached-routes.ts +1 -1
  506. package/src/sources/provider-router/financial-routes.ts +1 -0
  507. package/src/sources/provider-router/financials.ts +13 -0
  508. package/src/sources/provider-router/history.ts +43 -25
  509. package/src/sources/provider-router/search.ts +1 -0
  510. package/src/sync/core-contributors.ts +1 -0
  511. package/src/tickers/company-name.ts +75 -0
  512. package/src/tickers/instrument-kind.ts +25 -0
  513. package/src/tickers/open-target.ts +3 -2
  514. package/src/tickers/search/index.ts +45 -7
  515. package/src/tickers/search/ranking.ts +33 -17
  516. package/src/tickers/search/types.ts +7 -1
  517. package/src/tickers/us-listing.ts +34 -0
  518. package/src/time-series/hooks.ts +9 -0
  519. package/src/time-series/resolve.ts +95 -16
  520. package/src/time-series/types.ts +4 -2
  521. package/src/time-series/use-chart-resolution.ts +32 -0
  522. package/src/types/config.ts +6 -0
  523. package/src/types/data-provider.ts +5 -0
  524. package/src/types/financials.ts +52 -1
  525. package/src/types/headless.ts +61 -0
  526. package/src/types/plugin.ts +19 -0
  527. package/src/types/price-history.ts +7 -0
  528. package/src/types/ticker.ts +14 -0
  529. package/src/ui/host.tsx +18 -0
  530. package/src/ui/live-resize.ts +81 -0
  531. package/src/ui/live-stretch.ts +121 -0
  532. package/src/ui/measured-cells.ts +58 -0
  533. package/src/ui/pane-fill.ts +11 -0
  534. package/src/utils/basis-points.ts +17 -0
  535. package/src/utils/cli-output.ts +7 -1
  536. package/src/utils/concurrency.ts +25 -0
  537. package/src/utils/crypto-pair.ts +20 -0
  538. package/src/utils/currency-pair.ts +19 -0
  539. package/src/utils/decimal-comma.ts +19 -0
  540. package/src/utils/dom-screenshot-css.ts +265 -0
  541. package/src/utils/dom-screenshot-rasters.ts +276 -0
  542. package/src/utils/dom-screenshot.ts +437 -52
  543. package/src/utils/exchange-rate-snapshot.ts +6 -2
  544. package/src/utils/exchanges.ts +107 -1
  545. package/src/utils/format.ts +16 -3
  546. package/src/utils/fundamentals.ts +66 -0
  547. package/src/utils/futures-generic.ts +6 -12
  548. package/src/utils/fx-market-hours.ts +15 -5
  549. package/src/utils/price-history.ts +99 -42
  550. package/src/utils/sec-eps-basis.ts +24 -11
  551. package/src/utils/sec.ts +61 -7
  552. package/src/utils/utc-time.ts +51 -4
  553. package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
  554. /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
@@ -1,17 +1,29 @@
1
+ import type { EarningsTiming } from "../../../api-client/earnings";
1
2
  import type { PricePoint } from "../../../types/financials";
2
3
  import { zonedDateTimeParts } from "../../../utils/zoned-date-time";
3
- import { MACRO_EVENT_KINDS, type MacroEventKind, type MacroRelease } from "./releases";
4
+ import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, type MacroEventKind, type MacroRelease } from "./releases";
4
5
 
5
6
  const DAY_MS = 86_400_000;
6
7
  /** Longest calendar gap between two closes that is still one session apart: Thursday to Monday over a Friday holiday. */
7
8
  const MAX_SESSION_GAP_DAYS = 4;
8
9
 
10
+ /** One of the ticker's own earnings reports, reduced to what dates its reaction. */
11
+ export interface MacroDayEarningsReport {
12
+ /** Report date, New York. */
13
+ date: string;
14
+ timing: EarningsTiming | null;
15
+ /** The session the earnings history measures the report's move on, once the report is priced. */
16
+ reactionSession?: string | null;
17
+ }
18
+
9
19
  export interface MacroDayEvent {
10
20
  kind: MacroEventKind;
11
21
  /** Release day, New York date. */
12
22
  date: string;
13
23
  /** The session whose close carries the reaction: the release day, or the next session when the market was shut. */
14
24
  session: string;
25
+ /** The session is also the reaction session of the ticker's own earnings report: the move is the report's, not the release's. */
26
+ earnings: boolean;
15
27
  /** Close before the release to the session's close. */
16
28
  move: number;
17
29
  /** |move| over the normal day's average |move|. */
@@ -32,15 +44,22 @@ export interface MacroDayModel {
32
44
  symbol: string;
33
45
  /** Newest first. */
34
46
  events: MacroDayEvent[];
47
+ /** Without the earnings days. */
35
48
  byKind: Record<MacroEventKind, MacroDayStats>;
36
- /** Every release day of the three kinds together; a day with two releases counts once. */
49
+ /** Every release day of the three kinds together, without the earnings days; a day with two releases counts once. */
37
50
  allEvents: MacroDayStats;
38
- /** Sessions with no release of the three kinds. */
51
+ /** Release days that are the ticker's earnings reaction, which no other figure counts; a day with two releases counts once. */
52
+ earnings: MacroDayStats;
53
+ /** Sessions with no release of the three kinds, and none of the earnings days among them. */
39
54
  normal: MacroDayStats;
40
55
  start: string | null;
41
56
  asOf: string | null;
42
57
  }
43
58
 
59
+ /** The release's name, with the earnings marker on a day that is the ticker's own report reaction. */
60
+ export const macroDayEventLabel = (event: Pick<MacroDayEvent, "kind" | "earnings">) =>
61
+ event.earnings ? `${MACRO_EVENT_LABELS[event.kind]} (earnings)` : MACRO_EVENT_LABELS[event.kind];
62
+
44
63
  /** Daily labels at UTC midnight name that date; any other stamp reads in New York. */
45
64
  function sessionDate(time: number): string {
46
65
  if (time % DAY_MS === 0) return new Date(time).toISOString().slice(0, 10);
@@ -59,6 +78,27 @@ function stats(moves: number[], normalAbs: number | null): MacroDayStats {
59
78
  multiple: normalAbs ? meanAbs / normalAbs : null };
60
79
  }
61
80
 
81
+ /**
82
+ * The sessions whose close carries a report's reaction, on the history's own
83
+ * calendar. A priced report names its session in the earnings history. Any
84
+ * other reads as the earnings history reads it: before the open and during
85
+ * the session react the same day, after the close the next session. A report
86
+ * with no time on record may have reacted on either, so both count.
87
+ */
88
+ function earningsReactionSessions(reports: readonly MacroDayEarningsReport[], sessions: readonly string[]): Set<string> {
89
+ const reacted = new Set<string>();
90
+ for (const report of reports) {
91
+ if (report.reactionSession) { reacted.add(report.reactionSession); continue; }
92
+ const sameDay = report.timing === "bmo" || report.timing === "dmh";
93
+ const afterClose = report.timing === "amc";
94
+ const same = sessions.find((date) => date >= report.date);
95
+ const next = sessions.find((date) => date > report.date);
96
+ if (!afterClose && same) reacted.add(same);
97
+ if (!sameDay && next) reacted.add(next);
98
+ }
99
+ return reacted;
100
+ }
101
+
62
102
  /**
63
103
  * Close to close on the release day: 8:30 ET releases land before the open
64
104
  * and 14:00 FOMC statements before the close, so the release day's close
@@ -66,7 +106,9 @@ function stats(moves: number[], normalAbs: number | null): MacroDayStats {
66
106
  * is read on the next session, from the close before the release. A release
67
107
  * whose close-to-close would span a gap in the history is left out rather
68
108
  * than read across it. A lone missing bar on the release day itself cannot
69
- * be told from a holiday and is read on the next session.
109
+ * be told from a holiday and is read on the next session. A release whose
110
+ * session is also the reaction to the ticker's own earnings is kept, marked,
111
+ * and counted in neither the release figures nor the normal days.
70
112
  */
71
113
  export function projectMacroDays(history: readonly PricePoint[], options: {
72
114
  symbol: string;
@@ -74,6 +116,8 @@ export function projectMacroDays(history: readonly PricePoint[], options: {
74
116
  releases: readonly MacroRelease[];
75
117
  /** The release list's last covered day. */
76
118
  coveredThrough: string;
119
+ /** The ticker's own earnings reports; none when they could not be read. */
120
+ earnings?: readonly MacroDayEarningsReport[];
77
121
  }): MacroDayModel {
78
122
  const { coveredThrough } = options;
79
123
  const byDate = new Map<string, number>();
@@ -86,7 +130,7 @@ export function projectMacroDays(history: readonly PricePoint[], options: {
86
130
  const last = sessions.at(-1) ?? null;
87
131
  const empty = (): MacroDayStats => stats([], null);
88
132
  if (!last) return { symbol: options.symbol, events: [], byKind: { cpi: empty(), jobs: empty(), fomc: empty() },
89
- allEvents: empty(), normal: empty(), start: null, asOf: null };
133
+ allEvents: empty(), earnings: empty(), normal: empty(), start: null, asOf: null };
90
134
 
91
135
  // Sessions after the published lists end may be unlisted release days, so they are left out of both sides.
92
136
  const end = last < coveredThrough ? last : coveredThrough;
@@ -105,7 +149,7 @@ export function projectMacroDays(history: readonly PricePoint[], options: {
105
149
  }
106
150
 
107
151
  const reactionSessions = new Set<string>();
108
- const found: Omit<MacroDayEvent, "multiple">[] = [];
152
+ const found: Omit<MacroDayEvent, "multiple" | "earnings">[] = [];
109
153
  for (const release of options.releases) {
110
154
  if (release.date <= start || release.date > end || !isWeekday(release.date)) continue;
111
155
  const at = sessions.findIndex((date) => date >= release.date);
@@ -120,12 +164,14 @@ export function projectMacroDays(history: readonly PricePoint[], options: {
120
164
  const normalMoves = [...moves].filter(([date]) => !reactionSessions.has(date)).map(([, move]) => move);
121
165
  const normal = stats(normalMoves, null);
122
166
  const normalAbs = normal.meanAbs;
123
- const events = found.map((event) => ({ ...event, multiple: normalAbs ? Math.abs(event.move) / normalAbs : null }))
167
+ const reacted = earningsReactionSessions(options.earnings ?? [], sessions);
168
+ const events = found.map((event) => ({ ...event, earnings: reacted.has(event.session), multiple: normalAbs ? Math.abs(event.move) / normalAbs : null }))
124
169
  .sort((left, right) => right.date.localeCompare(left.date) || left.kind.localeCompare(right.kind));
125
170
  const byKind = Object.fromEntries(MACRO_EVENT_KINDS.map((kind) =>
126
- [kind, stats(events.filter((event) => event.kind === kind).map((event) => event.move), normalAbs)])) as Record<MacroEventKind, MacroDayStats>;
127
- const allEvents = stats([...reactionSessions].map((session) => moves.get(session)!), normalAbs);
128
- return { symbol: options.symbol, events, byKind, allEvents, normal, start, asOf: end };
171
+ [kind, stats(events.filter((event) => event.kind === kind && !event.earnings).map((event) => event.move), normalAbs)])) as Record<MacroEventKind, MacroDayStats>;
172
+ const sessionMoves = (flagged: boolean) => [...reactionSessions].filter((session) => reacted.has(session) === flagged).map((session) => moves.get(session)!);
173
+ return { symbol: options.symbol, events, byKind, allEvents: stats(sessionMoves(false), normalAbs), earnings: stats(sessionMoves(true), normalAbs),
174
+ normal, start, asOf: end };
129
175
  }
130
176
 
131
177
  /**
@@ -9,8 +9,8 @@ import { useAutoRefresh } from "../../../react/auto-refresh";
9
9
  import { useThemeColors } from "../../../theme/theme-context";
10
10
  import type { PaneProps } from "../../../types/plugin";
11
11
  import { Box } from "../../../ui";
12
- import { loadMacroDayHistory, loadMacroReleases } from "./client";
13
- import { projectMacroDays, type MacroDayEvent, type MacroDayModel } from "./model";
12
+ import { loadMacroDayEarnings, loadMacroDayHistory, loadMacroReleases, macroDayEarningsNotice } from "./client";
13
+ import { macroDayEventLabel, projectMacroDays, type MacroDayEvent, type MacroDayModel } from "./model";
14
14
  import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, staleMacroReleasesNotice, type MacroEventKind } from "./releases";
15
15
 
16
16
  export const LOOKBACK_OPTIONS = [{ value: "1", label: "1Y" }, { value: "2", label: "2Y" }, { value: "3", label: "3Y" }, { value: "5", label: "5Y" }];
@@ -23,14 +23,22 @@ function pct(value: number | null | undefined, signed = true): string {
23
23
  }
24
24
  const times = (value: number | null | undefined) => value == null ? "--" : `${value.toFixed(1)}x`;
25
25
 
26
- const COLUMNS: DataTableColumn[] = [
27
- { id: "date", label: "Date", width: 19, align: "left" },
28
- { id: "release", label: "Release", width: 8, align: "left" },
26
+ const EARNINGS_MARK = "earnings";
27
+ const DATE: DataTableColumn = { id: "date", label: "Date", width: 19, align: "left" };
28
+ const MOVE: DataTableColumn[] = [
29
29
  { id: "move", label: "Move", width: 9, align: "right" },
30
30
  { id: "multiple", label: "vs Normal", width: 10, align: "right" },
31
31
  ];
32
- /** One release selected: every row would repeat its name. */
33
- const ONE_RELEASE_COLUMNS = COLUMNS.filter((column) => column.id !== "release");
32
+
33
+ /**
34
+ * With every release listed, the release's name, widened to take the earnings marker where a row has one. With one
35
+ * selected, a column only for the marker: every row would repeat the name.
36
+ */
37
+ function columnsFor(release: string, rows: readonly MacroDayEvent[]): DataTableColumn[] {
38
+ const marked = rows.some((event) => event.earnings);
39
+ if (release === "all") return [DATE, { id: "release", label: "Release", width: marked ? 15 : 8, align: "left" }, ...MOVE];
40
+ return marked ? [DATE, { id: "release", label: "Note", width: EARNINGS_MARK.length, align: "left" }, ...MOVE] : [DATE, ...MOVE];
41
+ }
34
42
 
35
43
  function figures(model: MacroDayModel, release: string): StatItem[] {
36
44
  const normal: StatItem = { id: "normal", label: "Normal day", value: pct(model.normal.meanAbs, false), detail: "avg |move|" };
@@ -39,7 +47,8 @@ function figures(model: MacroDayModel, release: string): StatItem[] {
39
47
  const stats = model.byKind[kind];
40
48
  return { id: kind, label: `${MACRO_EVENT_LABELS[kind]} day`, value: pct(stats.meanAbs, false),
41
49
  detail: stats.count ? `${times(stats.multiple)} normal` : undefined };
42
- }), normal];
50
+ }), ...(model.earnings.count ? [{ id: "earnings", label: "Earnings day", value: pct(model.earnings.meanAbs, false),
51
+ detail: `${times(model.earnings.multiple)} normal` }] : []), normal];
43
52
  }
44
53
  const stats = model.byKind[release as MacroEventKind];
45
54
  const tone = (value: number | null) => value == null ? undefined : value >= 0 ? "positive" as const : "negative" as const;
@@ -64,12 +73,10 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
64
73
  const loader = useCallback(async (force: boolean) => {
65
74
  controller.current?.abort();
66
75
  controller.current = new AbortController();
67
- const [snapshot, releases] = await Promise.all([
68
- loadMacroDayHistory({ instrument: instrument!, forceRefresh: force, signal: controller.current.signal }),
69
- loadMacroReleases({ force }),
70
- ]);
76
+ const request = { instrument: instrument!, forceRefresh: force, signal: controller.current.signal };
77
+ const [snapshot, releases, earnings] = await Promise.all([loadMacroDayHistory(request), loadMacroReleases({ force }), loadMacroDayEarnings(request)]);
71
78
  if (!snapshot.history.length && snapshot.error) throw new Error(snapshot.error);
72
- return { ...snapshot, releases };
79
+ return { ...snapshot, releases, earnings };
73
80
  }, [instrumentKey]);
74
81
  const history = useAsyncResource(instrument ? loader : null);
75
82
  useEffect(() => () => controller.current?.abort(), [loader]);
@@ -77,26 +84,29 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
77
84
  usePaneRefreshKey(() => { void history.reload(); }, { focused });
78
85
 
79
86
  const model = useMemo(() => history.data ? projectMacroDays(history.data.history, { symbol: symbol ?? "", lookbackYears: Number(lookback) || 5,
80
- releases: history.data.releases.releases, coveredThrough: history.data.releases.coveredThrough }) : null, [history.data, lookback, symbol]);
87
+ releases: history.data.releases.releases, coveredThrough: history.data.releases.coveredThrough, earnings: history.data.earnings.reports }) : null,
88
+ [history.data, lookback, symbol]);
81
89
  const releasesNotice = history.data ? staleMacroReleasesNotice(history.data.releases) : null;
90
+ const earningsNotice = history.data ? macroDayEarningsNotice(history.data.earnings) : null;
82
91
  const rows = useMemo(() => model ? model.events.filter((event) => release === "all" || event.kind === release) : [], [model, release]);
83
92
  const stats = useMemo(() => model?.events.length ? figures(model, release) : [], [model, release]);
93
+ const columns = useMemo(() => columnsFor(release, rows), [release, rows]);
84
94
 
85
95
  usePaneNoticeFooter({ registrationId: "macro-day-notices", focused,
86
- notices: [...new Set([identityError, history.error, history.data?.error, releasesNotice].filter((value): value is string => !!value))] });
96
+ notices: [...new Set([identityError, history.error, history.data?.error, releasesNotice, earningsNotice].filter((value): value is string => !!value))] });
87
97
  usePaneFooter("macro-day", () => ({ info: [
88
98
  ...(history.loading ? [{ id: "loading", parts: [{ text: "loading history", tone: "muted" as const }] }] : []),
89
- ...(history.data?.stale ? [{ id: "stale", parts: [{ text: "stale history", tone: "warning" as const }] }] : []),
99
+ ...(history.data?.stale || history.data?.earnings.stale ? [{ id: "stale", parts: [{ text: "stale history", tone: "warning" as const }] }] : []),
90
100
  ...(model?.asOf ? [{ id: "date", parts: [{ text: `daily closes ${model.start} to ${model.asOf}`, tone: "muted" as const }] }] : []),
91
- ] }), [history.loading, history.data?.stale, model?.start, model?.asOf]);
101
+ ] }), [history.loading, history.data?.stale, history.data?.earnings.stale, model?.start, model?.asOf]);
92
102
 
93
103
  const renderCell = useCallback((event: MacroDayEvent, column: DataTableColumn): DataTableCell => {
94
104
  if (column.id === "date") return { text: event.session === event.date ? event.date : `${event.date} → ${event.session.slice(5)}`, value: event.date };
95
- if (column.id === "release") return { text: MACRO_EVENT_LABELS[event.kind] };
105
+ if (column.id === "release") return { text: release === "all" ? macroDayEventLabel(event) : event.earnings ? EARNINGS_MARK : "" };
96
106
  if (column.id === "move") return { text: pct(event.move), value: event.move * 100,
97
107
  color: event.move > 0 ? colors.positive : event.move < 0 ? colors.negative : undefined };
98
108
  return { text: times(event.multiple), value: event.multiple };
99
- }, [colors]);
109
+ }, [colors, release]);
100
110
 
101
111
  const contentHeight = Math.max(4, height - 1 - statGridRows(stats, width));
102
112
  const key = (event: MacroDayEvent) => `${event.kind}:${event.date}`;
@@ -110,7 +120,7 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
110
120
  error={!model ? history.error ?? identityError ?? null : null} empty={!!model && !model.events.length}>
111
121
  {model ? <>
112
122
  <StatGrid items={stats} width={width} />
113
- <DataTableView<MacroDayEvent> focused={focused} columns={release === "all" ? COLUMNS : ONE_RELEASE_COLUMNS} items={rows} rootWidth={width} rootHeight={contentHeight}
123
+ <DataTableView<MacroDayEvent> focused={focused} columns={columns} items={rows} rootWidth={width} rootHeight={contentHeight}
114
124
  getItemKey={key} emptyStateTitle="No release days." sortColumnId={null} sortDirection="desc" selectedTextOverridesCellColor
115
125
  selection={{ kind: "id", selectedId: selectedRow, getId: key, onChange: (id) => setSelectedRow(id) }}
116
126
  getExportMetadata={() => [["symbol", model.symbol], ["from", model.start ?? ""], ["to", model.asOf ?? ""],
@@ -77,18 +77,24 @@ const twoDigits = (value: number) => String(value).padStart(2, "0");
77
77
  /** A New York calendar day ("2026-10-09") as the footer names it: "Oct 9". */
78
78
  const dayLabel = (day: string) => formatShortDate(day, { year: false, utc: true, fallback: day });
79
79
 
80
+ const isTimestamp = (value: number | null | undefined): value is number =>
81
+ typeof value === "number" && Number.isFinite(value) && value > 0;
82
+
80
83
  /**
81
84
  * What a market board covers and when it is from, for the footer: how many of
82
85
  * the largest names it holds, the completed regular session it shows outside
83
- * that session, when the server put the snapshot together, in New York time,
84
- * dated when that day is not the session's (or, while the session trades,
85
- * today's), and when this client last checked, which is a separate figure.
86
- * A footer narrower than `maxWidth` loses the check first, then the snapshot,
87
- * then the session; after a failed refresh (`failed`) the age of the last good
88
- * check outlasts the snapshot. Null when not even the count fits.
86
+ * that session, the time its figures are as of, in New York time, dated when
87
+ * that day is not the session's (or, while the session trades, today's), and
88
+ * when this client last checked, which is a separate figure. A board of a
89
+ * completed session is as of that session's close (`regularSessionClosedAt`);
90
+ * otherwise, or from a server that does not say, the time is when the server
91
+ * put the snapshot together. A footer narrower than `maxWidth` loses the check
92
+ * first, then that time, then the session; after a failed refresh (`failed`)
93
+ * the age of the last good check outlasts the time. Null when not even the
94
+ * count fits.
89
95
  */
90
96
  export function heatmapBoardCaption(
91
- board: { topCount: number; fetchedAt: number; regularSessionDate?: string | null },
97
+ board: { topCount: number; fetchedAt: number; regularSessionDate?: string | null; regularSessionClosedAt?: number | null },
92
98
  { now, checked = null, failed = false, maxWidth = Number.POSITIVE_INFINITY }:
93
99
  { now: number; checked?: string | null; failed?: boolean; maxWidth?: number },
94
100
  ): string | null {
@@ -96,11 +102,13 @@ export function heatmapBoardCaption(
96
102
  // Display order; `rank` is the order they give way in, highest first.
97
103
  const parts: Array<{ text: string; rank: number }> = [{ text: `top ${board.topCount}`, rank: 0 }];
98
104
  if (sessionDate) parts.push({ text: `${dayLabel(sessionDate)} session`, rank: 1 });
99
- if (Number.isFinite(board.fetchedAt) && board.fetchedAt > 0) {
100
- const { hour, minute } = zonedDateTimeParts(board.fetchedAt, NEW_YORK);
105
+ const closedAt = sessionDate && isTimestamp(board.regularSessionClosedAt) ? board.regularSessionClosedAt : null;
106
+ const time = closedAt ?? (isTimestamp(board.fetchedAt) ? board.fetchedAt : null);
107
+ if (time != null) {
108
+ const { hour, minute } = zonedDateTimeParts(time, NEW_YORK);
101
109
  const clock = `${twoDigits(hour)}:${twoDigits(minute)} ET`;
102
- const day = zonedDateKey(board.fetchedAt, NEW_YORK);
103
- const text = `snapshot ${day === (sessionDate ?? zonedDateKey(now, NEW_YORK)) ? clock : `${dayLabel(day)} ${clock}`}`;
110
+ const day = zonedDateKey(time, NEW_YORK);
111
+ const text = `${closedAt != null ? "as of" : "snapshot"} ${day === (sessionDate ?? zonedDateKey(now, NEW_YORK)) ? clock : `${dayLabel(day)} ${clock}`}`;
104
112
  parts.push({ text, rank: failed ? 3 : 2 });
105
113
  }
106
114
  if (checked) parts.push({ text: `checked ${checked}`, rank: failed ? 2 : 3 });
@@ -5,7 +5,7 @@ import type { ColumnConfig } from "../../../types/config";
5
5
  import type { TickerFinancials } from "../../../types/financials";
6
6
  import {
7
7
  getRegularSessionMoveDisplay,
8
- getSessionMoveDisplay,
8
+ getTradedSessionMoveDisplay,
9
9
  type ActiveQuoteDisplay,
10
10
  type ExtendedSession,
11
11
  } from "../../../market-data/market/status";
@@ -227,7 +227,7 @@ export function buildPortfolioHeatmapAssets({
227
227
  // Colored like the market boards: an open extended session's move, else the regular session's.
228
228
  const display: (ActiveQuoteDisplay & { session?: ExtendedSession }) | null = regularSession
229
229
  ? getRegularSessionMoveDisplay(quote)
230
- : getSessionMoveDisplay(quote);
230
+ : getTradedSessionMoveDisplay(quote);
231
231
  const price = display != null && Number.isFinite(display.price) ? display.price : null;
232
232
  const size = positiveNumber(getSortValue(sizeColumn, ticker, snapshot, context));
233
233
  const hasChange = display?.changePercent != null && Number.isFinite(display.changePercent);
@@ -5,18 +5,19 @@ import type {
5
5
  CloudSessionMoversSide,
6
6
  } from "../../../api-client/market-movers";
7
7
  import type { DataProvider } from "../../../types/data-provider";
8
- import { CATEGORY_MAP, screenerQuoteFromQuote, type ScreenerTabId } from "./model";
8
+ import { CATEGORY_MAP, moverRegularSession, screenerQuoteFromQuote, type MoverQuote, type ScreenerTabId } from "./model";
9
9
  import {
10
10
  fetchPreferredMarketMovers,
11
11
  fetchTrending,
12
12
  type MarketMoversResult,
13
13
  type PreferredMoversOptions,
14
- type ScreenerQuote,
15
14
  type TrendingSymbol,
16
15
  } from "./screener";
17
16
 
18
17
  export interface MarketMoverTabResult extends MarketMoversResult {
19
18
  tab: ScreenerTabId;
19
+ /** In the list's order; Last and Chg% the regular session, as `ticker` reads them. */
20
+ quotes: MoverQuote[];
20
21
  }
21
22
 
22
23
  export interface MarketMoverTabDependencies {
@@ -36,9 +37,9 @@ const defaultDependencies: MarketMoverTabDependencies = {
36
37
  async function hydrateTrending(
37
38
  trending: readonly TrendingSymbol[],
38
39
  provider: DataProvider | null,
39
- ): Promise<ScreenerQuote[]> {
40
+ ): Promise<MoverQuote[]> {
40
41
  const symbols = trending.slice(0, 25).map(({ symbol }) => symbol);
41
- const bySymbol = new Map<string, ScreenerQuote>();
42
+ const bySymbol = new Map<string, MoverQuote>();
42
43
  if (!provider) return [];
43
44
  if (provider.getQuotesBatch) {
44
45
  const results = await provider.getQuotesBatch(
@@ -79,7 +80,8 @@ export async function loadMarketMoverTab(
79
80
  };
80
81
  }
81
82
  const result = await dependencies.fetchPreferred(CATEGORY_MAP[tab], 25, options);
82
- return { ...result, tab };
83
+ // Ranked by the live print first, so reading each row's regular session keeps its place.
84
+ return { ...result, quotes: result.quotes.map(moverRegularSession), tab };
83
85
  }
84
86
 
85
87
  /** The server refused the list for the account's plan. */
@@ -34,6 +34,7 @@ import {
34
34
  type MarketMoverColumn,
35
35
  type MarketMoverRow,
36
36
  type MarketMoverSortPreference,
37
+ type MoverQuote,
37
38
  type ScreenerTabId,
38
39
  type TabId,
39
40
  } from "./model";
@@ -58,7 +59,7 @@ import {
58
59
  /** Stable identity: a fresh literal here would reload the board every render. */
59
60
  const NO_SAVED_SELECTION: string[] = [];
60
61
  const SESSION_CHECK_MS = 30_000;
61
- const NO_QUOTES: ScreenerQuote[] = [];
62
+ const NO_QUOTES: MoverQuote[] = [];
62
63
  const moverKey = (row: Pick<ScreenerQuote, "symbol" | "exchange">) => publicTickerKey(row.symbol, row.exchange);
63
64
 
64
65
  /** What is trading in New York, re-read every half minute so the pane follows 04:00, 09:30 and 16:00. */
@@ -154,7 +155,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
154
155
  }) {
155
156
  const dataProvider = useAssetData();
156
157
  const { pinTicker } = usePluginTickerActions();
157
- const [quotes, setQuotes] = useState<ScreenerQuote[]>([]);
158
+ const [quotes, setQuotes] = useState<MoverQuote[]>([]);
158
159
  const [loadedTab, setLoadedTab] = useState<TabId | null>(null);
159
160
  const visibleQuotes = loadedTab === activeTab ? quotes : NO_QUOTES;
160
161
  // The first load starts before the effect runs; an empty board is not "no data".
@@ -184,6 +184,36 @@ function moverExtended(quote: Quote): ExtendedSessionDisplay | null {
184
184
  return extended && isFiniteNumber(extended.price) ? extended : null;
185
185
  }
186
186
 
187
+ /**
188
+ * A listed row as `ticker` reads its quote: Last and Chg% are the regular
189
+ * session's close and move, and a pre-market or after-hours print is its
190
+ * `extended`, measured from that close. The list ranked it by its live print
191
+ * first, so its place stays. A row without its quote's sessions (an older
192
+ * server) reads as it came.
193
+ */
194
+ export function moverRegularSession({ sessions, ...row }: ScreenerQuote): MoverQuote {
195
+ if (!sessions || row.price == null) return row;
196
+ const quote: Quote = {
197
+ symbol: row.symbol,
198
+ price: row.price,
199
+ change: row.change ?? Number.NaN,
200
+ changePercent: row.changePercent ?? Number.NaN,
201
+ previousClose: row.previousClose,
202
+ currency: row.currency,
203
+ lastUpdated: row.lastUpdated ?? Number.NaN,
204
+ listingExchangeName: row.exchange || undefined,
205
+ ...sessions,
206
+ };
207
+ const headline = getRegularSessionDisplay(quote)!;
208
+ return {
209
+ ...row,
210
+ price: screenerNumber(headline.price),
211
+ change: screenerNumber(headline.change),
212
+ changePercent: screenerNumber(headline.changePercent),
213
+ extended: moverExtended(quote),
214
+ };
215
+ }
216
+
187
217
  /**
188
218
  * A trending row from its quote: Last and Chg% are the regular session, as
189
219
  * `ticker` reads it, and a pre-market or after-hours print rides beside them.
@@ -8,7 +8,9 @@ import type {
8
8
  CloudMarketScreenerCategory,
9
9
  CloudMarketScreenerItem,
10
10
  CloudMarketScreenerPayload,
11
+ CloudMoverSessionFields,
11
12
  } from "../../../api-client/types";
13
+ import type { MarketState } from "../../../types/financials";
12
14
  import { hasProAccess } from "../../../api-client/plan-access";
13
15
 
14
16
  const CACHE_KIND = "market-screener";
@@ -184,6 +186,32 @@ export function convertScreenerPriceUnit(value: number | undefined, from: string
184
186
  ? value / source.divisor * target.divisor : undefined;
185
187
  }
186
188
 
189
+ const MARKET_STATES = new Set<string>(["PRE", "REGULAR", "POST", "PREPRE", "POSTPOST", "CLOSED"] satisfies MarketState[]);
190
+ const SESSION_NUMBERS = [
191
+ "regularClose", "regularChange", "regularChangePercent",
192
+ "preMarketPrice", "preMarketChange", "preMarketChangePercent",
193
+ "postMarketPrice", "postMarketChange", "postMarketChangePercent",
194
+ ] as const;
195
+ const isSessionDate = (value: unknown): value is string => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
196
+
197
+ /**
198
+ * A row's quote sessions as the server sends them beside its live print, or
199
+ * undefined when it sends none (an older server), so the row reads as it came.
200
+ */
201
+ function parseScreenerSessions(row: Record<string, unknown>): CloudMoverSessionFields | undefined {
202
+ if (typeof row.marketState !== "string" || !MARKET_STATES.has(row.marketState)) return undefined;
203
+ const sessions: CloudMoverSessionFields = { marketState: row.marketState as MarketState };
204
+ if (isSessionDate(row.changeSessionDate)) sessions.changeSessionDate = row.changeSessionDate;
205
+ for (const key of SESSION_NUMBERS) {
206
+ const value = screenerNumber(row[key]);
207
+ if (value != null) sessions[key] = value;
208
+ }
209
+ if (sessions.regularClose != null && isSessionDate(row.regularCloseSessionDate)) {
210
+ sessions.regularCloseSessionDate = row.regularCloseSessionDate;
211
+ }
212
+ return sessions;
213
+ }
214
+
187
215
  /** Validate numeric fields at the API boundary so absent values stay unavailable. */
188
216
  export function parseScreenerResponse(data: unknown): ScreenerQuote[] {
189
217
  if (!data || typeof data !== "object" || !("quotes" in data) || !Array.isArray(data.quotes)) {
@@ -202,6 +230,7 @@ export function parseScreenerResponse(data: unknown): ScreenerQuote[] {
202
230
  dayHigh: screenerNumber(q.dayHigh) ?? undefined, dayLow: screenerNumber(q.dayLow) ?? undefined,
203
231
  previousClose: screenerNumber(q.previousClose) ?? undefined, exchange: typeof q.exchange === "string" ? q.exchange : "",
204
232
  lastUpdated: typeof q.lastUpdated === "number" && Number.isFinite(q.lastUpdated) && q.lastUpdated > 0 ? q.lastUpdated : undefined,
233
+ sessions: parseScreenerSessions(q),
205
234
  }];
206
235
  });
207
236
  }
@@ -274,6 +303,8 @@ function mergeCloudScreenerItem(
274
303
  dayLow: screenerNumber(item.dayLow) ?? metadataPrice(metadata?.dayLow),
275
304
  exchange: item.exchange || metadata?.exchange || "",
276
305
  lastUpdated: item.lastUpdated,
306
+ previousClose: screenerNumber(item.previousClose) ?? undefined,
307
+ sessions: parseScreenerSessions(item as unknown as Record<string, unknown>),
277
308
  };
278
309
  }
279
310
 
@@ -1,64 +1,27 @@
1
- import { chartSeriesProvider } from "../../../capabilities";
2
- import type { ChartSeriesCatalogItem, ChartSeriesCapability } from "../../../capabilities";
3
- import { colors } from "../../../theme/colors";
4
- import type { ResolvedSeries, TimeSeriesPoint } from "../../../time-series/types";
1
+ import type { SeriesDefinition, SeriesLoadResult } from "../../../series/types";
5
2
  import { buildValuationSeries } from "./align";
6
3
  import { defaultValuationSeriesLoader, type ValuationSeriesLoader } from "./client";
7
4
  import { indicatorUnavailableReason, indicatorSeries, type IndicatorDef } from "./defs";
8
- import { findIndicator, INDICATORS } from "./indicators";
5
+ import { INDICATORS } from "./indicators";
9
6
  import type { DatedSeries } from "./series";
10
7
 
11
- export const MARKET_VALUATION_CAPABILITY_ID = "market-valuation";
8
+ /** `CAP:market-valuation:<indicator>`, the expression charts saved before the catalog had these. */
9
+ const MARKET_VALUATION_NAMESPACE = "market-valuation";
12
10
 
13
- function catalogItem(indicator: IndicatorDef): ChartSeriesCatalogItem {
14
- return {
15
- seriesId: indicator.id,
16
- label: indicator.label,
17
- description: indicator.description,
18
- detail: "Market valuation",
19
- style: "line",
20
- };
21
- }
22
-
23
- function matches(indicator: IndicatorDef, query: string): boolean {
24
- const haystack = `${indicator.id} ${indicator.label} ${indicator.shortLabel} ${indicator.description}`;
25
- return haystack.toLowerCase().includes(query);
11
+ /** Shiller's series move once a month; the Z.1 and GDP ratios once a quarter. */
12
+ function indicatorFrequency(indicator: IndicatorDef): "monthly" | "quarterly" {
13
+ return indicatorSeries(indicator).some((def) => def.source.kind === "shiller") ? "monthly" : "quarterly";
26
14
  }
27
15
 
28
16
  /**
29
- * A ratio is not a series anyone can fetch, so the pane's own computation is what
30
- * makes it chartable. Exposing it here lets G overlay, say, the Buffett indicator
31
- * against CPI without the pane being involved.
17
+ * A ratio is not a series anyone can fetch, so the pane's own computation is
18
+ * what makes it chartable: overlaying the Buffett indicator on CPI needs no
19
+ * valuation pane on screen.
32
20
  */
33
- export function createValuationChartSeriesCapability(
21
+ export async function loadValuationIndicator(
22
+ indicator: IndicatorDef,
34
23
  loader: ValuationSeriesLoader = defaultValuationSeriesLoader,
35
- ): ChartSeriesCapability {
36
- return chartSeriesProvider({
37
- id: MARKET_VALUATION_CAPABILITY_ID,
38
- name: "Market Valuation",
39
- provider: {
40
- catalog: ({ limit }) => INDICATORS.slice(0, limit ?? INDICATORS.length).map(catalogItem),
41
- search: ({ query, limit }) => {
42
- const needle = query?.trim().toLowerCase();
43
- const hits = needle
44
- ? INDICATORS.filter((indicator) => matches(indicator, needle))
45
- : [...INDICATORS];
46
- return hits.slice(0, limit ?? hits.length).map(catalogItem);
47
- },
48
- resolve: async ({ seriesId }) => resolveValuationSeries(seriesId, loader),
49
- },
50
- });
51
- }
52
-
53
- export async function resolveValuationSeries(
54
- seriesId: string,
55
- loader: ValuationSeriesLoader = defaultValuationSeriesLoader,
56
- ): Promise<ResolvedSeries> {
57
- const indicator = findIndicator(seriesId);
58
- if (indicator.id !== seriesId.trim().toLowerCase()) {
59
- throw new Error(`Unknown valuation series ${seriesId}`);
60
- }
61
-
24
+ ): Promise<SeriesLoadResult> {
62
25
  const unavailable = indicatorUnavailableReason(indicator);
63
26
  if (unavailable) throw new Error(unavailable);
64
27
  const legs = new Map<string, DatedSeries>();
@@ -74,26 +37,23 @@ export async function resolveValuationSeries(
74
37
  if (built.points.at(-1)?.ratio == null) {
75
38
  warnings.push(`${indicator.label}: latest observation unavailable (${built.points.at(-1)!.date})`);
76
39
  }
77
- const points: TimeSeriesPoint[] = built.points.map((point) => {
78
- const date = new Date(point.date);
79
- return { date, observedAt: date, value: point.ratio };
80
- });
81
-
82
40
  return {
83
- id: `market-valuation:${indicator.id}`,
84
- label: indicator.label,
85
- color: colors.textBright,
86
- // Source percentages need the same units as ratios scaled into percentages.
87
- unit: indicator.axisUnit === "%" ? "%" : "x",
88
- unitGroup: indicator.axisUnit === "%" ? "valuation-percent" : "valuation-ratio",
89
- nativeFrequency: "daily",
90
- dataShape: "scalar",
91
- style: "line",
92
- transform: "raw",
93
- axis: "left",
94
- panelId: "main",
95
- interpolation: "none",
41
+ points: built.points.map((point) => ({ date: point.date, value: point.ratio })),
96
42
  ...(warnings.length ? { warning: warnings.join("; ") } : {}),
97
- points,
98
43
  };
99
44
  }
45
+
46
+ export function valuationSeriesDefinitions(
47
+ loader: ValuationSeriesLoader = defaultValuationSeriesLoader,
48
+ ): SeriesDefinition[] {
49
+ return INDICATORS.map((indicator) => ({
50
+ id: `${MARKET_VALUATION_NAMESPACE}:${indicator.id}`,
51
+ name: indicator.label,
52
+ unit: indicator.axisUnit === "%" ? "%" : "x",
53
+ frequency: indicatorFrequency(indicator),
54
+ group: "Valuation",
55
+ tags: [indicator.shortLabel, "market valuation"],
56
+ description: indicator.description,
57
+ load: () => loadValuationIndicator(indicator, loader),
58
+ }));
59
+ }
@@ -1,6 +1,6 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { valuationCache } from "./cache";
3
- import { createValuationChartSeriesCapability } from "./chart-series";
3
+ import { valuationSeriesDefinitions } from "./chart-series";
4
4
  import { INDICATORS, resolveIndicatorArg } from "./indicators";
5
5
  import { marketValuationHeadless } from "./headless";
6
6
  import { MarketValuationPane } from "./pane";
@@ -35,8 +35,8 @@ const KEYWORDS = [...new Set([
35
35
  ])];
36
36
 
37
37
  export const marketValuationModule: PluginModule = {
38
- // Exposes each ratio as a chartable series, so G can overlay them on anything else.
39
- capabilities: [createValuationChartSeriesCapability()],
38
+ // Each ratio is a catalog series, so G can overlay them on anything else.
39
+ series: valuationSeriesDefinitions(),
40
40
  panes: [{
41
41
  id: MARKET_VALUATION_PANE_ID,
42
42
  name: "Market Valuation",
@@ -370,11 +370,6 @@ export const INDICATORS: readonly IndicatorDef[] = [
370
370
 
371
371
  export const DEFAULT_INDICATOR_ID = BUFFETT_INDICATOR.id;
372
372
 
373
- export function findIndicator(id: string | null | undefined): IndicatorDef {
374
- const normalized = id?.trim().toLowerCase();
375
- return INDICATORS.find((entry) => entry.id === normalized) ?? BUFFETT_INDICATOR;
376
- }
377
-
378
373
  /** Resolves shortcut arguments like `VAL cape` or `VAL buffett` onto an indicator. */
379
374
  export function resolveIndicatorArg(arg: string | null | undefined): IndicatorDef | null {
380
375
  const query = arg?.trim().toLowerCase();