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
@@ -5,10 +5,11 @@ import type { QueryEntry } from "../../../market-data/result-types";
5
5
  import type { DataProvider } from "../../../types/data-provider";
6
6
  import type { OptionsChain, Quote, TickerFinancials } from "../../../types/financials";
7
7
  import { canonicalExchange, parsePublicTickerKey } from "../../../utils/exchanges";
8
+ import { expiryIsoDate, findListedExpiry, missingExpiryText } from "../../../utils/option-expiry";
8
9
  import { surfaceTreasuryRate } from "../vol-surface/model";
9
10
  import { daysToExpiryFrom, extractImpliedForward, optionMid, solveImpliedVolatility } from "../shared/volatility";
10
11
  import type { YieldPoint } from "../yield-curve/treasury-data";
11
- import { parseLegs, validatePosition, type ScenarioControls, type ScenarioLeg, type ScenarioPosition } from "./model";
12
+ import { parseLegs, scenarioCurrency, validatePosition, type ScenarioControls, type ScenarioLeg, type ScenarioPosition } from "./model";
12
13
  import { abortable, abortError } from "../../../utils/async-deadline";
13
14
  import { errorMessage } from "../../../utils/errors";
14
15
 
@@ -26,6 +27,8 @@ export interface ScenarioMarketSnapshot {
26
27
  underlyingQuote: Quote | null;
27
28
  rateAsOf: string[];
28
29
  warnings: string[];
30
+ /** Why the requested expiry has no chain, naming the listed dates nearest it. */
31
+ missingExpiry?: string;
29
32
  }
30
33
 
31
34
  export interface ScenarioLoaderDependencies {
@@ -99,15 +102,31 @@ export async function loadScenarioMarket(
99
102
  const validDividend = symbolMatches(financialsSymbol, symbol) && !fundamentals?.stale && !financialsEntry?.error
100
103
  && typeof fundamentals?.dividendYield === "number" && Number.isFinite(fundamentals.dividendYield) && fundamentals.dividendYield >= 0;
101
104
  if (!validDividend) warnings.push("Dividend yield unavailable; supply an explicit assumption");
102
- const chainEntry = entry(chainResult, "Options chain");
105
+ let chainEntry = entry(chainResult, "Options chain");
103
106
  let chain = chainEntry ? resolveEntryValue(chainEntry) : null;
104
107
  if (chain && !symbolMatches(chain.underlyingSymbol, symbol)) {
105
108
  warnings.push("Options chain does not match the requested ticker"); chain = null;
106
109
  }
107
110
  const expirationDates = [...new Set((chain?.expirationDates ?? []).filter((expiration) =>
108
111
  positive(expiration) && daysToExpiryFrom(expiration, now) > 0))].sort((a, b) => a - b);
109
- if (chain && request.expiration != null && (!expirationDates.includes(request.expiration)
110
- || [...chain.calls, ...chain.puts].some((contract) => contract.expiration !== request.expiration))) {
112
+ // A typed date is that day's UTC midnight; the chain's own stamp for the
113
+ // expiry is matched by calendar date, as OMON matches it.
114
+ const listedExpiration = request.expiration == null ? undefined : findListedExpiry(request.expiration, expirationDates);
115
+ const onExpiry = (value: OptionsChain, expiration: number) => [...value.calls, ...value.puts]
116
+ .every((contract) => expiryIsoDate(contract.expiration) === expiryIsoDate(expiration));
117
+ if (chain && listedExpiration != null && listedExpiration !== request.expiration && !chain.calls.length && !chain.puts.length) {
118
+ // A source that keys chains by its exact stamp answered the midnight request empty.
119
+ chainEntry = await abortable(dependencies.loadOptions({ instrument: request.instrument, expirationDate: listedExpiration }, options)
120
+ .catch((error: unknown) => { warnings.push(`Options chain: ${errorMessage(error)}`); return null; }), request.signal, CANCELLED);
121
+ if (request.signal?.aborted) throw abortError(CANCELLED);
122
+ if (chainEntry?.error) warnings.push(`Options chain: ${chainEntry.error.message}`);
123
+ chain = chainEntry ? resolveEntryValue(chainEntry) : null;
124
+ }
125
+ let missingExpiry: string | undefined;
126
+ if (chain && request.expiration != null && listedExpiration == null) {
127
+ missingExpiry = sentence(missingExpiryText(request.expiration, expirationDates, symbol));
128
+ warnings.push(missingExpiry); chain = null;
129
+ } else if (chain && listedExpiration != null && !onExpiry(chain, listedExpiration)) {
111
130
  warnings.push("Selected expiration unavailable in the returned options chain"); chain = null;
112
131
  }
113
132
  if (!chain) warnings.push("Options chain unavailable");
@@ -117,15 +136,22 @@ export async function loadScenarioMarket(
117
136
  if (curveResult.status === "rejected") warnings.push(`Treasury: ${errorMessage(curveResult.reason)}`);
118
137
  const rate = surfaceTreasuryRate(curve, rateExpiration == null ? NaN : daysToExpiryFrom(rateExpiration, now) / 365);
119
138
  warnings.push(...rate.warnings);
139
+ // A currency does not go stale with its quote: the listing's quote, its snapshot, or its contracts name it.
140
+ const currency = [symbolMatches(quote?.symbol, symbol) ? quote?.currency : undefined,
141
+ symbolMatches(financialsSymbol, symbol) ? financials?.quote?.currency : undefined,
142
+ chain?.calls[0]?.currency, chain?.puts[0]?.currency].find((value) => !!value?.trim())?.trim().toUpperCase() ?? "";
120
143
  return {
121
- symbol, exchange: request.instrument.exchange, spot: quoteCurrent ? quote!.price : null, currency: quoteCurrent ? quote!.currency : "",
144
+ symbol, exchange: request.instrument.exchange, spot: quoteCurrent ? quote!.price : null, currency,
122
145
  asOf: quoteCurrent && positive(quote!.lastUpdated) ? quote!.lastUpdated : now,
123
146
  chain, expirationDates, rate: rate.rate, dividendYield: validDividend ? fundamentals!.dividendYield! : null,
124
147
  source: quoteCurrent ? quoteEntry?.source ?? quote!.providerId ?? null : null,
125
148
  underlyingQuote: quoteCurrent ? quote : null, rateAsOf: rate.asOf, warnings: [...new Set(warnings)],
149
+ ...(missingExpiry ? { missingExpiry } : {}),
126
150
  };
127
151
  }
128
152
 
153
+ const sentence = (text: string) => text.charAt(0).toUpperCase() + text.slice(1);
154
+
129
155
  function supplied(value: unknown): boolean { return value != null && value !== ""; }
130
156
 
131
157
  function numericSetting(settings: Record<string, unknown>, key: string): number | undefined {
@@ -175,13 +201,18 @@ export function scenarioPositionFromSettings(
175
201
  const dividendYield = dividendValue == null ? market?.dividendYield : dividendValue / 100;
176
202
  const legText = typeof settings.legs === "string" ? settings.legs : "";
177
203
  let legs = legText.trim() ? parseLegs(legText) : [];
204
+ const strike = numericSetting(settings, "strike");
205
+ if (strike != null && !(strike > 0)) throw new Error("strike must be positive");
206
+ if (strike != null && !supplied(settings.strategy)) throw new Error("strike picks the strike of --strategy; add --strategy put, vertical or straddle");
178
207
  if (!legs.length && supplied(settings.strategy)) {
179
- if (settings.strategy !== "vertical" && settings.strategy !== "straddle") throw new Error("strategy must be vertical or straddle");
180
- if (!market?.chain || market.warnings.includes("Options chain is stale")) throw new Error("A current options chain is required to seed a strategy");
208
+ if (!isScenarioStrategy(settings.strategy)) throw new Error("strategy must be vertical, straddle or put");
209
+ if (!market?.chain || market.warnings.includes("Options chain is stale")) {
210
+ throw new Error(market?.missingExpiry ?? "A current options chain is required to seed a strategy");
211
+ }
181
212
  if (spot == null) throw new Error("A current underlying price or explicit --spot is required");
182
213
  // Leg IVs are solved at the market's own spot and time, never at a what-if override.
183
214
  const pricing = market.spot != null && rate != null ? { spot: market.spot, asOf: market.asOf, rate } : null;
184
- const seeded = scenarioStrategyLegs(market.chain, spot, settings.strategy, pricing);
215
+ const seeded = scenarioStrategyLegs(market.chain, spot, settings.strategy, pricing, strike);
185
216
  legs = seeded.legs;
186
217
  // Mids priced off the parity forward are worth their cost only at the spot
187
218
  // that forward implies on the scenario's own rate and dividend yield. The
@@ -198,7 +229,7 @@ export function scenarioPositionFromSettings(
198
229
  if (dividendYield == null) throw new Error("Dividend yield unavailable; supply --dividend-yield as an annual percentage");
199
230
  const position: ScenarioPosition = {
200
231
  symbol, exchange: exchange ?? market?.exchange,
201
- currency: String(settings.currency ?? market?.currency ?? "").trim().toUpperCase() || "UNKNOWN",
232
+ currency: scenarioCurrency(settings.currency, market?.currency, exchange ?? market?.exchange),
202
233
  spot, rate, dividendYield, asOf: dateSetting(settings, "asOf") ?? market?.asOf ?? Date.now(), legs,
203
234
  };
204
235
  const error = validatePosition(position);
@@ -210,6 +241,9 @@ export function scenarioPositionFromSettings(
210
241
  return position;
211
242
  }
212
243
 
244
+ type ScenarioStrategy = "vertical" | "straddle" | "put";
245
+ const isScenarioStrategy = (value: unknown): value is ScenarioStrategy => value === "vertical" || value === "straddle" || value === "put";
246
+
213
247
  interface StrategyPricing { spot: number; asOf: number; rate: number }
214
248
 
215
249
  const usableVolatility = (value: unknown): value is number => positive(value) && value <= 5;
@@ -230,9 +264,13 @@ interface StrategyParity { forward: number; daysToExpiry: number }
230
264
  * keeps a usable provider IV, and without one it is not eligible. `parity` is
231
265
  * the forward a seeded leg was solved against, so the caller can start the
232
266
  * scenario at the spot it implies.
267
+ *
268
+ * Each strategy is anchored at the strike nearest spot, or at `strike` when
269
+ * one is given: a long put (a protective put), a call vertical from the
270
+ * anchor to the next listed strike up, or a straddle at the anchor.
233
271
  */
234
272
  function scenarioStrategyLegs(
235
- chain: OptionsChain, spot: number, strategy: "vertical" | "straddle", pricing: StrategyPricing | null,
273
+ chain: OptionsChain, spot: number, strategy: ScenarioStrategy, pricing: StrategyPricing | null, strike?: number,
236
274
  ): { legs: ScenarioLeg[]; parity: StrategyParity | null } {
237
275
  const parity = new Map<number, StrategyParity | null>();
238
276
  const parityFor = (expiration: number) => {
@@ -256,12 +294,30 @@ function scenarioStrategyLegs(
256
294
  if (usableVolatility(volatility)) return [{ contract, mid, volatility, solved: true }];
257
295
  return usableVolatility(contract.impliedVolatility) ? [{ contract, mid, volatility: contract.impliedVolatility, solved: false }] : [];
258
296
  });
259
- const calls = quoted(chain.calls, "call").sort((a, b) => Math.abs(a.contract.strike - spot) - Math.abs(b.contract.strike - spot));
260
- const puts = quoted(chain.puts, "put");
261
- const first = calls.find(({ contract: call }) => strategy === "vertical"
262
- ? calls.some(({ contract: other }) => other.expiration === call.expiration && other.strike > call.strike)
263
- : puts.some(({ contract: put }) => put.expiration === call.expiration && put.strike === call.strike));
264
- if (!first) throw new Error("The selected chain has no complete quoted strategy with usable IV");
297
+ const nearest = (a: { contract: { strike: number } }, b: { contract: { strike: number } }) =>
298
+ Math.abs(a.contract.strike - spot) - Math.abs(b.contract.strike - spot);
299
+ const anchored = ({ contract }: { contract: { strike: number } }) => strike == null || Math.abs(contract.strike - strike) < 1e-9;
300
+ const calls = quoted(chain.calls, "call").sort(nearest);
301
+ const puts = quoted(chain.puts, "put").sort(nearest);
302
+ const missing = (candidates: { contract: { strike: number } }[]) => {
303
+ if (strike == null) return new Error("The selected chain has no complete quoted strategy with usable IV");
304
+ const listed = [...new Set(candidates.map(({ contract }) => contract.strike))]
305
+ .sort((a, b) => Math.abs(a - strike) - Math.abs(b - strike)).slice(0, 4).sort((a, b) => a - b);
306
+ return new Error(`The selected chain has no complete quoted ${strategy} at strike ${strike} with usable IV`
307
+ + (listed.length ? `; nearest quoted strikes: ${listed.join(", ")}` : ""));
308
+ };
309
+ if (strategy === "put") {
310
+ const put = puts.find(anchored);
311
+ if (!put) throw missing(puts);
312
+ return { legs: [{ id: put.contract.contractSymbol, side: "put", quantity: 1, strike: put.contract.strike,
313
+ expiration: put.contract.expiration, price: put.mid, priceSource: "mid", volatility: put.volatility, multiplier: 100,
314
+ ...(put.solved ? { volatilitySource: "mid" as const } : {}) }],
315
+ parity: put.solved ? parityFor(put.contract.expiration) : null };
316
+ }
317
+ const first = calls.find((entry) => anchored(entry) && (strategy === "vertical"
318
+ ? calls.some(({ contract: other }) => other.expiration === entry.contract.expiration && other.strike > entry.contract.strike)
319
+ : puts.some(({ contract: put }) => put.expiration === entry.contract.expiration && put.strike === entry.contract.strike)));
320
+ if (!first) throw missing(calls);
265
321
  const second = strategy === "vertical"
266
322
  ? calls.filter(({ contract }) => contract.expiration === first.contract.expiration && contract.strike > first.contract.strike)
267
323
  .sort((a, b) => a.contract.strike - b.contract.strike)[0]!
@@ -270,7 +326,7 @@ function scenarioStrategyLegs(
270
326
  const legs = [first, second].map(({ contract, mid, volatility, solved }, index): ScenarioLeg => ({
271
327
  id: contract.contractSymbol, side: index === 1 && strategy === "straddle" ? "put" : "call",
272
328
  quantity: index === 1 && strategy === "vertical" ? -1 : 1, strike: contract.strike,
273
- expiration: contract.expiration, price: mid, volatility, multiplier: 100,
329
+ expiration: contract.expiration, price: mid, priceSource: "mid", volatility, multiplier: 100,
274
330
  ...(solved ? { volatilitySource: "mid" as const } : {}),
275
331
  }));
276
332
  return { legs, parity: first.solved || second.solved ? parityFor(first.contract.expiration) : null };
@@ -1,38 +1,66 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
2
  import { parsePublicTickerKey } from "../../../utils/exchanges";
3
+ import {
4
+ expirationOptionSeconds, OPTION_EXPIRATION_FORMAT, OPTION_EXPIRATION_PLACEHOLDER, readOptionExpiration,
5
+ } from "../../../utils/option-expiry";
3
6
  import { resolveHeadlessInstrument } from "../shared/headless-market-data";
4
7
  import {
5
8
  createScenarioDependencies, loadScenarioMarket, scenarioControlsFromSettings, scenarioPositionFromSettings,
6
9
  type ScenarioMarketSnapshot,
7
10
  } from "./client";
8
- import { buildScenario, parseLegs } from "./model";
11
+ import { buildScenario, currencyLabel, parseLegs, scenarioValueUnit } from "./model";
12
+ import { hedgeInputsFromSettings, hedgeReportEntries, scenarioHedgeBudget } from "./hedge";
9
13
 
10
14
  // Float noise at a flat origin or a breakeven prints as 0.00, not -0.00.
11
15
  const money = (value: unknown) => typeof value === "number" ? (Math.abs(value) < 0.005 ? 0 : value).toFixed(2) : "--";
16
+ const percent = (value: unknown) => typeof value === "number" ? `${(value * 100).toFixed(2)}%` : "--";
12
17
  const supplied = (value: unknown) => value != null && value !== "";
18
+ // A seeded entry is a quote midpoint, often to the half cent (5.495), so it keeps up to four decimals.
19
+ const premium = (value: unknown) => typeof value === "number"
20
+ ? value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 4, useGrouping: false }) : "--";
21
+ /** Entry prices and spot are quoted per share of the underlying. */
22
+ const PRICE_UNIT = "per share";
23
+ /** The Valuation entries, in model order, with the unit each is in. */
24
+ const VALUATION_LABELS: Record<string, (currency: string) => string> = {
25
+ price: () => "Value", pnl: () => "P&L", delta: () => "Delta (shares)", gamma: () => "Gamma (shares per 1.00 move)",
26
+ thetaPerDay: (currency) => `Theta (${currency} per day)`, vegaPerPoint: (currency) => `Vega (${currency} per vol point)`,
27
+ rhoPerPoint: (currency) => `Rho (${currency} per rate point)`,
28
+ };
13
29
 
14
30
  export const optionsScenarioHeadless: HeadlessPaneDefinition<"bundle"> = {
15
31
  shape: "bundle", argument: { kind: "ticker", description: "Underlying ticker" },
16
32
  describe: (args) => `OSA ${args.symbols[0] ?? ""}`,
33
+ description: "A typed or chain-seeded options position valued across spot, date and volatility: its legs, value and Greeks, expiry risk and a "
34
+ + "scenario P&L grid. With --nav and --budget-bps, how many contracts a hedge budget buys, the premium spent, the notional protected and "
35
+ + "coverage of an equity sleeve.",
17
36
  discovery: { screenshotReadiness: "ready", limitations: ["European scenario values do not model early assignment or exercise."] },
18
37
  options: [
19
38
  { key: "tab", type: "enum", values: ["payoff", "grid", "legs"].map((value) => ({ value })), defaultValue: "payoff",
20
39
  description: "Initial view", pluginState: { pluginId: "ticker-research", key: "activeTabId" } },
21
- { key: "legs", type: "string", description: "Semicolon-separated side,strike,YYYY-MM-DD,signed quantity,entry price,IV percent[,multiplier]" },
22
- { key: "strategy", type: "enum", values: [{ value: "vertical" }, { value: "straddle" }], description: "Build an explicit example from current two-sided chain quotes" },
23
- { key: "expiration", type: "integer", minimum: 1, description: "Chain expiration in Unix seconds" },
40
+ { key: "legs", type: "string", description: "Semicolon-separated side,strike,YYYY-MM-DD,signed quantity,entry price per share,IV percent[,multiplier]" },
41
+ { key: "strategy", type: "enum", values: [{ value: "vertical" }, { value: "straddle" }, { value: "put" }],
42
+ description: "Build a position from current two-sided chain quotes: a call vertical, a straddle, or one long put (a protective put)" },
43
+ { key: "strike", type: "string", description: "The --strategy strike: the put, the vertical's long call or the straddle. Nearest the money when omitted" },
44
+ { key: "expiration", type: "string", placeholder: OPTION_EXPIRATION_PLACEHOLDER, example: "--strategy vertical --expiration 2027-01-15",
45
+ description: `Chain expiry for --strategy, as ${OPTION_EXPIRATION_FORMAT}`, normalize: (value) => expirationOptionSeconds(value) },
24
46
  { key: "spot", type: "string", description: "Underlying price override, per share" },
25
47
  { key: "rate", type: "string", description: "Continuously compounded annual risk-free rate, percent" },
26
48
  { key: "dividendYield", aliases: ["dividend-yield"], type: "string", description: "Continuous annual dividend yield, percent" },
27
- { key: "currency", type: "string", description: "Monetary unit for supplied inputs, such as USD" },
49
+ { key: "currency", type: "string", description: "Currency of the inputs, such as USD; the underlying's when omitted" },
28
50
  { key: "asOf", aliases: ["as-of"], type: "string", description: "Position valuation timestamp: UTC ISO or YYYY-MM-DD" },
29
51
  { key: "date", type: "string", description: "Scenario date: UTC ISO or YYYY-MM-DD" },
30
52
  { key: "volShift", aliases: ["vol-shift"], type: "string", defaultValue: "0", description: "Parallel volatility shift, percentage points" },
31
53
  { key: "spotRange", aliases: ["spot-range"], type: "string", defaultValue: "30", description: "Spot grid range above and below current spot, percent" },
54
+ { key: "nav", type: "string", description: "Portfolio NAV for a hedge budget, such as 100m, 2.5bn, 250k, 1,000,000 or $100m. Needs --budget-bps" },
55
+ { key: "budgetBps", aliases: ["budget-bps"], type: "string", example: "--strategy put --strike 700 --expiration 2027-01-15 --nav 100m --budget-bps 50",
56
+ description: "Hedge budget in basis points of --nav (50 is 0.5%): the report says how many of the position it buys, the premium, and the notional protected" },
57
+ { key: "sleeve", aliases: ["equity-sleeve"], type: "string", description: "Equity sleeve the hedge protects, for coverage = notional / sleeve. Same amount forms as --nav" },
32
58
  ],
33
59
  async load(args, ctx) {
34
60
  const symbol = args.symbols[0]!;
35
61
  const settings: Record<string, unknown> = { ...ctx.settings, ...args.options, symbol };
62
+ // Bad hedge inputs fail before any market request.
63
+ hedgeInputsFromSettings(settings);
36
64
  const suppliedInputs = !!settings.legs && ["spot", "rate", "dividendYield"].every((key) => settings[key] != null && settings[key] !== "");
37
65
  let market: ScenarioMarketSnapshot;
38
66
  if (suppliedInputs) {
@@ -42,8 +70,8 @@ export const optionsScenarioHeadless: HeadlessPaneDefinition<"bundle"> = {
42
70
  } else {
43
71
  const instrument = await resolveHeadlessInstrument(ctx, symbol);
44
72
  const typedExpiries = typeof settings.legs === "string" && settings.legs.trim() ? parseLegs(settings.legs).map((leg) => leg.expiration) : [];
45
- market = await loadScenarioMarket({ instrument,
46
- expiration: typeof args.options.expiration === "number" ? args.options.expiration : undefined,
73
+ const expiration = readOptionExpiration(args.options.expiration);
74
+ market = await loadScenarioMarket({ instrument, expiration: expiration ?? undefined,
47
75
  rateExpiration: typedExpiries.length ? Math.min(...typedExpiries) : undefined, signal: ctx.signal,
48
76
  }, createScenarioDependencies(ctx.marketData, ctx.apiClient));
49
77
  }
@@ -51,13 +79,21 @@ export const optionsScenarioHeadless: HeadlessPaneDefinition<"bundle"> = {
51
79
  const position = scenarioPositionFromSettings(settings, market);
52
80
  const controls = position ? scenarioControlsFromSettings(settings, position) : null;
53
81
  const scenario = position && controls ? buildScenario(position, controls) : null;
54
- const errors = scenario ? [...new Set([...market.warnings, ...scenario.warnings])]
55
- : [...market.warnings, "No position supplied; add --legs or select --strategy"];
82
+ const hedge = scenarioHedgeBudget(settings, position, market);
83
+ // A typed rate or yield stands in for the source that could not supply one.
84
+ const marketWarnings = market.warnings.filter((warning) => !(supplied(settings.dividendYield) && warning.startsWith("Dividend yield unavailable"))
85
+ && !(supplied(settings.rate) && warning.startsWith("Treasury")));
86
+ const errors = scenario ? [...new Set([...marketWarnings, ...scenario.warnings])]
87
+ : [...marketWarnings, "No position supplied; add --legs or select --strategy"];
56
88
  const midVolatility = !!position?.legs.some((leg) => leg.volatilitySource === "mid");
57
89
  // A seeded strategy starts at the spot its quote mids imply; the last print is reported beside it.
58
90
  const lastPrint = midVolatility && !supplied(settings.spot) && market.spot != null && money(market.spot) !== money(position!.spot)
59
91
  ? market.spot : null;
60
92
  const chain = market.chain;
93
+ const currency = position ? currencyLabel(position.currency) : null;
94
+ // Value, P&L, risk and the grid add every contract; entry prices and spot are per share.
95
+ const valueUnit = position ? scenarioValueUnit(position) : null;
96
+ const per = `${currency} ${valueUnit}`;
61
97
  return {
62
98
  freshness: suppliedInputs
63
99
  ? { source: "Your inputs", status: "not-a-feed", basis: "scenario" }
@@ -66,33 +102,44 @@ export const optionsScenarioHeadless: HeadlessPaneDefinition<"bundle"> = {
66
102
  sections: scenario ? [
67
103
  { title: "Position", columns: [
68
104
  { key: "side", header: "Side" }, { key: "strike", header: "Strike" }, { key: "expiry", header: "Expiry" },
69
- { key: "quantity", header: "Quantity" }, { key: "price", header: "Entry", format: money },
105
+ { key: "quantity", header: "Contracts" }, { key: "price", header: "Entry/share", format: premium },
70
106
  { key: "volatility", header: "IV", format: (value) => typeof value === "number" ? `${(value * 100).toFixed(2)}%` : "--" },
71
107
  ...(midVolatility ? [{ key: "volatilityFrom", header: "IV from" }] : []),
72
108
  { key: "multiplier", header: "Multiplier" },
73
109
  ], rows: position!.legs.map((leg) => ({ ...leg, expiry: new Date(leg.expiration * 1000).toISOString().slice(0, 10),
74
110
  ...(midVolatility ? { volatilityFrom: leg.volatilitySource === "mid" ? "quote mid" : "provider" } : {}) })) },
75
- ...(lastPrint != null ? [{ title: "Spot", entries: [
76
- { key: "spot", label: "Implied by option quotes", value: position!.spot, formatted: money(position!.spot) },
77
- { key: "last", label: "Last price", value: lastPrint, formatted: money(lastPrint) },
78
- ] }] : []),
79
- { title: "Valuation", entries: Object.entries(scenario.valuation).map(([key, value]) => ({ key, label: key, value, formatted: money(value) })) },
80
- { title: "Expiry risk", entries: [
81
- { label: "Breakevens", value: scenario.expiryRisk.breakevens },
82
- { label: "Max profit", value: scenario.expiryRisk.maxProfit, formatted: scenario.expiryRisk.unlimitedProfit ? "Unlimited" : money(scenario.expiryRisk.maxProfit) },
83
- { label: "Max loss", value: scenario.expiryRisk.maxLoss, formatted: scenario.expiryRisk.unlimitedLoss ? "Unlimited" : money(scenario.expiryRisk.maxLoss) },
84
- ...(scenario.expiryRisk.reason ? [{ label: "Limitation", value: scenario.expiryRisk.reason }] : []),
111
+ { title: "Inputs", entries: [
112
+ { key: "spot", label: lastPrint != null ? "Spot implied by option quotes" : "Spot", value: position!.spot,
113
+ formatted: `${money(position!.spot)} ${PRICE_UNIT}` },
114
+ ...(lastPrint != null ? [{ key: "last", label: "Last price", value: lastPrint, formatted: `${money(lastPrint)} ${PRICE_UNIT}` }] : []),
115
+ { key: "rate", label: "Rate", value: position!.rate, formatted: percent(position!.rate) },
116
+ { key: "dividendYield", label: "Dividend yield", value: position!.dividendYield, formatted: percent(position!.dividendYield) },
117
+ { key: "currency", label: "Currency", value: position!.currency, formatted: currency! },
118
+ ] },
119
+ ...(hedge ? [{ title: "Hedge budget", entries: hedgeReportEntries(hedge) }] : []),
120
+ { title: `Valuation (${per})`, entries: Object.entries(scenario.valuation).map(([key, value]) => ({ key,
121
+ label: VALUATION_LABELS[key]?.(currency!) ?? key, value, formatted: money(value) })) },
122
+ { title: `Expiry risk (${per})`, entries: [
123
+ { key: "breakevens", label: "Breakevens (spot)", value: scenario.expiryRisk.breakevens,
124
+ formatted: scenario.expiryRisk.breakevens.map(money).join(", ") || "none" },
125
+ { key: "maxProfit", label: "Max profit", value: scenario.expiryRisk.maxProfit, formatted: scenario.expiryRisk.unlimitedProfit ? "Unlimited" : money(scenario.expiryRisk.maxProfit) },
126
+ { key: "maxLoss", label: "Max loss", value: scenario.expiryRisk.maxLoss, formatted: scenario.expiryRisk.unlimitedLoss ? "Unlimited" : money(scenario.expiryRisk.maxLoss) },
127
+ ...(scenario.expiryRisk.reason ? [{ key: "limitation", label: "Limitation", value: scenario.expiryRisk.reason }] : []),
85
128
  ] },
86
- { title: "Scenario grid", columns: [
87
- { key: "spot", header: "Spot" }, { key: "move", header: "Move", format: (value) => typeof value === "number" && Number.isFinite(value) ? `${(value * 100).toFixed(1)}%` : "--" },
129
+ { title: `Scenario grid (P&L, ${per})`, columns: [
130
+ { key: "spot", header: "Spot/share", format: money },
131
+ { key: "move", header: "Move", format: (value) => typeof value === "number" && Number.isFinite(value) ? `${(value * 100).toFixed(1)}%` : "--" },
88
132
  ...scenario.dates.map((date, index) => ({ key: `date${index}`, header: new Date(date).toISOString().slice(0, 10), format: money })),
89
133
  ], rows: scenario.grid.map((row) => ({ spot: row.spot, move: row.move,
90
134
  ...Object.fromEntries(row.values.map((value, index) => [`date${index}`, value])) })) },
91
135
  ] : [],
92
136
  complete: scenario != null && errors.length === 0,
93
137
  unavailableSymbols: scenario ? [] : [symbol], errors,
94
- metadata: { scenario, position, controls, market, inputSource: suppliedInputs ? "user" : "market",
95
- unit: position?.currency || "currency unspecified", methodology: "docs/research-data.md#options-scenario-analysis" },
138
+ metadata: { scenario, position, controls, market, ...(hedge ? { hedge } : {}), inputSource: suppliedInputs ? "user" : "market",
139
+ unit: position?.currency || "currency unspecified",
140
+ // Leg entry prices and spot are per share; value, P&L, risk, Greeks in currency and the grid are for valueUnit.
141
+ ...(position ? { units: { currency: position.currency, price: PRICE_UNIT, value: valueUnit } } : {}),
142
+ methodology: "docs/research-data.md#options-scenario-analysis" },
96
143
  };
97
144
  },
98
145
  };