gloomberb 0.18.3 → 0.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (886) hide show
  1. package/README.md +8 -8
  2. package/README.zh-CN.md +11 -11
  3. package/bin/{gloomberb → gloom} +3 -1
  4. package/package.json +6 -2
  5. package/src/account/checkout-handoff.tsx +225 -0
  6. package/src/account/device-signin-dialog.tsx +2 -2
  7. package/src/account/device-signin.ts +2 -2
  8. package/src/account/upgrade-dialog.tsx +1 -1
  9. package/src/api-client/askg.ts +2 -2
  10. package/src/api-client/auth.ts +2 -2
  11. package/src/api-client/central-bank-rates.ts +6 -0
  12. package/src/api-client/collections.ts +5 -1
  13. package/src/api-client/cot.ts +2 -0
  14. package/src/api-client/crypto-etf-flows.ts +37 -0
  15. package/src/api-client/crypto-markets.ts +60 -0
  16. package/src/api-client/data.ts +84 -5
  17. package/src/api-client/doe.ts +15 -0
  18. package/src/api-client/equity-screener.ts +28 -0
  19. package/src/api-client/exchange-announcements.ts +38 -0
  20. package/src/api-client/fund-facts.ts +39 -0
  21. package/src/api-client/futures-curve.ts +9 -1
  22. package/src/api-client/hiring.ts +301 -0
  23. package/src/api-client/index.ts +22 -5
  24. package/src/api-client/market-discovery.ts +14 -0
  25. package/src/api-client/members.ts +6 -0
  26. package/src/api-client/mna.ts +6 -0
  27. package/src/api-client/paths.ts +18 -1
  28. package/src/api-client/request.ts +21 -4
  29. package/src/api-client/short-side-publications.ts +37 -0
  30. package/src/api-client/socket.ts +64 -4
  31. package/src/api-client/swap-curve.ts +35 -0
  32. package/src/api-client/themes.ts +7 -1
  33. package/src/api-client/types.ts +90 -2
  34. package/src/app/pane-runtime/plugin-bindings.ts +9 -2
  35. package/src/app/runtime/desktop-deeplink.ts +5 -5
  36. package/src/app/runtime/plugin-bindings.ts +2 -2
  37. package/src/app/runtime/startup.ts +13 -0
  38. package/src/app/star-prompt/status-line.tsx +2 -2
  39. package/src/architecture/core-builtin-allowlist.json +5 -2
  40. package/src/architecture/ui-literals-allowlist.json +3 -3
  41. package/src/architecture/ui-tokens-allowlist.json +1 -1
  42. package/src/brokers/broker-portfolio-sync.ts +37 -0
  43. package/src/brokers/directory.ts +11 -0
  44. package/src/brokers/logos.ts +3 -1
  45. package/src/brokers/signed-in/adapter.ts +304 -26
  46. package/src/brokers/signed-in/client.ts +14 -2
  47. package/src/brokers/signed-in/connect.ts +10 -2
  48. package/src/brokers/sync-broker-instance.ts +9 -1
  49. package/src/capabilities/chart-series.ts +22 -6
  50. package/src/cli/bank-section.ts +1 -1
  51. package/src/cli/base-converter.ts +23 -14
  52. package/src/cli/central-bank-events.ts +1 -1
  53. package/src/cli/command-options.ts +59 -4
  54. package/src/cli/commands/api.ts +9 -6
  55. package/src/cli/commands/broker.ts +2 -2
  56. package/src/cli/commands/command-utils.ts +2 -2
  57. package/src/cli/commands/crypto-hints.ts +7 -20
  58. package/src/cli/commands/fred-series.ts +4 -2
  59. package/src/cli/commands/keybindings.ts +2 -2
  60. package/src/cli/commands/market.ts +1117 -264
  61. package/src/cli/commands/news-hints.ts +2 -2
  62. package/src/cli/commands/news-search.ts +59 -6
  63. package/src/cli/commands/options-leaps.ts +41 -26
  64. package/src/cli/commands/overview.ts +121 -48
  65. package/src/cli/commands/plugins.ts +9 -9
  66. package/src/cli/commands/remote.ts +13 -10
  67. package/src/cli/commands/search.ts +64 -44
  68. package/src/cli/commands/system.ts +27 -19
  69. package/src/cli/commands/themes.ts +1 -1
  70. package/src/cli/commands/ticker.ts +192 -47
  71. package/src/cli/context.ts +3 -2
  72. package/src/cli/desktop-pane-shot-view.ts +2 -2
  73. package/src/cli/desktop-pane-shot.ts +1 -1
  74. package/src/cli/earnings-calendar.ts +4 -4
  75. package/src/cli/entry.ts +18 -1
  76. package/src/cli/errors.ts +20 -2
  77. package/src/cli/exit-codes.ts +1 -1
  78. package/src/cli/export-source.ts +10 -4
  79. package/src/cli/freshness.ts +2 -8
  80. package/src/cli/fx-forward.ts +130 -0
  81. package/src/cli/fx-pair.ts +10 -9
  82. package/src/cli/glossary.ts +64 -20
  83. package/src/cli/help.ts +19 -13
  84. package/src/cli/history-export.ts +126 -0
  85. package/src/cli/history-rows.ts +196 -43
  86. package/src/cli/index-roots.ts +5 -45
  87. package/src/cli/index.ts +24 -18
  88. package/src/cli/known-gaps.ts +118 -25
  89. package/src/cli/listing-arg.ts +300 -41
  90. package/src/cli/name-entries.ts +30 -18
  91. package/src/cli/native-smoke.ts +2 -0
  92. package/src/cli/not-a-ticker.ts +19 -9
  93. package/src/cli/opentui-libc.ts +5 -0
  94. package/src/cli/options.ts +12 -0
  95. package/src/cli/pane-functions/capabilities.ts +75 -20
  96. package/src/cli/pane-functions/catalog.ts +24 -23
  97. package/src/cli/pane-functions/discovery.ts +1 -1
  98. package/src/cli/pane-functions/dom.ts +10 -7
  99. package/src/cli/pane-functions/financial-ratio-shot.ts +2 -2
  100. package/src/cli/pane-functions/freshness.ts +174 -23
  101. package/src/cli/pane-functions/function-help.ts +66 -27
  102. package/src/cli/pane-functions/headless.ts +24 -11
  103. package/src/cli/pane-functions/index.ts +18 -16
  104. package/src/cli/pane-functions/options.ts +22 -2
  105. package/src/cli/pane-functions/report-notices.ts +12 -4
  106. package/src/cli/pane-functions/report.ts +5 -5
  107. package/src/cli/pane-functions/resolver.ts +47 -10
  108. package/src/cli/pane-functions/screenshot-assessment.ts +322 -0
  109. package/src/cli/pane-functions/screenshot-bridge.ts +152 -0
  110. package/src/cli/pane-functions/screenshot-chart-evidence.ts +396 -0
  111. package/src/cli/pane-functions/screenshot-data-evidence.ts +383 -0
  112. package/src/cli/pane-functions/screenshot-evidence.ts +1 -1
  113. package/src/cli/pane-functions/screenshot-payload.ts +453 -0
  114. package/src/cli/pane-functions/screenshot-render.ts +316 -0
  115. package/src/cli/pane-functions/screenshot-types.ts +162 -0
  116. package/src/cli/pane-functions/shot-options.ts +8 -3
  117. package/src/cli/peso-dollar-rates.ts +79 -0
  118. package/src/cli/quote-answer.ts +38 -3
  119. package/src/cli/report-tables.ts +61 -16
  120. package/src/cli/restore-plugins.ts +29 -14
  121. package/src/cli/result.ts +111 -22
  122. package/src/cli/series-search.ts +5 -5
  123. package/src/cli/statement-filings.ts +19 -0
  124. package/src/cli/suffixed-listing-rows.ts +49 -0
  125. package/src/cli/symbol-args.ts +50 -15
  126. package/src/cli/symbol-cells.ts +120 -0
  127. package/src/cli/symbol-list.ts +198 -68
  128. package/src/cli/symbol-rows.ts +131 -25
  129. package/src/cli/ticker-resolution.ts +108 -11
  130. package/src/cli/treasury-yields.ts +5 -5
  131. package/src/cli/types.ts +2 -0
  132. package/src/cli/uncovered-instrument.ts +3 -3
  133. package/src/cli/unknown-command.ts +6 -6
  134. package/src/components/chart/composite/chart-layout.ts +131 -0
  135. package/src/components/chart/composite/composite-chart.tsx +373 -16
  136. package/src/components/chart/composite/interactions.ts +7 -2
  137. package/src/components/chart/composite/legend.tsx +75 -3
  138. package/src/components/chart/composite/levels.ts +5 -0
  139. package/src/components/chart/composite/panel-gestures.ts +41 -0
  140. package/src/components/chart/composite/panel-labels.ts +2 -1
  141. package/src/components/chart/composite/panel-surface.tsx +305 -11
  142. package/src/components/chart/composite/price-axis-labels.tsx +27 -3
  143. package/src/components/chart/composite/rasterizer.ts +44 -19
  144. package/src/components/chart/composite/scene.ts +16 -4
  145. package/src/components/chart/composite/text-renderer.ts +48 -21
  146. package/src/components/chart/composite/toolbar.tsx +52 -5
  147. package/src/components/chart/composite/types.ts +13 -0
  148. package/src/components/chart/composite/use-composite-chart-keyboard.ts +120 -13
  149. package/src/components/chart/composite/use-composite-value-scales.ts +44 -0
  150. package/src/components/chart/composite/use-composite-viewport.ts +12 -3
  151. package/src/components/chart/composite/value-scale.ts +149 -0
  152. package/src/components/chart/composite/volume-profile-paint.ts +6 -3
  153. package/src/components/chart/core/pointer.ts +2 -0
  154. package/src/components/chart-table/index.ts +2 -0
  155. package/src/components/chart-table/read.tsx +74 -0
  156. package/src/components/command-bar/commands/collection-list.ts +41 -19
  157. package/src/components/command-bar/commands/collection.ts +9 -3
  158. package/src/components/command-bar/commands/direct/index.ts +1 -1
  159. package/src/components/command-bar/commands/direct/runtime.ts +3 -2
  160. package/src/components/command-bar/pane-templates/items.ts +59 -6
  161. package/src/components/command-bar/pane-templates/workflow.ts +10 -1
  162. package/src/components/command-bar/routes/root/plugin-install.ts +1 -1
  163. package/src/components/command-bar/routes/root/provider-search.ts +42 -11
  164. package/src/components/command-bar/routes/root/results.ts +18 -4
  165. package/src/components/command-bar/routes/root/runtime.ts +15 -3
  166. package/src/components/command-bar/routes/root/search-providers.ts +25 -2
  167. package/src/components/command-bar/routes/root/selection.ts +6 -1
  168. package/src/components/command-bar/routes/root/shortcut-feedback.ts +4 -1
  169. package/src/components/command-bar/routes/root/shortcuts.ts +30 -3
  170. package/src/components/command-bar/routes/ticker-search/results.ts +73 -4
  171. package/src/components/command-bar/selection-runtime.ts +50 -10
  172. package/src/components/command-bar/surface/index.tsx +24 -2
  173. package/src/components/command-bar/view-model.ts +7 -0
  174. package/src/components/command-bar/workflow/broker.ts +2 -2
  175. package/src/components/command-bar/workflow/collection-actions.ts +46 -7
  176. package/src/components/command-bar/workflow/collection-ticker.ts +32 -4
  177. package/src/components/command-bar/workflow/fields.ts +8 -2
  178. package/src/components/command-bar/workflow/ops.ts +18 -4
  179. package/src/components/command-bar/workflow/portfolio-position.ts +53 -4
  180. package/src/components/command-bar/workflow/submit.ts +3 -2
  181. package/src/components/command-bar/workflow/tickers.ts +5 -1
  182. package/src/components/company-logo.tsx +39 -0
  183. package/src/components/data-table/sections.ts +4 -2
  184. package/src/components/form-modal/confirm.tsx +2 -1
  185. package/src/components/form-modal/request.ts +5 -0
  186. package/src/components/form-modal/routes.ts +4 -1
  187. package/src/components/index.ts +12 -6
  188. package/src/components/layout/header.tsx +7 -6
  189. package/src/components/layout/market-summary.ts +9 -6
  190. package/src/components/layout/pane/capture-status.ts +1 -1
  191. package/src/components/layout/pane/footer/model.ts +11 -5
  192. package/src/components/layout/shell/chrome.ts +19 -6
  193. package/src/components/layout/shell/index.tsx +2 -1
  194. package/src/components/layout/shell/pane/layers.tsx +19 -1
  195. package/src/components/layout/status-bar.tsx +2 -2
  196. package/src/components/market-board/index.tsx +11 -3
  197. package/src/components/onboarding/onboarding-wizard.tsx +1 -0
  198. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +2 -2
  199. package/src/components/onboarding/wizard-broker-sync.ts +4 -1
  200. package/src/components/pane-settings-dialog/desktop.tsx +20 -7
  201. package/src/components/pane-settings-dialog/tui.tsx +17 -9
  202. package/src/components/pane-settings-dialog/value.ts +8 -1
  203. package/src/components/pane-settings-dialog.tsx +44 -13
  204. package/src/components/sign-in-code-panel.tsx +27 -19
  205. package/src/components/ui/column-grid.tsx +48 -4
  206. package/src/components/ui/country-flag-art.tsx +180 -0
  207. package/src/components/ui/country-flag.tsx +118 -12
  208. package/src/components/ui/data-table/opentui/index.tsx +61 -2
  209. package/src/components/ui/data-table/opentui/model.ts +109 -2
  210. package/src/components/ui/data-table/types.ts +1 -1
  211. package/src/components/ui/display.tsx +5 -1
  212. package/src/components/ui/index.ts +3 -1
  213. package/src/components/ui/logo-mark.tsx +11 -2
  214. package/src/components/ui/query-bar.tsx +1 -1
  215. package/src/components/ui/row-notes.ts +79 -0
  216. package/src/core/state/app/layout.ts +4 -4
  217. package/src/data/cached-query.ts +8 -1
  218. package/src/data/config/chart-settings.ts +3 -3
  219. package/src/data/config/home.ts +10 -2
  220. package/src/data/config/layout.ts +4 -1
  221. package/src/data/config/store/node.ts +4 -0
  222. package/src/data/config/store/normalize.ts +13 -3
  223. package/src/data/config/store/portfolio-as-of-column.ts +2 -2
  224. package/src/data/json-storage.ts +2 -0
  225. package/src/data/saved-copies.ts +84 -0
  226. package/src/docs/snapshot.json +65 -32
  227. package/src/i18n/es.ts +29 -12
  228. package/src/i18n/ja.ts +29 -12
  229. package/src/i18n/ko.ts +29 -12
  230. package/src/i18n/zh-cn.ts +29 -12
  231. package/src/i18n/zh-tw.ts +29 -12
  232. package/src/layout/desks.ts +1 -1
  233. package/src/layout/launch-layout.ts +1 -1
  234. package/src/layout/pane-manager/floating-actions.ts +5 -3
  235. package/src/layout/pane-manager/floating.ts +43 -1
  236. package/src/layout/pane-manager/index.ts +2 -0
  237. package/src/layout/selected-ticker.ts +74 -0
  238. package/src/market-data/depositary-pair.ts +251 -0
  239. package/src/market-data/futures-board.ts +1 -1
  240. package/src/market-data/futures-contract.ts +124 -40
  241. package/src/market-data/fx-closes.ts +17 -2
  242. package/src/market-data/hooks.tsx +19 -11
  243. package/src/market-data/market/change-basis.ts +49 -7
  244. package/src/market-data/market/freshness.ts +34 -5
  245. package/src/market-data/market/period-return.ts +163 -0
  246. package/src/market-data/market/trading-sessions.ts +13 -0
  247. package/src/market-data/market/year-to-date.ts +8 -13
  248. package/src/market-data/performance.ts +20 -14
  249. package/src/market-data/perpetual-premium.ts +32 -0
  250. package/src/market-data/published-apac-sessions.ts +2 -1
  251. package/src/market-data/published-futures-sessions.ts +69 -0
  252. package/src/market-data/quotes/contributions.ts +5 -1
  253. package/src/market-data/quotes/freshness.ts +42 -0
  254. package/src/market-data/quotes/intraday-bar.ts +37 -0
  255. package/src/market-data/quotes/regular-session-valuation.ts +13 -8
  256. package/src/market-data/quotes/resolution.ts +4 -2
  257. package/src/market-data/tax-equivalent.ts +1 -1
  258. package/src/market-data/ticker-price-returns.ts +3 -1
  259. package/src/news/aggregator.ts +5 -1
  260. package/src/news/news-model.ts +4 -0
  261. package/src/news/types.ts +11 -0
  262. package/src/notifications/app-notifier.ts +1 -1
  263. package/src/plugins/auto-update.ts +2 -2
  264. package/src/plugins/builtin/account-management/index.tsx +1 -0
  265. package/src/plugins/builtin/account-management/pane.tsx +9 -8
  266. package/src/plugins/builtin/alerts/alert-engine.ts +29 -19
  267. package/src/plugins/builtin/alerts/calendar-events.ts +378 -0
  268. package/src/plugins/builtin/alerts/cli.ts +485 -76
  269. package/src/plugins/builtin/alerts/command.ts +5 -10
  270. package/src/plugins/builtin/alerts/event-rows.ts +9 -5
  271. package/src/plugins/builtin/alerts/events-pane.tsx +71 -56
  272. package/src/plugins/builtin/alerts/events.ts +107 -5
  273. package/src/plugins/builtin/alerts/fired-log.ts +78 -0
  274. package/src/plugins/builtin/alerts/format.ts +2 -1
  275. package/src/plugins/builtin/alerts/headless.ts +100 -39
  276. package/src/plugins/builtin/alerts/history-pane.tsx +105 -62
  277. package/src/plugins/builtin/alerts/history-rows.ts +89 -0
  278. package/src/plugins/builtin/alerts/index.tsx +45 -18
  279. package/src/plugins/builtin/alerts/iv-level.ts +365 -0
  280. package/src/plugins/builtin/alerts/levels.ts +3 -3
  281. package/src/plugins/builtin/alerts/pane.tsx +97 -29
  282. package/src/plugins/builtin/alerts/price.ts +35 -0
  283. package/src/plugins/builtin/alerts/quotes.ts +24 -0
  284. package/src/plugins/builtin/alerts/research-builder.ts +84 -11
  285. package/src/plugins/builtin/alerts/research-rules.ts +76 -3
  286. package/src/plugins/builtin/alerts/scheduled-check.ts +206 -32
  287. package/src/plugins/builtin/alerts/scheduled-sources.ts +26 -3
  288. package/src/plugins/builtin/alerts/scheduled.ts +188 -29
  289. package/src/plugins/builtin/alerts/storage.ts +13 -10
  290. package/src/plugins/builtin/alerts/targets.ts +12 -3
  291. package/src/plugins/builtin/analytics/index.tsx +8 -2
  292. package/src/plugins/builtin/analytics/metrics.ts +1 -1
  293. package/src/plugins/builtin/analytics/risk-book.ts +215 -0
  294. package/src/plugins/builtin/analytics/risk-client.ts +13 -6
  295. package/src/plugins/builtin/analytics/risk-headless.ts +55 -21
  296. package/src/plugins/builtin/analytics/risk-math.ts +6 -3
  297. package/src/plugins/builtin/analytics/risk-model.ts +297 -104
  298. package/src/plugins/builtin/analytics/risk-options.ts +17 -2
  299. package/src/plugins/builtin/analytics/risk-pane.tsx +97 -36
  300. package/src/plugins/builtin/attention/pane.tsx +6 -2
  301. package/src/plugins/builtin/awards/headless.ts +7 -3
  302. package/src/plugins/builtin/awards/model.ts +18 -0
  303. package/src/plugins/builtin/awards/pane.tsx +6 -3
  304. package/src/plugins/builtin/backtest/headless.ts +3 -3
  305. package/src/plugins/builtin/backtest/rules.ts +41 -5
  306. package/src/plugins/builtin/broker-manager/add-flow.tsx +12 -7
  307. package/src/plugins/builtin/broker-manager/detail.tsx +47 -19
  308. package/src/plugins/builtin/broker-manager/footer.ts +27 -11
  309. package/src/plugins/builtin/broker-manager/index.tsx +21 -12
  310. package/src/plugins/builtin/broker-manager/keyboard.ts +1 -1
  311. package/src/plugins/builtin/broker-manager/model.ts +12 -2
  312. package/src/plugins/builtin/broker-manager/pane-actions.tsx +183 -57
  313. package/src/plugins/builtin/broker-manager/table.ts +28 -12
  314. package/src/plugins/builtin/broker-trading/account-picker.tsx +2 -1
  315. package/src/plugins/builtin/broker-trading/chart-levels.ts +82 -13
  316. package/src/plugins/builtin/broker-trading/controller.ts +45 -13
  317. package/src/plugins/builtin/broker-trading/dock.tsx +22 -4
  318. package/src/plugins/builtin/broker-trading/headless.ts +2 -1
  319. package/src/plugins/builtin/broker-trading/index.tsx +7 -1
  320. package/src/plugins/builtin/broker-trading/linked-orders.ts +148 -0
  321. package/src/plugins/builtin/broker-trading/order-feed.ts +75 -7
  322. package/src/plugins/builtin/broker-trading/orders.tsx +109 -31
  323. package/src/plugins/builtin/broker-trading/overview-orders.tsx +9 -3
  324. package/src/plugins/builtin/broker-trading/pane-model.ts +30 -2
  325. package/src/plugins/builtin/broker-trading/pane.tsx +110 -23
  326. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +36 -7
  327. package/src/plugins/builtin/broker-trading/ticket.tsx +112 -31
  328. package/src/plugins/builtin/buildout/cells.tsx +4 -0
  329. package/src/plugins/builtin/buildout/detail/index.tsx +5 -1
  330. package/src/plugins/builtin/buildout/detail/ui.tsx +7 -4
  331. package/src/plugins/builtin/buildout/pane/index.tsx +25 -12
  332. package/src/plugins/builtin/buildout/pane/status.tsx +0 -4
  333. package/src/plugins/builtin/builtin-plugin-meta.ts +1 -1
  334. package/src/plugins/builtin/catalysts/detail.tsx +3 -2
  335. package/src/plugins/builtin/catalysts/index.tsx +1 -1
  336. package/src/plugins/builtin/catalysts/model.ts +49 -4
  337. package/src/plugins/builtin/catalysts/pane.tsx +6 -8
  338. package/src/plugins/builtin/central-bank-rates/client.ts +5 -0
  339. package/src/plugins/builtin/central-bank-rates/headless.ts +36 -10
  340. package/src/plugins/builtin/central-bank-rates/model.ts +33 -8
  341. package/src/plugins/builtin/changelog/index.tsx +1 -0
  342. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +1 -0
  343. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +2 -1
  344. package/src/plugins/builtin/chart-composer/chart-spec.ts +7 -0
  345. package/src/plugins/builtin/chart-composer/cli-options.ts +2 -5
  346. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  347. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -0
  348. package/src/plugins/builtin/chart-composer/editor.tsx +4 -1
  349. package/src/plugins/builtin/chart-composer/headless-schema.ts +28 -26
  350. package/src/plugins/builtin/chart-composer/headless.ts +253 -35
  351. package/src/plugins/builtin/chart-composer/index.tsx +12 -2
  352. package/src/plugins/builtin/chart-composer/leverage-overlay.ts +142 -0
  353. package/src/plugins/builtin/chart-composer/pane.tsx +127 -28
  354. package/src/plugins/builtin/chart-composer/series-catalog.ts +1 -0
  355. package/src/plugins/builtin/chart-composer/series-expression.ts +65 -6
  356. package/src/plugins/builtin/chart-composer/session-history.ts +15 -21
  357. package/src/plugins/builtin/chart-composer/session-range.ts +4 -2
  358. package/src/plugins/builtin/chart-composer/settings.ts +31 -19
  359. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +5 -6
  360. package/src/plugins/builtin/chart-composer/viewport-labels.ts +18 -8
  361. package/src/plugins/builtin/chat/attachments/model.ts +3 -1
  362. package/src/plugins/builtin/chat/content/index.tsx +1 -1
  363. package/src/plugins/builtin/chat/pane-links/open.ts +1 -1
  364. package/src/plugins/builtin/chat/people.ts +1 -1
  365. package/src/plugins/builtin/clinical-trials/client.ts +5 -2
  366. package/src/plugins/builtin/clinical-trials/model.ts +13 -4
  367. package/src/plugins/builtin/clinical-trials/pane.tsx +32 -12
  368. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +17 -0
  369. package/src/plugins/builtin/cloud/askg/controller.ts +1 -1
  370. package/src/plugins/builtin/cloud/askg/lock-note.ts +1 -1
  371. package/src/plugins/builtin/cloud/askg/tool-display.ts +1 -1
  372. package/src/plugins/builtin/cloud/auth-commands.ts +3 -3
  373. package/src/plugins/builtin/cloud/team/pane-sections.tsx +105 -47
  374. package/src/plugins/builtin/cloud/team/pane-ui.tsx +42 -4
  375. package/src/plugins/builtin/cloud/team/pane.tsx +204 -64
  376. package/src/plugins/builtin/cloud/thesis/store.ts +45 -22
  377. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  378. package/src/plugins/builtin/commodities-energy/index.tsx +29 -2
  379. package/src/plugins/builtin/commodities-energy/view-state.ts +2 -0
  380. package/src/plugins/builtin/commodity-balances/pane.tsx +11 -4
  381. package/src/plugins/builtin/company-attention/app-rank.tsx +7 -5
  382. package/src/plugins/builtin/company-attention/building-model.ts +222 -0
  383. package/src/plugins/builtin/company-attention/building.tsx +172 -0
  384. package/src/plugins/builtin/company-attention/client.ts +117 -1
  385. package/src/plugins/builtin/company-attention/evidence.ts +16 -3
  386. package/src/plugins/builtin/company-attention/headless.ts +101 -5
  387. package/src/plugins/builtin/company-attention/index.tsx +6 -3
  388. package/src/plugins/builtin/company-attention/model.ts +33 -7
  389. package/src/plugins/builtin/company-attention/moves-model.ts +253 -0
  390. package/src/plugins/builtin/company-attention/moves.tsx +159 -0
  391. package/src/plugins/builtin/company-attention/pane.tsx +79 -23
  392. package/src/plugins/builtin/company-attention/signals-model.ts +229 -0
  393. package/src/plugins/builtin/company-attention/signals.tsx +160 -0
  394. package/src/plugins/builtin/company-kpis/headless.ts +3 -2
  395. package/src/plugins/builtin/company-kpis/model.ts +2 -2
  396. package/src/plugins/builtin/company-kpis/pane.tsx +1 -1
  397. package/src/plugins/builtin/composite-plugins.ts +2 -1
  398. package/src/plugins/builtin/congress-trades/headless.ts +1 -1
  399. package/src/plugins/builtin/connections/index.ts +1 -0
  400. package/src/plugins/builtin/correlation/headless-schema.ts +3 -2
  401. package/src/plugins/builtin/correlation/headless.ts +5 -3
  402. package/src/plugins/builtin/cot/headless.ts +28 -6
  403. package/src/plugins/builtin/cot/model.ts +12 -0
  404. package/src/plugins/builtin/cot/pane.tsx +3 -2
  405. package/src/plugins/builtin/cpi/headless.ts +5 -1
  406. package/src/plugins/builtin/cpi/pane.tsx +11 -9
  407. package/src/plugins/builtin/credit-boards/model.ts +2 -0
  408. package/src/plugins/builtin/credit-documents/headless.ts +1 -1
  409. package/src/plugins/builtin/crypto-board/client.ts +86 -0
  410. package/src/plugins/builtin/crypto-board/defi-view.tsx +218 -0
  411. package/src/plugins/builtin/crypto-board/defi.ts +239 -0
  412. package/src/plugins/builtin/crypto-board/headless.ts +117 -6
  413. package/src/plugins/builtin/crypto-board/index.tsx +8 -3
  414. package/src/plugins/builtin/crypto-board/model.ts +40 -8
  415. package/src/plugins/builtin/crypto-board/pane.tsx +55 -21
  416. package/src/plugins/builtin/crypto-board/series.ts +19 -1
  417. package/src/plugins/builtin/crypto-etf-flows/client.ts +60 -0
  418. package/src/plugins/builtin/crypto-etf-flows/headless.ts +132 -0
  419. package/src/plugins/builtin/crypto-etf-flows/index.tsx +44 -0
  420. package/src/plugins/builtin/crypto-etf-flows/model.ts +121 -0
  421. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +193 -0
  422. package/src/plugins/builtin/debug/index.tsx +1 -0
  423. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  424. package/src/plugins/builtin/docs/index.tsx +1 -1
  425. package/src/plugins/builtin/docs/pane.tsx +2 -2
  426. package/src/plugins/builtin/docs/targets.ts +1 -1
  427. package/src/plugins/builtin/doe/client.ts +5 -1
  428. package/src/plugins/builtin/doe/headless.ts +12 -6
  429. package/src/plugins/builtin/doe/model.ts +43 -6
  430. package/src/plugins/builtin/doe/pane.tsx +18 -11
  431. package/src/plugins/builtin/earnings/board-model.ts +13 -6
  432. package/src/plugins/builtin/earnings/board-table.ts +4 -1
  433. package/src/plugins/builtin/earnings/board.tsx +253 -50
  434. package/src/plugins/builtin/earnings/calendar-model.ts +188 -0
  435. package/src/plugins/builtin/earnings/calendar.tsx +338 -0
  436. package/src/plugins/builtin/earnings/client.ts +4 -3
  437. package/src/plugins/builtin/earnings/headless.ts +53 -15
  438. package/src/plugins/builtin/earnings/history-model.ts +53 -36
  439. package/src/plugins/builtin/earnings/index.tsx +15 -22
  440. package/src/plugins/builtin/earnings/model.ts +17 -12
  441. package/src/plugins/builtin/earnings/scope.ts +89 -0
  442. package/src/plugins/builtin/earnings-calls/calendar.tsx +310 -0
  443. package/src/plugins/builtin/earnings-calls/data.ts +146 -2
  444. package/src/plugins/builtin/earnings-calls/headless.ts +151 -9
  445. package/src/plugins/builtin/earnings-calls/index.tsx +3 -3
  446. package/src/plugins/builtin/earnings-calls/lock-note.ts +1 -1
  447. package/src/plugins/builtin/earnings-calls/pane.tsx +149 -31
  448. package/src/plugins/builtin/earnings-calls/upcoming.ts +195 -0
  449. package/src/plugins/builtin/earnings-ripple/pane.tsx +10 -5
  450. package/src/plugins/builtin/econ/calendar-model.ts +132 -20
  451. package/src/plugins/builtin/econ/detail-view.tsx +48 -10
  452. package/src/plugins/builtin/econ/fred-catalog.ts +23 -6
  453. package/src/plugins/builtin/econ/fred-series-map.ts +4 -0
  454. package/src/plugins/builtin/econ/index.tsx +112 -25
  455. package/src/plugins/builtin/econ/print-watch.ts +2 -1
  456. package/src/plugins/builtin/econ/types.ts +32 -1
  457. package/src/plugins/builtin/econ-statistics/client.ts +16 -3
  458. package/src/plugins/builtin/econ-statistics/defs.ts +4 -0
  459. package/src/plugins/builtin/econ-statistics/detail.tsx +2 -2
  460. package/src/plugins/builtin/econ-statistics/headless.ts +13 -2
  461. package/src/plugins/builtin/econ-statistics/index.tsx +3 -3
  462. package/src/plugins/builtin/econ-statistics/pane.tsx +2 -1
  463. package/src/plugins/builtin/econ-statistics/stats.ts +103 -0
  464. package/src/plugins/builtin/econ-statistics/view.ts +16 -1
  465. package/src/plugins/builtin/equity-screener/client.ts +21 -3
  466. package/src/plugins/builtin/equity-screener/headless.ts +68 -6
  467. package/src/plugins/builtin/equity-screener/live.ts +1 -1
  468. package/src/plugins/builtin/equity-screener/model.ts +6 -0
  469. package/src/plugins/builtin/equity-screener/pane.tsx +45 -10
  470. package/src/plugins/builtin/equity-screener/screen-check.ts +90 -0
  471. package/src/plugins/builtin/equity-screener/ticker-check.tsx +148 -0
  472. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -1
  473. package/src/plugins/builtin/estimate-revisions/lock-note.ts +3 -3
  474. package/src/plugins/builtin/estimate-revisions/model.ts +8 -10
  475. package/src/plugins/builtin/exposure/headless.ts +51 -6
  476. package/src/plugins/builtin/exposure/index.tsx +28 -2
  477. package/src/plugins/builtin/exposure/model.ts +7 -0
  478. package/src/plugins/builtin/exposure/pane.tsx +6 -1
  479. package/src/plugins/builtin/fear-greed/index.tsx +1 -1
  480. package/src/plugins/builtin/filing-events/index.tsx +1 -1
  481. package/src/plugins/builtin/futures/model.ts +10 -9
  482. package/src/plugins/builtin/futures/returns.ts +8 -36
  483. package/src/plugins/builtin/futures/table.tsx +1 -1
  484. package/src/plugins/builtin/futures-curve/client.ts +40 -9
  485. package/src/plugins/builtin/futures-curve/headless.ts +39 -17
  486. package/src/plugins/builtin/futures-curve/model.ts +258 -69
  487. package/src/plugins/builtin/futures-curve/pane.tsx +46 -17
  488. package/src/plugins/builtin/fx-matrix/client.ts +15 -8
  489. package/src/plugins/builtin/fx-matrix/headless.ts +32 -14
  490. package/src/plugins/builtin/fx-matrix/index.tsx +13 -11
  491. package/src/plugins/builtin/fx-matrix/live-legs.ts +22 -0
  492. package/src/plugins/builtin/fx-matrix/settings.ts +2 -1
  493. package/src/plugins/builtin/gpu/headless.ts +9 -4
  494. package/src/plugins/builtin/help/index.tsx +13 -3
  495. package/src/plugins/builtin/holders/beneficial-model.ts +8 -11
  496. package/src/plugins/builtin/holders/beneficial-report.ts +6 -5
  497. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  498. package/src/plugins/builtin/holders/client.ts +13 -1
  499. package/src/plugins/builtin/holders/format.ts +28 -17
  500. package/src/plugins/builtin/holders/headless.ts +46 -7
  501. package/src/plugins/builtin/holders/index-holders.ts +264 -0
  502. package/src/plugins/builtin/holders/pane.tsx +79 -15
  503. package/src/plugins/builtin/holders/report-rows.ts +35 -7
  504. package/src/plugins/builtin/holders/table-model.ts +2 -1
  505. package/src/plugins/builtin/holders/treemap.tsx +2 -2
  506. package/src/plugins/builtin/insider/headless.ts +1 -1
  507. package/src/plugins/builtin/insider/signals-client.ts +1 -1
  508. package/src/plugins/builtin/insider/signals.tsx +1 -1
  509. package/src/plugins/builtin/ipo-calendar/pane.tsx +7 -4
  510. package/src/plugins/builtin/iv-history/client.ts +20 -0
  511. package/src/plugins/builtin/iv-history/headless.ts +4 -4
  512. package/src/plugins/builtin/iv-history/index.tsx +1 -1
  513. package/src/plugins/builtin/iv-history/model.ts +13 -4
  514. package/src/plugins/builtin/iv-history/series.ts +3 -1
  515. package/src/plugins/builtin/jobs/lock-note.ts +1 -1
  516. package/src/plugins/builtin/jobs/model.ts +4 -15
  517. package/src/plugins/builtin/jobs/pane.tsx +7 -5
  518. package/src/plugins/builtin/jobs/share-bars.tsx +11 -5
  519. package/src/plugins/builtin/jodi/pane.tsx +10 -4
  520. package/src/plugins/builtin/layout-manager/index.tsx +1 -0
  521. package/src/plugins/builtin/macro-day/headless.ts +20 -7
  522. package/src/plugins/builtin/macro-day/index.tsx +3 -2
  523. package/src/plugins/builtin/macro-day/model.ts +29 -8
  524. package/src/plugins/builtin/macro-day/pane.tsx +46 -10
  525. package/src/plugins/builtin/macro-day/releases.ts +55 -13
  526. package/src/plugins/builtin/market-heatmap/data.ts +9 -2
  527. package/src/plugins/builtin/market-heatmap/evidence.ts +1 -1
  528. package/src/plugins/builtin/market-heatmap/headless.ts +9 -6
  529. package/src/plugins/builtin/market-heatmap/index.tsx +3 -3
  530. package/src/plugins/builtin/market-heatmap/model.ts +10 -7
  531. package/src/plugins/builtin/market-movers/client.ts +33 -19
  532. package/src/plugins/builtin/market-movers/headless.ts +8 -2
  533. package/src/plugins/builtin/market-movers/index.tsx +16 -10
  534. package/src/plugins/builtin/market-movers/screener.ts +29 -8
  535. package/src/plugins/builtin/market-movers/session-body.tsx +4 -2
  536. package/src/plugins/builtin/market-movers/session-table.ts +4 -2
  537. package/src/plugins/builtin/market-movers/table.ts +4 -1
  538. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  539. package/src/plugins/builtin/market-valuation/view.ts +5 -0
  540. package/src/plugins/builtin/members/headless.ts +6 -5
  541. package/src/plugins/builtin/members/pane.tsx +11 -4
  542. package/src/plugins/builtin/mna/client.ts +1 -0
  543. package/src/plugins/builtin/mna/detail.tsx +20 -32
  544. package/src/plugins/builtin/mna/distress/detail.tsx +16 -4
  545. package/src/plugins/builtin/mna/distress/model.ts +3 -2
  546. package/src/plugins/builtin/mna/distress/pane.tsx +7 -4
  547. package/src/plugins/builtin/mna/headless.ts +6 -13
  548. package/src/plugins/builtin/mna/model.ts +93 -15
  549. package/src/plugins/builtin/mna/pane.tsx +5 -4
  550. package/src/plugins/builtin/news/headless.ts +12 -6
  551. package/src/plugins/builtin/news/index.tsx +10 -1
  552. package/src/plugins/builtin/news/wire/index.tsx +3 -2
  553. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +26 -10
  554. package/src/plugins/builtin/news/wire/news/query-presets.ts +6 -2
  555. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -3
  556. package/src/plugins/builtin/notes/cli.ts +49 -14
  557. package/src/plugins/builtin/openfda/pane.tsx +1 -1
  558. package/src/plugins/builtin/options/carry.ts +2 -2
  559. package/src/plugins/builtin/options/index.tsx +1 -1
  560. package/src/plugins/builtin/options/view.tsx +10 -10
  561. package/src/plugins/builtin/options-calculator/headless.ts +46 -7
  562. package/src/plugins/builtin/options-scenario/client.ts +123 -28
  563. package/src/plugins/builtin/options-scenario/collar.ts +8 -6
  564. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +127 -0
  565. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +165 -0
  566. package/src/plugins/builtin/options-scenario/csp-screen.ts +258 -0
  567. package/src/plugins/builtin/options-scenario/csp.ts +101 -21
  568. package/src/plugins/builtin/options-scenario/headless.ts +36 -15
  569. package/src/plugins/builtin/options-scenario/hedge.ts +20 -14
  570. package/src/plugins/builtin/options-scenario/index.tsx +28 -3
  571. package/src/plugins/builtin/options-scenario/model.ts +13 -0
  572. package/src/plugins/builtin/options-scenario/pane.tsx +9 -5
  573. package/src/plugins/builtin/perps/equity-row.tsx +11 -3
  574. package/src/plugins/builtin/perps/freshness.ts +8 -5
  575. package/src/plugins/builtin/perps/headless.ts +3 -1
  576. package/src/plugins/builtin/perps/list-perpetuals.ts +25 -4
  577. package/src/plugins/builtin/perps/live-rows.ts +161 -0
  578. package/src/plugins/builtin/perps/markets-section.tsx +154 -0
  579. package/src/plugins/builtin/perps/pane.tsx +36 -17
  580. package/src/plugins/builtin/perps/venue-markets.ts +96 -0
  581. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -1
  582. package/src/plugins/builtin/plugin-marketplace/pane.tsx +143 -59
  583. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +72 -24
  584. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +158 -141
  585. package/src/plugins/builtin/portfolio-list/cli/render.ts +49 -20
  586. package/src/plugins/builtin/portfolio-list/cli/shared.ts +3 -3
  587. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +29 -24
  588. package/src/plugins/builtin/portfolio-list/headless.ts +6 -3
  589. package/src/plugins/builtin/portfolio-list/income.ts +13 -2
  590. package/src/plugins/builtin/portfolio-list/index.tsx +9 -2
  591. package/src/plugins/builtin/portfolio-list/pane/index.tsx +21 -8
  592. package/src/plugins/builtin/portfolio-list/pane/purchase-date-fx.ts +46 -0
  593. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +16 -9
  594. package/src/plugins/builtin/portfolio-list/summary/totals.ts +2 -15
  595. package/src/plugins/builtin/power/client.ts +4 -1
  596. package/src/plugins/builtin/power/evidence.ts +22 -7
  597. package/src/plugins/builtin/power/gas.tsx +5 -1
  598. package/src/plugins/builtin/power/pane.tsx +8 -1
  599. package/src/plugins/builtin/power/prices.tsx +10 -3
  600. package/src/plugins/builtin/research/analyst-headless.ts +25 -8
  601. package/src/plugins/builtin/research/analyst-model.ts +68 -3
  602. package/src/plugins/builtin/research/corporate-actions-pane.tsx +63 -14
  603. package/src/plugins/builtin/research/event-model.ts +3 -1
  604. package/src/plugins/builtin/research/event-sources.ts +12 -4
  605. package/src/plugins/builtin/research/index.tsx +2 -1
  606. package/src/plugins/builtin/research/relative-valuation-basis.ts +58 -0
  607. package/src/plugins/builtin/research/relative-valuation-columns.ts +31 -2
  608. package/src/plugins/builtin/research/relative-valuation-currency.ts +12 -5
  609. package/src/plugins/builtin/research/relative-valuation-headless.ts +84 -26
  610. package/src/plugins/builtin/research/relative-valuation-model.ts +88 -19
  611. package/src/plugins/builtin/research/relative-valuation-pane.tsx +76 -9
  612. package/src/plugins/builtin/research/relative-valuation-peers.ts +1 -1
  613. package/src/plugins/builtin/reverse-dcf/model.ts +6 -2
  614. package/src/plugins/builtin/risk-factors/index.tsx +2 -2
  615. package/src/plugins/builtin/scanner/lock-note.ts +1 -1
  616. package/src/plugins/builtin/sec/forms.ts +13 -0
  617. package/src/plugins/builtin/sec/fund-abroad.ts +32 -0
  618. package/src/plugins/builtin/sec/fund-facts.ts +68 -8
  619. package/src/plugins/builtin/sec/headless.ts +85 -8
  620. package/src/plugins/builtin/sec/index.tsx +58 -12
  621. package/src/plugins/builtin/sectors/client.ts +2 -1
  622. package/src/plugins/builtin/sectors/sector-model.ts +11 -15
  623. package/src/plugins/builtin/series-inventory.ts +2 -0
  624. package/src/plugins/builtin/shared/broker-reconnect.ts +107 -22
  625. package/src/plugins/builtin/shared/cloud-upgrade.ts +34 -6
  626. package/src/plugins/builtin/shared/earnings-calendar.ts +375 -0
  627. package/src/plugins/builtin/{earnings/estimate-basis.ts → shared/earnings-estimate-basis.ts} +1 -1
  628. package/src/plugins/builtin/shared/earnings-report.ts +3 -3
  629. package/src/plugins/builtin/shared/fx-rate-freshness.ts +23 -1
  630. package/src/plugins/builtin/shared/job-posting.ts +129 -0
  631. package/src/plugins/builtin/shared/pro-lock-note.ts +3 -3
  632. package/src/plugins/builtin/shared/report-freshness.ts +3 -0
  633. package/src/plugins/builtin/shared/research-trading.tsx +21 -0
  634. package/src/plugins/builtin/shared/series-cache.ts +2 -0
  635. package/src/plugins/builtin/shared/ticker-surface.ts +2 -1
  636. package/src/plugins/builtin/short-interest/client.ts +2 -0
  637. package/src/plugins/builtin/short-interest/headless.ts +41 -12
  638. package/src/plugins/builtin/short-interest/index.tsx +23 -1
  639. package/src/plugins/builtin/short-interest/model.ts +70 -19
  640. package/src/plugins/builtin/short-interest/pane.tsx +19 -2
  641. package/src/plugins/builtin/short-interest/publications-client.ts +82 -0
  642. package/src/plugins/builtin/short-interest/publications-headless.ts +29 -0
  643. package/src/plugins/builtin/short-interest/publications-model.ts +50 -0
  644. package/src/plugins/builtin/short-interest/publications-pane.tsx +66 -0
  645. package/src/plugins/builtin/short-interest/schedule.ts +35 -2
  646. package/src/plugins/builtin/short-interest/surface.tsx +11 -4
  647. package/src/plugins/builtin/short-interest/types.ts +9 -0
  648. package/src/plugins/builtin/short-interest/watch-client.ts +14 -8
  649. package/src/plugins/builtin/short-interest/watch-model.ts +12 -25
  650. package/src/plugins/builtin/short-interest/watch-pane.tsx +5 -1
  651. package/src/plugins/builtin/supply-chain/flow.tsx +33 -9
  652. package/src/plugins/builtin/supply-chain/model.ts +51 -3
  653. package/src/plugins/builtin/supply-chain/pane.tsx +78 -39
  654. package/src/plugins/builtin/supply-chain/row-evidence.tsx +1 -1
  655. package/src/plugins/builtin/themes/headless.ts +8 -5
  656. package/src/plugins/builtin/themes/model.ts +14 -4
  657. package/src/plugins/builtin/themes/pane.tsx +24 -5
  658. package/src/plugins/builtin/thirteenf/headless.ts +5 -5
  659. package/src/plugins/builtin/thirteenf/signals.ts +3 -1
  660. package/src/plugins/builtin/ticker-detail/adr-pair/headless.ts +123 -0
  661. package/src/plugins/builtin/ticker-detail/adr-pair/model.ts +132 -0
  662. package/src/plugins/builtin/ticker-detail/adr-pair/tab.tsx +87 -0
  663. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +40 -26
  664. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +8 -4
  665. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +67 -3
  666. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +112 -5
  667. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +17 -2
  668. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +21 -7
  669. package/src/plugins/builtin/ticker-detail/financials/model.ts +36 -1
  670. package/src/plugins/builtin/ticker-detail/financials/offerings.ts +56 -0
  671. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +62 -13
  672. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +30 -4
  673. package/src/plugins/builtin/ticker-detail/headless-schema.ts +14 -2
  674. package/src/plugins/builtin/ticker-detail/headless.ts +148 -35
  675. package/src/plugins/builtin/ticker-detail/index.tsx +37 -5
  676. package/src/plugins/builtin/ticker-detail/overview/model.ts +65 -10
  677. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +9 -1
  678. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +68 -11
  679. package/src/plugins/builtin/ticker-detail/pane.tsx +11 -0
  680. package/src/plugins/builtin/ticker-detail/quote-footer.ts +8 -3
  681. package/src/plugins/builtin/ticker-detail/research-tabs.ts +11 -0
  682. package/src/plugins/builtin/vol-surface/client.ts +5 -4
  683. package/src/plugins/builtin/vol-surface/headless.ts +12 -3
  684. package/src/plugins/builtin/vol-surface/model.ts +4 -2
  685. package/src/plugins/builtin/volatility/model.ts +5 -10
  686. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +4 -3
  687. package/src/plugins/builtin/world-venue-map/basemap.ts +3 -1
  688. package/src/plugins/builtin/world-venue-map/chokepoint-chart.ts +40 -0
  689. package/src/plugins/builtin/world-venue-map/evidence.ts +1 -1
  690. package/src/plugins/builtin/world-venue-map/geo-series.ts +90 -11
  691. package/src/plugins/builtin/world-venue-map/headless.ts +1 -1
  692. package/src/plugins/builtin/world-venue-map/index.ts +3 -26
  693. package/src/plugins/builtin/world-venue-map/layers.ts +1 -1
  694. package/src/plugins/builtin/world-venue-map/map.tsx +2 -1
  695. package/src/plugins/builtin/world-venue-map/pane.tsx +4 -2
  696. package/src/plugins/builtin/world-venue-map/venue-header.tsx +15 -4
  697. package/src/plugins/builtin/yield-curve/client.ts +25 -0
  698. package/src/plugins/builtin/yield-curve/curves.ts +7 -4
  699. package/src/plugins/builtin/yield-curve/headless.ts +38 -4
  700. package/src/plugins/builtin/yield-curve/history.ts +4 -3
  701. package/src/plugins/builtin/yield-curve/index.tsx +12 -6
  702. package/src/plugins/builtin/yield-curve/swaps-model.ts +93 -0
  703. package/src/plugins/builtin/yield-curve/swaps.tsx +99 -0
  704. package/src/plugins/bundle.ts +1 -1
  705. package/src/plugins/catalog-browser.ts +2 -1
  706. package/src/plugins/compat.ts +3 -3
  707. package/src/plugins/dependencies.ts +75 -14
  708. package/src/plugins/extracted-plugins.ts +132 -34
  709. package/src/plugins/host-link.ts +149 -34
  710. package/src/plugins/installer.ts +35 -10
  711. package/src/plugins/loader.ts +206 -38
  712. package/src/plugins/manager-node.ts +2 -2
  713. package/src/plugins/marketplace/deactivation.ts +22 -0
  714. package/src/plugins/marketplace/model.ts +2 -2
  715. package/src/plugins/marketplace/packs.ts +1 -1
  716. package/src/plugins/ownership.ts +1 -1
  717. package/src/plugins/peer-retry.ts +155 -0
  718. package/src/plugins/registry/contributions.ts +3 -1
  719. package/src/plugins/registry/index.ts +4 -3
  720. package/src/plugins/registry/plugin-state.ts +3 -0
  721. package/src/plugins/seed-notice.ts +20 -0
  722. package/src/plugins/shortcut-argument.ts +39 -0
  723. package/src/portfolio/allocation.ts +1 -1
  724. package/src/portfolio/collection-name.ts +31 -0
  725. package/src/portfolio/column-values.ts +10 -8
  726. package/src/portfolio/cost-fx.ts +296 -19
  727. package/src/portfolio/manual-lots.ts +75 -0
  728. package/src/portfolio/mutations.ts +60 -22
  729. package/src/portfolio/suffixed-listings.ts +209 -0
  730. package/src/portfolio/totals-currency.ts +24 -0
  731. package/src/public/__snapshots__/public-api.test.ts.snap +14 -0
  732. package/src/public/time-series.ts +11 -8
  733. package/src/remote/schema.ts +1 -1
  734. package/src/renderers/browser/config-host.ts +3 -0
  735. package/src/renderers/browser/main.tsx +23 -3
  736. package/src/renderers/dom/boot-frame-page.ts +187 -0
  737. package/src/renderers/dom/boot-frame.ts +237 -0
  738. package/src/renderers/dom/build-assets.ts +46 -8
  739. package/src/renderers/dom/dom-ui-host.tsx +4 -29
  740. package/src/renderers/dom/host/ascii-text.ts +16 -2
  741. package/src/renderers/dom/host/box.tsx +43 -2
  742. package/src/renderers/dom/host/chart-surface.tsx +2 -0
  743. package/src/renderers/dom/host/hls.chunk.ts +3 -0
  744. package/src/renderers/dom/host/image-surface.tsx +49 -0
  745. package/src/renderers/dom/host/media-surface.tsx +24 -18
  746. package/src/renderers/dom/host/mouse.ts +30 -0
  747. package/src/renderers/dom/host/size-observer.ts +1 -1
  748. package/src/renderers/dom/host/style.ts +1 -1
  749. package/src/renderers/dom/styles.css +76 -0
  750. package/src/renderers/dom/test-utils.tsx +2 -1
  751. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -3
  752. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -0
  753. package/src/renderers/electrobun/bun/desktop/backend.ts +46 -3
  754. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +1 -1
  755. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -3
  756. package/src/renderers/electrobun/bun/desktop/initialization.ts +81 -18
  757. package/src/renderers/electrobun/bun/external-plugins.ts +29 -7
  758. package/src/renderers/electrobun/bun/index.ts +11 -1
  759. package/src/renderers/electrobun/bun/window/boot-frame.ts +67 -0
  760. package/src/renderers/electrobun/bun/window/create.ts +4 -1
  761. package/src/renderers/electrobun/shared/boot-frame.ts +7 -0
  762. package/src/renderers/electrobun/shared/protocol.ts +6 -1
  763. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  764. package/src/renderers/electrobun/view/config-host.ts +3 -0
  765. package/src/renderers/electrobun/view/fatal-screen.tsx +1 -1
  766. package/src/renderers/electrobun/view/main.tsx +30 -4
  767. package/src/renderers/electrobun/view/news-source.ts +5 -5
  768. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -1
  769. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  770. package/src/renderers/opentui/browser-open.ts +88 -0
  771. package/src/renderers/opentui/host.tsx +5 -14
  772. package/src/renderers/opentui/image/loader.ts +3 -2
  773. package/src/renderers/opentui/native-smoke.ts +10 -1
  774. package/src/renderers/opentui/pane-image/index.ts +2 -1
  775. package/src/renderers/opentui/render-lib.ts +253 -0
  776. package/src/renderers/opentui/render-timing-smoke.ts +1 -1
  777. package/src/renderers/opentui/start.tsx +1 -1
  778. package/src/renderers/share/layout-view.tsx +2 -2
  779. package/src/renderers/share/main.tsx +4 -4
  780. package/src/renderers/share/view.tsx +1 -1
  781. package/src/series/resolve.ts +22 -6
  782. package/src/series/standalone.ts +30 -2
  783. package/src/series/types.ts +8 -1
  784. package/src/shares/api.ts +4 -1
  785. package/src/shares/pane.ts +1 -1
  786. package/src/sources/gloomberb-cloud/fred-series.ts +46 -1
  787. package/src/sources/gloomberb-cloud/index.ts +32 -16
  788. package/src/sources/gloomberb-cloud/news.ts +39 -0
  789. package/src/sources/gloomberb-cloud/normalizers.ts +14 -1
  790. package/src/sources/gloomberb-cloud/perpetuals.ts +49 -1
  791. package/src/sources/listing-symbols.ts +37 -0
  792. package/src/sources/provider-errors.ts +16 -2
  793. package/src/sources/provider-router/financial-routes.ts +21 -3
  794. package/src/sources/provider-router/financials.ts +15 -2
  795. package/src/sources/provider-router/history.ts +30 -8
  796. package/src/sources/provider-router/primary.ts +1 -1
  797. package/src/state/app/bootstrap.ts +23 -0
  798. package/src/state/hooks/inline-tickers.ts +30 -5
  799. package/src/sync/controller.ts +1 -1
  800. package/src/sync/core-contributors.ts +46 -14
  801. package/src/team/collections-sync.ts +2 -1
  802. package/src/team/collections.ts +178 -17
  803. package/src/telemetry/crash-reports.ts +17 -4
  804. package/src/test-support/builtin-panes.ts +1 -1
  805. package/src/theme/colors.ts +3 -30
  806. package/src/theme/ui-tokens.ts +37 -1
  807. package/src/tickers/company-name-listing.ts +210 -33
  808. package/src/tickers/index-roots.ts +47 -0
  809. package/src/tickers/instrument-label.ts +5 -1
  810. package/src/tickers/metadata.ts +10 -0
  811. package/src/tickers/perpetual-listing.ts +5 -2
  812. package/src/tickers/search/home-listing.ts +75 -0
  813. package/src/tickers/search/index.ts +76 -27
  814. package/src/tickers/search/ranking.ts +54 -4
  815. package/src/tickers/search/upsert.ts +29 -0
  816. package/src/tickers/terminal-codes.ts +416 -40
  817. package/src/time-series/bar-aggregation.ts +243 -0
  818. package/src/time-series/base-series.ts +307 -0
  819. package/src/time-series/chart-bounds.ts +349 -0
  820. package/src/time-series/chart-seed.ts +142 -0
  821. package/src/time-series/economic.ts +5 -0
  822. package/src/time-series/fundamentals.ts +18 -3
  823. package/src/time-series/history-acquisition.ts +499 -0
  824. package/src/time-series/leverage.ts +100 -0
  825. package/src/time-series/market-resolution.ts +4 -2
  826. package/src/time-series/market.ts +2 -1
  827. package/src/time-series/price-comparison.ts +175 -30
  828. package/src/time-series/range.ts +9 -1
  829. package/src/time-series/resolution.ts +174 -16
  830. package/src/time-series/resolve-context.ts +157 -0
  831. package/src/time-series/resolve.ts +126 -1577
  832. package/src/time-series/series-name.ts +38 -0
  833. package/src/time-series/series-presentation.ts +208 -0
  834. package/src/time-series/spec.ts +11 -6
  835. package/src/time-series/studies.ts +109 -12
  836. package/src/time-series/tail-reconcile.ts +121 -0
  837. package/src/time-series/types.ts +29 -2
  838. package/src/time-series/use-chart-resolution.ts +4 -5
  839. package/src/types/broker.ts +10 -0
  840. package/src/types/config.ts +14 -0
  841. package/src/types/data-provider.ts +14 -0
  842. package/src/types/exchange-rate.ts +6 -0
  843. package/src/types/financials.ts +104 -7
  844. package/src/types/headless.ts +76 -6
  845. package/src/types/instrument.ts +3 -0
  846. package/src/types/plugin.ts +65 -8
  847. package/src/types/price-history.ts +2 -0
  848. package/src/types/ticker.ts +13 -0
  849. package/src/types/trading.ts +59 -1
  850. package/src/ui/ascii-font.ts +13 -1
  851. package/src/ui/host.tsx +6 -1
  852. package/src/updater/command-names.ts +175 -0
  853. package/src/updater/index.ts +10 -5
  854. package/src/utils/calendar-date.ts +32 -15
  855. package/src/utils/cash-runway.ts +241 -0
  856. package/src/utils/central-banks.ts +42 -0
  857. package/src/utils/cli-output.ts +259 -80
  858. package/src/utils/contract-month.ts +16 -0
  859. package/src/utils/crypto-pair.ts +61 -3
  860. package/src/utils/currency-units.ts +29 -0
  861. package/src/utils/datetime-format.ts +10 -0
  862. package/src/utils/debug-log.ts +1 -1
  863. package/src/utils/decimal-comma.ts +39 -6
  864. package/src/utils/exchange-announcements.ts +71 -0
  865. package/src/utils/exchange-rate-snapshot.ts +10 -0
  866. package/src/utils/financial-statements.ts +83 -4
  867. package/src/utils/format.ts +34 -16
  868. package/src/utils/fundamentals.ts +150 -34
  869. package/src/utils/fx-market-hours.ts +10 -1
  870. package/src/utils/latest-financial-period.ts +8 -0
  871. package/src/utils/linux-libc.ts +54 -0
  872. package/src/utils/market-capitalization.ts +111 -15
  873. package/src/utils/max-drawdown.ts +64 -0
  874. package/src/utils/network-failure.ts +83 -0
  875. package/src/utils/option-expiry.ts +139 -7
  876. package/src/utils/price-earnings.ts +11 -5
  877. package/src/utils/price-history.ts +18 -8
  878. package/src/utils/price-window.ts +131 -0
  879. package/src/utils/release-date.ts +47 -0
  880. package/src/utils/sec.ts +3 -0
  881. package/src/utils/throttled-fetch.ts +11 -4
  882. package/src/utils/trailing-eps.ts +67 -0
  883. package/src/utils/typed-text.ts +21 -0
  884. package/src/utils/utc-time.ts +24 -1
  885. package/src/version.ts +6 -0
  886. package/src/cli/pane-functions/screenshot.ts +0 -2122
@@ -6,15 +6,19 @@ import type { DataProvider } from "../../../types/data-provider";
6
6
  import type { CorporateActionsData, OptionsChain, Quote, TickerFinancials } from "../../../types/financials";
7
7
  import { canonicalExchange, parsePublicTickerKey } from "../../../utils/exchanges";
8
8
  import { numberErrorMessage } from "../../../utils/decimal-comma";
9
- import { expiryIsoDate, findListedExpiry, missingExpiryText } from "../../../utils/option-expiry";
9
+ import {
10
+ expiredExpiryText, expiryChoiceText, expiryDayLabel, expiryIsoDate, findListedExpiry, isLiveOptionExpiry, liveDefaultExpiry, liveOptionExpiries,
11
+ missingExpiryText, nextLiveExpiry,
12
+ } from "../../../utils/option-expiry";
10
13
  import { surfaceTreasuryRate } from "../vol-surface/model";
11
- import { daysToExpiryFrom, extractImpliedForward, optionMid, solveImpliedVolatility } from "../shared/volatility";
14
+ import { daysToExpiryFrom, extractImpliedForward, optionDelta, optionMid, solveImpliedVolatility, valueOption } from "../shared/volatility";
12
15
  import type { YieldPoint } from "../yield-curve/treasury-data";
13
16
  import { parseLegs, scenarioCurrency, validatePosition, type ScenarioControls, type ScenarioLeg, type ScenarioPosition } from "./model";
14
17
  import { loadModelDividendYield, type ModelDividendYield } from "../../../market-data/model-dividend-yield";
15
18
  import { abortable, abortError } from "../../../utils/async-deadline";
16
19
  import { extendedSession, modelSpotStamp, modelSpotStampVariants, type OptionModelSpot } from "../options/model-spot";
17
20
  import { errorMessage } from "../../../utils/errors";
21
+ import { dateOptionError, dateOptionFormatsOr, readCalendarDate } from "../../../utils/calendar-date";
18
22
 
19
23
  export interface ScenarioMarketSnapshot {
20
24
  symbol: string;
@@ -86,10 +90,13 @@ export async function loadScenarioMarket(
86
90
  const symbol = request.instrument.symbol.trim().toUpperCase();
87
91
  const options = { forceRefresh: request.forceRefresh };
88
92
  const warnings: string[] = [];
93
+ const exchange = request.instrument.exchange;
94
+ // An expiry whose last session has closed has no chain: the default one lists the live expiries to name instead.
95
+ const expired = request.expiration != null && !isLiveOptionExpiry(request.expiration, now, exchange);
89
96
  const settled = await abortable(Promise.allSettled([
90
97
  dependencies.loadQuote(request.instrument, options),
91
98
  dependencies.loadSnapshot(request.instrument, options),
92
- dependencies.loadOptions({ instrument: request.instrument, expirationDate: request.expiration }, options),
99
+ dependencies.loadOptions({ instrument: request.instrument, expirationDate: expired ? undefined : request.expiration }, options),
93
100
  dependencies.loadYieldCurve(),
94
101
  ]), request.signal, CANCELLED);
95
102
  if (request.signal?.aborted) throw abortError(CANCELLED);
@@ -125,31 +132,41 @@ export async function loadScenarioMarket(
125
132
  if (chain && !symbolMatches(chain.underlyingSymbol, symbol)) {
126
133
  warnings.push("Options chain does not match the requested ticker"); chain = null;
127
134
  }
128
- const expirationDates = [...new Set((chain?.expirationDates ?? []).filter((expiration) =>
129
- positive(expiration) && daysToExpiryFrom(expiration, now) > 0))].sort((a, b) => a - b);
135
+ const expirationDates = liveOptionExpiries(chain?.expirationDates ?? [], now, exchange);
130
136
  // A typed date is that day's UTC midnight; the chain's own stamp for the
131
137
  // expiry is matched by calendar date, as OMON matches it.
132
- const listedExpiration = request.expiration == null ? undefined : findListedExpiry(request.expiration, expirationDates);
138
+ const listedExpiration = request.expiration == null || expired ? undefined : findListedExpiry(request.expiration, expirationDates);
133
139
  const onExpiry = (value: OptionsChain, expiration: number) => [...value.calls, ...value.puts]
134
140
  .every((contract) => expiryIsoDate(contract.expiration) === expiryIsoDate(expiration));
135
- if (chain && listedExpiration != null && listedExpiration !== request.expiration && !chain.calls.length && !chain.puts.length) {
136
- // A source that keys chains by its exact stamp answered the midnight request empty.
137
- chainEntry = await abortable(dependencies.loadOptions({ instrument: request.instrument, expirationDate: listedExpiration }, options)
141
+ // A default chain on the expiry that lapsed at the last close gives way to the nearest live one.
142
+ const liveDefault = request.expiration == null && chain ? liveDefaultExpiry(chain, now, exchange) : undefined;
143
+ const reload = liveDefault ?? (chain && listedExpiration != null && listedExpiration !== request.expiration
144
+ && !chain.calls.length && !chain.puts.length ? listedExpiration : undefined);
145
+ if (reload != null) {
146
+ // Or a source that keys chains by its exact stamp answered the midnight request empty.
147
+ chainEntry = await abortable(dependencies.loadOptions({ instrument: request.instrument, expirationDate: reload }, options)
138
148
  .catch((error: unknown) => { warnings.push(`Options chain: ${errorMessage(error)}`); return null; }), request.signal, CANCELLED);
139
149
  if (request.signal?.aborted) throw abortError(CANCELLED);
140
150
  if (chainEntry?.error) warnings.push(`Options chain: ${chainEntry.error.message}`);
141
151
  chain = chainEntry ? resolveEntryValue(chainEntry) : null;
142
152
  }
143
153
  let missingExpiry: string | undefined;
144
- if (chain && request.expiration != null && listedExpiration == null) {
154
+ if (expired) {
155
+ missingExpiry = sentence(`expiry ${expiryIsoDate(request.expiration!)} for ${symbol} `
156
+ + expiredExpiryText(request.expiration!, expirationDates, now, { exchange, flag: "expiration" }));
157
+ warnings.push(missingExpiry); chain = null;
158
+ } else if (chain && request.expiration != null && listedExpiration == null) {
145
159
  missingExpiry = sentence(missingExpiryText(request.expiration, expirationDates, symbol));
146
160
  warnings.push(missingExpiry); chain = null;
147
- } else if (chain && listedExpiration != null && !onExpiry(chain, listedExpiration)) {
161
+ } else if (liveDefault === null) {
162
+ missingExpiry = `No live expiry listed for ${symbol}`;
163
+ warnings.push(missingExpiry); chain = null;
164
+ } else if (chain && (listedExpiration ?? liveDefault) != null && !onExpiry(chain, (listedExpiration ?? liveDefault)!)) {
148
165
  warnings.push("Selected expiration unavailable in the returned options chain"); chain = null;
149
166
  }
150
167
  if (!chain) warnings.push("Options chain unavailable");
151
168
  else if (chainEntry?.error || (chainEntry?.staleAt != null && chainEntry.staleAt <= now)) warnings.push("Options chain is stale");
152
- const rateExpiration = request.rateExpiration ?? request.expiration ?? expirationDates[0];
169
+ const rateExpiration = request.rateExpiration ?? (expired ? undefined : request.expiration) ?? expirationDates[0];
153
170
  const curve = curveResult.status === "fulfilled" ? curveResult.value : [];
154
171
  if (curveResult.status === "rejected") warnings.push(`Treasury: ${errorMessage(curveResult.reason)}`);
155
172
  const rate = surfaceTreasuryRate(curve, rateExpiration == null ? NaN : daysToExpiryFrom(rateExpiration, now) / 365);
@@ -225,13 +242,17 @@ function numericSetting(settings: Record<string, unknown>, key: string): number
225
242
  return Number(value);
226
243
  }
227
244
 
228
- /** Dates are explicit UTC ISO timestamps or UTC calendar dates. Milliseconds are accepted in persisted state. */
245
+ /** Dates are explicit UTC ISO timestamps or UTC calendar dates, written as a date option takes them. Milliseconds are accepted in persisted state. */
229
246
  function dateSetting(settings: Record<string, unknown>, key: string): number | undefined {
230
247
  const value = settings[key];
231
248
  if (!supplied(value)) return undefined;
232
249
  if (typeof value === "number" && positive(value) && Number.isFinite(new Date(value).getTime())) return value;
250
+ const day = typeof value === "string" ? readCalendarDate(value) : null;
251
+ if (day) return Date.parse(`${day}T00:00:00Z`);
233
252
  if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?Z)?$/.test(value)) {
234
- throw new Error(`${key} must be YYYY-MM-DD or an ISO timestamp ending in Z`);
253
+ throw new Error(dateOptionError(String(value), key === "asOf" ? "--as-of" : `--${key}`, {
254
+ subject: key === "asOf" ? "a valuation date" : "a scenario date", formats: dateOptionFormatsOr("an ISO timestamp ending in Z"),
255
+ }));
235
256
  }
236
257
  const date = Date.parse(value);
237
258
  if (!positive(date) || new Date(date).toISOString().slice(0, 10) !== value.slice(0, 10)) throw new Error(`${key} is not a valid date`);
@@ -266,6 +287,8 @@ export function scenarioPositionFromSettings(
266
287
  const strike = numericSetting(settings, "strike");
267
288
  if (strike != null && !(strike > 0)) throw new Error("strike must be positive");
268
289
  if (strike != null && !supplied(settings.strategy)) throw new Error("strike picks the strike of --strategy; add --strategy put, csp, vertical or straddle");
290
+ const delta = putDeltaSetting(settings);
291
+ if (delta != null && strike != null) throw new Error("delta and strike each pick the put; give one of them");
269
292
  const collarStrikes = { put: numericSetting(settings, "putStrike"), call: numericSetting(settings, "callStrike") };
270
293
  if ((collarStrikes.put != null || collarStrikes.call != null) && settings.strategy !== "collar") {
271
294
  throw new Error("put-strike and call-strike pick a collar's strikes; add --strategy collar");
@@ -288,8 +311,10 @@ export function scenarioPositionFromSettings(
288
311
  }
289
312
  if (spot == null) throw new Error("A current underlying price or explicit --spot is required");
290
313
  // Leg IVs are solved at the market's own spot and time, never at a what-if override.
291
- const pricing = market.spot != null && rate != null ? { spot: market.spot, asOf: market.asOf, rate } : null;
292
- const seeded = scenarioStrategyLegs(market.chain, spot, settings.strategy, pricing, strike, collarStrikes);
314
+ const pricing = market.spot != null && rate != null ? { spot: market.spot, asOf: market.asOf, rate, dividendYield: dividendYield ?? 0 } : null;
315
+ if (delta != null && !pricing) throw new Error("Choosing the put by delta needs the Treasury rate; supply --rate, or pick it with --strike");
316
+ const seeded = scenarioStrategyLegs(market.chain, spot, settings.strategy, pricing, strike, collarStrikes, delta,
317
+ unusableChainText(market, exchange));
293
318
  legs = seeded.legs;
294
319
  shareEntry = market.spot;
295
320
  // Mids priced off the parity forward are worth their cost only at the spot
@@ -326,6 +351,19 @@ export function scenarioPositionFromSettings(
326
351
  return position;
327
352
  }
328
353
 
354
+ /**
355
+ * `--delta`: the put a csp or put seed sells or buys, by the absolute delta it
356
+ * is nearest (0.30, or -0.30 as a put's own sign reads it); undefined when not set.
357
+ */
358
+ function putDeltaSetting(settings: Record<string, unknown>): number | undefined {
359
+ const value = numericSetting(settings, "delta");
360
+ if (value == null) return undefined;
361
+ if (settings.strategy !== "csp" && settings.strategy !== "put") throw new Error("delta picks the put of --strategy csp or put");
362
+ const delta = Math.abs(value);
363
+ if (!(delta > 0 && delta < 1)) throw new Error(`delta must be a put's delta between 0 and 1, such as 0.30; got ${String(settings.delta)}`);
364
+ return delta;
365
+ }
366
+
329
367
  /** `--shares`: a signed whole number of shares of the underlying, or undefined when not set. */
330
368
  function sharesSetting(settings: Record<string, unknown>): number | undefined {
331
369
  const value = settings.shares;
@@ -346,7 +384,7 @@ export const SCENARIO_STRATEGIES = ["vertical", "straddle", "put", "csp", "colla
346
384
  type ScenarioStrategy = typeof SCENARIO_STRATEGIES[number];
347
385
  const isScenarioStrategy = (value: unknown): value is ScenarioStrategy => SCENARIO_STRATEGIES.includes(value as ScenarioStrategy);
348
386
 
349
- interface StrategyPricing { spot: number; asOf: number; rate: number }
387
+ interface StrategyPricing { spot: number; asOf: number; rate: number; dividendYield: number }
350
388
 
351
389
  const usableVolatility = (value: unknown): value is number => positive(value) && value <= 5;
352
390
 
@@ -357,8 +395,8 @@ interface StrategyParity { forward: number; daysToExpiry: number }
357
395
  * at its quote midpoint, so each leg takes the IV that midpoint implies against
358
396
  * the expiry's put-call parity forward, with the time left at the market
359
397
  * timestamp OSA values at. This holds even when the chain carries a provider
360
- * IV measured at another time (a same-day IV solved when the chain was served,
361
- * hours after the after-hours print OSA values at). The forward comes from the
398
+ * IV measured at another time (a same-day IV solved at the close's quotes,
399
+ * hours before the after-hours print OSA values at). The forward comes from the
362
400
  * same quotes, so a spot that moved after they were taken (an after-hours or
363
401
  * pre-market print against the close's chain) does not skew the call against
364
402
  * the put. A leg whose midpoint cannot be solved (a spot too far from every
@@ -373,10 +411,12 @@ interface StrategyParity { forward: number; daysToExpiry: number }
373
411
  * to the next listed strike up, or a straddle at the anchor. A collar buys
374
412
  * the put at `collar.put` and sells the call at `collar.call` on the put's
375
413
  * expiry, each the quoted strike nearest 10% out of the money when omitted.
414
+ * A put or csp seed given a `delta` takes the put whose spot delta, valued at
415
+ * the IV its own midpoint implies, is nearest that delta's negative.
376
416
  */
377
417
  function scenarioStrategyLegs(
378
418
  chain: OptionsChain, spot: number, strategy: ScenarioStrategy, pricing: StrategyPricing | null, strike?: number,
379
- collar: { put?: number; call?: number } = {},
419
+ collar: { put?: number; call?: number } = {}, delta?: number, unusable: UnusableChainText = defaultUnusableChainText,
380
420
  ): { legs: ScenarioLeg[]; parity: StrategyParity | null } {
381
421
  const parity = new Map<number, StrategyParity | null>();
382
422
  const parityFor = (expiration: number) => {
@@ -406,15 +446,16 @@ function scenarioStrategyLegs(
406
446
  const calls = quoted(chain.calls, "call").sort(nearest);
407
447
  const puts = quoted(chain.puts, "put").sort(nearest);
408
448
  const missing = (candidates: { contract: { strike: number } }[], at = strike, what: string = strategy === "csp" ? "put to sell" : strategy) => {
409
- if (at == null) return new Error("The selected chain has no complete quoted strategy with usable IV");
449
+ if (at == null) return new Error(unusable("strategy"));
410
450
  const listed = [...new Set(candidates.map(({ contract }) => contract.strike))]
411
451
  .sort((a, b) => Math.abs(a - at) - Math.abs(b - at)).slice(0, 4).sort((a, b) => a - b);
412
- return new Error(`The selected chain has no complete quoted ${what} at strike ${at} with usable IV`
413
- + (listed.length ? `; nearest quoted strikes: ${listed.join(", ")}` : ""));
452
+ // Another strike on this expiry is the nearer fix; with none quoted, the next expiry is.
453
+ return new Error(listed.length ? `${unusable(`${what} at strike ${at}`, false)}; nearest quoted strikes: ${listed.join(", ")}`
454
+ : unusable(`${what} at strike ${at}`));
414
455
  };
415
- if (strategy === "collar") return collarLegs(calls, puts, spot, collar, missing, parityFor);
456
+ if (strategy === "collar") return collarLegs(calls, puts, spot, collar, missing, parityFor, unusable);
416
457
  if (strategy === "put" || strategy === "csp") {
417
- const put = puts.find(anchored);
458
+ const put = delta == null ? puts.find(anchored) : nearestDelta(puts, -delta, spot, pricing, parityFor);
418
459
  if (!put) throw missing(puts);
419
460
  return { legs: [{ id: put.contract.contractSymbol, side: "put", quantity: strategy === "csp" ? -1 : 1, strike: put.contract.strike,
420
461
  expiration: put.contract.expiration, price: put.mid, priceSource: "mid", volatility: put.volatility, multiplier: 100,
@@ -441,6 +482,60 @@ function scenarioStrategyLegs(
441
482
 
442
483
  interface QuotedContract { contract: OptionsChain["calls"][number]; mid: number; volatility: number; solved: boolean }
443
484
 
485
+ /** Why a chain seeds nothing: `what` it has no usable quote for, then the next live expiry to try unless `next` is false. */
486
+ type UnusableChainText = (what: string, next?: boolean) => string;
487
+ const defaultUnusableChainText: UnusableChainText = (what) => `The selected chain has no complete quoted ${what} with usable IV`;
488
+
489
+ /**
490
+ * The seed failure naming the expiry the chain is on and the next live one to
491
+ * ask for: `The 30 Oct chain has no complete quoted strategy with usable IV;
492
+ * try the next expiry, 6 Nov (--expiration 2026-11-06)`.
493
+ */
494
+ function unusableChainText(market: ScenarioMarketSnapshot, exchange: string | undefined): UnusableChainText {
495
+ const contracts = market.chain ? [...market.chain.calls, ...market.chain.puts] : [];
496
+ const tried = contracts[0]?.expiration;
497
+ if (tried == null) return defaultUnusableChainText;
498
+ const now = market.asOf;
499
+ const next = nextLiveExpiry(tried, market.expirationDates, now, exchange ?? market.exchange);
500
+ return (what, withNext = true) => `The ${expiryDayLabel(tried, now)} chain has no complete quoted ${what} with usable IV`
501
+ + (withNext && next != null ? `; try the next expiry, ${expiryChoiceText(next, now, "expiration")}` : "");
502
+ }
503
+
504
+ /**
505
+ * A put's spot delta at the IV it trades at: against the parity forward its
506
+ * IV was solved on when it was (forward delta, carried to spot), else at the
507
+ * seed's spot. Null when it cannot be valued.
508
+ */
509
+ function quotedPutDelta(quoted: QuotedContract, spot: number, pricing: StrategyPricing | null, parity: StrategyParity | null): number | null {
510
+ const { contract, volatility, solved } = quoted;
511
+ if (!pricing) return null;
512
+ if (solved && parity) {
513
+ // On the forward, with the dividend yield set to the rate so it carries no drift, as the IV was solved.
514
+ const forwardDelta = valueOption({ side: "put", spot: parity.forward, strike: contract.strike, daysToExpiry: parity.daysToExpiry,
515
+ rate: pricing.rate, dividendYield: pricing.rate, volatility }).delta;
516
+ return Number.isFinite(forwardDelta) ? forwardDelta * Math.exp((pricing.rate - pricing.dividendYield) * parity.daysToExpiry / 365) : null;
517
+ }
518
+ const years = daysToExpiryFrom(contract.expiration, pricing.asOf) / 365;
519
+ return optionDelta({ spot, years, volatility, rate: pricing.rate, dividendYield: pricing.dividendYield }, contract.strike, "put");
520
+ }
521
+
522
+ /** The put whose delta is nearest `target` (negative, as a put's delta reads), the lower strike on a tie; null when none can be valued. */
523
+ function nearestDelta(
524
+ puts: readonly QuotedContract[], target: number, spot: number, pricing: StrategyPricing | null,
525
+ parityFor: (expiration: number) => StrategyParity | null,
526
+ ): QuotedContract | null {
527
+ let best: { put: QuotedContract; distance: number } | null = null;
528
+ for (const put of puts) {
529
+ const delta = quotedPutDelta(put, spot, pricing, put.solved ? parityFor(put.contract.expiration) : null);
530
+ if (delta == null) continue;
531
+ const distance = Math.abs(delta - target);
532
+ if (!best || distance < best.distance - 1e-12 || (Math.abs(distance - best.distance) <= 1e-12 && put.contract.strike < best.put.contract.strike)) {
533
+ best = { put, distance };
534
+ }
535
+ }
536
+ return best?.put ?? null;
537
+ }
538
+
444
539
  /** How far out of the money a collar's put and call sit when their strikes are not given. */
445
540
  const COLLAR_DEFAULT_DISTANCE = 0.1;
446
541
 
@@ -448,18 +543,18 @@ const COLLAR_DEFAULT_DISTANCE = 0.1;
448
543
  function collarLegs(
449
544
  calls: QuotedContract[], puts: QuotedContract[], spot: number, strikes: { put?: number; call?: number },
450
545
  missing: (candidates: QuotedContract[], at: number | undefined, what: string) => Error,
451
- parityFor: (expiration: number) => StrategyParity | null,
546
+ parityFor: (expiration: number) => StrategyParity | null, unusable: UnusableChainText,
452
547
  ): { legs: ScenarioLeg[]; parity: StrategyParity | null } {
453
548
  const closest = (candidates: QuotedContract[], target: number) => candidates.toSorted((a, b) =>
454
549
  Math.abs(a.contract.strike - target) - Math.abs(b.contract.strike - target) || a.contract.strike - b.contract.strike);
455
550
  const at = (candidates: QuotedContract[], value: number) => candidates.filter(({ contract }) => Math.abs(contract.strike - value) < 1e-9);
456
551
  const put = strikes.put != null ? at(puts, strikes.put)[0]
457
552
  : closest(puts.filter(({ contract }) => strikes.call == null || contract.strike < strikes.call), spot * (1 - COLLAR_DEFAULT_DISTANCE))[0];
458
- if (!put) throw strikes.put != null ? missing(puts, strikes.put, "put to buy") : new Error("The selected chain has no complete quoted put to buy with usable IV");
553
+ if (!put) throw strikes.put != null ? missing(puts, strikes.put, "put to buy") : new Error(unusable("put to buy"));
459
554
  const sameExpiry = calls.filter(({ contract }) => contract.expiration === put.contract.expiration);
460
555
  const call = strikes.call != null ? at(sameExpiry, strikes.call)[0]
461
556
  : closest(sameExpiry.filter(({ contract }) => contract.strike > put.contract.strike), spot * (1 + COLLAR_DEFAULT_DISTANCE))[0];
462
- if (!call) throw strikes.call != null ? missing(sameExpiry, strikes.call, "call to sell") : new Error("The selected chain has no complete quoted call to sell above the put with usable IV");
557
+ if (!call) throw strikes.call != null ? missing(sameExpiry, strikes.call, "call to sell") : new Error(unusable("call to sell above the put"));
463
558
  if (call.contract.strike <= put.contract.strike) throw new Error("A collar's put strike must be below its call strike");
464
559
  if (put.contract.currency !== call.contract.currency) throw new Error("Strategy quote currencies differ");
465
560
  const legs = [[put, "put", 1], [call, "call", -1]] as const;
@@ -1,7 +1,7 @@
1
1
  import type { StatItem } from "../../../components";
2
2
  import type { HeadlessPaneEntry } from "../../../types/headless";
3
3
  import { formatHedgePrice } from "./hedge";
4
- import { UNKNOWN_CURRENCY, type ScenarioPosition } from "./model";
4
+ import { amountUnit, UNKNOWN_CURRENCY, type ScenarioPosition } from "./model";
5
5
 
6
6
  /**
7
7
  * A collar (docs/research-data.md#collar): one long put and one short call on
@@ -104,17 +104,19 @@ function boundText(collar: Collar, value: number | null, side: "put" | "call"):
104
104
  export function collarReportEntries(collar: Collar): HeadlessPaneEntry[] {
105
105
  const unit = unitOf(collar);
106
106
  const premium = Math.abs(collar.netPremium);
107
+ // Every figure is per share, so an export writes the number with that unit.
108
+ const perShare = { unit: `${amountUnit(collar.currency)} per share` };
107
109
  return [
108
- { key: "putBought", label: "Put bought", value: collar.put.price,
110
+ { key: "putBought", label: "Put bought", value: collar.put.price, ...perShare,
109
111
  formatted: `${strikeText(collar.put.strike)} put at ${formatHedgePrice(collar.put.price)} per share (${priceWord(collar.put)})` },
110
- { key: "callSold", label: "Call sold", value: collar.call.price,
112
+ { key: "callSold", label: "Call sold", value: collar.call.price, ...perShare,
111
113
  formatted: `${strikeText(collar.call.strike)} call at ${formatHedgePrice(collar.call.price)} per share (${priceWord(collar.call)})` },
112
- { key: "netPremium", label: "Net premium", value: collar.netPremium,
114
+ { key: "netPremium", label: "Net premium", value: collar.netPremium, ...perShare,
113
115
  formatted: collar.netPremium === 0 ? `0.00 per share (zero cost)`
114
116
  : `${formatHedgePrice(premium)} per share ${premiumWord(collar)} (${money(Math.abs(collar.netPremiumTotal))}${unit} `
115
117
  + `for ${collar.contracts.toLocaleString("en-US")} ${collar.contracts === 1 ? "contract" : "contracts"} each)` },
116
- { key: "floor", label: "Floor", value: collar.floor, formatted: boundText(collar, collar.floor, "put") },
117
- { key: "cap", label: "Cap", value: collar.cap, formatted: boundText(collar, collar.cap, "call") },
118
+ { key: "floor", label: "Floor", value: collar.floor, ...perShare, formatted: boundText(collar, collar.floor, "put") },
119
+ { key: "cap", label: "Cap", value: collar.cap, ...perShare, formatted: boundText(collar, collar.cap, "call") },
118
120
  ];
119
121
  }
120
122
 
@@ -0,0 +1,127 @@
1
+ import type { HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/plugin";
2
+ import { expirationOptionSeconds, OPTION_EXPIRATION_FORMAT, OPTION_EXPIRATION_PLACEHOLDER } from "../../../utils/option-expiry";
3
+ import { resolveHeadlessInstrument } from "../shared/headless-market-data";
4
+ import { createScenarioDependencies } from "./client";
5
+ import { formatHedgePrice } from "./hedge";
6
+ import { exportPercent } from "./model";
7
+ import {
8
+ CSP_SCREEN_DEFAULTS, CSP_SCREEN_LIMIT, cspEarningsText, cspScreenCriteriaText, loadCspScreen, readCspScreenCriteria,
9
+ type CspScreen, type CspScreenRow,
10
+ } from "./csp-screen";
11
+
12
+ const percentCell = (digits: number) => (value: unknown) => typeof value === "number" ? `${value.toFixed(digits)}%` : "--";
13
+ const priceCell = (value: unknown) => typeof value === "number" ? formatHedgePrice(value) : "--";
14
+ const strikeCell = (value: unknown) => typeof value === "number" ? String(value) : "--";
15
+
16
+ /** One name's row, flat, every percent in percent. */
17
+ export type CspScreenReportRow = {
18
+ rank: number | null; symbol: string; expiry: string | null; days: number | null; strike: number | null;
19
+ bid: number | null; ask: number | null; spread: number | null; wide: boolean | null; delta: number | null; otm: number | null;
20
+ midYield: number | null; bidYield: number | null; ivRank: number | null;
21
+ earnings: string; earningsBeforeExpiry: boolean | null; earningsDate: string | null; spot: number | null; note: string | null;
22
+ };
23
+
24
+ export function cspScreenReportRow(row: CspScreenRow): CspScreenReportRow {
25
+ const { csp } = row;
26
+ return {
27
+ rank: row.rank, symbol: row.symbol,
28
+ expiry: csp?.expiry ?? null, days: csp?.daysToExpiry ?? null, strike: csp?.strike ?? null,
29
+ bid: csp?.quote?.bid ?? null, ask: csp?.quote?.ask ?? null, spread: exportPercent(csp?.quote?.spreadOfMid), wide: csp?.quote ? row.wideSpread : null,
30
+ delta: row.delta, otm: exportPercent(csp?.outOfTheMoney), midYield: exportPercent(csp?.annualizedYield),
31
+ bidYield: exportPercent(csp?.quote?.bidAnnualizedYield), ivRank: row.ivRank,
32
+ earnings: cspEarningsText(csp), earningsBeforeExpiry: csp ? csp.earnings.beforeExpiry : null, earningsDate: csp?.earnings.date ?? null,
33
+ spot: csp?.spot ?? null, note: row.note,
34
+ };
35
+ }
36
+
37
+ const COLUMNS: HeadlessPaneColumn[] = [
38
+ { key: "rank", header: "#", align: "right", format: (value) => typeof value === "number" ? String(value) : "" },
39
+ { key: "symbol", header: "Symbol" },
40
+ { key: "expiry", header: "Expiry" },
41
+ { key: "days", header: "DTE", align: "right" },
42
+ { key: "strike", header: "Strike", align: "right", format: strikeCell },
43
+ { key: "bid", header: "Bid", align: "right", format: priceCell },
44
+ { key: "ask", header: "Ask", align: "right", format: priceCell },
45
+ // The text view marks a wide spread in its cell; exports keep the number and say so in Wide.
46
+ { key: "spread", header: "Spread", align: "right", format: percentCell(1),
47
+ textFormat: (value, row) => `${percentCell(1)(value)}${row.wide ? " wide" : ""}` },
48
+ { key: "delta", header: "Delta", align: "right", format: (value) => typeof value === "number" ? value.toFixed(2) : "--" },
49
+ { key: "otm", header: "OTM", align: "right", format: percentCell(1) },
50
+ { key: "midYield", header: "Mid yield/yr", align: "right", format: percentCell(2) },
51
+ { key: "bidYield", header: "Bid yield/yr", align: "right", format: percentCell(2) },
52
+ { key: "ivRank", header: "IVR", align: "right", format: (value) => typeof value === "number" ? value.toFixed(0) : "--" },
53
+ { key: "earnings", header: "Earnings" },
54
+ ];
55
+ const NOTE: HeadlessPaneColumn = { key: "note", header: "Note", underRow: true };
56
+ const EXPORT_ONLY: HeadlessPaneColumn[] = [
57
+ { key: "wide", header: "Wide spread", format: (value) => value === true ? "yes" : value === false ? "no" : "" },
58
+ { key: "earningsBeforeExpiry", header: "Earnings before expiry", format: (value) => value === true ? "yes" : value === false ? "no" : "unknown" },
59
+ { key: "earningsDate", header: "Earnings date" },
60
+ { key: "spot", header: "Spot", format: priceCell },
61
+ ];
62
+
63
+ /** `A`, `A and B`, `A, B and C`. */
64
+ const listText = (items: readonly string[]) => items.length <= 1 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
65
+
66
+ /**
67
+ * The ranking basis and the wide flag's limit, then the names IV history has no
68
+ * rank for: those with a reason say it (`No IV rank: BBB (fewer than 120
69
+ * sessions).`), and a bare symbol, which has none to give, is just not ranked
70
+ * yet (`No IV rank for DAL yet.`).
71
+ */
72
+ export function cspScreenNotes(screen: Pick<CspScreen, "criteria" | "missingRanks">): string[] {
73
+ const reasoned = screen.missingRanks.filter((entry) => entry.includes(" "));
74
+ const bare = screen.missingRanks.filter((entry) => !entry.includes(" "));
75
+ return [
76
+ `Ranked by annualised yield at the bid. Wide is a spread over ${screen.criteria.maxSpreadPercent}% of the mid (--max-spread).`,
77
+ ...(reasoned.length ? [`No IV rank: ${reasoned.join(", ")}.`] : []),
78
+ ...(bare.length ? [`No IV rank for ${listText(bare)} yet.`] : []),
79
+ ];
80
+ }
81
+
82
+ export const cspScreenHeadless: HeadlessPaneDefinition<"rows"> = {
83
+ shape: "rows",
84
+ argument: { kind: "tickers", maximum: CSP_SCREEN_LIMIT, description: "US option underlyings to rank" },
85
+ usListingsOnly: "Options data",
86
+ failsWithoutData: true,
87
+ description: "Cash-secured puts across a list, ranked by annualised yield on cash at the bid: for each name, the put nearest a delta "
88
+ + "(0.30) on the listed expiry nearest a date or a number of days out (30), with its strike, bid, ask, spread (flagged wide over "
89
+ + "--max-spread, never left out), delta, distance out of the money, yield at the mid and at the bid, IV rank and whether earnings fall before expiry.",
90
+ discovery: { screenshotReadiness: "live-dom", dataRequirements: ["Current option chains", "Cloud stored implied volatility", "Earnings calendar"],
91
+ limitations: ["Assignment, margin and the interest the cash earns are not modelled."] },
92
+ describe: (_args, result) => {
93
+ const criteria = (result?.metadata as { criteria?: CspScreen["criteria"] } | undefined)?.criteria;
94
+ return ["Cash-secured puts", ...(criteria ? [cspScreenCriteriaText(criteria, Date.now())] : [])].join(" · ");
95
+ },
96
+ options: [
97
+ { key: "delta", type: "string", placeholder: "0.30", example: "--delta 0.20",
98
+ description: `Each name's put is the one whose delta is nearest this, as a put's delta reads without its sign. Default ${CSP_SCREEN_DEFAULTS.delta.toFixed(2)}` },
99
+ { key: "days", type: "string", placeholder: "days", example: "--days 45",
100
+ description: `Each name's expiry is its listed one nearest this many calendar days out. Default ${CSP_SCREEN_DEFAULTS.days}` },
101
+ { key: "expiration", type: "string", placeholder: OPTION_EXPIRATION_PLACEHOLDER, example: "--expiration 2026-11-20",
102
+ description: `Each name's expiry is its listed one nearest this date, in place of --days, as ${OPTION_EXPIRATION_FORMAT}`,
103
+ normalize: (value) => expirationOptionSeconds(value) },
104
+ { key: "maxSpread", aliases: ["max-spread"], type: "string", placeholder: "percent", example: "--max-spread 15",
105
+ description: `A put whose bid/ask spread is over this percent of its mid is marked wide; it is still ranked. Default ${CSP_SCREEN_DEFAULTS.maxSpreadPercent}` },
106
+ ],
107
+ async load(args, ctx) {
108
+ const criteria = readCspScreenCriteria(args.options);
109
+ const instruments = await Promise.all(args.symbols.map((symbol) => resolveHeadlessInstrument(ctx, symbol)));
110
+ const screen = await loadCspScreen(instruments, criteria, {
111
+ scenario: createScenarioDependencies(ctx.marketData, ctx.apiClient), earnings: ctx.marketData, ivApi: ctx.apiClient,
112
+ }, ctx.signal);
113
+ const unavailable = screen.rows.filter((row) => !row.csp);
114
+ return {
115
+ freshness: screen.freshness,
116
+ columns: [...COLUMNS, ...EXPORT_ONLY, NOTE],
117
+ textColumns: [...COLUMNS, NOTE],
118
+ rows: screen.rows.map(cspScreenReportRow),
119
+ complete: unavailable.length === 0 && !screen.ivRankError,
120
+ unavailableSymbols: unavailable.map((row) => row.symbol),
121
+ errors: screen.ivRankError ? [`IV rank unavailable: ${screen.ivRankError}`] : [],
122
+ notes: cspScreenNotes(screen),
123
+ metadata: { criteria: screen.criteria, rankedBy: "annualized yield on cash at the bid", unit: "percent; prices per share",
124
+ methodology: "docs/research-data.md#cash-secured-put-screen" },
125
+ };
126
+ },
127
+ };
@@ -0,0 +1,165 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { DataTableView, PaneStatusBody, QueryBar, usePaneFooter, usePaneNoticeFooter, type DataTableColumn, type DataTableKeyEvent } from "../../../components";
3
+ import { useAsyncResource } from "../../../react/async-resource";
4
+ import { useAutoRefresh } from "../../../react/auto-refresh";
5
+ import { usePaneSettingValue, usePluginAppActions } from "../../../public/react";
6
+ import { useThemeColors } from "../../../theme/theme-context";
7
+ import type { PaneProps } from "../../../types/plugin";
8
+ import { Box } from "../../../ui";
9
+ import { parsePublicTickerKey } from "../../../utils/exchanges";
10
+ import { compareSortValues, nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
11
+ import { useAssetData } from "../../runtime";
12
+ import { createScenarioDependencies } from "./client";
13
+ import { cspScreenNotes, cspScreenReportRow, type CspScreenReportRow } from "./csp-screen-headless";
14
+ import { CSP_SCREEN_DEFAULTS, CSP_SCREEN_LIMIT, cspScreenCriteriaText, cspTargetDate, loadCspScreen, readCspScreenCriteria, type CspScreenRow } from "./csp-screen";
15
+ import { formatHedgePrice } from "./hedge";
16
+
17
+ type ScreenRow = CspScreenReportRow & { source: CspScreenRow };
18
+
19
+ const COLUMNS: DataTableColumn[] = [
20
+ { id: "rank", label: "#", width: 3, align: "right" },
21
+ { id: "symbol", label: "Symbol", width: 7, align: "left" },
22
+ { id: "expiry", label: "Expiry", width: 10, align: "right" },
23
+ { id: "days", label: "DTE", width: 4, align: "right" },
24
+ { id: "strike", label: "Strike", width: 8, align: "right" },
25
+ { id: "bid", label: "Bid", width: 7, align: "right" },
26
+ { id: "ask", label: "Ask", width: 7, align: "right" },
27
+ { id: "spread", label: "Spread", width: 11, align: "right" },
28
+ { id: "delta", label: "Delta", width: 6, align: "right" },
29
+ { id: "otm", label: "OTM", width: 6, align: "right" },
30
+ { id: "midYield", label: "Mid yield/yr", width: 12, align: "right" },
31
+ { id: "bidYield", label: "Bid yield/yr", width: 12, align: "right" },
32
+ { id: "ivRank", label: "IVR", width: 4, align: "right" },
33
+ { id: "earnings", label: "Earnings", width: 17, align: "left" },
34
+ ];
35
+
36
+ const DELTA_OPTIONS = ["0.10", "0.15", "0.20", "0.25", "0.30", "0.35", "0.40", "0.50"];
37
+ const DAYS_OPTIONS = ["7", "14", "21", "30", "45", "60", "90"];
38
+ const SYMBOLS_FIELD_WIDTH = 24;
39
+ /** Symbols apply once typing pauses, so a half-typed ticker does not start a screen. */
40
+ const SYMBOLS_DEBOUNCE_MS = 700;
41
+
42
+ const symbolList = (text: string) => [...new Set(text.split(/[\s,;]+/).map((symbol) => symbol.trim().toUpperCase()).filter(Boolean))];
43
+ const percentText = (value: unknown, digits: number) => typeof value === "number" ? `${value.toFixed(digits)}%` : "--";
44
+ const priceText = (value: unknown) => typeof value === "number" ? formatHedgePrice(value) : "--";
45
+ /** A column's value to sort on: earnings by date, a flag as a number. */
46
+ function sortValue(row: CspScreenReportRow, columnId: string): string | number | null {
47
+ if (columnId === "earnings") return row.earningsDate;
48
+ const value = row[columnId as keyof CspScreenReportRow];
49
+ return typeof value === "boolean" ? Number(value) : value;
50
+ }
51
+ /** A select's options with the current value kept when it is not a preset (a delta typed on the command line). */
52
+ const withCurrent = (presets: readonly string[], current: string, label: (value: string) => string) =>
53
+ [...new Set([...presets, current])].sort((a, b) => Number(a) - Number(b)).map((value) => ({ value, label: label(value) }));
54
+
55
+ export function CspScreenPane({ width, height, focused }: PaneProps) {
56
+ const colors = useThemeColors();
57
+ const [symbolsText, setSymbolsText] = usePaneSettingValue<string>("symbols", "");
58
+ const [delta, setDelta] = usePaneSettingValue<string | number>("delta", "");
59
+ const [days, setDays] = usePaneSettingValue<string | number>("days", "");
60
+ const [expiration, setExpiration] = usePaneSettingValue<string | number>("expiration", "");
61
+ const [maxSpread] = usePaneSettingValue<string | number>("maxSpread", "");
62
+ const [symbolsActive, setSymbolsActive] = useState(false);
63
+ const { createPaneFromTemplate } = usePluginAppActions();
64
+ const assetData = useAssetData();
65
+ const typed = useMemo(() => symbolList(String(symbolsText ?? "")), [symbolsText]);
66
+ // The screen reads the first names it can rank; the footer names the rest.
67
+ const symbols = useMemo(() => typed.slice(0, CSP_SCREEN_LIMIT), [typed]);
68
+ const leftOut = typed.slice(CSP_SCREEN_LIMIT);
69
+ const criteria = useMemo(() => {
70
+ try { return { value: readCspScreenCriteria({ delta, days, expiration, maxSpread }), error: null }; }
71
+ catch (error) { return { value: null, error: error instanceof Error ? error.message : String(error) }; }
72
+ }, [delta, days, expiration, maxSpread]);
73
+ const controller = useRef<AbortController | null>(null);
74
+ const key = JSON.stringify([symbols, criteria.value]);
75
+ const loader = useCallback(async () => {
76
+ controller.current?.abort();
77
+ const current = new AbortController();
78
+ controller.current = current;
79
+ return loadCspScreen(symbols.map((symbol) => parsePublicTickerKey(symbol)), criteria.value!,
80
+ { scenario: createScenarioDependencies(), earnings: assetData }, current.signal);
81
+ }, [key, assetData]);
82
+ const resource = useAsyncResource(symbols.length && criteria.value ? loader : null);
83
+ useEffect(() => () => controller.current?.abort(), [loader]);
84
+ useAutoRefresh(resource.updatedAt, resource.load);
85
+ const [sort, setSort] = useState<{ columnId: string; direction: SortDirection }>({ columnId: "rank", direction: "asc" });
86
+ const rows = useMemo<ScreenRow[]>(() => {
87
+ const flat = (resource.data?.rows ?? []).map((row) => ({ ...cspScreenReportRow(row), source: row }));
88
+ // Unranked names stay under the ranked ones whichever way rank sorts.
89
+ return flat.sort((left, right) => sort.columnId === "rank"
90
+ ? compareSortValues(left.rank ?? Number.POSITIVE_INFINITY, right.rank ?? Number.POSITIVE_INFINITY, sort.direction)
91
+ : compareSortValues(sortValue(left, sort.columnId), sortValue(right, sort.columnId), sort.direction));
92
+ }, [resource.data, sort]);
93
+ const [selected, setSelected] = useState<string | null>(null);
94
+ const screen = resource.data;
95
+ const notices = [criteria.error, resource.data ? null : resource.error,
96
+ leftOut.length ? `Ranks the first ${CSP_SCREEN_LIMIT} symbols; ${leftOut.length} left out: ${leftOut.join(", ")}` : null,
97
+ ...(screen?.ivRankError ? [`IV rank unavailable: ${screen.ivRankError}`] : []),
98
+ ...(screen?.rows ?? []).flatMap((row) => row.note ? [`${row.symbol}: ${row.note}`] : []),
99
+ ...(screen ? cspScreenNotes(screen).slice(1) : [])].filter((value): value is string => !!value);
100
+ usePaneNoticeFooter({ registrationId: "csp-screen-notices", notices, focused });
101
+ // The bar names the delta and the expiry; the footer says only what changes.
102
+ usePaneFooter("csp-screen", () => ({ info: [
103
+ ...(resource.loading ? [{ id: "loading", parts: [{ text: "loading", tone: "muted" as const }] }] : []),
104
+ ] }), [resource.loading]);
105
+ const criteriaText = criteria.value ? cspScreenCriteriaText(criteria.value, Date.now()) : "";
106
+ // Enter opens the name's put in OSA, at the strike and expiry the screen read.
107
+ const openScenario = (row: ScreenRow) => {
108
+ const csp = row.source.csp;
109
+ if (!csp) return;
110
+ createPaneFromTemplate("options-scenario-pane", { symbol: row.symbol, values: { strategy: "csp", strike: String(csp.strike), expiration: csp.expiry } });
111
+ };
112
+ const handleKey = (event: DataTableKeyEvent): boolean => {
113
+ if (event.ctrl || event.alt || event.meta || event.name !== "r") return false;
114
+ void resource.reload();
115
+ event.preventDefault?.(); event.stopPropagation?.(); return true;
116
+ };
117
+ const cell = (row: ScreenRow, id: string): { text: string; color?: string } => {
118
+ switch (id) {
119
+ case "rank": return { text: row.rank == null ? "" : String(row.rank), color: colors.textDim };
120
+ case "symbol": return { text: row.symbol, color: colors.textBright };
121
+ case "expiry": return { text: String(row.expiry ?? "--"), color: colors.textDim };
122
+ case "days": return { text: row.days == null ? "--" : String(row.days) };
123
+ case "strike": return { text: row.strike == null ? "--" : String(row.strike) };
124
+ case "bid": return { text: priceText(row.bid) };
125
+ case "ask": return { text: priceText(row.ask) };
126
+ case "spread": return { text: `${percentText(row.spread, 1)}${row.wide ? " wide" : ""}`, color: row.wide ? colors.warning : undefined };
127
+ case "delta": return { text: typeof row.delta === "number" ? row.delta.toFixed(2) : "--" };
128
+ case "otm": return { text: percentText(row.otm, 1) };
129
+ case "midYield": return { text: percentText(row.midYield, 2), color: colors.textDim };
130
+ case "bidYield": return { text: percentText(row.bidYield, 2), color: colors.positive };
131
+ case "ivRank": return { text: typeof row.ivRank === "number" ? row.ivRank.toFixed(0) : "--" };
132
+ case "earnings": return { text: String(row.earnings), color: row.earningsBeforeExpiry === true ? colors.warning : colors.textDim };
133
+ default: return { text: "" };
134
+ }
135
+ };
136
+ const deltaValue = criteria.value ? criteria.value.delta.toFixed(2) : CSP_SCREEN_DEFAULTS.delta.toFixed(2);
137
+ const daysValue = String(criteria.value?.days ?? CSP_SCREEN_DEFAULTS.days);
138
+ // A date typed on the command line is the expiry's choice until a number of days is picked.
139
+ const typedDate = criteria.value?.expiration != null ? cspTargetDate(criteria.value, Date.now()) : null;
140
+ return <Box width={width} height={height} flexDirection="column" overflow="hidden">
141
+ <QueryBar width={width} filters={[
142
+ { id: "symbols", kind: "text" as const, label: "Symbols", value: String(symbolsText ?? ""), placeholder: "JPM, GS, KO",
143
+ width: SYMBOLS_FIELD_WIDTH, debounceMs: SYMBOLS_DEBOUNCE_MS, focused, active: symbolsActive, onActiveChange: setSymbolsActive,
144
+ onChange: setSymbolsText },
145
+ { id: "delta", label: "Delta", value: deltaValue, defaultValue: CSP_SCREEN_DEFAULTS.delta.toFixed(2),
146
+ options: withCurrent(DELTA_OPTIONS, deltaValue, (value) => value), onChange: (value: string) => setDelta(value) },
147
+ { id: "expiry", label: "Expiry", value: typedDate ?? daysValue, defaultValue: String(CSP_SCREEN_DEFAULTS.days),
148
+ options: [...withCurrent(DAYS_OPTIONS, daysValue, (value) => `${value} days`), ...(typedDate ? [{ value: typedDate, label: typedDate }] : [])],
149
+ onChange: (value: string) => { if (value === typedDate) return; setExpiration(""); setDays(value); } },
150
+ ]} />
151
+ <PaneStatusBody subject="cash-secured puts" loading={resource.loading && !resource.data}
152
+ error={criteria.error && !criteria.value ? criteria.error : !resource.data ? resource.error : null}>
153
+ <DataTableView<ScreenRow> focused={focused && !symbolsActive} columns={COLUMNS} items={rows} rootWidth={width}
154
+ rootHeight={Math.max(2, height - 1)}
155
+ getItemKey={(row) => row.symbol} sortColumnId={sort.columnId} sortDirection={sort.direction}
156
+ emptyStateTitle={symbols.length ? "No puts to rank." : "Type symbols to rank their cash-secured puts."}
157
+ onHeaderClick={(id) => setSort((current) => nextHeaderSort(current, id, { firstDirection: id === "rank" || id === "symbol" ? "asc" : "desc" }))}
158
+ selection={{ kind: "id", selectedId: selected ?? rows[0]?.symbol ?? "", getId: (row) => row.symbol, onChange: (id) => setSelected(id) }}
159
+ onActivate={openScenario} onRootKeyDown={handleKey}
160
+ getExportMetadata={() => [["puts", criteriaText || undefined], ["ranked by", "annualised yield on cash at the bid"],
161
+ ["wide", criteria.value ? `spread over ${criteria.value.maxSpreadPercent}% of the mid` : undefined]]}
162
+ renderCell={(row, column) => cell(row, column.id)} />
163
+ </PaneStatusBody>
164
+ </Box>;
165
+ }