gloomberb 0.18.4 → 0.19.0

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 (600) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +169 -5
  5. package/src/account/auth-model.ts +7 -3
  6. package/src/api-client/auth.ts +9 -4
  7. package/src/api-client/catalysts.ts +2 -1
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +31 -6
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/index.ts +171 -11
  14. package/src/api-client/live-scan.ts +91 -0
  15. package/src/api-client/market-discovery.ts +10 -0
  16. package/src/api-client/market-movers.ts +7 -0
  17. package/src/api-client/members.ts +13 -1
  18. package/src/api-client/perps.ts +43 -0
  19. package/src/api-client/request.ts +222 -41
  20. package/src/api-client/research-activity.ts +6 -12
  21. package/src/api-client/types.ts +74 -0
  22. package/src/app/keybindings/index.ts +3 -0
  23. package/src/app/keybindings/labels.ts +18 -1
  24. package/src/app/keybindings/resolve.ts +14 -0
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +42 -27
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +1 -1
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/fred-series.ts +5 -0
  35. package/src/cli/commands/keybindings.ts +23 -0
  36. package/src/cli/commands/market.ts +138 -46
  37. package/src/cli/commands/options-chain.ts +107 -10
  38. package/src/cli/commands/options-leaps.ts +9 -3
  39. package/src/cli/commands/overview.ts +176 -39
  40. package/src/cli/commands/search.ts +39 -3
  41. package/src/cli/commands/themes.ts +11 -2
  42. package/src/cli/commands/ticker.ts +57 -35
  43. package/src/cli/company-name-hint.ts +41 -0
  44. package/src/cli/earnings-calendar.ts +1 -1
  45. package/src/cli/errors.ts +10 -2
  46. package/src/cli/export-source.ts +29 -1
  47. package/src/cli/glossary.ts +73 -1
  48. package/src/cli/history-rows.ts +87 -16
  49. package/src/cli/index.ts +2 -0
  50. package/src/cli/known-gaps.ts +5 -2
  51. package/src/cli/listing-arg.ts +195 -56
  52. package/src/cli/output-file.ts +68 -0
  53. package/src/cli/pane-functions/capabilities.ts +3 -0
  54. package/src/cli/pane-functions/catalog.ts +80 -7
  55. package/src/cli/pane-functions/dom.ts +13 -3
  56. package/src/cli/pane-functions/freshness.ts +111 -16
  57. package/src/cli/pane-functions/function-help.ts +23 -13
  58. package/src/cli/pane-functions/headless.ts +56 -12
  59. package/src/cli/pane-functions/index.ts +58 -16
  60. package/src/cli/pane-functions/market-session.ts +81 -9
  61. package/src/cli/pane-functions/options.ts +19 -1
  62. package/src/cli/pane-functions/report.ts +5 -1
  63. package/src/cli/pane-functions/resolver.ts +35 -8
  64. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  65. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  66. package/src/cli/quote-answer.ts +30 -13
  67. package/src/cli/report-tables.ts +18 -7
  68. package/src/cli/restore-plugins.ts +13 -1
  69. package/src/cli/result.ts +57 -5
  70. package/src/cli/ticker-resolution.ts +3 -2
  71. package/src/components/chart/composite/composite-chart.tsx +26 -4
  72. package/src/components/chart/composite/format.ts +27 -0
  73. package/src/components/chart/composite/interactions.ts +6 -0
  74. package/src/components/chart/composite/legend.tsx +45 -7
  75. package/src/components/chart/composite/levels.ts +2 -0
  76. package/src/components/chart/composite/panel-gestures.ts +33 -0
  77. package/src/components/chart/composite/panel-series.ts +1 -0
  78. package/src/components/chart/composite/panel-surface.tsx +61 -5
  79. package/src/components/chart/composite/range-statistics.ts +62 -0
  80. package/src/components/chart/composite/rasterizer.ts +24 -13
  81. package/src/components/chart/composite/scene.ts +46 -7
  82. package/src/components/chart/composite/series-axis-tags.tsx +16 -9
  83. package/src/components/chart/composite/series-cues.ts +14 -0
  84. package/src/components/chart/composite/text-renderer.ts +9 -2
  85. package/src/components/chart/composite/tool-catalog.ts +36 -0
  86. package/src/components/chart/composite/tools.ts +203 -44
  87. package/src/components/chart/composite/types.ts +4 -0
  88. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  89. package/src/components/command-bar/assist/inventory.ts +2 -1
  90. package/src/components/command-bar/commands/direct/index.ts +28 -13
  91. package/src/components/command-bar/pane-templates/items.ts +3 -2
  92. package/src/components/command-bar/panel/prompt-binding.ts +0 -8
  93. package/src/components/command-bar/workflow/alert.ts +1 -1
  94. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  95. package/src/components/layout/header.tsx +26 -40
  96. package/src/components/layout/pane/footer/index.tsx +1 -0
  97. package/src/components/layout/pane/footer/model.ts +27 -0
  98. package/src/components/onboarding/onboarding-frame.tsx +1 -1
  99. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  100. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
  101. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  102. package/src/components/onboarding/wizard-positions.ts +3 -3
  103. package/src/components/ticker/list-table-view.tsx +4 -1
  104. package/src/components/ui/choice-dialog.tsx +23 -18
  105. package/src/components/ui/choice-groups.ts +18 -0
  106. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  107. package/src/components/ui/data-table/opentui/model.ts +26 -1
  108. package/src/components/ui/data-table/types.ts +2 -0
  109. package/src/components/ui/display.tsx +5 -1
  110. package/src/components/ui/list-view.tsx +8 -4
  111. package/src/components/ui/query-bar-keys.tsx +2 -0
  112. package/src/components/ui/query-bar.tsx +1 -0
  113. package/src/components/ui/select-button.tsx +5 -1
  114. package/src/components/ui/select-field.tsx +1 -0
  115. package/src/components/ui/table-layout.ts +8 -5
  116. package/src/core/app-runtime.ts +2 -0
  117. package/src/core/app-services.ts +2 -2
  118. package/src/core/state/app/layout.ts +2 -0
  119. package/src/core/state/app/state.ts +9 -0
  120. package/src/core/state/app/types.ts +3 -0
  121. package/src/data/config/chart-settings.ts +1 -1
  122. package/src/data/json-ticker-repository.ts +17 -8
  123. package/src/data/saved-copies.ts +55 -16
  124. package/src/data/ticker-repository.ts +16 -9
  125. package/src/data/ticker-writes.ts +81 -0
  126. package/src/data/ttl-cache.ts +165 -0
  127. package/src/docs/snapshot.json +427 -216
  128. package/src/i18n/es.ts +19 -3
  129. package/src/i18n/ja.ts +19 -3
  130. package/src/i18n/ko.ts +19 -3
  131. package/src/i18n/zh-cn.ts +19 -3
  132. package/src/i18n/zh-tw.ts +19 -3
  133. package/src/market-data/coordinator/financials.ts +10 -2
  134. package/src/market-data/coordinator/index.ts +3 -1
  135. package/src/market-data/coordinator/quotes.ts +4 -2
  136. package/src/market-data/depositary-pair.ts +2 -2
  137. package/src/market-data/fund-facts.ts +50 -0
  138. package/src/market-data/market/change-basis.ts +48 -7
  139. package/src/market-data/market/dated-previous-close.ts +203 -0
  140. package/src/market-data/market/format.ts +25 -0
  141. package/src/market-data/market/freshness.ts +2 -1
  142. package/src/market-data/market/futures-week.ts +68 -0
  143. package/src/market-data/market/status.ts +16 -4
  144. package/src/market-data/options-alternatives.ts +141 -4
  145. package/src/market-data/quotes/contributions.ts +20 -2
  146. package/src/market-data/quotes/resolution.ts +3 -0
  147. package/src/market-data/selectors.ts +3 -0
  148. package/src/news/aggregator.ts +113 -9
  149. package/src/news/cloud-session.ts +21 -0
  150. package/src/news/hooks.ts +1 -0
  151. package/src/news/news-model.ts +1 -0
  152. package/src/news/types.ts +11 -0
  153. package/src/notifications/notification-log.ts +6 -4
  154. package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
  155. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  156. package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
  157. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  158. package/src/plugins/builtin/alerts/cli.ts +380 -82
  159. package/src/plugins/builtin/alerts/command.ts +31 -0
  160. package/src/plugins/builtin/alerts/constants.ts +1 -1
  161. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  162. package/src/plugins/builtin/alerts/fired-log.ts +113 -8
  163. package/src/plugins/builtin/alerts/format.ts +80 -4
  164. package/src/plugins/builtin/alerts/headless.ts +30 -7
  165. package/src/plugins/builtin/alerts/history-rows.ts +7 -3
  166. package/src/plugins/builtin/alerts/history.ts +2 -0
  167. package/src/plugins/builtin/alerts/index.tsx +135 -22
  168. package/src/plugins/builtin/alerts/live.ts +3 -3
  169. package/src/plugins/builtin/alerts/metric-command.ts +134 -0
  170. package/src/plugins/builtin/alerts/metric-data.ts +111 -0
  171. package/src/plugins/builtin/alerts/metric-model.ts +324 -0
  172. package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
  173. package/src/plugins/builtin/alerts/pane.tsx +90 -39
  174. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  175. package/src/plugins/builtin/alerts/storage.ts +37 -10
  176. package/src/plugins/builtin/alerts/types.ts +7 -0
  177. package/src/plugins/builtin/analytics/index.tsx +2 -1
  178. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  179. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  180. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  181. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  182. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  183. package/src/plugins/builtin/attention/index.tsx +2 -1
  184. package/src/plugins/builtin/attention/listing.ts +14 -0
  185. package/src/plugins/builtin/backtest/headless.ts +10 -5
  186. package/src/plugins/builtin/backtest/pane.tsx +9 -4
  187. package/src/plugins/builtin/backtest/presets.ts +29 -3
  188. package/src/plugins/builtin/broker-manager/index.tsx +10 -5
  189. package/src/plugins/builtin/broker-manager/model.ts +6 -1
  190. package/src/plugins/builtin/broker-trading/controller.ts +22 -12
  191. package/src/plugins/builtin/broker-trading/index.tsx +13 -1
  192. package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
  193. package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
  194. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  195. package/src/plugins/builtin/buildout/format.ts +38 -0
  196. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  197. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  198. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  199. package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
  200. package/src/plugins/builtin/catalysts/model.ts +2 -1
  201. package/src/plugins/builtin/changelog/index.tsx +9 -6
  202. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
  203. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
  204. package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
  205. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
  206. package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
  207. package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
  208. package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
  209. package/src/plugins/builtin/chart-composer/formula.ts +71 -18
  210. package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
  211. package/src/plugins/builtin/chart-composer/headless.ts +19 -3
  212. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  213. package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
  214. package/src/plugins/builtin/chart-composer/presets.ts +50 -15
  215. package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
  216. package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
  217. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  218. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
  219. package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
  220. package/src/plugins/builtin/chat/content/composer.tsx +13 -6
  221. package/src/plugins/builtin/chat/content/footer.tsx +6 -3
  222. package/src/plugins/builtin/chat/content/index.tsx +50 -21
  223. package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
  224. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
  225. package/src/plugins/builtin/chat/index.tsx +23 -0
  226. package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
  227. package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
  228. package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
  229. package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
  230. package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
  231. package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
  232. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
  233. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  234. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  235. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  236. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  237. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  238. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  239. package/src/plugins/builtin/company-attention/client.ts +8 -9
  240. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  241. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  242. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  243. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  244. package/src/plugins/builtin/correlation/headless.ts +13 -5
  245. package/src/plugins/builtin/correlation/history.ts +22 -0
  246. package/src/plugins/builtin/cot/client.ts +22 -18
  247. package/src/plugins/builtin/cot/headless.ts +15 -10
  248. package/src/plugins/builtin/cot/model.ts +14 -1
  249. package/src/plugins/builtin/cot/pane.tsx +22 -14
  250. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  251. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  252. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  253. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  254. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  255. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  256. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  257. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  258. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  259. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  260. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  261. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  262. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  263. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  265. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  266. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  267. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  268. package/src/plugins/builtin/docs/targets.ts +5 -1
  269. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  270. package/src/plugins/builtin/earnings/board.tsx +62 -71
  271. package/src/plugins/builtin/earnings/headless.ts +40 -4
  272. package/src/plugins/builtin/earnings/scope.ts +31 -43
  273. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  274. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  275. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  276. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  277. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  278. package/src/plugins/builtin/econ/calendar-model.ts +14 -9
  279. package/src/plugins/builtin/econ/figures.ts +72 -0
  280. package/src/plugins/builtin/econ/index.tsx +33 -3
  281. package/src/plugins/builtin/econ/types.ts +4 -0
  282. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  283. package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
  284. package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
  285. package/src/plugins/builtin/econ-statistics/view.ts +3 -0
  286. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  287. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  288. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  289. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  290. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  291. package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
  292. package/src/plugins/builtin/exposure/charts.tsx +2 -2
  293. package/src/plugins/builtin/exposure/model.ts +13 -1
  294. package/src/plugins/builtin/exposure/pane.tsx +4 -4
  295. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  296. package/src/plugins/builtin/futures/index.tsx +1 -0
  297. package/src/plugins/builtin/futures/model.ts +2 -1
  298. package/src/plugins/builtin/futures/table.tsx +2 -2
  299. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  300. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  301. package/src/plugins/builtin/fx-matrix/client.ts +11 -3
  302. package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
  303. package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
  304. package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
  305. package/src/plugins/builtin/gpu/headless.ts +8 -2
  306. package/src/plugins/builtin/help/function-index.ts +4 -1
  307. package/src/plugins/builtin/help/index.tsx +1 -0
  308. package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
  309. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  310. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  311. package/src/plugins/builtin/holders/headless.ts +18 -1
  312. package/src/plugins/builtin/iv-history/client.ts +52 -11
  313. package/src/plugins/builtin/iv-history/format.ts +36 -23
  314. package/src/plugins/builtin/iv-history/headless.ts +36 -18
  315. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  316. package/src/plugins/builtin/iv-history/model.ts +163 -27
  317. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  318. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  319. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  320. package/src/plugins/builtin/jobs/client.ts +25 -29
  321. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  322. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  323. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  324. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  325. package/src/plugins/builtin/macro-day/pane.tsx +7 -5
  326. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  327. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  328. package/src/plugins/builtin/market-movers/client.ts +25 -7
  329. package/src/plugins/builtin/market-movers/footer.ts +2 -2
  330. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  331. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  332. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  333. package/src/plugins/builtin/market-movers/model.ts +4 -1
  334. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  335. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  336. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  337. package/src/plugins/builtin/market-movers/table.ts +17 -5
  338. package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
  339. package/src/plugins/builtin/members/client.ts +19 -4
  340. package/src/plugins/builtin/members/headless.ts +20 -7
  341. package/src/plugins/builtin/members/index.tsx +9 -9
  342. package/src/plugins/builtin/members/model.ts +23 -4
  343. package/src/plugins/builtin/members/pane.tsx +25 -15
  344. package/src/plugins/builtin/news/index.tsx +1 -0
  345. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  346. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  347. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  348. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  349. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  350. package/src/plugins/builtin/openfda/client.ts +14 -2
  351. package/src/plugins/builtin/openfda/index.tsx +3 -2
  352. package/src/plugins/builtin/openfda/model.ts +9 -0
  353. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  354. package/src/plugins/builtin/options/analytics.ts +54 -6
  355. package/src/plugins/builtin/options/enrichment.ts +17 -5
  356. package/src/plugins/builtin/options/model-spot.ts +5 -2
  357. package/src/plugins/builtin/options/strike-window.ts +2 -1
  358. package/src/plugins/builtin/options/table.ts +6 -9
  359. package/src/plugins/builtin/options/types.ts +2 -0
  360. package/src/plugins/builtin/options/view.tsx +42 -12
  361. package/src/plugins/builtin/options-positioning/client.ts +30 -0
  362. package/src/plugins/builtin/options-positioning/headless.ts +74 -21
  363. package/src/plugins/builtin/options-positioning/index.tsx +4 -4
  364. package/src/plugins/builtin/options-positioning/model.ts +122 -14
  365. package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
  366. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  367. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  368. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  369. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  370. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  371. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  372. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  373. package/src/plugins/builtin/pe-band/model.ts +12 -49
  374. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  375. package/src/plugins/builtin/perps/cascade.ts +236 -0
  376. package/src/plugins/builtin/perps/client.ts +56 -2
  377. package/src/plugins/builtin/perps/freshness.ts +12 -10
  378. package/src/plugins/builtin/perps/headless.ts +65 -9
  379. package/src/plugins/builtin/perps/index.tsx +7 -5
  380. package/src/plugins/builtin/perps/live-rows.ts +10 -3
  381. package/src/plugins/builtin/perps/model.ts +18 -4
  382. package/src/plugins/builtin/perps/pane.tsx +32 -31
  383. package/src/plugins/builtin/perps/status-line.ts +25 -0
  384. package/src/plugins/builtin/plugin-module.ts +5 -1
  385. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  386. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  387. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  388. package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
  389. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  390. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  391. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  392. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  393. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  394. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  395. package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
  396. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  397. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  398. package/src/plugins/builtin/rate-path/pane.tsx +2 -2
  399. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  400. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  401. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  402. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  403. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  404. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  405. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  406. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  407. package/src/plugins/builtin/research/event-model.ts +10 -8
  408. package/src/plugins/builtin/research/headless.ts +1 -1
  409. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  410. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  411. package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
  412. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  413. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  414. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  415. package/src/plugins/builtin/scanner/feed.ts +2 -2
  416. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  417. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  418. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  419. package/src/plugins/builtin/scanner/headless.ts +204 -35
  420. package/src/plugins/builtin/scanner/index.ts +40 -6
  421. package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
  422. package/src/plugins/builtin/scanner/live-feed.ts +168 -0
  423. package/src/plugins/builtin/scanner/live-headless.ts +138 -0
  424. package/src/plugins/builtin/scanner/live-model.ts +362 -0
  425. package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
  426. package/src/plugins/builtin/scanner/live-track.ts +186 -0
  427. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  428. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  429. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  430. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  431. package/src/plugins/builtin/sec/headless.ts +2 -1
  432. package/src/plugins/builtin/series-inventory.ts +3 -0
  433. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  434. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  435. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  436. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  437. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  438. package/src/plugins/builtin/shared/macro-releases.ts +65 -0
  439. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  440. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  441. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  442. package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
  443. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  444. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  445. package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
  446. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  447. package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
  448. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  449. package/src/plugins/builtin/themes/headless.ts +6 -4
  450. package/src/plugins/builtin/themes/model.ts +17 -8
  451. package/src/plugins/builtin/themes/pane.tsx +7 -7
  452. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  453. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  456. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  457. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  458. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  459. package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
  460. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  461. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  462. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  463. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  464. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  465. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  466. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  467. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  468. package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
  469. package/src/plugins/builtin/vol-surface/client.ts +9 -17
  470. package/src/plugins/builtin/vol-surface/headless.ts +63 -14
  471. package/src/plugins/builtin/vol-surface/index.tsx +2 -2
  472. package/src/plugins/builtin/vol-surface/model.ts +22 -7
  473. package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
  474. package/src/plugins/builtin/vol-surface/tables.ts +31 -3
  475. package/src/plugins/builtin/volatility/dvol.ts +46 -0
  476. package/src/plugins/builtin/volatility/index.tsx +2 -2
  477. package/src/plugins/builtin/volatility/pane.tsx +4 -4
  478. package/src/plugins/builtin/volatility/series.ts +26 -0
  479. package/src/plugins/builtin/world-indices/footer.ts +1 -1
  480. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  481. package/src/plugins/catalog-browser.ts +13 -8
  482. package/src/plugins/catalog-manifest.ts +2 -1
  483. package/src/plugins/marketplace/model.ts +18 -5
  484. package/src/portfolio/column-values.ts +5 -5
  485. package/src/portfolio/futures-positions.ts +6 -3
  486. package/src/portfolio/position-quantity.ts +15 -9
  487. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  488. package/src/remote/resources.ts +2 -1
  489. package/src/renderers/browser/app-services.ts +1 -7
  490. package/src/renderers/browser/config-host.ts +2 -55
  491. package/src/renderers/browser/main.tsx +7 -57
  492. package/src/renderers/dom/data-table/row.tsx +26 -0
  493. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  494. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  495. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  496. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  497. package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
  498. package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
  499. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  500. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  501. package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
  502. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
  503. package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
  504. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  505. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
  506. package/src/renderers/electrobun/bun/index.ts +25 -1
  507. package/src/renderers/electrobun/shared/app-name.ts +30 -0
  508. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  509. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  510. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  511. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
  512. package/src/renderers/electrobun/view/main.tsx +24 -1
  513. package/src/renderers/electrobun/view/news-source.ts +3 -1
  514. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  515. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  516. package/src/renderers/opentui/start.tsx +5 -0
  517. package/src/renderers/opentui/toast-host.tsx +71 -43
  518. package/src/series/registry.ts +1 -0
  519. package/src/series/types.ts +6 -0
  520. package/src/series/validate.ts +1 -0
  521. package/src/sources/gloomberb-cloud/index.ts +1 -0
  522. package/src/sources/gloomberb-cloud/news.ts +47 -7
  523. package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
  524. package/src/sources/provider-router/batches.ts +10 -2
  525. package/src/sources/provider-router/financial-routes.ts +3 -3
  526. package/src/sources/provider-router/financials.ts +15 -3
  527. package/src/sources/provider-router/history.ts +28 -7
  528. package/src/sources/provider-router/index.ts +149 -10
  529. package/src/sources/provider-router/search.ts +13 -2
  530. package/src/state/app/context/index.tsx +3 -0
  531. package/src/state/ticker-cleanup.ts +50 -0
  532. package/src/sync/auth-transition.ts +19 -9
  533. package/src/sync/baseline.ts +10 -4
  534. package/src/sync/controller.ts +70 -19
  535. package/src/sync/core-contributors.ts +90 -31
  536. package/src/sync/react.ts +4 -1
  537. package/src/sync/types.ts +3 -0
  538. package/src/telemetry/stall-reports-dom.ts +38 -0
  539. package/src/telemetry/stall-reports-node.ts +75 -0
  540. package/src/telemetry/stall-reports.ts +214 -0
  541. package/src/telemetry/usage-counts.ts +0 -18
  542. package/src/test-support/preload.ts +14 -0
  543. package/src/theme/colors.ts +8 -0
  544. package/src/theme/themes.ts +10 -0
  545. package/src/tickers/company-name.ts +103 -3
  546. package/src/tickers/listing-venues.ts +38 -3
  547. package/src/tickers/search/bare-symbol.ts +36 -0
  548. package/src/tickers/search/index.ts +36 -7
  549. package/src/tickers/search/ranking.ts +10 -1
  550. package/src/tickers/search/types.ts +5 -0
  551. package/src/tickers/terminal-codes.ts +19 -0
  552. package/src/time-series/alignment.ts +1 -1
  553. package/src/time-series/base-series.ts +13 -5
  554. package/src/time-series/daily-valuation.ts +62 -0
  555. package/src/time-series/economic.ts +18 -4
  556. package/src/time-series/field-catalog.ts +2 -4
  557. package/src/time-series/filed-trailing-eps.ts +197 -0
  558. package/src/time-series/forward-valuation.ts +32 -0
  559. package/src/time-series/fundamentals.ts +198 -7
  560. package/src/time-series/hooks.ts +1 -0
  561. package/src/time-series/ice-credit.ts +3 -0
  562. package/src/time-series/price-comparison.ts +30 -2
  563. package/src/time-series/price-units.ts +72 -2
  564. package/src/time-series/resolve-context.ts +7 -1
  565. package/src/time-series/resolve.ts +116 -15
  566. package/src/time-series/series-presentation.ts +40 -6
  567. package/src/time-series/session-bar.ts +11 -0
  568. package/src/time-series/spec.ts +5 -3
  569. package/src/time-series/studies.ts +278 -74
  570. package/src/time-series/total-return.ts +160 -0
  571. package/src/time-series/transforms.ts +3 -3
  572. package/src/time-series/types.ts +27 -1
  573. package/src/time-series/valuation-releases.ts +39 -0
  574. package/src/types/data-provider.ts +9 -1
  575. package/src/types/desktop-window.ts +2 -0
  576. package/src/types/financials.ts +73 -2
  577. package/src/types/headless.ts +61 -5
  578. package/src/types/instrument.ts +3 -0
  579. package/src/types/news-source.ts +1 -0
  580. package/src/types/plugin.ts +10 -1
  581. package/src/ui/host.tsx +3 -0
  582. package/src/updater/command-names.ts +12 -7
  583. package/src/updater/failure.ts +4 -1
  584. package/src/utils/cli-output.ts +42 -1
  585. package/src/utils/crypto-pair.ts +44 -0
  586. package/src/utils/financial-statements.ts +6 -1
  587. package/src/utils/forward-dividend.ts +2 -0
  588. package/src/utils/fundamentals.ts +24 -3
  589. package/src/utils/fx-market-hours.ts +26 -3
  590. package/src/utils/main-thread-stall.ts +35 -2
  591. package/src/utils/market-capitalization.ts +24 -1
  592. package/src/utils/network-failure.ts +9 -6
  593. package/src/utils/option-expiry.ts +48 -1
  594. package/src/utils/options.ts +10 -0
  595. package/src/utils/plugin-proxy-hosts.json +1 -0
  596. package/src/utils/price-history.ts +12 -1
  597. package/src/utils/shell-word.ts +8 -0
  598. package/src/utils/trailing-dividend.ts +53 -4
  599. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
  600. package/src/renderers/browser/first-screen.ts +0 -193
@@ -1,6 +1,10 @@
1
+ import { METRIC_ALERTS_KEY, RELATIVE_ALERTS_KEY, closeBasis, metricName, metricReadingText } from "./metric-model";
2
+ import { metricAvailabilityError } from "./metric-data";
3
+ import { relativeAlertText } from "./metric-command";
4
+ import { levelAlertStoreKey, readLevelAlertStores } from "./storage";
1
5
  import { Box, type ScrollBoxRenderable } from "../../../ui";
2
6
  import { Button, DetailScrollBody, KeyValueRow, Notice, PaneStatusBody, PaneFooterScope, usePaneTabs } from "../../../components";
3
- import { usePluginPaneState } from "../../runtime";
7
+ import { usePluginPaneState, usePluginState } from "../../runtime";
4
8
  import { EventAlertsPane } from "./events-pane";
5
9
  import { AlertHistoryPane } from "./history-pane";
6
10
  import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, type RefObject } from "react";
@@ -27,16 +31,21 @@ import {
27
31
  readAlertsStoreError,
28
32
  serializeAlerts,
29
33
  } from "./alert-engine";
30
- import { parseAlertCommandValues } from "./command";
34
+ import { parseAlertEdit } from "./command";
31
35
  import { ALERT_TABS, ALERTS_KEY } from "./constants";
32
36
  import {
37
+ alertListing,
33
38
  conditionLabel,
34
39
  formatAlertDistance,
35
40
  formatAlertTargetPrice,
36
41
  formatCurrentPrice,
37
42
  formatQuoteChecked,
43
+ isoUtc,
44
+ listingWithCurrency,
38
45
  relativeTime,
39
46
  } from "./format";
47
+ import { FIRED_LOG_KEY } from "./fired-log";
48
+ import { withAlertChange } from "./alert-log";
40
49
  import type { AlertRule } from "./types";
41
50
  import { useQuoteEntries } from "../../../market-data/hooks";
42
51
  import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors";
@@ -48,35 +57,37 @@ import { alertTarget, alertViewHints, priceRuleSubject, useOpenAlertTarget } fro
48
57
  type AlertColumnId =
49
58
  | "status"
50
59
  | "symbol"
60
+ | "metric"
51
61
  | "current"
52
62
  | "target"
53
63
  | "away"
54
64
  | "condition"
55
65
  | "quote"
56
- | "triggered";
66
+ | "triggered"
67
+ | "created";
57
68
 
58
69
  type AlertColumn = DataTableColumn & { id: AlertColumnId };
59
70
 
60
71
  const ALERT_COLUMNS: AlertColumn[] = [
61
72
  { id: "status", label: "State", width: 9, align: "left" },
62
73
  { id: "symbol", label: "Symbol", width: 7, align: "left" },
63
- { id: "current", label: "Current", width: 9, align: "right" },
64
- { id: "target", label: "Target", width: 9, align: "right" },
74
+ { id: "metric", label: "Measure", width: 14, align: "left" },
75
+ { id: "current", label: "Current", width: 8, align: "right" },
76
+ { id: "target", label: "Target", width: 11, align: "right" },
65
77
  { id: "away", label: "Away", width: 8, align: "right" },
66
- { id: "condition", label: "Trigger", width: 7, align: "left" },
67
- { id: "quote", label: "Quote", width: 8, align: "left" },
78
+ { id: "condition", label: "Trigger", width: 12, align: "left" },
79
+ // Wide enough to say "not checked yet" for an alert no check has read.
80
+ { id: "quote", label: "Quote", width: 15, align: "left" },
68
81
  { id: "triggered", label: "Alerted", width: 8, align: "left" },
82
+ // The day the alert was made (UTC), as Events lists its rules; the detail gives the instant.
83
+ { id: "created", label: "Since", width: 10, align: "left" },
69
84
  ];
70
85
 
71
- const ALERT_TABLE_CONTENT_WIDTH = ALERT_COLUMNS.reduce(
72
- (sum, column) => sum + column.width + 1,
73
- 2,
74
- );
75
-
76
86
  const DETAIL_ROW = { labelWidth: 10, emphasis: false } as const;
77
87
 
78
88
  /** "AAPL above 400.00": the rule, which titles its detail. */
79
89
  function alertTitle(alert: AlertRule): string {
90
+ if (alert.metric) return formatAlertDescription(alert);
80
91
  return `${alert.symbol} ${conditionLabel(alert.condition).toLowerCase()} ${formatAlertTargetPrice(alert)}`;
81
92
  }
82
93
 
@@ -88,19 +99,28 @@ function AlertDetail({ alert, scrollRef }: { alert: AlertRule; scrollRef: RefObj
88
99
  const reason = alert.lastCheckError
89
100
  ? alert.lastCheckError.includes(alert.symbol) ? alert.lastCheckError : `${alert.symbol}: ${alert.lastCheckError}`
90
101
  : null;
102
+ const basis = alert.metric ? closeBasis(alert.metric) : null;
91
103
  return (
92
104
  <DetailScrollBody ref={scrollRef} resetScrollKey={alert.id}>
93
105
  <Box flexDirection="column">
94
106
  {reason ? <Box paddingBottom={1}><Notice tone="warning">{reason}</Notice></Box> : null}
95
107
  <KeyValueRow {...DETAIL_ROW} label="State" value={alert.status === "triggered" ? "Triggered" : "Active"} tone={alert.status === "triggered" ? "positive" : "muted"} />
108
+ <KeyValueRow {...DETAIL_ROW} label="Listing" value={listingWithCurrency(alertListing(alert), alert.lastQuoteCurrency)} />
96
109
  {/* With no quote the reason above says why; the price and its distance wait for one. */}
110
+ {basis ? <KeyValueRow {...DETAIL_ROW} label="Close date" value={alert.lastDailyClose ?? `Awaiting ${basis}`} /> : null}
97
111
  {reason ? null : <KeyValueRow {...DETAIL_ROW} label="Current" value={formatCurrentPrice(alert, 24)} />}
112
+ {/* The two readings that place the quadrant, as RRG states them. */}
113
+ {!reason && alert.metric?.kind === "rrg" && alert.lastCheckedPrice != null && alert.lastCheckedTarget != null ? <>
114
+ <KeyValueRow {...DETAIL_ROW} label="Strength" value={alert.lastCheckedPrice.toFixed(2)} />
115
+ <KeyValueRow {...DETAIL_ROW} label="Momentum" value={alert.lastCheckedTarget.toFixed(2)} />
116
+ </> : null}
98
117
  {reason ? null : <KeyValueRow {...DETAIL_ROW} label="Away" value={formatAlertDistance(alert)} />}
99
118
  {reason
100
119
  ? alert.lastCheckedAt ? <KeyValueRow {...DETAIL_ROW} label="Checked" value={relativeTime(alert.lastCheckedAt)} tone="muted" /> : null
101
120
  : <KeyValueRow {...DETAIL_ROW} label="Quote" value={formatQuoteChecked(alert)} tone="muted" />}
102
121
  {alert.triggeredAt ? <KeyValueRow {...DETAIL_ROW} label="Alerted" value={relativeTime(alert.triggeredAt)} /> : null}
103
122
  <KeyValueRow {...DETAIL_ROW} label="Armed" value={relativeTime(alert.armedAt ?? alert.createdAt)} tone="muted" />
123
+ <KeyValueRow {...DETAIL_ROW} label="Created" value={isoUtc(alert.createdAt)} tone="muted" />
104
124
  </Box>
105
125
  </DetailScrollBody>
106
126
  );
@@ -141,18 +161,27 @@ export function AlertsPane(props: PaneProps) {
141
161
 
142
162
  function PriceAlertsPane({ focused, width, height }: PaneProps) {
143
163
  const [alertsJson, setAlertsJson] = usePluginConfigState<unknown>(ALERTS_KEY, "[]");
164
+ const [metricsJson, setMetricsJson] = usePluginConfigState<unknown>(METRIC_ALERTS_KEY, "[]");
165
+ const [relativeJson, setRelativeJson] = usePluginConfigState<unknown>(RELATIVE_ALERTS_KEY, "[]");
144
166
  const { openPluginCommandWorkflow, notify } = usePluginAppActions();
167
+ // Deleting or re-arming a rule goes in this device's alert log, beside the alerts that fired.
168
+ const [, setAlertLog] = usePluginState<unknown>(FIRED_LOG_KEY, []);
145
169
  const dialog = useDialog();
146
170
  const [selectedIdx, setSelectedIdx] = useState(0);
147
171
  const [detailOpen, setDetailOpen] = useState(false);
148
172
  // The alert the open detail shows, by id, so it never becomes whichever alert takes its row.
149
173
  const detailAlertId = useRef<string | null>(null);
150
174
  const detailScrollRef = useRef<ScrollBoxRenderable | null>(null);
151
- const showHorizontalScrollbar = ALERT_TABLE_CONTENT_WIDTH > width;
152
- const storeError = useMemo(() => readAlertsStoreError(alertsJson), [alertsJson]);
175
+ // Keep the rule and daily-close basis visible in a narrow pane; the detail keeps all figures.
176
+ const columns = width < 106 ? ALERT_COLUMNS.filter(column => column.id !== "away" && column.id !== "triggered" && column.id !== "created")
177
+ : width < 118 ? ALERT_COLUMNS.filter(column => column.id !== "created") : ALERT_COLUMNS;
178
+ const showHorizontalScrollbar = columns.reduce((sum, column) => sum + column.width + 1, 2) > width;
179
+ const stores = useMemo(() => readLevelAlertStores(key => key === ALERTS_KEY ? alertsJson : key === METRIC_ALERTS_KEY ? metricsJson : relativeJson),
180
+ [alertsJson, metricsJson, relativeJson]);
181
+ const storeError = stores.error;
153
182
 
154
183
  const { alerts, rows } = useMemo(() => {
155
- const parsed = deserializeAlerts(alertsJson);
184
+ const parsed = stores.alerts;
156
185
  const activeAlerts = parsed.filter((a) => a.status === "active");
157
186
  const triggeredAlerts = parsed
158
187
  .filter((a) => a.status === "triggered")
@@ -162,7 +191,7 @@ function PriceAlertsPane({ focused, width, height }: PaneProps) {
162
191
  alerts: parsed,
163
192
  rows: [...activeAlerts, ...triggeredAlerts],
164
193
  };
165
- }, [alertsJson]);
194
+ }, [stores]);
166
195
 
167
196
  const focusedAlertId = useSyncExternalStore(subscribePriceAlertFocus, getPriceAlertFocus, getPriceAlertFocus);
168
197
  useEffect(() => {
@@ -177,7 +206,7 @@ function PriceAlertsPane({ focused, width, height }: PaneProps) {
177
206
  // symbol streams the table shows its live price instead.
178
207
  const liveInstruments = useMemo(() => {
179
208
  const unique = new Map(rows
180
- .filter((alert) => alert.status === "active")
209
+ .filter((alert) => !alert.metric && alert.status === "active")
181
210
  .map((alert) => [buildQuoteKey(alertInstrument(alert)), alertInstrument(alert)] as const));
182
211
  return [...unique.values()];
183
212
  }, [rows]);
@@ -189,22 +218,27 @@ function PriceAlertsPane({ focused, width, height }: PaneProps) {
189
218
  syncAlertQuoteStream();
190
219
  }, [liveSymbolsKey]);
191
220
  const displayRows = useMemo(() => rows.map((alert) => {
221
+ if (alert.metric) {
222
+ const error = metricAvailabilityError(alert) ?? alert.lastCheckError;
223
+ return error ? { ...alert, lastCheckError: error } : alert;
224
+ }
192
225
  if (alert.status !== "active") return alert;
193
226
  const quote = streamedAlertQuote(resolveEntryData(liveEntries.get(buildQuoteKey(alertInstrument(alert)))));
194
227
  return quote ? { ...alert, ...quoteAlertFields(quote, quote.receivedAt) } : alert;
195
228
  }), [liveEntries, rows]);
196
229
 
197
- const savePaneAlerts = useCallback((next: (current: AlertRule[]) => AlertRule[]): string | null => {
230
+ const savePaneAlerts = useCallback((id: string, next: (current: AlertRule[]) => AlertRule[]): string | null => {
231
+ const found = alerts.find(alert => alert.id === id);
232
+ const key = found ? levelAlertStoreKey(found) : ALERTS_KEY;
233
+ if (stores.errors[key]) return stores.errors[key];
198
234
  let error: string | null = null;
199
- setAlertsJson((currentJson: unknown) => {
235
+ const setJson = key === METRIC_ALERTS_KEY ? setMetricsJson : key === RELATIVE_ALERTS_KEY ? setRelativeJson : setAlertsJson;
236
+ setJson((currentJson: unknown) => {
200
237
  error = readAlertsStoreError(currentJson);
201
- // The config hook skips persistence when the updater returns the unchanged value.
202
- if (error) return currentJson;
203
- const current = deserializeAlerts(currentJson);
204
- return serializeAlerts(next(current));
238
+ return error ? currentJson : serializeAlerts(next(deserializeAlerts(currentJson)));
205
239
  });
206
240
  return error;
207
- }, [setAlertsJson]);
241
+ }, [alerts, setAlertsJson, setMetricsJson, setRelativeJson, stores]);
208
242
 
209
243
  const deleteAlert = useCallback(async (id: string) => {
210
244
  const alert = alerts.find((a) => a.id === id);
@@ -215,20 +249,29 @@ function PriceAlertsPane({ focused, width, height }: PaneProps) {
215
249
  confirmLabel: "Delete",
216
250
  });
217
251
  if (!confirmed) return;
218
- const error = savePaneAlerts((current) => current.filter((a) => a.id !== id));
252
+ const error = savePaneAlerts(id, (current) => current.filter((a) => a.id !== id));
219
253
  if (error) {
220
254
  notify({ body: error, type: "error" });
221
255
  return;
222
256
  }
257
+ setAlertLog((saved: unknown) => withAlertChange(saved, "deleted", alert));
223
258
  setSelectedIdx((prev) => Math.max(0, Math.min(prev, rows.length - 2)));
224
- }, [alerts, dialog, notify, rows.length, savePaneAlerts]);
259
+ }, [alerts, dialog, notify, rows.length, savePaneAlerts, setAlertLog]);
225
260
 
226
261
  const rearmAlert = useCallback((id: string) => {
227
- const error = savePaneAlerts(
228
- (current) => current.map((a) => (a.id === id ? rebuildAlert(a) : a)),
229
- );
230
- if (error) notify({ body: error, type: "error" });
231
- }, [notify, savePaneAlerts]);
262
+ // Only an alert that went off is re-armed, as the store reads at the write: one still waiting is left, and not logged.
263
+ const rearmed: AlertRule[] = [];
264
+ const error = savePaneAlerts(id, (current) => current.map((a) => {
265
+ if (a.id !== id || a.status !== "triggered") return a;
266
+ rearmed.push(a);
267
+ return rebuildAlert(a);
268
+ }));
269
+ if (error) {
270
+ notify({ body: error, type: "error" });
271
+ return;
272
+ }
273
+ for (const alert of rearmed) setAlertLog((saved: unknown) => withAlertChange(saved, "rearmed", alert));
274
+ }, [notify, savePaneAlerts, setAlertLog]);
232
275
 
233
276
  const startAddAlert = useCallback(() => {
234
277
  openPluginCommandWorkflow("set-alert");
@@ -266,13 +309,12 @@ function PriceAlertsPane({ focused, width, height }: PaneProps) {
266
309
  description: "SYMBOL above|below|crosses PRICE",
267
310
  placeholder: "AAPL above 200",
268
311
  }}
269
- currentValue={`${selected.symbol} ${selected.condition} ${selected.targetPrice}`}
312
+ currentValue={relativeAlertText(selected) ?? (selected.metric ? formatAlertDescription(selected).replace(" (TTM)", "").replace(" · on daily close", "") : `${selected.symbol} ${selected.condition} ${selected.targetPrice}`)}
270
313
  onApply={async (value) => {
271
- const parsed = parseAlertCommandValues({ shortcut: value });
272
- if (!parsed) throw new Error("Use SYMBOL above|below|crosses PRICE.");
273
- const error = savePaneAlerts((current) => current.map((alert) => (
314
+ const parsed = parseAlertEdit(selected, value);
315
+ const error = savePaneAlerts(selected.id, (current) => current.map((alert) => (
274
316
  alert.id === selected.id
275
- ? editAlert(alert, parsed.symbol, parsed.condition, parsed.price)
317
+ ? { ...editAlert(alert, parsed.symbol, parsed.condition, parsed.price), ...(parsed.metric ? { metric: parsed.metric } : {}) }
276
318
  : alert
277
319
  )));
278
320
  if (error) throw new Error(error);
@@ -356,7 +398,7 @@ function PriceAlertsPane({ focused, width, height }: PaneProps) {
356
398
  switch (column.id) {
357
399
  case "status":
358
400
  return {
359
- text: alert.status === "triggered" ? "Triggered" : "Active",
401
+ text: alert.status === "triggered" ? "Triggered" : alert.lastCheckError && alert.metric ? "Blocked" : "Active",
360
402
  color: alert.status === "triggered" ? colors.positive : colors.textDim,
361
403
  attributes: alert.status === "triggered" ? TextAttributes.BOLD : TextAttributes.NONE,
362
404
  };
@@ -366,9 +408,14 @@ function PriceAlertsPane({ focused, width, height }: PaneProps) {
366
408
  color: colors.textBright,
367
409
  attributes: TextAttributes.BOLD,
368
410
  };
411
+ case "metric":
412
+ return { text: alert.metric ? metricName(alert.metric) : "Price" };
369
413
  case "current":
370
414
  return {
371
415
  text: formatCurrentPrice(alert, column.width),
416
+ // The quadrant word exports with the readings that place it.
417
+ ...(alert.metric?.kind === "rrg" && !alert.lastCheckError && alert.lastCheckedPrice != null && alert.lastCheckedTarget != null
418
+ ? { value: metricReadingText(alert.metric, { value: alert.lastCheckedPrice, target: alert.lastCheckedTarget }) } : {}),
372
419
  color: alert.lastCheckError ? colors.negative : colors.text,
373
420
  };
374
421
  case "target":
@@ -379,10 +426,12 @@ function PriceAlertsPane({ focused, width, height }: PaneProps) {
379
426
  color: colors.textDim,
380
427
  };
381
428
  case "condition":
382
- return { text: conditionLabel(alert.condition) };
429
+ return { text: alert.metric?.kind === "ma_cross" ? `Cross ${alert.metric.direction}` : alert.metric?.kind === "rrg" ? "Enters" : conditionLabel(alert.condition) };
383
430
  case "quote":
384
431
  return {
385
432
  text: formatQuoteChecked(alert),
433
+ // A settled close exports as its session date.
434
+ ...(alert.metric && closeBasis(alert.metric) && alert.lastDailyClose && !alert.lastCheckError ? { value: alert.lastDailyClose } : {}),
386
435
  color: alert.lastCheckError ? colors.negative : colors.textDim,
387
436
  };
388
437
  case "triggered":
@@ -390,6 +439,8 @@ function PriceAlertsPane({ focused, width, height }: PaneProps) {
390
439
  text: alert.triggeredAt ? relativeTime(alert.triggeredAt) : "-",
391
440
  color: colors.textDim,
392
441
  };
442
+ case "created":
443
+ return { text: isoUtc(alert.createdAt).slice(0, 10), color: colors.textDim };
393
444
  }
394
445
  }, []);
395
446
 
@@ -410,7 +461,7 @@ function PriceAlertsPane({ focused, width, height }: PaneProps) {
410
461
  rootWidth={width}
411
462
  rootHeight={height}
412
463
  rootBackgroundColor={colors.bg}
413
- columns={ALERT_COLUMNS}
464
+ columns={columns}
414
465
  items={displayRows}
415
466
  sortColumnId={null}
416
467
  sortDirection="asc"
@@ -2,6 +2,7 @@ import type { DataProvider } from "../../../types/data-provider";
2
2
  import type { Quote } from "../../../types/financials";
3
3
  import { getActiveQuoteDisplay } from "../../../market-data/market/status";
4
4
  import { isQuoteStaleForCurrentSession } from "../../../market-data/quotes/freshness";
5
+ import { canonicalExchange } from "../../../utils/exchanges";
5
6
  import type { AlertRule } from "./types";
6
7
 
7
8
  export function normalizeAlertSymbol(value: string | null | undefined): string {
@@ -46,13 +47,18 @@ export function quoteAlertFields(
46
47
  | "lastQuoteUpdatedAt"
47
48
  | "lastQuoteSource"
48
49
  | "lastQuoteProviderId"
50
+ | "lastQuoteCurrency"
51
+ | "lastQuoteExchange"
49
52
  > & { lastCheckError?: undefined } {
53
+ const exchange = quote.listingExchangeName || quote.exchangeName;
50
54
  return {
51
55
  lastCheckedPrice: quote.price,
52
56
  lastCheckedAt: checkedAt,
53
57
  lastQuoteUpdatedAt: quote.lastUpdated,
54
58
  lastQuoteSource: quote.dataSource,
55
59
  lastQuoteProviderId: quote.providerId,
60
+ ...(quote.currency ? { lastQuoteCurrency: quote.currency } : {}),
61
+ ...(exchange ? { lastQuoteExchange: canonicalExchange(exchange) } : {}),
56
62
  lastCheckError: undefined,
57
63
  };
58
64
  }
@@ -1,3 +1,4 @@
1
+ import { METRIC_ALERTS_KEY, RELATIVE_ALERTS_KEY, metricAlertsKey } from "./metric-model";
1
2
  import type { AppConfig } from "../../../types/config";
2
3
  import type { GloomPluginContext } from "../../../types/plugin";
3
4
  import {
@@ -10,30 +11,56 @@ import { readEventAlertStores } from "./events";
10
11
  import type { AlertRule } from "./types";
11
12
 
12
13
  export function loadAlerts(ctx: GloomPluginContext): AlertRule[] {
13
- const json = ctx.configState.get<unknown>(ALERTS_KEY);
14
- // Background evaluation must not notify from a partial store it cannot safely update.
15
- if (readAlertsStoreError(json)) return [];
16
- return deserializeAlerts(json);
14
+ const result = readLevelAlertStores((key) => ctx.configState.get<unknown>(key));
15
+ return result.alerts;
17
16
  }
18
17
 
19
18
  export async function saveAlerts(
20
19
  ctx: GloomPluginContext,
21
20
  alerts: AlertRule[],
21
+ scope: "all" | "price" | "metric" = "all",
22
22
  ): Promise<void> {
23
- const error = readAlertsStoreError(ctx.configState.get<unknown>(ALERTS_KEY));
24
- if (error) throw new Error(error);
25
- await ctx.configState.set(ALERTS_KEY, serializeAlerts(alerts));
23
+ const stores = readLevelAlertStores((key) => ctx.configState.get<unknown>(key));
24
+ // An unreadable list is never written over; one that has nothing to write does not hold up the others.
25
+ const entries = Object.entries(writeLevelAlertStores(alerts)).filter(([key, value]) => (scope === "all"
26
+ || (scope === "price" ? key === ALERTS_KEY : key !== ALERTS_KEY)) && !(stores.errors[key] && value === "[]"));
27
+ for (const [key] of entries) if (stores.errors[key]) throw new Error(stores.errors[key]!);
28
+ for (const [key, value] of entries) {
29
+ if (ctx.configState.get<unknown>(key) !== value) await ctx.configState.set(key, value);
30
+ }
26
31
  }
27
32
 
28
33
  /** Both alert stores as the saved config holds them, for the CLI and the reports, which have no plugin context. */
29
34
  export function readAlertStores(config: AppConfig) {
30
35
  const pluginState = config.pluginConfig[ALERTS_PLUGIN_ID] ?? {};
31
- const rawPrice = pluginState[ALERTS_KEY];
36
+ const levels = readLevelAlertStores((key) => pluginState[key]);
32
37
  return {
33
38
  pluginState,
34
- alerts: deserializeAlerts(rawPrice),
35
- alertsError: readAlertsStoreError(rawPrice),
39
+ alerts: levels.alerts,
40
+ alertsError: levels.error,
41
+ levelErrors: levels.errors,
36
42
  // The event alerts, and the IV levels kept apart from them.
37
43
  events: readEventAlertStores((key) => pluginState[key]),
38
44
  };
39
45
  }
46
+
47
+ export function readLevelAlertStores(get: (key: string) => unknown): { alerts: AlertRule[]; error: string | null; errors: Record<string, string | null> } {
48
+ const alerts: AlertRule[] = [];
49
+ const errors: Record<string, string | null> = {};
50
+ for (const key of LEVEL_ALERT_KEYS) {
51
+ const raw = get(key);
52
+ let error = readAlertsStoreError(raw);
53
+ const rows = error ? [] : deserializeAlerts(raw);
54
+ if (rows.some(a => levelAlertStoreKey(a) !== key)) error = "Saved alerts contain a rule in the wrong list. Original data has been kept.";
55
+ errors[key] = key === METRIC_ALERTS_KEY ? error?.replaceAll("price alerts", "valuation and technical alerts") ?? null
56
+ : key === RELATIVE_ALERTS_KEY ? error?.replaceAll("price alerts", "relative strength alerts") ?? null : error;
57
+ if (!error) alerts.push(...rows);
58
+ }
59
+ return { alerts, errors, error: Object.values(errors).filter(Boolean).join(" ") || null };
60
+ }
61
+ export function writeLevelAlertStores(alerts: AlertRule[]): Record<string, string> {
62
+ return Object.fromEntries(LEVEL_ALERT_KEYS.map(key => [key, serializeAlerts(alerts.filter(a => levelAlertStoreKey(a) === key))]));
63
+ }
64
+ /** Price, valuation/technical and relative strength rules each keep their own synced list. */
65
+ const LEVEL_ALERT_KEYS = [ALERTS_KEY, METRIC_ALERTS_KEY, RELATIVE_ALERTS_KEY] as const;
66
+ export const levelAlertStoreKey = (alert: Pick<AlertRule, "metric">): string => alert.metric ? metricAlertsKey(alert.metric) : ALERTS_KEY;
@@ -9,6 +9,9 @@ export interface AlertRule {
9
9
  exchange?: string;
10
10
  condition: AlertCondition;
11
11
  targetPrice: number;
12
+ metric?: import("./metric-model").AlertMetric;
13
+ lastDailyClose?: string;
14
+ lastCheckedTarget?: number;
12
15
  createdAt: number;
13
16
  /** When an edit or re-arm last armed the rule; createdAt until then. Gloom Cloud emails a trigger once per arming. */
14
17
  armedAt?: number;
@@ -20,5 +23,9 @@ export interface AlertRule {
20
23
  lastQuoteUpdatedAt?: number;
21
24
  lastQuoteSource?: QuoteDataSource;
22
25
  lastQuoteProviderId?: string;
26
+ /** The currency of the quote the alert was last checked on (USD): its listing's, which lists show beside it. */
27
+ lastQuoteCurrency?: string;
28
+ /** The venue that quote was from (ARCA), which names the listing of a rule set by symbol alone. */
29
+ lastQuoteExchange?: string;
23
30
  message?: string;
24
31
  }
@@ -1,6 +1,7 @@
1
1
  import { usePaneSettingValue } from "../../../public/react";
2
2
  import { PortfolioRiskPane } from "./risk-pane";
3
3
  import { portfolioRiskHeadless } from "./risk-headless";
4
+ import { NO_HOLDINGS_HINT } from "./risk-model";
4
5
  import { portfolioRiskCache } from "./risk-client";
5
6
  import { Box, Text } from "../../../ui";
6
7
  import { useCallback, useEffect, useMemo, useState } from "react";
@@ -398,7 +399,7 @@ function LegacyPortfolioAnalyticsPane({ focused, width, height }: PaneProps) {
398
399
  message={accountsError ?? undefined}
399
400
  hint={accountsError
400
401
  ? "Reconnect the broker in the Brokers pane (BR), then refresh."
401
- : "Add holdings from the Portfolio pane (PF), or connect a broker in BR to sync them."}
402
+ : NO_HOLDINGS_HINT}
402
403
  />
403
404
  </Box>
404
405
  ) : historyOnly ? (
@@ -58,7 +58,12 @@ interface RiskMarketHistory {
58
58
  * Set when the listing quotes in another currency and its closes were
59
59
  * restated in USD at same-date daily FX closes; quote and closeMark are then in USD.
60
60
  */
61
- converted?: { currency: string; fxAsOf: string } | null;
61
+ converted?: {
62
+ currency: string;
63
+ fxAsOf: string;
64
+ /** US sessions converted at the FX close before them, the pair having none that day (bridgedFxClose). */
65
+ fxBridged?: string[];
66
+ } | null;
62
67
  error: string | null;
63
68
  }
64
69
  export interface RiskMarketSnapshot {
@@ -162,6 +167,8 @@ export function validateRiskHistory(
162
167
  closeMark: { price: number; date: string; currency: string } | null;
163
168
  /** Latest completed close in the returns' currency, whatever marks the holding. */
164
169
  latestClose: { price: number; date: string };
170
+ /** With a conversion, the US sessions converted at the FX close before them (bridgedFxClose). */
171
+ fxBridged?: string[];
165
172
  } {
166
173
  if (
167
174
  response.status !== "success" ||
@@ -244,13 +251,14 @@ export function validateRiskHistory(
244
251
  if (local.integrity)
245
252
  throw new Error("Daily history has inconsistent OHLC observations");
246
253
  // A converted series is on the NYSE calendar the basket and SPY share.
247
- const series = conversion
254
+ const usd = conversion
248
255
  ? convertClosesToUsd(
249
256
  points.map((point) => ({ date: new Date(point.date).toISOString().slice(0, 10), close: point.close })),
250
257
  conversion.fx,
251
258
  conversion.divisor,
252
259
  )
253
- : prices;
260
+ : null;
261
+ const series = usd ? usd.points : prices;
254
262
  const returns = conversion ? resolveDatedReturns(series).returns : local.returns;
255
263
  const cadence = qualifySharpeCadence(returns, [
256
264
  { symbol: instrument.symbol, exchange: conversion ? "NYSE" : exchange, history: series },
@@ -264,6 +272,7 @@ export function validateRiskHistory(
264
272
  };
265
273
  return {
266
274
  returns,
275
+ ...(usd?.bridged.length ? { fxBridged: usd.bridged } : {}),
267
276
  asOf: latestClose.date,
268
277
  // A missing or stale quote is not a current mark; the latest dated close is.
269
278
  closeMark:
@@ -591,7 +600,7 @@ export async function fetchPortfolioRiskMarket(
591
600
  historyParams,
592
601
  signal ? { signal } : undefined,
593
602
  );
594
- const { latestClose, ...resolved } = validateRiskHistory(
603
+ const { latestClose, fxBridged, ...resolved } = validateRiskHistory(
595
604
  response,
596
605
  instrument,
597
606
  quote,
@@ -610,7 +619,9 @@ export async function fetchPortfolioRiskMarket(
610
619
  quote: current,
611
620
  currency: "USD",
612
621
  closeMark: current ? null : { ...latestClose, currency: "USD" },
613
- converted: { currency: conversion.listingCurrency, fxAsOf: fxCloses.asOf },
622
+ converted: {
623
+ currency: conversion.listingCurrency, fxAsOf: fxCloses.asOf, ...(fxBridged?.length ? { fxBridged } : {}),
624
+ },
614
625
  error: null,
615
626
  };
616
627
  continue;