@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
@@ -4,6 +4,8 @@ import {
4
4
  KPI_FIELDS,
5
5
  NUMERIC_FIELDS,
6
6
  type NumericField,
7
+ type ScreenMetric,
8
+ type ScreenPayload,
7
9
  type ScreenRow,
8
10
  } from "../../../api-client/equity-screener";
9
11
  import { KPI_COVERAGE_NOTE } from "../company-kpis/model";
@@ -12,9 +14,19 @@ import { buildQuoteKey } from "../../../market-data/selectors";
12
14
  import type { Quote } from "../../../types/financials";
13
15
  import { formatCurrency, formatPercentRaw } from "../../../utils/format";
14
16
  import { fetchSavedScreens, fetchScreen, fetchScreenCheck } from "./client";
17
+ import { SCREEN_IDEAS, screenIdea } from "./ideas";
15
18
  import { checkRows, screenCheckSummary, screenCheckTicker } from "./screen-check";
16
19
  import { overlayScreenQuotes, screenExtendedSessions, sortLiveScreenRows } from "./live";
17
20
  import {
21
+ CATEGORY_NAMES_POINTER,
22
+ SCREEN_INDUSTRIES,
23
+ SCREEN_SECTORS,
24
+ unmatchedCategoryText,
25
+ unmatchedCategoryValues,
26
+ } from "./categories";
27
+ import { REPORT_ROWS } from "../shared/headless-options";
28
+ import {
29
+ appendScreenPage,
18
30
  DEFAULT_SCREEN,
19
31
  EXAMPLE_SCREEN_DEFINITION,
20
32
  formatScreenMetric,
@@ -71,6 +83,75 @@ function extendedColumn(session: ExtendedSession): HeadlessPaneColumn {
71
83
  };
72
84
  }
73
85
 
86
+ /** Rows a report lists without `--limit`: one page, as the pane loads before it pages. */
87
+ const SCREEN_PAGE_ROWS = 100;
88
+ /** The most rows `--limit` lists, read a page at a time. */
89
+ const SCREEN_ROW_LIMIT = 500;
90
+ /** The most rows the server returns in one page. */
91
+ const SCREEN_MAX_PAGE = 200;
92
+
93
+ /** The rows a report lists: pages of the screen, in its order, until `limit` rows or the last match. */
94
+ async function fetchScreenRows(definition: ScreenDefinition, limit: number, ctx: HeadlessPaneContext): Promise<ScreenPayload> {
95
+ let data = await fetchScreen(definition, null, ctx.signal, ctx.apiClient, Math.min(limit, SCREEN_MAX_PAGE));
96
+ while (data.rows.length < limit && data.nextCursor) {
97
+ const page = await fetchScreen(definition, data.nextCursor, ctx.signal, ctx.apiClient, Math.min(limit - data.rows.length, SCREEN_MAX_PAGE));
98
+ data = { ...appendScreenPage(data, page), warnings: [...new Set([...data.warnings, ...page.warnings])] };
99
+ }
100
+ return data;
101
+ }
102
+
103
+ /** What a report of the first rows says of the rest. */
104
+ function moreMatchesNotice(shown: number, matched: number): string {
105
+ return matched <= SCREEN_ROW_LIMIT
106
+ ? `Top ${shown} of ${matched} matches; --limit ${matched} lists them all.`
107
+ : `Top ${shown} of ${matched} matches; --limit lists up to ${SCREEN_ROW_LIMIT}, or narrow the criteria to see the rest.`;
108
+ }
109
+
110
+ const isNumericField = (field: string): field is NumericField => (NUMERIC_FIELDS as readonly string[]).includes(field);
111
+ const metricToken = (value: string) => value.trim().toLowerCase().replace(/[\s_-]+/g, "");
112
+
113
+ /** `--metric trailingPE,dividendYieldPercent` as the fields it names, each once, in order; an unknown one fails. */
114
+ function readMetrics(value: string): NumericField[] {
115
+ const fields = value.split(",").map((part) => part.trim()).filter(Boolean).map((part) => {
116
+ const field = NUMERIC_FIELDS.find((candidate) => metricToken(candidate) === metricToken(part));
117
+ if (!field) throw new Error(`Invalid --metric value "${part}". Use one of: ${NUMERIC_FIELDS.join(", ")}.`);
118
+ return field;
119
+ });
120
+ if (fields.length === 0) throw new Error("--metric requires a value, such as --metric trailingPE.");
121
+ return [...new Set(fields)];
122
+ }
123
+
124
+ type ShownCategory = "sector" | "industry" | "currency";
125
+ const CATEGORY_HEADERS: Record<ShownCategory, string> = { sector: "Sector", industry: "Industry", currency: "Currency" };
126
+ /** Columns every report prints, so neither another metric nor the sort field adds them again. */
127
+ const ALWAYS_SHOWN = new Set(["symbol", "exchange", "name", "price", "changePercent"]);
128
+
129
+ /**
130
+ * The columns a report adds after the first metric's: the other metrics
131
+ * asked for, then the field the screen is sorted by, so the order of the
132
+ * rows is explained by a column (P/E focus, sorted by dividend yield). A
133
+ * column every report prints (Last, Chg%) is not added twice, and its raw
134
+ * number stays in the row.
135
+ */
136
+ function extraScreenColumns(metrics: readonly NumericField[], sortField: string): Array<NumericField | ShownCategory> {
137
+ return [...new Set([...metrics.slice(1), sortField as NumericField | ShownCategory])]
138
+ .filter((field) => field !== metrics[0] && !ALWAYS_SHOWN.has(field));
139
+ }
140
+
141
+ /** Another metric's cell: its value as the focus column prints it, with the currency of a money value. */
142
+ function extraMetricCell(field: NumericField, metric: ScreenMetric | undefined): string {
143
+ if (!metric) return "-";
144
+ if (metric.access === "pro") return "locked";
145
+ const value = formatScreenMetric(field, metric);
146
+ return metric.value != null && /^[A-Z]{3}$/.test(metric.unit) ? `${value} ${metric.unit}` : value;
147
+ }
148
+
149
+ /** What the State column's words mean, said once under a report whose first metric has them. */
150
+ const STATE_LEGEND: ReadonlyArray<{ state: ScreenMetric["state"]; text: string }> = [
151
+ { state: "partial", text: "A partial State means the source gives no date for the figure, so As of is when it was collected, or that it covers part of what it measures." },
152
+ { state: "stale", text: "A stale State means the figure is older than its field allows, such as a quote over 4 days old or fundamentals collected over 7 days ago." },
153
+ ];
154
+
74
155
  /** A typed `--definition`, checked; every error points at the worked example. */
75
156
  function readDefinition(text: string): ScreenDefinition {
76
157
  const pointer = "The example in gloom catalog EQS is a valid definition to start from.";
@@ -112,6 +193,14 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
112
193
  shot: { reportOnly: "opens a saved screen for the report; open it from Saved in the EQS pane" },
113
194
  replaces: ["definition"],
114
195
  },
196
+ {
197
+ key: "preset",
198
+ type: "enum",
199
+ description: `A ready screen from Ideas, ranked by its own metric unless --metric says otherwise: ${SCREEN_IDEAS.map((entry) => `${entry.id} (${entry.name})`).join(", ")}. Cannot be combined with --saved or --definition.`,
200
+ values: SCREEN_IDEAS.map((entry) => ({ value: entry.id })),
201
+ example: "--preset golden-cross",
202
+ replaces: ["definition", "saved"],
203
+ },
115
204
  {
116
205
  key: "definition",
117
206
  type: "string",
@@ -126,15 +215,32 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
126
215
  {
127
216
  key: "metric",
128
217
  type: "enum",
218
+ list: true,
219
+ // No default: a preset leads with its own metric, anything else with marketCap.
129
220
  description:
130
- "Metric to display with its source date and covered-universe rank.",
221
+ "Metric to display with its source date and covered-universe rank: marketCap, or the preset's own metric. Several, as a comma list or the flag again, add a column each after the first.",
222
+ example: "--metric trailingPE --metric dividendYieldPercent",
131
223
  values: NUMERIC_FIELDS.map((value) => ({ value })),
132
- defaultValue: "marketCap",
224
+ normalize: (value) => readMetrics(value).join(","),
225
+ },
226
+ {
227
+ key: "limit",
228
+ type: "integer",
229
+ shot: REPORT_ROWS,
230
+ minimum: 1,
231
+ maximum: SCREEN_ROW_LIMIT,
232
+ defaultValue: SCREEN_PAGE_ROWS,
233
+ description: `Matching rows to list, in the screen's order, up to ${SCREEN_ROW_LIMIT}.`,
234
+ example: "--limit 40",
133
235
  },
134
236
  ],
135
237
  describe: "Equity screener",
136
238
  discovery: {
137
239
  aliases: ["EQS"],
240
+ reference: [
241
+ { title: "Sectors", items: [...SCREEN_SECTORS] },
242
+ { title: "Industries", items: [...SCREEN_INDUSTRIES] },
243
+ ],
138
244
  dataRequirements: ["Gloom Cloud stored screener snapshot"],
139
245
  limitations: [
140
246
  "Covered stored universe, not every listed equity.",
@@ -142,13 +248,18 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
142
248
  "Observed-subset insider activity cannot establish an absence.",
143
249
  "ATR% is the 14-session average true range as a percent of the last close, for US listings with at least 15 completed daily sessions.",
144
250
  "IV rank is a rank, not an IV percentile: 0 is the lowest 30-day implied volatility of the past 52 weeks and 100 the highest.",
251
+ "IV percentile is the share of the past 52 weeks' options closes with IV30 below the latest; IV30 changes are in volatility points (25% to 27% is +2). IV fields with no close in five days are unavailable. HV20 is 20-session realized volatility, annualized, and HV rank places it between its 52-week low and high.",
252
+ "Technical fields (RSI, moving averages, MACD and golden crosses, new highs and lows, Bollinger width) read US listings' completed daily sessions: they describe the last close, not the session in progress. A days-since field counts trading days, 0 at the last session.",
253
+ "Shares outstanding, float, short % of float and institutional ownership cover US filers: the 10-K or 10-Q cover count, the stored statistics float, the FINRA settlement, the 13F quarter. The source does not say when it measured the float, so float and short % of float are partial, dated by when the statistics were retrieved (within 30 days) and never above the dated shares outstanding. A stale or inconsistent input leaves a field unavailable with its reason. Institutional ownership is over the balance-sheet count at the 13F quarter end; over 100% is partial with its reason.",
145
254
  ],
146
255
  },
147
256
  async load(args, ctx) {
148
- if (args.options.saved && args.options.definition) throw new Error("Choose --saved or --definition, not both.");
149
- let definition = args.options.definition
257
+ if ([args.options.preset, args.options.saved, args.options.definition].filter(Boolean).length > 1)
258
+ throw new Error(args.options.preset ? "Choose one of --preset, --saved and --definition." : "Choose --saved or --definition, not both.");
259
+ const preset = screenIdea(args.options.preset == null ? undefined : String(args.options.preset));
260
+ let definition = preset?.definition ?? (args.options.definition
150
261
  ? readDefinition(String(args.options.definition))
151
- : DEFAULT_SCREEN;
262
+ : DEFAULT_SCREEN);
152
263
  let savedName: string | undefined;
153
264
  if (args.options.saved) {
154
265
  const screens = await fetchSavedScreens(ctx.signal, ctx.apiClient);
@@ -189,8 +300,10 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
189
300
  },
190
301
  };
191
302
  }
192
- const data = await fetchScreen(definition, null, ctx.signal, ctx.apiClient);
193
- const metric = (args.options.metric ?? "marketCap") as NumericField;
303
+ const limit = typeof args.options.limit === "number" ? args.options.limit : SCREEN_PAGE_ROWS;
304
+ const data = await fetchScreenRows(definition, limit, ctx);
305
+ const metrics = readMetrics(args.options.metric == null ? preset?.metric ?? "marketCap" : String(args.options.metric));
306
+ const metric = metrics[0]!;
194
307
  // Last, Chg% and market cap are the regular session, as `quote` and the
195
308
  // pane read each row's quote; an extended print is a column of its own.
196
309
  // A sort on one of them is redone on those figures.
@@ -204,21 +317,33 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
204
317
  const kpiGaps = !withheld && missing > 0 && (KPI_FIELDS as readonly string[]).includes(metric)
205
318
  ? [`${missing} of ${rows.length} ${rows.length === 1 ? "row has" : "rows have"} no ${screenLabel(metric)}. ${KPI_COVERAGE_NOTE}`]
206
319
  : [];
320
+ // A category value that names nothing (`Utility` for Utilities) says so with the names it was likely meant as.
321
+ const unmatched = unmatchedCategoryValues(data.definition, data, rows).map(unmatchedCategoryText);
322
+ const unmatchedText = unmatched.length > 0 ? [...unmatched, CATEGORY_NAMES_POINTER].join(" ") : null;
207
323
  // A CAD screen of listings quoted in USD is empty for its currency, not for want of companies: say so, and give the screen in USD.
208
324
  const currencyEmpty = rows.length === 0 ? screenCurrencyEmpty(data.definition, data.universe) : null;
209
- const empty = currencyEmpty
325
+ const empty = rows.length === 0 && unmatchedText
326
+ ? unmatchedText
327
+ : currencyEmpty
210
328
  ? [
211
329
  `${currencyEmpty.title} ${currencyEmpty.detail}`,
212
330
  `In the base currency, ${currencyEmpty.base.currency}: gloom fn EQS --definition '${spacedJson(currencyEmpty.base)}'${metric === "marketCap" ? "" : ` --metric ${metric}`}`,
213
331
  ].join("\n")
214
332
  : null;
333
+ const ytdBase = screenYtdBase(data.rows, data.snapshot?.assembledAt);
334
+ // The metrics after the first, then the field the rows are sorted by, so the order shown is explained by a column.
335
+ const extra = extraScreenColumns(metrics, data.definition.sort.field);
336
+ const states = new Set(rows.map((row) => row.metrics[metric].state));
337
+ const legend = STATE_LEGEND.filter(({ state }) => states.has(state)).map(({ text }) => text);
338
+ const notes = [...kpiGaps, ...legend];
215
339
  // Payload status also counts ordinary per-field sparsity; only generation
216
340
  // warnings are failures, and more pages are not an error.
217
341
  return {
218
342
  freshness: { asOf: data.snapshot?.sourceNewestAt ?? null },
219
343
  complete: !data.nextCursor && errors.length === 0,
220
344
  errors,
221
- ...(kpiGaps.length ? { notes: kpiGaps } : {}),
345
+ ...(notes.length ? { notes } : {}),
346
+ ...(rows.length > 0 && unmatchedText ? { warnings: [unmatchedText] } : {}),
222
347
  sections: [
223
348
  {
224
349
  title: "Matching equities",
@@ -232,11 +357,14 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
232
357
  ...(metric === "changePercent" ? [] : [{ key: "changePercent", header: "Chg%", align: "right" as const, format: percent }]),
233
358
  ...extendedSessions.map(extendedColumn),
234
359
  // YTD names the close it is measured from: `YTD% vs 31 Dec close`.
235
- { key: "value", header: screenColumnLabel(metric, screenYtdBase(data.rows, data.snapshot?.assembledAt), "report"), align: "right" },
360
+ { key: "value", header: screenColumnLabel(metric, ytdBase, "report"), align: "right" },
236
361
  { key: "unit", header: "Unit" },
237
362
  { key: "percentile", header: "Covered pctl", align: "right" },
238
363
  { key: "asOf", header: "As of" },
239
364
  { key: "state", header: "State" },
365
+ ...extra.map((field): HeadlessPaneColumn => isNumericField(field)
366
+ ? { key: field, header: screenColumnLabel(field, ytdBase, "report"), align: "right" }
367
+ : { key: field, header: CATEGORY_HEADERS[field] }),
240
368
  ],
241
369
  rows: rows.map((row) => {
242
370
  const observation = row.metrics[metric];
@@ -260,6 +388,9 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
260
388
  : Math.round(observation.percentile.value),
261
389
  asOf: stamp.collected ? `${stamp.text} (collected)` : stamp.text,
262
390
  state: observation.access === "pro" ? "locked" : observation.state,
391
+ ...Object.fromEntries(extra.map((field) => [field, isNumericField(field)
392
+ ? extraMetricCell(field, row.metrics[field])
393
+ : row[field]])),
263
394
  };
264
395
  }),
265
396
  },
@@ -272,11 +403,11 @@ export const equityScreenerHeadless: HeadlessPaneDefinition<"bundle"> = {
272
403
  covered: data.universe.covered,
273
404
  returned: data.rows.length,
274
405
  hasMore: !!data.nextCursor,
406
+ metrics,
407
+ ...(unmatched.length > 0 ? { unmatchedValues: unmatchedCategoryValues(data.definition, data, rows) } : {}),
275
408
  priceBasis: "price, changePercent and the market cap are the regular session, read from each row's quote as `quote` reads it: after the close, its close and that close's move. A pre-market, after-hours or overnight print is in extendedPrice and extendedChangePercent, measured from that close. Membership and percentiles are the snapshot's, which ranks by the close; a sort on price, changePercent, volume or marketCap orders these rows by the figures shown.",
276
409
  notices: data.nextCursor
277
- ? [
278
- `Top ${data.rows.length} of ${data.universe.matched} matches; narrow the criteria to see the rest.`,
279
- ]
410
+ ? [moreMatchesNotice(data.rows.length, data.universe.matched)]
280
411
  : [],
281
412
  },
282
413
  };
@@ -0,0 +1,49 @@
1
+ import type { NumericField, ScreenCriterion, ScreenDefinition } from "../../../api-client/equity-screener";
2
+
3
+ /** A ready screen in the Ideas tab and `--preset`: its definition and the metric it leads with. */
4
+ export interface ScreenIdea {
5
+ id: string;
6
+ name: string;
7
+ metric: NumericField;
8
+ definition: ScreenDefinition;
9
+ }
10
+
11
+ /** Companies above 2B, so an idea lists names that trade, not every small listing that moved. */
12
+ const TRADED: ScreenCriterion = { field: "marketCap", op: "gte", value: 2_000_000_000 };
13
+
14
+ const idea = (
15
+ id: string,
16
+ name: string,
17
+ criteria: ScreenCriterion[],
18
+ sort: ScreenDefinition["sort"] & { field: NumericField },
19
+ ): ScreenIdea => ({
20
+ id,
21
+ name,
22
+ metric: sort.field,
23
+ definition: { version: 1, currency: "USD", criteria: [...criteria, TRADED], sort },
24
+ });
25
+
26
+ /** Technical ideas, read at the last completed session. */
27
+ export const SCREEN_IDEAS: ScreenIdea[] = [
28
+ idea("oversold-uptrend", "Oversold in an uptrend", [
29
+ { field: "rsi14", op: "lte", value: 30 },
30
+ { field: "priceVsSma200Percent", op: "gt", value: 0 },
31
+ ], { field: "rsi14", direction: "asc" }),
32
+ idea("golden-cross", "Fresh golden cross", [
33
+ { field: "goldenCrossDays", op: "lte", value: 10 },
34
+ ], { field: "goldenCrossDays", direction: "asc" }),
35
+ // Every match is at its high, so the largest companies lead.
36
+ idea("52-week-high", "52-week high breakout", [
37
+ { field: "newHigh52wDays", op: "eq", value: 0 },
38
+ ], { field: "marketCap", direction: "desc" }),
39
+ idea("bollinger-squeeze", "Bollinger squeeze", [
40
+ { field: "bollingerWidthPercentile", op: "lte", value: 5 },
41
+ ], { field: "bollingerWidthPercentile", direction: "asc" }),
42
+ idea("macd-turn", "MACD turns up in an uptrend", [
43
+ { field: "macdBullishCrossDays", op: "lte", value: 3 },
44
+ { field: "priceVsSma200Percent", op: "gt", value: 0 },
45
+ ], { field: "macdBullishCrossDays", direction: "asc" }),
46
+ ];
47
+
48
+ export const screenIdea = (id: string | undefined): ScreenIdea | undefined =>
49
+ SCREEN_IDEAS.find((entry) => entry.id === id);
@@ -4,9 +4,12 @@ import {
4
4
  HIRING_APP_FIELDS,
5
5
  CATEGORY_FIELDS,
6
6
  NUMERIC_FIELDS,
7
+ OPTION_VOLATILITY_FIELDS,
8
+ OWNERSHIP_FIELDS,
7
9
  RESEARCH_FIELDS,
8
10
  KPI_FIELDS,
9
11
  SOCIAL_FIELDS,
12
+ TECHNICAL_FIELDS,
10
13
  VOLATILITY_FIELDS,
11
14
  type NumericField,
12
15
  type ScreenMetric,
@@ -22,6 +25,7 @@ import { canonicalExchange } from "../../../utils/exchanges";
22
25
  import { LATEST_DISTRIBUTION, TRAILING_DIVIDEND_BASIS } from "../../../utils/forward-dividend";
23
26
  import { formatCompact, formatNumber } from "../../../utils/format";
24
27
  import { formatPriceEarnings, NOT_MEANINGFUL } from "../../../utils/price-earnings";
28
+ import { ATTENTION_LISTED } from "../attention/listing";
25
29
  import { kpiFreeLimitNote } from "../company-kpis/model";
26
30
 
27
31
  /**
@@ -90,6 +94,20 @@ export const SHORT_LABELS: Record<NumericField, string> = {
90
94
  fromHigh52WPercent: "VS 52W HI%",
91
95
  beta: "BETA",
92
96
  atrPercent: "ATR%",
97
+ rsi14: "RSI 14",
98
+ priceVsSma20Percent: "VS SMA20%",
99
+ priceVsSma50Percent: "VS SMA50%",
100
+ priceVsSma200Percent: "VS SMA200%",
101
+ // Sessions since the event: 0 is the last completed session.
102
+ macdBullishCrossDays: "MACD BULL AGO",
103
+ macdBearishCrossDays: "MACD BEAR AGO",
104
+ goldenCrossDays: "GOLDEN X AGO",
105
+ deathCrossDays: "DEATH X AGO",
106
+ newHigh20dDays: "20D HIGH AGO",
107
+ newLow20dDays: "20D LOW AGO",
108
+ newHigh52wDays: "52W HIGH AGO",
109
+ newLow52wDays: "52W LOW AGO",
110
+ bollingerWidthPercentile: "BB WIDTH PCTL",
93
111
  priceToBook: "P/B",
94
112
  evToEbitda: "EV/EBITDA",
95
113
  roePercent: "FY ROE%",
@@ -97,6 +115,17 @@ export const SHORT_LABELS: Record<NumericField, string> = {
97
115
  analystUpsidePercent: "UPSIDE%",
98
116
  ivRank: "IV RANK",
99
117
  ivToHv: "IV/HV",
118
+ iv30Percent: "IV30%",
119
+ ivPercentile: "IV PCTL",
120
+ // Volatility points: IV30 25% to 27% is +2.
121
+ iv30Change1d: "IV30 CHG 1D",
122
+ iv30Change5d: "IV30 CHG 5D",
123
+ hv20Percent: "HV20%",
124
+ hvRank: "HV RANK",
125
+ sharesOutstanding: "SHARES OUT",
126
+ floatShares: "FLOAT",
127
+ shortPercentOfFloat: "SI % FLOAT",
128
+ institutionalOwnershipPercent: "INST OWN%",
100
129
  hiringOpenRoles: "OPEN ROLES",
101
130
  hiringNetAddsWeek: "NET ROLES/WK",
102
131
  hiringRemotePercent: "REMOTE%",
@@ -106,7 +135,7 @@ export const SHORT_LABELS: Record<NumericField, string> = {
106
135
  appRatingDrift7d: "RATING 7D",
107
136
  appRatingCountGrowth7d: "RATINGS 7D",
108
137
  };
109
- const COMPACT = new Set<NumericField>(["kpiArr", "kpiRpo", "kpiCrpo", "kpiSubscribers", "kpiDau", "kpiMau", "kpiGmv", "kpiStores", "kpiBacklog", "marketCap", "volume", "averageVolume20d", "shortInterestShares"]);
138
+ const COMPACT = new Set<NumericField>(["kpiArr", "kpiRpo", "kpiCrpo", "kpiSubscribers", "kpiDau", "kpiMau", "kpiGmv", "kpiStores", "kpiBacklog", "marketCap", "volume", "averageVolume20d", "shortInterestShares", "sharesOutstanding", "floatShares"]);
110
139
  /** Daily counts: whole numbers below a thousand, compact above. */
111
140
  const DAILY_COUNTS = new Set<NumericField>(["xPostsPerDay", "wikiViewsPerDay"]);
112
141
  const RATIOS = new Set<NumericField>(["kpiBookToBill", "xPostsVsMedian", "wikiViewsVsMedian", "priceToBook", "evToEbitda", "ivToHv"]);
@@ -116,10 +145,15 @@ const SIGNED = new Set<NumericField>(["hiringNetAddsWeek", "hiringZScore", "appR
116
145
  "changePercent", "revenueGrowthPercent", "earningsGrowthPercent", "shortInterestChangePercent",
117
146
  "dividendGrowth1YPercent", "dividendGrowth3YPercent",
118
147
  ...RETURNS, "fromHigh52WPercent", "epsRevision30dPercent", "analystUpsidePercent",
148
+ "priceVsSma20Percent", "priceVsSma50Percent", "priceVsSma200Percent",
149
+ "iv30Change1d", "iv30Change5d",
119
150
  ]);
120
151
  /** Moves whose sign is the news: they take the sign colour. */
121
- export const SIGN_COLORED = new Set<NumericField>(["changePercent", ...RETURNS, "epsRevision30dPercent", "analystUpsidePercent"]);
122
- const COUNTS = new Set<NumericField>(["appRatingCountGrowth7d", "hiringNetAddsWeek", "hiringOpenRoles", "attentionResearchUnits", "insiderPurchases90d", "insiderSales90d", "institutionalHolders", "institutionalNewHolders", "institutionalExits", "ivRank"]);
152
+ export const SIGN_COLORED = new Set<NumericField>(["changePercent", ...RETURNS, "epsRevision30dPercent", "analystUpsidePercent",
153
+ "priceVsSma20Percent", "priceVsSma50Percent", "priceVsSma200Percent"]);
154
+ const COUNTS = new Set<NumericField>(["appRatingCountGrowth7d", "hiringNetAddsWeek", "hiringOpenRoles", "attentionResearchUnits", "insiderPurchases90d", "insiderSales90d", "institutionalHolders", "institutionalNewHolders", "institutionalExits", "ivRank", "ivPercentile", "hvRank",
155
+ "macdBullishCrossDays", "macdBearishCrossDays", "goldenCrossDays", "deathCrossDays",
156
+ "newHigh20dDays", "newLow20dDays", "newHigh52wDays", "newLow52wDays", "bollingerWidthPercentile"]);
123
157
  const MONETARY = new Set<NumericField>(["price", "marketCap", "kpiArr", "kpiRpo", "kpiCrpo", "kpiGmv", "kpiBacklog"]);
124
158
  /** Shown after the screen's own criteria when the pane has room. */
125
159
  const CONTEXT_FIELDS: NumericField[] = ["marketCap", "price", "changePercent", "trailingPE", "revenueGrowthPercent", "operatingMarginPercent", "dividendYieldPercent"];
@@ -161,13 +195,30 @@ export function metricDate(metric: ScreenMetric): { text: string; collected: boo
161
195
  return { text: "--", collected: false };
162
196
  }
163
197
 
164
- /** Focus metric, then the screen's numeric criteria, then context columns. Money needs one currency. */
165
- export function resultFields(definition: ScreenDefinition, metric: NumericField): NumericField[] {
166
- const numeric = (field: string): field is NumericField => (NUMERIC_FIELDS as readonly string[]).includes(field);
167
- const fields = [metric, ...definition.criteria.map((criterion) => criterion.field).filter(numeric), ...CONTEXT_FIELDS];
198
+ const isNumeric = (field: string): field is NumericField => (NUMERIC_FIELDS as readonly string[]).includes(field);
199
+
200
+ /**
201
+ * Focus metric, the other metrics asked for, the field the rows are sorted
202
+ * by (so their order is explained by a column), then the screen's numeric
203
+ * criteria and the context columns. Money needs one currency.
204
+ */
205
+ export function resultFields(definition: ScreenDefinition, metric: NumericField, others: readonly NumericField[] = []): NumericField[] {
206
+ const sort = isNumeric(definition.sort.field) ? [definition.sort.field] : [];
207
+ const fields = [metric, ...others, ...sort, ...definition.criteria.map((criterion) => criterion.field).filter(isNumeric), ...CONTEXT_FIELDS];
168
208
  return [...new Set(fields)].filter((field) => definition.currency || !MONETARY.has(field) || field === metric);
169
209
  }
170
210
 
211
+ /**
212
+ * The metrics a pane's `metric` setting names, the focus first: one field, or
213
+ * the comma list `--metric` leaves (`trailingPE,dividendYieldPercent`).
214
+ * Unknown names are skipped; none known leaves `fallback`.
215
+ */
216
+ export function screenMetricSetting(setting: unknown, fallback: NumericField): [NumericField, ...NumericField[]] {
217
+ const fields = typeof setting === "string" ? setting.split(",").map((part) => part.trim()).filter(isNumeric) : [];
218
+ const unique = [...new Set(fields)];
219
+ return unique.length > 0 ? unique as [NumericField, ...NumericField[]] : [fallback];
220
+ }
221
+
171
222
  /**
172
223
  * The close the screen's YTD returns are measured from, `31 Dec`: NYSE's last
173
224
  * session of the year before the newest session a row's YTD ends on, the
@@ -288,6 +339,19 @@ export const screenLabel = (field: string) =>
288
339
  fromHigh52WPercent: "From 52-week high %",
289
340
  beta: "Beta",
290
341
  atrPercent: "ATR%",
342
+ rsi14: "RSI 14",
343
+ priceVsSma20Percent: "Price vs SMA 20 %",
344
+ priceVsSma50Percent: "Price vs SMA 50 %",
345
+ priceVsSma200Percent: "Price vs SMA 200 %",
346
+ macdBullishCrossDays: "Days since MACD bullish cross",
347
+ macdBearishCrossDays: "Days since MACD bearish cross",
348
+ goldenCrossDays: "Days since golden cross",
349
+ deathCrossDays: "Days since death cross",
350
+ newHigh20dDays: "Days since new 20-day high",
351
+ newLow20dDays: "Days since new 20-day low",
352
+ newHigh52wDays: "Days since new 52-week high",
353
+ newLow52wDays: "Days since new 52-week low",
354
+ bollingerWidthPercentile: "Bollinger width percentile",
291
355
  priceToBook: "P/B",
292
356
  evToEbitda: "EV / EBITDA",
293
357
  roePercent: "Annual ROE %",
@@ -295,6 +359,16 @@ export const screenLabel = (field: string) =>
295
359
  analystUpsidePercent: "Analyst upside %",
296
360
  ivRank: "IV rank",
297
361
  ivToHv: "IV / HV",
362
+ iv30Percent: "IV30 %",
363
+ ivPercentile: "IV percentile",
364
+ iv30Change1d: "IV30 change 1D, vol pts",
365
+ iv30Change5d: "IV30 change 5D, vol pts",
366
+ hv20Percent: "HV20 %",
367
+ hvRank: "HV rank",
368
+ sharesOutstanding: "Shares outstanding",
369
+ floatShares: "Float",
370
+ shortPercentOfFloat: "Short interest % of float",
371
+ institutionalOwnershipPercent: "Institutional ownership % (13F)",
298
372
  hiringOpenRoles: "Hiring open roles",
299
373
  hiringNetAddsWeek: "Net open roles per week",
300
374
  hiringRemotePercent: "Remote roles %",
@@ -538,6 +612,21 @@ const unavailableMetric = (): ScreenMetric => ({
538
612
  state: "unavailable", scope: "reported", reason: "Not provided by this server", sourceUrl: null,
539
613
  percentile: { value: null, sampleCount: 0, scope: "covered-universe" },
540
614
  });
615
+ /**
616
+ * The fields the field and metric pickers offer. ATTN's two (ATTN HRS,
617
+ * ATTN Z) stay out until ATTN is listed again (attention/listing.ts), but for
618
+ * one the screen already uses, so a saved screen still shows it.
619
+ */
620
+ export function pickerFields<Field extends { id: string }>(
621
+ fields: readonly Field[],
622
+ inUse: readonly string[] = [],
623
+ listed: boolean = ATTENTION_LISTED,
624
+ ): Field[] {
625
+ if (listed) return [...fields];
626
+ const unlisted = new Set<string>(ATTENTION_FIELDS);
627
+ return fields.filter((field) => !unlisted.has(field.id) || inUse.includes(field.id));
628
+ }
629
+
541
630
  /** Company KPIs, guidance, hiring and app attention: the server leaves them out of a screen for a caller without Gloom Pro. */
542
631
  const PRO_FIELDS: ReadonlySet<string> = new Set([...KPI_FIELDS, ...HIRING_APP_FIELDS]);
543
632
  const withheldMetric = (): ScreenMetric => ({ ...unavailableMetric(), reason: "Needs Gloom Pro", access: "pro" });
@@ -593,9 +682,9 @@ export function validateScreenPayload(value: unknown): ScreenPayload {
593
682
  throw new Error("Invalid screener listing identity.");
594
683
  ids.add(screenRowId(row));
595
684
  for (const field of NUMERIC_FIELDS) {
596
- // A server from before social, research, volatility or dividend fields omits them; they read as unavailable.
597
- // A Pro field it left out was held back from this caller.
598
- if (!row.metrics[field] && ([...SOCIAL_FIELDS, ...RESEARCH_FIELDS, ...VOLATILITY_FIELDS, ...ATTENTION_FIELDS, ...HIRING_APP_FIELDS, ...KPI_FIELDS, ...DIVIDEND_FIELDS] as readonly string[]).includes(field))
685
+ // A server from before social, research, volatility, dividend, technical, option volatility or ownership fields
686
+ // omits them; they read as unavailable. A Pro field it left out was held back from this caller.
687
+ if (!row.metrics[field] && ([...SOCIAL_FIELDS, ...RESEARCH_FIELDS, ...VOLATILITY_FIELDS, ...TECHNICAL_FIELDS, ...OPTION_VOLATILITY_FIELDS, ...OWNERSHIP_FIELDS, ...ATTENTION_FIELDS, ...HIRING_APP_FIELDS, ...KPI_FIELDS, ...DIVIDEND_FIELDS] as readonly string[]).includes(field))
599
688
  row.metrics[field] = PRO_FIELDS.has(field) ? withheldMetric() : unavailableMetric();
600
689
  const metric = row.metrics[field];
601
690
  if (