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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gloomberb",
3
- "version": "0.18.0",
3
+ "version": "0.18.2",
4
4
  "packageManager": "bun@1.3.11",
5
5
  "description": "Market research and portfolio tracker for the terminal",
6
6
  "module": "src/public/index.ts",
@@ -44,6 +44,12 @@ export interface PsdBalanceRow {
44
44
  unit: string;
45
45
  /** Market year, 2026/27. */
46
46
  year: string;
47
+ /**
48
+ * YYYY-MM of the crop's newest estimate: the WASDE for grains, oilseeds and
49
+ * cotton, USDA's semiannual report for coffee and sugar. Absent from boards
50
+ * served before it was sent.
51
+ */
52
+ month?: string | null;
47
53
  cropOrder: number;
48
54
  countryOrder: number;
49
55
  attributeOrder: number;
@@ -83,6 +83,10 @@ export interface KpiObservation extends Omit<KpiObservationInput, "quote" | "sca
83
83
  conflict: boolean
84
84
  contested?: boolean
85
85
  evidence: KpiEvidence[]
86
+ /** Stored facts shown as this one: the same figure under other labels or periods. */
87
+ merged?: string[]
88
+ /** The labels that tell its series apart, the same on each of its facts; the stored ones stay in `dimensions`. */
89
+ seriesLabels?: Record<string, string>
86
90
  }
87
91
  export interface KpiGuidance extends Omit<KpiGuidanceInput, "quote" | "scale"> {
88
92
  id: string
@@ -97,6 +101,10 @@ export interface KpiGuidance extends Omit<KpiGuidanceInput, "quote" | "scale"> {
97
101
  direction: GuidanceDirection
98
102
  previousId: string | null
99
103
  midpointChangePct: number | null
104
+ /** Stored guides shown as this one. */
105
+ merged?: string[]
106
+ /** The labels that tell its series apart, as on its actuals. */
107
+ seriesLabels?: Record<string, string>
100
108
  actual: { observationId: string; value: number; outcome: "above" | "below" | "within"; favorable: "beat" | "miss" | "in_line" | "neutral"; difference: number; differencePct: number | null } | null
101
109
  }
102
110
  interface KpiCoverage {
@@ -118,6 +126,7 @@ export interface KpiSeries {
118
126
  currency: string | null
119
127
  basis: KpiBasis
120
128
  dimensions: Record<string, string>
129
+ seriesLabels?: Record<string, string>
121
130
  latest: KpiObservation
122
131
  observations: KpiObservation[]
123
132
  }
@@ -38,9 +38,16 @@ export interface CryptoMarketAsset {
38
38
  /** Market-cap rank within its kind. */
39
39
  rank: number;
40
40
  price: number;
41
+ /**
42
+ * The day's move as `quote` gives the coin: from the previous UTC day's
43
+ * close, the price at 00:00 UTC. A coin whose close Gloom Cloud cannot date
44
+ * to the last two days moves over 24 hours (`changeBasis` "24h") or not at
45
+ * all ("none"), its previousClose null.
46
+ */
41
47
  previousClose: number | null;
42
48
  change: number | null;
43
49
  changePercent: number | null;
50
+ changeBasis?: "24h" | "none";
44
51
  dayHigh: number | null;
45
52
  dayLow: number | null;
46
53
  volume24h: number | null;
@@ -62,6 +69,8 @@ export interface CryptoMarketAsset {
62
69
  export interface CryptoMarketsPayload {
63
70
  version: 1;
64
71
  generatedAt: string;
72
+ /** Each asset's change is since 00:00 UTC; a server before it sent the screener's 24-hour move. */
73
+ dayChangeBasis?: "utc-day";
65
74
  asOf: string | null;
66
75
  status: "available" | "partial" | "unavailable";
67
76
  source: {
@@ -610,12 +610,12 @@ export class CloudDataApi {
610
610
  }
611
611
 
612
612
  /** `listing` names a listing abroad's venue and company; a US listing needs neither. */
613
- async getCloudCompanyKpis(symbol: string, options: KpiQueryOptions = {}, listing: IssuerListingParams = {}): Promise<KpisPayload> {
614
- return this.request<KpisPayload>(companyDisclosurePath("kpis", symbol, options, listing));
613
+ async getCloudCompanyKpis(symbol: string, options: KpiQueryOptions = {}, listing: IssuerListingParams = {}, request?: { signal?: AbortSignal }): Promise<KpisPayload> {
614
+ return this.request<KpisPayload>(companyDisclosurePath("kpis", symbol, options, listing), request);
615
615
  }
616
616
 
617
- async getCloudCompanyGuidance(symbol: string, options: KpiQueryOptions = {}, listing: IssuerListingParams = {}): Promise<GuidancePayload> {
618
- return this.request<GuidancePayload>(companyDisclosurePath("guidance", symbol, options, listing));
617
+ async getCloudCompanyGuidance(symbol: string, options: KpiQueryOptions = {}, listing: IssuerListingParams = {}, request?: { signal?: AbortSignal }): Promise<GuidancePayload> {
618
+ return this.request<GuidancePayload>(companyDisclosurePath("guidance", symbol, options, listing), request);
619
619
  }
620
620
  async getCloudPerpsBoard(query: PerpBoardQuery = {}): Promise<PerpBoardPayload> {
621
621
  return this.request(`/cloud/perps/board?${new URLSearchParams(Object.entries(query).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]))}`);
@@ -1040,8 +1040,9 @@ export class CloudDataApi {
1040
1040
 
1041
1041
  async getCloudSecFilings(
1042
1042
  params: CloudSecFilingsParams,
1043
+ options?: { signal?: AbortSignal },
1043
1044
  ): Promise<CloudSecFilingsResponse> {
1044
- return this.request<CloudSecFilingsResponse>(cloudSecFilingsPath(params));
1045
+ return this.request<CloudSecFilingsResponse>(cloudSecFilingsPath(params), options);
1045
1046
  }
1046
1047
 
1047
1048
  /** Schedule 13D/13G reports about an issuer. Unknown tickers answer 404; no reports is an empty `owners`. */
@@ -110,6 +110,8 @@ export interface ScreenMetric {
110
110
  scope: "reported" | "derived" | "observed-subset";
111
111
  reason: string | null;
112
112
  sourceUrl: string | null;
113
+ /** Set by the client on a Pro field the server left out for a caller without Gloom Pro; never sent. */
114
+ access?: "pro";
113
115
  percentile: {
114
116
  value: number | null;
115
117
  sampleCount: number;
@@ -50,6 +50,13 @@ export interface CloudSessionMoversPayload {
50
50
  phase: CloudSessionPhase;
51
51
  asOf: string;
52
52
  stale?: boolean;
53
+ /**
54
+ * `delayed` for an account without Pro: the list as it was built at least
55
+ * `delayMinutes` before it was served, as old as that account's quotes,
56
+ * and `asOf` that time. Absent on Pro's live list.
57
+ */
58
+ access?: "delayed";
59
+ delayMinutes?: number;
53
60
  items: CloudSessionMoverItem[];
54
61
  }
55
62
 
@@ -1005,6 +1005,13 @@ interface CloudProxyStatementSummaryPayload {
1005
1005
  }
1006
1006
 
1007
1007
  export interface CloudProxyStatementPayload extends CloudProxyStatementSummaryPayload {
1008
+ /**
1009
+ * `preview` for an account without Pro: each executive's total pay from
1010
+ * the latest proxy, with the breakdown, the year before, the pay ratio,
1011
+ * say-on-pay, highlights, key figures and other years null or empty.
1012
+ * Absent on Pro's full statement.
1013
+ */
1014
+ access?: "preview";
1008
1015
  docUrl: string;
1009
1016
  ceo: (CloudExecutiveRowPayload & { priorYearTotal: number | null }) | null;
1010
1017
  namedExecutives: CloudExecutiveRowPayload[];
@@ -1126,6 +1133,8 @@ export interface CloudProxyStatementListPayload {
1126
1133
  shortName: string;
1127
1134
  };
1128
1135
  proxies: CloudProxyStatementSummaryPayload[];
1136
+ /** `preview` for an account without Pro: the latest proxy alone, without the year before or the pay ratio. */
1137
+ access?: "preview";
1129
1138
  }
1130
1139
 
1131
1140
  export interface CloudTranscriptTurnPayload {
@@ -1554,6 +1563,11 @@ export interface CloudMarketResponse<T> {
1554
1563
  omittedSessions?: unknown;
1555
1564
  /** Untrusted until the history adapter validates its boundary dates. */
1556
1565
  coverage?: CloudHistoryCoverage;
1566
+ /**
1567
+ * Untrusted: a US listing that no longer trades, `{ status, lastTradeDate,
1568
+ * historyStart }`. Its history ends on `lastTradeDate`. Read by parseListingDelisting.
1569
+ */
1570
+ delisting?: unknown;
1557
1571
  providerMeta?: {
1558
1572
  provider?: string;
1559
1573
  upstream?: string;
@@ -479,9 +479,11 @@ export function bindAppPanePluginRegistry({
479
479
  throw error;
480
480
  }
481
481
  }
482
+ // Recorded before it registers: what redraws as its contributions land
483
+ // (a broker list and its logo) reads the entry this checkout now is.
484
+ upsertExternalPlugin(entry);
482
485
  try {
483
486
  await pluginRegistry.register(entry.plugin);
484
- upsertExternalPlugin(entry);
485
487
  } catch (error) {
486
488
  upsertExternalPlugin({ ...entry, error: error instanceof Error ? error.message : String(error) });
487
489
  reportCrash(error, { kind: "plugin", plugin: pluginId });
@@ -24,7 +24,7 @@ import {
24
24
  tickerOpenFailureMessage,
25
25
  type TickerOpenTarget,
26
26
  } from "../../tickers/open-target";
27
- import { findExactTickerSearchMatch } from "../../tickers/search";
27
+ import { findExactTickerSearchMatch, upsertTickerFromSearchResult } from "../../tickers/search";
28
28
  import { offerTickerListings, publishTickerOpenTarget as publishOpenTarget } from "./ticker-open-target";
29
29
  import { parsePublicTickerKey, publicTickerKey } from "../../utils/exchanges";
30
30
  import { attentionActionForPane, captureAttentionAction } from "../../telemetry/attention-counts";
@@ -158,7 +158,7 @@ export function useAppTickerOpenRuntime({
158
158
  persistLayout(nextLayout);
159
159
  }
160
160
  if (paneType === TICKER_RESEARCH_PANE_ID && options?.tabId) {
161
- dispatch({ type: "UPDATE_PANE_STATE", paneId: existing.instanceId, patch: { activeTabId: options.tabId } });
161
+ dispatch({ type: "UPDATE_PANE_STATE", paneId: existing.instanceId, patch: { ...options.tabState, activeTabId: options.tabId } });
162
162
  }
163
163
  focusVisiblePane(existing.instanceId, nextLayout);
164
164
  return;
@@ -186,7 +186,7 @@ export function useAppTickerOpenRuntime({
186
186
  );
187
187
  persistLayout(nextLayout);
188
188
  if (paneType === TICKER_RESEARCH_PANE_ID && options?.tabId) {
189
- dispatch({ type: "UPDATE_PANE_STATE", paneId: instance.instanceId, patch: { activeTabId: options.tabId } });
189
+ dispatch({ type: "UPDATE_PANE_STATE", paneId: instance.instanceId, patch: { ...options.tabState, activeTabId: options.tabId } });
190
190
  }
191
191
  activatePane(instance.instanceId);
192
192
  }, [
@@ -205,9 +205,21 @@ export function useAppTickerOpenRuntime({
205
205
  // Before resolving, so a refused open adds no ticker either.
206
206
  if (refuseWhileOwnerOff(rawSymbol, options, () => { void openPinnedTicker(rawSymbol, options); })) return;
207
207
  const recordAttention = captureAttentionAction();
208
- const selectedTicker = options?.instrument !== undefined && (options.instrument || options.listing) ? await tickerRepository.loadTicker(rawSymbol) : null;
208
+ let selected = options?.instrument !== undefined && (options.instrument || options.listing)
209
+ ? { ticker: await tickerRepository.loadTicker(rawSymbol), created: false } : null;
210
+ // A listing the caller chose, such as one contract of an options chain,
211
+ // opens as given: no search could name it better than its own chain.
212
+ if (selected && !selected.ticker && options?.listing) {
213
+ const { listing, instrument } = options;
214
+ selected = await upsertTickerFromSearchResult(tickerRepository, {
215
+ providerId: instrument?.brokerId ?? "", ...(instrument?.brokerInstanceId ? { brokerInstanceId: instrument.brokerInstanceId } : {}),
216
+ symbol: rawSymbol, name: listing.name, exchange: listing.exchange, type: listing.type,
217
+ ...(listing.currency ? { currency: listing.currency } : {}), ...(instrument ? { brokerContract: instrument } : {}),
218
+ });
219
+ }
220
+ const selectedTicker = selected?.ticker;
209
221
  const target = selectedTicker
210
- ? { symbol: selectedTicker.metadata.ticker, ticker: selectedTicker, created: false, instrument: options?.instrument, listing: options?.listing }
222
+ ? { symbol: selectedTicker.metadata.ticker, ticker: selectedTicker, created: selected!.created, instrument: options?.instrument, listing: options?.listing }
211
223
  : await resolveOpenTickerTarget(rawSymbol, options?.instrument === null);
212
224
  if (!target) return;
213
225
  placePinnedTickerTarget(target, options, rawSymbol);
@@ -105,7 +105,7 @@ export function bindPluginRegistryRuntimeAccess({
105
105
  pluginRegistry.events.emit("config:changed", { config: nextConfig });
106
106
  },
107
107
 
108
- createBrokerInstance: async (brokerType, label, values) => {
108
+ createBrokerInstance: async (brokerType, label, values, options = {}) => {
109
109
  const instanceId = createBrokerInstanceId(
110
110
  brokerType,
111
111
  label,
@@ -118,6 +118,7 @@ export function bindPluginRegistryRuntimeAccess({
118
118
  connectionMode: typeof values.connectionMode === "string" ? values.connectionMode : undefined,
119
119
  config: values,
120
120
  enabled: true,
121
+ ...(options.localOnly ? { localOnly: true } : {}),
121
122
  };
122
123
  const nextConfig = {
123
124
  ...stateRef.current.config,
@@ -153,12 +154,15 @@ export function bindPluginRegistryRuntimeAccess({
153
154
  instance.id === instanceId
154
155
  ? (() => {
155
156
  const nextValues = options.replaceConfig ? values : { ...instance.config, ...values };
157
+ const { localOnly: wasLocalOnly, ...rest } = instance;
158
+ const localOnly = options.localOnly ?? wasLocalOnly === true;
156
159
  return {
157
- ...instance,
160
+ ...rest,
158
161
  label: options.label ?? instance.label,
159
162
  enabled: options.enabled ?? instance.enabled,
160
163
  connectionMode: typeof nextValues.connectionMode === "string" ? nextValues.connectionMode : instance.connectionMode,
161
164
  config: nextValues,
165
+ ...(localOnly ? { localOnly: true } : {}),
162
166
  };
163
167
  })()
164
168
  : instance,
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The logo a broker is listed with on the desktop and the web: the icon of the
3
+ * plugin that registered it (its gloom.json `icon`), else the app's own for a
4
+ * broker Gloom connects for you (a signed-in broker, which no plugin owns),
5
+ * else none, and the list draws the broker's initial instead (LogoMark).
6
+ */
7
+ import type { BrokerAdapter } from "../types/broker";
8
+ import type { BrokerInstanceConfig } from "../types/config";
9
+ import type { Portfolio } from "../types/ticker";
10
+ import type { BrokerDirectoryEntry } from "./directory";
11
+
12
+ function svg(markup: string): string {
13
+ return `data:image/svg+xml,${encodeURIComponent(markup)}`;
14
+ }
15
+
16
+ /**
17
+ * The marks the official plugins ship as their icon, for the same brokers
18
+ * when Gloom connects them, keyed by the backend's broker id.
19
+ */
20
+ const SIGNED_IN_BROKER_LOGOS: Readonly<Record<string, string>> = {
21
+ ibkr: svg('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><linearGradient id="t" x1="0" y1="0" x2="0" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#262626"/><stop offset="1" stop-color="#0F0F0F"/></linearGradient><linearGradient id="m" gradientUnits="userSpaceOnUse" x1="3633.335" y1="66.0445" x2="3671.0737" y2="66.0445" gradientTransform="matrix(-1 0 0 1 3673.7529 0)"><stop offset="0" stop-color="#D71F27"/><stop offset="1" stop-color="#971B1E"/></linearGradient></defs><rect width="64" height="64" fill="url(#t)"/><g transform="translate(20.4 8.2) scale(0.539)"><polygon points="40.4,86.8 2.7,86.8 2.7,45.3" fill="url(#m)"/><polygon points="40.4,1.5 2.7,45.3 2.7,86.8" fill="#D71F27"/><circle cx="35.2" cy="55.5" r="11.3" fill="#D71F27"/></g></svg>'),
22
+ robinhood: svg('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" fill="#CCFF00"/><path transform="translate(14 14) scale(1.5)" d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852" fill="#1A1A1A"/></svg>'),
23
+ };
24
+
25
+ /** The plugin icon of a broker adapter (`getBrokerLogo` on the plugin runtime). */
26
+ export type BrokerPluginLogo = (brokerType: string) => string | null;
27
+
28
+ function logoFor(brokerKey: string, pluginIcon: string | null): string | null {
29
+ return pluginIcon ?? SIGNED_IN_BROKER_LOGOS[brokerKey] ?? null;
30
+ }
31
+
32
+ /** A broker in Add Broker's list: the icon of the plugin that connects it on this device, else Gloom's own. */
33
+ export function brokerDirectoryLogo(entry: BrokerDirectoryEntry, pluginLogo: BrokerPluginLogo): string | null {
34
+ const device = entry.methods.find((method) => method.kind === "device");
35
+ return logoFor(entry.key, device ? pluginLogo(device.adapter.id) : null);
36
+ }
37
+
38
+ /** A broker profile: its adapter's plugin icon, or the logo of the broker a signed-in profile connects. */
39
+ export function brokerProfileLogo(
40
+ instance: BrokerInstanceConfig,
41
+ adapter: BrokerAdapter | null,
42
+ pluginLogo: BrokerPluginLogo,
43
+ ): string | null {
44
+ const brokerKey = adapter?.portfolioBrokerId?.(instance) || instance.brokerType;
45
+ return logoFor(brokerKey, pluginLogo(instance.brokerType));
46
+ }
47
+
48
+ /**
49
+ * The broker a portfolio's account is at, for its tab: the logo and the name
50
+ * its initial stands in for. Null for a portfolio no broker fills.
51
+ */
52
+ export function brokerPortfolioLogo(
53
+ portfolio: Pick<Portfolio, "brokerId" | "brokerInstanceId">,
54
+ instances: readonly BrokerInstanceConfig[],
55
+ getAdapter: (brokerType: string) => BrokerAdapter | null,
56
+ pluginLogo: BrokerPluginLogo,
57
+ ): { src: string | null; name: string } | null {
58
+ if (!portfolio.brokerInstanceId) return null;
59
+ const instance = instances.find((candidate) => candidate.id === portfolio.brokerInstanceId);
60
+ if (!instance) {
61
+ return portfolio.brokerId ? { src: logoFor(portfolio.brokerId, null), name: portfolio.brokerId } : null;
62
+ }
63
+ const adapter = getAdapter(instance.brokerType);
64
+ const name = adapter?.describeInstance?.(instance).brokerName ?? adapter?.name ?? instance.label;
65
+ return { src: brokerProfileLogo(instance, adapter, pluginLogo), name };
66
+ }
@@ -6,6 +6,8 @@ import { t, tf } from "../i18n";
6
6
  export interface BrokerProfileDraft {
7
7
  label: string;
8
8
  enabled: boolean;
9
+ /** Keep the connection's data on this device, out of Gloom Cloud sync. */
10
+ localOnly: boolean;
9
11
  values: Record<string, string>;
10
12
  }
11
13
 
@@ -47,6 +49,7 @@ export function createBrokerProfileDraft(
47
49
  return {
48
50
  label: instance?.label || adapter.name,
49
51
  enabled: instance?.enabled !== false,
52
+ localOnly: instance?.localOnly === true,
50
53
  values: flattenBrokerConfigValues(adapter, instance),
51
54
  };
52
55
  }
@@ -1,6 +1,6 @@
1
1
  import type { TimeRange } from "../time-series/range";
2
2
  import type { ManualChartResolution } from "../time-series/resolution";
3
- import type { BrokerAdapter, BrokerConnectionStatus } from "../types/broker";
3
+ import type { BrokerAdapter, BrokerConnectionStatus, BrokerHistoryOptions, BrokerOrderListener } from "../types/broker";
4
4
  import type { BrokerInstanceConfig } from "../types/config";
5
5
  import type { QuoteSubscriptionTarget } from "../types/data-provider";
6
6
  import type { BrokerContractRef } from "../types/instrument";
@@ -15,6 +15,7 @@ export interface BrokerRemoteClient {
15
15
  targets: QuoteSubscriptionTarget[],
16
16
  onQuote: (target: QuoteSubscriptionTarget, quote: any) => void,
17
17
  ): () => void;
18
+ subscribeOrders(instanceId: string, listener: BrokerOrderListener): () => void;
18
19
  removeInstance(instanceId: string): Promise<void>;
19
20
  destroyAll(): Promise<void>;
20
21
  }
@@ -73,7 +74,8 @@ export function createRemoteBrokerAdapter(adapter: BrokerAdapter): BrokerAdapter
73
74
  bufferRange: TimeRange,
74
75
  resolution: ManualChartResolution,
75
76
  instrument?: BrokerContractRef | null,
76
- ) => invoke(instance, "getPriceHistoryForResolution", [ticker, exchange, bufferRange, resolution, instrument]),
77
+ options?: BrokerHistoryOptions,
78
+ ) => invoke(instance, "getPriceHistoryForResolution", [ticker, exchange, bufferRange, resolution, instrument, ...(options ? [options] : [])]),
77
79
  getDetailedPriceHistory: (
78
80
  ticker,
79
81
  instance,
@@ -82,7 +84,8 @@ export function createRemoteBrokerAdapter(adapter: BrokerAdapter): BrokerAdapter
82
84
  endDate: Date,
83
85
  barSize: string,
84
86
  instrument?: BrokerContractRef | null,
85
- ) => invoke(instance, "getDetailedPriceHistory", [ticker, exchange, startDate, endDate, barSize, instrument]),
87
+ options?: BrokerHistoryOptions,
88
+ ) => invoke(instance, "getDetailedPriceHistory", [ticker, exchange, startDate, endDate, barSize, instrument, ...(options ? [options] : [])]),
86
89
  getChartResolutionSupport: (ticker, instance, exchange?: string, instrument?: BrokerContractRef | null) =>
87
90
  invoke(instance, "getChartResolutionSupport", [ticker, exchange, instrument]),
88
91
  getChartResolutionCapabilities: (ticker, instance, exchange?: string, instrument?: BrokerContractRef | null) =>
@@ -95,7 +98,14 @@ export function createRemoteBrokerAdapter(adapter: BrokerAdapter): BrokerAdapter
95
98
  subscribeQuotes: adapter.subscribeQuotes
96
99
  ? (instance, targets, onQuote) => getBrokerRemoteClient()?.subscribeQuotes(instance.id, targets, onQuote) ?? (() => {})
97
100
  : undefined,
101
+ subscribeOrders: adapter.subscribeOrders
102
+ ? (instance, listener) => getBrokerRemoteClient()?.subscribeOrders(instance.id, listener) ?? (() => {})
103
+ : undefined,
98
104
  listOpenOrders: (instance) => invoke(instance, "listOpenOrders"),
105
+ // The view's own adapter has no session: a ticket confirming an order must ask the one that placed it.
106
+ getOrderStatus: adapter.getOrderStatus
107
+ ? (instance, orderId: number) => invoke(instance, "getOrderStatus", [orderId])
108
+ : undefined,
99
109
  listExecutions: (instance) => invoke(instance, "listExecutions"),
100
110
  previewOrder: (instance, request: BrokerOrderRequest) => invoke(instance, "previewOrder", [request]),
101
111
  placeOrder: (instance, request: BrokerOrderRequest) => invoke(instance, "placeOrder", [request]),
@@ -25,7 +25,7 @@ export {
25
25
  AI_RUNNER_CAPABILITY_ID, BROKER_CAPABILITY_ID, NOTES_FILES_CAPABILITY_ID,
26
26
  } from "./service-capabilities";
27
27
  export type {
28
- AiAuthProgressEvent, AiRunnerAgentMessage, AiRunnerEvent, BrokerQuoteEvent, BrokerRemoteEvent,
28
+ AiAuthProgressEvent, AiRunnerAgentMessage, AiRunnerEvent, BrokerOrderEvent, BrokerQuoteEvent, BrokerRemoteEvent,
29
29
  BrokerStatusEvent,
30
30
  } from "./service-capabilities";
31
31
  export {
@@ -1,6 +1,7 @@
1
1
  import type { BrokerConnectionStatus } from "../types/broker";
2
2
  import type { QuoteSubscriptionTarget } from "../types/data-provider";
3
3
  import type { Quote } from "../types/financials";
4
+ import type { BrokerOrder } from "../types/trading";
4
5
 
5
6
  export const BROKER_CAPABILITY_ID = "broker.core";
6
7
  export const NOTES_FILES_CAPABILITY_ID = "notes.files";
@@ -18,7 +19,12 @@ export type BrokerQuoteEvent = {
18
19
  quote: Quote;
19
20
  };
20
21
 
21
- export type BrokerRemoteEvent = BrokerStatusEvent | BrokerQuoteEvent;
22
+ /** An order change, or the order stream going live or quiet, from `BrokerAdapter.subscribeOrders`. */
23
+ export type BrokerOrderEvent =
24
+ | { kind: "order"; order: BrokerOrder }
25
+ | { kind: "orders-live"; live: boolean };
26
+
27
+ export type BrokerRemoteEvent = BrokerStatusEvent | BrokerQuoteEvent | BrokerOrderEvent;
22
28
 
23
29
  /**
24
30
  * What an AI runtime reports while it signs an account in.
@@ -2,34 +2,76 @@ import type { DataProvider } from "../types/data-provider";
2
2
  import { fixedUsdRate } from "../utils/currency-units";
3
3
  import { logFailure } from "../utils/log-failure";
4
4
 
5
- export function createBaseConverter(dataProvider: Pick<DataProvider, "getExchangeRate">, baseCurrency: string) {
6
- const rateCache = new Map<string, number>([["USD", 1]]);
5
+ /** One rate a converter used: units of `baseCurrency` per unit of `currency`, and when it was observed. */
6
+ export interface BaseConversion {
7
+ currency: string;
8
+ baseCurrency: string;
9
+ rate: number;
10
+ /** Epoch ms of the older leg's observation; null when the source did not date it. */
11
+ asOf: number | null;
12
+ }
7
13
 
8
- const getRate = async (currency: string): Promise<number> => {
9
- const normalizedCurrency = currency.toUpperCase();
10
- const cached = rateCache.get(normalizedCurrency) ?? fixedUsdRate(normalizedCurrency);
11
- if (cached != null) return cached;
14
+ /** Converts an amount into the base currency, and lists the rates it used so a report can state them. */
15
+ export type BaseConverter = ((value: number, fromCurrency: string) => Promise<number>) & {
16
+ conversions(): BaseConversion[];
17
+ };
18
+
19
+ interface UsdRate {
20
+ rate: number;
21
+ asOf: number | null;
22
+ }
23
+
24
+ export function createBaseConverter(
25
+ dataProvider: Pick<DataProvider, "getExchangeRate" | "getCachedQuery" | "revalidatesInBackground">,
26
+ baseCurrency: string,
27
+ ): BaseConverter {
28
+ const rateCache = new Map<string, Promise<UsdRate>>();
29
+ const used = new Map<string, BaseConversion>();
30
+
31
+ /** USD per unit, dated when the cached query says when it was observed (as `fx` dates it). */
32
+ const loadRate = async (currency: string): Promise<UsdRate> => {
33
+ const fixed = fixedUsdRate(currency);
34
+ if (fixed != null) return { rate: fixed, asOf: null };
12
35
  try {
13
- const rate = await dataProvider.getExchangeRate(normalizedCurrency);
14
- const validRate = Number.isFinite(rate) && rate > 0 ? rate : Number.NaN;
15
- rateCache.set(normalizedCurrency, validRate);
16
- return validRate;
36
+ if (dataProvider.getCachedQuery) {
37
+ const entry = await dataProvider.getCachedQuery("getExchangeRate", [currency])
38
+ .load({ background: dataProvider.revalidatesInBackground });
39
+ return { rate: Number.isFinite(entry.value) && entry.value > 0 ? entry.value : Number.NaN, asOf: entry.asOf ?? null };
40
+ }
41
+ const rate = await dataProvider.getExchangeRate(currency);
42
+ return { rate: Number.isFinite(rate) && rate > 0 ? rate : Number.NaN, asOf: null };
17
43
  } catch (error) {
18
44
  logFailure("cli-base-converter", "Could not load base conversion exchange rate")(error);
19
- return Number.NaN;
45
+ return { rate: Number.NaN, asOf: null };
20
46
  }
21
47
  };
22
48
 
23
- return async (value: number, fromCurrency: string): Promise<number> => {
49
+ const getRate = (currency: string): Promise<UsdRate> => {
50
+ const normalizedCurrency = currency.toUpperCase();
51
+ let pending = rateCache.get(normalizedCurrency);
52
+ if (!pending) {
53
+ pending = loadRate(normalizedCurrency);
54
+ rateCache.set(normalizedCurrency, pending);
55
+ }
56
+ return pending;
57
+ };
58
+
59
+ const convert = async (value: number, fromCurrency: string): Promise<number> => {
24
60
  const normalizedFrom = fromCurrency.toUpperCase();
25
61
  const normalizedBase = baseCurrency.toUpperCase();
26
62
  if (normalizedFrom === normalizedBase) return value;
27
63
 
28
- const [fromRate, baseRate] = await Promise.all([
29
- getRate(normalizedFrom),
30
- getRate(normalizedBase),
31
- ]);
32
- if (!Number.isFinite(fromRate) || !Number.isFinite(baseRate) || baseRate <= 0) return Number.NaN;
33
- return (value * fromRate) / baseRate;
64
+ const [from, base] = await Promise.all([getRate(normalizedFrom), getRate(normalizedBase)]);
65
+ if (!Number.isFinite(from.rate) || !Number.isFinite(base.rate) || base.rate <= 0) return Number.NaN;
66
+ const dates = [from.asOf, base.asOf].filter((time): time is number => time != null);
67
+ used.set(normalizedFrom, {
68
+ currency: normalizedFrom,
69
+ baseCurrency: normalizedBase,
70
+ rate: from.rate / base.rate,
71
+ asOf: dates.length > 0 ? Math.min(...dates) : null,
72
+ });
73
+ return (value * from.rate) / base.rate;
34
74
  };
75
+
76
+ return Object.assign(convert, { conversions: () => [...used.values()] });
35
77
  }
@@ -1,4 +1,5 @@
1
1
  import { seriesMoves, type MoveHorizon } from "../../plugins/builtin/shared/move-rank";
2
+ import { seriesNotOnFred } from "../../sources/gloomberb-cloud/fred-series";
2
3
  import { ICE_SPREAD_SERIES } from "../../time-series/ice-credit";
3
4
  import { toBasisPoints } from "../../utils/basis-points";
4
5
  import { parseReportTime } from "../../utils/utc-time";
@@ -138,7 +139,10 @@ export interface FredProvenance {
138
139
  * looking for the figure in FRED's own data knows it is not there yet:
139
140
  * `U.S. Treasury via FRED, published 2026-10-09`. Null for FRED's own.
140
141
  */
141
- export function fredProvenance(observations: ReadonlyArray<{ date: string; publisher?: string; publishedAt?: string }>): FredProvenance | null {
142
+ export function fredProvenance(
143
+ observations: ReadonlyArray<{ date: string; publisher?: string; publishedAt?: string }>,
144
+ seriesId?: string,
145
+ ): FredProvenance | null {
142
146
  const ahead = observations.filter((observation) => observation.publisher?.trim());
143
147
  if (ahead.length === 0) return null;
144
148
  const publishers = [...new Set(ahead.map((observation) => observation.publisher!.trim()))];
@@ -149,7 +153,8 @@ export function fredProvenance(observations: ReadonlyArray<{ date: string; publi
149
153
  const publishedAt = times.at(-1);
150
154
  const publisher = publishers.join(" and ");
151
155
  return {
152
- source: `${publisher} via FRED${publishedAt ? `, published ${publishedAt.slice(0, 10)}` : ""}`,
156
+ // A series FRED does not carry names whose publication it is read from instead (TurkStat via the CBRT).
157
+ source: `${publisher} via ${(seriesId ? seriesNotOnFred(seriesId)?.via : null) ?? "FRED"}${publishedAt ? `, published ${publishedAt.slice(0, 10)}` : ""}`,
153
158
  publisher,
154
159
  ...(publishedAt ? { publishedAt } : {}),
155
160
  };
@@ -275,6 +280,11 @@ export interface FredListRow {
275
280
  group: string;
276
281
  }
277
282
 
283
+ /** Lower case without accents, so `turk` and `turkiye` find Türkiye. */
284
+ function foldAccents(text: string): string {
285
+ return text.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
286
+ }
287
+
278
288
  /** How well a word of the filter matches a series, best first: its id or group name, a word of its group or title, the start of its id, then any text. */
279
289
  function matchTier(term: string, row: FredListRow, words: readonly string[]): number {
280
290
  if (row.id.toLowerCase() === term || row.group.toLowerCase() === term) return 0;
@@ -290,11 +300,11 @@ function matchTier(term: string, row: FredListRow, words: readonly string[]): nu
290
300
  * Catalog order breaks ties; an empty filter keeps the catalog as it is.
291
301
  */
292
302
  export function filterFredSeries(rows: readonly FredListRow[], filter: string): FredListRow[] {
293
- const terms = filter.toLowerCase().split(/\s+/).filter(Boolean);
303
+ const terms = foldAccents(filter).split(/\s+/).filter(Boolean);
294
304
  const ranked = rows.flatMap((row, index) => {
295
- const text = `${row.id} ${row.title} ${row.group}`.toLowerCase();
305
+ const text = foldAccents(`${row.id} ${row.title} ${row.group}`);
296
306
  if (!terms.every((term) => text.includes(term))) return [];
297
- const words = `${row.title} ${row.group}`.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
307
+ const words = foldAccents(`${row.title} ${row.group}`).split(/[^a-z0-9]+/).filter(Boolean);
298
308
  const tiers = terms.map((term) => matchTier(term, row, words));
299
309
  return [{ row, index, worst: Math.max(0, ...tiers), total: tiers.reduce((sum, tier) => sum + tier, 0) }];
300
310
  });