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
@@ -30,6 +30,8 @@ import {
30
30
  type PortfolioFuturesTerms,
31
31
  } from "../../../../portfolio/futures-positions";
32
32
  import { dailyFuturesHistory, loadPortfolioFuturesTerms, type PortfolioFuturesSources } from "../../../../portfolio/futures-terms";
33
+ import { loadPurchaseDateCostFx, withPurchaseDateCostFx, type CostFxHistoryClient, type PurchaseDateCostFx } from "../../../../portfolio/cost-fx";
34
+ import { apiClient } from "../../../../api-client";
33
35
  import { formatUtcDayTime, parseReportTime } from "../../../../utils/utc-time";
34
36
  import { quoteFreshnessFields } from "../../shared/report-freshness";
35
37
  import { resolvePortfolioTotalsCurrency } from "../summary/totals";
@@ -85,7 +87,7 @@ import { importedFromText } from "../imported-from";
85
87
  import { findNamedCollection } from "../../../../portfolio/collection-name";
86
88
  import { isoDateToday } from "../../../../utils/calendar-date";
87
89
  import { lastTradeDate } from "../../../../tickers/listing-fate";
88
- import { DIVIDEND_READ_DEADLINE_MS, incomeGapNote, loadHoldingDividends, portfolioIncome, projectDrip, type DripYear, type HoldingDividend } from "../income";
90
+ import { DIVIDEND_READ_DEADLINE_MS, incomeGapNote, loadHoldingDividends, portfolioIncome, projectDrip, showsIncomeColumns, type DripYear, type HoldingDividend } from "../income";
89
91
 
90
92
  /** Money prices pad to the currency's minor unit ($337.90, not $337.9), as the ticker view does. */
91
93
  function priceFormatOptions(quote: Parameters<typeof quoteFormatOptions>[0] & { currency?: string }, assetCategory?: string) {
@@ -202,6 +204,8 @@ export interface ShowCollectionOptions {
202
204
  dividendDeadlineMs?: number;
203
205
  /** Where futures' terms and settlements are read; Gloom Cloud's terms and the data provider's daily bars by default. */
204
206
  futuresSources?: PortfolioFuturesSources;
207
+ /** Where the FX closes of the lots' purchase dates are read; Gloom Cloud by default. */
208
+ costFxHistory?: CostFxHistoryClient;
205
209
  }
206
210
 
207
211
  export async function showCollection(name: string, ctx: CliCommandContext, options: ShowCollectionOptions = {}) {
@@ -317,8 +321,14 @@ export interface PortfolioValuation {
317
321
  unknownContractSize: Set<string>;
318
322
  /** Futures valued at the close without a confirmed settlement to value them at. */
319
323
  unsettled: Set<string>;
320
- /** Holdings in another currency whose cost converts at today's rate: no purchase date or cost FX rate was entered. */
324
+ /**
325
+ * Holdings in another currency whose cost converts at today's rate: no
326
+ * purchase date or cost FX rate was entered (`costFxUndated`), or the
327
+ * date's FX close could not be read (`costFxUnread`).
328
+ */
321
329
  costAtTodaysRate: Set<string>;
330
+ costFxUndated: Set<string>;
331
+ costFxUnread: Set<string>;
322
332
  /** Long and short market value of the priced lots, and their net and gross, in the portfolio's currency. */
323
333
  exposure: PortfolioExposure;
324
334
  }
@@ -346,10 +356,16 @@ function portfolioExposure(values: Iterable<number | null | undefined>): Portfol
346
356
  /**
347
357
  * What a book says of holdings in another currency whose cost converts at
348
358
  * today's rate, which leaves the currency's move since purchase out of their
349
- * P&L; `named` lists them as the table names its rows.
359
+ * P&L: those with no purchase date or rate, and those whose date's FX close
360
+ * could not be read. `named` lists them as the table names its rows.
350
361
  */
351
- export function costAtTodaysRateNote(named: string): string {
352
- return `Cost converted at today's rate for ${named}; give portfolio position set a --date or --cost-fx to use the rate when bought.`;
362
+ export function costAtTodaysRateNotes(valuation: Pick<PortfolioValuation, "costFxUndated" | "costFxUnread">,
363
+ named: (symbols: Iterable<string>) => string): string[] {
364
+ const undated = valuation.costFxUndated;
365
+ return [
366
+ ...undated.size ? [`Cost converted at today's rate for ${named(undated)}; give portfolio position set a --date or --cost-fx to use the rate when bought.`] : [],
367
+ ...valuation.costFxUnread.size ? [`Cost converted at today's rate for ${named(valuation.costFxUnread)}: the FX close of the purchase date could not be read.`] : [],
368
+ ];
353
369
  }
354
370
 
355
371
  /**
@@ -368,6 +384,7 @@ export async function valuePortfolioPositions({
368
384
  toBase,
369
385
  basis = "latest-print",
370
386
  futures,
387
+ purchaseDateFx,
371
388
  }: {
372
389
  tickers: readonly TickerRecord[];
373
390
  quotes: ReadonlyMap<string, CollectionQuote>;
@@ -378,6 +395,11 @@ export async function valuePortfolioPositions({
378
395
  basis?: PortfolioValuationBasis;
379
396
  /** Each future's contract size and settlement (loadPortfolioFuturesTerms), by symbol. */
380
397
  futures?: ReadonlyMap<string, PortfolioFuturesTerms>;
398
+ /**
399
+ * The FX closes of the lots' purchase dates in `currency` (loadPurchaseDateCostFx): a dated lot with no rate of
400
+ * its own into `currency` converts its cost at its date's close, and at today's rate when that could not be read.
401
+ */
402
+ purchaseDateFx?: PurchaseDateCostFx;
381
403
  }): Promise<PortfolioValuation> {
382
404
  const id = portfolioId;
383
405
  let totalPnl = 0;
@@ -388,11 +410,13 @@ export async function valuePortfolioPositions({
388
410
  const unknownContractSize = new Set<string>();
389
411
  const unsettled = new Set<string>();
390
412
  const costAtTodaysRate = new Set<string>();
413
+ const costFxUndated = new Set<string>();
414
+ const costFxUnread = new Set<string>();
391
415
  const values: PortfolioPositionValue[] = [];
392
416
  const holdings: AllocationHolding[] = [];
393
417
  const known = (value: number | null | undefined): number | null => value != null && Number.isFinite(value) ? value : null;
394
418
 
395
- for (const ticker of tickers) {
419
+ for (const ticker of purchaseDateFx ? withPurchaseDateCostFx(tickers, purchaseDateFx, currency, id) : tickers) {
396
420
  const quote = quotes.get(ticker.metadata.ticker);
397
421
  const futuresTerms = isFuturesTicker(ticker) ? futures?.get(ticker.metadata.ticker) ?? { size: null, settlement: null } : null;
398
422
  // A lot entered without its contract size takes the one the terms state; the entries keep the saved record.
@@ -430,7 +454,7 @@ export async function valuePortfolioPositions({
430
454
  const atSettlement = valuesAtSettlement(metrics, basis);
431
455
  const positionCurrency = metrics.positionCurrency;
432
456
  const positionRate = positionCurrency ? await toBase(1, positionCurrency) : Number.NaN;
433
- // The cost converts at the rate the lot was bought at when one was entered, else at today's, as its value does.
457
+ // The cost converts at the rate the lot was bought at, entered or its purchase date's close, else at today's, as its value does.
434
458
  const baseMetrics = getPortfolioPositionMetrics({ ...ticker, metadata: { ...ticker.metadata, positions: [position] } }, id, quoteCurrency,
435
459
  { currency, convert: value => value * positionRate }, quote);
436
460
  const boughtRate = positionCurrency ? lotCostFxRate(position, positionCurrency, currency) : null;
@@ -438,7 +462,10 @@ export async function valuePortfolioPositions({
438
462
  : boughtRate !== null ? baseMetrics.totalCost : await toBase(metrics.totalCost, positionCurrency);
439
463
  // Only a lot entered by hand can be given the rate it was bought at; a broker's lot reads as it always has.
440
464
  const atTodaysRate = position.broker === "manual" && !!positionCurrency && lotCostAtTodaysRate(position, positionCurrency, currency);
441
- if (atTodaysRate && metrics.hasCostBasis) costAtTodaysRate.add(ticker.metadata.ticker);
465
+ if (atTodaysRate && metrics.hasCostBasis) {
466
+ costAtTodaysRate.add(ticker.metadata.ticker);
467
+ (purchaseDateFx && position.dateAcquired ? costFxUnread : costFxUndated).add(ticker.metadata.ticker);
468
+ }
442
469
  const costFx = boughtRate !== null ? { rate: boughtRate, currency } : atTodaysRate ? null : undefined;
443
470
  const currentValueBase = resolvePortfolioMarketValue(baseMetrics,
444
471
  valuationQuote ? await toBase(valuationQuote.price, quoteCurrency) : null)?.gross ?? null;
@@ -481,7 +508,7 @@ export async function valuePortfolioPositions({
481
508
 
482
509
  const exposure = portfolioExposure(values.map((entry) => entry.position ? entry.row.marketValue as number | null : null));
483
510
  return { basis, positions: values, holdings, totalPnl, unavailablePnl, unavailableCost, unavailableMarketValue, brokerPnlSymbols,
484
- unknownContractSize, unsettled, costAtTodaysRate, exposure };
511
+ unknownContractSize, unsettled, costAtTodaysRate, costFxUndated, costFxUnread, exposure };
485
512
  }
486
513
 
487
514
  /**
@@ -686,7 +713,7 @@ async function showCollectionWithMarketData(
686
713
  name: string,
687
714
  ctx: CliCommandContext,
688
715
  { config, store, dataProvider, persistence }: MarketContext,
689
- { explain = false, basis = "latest-print", drip, dividendDeadlineMs = DIVIDEND_READ_DEADLINE_MS, futuresSources }: ShowCollectionOptions,
716
+ { explain = false, basis = "latest-print", drip, dividendDeadlineMs = DIVIDEND_READ_DEADLINE_MS, futuresSources, costFxHistory = apiClient }: ShowCollectionOptions,
690
717
  ) {
691
718
  const tickers = (await store.loadAllTickers()).sort((left, right) =>
692
719
  left.metadata.ticker.localeCompare(right.metadata.ticker)
@@ -702,10 +729,10 @@ async function showCollectionWithMarketData(
702
729
 
703
730
  const matchedPortfolio = collection.kind === "portfolio" ? collection.portfolio : undefined;
704
731
  if (!matchedPortfolio && basis === "regular-close") {
705
- ctx.fail(`--close values a portfolio; "${collection.name}" is a watchlist.`, "Usage: gloomberb portfolio show <portfolio> --close");
732
+ ctx.fail(`--close values a portfolio; "${collection.name}" is a watchlist.`, "Usage: gloom portfolio show <portfolio> --close");
706
733
  }
707
734
  if (!matchedPortfolio && drip) {
708
- ctx.fail(`--drip projects a portfolio's dividends; "${collection.name}" is a watchlist.`, "Usage: gloomberb portfolio show <portfolio> --drip");
735
+ ctx.fail(`--drip projects a portfolio's dividends; "${collection.name}" is a watchlist.`, "Usage: gloom portfolio show <portfolio> --drip");
709
736
  }
710
737
  const isPortfolio = !!matchedPortfolio;
711
738
  const id = collection.id;
@@ -731,12 +758,13 @@ async function showCollectionWithMarketData(
731
758
 
732
759
  const futuresHeld = filtered.filter((ticker) => isFuturesTicker(ticker)
733
760
  && ticker.metadata.positions.some((position) => position.portfolio === id && position.shares !== 0));
734
- const [quotes, futures] = await Promise.all([
761
+ const [quotes, futures, purchaseDateFx] = await Promise.all([
735
762
  loadCollectionQuotes(filtered, collection, dataProvider),
736
763
  isPortfolio ? loadPortfolioFuturesTerms(futuresHeld, {
737
764
  settlement: basis === "regular-close",
738
765
  sources: { history: dailyFuturesHistory(dataProvider), ...futuresSources },
739
766
  }) : undefined,
767
+ isPortfolio ? loadPurchaseDateCostFx(filtered, { into: currency, history: costFxHistory, portfolioId: id }) : undefined,
740
768
  ]);
741
769
  // Worst of across the holdings' quotes, as the PORT and watchlist reports state it.
742
770
  const broker = !!(matchedPortfolio?.brokerId || matchedPortfolio?.brokerInstanceId);
@@ -756,7 +784,7 @@ async function showCollectionWithMarketData(
756
784
  }
757
785
 
758
786
  if (matchedPortfolio) {
759
- const valuation = await valuePortfolioPositions({ tickers: filtered, quotes, portfolioId: id, currency, baseCurrency: config.baseCurrency, toBase, basis, futures });
787
+ const valuation = await valuePortfolioPositions({ tickers: filtered, quotes, portfolioId: id, currency, baseCurrency: config.baseCurrency, toBase, basis, futures, purchaseDateFx });
760
788
  // Exports carry no header line, so their source line names the basis.
761
789
  freshness = portfolioValuationFreshness(valuation, source, { note: structured });
762
790
  const {
@@ -774,7 +802,8 @@ async function showCollectionWithMarketData(
774
802
  const { allocation, cash } = await valuePortfolioAllocation({ valuation, portfolio: matchedPortfolio, account, toBase });
775
803
  const allocationBySymbol = new Map(allocation.rows.map((row) => [row.symbol, row]));
776
804
  // What each holding pays a year: trailing dividends per share times its shares, in the portfolio's currency.
777
- const held = filtered.filter((ticker) => valued.some((entry) => entry.ticker === ticker && entry.position));
805
+ // By symbol: a lot whose cost converts at its purchase date's FX close is valued on a copy of its record.
806
+ const held = filtered.filter((ticker) => valued.some((entry) => entry.ticker.metadata.ticker === ticker.metadata.ticker && entry.position));
778
807
  const { dividends } = await loadHoldingDividends(held, quotes, dataProvider, { deadlineMs: dividendDeadlineMs });
779
808
  // Lots without a position or shares are no lot of income; a row of their own keeps its place with none.
780
809
  const income = await portfolioIncome(valued.map((entry) => ({
@@ -853,7 +882,7 @@ async function showCollectionWithMarketData(
853
882
  const symbol = ticker.metadata.ticker;
854
883
  const dividend = dividends.get(symbol);
855
884
  if (!dividend) continue;
856
- const shares = valued.filter((entry) => entry.ticker === ticker && entry.position && entry.metrics)
885
+ const shares = valued.filter((entry) => entry.ticker.metadata.ticker === symbol && entry.position && entry.metrics)
857
886
  .reduce((sum, entry) => sum + entry.metrics!.totalShares, 0);
858
887
  if (shares === 0) continue;
859
888
  // Income the table could not show is not projected either; the note under it says why.
@@ -930,7 +959,7 @@ async function showCollectionWithMarketData(
930
959
  exposure,
931
960
  ...(unknownContractSize.size > 0 ? { unknownContractSizeSymbols: [...unknownContractSize] } : {}),
932
961
  ...(unsettled.size > 0 ? { unsettledSymbols: [...unsettled] } : {}),
933
- // Holdings in another currency with no purchase date or cost FX rate: their cost converts at today's rate.
962
+ // Holdings in another currency whose cost converts at today's rate: no purchase date or cost FX rate, or the date's close was not read.
934
963
  ...(costAtTodaysRate.size > 0 ? { costAtTodaysRateSymbols: [...costAtTodaysRate] } : {}),
935
964
  accountingBasis, ...(manualAccounting ? { manualAccounting } : {}),
936
965
  ...(matchedPortfolio.importedFrom ? { importedFrom: matchedPortfolio.importedFrom } : {}),
@@ -959,8 +988,8 @@ async function showCollectionWithMarketData(
959
988
  return [weight, target, drift, trade, tradeValue];
960
989
  };
961
990
 
962
- // Income and its yield, once any share or fund has one; with none known, the note under the table says why.
963
- const showIncome = income.knownHoldings > 0;
991
+ // Income and its yield, once any share or fund has one or only lacks a rate into the portfolio's currency; otherwise the note under the table says why.
992
+ const showIncome = showsIncomeColumns(income);
964
993
  const incomeCells = (income: number | null | undefined, yieldPercent: number | null | undefined): string[] =>
965
994
  showIncome ? [money(income), formatIncomeYield(yieldPercent)] : [];
966
995
  const extendedSessions = extendedSessionsOf(valued.map(({ extended }) => extended?.session));
@@ -1086,7 +1115,7 @@ async function showCollectionWithMarketData(
1086
1115
  const targetNote = describeTargetSum(allocation.targetSum, allocation.weightBasis);
1087
1116
  if (targetNote) notes.push(cliStyles.muted(targetNote));
1088
1117
  if (brokerPnlSymbols.size > 0) notes.push(cliStyles.muted(`P&L for ${named(brokerPnlSymbols)} is the broker's last snapshot.`));
1089
- if (costAtTodaysRate.size > 0) notes.push(cliStyles.muted(costAtTodaysRateNote(named(costAtTodaysRate))));
1118
+ notes.push(...costAtTodaysRateNotes(valuation, named).map((note) => cliStyles.muted(note)));
1090
1119
  if (unknownContractSize.size > 0) {
1091
1120
  notes.push(cliStyles.warning(`No contract size for ${named(unknownContractSize)}, so ${unknownContractSize.size === 1 ? "its" : "their"} value, cost and P&L are not shown.`));
1092
1121
  }
@@ -91,9 +91,9 @@ export function misplacedSymbolList(
91
91
  const more = collections.length - names.length;
92
92
  const existing = names.length > 0
93
93
  ? `${label}: ${names.join(", ")}${more > 0 ? ` and ${more} more` : ""}`
94
- : `no ${label} yet, create one with \`gloomberb ${noun} create <name>\``;
94
+ : `no ${label} yet, create one with \`gloom ${noun} create <name>\``;
95
95
  return {
96
- message: `"${preview}" reads as a list of symbols, not a ${noun} name; did you mean \`gloomberb ${noun} ${verb} <name> <list>\`; ${existing}`,
96
+ message: `"${preview}" reads as a list of symbols, not a ${noun} name; did you mean \`gloom ${noun} ${verb} <name> <list>\`; ${existing}`,
97
97
  details: usage,
98
98
  };
99
99
  }
@@ -146,6 +146,6 @@ export function rejectCashSymbol(symbol: string, exchange: string | undefined, p
146
146
  if (!isCashArgument(symbol, exchange)) return;
147
147
  throw new PortfolioCliError(
148
148
  "CASH is the portfolio's cash line, not a ticker.",
149
- `Record cash with: gloomberb portfolio cash set "${portfolioName}" <amount> [currency]. For the stock that trades as CASH, name its exchange: CASH:NASDAQ.`,
149
+ `Record cash with: gloom portfolio cash set "${portfolioName}" <amount> [currency]. For the stock that trades as CASH, name its exchange: CASH:NASDAQ.`,
150
150
  );
151
151
  }
@@ -1,9 +1,10 @@
1
1
  import { saveConfig } from "../../../../data/config/store";
2
- import { withConfigData, withMarketData } from "../../../../cli/scoped-context";
2
+ import { withConfigData } from "../../../../cli/scoped-context";
3
+ import { withCollectionConfigData, withCollectionMarketData } from "../../../../cli/suffixed-listing-rows";
3
4
  import { countCollectionTickers, findWatchlist } from "../../../../cli/helpers";
4
5
  import { slugifyName } from "../../../../utils/slugify";
5
6
  import { takeOption } from "../../../../cli/commands/command-utils";
6
- import { EXCHANGE_OPTION, loadSavedListing, requireListingArg, savedListingName } from "../../../../cli/listing-arg";
7
+ import { EXCHANGE_OPTION, requireSavedListing, savedListingName } from "../../../../cli/listing-arg";
7
8
  import {
8
9
  cliStyles,
9
10
  renderSection,
@@ -21,14 +22,12 @@ import {
21
22
  } from "../../../../portfolio/mutations";
22
23
  import { showCollection } from "./render";
23
24
  import { listingFields, misplacedSymbolList, refuseDryRun, takeCollectionName } from "./shared";
24
- import { addOneSymbol, addSymbols, FILE_OPTION, printSymbolRows, removeSymbols, requireSymbolList, retryNotice, SYMBOL_LIST_HELP } from "./bulk-symbols";
25
+ import { addOneSymbol, addSymbols, FILE_OPTION, printSymbolRows, removeSymbols, requireSymbolList, retryNotice, shellWord, SYMBOL_LIST_HELP } from "./bulk-symbols";
25
26
  import { readNameEntries } from "../../../../cli/name-entries";
26
27
  import { CLI_COMMAND_GROUPS } from "../../../../cli/help";
27
28
 
28
- const ADD_USAGE = "Usage: gloomberb watchlist add <watchlist> <symbols...> [--file <path>]";
29
- const REMOVE_USAGE = "Usage: gloomberb watchlist remove <watchlist> <symbols...> [--file <path>]";
30
- /** A collection's name as a command argument: quoted when it has spaces. */
31
- const quotedName = (name: string) => /\s/.test(name) ? `"${name}"` : name;
29
+ const ADD_USAGE = "Usage: gloom watchlist add <watchlist> <symbols...> [--file <path>]";
30
+ const REMOVE_USAGE = "Usage: gloom watchlist remove <watchlist> <symbols...> [--file <path>]";
32
31
 
33
32
  /** Actions that change a watchlist without a preview, so --dry-run would change it anyway. add previews. */
34
33
  const MUTATING_ACTIONS = new Set(["create", "delete", "rm", "remove"]);
@@ -59,7 +58,7 @@ function requireWatchlistArgs(
59
58
  async function createWatchlist(name: string, ctx: CliCommandContext) {
60
59
  await withConfigData(ctx, async ({ config }) => {
61
60
  const trimmedName = name.trim();
62
- if (!trimmedName) ctx.fail("Usage: gloomberb watchlist create <name>");
61
+ if (!trimmedName) ctx.fail("Usage: gloom watchlist create <name>");
63
62
 
64
63
  const id = slugifyName(trimmedName, "watchlist");
65
64
  const duplicate = config.watchlists.some((watchlist) =>
@@ -78,7 +77,7 @@ async function createWatchlist(name: string, ctx: CliCommandContext) {
78
77
  }
79
78
 
80
79
  async function deleteWatchlist(name: string, ctx: CliCommandContext) {
81
- await withConfigData(ctx, async ({ config, store }) => {
80
+ await withCollectionConfigData(ctx, async ({ config, store }) => {
82
81
  const watchlist = findWatchlist(config, name);
83
82
  if (!watchlist) ctx.fail(`Watchlist "${name}" was not found.`);
84
83
 
@@ -93,26 +92,29 @@ async function deleteWatchlist(name: string, ctx: CliCommandContext) {
93
92
  });
94
93
  }
95
94
 
96
- async function addTickerToWatchlistCommand(watchlist: Watchlist, symbol: string, exchange: string | undefined, ctx: CliCommandContext, market: MarketContext) {
95
+ async function addTickerToWatchlistCommand(
96
+ watchlist: Watchlist, symbol: string, notes: readonly string[], exchange: string | undefined, ctx: CliCommandContext, market: MarketContext,
97
+ ) {
97
98
  // A bare symbol is stored on the listing `quote` reads it as, and the notes say which; a match a list would leave for a choice fails.
98
99
  await addOneSymbol(ctx, market, {
99
100
  symbol,
100
101
  exchange,
101
102
  collection: { kind: "watchlist", name: watchlist.name },
102
- retry: (key) => `gloomberb watchlist add ${quotedName(watchlist.name)} ${key}`,
103
+ notes,
104
+ retry: (key) => `gloom watchlist add ${shellWord(watchlist.name)} ${key}`,
103
105
  add: (ticker) => addTickerToWatchlist(ticker, watchlist.id),
104
106
  });
105
107
  }
106
108
 
107
109
  async function addToWatchlistCommand(args: string[], file: string | undefined, exchange: string | undefined, ctx: CliCommandContext) {
108
- await withMarketData(ctx, async (market) => {
110
+ await withCollectionMarketData(ctx, async (market) => {
109
111
  const { watchlist, rest } = requireWatchlistArgs(market.config, args, file, "add", ADD_USAGE, ctx);
110
112
  const list = await requireSymbolList(ctx, rest, file, ADD_USAGE, (entries) => readNameEntries(entries, market.dataProvider));
111
113
  if (list.single) {
112
- await addTickerToWatchlistCommand(watchlist, list.single, exchange, ctx, market);
114
+ await addTickerToWatchlistCommand(watchlist, list.single, list.notes, exchange, ctx, market);
113
115
  return;
114
116
  }
115
- const retry = (key: string) => `gloomberb watchlist add ${quotedName(watchlist.name)} ${key}`;
117
+ const retry = (key: string) => `gloom watchlist add ${shellWord(watchlist.name)} ${key}`;
116
118
  const dryRun = ctx.cliOptions.dryRun;
117
119
  const rows = await addSymbols({
118
120
  symbols: list.symbols,
@@ -129,33 +131,36 @@ async function addToWatchlistCommand(args: string[], file: string | undefined, e
129
131
  });
130
132
  }
131
133
 
132
- async function removeTickerFromWatchlist(watchlist: Watchlist, symbol: string, exchange: string | undefined, ctx: CliCommandContext, { store }: ConfigContext) {
134
+ async function removeTickerFromWatchlist(
135
+ watchlist: Watchlist, symbol: string, notes: readonly string[], exchange: string | undefined, ctx: CliCommandContext, { store }: ConfigContext,
136
+ ) {
133
137
  // SAN:EPA and SAN --exchange EPA name the stored listing, which a bare SAN may hold under another venue.
134
- const listing = await requireListingArg(symbol, exchange, ctx);
135
- const ticker = await loadSavedListing(store, listing);
138
+ // GARAN.IS is the row of GARAN:BIST, else a row still saved under that key (a broker's).
139
+ const { listing, ticker } = await requireSavedListing(symbol, exchange, ctx, store, (row) => row.metadata.watchlists.includes(watchlist.id));
136
140
  if (!ticker) ctx.fail(`Ticker "${listing.key}" was not found in your local data.`);
137
141
  const saved = savedListingName(ticker);
138
142
  const result = removeWatchlistMembership(ticker, watchlist.id);
139
143
  if (!result.changed) ctx.fail(`${saved.key} is not in "${watchlist.name}".`);
140
144
 
141
145
  await store.saveTicker(result.ticker);
142
- ctx.printResult({ data: { changed: true, ...listingFields(saved), watchlist: watchlist.name } }, {
146
+ ctx.printResult({ data: { changed: true, ...listingFields(saved), watchlist: watchlist.name }, ...notes.length > 0 ? { warnings: [...notes] } : {} }, {
143
147
  text: () => cliStyles.success(`Removed ${saved.label} from "${watchlist.name}".`),
144
148
  });
145
149
  }
146
150
 
147
151
  async function removeFromWatchlistCommand(args: string[], file: string | undefined, exchange: string | undefined, ctx: CliCommandContext) {
148
- await withConfigData(ctx, async (context) => {
152
+ await withCollectionConfigData(ctx, async (context) => {
149
153
  const { watchlist, rest } = requireWatchlistArgs(context.config, args, file, "remove", REMOVE_USAGE, ctx);
150
154
  const list = await requireSymbolList(ctx, rest, file, REMOVE_USAGE);
151
155
  if (list.single) {
152
- await removeTickerFromWatchlist(watchlist, list.single, exchange, ctx, context);
156
+ await removeTickerFromWatchlist(watchlist, list.single, list.notes, exchange, ctx, context);
153
157
  return;
154
158
  }
155
159
  const rows = await removeSymbols({
156
160
  symbols: list.symbols,
157
161
  exchange,
158
162
  store: context.store,
163
+ holds: (ticker) => ticker.metadata.watchlists.includes(watchlist.id),
159
164
  remove: (ticker) => removeWatchlistMembership(ticker, watchlist.id),
160
165
  });
161
166
  printSymbolRows(ctx, rows, { verb: "remove", collection: { kind: "watchlist", name: watchlist.name }, repeated: list.repeated, notes: list.notes });
@@ -247,21 +252,21 @@ export const watchlistCliCommand: CliCommandDef = {
247
252
 
248
253
  if (action === "show") {
249
254
  const name = args.slice(1).join(" ");
250
- if (!name) ctx.fail("Usage: gloomberb watchlist show <name>");
255
+ if (!name) ctx.fail("Usage: gloom watchlist show <name>");
251
256
  await showCollection(name, ctx);
252
257
  return;
253
258
  }
254
259
 
255
260
  if (action === "create") {
256
261
  const name = args.slice(1).join(" ");
257
- if (!name) ctx.fail("Usage: gloomberb watchlist create <name>");
262
+ if (!name) ctx.fail("Usage: gloom watchlist create <name>");
258
263
  await createWatchlist(name, ctx);
259
264
  return;
260
265
  }
261
266
 
262
267
  if (action === "delete" || action === "rm") {
263
268
  const name = args.slice(1).join(" ");
264
- if (!name) ctx.fail("Usage: gloomberb watchlist delete <name>");
269
+ if (!name) ctx.fail("Usage: gloom watchlist delete <name>");
265
270
  await deleteWatchlist(name, ctx);
266
271
  return;
267
272
  }
@@ -282,7 +287,7 @@ export const watchlistCliCommand: CliCommandDef = {
282
287
  const path = /\.\w+$/.test(rest.at(-1) ?? "") ? rest.at(-1)! : null;
283
288
  const name = (path ? rest.slice(0, -1) : rest).join(" ") || "<watchlist>";
284
289
  ctx.fail(
285
- `watchlist has no import. To add the symbols in ${path ?? "a file"}, run: gloomberb watchlist add ${/\s/.test(name) ? `"${name}"` : name} --file ${path ?? "<path>"}`,
290
+ `watchlist has no import. To add the symbols in ${path ?? "a file"}, run: gloom watchlist add ${/\s/.test(name) ? `"${name}"` : name} --file ${path ?? "<path>"}`,
286
291
  ADD_USAGE,
287
292
  );
288
293
  }
@@ -24,13 +24,14 @@ import {
24
24
  valuationTimeText,
25
25
  valuePortfolioAllocation,
26
26
  valuePortfolioPositions,
27
- costAtTodaysRateNote,
27
+ costAtTodaysRateNotes,
28
28
  type CollectionMatch,
29
29
  type PortfolioValuation,
30
30
  } from "./cli/render";
31
31
  import { valuationBasisLabel, type PortfolioValuationBasis } from "../../../portfolio/valuation-basis";
32
32
  import { futuresQuantityText, isFuturesTicker } from "../../../portfolio/futures-positions";
33
33
  import { dailyFuturesHistory, loadPortfolioFuturesTerms } from "../../../portfolio/futures-terms";
34
+ import { loadPurchaseDateCostFx } from "../../../portfolio/cost-fx";
34
35
  import { EXTENDED_SESSION_LABELS, quoteHeadlineTime, type ExtendedSession } from "../../../market-data/market/status";
35
36
  import {
36
37
  BROKER_ACCOUNT_MISSING_NOTE,
@@ -289,12 +290,13 @@ async function portfolioHoldings(
289
290
  const currency = resolvePortfolioTotalsCurrency(target.portfolio, ctx.config.baseCurrency);
290
291
  const futuresHeld = tickers.filter((ticker) => isFuturesTicker(ticker)
291
292
  && ticker.metadata.positions.some((position) => position.portfolio === target.id && position.shares !== 0));
292
- const [quotes, futures] = await Promise.all([
293
+ const [quotes, futures, purchaseDateFx] = await Promise.all([
293
294
  loadCollectionQuotes(tickers, target, ctx.marketData),
294
295
  loadPortfolioFuturesTerms(futuresHeld, {
295
296
  settlement: basis === "regular-close",
296
297
  sources: { history: dailyFuturesHistory(ctx.marketData) },
297
298
  }),
299
+ loadPurchaseDateCostFx(tickers, { into: currency, history: ctx.apiClient, portfolioId: target.id }),
298
300
  ]);
299
301
  ctx.signal.throwIfAborted();
300
302
  const toBase = createBaseConverter(ctx.marketData, currency);
@@ -307,6 +309,7 @@ async function portfolioHoldings(
307
309
  toBase,
308
310
  basis,
309
311
  futures,
312
+ purchaseDateFx,
310
313
  });
311
314
  const { allocation, cash } = await valuePortfolioAllocation({ valuation, portfolio: resolved.portfolio, account: resolved.account, toBase });
312
315
  ctx.signal.throwIfAborted();
@@ -402,7 +405,7 @@ async function portfolioHoldings(
402
405
  : []),
403
406
  ...(targetNote ? [targetNote] : []),
404
407
  ...(target.portfolio.brokerInstanceId && !resolved.account ? [BROKER_ACCOUNT_MISSING_NOTE] : []),
405
- ...(valuation.costAtTodaysRate.size > 0 ? [costAtTodaysRateNote([...valuation.costAtTodaysRate].join(", "))] : []),
408
+ ...costAtTodaysRateNotes(valuation, (symbols) => [...symbols].join(", ")),
406
409
  ];
407
410
 
408
411
  const positionColumns = allocation.weightBasis === "gross"
@@ -2,6 +2,7 @@ import type { DividendData } from "../dividend-yield/client";
2
2
  import { fetchProviderDividendData } from "../dividend-yield/provider-client";
3
3
  import { refusedLock } from "../shared/cloud-denied";
4
4
  import { ApiRequestError } from "../../../api-client/errors";
5
+ import { isNetworkFailure } from "../../../utils/network-failure";
5
6
  import { regularSessionValuationPrice } from "../../../market-data/quotes/regular-session-valuation";
6
7
  import { resolveTickerInstrumentKind } from "../../../tickers/instrument-kind";
7
8
  import { mapWithConcurrency } from "../../../utils/concurrency";
@@ -65,7 +66,7 @@ function unreadWhy(error: unknown): "sign-in" | "offline" | "failed" {
65
66
  current = current instanceof Error ? current.cause : undefined;
66
67
  }
67
68
  // A request that never got an answer: no connection, DNS, a refused socket.
68
- return /fetch failed|network|ECONN|ENOTFOUND|EAI_AGAIN|Unable to connect|socket|offline/i.test(String(error instanceof Error ? error.message : error))
69
+ return isNetworkFailure(error) || /fetch failed|network|ECONN|ENOTFOUND|EAI_AGAIN|Unable to connect|socket|offline/i.test(String(error instanceof Error ? error.message : error))
69
70
  ? "offline" : "failed";
70
71
  }
71
72
 
@@ -184,6 +185,16 @@ export async function portfolioIncome(
184
185
  };
185
186
  }
186
187
 
188
+ /**
189
+ * Whether `portfolio show` keeps its Income and Yield columns: once any
190
+ * holding's income is known, or a holding's is unknown only for want of a
191
+ * rate into the portfolio's currency, whose rows then show no figure. Income
192
+ * that could not be read at all gives the columns up to the note alone.
193
+ */
194
+ export function showsIncomeColumns(income: PortfolioIncome): boolean {
195
+ return income.knownHoldings > 0 || [...income.gaps.values()].some((gap) => gap.cause === "fx");
196
+ }
197
+
187
198
  const UNREAD_WORDS: Record<"timed-out" | "sign-in" | "offline" | "failed", (seconds: number) => string> = {
188
199
  "timed-out": (seconds) => `dividends not read within ${seconds}s`,
189
200
  "sign-in": () => "dividends not read without a Gloom sign-in",
@@ -209,7 +220,7 @@ export function incomeGapNote(
209
220
  groups.set(key, [...(groups.get(key) ?? []), symbol]);
210
221
  }
211
222
  const causes = [...groups].map(([cause, symbols]) => `${cause} for ${name(symbols)}`).join("; ");
212
- if (income.knownHoldings === 0) return `Income not shown: ${causes}.`;
223
+ if (income.knownHoldings === 0) return `Income ${showsIncomeColumns(income) ? "unavailable" : "not shown"}: ${causes}.`;
213
224
  const others = income.knownHoldings;
214
225
  return `Income unavailable: ${causes}. The other ${others === 1 ? "holding pays" : `${others} holdings pay`} ${money(income.knownIncome)} a year.`;
215
226
  }
@@ -1,5 +1,6 @@
1
- import type { PaneTemplateContext, PaneTemplateInstanceConfig } from "../../../types/plugin";
1
+ import type { PaneTemplateContext, PaneTemplateInstanceConfig, ShortcutArgError } from "../../../types/plugin";
2
2
  import type { PluginModule } from "../plugin-module";
3
+ import { unknownCollectionArgError } from "../../../portfolio/collection-name";
3
4
  import { PortfolioListPane } from "./pane";
4
5
  import {
5
6
  buildPortfolioPaneSettingsDef,
@@ -28,6 +29,11 @@ function resolveCollectionIdForKind(context: PaneTemplateContext, kind: "portfol
28
29
  : (context.config.watchlists[0]?.id ?? null);
29
30
  }
30
31
 
32
+ /** `PF Nope`: no portfolio or watchlist has that name or id, so the bar names those that do. */
33
+ function unknownCollectionArg(arg: string, context: PaneTemplateContext): ShortcutArgError | null {
34
+ return unknownCollectionArgError(arg, [...context.config.portfolios, ...context.config.watchlists], "portfolio or watchlist");
35
+ }
36
+
31
37
  function createCollectionPaneInstance(
32
38
  context: PaneTemplateContext,
33
39
  kind?: "portfolio" | "watchlist",
@@ -91,7 +97,8 @@ export const portfolioListModule: PluginModule = {
91
97
  "portfolio", "watchlist", "collection", "pane", "list",
92
98
  "portfolio rebalance", "allocation drift", "target weights", "asset allocation", "cash balance",
93
99
  ],
94
- shortcut: { prefix: "PF" },
100
+ // The argument is the report's: `PF Book` opens Book, by name or id in any case.
101
+ shortcut: { prefix: "PF", argError: unknownCollectionArg },
95
102
  headless: collectionHoldingsHeadless,
96
103
  canCreate: (context) => resolveCollectionPaneId(context) !== null,
97
104
  createInstance: (context, options) => createCollectionPaneInstance(context, undefined, options?.arg),
@@ -102,7 +102,9 @@ import { useColumnClock } from "../use-column-clock";
102
102
  import { liveMarketCapitalization } from "../../../../market-data/quotes/live-valuation";
103
103
  import { hasUnknownOptionMultiplier } from "../../../../portfolio/position-metrics";
104
104
  import { withFuturesContractSizes } from "../../../../portfolio/futures-positions";
105
+ import { withPurchaseDateCostFx } from "../../../../portfolio/cost-fx";
105
106
  import { usePortfolioFuturesTerms } from "./futures-terms";
107
+ import { usePurchaseDateCostFx } from "./purchase-date-fx";
106
108
  import {
107
109
  allocationWeightBase,
108
110
  buildPortfolioAllocation,
@@ -177,11 +179,18 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
177
179
  () => getCollectionTickersFromConfig(config, tickersBySymbol, activeCollectionId),
178
180
  [activeCollectionId, config, tickersBySymbol],
179
181
  );
182
+ // Totals, money columns and account values are in the portfolio's currency.
183
+ const totalsCurrency = useMemo(
184
+ () => resolvePortfolioTotalsCurrency(currentPortfolio, config.baseCurrency),
185
+ [config.baseCurrency, currentPortfolio],
186
+ );
180
187
  // A future counts contracts of the size its terms give, and at the close is valued at its settlement.
181
188
  const futuresTerms = usePortfolioFuturesTerms(collectionTickers, { enabled: isPortfolioTab, settlement: valuationBasis === "regular-close" });
189
+ // A dated lot in another currency costs what its purchase date's FX close says in that currency, as portfolio show values it.
190
+ const purchaseDateFx = usePurchaseDateCostFx(collectionTickers, { enabled: isPortfolioTab, portfolioId: activeCollectionId, into: totalsCurrency });
182
191
  const tickers = useMemo(
183
- () => withFuturesContractSizes(collectionTickers, futuresTerms.terms),
184
- [collectionTickers, futuresTerms.terms],
192
+ () => withPurchaseDateCostFx(withFuturesContractSizes(collectionTickers, futuresTerms.terms), purchaseDateFx, totalsCurrency, activeCollectionId),
193
+ [activeCollectionId, collectionTickers, futuresTerms.terms, purchaseDateFx, totalsCurrency],
185
194
  );
186
195
  const financialsInstrumentOptions = useMemo(() => ({
187
196
  portfolioId: isPortfolioTab ? activeCollectionId : undefined,
@@ -200,11 +209,6 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
200
209
  // The positions stay as last synced while the broker is away, dated, with one key to bring it back.
201
210
  const offlineBrokerFooter = useOfflineBrokerFooter(brokerPortfolio ? offline : null);
202
211
  const portfolioTargets = currentPortfolio?.targetWeights;
203
- // Totals, money columns and account values are in the portfolio's currency.
204
- const totalsCurrency = useMemo(
205
- () => resolvePortfolioTotalsCurrency(currentPortfolio, config.baseCurrency),
206
- [config.baseCurrency, currentPortfolio],
207
- );
208
212
  // A boolean, so a quote tick does not rebuild the columns.
209
213
  const otherCurrencies = holdsOtherCurrencies(tickers, financialsMap, totalsCurrency);
210
214
  const columns = useMemo(
@@ -214,7 +218,9 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
214
218
  const supplementalData = usePortfolioSupplementalData(tickers, columns, appActive, financialsMap);
215
219
  // One lookup for the whole list, only while a perpetual column is on screen.
216
220
  const showsPerpetuals = viewMode === "table" && columns.some((column) => PERPETUAL_COLUMN_IDS.has(column.id));
217
- const perpetuals = useListPerpetuals(tickers, showsPerpetuals);
221
+ // The rows in view, from the last render: their contracts follow the quote stream.
222
+ const [perpetualsInView, setPerpetualsInView] = useState<ReadonlySet<string>>(() => new Set());
223
+ const perpetuals = useListPerpetuals(tickers, showsPerpetuals, perpetualsInView);
218
224
  const openPerpMarket = useOpenPerpMarket();
219
225
  const visibleWarmupRequirements = useMemo(
220
226
  () => resolveVisibleWarmupRequirements(columns),
@@ -550,6 +556,13 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
550
556
  }
551
557
  ), [height, sortedTickers.length, streamWindow, viewMode]);
552
558
 
559
+ const perpetualsInViewKey = showsPerpetuals
560
+ ? sortedTickers.slice(effectiveStreamWindow.start, effectiveStreamWindow.end).map((ticker) => ticker.metadata.ticker).join("\u001f")
561
+ : "";
562
+ useEffect(() => {
563
+ setPerpetualsInView(new Set(perpetualsInViewKey ? perpetualsInViewKey.split("\u001f") : []));
564
+ }, [perpetualsInViewKey]);
565
+
553
566
  usePortfolioPaneStreaming({
554
567
  appActive,
555
568
  activeCollectionId: isPortfolioTab ? activeCollectionId : undefined,
@@ -0,0 +1,46 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { apiClient } from "../../../../api-client";
3
+ import { loadPurchaseDateCostFx, purchaseDatesToRead, type PurchaseDateCostFx } from "../../../../portfolio/cost-fx";
4
+ import type { TickerRecord } from "../../../../types/ticker";
5
+
6
+ /** A past close does not change; a date that could not be read is tried again this often. */
7
+ const REFRESH_MS = 30 * 60_000;
8
+ const NO_RATES: PurchaseDateCostFx = new Map();
9
+
10
+ /**
11
+ * The FX closes of the purchase dates of the portfolio's dated lots, as
12
+ * `portfolio show` reads them, so their cost converts at the rate of the day
13
+ * they were bought into `into`, the currency the pane values the portfolio
14
+ * in. Read again only when the dated lots or the currency change. Nothing is
15
+ * read for a portfolio with no such lot.
16
+ */
17
+ export function usePurchaseDateCostFx(
18
+ tickers: readonly TickerRecord[],
19
+ { enabled, portfolioId, into }: { enabled: boolean; portfolioId: string; into: string },
20
+ ): PurchaseDateCostFx {
21
+ const key = useMemo(() => enabled ? [...purchaseDatesToRead(tickers, into, portfolioId).keys()].sort().join(",") : "", [enabled, into, portfolioId, tickers]);
22
+ const tickersRef = useRef(tickers);
23
+ tickersRef.current = tickers;
24
+ // Rates are by currency and date, so those read before a lot was added still hold while the new date is read.
25
+ const scope = `${into}|${portfolioId}`;
26
+ const [state, setState] = useState<{ scope: string; rates: PurchaseDateCostFx }>({ scope: "", rates: NO_RATES });
27
+ const [refresh, setRefresh] = useState(0);
28
+ const complete = !!key && state.scope === scope && key.split(",").every((date) => state.rates.has(date));
29
+
30
+ useEffect(() => {
31
+ if (!key || complete) return;
32
+ const timer = setInterval(() => setRefresh((count) => count + 1), REFRESH_MS);
33
+ return () => clearInterval(timer);
34
+ }, [complete, key]);
35
+
36
+ useEffect(() => {
37
+ if (!key) return;
38
+ let cancelled = false;
39
+ void loadPurchaseDateCostFx(tickersRef.current, { into, history: apiClient, portfolioId }).then((rates) => {
40
+ if (!cancelled) setState({ scope: `${into}|${portfolioId}`, rates });
41
+ });
42
+ return () => { cancelled = true; };
43
+ }, [into, key, portfolioId, refresh]);
44
+
45
+ return key && state.scope === scope ? state.rates : NO_RATES;
46
+ }