gloomberb 0.18.4 → 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 (600) 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 +169 -5
  5. package/src/account/auth-model.ts +7 -3
  6. package/src/api-client/auth.ts +9 -4
  7. package/src/api-client/catalysts.ts +2 -1
  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 +31 -6
  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/index.ts +171 -11
  14. package/src/api-client/live-scan.ts +91 -0
  15. package/src/api-client/market-discovery.ts +10 -0
  16. package/src/api-client/market-movers.ts +7 -0
  17. package/src/api-client/members.ts +13 -1
  18. package/src/api-client/perps.ts +43 -0
  19. package/src/api-client/request.ts +222 -41
  20. package/src/api-client/research-activity.ts +6 -12
  21. package/src/api-client/types.ts +74 -0
  22. package/src/app/keybindings/index.ts +3 -0
  23. package/src/app/keybindings/labels.ts +18 -1
  24. package/src/app/keybindings/resolve.ts +14 -0
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +42 -27
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +1 -1
  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/fred-series.ts +5 -0
  35. package/src/cli/commands/keybindings.ts +23 -0
  36. package/src/cli/commands/market.ts +138 -46
  37. package/src/cli/commands/options-chain.ts +107 -10
  38. package/src/cli/commands/options-leaps.ts +9 -3
  39. package/src/cli/commands/overview.ts +176 -39
  40. package/src/cli/commands/search.ts +39 -3
  41. package/src/cli/commands/themes.ts +11 -2
  42. package/src/cli/commands/ticker.ts +57 -35
  43. package/src/cli/company-name-hint.ts +41 -0
  44. package/src/cli/earnings-calendar.ts +1 -1
  45. package/src/cli/errors.ts +10 -2
  46. package/src/cli/export-source.ts +29 -1
  47. package/src/cli/glossary.ts +73 -1
  48. package/src/cli/history-rows.ts +87 -16
  49. package/src/cli/index.ts +2 -0
  50. package/src/cli/known-gaps.ts +5 -2
  51. package/src/cli/listing-arg.ts +195 -56
  52. package/src/cli/output-file.ts +68 -0
  53. package/src/cli/pane-functions/capabilities.ts +3 -0
  54. package/src/cli/pane-functions/catalog.ts +80 -7
  55. package/src/cli/pane-functions/dom.ts +13 -3
  56. package/src/cli/pane-functions/freshness.ts +111 -16
  57. package/src/cli/pane-functions/function-help.ts +23 -13
  58. package/src/cli/pane-functions/headless.ts +56 -12
  59. package/src/cli/pane-functions/index.ts +58 -16
  60. package/src/cli/pane-functions/market-session.ts +81 -9
  61. package/src/cli/pane-functions/options.ts +19 -1
  62. package/src/cli/pane-functions/report.ts +5 -1
  63. package/src/cli/pane-functions/resolver.ts +35 -8
  64. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  65. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  66. package/src/cli/quote-answer.ts +30 -13
  67. package/src/cli/report-tables.ts +18 -7
  68. package/src/cli/restore-plugins.ts +13 -1
  69. package/src/cli/result.ts +57 -5
  70. package/src/cli/ticker-resolution.ts +3 -2
  71. package/src/components/chart/composite/composite-chart.tsx +26 -4
  72. package/src/components/chart/composite/format.ts +27 -0
  73. package/src/components/chart/composite/interactions.ts +6 -0
  74. package/src/components/chart/composite/legend.tsx +45 -7
  75. package/src/components/chart/composite/levels.ts +2 -0
  76. package/src/components/chart/composite/panel-gestures.ts +33 -0
  77. package/src/components/chart/composite/panel-series.ts +1 -0
  78. package/src/components/chart/composite/panel-surface.tsx +61 -5
  79. package/src/components/chart/composite/range-statistics.ts +62 -0
  80. package/src/components/chart/composite/rasterizer.ts +24 -13
  81. package/src/components/chart/composite/scene.ts +46 -7
  82. package/src/components/chart/composite/series-axis-tags.tsx +16 -9
  83. package/src/components/chart/composite/series-cues.ts +14 -0
  84. package/src/components/chart/composite/text-renderer.ts +9 -2
  85. package/src/components/chart/composite/tool-catalog.ts +36 -0
  86. package/src/components/chart/composite/tools.ts +203 -44
  87. package/src/components/chart/composite/types.ts +4 -0
  88. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  89. package/src/components/command-bar/assist/inventory.ts +2 -1
  90. package/src/components/command-bar/commands/direct/index.ts +28 -13
  91. package/src/components/command-bar/pane-templates/items.ts +3 -2
  92. package/src/components/command-bar/panel/prompt-binding.ts +0 -8
  93. package/src/components/command-bar/workflow/alert.ts +1 -1
  94. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  95. package/src/components/layout/header.tsx +26 -40
  96. package/src/components/layout/pane/footer/index.tsx +1 -0
  97. package/src/components/layout/pane/footer/model.ts +27 -0
  98. package/src/components/onboarding/onboarding-frame.tsx +1 -1
  99. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  100. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
  101. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  102. package/src/components/onboarding/wizard-positions.ts +3 -3
  103. package/src/components/ticker/list-table-view.tsx +4 -1
  104. package/src/components/ui/choice-dialog.tsx +23 -18
  105. package/src/components/ui/choice-groups.ts +18 -0
  106. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  107. package/src/components/ui/data-table/opentui/model.ts +26 -1
  108. package/src/components/ui/data-table/types.ts +2 -0
  109. package/src/components/ui/display.tsx +5 -1
  110. package/src/components/ui/list-view.tsx +8 -4
  111. package/src/components/ui/query-bar-keys.tsx +2 -0
  112. package/src/components/ui/query-bar.tsx +1 -0
  113. package/src/components/ui/select-button.tsx +5 -1
  114. package/src/components/ui/select-field.tsx +1 -0
  115. package/src/components/ui/table-layout.ts +8 -5
  116. package/src/core/app-runtime.ts +2 -0
  117. package/src/core/app-services.ts +2 -2
  118. package/src/core/state/app/layout.ts +2 -0
  119. package/src/core/state/app/state.ts +9 -0
  120. package/src/core/state/app/types.ts +3 -0
  121. package/src/data/config/chart-settings.ts +1 -1
  122. package/src/data/json-ticker-repository.ts +17 -8
  123. package/src/data/saved-copies.ts +55 -16
  124. package/src/data/ticker-repository.ts +16 -9
  125. package/src/data/ticker-writes.ts +81 -0
  126. package/src/data/ttl-cache.ts +165 -0
  127. package/src/docs/snapshot.json +427 -216
  128. package/src/i18n/es.ts +19 -3
  129. package/src/i18n/ja.ts +19 -3
  130. package/src/i18n/ko.ts +19 -3
  131. package/src/i18n/zh-cn.ts +19 -3
  132. package/src/i18n/zh-tw.ts +19 -3
  133. package/src/market-data/coordinator/financials.ts +10 -2
  134. package/src/market-data/coordinator/index.ts +3 -1
  135. package/src/market-data/coordinator/quotes.ts +4 -2
  136. package/src/market-data/depositary-pair.ts +2 -2
  137. package/src/market-data/fund-facts.ts +50 -0
  138. package/src/market-data/market/change-basis.ts +48 -7
  139. package/src/market-data/market/dated-previous-close.ts +203 -0
  140. package/src/market-data/market/format.ts +25 -0
  141. package/src/market-data/market/freshness.ts +2 -1
  142. package/src/market-data/market/futures-week.ts +68 -0
  143. package/src/market-data/market/status.ts +16 -4
  144. package/src/market-data/options-alternatives.ts +141 -4
  145. package/src/market-data/quotes/contributions.ts +20 -2
  146. package/src/market-data/quotes/resolution.ts +3 -0
  147. package/src/market-data/selectors.ts +3 -0
  148. package/src/news/aggregator.ts +113 -9
  149. package/src/news/cloud-session.ts +21 -0
  150. package/src/news/hooks.ts +1 -0
  151. package/src/news/news-model.ts +1 -0
  152. package/src/news/types.ts +11 -0
  153. package/src/notifications/notification-log.ts +6 -4
  154. package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
  155. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  156. package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
  157. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  158. package/src/plugins/builtin/alerts/cli.ts +380 -82
  159. package/src/plugins/builtin/alerts/command.ts +31 -0
  160. package/src/plugins/builtin/alerts/constants.ts +1 -1
  161. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  162. package/src/plugins/builtin/alerts/fired-log.ts +113 -8
  163. package/src/plugins/builtin/alerts/format.ts +80 -4
  164. package/src/plugins/builtin/alerts/headless.ts +30 -7
  165. package/src/plugins/builtin/alerts/history-rows.ts +7 -3
  166. package/src/plugins/builtin/alerts/history.ts +2 -0
  167. package/src/plugins/builtin/alerts/index.tsx +135 -22
  168. package/src/plugins/builtin/alerts/live.ts +3 -3
  169. package/src/plugins/builtin/alerts/metric-command.ts +134 -0
  170. package/src/plugins/builtin/alerts/metric-data.ts +111 -0
  171. package/src/plugins/builtin/alerts/metric-model.ts +324 -0
  172. package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
  173. package/src/plugins/builtin/alerts/pane.tsx +90 -39
  174. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  175. package/src/plugins/builtin/alerts/storage.ts +37 -10
  176. package/src/plugins/builtin/alerts/types.ts +7 -0
  177. package/src/plugins/builtin/analytics/index.tsx +2 -1
  178. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  179. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  180. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  181. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  182. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  183. package/src/plugins/builtin/attention/index.tsx +2 -1
  184. package/src/plugins/builtin/attention/listing.ts +14 -0
  185. package/src/plugins/builtin/backtest/headless.ts +10 -5
  186. package/src/plugins/builtin/backtest/pane.tsx +9 -4
  187. package/src/plugins/builtin/backtest/presets.ts +29 -3
  188. package/src/plugins/builtin/broker-manager/index.tsx +10 -5
  189. package/src/plugins/builtin/broker-manager/model.ts +6 -1
  190. package/src/plugins/builtin/broker-trading/controller.ts +22 -12
  191. package/src/plugins/builtin/broker-trading/index.tsx +13 -1
  192. package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
  193. package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
  194. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  195. package/src/plugins/builtin/buildout/format.ts +38 -0
  196. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  197. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  198. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  199. package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
  200. package/src/plugins/builtin/catalysts/model.ts +2 -1
  201. package/src/plugins/builtin/changelog/index.tsx +9 -6
  202. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
  203. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
  204. package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
  205. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
  206. package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
  207. package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
  208. package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
  209. package/src/plugins/builtin/chart-composer/formula.ts +71 -18
  210. package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
  211. package/src/plugins/builtin/chart-composer/headless.ts +19 -3
  212. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  213. package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
  214. package/src/plugins/builtin/chart-composer/presets.ts +50 -15
  215. package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
  216. package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
  217. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  218. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
  219. package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
  220. package/src/plugins/builtin/chat/content/composer.tsx +13 -6
  221. package/src/plugins/builtin/chat/content/footer.tsx +6 -3
  222. package/src/plugins/builtin/chat/content/index.tsx +50 -21
  223. package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
  224. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
  225. package/src/plugins/builtin/chat/index.tsx +23 -0
  226. package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
  227. package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
  228. package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
  229. package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
  230. package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
  231. package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
  232. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
  233. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  234. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  235. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  236. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  237. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  238. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  239. package/src/plugins/builtin/company-attention/client.ts +8 -9
  240. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  241. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  242. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  243. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  244. package/src/plugins/builtin/correlation/headless.ts +13 -5
  245. package/src/plugins/builtin/correlation/history.ts +22 -0
  246. package/src/plugins/builtin/cot/client.ts +22 -18
  247. package/src/plugins/builtin/cot/headless.ts +15 -10
  248. package/src/plugins/builtin/cot/model.ts +14 -1
  249. package/src/plugins/builtin/cot/pane.tsx +22 -14
  250. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  251. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  252. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  253. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  254. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  255. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  256. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  257. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  258. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  259. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  260. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  261. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  262. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  263. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  265. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  266. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  267. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  268. package/src/plugins/builtin/docs/targets.ts +5 -1
  269. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  270. package/src/plugins/builtin/earnings/board.tsx +62 -71
  271. package/src/plugins/builtin/earnings/headless.ts +40 -4
  272. package/src/plugins/builtin/earnings/scope.ts +31 -43
  273. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  274. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  275. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  276. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  277. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  278. package/src/plugins/builtin/econ/calendar-model.ts +14 -9
  279. package/src/plugins/builtin/econ/figures.ts +72 -0
  280. package/src/plugins/builtin/econ/index.tsx +33 -3
  281. package/src/plugins/builtin/econ/types.ts +4 -0
  282. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  283. package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
  284. package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
  285. package/src/plugins/builtin/econ-statistics/view.ts +3 -0
  286. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  287. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  288. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  289. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  290. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  291. package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
  292. package/src/plugins/builtin/exposure/charts.tsx +2 -2
  293. package/src/plugins/builtin/exposure/model.ts +13 -1
  294. package/src/plugins/builtin/exposure/pane.tsx +4 -4
  295. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  296. package/src/plugins/builtin/futures/index.tsx +1 -0
  297. package/src/plugins/builtin/futures/model.ts +2 -1
  298. package/src/plugins/builtin/futures/table.tsx +2 -2
  299. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  300. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  301. package/src/plugins/builtin/fx-matrix/client.ts +11 -3
  302. package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
  303. package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
  304. package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
  305. package/src/plugins/builtin/gpu/headless.ts +8 -2
  306. package/src/plugins/builtin/help/function-index.ts +4 -1
  307. package/src/plugins/builtin/help/index.tsx +1 -0
  308. package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
  309. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  310. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  311. package/src/plugins/builtin/holders/headless.ts +18 -1
  312. package/src/plugins/builtin/iv-history/client.ts +52 -11
  313. package/src/plugins/builtin/iv-history/format.ts +36 -23
  314. package/src/plugins/builtin/iv-history/headless.ts +36 -18
  315. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  316. package/src/plugins/builtin/iv-history/model.ts +163 -27
  317. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  318. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  319. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  320. package/src/plugins/builtin/jobs/client.ts +25 -29
  321. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  322. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  323. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  324. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  325. package/src/plugins/builtin/macro-day/pane.tsx +7 -5
  326. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  327. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  328. package/src/plugins/builtin/market-movers/client.ts +25 -7
  329. package/src/plugins/builtin/market-movers/footer.ts +2 -2
  330. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  331. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  332. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  333. package/src/plugins/builtin/market-movers/model.ts +4 -1
  334. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  335. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  336. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  337. package/src/plugins/builtin/market-movers/table.ts +17 -5
  338. package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
  339. package/src/plugins/builtin/members/client.ts +19 -4
  340. package/src/plugins/builtin/members/headless.ts +20 -7
  341. package/src/plugins/builtin/members/index.tsx +9 -9
  342. package/src/plugins/builtin/members/model.ts +23 -4
  343. package/src/plugins/builtin/members/pane.tsx +25 -15
  344. package/src/plugins/builtin/news/index.tsx +1 -0
  345. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  346. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  347. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  348. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  349. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  350. package/src/plugins/builtin/openfda/client.ts +14 -2
  351. package/src/plugins/builtin/openfda/index.tsx +3 -2
  352. package/src/plugins/builtin/openfda/model.ts +9 -0
  353. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  354. package/src/plugins/builtin/options/analytics.ts +54 -6
  355. package/src/plugins/builtin/options/enrichment.ts +17 -5
  356. package/src/plugins/builtin/options/model-spot.ts +5 -2
  357. package/src/plugins/builtin/options/strike-window.ts +2 -1
  358. package/src/plugins/builtin/options/table.ts +6 -9
  359. package/src/plugins/builtin/options/types.ts +2 -0
  360. package/src/plugins/builtin/options/view.tsx +42 -12
  361. package/src/plugins/builtin/options-positioning/client.ts +30 -0
  362. package/src/plugins/builtin/options-positioning/headless.ts +74 -21
  363. package/src/plugins/builtin/options-positioning/index.tsx +4 -4
  364. package/src/plugins/builtin/options-positioning/model.ts +122 -14
  365. package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
  366. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  367. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  368. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  369. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  370. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  371. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  372. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  373. package/src/plugins/builtin/pe-band/model.ts +12 -49
  374. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  375. package/src/plugins/builtin/perps/cascade.ts +236 -0
  376. package/src/plugins/builtin/perps/client.ts +56 -2
  377. package/src/plugins/builtin/perps/freshness.ts +12 -10
  378. package/src/plugins/builtin/perps/headless.ts +65 -9
  379. package/src/plugins/builtin/perps/index.tsx +7 -5
  380. package/src/plugins/builtin/perps/live-rows.ts +10 -3
  381. package/src/plugins/builtin/perps/model.ts +18 -4
  382. package/src/plugins/builtin/perps/pane.tsx +32 -31
  383. package/src/plugins/builtin/perps/status-line.ts +25 -0
  384. package/src/plugins/builtin/plugin-module.ts +5 -1
  385. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  386. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  387. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  388. package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
  389. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  390. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  391. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  392. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  393. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  394. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  395. package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
  396. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  397. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  398. package/src/plugins/builtin/rate-path/pane.tsx +2 -2
  399. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  400. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  401. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  402. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  403. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  404. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  405. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  406. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  407. package/src/plugins/builtin/research/event-model.ts +10 -8
  408. package/src/plugins/builtin/research/headless.ts +1 -1
  409. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  410. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  411. package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
  412. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  413. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  414. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  415. package/src/plugins/builtin/scanner/feed.ts +2 -2
  416. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  417. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  418. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  419. package/src/plugins/builtin/scanner/headless.ts +204 -35
  420. package/src/plugins/builtin/scanner/index.ts +40 -6
  421. package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
  422. package/src/plugins/builtin/scanner/live-feed.ts +168 -0
  423. package/src/plugins/builtin/scanner/live-headless.ts +138 -0
  424. package/src/plugins/builtin/scanner/live-model.ts +362 -0
  425. package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
  426. package/src/plugins/builtin/scanner/live-track.ts +186 -0
  427. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  428. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  429. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  430. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  431. package/src/plugins/builtin/sec/headless.ts +2 -1
  432. package/src/plugins/builtin/series-inventory.ts +3 -0
  433. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  434. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  435. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  436. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  437. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  438. package/src/plugins/builtin/shared/macro-releases.ts +65 -0
  439. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  440. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  441. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  442. package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
  443. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  444. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  445. package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
  446. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  447. package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
  448. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  449. package/src/plugins/builtin/themes/headless.ts +6 -4
  450. package/src/plugins/builtin/themes/model.ts +17 -8
  451. package/src/plugins/builtin/themes/pane.tsx +7 -7
  452. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  453. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  456. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  457. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  458. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  459. package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
  460. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  461. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  462. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  463. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  464. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  465. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  466. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  467. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  468. package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
  469. package/src/plugins/builtin/vol-surface/client.ts +9 -17
  470. package/src/plugins/builtin/vol-surface/headless.ts +63 -14
  471. package/src/plugins/builtin/vol-surface/index.tsx +2 -2
  472. package/src/plugins/builtin/vol-surface/model.ts +22 -7
  473. package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
  474. package/src/plugins/builtin/vol-surface/tables.ts +31 -3
  475. package/src/plugins/builtin/volatility/dvol.ts +46 -0
  476. package/src/plugins/builtin/volatility/index.tsx +2 -2
  477. package/src/plugins/builtin/volatility/pane.tsx +4 -4
  478. package/src/plugins/builtin/volatility/series.ts +26 -0
  479. package/src/plugins/builtin/world-indices/footer.ts +1 -1
  480. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  481. package/src/plugins/catalog-browser.ts +13 -8
  482. package/src/plugins/catalog-manifest.ts +2 -1
  483. package/src/plugins/marketplace/model.ts +18 -5
  484. package/src/portfolio/column-values.ts +5 -5
  485. package/src/portfolio/futures-positions.ts +6 -3
  486. package/src/portfolio/position-quantity.ts +15 -9
  487. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  488. package/src/remote/resources.ts +2 -1
  489. package/src/renderers/browser/app-services.ts +1 -7
  490. package/src/renderers/browser/config-host.ts +2 -55
  491. package/src/renderers/browser/main.tsx +7 -57
  492. package/src/renderers/dom/data-table/row.tsx +26 -0
  493. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  494. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  495. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  496. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  497. package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
  498. package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
  499. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  500. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  501. package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
  502. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
  503. package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
  504. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  505. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
  506. package/src/renderers/electrobun/bun/index.ts +25 -1
  507. package/src/renderers/electrobun/shared/app-name.ts +30 -0
  508. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  509. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  510. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  511. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
  512. package/src/renderers/electrobun/view/main.tsx +24 -1
  513. package/src/renderers/electrobun/view/news-source.ts +3 -1
  514. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  515. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  516. package/src/renderers/opentui/start.tsx +5 -0
  517. package/src/renderers/opentui/toast-host.tsx +71 -43
  518. package/src/series/registry.ts +1 -0
  519. package/src/series/types.ts +6 -0
  520. package/src/series/validate.ts +1 -0
  521. package/src/sources/gloomberb-cloud/index.ts +1 -0
  522. package/src/sources/gloomberb-cloud/news.ts +47 -7
  523. package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
  524. package/src/sources/provider-router/batches.ts +10 -2
  525. package/src/sources/provider-router/financial-routes.ts +3 -3
  526. package/src/sources/provider-router/financials.ts +15 -3
  527. package/src/sources/provider-router/history.ts +28 -7
  528. package/src/sources/provider-router/index.ts +149 -10
  529. package/src/sources/provider-router/search.ts +13 -2
  530. package/src/state/app/context/index.tsx +3 -0
  531. package/src/state/ticker-cleanup.ts +50 -0
  532. package/src/sync/auth-transition.ts +19 -9
  533. package/src/sync/baseline.ts +10 -4
  534. package/src/sync/controller.ts +70 -19
  535. package/src/sync/core-contributors.ts +90 -31
  536. package/src/sync/react.ts +4 -1
  537. package/src/sync/types.ts +3 -0
  538. package/src/telemetry/stall-reports-dom.ts +38 -0
  539. package/src/telemetry/stall-reports-node.ts +75 -0
  540. package/src/telemetry/stall-reports.ts +214 -0
  541. package/src/telemetry/usage-counts.ts +0 -18
  542. package/src/test-support/preload.ts +14 -0
  543. package/src/theme/colors.ts +8 -0
  544. package/src/theme/themes.ts +10 -0
  545. package/src/tickers/company-name.ts +103 -3
  546. package/src/tickers/listing-venues.ts +38 -3
  547. package/src/tickers/search/bare-symbol.ts +36 -0
  548. package/src/tickers/search/index.ts +36 -7
  549. package/src/tickers/search/ranking.ts +10 -1
  550. package/src/tickers/search/types.ts +5 -0
  551. package/src/tickers/terminal-codes.ts +19 -0
  552. package/src/time-series/alignment.ts +1 -1
  553. package/src/time-series/base-series.ts +13 -5
  554. package/src/time-series/daily-valuation.ts +62 -0
  555. package/src/time-series/economic.ts +18 -4
  556. package/src/time-series/field-catalog.ts +2 -4
  557. package/src/time-series/filed-trailing-eps.ts +197 -0
  558. package/src/time-series/forward-valuation.ts +32 -0
  559. package/src/time-series/fundamentals.ts +198 -7
  560. package/src/time-series/hooks.ts +1 -0
  561. package/src/time-series/ice-credit.ts +3 -0
  562. package/src/time-series/price-comparison.ts +30 -2
  563. package/src/time-series/price-units.ts +72 -2
  564. package/src/time-series/resolve-context.ts +7 -1
  565. package/src/time-series/resolve.ts +116 -15
  566. package/src/time-series/series-presentation.ts +40 -6
  567. package/src/time-series/session-bar.ts +11 -0
  568. package/src/time-series/spec.ts +5 -3
  569. package/src/time-series/studies.ts +278 -74
  570. package/src/time-series/total-return.ts +160 -0
  571. package/src/time-series/transforms.ts +3 -3
  572. package/src/time-series/types.ts +27 -1
  573. package/src/time-series/valuation-releases.ts +39 -0
  574. package/src/types/data-provider.ts +9 -1
  575. package/src/types/desktop-window.ts +2 -0
  576. package/src/types/financials.ts +73 -2
  577. package/src/types/headless.ts +61 -5
  578. package/src/types/instrument.ts +3 -0
  579. package/src/types/news-source.ts +1 -0
  580. package/src/types/plugin.ts +10 -1
  581. package/src/ui/host.tsx +3 -0
  582. package/src/updater/command-names.ts +12 -7
  583. package/src/updater/failure.ts +4 -1
  584. package/src/utils/cli-output.ts +42 -1
  585. package/src/utils/crypto-pair.ts +44 -0
  586. package/src/utils/financial-statements.ts +6 -1
  587. package/src/utils/forward-dividend.ts +2 -0
  588. package/src/utils/fundamentals.ts +24 -3
  589. package/src/utils/fx-market-hours.ts +26 -3
  590. package/src/utils/main-thread-stall.ts +35 -2
  591. package/src/utils/market-capitalization.ts +24 -1
  592. package/src/utils/network-failure.ts +9 -6
  593. package/src/utils/option-expiry.ts +48 -1
  594. package/src/utils/options.ts +10 -0
  595. package/src/utils/plugin-proxy-hosts.json +1 -0
  596. package/src/utils/price-history.ts +12 -1
  597. package/src/utils/shell-word.ts +8 -0
  598. package/src/utils/trailing-dividend.ts +53 -4
  599. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
  600. package/src/renderers/browser/first-screen.ts +0 -193
@@ -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,
@@ -13,11 +13,33 @@ import {
13
13
  chainOpenInterestAsOf, loadListedOpenInterest, openInterestAsOfLabel, type OpenInterestAsOf, statedOpenInterestAsOf,
14
14
  } from "../../plugins/builtin/options/open-interest-as-of";
15
15
  import { DEFAULT_OPTION_CALC_DRAFT } from "../../plugins/builtin/options-calculator/model";
16
- import { expiryIsoDate, OPTION_EXPIRATION_PLACEHOLDER, parseOptionExpiration } from "../../utils/option-expiry";
16
+ import { expiryIsoDate, OPTION_EXPIRATION_PLACEHOLDER, parseOptionExpiration, settlementStampTime } from "../../utils/option-expiry";
17
17
  import { loadModelDividendYield } from "../../market-data/model-dividend-yield";
18
+ import { formatUtcTime } from "../../utils/utc-time";
19
+ import { takeOption } from "./command-utils";
18
20
 
19
21
  export const OPTIONS_USAGE = `options <symbol> [--expiration <${OPTION_EXPIRATION_PLACEHOLDER}>]`;
20
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
+
21
43
  export { parseOptionExpiration };
22
44
 
23
45
  /** Whether the chain holds contracts of the requested expiry, matched by UTC calendar date as the chain stamps them. */
@@ -62,16 +84,39 @@ export interface OptionModelInputs {
62
84
  interface OptionModelFigures {
63
85
  /** Annualized volatility as a fraction, solved from the chain's quotes. */
64
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;
65
89
  delta: number | null;
66
90
  }
67
91
 
68
92
  /** The risk-free rate every options valuation in the app assumes. */
69
93
  export const OPTION_MODEL_RATE = DEFAULT_OPTION_CALC_DRAFT.rate;
70
94
 
95
+ /** The venue's own marks a chain carries (Deribit's), stated in its JSON metadata in place of OPTION_VOLATILITY_FIELDS. */
96
+ export const MARKED_OPTION_FIELDS = {
97
+ iv: "The venue's mark implied volatility (decimal), one per contract. Delta and the other greeks under mark are the venue's too: "
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.",
100
+ bid: "Prices in USD are the coin prices (coin) at the expiry's forward (mark.forward), the venue's own model price.",
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.",
102
+ } as const;
103
+
104
+ /** Whether a venue marks the chain's contracts itself (Deribit): its IV and greeks are then used as they are. */
105
+ export function isMarkedChain(chain: OptionsChain): boolean {
106
+ return [...chain.calls, ...chain.puts].some((contract) => contract.mark != null);
107
+ }
108
+
109
+ /** The coin a chain's premiums are quoted in (BTC), when a venue quotes them in one. */
110
+ export function chainCoinCurrency(chain: OptionsChain): string | null {
111
+ return [...chain.calls, ...chain.puts].find((contract) => contract.coin)?.coin?.currency ?? null;
112
+ }
113
+
71
114
  /** What each contract's two volatilities are, stated in the chain's JSON metadata so neither is read as the other. */
72
115
  export const OPTION_VOLATILITY_FIELDS = {
73
116
  iv: "Annualized volatility (decimal) solved from the quote midpoint, one per strike from its out-of-the-money side, at the "
74
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.",
75
120
  impliedVolatility: "The data provider's own reported implied volatility, passed through unchanged. Delta does not use it; "
76
121
  + "GEX falls back to it only at a strike with no two-sided quote on either side.",
77
122
  } as const;
@@ -97,8 +142,9 @@ function optionModelFigures(chain: OptionsChain, inputs: OptionModelInputs): Map
97
142
  for (const contract of contracts) {
98
143
  const volatility = volatilities.byStrike.get(contract.strike);
99
144
  const delta = calculateOptionGreeks(contract, side, inputs.spot, inputs.dividendYield, volatilities)?.delta;
145
+ const iv = volatility != null && Number.isFinite(volatility) && volatility > 0 ? volatility : null;
100
146
  figures.set(contract, {
101
- iv: volatility != null && Number.isFinite(volatility) && volatility > 0 ? volatility : null,
147
+ iv, ivFilled: iv != null && volatilities.filled.has(contract.strike),
102
148
  delta: delta != null && Number.isFinite(delta) ? delta : null,
103
149
  });
104
150
  }
@@ -148,7 +194,8 @@ export async function loadOptionModelInputs(
148
194
  /** The chain with each contract's modeled `iv` and `delta` added; every provider field is kept. */
149
195
  export function chainWithModelFigures(chain: OptionsChain, inputs: OptionModelInputs): OptionsChain {
150
196
  const figures = optionModelFigures(chain, inputs);
151
- 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 });
152
199
  return { ...chain, calls: chain.calls.map(add), puts: chain.puts.map(add) };
153
200
  }
154
201
 
@@ -161,6 +208,13 @@ export interface OptionsChainBasis {
161
208
  spot: OptionModelSpot | null;
162
209
  dividendYield: number | null;
163
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);
164
218
  }
165
219
 
166
220
  /** The chain with `iv` and `delta` valued at the spot `optionModelSpot` picks for it, and that spot. */
@@ -188,16 +242,31 @@ export async function loadOptionsChainOpenInterest(symbol: string, chain: Option
188
242
  }
189
243
 
190
244
  /**
191
- * 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`.
192
- * An open interest whose date cannot be established says why.
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.
247
+ * An open interest whose date cannot be established says why. A chain its
248
+ * venue marks says whose marks they are: `Deribit marks: IV and greeks at
249
+ * each expiry's forward, rate 0%; $ is the BTC price at the forward · OI live, Thu 22 Oct 22:42 UTC`.
193
250
  */
194
- export function optionsBasisFootnote(basis: OptionsChainBasis): string {
251
+ export function optionsBasisFootnote(basis: OptionsChainBasis, chain?: OptionsChain): string {
195
252
  const parts: string[] = [];
253
+ if (chain && isMarkedChain(chain)) {
254
+ const coin = chainCoinCurrency(chain);
255
+ parts.push(`${chain.venue ?? "Venue"} marks: IV and greeks at each expiry's forward, rate ${Number(((chain.rate ?? 0) * 100).toFixed(2))}%`
256
+ + (coin ? `; $ is the ${coin} price at the forward` : ""));
257
+ // Its expiries settle at a stated time (08:00 UTC), which the expiration stamps carry.
258
+ const settles = [...chain.calls, ...chain.puts].map((contract) => settlementStampTime(contract.expiration)).find(Boolean);
259
+ if (settles) parts.push(`expiries settle ${settles}`);
260
+ const asOf = chain.asOf ? Date.parse(chain.asOf) : Number.NaN;
261
+ parts.push(Number.isFinite(asOf) ? `OI live, ${formatUtcTime(new Date(asOf).toISOString())}` : openInterestAsOfLabel(basis.openInterest));
262
+ return parts.join(" · ");
263
+ }
196
264
  if (basis.spot) {
197
265
  parts.push(`Greeks at spot ${formatModelSpotPrice(basis.spot.price)} (${modelSpotLabel(basis.spot)}), `
198
266
  // An unknown yield is valued as none, and says so rather than read as a stated 0%.
199
267
  + `dividend yield ${basis.dividendYield == null ? "unknown (valued at 0%)" : `${Number((basis.dividendYield * 100).toFixed(2))}%`}, `
200
268
  + `rate ${Number((OPTION_MODEL_RATE * 100).toFixed(2))}%`);
269
+ parts.push(ivSolvedNote(basis.ivFilled === true));
201
270
  }
202
271
  const oi = basis.openInterest;
203
272
  parts.push(oi?.date ? openInterestAsOfLabel(oi)
@@ -234,20 +303,48 @@ export function optionRows(chain: OptionsChain) {
234
303
  volume: contract.volume,
235
304
  openInterest: contract.openInterest,
236
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",
237
308
  delta: modeled.delta ?? null,
238
309
  expiration: expiryIsoDate(contract.expiration),
310
+ // A venue's marks and coin prices (Deribit), beside the dollar ones.
311
+ ...(contract.mark ? {
312
+ mark: contract.mark.price, gamma: contract.mark.gamma, vega: contract.mark.vega, theta: contract.mark.theta,
313
+ forward: contract.mark.forward, expiresAt: new Date(contract.expiration * 1000).toISOString(),
314
+ } : {}),
315
+ ...(contract.coin ? {
316
+ coinCurrency: contract.coin.currency, coinBid: contract.coin.bid, coinAsk: contract.coin.ask,
317
+ coinMark: contract.coin.mark, coinLast: contract.coin.last,
318
+ } : {}),
239
319
  };
240
320
  });
241
321
  }
242
322
 
323
+ /** A premium in a coin, to the venue's tick (0.0001 BTC); a dash for none. */
324
+ export function formatCoinPremiumCell(value: unknown): string {
325
+ return isFiniteNumber(value) && value > 0 ? value.toFixed(4) : "—";
326
+ }
327
+
328
+ /** A dollar greek or mark (vega, theta, mark) to cents; a dash for none. */
329
+ export function formatOptionDollarCell(value: unknown): string {
330
+ return isFiniteNumber(value) ? value.toFixed(2) : "—";
331
+ }
332
+
333
+ /** Gamma per 1.00 move, tiny for a coin worth 80,000 dollars: to six places, as the venue shows it to five. */
334
+ export function formatOptionGammaCell(value: unknown): string {
335
+ return isFiniteNumber(value) ? value.toFixed(6) : "—";
336
+ }
337
+
243
338
  export function formatOptionQuoteCell(row: Record<string, unknown>, side: "bid" | "ask"): string {
244
339
  const quote = optionQuoteSide({ bid: Number(row.bid), ask: Number(row.ask) }, side);
245
340
  return quote == null ? "—" : String(quote);
246
341
  }
247
342
 
248
- /** Implied volatility as a percent; a dash when it could not be solved, never 0.0%. */
249
- export function formatOptionIvCell(value: unknown): string {
250
- 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;
251
348
  }
252
349
 
253
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: [
@@ -3,6 +3,8 @@ import { apiClient } from "../../api-client";
3
3
  import { ApiRequestError } from "../../api-client/errors";
4
4
  import type { CliCommandDef } from "../../types/plugin";
5
5
  import { withCliServices, withMarketData } from "../context";
6
+ import { CLI_ERROR_CODE, failWithFields } from "../errors";
7
+ import type { CliResultColumn } from "../result";
6
8
  import { formatCompact } from "../../utils/format";
7
9
  import { DATE_OPTION_EXAMPLES, DATE_OPTION_FORMATS, dateOptionError, readCalendarDate } from "../../utils/calendar-date";
8
10
  import {
@@ -21,6 +23,7 @@ import {
21
23
  type MoverRankBasis,
22
24
  } from "../../plugins/builtin/market-movers/model";
23
25
  import { MOVER_CANDIDATES } from "../../plugins/builtin/market-movers/client";
26
+ import { hasPartialFloat, meetsMinFloat, minFloatShares, normalizeMinFloat, PARTIAL_FLOAT_NOTE } from "../../plugins/builtin/market-movers/min-float";
24
27
  import { fetchEconCalendarFrom } from "../../plugins/builtin/econ/calendar-source";
25
28
  import { computedFredSeriesNote, seriesNotOnFred } from "../../sources/gloomberb-cloud/fred-series";
26
29
  import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, type ExtendedSession } from "../../market-data/market/status";
@@ -39,7 +42,8 @@ import {
39
42
  type CountryFilter,
40
43
  type ImpactFilter,
41
44
  } from "../../plugins/builtin/econ/calendar-model";
42
- import { hasNoReleaseTime, hasNoStatedTime } from "../../plugins/builtin/econ/types";
45
+ import { hasNoReleaseTime, hasNoStatedTime, type EconEvent } from "../../plugins/builtin/econ/types";
46
+ import { econRowFigures } from "../../plugins/builtin/econ/figures";
43
47
  import { isoDate, rejectExtraArgs, requireArg, requireOneArg, takeFlag, takeOption } from "./command-utils";
44
48
  import { buildCorrelationSeries } from "../../plugins/builtin/correlation/matrix/model";
45
49
  import { alignDailyCloses, correlateDailyCloses } from "../../plugins/builtin/correlation/compute";
@@ -70,6 +74,7 @@ import {
70
74
  fredExportValue,
71
75
  fredHistoryNotes,
72
76
  fredInterval,
77
+ fredObservesDays,
73
78
  fredObservationsFromStart,
74
79
  fredProvenance,
75
80
  fredPublicationTime,
@@ -137,7 +142,7 @@ const MOVER_LISTS: Record<string, ScreenerCategory | "trending"> = {
137
142
  "most-active": "most_actives",
138
143
  trending: "trending",
139
144
  };
140
- const MOVERS_USAGE = "movers [gainers|losers|active|trending] [--session regular|extended]";
145
+ const MOVERS_USAGE = "movers [gainers|losers|active|trending] [--session regular|extended] [--min-float <shares>]";
141
146
  /** The most rows the movers list serves in one request. */
142
147
  const MOVER_REQUEST_CAP = 100;
143
148
  /** A one-shot list waits this long for its rows' average volumes and sizes, as `fn MOST` does. */
@@ -150,6 +155,16 @@ function moverRankBasis(value: string | undefined, ctx: Parameters<CliCommandDef
150
155
  return basis;
151
156
  }
152
157
 
158
+ /** `--min-float` in shares, null for none; fails on a value that is not a share count. */
159
+ function moverMinFloat(value: string | undefined, ctx: Parameters<CliCommandDef["execute"]>[1]): number | null {
160
+ if (value == null) return null;
161
+ try {
162
+ return minFloatShares(normalizeMinFloat(value));
163
+ } catch (error) {
164
+ ctx.fail(error instanceof Error ? error.message : String(error));
165
+ }
166
+ }
167
+
153
168
  function screenerCategory(args: readonly string[], ctx: Parameters<CliCommandDef["execute"]>[1]): ScreenerCategory | "trending" {
154
169
  rejectExtraArgs(args, 1, { usage: MOVERS_USAGE, takes: "one list" }, ctx);
155
170
  const list = args[0]?.toLowerCase() ?? "gainers";
@@ -195,12 +210,14 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
195
210
  const args = [...rawArgs];
196
211
  const sessionOption = takeOption(args, "--session");
197
212
  const basis = moverRankBasis(sessionOption, ctx);
213
+ const minFloat = moverMinFloat(takeOption(args, "--min-float"), ctx);
198
214
  const category = screenerCategory(args, ctx);
199
215
  const limit = ctx.cliOptions.limit ?? 25;
200
216
  if (sessionOption != null && (category === "trending" || category === "most_actives")) {
201
217
  ctx.fail("--session ranks Gainers and Losers.", category === "trending"
202
218
  ? "Trending keeps its source's order." : "Most active ranks by volume.");
203
219
  }
220
+ if (minFloat != null && category === "trending") ctx.fail("--min-float filters Gainers, Losers and Most active.", "Trending carries no float.");
204
221
  if (category === "trending") {
205
222
  await withCliServices(ctx, async (services) => {
206
223
  const trending = await fetchTrending(limit, undefined, { forceRefresh: ctx.cliOptions.refresh });
@@ -216,8 +233,8 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
216
233
  }
217
234
 
218
235
  // After the close a price list keeps only the rows moving its way on the basis
219
- // asked for, so it reads more candidates than it shows.
220
- const count = category === "most_actives" ? limit : Math.min(MOVER_REQUEST_CAP, Math.max(limit, MOVER_CANDIDATES));
236
+ // asked for, and a minimum float drops rows, so they read more candidates than they show.
237
+ const count = category === "most_actives" && minFloat == null ? limit : Math.min(MOVER_REQUEST_CAP, Math.max(limit, MOVER_CANDIDATES));
221
238
  // With the saved session, as MOST reads it: a Pro account gets the real-time lists, anyone else the delayed ones.
222
239
  const movers = await withMarketData(ctx, () =>
223
240
  fetchPreferredMarketMovers(category, count, { forceRefresh: ctx.cliOptions.refresh, session: basis, metadataWaitMs: MOVER_METADATA_WAIT_MS }));
@@ -226,7 +243,7 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
226
243
  // the regular session's move (its close's after the close), or with
227
244
  // --session extended by the extended print's move from that close.
228
245
  const { quotes, rankedBy } = rankMoverQuotes(category, movers.quotes.map(moverRegularSession), basis);
229
- const rows = quotes.slice(0, limit).map((quote) => {
246
+ const rows = quotes.filter((quote) => meetsMinFloat(quote.floatShares, minFloat)).slice(0, limit).map((quote) => {
230
247
  const { extended, ...row } = quote;
231
248
  return {
232
249
  ...row,
@@ -240,16 +257,18 @@ async function runMoverCommand(rawArgs: string[], ctx: Parameters<CliCommandDef[
240
257
  // As the MOST report reads it: real-time for a Pro account, delayed otherwise, each row dated by its own last price.
241
258
  ctx.printResult({
242
259
  data: rows,
243
- metadata: { category, rankedBy },
260
+ metadata: { category, rankedBy, ...(minFloat != null ? { minFloat } : {}) },
244
261
  ...(fellBack ? { warnings: ["No pre-market or after-hours prints now, so the list is ranked by the regular-session move."] } : {}),
245
262
  freshness: rowsFreshness(rows, { status: movers.source === "cloud" ? "live" : "delayed" }, { stale: movers.stale }),
246
263
  }, {
247
264
  heading: `Ranked by ${MOVER_RANKING_LABELS[rankedBy]}`,
265
+ ...(minFloat != null && hasPartialFloat(rows) ? { footnote: PARTIAL_FLOAT_NOTE } : {}),
248
266
  columns: [
249
267
  ...MOVER_COLUMNS.slice(0, 4),
250
268
  ...EXTENDED_COLUMNS,
251
269
  { key: "volume", header: "Volume", align: "right", value: (row) => formatCompact(Number(row.volume)) },
252
270
  { ...MARKET_CAP_CELL, value: (row) => row.marketCap == null ? "" : formatCompact(Number(row.marketCap)) },
271
+ ...(minFloat != null ? [{ key: "floatShares", header: "Float", align: "right" as const, value: (row: Record<string, unknown>) => formatCompact(Number(row.floatShares)) }] : []),
253
272
  ],
254
273
  });
255
274
  }
@@ -299,6 +318,70 @@ function publisherClock(localTime: unknown, timeZone: unknown): string {
299
318
  }
300
319
  const DAY_MS = 86_400_000;
301
320
 
321
+ /** The calendar's columns up to the event: shared by the text table and the exports, which keep the raw values under them. */
322
+ function econHeadColumns(viewerZone: string | null): CliResultColumn[] {
323
+ return [
324
+ // Text shows both halves of the event timestamp in UTC, never the host's zone; exports keep the source values.
325
+ { key: "date", header: "Date", format: (value) => utcDateTimePart(value, "date") },
326
+ // The weekday sits before the clock it is on: the UTC time's, or with --tz the viewer's.
327
+ ...viewerZone ? [] : [ECON_DAY_COLUMN],
328
+ // With --tz the viewer's clock has its own column, so the UTC time does not repeat it.
329
+ { key: "time", header: "Time", format: (value, row) => value === "All Day" ? "All day" : value === ""
330
+ ? "" : utcDateTimePart(row.date, "time", !viewerZone) },
331
+ ...viewerZone ? [ECON_DAY_COLUMN] : [],
332
+ // Only --tz fills it: the viewer's clock, its weekday in Day before it.
333
+ { key: "viewerTime", header: viewerZone ? timeZoneCity(viewerZone) : "Viewer", shrink: false,
334
+ format: (value) => typeof value === "string" && viewerZone ? value : "" },
335
+ // Only rows a publisher times itself fill it, so the feeds' calendar prints as before.
336
+ { key: "localTime", header: "Local", optional: true, dropPriority: 1, format: (value, row) => publisherClock(value, row.timeZone) },
337
+ { key: "country", header: "Country" },
338
+ { key: "impact", header: "Impact" },
339
+ { key: "event", header: "Event" },
340
+ ];
341
+ }
342
+
343
+ /**
344
+ * The text table's columns up to the event. Plain output keeps every column a
345
+ * query has, so a column that is empty by construction has to be left out
346
+ * here, not hidden by the table: the viewer's clock only exists with --tz, and
347
+ * the publisher's own clock only on rows a bank or statistics office times
348
+ * itself. Exports keep both, so a script's header never moves.
349
+ */
350
+ function econTextHeadColumns(viewerZone: string | null, publisherTimed: boolean): CliResultColumn[] {
351
+ return econHeadColumns(viewerZone).filter((column) => (
352
+ column.key === "viewerTime" ? !!viewerZone : column.key === "localTime" ? publisherTimed : true
353
+ ));
354
+ }
355
+
356
+ /**
357
+ * Actual, Forecast and Prior: as the text draws them (`30.3K`, `-4.1M bbl`) in the table, as
358
+ * numbers in the exports (30300, -4100000), where Unit and Scale say what they count and how they were drawn.
359
+ */
360
+ function econFigureColumns(text: boolean): CliResultColumn[] {
361
+ return text
362
+ ? [{ key: "actualText", header: "Actual" }, { key: "forecastText", header: "Forecast" }, { key: "priorText", header: "Prior" }]
363
+ : [{ key: "actual", header: "Actual" }, { key: "forecast", header: "Forecast" }, { key: "prior", header: "Prior" }];
364
+ }
365
+
366
+ const ECON_PUBLISHER_COLUMN: CliResultColumn = {
367
+ key: "publisher", header: "Publisher", format: (value) => typeof value === "string" ? value : "",
368
+ };
369
+
370
+ /** What only the exports say about a row: how to read its figures, who publishes it and where, and the figures as drawn. */
371
+ const econExportColumns: CliResultColumn[] = [
372
+ { key: "unit", header: "Unit" },
373
+ { key: "scale", header: "Scale" },
374
+ { key: "publisher", header: "Publisher" },
375
+ { key: "sourceUrl", header: "Link" },
376
+ // The figures as the calendar draws them, so a bound (`<1.25%`) that is no number keeps its text.
377
+ { key: "actualText", header: "actual_text" },
378
+ { key: "forecastText", header: "forecast_text" },
379
+ { key: "priorText", header: "prior_text" },
380
+ // Which feed the forecast was read from and when, so a consensus can be told from another read of it.
381
+ { key: "consensusFeed", header: "consensus_feed" },
382
+ { key: "consensusReadAt", header: "consensus_read_at" },
383
+ ];
384
+
302
385
  /**
303
386
  * What an empty calendar says. A window that starts after the last day listed
304
387
  * says where the listing stops, whatever the filters, rather than nothing.
@@ -357,6 +440,32 @@ function blankTimeNotes(rows: ReadonlyArray<{ event: string; country: string; ti
357
440
  return [...byNote].map(([note, events]) => `No time for ${events.join(", ")}: ${note.charAt(0).toLowerCase()}${note.slice(1)}.`);
358
441
  }
359
442
 
443
+ /**
444
+ * Whether a row has come out: it has an actual, or it has no figure at all (a
445
+ * speech, a meeting, a holiday) and its time has passed. A row with a forecast
446
+ * or a prior and no actual is still to come, whatever its time.
447
+ */
448
+ function cameOut(event: Pick<EconEvent, "actual" | "forecast" | "prior" | "date">, now: number): boolean {
449
+ if (event.actual?.trim()) return true;
450
+ return !event.forecast?.trim() && !event.prior?.trim() && event.date.getTime() <= now;
451
+ }
452
+
453
+ /**
454
+ * A `--from` the stored calendar does not reach back to, which Gloom Cloud refuses
455
+ * with "The calendar reaches back 400 days": an input error naming the earliest
456
+ * day it takes, and `--json` carries it as `earliest`. Any other failure goes on as it was.
457
+ */
458
+ function failBeforeCalendar(error: unknown, from: string, todayStart: number): never {
459
+ const days = error instanceof ApiRequestError && error.status === 422 ? /reaches back (\d+) days?/i.exec(error.message)?.[1] : undefined;
460
+ if (!days) throw error;
461
+ const earliest = isoDate(new Date(todayStart - Number(days) * DAY_MS)).slice(0, 10);
462
+ return failWithFields(
463
+ `--from ${from} is before the earliest day the calendar reaches back to, ${earliest}. Use --from ${earliest} or later.`,
464
+ CLI_ERROR_CODE,
465
+ { earliest, days: Number(days) },
466
+ );
467
+ }
468
+
360
469
  async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
361
470
  const rawArgs = [...args];
362
471
  const country = parseCountry(takeOption(rawArgs, "--country"), ctx);
@@ -369,10 +478,13 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
369
478
  }
370
479
  rejectArgs(rawArgs, ECON_USAGE, ctx, "Filter with --country and --impact.");
371
480
  await withCliServices(ctx, async () => {
372
- const todayStart = Math.floor(Date.now() / DAY_MS) * DAY_MS;
481
+ const now = Date.now();
482
+ const todayStart = Math.floor(now / DAY_MS) * DAY_MS;
373
483
  // An earlier day reads the stored calendar, which keeps past events after the week's listing drops them.
374
484
  // A server without it, a server error or no network lists the current calendar (cached when offline) instead.
375
- const stored = fromOption != null && fromOption < isoDate(new Date(todayStart)) ? await fetchEconCalendarFrom(fromOption) : null;
485
+ const stored = fromOption != null && fromOption < isoDate(new Date(todayStart))
486
+ ? await fetchEconCalendarFrom(fromOption).catch((error: unknown) => failBeforeCalendar(error, fromOption, todayStart))
487
+ : null;
376
488
  const past = stored?.events ? stored : null;
377
489
  const unread = stored && !stored.events && stored.failure ? `The stored calendar could not be read (${stored.failure}); listing the current calendar` : null;
378
490
  const { data: events, fetchedAt, stale } = past ? { data: past.events, fetchedAt: Date.now(), stale: false } : await loadCalendar(ctx.cliOptions.refresh);
@@ -400,6 +512,8 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
400
512
  .map((event) => {
401
513
  // The viewer's clock (--tz) for a row with a time: its date and time there.
402
514
  const clock = viewerZone && !hasNoReleaseTime(event) ? zonedClock(event.date.getTime(), viewerZone) : null;
515
+ // The figures as numbers, scale multiplied out, with their unit and the scale they are drawn in; the text stays as drawn.
516
+ const figures = econRowFigures(event);
403
517
  return {
404
518
  date: isoDate(event.date),
405
519
  // The weekday on the viewer's clock (--tz), else the UTC day's; a row with no time is its day's.
@@ -409,9 +523,14 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
409
523
  country: eventCountry(event),
410
524
  impact: event.impact,
411
525
  event: event.event,
412
- actual: event.actual ?? "",
413
- forecast: event.forecast ?? "",
414
- prior: event.prior ?? "",
526
+ actual: figures.actual,
527
+ forecast: figures.forecast,
528
+ prior: figures.prior,
529
+ unit: figures.unit,
530
+ scale: figures.scale,
531
+ actualText: event.actual ?? null,
532
+ forecastText: event.forecast ?? null,
533
+ priorText: event.prior ?? null,
415
534
  // The publisher's own clock, for rows Gloom Cloud adds from a bank's or a statistics office's calendar.
416
535
  localTime: event.localTime ?? null,
417
536
  timeZone: event.timeZone ?? null,
@@ -420,15 +539,23 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
420
539
  // Who publishes the figure (Freddie Mac for the 30-year mortgage rate), the period it covers and its page.
421
540
  publisher: event.source?.name ?? null,
422
541
  referencePeriod: event.referencePeriod ?? null,
423
- source: event.source?.name ?? null,
542
+ // When the figure came out: the row's time once it has an actual, and for a row with no figures (a speech, a
543
+ // meeting) once its time has passed; its day alone when it states no time, null before.
544
+ released: cameOut(event, now) ? (hasNoReleaseTime(event) ? isoDate(event.date).slice(0, 10) : isoDate(event.date)) : null,
424
545
  sourceUrl: event.source?.url ?? null,
546
+ // The same indicator from one release to the next, as Gloom Cloud names it.
547
+ indicatorId: event.indicatorId ?? null,
548
+ // The feed the forecast was read from and when, for a row with a forecast.
549
+ consensusFeed: event.consensus?.source ?? null,
550
+ consensusReadAt: event.consensus?.readAt ?? null,
425
551
  };
426
552
  });
427
553
  // A filter of countries only, none of which has anything in the window, says so as its empty list.
428
554
  const allUnscheduled = unscheduled.length > 0 && unscheduled.length === country.split(",").length;
429
- const footnote = [listedBefore, listedAfter, ...(rows.length > 0 ? blankTimeNotes(rows) : []),
555
+ const notes = [listedBefore, listedAfter, ...(rows.length > 0 ? blankTimeNotes(rows) : []),
430
556
  rows.length > 0 && unscheduled.length > 0 ? noScheduledReleasesMessage(unscheduled) : null]
431
- .filter((note): note is string => !!note).join("\n");
557
+ .filter((note): note is string => !!note);
558
+ const footnote = notes.join("\n");
432
559
  ctx.printResult({
433
560
  data: rows,
434
561
  ...(unread ? { warnings: [unread] } : {}),
@@ -440,28 +567,21 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
440
567
  defaultLimit: 50,
441
568
  empty: allUnscheduled ? noScheduledReleasesMessage(unscheduled) : econEmptyMessage(from, listedThrough, listedAfter != null),
442
569
  ...(footnote ? { footnote } : {}),
443
- columns: [
444
- // Text shows both halves of the event timestamp in UTC, never the host's zone; exports keep the source values.
445
- { key: "date", header: "Date", format: (value) => utcDateTimePart(value, "date") },
446
- // The weekday sits before the clock it is on: the UTC time's, or with --tz the viewer's.
447
- ...viewerZone ? [] : [ECON_DAY_COLUMN],
448
- // With --tz the viewer's clock has its own column, so the UTC time does not repeat it.
449
- { key: "time", header: "Time", format: (value, row) => value === "All Day" ? "All day" : value === ""
450
- ? "" : utcDateTimePart(row.date, "time", !viewerZone) },
451
- ...viewerZone ? [ECON_DAY_COLUMN] : [],
452
- // Only --tz fills it: the viewer's clock, its weekday in Day before it.
453
- { key: "viewerTime", header: viewerZone ? timeZoneCity(viewerZone) : "Viewer", shrink: false,
454
- format: (value) => typeof value === "string" && viewerZone ? value : "" },
455
- // Only rows a publisher times itself fill it, so the feeds' calendar prints as before.
456
- { key: "localTime", header: "Local", optional: true, dropPriority: 1, format: (value, row) => publisherClock(value, row.timeZone) },
457
- { key: "country", header: "Country" },
458
- { key: "impact", header: "Impact" },
459
- { key: "event", header: "Event" },
460
- { key: "actual", header: "Actual" },
461
- { key: "forecast", header: "Forecast" },
462
- { key: "prior", header: "Prior" },
463
- { key: "publisher", header: "Publisher", format: (value) => typeof value === "string" ? value : "" },
464
- ],
570
+ // What the text says under the table, a script reads in the CSV's closing lines.
571
+ exportNotes: notes,
572
+ // `as_of` is the release: when a row's figure came out, blank for one that has none yet.
573
+ exportDated: true,
574
+ exportIdentity: (row) => ({
575
+ symbolOrSeries: typeof row.indicatorId === "string" ? row.indicatorId : null,
576
+ // A percent is `pct` here as in `fred` and `yield-curve`; `unit` keeps the `%` the calendar draws.
577
+ currencyOrUnit: row.unit === "%" ? "pct" : typeof row.unit === "string" ? row.unit : null,
578
+ asOf: typeof row.released === "string" ? row.released : null,
579
+ released: typeof row.released === "string" ? row.released : null,
580
+ period: typeof row.referencePeriod === "string" ? row.referencePeriod : null,
581
+ }),
582
+ // The exports add what a script needs to read a row; the text table keeps the columns it had.
583
+ columns: [...econHeadColumns(viewerZone), ...econFigureColumns(false), ...econExportColumns],
584
+ textColumns: [...econTextHeadColumns(viewerZone, rows.some((row) => !!publisherClock(row.localTime, row.timeZone))), ...econFigureColumns(true), ECON_PUBLISHER_COLUMN],
465
585
  });
466
586
  });
467
587
  }
@@ -545,12 +665,16 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
545
665
  if (mismatch) ctx.fail(mismatch);
546
666
  // Each row's change in the unit its value exports in, and the date it is from; a holiday's stays blank.
547
667
  const changes = horizon ? fredChanges(seriesId, data.observations, horizon) : null;
668
+ const releasedOf = (observation: { publisher?: string; publishedAt?: string; releasedOn?: string }) =>
669
+ observation.releasedOn ?? (observation.publisher?.trim() ? observation.publishedAt : undefined);
548
670
  // A value FRED does not list yet carries its publisher and when it published, in every export.
549
- const exportRow = (observation: { date: string; value: number | null; publisher?: string; publishedAt?: string }) => ({
671
+ const exportRow = (observation: { date: string; value: number | null; publisher?: string; publishedAt?: string; releasedOn?: string }) => ({
550
672
  date: observation.date,
551
673
  value: fredExportValue(seriesId, observation.value),
552
674
  ...(changes ? { change: changes.get(observation.date)?.change ?? null, changeFrom: changes.get(observation.date)?.changeFrom ?? null } : {}),
553
675
  ...(observation.publisher?.trim() ? { publisher: observation.publisher.trim(), ...(observation.publishedAt ? { publishedAt: observation.publishedAt } : {}) } : {}),
676
+ // When the figure came out, where the data says: only a series' newest observation does. `date` is the period it covers.
677
+ ...releasedOf(observation) ? { released: releasedOf(observation) } : {},
554
678
  });
555
679
  // A --start before the series begins comes back padded with blank days: the rows start at its first observation.
556
680
  const rows = fredObservationsFromStart(data.observations, data.info?.observationStart)
@@ -570,6 +694,7 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
570
694
  ctx.fail(`No ${seriesId} observations on or after ${startDate}.`, fredEmptyAdvice(seriesId, latest));
571
695
  }
572
696
  const periodicity = periodicityOf(data.info?.frequency);
697
+ const observesDays = fredObservesDays(data.info?.frequency);
573
698
  const newestDate = rows.reduce<string | null>((newest, row) => (!newest || row.date > newest ? row.date : newest), null);
574
699
  // A newest value FRED does not list yet names its publisher, Treasury for a yield; FRED's own stay "FRED".
575
700
  const ahead = fredProvenance(data.observations.filter((observation) => observation.date === newestDate), seriesId);
@@ -598,7 +723,16 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
598
723
  dateKey: "date",
599
724
  empty: fredEmptyMessage(startDate, latest == null ? null : exportRow(latest)),
600
725
  ...(notes.length ? { footnote: notes.join("\n"), exportNotes: notes } : {}),
601
- exportIdentity: { symbolOrSeries: seriesId, currencyOrUnit: unit, interval },
726
+ // `as_of` is the release. A figure with none stated is as of its own day when the series is known to observe
727
+ // days (a daily or weekly rate), and of nothing otherwise, never the first day of the period it covers.
728
+ exportDated: true,
729
+ exportIdentity: (row) => {
730
+ const released = typeof row.released === "string" ? row.released : null;
731
+ const date = typeof row.date === "string" ? row.date : null;
732
+ return { symbolOrSeries: seriesId, currencyOrUnit: unit, interval, asOf: released ?? (observesDays ? date : null), period: date, released };
733
+ },
734
+ // The release closes the row with the other dating columns, so it is not one of the observation's own.
735
+ columns: [...new Set(rows.flatMap((row) => Object.keys(row)))].filter((key) => key !== "released").map((key) => ({ key, header: key })),
602
736
  textColumns: [
603
737
  { key: "date", header: "Date" },
604
738
  isSpreadSeries(seriesId)
@@ -738,8 +872,11 @@ export const overviewCliCommands: CliCommandDef[] = [
738
872
  options: [{
739
873
  flags: "--session <basis>",
740
874
  description: "Rank gainers and losers by the regular-session move (regular, the default) or by the pre-market or after-hours move from the close (extended)",
875
+ }, {
876
+ flags: "--min-float <shares>",
877
+ description: "Keep the gainers, losers or most active whose float (the one EQS serves) is at least this many shares, such as 10M or 500k; a row with no float is left out",
741
878
  }],
742
- examples: ["movers", "movers losers --limit 10", "movers gainers --session extended", "movers trending"],
879
+ examples: ["movers", "movers losers --limit 10", "movers gainers --session extended", "movers gainers --min-float 10M", "movers trending"],
743
880
  },
744
881
  execute: runMoverCommand,
745
882
  },