gloomberb 0.19.2 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. package/README.md +7 -1
  2. package/package.json +1 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +20 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app.tsx +23 -1
  20. package/src/architecture/core-builtin-allowlist.json +2 -1
  21. package/src/capabilities/factories.ts +1 -0
  22. package/src/cli/ascii-output.ts +42 -0
  23. package/src/cli/base-converter.ts +10 -2
  24. package/src/cli/command-options.ts +16 -1
  25. package/src/cli/commands/crypto-hints.ts +23 -4
  26. package/src/cli/commands/market.ts +92 -23
  27. package/src/cli/commands/news-search.ts +24 -5
  28. package/src/cli/commands/overview.ts +9 -4
  29. package/src/cli/commands/search.ts +71 -12
  30. package/src/cli/commands/system.ts +22 -30
  31. package/src/cli/commands/ticker.ts +49 -9
  32. package/src/cli/commands/version.ts +33 -0
  33. package/src/cli/context.ts +5 -9
  34. package/src/cli/data-dir.ts +7 -0
  35. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  36. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  37. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  38. package/src/cli/desktop-pane-shot.ts +196 -256
  39. package/src/cli/early-command.ts +42 -0
  40. package/src/cli/earnings-calendar.ts +9 -0
  41. package/src/cli/entry.ts +19 -13
  42. package/src/cli/errors.ts +12 -2
  43. package/src/cli/help.ts +1 -1
  44. package/src/cli/history-export.ts +22 -2
  45. package/src/cli/history-rows.ts +154 -4
  46. package/src/cli/index.ts +18 -1
  47. package/src/cli/known-gaps.ts +40 -5
  48. package/src/cli/listing-arg.ts +46 -9
  49. package/src/cli/options.ts +1 -1
  50. package/src/cli/output-file.ts +60 -13
  51. package/src/cli/pane-functions/catalog.ts +14 -3
  52. package/src/cli/pane-functions/data.ts +1 -1
  53. package/src/cli/pane-functions/dom.ts +45 -0
  54. package/src/cli/pane-functions/freshness.ts +46 -9
  55. package/src/cli/pane-functions/function-help.ts +17 -9
  56. package/src/cli/pane-functions/headless.ts +22 -14
  57. package/src/cli/pane-functions/index.ts +6 -2
  58. package/src/cli/pane-functions/options.ts +6 -2
  59. package/src/cli/pane-functions/resolver.ts +27 -5
  60. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  61. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  62. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  63. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  64. package/src/cli/quote-answer.ts +20 -0
  65. package/src/cli/registry.ts +41 -13
  66. package/src/cli/result.ts +12 -6
  67. package/src/cli/ticker-resolution.ts +2 -1
  68. package/src/cli/types.ts +2 -0
  69. package/src/components/chart/composite/format.ts +3 -0
  70. package/src/components/chart-table/header.tsx +1 -1
  71. package/src/components/chart-table/layout.ts +28 -11
  72. package/src/components/command-bar/action-runtime.ts +4 -0
  73. package/src/components/command-bar/commands/direct/index.ts +18 -33
  74. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  75. package/src/components/command-bar/commands/registry.ts +10 -1
  76. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  77. package/src/components/command-bar/routes/root/runtime.ts +24 -1
  78. package/src/components/command-bar/surface/index.tsx +9 -3
  79. package/src/components/company-logo.tsx +2 -1
  80. package/src/components/layout/header.tsx +8 -3
  81. package/src/components/layout/market-summary.ts +25 -7
  82. package/src/components/layout/pane/footer/index.tsx +7 -2
  83. package/src/components/layout/pane/footer/model.ts +40 -5
  84. package/src/components/market-board/index.tsx +3 -2
  85. package/src/components/onboarding/desks-step.tsx +12 -4
  86. package/src/components/onboarding/first-run-workspace.ts +81 -4
  87. package/src/components/onboarding/onboarding-frame.tsx +2 -1
  88. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  89. package/src/components/onboarding/onboarding-wizard.tsx +54 -23
  90. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  91. package/src/components/onboarding/run-setup-again.ts +24 -0
  92. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  93. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  94. package/src/components/onboarding/wizard-model.ts +57 -3
  95. package/src/components/onboarding/wizard-positions.ts +1 -1
  96. package/src/components/ui/delayed-mark.tsx +56 -0
  97. package/src/components/ui/external-link.tsx +3 -6
  98. package/src/components/ui/icon.tsx +8 -1
  99. package/src/components/ui/logo-mark.tsx +8 -5
  100. package/src/components/ui/stat-grid.tsx +263 -19
  101. package/src/data/config/store/node.ts +11 -5
  102. package/src/data/config/store/normalize.ts +16 -3
  103. package/src/data/profile-permissions.ts +47 -0
  104. package/src/data/resource-store.ts +34 -31
  105. package/src/data/sqlite/access.ts +19 -8
  106. package/src/data/sqlite/checkpointer.ts +95 -8
  107. package/src/data/sqlite/database.ts +40 -7
  108. package/src/docs/snapshot.json +191 -78
  109. package/src/i18n/es.ts +18 -2
  110. package/src/i18n/ja.ts +18 -2
  111. package/src/i18n/ko.ts +18 -2
  112. package/src/i18n/zh-cn.ts +18 -2
  113. package/src/i18n/zh-tw.ts +18 -2
  114. package/src/layout/desk-keys.ts +4 -1
  115. package/src/layout/desks.ts +38 -5
  116. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  117. package/src/market-data/batched-reads.ts +16 -0
  118. package/src/market-data/coordinator/auxiliary.ts +4 -2
  119. package/src/market-data/coordinator/index.ts +11 -2
  120. package/src/market-data/futures-board.ts +1 -1
  121. package/src/market-data/futures-contract.ts +3 -1
  122. package/src/market-data/market/index-level.ts +6 -4
  123. package/src/market-data/published-apac-sessions.ts +6 -0
  124. package/src/news/aggregator.ts +7 -4
  125. package/src/news/news-model.ts +11 -0
  126. package/src/news/story-id.ts +22 -0
  127. package/src/news/ticker-symbols.ts +17 -1
  128. package/src/notifications/app-notifier.ts +35 -14
  129. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  130. package/src/plugins/builtin/account-management/index.tsx +2 -3
  131. package/src/plugins/builtin/account-management/model.ts +1 -1
  132. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  133. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  134. package/src/plugins/builtin/alerts/events.ts +10 -2
  135. package/src/plugins/builtin/alerts/index.tsx +5 -5
  136. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  137. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  138. package/src/plugins/builtin/alerts/targets.ts +4 -1
  139. package/src/plugins/builtin/attention/headless.ts +1 -1
  140. package/src/plugins/builtin/attention/index.tsx +2 -4
  141. package/src/plugins/builtin/attention/listing.ts +1 -1
  142. package/src/plugins/builtin/attention/series.ts +1 -1
  143. package/src/plugins/builtin/awards/index.tsx +1 -2
  144. package/src/plugins/builtin/backtest/client.ts +1 -0
  145. package/src/plugins/builtin/backtest/index.tsx +1 -2
  146. package/src/plugins/builtin/backtest/rules.ts +3 -1
  147. package/src/plugins/builtin/backtest/window.ts +8 -7
  148. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  149. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  150. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  151. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  152. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  153. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  154. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  155. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  156. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  157. package/src/plugins/builtin/buildout/format.ts +9 -0
  158. package/src/plugins/builtin/buildout/index.tsx +51 -5
  159. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  160. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  161. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  162. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  163. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  164. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  165. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  166. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  167. package/src/plugins/builtin/cds/headless.ts +10 -2
  168. package/src/plugins/builtin/cds/model.ts +7 -3
  169. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  170. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  171. package/src/plugins/builtin/changelog/index.tsx +3 -2
  172. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
  173. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  174. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  175. package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
  176. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  177. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  178. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  179. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  180. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  181. package/src/plugins/builtin/chat/index.tsx +5 -5
  182. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  183. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  184. package/src/plugins/builtin/cloud/index.tsx +1 -2
  185. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  186. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  187. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  188. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  189. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  190. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  191. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  192. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  193. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  194. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  195. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  196. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  197. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  198. package/src/plugins/builtin/connections/index.ts +2 -3
  199. package/src/plugins/builtin/cot/index.tsx +1 -2
  200. package/src/plugins/builtin/cpi/index.tsx +1 -2
  201. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  202. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  203. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  204. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  205. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  206. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  207. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  208. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  209. package/src/plugins/builtin/crypto-board/series.ts +2 -0
  210. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  211. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  212. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  213. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  214. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  215. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  216. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  217. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  218. package/src/plugins/builtin/debug/index.tsx +1 -1
  219. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  220. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  221. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  222. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  223. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  224. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  225. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  226. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  227. package/src/plugins/builtin/docs/index.tsx +2 -3
  228. package/src/plugins/builtin/doe/headless.ts +16 -3
  229. package/src/plugins/builtin/doe/index.tsx +5 -4
  230. package/src/plugins/builtin/earnings/board.tsx +2 -1
  231. package/src/plugins/builtin/earnings/history.tsx +2 -1
  232. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  233. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  234. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  235. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  236. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  237. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  238. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  239. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  240. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  241. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  242. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  243. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  244. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  245. package/src/plugins/builtin/exposure/index.tsx +1 -2
  246. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  247. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  248. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  249. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  250. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  251. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  252. package/src/plugins/builtin/futures/index.tsx +15 -0
  253. package/src/plugins/builtin/futures/model.ts +14 -0
  254. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  255. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  256. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  257. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  258. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  259. package/src/plugins/builtin/gpu/board.tsx +51 -12
  260. package/src/plugins/builtin/gpu/client.ts +15 -2
  261. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  262. package/src/plugins/builtin/gpu/headless.ts +14 -2
  263. package/src/plugins/builtin/gpu/index.tsx +1 -2
  264. package/src/plugins/builtin/gpu/model.ts +20 -2
  265. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  266. package/src/plugins/builtin/help/index.tsx +1 -1
  267. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  268. package/src/plugins/builtin/holders/index.tsx +2 -3
  269. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  270. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  271. package/src/plugins/builtin/jodi/index.tsx +1 -2
  272. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  273. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  274. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  275. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  276. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  277. package/src/plugins/builtin/market-movers/index.tsx +7 -1
  278. package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
  279. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  280. package/src/plugins/builtin/members/index.tsx +1 -2
  281. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  282. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  283. package/src/plugins/builtin/news/headless.ts +7 -0
  284. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  285. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  286. package/src/plugins/builtin/notes/index.tsx +3 -6
  287. package/src/plugins/builtin/openfda/index.tsx +1 -2
  288. package/src/plugins/builtin/options/view.tsx +6 -5
  289. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  290. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  291. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  292. package/src/plugins/builtin/perps/index.tsx +1 -2
  293. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  294. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  295. package/src/plugins/builtin/plugin-module.ts +16 -2
  296. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  297. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  298. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  299. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  300. package/src/plugins/builtin/power/index.tsx +1 -2
  301. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  302. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  303. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  304. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  305. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  306. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  307. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  308. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  309. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  310. package/src/plugins/builtin/research/index.tsx +5 -8
  311. package/src/plugins/builtin/research-search/index.tsx +1 -2
  312. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  313. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  314. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  315. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  316. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  317. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  318. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  319. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  320. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  321. package/src/plugins/builtin/sec/headless.ts +5 -1
  322. package/src/plugins/builtin/sec/index.tsx +4 -2
  323. package/src/plugins/builtin/sec/model.ts +2 -0
  324. package/src/plugins/builtin/sectors/index.tsx +11 -5
  325. package/src/plugins/builtin/series-inventory.ts +2 -0
  326. package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
  327. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  328. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  329. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  330. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  331. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  332. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  333. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  334. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  335. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  336. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  337. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  338. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  339. package/src/plugins/builtin/thirteenf/api.ts +116 -13
  340. package/src/plugins/builtin/thirteenf/data.ts +132 -30
  341. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  342. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  343. package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
  344. package/src/plugins/builtin/thirteenf/types.ts +12 -0
  345. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
  346. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  347. package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
  348. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  349. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  350. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  351. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  352. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  353. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  354. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  355. package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  357. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  358. package/src/plugins/builtin/volatility/index.tsx +1 -2
  359. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  360. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  361. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  362. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  363. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  364. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  365. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  366. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  367. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  368. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  369. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  370. package/src/plugins/bundle.ts +52 -2
  371. package/src/plugins/catalog-browser.ts +2 -2
  372. package/src/plugins/catalog-cli.ts +20 -0
  373. package/src/plugins/host-link.ts +13 -1
  374. package/src/plugins/installer.ts +15 -4
  375. package/src/plugins/loader.ts +43 -11
  376. package/src/plugins/peer-imports.ts +201 -0
  377. package/src/plugins/peer-retry.ts +26 -29
  378. package/src/plugins/plugin-process.ts +128 -0
  379. package/src/plugins/redirection-guard.ts +45 -0
  380. package/src/remote/endpoint.ts +16 -3
  381. package/src/renderers/browser/main.tsx +3 -2
  382. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  383. package/src/renderers/dom/desktop/icons.tsx +2 -0
  384. package/src/renderers/dom/host/image-surface.tsx +46 -21
  385. package/src/renderers/dom/styles.css +7 -0
  386. package/src/renderers/dom/toast-host.tsx +2 -0
  387. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  388. package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
  389. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  390. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  391. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  392. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  393. package/src/renderers/electrobun/bun/window/create.ts +4 -2
  394. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  395. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  396. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  397. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  398. package/src/renderers/electrobun/view/main.tsx +7 -2
  399. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  400. package/src/renderers/opentui/browser-open.ts +10 -4
  401. package/src/renderers/opentui/image/surface.tsx +1 -1
  402. package/src/renderers/opentui/start.tsx +12 -6
  403. package/src/renderers/opentui/toast-host.tsx +2 -1
  404. package/src/series/registry.ts +18 -0
  405. package/src/series/standalone.ts +10 -1
  406. package/src/series/types.ts +7 -0
  407. package/src/series/validate.ts +11 -2
  408. package/src/sources/gloomberb-cloud/index.ts +28 -5
  409. package/src/sources/gloomberb-cloud/news.ts +24 -3
  410. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  411. package/src/sources/provider-errors.ts +26 -3
  412. package/src/sources/provider-router/history.ts +5 -0
  413. package/src/sources/provider-router/index.ts +13 -0
  414. package/src/sync/core-contributors.ts +32 -0
  415. package/src/telemetry/attention-counts.ts +64 -6
  416. package/src/telemetry/attention-setting.ts +33 -0
  417. package/src/telemetry/crash-reports-node.ts +3 -1
  418. package/src/telemetry/stall-clocks.ts +93 -0
  419. package/src/telemetry/stall-reports-node.ts +12 -7
  420. package/src/telemetry/stall-reports.ts +34 -5
  421. package/src/test-support/preload.ts +9 -0
  422. package/src/tickers/company-name.ts +19 -0
  423. package/src/tickers/listing-fate.ts +14 -3
  424. package/src/tickers/saved-instrument.ts +17 -0
  425. package/src/tickers/search/home-listing.ts +13 -2
  426. package/src/tickers/search/index.ts +4 -1
  427. package/src/tickers/search/types.ts +6 -1
  428. package/src/time-series/bar-aggregation.ts +7 -1
  429. package/src/time-series/base-series.ts +3 -3
  430. package/src/time-series/chart-bounds.ts +1 -1
  431. package/src/time-series/chart-data.ts +5 -2
  432. package/src/time-series/chart-seed.ts +1 -1
  433. package/src/time-series/coin-session-close.ts +211 -0
  434. package/src/time-series/live-bars.ts +13 -2
  435. package/src/time-series/live-quotes.ts +1 -1
  436. package/src/time-series/official-close.ts +159 -0
  437. package/src/time-series/price-comparison.ts +55 -6
  438. package/src/time-series/resolve.ts +12 -3
  439. package/src/time-series/series-presentation.ts +1 -1
  440. package/src/time-series/studies/formula.ts +205 -0
  441. package/src/time-series/studies/indicators.ts +299 -0
  442. package/src/time-series/studies/leverage.ts +75 -0
  443. package/src/time-series/studies/pair.ts +138 -0
  444. package/src/time-series/studies/resolve.ts +333 -0
  445. package/src/time-series/studies/shared.ts +141 -0
  446. package/src/time-series/studies/units.ts +243 -0
  447. package/src/time-series/studies/vwap.ts +274 -0
  448. package/src/types/config.ts +18 -2
  449. package/src/types/data-provider.ts +23 -0
  450. package/src/types/financials.ts +22 -0
  451. package/src/types/headless.ts +42 -3
  452. package/src/types/plugin.ts +32 -3
  453. package/src/ui/host.tsx +11 -0
  454. package/src/ui/toast.tsx +26 -1
  455. package/src/utils/ascii-text.ts +140 -0
  456. package/src/utils/cli-output.ts +91 -18
  457. package/src/utils/concurrency.ts +24 -0
  458. package/src/utils/exchanges.ts +6 -0
  459. package/src/utils/external-url.ts +20 -7
  460. package/src/utils/futures-generic.ts +4 -4
  461. package/src/utils/http-proxy-upstream.ts +124 -0
  462. package/src/utils/main-thread-stall.ts +75 -3
  463. package/src/utils/perf-marks.ts +36 -2
  464. package/src/utils/private-files.ts +141 -0
  465. package/src/utils/public-address.ts +99 -0
  466. package/src/account/upgrade-personalization.ts +0 -57
  467. package/src/account/upgrade-tickers.ts +0 -124
  468. package/src/api-client/upgrade-experiment.ts +0 -91
  469. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  470. package/src/renderers/electrobun/launcher/preload.ts +0 -13
  471. package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
  472. package/src/time-series/studies.ts +0 -1628
@@ -0,0 +1,299 @@
1
+ import { mergePriceHistoryIntegrity } from "../../utils/price-history-integrity";
2
+ import { isRealizedVolatilityEstimator, rollingRealizedVolatility } from "../../market-data/realized-volatility";
3
+ import { averageTrueRange } from "../trader-studies";
4
+ import type { ChartStudySpec, ResolvedSeries, TimeSeriesPoint } from "../types";
5
+ import { isFiniteNumber } from "../../utils/guards";
6
+ import {
7
+ derivedPoint,
8
+ outputSeries,
9
+ positiveInteger,
10
+ samplesFor,
11
+ STUDY_COLORS,
12
+ studyBars,
13
+ studyPeriod,
14
+ type NumericSample,
15
+ } from "./shared";
16
+
17
+ export interface IndexedValue {
18
+ index: number;
19
+ value: number;
20
+ }
21
+
22
+ /** Exported so backtest rules and chart studies share identical math. */
23
+ export function sma(values: readonly number[], period: number): IndexedValue[] {
24
+ if (values.length < period) return [];
25
+ const result: IndexedValue[] = [];
26
+ let sum = 0;
27
+ for (let index = 0; index < values.length; index += 1) {
28
+ sum += values[index]!;
29
+ if (index >= period) sum -= values[index - period]!;
30
+ if (index >= period - 1) result.push({ index, value: sum / period });
31
+ }
32
+ return result;
33
+ }
34
+
35
+ export function ema(values: readonly number[], period: number): IndexedValue[] {
36
+ if (values.length < period) return [];
37
+ const result: IndexedValue[] = [];
38
+ let current = values.slice(0, period).reduce((sum, value) => sum + value, 0) / period;
39
+ result.push({ index: period - 1, value: current });
40
+ const multiplier = 2 / (period + 1);
41
+ for (let index = period; index < values.length; index += 1) {
42
+ current = values[index]! * multiplier + current * (1 - multiplier);
43
+ result.push({ index, value: current });
44
+ }
45
+ return result;
46
+ }
47
+
48
+ export function rsi(values: readonly number[], period: number): IndexedValue[] {
49
+ if (values.length < period + 1) return [];
50
+ let averageGain = 0;
51
+ let averageLoss = 0;
52
+ for (let index = 1; index <= period; index += 1) {
53
+ const change = values[index]! - values[index - 1]!;
54
+ if (change > 0) averageGain += change;
55
+ else averageLoss -= change;
56
+ }
57
+ averageGain /= period;
58
+ averageLoss /= period;
59
+ const result: IndexedValue[] = [{
60
+ index: period,
61
+ value: averageLoss === 0 ? 100 : 100 - 100 / (1 + averageGain / averageLoss),
62
+ }];
63
+ for (let index = period + 1; index < values.length; index += 1) {
64
+ const change = values[index]! - values[index - 1]!;
65
+ averageGain = (averageGain * (period - 1) + Math.max(change, 0)) / period;
66
+ averageLoss = (averageLoss * (period - 1) + Math.max(-change, 0)) / period;
67
+ result.push({
68
+ index,
69
+ value: averageLoss === 0 ? 100 : 100 - 100 / (1 + averageGain / averageLoss),
70
+ });
71
+ }
72
+ return result;
73
+ }
74
+
75
+ function indexedPoints(samples: readonly NumericSample[], values: readonly IndexedValue[]): TimeSeriesPoint[] {
76
+ return values.flatMap(({ index, value }) => {
77
+ const sample = samples[index];
78
+ return sample ? [derivedPoint(sample, value)] : [];
79
+ });
80
+ }
81
+
82
+ export function studyWarmupPoints(spec: ChartStudySpec): number {
83
+ if (spec.kind === "realized-vol") {
84
+ const window = positiveInteger(spec.parameters.window, 30);
85
+ return spec.parameters.estimator === undefined || spec.parameters.estimator === "close-to-close" || spec.parameters.estimator === "yang-zhang"
86
+ ? window : window - 1;
87
+ }
88
+ if (spec.kind === "sma" || spec.kind === "ema" || spec.kind === "bollinger") {
89
+ return studyPeriod(spec, 20) - 1;
90
+ }
91
+ if (spec.kind === "rsi") return studyPeriod(spec, 14);
92
+ if (spec.kind === "macd") {
93
+ const slow = positiveInteger(spec.parameters.slow, 26);
94
+ const signal = positiveInteger(spec.parameters.signal, 9);
95
+ return slow + signal - 2;
96
+ }
97
+ if (spec.kind === "correlation") return studyPeriod(spec, 20);
98
+ if (spec.kind === "atr") return studyPeriod(spec, 14) - 1;
99
+ return 0;
100
+ }
101
+
102
+ export function resolveRealizedVolatility(spec: ChartStudySpec, input: ResolvedSeries, color: string): ResolvedSeries[] {
103
+ const window = positiveInteger(spec.parameters.window, 30);
104
+ const estimator = isRealizedVolatilityEstimator(spec.parameters.estimator) ? spec.parameters.estimator : "close-to-close";
105
+ // Retain invalid rows and the last correction for each date. Dropping one
106
+ // would turn two separated returns into adjacent observations.
107
+ const byDate = new Map(input.points.map((point) => [point.date.getTime(), point]));
108
+ const source = [...byDate.values()].sort((a, b) => a.date.getTime() - b.date.getTime());
109
+ const calculated = rollingRealizedVolatility(source.map((point) => ({
110
+ date: point.date,
111
+ close: point.provenance?.priceHistoryIntegrity || point.provenance?.valuationPriceIssues?.length
112
+ ? Number.NaN : (point.close === undefined ? point.value : point.close) ?? Number.NaN,
113
+ open: point.open ?? undefined,
114
+ high: point.high ?? undefined,
115
+ low: point.low ?? undefined,
116
+ })), { windows: [window], estimator });
117
+ const warmup = studyWarmupPoints(spec);
118
+ const points = calculated.map((point, index): TimeSeriesPoint => {
119
+ const original = source[index]!;
120
+ const value = point.values[window];
121
+ const derived = derivedPoint({ point: original, value: 0 }, value == null ? null : value * 100);
122
+ if (value == null) {
123
+ const affected = source.slice(Math.max(0, index - warmup), index + 1);
124
+ const integrity = affected.flatMap((entry) => entry.provenance?.priceHistoryIntegrity ? [entry.provenance.priceHistoryIntegrity] : []);
125
+ if (integrity.length) derived.provenance!.priceHistoryIntegrity = mergePriceHistoryIntegrity(...integrity);
126
+ }
127
+ return derived;
128
+ });
129
+ return [outputSeries(spec, input, {
130
+ label: `RV(${window}, ${estimator}) ${input.label}`,
131
+ points,
132
+ color,
133
+ unit: "%",
134
+ unitGroup: "percent",
135
+ axis: "left",
136
+ })];
137
+ }
138
+
139
+ export function resolveMovingAverage(
140
+ spec: ChartStudySpec,
141
+ input: ResolvedSeries,
142
+ color: string,
143
+ exponential: boolean,
144
+ ): ResolvedSeries[] {
145
+ const period = studyPeriod(spec, 20);
146
+ const samples = samplesFor(input);
147
+ const calculated = exponential
148
+ ? ema(samples.map(({ value }) => value), period)
149
+ : sma(samples.map(({ value }) => value), period);
150
+ const name = exponential ? "EMA" : "SMA";
151
+ return [outputSeries(spec, input, {
152
+ label: `${name}(${period}) ${input.label}`,
153
+ points: indexedPoints(samples, calculated),
154
+ color,
155
+ })];
156
+ }
157
+
158
+ export function resolveBollinger(
159
+ spec: ChartStudySpec,
160
+ input: ResolvedSeries,
161
+ color: string,
162
+ ): ResolvedSeries[] {
163
+ const period = studyPeriod(spec, 20);
164
+ const deviations = isFiniteNumber(spec.parameters.stdDev) && spec.parameters.stdDev > 0
165
+ ? spec.parameters.stdDev
166
+ : 2;
167
+ const samples = samplesFor(input);
168
+ const values = samples.map(({ value }) => value);
169
+ const middle = sma(values, period);
170
+ const upper: IndexedValue[] = [];
171
+ const lower: IndexedValue[] = [];
172
+ for (const point of middle) {
173
+ const window = values.slice(point.index - period + 1, point.index + 1);
174
+ const variance = window.reduce((sum, value) => sum + (value - point.value) ** 2, 0) / period;
175
+ const deviation = Math.sqrt(variance) * deviations;
176
+ upper.push({ index: point.index, value: point.value + deviation });
177
+ lower.push({ index: point.index, value: point.value - deviation });
178
+ }
179
+ const label = `Bollinger(${period},${deviations}) ${input.label}`;
180
+ return [
181
+ outputSeries(spec, input, {
182
+ id: `${spec.id}:upper`,
183
+ label: `${label} Upper`,
184
+ points: indexedPoints(samples, upper),
185
+ color,
186
+ }),
187
+ outputSeries(spec, input, {
188
+ id: `${spec.id}:middle`,
189
+ label: `${label} Middle`,
190
+ points: indexedPoints(samples, middle),
191
+ color,
192
+ }),
193
+ outputSeries(spec, input, {
194
+ id: `${spec.id}:lower`,
195
+ label: `${label} Lower`,
196
+ points: indexedPoints(samples, lower),
197
+ color,
198
+ }),
199
+ ];
200
+ }
201
+
202
+ export function resolveRsi(spec: ChartStudySpec, input: ResolvedSeries, color: string): ResolvedSeries[] {
203
+ const period = studyPeriod(spec, 14);
204
+ const samples = samplesFor(input);
205
+ return [outputSeries(spec, input, {
206
+ label: `RSI(${period}) ${input.label}`,
207
+ points: indexedPoints(samples, rsi(samples.map(({ value }) => value), period)),
208
+ color,
209
+ unit: "index",
210
+ unitGroup: "oscillator-0-100",
211
+ axis: "left",
212
+ })];
213
+ }
214
+
215
+ export function resolveMacd(spec: ChartStudySpec, input: ResolvedSeries, color: string): ResolvedSeries[] {
216
+ const fastPeriod = positiveInteger(spec.parameters.fast, 12);
217
+ const slowPeriod = positiveInteger(spec.parameters.slow, 26);
218
+ const signalPeriod = positiveInteger(spec.parameters.signal, 9);
219
+ if (fastPeriod >= slowPeriod) return [];
220
+ const samples = samplesFor(input);
221
+ const values = samples.map(({ value }) => value);
222
+ const fast = new Map(ema(values, fastPeriod).map((point) => [point.index, point.value]));
223
+ const macd = ema(values, slowPeriod).flatMap(({ index, value }) => {
224
+ const fastValue = fast.get(index);
225
+ return fastValue === undefined ? [] : [{ index, value: fastValue - value }];
226
+ });
227
+ const signalOnMacd = ema(macd.map(({ value }) => value), signalPeriod);
228
+ const signal = signalOnMacd.flatMap(({ index, value }) => {
229
+ const source = macd[index];
230
+ return source ? [{ index: source.index, value }] : [];
231
+ });
232
+ const macdByIndex = new Map(macd.map((point) => [point.index, point.value]));
233
+ const histogram = signal.map(({ index, value }) => ({ index, value: macdByIndex.get(index)! - value }));
234
+ const label = `MACD(${fastPeriod},${slowPeriod},${signalPeriod}) ${input.label}`;
235
+ return [
236
+ outputSeries(spec, input, {
237
+ id: `${spec.id}:macd`,
238
+ label,
239
+ points: indexedPoints(samples, macd),
240
+ color,
241
+ axis: "left",
242
+ }),
243
+ outputSeries(spec, input, {
244
+ id: `${spec.id}:signal`,
245
+ label: `${label} Signal`,
246
+ points: indexedPoints(samples, signal),
247
+ color: STUDY_COLORS[1]!,
248
+ axis: "left",
249
+ }),
250
+ outputSeries(spec, input, {
251
+ id: `${spec.id}:histogram`,
252
+ label: `${label} Histogram`,
253
+ points: indexedPoints(samples, histogram),
254
+ color: STUDY_COLORS[4]!,
255
+ style: "columns",
256
+ axis: "left",
257
+ }),
258
+ ];
259
+ }
260
+
261
+ export function resolveVolume(spec: ChartStudySpec, input: ResolvedSeries, color: string, nameInput: boolean): ResolvedSeries[] {
262
+ // No bar has volume to plot (spot FX quotes 0 for every bar; a coin's window
263
+ // can miss every bar's), so the study adds no panel rather than an empty one.
264
+ if (input.points.length > 0 && !input.points.some((point) => isFiniteNumber(point.volume) && point.volume !== 0)) return [];
265
+ // Left to its own colour, volume follows the candles: the price's colour for
266
+ // a bar that rose, the falling colour for one that fell. A colour picked for it stays flat.
267
+ const directional = !spec.color;
268
+ const points = [...input.points]
269
+ .sort((left, right) => left.date.getTime() - right.date.getTime())
270
+ .flatMap((point) => isFiniteNumber(point.volume)
271
+ ? [{
272
+ ...derivedPoint({ point, value: point.volume }, point.volume),
273
+ ...(directional && isFiniteNumber(point.open) && isFiniteNumber(point.close) && point.close < point.open ? { fell: true } : {}),
274
+ }]
275
+ : []);
276
+ return [outputSeries(spec, input, {
277
+ label: nameInput ? `Volume ${input.label}` : "Volume",
278
+ points,
279
+ color: directional ? input.color : color,
280
+ unit: input.volumeUnit ?? "",
281
+ unitGroup: "volume",
282
+ style: "columns",
283
+ axis: "left",
284
+ })];
285
+ }
286
+
287
+ export function resolveAtr(spec: ChartStudySpec, input: ResolvedSeries, color: string, nameInput: boolean): ResolvedSeries[] {
288
+ const period = studyPeriod(spec, 14);
289
+ const bars = studyBars(input);
290
+ return [outputSeries(spec, input, {
291
+ label: nameInput ? `ATR(${period}) ${input.label}` : `ATR(${period})`,
292
+ points: averageTrueRange(bars, period).flatMap(({ index, value }) => {
293
+ const bar = bars[index];
294
+ return bar ? [derivedPoint(bar.sample, value)] : [];
295
+ }),
296
+ color,
297
+ axis: "left",
298
+ })];
299
+ }
@@ -0,0 +1,75 @@
1
+ import type { ManualChartResolution } from "../resolution";
2
+ import type { ChartStudySpec, ResolvedSeries } from "../types";
3
+ import { studyMessageName } from "../series-name";
4
+ import {
5
+ dailyLeverageGrowth,
6
+ leverageDecayPoints,
7
+ leverageFactorLabel,
8
+ leverageSymbol,
9
+ sessionCloses,
10
+ type StudyAnchor,
11
+ } from "../leverage";
12
+ import { DECAY_COLORS, derivedPoint, outputSeries } from "./shared";
13
+
14
+ /** The objective's line and its decay compound session returns: they need daily bars, whatever the chart's range. */
15
+ export function dailyBarsIssue(spec: ChartStudySpec, input: ResolvedSeries, marketResolution: ManualChartResolution | undefined): string | null {
16
+ const resolution = input.historyResolution === undefined ? marketResolution : input.historyResolution;
17
+ if (input.nativeFrequency === "daily" && (resolution === undefined || resolution === "1d")) return null;
18
+ return `${studyMessageName(spec)}: ${leverageFactorLabel(Number(spec.parameters.factor))} daily needs daily bars. Choose D bars.`;
19
+ }
20
+
21
+ /**
22
+ * The line a leveraged or inverse fund's daily objective draws from its index
23
+ * fund, in percent from where the chart compares (3× daily QQQ beside TQQQ),
24
+ * on the percent axis the compared legs share.
25
+ */
26
+ export function resolveLeverageLine(spec: ChartStudySpec, index: ResolvedSeries, color: string, anchor: StudyAnchor | undefined): ResolvedSeries[] {
27
+ const factor = spec.parameters.factor as number;
28
+ const closes = sessionCloses(index, anchor);
29
+ const growth = dailyLeverageGrowth(closes.map((entry) => entry.close), factor);
30
+ return [outputSeries(spec, index, {
31
+ label: `${leverageFactorLabel(factor)} daily ${leverageSymbol(index)}`,
32
+ points: closes.map((entry, position) => derivedPoint({ point: entry.point, value: entry.close }, (growth[position]! - 1) * 100)),
33
+ color,
34
+ unit: "%",
35
+ unitGroup: "percent",
36
+ statedUnit: null,
37
+ priceAssetCategory: null,
38
+ axis: "left",
39
+ })];
40
+ }
41
+
42
+ /**
43
+ * How far the fund falls short of its daily objective, in percentage points
44
+ * from where the chart compares: the fund against the daily line (fees,
45
+ * financing, tracking), and the daily line against the factor times the index
46
+ * fund's return over the period (compounding, which daily resets cost in a
47
+ * market that swings).
48
+ */
49
+ export function resolveLeverageDecay(
50
+ spec: ChartStudySpec,
51
+ fund: ResolvedSeries,
52
+ index: ResolvedSeries,
53
+ anchors: ReadonlyMap<string, StudyAnchor> | undefined,
54
+ ): ResolvedSeries[] {
55
+ const factor = spec.parameters.factor as number;
56
+ const decay = leverageDecayPoints(sessionCloses(fund, anchors?.get(fund.id)), sessionCloses(index, anchors?.get(index.id)), factor);
57
+ const daily = `${leverageFactorLabel(factor)} daily`;
58
+ const line = (id: string, label: string, color: string, value: (point: (typeof decay)[number]) => number) => outputSeries(spec, fund, {
59
+ id: `${spec.id}:${id}`,
60
+ label,
61
+ points: decay.map((entry) => derivedPoint({ point: entry.point, value: entry.fund }, value(entry))),
62
+ color: spec.color ?? color,
63
+ unit: "pp",
64
+ // Not a percent group: a difference of returns reads in points (-0.96 pp), not as a return.
65
+ unitGroup: "pp",
66
+ statedUnit: null,
67
+ priceAssetCategory: null,
68
+ axis: "left",
69
+ });
70
+ return [
71
+ line("fund", `${leverageSymbol(fund)} decay vs ${daily} ${leverageSymbol(index)}`, DECAY_COLORS[0], (entry) => entry.fund - entry.daily),
72
+ // `3× QQQ`: three times QQQ's return over the period, the multiple a daily reset does not deliver.
73
+ line("compounding", `${daily} ${leverageSymbol(index)} decay vs ${leverageFactorLabel(factor)} ${leverageSymbol(index)}`, DECAY_COLORS[1], (entry) => entry.daily - entry.period),
74
+ ];
75
+ }
@@ -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
+ }