@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
@@ -1,5 +1,4 @@
1
1
  import { useCallback, useMemo, useRef } from "react";
2
- import { QUICK_ADD_KEY } from "./quick-add";
3
2
  import {
4
3
  TickerListTableView,
5
4
  type DataTableKeyEvent,
@@ -111,6 +110,7 @@ export function PortfolioTickerTable({
111
110
  onCursorChange,
112
111
  financialsMap,
113
112
  columnContext,
113
+ futuresFrontNotes,
114
114
  flashSymbols,
115
115
  sinceLastSeen,
116
116
  onRootKeyDown,
@@ -119,7 +119,7 @@ export function PortfolioTickerTable({
119
119
  visibleRangeBuffer,
120
120
  resetScrollKey,
121
121
  onRowActivate,
122
- onAddTicker,
122
+ addTickerShortcut,
123
123
  onOpenPerpetual,
124
124
  rootHeight,
125
125
  }: {
@@ -134,6 +134,7 @@ export function PortfolioTickerTable({
134
134
  onCursorChange?: (ticker: TickerRecord, index: number) => void;
135
135
  financialsMap: Map<string, TickerFinancials>;
136
136
  columnContext: ColumnContext;
137
+ futuresFrontNotes?: ReadonlyMap<string, string>;
137
138
  flashSymbols: Map<string, QuoteFlashDirection>;
138
139
  /** For a few seconds after coming back to the pane, LAST is tinted by each row's move since the user left. */
139
140
  sinceLastSeen?: SinceLastSeenRecap | null;
@@ -144,8 +145,8 @@ export function PortfolioTickerTable({
144
145
  visibleRangeBuffer?: number;
145
146
  resetScrollKey?: unknown;
146
147
  onRowActivate?: (ticker: TickerRecord) => void;
147
- /** Opens the add row; the empty table offers it as a button. */
148
- onAddTicker?: () => void;
148
+ /** The key of the add row, which the empty table names. The pane shows the row itself, once. */
149
+ addTickerShortcut?: string;
149
150
  /** Opens PERP on a row's stock perpetual: its PERP% and FUND 8H cells link to it. */
150
151
  onOpenPerpetual?: (marketId: string) => void;
151
152
  rootHeight?: number;
@@ -155,21 +156,26 @@ export function PortfolioTickerTable({
155
156
  openPerpetualRef.current = onOpenPerpetual;
156
157
  const sparklineVisible = columns.some((column) => column.id === PRICE_SPARKLINE_COLUMN_ID);
157
158
  const tableFinancialsMap = useLiveSparklineFinancials(financialsMap, sortedTickers, sparklineVisible);
159
+ const noteColumnId = columns.some((column) => column.id === "name") ? "name" : "ticker";
160
+ const hasFrontReference = useMemo(() => sortedTickers.some((ticker) => columnContext.futuresTerms?.get(ticker.metadata.ticker)?.front),
161
+ [columnContext.futuresTerms, sortedTickers]);
158
162
  const resolveCell = useCallback(
159
163
  (column: ColumnConfig, ticker: TickerRecord, financials: TickerFinancials | undefined) => {
160
164
  const key = `${ticker.metadata.ticker}:${column.id}`;
161
165
  const version = buildCellVersion(column, ticker, financials, columnContext);
162
- const cell = cellCacheRef.current.get(key, version, () => {
166
+ const cached = cellCacheRef.current.get(key, version, () => {
163
167
  const cell = getColumnValue(column, ticker, financials, columnContext);
164
168
  const marketId = PERPETUAL_COLUMN_IDS.has(column.id) ? columnContext.perpetuals?.get(ticker.metadata.ticker)?.marketId : undefined;
165
169
  return marketId ? { ...cell, onPress: () => openPerpetualRef.current?.(marketId) } : cell;
166
170
  });
171
+ const note = column.id === noteColumnId ? futuresFrontNotes?.get(ticker.metadata.ticker) : undefined;
172
+ const cell = note ? { ...cached, note } : cached;
167
173
  // LAST keeps its price: a move since the user last looked only tints the cell.
168
174
  if (column.id !== "price" || !sinceLastSeen) return cell;
169
175
  const move = sinceLastSeen.moves.get(ticker.metadata.ticker);
170
176
  return move == null ? cell : { ...cell, backgroundColor: sinceLastSeenTint(move, sinceLastSeen.fading) };
171
177
  },
172
- [columnContext, sinceLastSeen],
178
+ [columnContext, futuresFrontNotes, noteColumnId, sinceLastSeen],
173
179
  );
174
180
  const pnlColumn = columns.find((column) => column.id === "pnl" || column.id === "pnl_pct");
175
181
  // Reads the same cached P&L cells the rows show; only changed rows recompute.
@@ -202,7 +208,8 @@ export function PortfolioTickerTable({
202
208
  : pnlLabel !== "P&L" && (column.id === "pnl" || column.id === "pnl_pct") ? {
203
209
  ...column,
204
210
  label: column.id === "pnl_pct" ? pnlLabel.replace("P&L", "%") : pnlLabel,
205
- } : column), [columns, grossWeights, hasNonShareQuantity, pnlLabel, sharesWidth, tickerWidth]);
211
+ } : column).map((column) => hasFrontReference && column.id === noteColumnId ? { ...column, flexGrow: 1 } : column),
212
+ [columns, grossWeights, hasFrontReference, hasNonShareQuantity, noteColumnId, pnlLabel, sharesWidth, tickerWidth]);
206
213
  // The table offers "Sort by…" while it has a sort column. A watchlist starts
207
214
  // unsorted and a third header click turns sorting off, so the unsorted state
208
215
  // needs its own entry until TickerListTableView takes `sortable`.
@@ -235,8 +242,7 @@ export function PortfolioTickerTable({
235
242
  resetScrollKey={resetScrollKey}
236
243
  onRowActivate={onRowActivate}
237
244
  rowMenuAccelerators={PORTFOLIO_ROW_MENU_KEYS}
238
- onAddTicker={onAddTicker}
239
- addTickerShortcut={onAddTicker ? QUICK_ADD_KEY : undefined}
245
+ addTickerShortcut={addTickerShortcut}
240
246
  rootHeight={rootHeight}
241
247
  />
242
248
  );
@@ -1,3 +1,4 @@
1
+ import { createTtlCache } from "../../../data/ttl-cache";
1
2
  import { httpFetch } from "../../../utils/http-transport";
2
3
  import { isoDateOrNull, numericOrNull, recordOrNull, textOrNull } from "../../../utils/guards";
3
4
  import {
@@ -84,20 +85,14 @@ async function readJson(url: string): Promise<unknown> {
84
85
  }
85
86
 
86
87
  /** The series break changes about every two years; one read serves the session. */
87
- let seriesBreakRead: Promise<string> | null = null;
88
+ const seriesBreaks = createTtlCache<string>({ ttlMs: Infinity });
88
89
 
89
90
  function currentSeriesBreak(force: boolean): Promise<string> {
90
- if (!seriesBreakRead || force) {
91
- const read = readJson(`${API}/list/seriesbreaks.json`).then((payload) => {
92
- const seriesBreak = newestSeriesBreak(parseSeriesBreaks(payload));
93
- if (!seriesBreak) throw new Error("No series break was listed.");
94
- return seriesBreak;
95
- });
96
- seriesBreakRead = read;
97
- // A failed read is not kept, so the next one asks again.
98
- read.catch(() => { if (seriesBreakRead === read) seriesBreakRead = null; });
99
- }
100
- return seriesBreakRead;
91
+ return seriesBreaks.load("current", async () => {
92
+ const seriesBreak = newestSeriesBreak(parseSeriesBreaks(await readJson(`${API}/list/seriesbreaks.json`)));
93
+ if (!seriesBreak) throw new Error("No series break was listed.");
94
+ return seriesBreak;
95
+ }, { force });
101
96
  }
102
97
 
103
98
  /**
@@ -117,31 +112,11 @@ export async function fetchPrimaryDealers(): Promise<DealerBoard> {
117
112
  };
118
113
  }
119
114
 
120
- interface SeriesRead {
121
- at: number;
122
- read: Promise<SeriesPoint[]>;
123
- points: SeriesPoint[] | null;
124
- }
125
-
126
- const seriesReads = new Map<string, SeriesRead>();
115
+ const seriesReads = createTtlCache<SeriesPoint[]>({ ttlMs: HISTORY_TTL_MS });
127
116
 
128
117
  function seriesRead(seriesBreak: string, key: string, force: boolean): Promise<SeriesPoint[]> {
129
- const id = `${seriesBreak}:${key}`;
130
- const known = seriesReads.get(id);
131
- if (known && !force && Date.now() - known.at < HISTORY_TTL_MS) return known.read;
132
118
  const url = `${API}/get/${encodeURIComponent(seriesBreak)}/timeseries/${encodeURIComponent(key)}.json`;
133
- const entry: SeriesRead = {
134
- at: Date.now(),
135
- points: null,
136
- read: readJson(url).then((payload) => {
137
- entry.points = parseSeriesPoints(payload);
138
- return entry.points;
139
- }),
140
- };
141
- seriesReads.set(id, entry);
142
- // A failed read is not kept, so the next one asks again.
143
- entry.read.catch(() => { if (seriesReads.get(id) === entry) seriesReads.delete(id); });
144
- return entry.read;
119
+ return seriesReads.load(`${seriesBreak}:${key}`, async () => parseSeriesPoints(await readJson(url)), { force });
145
120
  }
146
121
 
147
122
  /** A row's history, one read per series behind it, each kept for the session. */
@@ -158,9 +133,9 @@ export async function loadDealerHistory(
158
133
  function heldSeries(seriesBreak: string, row: Pick<DealerRow, "keys">): SeriesPoint[][] | null {
159
134
  const series: SeriesPoint[][] = [];
160
135
  for (const key of row.keys) {
161
- const known = seriesReads.get(`${seriesBreak}:${key}`);
162
- if (!known?.points || Date.now() - known.at >= HISTORY_TTL_MS) return null;
163
- series.push(known.points);
136
+ const points = seriesReads.peek(`${seriesBreak}:${key}`);
137
+ if (!points) return null;
138
+ series.push(points);
164
139
  }
165
140
  return series;
166
141
  }
@@ -178,6 +153,6 @@ export function cachedDealerHistory(seriesBreak: string, row: Pick<DealerRow, "i
178
153
 
179
154
  /** Forgets what was read, for a test or a refresh that must see the release again. */
180
155
  export function resetPrimaryDealersCache(): void {
181
- seriesBreakRead = null;
156
+ seriesBreaks.clear();
182
157
  seriesReads.clear();
183
158
  }
@@ -4,6 +4,7 @@ import type { QueryEntry } from "../../../market-data/result-types";
4
4
  import type { DataProvider } from "../../../types/data-provider";
5
5
  import type { PricePoint } from "../../../types/financials";
6
6
  import { createSurfaceDependencies, loadVolatilitySurface, type SurfaceLoaderDependencies } from "../vol-surface/client";
7
+ import type { SurfaceSnapshot } from "../vol-surface/model";
7
8
  import { projectCurrentAtmIv, type CurrentAtmIvSnapshot } from "./model";
8
9
  import { abortable, abortError } from "../../../utils/async-deadline";
9
10
  import { errorMessage } from "../../../utils/errors";
@@ -70,7 +71,9 @@ export interface CurrentAtmIvRequest extends RealizedHistoryRequest {
70
71
  * Re-read the current reference's own expiry at the live spot: that one chain
71
72
  * is refetched and refitted, the catalogue comes from cache, and the other
72
73
  * slices of the full load are not touched. When the refreshed slice cannot
73
- * price an ATM level, the previous dated reference stands.
74
+ * price an ATM level, the refresh rejects with why ("ATM IV 31d not
75
+ * refreshed: ..."): the dated reference already shown stays, and the pane
76
+ * warns that it is not moving.
74
77
  */
75
78
  export async function refreshCurrentAtmIv(
76
79
  request: CurrentAtmIvRequest,
@@ -80,14 +83,19 @@ export async function refreshCurrentAtmIv(
80
83
  const reference = previous.reference;
81
84
  if (!reference || !(request.spot > 0) || !Number.isFinite(request.spot)) return previous;
82
85
  if (request.signal?.aborted) throw abortError(CANCELLED);
86
+ const notRefreshed = (reason: string) => new Error(`${reference.label} not refreshed: ${reason}`);
87
+ let surface: SurfaceSnapshot;
83
88
  try {
84
- const next = projectCurrentAtmIv(await loadVolatilitySurface({ ...request, forceRefresh: true, expiries: [reference.expiration],
85
- settings: { ivSource: reference.ivSource } }, dependencies));
86
- return next.reference?.expiration === reference.expiration ? { ...previous, reference: next.reference } : previous;
89
+ surface = await loadVolatilitySurface({ ...request, forceRefresh: true, expiries: [reference.expiration],
90
+ settings: { ivSource: reference.ivSource } }, dependencies);
87
91
  } catch (error) {
88
92
  if (request.signal?.aborted || (error instanceof Error && error.name === "AbortError")) throw abortError(CANCELLED);
89
- return previous;
93
+ throw notRefreshed(errorMessage(error));
90
94
  }
95
+ const next = projectCurrentAtmIv(surface);
96
+ if (next.reference?.expiration === reference.expiration) return { ...previous, reference: next.reference };
97
+ const reasons = [...new Set(surface.failures.map((failure) => failure.message))];
98
+ throw notRefreshed(reasons.join("; ") || "no clean quote at the money");
91
99
  }
92
100
 
93
101
  /** Options are independent of daily history and use the existing surface chain cache. */
@@ -19,9 +19,18 @@ export interface RealizedVolEvidenceStatus {
19
19
  source: string | null;
20
20
  asOf: string | null;
21
21
  errors: string[];
22
+ /** The errors that are the current IV's alone: a chart missing only its IV point names that, not the symbol. */
23
+ ivErrors?: string[];
24
+ /** The windows the history is too short for: their series have no values, and the capture is whole without them. */
25
+ shortWindows?: number[];
22
26
  currentIv: { value: number; date: string; label: string; source: string | null; expiration: number } | null;
23
27
  }
24
28
 
29
+ /** A series a short history leaves without values: a selected window it is too short for. */
30
+ function shortSeries(id: string, shortWindows: readonly number[] | undefined): boolean {
31
+ return /^hv-\d+$/.test(id) && (shortWindows ?? []).includes(Number(id.slice(3)));
32
+ }
33
+
25
34
  export interface RealizedVolEvidence extends RealizedVolEvidenceStatus {
26
35
  kind: "realized-volatility";
27
36
  version: 1;
@@ -38,7 +47,8 @@ export function realizedVolSemanticEvidence(series: readonly ResolvedSeries[], s
38
47
  const plottedValueCount = plotted.reduce((sum, entry) => sum + entry.points.filter((point) => isFiniteNumber(point.value)).length, 0);
39
48
  return { ...status, kind: "realized-volatility", version: 1, series: plotted, plottedValueCount,
40
49
  complete: !status.loading && !status.stale && status.errors.length === 0 && !!status.asOf
41
- && plotted.length > 0 && plotted.every((entry) => entry.points.some((point) => isFiniteNumber(point.value)))
50
+ && plotted.length > 0 && plotted.some((entry) => entry.points.some((point) => isFiniteNumber(point.value)))
51
+ && plotted.every((entry) => shortSeries(entry.id, status.shortWindows) || entry.points.some((point) => isFiniteNumber(point.value)))
42
52
  && (!status.showIv || status.currentIv != null),
43
53
  };
44
54
  }
@@ -56,12 +66,15 @@ function readRealizedVolEvidence(value: unknown): RealizedVolEvidence | null {
56
66
  || !isFiniteNumber(value.lookbackYears) || typeof value.showIv !== "boolean" || typeof value.complete !== "boolean"
57
67
  || typeof value.loading !== "boolean" || typeof value.stale !== "boolean" || !isDateString(value.asOf)
58
68
  || !isStringArray(value.errors)
69
+ || (value.shortWindows !== undefined && (!Array.isArray(value.shortWindows) || !value.shortWindows.every(isFiniteNumber)))
70
+ || (value.ivErrors !== undefined && !isStringArray(value.ivErrors))
59
71
  || !Array.isArray(value.series) || !value.series.every((entry) => isRecord(entry) && typeof entry.id === "string"
60
72
  && typeof entry.unit === "string" && Array.isArray(entry.points) && entry.points.every((point) => isRecord(point)
61
73
  && isDateString(point.date) && (point.value === null || isFiniteNumber(point.value))))) return null;
62
74
  const evidence = value as unknown as RealizedVolEvidence;
75
+ // The IV point is drawn only when there is one: a chart without it matches its data, and is incomplete for it.
63
76
  const required = evidence.view === "graph" ? [...evidence.windows.map((window) => `hv-${window}`), "price",
64
- ...(evidence.showIv ? ["current-iv"] : [])] : ["min", "max", "mean", "current"];
77
+ ...(evidence.showIv && evidence.currentIv != null ? ["current-iv"] : [])] : ["min", "max", "mean", "current"];
65
78
  if (!required.length || (evidence.view === "graph" && !evidence.windows.length)
66
79
  || evidence.series.length !== required.length || new Set(evidence.series.map((entry) => entry.id)).size !== required.length
67
80
  || required.some((id) => !evidence.series.some((entry) => entry.id === id))) return null;
@@ -75,10 +88,19 @@ function readRealizedVolEvidence(value: unknown): RealizedVolEvidence | null {
75
88
  }
76
89
  if (evidence.complete && (evidence.loading || evidence.stale || evidence.errors.length > 0
77
90
  || (evidence.showIv && !evidence.currentIv)
78
- || evidence.series.some((entry) => !entry.points.some((point) => isFiniteNumber(point.value))))) return null;
91
+ || evidence.series.some((entry) => !shortSeries(entry.id, evidence.shortWindows) && !entry.points.some((point) => isFiniteNumber(point.value))))) return null;
79
92
  return evidence;
80
93
  }
81
94
 
95
+ /** Drawn, current and whole but for the current IV, whose errors are the only ones. */
96
+ function missingOnlyIv(evidence: RealizedVolEvidence): boolean {
97
+ const ivErrors = new Set(evidence.ivErrors ?? []);
98
+ return evidence.view === "graph" && evidence.showIv && evidence.currentIv == null
99
+ && !evidence.loading && !evidence.stale && evidence.plottedValueCount > 0
100
+ && evidence.errors.every((error) => ivErrors.has(error))
101
+ && evidence.series.every((entry) => shortSeries(entry.id, evidence.shortWindows) || entry.points.some((point) => isFiniteNumber(point.value)));
102
+ }
103
+
82
104
  export const realizedVolScreenshotEvidence: PaneScreenshotEvidenceHook<RealizedVolEvidence> = {
83
105
  paneId: "realized-vol",
84
106
  kind: "realized-volatility",
@@ -98,6 +120,9 @@ export const realizedVolScreenshotEvidence: PaneScreenshotEvidenceHook<RealizedV
98
120
  },
99
121
  unavailable(evidence, { resolved, payload }) {
100
122
  const symbol = payload.financials[0]?.[0] ?? resolved.createOptions?.symbol;
101
- return evidence?.complete && !evidence.loading ? [] : [symbol ?? "realized volatility"];
123
+ if (evidence?.complete && !evidence.loading) return [];
124
+ // The volatility drawn whole and only the IV point missing: the capture names the IV, not the symbol.
125
+ if (evidence && missingOnlyIv(evidence)) return [`${symbol ?? evidence.symbol} current ATM IV`];
126
+ return [symbol ?? "realized volatility"];
102
127
  },
103
128
  };
@@ -3,7 +3,7 @@ import type { HeadlessPaneDefinition } from "../../../types/plugin";
3
3
  import { createSurfaceDependencies } from "../vol-surface/client";
4
4
  import { resolveHeadlessInstrument } from "../shared/headless-market-data";
5
5
  import { createRealizedVolatilityDependencies, loadCurrentAtmIv, loadRealizedVolatilityHistory } from "./client";
6
- import { projectRealizedVolatility, type CurrentAtmIvSnapshot } from "./model";
6
+ import { atmIvObservedLabel, projectRealizedVolatility, type CurrentAtmIvSnapshot } from "./model";
7
7
  import { ESTIMATOR_OPTIONS, selectedWindows } from "./settings";
8
8
  import { DAILY_CLOSES } from "../shared/report-freshness";
9
9
  import { listingAbroadVenue } from "../../../utils/sec";
@@ -69,13 +69,17 @@ export function realizedVolHeadless(initialView: "graph" | "cone"): HeadlessPane
69
69
  formatted: iv?.reference ? new Date(iv.reference.expiration * 1000).toISOString().slice(0, 10) : "--" },
70
70
  { label: "Days", value: iv?.reference?.daysToExpiry ?? null,
71
71
  formatted: iv?.reference ? String(Math.round(iv.reference.daysToExpiry)) : "--" },
72
- { label: "Observed", value: iv?.reference?.date.toISOString() ?? null },
72
+ // To the minute, as the pane reads it; the JSON keeps the chain's read time whole.
73
+ { label: "Observed", value: iv?.reference?.date.toISOString() ?? null,
74
+ formatted: iv?.reference ? atmIvObservedLabel(iv.reference.date) : "--" },
73
75
  ] },
74
76
  ],
75
- complete: model.cone.every((row) => row.current != null) && model.warnings.length === 0 && !history.stale && errors.length === 0
77
+ // A short history is whole: the windows it is too short for have no estimate yet, and the note says when it starts.
78
+ complete: model.cone.every((row) => row.current != null || model.shortWindows.includes(row.window))
79
+ && model.warnings.length === 0 && !history.stale && errors.length === 0
76
80
  && (!args.options.showIv || iv?.reference != null || iv?.noOptionChain === true),
77
81
  unavailableSymbols: model.history.length ? [] : [instrument.symbol], errors,
78
- notes: [barsPerYearLabel(model.barsPerYear)],
82
+ notes: [barsPerYearLabel(model.barsPerYear), ...model.historyNote ? [model.historyNote] : []],
79
83
  metadata: { unit: "decimal annualized volatility", annualization: model.barsPerYear, model, currentIv: iv,
80
84
  ...(abroad && args.options.showIv ? { notices: [`Options data covers US listings only, so ${args.symbols[0]} has no current IV.`] } : {}),
81
85
  source: history.source, stale: history.stale, fetchedAt: history.fetchedAt,
@@ -21,13 +21,13 @@ export const realizedVolModule: PluginModule = {
21
21
  { ...createTickerSurfacePaneTemplate({
22
22
  id: "realized-vol-graph-pane", paneId: "realized-vol", label: "Realized Volatility Graph",
23
23
  description: "Rolling realized volatility, price and a dated current ATM IV reference.",
24
- keywords: ["hvg", "realized", "historical", "volatility", "gv"], shortcut: "HVG", publicShare: true,
24
+ keywords: ["hvg", "realized", "historical", "volatility", "vol", "gv"], shortcut: "HVG", publicShare: true,
25
25
  viewKey: "graph", settings: () => ({ initialView: "graph" }),
26
26
  }), headless: realizedVolHeadless("graph") },
27
27
  { ...createTickerSurfacePaneTemplate({
28
28
  id: "realized-vol-table-pane", paneId: "realized-vol", label: "Realized Volatility Cone",
29
29
  description: "Current realized volatility against its historical range and percentile.",
30
- keywords: ["hvt", "volatility", "cone", "percentile"], shortcut: "HVT", publicShare: true,
30
+ keywords: ["hvt", "volatility", "vol", "cone", "percentile"], shortcut: "HVT", publicShare: true,
31
31
  viewKey: "cone", settings: () => ({ initialView: "cone" }),
32
32
  }), headless: realizedVolHeadless("cone") },
33
33
  ],
@@ -19,6 +19,24 @@ export interface RealizedVolatilityModel {
19
19
  /** What the volatility is annualised at, read from the series' own dates. */
20
20
  barsPerYear: BarsPerYear;
21
21
  warnings: string[];
22
+ /**
23
+ * The windows the history is too short for, longest last: a stock that
24
+ * listed in May has no 180- or 260-bar estimate yet, and nothing failed.
25
+ */
26
+ shortWindows: number[];
27
+ /**
28
+ * What a short history leaves out, said once: "History starts 2026-05-07: 121
29
+ * daily bars so far; the 180- and 260-bar windows need more". Null when the
30
+ * history covers the lookback and every window.
31
+ */
32
+ historyNote: string | null;
33
+ }
34
+
35
+ /** "the 180-bar window", "the 180- and 260-bar windows", "the 90-, 180- and 260-bar windows". */
36
+ function windowsText(windows: readonly number[]): string {
37
+ if (windows.length === 1) return `the ${windows[0]}-bar window`;
38
+ const head = windows.slice(0, -1).map((window) => `${window}-`);
39
+ return `the ${head.join(", ")} and ${windows.at(-1)}-bar windows`;
22
40
  }
23
41
 
24
42
  export interface RealizedVolatilityProjectionOptions {
@@ -45,6 +63,10 @@ export function projectRealizedVolatility(
45
63
  const windows = [...new Set(options.windows ?? REALIZED_VOLATILITY_WINDOWS)]
46
64
  .filter((window) => REALIZED_VOLATILITY_WINDOWS.some((standard) => standard === window));
47
65
  const warnings: string[] = [];
66
+ // What follows from a history shorter than the windows and lookback asked for: notes, not failures.
67
+ let shortLookback = false;
68
+ let shortWarmup = false;
69
+ const shortWindows: number[] = [];
48
70
  const dates = new Map<number, PricePoint>();
49
71
  for (const point of points) {
50
72
  const date = new Date(point.date);
@@ -66,24 +88,28 @@ export function projectRealizedVolatility(
66
88
  const cone = volatilityCone(cadenceIssue ? [] : history, { estimator, lookbackYears });
67
89
  const visible = history.filter((point) => point.date.getTime() > cutoff);
68
90
  const visibleSeries = series.filter((point) => point.date.getTime() > cutoff);
69
- if (history.length > 0 && history[0]!.date.getTime() > cutoff) {
70
- warnings.push(`History covers less than the requested ${lookbackYears}Y lookback`);
71
- }
91
+ if (history.length > 0 && history[0]!.date.getTime() > cutoff) shortLookback = true;
72
92
  for (const window of history.length > 0 && !cadenceIssue ? REALIZED_VOLATILITY_WINDOWS : []) {
73
93
  const result = realizedVolatilityResult(history, window, estimator);
74
- if (result.reason === "insufficient-history") warnings.push(`HV${window} needs more daily history`);
94
+ if (result.reason === "insufficient-history") shortWindows.push(window);
75
95
  else if (result.reason) warnings.push(`HV${window} unavailable: ${result.reason.replaceAll("-", " ")}`);
76
96
  }
77
97
  const priorWarmup = history.filter((point) => point.date.getTime() <= cutoff).length;
78
98
  const requiredWarmup = estimator === "close-to-close" || estimator === "yang-zhang" ? 260 : 259;
79
99
  if (history.length > 0 && !cadenceIssue && priorWarmup < requiredWarmup) {
80
- warnings.push("Long-window cone estimates do not cover the complete lookback");
100
+ shortWarmup = true;
81
101
  } else if (!cadenceIssue && cone.some((row) => row.sampleSize < visible.length)) {
82
102
  warnings.push("Unavailable historical estimates reduce volatility-cone sample coverage");
83
103
  }
104
+ const first = history[0]?.date.toISOString().slice(0, 10);
105
+ // Where the history starts, never read as the listing date: a source's first bar is not proof of when it listed.
106
+ const historyNote = !first || (!shortLookback && !shortWarmup && !shortWindows.length) ? null
107
+ : `History starts ${first}: ${history.length} daily bars so far; ${shortWindows.length
108
+ ? `${windowsText(shortWindows)} need${shortWindows.length === 1 ? "s" : ""} more`
109
+ : "the long windows' cone covers part of the lookback"}`;
84
110
  return { symbol: options.symbol.trim().toUpperCase(), estimator, lookbackYears, windows,
85
111
  history: visible, series: visibleSeries, cone, asOf, barsPerYear: barsPerYear(history.map((point) => point.date)),
86
- warnings: [...new Set(warnings)] };
112
+ warnings: [...new Set(warnings)], shortWindows, historyNote };
87
113
  }
88
114
 
89
115
  interface CurrentAtmIvReference {
@@ -107,6 +133,11 @@ export interface CurrentAtmIvSnapshot {
107
133
  noOptionChain?: boolean;
108
134
  }
109
135
 
136
+ /** When an IV was observed, to the minute in UTC (`2026-10-27 20:14 UTC`), in the pane and the report alike. */
137
+ export function atmIvObservedLabel(date: Date): string {
138
+ return `${date.toISOString().slice(0, 16).replace("T", " ")} UTC`;
139
+ }
140
+
110
141
  /** A LEAPS ATM IV is not comparable with a 10 to 260 session realized cone. */
111
142
  const MAX_CURRENT_ATM_IV_DAYS = 90;
112
143
 
@@ -18,7 +18,7 @@ import { useLiveSessionRefresh } from "../shared/volatility/live-session";
18
18
  import type { RealizedVolatilityEstimator, VolatilityConeStatistics } from "../shared/volatility";
19
19
  import { loadCurrentAtmIv, loadRealizedVolatilityHistory, refreshCurrentAtmIv } from "./client";
20
20
  import type { CurrentAtmIvSnapshot } from "./model";
21
- import { projectRealizedVolatility } from "./model";
21
+ import { atmIvObservedLabel, projectRealizedVolatility } from "./model";
22
22
  import { RealizedVolGraph, VolatilityConeChart } from "./charts";
23
23
  import { coneWindowDate } from "./chart-model";
24
24
  import type { RealizedVolEvidenceStatus } from "./evidence";
@@ -109,7 +109,7 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
109
109
  const refreshedCurrent = refreshedIv?.owner === ivLoader && refreshedIv.at > (loadedIv.updatedAt ?? 0) ? refreshedIv.data : null;
110
110
  const iv = { ...loadedIv, data: refreshedCurrent ?? loadedIv.data };
111
111
  ivDataRef.current = iv.data;
112
- useLiveSessionRefresh(refreshIv, ATM_IV_REFRESH_MS, showIv && spotAvailable && !!iv.data?.reference && !loadedIv.loading,
112
+ const ivRefresh = useLiveSessionRefresh(refreshIv, ATM_IV_REFRESH_MS, showIv && spotAvailable && !!iv.data?.reference && !loadedIv.loading,
113
113
  loadedIv.updatedAt);
114
114
  const model = useMemo(() => history.data ? projectRealizedVolatility(history.data.history, {
115
115
  symbol: history.data.symbol, estimator, windows, lookbackYears: Number(lookback) === 2 ? 2 : 1,
@@ -117,8 +117,8 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
117
117
  const chartInput = useMemo(() => model ? { history: model.history, rolling: model.series, windows: model.windows,
118
118
  priceUnit: priceSeriesUnit(quote?.currency ?? "", quote?.instrumentType), iv: showIv ? iv.data?.reference ?? null : null } : null,
119
119
  [model, quote?.currency, quote?.instrumentType, showIv, iv.data]);
120
- const notices = [identityError, history.error, history.data?.error, iv.error, iv.data?.error,
121
- ...(model?.warnings ?? []), ...(showIv ? iv.data?.warnings ?? [] : []),
120
+ const notices = [identityError, history.error, history.data?.error, iv.error, ivRefresh.error, iv.data?.error,
121
+ ...(model?.warnings ?? []), model?.historyNote, ...(showIv ? iv.data?.warnings ?? [] : []),
122
122
  ...(showIv && symbol && !spotAvailable ? ["Current ATM IV unavailable: underlying quote is missing or stale"] : []),
123
123
  ].filter((value): value is string => !!value);
124
124
  usePaneNoticeFooter({ registrationId: "realized-vol-notices", notices: [...new Set(notices)], focused });
@@ -154,15 +154,17 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
154
154
  role: "report-notice", label: "Chart window",
155
155
  getMetadata: () => ({ text: `Chart legend: ${legendWindow}-bar window`, key: "legendWindow", value: legendWindow }),
156
156
  } : null);
157
+ const ivErrors = showIv ? [...new Set([iv.error, ivRefresh.error, iv.data?.error, !iv.data?.reference ? "Current ATM IV unavailable" : null]
158
+ .filter((value): value is string => !!value))] : [];
157
159
  const evidence: RealizedVolEvidenceStatus = {
158
160
  symbol: model?.symbol ?? symbol ?? "", view: view === "cone" ? "cone" : "graph", estimator, legendWindow,
159
- windows, lookbackYears: Number(lookback) === 2 ? 2 : 1, showIv,
161
+ windows, lookbackYears: Number(lookback) === 2 ? 2 : 1, showIv, shortWindows: model?.shortWindows ?? [],
160
162
  loading: history.loading || (showIv && iv.loading),
161
163
  stale: history.data?.stale ?? false, source: history.data?.source ?? null,
162
164
  asOf: model?.asOf?.toISOString() ?? null,
163
- errors: [...new Set([history.error, history.data?.error, ...(model?.warnings ?? []),
164
- ...(showIv ? [iv.error, iv.data?.error, !iv.data?.reference ? "Current ATM IV unavailable" : null] : []),
165
- ].filter((value): value is string => !!value))],
165
+ errors: [...new Set([history.error, history.data?.error, ...(model?.warnings ?? []), ...ivErrors]
166
+ .filter((value): value is string => !!value))],
167
+ ivErrors,
166
168
  currentIv: showIv && iv.data?.reference ? { value: iv.data.reference.value * 100,
167
169
  date: iv.data.reference.date.toISOString(), label: iv.data.reference.label,
168
170
  source: iv.data.reference.source, expiration: iv.data.reference.expiration } : null,
@@ -183,7 +185,7 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
183
185
  <QueryBar width={width} filters={[
184
186
  { id: "estimator", label: "Estimator", value: estimator, options: ESTIMATOR_OPTIONS, onChange: (value: string) => setEstimator(value as RealizedVolatilityEstimator) },
185
187
  { id: "lookback", label: "Lookback", value: String(lookback), options: [{ value: "1", label: "1Y" }, { value: "2", label: "2Y" }], onChange: setLookback },
186
- ]} meta={reference ? `ATM IV observed ${reference.date.toISOString().slice(0, 16).replace("T", " ")} UTC` : "Annualized %"} />
188
+ ]} meta={reference ? `ATM IV observed ${atmIvObservedLabel(reference.date)}` : "Annualized %"} />
187
189
  {!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="realized volatility" loading={history.loading && !model}
188
190
  error={!model ? history.error ?? identityError ?? null : null} empty={!!model && !model.history.length}>
189
191
  {model && view === "cone" ? <>
@@ -7,6 +7,7 @@ import type {
7
7
  } from "../../../api-client/types";
8
8
  import { createPluginCache } from "../../../data/plugin-cache";
9
9
  import { isAccessDenied } from "../../../api-client/errors";
10
+ import { quoteTradingExchange } from "../../../market-data/quotes/metadata";
10
11
  import { cachedCloudResource, loadCloudResource } from "../shared/cloud-resource";
11
12
  import {
12
13
  buildRotation,
@@ -75,6 +76,10 @@ export function validateRotationHistory(
75
76
  throw new Error("Invalid daily history.");
76
77
  return response.data;
77
78
  }
79
+ /** The venue a quote is for, as `quote` names it; empty when it names none. */
80
+ function listingVenue(quote: CloudQuotePayload | undefined): string {
81
+ return quote ? quoteTradingExchange(quote) : "";
82
+ }
78
83
  export async function fetchRotation(
79
84
  benchmark: RotationInstrument,
80
85
  instruments: RotationInstrument[],
@@ -106,6 +111,16 @@ export async function fetchRotation(
106
111
  if (isAccessDenied(error)) throw error;
107
112
  quoteError = "Listing currencies unavailable.";
108
113
  }
114
+ // A benchmark typed without its exchange (ACWI) is the listing its quote is for (ACWI:NASDAQ), as `quote`
115
+ // reads it: that venue's calendar picks the weekly closes.
116
+ const venue = benchmark.exchange
117
+ ? ""
118
+ : listingVenue(quotes.get(rotationId(benchmark)));
119
+ const reference = venue ? { ...benchmark, exchange: venue } : benchmark;
120
+ const requested = unique.map((row) => ({
121
+ id: rotationId(row),
122
+ instrument: rotationId(row) === rotationId(benchmark) ? reference : row,
123
+ }));
109
124
  const start = new Date(now);
110
125
  start.setUTCFullYear(start.getUTCFullYear() - 3);
111
126
  start.setUTCDate(1);
@@ -115,10 +130,9 @@ export async function fetchRotation(
115
130
  let next = 0;
116
131
  // Bound Cloud history requests; shared server caches batch upstream demand.
117
132
  await Promise.all(
118
- Array.from({ length: Math.min(4, unique.length) }, async () => {
119
- while (next < unique.length) {
120
- const instrument = unique[next++]!,
121
- id = rotationId(instrument),
133
+ Array.from({ length: Math.min(4, requested.length) }, async () => {
134
+ while (next < requested.length) {
135
+ const { id, instrument } = requested[next++]!,
122
136
  currency = quotes.get(id)?.currency ?? null;
123
137
  try {
124
138
  const response = await client.getCloudHistory(
@@ -1,6 +1,6 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
2
  import { fetchRotation } from "./client";
3
- import { rotationInstruments, sectorRotationInstruments } from "./model";
3
+ import { DEFAULT_ROTATION_BENCHMARK, rotationInstruments, sectorRotationInstruments } from "./model";
4
4
 
5
5
  /** Same rounding as the pane: ratios to 2 decimals, percentiles whole. */
6
6
  const fixed = (value: unknown, digits: number) =>
@@ -21,7 +21,7 @@ export const rotationHeadless: HeadlessPaneDefinition<"bundle"> = {
21
21
  key: "benchmark",
22
22
  type: "string",
23
23
  description: "Benchmark listing.",
24
- defaultValue: "SPY:NYSEARCA",
24
+ defaultValue: DEFAULT_ROTATION_BENCHMARK,
25
25
  },
26
26
  {
27
27
  key: "trail",
@@ -44,7 +44,7 @@ export const rotationHeadless: HeadlessPaneDefinition<"bundle"> = {
44
44
  },
45
45
  async load(args, ctx) {
46
46
  const references = rotationInstruments(
47
- String(args.options.benchmark ?? "SPY:NYSEARCA"),
47
+ String(args.options.benchmark ?? DEFAULT_ROTATION_BENCHMARK),
48
48
  );
49
49
  if (references.length !== 1) throw new Error("Choose one benchmark.");
50
50
  const instruments = args.symbols.length
@@ -3,6 +3,7 @@ import type { PluginModule } from "../plugin-module";
3
3
  import { rotationCache } from "./client";
4
4
  import { rotationHeadless } from "./headless";
5
5
  import { RelativeRotationPane } from "./pane";
6
+ import { DEFAULT_ROTATION_BENCHMARK } from "./model";
6
7
 
7
8
  export const relativeRotationModule: PluginModule = {
8
9
  panes: [
@@ -41,7 +42,7 @@ export const relativeRotationModule: PluginModule = {
41
42
  key: "benchmark",
42
43
  label: "Benchmark",
43
44
  type: "text",
44
- placeholder: "SPY:NYSEARCA",
45
+ placeholder: DEFAULT_ROTATION_BENCHMARK,
45
46
  },
46
47
  {
47
48
  key: "trail",