gloomberb 0.18.2 → 0.18.4

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 (954) hide show
  1. package/README.md +8 -8
  2. package/README.zh-CN.md +11 -11
  3. package/bin/{gloomberb → gloom} +3 -1
  4. package/package.json +6 -2
  5. package/patches/electrobun@1.18.1.patch +106 -5
  6. package/src/account/checkout-handoff.tsx +225 -0
  7. package/src/account/device-signin-dialog.tsx +2 -2
  8. package/src/account/device-signin.ts +2 -2
  9. package/src/account/upgrade-dialog.tsx +1 -1
  10. package/src/api-client/askg.ts +2 -2
  11. package/src/api-client/auth.ts +2 -2
  12. package/src/api-client/central-bank-rates.ts +6 -0
  13. package/src/api-client/collections.ts +5 -1
  14. package/src/api-client/cot.ts +2 -0
  15. package/src/api-client/crypto-etf-flows.ts +37 -0
  16. package/src/api-client/crypto-markets.ts +60 -0
  17. package/src/api-client/data.ts +99 -5
  18. package/src/api-client/doe.ts +15 -0
  19. package/src/api-client/equity-screener.ts +28 -0
  20. package/src/api-client/exchange-announcements.ts +38 -0
  21. package/src/api-client/fund-facts.ts +39 -0
  22. package/src/api-client/futures-curve.ts +9 -1
  23. package/src/api-client/hiring.ts +301 -0
  24. package/src/api-client/index.ts +24 -5
  25. package/src/api-client/market-discovery.ts +14 -0
  26. package/src/api-client/members.ts +6 -0
  27. package/src/api-client/mna.ts +6 -0
  28. package/src/api-client/paths.ts +18 -1
  29. package/src/api-client/request.ts +26 -7
  30. package/src/api-client/short-side-publications.ts +37 -0
  31. package/src/api-client/socket.ts +64 -4
  32. package/src/api-client/swap-curve.ts +35 -0
  33. package/src/api-client/telemetry.ts +17 -0
  34. package/src/api-client/themes.ts +7 -1
  35. package/src/api-client/types.ts +90 -2
  36. package/src/app/global-shortcuts.ts +1 -16
  37. package/src/app/pane-runtime/plugin-bindings.ts +9 -2
  38. package/src/app/runtime/desktop-deeplink.ts +5 -5
  39. package/src/app/runtime/plugin-bindings.ts +2 -2
  40. package/src/app/runtime/startup.ts +13 -0
  41. package/src/app/runtime/update.ts +28 -8
  42. package/src/app/star-prompt/status-line.tsx +2 -2
  43. package/src/architecture/core-builtin-allowlist.json +5 -2
  44. package/src/architecture/pane-tabs-allowlist.json +65 -0
  45. package/src/architecture/ui-literals-allowlist.json +3 -3
  46. package/src/architecture/ui-tokens-allowlist.json +1 -1
  47. package/src/brokers/broker-portfolio-sync.ts +37 -0
  48. package/src/brokers/directory.ts +11 -0
  49. package/src/brokers/logos.ts +3 -1
  50. package/src/brokers/signed-in/adapter.ts +304 -26
  51. package/src/brokers/signed-in/client.ts +14 -2
  52. package/src/brokers/signed-in/connect.ts +10 -2
  53. package/src/brokers/sync-broker-instance.ts +9 -1
  54. package/src/capabilities/chart-series.ts +22 -6
  55. package/src/cli/bank-section.ts +1 -1
  56. package/src/cli/base-converter.ts +23 -14
  57. package/src/cli/central-bank-events.ts +1 -1
  58. package/src/cli/command-options.ts +59 -4
  59. package/src/cli/commands/api.ts +9 -6
  60. package/src/cli/commands/broker.ts +2 -2
  61. package/src/cli/commands/command-utils.ts +2 -2
  62. package/src/cli/commands/crypto-hints.ts +7 -20
  63. package/src/cli/commands/fred-series.ts +4 -2
  64. package/src/cli/commands/keybindings.ts +2 -2
  65. package/src/cli/commands/market.ts +1189 -296
  66. package/src/cli/commands/news-hints.ts +2 -2
  67. package/src/cli/commands/news-search.ts +59 -6
  68. package/src/cli/commands/options-leaps.ts +41 -26
  69. package/src/cli/commands/overview.ts +121 -48
  70. package/src/cli/commands/plugins.ts +9 -9
  71. package/src/cli/commands/remote.ts +13 -10
  72. package/src/cli/commands/search.ts +64 -44
  73. package/src/cli/commands/system.ts +27 -19
  74. package/src/cli/commands/themes.ts +1 -1
  75. package/src/cli/commands/ticker.ts +272 -70
  76. package/src/cli/context.ts +3 -2
  77. package/src/cli/desktop-pane-shot-view.ts +2 -2
  78. package/src/cli/desktop-pane-shot.ts +1 -1
  79. package/src/cli/earnings-calendar.ts +4 -4
  80. package/src/cli/entry.ts +18 -1
  81. package/src/cli/errors.ts +20 -2
  82. package/src/cli/exit-codes.ts +1 -1
  83. package/src/cli/export-source.ts +10 -4
  84. package/src/cli/freshness.ts +2 -8
  85. package/src/cli/fx-forward.ts +130 -0
  86. package/src/cli/fx-pair.ts +10 -9
  87. package/src/cli/glossary.ts +64 -20
  88. package/src/cli/help.ts +20 -14
  89. package/src/cli/history-export.ts +126 -0
  90. package/src/cli/history-rows.ts +196 -43
  91. package/src/cli/index-roots.ts +5 -45
  92. package/src/cli/index.ts +32 -18
  93. package/src/cli/issuer-membership.ts +103 -0
  94. package/src/cli/known-gaps.ts +118 -25
  95. package/src/cli/listing-arg.ts +315 -43
  96. package/src/cli/name-entries.ts +73 -22
  97. package/src/cli/native-smoke.ts +2 -0
  98. package/src/cli/not-a-ticker.ts +19 -9
  99. package/src/cli/opentui-libc.ts +5 -0
  100. package/src/cli/options.ts +12 -0
  101. package/src/cli/pane-functions/capabilities.ts +77 -20
  102. package/src/cli/pane-functions/catalog.ts +40 -23
  103. package/src/cli/pane-functions/discovery.ts +1 -1
  104. package/src/cli/pane-functions/dom.ts +12 -8
  105. package/src/cli/pane-functions/financial-ratio-shot.ts +2 -2
  106. package/src/cli/pane-functions/freshness.ts +174 -23
  107. package/src/cli/pane-functions/function-help.ts +66 -27
  108. package/src/cli/pane-functions/headless.ts +48 -16
  109. package/src/cli/pane-functions/index.ts +82 -21
  110. package/src/cli/pane-functions/options.ts +22 -2
  111. package/src/cli/pane-functions/report-notices.ts +12 -4
  112. package/src/cli/pane-functions/report.ts +5 -5
  113. package/src/cli/pane-functions/resolver.ts +65 -14
  114. package/src/cli/pane-functions/screenshot-assessment.ts +322 -0
  115. package/src/cli/pane-functions/screenshot-bridge.ts +152 -0
  116. package/src/cli/pane-functions/screenshot-chart-evidence.ts +396 -0
  117. package/src/cli/pane-functions/screenshot-data-evidence.ts +383 -0
  118. package/src/cli/pane-functions/screenshot-evidence.ts +1 -1
  119. package/src/cli/pane-functions/screenshot-payload.ts +453 -0
  120. package/src/cli/pane-functions/screenshot-render.ts +316 -0
  121. package/src/cli/pane-functions/screenshot-types.ts +162 -0
  122. package/src/cli/pane-functions/shot-options.ts +8 -3
  123. package/src/cli/peso-dollar-rates.ts +79 -0
  124. package/src/cli/quote-answer.ts +38 -3
  125. package/src/cli/report-tables.ts +190 -27
  126. package/src/cli/restore-plugins.ts +29 -14
  127. package/src/cli/result.ts +122 -25
  128. package/src/cli/series-search.ts +5 -5
  129. package/src/cli/statement-filings.ts +19 -0
  130. package/src/cli/suffixed-listing-rows.ts +49 -0
  131. package/src/cli/symbol-args.ts +50 -15
  132. package/src/cli/symbol-cells.ts +120 -0
  133. package/src/cli/symbol-list.ts +226 -60
  134. package/src/cli/symbol-rows.ts +131 -25
  135. package/src/cli/ticker-resolution.ts +108 -11
  136. package/src/cli/treasury-yields.ts +5 -5
  137. package/src/cli/types.ts +2 -0
  138. package/src/cli/uncovered-instrument.ts +3 -3
  139. package/src/cli/unknown-command.ts +6 -6
  140. package/src/components/chart/composite/chart-layout.ts +131 -0
  141. package/src/components/chart/composite/composite-chart.tsx +387 -21
  142. package/src/components/chart/composite/drawing-store.ts +26 -6
  143. package/src/components/chart/composite/interactions.ts +9 -3
  144. package/src/components/chart/composite/legend.tsx +75 -3
  145. package/src/components/chart/composite/levels.ts +5 -0
  146. package/src/components/chart/composite/panel-gestures.ts +41 -0
  147. package/src/components/chart/composite/panel-labels.ts +2 -1
  148. package/src/components/chart/composite/panel-surface.tsx +316 -11
  149. package/src/components/chart/composite/price-axis-labels.tsx +27 -3
  150. package/src/components/chart/composite/rasterizer.ts +44 -19
  151. package/src/components/chart/composite/scene.ts +16 -4
  152. package/src/components/chart/composite/text-renderer.ts +48 -21
  153. package/src/components/chart/composite/toolbar.tsx +52 -5
  154. package/src/components/chart/composite/types.ts +19 -0
  155. package/src/components/chart/composite/use-composite-chart-keyboard.ts +120 -13
  156. package/src/components/chart/composite/use-composite-value-scales.ts +44 -0
  157. package/src/components/chart/composite/use-composite-viewport.ts +12 -3
  158. package/src/components/chart/composite/value-scale.ts +149 -0
  159. package/src/components/chart/composite/volume-profile-paint.ts +6 -3
  160. package/src/components/chart/core/pointer.ts +2 -0
  161. package/src/components/chart-table/index.ts +2 -0
  162. package/src/components/chart-table/read.tsx +74 -0
  163. package/src/components/command-bar/commands/collection-list.ts +41 -19
  164. package/src/components/command-bar/commands/collection.ts +9 -3
  165. package/src/components/command-bar/commands/direct/index.ts +1 -1
  166. package/src/components/command-bar/commands/direct/runtime.ts +3 -2
  167. package/src/components/command-bar/pane-templates/items.ts +59 -6
  168. package/src/components/command-bar/pane-templates/workflow.ts +10 -1
  169. package/src/components/command-bar/routes/root/command-items.ts +32 -6
  170. package/src/components/command-bar/routes/root/plugin-install.ts +1 -1
  171. package/src/components/command-bar/routes/root/provider-search.ts +42 -11
  172. package/src/components/command-bar/routes/root/results.ts +18 -4
  173. package/src/components/command-bar/routes/root/runtime.ts +15 -3
  174. package/src/components/command-bar/routes/root/search-providers.ts +25 -2
  175. package/src/components/command-bar/routes/root/selection.ts +6 -1
  176. package/src/components/command-bar/routes/root/shortcut-feedback.ts +4 -1
  177. package/src/components/command-bar/routes/root/shortcuts.ts +30 -3
  178. package/src/components/command-bar/routes/ticker-search/results.ts +73 -4
  179. package/src/components/command-bar/selection-runtime.ts +50 -10
  180. package/src/components/command-bar/surface/index.tsx +24 -2
  181. package/src/components/command-bar/view-model.ts +7 -0
  182. package/src/components/command-bar/workflow/broker.ts +2 -2
  183. package/src/components/command-bar/workflow/collection-actions.ts +46 -7
  184. package/src/components/command-bar/workflow/collection-ticker.ts +32 -4
  185. package/src/components/command-bar/workflow/fields.ts +8 -2
  186. package/src/components/command-bar/workflow/ops.ts +18 -4
  187. package/src/components/command-bar/workflow/portfolio-position.ts +53 -4
  188. package/src/components/command-bar/workflow/submit.ts +3 -2
  189. package/src/components/command-bar/workflow/tickers.ts +5 -1
  190. package/src/components/company-logo.tsx +39 -0
  191. package/src/components/data-table/sections.ts +4 -2
  192. package/src/components/data-table/view.tsx +23 -3
  193. package/src/components/form-modal/confirm.tsx +2 -1
  194. package/src/components/form-modal/request.ts +5 -0
  195. package/src/components/form-modal/routes.ts +4 -1
  196. package/src/components/index.ts +13 -6
  197. package/src/components/layout/detached-pane-shell.tsx +1 -0
  198. package/src/components/layout/floating-pane.tsx +1 -0
  199. package/src/components/layout/header.tsx +234 -60
  200. package/src/components/layout/market-summary.ts +9 -6
  201. package/src/components/layout/pane/capture-status.ts +1 -1
  202. package/src/components/layout/pane/footer/model.ts +11 -5
  203. package/src/components/layout/pane/header.tsx +10 -3
  204. package/src/components/layout/pane/index.tsx +1 -0
  205. package/src/components/layout/pane/pane-tabs.tsx +9 -1
  206. package/src/components/layout/shell/chrome.ts +19 -6
  207. package/src/components/layout/shell/index.tsx +2 -1
  208. package/src/components/layout/shell/pane/layers.tsx +19 -1
  209. package/src/components/layout/status-bar.tsx +2 -2
  210. package/src/components/market-board/index.tsx +11 -3
  211. package/src/components/onboarding/onboarding-wizard.tsx +1 -0
  212. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +2 -2
  213. package/src/components/onboarding/wizard-broker-sync.ts +4 -1
  214. package/src/components/pane-settings-dialog/desktop.tsx +20 -7
  215. package/src/components/pane-settings-dialog/tui.tsx +17 -9
  216. package/src/components/pane-settings-dialog/value.ts +8 -1
  217. package/src/components/pane-settings-dialog.tsx +44 -13
  218. package/src/components/sign-in-code-panel.tsx +27 -19
  219. package/src/components/ui/column-grid.tsx +48 -4
  220. package/src/components/ui/country-flag-art.tsx +180 -0
  221. package/src/components/ui/country-flag.tsx +118 -12
  222. package/src/components/ui/data-table/opentui/index.tsx +61 -2
  223. package/src/components/ui/data-table/opentui/model.ts +109 -2
  224. package/src/components/ui/data-table/types.ts +1 -1
  225. package/src/components/ui/display.tsx +5 -1
  226. package/src/components/ui/index.ts +3 -1
  227. package/src/components/ui/logo-mark.tsx +11 -2
  228. package/src/components/ui/query-bar.tsx +1 -1
  229. package/src/components/ui/row-notes.ts +79 -0
  230. package/src/components/ui/stat-grid.tsx +28 -3
  231. package/src/components/ui/status.tsx +3 -1
  232. package/src/core/app-services.ts +4 -0
  233. package/src/core/state/app/layout.ts +4 -4
  234. package/src/data/cached-query.ts +8 -1
  235. package/src/data/config/chart-settings.ts +3 -3
  236. package/src/data/config/home.ts +10 -2
  237. package/src/data/config/layout.ts +4 -1
  238. package/src/data/config/store/node.ts +4 -0
  239. package/src/data/config/store/normalize.ts +13 -3
  240. package/src/data/config/store/portfolio-as-of-column.ts +2 -2
  241. package/src/data/json-storage.ts +2 -0
  242. package/src/data/saved-copies.ts +84 -0
  243. package/src/docs/snapshot.json +65 -32
  244. package/src/i18n/es.ts +51 -12
  245. package/src/i18n/ja.ts +51 -12
  246. package/src/i18n/ko.ts +51 -12
  247. package/src/i18n/zh-cn.ts +51 -12
  248. package/src/i18n/zh-tw.ts +51 -12
  249. package/src/layout/desks.ts +1 -1
  250. package/src/layout/launch-layout.ts +1 -1
  251. package/src/layout/pane-manager/floating-actions.ts +5 -3
  252. package/src/layout/pane-manager/floating.ts +43 -1
  253. package/src/layout/pane-manager/index.ts +2 -0
  254. package/src/layout/selected-ticker.ts +74 -0
  255. package/src/market-data/depositary-pair.ts +251 -0
  256. package/src/market-data/futures-board.ts +1 -1
  257. package/src/market-data/futures-contract.ts +124 -40
  258. package/src/market-data/fx-closes.ts +17 -2
  259. package/src/market-data/hooks.tsx +19 -11
  260. package/src/market-data/market/change-basis.ts +49 -7
  261. package/src/market-data/market/freshness.ts +34 -5
  262. package/src/market-data/market/period-return.ts +163 -0
  263. package/src/market-data/market/trading-sessions.ts +13 -0
  264. package/src/market-data/market/year-to-date.ts +8 -13
  265. package/src/market-data/performance.ts +20 -14
  266. package/src/market-data/perpetual-premium.ts +32 -0
  267. package/src/market-data/published-apac-sessions.ts +2 -1
  268. package/src/market-data/published-futures-sessions.ts +69 -0
  269. package/src/market-data/quotes/contributions.ts +5 -1
  270. package/src/market-data/quotes/freshness.ts +42 -0
  271. package/src/market-data/quotes/intraday-bar.ts +37 -0
  272. package/src/market-data/quotes/regular-session-valuation.ts +13 -8
  273. package/src/market-data/quotes/resolution.ts +4 -2
  274. package/src/market-data/tax-equivalent.ts +1 -1
  275. package/src/market-data/ticker-price-returns.ts +3 -1
  276. package/src/news/aggregator.ts +5 -1
  277. package/src/news/news-model.ts +4 -0
  278. package/src/news/types.ts +11 -0
  279. package/src/notifications/app-notifier.ts +1 -1
  280. package/src/plugins/auto-update.ts +2 -2
  281. package/src/plugins/builtin/account-management/index.tsx +1 -0
  282. package/src/plugins/builtin/account-management/pane.tsx +9 -8
  283. package/src/plugins/builtin/alerts/alert-engine.ts +30 -18
  284. package/src/plugins/builtin/alerts/calendar-events.ts +378 -0
  285. package/src/plugins/builtin/alerts/cli.ts +485 -76
  286. package/src/plugins/builtin/alerts/command.ts +5 -10
  287. package/src/plugins/builtin/alerts/event-rows.ts +9 -5
  288. package/src/plugins/builtin/alerts/events-pane.tsx +71 -56
  289. package/src/plugins/builtin/alerts/events.ts +108 -5
  290. package/src/plugins/builtin/alerts/fired-log.ts +78 -0
  291. package/src/plugins/builtin/alerts/format.ts +2 -1
  292. package/src/plugins/builtin/alerts/headless.ts +100 -39
  293. package/src/plugins/builtin/alerts/history-pane.tsx +105 -62
  294. package/src/plugins/builtin/alerts/history-rows.ts +89 -0
  295. package/src/plugins/builtin/alerts/index.tsx +45 -18
  296. package/src/plugins/builtin/alerts/iv-level.ts +365 -0
  297. package/src/plugins/builtin/alerts/levels.ts +3 -3
  298. package/src/plugins/builtin/alerts/pane.tsx +97 -29
  299. package/src/plugins/builtin/alerts/price.ts +35 -0
  300. package/src/plugins/builtin/alerts/quotes.ts +24 -0
  301. package/src/plugins/builtin/alerts/research-builder.ts +84 -11
  302. package/src/plugins/builtin/alerts/research-rules.ts +77 -3
  303. package/src/plugins/builtin/alerts/scheduled-check.ts +206 -32
  304. package/src/plugins/builtin/alerts/scheduled-sources.ts +26 -3
  305. package/src/plugins/builtin/alerts/scheduled.ts +188 -29
  306. package/src/plugins/builtin/alerts/storage.ts +13 -10
  307. package/src/plugins/builtin/alerts/targets.ts +12 -3
  308. package/src/plugins/builtin/analytics/index.tsx +8 -2
  309. package/src/plugins/builtin/analytics/metrics.ts +1 -1
  310. package/src/plugins/builtin/analytics/risk-book.ts +215 -0
  311. package/src/plugins/builtin/analytics/risk-client.ts +13 -6
  312. package/src/plugins/builtin/analytics/risk-headless.ts +55 -21
  313. package/src/plugins/builtin/analytics/risk-math.ts +6 -3
  314. package/src/plugins/builtin/analytics/risk-model.ts +297 -104
  315. package/src/plugins/builtin/analytics/risk-options.ts +17 -2
  316. package/src/plugins/builtin/analytics/risk-pane.tsx +107 -43
  317. package/src/plugins/builtin/attention/pane.tsx +22 -16
  318. package/src/plugins/builtin/awards/headless.ts +7 -3
  319. package/src/plugins/builtin/awards/model.ts +18 -0
  320. package/src/plugins/builtin/awards/pane.tsx +12 -9
  321. package/src/plugins/builtin/backtest/export.ts +1 -1
  322. package/src/plugins/builtin/backtest/headless.ts +3 -3
  323. package/src/plugins/builtin/backtest/rules.ts +41 -5
  324. package/src/plugins/builtin/broker-manager/add-flow.tsx +12 -7
  325. package/src/plugins/builtin/broker-manager/detail.tsx +47 -19
  326. package/src/plugins/builtin/broker-manager/footer.ts +27 -11
  327. package/src/plugins/builtin/broker-manager/index.tsx +21 -12
  328. package/src/plugins/builtin/broker-manager/keyboard.ts +1 -1
  329. package/src/plugins/builtin/broker-manager/model.ts +12 -2
  330. package/src/plugins/builtin/broker-manager/pane-actions.tsx +183 -57
  331. package/src/plugins/builtin/broker-manager/table.ts +28 -12
  332. package/src/plugins/builtin/broker-trading/account-picker.tsx +2 -1
  333. package/src/plugins/builtin/broker-trading/chart-levels.ts +82 -13
  334. package/src/plugins/builtin/broker-trading/controller.ts +45 -13
  335. package/src/plugins/builtin/broker-trading/dock.tsx +24 -4
  336. package/src/plugins/builtin/broker-trading/headless.ts +2 -1
  337. package/src/plugins/builtin/broker-trading/index.tsx +7 -1
  338. package/src/plugins/builtin/broker-trading/linked-orders.ts +148 -0
  339. package/src/plugins/builtin/broker-trading/order-feed.ts +75 -7
  340. package/src/plugins/builtin/broker-trading/orders.tsx +109 -31
  341. package/src/plugins/builtin/broker-trading/overview-orders.tsx +9 -3
  342. package/src/plugins/builtin/broker-trading/pane-model.ts +30 -2
  343. package/src/plugins/builtin/broker-trading/pane.tsx +115 -26
  344. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +36 -7
  345. package/src/plugins/builtin/broker-trading/ticket.tsx +122 -36
  346. package/src/plugins/builtin/buildout/cells.tsx +4 -0
  347. package/src/plugins/builtin/buildout/detail/index.tsx +5 -1
  348. package/src/plugins/builtin/buildout/detail/ui.tsx +7 -4
  349. package/src/plugins/builtin/buildout/pane/index.tsx +34 -18
  350. package/src/plugins/builtin/buildout/pane/status.tsx +0 -4
  351. package/src/plugins/builtin/builtin-plugin-meta.ts +1 -1
  352. package/src/plugins/builtin/catalysts/client.ts +2 -2
  353. package/src/plugins/builtin/catalysts/detail.tsx +3 -2
  354. package/src/plugins/builtin/catalysts/index.tsx +1 -1
  355. package/src/plugins/builtin/catalysts/model.ts +49 -4
  356. package/src/plugins/builtin/catalysts/pane.tsx +9 -11
  357. package/src/plugins/builtin/central-bank-rates/client.ts +5 -0
  358. package/src/plugins/builtin/central-bank-rates/headless.ts +36 -10
  359. package/src/plugins/builtin/central-bank-rates/model.ts +33 -8
  360. package/src/plugins/builtin/changelog/index.tsx +1 -0
  361. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +1 -0
  362. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +2 -1
  363. package/src/plugins/builtin/chart-composer/chart-spec.ts +7 -0
  364. package/src/plugins/builtin/chart-composer/cli-options.ts +2 -5
  365. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  366. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -0
  367. package/src/plugins/builtin/chart-composer/editor.tsx +4 -1
  368. package/src/plugins/builtin/chart-composer/headless-schema.ts +28 -26
  369. package/src/plugins/builtin/chart-composer/headless.ts +253 -35
  370. package/src/plugins/builtin/chart-composer/index.tsx +12 -2
  371. package/src/plugins/builtin/chart-composer/leverage-overlay.ts +142 -0
  372. package/src/plugins/builtin/chart-composer/pane.tsx +128 -28
  373. package/src/plugins/builtin/chart-composer/series-catalog.ts +1 -0
  374. package/src/plugins/builtin/chart-composer/series-expression.ts +65 -6
  375. package/src/plugins/builtin/chart-composer/session-history.ts +15 -21
  376. package/src/plugins/builtin/chart-composer/session-range.ts +4 -2
  377. package/src/plugins/builtin/chart-composer/settings.ts +31 -19
  378. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +5 -6
  379. package/src/plugins/builtin/chart-composer/viewport-labels.ts +18 -8
  380. package/src/plugins/builtin/chat/attachments/model.ts +3 -1
  381. package/src/plugins/builtin/chat/content/index.tsx +1 -1
  382. package/src/plugins/builtin/chat/pane-links/open.ts +1 -1
  383. package/src/plugins/builtin/chat/people.ts +1 -1
  384. package/src/plugins/builtin/clinical-trials/client.ts +5 -2
  385. package/src/plugins/builtin/clinical-trials/model.ts +13 -4
  386. package/src/plugins/builtin/clinical-trials/pane.tsx +32 -12
  387. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +17 -0
  388. package/src/plugins/builtin/cloud/askg/controller.ts +1 -1
  389. package/src/plugins/builtin/cloud/askg/lock-note.ts +1 -1
  390. package/src/plugins/builtin/cloud/askg/tool-display.ts +1 -1
  391. package/src/plugins/builtin/cloud/auth-commands.ts +3 -3
  392. package/src/plugins/builtin/cloud/team/pane-sections.tsx +105 -47
  393. package/src/plugins/builtin/cloud/team/pane-ui.tsx +42 -4
  394. package/src/plugins/builtin/cloud/team/pane.tsx +204 -64
  395. package/src/plugins/builtin/cloud/thesis/store.ts +45 -22
  396. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  397. package/src/plugins/builtin/commodities-energy/index.tsx +29 -2
  398. package/src/plugins/builtin/commodities-energy/view-state.ts +2 -0
  399. package/src/plugins/builtin/commodity-balances/pane.tsx +14 -8
  400. package/src/plugins/builtin/company-attention/app-rank.tsx +11 -10
  401. package/src/plugins/builtin/company-attention/building-model.ts +222 -0
  402. package/src/plugins/builtin/company-attention/building.tsx +172 -0
  403. package/src/plugins/builtin/company-attention/client.ts +117 -1
  404. package/src/plugins/builtin/company-attention/evidence.ts +16 -3
  405. package/src/plugins/builtin/company-attention/headless.ts +101 -5
  406. package/src/plugins/builtin/company-attention/index.tsx +6 -3
  407. package/src/plugins/builtin/company-attention/model.ts +33 -7
  408. package/src/plugins/builtin/company-attention/moves-model.ts +253 -0
  409. package/src/plugins/builtin/company-attention/moves.tsx +159 -0
  410. package/src/plugins/builtin/company-attention/pane.tsx +89 -31
  411. package/src/plugins/builtin/company-attention/signals-model.ts +229 -0
  412. package/src/plugins/builtin/company-attention/signals.tsx +160 -0
  413. package/src/plugins/builtin/company-kpis/headless.ts +3 -2
  414. package/src/plugins/builtin/company-kpis/model.ts +2 -2
  415. package/src/plugins/builtin/company-kpis/pane.tsx +5 -4
  416. package/src/plugins/builtin/composite-plugins.ts +2 -1
  417. package/src/plugins/builtin/congress-trades/headless.ts +1 -1
  418. package/src/plugins/builtin/connections/index.ts +1 -0
  419. package/src/plugins/builtin/correlation/headless-schema.ts +3 -2
  420. package/src/plugins/builtin/correlation/headless.ts +7 -4
  421. package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
  422. package/src/plugins/builtin/correlation/settings.ts +1 -0
  423. package/src/plugins/builtin/cot/headless.ts +28 -6
  424. package/src/plugins/builtin/cot/model.ts +12 -0
  425. package/src/plugins/builtin/cot/pane.tsx +7 -5
  426. package/src/plugins/builtin/cpi/headless.ts +5 -1
  427. package/src/plugins/builtin/cpi/pane.tsx +11 -9
  428. package/src/plugins/builtin/credit-boards/model.ts +2 -0
  429. package/src/plugins/builtin/credit-documents/headless.ts +1 -1
  430. package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
  431. package/src/plugins/builtin/crypto-board/client.ts +86 -0
  432. package/src/plugins/builtin/crypto-board/defi-view.tsx +218 -0
  433. package/src/plugins/builtin/crypto-board/defi.ts +239 -0
  434. package/src/plugins/builtin/crypto-board/headless.ts +117 -6
  435. package/src/plugins/builtin/crypto-board/index.tsx +8 -3
  436. package/src/plugins/builtin/crypto-board/model.ts +40 -8
  437. package/src/plugins/builtin/crypto-board/pane.tsx +55 -21
  438. package/src/plugins/builtin/crypto-board/series.ts +19 -1
  439. package/src/plugins/builtin/crypto-etf-flows/client.ts +60 -0
  440. package/src/plugins/builtin/crypto-etf-flows/headless.ts +132 -0
  441. package/src/plugins/builtin/crypto-etf-flows/index.tsx +44 -0
  442. package/src/plugins/builtin/crypto-etf-flows/model.ts +121 -0
  443. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +193 -0
  444. package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
  445. package/src/plugins/builtin/debug/index.tsx +1 -0
  446. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  447. package/src/plugins/builtin/docs/index.tsx +1 -1
  448. package/src/plugins/builtin/docs/pane.tsx +2 -2
  449. package/src/plugins/builtin/docs/targets.ts +1 -1
  450. package/src/plugins/builtin/doe/client.ts +5 -1
  451. package/src/plugins/builtin/doe/headless.ts +12 -6
  452. package/src/plugins/builtin/doe/model.ts +43 -6
  453. package/src/plugins/builtin/doe/pane.tsx +19 -12
  454. package/src/plugins/builtin/earnings/board-model.ts +13 -6
  455. package/src/plugins/builtin/earnings/board-table.ts +4 -1
  456. package/src/plugins/builtin/earnings/board.tsx +253 -50
  457. package/src/plugins/builtin/earnings/calendar-model.ts +188 -0
  458. package/src/plugins/builtin/earnings/calendar.tsx +338 -0
  459. package/src/plugins/builtin/earnings/client.ts +4 -3
  460. package/src/plugins/builtin/earnings/headless.ts +53 -15
  461. package/src/plugins/builtin/earnings/history-model.ts +53 -36
  462. package/src/plugins/builtin/earnings/index.tsx +15 -22
  463. package/src/plugins/builtin/earnings/model.ts +17 -12
  464. package/src/plugins/builtin/earnings/scope.ts +89 -0
  465. package/src/plugins/builtin/earnings-calls/calendar.tsx +310 -0
  466. package/src/plugins/builtin/earnings-calls/data.ts +146 -2
  467. package/src/plugins/builtin/earnings-calls/headless.ts +151 -9
  468. package/src/plugins/builtin/earnings-calls/index.tsx +3 -3
  469. package/src/plugins/builtin/earnings-calls/lock-note.ts +1 -1
  470. package/src/plugins/builtin/earnings-calls/pane.tsx +149 -31
  471. package/src/plugins/builtin/earnings-calls/upcoming.ts +195 -0
  472. package/src/plugins/builtin/earnings-ripple/pane.tsx +10 -5
  473. package/src/plugins/builtin/econ/calendar-model.ts +132 -20
  474. package/src/plugins/builtin/econ/detail-view.tsx +48 -10
  475. package/src/plugins/builtin/econ/fred-catalog.ts +23 -6
  476. package/src/plugins/builtin/econ/fred-series-map.ts +4 -0
  477. package/src/plugins/builtin/econ/index.tsx +112 -25
  478. package/src/plugins/builtin/econ/print-watch.ts +2 -1
  479. package/src/plugins/builtin/econ/types.ts +32 -1
  480. package/src/plugins/builtin/econ-statistics/client.ts +16 -3
  481. package/src/plugins/builtin/econ-statistics/defs.ts +4 -0
  482. package/src/plugins/builtin/econ-statistics/detail.tsx +2 -2
  483. package/src/plugins/builtin/econ-statistics/headless.ts +13 -2
  484. package/src/plugins/builtin/econ-statistics/index.tsx +3 -3
  485. package/src/plugins/builtin/econ-statistics/pane.tsx +2 -1
  486. package/src/plugins/builtin/econ-statistics/stats.ts +103 -0
  487. package/src/plugins/builtin/econ-statistics/view.ts +16 -1
  488. package/src/plugins/builtin/equity-screener/client.ts +21 -3
  489. package/src/plugins/builtin/equity-screener/headless.ts +77 -5
  490. package/src/plugins/builtin/equity-screener/live.ts +1 -1
  491. package/src/plugins/builtin/equity-screener/model.ts +34 -0
  492. package/src/plugins/builtin/equity-screener/pane.tsx +148 -106
  493. package/src/plugins/builtin/equity-screener/screen-check.ts +90 -0
  494. package/src/plugins/builtin/equity-screener/ticker-check.tsx +148 -0
  495. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -1
  496. package/src/plugins/builtin/estimate-revisions/lock-note.ts +3 -3
  497. package/src/plugins/builtin/estimate-revisions/model.ts +8 -10
  498. package/src/plugins/builtin/exposure/headless.ts +51 -6
  499. package/src/plugins/builtin/exposure/index.tsx +28 -2
  500. package/src/plugins/builtin/exposure/model.ts +7 -0
  501. package/src/plugins/builtin/exposure/pane.tsx +16 -10
  502. package/src/plugins/builtin/fear-greed/index.tsx +1 -1
  503. package/src/plugins/builtin/filing-events/index.tsx +1 -1
  504. package/src/plugins/builtin/futures/model.ts +10 -9
  505. package/src/plugins/builtin/futures/returns.ts +8 -36
  506. package/src/plugins/builtin/futures/table.tsx +1 -1
  507. package/src/plugins/builtin/futures-curve/client.ts +40 -9
  508. package/src/plugins/builtin/futures-curve/headless.ts +39 -17
  509. package/src/plugins/builtin/futures-curve/model.ts +258 -69
  510. package/src/plugins/builtin/futures-curve/pane.tsx +46 -17
  511. package/src/plugins/builtin/fx-matrix/client.ts +15 -8
  512. package/src/plugins/builtin/fx-matrix/headless.ts +32 -14
  513. package/src/plugins/builtin/fx-matrix/index.tsx +13 -11
  514. package/src/plugins/builtin/fx-matrix/live-legs.ts +22 -0
  515. package/src/plugins/builtin/fx-matrix/settings.ts +2 -1
  516. package/src/plugins/builtin/gpu/board.tsx +4 -1
  517. package/src/plugins/builtin/gpu/changes.tsx +10 -10
  518. package/src/plugins/builtin/gpu/equities.tsx +4 -1
  519. package/src/plugins/builtin/gpu/headless.ts +9 -4
  520. package/src/plugins/builtin/gpu/history.tsx +4 -2
  521. package/src/plugins/builtin/gpu/pane.tsx +5 -3
  522. package/src/plugins/builtin/help/index.tsx +13 -3
  523. package/src/plugins/builtin/holders/beneficial-model.ts +8 -11
  524. package/src/plugins/builtin/holders/beneficial-report.ts +6 -5
  525. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  526. package/src/plugins/builtin/holders/client.ts +15 -2
  527. package/src/plugins/builtin/holders/format.ts +28 -17
  528. package/src/plugins/builtin/holders/headless.ts +48 -8
  529. package/src/plugins/builtin/holders/index-holders.ts +264 -0
  530. package/src/plugins/builtin/holders/pane.tsx +79 -15
  531. package/src/plugins/builtin/holders/report-rows.ts +35 -7
  532. package/src/plugins/builtin/holders/table-model.ts +2 -1
  533. package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
  534. package/src/plugins/builtin/holders/treemap.tsx +2 -2
  535. package/src/plugins/builtin/insider/base-values.ts +11 -7
  536. package/src/plugins/builtin/insider/client.ts +3 -0
  537. package/src/plugins/builtin/insider/headless.ts +20 -7
  538. package/src/plugins/builtin/insider/index.tsx +17 -8
  539. package/src/plugins/builtin/insider/issuer.ts +34 -4
  540. package/src/plugins/builtin/insider/signals-client.ts +3 -3
  541. package/src/plugins/builtin/insider/signals.tsx +1 -1
  542. package/src/plugins/builtin/insider/table-model.ts +35 -13
  543. package/src/plugins/builtin/insider/table.tsx +5 -2
  544. package/src/plugins/builtin/ipo-calendar/pane.tsx +7 -4
  545. package/src/plugins/builtin/iv-history/client.ts +20 -0
  546. package/src/plugins/builtin/iv-history/events.ts +145 -0
  547. package/src/plugins/builtin/iv-history/format.ts +3 -0
  548. package/src/plugins/builtin/iv-history/headless.ts +91 -7
  549. package/src/plugins/builtin/iv-history/index.tsx +1 -1
  550. package/src/plugins/builtin/iv-history/model.ts +13 -4
  551. package/src/plugins/builtin/iv-history/series.ts +3 -1
  552. package/src/plugins/builtin/jobs/lock-note.ts +1 -1
  553. package/src/plugins/builtin/jobs/model.ts +4 -15
  554. package/src/plugins/builtin/jobs/pane.tsx +7 -5
  555. package/src/plugins/builtin/jobs/share-bars.tsx +11 -5
  556. package/src/plugins/builtin/jodi/pane.tsx +14 -8
  557. package/src/plugins/builtin/layout-manager/index.tsx +1 -0
  558. package/src/plugins/builtin/macro-day/headless.ts +20 -7
  559. package/src/plugins/builtin/macro-day/index.tsx +3 -2
  560. package/src/plugins/builtin/macro-day/model.ts +29 -8
  561. package/src/plugins/builtin/macro-day/pane.tsx +46 -10
  562. package/src/plugins/builtin/macro-day/releases.ts +55 -13
  563. package/src/plugins/builtin/market-heatmap/data.ts +9 -2
  564. package/src/plugins/builtin/market-heatmap/evidence.ts +1 -1
  565. package/src/plugins/builtin/market-heatmap/headless.ts +9 -6
  566. package/src/plugins/builtin/market-heatmap/index.tsx +3 -3
  567. package/src/plugins/builtin/market-heatmap/model.ts +10 -7
  568. package/src/plugins/builtin/market-movers/client.ts +36 -21
  569. package/src/plugins/builtin/market-movers/headless.ts +8 -2
  570. package/src/plugins/builtin/market-movers/index.tsx +16 -10
  571. package/src/plugins/builtin/market-movers/screener.ts +33 -11
  572. package/src/plugins/builtin/market-movers/session-body.tsx +4 -2
  573. package/src/plugins/builtin/market-movers/session-table.ts +4 -2
  574. package/src/plugins/builtin/market-movers/table.ts +4 -1
  575. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  576. package/src/plugins/builtin/market-valuation/view.ts +5 -0
  577. package/src/plugins/builtin/members/headless.ts +6 -5
  578. package/src/plugins/builtin/members/pane.tsx +11 -4
  579. package/src/plugins/builtin/mna/client.ts +1 -0
  580. package/src/plugins/builtin/mna/detail.tsx +20 -32
  581. package/src/plugins/builtin/mna/distress/detail.tsx +16 -4
  582. package/src/plugins/builtin/mna/distress/model.ts +3 -2
  583. package/src/plugins/builtin/mna/distress/pane.tsx +7 -4
  584. package/src/plugins/builtin/mna/headless.ts +6 -13
  585. package/src/plugins/builtin/mna/model.ts +93 -15
  586. package/src/plugins/builtin/mna/pane.tsx +5 -4
  587. package/src/plugins/builtin/news/headless.ts +12 -6
  588. package/src/plugins/builtin/news/index.tsx +10 -1
  589. package/src/plugins/builtin/news/wire/index.tsx +3 -2
  590. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +26 -10
  591. package/src/plugins/builtin/news/wire/news/query-presets.ts +6 -2
  592. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -3
  593. package/src/plugins/builtin/notes/cli.ts +50 -15
  594. package/src/plugins/builtin/notes/export.ts +17 -6
  595. package/src/plugins/builtin/notes/files.ts +16 -5
  596. package/src/plugins/builtin/openfda/pane.tsx +1 -1
  597. package/src/plugins/builtin/options/carry.ts +2 -2
  598. package/src/plugins/builtin/options/enrichment-model.ts +10 -9
  599. package/src/plugins/builtin/options/index.tsx +1 -1
  600. package/src/plugins/builtin/options/model-spot.ts +70 -4
  601. package/src/plugins/builtin/options/view.tsx +51 -23
  602. package/src/plugins/builtin/options-calculator/headless.ts +46 -7
  603. package/src/plugins/builtin/options-calculator/pane.tsx +3 -2
  604. package/src/plugins/builtin/options-positioning/headless.ts +11 -3
  605. package/src/plugins/builtin/options-positioning/model.ts +24 -0
  606. package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
  607. package/src/plugins/builtin/options-scenario/client.ts +142 -29
  608. package/src/plugins/builtin/options-scenario/collar.ts +8 -6
  609. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +127 -0
  610. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +165 -0
  611. package/src/plugins/builtin/options-scenario/csp-screen.ts +258 -0
  612. package/src/plugins/builtin/options-scenario/csp.ts +101 -21
  613. package/src/plugins/builtin/options-scenario/headless.ts +49 -21
  614. package/src/plugins/builtin/options-scenario/hedge.ts +20 -14
  615. package/src/plugins/builtin/options-scenario/index.tsx +28 -3
  616. package/src/plugins/builtin/options-scenario/model.ts +13 -0
  617. package/src/plugins/builtin/options-scenario/pane.tsx +22 -10
  618. package/src/plugins/builtin/perps/equity-row.tsx +11 -3
  619. package/src/plugins/builtin/perps/freshness.ts +8 -5
  620. package/src/plugins/builtin/perps/headless.ts +3 -1
  621. package/src/plugins/builtin/perps/list-perpetuals.ts +25 -4
  622. package/src/plugins/builtin/perps/live-rows.ts +161 -0
  623. package/src/plugins/builtin/perps/markets-section.tsx +154 -0
  624. package/src/plugins/builtin/perps/pane.tsx +36 -17
  625. package/src/plugins/builtin/perps/venue-markets.ts +96 -0
  626. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -1
  627. package/src/plugins/builtin/plugin-marketplace/pane.tsx +145 -60
  628. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +78 -27
  629. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +158 -141
  630. package/src/plugins/builtin/portfolio-list/cli/render.ts +49 -20
  631. package/src/plugins/builtin/portfolio-list/cli/shared.ts +3 -3
  632. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +29 -24
  633. package/src/plugins/builtin/portfolio-list/headless.ts +6 -3
  634. package/src/plugins/builtin/portfolio-list/income.ts +13 -2
  635. package/src/plugins/builtin/portfolio-list/index.tsx +9 -2
  636. package/src/plugins/builtin/portfolio-list/pane/index.tsx +21 -8
  637. package/src/plugins/builtin/portfolio-list/pane/purchase-date-fx.ts +46 -0
  638. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +16 -9
  639. package/src/plugins/builtin/portfolio-list/summary/totals.ts +2 -15
  640. package/src/plugins/builtin/power/client.ts +6 -3
  641. package/src/plugins/builtin/power/evidence.ts +22 -7
  642. package/src/plugins/builtin/power/gas.tsx +5 -1
  643. package/src/plugins/builtin/power/pane.tsx +44 -32
  644. package/src/plugins/builtin/power/prices.tsx +10 -3
  645. package/src/plugins/builtin/research/analyst-headless.ts +25 -8
  646. package/src/plugins/builtin/research/analyst-model.ts +68 -3
  647. package/src/plugins/builtin/research/corporate-actions-pane.tsx +63 -14
  648. package/src/plugins/builtin/research/event-model.ts +3 -1
  649. package/src/plugins/builtin/research/event-sources.ts +12 -4
  650. package/src/plugins/builtin/research/index.tsx +2 -1
  651. package/src/plugins/builtin/research/relative-valuation-basis.ts +58 -0
  652. package/src/plugins/builtin/research/relative-valuation-columns.ts +31 -2
  653. package/src/plugins/builtin/research/relative-valuation-currency.ts +12 -5
  654. package/src/plugins/builtin/research/relative-valuation-headless.ts +84 -26
  655. package/src/plugins/builtin/research/relative-valuation-model.ts +88 -19
  656. package/src/plugins/builtin/research/relative-valuation-pane.tsx +76 -9
  657. package/src/plugins/builtin/research/relative-valuation-peers.ts +1 -1
  658. package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
  659. package/src/plugins/builtin/reverse-dcf/model.ts +10 -5
  660. package/src/plugins/builtin/risk-factors/index.tsx +2 -2
  661. package/src/plugins/builtin/scanner/lock-note.ts +1 -1
  662. package/src/plugins/builtin/seasonality/client.ts +2 -1
  663. package/src/plugins/builtin/sec/forms.ts +13 -0
  664. package/src/plugins/builtin/sec/fund-abroad.ts +32 -0
  665. package/src/plugins/builtin/sec/fund-facts.ts +68 -8
  666. package/src/plugins/builtin/sec/headless.ts +85 -8
  667. package/src/plugins/builtin/sec/index.tsx +58 -12
  668. package/src/plugins/builtin/sectors/client.ts +6 -4
  669. package/src/plugins/builtin/sectors/sector-model.ts +11 -15
  670. package/src/plugins/builtin/series-inventory.ts +2 -0
  671. package/src/plugins/builtin/shared/broker-reconnect.ts +107 -22
  672. package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
  673. package/src/plugins/builtin/shared/cloud-upgrade.ts +34 -6
  674. package/src/plugins/builtin/shared/earnings-calendar.ts +375 -0
  675. package/src/plugins/builtin/{earnings/estimate-basis.ts → shared/earnings-estimate-basis.ts} +1 -1
  676. package/src/plugins/builtin/shared/earnings-report.ts +3 -3
  677. package/src/plugins/builtin/shared/fx-rate-freshness.ts +23 -1
  678. package/src/plugins/builtin/shared/job-posting.ts +129 -0
  679. package/src/plugins/builtin/shared/pro-lock-note.ts +3 -3
  680. package/src/plugins/builtin/shared/report-freshness.ts +3 -0
  681. package/src/plugins/builtin/shared/research-trading.tsx +23 -0
  682. package/src/plugins/builtin/shared/series-cache.ts +2 -0
  683. package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
  684. package/src/plugins/builtin/shared/ticker-surface.ts +2 -1
  685. package/src/plugins/builtin/shared/volatility/options.ts +2 -1
  686. package/src/plugins/builtin/short-interest/client.ts +2 -0
  687. package/src/plugins/builtin/short-interest/headless.ts +41 -12
  688. package/src/plugins/builtin/short-interest/index.tsx +23 -1
  689. package/src/plugins/builtin/short-interest/model.ts +70 -19
  690. package/src/plugins/builtin/short-interest/pane.tsx +19 -2
  691. package/src/plugins/builtin/short-interest/publications-client.ts +82 -0
  692. package/src/plugins/builtin/short-interest/publications-headless.ts +29 -0
  693. package/src/plugins/builtin/short-interest/publications-model.ts +50 -0
  694. package/src/plugins/builtin/short-interest/publications-pane.tsx +66 -0
  695. package/src/plugins/builtin/short-interest/schedule.ts +35 -2
  696. package/src/plugins/builtin/short-interest/surface.tsx +11 -4
  697. package/src/plugins/builtin/short-interest/types.ts +9 -0
  698. package/src/plugins/builtin/short-interest/watch-client.ts +14 -8
  699. package/src/plugins/builtin/short-interest/watch-model.ts +12 -25
  700. package/src/plugins/builtin/short-interest/watch-pane.tsx +5 -1
  701. package/src/plugins/builtin/social-mentions/client.ts +2 -1
  702. package/src/plugins/builtin/supply-chain/flow.tsx +33 -9
  703. package/src/plugins/builtin/supply-chain/model.ts +51 -3
  704. package/src/plugins/builtin/supply-chain/pane.tsx +78 -39
  705. package/src/plugins/builtin/supply-chain/row-evidence.tsx +1 -1
  706. package/src/plugins/builtin/themes/headless.ts +8 -5
  707. package/src/plugins/builtin/themes/model.ts +14 -4
  708. package/src/plugins/builtin/themes/pane.tsx +24 -5
  709. package/src/plugins/builtin/thirteenf/headless.ts +5 -5
  710. package/src/plugins/builtin/thirteenf/signals.ts +3 -1
  711. package/src/plugins/builtin/ticker-detail/adr-pair/headless.ts +123 -0
  712. package/src/plugins/builtin/ticker-detail/adr-pair/model.ts +132 -0
  713. package/src/plugins/builtin/ticker-detail/adr-pair/tab.tsx +87 -0
  714. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +40 -26
  715. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +8 -4
  716. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +67 -3
  717. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +112 -5
  718. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +17 -2
  719. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +21 -7
  720. package/src/plugins/builtin/ticker-detail/financials/model.ts +36 -1
  721. package/src/plugins/builtin/ticker-detail/financials/offerings.ts +56 -0
  722. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +62 -13
  723. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +30 -4
  724. package/src/plugins/builtin/ticker-detail/headless-schema.ts +14 -2
  725. package/src/plugins/builtin/ticker-detail/headless.ts +148 -35
  726. package/src/plugins/builtin/ticker-detail/index.tsx +37 -5
  727. package/src/plugins/builtin/ticker-detail/overview/model.ts +65 -10
  728. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +9 -1
  729. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +68 -11
  730. package/src/plugins/builtin/ticker-detail/pane.tsx +11 -0
  731. package/src/plugins/builtin/ticker-detail/quote-footer.ts +8 -3
  732. package/src/plugins/builtin/ticker-detail/research-tabs.ts +11 -0
  733. package/src/plugins/builtin/vol-surface/client.ts +28 -7
  734. package/src/plugins/builtin/vol-surface/headless.ts +43 -15
  735. package/src/plugins/builtin/vol-surface/model.ts +34 -12
  736. package/src/plugins/builtin/vol-surface/pane.tsx +33 -13
  737. package/src/plugins/builtin/vol-surface/stored.ts +34 -3
  738. package/src/plugins/builtin/vol-surface/tables.ts +14 -0
  739. package/src/plugins/builtin/volatility/model.ts +5 -10
  740. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +4 -3
  741. package/src/plugins/builtin/world-venue-map/basemap.ts +3 -1
  742. package/src/plugins/builtin/world-venue-map/chokepoint-chart.ts +40 -0
  743. package/src/plugins/builtin/world-venue-map/evidence.ts +1 -1
  744. package/src/plugins/builtin/world-venue-map/geo-series.ts +90 -11
  745. package/src/plugins/builtin/world-venue-map/headless.ts +1 -1
  746. package/src/plugins/builtin/world-venue-map/index.ts +3 -26
  747. package/src/plugins/builtin/world-venue-map/layers.ts +1 -1
  748. package/src/plugins/builtin/world-venue-map/map.tsx +2 -1
  749. package/src/plugins/builtin/world-venue-map/pane.tsx +4 -2
  750. package/src/plugins/builtin/world-venue-map/venue-header.tsx +15 -4
  751. package/src/plugins/builtin/yield-curve/client.ts +25 -0
  752. package/src/plugins/builtin/yield-curve/curves.ts +7 -4
  753. package/src/plugins/builtin/yield-curve/headless.ts +38 -4
  754. package/src/plugins/builtin/yield-curve/history.ts +4 -3
  755. package/src/plugins/builtin/yield-curve/index.tsx +12 -6
  756. package/src/plugins/builtin/yield-curve/swaps-model.ts +93 -0
  757. package/src/plugins/builtin/yield-curve/swaps.tsx +99 -0
  758. package/src/plugins/bundle.ts +1 -1
  759. package/src/plugins/catalog-browser.ts +2 -1
  760. package/src/plugins/compat.ts +3 -3
  761. package/src/plugins/dependencies.ts +75 -14
  762. package/src/plugins/extracted-plugins.ts +132 -34
  763. package/src/plugins/host-link.ts +149 -34
  764. package/src/plugins/installer.ts +35 -10
  765. package/src/plugins/loader.ts +206 -38
  766. package/src/plugins/manager-node.ts +2 -2
  767. package/src/plugins/marketplace/deactivation.ts +22 -0
  768. package/src/plugins/marketplace/model.ts +2 -2
  769. package/src/plugins/marketplace/packs.ts +1 -1
  770. package/src/plugins/ownership.ts +1 -1
  771. package/src/plugins/peer-retry.ts +155 -0
  772. package/src/plugins/registry/contributions.ts +3 -1
  773. package/src/plugins/registry/index.ts +4 -3
  774. package/src/plugins/registry/plugin-state.ts +3 -0
  775. package/src/plugins/seed-notice.ts +20 -0
  776. package/src/plugins/shortcut-argument.ts +39 -0
  777. package/src/portfolio/allocation.ts +1 -1
  778. package/src/portfolio/collection-name.ts +31 -0
  779. package/src/portfolio/column-values.ts +10 -8
  780. package/src/portfolio/cost-fx.ts +296 -19
  781. package/src/portfolio/manual-lots.ts +75 -0
  782. package/src/portfolio/mutations.ts +60 -22
  783. package/src/portfolio/suffixed-listings.ts +209 -0
  784. package/src/portfolio/totals-currency.ts +24 -0
  785. package/src/public/__snapshots__/public-api.test.ts.snap +14 -0
  786. package/src/public/time-series.ts +11 -8
  787. package/src/remote/schema.ts +1 -1
  788. package/src/renderers/browser/config-host.ts +3 -0
  789. package/src/renderers/browser/main.tsx +23 -3
  790. package/src/renderers/dom/boot-frame-page.ts +187 -0
  791. package/src/renderers/dom/boot-frame.ts +237 -0
  792. package/src/renderers/dom/build-assets.ts +46 -8
  793. package/src/renderers/dom/desktop/controls.tsx +1 -1
  794. package/src/renderers/dom/desktop/query-bar.tsx +2 -0
  795. package/src/renderers/dom/dom-ui-host.tsx +4 -29
  796. package/src/renderers/dom/host/ascii-text.ts +16 -2
  797. package/src/renderers/dom/host/box.tsx +43 -2
  798. package/src/renderers/dom/host/chart-surface.tsx +2 -0
  799. package/src/renderers/dom/host/hls.chunk.ts +3 -0
  800. package/src/renderers/dom/host/image-surface.tsx +49 -0
  801. package/src/renderers/dom/host/media-surface.tsx +24 -18
  802. package/src/renderers/dom/host/mouse.ts +30 -0
  803. package/src/renderers/dom/host/size-observer.ts +1 -1
  804. package/src/renderers/dom/host/style.ts +1 -1
  805. package/src/renderers/dom/host/tabs.tsx +16 -5
  806. package/src/renderers/dom/host/top-surface.ts +127 -0
  807. package/src/renderers/dom/styles.css +115 -0
  808. package/src/renderers/dom/test-utils.tsx +2 -1
  809. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -3
  810. package/src/renderers/electrobun/bun/core-capabilities.ts +9 -1
  811. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -0
  812. package/src/renderers/electrobun/bun/desktop/backend.ts +56 -3
  813. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +1 -1
  814. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -3
  815. package/src/renderers/electrobun/bun/desktop/initialization.ts +81 -18
  816. package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
  817. package/src/renderers/electrobun/bun/external-plugins.ts +29 -7
  818. package/src/renderers/electrobun/bun/index.ts +11 -1
  819. package/src/renderers/electrobun/bun/window/boot-frame.ts +67 -0
  820. package/src/renderers/electrobun/bun/window/create.ts +4 -1
  821. package/src/renderers/electrobun/shared/boot-frame.ts +7 -0
  822. package/src/renderers/electrobun/shared/protocol.ts +6 -1
  823. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  824. package/src/renderers/electrobun/view/config-host.ts +3 -0
  825. package/src/renderers/electrobun/view/fatal-screen.tsx +1 -1
  826. package/src/renderers/electrobun/view/main.tsx +30 -4
  827. package/src/renderers/electrobun/view/news-source.ts +9 -7
  828. package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -2
  829. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  830. package/src/renderers/opentui/browser-open.ts +88 -0
  831. package/src/renderers/opentui/host.tsx +5 -14
  832. package/src/renderers/opentui/image/loader.ts +3 -2
  833. package/src/renderers/opentui/native-smoke.ts +10 -1
  834. package/src/renderers/opentui/pane-image/index.ts +2 -1
  835. package/src/renderers/opentui/render-lib.ts +253 -0
  836. package/src/renderers/opentui/render-timing-smoke.ts +1 -1
  837. package/src/renderers/opentui/start.tsx +8 -1
  838. package/src/renderers/share/layout-view.tsx +2 -2
  839. package/src/renderers/share/main.tsx +4 -4
  840. package/src/renderers/share/view.tsx +1 -1
  841. package/src/series/resolve.ts +22 -6
  842. package/src/series/standalone.ts +30 -2
  843. package/src/series/types.ts +8 -1
  844. package/src/shares/api.ts +4 -1
  845. package/src/shares/pane.ts +1 -1
  846. package/src/sources/gloomberb-cloud/fred-series.ts +46 -1
  847. package/src/sources/gloomberb-cloud/index.ts +39 -16
  848. package/src/sources/gloomberb-cloud/news.ts +39 -0
  849. package/src/sources/gloomberb-cloud/normalizers.ts +14 -1
  850. package/src/sources/gloomberb-cloud/perpetuals.ts +49 -1
  851. package/src/sources/listing-symbols.ts +37 -0
  852. package/src/sources/provider-errors.ts +16 -2
  853. package/src/sources/provider-router/financial-routes.ts +21 -3
  854. package/src/sources/provider-router/financials.ts +15 -2
  855. package/src/sources/provider-router/history.ts +30 -8
  856. package/src/sources/provider-router/index.ts +16 -0
  857. package/src/sources/provider-router/primary.ts +1 -1
  858. package/src/state/app/bootstrap.ts +23 -0
  859. package/src/state/hooks/inline-tickers.ts +30 -5
  860. package/src/sync/controller.ts +1 -1
  861. package/src/sync/core-contributors.ts +46 -14
  862. package/src/team/collections-sync.ts +2 -1
  863. package/src/team/collections.ts +178 -17
  864. package/src/telemetry/crash-reports.ts +21 -4
  865. package/src/telemetry/update-failures-node.ts +59 -0
  866. package/src/telemetry/update-failures.ts +307 -0
  867. package/src/test-support/builtin-panes.ts +1 -1
  868. package/src/theme/colors.ts +3 -30
  869. package/src/theme/ui-tokens.ts +37 -1
  870. package/src/tickers/company-name-listing.ts +215 -36
  871. package/src/tickers/company-name.ts +43 -0
  872. package/src/tickers/index-roots.ts +47 -0
  873. package/src/tickers/instrument-label.ts +5 -1
  874. package/src/tickers/listing-fate.ts +30 -0
  875. package/src/tickers/metadata.ts +10 -0
  876. package/src/tickers/perpetual-listing.ts +5 -2
  877. package/src/tickers/search/home-listing.ts +75 -0
  878. package/src/tickers/search/index.ts +76 -27
  879. package/src/tickers/search/ranking.ts +54 -4
  880. package/src/tickers/search/upsert.ts +29 -0
  881. package/src/tickers/terminal-codes.ts +416 -40
  882. package/src/time-series/bar-aggregation.ts +243 -0
  883. package/src/time-series/base-series.ts +307 -0
  884. package/src/time-series/chart-bounds.ts +349 -0
  885. package/src/time-series/chart-seed.ts +142 -0
  886. package/src/time-series/economic.ts +5 -0
  887. package/src/time-series/fundamentals.ts +18 -3
  888. package/src/time-series/history-acquisition.ts +499 -0
  889. package/src/time-series/leverage.ts +100 -0
  890. package/src/time-series/market-resolution.ts +4 -2
  891. package/src/time-series/market.ts +2 -1
  892. package/src/time-series/price-comparison.ts +175 -30
  893. package/src/time-series/range.ts +9 -1
  894. package/src/time-series/resolution.ts +174 -16
  895. package/src/time-series/resolve-context.ts +157 -0
  896. package/src/time-series/resolve.ts +126 -1577
  897. package/src/time-series/series-name.ts +38 -0
  898. package/src/time-series/series-presentation.ts +208 -0
  899. package/src/time-series/spec.ts +11 -6
  900. package/src/time-series/studies.ts +109 -12
  901. package/src/time-series/tail-reconcile.ts +121 -0
  902. package/src/time-series/types.ts +29 -2
  903. package/src/time-series/use-chart-resolution.ts +4 -5
  904. package/src/types/broker.ts +10 -0
  905. package/src/types/config.ts +14 -0
  906. package/src/types/data-provider.ts +20 -0
  907. package/src/types/exchange-rate.ts +6 -0
  908. package/src/types/financials.ts +104 -7
  909. package/src/types/headless.ts +83 -6
  910. package/src/types/instrument.ts +3 -0
  911. package/src/types/issuer-listings.ts +28 -0
  912. package/src/types/plugin.ts +65 -8
  913. package/src/types/price-history.ts +2 -0
  914. package/src/types/ticker.ts +13 -0
  915. package/src/types/trading.ts +59 -1
  916. package/src/ui/ascii-font.ts +13 -1
  917. package/src/ui/host.tsx +6 -1
  918. package/src/updater/command-names.ts +175 -0
  919. package/src/updater/failure.ts +163 -0
  920. package/src/updater/index.ts +97 -28
  921. package/src/utils/calendar-date.ts +32 -15
  922. package/src/utils/cash-runway.ts +241 -0
  923. package/src/utils/central-banks.ts +42 -0
  924. package/src/utils/cli-output.ts +259 -80
  925. package/src/utils/contract-month.ts +16 -0
  926. package/src/utils/crypto-pair.ts +61 -3
  927. package/src/utils/currency-pair.ts +26 -0
  928. package/src/utils/currency-units.ts +29 -0
  929. package/src/utils/datetime-format.ts +10 -0
  930. package/src/utils/debug-log.ts +1 -1
  931. package/src/utils/decimal-comma.ts +39 -6
  932. package/src/utils/exchange-announcements.ts +71 -0
  933. package/src/utils/exchange-rate-snapshot.ts +10 -0
  934. package/src/utils/financial-statements.ts +83 -4
  935. package/src/utils/format.ts +34 -16
  936. package/src/utils/fundamentals.ts +150 -34
  937. package/src/utils/fx-market-hours.ts +10 -1
  938. package/src/utils/latest-financial-period.ts +8 -0
  939. package/src/utils/linux-libc.ts +54 -0
  940. package/src/utils/market-capitalization.ts +111 -15
  941. package/src/utils/max-drawdown.ts +64 -0
  942. package/src/utils/network-failure.ts +83 -0
  943. package/src/utils/option-expiry.ts +139 -7
  944. package/src/utils/price-earnings.ts +11 -5
  945. package/src/utils/price-history.ts +18 -8
  946. package/src/utils/price-window.ts +131 -0
  947. package/src/utils/release-date.ts +47 -0
  948. package/src/utils/sec.ts +71 -0
  949. package/src/utils/throttled-fetch.ts +11 -4
  950. package/src/utils/trailing-eps.ts +67 -0
  951. package/src/utils/typed-text.ts +21 -0
  952. package/src/utils/utc-time.ts +24 -1
  953. package/src/version.ts +6 -0
  954. package/src/cli/pane-functions/screenshot.ts +0 -2122
@@ -0,0 +1,251 @@
1
+ import type { DepositaryPair, Fundamentals, Quote } from "../types/financials";
2
+ import { canonicalExchange, parsePublicTickerKey } from "../utils/exchanges";
3
+ import { formatNumber } from "../utils/format";
4
+ import { formatUtcDayTime } from "../utils/utc-time";
5
+ import { heldCurrencyUnit } from "../utils/currency-units";
6
+ import { regularSessionCloseOn } from "./market/freshness";
7
+ import { getRegularSessionDisplay, regularSessionDisplayDate } from "./market/status";
8
+
9
+ /**
10
+ * A depositary receipt beside its home listing: the ratio Gloom Cloud serves
11
+ * on both lines, the two prices, and the dollar rate they imply. For GGAL
12
+ * (1 ADR = 10 shares) at $37.91 and GGAL.BA at ARS 6,260, 6,260 × 10 / 37.91
13
+ * = 1,651 pesos a dollar: the rate paid by buying the shares in pesos and
14
+ * selling the receipt abroad, the CCL rate, against an official 1,529.
15
+ */
16
+
17
+ /** A served pair, when it is whole: a receipt, a positive ratio, at least one home line. */
18
+ function readDepositaryPair(value: unknown): DepositaryPair | null {
19
+ if (!value || typeof value !== "object") return null;
20
+ const { receipt, ratio, ordinary } = value as Partial<DepositaryPair>;
21
+ if (typeof receipt !== "string" || !receipt.trim()) return null;
22
+ if (typeof ratio !== "number" || !Number.isFinite(ratio) || ratio <= 0) return null;
23
+ const lines = Array.isArray(ordinary) ? ordinary.filter((line): line is string => typeof line === "string" && !!line.trim()) : [];
24
+ return lines.length > 0 ? { receipt: receipt.trim().toUpperCase(), ratio, ordinary: lines } : null;
25
+ }
26
+
27
+ type PairRecords = { quote?: Pick<Quote, "depositaryPair" | "isDepositaryReceipt"> | null; fundamentals?: Pick<Fundamentals, "depositaryPair" | "isDepositaryReceipt"> | null };
28
+
29
+ /** The pair a listing's records name: the quote's, else the fundamentals'. */
30
+ export function listingDepositaryPair(records: PairRecords | null | undefined): DepositaryPair | null {
31
+ return readDepositaryPair(records?.quote?.depositaryPair) ?? readDepositaryPair(records?.fundamentals?.depositaryPair);
32
+ }
33
+
34
+ /** Whether the listing is the pair's receipt rather than one of its home lines. */
35
+ export function isReceiptSide(records: PairRecords | null | undefined): boolean {
36
+ return (records?.quote?.isDepositaryReceipt ?? records?.fundamentals?.isDepositaryReceipt) === true;
37
+ }
38
+
39
+ /** `10`, `0.25`: a ratio without float noise. */
40
+ export function ratioCount(ratio: number): string {
41
+ return Number.isInteger(ratio) ? String(ratio) : String(Number(ratio.toPrecision(6)));
42
+ }
43
+
44
+ /** What a home line says of its receipt: `1 GGAL ADR = 10 of these shares`. */
45
+ function homeLineRatioText(pair: DepositaryPair): string {
46
+ return `1 ${pair.receipt} ADR = ${ratioCount(pair.ratio)} of these share${pair.ratio === 1 ? "" : "s"}`;
47
+ }
48
+
49
+ /**
50
+ * The ratio as a listing's report states it, from its side of the pair:
51
+ * `1 ADR = 10 ordinary shares of GGAL.BA` on the receipt, `1 GGAL ADR = 10
52
+ * of these shares` on a home line.
53
+ */
54
+ export function depositaryPairText(pair: DepositaryPair, receiptSide: boolean): string {
55
+ if (!receiptSide) return homeLineRatioText(pair);
56
+ return `1 ADR = ${ratioCount(pair.ratio)} ordinary share${pair.ratio === 1 ? "" : "s"} of ${pair.ordinary.join(" / ")}`;
57
+ }
58
+
59
+ /** A price as the comparison reads it, with when it was observed. */
60
+ export interface PairPriceInput {
61
+ symbol: string;
62
+ price: number;
63
+ currency: string;
64
+ /** When the price was set: the last trade, or the close of a session that ended. */
65
+ observedAt: number | null;
66
+ /** The listing's regular session is trading. */
67
+ open: boolean;
68
+ }
69
+
70
+ /**
71
+ * A listing's regular-session price (an after-hours print never moves it),
72
+ * observed at its last trade while the session runs, and once it is over at
73
+ * that trade or the close, whichever came first.
74
+ */
75
+ export function pairPriceInput(symbol: string, quote: Quote | null | undefined): PairPriceInput | null {
76
+ const quoted = getRegularSessionDisplay(quote)?.price;
77
+ if (!quote || quoted == null || !Number.isFinite(quoted) || quoted <= 0 || !quote.currency) return null;
78
+ // A price in a minor unit (pence, GBp) is read in its currency, as the dollar rate is served for it.
79
+ const unit = heldCurrencyUnit(quote.currency);
80
+ if (!unit.currency) return null;
81
+ const price = quoted / unit.divisor;
82
+ const open = quote.marketState === "REGULAR";
83
+ const lastUpdated = Number.isFinite(quote.lastUpdated) && quote.lastUpdated > 0 ? quote.lastUpdated : null;
84
+ const sessionDate = open ? null : regularSessionDisplayDate(quote);
85
+ const close = sessionDate ? regularSessionCloseOn(quote.listingExchangeName || quote.exchangeName, sessionDate) : null;
86
+ const observedAt = close != null && (lastUpdated == null || lastUpdated > close) ? close : lastUpdated;
87
+ return { symbol, price, currency: unit.currency, observedAt, open };
88
+ }
89
+
90
+ /** The home currency's dollar rate (units of it per USD) and when it was read. */
91
+ export interface PairSpotInput {
92
+ rate: number;
93
+ observedAt: number | null;
94
+ }
95
+
96
+ /** The spot from a USD value of one unit of the currency, as exchange rates are served. */
97
+ export function spotFromUsdValue(usdPerUnit: number | null | undefined, observedAt?: number | null): PairSpotInput | null {
98
+ if (usdPerUnit == null || !Number.isFinite(usdPerUnit) || usdPerUnit <= 0) return null;
99
+ return { rate: 1 / usdPerUnit, observedAt: observedAt ?? null };
100
+ }
101
+
102
+ export interface DepositaryPairComparison {
103
+ ratio: number;
104
+ receipt: PairPriceInput;
105
+ home: PairPriceInput;
106
+ /** Units of the home currency per dollar the two prices imply: home × ratio / receipt. */
107
+ impliedFx: number;
108
+ spot: PairSpotInput | null;
109
+ /** The receipt over the home shares it holds at spot, in percent: spot / implied - 1. */
110
+ premiumPercent: number | null;
111
+ /** Said when the two prices were not set together; null when they were. */
112
+ timingNote: string | null;
113
+ }
114
+
115
+ /** Inputs this far apart are from different times. */
116
+ const SAME_TIME_MS = 30 * 60_000;
117
+
118
+ /** `14 h 30 min`, `2 days 6 h`. */
119
+ function gapText(ms: number): string {
120
+ const minutes = Math.round(ms / 60_000);
121
+ const days = Math.floor(minutes / 1440);
122
+ const hours = Math.floor((minutes % 1440) / 60);
123
+ const rest = minutes % 60;
124
+ if (days > 0) return [`${days} day${days === 1 ? "" : "s"}`, hours ? `${hours} h` : ""].filter(Boolean).join(" ");
125
+ return [hours ? `${hours} h` : "", rest || !hours ? `${rest} min` : ""].filter(Boolean).join(" ");
126
+ }
127
+
128
+ /** `Fri 23 Oct 20:00 UTC`. */
129
+ export function pairTimeText(time: number, now = Date.now()): string {
130
+ return `${formatUtcDayTime({ time, dateOnly: false }, now)} UTC`;
131
+ }
132
+
133
+ function timingNote(receipt: PairPriceInput, home: PairPriceInput, now: number): string | null {
134
+ if (receipt.open && home.open) return null;
135
+ if (receipt.observedAt == null || home.observedAt == null) return null;
136
+ const gap = Math.abs(receipt.observedAt - home.observedAt);
137
+ if (receipt.open !== home.open) {
138
+ const [closed, trading] = receipt.open ? [home, receipt] : [receipt, home];
139
+ return `${closed.symbol} is closed, its price from ${pairTimeText(closed.observedAt!, now)}, while ${trading.symbol} trades: the prices are ${gapText(gap)} apart.`;
140
+ }
141
+ if (gap <= SAME_TIME_MS) return null;
142
+ const [first, second] = home.observedAt <= receipt.observedAt ? [home, receipt] : [receipt, home];
143
+ return `Both markets are closed: ${first.symbol} from ${pairTimeText(first.observedAt!, now)}, ${second.symbol} from ${pairTimeText(second.observedAt!, now)}, ${gapText(gap)} apart.`;
144
+ }
145
+
146
+ /** The comparison, or null when the receipt is not priced in dollars or a price is missing. */
147
+ export function compareDepositaryPair(
148
+ ratio: number,
149
+ receipt: PairPriceInput | null,
150
+ home: PairPriceInput | null,
151
+ spot: PairSpotInput | null,
152
+ now = Date.now(),
153
+ ): DepositaryPairComparison | null {
154
+ if (!receipt || !home || !(ratio > 0) || receipt.currency !== "USD") return null;
155
+ const impliedFx = (home.price * ratio) / receipt.price;
156
+ if (!Number.isFinite(impliedFx) || impliedFx <= 0) return null;
157
+ const premiumPercent = spot ? (spot.rate / impliedFx - 1) * 100 : null;
158
+ return { ratio, receipt, home, impliedFx, spot, premiumPercent, timingNote: timingNote(receipt, home, now) };
159
+ }
160
+
161
+ /** `1,651.28`, `28.4942`, `0.890625`: a dollar rate to its useful digits. */
162
+ export function formatPairRate(rate: number): string {
163
+ const size = Math.abs(rate);
164
+ return formatNumber(rate, size >= 100 ? 2 : size >= 1 ? 4 : 6);
165
+ }
166
+
167
+ /** `+14.3% premium`, `-7.4% discount`. */
168
+ export function premiumText(percent: number): string {
169
+ const rounded = Math.round(percent * 10) / 10;
170
+ if (rounded === 0) return "0.0% at par";
171
+ return `${rounded > 0 ? "+" : ""}${rounded.toFixed(1)}% ${rounded > 0 ? "premium" : "discount"}`;
172
+ }
173
+
174
+ /** `USD/ARS`: the home currency's dollar pair, as `fx` names it. */
175
+ export function dollarPairLabel(currency: string): string {
176
+ return `USD/${currency}`;
177
+ }
178
+
179
+ /** What the served dollar rate of a currency is, where it is not the one market rate: the peso's official one. */
180
+ export function spotRateKind(currency: string): string | null {
181
+ return currency.trim().toUpperCase() === "ARS" ? "official wholesale" : null;
182
+ }
183
+
184
+ /**
185
+ * Buenos Aires lists a share's dollar lines under its peso symbol with a
186
+ * letter added: D settles in dollars at home (the MEP line), C abroad (the CCL
187
+ * line). Null for a listing elsewhere.
188
+ */
189
+ export function bymaDollarLineKeys(homeKey: string): { mep: string; ccl: string } | null {
190
+ const { symbol, exchange } = parsePublicTickerKey(homeKey);
191
+ const suffix = /^(.+)\.BA$/i.exec(homeKey.trim());
192
+ if (suffix) return { mep: `${suffix[1]!.toUpperCase()}D.BA`, ccl: `${suffix[1]!.toUpperCase()}C.BA` };
193
+ if (canonicalExchange(exchange) !== "BYMA") return null;
194
+ return { mep: `${symbol.toUpperCase()}D.BA`, ccl: `${symbol.toUpperCase()}C.BA` };
195
+ }
196
+
197
+ /** The receipt the peso's implied dollar rates are read through: Galicia, the most traded Argentine pair. */
198
+ export const PESO_REFERENCE_RECEIPT = "GGAL";
199
+
200
+ type ImpliedDollarRateKind = "ccl-receipt" | "ccl-line" | "mep-line";
201
+
202
+ /** A dollar rate a pair of prices implies, with the prices it came from. */
203
+ export interface ImpliedDollarRate {
204
+ kind: ImpliedDollarRateKind;
205
+ /** `CCL via GGAL ADR`. */
206
+ label: string;
207
+ /** Pesos per dollar. */
208
+ rate: number;
209
+ /** `6,260 × 10 / 37.91`. */
210
+ formula: string;
211
+ inputs: PairPriceInput[];
212
+ }
213
+
214
+ const PESO_RATE_LABELS: Record<ImpliedDollarRateKind, (symbol: string) => string> = {
215
+ "ccl-receipt": (receipt) => `CCL via ${receipt} ADR`,
216
+ "ccl-line": (line) => `CCL via ${line}`,
217
+ "mep-line": (line) => `MEP via ${line}`,
218
+ };
219
+
220
+ /**
221
+ * The peso's market dollar rates from one Argentine pair: the CCL rate its
222
+ * receipt implies (home × ratio / receipt) and the one its Buenos Aires CCL
223
+ * line implies (home / C line), and the MEP rate of its D line (home / D
224
+ * line). A rate whose prices are missing is left out, as is the receipt's
225
+ * without a ratio on record.
226
+ */
227
+ export function pesoImpliedRates(
228
+ receiptRatio: number | null,
229
+ inputs: { home: PairPriceInput | null; receipt: PairPriceInput | null; mep: PairPriceInput | null; ccl: PairPriceInput | null },
230
+ ): ImpliedDollarRate[] {
231
+ const { home } = inputs;
232
+ if (!home || home.currency !== "ARS") return [];
233
+ const rates: ImpliedDollarRate[] = [];
234
+ const price = (input: PairPriceInput) => formatNumber(input.price, input.price >= 100 ? 2 : 4).replace(/\.?0+$/, "");
235
+ const add = (kind: ImpliedDollarRateKind, dollar: PairPriceInput | null, ratio: number | null) => {
236
+ if (!dollar || dollar.currency !== "USD" || ratio == null || !(ratio > 0)) return;
237
+ const rate = (home.price * ratio) / dollar.price;
238
+ if (!Number.isFinite(rate) || rate <= 0) return;
239
+ rates.push({
240
+ kind,
241
+ label: PESO_RATE_LABELS[kind](dollar.symbol),
242
+ rate,
243
+ formula: `${price(home)}${ratio === 1 ? "" : ` × ${ratioCount(ratio)}`} / ${price(dollar)}`,
244
+ inputs: [home, dollar],
245
+ });
246
+ };
247
+ add("ccl-receipt", inputs.receipt, receiptRatio);
248
+ add("ccl-line", inputs.ccl, 1);
249
+ add("mep-line", inputs.mep, 1);
250
+ return rates;
251
+ }
@@ -37,7 +37,7 @@ export interface FuturesContract {
37
37
  * is this future wherever a symbol is typed. Codes a security also uses (NG
38
38
  * is NovaGold, CL Colgate, ZM Zoom, GC several venues' listings) leave it
39
39
  * unset: the security keeps the code and the future is its `=F` symbol.
40
- * Checked with `gloomberb quote <code>` signed out on 2026-10-11: each of
40
+ * Checked with `gloom quote <code>` signed out on 2026-10-11: each of
41
41
  * these was "Not a ticker", or for ZT and ZF a quote last updated in 2023 and
42
42
  * 2021 that search no longer lists. The sector, index and energy rows were
43
43
  * checked the same way on 2026-10-14: XAR is an ETF, XAU Goldmoney, SXI
@@ -4,10 +4,12 @@ import { classifyInstrumentType } from "../tickers/instrument-kind";
4
4
  import type { Quote } from "../types/financials";
5
5
  import { parsePublicTickerKey } from "../utils/exchanges";
6
6
  import { logFailure } from "../utils/log-failure";
7
- import { futuresBoardContract, type FuturesContract } from "./futures-board";
7
+ import { futuresBoardContract, futuresContractByCode, type FuturesContract } from "./futures-board";
8
8
  import { formatMarketPrice, formatMarketPriceWithCurrency, quoteFormatOptions, withCurrencyMinorDigits } from "./market/format";
9
+ import { previousPublishedSession } from "./market/freshness";
9
10
  import { minorUnitSign } from "../utils/currency-units";
10
11
  import { contractPriceUnit } from "../time-series/price-units";
12
+ import { deliveryMonthLabel } from "../utils/contract-month";
11
13
 
12
14
  /**
13
15
  * What a futures symbol trades, as `ticker`, `quote` and the Overview tab read
@@ -85,6 +87,11 @@ export function futuresContractLines(terms: FuturesContractTermsPayload, symbol:
85
87
  const lines: FuturesContractLine[] = [];
86
88
  if (terms.contract && terms.contract !== contractCode(symbol)) lines.push({ key: "contract", label: "Contract", value: terms.contract });
87
89
  if (terms.spec) lines.push(...futuresSpecLines(terms.spec));
90
+ else {
91
+ // A root the server sends no terms for still says what its price counts: Dutch TTF gas is EUR / MWh.
92
+ const unit = futuresContractByCode(terms.root)?.unit;
93
+ if (unit) lines.push({ key: "unit", label: "Unit", value: unit });
94
+ }
88
95
  if (terms.firstNotice) {
89
96
  lines.push(terms.inDelivery
90
97
  ? { key: "firstNotice", label: "First Notice", value: `${terms.firstNotice} (passed)`, warning: true }
@@ -108,48 +115,119 @@ export function fitFuturesContractLines(lines: readonly FuturesContractLine[], m
108
115
  * Without a quote currency the price is a bare number, never a guessed `$`: a
109
116
  * grain settles in cents. A contract without a confirmed settlement reads
110
117
  * `--`: never an older settlement, and never the last trade.
118
+ *
119
+ * `movesFrom` is the session the Change line beside it is measured from
120
+ * (futuresChangeBasis). The settlement is confirmed overnight, after the
121
+ * quote has moved on to the next session, so for those hours the terms carry
122
+ * the session before the one the move is from: that settlement would sit
123
+ * beside a move from another session's close. It reads `--` and says which
124
+ * session is not confirmed yet. A settlement of that session or a later one
125
+ * is shown: the same session, or the one the move itself belongs to.
111
126
  */
112
127
  export function futuresSettleText(
113
128
  terms: Pick<FuturesContractTermsPayload, "settlement">,
114
129
  quote: Pick<Quote, "currency" | "instrumentType" | "priceBasis" | "providerPriceDivisor"> | null | undefined,
130
+ movesFrom?: string | null,
115
131
  ): string {
116
132
  if (!terms.settlement) return FUTURES_MISSING;
133
+ if (movesFrom && terms.settlement.date < movesFrom) return `${FUTURES_MISSING} (${movesFrom} not confirmed yet)`;
117
134
  const currency = quote?.currency;
118
135
  const options = withCurrencyMinorDigits(quoteFormatOptions(quote, "FUTURE"), currency);
119
136
  const price = currency ? formatMarketPriceWithCurrency(terms.settlement.price, currency, options) : formatMarketPrice(terms.settlement.price, options);
120
137
  return `${price} (${terms.settlement.date})`;
121
138
  }
122
139
 
123
- const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
140
+ /** The quantities a price is per, as traders abbreviate them: 467.25¢/bu for corn, $99.20/bbl for crude. */
141
+ const PER_ABBREVIATIONS: Readonly<Record<string, string>> = {
142
+ bushel: "bu", bushels: "bu", pound: "lb", pounds: "lb", barrel: "bbl", barrels: "bbl", gallon: "gal", gallons: "gal",
143
+ };
144
+
145
+ type PricedTerms = Pick<FuturesContractTermsPayload, "spec"> & Partial<Pick<FuturesContractTermsPayload, "root">>;
124
146
 
125
- /** The quantities a cents price is per, as traders abbreviate them: 467.25¢/bu for corn. */
126
- const PER_ABBREVIATIONS: Readonly<Record<string, string>> = { bushel: "bu", bushels: "bu", pound: "lb", pounds: "lb" };
147
+ /** What a root's price counts: the exchange's unit from the terms, else the catalog's (Dutch TTF gas has none from the server). */
148
+ function termsPriceUnit(terms: PricedTerms | null | undefined): string | null {
149
+ return futuresPriceUnit(futuresContractByCode(terms?.root), terms);
150
+ }
127
151
 
128
152
  /**
129
- * What a price in a minor unit is per, to follow its sign: "/lb" for coffee at
130
- * 314.95¢/lb, "/bu" for corn, from the contract's unit ("US cents / lb").
131
- * Empty for a price in a major currency, which reads as it always has, and
132
- * when the terms name no unit in the quote's own currency.
153
+ * What a futures price is per, to follow it: "/lb" for coffee at 314.95¢/lb
154
+ * and "/bu" for corn, where a cents sign alone says nothing of the quantity;
155
+ * and for every energy contract, "/MWh" for Dutch TTF gas at €90.30/MWh,
156
+ * "/MMBtu" for natural gas, "/bbl" for crude and "/gal" for gasoline and
157
+ * heating oil, whose price means little without it. Read from the contract's
158
+ * unit, the catalog's where the server sends none. Empty for any other price
159
+ * in a major currency, which reads as it always has, and when the unit is not
160
+ * in the quote's own currency.
133
161
  */
134
- export function minorUnitPricePer(
135
- terms: Pick<FuturesContractTermsPayload, "spec"> | null | undefined,
136
- currency: string | null | undefined,
137
- ): string {
138
- if (!minorUnitSign(currency)) return "";
139
- const per = contractPriceUnit(terms?.spec?.unit, currency)?.split("/")[1]?.trim();
162
+ export function futuresPricePer(terms: PricedTerms | null | undefined, currency: string | null | undefined): string {
163
+ if (!minorUnitSign(currency) && futuresContractByCode(terms?.root)?.sector !== "energy") return "";
164
+ const per = contractPriceUnit(termsPriceUnit(terms), currency)?.split("/")[1]?.trim();
140
165
  return per ? `/${PER_ABBREVIATIONS[per.toLowerCase()] ?? per}` : "";
141
166
  }
142
167
 
143
- const DAY_MS = 86_400_000;
144
- const isoDate = /^\d{4}-\d{2}-\d{2}$/;
168
+ /** MMBtu in a megawatt hour: 3.412 (3,412,142 Btu). */
169
+ const MMBTU_PER_MWH = 3.412;
145
170
 
146
171
  /**
147
- * The weekday on or before a date (`back` 0) or before it (`back` 1), Saturday
148
- * and Sunday skipped. Futures venues' holidays are not in the app's calendars,
149
- * so a holiday reads as a session.
172
+ * A gas price per MWh as dollars per MMBtu, the unit Henry Hub gas (NG)
173
+ * trades in, so European gas can be set against it: TTF at €90.30/MWh is
174
+ * $30.832/MMBtu at EUR/USD 1.1650. `usdPerUnit` is one unit of the price's
175
+ * currency in dollars. Null where the contract is not priced per MWh in that
176
+ * currency, or the rate is unknown.
150
177
  */
151
- function weekdayBefore(date: string, back: 0 | 1): string {
152
- let at = Date.parse(`${date}T00:00:00Z`) - back * DAY_MS;
178
+ export function usdPerMmbtu(
179
+ price: number,
180
+ terms: PricedTerms | null | undefined,
181
+ currency: string | null | undefined,
182
+ usdPerUnit: number | null | undefined,
183
+ ): number | null {
184
+ const unit = contractPriceUnit(termsPriceUnit(terms), currency);
185
+ if (!unit || unit.split("/")[1]?.trim().toLowerCase() !== "mwh") return null;
186
+ if (!Number.isFinite(price) || usdPerUnit == null || !Number.isFinite(usdPerUnit) || usdPerUnit <= 0) return null;
187
+ return (price * usdPerUnit) / MMBTU_PER_MWH;
188
+ }
189
+
190
+ /** A per-MWh gas price in dollars per MMBtu, and the rate it was converted at: one unit of its currency in dollars. */
191
+ export interface UsdPerMmbtu {
192
+ value: number;
193
+ currency: string;
194
+ usdPerUnit: number;
195
+ }
196
+
197
+ /**
198
+ * A per-MWh gas price (Dutch TTF) in dollars per MMBtu at the rate `rate`
199
+ * reads for its currency (USD per unit); null for any other contract, or
200
+ * when the rate does not load.
201
+ */
202
+ export async function loadUsdPerMmbtu(
203
+ price: number | null | undefined,
204
+ terms: PricedTerms | null | undefined,
205
+ currency: string | null | undefined,
206
+ rate: (currency: string) => Promise<number | null | undefined>,
207
+ ): Promise<UsdPerMmbtu | null> {
208
+ const code = currency?.trim().toUpperCase();
209
+ if (price == null || !code || usdPerMmbtu(price, terms, code, 1) == null) return null;
210
+ const usdPerUnit = code === "USD" ? 1 : await rate(code)
211
+ .catch(logFailure("futures-contract", "Read the rate for a gas price in USD failed", null));
212
+ const value = usdPerMmbtu(price, terms, code, usdPerUnit);
213
+ return value == null ? null : { value, currency: code, usdPerUnit: usdPerUnit! };
214
+ }
215
+
216
+ /**
217
+ * The dollar equivalent beside a per-MWh price, marked as a conversion and
218
+ * with the rate it used, the way converted market caps name theirs:
219
+ * "≈ $30.832/MMBtu at EUR/USD 1.1650".
220
+ */
221
+ export function usdPerMmbtuText({ value, currency, usdPerUnit }: UsdPerMmbtu): string {
222
+ return `≈ $${value.toFixed(3)}/MMBtu at ${currency}/USD ${usdPerUnit.toFixed(4)}`;
223
+ }
224
+
225
+ const DAY_MS = 86_400_000;
226
+ const isoDate = /^\d{4}-\d{2}-\d{2}$/;
227
+
228
+ /** The weekday on or before a date, Saturday and Sunday skipped. */
229
+ function weekdayOnOrBefore(date: string): string {
230
+ let at = Date.parse(`${date}T00:00:00Z`);
153
231
  while (new Date(at).getUTCDay() % 6 === 0) at -= DAY_MS;
154
232
  return new Date(at).toISOString().slice(0, 10);
155
233
  }
@@ -179,43 +257,49 @@ export interface FuturesChangeBasis {
179
257
 
180
258
  /**
181
259
  * What the move a futures quote headlines is measured from, for the words
182
- * after it ("vs Fri 16 Oct settle", quoteChangeBasis). `sessionDate` is the session the move belongs
183
- * to (regularSessionDisplayDate); a Saturday or Sunday is Friday's, as no
184
- * session is dated on one. The basis is only what the numbers show, against
185
- * the one settlement the contract terms carry: a reference (the price less
186
- * the move) within half a tick of it is that settlement, of whichever session
187
- * it is. A reference that is not the settlement of the session just before
188
- * the move's is that session's last trade. Otherwise, when the terms carry no
189
- * settlement that old or one the reference can be checked against (the move's
190
- * own session has settled, and the one before is not in the terms), it is the
191
- * session before's close, left unnamed. Null without a session date, a move
192
- * or terms.
260
+ * after it ("vs Fri 16 Oct settle", quoteChangeBasis). `sessionDate` is the
261
+ * session the move belongs to (regularSessionDisplayDate); a Saturday or
262
+ * Sunday is Friday's, as no session is dated on one, and a holiday CME Globex
263
+ * traded is the next trade date's. The basis is only what the numbers show,
264
+ * against the one settlement the contract terms carry: a reference (the price
265
+ * less the move) within half a tick of it is that settlement, of whichever
266
+ * session it is. A reference that is not the settlement of the session just
267
+ * before the move's is that session's last trade. Otherwise, when the terms
268
+ * carry no settlement that old or one the reference can be checked against
269
+ * (the move's own session has settled, and the one before is not in the
270
+ * terms), it is the session before's close, left unnamed. The session before
271
+ * is the trade date before the move's on the `exchange`'s published holiday
272
+ * calendar (previousPublishedSession), so the Tuesday after Memorial Day
273
+ * moves from Friday. On a venue without one, only a matching settlement is
274
+ * named, by the date the terms give it. Null without a session date, a move
275
+ * or terms, or when nothing but the calendar could date the reference and
276
+ * there is none.
193
277
  */
194
278
  export function futuresChangeBasis(
195
279
  terms: Pick<FuturesContractTermsPayload, "settlement"> & { spec?: Pick<FuturesContractSpec, "tick"> | null } | null | undefined,
196
280
  move: { price: number; change?: number | null; sessionDate?: string | null } | null | undefined,
281
+ exchange: string | null | undefined,
197
282
  ): FuturesChangeBasis | null {
198
283
  const dated = move?.sessionDate;
199
284
  if (!terms || !move || !dated || !isoDate.test(dated) || move.change == null) return null;
200
285
  const reference = move.price - move.change;
201
286
  if (!Number.isFinite(reference)) return null;
202
- const session = weekdayBefore(dated, 0);
203
- const previous = weekdayBefore(session, 1);
287
+ const session = weekdayOnOrBefore(dated);
288
+ const previous = previousPublishedSession(exchange ?? undefined, dated, { closedDayRollsForward: true })?.date ?? null;
204
289
  const settled = terms.settlement;
205
290
  if (settled && settled.date <= session) {
206
291
  if (Math.abs(reference - settled.price) <= settlementTolerance(terms.spec?.tick, settled.price)) {
207
292
  return { kind: "settle", session: settled.date };
208
293
  }
209
- if (settled.date < session && settled.date >= previous) return { kind: "last trade", session: settled.date };
294
+ if (settled.date === previous) return { kind: "last trade", session: settled.date };
210
295
  }
211
- return { kind: "close", session: previous };
296
+ return previous ? { kind: "close", session: previous } : null;
212
297
  }
213
298
 
214
- /** A listed contract as code and month: "NGX26 (Nov 26)". */
299
+ /** A listed contract as code and month: "NGX26 (Nov 2026)". */
215
300
  function contractWithMonth(contract: { contract: string; deliveryMonth: string }): string {
216
- const [year, month] = contract.deliveryMonth.split("-");
217
- const name = MONTHS[Number(month) - 1];
218
- return name && year ? `${contract.contract} (${name} ${year.slice(2)})` : contract.contract;
301
+ const month = deliveryMonthLabel(contract.deliveryMonth);
302
+ return month ? `${contract.contract} (${month})` : contract.contract;
219
303
  }
220
304
 
221
305
  /**
@@ -108,11 +108,26 @@ const isoDay = (time: number) => new Date(time).toISOString().slice(0, 10);
108
108
  * in that week. A cost bought on a date and `fx --date` both read it.
109
109
  */
110
110
  export async function fxCloseOnDate(leg: FxLeg, date: string, client: FxHistoryClient, now = new Date()): Promise<FxCloseReading | null> {
111
+ return (await readFxCloseOnDate(leg, date, client, now)).reading;
112
+ }
113
+
114
+ /**
115
+ * `fxCloseOnDate`'s reading, and whether the service answered with any daily
116
+ * bars of the pair at all: an `empty` or `unsupported` answer is a pair it
117
+ * keeps no daily closes of, not a week without one.
118
+ */
119
+ export async function readFxCloseOnDate(
120
+ leg: FxLeg,
121
+ date: string,
122
+ client: FxHistoryClient,
123
+ now = new Date(),
124
+ ): Promise<{ reading: FxCloseReading | null; served: boolean }> {
111
125
  const day = Date.parse(`${date}T00:00:00Z`);
112
126
  const response = await client.getCloudHistory(leg.instrument.symbol, leg.instrument.exchange ?? "", {
113
127
  interval: "1day", startDate: isoDay(day - FX_MAX_AGE_DAYS * DAY_MS), endDate: isoDay(day + DAY_MS), outputsize: FX_MAX_AGE_DAYS + 4,
114
128
  });
115
- if (response.status !== "success" && response.status !== "partial") return null;
129
+ const served = response.status !== "empty" && response.status !== "unsupported";
130
+ if (response.status !== "success" && response.status !== "partial") return { reading: null, served };
116
131
  const today = isoDay(now.getTime());
117
132
  let best: FxCloseReading | null = null;
118
133
  for (const row of Array.isArray(response.data) ? response.data : []) {
@@ -122,7 +137,7 @@ export async function fxCloseOnDate(leg: FxLeg, date: string, client: FxHistoryC
122
137
  if (rowDay > date || rowDay >= today || Date.parse(`${rowDay}T00:00:00Z`) < day - FX_MAX_AGE_DAYS * DAY_MS) continue;
123
138
  if (!best || rowDay > best.date) best = { rate: leg.invert ? 1 / row.close : row.close, date: rowDay };
124
139
  }
125
- return best;
140
+ return { reading: best, served };
126
141
  }
127
142
 
128
143
  /**
@@ -1,4 +1,5 @@
1
1
  import { logFailure } from "../utils/log-failure";
2
+ import { fxRateCurrencies } from "../utils/currency-units";
2
3
  import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
4
  import type { SecFilingDocument, SecFilingItem } from "../types/data-provider";
4
5
  import type { OptionsChain, PricePoint, Quote, TickerFinancials } from "../types/financials";
@@ -77,13 +78,7 @@ function useCoordinatorKeysVersion(keys: readonly string[]): number {
77
78
  return useSyncExternalStore(subscribe, getSnapshot, () => 0);
78
79
  }
79
80
 
80
- function stableCurrencyList(currencies: Array<string | null | undefined>): string[] {
81
- return [...new Set(
82
- currencies
83
- .map((currency) => currency?.trim().toUpperCase() ?? "")
84
- .filter(Boolean),
85
- )].sort((left, right) => left.localeCompare(right));
86
- }
81
+
87
82
 
88
83
  function buildTickerFinancialsMapKey(tickers: TickerRecord[], options: TickerInstrumentOptions = {}): string {
89
84
  return tickers.map((ticker) => {
@@ -570,9 +565,22 @@ export function useArticleSummary(url: string | null | undefined): QueryEntry<st
570
565
  return entry;
571
566
  }
572
567
 
568
+ /** A currency's US dollar rate as the app holds it, and when its source observed it (epoch ms; null when undated, and for USD). */
569
+ export interface FxRateObservation {
570
+ rate: number;
571
+ asOf: number | null;
572
+ }
573
+
573
574
  export function useFxRatesMap(currencies: Array<string | null | undefined>): Map<string, number> {
575
+ const observations = useFxRateObservations(currencies);
576
+ return useMemo(() => new Map([...observations].map(([currency, observation]) => [currency, observation.rate])), [observations]);
577
+ }
578
+
579
+ /** useFxRatesMap with each rate's observation time, for a figure that states the rate it was converted at. */
580
+ export function useFxRateObservations(currencies: Array<string | null | undefined>): Map<string, FxRateObservation> {
574
581
  const coordinator = getSharedMarketDataCoordinator();
575
- const normalizedCurrencyKey = stableCurrencyList(currencies).join("|");
582
+ // A minor unit's major currency is loaded too: GBX and ILA have no rate of their own.
583
+ const normalizedCurrencyKey = fxRateCurrencies(currencies).join("|");
576
584
  const normalizedCurrencies = useMemo(
577
585
  () => (normalizedCurrencyKey ? normalizedCurrencyKey.split("|") : []),
578
586
  [normalizedCurrencyKey],
@@ -605,12 +613,12 @@ export function useFxRatesMap(currencies: Array<string | null | undefined>): Map
605
613
  }, [appVisible, normalizedCurrencyKey]);
606
614
 
607
615
  return useMemo(() => {
608
- const rates = new Map<string, number>();
609
- rates.set("USD", 1);
616
+ const rates = new Map<string, FxRateObservation>();
617
+ rates.set("USD", { rate: 1, asOf: null });
610
618
  for (const [currency, entry] of entries) {
611
619
  const rate = resolveEntryValue(entry);
612
620
  if (rate != null) {
613
- rates.set(currency, rate);
621
+ rates.set(currency, { rate, asOf: entry?.asOf ?? null });
614
622
  }
615
623
  }
616
624
  return rates;