@gloom-sh/terminal 0.18.5 → 0.19.1

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 (570) 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 +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  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 +19 -7
  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/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  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/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -1,9 +1,11 @@
1
- import { METRIC_CHOICES, metricWizardFields } from "./metric-command";
2
- import { metricReadings } from "./metric-data";
3
- import { metricTrigger, isDailyMetric, type MetricReading } from "./metric-model";
1
+ import { METRIC_CHOICES, benchmarkKey, metricWizardFields, parseMetricAlert } from "./metric-command";
2
+ import { firstDailyCheck, metricAvailabilityError, metricReadings } from "./metric-data";
3
+ import { alertLogRule } from "./alert-log";
4
+ import { closeBasis, isDailyMetric, isRelativeMetric, metricReadingText, metricTrigger, validMetricRule, type MetricReading } from "./metric-model";
5
+ import { canonicalExchange } from "../../../utils/exchanges";
4
6
  import { startCatalystAlerts } from "./catalysts";
5
7
  import { notTheCoinNote } from "../../../cli/commands/crypto-hints";
6
- import { createFiredAlertRecorder, firedAlertTime, quoteSourceWords } from "./fired-log";
8
+ import { alertChangeEntry, createFiredAlertRecorder, firedAlertTime, quoteSourceWords } from "./fired-log";
7
9
  import { checkIvLevelAlertsNow, startIvLevelAlerts } from "./iv-level";
8
10
  import { logFailure } from "../../../utils/log-failure";
9
11
  import { RESEARCH_FORM_KINDS, RESEARCH_LABELS, researchWizardFields, createResearchAlert } from "./research-builder";
@@ -22,12 +24,15 @@ import { isResearchAlertKind, MAX_LEAD_DAYS } from "./research-rules";
22
24
  import { EVENT_ALERT_TEMPLATES, MAX_EVENT_ALERTS, createEventAlert, eventAlertStoreKey, readEventAlertStores, updateEventAlerts } from "./events";
23
25
  import { formatMarketPrice } from "../../../market-data/market/format";
24
26
  import { settleTypedListingKey } from "../../../tickers/listing-venues";
27
+ import { parseListingArg } from "../../../cli/listing-arg";
28
+ import { notATickerSymbol } from "../../../sources/provider-errors";
25
29
  import type { Quote } from "../../../types/financials";
26
30
  import type { GloomPlugin } from "../../../types/plugin";
27
31
  import {
28
32
  createAlert,
29
33
  evaluateAlert,
30
34
  formatAlertDescription,
35
+ sameLevelRule,
31
36
  } from "./alert-engine";
32
37
  import {
33
38
  parseAlertCommandValues,
@@ -65,6 +70,16 @@ let alertStream: AlertQuoteStream | null = null;
65
70
  */
66
71
  const STREAM_PERSIST_MS = 60_000;
67
72
 
73
+ /** BTC/USD, BRK/B and EUR/USD are one listing written with a slash, as `alerts add` reads them; XLK/SPY is not. */
74
+ function slashSpellsListing(symbol: string): boolean {
75
+ try {
76
+ return parseListingArg(symbol).key !== symbol.toUpperCase();
77
+ } catch {
78
+ // Intentional ignore: text the listing reader refuses is no pair to divide, so the quote's own error stands.
79
+ return true;
80
+ }
81
+ }
82
+
68
83
  export const alertsPlugin: GloomPlugin = {
69
84
  id: "alerts",
70
85
  name: "Alerts",
@@ -186,31 +201,75 @@ export const alertsPlugin: GloomPlugin = {
186
201
  ...metricWizardFields,
187
202
  ],
188
203
  async execute(values) {
189
- const input = parseAlertCommandValues(values);
204
+ let input = parseAlertCommandValues(values);
190
205
  if (!input) throw new Error("Use a symbol, condition, and target price.");
191
206
  // 7203:TSE, typed or from a link, is the venue its symbol trades on, as `alerts add` reads it.
192
- const symbol = (await settleTypedListingKey(input.symbol, { store: ctx.tickerRepository, dataProvider: ctx.marketData })).toUpperCase();
193
-
194
- const quote = await resolveAlertQuote(ctx.marketData, symbol);
207
+ const quoteFor = async (typed: string) => {
208
+ const key = (await settleTypedListingKey(typed, { store: ctx.tickerRepository, dataProvider: ctx.marketData })).toUpperCase();
209
+ return { key, quote: await resolveAlertQuote(ctx.marketData, key) };
210
+ };
211
+ let resolved: Awaited<ReturnType<typeof quoteFor>>;
212
+ try {
213
+ resolved = await quoteFor(input.symbol);
214
+ } catch (error) {
215
+ // XLK/SPY is a listing first; only one the service says is no ticker is the ratio of the two, as `alerts add` reads it.
216
+ // A timeout stays a timeout, and BTC/USD, BRK/B or EUR/USD, one listing written with a slash, keeps its own answer.
217
+ const pair = !input.metric && /^[^/\s]+\/[^/\s]+$/.test(input.symbol) && notATickerSymbol(error instanceof Error ? error.message : String(error))
218
+ && !slashSpellsListing(input.symbol) ? parseMetricAlert(`${input.symbol} ratio ${input.condition} ${input.price}`) : null;
219
+ if (!pair) throw error;
220
+ input = pair;
221
+ resolved = await quoteFor(pair.symbol);
222
+ }
223
+ const { key: symbol, quote } = resolved;
224
+ let metric = input.metric;
225
+ if (metric && isRelativeMetric(metric)) {
226
+ const reference = (await quoteFor(benchmarkKey(metric))).quote;
227
+ const benchmarkExchange = canonicalExchange(reference.listingExchangeName || reference.exchangeName || "");
228
+ const exchange = canonicalExchange(quote.listingExchangeName || quote.exchangeName || "");
229
+ const benchmark = reference.symbol || metric.benchmark;
230
+ if (benchmark === (quote.symbol || symbol) && benchmarkExchange === exchange) throw new Error(`Compare ${benchmark} with another listing.`);
231
+ metric = { ...metric, benchmark, ...(benchmarkExchange ? { benchmarkExchange } : {}) };
232
+ // Never save a rule its own list would refuse to read back.
233
+ if (!validMetricRule({ metric, condition: input.condition, targetPrice: input.price })) throw new Error(`${benchmark} cannot be a benchmark: relative strength alerts compare two US listings.`);
234
+ }
195
235
  // A target ten times away is a slip or another instrument's price, such as bitcoin's on the BTC fund.
196
236
  const far = farTargetMessage(symbol, input.price, quote.price, symbol.includes(":") ? null : notTheCoinNote(symbol, quote));
197
237
  if (!input.metric && far) throw new Error(far);
198
238
 
199
- const alert = {
239
+ const alert: AlertRule = {
200
240
  ...createAlert(quote.symbol || symbol, input.condition, input.price),
201
- ...(!input.metric ? quoteAlertFields(quote) : { metric: input.metric, exchange: quote.listingExchangeName || quote.exchangeName }),
241
+ ...(!metric ? quoteAlertFields(quote) : {
242
+ metric, exchange: quote.listingExchangeName || quote.exchangeName,
243
+ ...quote.currency ? { lastQuoteCurrency: quote.currency } : {},
244
+ }),
202
245
  };
203
- if (input.metric) {
204
- const reading = await metricReadings(ctx.marketData)(alert);
205
- Object.assign(alert, { lastCheckedPrice: reading.value, lastCheckedTarget: reading.target, lastQuoteUpdatedAt: reading.at });
246
+ // A close not yet final is read once it is: the rule is kept and says when it is first judged.
247
+ let firstCheck: string | null = null;
248
+ if (metric) {
249
+ const now = Date.now();
250
+ if (isDailyMetric(metric)) {
251
+ const check = firstDailyCheck(alert.exchange, now, metric.kind === "rrg");
252
+ if (check) firstCheck = `first checked on the ${check.day} ${closeBasis(metric) === "weekly close" ? "weekly close" : "close"}, from ${new Date(check.at).toISOString().slice(11, 16)} UTC`;
253
+ }
254
+ try {
255
+ const reading = await metricReadings(ctx.marketData, now)(alert);
256
+ Object.assign(alert, { lastCheckedPrice: reading.value, lastCheckedTarget: reading.target, lastQuoteUpdatedAt: reading.at,
257
+ lastCheckedAt: now, ...reading.day ? { lastDailyClose: reading.day } : {} });
258
+ } catch (error) {
259
+ if (!isDailyMetric(metric) || metricAvailabilityError(alert)) throw error;
260
+ }
206
261
  }
207
262
  const existing = loadAlerts(ctx);
263
+ // The same rule on the same listing is one alert: adding it again names it and adds nothing.
264
+ const duplicate = existing.find((entry) => sameLevelRule(entry, alert));
265
+ if (duplicate) throw new Error(`${formatAlertDescription(alert)} already exists (alert ${duplicate.id}). Nothing was added.`);
208
266
  existing.push(alert);
209
267
  await saveAlerts(ctx, existing, input.metric ? "metric" : "price");
268
+ recordFired(alertChangeEntry("created", alertLogRule(alert)));
210
269
  alertStream?.sync();
211
270
 
212
271
  ctx.notify({
213
- body: `Alert set: ${formatAlertDescription(alert)}${input.metric ? "" : ` (current ${formatMarketPrice(quote.price, { minimumFractionDigits: 2 })})`}`,
272
+ body: `Alert set: ${formatAlertDescription(alert)}${input.metric ? firstCheck ? `, ${firstCheck}` : "" : ` (current ${formatMarketPrice(quote.price, { minimumFractionDigits: 2 })})`}`,
214
273
  type: "success",
215
274
  });
216
275
  },
@@ -281,6 +340,7 @@ export const alertsPlugin: GloomPlugin = {
281
340
  // An IV level goes in its own list, which released versions never read.
282
341
  const key = eventAlertStoreKey(rule);
283
342
  await ctx.configState.set(key, updateEventAlerts(ctx.configState.get<string>(key), (rules) => [...rules, rule]));
343
+ recordFired(alertChangeEntry("created", alertLogRule(rule)));
284
344
  ctx.notify({ body: "Event alert saved.", type: "success" });
285
345
  if (scheduled) checkScheduledEventAlertsNow();
286
346
  if (rule.kind === "iv_level") checkIvLevelAlertsNow();
@@ -335,11 +395,14 @@ export const alertsPlugin: GloomPlugin = {
335
395
  const firedAt = Date.now();
336
396
  alert.status = "triggered"; alert.triggeredAt = firedAt;
337
397
  const description = formatAlertDescription(alert);
398
+ const readingText = metricReadingText(alert.metric, reading);
338
399
  recordFired({ kind: alert.metric.kind, ruleId: alert.id, rule: description, firedAt, value: reading.value,
339
400
  unit: alert.metric.kind === "pe" || alert.metric.kind === "ps" ? "x" : undefined,
340
- quoteAt: firedAlertTime(reading.at), source: isDailyMetric(alert.metric) ? "daily close" : null,
401
+ // An RRG quadrant is two readings; the figure is strength, the detail says both.
402
+ ...(alert.metric.kind === "rrg" ? { detail: readingText } : {}),
403
+ quoteAt: firedAlertTime(reading.at), source: closeBasis(alert.metric),
341
404
  symbol: alert.symbol, exchange: alert.exchange });
342
- ctx.notify({ body: `${description} triggered at ${Number(reading.value.toFixed(4))}`, type: "success",
405
+ ctx.notify({ body: alert.metric.kind === "rrg" ? `${description} triggered: ${readingText}` : `${description} triggered at ${readingText}`, type: "success",
343
406
  refId: `price:${alert.id}`, desktop: "always", persistent: true, sound: "Glass",
344
407
  action: { label: "Open", onClick: () => ctx.showPane("alerts") } });
345
408
  }
@@ -1,8 +1,9 @@
1
- import type { AlertMetric } from "./metric-model";
2
- import { validMetricRule } from "./metric-model";
1
+ import type { AlertMetric, RotationQuadrant } from "./metric-model";
2
+ import { ROTATION_QUADRANTS, validMetricRule } from "./metric-model";
3
3
  import { readAlertPrice } from "./price";
4
4
  import type { AlertCondition } from "./types";
5
5
  import type { WizardStep } from "../../../types/plugin";
6
+ import { parsePublicTickerKey } from "../../../utils/exchanges";
6
7
 
7
8
  export interface MetricAlertInput { symbol: string; condition: AlertCondition; price: number; metric: AlertMetric }
8
9
  const direction = (s: string) => /^(above|over|>)$/i.test(s) ? "above" as const : /^(below|under|<)$/i.test(s) ? "below" as const : null;
@@ -11,10 +12,58 @@ function checked(input: MetricAlertInput): MetricAlertInput {
11
12
  if (!validMetricRule({ ...input, targetPrice: input.price })) throw new Error("Use a positive multiple, RSI from 0 to 100, and whole MA/RSI periods from 2 to 200 (fast MA shorter than slow MA).");
12
13
  return input;
13
14
  }
15
+ // RRG's own default benchmark (relative-rotation/model.ts), kept here so the CLI does not load the RRG pane's model.
16
+ const DEFAULT_ROTATION_BENCHMARK = "SPY:NYSEARCA";
17
+ const RELATIVE_USAGE = "Relative strength alerts take the ratio of two listings or an RRG quadrant: alerts add XLK/SPY above 0.25, or alerts add XLE rrg leading [--benchmark SPY].";
18
+ const QUADRANT_USAGE = "An RRG alert names the quadrant it enters: leading, weakening, lagging or improving, such as alerts add XLE rrg leading [--benchmark SPY].";
19
+ /** A benchmark typed as SPY or SPY:NYSEARCA; its listing is confirmed when the alert is added. */
20
+ function benchmarkListing(text: string): { benchmark: string; benchmarkExchange?: string } {
21
+ const parsed = parsePublicTickerKey(text.toUpperCase());
22
+ if (!/^[A-Z0-9.^=-]{1,20}$/.test(parsed.symbol)) throw new Error(`Not a benchmark listing: ${text}.`);
23
+ return { benchmark: parsed.symbol, ...(parsed.exchange ? { benchmarkExchange: parsed.exchange } : {}) };
24
+ }
25
+ const sameListing = (symbol: string, metric: { benchmark: string }) => parsePublicTickerKey(symbol).symbol === metric.benchmark;
26
+ /**
27
+ * `XLK/SPY ratio above 0.25` and `XLE rrg leading [vs SPY]`; undefined for anything else. `XLK/SPY above 0.25`
28
+ * without the word is first a listing (BRK/B, EUR/USD), so `alerts add` reads it as a ratio only once it quotes nothing.
29
+ */
30
+ function parseRelativeAlert(parts: readonly string[]): MetricAlertInput | undefined {
31
+ const [first = "", second = "", ...rest] = parts;
32
+ const kind = second.toLowerCase();
33
+ if (kind === "rrg") {
34
+ const [quadrant = "", vs, benchmark, ...extra] = rest;
35
+ if (first.includes("/")) throw new Error(`Name the benchmark of an RRG alert after it: alerts add ${first.split("/")[0]!.toUpperCase()} rrg ${quadrant.toLowerCase() || "leading"} --benchmark ${first.split("/")[1]!.toUpperCase()}.`);
36
+ if (!(ROTATION_QUADRANTS as readonly string[]).includes(quadrant.toLowerCase())) throw new Error(QUADRANT_USAGE);
37
+ if (vs !== undefined && (!/^(vs\.?|versus|against)$/i.test(vs) || !benchmark || extra.length > 0)) throw new Error(QUADRANT_USAGE);
38
+ const metric = { kind: "rrg" as const, quadrant: quadrant.toLowerCase() as RotationQuadrant, ...benchmarkListing(benchmark ?? DEFAULT_ROTATION_BENCHMARK) };
39
+ if (sameListing(first, metric)) throw new Error(`An RRG alert compares ${first.toUpperCase()} with another listing: alerts add ${first.toUpperCase()} rrg ${metric.quadrant} --benchmark SPY.`);
40
+ return checked({ symbol: first.toUpperCase(), condition: "crosses", price: 0, metric });
41
+ }
42
+ const pair = /^([^/]+)\/([^/]+)$/.exec(first);
43
+ if (pair && kind === "ratio") {
44
+ const [typed = "", level, ...extra] = rest;
45
+ const condition = direction(typed) ?? (/^(crosses|cross|x)$/i.test(typed) ? "crosses" as const : null);
46
+ const example = `alerts add ${first.toUpperCase()} above 0.25`;
47
+ if (!condition || level === undefined || extra.length > 0) throw new Error(`A ratio alert takes above, below or crosses and a ratio: ${example}.`);
48
+ const metric = { kind: "rs_ratio" as const, ...benchmarkListing(pair[2]!) };
49
+ if (sameListing(pair[1]!, metric)) throw new Error(`A ratio alert divides one listing by another: ${example}.`);
50
+ const price = readAlertPrice(level);
51
+ if (!(price > 0)) throw new Error(`A ratio alert takes a positive ratio: ${example}.`);
52
+ return checked({ symbol: pair[1]!.toUpperCase(), condition, price, metric });
53
+ }
54
+ // `XLE rs above 100`, `XLE vs SPY above 1`, `XLE relative strength leading`: a relative alert typed another way.
55
+ if (/^(rs|rsr|rs-?ratio|rs_ratio|rs-?momentum|rel|relative|rotation|quadrant|ratio|vs\.?|versus)$/i.test(kind)
56
+ && rest.some((word) => /^(above|below|over|under|crosses|cross|>|<|leading|weakening|lagging|improving|rrg)$/i.test(word))) {
57
+ throw new Error(RELATIVE_USAGE);
58
+ }
59
+ return undefined;
60
+ }
14
61
  /** Parses both compact CLI notation and the words traders type, without swallowing malformed metrics as calendar events. */
15
62
  export function parseMetricAlert(input: string): MetricAlertInput | null {
16
63
  const words = input.trim().replace(/\s+/g, " ");
17
64
  const parts = words.split(" ");
65
+ const relative = parseRelativeAlert(parts);
66
+ if (relative) return relative;
18
67
  if (!/^(p\/?e|p\/?s|rsi(?:\(|$)|ma(?:\(|$)|\d+-day|closes?$)/i.test(parts[1] ?? "")) return null;
19
68
  const ratio = /^(\S+) (p\/?e|p\/?s) (above|below|over|under|>|<) (\S+)$/i.exec(words);
20
69
  if (ratio) return checked({ symbol: ratio[1]!.toUpperCase(), metric: { kind: ratio[2]!.toLowerCase().replace("/", "") as "pe" | "ps" }, condition: direction(ratio[3]!)!, price: readAlertPrice(ratio[4]!) });
@@ -27,8 +76,17 @@ export function parseMetricAlert(input: string): MetricAlertInput | null {
27
76
  if (close) return checked({ symbol: close[1]!.toUpperCase(), metric: { kind: "close_ma", period: Number(close[3]) }, condition: direction(close[2]!)!, price: 0 });
28
77
  throw new Error(USAGE);
29
78
  }
79
+ export const benchmarkKey = (metric: { benchmark: string; benchmarkExchange?: string }) => `${metric.benchmark}${metric.benchmarkExchange ? `:${metric.benchmarkExchange}` : ""}`;
80
+ /** A relative alert as `alerts add` takes it, for editing: `XLK/SPY ratio above 0.25`, `XLE rrg leading vs SPY`. */
81
+ export function relativeAlertText(input: { symbol: string; condition: AlertCondition; targetPrice: number; metric?: AlertMetric }): string | null {
82
+ const m = input.metric;
83
+ if (m?.kind === "rs_ratio") return `${input.symbol}/${m.benchmark} ratio ${input.condition} ${input.targetPrice}`;
84
+ return m?.kind === "rrg" ? `${input.symbol} rrg ${m.quadrant} vs ${m.benchmark}` : null;
85
+ }
30
86
  export function metricShortcut(input: MetricAlertInput): Record<string, string> {
31
87
  const m = input.metric;
88
+ if (m.kind === "rs_ratio") return { symbol: input.symbol, kind: m.kind, "rs_ratio:benchmark": benchmarkKey(m), "rs_ratio:condition": input.condition, "rs_ratio:target": String(input.price) };
89
+ if (m.kind === "rrg") return { symbol: input.symbol, kind: m.kind, "rrg:quadrant": m.quadrant, "rrg:benchmark": benchmarkKey(m) };
32
90
  return { symbol: input.symbol, kind: m.kind, [`${m.kind}:condition`]: m.kind === "ma_cross" ? m.direction : input.condition,
33
91
  [`${m.kind}:target`]: String(input.price), ...("period" in m ? { [`${m.kind}:period`]: String(m.period) } : {}),
34
92
  ...(m.kind === "ma_cross" ? { "ma_cross:slowPeriod": String(m.slowPeriod) } : {}) };
@@ -38,6 +96,11 @@ export function metricFormInput(values: Record<string, string>): MetricAlertInpu
38
96
  if (!kind || kind === "price") return null;
39
97
  const symbol = values.symbol?.trim().toUpperCase();
40
98
  if (!symbol) return null;
99
+ if (kind === "rs_ratio") {
100
+ const benchmark = values["rs_ratio:benchmark"]?.trim();
101
+ return benchmark ? parseMetricAlert(`${symbol}/${benchmark} ratio ${values["rs_ratio:condition"] ?? "above"} ${values["rs_ratio:target"] ?? ""}`) : null;
102
+ }
103
+ if (kind === "rrg") return parseMetricAlert(`${symbol} rrg ${values["rrg:quadrant"] ?? "leading"} vs ${values["rrg:benchmark"]?.trim() || DEFAULT_ROTATION_BENCHMARK}`);
41
104
  const condition = values[`${kind}:condition`] ?? "below";
42
105
  const window = values[`${kind}:period`] ?? (kind === "rsi" ? "14" : kind === "ma_cross" ? "50" : "200");
43
106
  const text = kind === "ma_cross" ? `${symbol} ma(${window}) crosses ${condition} ma(${values["ma_cross:slowPeriod"] ?? "200"})`
@@ -48,8 +111,18 @@ export function metricFormInput(values: Record<string, string>): MetricAlertInpu
48
111
  export const METRIC_CHOICES = [
49
112
  { value: "price", label: "Price" }, { value: "pe", label: "P/E (TTM)" }, { value: "ps", label: "P/S (TTM)" },
50
113
  { value: "rsi", label: "RSI" }, { value: "ma_cross", label: "MA crossover" }, { value: "close_ma", label: "Close vs MA" },
114
+ { value: "rs_ratio", label: "Ratio to benchmark" }, { value: "rrg", label: "RRG quadrant" },
115
+ ];
116
+ const RELATIVE_FIELDS: WizardStep[] = [
117
+ { key: "rs_ratio:benchmark", label: "Divided by", type: "text", placeholder: "SPY", dependsOn: { key: "kind", value: "rs_ratio" } },
118
+ { key: "rs_ratio:condition", label: "Condition", type: "select", defaultValue: "above", dependsOn: { key: "kind", value: "rs_ratio" },
119
+ options: [{ label: "Above", value: "above" }, { label: "Below", value: "below" }, { label: "Crosses", value: "crosses" }] },
120
+ { key: "rs_ratio:target", label: "Ratio", type: "number", placeholder: "0.25", dependsOn: { key: "kind", value: "rs_ratio" } },
121
+ { key: "rrg:quadrant", label: "Enters", type: "select", defaultValue: "leading", dependsOn: { key: "kind", value: "rrg" },
122
+ options: ROTATION_QUADRANTS.map((value) => ({ label: `${value[0]!.toUpperCase()}${value.slice(1)}`, value })) },
123
+ { key: "rrg:benchmark", label: "Benchmark", type: "text", defaultValue: "SPY", placeholder: "SPY", dependsOn: { key: "kind", value: "rrg" } },
51
124
  ];
52
- export const metricWizardFields: WizardStep[] = METRIC_CHOICES.filter(x => x.value !== "price").flatMap(({ value: kind }) => {
125
+ export const metricWizardFields: WizardStep[] = [...METRIC_CHOICES.filter(x => x.value !== "price" && x.value !== "rs_ratio" && x.value !== "rrg").flatMap(({ value: kind }) => {
53
126
  const dependsOn = { key: "kind", value: kind };
54
127
  return [
55
128
  ...(["rsi", "ma_cross", "close_ma"].includes(kind) ? [{ key: `${kind}:period`, label: kind === "ma_cross" ? "Fast MA (days)" : kind === "rsi" ? "RSI period" : "MA (days)", type: "number" as const, defaultValue: kind === "rsi" ? "14" : kind === "ma_cross" ? "50" : "200", dependsOn }] : []),
@@ -58,4 +131,4 @@ export const metricWizardFields: WizardStep[] = METRIC_CHOICES.filter(x => x.val
58
131
  ...(kind === "ma_cross" ? [{ key: "ma_cross:slowPeriod", label: "Slow MA (days)", type: "number" as const, defaultValue: "200", dependsOn }]
59
132
  : kind === "close_ma" ? [] : [{ key: `${kind}:target`, label: kind === "rsi" ? "RSI level" : "Target multiple", type: "number" as const, placeholder: kind === "rsi" ? "30" : kind === "pe" ? "25" : "8", dependsOn }]),
60
133
  ];
61
- });
134
+ }), ...RELATIVE_FIELDS];
@@ -5,7 +5,9 @@ import { comparablePriceEarnings } from "../../../utils/price-earnings";
5
5
  import { canonicalExchange } from "../../../utils/exchanges";
6
6
  import { getPublishedUsEquitySession } from "../../../market-data/published-us-sessions";
7
7
  import { barSessionDay } from "../../../market-data/market/period-return";
8
- import { dailyMetricReading, latestSettledClose, DailyMetricCache, isDailyMetric, type MetricReading } from "./metric-model";
8
+ import { finalDailyBars } from "../../../time-series/session-bar";
9
+ import { getPricePointTimestamp } from "../../../utils/price-history";
10
+ import { DAILY_CLOSE_SETTLE_MS, dailyMetricReading, latestSettledClose, DailyMetricCache, isDailyMetric, isRelativeMetric, type MetricReading } from "./metric-model";
9
11
  import type { AlertRule } from "./types";
10
12
 
11
13
  // Keep valuation fixtures in step with the delivery evaluator and the research panes.
@@ -21,12 +23,43 @@ export function valuationAlertValue(kind: "pe" | "ps", data: TickerFinancials):
21
23
  return Number.isFinite(value) && value > 0 ? value : null;
22
24
  }
23
25
 
24
- export function metricAvailabilityError(rule: AlertRule): string | null {
26
+ const US_CALENDAR = "Daily technical alerts need a published US listing calendar; delivery is unavailable for this listing.";
27
+ /** Why a rule's listings cannot be judged on settled closes: a benchmark is held to the same US calendar as the listing. */
28
+ export function metricAvailabilityError(rule: AlertRule, now = Date.now()): string | null {
25
29
  if (!rule.metric || !isDailyMetric(rule.metric)) return null;
26
- return getPublishedUsEquitySession(canonicalExchange(rule.exchange ?? ""), new Date().toISOString().slice(0, 10))
27
- ? null : "Daily technical alerts need a published US listing calendar; delivery is unavailable for this listing.";
30
+ const today = new Date(now).toISOString().slice(0, 10);
31
+ if (!getPublishedUsEquitySession(canonicalExchange(rule.exchange ?? ""), today)) return US_CALENDAR;
32
+ return isRelativeMetric(rule.metric) && !getPublishedUsEquitySession(canonicalExchange(rule.metric.benchmarkExchange ?? ""), today)
33
+ ? `Relative strength alerts compare two US listings; ${rule.metric.benchmark} has no published US listing calendar here.` : null;
28
34
  }
29
35
 
36
+ /**
37
+ * The first close a daily technical rule set at `now` is judged on, and when:
38
+ * the next session to close after it, read once the close has settled
39
+ * (DAILY_CLOSE_SETTLE_MS). `weekly` (an RRG quadrant) waits for the last
40
+ * session of that Monday-Friday week, its weekly close. Null when the listing
41
+ * has no published calendar.
42
+ */
43
+ export function firstDailyCheck(exchange: string | undefined, now = Date.now(), weekly = false): { day: string; at: number } | null {
44
+ const venue = canonicalExchange(exchange ?? "");
45
+ let first: { day: string; close: number } | null = null;
46
+ for (let offset = 0; offset < 15; offset++) {
47
+ const time = now + offset * 86_400_000;
48
+ const day = new Date(time).toISOString().slice(0, 10);
49
+ // The weekly close is the last session before the weekend after the first close.
50
+ if (first && new Date(time).getUTCDay() === 6) break;
51
+ const session = getPublishedUsEquitySession(venue, day);
52
+ if (!session) return null;
53
+ if (session.kind !== "session" || session.close <= now) continue;
54
+ first = { day, close: session.close };
55
+ if (!weekly) break;
56
+ }
57
+ return first ? { day: first.day, at: first.close + DAILY_CLOSE_SETTLE_MS } : null;
58
+ }
59
+
60
+ /** The bar size the daily rules read, the one `history` and the charts ask for. */
61
+ const DAILY_BARS = "1d";
62
+
30
63
  const dailyCaches = new WeakMap<AssetDataProvider, DailyMetricCache>();
31
64
 
32
65
  /** A pass shares each listing's reads; there is no second provider or quote subscription. */
@@ -48,23 +81,31 @@ export function metricReadings(provider: AssetDataProvider, now = Date.now()) {
48
81
  }
49
82
  const exchange = canonicalExchange(rule.exchange ?? "");
50
83
  const session = (day: string) => getPublishedUsEquitySession(exchange, day);
51
- if (!session(new Date(now).toISOString().slice(0, 10))) throw new Error("Daily technical alerts need a published US listing calendar; delivery is unavailable for this listing.");
84
+ const unavailable = metricAvailabilityError(rule, now);
85
+ if (unavailable) throw new Error(unavailable);
52
86
  const latest = latestSettledClose(now, session);
53
87
  const ruleKey = JSON.stringify([rule.id, rule.armedAt ?? rule.createdAt, key, rule.condition, rule.targetPrice, m]);
54
88
  return daily.read(ruleKey, latest.day, now, async () => {
55
- const load = async () => {
89
+ const load = async (symbol: string, venue: string) => {
56
90
  // An explicit end at the settled session avoids today's forming-bar quote read.
57
91
  const end = new Date(`${latest.day}T23:59:59Z`);
58
92
  const start = new Date(Date.parse(latest.day) - 366 * 86_400_000);
59
- if (provider.getDetailedPriceHistoryWithMetadata) return (await provider.getDetailedPriceHistoryWithMetadata(rule.symbol, exchange, start, end, "1 day", { cacheMode: "current" })).points;
60
- if (provider.getDetailedPriceHistory) return provider.getDetailedPriceHistory(rule.symbol, exchange, start, end, "1 day", { cacheMode: "current" });
93
+ if (provider.getDetailedPriceHistoryWithMetadata) return (await provider.getDetailedPriceHistoryWithMetadata(symbol, venue, start, end, DAILY_BARS, { cacheMode: "current" })).points;
94
+ if (provider.getDetailedPriceHistory) return provider.getDetailedPriceHistory(symbol, venue, start, end, DAILY_BARS, { cacheMode: "current" });
61
95
  throw new Error("Completed daily history is unavailable from this connection.");
62
96
  };
63
- const request = history.get(key) ?? load();
64
- history.set(key, request);
65
- const points = await request;
66
- const bars = points.filter(point => !point.partial).map(point => ({ date: barSessionDay(point.date.getTime(), exchange), close: point.close }));
67
- return dailyMetricReading(rule, bars, now, session);
97
+ const completed = async (symbol: string, venue: string) => {
98
+ const listing = `${symbol}|${venue}`;
99
+ const request = history.get(listing) ?? load(symbol, venue);
100
+ history.set(listing, request);
101
+ // Final as `history` shows it: a close it prints without `partial` is one the rule may judge.
102
+ // Bars read back from the saved cache carry their dates as text.
103
+ return finalDailyBars(await request, { now, venue }).map(point => ({ date: barSessionDay(getPricePointTimestamp(point), venue), close: point.close }));
104
+ };
105
+ const bars = await completed(rule.symbol, exchange);
106
+ // RS ratios and RRG quadrants read the benchmark's own completed bars, never a quote.
107
+ const benchmark = isRelativeMetric(m) ? await completed(m.benchmark, canonicalExchange(m.benchmarkExchange ?? "")) : undefined;
108
+ return dailyMetricReading(rule, bars, now, session, benchmark);
68
109
  });
69
110
  };
70
111
  }