gloomberb 0.16.2 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (527) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -0,0 +1,172 @@
1
+ import type { OptionContract, OptionsChain, Quote } from "../../types/financials";
2
+ import type { DataProvider } from "../../types/data-provider";
3
+ import type { CliListing } from "../listing-arg";
4
+ import { cliStyles, cliTerminalWidth, wrapText } from "../../utils/cli-output";
5
+ import { isFiniteNumber } from "../../utils/guards";
6
+ import { calculateOptionGreeks, solveChainVolatilities } from "../../plugins/builtin/options/analytics";
7
+ import { optionQuoteSide } from "../../plugins/builtin/options/market-reference";
8
+ import { DEFAULT_OPTION_CALC_DRAFT } from "../../plugins/builtin/options-calculator/model";
9
+ import { expiryIsoDate, missingExpiryText, parseOptionExpiration } from "../../utils/option-expiry";
10
+
11
+ export const OPTIONS_USAGE = "options <symbol> [--expiration <YYYY-MM-DD|unix>]";
12
+
13
+ export { parseOptionExpiration };
14
+
15
+ /** Whether the chain holds contracts of the requested expiry, matched by UTC calendar date as the chain stamps them. */
16
+ export function chainHasExpiry(chain: OptionsChain, requested: number): boolean {
17
+ const date = expiryIsoDate(requested);
18
+ return [...chain.calls, ...chain.puts].some((contract) => expiryIsoDate(contract.expiration) === date);
19
+ }
20
+
21
+ /** Every listed expiry as a unique, ascending UTC date. */
22
+ function listedDates(expirations: readonly number[]): string[] {
23
+ return [...new Set(expirations.map((expiration) => expiryIsoDate(expiration)).filter(Boolean))].sort();
24
+ }
25
+
26
+ /**
27
+ * Why a chain request for one expiry came back without contracts, and which
28
+ * expiries it could have been: the few nearest the requested date, so a typo or
29
+ * an unlisted LEAPS date points at the neighbours to pick.
30
+ */
31
+ export function missingExpiryMessage(
32
+ symbol: string,
33
+ requested: number,
34
+ expirations: readonly number[],
35
+ ): { message: string; details?: string } {
36
+ return {
37
+ message: missingExpiryText(requested, expirations, symbol),
38
+ details: `Run gloomberb options ${symbol} to list every expiry (--json carries them too).`,
39
+ };
40
+ }
41
+
42
+ const EXPIRY_LIST_LIMIT = 40;
43
+ const EXPIRY_LIST_WIDTH = 100;
44
+ const EXPIRY_GAP = 2;
45
+
46
+ /** The chain's expiries as compact date lines, nearest first, for picking an `--expiration`. */
47
+ export function formatExpiryList(expirations: readonly number[], width = cliTerminalWidth() ?? EXPIRY_LIST_WIDTH): string {
48
+ const dates = listedDates(expirations);
49
+ if (dates.length === 0) return "";
50
+ const shown = dates.slice(0, EXPIRY_LIST_LIMIT);
51
+ const room = Math.max(24, width - 2);
52
+ const hidden = dates.length - shown.length;
53
+ const count = hidden > 0 ? `${dates.length}, nearest ${shown.length} shown, last ${dates.at(-1)}` : String(dates.length);
54
+ return [
55
+ ...wrapText(cliStyles.muted(`Expiries (${count}); pick one with --expiration <date>`), room),
56
+ ...wrapText(shown.join(" ".repeat(EXPIRY_GAP)), room).map((line) => ` ${line}`),
57
+ ].join("\n");
58
+ }
59
+
60
+ /** What a contract's implied volatility and delta are valued from. */
61
+ export interface OptionModelInputs {
62
+ /** The underlying's current price; absent when the quote is missing or stale. */
63
+ spot?: number;
64
+ dividendYield?: number;
65
+ now?: number;
66
+ }
67
+
68
+ interface OptionModelFigures {
69
+ /** Annualized volatility as a fraction, solved from the chain's quotes. */
70
+ iv: number | null;
71
+ delta: number | null;
72
+ }
73
+
74
+ /** The risk-free rate every options valuation in the app assumes. */
75
+ export const OPTION_MODEL_RATE = DEFAULT_OPTION_CALC_DRAFT.rate;
76
+
77
+ /**
78
+ * IV and delta per contract, from the same solver and Greeks as the options
79
+ * pane (put-call parity forward, one volatility per strike, the pane's rate).
80
+ * The provider's own IV is not used: it is a placeholder on many contracts.
81
+ * Each expiry is valued on its own, as the pane does. A contract that cannot be
82
+ * valued (no current spot, expired, no two-sided market) has nulls, never zeros.
83
+ */
84
+ function optionModelFigures(chain: OptionsChain, inputs: OptionModelInputs): Map<OptionContract, OptionModelFigures> {
85
+ const figures = new Map<OptionContract, OptionModelFigures>();
86
+ const expirations = new Set([...chain.calls, ...chain.puts].map((contract) => contract.expiration));
87
+ for (const expiration of expirations) {
88
+ const slice: OptionsChain = {
89
+ ...chain,
90
+ calls: chain.calls.filter((contract) => contract.expiration === expiration),
91
+ puts: chain.puts.filter((contract) => contract.expiration === expiration),
92
+ };
93
+ const volatilities = solveChainVolatilities(slice, inputs.spot, inputs.dividendYield, inputs.now);
94
+ for (const [side, contracts] of [["call", slice.calls], ["put", slice.puts]] as const) {
95
+ for (const contract of contracts) {
96
+ const volatility = volatilities.byStrike.get(contract.strike);
97
+ const delta = calculateOptionGreeks(contract, side, inputs.spot, inputs.dividendYield, volatilities)?.delta;
98
+ figures.set(contract, {
99
+ iv: volatility != null && Number.isFinite(volatility) && volatility > 0 ? volatility : null,
100
+ delta: delta != null && Number.isFinite(delta) ? delta : null,
101
+ });
102
+ }
103
+ }
104
+ }
105
+ return figures;
106
+ }
107
+
108
+ /** Spot and dividend yield for IV and delta, read from the underlying the way the options pane does; missing ones stay missing. */
109
+ export async function loadOptionModelInputs(
110
+ dataProvider: Pick<DataProvider, "getTickerFinancials">,
111
+ listing: CliListing,
112
+ quote: Promise<Quote | null>,
113
+ refresh: boolean,
114
+ ): Promise<OptionModelInputs> {
115
+ const financials = await Promise.resolve().then(() => dataProvider.getTickerFinancials(
116
+ listing.request.symbol, listing.request.exchange, { cacheMode: refresh ? "refresh" : "default" },
117
+ )).catch(() => null);
118
+ const current = [await quote, financials?.quote]
119
+ .find((candidate) => candidate && candidate.stale !== true && isFiniteNumber(candidate.price) && candidate.price > 0);
120
+ const dividendYield = financials?.fundamentals?.dividendYield;
121
+ return {
122
+ ...(current ? { spot: current.price } : {}),
123
+ ...(isFiniteNumber(dividendYield) && dividendYield >= 0 ? { dividendYield } : {}),
124
+ };
125
+ }
126
+
127
+ /** The chain with each contract's modeled `iv` and `delta` added; every provider field is kept. */
128
+ export function chainWithModelFigures(chain: OptionsChain, inputs: OptionModelInputs): OptionsChain {
129
+ const figures = optionModelFigures(chain, inputs);
130
+ const add = (contract: OptionContract) => ({ ...contract, iv: figures.get(contract)?.iv ?? null, delta: figures.get(contract)?.delta ?? null });
131
+ return { ...chain, calls: chain.calls.map(add), puts: chain.puts.map(add) };
132
+ }
133
+
134
+ export function optionRows(chain: OptionsChain) {
135
+ const contracts = [
136
+ ...chain.calls.map((contract) => ({ side: "call", contract })),
137
+ ...chain.puts.map((contract) => ({ side: "put", contract })),
138
+ ];
139
+ return contracts.map(({ side, contract }) => {
140
+ const modeled = contract as OptionContract & Partial<OptionModelFigures>;
141
+ return {
142
+ side,
143
+ contract: contract.contractSymbol,
144
+ strike: contract.strike,
145
+ last: contract.lastPrice,
146
+ bid: contract.bid,
147
+ ask: contract.ask,
148
+ volume: contract.volume,
149
+ openInterest: contract.openInterest,
150
+ iv: modeled.iv ?? null,
151
+ delta: modeled.delta ?? null,
152
+ expiration: expiryIsoDate(contract.expiration),
153
+ };
154
+ });
155
+ }
156
+
157
+ export function formatOptionQuoteCell(row: Record<string, unknown>, side: "bid" | "ask"): string {
158
+ const quote = optionQuoteSide({ bid: Number(row.bid), ask: Number(row.ask) }, side);
159
+ return quote == null ? "—" : String(quote);
160
+ }
161
+
162
+ /** Implied volatility as a percent; a dash when it could not be solved, never 0.0%. */
163
+ export function formatOptionIvCell(value: unknown): string {
164
+ return isFiniteNumber(value) && value > 0 ? `${(value * 100).toFixed(1)}%` : "—";
165
+ }
166
+
167
+ /** Delta to two places, signed for puts; a dash when it could not be valued, and no "-0.00" for a far wing. */
168
+ export function formatOptionDeltaCell(value: unknown): string {
169
+ if (!isFiniteNumber(value)) return "—";
170
+ const text = value.toFixed(2);
171
+ return text === "-0.00" ? "0.00" : text;
172
+ }
@@ -0,0 +1,422 @@
1
+ import type { OptionContract, OptionsChain } from "../../types/financials";
2
+ import type { CliCommandContext } from "../../types/plugin";
3
+ import type { MarketContext } from "../types";
4
+ import type { CliResultColumn } from "../result";
5
+ import { requireCliListing, loadListingQuote, type CliListing } from "../listing-arg";
6
+ import { rowsFreshness } from "../freshness";
7
+ import { formatCountCell } from "../helpers";
8
+ import { cliStyles } from "../../utils/cli-output";
9
+ import { errorMessage } from "../../utils/errors";
10
+ import { daysToExpiryFrom, type OptionSide } from "../../plugins/builtin/shared/volatility";
11
+ import { optionQuoteValuationTime } from "../../plugins/builtin/vol-surface/model";
12
+ import { optionCarry, optionSpreadPercent } from "../../plugins/builtin/options/carry";
13
+ import { parseDeltaBand } from "../../plugins/builtin/options/strike-window";
14
+ import { chainWithModelFigures, loadOptionModelInputs, OPTION_MODEL_RATE, type OptionModelInputs } from "./options-chain";
15
+ import { takeFlag, takeOption } from "./command-utils";
16
+
17
+ /**
18
+ * `gloomberb options <symbol...> --leaps`: contracts more than a year out
19
+ * ranked across symbols for a stock replacement, by what the time value costs
20
+ * per year. Delta, IV and carry come from the same solver and formulas as the
21
+ * options pane (`carry.ts`, `analytics.ts`).
22
+ */
23
+ export const LEAPS_USAGE = "options <symbol...> --leaps [--side calls|puts] [--delta <low-high>] [--max-spread <percent>] [--min-oi <contracts>] [--sort <order>]";
24
+
25
+ export type LeapsSort = "carry" | "spread" | "oi" | "delta" | "expiry" | "symbol";
26
+ const LEAPS_SORTS: readonly LeapsSort[] = ["carry", "spread", "oi", "delta", "expiry", "symbol"];
27
+
28
+ export interface LeapsCriteria {
29
+ side: OptionSide;
30
+ minDelta: number;
31
+ maxDelta: number;
32
+ /** Widest bid/ask spread kept, in percent of the midpoint. */
33
+ maxSpreadPercent: number;
34
+ minOpenInterest: number;
35
+ sort: LeapsSort;
36
+ }
37
+
38
+ export const DEFAULT_LEAPS_CRITERIA: LeapsCriteria = {
39
+ side: "call",
40
+ minDelta: 0.7,
41
+ maxDelta: 0.9,
42
+ maxSpreadPercent: 5,
43
+ minOpenInterest: 100,
44
+ sort: "carry",
45
+ };
46
+
47
+ /** A LEAPS expiry is more than this many calendar days out. */
48
+ const LEAPS_MIN_DAYS = 365;
49
+ /** Most symbols one run takes, and most expiries read per symbol, so a run stays a few dozen requests. */
50
+ const MAX_LEAPS_SYMBOLS = 20;
51
+ const MAX_LEAPS_EXPIRIES = 8;
52
+ /** Rows the text table shows unless --limit or --tail says otherwise; exports carry every row. */
53
+ const LEAPS_TEXT_ROWS = 40;
54
+ /** Chain requests in flight at once across every symbol. */
55
+ const LEAPS_CHAIN_CONCURRENCY = 4;
56
+ // Delta prints to three places; a contract that reads 0.700 is in a 0.70 band.
57
+ const DELTA_TOLERANCE = 5e-4;
58
+
59
+ /** `--leaps` and the options that only mean something with it, taken out of `args`. */
60
+ export function takeLeapsOptions(args: string[]): { leaps: boolean; raw: Record<string, string | undefined>; given: string[] } {
61
+ const given: string[] = [];
62
+ const raw: Record<string, string | undefined> = {};
63
+ for (const flag of ["--side", "--delta", "--max-spread", "--min-oi", "--sort"]) {
64
+ if (args.some((arg) => arg === flag || arg.startsWith(`${flag}=`))) given.push(flag);
65
+ raw[flag] = takeOption(args, flag);
66
+ }
67
+ return { leaps: takeFlag(args, "--leaps"), raw, given };
68
+ }
69
+
70
+ /** The screen's criteria from its options; a message naming the option when one cannot be read. */
71
+ export function parseLeapsCriteria(raw: Record<string, string | undefined>): LeapsCriteria | { error: string } {
72
+ const criteria = { ...DEFAULT_LEAPS_CRITERIA };
73
+ const side = raw["--side"]?.trim().toLowerCase();
74
+ if (side != null) {
75
+ if (side === "call" || side === "calls") criteria.side = "call";
76
+ else if (side === "put" || side === "puts") criteria.side = "put";
77
+ else return { error: `Invalid --side "${raw["--side"]}". Use calls or puts.` };
78
+ }
79
+ const delta = raw["--delta"];
80
+ if (delta != null) {
81
+ const band = parseDeltaBand(delta);
82
+ if (!band) return { error: `Invalid --delta "${delta}". Use a band of absolute deltas such as 0.70-0.90.` };
83
+ criteria.minDelta = band.min;
84
+ criteria.maxDelta = band.max;
85
+ }
86
+ const spread = raw["--max-spread"];
87
+ if (spread != null) {
88
+ const value = Number(spread.trim().replace(/%$/, ""));
89
+ if (!spread.trim() || !Number.isFinite(value) || value <= 0) {
90
+ return { error: `Invalid --max-spread "${spread}". Use a percent of the midpoint above 0, such as 5.` };
91
+ }
92
+ criteria.maxSpreadPercent = value;
93
+ }
94
+ const openInterest = raw["--min-oi"];
95
+ if (openInterest != null) {
96
+ const value = Number(openInterest.trim());
97
+ if (!/^\d+$/.test(openInterest.trim()) || !Number.isSafeInteger(value)) {
98
+ return { error: `Invalid --min-oi "${openInterest}". Use a whole number of contracts, such as 100.` };
99
+ }
100
+ criteria.minOpenInterest = value;
101
+ }
102
+ const sort = raw["--sort"]?.trim().toLowerCase();
103
+ if (sort != null) {
104
+ if (!LEAPS_SORTS.includes(sort as LeapsSort)) return { error: `Invalid --sort "${raw["--sort"]}". Use one of ${LEAPS_SORTS.join(", ")}.` };
105
+ criteria.sort = sort as LeapsSort;
106
+ }
107
+ return criteria;
108
+ }
109
+
110
+ export type LeapsRow = {
111
+ symbol: string;
112
+ /** Expiry date, YYYY-MM-DD. */
113
+ expiration: string;
114
+ /** Calendar days to the expiry's close. */
115
+ days: number;
116
+ side: OptionSide;
117
+ contract: string;
118
+ strike: number;
119
+ delta: number;
120
+ bid: number;
121
+ ask: number;
122
+ /** Bid/ask width in percent of the midpoint. */
123
+ spreadPercent: number;
124
+ openInterest: number;
125
+ /** Midpoint less intrinsic value, per share. */
126
+ extrinsic: number;
127
+ /** Extrinsic as a percent of spot per year to expiry. */
128
+ extrinsicPerYearPercent: number;
129
+ spot: number;
130
+ currency: string;
131
+ /** When the chain was observed. */
132
+ asOf: string | null;
133
+ dataSource: "live" | "delayed" | null;
134
+ delayMinutes: number | null;
135
+ };
136
+
137
+ const round = (value: number, digits: number) => Number(value.toFixed(digits));
138
+
139
+ function utcDate(seconds: number): string {
140
+ const date = new Date(seconds * 1000);
141
+ return Number.isNaN(date.getTime()) ? "" : date.toISOString().slice(0, 10);
142
+ }
143
+
144
+ /** The listed expiries more than a year out, nearest first. */
145
+ function leapsExpirations(expirations: readonly number[], now: number): number[] {
146
+ return [...new Set(expirations)]
147
+ .filter((expiration) => daysToExpiryFrom(expiration, now) > LEAPS_MIN_DAYS)
148
+ .sort((left, right) => left - right);
149
+ }
150
+
151
+ /**
152
+ * The contracts of one chain that pass the screen: one side, more than a year
153
+ * out, delta in the band, a two-sided quote no wider than the cap, enough open
154
+ * interest, and a carry that can be read. `chain` carries modeled deltas
155
+ * (`chainWithModelFigures`).
156
+ */
157
+ function screenLeapsChain(
158
+ symbol: string,
159
+ chain: OptionsChain,
160
+ criteria: LeapsCriteria,
161
+ inputs: OptionModelInputs,
162
+ now: number,
163
+ ): { rows: LeapsRow[]; scanned: number } {
164
+ const spot = inputs.spot;
165
+ const contracts = (criteria.side === "call" ? chain.calls : chain.puts) as Array<OptionContract & { delta?: number | null }>;
166
+ const valuationTime = optionQuoteValuationTime(chain, now);
167
+ const rows: LeapsRow[] = [];
168
+ let scanned = 0;
169
+ for (const contract of contracts) {
170
+ const days = daysToExpiryFrom(contract.expiration, now);
171
+ if (!(days > LEAPS_MIN_DAYS)) continue;
172
+ scanned += 1;
173
+ if (spot == null || contract.delta == null || !Number.isFinite(contract.delta)) continue;
174
+ const delta = Math.abs(contract.delta);
175
+ if (delta < criteria.minDelta - DELTA_TOLERANCE || delta > criteria.maxDelta + DELTA_TOLERANCE) continue;
176
+ const spreadPercent = optionSpreadPercent(contract);
177
+ if (spreadPercent == null || spreadPercent > criteria.maxSpreadPercent) continue;
178
+ const openInterest = contract.openInterest;
179
+ if (openInterest == null || !Number.isFinite(openInterest) || openInterest < criteria.minOpenInterest) continue;
180
+ const carry = optionCarry(contract, criteria.side, spot, valuationTime);
181
+ if (!carry || carry.extrinsicPerYear == null) continue;
182
+ rows.push({
183
+ symbol,
184
+ expiration: utcDate(contract.expiration),
185
+ days: Math.floor(days),
186
+ side: criteria.side,
187
+ contract: contract.contractSymbol,
188
+ strike: contract.strike,
189
+ delta: round(contract.delta, 4),
190
+ bid: contract.bid,
191
+ ask: contract.ask,
192
+ spreadPercent: round(spreadPercent, 3),
193
+ openInterest,
194
+ extrinsic: round(carry.extrinsic, 4),
195
+ extrinsicPerYearPercent: round(carry.extrinsicPerYear * 100, 3),
196
+ spot,
197
+ currency: contract.currency,
198
+ asOf: chain.asOf ?? null,
199
+ dataSource: chain.dataSource ?? null,
200
+ delayMinutes: chain.delayMinutes ?? null,
201
+ });
202
+ }
203
+ return { rows, scanned };
204
+ }
205
+
206
+ /** Rows in the screen's order; ties fall back to carry, then symbol, expiry and strike. */
207
+ export function sortLeapsRows(rows: readonly LeapsRow[], sort: LeapsSort): LeapsRow[] {
208
+ const byCarry = (left: LeapsRow, right: LeapsRow) => left.extrinsicPerYearPercent - right.extrinsicPerYearPercent;
209
+ const stable = (left: LeapsRow, right: LeapsRow) => left.symbol.localeCompare(right.symbol)
210
+ || left.expiration.localeCompare(right.expiration) || left.strike - right.strike;
211
+ const primary: Record<LeapsSort, (left: LeapsRow, right: LeapsRow) => number> = {
212
+ carry: byCarry,
213
+ spread: (left, right) => left.spreadPercent - right.spreadPercent,
214
+ oi: (left, right) => right.openInterest - left.openInterest,
215
+ delta: (left, right) => Math.abs(right.delta) - Math.abs(left.delta),
216
+ expiry: (left, right) => left.expiration.localeCompare(right.expiration),
217
+ symbol: (left, right) => left.symbol.localeCompare(right.symbol),
218
+ };
219
+ return [...rows].sort((left, right) => primary[sort](left, right) || byCarry(left, right) || stable(left, right));
220
+ }
221
+
222
+ const SORT_LABELS: Record<LeapsSort, string> = {
223
+ carry: "extrinsic per year, lowest first",
224
+ spread: "spread, tightest first",
225
+ oi: "open interest, highest first",
226
+ delta: "delta, highest first",
227
+ expiry: "expiry, nearest first",
228
+ symbol: "symbol",
229
+ };
230
+
231
+ /** The criteria in one line above the table. */
232
+ function describeLeapsCriteria(criteria: LeapsCriteria): string {
233
+ const side = criteria.side === "call" ? "Calls" : "Puts";
234
+ return `${side} more than a year out, delta ${criteria.minDelta.toFixed(2)} to ${criteria.maxDelta.toFixed(2)}, `
235
+ + `spread at most ${criteria.maxSpreadPercent}%, open interest at least ${criteria.minOpenInterest}; `
236
+ + `sorted by ${SORT_LABELS[criteria.sort]}.`;
237
+ }
238
+
239
+ interface LeapsSymbolResult {
240
+ symbol: string;
241
+ spot: number | null;
242
+ /** The LEAPS expiries read, YYYY-MM-DD. */
243
+ expirations: string[];
244
+ /** Contracts on the screened side more than a year out. */
245
+ scanned: number;
246
+ matched: number;
247
+ /** Why the symbol has no rows, when it is not just that none passed. */
248
+ error?: string;
249
+ }
250
+
251
+ /** Runs at most `limit` tasks at once. */
252
+ function concurrencyLimit(limit: number) {
253
+ let active = 0;
254
+ const waiting: Array<() => void> = [];
255
+ return async <T>(task: () => Promise<T>): Promise<T> => {
256
+ if (active >= limit) await new Promise<void>((resolve) => waiting.push(resolve));
257
+ active += 1;
258
+ try {
259
+ return await task();
260
+ } finally {
261
+ active -= 1;
262
+ waiting.shift()?.();
263
+ }
264
+ };
265
+ }
266
+
267
+ async function loadChain(market: MarketContext, listing: CliListing, expiration: number | undefined, refresh: boolean): Promise<OptionsChain> {
268
+ const { symbol, exchange } = listing.request;
269
+ const cached = await market.dataProvider.getCachedQuery?.("getOptionsChain", [symbol, exchange, expiration, undefined])
270
+ .load({ force: refresh });
271
+ return cached?.value ?? await market.dataProvider.getOptionsChain(symbol, exchange, expiration, {
272
+ cacheMode: refresh ? "refresh" : "default",
273
+ });
274
+ }
275
+
276
+ async function screenSymbol(
277
+ market: MarketContext,
278
+ listing: CliListing,
279
+ criteria: LeapsCriteria,
280
+ refresh: boolean,
281
+ limit: ReturnType<typeof concurrencyLimit>,
282
+ now: number,
283
+ ): Promise<{ result: LeapsSymbolResult; rows: LeapsRow[]; warning: string | null }> {
284
+ const symbol = listing.key;
285
+ const quote = loadListingQuote(market.dataProvider, listing);
286
+ const inputsPromise = loadOptionModelInputs(market.dataProvider, listing, quote, refresh);
287
+ const fail = (error: string, spot: number | null = null) => ({
288
+ result: { symbol, spot, expirations: [], scanned: 0, matched: 0, error },
289
+ rows: [],
290
+ warning: `${symbol}: ${error}`,
291
+ });
292
+ let catalogue: OptionsChain;
293
+ try {
294
+ catalogue = await limit(() => loadChain(market, listing, undefined, refresh));
295
+ } catch (error) {
296
+ return fail(`options chain unavailable (${errorMessage(error) ?? "no response"})`);
297
+ }
298
+ const inputs = await inputsPromise;
299
+ const spot = inputs.spot ?? null;
300
+ const listed = leapsExpirations(catalogue.expirationDates, now);
301
+ if (listed.length === 0) {
302
+ const last = [...catalogue.expirationDates].sort((left, right) => left - right).at(-1);
303
+ return fail(last == null ? "no listed options" : `no expiry more than a year out (last ${utcDate(last)})`, spot);
304
+ }
305
+ if (spot == null) return fail("no current quote to value delta and carry", null);
306
+ const expirations = listed.slice(0, MAX_LEAPS_EXPIRIES);
307
+ const chains = await Promise.all(expirations.map((expiration) => limit(() => loadChain(market, listing, expiration, refresh))
308
+ .then((chain) => ({ chain }), (error: unknown) => ({ error: errorMessage(error) ?? "no response", expiration }))));
309
+ const rows: LeapsRow[] = [];
310
+ let scanned = 0;
311
+ const missed: string[] = [];
312
+ for (const loaded of chains) {
313
+ if ("error" in loaded) {
314
+ missed.push(utcDate(loaded.expiration));
315
+ continue;
316
+ }
317
+ const screened = screenLeapsChain(symbol, chainWithModelFigures(loaded.chain, { ...inputs, now }), criteria, inputs, now);
318
+ rows.push(...screened.rows);
319
+ scanned += screened.scanned;
320
+ }
321
+ const notes = [
322
+ missed.length > 0 ? `chain unavailable for ${missed.join(", ")}` : null,
323
+ listed.length > expirations.length ? `read the nearest ${expirations.length} of ${listed.length} LEAPS expiries` : null,
324
+ ].filter((note): note is string => note != null);
325
+ if (missed.length === expirations.length) return fail(`options chain unavailable for ${missed.join(", ")}`, spot);
326
+ return {
327
+ result: { symbol, spot, expirations: expirations.map(utcDate), scanned, matched: rows.length },
328
+ rows,
329
+ warning: notes.length > 0 ? `${symbol}: ${notes.join("; ")}` : null,
330
+ };
331
+ }
332
+
333
+ const formatDelta = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value.toFixed(3) : "—";
334
+ const formatPrice = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? value.toFixed(2) : "—";
335
+ const formatPercent = (value: unknown) => typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(1)}%` : "—";
336
+
337
+ /** Every exported column; the text table keeps those that fit 100 columns. */
338
+ const LEAPS_COLUMNS: CliResultColumn<LeapsRow>[] = [
339
+ { key: "symbol", header: "Symbol", shrink: false },
340
+ { key: "expiration", header: "Expiry" },
341
+ { key: "days", header: "Days", align: "right" },
342
+ { key: "side", header: "Side" },
343
+ { key: "contract", header: "Contract", shrink: false },
344
+ { key: "strike", header: "Strike", align: "right" },
345
+ { key: "delta", header: "Delta", align: "right", format: formatDelta },
346
+ { key: "bid", header: "Bid", align: "right", format: formatPrice },
347
+ { key: "ask", header: "Ask", align: "right", format: formatPrice },
348
+ { key: "spreadPercent", header: "Spread %", align: "right", format: formatPercent },
349
+ { key: "openInterest", header: "OI", align: "right", format: formatCountCell },
350
+ { key: "extrinsic", header: "Extrinsic", align: "right", format: formatPrice },
351
+ { key: "extrinsicPerYearPercent", header: "Extrinsic/yr %", align: "right", format: formatPercent },
352
+ { key: "spot", header: "Spot", align: "right", format: formatPrice },
353
+ { key: "currency", header: "Currency" },
354
+ { key: "asOf", header: "As Of" },
355
+ { key: "dataSource", header: "Quotes" },
356
+ { key: "delayMinutes", header: "Delay Min", align: "right" },
357
+ ];
358
+
359
+ const TEXT_COLUMN_KEYS = ["symbol", "expiration", "days", "strike", "delta", "bid", "ask", "spreadPercent", "openInterest", "extrinsic", "extrinsicPerYearPercent"];
360
+ const LEAPS_TEXT_COLUMNS: CliResultColumn<LeapsRow>[] = TEXT_COLUMN_KEYS.map((key) => {
361
+ const column = LEAPS_COLUMNS.find((candidate) => candidate.key === key)!;
362
+ if (key === "spreadPercent") return { ...column, header: "Sprd" };
363
+ if (key === "extrinsic") return { ...column, header: "Extr" };
364
+ if (key === "extrinsicPerYearPercent") return { ...column, header: "Extr/yr" };
365
+ return column;
366
+ });
367
+
368
+ export async function runLeapsScreen(
369
+ rawSymbols: readonly string[],
370
+ exchangeOption: string | undefined,
371
+ criteria: LeapsCriteria,
372
+ market: MarketContext,
373
+ ctx: Pick<CliCommandContext, "fail" | "printResult" | "cliOptions">,
374
+ now = Date.now(),
375
+ ): Promise<void> {
376
+ if (rawSymbols.length > MAX_LEAPS_SYMBOLS) {
377
+ ctx.fail(`--leaps screens at most ${MAX_LEAPS_SYMBOLS} symbols at once; ${rawSymbols.length} were given.`);
378
+ }
379
+ const listings = await Promise.all(rawSymbols.map((symbol) => requireCliListing(
380
+ symbol, exchangeOption, market, ctx, { ownExchangeWins: rawSymbols.length > 1 },
381
+ )));
382
+ const refresh = ctx.cliOptions.refresh;
383
+ const limit = concurrencyLimit(LEAPS_CHAIN_CONCURRENCY);
384
+ const screened = await Promise.all(listings.map((listing) => screenSymbol(market, listing, criteria, refresh, limit, now)));
385
+ const symbols = screened.map((entry) => entry.result);
386
+ // Nothing to rank only when no symbol's chain loaded at all; a symbol without LEAPS is an answer.
387
+ if (symbols.every((entry) => entry.error?.startsWith("options chain unavailable"))) {
388
+ ctx.fail(screened.map((entry) => entry.warning).filter(Boolean).join("\n"));
389
+ }
390
+ const rows = sortLeapsRows(screened.flatMap((entry) => entry.rows), criteria.sort);
391
+ const warnings = screened.map((entry) => entry.warning).filter((warning): warning is string => warning != null);
392
+ const { format, limit: rowLimit, tail } = ctx.cliOptions;
393
+ const capped = format === "text" && rowLimit == null && tail == null && rows.length > LEAPS_TEXT_ROWS;
394
+ const describe = (data: readonly LeapsRow[]) => {
395
+ const criteriaLine = describeLeapsCriteria(criteria);
396
+ if (data.length === 0) {
397
+ // With no rows the summary stands in for the empty message, so it carries both.
398
+ return `${criteriaLine}\n\n${cliStyles.muted("No contract passed the screen. Widen --delta, --max-spread or --min-oi.")}`;
399
+ }
400
+ return capped
401
+ ? `${criteriaLine}\n${cliStyles.muted(`The best ${LEAPS_TEXT_ROWS} of ${data.length}; --limit ${data.length} shows them all, --csv and --json carry every row.`)}`
402
+ : criteriaLine;
403
+ };
404
+ ctx.printResult({
405
+ data: rows,
406
+ metadata: {
407
+ criteria: { ...criteria, minDays: LEAPS_MIN_DAYS },
408
+ symbols,
409
+ // What delta and carry are valued from; spot per symbol is on each row and in `symbols`.
410
+ model: { rate: OPTION_MODEL_RATE },
411
+ },
412
+ ...(warnings.length > 0 ? { warnings } : {}),
413
+ freshness: rowsFreshness(rows),
414
+ }, {
415
+ rows: (data) => data,
416
+ columns: LEAPS_COLUMNS,
417
+ textColumns: LEAPS_TEXT_COLUMNS,
418
+ layout: "table",
419
+ ...(capped ? { defaultLimit: LEAPS_TEXT_ROWS } : {}),
420
+ summary: describe,
421
+ });
422
+ }