gloomberb 0.19.2 → 0.19.3

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 (472) hide show
  1. package/README.md +7 -1
  2. package/package.json +1 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +20 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app.tsx +23 -1
  20. package/src/architecture/core-builtin-allowlist.json +2 -1
  21. package/src/capabilities/factories.ts +1 -0
  22. package/src/cli/ascii-output.ts +42 -0
  23. package/src/cli/base-converter.ts +10 -2
  24. package/src/cli/command-options.ts +16 -1
  25. package/src/cli/commands/crypto-hints.ts +23 -4
  26. package/src/cli/commands/market.ts +92 -23
  27. package/src/cli/commands/news-search.ts +24 -5
  28. package/src/cli/commands/overview.ts +9 -4
  29. package/src/cli/commands/search.ts +71 -12
  30. package/src/cli/commands/system.ts +22 -30
  31. package/src/cli/commands/ticker.ts +49 -9
  32. package/src/cli/commands/version.ts +33 -0
  33. package/src/cli/context.ts +5 -9
  34. package/src/cli/data-dir.ts +7 -0
  35. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  36. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  37. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  38. package/src/cli/desktop-pane-shot.ts +196 -256
  39. package/src/cli/early-command.ts +42 -0
  40. package/src/cli/earnings-calendar.ts +9 -0
  41. package/src/cli/entry.ts +19 -13
  42. package/src/cli/errors.ts +12 -2
  43. package/src/cli/help.ts +1 -1
  44. package/src/cli/history-export.ts +22 -2
  45. package/src/cli/history-rows.ts +154 -4
  46. package/src/cli/index.ts +18 -1
  47. package/src/cli/known-gaps.ts +40 -5
  48. package/src/cli/listing-arg.ts +46 -9
  49. package/src/cli/options.ts +1 -1
  50. package/src/cli/output-file.ts +60 -13
  51. package/src/cli/pane-functions/catalog.ts +14 -3
  52. package/src/cli/pane-functions/data.ts +1 -1
  53. package/src/cli/pane-functions/dom.ts +45 -0
  54. package/src/cli/pane-functions/freshness.ts +46 -9
  55. package/src/cli/pane-functions/function-help.ts +17 -9
  56. package/src/cli/pane-functions/headless.ts +22 -14
  57. package/src/cli/pane-functions/index.ts +6 -2
  58. package/src/cli/pane-functions/options.ts +6 -2
  59. package/src/cli/pane-functions/resolver.ts +27 -5
  60. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  61. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  62. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  63. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  64. package/src/cli/quote-answer.ts +20 -0
  65. package/src/cli/registry.ts +41 -13
  66. package/src/cli/result.ts +12 -6
  67. package/src/cli/ticker-resolution.ts +2 -1
  68. package/src/cli/types.ts +2 -0
  69. package/src/components/chart/composite/format.ts +3 -0
  70. package/src/components/chart-table/header.tsx +1 -1
  71. package/src/components/chart-table/layout.ts +28 -11
  72. package/src/components/command-bar/action-runtime.ts +4 -0
  73. package/src/components/command-bar/commands/direct/index.ts +18 -33
  74. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  75. package/src/components/command-bar/commands/registry.ts +10 -1
  76. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  77. package/src/components/command-bar/routes/root/runtime.ts +24 -1
  78. package/src/components/command-bar/surface/index.tsx +9 -3
  79. package/src/components/company-logo.tsx +2 -1
  80. package/src/components/layout/header.tsx +8 -3
  81. package/src/components/layout/market-summary.ts +25 -7
  82. package/src/components/layout/pane/footer/index.tsx +7 -2
  83. package/src/components/layout/pane/footer/model.ts +40 -5
  84. package/src/components/market-board/index.tsx +3 -2
  85. package/src/components/onboarding/desks-step.tsx +12 -4
  86. package/src/components/onboarding/first-run-workspace.ts +81 -4
  87. package/src/components/onboarding/onboarding-frame.tsx +2 -1
  88. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  89. package/src/components/onboarding/onboarding-wizard.tsx +54 -23
  90. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  91. package/src/components/onboarding/run-setup-again.ts +24 -0
  92. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  93. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  94. package/src/components/onboarding/wizard-model.ts +57 -3
  95. package/src/components/onboarding/wizard-positions.ts +1 -1
  96. package/src/components/ui/delayed-mark.tsx +56 -0
  97. package/src/components/ui/external-link.tsx +3 -6
  98. package/src/components/ui/icon.tsx +8 -1
  99. package/src/components/ui/logo-mark.tsx +8 -5
  100. package/src/components/ui/stat-grid.tsx +263 -19
  101. package/src/data/config/store/node.ts +11 -5
  102. package/src/data/config/store/normalize.ts +16 -3
  103. package/src/data/profile-permissions.ts +47 -0
  104. package/src/data/resource-store.ts +34 -31
  105. package/src/data/sqlite/access.ts +19 -8
  106. package/src/data/sqlite/checkpointer.ts +95 -8
  107. package/src/data/sqlite/database.ts +40 -7
  108. package/src/docs/snapshot.json +191 -78
  109. package/src/i18n/es.ts +18 -2
  110. package/src/i18n/ja.ts +18 -2
  111. package/src/i18n/ko.ts +18 -2
  112. package/src/i18n/zh-cn.ts +18 -2
  113. package/src/i18n/zh-tw.ts +18 -2
  114. package/src/layout/desk-keys.ts +4 -1
  115. package/src/layout/desks.ts +38 -5
  116. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  117. package/src/market-data/batched-reads.ts +16 -0
  118. package/src/market-data/coordinator/auxiliary.ts +4 -2
  119. package/src/market-data/coordinator/index.ts +11 -2
  120. package/src/market-data/futures-board.ts +1 -1
  121. package/src/market-data/futures-contract.ts +3 -1
  122. package/src/market-data/market/index-level.ts +6 -4
  123. package/src/market-data/published-apac-sessions.ts +6 -0
  124. package/src/news/aggregator.ts +7 -4
  125. package/src/news/news-model.ts +11 -0
  126. package/src/news/story-id.ts +22 -0
  127. package/src/news/ticker-symbols.ts +17 -1
  128. package/src/notifications/app-notifier.ts +35 -14
  129. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  130. package/src/plugins/builtin/account-management/index.tsx +2 -3
  131. package/src/plugins/builtin/account-management/model.ts +1 -1
  132. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  133. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  134. package/src/plugins/builtin/alerts/events.ts +10 -2
  135. package/src/plugins/builtin/alerts/index.tsx +5 -5
  136. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  137. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  138. package/src/plugins/builtin/alerts/targets.ts +4 -1
  139. package/src/plugins/builtin/attention/headless.ts +1 -1
  140. package/src/plugins/builtin/attention/index.tsx +2 -4
  141. package/src/plugins/builtin/attention/listing.ts +1 -1
  142. package/src/plugins/builtin/attention/series.ts +1 -1
  143. package/src/plugins/builtin/awards/index.tsx +1 -2
  144. package/src/plugins/builtin/backtest/client.ts +1 -0
  145. package/src/plugins/builtin/backtest/index.tsx +1 -2
  146. package/src/plugins/builtin/backtest/rules.ts +3 -1
  147. package/src/plugins/builtin/backtest/window.ts +8 -7
  148. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  149. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  150. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  151. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  152. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  153. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  154. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  155. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  156. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  157. package/src/plugins/builtin/buildout/format.ts +9 -0
  158. package/src/plugins/builtin/buildout/index.tsx +51 -5
  159. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  160. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  161. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  162. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  163. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  164. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  165. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  166. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  167. package/src/plugins/builtin/cds/headless.ts +10 -2
  168. package/src/plugins/builtin/cds/model.ts +7 -3
  169. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  170. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  171. package/src/plugins/builtin/changelog/index.tsx +3 -2
  172. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
  173. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  174. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  175. package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
  176. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  177. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  178. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  179. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  180. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  181. package/src/plugins/builtin/chat/index.tsx +5 -5
  182. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  183. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  184. package/src/plugins/builtin/cloud/index.tsx +1 -2
  185. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  186. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  187. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  188. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  189. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  190. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  191. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  192. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  193. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  194. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  195. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  196. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  197. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  198. package/src/plugins/builtin/connections/index.ts +2 -3
  199. package/src/plugins/builtin/cot/index.tsx +1 -2
  200. package/src/plugins/builtin/cpi/index.tsx +1 -2
  201. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  202. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  203. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  204. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  205. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  206. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  207. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  208. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  209. package/src/plugins/builtin/crypto-board/series.ts +2 -0
  210. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  211. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  212. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  213. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  214. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  215. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  216. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  217. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  218. package/src/plugins/builtin/debug/index.tsx +1 -1
  219. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  220. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  221. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  222. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  223. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  224. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  225. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  226. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  227. package/src/plugins/builtin/docs/index.tsx +2 -3
  228. package/src/plugins/builtin/doe/headless.ts +16 -3
  229. package/src/plugins/builtin/doe/index.tsx +5 -4
  230. package/src/plugins/builtin/earnings/board.tsx +2 -1
  231. package/src/plugins/builtin/earnings/history.tsx +2 -1
  232. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  233. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  234. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  235. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  236. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  237. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  238. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  239. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  240. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  241. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  242. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  243. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  244. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  245. package/src/plugins/builtin/exposure/index.tsx +1 -2
  246. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  247. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  248. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  249. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  250. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  251. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  252. package/src/plugins/builtin/futures/index.tsx +15 -0
  253. package/src/plugins/builtin/futures/model.ts +14 -0
  254. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  255. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  256. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  257. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  258. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  259. package/src/plugins/builtin/gpu/board.tsx +51 -12
  260. package/src/plugins/builtin/gpu/client.ts +15 -2
  261. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  262. package/src/plugins/builtin/gpu/headless.ts +14 -2
  263. package/src/plugins/builtin/gpu/index.tsx +1 -2
  264. package/src/plugins/builtin/gpu/model.ts +20 -2
  265. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  266. package/src/plugins/builtin/help/index.tsx +1 -1
  267. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  268. package/src/plugins/builtin/holders/index.tsx +2 -3
  269. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  270. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  271. package/src/plugins/builtin/jodi/index.tsx +1 -2
  272. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  273. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  274. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  275. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  276. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  277. package/src/plugins/builtin/market-movers/index.tsx +7 -1
  278. package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
  279. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  280. package/src/plugins/builtin/members/index.tsx +1 -2
  281. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  282. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  283. package/src/plugins/builtin/news/headless.ts +7 -0
  284. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  285. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  286. package/src/plugins/builtin/notes/index.tsx +3 -6
  287. package/src/plugins/builtin/openfda/index.tsx +1 -2
  288. package/src/plugins/builtin/options/view.tsx +6 -5
  289. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  290. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  291. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  292. package/src/plugins/builtin/perps/index.tsx +1 -2
  293. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  294. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  295. package/src/plugins/builtin/plugin-module.ts +16 -2
  296. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  297. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  298. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  299. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  300. package/src/plugins/builtin/power/index.tsx +1 -2
  301. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  302. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  303. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  304. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  305. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  306. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  307. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  308. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  309. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  310. package/src/plugins/builtin/research/index.tsx +5 -8
  311. package/src/plugins/builtin/research-search/index.tsx +1 -2
  312. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  313. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  314. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  315. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  316. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  317. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  318. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  319. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  320. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  321. package/src/plugins/builtin/sec/headless.ts +5 -1
  322. package/src/plugins/builtin/sec/index.tsx +4 -2
  323. package/src/plugins/builtin/sec/model.ts +2 -0
  324. package/src/plugins/builtin/sectors/index.tsx +11 -5
  325. package/src/plugins/builtin/series-inventory.ts +2 -0
  326. package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
  327. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  328. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  329. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  330. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  331. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  332. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  333. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  334. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  335. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  336. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  337. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  338. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  339. package/src/plugins/builtin/thirteenf/api.ts +116 -13
  340. package/src/plugins/builtin/thirteenf/data.ts +132 -30
  341. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  342. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  343. package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
  344. package/src/plugins/builtin/thirteenf/types.ts +12 -0
  345. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
  346. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  347. package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
  348. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  349. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  350. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  351. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  352. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  353. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  354. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  355. package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  357. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  358. package/src/plugins/builtin/volatility/index.tsx +1 -2
  359. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  360. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  361. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  362. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  363. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  364. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  365. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  366. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  367. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  368. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  369. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  370. package/src/plugins/bundle.ts +52 -2
  371. package/src/plugins/catalog-browser.ts +2 -2
  372. package/src/plugins/catalog-cli.ts +20 -0
  373. package/src/plugins/host-link.ts +13 -1
  374. package/src/plugins/installer.ts +15 -4
  375. package/src/plugins/loader.ts +43 -11
  376. package/src/plugins/peer-imports.ts +201 -0
  377. package/src/plugins/peer-retry.ts +26 -29
  378. package/src/plugins/plugin-process.ts +128 -0
  379. package/src/plugins/redirection-guard.ts +45 -0
  380. package/src/remote/endpoint.ts +16 -3
  381. package/src/renderers/browser/main.tsx +3 -2
  382. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  383. package/src/renderers/dom/desktop/icons.tsx +2 -0
  384. package/src/renderers/dom/host/image-surface.tsx +46 -21
  385. package/src/renderers/dom/styles.css +7 -0
  386. package/src/renderers/dom/toast-host.tsx +2 -0
  387. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  388. package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
  389. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  390. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  391. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  392. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  393. package/src/renderers/electrobun/bun/window/create.ts +4 -2
  394. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  395. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  396. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  397. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  398. package/src/renderers/electrobun/view/main.tsx +7 -2
  399. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  400. package/src/renderers/opentui/browser-open.ts +10 -4
  401. package/src/renderers/opentui/image/surface.tsx +1 -1
  402. package/src/renderers/opentui/start.tsx +12 -6
  403. package/src/renderers/opentui/toast-host.tsx +2 -1
  404. package/src/series/registry.ts +18 -0
  405. package/src/series/standalone.ts +10 -1
  406. package/src/series/types.ts +7 -0
  407. package/src/series/validate.ts +11 -2
  408. package/src/sources/gloomberb-cloud/index.ts +28 -5
  409. package/src/sources/gloomberb-cloud/news.ts +24 -3
  410. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  411. package/src/sources/provider-errors.ts +26 -3
  412. package/src/sources/provider-router/history.ts +5 -0
  413. package/src/sources/provider-router/index.ts +13 -0
  414. package/src/sync/core-contributors.ts +32 -0
  415. package/src/telemetry/attention-counts.ts +64 -6
  416. package/src/telemetry/attention-setting.ts +33 -0
  417. package/src/telemetry/crash-reports-node.ts +3 -1
  418. package/src/telemetry/stall-clocks.ts +93 -0
  419. package/src/telemetry/stall-reports-node.ts +12 -7
  420. package/src/telemetry/stall-reports.ts +34 -5
  421. package/src/test-support/preload.ts +9 -0
  422. package/src/tickers/company-name.ts +19 -0
  423. package/src/tickers/listing-fate.ts +14 -3
  424. package/src/tickers/saved-instrument.ts +17 -0
  425. package/src/tickers/search/home-listing.ts +13 -2
  426. package/src/tickers/search/index.ts +4 -1
  427. package/src/tickers/search/types.ts +6 -1
  428. package/src/time-series/bar-aggregation.ts +7 -1
  429. package/src/time-series/base-series.ts +3 -3
  430. package/src/time-series/chart-bounds.ts +1 -1
  431. package/src/time-series/chart-data.ts +5 -2
  432. package/src/time-series/chart-seed.ts +1 -1
  433. package/src/time-series/coin-session-close.ts +211 -0
  434. package/src/time-series/live-bars.ts +13 -2
  435. package/src/time-series/live-quotes.ts +1 -1
  436. package/src/time-series/official-close.ts +159 -0
  437. package/src/time-series/price-comparison.ts +55 -6
  438. package/src/time-series/resolve.ts +12 -3
  439. package/src/time-series/series-presentation.ts +1 -1
  440. package/src/time-series/studies/formula.ts +205 -0
  441. package/src/time-series/studies/indicators.ts +299 -0
  442. package/src/time-series/studies/leverage.ts +75 -0
  443. package/src/time-series/studies/pair.ts +138 -0
  444. package/src/time-series/studies/resolve.ts +333 -0
  445. package/src/time-series/studies/shared.ts +141 -0
  446. package/src/time-series/studies/units.ts +243 -0
  447. package/src/time-series/studies/vwap.ts +274 -0
  448. package/src/types/config.ts +18 -2
  449. package/src/types/data-provider.ts +23 -0
  450. package/src/types/financials.ts +22 -0
  451. package/src/types/headless.ts +42 -3
  452. package/src/types/plugin.ts +32 -3
  453. package/src/ui/host.tsx +11 -0
  454. package/src/ui/toast.tsx +26 -1
  455. package/src/utils/ascii-text.ts +140 -0
  456. package/src/utils/cli-output.ts +91 -18
  457. package/src/utils/concurrency.ts +24 -0
  458. package/src/utils/exchanges.ts +6 -0
  459. package/src/utils/external-url.ts +20 -7
  460. package/src/utils/futures-generic.ts +4 -4
  461. package/src/utils/http-proxy-upstream.ts +124 -0
  462. package/src/utils/main-thread-stall.ts +75 -3
  463. package/src/utils/perf-marks.ts +36 -2
  464. package/src/utils/private-files.ts +141 -0
  465. package/src/utils/public-address.ts +99 -0
  466. package/src/account/upgrade-personalization.ts +0 -57
  467. package/src/account/upgrade-tickers.ts +0 -124
  468. package/src/api-client/upgrade-experiment.ts +0 -91
  469. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  470. package/src/renderers/electrobun/launcher/preload.ts +0 -13
  471. package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
  472. package/src/time-series/studies.ts +0 -1628
@@ -2,6 +2,7 @@ import { Fragment, createElement, type ReactNode } from "react";
2
2
  import type {
3
3
  GloomPlugin,
4
4
  GloomPluginContext,
5
+ PaneDef,
5
6
  } from "../../types/plugin";
6
7
  import { pluginContextInNamespace } from "../registry/plugin-state";
7
8
  import { withPluginStateNamespace } from "../runtime/context";
@@ -63,6 +64,19 @@ function moduleEntry(input: PluginModuleInput): PluginModuleEntry {
63
64
  return "module" in input ? input : { module: input };
64
65
  }
65
66
 
67
+ /** Keep a pane's component lazy while moving its state into another plugin. */
68
+ function paneInNamespace(pane: PaneDef, stateId: string): PaneDef {
69
+ let component: PaneDef["component"] | undefined;
70
+ return Object.create(Object.getPrototypeOf(pane), {
71
+ ...Object.getOwnPropertyDescriptors(pane),
72
+ component: {
73
+ enumerable: true,
74
+ configurable: true,
75
+ get: () => component ??= withPluginStateNamespace(stateId, pane.component),
76
+ },
77
+ });
78
+ }
79
+
66
80
  /**
67
81
  * The module as it runs under a namespace other than its plugin's: every
68
82
  * surface it renders and the context its setup receives are moved there,
@@ -76,7 +90,7 @@ function moduleInNamespace(module: PluginModule, stateId: string): PluginModule
76
90
  return {
77
91
  ...module,
78
92
  ...(module.panes ? {
79
- panes: module.panes.map((pane) => ({ ...pane, component: withPluginStateNamespace(stateId, pane.component) })),
93
+ panes: module.panes.map((pane) => paneInNamespace(pane, stateId)),
80
94
  } : {}),
81
95
  ...(slots ? { slots } : {}),
82
96
  ...(module.setup ? {
@@ -84,7 +98,7 @@ function moduleInNamespace(module: PluginModule, stateId: string): PluginModule
84
98
  const scoped = pluginContextInNamespace(ctx, stateId);
85
99
  return module.setup!({
86
100
  ...scoped,
87
- registerPane: (pane) => ctx.registerPane({ ...pane, component: withPluginStateNamespace(stateId, pane.component) }),
101
+ registerPane: (pane) => ctx.registerPane(paneInNamespace(pane, stateId)),
88
102
  registerTickerResearchTab: (tab) => ctx.registerTickerResearchTab({
89
103
  ...tab,
90
104
  component: withPluginStateNamespace(stateId, tab.component),
@@ -1,5 +1,7 @@
1
1
  import { logFailure } from "../../../../utils/log-failure";
2
- import { createBaseConverter } from "../../../../cli/base-converter";
2
+ import { createBaseConverter, type BaseConversion } from "../../../../cli/base-converter";
3
+ import { quotedFxPair } from "../../../../utils/currency-pair";
4
+ import { formatFxRate } from "../../../../cli/fx-pair";
3
5
  import { savedListingName } from "../../../../cli/listing-arg";
4
6
  import { currentListingName } from "../../../../tickers/listing-name";
5
7
  import { withMarketData } from "../../../../cli/scoped-context";
@@ -575,6 +577,38 @@ export function weightBasisLine(allocation: Pick<PortfolioAllocation, "weightBas
575
577
  return weightBasisNote(allocation, (value) => formatAllocationMoney(value, currency, { whole: true }));
576
578
  }
577
579
 
580
+ /** `47h old`, or `3 days old` past two days: how far behind a stale rate is. */
581
+ function rateAge(asOf: number, now: number): string {
582
+ const hours = Math.max(0, Math.floor((now - asOf) / 3_600_000));
583
+ return hours < 48 ? `${hours}h old` : `${Math.floor(hours / 24)} days old`;
584
+ }
585
+
586
+ /** A rate the report converted at, as markets quote the pair and `fx` writes it, for `--json`. */
587
+ function fxRateRecord({ currency, baseCurrency, rate, asOf, stale }: BaseConversion) {
588
+ const quoted = quotedFxPair(currency, baseCurrency, rate);
589
+ return { currency, baseCurrency, pair: quoted.pair, rate: Number(formatFxRate(quoted.rate)), asOf: asOf == null ? null : new Date(asOf).toISOString(), stale };
590
+ }
591
+
592
+ /**
593
+ * `FX: USD/ZAR 16.7237, as of Fri 30 Oct 18:12 UTC`: each rate a portfolio's
594
+ * holdings in another currency were converted at, as markets quote the pair,
595
+ * dated; one past its fresh window says it is stale and how old. Null when
596
+ * nothing was converted.
597
+ */
598
+ function fxRatesLine(
599
+ conversions: readonly BaseConversion[],
600
+ { now = Date.now(), warning = (text: string) => text }: { now?: number; warning?: (text: string) => string } = {},
601
+ ): string | null {
602
+ const rates = conversions.filter((conversion) => conversion.currency !== conversion.baseCurrency && Number.isFinite(conversion.rate));
603
+ if (rates.length === 0) return null;
604
+ return `FX: ${rates.map((conversion) => {
605
+ const quoted = quotedFxPair(conversion.currency, conversion.baseCurrency, conversion.rate);
606
+ const dated = conversion.asOf == null ? "" : `, as of ${formatUtcDayTime({ time: conversion.asOf, dateOnly: false }, now)} UTC`;
607
+ const text = `${quoted.pair} ${formatFxRate(quoted.rate)}${dated}`;
608
+ return conversion.stale ? warning(`${text} (stale${conversion.asOf == null ? "" : `, ${rateAge(conversion.asOf, now)}`})`) : text;
609
+ }).join("; ")}`;
610
+ }
611
+
578
612
  /** What the TOTAL row weighs: all of a total; nothing on a gross base, where the weights net to the book's net over its gross. */
579
613
  function totalWeight(allocation: Pick<PortfolioAllocation, "weightBasis" | "total">): number | null {
580
614
  return allocation.weightBasis === "total" && allocation.total != null && allocation.total > 0 ? 100 : null;
@@ -818,7 +852,12 @@ async function showCollectionWithMarketData(
818
852
  })), dividends, toBase);
819
853
  const lotIncome = income.lots;
820
854
  const totalIncome = income.totalIncome;
821
- const portfolioYield = totalIncome != null && allocation.total != null && allocation.total > 0 ? (totalIncome / allocation.total) * 100 : null;
855
+ // The yield is on what is invested: cash pays no dividend, so it does not dilute it. A long-only book's is
856
+ // its priced holdings' net value. A book that holds a short is weighed on its gross exposure, and so is its
857
+ // yield: the net shrinks as the hedge grows (long 100k against short 80k nets 20k), which would inflate it.
858
+ const hedged = allocation.weightBasis === "gross";
859
+ const yieldBase = hedged ? allocation.weightBase : exposure.net;
860
+ const portfolioYield = totalIncome != null && yieldBase != null && yieldBase > 0 ? (totalIncome / yieldBase) * 100 : null;
822
861
  const incomeUnavailable = [...income.gaps].map(([symbol, gap]) => ({ symbol, ...gap }));
823
862
  const showTargets = allocation.targetSum != null;
824
863
  // Never dropped on a narrow terminal, so --explain reads the terms off them.
@@ -956,7 +995,9 @@ async function showCollectionWithMarketData(
956
995
  totalUnrealizedPnl: totalPnlValue,
957
996
  annualIncome: totalIncome, knownAnnualIncome: income.knownIncome, portfolioYield,
958
997
  incomeComplete: income.gaps.size === 0, incomeUnavailable,
959
- incomeBasis: "Trailing twelve-month dividends per share times shares held, in the portfolio's currency; a short owes the dividend, so its income is negative. Portfolio yield is the total over the total value; annualIncome is null when any holding's income is unknown, and knownAnnualIncome sums the holdings that have one.",
998
+ incomeBasis: "Trailing twelve-month dividends per share times shares held, in the portfolio's currency; a short owes the dividend, so its income is negative. Portfolio yield is the total over the invested value without cash: the priced holdings' net value (exposure.net), or for a book that holds a short its gross exposure (weightBase), as its weights are; annualIncome is null when any holding's income is unknown, and knownAnnualIncome sums the holdings that have one.",
999
+ // Each rate a holding in another currency was converted at, dated, and whether it is stale.
1000
+ fxRates: toBase.conversions().filter((conversion) => conversion.currency !== conversion.baseCurrency).map(fxRateRecord),
960
1001
  complete: unavailablePnl.size === 0 && unavailableCost.size === 0 && unavailableMarketValue.size === 0 && income.gaps.size === 0,
961
1002
  unavailableSymbols: [...new Set([...unavailablePnl, ...unavailableCost, ...unavailableMarketValue])],
962
1003
  unavailableCostSymbols: [...unavailableCost], brokerPnlSymbols: [...brokerPnlSymbols],
@@ -1098,7 +1139,16 @@ async function showCollectionWithMarketData(
1098
1139
  console.log("");
1099
1140
  if (basis === "regular-close") console.log(dayMoveLine(dayMove, currency, colorBySign, cliStyles.muted));
1100
1141
  if (valued.some((entry) => entry.position)) console.log(exposureLine(exposure, currency));
1142
+ // The rates the holdings in another currency are valued at, dated, so a stale one is seen beside the value it moves.
1143
+ const fxLine = fxRatesLine(toBase.conversions(), { warning: cliStyles.warning });
1144
+ if (fxLine) console.log(fxLine);
1101
1145
  const notes: string[] = [];
1146
+ // TOTAL's yield says what it divides: a hedged book's gross exposure, or, with cash in the book, the holdings alone.
1147
+ if (showIncome && portfolioYield != null && yieldBase != null && hedged) {
1148
+ notes.push(cliStyles.muted(`TOTAL yield is income over the gross exposure of ${wholeMoney(yieldBase)}, as the book holds a short; cash excluded.`));
1149
+ } else if (showIncome && portfolioYield != null && yieldBase != null && cashExport?.value) {
1150
+ notes.push(cliStyles.muted(`TOTAL yield is income over the invested ${wholeMoney(yieldBase)}, cash excluded.`));
1151
+ }
1102
1152
  const weightNote = weightBasisLine(allocation, currency);
1103
1153
  if (weightNote) notes.push(cliStyles.muted(weightNote));
1104
1154
  // The notes name the rows as the table does.
@@ -1,7 +1,6 @@
1
1
  import type { PaneTemplateContext, PaneTemplateInstanceConfig, ShortcutArgError } from "../../../types/plugin";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { unknownCollectionArgError } from "../../../portfolio/collection-name";
4
- import { PortfolioListPane } from "./pane";
5
4
  import {
6
5
  buildPortfolioPaneSettingsDef,
7
6
  getPortfolioPaneSettings,
@@ -54,7 +53,7 @@ export const portfolioListModule: PluginModule = {
54
53
  reportFreshness: { source: "Local portfolio and Gloom Cloud" },
55
54
  name: "Portfolio",
56
55
  icon: "P",
57
- component: PortfolioListPane,
56
+ get component(): typeof import("./pane").PortfolioListPane { const { PortfolioListPane } = require("./pane"); return PortfolioListPane; },
58
57
  defaultPosition: "left",
59
58
  defaultMode: "floating",
60
59
  defaultWidth: "40%",
@@ -34,6 +34,7 @@ import { convertCurrency } from "../../../../utils/format";
34
34
  import { nextHeaderSort } from "../../../../utils/sort-values";
35
35
  import { getSharedMarketDataCoordinator } from "../../../../market-data/coordinator";
36
36
  import type { TickerRecord } from "../../../../types/ticker";
37
+ import type { TickerFinancials } from "../../../../types/financials";
37
38
  import type { PaneProps } from "../../../../types/plugin";
38
39
  import type { InstrumentRef } from "../../../../market-data/request-types";
39
40
  import {
@@ -56,6 +57,7 @@ import {
56
57
  } from "../summary";
57
58
  import { usePortfolioAccountState } from "../summary/live-accounts";
58
59
  import { useOfflineBrokerFooter } from "../../shared/broker-reconnect";
60
+ import { useQuoteDelayFooter } from "../../shared/cloud-upgrade";
59
61
  import {
60
62
  getCollectionEntries,
61
63
  getPortfolioPaneSettings,
@@ -598,6 +600,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
598
600
  summaryAccountState,
599
601
  ], FOOTER_TOTALS_THROTTLE_MS);
600
602
  const fxWarning = !!(fxStatus.stale || fxStatus.unknownTime || fxStatus.unavailable);
603
+ // Signed out or free, the rows' prices are the delayed ones; a list of coins is live and says nothing.
604
+ const quoteDelay = useQuoteDelayFooter(rowQuotes(sortedTickers, financialsMap), "portfolio-delay");
601
605
  const summaryNotices = isPortfolioTab
602
606
  ? buildPortfolioSummaryNotices({
603
607
  totals: portfolioSummaryTotals,
@@ -675,6 +679,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
675
679
  ...offlineBrokerFooter.status,
676
680
  ...(fxStatusText && !fxWarning ? [{ id: "fx", parts: [{ text: `FX ${fxStatusText}`, tone: "muted" as const }] }] : []),
677
681
  ...(showsPerpetuals && perpetuals.stale ? [{ id: "perpetuals", parts: [{ text: "perpetuals stale", tone: "warning" as const }] }] : []),
682
+ ...(quoteDelay ? [quoteDelay] : []),
678
683
  ],
679
684
  hints: [
680
685
  ...offlineBrokerFooter.hints,
@@ -714,6 +719,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
714
719
  isPortfolioTab,
715
720
  offlineBrokerFooter,
716
721
  perpetuals.stale,
722
+ quoteDelay,
717
723
  removalCollection,
718
724
  removeSelectedTicker,
719
725
  selectedTicker,
@@ -840,3 +846,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
840
846
  </Box>
841
847
  );
842
848
  }
849
+
850
+ /** The quotes of the rows, in order, for the delayed clock, which stops at the first late one. */
851
+ function* rowQuotes(tickers: readonly TickerRecord[], financialsMap: ReadonlyMap<string, TickerFinancials>) {
852
+ for (const ticker of tickers) yield financialsMap.get(ticker.metadata.ticker)?.quote;
853
+ }
@@ -12,7 +12,7 @@ import { usePluginAppActions } from "../../../runtime";
12
12
  import { isDialogOpen } from "../../../../ui/dialog-stack";
13
13
  import { colors, priceColor } from "../../../../theme/colors";
14
14
  import { formatPercentRaw } from "../../../../utils/format";
15
- import { formatMarketPrice } from "../../../../market-data/market/format";
15
+ import { formatMarketPrice, quoteFormatOptions } from "../../../../market-data/market/format";
16
16
  import { listingChoiceQuery, upsertTickerFromSearchResult } from "../../../../tickers/search";
17
17
  import type { TickerRecord } from "../../../../types/ticker";
18
18
  import { addTickerToPortfolio, addTickerToWatchlist } from "../../../../portfolio/mutations";
@@ -79,7 +79,7 @@ function QuickAddPreview({
79
79
  const assetCategory = validation.ticker?.metadata.assetCategory
80
80
  ?? (validation.resolved.kind === "provider" ? validation.resolved.result.type : undefined);
81
81
  const priceText = quote?.price != null
82
- ? formatMarketPrice(quote.price, { assetCategory, maxWidth: 12 })
82
+ ? formatMarketPrice(quote.price, { ...quoteFormatOptions(quote, assetCategory), maxWidth: 12 })
83
83
  : "-";
84
84
  const changeValue = quote?.changePercent;
85
85
  const name = tickerNameFromValidation(validation);
@@ -2,7 +2,6 @@ import type { PaneTemplateInstanceConfig } from "../../../types/plugin";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { powerBoardCache, powerDetailCache, powerHistoryCache } from "./client";
4
4
  import { powerHeadless } from "./headless";
5
- import { PowerPane } from "./pane";
6
5
  import { carbonHistoryCache, carbonPriceCache, powerPriceHistoryCache, powerPricesCache } from "./prices-client";
7
6
  import { powerPricesHeadless } from "./prices-headless";
8
7
  import { CARBON_SELECTION, CARBON_SERIES, DAY_AHEAD_SERIES, isCarbonQuery, POWER_PRICE_MARKETS, powerPriceMarket } from "./prices-model";
@@ -15,7 +14,7 @@ const CACHES = [powerBoardCache, powerHistoryCache, powerDetailCache, powerPrice
15
14
  export const powerModule: PluginModule = {
16
15
  series: [...DAY_AHEAD_SERIES, ...CARBON_SERIES, ...GAS_STORAGE_SERIES],
17
16
  // POWER sets the symbol param, EPWR the prices tab.
18
- panes: [{ id: "power", name: "Power & Grid", icon: "P", component: PowerPane, defaultPosition: "right", defaultMode: "floating", identitySettings: ["tab", "symbol"],
17
+ panes: [{ id: "power", name: "Power & Grid", icon: "P", get component(): typeof import("./pane").PowerPane { const { PowerPane } = require("./pane"); return PowerPane; }, defaultPosition: "right", defaultMode: "floating", identitySettings: ["tab", "symbol"],
19
18
  defaultFloatingSize: { width: 132, height: 34 }, tableExport: true, headless: powerHeadless }],
20
19
  paneTemplates: [{ id: "power-pane", paneId: "power", label: "Power & Grid",
21
20
  description: "Pro: interconnection queues, historical MW and outcomes, datacenter loads, utility exposure and generation capacity, with primary evidence.",
@@ -1,10 +1,9 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { ratePathCache } from "./client";
3
3
  import { ratePathHeadless } from "./headless";
4
- import { RatePathPane } from "./pane";
5
4
 
6
5
  export const ratePathModule: PluginModule = {
7
- panes: [{ id: "rate-path", name: "US Rate Path", icon: "R", component: RatePathPane,
6
+ panes: [{ id: "rate-path", name: "US Rate Path", icon: "R", get component(): typeof import("./pane").RatePathPane { const { RatePathPane } = require("./pane"); return RatePathPane; },
8
7
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 86, height: 30 },
9
8
  tableExport: true, headless: ratePathHeadless }],
10
9
  paneTemplates: [{
@@ -55,6 +55,8 @@ export function realizedVolHeadless(initialView: "graph" | "cone"): HeadlessPane
55
55
  const lastBar = model.asOf && Number.isFinite(model.asOf.getTime()) ? model.asOf.toISOString().slice(0, 10) : null;
56
56
  return {
57
57
  freshness: { asOf: lastBar },
58
+ // The session still trading is left out of every estimate, and said, as BT says it.
59
+ ...(model.formingNote ? { warnings: [`${model.formingNote}.`] } : {}),
58
60
  sections: [
59
61
  { title: "Volatility cone", columns: [
60
62
  { key: "window", header: "Bars" },
@@ -1,12 +1,11 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
3
- import { RealizedVolPane } from "./pane";
4
3
  import { realizedVolHeadless } from "./headless";
5
4
  import { ESTIMATOR_OPTIONS, WINDOW_OPTIONS } from "./settings";
6
5
 
7
6
  export const realizedVolModule: PluginModule = {
8
7
  panes: [{
9
- id: "realized-vol", name: "Realized Volatility", icon: "V", component: RealizedVolPane,
8
+ id: "realized-vol", name: "Realized Volatility", icon: "V", get component(): typeof import("./pane").RealizedVolPane { const { RealizedVolPane } = require("./pane"); return RealizedVolPane; },
10
9
  defaultPosition: "right", tickerFollower: true, defaultMode: "floating", defaultFloatingSize: { width: 112, height: 36 },
11
10
  tableExport: true,
12
11
  quickSettings: [{ type: "toggle", key: "showIv", icon: "zap", label: "Current ATM IV" }],
@@ -5,6 +5,7 @@ import {
5
5
  type RealizedVolatilityEstimator, type RollingRealizedVolatilityPoint, type VolatilityConeStatistics,
6
6
  } from "../shared/volatility";
7
7
  import type { SurfaceExpiry, SurfaceSnapshot } from "../vol-surface/model";
8
+ import { formingBarsState, isFormingBar } from "../shared/forming-bars";
8
9
 
9
10
  export interface RealizedVolatilityModel {
10
11
  symbol: string;
@@ -30,6 +31,12 @@ export interface RealizedVolatilityModel {
30
31
  * history covers the lookback and every window.
31
32
  */
32
33
  historyNote: string | null;
34
+ /**
35
+ * The newest session left out while it is not final, as BT leaves it out:
36
+ * "2026-10-30 is still forming (partial) and left out: the estimates run
37
+ * through 2026-10-29, the last complete session". Null when every bar is final.
38
+ */
39
+ formingNote: string | null;
33
40
  }
34
41
 
35
42
  /** "the 180-bar window", "the 180- and 260-bar windows", "the 90-, 180- and 260-bar windows". */
@@ -77,8 +84,15 @@ export function projectRealizedVolatility(
77
84
  }
78
85
  dates.set(date.getTime(), { ...point, date });
79
86
  }
80
- const history = [...dates.entries()].sort(([a], [b]) => a - b).map(([, point]) => point);
87
+ const served = [...dates.entries()].sort(([a], [b]) => a - b).map(([, point]) => point);
88
+ // A session still trading moves every estimate it is in until it ends: left out, as BT leaves it out, and said.
89
+ const forming = served.filter(isFormingBar);
90
+ const history = forming.length ? served.filter((point) => !isFormingBar(point)) : served;
81
91
  const asOf = history.at(-1)?.date ?? null;
92
+ const day = (date: Date) => date.toISOString().slice(0, 10);
93
+ const formingNote = forming.length === 0 ? null
94
+ : `${forming.map((point) => day(point.date)).join(", ")} ${formingBarsState(forming)} and left out`
95
+ + `${asOf ? `: the estimates run through ${day(asOf)}, the last complete session` : ""}`;
82
96
  const cutoff = asOf ? lookbackCutoff(asOf, lookbackYears) : 0;
83
97
  const cadenceIssue = realizedVolatilityCadenceIssue(history);
84
98
  if (cadenceIssue) warnings.push(cadenceIssue);
@@ -109,7 +123,7 @@ export function projectRealizedVolatility(
109
123
  : "the long windows' cone covers part of the lookback"}`;
110
124
  return { symbol: options.symbol.trim().toUpperCase(), estimator, lookbackYears, windows,
111
125
  history: visible, series: visibleSeries, cone, asOf, barsPerYear: barsPerYear(history.map((point) => point.date)),
112
- warnings: [...new Set(warnings)], shortWindows, historyNote };
126
+ warnings: [...new Set(warnings)], shortWindows, historyNote, formingNote };
113
127
  }
114
128
 
115
129
  interface CurrentAtmIvReference {
@@ -115,10 +115,10 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
115
115
  symbol: history.data.symbol, estimator, windows, lookbackYears: Number(lookback) === 2 ? 2 : 1,
116
116
  }) : null, [history.data, estimator, windows, lookback]);
117
117
  const chartInput = useMemo(() => model ? { history: model.history, rolling: model.series, windows: model.windows,
118
- priceUnit: priceSeriesUnit(quote?.currency ?? "", quote?.instrumentType), iv: showIv ? iv.data?.reference ?? null : null } : null,
119
- [model, quote?.currency, quote?.instrumentType, showIv, iv.data]);
118
+ priceUnit: priceSeriesUnit(quote?.currency ?? "", quote?.instrumentType, quote?.priceBasis), iv: showIv ? iv.data?.reference ?? null : null } : null,
119
+ [model, quote?.currency, quote?.instrumentType, quote?.priceBasis, showIv, iv.data]);
120
120
  const notices = [identityError, history.error, history.data?.error, iv.error, ivRefresh.error, iv.data?.error,
121
- ...(model?.warnings ?? []), model?.historyNote, ...(showIv ? iv.data?.warnings ?? [] : []),
121
+ ...(model?.warnings ?? []), model?.historyNote, model?.formingNote, ...(showIv ? iv.data?.warnings ?? [] : []),
122
122
  ...(showIv && symbol && !spotAvailable ? ["Current ATM IV unavailable: underlying quote is missing or stale"] : []),
123
123
  ].filter((value): value is string => !!value);
124
124
  usePaneNoticeFooter({ registrationId: "realized-vol-notices", notices: [...new Set(notices)], focused });
@@ -2,7 +2,6 @@ import { isOnPortfolio } from "../../../shares/pane-image";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { rotationCache } from "./client";
4
4
  import { rotationHeadless } from "./headless";
5
- import { RelativeRotationPane } from "./pane";
6
5
  import { DEFAULT_ROTATION_BENCHMARK } from "./model";
7
6
 
8
7
  export const relativeRotationModule: PluginModule = {
@@ -11,7 +10,7 @@ export const relativeRotationModule: PluginModule = {
11
10
  id: "relative-rotation",
12
11
  name: "Relative Rotation",
13
12
  icon: "R",
14
- component: RelativeRotationPane,
13
+ get component(): typeof import("./pane").RelativeRotationPane { const { RelativeRotationPane } = require("./pane"); return RelativeRotationPane; },
15
14
  defaultPosition: "right",
16
15
  defaultMode: "floating",
17
16
  defaultFloatingSize: { width: 100, height: 36 },
@@ -20,6 +20,7 @@ import {
20
20
  recommendationMix,
21
21
  formatRatingLabel,
22
22
  formatRatingTarget,
23
+ ratingReason,
23
24
  recommendationTotal,
24
25
  sortRatingRows,
25
26
  type RatingColumnId,
@@ -43,6 +44,27 @@ const RATING_COLUMNS: HeadlessPaneColumn[] = [
43
44
  { key: "prior", header: "Prior" },
44
45
  ];
45
46
 
47
+ type RatingRow = AnalystResearchData["ratings"][number];
48
+
49
+ /**
50
+ * The reason coverage gave, on its own line under the action with its
51
+ * article; exports keep the line and the article's address as columns. Only
52
+ * a history with a reason somewhere has them.
53
+ */
54
+ const REASON_COLUMNS: HeadlessPaneColumn[] = [
55
+ {
56
+ key: "reason",
57
+ header: "Reason",
58
+ underRow: true,
59
+ format: (_value, row) => ratingReason(row as unknown as RatingRow)?.text ?? "",
60
+ textFormat: (_value, row) => {
61
+ const reason = ratingReason(row as unknown as RatingRow);
62
+ return reason ? `${reason.text} · ${reason.url}` : "";
63
+ },
64
+ },
65
+ { key: "reasonUrl", header: "Reason URL", underRow: true, textFormat: () => "" },
66
+ ];
67
+
46
68
  const SORT_COLUMNS: Record<string, RatingColumnId> = {
47
69
  date: "date",
48
70
  firm: "firm",
@@ -170,14 +192,19 @@ export function createAnalystResearchHeadless(
170
192
  direction: args.options.order === "asc" ? "asc" : "desc",
171
193
  })
172
194
  .slice(0, Number(args.options.limit))
173
- .map((rating) => ({
174
- ...rating,
175
- target: rating.currentPriceTarget ?? rating.priorPriceTarget ?? null,
176
- currency: currency ?? null,
177
- }));
195
+ .map((rating) => {
196
+ const reason = ratingReason(rating);
197
+ return {
198
+ ...rating,
199
+ target: rating.currentPriceTarget ?? rating.priorPriceTarget ?? null,
200
+ currency: currency ?? null,
201
+ ...(reason ? { reasonUrl: reason.url } : {}),
202
+ };
203
+ });
204
+ const columns = ratings.some((rating) => ratingReason(rating)) ? [...RATING_COLUMNS, ...REASON_COLUMNS] : RATING_COLUMNS;
178
205
  const sections: HeadlessBundleSection[] = [
179
206
  { title: "Summary", entries: overviewEntries(data, quote) },
180
- { title: "Recent analyst actions", columns: RATING_COLUMNS, rows: ratings },
207
+ { title: "Recent analyst actions", columns, rows: ratings },
181
208
  ];
182
209
  return {
183
210
  sections,
@@ -235,7 +235,7 @@ export function ratingTargetDelta(row: AnalystResearchData["ratings"][number]):
235
235
  return row.currentPriceTarget - prior;
236
236
  }
237
237
 
238
- export type RatingColumnId = "date" | "firm" | "action" | "current" | "target" | "prior";
238
+ export type RatingColumnId = "date" | "firm" | "action" | "current" | "target" | "prior" | "reason";
239
239
  export type RatingColumn = DataTableColumn & { id: RatingColumnId } & Partial<RatingTargetColumnSizing>;
240
240
 
241
241
  export interface RatingSortPreference {
@@ -255,6 +255,7 @@ const DEFAULT_RATING_SORT_DIRECTIONS: Record<RatingColumnId, SortDirection> = {
255
255
  current: "asc",
256
256
  target: "desc",
257
257
  prior: "asc",
258
+ reason: "asc",
258
259
  };
259
260
 
260
261
  const BASE_RATING_COLUMNS: RatingColumn[] = [
@@ -266,6 +267,36 @@ const BASE_RATING_COLUMNS: RatingColumn[] = [
266
267
  { id: "prior", label: "PRIOR", width: 13, align: "left" },
267
268
  ];
268
269
 
270
+ /** The reason coverage gives for an action: last, taking the room the pane has left. */
271
+ const REASON_COLUMN: RatingColumn = { id: "reason", label: "REASON", width: 28, align: "left", flexGrow: 1 };
272
+
273
+ /** The line coverage of the action quotes, or null when none gives one. */
274
+ export function ratingReason(row: AnalystRatingRecord): AnalystRatingRecord["reason"] | null {
275
+ const reason = row.reason;
276
+ return reason?.text?.trim() && reason.url?.trim() ? reason : null;
277
+ }
278
+
279
+ /**
280
+ * The status bar room the selected action's article takes: its `source`
281
+ * link, the `o` hint that opens it and their separators. The consensus
282
+ * context gives way to it rather than cut it.
283
+ */
284
+ export function ratingReasonFooterWidth(reason: AnalystRatingRecord["reason"] | null): number {
285
+ if (!reason) return 0;
286
+ return displayWidth(`source ${ratingReasonSource(reason)}`) + displayWidth(" · ") + displayWidth("[o]pen") + 2;
287
+ }
288
+
289
+ /** Who published the reason: the publisher's name, else the article's site. */
290
+ export function ratingReasonSource(reason: NonNullable<AnalystRatingRecord["reason"]>): string {
291
+ const named = reason.source?.trim();
292
+ if (named) return named;
293
+ try {
294
+ return new URL(reason.url).hostname.replace(/^www\./, "");
295
+ } catch {
296
+ return "source";
297
+ }
298
+ }
299
+
269
300
  export function buildRatingColumns(
270
301
  rows: readonly AnalystRatingRecord[],
271
302
  currency: string | undefined,
@@ -288,9 +319,11 @@ export function buildRatingColumns(
288
319
  BASE_RATING_COLUMNS.find((column) => column.id === "target")?.width ?? 13,
289
320
  );
290
321
 
291
- return BASE_RATING_COLUMNS.map((column) => {
322
+ const columns = BASE_RATING_COLUMNS.map((column) => {
292
323
  return column.id === "target" ? { ...column, ...targetSizing, width: targetWidth } : column;
293
324
  });
325
+ // A history with no reason anywhere keeps the table it always had.
326
+ return rows.some((row) => ratingReason(row)) ? [...columns, REASON_COLUMN] : columns;
294
327
  }
295
328
 
296
329
  function normalizedText(value: string | undefined): string | null {
@@ -316,6 +349,8 @@ function ratingSortValue(row: AnalystRatingRecord, columnId: RatingColumnId): st
316
349
  return ratingTargetSortValue(row);
317
350
  case "prior":
318
351
  return normalizedText(row.prior);
352
+ case "reason":
353
+ return normalizedText(ratingReason(row)?.text);
319
354
  }
320
355
  }
321
356
 
@@ -8,7 +8,7 @@ import {
8
8
  spanAxisFormatter,
9
9
  staticSeries,
10
10
  useChartTableSelection,
11
- usePaneFooter,
11
+ useExternalLinkFooter,
12
12
  type ChartTableSelection,
13
13
  type DataTableCell,
14
14
  type DataTableKeyEvent,
@@ -39,6 +39,9 @@ import {
39
39
  buildRatingColumns,
40
40
  firstRatingSortDirection,
41
41
  formatRatingTarget,
42
+ ratingReason,
43
+ ratingReasonFooterWidth,
44
+ ratingReasonSource,
42
45
  ratingSplit,
43
46
  ratingTargetDelta,
44
47
  sortRatingRows,
@@ -142,13 +145,16 @@ function AnalystHeader({ data, history, series, currency, width, height, tableRo
142
145
  /**
143
146
  * Streams the bound symbol and keeps the status bar's reference price on it.
144
147
  * Rendered beside the table so a tick redraws the footer, not the ratings.
148
+ * The selected action's reason, when coverage gave one, is a link there.
145
149
  */
146
- function AnalystFooter({ data, loading, error, width, binding }: {
150
+ function AnalystFooter({ data, loading, error, width, binding, focused, reason }: {
147
151
  data: AnalystResearchData | null;
148
152
  loading: boolean;
149
153
  error: string | null;
150
154
  width: number;
151
155
  binding: AnalystQuoteBinding;
156
+ focused: boolean;
157
+ reason: ReturnType<typeof ratingReason>;
152
158
  }) {
153
159
  // The upside is a percentage; about one update a second is enough for it.
154
160
  const financials = useLiveTickerFinancials(binding.ticker ? binding.symbol : null, binding.ticker, {
@@ -157,9 +163,17 @@ function AnalystFooter({ data, loading, error, width, binding }: {
157
163
  weight: 40,
158
164
  });
159
165
  const reference = analystReferencePrice(data, financials?.quote);
160
- usePaneFooter("analyst-research", () => ({
161
- info: buildAnalystFooterInfo(data, { width, loading, error, reference }),
162
- }), [data, error, loading, reference.freshness, reference.live, reference.price, width]);
166
+ const info = useMemo(
167
+ () => buildAnalystFooterInfo(data, { width: width - ratingReasonFooterWidth(reason), loading, error, reference }),
168
+ [data, error, loading, reason, reference.freshness, reference.live, reference.price, width],
169
+ );
170
+ useExternalLinkFooter({
171
+ registrationId: "analyst-research",
172
+ focused,
173
+ url: reason?.url,
174
+ source: reason ? ratingReasonSource(reason) : null,
175
+ info,
176
+ });
163
177
  return null;
164
178
  }
165
179
 
@@ -246,6 +260,9 @@ export function AnalystResearchView({ focused, width, height }: { focused: boole
246
260
  }
247
261
  case "prior":
248
262
  return { text: row.prior ?? "-", color: colors.textDim };
263
+ case "reason":
264
+ // Blank, not a dash: no coverage gave a reason.
265
+ return { text: ratingReason(row)?.text ?? "", color: colors.textDim };
249
266
  }
250
267
  }, [ratingCurrency]);
251
268
 
@@ -258,6 +275,7 @@ export function AnalystResearchView({ focused, width, height }: { focused: boole
258
275
  }));
259
276
  }, []);
260
277
 
278
+ const selectedRow = rows.length > 0 ? rows[selectedIdx] : undefined;
261
279
  const footer = (
262
280
  <AnalystFooter
263
281
  data={authWall ? null : data}
@@ -265,6 +283,8 @@ export function AnalystResearchView({ focused, width, height }: { focused: boole
265
283
  error={authWall ? null : error}
266
284
  width={width}
267
285
  binding={binding}
286
+ focused={focused}
287
+ reason={authWall || !selectedRow ? null : ratingReason(selectedRow)}
268
288
  />
269
289
  );
270
290
 
@@ -1,10 +1,7 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { parseTickerListInput, formatTickerListInput } from "../../../tickers/list";
3
3
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
4
- import { AnalystResearchView } from "./analyst-pane";
5
4
  import { CorporateActionsView } from "./corporate-actions-pane";
6
- import { EquityDiagnosticView } from "./equity-diagnostic-pane";
7
- import { RelativeValuationPane } from "./relative-valuation-pane";
8
5
  import { relativeValuationHeadless } from "./relative-valuation-headless";
9
6
  import { AUTO_PEERS_SETTING, relativeValuationTitle } from "./relative-valuation-peers";
10
7
  import { buildRelativeValuationSettingsDef, MAX_RELATIVE_VALUATION_TICKERS } from "./relative-valuation-settings";
@@ -29,14 +26,14 @@ export const researchModule: PluginModule = {
29
26
  id: "analyst-research",
30
27
  name: "Analyst",
31
28
  order: 32,
32
- component: AnalystResearchView,
29
+ get component(): typeof import("./analyst-pane").AnalystResearchView { const { AnalystResearchView } = require("./analyst-pane"); return AnalystResearchView; },
33
30
  instruments: ["equity"],
34
31
  });
35
32
  ctx.registerTickerResearchTab({
36
33
  id: "equity-diagnostic",
37
34
  name: "Diagnostic",
38
35
  order: 33,
39
- component: EquityDiagnosticView,
36
+ get component(): typeof import("./equity-diagnostic-pane").EquityDiagnosticView { const { EquityDiagnosticView } = require("./equity-diagnostic-pane"); return EquityDiagnosticView; },
40
37
  instruments: ["equity"],
41
38
  });
42
39
  ctx.registerTickerResearchTab({
@@ -53,7 +50,7 @@ export const researchModule: PluginModule = {
53
50
  id: "analyst-research",
54
51
  name: "Analyst Research",
55
52
  icon: "A",
56
- component: AnalystResearchView,
53
+ get component(): typeof import("./analyst-pane").AnalystResearchView { const { AnalystResearchView } = require("./analyst-pane"); return AnalystResearchView; },
57
54
  defaultPosition: "right",
58
55
  tickerFollower: true,
59
56
  instruments: ["equity"],
@@ -68,7 +65,7 @@ export const researchModule: PluginModule = {
68
65
  reportRequirements: ["Free Gloom sign-in"],
69
66
  name: "Equity Diagnostic",
70
67
  icon: "D",
71
- component: EquityDiagnosticView,
68
+ get component(): typeof import("./equity-diagnostic-pane").EquityDiagnosticView { const { EquityDiagnosticView } = require("./equity-diagnostic-pane"); return EquityDiagnosticView; },
72
69
  defaultPosition: "right",
73
70
  tickerFollower: true,
74
71
  instruments: ["equity"],
@@ -91,7 +88,7 @@ export const researchModule: PluginModule = {
91
88
  id: "relative-valuation",
92
89
  name: "Relative Valuation",
93
90
  icon: "R",
94
- component: RelativeValuationPane,
91
+ get component(): typeof import("./relative-valuation-pane").RelativeValuationPane { const { RelativeValuationPane } = require("./relative-valuation-pane"); return RelativeValuationPane; },
95
92
  headless: relativeValuationHeadless,
96
93
  defaultPosition: "right",
97
94
  defaultMode: "floating",