@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,138 @@
1
+ import { alignTimeSeries } from "../alignment";
2
+ import type { ManualChartResolution } from "../resolution";
3
+ import type { ChartStudySpec, ResolvedSeries, SeriesPeriod, TimeSeriesPoint } from "../types";
4
+ import { isFiniteNumber } from "../../utils/guards";
5
+ import { derivedPoint, outputSeries, studyPeriod } from "./shared";
6
+ import { derivedValueDecimals, isPriceGroup, ratioUnit } from "./units";
7
+
8
+ interface PairedSample {
9
+ point: TimeSeriesPoint;
10
+ left: number;
11
+ right: number;
12
+ }
13
+
14
+ function pairedSamples(left: ResolvedSeries, right: ResolvedSeries, carryForward: boolean): PairedSample[] {
15
+ return alignTimeSeries([left, right], {
16
+ mode: "intersection",
17
+ // Ratios/spreads compare latest known levels; correlations need actual
18
+ // shared observations before calculating returns. Carrying a closed market
19
+ // invents zero returns and mismatches both sides of a weekend/holiday move.
20
+ // Both modes still respect the publication time of each observation.
21
+ carryForward,
22
+ }).flatMap((row) => {
23
+ const leftValue = row.values[left.id];
24
+ const rightValue = row.values[right.id];
25
+ if (!isFiniteNumber(leftValue?.value) || !isFiniteNumber(rightValue?.value)) return [];
26
+ const availability = Math.max(
27
+ leftValue.point.availableAt?.getTime() ?? leftValue.point.date.getTime(),
28
+ rightValue.point.availableAt?.getTime() ?? rightValue.point.date.getTime(),
29
+ );
30
+ return [{
31
+ left: leftValue.value,
32
+ right: rightValue.value,
33
+ point: {
34
+ date: new Date(row.date),
35
+ observedAt: new Date(row.date),
36
+ availableAt: Number.isFinite(availability) ? new Date(availability) : undefined,
37
+ value: null,
38
+ provenance: { quality: "derived" as const },
39
+ },
40
+ }];
41
+ });
42
+ }
43
+
44
+ function pairedFrequency(left: ResolvedSeries, right: ResolvedSeries): SeriesPeriod {
45
+ return left.nativeFrequency === right.nativeFrequency ? left.nativeFrequency : "auto";
46
+ }
47
+
48
+ function pairedHistoryResolution(left: ResolvedSeries, right: ResolvedSeries): ManualChartResolution | null | undefined {
49
+ return left.historyResolution === right.historyResolution ? left.historyResolution : null;
50
+ }
51
+
52
+ export function resolvePairStudy(
53
+ spec: ChartStudySpec,
54
+ left: ResolvedSeries,
55
+ right: ResolvedSeries,
56
+ color: string,
57
+ ): ResolvedSeries[] {
58
+ const paired = pairedSamples(left, right, spec.kind !== "correlation");
59
+ if (spec.kind === "ratio" || spec.kind === "spread") {
60
+ const multiplier = isFiniteNumber(spec.parameters.multiplier) ? spec.parameters.multiplier : 1;
61
+ const points = paired.map((sample) => derivedPoint(
62
+ { point: sample.point, value: sample.left },
63
+ spec.kind === "ratio"
64
+ ? sample.right === 0 ? null : sample.left / sample.right
65
+ : sample.left - sample.right * multiplier,
66
+ ));
67
+ const ratioStudy = spec.kind === "ratio";
68
+ const outputUnit = ratioStudy
69
+ ? ratioUnit(left, right)
70
+ : { unit: left.unit, unitGroup: left.unitGroup };
71
+ // A price over an FX rate is a price in the other currency (EUR / barrel),
72
+ // stated as a future's unit is; a plain ratio keeps its own decimals.
73
+ const convertedPrice = ratioStudy && isPriceGroup(outputUnit.unitGroup);
74
+ const valueDecimals = !ratioStudy || convertedPrice ? derivedValueDecimals([left, right], points) : undefined;
75
+ return [outputSeries(spec, left, {
76
+ ...(convertedPrice ? { statedUnit: outputUnit.unit, priceAssetCategory: null } : {}),
77
+ ...(valueDecimals !== undefined ? { valueDecimals } : {}),
78
+ label: ratioStudy
79
+ ? `${left.label} / ${right.label}`
80
+ : `${left.label} - ${multiplier === 1 ? "" : `${multiplier}×`}${right.label}`,
81
+ points,
82
+ color,
83
+ unit: outputUnit.unit,
84
+ unitGroup: outputUnit.unitGroup,
85
+ // Pair formulas are calculated with as-of carry on the union of both
86
+ // inputs' event dates. Their value is therefore piecewise constant until
87
+ // either input changes, even when an input is displayed as columns.
88
+ style: "step",
89
+ interpolation: "step-after",
90
+ axis: "left",
91
+ nativeFrequency: pairedFrequency(left, right),
92
+ historyResolution: pairedHistoryResolution(left, right),
93
+ })];
94
+ }
95
+
96
+ const period = studyPeriod(spec, 20);
97
+ const useReturns = spec.parameters.returns !== 0;
98
+ const values = useReturns
99
+ ? paired.slice(1).flatMap((sample, index) => {
100
+ const previous = paired[index]!;
101
+ if (previous.left === 0 || previous.right === 0) return [];
102
+ return [{
103
+ point: sample.point,
104
+ left: (sample.left - previous.left) / Math.abs(previous.left),
105
+ right: (sample.right - previous.right) / Math.abs(previous.right),
106
+ }];
107
+ })
108
+ : paired;
109
+ const points: TimeSeriesPoint[] = [];
110
+ for (let index = period - 1; index < values.length; index += 1) {
111
+ const window = values.slice(index - period + 1, index + 1);
112
+ const leftMean = window.reduce((sum, item) => sum + item.left, 0) / period;
113
+ const rightMean = window.reduce((sum, item) => sum + item.right, 0) / period;
114
+ let covariance = 0;
115
+ let leftVariance = 0;
116
+ let rightVariance = 0;
117
+ for (const item of window) {
118
+ const leftDelta = item.left - leftMean;
119
+ const rightDelta = item.right - rightMean;
120
+ covariance += leftDelta * rightDelta;
121
+ leftVariance += leftDelta ** 2;
122
+ rightVariance += rightDelta ** 2;
123
+ }
124
+ const denominator = Math.sqrt(leftVariance * rightVariance);
125
+ const value = denominator === 0 ? null : Math.max(-1, Math.min(1, covariance / denominator));
126
+ points.push(derivedPoint({ point: values[index]!.point, value: values[index]!.left }, value));
127
+ }
128
+ return [outputSeries(spec, left, {
129
+ label: `Correlation(${period}) ${left.label} / ${right.label}`,
130
+ points,
131
+ color,
132
+ unit: "correlation",
133
+ unitGroup: "correlation",
134
+ axis: "left",
135
+ nativeFrequency: pairedFrequency(left, right),
136
+ historyResolution: pairedHistoryResolution(left, right),
137
+ })];
138
+ }
@@ -0,0 +1,333 @@
1
+ import { effectiveTimeSeriesPointTime } from "../alignment";
2
+ import { mergePriceHistoryIntegrity } from "../../utils/price-history-integrity";
3
+ import { isRealizedVolatilityEstimator, realizedVolatilityCadenceIssue } from "../../market-data/realized-volatility";
4
+ import type { ManualChartResolution } from "../resolution";
5
+ import type { ChartStudyKind, ChartStudySpec, ResolvedSeries, TimeSeriesPoint } from "../types";
6
+ import { studyMessageName } from "../series-name";
7
+ import { isLeverageFactor, leverageFactorLabel, leverageSymbol, type StudyAnchor } from "../leverage";
8
+ import { KIND_COLORS, STUDY_COLORS } from "./shared";
9
+ import {
10
+ resolveAtr,
11
+ resolveBollinger,
12
+ resolveMacd,
13
+ resolveMovingAverage,
14
+ resolveRealizedVolatility,
15
+ resolveRsi,
16
+ resolveVolume,
17
+ studyWarmupPoints,
18
+ } from "./indicators";
19
+ import { resolveAnchoredVwap, resolveVolumeProfile, resolveVwap } from "./vwap";
20
+ import {
21
+ fxConversionIssue,
22
+ knownUnitSignature,
23
+ legName,
24
+ ratioUnit,
25
+ seriesCurrency,
26
+ unitCurrencies,
27
+ unitMismatchMessage,
28
+ type SeriesUnit,
29
+ } from "./units";
30
+ import { resolvePairStudy } from "./pair";
31
+ import { formulaTerms, formulaUnit, resolveFormula, type FormulaTerms } from "./formula";
32
+ import { dailyBarsIssue, resolveLeverageDecay, resolveLeverageLine } from "./leverage";
33
+
34
+ export interface StudyResolutionResult {
35
+ series: ResolvedSeries[];
36
+ warnings: string[];
37
+ errors: string[];
38
+ }
39
+
40
+ export function maxStudyWarmupPoints(specs: readonly ChartStudySpec[]): number {
41
+ return Math.max(0, ...specs.filter((spec) => spec.visible !== false).map(studyWarmupPoints));
42
+ }
43
+
44
+ /** Formulas of levels, which carry each input's latest value to the other inputs' dates. */
45
+ /** The study's own colour, or the next after it no series or formula on the chart has taken. */
46
+ function freeStudyColor(index: number, used: ReadonlySet<string>): string {
47
+ for (let offset = 0; offset < STUDY_COLORS.length; offset += 1) {
48
+ const color = STUDY_COLORS[(index + offset) % STUDY_COLORS.length]!;
49
+ if (!used.has(color.toLowerCase())) return color;
50
+ }
51
+ return STUDY_COLORS[index % STUDY_COLORS.length]!;
52
+ }
53
+
54
+ function isLevelFormula(kind: ChartStudyKind): boolean {
55
+ return kind === "ratio" || kind === "spread" || kind === "formula";
56
+ }
57
+
58
+ function requiredInputs(kind: ChartStudyKind): number {
59
+ return kind === "ratio" || kind === "spread" || kind === "correlation" || kind === "leverage-decay" ? 2 : 1;
60
+ }
61
+
62
+ /**
63
+ * A contradictory observation interrupts the input history. Running studies on
64
+ * each continuous segment keeps finite windows from skipping the missing bar
65
+ * and makes recursive indicators warm up again from valid observations.
66
+ */
67
+ function resolveInterruptedStudy(
68
+ inputs: readonly ResolvedSeries[],
69
+ spec: ChartStudySpec,
70
+ nameVolumeInput: boolean,
71
+ ): StudyResolutionResult | null {
72
+ const gaps = new Map<number, TimeSeriesPoint>();
73
+ for (const input of inputs) {
74
+ for (const point of input.points) {
75
+ const integrity = point.provenance?.priceHistoryIntegrity;
76
+ const priceIssues = point.provenance?.valuationPriceIssues;
77
+ if (!integrity && !priceIssues?.length) continue;
78
+ const timestamp = effectiveTimeSeriesPointTime(point);
79
+ const previous = gaps.get(timestamp)?.provenance;
80
+ gaps.set(timestamp, {
81
+ date: new Date(timestamp), observedAt: new Date(timestamp), value: null,
82
+ provenance: {
83
+ quality: "derived",
84
+ ...(integrity || previous?.priceHistoryIntegrity ? { priceHistoryIntegrity: previous?.priceHistoryIntegrity && integrity
85
+ ? mergePriceHistoryIntegrity(previous.priceHistoryIntegrity, integrity) : integrity ?? previous?.priceHistoryIntegrity } : {}),
86
+ ...(priceIssues?.length || previous?.valuationPriceIssues?.length
87
+ ? { valuationPriceIssues: [...(previous?.valuationPriceIssues ?? []), ...(priceIssues ?? [])] } : {}),
88
+ },
89
+ });
90
+ }
91
+ }
92
+ if (!gaps.size) return null;
93
+ const times = [...gaps.keys()].sort((left, right) => left - right);
94
+ const outputs = new Map<string, ResolvedSeries>();
95
+ const warnings = new Set<string>();
96
+ const errors = new Set<string>();
97
+ const cursors = inputs.map((input) => ({
98
+ input,
99
+ points: [...input.points].sort((left, right) => effectiveTimeSeriesPointTime(left) - effectiveTimeSeriesPointTime(right)),
100
+ index: 0,
101
+ previous: undefined as TimeSeriesPoint | undefined,
102
+ }));
103
+ let start = Number.NEGATIVE_INFINITY;
104
+ for (const end of [...times, Number.POSITIVE_INFINITY]) {
105
+ const segmentInputs = cursors.map((cursor) => {
106
+ while (cursor.index < cursor.points.length && effectiveTimeSeriesPointTime(cursor.points[cursor.index]!) <= start) {
107
+ cursor.previous = cursor.points[cursor.index++];
108
+ }
109
+ const previous = cursor.previous;
110
+ const points: TimeSeriesPoint[] = [];
111
+ while (cursor.index < cursor.points.length && effectiveTimeSeriesPointTime(cursor.points[cursor.index]!) < end) {
112
+ const point = cursor.points[cursor.index++]!;
113
+ points.push(point);
114
+ cursor.previous = point;
115
+ }
116
+ // A valid unaffected peer remains usable for as-of ratio/spread levels.
117
+ // Correlations instead restart their shared observations and returns.
118
+ if (isLevelFormula(spec.kind)) {
119
+ if (previous && !previous.provenance?.priceHistoryIntegrity && !previous.provenance?.valuationPriceIssues?.length) points.unshift(previous);
120
+ }
121
+ return { ...cursor.input, points };
122
+ });
123
+ const segment = resolveStudySpecs(segmentInputs, [spec], nameVolumeInput);
124
+ for (const output of segment.series) {
125
+ const points = output.points.filter((point) => effectiveTimeSeriesPointTime(point) > start);
126
+ const gap = gaps.get(start);
127
+ if (gap && !isLevelFormula(spec.kind) && spec.kind !== "volume") {
128
+ // A gap just outside the visible window still invalidates the next
129
+ // warmup observations. Keep those nulls and their original diagnostic
130
+ // explicit so clipping cannot hide why a study is unavailable.
131
+ const firstComputed = points[0] ? effectiveTimeSeriesPointTime(points[0]) : Number.POSITIVE_INFINITY;
132
+ points.unshift(...segmentInputs[0]!.points.filter((point) => effectiveTimeSeriesPointTime(point) < firstComputed).map((point) => ({
133
+ date: new Date(point.date), observedAt: new Date(point.observedAt),
134
+ availableAt: point.availableAt ? new Date(point.availableAt) : undefined,
135
+ value: null, provenance: gap.provenance,
136
+ })));
137
+ }
138
+ const previous = outputs.get(output.id);
139
+ outputs.set(output.id, { ...output, points: [...(previous?.points ?? []), ...points] });
140
+ }
141
+ for (const warning of segment.warnings) {
142
+ if (!warning.includes("not enough valid history")) warnings.add(warning);
143
+ }
144
+ for (const error of segment.errors) errors.add(error);
145
+ start = end;
146
+ }
147
+ for (const output of outputs.values()) {
148
+ output.points.push(...gaps.values());
149
+ output.points.sort((left, right) => left.date.getTime() - right.date.getTime());
150
+ }
151
+ return { series: [...outputs.values()], warnings: [...warnings], errors: [...errors] };
152
+ }
153
+
154
+ /** Base series that must be calculated for currently visible studies. */
155
+ export function activeStudyInputSeriesIds(
156
+ studySpecs: readonly ChartStudySpec[],
157
+ ): Set<string> {
158
+ return new Set(studySpecs
159
+ .filter((spec) => spec.visible !== false)
160
+ .flatMap((spec) => spec.inputSeriesIds));
161
+ }
162
+
163
+ /**
164
+ * `anchors`: where each input series is measured from and to, for studies
165
+ * that read returns over the chart's period (a leveraged fund's daily line and
166
+ * its decay start where a comparison starts its legs). Without one, a series'
167
+ * own first and last observations.
168
+ */
169
+ export function resolveStudies(
170
+ baseSeries: readonly ResolvedSeries[],
171
+ studySpecs: readonly ChartStudySpec[],
172
+ marketResolution?: ManualChartResolution,
173
+ historicalPriceSeries?: ReadonlyMap<string, ResolvedSeries>,
174
+ anchors?: ReadonlyMap<string, StudyAnchor>,
175
+ ): StudyResolutionResult {
176
+ // With one security on the chart its volume is just "Volume"; beside a
177
+ // comparison the legend names whose volume it is.
178
+ const nameVolumeInput = baseSeries.filter((series) => series.observationKind === "market").length > 1;
179
+ return resolveStudySpecs(baseSeries, studySpecs, nameVolumeInput, marketResolution, historicalPriceSeries, anchors);
180
+ }
181
+
182
+ function resolveStudySpecs(
183
+ baseSeries: readonly ResolvedSeries[],
184
+ studySpecs: readonly ChartStudySpec[],
185
+ nameVolumeInput: boolean,
186
+ marketResolution?: ManualChartResolution,
187
+ historicalPriceSeries?: ReadonlyMap<string, ResolvedSeries>,
188
+ anchors?: ReadonlyMap<string, StudyAnchor>,
189
+ ): StudyResolutionResult {
190
+ const byId = new Map(baseSeries.map((series) => [series.id, series]));
191
+ // A formula never takes a colour a series on the chart has: a crack in euros is not drawn in its EURUSD leg's yellow.
192
+ const usedColors = new Set(baseSeries.map((series) => series.color.toLowerCase()));
193
+ const resolved: ResolvedSeries[] = [];
194
+ const warnings: string[] = [];
195
+ const errors: string[] = [];
196
+
197
+ studySpecs.forEach((spec, index) => {
198
+ if (spec.visible === false) return;
199
+ const required = spec.kind === "formula" ? Math.max(1, spec.inputSeriesIds.length) : requiredInputs(spec.kind);
200
+ const inputs = spec.inputSeriesIds.map((id) => spec.kind === "realized-vol"
201
+ ? historicalPriceSeries?.get(id) ?? byId.get(id) : byId.get(id));
202
+ if (inputs.length !== required || inputs.some((input) => !input)) {
203
+ errors.push(`${studyMessageName(spec)}: ${spec.kind} requires ${required} valid input series.`);
204
+ return;
205
+ }
206
+ const input = inputs[0]!;
207
+ if (spec.kind === "spread") {
208
+ const pairedInput = inputs[1]!;
209
+ const inputUnit = knownUnitSignature(input);
210
+ if (inputUnit === null || inputUnit !== knownUnitSignature(pairedInput)) {
211
+ errors.push(`${studyMessageName(spec)}: ${unitMismatchMessage("spread", { series: input, unit: input.unit }, { series: pairedInput, unit: pairedInput.unit })}`);
212
+ return;
213
+ }
214
+ }
215
+ let formula: { terms: FormulaTerms; unit: SeriesUnit } | null = null;
216
+ if (spec.kind === "formula") {
217
+ const terms = formulaTerms(spec, inputs as ResolvedSeries[]);
218
+ const unit = "error" in terms ? terms : formulaUnit(terms, inputs as ResolvedSeries[]);
219
+ if ("error" in unit) {
220
+ errors.push(`${studyMessageName(spec)}: ${unit.error}`);
221
+ return;
222
+ }
223
+ formula = { terms: terms as FormulaTerms, unit };
224
+ }
225
+ const color = spec.color ?? KIND_COLORS[spec.kind]
226
+ ?? (isLevelFormula(spec.kind) ? freeStudyColor(index, usedColors) : STUDY_COLORS[index % STUDY_COLORS.length]!);
227
+ if (isLevelFormula(spec.kind)) usedColors.add(color.toLowerCase());
228
+ if (spec.kind === "leverage" || spec.kind === "leverage-decay") {
229
+ const index = inputs.at(-1)!;
230
+ if (!isLeverageFactor(spec.parameters.factor)) {
231
+ errors.push(`${studyMessageName(spec)}: choose a nonzero leverage factor of at most 5.`);
232
+ return;
233
+ }
234
+ const issue = inputs.map((entry) => dailyBarsIssue(spec, entry!, marketResolution)).find(Boolean);
235
+ if (issue) {
236
+ warnings.push(issue);
237
+ return;
238
+ }
239
+ const outputs = spec.kind === "leverage"
240
+ ? resolveLeverageLine(spec, index, color, anchors?.get(index.id))
241
+ : resolveLeverageDecay(spec, input, index, anchors);
242
+ if (outputs.every((output) => output.points.length < 2)) {
243
+ warnings.push(`${studyMessageName(spec)}: not enough shared daily closes to measure ${leverageFactorLabel(spec.parameters.factor as number)} daily ${leverageSymbol(index)}.`);
244
+ }
245
+ resolved.push(...outputs);
246
+ return;
247
+ }
248
+ if (spec.kind === "realized-vol") {
249
+ const window = spec.parameters.window ?? 30;
250
+ if (typeof window !== "number" || !Number.isInteger(window) || window < 2
251
+ || (spec.parameters.estimator !== undefined && !isRealizedVolatilityEstimator(spec.parameters.estimator))) {
252
+ errors.push(`${studyMessageName(spec)}: choose a supported volatility estimator and a whole window of at least two sessions.`);
253
+ return;
254
+ }
255
+ const inputResolution = input.historyResolution === undefined ? marketResolution : input.historyResolution;
256
+ if (input.nativeFrequency !== "daily"
257
+ || (inputResolution !== undefined && inputResolution !== "1d")
258
+ || (input.timeBasis?.cadenceMs !== undefined && input.timeBasis.cadenceMs !== 86_400_000)
259
+ || !(input.unitGroup === "price" || input.unitGroup.startsWith("price:"))) {
260
+ errors.push(`${studyMessageName(spec)}: realized volatility requires daily prices. Choose Auto or 1D resolution and a daily price source.`);
261
+ return;
262
+ }
263
+ const cadenceIssue = realizedVolatilityCadenceIssue(input.points);
264
+ if (cadenceIssue) {
265
+ errors.push(`${studyMessageName(spec)}: ${cadenceIssue}`);
266
+ return;
267
+ }
268
+ const outputs = resolveRealizedVolatility(spec, input, color);
269
+ if (outputs.every((output) => output.points.every((point) => point.value === null))) {
270
+ warnings.push(`${studyMessageName(spec)}: not enough valid daily history to calculate ${spec.parameters.estimator ?? "close-to-close"} realized volatility.`);
271
+ }
272
+ resolved.push(...outputs);
273
+ return;
274
+ }
275
+ const interrupted = resolveInterruptedStudy(inputs as ResolvedSeries[], { ...spec, color }, nameVolumeInput);
276
+ if (interrupted) {
277
+ resolved.push(...interrupted.series);
278
+ warnings.push(...interrupted.warnings);
279
+ errors.push(...interrupted.errors);
280
+ return;
281
+ }
282
+ let outputs: ResolvedSeries[] = [];
283
+ if (spec.kind === "sma") outputs = resolveMovingAverage(spec, input, color, false);
284
+ else if (spec.kind === "ema") outputs = resolveMovingAverage(spec, input, color, true);
285
+ else if (spec.kind === "bollinger") outputs = resolveBollinger(spec, input, color);
286
+ else if (spec.kind === "rsi") outputs = resolveRsi(spec, input, color);
287
+ else if (spec.kind === "macd") outputs = resolveMacd(spec, input, color);
288
+ else if (spec.kind === "atr") outputs = resolveAtr(spec, input, color, nameVolumeInput);
289
+ else if (spec.kind === "vwap" || spec.kind === "anchored-vwap" || spec.kind === "volume-profile") {
290
+ outputs = spec.kind === "vwap"
291
+ ? resolveVwap(spec, input, color, nameVolumeInput, marketResolution, warnings)
292
+ : spec.kind === "anchored-vwap"
293
+ ? resolveAnchoredVwap(spec, input, color, marketResolution, warnings)
294
+ : resolveVolumeProfile(spec, input, color, warnings);
295
+ resolved.push(...outputs);
296
+ return;
297
+ }
298
+ else if (spec.kind === "formula" && formula) outputs = resolveFormula(spec, inputs as ResolvedSeries[], formula.terms, formula.unit, color);
299
+ else if (spec.kind === "volume") {
300
+ outputs = resolveVolume(spec, input, color, nameVolumeInput);
301
+ if (!input.volumeUnit && outputs.some((output) => output.points.length > 0)) {
302
+ warnings.push(`Volume unit unknown: ${input.label}.`);
303
+ }
304
+ }
305
+ else {
306
+ const pairedInput = inputs[1]!;
307
+ const inputCurrency = seriesCurrency(input);
308
+ const pairedCurrency = seriesCurrency(pairedInput);
309
+ const differentCurrencies = inputCurrency !== null
310
+ && pairedCurrency !== null
311
+ && inputCurrency !== pairedCurrency;
312
+ // Over an FX rate a price is converted, leaving one currency: EUR / barrel.
313
+ const ratio = spec.kind === "ratio" ? ratioUnit(input, pairedInput) : null;
314
+ if (ratio && differentCurrencies && unitCurrencies(ratio.unit).size !== 1) {
315
+ const issue = fxConversionIssue(input, pairedInput, -1, ratio, legName(input));
316
+ warnings.push(`${studyMessageName(spec)}: ${issue
317
+ ?? `${spec.kind} inputs use different currencies (${inputCurrency} and ${pairedCurrency}); raw values are not FX-converted.`}`);
318
+ }
319
+ if (spec.kind === "correlation" && input.nativeFrequency !== pairedInput.nativeFrequency) {
320
+ warnings.push(`${studyMessageName(spec)}: correlation mixes ${input.nativeFrequency} and ${pairedInput.nativeFrequency} observations; only matching observation times contribute.`);
321
+ }
322
+ outputs = resolvePairStudy(spec, input, pairedInput, color);
323
+ }
324
+ if (
325
+ spec.kind !== "volume"
326
+ && (outputs.length === 0 || outputs.every((output) => output.points.length === 0))
327
+ ) {
328
+ warnings.push(`${studyMessageName(spec)}: not enough valid history to calculate ${spec.kind}.`);
329
+ }
330
+ resolved.push(...outputs);
331
+ });
332
+ return { series: resolved, warnings, errors };
333
+ }
@@ -0,0 +1,141 @@
1
+ import { scalarPointValue } from "../alignment";
2
+ import type { ManualChartResolution } from "../resolution";
3
+ import type { StudyBar } from "../trader-studies";
4
+ import type {
5
+ ChartStudyKind,
6
+ ChartStudySpec,
7
+ ResolvedSeries,
8
+ SeriesAxis,
9
+ SeriesInterpolation,
10
+ SeriesPeriod,
11
+ SeriesStyle,
12
+ TimeSeriesPoint,
13
+ } from "../types";
14
+ import { isFiniteNumber } from "../../utils/guards";
15
+
16
+ export interface NumericSample {
17
+ point: TimeSeriesPoint;
18
+ value: number;
19
+ }
20
+
21
+ export const STUDY_COLORS = ["#f6c85f", "#4dabf7", "#b197fc", "#63e6be", "#ffa94d", "#ff6b6b"];
22
+ /**
23
+ * Overlays that sit on the price keep colours of their own, never the blue the
24
+ * first price series takes; the profile is grey so it stays behind the bars.
25
+ */
26
+ export const KIND_COLORS: Partial<Record<ChartStudyKind, string>> = {
27
+ vwap: "#ffa94d",
28
+ "anchored-vwap": "#b197fc",
29
+ "volume-profile": "#adb5bd",
30
+ atr: "#63e6be",
31
+ leverage: "#e599f7",
32
+ };
33
+ /** The decay study's two lines: the fund against the daily line, the daily line against the period multiple. */
34
+ export const DECAY_COLORS = ["#ffa94d", "#66d9e8"] as const;
35
+ /**
36
+ * One per anchor, so eight lines never repeat a colour. None is red or pink
37
+ * (falling candles take the theme's negative), orange (VWAP), yellow (the
38
+ * first moving average) or blue (the price).
39
+ */
40
+ export const ANCHOR_COLORS = ["#b197fc", "#66d9e8", "#8ce99a", "#e599f7", "#9775fa", "#3bc9db", "#c0eb75", "#d0bfff"];
41
+
42
+ export function positiveInteger(value: unknown, fallback: number): number {
43
+ return isFiniteNumber(value) && value > 0 ? Math.max(1, Math.floor(value)) : fallback;
44
+ }
45
+
46
+ export function samplesFor(series: ResolvedSeries): NumericSample[] {
47
+ return [...series.points]
48
+ .sort((left, right) => left.date.getTime() - right.date.getTime())
49
+ .flatMap((point) => {
50
+ const value = scalarPointValue(point);
51
+ return value === null ? [] : [{ point, value }];
52
+ });
53
+ }
54
+
55
+ export function derivedPoint(sample: NumericSample, value: number | null): TimeSeriesPoint {
56
+ return {
57
+ date: new Date(sample.point.date),
58
+ observedAt: new Date(sample.point.observedAt),
59
+ availableAt: sample.point.availableAt ? new Date(sample.point.availableAt) : undefined,
60
+ value,
61
+ periodLabel: sample.point.periodLabel,
62
+ provenance: {
63
+ providerId: sample.point.provenance?.providerId,
64
+ quality: "derived",
65
+ },
66
+ };
67
+ }
68
+
69
+ export function outputSeries(
70
+ spec: ChartStudySpec,
71
+ input: ResolvedSeries,
72
+ options: {
73
+ id?: string;
74
+ label: string;
75
+ points: TimeSeriesPoint[];
76
+ color: string;
77
+ unit?: string;
78
+ unitGroup?: string;
79
+ style?: SeriesStyle;
80
+ interpolation?: SeriesInterpolation;
81
+ axis?: Exclude<SeriesAxis, "auto">;
82
+ nativeFrequency?: SeriesPeriod;
83
+ historyResolution?: ManualChartResolution | null;
84
+ /** The unit the legend states; by default the input's, while the output keeps its unit. */
85
+ statedUnit?: string | null;
86
+ /** The kind of price the output is, for its decimals; by default the input's, while the output keeps its unit. */
87
+ priceAssetCategory?: string | null;
88
+ /** The decimals a value worked out from prices prints at. */
89
+ valueDecimals?: number;
90
+ },
91
+ ): ResolvedSeries {
92
+ const historyResolution = options.historyResolution === undefined ? input.historyResolution : options.historyResolution;
93
+ const sameUnit = (options.unit ?? input.unit) === input.unit && (options.unitGroup ?? input.unitGroup) === input.unitGroup;
94
+ const statedUnit = options.statedUnit === undefined ? sameUnit ? input.statedUnit : undefined : options.statedUnit ?? undefined;
95
+ return {
96
+ id: options.id ?? spec.id,
97
+ label: options.label,
98
+ color: options.color,
99
+ unit: options.unit ?? input.unit,
100
+ unitGroup: options.unitGroup ?? input.unitGroup,
101
+ ...(statedUnit ? { statedUnit } : {}),
102
+ ...(options.valueDecimals !== undefined ? { valueDecimals: options.valueDecimals } : {}),
103
+ priceAssetCategory: options.priceAssetCategory === undefined
104
+ ? sameUnit ? input.priceAssetCategory : undefined
105
+ : options.priceAssetCategory ?? undefined,
106
+ nativeFrequency: options.nativeFrequency ?? input.nativeFrequency,
107
+ ...(historyResolution !== undefined ? { historyResolution } : {}),
108
+ ...(input.historyStart && historyResolution === input.historyResolution ? { historyStart: input.historyStart } : {}),
109
+ ...(input.barSize && historyResolution === input.historyResolution ? { barSize: input.barSize } : {}),
110
+ dataShape: "scalar",
111
+ style: options.style ?? "line",
112
+ transform: "raw",
113
+ axis: spec.axis === "auto" ? options.axis ?? input.axis : spec.axis,
114
+ panelId: spec.panelId,
115
+ interpolation: options.interpolation ?? "none",
116
+ timeBasis: historyResolution === null && input.timeBasis ? { ...input.timeBasis, cadenceMs: undefined } : input.timeBasis,
117
+ observationKind: input.observationKind,
118
+ points: options.points,
119
+ };
120
+ }
121
+
122
+ export function studyPeriod(spec: ChartStudySpec, fallback: number): number {
123
+ return positiveInteger(spec.parameters.period, fallback);
124
+ }
125
+
126
+ /** Bars with a range and volume; a close-only point stands for a bar with no range. */
127
+ export function studyBars(input: ResolvedSeries): Array<StudyBar & { sample: NumericSample }> {
128
+ return samplesFor(input).flatMap((sample) => {
129
+ const close = isFiniteNumber(sample.point.close) ? sample.point.close : sample.value;
130
+ const high = isFiniteNumber(sample.point.high) ? sample.point.high : close;
131
+ const low = isFiniteNumber(sample.point.low) ? sample.point.low : close;
132
+ return [{
133
+ time: sample.point.date.getTime(),
134
+ high: Math.max(high, low, close),
135
+ low: Math.min(high, low, close),
136
+ close,
137
+ volume: isFiniteNumber(sample.point.volume) ? sample.point.volume : 0,
138
+ sample,
139
+ }];
140
+ });
141
+ }