@gloom-sh/terminal 0.18.5 → 0.19.1

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 (570) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +19 -7
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -9,11 +9,13 @@ import { fetchIpoCalendar } from "./client";
9
9
  import {
10
10
  calendarDate,
11
11
  filterIpoDeals,
12
+ formatIpoLockupEnd,
12
13
  formatIpoPrice,
13
14
  formatIpoReturn,
14
15
  formatIpoSize,
15
16
  IPO_STATUSES,
16
17
  ipoBoardStatus,
18
+ ipoKeyword,
17
19
  isIpoTab,
18
20
  marketsBehind,
19
21
  marketsBehindText,
@@ -21,7 +23,10 @@ import {
21
23
  DEFAULT_IPO_SORT,
22
24
  type IpoTab,
23
25
  } from "./model";
26
+ import { ipoDetailSentences, loadIpoLockupNews, type IpoLockupStory } from "./listing-ipo";
24
27
  import { REPORT_FILTER, REPORT_ROWS } from "../shared/headless-options";
28
+ import { DATE_OPTION_EXAMPLES, isoDateOption } from "../../../utils/calendar-date";
29
+ import { logFailure } from "../../../utils/log-failure";
25
30
 
26
31
  const COLUMNS: HeadlessPaneColumn[] = [
27
32
  { key: "ticker", header: "Ticker" },
@@ -33,6 +38,8 @@ const COLUMNS: HeadlessPaneColumn[] = [
33
38
  { key: "price", header: "Price", align: "right" },
34
39
  { key: "offerSizeUsd", header: "Size", align: "right", format: (value) => formatIpoSize(value as number | null) },
35
40
  { key: "firstDayReturn", header: "Return", align: "right", format: (value) => formatIpoReturn(value as number | null) },
41
+ // A US IPO's estimated lock-up end: "~2026-11-02".
42
+ { key: "lockupEnds", header: "Lockup ends", format: (value) => formatIpoLockupEnd(typeof value === "string" ? value : null) },
36
43
  ];
37
44
 
38
45
  type StatusOption = IpoStatus | "all";
@@ -68,7 +75,7 @@ function toRow(deal: IpoDeal, now: Date) {
68
75
  status: ipoBoardStatus(deal, now),
69
76
  listingType: deal.listingType,
70
77
  currency: deal.currency,
71
- price: formatIpoPrice(deal),
78
+ price: formatIpoPrice(deal, { full: true }),
72
79
  priceLow: deal.priceLow,
73
80
  priceHigh: deal.priceHigh,
74
81
  offerPrice: deal.offerPrice,
@@ -78,26 +85,60 @@ function toRow(deal: IpoDeal, now: Date) {
78
85
  firstDayClose: deal.firstDay?.close ?? null,
79
86
  firstDayReturn: deal.firstDay?.returnPct ?? null,
80
87
  filedDate: deal.filedDate,
88
+ lockupEnds: deal.lockup?.endsOn ?? null,
89
+ lockupBasis: deal.lockup?.basis ?? null,
90
+ prospectusDate: deal.lockup?.prospectusDate ?? null,
81
91
  };
82
92
  }
83
93
 
84
- function projectIpoCalendarHeadless(payload: IpoCalendarPayload, args: HeadlessPaneLoadArgs): HeadlessRowsResult {
94
+ /** One deal found: its IPO in words, as `ticker` gives it, with its price now and its lock-up news. */
95
+ interface IpoDealDetail {
96
+ price: number | null;
97
+ news: IpoLockupStory[];
98
+ }
99
+
100
+ /** Lock-up estimates soonest first, then the ones that have ended, latest first. */
101
+ function byLockupEnd(deals: readonly IpoDeal[], now: Date): IpoDeal[] {
102
+ const today = now.toISOString().slice(0, 10);
103
+ const rank = (deal: IpoDeal) => (deal.lockup!.endsOn >= today ? 0 : 1);
104
+ return [...deals].sort((a, b) => rank(a) - rank(b)
105
+ || (rank(a) === 0 ? a.lockup!.endsOn.localeCompare(b.lockup!.endsOn) : b.lockup!.endsOn.localeCompare(a.lockup!.endsOn))
106
+ || a.id.localeCompare(b.id));
107
+ }
108
+
109
+ /** The most rows `--limit` lists. */
110
+ const MAX_ROWS = 500;
111
+
112
+ const SPAC_NOTE = "A SPAC's merger with its target (de-SPAC) is no IPO, so the calendar lists the SPACs' own IPOs.";
113
+
114
+ export function projectIpoCalendarHeadless(
115
+ payload: IpoCalendarPayload,
116
+ args: HeadlessPaneLoadArgs,
117
+ detail: IpoDealDetail | null = null,
118
+ now = new Date(),
119
+ ): HeadlessRowsResult {
85
120
  const query = typeof args.argument === "string" ? args.argument.trim() : "";
86
121
  const region = regionOption(args);
87
122
  const status = statusOption(args);
88
123
  const limit = Number(args.options.limit ?? 50);
89
- const now = new Date();
90
- const matching = sortIpoDeals(
91
- filterIpoDeals(payload.deals, region, query, now).filter((deal) => status === "all" || deal.status === status),
92
- DEFAULT_IPO_SORT,
93
- now,
94
- );
124
+ const keyword = ipoKeyword(query);
125
+ const found = filterIpoDeals(payload.deals, region, query, now).filter((deal) => status === "all" || deal.status === status);
126
+ const matching = keyword === "lockup" ? byLockupEnd(found, now) : sortIpoDeals(found, DEFAULT_IPO_SORT, now);
95
127
  const rows = matching.slice(0, limit).map((deal) => toRow(deal, now));
96
128
  const behind = marketsBehindText(marketsBehind(payload.sources, region, payload.deals));
129
+ const single = matching.length === 1 && query && !keyword ? matching[0]! : null;
130
+ const notes = [
131
+ ...rows.length < matching.length
132
+ ? [`Showing ${rows.length} of ${matching.length} deals; --limit ${Math.min(matching.length, MAX_ROWS)} lists ${matching.length <= MAX_ROWS ? "them all" : "more"}.`]
133
+ : [],
134
+ ...single ? ipoDetailSentences(single, detail?.price ?? null, detail?.news ?? [], now) : [],
135
+ ...keyword === "spac" || keyword === "de-spac" ? [SPAC_NOTE] : [],
136
+ ];
97
137
  return {
98
138
  columns: COLUMNS,
99
139
  rows,
100
140
  ...(behind ? { errors: [behind] } : {}),
141
+ ...(notes.length ? { notes } : {}),
101
142
  metadata: {
102
143
  asOf: payload.asOf,
103
144
  total: matching.length,
@@ -149,7 +190,15 @@ export const ipoCalendarHeadless: HeadlessPaneDefinition<"rows"> = {
149
190
  type: "integer",
150
191
  defaultValue: 50,
151
192
  minimum: 1,
152
- maximum: 500,
193
+ maximum: MAX_ROWS,
194
+ },
195
+ {
196
+ key: "from",
197
+ type: "string",
198
+ placeholder: "date",
199
+ description: `First listing date, as ${DATE_OPTION_EXAMPLES}; the calendar keeps a year of listings.`,
200
+ example: "--from 2025-11-01",
201
+ normalize: (value) => isoDateOption(value, "from"),
153
202
  },
154
203
  ],
155
204
  columns: COLUMNS,
@@ -164,7 +213,22 @@ export const ipoCalendarHeadless: HeadlessPaneDefinition<"rows"> = {
164
213
  async load(args, ctx) {
165
214
  // The default window has no filed or withdrawn deals, so a stage is asked for by name.
166
215
  const status = statusOption(args);
167
- const params = status === "all" ? {} : { status };
168
- return projectIpoCalendarHeadless(await fetchIpoCalendar(ctx.apiClient, ctx.signal, params), args);
216
+ const from = typeof args.options.from === "string" && args.options.from ? args.options.from : undefined;
217
+ const params = { ...(status === "all" ? {} : { status }), ...(from ? { from } : {}) };
218
+ const payload = await fetchIpoCalendar(ctx.apiClient, ctx.signal, params);
219
+ const projected = projectIpoCalendarHeadless(payload, args);
220
+ // One deal found: its price now and its lock-up news, which never fail the report.
221
+ const query = typeof args.argument === "string" ? args.argument.trim() : "";
222
+ const matched = projected.rows.length === 1 && query && !ipoKeyword(query)
223
+ ? payload.deals.find((deal) => deal.id === projected.rows[0]!.id) : undefined;
224
+ if (!matched?.symbol) return projected;
225
+ const [price, news] = await Promise.all([
226
+ matched.status === "listed"
227
+ ? ctx.marketData.getQuote(matched.symbol, matched.exchange ?? undefined).then((quote) => quote.price ?? null,
228
+ logFailure("plugins.ipo-calendar", "Load the price of an IPO failed", null))
229
+ : Promise.resolve(null),
230
+ loadIpoLockupNews(ctx.apiClient, matched).catch(logFailure("plugins.ipo-calendar", "Load an IPO's lock-up news failed", [] as IpoLockupStory[])),
231
+ ]);
232
+ return projectIpoCalendarHeadless(payload, args, { price, news });
169
233
  },
170
234
  };
@@ -0,0 +1,160 @@
1
+ import { apiClient } from "../../../api-client";
2
+ import type { IpoDeal } from "../../../api-client/ipo";
3
+ import type { HeadlessPaneApiClient } from "../../../types/plugin";
4
+ import { canonicalExchange } from "../../../utils/exchanges";
5
+ import { fetchIpoCalendar } from "./client";
6
+ import {
7
+ formatIpoAmount,
8
+ formatIpoPrice,
9
+ formatIpoReturn,
10
+ ipoLockupBasis,
11
+ ipoLockupText,
12
+ ipoReturnSince,
13
+ ipoTickerKey,
14
+ isLockupStory,
15
+ } from "./model";
16
+
17
+ /** How far back a listing's IPO is looked for: the calendar keeps a year, and a lock-up runs about half of one. */
18
+ const IPO_LOOKBACK_DAYS = 400;
19
+ const LOCKUP_NEWS_LIMIT = 3;
20
+
21
+ /** A lock-up story filed under a listing: its headline, publisher and day. */
22
+ export interface IpoLockupStory {
23
+ title: string;
24
+ source: string;
25
+ publishedAt: string;
26
+ url: string;
27
+ }
28
+
29
+ /** The stories filed under a listing about its lock-up since it listed (an early release, a waiver), newest first. */
30
+ export async function loadIpoLockupNews(
31
+ client: Pick<HeadlessPaneApiClient, "searchCloudNews">,
32
+ deal: IpoDeal,
33
+ ): Promise<IpoLockupStory[]> {
34
+ const key = ipoTickerKey(deal);
35
+ if (!key || !deal.lockup) return [];
36
+ const response = await client.searchCloudNews("lock-up", 20, undefined, { tickers: [key] });
37
+ const since = deal.listingDate ?? deal.firstDay?.session ?? "";
38
+ return response.items
39
+ .map((item) => ({
40
+ title: item.headline,
41
+ source: item.primaryPublisher || item.primarySource,
42
+ publishedAt: item.lastPublishedAt || item.firstPublishedAt,
43
+ url: item.primaryUrl,
44
+ }))
45
+ .filter((story) => isLockupStory(story.title) && story.publishedAt.slice(0, 10) >= since)
46
+ .slice(0, LOCKUP_NEWS_LIMIT);
47
+ }
48
+
49
+ function shiftDays(day: string, days: number): string {
50
+ return new Date(Date.parse(`${day}T00:00:00Z`) + days * 86_400_000).toISOString().slice(0, 10);
51
+ }
52
+
53
+ /**
54
+ * The IPO a listing came to market with in the last year or so, from the
55
+ * IPO calendar: the deal listed under its symbol on its exchange. Null for a
56
+ * listing older than that, one the calendar does not have, or one whose
57
+ * symbol two deals on its exchange share.
58
+ */
59
+ export async function loadListingIpo(
60
+ client: Pick<HeadlessPaneApiClient, "getCloudIpoCalendar">,
61
+ listing: { symbol: string; exchange?: string | null },
62
+ { signal, now = new Date() }: { signal?: AbortSignal; now?: Date } = {},
63
+ ): Promise<IpoDeal | null> {
64
+ const symbol = listing.symbol.trim().toUpperCase();
65
+ if (!symbol) return null;
66
+ const today = now.toISOString().slice(0, 10);
67
+ const payload = await fetchIpoCalendar(client, signal, { q: symbol, from: shiftDays(today, -IPO_LOOKBACK_DAYS), to: today });
68
+ const exchange = listing.exchange ? canonicalExchange(listing.exchange) : "";
69
+ const deals = payload.deals.filter((deal) => (deal.status === "listed" || deal.status === "priced")
70
+ && deal.symbol?.toUpperCase() === symbol
71
+ && (!exchange || (deal.exchange != null && canonicalExchange(deal.exchange) === exchange)));
72
+ return deals.length === 1 ? deals[0]! : null;
73
+ }
74
+
75
+ /**
76
+ * A deal's IPO in rows of a report, label and text:
77
+ * - IPO date 2026-05-07 on NYSE
78
+ * - Offer $26 (or "direct listing, no offer price")
79
+ * - Since IPO +62.7% ($26 to $42.30)
80
+ * - Lockup lockup ends ~2026-11-02 (estimate)
81
+ */
82
+ export function ipoMetricRows(deal: IpoDeal, price: number | null | undefined, now = new Date()): Array<[string, string]> {
83
+ const day = deal.listingDate ?? deal.firstDay?.session ?? null;
84
+ const rows: Array<[string, string]> = [];
85
+ if (day) rows.push(["IPO date", `${day}${deal.venue ? ` on ${deal.venue}` : ""}`]);
86
+ rows.push(["Offer", formatIpoPrice(deal, { full: true })]);
87
+ const since = ipoReturnSince(deal, price);
88
+ if (since) {
89
+ const from = since.basis === "offer" ? formatIpoAmount(deal, since.from) : `first trade ${formatIpoAmount(deal, since.from)}`;
90
+ rows.push(["Since IPO", `${formatIpoReturn(since.returnPct)} (${from} to ${formatIpoAmount(deal, price!)})`]);
91
+ }
92
+ const lockup = ipoLockupText(deal, now);
93
+ if (lockup) rows.push(["Lockup", lockup.replace(/^lockup /, "")]);
94
+ return rows;
95
+ }
96
+
97
+ /** The lines under those rows: what the lock-up estimate counts from, then the lock-up stories. */
98
+ export function ipoDetailNotes(deal: IpoDeal, news: readonly IpoLockupStory[]): string[] {
99
+ const basis = ipoLockupBasis(deal);
100
+ return [
101
+ ...basis ? [`Lockup estimate: ${basis}; underwriters can release shares early.`] : [],
102
+ ...news.map((story) => `Lock-up news ${story.publishedAt.slice(0, 10)}: ${story.title} (${story.source})`),
103
+ ];
104
+ }
105
+
106
+ /**
107
+ * A deal's IPO in sentences, for a report's notes:
108
+ * - "HAWK listed 2026-05-07 on NYSE at a $26 offer price; $42.30 now, +62.7% since the IPO."
109
+ * - "GYGY listed 2026-07-30 on Nasdaq: direct listing, no offer price; $30.10 now, +33.8% since the first trade at $22.50."
110
+ * - "HAWK lockup ends ~2026-11-02 (estimate): 180 days from the prospectus dated 2026-05-06; underwriters can release shares early."
111
+ * - "Lock-up news 2026-08-31: HawkEye 360 Confirms Early Lock-up Release for September 2nd (PR Newswire)"
112
+ */
113
+ export function ipoDetailSentences(deal: IpoDeal, price: number | null | undefined, news: readonly IpoLockupStory[], now = new Date()): string[] {
114
+ const name = deal.symbol ?? deal.company;
115
+ const day = deal.listingDate ?? deal.firstDay?.session ?? null;
116
+ const listed = deal.status === "listed" ? "listed" : "prices to list";
117
+ const where = `${day ? ` ${day}` : ""}${deal.venue ? ` on ${deal.venue}` : ""}`;
118
+ const offer = deal.offerPrice != null ? ` at a ${formatIpoAmount(deal, deal.offerPrice)} offer price`
119
+ : deal.listingType === "direct" ? `: ${formatIpoPrice(deal, { full: true })}` : "";
120
+ // The table's Return is the first day's; the sentence says which is which.
121
+ const firstDay = deal.firstDay?.returnPct != null && deal.offerPrice != null
122
+ ? `, ${formatIpoReturn(deal.firstDay.returnPct)} on the first day` : "";
123
+ const since = ipoReturnSince(deal, price);
124
+ const priceNow = since ? `; ${formatIpoAmount(deal, price!)} now, ${formatIpoReturn(since.returnPct)} since ${
125
+ since.basis === "offer" ? "the IPO" : `the first trade at ${formatIpoAmount(deal, since.from)}`}` : "";
126
+ const lockup = ipoLockupText(deal, now);
127
+ const basis = ipoLockupBasis(deal);
128
+ return [
129
+ `${name} ${listed}${where}${offer}${firstDay}${priceNow}.`,
130
+ ...lockup ? [`${name} ${lockup}${basis ? `: ${basis}` : ""}; underwriters can release shares early.`] : [],
131
+ ...news.map((story) => `Lock-up news ${story.publishedAt.slice(0, 10)}: ${story.title} (${story.source})`),
132
+ ];
133
+ }
134
+
135
+ /** A listing's recent IPO and its lock-up news, as `ticker` reads them beside the rest. */
136
+ export interface TickerIpo {
137
+ deal: IpoDeal;
138
+ news: IpoLockupStory[];
139
+ }
140
+
141
+ /** How long `ticker` waits for the IPO calendar before leaving the section out. */
142
+ const TICKER_IPO_WAIT_MS = 6_000;
143
+
144
+ /**
145
+ * A stock's IPO of the last year or so with its lock-up news, for `ticker`;
146
+ * null for any other listing, and when the calendar does not answer in time.
147
+ */
148
+ export async function loadTickerIpo(
149
+ listing: { symbol: string; exchange?: string | null },
150
+ client: Pick<HeadlessPaneApiClient, "getCloudIpoCalendar" | "searchCloudNews"> = apiClient,
151
+ ): Promise<TickerIpo | null> {
152
+ const signal = AbortSignal.timeout(TICKER_IPO_WAIT_MS);
153
+ const deal = await loadListingIpo(client, listing, { signal });
154
+ if (!deal) return null;
155
+ const news = await loadIpoLockupNews(client, deal).catch(() => {
156
+ // Intentional ignore: the IPO's rows stand without its lock-up news.
157
+ return [];
158
+ });
159
+ return { deal, news };
160
+ }
@@ -161,16 +161,36 @@ const REGION_WORDS: Partial<Record<IpoRegion, readonly string[]>> = {
161
161
  apac: ["apac", "asia", "asia-pacific"],
162
162
  };
163
163
 
164
+ /**
165
+ * Words that name a kind of deal rather than one: `lockup` (`lock-up`,
166
+ * `lock up`, `lockups`) lists the deals with a lock-up estimate, `spac` the
167
+ * SPACs' own IPOs, and `de-spac` (`despac`) those too: a SPAC's merger with
168
+ * its target is no IPO, so the calendar has none of its own.
169
+ */
170
+ export type IpoKeyword = "lockup" | "spac" | "de-spac";
171
+
172
+ export function ipoKeyword(query: string): IpoKeyword | null {
173
+ const word = query.trim().toLowerCase().replace(/[\s_-]+/g, "");
174
+ if (/^lockups?$/.test(word)) return "lockup";
175
+ if (/^spacs?$/.test(word)) return "spac";
176
+ if (/^despacs?$/.test(word)) return "de-spac";
177
+ return null;
178
+ }
179
+
164
180
  /**
165
181
  * Company in either script, symbol, venue, country code or name, and the
166
182
  * status the board shows. The word `pending` is no status of its own: it is
167
183
  * every deal not priced yet, offering and overdue ones included.
168
184
  * A region's name (`europe`, `asia`) lists the region's deals, on whichever tab is open.
185
+ * A kind of deal (`lockup`, `spac`, `de-spac`, see ipoKeyword) lists that kind.
169
186
  */
170
187
  function matchesIpoQuery(deal: IpoDeal, query: string, now: Date): boolean {
171
188
  const needle = query.trim().toLowerCase();
172
189
  if (!needle) return true;
173
190
  if (needle === "pending") return NOT_PRICED_STATUSES.includes(deal.status);
191
+ const keyword = ipoKeyword(needle);
192
+ if (keyword === "lockup") return !!deal.lockup;
193
+ if (keyword) return deal.listingType === "spac";
174
194
  if (REGION_WORDS[deal.region]?.includes(needle.replace(/\s+/g, "-"))) return true;
175
195
  return [
176
196
  deal.company, deal.companyLocal, deal.symbol, deal.venue, deal.mic, deal.exchange,
@@ -343,9 +363,17 @@ function amount(value: number): string {
343
363
  return value.toLocaleString("en-US", { minimumFractionDigits: whole ? 0 : 2, maximumFractionDigits: 2 });
344
364
  }
345
365
 
346
- /** "$18", "HK$26.20-28.50", "240-260p", "CHF 45": the offer price once set, else the range. */
347
- export function formatIpoPrice(deal: IpoDeal): string {
366
+ /** What a direct listing with no price reads in a column; a report's row says why in full. */
367
+ const DIRECT_LISTING_PRICE = "direct listing";
368
+ const DIRECT_LISTING_NO_PRICE = "direct listing, no offer price";
369
+
370
+ /**
371
+ * "$18", "HK$26.20-28.50", "240-260p", "CHF 45": the offer price once set, else the range.
372
+ * A direct listing sells no shares at a price: "direct listing" (`full`: "direct listing, no offer price").
373
+ */
374
+ export function formatIpoPrice(deal: IpoDeal, { full = false }: { full?: boolean } = {}): string {
348
375
  const low = deal.offerPrice ?? deal.priceLow ?? deal.priceHigh;
376
+ if (low == null && deal.listingType === "direct") return full ? DIRECT_LISTING_NO_PRICE : DIRECT_LISTING_PRICE;
349
377
  if (low == null) return MISSING;
350
378
  const high = deal.offerPrice == null && deal.priceLow != null && deal.priceHigh != null && deal.priceHigh !== deal.priceLow
351
379
  ? deal.priceHigh
@@ -387,3 +415,71 @@ export function formatIpoDate(value: string | null, now = new Date()): string {
387
415
  ? `${month} ${date.getUTCDate()}`
388
416
  : `${month} ${date.getUTCFullYear()}`;
389
417
  }
418
+
419
+ /** "~2026-11-02": when a deal's lock-up is estimated to end; "—" without an estimate. */
420
+ export function formatIpoLockupEnd(endsOn: string | null | undefined): string {
421
+ return endsOn ? `~${endsOn}` : MISSING;
422
+ }
423
+
424
+ /** Today in UTC, YYYY-MM-DD. */
425
+ function utcToday(now: Date): string {
426
+ return now.toISOString().slice(0, 10);
427
+ }
428
+
429
+ /**
430
+ * "lockup ends ~2026-11-02 (estimate)", "lockup ended ~2026-07-28 (estimate)":
431
+ * a deal's lock-up estimate in words; null without one.
432
+ */
433
+ export function ipoLockupText(deal: Pick<IpoDeal, "lockup">, now = new Date()): string | null {
434
+ if (!deal.lockup) return null;
435
+ const verb = deal.lockup.endsOn < utcToday(now) ? "ended" : "ends";
436
+ return `lockup ${verb} ~${deal.lockup.endsOn} (estimate)`;
437
+ }
438
+
439
+ /**
440
+ * What the estimate counts from, as the server made it. The period is the
441
+ * prospectus's own (`prospectus`) or the typical 180 days; it counts from
442
+ * the prospectus date when one was read, else from the first session:
443
+ * - "180 days from the prospectus dated 2026-05-06"
444
+ * - "90 days from the first session on 2026-05-07, the period the prospectus states"
445
+ * - "the typical 180 days from the prospectus dated 2026-05-06"
446
+ * - "the typical 180 days from the first session on 2026-05-07; the prospectus has not been read"
447
+ */
448
+ export function ipoLockupBasis(deal: Pick<IpoDeal, "lockup" | "listingDate">): string | null {
449
+ const lockup = deal.lockup;
450
+ if (!lockup) return null;
451
+ const stated = lockup.basis === "prospectus";
452
+ const from = lockup.prospectusDate
453
+ ? `from the prospectus dated ${lockup.prospectusDate}`
454
+ : `from the first session${deal.listingDate ? ` on ${deal.listingDate}` : ""}`;
455
+ if (stated) return `${lockup.days} days ${from}${lockup.prospectusDate ? "" : ", the period the prospectus states"}`;
456
+ // A prospectus read with no period it states (or none of a lock-up's length) is still read.
457
+ const read = lockup.prospectusDate != null || lockup.prospectusUrl != null;
458
+ return `the typical ${lockup.days} days ${from}${read ? "" : "; the prospectus has not been read"}`;
459
+ }
460
+
461
+ /** A price in the deal's own currency: "$26", "HK$7.20". */
462
+ export function formatIpoAmount(deal: Pick<IpoDeal, "currency">, value: number): string {
463
+ return formatIpoPrice({ offerPrice: value, priceLow: null, priceHigh: null, currency: deal.currency, listingType: "ipo" } as IpoDeal);
464
+ }
465
+
466
+ /**
467
+ * What a listed deal's price has done since it listed: from the offer price,
468
+ * else (a direct listing) from its first trade. Null without a price now or a
469
+ * price to measure from.
470
+ */
471
+ export function ipoReturnSince(deal: IpoDeal, price: number | null | undefined): { from: number; basis: "offer" | "first trade"; returnPct: number } | null {
472
+ if (price == null || !Number.isFinite(price) || price <= 0) return null;
473
+ const offer = deal.offerPrice;
474
+ if (offer != null && offer > 0) return { from: offer, basis: "offer", returnPct: price / offer - 1 };
475
+ const open = deal.firstDay?.open;
476
+ if (deal.listingType === "direct" && open != null && open > 0) return { from: open, basis: "first trade", returnPct: price / open - 1 };
477
+ return null;
478
+ }
479
+
480
+ /** The lock-up stories worth listing beside a deal: on or after its listing, about its lock-up. */
481
+ const LOCKUP_WORDS = /\block[\s-]?ups?\b/i;
482
+
483
+ export function isLockupStory(title: string): boolean {
484
+ return LOCKUP_WORDS.test(title);
485
+ }
@@ -5,10 +5,18 @@ import type { ChartRequest, InstrumentRef } from "../../../market-data/request-t
5
5
  import type { QueryEntry } from "../../../market-data/result-types";
6
6
  import type { DataProvider } from "../../../types/data-provider";
7
7
  import type { PricePoint } from "../../../types/financials";
8
- import { realizedVolatility, realizedVolatilityCadenceIssue } from "../shared/volatility";
8
+ import { realizedVolatilityCadenceIssue, rollingRealizedVolatility } from "../shared/volatility";
9
9
  import { NO_FATE, type ListingFate } from "../../../tickers/listing-fate";
10
10
 
11
11
  export type IvMethod = "quote-mid" | "trade-close";
12
+ /**
13
+ * Which prices a trade-close reading was solved from: each option's closing
14
+ * print, kept because it sat inside the closing two-sided quote (`print`), the
15
+ * quote midpoint, used where a print sat outside it (`mid`), or both
16
+ * (`mixed`). Absent when no closing quote was stored to check the prints
17
+ * against, as for most history before the checks began.
18
+ */
19
+ export type IvCloseBasis = "print" | "mid" | "mixed";
12
20
  export type IvCoverageStatus = "ready" | "backfilling" | "queued" | "unavailable";
13
21
  export interface IvPoint {
14
22
  sessionDate: string;
@@ -27,11 +35,14 @@ export interface IvPoint {
27
35
  */
28
36
  put25_30?: number | null;
29
37
  call25_30?: number | null;
38
+ /** Prices a trade close was solved from; see `IvCloseBasis`. Absent from older servers. */
39
+ basis?: IvCloseBasis | null;
30
40
  }
31
41
  export interface IvStats {
32
42
  value: number;
33
43
  date: string;
34
44
  method: IvMethod;
45
+ basis?: IvCloseBasis | null;
35
46
  rank: number | null;
36
47
  percentile: number | null;
37
48
  low: number | null;
@@ -54,6 +65,7 @@ interface IvReading {
54
65
  iv90: number | null;
55
66
  iv180: number | null;
56
67
  iv365: number | null;
68
+ basis?: IvCloseBasis | null;
57
69
  }
58
70
  export interface IvHistoryPayload {
59
71
  version: 1;
@@ -155,6 +167,37 @@ export function createHvDependencies(marketData?: DataProvider): HvDependencies
155
167
  /** Screen rows with no reading whose symbol's fate is looked up; a long list of them is a backfill, not a lookup. */
156
168
  const MAX_FATE_LOOKUPS = 10;
157
169
 
170
+ /**
171
+ * A symbol's realized volatility over one window: the newest, and after every
172
+ * session's close, so a reading is divided by the HV of its own date and not
173
+ * of today.
174
+ */
175
+ export interface ScreenHv {
176
+ /** HV over the newest window of closes. */
177
+ value: number | null;
178
+ /** Date of the newest daily bar: the newest session the symbol's prices reach. */
179
+ date: string | null;
180
+ /** HV after each session's close, oldest first; sessions without a full window are left out. */
181
+ series: ReadonlyArray<readonly [date: string, value: number]>;
182
+ }
183
+
184
+ const barDay = (value: Date) => new Date(value).toISOString().slice(0, 10);
185
+
186
+ /** `ScreenHv` from daily closes; a provider that fell back to weekly or intraday bars would misstate daily HV, so it has none. */
187
+ export function screenHv(history: readonly PricePoint[], window: number): ScreenHv {
188
+ if (realizedVolatilityCadenceIssue(history)) return { value: null, date: null, series: [] };
189
+ const rolling = rollingRealizedVolatility(history, { windows: [window] });
190
+ const series = rolling.flatMap((point) => {
191
+ const value = point.values[window];
192
+ return value == null ? [] : [[barDay(point.date), value] as const];
193
+ });
194
+ const newest = history.reduce<string | null>((latest, point) => {
195
+ const date = barDay(point.date);
196
+ return latest == null || date > latest ? date : latest;
197
+ }, null);
198
+ return { value: rolling.at(-1)?.values[window] ?? null, date: newest, series };
199
+ }
200
+
158
201
  /**
159
202
  * HV for a screen's symbols (`loadRealizedVolatilities`), started at once,
160
203
  * and what became of the ones its rows have no reading for, at most
@@ -166,11 +209,11 @@ const MAX_FATE_LOOKUPS = 10;
166
209
  */
167
210
  export async function loadScreenVolatilities(
168
211
  instruments: readonly InstrumentRef[], rows: Promise<readonly IvScreenRow[]>, window: number,
169
- options: { signal?: AbortSignal; onValue?: (symbol: string, value: number | null) => void; onFates?: (fates: ReadonlyMap<string, ListingFate>) => void } = {},
212
+ options: { signal?: AbortSignal; onValue?: (symbol: string, value: ScreenHv | null) => void; onFates?: (fates: ReadonlyMap<string, ListingFate>) => void } = {},
170
213
  dependencies: HvDependencies = createHvDependencies(),
171
- ): Promise<{ hv: Map<string, number | null>; fates: Map<string, ListingFate> }> {
214
+ ): Promise<{ hv: Map<string, ScreenHv | null>; fates: Map<string, ListingFate> }> {
172
215
  const moved = new Set<string>();
173
- const onValue = options.onValue && ((symbol: string, value: number | null) => { if (!moved.has(symbol)) options.onValue!(symbol, value); });
216
+ const onValue = options.onValue && ((symbol: string, value: ScreenHv | null) => { if (!moved.has(symbol)) options.onValue!(symbol, value); });
174
217
  const [hv, fates] = await Promise.all([
175
218
  loadRealizedVolatilities(instruments, window, { ...options, onValue }, dependencies),
176
219
  rows.then(async (screened) => {
@@ -189,20 +232,18 @@ export async function loadScreenVolatilities(
189
232
 
190
233
  /** Close-to-close HV over one window for each symbol from one year of daily closes, four at a time. */
191
234
  async function loadRealizedVolatilities(
192
- instruments: readonly InstrumentRef[], window: number, options: { signal?: AbortSignal; onValue?: (symbol: string, value: number | null) => void } = {},
235
+ instruments: readonly InstrumentRef[], window: number, options: { signal?: AbortSignal; onValue?: (symbol: string, value: ScreenHv | null) => void } = {},
193
236
  dependencies: HvDependencies = createHvDependencies(),
194
- ): Promise<Map<string, number | null>> {
195
- const result = new Map<string, number | null>();
237
+ ): Promise<Map<string, ScreenHv | null>> {
238
+ const result = new Map<string, ScreenHv | null>();
196
239
  let next = 0;
197
240
  const worker = async () => {
198
241
  while (next < instruments.length && !options.signal?.aborted) {
199
242
  const instrument = instruments[next++]!;
200
- let value: number | null = null;
243
+ let value: ScreenHv | null = null;
201
244
  try {
202
245
  const entry = await dependencies.loadChart({ instrument, bufferRange: "1Y", granularity: "resolution", resolution: "1d" });
203
- const history = resolveEntryValue(entry) ?? [];
204
- // A provider that fell back to weekly or intraday bars would misstate daily HV.
205
- value = realizedVolatilityCadenceIssue(history) ? null : realizedVolatility(history, window);
246
+ value = screenHv(resolveEntryValue(entry) ?? [], window);
206
247
  } catch { value = null; }
207
248
  result.set(instrument.symbol, value);
208
249
  options.onValue?.(instrument.symbol, value);
@@ -1,26 +1,32 @@
1
- import type { IvMethod } from "./client";
2
- import type { IvStatRow, IvStatUnit, RichCheap, RichCheapDates } from "./model";
1
+ import type { IvCloseBasis, IvMethod } from "./client";
2
+ import { readingSource, type IvStatRow, type IvStatUnit, type RichCheap, type RichCheapDates } from "./model";
3
3
 
4
4
  /**
5
5
  * VCA's sign conventions, and what VIX is beside an at-the-money IV30, one
6
- * line each: under the table, in the shot and in the report's notes. The skew
7
- * is the stored 25-delta put and call IVs at a 30-day constant maturity, from
8
- * the latest quote capture; both differences are decimal IV shown in points.
6
+ * line each: under the table, in the shot and in the report's notes. The risk
7
+ * reversal is the stored 25-delta call and put IVs at a 30-day constant
8
+ * maturity, from the latest quote capture, signed as OVDV signs it; both
9
+ * differences are decimal IV shown in points. Its line is there only while a
10
+ * risk reversal column is.
9
11
  */
10
- export const VCA_DEFINITIONS = [
11
- "25D skew: 30-day 25-delta put IV minus 25-delta call IV, in vol points; positive means puts are richer. 30-90: IV30 minus IV90.",
12
- "VIX is Cboe's 30-day S&P 500 implied vol from the whole SPX strike strip, skew included; IV30 here is at the money, so VIX usually reads higher.",
13
- ] as const;
12
+ export function vcaDefinitions({ skew }: { skew: boolean }): string[] {
13
+ return [
14
+ ...(skew ? ["25D RR: 30-day 25-delta call IV minus 25-delta put IV, in vol points, as OVDV's risk reversal; positive means calls are richer."] : []),
15
+ "30-90: IV30 minus IV90. IV/HV: IV30 over the HV20 of the same date.",
16
+ "VIX is Cboe's 30-day S&P 500 implied vol from the whole SPX strike strip, skew included; IV30 here is at the money, so VIX usually reads higher.",
17
+ ];
18
+ }
14
19
 
15
20
  /** Words for implied volatility that are no function's code: `fn IVOL` names HIVG and OVDV, which list them as `suggestFor`. */
16
21
  export const IMPLIED_VOL_WORDS = ["IVOL", "IV", "OVOL", "IMPVOL", "IMPLIEDVOL"];
17
22
 
18
23
  /**
19
- * A skew column's header names its sign, since the conventions differ: the
20
- * 25-delta skew is put minus call (HIVG, VCA), the risk reversal call minus
21
- * put (OVDV). "skew25d put-call (pts)": the stem a script keys on, the
22
- * sign, and vol points in the unit's usual place, as in "IV30 (%)". No
23
- * comma, so a CSV header needs no quoting.
24
+ * A skew column's header names its sign. The risk reversal is call minus put
25
+ * wherever it is shown (OVDV, VCA, HIVG's rr25d), so positive means calls are
26
+ * richer; HIVG's skew25d keeps the put-minus-call sign it was first exported
27
+ * with, the negative of that. "rr25d call-put (pts)": the stem a script keys
28
+ * on, the sign, and vol points in the unit's usual place, as in "IV30 (%)".
29
+ * No comma, so a CSV header needs no quoting.
24
30
  */
25
31
  export const skewHeader = (stem: string, sign: string) => `${stem} ${sign} (pts)`;
26
32
 
@@ -43,22 +49,29 @@ export function formatStat(value: number | null | undefined, unit: IvStatUnit):
43
49
  /**
44
50
  * How a stored reading was taken. A quote capture is the option quote mids
45
51
  * from the minutes before the close, taken once a session and never updated:
46
- * it is not a live feed, so it is not called live.
52
+ * it is not a live feed, so it is not called live. A close solved from quote
53
+ * midpoints, where its prints were off the market, says so.
54
+ */
55
+ export function statSource(row: Pick<IvStatRow, "method"> & { basis?: IvCloseBasis | null }): string {
56
+ return row.method === "prices" ? "closes" : row.method ? readingSource(row.method, row.basis) : "";
57
+ }
58
+ /**
59
+ * "2026-10-09 quote": the session a reading belongs to and how it was taken.
60
+ * A stale reading says so instead, "2026-05-18 close, stale", and in the short
61
+ * form "05-18 stale".
47
62
  */
48
- const readingSource = (method: IvMethod | null | undefined) => method === "quote-mid" ? "quote" : "close";
49
- export function statSource(row: Pick<IvStatRow, "method">): string {
50
- return row.method === "prices" ? "closes" : row.method ? readingSource(row.method) : "";
63
+ export function readingLabel(date: string, method: IvMethod | null | undefined, short = false,
64
+ extra: { basis?: IvCloseBasis | null; stale?: boolean } = {}): string {
65
+ if (extra.stale) return short ? `${shortDate(date)} stale` : `${date} ${readingSource(method, extra.basis)}, stale`;
66
+ return `${short ? shortDate(date) : date} ${readingSource(method, extra.basis)}`;
51
67
  }
52
- /** "2026-10-09 quote": the session a reading belongs to and how it was taken. */
53
- export const readingLabel = (date: string, method: IvMethod | null | undefined, short = false) =>
54
- `${short ? shortDate(date) : date} ${readingSource(method)}`;
55
68
  /**
56
69
  * The dates every row agrees on, said once: the reading's, and the trade close
57
70
  * its rank is measured on. `short` is for a footer with little room: month and
58
71
  * day, and no window.
59
72
  */
60
73
  export function sharedDates({ reading, rank }: Pick<RichCheapDates, "reading" | "rank">, short = false): string[] {
61
- if (reading?.method === "trade-close" && reading.date === rank) return [readingLabel(reading.date, reading.method, short)];
62
- return [reading && `IV30 ${readingLabel(reading.date, reading.method, short)}`,
74
+ if (reading?.method === "trade-close" && reading.date === rank) return [readingLabel(reading.date, reading.method, short, { basis: reading.basis })];
75
+ return [reading && `IV30 ${readingLabel(reading.date, reading.method, short, { basis: reading.basis })}`,
63
76
  rank && (short ? `IVR/IVP ${shortDate(rank)} close` : `IVR/IVP vs 52 weeks to ${rank} close`)].filter((part): part is string => !!part);
64
77
  }