@gloom-sh/terminal 0.19.1 → 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 (554) hide show
  1. package/README.md +7 -1
  2. package/package.json +2 -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 +58 -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/runtime/startup.ts +3 -1
  20. package/src/app.tsx +23 -1
  21. package/src/architecture/core-builtin-allowlist.json +2 -1
  22. package/src/capabilities/chart-series.ts +2 -1
  23. package/src/capabilities/factories.ts +1 -0
  24. package/src/cli/ascii-output.ts +42 -0
  25. package/src/cli/base-converter.ts +10 -2
  26. package/src/cli/command-options.ts +16 -1
  27. package/src/cli/commands/crypto-hints.ts +23 -4
  28. package/src/cli/commands/market.ts +101 -27
  29. package/src/cli/commands/news-search.ts +24 -5
  30. package/src/cli/commands/overview.ts +14 -10
  31. package/src/cli/commands/search.ts +84 -25
  32. package/src/cli/commands/system.ts +22 -30
  33. package/src/cli/commands/ticker.ts +49 -9
  34. package/src/cli/commands/version.ts +33 -0
  35. package/src/cli/context.ts +6 -9
  36. package/src/cli/data-dir.ts +7 -0
  37. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  38. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  39. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  40. package/src/cli/desktop-pane-shot.ts +196 -256
  41. package/src/cli/early-command.ts +42 -0
  42. package/src/cli/earnings-calendar.ts +9 -0
  43. package/src/cli/entry.ts +19 -13
  44. package/src/cli/errors.ts +12 -2
  45. package/src/cli/glossary.ts +2 -1
  46. package/src/cli/help.ts +1 -1
  47. package/src/cli/history-export.ts +22 -2
  48. package/src/cli/history-rows.ts +189 -11
  49. package/src/cli/index.ts +18 -1
  50. package/src/cli/known-gaps.ts +51 -5
  51. package/src/cli/listing-arg.ts +63 -10
  52. package/src/cli/options.ts +1 -1
  53. package/src/cli/output-file.ts +60 -13
  54. package/src/cli/pane-functions/catalog.ts +14 -3
  55. package/src/cli/pane-functions/data.ts +1 -1
  56. package/src/cli/pane-functions/dom.ts +45 -0
  57. package/src/cli/pane-functions/freshness.ts +46 -9
  58. package/src/cli/pane-functions/function-help.ts +17 -9
  59. package/src/cli/pane-functions/headless.ts +22 -14
  60. package/src/cli/pane-functions/index.ts +6 -2
  61. package/src/cli/pane-functions/options.ts +6 -2
  62. package/src/cli/pane-functions/resolver.ts +27 -5
  63. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  64. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  65. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  66. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  67. package/src/cli/quote-answer.ts +49 -6
  68. package/src/cli/registry.ts +41 -13
  69. package/src/cli/result.ts +12 -6
  70. package/src/cli/ticker-resolution.ts +2 -1
  71. package/src/cli/types.ts +2 -0
  72. package/src/components/chart/composite/format.ts +3 -0
  73. package/src/components/chart-table/header.tsx +1 -1
  74. package/src/components/chart-table/layout.ts +28 -11
  75. package/src/components/command-bar/action-runtime.ts +4 -0
  76. package/src/components/command-bar/commands/direct/index.ts +18 -33
  77. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  78. package/src/components/command-bar/commands/registry.ts +10 -1
  79. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  80. package/src/components/command-bar/routes/root/runtime.ts +51 -6
  81. package/src/components/command-bar/selection-runtime.ts +12 -29
  82. package/src/components/command-bar/surface/index.tsx +9 -3
  83. package/src/components/company-logo.tsx +2 -1
  84. package/src/components/layout/detached-pane-shell.tsx +2 -2
  85. package/src/components/layout/header.tsx +10 -5
  86. package/src/components/layout/market-summary.ts +25 -7
  87. package/src/components/layout/pane/footer/index.tsx +8 -2
  88. package/src/components/layout/pane/footer/model.ts +73 -6
  89. package/src/components/layout/pane/header.tsx +2 -2
  90. package/src/components/layout/pane/status-footer.ts +2 -1
  91. package/src/components/market-board/index.tsx +3 -2
  92. package/src/components/onboarding/desks-step.tsx +12 -4
  93. package/src/components/onboarding/first-run-workspace.ts +81 -4
  94. package/src/components/onboarding/onboarding-frame.tsx +4 -2
  95. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  96. package/src/components/onboarding/onboarding-wizard.tsx +60 -26
  97. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  98. package/src/components/onboarding/run-setup-again.ts +24 -0
  99. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  100. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  101. package/src/components/onboarding/wizard-model.ts +57 -3
  102. package/src/components/onboarding/wizard-positions.ts +1 -1
  103. package/src/components/ui/delayed-mark.tsx +56 -0
  104. package/src/components/ui/display.tsx +16 -5
  105. package/src/components/ui/external-link.tsx +3 -6
  106. package/src/components/ui/icon.tsx +16 -3
  107. package/src/components/ui/logo-mark.tsx +8 -5
  108. package/src/components/ui/stat-grid.tsx +263 -19
  109. package/src/core/app-services.ts +11 -2
  110. package/src/data/app-persistence.ts +9 -7
  111. package/src/data/config/store/node.ts +11 -5
  112. package/src/data/config/store/normalize.ts +16 -3
  113. package/src/data/plugin-cache.ts +15 -0
  114. package/src/data/plugin-state-store.ts +81 -24
  115. package/src/data/profile-permissions.ts +47 -0
  116. package/src/data/resource-store.ts +165 -90
  117. package/src/data/session-store.ts +31 -8
  118. package/src/data/sqlite/access.ts +324 -0
  119. package/src/data/sqlite/checkpointer.ts +285 -0
  120. package/src/data/sqlite/database.ts +80 -4
  121. package/src/data/sqlite/retry.ts +50 -9
  122. package/src/data/ticker-store.ts +57 -23
  123. package/src/docs/snapshot.json +191 -78
  124. package/src/i18n/es.ts +18 -2
  125. package/src/i18n/ja.ts +18 -2
  126. package/src/i18n/ko.ts +18 -2
  127. package/src/i18n/zh-cn.ts +18 -2
  128. package/src/i18n/zh-tw.ts +18 -2
  129. package/src/layout/desk-keys.ts +4 -1
  130. package/src/layout/desks.ts +38 -5
  131. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  132. package/src/market-data/batched-reads.ts +16 -0
  133. package/src/market-data/coordinator/auxiliary.ts +4 -2
  134. package/src/market-data/coordinator/index.ts +11 -2
  135. package/src/market-data/futures-board.ts +1 -1
  136. package/src/market-data/futures-contract.ts +3 -1
  137. package/src/market-data/market/index-level.ts +6 -4
  138. package/src/market-data/published-apac-sessions.ts +6 -0
  139. package/src/market-data/realized-volatility.ts +4 -2
  140. package/src/news/aggregator.ts +7 -4
  141. package/src/news/news-model.ts +11 -0
  142. package/src/news/story-id.ts +22 -0
  143. package/src/news/ticker-symbols.ts +17 -1
  144. package/src/notifications/app-notifier.ts +35 -14
  145. package/src/plugins/auto-update-node.ts +10 -6
  146. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  147. package/src/plugins/builtin/account-management/index.tsx +2 -3
  148. package/src/plugins/builtin/account-management/model.ts +1 -1
  149. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  150. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  151. package/src/plugins/builtin/alerts/events.ts +10 -2
  152. package/src/plugins/builtin/alerts/index.tsx +5 -5
  153. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  154. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  155. package/src/plugins/builtin/alerts/targets.ts +4 -1
  156. package/src/plugins/builtin/attention/headless.ts +1 -1
  157. package/src/plugins/builtin/attention/index.tsx +2 -4
  158. package/src/plugins/builtin/attention/listing.ts +1 -1
  159. package/src/plugins/builtin/attention/series.ts +1 -1
  160. package/src/plugins/builtin/awards/index.tsx +1 -2
  161. package/src/plugins/builtin/backtest/client.ts +1 -0
  162. package/src/plugins/builtin/backtest/index.tsx +1 -2
  163. package/src/plugins/builtin/backtest/rules.ts +3 -1
  164. package/src/plugins/builtin/backtest/window.ts +8 -7
  165. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  166. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  167. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  168. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  169. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  170. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  171. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  172. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  173. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  174. package/src/plugins/builtin/buildout/format.ts +9 -0
  175. package/src/plugins/builtin/buildout/index.tsx +51 -5
  176. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  177. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  178. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  179. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  180. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  181. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  182. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  183. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  184. package/src/plugins/builtin/cds/headless.ts +10 -2
  185. package/src/plugins/builtin/cds/model.ts +7 -3
  186. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  187. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  188. package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
  189. package/src/plugins/builtin/changelog/index.tsx +3 -2
  190. package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
  191. package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
  192. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  193. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  194. package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
  195. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  196. package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
  197. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  198. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  199. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  200. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  201. package/src/plugins/builtin/chat/index.tsx +5 -5
  202. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  203. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  204. package/src/plugins/builtin/cloud/index.tsx +1 -2
  205. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  206. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  207. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  208. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  209. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  210. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  211. package/src/plugins/builtin/commodity-balances/client.ts +4 -1
  212. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  213. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  214. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  215. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  216. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  217. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  218. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  219. package/src/plugins/builtin/connections/index.ts +2 -3
  220. package/src/plugins/builtin/cot/client.ts +28 -2
  221. package/src/plugins/builtin/cot/headless.ts +4 -1
  222. package/src/plugins/builtin/cot/index.tsx +5 -4
  223. package/src/plugins/builtin/cot/model.ts +58 -7
  224. package/src/plugins/builtin/cot/pane.tsx +6 -2
  225. package/src/plugins/builtin/cpi/client.ts +6 -5
  226. package/src/plugins/builtin/cpi/index.tsx +1 -2
  227. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  228. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  229. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  230. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  231. package/src/plugins/builtin/credit-boards/series.ts +3 -4
  232. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  233. package/src/plugins/builtin/credit-conditions/client.ts +22 -14
  234. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  235. package/src/plugins/builtin/crypto-board/client.ts +5 -5
  236. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  237. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  238. package/src/plugins/builtin/crypto-board/series.ts +6 -4
  239. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  240. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  241. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  242. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  243. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  244. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  245. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  246. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  247. package/src/plugins/builtin/debt-maturities/series.ts +3 -3
  248. package/src/plugins/builtin/debug/index.tsx +1 -1
  249. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  250. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  251. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  252. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  253. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  254. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  255. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  256. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  257. package/src/plugins/builtin/docs/index.tsx +2 -3
  258. package/src/plugins/builtin/doe/client.ts +6 -5
  259. package/src/plugins/builtin/doe/headless.ts +16 -3
  260. package/src/plugins/builtin/doe/index.tsx +5 -4
  261. package/src/plugins/builtin/doe/prices.ts +2 -3
  262. package/src/plugins/builtin/doe/series.ts +4 -4
  263. package/src/plugins/builtin/earnings/board.tsx +2 -1
  264. package/src/plugins/builtin/earnings/history.tsx +2 -1
  265. package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
  266. package/src/plugins/builtin/earnings-calls/data.ts +19 -11
  267. package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
  268. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  269. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  270. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  271. package/src/plugins/builtin/econ/detail-view.tsx +16 -2
  272. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  273. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  274. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  276. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  277. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  278. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  279. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  280. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  281. package/src/plugins/builtin/executives/data.ts +6 -1
  282. package/src/plugins/builtin/executives/pane.tsx +9 -21
  283. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  284. package/src/plugins/builtin/exposure/index.tsx +1 -2
  285. package/src/plugins/builtin/fear-greed/cache.ts +4 -0
  286. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  287. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  288. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  289. package/src/plugins/builtin/fear-greed/series.ts +6 -4
  290. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  291. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  292. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  293. package/src/plugins/builtin/futures/index.tsx +15 -0
  294. package/src/plugins/builtin/futures/model.ts +14 -0
  295. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  296. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  297. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  298. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  299. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  300. package/src/plugins/builtin/gpu/board.tsx +51 -12
  301. package/src/plugins/builtin/gpu/client.ts +19 -3
  302. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  303. package/src/plugins/builtin/gpu/headless.ts +14 -2
  304. package/src/plugins/builtin/gpu/index.tsx +1 -2
  305. package/src/plugins/builtin/gpu/model.ts +20 -2
  306. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  307. package/src/plugins/builtin/gpu/series.ts +2 -3
  308. package/src/plugins/builtin/help/index.tsx +1 -1
  309. package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
  310. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  311. package/src/plugins/builtin/holders/index.tsx +2 -3
  312. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  313. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  314. package/src/plugins/builtin/jodi/client.ts +4 -1
  315. package/src/plugins/builtin/jodi/index.tsx +1 -2
  316. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  317. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  318. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  319. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  320. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  321. package/src/plugins/builtin/market-movers/client.ts +1 -0
  322. package/src/plugins/builtin/market-movers/index.tsx +16 -5
  323. package/src/plugins/builtin/market-movers/screener.ts +23 -6
  324. package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
  325. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  326. package/src/plugins/builtin/members/index.tsx +1 -2
  327. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  328. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  329. package/src/plugins/builtin/money-markets/series.ts +3 -5
  330. package/src/plugins/builtin/news/headless.ts +7 -0
  331. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  332. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  333. package/src/plugins/builtin/notes/index.tsx +3 -6
  334. package/src/plugins/builtin/openfda/index.tsx +1 -2
  335. package/src/plugins/builtin/options/view.tsx +6 -5
  336. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  337. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  338. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  339. package/src/plugins/builtin/perps/index.tsx +1 -2
  340. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  341. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  342. package/src/plugins/builtin/plugin-module.ts +16 -2
  343. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  344. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  345. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  346. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  347. package/src/plugins/builtin/power/gas-client.ts +2 -1
  348. package/src/plugins/builtin/power/index.tsx +1 -2
  349. package/src/plugins/builtin/power/prices-client.ts +3 -2
  350. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  351. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  352. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  353. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  354. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  355. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  356. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  357. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  358. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  359. package/src/plugins/builtin/research/index.tsx +5 -8
  360. package/src/plugins/builtin/research-search/index.tsx +1 -2
  361. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  362. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  363. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  364. package/src/plugins/builtin/risk-factors/data.ts +8 -4
  365. package/src/plugins/builtin/risk-factors/index.tsx +3 -1
  366. package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
  367. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  368. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  369. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  370. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  371. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  372. package/src/plugins/builtin/scanner/lock-note.ts +26 -3
  373. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  374. package/src/plugins/builtin/sec/headless.ts +5 -1
  375. package/src/plugins/builtin/sec/index.tsx +4 -2
  376. package/src/plugins/builtin/sec/model.ts +2 -0
  377. package/src/plugins/builtin/sectors/index.tsx +11 -5
  378. package/src/plugins/builtin/series-inventory.ts +2 -0
  379. package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
  380. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  381. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  382. package/src/plugins/builtin/shared/fred-points.ts +6 -1
  383. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  384. package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
  385. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  386. package/src/plugins/builtin/shared/series-warning.ts +13 -4
  387. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  388. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  389. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  390. package/src/plugins/builtin/short-volume/series.ts +2 -3
  391. package/src/plugins/builtin/social-mentions/client.ts +15 -10
  392. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  393. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  394. package/src/plugins/builtin/social-mentions/series.ts +2 -3
  395. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  396. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  397. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  398. package/src/plugins/builtin/thirteenf/api.ts +127 -29
  399. package/src/plugins/builtin/thirteenf/data.ts +165 -35
  400. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  401. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  402. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
  403. package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
  404. package/src/plugins/builtin/thirteenf/types.ts +14 -0
  405. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
  406. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  407. package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
  408. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
  409. package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
  410. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  411. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  412. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  413. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  414. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  415. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  416. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  417. package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
  418. package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
  419. package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
  420. package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
  421. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  422. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  423. package/src/plugins/builtin/volatility/client.ts +43 -16
  424. package/src/plugins/builtin/volatility/headless.ts +5 -2
  425. package/src/plugins/builtin/volatility/index.tsx +1 -2
  426. package/src/plugins/builtin/volatility/model.ts +17 -6
  427. package/src/plugins/builtin/volatility/pane.tsx +6 -2
  428. package/src/plugins/builtin/volatility/series.ts +2 -1
  429. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  430. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  431. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  432. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  433. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  434. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  435. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  436. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  437. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  438. package/src/plugins/builtin/yield-curve/client.ts +4 -6
  439. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  440. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  441. package/src/plugins/bundle.ts +52 -2
  442. package/src/plugins/catalog-browser.ts +2 -2
  443. package/src/plugins/catalog-cli.ts +20 -0
  444. package/src/plugins/host-link.ts +13 -1
  445. package/src/plugins/installer.ts +64 -42
  446. package/src/plugins/loader.ts +43 -11
  447. package/src/plugins/peer-imports.ts +201 -0
  448. package/src/plugins/peer-retry.ts +26 -29
  449. package/src/plugins/plugin-process.ts +128 -0
  450. package/src/plugins/redirection-guard.ts +45 -0
  451. package/src/remote/endpoint.ts +16 -3
  452. package/src/renderers/browser/main.tsx +3 -2
  453. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  454. package/src/renderers/dom/desktop/icons.tsx +5 -2
  455. package/src/renderers/dom/host/image-surface.tsx +46 -21
  456. package/src/renderers/dom/styles.css +7 -0
  457. package/src/renderers/dom/toast-host.tsx +2 -0
  458. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  459. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
  460. package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
  461. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  462. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  463. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  464. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  465. package/src/renderers/electrobun/bun/window/attention.ts +54 -0
  466. package/src/renderers/electrobun/bun/window/create.ts +6 -2
  467. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  469. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  470. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  471. package/src/renderers/electrobun/view/main.tsx +7 -2
  472. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  473. package/src/renderers/electrobun/view/ui-host.tsx +9 -2
  474. package/src/renderers/opentui/browser-open.ts +10 -4
  475. package/src/renderers/opentui/image/surface.tsx +1 -1
  476. package/src/renderers/opentui/start.tsx +12 -6
  477. package/src/renderers/opentui/toast-host.tsx +2 -1
  478. package/src/series/registry.ts +18 -0
  479. package/src/series/resolve.ts +2 -1
  480. package/src/series/standalone.ts +10 -1
  481. package/src/series/types.ts +12 -0
  482. package/src/series/validate.ts +11 -2
  483. package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
  484. package/src/sources/gloomberb-cloud/index.ts +28 -5
  485. package/src/sources/gloomberb-cloud/news.ts +24 -3
  486. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  487. package/src/sources/provider-errors.ts +26 -3
  488. package/src/sources/provider-router/history.ts +5 -0
  489. package/src/sources/provider-router/index.ts +13 -0
  490. package/src/sync/core-contributors.ts +32 -0
  491. package/src/telemetry/attention-counts.ts +64 -6
  492. package/src/telemetry/attention-setting.ts +33 -0
  493. package/src/telemetry/crash-reports-node.ts +3 -1
  494. package/src/telemetry/stall-clocks.ts +93 -0
  495. package/src/telemetry/stall-reports-node.ts +25 -9
  496. package/src/telemetry/stall-reports.ts +218 -13
  497. package/src/test-support/outage.ts +8 -0
  498. package/src/test-support/preload.ts +9 -0
  499. package/src/tickers/company-name-listing.ts +59 -29
  500. package/src/tickers/company-name.ts +84 -1
  501. package/src/tickers/listing-fate.ts +14 -3
  502. package/src/tickers/saved-instrument.ts +17 -0
  503. package/src/tickers/search/home-listing.ts +104 -8
  504. package/src/tickers/search/index.ts +5 -2
  505. package/src/tickers/search/types.ts +6 -1
  506. package/src/time-series/bar-aggregation.ts +7 -1
  507. package/src/time-series/base-series.ts +3 -3
  508. package/src/time-series/chart-bounds.ts +1 -1
  509. package/src/time-series/chart-data.ts +5 -2
  510. package/src/time-series/chart-seed.ts +1 -1
  511. package/src/time-series/coin-session-close.ts +211 -0
  512. package/src/time-series/live-bars.ts +13 -2
  513. package/src/time-series/live-quotes.ts +1 -1
  514. package/src/time-series/market.ts +5 -3
  515. package/src/time-series/official-close.ts +159 -0
  516. package/src/time-series/price-comparison.ts +75 -8
  517. package/src/time-series/resolve.ts +32 -4
  518. package/src/time-series/series-presentation.ts +1 -1
  519. package/src/time-series/studies/formula.ts +205 -0
  520. package/src/time-series/studies/indicators.ts +299 -0
  521. package/src/time-series/studies/leverage.ts +75 -0
  522. package/src/time-series/studies/pair.ts +138 -0
  523. package/src/time-series/studies/resolve.ts +333 -0
  524. package/src/time-series/studies/shared.ts +141 -0
  525. package/src/time-series/studies/units.ts +243 -0
  526. package/src/time-series/studies/vwap.ts +274 -0
  527. package/src/time-series/types.ts +7 -0
  528. package/src/types/config.ts +18 -2
  529. package/src/types/data-provider.ts +23 -0
  530. package/src/types/financials.ts +40 -3
  531. package/src/types/headless.ts +42 -3
  532. package/src/types/plugin.ts +32 -3
  533. package/src/ui/host.tsx +17 -1
  534. package/src/ui/toast.tsx +26 -1
  535. package/src/utils/ascii-text.ts +140 -0
  536. package/src/utils/cli-output.ts +91 -18
  537. package/src/utils/concurrency.ts +24 -0
  538. package/src/utils/exchanges.ts +6 -0
  539. package/src/utils/external-url.ts +20 -7
  540. package/src/utils/financial-statements.ts +73 -16
  541. package/src/utils/fundamentals.ts +17 -5
  542. package/src/utils/futures-generic.ts +4 -4
  543. package/src/utils/http-proxy-upstream.ts +124 -0
  544. package/src/utils/lease-liabilities.ts +54 -0
  545. package/src/utils/main-thread-stall.ts +122 -5
  546. package/src/utils/perf-marks.ts +58 -12
  547. package/src/utils/price-history-integrity.ts +47 -1
  548. package/src/utils/private-files.ts +141 -0
  549. package/src/utils/public-address.ts +99 -0
  550. package/src/account/upgrade-personalization.ts +0 -57
  551. package/src/account/upgrade-tickers.ts +0 -124
  552. package/src/api-client/upgrade-experiment.ts +0 -91
  553. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  554. package/src/time-series/studies.ts +0 -1628
@@ -0,0 +1,243 @@
1
+ import { scalarPointValue } from "../alignment";
2
+ import { resolveCurrencyUnit, US_CENTS } from "../../utils/currency-units";
3
+ import { isCurrencyCode } from "../../utils/currency-pair";
4
+ import { stablePriceFractionDigits } from "../../market-data/market/format";
5
+ import type { ResolvedSeries, TimeSeriesPoint } from "../types";
6
+ import {
7
+ GALLONS_PER_BARREL,
8
+ isPerBarrelUnit,
9
+ isPerGallonUnit,
10
+ quantityIdentity,
11
+ unitCurrencyToken,
12
+ } from "../price-units";
13
+
14
+ export function seriesCurrency(series: ResolvedSeries): string | null {
15
+ const grouped = series.unitGroup.match(/:([A-Z]{3})$/i)?.[1];
16
+ if (grouped) return grouped.toUpperCase();
17
+ if (!/^(?:price|currency-total|per-share)$/.test(series.unitGroup)) return null;
18
+ return series.unit.match(/^([A-Z]{3})(?:$|\/)/i)?.[1]?.toUpperCase() ?? null;
19
+ }
20
+
21
+ function unitFactorIdentity(value: string): string {
22
+ // "US cents", as futures terms word a grain's quote, is the USX the quote names.
23
+ const token = unitCurrencyToken(value) ?? value.trim();
24
+ // Three letters are a currency unless they spell a quantity: "bbl" is a barrel.
25
+ if (/^[A-Za-z]{3}$/.test(token) && quantityIdentity(token) === token.toLowerCase()) {
26
+ const { currency, divisor } = resolveCurrencyUnit(token);
27
+ return `${currency}:${divisor}`;
28
+ }
29
+ return quantityIdentity(token);
30
+ }
31
+
32
+ /** Whether a unit's factor is money (USD, GBp, US cents) rather than a quantity it buys. */
33
+ function isCurrencyFactor(value: string): boolean {
34
+ const currency = /^([A-Z]{3}):\d+$/.exec(unitFactorIdentity(value))?.[1];
35
+ return !!currency && (currency === US_CENTS || isCurrencyCode(currency));
36
+ }
37
+
38
+ export function knownUnitSignature(series: Pick<ResolvedSeries, "unit" | "unitGroup">): string | null {
39
+ const unit = series.unit.trim();
40
+ if (!unit || /(?:^|\W)(?:currency|unknown|unspecified|n\/a)(?:$|\W)/i.test(unit)
41
+ || /^(?:units?|[-?]+)$/i.test(unit)) return null;
42
+ const parts = unit.split("/");
43
+ if (parts.some((part) => !part.trim() || /^[-?]+$/.test(part.trim()))) return null;
44
+
45
+ const monetary = series.unitGroup.match(/^(price|currency-total|per-share)(?::([^:]+))?$/);
46
+ // A futures contract writes its unit spaced ("USD / gallon"); a catalog series does not ("USD/gallon").
47
+ const currency = unitCurrencyToken(parts[0]!);
48
+ if (monetary && (!currency || (monetary[2] && unitFactorIdentity(monetary[2]) !== unitFactorIdentity(currency)))) return null;
49
+
50
+ // Currency alone does not establish a price's physical/share basis. A scalar
51
+ // multiplier cannot establish it either. Explicit USD/share price and EPS
52
+ // units, however, match despite different groups.
53
+ if (monetary?.[1] === "price" && !unit.includes("/")) return null;
54
+ const basis = monetary && !unit.includes("/") ? monetary[1] : "";
55
+ // Keep currency scale and compound order: GBp/GBX are pence, not pounds;
56
+ // USD/EUR cannot subtract EUR/USD.
57
+ return `${basis ?? ""}|${parts.map(unitFactorIdentity).join("/")}`;
58
+ }
59
+
60
+ /** A leg as an error names it: its ticker (HO=F), else its label. */
61
+ export function legName(series: ResolvedSeries): string {
62
+ return series.listing?.symbol || series.label;
63
+ }
64
+
65
+ /** What a leg's unit is, in words: "is USD / gallon", "is in %", "is in USD per an unknown quantity". */
66
+ function unitPhrase(series: ResolvedSeries, unit: string): string {
67
+ const text = unit.trim();
68
+ // A rate reads as itself (HO=F is USD / gallon); an amount or a scale is what a leg is in (REVENUE is in USD).
69
+ if (knownUnitSignature({ unit: text, unitGroup: series.unitGroup }) !== null) return text.includes("/") ? `is ${text}` : `is in ${text}`;
70
+ if (unitCurrencyToken(text) && /^price(?::|$)/.test(series.unitGroup)) return `is in ${text} per an unknown quantity`;
71
+ return "has no known unit";
72
+ }
73
+
74
+ function unitCurrency(series: ResolvedSeries, unit: string): string | null {
75
+ const token = unitCurrencyToken(unit.split("/")[0] ?? "");
76
+ return token ? unitFactorIdentity(token) : seriesCurrency(series);
77
+ }
78
+
79
+ /**
80
+ * Why two legs cannot be added or subtracted, naming each one's unit and the
81
+ * fix: a per-gallon leg beside a per-barrel one is 42 gallons short of it.
82
+ */
83
+ export function unitMismatchMessage(
84
+ kind: "spread" | "formula",
85
+ left: { series: ResolvedSeries; unit: string },
86
+ right: { series: ResolvedSeries; unit: string },
87
+ ): string {
88
+ // Two fields of one listing are told apart by their labels.
89
+ const [leftName, rightName] = legName(left.series) === legName(right.series)
90
+ ? [left.series.label, right.series.label]
91
+ : [legName(left.series), legName(right.series)];
92
+ const named = `${leftName} ${unitPhrase(left.series, left.unit)} and ${rightName} ${unitPhrase(right.series, right.unit)}`;
93
+ const leftCurrency = unitCurrency(left.series, left.unit);
94
+ const rightCurrency = unitCurrency(right.series, right.unit);
95
+ const gallons = isPerGallonUnit(left.unit) && isPerBarrelUnit(right.unit) ? leftName
96
+ : isPerBarrelUnit(left.unit) && isPerGallonUnit(right.unit) ? rightName : null;
97
+ if (gallons && leftCurrency === rightCurrency) {
98
+ return `${named}: write ${GALLONS_PER_BARREL}*${gallons} to compare per barrel.`;
99
+ }
100
+ const subject = kind === "spread" ? "a spread" : "a formula";
101
+ if (leftCurrency && rightCurrency && leftCurrency !== rightCurrency) return `${named}: ${subject} does not convert currencies.`;
102
+ const known = knownUnitSignature({ unit: left.unit, unitGroup: left.series.unitGroup }) !== null
103
+ && knownUnitSignature({ unit: right.unit, unitGroup: right.series.unitGroup }) !== null;
104
+ return `${named}: ${subject} needs ${kind === "spread" ? "both" : "every leg"} in one${known ? "" : " known"} unit.`;
105
+ }
106
+
107
+ function unitFactors(unit: string): { numerator: string[]; denominator: string[] } {
108
+ const [numerator = "", ...denominator] = unit.split("/");
109
+ const factor = (value: string) => value.split("·").flatMap((part) => {
110
+ const normalized = part.trim();
111
+ if (!normalized || normalized === "1") return [];
112
+ return [normalized.toLowerCase() === "shares" ? "share" : normalized];
113
+ });
114
+ return {
115
+ numerator: factor(numerator),
116
+ denominator: denominator.flatMap(factor),
117
+ };
118
+ }
119
+
120
+ export interface SeriesUnit {
121
+ unit: string;
122
+ unitGroup: string;
123
+ }
124
+
125
+ const UNKNOWN_UNIT: SeriesUnit = { unit: "unknown", unitGroup: "derived-unit:unknown" };
126
+
127
+ export function isPriceGroup(unitGroup: string): boolean {
128
+ return /^price(?::|$)/.test(unitGroup);
129
+ }
130
+
131
+ /**
132
+ * The unit of `left` times (`power` 1) or over (-1) `right`, the factors they
133
+ * share cancelling: USD / barrel over an FX rate's USD / EUR is EUR / barrel.
134
+ * Units are spaced as either side writes them ("USD / gallon"). A price over
135
+ * or times a price that leaves one currency per one quantity is a price in
136
+ * that currency; anything else is a derived unit, or a plain ratio.
137
+ */
138
+ export function combinedUnit(left: SeriesUnit, right: SeriesUnit, power: 1 | -1): SeriesUnit {
139
+ if (!knownUnitSignature(left) || !knownUnitSignature(right)) return UNKNOWN_UNIT;
140
+ const leftFactors = unitFactors(left.unit);
141
+ const rightFactors = unitFactors(right.unit);
142
+ const numerator = [...leftFactors.numerator, ...(power < 0 ? rightFactors.denominator : rightFactors.numerator)];
143
+ const denominator = [...leftFactors.denominator, ...(power < 0 ? rightFactors.numerator : rightFactors.denominator)];
144
+ for (let index = numerator.length - 1; index >= 0; index -= 1) {
145
+ const match = denominator.findIndex((factor) => (
146
+ unitFactorIdentity(factor) === unitFactorIdentity(numerator[index]!)
147
+ ));
148
+ if (match < 0) continue;
149
+ numerator.splice(index, 1);
150
+ denominator.splice(match, 1);
151
+ }
152
+ const slash = [left.unit, right.unit].some((unit) => /\s\/\s/.test(unit)) ? " / " : "/";
153
+ const unit = denominator.length === 0
154
+ ? numerator.join("·") || "x"
155
+ : `${numerator.join("·") || "1"}${slash}${denominator.join("·")}`;
156
+ if (isPriceGroup(left.unitGroup) && isPriceGroup(right.unitGroup) && numerator.length === 1 && isCurrencyFactor(numerator[0]!)
157
+ && (denominator.length === 0 || (denominator.length === 1 && !isCurrencyFactor(denominator[0]!)))) {
158
+ return { unit, unitGroup: `price:${unitCurrencyToken(numerator[0]!) ?? numerator[0]!}` };
159
+ }
160
+ const groupFactor = (value: string) => {
161
+ const identity = unitFactorIdentity(value);
162
+ return (/^[A-Za-z]{3}$/.test(value.trim()) ? identity.replace(/:1$/, "") : identity).toLowerCase();
163
+ };
164
+ const groupNumerator = numerator.map(groupFactor).join("·");
165
+ const groupDenominator = denominator.map(groupFactor).join("·");
166
+ const group = groupDenominator ? `${groupNumerator || "1"}/${groupDenominator}` : groupNumerator;
167
+ return {
168
+ unit,
169
+ unitGroup: unit === "x" ? "ratio" : `derived-unit:${group}`,
170
+ };
171
+ }
172
+
173
+ export function ratioUnit(left: SeriesUnit, right: SeriesUnit): SeriesUnit {
174
+ return combinedUnit(left, right, -1);
175
+ }
176
+
177
+ /** An FX rate's unit: one currency per another, as USD / EUR. */
178
+ function isRateUnit(unit: string): boolean {
179
+ const { numerator, denominator } = unitFactors(unit);
180
+ return numerator.length === 1 && denominator.length === 1 && isCurrencyFactor(numerator[0]!) && isCurrencyFactor(denominator[0]!);
181
+ }
182
+
183
+ /**
184
+ * Why a price times (`power` 1) or over (-1) an FX rate is no price, with how
185
+ * to convert it instead: a crack in USD / barrel over USDJPY=X (JPY / USD) is
186
+ * USD·USD / barrel·JPY, and times it JPY / barrel. A ratio, which cannot
187
+ * multiply, names the formula to write (`priceName` its numerator). Null when
188
+ * the rate converts the price, or either is not what this asks about.
189
+ */
190
+ export function fxConversionIssue(price: SeriesUnit, rate: ResolvedSeries, power: 1 | -1, result: SeriesUnit, priceName?: string): string | null {
191
+ if (!isPriceGroup(price.unitGroup) || !isPriceGroup(rate.unitGroup) || !isRateUnit(rate.unit) || isPriceGroup(result.unitGroup)) {
192
+ return null;
193
+ }
194
+ const name = legName(rate);
195
+ const done = `${power < 0 ? "Dividing" : "Multiplying"} by ${name} (${rate.unit}) leaves ${result.unit}`;
196
+ const other = combinedUnit(price, rate, power < 0 ? 1 : -1);
197
+ if (isPriceGroup(other.unitGroup)) {
198
+ const fix = priceName ? `write (${priceName})${power < 0 ? "*" : "/"}${name}` : `${power < 0 ? "multiply" : "divide"} by ${name}`;
199
+ return `${done}; ${fix} instead to get ${other.unit}.`;
200
+ }
201
+ const currency = price.unitGroup.slice("price:".length) || "its currency";
202
+ return `${done}; ${price.unit} converts only with a rate that has ${currency} on one side.`;
203
+ }
204
+
205
+ /** The distinct currencies a unit still counts, once its factors cancel. */
206
+ export function unitCurrencies(unit: string): Set<string> {
207
+ const { numerator, denominator } = unitFactors(unit);
208
+ return new Set([...numerator, ...denominator].filter(isCurrencyFactor).map((factor) => unitFactorIdentity(factor).split(":")[0]!));
209
+ }
210
+
211
+ /**
212
+ * The decimals a value worked out from prices prints at: the most its legs
213
+ * print, from two to four, and no more than a value of its own size needs:
214
+ * four under 10, two from 10. A crack of 4.9842 USD / gallon heating oil and
215
+ * 116.72 USD / barrel Brent reads 92.62; a gasoline-heating oil spread reads
216
+ * 1.7726. Undefined when a leg is not a price or the value has none yet.
217
+ */
218
+ export function derivedValueDecimals(inputs: readonly ResolvedSeries[], points: readonly TimeSeriesPoint[]): number | undefined {
219
+ let digits = 0;
220
+ for (const input of inputs) {
221
+ if (!isPriceGroup(input.unitGroup) || input.transform !== "raw") return undefined;
222
+ const reference = latestScalarValue(input.points);
223
+ if (reference === undefined) return undefined;
224
+ digits = Math.max(digits, stablePriceFractionDigits({
225
+ assetCategory: input.priceAssetCategory,
226
+ currency: unitCurrencyToken(input.unit.split("/")[0] ?? "") ?? undefined,
227
+ referencePrice: reference,
228
+ sessionPrices: [reference],
229
+ }));
230
+ }
231
+ const size = latestScalarValue(points);
232
+ if (size === undefined) return undefined;
233
+ return Math.min(size < 10 ? 4 : 2, Math.max(2, digits));
234
+ }
235
+
236
+ /** The size of a series' last nonzero value, which its decimals are read from. */
237
+ function latestScalarValue(points: readonly TimeSeriesPoint[]): number | undefined {
238
+ for (let index = points.length - 1; index >= 0; index -= 1) {
239
+ const value = scalarPointValue(points[index]!);
240
+ if (value !== null && Number.isFinite(value) && value !== 0) return Math.abs(value);
241
+ }
242
+ return undefined;
243
+ }
@@ -0,0 +1,274 @@
1
+ import { latestTradingSessionOpen } from "../../market-data/market/trading-sessions";
2
+ import { CHART_RESOLUTION_STEP_MS, isIntradayResolution, type ManualChartResolution } from "../resolution";
3
+ import { anchoredVwap, sessionVwap, type StudyBar, type VwapValue } from "../trader-studies";
4
+ import { zonedDateTimeParts } from "../../utils/zoned-date-time";
5
+ import type { ChartStudySpec, ResolvedSeries } from "../types";
6
+ import { isFiniteNumber } from "../../utils/guards";
7
+ import { ANCHOR_COLORS, derivedPoint, outputSeries, studyBars, type NumericSample } from "./shared";
8
+
9
+ const DAY_MS = 86_400_000;
10
+ /** Most anchors one anchored VWAP keeps; each draws its own line. */
11
+ const MAX_VWAP_ANCHORS = 8;
12
+ export const DEFAULT_PROFILE_ROWS = 24;
13
+
14
+ /** Anchor times of an anchored VWAP, kept as `anchor1`, `anchor2`, ... so the spec stays numeric. */
15
+ export function vwapAnchors(spec: Pick<ChartStudySpec, "parameters">): number[] {
16
+ return [...new Set(Object.entries(spec.parameters)
17
+ .filter(([key, value]) => /^anchor\d+$/.test(key) && isFiniteNumber(value))
18
+ .map(([, value]) => value as number))]
19
+ .sort((left, right) => left - right)
20
+ .slice(0, MAX_VWAP_ANCHORS);
21
+ }
22
+
23
+ export function withVwapAnchors(
24
+ parameters: ChartStudySpec["parameters"],
25
+ anchors: readonly number[],
26
+ ): ChartStudySpec["parameters"] {
27
+ const kept = Object.fromEntries(Object.entries(parameters).filter(([key]) => !/^anchor\d+$/.test(key)));
28
+ const sorted = [...new Set(anchors.filter(Number.isFinite))].sort((left, right) => left - right).slice(-MAX_VWAP_ANCHORS);
29
+ return { ...kept, ...Object.fromEntries(sorted.map((anchor, index) => [`anchor${index + 1}`, anchor])) };
30
+ }
31
+
32
+ function isIntradayInput(input: ResolvedSeries, marketResolution?: ManualChartResolution): boolean {
33
+ const resolution = input.historyResolution === undefined ? marketResolution : input.historyResolution;
34
+ if (resolution) return isIntradayResolution(resolution);
35
+ const cadence = input.timeBasis?.cadenceMs;
36
+ return cadence !== undefined && cadence < DAY_MS;
37
+ }
38
+
39
+ function localDay(time: number, timeZone: string | undefined): number {
40
+ if (!timeZone) return Math.floor(time / DAY_MS);
41
+ const { year, month, day } = zonedDateTimeParts(time, timeZone);
42
+ return Date.UTC(year, month - 1, day) / DAY_MS;
43
+ }
44
+
45
+ /**
46
+ * `step` at each of `times` (ascending) for a step function that never falls
47
+ * as time moves on, such as a session's open or a local day. It is asked at
48
+ * both ends of a run and a run whose ends agree takes that value throughout,
49
+ * so a few calendar lookups per session cover every bar. The live chart
50
+ * resolves on every quote, and a lookup per 1-minute bar took tens of
51
+ * milliseconds over a week.
52
+ */
53
+ function risingSteps<T>(times: readonly number[], step: (time: number) => T): T[] {
54
+ const values = new Array<T>(times.length);
55
+ if (times.length === 0) return values;
56
+ const last = times.length - 1;
57
+ values[0] = step(times[0]!);
58
+ values[last] = step(times[last]!);
59
+ const fill = (from: number, to: number): void => {
60
+ if (to - from < 2) return;
61
+ if (values[from] === values[to]) {
62
+ values.fill(values[from]!, from + 1, to);
63
+ return;
64
+ }
65
+ const middle = (from + to) >> 1;
66
+ values[middle] = step(times[middle]!);
67
+ fill(from, middle);
68
+ fill(middle, to);
69
+ };
70
+ fill(0, last);
71
+ return values;
72
+ }
73
+
74
+ /** When the session holding `time` opened on the input's listing, or null for a venue without known hours. */
75
+ function sessionOpenOf(input: ResolvedSeries): (time: number) => number | null {
76
+ const symbol = input.listing?.symbol;
77
+ const exchange = input.listing?.exchange || input.timeBasis?.exchange;
78
+ return (time) => latestTradingSessionOpen(symbol, exchange, time);
79
+ }
80
+
81
+ /** The bar spacing: the requested cadence, else the history's resolution, else a minute. */
82
+ function barStep(input: ResolvedSeries): number {
83
+ return input.timeBasis?.cadenceMs
84
+ ?? (input.historyResolution ? CHART_RESOLUTION_STEP_MS[input.historyResolution] : undefined)
85
+ ?? 60_000;
86
+ }
87
+
88
+ /**
89
+ * Each bar's session, named by the open it follows, so the sums restart at
90
+ * the open and a bar before it continues the previous session: the regular
91
+ * open for stocks, the venue's published session open for futures (17:00
92
+ * Central on CME Globex). A venue without known hours starts over with each
93
+ * local day. The first session is left out when the history begins after its
94
+ * open, since its sums would be missing the bars before.
95
+ */
96
+ function regularSessionKeys(bars: readonly StudyBar[], input: ResolvedSeries): Array<number | null> {
97
+ const step = barStep(input);
98
+ const times = bars.map((bar) => bar.time);
99
+ const opens = risingSteps(times, sessionOpenOf(input));
100
+ const days = opens.includes(null) ? risingSteps(times, (time) => localDay(time, input.timeBasis?.timeZone)) : [];
101
+ const keys = opens.map((open, index) => open ?? -1 - days[index]!);
102
+ const first = keys[0];
103
+ if (first !== undefined && first >= 0 && bars[0]!.time >= first + step) {
104
+ return keys.map((key) => key === first ? null : key);
105
+ }
106
+ return keys;
107
+ }
108
+
109
+ function vwapOutputs(
110
+ spec: ChartStudySpec,
111
+ input: ResolvedSeries,
112
+ bars: ReadonlyArray<{ sample: NumericSample }>,
113
+ values: readonly VwapValue[],
114
+ options: { id: string; label: string; color: string; bands: number; sessionOf?: (index: number) => number | null },
115
+ ): ResolvedSeries[] {
116
+ // A value opening a new session follows a missing one just before it, so
117
+ // the line restarts there instead of joining the sessions with a diagonal.
118
+ const at = (pick: (value: VwapValue) => number) => values.flatMap((value, position) => {
119
+ const bar = bars[value.index];
120
+ if (!bar) return [];
121
+ const point = derivedPoint(bar.sample, pick(value));
122
+ const previous = values[position - 1];
123
+ if (!previous || !options.sessionOf || options.sessionOf(previous.index) === options.sessionOf(value.index)) return [point];
124
+ const before = new Date(point.date.getTime() - 1);
125
+ return [{ ...derivedPoint(bar.sample, null), date: before, observedAt: before }, point];
126
+ });
127
+ const outputs = [outputSeries(spec, input, {
128
+ id: options.id,
129
+ label: options.label,
130
+ points: at((value) => value.value),
131
+ color: options.color,
132
+ })];
133
+ if (options.bands > 0) {
134
+ outputs.push(
135
+ outputSeries(spec, input, {
136
+ id: `${options.id}:upper`,
137
+ label: `${options.label} +${options.bands}σ`,
138
+ points: at((value) => value.value + value.deviation * options.bands),
139
+ color: options.color,
140
+ }),
141
+ outputSeries(spec, input, {
142
+ id: `${options.id}:lower`,
143
+ label: `${options.label} -${options.bands}σ`,
144
+ points: at((value) => value.value - value.deviation * options.bands),
145
+ color: options.color,
146
+ }),
147
+ );
148
+ }
149
+ return outputs;
150
+ }
151
+
152
+ function hasVolume(bars: readonly StudyBar[]): boolean {
153
+ return bars.some((bar) => bar.volume > 0);
154
+ }
155
+
156
+ export function resolveVwap(
157
+ spec: ChartStudySpec,
158
+ input: ResolvedSeries,
159
+ color: string,
160
+ nameInput: boolean,
161
+ marketResolution: ManualChartResolution | undefined,
162
+ warnings: string[],
163
+ ): ResolvedSeries[] {
164
+ const label = nameInput ? `VWAP ${input.label}` : "VWAP";
165
+ if (!isIntradayInput(input, marketResolution)) {
166
+ warnings.push(`${label} needs intraday bars: choose 1D or 1W, or a minute timeframe.`);
167
+ return [];
168
+ }
169
+ const bars = studyBars(input);
170
+ if (!hasVolume(bars)) {
171
+ warnings.push(`${label} needs traded volume, which ${input.label} does not report.`);
172
+ return [];
173
+ }
174
+ const keys = regularSessionKeys(bars, input);
175
+ const bands = Math.min(3, Math.max(0, Math.round(isFiniteNumber(spec.parameters.bands) ? spec.parameters.bands : 0)));
176
+ return vwapOutputs(spec, input, bars, sessionVwap(bars, (_bar, index) => keys[index] ?? null), {
177
+ id: spec.id,
178
+ label,
179
+ color,
180
+ bands,
181
+ sessionOf: (index) => keys[index] ?? null,
182
+ });
183
+ }
184
+
185
+ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
186
+
187
+ /** `Sep 30 09:30` on intraday bars, in the exchange's time; `Sep 30 2025` on daily ones. */
188
+ function formatAnchor(anchor: number, input: ResolvedSeries, intraday: boolean): string {
189
+ const parts = zonedDateTimeParts(anchor, intraday ? input.timeBasis?.timeZone ?? "UTC" : "UTC");
190
+ const date = `${MONTHS[parts.month - 1]} ${parts.day}`;
191
+ return intraday
192
+ ? `${date} ${String(parts.hour).padStart(2, "0")}:${String(parts.minute).padStart(2, "0")}`
193
+ : `${date} ${parts.year}`;
194
+ }
195
+
196
+ /**
197
+ * Whether the loaded bars reach back to an anchor: a bar at or before it, or
198
+ * the first bar opening the same local day the anchor names.
199
+ */
200
+ function historyReaches(bars: readonly StudyBar[], anchor: number, input: ResolvedSeries): boolean {
201
+ const first = bars[0];
202
+ if (!first) return false;
203
+ if (first.time <= anchor) return true;
204
+ const timeZone = input.timeBasis?.timeZone;
205
+ if (localDay(first.time, timeZone) !== localDay(anchor, timeZone)) return false;
206
+ const open = sessionOpenOf(input)(first.time);
207
+ return open === null || first.time < open + barStep(input);
208
+ }
209
+
210
+ export function resolveAnchoredVwap(
211
+ spec: ChartStudySpec,
212
+ input: ResolvedSeries,
213
+ color: string,
214
+ marketResolution: ManualChartResolution | undefined,
215
+ warnings: string[],
216
+ ): ResolvedSeries[] {
217
+ const anchors = vwapAnchors(spec);
218
+ if (anchors.length === 0) return [];
219
+ const bars = studyBars(input);
220
+ if (!hasVolume(bars)) {
221
+ warnings.push(`Anchored VWAP needs traded volume, which ${input.label} does not report.`);
222
+ return [];
223
+ }
224
+ const intraday = isIntradayInput(input, marketResolution);
225
+ const resolution = input.historyResolution ?? marketResolution;
226
+ const barMs = input.timeBasis?.cadenceMs ?? (resolution ? CHART_RESOLUTION_STEP_MS[resolution] : 0);
227
+ return anchors.flatMap((anchor, offset) => {
228
+ const label = `AVWAP ${formatAnchor(anchor, input, intraday)}`;
229
+ if (!historyReaches(bars, anchor, input)) {
230
+ warnings.push(`${label} starts before the loaded history; choose a longer range.`);
231
+ return [];
232
+ }
233
+ return vwapOutputs(spec, input, bars, anchoredVwap(bars, anchor, barMs), {
234
+ id: `${spec.id}:${anchor}`,
235
+ label,
236
+ color: offset === 0 ? color : ANCHOR_COLORS[offset % ANCHOR_COLORS.length]!,
237
+ bands: 0,
238
+ });
239
+ });
240
+ }
241
+
242
+ /** The input's bars, passed through for the chart to profile over what is in view. */
243
+ export function resolveVolumeProfile(
244
+ spec: ChartStudySpec,
245
+ input: ResolvedSeries,
246
+ color: string,
247
+ warnings: string[],
248
+ ): ResolvedSeries[] {
249
+ if (input.transform !== "raw") {
250
+ warnings.push("Volume profile needs prices; switch the price series back to its own values.");
251
+ return [];
252
+ }
253
+ const bars = studyBars(input);
254
+ if (!hasVolume(bars)) {
255
+ warnings.push(`Volume profile needs traded volume, which ${input.label} does not report.`);
256
+ return [];
257
+ }
258
+ const rows = Math.min(100, Math.max(4, Math.round(isFiniteNumber(spec.parameters.rows) ? spec.parameters.rows : DEFAULT_PROFILE_ROWS)));
259
+ return [{
260
+ ...outputSeries(spec, input, {
261
+ label: "VP POC",
262
+ points: bars.map(({ sample }) => ({
263
+ ...derivedPoint(sample, sample.value),
264
+ open: sample.point.open,
265
+ high: sample.point.high,
266
+ low: sample.point.low,
267
+ close: sample.point.close,
268
+ volume: sample.point.volume,
269
+ })),
270
+ color,
271
+ }),
272
+ profile: { rows },
273
+ }];
274
+ }
@@ -265,6 +265,8 @@ export interface ResolvedSeries {
265
265
  /** Rejected valuation price inputs, retained independently of usable observations. */
266
266
  valuationPriceIssues?: import("./valuation-price").ValuationPriceIssue[];
267
267
  warning?: string;
268
+ /** When the copy the points came from was saved (ms), when its refresh failed and that copy answered. */
269
+ savedAt?: number;
268
270
  /** Listed in the legend so it can be restored, but not drawn. */
269
271
  hidden?: boolean;
270
272
  /**
@@ -324,6 +326,11 @@ export interface ChartResolutionResult {
324
326
  loading: boolean;
325
327
  errors: string[];
326
328
  warnings: string[];
329
+ /**
330
+ * The oldest saved copy a shown series answered from after its refresh
331
+ * failed (ms), which the chart's footer dates: "saved copy from …".
332
+ */
333
+ savedAt?: number;
327
334
  /**
328
335
  * Where a series' source starts after the range asked for, and why (a
329
336
  * publisher's cap on history), as `warnings` also carries it, so a report
@@ -193,8 +193,13 @@ export interface OnboardingProgress {
193
193
  positionsImported?: number;
194
194
  accountStatus?: "signed-in" | "skipped";
195
195
  checkoutOpenedAt?: string;
196
- /** Desks picked at "What do you trade?", in the order picked; empty when skipped. */
196
+ /** Desks picked at "What do you trade most?", in the order picked; empty when skipped. */
197
197
  desks?: string[];
198
+ /**
199
+ * Opened again from Settings (Run Setup Again) after setup was done: every
200
+ * step can be left, and the layouts are rebuilt in place.
201
+ */
202
+ rerun?: boolean;
198
203
  }
199
204
 
200
205
  /**
@@ -231,8 +236,19 @@ export interface TelemetryConfig {
231
236
  * both off; absent means on.
232
237
  */
233
238
  usage?: boolean;
234
- /** Ticker-level research counts. Only an explicit true opts in; absent is OFF. */
239
+ /**
240
+ * Ticker-level research counts (Attention Counts). On by default: `false`
241
+ * turns them off and `true` is an explicit on. While unset, an account is
242
+ * counted only once this installation has shown it the one-time notice.
243
+ * Cloud sync carries only whether it is off, so an off reaches the
244
+ * account's other devices.
245
+ */
235
246
  attention?: boolean;
247
+ /**
248
+ * The accounts this installation has shown the Attention Counts notice to,
249
+ * oldest first. Kept on this device; never synced or sent.
250
+ */
251
+ attentionNoticeShownTo?: string[];
236
252
  }
237
253
 
238
254
  /**
@@ -68,6 +68,11 @@ export interface SecFilingItem {
68
68
  * Frankfurt reads SK hynix's). Absent on the listing's own filings.
69
69
  */
70
70
  companyFrom?: CompanyFrom;
71
+ /**
72
+ * A current report's press release (its EX-99.1), when its index names one:
73
+ * the page the results or the news are on, not the cover page.
74
+ */
75
+ pressReleaseUrl?: string;
71
76
  }
72
77
 
73
78
  export interface SecFilingDocument {
@@ -137,6 +142,9 @@ export interface EarningsEvent {
137
142
  timing: "BMO" | "AMC" | "TNS" | "";
138
143
  }
139
144
 
145
+ /** The reads a provider may send in batches (`DataProvider.batchesReads`). */
146
+ export type BatchedReadMethod = "getPriceHistory" | "getOptionsChain";
147
+
140
148
  export interface MarketDataRequestContext {
141
149
  brokerId?: string;
142
150
  brokerInstanceId?: string;
@@ -155,6 +163,12 @@ export interface MarketDataRequestContext {
155
163
  historySession?: "extended";
156
164
  /** The listing's company, from its own quote: an SEC lookup for a non-US listing checks the registrant against it. */
157
165
  listingName?: string;
166
+ /**
167
+ * The caller asks many reads like this one in the same moment (a board's
168
+ * rows), having checked `batchesReads`: a provider that batches them may
169
+ * hold this one a moment to send it with the others.
170
+ */
171
+ batch?: boolean;
158
172
  /**
159
173
  * The caller prints what the quote's move is from: a short-lived process
160
174
  * (the CLI) waits briefly for the daily closes that date a previous close
@@ -259,6 +273,15 @@ export interface AssetDataProvider {
259
273
  primaryMarketSourceName?(): string | null | Promise<string | null>;
260
274
  getCachedFinancialsForTargets?(targets: CachedFinancialsTarget[], options?: { allowExpired?: boolean; includeStaleQuotes?: boolean }): Map<string, TickerFinancials> | Promise<Map<string, TickerFinancials>>;
261
275
  getQuotesBatch?(targets: QuoteSubscriptionTarget[], options?: QuoteBatchOptions): Promise<QuoteBatchResult[]>;
276
+ /**
277
+ * Whether reads of `method` asked with others in one moment (context
278
+ * `batch`) go out as batch requests (trailing history, dated option
279
+ * expiries), so a caller that otherwise keeps a few in flight may ask them
280
+ * all at once. A router answers true only when every source it could ask
281
+ * for them does; a remote transport asks the process that owns the router.
282
+ * Absent or false: ask a few at a time, without `batch`.
283
+ */
284
+ batchesReads?(method: BatchedReadMethod): boolean | Promise<boolean>;
262
285
  getTickerFinancialsBatch?(targets: CachedFinancialsTarget[], options?: { forceRefresh?: boolean; datesChangeBasis?: boolean }): Promise<TickerFinancialsBatchResult[]>;
263
286
  getTickerFinancials(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<TickerFinancials>;
264
287
  getQuote(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<Quote>;