gloomberb 0.16.3 → 0.16.4

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 (451) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/signed-in/adapter.ts +33 -2
  30. package/src/brokers/signed-in/client.ts +10 -0
  31. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  32. package/src/brokers/signed-in/sign-in.ts +62 -5
  33. package/src/capabilities/registry.ts +8 -1
  34. package/src/capabilities/types.ts +2 -0
  35. package/src/cli/commands/crypto-hints.ts +12 -4
  36. package/src/cli/commands/market.ts +359 -99
  37. package/src/cli/commands/options-chain.ts +172 -0
  38. package/src/cli/commands/options-leaps.ts +422 -0
  39. package/src/cli/commands/overview.ts +164 -26
  40. package/src/cli/commands/search.ts +29 -8
  41. package/src/cli/commands/system.ts +99 -19
  42. package/src/cli/commands/themes.ts +64 -0
  43. package/src/cli/commands/ticker.ts +62 -30
  44. package/src/cli/desktop-pane-shot.ts +97 -16
  45. package/src/cli/errors.ts +25 -2
  46. package/src/cli/freshness.ts +7 -4
  47. package/src/cli/fx-pair.ts +57 -0
  48. package/src/cli/help.ts +14 -1
  49. package/src/cli/index-roots.ts +51 -0
  50. package/src/cli/index.ts +9 -6
  51. package/src/cli/listing-arg.ts +211 -3
  52. package/src/cli/not-a-ticker.ts +37 -0
  53. package/src/cli/options.ts +50 -6
  54. package/src/cli/pane-functions/capabilities.ts +8 -2
  55. package/src/cli/pane-functions/catalog.ts +13 -2
  56. package/src/cli/pane-functions/data.ts +36 -5
  57. package/src/cli/pane-functions/dom.ts +21 -13
  58. package/src/cli/pane-functions/freshness.ts +205 -26
  59. package/src/cli/pane-functions/function-help.ts +67 -22
  60. package/src/cli/pane-functions/headless.ts +5 -4
  61. package/src/cli/pane-functions/index.ts +3 -0
  62. package/src/cli/pane-functions/market-session.ts +133 -0
  63. package/src/cli/pane-functions/options.ts +15 -4
  64. package/src/cli/pane-functions/report-notices.ts +63 -0
  65. package/src/cli/pane-functions/report.ts +4 -0
  66. package/src/cli/pane-functions/resolver.ts +4 -6
  67. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  68. package/src/cli/pane-functions/screenshot.ts +105 -23
  69. package/src/cli/registry.ts +2 -1
  70. package/src/cli/result.ts +50 -13
  71. package/src/cli/row-window.ts +76 -0
  72. package/src/cli/scroll-truncation.ts +34 -0
  73. package/src/components/chart/composite/composite-chart.tsx +43 -4
  74. package/src/components/chart/composite/legend.tsx +39 -6
  75. package/src/components/chart/composite/rasterizer.ts +64 -4
  76. package/src/components/chart/composite/series-cues.ts +71 -0
  77. package/src/components/chart/composite/text-renderer.ts +16 -8
  78. package/src/components/chart/composite/types.ts +3 -0
  79. package/src/components/command-bar/commands/direct/index.ts +9 -1
  80. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  81. package/src/components/command-bar/commands/registry.ts +7 -0
  82. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  83. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  84. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  85. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  86. package/src/components/command-bar/routes/root/results.ts +40 -10
  87. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  88. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  89. package/src/components/command-bar/surface/index.tsx +18 -4
  90. package/src/components/command-bar/theme-picker.tsx +48 -13
  91. package/src/components/command-bar/workflow/ops.ts +5 -3
  92. package/src/components/data-table/view.tsx +28 -9
  93. package/src/components/layout/floating-pane.tsx +6 -2
  94. package/src/components/layout/market-summary.ts +23 -9
  95. package/src/components/layout/pane/content.tsx +46 -29
  96. package/src/components/layout/pane/cycle-order.ts +2 -11
  97. package/src/components/layout/pane/header.tsx +2 -1
  98. package/src/components/layout/pane/title.ts +1 -1
  99. package/src/components/layout/presentation.ts +22 -0
  100. package/src/components/layout/shell/active-drag.ts +1 -1
  101. package/src/components/layout/shell/drag/index.ts +4 -0
  102. package/src/components/layout/shell/drag/live.ts +76 -5
  103. package/src/components/layout/shell/drag/runtime.ts +8 -8
  104. package/src/components/layout/shell/index.tsx +47 -6
  105. package/src/components/layout/shell/layout-state.ts +5 -11
  106. package/src/components/layout/shell/menu.ts +28 -1
  107. package/src/components/layout/shell/pane/actions.ts +2 -1
  108. package/src/components/layout/shell/pane/layers.tsx +51 -10
  109. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  110. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  111. package/src/components/layout/shell/shortcuts.ts +5 -2
  112. package/src/components/layout/shell/visible-layout.ts +44 -1
  113. package/src/components/layout/status-bar.tsx +4 -1
  114. package/src/components/markdown-text.tsx +9 -5
  115. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  116. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  117. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  118. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  119. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  120. package/src/components/onboarding/pro-step-layout.ts +86 -0
  121. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  122. package/src/components/ui/button.tsx +10 -0
  123. package/src/components/ui/data-table/index.tsx +38 -0
  124. package/src/components/ui/data-table/types.ts +8 -1
  125. package/src/components/ui/data-table/visible-range.ts +2 -0
  126. package/src/components/ui/icon.tsx +11 -2
  127. package/src/components/ui/index.ts +1 -1
  128. package/src/core/connection-health.ts +1 -1
  129. package/src/core/state/app/layout-reducer.ts +89 -3
  130. package/src/core/state/app/state.ts +8 -5
  131. package/src/core/state/app/types.ts +13 -2
  132. package/src/data/config/layout.ts +55 -21
  133. package/src/data/config/store/fold-panes.ts +149 -0
  134. package/src/data/config/store/migrations.ts +7 -0
  135. package/src/data/config/store/node.ts +27 -6
  136. package/src/data/config/store/normalize.ts +37 -3
  137. package/src/i18n/es.ts +14 -1
  138. package/src/i18n/ja.ts +14 -1
  139. package/src/i18n/ko.ts +14 -1
  140. package/src/i18n/zh-cn.ts +14 -1
  141. package/src/i18n/zh-tw.ts +14 -1
  142. package/src/layout/desks.ts +1 -2
  143. package/src/layout/pane-layout-move.ts +264 -0
  144. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  145. package/src/layout/pane-manager/index.ts +1 -0
  146. package/src/market-data/coordinator/entries.ts +7 -1
  147. package/src/market-data/coordinator/quotes.ts +2 -2
  148. package/src/market-data/market/freshness.ts +118 -18
  149. package/src/market-data/market/status.ts +40 -0
  150. package/src/market-data/options-alternatives.ts +69 -0
  151. package/src/market-data/quotes/contributions.ts +19 -2
  152. package/src/market-data/quotes/metadata.ts +16 -0
  153. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  154. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  155. package/src/plugins/builtin/account-management/model.ts +1 -0
  156. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  157. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  158. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  159. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  160. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  161. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  162. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  163. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  164. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  165. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  166. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  167. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  168. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  169. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  170. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  171. package/src/plugins/builtin/chat/channels.ts +34 -36
  172. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  173. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  174. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  175. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  176. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  177. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  178. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  179. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  180. package/src/plugins/builtin/chat/conversations.ts +256 -0
  181. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  182. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  183. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  184. package/src/plugins/builtin/chat/index.tsx +40 -2
  185. package/src/plugins/builtin/chat/layout.ts +2 -0
  186. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  187. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  188. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  189. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  190. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  191. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  192. package/src/plugins/builtin/chat/pane.tsx +8 -0
  193. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  194. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  195. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  196. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  197. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  198. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  199. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  200. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  201. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  202. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  203. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  204. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  205. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  206. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  207. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  208. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  209. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  210. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  211. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  212. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  213. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  214. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  215. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  216. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  217. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  218. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  219. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  220. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  221. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  222. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  223. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  224. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  225. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  226. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  227. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  228. package/src/plugins/builtin/composite-plugins.ts +107 -64
  229. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  230. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  231. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  232. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  233. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  234. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  235. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  236. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  237. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  238. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  239. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  240. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  241. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  242. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  243. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  244. package/src/plugins/builtin/econ/index.tsx +68 -22
  245. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  246. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  247. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  248. package/src/plugins/builtin/executives/data.ts +17 -6
  249. package/src/plugins/builtin/executives/pane.tsx +16 -7
  250. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  251. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  254. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  255. package/src/plugins/builtin/gpu/headless.ts +9 -3
  256. package/src/plugins/builtin/gpu/model.ts +14 -5
  257. package/src/plugins/builtin/help/index.tsx +1 -0
  258. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  259. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  260. package/src/plugins/builtin/holders/headless.ts +6 -1
  261. package/src/plugins/builtin/holders/pane.tsx +3 -0
  262. package/src/plugins/builtin/insider/index.tsx +4 -4
  263. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  264. package/src/plugins/builtin/iv-history/client.ts +12 -2
  265. package/src/plugins/builtin/iv-history/format.ts +33 -2
  266. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  267. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  268. package/src/plugins/builtin/iv-history/model.ts +61 -13
  269. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  270. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  271. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  272. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  273. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  274. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  275. package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
  276. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  277. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  278. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  279. package/src/plugins/builtin/macro-day/client.ts +24 -0
  280. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  281. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  282. package/src/plugins/builtin/macro-day/model.ts +11 -6
  283. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  284. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  285. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  286. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  287. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  288. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  289. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  290. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  291. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  292. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  293. package/src/plugins/builtin/market-movers/model.ts +71 -17
  294. package/src/plugins/builtin/market-movers/table.ts +50 -23
  295. package/src/plugins/builtin/news/headless.ts +9 -2
  296. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  297. package/src/plugins/builtin/openfda/client.ts +1 -1
  298. package/src/plugins/builtin/options/carry.ts +95 -0
  299. package/src/plugins/builtin/options/index.tsx +58 -5
  300. package/src/plugins/builtin/options/strike-window.ts +183 -0
  301. package/src/plugins/builtin/options/table.ts +55 -3
  302. package/src/plugins/builtin/options/types.ts +8 -1
  303. package/src/plugins/builtin/options/view.tsx +161 -12
  304. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  305. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  306. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  307. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  308. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  309. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  310. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  311. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  312. package/src/plugins/builtin/pe-band/model.ts +2 -2
  313. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  314. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  315. package/src/plugins/builtin/perps/model.ts +16 -5
  316. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  317. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  318. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  319. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  320. package/src/plugins/builtin/plugin-module.ts +87 -4
  321. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  322. package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
  323. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  324. package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
  325. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  326. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  327. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  328. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  329. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  330. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  331. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  332. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  333. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  334. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  335. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  336. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  337. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  338. package/src/plugins/builtin/sec/index.tsx +5 -5
  339. package/src/plugins/builtin/sectors/client.ts +10 -2
  340. package/src/plugins/builtin/sectors/headless.ts +35 -6
  341. package/src/plugins/builtin/sectors/index.tsx +18 -1
  342. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  343. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  344. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  345. package/src/plugins/builtin/shared/members-menu.tsx +4 -4
  346. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  347. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  348. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  349. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  350. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  351. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  352. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  353. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  354. package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
  355. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  356. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  357. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  358. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  359. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  360. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  361. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  362. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  363. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  364. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  365. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  366. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  367. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  368. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  369. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  370. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  371. package/src/plugins/builtin/world-indices/client.ts +22 -4
  372. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  373. package/src/plugins/builtin/world-indices/model.ts +4 -2
  374. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  375. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  376. package/src/plugins/builtin/world-venue-map/index.ts +1 -1
  377. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  378. package/src/plugins/builtin-editorial.ts +126 -0
  379. package/src/plugins/catalog-backend.ts +16 -9
  380. package/src/plugins/catalog-browser.ts +127 -63
  381. package/src/plugins/catalog-manifest.ts +73 -40
  382. package/src/plugins/catalog-ui.ts +26 -6
  383. package/src/plugins/extracted-plugins.ts +7 -1
  384. package/src/plugins/ownership.ts +150 -7
  385. package/src/plugins/plugin-functions.ts +54 -0
  386. package/src/plugins/plugin-off.ts +36 -0
  387. package/src/plugins/registry/host-actions.ts +16 -0
  388. package/src/plugins/registry/index.ts +84 -24
  389. package/src/plugins/registry/plugin-state.ts +25 -1
  390. package/src/plugins/runtime/context.tsx +29 -0
  391. package/src/react/input.ts +48 -1
  392. package/src/remote/app-host.tsx +5 -0
  393. package/src/remote/controller.ts +3 -53
  394. package/src/remote/matching.ts +69 -0
  395. package/src/remote/schema.ts +1 -1
  396. package/src/remote/semantic-tree.tsx +14 -0
  397. package/src/remote/types.ts +2 -0
  398. package/src/renderers/dom/desktop/icons.tsx +14 -0
  399. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  400. package/src/renderers/dom/host/box.tsx +1 -1
  401. package/src/renderers/dom/host/drag-shield.ts +17 -6
  402. package/src/renderers/dom/host/live-frame.ts +10 -2
  403. package/src/renderers/dom/input-host.tsx +6 -1
  404. package/src/renderers/dom/styles.css +13 -0
  405. package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
  406. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  407. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  408. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  409. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  410. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  411. package/src/renderers/electrobun/view/main.tsx +4 -1
  412. package/src/renderers/opentui/input-host.tsx +6 -1
  413. package/src/renderers/opentui/start.tsx +9 -0
  414. package/src/shares/portable-layout.ts +16 -6
  415. package/src/sources/gloomberb-cloud/index.ts +19 -10
  416. package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
  417. package/src/sources/listing-symbols.ts +11 -5
  418. package/src/sources/provider-errors.ts +88 -3
  419. package/src/sources/provider-router/cached-routes.ts +8 -4
  420. package/src/sources/provider-router/financial-routes.ts +11 -5
  421. package/src/sources/provider-router/financials.ts +42 -15
  422. package/src/sources/provider-router/history.ts +27 -11
  423. package/src/sources/provider-router/index.ts +1 -1
  424. package/src/sources/provider-router/primary.ts +23 -5
  425. package/src/sources/sec-edgar.ts +68 -8
  426. package/src/sync/core-contributors.ts +4 -3
  427. package/src/test-support/cli-context.ts +2 -1
  428. package/src/test-support/data-provider.ts +34 -0
  429. package/src/test-support/shell-pane-keys.tsx +1 -0
  430. package/src/theme/series-colors.ts +46 -0
  431. package/src/theme/themes.ts +149 -0
  432. package/src/tickers/search/index.ts +22 -2
  433. package/src/tickers/search/ranking.ts +45 -1
  434. package/src/tickers/search/types.ts +3 -1
  435. package/src/time-series/resolve.ts +1 -14
  436. package/src/types/config.ts +61 -1
  437. package/src/types/headless.ts +11 -0
  438. package/src/types/instrument.ts +8 -0
  439. package/src/types/plugin.ts +49 -3
  440. package/src/ui/host.tsx +8 -4
  441. package/src/ui/index.tsx +2 -0
  442. package/src/utils/cli-output.ts +14 -1
  443. package/src/utils/exchanges.ts +13 -0
  444. package/src/utils/format.ts +2 -1
  445. package/src/utils/fx-market-hours.ts +34 -0
  446. package/src/utils/option-expiry.ts +126 -0
  447. package/src/utils/price-history.ts +39 -6
  448. package/src/utils/rpc-timeout-error.ts +6 -2
  449. package/src/utils/sec-eps-basis.ts +159 -30
  450. package/src/utils/sec.ts +38 -34
  451. package/src/utils/throttled-fetch.ts +51 -22
@@ -1,5 +1,13 @@
1
1
  import { resolveCurrencyUnit } from "../../../utils/currency-units";
2
2
  import { overlayScreenerQuoteEntries } from "../../../market-data/quotes/screener-live-quotes";
3
+ import {
4
+ getExtendedSessionDisplay,
5
+ getRegularSessionDisplay,
6
+ type ExtendedSession,
7
+ type ExtendedSessionDisplay,
8
+ } from "../../../market-data/market/status";
9
+ import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors";
10
+ import { isFiniteNumber } from "../../../utils/guards";
3
11
  import type { Quote } from "../../../types/financials";
4
12
  import type { CloudSessionMoversCategory } from "../../../api-client/market-movers";
5
13
  import type { QueryEntry } from "../../../market-data/result-types";
@@ -37,12 +45,20 @@ type MarketMoverColumnId =
37
45
  | "name"
38
46
  | "price"
39
47
  | "changePercent"
48
+ | "preMarket"
49
+ | "afterHours"
40
50
  | "volume"
41
51
  | "volumeRatio"
42
52
  | "range"
43
53
  | "marketCap";
44
54
  export type MarketMoverColumn = DataTableColumn & { id: MarketMoverColumnId };
45
- export type MarketMoverRow = ScreenerQuote & { rank: number };
55
+ /**
56
+ * A list row. Price and change are the regular session's, as `ticker` reads
57
+ * them; a row whose quote has a pre-market or after-hours print carries it in
58
+ * `extended`, measured from that session's close.
59
+ */
60
+ export type MoverQuote = ScreenerQuote & { extended?: ExtendedSessionDisplay | null };
61
+ export type MarketMoverRow = MoverQuote & { rank: number };
46
62
 
47
63
  export interface MarketMoverSortPreference {
48
64
  columnId: MarketMoverColumnId | null;
@@ -96,6 +112,9 @@ function getSortValue(
96
112
  return row.price;
97
113
  case "changePercent":
98
114
  return row.changePercent;
115
+ case "preMarket":
116
+ case "afterHours":
117
+ return moverExtendedMove(row, columnId === "preMarket" ? "PRE" : "POST");
99
118
  case "volume":
100
119
  return row.volume;
101
120
  case "volumeRatio":
@@ -123,7 +142,17 @@ export function sortRows(
123
142
  /** A row is rebuilt only when its quote or rank moved, so unchanged rows skip the render. */
124
143
  const moverRows = new WeakMap<ScreenerQuote, MarketMoverRow>();
125
144
 
126
- export function createRows(quotes: ScreenerQuote[]): MarketMoverRow[] {
145
+ /** The extended print's move from the close, when the row has one for `session`. */
146
+ export function moverExtendedMove(row: MoverQuote, session: ExtendedSession): number | null {
147
+ return row.extended?.session === session && isFiniteNumber(row.extended.changePercent) ? row.extended.changePercent : null;
148
+ }
149
+
150
+ /** The extended sessions some row has a print for, in the order their columns go. */
151
+ export function moverExtendedSessions(rows: readonly MoverQuote[]): ExtendedSession[] {
152
+ return (["PRE", "POST"] as const).filter((session) => rows.some((row) => row.extended?.session === session));
153
+ }
154
+
155
+ export function createRows(quotes: readonly MoverQuote[]): MarketMoverRow[] {
127
156
  return quotes.map((quote, index) => {
128
157
  const cached = moverRows.get(quote);
129
158
  if (cached?.rank === index + 1) return cached;
@@ -137,26 +166,36 @@ export function createRows(quotes: ScreenerQuote[]): MarketMoverRow[] {
137
166
  });
138
167
  }
139
168
 
140
- export function summaryQuoteFromQuote(
141
- symbol: string,
142
- quote: { name?: string; price: number; change: number; changePercent: number },
143
- ): MarketSummaryQuote {
169
+ /** An index in the summary strip, at its regular session's level and move. */
170
+ export function summaryQuoteFromQuote(symbol: string, quote: Quote): MarketSummaryQuote {
171
+ const headline = getRegularSessionDisplay(quote)!;
144
172
  return {
145
173
  symbol,
146
174
  name: quote.name ?? symbol,
147
- price: quote.price,
148
- change: quote.change,
149
- changePercent: quote.changePercent,
175
+ price: headline.price,
176
+ change: headline.change ?? Number.NaN,
177
+ changePercent: headline.changePercent ?? Number.NaN,
150
178
  };
151
179
  }
152
180
 
153
- export function screenerQuoteFromQuote(symbol: string, quote: { name?: string; price?: number; change?: number; changePercent?: number; volume?: number; currency?: string; exchangeName?: string; listingExchangeName?: string; lastUpdated?: number; previousClose?: number }): ScreenerQuote {
181
+ /** The extended print beside a quote's regular session, or null; a non-finite price is none. */
182
+ function moverExtended(quote: Quote): ExtendedSessionDisplay | null {
183
+ const extended = getExtendedSessionDisplay(quote);
184
+ return extended && isFiniteNumber(extended.price) ? extended : null;
185
+ }
186
+
187
+ /**
188
+ * A trending row from its quote: Last and Chg% are the regular session, as
189
+ * `ticker` reads it, and a pre-market or after-hours print rides beside them.
190
+ */
191
+ export function screenerQuoteFromQuote(symbol: string, quote: Quote): MoverQuote {
192
+ const headline = getRegularSessionDisplay(quote)!;
154
193
  return {
155
194
  symbol,
156
195
  name: quote.name ?? symbol,
157
- price: screenerNumber(quote.price),
158
- change: screenerNumber(quote.change),
159
- changePercent: screenerNumber(quote.changePercent),
196
+ price: screenerNumber(headline.price),
197
+ change: screenerNumber(headline.change),
198
+ changePercent: screenerNumber(headline.changePercent),
160
199
  volume: screenerVolume(quote.volume),
161
200
  avgVolume: null,
162
201
  volumeRatio: null,
@@ -169,6 +208,7 @@ export function screenerQuoteFromQuote(symbol: string, quote: { name?: string; p
169
208
  exchange: quote.listingExchangeName ?? quote.exchangeName ?? "",
170
209
  lastUpdated: quote.lastUpdated,
171
210
  previousClose: screenerNumber(quote.previousClose) ?? undefined,
211
+ extended: moverExtended(quote),
172
212
  };
173
213
  }
174
214
 
@@ -198,21 +238,35 @@ export function formatMoverPrice(price: number | null, currency: string, referen
198
238
  }
199
239
 
200
240
  /** Keyed by the overlaid row, which the shared overlay keeps while its quote holds. */
201
- const convertedOverlays = new WeakMap<ScreenerQuote, ScreenerQuote>();
241
+ const convertedOverlays = new WeakMap<ScreenerQuote, MoverQuote>();
202
242
 
203
- /** Range endpoints and the previous close belong to the original screener price denomination. */
243
+ /**
244
+ * Live quotes on the list's rows, which keep the list's order. Price and
245
+ * change are the regular session's, as `ticker` reads them, so after the
246
+ * close they hold at it and a pre-market or after-hours print is the row's
247
+ * `extended`. Range endpoints and the previous close belong to the original
248
+ * screener price denomination.
249
+ */
204
250
  export function overlayMarketMoverQuotes(
205
- rows: readonly ScreenerQuote[],
251
+ rows: readonly MoverQuote[],
206
252
  entries: ReadonlyMap<string, QueryEntry<Quote>>,
207
- ): ScreenerQuote[] {
253
+ ): MoverQuote[] {
208
254
  return overlayScreenerQuoteEntries(rows, entries).map((row, index) => {
209
255
  const original = rows[index]!;
210
256
  if (row === original) return row;
211
257
  const cached = convertedOverlays.get(row);
212
258
  if (cached) return cached;
213
259
  const convert = (value: number | undefined) => convertScreenerPriceUnit(value, original.currency, row.currency);
260
+ const quote = resolveEntryData(entries.get(buildQuoteKey({ symbol: row.symbol, exchange: row.exchange })));
261
+ const headline = quote ? getRegularSessionDisplay(quote) : null;
214
262
  const converted = {
215
263
  ...row,
264
+ ...(quote && headline ? {
265
+ price: headline.price,
266
+ change: isFiniteNumber(headline.change) ? headline.change : null,
267
+ changePercent: isFiniteNumber(headline.changePercent) ? headline.changePercent : null,
268
+ extended: moverExtended(quote),
269
+ } : {}),
216
270
  fiftyTwoWeekLow: convert(original.fiftyTwoWeekLow),
217
271
  fiftyTwoWeekHigh: convert(original.fiftyTwoWeekHigh),
218
272
  dayLow: convert(original.dayLow),
@@ -1,9 +1,11 @@
1
1
  import type { DataTableCell } from "../../../components";
2
+ import { getTableWidth } from "../../../components/ui/table-layout";
2
3
  import { TextAttributes } from "../../../ui";
3
4
  import { colors, priceColor } from "../../../theme/colors";
4
5
  import { formatCompact, formatPercentRaw } from "../../../utils/format";
6
+ import { marketStateLabel, type ExtendedSession } from "../../../market-data/market/status";
5
7
  import type { MarketMoverColumn, MarketMoverRow } from "./model";
6
- import { fiftyTwoWeekPositionPercent, formatMoverPrice, moverReferencePrice } from "./model";
8
+ import { fiftyTwoWeekPositionPercent, formatMoverPrice, moverExtendedMove, moverReferencePrice } from "./model";
7
9
 
8
10
  export function formatVolRatio(ratio: number | null): string {
9
11
  if (ratio == null || !Number.isFinite(ratio) || ratio < 0) return "—";
@@ -22,30 +24,45 @@ function fiftyTwoWeekPosition(price: number | null, low: number | undefined, hig
22
24
  return pct == null ? "—" : `${Math.round(pct)}%`;
23
25
  }
24
26
 
25
- export function buildMarketMoverColumns(width: number): MarketMoverColumn[] {
26
- const rankWidth = 3;
27
- const tickerWidth = 8;
28
- const priceWidth = 11;
29
- const chgWidth = 9;
30
- const volWidth = 8;
31
- const volRatioWidth = 6;
32
- const rangeWidth = 6;
33
- const mcapWidth = 8;
34
- const columnCount = 9;
35
- const fixedWidth = rankWidth + tickerWidth + priceWidth + chgWidth + volWidth + volRatioWidth + rangeWidth + mcapWidth;
36
- const nameWidth = Math.max(6, width - 2 - columnCount - fixedWidth);
27
+ const EXTENDED_COLUMN_WIDTH = 9;
28
+ /** With an extended column, a narrow pane drops MCAP, then 52W%, before the name gets narrower than this. */
29
+ const EXTENDED_MIN_NAME_WIDTH = 12;
37
30
 
38
- return [
39
- { id: "rank", label: "#", width: rankWidth, align: "left" },
40
- { id: "symbol", label: "TICKER", width: tickerWidth, align: "left" },
41
- { id: "name", label: "NAME", width: nameWidth, align: "left" },
42
- { id: "price", label: "LAST", width: priceWidth, align: "right" },
43
- { id: "changePercent", label: "CHG%", width: chgWidth, align: "right" },
44
- { id: "volume", label: "VOL", width: volWidth, align: "right" },
45
- { id: "volumeRatio", label: "V/AVG", width: volRatioWidth, align: "right" },
46
- { id: "range", label: "52W%", width: rangeWidth, align: "right" },
47
- { id: "marketCap", label: "MCAP", width: mcapWidth, align: "right" },
31
+ /**
32
+ * The list's columns at `width`. A pre-market or after-hours column follows
33
+ * CHG% for each session in `extendedSessions`; to make room for it a narrow
34
+ * pane gives up MCAP, then 52W%, before it cuts the names short.
35
+ */
36
+ export function buildMarketMoverColumns(width: number, extendedSessions: readonly ExtendedSession[] = []): MarketMoverColumn[] {
37
+ const lead: MarketMoverColumn[] = [
38
+ { id: "rank", label: "#", width: 3, align: "left" },
39
+ { id: "symbol", label: "TICKER", width: 8, align: "left" },
48
40
  ];
41
+ const quote: MarketMoverColumn[] = [
42
+ { id: "price", label: "LAST", width: 11, align: "right" },
43
+ { id: "changePercent", label: "CHG%", width: 9, align: "right" },
44
+ ...extendedSessions.map((session): MarketMoverColumn => ({
45
+ id: session === "PRE" ? "preMarket" : "afterHours",
46
+ // Named for the session it trades in, as the header labels it.
47
+ label: marketStateLabel(session),
48
+ width: EXTENDED_COLUMN_WIDTH,
49
+ align: "right",
50
+ })),
51
+ ];
52
+ const trailing: MarketMoverColumn[] = [
53
+ { id: "volume", label: "VOL", width: 8, align: "right" },
54
+ { id: "volumeRatio", label: "V/AVG", width: 6, align: "right" },
55
+ { id: "range", label: "52W%", width: 6, align: "right" },
56
+ { id: "marketCap", label: "MCAP", width: 8, align: "right" },
57
+ ];
58
+ // Measured the way the table draws them (a header and its sort mark widen a
59
+ // column, gaps between them); the name adds its own width and a gap.
60
+ const nameRoom = () => width - getTableWidth([...lead, ...quote, ...trailing]) - 1;
61
+ for (const id of ["marketCap", "range"] as const) {
62
+ if (extendedSessions.length === 0 || nameRoom() >= EXTENDED_MIN_NAME_WIDTH) break;
63
+ trailing.splice(trailing.findIndex((column) => column.id === id), 1);
64
+ }
65
+ return [...lead, { id: "name", label: "NAME", width: Math.max(6, nameRoom()), align: "left" }, ...quote, ...trailing];
49
66
  }
50
67
 
51
68
  export function renderMarketMoverCell(
@@ -71,6 +88,16 @@ export function renderMarketMoverCell(
71
88
  value: row.changePercent,
72
89
  color: priceColor(row.changePercent ?? 0),
73
90
  };
91
+ case "preMarket":
92
+ case "afterHours": {
93
+ // The extended print's move from the close in LAST.
94
+ const move = moverExtendedMove(row, column.id === "preMarket" ? "PRE" : "POST");
95
+ return {
96
+ text: formatPercentRaw(move ?? undefined),
97
+ value: move,
98
+ color: move == null ? colors.textDim : priceColor(move),
99
+ };
100
+ }
74
101
  case "volume":
75
102
  return { text: formatCompact(row.volume ?? undefined, { fixedDecimals: true }), value: row.volume, color: colors.textDim };
76
103
  case "volumeRatio":
@@ -11,6 +11,7 @@ import {
11
11
  mapCloudNewsArticle,
12
12
  } from "../../../sources/gloomberb-cloud/news";
13
13
  import { parsePublicTickerKey } from "../../../utils/exchanges";
14
+ import { cloudNewsFreshness, cloudRealtimeAccess } from "../shared/report-freshness";
14
15
 
15
16
  const NEWS_COLUMNS = [
16
17
  { key: "publishedAt", header: "Published" },
@@ -27,11 +28,14 @@ export interface NewsHeadlessDependencies {
27
28
  context: HeadlessPaneContext,
28
29
  ): Promise<CloudNewsListResponse>;
29
30
  now(): Date;
31
+ /** Whether the session has real-time news; without it the stories are held back. */
32
+ realtimeAccess?(): Promise<boolean | null>;
30
33
  }
31
34
 
32
35
  const defaultDependencies: NewsHeadlessDependencies = {
33
36
  loadNews: (query, context) => context.apiClient.getCloudNews(cloudNewsParams(query)),
34
37
  now: () => new Date(),
38
+ realtimeAccess: cloudRealtimeAccess,
35
39
  };
36
40
 
37
41
  function newsRow(article: NewsArticle) {
@@ -67,6 +71,7 @@ function projectNewsHeadless(
67
71
  response: CloudNewsListResponse,
68
72
  args: HeadlessPaneLoadArgs,
69
73
  now: Date,
74
+ realtime: boolean | null,
70
75
  ): HeadlessSnapshotResult {
71
76
  const sentiment = selectedSentiment(args);
72
77
  const minImportance = Number(args.options.minImportance ?? 0);
@@ -81,8 +86,8 @@ function projectNewsHeadless(
81
86
  return {
82
87
  asOf: now.toISOString(),
83
88
  items,
84
- // Published stories, dated by the newest; the list is a history, so its last story is not stale data.
85
- freshness: { status: "not-a-feed", basis: "published stories", observedKey: "publishedAt", oldest: null },
89
+ // Dated by the newest story, held back for an account without real-time news.
90
+ freshness: cloudNewsFreshness(realtime),
86
91
  metadata: {
87
92
  nextCursor: response.nextCursor,
88
93
  sentiment,
@@ -179,6 +184,7 @@ export function createNewsFeedHeadless(
179
184
  await dependencies.loadNews(queryFor(args, "latest"), context),
180
185
  args,
181
186
  dependencies.now(),
187
+ await dependencies.realtimeAccess?.() ?? null,
182
188
  );
183
189
  },
184
190
  };
@@ -205,6 +211,7 @@ export function createTickerNewsHeadless(
205
211
  ),
206
212
  args,
207
213
  dependencies.now(),
214
+ await dependencies.realtimeAccess?.() ?? null,
208
215
  );
209
216
  },
210
217
  };
@@ -10,6 +10,7 @@ import { CLOUD_NEWS_DELAY_HOURS } from "../../../../../api-client/plan-access";
10
10
  import { usePaneStatusLinkFooter } from "../../../../../components/layout/pane/status-footer";
11
11
  import { usePluginAppActions } from "../../../../runtime";
12
12
  import { useOptionalPaneInstanceId } from "../../../../../state/app/context";
13
+ import { useRemoteUiNode } from "../../../../../remote/semantic-tree";
13
14
 
14
15
  interface NewsFooterArticle {
15
16
  title?: string | null;
@@ -60,6 +61,15 @@ export function useNewsArticleFooter({
60
61
  shortcutScope: `${registrationId}:news-upgrade`,
61
62
  });
62
63
 
64
+ // A report or capture of the pane says how far its stories are held back, as the footer does.
65
+ useRemoteUiNode({
66
+ role: "report-freshness",
67
+ label: "News freshness",
68
+ getMetadata: () => (access.hasProAccess
69
+ ? { status: "live" }
70
+ : { status: "delayed", delayMinutes: CLOUD_NEWS_DELAY_HOURS * 60 }),
71
+ });
72
+
63
73
  const accessInfo = useMemo<PaneFooterSegment[]>(() => {
64
74
  if (access.isPayingPro) {
65
75
  return [{ id: "news-access", parts: [{ text: t("real-time news"), tone: "positive" }] }];
@@ -280,7 +280,7 @@ export class OpenFdaClient {
280
280
  async listPage(dataset: OpenFdaDataset, query: string, offset: number, signal?: AbortSignal): Promise<OpenFdaPage> {
281
281
  const response = await openFdaFetch.fetch(buildOpenFdaUrl(dataset, query, offset), {
282
282
  headers: authorizationHeaders(),
283
- signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(DEFAULT_TIMEOUT_MS)]) : undefined,
283
+ signal,
284
284
  });
285
285
  // openFDA answers a search with no matches with a 404.
286
286
  if (response.status === 404) {
@@ -0,0 +1,95 @@
1
+ import type { OptionContract } from "../../../types/financials";
2
+ import { daysToExpiryFrom, optionMid, type OptionSide } from "../shared/volatility";
3
+ import { optionSpread } from "./market-reference";
4
+
5
+ /**
6
+ * What holding a contract costs in time value, read the same way by the OMON
7
+ * columns and the LEAPS screen (`gloomberb options --leaps`):
8
+ *
9
+ * intrinsic = max(0, spot - strike) for a call, max(0, strike - spot) for a put
10
+ * extrinsic = midpoint - intrinsic, the midpoint of a two-sided quote
11
+ * extrinsic per year = extrinsic / spot / years to expiry
12
+ *
13
+ * Years are calendar days to the 16:00 New York close on the expiry date, over
14
+ * 365, counted from the instant the Greeks are valued at. Under a day from the
15
+ * close there is no yearly figure: a few cents over a few hours annualises to
16
+ * noise. Extrinsic is negative when the midpoint sits below intrinsic value, as
17
+ * a wide deep in-the-money quote can. Without a two-sided quote or a current
18
+ * spot there is no figure, never a zero.
19
+ */
20
+ export interface OptionCarry {
21
+ /** Time value per share, in the contract's currency. */
22
+ extrinsic: number;
23
+ /** Time value as a fraction of spot per year: 0.042 reads 4.2%. Null under a day from expiry. */
24
+ extrinsicPerYear: number | null;
25
+ }
26
+
27
+ /** Less time than this to expiry and a yearly rate means nothing. */
28
+ const MIN_CARRY_DAYS = 1;
29
+
30
+ function positive(value: number | null | undefined): value is number {
31
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
32
+ }
33
+
34
+ function optionIntrinsicValue(side: OptionSide, strike: number, spot: number): number {
35
+ return Math.max(0, side === "call" ? spot - strike : strike - spot);
36
+ }
37
+
38
+ export function optionCarry(
39
+ contract: Pick<OptionContract, "bid" | "ask" | "strike" | "expiration">,
40
+ side: OptionSide,
41
+ spot: number | null | undefined,
42
+ valuationTime: number,
43
+ ): OptionCarry | null {
44
+ const mid = optionMid(contract);
45
+ if (mid == null || !positive(spot) || !positive(contract.strike)) return null;
46
+ const extrinsic = mid - optionIntrinsicValue(side, contract.strike, spot);
47
+ const days = daysToExpiryFrom(contract.expiration, valuationTime);
48
+ return { extrinsic, extrinsicPerYear: days >= MIN_CARRY_DAYS ? extrinsic / spot / (days / 365) : null };
49
+ }
50
+
51
+ /** Bid/ask width as a percent of the midpoint; null without a two-sided quote. */
52
+ export function optionSpreadPercent(contract: Pick<OptionContract, "bid" | "ask">): number | null {
53
+ const spread = optionSpread(contract);
54
+ return spread.kind === "two-sided" ? spread.percentOfMid : null;
55
+ }
56
+
57
+ /**
58
+ * What a contract costs against the stock it covers: the midpoint of a
59
+ * two-sided quote over spot, so a protective put's premium reads as a share of
60
+ * the position it protects (a 5.495 put on a 778.57 underlying is 0.706%).
61
+ * Null without a two-sided quote or a current spot, never a zero.
62
+ */
63
+ export function optionCostOfSpot(
64
+ contract: Pick<OptionContract, "bid" | "ask">,
65
+ spot: number | null | undefined,
66
+ ): number | null {
67
+ const mid = optionMid(contract);
68
+ return mid == null || !positive(spot) ? null : mid / spot;
69
+ }
70
+
71
+ /** Below this a cost of spot reads as a bound: the digits would outrun the column. */
72
+ const MIN_COST_OF_SPOT_PERCENT = 0.001;
73
+
74
+ /**
75
+ * A fraction as a percent to three significant digits: 0.00706 reads 0.706%,
76
+ * 0.0123 reads 1.23%, 0.123 reads 12.3%. Under a thousandth of a percent it
77
+ * reads <0.001%, so a far out-of-the-money quote stays inside the column.
78
+ */
79
+ export function formatCostOfSpot(value: number | null | undefined): string {
80
+ if (value == null || !Number.isFinite(value) || value < 0) return "\u2014";
81
+ const percent = value * 100;
82
+ if (percent === 0) return "0%";
83
+ if (percent < MIN_COST_OF_SPOT_PERCENT) return `<${MIN_COST_OF_SPOT_PERCENT}%`;
84
+ // Rounded first, so 9.996 reads 10.0% rather than 10.00%.
85
+ const rounded = Number(percent.toPrecision(3));
86
+ const decimals = Math.max(0, 2 - Math.floor(Math.log10(rounded)));
87
+ return `${rounded.toFixed(decimals)}%`;
88
+ }
89
+
90
+ /** A fraction as a percent: one decimal under 100% (0.0421 reads 4.2%), whole percents above. */
91
+ export function formatCarryPercent(value: number | null | undefined): string {
92
+ if (value == null || !Number.isFinite(value)) return "—";
93
+ const percent = value * 100;
94
+ return `${percent.toFixed(Math.abs(percent) < 100 ? 1 : 0)}%`;
95
+ }
@@ -1,9 +1,10 @@
1
- import type { PaneSettingsDef, TickerResearchTabProps } from "../../../types/plugin";
1
+ import type { PaneReportOptionDef, PaneSettingsDef, TickerResearchTabProps } from "../../../types/plugin";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
4
4
  import type { OptionsViewProps } from "./types";
5
5
  import { OptionsView } from "./view";
6
6
  import { isKnownMutualFund } from "../../../tickers/instrument-kind";
7
+ import { expirationOptionSeconds, expiryIsoDate, OPTION_EXPIRATION_FORMAT, OPTION_EXPIRATION_PLACEHOLDER, readOptionExpiration } from "../../../utils/option-expiry";
7
8
 
8
9
  /** The registered chain shows IV rank; isolated view renders stay offline. */
9
10
  function OptionsPane(props: OptionsViewProps) {
@@ -22,19 +23,40 @@ import {
22
23
  LIVE_STREAMING_QUICK_SETTING,
23
24
  withLiveStreamingSetting,
24
25
  } from "../../../state/hooks/live-streaming";
25
- import { OPTION_FIELD_DEFS, resolveOptionFieldIds } from "./table";
26
+ import { OPTION_FIELD_DEFS, normalizeOptionColumnsOption, resolveOptionFieldIds } from "./table";
27
+ import {
28
+ STRIKE_WINDOW_PRESETS,
29
+ STRIKE_WINDOW_SETTING,
30
+ normalizeStrikeWindowOption,
31
+ resolveStrikeWindow,
32
+ strikeWindowShortLabel,
33
+ strikeWindowValue,
34
+ } from "./strike-window";
26
35
 
27
36
  function optionsSettings(settings: Record<string, unknown>): PaneSettingsDef {
37
+ const strikeWindow = resolveStrikeWindow(settings[STRIKE_WINDOW_SETTING]);
38
+ const strikeWindowText = strikeWindowValue(strikeWindow);
39
+ const strikeWindowOptions = STRIKE_WINDOW_PRESETS.map(({ value, label, description }) => ({ value, label, description }));
28
40
  return {
29
41
  title: "Options Settings",
30
42
  values: {
31
43
  optionColumnIds: resolveOptionFieldIds(settings.optionColumnIds),
44
+ [STRIKE_WINDOW_SETTING]: strikeWindowText,
32
45
  },
33
46
  fields: [
47
+ {
48
+ key: STRIKE_WINDOW_SETTING,
49
+ label: "Strikes",
50
+ description: "Which strikes the chain lists: all of them, a count either side of the money, or those whose call or put delta falls in a band.",
51
+ type: "select",
52
+ // A window typed on the command line that no preset matches stays selectable.
53
+ options: strikeWindowOptions.some((option) => option.value === strikeWindowText) ? strikeWindowOptions
54
+ : [...strikeWindowOptions, { value: strikeWindowText, label: strikeWindowShortLabel(strikeWindow), description: "Set from the command line." }],
55
+ },
34
56
  {
35
57
  key: "optionColumnIds",
36
58
  label: "Columns",
37
- description: "Choose and order the fields mirrored around the strike.",
59
+ description: "Choose and order the fields mirrored around the strike. Cost % of spot shows on the put side only.",
38
60
  type: "ordered-multi-select",
39
61
  options: OPTION_FIELD_DEFS.map((field) => ({
40
62
  value: field.id,
@@ -58,6 +80,16 @@ function optionsSettings(settings: Record<string, unknown>): PaneSettingsDef {
58
80
  };
59
81
  }
60
82
 
83
+ /** `fn OMON AAPL --expiration 2028-01-21`: the expiry the chain opens at, as the pane setting a handoff sets. */
84
+ const EXPIRATION_REPORT_OPTION: PaneReportOptionDef = {
85
+ key: "expiration",
86
+ type: "string",
87
+ placeholder: OPTION_EXPIRATION_PLACEHOLDER,
88
+ description: `The expiry to show, as ${OPTION_EXPIRATION_FORMAT}. Without it, the nearest one.`,
89
+ example: "--expiration 2028-01-21",
90
+ normalize: (value) => expiryIsoDate(expirationOptionSeconds(value)),
91
+ };
92
+
61
93
  export const optionsModule: PluginModule = {
62
94
  panes: [
63
95
  {
@@ -72,6 +104,25 @@ export const optionsModule: PluginModule = {
72
104
  quickSettings: [LIVE_STREAMING_QUICK_SETTING],
73
105
  settings: (context) => withLiveStreamingSetting(optionsSettings(context.settings), context.settings),
74
106
  tableExport: true,
107
+ reportOptions: [EXPIRATION_REPORT_OPTION, {
108
+ key: STRIKE_WINDOW_SETTING,
109
+ aliases: ["delta"],
110
+ type: "string",
111
+ placeholder: "all|count|delta band",
112
+ description: "Strikes to list: all (the default), a count either side of the money such as 10, "
113
+ + "or a call or put delta band such as 0.70-0.90. --delta 0.70-0.90 reads the same",
114
+ example: "--strikes 0.70-0.90",
115
+ normalize: normalizeStrikeWindowOption,
116
+ }, {
117
+ key: "columns",
118
+ settingKey: "optionColumnIds",
119
+ type: "string",
120
+ placeholder: "fields",
121
+ description: `Fields mirrored around the strike, in order, as the Columns setting picks them: ${OPTION_FIELD_DEFS.map((field) => field.id).join(", ")}. `
122
+ + "costOfSpot (the put's midpoint as a percent of spot) shows on the put side only",
123
+ example: "--columns bid,ask,spread,delta,openInterest,extrinsicPerYear",
124
+ normalize: normalizeOptionColumnsOption,
125
+ }],
75
126
  },
76
127
  ],
77
128
 
@@ -83,8 +134,10 @@ export const optionsModule: PluginModule = {
83
134
  description: "Options chain for the selected ticker.",
84
135
  keywords: ["options", "chain", "calls", "puts", "omon"],
85
136
  shortcut: "OMON",
86
- settings: (_symbol, _context, options) => options?.values?.expiration
87
- ? { expiration: Number(options.values.expiration), expirationTargetKey: null } : {},
137
+ settings: (_symbol, _context, options) => {
138
+ const expiration = readOptionExpiration(options?.values?.expiration);
139
+ return expiration == null ? {} : { expiration, expirationTargetKey: null };
140
+ },
88
141
  publicShare: true,
89
142
  }),
90
143
  ],