@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
@@ -12,7 +12,7 @@ import type {
12
12
  Quote,
13
13
  TickerFinancials,
14
14
  } from "../../types/financials";
15
- import { currencyPriceMinDigits, formatMarketPrice, formatMarketPriceWithCurrency, quoteFormatOptions, resolveAssetDisplayKind, withCurrencyMinorDigits } from "../../market-data/market/format";
15
+ import { currencyPriceMinDigits, formatMarketPrice, formatMarketPriceWithCurrency, penceFigure, quoteFormatOptions, resolveAssetDisplayKind, withCurrencyMinorDigits } from "../../market-data/market/format";
16
16
  import { INDEX_POINTS_EXPORT_UNIT, isIndexLevel, reportedQuoteVolume } from "../../market-data/market/index-level";
17
17
  import { changeBasisJson, changeBasisLabel, changeBasisNote, quoteChangeBasis, quotePreviousCloseDate, quoteSettleText } from "../../market-data/market/change-basis";
18
18
  import {
@@ -41,6 +41,7 @@ import {
41
41
  import { cliArrow, cliStyles, colorBySign, renderSection, renderStats } from "../../utils/cli-output";
42
42
  import { filledPeriodsNote, stalePeriodNote, statementHistoryNotes } from "../../utils/financial-statements";
43
43
  import { formatPerShareNumber } from "../../utils/reported-money";
44
+ import { isCurrencyPairListing } from "../../utils/fx-market-hours";
44
45
  import {
45
46
  analystTargetCurrency,
46
47
  formatAnalystPrice,
@@ -58,7 +59,7 @@ import {
58
59
  } from "../../market-data/options-alternatives";
59
60
  import {
60
61
  atReportTtmRate, cliFuturesContract, futuresContractMetrics, exportedDividendFundamentals, fundamentalsReportTables, loadDividendDistributions,
61
- loadReportBondFacts, renderFundamentalsReport, writesCurrencyCodes,
62
+ loadReportFundFacts, renderFundamentalsReport, writesCurrencyCodes,
62
63
  } from "./ticker";
63
64
  import { createBaseConverter } from "../base-converter";
64
65
  import { loadStatementFilings } from "../statement-filings";
@@ -75,6 +76,7 @@ import {
75
76
  loadOptionModelInputs,
76
77
  loadOptionsChainOpenInterest,
77
78
  chainCoinCurrency,
79
+ chainHasFilledIv,
78
80
  formatCoinPremiumCell,
79
81
  formatOptionDeltaCell,
80
82
  formatOptionDollarCell,
@@ -90,6 +92,7 @@ import {
90
92
  optionsBasisFields,
91
93
  optionsBasisFootnote,
92
94
  parseOptionExpiration,
95
+ takeExpirationOption,
93
96
  valueChainAtModelSpot,
94
97
  type OptionsChainBasis,
95
98
  } from "./options-chain";
@@ -139,6 +142,7 @@ import {
139
142
  historyPriceDecimals,
140
143
  historyRows,
141
144
  historyUnit,
145
+ quotedPriceDivisor,
142
146
  historyVolume,
143
147
  historyVolumeText,
144
148
  type HistoryRow,
@@ -153,7 +157,7 @@ import {
153
157
  symbolListNotes,
154
158
  type HistoryListRow,
155
159
  } from "../history-export";
156
- import { isCliFailure } from "../errors";
160
+ import { fail, isCliFailure, NO_DATA_CODE } from "../errors";
157
161
  import { mapWithConcurrency } from "../../utils/concurrency";
158
162
  import { MISSING_VOLUME_TEXT } from "../../utils/price-history-integrity";
159
163
  import { CRYPTO_BOARD_HINT, quoteNotes } from "./crypto-hints";
@@ -215,6 +219,7 @@ import {
215
219
  type ResolvedEarningsEvent,
216
220
  } from "../../plugins/builtin/shared/earnings-calendar";
217
221
  import {
222
+ coinListingNotes,
218
223
  EXCHANGE_OPTION,
219
224
  listingHeading,
220
225
  listingIdentity,
@@ -303,6 +308,7 @@ import {
303
308
  unvaluedHoldersNote,
304
309
  } from "../../plugins/builtin/holders/report-rows";
305
310
  import { fetchBeneficialOwners } from "../../plugins/builtin/holders/beneficial-client";
311
+ import { fundHoldersAnswer } from "../../plugins/builtin/holders/fund-holders";
306
312
  import { centralBankEventsMessage } from "../central-bank-events";
307
313
  import { classSharesAsOf, classSharesToday } from "../../plugins/builtin/holders/client";
308
314
  import {
@@ -465,7 +471,8 @@ const QUOTE_LEAD_COLUMNS = [
465
471
  row.error && !row.price
466
472
  // A typo is not an outage: say what is wrong with the symbol itself.
467
473
  ? isNotATickerMessage(row.error) ? cliStyles.warning("not a ticker") : cliStyles.danger("unavailable")
468
- : yieldLevelCell(row.price, row)
474
+ // A London line adds the pence it trades in, as the exchange and RNS write it, muted as a table cell's note is: £6.308 630.8p.
475
+ : `${yieldLevelCell(row.price, row)}${row.pence ? ` ${cliStyles.muted(row.pence)}` : ""}`
469
476
  ),
470
477
  },
471
478
  {
@@ -729,10 +736,12 @@ function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
729
736
  // A price in cents says what it is per, as the contract terms word it: 314.95¢/lb.
730
737
  const per = display.price != null ? futuresPricePer(result.futuresContract, quote?.currency) : "";
731
738
  const previousClose = display.price != null && display.change != null ? display.price - display.change : quote?.previousClose;
739
+ const last = price(display.price);
732
740
  return {
733
741
  symbol: result.target.symbol,
734
742
  name: quote?.name || result.listing?.name || "",
735
- price: `${price(display.price)}${per}`,
743
+ price: `${last}${per}`,
744
+ pence: penceFigure(display.price, quote, /\.(\d+)/.exec(last)?.[1]?.length ?? 0),
736
745
  rawPrice: display.price,
737
746
  priceBasis: quote?.priceBasis ?? null,
738
747
  instrumentType: quote?.instrumentType ?? null,
@@ -1102,7 +1111,7 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
1102
1111
  return pair ? [`${row.target.symbol}: ${depositaryPairText(pair, isReceiptSide({ quote: row.quote }))}.`] : [];
1103
1112
  }),
1104
1113
  // A cell shows only "unavailable", and an exported row only its status, so stderr names each failure; the JSON rows carry each reason.
1105
- ...ctx.cliOptions.format !== "json" ? quoteNotes(data, { exchange }) : [],
1114
+ ...ctx.cliOptions.format !== "json" ? quoteNotes(data) : [],
1106
1115
  ];
1107
1116
  const columns = commandName === "compare" ? compareColumns() : quoteColumns();
1108
1117
  const basisNotes = changeBasisNotes(data, now);
@@ -1117,7 +1126,8 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
1117
1126
  // close does not read as a contradiction of DGS10 or GC for the same day.
1118
1127
  // Dated by the quotes as served, which say whether one is the saved copy.
1119
1128
  freshness: withSavedCopy(
1120
- quotesFreshness(data.map((row) => row.quote), yieldsOnly ? { schedule: `Cboe close ${CBOE_YIELD_INDEX_CLOSE}` } : undefined),
1129
+ // A table across markets (US and London lines) dates each market's session and says when each opens.
1130
+ quotesFreshness(data.map((row) => row.quote), { datesEachVenue: true, ...yieldsOnly ? { schedule: `Cboe close ${CBOE_YIELD_INDEX_CLOSE}` } : {} }),
1121
1131
  answers.map(({ result }) => result.quote),
1122
1132
  ),
1123
1133
  }, {
@@ -1399,7 +1409,11 @@ async function loadListingHistory(
1399
1409
  };
1400
1410
  // A coin's volume is traded value in its quote currency: said in every row and on the status line.
1401
1411
  const volume = historyVolume(unit, traded);
1402
- const rows = historyRows(points, resolution, unit.currency, unit.unit, volume);
1412
+ // Spot FX has no exchange to count its trades: a source's 0 is no volume, printed as missing.
1413
+ const pair = quote?.instrumentType === "CURRENCY" || isCurrencyPairListing(symbol, exchange);
1414
+ // A London line's bars were priced in pence: cleaned there, and printed to the pence decimals `quote` shows.
1415
+ const priceDivisor = quotedPriceDivisor(quote);
1416
+ const rows = historyRows(pair ? points.map(({ volume: _volume, ...point }) => point) : points, resolution, unit.currency, unit.unit, volume, priceDivisor);
1403
1417
  // Bars stop arriving on the listing's own trading days, not New York's.
1404
1418
  const venue = quote?.listingExchangeName || exchange || undefined;
1405
1419
  const served = range === "YTD" && !window ? rows.slice(Math.max(0, yearToDateStartIndex(rows.map((row) => row.date), rows[0]?.interval, venue))) : rows;
@@ -1450,7 +1464,7 @@ async function loadListingHistory(
1450
1464
  ].flat();
1451
1465
  return {
1452
1466
  listing, identity, unit, data, resolution, interval, intraday, volume, freshness, notes,
1453
- decimals: historyPriceDecimals(data, listing.saved?.metadata.assetCategory),
1467
+ decimals: historyPriceDecimals(data, listing.saved?.metadata.assetCategory, priceDivisor),
1454
1468
  };
1455
1469
  }
1456
1470
 
@@ -1720,6 +1734,22 @@ const STATEMENT_COLUMNS: CliResultColumn<ReturnType<typeof financialStatementRow
1720
1734
  { key: "currency", header: "Cur" },
1721
1735
  ];
1722
1736
 
1737
+ /** A symbol as typed in capitals (`NEE`, `BP:LSE`, `7203.T`), never a word of a company's name. */
1738
+ const TYPED_SYMBOL = /^[A-Z0-9][A-Z0-9.:^=&-]{0,15}$/;
1739
+
1740
+ /**
1741
+ * The symbols of a command given several where it takes one: `NEE FSLR` as
1742
+ * two words, or as one (`"NEE FSLR"`, `NEE,FSLR`). One word with spaces is
1743
+ * a list only when every part is a symbol as typed, so a company's name
1744
+ * (`"Bank of America"`) is still searched for. Null for one symbol.
1745
+ */
1746
+ export function comparedSymbols(args: readonly string[]): string[] | null {
1747
+ const words = args.flatMap((arg) => arg.split(/[\s,]+/)).filter(Boolean);
1748
+ if (words.length < 2) return null;
1749
+ if (args.length > 1 || args.some((arg) => arg.includes(","))) return words;
1750
+ return words.every((word) => TYPED_SYMBOL.test(word)) ? words : null;
1751
+ }
1752
+
1723
1753
  async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1], view: FinancialsView) {
1724
1754
  const args = [...rawArgs];
1725
1755
  const exchangeOption = takeOption(args, "--exchange");
@@ -1744,6 +1774,12 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
1744
1774
  const tabular = ctx.cliOptions.format === "csv" || ctx.cliOptions.format === "ndjson";
1745
1775
  if (sectionFlag && !tabular) ctx.fail("--section picks one table of --csv or --ndjson output.");
1746
1776
  if (sectionFlag && !section?.trim()) ctx.fail("--section needs a section title or number.");
1777
+ // Several symbols are a comparison, which RV draws side by side.
1778
+ const compared = view === "valuation" ? comparedSymbols(args) : null;
1779
+ if (compared) {
1780
+ ctx.fail(`valuation takes one symbol; got ${compared.join(" ")}.`,
1781
+ `To compare their valuations side by side: gloom fn RV ${compared.join(",")}\nUsage: gloom valuation <symbol>`);
1782
+ }
1747
1783
  const raw = requireOneArg(args, `${commandName} <symbol>`, "symbol", ctx);
1748
1784
  const selectTables = (tables: CliReportTables) => {
1749
1785
  try {
@@ -1765,8 +1801,8 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
1765
1801
  // declared distribution it annualizes (not in JSON), and one whose dividend stopped says so; read beside the listing notes.
1766
1802
  const distributions = view === "statements" ? null : loadDividendDistributions(
1767
1803
  market.dataProvider, listing.request, financials, { declared: ctx.cliOptions.format !== "json" });
1768
- // A bond fund's SEC yield, yield to worst and duration, beside its distribution yield.
1769
- const bondFund = view === "statements" ? null : loadReportBondFacts(listing.request.symbol,
1804
+ // A fund's facts: its bond figures beside its distribution yield, and whether fn ETF has any to point to.
1805
+ const fundFacts = view === "statements" ? null : loadReportFundFacts(listing.request.symbol,
1770
1806
  financials.quote?.tradingExchangeName || listing.request.exchange || financials.quote?.listingExchangeName || financials.quote?.exchangeName,
1771
1807
  financials, listing.saved);
1772
1808
  const treasury = rates ? loadTreasuryTenors() : null;
@@ -1795,13 +1831,14 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
1795
1831
  loadBankFigures(market.statementSources, listing.request, financials),
1796
1832
  ])
1797
1833
  : [null, { bank: null, note: null }];
1798
- const bond = await bondFund;
1834
+ const facts = await fundFacts;
1835
+ const bond = facts?.bond ?? null;
1799
1836
  // On the SEC yield when the sponsor publishes one, else the distribution yield the line above shows.
1800
1837
  const taxEquivalent = rates ? analyzeTaxEquivalent({
1801
1838
  symbol: identity.symbol || listing.key, bond, rates, treasury: await treasury,
1802
1839
  distribution: distributionBasis(priced.fundamentals?.dividendYield, priced.fundamentals?.dividendYieldBasis === "trailing" ? "trailing" : "forward"),
1803
1840
  }) : null;
1804
- const report = { ...data, distributions: await distributions, statementFilings, bank: bank.bank, bankNote: bank.note, bondFund: bond, taxEquivalent,
1841
+ const report = { ...data, distributions: await distributions, statementFilings, bank: bank.bank, bankNote: bank.note, bondFund: bond, fundFacts: facts, taxEquivalent,
1805
1842
  // A base currency other than USD reads every amount with its code, as `ticker` does.
1806
1843
  currencyCodes: writesCurrencyCodes(market.config) };
1807
1844
  ctx.printResult({
@@ -2043,7 +2080,8 @@ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
2043
2080
  return;
2044
2081
  }
2045
2082
  const empty = emptyMessage(false);
2046
- const read = listing ? listingReadNotes(listing) : [];
2083
+ // A bare coin ticker read as another listing (ETH, the Grayscale fund) says where the coin is, as `quote` does.
2084
+ const read = listing ? [...listingReadNotes(listing), ...coinListingNotes(listing, quote)] : [];
2047
2085
  // A word that is also the company's name ("gold" for Gold.com Inc.) says once which ticker it found and how
2048
2086
  // to search the stories that mention it instead, when those stories are mostly about something else.
2049
2087
  const typed = args[0]?.trim() ?? "";
@@ -2401,14 +2439,20 @@ async function runHolders(
2401
2439
  // --tail counts back from the end of the rows loaded, so it loads at least as many as it asks for.
2402
2440
  const tail = ctx.cliOptions.tail;
2403
2441
  const indexLimit = Math.min(ctx.cliOptions.limit ?? Math.max(tail ?? 0, HOLDERS_DEFAULT_ROWS), HOLDER_ROWS_MAX);
2442
+ const quoted = loadListingQuote(market.dataProvider, listing);
2404
2443
  const [source, quote, form4, index] = await Promise.all([
2405
2444
  loadForListing(
2406
2445
  listing, market, ctx,
2407
2446
  () => market.dataProvider.getHolders(listing.request.symbol, listing.request.exchange),
2408
2447
  (found) => found.holders.length === 0,
2409
2448
  { command: commandName, noun: "holder data" },
2410
- ),
2411
- loadListingQuote(market.dataProvider, listing),
2449
+ ).catch(async (error: unknown) => {
2450
+ // A fund has no ownership breakdown: say so, and what reads it, rather than the source's NO_DATA.
2451
+ const fund = await fundHoldersAnswer(listing.symbol, error, await quoted, apiClient);
2452
+ if (fund) fail(fund.message, fund.details.join("\n") || undefined, NO_DATA_CODE);
2453
+ throw error;
2454
+ }),
2455
+ quoted,
2412
2456
  insiderCommand ? loadForm4Report(market, listing) : null,
2413
2457
  indexTicker ? loadIndexedHolders(indexTicker, indexLimit).catch(logFailure("cli.commands-market", "Read the 13F holder index failed", null)) : null,
2414
2458
  ]);
@@ -2616,12 +2660,13 @@ async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execu
2616
2660
  async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
2617
2661
  const args = [...rawArgs];
2618
2662
  const leapsOptions = takeLeapsOptions(args);
2619
- const expiration = takeOption(args, "--expiration");
2663
+ const expirationOption = takeExpirationOption(args);
2664
+ const { value: expiration, flag: expirationFlag } = expirationOption;
2665
+ if (expirationOption.both) ctx.fail("--expiry is --expiration; give one of them.", `Usage: gloom ${OPTIONS_USAGE}`);
2620
2666
  const exchangeOption = takeOption(args, "--exchange");
2621
2667
  if (leapsOptions.leaps) {
2622
2668
  // With --leaps, --expiration is a window of one expiry.
2623
- const expirationGiven = expiration != null || rawArgs.some((arg) => arg === "--expiration" || arg.startsWith("--expiration="));
2624
- const criteria = parseLeapsCriteria({ ...leapsOptions.raw, ...expirationGiven ? { "--expiration": expiration ?? "" } : {} });
2669
+ const criteria = parseLeapsCriteria({ ...leapsOptions.raw, ...expirationOption.given ? { "--expiration": expiration ?? "" } : {} });
2625
2670
  if ("error" in criteria) ctx.fail(criteria.error, `Usage: gloom ${LEAPS_USAGE}`);
2626
2671
  const entries = normalizeSymbols(args);
2627
2672
  if (entries.length === 0) ctx.fail("--leaps needs at least one symbol.", `Usage: gloom ${LEAPS_USAGE}`);
@@ -2633,11 +2678,11 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
2633
2678
  }
2634
2679
  const raw = requireOneArg(args, OPTIONS_USAGE, "symbol", ctx);
2635
2680
  let expirationDate: number | undefined;
2636
- if (expiration != null || rawArgs.includes("--expiration")) {
2681
+ if (expirationOption.given) {
2637
2682
  const parsed = expiration == null ? null : parseOptionExpiration(expiration);
2638
2683
  if (parsed == null) {
2639
2684
  ctx.fail(
2640
- expiration == null ? "--expiration needs a date." : `Invalid --expiration "${expiration}".`,
2685
+ expiration == null ? `${expirationFlag} needs a date.` : `Invalid ${expirationFlag} "${expiration}".`,
2641
2686
  `Use ${OPTION_EXPIRATION_FORMAT}.${expiration == null ? "" : expirationHint(expiration)}\nUsage: gloom ${OPTIONS_USAGE}`,
2642
2687
  );
2643
2688
  }
@@ -2669,7 +2714,7 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
2669
2714
  const listed = await readChain(undefined).then(({ chain }) => chain.expirationDates,
2670
2715
  logFailure("cli-market", "Read available option expirations failed", []));
2671
2716
  ctx.fail(`expiry ${expiryIsoDate(expirationDate)} for ${listing.key} `
2672
- + expiredExpiryText(expirationDate, listed, now, { exchange: expiryVenue, flag: "expiration" }),
2717
+ + expiredExpiryText(expirationDate, listed, now, { exchange: expiryVenue, flag: expirationFlag.slice(2) }),
2673
2718
  `Run gloom options ${listing.key} to list the live expiries.`);
2674
2719
  }
2675
2720
  const { result, chain: read } = await loadForListing(listing, market, ctx, async () => {
@@ -2711,7 +2756,7 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
2711
2756
  // IV and delta are valued at one spot and instant, named under the table and on every exported row.
2712
2757
  const { chain, spot: modelSpot } = valueChainAtModelSpot(loaded, inputs, Date.now(), identity.exchange);
2713
2758
  const basis: OptionsChainBasis = { spot: modelSpot, dividendYield: inputs.dividendYield ?? null,
2714
- openInterest: await loadOptionsChainOpenInterest(symbol, chain) };
2759
+ openInterest: await loadOptionsChainOpenInterest(symbol, chain), ivFilled: chainHasFilledIv(chain) };
2715
2760
  const basisFields = optionsBasisFields(basis);
2716
2761
  const currency = [...chain.calls, ...chain.puts].find((contract) => contract.currency?.trim())?.currency.trim().toUpperCase()
2717
2762
  ?? quote?.currency ?? null;
@@ -2783,6 +2828,7 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
2783
2828
  { key: "bid", header: "Bid" },
2784
2829
  { key: "ask", header: "Ask" },
2785
2830
  { key: "iv", header: "IV (annualized decimal)" },
2831
+ { key: "ivSource", header: "IV source" },
2786
2832
  { key: "delta", header: "Delta (decimal)" },
2787
2833
  { key: "volume", header: "Vol" },
2788
2834
  { key: "openInterest", header: "OI" },
@@ -2810,7 +2856,7 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
2810
2856
  { key: "coinAsk", header: `Ask ${coin}`, align: "right", format: formatCoinPremiumCell },
2811
2857
  { key: "coinMark", header: `Mark ${coin}`, align: "right", format: formatCoinPremiumCell },
2812
2858
  { key: "mark", header: "Mark $", align: "right", format: formatOptionDollarCell },
2813
- { key: "iv", header: "IV", align: "right", format: formatOptionIvCell },
2859
+ { key: "iv", header: "IV", align: "right", format: (value, row) => formatOptionIvCell(value, row) },
2814
2860
  { key: "delta", header: "Delta", align: "right", format: formatOptionDeltaCell },
2815
2861
  { key: "gamma", header: "Gamma", align: "right", format: formatOptionGammaCell, optional: true, dropPriority: 3 },
2816
2862
  { key: "vega", header: "Vega $", align: "right", format: formatOptionDollarCell, optional: true, dropPriority: 2 },
@@ -2826,7 +2872,7 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
2826
2872
  { key: "last", header: "Last", align: "right" },
2827
2873
  { key: "bid", header: "Bid", align: "right", format: (_value, row) => formatOptionQuoteCell(row, "bid") },
2828
2874
  { key: "ask", header: "Ask", align: "right", format: (_value, row) => formatOptionQuoteCell(row, "ask") },
2829
- { key: "iv", header: "IV", align: "right", format: formatOptionIvCell },
2875
+ { key: "iv", header: "IV", align: "right", format: (value, row) => formatOptionIvCell(value, row) },
2830
2876
  { key: "delta", header: "Delta", align: "right", format: formatOptionDeltaCell },
2831
2877
  { key: "volume", header: "Vol", align: "right", format: formatCountCell, optional: true, dropPriority: 1 },
2832
2878
  { key: "openInterest", header: "OI", align: "right", format: formatCountCell, optional: true, dropPriority: 1 },
@@ -3250,7 +3296,7 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
3250
3296
  usage: [OPTIONS_USAGE, LEAPS_USAGE],
3251
3297
  options: [
3252
3298
  {
3253
- flags: `--expiration <${OPTION_EXPIRATION_PLACEHOLDER}>`,
3299
+ flags: `--expiration <${OPTION_EXPIRATION_PLACEHOLDER}>, --expiry <${OPTION_EXPIRATION_PLACEHOLDER}>`,
3254
3300
  description: "Expiration as a date (2028-01-21) or Unix seconds; defaults to the nearest one, and lists the others. With --leaps: rank that expiry alone",
3255
3301
  },
3256
3302
  EXCHANGE_OPTION,
@@ -4,7 +4,7 @@ import type { DataProvider } from "../../types/data-provider";
4
4
  import type { CliListing } from "../listing-arg";
5
5
  import { cliStyles, cliTerminalWidth, wrapText } from "../../utils/cli-output";
6
6
  import { isFiniteNumber } from "../../utils/guards";
7
- import { calculateOptionGreeks, solveChainVolatilities } from "../../plugins/builtin/options/analytics";
7
+ import { calculateOptionGreeks, ivSolvedNote, solveChainVolatilities } from "../../plugins/builtin/options/analytics";
8
8
  import { optionQuoteSide } from "../../plugins/builtin/options/market-reference";
9
9
  import {
10
10
  formatModelSpotPrice, modelSpotLabel, optionModelSpot, type OptionModelSpot,
@@ -16,9 +16,30 @@ import { DEFAULT_OPTION_CALC_DRAFT } from "../../plugins/builtin/options-calcula
16
16
  import { expiryIsoDate, OPTION_EXPIRATION_PLACEHOLDER, parseOptionExpiration, settlementStampTime } from "../../utils/option-expiry";
17
17
  import { loadModelDividendYield } from "../../market-data/model-dividend-yield";
18
18
  import { formatUtcTime } from "../../utils/utc-time";
19
+ import { takeOption } from "./command-utils";
19
20
 
20
21
  export const OPTIONS_USAGE = `options <symbol> [--expiration <${OPTION_EXPIRATION_PLACEHOLDER}>]`;
21
22
 
23
+ /** `--expiration` as `options` names it, and `--expiry` as `fn GEX` and `fn OPX` do. */
24
+ const OPTIONS_EXPIRATION_FLAGS = ["--expiration", "--expiry"] as const;
25
+ type OptionsExpirationFlag = (typeof OPTIONS_EXPIRATION_FLAGS)[number];
26
+
27
+ /**
28
+ * Takes the expiry option out of `args`, under either spelling: its value,
29
+ * the flag as typed (for messages), whether it was given at all (a flag with
30
+ * no value is given), and whether both spellings were.
31
+ */
32
+ export function takeExpirationOption(args: string[]): {
33
+ value: string | undefined;
34
+ flag: OptionsExpirationFlag;
35
+ given: boolean;
36
+ both: boolean;
37
+ } {
38
+ const typed = OPTIONS_EXPIRATION_FLAGS.filter((flag) => args.some((arg) => arg === flag || arg.startsWith(`${flag}=`)));
39
+ const [expiration, expiry] = OPTIONS_EXPIRATION_FLAGS.map((flag) => takeOption(args, flag));
40
+ return { value: expiration ?? expiry, flag: typed.length === 1 ? typed[0]! : "--expiration", given: typed.length > 0, both: typed.length > 1 };
41
+ }
42
+
22
43
  export { parseOptionExpiration };
23
44
 
24
45
  /** Whether the chain holds contracts of the requested expiry, matched by UTC calendar date as the chain stamps them. */
@@ -63,6 +84,8 @@ export interface OptionModelInputs {
63
84
  interface OptionModelFigures {
64
85
  /** Annualized volatility as a fraction, solved from the chain's quotes. */
65
86
  iv: number | null;
87
+ /** `iv` was read off the neighbouring strikes: this strike has no two-sided out-of-the-money quote to solve from. */
88
+ ivFilled?: boolean;
66
89
  delta: number | null;
67
90
  }
68
91
 
@@ -73,6 +96,7 @@ export const OPTION_MODEL_RATE = DEFAULT_OPTION_CALC_DRAFT.rate;
73
96
  export const MARKED_OPTION_FIELDS = {
74
97
  iv: "The venue's mark implied volatility (decimal), one per contract. Delta and the other greeks under mark are the venue's too: "
75
98
  + "Black-76 at the expiry's forward with no discounting, to its settlement.",
99
+ ivSource: "mark: iv is the venue's own, never solved or filled here.",
76
100
  bid: "Prices in USD are the coin prices (coin) at the expiry's forward (mark.forward), the venue's own model price.",
77
101
  mark: "price in USD; iv; delta and gamma per 1.00 move in the underlying; vega per volatility point and theta per calendar day, in USD.",
78
102
  } as const;
@@ -91,6 +115,8 @@ export function chainCoinCurrency(chain: OptionsChain): string | null {
91
115
  export const OPTION_VOLATILITY_FIELDS = {
92
116
  iv: "Annualized volatility (decimal) solved from the quote midpoint, one per strike from its out-of-the-money side, at the "
93
117
  + "spot, valuation time and rate in metadata.model. Delta here and dealer gamma (GEX) are valued with it.",
118
+ ivSource: "solved: iv comes from the strike's out-of-the-money quote midpoint. filled: that side has no two-sided quote, so iv is read off the "
119
+ + "nearest solved strikes (flat beyond the last one); it is no market's volatility, and delta is valued with it all the same.",
94
120
  impliedVolatility: "The data provider's own reported implied volatility, passed through unchanged. Delta does not use it; "
95
121
  + "GEX falls back to it only at a strike with no two-sided quote on either side.",
96
122
  } as const;
@@ -116,8 +142,9 @@ function optionModelFigures(chain: OptionsChain, inputs: OptionModelInputs): Map
116
142
  for (const contract of contracts) {
117
143
  const volatility = volatilities.byStrike.get(contract.strike);
118
144
  const delta = calculateOptionGreeks(contract, side, inputs.spot, inputs.dividendYield, volatilities)?.delta;
145
+ const iv = volatility != null && Number.isFinite(volatility) && volatility > 0 ? volatility : null;
119
146
  figures.set(contract, {
120
- iv: volatility != null && Number.isFinite(volatility) && volatility > 0 ? volatility : null,
147
+ iv, ivFilled: iv != null && volatilities.filled.has(contract.strike),
121
148
  delta: delta != null && Number.isFinite(delta) ? delta : null,
122
149
  });
123
150
  }
@@ -167,7 +194,8 @@ export async function loadOptionModelInputs(
167
194
  /** The chain with each contract's modeled `iv` and `delta` added; every provider field is kept. */
168
195
  export function chainWithModelFigures(chain: OptionsChain, inputs: OptionModelInputs): OptionsChain {
169
196
  const figures = optionModelFigures(chain, inputs);
170
- const add = (contract: OptionContract) => ({ ...contract, iv: figures.get(contract)?.iv ?? null, delta: figures.get(contract)?.delta ?? null });
197
+ const add = (contract: OptionContract) => ({ ...contract, iv: figures.get(contract)?.iv ?? null, ivFilled: figures.get(contract)?.ivFilled ?? false,
198
+ delta: figures.get(contract)?.delta ?? null });
171
199
  return { ...chain, calls: chain.calls.map(add), puts: chain.puts.map(add) };
172
200
  }
173
201
 
@@ -180,6 +208,13 @@ export interface OptionsChainBasis {
180
208
  spot: OptionModelSpot | null;
181
209
  dividendYield: number | null;
182
210
  openInterest: OpenInterestAsOf | null;
211
+ /** A strike's IV is filled from its neighbours (`chainHasFilledIv`), which the closing line says. */
212
+ ivFilled?: boolean;
213
+ }
214
+
215
+ /** Whether any contract's IV was read off the neighbouring strikes (see `ChainVolatilities.filled`). */
216
+ export function chainHasFilledIv(chain: OptionsChain): boolean {
217
+ return [...chain.calls, ...chain.puts].some((contract) => (contract as OptionContract & OptionModelFigures).ivFilled === true);
183
218
  }
184
219
 
185
220
  /** The chain with `iv` and `delta` valued at the spot `optionModelSpot` picks for it, and that spot. */
@@ -207,7 +242,8 @@ export async function loadOptionsChainOpenInterest(symbol: string, chain: Option
207
242
  }
208
243
 
209
244
  /**
210
- * The line under the chain: `Greeks at spot 89.79 (close Mon 12 Oct 20:00 UTC), dividend yield 2.34%, rate 4% · OI as of Fri 9 Oct`.
245
+ * The line under the chain: `Greeks at spot 89.79 (close Mon 12 Oct 20:00 UTC), dividend yield 2.34%, rate 4% · IV solved from quote mids ·
246
+ * OI as of Fri 9 Oct`, with `, ~ filled from neighbouring strikes` after the IV when a strike has no quote to solve from.
211
247
  * An open interest whose date cannot be established says why. A chain its
212
248
  * venue marks says whose marks they are: `Deribit marks: IV and greeks at
213
249
  * each expiry's forward, rate 0%; $ is the BTC price at the forward · OI live, Thu 22 Oct 22:42 UTC`.
@@ -230,6 +266,7 @@ export function optionsBasisFootnote(basis: OptionsChainBasis, chain?: OptionsCh
230
266
  // An unknown yield is valued as none, and says so rather than read as a stated 0%.
231
267
  + `dividend yield ${basis.dividendYield == null ? "unknown (valued at 0%)" : `${Number((basis.dividendYield * 100).toFixed(2))}%`}, `
232
268
  + `rate ${Number((OPTION_MODEL_RATE * 100).toFixed(2))}%`);
269
+ parts.push(ivSolvedNote(basis.ivFilled === true));
233
270
  }
234
271
  const oi = basis.openInterest;
235
272
  parts.push(oi?.date ? openInterestAsOfLabel(oi)
@@ -266,6 +303,8 @@ export function optionRows(chain: OptionsChain) {
266
303
  volume: contract.volume,
267
304
  openInterest: contract.openInterest,
268
305
  iv: modeled.iv ?? null,
306
+ // Solved from the strike's quote, or filled from its neighbours when it has none to solve from; a venue's own mark is neither.
307
+ ivSource: modeled.iv == null ? null : contract.mark ? "mark" : modeled.ivFilled ? "filled" : "solved",
269
308
  delta: modeled.delta ?? null,
270
309
  expiration: expiryIsoDate(contract.expiration),
271
310
  // A venue's marks and coin prices (Deribit), beside the dollar ones.
@@ -301,9 +340,11 @@ export function formatOptionQuoteCell(row: Record<string, unknown>, side: "bid"
301
340
  return quote == null ? "—" : String(quote);
302
341
  }
303
342
 
304
- /** Implied volatility as a percent; a dash when it could not be solved, never 0.0%. */
305
- export function formatOptionIvCell(value: unknown): string {
306
- return isFiniteNumber(value) && value > 0 ? `${(value * 100).toFixed(1)}%` : "—";
343
+ /** Implied volatility as a percent; a dash when it could not be solved, never 0.0%, and a leading "~" when it was filled from neighbouring strikes. */
344
+ export function formatOptionIvCell(value: unknown, row?: Record<string, unknown>): string {
345
+ if (!isFiniteNumber(value) || !(value > 0)) return "—";
346
+ const text = `${(value * 100).toFixed(1)}%`;
347
+ return row?.ivSource === "filled" ? `~${text}` : text;
307
348
  }
308
349
 
309
350
  /** Delta to two places, signed for puts; a dash when it could not be valued, and no "-0.00" for a far wing. */
@@ -281,6 +281,8 @@ export type LeapsRow = {
281
281
  // The fields below are on a row only when `--columns` asked for them.
282
282
  /** `iv`: the implied volatility the contract's delta is valued at, percent a year. */
283
283
  ivPercent?: number | null;
284
+ /** `iv`: `filled` when that strike has no two-sided out-of-the-money quote to solve from and its IV is read off the neighbouring strikes. */
285
+ ivSource?: "solved" | "filled" | null;
284
286
  /** `ivr`: the underlying's 30-day IV rank against its 52 weeks, 0 to 100, and the close it ranks. */
285
287
  ivRank?: number | null;
286
288
  ivRankAsOf?: string | null;
@@ -369,7 +371,10 @@ function screenLeapsChain(
369
371
  dataSource: chain.dataSource ?? null,
370
372
  delayMinutes: chain.delayMinutes ?? null,
371
373
  };
372
- if (wants.has("iv")) row.ivPercent = contract.iv != null && Number.isFinite(contract.iv) ? round(contract.iv * 100, 2) : null;
374
+ if (wants.has("iv")) {
375
+ row.ivPercent = contract.iv != null && Number.isFinite(contract.iv) ? round(contract.iv * 100, 2) : null;
376
+ row.ivSource = row.ivPercent == null ? null : (contract as { ivFilled?: boolean }).ivFilled ? "filled" : "solved";
377
+ }
373
378
  // A two-sided quote passed the spread cap, so the midpoint is there.
374
379
  const mid = optionMid(contract)!;
375
380
  if (wants.has("cost")) row.costPerContract = round(mid * SHARES_PER_CONTRACT, 2);
@@ -694,7 +699,7 @@ const LEAPS_COLUMNS: CliResultColumn<LeapsRow>[] = [
694
699
 
695
700
  /** What each `--columns` name adds to the exports, after Extrinsic/yr %. */
696
701
  const EXTRA_EXPORT_COLUMNS: Record<LeapsExtraColumn, CliResultColumn<LeapsRow>[]> = {
697
- iv: [{ key: "ivPercent", header: "IV %", align: "right", format: formatPercent }],
702
+ iv: [{ key: "ivPercent", header: "IV %", align: "right", format: formatPercent }, { key: "ivSource", header: "IV source" }],
698
703
  ivr: [{ key: "ivRank", header: "IV Rank", align: "right" }, { key: "ivRankAsOf", header: "IV Rank As Of" }],
699
704
  cost: [{ key: "costPerContract", header: "Cost/Contract (mid x 100)", align: "right" }],
700
705
  breakeven: [
@@ -717,7 +722,8 @@ const LEAPS_TEXT_COLUMNS: CliResultColumn<LeapsRow>[] = TEXT_COLUMN_KEYS.map((ke
717
722
 
718
723
  /** The text table's columns for a contract's own `--columns` figures; the underlying's go in a table of their own above. */
719
724
  const EXTRA_TEXT_COLUMNS: Partial<Record<LeapsExtraColumn, CliResultColumn<LeapsRow>[]>> = {
720
- iv: [{ key: "ivPercent", header: "IV", align: "right", format: formatPercent }],
725
+ iv: [{ key: "ivPercent", header: "IV", align: "right", format: (value, row) => row.ivSource === "filled" && typeof value === "number" && Number.isFinite(value)
726
+ ? `~${value.toFixed(1)}%` : formatPercent(value) }],
721
727
  "carry-net": [{ key: "carryNetPerYearPercent", header: "Net/yr", align: "right", format: formatPercent }],
722
728
  cost: [{ key: "costPerContract", header: "Cost", align: "right", format: formatWhole }],
723
729
  breakeven: [