gloomberb 0.18.0 → 0.18.2

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 (365) hide show
  1. package/package.json +1 -1
  2. package/src/api-client/commodity-balances.ts +6 -0
  3. package/src/api-client/company-kpis.ts +9 -0
  4. package/src/api-client/crypto-markets.ts +9 -0
  5. package/src/api-client/data.ts +6 -5
  6. package/src/api-client/equity-screener.ts +2 -0
  7. package/src/api-client/market-movers.ts +7 -0
  8. package/src/api-client/types.ts +14 -0
  9. package/src/app/pane-runtime/plugin-bindings.ts +3 -1
  10. package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
  11. package/src/app/runtime/plugin-bindings.ts +6 -2
  12. package/src/brokers/logos.ts +66 -0
  13. package/src/brokers/profile-form.ts +3 -0
  14. package/src/brokers/remote-broker-adapter.ts +13 -3
  15. package/src/capabilities/index.ts +1 -1
  16. package/src/capabilities/service-capabilities.ts +7 -1
  17. package/src/cli/base-converter.ts +60 -18
  18. package/src/cli/commands/fred-series.ts +15 -5
  19. package/src/cli/commands/market.ts +290 -159
  20. package/src/cli/commands/news-search.ts +35 -8
  21. package/src/cli/commands/overview.ts +31 -6
  22. package/src/cli/commands/system.ts +35 -1
  23. package/src/cli/commands/ticker.ts +105 -52
  24. package/src/cli/currency-hints.ts +27 -0
  25. package/src/cli/fx-pair.ts +41 -4
  26. package/src/cli/glossary.ts +57 -4
  27. package/src/cli/helpers.ts +36 -3
  28. package/src/cli/index.ts +4 -1
  29. package/src/cli/known-gaps.ts +37 -1
  30. package/src/cli/listing-arg.ts +37 -2
  31. package/src/cli/pane-functions/catalog.ts +3 -1
  32. package/src/cli/pane-functions/discovery.ts +15 -1
  33. package/src/cli/pane-functions/dom.ts +9 -4
  34. package/src/cli/pane-functions/freshness.ts +4 -4
  35. package/src/cli/pane-functions/function-help.ts +31 -7
  36. package/src/cli/pane-functions/headless.ts +6 -5
  37. package/src/cli/pane-functions/index.ts +3 -2
  38. package/src/cli/pane-functions/lock-note.ts +7 -4
  39. package/src/cli/pane-functions/resolver.ts +2 -1
  40. package/src/cli/quote-answer.ts +179 -0
  41. package/src/cli/series-search.ts +2 -1
  42. package/src/cli/symbol-list.ts +51 -16
  43. package/src/cli/types.ts +4 -0
  44. package/src/components/chart/composite/composite-chart.tsx +22 -7
  45. package/src/components/chart/composite/format.ts +13 -1
  46. package/src/components/chart/composite/level-labels.ts +30 -0
  47. package/src/components/chart/composite/levels.ts +17 -6
  48. package/src/components/chart/composite/panel-surface.tsx +64 -16
  49. package/src/components/chart/composite/price-axis-labels.tsx +2 -1
  50. package/src/components/chart/composite/types.ts +4 -0
  51. package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
  52. package/src/components/command-bar/workflow/collection-actions.ts +4 -0
  53. package/src/components/index.ts +2 -0
  54. package/src/components/layout/pane/content.tsx +16 -14
  55. package/src/components/layout/pane/footer/index.tsx +22 -3
  56. package/src/components/layout/pane/footer/model.ts +99 -4
  57. package/src/components/layout/pane/header-tabs.tsx +15 -1
  58. package/src/components/onboarding/onboarding-frame.tsx +9 -11
  59. package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
  60. package/src/components/onboarding/wizard-model.ts +10 -3
  61. package/src/components/onboarding/wizard-positions.ts +8 -0
  62. package/src/components/ui/button.tsx +11 -4
  63. package/src/components/ui/choice-dialog.tsx +8 -5
  64. package/src/components/ui/display.tsx +11 -6
  65. package/src/components/ui/fields.tsx +100 -20
  66. package/src/components/ui/icon.tsx +6 -1
  67. package/src/components/ui/list-row-lines.tsx +87 -0
  68. package/src/components/ui/list-view.tsx +14 -0
  69. package/src/components/ui/logo-mark.tsx +64 -0
  70. package/src/components/ui/number-step.ts +54 -0
  71. package/src/components/ui/select-button.tsx +5 -4
  72. package/src/components/ui/select-field.tsx +3 -3
  73. package/src/components/ui/status.tsx +17 -3
  74. package/src/components/ui/tabs.tsx +9 -1
  75. package/src/components/ui/toggle.tsx +17 -3
  76. package/src/core/state/app/instrument.ts +8 -3
  77. package/src/data/config/store/normalize.ts +4 -1
  78. package/src/data/plugin-cache.ts +32 -6
  79. package/src/docs/snapshot.json +57 -22
  80. package/src/i18n/es.ts +9 -3
  81. package/src/i18n/ja.ts +9 -3
  82. package/src/i18n/ko.ts +9 -3
  83. package/src/i18n/zh-cn.ts +10 -4
  84. package/src/i18n/zh-tw.ts +10 -4
  85. package/src/market-data/commodity-names.ts +80 -0
  86. package/src/market-data/coordinator/auxiliary.ts +3 -1
  87. package/src/market-data/coordinator/chart.ts +3 -1
  88. package/src/market-data/futures-board.ts +38 -1
  89. package/src/market-data/futures-contract.ts +91 -0
  90. package/src/market-data/fx-closes.ts +78 -1
  91. package/src/market-data/market/change-basis.ts +219 -0
  92. package/src/market-data/market/format.ts +28 -12
  93. package/src/market-data/market/freshness.ts +38 -0
  94. package/src/market-data/market/status.ts +55 -9
  95. package/src/market-data/market/year-to-date.ts +46 -0
  96. package/src/market-data/quotes/contributions.ts +12 -0
  97. package/src/market-data/quotes/delisting.ts +34 -0
  98. package/src/market-data/quotes/live-valuation.ts +23 -0
  99. package/src/market-data/quotes/resolution.ts +8 -2
  100. package/src/market-data/tax-equivalent.ts +10 -2
  101. package/src/notifications/app-notifier.ts +2 -2
  102. package/src/notifications/notification-log.ts +8 -6
  103. package/src/plugins/builtin/alerts/cli.ts +26 -18
  104. package/src/plugins/builtin/alerts/headless.ts +1 -1
  105. package/src/plugins/builtin/alerts/index.tsx +13 -4
  106. package/src/plugins/builtin/analytics/index.tsx +1 -1
  107. package/src/plugins/builtin/analytics/risk-client.ts +44 -11
  108. package/src/plugins/builtin/analytics/risk-fx.ts +61 -0
  109. package/src/plugins/builtin/analytics/risk-headless.ts +10 -1
  110. package/src/plugins/builtin/analytics/risk-model.ts +79 -16
  111. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  112. package/src/plugins/builtin/analytics/risk-test-data.ts +3 -0
  113. package/src/plugins/builtin/attention/trending.tsx +8 -6
  114. package/src/plugins/builtin/backtest/client.ts +5 -2
  115. package/src/plugins/builtin/backtest/window.ts +16 -3
  116. package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
  117. package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
  118. package/src/plugins/builtin/broker-manager/index.tsx +20 -9
  119. package/src/plugins/builtin/broker-manager/model.ts +5 -0
  120. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
  121. package/src/plugins/builtin/broker-manager/table.ts +5 -1
  122. package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
  123. package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
  124. package/src/plugins/builtin/broker-trading/command.ts +103 -0
  125. package/src/plugins/builtin/broker-trading/controller.ts +688 -0
  126. package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
  127. package/src/plugins/builtin/broker-trading/headless.ts +76 -0
  128. package/src/plugins/builtin/broker-trading/index.tsx +77 -0
  129. package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
  130. package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
  131. package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
  132. package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
  133. package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
  134. package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
  135. package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
  136. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
  137. package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
  138. package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
  139. package/src/plugins/builtin/buildout/format.ts +1 -0
  140. package/src/plugins/builtin/buildout/model/index.ts +16 -7
  141. package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
  142. package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
  143. package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
  144. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  145. package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
  146. package/src/plugins/builtin/chart-composer/headless.ts +2 -1
  147. package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
  148. package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
  149. package/src/plugins/builtin/chart-composer/presets.ts +2 -2
  150. package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
  151. package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
  152. package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
  153. package/src/plugins/builtin/chat/index.tsx +1 -1
  154. package/src/plugins/builtin/chat/status-widget.tsx +1 -1
  155. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
  156. package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
  157. package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -3
  158. package/src/plugins/builtin/cloud/team/pane.tsx +30 -9
  159. package/src/plugins/builtin/commodity-balances/client.ts +6 -4
  160. package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
  161. package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
  162. package/src/plugins/builtin/commodity-balances/model.ts +22 -0
  163. package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
  164. package/src/plugins/builtin/company-kpis/client.ts +3 -2
  165. package/src/plugins/builtin/company-kpis/headless.ts +5 -7
  166. package/src/plugins/builtin/company-kpis/model.ts +85 -2
  167. package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
  168. package/src/plugins/builtin/composite-plugins.ts +3 -2
  169. package/src/plugins/builtin/cot/headless.ts +1 -1
  170. package/src/plugins/builtin/cot/index.tsx +1 -1
  171. package/src/plugins/builtin/cot/model.ts +35 -12
  172. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  173. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
  174. package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
  175. package/src/plugins/builtin/crypto-board/client.ts +33 -2
  176. package/src/plugins/builtin/crypto-board/headless.ts +5 -2
  177. package/src/plugins/builtin/crypto-board/model.ts +28 -8
  178. package/src/plugins/builtin/custom-view/loader.ts +3 -2
  179. package/src/plugins/builtin/custom-view/pane.tsx +5 -4
  180. package/src/plugins/builtin/custom-view/view-spec.ts +1 -0
  181. package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
  182. package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
  183. package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
  184. package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
  185. package/src/plugins/builtin/econ/calendar-model.ts +38 -2
  186. package/src/plugins/builtin/econ/fred-catalog.ts +6 -2
  187. package/src/plugins/builtin/econ/fred-series-map.ts +12 -1
  188. package/src/plugins/builtin/econ/index.tsx +31 -4
  189. package/src/plugins/builtin/econ/print-watch.ts +279 -0
  190. package/src/plugins/builtin/equity-screener/headless.ts +20 -5
  191. package/src/plugins/builtin/equity-screener/model.ts +40 -2
  192. package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
  193. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
  194. package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
  195. package/src/plugins/builtin/executives/data.ts +24 -8
  196. package/src/plugins/builtin/executives/index.tsx +2 -18
  197. package/src/plugins/builtin/executives/pane.tsx +34 -10
  198. package/src/plugins/builtin/exposure/client.ts +4 -1
  199. package/src/plugins/builtin/exposure/editor.tsx +3 -3
  200. package/src/plugins/builtin/exposure/evidence.ts +4 -1
  201. package/src/plugins/builtin/exposure/holdings.ts +3 -1
  202. package/src/plugins/builtin/exposure/pane.tsx +37 -16
  203. package/src/plugins/builtin/filing-events/index.tsx +9 -3
  204. package/src/plugins/builtin/futures/table.tsx +6 -3
  205. package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
  206. package/src/plugins/builtin/insider/base-values.ts +72 -0
  207. package/src/plugins/builtin/insider/headless.ts +29 -4
  208. package/src/plugins/builtin/jobs/headless.ts +2 -2
  209. package/src/plugins/builtin/jobs/lock-note.ts +14 -3
  210. package/src/plugins/builtin/market-halts/model.ts +31 -6
  211. package/src/plugins/builtin/market-halts/pane.tsx +3 -0
  212. package/src/plugins/builtin/market-movers/client.ts +2 -2
  213. package/src/plugins/builtin/market-movers/index.tsx +0 -3
  214. package/src/plugins/builtin/market-movers/session-body.tsx +27 -11
  215. package/src/plugins/builtin/market-movers/session.ts +25 -10
  216. package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
  217. package/src/plugins/builtin/members/pane.tsx +8 -3
  218. package/src/plugins/builtin/news/wire/feed-config.ts +33 -13
  219. package/src/plugins/builtin/news/wire/index.tsx +7 -2
  220. package/src/plugins/builtin/news/wire/letters/client.ts +1 -0
  221. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +6 -3
  222. package/src/plugins/builtin/news/wire/news/detail-view.tsx +2 -1
  223. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
  224. package/src/plugins/builtin/news/wire/news/table.tsx +2 -1
  225. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
  226. package/src/plugins/builtin/news/wire/rss/feed-xml.ts +6 -1
  227. package/src/plugins/builtin/news/wire/rss/managed-feed.ts +1 -0
  228. package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
  229. package/src/plugins/builtin/notes/cli.ts +33 -8
  230. package/src/plugins/builtin/notes/export.ts +49 -3
  231. package/src/plugins/builtin/notes/files.ts +14 -3
  232. package/src/plugins/builtin/notes/index.tsx +3 -3
  233. package/src/plugins/builtin/notes/quick-notes-pane.tsx +45 -10
  234. package/src/plugins/builtin/notes/store.ts +13 -3
  235. package/src/plugins/builtin/options/footer.ts +25 -20
  236. package/src/plugins/builtin/options/live-quotes.ts +33 -18
  237. package/src/plugins/builtin/options/view.tsx +77 -22
  238. package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
  239. package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
  240. package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
  241. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -7
  242. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +166 -15
  243. package/src/plugins/builtin/portfolio-list/cli/render.ts +69 -15
  244. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +2 -2
  245. package/src/plugins/builtin/portfolio-list/headless.ts +3 -1
  246. package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
  247. package/src/plugins/builtin/portfolio-list/pane/index.tsx +44 -17
  248. package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
  249. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -1
  250. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +42 -7
  251. package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
  252. package/src/plugins/builtin/primary-dealers/client.ts +137 -62
  253. package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
  254. package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
  255. package/src/plugins/builtin/primary-dealers/model.ts +179 -129
  256. package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
  257. package/src/plugins/builtin/research/analyst-model.ts +5 -1
  258. package/src/plugins/builtin/risk-factors/index.tsx +12 -3
  259. package/src/plugins/builtin/scanner/feed.ts +6 -1
  260. package/src/plugins/builtin/scanner/headless.ts +1 -1
  261. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  262. package/src/plugins/builtin/series-inventory.ts +1 -0
  263. package/src/plugins/builtin/shared/broker-reconnect.ts +105 -0
  264. package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
  265. package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
  266. package/src/plugins/builtin/shared/free-routes.ts +24 -0
  267. package/src/plugins/builtin/shared/headless-options.ts +11 -0
  268. package/src/plugins/builtin/shared/pro-feature-wall.tsx +9 -4
  269. package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
  270. package/src/plugins/builtin/shared/research-trading.tsx +62 -0
  271. package/src/plugins/builtin/social-mentions/headless.ts +1 -1
  272. package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
  273. package/src/plugins/builtin/themes/headless.ts +8 -2
  274. package/src/plugins/builtin/themes/model.ts +43 -10
  275. package/src/plugins/builtin/themes/pane.tsx +8 -6
  276. package/src/plugins/builtin/thirteenf/api.ts +4 -1
  277. package/src/plugins/builtin/thirteenf/data.ts +5 -1
  278. package/src/plugins/builtin/thirteenf/headless.ts +3 -1
  279. package/src/plugins/builtin/thirteenf/pane.tsx +8 -3
  280. package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
  281. package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
  282. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
  283. package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
  284. package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
  285. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
  286. package/src/plugins/builtin/time-sales/pane.tsx +2 -2
  287. package/src/plugins/builtin/trace-bonds/client.ts +14 -3
  288. package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
  289. package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
  290. package/src/plugins/builtin-editorial.ts +1 -1
  291. package/src/plugins/compat.ts +3 -0
  292. package/src/plugins/external-runtime.ts +5 -0
  293. package/src/plugins/loader.ts +6 -0
  294. package/src/plugins/plugin-icon.ts +61 -0
  295. package/src/plugins/registry/host-actions.ts +2 -1
  296. package/src/plugins/registry/index.ts +9 -1
  297. package/src/plugins/runtime/context.tsx +5 -0
  298. package/src/plugins/runtime/index.tsx +3 -0
  299. package/src/portfolio/column-values.ts +13 -4
  300. package/src/portfolio/cost-fx.ts +61 -0
  301. package/src/portfolio/mutations.ts +16 -2
  302. package/src/portfolio/position-metrics.ts +45 -4
  303. package/src/portfolio/position-quantity.ts +43 -0
  304. package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
  305. package/src/public/broker.ts +8 -0
  306. package/src/public/market-data.ts +5 -0
  307. package/src/renderers/dom/desktop/control-styles.ts +7 -1
  308. package/src/renderers/dom/desktop/controls.tsx +39 -7
  309. package/src/renderers/dom/desktop/icons.tsx +14 -0
  310. package/src/renderers/dom/desktop/list-view.tsx +51 -19
  311. package/src/renderers/dom/desktop/menu.tsx +10 -1
  312. package/src/renderers/dom/desktop/select-field.tsx +8 -6
  313. package/src/renderers/dom/host/style.ts +6 -1
  314. package/src/renderers/dom/host/tabs.tsx +6 -0
  315. package/src/renderers/dom/styles.css +12 -5
  316. package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
  317. package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
  318. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  319. package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
  320. package/src/renderers/electrobun/view/external-plugins.ts +1 -0
  321. package/src/series/validate.ts +2 -2
  322. package/src/sources/gloomberb-cloud/fred-series.ts +27 -2
  323. package/src/sources/gloomberb-cloud/index.ts +50 -17
  324. package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
  325. package/src/sources/history-result.ts +3 -0
  326. package/src/sources/provider-errors.ts +3 -1
  327. package/src/sources/provider-router/history.ts +49 -6
  328. package/src/sources/provider-router/index.ts +2 -1
  329. package/src/state/app/activity.ts +13 -0
  330. package/src/sync/controller.ts +4 -0
  331. package/src/sync/core-contributors.ts +63 -11
  332. package/src/sync/local-only.ts +121 -0
  333. package/src/sync/react.ts +6 -0
  334. package/src/sync/types.ts +3 -0
  335. package/src/team/collections.ts +2 -0
  336. package/src/tickers/listing-fate.ts +27 -8
  337. package/src/tickers/metadata.ts +10 -0
  338. package/src/tickers/search/index.ts +24 -3
  339. package/src/time-series/price-comparison.ts +35 -0
  340. package/src/time-series/resolve.ts +52 -11
  341. package/src/time-series/spec.ts +1 -1
  342. package/src/time-series/types.ts +5 -0
  343. package/src/types/broker.ts +56 -1
  344. package/src/types/config.ts +6 -0
  345. package/src/types/data-provider.ts +5 -1
  346. package/src/types/financials.ts +23 -0
  347. package/src/types/headless.ts +12 -2
  348. package/src/types/instrument.ts +4 -0
  349. package/src/types/plugin.ts +24 -6
  350. package/src/types/price-history.ts +6 -1
  351. package/src/types/ticker.ts +9 -0
  352. package/src/types/trading.ts +67 -0
  353. package/src/ui/host.tsx +4 -0
  354. package/src/utils/base-currency.ts +108 -0
  355. package/src/utils/currency-names.ts +128 -0
  356. package/src/utils/currency-pair.ts +2 -1
  357. package/src/utils/currency-units.ts +41 -1
  358. package/src/utils/dom-screenshot.ts +22 -9
  359. package/src/utils/exchange-notation.generated.ts +360 -0
  360. package/src/utils/exchanges.ts +15 -322
  361. package/src/utils/format.ts +31 -1
  362. package/src/utils/instrument-identity.ts +38 -1
  363. package/src/utils/options.ts +46 -1
  364. package/src/utils/price-history.ts +4 -0
  365. package/src/utils/reported-money.ts +8 -4
@@ -4,6 +4,7 @@ import { isFiniteNumber } from "../../utils/guards";
4
4
  import { canonicalExchange, isKnownExchangeCode } from "../../utils/exchanges";
5
5
  import { quoteTradingDay } from "../quotes/day-range";
6
6
  import { isQuoteBeforeLatestSession } from "../quotes/freshness";
7
+ import { delistingLabel, delistingStateLabel } from "../quotes/delisting";
7
8
  import { preMarketOpenOn, regularSessionCloseOn } from "./freshness";
8
9
 
9
10
  const CLOSED_CHANGE_MUTING_RATIO = 0.55;
@@ -66,6 +67,30 @@ export function marketStateLabel(state: MarketState): string {
66
67
  const STALE_SESSION_LABEL = "STALE";
67
68
 
68
69
  const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
70
+ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] as const;
71
+
72
+ /** The books an extended session's bid and ask belong to, as `ticker` names them. */
73
+ const EXTENDED_BOOK_LABELS: Partial<Record<MarketState, string>> = { PRE: "pre-market book", POST: "after-hours book" };
74
+
75
+ /**
76
+ * What a quote's bid and ask are when the regular session is not on: the
77
+ * pre-market or after-hours book, or a closed market's last quote with the
78
+ * day it is from (ITA's $202.00 against $208.74 around a $206.65 close is no
79
+ * open book). Null in the regular session or without a known state, when the
80
+ * book is the live one. `ticker` prints it after Bid / Ask; the overview's
81
+ * header already names the session, so its quote book does not repeat it.
82
+ */
83
+ export function quoteBookSessionLabel(quote: Quote): string | null {
84
+ const state = quote.marketState;
85
+ if (!state || state === "REGULAR") return null;
86
+ const extended = EXTENDED_BOOK_LABELS[state];
87
+ if (extended) return extended;
88
+ const day = quoteTradingDay(quote);
89
+ const date = day ? new Date(`${day}T00:00:00Z`) : null;
90
+ return date && Number.isFinite(date.getTime())
91
+ ? `last quote, ${WEEKDAYS[date.getUTCDay()]} ${date.getUTCDate()} ${MONTHS[date.getUTCMonth()]}`
92
+ : "last quote";
93
+ }
69
94
 
70
95
  /** `Thu close`: the weekday of the session whose close a quote still stands at. */
71
96
  function closeLabel(date: string): string {
@@ -75,9 +100,12 @@ function closeLabel(date: string): string {
75
100
  /**
76
101
  * A quote's market state as a status cell names it (marketStateLabel), STALE
77
102
  * for a print older than its venue's latest session, and the close it stands
78
- * at (`Thu close`) in a pre-market nothing has traded in yet.
103
+ * at (`Thu close`) in a pre-market nothing has traded in yet. A listing that
104
+ * no longer trades says so with its last session ("Delisted Oct 27, 2022"),
105
+ * whatever state its last print carries.
79
106
  */
80
107
  export function quoteMarketStateLabel(quote: Quote | null | undefined, now = Date.now()): string | null {
108
+ if (quote?.delisting) return delistingLabel(quote.delisting);
81
109
  if (!quote?.marketState) return null;
82
110
  if (isQuoteBeforeLatestSession(quote, now)) return STALE_SESSION_LABEL;
83
111
  const awaiting = preMarketCloseAwaitingPrint(quote);
@@ -160,8 +188,9 @@ function overnightSessionDisplay(quote: Quote): ExtendedSessionDisplay | null {
160
188
  }
161
189
 
162
190
  /**
163
- * A quote's session as a status cell names it: STALE when its last print is
164
- * older than its venue's latest completed session (isQuoteBeforeLatestSession),
191
+ * A quote's session as a status cell names it: "Delisted Oct 27, 2022" (or
192
+ * "Last traded ...") for a listing that no longer trades, STALE when its last
193
+ * print is older than its venue's latest completed session (isQuoteBeforeLatestSession),
165
194
  * the overnight session while the quote is priced by it, the close it stands
166
195
  * at (`Thu close`) in a pre-market nothing has traded in yet, else its market
167
196
  * state (marketStateLabel). The exchange stays closed overnight, so the state
@@ -174,13 +203,15 @@ export function quoteSessionLabel(quote: Quote | null | undefined, now = Date.no
174
203
  /**
175
204
  * quoteSessionLabel as an export keeps it, one of a fixed set: the market
176
205
  * state (`PRE-MKT`) in a pre-market nothing has traded in yet, where the
177
- * status cell names the close (`Thu close`).
206
+ * status cell names the close (`Thu close`), and DELISTED or INACTIVE for a
207
+ * listing that no longer trades, its date being the export's own field.
178
208
  */
179
209
  export function quoteSessionStateLabel(quote: Quote | null | undefined, now = Date.now()): string | null {
180
210
  return sessionLabel(quote, now, false);
181
211
  }
182
212
 
183
213
  function sessionLabel(quote: Quote | null | undefined, now: number, standingClose: boolean): string | null {
214
+ if (quote?.delisting) return standingClose ? delistingLabel(quote.delisting) : delistingStateLabel(quote.delisting);
184
215
  if (!quote?.marketState) return null;
185
216
  if (isQuoteBeforeLatestSession(quote, now)) return STALE_SESSION_LABEL;
186
217
  if (overnightSessionDisplay(quote)) return "OVERNIGHT";
@@ -189,11 +220,13 @@ function sessionLabel(quote: Quote | null | undefined, now: number, standingClos
189
220
  }
190
221
 
191
222
  /**
192
- * The color of quoteSessionLabel: STALE in the warning color every stale
193
- * mark uses, an extended session's while the overnight session prices the
194
- * quote, a closed session's for the close a pre-market still stands at.
223
+ * The color of quoteSessionLabel: STALE, and a listing that no longer trades,
224
+ * in the warning color every stale mark uses, an extended session's while the
225
+ * overnight session prices the quote, a closed session's for the close a
226
+ * pre-market still stands at.
195
227
  */
196
228
  export function quoteSessionColor(quote: Quote | null | undefined, palette: ThemeColors = colors, now = Date.now()): string {
229
+ if (quote?.delisting) return palette.warning;
197
230
  if (!quote?.marketState) return palette.textDim;
198
231
  if (isQuoteBeforeLatestSession(quote, now)) return palette.warning;
199
232
  if (overnightSessionDisplay(quote)) return marketStateColor("POST", palette);
@@ -206,7 +239,7 @@ export function quoteSessionColor(quote: Quote | null | undefined, palette: Them
206
239
  * with no known session rather than an open market.
207
240
  */
208
241
  export function quoteDotMarketState(quote: Quote | null | undefined, now = Date.now()): MarketState | undefined {
209
- return quote && !isQuoteBeforeLatestSession(quote, now) ? quote.marketState : undefined;
242
+ return quote && !quote.delisting && !isQuoteBeforeLatestSession(quote, now) ? quote.marketState : undefined;
210
243
  }
211
244
 
212
245
  /** Closed prices are final snapshots, so they should not look live or directional. */
@@ -378,6 +411,8 @@ export function quoteHeadlineTime(quote: Quote): number {
378
411
  */
379
412
  export function getRegularSessionDisplay(quote: Quote | null | undefined): ActiveQuoteDisplay | null {
380
413
  if (!quote) return null;
414
+ // A listing that no longer trades: its last session's close, which moves no more.
415
+ if (quote.delisting) return { price: quote.price };
381
416
  const session = completedRegularSession(quote);
382
417
  if (session == null) return { price: quote.price, change: quote.change, changePercent: quote.changePercent };
383
418
  const { close, reported } = session;
@@ -390,6 +425,17 @@ export function getRegularSessionDisplay(quote: Quote | null | undefined): Activ
390
425
  return { price: close, change, changePercent: (change / reference) * 100 };
391
426
  }
392
427
 
428
+ /**
429
+ * The exchange-local session getRegularSessionDisplay's move belongs to: the
430
+ * completed session's while the headline still shows its close (a pre-market
431
+ * quote before anything trades, whose own session is the day ahead), the
432
+ * quote's declared session otherwise. Null when the quote names neither.
433
+ */
434
+ export function regularSessionDisplayDate(quote: Quote | null | undefined): string | null {
435
+ if (!quote) return null;
436
+ return completedRegularSession(quote)?.date ?? quote.changeSessionDate ?? null;
437
+ }
438
+
393
439
  /**
394
440
  * getRegularSessionDisplay once the regular session is over (and in the
395
441
  * pre-market, while the quote carries the last session's close and move), null
@@ -429,7 +475,7 @@ export function getRegularSessionBarClose(quote: Quote | null | undefined): { pr
429
475
  * when there is no extended print to show.
430
476
  */
431
477
  export function getExtendedSessionDisplay(quote: Quote | null | undefined): ExtendedSessionDisplay | null {
432
- if (!quote) return null;
478
+ if (!quote || quote.delisting) return null;
433
479
  const overnight = overnightSessionDisplay(quote);
434
480
  if (overnight) return overnight;
435
481
  const state = quote.marketState;
@@ -0,0 +1,46 @@
1
+ import { barLastSession } from "./bar-period";
2
+
3
+ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] as const;
4
+
5
+ /**
6
+ * The session a year-to-date figure dated `day` is measured from at `venue`:
7
+ * the venue's last close of the year before, `YYYY-MM-DD`. 2026 runs from
8
+ * Wed 31 Dec 2025, 2023 from Fri 30 Dec 2022 (31 Dec was a Saturday). A venue
9
+ * whose sessions are unknown reads December's last weekday.
10
+ */
11
+ export function yearToDateBaseDay(day: string, venue: string | undefined, now = Date.now()): string | null {
12
+ if (!/^\d{4}-\d{2}-\d{2}/.test(day)) return null;
13
+ return barLastSession(`${Number(day.slice(0, 4)) - 1}-12-01`, "month", venue, now);
14
+ }
15
+
16
+ /** That close as a column header names it, `31 Dec`. */
17
+ export function yearToDateBaseLabel(day: string, venue: string | undefined, now = Date.now()): string {
18
+ const base = yearToDateBaseDay(day, venue, now) ?? `${Number(day.slice(0, 4)) - 1}-12-31`;
19
+ const date = new Date(`${base}T00:00:00Z`);
20
+ return Number.isFinite(date.getTime()) ? `${date.getUTCDate()} ${MONTHS[date.getUTCMonth()]}` : "31 Dec";
21
+ }
22
+
23
+ /**
24
+ * Where a year-to-date run of bars starts: the last bar whose close is from on
25
+ * or before 31 Dec of the year before the newest bar, the close the year is
26
+ * measured from, never the year's first session. A weekly bar (dated by its
27
+ * week's start) closes as its week ends and a monthly one as its month does,
28
+ * so the week of Mon 29 Dec 2025, which closes on 2 Jan, is after it. 0 when
29
+ * every bar is from the year itself (a listing that started trading in it);
30
+ * -1 without bars.
31
+ */
32
+ export function yearToDateStartIndex(dates: readonly string[], interval?: string | null): number {
33
+ const last = dates.at(-1);
34
+ if (!last) return -1;
35
+ const yearEnd = `${Number(last.slice(0, 4)) - 1}-12-31`;
36
+ const closes = (date: string) => {
37
+ const day = Date.parse(`${date.slice(0, 10)}T00:00:00Z`);
38
+ if (interval === "1wk") return new Date(day + 6 * 86_400_000).toISOString().slice(0, 10);
39
+ if (interval === "1mo") return new Date(Date.UTC(new Date(day).getUTCFullYear(), new Date(day).getUTCMonth() + 1, 0)).toISOString().slice(0, 10);
40
+ return date.slice(0, 10);
41
+ };
42
+ for (let index = dates.length - 1; index >= 0; index -= 1) {
43
+ if (closes(dates[index]!) <= yearEnd) return index;
44
+ }
45
+ return 0;
46
+ }
@@ -212,12 +212,24 @@ export function mergeQuoteContribution(
212
212
  overnightPriceKind: next.overnightPriceKind,
213
213
  // The board this observation was read on; one read on the listing's own line names none.
214
214
  tradingExchangeName: next.tradingExchangeName,
215
+ // Whether the listing no longer trades is said with each observation: a symbol that trades again drops it.
216
+ delisting: next.delisting,
215
217
  regularClose: next.regularClose,
216
218
  regularCloseSessionDate: next.regularClose != null ? next.regularCloseSessionDate : undefined,
217
219
  regularChange: next.regularClose != null ? next.regularChange : undefined,
218
220
  regularChangePercent: next.regularClose != null ? next.regularChangePercent : undefined,
219
221
  };
220
222
 
223
+ // What this observation's move is from, when not its previous close (a coin's
224
+ // 24 hours, Linear: GLO-1089), is said with each one: a quote back on its
225
+ // previous close drops an earlier "24h", and one on 24 hours drops the
226
+ // earlier close its move no longer runs from.
227
+ if (next.changeBasis == null) {
228
+ delete merged.changeBasis;
229
+ } else if (next.previousClose === undefined) {
230
+ delete merged.previousClose;
231
+ }
232
+
221
233
  if (isOtherTradingDay(current, next)) {
222
234
  const record = merged as unknown as Record<string, unknown>;
223
235
  for (const field of TRADING_DAY_FIELDS) {
@@ -0,0 +1,34 @@
1
+ import type { ListingDelisting } from "../../types/financials";
2
+ import { tf } from "../../i18n";
3
+ import { formatShortDate } from "../../utils/datetime-format";
4
+
5
+ const DATE = /^\d{4}-\d{2}-\d{2}$/;
6
+
7
+ /**
8
+ * A listing's delisting as Gloom Cloud sends it, or undefined for anything
9
+ * else: the service's answer is untrusted, and a malformed one must not mark
10
+ * a listing that trades.
11
+ */
12
+ export function parseListingDelisting(value: unknown): ListingDelisting | undefined {
13
+ if (!value || typeof value !== "object") return undefined;
14
+ const { status, lastTradeDate, historyStart } = value as Record<string, unknown>;
15
+ if (status !== "delisted" && status !== "inactive") return undefined;
16
+ if (typeof lastTradeDate !== "string" || !DATE.test(lastTradeDate)) return undefined;
17
+ if (typeof historyStart !== "string" || !DATE.test(historyStart) || historyStart > lastTradeDate) return undefined;
18
+ return { status, lastTradeDate, historyStart };
19
+ }
20
+
21
+ /**
22
+ * What a listing that no longer trades reads as beside its symbol, always with
23
+ * its last session: "Delisted Oct 27, 2022" when the exchange's removal is on
24
+ * record, "Last traded Apr 28, 2023" when only the last trade is known.
25
+ */
26
+ export function delistingLabel(delisting: ListingDelisting): string {
27
+ const date = formatShortDate(delisting.lastTradeDate, { utc: true });
28
+ return delisting.status === "delisted" ? tf("Delisted {date}", { date }) : tf("Last traded {date}", { date });
29
+ }
30
+
31
+ /** The state alone, for a cell that names a session in a word (OPEN, CLOSED): DELISTED or INACTIVE. */
32
+ export function delistingStateLabel(delisting: ListingDelisting): "DELISTED" | "INACTIVE" {
33
+ return delisting.status === "delisted" ? "DELISTED" : "INACTIVE";
34
+ }
@@ -189,6 +189,29 @@ export function liveFiftyTwoWeekRange(
189
189
  return high > low ? { low, high } : null;
190
190
  }
191
191
 
192
+ /**
193
+ * The currency an analyst's targets are written in: the one the target or its
194
+ * research names, whatever the quote's (a target in pounds under a quote in
195
+ * pence stays in pounds). When neither names one, the quote's, but its major
196
+ * unit when the quote is in a minor one (GBp, USX) and the target's reference
197
+ * price, or the target itself, is a hundred times under the quote's price.
198
+ */
199
+ export function analystTargetDisplayCurrency(
200
+ analyst: AnalystResearchData | null | undefined,
201
+ quoteCurrency: string,
202
+ currentPrice: number | null | undefined,
203
+ ): string {
204
+ const named = analyst?.priceTarget?.currency?.trim() || analyst?.currency?.trim();
205
+ if (named) return named;
206
+ const unit = resolveCurrencyUnit(quoteCurrency);
207
+ if (unit.divisor === 1 || !positive(currentPrice)) return quoteCurrency;
208
+ const target = analyst?.priceTarget;
209
+ const sample = [target?.current, target?.average, target?.median].find(positive);
210
+ return sample != null && sample < currentPrice
211
+ && hasLikelyQuoteUnitMismatch({ currency: quoteCurrency, price: sample }, { currency: quoteCurrency, price: currentPrice })
212
+ ? unit.currency : quoteCurrency;
213
+ }
214
+
192
215
  /**
193
216
  * The price an analyst target is compared with. The current price when the
194
217
  * target is quoted in the listing's currency; otherwise the research snapshot's
@@ -213,7 +213,9 @@ function assignDailyChangeFields(
213
213
  const referenceProvider = buildDailyReferenceCandidates(candidates, priceProvider)[0];
214
214
  if (!referenceProvider) {
215
215
  pickField(target, provenance, "previousClose", candidates);
216
- pickField(target, provenance, "change", candidates);
216
+ // A move not from a previous close says what it is from (a coin's 24 hours,
217
+ // Linear: GLO-1089), and that is said by the source whose move it is.
218
+ assignField(target, provenance, "changeBasis", pickField(target, provenance, "change", candidates));
217
219
  pickField(target, provenance, "changePercent", candidates);
218
220
  return;
219
221
  }
@@ -222,7 +224,7 @@ function assignDailyChangeFields(
222
224
  const price = target.price;
223
225
  const previousClose = target.previousClose;
224
226
  if (!finitePositiveNumber(price) || !finitePositiveNumber(previousClose)) {
225
- pickField(target, provenance, "change", candidates);
227
+ assignField(target, provenance, "changeBasis", pickField(target, provenance, "change", candidates));
226
228
  pickField(target, provenance, "changePercent", candidates);
227
229
  return;
228
230
  }
@@ -495,6 +497,8 @@ export function resolveCanonicalQuote(
495
497
  currency: String(resolved.currency ?? priceProvider?.currency ?? contributions[0]!.currency ?? ""),
496
498
  change: resolved.change ?? Number.NaN,
497
499
  changePercent: resolved.changePercent ?? Number.NaN,
500
+ // What that move is from when it is not previousClose; absent when it is.
501
+ ...(resolved.changeBasis ? { changeBasis: resolved.changeBasis } : {}),
498
502
  previousClose: resolved.previousClose as Quote["previousClose"],
499
503
  regularClose: priceProvider?.regularClose,
500
504
  regularCloseSessionDate: priceProvider?.regularClose != null ? priceProvider.regularCloseSessionDate : undefined,
@@ -534,6 +538,8 @@ export function resolveCanonicalQuote(
534
538
  overnightPriceKind: priceProvider?.overnightPriceKind,
535
539
  // An index's regular sessions come with the print they date.
536
540
  indexSession: priceProvider?.indexSession,
541
+ // A listing that no longer trades: the price is its last trade.
542
+ delisting: priceProvider?.delisting,
537
543
  bid: resolved.bid as Quote["bid"],
538
544
  ask: resolved.ask as Quote["ask"],
539
545
  bidSize: resolved.bidSize as Quote["bidSize"],
@@ -161,6 +161,8 @@ export function taxEquivalentYield(yieldPercent: number, combinedRatePercent: nu
161
161
  export interface TaxEquivalentAnalysis {
162
162
  symbol: string;
163
163
  rates: TaxRates;
164
+ /** A failed prerequisite check, distinct from a confirmed inapplicable fund. */
165
+ checkError?: string;
164
166
  /** Why there is no tax-equivalent yield: the fund is not a muni fund, or nothing classes it. */
165
167
  notApplicable?: string;
166
168
  /** The rate the yield is grossed up by: federal, plus the state's for one of its own funds. */
@@ -187,16 +189,18 @@ function formatRate(value: number): string {
187
189
  * fund its sponsor classes as municipal has one; any other says why not.
188
190
  */
189
191
  export function analyzeTaxEquivalent({
190
- symbol, bond, rates, distribution, treasury,
192
+ symbol, bond, rates, distribution, treasury, checkError,
191
193
  }: {
192
194
  symbol: string;
193
195
  bond: FundBondFacts | null | undefined;
194
196
  rates: TaxRates;
197
+ checkError?: string;
195
198
  /** The distribution yield in percent and its label, for a fund without a SEC yield. */
196
199
  distribution?: YieldBasis | null;
197
200
  /** The Treasury curve's points, or null when it could not be read. */
198
201
  treasury?: readonly TreasuryTenor[] | null;
199
202
  }): TaxEquivalentAnalysis {
203
+ if (checkError) return { symbol, rates, checkError };
200
204
  if (!bond) {
201
205
  return { symbol, rates, notApplicable: `no tax-equivalent yield: ${symbol} is not a bond fund with sponsor figures (iShares, Vanguard and SPDR bond ETFs have them)` };
202
206
  }
@@ -257,6 +261,9 @@ export interface TaxEquivalentLine {
257
261
  * Muni/Treasury ratio 81.0% (SEC yield 4.04% / 7Y Treasury 4.99% on 2026-10-16, the tenor nearest its 7.13-year duration)
258
262
  */
259
263
  export function taxEquivalentLines(analysis: TaxEquivalentAnalysis): TaxEquivalentLine[] {
264
+ if (analysis.checkError) {
265
+ return [{ key: "taxEquivalentYield", label: "Tax-equivalent yield", text: analysis.checkError, value: null }];
266
+ }
260
267
  const { rates } = analysis;
261
268
  const stateText = !rates.state ? (analysis.notApplicable ? "federal" : "federal; no state rate given")
262
269
  : analysis.stateApplied ? `federal ${formatRate(rates.federal)} + ${rates.state.code} ${formatRate(rates.state.rate)}`
@@ -302,7 +309,8 @@ export function taxEquivalentJson(tax: TaxEquivalentAnalysis) {
302
309
  value: tax.ratio.value, tenor: tax.ratio.treasury.tenor, treasuryYield: tax.ratio.treasury.yield,
303
310
  treasuryAsOf: tax.ratio.treasury.asOf, durationYears: tax.ratio.durationYears,
304
311
  } : null,
305
- notes: [tax.notApplicable, tax.stateNote, tax.ratioNote].filter((note): note is string => !!note),
312
+ ...(tax.checkError ? { checkError: tax.checkError } : {}),
313
+ notes: [tax.checkError, tax.notApplicable, tax.stateNote, tax.ratioNote].filter((note): note is string => !!note),
306
314
  formula: TAX_EQUIVALENT_FORMULA,
307
315
  ratioFormula: MUNI_TREASURY_RATIO_FORMULA,
308
316
  };
@@ -245,8 +245,8 @@ export function createAppNotifier({
245
245
  }: CreateAppNotifierOptions): AppNotifier {
246
246
  return {
247
247
  notify(request) {
248
- // A missed price, catalyst or team notice waits in the Unread list; using
249
- // its toast action means it was seen.
248
+ // A missed price, catalyst, economic release or team notice waits in the
249
+ // Unread list; using its toast action means it was seen.
250
250
  const logged = appendNotificationLog(request);
251
251
  const notification: AppNotificationRequest = logged && request.action
252
252
  ? {
@@ -6,20 +6,22 @@ export const NOTIFICATION_LOG_LIMIT = 300;
6
6
 
7
7
  /**
8
8
  * The notices worth finding again after their toast is gone: a price alert or
9
- * a catalyst that fired, and a team card. Chat keeps its own unread state on
10
- * the account, and plain info, success and error toasts are not logged.
9
+ * a catalyst that fired, a high-impact economic print, and a team card. Chat
10
+ * keeps its own unread state on the account, and plain info, success and
11
+ * error toasts are not logged.
11
12
  */
12
- export type MissedNoticeKind = "price" | "catalyst" | "team";
13
+ export type MissedNoticeKind = "price" | "catalyst" | "econ" | "team";
13
14
 
14
15
  const KIND_PREFIXES: ReadonlyArray<readonly [MissedNoticeKind, string]> = [
15
16
  ["price", "price:"],
16
17
  ["catalyst", "catalyst:"],
18
+ ["econ", "econ:"],
17
19
  ["team", "team:"],
18
20
  ];
19
21
 
20
22
  export interface NotificationLogEntry {
21
23
  id: string;
22
- /** `price:<alertId>`, `catalyst:<eventId>` or `team:<cardId>`. A later notice with the same ref updates this row. */
24
+ /** `price:<alertId>`, `catalyst:<eventId>`, `econ:<eventId>` or `team:<cardId>`. A later notice with the same ref updates this row. */
23
25
  refId: string;
24
26
  title?: string;
25
27
  body: string;
@@ -134,8 +136,8 @@ export function unreadNotificationLogCount(log: readonly NotificationLogEntry[]
134
136
  }
135
137
 
136
138
  /**
137
- * Records a notice whose `refId` names a price alert, a catalyst or a team
138
- * card; anything else returns null and is not kept. The same ref replaces its
139
+ * Records a notice whose `refId` names a price alert, a catalyst, an economic
140
+ * release or a team card; anything else returns null and is not kept. The same ref replaces its
139
141
  * row: a price alert that fires again is new and unread again.
140
142
  */
141
143
  export function appendNotificationLog(
@@ -2,11 +2,12 @@ import { withConfigData, withMarketData } from "../../../cli/scoped-context";
2
2
  import { CLI_COMMAND_GROUPS } from "../../../cli/help";
3
3
  import { dryRunNote } from "../../../cli/helpers";
4
4
  import { requireArg, takeOption } from "../../../cli/commands/command-utils";
5
- import { EXCHANGE_OPTION, listingReadNotes, requireListingArg, type ListingArg } from "../../../cli/listing-arg";
5
+ import { requireQuotedListing } from "../../../cli/quote-answer";
6
+ import { EXCHANGE_OPTION, listingReadNotes, requireListingArg } from "../../../cli/listing-arg";
6
7
  import { saveConfig } from "../../../data/config/store";
7
- import type { CliCommandContext, CliCommandDef } from "../../../types/plugin";
8
+ import type { CliCommandDef } from "../../../types/plugin";
9
+ import type { Quote } from "../../../types/financials";
8
10
  import { getActiveQuoteDisplay } from "../../../market-data/market/status";
9
- import { settleWithin } from "../../../utils/async-deadline";
10
11
  import { cliStyles } from "../../../utils/cli-output";
11
12
  import { ambiguousNumberMessage, meantThousandsDot, parsePastedNumber, thousandsDotReadings, type ThousandsDotReadings } from "../../../utils/decimal-comma";
12
13
  import { newYorkToday } from "../earnings/board-model";
@@ -38,6 +39,15 @@ import { scheduledEventSources } from "./scheduled-sources";
38
39
  import { readAlertStores } from "./storage";
39
40
  import type { AlertCondition, AlertRule } from "./types";
40
41
 
42
+ /**
43
+ * What delivers a price alert, as docs/usage.md states it: the app checks it on
44
+ * every tick while it runs. Signed in with Cloud sync on, Gloom Cloud also
45
+ * pushes the trigger to a registered phone, and emails it once no app has
46
+ * synced the account for a while, so a rule the app has synced reaches you
47
+ * with the app closed. Signed out, only the running app does.
48
+ */
49
+ const PRICE_DELIVERY = "the app alerts while it is running; sign in with Cloud sync for email or phone alerts when it is closed";
50
+
41
51
  const ADD_USAGE = "Usage: gloomberb alerts add <symbol> <above|below|crosses> <price>, alerts add <symbol> earnings [--days <n>], or alerts add <CPI|FOMC> [--days <n>]";
42
52
  const DAYS_RULE = `--days takes a whole number of days from 1 to ${MAX_LEAD_DAYS}; without it the alert goes off 1 day before.`;
43
53
 
@@ -112,19 +122,13 @@ function retypeCommand(symbol: string, condition: AlertCondition, price: number,
112
122
  return `gloomberb alerts add ${symbol} ${condition} ${price}${exchange ? ` --exchange ${exchange}` : ""}`;
113
123
  }
114
124
 
115
- const LAST_PRICE_TIMEOUT_MS = 10_000;
116
-
117
125
  /**
118
126
  * The listing's last price, as the alert will check it, for telling 81.000
119
127
  * typed for 81000 from 81; null when there is none, and the price is then
120
128
  * read as written.
121
129
  */
122
- async function lastPrice(listing: ListingArg, ctx: Pick<CliCommandContext, "initMarketData">): Promise<number | null> {
123
- const quote = await settleWithin(
124
- withMarketData(ctx, (market) => market.dataProvider.getQuote(listing.key.toUpperCase(), listing.exchange)),
125
- LAST_PRICE_TIMEOUT_MS,
126
- );
127
- const price = quote ? getActiveQuoteDisplay(quote)?.price ?? quote.price : null;
130
+ function lastPrice(quote: Quote): number | null {
131
+ const price = getActiveQuoteDisplay(quote)?.price ?? quote.price;
128
132
  return price != null && Number.isFinite(price) && price !== 0 ? price : null;
129
133
  }
130
134
 
@@ -271,13 +275,17 @@ export const alertsCliCommand: CliCommandDef = {
271
275
  }
272
276
  };
273
277
  const request = action === "add" ? parseAdd() : null;
274
- const priceListing = request?.kind === "price" ? await requireListingArg(request.symbol, exchangeOption, ctx) : null;
275
- if (request?.kind === "price" && request.thousandsDot && priceListing) {
276
- const question = thousandsDotMessage(request, priceListing.key, await lastPrice(priceListing, ctx), exchangeOption);
278
+ // A price alert is kept for the listing `quote` prints for the symbol, and for no symbol that quotes nothing.
279
+ const priced = request?.kind === "price"
280
+ ? await withMarketData(ctx, (market) => requireQuotedListing(request.symbol, exchangeOption, market, ctx, "alerts add"))
281
+ : null;
282
+ const priceListing = priced?.listing ?? null;
283
+ if (request?.kind === "price" && request.thousandsDot && priced) {
284
+ const question = thousandsDotMessage(request, priced.listing.key, lastPrice(priced.quote), exchangeOption);
277
285
  if (question) ctx.fail(question);
278
286
  }
279
- // What a typed listing was read as (7203:TSE as 7203:JPX), said beside what was added.
280
- let read = priceListing ? listingReadNotes(priceListing) : [];
287
+ // What a typed listing was read as (7203:TSE as 7203:JPX, MXN as MXN:FWB2), said beside what was added.
288
+ let read = priced?.notes ?? [];
281
289
  let newEventRule: EventAlertRule | null = null;
282
290
  if (request && request.kind !== "price") {
283
291
  const target: ScheduledEventTarget = request.kind === "macro"
@@ -356,8 +364,8 @@ export const alertsCliCommand: CliCommandDef = {
356
364
  const alert = createAlert(symbol, condition, price, priceListing?.exchange);
357
365
  await save({ alerts: [...alerts, alert] });
358
366
  ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, alert }, ...(read.length > 0 ? { warnings: read } : {}) }, {
359
- // No full stop after the price, which would read as part of it (below 81.).
360
- text: (data) => `Added alert ${data.alert.id}: ${symbol} ${condition} ${price}${dryRunNote(data.dryRun)}`,
367
+ // The delivery, as docs/usage.md states it, before any full stop, which after the price would read as part of it (below 81.).
368
+ text: (data) => `Added alert ${data.alert.id}: ${symbol} ${condition} ${price} (${PRICE_DELIVERY}).${dryRunNote(data.dryRun)}`,
361
369
  });
362
370
  return;
363
371
  }
@@ -139,7 +139,7 @@ async function historyResult(ctx: HeadlessPaneContext): Promise<HeadlessRowsResu
139
139
  offset = history.hasMore ? history.nextOffset : null;
140
140
  }
141
141
  } catch (error) {
142
- throw explainRefusal(error, alertHistoryLockNote, { symbol: null, token: "ALRT" });
142
+ throw await explainRefusal(error, alertHistoryLockNote, { symbol: null, token: "ALRT" });
143
143
  }
144
144
  const unavailable = first?.status === "unavailable";
145
145
  return {
@@ -60,7 +60,7 @@ export const alertsPlugin: GloomPlugin = {
60
60
  id: "alerts",
61
61
  name: "Alerts",
62
62
  version: "1.0.0",
63
- description: "Price, market and filing event alerts",
63
+ description: "Price, earnings, filing, news and flow alerts",
64
64
  toggleable: true,
65
65
  cliCommands: [alertsCliCommand],
66
66
 
@@ -189,7 +189,11 @@ export const alertsPlugin: GloomPlugin = {
189
189
  id: "set-event-alert",
190
190
  label: "Add Event Alert",
191
191
  description: "Create an alert before earnings, CPI or FOMC, or for filings, news and market moves",
192
- keywords: ["alert", "event", "congress", "13f", "fund", "member", "follow", "earnings", "cpi", "fomc", "days before"],
192
+ keywords: [
193
+ "alert", "event", "congress", "13f", "fund", "member", "follow", "earnings", "cpi", "fomc", "days before",
194
+ "filing", "filings", "sec", "8-k", "news", "keyword", "analyst", "52-week", "unusual volume", "volume",
195
+ "short interest", "insider", "catalyst", "implied volatility", "iv spike", "options flow", "perps", "funding",
196
+ ],
193
197
  category: "data",
194
198
  wizardLayout: "form",
195
199
  wizard: [
@@ -348,8 +352,13 @@ export const alertsPlugin: GloomPlugin = {
348
352
  id: "alerts-pane",
349
353
  paneId: "alerts",
350
354
  label: "Alerts",
351
- description: "Price, market and filing event alerts",
352
- keywords: ["alerts", "price", "trigger", "alarm", "watch", "notify"],
355
+ description: "Price, earnings, filing, news and flow alerts",
356
+ keywords: [
357
+ "alerts", "alert", "price", "trigger", "alarm", "watch", "notify", "notification",
358
+ "earnings", "cpi", "fomc", "filing", "filings", "sec", "8-k", "13f", "news", "keyword",
359
+ "analyst", "upgrade", "downgrade", "52-week", "unusual volume", "volume", "short interest",
360
+ "insider", "congress", "catalyst", "implied volatility", "iv spike", "options flow", "perps", "funding",
361
+ ],
353
362
  shortcut: { prefix: "ALRT" },
354
363
  headless: alertsHeadless,
355
364
  });
@@ -482,7 +482,7 @@ export const portfolioAnalyticsModule: PluginModule = {
482
482
  paneId: "analytics",
483
483
  label: prefix === "PORT" ? "Portfolio Analytics" : "Portfolio Market Risk",
484
484
  description: "Benchmark-relative risk, factor betas, stress shifts and local account evidence.",
485
- keywords: ["risk", "analytics", "sharpe", "beta", "sector", "allocation", "portfolio"],
485
+ keywords: ["risk", "analytics", "sharpe", "beta", "sector", "allocation", "portfolio", "style", "quality", "factor tilt"],
486
486
  shortcut: { prefix, argKind: "text", argOptional: true, argPlaceholder: "portfolio-id" },
487
487
  headless: portfolioRiskHeadless,
488
488
  canCreate: (context) => context.config.portfolios.length > 0,