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
@@ -3,24 +3,29 @@ import { apiClient } from "../../../api-client";
3
3
  import { t, tf } from "../../../i18n";
4
4
  import { useAppLanguage } from "../../../i18n/react";
5
5
  import { Button } from "../../../components/ui/button";
6
- import { useCommandBarShortcut } from "../../../ui";
6
+ import { DelayedMark } from "../../../components/ui/delayed-mark";
7
+ import { Box, useCommandBarShortcut, useUiCapabilities } from "../../../ui";
7
8
  import { chatController, type ChatController } from "../chat/controller";
8
9
  import { useCloudPlanAction, useCloudUpgradeAction } from "../shared/cloud-upgrade";
9
- import { resolvePlanAccess } from "../../../api-client/plan-access";
10
+ import { CLOUD_QUOTE_DELAY_MINUTES, resolvePlanAccess } from "../../../api-client/plan-access";
11
+ import { useTrialOfferShowing } from "./trial-offer-status-widget";
10
12
 
11
13
  interface CloudUpgradeStatusWidgetProps {
12
14
  controller?: Pick<ChatController, "getSnapshot" | "subscribe">;
13
15
  }
14
16
 
15
17
  /**
16
- * Global entitlement status: the trial countdown while Pro is on loan, or the
17
- * delay free accounts get on cloud data. Signed-out users already have sign-in
18
- * affordances next to it, and paying subscribers have nothing to report.
18
+ * Global entitlement status: the trial countdown while Pro is on loan, or,
19
+ * signed out or on the free plan, the delayed mark the app's prices carry and
20
+ * the upgrade. While the trial offer line already pitches Pro, the mark stands
21
+ * alone. Pro accounts have nothing to report.
19
22
  */
20
23
  export function CloudUpgradeStatusWidget({ controller = chatController }: CloudUpgradeStatusWidgetProps) {
21
24
  useAppLanguage();
22
25
  const openUpgrade = useCloudUpgradeAction("status-widget");
23
26
  const openPlan = useCloudPlanAction();
27
+ const offerShowing = useTrialOfferShowing();
28
+ const { nativePaneChrome } = useUiCapabilities();
24
29
  // The status bar takes no keyboard focus; the chip names the command instead.
25
30
  const commandBarKey = useCommandBarShortcut();
26
31
  const [access, setAccess] = useState(() => resolvePlanAccess(apiClient.getCurrentUser()));
@@ -31,15 +36,34 @@ export function CloudUpgradeStatusWidget({ controller = chatController }: CloudU
31
36
  [controller],
32
37
  );
33
38
 
34
- if (!access.signedIn || access.isPayingPro) return null;
39
+ if (access.isTrialActive) {
40
+ return (
41
+ <Button
42
+ label={tf("Pro trial {days}d", { days: access.trialDaysLeft })}
43
+ title={tf("Your plan ({key}, then {command})", { key: commandBarKey, command: "ACM" })}
44
+ variant="plain"
45
+ compact
46
+ stopPropagation
47
+ onPress={openPlan}
48
+ />
49
+ );
50
+ }
51
+ if (access.hasProAccess) return null;
35
52
 
36
- const trial = access.isTrialActive;
37
- const label = trial
38
- ? tf("Pro trial {days}d", { days: access.trialDaysLeft })
39
- : t("delayed data · upgrade");
40
-
41
- const title = trial
42
- ? tf("Your plan ({key}, then {command})", { key: commandBarKey, command: "ACM" })
43
- : tf("Upgrade to Pro ({key}, then {command})", { key: commandBarKey, command: "UPGRADE" });
44
- return <Button label={label} title={title} variant="plain" compact stopPropagation onPress={trial ? openPlan : openUpgrade} />;
53
+ const mark = <DelayedMark minutes={CLOUD_QUOTE_DELAY_MINUTES} />;
54
+ return (
55
+ <Box flexDirection="row" alignItems="center" gap={1} paddingRight={offerShowing || !nativePaneChrome ? 1 : 0} data-gloom-role="delayed-status">
56
+ {mark}
57
+ {offerShowing ? null : (
58
+ <Button
59
+ label={t("delayed data · upgrade")}
60
+ title={tf("Upgrade to Pro ({key}, then {command})", { key: commandBarKey, command: "UPGRADE" })}
61
+ variant="plain"
62
+ compact
63
+ stopPropagation
64
+ onPress={openUpgrade}
65
+ />
66
+ )}
67
+ </Box>
68
+ );
45
69
  }
@@ -1,5 +1,4 @@
1
1
  import type { GloomPlugin, PaneTemplateCreateOptions } from "../../../types/plugin";
2
- import { CommentLettersPane } from "./pane";
3
2
  import { COMMENT_LETTERS_PANE_ID, COMMENT_LETTERS_PLUGIN_ID } from "./types";
4
3
 
5
4
  const createCommentLettersPaneInstance = (options?: PaneTemplateCreateOptions) => {
@@ -37,7 +36,7 @@ export const commentLettersPlugin: GloomPlugin = {
37
36
  reportFreshness: { source: "SEC EDGAR", status: "not-a-feed", basis: "comment letters" },
38
37
  name: "Comment Letters",
39
38
  icon: "L",
40
- component: CommentLettersPane,
39
+ get component(): typeof import("./pane").CommentLettersPane { const { CommentLettersPane } = require("./pane"); return CommentLettersPane; },
41
40
  defaultPosition: "right",
42
41
  defaultMode: "floating",
43
42
  defaultFloatingSize: { width: 100, height: 30 },
@@ -2,7 +2,6 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { commandRequestParams } from "../shared/command-tab";
3
3
  import { commodityBalancesHeadless } from "./headless";
4
4
  import { PSD_CROPS, psdCrop, unknownCropMessage } from "./model";
5
- import { CommodityBalancesPane } from "./pane";
6
5
 
7
6
  export const commodityBalancesModule: PluginModule = {
8
7
  panes: [
@@ -10,7 +9,7 @@ export const commodityBalancesModule: PluginModule = {
10
9
  id: "commodity-balances",
11
10
  name: "Crop Balances",
12
11
  icon: "B",
13
- component: CommodityBalancesPane,
12
+ get component(): typeof import("./pane").CommodityBalancesPane { const { CommodityBalancesPane } = require("./pane"); return CommodityBalancesPane; },
14
13
  defaultPosition: "right",
15
14
  defaultMode: "floating",
16
15
  defaultFloatingSize: { width: 108, height: 28 },
@@ -25,7 +24,8 @@ export const commodityBalancesModule: PluginModule = {
25
24
  paneId: "commodity-balances",
26
25
  label: "Crop Balances",
27
26
  description: "World balances for wheat, corn, soybeans, cotton, coffee and sugar, with season-average price forecasts when a table is published.",
28
- keywords: ["wheat", "corn", "soybeans", "cotton", "coffee", "sugar", "crop", "balances", "production", "exports", "stocks", "harvest"],
27
+ keywords: ["wheat", "corn", "soybeans", "cotton", "coffee", "sugar", "crop", "balances", "production", "exports", "stocks", "harvest",
28
+ "usda", "wasde", "grain", "grains", "fertilizer", "fertiliser", "potash"],
29
29
  shortcut: {
30
30
  prefix: "PSD",
31
31
  argKind: "text",
@@ -9,7 +9,7 @@ import {
9
9
  type DataTableColumn,
10
10
  } from "../../../components";
11
11
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
12
- import type { PaneFooterSegment } from "../../../components/layout/pane/footer";
12
+ import { ageFooterSegment, type PaneFooterSegment } from "../../../components/layout/pane/footer";
13
13
  import { useAsyncResource } from "../../../react/async-resource";
14
14
  import { useAutoRefresh, useUpdatedAgo } from "../../../react/auto-refresh";
15
15
  import { usePaneInstance } from "../../../state/app/context";
@@ -96,7 +96,7 @@ export function CommodityBalancesPane({ width, height, focused }: PaneProps) {
96
96
  const updatedAgo = useUpdatedAgo(updatedAt);
97
97
  const info = useMemo<PaneFooterSegment[]>(() => {
98
98
  const year = data?.marketYear;
99
- const updated: PaneFooterSegment[] = updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" }] }] : [];
99
+ const updated: PaneFooterSegment[] = updatedAgo ? [ageFooterSegment(updatedAgo)] : [];
100
100
  return year ? [...updated, { id: "year", parts: [{ text: year, tone: "muted" }] }] : updated;
101
101
  }, [data?.marketYear, updatedAgo]);
102
102
 
@@ -1,5 +1,11 @@
1
1
  import type { CloudCongressMemberPayload, CloudCongressTickerPayload, CloudCongressTradePayload } from "../../../api-client";
2
2
 
3
+ function median(values: number[]): number | null {
4
+ if (!values.length) return null;
5
+ const sorted = [...values].sort((a, b) => a - b);
6
+ return (sorted[Math.floor((sorted.length - 1) / 2)]! + sorted[Math.floor(sorted.length / 2)]!) / 2;
7
+ }
8
+
3
9
  /** Aggregates exactly the distinct trades loaded, including appended years. */
4
10
  export function aggregateLoadedCongress(trades: CloudCongressTradePayload[], metadata: CloudCongressMemberPayload[] = []) {
5
11
  const members = new Map<string, CloudCongressMemberPayload>();
@@ -7,6 +13,7 @@ export function aggregateLoadedCongress(trades: CloudCongressTradePayload[], met
7
13
  const lags = new Map<string, number[]>();
8
14
  const returns = new Map<string, number[]>();
9
15
  const buys = new Map<string, number[]>();
16
+ const excess = new Map<string, number[]>();
10
17
  const known = new Map(metadata.map((member) => [member.id, member]));
11
18
  const seen = new Set<string>();
12
19
  for (const trade of trades) {
@@ -22,6 +29,7 @@ export function aggregateLoadedCongress(trades: CloudCongressTradePayload[], met
22
29
  lags.set(memberId, []);
23
30
  returns.set(memberId, []);
24
31
  buys.set(memberId, []);
32
+ excess.set(memberId, []);
25
33
  }
26
34
  const summaries: (CloudCongressMemberPayload | CloudCongressTickerPayload)[] = [member];
27
35
  if (trade.ticker) {
@@ -39,6 +47,7 @@ export function aggregateLoadedCongress(trades: CloudCongressTradePayload[], met
39
47
  returns.get(memberId)!.push(trade.returnSinceTx);
40
48
  if (trade.side === "BUY") buys.get(memberId)!.push(trade.returnSinceTx);
41
49
  }
50
+ if (trade.excessSinceFiling != null && Number.isFinite(trade.excessSinceFiling)) excess.get(memberId)!.push(trade.excessSinceFiling);
42
51
  if (!member.lastFilingDate || trade.filingDate >= member.lastFilingDate) {
43
52
  member.party = trade.party ?? member.party ?? null;
44
53
  member.committees = trade.committees ?? member.committees ?? [];
@@ -57,11 +66,13 @@ export function aggregateLoadedCongress(trades: CloudCongressTradePayload[], met
57
66
  }
58
67
  for (const [id, member] of members) {
59
68
  const values = lags.get(id)!;
60
- const priced = returns.get(id)!.sort((a, b) => a - b);
69
+ const priced = returns.get(id)!;
61
70
  const pricedBuys = buys.get(id)!;
62
71
  member.pricedTradeCount = priced.length;
63
72
  member.pricedBuyCount = pricedBuys.length;
64
- member.medianReturn = priced.length ? (priced[Math.floor((priced.length - 1) / 2)]! + priced[Math.floor(priced.length / 2)]!) / 2 : null;
73
+ member.excessTradeCount = excess.get(id)!.length;
74
+ member.medianReturn = median(priced);
75
+ member.medianExcessSinceFiling = median(excess.get(id)!);
65
76
  member.buyHitRate = pricedBuys.length ? 100 * pricedBuys.filter(value => value > 0).length / pricedBuys.length : null;
66
77
  member.avgLagDays = values.length ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) : null;
67
78
  }
@@ -40,11 +40,24 @@ import {
40
40
  type TradeColumn,
41
41
  type TradeColumnId,
42
42
  } from "./model";
43
- import { renderCongressTradeCell } from "./table";
43
+ import { congressReturnColor, renderCongressTradeCell } from "./table";
44
44
  import { loadCongressHouse } from "./client";
45
45
 
46
46
  const DETAIL_ROW = { labelWidth: 16, emphasis: false } as const;
47
47
 
48
+ /** A window's return, or its excess with SPY's own return beside it. */
49
+ function ReturnRow({ label, value, spy }: { label: string; value: number | null | undefined; spy?: number | null }) {
50
+ return (
51
+ <KeyValueRow
52
+ {...DETAIL_ROW}
53
+ label={label}
54
+ value={formatCongressReturn(value)}
55
+ color={congressReturnColor(value)}
56
+ detail={spy == null ? undefined : `SPY ${formatCongressReturn(spy)}`}
57
+ />
58
+ );
59
+ }
60
+
48
61
  export function TradeDetail({
49
62
  trade,
50
63
  width,
@@ -68,8 +81,10 @@ export function TradeDetail({
68
81
  <KeyValueRow {...DETAIL_ROW} label="asset" value={fit(trade.assetName)} />
69
82
  <KeyValueRow {...DETAIL_ROW} label="amount" value={trade.amount} color={colors.textBright} />
70
83
  <KeyValueRow {...DETAIL_ROW} label="owner" value={trade.owner} />
71
- <KeyValueRow {...DETAIL_ROW} label="tx return" value={formatCongressReturn(trade.returnSinceTx)} />
72
- <KeyValueRow {...DETAIL_ROW} label="filed return" value={formatCongressReturn(trade.returnSinceFiling)} />
84
+ <ReturnRow label="tx return" value={trade.returnSinceTx} />
85
+ <ReturnRow label="tx vs SPY" value={trade.excessSinceTx} spy={trade.spyReturnSinceTx} />
86
+ <ReturnRow label="filed return" value={trade.returnSinceFiling} />
87
+ <ReturnRow label="filed vs SPY" value={trade.excessSinceFiling} spy={trade.spyReturnSinceFiling} />
73
88
  <KeyValueRow {...DETAIL_ROW} label="price as of" value={trade.returnAsOf ?? "--"} />
74
89
  <KeyValueRow {...DETAIL_ROW} label="tx date" value={trade.transactionDate ?? "--"} />
75
90
  <KeyValueRow {...DETAIL_ROW} label="notification" value={trade.notificationDate ?? "--"} />
@@ -292,7 +307,8 @@ export function MemberTradesDetail({
292
307
  // detail adds what the row lacks: how many priced trades stand behind the
293
308
  // return figures, and the committees.
294
309
  const summaryItems: StatItem[] = [
295
- { id: "median", label: "Median return", value: formatCongressReturn(summaryMember.medianReturn), detail: `${summaryMember.pricedTradeCount ?? 0} priced` },
310
+ { id: "median", label: "Median return", value: formatCongressReturn(summaryMember.medianReturn), color: congressReturnColor(summaryMember.medianReturn), detail: `${summaryMember.pricedTradeCount ?? 0} priced` },
311
+ { id: "vs-spy", label: "Median vs SPY", value: formatCongressReturn(summaryMember.medianExcessSinceFiling), color: congressReturnColor(summaryMember.medianExcessSinceFiling), detail: `since filing, ${summaryMember.excessTradeCount ?? 0} priced`, detailVariants: ["since filing"] },
296
312
  { id: "hit", label: "Buy hit rate", value: summaryMember.buyHitRate == null ? "--" : `${summaryMember.buyHitRate.toFixed(0)}%`, detail: `${summaryMember.pricedBuyCount ?? 0} priced buys` },
297
313
  ...(summaryMember.committees ?? []).map((committee): StatItem => ({ id: `committee:${committee}`, label: "Committee", value: committee, wide: true })),
298
314
  ];
@@ -51,6 +51,7 @@ const TRADE_COLUMNS: HeadlessPaneColumn[] = [
51
51
  { key: "owner", header: "Owner" },
52
52
  { key: "returnSinceTx", header: "Tx return", format: value => formatCongressReturn(typeof value === "number" ? value : null) },
53
53
  { key: "returnSinceFiling", header: "Filed return", format: value => formatCongressReturn(typeof value === "number" ? value : null) },
54
+ { key: "excessSinceFiling", header: "Vs SPY", format: value => formatCongressReturn(typeof value === "number" ? value : null) },
54
55
  ];
55
56
 
56
57
  const MEMBER_COLUMNS: HeadlessPaneColumn[] = [
@@ -73,6 +74,7 @@ const MEMBER_COLUMNS: HeadlessPaneColumn[] = [
73
74
  { key: "avgLagDays", header: "Avg", align: "right", format: (value) => formatLag(typeof value === "number" ? value : null) },
74
75
  { key: "medianReturn", header: "Median return", format: value => formatCongressReturn(typeof value === "number" ? value : null) },
75
76
  { key: "pricedTradeCount", header: "Priced trades" },
77
+ { key: "medianExcessSinceFiling", header: "Median vs SPY", format: value => formatCongressReturn(typeof value === "number" ? value : null) },
76
78
  { key: "buyHitRate", header: "Buy hit", align: "right", format: value => typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(1)}%` : "--" },
77
79
  { key: "pricedBuyCount", header: "Priced buys" },
78
80
  ];
@@ -1,4 +1,5 @@
1
1
  import type { DataTableColumn } from "../../../components";
2
+ import { dropColumnsToFit, getTableWidth } from "../../../components/ui/table-layout";
2
3
  import type { ShortDateOptions } from "../../../utils/datetime-format";
3
4
  import { formatCompact } from "../../../utils/format";
4
5
  import type { SortDirection } from "../../../utils/sort-values";
@@ -28,6 +29,7 @@ export type DetailMode =
28
29
  export type TradeColumnId =
29
30
  | "returnSinceTx"
30
31
  | "returnSinceFiling"
32
+ | "excessSinceFiling"
31
33
  | "filed"
32
34
  | "tx"
33
35
  | "lag"
@@ -45,6 +47,7 @@ export type TradeColumn = DataTableColumn & {
45
47
  export type MemberColumnId =
46
48
  | "party"
47
49
  | "medianReturn"
50
+ | "medianExcessSinceFiling"
48
51
  | "buyHitRate"
49
52
  | "member"
50
53
  | "district"
@@ -151,6 +154,7 @@ function compareTrade(
151
154
  switch (columnId) {
152
155
  case "returnSinceTx":
153
156
  case "returnSinceFiling":
157
+ case "excessSinceFiling":
154
158
  return compareOptionalNumber(left[columnId], right[columnId]);
155
159
  case "filed":
156
160
  return dateValue(left.filingDate) - dateValue(right.filingDate);
@@ -182,6 +186,7 @@ function compareMember(
182
186
  case "party":
183
187
  return compareText(left.party ?? "", right.party ?? "");
184
188
  case "medianReturn":
189
+ case "medianExcessSinceFiling":
185
190
  case "buyHitRate":
186
191
  return compareOptionalNumber(left[columnId], right[columnId]);
187
192
  case "member":
@@ -203,87 +208,102 @@ function compareMember(
203
208
  }
204
209
  }
205
210
 
211
+ const VS_SPY_COLUMN: TradeColumn = { id: "excessSinceFiling", label: "VS SPY", width: 10, align: "right" };
212
+ const TRADE_MEMBER_MIN_WIDTH = 14;
213
+ /** Narrower than this cuts most member names, so other columns give way first. */
214
+ const MEMBER_READABLE_WIDTH = 18;
215
+
216
+ /**
217
+ * Short of room, the trades give way in this order. The return since the
218
+ * trade goes first: FILE RET% and VS SPY read over the same days, from the
219
+ * filing a follower could act on. Then the dates lose their year, the lag
220
+ * (it follows from the two dates), the owner and the trade date go, and VS
221
+ * SPY outlasts FILE RET%. The detail keeps every figure.
222
+ */
223
+ const TRADE_DROP_ORDER = ["returnSinceTx", "lag", "owner", "tx", "returnSinceFiling"] as const;
224
+
225
+ function tradeColumns(tickerView: boolean, dateWidth: number, memberWidth: number, assetWidth: number | null): TradeColumn[] {
226
+ return [
227
+ { id: "filed", label: "FILED", width: dateWidth, align: "left" },
228
+ { id: "tx", label: "TX", width: dateWidth, align: "left" },
229
+ { id: "lag", label: "LAG", width: 5, align: "right" },
230
+ { id: "member", label: "MEMBER", width: memberWidth, align: "left" },
231
+ { id: "side", label: "SIDE", width: 5, align: "left" },
232
+ ...(!tickerView ? [{ id: "ticker" as const, label: "TICKER", width: 12, align: "left" as const, assetFallback: assetWidth == null }] : []),
233
+ ...(assetWidth != null ? [{ id: "asset" as const, label: "ASSET", width: assetWidth, align: "left" as const }] : []),
234
+ { id: "amount", label: "AMOUNT", width: 14, align: "right" },
235
+ { id: "owner", label: "OWNER", width: 8, align: "left" },
236
+ { id: "returnSinceTx", label: "TX RET%", width: 10, align: "right" },
237
+ { id: "returnSinceFiling", label: "FILE RET%", width: 10, align: "right" },
238
+ VS_SPY_COLUMN,
239
+ ];
240
+ }
241
+
206
242
  export function buildTradeColumns(width: number, tickerView = false): TradeColumn[] {
207
- const filedWidth = 10;
208
- const txWidth = 10;
209
- const lagWidth = 5;
210
- const sideWidth = 5;
211
- const tickerWidth = tickerView ? 0 : 12;
212
- const amountWidth = 14;
213
- const ownerWidth = 8;
214
- const flexWidth = Math.max(
215
- 14,
216
- width - filedWidth - txWidth - lagWidth - sideWidth - tickerWidth - amountWidth - ownerWidth - 36,
217
- );
243
+ // Full dates while dropping TX RET% is enough, then short ones and the rest of the order.
244
+ const long = dropColumnsToFit(tradeColumns(tickerView, 10, MEMBER_READABLE_WIDTH, null), width, TRADE_DROP_ORDER.slice(0, 1));
245
+ const fitted = getTableWidth(long) <= width ? long : dropColumnsToFit(tradeColumns(tickerView, 7, MEMBER_READABLE_WIDTH, null), width, TRADE_DROP_ORDER);
246
+ const shown = new Set(fitted.map((column) => column.id));
247
+ const dateWidth = fitted[0]!.width;
248
+ const layout = (memberWidth: number, assetWidth: number | null) => tradeColumns(tickerView, dateWidth, memberWidth, assetWidth)
249
+ .filter((column) => shown.has(column.id) || column.id === "asset");
250
+ // What the other columns leave, as the table draws them, names the member and then the asset.
251
+ const flexWidth = Math.max(TRADE_MEMBER_MIN_WIDTH, MEMBER_READABLE_WIDTH + width - getTableWidth(fitted));
218
252
  // Member names rarely pass 22 cells; the rest names the asset, which is the
219
253
  // only identification for trades without a ticker (bonds, T-bills, funds).
220
254
  const memberWidth = Math.min(22, Math.max(18, flexWidth - 13));
221
255
  const assetWidth = flexWidth - memberWidth - 1;
222
- const showAsset = assetWidth >= 12;
223
- return [
224
- { id: "filed", label: "FILED", width: filedWidth, align: "left" },
225
- { id: "tx", label: "TX", width: txWidth, align: "left" },
226
- { id: "lag", label: "LAG", width: lagWidth, align: "right" },
227
- { id: "member", label: "MEMBER", width: showAsset ? memberWidth : flexWidth, align: "left" },
228
- { id: "side", label: "SIDE", width: sideWidth, align: "left" },
229
- ...(!tickerView ? [{ id: "ticker" as const, label: "TICKER", width: tickerWidth, align: "left" as const, assetFallback: !showAsset }] : []),
230
- ...(showAsset ? [{ id: "asset" as const, label: "ASSET", width: assetWidth, align: "left" as const }] : []),
231
- { id: "amount", label: "AMOUNT", width: amountWidth, align: "right" },
232
- { id: "owner", label: "OWNER", width: ownerWidth, align: "left" },
233
- { id: "returnSinceTx", label: "TX RET%", width: 10, align: "right" },
234
- { id: "returnSinceFiling", label: "FILE RET%", width: 10, align: "right" },
235
- ];
256
+ return assetWidth >= 12 ? layout(memberWidth, assetWidth) : layout(flexWidth, null);
236
257
  }
237
258
 
259
+ const MEMBER_TRADE_ASSET_MIN_WIDTH = 18;
260
+ /** As on the trades; the member row above also carries their average lag. */
261
+ const MEMBER_TRADE_DROP_ORDER = ["lag", "owner", "tx"] as const;
262
+
238
263
  export function buildMemberTradeColumns(width: number): TradeColumn[] {
239
- const filedWidth = 7;
240
- const txWidth = 7;
241
- const sideWidth = 8;
242
- const tickerWidth = 12;
243
- const amountWidth = 14;
244
- const ownerWidth = 8;
245
- const lagWidth = 5;
246
- const assetWidth = Math.max(
247
- 18,
248
- width - filedWidth - txWidth - sideWidth - tickerWidth - amountWidth - ownerWidth - lagWidth - 9,
249
- );
250
- return [
251
- { id: "filed", label: "FILED", width: filedWidth, align: "left" },
252
- { id: "tx", label: "TX", width: txWidth, align: "left" },
253
- { id: "side", label: "SIDE", width: sideWidth, align: "left" },
254
- { id: "ticker", label: "TICKER", width: tickerWidth, align: "left" },
255
- { id: "amount", label: "AMOUNT", width: amountWidth, align: "right" },
264
+ const layout = (assetWidth: number): TradeColumn[] => [
265
+ { id: "filed", label: "FILED", width: 7, align: "left" },
266
+ { id: "tx", label: "TX", width: 7, align: "left" },
267
+ { id: "side", label: "SIDE", width: 8, align: "left" },
268
+ { id: "ticker", label: "TICKER", width: 12, align: "left" },
269
+ { id: "amount", label: "AMOUNT", width: 14, align: "right" },
256
270
  { id: "asset", label: "ASSET", width: assetWidth, align: "left" },
257
- { id: "owner", label: "OWNER", width: ownerWidth, align: "left" },
258
- { id: "lag", label: "LAG", width: lagWidth, align: "right" },
271
+ { id: "owner", label: "OWNER", width: 8, align: "left" },
272
+ { id: "lag", label: "LAG", width: 5, align: "right" },
273
+ VS_SPY_COLUMN,
259
274
  ];
275
+ const fitted = dropColumnsToFit(layout(MEMBER_TRADE_ASSET_MIN_WIDTH), width, MEMBER_TRADE_DROP_ORDER);
276
+ const shown = new Set(fitted.map((column) => column.id));
277
+ const assetWidth = Math.max(MEMBER_TRADE_ASSET_MIN_WIDTH, MEMBER_TRADE_ASSET_MIN_WIDTH + width - getTableWidth(fitted));
278
+ return layout(assetWidth).filter((column) => shown.has(column.id));
260
279
  }
261
280
 
281
+ /**
282
+ * As on the trades, the median return since the trade gives way before the one
283
+ * against SPY; then the average lag, the last filing, the district and the
284
+ * buy hit rate, which the member's detail repeats.
285
+ */
286
+ const MEMBER_DROP_ORDER = ["medianReturn", "lag", "last", "district", "buyHitRate"] as const;
287
+
262
288
  export function buildMemberColumns(width: number): MemberColumn[] {
263
- const districtWidth = 6;
264
- const tradesWidth = 7;
265
- const buysWidth = 5;
266
- const sellsWidth = 6;
267
- const rangeWidth = 17;
268
- const lastWidth = 7;
269
- const lagWidth = 6;
270
- const memberWidth = Math.max(
271
- 18,
272
- width - districtWidth - tradesWidth - buysWidth - sellsWidth - rangeWidth - lastWidth - lagWidth - 47,
273
- );
274
- return [
289
+ const layout = (memberWidth: number): MemberColumn[] => [
275
290
  { id: "member", label: "MEMBER", width: memberWidth, align: "left" },
276
- { id: "district", label: "DIST", width: districtWidth, align: "left" },
291
+ { id: "district", label: "DIST", width: 6, align: "left" },
277
292
  { id: "party", label: "PARTY", width: 11, align: "left" },
278
- { id: "trades", label: "TRADES", width: tradesWidth, align: "right" },
279
- { id: "buys", label: "BUY", width: buysWidth, align: "right" },
280
- { id: "sells", label: "SELL", width: sellsWidth, align: "right" },
281
- { id: "range", label: "EST RANGE", width: rangeWidth, align: "right" },
282
- { id: "last", label: "LAST", width: lastWidth, align: "left" },
283
- { id: "lag", label: "AVG", width: lagWidth, align: "right" },
293
+ { id: "trades", label: "TRADES", width: 7, align: "right" },
294
+ { id: "buys", label: "BUY", width: 5, align: "right" },
295
+ { id: "sells", label: "SELL", width: 6, align: "right" },
296
+ { id: "range", label: "EST RANGE", width: 17, align: "right" },
297
+ { id: "last", label: "LAST", width: 7, align: "left" },
298
+ { id: "lag", label: "AVG", width: 6, align: "right" },
284
299
  { id: "medianReturn", label: "MED RET%", width: 9, align: "right" },
300
+ { id: "medianExcessSinceFiling", label: "MED VS SPY", width: 10, align: "right" },
285
301
  { id: "buyHitRate", label: "BUY HIT%", width: 9, align: "right" },
286
302
  ];
303
+ const fitted = dropColumnsToFit(layout(MEMBER_READABLE_WIDTH), width, MEMBER_DROP_ORDER);
304
+ const shown = new Set(fitted.map((column) => column.id));
305
+ const memberWidth = Math.max(MEMBER_READABLE_WIDTH, MEMBER_READABLE_WIDTH + width - getTableWidth(fitted));
306
+ return layout(memberWidth).filter((column) => shown.has(column.id));
287
307
  }
288
308
 
289
309
  export function sortedTrades(
@@ -394,6 +414,14 @@ export function mergeCongressPages(
394
414
  return { ...next, trades: [...byTradeId.values()], members };
395
415
  }
396
416
 
417
+ /** Percent to one decimal; a figure that rounds to zero reads 0.0%, never -0.0%. */
397
418
  export function formatCongressReturn(value: number | null | undefined): string {
398
- return value == null || !Number.isFinite(value) ? "--" : `${value > 0 ? "+" : ""}${value.toFixed(1)}%`;
419
+ if (value == null || !Number.isFinite(value)) return "--";
420
+ const shown = roundCongressReturn(value);
421
+ return `${shown > 0 ? "+" : ""}${shown === 0 ? "0.0" : shown.toFixed(1)}%`;
422
+ }
423
+
424
+ /** The figure as shown, so its colour follows the digits: SPY at -0.04% is flat, not red. */
425
+ export function roundCongressReturn(value: number): number {
426
+ return Math.round(value * 10) / 10 || 0;
399
427
  }
@@ -1,6 +1,7 @@
1
1
  import { TextAttributes } from "../../../ui";
2
2
  import { TickerBadgeList, type DataTableCell } from "../../../components";
3
3
  import { colors } from "../../../theme/colors";
4
+ import { signedColor } from "../shared/research-cells";
4
5
  import { formatShortDate } from "../../../utils/datetime-format";
5
6
  import type {
6
7
  CloudCongressMemberPayload,
@@ -11,6 +12,7 @@ import {
11
12
  formatAmountRange,
12
13
  formatCongressReturn,
13
14
  formatLag,
15
+ roundCongressReturn,
14
16
  FILING_DAY_FORMAT,
15
17
  tradeAssetLabel,
16
18
  type MemberColumn,
@@ -18,6 +20,15 @@ import {
18
20
  type TickerColumn,
19
21
  } from "./model";
20
22
 
23
+ /** A return or its excess over SPY takes the shared signed colours wherever CG shows one, by its digits. */
24
+ export function congressReturnColor(value: number | null | undefined): string {
25
+ return signedColor(value == null ? value : roundCongressReturn(value), colors);
26
+ }
27
+
28
+ function congressReturnCell(value: number | null | undefined): DataTableCell {
29
+ return { text: formatCongressReturn(value), color: congressReturnColor(value) };
30
+ }
31
+
21
32
  function sideColor(side: CloudCongressTradePayload["side"]): string {
22
33
  if (side === "BUY") return colors.positive;
23
34
  if (side === "SELL") return colors.negative;
@@ -32,7 +43,7 @@ export function renderCongressTradeCell(
32
43
  rowState: { selected: boolean },
33
44
  ): DataTableCell {
34
45
  switch (column.id) {
35
- case "returnSinceTx": case "returnSinceFiling": return { text: formatCongressReturn(trade[column.id]), color: trade[column.id] == null ? colors.textDim : trade[column.id]! >= 0 ? colors.positive : colors.negative };
46
+ case "returnSinceTx": case "returnSinceFiling": case "excessSinceFiling": return congressReturnCell(trade[column.id]);
36
47
  case "filed":
37
48
  return { text: column.width >= 10 ? trade.filingDate : formatShortDate(trade.filingDate, FILING_DAY_FORMAT), color: colors.textDim };
38
49
  case "tx":
@@ -81,7 +92,8 @@ export function renderCongressMemberCell(
81
92
  ): DataTableCell {
82
93
  switch (column.id) {
83
94
  case "party": return { text: member.party ?? "--", color: colors.textDim };
84
- case "medianReturn": case "buyHitRate": return { text: column.id === "buyHitRate" ? member.buyHitRate == null ? "--" : `${member.buyHitRate.toFixed(0)}%` : formatCongressReturn(member.medianReturn), color: colors.text };
95
+ case "medianReturn": case "medianExcessSinceFiling": return congressReturnCell(member[column.id]);
96
+ case "buyHitRate": return { text: member.buyHitRate == null ? "--" : `${member.buyHitRate.toFixed(0)}%`, color: colors.text };
85
97
  case "member":
86
98
  return { text: member.memberName, color: colors.text };
87
99
  case "district":
@@ -1,7 +1,6 @@
1
1
  import type { PluginCapability } from "../../../capabilities";
2
2
  import type { ConnectionHealthRegistry } from "../../../core/connection-health";
3
3
  import type { PluginModule } from "../plugin-module";
4
- import { ConnectionsPane } from "./pane";
5
4
 
6
5
  export const CONNECTION_HEALTH_CAPABILITY_ID = "application.connection-health";
7
6
 
@@ -35,8 +34,8 @@ export const connectionsModule: PluginModule = {
35
34
  name: "Connections",
36
35
  personalData: "account",
37
36
  icon: "C",
38
- component: ConnectionsPane,
39
- reportFreshness: { status: "not-a-feed", basis: "connection health" },
37
+ get component(): typeof import("./pane").ConnectionsPane { const { ConnectionsPane } = require("./pane"); return ConnectionsPane; },
38
+ reportFreshness: { status: "not-a-feed", basis: "connection health", local: true },
40
39
  defaultPosition: "right",
41
40
  defaultMode: "floating",
42
41
  defaultFloatingSize: { width: 80, height: 24 },
@@ -2,12 +2,11 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { cotBoardCache } from "./client";
3
3
  import { cotHeadless } from "./headless";
4
4
  import { COT_ROOT_OPTIONS, cotContractCode, cotContractCodeForTicker, cotProxyFor, cotRoot, cotUnknownMarketMessage } from "./model";
5
- import { CotPane } from "./pane";
6
5
  import { COT_SERIES } from "./series";
7
6
 
8
7
  export const cotModule: PluginModule = {
9
8
  series: COT_SERIES,
10
- panes: [{ id: "cot", name: "CFTC Positioning", icon: "P", component: CotPane,
9
+ panes: [{ id: "cot", name: "CFTC Positioning", icon: "P", get component(): typeof import("./pane").CotPane { const { CotPane } = require("./pane"); return CotPane; },
11
10
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 106, height: 30 },
12
11
  tableExport: true, headless: cotHeadless }],
13
12
  paneTemplates: [{ id: "cot-cot", paneId: "cot", label: "CFTC Positioning",
@@ -3,7 +3,6 @@ import type { PluginModule } from "../plugin-module";
3
3
  import { cpiBoardCache } from "./client";
4
4
  import { cpiHeadless } from "./headless";
5
5
  import { CPI_ROW_OPTIONS, cpiRowOption } from "./model";
6
- import { CpiPane } from "./pane";
7
6
 
8
7
  /** Opens on the table, or on the component typed after the mnemonic. */
9
8
  function cpiInstance(options?: PaneTemplateCreateOptions): PaneTemplateInstanceConfig {
@@ -14,7 +13,7 @@ function cpiInstance(options?: PaneTemplateCreateOptions): PaneTemplateInstanceC
14
13
  }
15
14
 
16
15
  export const cpiModule: PluginModule = {
17
- panes: [{ id: "cpi", name: "US Consumer Prices", icon: "C", component: CpiPane,
16
+ panes: [{ id: "cpi", name: "US Consumer Prices", icon: "C", get component(): typeof import("./pane").CpiPane { const { CpiPane } = require("./pane"); return CpiPane; },
18
17
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 100, height: 36 },
19
18
  tableExport: true, headless: cpiHeadless }],
20
19
  paneTemplates: [{
@@ -32,6 +32,7 @@ import {
32
32
  formatLevel,
33
33
  formatMove,
34
34
  partialDayText,
35
+ SETTLEMENT_TEXT,
35
36
  type CdxRow,
36
37
  } from "./model";
37
38
 
@@ -69,7 +70,7 @@ function IndexDetail({ row, width, height, focused }: { row: CdxRow; width: numb
69
70
  const through = day.partial && day.changeDate ? `to ${day.changeDate}` : undefined;
70
71
  const items: StatItem[] = [
71
72
  { id: "level", label: index.quote === "spread" ? "5Y spread" : "5Y price", value: formatLevel(index.quote, index.level),
72
- detail: `${formatPercentileRank(year.percentile, "1Y")} · ${index.date ?? "--"}` },
73
+ detail: [formatPercentileRank(year.percentile, "1Y"), index.date ?? "--", ...(index.kind === "settlement" ? [SETTLEMENT_TEXT] : [])].join(" · ") },
73
74
  ...(index.quote === "price" && index.spreadBp !== undefined ? [{ id: "spread", label: "Spread eq", value: formatLevel("spread", index.spreadBp ?? null),
74
75
  ...(index.recovery != null ? { detail: `${Math.round(index.recovery * 100)}% recovery` } : {}) }] : []),
75
76
  { id: "1d", label: "1D", value: formatMove(index.quote, day.change1D), ...(through ? { detail: through } : {}) },
@@ -84,8 +84,10 @@ export type PriceHistoryLoader = (symbol: string) => Promise<PricePoint[]>;
84
84
 
85
85
  /** Closes a month back are yesterday's news for half an hour. */
86
86
  const FX_HISTORY_TTL_MS = 30 * 60_000;
87
- /** Twenty-odd currencies at once; a few requests at a time keep the feed from refusing them. */
87
+ /** Twenty-odd currencies, a few at a time from a source that takes them one by one. */
88
88
  const FX_HISTORY_CONCURRENCY = 4;
89
+ /** All at once from a source that batches them: the API client sends them sixteen a request. */
90
+ const FX_HISTORY_BATCHED_CONCURRENCY = 32;
89
91
  const fxMoves = createTtlCache<number | null>({ ttlMs: FX_HISTORY_TTL_MS });
90
92
 
91
93
  /** A load that fails reads as unknown for everyone waiting on it and is asked again next time, never remembered as unknown. */
@@ -97,10 +99,13 @@ function currencyMove(symbol: string, dollarsPerUnit: boolean, loadHistory: Pric
97
99
  /**
98
100
  * Each currency's 1M move against the dollar from its daily closes, shared
99
101
  * across panes. A currency with no pair or no history reads as unknown.
102
+ * `batched`: `loadHistory` asks a source that batches them
103
+ * (`DataProvider.batchesReads`) with `batch`, so they are all asked at once.
100
104
  */
101
105
  export async function loadCurrencyMoves(
102
106
  currencies: readonly string[],
103
107
  loadHistory: PriceHistoryLoader,
108
+ batched = false,
104
109
  ): Promise<Map<string, number | null>> {
105
110
  const queue = [...new Set(currencies)];
106
111
  const moves = new Map<string, number | null>();
@@ -110,6 +115,6 @@ export async function loadCurrencyMoves(
110
115
  moves.set(currency, pair ? await currencyMove(pair.symbol, pair.dollarsPerUnit, loadHistory) : null);
111
116
  }
112
117
  };
113
- await Promise.all(Array.from({ length: Math.min(FX_HISTORY_CONCURRENCY, queue.length) }, worker));
118
+ await Promise.all(Array.from({ length: Math.min(batched ? FX_HISTORY_BATCHED_CONCURRENCY : FX_HISTORY_CONCURRENCY, queue.length) }, worker));
114
119
  return moves;
115
120
  }