gloomberb 0.18.1 → 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 (307) 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/types.ts +5 -0
  8. package/src/app/pane-runtime/plugin-bindings.ts +3 -1
  9. package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
  10. package/src/app/runtime/plugin-bindings.ts +6 -2
  11. package/src/brokers/logos.ts +66 -0
  12. package/src/brokers/profile-form.ts +3 -0
  13. package/src/brokers/remote-broker-adapter.ts +13 -3
  14. package/src/capabilities/index.ts +1 -1
  15. package/src/capabilities/service-capabilities.ts +7 -1
  16. package/src/cli/base-converter.ts +60 -18
  17. package/src/cli/commands/market.ts +281 -154
  18. package/src/cli/commands/news-search.ts +35 -8
  19. package/src/cli/commands/system.ts +35 -1
  20. package/src/cli/commands/ticker.ts +100 -50
  21. package/src/cli/currency-hints.ts +27 -0
  22. package/src/cli/fx-pair.ts +41 -4
  23. package/src/cli/glossary.ts +57 -4
  24. package/src/cli/helpers.ts +36 -3
  25. package/src/cli/index.ts +4 -1
  26. package/src/cli/known-gaps.ts +37 -1
  27. package/src/cli/listing-arg.ts +37 -2
  28. package/src/cli/pane-functions/catalog.ts +3 -1
  29. package/src/cli/pane-functions/discovery.ts +15 -1
  30. package/src/cli/pane-functions/dom.ts +9 -4
  31. package/src/cli/pane-functions/freshness.ts +4 -4
  32. package/src/cli/pane-functions/function-help.ts +27 -3
  33. package/src/cli/pane-functions/headless.ts +6 -5
  34. package/src/cli/pane-functions/index.ts +3 -2
  35. package/src/cli/pane-functions/lock-note.ts +7 -4
  36. package/src/cli/pane-functions/resolver.ts +2 -1
  37. package/src/cli/quote-answer.ts +179 -0
  38. package/src/cli/series-search.ts +2 -1
  39. package/src/cli/types.ts +4 -0
  40. package/src/components/chart/composite/composite-chart.tsx +11 -2
  41. package/src/components/chart/composite/format.ts +13 -1
  42. package/src/components/chart/composite/level-labels.ts +30 -0
  43. package/src/components/chart/composite/levels.ts +17 -6
  44. package/src/components/chart/composite/panel-surface.tsx +58 -8
  45. package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
  46. package/src/components/index.ts +2 -0
  47. package/src/components/layout/pane/content.tsx +16 -14
  48. package/src/components/layout/pane/footer/index.tsx +22 -3
  49. package/src/components/layout/pane/footer/model.ts +99 -4
  50. package/src/components/layout/pane/header-tabs.tsx +15 -1
  51. package/src/components/onboarding/onboarding-frame.tsx +9 -11
  52. package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
  53. package/src/components/onboarding/wizard-model.ts +10 -3
  54. package/src/components/ui/button.tsx +11 -4
  55. package/src/components/ui/choice-dialog.tsx +8 -5
  56. package/src/components/ui/display.tsx +11 -6
  57. package/src/components/ui/fields.tsx +100 -20
  58. package/src/components/ui/icon.tsx +6 -1
  59. package/src/components/ui/list-row-lines.tsx +87 -0
  60. package/src/components/ui/list-view.tsx +14 -0
  61. package/src/components/ui/logo-mark.tsx +64 -0
  62. package/src/components/ui/number-step.ts +54 -0
  63. package/src/components/ui/select-button.tsx +5 -4
  64. package/src/components/ui/select-field.tsx +3 -3
  65. package/src/components/ui/status.tsx +17 -3
  66. package/src/components/ui/tabs.tsx +9 -1
  67. package/src/components/ui/toggle.tsx +17 -3
  68. package/src/core/state/app/instrument.ts +8 -3
  69. package/src/data/config/store/normalize.ts +4 -1
  70. package/src/data/plugin-cache.ts +32 -6
  71. package/src/docs/snapshot.json +5 -5
  72. package/src/i18n/es.ts +9 -3
  73. package/src/i18n/ja.ts +9 -3
  74. package/src/i18n/ko.ts +9 -3
  75. package/src/i18n/zh-cn.ts +10 -4
  76. package/src/i18n/zh-tw.ts +10 -4
  77. package/src/market-data/commodity-names.ts +80 -0
  78. package/src/market-data/coordinator/auxiliary.ts +3 -1
  79. package/src/market-data/coordinator/chart.ts +3 -1
  80. package/src/market-data/futures-board.ts +38 -1
  81. package/src/market-data/futures-contract.ts +91 -0
  82. package/src/market-data/fx-closes.ts +78 -1
  83. package/src/market-data/market/change-basis.ts +219 -0
  84. package/src/market-data/market/format.ts +28 -12
  85. package/src/market-data/market/freshness.ts +38 -0
  86. package/src/market-data/market/status.ts +55 -9
  87. package/src/market-data/market/year-to-date.ts +46 -0
  88. package/src/market-data/quotes/contributions.ts +12 -0
  89. package/src/market-data/quotes/delisting.ts +34 -0
  90. package/src/market-data/quotes/live-valuation.ts +23 -0
  91. package/src/market-data/quotes/resolution.ts +8 -2
  92. package/src/market-data/tax-equivalent.ts +10 -2
  93. package/src/notifications/app-notifier.ts +2 -2
  94. package/src/notifications/notification-log.ts +8 -6
  95. package/src/plugins/builtin/alerts/cli.ts +26 -18
  96. package/src/plugins/builtin/alerts/headless.ts +1 -1
  97. package/src/plugins/builtin/alerts/index.tsx +13 -4
  98. package/src/plugins/builtin/attention/trending.tsx +8 -6
  99. package/src/plugins/builtin/backtest/client.ts +5 -2
  100. package/src/plugins/builtin/backtest/window.ts +16 -3
  101. package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
  102. package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
  103. package/src/plugins/builtin/broker-manager/index.tsx +20 -9
  104. package/src/plugins/builtin/broker-manager/model.ts +5 -0
  105. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
  106. package/src/plugins/builtin/broker-manager/table.ts +5 -1
  107. package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
  108. package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
  109. package/src/plugins/builtin/broker-trading/command.ts +103 -0
  110. package/src/plugins/builtin/broker-trading/controller.ts +688 -0
  111. package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
  112. package/src/plugins/builtin/broker-trading/headless.ts +76 -0
  113. package/src/plugins/builtin/broker-trading/index.tsx +77 -0
  114. package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
  115. package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
  116. package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
  117. package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
  118. package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
  119. package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
  120. package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
  121. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
  122. package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
  123. package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
  124. package/src/plugins/builtin/buildout/format.ts +1 -0
  125. package/src/plugins/builtin/buildout/model/index.ts +16 -7
  126. package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
  127. package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
  128. package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
  129. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  130. package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
  131. package/src/plugins/builtin/chart-composer/headless.ts +2 -1
  132. package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
  133. package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
  134. package/src/plugins/builtin/chart-composer/presets.ts +2 -2
  135. package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
  136. package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
  137. package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
  138. package/src/plugins/builtin/chat/index.tsx +1 -1
  139. package/src/plugins/builtin/chat/status-widget.tsx +1 -1
  140. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
  141. package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
  142. package/src/plugins/builtin/commodity-balances/client.ts +6 -4
  143. package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
  144. package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
  145. package/src/plugins/builtin/commodity-balances/model.ts +22 -0
  146. package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
  147. package/src/plugins/builtin/company-kpis/client.ts +3 -2
  148. package/src/plugins/builtin/company-kpis/headless.ts +5 -7
  149. package/src/plugins/builtin/company-kpis/model.ts +85 -2
  150. package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
  151. package/src/plugins/builtin/composite-plugins.ts +3 -2
  152. package/src/plugins/builtin/cot/headless.ts +1 -1
  153. package/src/plugins/builtin/cot/index.tsx +1 -1
  154. package/src/plugins/builtin/cot/model.ts +35 -12
  155. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  156. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
  157. package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
  158. package/src/plugins/builtin/crypto-board/client.ts +33 -2
  159. package/src/plugins/builtin/crypto-board/headless.ts +5 -2
  160. package/src/plugins/builtin/crypto-board/model.ts +28 -8
  161. package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
  162. package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
  163. package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
  164. package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
  165. package/src/plugins/builtin/econ/calendar-model.ts +35 -0
  166. package/src/plugins/builtin/econ/index.tsx +31 -4
  167. package/src/plugins/builtin/econ/print-watch.ts +279 -0
  168. package/src/plugins/builtin/equity-screener/headless.ts +20 -5
  169. package/src/plugins/builtin/equity-screener/model.ts +40 -2
  170. package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
  171. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
  172. package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
  173. package/src/plugins/builtin/exposure/client.ts +4 -1
  174. package/src/plugins/builtin/exposure/editor.tsx +3 -3
  175. package/src/plugins/builtin/exposure/evidence.ts +4 -1
  176. package/src/plugins/builtin/exposure/holdings.ts +3 -1
  177. package/src/plugins/builtin/exposure/pane.tsx +37 -16
  178. package/src/plugins/builtin/filing-events/index.tsx +9 -3
  179. package/src/plugins/builtin/futures/table.tsx +6 -3
  180. package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
  181. package/src/plugins/builtin/insider/base-values.ts +72 -0
  182. package/src/plugins/builtin/insider/headless.ts +29 -4
  183. package/src/plugins/builtin/jobs/headless.ts +2 -2
  184. package/src/plugins/builtin/jobs/lock-note.ts +14 -3
  185. package/src/plugins/builtin/market-halts/model.ts +31 -6
  186. package/src/plugins/builtin/market-halts/pane.tsx +3 -0
  187. package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
  188. package/src/plugins/builtin/members/pane.tsx +8 -3
  189. package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
  190. package/src/plugins/builtin/news/wire/index.tsx +7 -2
  191. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
  192. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
  193. package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
  194. package/src/plugins/builtin/options/footer.ts +25 -20
  195. package/src/plugins/builtin/options/live-quotes.ts +33 -18
  196. package/src/plugins/builtin/options/view.tsx +77 -22
  197. package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
  198. package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
  199. package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
  200. package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
  201. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  202. package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
  203. package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
  204. package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
  205. package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
  206. package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
  207. package/src/plugins/builtin/primary-dealers/client.ts +137 -62
  208. package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
  209. package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
  210. package/src/plugins/builtin/primary-dealers/model.ts +179 -129
  211. package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
  212. package/src/plugins/builtin/research/analyst-model.ts +5 -1
  213. package/src/plugins/builtin/risk-factors/index.tsx +12 -3
  214. package/src/plugins/builtin/scanner/feed.ts +6 -1
  215. package/src/plugins/builtin/scanner/headless.ts +1 -1
  216. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  217. package/src/plugins/builtin/series-inventory.ts +1 -0
  218. package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
  219. package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
  220. package/src/plugins/builtin/shared/free-routes.ts +24 -0
  221. package/src/plugins/builtin/shared/headless-options.ts +11 -0
  222. package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
  223. package/src/plugins/builtin/shared/research-trading.tsx +62 -0
  224. package/src/plugins/builtin/social-mentions/headless.ts +1 -1
  225. package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
  226. package/src/plugins/builtin/themes/headless.ts +8 -2
  227. package/src/plugins/builtin/themes/model.ts +43 -10
  228. package/src/plugins/builtin/themes/pane.tsx +8 -6
  229. package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
  230. package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
  231. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
  232. package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
  233. package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
  234. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
  235. package/src/plugins/builtin/time-sales/pane.tsx +2 -2
  236. package/src/plugins/builtin/trace-bonds/client.ts +14 -3
  237. package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
  238. package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
  239. package/src/plugins/builtin-editorial.ts +1 -1
  240. package/src/plugins/compat.ts +3 -0
  241. package/src/plugins/external-runtime.ts +5 -0
  242. package/src/plugins/loader.ts +6 -0
  243. package/src/plugins/plugin-icon.ts +61 -0
  244. package/src/plugins/registry/host-actions.ts +2 -1
  245. package/src/plugins/registry/index.ts +9 -1
  246. package/src/plugins/runtime/context.tsx +5 -0
  247. package/src/plugins/runtime/index.tsx +3 -0
  248. package/src/portfolio/column-values.ts +13 -4
  249. package/src/portfolio/cost-fx.ts +5 -28
  250. package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
  251. package/src/public/broker.ts +8 -0
  252. package/src/public/market-data.ts +5 -0
  253. package/src/renderers/dom/desktop/control-styles.ts +7 -1
  254. package/src/renderers/dom/desktop/controls.tsx +39 -7
  255. package/src/renderers/dom/desktop/icons.tsx +14 -0
  256. package/src/renderers/dom/desktop/list-view.tsx +51 -19
  257. package/src/renderers/dom/desktop/menu.tsx +10 -1
  258. package/src/renderers/dom/desktop/select-field.tsx +8 -6
  259. package/src/renderers/dom/host/style.ts +6 -1
  260. package/src/renderers/dom/host/tabs.tsx +6 -0
  261. package/src/renderers/dom/styles.css +12 -5
  262. package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
  263. package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
  264. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  265. package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
  266. package/src/renderers/electrobun/view/external-plugins.ts +1 -0
  267. package/src/series/validate.ts +2 -2
  268. package/src/sources/gloomberb-cloud/index.ts +50 -17
  269. package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
  270. package/src/sources/history-result.ts +3 -0
  271. package/src/sources/provider-errors.ts +3 -1
  272. package/src/sources/provider-router/history.ts +49 -6
  273. package/src/sources/provider-router/index.ts +2 -1
  274. package/src/state/app/activity.ts +13 -0
  275. package/src/sync/controller.ts +4 -0
  276. package/src/sync/core-contributors.ts +62 -11
  277. package/src/sync/local-only.ts +121 -0
  278. package/src/sync/react.ts +6 -0
  279. package/src/sync/types.ts +3 -0
  280. package/src/tickers/listing-fate.ts +27 -8
  281. package/src/tickers/search/index.ts +24 -3
  282. package/src/time-series/price-comparison.ts +35 -0
  283. package/src/time-series/resolve.ts +52 -11
  284. package/src/time-series/spec.ts +1 -1
  285. package/src/time-series/types.ts +5 -0
  286. package/src/types/broker.ts +56 -1
  287. package/src/types/config.ts +6 -0
  288. package/src/types/data-provider.ts +5 -1
  289. package/src/types/financials.ts +23 -0
  290. package/src/types/headless.ts +12 -2
  291. package/src/types/instrument.ts +4 -0
  292. package/src/types/plugin.ts +24 -6
  293. package/src/types/price-history.ts +6 -1
  294. package/src/types/trading.ts +67 -0
  295. package/src/ui/host.tsx +4 -0
  296. package/src/utils/base-currency.ts +108 -0
  297. package/src/utils/currency-names.ts +128 -0
  298. package/src/utils/currency-pair.ts +2 -1
  299. package/src/utils/currency-units.ts +32 -1
  300. package/src/utils/dom-screenshot.ts +22 -9
  301. package/src/utils/exchange-notation.generated.ts +360 -0
  302. package/src/utils/exchanges.ts +15 -322
  303. package/src/utils/format.ts +31 -1
  304. package/src/utils/instrument-identity.ts +38 -1
  305. package/src/utils/options.ts +46 -1
  306. package/src/utils/price-history.ts +4 -0
  307. package/src/utils/reported-money.ts +8 -4
@@ -2,7 +2,7 @@ import { cliStyles, cliTerminalWidth, renderDefinitions, renderSection, wrapText
2
2
 
3
3
  /**
4
4
  * Plain-words meanings of the rates, auction, credit and bond yield, energy, perpetual-futures, portfolio,
5
- * fund, bond fund (SEC yield, tax-equivalent yield, muni/Treasury ratio), short interest, quote-board, futures (basis, contango, roll yield), VIX curve (term structure, 3M/30D, VVIX) and economic-calendar terms the functions
5
+ * fund, bond fund (SEC yield, tax-equivalent yield, muni/Treasury ratio), short interest, quote-board, futures (basis, contango, roll yield), VIX curve (term structure, 3M/30D, VVIX), CFTC positioning (COT trader classes, net % of open interest) and economic-calendar terms the functions
6
6
  * show in their rows, headers and footers.
7
7
  * `catalog glossary` lists them, `catalog explain <term>` prints one, and
8
8
  * `fn <function> --explain` and `portfolio show --explain` follow a report with
@@ -242,7 +242,7 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
242
242
  definition: "The first date the new rate level appears in the data, not necessarily the announcement or effective date.",
243
243
  },
244
244
  {
245
- term: "Pctl 1Y", aliases: ["percentile", "1y percentile", "pctl"], functions: ["BTMM", "WIRP", "CBR", "CDX", "SOVR"],
245
+ term: "Pctl 1Y", aliases: ["percentile", "1y percentile", "1-year percentile", "pctl"], functions: ["BTMM", "WIRP", "CBR", "CDX", "SOVR", "COT"],
246
246
  definition: "Where today's value ranks against the past year of the same series: 100 is the highest, 0 the lowest.",
247
247
  },
248
248
  {
@@ -360,8 +360,8 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
360
360
  + "a simple annualisation of the rate now, not funding actually paid.",
361
361
  },
362
362
  {
363
- term: "Open interest", short: "OI", aliases: ["open interest usd"], functions: ["PERP"],
364
- definition: "The total value of contracts that are open and not yet closed, counting each buyer and seller pair once, usually in dollars. "
363
+ term: "Open interest", short: "OI", aliases: ["open interest usd"], functions: ["PERP", "COT"],
364
+ definition: "The contracts that are open and not yet closed, counting each buyer and seller pair once: COT counts them, PERP gives their value, usually in dollars. "
365
365
  + "It rises as new positions open and falls as they close.",
366
366
  },
367
367
  {
@@ -631,6 +631,59 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
631
631
  definition: "A futures contract's official price for the day, set by its exchange from trading near the close. "
632
632
  + "The day's change and margin are measured from it, and it can differ from the last trade. FUT's PREV is the previous one.",
633
633
  },
634
+ // CFTC positioning (COT): the legacy report's trader classes, then the disaggregated report's.
635
+ {
636
+ term: "Commitments of Traders", short: "COT", aliases: ["cot report", "commitment of traders", "cftc", "cftc report", "cftc positioning", "positioning"],
637
+ functions: ["COT"], shown: ["CFTC", "COT"],
638
+ definition: "The CFTC's weekly count of who holds each US futures market: open contracts split by type of trader, as of Tuesday and published that Friday. "
639
+ + "It shows whether speculators or hedgers are net long or short, and how that moved since the week before.",
640
+ },
641
+ {
642
+ term: "Noncommercial", aliases: ["non-commercial", "noncommercials", "large speculators", "speculators", "large specs"], functions: ["COT"],
643
+ definition: "Large traders who do not hedge a business with the contract, such as funds and other speculators. "
644
+ + "Their net position is the one most often read as the market's bet.",
645
+ },
646
+ {
647
+ term: "Commercial", aliases: ["commercials", "hedgers", "commercial hedgers"], functions: ["COT"],
648
+ definition: "Large traders who use the contract to hedge a business: producers, processors, merchants and dealers. "
649
+ + "In a commodity they are usually net short, selling ahead what they will grow, mine or deliver.",
650
+ },
651
+ {
652
+ term: "Nonreportable", aliases: ["non-reportable", "nonreportables", "small traders", "small speculators"], functions: ["COT"],
653
+ definition: "Traders too small to report their positions to the CFTC, counted as open interest less every reported position. Mostly smaller speculators.",
654
+ },
655
+ {
656
+ term: "Net position", short: "Net", aliases: ["net long", "net short", "net positioning", "net spec"], functions: ["COT"], shown: ["Net"],
657
+ definition: "A group's long contracts less its short ones: positive is net long, negative net short.",
658
+ },
659
+ {
660
+ term: "Net % of open interest", short: "Net % OI", aliases: ["% of open interest", "percent of open interest", "net percent of open interest"], functions: ["COT"],
661
+ definition: "A group's net position as a share of all open contracts, so markets of different sizes compare: +8.3% means its longs exceed its shorts by 8.3% of open interest.",
662
+ },
663
+ {
664
+ term: "Weekly change", short: "1W Δ", aliases: ["1w change", "week change", "weekly net change"], functions: ["COT"],
665
+ definition: "How far a group's net position moved since the previous weekly report, in contracts.",
666
+ },
667
+ {
668
+ term: "Pctl 3Y", aliases: ["3y percentile", "3-year percentile", "three-year percentile"], functions: ["COT"],
669
+ definition: "Where this week's net position ranks against the past three years of weekly reports: 100 is the most net long it has been, 0 the most net short.",
670
+ },
671
+ {
672
+ term: "Managed money", aliases: ["money managers", "managed money traders", "hedge funds", "ctas"], functions: ["COT"], shown: ["Managed Money"],
673
+ definition: "The disaggregated report's funds that trade futures for clients, such as hedge funds and CTAs: the speculators most often watched.",
674
+ },
675
+ {
676
+ term: "Producer/merchant", aliases: ["producers", "merchants", "producer merchant", "processors"], functions: ["COT"], shown: ["Producer/Merchant"],
677
+ definition: "The disaggregated report's growers, miners, processors and merchants that hedge the physical commodity they produce or handle.",
678
+ },
679
+ {
680
+ term: "Swap dealers", aliases: ["swap dealer", "swaps dealers"], functions: ["COT"], shown: ["Swap Dealers"],
681
+ definition: "The disaggregated report's banks and dealers that hedge the commodity swaps they sell to clients, often index investors.",
682
+ },
683
+ {
684
+ term: "Other reportables", aliases: ["other reportable", "other reportable traders"], functions: ["COT"], shown: ["Other Reportables"],
685
+ definition: "The disaggregated report's large traders that fit none of its other classes, such as proprietary traders and family offices.",
686
+ },
634
687
  // Futures basis and curve shape (CTM, FUT, PERP).
635
688
  {
636
689
  term: "Futures basis", aliases: ["basis", "basis trade", "cash and carry", "cash-and-carry", "vs spot", "spot spread"],
@@ -1,10 +1,15 @@
1
1
  import {
2
2
  formatCompact,
3
3
  formatCurrency,
4
+ formatCurrencyCode,
4
5
  formatNumber,
5
6
  formatPercentRaw,
6
7
  } from "../utils/format";
7
- import { formatMarketPriceWithCurrency, type MarketFormatOptions } from "../market-data/market/format";
8
+ import { formatMarketChangeWithCurrency, formatMarketPriceWithCurrency, type MarketFormatOptions } from "../market-data/market/format";
9
+ import { changeBasisLabel, changeBasisNote, quoteChangeBasis } from "../market-data/market/change-basis";
10
+ import type { FuturesContractTermsPayload } from "../api-client/futures-curve";
11
+ import type { ActiveQuoteDisplay } from "../market-data/market/status";
12
+ import type { Quote } from "../types/financials";
8
13
  import { cliStyles, colorBySign } from "../utils/cli-output";
9
14
  import type { AppConfig } from "../types/config";
10
15
  import type { Watchlist, TickerRecord } from "../types/ticker";
@@ -12,8 +17,36 @@ import { isFiniteNumber } from "../utils/guards";
12
17
  import { formatUtcTime } from "../utils/utc-time";
13
18
  import { findNamedCollection } from "../portfolio/collection-name";
14
19
 
15
- export function formatSignedCurrency(value: number, currency: string): string {
16
- return value > 0 ? `+${formatCurrency(value, currency)}` : formatCurrency(value, currency);
20
+ /** A signed amount with its sign ($), or with `code` its code (+12.50 CAD) as a report for another base currency writes it. */
21
+ export function formatSignedCurrency(value: number, currency: string, code = false): string {
22
+ const text = (code ? formatCurrencyCode : formatCurrency)(value, currency);
23
+ return value > 0 ? `+${text}` : text;
24
+ }
25
+
26
+ /**
27
+ * The Change line of `ticker` and of `quote`'s futures block: the regular
28
+ * session's move and its percent, colored by sign, and what the move is
29
+ * measured from, "-HK$0.10 (-1.05%) vs Fri 16 Oct close" (a future's from
30
+ * its terms, "vs Fri 16 Oct settle"), with the venue's holidays since that
31
+ * close on the line under it ("HKEX closed Mon 19 Oct, holiday").
32
+ */
33
+ export function quoteChangeText(
34
+ quote: Quote,
35
+ session: Pick<ActiveQuoteDisplay, "price" | "change" | "changePercent">,
36
+ options: MarketFormatOptions,
37
+ terms?: Pick<FuturesContractTermsPayload, "settlement" | "spec"> | null,
38
+ now = Date.now(),
39
+ ): string {
40
+ // A listing that no longer trades: its last close moves no more.
41
+ if (quote.delisting) return "—";
42
+ const move = colorBySign(
43
+ `${formatMarketChangeWithCurrency(session.change, quote.currency ?? "USD", options, session.price)} (${formatPercentRaw(session.changePercent)})`,
44
+ session.change,
45
+ );
46
+ const basis = quoteChangeBasis(quote, { terms });
47
+ if (!basis) return move;
48
+ const note = changeBasisNote(basis, now);
49
+ return `${move} ${cliStyles.muted(changeBasisLabel(basis, now))}${note ? `\n${cliStyles.muted(note)}` : ""}`;
17
50
  }
18
51
 
19
52
  // Text-table cell formats. They leave missing values blank; CSV and JSON keep the raw numbers.
package/src/cli/index.ts CHANGED
@@ -47,6 +47,7 @@ import {
47
47
  runPaneScreenshot,
48
48
  } from "./pane-functions";
49
49
  import { catalogPointer, tickerPointer, type CatalogCommandHit } from "./unknown-command";
50
+ import { exportCommandPointer } from "./known-gaps";
50
51
  import { catalogFredSeriesIds } from "./series-search";
51
52
  import { primeBoardCoins } from "./board-coins";
52
53
 
@@ -382,7 +383,9 @@ function failUnknownCommand(token: string, commands: CliCommandDef[], pointers:
382
383
  [`Run: ${pointers.nearFn.command}`, ticker].filter(Boolean).join("\n"),
383
384
  );
384
385
  }
385
- const catalog = suggestion ? null : catalogPointer(token, pointers.hits, pointers.args);
386
+ // Exporting is a flag, not a command: `export LMT` says how, rather than listing functions that mention exports.
387
+ const exported = suggestion ? null : exportCommandPointer(token, pointers.args);
388
+ const catalog = suggestion ? null : exported ?? catalogPointer(token, pointers.hits, pointers.args);
386
389
  const help = suggestion
387
390
  ? `Did you mean ${suggestion}? Run gloomberb help to see every command.`
388
391
  : catalog ? `${catalog}\nRun gloomberb help to see every command.` : "Run gloomberb help to see every command.";
@@ -109,6 +109,23 @@ const BANK_FIGURES_TOPIC = {
109
109
  closest: [["EQS", "NIM%, Pro: gloomberb fn EQS --metric kpiNimPercent"]],
110
110
  } as const;
111
111
 
112
+ /**
113
+ * How to export: a flag on every command, which `export LMT`, `catalog export` and `catalog csv` look for as a
114
+ * command or a function. One example, writing a file.
115
+ */
116
+ function exportLine(symbol = "LMT"): string {
117
+ return `Export: add --csv or --json to any command to print its result as CSV or JSON, e.g. gloomberb history ${symbol} --csv > ${
118
+ symbol.toLowerCase().replace(/[^a-z0-9]+/g, "-")}.csv`;
119
+ }
120
+
121
+ const EXPORT_POINTER = {
122
+ names: ["export", "exporting", "csv", "json", "excel", "spreadsheet", "xlsx"],
123
+ words: ["data", "to", "as", "a", "an", "the", "in", "into", "file", "files", "table", "tables", "result", "results", "output",
124
+ "format", "save", "how", "can", "i", "my", "report", "reports", "command", "commands"],
125
+ line: exportLine(),
126
+ leads: true,
127
+ } as const;
128
+
112
129
  /** "bank", "bank figures": beside the functions a bank's name finds (CBR), where its figures are. */
113
130
  const BANK_FIGURES_POINTER = {
114
131
  names: ["bank", "banks", "banking"],
@@ -193,8 +210,9 @@ export function topicPointer(query: string): { message: string; codes: string[]
193
210
  * "dark pool"; "drip", "dividend income", but not "income statement" or "net income"; "tether", "depeg",
194
211
  * "usdc peg", but not "peg ratio".
195
212
  */
196
- const COMMAND_POINTERS: ReadonlyArray<{ names: readonly string[]; words: readonly string[]; line: string }> = [
213
+ const COMMAND_POINTERS: ReadonlyArray<{ names: readonly string[]; words: readonly string[]; line: string; leads?: true }> = [
197
214
  BANK_FIGURES_POINTER,
215
+ EXPORT_POINTER,
198
216
  {
199
217
  names: ["theme", "themes", "light", "dark", "colorblind", "colourblind", "blind"],
200
218
  words: ["mode", "color", "colour", "colors", "colours", "high", "contrast", "change", "switch", "set", "setting", "settings"],
@@ -218,6 +236,24 @@ export function commandPointer(query: string): string | null {
218
236
  return findCommandPointer(query)?.line ?? null;
219
237
  }
220
238
 
239
+ /**
240
+ * Whether the query's command line answers it ahead of the functions its words find: "csv" is the flag, not
241
+ * CDS, whose description happens to mention it.
242
+ */
243
+ export function commandPointerLeads(query: string): boolean {
244
+ return findCommandPointer(query)?.leads === true;
245
+ }
246
+
247
+ /**
248
+ * The line for a command word typed as `gloomberb export LMT`: exporting is a flag every command takes, not a
249
+ * command, so the example runs on the symbol typed. Null for any other word.
250
+ */
251
+ export function exportCommandPointer(token: string, args: readonly string[]): string | null {
252
+ if (findCommandPointer(token) !== EXPORT_POINTER) return null;
253
+ const symbol = args.find((arg) => /^\^?[A-Z0-9][A-Z0-9.=-]{0,11}(?::[A-Z0-9]{1,8})?$/.test(arg) && /[A-Z]/.test(arg));
254
+ return exportLine(symbol);
255
+ }
256
+
221
257
  function findCommandPointer(query: string): (typeof COMMAND_POINTERS)[number] | null {
222
258
  const asked = words(query);
223
259
  if (asked.length === 0) return null;
@@ -42,6 +42,7 @@ import { classifyInstrumentType, kindFromListingSyntax } from "../tickers/instru
42
42
  import type { InstrumentOfKind } from "../tickers/instrument-coverage";
43
43
  import { brokerIndexCode, brokerIndexReadAs } from "./index-roots";
44
44
  import { readTerminalListingCode, terminalCodeReadAs } from "../tickers/terminal-codes";
45
+ import { listingAbroadVenue } from "../utils/sec";
45
46
 
46
47
  /** The `--exchange` option of every command that takes a ticker. */
47
48
  export const EXCHANGE_OPTION = {
@@ -342,7 +343,7 @@ export function listingChoiceNote(
342
343
  }
343
344
 
344
345
  // Quotes of these never name a company's listing, so they are never a choice between companies.
345
- const UNLISTED_TYPES = new Set(["FUTURE", "INDEX", "CURRENCY", "CRYPTOCURRENCY", "OPTION"]);
346
+ export const UNLISTED_TYPES = new Set(["FUTURE", "INDEX", "CURRENCY", "CRYPTOCURRENCY", "OPTION"]);
346
347
 
347
348
  /** Whether the data service chose the listing: a bare symbol, with no venue named and none saved. */
348
349
  function isBareListing(listing: Pick<CliListing, "exchange" | "saved">): boolean {
@@ -860,11 +861,43 @@ async function builderDexMarket(listing: ListingArg, deps: ListingDeps): Promise
860
861
  : null;
861
862
  }
862
863
 
864
+ /** A symbol written with a venue's suffix, as data vendors write a listing abroad: RY.TO, 7203.T, VOD.L, 0700.HK. */
865
+ const VENUE_SUFFIX = /^([A-Z0-9][A-Z0-9&-]*)\.([A-Z]{1,3})$/;
866
+
867
+ /**
868
+ * RY.TO, 7203.T, VOD.L: a listing abroad written with a data vendor's venue
869
+ * suffix, read as the listing its own quote names (RY:TSX, 7203:JPX,
870
+ * VOD:LSE), the way `quote` names it, so data that is read by venue (a
871
+ * company's SEC filings, data that covers US listings only) finds the same
872
+ * listing the quote is for. A US share class (BRK.B, quoted on NYSE), a
873
+ * suffix no quote answers and a named venue stay as typed.
874
+ */
875
+ async function readVenueSuffix(listing: ListingArg, deps: Pick<ListingDeps, "dataProvider">): Promise<ListingArg | null> {
876
+ const match = listing.exchange ? null : VENUE_SUFFIX.exec(listing.symbol);
877
+ if (!match) return null;
878
+ let quote: Quote | null;
879
+ try {
880
+ quote = await deps.dataProvider.getQuote(listing.symbol, "");
881
+ } catch (error) {
882
+ // The command's own request then says what is wrong with the symbol.
883
+ if (!isNoProviderError(error)) logFailure("cli.listing-arg", "Quote venue suffix failed")(error);
884
+ return null;
885
+ }
886
+ const venue = canonicalExchange(quote?.listingExchangeName || quote?.exchangeName);
887
+ const key = `${match[1]}:${venue}`;
888
+ if (!venue || !isKnownExchangeCode(venue) || !listingAbroadVenue(key)) return null;
889
+ return { symbol: match[1]!, exchange: venue, key, readAs: `Read ${listing.symbol} as ${key}.` };
890
+ }
891
+
863
892
  export async function resolveCliListing(
864
893
  raw: string,
865
894
  exchangeOption: string | null | undefined,
866
895
  deps: ListingDeps,
867
- options: { ownExchangeWins?: boolean } = {},
896
+ options: {
897
+ ownExchangeWins?: boolean;
898
+ /** Read a venue suffix (RY.TO) as the listing its quote names (RY:TSX): `fn` and `shot`, whose functions read data by venue. */
899
+ venueSuffix?: boolean;
900
+ } = {},
868
901
  ): Promise<CliListing> {
869
902
  // "JPM US Equity" is Bloomberg's code for JPM; "Putnam US Equity", whose root is no US ticker, is a name.
870
903
  const typed = stripStrayEdges(raw).text || raw;
@@ -879,6 +912,8 @@ export async function resolveCliListing(
879
912
  // USD-NGN and USDNGN are the pair USDNGN=X unless a security is listed under those letters.
880
913
  const pair = parsed.exchange ? null : await readAmbiguousCurrencyPair(parsed.symbol, deps);
881
914
  if (pair) parsed = { symbol: pair, exchange: "", key: pair };
915
+ const suffixed = pair || named || !options.venueSuffix ? null : await readVenueSuffix(parsed, deps);
916
+ if (suffixed) parsed = parsed.readAs ? { ...suffixed, readAs: `${parsed.readAs} ${suffixed.readAs}` } : suffixed;
882
917
  try {
883
918
  await checkListingExchange(parsed, deps);
884
919
  } catch (error) {
@@ -694,13 +694,15 @@ export function renderPaneCatalogReport(
694
694
  series: readonly SeriesMatch[] = [],
695
695
  /** The `config` command a query names a setting of ("light theme"), printed last, beside the functions. */
696
696
  setting: string | null = null,
697
+ /** The setting answers the query ahead of the functions its words find (`csv` is the flag, not CDS): printed first. */
698
+ settingFirst = false,
697
699
  ): string {
698
700
  const query = args.query.trim().toLowerCase();
699
701
  const exact = query
700
702
  ? entries.find((entry) => entry.token.toLowerCase() === query)
701
703
  ?? entries.find((entry) => entry.aliases.some((alias) => alias.toLowerCase() === query))
702
704
  : undefined;
703
- const withSetting = (text: string) => [text, setting ?? ""].filter(Boolean).join("\n\n");
705
+ const withSetting = (text: string) => (settingFirst ? [setting ?? "", text] : [text, setting ?? ""]).filter(Boolean).join("\n\n");
704
706
  if (exact) return withSetting([renderCatalogEntry(exact), renderAlsoMatches(exact, entries, query)].filter(Boolean).join("\n\n"));
705
707
 
706
708
  const width = Math.min(cliTerminalWidth() ?? CATALOG_TEXT_WIDTH, CATALOG_TEXT_WIDTH);
@@ -1,6 +1,7 @@
1
1
  import { ConnectionHealthRegistry } from "../../core/connection-health";
2
2
  import type { TickerRepository } from "../../data/ticker-repository";
3
3
  import type { AppConfig } from "../../types/config";
4
+ import type { BrokerAdapter } from "../../types/broker";
4
5
  import type { DataProvider } from "../../types/data-provider";
5
6
  import type { PersistedResourceValue } from "../../types/persistence";
6
7
  import type {
@@ -20,6 +21,7 @@ export interface PaneDiscoveryOptions {
20
21
  tickerRepository?: TickerRepository;
21
22
  panes?: Map<string, PaneDef>;
22
23
  paneTemplates?: Map<string, PaneTemplateDef>;
24
+ brokers?: Map<string, BrokerAdapter>;
23
25
  }
24
26
 
25
27
  /** Throws only if a plugin actually reaches for a dependency discovery has no answer for. */
@@ -43,10 +45,12 @@ export function createPaneDiscoveryContext(options: PaneDiscoveryOptions): Gloom
43
45
  } {
44
46
  const panes = options.panes ?? new Map<string, PaneDef>();
45
47
  const paneTemplates = options.paneTemplates ?? new Map<string, PaneTemplateDef>();
48
+ const brokers = options.brokers ?? new Map<string, BrokerAdapter>();
46
49
  return {
47
50
  ...buildDiscoveryContext({
48
51
  panes,
49
52
  paneTemplates,
53
+ brokers,
50
54
  getConfig: options.getConfig,
51
55
  marketData: options.marketData ?? unavailable<DataProvider>("Market data"),
52
56
  tickerRepository: options.tickerRepository ?? unavailable<TickerRepository>("The ticker repository"),
@@ -60,6 +64,7 @@ export function createPaneDiscoveryContext(options: PaneDiscoveryOptions): Gloom
60
64
  function buildDiscoveryContext({
61
65
  panes,
62
66
  paneTemplates,
67
+ brokers,
63
68
  getConfig,
64
69
  marketData,
65
70
  tickerRepository,
@@ -67,6 +72,7 @@ function buildDiscoveryContext({
67
72
  }: {
68
73
  panes: Map<string, PaneDef>;
69
74
  paneTemplates: Map<string, PaneTemplateDef>;
75
+ brokers: Map<string, BrokerAdapter>;
70
76
  getConfig: () => AppConfig;
71
77
  marketData: DataProvider;
72
78
  tickerRepository: TickerRepository;
@@ -84,7 +90,8 @@ function buildDiscoveryContext({
84
90
  registerCommand: () => {},
85
91
  registerCommandBarSearchProvider: () => () => {},
86
92
  registerColumn: () => {},
87
- registerBroker: () => {},
93
+ registerBroker: (broker) => { brokers.set(broker.id, broker); },
94
+ getBrokerAdapter: (brokerType) => brokers.get(brokerType) ?? null,
88
95
  registerCapability: () => {},
89
96
  registerTickerResearchTab: () => {},
90
97
  registerShortcut: () => {},
@@ -155,10 +162,15 @@ function buildDiscoveryContext({
155
162
 
156
163
  export async function createPaneCatalog(context: MarketContext, plugins: GloomPlugin[]): Promise<PaneFunctionCatalog> {
157
164
  const setupPlugins: GloomPlugin[] = [];
165
+ const brokers = new Map<string, BrokerAdapter>();
166
+ const previousResolver = context.resolveBroker;
167
+ const resolveBroker = (brokerType: string) => brokers.get(brokerType) ?? null;
168
+ context.resolveBroker = resolveBroker;
158
169
  const { panes, paneTemplates, ...discoveryContext } = createPaneDiscoveryContext({
159
170
  getConfig: () => context.config,
160
171
  marketData: context.dataProvider,
161
172
  tickerRepository: context.store,
173
+ brokers,
162
174
  });
163
175
 
164
176
  const destroy = () => {
@@ -166,6 +178,8 @@ export async function createPaneCatalog(context: MarketContext, plugins: GloomPl
166
178
  for (const plugin of setupPlugins.splice(0).reverse()) {
167
179
  try { plugin.dispose?.(); } catch (error) { failure ??= error; }
168
180
  }
181
+ brokers.clear();
182
+ if (context.resolveBroker === resolveBroker) context.resolveBroker = previousResolver;
169
183
  if (failure) throw failure;
170
184
  };
171
185
 
@@ -6,6 +6,8 @@ import type { PaneFunctionReport } from "./report";
6
6
  import type { ResolvedPaneFunction } from "./resolver";
7
7
  import { collectShotSymbols } from "./data";
8
8
  import { lockNoteLines } from "./lock-note";
9
+ import { createHeadlessPaneContext } from "./headless";
10
+ import type { HeadlessPaneContext } from "../../types/headless";
9
11
  import { lockNoteReason } from "../../plugins/builtin/shared/pro-lock-note";
10
12
  import { renderDesktopShot, type PaneScreenshotResult } from "./screenshot";
11
13
  import { deriveRenderedFreshness, formatFreshnessLine, type ReportFreshness } from "./freshness";
@@ -204,10 +206,12 @@ function reportRows(result: PaneScreenshotResult): DesktopPaneShotRenderedRow[]
204
206
  }];
205
207
  }
206
208
 
207
- export function buildDomPaneReportFromRender(
209
+ export async function buildDomPaneReportFromRender(
208
210
  resolved: ResolvedPaneFunction,
209
211
  result: PaneScreenshotResult,
210
- ): PaneFunctionReport {
212
+ /** Reads data for the lock note, which names a free route only when it has data for the symbol. */
213
+ context?: HeadlessPaneContext,
214
+ ): Promise<PaneFunctionReport> {
211
215
  const rows = reportRows(result);
212
216
  const hasStructuredRows = (result.render.reportRows ?? result.render.rows).length > 0;
213
217
  // A table that reported every row is whole, whatever the viewport cut from the capture.
@@ -227,11 +231,12 @@ export function buildDomPaneReportFromRender(
227
231
  // what is locked and how to reach it, and cites no source.
228
232
  const gate = !hasStructuredRows ? result.render.accessGate ?? null : null;
229
233
  const lockNote = gate
230
- ? resolved.pane.reportLockNote?.({
234
+ ? await resolved.pane.reportLockNote?.({
231
235
  gate,
232
236
  symbol: result.symbols[0] ?? null,
233
237
  token: resolved.token,
234
238
  ...gate === "sign-in" && asksToVerifyEmail(result.render.visibleText ?? "") ? { verifyEmail: true } : {},
239
+ ...context ? { context } : {},
235
240
  }) ?? null
236
241
  : null;
237
242
  const locked = gate === "sign-in" || lockNote ? gate : null;
@@ -399,7 +404,7 @@ export async function buildDomFunctionReport(
399
404
  const failure = renderedReportFailure(result.render.semanticUi ?? []);
400
405
  if (failure) throw new RenderedReportFailure(failure.message, failure.details);
401
406
  try {
402
- return buildDomPaneReportFromRender(resolved, result);
407
+ return await buildDomPaneReportFromRender(resolved, result, createHeadlessPaneContext(resolved, context, new AbortController().signal));
403
408
  } catch (error) {
404
409
  return failedDomReport(resolved, rawArg, error);
405
410
  }
@@ -43,7 +43,7 @@ export interface ReportFreshness {
43
43
  feed?: "live" | "delayed";
44
44
  basis?: string;
45
45
  /** Set when the dates are the start of a period: the status line then reads `Sep 2026 (monthly)`. */
46
- periodicity?: "monthly" | "quarterly" | "annual";
46
+ periodicity?: "monthly" | "quarterly" | "semiannual" | "annual";
47
47
  /**
48
48
  * The newest bar's week or month had not ended, and the last day it covers
49
49
  * so far (`asOf`, when known): the status line reads `month to date, through
@@ -335,7 +335,7 @@ function datasetLagDays(observed: ReportTime, periodicity: ReportFreshness["peri
335
335
  const month = day.getUTCMonth();
336
336
  const through = !periodicity ? Date.UTC(year, month, day.getUTCDate())
337
337
  // Day 0 of the month after the period is its last day.
338
- : periodicity === "monthly" ? Date.UTC(year, month + 1, 0)
338
+ : periodicity === "monthly" || periodicity === "semiannual" ? Date.UTC(year, month + 1, 0)
339
339
  : periodicity === "quarterly" ? Date.UTC(year, month - (month % 3) + 3, 0)
340
340
  : Date.UTC(year, 12, 0);
341
341
  const today = new Date(now);
@@ -589,13 +589,13 @@ function tradingDayText(date: string, market: string | undefined): string {
589
589
  return market ? `${market} trading day ${dayText(date)}` : dayText(date);
590
590
  }
591
591
 
592
- /** `Sep 2026`, `Q2 2026` or `2026` for the period a date starts. */
592
+ /** `Sep 2026`, `Q2 2026` or `2026` for the period a date starts; a semiannual estimate's month. */
593
593
  function periodText(date: string, periodicity: NonNullable<ReportFreshness["periodicity"]>): string {
594
594
  const day = new Date(`${date.slice(0, 10)}T00:00:00Z`);
595
595
  if (!Number.isFinite(day.getTime())) return date;
596
596
  const year = day.getUTCFullYear();
597
597
  const month = day.getUTCMonth();
598
- return periodicity === "monthly" ? `${MONTHS[month]} ${year}`
598
+ return periodicity === "monthly" || periodicity === "semiannual" ? `${MONTHS[month]} ${year}`
599
599
  : periodicity === "quarterly" ? `Q${Math.floor(month / 3) + 1} ${year}`
600
600
  : `${year}`;
601
601
  }
@@ -93,7 +93,7 @@ const POP_OUT = key("p", "op out");
93
93
  const STEP = key("←/→", " step");
94
94
  /** THEM and MEMB members: open them in RRG, CORR, SIW or RIPL, or save them as a watchlist. */
95
95
  const MEMBERS_MENU = key(".", " open members in, save as watchlist");
96
- const CHART_KEYS = [key("s", "eries"), key("i", "ndicators"), key("t", "imeframe"), key("f", "ormulas")];
96
+ const CHART_KEYS = [key("s", "eries"), key("i", "ndicators"), key("b", "ars"), key("f", "ormulas")];
97
97
 
98
98
  export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
99
99
  // Research companies
@@ -971,7 +971,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
971
971
  bloomberg: [],
972
972
  },
973
973
  PDLR: {
974
- summary: "Weekly primary dealer net positions and settlement fails across Treasuries and mortgage-backed securities.",
974
+ summary: "Weekly primary dealer net positions with history, by sector and Treasury tenor (bills, coupons, TIPS, FRN), and settlement fails.",
975
975
  usage: ["PDLR"],
976
976
  keys: [TABS],
977
977
  data: same("Weekly, as published"),
@@ -1110,7 +1110,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
1110
1110
  docs: "PORT",
1111
1111
  },
1112
1112
  ALRT: {
1113
- summary: "Your price alerts with current price, distance to target and trigger state, plus alerts days before earnings, CPI or FOMC and market and filing event alerts.",
1113
+ summary: "Your price alerts with current price, distance to target and trigger state, plus alerts on earnings, CPI and FOMC dates, filings and 13F, news keywords, analyst changes, 52-week highs and lows, unusual volume, short interest, insider and congress trades, catalysts, IV spikes, options flow and perps.",
1114
1114
  usage: ["ALRT"],
1115
1115
  keys: [key("a", "dd alert"), key("e", "dit"), key("d", "elete"), key("m", "re-arm")],
1116
1116
  data: QUOTES,
@@ -1238,6 +1238,30 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
1238
1238
  data: null,
1239
1239
  bloomberg: [],
1240
1240
  },
1241
+ BUY: {
1242
+ summary: "Open the shared Trade ticket with a buy quantity and optional limit price. Focus starts on Review; choose an account and review the broker preview before confirming.",
1243
+ usage: ["BUY AAPL 10", "BUY AAPL 10 150.25"],
1244
+ keys: [key("r", "eview"), key("o", "rders")],
1245
+ data: same("Broker account and quote"),
1246
+ bloomberg: [],
1247
+ docs: "BR",
1248
+ },
1249
+ SELL: {
1250
+ summary: "Open the shared Trade ticket with a sell quantity and optional limit price. Focus starts on Review; holdings and broker capabilities are checked before confirmation.",
1251
+ usage: ["SELL AAPL 10", "SELL AAPL 10 350.25"],
1252
+ keys: [key("r", "eview"), key("o", "rders")],
1253
+ data: same("Broker account and quote"),
1254
+ bloomberg: [],
1255
+ docs: "BR",
1256
+ },
1257
+ ORD: {
1258
+ summary: "Open orders and recent activity for a selected broker profile and account. Review a modification or confirm cancellation from an open order; uncertain outcomes require reconciliation before another action.",
1259
+ usage: ["ORD"],
1260
+ keys: [key("m", "odify"), key("c", "ancel")],
1261
+ data: same("Broker account snapshot"),
1262
+ bloomberg: [],
1263
+ docs: "BR",
1264
+ },
1241
1265
 
1242
1266
  // Layouts and settings
1243
1267
  GL: {
@@ -168,12 +168,12 @@ async function loadHeadlessPaneModel(
168
168
  return result;
169
169
  }
170
170
 
171
- type HeadlessMarketContext = Pick<MarketContext, "config" | "store" | "refresh"> & Partial<Pick<MarketContext, "persistence" | "seriesCapabilities">> & {
171
+ type HeadlessMarketContext = Pick<MarketContext, "config" | "store" | "refresh" | "resolveBroker"> & Partial<Pick<MarketContext, "persistence" | "seriesCapabilities">> & {
172
172
  dataProvider: HeadlessPaneContext["marketData"];
173
173
  };
174
174
 
175
175
  /** What a headless loader reads the market, the account and the saved collections through. */
176
- function createHeadlessPaneContext(
176
+ export function createHeadlessPaneContext(
177
177
  resolved: ResolvedPaneFunction,
178
178
  context: HeadlessMarketContext,
179
179
  signal: AbortSignal,
@@ -186,6 +186,7 @@ function createHeadlessPaneContext(
186
186
  ...(context.refresh ? { refresh: true } : {}),
187
187
  settings: resolved.instance.settings,
188
188
  capabilities: getSharedRegistry() ?? context.seriesCapabilities,
189
+ resolveBroker: context.resolveBroker,
189
190
  async resolvePortfolio(id) {
190
191
  const portfolio = findNamedCollection(context.config.portfolios, id);
191
192
  if (!portfolio) return null;
@@ -498,7 +499,7 @@ function renderRows(
498
499
  };
499
500
  return renderTable(
500
501
  columns.map((column) => ({
501
- header: displayedHeader(column),
502
+ header: column.textHeader ?? displayedHeader(column),
502
503
  align: column.align,
503
504
  width: column.width,
504
505
  shrink: column.shrink,
@@ -939,9 +940,9 @@ export async function buildHeadlessFunctionReport(
939
940
  loaded = await loadResolvedHeadlessPaneModel(resolved, context, rawArgument);
940
941
  } catch (error) {
941
942
  // A Pro pane the server refused reports what its wall covers, as its rendered view does.
942
- const locked = lockedHeadlessReport(resolved, error, () => (
943
+ const locked = await lockedHeadlessReport(resolved, error, () => (
943
944
  buildHeadlessPaneLoadArgs(resolved.headless!, resolved.token, rawArgument, resolved.options).symbols
944
- ));
945
+ ), createHeadlessPaneContext(resolved, context, new AbortController().signal));
945
946
  if (locked) return locked;
946
947
  throw error;
947
948
  }
@@ -36,7 +36,7 @@ import {
36
36
  import { isNotATickerMessage } from "../not-a-ticker";
37
37
  import { isCliFailure } from "../errors";
38
38
  import { isTickerLinkPeer } from "../../layout/ticker-navigation";
39
- import { knownGapMessage, commandPointer, topicPointer } from "../known-gaps";
39
+ import { knownGapMessage, commandPointer, commandPointerLeads, topicPointer } from "../known-gaps";
40
40
  import { selectReportTables } from "../report-tables";
41
41
  import { cliOutputWritten } from "../result";
42
42
  import type { CatalogCommandHit } from "../unknown-command";
@@ -486,6 +486,7 @@ export async function runPaneCatalog(args: string[], ctx: CliCommandContext) {
486
486
  // A setting or a report can be a command, not a function: "light theme" names `config themes` beside THEM,
487
487
  // "drip" names `portfolio show --drip` beside DVD.
488
488
  const setting = commandPointer(parsed.query);
489
+ const settingLeads = commandPointerLeads(parsed.query);
489
490
  // The data catalog series the query names, which `fn G` charts.
490
491
  const series = findCatalogSeries(ctx.plugins, parsed.query);
491
492
  const listed = pointer
@@ -496,7 +497,7 @@ export async function runPaneCatalog(args: string[], ctx: CliCommandContext) {
496
497
  ...series.length ? { metadata: { series } } : {},
497
498
  ...(gap || setting) && ctx.cliOptions.format !== "text" ? { warnings: [gap, setting].filter((note): note is string => !!note) } : {},
498
499
  }, {
499
- text: () => renderPaneCatalogReport(filtered, effectiveParsed, glossary, gap, series, setting),
500
+ text: () => renderPaneCatalogReport(filtered, effectiveParsed, glossary, gap, series, setting, settingLeads),
500
501
  });
501
502
  } finally {
502
503
  registry.destroy();
@@ -2,6 +2,7 @@ import { refusedLock } from "../../plugins/builtin/shared/cloud-denied";
2
2
  import { lockNoteReason } from "../../plugins/builtin/shared/pro-lock-note";
3
3
  import { cliTerminalWidth, wrapText } from "../../utils/cli-output";
4
4
  import { reportFooterLines } from "../report-tables";
5
+ import type { HeadlessPaneContext } from "../../types/headless";
5
6
  import type { PaneFunctionReport } from "./report";
6
7
  import type { ResolvedPaneFunction } from "./resolver";
7
8
 
@@ -22,19 +23,21 @@ export function lockNoteLines(note: string): string[] {
22
23
  * `reportLockNote`, no source line and `locked` in JSON. The refusal may be
23
24
  * the error itself or the cause of the one a headless read wrote for it
24
25
  * (`explainRefusal`). Null when the error is no such refusal or the pane has
25
- * no note, and the error stands.
26
+ * no note, and the error stands. The context lets the note check the free
27
+ * route it names.
26
28
  */
27
- export function lockedHeadlessReport(
29
+ export async function lockedHeadlessReport(
28
30
  resolved: ResolvedPaneFunction,
29
31
  error: unknown,
30
32
  /** The symbols the report was asked for, read once the error is known to be a refusal. */
31
33
  symbolsOf: () => string[],
32
- ): PaneFunctionReport | null {
34
+ context?: HeadlessPaneContext,
35
+ ): Promise<PaneFunctionReport | null> {
33
36
  const refused = refusedLock(error);
34
37
  if (!refused || !resolved.pane.reportLockNote) return null;
35
38
  const gate = refused.gate;
36
39
  const symbols = symbolsOf();
37
- const note = resolved.pane.reportLockNote({ ...refused, symbol: symbols[0] ?? null, token: resolved.token });
40
+ const note = await resolved.pane.reportLockNote({ ...refused, symbol: symbols[0] ?? null, token: resolved.token, ...context ? { context } : {} });
38
41
  const reason = lockNoteReason(note);
39
42
  return {
40
43
  data: {