gloomberb 0.16.3 → 0.16.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (820) hide show
  1. package/README.md +14 -91
  2. package/package.json +7 -1
  3. package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
  4. package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
  5. package/src/account/billing.ts +57 -0
  6. package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
  7. package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
  8. package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
  9. package/src/account/session.ts +16 -0
  10. package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +42 -17
  11. package/src/account/upgrade-personalization.ts +57 -0
  12. package/src/account/upgrade-tickers.ts +124 -0
  13. package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
  14. package/src/api-client/askg.ts +43 -0
  15. package/src/api-client/auth.ts +15 -0
  16. package/src/api-client/beneficial-owners.ts +4 -0
  17. package/src/api-client/chat.ts +19 -0
  18. package/src/api-client/company-kpis.ts +14 -2
  19. package/src/api-client/data.ts +58 -18
  20. package/src/api-client/earnings.ts +6 -0
  21. package/src/api-client/futures-curve.ts +56 -0
  22. package/src/api-client/index.ts +16 -1
  23. package/src/api-client/market-discovery.ts +10 -1
  24. package/src/api-client/normalizers.ts +27 -0
  25. package/src/api-client/paths.ts +95 -23
  26. package/src/api-client/request.ts +2 -1
  27. package/src/api-client/research-activity.ts +46 -10
  28. package/src/api-client/socket.ts +39 -8
  29. package/src/api-client/types.ts +74 -2
  30. package/src/api-client/upgrade-experiment.ts +90 -0
  31. package/src/api-client/yield-curves.ts +8 -0
  32. package/src/app/global-shortcuts.ts +26 -2
  33. package/src/app/keybindings/actions.ts +10 -0
  34. package/src/app/pane-runtime/index.tsx +8 -0
  35. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  36. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  37. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  38. package/src/app/pane-runtime/ticker-open-runtime.ts +25 -4
  39. package/src/app/runtime/desktop-deeplink.ts +5 -0
  40. package/src/app/star-prompt/model.ts +98 -0
  41. package/src/app/star-prompt/runtime.ts +41 -0
  42. package/src/app/star-prompt/status-line.tsx +190 -0
  43. package/src/app.tsx +13 -7
  44. package/src/architecture/core-builtin-allowlist.json +61 -0
  45. package/src/brokers/signed-in/adapter.ts +33 -2
  46. package/src/brokers/signed-in/client.ts +10 -0
  47. package/src/brokers/signed-in/sign-in-dialog.tsx +34 -14
  48. package/src/brokers/signed-in/sign-in.ts +62 -5
  49. package/src/capabilities/chart-series.ts +11 -1
  50. package/src/capabilities/registry.ts +8 -1
  51. package/src/capabilities/types.ts +2 -0
  52. package/src/cli/commands/crypto-hints.ts +19 -4
  53. package/src/cli/commands/fred-series.ts +61 -0
  54. package/src/cli/commands/market.ts +744 -181
  55. package/src/cli/commands/news-hints.ts +62 -0
  56. package/src/cli/commands/news-search.ts +53 -0
  57. package/src/cli/commands/options-chain.ts +172 -0
  58. package/src/cli/commands/options-leaps.ts +425 -0
  59. package/src/cli/commands/overview.ts +251 -40
  60. package/src/cli/commands/remote.ts +2 -1
  61. package/src/cli/commands/search.ts +109 -15
  62. package/src/cli/commands/system.ts +151 -24
  63. package/src/cli/commands/themes.ts +64 -0
  64. package/src/cli/commands/ticker.ts +149 -40
  65. package/src/cli/context.ts +45 -14
  66. package/src/cli/desktop-pane-shot.ts +150 -17
  67. package/src/cli/earnings-calendar.ts +71 -0
  68. package/src/cli/errors.ts +25 -2
  69. package/src/cli/freshness.ts +22 -8
  70. package/src/cli/fx-pair.ts +57 -0
  71. package/src/cli/glossary.ts +429 -0
  72. package/src/cli/help.ts +14 -1
  73. package/src/cli/history-rows.ts +48 -7
  74. package/src/cli/index-roots.ts +58 -0
  75. package/src/cli/index.ts +29 -12
  76. package/src/cli/known-gaps.ts +27 -0
  77. package/src/cli/listing-arg.ts +595 -26
  78. package/src/cli/not-a-ticker.ts +44 -0
  79. package/src/cli/options.ts +50 -6
  80. package/src/cli/pane-functions/access-gate.ts +5 -0
  81. package/src/cli/pane-functions/capabilities.ts +11 -4
  82. package/src/cli/pane-functions/catalog.ts +48 -7
  83. package/src/cli/pane-functions/data.ts +48 -13
  84. package/src/cli/pane-functions/discovery.ts +1 -0
  85. package/src/cli/pane-functions/dom.ts +41 -19
  86. package/src/cli/pane-functions/freshness.ts +531 -32
  87. package/src/cli/pane-functions/function-help.ts +77 -25
  88. package/src/cli/pane-functions/headless.ts +50 -21
  89. package/src/cli/pane-functions/index.ts +66 -10
  90. package/src/cli/pane-functions/market-session.ts +147 -0
  91. package/src/cli/pane-functions/options.ts +77 -25
  92. package/src/cli/pane-functions/report-notices.ts +81 -0
  93. package/src/cli/pane-functions/report.ts +19 -8
  94. package/src/cli/pane-functions/resolver.ts +53 -18
  95. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  96. package/src/cli/pane-functions/screenshot.ts +199 -30
  97. package/src/cli/registry.ts +2 -1
  98. package/src/cli/report-tables.ts +3 -0
  99. package/src/cli/result.ts +53 -13
  100. package/src/cli/row-window.ts +76 -0
  101. package/src/cli/scroll-truncation.ts +34 -0
  102. package/src/cli/staged-tickers.ts +52 -0
  103. package/src/cli/symbol-args.ts +9 -0
  104. package/src/cli/symbol-list.ts +172 -0
  105. package/src/cli/ticker-resolution.ts +223 -30
  106. package/src/cli/types.ts +5 -0
  107. package/src/components/chart/composite/axis-overlays.tsx +6 -2
  108. package/src/components/chart/composite/composite-chart.tsx +83 -10
  109. package/src/components/chart/composite/legend.tsx +88 -8
  110. package/src/components/chart/composite/panel-surface.tsx +44 -17
  111. package/src/components/chart/composite/price-axis-labels.tsx +3 -1
  112. package/src/components/chart/composite/rasterizer.ts +64 -4
  113. package/src/components/chart/composite/series-cues.ts +71 -0
  114. package/src/components/chart/composite/text-renderer.ts +16 -8
  115. package/src/components/chart/composite/types.ts +3 -0
  116. package/src/components/command-bar/commands/direct/index.ts +9 -1
  117. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  118. package/src/components/command-bar/commands/registry.ts +7 -0
  119. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  120. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  121. package/src/components/command-bar/panel/index.tsx +11 -3
  122. package/src/components/command-bar/panel/runtime.ts +3 -0
  123. package/src/components/command-bar/panel/types.ts +2 -0
  124. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  125. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  126. package/src/components/command-bar/routes/root/provider-search.ts +4 -3
  127. package/src/components/command-bar/routes/root/results.ts +40 -10
  128. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  129. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  130. package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
  131. package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
  132. package/src/components/command-bar/surface/index.tsx +18 -4
  133. package/src/components/command-bar/theme-picker.tsx +48 -13
  134. package/src/components/command-bar/workflow/ops.ts +5 -3
  135. package/src/components/command-bar/workflow/tickers.ts +23 -4
  136. package/src/components/data-table/view.tsx +28 -9
  137. package/src/components/layout/detached-pane-shell.tsx +9 -0
  138. package/src/components/layout/floating-pane.tsx +9 -2
  139. package/src/components/layout/header.tsx +20 -5
  140. package/src/components/layout/market-summary.ts +23 -9
  141. package/src/components/layout/pane/content.tsx +40 -29
  142. package/src/components/layout/pane/cycle-order.ts +2 -11
  143. package/src/components/layout/pane/footer/index.tsx +6 -2
  144. package/src/components/layout/pane/footer/model.ts +35 -1
  145. package/src/components/layout/pane/header.tsx +26 -2
  146. package/src/components/layout/pane/index.tsx +12 -2
  147. package/src/components/layout/pane/sidebar.tsx +78 -10
  148. package/src/components/layout/pane/title.ts +1 -1
  149. package/src/components/layout/presentation.ts +22 -0
  150. package/src/components/layout/shell/active-drag.ts +21 -5
  151. package/src/components/layout/shell/drag/index.ts +43 -9
  152. package/src/components/layout/shell/drag/live.ts +87 -6
  153. package/src/components/layout/shell/drag/runtime.ts +62 -20
  154. package/src/components/layout/shell/index.tsx +71 -12
  155. package/src/components/layout/shell/layout-state.ts +5 -11
  156. package/src/components/layout/shell/menu.ts +33 -1
  157. package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
  158. package/src/components/layout/shell/pane/actions.ts +2 -1
  159. package/src/components/layout/shell/pane/layers.tsx +216 -28
  160. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  161. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  162. package/src/components/layout/shell/shortcuts.ts +5 -2
  163. package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
  164. package/src/components/layout/shell/visible-layout.ts +44 -1
  165. package/src/components/layout/status-bar.tsx +4 -1
  166. package/src/components/markdown-text.tsx +69 -23
  167. package/src/components/metric-treemap/heat-surface.tsx +37 -9
  168. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  169. package/src/components/onboarding/account-step/form-panel.tsx +1 -1
  170. package/src/components/onboarding/account-step/index.tsx +1 -1
  171. package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
  172. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  173. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  174. package/src/components/onboarding/onboarding-keyboard.ts +1 -1
  175. package/src/components/onboarding/onboarding-wizard.tsx +50 -41
  176. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  177. package/src/components/onboarding/pro-step-layout.ts +86 -0
  178. package/src/components/onboarding/use-onboarding-upgrade.ts +8 -4
  179. package/src/components/onboarding/wizard-account.ts +2 -2
  180. package/src/components/pane-template-wizard.tsx +9 -1
  181. package/src/components/price-selector-dialog.tsx +11 -2
  182. package/src/components/sign-in-gate.tsx +3 -3
  183. package/src/components/ticker/list-table-view.tsx +24 -1
  184. package/src/components/ui/action-row.tsx +2 -1
  185. package/src/components/ui/button.tsx +17 -1
  186. package/src/components/ui/choice-dialog.tsx +6 -4
  187. package/src/components/ui/confirm-dialog.tsx +28 -18
  188. package/src/components/ui/data-table/index.tsx +49 -0
  189. package/src/components/ui/data-table/opentui/index.tsx +37 -6
  190. package/src/components/ui/data-table/types.ts +20 -1
  191. package/src/components/ui/data-table/visible-range.ts +2 -0
  192. package/src/components/ui/dialog-actions.tsx +64 -0
  193. package/src/components/ui/display.tsx +17 -3
  194. package/src/components/ui/frame.tsx +35 -2
  195. package/src/components/ui/icon.tsx +13 -2
  196. package/src/components/ui/index.ts +1 -1
  197. package/src/components/ui/text-prompt-dialog.tsx +11 -9
  198. package/src/core/connection-health.ts +1 -1
  199. package/src/core/state/app/layout-reducer.ts +89 -3
  200. package/src/core/state/app/state.ts +8 -5
  201. package/src/core/state/app/types.ts +13 -2
  202. package/src/data/cached-query.ts +2 -0
  203. package/src/data/config/layout.ts +55 -21
  204. package/src/data/config/store/fold-panes.ts +149 -0
  205. package/src/data/config/store/migrations.ts +7 -0
  206. package/src/data/config/store/node.ts +27 -6
  207. package/src/data/config/store/normalize.ts +44 -5
  208. package/src/data/ticker-repository.ts +5 -0
  209. package/src/data/ticker-store.ts +15 -0
  210. package/src/docs/content.ts +215 -0
  211. package/src/docs/load.ts +139 -0
  212. package/src/docs/requests.ts +39 -0
  213. package/src/docs/snapshot.chunk.ts +8 -0
  214. package/src/docs/snapshot.json +7416 -0
  215. package/src/docs/topics.ts +216 -0
  216. package/src/i18n/es.ts +75 -5
  217. package/src/i18n/ja.ts +75 -5
  218. package/src/i18n/ko.ts +75 -5
  219. package/src/i18n/zh-cn.ts +75 -5
  220. package/src/i18n/zh-tw.ts +75 -5
  221. package/src/layout/desks.ts +2 -3
  222. package/src/layout/pane-layout-move.ts +264 -0
  223. package/src/layout/pane-manager/dock-tree.ts +18 -0
  224. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  225. package/src/layout/pane-manager/index.ts +2 -0
  226. package/src/market-data/coordinator/chart.ts +10 -4
  227. package/src/market-data/coordinator/entries.ts +7 -1
  228. package/src/market-data/coordinator/index.ts +6 -3
  229. package/src/market-data/coordinator/quotes.ts +2 -2
  230. package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
  231. package/src/market-data/futures-contract.ts +151 -0
  232. package/src/market-data/hooks.tsx +10 -2
  233. package/src/market-data/market/format.ts +39 -21
  234. package/src/market-data/market/freshness.ts +193 -30
  235. package/src/market-data/market/status.ts +59 -23
  236. package/src/market-data/options-alternatives.ts +69 -0
  237. package/src/market-data/published-apac-sessions.ts +23 -1
  238. package/src/market-data/quotes/contributions.ts +22 -2
  239. package/src/market-data/quotes/metadata.ts +8 -9
  240. package/src/market-data/quotes/resolution.ts +2 -0
  241. package/src/market-data/quotes/screener-live-quotes.ts +61 -9
  242. package/src/market-data/result-types.ts +2 -0
  243. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  244. package/src/plugins/builtin/account-management/model.ts +2 -56
  245. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  246. package/src/plugins/builtin/account-management/pane.tsx +37 -8
  247. package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
  248. package/src/plugins/builtin/alerts/cli.ts +313 -47
  249. package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
  250. package/src/plugins/builtin/alerts/events.ts +23 -2
  251. package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
  252. package/src/plugins/builtin/alerts/history.ts +2 -0
  253. package/src/plugins/builtin/alerts/index.tsx +41 -9
  254. package/src/plugins/builtin/alerts/pane.tsx +8 -0
  255. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  256. package/src/plugins/builtin/alerts/research-builder.ts +8 -3
  257. package/src/plugins/builtin/alerts/research-rules.ts +18 -2
  258. package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
  259. package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
  260. package/src/plugins/builtin/alerts/scheduled.ts +254 -0
  261. package/src/plugins/builtin/alerts/targets.ts +92 -0
  262. package/src/plugins/builtin/analytics/index.tsx +4 -2
  263. package/src/plugins/builtin/analytics/risk-headless.ts +33 -13
  264. package/src/plugins/builtin/analytics/risk-math.ts +91 -27
  265. package/src/plugins/builtin/analytics/risk-model.ts +71 -12
  266. package/src/plugins/builtin/analytics/risk-pane.tsx +23 -12
  267. package/src/plugins/builtin/attention/index.tsx +2 -0
  268. package/src/plugins/builtin/attention/pane.tsx +3 -1
  269. package/src/plugins/builtin/attention/series.ts +26 -0
  270. package/src/plugins/builtin/awards/index.tsx +2 -0
  271. package/src/plugins/builtin/awards/pane.tsx +3 -1
  272. package/src/plugins/builtin/awards/series.ts +29 -0
  273. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  274. package/src/plugins/builtin/bond-calculator/model.ts +2 -1
  275. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  276. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  277. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  278. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  279. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  280. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  281. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  282. package/src/plugins/builtin/catalysts/pane.tsx +3 -1
  283. package/src/plugins/builtin/cds/client.ts +70 -13
  284. package/src/plugins/builtin/cds/headless.ts +116 -19
  285. package/src/plugins/builtin/cds/index.tsx +3 -1
  286. package/src/plugins/builtin/cds/model.ts +193 -31
  287. package/src/plugins/builtin/cds/pane.tsx +56 -14
  288. package/src/plugins/builtin/cds/series.ts +19 -0
  289. package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
  290. package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
  291. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
  292. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
  293. package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
  294. package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
  295. package/src/plugins/builtin/chart-composer/headless.ts +8 -3
  296. package/src/plugins/builtin/chart-composer/index.tsx +9 -2
  297. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  298. package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
  299. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
  300. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
  301. package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
  302. package/src/plugins/builtin/chat/channels.ts +34 -36
  303. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  304. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  305. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  306. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  307. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  308. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  309. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
  310. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  311. package/src/plugins/builtin/chat/controller/index.ts +50 -0
  312. package/src/plugins/builtin/chat/conversations.ts +256 -0
  313. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  314. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  315. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  316. package/src/plugins/builtin/chat/index.tsx +40 -2
  317. package/src/plugins/builtin/chat/layout.ts +2 -0
  318. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  319. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  320. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  321. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  322. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  323. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  324. package/src/plugins/builtin/chat/pane.tsx +8 -0
  325. package/src/plugins/builtin/cloud/askg/controller.ts +223 -23
  326. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  327. package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
  328. package/src/plugins/builtin/cloud/askg/manifest.ts +33 -7
  329. package/src/plugins/builtin/cloud/askg/model.ts +156 -78
  330. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  331. package/src/plugins/builtin/cloud/askg/protocol.ts +50 -3
  332. package/src/plugins/builtin/cloud/askg/tool-display.ts +440 -0
  333. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  334. package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
  335. package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
  336. package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
  337. package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
  338. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  339. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  340. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +363 -0
  341. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  342. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  343. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  344. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  345. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  346. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  347. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +734 -0
  348. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  349. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  350. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  351. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +239 -0
  352. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +162 -0
  353. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +192 -0
  354. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  355. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  356. package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
  357. package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
  358. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  359. package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
  360. package/src/plugins/builtin/company-attention/headless.ts +19 -5
  361. package/src/plugins/builtin/company-attention/index.tsx +2 -0
  362. package/src/plugins/builtin/company-attention/model.ts +26 -7
  363. package/src/plugins/builtin/company-attention/pane.tsx +12 -9
  364. package/src/plugins/builtin/company-attention/series.ts +52 -0
  365. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  366. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  367. package/src/plugins/builtin/company-kpis/pane.tsx +19 -9
  368. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  369. package/src/plugins/builtin/composite-plugins.ts +116 -67
  370. package/src/plugins/builtin/cot/index.tsx +3 -1
  371. package/src/plugins/builtin/cot/model.ts +40 -11
  372. package/src/plugins/builtin/cot/series.ts +27 -0
  373. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  374. package/src/plugins/builtin/credit-boards/index.tsx +23 -21
  375. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  376. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  377. package/src/plugins/builtin/credit-boards/series.ts +66 -0
  378. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  379. package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
  380. package/src/plugins/builtin/credit-conditions/series.ts +26 -0
  381. package/src/plugins/builtin/credit-documents/headless.ts +31 -1
  382. package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
  383. package/src/plugins/builtin/crypto-board/headless.ts +2 -3
  384. package/src/plugins/builtin/crypto-board/index.tsx +2 -0
  385. package/src/plugins/builtin/crypto-board/model.ts +12 -0
  386. package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
  387. package/src/plugins/builtin/crypto-board/series.ts +48 -0
  388. package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
  389. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  390. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  391. package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
  392. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  393. package/src/plugins/builtin/debt-maturities/series.ts +45 -0
  394. package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
  395. package/src/plugins/builtin/dividend-yield/index.tsx +3 -1
  396. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  397. package/src/plugins/builtin/dividend-yield/series.ts +20 -0
  398. package/src/plugins/builtin/docs/blocks.tsx +181 -0
  399. package/src/plugins/builtin/docs/index.tsx +68 -0
  400. package/src/plugins/builtin/docs/pane.tsx +426 -0
  401. package/src/plugins/builtin/docs/targets.ts +72 -0
  402. package/src/plugins/builtin/docs/use-docs.ts +60 -0
  403. package/src/plugins/builtin/doe/index.tsx +2 -0
  404. package/src/plugins/builtin/doe/series.ts +62 -0
  405. package/src/plugins/builtin/earnings/client.ts +3 -2
  406. package/src/plugins/builtin/earnings/format.ts +1 -5
  407. package/src/plugins/builtin/earnings/headless.ts +58 -6
  408. package/src/plugins/builtin/earnings/history-model.ts +15 -1
  409. package/src/plugins/builtin/earnings/history-table.ts +13 -2
  410. package/src/plugins/builtin/earnings/history.tsx +7 -3
  411. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  412. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  413. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  414. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  415. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  416. package/src/plugins/builtin/econ/fred-catalog.ts +189 -0
  417. package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
  418. package/src/plugins/builtin/econ/index.tsx +70 -22
  419. package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
  420. package/src/plugins/builtin/econ-statistics/headless.ts +30 -5
  421. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  422. package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
  423. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  424. package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
  425. package/src/plugins/builtin/executives/data.ts +17 -6
  426. package/src/plugins/builtin/executives/pane.tsx +16 -7
  427. package/src/plugins/builtin/exposure/holdings.ts +1 -1
  428. package/src/plugins/builtin/exposure/pane.tsx +3 -1
  429. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  430. package/src/plugins/builtin/fear-greed/series.ts +52 -0
  431. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  432. package/src/plugins/builtin/futures/index.tsx +43 -3
  433. package/src/plugins/builtin/futures/model.ts +1 -1
  434. package/src/plugins/builtin/futures/returns.ts +1 -1
  435. package/src/plugins/builtin/futures/series.ts +13 -0
  436. package/src/plugins/builtin/futures/table.tsx +1 -1
  437. package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
  438. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  439. package/src/plugins/builtin/futures-curve/headless.ts +70 -9
  440. package/src/plugins/builtin/futures-curve/index.tsx +1 -1
  441. package/src/plugins/builtin/futures-curve/model.ts +66 -11
  442. package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
  443. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  444. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  445. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  446. package/src/plugins/builtin/gpu/board.tsx +13 -10
  447. package/src/plugins/builtin/gpu/changes.tsx +4 -4
  448. package/src/plugins/builtin/gpu/client.ts +19 -1
  449. package/src/plugins/builtin/gpu/equities.tsx +14 -10
  450. package/src/plugins/builtin/gpu/headless.ts +64 -27
  451. package/src/plugins/builtin/gpu/history.tsx +3 -3
  452. package/src/plugins/builtin/gpu/index.tsx +2 -0
  453. package/src/plugins/builtin/gpu/model.ts +114 -20
  454. package/src/plugins/builtin/gpu/pane.tsx +28 -12
  455. package/src/plugins/builtin/gpu/series.ts +38 -0
  456. package/src/plugins/builtin/help/function-card.tsx +25 -6
  457. package/src/plugins/builtin/help/index.tsx +18 -8
  458. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  459. package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
  460. package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
  461. package/src/plugins/builtin/holders/beneficial-view.tsx +62 -14
  462. package/src/plugins/builtin/holders/client.ts +22 -3
  463. package/src/plugins/builtin/holders/format.ts +18 -3
  464. package/src/plugins/builtin/holders/headless.ts +42 -134
  465. package/src/plugins/builtin/holders/pane.tsx +14 -4
  466. package/src/plugins/builtin/holders/report-header.ts +40 -5
  467. package/src/plugins/builtin/holders/report-rows.ts +240 -0
  468. package/src/plugins/builtin/insider/client.ts +35 -20
  469. package/src/plugins/builtin/insider/headless.ts +41 -11
  470. package/src/plugins/builtin/insider/index.tsx +27 -10
  471. package/src/plugins/builtin/insider/insider-data.ts +10 -0
  472. package/src/plugins/builtin/insider/issuer.ts +49 -0
  473. package/src/plugins/builtin/insider/model.ts +3 -0
  474. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  475. package/src/plugins/builtin/iv-history/client.ts +26 -6
  476. package/src/plugins/builtin/iv-history/format.ts +33 -2
  477. package/src/plugins/builtin/iv-history/headless.ts +27 -12
  478. package/src/plugins/builtin/iv-history/index.tsx +5 -3
  479. package/src/plugins/builtin/iv-history/model.ts +172 -20
  480. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  481. package/src/plugins/builtin/iv-history/screen-pane.tsx +45 -25
  482. package/src/plugins/builtin/iv-history/series.ts +29 -0
  483. package/src/plugins/builtin/jobs/headless.ts +28 -3
  484. package/src/plugins/builtin/jobs/index.tsx +3 -1
  485. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  486. package/src/plugins/builtin/jobs/series.ts +19 -0
  487. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  488. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  489. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  490. package/src/plugins/builtin/kelly-sizer/pane.tsx +111 -40
  491. package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
  492. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  493. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  494. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  495. package/src/plugins/builtin/macro-day/client.ts +68 -3
  496. package/src/plugins/builtin/macro-day/headless.ts +18 -12
  497. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  498. package/src/plugins/builtin/macro-day/model.ts +66 -15
  499. package/src/plugins/builtin/macro-day/pane.tsx +34 -20
  500. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  501. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  502. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  503. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  504. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  505. package/src/plugins/builtin/market-heatmap/model.ts +102 -3
  506. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  507. package/src/plugins/builtin/market-movers/client.ts +7 -5
  508. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  509. package/src/plugins/builtin/market-movers/index.tsx +11 -3
  510. package/src/plugins/builtin/market-movers/model.ts +101 -17
  511. package/src/plugins/builtin/market-movers/screener.ts +31 -0
  512. package/src/plugins/builtin/market-movers/table.ts +50 -23
  513. package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
  514. package/src/plugins/builtin/market-valuation/index.tsx +3 -3
  515. package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
  516. package/src/plugins/builtin/members/headless.ts +7 -2
  517. package/src/plugins/builtin/members/model.ts +31 -3
  518. package/src/plugins/builtin/members/pane.tsx +3 -6
  519. package/src/plugins/builtin/mna/headless.ts +15 -3
  520. package/src/plugins/builtin/money-markets/index.tsx +2 -0
  521. package/src/plugins/builtin/money-markets/series.ts +41 -0
  522. package/src/plugins/builtin/news/headless.ts +9 -2
  523. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  524. package/src/plugins/builtin/notes/cli.ts +12 -7
  525. package/src/plugins/builtin/openfda/client.ts +1 -1
  526. package/src/plugins/builtin/options/carry.ts +95 -0
  527. package/src/plugins/builtin/options/index.tsx +58 -5
  528. package/src/plugins/builtin/options/strike-window.ts +183 -0
  529. package/src/plugins/builtin/options/table.ts +55 -3
  530. package/src/plugins/builtin/options/types.ts +8 -1
  531. package/src/plugins/builtin/options/view.tsx +162 -13
  532. package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
  533. package/src/plugins/builtin/options-positioning/client.ts +7 -3
  534. package/src/plugins/builtin/options-positioning/headless.ts +5 -2
  535. package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
  536. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  537. package/src/plugins/builtin/options-scenario/client.ts +75 -18
  538. package/src/plugins/builtin/options-scenario/headless.ts +72 -24
  539. package/src/plugins/builtin/options-scenario/hedge.ts +285 -0
  540. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  541. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  542. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  543. package/src/plugins/builtin/pe-band/model.ts +2 -2
  544. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  545. package/src/plugins/builtin/perps/client.ts +40 -6
  546. package/src/plugins/builtin/perps/equity-row.tsx +33 -8
  547. package/src/plugins/builtin/perps/freshness.ts +66 -0
  548. package/src/plugins/builtin/perps/headless.ts +45 -30
  549. package/src/plugins/builtin/perps/index.tsx +18 -1
  550. package/src/plugins/builtin/perps/model.ts +32 -6
  551. package/src/plugins/builtin/perps/pane.tsx +76 -20
  552. package/src/plugins/builtin/perps/series.ts +76 -0
  553. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  554. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  555. package/src/plugins/builtin/plugin-marketplace/pane.tsx +357 -39
  556. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  557. package/src/plugins/builtin/plugin-module.ts +90 -4
  558. package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
  559. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
  560. package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
  561. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +400 -99
  562. package/src/plugins/builtin/portfolio-list/cli/render.ts +281 -43
  563. package/src/plugins/builtin/portfolio-list/cli/shared.ts +26 -16
  564. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
  565. package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
  566. package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
  567. package/src/plugins/builtin/portfolio-list/headless.ts +70 -9
  568. package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
  569. package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
  570. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
  571. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
  572. package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
  573. package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
  574. package/src/plugins/builtin/power/client.ts +16 -0
  575. package/src/plugins/builtin/power/evidence.ts +6 -2
  576. package/src/plugins/builtin/power/headless.ts +71 -7
  577. package/src/plugins/builtin/power/index.tsx +4 -1
  578. package/src/plugins/builtin/power/model.ts +72 -1
  579. package/src/plugins/builtin/power/pane.tsx +32 -9
  580. package/src/plugins/builtin/rate-path/headless.ts +11 -3
  581. package/src/plugins/builtin/realized-vol/headless.ts +8 -0
  582. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  583. package/src/plugins/builtin/research/corporate-actions-pane.tsx +7 -4
  584. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  585. package/src/plugins/builtin/research/event-model.ts +68 -24
  586. package/src/plugins/builtin/research/events-headless.ts +2 -1
  587. package/src/plugins/builtin/research/headless.ts +2 -1
  588. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  589. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  590. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  591. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  592. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  593. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  594. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  595. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  596. package/src/plugins/builtin/seasonality/client.ts +15 -7
  597. package/src/plugins/builtin/seasonality/headless.ts +36 -12
  598. package/src/plugins/builtin/seasonality/index.tsx +2 -2
  599. package/src/plugins/builtin/seasonality/model.ts +119 -0
  600. package/src/plugins/builtin/seasonality/pane.tsx +108 -37
  601. package/src/plugins/builtin/sec/index.tsx +5 -5
  602. package/src/plugins/builtin/sectors/client.ts +10 -2
  603. package/src/plugins/builtin/sectors/headless.ts +35 -6
  604. package/src/plugins/builtin/sectors/index.tsx +18 -1
  605. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  606. package/src/plugins/builtin/series-inventory.ts +91 -0
  607. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
  608. package/src/plugins/builtin/shared/command-tab.ts +69 -0
  609. package/src/plugins/builtin/shared/earnings-report.ts +55 -0
  610. package/src/plugins/builtin/shared/fred-points.ts +20 -0
  611. package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
  612. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  613. package/src/plugins/builtin/shared/members-menu.tsx +4 -4
  614. package/src/plugins/builtin/shared/open-description.ts +29 -0
  615. package/src/plugins/builtin/shared/report-freshness.ts +59 -2
  616. package/src/plugins/builtin/shared/series-warning.ts +5 -0
  617. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  618. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  619. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  620. package/src/plugins/builtin/short-interest/headless.ts +1 -0
  621. package/src/plugins/builtin/short-interest/index.tsx +21 -4
  622. package/src/plugins/builtin/short-interest/series.ts +36 -0
  623. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  624. package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
  625. package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
  626. package/src/plugins/builtin/short-volume/headless.ts +1 -0
  627. package/src/plugins/builtin/short-volume/index.tsx +3 -10
  628. package/src/plugins/builtin/short-volume/series.ts +23 -0
  629. package/src/plugins/builtin/social-mentions/index.tsx +2 -0
  630. package/src/plugins/builtin/social-mentions/series.ts +23 -0
  631. package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
  632. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  633. package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
  634. package/src/plugins/builtin/thirteenf/api.ts +1 -0
  635. package/src/plugins/builtin/thirteenf/data.ts +6 -4
  636. package/src/plugins/builtin/thirteenf/headless.ts +217 -36
  637. package/src/plugins/builtin/thirteenf/model.ts +39 -1
  638. package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
  639. package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
  640. package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
  641. package/src/plugins/builtin/thirteenf/types.ts +2 -0
  642. package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
  643. package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
  644. package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
  645. package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
  646. package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
  647. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
  648. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
  649. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  650. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  651. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
  652. package/src/plugins/builtin/time-sales/headless.ts +1 -0
  653. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  654. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  655. package/src/plugins/builtin/treasury-auctions/headless.ts +56 -6
  656. package/src/plugins/builtin/treasury-auctions/index.tsx +12 -9
  657. package/src/plugins/builtin/treasury-auctions/model.ts +205 -30
  658. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  659. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  660. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  661. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  662. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  663. package/src/plugins/builtin/vol-surface/headless.ts +125 -20
  664. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  665. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  666. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  667. package/src/plugins/builtin/volatility/index.tsx +2 -0
  668. package/src/plugins/builtin/volatility/series.ts +84 -0
  669. package/src/plugins/builtin/world-indices/client.ts +22 -4
  670. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  671. package/src/plugins/builtin/world-indices/model.ts +4 -2
  672. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  673. package/src/plugins/builtin/world-venue-map/geo-series.ts +27 -2
  674. package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
  675. package/src/plugins/builtin/world-venue-map/index.ts +3 -2
  676. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
  677. package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
  678. package/src/plugins/builtin/yield-curve/chart.ts +2 -2
  679. package/src/plugins/builtin/yield-curve/curves.ts +21 -8
  680. package/src/plugins/builtin/yield-curve/headless.ts +71 -18
  681. package/src/plugins/builtin/yield-curve/index.tsx +4 -2
  682. package/src/plugins/builtin/yield-curve/series.ts +86 -0
  683. package/src/plugins/builtin-editorial.ts +126 -0
  684. package/src/plugins/catalog-backend.ts +16 -9
  685. package/src/plugins/catalog-browser.ts +129 -64
  686. package/src/plugins/catalog-manifest.ts +73 -40
  687. package/src/plugins/catalog-ui.ts +26 -6
  688. package/src/plugins/extracted-plugins.ts +7 -1
  689. package/src/plugins/ownership.ts +150 -7
  690. package/src/plugins/plugin-functions.ts +54 -0
  691. package/src/plugins/plugin-off.ts +36 -0
  692. package/src/plugins/registry/host-actions.ts +16 -0
  693. package/src/plugins/registry/index.ts +173 -28
  694. package/src/plugins/registry/plugin-state.ts +25 -1
  695. package/src/plugins/runtime/context.tsx +29 -0
  696. package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
  697. package/src/portfolio/position-value.ts +40 -0
  698. package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
  699. package/src/public/time-series.ts +17 -0
  700. package/src/react/auto-refresh.ts +13 -3
  701. package/src/react/input.ts +48 -1
  702. package/src/remote/app-host.tsx +25 -4
  703. package/src/remote/confirm-change.tsx +85 -0
  704. package/src/remote/controller-utils.ts +8 -1
  705. package/src/remote/controller.ts +126 -59
  706. package/src/remote/matching.ts +69 -0
  707. package/src/remote/pane-screenshot.ts +47 -0
  708. package/src/remote/schema.ts +63 -1
  709. package/src/remote/semantic-tree.tsx +14 -0
  710. package/src/remote/types.ts +32 -0
  711. package/src/remote/watchlist.ts +306 -0
  712. package/src/renderers/browser/main.tsx +3 -2
  713. package/src/renderers/cloudflare/worker.ts +1 -1
  714. package/src/renderers/dom/data-table/frozen-column.ts +3 -1
  715. package/src/renderers/dom/data-table/index.tsx +33 -6
  716. package/src/renderers/dom/data-table/row.tsx +27 -1
  717. package/src/renderers/dom/desktop/control-styles.ts +63 -1
  718. package/src/renderers/dom/desktop/controls.tsx +30 -23
  719. package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
  720. package/src/renderers/dom/desktop/icons.tsx +21 -0
  721. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  722. package/src/renderers/dom/dom-ui-host.tsx +3 -0
  723. package/src/renderers/dom/host/box.tsx +4 -1
  724. package/src/renderers/dom/host/chart-surface.tsx +6 -2
  725. package/src/renderers/dom/host/drag-shield.ts +17 -6
  726. package/src/renderers/dom/host/live-frame.ts +14 -2
  727. package/src/renderers/dom/host/overflow-fade.ts +14 -1
  728. package/src/renderers/dom/host/size-observer.ts +26 -0
  729. package/src/renderers/dom/host/surface-3d.tsx +7 -2
  730. package/src/renderers/dom/host/text.tsx +1 -1
  731. package/src/renderers/dom/input-host.tsx +6 -1
  732. package/src/renderers/dom/styles.css +74 -0
  733. package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
  734. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  735. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  736. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  737. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +18 -3
  738. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +109 -5
  739. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
  740. package/src/renderers/electrobun/view/main.tsx +6 -1
  741. package/src/renderers/opentui/input-host.tsx +6 -1
  742. package/src/renderers/opentui/start.tsx +9 -0
  743. package/src/series/capability.ts +75 -0
  744. package/src/series/csv.ts +160 -0
  745. package/src/series/files.ts +107 -0
  746. package/src/series/registry.ts +198 -0
  747. package/src/series/resolve.ts +107 -0
  748. package/src/series/standalone.ts +48 -0
  749. package/src/series/types.ts +145 -0
  750. package/src/series/validate.ts +171 -0
  751. package/src/shares/portable-layout.ts +16 -6
  752. package/src/sources/gloomberb-cloud/index.ts +26 -17
  753. package/src/sources/gloomberb-cloud/normalizers.ts +5 -1
  754. package/src/sources/history-result.ts +1 -0
  755. package/src/sources/listing-symbols.ts +14 -5
  756. package/src/sources/provider-errors.ts +88 -3
  757. package/src/sources/provider-router/batches.ts +4 -1
  758. package/src/sources/provider-router/cache.ts +4 -2
  759. package/src/sources/provider-router/cached-routes.ts +9 -5
  760. package/src/sources/provider-router/financial-routes.ts +12 -5
  761. package/src/sources/provider-router/financials.ts +55 -15
  762. package/src/sources/provider-router/history.ts +68 -34
  763. package/src/sources/provider-router/index.ts +1 -1
  764. package/src/sources/provider-router/primary.ts +23 -5
  765. package/src/sources/provider-router/search.ts +1 -0
  766. package/src/sources/sec-edgar.ts +68 -8
  767. package/src/sync/core-contributors.ts +5 -3
  768. package/src/test-support/cli-context.ts +2 -1
  769. package/src/test-support/data-provider.ts +34 -0
  770. package/src/test-support/shell-pane-keys.tsx +1 -0
  771. package/src/theme/series-colors.ts +46 -0
  772. package/src/theme/themes.ts +149 -0
  773. package/src/tickers/company-name.ts +75 -0
  774. package/src/tickers/instrument-kind.ts +25 -0
  775. package/src/tickers/open-target.ts +3 -2
  776. package/src/tickers/search/index.ts +67 -9
  777. package/src/tickers/search/ranking.ts +78 -18
  778. package/src/tickers/search/types.ts +10 -2
  779. package/src/tickers/starter-symbols.ts +8 -0
  780. package/src/tickers/us-listing.ts +34 -0
  781. package/src/time-series/hooks.ts +9 -0
  782. package/src/time-series/resolve.ts +96 -30
  783. package/src/time-series/types.ts +4 -2
  784. package/src/time-series/use-chart-resolution.ts +32 -0
  785. package/src/types/config.ts +67 -1
  786. package/src/types/data-provider.ts +5 -0
  787. package/src/types/financials.ts +52 -1
  788. package/src/types/headless.ts +72 -0
  789. package/src/types/instrument.ts +8 -0
  790. package/src/types/plugin.ts +68 -3
  791. package/src/types/price-history.ts +7 -0
  792. package/src/types/ticker.ts +14 -0
  793. package/src/ui/host.tsx +26 -4
  794. package/src/ui/index.tsx +2 -0
  795. package/src/ui/live-resize.ts +81 -0
  796. package/src/ui/live-stretch.ts +121 -0
  797. package/src/ui/measured-cells.ts +58 -0
  798. package/src/ui/pane-fill.ts +11 -0
  799. package/src/utils/basis-points.ts +17 -0
  800. package/src/utils/cli-output.ts +21 -2
  801. package/src/utils/concurrency.ts +25 -0
  802. package/src/utils/crypto-pair.ts +20 -0
  803. package/src/utils/currency-pair.ts +19 -0
  804. package/src/utils/decimal-comma.ts +19 -0
  805. package/src/utils/dom-screenshot-css.ts +265 -0
  806. package/src/utils/dom-screenshot-rasters.ts +276 -0
  807. package/src/utils/dom-screenshot.ts +437 -52
  808. package/src/utils/exchange-rate-snapshot.ts +6 -2
  809. package/src/utils/exchanges.ts +120 -1
  810. package/src/utils/format.ts +17 -3
  811. package/src/utils/fundamentals.ts +66 -0
  812. package/src/utils/futures-generic.ts +6 -12
  813. package/src/utils/fx-market-hours.ts +44 -0
  814. package/src/utils/option-expiry.ts +126 -0
  815. package/src/utils/price-history.ts +130 -40
  816. package/src/utils/rpc-timeout-error.ts +6 -2
  817. package/src/utils/sec-eps-basis.ts +178 -36
  818. package/src/utils/sec.ts +98 -40
  819. package/src/utils/throttled-fetch.ts +51 -22
  820. package/src/utils/utc-time.ts +51 -4
@@ -6,9 +6,12 @@ import { isPlainKey } from "../../../utils/keyboard";
6
6
  import { formatCompact, formatPercentRaw } from "../../../utils/format";
7
7
  import { formatMarketPrice, formatSignedMarketPrice, liveQuoteFormatOptions } from "../../../market-data/market/format";
8
8
  import { formatExpDate, resolveOptionsTarget } from "../../../utils/options";
9
+ import { expiryIsoDate, findListedExpiry, formatDaysToExpiry, daysToExpiry, missingExpiryText, readOptionExpiration } from "../../../utils/option-expiry";
9
10
  import { canonicalTickerKey } from "../../../utils/exchanges";
11
+ import { isOptionsUnavailableError, listedOptionsAlternative, optionsUnavailableTitle } from "../../../market-data/options-alternatives";
10
12
  import { useChartQueries, useOptionsQuery, useResolvedEntryValue, useTickerFinancials } from "../../../market-data/hooks";
11
13
  import {
14
+ Button,
12
15
  DataTableView,
13
16
  EmptyState,
14
17
  usePaneFooter,
@@ -57,7 +60,7 @@ import {
57
60
  import { useOptionsAccessFooter } from "./footer";
58
61
  import { AnalyticsAsOfDialog, analyticsAsOfRows } from "./analytics-as-of";
59
62
  import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
60
- import { signedPositionDirection } from "../portfolio-list/position-metrics";
63
+ import { signedPositionDirection } from "../../../portfolio/position-metrics";
61
64
  import { optionMarketReference } from "./market-reference";
62
65
  import { useOptionsEnrichment } from "./enrichment";
63
66
  import type { OptionsEnrichmentSnapshot } from "./enrichment-model";
@@ -67,6 +70,19 @@ import type { TickerFinancials } from "../../../types/financials";
67
70
  import type { IvStats } from "../iv-history/client";
68
71
  import { useIvRank } from "../iv-history/rank";
69
72
  import { useOptionsSessionOpen, useThrottledValue } from "../shared/volatility/live-session";
73
+ import { useRemoteUiNode } from "../../../remote/semantic-tree";
74
+ import { optionCarry, optionCostOfSpot } from "./carry";
75
+ import {
76
+ ALL_STRIKES,
77
+ STRIKE_WINDOW_PRESETS,
78
+ STRIKE_WINDOW_SETTING,
79
+ applyStrikeWindow,
80
+ chainDeltasByStrike,
81
+ resolveStrikeWindow,
82
+ strikeWindowNotice,
83
+ strikeWindowShortLabel,
84
+ strikeWindowValue,
85
+ } from "./strike-window";
70
86
 
71
87
  /** The summary strip and analytics recompute from live quotes at most this often. */
72
88
  const OPTIONS_SUMMARY_THROTTLE_MS = 1_000;
@@ -76,6 +92,10 @@ const EXPIRY_NEXT_KEY = "]";
76
92
  /** Switches the cursor row between its call and its put, as clicking either side does. */
77
93
  const SIDE_KEY = "x";
78
94
 
95
+ function sentenceCase(text: string): string {
96
+ return text.charAt(0).toUpperCase() + text.slice(1);
97
+ }
98
+
79
99
  function formatRatio(value: number | null | undefined): string {
80
100
  return value == null || !Number.isFinite(value) ? "--" : value.toFixed(2);
81
101
  }
@@ -152,7 +172,8 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
152
172
  [savedTicker, boundSymbol, fallbackExchange, fallbackCurrency]);
153
173
  const { createPaneFromTemplate } = usePluginAppActions();
154
174
  const liveStreaming = useLiveStreamingSetting();
155
- const [seededExpiration] = usePaneSettingValue<number | undefined>("expiration", undefined);
175
+ // A number from the app, or the date or seconds `fn OMON --expiration` took as text.
176
+ const [seededSetting] = usePaneSettingValue<unknown>("expiration", undefined);
156
177
  const [expirationTargetKey] = usePaneSettingValue<string | null>("expirationTargetKey", null);
157
178
  const updatePaneSettings = useUpdatePaneSettings();
158
179
  const [calcSide, setCalcSide] = useState<OptionSide | null>(null);
@@ -207,11 +228,18 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
207
228
  const [chainRefreshMinutes] = usePaneSettingValue<string>("chainRefreshMinutes", "");
208
229
  const [storedOptionFieldIds] = usePaneSettingValue<OptionFieldId[]>("optionColumnIds", DEFAULT_OPTION_FIELD_IDS);
209
230
  const optionFieldIds = useMemo(() => resolveOptionFieldIds(storedOptionFieldIds), [storedOptionFieldIds]);
231
+ const [storedStrikeWindow] = usePaneSettingValue<unknown>(STRIKE_WINDOW_SETTING, ALL_STRIKES);
232
+ const strikeWindow = useMemo(() => resolveStrikeWindow(storedStrikeWindow), [storedStrikeWindow]);
233
+ const strikeWindowText = strikeWindowValue(strikeWindow);
210
234
  const initialChainEntry = useOptionsQuery(baseRequest);
211
235
  const initialChain = useResolvedEntryValue(initialChainEntry);
212
236
  const initialExpiration = initialChain?.expirationDates.reduce((best, expiration) => (
213
237
  parsed && Math.abs(expiration - parsed.expTs) < Math.abs(best - parsed.expTs) ? expiration : best
214
238
  ), initialChain.expirationDates[0]!);
239
+ // A typed date names a day; the catalogue's stamp for that day is the expiry.
240
+ const seededDate = readOptionExpiration(seededSetting);
241
+ const seededExpiration = seededDate == null ? undefined
242
+ : findListedExpiry(seededDate, initialChain?.expirationDates ?? []) ?? seededDate;
215
243
  const selectedExpiration = expirationTargetKey == null || expirationTargetKey === selectionTargetKey
216
244
  ? seededExpiration ?? initialExpiration : initialExpiration;
217
245
  const viewportKey = `${effectiveTicker}:${selectedExpiration ?? "initial"}`;
@@ -257,7 +285,24 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
257
285
  const expirationPickRef = useRef<(value: string) => void>(() => {});
258
286
  const { nativePaneChrome } = useUiCapabilities();
259
287
  const selectedExpirationIndex = selectedExpiration == null ? -1 : expirationDates.indexOf(selectedExpiration);
260
- const expirationFilters = useMemo<QueryBarFilter[]>(() => [{
288
+ const strikeWindowOptions = useMemo(() => {
289
+ const presets = STRIKE_WINDOW_PRESETS.map(({ value, label, short, description }) => ({ value, label, short, description }));
290
+ // A window typed on the command line that no preset matches still reads on the chip.
291
+ if (presets.some((preset) => preset.value === strikeWindowText)) return presets;
292
+ const label = strikeWindowShortLabel(resolveStrikeWindow(strikeWindowText));
293
+ return [...presets, { value: strikeWindowText, label, short: label, description: "Set from the command line." }];
294
+ }, [strikeWindowText]);
295
+ // The window chip leads: the expiry strip after it can run past the edge of the bar.
296
+ const queryFilters = useMemo<QueryBarFilter[]>(() => [{
297
+ id: "strikes",
298
+ label: "Strikes",
299
+ title: "Strikes",
300
+ value: strikeWindowText,
301
+ // Every strike is the default, so any window narrows the chain and the chip says so.
302
+ defaultValue: ALL_STRIKES,
303
+ options: strikeWindowOptions,
304
+ onChange: (value: string) => updatePaneSettings({ [STRIKE_WINDOW_SETTING]: value }),
305
+ }, {
261
306
  id: "expiration",
262
307
  label: "Exp",
263
308
  inline: true,
@@ -272,7 +317,7 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
272
317
  : index === selectedExpirationIndex + 1 ? EXPIRY_NEXT_KEY : undefined,
273
318
  })),
274
319
  onChange: (value: string) => expirationPickRef.current(value),
275
- }], [expirationDates, selectedExpiration, selectedExpirationIndex]);
320
+ }], [expirationDates, selectedExpiration, selectedExpirationIndex, strikeWindowOptions, strikeWindowText, updatePaneSettings]);
276
321
  // A scheduled refresh of a chain already on screen is quiet: the in-session
277
322
  // cadence would otherwise blink the footer every few seconds.
278
323
  const loading = (initialChainEntry?.phase === "loading" || initialChainEntry?.phase === "refreshing") && !chain
@@ -280,7 +325,10 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
280
325
  || (expirationChainEntry?.phase === "refreshing" && strikeChain === null);
281
326
  // Refresh failures keep a ready entry with last-good data and an error.
282
327
  // Surface that warning even when the cached chain is still usable.
283
- const error = (expirationUnavailable ? "Selected expiration unavailable." : null)
328
+ // The body names the dates nearest the one asked for; the footer only flags it.
329
+ const unavailableExpiry = expirationUnavailable && selectedExpiration != null
330
+ ? sentenceCase(missingExpiryText(selectedExpiration, availableExpirations)) : null;
331
+ const error = (expirationUnavailable ? `Expiry ${expiryIsoDate(selectedExpiration!)} unavailable` : null)
284
332
  ?? initialChainEntry?.error?.message ?? expirationChainEntry?.error?.message
285
333
  ?? (initialChainEntry?.phase === "error" || expirationChainEntry?.phase === "error"
286
334
  ? "Failed to load options" : null);
@@ -316,9 +364,76 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
316
364
 
317
365
  useEffect(() => {
318
366
  userSelectedStrikeRef.current = false;
319
- }, [selectedExpiration]);
367
+ }, [selectedExpiration, strikeWindowText]);
320
368
 
321
- const strikes = useMemo(() => strikeChain ? buildStrikeList(strikeChain) : [], [strikeChain]);
369
+ const allStrikes = useMemo(() => strikeChain ? buildStrikeList(strikeChain) : [], [strikeChain]);
370
+ // The window is read once per chain snapshot, and again when a price first
371
+ // arrives, so streamed quotes never move strikes in or out under the cursor.
372
+ const underlyingPrice = underlying?.quote?.price;
373
+ const windowCenter = useMemo(
374
+ () => resolveDefaultStrikeTarget(parsed?.strike, underlyingPrice),
375
+ [strikeChain, parsed?.strike, underlyingPrice == null],
376
+ );
377
+ const windowSpot = useMemo(() => spot, [strikeChain, spot == null]);
378
+ const windowDeltas = useMemo(
379
+ () => strikeWindow.kind === "delta" && strikeChain ? chainDeltasByStrike(strikeChain, windowSpot, dividendYield) : null,
380
+ [dividendYield, strikeChain, strikeWindow.kind, windowSpot],
381
+ );
382
+ const strikeWindowResult = useMemo(
383
+ () => applyStrikeWindow(allStrikes, strikeWindow, { center: windowCenter, deltas: windowDeltas, keep: parsed?.strike ?? null }),
384
+ [allStrikes, parsed?.strike, strikeWindow, windowCenter, windowDeltas],
385
+ );
386
+ const strikes = strikeWindowResult.strikes;
387
+ usePaneNoticeFooter({ registrationId: "options-strike-window", focused,
388
+ notices: strikeWindowResult.fallback === "no-delta"
389
+ ? ["The delta window needs a current underlying quote; showing every strike."]
390
+ : strikeWindowResult.fallback === "no-price" ? ["No price to centre the strike window on; showing every strike."] : [] });
391
+ // What the chain shows, in every renderer and in its reports: the expiry
392
+ // and its days left from the chain's as-of, beside the chain's own feed.
393
+ // Published ahead of the strike window, so a report names the expiry first.
394
+ const asOfChain = strikeChain ?? chain;
395
+ const chainAsOfMs = asOfChain?.asOf ? Date.parse(asOfChain.asOf) : Number.NaN;
396
+ const expiryAsOfMs = Number.isFinite(chainAsOfMs) ? chainAsOfMs : Date.now();
397
+ const expiryDate = selectedExpiration == null ? "" : expiryIsoDate(selectedExpiration);
398
+ const expiryDays = selectedExpiration == null ? "" : formatDaysToExpiry(selectedExpiration, expiryAsOfMs);
399
+ useRemoteUiNode(expiryDate && chain ? {
400
+ role: "report-notice",
401
+ label: "Expiry",
402
+ getMetadata: () => ({
403
+ text: `Expiry ${expiryDate} (${expiryDays})`,
404
+ key: "expiry",
405
+ value: { date: expiryDate, daysToExpiry: daysToExpiry(selectedExpiration!, expiryAsOfMs) },
406
+ }),
407
+ } : null);
408
+ // A refresh that failed keeps the last chain, which is then stale.
409
+ const asOfEntry = strikeChain == null ? null : strikeChain === expirationChain ? expirationChainEntry : initialChainEntry;
410
+ const chainDelay = asOfChain?.delayMinutes ?? 0;
411
+ const chainStatus = asOfEntry?.error != null ? "stale" as const
412
+ : asOfChain?.dataSource ?? (chainDelay > 0 ? "delayed" as const : undefined);
413
+ useRemoteUiNode(asOfChain ? {
414
+ role: "report-freshness",
415
+ label: "Chain freshness",
416
+ getMetadata: () => ({
417
+ ...(asOfChain.asOf ? { asOf: asOfChain.asOf } : {}),
418
+ ...(chainStatus ? { status: chainStatus } : {}),
419
+ ...(chainDelay > 0 ? { delayMinutes: chainDelay } : {}),
420
+ }),
421
+ } : null);
422
+ // A report of the pane (`gloomberb fn OMON`) says how many strikes it lists of how many the expiry has.
423
+ useRemoteUiNode(strikeChain ? {
424
+ role: "report-notice",
425
+ label: "Strike window",
426
+ getMetadata: () => ({
427
+ text: strikeWindowNotice(strikeWindow, strikeWindowResult),
428
+ key: "strikes",
429
+ value: {
430
+ window: strikeWindowText,
431
+ shown: strikeWindowResult.strikes.length,
432
+ total: strikeWindowResult.total,
433
+ ...(strikeWindowResult.fallback ? { fallback: strikeWindowResult.fallback } : {}),
434
+ },
435
+ }),
436
+ } : null);
322
437
  const selectedStrikeIdx = selectedContract ? strikes.indexOf(selectedContract.strike) : strikeIdx;
323
438
  // The snapshot's contracts decide which symbols stream; they do not change
324
439
  // with the stream itself, so the subscription is stable between refreshes.
@@ -395,6 +510,9 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
395
510
  impliedVolatility: volatilities?.byStrike.get(strike),
396
511
  callGreeks: volatilities ? calculateOptionGreeks(call, "call", spot, dividendYield, volatilities) : undefined,
397
512
  putGreeks: volatilities ? calculateOptionGreeks(put, "put", spot, dividendYield, volatilities) : undefined,
513
+ callExtrinsicPerYear: call && volatilities ? optionCarry(call, "call", spot, volatilities.valuationTime)?.extrinsicPerYear ?? null : null,
514
+ putExtrinsicPerYear: put && volatilities ? optionCarry(put, "put", spot, volatilities.valuationTime)?.extrinsicPerYear ?? null : null,
515
+ putCostOfSpot: put ? optionCostOfSpot(put, spot) : null,
398
516
  isPositionStrike: !!parsed && strike === parsed.strike,
399
517
  };
400
518
  }), [callsByStrike, dividendYield, parsed, putsByStrike, spot, strikes, volatilities]);
@@ -643,9 +761,23 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
643
761
  }, { enabled: focused, phase: "before" });
644
762
 
645
763
  if (!ticker) {
646
- return <EmptyState title="No ticker selected." message="Select a ticker to view options." />;
764
+ return <EmptyState title="Select a ticker." />;
647
765
  }
648
766
  if (loading && !chain) return <Spinner label="Loading options chain..." />;
767
+ if (error && !chain && isOptionsUnavailableError(error)) {
768
+ // A future or commodity with no chain of its own: the listed fund whose options stand in, and OSA on it.
769
+ const alternative = listedOptionsAlternative(effectiveTicker);
770
+ return (
771
+ <EmptyState
772
+ title={optionsUnavailableTitle(effectiveTicker)}
773
+ hint={alternative ? `Nearest listed alternative: ${alternative.symbol} (${alternative.name}).` : "OSA builds strategy payoffs on a listed underlying."}
774
+ actions={alternative ? <>
775
+ <Button label={`${alternative.symbol} options`} onPress={() => createPaneFromTemplate("options-pane", { symbol: alternative.symbol })} />
776
+ <Button label={`OSA ${alternative.symbol}`} onPress={() => createPaneFromTemplate("options-scenario-pane", { symbol: alternative.symbol })} />
777
+ </> : undefined}
778
+ />
779
+ );
780
+ }
649
781
  if (error && !chain) return <EmptyState title="Options chain unavailable." message={error} />;
650
782
  if (!chain || expirationDates.length === 0) {
651
783
  return <EmptyState title={`No options available for ${effectiveTicker}.`} />;
@@ -656,7 +788,7 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
656
788
  : 0;
657
789
  // The root insets the terminal body, so the band gets the width inside it.
658
790
  const statWidth = Math.max(1, width - (nativePaneChrome ? 0 : 2));
659
- const statItems = optionsSummaryItems({
791
+ const figures = optionsSummaryItems({
660
792
  spot: spotItem(underlying, isOpt ? undefined : ticker.metadata.assetCategory),
661
793
  summary, enrichment, currency: underlying?.quote?.currency || ticker.metadata.currency || "",
662
794
  ivRank: showIvRank ? { stats: ivRank } : null,
@@ -665,8 +797,23 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
665
797
  // Slope or IVR detail is the widest cell and would otherwise halve the
666
798
  // columns and push the chain down; the detail is cut short instead.
667
799
  const statRowBudget = width >= 110 ? 3 : width >= 65 ? 4 : 6;
668
- const statColumns = Math.max(statGridColumns(statItems, statWidth), Math.ceil(statItems.length / statRowBudget));
800
+ const figureColumns = Math.max(statGridColumns(figures, statWidth), Math.ceil(figures.length / statRowBudget));
801
+ // The expiry leads the band, since the strip above can scroll it out of
802
+ // view. It joins the figures as one more cell where the row budget leaves a
803
+ // cell wide enough for all of it, and otherwise takes a row of its own so no
804
+ // figure is squeezed. Sized for four-digit days, so the band does not
805
+ // reflow as the chain's as-of arrives or the expiry steps.
806
+ const expiryItem: StatItem = { id: "expiry", label: "Expiry", value: expiryDate, detail: `(${expiryDays})` };
807
+ const cellColumns = Math.max(figureColumns, Math.ceil((figures.length + 1) / statRowBudget));
808
+ const expiryCells = Math.max(...figures.map((item) => item.label.length), expiryItem.label.length) + 1
809
+ + expiryDate.length + 2 + "(9999d)".length;
810
+ const expiryInline = Math.floor((statWidth - 2 * (cellColumns - 1)) / cellColumns) >= expiryCells;
811
+ const statColumns = expiryInline ? cellColumns : figureColumns;
812
+ const statItems = [expiryInline ? expiryItem : { ...expiryItem, wide: true }, ...figures];
669
813
  const summaryRowCount = Math.min(statGridRows(statItems, statWidth, statColumns), Math.max(0, height - 6));
814
+ // A short band gives up figures from the end; the expiry stays.
815
+ const shownStatItems = summaryRowCount === 0 ? []
816
+ : [statItems[0]!, ...figures.slice(0, summaryRowCount * statColumns - (expiryInline ? 1 : statColumns))];
670
817
  // No term here follows the selection or the load, so an empty cold-expiry
671
818
  // response cannot resize the table: growing it during loading would turn a
672
819
  // clamped scroll into apparent user navigation.
@@ -677,10 +824,10 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
677
824
 
678
825
  return (
679
826
  <Box flexDirection="column" flexGrow={1} paddingX={nativePaneChrome ? 0 : 1}>
680
- <QueryBar width={Math.max(1, width - 2)} filters={expirationFilters} />
827
+ <QueryBar width={Math.max(1, width - 2)} filters={queryFilters} />
681
828
 
682
829
  {summaryRowCount > 0 && (
683
- <StatGrid items={statItems.slice(0, summaryRowCount * statColumns)} width={statWidth} columns={statColumns} />
830
+ <StatGrid items={shownStatItems} width={statWidth} columns={statColumns} />
684
831
  )}
685
832
 
686
833
  {isOpt && parsed && (
@@ -721,7 +868,9 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
721
868
  getItemKey={(row) => String(row.strike)}
722
869
  renderCell={renderCell}
723
870
  selectedTextOverridesCellColor
724
- emptyStateTitle={error && !strikeChain ? "Selected expiration unavailable." : strikesLoading ? "Loading strikes..." : "No strikes available."}
871
+ emptyStateTitle={unavailableExpiry ?? (error && !strikeChain ? "Selected expiration unavailable." : strikesLoading ? "Loading strikes..."
872
+ : strikeWindowResult.total > 0 ? "No strikes in this window." : "No strikes available.")}
873
+ reportEveryRow
725
874
  rootWidth={Math.max(1, width - 2 + inset * 2)}
726
875
  rootHeight={tableHeight}
727
876
  columnGap={0}
@@ -1,3 +1,4 @@
1
+ import { numberErrorMessage } from "../../../utils/decimal-comma";
1
2
  import { DEFAULT_OPTION_CALC_DRAFT, updateOptionCalcDraft, type OptionCalcDraft } from "./model";
2
3
  import type { CashDividend } from "./binomial";
3
4
 
@@ -5,7 +6,7 @@ const DECIMAL = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
5
6
 
6
7
  function numberInput(value: unknown, label: string): number {
7
8
  if (typeof value !== "number" && (typeof value !== "string" || !DECIMAL.test(value.trim()))) {
8
- throw new Error(`${label} must be a finite number.`);
9
+ throw new Error(numberErrorMessage(`${label} must be a finite number.`, value));
9
10
  }
10
11
  const result = Number(value);
11
12
  if (!Number.isFinite(result)) throw new Error(`${label} must be a finite number.`);
@@ -1,5 +1,5 @@
1
1
  import { apiClient } from "../../../api-client";
2
- import { parsePublicTickerKey } from "../../../utils/exchanges";
2
+ import { usListingSymbol } from "../../../utils/sec";
3
3
 
4
4
  export interface ExpiryOpenInterest {
5
5
  date: string;
@@ -78,8 +78,12 @@ export interface GammaPayload {
78
78
 
79
79
  export type OptionsPositioningApi = Pick<typeof apiClient, "optionsPositioning">;
80
80
 
81
- /** A listing key ("SPY:ARCX") keeps only its symbol; an index keeps its caret (^SPX). */
82
- export const positioningSymbol = (symbol: string) => parsePublicTickerKey(symbol.trim()).symbol.toUpperCase();
81
+ /**
82
+ * A US listing key ("SPY:ARCX") keeps only its symbol; an index keeps its
83
+ * caret (^SPX). A listing abroad is refused: its bare symbol's chain is
84
+ * another company's (SAN:EPA's would be Banco Santander's).
85
+ */
86
+ export const positioningSymbol = (symbol: string) => usListingSymbol(symbol, "Options data");
83
87
 
84
88
  const query = (symbol: string, expiry: string | null) =>
85
89
  new URLSearchParams({ symbol: positioningSymbol(symbol), ...(expiry ? { expiry } : {}) }).toString();
@@ -1,4 +1,5 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
+ import { expirationOptionSeconds, expiryIsoDate, OPTION_EXPIRATION_FORMAT, OPTION_EXPIRATION_PLACEHOLDER } from "../../../utils/option-expiry";
2
3
  import { loadGamma, loadOpenInterest, positioningSymbol } from "./client";
3
4
  import {
4
5
  ALL_EXPIRIES,
@@ -25,6 +26,7 @@ export const optionsPositioningHeadless = (initialTab: PositioningTab): Headless
25
26
  shape: "bundle",
26
27
  freshness: { status: "not-a-feed", basis: "daily open interest", cadence: "daily" },
27
28
  argument: { kind: "ticker", optional: true, description: "US option underlying, or SPX; SPY when omitted" },
29
+ usListingsOnly: "Options data",
28
30
  describe: (args) => `${initialTab === "gex" ? "GEX" : "OPX"} ${args.symbols[0] ?? "SPY"}`,
29
31
  discovery: {
30
32
  aliases: initialTab === "gex" ? ["GEX"] : ["OPX", "MAXPAIN"],
@@ -39,8 +41,9 @@ export const optionsPositioningHeadless = (initialTab: PositioningTab): Headless
39
41
  options: [
40
42
  { key: "tab", type: "enum", values: POSITIONING_TABS.map((tab) => ({ value: tab.value })), defaultValue: initialTab,
41
43
  settingKey: "tab", description: "View the pane opens on: strikes, expiries or gex" },
42
- { key: "expiry", type: "string", settingKey: "expiry",
43
- description: "Expiry for the strikes and gamma, YYYY-MM-DD; the busiest near expiry when omitted (all expiries for gamma)" },
44
+ { key: "expiry", type: "string", settingKey: "expiry", placeholder: OPTION_EXPIRATION_PLACEHOLDER, example: "--expiry 2027-01-15",
45
+ description: `Expiry for the strikes and gamma, as ${OPTION_EXPIRATION_FORMAT}; the busiest near expiry when omitted (all expiries for gamma)`,
46
+ normalize: (value) => expiryIsoDate(expirationOptionSeconds(value, "expiry")) },
44
47
  ],
45
48
  async load(args, ctx) {
46
49
  const symbol = positioningSymbol(args.symbols[0] ?? "SPY");
@@ -34,13 +34,13 @@ import type { ResolvedSeries } from "../../../time-series/types";
34
34
  import type { PaneProps } from "../../../types/plugin";
35
35
  import { Box, Text } from "../../../ui";
36
36
  import { formatCompact } from "../../../utils/format";
37
+ import { parsePublicTickerKey } from "../../../utils/exchanges";
37
38
  import { isPlainKey } from "../../../utils/keyboard";
38
39
  import { nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
39
40
  import { useCloudAccessFooter } from "../shared/cloud-upgrade";
40
41
  import {
41
42
  loadGamma,
42
43
  loadOpenInterest,
43
- positioningSymbol,
44
44
  type ExpiryOpenInterest,
45
45
  type GammaPayload,
46
46
  type GammaStrike,
@@ -583,7 +583,7 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
583
583
  return <Box width={width} height={height} flexDirection="column">
584
584
  <PaneStatusBody loading={openInterest.loading && !data} error={!data ? openInterest.error : null}
585
585
  empty={!!data && !data.expiries.length} subject="open interest"
586
- emptyTitle={`No listed options for ${positioningSymbol(symbol).replace(/^\^/, "")}.`}>
586
+ emptyTitle={`No listed options for ${parsePublicTickerKey(symbol).symbol.replace(/^\^/, "")}.`}>
587
587
  {data?.expiries.length ? <>
588
588
  {tabStrip}
589
589
  {content}
@@ -3,7 +3,7 @@ import { StaticChartSurface, type StaticChartOverlay } from "../../../components
3
3
  import { Box, Text } from "../../../ui";
4
4
  import { resolveChartPalette } from "../../../components/chart/core/palette";
5
5
  import { useThemeColors } from "../../../theme/theme-context";
6
- import type { buildScenario } from "./model";
6
+ import { currencyLabel, scenarioValueUnit, type buildScenario } from "./model";
7
7
 
8
8
  export function ScenarioPayoffChart({ scenario, width, height, focused = false }: {
9
9
  scenario: ReturnType<typeof buildScenario>; width: number; height: number; focused?: boolean;
@@ -28,7 +28,7 @@ export function ScenarioPayoffChart({ scenario, width, height, focused = false }
28
28
  <Box height={1} paddingX={1} flexDirection="row" gap={3}>
29
29
  <Text fg={colors.warning}>{`${scenario.expiryRisk.reason ? "First expiry" : "Expiry"} ${date(scenario.expiryDate)}`}</Text>
30
30
  <Text fg={colors.borderFocused}>{`Selected ${date(scenario.controls.date)}`}</Text>
31
- <Text fg={colors.textDim}>{`P&L ${scenario.position.currency}`}</Text>
31
+ <Text fg={colors.textDim}>{`P&L ${currencyLabel(scenario.position.currency)} ${scenarioValueUnit(scenario.position)}`}</Text>
32
32
  </Box>
33
33
  <StaticChartSurface points={points} overlays={overlays} calendarSpaced
34
34
  width={width} height={Math.max(2, height - 1)} colors={{ ...resolveChartPalette(colors), lineColor: colors.warning }}
@@ -5,10 +5,12 @@ 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 { numberErrorMessage } from "../../../utils/decimal-comma";
9
+ import { expiryIsoDate, findListedExpiry, missingExpiryText } from "../../../utils/option-expiry";
8
10
  import { surfaceTreasuryRate } from "../vol-surface/model";
9
11
  import { daysToExpiryFrom, extractImpliedForward, optionMid, solveImpliedVolatility } from "../shared/volatility";
10
12
  import type { YieldPoint } from "../yield-curve/treasury-data";
11
- import { parseLegs, validatePosition, type ScenarioControls, type ScenarioLeg, type ScenarioPosition } from "./model";
13
+ import { parseLegs, scenarioCurrency, validatePosition, type ScenarioControls, type ScenarioLeg, type ScenarioPosition } from "./model";
12
14
  import { abortable, abortError } from "../../../utils/async-deadline";
13
15
  import { errorMessage } from "../../../utils/errors";
14
16
 
@@ -26,6 +28,8 @@ export interface ScenarioMarketSnapshot {
26
28
  underlyingQuote: Quote | null;
27
29
  rateAsOf: string[];
28
30
  warnings: string[];
31
+ /** Why the requested expiry has no chain, naming the listed dates nearest it. */
32
+ missingExpiry?: string;
29
33
  }
30
34
 
31
35
  export interface ScenarioLoaderDependencies {
@@ -99,15 +103,31 @@ export async function loadScenarioMarket(
99
103
  const validDividend = symbolMatches(financialsSymbol, symbol) && !fundamentals?.stale && !financialsEntry?.error
100
104
  && typeof fundamentals?.dividendYield === "number" && Number.isFinite(fundamentals.dividendYield) && fundamentals.dividendYield >= 0;
101
105
  if (!validDividend) warnings.push("Dividend yield unavailable; supply an explicit assumption");
102
- const chainEntry = entry(chainResult, "Options chain");
106
+ let chainEntry = entry(chainResult, "Options chain");
103
107
  let chain = chainEntry ? resolveEntryValue(chainEntry) : null;
104
108
  if (chain && !symbolMatches(chain.underlyingSymbol, symbol)) {
105
109
  warnings.push("Options chain does not match the requested ticker"); chain = null;
106
110
  }
107
111
  const expirationDates = [...new Set((chain?.expirationDates ?? []).filter((expiration) =>
108
112
  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))) {
113
+ // A typed date is that day's UTC midnight; the chain's own stamp for the
114
+ // expiry is matched by calendar date, as OMON matches it.
115
+ const listedExpiration = request.expiration == null ? undefined : findListedExpiry(request.expiration, expirationDates);
116
+ const onExpiry = (value: OptionsChain, expiration: number) => [...value.calls, ...value.puts]
117
+ .every((contract) => expiryIsoDate(contract.expiration) === expiryIsoDate(expiration));
118
+ if (chain && listedExpiration != null && listedExpiration !== request.expiration && !chain.calls.length && !chain.puts.length) {
119
+ // A source that keys chains by its exact stamp answered the midnight request empty.
120
+ chainEntry = await abortable(dependencies.loadOptions({ instrument: request.instrument, expirationDate: listedExpiration }, options)
121
+ .catch((error: unknown) => { warnings.push(`Options chain: ${errorMessage(error)}`); return null; }), request.signal, CANCELLED);
122
+ if (request.signal?.aborted) throw abortError(CANCELLED);
123
+ if (chainEntry?.error) warnings.push(`Options chain: ${chainEntry.error.message}`);
124
+ chain = chainEntry ? resolveEntryValue(chainEntry) : null;
125
+ }
126
+ let missingExpiry: string | undefined;
127
+ if (chain && request.expiration != null && listedExpiration == null) {
128
+ missingExpiry = sentence(missingExpiryText(request.expiration, expirationDates, symbol));
129
+ warnings.push(missingExpiry); chain = null;
130
+ } else if (chain && listedExpiration != null && !onExpiry(chain, listedExpiration)) {
111
131
  warnings.push("Selected expiration unavailable in the returned options chain"); chain = null;
112
132
  }
113
133
  if (!chain) warnings.push("Options chain unavailable");
@@ -117,15 +137,22 @@ export async function loadScenarioMarket(
117
137
  if (curveResult.status === "rejected") warnings.push(`Treasury: ${errorMessage(curveResult.reason)}`);
118
138
  const rate = surfaceTreasuryRate(curve, rateExpiration == null ? NaN : daysToExpiryFrom(rateExpiration, now) / 365);
119
139
  warnings.push(...rate.warnings);
140
+ // A currency does not go stale with its quote: the listing's quote, its snapshot, or its contracts name it.
141
+ const currency = [symbolMatches(quote?.symbol, symbol) ? quote?.currency : undefined,
142
+ symbolMatches(financialsSymbol, symbol) ? financials?.quote?.currency : undefined,
143
+ chain?.calls[0]?.currency, chain?.puts[0]?.currency].find((value) => !!value?.trim())?.trim().toUpperCase() ?? "";
120
144
  return {
121
- symbol, exchange: request.instrument.exchange, spot: quoteCurrent ? quote!.price : null, currency: quoteCurrent ? quote!.currency : "",
145
+ symbol, exchange: request.instrument.exchange, spot: quoteCurrent ? quote!.price : null, currency,
122
146
  asOf: quoteCurrent && positive(quote!.lastUpdated) ? quote!.lastUpdated : now,
123
147
  chain, expirationDates, rate: rate.rate, dividendYield: validDividend ? fundamentals!.dividendYield! : null,
124
148
  source: quoteCurrent ? quoteEntry?.source ?? quote!.providerId ?? null : null,
125
149
  underlyingQuote: quoteCurrent ? quote : null, rateAsOf: rate.asOf, warnings: [...new Set(warnings)],
150
+ ...(missingExpiry ? { missingExpiry } : {}),
126
151
  };
127
152
  }
128
153
 
154
+ const sentence = (text: string) => text.charAt(0).toUpperCase() + text.slice(1);
155
+
129
156
  function supplied(value: unknown): boolean { return value != null && value !== ""; }
130
157
 
131
158
  function numericSetting(settings: Record<string, unknown>, key: string): number | undefined {
@@ -133,7 +160,7 @@ function numericSetting(settings: Record<string, unknown>, key: string): number
133
160
  if (!supplied(value)) return undefined;
134
161
  if ((typeof value !== "string" && typeof value !== "number")
135
162
  || (typeof value === "string" && !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value.trim()))
136
- || !Number.isFinite(Number(value))) throw new Error(`${key} must be a finite number`);
163
+ || !Number.isFinite(Number(value))) throw new Error(numberErrorMessage(`${key} must be a finite number`, value));
137
164
  return Number(value);
138
165
  }
139
166
 
@@ -175,13 +202,18 @@ export function scenarioPositionFromSettings(
175
202
  const dividendYield = dividendValue == null ? market?.dividendYield : dividendValue / 100;
176
203
  const legText = typeof settings.legs === "string" ? settings.legs : "";
177
204
  let legs = legText.trim() ? parseLegs(legText) : [];
205
+ const strike = numericSetting(settings, "strike");
206
+ if (strike != null && !(strike > 0)) throw new Error("strike must be positive");
207
+ if (strike != null && !supplied(settings.strategy)) throw new Error("strike picks the strike of --strategy; add --strategy put, vertical or straddle");
178
208
  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");
209
+ if (!isScenarioStrategy(settings.strategy)) throw new Error("strategy must be vertical, straddle or put");
210
+ if (!market?.chain || market.warnings.includes("Options chain is stale")) {
211
+ throw new Error(market?.missingExpiry ?? "A current options chain is required to seed a strategy");
212
+ }
181
213
  if (spot == null) throw new Error("A current underlying price or explicit --spot is required");
182
214
  // Leg IVs are solved at the market's own spot and time, never at a what-if override.
183
215
  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);
216
+ const seeded = scenarioStrategyLegs(market.chain, spot, settings.strategy, pricing, strike);
185
217
  legs = seeded.legs;
186
218
  // Mids priced off the parity forward are worth their cost only at the spot
187
219
  // that forward implies on the scenario's own rate and dividend yield. The
@@ -198,7 +230,7 @@ export function scenarioPositionFromSettings(
198
230
  if (dividendYield == null) throw new Error("Dividend yield unavailable; supply --dividend-yield as an annual percentage");
199
231
  const position: ScenarioPosition = {
200
232
  symbol, exchange: exchange ?? market?.exchange,
201
- currency: String(settings.currency ?? market?.currency ?? "").trim().toUpperCase() || "UNKNOWN",
233
+ currency: scenarioCurrency(settings.currency, market?.currency, exchange ?? market?.exchange),
202
234
  spot, rate, dividendYield, asOf: dateSetting(settings, "asOf") ?? market?.asOf ?? Date.now(), legs,
203
235
  };
204
236
  const error = validatePosition(position);
@@ -210,6 +242,9 @@ export function scenarioPositionFromSettings(
210
242
  return position;
211
243
  }
212
244
 
245
+ type ScenarioStrategy = "vertical" | "straddle" | "put";
246
+ const isScenarioStrategy = (value: unknown): value is ScenarioStrategy => value === "vertical" || value === "straddle" || value === "put";
247
+
213
248
  interface StrategyPricing { spot: number; asOf: number; rate: number }
214
249
 
215
250
  const usableVolatility = (value: unknown): value is number => positive(value) && value <= 5;
@@ -230,9 +265,13 @@ interface StrategyParity { forward: number; daysToExpiry: number }
230
265
  * keeps a usable provider IV, and without one it is not eligible. `parity` is
231
266
  * the forward a seeded leg was solved against, so the caller can start the
232
267
  * scenario at the spot it implies.
268
+ *
269
+ * Each strategy is anchored at the strike nearest spot, or at `strike` when
270
+ * one is given: a long put (a protective put), a call vertical from the
271
+ * anchor to the next listed strike up, or a straddle at the anchor.
233
272
  */
234
273
  function scenarioStrategyLegs(
235
- chain: OptionsChain, spot: number, strategy: "vertical" | "straddle", pricing: StrategyPricing | null,
274
+ chain: OptionsChain, spot: number, strategy: ScenarioStrategy, pricing: StrategyPricing | null, strike?: number,
236
275
  ): { legs: ScenarioLeg[]; parity: StrategyParity | null } {
237
276
  const parity = new Map<number, StrategyParity | null>();
238
277
  const parityFor = (expiration: number) => {
@@ -256,12 +295,30 @@ function scenarioStrategyLegs(
256
295
  if (usableVolatility(volatility)) return [{ contract, mid, volatility, solved: true }];
257
296
  return usableVolatility(contract.impliedVolatility) ? [{ contract, mid, volatility: contract.impliedVolatility, solved: false }] : [];
258
297
  });
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");
298
+ const nearest = (a: { contract: { strike: number } }, b: { contract: { strike: number } }) =>
299
+ Math.abs(a.contract.strike - spot) - Math.abs(b.contract.strike - spot);
300
+ const anchored = ({ contract }: { contract: { strike: number } }) => strike == null || Math.abs(contract.strike - strike) < 1e-9;
301
+ const calls = quoted(chain.calls, "call").sort(nearest);
302
+ const puts = quoted(chain.puts, "put").sort(nearest);
303
+ const missing = (candidates: { contract: { strike: number } }[]) => {
304
+ if (strike == null) return new Error("The selected chain has no complete quoted strategy with usable IV");
305
+ const listed = [...new Set(candidates.map(({ contract }) => contract.strike))]
306
+ .sort((a, b) => Math.abs(a - strike) - Math.abs(b - strike)).slice(0, 4).sort((a, b) => a - b);
307
+ return new Error(`The selected chain has no complete quoted ${strategy} at strike ${strike} with usable IV`
308
+ + (listed.length ? `; nearest quoted strikes: ${listed.join(", ")}` : ""));
309
+ };
310
+ if (strategy === "put") {
311
+ const put = puts.find(anchored);
312
+ if (!put) throw missing(puts);
313
+ return { legs: [{ id: put.contract.contractSymbol, side: "put", quantity: 1, strike: put.contract.strike,
314
+ expiration: put.contract.expiration, price: put.mid, priceSource: "mid", volatility: put.volatility, multiplier: 100,
315
+ ...(put.solved ? { volatilitySource: "mid" as const } : {}) }],
316
+ parity: put.solved ? parityFor(put.contract.expiration) : null };
317
+ }
318
+ const first = calls.find((entry) => anchored(entry) && (strategy === "vertical"
319
+ ? calls.some(({ contract: other }) => other.expiration === entry.contract.expiration && other.strike > entry.contract.strike)
320
+ : puts.some(({ contract: put }) => put.expiration === entry.contract.expiration && put.strike === entry.contract.strike)));
321
+ if (!first) throw missing(calls);
265
322
  const second = strategy === "vertical"
266
323
  ? calls.filter(({ contract }) => contract.expiration === first.contract.expiration && contract.strike > first.contract.strike)
267
324
  .sort((a, b) => a.contract.strike - b.contract.strike)[0]!
@@ -270,7 +327,7 @@ function scenarioStrategyLegs(
270
327
  const legs = [first, second].map(({ contract, mid, volatility, solved }, index): ScenarioLeg => ({
271
328
  id: contract.contractSymbol, side: index === 1 && strategy === "straddle" ? "put" : "call",
272
329
  quantity: index === 1 && strategy === "vertical" ? -1 : 1, strike: contract.strike,
273
- expiration: contract.expiration, price: mid, volatility, multiplier: 100,
330
+ expiration: contract.expiration, price: mid, priceSource: "mid", volatility, multiplier: 100,
274
331
  ...(solved ? { volatilitySource: "mid" as const } : {}),
275
332
  }));
276
333
  return { legs, parity: first.solved || second.solved ? parityFor(first.contract.expiration) : null };