gloomberb 0.16.4 → 0.16.5

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 (554) hide show
  1. package/README.md +14 -91
  2. package/package.json +4 -1
  3. package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
  4. package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
  5. package/src/account/billing.ts +57 -0
  6. package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
  7. package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
  8. package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
  9. package/src/account/session.ts +16 -0
  10. package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
  11. package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
  12. package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
  13. package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
  14. package/src/api-client/data.ts +24 -2
  15. package/src/api-client/earnings.ts +6 -0
  16. package/src/api-client/futures-curve.ts +56 -0
  17. package/src/api-client/index.ts +4 -1
  18. package/src/api-client/market-discovery.ts +10 -1
  19. package/src/api-client/paths.ts +7 -0
  20. package/src/api-client/research-activity.ts +15 -10
  21. package/src/api-client/types.ts +17 -1
  22. package/src/api-client/yield-curves.ts +8 -0
  23. package/src/app/global-shortcuts.ts +15 -1
  24. package/src/app/pane-runtime/ticker-open-runtime.ts +7 -3
  25. package/src/architecture/core-builtin-allowlist.json +4 -13
  26. package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
  27. package/src/capabilities/chart-series.ts +11 -1
  28. package/src/cli/commands/crypto-hints.ts +7 -0
  29. package/src/cli/commands/fred-series.ts +61 -0
  30. package/src/cli/commands/market.ts +407 -104
  31. package/src/cli/commands/news-hints.ts +62 -0
  32. package/src/cli/commands/news-search.ts +53 -0
  33. package/src/cli/commands/options-leaps.ts +5 -2
  34. package/src/cli/commands/overview.ts +93 -20
  35. package/src/cli/commands/remote.ts +2 -1
  36. package/src/cli/commands/search.ts +82 -9
  37. package/src/cli/commands/system.ts +55 -8
  38. package/src/cli/commands/ticker.ts +88 -11
  39. package/src/cli/context.ts +45 -14
  40. package/src/cli/desktop-pane-shot.ts +59 -7
  41. package/src/cli/earnings-calendar.ts +71 -0
  42. package/src/cli/freshness.ts +18 -7
  43. package/src/cli/glossary.ts +429 -0
  44. package/src/cli/history-rows.ts +48 -7
  45. package/src/cli/index-roots.ts +7 -0
  46. package/src/cli/index.ts +21 -7
  47. package/src/cli/known-gaps.ts +27 -0
  48. package/src/cli/listing-arg.ts +417 -56
  49. package/src/cli/not-a-ticker.ts +12 -5
  50. package/src/cli/pane-functions/access-gate.ts +5 -0
  51. package/src/cli/pane-functions/capabilities.ts +3 -2
  52. package/src/cli/pane-functions/catalog.ts +35 -5
  53. package/src/cli/pane-functions/data.ts +12 -8
  54. package/src/cli/pane-functions/discovery.ts +1 -0
  55. package/src/cli/pane-functions/dom.ts +20 -6
  56. package/src/cli/pane-functions/freshness.ts +363 -43
  57. package/src/cli/pane-functions/function-help.ts +12 -5
  58. package/src/cli/pane-functions/headless.ts +45 -17
  59. package/src/cli/pane-functions/index.ts +63 -10
  60. package/src/cli/pane-functions/market-session.ts +22 -8
  61. package/src/cli/pane-functions/options.ts +63 -22
  62. package/src/cli/pane-functions/report-notices.ts +25 -7
  63. package/src/cli/pane-functions/report.ts +15 -8
  64. package/src/cli/pane-functions/resolver.ts +49 -12
  65. package/src/cli/pane-functions/screenshot.ts +110 -23
  66. package/src/cli/report-tables.ts +3 -0
  67. package/src/cli/result.ts +4 -1
  68. package/src/cli/staged-tickers.ts +52 -0
  69. package/src/cli/symbol-args.ts +9 -0
  70. package/src/cli/symbol-list.ts +172 -0
  71. package/src/cli/ticker-resolution.ts +223 -30
  72. package/src/cli/types.ts +5 -0
  73. package/src/components/chart/composite/axis-overlays.tsx +6 -2
  74. package/src/components/chart/composite/composite-chart.tsx +40 -6
  75. package/src/components/chart/composite/legend.tsx +49 -2
  76. package/src/components/chart/composite/panel-surface.tsx +44 -17
  77. package/src/components/chart/composite/price-axis-labels.tsx +3 -1
  78. package/src/components/command-bar/panel/index.tsx +11 -3
  79. package/src/components/command-bar/panel/runtime.ts +3 -0
  80. package/src/components/command-bar/panel/types.ts +2 -0
  81. package/src/components/command-bar/routes/root/provider-search.ts +4 -3
  82. package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
  83. package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
  84. package/src/components/command-bar/workflow/tickers.ts +23 -4
  85. package/src/components/layout/detached-pane-shell.tsx +9 -0
  86. package/src/components/layout/floating-pane.tsx +3 -0
  87. package/src/components/layout/header.tsx +20 -5
  88. package/src/components/layout/pane/content.tsx +7 -13
  89. package/src/components/layout/pane/footer/index.tsx +6 -2
  90. package/src/components/layout/pane/footer/model.ts +35 -1
  91. package/src/components/layout/pane/header.tsx +24 -1
  92. package/src/components/layout/pane/index.tsx +12 -2
  93. package/src/components/layout/pane/sidebar.tsx +78 -10
  94. package/src/components/layout/shell/active-drag.ts +21 -5
  95. package/src/components/layout/shell/drag/index.ts +39 -9
  96. package/src/components/layout/shell/drag/live.ts +61 -51
  97. package/src/components/layout/shell/drag/runtime.ts +54 -12
  98. package/src/components/layout/shell/index.tsx +25 -7
  99. package/src/components/layout/shell/menu.ts +5 -0
  100. package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
  101. package/src/components/layout/shell/pane/layers.tsx +192 -45
  102. package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
  103. package/src/components/markdown-text.tsx +62 -20
  104. package/src/components/metric-treemap/heat-surface.tsx +10 -1
  105. package/src/components/onboarding/account-step/form-panel.tsx +1 -1
  106. package/src/components/onboarding/account-step/index.tsx +1 -1
  107. package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
  108. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  109. package/src/components/onboarding/onboarding-keyboard.ts +1 -1
  110. package/src/components/onboarding/onboarding-wizard.tsx +2 -2
  111. package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
  112. package/src/components/onboarding/wizard-account.ts +2 -2
  113. package/src/components/pane-template-wizard.tsx +9 -1
  114. package/src/components/price-selector-dialog.tsx +11 -2
  115. package/src/components/sign-in-gate.tsx +3 -3
  116. package/src/components/ticker/list-table-view.tsx +24 -1
  117. package/src/components/ui/action-row.tsx +2 -1
  118. package/src/components/ui/button.tsx +7 -1
  119. package/src/components/ui/choice-dialog.tsx +6 -4
  120. package/src/components/ui/confirm-dialog.tsx +28 -18
  121. package/src/components/ui/data-table/index.tsx +17 -6
  122. package/src/components/ui/data-table/opentui/index.tsx +37 -6
  123. package/src/components/ui/data-table/types.ts +12 -0
  124. package/src/components/ui/dialog-actions.tsx +64 -0
  125. package/src/components/ui/display.tsx +17 -3
  126. package/src/components/ui/frame.tsx +35 -2
  127. package/src/components/ui/icon.tsx +2 -0
  128. package/src/components/ui/text-prompt-dialog.tsx +11 -9
  129. package/src/data/cached-query.ts +2 -0
  130. package/src/data/config/store/normalize.ts +7 -2
  131. package/src/data/ticker-repository.ts +5 -0
  132. package/src/data/ticker-store.ts +15 -0
  133. package/src/docs/content.ts +215 -0
  134. package/src/docs/load.ts +139 -0
  135. package/src/docs/requests.ts +39 -0
  136. package/src/docs/snapshot.chunk.ts +8 -0
  137. package/src/docs/snapshot.json +7416 -0
  138. package/src/docs/topics.ts +216 -0
  139. package/src/i18n/es.ts +61 -4
  140. package/src/i18n/ja.ts +61 -4
  141. package/src/i18n/ko.ts +61 -4
  142. package/src/i18n/zh-cn.ts +61 -4
  143. package/src/i18n/zh-tw.ts +61 -4
  144. package/src/layout/desks.ts +1 -1
  145. package/src/layout/pane-manager/dock-tree.ts +18 -0
  146. package/src/layout/pane-manager/index.ts +1 -0
  147. package/src/market-data/coordinator/chart.ts +10 -4
  148. package/src/market-data/coordinator/index.ts +6 -3
  149. package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
  150. package/src/market-data/futures-contract.ts +151 -0
  151. package/src/market-data/hooks.tsx +10 -2
  152. package/src/market-data/market/format.ts +39 -21
  153. package/src/market-data/market/freshness.ts +81 -18
  154. package/src/market-data/market/status.ts +19 -23
  155. package/src/market-data/published-apac-sessions.ts +23 -1
  156. package/src/market-data/quotes/contributions.ts +3 -0
  157. package/src/market-data/quotes/metadata.ts +7 -24
  158. package/src/market-data/quotes/resolution.ts +2 -0
  159. package/src/market-data/quotes/screener-live-quotes.ts +5 -4
  160. package/src/market-data/result-types.ts +2 -0
  161. package/src/plugins/builtin/account-management/model.ts +1 -56
  162. package/src/plugins/builtin/account-management/pane.tsx +6 -4
  163. package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
  164. package/src/plugins/builtin/alerts/cli.ts +313 -47
  165. package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
  166. package/src/plugins/builtin/alerts/events.ts +23 -2
  167. package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
  168. package/src/plugins/builtin/alerts/history.ts +2 -0
  169. package/src/plugins/builtin/alerts/index.tsx +41 -9
  170. package/src/plugins/builtin/alerts/pane.tsx +8 -0
  171. package/src/plugins/builtin/alerts/research-builder.ts +8 -3
  172. package/src/plugins/builtin/alerts/research-rules.ts +18 -2
  173. package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
  174. package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
  175. package/src/plugins/builtin/alerts/scheduled.ts +254 -0
  176. package/src/plugins/builtin/alerts/targets.ts +92 -0
  177. package/src/plugins/builtin/analytics/index.tsx +4 -2
  178. package/src/plugins/builtin/analytics/risk-headless.ts +30 -12
  179. package/src/plugins/builtin/analytics/risk-math.ts +91 -27
  180. package/src/plugins/builtin/analytics/risk-model.ts +35 -5
  181. package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
  182. package/src/plugins/builtin/attention/index.tsx +2 -0
  183. package/src/plugins/builtin/attention/pane.tsx +3 -1
  184. package/src/plugins/builtin/attention/series.ts +26 -0
  185. package/src/plugins/builtin/awards/index.tsx +2 -0
  186. package/src/plugins/builtin/awards/pane.tsx +3 -1
  187. package/src/plugins/builtin/awards/series.ts +29 -0
  188. package/src/plugins/builtin/bond-calculator/model.ts +2 -1
  189. package/src/plugins/builtin/catalysts/pane.tsx +3 -1
  190. package/src/plugins/builtin/cds/client.ts +70 -13
  191. package/src/plugins/builtin/cds/headless.ts +116 -19
  192. package/src/plugins/builtin/cds/index.tsx +3 -1
  193. package/src/plugins/builtin/cds/model.ts +193 -31
  194. package/src/plugins/builtin/cds/pane.tsx +56 -14
  195. package/src/plugins/builtin/cds/series.ts +19 -0
  196. package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
  197. package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
  198. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
  199. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
  200. package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
  201. package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
  202. package/src/plugins/builtin/chart-composer/headless.ts +8 -3
  203. package/src/plugins/builtin/chart-composer/index.tsx +9 -2
  204. package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
  205. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
  206. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
  207. package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
  208. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
  209. package/src/plugins/builtin/chat/controller/index.ts +2 -0
  210. package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
  211. package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
  212. package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
  213. package/src/plugins/builtin/cloud/askg/model.ts +27 -8
  214. package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
  215. package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
  216. package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
  217. package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
  218. package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
  219. package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
  220. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  221. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
  222. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
  223. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
  224. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
  225. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
  226. package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
  227. package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
  228. package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
  229. package/src/plugins/builtin/company-attention/headless.ts +19 -5
  230. package/src/plugins/builtin/company-attention/index.tsx +2 -0
  231. package/src/plugins/builtin/company-attention/model.ts +26 -7
  232. package/src/plugins/builtin/company-attention/pane.tsx +12 -9
  233. package/src/plugins/builtin/company-attention/series.ts +52 -0
  234. package/src/plugins/builtin/company-kpis/pane.tsx +4 -2
  235. package/src/plugins/builtin/composite-plugins.ts +19 -13
  236. package/src/plugins/builtin/cot/index.tsx +3 -1
  237. package/src/plugins/builtin/cot/model.ts +40 -11
  238. package/src/plugins/builtin/cot/series.ts +27 -0
  239. package/src/plugins/builtin/credit-boards/index.tsx +2 -0
  240. package/src/plugins/builtin/credit-boards/series.ts +66 -0
  241. package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
  242. package/src/plugins/builtin/credit-conditions/series.ts +26 -0
  243. package/src/plugins/builtin/credit-documents/headless.ts +31 -1
  244. package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
  245. package/src/plugins/builtin/crypto-board/headless.ts +2 -3
  246. package/src/plugins/builtin/crypto-board/index.tsx +2 -0
  247. package/src/plugins/builtin/crypto-board/model.ts +12 -0
  248. package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
  249. package/src/plugins/builtin/crypto-board/series.ts +48 -0
  250. package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
  251. package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
  252. package/src/plugins/builtin/debt-maturities/series.ts +45 -0
  253. package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
  254. package/src/plugins/builtin/dividend-yield/index.tsx +2 -0
  255. package/src/plugins/builtin/dividend-yield/series.ts +20 -0
  256. package/src/plugins/builtin/docs/blocks.tsx +181 -0
  257. package/src/plugins/builtin/docs/index.tsx +68 -0
  258. package/src/plugins/builtin/docs/pane.tsx +426 -0
  259. package/src/plugins/builtin/docs/targets.ts +72 -0
  260. package/src/plugins/builtin/docs/use-docs.ts +60 -0
  261. package/src/plugins/builtin/doe/index.tsx +2 -0
  262. package/src/plugins/builtin/doe/series.ts +62 -0
  263. package/src/plugins/builtin/earnings/client.ts +3 -2
  264. package/src/plugins/builtin/earnings/format.ts +1 -5
  265. package/src/plugins/builtin/earnings/headless.ts +58 -6
  266. package/src/plugins/builtin/earnings/history-model.ts +15 -1
  267. package/src/plugins/builtin/earnings/history-table.ts +13 -2
  268. package/src/plugins/builtin/earnings/history.tsx +7 -3
  269. package/src/plugins/builtin/econ/fred-catalog.ts +189 -0
  270. package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
  271. package/src/plugins/builtin/econ/index.tsx +2 -0
  272. package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
  273. package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
  274. package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
  276. package/src/plugins/builtin/exposure/holdings.ts +1 -1
  277. package/src/plugins/builtin/exposure/pane.tsx +3 -1
  278. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  279. package/src/plugins/builtin/fear-greed/series.ts +52 -0
  280. package/src/plugins/builtin/futures/index.tsx +43 -3
  281. package/src/plugins/builtin/futures/model.ts +1 -1
  282. package/src/plugins/builtin/futures/returns.ts +1 -1
  283. package/src/plugins/builtin/futures/series.ts +13 -0
  284. package/src/plugins/builtin/futures/table.tsx +1 -1
  285. package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
  286. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  287. package/src/plugins/builtin/futures-curve/headless.ts +70 -9
  288. package/src/plugins/builtin/futures-curve/index.tsx +1 -1
  289. package/src/plugins/builtin/futures-curve/model.ts +66 -11
  290. package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
  291. package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
  292. package/src/plugins/builtin/gpu/board.tsx +13 -10
  293. package/src/plugins/builtin/gpu/changes.tsx +4 -4
  294. package/src/plugins/builtin/gpu/client.ts +19 -1
  295. package/src/plugins/builtin/gpu/equities.tsx +14 -10
  296. package/src/plugins/builtin/gpu/headless.ts +56 -25
  297. package/src/plugins/builtin/gpu/history.tsx +3 -3
  298. package/src/plugins/builtin/gpu/index.tsx +2 -0
  299. package/src/plugins/builtin/gpu/model.ts +101 -16
  300. package/src/plugins/builtin/gpu/pane.tsx +28 -12
  301. package/src/plugins/builtin/gpu/series.ts +38 -0
  302. package/src/plugins/builtin/help/function-card.tsx +25 -6
  303. package/src/plugins/builtin/help/index.tsx +17 -8
  304. package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
  305. package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
  306. package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
  307. package/src/plugins/builtin/holders/client.ts +22 -3
  308. package/src/plugins/builtin/holders/format.ts +18 -3
  309. package/src/plugins/builtin/holders/headless.ts +36 -133
  310. package/src/plugins/builtin/holders/pane.tsx +11 -4
  311. package/src/plugins/builtin/holders/report-header.ts +40 -5
  312. package/src/plugins/builtin/holders/report-rows.ts +240 -0
  313. package/src/plugins/builtin/insider/client.ts +35 -20
  314. package/src/plugins/builtin/insider/headless.ts +41 -11
  315. package/src/plugins/builtin/insider/index.tsx +26 -9
  316. package/src/plugins/builtin/insider/insider-data.ts +10 -0
  317. package/src/plugins/builtin/insider/issuer.ts +49 -0
  318. package/src/plugins/builtin/insider/model.ts +3 -0
  319. package/src/plugins/builtin/iv-history/client.ts +14 -4
  320. package/src/plugins/builtin/iv-history/headless.ts +7 -4
  321. package/src/plugins/builtin/iv-history/index.tsx +2 -0
  322. package/src/plugins/builtin/iv-history/model.ts +111 -7
  323. package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
  324. package/src/plugins/builtin/iv-history/series.ts +29 -0
  325. package/src/plugins/builtin/jobs/headless.ts +28 -3
  326. package/src/plugins/builtin/jobs/index.tsx +3 -1
  327. package/src/plugins/builtin/jobs/series.ts +19 -0
  328. package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
  329. package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
  330. package/src/plugins/builtin/macro-day/client.ts +44 -3
  331. package/src/plugins/builtin/macro-day/headless.ts +13 -10
  332. package/src/plugins/builtin/macro-day/model.ts +56 -10
  333. package/src/plugins/builtin/macro-day/pane.tsx +30 -20
  334. package/src/plugins/builtin/market-heatmap/model.ts +19 -11
  335. package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
  336. package/src/plugins/builtin/market-movers/client.ts +7 -5
  337. package/src/plugins/builtin/market-movers/index.tsx +3 -2
  338. package/src/plugins/builtin/market-movers/model.ts +30 -0
  339. package/src/plugins/builtin/market-movers/screener.ts +31 -0
  340. package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
  341. package/src/plugins/builtin/market-valuation/index.tsx +3 -3
  342. package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
  343. package/src/plugins/builtin/members/headless.ts +7 -2
  344. package/src/plugins/builtin/members/model.ts +31 -3
  345. package/src/plugins/builtin/members/pane.tsx +3 -6
  346. package/src/plugins/builtin/mna/headless.ts +15 -3
  347. package/src/plugins/builtin/money-markets/index.tsx +2 -0
  348. package/src/plugins/builtin/money-markets/series.ts +41 -0
  349. package/src/plugins/builtin/notes/cli.ts +12 -7
  350. package/src/plugins/builtin/options/view.tsx +1 -1
  351. package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
  352. package/src/plugins/builtin/options-positioning/client.ts +7 -3
  353. package/src/plugins/builtin/options-positioning/headless.ts +1 -0
  354. package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
  355. package/src/plugins/builtin/options-scenario/client.ts +2 -1
  356. package/src/plugins/builtin/options-scenario/headless.ts +1 -0
  357. package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
  358. package/src/plugins/builtin/perps/client.ts +40 -6
  359. package/src/plugins/builtin/perps/equity-row.tsx +33 -11
  360. package/src/plugins/builtin/perps/freshness.ts +66 -0
  361. package/src/plugins/builtin/perps/headless.ts +45 -30
  362. package/src/plugins/builtin/perps/index.tsx +18 -1
  363. package/src/plugins/builtin/perps/model.ts +20 -5
  364. package/src/plugins/builtin/perps/pane.tsx +76 -20
  365. package/src/plugins/builtin/perps/series.ts +76 -0
  366. package/src/plugins/builtin/plugin-marketplace/pane.tsx +67 -61
  367. package/src/plugins/builtin/plugin-module.ts +3 -0
  368. package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
  369. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
  370. package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
  371. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +331 -65
  372. package/src/plugins/builtin/portfolio-list/cli/render.ts +200 -31
  373. package/src/plugins/builtin/portfolio-list/cli/shared.ts +24 -0
  374. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
  375. package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
  376. package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
  377. package/src/plugins/builtin/portfolio-list/headless.ts +34 -4
  378. package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
  379. package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
  380. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
  381. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
  382. package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
  383. package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
  384. package/src/plugins/builtin/power/client.ts +16 -0
  385. package/src/plugins/builtin/power/evidence.ts +6 -2
  386. package/src/plugins/builtin/power/headless.ts +71 -7
  387. package/src/plugins/builtin/power/index.tsx +4 -1
  388. package/src/plugins/builtin/power/model.ts +72 -1
  389. package/src/plugins/builtin/power/pane.tsx +32 -9
  390. package/src/plugins/builtin/rate-path/headless.ts +11 -3
  391. package/src/plugins/builtin/realized-vol/headless.ts +5 -0
  392. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
  393. package/src/plugins/builtin/research/event-model.ts +68 -24
  394. package/src/plugins/builtin/research/events-headless.ts +2 -1
  395. package/src/plugins/builtin/research/headless.ts +2 -1
  396. package/src/plugins/builtin/seasonality/client.ts +15 -7
  397. package/src/plugins/builtin/seasonality/headless.ts +36 -12
  398. package/src/plugins/builtin/seasonality/index.tsx +2 -2
  399. package/src/plugins/builtin/seasonality/model.ts +119 -0
  400. package/src/plugins/builtin/seasonality/pane.tsx +108 -37
  401. package/src/plugins/builtin/series-inventory.ts +91 -0
  402. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
  403. package/src/plugins/builtin/shared/command-tab.ts +30 -12
  404. package/src/plugins/builtin/shared/earnings-report.ts +55 -0
  405. package/src/plugins/builtin/shared/fred-points.ts +20 -0
  406. package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
  407. package/src/plugins/builtin/shared/open-description.ts +29 -0
  408. package/src/plugins/builtin/shared/report-freshness.ts +9 -0
  409. package/src/plugins/builtin/shared/series-warning.ts +5 -0
  410. package/src/plugins/builtin/short-interest/headless.ts +1 -0
  411. package/src/plugins/builtin/short-interest/index.tsx +2 -0
  412. package/src/plugins/builtin/short-interest/series.ts +36 -0
  413. package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
  414. package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
  415. package/src/plugins/builtin/short-volume/headless.ts +1 -0
  416. package/src/plugins/builtin/short-volume/index.tsx +2 -0
  417. package/src/plugins/builtin/short-volume/series.ts +23 -0
  418. package/src/plugins/builtin/social-mentions/index.tsx +2 -0
  419. package/src/plugins/builtin/social-mentions/series.ts +23 -0
  420. package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
  421. package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
  422. package/src/plugins/builtin/thirteenf/api.ts +1 -0
  423. package/src/plugins/builtin/thirteenf/data.ts +6 -4
  424. package/src/plugins/builtin/thirteenf/headless.ts +217 -36
  425. package/src/plugins/builtin/thirteenf/model.ts +39 -1
  426. package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
  427. package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
  428. package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
  429. package/src/plugins/builtin/thirteenf/types.ts +2 -0
  430. package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
  431. package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
  432. package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
  433. package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
  434. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
  435. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
  436. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
  437. package/src/plugins/builtin/time-sales/headless.ts +1 -0
  438. package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
  439. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  440. package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
  441. package/src/plugins/builtin/vol-surface/headless.ts +1 -0
  442. package/src/plugins/builtin/volatility/index.tsx +2 -0
  443. package/src/plugins/builtin/volatility/series.ts +84 -0
  444. package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
  445. package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
  446. package/src/plugins/builtin/world-venue-map/index.ts +2 -1
  447. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
  448. package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
  449. package/src/plugins/builtin/yield-curve/chart.ts +2 -2
  450. package/src/plugins/builtin/yield-curve/curves.ts +21 -8
  451. package/src/plugins/builtin/yield-curve/headless.ts +71 -18
  452. package/src/plugins/builtin/yield-curve/index.tsx +3 -1
  453. package/src/plugins/builtin/yield-curve/series.ts +86 -0
  454. package/src/plugins/catalog-browser.ts +2 -1
  455. package/src/plugins/registry/index.ts +89 -4
  456. package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
  457. package/src/portfolio/position-value.ts +40 -0
  458. package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
  459. package/src/public/time-series.ts +17 -0
  460. package/src/react/auto-refresh.ts +13 -3
  461. package/src/remote/app-host.tsx +20 -4
  462. package/src/remote/confirm-change.tsx +85 -0
  463. package/src/remote/controller-utils.ts +8 -1
  464. package/src/remote/controller.ts +123 -6
  465. package/src/remote/pane-screenshot.ts +47 -0
  466. package/src/remote/schema.ts +62 -0
  467. package/src/remote/types.ts +30 -0
  468. package/src/remote/watchlist.ts +306 -0
  469. package/src/renderers/browser/main.tsx +3 -2
  470. package/src/renderers/cloudflare/worker.ts +1 -1
  471. package/src/renderers/dom/data-table/frozen-column.ts +3 -1
  472. package/src/renderers/dom/data-table/index.tsx +33 -6
  473. package/src/renderers/dom/data-table/row.tsx +27 -1
  474. package/src/renderers/dom/desktop/control-styles.ts +63 -1
  475. package/src/renderers/dom/desktop/controls.tsx +30 -23
  476. package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
  477. package/src/renderers/dom/desktop/icons.tsx +7 -0
  478. package/src/renderers/dom/dom-ui-host.tsx +3 -0
  479. package/src/renderers/dom/host/box.tsx +3 -0
  480. package/src/renderers/dom/host/chart-surface.tsx +6 -2
  481. package/src/renderers/dom/host/live-frame.ts +6 -2
  482. package/src/renderers/dom/host/overflow-fade.ts +14 -1
  483. package/src/renderers/dom/host/size-observer.ts +26 -0
  484. package/src/renderers/dom/host/surface-3d.tsx +7 -2
  485. package/src/renderers/dom/host/text.tsx +1 -1
  486. package/src/renderers/dom/styles.css +61 -0
  487. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +17 -2
  488. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +19 -11
  489. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
  490. package/src/renderers/electrobun/view/main.tsx +2 -0
  491. package/src/series/capability.ts +75 -0
  492. package/src/series/csv.ts +160 -0
  493. package/src/series/files.ts +107 -0
  494. package/src/series/registry.ts +198 -0
  495. package/src/series/resolve.ts +107 -0
  496. package/src/series/standalone.ts +48 -0
  497. package/src/series/types.ts +145 -0
  498. package/src/series/validate.ts +171 -0
  499. package/src/sources/gloomberb-cloud/index.ts +7 -7
  500. package/src/sources/gloomberb-cloud/normalizers.ts +1 -0
  501. package/src/sources/history-result.ts +1 -0
  502. package/src/sources/listing-symbols.ts +4 -1
  503. package/src/sources/provider-router/batches.ts +4 -1
  504. package/src/sources/provider-router/cache.ts +4 -2
  505. package/src/sources/provider-router/cached-routes.ts +1 -1
  506. package/src/sources/provider-router/financial-routes.ts +1 -0
  507. package/src/sources/provider-router/financials.ts +13 -0
  508. package/src/sources/provider-router/history.ts +43 -25
  509. package/src/sources/provider-router/search.ts +1 -0
  510. package/src/sync/core-contributors.ts +1 -0
  511. package/src/tickers/company-name.ts +75 -0
  512. package/src/tickers/instrument-kind.ts +25 -0
  513. package/src/tickers/open-target.ts +3 -2
  514. package/src/tickers/search/index.ts +45 -7
  515. package/src/tickers/search/ranking.ts +33 -17
  516. package/src/tickers/search/types.ts +7 -1
  517. package/src/tickers/us-listing.ts +34 -0
  518. package/src/time-series/hooks.ts +9 -0
  519. package/src/time-series/resolve.ts +95 -16
  520. package/src/time-series/types.ts +4 -2
  521. package/src/time-series/use-chart-resolution.ts +32 -0
  522. package/src/types/config.ts +6 -0
  523. package/src/types/data-provider.ts +5 -0
  524. package/src/types/financials.ts +52 -1
  525. package/src/types/headless.ts +61 -0
  526. package/src/types/plugin.ts +19 -0
  527. package/src/types/price-history.ts +7 -0
  528. package/src/types/ticker.ts +14 -0
  529. package/src/ui/host.tsx +18 -0
  530. package/src/ui/live-resize.ts +81 -0
  531. package/src/ui/live-stretch.ts +121 -0
  532. package/src/ui/measured-cells.ts +58 -0
  533. package/src/ui/pane-fill.ts +11 -0
  534. package/src/utils/basis-points.ts +17 -0
  535. package/src/utils/cli-output.ts +7 -1
  536. package/src/utils/concurrency.ts +25 -0
  537. package/src/utils/crypto-pair.ts +20 -0
  538. package/src/utils/currency-pair.ts +19 -0
  539. package/src/utils/decimal-comma.ts +19 -0
  540. package/src/utils/dom-screenshot-css.ts +265 -0
  541. package/src/utils/dom-screenshot-rasters.ts +276 -0
  542. package/src/utils/dom-screenshot.ts +437 -52
  543. package/src/utils/exchange-rate-snapshot.ts +6 -2
  544. package/src/utils/exchanges.ts +107 -1
  545. package/src/utils/format.ts +16 -3
  546. package/src/utils/fundamentals.ts +66 -0
  547. package/src/utils/futures-generic.ts +6 -12
  548. package/src/utils/fx-market-hours.ts +15 -5
  549. package/src/utils/price-history.ts +99 -42
  550. package/src/utils/sec-eps-basis.ts +24 -11
  551. package/src/utils/sec.ts +61 -7
  552. package/src/utils/utc-time.ts +51 -4
  553. package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
  554. /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
@@ -56,7 +56,8 @@ interface HistoryRequestDescriptor {
56
56
  context?: MarketDataRequestContext;
57
57
  cachePolicyKey: PriceHistoryCachePolicyKey;
58
58
  missingProviderError?: string;
59
- isCachedValueStale(value: PriceHistoryResult): boolean;
59
+ /** `fetchedAt` is when the cached copy was fetched. */
60
+ isCachedValueStale(value: PriceHistoryResult, fetchedAt: number): boolean;
60
61
  isFetchedValueStale(value: PriceHistoryResult): boolean;
61
62
  fetchBroker(candidate: BrokerCandidate): Promise<PricePoint[] | null>;
62
63
  fetchProvider(provider: DataProvider): Promise<PriceHistoryResult | null>;
@@ -243,15 +244,20 @@ function mergeRequestHistoryGaps(
243
244
  }
244
245
 
245
246
  function normalizeRequestResult(value: PriceHistoryResult, request: HistoryRequestDescriptor): PriceHistoryResult {
246
- return { ...value, points: normalizeRequestHistory(value.points, request),
247
+ // Only this router says a copy is behind, when it answers: neither a source
248
+ // nor a stored copy can carry that verdict in.
249
+ const { savedCopyBehind: _behind, ...kept } = value;
250
+ return { ...kept, points: normalizeRequestHistory(value.points, request),
247
251
  // A public equity response cannot establish a broker contract's session.
248
252
  ...(request.context?.instrument ? { session: undefined } : {}),
249
253
  };
250
254
  }
251
255
 
252
- function resultIsStale(value: PriceHistoryResult, symbol: string, exchange: string, intervalMs?: number | null): boolean {
256
+ /** A copy fetched at `fetchedAt`, now for a source's answer. */
257
+ function resultIsStale(value: PriceHistoryResult, symbol: string, exchange: string, fetchedAt: number, intervalMs?: number | null): boolean {
253
258
  return isPriceHistoryStaleForCurrentWindow(value.points, Date.now(), {
254
259
  symbol, exchange, intervalMs: intervalMs ?? (value.resolution ? priceHistoryIntervalMs(value.resolution) : undefined), session: value.session,
260
+ fetchedAt,
255
261
  });
256
262
  }
257
263
 
@@ -351,8 +357,8 @@ export class ProviderRouterHistoryRoutes {
351
357
  context,
352
358
  cachePolicyKey: intraday ? "priceHistoryIntraday" : "priceHistoryDaily",
353
359
  missingProviderError: `No history provider available for ${ticker}`,
354
- isCachedValueStale: (value) => intraday && resultIsStale(value, ticker, exchange),
355
- isFetchedValueStale: (value) => intraday && resultIsStale(value, ticker, exchange),
360
+ isCachedValueStale: (value, fetchedAt) => intraday && resultIsStale(value, ticker, exchange, fetchedAt),
361
+ isFetchedValueStale: (value) => intraday && resultIsStale(value, ticker, exchange, Date.now()),
356
362
  fetchBroker: async (candidate) => candidate.broker.getPriceHistory
357
363
  ? candidate.broker.getPriceHistory(
358
364
  ticker,
@@ -408,8 +414,8 @@ export class ProviderRouterHistoryRoutes {
408
414
  context,
409
415
  cachePolicyKey: intraday ? "priceHistoryIntraday" : "priceHistoryDaily",
410
416
  missingProviderError: `No resolution-aware history provider available for ${ticker}`,
411
- isCachedValueStale: (value) => intraday && resultIsStale(value, ticker, exchange, intervalMs),
412
- isFetchedValueStale: (value) => intraday && resultIsStale(value, ticker, exchange, intervalMs),
417
+ isCachedValueStale: (value, fetchedAt) => intraday && resultIsStale(value, ticker, exchange, fetchedAt, intervalMs),
418
+ isFetchedValueStale: (value) => intraday && resultIsStale(value, ticker, exchange, Date.now(), intervalMs),
413
419
  fetchBroker: async (candidate) => candidate.broker.getPriceHistoryForResolution
414
420
  ? candidate.broker.getPriceHistoryForResolution(
415
421
  ticker,
@@ -523,10 +529,10 @@ export class ProviderRouterHistoryRoutes {
523
529
  interval: barSize,
524
530
  context,
525
531
  cachePolicyKey: intervalMs != null && intervalMs >= 24 * 60 * 60 * 1000 ? "priceHistoryDaily" : "priceHistoryIntraday",
526
- isCachedValueStale: (value) => currentWindowAtLookup
527
- && resultIsStale(value, ticker, exchange, intervalMs),
532
+ isCachedValueStale: (value, fetchedAt) => currentWindowAtLookup
533
+ && resultIsStale(value, ticker, exchange, fetchedAt, intervalMs),
528
534
  isFetchedValueStale: (value) => isCurrentHistoryWindow(endDate)
529
- && resultIsStale(value, ticker, exchange, intervalMs),
535
+ && resultIsStale(value, ticker, exchange, Date.now(), intervalMs),
530
536
  fetchBroker: async (candidate) => candidate.broker.getDetailedPriceHistory
531
537
  ? candidate.broker.getDetailedPriceHistory(
532
538
  ticker,
@@ -604,7 +610,7 @@ export class ProviderRouterHistoryRoutes {
604
610
  const withReportedGaps = (value: PriceHistoryResult) => historyCoverage(request).merge(value, reportedGaps);
605
611
  const clip = (value: PriceHistoryResult) => request.requestedRange
606
612
  ? clipHistoryToRange(value, request.requestedRange, request.target.exchange) : value;
607
- const cachedHistoryStale = request.isCachedValueStale(cachedValue);
613
+ const cachedHistoryStale = request.isCachedValueStale(cachedValue, cached?.fetchedAt ?? Number.NEGATIVE_INFINITY);
608
614
  const forceRefresh = request.context?.cacheMode === "refresh";
609
615
  // A recent re-check, even a failed, empty or rejected one, defers the next
610
616
  // one; it does not make the copy current. A broader copy only tells what
@@ -613,8 +619,10 @@ export class ProviderRouterHistoryRoutes {
613
619
  request.exactCacheVariantKeys.includes(cached.variantKey) ? cached.fetchedAt : Number.NEGATIVE_INFINITY)) : "current";
614
620
  const cachedBeforeClose = servedState === "unsettled" || servedState === "recheck";
615
621
  if (cachedBeforeClose) this.markCalendarChecked(request, now);
616
- const usableCached = hasUsablePriceHistory(cachedValue.points) && cached && !cached.expired && !cachedHistoryStale
617
- && !cachedBeforeClose;
622
+ // A copy that a re-check since the close could not replace answers until
623
+ // the next one is due, though it does not hold the whole session.
624
+ const usableCached = hasUsablePriceHistory(cachedValue.points) && cached && !cached.expired
625
+ && (!cachedHistoryStale || servedState === "pending") && !cachedBeforeClose;
618
626
  // A copy behind or before the latest session is re-checked on the paced
619
627
  // schedule above, not on its short TTL: the source is likely to answer
620
628
  // the same. A short-lived process (the CLI) closes its store before a
@@ -627,9 +635,11 @@ export class ProviderRouterHistoryRoutes {
627
635
  if (refreshDue) {
628
636
  scheduleRouterRevalidation(this.historyRefreshInFlight, request.identity.revalidationKey, () => this.refreshHistory(request));
629
637
  }
638
+ // Only a copy a re-check could not replace reaches here short of the latest session.
639
+ const answered = cachedHistoryStale ? { ...cachedValue, savedCopyBehind: true as const } : cachedValue;
630
640
  return exactHit || !request.requestedRange
631
- ? withReportedGaps(cachedValue)
632
- : clip(withReportedGaps(cachedValue));
641
+ ? withReportedGaps(answered)
642
+ : clip(withReportedGaps(answered));
633
643
  }
634
644
 
635
645
  const supersededCacheSources = new Set<string>();
@@ -651,13 +661,21 @@ export class ProviderRouterHistoryRoutes {
651
661
  if (providerResult && hasUsablePriceHistory(providerResult.value.points)) return withReportedGaps(providerResult.value);
652
662
  if (providerResult) reportedGaps.push(providerResult.value);
653
663
  // A failed refetch falls back in the same order the cache hit used, so a
654
- // failing request answers what the next request will.
655
- const fallbackValue = ranked
664
+ // failing request answers what the next request will: a current copy
665
+ // first, else a saved copy short of the latest session, which beats an
666
+ // empty chart. Intraday copies expire two days after they are saved. A
667
+ // coin never closes, so its old copy is not shown: the miss says why.
668
+ const coin = isRoundTheClockCoin(target.symbol, target.exchange || request.target.exchange);
669
+ const fallbackRecords = ranked
656
670
  .map((entry) => entry.record)
657
- .filter((record) => !supersededCacheSources.has(record.sourceKey))
658
- .map((record) => record.value)
659
- .find((value) => !request.isCachedValueStale(value));
660
- if (fallbackValue) {
671
+ .filter((record) => !supersededCacheSources.has(record.sourceKey));
672
+ const fallbackRecord = fallbackRecords.find((record) => !request.isCachedValueStale(record.value, record.fetchedAt))
673
+ ?? (coin ? undefined : fallbackRecords[0]);
674
+ if (fallbackRecord) {
675
+ // A copy short of the latest session says so, so the chart can tell it
676
+ // from a current one: a thin listing's complete copy is not behind.
677
+ const fallbackValue = request.isCachedValueStale(fallbackRecord.value, fallbackRecord.fetchedAt)
678
+ ? { ...fallbackRecord.value, savedCopyBehind: true as const } : fallbackRecord.value;
661
679
  return request.requestedRange
662
680
  ? clip(withReportedGaps(fallbackValue))
663
681
  : withReportedGaps(fallbackValue);
@@ -673,11 +691,11 @@ export class ProviderRouterHistoryRoutes {
673
691
  }
674
692
  if (!providerResult && request.missingProviderError && !reportedGaps.some((value) => value.points.length > 0)) {
675
693
  // A source answered a coin's history but it stops short of the present: say so, not that nothing answered.
676
- const behind = isRoundTheClockCoin(target.symbol, target.exchange || request.target.exchange)
677
- && [...attempts.outcomes.values()].some((outcome) => outcome.outcome === "stale");
694
+ const behind = coin && [...attempts.outcomes.values()].some((outcome) => outcome.outcome === "stale");
678
695
  if (behind) throw new Error(`Latest history for ${target.symbol} is behind`);
679
- // The service has no listing for the symbol: say it is not a ticker, not that no source answered.
680
- throw noProviderError(request.missingProviderError, { notFound: attempts.misses.notFound }, request.target.symbol);
696
+ // A reason the service gave ("ADX listings have daily bars only.") says why; one that has no
697
+ // listing for the symbol says it is not a ticker; otherwise no source answered.
698
+ throw noProviderError(request.missingProviderError, attempts.misses, request.target.symbol);
681
699
  }
682
700
  return withReportedGaps(providerResult?.value ?? { points: [], resolution: historyResolutionForInterval(request.interval) });
683
701
  }
@@ -28,6 +28,7 @@ function buildSearchCacheKey(query: string, context?: SearchRequestContext): str
28
28
  context?.preferBroker ?? true,
29
29
  context?.brokerInstanceId ?? "",
30
30
  context?.brokerId ?? "",
31
+ context?.limit ?? null,
31
32
  ]);
32
33
  }
33
34
 
@@ -107,6 +107,7 @@ function sanitizePortfolio(portfolio: Portfolio): Portfolio {
107
107
  // them would erase them on this device.
108
108
  ...(portfolio.cash ? { cash: { ...portfolio.cash } } : {}),
109
109
  ...(portfolio.targetWeights ? { targetWeights: { ...portfolio.targetWeights } } : {}),
110
+ ...(portfolio.importedFrom ? { importedFrom: { ...portfolio.importedFrom } } : {}),
110
111
  };
111
112
  }
112
113
 
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Company names as venues spell them, compared and shortened for sentences.
3
+ * One company's listings carry its name with different legal forms and share
4
+ * wording ("Microsoft Corporation Common Stock", "Coca-Cola Company CDR"), so
5
+ * they are compared by the words that say whose they are.
6
+ */
7
+
8
+ // Words that differ between one company's listings: legal forms, and how a depositary receipt is named.
9
+ const NAME_NOISE = new Set([
10
+ "co", "company", "corp", "corporation", "inc", "incorporated", "ltd", "limited", "llc", "lp", "plc", "sa", "ag", "nv", "se",
11
+ "spa", "ab", "as", "asa", "oyj", "bv", "gmbh", "kgaa", "bhd", "berhad", "pcl", "the", "adr", "ads", "cedear", "cedears",
12
+ "sponsored", "unsponsored", "cdr", "cdi",
13
+ ]);
14
+ // Words a venue adds to the share it lists ("Microsoft Corporation Common Stock"), never part of whose it is.
15
+ const SHARE_NOISE = new Set(["common", "ordinary", "stock", "shares"]);
16
+
17
+ /** A company's name for comparing its listings: "Kotobuki Spirits Co., Ltd." is "kotobuki spirits". */
18
+ function companyKey(name: string): string {
19
+ return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase()
20
+ .replace(/\([^)]*\)/g, " ")
21
+ .replace(/\b(?:(?:american|global) )?depositary (?:shares?|receipts?)\b/g, " ")
22
+ .replace(/\./g, "")
23
+ .split(/[^\p{L}\p{N}]+/u)
24
+ .filter((word) => word && !NAME_NOISE.has(word))
25
+ .join(" ");
26
+ }
27
+
28
+ function companyWords(name: string | null | undefined): Set<string> {
29
+ return new Set(companyKey(name ?? "").split(" ").filter((word) => word && !SHARE_NOISE.has(word)));
30
+ }
31
+
32
+ /**
33
+ * Whether two names are one company's, however each venue spells it: one
34
+ * name's words are all in the other's, so "SPDR S&P 500 ETF Trust" is State
35
+ * Street's, "Coca-Cola Company CDR" Coca-Cola's, and "CAR Group" not Avis
36
+ * Budget Group. An empty name matches none.
37
+ */
38
+ export function sameCompany(left: string | null | undefined, right: string | null | undefined): boolean {
39
+ const a = companyWords(left);
40
+ const b = companyWords(right);
41
+ if (a.size === 0 || b.size === 0) return false;
42
+ const [fewer, more] = a.size <= b.size ? [a, b] : [b, a];
43
+ return [...fewer].every((word) => more.has(word));
44
+ }
45
+
46
+ /** Items grouped by the company their names are, in the order each company first appears. Unnamed items are left out. */
47
+ export function groupByCompany<T>(items: readonly T[], nameOf: (item: T) => string | null | undefined): T[][] {
48
+ const groups: T[][] = [];
49
+ for (const item of items) {
50
+ if (companyWords(nameOf(item)).size === 0) continue;
51
+ const group = groups.find((members) => members.some((member) => sameCompany(nameOf(member), nameOf(item))));
52
+ if (group) group.push(item);
53
+ else groups.push([item]);
54
+ }
55
+ return groups;
56
+ }
57
+
58
+ /** A company's name without its legal form, for sentences: "Avis Budget Group, Inc." is "Avis Budget Group". */
59
+ export function shortCompanyName(name: string): string {
60
+ const words = name.trim().split(/\s+/).filter(Boolean);
61
+ const legal = (word: string) => {
62
+ const plain = word.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^\p{L}\p{N}]/gu, "");
63
+ // "Public Limited Company" is a legal form too, though "Public" alone can be a name.
64
+ return NAME_NOISE.has(plain) || SHARE_NOISE.has(plain) || (plain === "public" && words.length > 2);
65
+ };
66
+ while (words.length > 1 && legal(words[words.length - 1]!)) words.pop();
67
+ if (words.length > 1 && words[0]!.toLowerCase() === "the") words.shift();
68
+ return words.join(" ").replace(/[,;]+$/, "");
69
+ }
70
+
71
+ /** "CAR Group (ASX)": a listing named by its company without the legal form, for a sentence about the company. */
72
+ export function shortListingTitle(listing: { symbol: string; exchange: string; name: string | null }): string {
73
+ const name = listing.name?.trim() ? shortCompanyName(listing.name) : listing.symbol;
74
+ return listing.exchange ? `${name} (${listing.exchange})` : name;
75
+ }
@@ -130,3 +130,28 @@ export function resolveTickerInstrumentKind(
130
130
  ?? kindFromListing(ticker)
131
131
  ?? "equity";
132
132
  }
133
+
134
+ /**
135
+ * A coin-typed listing named "<asset> (Derivatives)", such as NATGAS-USD
136
+ * "Natural Gas (Derivatives) USD": the price perpetual futures on that asset
137
+ * trade at on crypto venues, around the clock. It is neither the asset's
138
+ * exchange-listed future nor its spot price, though its type reads as a coin.
139
+ */
140
+ export function isCryptoVenuePerpetual(type: string | null | undefined, name: string | null | undefined): boolean {
141
+ return classifyInstrumentType(type) === "crypto" && /\(derivatives\)/i.test(name ?? "");
142
+ }
143
+
144
+ /**
145
+ * A listing's type, in plain words where the provider's type misleads (a
146
+ * perpetual typed as a coin). `detail` says what it is not, for a report line.
147
+ */
148
+ export function instrumentTypeLabel(
149
+ type: string | null | undefined,
150
+ name: string | null | undefined,
151
+ options: { detail?: boolean } = {},
152
+ ): string | undefined {
153
+ if (isCryptoVenuePerpetual(type, name)) {
154
+ return options.detail ? "Perpetual futures price (24/7 on crypto venues; not the listed future, not spot)" : "Perpetual futures price";
155
+ }
156
+ return type?.trim() || undefined;
157
+ }
@@ -11,7 +11,7 @@ import {
11
11
  import { tickerSelectionFromSearchResult } from "./selection";
12
12
  import { normalizeTickerSymbol } from "./search/ranking";
13
13
  import type { TickerOpenTarget } from "./search/types";
14
- import { parsePublicTickerKey } from "../utils/exchanges";
14
+ import { isSharedExchangeCode, parsePublicTickerKey, typedExchangeCode } from "../utils/exchanges";
15
15
  import { getListingSymbol, tickerHasListingSuffix } from "../sources/listing-symbols";
16
16
 
17
17
  export type { TickerOpenTarget } from "./search/types";
@@ -34,7 +34,8 @@ export async function resolveTickerOpenTarget({
34
34
  const symbol = normalizeTickerInput(null, query);
35
35
  if (!symbol) return null;
36
36
  const requested = parsePublicTickerKey(symbol);
37
- const preserveListingKey = !!requested.exchange || tickerHasListingSuffix(symbol);
37
+ // A code that names several venues (7203:TSE) opens the listing search settled on, under its own key.
38
+ const preserveListingKey = (!!requested.exchange && !isSharedExchangeCode(typedExchangeCode(symbol))) || tickerHasListingSuffix(symbol);
38
39
 
39
40
  let resolved: Awaited<ReturnType<typeof resolveTickerSearch>> | null = null;
40
41
  try {
@@ -1,11 +1,12 @@
1
1
  import type { SearchRequestContext, DataProvider } from "../../types/data-provider";
2
2
  import type { InstrumentSearchResult } from "../../types/instrument";
3
3
  import type { TickerRecord } from "../../types/ticker";
4
- import { canonicalExchange, parsePublicTickerKey, publicTickerKey } from "../../utils/exchanges";
4
+ import { canonicalExchange, isSharedExchangeCode, isUsListingExchange, parsePublicTickerKey, publicTickerKey, typedExchangeCode } from "../../utils/exchanges";
5
5
  import { getListingSymbol, tickerHasListingSuffix } from "../../sources/listing-symbols";
6
6
  import { parseOptionSymbol } from "../../utils/options";
7
7
  import { resolveCurrencyUnit } from "../../utils/currency-units";
8
8
  import { assetClassMarketSymbol, parseAssetClassQuery } from "./asset-classes";
9
+ import { bareFuturesCode, futuresContractByCode } from "../../market-data/futures-board";
9
10
  import { searchContractKey, searchInstrumentKey } from "./identity";
10
11
  import { tickerInstrumentLabel } from "../instrument-label";
11
12
  import {
@@ -40,9 +41,21 @@ export {
40
41
  } from "./ranking";
41
42
  export { upsertTickerFromSearchResult } from "./upsert";
42
43
 
44
+ const MAX_NAMED_LISTINGS = 6;
45
+
46
+ /**
47
+ * Search rows a symbol's listings are read from, by the command bar's `SAN:`
48
+ * venue list and the CLI's venue list alike. A keystroke's ten stop short of a
49
+ * symbol several companies use: CAR's ASX line is its eleventh.
50
+ */
51
+ export const LISTING_SEARCH_LIMIT = 30;
52
+
43
53
  export class AmbiguousTickerError extends Error {
44
54
  constructor(readonly query: string, readonly listings: readonly string[], readonly listingNames: Readonly<Record<string, string>> = {}) {
45
- super(`Multiple listings match ${query}. Choose an exchange in search or use ${listings.slice(0, 3).join(", ")}.`);
55
+ // Every listing is a choice: a list cut short could leave out the one meant.
56
+ const more = listings.length - MAX_NAMED_LISTINGS;
57
+ const named = `${listings.slice(0, MAX_NAMED_LISTINGS).join(", ")}${more > 0 ? ` or one of ${more} more in search` : ""}`;
58
+ super(`Multiple listings match ${query}. Choose an exchange in search or use ${named}.`);
46
59
  this.name = "AmbiguousTickerError";
47
60
  }
48
61
  }
@@ -291,7 +304,10 @@ export function resolveTickerSearch(options: ResolveTickerSearchOptions): Promis
291
304
  if (!requested) return Promise.resolve(null);
292
305
  // `NET:` lists NET's venues instead of settling on one.
293
306
  const pickListing = isListingPickerQuery(requested);
294
- const symbol = symbolSearchQuery(requested);
307
+ const typed = symbolSearchQuery(requested);
308
+ // A futures board code no security uses is its future (TTF is TTF=F), unless a saved ticker holds it.
309
+ const future = pickListing || options.tickers.has(typed) ? null : bareFuturesCode(typed);
310
+ const symbol = future?.symbol ?? typed;
295
311
  const literal = resolveTickerSymbol(symbol, options, pickListing ? requested : null);
296
312
  const alternatives = pickListing ? [] : dottedVenueQueries(symbol);
297
313
  // Most input has no venue code to retry: hand back the literal lookup
@@ -346,6 +362,9 @@ async function resolveTickerSymbol(
346
362
  publicTickerKey(item.symbol, listingExchange(item.result!)), item.result!.name,
347
363
  ])));
348
364
  if (pickerQuery && listings.size > 1) throw ambiguity(pickerQuery);
365
+ // A code that names several venues (TSE) the symbol trades on more than one of is a choice.
366
+ if (!saved && isSharedExchangeCode(typedExchangeCode(symbol))
367
+ && new Set(matches.map((item) => canonicalExchange(listingExchange(item.result!)))).size > 1) throw ambiguity(symbol);
349
368
  if (saved) return saved;
350
369
  if (listings.size > 1 && !parsePublicTickerKey(symbol).exchange && !tickerHasListingSuffix(symbol)) {
351
370
  // Search order is relevance, not a canonical listing identifier. Align bare
@@ -360,14 +379,14 @@ async function resolveTickerSymbol(
360
379
  const currency = resolveCurrencyUnit(quote.currency).currency;
361
380
  if (exchange && currency && Number.isFinite(quote.price) && quote.price !== 0
362
381
  && Number.isFinite(quote.lastUpdated) && quote.lastUpdated > 0) {
363
- verified = matches.filter((item) => {
382
+ const sameSecurity = matches.filter((item) => {
364
383
  const result = item.result!;
365
384
  const candidateCurrency = resolveCurrencyUnit(result.currency).currency;
366
- return canonicalExchange(listingExchange(result)) === exchange
367
- && (!candidateCurrency || candidateCurrency === currency)
385
+ return (!candidateCurrency || candidateCurrency === currency)
368
386
  && findExactTickerSearchMatch([{ label: quote.symbol, right: exchange, instrumentType: quote.instrumentType }], item.symbol)
369
387
  && (!isCryptoInstrumentType(result.type) || quote.price > 0);
370
- }).map((item) => ({ ...item, result: { ...item.result!, currency } }));
388
+ });
389
+ verified = quotedListings(sameSecurity, exchange).map((item) => ({ ...item, result: { ...item.result!, currency } }));
371
390
  }
372
391
  } catch {
373
392
  // An unavailable quote cannot establish the default listing.
@@ -376,6 +395,8 @@ async function resolveTickerSymbol(
376
395
  throw ambiguity(symbol);
377
396
  }
378
397
  exactMatch = verified[0]!;
398
+ const chosen = publicTickerKey(exactMatch.symbol, listingExchange(exactMatch.result!));
399
+ return { kind: "provider", symbol: exactMatch.symbol, result: exactMatch.result!, otherListings: [...listings].filter((key) => key !== chosen) };
379
400
  }
380
401
 
381
402
  return {
@@ -385,6 +406,19 @@ async function resolveTickerSymbol(
385
406
  };
386
407
  }
387
408
 
409
+ /**
410
+ * The listings a bare symbol's own quote is on: those on the venue it names.
411
+ * The US exchanges share one symbol namespace, so a quote on any of them is
412
+ * the symbol's one US listing, whichever venue the feed labels it with (ET's
413
+ * NYSE line comes back as NYSE American).
414
+ */
415
+ function quotedListings(items: TickerSearchCandidate[], exchange: string): TickerSearchCandidate[] {
416
+ const venue = (item: TickerSearchCandidate) => canonicalExchange(listingExchange(item.result!));
417
+ const named = items.filter((item) => venue(item) === exchange);
418
+ if (named.length > 0 || !isUsListingExchange(exchange)) return named;
419
+ return items.filter((item) => isUsListingExchange(venue(item)));
420
+ }
421
+
388
422
  function listingExchange(result: InstrumentSearchResult): string {
389
423
  return result.exchange === "SMART" ? result.primaryExchange || result.exchange
390
424
  : result.exchange || result.primaryExchange || "";
@@ -455,6 +489,10 @@ async function searchProviderResults(
455
489
  // number of spellings tried.
456
490
  const searchQueries = buildProviderSearchQueries(query);
457
491
  if (marketSymbol && !searchQueries.includes(marketSymbol)) searchQueries.push(marketSymbol);
492
+ // A futures board code also finds its future, which the catalogue files under
493
+ // the continuous symbol only: NG finds NovaGold and NG=F.
494
+ const boardFuture = assetClass ? null : futuresContractByCode(query)?.symbol;
495
+ if (boardFuture && !searchQueries.includes(boardFuture)) searchQueries.push(boardFuture);
458
496
  await Promise.all(searchQueries.map(async (searchQuery) => {
459
497
  try {
460
498
  const results = await dataProvider.search(searchQuery, {
@@ -3,8 +3,10 @@ import type {
3
3
  TickerSearchRankableItem,
4
4
  } from "./types";
5
5
  import { getListingSymbol, tickerHasListingSuffix } from "../../sources/listing-symbols";
6
- import { canonicalExchange, parsePublicTickerKey } from "../../utils/exchanges";
6
+ import { currencyPairCode } from "../../utils/currency-pair";
7
+ import { canonicalExchange, exchangeCodeVenues, parsePublicTickerKey, typedExchangeCode } from "../../utils/exchanges";
7
8
  import { assetClassKeeps, assetClassMarketSymbol, instrumentClassCode, parseAssetClassQuery } from "./asset-classes";
9
+ import { futuresContractByCode } from "../../market-data/futures-board";
8
10
 
9
11
  const FUND_TYPES = new Set(["ETF", "ETN", "ETP", "FUND", "MUTUALFUND", "CEF", "CLOSEDEND"]);
10
12
  const DERIVATIVE_TYPES = new Set(["OPT", "OPTION", "OPTIONS", "FUT", "FUTURE", "FUTURES", "WARRANT", "WARRANTS", "RIGHT", "RIGHTS"]);
@@ -28,15 +30,15 @@ const COMPANY_NAME_SUFFIXES = new Set([
28
30
  "SE",
29
31
  ]);
30
32
 
31
- const EXCHANGE_HINT_ALIASES: Record<string, string[]> = {
33
+ /** Words in a search that say where to look ("toyota tokyo"), each with the venue labels it matches. */
34
+ const VENUE_HINT_ALIASES: Record<string, string[]> = {
32
35
  NASDAQ: ["NASDAQ", "NMS"],
33
36
  NYSE: ["NYSE", "NYSE ARCA", "ARCA"],
34
37
  AMEX: ["AMEX"],
35
38
  TSX: ["TSX", "TORONTO"],
36
39
  TORONTO: ["TORONTO", "TSX"],
37
40
  XETRA: ["XETRA"],
38
- TSE: ["TOKYO STOCK EXCHANGE", "TOKYO", "JPX", "TSE"],
39
- TOKYO: ["TOKYO STOCK EXCHANGE", "TOKYO", "JPX", "TSE"],
41
+ TOKYO: ["TOKYO STOCK EXCHANGE", "TOKYO", "JPX"],
40
42
  JPX: ["JPX", "TOKYO STOCK EXCHANGE", "TOKYO"],
41
43
  LSE: ["LSE", "LONDON"],
42
44
  HKEX: ["HKEX", "HONG KONG"],
@@ -46,6 +48,13 @@ const EXCHANGE_HINT_ALIASES: Record<string, string[]> = {
46
48
  AIRES: ["BUENOS AIRES", "BUE"],
47
49
  };
48
50
 
51
+ /** A code the venue list reads as several venues (TSE: Toronto or Tokyo) ranks a row on any of them. */
52
+ function sharedCodeHint(code: string): string[] {
53
+ return [code, ...exchangeCodeVenues(code).flatMap((venue) => VENUE_HINT_ALIASES[venue] ?? [venue])];
54
+ }
55
+
56
+ const EXCHANGE_HINT_ALIASES: Record<string, string[]> = { ...VENUE_HINT_ALIASES, TSE: sharedCodeHint("TSE") };
57
+
49
58
  const ASSET_HINT_MAP: Record<string, TickerSearchInstrumentClass> = {
50
59
  STOCK: "equity",
51
60
  STK: "equity",
@@ -121,15 +130,8 @@ export function isExplicitMarketSymbol(symbol: string): boolean {
121
130
  return /[=^]/.test(symbol) || /^[A-Z]{3}\/[A-Z]{3}$/i.test(symbol.trim());
122
131
  }
123
132
 
124
- function forexPair(symbol: string): string | null {
125
- if (/^[A-Z]{3}\/[A-Z]{3}$/.test(symbol)) return symbol.replace("/", "");
126
- if (/^[A-Z]{6}=X$/.test(symbol)) return symbol.slice(0, -2);
127
- if (/^[A-Z]{3}=X$/.test(symbol)) return `USD${symbol.slice(0, -2)}`;
128
- return null;
129
- }
130
-
131
133
  export function getForexQuoteCurrency(symbol: string): string | undefined {
132
- return forexPair(parsePublicTickerKey(normalizeTickerSymbol(symbol)).symbol)?.slice(3);
134
+ return currencyPairCode(normalizeTickerSymbol(symbol))?.slice(3);
133
135
  }
134
136
 
135
137
  /** Venue and market syntax must survive exact matching. Only explicit FX forms are equivalent. */
@@ -145,12 +147,16 @@ function matchesQualifiedTicker(
145
147
  const exchanges = [candidate.exchange, item.exchangeLabel, item.primaryExchangeLabel, item.right]
146
148
  .filter((value): value is string => !!value);
147
149
  if (requested.exchange) {
148
- return candidate.symbol === requested.symbol
149
- && exchanges.some((exchange) => canonicalExchange(exchange) === requested.exchange);
150
+ // EURUSD:CCY and EURUSD:FX name the pair listed as EURUSD=X on CCY.
151
+ const pair = currencyPairCode(requested.symbol, requested.exchange);
152
+ // A code that names several venues (TSE: Toronto or Tokyo) matches a row on any of them.
153
+ const venues = exchangeCodeVenues(typedExchangeCode(normalized));
154
+ return (candidate.symbol === requested.symbol || (pair != null && currencyPairCode(candidate.symbol) === pair))
155
+ && exchanges.some((exchange) => venues.includes(canonicalExchange(exchange)));
150
156
  }
151
157
  if (isExplicitMarketSymbol(requested.symbol)) {
152
- const pair = forexPair(requested.symbol);
153
- return candidate.symbol === requested.symbol || (pair != null && forexPair(candidate.symbol) === pair);
158
+ const pair = currencyPairCode(requested.symbol);
159
+ return candidate.symbol === requested.symbol || (pair != null && currencyPairCode(candidate.symbol) === pair);
154
160
  }
155
161
  return exchanges.some((exchange) => getListingSymbol(candidate.symbol, exchange).toUpperCase() === normalized);
156
162
  }
@@ -408,6 +414,16 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
408
414
  ? deduped.findIndex((item) => parsePublicTickerKey(item.symbol || item.label).symbol === marketSymbol)
409
415
  : -1;
410
416
  if (marketIndex > 0) deduped.unshift(...deduped.splice(marketIndex, 1));
417
+ // A futures board code's future ranks with its code, not with the derivatives
418
+ // the word ranking puts last: first where no security uses the code (TTF),
419
+ // right after the security that keeps it otherwise (NovaGold, then NG=F).
420
+ const boardFuture = assetClass ? null : futuresContractByCode(query);
421
+ const futureIndex = boardFuture
422
+ ? deduped.findIndex((item) => normalizeTickerSymbol(item.symbol || item.label) === boardFuture.symbol)
423
+ : -1;
424
+ if (boardFuture && futureIndex >= 0) {
425
+ deduped.splice(boardFuture.bareCode ? 0 : Math.min(1, futureIndex), 0, ...deduped.splice(futureIndex, 1));
426
+ }
411
427
  return deduped;
412
428
  }
413
429
 
@@ -432,7 +448,7 @@ export function buildSymbolAliases(symbol: string): string[] {
432
448
  const normalizedSymbol = normalizeTickerSymbol(symbol);
433
449
  if (!normalizedSymbol) return [];
434
450
  if (isExplicitMarketSymbol(normalizedSymbol)) {
435
- const pair = forexPair(normalizedSymbol);
451
+ const pair = currencyPairCode(normalizedSymbol);
436
452
  return pair
437
453
  ? [...new Set([normalizedSymbol, `${pair}=X`, `${pair.slice(0, 3)}/${pair.slice(3)}`])]
438
454
  : [normalizedSymbol];
@@ -38,7 +38,13 @@ export interface TickerSearchCandidate extends TickerSearchRankableItem {
38
38
 
39
39
  export type ResolvedTickerSearch =
40
40
  | { kind: "local"; symbol: string; ticker: TickerRecord }
41
- | { kind: "provider"; symbol: string; result: InstrumentSearchResult };
41
+ | {
42
+ kind: "provider";
43
+ symbol: string;
44
+ result: InstrumentSearchResult;
45
+ /** A bare symbol's other listings (SYM:EXCH) when its quote chose one of several. */
46
+ otherListings?: string[];
47
+ };
42
48
 
43
49
  export interface TickerOpenTarget {
44
50
  symbol: string;
@@ -0,0 +1,34 @@
1
+ import type { DataProvider } from "../types/data-provider";
2
+ import { canonicalExchange, isUsListingExchange, parsePublicTickerKey } from "../utils/exchanges";
3
+ import { sameCompany } from "./company-name";
4
+ import { LISTING_SEARCH_LIMIT } from "./search";
5
+ import { classifyInstrumentKind } from "./search/ranking";
6
+
7
+ export interface UsListing {
8
+ symbol: string;
9
+ /** Canonical code of its US exchange. */
10
+ exchange: string;
11
+ name: string;
12
+ }
13
+
14
+ /**
15
+ * A company's own listing on a US exchange, found by its name: Sanofi's SNY,
16
+ * Toyota's TM. Funds and contracts that carry its name are not its listing.
17
+ * Null when search knows no US line of the company.
18
+ */
19
+ export async function findUsListingOfCompany(
20
+ dataProvider: Pick<DataProvider, "search">,
21
+ company: string | null | undefined,
22
+ ): Promise<UsListing | null> {
23
+ const name = company?.trim();
24
+ if (!name) return null;
25
+ const results = await dataProvider.search(name, { limit: LISTING_SEARCH_LIMIT }).catch(() => []);
26
+ for (const result of results) {
27
+ const exchange = canonicalExchange(result.exchange === "SMART" ? result.primaryExchange : result.exchange || result.primaryExchange);
28
+ const kind = classifyInstrumentKind(result.brokerContract?.secType || result.type);
29
+ if (!isUsListingExchange(exchange) || kind === "fund" || kind === "derivative") continue;
30
+ if (!sameCompany(result.name, name) || parsePublicTickerKey(result.symbol).exchange) continue;
31
+ return { symbol: result.symbol.toUpperCase(), exchange, name: result.name };
32
+ }
33
+ return null;
34
+ }
@@ -3,6 +3,7 @@ import { apiClient } from "../api-client";
3
3
  import { loadCachedFredSeries } from "../sources/gloomberb-cloud/fred-series";
4
4
  import { instrumentFromTicker } from "../market-data/request-types";
5
5
  import { useAssetData, useCapabilityInvoker } from "../plugins/runtime";
6
+ import { getSharedRegistry } from "../plugins/registry";
6
7
  import { createChartSeriesResolver } from "../capabilities";
7
8
  import { useAppSelector } from "../state/app/context";
8
9
  import type { FredSeriesRequest } from "../sources/gloomberb-cloud/fred-series";
@@ -17,6 +18,13 @@ import {
17
18
  /** A scoped, immutable model for exports that must render exactly the loaded data. */
18
19
  export const ChartSnapshotContext = createContext<ChartResolutionResult | null>(null);
19
20
 
21
+ /** A registered series' own reload cadence, when it has one. */
22
+ function registeredSeriesRefreshMs(source: { capabilityId: string; seriesId: string }): number | undefined {
23
+ const found = getSharedRegistry()?.series.findLoaded(source.capabilityId, source.seriesId);
24
+ const definition = found?.series.definition;
25
+ return definition && "refreshMs" in definition ? definition.refreshMs : undefined;
26
+ }
27
+
20
28
  async function loadFred(request: FredSeriesRequest) {
21
29
  return loadCachedFredSeries(
22
30
  request,
@@ -71,6 +79,7 @@ export function useResolvedChartSpec(
71
79
  dataProvider,
72
80
  loadFredSeries: loadFred,
73
81
  resolveCapabilitySeries: createChartSeriesResolver(capabilityInvoker),
82
+ capabilityRefreshMs: registeredSeriesRefreshMs,
74
83
  }), [capabilityInvoker, dataProvider]);
75
84
  return useChartResolution(hydratedSpec, sources, { ...options, snapshot });
76
85
  }