@gloom-sh/terminal 0.18.5 → 0.19.0

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 (436) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +140 -7
  3. package/src/api-client/cot.ts +6 -2
  4. package/src/api-client/credit-documents.ts +6 -0
  5. package/src/api-client/data.ts +17 -6
  6. package/src/api-client/equity-screener.ts +24 -0
  7. package/src/api-client/fund-facts.ts +45 -4
  8. package/src/api-client/index.ts +11 -0
  9. package/src/api-client/market-discovery.ts +10 -0
  10. package/src/api-client/market-movers.ts +7 -0
  11. package/src/api-client/members.ts +1 -1
  12. package/src/api-client/perps.ts +43 -0
  13. package/src/api-client/research-activity.ts +3 -2
  14. package/src/api-client/types.ts +70 -2
  15. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  16. package/src/app/runtime/broker-import.ts +0 -2
  17. package/src/app/runtime/plugin-bindings.ts +73 -56
  18. package/src/architecture/pane-tabs-allowlist.json +3 -0
  19. package/src/architecture/ttl-caches-allowlist.json +19 -0
  20. package/src/brokers/sync-broker-instance.ts +2 -8
  21. package/src/capabilities/chart-series.ts +1 -1
  22. package/src/cli/commands/crypto-hints.ts +40 -24
  23. package/src/cli/commands/market.ts +72 -26
  24. package/src/cli/commands/options-chain.ts +48 -7
  25. package/src/cli/commands/options-leaps.ts +9 -3
  26. package/src/cli/commands/overview.ts +61 -12
  27. package/src/cli/commands/search.ts +21 -1
  28. package/src/cli/commands/themes.ts +11 -2
  29. package/src/cli/commands/ticker.ts +55 -34
  30. package/src/cli/company-name-hint.ts +41 -0
  31. package/src/cli/earnings-calendar.ts +1 -1
  32. package/src/cli/errors.ts +8 -0
  33. package/src/cli/glossary.ts +25 -2
  34. package/src/cli/history-rows.ts +57 -9
  35. package/src/cli/index.ts +2 -0
  36. package/src/cli/listing-arg.ts +180 -52
  37. package/src/cli/output-file.ts +68 -0
  38. package/src/cli/pane-functions/capabilities.ts +3 -0
  39. package/src/cli/pane-functions/catalog.ts +80 -7
  40. package/src/cli/pane-functions/dom.ts +13 -3
  41. package/src/cli/pane-functions/freshness.ts +86 -3
  42. package/src/cli/pane-functions/function-help.ts +13 -10
  43. package/src/cli/pane-functions/headless.ts +17 -7
  44. package/src/cli/pane-functions/index.ts +49 -14
  45. package/src/cli/pane-functions/market-session.ts +66 -4
  46. package/src/cli/pane-functions/options.ts +19 -1
  47. package/src/cli/pane-functions/resolver.ts +19 -8
  48. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  49. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  50. package/src/cli/quote-answer.ts +22 -11
  51. package/src/cli/ticker-resolution.ts +3 -2
  52. package/src/components/chart/composite/composite-chart.tsx +4 -0
  53. package/src/components/chart/composite/interactions.ts +6 -0
  54. package/src/components/chart/composite/legend.tsx +45 -7
  55. package/src/components/chart/composite/panel-gestures.ts +33 -0
  56. package/src/components/chart/composite/panel-surface.tsx +52 -3
  57. package/src/components/chart/composite/rasterizer.ts +24 -13
  58. package/src/components/chart/composite/scene.ts +39 -4
  59. package/src/components/chart/composite/series-cues.ts +14 -0
  60. package/src/components/chart/composite/text-renderer.ts +9 -2
  61. package/src/components/chart/composite/tool-catalog.ts +36 -0
  62. package/src/components/chart/composite/tools.ts +203 -44
  63. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  64. package/src/components/command-bar/assist/inventory.ts +2 -1
  65. package/src/components/command-bar/commands/direct/index.ts +28 -13
  66. package/src/components/command-bar/pane-templates/items.ts +3 -2
  67. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  68. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  69. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  70. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  71. package/src/components/onboarding/wizard-positions.ts +3 -3
  72. package/src/components/ticker/list-table-view.tsx +4 -1
  73. package/src/components/ui/choice-dialog.tsx +23 -18
  74. package/src/components/ui/choice-groups.ts +18 -0
  75. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  76. package/src/components/ui/data-table/opentui/model.ts +26 -1
  77. package/src/components/ui/data-table/types.ts +2 -0
  78. package/src/components/ui/list-view.tsx +8 -4
  79. package/src/components/ui/query-bar-keys.tsx +2 -0
  80. package/src/components/ui/query-bar.tsx +1 -0
  81. package/src/components/ui/select-button.tsx +5 -1
  82. package/src/components/ui/select-field.tsx +1 -0
  83. package/src/components/ui/table-layout.ts +8 -5
  84. package/src/core/app-runtime.ts +2 -0
  85. package/src/core/app-services.ts +2 -2
  86. package/src/data/config/chart-settings.ts +1 -1
  87. package/src/data/json-ticker-repository.ts +17 -8
  88. package/src/data/ticker-repository.ts +16 -9
  89. package/src/data/ticker-writes.ts +81 -0
  90. package/src/data/ttl-cache.ts +165 -0
  91. package/src/docs/snapshot.json +71 -137
  92. package/src/i18n/es.ts +18 -2
  93. package/src/i18n/ja.ts +18 -2
  94. package/src/i18n/ko.ts +18 -2
  95. package/src/i18n/zh-cn.ts +18 -2
  96. package/src/i18n/zh-tw.ts +18 -2
  97. package/src/market-data/depositary-pair.ts +2 -2
  98. package/src/market-data/fund-facts.ts +50 -0
  99. package/src/market-data/market/change-basis.ts +16 -3
  100. package/src/market-data/market/format.ts +25 -0
  101. package/src/market-data/market/freshness.ts +2 -1
  102. package/src/market-data/options-alternatives.ts +81 -4
  103. package/src/news/aggregator.ts +113 -9
  104. package/src/news/cloud-session.ts +21 -0
  105. package/src/news/hooks.ts +1 -0
  106. package/src/news/news-model.ts +1 -0
  107. package/src/news/types.ts +11 -0
  108. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  109. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  110. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  111. package/src/plugins/builtin/alerts/cli.ts +336 -77
  112. package/src/plugins/builtin/alerts/command.ts +25 -1
  113. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  114. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  115. package/src/plugins/builtin/alerts/format.ts +75 -5
  116. package/src/plugins/builtin/alerts/headless.ts +24 -6
  117. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  118. package/src/plugins/builtin/alerts/history.ts +1 -1
  119. package/src/plugins/builtin/alerts/index.tsx +79 -16
  120. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  121. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  122. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  123. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  124. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  125. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  126. package/src/plugins/builtin/alerts/storage.ts +12 -8
  127. package/src/plugins/builtin/alerts/types.ts +4 -0
  128. package/src/plugins/builtin/analytics/index.tsx +2 -1
  129. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  130. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  131. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  132. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  134. package/src/plugins/builtin/attention/index.tsx +2 -1
  135. package/src/plugins/builtin/attention/listing.ts +14 -0
  136. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  137. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  138. package/src/plugins/builtin/buildout/format.ts +38 -0
  139. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  140. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  141. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  142. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  143. package/src/plugins/builtin/changelog/index.tsx +9 -6
  144. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  145. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  146. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  147. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  148. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  149. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  150. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  151. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  152. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  153. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  154. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  155. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  156. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  157. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  158. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  159. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  160. package/src/plugins/builtin/company-attention/client.ts +8 -9
  161. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  162. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  163. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  164. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  165. package/src/plugins/builtin/correlation/headless.ts +13 -5
  166. package/src/plugins/builtin/correlation/history.ts +22 -0
  167. package/src/plugins/builtin/cot/client.ts +22 -18
  168. package/src/plugins/builtin/cot/headless.ts +15 -10
  169. package/src/plugins/builtin/cot/model.ts +14 -1
  170. package/src/plugins/builtin/cot/pane.tsx +22 -14
  171. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  172. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  173. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  174. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  175. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  176. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  177. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  178. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  179. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  180. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  181. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  182. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  183. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  184. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  185. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  186. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  187. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  188. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  189. package/src/plugins/builtin/docs/targets.ts +5 -1
  190. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  191. package/src/plugins/builtin/earnings/board.tsx +62 -71
  192. package/src/plugins/builtin/earnings/headless.ts +40 -4
  193. package/src/plugins/builtin/earnings/scope.ts +31 -43
  194. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  195. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  196. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  197. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  198. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  199. package/src/plugins/builtin/econ/types.ts +2 -0
  200. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  201. package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
  202. package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
  203. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  204. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  205. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  206. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  207. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  208. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  209. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  210. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  211. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  212. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  213. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  214. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  215. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  216. package/src/plugins/builtin/gpu/headless.ts +8 -2
  217. package/src/plugins/builtin/help/function-index.ts +4 -1
  218. package/src/plugins/builtin/help/index.tsx +1 -0
  219. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  220. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  221. package/src/plugins/builtin/holders/headless.ts +18 -1
  222. package/src/plugins/builtin/iv-history/client.ts +52 -11
  223. package/src/plugins/builtin/iv-history/format.ts +36 -23
  224. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  225. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  226. package/src/plugins/builtin/iv-history/model.ts +163 -27
  227. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  228. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  229. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  230. package/src/plugins/builtin/jobs/client.ts +25 -29
  231. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  232. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  233. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  234. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  235. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  236. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  237. package/src/plugins/builtin/market-movers/client.ts +25 -7
  238. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  239. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  240. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  241. package/src/plugins/builtin/market-movers/model.ts +4 -1
  242. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  243. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  244. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  245. package/src/plugins/builtin/market-movers/table.ts +17 -5
  246. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  247. package/src/plugins/builtin/news/index.tsx +1 -0
  248. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  249. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  250. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  251. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  252. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  253. package/src/plugins/builtin/openfda/client.ts +14 -2
  254. package/src/plugins/builtin/openfda/index.tsx +3 -2
  255. package/src/plugins/builtin/openfda/model.ts +9 -0
  256. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  257. package/src/plugins/builtin/options/analytics.ts +29 -5
  258. package/src/plugins/builtin/options/enrichment.ts +17 -5
  259. package/src/plugins/builtin/options/table.ts +5 -2
  260. package/src/plugins/builtin/options/types.ts +2 -0
  261. package/src/plugins/builtin/options/view.tsx +17 -4
  262. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  263. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  264. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  265. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  266. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  267. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  268. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  269. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  270. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  271. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  272. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  273. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  274. package/src/plugins/builtin/pe-band/model.ts +12 -49
  275. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  276. package/src/plugins/builtin/perps/cascade.ts +236 -0
  277. package/src/plugins/builtin/perps/client.ts +56 -2
  278. package/src/plugins/builtin/perps/headless.ts +65 -9
  279. package/src/plugins/builtin/perps/index.tsx +7 -5
  280. package/src/plugins/builtin/perps/model.ts +18 -4
  281. package/src/plugins/builtin/perps/pane.tsx +19 -22
  282. package/src/plugins/builtin/perps/status-line.ts +25 -0
  283. package/src/plugins/builtin/plugin-module.ts +5 -1
  284. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  285. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  286. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  287. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  288. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  289. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  290. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  291. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  292. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  293. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  294. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  295. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  296. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  297. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  298. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  299. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  300. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  301. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  302. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  303. package/src/plugins/builtin/research/event-model.ts +10 -8
  304. package/src/plugins/builtin/research/headless.ts +1 -1
  305. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  306. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  307. package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
  308. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  309. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  310. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  311. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  312. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  313. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  314. package/src/plugins/builtin/scanner/headless.ts +204 -35
  315. package/src/plugins/builtin/scanner/index.ts +16 -5
  316. package/src/plugins/builtin/scanner/live-headless.ts +27 -3
  317. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  318. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  319. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  320. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  321. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  322. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  323. package/src/plugins/builtin/sec/headless.ts +2 -1
  324. package/src/plugins/builtin/series-inventory.ts +2 -0
  325. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  326. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  327. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  328. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  329. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  330. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  331. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  332. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  333. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  334. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  335. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  336. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  337. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  338. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  339. package/src/plugins/builtin/themes/model.ts +16 -7
  340. package/src/plugins/builtin/themes/pane.tsx +5 -5
  341. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  342. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  343. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  344. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  345. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  346. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  347. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  348. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  349. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  350. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  351. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  352. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  353. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  354. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  355. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  357. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  358. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  359. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  360. package/src/plugins/builtin/volatility/index.tsx +2 -2
  361. package/src/plugins/builtin/volatility/pane.tsx +2 -2
  362. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  363. package/src/plugins/catalog-manifest.ts +2 -1
  364. package/src/plugins/marketplace/model.ts +18 -5
  365. package/src/portfolio/futures-positions.ts +6 -3
  366. package/src/portfolio/position-quantity.ts +15 -9
  367. package/src/remote/resources.ts +2 -1
  368. package/src/renderers/dom/data-table/row.tsx +26 -0
  369. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  370. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  371. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  372. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  373. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  374. package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
  375. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  376. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  377. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  378. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  379. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  380. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  381. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  382. package/src/renderers/electrobun/view/main.tsx +24 -1
  383. package/src/renderers/electrobun/view/news-source.ts +3 -1
  384. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  385. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  386. package/src/renderers/opentui/start.tsx +5 -0
  387. package/src/series/registry.ts +1 -0
  388. package/src/series/types.ts +6 -0
  389. package/src/series/validate.ts +1 -0
  390. package/src/sources/gloomberb-cloud/index.ts +1 -0
  391. package/src/sources/gloomberb-cloud/news.ts +47 -7
  392. package/src/sources/provider-router/financials.ts +15 -3
  393. package/src/sources/provider-router/index.ts +8 -5
  394. package/src/state/ticker-cleanup.ts +50 -0
  395. package/src/sync/controller.ts +4 -3
  396. package/src/sync/core-contributors.ts +90 -31
  397. package/src/telemetry/stall-reports-dom.ts +38 -0
  398. package/src/telemetry/stall-reports-node.ts +75 -0
  399. package/src/telemetry/stall-reports.ts +214 -0
  400. package/src/test-support/preload.ts +14 -0
  401. package/src/theme/themes.ts +10 -0
  402. package/src/tickers/company-name.ts +44 -3
  403. package/src/tickers/listing-venues.ts +15 -3
  404. package/src/tickers/terminal-codes.ts +19 -0
  405. package/src/time-series/alignment.ts +1 -1
  406. package/src/time-series/economic.ts +18 -4
  407. package/src/time-series/field-catalog.ts +1 -1
  408. package/src/time-series/filed-trailing-eps.ts +197 -0
  409. package/src/time-series/fundamentals.ts +44 -1
  410. package/src/time-series/ice-credit.ts +3 -0
  411. package/src/time-series/price-comparison.ts +30 -2
  412. package/src/time-series/resolve-context.ts +3 -1
  413. package/src/time-series/resolve.ts +56 -4
  414. package/src/time-series/session-bar.ts +11 -0
  415. package/src/time-series/spec.ts +3 -2
  416. package/src/time-series/studies.ts +8 -2
  417. package/src/time-series/total-return.ts +160 -0
  418. package/src/time-series/transforms.ts +2 -3
  419. package/src/time-series/types.ts +22 -1
  420. package/src/types/financials.ts +32 -1
  421. package/src/types/headless.ts +21 -3
  422. package/src/types/news-source.ts +1 -0
  423. package/src/types/plugin.ts +9 -0
  424. package/src/ui/host.tsx +3 -0
  425. package/src/updater/failure.ts +4 -1
  426. package/src/utils/cli-output.ts +42 -1
  427. package/src/utils/crypto-pair.ts +44 -0
  428. package/src/utils/forward-dividend.ts +2 -0
  429. package/src/utils/fundamentals.ts +24 -3
  430. package/src/utils/fx-market-hours.ts +26 -3
  431. package/src/utils/main-thread-stall.ts +35 -2
  432. package/src/utils/market-capitalization.ts +24 -1
  433. package/src/utils/price-history.ts +7 -1
  434. package/src/utils/shell-word.ts +8 -0
  435. package/src/utils/trailing-dividend.ts +53 -4
  436. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -264,6 +264,7 @@ function HelpPane({ focused, width, height }: PaneProps) {
264
264
  entry("zoom-tool", ["Shift+Z", "Shift+Drag"], "Pick the zoom tool, then drag a time range."),
265
265
  entry("ruler", ["Shift+M", "Alt+Drag"], "Pick the ruler, then drag to measure a move."),
266
266
  entry("draw", ["Shift+D", "Shift+P"], "Draw a trend line or a freehand shape."),
267
+ entry("shapes", ["Shift+J", "Shift+B", "Shift+F"], "Draw a horizontal ray, a rectangle or a Fib retracement."),
267
268
  entry("place", ["Enter", "Arrows"], "With a tool picked, Enter starts it at the cursor, the arrows move its end, and Enter finishes it."),
268
269
  entry("drawings", ["[", "]"], "With a drawing tool picked, pick a drawing."),
269
270
  entry("colour", ["c", "Backspace"], "Cycle the drawing colour, or delete the picked drawing (the last one while a drawing tool is picked)."),
@@ -18,7 +18,7 @@ export function resolveWindowTemplates(registry: SharedRegistry): HelpShortcutEn
18
18
  const allPlugins = registry.allPlugins ?? new Map<string, { name?: string }>();
19
19
 
20
20
  return [...registry.paneTemplates.values()]
21
- .filter((template) => template.shortcut)
21
+ .filter((template) => template.shortcut && template.listed !== false)
22
22
  .filter((template) => {
23
23
  const pluginId = registry.getPaneTemplatePluginId?.(template.id);
24
24
  return !pluginId || !disabledPlugins.has(pluginId);
@@ -0,0 +1,51 @@
1
+ import type { Quote } from "../../../types/financials";
2
+ import { quoteTradingExchange } from "../../../market-data/quotes/metadata";
3
+ import { isUsListingExchange } from "../../../utils/exchanges";
4
+ import { isFundType } from "../../../utils/sec";
5
+ import { isNoSourceAnswer } from "../../../sources/provider-errors";
6
+ import { logFailure } from "../../../utils/log-failure";
7
+ import { fetchFunds } from "../members/client";
8
+ import { coveredFund } from "../members/model";
9
+
10
+ type FundQuote = Pick<Quote, "instrumentType" | "tradingExchangeName" | "listingExchangeName" | "exchangeName">;
11
+
12
+ /** What a holder report says for a fund in place of its holders: the reason, and the commands that read the fund. */
13
+ export interface FundHoldersAnswer {
14
+ message: string;
15
+ /** One command a line, each for what it reads; empty for a fund abroad, which those do not cover. */
16
+ details: string[];
17
+ }
18
+
19
+ /**
20
+ * What `holders`, `13f` and `fn HDS` say for a fund no holder source has an
21
+ * ownership breakdown of (XLE, SPY, EWJ), in place of the source's bare
22
+ * NO_DATA: that it is a fund, and for a US-listed one the commands that read
23
+ * it, each only where it runs: the 13F filers that hold it (`fn 13F`), what it
24
+ * holds where `fn MEMB` covers it, and its fund facts (`fn ETF`). Null for a
25
+ * listing that is not a fund, or a failure that is no answer (a timeout).
26
+ */
27
+ export async function fundHoldersAnswer(
28
+ symbol: string,
29
+ error: unknown,
30
+ quote: FundQuote | null | undefined,
31
+ client: NonNullable<Parameters<typeof fetchFunds>[0]>,
32
+ ): Promise<FundHoldersAnswer | null> {
33
+ if (!isNoSourceAnswer(error) || !quote || !isFundType(quote.instrumentType)) return null;
34
+ const fund = symbol.split(":")[0]!.trim().toUpperCase();
35
+ const message = `${fund} is a fund: there is no ownership breakdown for funds.`;
36
+ if (!isUsListingExchange(quoteTradingExchange(quote))) return { message, details: [] };
37
+ // A covered-fund list that cannot be read names no fn MEMB, which may not run for this fund.
38
+ const funds = await fetchFunds(client).then((payload) => payload.funds)
39
+ .catch(logFailure("holders.fund-holders", "Read the funds fn MEMB covers failed", null));
40
+ const covered = !!funds && !!coveredFund(funds, fund);
41
+ // Shares of an exchange-traded fund are 13F securities; a mutual fund's are not.
42
+ const traded = !/MUTUAL/i.test(quote.instrumentType ?? "");
43
+ return {
44
+ message,
45
+ details: [
46
+ ...traded ? [`13F filers that hold it: gloom fn 13F ${fund}`] : [],
47
+ ...covered ? [`What it holds: gloom fn MEMB ${fund}`] : [],
48
+ `Fund facts: gloom fn ETF ${fund}`,
49
+ ],
50
+ };
51
+ }
@@ -33,6 +33,7 @@ import {
33
33
  holderReportUnits,
34
34
  } from "./report-rows";
35
35
  import { REPORT_ROWS } from "../shared/headless-options";
36
+ import { fundHoldersAnswer } from "./fund-holders";
36
37
  import {
37
38
  HOLDER_ROWS_MAX,
38
39
  indexedHoldersPartialNote,
@@ -224,11 +225,27 @@ export function createHoldersHeadless(
224
225
  .catch(logFailure("holders.headless", "Resolve the listing's venue for the 13F holder index failed", null)))?.exchange ?? "";
225
226
  const indexTicker = indexHolderTicker(symbol, exchange);
226
227
  const [snapshot, index] = await Promise.all([
227
- dependencies.loadSnapshot(symbol, args, ctx),
228
+ dependencies.loadSnapshot(symbol, args, ctx).catch(async (error: unknown) => {
229
+ const quote = await ctx.marketData.getQuote(symbol)
230
+ .catch(logFailure("holders.headless", "Read the quote of a listing with no holders failed", null));
231
+ const fund = await fundHoldersAnswer(symbol, error, quote, ctx.apiClient);
232
+ if (fund) return fund;
233
+ throw error;
234
+ }),
228
235
  indexTicker
229
236
  ? dependencies.loadIndexedHolders(indexTicker, limit, ctx).catch(logFailure("holders.headless", "Load 13F holder index failed", null))
230
237
  : null,
231
238
  ]);
239
+ // A fund has no ownership breakdown: the report says so in place of rows, with what reads it, and fails as no data.
240
+ if ("message" in snapshot) {
241
+ return {
242
+ columns: HOLDER_REPORT_COLUMNS,
243
+ rows: [],
244
+ empty: [snapshot.message, ...snapshot.details].join("\n"),
245
+ unavailableSymbols: [symbol],
246
+ metadata: { symbol },
247
+ };
248
+ }
232
249
  const { marketCap } = snapshot;
233
250
  const indexed = !!index?.rows.length && indexHolderTicker(symbol, exchange, snapshot.data) != null;
234
251
  const partialNote = indexed ? indexedHoldersPartialNote(index, limit) : null;
@@ -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
  }
@@ -2,8 +2,8 @@ import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
2
  import { resolveHeadlessInstrument } from "../shared/headless-market-data";
3
3
  import { createRealizedVolatilityDependencies, loadRealizedVolatilityHistory } from "../realized-vol/client";
4
4
  import { createHvDependencies, loadIvHistory, loadIvScreen, loadScreenVolatilities } from "./client";
5
- import { formatPoints, formatRank, formatSpot, formatStat, formatVol, IMPLIED_VOL_WORDS, readingLabel, sharedDates, skewHeader, VCA_DEFINITIONS, verdictLabel } from "./format";
6
- import { HV_WINDOWS, type HvWindow, type IvLookback, type IvStatRow, projectIvHistory, projectRichCheap, type RichCheapRow, richCheapDates, screenCoverage, VCA_LIMIT, VCA_PRESETS } from "./model";
5
+ import { formatPoints, formatRank, formatSpot, formatStat, formatVol, IMPLIED_VOL_WORDS, readingLabel, sharedDates, skewHeader, statSource, vcaDefinitions, verdictLabel } from "./format";
6
+ import { HV_WINDOWS, type HvWindow, type IvLookback, type IvStatRow, projectIvHistory, projectRichCheap, type RichCheapRow, richCheapDates, screenCoverage, screenStaleNote, VCA_LIMIT, VCA_PRESETS } from "./model";
7
7
  import { vcaUniverse } from "./universe";
8
8
  import { offsetText, releaseOffsetRows } from "./events";
9
9
  import { loadMacroReleases } from "../macro-day/client";
@@ -16,6 +16,7 @@ import type { HeadlessPaneContext } from "../../../types/headless";
16
16
  import { zonedDateKey } from "../../../utils/zoned-date-time";
17
17
  import { listingFateNotes, listingFateReport, loadListingFate, MovedSymbolError } from "../../../tickers/listing-fate";
18
18
  import { startTickerChangeLookup } from "../../../tickers/ticker-changes";
19
+ import { fxVolatilityRefusal } from "../../../market-data/options-alternatives";
19
20
 
20
21
  const METHODOLOGY = "docs/research-data.md#implied-volatility-history";
21
22
  const percent = (value: unknown) => typeof value === "number" ? formatVol(value) : "--";
@@ -24,6 +25,7 @@ const points = (value: unknown) => typeof value === "number" ? formatPoints(valu
24
25
  export const ivHistoryHeadless: HeadlessPaneDefinition<"bundle"> = {
25
26
  shape: "bundle", argument: { kind: "ticker", description: "US option underlying" },
26
27
  usListingsOnly: "Implied volatility history",
28
+ refuseSymbol: fxVolatilityRefusal("HIVG"),
27
29
  freshness: { status: "not-a-feed", basis: "daily implied volatility", cadence: "daily", observedKey: "date", oldest: null },
28
30
  describe: (args) => `HIVG ${args.symbols[0] ?? ""}`,
29
31
  discovery: { aliases: ["HIVG"], suggestFor: IMPLIED_VOL_WORDS, screenshotReadiness: "live-dom", dataRequirements: ["Cloud stored implied volatility", "Daily price history"],
@@ -79,7 +81,7 @@ export const ivHistoryHeadless: HeadlessPaneDefinition<"bundle"> = {
79
81
  // With nothing served, the fate is why: no "not a ticker" or missing-history errors beside it.
80
82
  const { notes: fateNotes, errors } = listingFateReport(fate, { served },
81
83
  [prices.error, ...model.warnings].filter((value): value is string => !!value));
82
- const notes = [...fateNotes, ...[model.oneYearNote, model.skewNote].filter((note): note is string => !!note)];
84
+ const notes = [...fateNotes, ...[model.oneYearNote, model.skewNote, model.staleNote].filter((note): note is string => !!note)];
83
85
  if (event && releases) {
84
86
  const lookback = String(args.options.lookback) as IvLookback;
85
87
  const label = MACRO_EVENT_LABELS[event];
@@ -117,18 +119,27 @@ export const ivHistoryHeadless: HeadlessPaneDefinition<"bundle"> = {
117
119
  releaseSource: MACRO_RELEASE_SOURCES[event], unit: "decimal annualized volatility", methodology: METHODOLOGY },
118
120
  };
119
121
  }
122
+ // The text report dates a stale measure "stale"; exports keep the bare date and say it in `stale`.
123
+ const statColumns = [
124
+ { key: "label", header: "Measure" },
125
+ { key: "value", header: "Current", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
126
+ { key: "date", header: "As of" }, { key: "method", header: "Method" },
127
+ { key: "low", header: "52w low", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
128
+ { key: "high", header: "52w high", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
129
+ { key: "rank", header: "Rank", format: (value: unknown) => formatRank(value as number | null) },
130
+ { key: "percentile", header: "Pctl", format: (value: unknown) => formatRank(value as number | null) },
131
+ { key: "samples", header: "Sessions" },
132
+ ];
133
+ const statTextColumns = statColumns.map((column) => column.key === "date"
134
+ ? { ...column, textFormat: (value: unknown, row: unknown) => typeof value !== "string" ? "-" : (row as IvStatRow).stale ? `${value} stale` : value }
135
+ : column.key === "method"
136
+ // The wording the pane's source column uses; exports keep the raw method and carry the basis.
137
+ ? { ...column, textFormat: (value: unknown, row: unknown) => typeof value !== "string" ? "-" : statSource(row as IvStatRow) }
138
+ : column);
120
139
  return {
121
140
  sections: [
122
- { title: "Statistics", columns: [
123
- { key: "label", header: "Measure" },
124
- { key: "value", header: "Current", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
125
- { key: "date", header: "As of" }, { key: "method", header: "Method" },
126
- { key: "low", header: "52w low", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
127
- { key: "high", header: "52w high", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
128
- { key: "rank", header: "Rank", format: (value: unknown) => formatRank(value as number | null) },
129
- { key: "percentile", header: "Pctl", format: (value: unknown) => formatRank(value as number | null) },
130
- { key: "samples", header: "Sessions" },
131
- ], rows: model.stats.map((row) => ({ ...row })) },
141
+ { title: "Statistics", columns: [...statColumns, { key: "basis", header: "Basis" }, { key: "stale", header: "Stale" }], textColumns: statTextColumns,
142
+ rows: model.stats.map((row) => ({ ...row })) },
132
143
  // Exports always carry the skew columns, so a script's header never moves; the text table drops them when none has a value.
133
144
  { title: "Daily history", columns: [...historyColumns, ...skewColumns], rows,
134
145
  ...(rows.some((row) => row.skew25d != null) ? {} : { textColumns: historyColumns }) },
@@ -151,6 +162,7 @@ export const ivScreenHeadless: HeadlessPaneDefinition<"bundle"> = {
151
162
  freshness: { status: "not-a-feed", basis: "daily implied volatility", cadence: "daily", observedKey: "date" },
152
163
  argument: { kind: "symbol-list", optional: true, maximum: VCA_LIMIT, description: "US option underlyings; defaults to index and sector ETFs." },
153
164
  usListingsOnly: "Implied volatility",
165
+ refuseSymbol: fxVolatilityRefusal("VCA"),
154
166
  options: [{ key: "preset", type: "enum", values: [{ value: "etfs" }, { value: "megacaps" }], defaultValue: "etfs", description: "Preset when no symbols are given" }],
155
167
  describe: "Volatility rich/cheap",
156
168
  // The pane names its universe `scope`; symbols typed open their own (custom), which a preset does not replace.
@@ -180,14 +192,15 @@ export const ivScreenHeadless: HeadlessPaneDefinition<"bundle"> = {
180
192
  // A listing that stopped trading has its row's note, not a line saying it has no options history.
181
193
  const coverage = screenCoverage(rows, { explained: new Set([...noted].filter(([, entry]) => entry.fate.endedOn).map(([symbol]) => symbol)) });
182
194
  const dates = richCheapDates(rows);
183
- const hasSkew = rows.some((row) => row.skew != null);
195
+ const hasSkew = rows.some((row) => row.riskReversal != null);
184
196
  const hasIv1y = rows.some((row) => row.iv1y != null), hasIv1yRank = rows.some((row) => row.iv1yPercentile != null);
185
197
  const dated = (value: unknown) => typeof value === "string" ? value : "--";
186
198
  return {
187
199
  sections: [{ title: ["Rich/cheap", universe.label, ...sharedDates(dates)].join(" · "), columns: [
188
200
  { key: "symbol", header: "Symbol" }, { key: "iv30", header: "IV30", format: percent },
189
201
  ...(dates.reading ? [] : [{ key: "date", header: "As of", format: (value: unknown, row?: unknown) =>
190
- typeof value === "string" ? readingLabel(value, (row as RichCheapRow).method) : "--" }]),
202
+ typeof value === "string" ? readingLabel(value, (row as RichCheapRow).method, false,
203
+ { basis: (row as RichCheapRow).basis, stale: (row as RichCheapRow).stale }) : "--" }]),
191
204
  // The underlying price each reading was taken at, on the date the title or the As of column gives.
192
205
  { key: "spot", header: "Spot", format: (value: unknown) => typeof value === "number" ? formatSpot(value) : "--" },
193
206
  ...(dates.rankApart ? [{ key: "rankDate", header: "Rank as of", format: (value: unknown) => typeof value === "string" ? `${value} close` : "--" }] : []),
@@ -197,16 +210,18 @@ export const ivScreenHeadless: HeadlessPaneDefinition<"bundle"> = {
197
210
  ...(hasIv1y ? [{ key: "iv1y", header: "IV1Y", format: percent }] : []),
198
211
  ...(hasIv1yRank ? [{ key: "iv1yPercentile", header: "IVP1Y", format: (value: unknown) => formatRank(value as number | null) }] : []),
199
212
  { key: "termSlope", header: "30-90", format: (value: unknown) => formatPoints(value as number | null) },
200
- ...(hasSkew ? [{ key: "skew", header: skewHeader("25D skew", "put-call"), format: (value: unknown) => formatPoints(value as number | null) }] : []),
201
- ...(hasSkew && dates.skewApart ? [{ key: "skewDate", header: "Skew as of", format: dated }] : []),
213
+ ...(hasSkew ? [{ key: "riskReversal", header: skewHeader("25D RR", "call-put"), format: (value: unknown) => formatPoints(value as number | null) }] : []),
214
+ ...(hasSkew && dates.skewApart ? [{ key: "skewDate", header: "RR as of", format: dated }] : []),
202
215
  { key: "hv", header: "HV20", format: percent },
216
+ // Said only where an HV is not the one of its IV's date: a stale reading's, or one a few sessions off.
217
+ ...(dates.hvApart ? [{ key: "hvDate", header: "HV as of", format: dated }] : []),
203
218
  { key: "ivHv", header: "IV/HV", format: (value: unknown) => typeof value === "number" ? value.toFixed(2) : "--" },
204
219
  ...(noted.size > 0 ? [{ key: "note", header: "Note", underRow: true }] : []),
205
220
  ], rows: rows.map((row) => ({ ...row, ...noted.has(row.symbol) ? { note: noted.get(row.symbol)!.note } : {} })) }],
206
221
  // A symbol with no options history is final; only history still to come leaves the screen incomplete.
207
222
  complete: !coverage.pending.length && !universe.error, unavailableSymbols: coverage.missing,
208
223
  errors: [universe.error].filter((value): value is string => !!value),
209
- notes: [...coverage.notes, ...VCA_DEFINITIONS],
224
+ notes: [...coverage.notes, ...[screenStaleNote(rows)].filter((note): note is string => !!note), ...vcaDefinitions({ skew: hasSkew })],
210
225
  metadata: { presets: VCA_PRESETS, methodology: METHODOLOGY },
211
226
  };
212
227
  },
@@ -32,13 +32,13 @@ export const ivHistoryModule: PluginModule = {
32
32
  { ...createTickerSurfacePaneTemplate({
33
33
  id: "iv-history-pane", paneId: "iv-history", label: "Implied Volatility History",
34
34
  description: "30-day, 90-day and one-year ATM implied volatility since 2024 against realized, with IV rank and percentile.",
35
- keywords: ["hivg", "implied", "volatility", "history", "iv rank", "ivr", "percentile", "vrp", "iv1y", "leaps"], shortcut: "HIVG", publicShare: true,
35
+ keywords: ["hivg", "implied", "volatility", "vol", "history", "iv rank", "ivr", "percentile", "vrp", "iv1y", "leaps"], shortcut: "HIVG", publicShare: true,
36
36
  }), headless: ivHistoryHeadless },
37
37
  {
38
38
  id: "iv-screen-pane", paneId: "iv-screen", label: "Volatility Rich/Cheap",
39
39
  description: "IV rank, percentile, one-year IV, term slope, skew and IV/HV across a list, ranked rich to cheap.",
40
40
  // Where a put seller finds rich premium; CSP and OSA own the exact put-selling phrases and rank first on them.
41
- keywords: ["vca", "rich", "cheap", "iv rank", "implied volatility", "screen", "cash-secured puts", "selling puts", "short-put candidates",
41
+ keywords: ["vca", "vol", "rich", "cheap", "iv rank", "implied volatility", "screen", "cash-secured puts", "selling puts", "short-put candidates",
42
42
  "csp candidates", "wheel candidates"],
43
43
  shortcut: { prefix: "VCA", argKind: "ticker-list" as const, argOptional: true }, headless: ivScreenHeadless,
44
44
  createInstance: (_context, options) => ({