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
@@ -93,7 +93,7 @@ const POP_OUT = key("p", "op out");
93
93
  const STEP = key("←/→", " step");
94
94
  /** THEM and MEMB members: open them in RRG, CORR, SIW or RIPL, or save them as a watchlist. */
95
95
  const MEMBERS_MENU = key(".", " open members in, save as watchlist");
96
- const CHART_KEYS = [key("s", "eries"), key("i", "ndicators"), key("t", "imeframe"), key("f", "ormulas")];
96
+ const CHART_KEYS = [key("s", "eries"), key("i", "ndicators"), key("b", "ars"), key("f", "ormulas")];
97
97
 
98
98
  export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
99
99
  // Research companies
@@ -445,8 +445,8 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
445
445
  summary: "Named executive officers and what they were paid, read from the proxy statement and checked against the filing.",
446
446
  usage: ["EXEC NVDA"],
447
447
  keys: [key("y", "ear"), key("o", "pen filing")],
448
- data: pro(AS_FILED.pro),
449
- access: "pro",
448
+ data: { free: "As filed; each executive's total pay from the latest proxy", pro: "As filed; the pay breakdown and every year" },
449
+ access: "preview",
450
450
  bloomberg: ["MGMT"],
451
451
  },
452
452
  EK: {
@@ -681,10 +681,10 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
681
681
 
682
682
  // Watch markets
683
683
  MOST: {
684
- summary: "Today's top gainers, losers, most active and trending tickers with price, change, volume against average and market cap. On Pro, pre-market and after-hours movers and the gaps at the open.",
684
+ summary: "Today's top gainers, losers, most active and trending tickers with price, change, volume against average and market cap, and the pre-market and after-hours movers. On Pro, the gaps at the open.",
685
685
  usage: ["MOST"],
686
686
  keys: [TABS, OPEN],
687
- data: { free: DELAYED, pro: "Real-time, pre-market and after-hours" },
687
+ data: { free: DELAYED, pro: REAL_TIME },
688
688
  bloomberg: ["MOST"],
689
689
  },
690
690
  WEI: {
@@ -971,7 +971,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
971
971
  bloomberg: [],
972
972
  },
973
973
  PDLR: {
974
- summary: "Weekly primary dealer net positions and settlement fails across Treasuries and mortgage-backed securities.",
974
+ summary: "Weekly primary dealer net positions with history, by sector and Treasury tenor (bills, coupons, TIPS, FRN), and settlement fails.",
975
975
  usage: ["PDLR"],
976
976
  keys: [TABS],
977
977
  data: same("Weekly, as published"),
@@ -1110,7 +1110,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
1110
1110
  docs: "PORT",
1111
1111
  },
1112
1112
  ALRT: {
1113
- summary: "Your price alerts with current price, distance to target and trigger state, plus alerts days before earnings, CPI or FOMC and market and filing event alerts.",
1113
+ summary: "Your price alerts with current price, distance to target and trigger state, plus alerts on earnings, CPI and FOMC dates, filings and 13F, news keywords, analyst changes, 52-week highs and lows, unusual volume, short interest, insider and congress trades, catalysts, IV spikes, options flow and perps.",
1114
1114
  usage: ["ALRT"],
1115
1115
  keys: [key("a", "dd alert"), key("e", "dit"), key("d", "elete"), key("m", "re-arm")],
1116
1116
  data: QUOTES,
@@ -1238,6 +1238,30 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
1238
1238
  data: null,
1239
1239
  bloomberg: [],
1240
1240
  },
1241
+ BUY: {
1242
+ summary: "Open the shared Trade ticket with a buy quantity and optional limit price. Focus starts on Review; choose an account and review the broker preview before confirming.",
1243
+ usage: ["BUY AAPL 10", "BUY AAPL 10 150.25"],
1244
+ keys: [key("r", "eview"), key("o", "rders")],
1245
+ data: same("Broker account and quote"),
1246
+ bloomberg: [],
1247
+ docs: "BR",
1248
+ },
1249
+ SELL: {
1250
+ summary: "Open the shared Trade ticket with a sell quantity and optional limit price. Focus starts on Review; holdings and broker capabilities are checked before confirmation.",
1251
+ usage: ["SELL AAPL 10", "SELL AAPL 10 350.25"],
1252
+ keys: [key("r", "eview"), key("o", "rders")],
1253
+ data: same("Broker account and quote"),
1254
+ bloomberg: [],
1255
+ docs: "BR",
1256
+ },
1257
+ ORD: {
1258
+ summary: "Open orders and recent activity for a selected broker profile and account. Review a modification or confirm cancellation from an open order; uncertain outcomes require reconciliation before another action.",
1259
+ usage: ["ORD"],
1260
+ keys: [key("m", "odify"), key("c", "ancel")],
1261
+ data: same("Broker account snapshot"),
1262
+ bloomberg: [],
1263
+ docs: "BR",
1264
+ },
1241
1265
 
1242
1266
  // Layouts and settings
1243
1267
  GL: {
@@ -168,12 +168,12 @@ async function loadHeadlessPaneModel(
168
168
  return result;
169
169
  }
170
170
 
171
- type HeadlessMarketContext = Pick<MarketContext, "config" | "store" | "refresh"> & Partial<Pick<MarketContext, "persistence" | "seriesCapabilities">> & {
171
+ type HeadlessMarketContext = Pick<MarketContext, "config" | "store" | "refresh" | "resolveBroker"> & Partial<Pick<MarketContext, "persistence" | "seriesCapabilities">> & {
172
172
  dataProvider: HeadlessPaneContext["marketData"];
173
173
  };
174
174
 
175
175
  /** What a headless loader reads the market, the account and the saved collections through. */
176
- function createHeadlessPaneContext(
176
+ export function createHeadlessPaneContext(
177
177
  resolved: ResolvedPaneFunction,
178
178
  context: HeadlessMarketContext,
179
179
  signal: AbortSignal,
@@ -186,6 +186,7 @@ function createHeadlessPaneContext(
186
186
  ...(context.refresh ? { refresh: true } : {}),
187
187
  settings: resolved.instance.settings,
188
188
  capabilities: getSharedRegistry() ?? context.seriesCapabilities,
189
+ resolveBroker: context.resolveBroker,
189
190
  async resolvePortfolio(id) {
190
191
  const portfolio = findNamedCollection(context.config.portfolios, id);
191
192
  if (!portfolio) return null;
@@ -498,7 +499,7 @@ function renderRows(
498
499
  };
499
500
  return renderTable(
500
501
  columns.map((column) => ({
501
- header: displayedHeader(column),
502
+ header: column.textHeader ?? displayedHeader(column),
502
503
  align: column.align,
503
504
  width: column.width,
504
505
  shrink: column.shrink,
@@ -939,9 +940,9 @@ export async function buildHeadlessFunctionReport(
939
940
  loaded = await loadResolvedHeadlessPaneModel(resolved, context, rawArgument);
940
941
  } catch (error) {
941
942
  // A Pro pane the server refused reports what its wall covers, as its rendered view does.
942
- const locked = lockedHeadlessReport(resolved, error, () => (
943
+ const locked = await lockedHeadlessReport(resolved, error, () => (
943
944
  buildHeadlessPaneLoadArgs(resolved.headless!, resolved.token, rawArgument, resolved.options).symbols
944
- ));
945
+ ), createHeadlessPaneContext(resolved, context, new AbortController().signal));
945
946
  if (locked) return locked;
946
947
  throw error;
947
948
  }
@@ -36,7 +36,7 @@ import {
36
36
  import { isNotATickerMessage } from "../not-a-ticker";
37
37
  import { isCliFailure } from "../errors";
38
38
  import { isTickerLinkPeer } from "../../layout/ticker-navigation";
39
- import { knownGapMessage, commandPointer, topicPointer } from "../known-gaps";
39
+ import { knownGapMessage, commandPointer, commandPointerLeads, topicPointer } from "../known-gaps";
40
40
  import { selectReportTables } from "../report-tables";
41
41
  import { cliOutputWritten } from "../result";
42
42
  import type { CatalogCommandHit } from "../unknown-command";
@@ -486,6 +486,7 @@ export async function runPaneCatalog(args: string[], ctx: CliCommandContext) {
486
486
  // A setting or a report can be a command, not a function: "light theme" names `config themes` beside THEM,
487
487
  // "drip" names `portfolio show --drip` beside DVD.
488
488
  const setting = commandPointer(parsed.query);
489
+ const settingLeads = commandPointerLeads(parsed.query);
489
490
  // The data catalog series the query names, which `fn G` charts.
490
491
  const series = findCatalogSeries(ctx.plugins, parsed.query);
491
492
  const listed = pointer
@@ -496,7 +497,7 @@ export async function runPaneCatalog(args: string[], ctx: CliCommandContext) {
496
497
  ...series.length ? { metadata: { series } } : {},
497
498
  ...(gap || setting) && ctx.cliOptions.format !== "text" ? { warnings: [gap, setting].filter((note): note is string => !!note) } : {},
498
499
  }, {
499
- text: () => renderPaneCatalogReport(filtered, effectiveParsed, glossary, gap, series, setting),
500
+ text: () => renderPaneCatalogReport(filtered, effectiveParsed, glossary, gap, series, setting, settingLeads),
500
501
  });
501
502
  } finally {
502
503
  registry.destroy();
@@ -2,6 +2,7 @@ import { refusedLock } from "../../plugins/builtin/shared/cloud-denied";
2
2
  import { lockNoteReason } from "../../plugins/builtin/shared/pro-lock-note";
3
3
  import { cliTerminalWidth, wrapText } from "../../utils/cli-output";
4
4
  import { reportFooterLines } from "../report-tables";
5
+ import type { HeadlessPaneContext } from "../../types/headless";
5
6
  import type { PaneFunctionReport } from "./report";
6
7
  import type { ResolvedPaneFunction } from "./resolver";
7
8
 
@@ -22,19 +23,21 @@ export function lockNoteLines(note: string): string[] {
22
23
  * `reportLockNote`, no source line and `locked` in JSON. The refusal may be
23
24
  * the error itself or the cause of the one a headless read wrote for it
24
25
  * (`explainRefusal`). Null when the error is no such refusal or the pane has
25
- * no note, and the error stands.
26
+ * no note, and the error stands. The context lets the note check the free
27
+ * route it names.
26
28
  */
27
- export function lockedHeadlessReport(
29
+ export async function lockedHeadlessReport(
28
30
  resolved: ResolvedPaneFunction,
29
31
  error: unknown,
30
32
  /** The symbols the report was asked for, read once the error is known to be a refusal. */
31
33
  symbolsOf: () => string[],
32
- ): PaneFunctionReport | null {
34
+ context?: HeadlessPaneContext,
35
+ ): Promise<PaneFunctionReport | null> {
33
36
  const refused = refusedLock(error);
34
37
  if (!refused || !resolved.pane.reportLockNote) return null;
35
38
  const gate = refused.gate;
36
39
  const symbols = symbolsOf();
37
- const note = resolved.pane.reportLockNote({ ...refused, symbol: symbols[0] ?? null, token: resolved.token });
40
+ const note = await resolved.pane.reportLockNote({ ...refused, symbol: symbols[0] ?? null, token: resolved.token, ...context ? { context } : {} });
38
41
  const reason = lockNoteReason(note);
39
42
  return {
40
43
  data: {
@@ -148,7 +148,8 @@ export async function applyListingArgument(
148
148
  // A list entry with spaces is a company's name search finds, or the symbols it holds.
149
149
  const typed = takesOneTicker ? [args.arg] : await readNameEntries(splitSymbolArgs([args.arg]), context.dataProvider);
150
150
  if (typed.length === 0) return args;
151
- let listings = await Promise.all(typed.map((raw) => resolveCliListing(raw, exchange, context, { ownExchangeWins: typed.length > 1 })));
151
+ // RY.TO is the listing its quote names, RY:TSX, as `quote` reads it: a function's data is read by venue.
152
+ let listings = await Promise.all(typed.map((raw) => resolveCliListing(raw, exchange, context, { ownExchangeWins: typed.length > 1, venueSuffix: true })));
152
153
  // A separator stuck to the end of a pasted list (`UPST;`) is not part of a symbol; it is named.
153
154
  const notes: string[] = takesOneTicker ? [] : stripStrayEdges(args.arg).notes;
154
155
  const token = found.template?.shortcut?.prefix ?? args.target.toUpperCase();
@@ -0,0 +1,179 @@
1
+ import type { QuoteBatchResult } from "../types/data-provider";
2
+ import type { Quote } from "../types/financials";
3
+ import { bareListingNote } from "../tickers/listing-fate";
4
+ import { notATickerSymbol } from "../sources/provider-errors";
5
+ import { looksLikeCurrencyPair } from "../utils/currency-pair";
6
+ import { canonicalExchange } from "../utils/exchanges";
7
+ import type { CliCommandDef } from "../types/plugin";
8
+ import { currencyCodeNote } from "./currency-hints";
9
+ import { describeSuggestion, suggestListing, suggestUsTicker, type ListingSuggestion } from "./listing-suggestion";
10
+ import { indexRootAlsoLine, indexRootFor, indexRootTryLine } from "./index-roots";
11
+ import { treasuryYieldLine, treasuryYieldTenor } from "./treasury-yields";
12
+ import { knownGapForTicker } from "./known-gaps";
13
+ import {
14
+ futuresTryLine,
15
+ isNoProviderError,
16
+ listingChoiceNotes,
17
+ listingReadNotes,
18
+ loadBareVenues,
19
+ notTradedMessage,
20
+ quoteBareListing,
21
+ requireCliListing,
22
+ tradingBoardNote,
23
+ type CliListing,
24
+ type ListingDataRequest,
25
+ type ListingVenue,
26
+ } from "./listing-arg";
27
+ import { withListingSuggestion } from "./not-a-ticker";
28
+ import type { MarketContext } from "./types";
29
+
30
+ /** The text of an error, or null for none. */
31
+ export function errorMessage(error: unknown): string | null {
32
+ if (error == null) return null;
33
+ return error instanceof Error ? error.message : String(error);
34
+ }
35
+
36
+ /**
37
+ * "APPL is not a US ticker: it is Aptus Pharma Limited on BSE (INR). Did you
38
+ * mean AAPL (Apple Inc.)?", for a symbol read on a listing abroad that is one
39
+ * slip from a US ticker, worded as the not-a-ticker answers word it.
40
+ */
41
+ export function notUsTickerSentence({ target, quote, listing, usSuggestion }: { target: { symbol: string }; quote: Quote | null; listing?: { name: string | null }; usSuggestion?: ListingSuggestion }): string | null {
42
+ if (!quote || !usSuggestion) return null;
43
+ const venue = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
44
+ const name = quote.name || listing?.name;
45
+ const what = `${name ? `${name} on ` : ""}${venue}${quote.currency ? ` (${quote.currency})` : ""}`;
46
+ return `${target.symbol} is not a US ticker: it is ${what}. Did you mean ${describeSuggestion(usSuggestion)}?`;
47
+ }
48
+
49
+ export interface QuoteAnswer {
50
+ result: QuoteBatchResult;
51
+ /** The listing the row is: the one asked for, or the venue a bare symbol was quoted on. */
52
+ listing: CliListing;
53
+ /** Why there is no quote, when there is more to say than the source's own error. */
54
+ failure?: { message: string; details?: string; oneLine: string };
55
+ notes: string[];
56
+ }
57
+
58
+ /**
59
+ * One symbol's row of `quote` and `compare`. A listing on an exchange its symbol
60
+ * is not listed on says so. A bare symbol the data service read on its home
61
+ * listing abroad names that venue (SXR8 -> XETRA). One no source quotes is
62
+ * quoted on its listings (`quoteBareListing`), else names them, or asks for an
63
+ * exchange when several companies list it (2222); a bare index root (VIX) also
64
+ * names its index (^VIX), and a futures board code a security keeps (GC) its
65
+ * future (GC=F). Venues are only looked up for a
66
+ * symbol with no quote, so a request that succeeds costs no search.
67
+ */
68
+ export async function answerQuote(
69
+ result: QuoteBatchResult,
70
+ listing: CliListing,
71
+ market: MarketContext,
72
+ request: ListingDataRequest,
73
+ refresh: boolean,
74
+ venues: Promise<ListingVenue[]> | null,
75
+ ): Promise<QuoteAnswer> {
76
+ const root = listing.exchange ? null : indexRootFor(listing.symbol);
77
+ // A bare root that quoted something else, such as MOVE (Corvex), points at the index too.
78
+ const also = (quote: Quote) => root && quote.instrumentType?.trim().toUpperCase() !== "INDEX"
79
+ ? [indexRootAlsoLine(root, request.command)] : [];
80
+ if (result.quote) {
81
+ // "CAR = Avis Budget Group, NASDAQ (also XETRA, ASX)" names the venue too, so it replaces "SXR8 -> XETRA".
82
+ const choice = venues ? await listingChoiceNotes(listing, market, result.quote, venues) : listingReadNotes(listing);
83
+ const venue = choice.length > 0 ? null : bareListingNote(listing, result.quote);
84
+ // A row saved as 247540:KRX is quoted from KOSDAQ, the board it trades on.
85
+ const board = tradingBoardNote(listing, result.quote);
86
+ return { result, listing, notes: [...choice, venue ?? [], board ?? [], also(result.quote), currencyCodeNote(listing, result.quote) ?? []].flat() };
87
+ }
88
+ const notTraded = await notTradedMessage(listing, market);
89
+ if (notTraded) return { result, listing, failure: { message: notTraded, oneLine: notTraded }, notes: [] };
90
+ if (!isNoProviderError(result.error)) return { result, listing, notes: [] };
91
+ const other = await quoteBareListing(listing, market, request, { refresh });
92
+ if (other?.kind === "quoted") {
93
+ return {
94
+ result: { target: other.listing.request, quote: other.quote },
95
+ listing: other.listing,
96
+ notes: [other.note, ...also(other.quote), currencyCodeNote(listing, other.quote) ?? []].flat(),
97
+ };
98
+ }
99
+ // Other listings already name a futures board code's future (GC=F); with none, it is named here.
100
+ // A Treasury yield's root (TNX) names its FRED series and the curve beside its index.
101
+ const yieldTenor = root ? treasuryYieldTenor(listing.symbol) : null;
102
+ const tryLines = [
103
+ yieldTenor ? treasuryYieldLine(yieldTenor) : root ? indexRootTryLine(root, request.command) : null,
104
+ other || listing.exchange ? null : futuresTryLine(listing.symbol, request),
105
+ // BDI asks which exchange (Black Diamond Group) and says nowhere that the Baltic Dry Index is not here.
106
+ listing.exchange ? null : knownGapForTicker(listing.symbol),
107
+ ].filter((line): line is string => !!line);
108
+ if (!other && tryLines.length === 0) return { result: await withCurrencyPairHint(result, market), listing, notes: [] };
109
+ const message = other?.message ?? `${listing.symbol} has no ${request.noun}.`;
110
+ const details = [other?.details, ...tryLines].filter(Boolean).join("\n");
111
+ const oneLine = [
112
+ message,
113
+ other ? `${other.heading}: ${other.listings.join(", ")}.` : "",
114
+ ...tryLines,
115
+ ].filter(Boolean).join(" ");
116
+ return { result, listing, failure: { message, details, oneLine }, notes: [] };
117
+ }
118
+
119
+ /** A listing a command found a quote for, with what `quote` says beside it. */
120
+ interface QuotedListing {
121
+ /** The listing the quote is of: the one typed, or the venue a bare symbol was quoted on. */
122
+ listing: CliListing;
123
+ quote: Quote;
124
+ /** The venue a bare symbol was read on, the currency it may have meant, the US ticker it is one slip from. */
125
+ notes: string[];
126
+ }
127
+
128
+ /**
129
+ * The listing a command that keeps a symbol (`alerts add`) was given, read as
130
+ * `quote` reads it: the same resolver and the same quote request, so what is
131
+ * kept is the listing `quote` prints (USDMXN and USD/MXN are USDMXN=X, MXN is
132
+ * MXN:FWB2) and `quote`'s own notes come back with it. A symbol nothing
133
+ * quotes fails the command with why, and the nearest listing search finds when
134
+ * it is near ("Not a ticker: ZZZQQ. Did you mean ZZZQ?"), so nothing is kept
135
+ * for it.
136
+ */
137
+ export async function requireQuotedListing(
138
+ raw: string,
139
+ exchange: string | null | undefined,
140
+ market: MarketContext,
141
+ ctx: Pick<Parameters<CliCommandDef["execute"]>[1], "fail">,
142
+ command: string,
143
+ ): Promise<QuotedListing> {
144
+ const listing = await requireCliListing(raw, exchange, market, ctx);
145
+ const venues = loadBareVenues(listing, market);
146
+ const [result] = await market.dataProvider.getQuotesBatch([listing.request], { revalidate: true });
147
+ const answer = await answerQuote(result ?? { target: listing.request, quote: null }, listing, market, { command, noun: "quote" }, false, venues);
148
+ const quote = answer.result.quote;
149
+ if (!quote) {
150
+ if (answer.failure) ctx.fail(answer.failure.message, answer.failure.details);
151
+ const message = errorMessage(answer.result.error) ?? `No quote found for ${listing.key}.`;
152
+ // The listing search names, else the US ticker it is one slip from (AAPLL for AAPL), which search does not list for a typo.
153
+ const suggestion = notATickerSymbol(message) && !message.includes(" Did you mean ")
154
+ ? await suggestListing(listing.symbol, market) ?? await suggestUsTicker(listing.symbol, market)
155
+ : null;
156
+ ctx.fail(suggestion ? withListingSuggestion(message, describeSuggestion(suggestion)) : message);
157
+ }
158
+ // A bare symbol read on a listing abroad that is one slip from a US ticker says so, as `quote` does.
159
+ const venue = answer.listing.saved ? null : bareListingNote(answer.listing, quote);
160
+ const usSuggestion = venue && answer.notes.includes(venue) ? await suggestUsTicker(answer.listing.symbol, market) : null;
161
+ const notUs = notUsTickerSentence({ target: answer.result.target, quote, listing: { name: quote.name ?? null }, usSuggestion: usSuggestion ?? undefined });
162
+ return { listing: answer.listing, quote, notes: [...answer.notes, ...notUs ? [notUs] : []] };
163
+ }
164
+
165
+ /**
166
+ * A symbol written like a currency pair that is not a ticker (USD_NGN, as some
167
+ * FX feeds write a pair) names the listing `gloomberb search` finds nearest to
168
+ * it, as `watchlist add` does: "Did you mean USDNGN=X (US Dollar/Nigerian
169
+ * Naira)?". When search finds nothing near, the row keeps its own error and its
170
+ * search hint.
171
+ */
172
+ async function withCurrencyPairHint(result: QuoteBatchResult, market: MarketContext): Promise<QuoteBatchResult> {
173
+ const message = errorMessage(result.error);
174
+ const symbol = message ? notATickerSymbol(message) : null;
175
+ if (!message || !symbol || !looksLikeCurrencyPair(symbol)) return result;
176
+ const suggestion = await suggestListing(symbol, market);
177
+ return suggestion ? { ...result, error: withListingSuggestion(message, describeSuggestion(suggestion)) } : result;
178
+ }
179
+
@@ -1,6 +1,7 @@
1
1
  import { seriesExpression } from "../series/registry";
2
2
  import type { SeriesDefinition } from "../series/types";
3
3
  import { cliStyles, renderSection, renderTable, wrapText } from "../utils/cli-output";
4
+ import { currencyCodeLabel } from "../utils/currency-units";
4
5
 
5
6
  /** A data catalog series a query names, as the CLI lists it. */
6
7
  export interface SeriesMatch {
@@ -108,7 +109,7 @@ export function renderSeriesMatches(
108
109
  renderTable(
109
110
  [{ header: "Series" }, { header: "Unit" }, { header: "Frequency", optional: true }, { header: "Publisher", optional: true },
110
111
  { header: "Chart", shrink: false }],
111
- shown.map((match) => [match.name, match.unit, match.frequency, match.publisher ?? "", cliStyles.command(shellArgument(match.expression))]),
112
+ shown.map((match) => [match.name, currencyCodeLabel(match.unit), match.frequency, match.publisher ?? "", cliStyles.command(shellArgument(match.expression))]),
112
113
  ),
113
114
  "",
114
115
  ...wrapText(hint, width).map((line) => cliStyles.muted(line)),
@@ -74,28 +74,59 @@ function looksLikeSymbol(cell: string): boolean {
74
74
  return SYMBOL_TOKEN.test(cell) && !(NUMBER_CELL.test(cell) && /[.,%]/.test(cell));
75
75
  }
76
76
 
77
+ /** How many cells a table's ignored-cells note names before it counts the rest. */
78
+ const IGNORED_PREVIEW = 8;
79
+
80
+ /** What a file or piped text holds: its symbols in order, and the cells of a table read by one column that were left out. */
81
+ interface SymbolText {
82
+ symbols: string[];
83
+ /** Cells beside the symbol column of a table with no header, in reading order; empty for a list or a CSV with a header. */
84
+ ignored: string[];
85
+ }
86
+
77
87
  /**
78
88
  * The symbols in a file or piped text, in order. A spreadsheet export reads
79
- * from the column its header names (Symbol, Ticker, Code), or else its first
80
- * column; a list split by commas, spaces or newlines gives every entry. A byte
81
- * order mark, Windows line ends, blank lines and lines starting with # or //
82
- * are ignored.
89
+ * from the column its header names (Symbol, Ticker, Code). Without a header,
90
+ * every cell is a symbol unless some column after the first holds a name, a
91
+ * price or a weight: that table is read by its first column, and the cells
92
+ * beside it are returned as `ignored` so the command can name them. A list
93
+ * split by commas, spaces or newlines gives every entry, even when a line of
94
+ * it is ragged or carries a stray separator (`"BIMAS ,"`). A byte order mark,
95
+ * Windows line ends, blank lines and lines starting with # or // are ignored.
83
96
  */
84
- export function parseSymbolText(text: string): string[] {
97
+ export function parseSymbolText(text: string): SymbolText {
85
98
  const normalized = text.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
86
99
  const lines = normalized.split("\n");
87
100
  const delimiter = detectDelimiter(lines);
88
101
  const records = (delimiter ? parseDelimited(normalized, delimiter) : lines.map((line) => line.trim().split(/\s+/)))
89
102
  .filter((cells) => cells.some(Boolean) && !isComment(cells[0] ?? ""));
90
- if (records.length === 0) return [];
103
+ if (records.length === 0) return { symbols: [], ignored: [] };
91
104
  const header = records[0]!.map(headerKey);
92
105
  const exact = header.findIndex((cell) => SYMBOL_HEADERS.has(cell));
93
106
  const column = exact >= 0 ? exact : header.findIndex((cell) => cell.includes("symbol") || cell.includes("ticker"));
94
- if (column >= 0) return records.slice(1).map((cells) => cells[column] ?? "").filter(Boolean);
95
- // Without a header, symbols laid out across columns are all symbols; a
96
- // table with names, prices or weights beside them is read by its first column.
97
- const grid = records.every((cells) => cells.every((cell, index) => !cell || (looksLikeSymbol(cell) && (index === 0 || !NUMBER_CELL.test(cell)))));
98
- return grid ? records.flat().filter(Boolean) : records.map((cells) => cells[0] ?? "").filter(Boolean);
107
+ if (column >= 0) return { symbols: records.slice(1).map((cells) => cells[column] ?? "").filter(Boolean), ignored: [] };
108
+ // A separator or quote mark stuck to a cell is dropped (and named) when it is read, so it does not make the list a table.
109
+ const cleaned = records.map((cells) => cells.map((cell) => stripStrayEdges(cell).text));
110
+ const grid = cleaned.every((cells) => cells.every((cell, index) => !cell || (looksLikeSymbol(cell) && (index === 0 || !NUMBER_CELL.test(cell)))));
111
+ if (grid) return { symbols: records.flat().filter(Boolean), ignored: [] };
112
+ return {
113
+ symbols: records.map((cells) => cells[0] ?? "").filter(Boolean),
114
+ ignored: records.flatMap((cells) => cells.slice(1)).filter(Boolean),
115
+ };
116
+ }
117
+
118
+ /**
119
+ * The note a table read by its first column gets, naming what was left out:
120
+ * `Read the first column as symbols; ignored the 4 cells beside it: Apple
121
+ * Inc., 336.64, Microsoft, 535.07.` Long tables name the first few and count
122
+ * the rest. Null when nothing was ignored.
123
+ */
124
+ function ignoredCellsNote(ignored: readonly string[]): string | null {
125
+ if (ignored.length === 0) return null;
126
+ const named = ignored.slice(0, IGNORED_PREVIEW).join(", ");
127
+ const more = ignored.length - IGNORED_PREVIEW;
128
+ const cells = ignored.length === 1 ? "the cell" : `the ${ignored.length} cells`;
129
+ return `Read the first column as symbols; ignored ${cells} beside it: ${named}${more > 0 ? ` and ${more} more` : ""}.`;
99
130
  }
100
131
 
101
132
  /** Whether standard input is a pipe or a file rather than a terminal, so a command may read its list from it. */
@@ -138,9 +169,10 @@ export class SymbolListError extends Error {
138
169
  * standard input when an argument is `-` or nothing else was given and input
139
170
  * is piped in. Repeats are dropped. A separator stuck to a symbol in a file, or
140
171
  * left on the end of what was typed (`UPST;`), is dropped with the quote marks
141
- * around it, and each is named in `strays` so the command can say so.
172
+ * around it, and the cells beside the symbols of a table read by its first
173
+ * column are left out; `notes` names each so the command can say so.
142
174
  */
143
- export async function readSymbolList(source: SymbolListSource): Promise<{ symbols: string[]; repeated: number; strays: string[] }> {
175
+ export async function readSymbolList(source: SymbolListSource): Promise<{ symbols: string[]; repeated: number; notes: string[] }> {
144
176
  const readStdin = source.readStdin ?? (() => Bun.stdin.text());
145
177
  const wantsStdin = source.args.includes("-") || source.file === "-";
146
178
  const typedArgs = source.args.filter((arg) => arg !== "-");
@@ -148,12 +180,14 @@ export async function readSymbolList(source: SymbolListSource): Promise<{ symbol
148
180
  const collected = [...typed];
149
181
  // A separator left on the end of what was typed: `UPST;`, or a list that ends in a comma.
150
182
  const strays = new Set<string>(stripStrayEdges(typedArgs.join(" ")).notes);
151
- const addCells = (cells: readonly string[]) => {
152
- for (const cell of cells) {
183
+ const ignored: string[] = [];
184
+ const addCells = ({ symbols, ignored: beside }: SymbolText) => {
185
+ for (const cell of symbols) {
153
186
  const cleaned = stripStrayEdges(cell);
154
187
  cleaned.notes.forEach((note) => strays.add(note));
155
188
  collected.push(cleaned.text);
156
189
  }
190
+ ignored.push(...beside);
157
191
  };
158
192
  if (source.file != null && source.file !== "-") {
159
193
  if (!source.file.trim() || source.file.startsWith("--")) throw new SymbolListError("--file needs a path, such as --file symbols.csv.");
@@ -169,5 +203,6 @@ export async function readSymbolList(source: SymbolListSource): Promise<{ symbol
169
203
  if (wantsStdin || (typed.length === 0 && source.file == null && (source.stdinPiped ?? stdinIsPiped()))) {
170
204
  addCells(parseSymbolText(await readStdin()));
171
205
  }
172
- return { ...uniqueSymbols(collected), strays: [...strays] };
206
+ const ignoredNote = ignoredCellsNote(ignored);
207
+ return { ...uniqueSymbols(collected), notes: [...strays, ...ignoredNote ? [ignoredNote] : []] };
173
208
  }
package/src/cli/types.ts CHANGED
@@ -5,6 +5,7 @@ import { AssetDataRouter } from "../sources/provider-router";
5
5
  import type { AppServices } from "../core/app-services";
6
6
  import type { CapabilityInvoker } from "../capabilities/types";
7
7
  import type { StatementReportSources } from "./statement-filings";
8
+ import type { FxHistoryClient } from "../market-data/fx-closes";
8
9
 
9
10
  export type ConfigContext = {
10
11
  config: AppConfig;
@@ -15,12 +16,15 @@ export type ConfigContext = {
15
16
 
16
17
  export type MarketContext = ConfigContext & {
17
18
  dataProvider: AssetDataRouter;
19
+ resolveBroker?: (brokerType: string) => import("../types/broker").BrokerAdapter | null;
18
20
  /** Charts catalog series in a report run without the plugin runtime. */
19
21
  seriesCapabilities?: CapabilityInvoker;
20
22
  /** `--refresh`: prefetched data bypasses the local cache. */
21
23
  refresh?: boolean;
22
24
  /** Filings behind the statements and a bank's figures, read from Gloom Cloud; absent where the data service is a stand-in. */
23
25
  statementSources?: StatementReportSources;
26
+ /** Daily FX closes (`fx --date`, converted INS values); Gloom Cloud unless a test stands in for it. */
27
+ fxHistory?: FxHistoryClient;
24
28
  };
25
29
 
26
30
  export type CliServicesContext = {
@@ -11,6 +11,7 @@ import { ChartToolbar, CHART_TOOLBAR_WIDTH } from "./toolbar";
11
11
  import { AsciiText, Box, ChartSurface, Text, useUiCapabilities, type BoxRenderable } from "../../../ui";
12
12
  import { useLiveStretch } from "../../../ui/live-stretch";
13
13
  import { useOptionalPaneInstanceId } from "../../../state/app/context";
14
+ import { usePaneFocused } from "../../../state/app/activity";
14
15
  import { useTabCursorLink } from "../../../state/app/tab-cursor";
15
16
  import { useThemeColors, useThemeId } from "../../../theme/theme-context";
16
17
  import { themedSeriesColor, themeSeriesColors } from "../../../theme/series-colors";
@@ -155,6 +156,8 @@ function ThemedCompositeChart({
155
156
  const showTextFallback = useShowChartTextFallback();
156
157
  const [internalCursorDate, setInternalCursorDate] = useState<Date | null>(null);
157
158
  const [toolSpan, setToolSpan] = useState<ChartToolSpan | null>(null);
159
+ const [pointerDragging, setPointerDragging] = useState(false);
160
+ const paneFocused = usePaneFocused();
158
161
  const paneInstanceId = useOptionalPaneInstanceId();
159
162
  const [drawings, setDrawings] = useState<readonly ChartDrawing[]>(NO_DRAWINGS);
160
163
  const [selectedDrawingId, setSelectedDrawingId] = useState<string | null>(null);
@@ -186,7 +189,13 @@ function ThemedCompositeChart({
186
189
  (drawing) => drawing.id === selectedDrawingId ? { ...drawing, color } : drawing,
187
190
  ));
188
191
  }, [selectedDrawingId]);
189
- const resolvedCursorDate = cursorDate === undefined ? internalCursorDate : cursorDate;
192
+ // A cursor the owner rests on without following its moves (a seasonal
193
+ // chart's latest week) is where the chart's own cursor goes back to: the
194
+ // pointer moves it as on any chart, and the pointer leaving returns it.
195
+ const restingCursor = cursorDate !== undefined && !onCursorDateChange;
196
+ const ownsCursor = cursorDate === undefined || restingCursor;
197
+ const resolvedCursorDate = cursorDate === undefined ? internalCursorDate
198
+ : restingCursor ? internalCursorDate ?? cursorDate : cursorDate;
190
199
  const totalWidth = Math.max(1, Math.floor(width));
191
200
  const totalHeight = Math.max(1, Math.floor(height));
192
201
  const seriesIdentityRef = useRef<ResolvedSeries[]>([]);
@@ -327,6 +336,7 @@ function ThemedCompositeChart({
327
336
  availableAxisWidth,
328
337
  Math.max(
329
338
  Math.min(MINIMUM_AXIS_LABEL_WIDTH, maximumAxisWidth),
339
+ ...(levels?.items ?? []).map((level) => level.axisLabel?.length ?? 0),
330
340
  ...(layoutPanels ?? []).flatMap((panel) => (["left", "right"] as const).map((side) => {
331
341
  const width = compositeAxisLabelWidth(panel.axes[side], formatAxisValue, includeCursorLabels);
332
342
  if (!includeSeriesTags || !width) return includeCursorLabels ? width : Math.min(width, maximumAxisWidth);
@@ -337,7 +347,7 @@ function ThemedCompositeChart({
337
347
  return (width + 1) * lanes;
338
348
  })),
339
349
  ),
340
- ), [availableAxisWidth, formatAxisValue, includeCursorLabels, includeSeriesTags, layoutPanels, maximumAxisWidth]);
350
+ ), [availableAxisWidth, formatAxisValue, includeCursorLabels, includeSeriesTags, layoutPanels, maximumAxisWidth, levels?.items]);
341
351
  const leftAxisWidth = hasLeftAxis ? resolvedAxisWidth : 0;
342
352
  const rightAxisWidth = hasRightAxis ? resolvedAxisWidth : 0;
343
353
  const axisGap = resolvedAxisWidth > 0 ? 1 : 0;
@@ -417,18 +427,18 @@ function ThemedCompositeChart({
417
427
  const nextTimestamp = timestamp !== null && Number.isFinite(timestamp) ? timestamp : null;
418
428
  if (lastCursorTimestampRef.current === nextTimestamp) return;
419
429
  lastCursorTimestampRef.current = nextTimestamp;
420
- if (cursorDate === undefined) setInternalCursorDate(date);
430
+ if (ownsCursor) setInternalCursorDate(date);
421
431
  onCursorDateChange?.(date);
422
432
  publishCursor?.(date && baseScene && nextTimestamp !== null ? compositeCursorAsOf(baseScene, date) : null);
423
- }, [baseScene, cursorDate, onCursorDateChange, publishCursor]);
433
+ }, [baseScene, onCursorDateChange, ownsCursor, publishCursor]);
424
434
  // Another chart taking the cursor, or clearing it, takes it from this one,
425
435
  // so it does not come back here later. Read live: a move since the render
426
436
  // makes this chart's cursor the shared one again.
427
437
  useEffect(() => {
428
438
  void holdsSharedCursor;
429
- if (!link || cursorDate !== undefined || lastCursorTimestampRef.current === null || link.held()) return;
439
+ if (!link || !ownsCursor || lastCursorTimestampRef.current === null || link.held()) return;
430
440
  updateCursor(null);
431
- }, [cursorDate, holdsSharedCursor, link, updateCursor]);
441
+ }, [holdsSharedCursor, link, ownsCursor, updateCursor]);
432
442
  // Esc on a chart showing another chart's cursor clears it, as on that chart.
433
443
  // While the tab's cursor is pinned the arrows move the pin and Esc unpins it.
434
444
  const updateKeyboardCursor = useCallback((date: Date | null) => {
@@ -644,7 +654,11 @@ function ThemedCompositeChart({
644
654
  );
645
655
  }
646
656
 
647
- const showToolbar = interactive && navigable && plotWidth > CHART_TOOLBAR_WIDTH + 4;
657
+ // The tools belong to the focused pane, and stay while one is in hand or a
658
+ // press drags the plot, so focus moving elsewhere never hides what is
659
+ // drawing. A chart outside a pane (a share page) keeps them.
660
+ const toolbarWanted = paneFocused !== false || armedTool !== null || pointerDragging || toolSpan !== null;
661
+ const showToolbar = interactive && navigable && toolbarWanted && plotWidth > CHART_TOOLBAR_WIDTH + 4;
648
662
  // Bars shorter than a day carry a time of day the cursor reads out.
649
663
  const intradayBars = scene.timeZone !== undefined || visibleSeries.some((entry) => (
650
664
  !!entry.historyResolution && isIntradayResolution(entry.historyResolution)
@@ -766,6 +780,7 @@ function ThemedCompositeChart({
766
780
  onZoomViewport={zoomViewport}
767
781
  onSetViewport={setViewportRange}
768
782
  onToolSpanChange={setToolSpan}
783
+ onDragActiveChange={setPointerDragging}
769
784
  keyboardToolDrag={keyboardToolDrag?.panelId === panel.id ? keyboardToolDrag : null}
770
785
  onPointerPress={cancelKeyboardPlacement}
771
786
  showTextFallback={showTextFallback}