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
@@ -1,6 +1,7 @@
1
1
  import type { HeadlessPaneColumn, HeadlessPaneDefinition, HeadlessPaneRow } from "../../../types/plugin";
2
2
  import type { CdxBoardIndex, CreditIndexQuote } from "../../../api-client/credit-boards";
3
3
  import { CDX_MAX_DAYS, fetchCdxBoard, fetchSovrBoard, loadCurrencyMoves } from "./client";
4
+ import { readsInBatches } from "../../../market-data/batched-reads";
4
5
  import {
5
6
  cdxDay,
6
7
  cdxRows,
@@ -10,6 +11,7 @@ import {
10
11
  formatCurrencyMove,
11
12
  formatLevel,
12
13
  formatMove,
14
+ levelKinds,
13
15
  partialDayText,
14
16
  quoteUnits,
15
17
  sovrRows,
@@ -74,6 +76,8 @@ function historySection(index: CdxBoardIndex, days: number, now: number) {
74
76
  const priced = index.quote === "price" && index.points.some((point) => point.spreadBp !== undefined);
75
77
  const partial = today.partial && latest === index.date;
76
78
  const symbol = cdxSeries(index.id);
79
+ const shown = index.points.filter((point) => point.date > from).reverse();
80
+ const kinds = levelKinds(shown, (point) => point.kind);
77
81
  return {
78
82
  title: `${index.name} 5Y daily ${index.quote === "spread" ? "spread" : "price"}`,
79
83
  columns: [
@@ -83,11 +87,12 @@ function historySection(index: CdxBoardIndex, days: number, now: number) {
83
87
  { key: "prints", header: "Prints", align: "right" as const },
84
88
  { key: "maturity", header: "Contract", textFormat: (value: unknown) => formatContract(typeof value === "string" ? value : null) },
85
89
  ...(partial ? [dayColumn] : []),
90
+ ...kinds.columns,
86
91
  ],
87
- rows: index.points.filter((point) => point.date > from).reverse().map((point) => ({
92
+ rows: shown.map((point) => ({
88
93
  day: point.date, level: point.level, unit,
89
94
  ...(priced ? { spreadBp: point.spreadBp ?? null, spreadUnit: "bp" } : {}),
90
- prints: point.prints, maturity: point.maturity, series: symbol,
95
+ prints: point.prints, maturity: point.maturity, series: symbol, ...kinds.row(point),
91
96
  ...(partial && point.date === latest ? { session: partialDayText(point.prints, today.lastPrintAt), partial: true, lastPrintAt: today.lastPrintAt } : {}),
92
97
  })),
93
98
  rowUnitKey: "unit",
@@ -133,6 +138,7 @@ export const cdxHeadless: HeadlessPaneDefinition<"bundle"> = {
133
138
  const shown = index ? [index] : payload.indexes;
134
139
  // A server that predates the spread equivalent sends none: no column of blanks.
135
140
  const spreads = shown.some((entry) => entry.spreadBp !== undefined);
141
+ const kinds = levelKinds(shown, (entry) => entry.kind);
136
142
  const rows = cdxRows(shown, payload.asOf, now).map((row) => {
137
143
  const units = quoteUnits(row.index.quote);
138
144
  const priced = spreads && row.index.quote === "price";
@@ -151,6 +157,7 @@ export const cdxHeadless: HeadlessPaneDefinition<"bundle"> = {
151
157
  contract: formatContract(row.index.maturity),
152
158
  date: row.index.date,
153
159
  changeDate: row.day.changeDate,
160
+ ...kinds.row(row.index),
154
161
  ...(row.day.partial ? { session: partialDayText(row.index.prints, row.day.lastPrintAt), partial: true, lastPrintAt: row.day.lastPrintAt } : {}),
155
162
  };
156
163
  });
@@ -158,7 +165,7 @@ export const cdxHeadless: HeadlessPaneDefinition<"bundle"> = {
158
165
  return {
159
166
  sections: [
160
167
  {
161
- title: "On the run, 5Y", columns: [...CDX_COLUMNS.filter((column) => spreads || column !== spreadColumn), ...(anyPartial ? [dayColumn] : [])], rows,
168
+ title: "On the run, 5Y", columns: [...CDX_COLUMNS.filter((column) => spreads || column !== spreadColumn), ...(anyPartial ? [dayColumn] : []), ...kinds.columns], rows,
162
169
  rowUnitKey: "unit", rowSymbolKey: "series", rowAsOfKey: "date", interval: "daily",
163
170
  },
164
171
  ...(index ? [historySection(index, days, now)] : []),
@@ -170,6 +177,15 @@ export const cdxHeadless: HeadlessPaneDefinition<"bundle"> = {
170
177
  },
171
178
  };
172
179
 
180
+ const SOVR_COLUMNS: HeadlessPaneColumn[] = [
181
+ { key: "name", header: "Sovereign" },
182
+ levelColumn,
183
+ moveColumn("change1M", "1M", "unit"),
184
+ { key: "currency", header: "Ccy 1M", align: "right" },
185
+ { key: "percentile", header: "Pctl 1Y", align: "right", format: percentile },
186
+ { key: "date", header: "As of" },
187
+ ];
188
+
173
189
  export const sovrHeadless: HeadlessPaneDefinition<"rows"> = {
174
190
  discovery: { aliases: ["SOVR", "WCDS"], dataRequirements: ["Gloom Cloud sovereign CDS endpoint"],
175
191
  limitations: ["Daily 5Y levels from public DTCC prints; thinly traded names can miss days."] },
@@ -178,14 +194,7 @@ export const sovrHeadless: HeadlessPaneDefinition<"rows"> = {
178
194
  argument: { kind: "free-text", optional: true, placeholder: "country",
179
195
  description: "A country (Brazil, BR) to list its row alone; omit for every sovereign." },
180
196
  options: [],
181
- columns: [
182
- { key: "name", header: "Sovereign" },
183
- levelColumn,
184
- moveColumn("change1M", "1M", "unit"),
185
- { key: "currency", header: "Ccy 1M", align: "right" },
186
- { key: "percentile", header: "Pctl 1Y", align: "right", format: percentile },
187
- { key: "date", header: "As of" },
188
- ],
197
+ columns: SOVR_COLUMNS,
189
198
  describe: "Sovereign CDS",
190
199
  async load(args, ctx) {
191
200
  const payload = await fetchSovrBoard(ctx.apiClient);
@@ -193,10 +202,13 @@ export const sovrHeadless: HeadlessPaneDefinition<"rows"> = {
193
202
  const country = query ? findSovereign(payload.sovereigns, query) : null;
194
203
  if (query && !country) throw new Error(`No sovereign ${query}. Use one of: ${payload.sovereigns.map((entry) => entry.name).join(", ")}.`);
195
204
  const sovereigns = country ? [country] : payload.sovereigns;
205
+ const batch = await readsInBatches(ctx.marketData, "getPriceHistory");
196
206
  const moves = await loadCurrencyMoves(sovereigns.map((row) => row.currency),
197
- (symbol) => ctx.marketData.getPriceHistory(symbol, "", "3M"));
207
+ (symbol) => ctx.marketData.getPriceHistory(symbol, "", "3M", batch ? { batch } : undefined), batch);
198
208
  const publisher = boardPublisher(payload.source);
209
+ const kinds = levelKinds(sovereigns, (row) => row.kind);
199
210
  return {
211
+ ...(kinds.columns.length ? { columns: [...SOVR_COLUMNS, ...kinds.columns] } : {}),
200
212
  rows: sovrRows(sovereigns, payload.asOf, moves).map((row) => ({
201
213
  name: row.label,
202
214
  series: sovereignSeries(row.sovereign.id),
@@ -206,6 +218,7 @@ export const sovrHeadless: HeadlessPaneDefinition<"rows"> = {
206
218
  currency: formatCurrencyMove(row.sovereign.currency, row.currencyMove),
207
219
  percentile: rank(row.percentile),
208
220
  date: row.sovereign.date,
221
+ ...kinds.row(row.sovereign),
209
222
  })),
210
223
  rowUnitKey: "unit",
211
224
  rowSymbolKey: "series",
@@ -2,12 +2,14 @@ import type {
2
2
  CdxBoardIndex,
3
3
  CreditBoardPoint,
4
4
  CreditIndexQuote,
5
+ CreditLevelKind,
5
6
  SovrBoardSovereign,
6
7
  } from "../../../api-client/credit-boards";
7
8
  import { spanAxisFormatter, formatBpAxis } from "../../../components/chart-table/axis";
8
9
  import { historyStatistics } from "../../../components/chart/curve/model";
9
10
  import type { MarketBoardRow } from "../../../components/market-board";
10
11
  import type { PricePoint } from "../../../types/financials";
12
+ import type { HeadlessPaneColumn } from "../../../types/plugin";
11
13
 
12
14
  const YEAR_DAYS = 365;
13
15
  /** A level older than this is not the current market. */
@@ -93,6 +95,33 @@ function utcClock(instant: string): string | null {
93
95
  return Number.isFinite(time) ? new Date(time).toISOString().slice(11, 16) : null;
94
96
  }
95
97
 
98
+ /**
99
+ * How a level that is the day's settlement price, not its trades, is marked
100
+ * beside its date.
101
+ */
102
+ export const SETTLEMENT_TEXT = "settlement";
103
+
104
+ /**
105
+ * A report's mark for the levels that are settlement prices: blank for a
106
+ * level from the day's trades. Shown only when a row is one, and each row
107
+ * then carries `kind`.
108
+ */
109
+ const levelKindColumn: HeadlessPaneColumn = {
110
+ key: "kind", header: "Level",
111
+ description: "Blank for a level from the day's trades. settlement for the day's end-of-day settlement price, which stands in "
112
+ + "on a day without enough agreeing trades; its prints are 0.",
113
+ format: (value) => value === "settlement" ? SETTLEMENT_TEXT : "",
114
+ };
115
+
116
+ /** `kind` on every row once one level is a settlement; nothing otherwise, so a report without one is unchanged. */
117
+ export function levelKinds<T>(items: readonly T[], kindOf: (item: T) => CreditLevelKind | null | undefined) {
118
+ const shown = items.some((item) => kindOf(item) === "settlement");
119
+ return {
120
+ columns: shown ? [levelKindColumn] : [],
121
+ row: (item: T) => shown ? { kind: kindOf(item) ?? "traded" } : {},
122
+ };
123
+ }
124
+
96
125
  /**
97
126
  * What a day still trading rests on: `so far, 38 prints, last 08:09 UTC`.
98
127
  * The print time is left out when the server does not say it.
@@ -25,12 +25,14 @@ import { formatAsOf } from "../cds/model";
25
25
  import { FIXED_INCOME_PANE_ID } from "../fixed-income/model";
26
26
  import { useCommandValue } from "../shared/command-tab";
27
27
  import { getCachedSovrBoard, loadCurrencyMoves, loadSovrBoard } from "./client";
28
+ import { readsInBatches } from "../../../market-data/batched-reads";
28
29
  import {
29
30
  findSovereign,
30
31
  formatContract,
31
32
  formatCurrencyMove,
32
33
  formatLevel,
33
34
  formatMove,
35
+ SETTLEMENT_TEXT,
34
36
  sovrRows,
35
37
  type SovrRow,
36
38
  } from "./model";
@@ -65,7 +67,7 @@ function SovereignDetail({ row, width, height, focused }: { row: SovrRow; width:
65
67
  const { sovereign, year } = row;
66
68
  const items: StatItem[] = [
67
69
  { id: "level", label: "5Y spread", value: formatLevel("spread", sovereign.level),
68
- detail: `${formatPercentileRank(year.percentile, "1Y")} · ${sovereign.date}` },
70
+ detail: [formatPercentileRank(year.percentile, "1Y"), sovereign.date, ...(sovereign.kind === "settlement" ? [SETTLEMENT_TEXT] : [])].join(" · ") },
69
71
  { id: "1m", label: "1M", value: formatMove("spread", sovereign.change1M) },
70
72
  { id: "1w", label: "1W", value: formatMove("spread", sovereign.change1W) },
71
73
  { id: "fx", label: "Currency 1M", value: formatCurrencyMove(sovereign.currency, row.currencyMove) },
@@ -99,9 +101,12 @@ export function SovrPane({ focused, width, height }: PaneProps) {
99
101
  // Each currency's month against the dollar, from the same daily closes the FX panes use.
100
102
  const currencies = useMemo(() => [...new Set(payload?.sovereigns.map((row) => row.currency) ?? [])].sort().join(","),
101
103
  [payload]);
102
- const movesLoader = useCallback(() => provider && currencies
103
- ? loadCurrencyMoves(currencies.split(","), (symbol) => provider.getPriceHistory(symbol, "", "3M"))
104
- : Promise.resolve(NO_MOVES), [currencies, provider]);
104
+ const movesLoader = useCallback(async () => {
105
+ if (!provider || !currencies) return NO_MOVES;
106
+ // Every currency at once, in batch requests, from a source that batches them; a few at a time otherwise.
107
+ const batch = await readsInBatches(provider, "getPriceHistory");
108
+ return loadCurrencyMoves(currencies.split(","), (symbol) => provider.getPriceHistory(symbol, "", "3M", batch ? { batch } : undefined), batch);
109
+ }, [currencies, provider]);
105
110
  const moves = useAsyncResource(movesLoader, { keepPreviousData: true });
106
111
  const rows = useMemo(() => payload ? sovrRows(payload.sovereigns, payload.asOf, moves.data ?? NO_MOVES) : EMPTY_ROWS,
107
112
  [moves.data, payload]);
@@ -0,0 +1,143 @@
1
+ import { apiClient } from "../../../api-client";
2
+ import { ApiRequestError } from "../../../api-client/errors";
3
+ import type { BitcoinNetworkDailyValue, BitcoinNetworkPayload } from "../../../api-client/crypto-markets";
4
+ import { createPluginCache } from "../../../data/plugin-cache";
5
+ import type { SeriesAttribution, SeriesDefinition, SeriesPoint } from "../../../series/types";
6
+ import { isFiniteNumber } from "../../../utils/guards";
7
+ import { loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
8
+ import { cachedSeriesNote } from "../shared/series-warning";
9
+
10
+ const bitcoinNetworkCache = createPluginCache<BitcoinNetworkPayload>({
11
+ kind: "bitcoin-network",
12
+ source: "gloom-cloud",
13
+ schemaVersion: 1,
14
+ // The server reads the estimate every half hour and the daily figures every six hours.
15
+ policy: { staleMs: 30 * 60_000, expireMs: 30 * 86_400_000 },
16
+ });
17
+
18
+ export const BITCOIN_NETWORK_CACHES = [bitcoinNetworkCache] as const;
19
+
20
+ const MAX_DAYS = 20_000;
21
+ const date = (value: unknown): value is string => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
22
+ const instant = (value: unknown): value is string => typeof value === "string" && Number.isFinite(Date.parse(value));
23
+ const daily = (rows: unknown): rows is BitcoinNetworkDailyValue[] => Array.isArray(rows) && rows.length <= MAX_DAYS
24
+ && rows.every((row) => !!row && date((row as BitcoinNetworkDailyValue).date) && isFiniteNumber((row as BitcoinNetworkDailyValue).value));
25
+
26
+ function validateBitcoinNetwork(data: BitcoinNetworkPayload): BitcoinNetworkPayload {
27
+ const retarget = data?.retarget;
28
+ if (
29
+ !data
30
+ || data.version !== 1
31
+ || !instant(data.generatedAt)
32
+ || typeof data.source?.name !== "string" || !data.source.name.trim()
33
+ || !["available", "stale", "unavailable"].includes(data.status)
34
+ || !daily(data.hashrate)
35
+ || !daily(data.hashprice)
36
+ || !daily(data.retargetHistory)
37
+ || !Array.isArray(data.difficulty)
38
+ || !data.difficulty.every((row) => !!row && date(row.date) && isFiniteNumber(row.difficulty) && row.difficulty > 0)
39
+ || (retarget !== null && (!retarget || !isFiniteNumber(retarget.changePercent) || !instant(retarget.estimatedAt) || !instant(retarget.readAt)))
40
+ || !Array.isArray(data.warnings)
41
+ ) {
42
+ throw new Error("The server returned invalid bitcoin network data");
43
+ }
44
+ return data;
45
+ }
46
+
47
+ async function fetchBitcoinNetwork(client: Pick<typeof apiClient, "getCloudBitcoinNetwork"> = apiClient): Promise<BitcoinNetworkPayload> {
48
+ try {
49
+ return validateBitcoinNetwork(await client.getCloudBitcoinNetwork());
50
+ } catch (error) {
51
+ if (error instanceof ApiRequestError && error.status === 503) throw unavailableOnServer(error, "Bitcoin network data is temporarily unavailable", [503]);
52
+ throw unavailableOnServer(error, "Bitcoin network data is not available yet.");
53
+ }
54
+ }
55
+
56
+ function loadBitcoinNetwork() {
57
+ return loadCloudResource(bitcoinNetworkCache, "all", () => fetchBitcoinNetwork(), { validate: validateBitcoinNetwork });
58
+ }
59
+
60
+ const points = (rows: readonly BitcoinNetworkDailyValue[]): SeriesPoint[] =>
61
+ rows.map((row) => ({ date: Date.parse(`${row.date}T00:00:00Z`), value: row.value }));
62
+
63
+ const TAGS = ["bitcoin", "BTC", "BTC-USD", "mining", "miner", "miners", "bitcoin mining", "network", "on-chain"];
64
+
65
+ /**
66
+ * A series of the network board: its points, cited as the server names its
67
+ * source, with the board's warnings about this figure (`concerns`: "Hash
68
+ * rate, difficulty and hashprice could not refresh" is three series', "The
69
+ * retarget estimate is unavailable" the estimate's alone) and the
70
+ * cached-copy note.
71
+ */
72
+ function networkSeries(
73
+ definition: Omit<Extract<SeriesDefinition, { load: unknown }>, "load" | "frequency" | "group">
74
+ & { frequency?: SeriesDefinition["frequency"] },
75
+ read: (payload: BitcoinNetworkPayload) => SeriesPoint[],
76
+ { concerns, derived }: { concerns: RegExp; derived?: string },
77
+ ): SeriesDefinition {
78
+ return {
79
+ frequency: "daily",
80
+ group: "Crypto",
81
+ refreshMs: 30 * 60_000,
82
+ ...definition,
83
+ load: async () => {
84
+ const resource = await loadBitcoinNetwork();
85
+ const found = read(resource.payload);
86
+ if (!found.length) throw new Error(`${definition.name} is not available yet.`);
87
+ const note = cachedSeriesNote(resource);
88
+ const own = resource.payload.warnings.filter((line) => concerns.test(line)).join(" ");
89
+ const warning = note.warning ?? (own || undefined);
90
+ const source = resource.payload.source.name.trim();
91
+ const attribution: SeriesAttribution = { source: derived ? `${source}, ${derived}` : source };
92
+ return { points: found, attribution, ...note, ...warning ? { warning } : {} };
93
+ },
94
+ } as SeriesDefinition;
95
+ }
96
+
97
+ /**
98
+ * The bitcoin network for `fn G`: hash rate, difficulty, the next
99
+ * retarget's estimate and hashprice. `fn G hashrate`, `difficulty`,
100
+ * `retarget` and `hashprice` chart them by their aliases; `catalog
101
+ * hashrate`, `hashprice`, `difficulty`, `mining` and `miner` find them by
102
+ * their tags.
103
+ */
104
+ export const BITCOIN_NETWORK_SERIES: readonly SeriesDefinition[] = [
105
+ networkSeries({
106
+ id: "bitcoin:hashrate",
107
+ name: "Bitcoin network hash rate",
108
+ unit: "EH/s",
109
+ tags: [...TAGS, "hashrate", "hash rate"],
110
+ aliases: ["hashrate"],
111
+ description: "The bitcoin network's hash rate in exahashes per second, each UTC day's average, estimated from the blocks found; the newest day is the day so far.",
112
+ }, (payload) => points(payload.hashrate), { concerns: /\bhash rate\b/i }),
113
+ networkSeries({
114
+ id: "bitcoin:difficulty",
115
+ name: "Bitcoin mining difficulty (trillions)",
116
+ unit: "number",
117
+ frequency: "irregular",
118
+ style: "step",
119
+ tags: [...TAGS, "difficulty", "retarget", "difficulty adjustment"],
120
+ aliases: ["difficulty"],
121
+ description: "The difficulty each retarget set, every 2,016 blocks, in trillions, dated by the block that set it.",
122
+ }, (payload) => payload.difficulty.map((row) => ({ date: Date.parse(`${row.date}T00:00:00Z`), value: Number((row.difficulty / 1e12).toFixed(4)) })),
123
+ { concerns: /\bdifficulty\b/i }),
124
+ networkSeries({
125
+ id: "bitcoin:retarget-estimate",
126
+ name: "Bitcoin next difficulty retarget estimate",
127
+ unit: "%",
128
+ tags: [...TAGS, "difficulty", "retarget", "difficulty adjustment", "estimate"],
129
+ aliases: ["retarget"],
130
+ description: "The estimated change in difficulty at the next retarget, in percent, as read each UTC day since Gloom began keeping it in October 2026.",
131
+ }, (payload) => points(payload.retargetHistory), { concerns: /\bretarget\b/i }),
132
+ networkSeries({
133
+ id: "bitcoin:hashprice",
134
+ name: "Bitcoin hashprice (derived)",
135
+ unit: "USD/PH/s/day",
136
+ tags: [...TAGS, "hashprice", "hash price", "miner revenue"],
137
+ aliases: ["hashprice"],
138
+ description: "Derived, not quoted: what 1 PH/s is expected to earn in a day, in USD, at the day's difficulty, average block reward (subsidy and fees) and BTC price.",
139
+ }, (payload) => points(payload.hashprice), {
140
+ concerns: /\bhashprice\b/i,
141
+ derived: "derived from the block rewards, the BTC price and the difficulty",
142
+ }),
143
+ ];
@@ -31,15 +31,16 @@ const COIN_COLUMNS: HeadlessPaneColumn[] = [
31
31
  { key: "rank", header: "#", align: "right" },
32
32
  { key: "code", header: "Coin" },
33
33
  { key: "name", header: "Name" },
34
- { key: "price", header: "Price", align: "right", format: (_value, row) => String(row.priceText ?? "—") },
34
+ // Every coin is priced, traded and valued in US dollars: the text headers say so; an export keeps its headers.
35
+ { key: "price", header: "Price", textHeader: "Price USD", align: "right", format: (_value, row) => String(row.priceText ?? "—") },
35
36
  // Since 00:00 UTC, the day `quote` gives a coin (CRYPTO_CHANGE_BASIS_LABEL): the text says so, as the pane's
36
37
  // CHG% UTC does, and an export keeps the header it has always had.
37
38
  { key: "changePercent", header: "Chg %", textHeader: "Chg % UTC", align: "right", format: percent },
38
39
  { key: "return7d", header: "7D %", align: "right", format: percent },
39
40
  { key: "return30d", header: "30D %", align: "right", format: percent },
40
41
  { key: "return1y", header: "1Y %", align: "right", format: percent },
41
- { key: "volume24h", header: "Vol 24h", align: "right", format: compact },
42
- { key: "marketCap", header: "Market cap", align: "right", format: compact },
42
+ { key: "volume24h", header: "Vol 24h", textHeader: "Vol 24h USD", align: "right", format: compact },
43
+ { key: "marketCap", header: "Market cap", textHeader: "Market cap USD", align: "right", format: compact },
43
44
  ];
44
45
 
45
46
  /**
@@ -1,8 +1,8 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { LIVE_STREAMING_QUICK_SETTING, withLiveStreamingSetting } from "../../../state/hooks/live-streaming";
3
3
  import { cryptoMarketsCache, DEFI_TVL_CACHES, STABLECOIN_SUPPLY_CACHES } from "./client";
4
+ import { BITCOIN_NETWORK_CACHES } from "./bitcoin-network";
4
5
  import { cryptoBoardHeadless } from "./headless";
5
- import { CryptoBoardPane } from "./pane";
6
6
  import { CRYPTO_SERIES } from "./series";
7
7
 
8
8
  export const cryptoBoardModule: PluginModule = {
@@ -12,7 +12,7 @@ export const cryptoBoardModule: PluginModule = {
12
12
  id: "crypto-board",
13
13
  name: "Crypto",
14
14
  icon: "C",
15
- component: CryptoBoardPane,
15
+ get component(): typeof import("./pane").CryptoBoardPane { const { CryptoBoardPane } = require("./pane"); return CryptoBoardPane; },
16
16
  defaultPosition: "right",
17
17
  defaultMode: "floating",
18
18
  defaultFloatingSize: { width: 120, height: 32 },
@@ -40,10 +40,12 @@ export const cryptoBoardModule: PluginModule = {
40
40
  cryptoMarketsCache.attach(ctx.persistence);
41
41
  for (const cache of STABLECOIN_SUPPLY_CACHES) cache.attach(ctx.persistence);
42
42
  for (const cache of DEFI_TVL_CACHES) cache.attach(ctx.persistence);
43
+ for (const cache of BITCOIN_NETWORK_CACHES) cache.attach(ctx.persistence);
43
44
  },
44
45
  dispose() {
45
46
  cryptoMarketsCache.reset();
46
47
  for (const cache of STABLECOIN_SUPPLY_CACHES) cache.reset();
47
48
  for (const cache of DEFI_TVL_CACHES) cache.reset();
49
+ for (const cache of BITCOIN_NETWORK_CACHES) cache.reset();
48
50
  },
49
51
  };
@@ -1,6 +1,7 @@
1
1
  import type { SeriesDefinition, SeriesPoint } from "../../../series/types";
2
2
  import { cachedSeriesNote } from "../shared/series-warning";
3
3
  import { loadDefiTvl, loadStablecoinSupply, loadStablecoinSupplyHistory } from "./client";
4
+ import { BITCOIN_NETWORK_SERIES } from "./bitcoin-network";
4
5
 
5
6
  const DAY_MS = 86_400_000;
6
7
 
@@ -114,4 +115,5 @@ export const CRYPTO_SERIES: readonly SeriesDefinition[] = [
114
115
  },
115
116
  ...STABLECOIN_SUPPLY_SERIES,
116
117
  DEFI_TVL_SERIES,
118
+ ...BITCOIN_NETWORK_SERIES,
117
119
  ];
@@ -18,7 +18,9 @@ const textOrNull = (value: unknown) => value === null || text(value);
18
18
  /**
19
19
  * The flows as served, or an error: the asset asked for, its funds once each,
20
20
  * every day newest first with a figure or none for each fund, and where they
21
- * are from. A payload that breaks any of it is not drawn.
21
+ * are from. A payload that breaks any of it is not drawn. A day's price move
22
+ * that is not a number or null is dropped from that day alone: the flows
23
+ * stand without it.
22
24
  */
23
25
  export function validateCryptoEtfFlows(payload: CryptoEtfFlowsPayload, asset: CryptoEtfFlowAsset): CryptoEtfFlowsPayload {
24
26
  const invalid = (): never => { throw new Error("The server returned invalid crypto ETF flows"); };
@@ -35,7 +37,12 @@ export function validateCryptoEtfFlows(payload: CryptoEtfFlowsPayload, asset: Cr
35
37
  || !Array.isArray(day.pending) || day.pending.some((ticker) => !known.has(ticker))) return invalid();
36
38
  }
37
39
  if (payload.days[0] && payload.days[0].date !== payload.asOf) return invalid();
38
- return payload;
40
+ if (payload.days.every((day) => day.priceChangePct === undefined || flow(day.priceChangePct))) return payload;
41
+ return { ...payload, days: payload.days.map((day) => {
42
+ if (day.priceChangePct === undefined || flow(day.priceChangePct)) return day;
43
+ const { priceChangePct: _dropped, ...rest } = day;
44
+ return rest;
45
+ }) };
39
46
  }
40
47
 
41
48
  export async function fetchCryptoEtfFlows(
@@ -2,6 +2,7 @@ import type { CryptoEtfFlowsPayload } from "../../../api-client/crypto-etf-flows
2
2
  import type { HeadlessPaneColumn } from "../../../types/headless";
3
3
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
4
4
  import { REPORT_ROWS } from "../shared/headless-options";
5
+ import { readPriceWindow, type PriceWindow } from "../../../utils/price-window";
5
6
  import { fetchCryptoEtfFlows } from "./client";
6
7
  import {
7
8
  cryptoEtfFlowAsset,
@@ -9,16 +10,22 @@ import {
9
10
  cryptoEtfFlowSummary,
10
11
  formatFlow,
11
12
  formatFlowAmount,
13
+ formatPriceChange,
14
+ hasFundPriceChanges,
12
15
  pendingText,
13
16
  } from "./model";
14
17
 
15
18
  const flowCell = (value: unknown) => formatFlow(typeof value === "number" ? value : null);
19
+ const optionText = (value: unknown) => typeof value === "string" && value.trim() ? value : undefined;
16
20
 
17
- /** Date, the day's total and each fund, in US$ millions as published. */
21
+ /** Date, the day's total, the funds' price move when served, and each fund, in US$ millions as published. */
18
22
  function flowColumns(payload: CryptoEtfFlowsPayload): HeadlessPaneColumn[] {
19
23
  return [
20
24
  { key: "date", header: "Date", shrink: false },
21
25
  { key: "total", header: "Total", align: "right", format: flowCell },
26
+ ...hasFundPriceChanges(payload)
27
+ ? [{ key: "priceChangePct", header: "Price %", literalHeader: true, align: "right" as const, format: (value: unknown) => formatPriceChange(typeof value === "number" ? value : null) }]
28
+ : [],
22
29
  // A fund the day still waits on reads "pending", as in the pane; none at all is a dash.
23
30
  ...payload.funds.map((fund): HeadlessPaneColumn => ({
24
31
  key: fund.ticker, header: fund.ticker, literalHeader: true, align: "right",
@@ -28,6 +35,13 @@ function flowColumns(payload: CryptoEtfFlowsPayload): HeadlessPaneColumn[] {
28
35
  ];
29
36
  }
30
37
 
38
+ /** "Net flows of the US spot bitcoin ETFs in US$ millions. 2026-10-19 to 2026-10-23: +$301.20M over 5 trading days." */
39
+ function windowSummaryLine(payload: CryptoEtfFlowsPayload, window: PriceWindow, days: CryptoEtfFlowsPayload["days"]): string {
40
+ const total = days.some((day) => day.total != null) ? days.reduce((sum, day) => sum + (day.total ?? 0), 0) : null;
41
+ return `Net flows of the US spot ${cryptoEtfFlowAssetLabel(payload.asset).toLowerCase()} ETFs in US$ millions. ${window.from} to ${window.to}: `
42
+ + `${formatFlowAmount(total)} over ${days.length} trading ${days.length === 1 ? "day" : "days"}.`;
43
+ }
44
+
31
45
  /** "Net flows of the US spot bitcoin ETFs in US$ millions. 2026-10-20: -$6.70M (IBIT pending); 5 days ..., since 2024-01-11 +$57.47B." */
32
46
  function summaryLine(payload: CryptoEtfFlowsPayload): string {
33
47
  const summary = cryptoEtfFlowSummary(payload);
@@ -43,6 +57,15 @@ function summaryLine(payload: CryptoEtfFlowsPayload): string {
43
57
  ].join(" ");
44
58
  }
45
59
 
60
+ /**
61
+ * "Funds: IBIT iShares Bitcoin Trust ETF, ..., BTC Grayscale Bitcoin Mini Trust ETF.": a column is headed by
62
+ * the fund's ticker, and BTC or ETH there is Grayscale's mini fund, not the coin. Null when no fund is named.
63
+ */
64
+ function fundsLine(payload: CryptoEtfFlowsPayload): string | null {
65
+ const named = payload.funds.flatMap((fund) => fund.name ? [`${fund.ticker} ${fund.name}`] : []);
66
+ return named.length ? `Funds: ${named.join(", ")}.` : null;
67
+ }
68
+
46
69
  export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
47
70
  shape: "rows",
48
71
  argument: { kind: "none" },
@@ -69,6 +92,22 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
69
92
  minimum: 1,
70
93
  maximum: 2000,
71
94
  },
95
+ {
96
+ key: "from",
97
+ type: "string",
98
+ placeholder: "year|date",
99
+ description: "First US trading day listed, as a year (2024) or a date (2026-10-19); lists every day of the window in place of --limit.",
100
+ example: "--from 2026-10-19 --to 2026-10-23",
101
+ shot: { reportOnly: "lists the report's days for those dates" },
102
+ replaces: ["limit"],
103
+ },
104
+ {
105
+ key: "to",
106
+ type: "string",
107
+ placeholder: "year|date",
108
+ description: "Last US trading day listed, with --from (default the newest).",
109
+ shot: { reportOnly: "lists the report's days for those dates" },
110
+ },
72
111
  ],
73
112
  columns: [
74
113
  { key: "date", header: "Date" },
@@ -81,24 +120,36 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
81
120
  limitations: [
82
121
  "US spot bitcoin and ether ETFs only, net creations and redemptions in US$ millions as Farside Investors publishes them.",
83
122
  "The newest day fills in fund by fund; a fund that has not reported shows as pending.",
123
+ "0.0 is the publisher's zero for the fund that day; a dash is a day it published no figure for.",
124
+ "Price % is the funds' price move that day: the median of their close-to-close changes.",
84
125
  ],
85
126
  },
86
- freshness: { source: "Farside Investors", publisher: "Farside Investors", status: "not-a-feed", basis: "daily fund flows", cadence: "daily", tradingDayMarket: "US" },
127
+ // Stale once a trading day's flows are missing (the load's `cadence`), except in a window that ended before the newest day.
128
+ freshness: { source: "Farside Investors", publisher: "Farside Investors", status: "not-a-feed", basis: "daily fund flows", tradingDayMarket: "US" },
87
129
  async load(args, ctx) {
88
130
  const asset = cryptoEtfFlowAsset(args.options.asset);
131
+ // --from 2026-10-19 --to 2026-10-23 lists those days, every one of them, in place of the newest --limit.
132
+ const window = readPriceWindow(optionText(args.options.from), optionText(args.options.to));
89
133
  const payload = await fetchCryptoEtfFlows(asset, ctx.apiClient);
90
134
  const limit = Math.max(1, Number(args.options.limit) || 20);
91
- const rows = payload.days.slice(0, limit).map((day) => ({
135
+ const days = window ? payload.days.filter((day) => day.date >= window.from && day.date <= window.to) : payload.days.slice(0, limit);
136
+ const priced = hasFundPriceChanges(payload);
137
+ const rows = days.map((day) => ({
92
138
  date: day.date,
93
139
  total: day.total,
140
+ ...(priced ? { priceChangePct: day.priceChangePct ?? null } : {}),
94
141
  ...Object.fromEntries(payload.funds.map((fund) => [fund.ticker, day.flows[fund.ticker] ?? null])),
95
142
  pending: day.pending,
96
143
  }));
97
144
  const summary = cryptoEtfFlowSummary(payload);
145
+ const funds = fundsLine(payload);
146
+ // A window that ends before the newest day is dated by its own last day.
147
+ const asOf = window ? days[0]?.date ?? null : payload.asOf;
98
148
  return {
99
149
  rows,
100
150
  columns: flowColumns(payload),
101
- notes: [summaryLine(payload), `Source: ${payload.source.publisher}, ${payload.source.url}`],
151
+ ...(window && !days.length ? { errors: [`No US trading day with flows from ${window.from} to ${window.to}.`] } : {}),
152
+ notes: [window ? windowSummaryLine(payload, window, days) : summaryLine(payload), ...funds ? [funds] : [], `Source: ${payload.source.publisher}, ${payload.source.url}`],
102
153
  complete: !payload.stale,
103
154
  ...(payload.stale ? { warnings: [`The flows were last read ${payload.fetchedAt.slice(0, 10)}; newer days may be missing.`] } : {}),
104
155
  freshness: {
@@ -106,10 +157,12 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
106
157
  publisher: payload.source.publisher,
107
158
  status: "not-a-feed",
108
159
  basis: "daily fund flows",
109
- cadence: "daily",
160
+ ...(window && asOf !== payload.asOf ? {} : { cadence: "daily" as const }),
110
161
  tradingDayMarket: "US",
111
- asOf: payload.asOf,
162
+ ...(asOf ? { asOf } : {}),
112
163
  oldest: null,
164
+ // When the server last read the publisher's table, which is what this report retrieved.
165
+ retrievedAt: payload.fetchedAt,
113
166
  },
114
167
  metadata: {
115
168
  asset,
@@ -117,6 +170,8 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
117
170
  asOf: payload.asOf,
118
171
  fetchedAt: payload.fetchedAt,
119
172
  stale: payload.stale,
173
+ ...(window ? { requestedWindow: window } : {}),
174
+ ...(priced ? { priceChangeBasis: "median of the funds' close-to-close price changes, percent" } : {}),
120
175
  source: payload.source,
121
176
  funds: payload.funds,
122
177
  summary: {
@@ -2,7 +2,6 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { cryptoEtfFlowsCache } from "./client";
3
3
  import { cryptoEtfFlowsHeadless } from "./headless";
4
4
  import { CRYPTO_ETF_FLOWS_PANE_ID } from "./model";
5
- import { CryptoEtfFlowsPane } from "./pane";
6
5
 
7
6
  /** ETFF: the daily net flows of the US spot bitcoin and ether ETFs, per fund. */
8
7
  export const cryptoEtfFlowsModule: PluginModule = {
@@ -11,7 +10,7 @@ export const cryptoEtfFlowsModule: PluginModule = {
11
10
  id: CRYPTO_ETF_FLOWS_PANE_ID,
12
11
  name: "Crypto ETF Flows",
13
12
  icon: "F",
14
- component: CryptoEtfFlowsPane,
13
+ get component(): typeof import("./pane").CryptoEtfFlowsPane { const { CryptoEtfFlowsPane } = require("./pane"); return CryptoEtfFlowsPane; },
15
14
  defaultPosition: "right",
16
15
  defaultMode: "floating",
17
16
  defaultFloatingSize: { width: 120, height: 32 },
@@ -103,11 +103,24 @@ export function pendingText(day: CryptoEtfFlowDay | null | undefined): string |
103
103
 
104
104
  export type CryptoEtfFlowColumn = Omit<DataTableColumn, "id"> & { id: string };
105
105
 
106
- /** Date and the day's total, then each fund in the publisher's order. */
106
+ /** Whether the server sent the funds' daily price move beside the flows. */
107
+ export const hasFundPriceChanges = (payload: CryptoEtfFlowsPayload | null | undefined) =>
108
+ !!payload?.days.some((day) => day.priceChangePct !== undefined);
109
+
110
+ /** A day's price move as the table shows it: `+2.15%`, `-0.40%`, `—` for none. */
111
+ export function formatPriceChange(value: number | null | undefined): string {
112
+ if (value == null || !Number.isFinite(value)) return "—";
113
+ return `${value > 0 ? "+" : ""}${value.toFixed(2)}%`;
114
+ }
115
+
116
+ /** Date, the day's total and the funds' price move that day, then each fund in the publisher's order. */
107
117
  export function cryptoEtfFlowColumns(payload: CryptoEtfFlowsPayload | null | undefined): CryptoEtfFlowColumn[] {
108
118
  return [
109
119
  { id: "date", label: "Date", width: 12, align: "left" },
110
120
  { id: "total", label: "Total", width: 9, align: "right", title: "The day's net flow across the funds, US$ millions" },
121
+ ...hasFundPriceChanges(payload)
122
+ ? [{ id: "price", label: "Price %", width: 9, align: "right" as const, title: "The funds' price move that day: the median of their close-to-close changes" }]
123
+ : [],
111
124
  ...(payload?.funds ?? []).map((fund): CryptoEtfFlowColumn => ({
112
125
  id: `fund:${fund.ticker}`,
113
126
  label: fund.ticker,