gloomberb 0.18.5 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +140 -7
  3. package/src/api-client/cot.ts +6 -2
  4. package/src/api-client/credit-documents.ts +6 -0
  5. package/src/api-client/data.ts +17 -6
  6. package/src/api-client/equity-screener.ts +24 -0
  7. package/src/api-client/fund-facts.ts +45 -4
  8. package/src/api-client/index.ts +11 -0
  9. package/src/api-client/market-discovery.ts +10 -0
  10. package/src/api-client/market-movers.ts +7 -0
  11. package/src/api-client/members.ts +1 -1
  12. package/src/api-client/perps.ts +43 -0
  13. package/src/api-client/research-activity.ts +3 -2
  14. package/src/api-client/types.ts +70 -2
  15. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  16. package/src/app/runtime/broker-import.ts +0 -2
  17. package/src/app/runtime/plugin-bindings.ts +73 -56
  18. package/src/architecture/pane-tabs-allowlist.json +3 -0
  19. package/src/architecture/ttl-caches-allowlist.json +19 -0
  20. package/src/brokers/sync-broker-instance.ts +2 -8
  21. package/src/capabilities/chart-series.ts +1 -1
  22. package/src/cli/commands/crypto-hints.ts +40 -24
  23. package/src/cli/commands/market.ts +72 -26
  24. package/src/cli/commands/options-chain.ts +48 -7
  25. package/src/cli/commands/options-leaps.ts +9 -3
  26. package/src/cli/commands/overview.ts +61 -12
  27. package/src/cli/commands/search.ts +21 -1
  28. package/src/cli/commands/themes.ts +11 -2
  29. package/src/cli/commands/ticker.ts +55 -34
  30. package/src/cli/company-name-hint.ts +41 -0
  31. package/src/cli/earnings-calendar.ts +1 -1
  32. package/src/cli/errors.ts +8 -0
  33. package/src/cli/glossary.ts +25 -2
  34. package/src/cli/history-rows.ts +57 -9
  35. package/src/cli/index.ts +2 -0
  36. package/src/cli/listing-arg.ts +180 -52
  37. package/src/cli/output-file.ts +68 -0
  38. package/src/cli/pane-functions/capabilities.ts +3 -0
  39. package/src/cli/pane-functions/catalog.ts +80 -7
  40. package/src/cli/pane-functions/dom.ts +13 -3
  41. package/src/cli/pane-functions/freshness.ts +86 -3
  42. package/src/cli/pane-functions/function-help.ts +13 -10
  43. package/src/cli/pane-functions/headless.ts +17 -7
  44. package/src/cli/pane-functions/index.ts +49 -14
  45. package/src/cli/pane-functions/market-session.ts +66 -4
  46. package/src/cli/pane-functions/options.ts +19 -1
  47. package/src/cli/pane-functions/resolver.ts +19 -8
  48. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  49. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  50. package/src/cli/quote-answer.ts +22 -11
  51. package/src/cli/ticker-resolution.ts +3 -2
  52. package/src/components/chart/composite/composite-chart.tsx +4 -0
  53. package/src/components/chart/composite/interactions.ts +6 -0
  54. package/src/components/chart/composite/legend.tsx +45 -7
  55. package/src/components/chart/composite/panel-gestures.ts +33 -0
  56. package/src/components/chart/composite/panel-surface.tsx +52 -3
  57. package/src/components/chart/composite/rasterizer.ts +24 -13
  58. package/src/components/chart/composite/scene.ts +39 -4
  59. package/src/components/chart/composite/series-cues.ts +14 -0
  60. package/src/components/chart/composite/text-renderer.ts +9 -2
  61. package/src/components/chart/composite/tool-catalog.ts +36 -0
  62. package/src/components/chart/composite/tools.ts +203 -44
  63. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  64. package/src/components/command-bar/assist/inventory.ts +2 -1
  65. package/src/components/command-bar/commands/direct/index.ts +28 -13
  66. package/src/components/command-bar/pane-templates/items.ts +3 -2
  67. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  68. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  69. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  70. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  71. package/src/components/onboarding/wizard-positions.ts +3 -3
  72. package/src/components/ticker/list-table-view.tsx +4 -1
  73. package/src/components/ui/choice-dialog.tsx +23 -18
  74. package/src/components/ui/choice-groups.ts +18 -0
  75. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  76. package/src/components/ui/data-table/opentui/model.ts +26 -1
  77. package/src/components/ui/data-table/types.ts +2 -0
  78. package/src/components/ui/list-view.tsx +8 -4
  79. package/src/components/ui/query-bar-keys.tsx +2 -0
  80. package/src/components/ui/query-bar.tsx +1 -0
  81. package/src/components/ui/select-button.tsx +5 -1
  82. package/src/components/ui/select-field.tsx +1 -0
  83. package/src/components/ui/table-layout.ts +8 -5
  84. package/src/core/app-runtime.ts +2 -0
  85. package/src/core/app-services.ts +2 -2
  86. package/src/data/config/chart-settings.ts +1 -1
  87. package/src/data/json-ticker-repository.ts +17 -8
  88. package/src/data/ticker-repository.ts +16 -9
  89. package/src/data/ticker-writes.ts +81 -0
  90. package/src/data/ttl-cache.ts +165 -0
  91. package/src/docs/snapshot.json +71 -137
  92. package/src/i18n/es.ts +18 -2
  93. package/src/i18n/ja.ts +18 -2
  94. package/src/i18n/ko.ts +18 -2
  95. package/src/i18n/zh-cn.ts +18 -2
  96. package/src/i18n/zh-tw.ts +18 -2
  97. package/src/market-data/depositary-pair.ts +2 -2
  98. package/src/market-data/fund-facts.ts +50 -0
  99. package/src/market-data/market/change-basis.ts +16 -3
  100. package/src/market-data/market/format.ts +25 -0
  101. package/src/market-data/market/freshness.ts +2 -1
  102. package/src/market-data/options-alternatives.ts +81 -4
  103. package/src/news/aggregator.ts +113 -9
  104. package/src/news/cloud-session.ts +21 -0
  105. package/src/news/hooks.ts +1 -0
  106. package/src/news/news-model.ts +1 -0
  107. package/src/news/types.ts +11 -0
  108. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  109. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  110. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  111. package/src/plugins/builtin/alerts/cli.ts +336 -77
  112. package/src/plugins/builtin/alerts/command.ts +25 -1
  113. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  114. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  115. package/src/plugins/builtin/alerts/format.ts +75 -5
  116. package/src/plugins/builtin/alerts/headless.ts +24 -6
  117. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  118. package/src/plugins/builtin/alerts/history.ts +1 -1
  119. package/src/plugins/builtin/alerts/index.tsx +79 -16
  120. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  121. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  122. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  123. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  124. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  125. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  126. package/src/plugins/builtin/alerts/storage.ts +12 -8
  127. package/src/plugins/builtin/alerts/types.ts +4 -0
  128. package/src/plugins/builtin/analytics/index.tsx +2 -1
  129. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  130. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  131. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  132. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  134. package/src/plugins/builtin/attention/index.tsx +2 -1
  135. package/src/plugins/builtin/attention/listing.ts +14 -0
  136. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  137. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  138. package/src/plugins/builtin/buildout/format.ts +38 -0
  139. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  140. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  141. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  142. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  143. package/src/plugins/builtin/changelog/index.tsx +9 -6
  144. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  145. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  146. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  147. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  148. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  149. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  150. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  151. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  152. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  153. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  154. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  155. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  156. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  157. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  158. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  159. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  160. package/src/plugins/builtin/company-attention/client.ts +8 -9
  161. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  162. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  163. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  164. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  165. package/src/plugins/builtin/correlation/headless.ts +13 -5
  166. package/src/plugins/builtin/correlation/history.ts +22 -0
  167. package/src/plugins/builtin/cot/client.ts +22 -18
  168. package/src/plugins/builtin/cot/headless.ts +15 -10
  169. package/src/plugins/builtin/cot/model.ts +14 -1
  170. package/src/plugins/builtin/cot/pane.tsx +22 -14
  171. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  172. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  173. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  174. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  175. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  176. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  177. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  178. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  179. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  180. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  181. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  182. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  183. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  184. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  185. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  186. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  187. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  188. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  189. package/src/plugins/builtin/docs/targets.ts +5 -1
  190. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  191. package/src/plugins/builtin/earnings/board.tsx +62 -71
  192. package/src/plugins/builtin/earnings/headless.ts +40 -4
  193. package/src/plugins/builtin/earnings/scope.ts +31 -43
  194. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  195. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  196. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  197. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  198. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  199. package/src/plugins/builtin/econ/types.ts +2 -0
  200. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  201. package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
  202. package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
  203. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  204. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  205. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  206. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  207. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  208. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  209. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  210. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  211. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  212. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  213. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  214. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  215. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  216. package/src/plugins/builtin/gpu/headless.ts +8 -2
  217. package/src/plugins/builtin/help/function-index.ts +4 -1
  218. package/src/plugins/builtin/help/index.tsx +1 -0
  219. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  220. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  221. package/src/plugins/builtin/holders/headless.ts +18 -1
  222. package/src/plugins/builtin/iv-history/client.ts +52 -11
  223. package/src/plugins/builtin/iv-history/format.ts +36 -23
  224. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  225. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  226. package/src/plugins/builtin/iv-history/model.ts +163 -27
  227. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  228. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  229. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  230. package/src/plugins/builtin/jobs/client.ts +25 -29
  231. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  232. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  233. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  234. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  235. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  236. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  237. package/src/plugins/builtin/market-movers/client.ts +25 -7
  238. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  239. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  240. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  241. package/src/plugins/builtin/market-movers/model.ts +4 -1
  242. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  243. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  244. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  245. package/src/plugins/builtin/market-movers/table.ts +17 -5
  246. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  247. package/src/plugins/builtin/news/index.tsx +1 -0
  248. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  249. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  250. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  251. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  252. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  253. package/src/plugins/builtin/openfda/client.ts +14 -2
  254. package/src/plugins/builtin/openfda/index.tsx +3 -2
  255. package/src/plugins/builtin/openfda/model.ts +9 -0
  256. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  257. package/src/plugins/builtin/options/analytics.ts +29 -5
  258. package/src/plugins/builtin/options/enrichment.ts +17 -5
  259. package/src/plugins/builtin/options/table.ts +5 -2
  260. package/src/plugins/builtin/options/types.ts +2 -0
  261. package/src/plugins/builtin/options/view.tsx +17 -4
  262. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  263. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  264. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  265. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  266. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  267. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  268. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  269. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  270. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  271. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  272. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  273. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  274. package/src/plugins/builtin/pe-band/model.ts +12 -49
  275. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  276. package/src/plugins/builtin/perps/cascade.ts +236 -0
  277. package/src/plugins/builtin/perps/client.ts +56 -2
  278. package/src/plugins/builtin/perps/headless.ts +65 -9
  279. package/src/plugins/builtin/perps/index.tsx +7 -5
  280. package/src/plugins/builtin/perps/model.ts +18 -4
  281. package/src/plugins/builtin/perps/pane.tsx +19 -22
  282. package/src/plugins/builtin/perps/status-line.ts +25 -0
  283. package/src/plugins/builtin/plugin-module.ts +5 -1
  284. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  285. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  286. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  287. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  288. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  289. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  290. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  291. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  292. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  293. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  294. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  295. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  296. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  297. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  298. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  299. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  300. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  301. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  302. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  303. package/src/plugins/builtin/research/event-model.ts +10 -8
  304. package/src/plugins/builtin/research/headless.ts +1 -1
  305. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  306. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  307. package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
  308. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  309. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  310. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  311. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  312. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  313. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  314. package/src/plugins/builtin/scanner/headless.ts +204 -35
  315. package/src/plugins/builtin/scanner/index.ts +16 -5
  316. package/src/plugins/builtin/scanner/live-headless.ts +27 -3
  317. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  318. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  319. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  320. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  321. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  322. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  323. package/src/plugins/builtin/sec/headless.ts +2 -1
  324. package/src/plugins/builtin/series-inventory.ts +2 -0
  325. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  326. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  327. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  328. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  329. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  330. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  331. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  332. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  333. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  334. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  335. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  336. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  337. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  338. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  339. package/src/plugins/builtin/themes/model.ts +16 -7
  340. package/src/plugins/builtin/themes/pane.tsx +5 -5
  341. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  342. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  343. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  344. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  345. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  346. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  347. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  348. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  349. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  350. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  351. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  352. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  353. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  354. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  355. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  357. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  358. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  359. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  360. package/src/plugins/builtin/volatility/index.tsx +2 -2
  361. package/src/plugins/builtin/volatility/pane.tsx +2 -2
  362. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  363. package/src/plugins/catalog-manifest.ts +2 -1
  364. package/src/plugins/marketplace/model.ts +18 -5
  365. package/src/portfolio/futures-positions.ts +6 -3
  366. package/src/portfolio/position-quantity.ts +15 -9
  367. package/src/remote/resources.ts +2 -1
  368. package/src/renderers/dom/data-table/row.tsx +26 -0
  369. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  370. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  371. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  372. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  373. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  374. package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
  375. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  376. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  377. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  378. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  379. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  380. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  381. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  382. package/src/renderers/electrobun/view/main.tsx +24 -1
  383. package/src/renderers/electrobun/view/news-source.ts +3 -1
  384. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  385. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  386. package/src/renderers/opentui/start.tsx +5 -0
  387. package/src/series/registry.ts +1 -0
  388. package/src/series/types.ts +6 -0
  389. package/src/series/validate.ts +1 -0
  390. package/src/sources/gloomberb-cloud/index.ts +1 -0
  391. package/src/sources/gloomberb-cloud/news.ts +47 -7
  392. package/src/sources/provider-router/financials.ts +15 -3
  393. package/src/sources/provider-router/index.ts +8 -5
  394. package/src/state/ticker-cleanup.ts +50 -0
  395. package/src/sync/controller.ts +4 -3
  396. package/src/sync/core-contributors.ts +90 -31
  397. package/src/telemetry/stall-reports-dom.ts +38 -0
  398. package/src/telemetry/stall-reports-node.ts +75 -0
  399. package/src/telemetry/stall-reports.ts +214 -0
  400. package/src/test-support/preload.ts +14 -0
  401. package/src/theme/themes.ts +10 -0
  402. package/src/tickers/company-name.ts +44 -3
  403. package/src/tickers/listing-venues.ts +15 -3
  404. package/src/tickers/terminal-codes.ts +19 -0
  405. package/src/time-series/alignment.ts +1 -1
  406. package/src/time-series/economic.ts +18 -4
  407. package/src/time-series/field-catalog.ts +1 -1
  408. package/src/time-series/filed-trailing-eps.ts +197 -0
  409. package/src/time-series/fundamentals.ts +44 -1
  410. package/src/time-series/ice-credit.ts +3 -0
  411. package/src/time-series/price-comparison.ts +30 -2
  412. package/src/time-series/resolve-context.ts +3 -1
  413. package/src/time-series/resolve.ts +56 -4
  414. package/src/time-series/session-bar.ts +11 -0
  415. package/src/time-series/spec.ts +3 -2
  416. package/src/time-series/studies.ts +8 -2
  417. package/src/time-series/total-return.ts +160 -0
  418. package/src/time-series/transforms.ts +2 -3
  419. package/src/time-series/types.ts +22 -1
  420. package/src/types/financials.ts +32 -1
  421. package/src/types/headless.ts +21 -3
  422. package/src/types/news-source.ts +1 -0
  423. package/src/types/plugin.ts +9 -0
  424. package/src/ui/host.tsx +3 -0
  425. package/src/updater/failure.ts +4 -1
  426. package/src/utils/cli-output.ts +42 -1
  427. package/src/utils/crypto-pair.ts +44 -0
  428. package/src/utils/forward-dividend.ts +2 -0
  429. package/src/utils/fundamentals.ts +24 -3
  430. package/src/utils/fx-market-hours.ts +26 -3
  431. package/src/utils/main-thread-stall.ts +35 -2
  432. package/src/utils/market-capitalization.ts +24 -1
  433. package/src/utils/price-history.ts +7 -1
  434. package/src/utils/shell-word.ts +8 -0
  435. package/src/utils/trailing-dividend.ts +53 -4
  436. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -1,21 +1,32 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import type { ScannerFlowEvent } from "../../../api-client";
1
+ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
2
+ import type { ScannerFlowEvent, ScannerFlowSession } from "../../../api-client";
3
3
  import {
4
+ ChartTableHeader,
4
5
  DataTableView,
5
6
  PaneStatusBody,
6
7
  QueryBar,
8
+ scalarPoint,
9
+ staticSeries,
10
+ useChartTableSelection,
11
+ usePaneTabs,
7
12
  useTableLoadMore,
13
+ type ChartTableChart,
8
14
  type DataTableCell,
9
15
  type DataTableColumn,
10
16
  type DataTableKeyEvent,
11
17
  type PaneFooterSegment,
12
18
  } from "../../../components";
19
+ import { fitChartTableColumns, formatCompactAxis } from "../../../components/chart-table";
20
+ import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
21
+ import type { CompositeAxisDomain } from "../../../components/chart/composite/types";
22
+ import { useAsyncResource, useAutoRefresh, usePluginAppActions, usePluginPaneState } from "../../../public/react";
13
23
  import { useAppSelector, usePaneSettingValue, usePaneStateValue } from "../../../state/app/context";
14
24
  import { colors } from "../../../theme/colors";
25
+ import type { ResolvedSeries } from "../../../time-series/types";
15
26
  import { TICKER_RESEARCH_PANE_ID } from "../../../types/config";
16
27
  import type { PaneProps } from "../../../types/plugin";
17
- import { Box, TextAttributes, type ScrollBoxRenderable } from "../../../ui";
18
- import { formatCompact, formatNumber } from "../../../utils/format";
28
+ import { Box, Text, TextAttributes, type ScrollBoxRenderable } from "../../../ui";
29
+ import { MISSING_VALUE, formatCompact, formatNumber } from "../../../utils/format";
19
30
  import { isPlainKey } from "../../../utils/keyboard";
20
31
  import { usePluginTickerActions } from "../../runtime";
21
32
  import { ScannerDeniedState } from "./denied";
@@ -28,6 +39,8 @@ import {
28
39
  flowEmptyState,
29
40
  flowHistoryQuery,
30
41
  flowRowsSpanDays,
42
+ flowScopeSymbol,
43
+ flowSessionSearch,
31
44
  keepFlowPrints,
32
45
  mergeFlowRows,
33
46
  formatFlowExpiry,
@@ -40,23 +53,51 @@ import {
40
53
  type FlowFilters,
41
54
  type FlowKind,
42
55
  type FlowMinPremium,
56
+ type FlowScope,
43
57
  type FlowSide,
44
58
  type FlowUniverse,
45
59
  type FlowVolOi,
46
60
  } from "./flow-model";
61
+ import {
62
+ FLOW_SESSION_LIVE_REFRESH_MS,
63
+ flowStrikeRows,
64
+ flowTideMoves,
65
+ flowTidePoints,
66
+ flowTideRows,
67
+ formatFlowSessionDate,
68
+ formatSignedFlowPremium,
69
+ formatStrikeExpiry,
70
+ formatTideTime,
71
+ loadFlowSession,
72
+ sortFlowStrikes,
73
+ type FlowStrikeRow,
74
+ type FlowStrikeSort,
75
+ type FlowTideRow,
76
+ } from "./flow-session";
77
+
78
+ type FlowTab = "prints" | "tide" | "strikes";
47
79
 
48
- function buildColumns(width: number, dated: boolean): DataTableColumn[] {
80
+ /** The tabs a pane offers: the strike breakdown is one name's. */
81
+ function flowTabs(symbol: string | null): Array<{ label: string; value: FlowTab }> {
82
+ return [
83
+ { label: "Prints", value: "prints" },
84
+ { label: "Tide", value: "tide" },
85
+ ...(symbol ? [{ label: "Strikes", value: "strikes" as const }] : []),
86
+ ];
87
+ }
88
+
89
+ function buildColumns(width: number, dated: boolean, ticker: boolean): DataTableColumn[] {
49
90
  // Prints from earlier days carry their date ("09/22 15:04"), so the time
50
- // column widens for them.
51
- const fixed = { time: dated ? 11 : 8, ticker: 7, type: 8, strike: 8, exp: 6, side: 5, size: 7, prem: 7, volOi: 6 };
91
+ // column widens for them. One name's pane names it in its title, not a column.
92
+ const fixed = { time: dated ? 11 : 8, ticker: ticker ? 7 : 0, type: 8, strike: 8, exp: 6, side: 5, size: 7, prem: 7, volOi: 6 };
52
93
  const total = Object.values(fixed).reduce((sum, value) => sum + value, 0);
53
94
  // Table chrome is one gap per column, the table's extra gutter where
54
95
  // right-aligned EXP meets left-aligned SIDE, two cells of padding, and the scrollbar.
55
- const slack = Math.max(0, width - total - 9 - 1 - 2 - 1);
96
+ const slack = Math.max(0, width - total - (ticker ? 9 : 8) - 1 - 2 - 1);
56
97
  return [
57
98
  { id: "time", label: "TIME", width: fixed.time, align: "left" },
58
- { id: "ticker", label: "TICKER", width: fixed.ticker + Math.min(4, slack), align: "left" },
59
- { id: "type", label: "TYPE", width: fixed.type, align: "left" },
99
+ ...(ticker ? [{ id: "ticker", label: "TICKER", width: fixed.ticker + Math.min(4, slack), align: "left" as const }] : []),
100
+ { id: "type", label: "TYPE", width: fixed.type + (ticker ? 0 : Math.min(4, slack)), align: "left" },
60
101
  { id: "strike", label: "STRIKE", width: fixed.strike, align: "right" },
61
102
  { id: "expiry", label: "EXP", width: fixed.exp, align: "right" },
62
103
  { id: "side", label: "SIDE", width: fixed.side, align: "left" },
@@ -66,14 +107,43 @@ function buildColumns(width: number, dated: boolean): DataTableColumn[] {
66
107
  ];
67
108
  }
68
109
 
110
+ const TIDE_COLUMNS: DataTableColumn[] = [
111
+ { id: "time", label: "TIME", width: 6, align: "left" },
112
+ { id: "netCalls", label: "NET CALLS", width: 10, align: "right" },
113
+ { id: "netPuts", label: "NET PUTS", width: 10, align: "right" },
114
+ { id: "net", label: "NET PREM", width: 10, align: "right" },
115
+ { id: "change", label: "CHG", width: 9, align: "right" },
116
+ ];
117
+
118
+ const STRIKE_COLUMNS: Array<DataTableColumn & { id: FlowStrikeSort }> = [
119
+ { id: "expiry", label: "EXP", width: 8, align: "left" },
120
+ { id: "strike", label: "STRIKE", width: 8, align: "right" },
121
+ { id: "callPremium", label: "CALLS", width: 8, align: "right" },
122
+ { id: "callAsk", label: "C ASK", width: 8, align: "right" },
123
+ { id: "callBid", label: "C BID", width: 8, align: "right" },
124
+ { id: "putPremium", label: "PUTS", width: 8, align: "right" },
125
+ { id: "putAsk", label: "P ASK", width: 8, align: "right" },
126
+ { id: "putBid", label: "P BID", width: 8, align: "right" },
127
+ { id: "net", label: "NET", width: 8, align: "right" },
128
+ ];
129
+ /** The totals go before the side columns that make up the breakdown. */
130
+ const STRIKE_DROP_ORDER: FlowStrikeSort[] = ["putPremium", "callPremium"];
131
+
69
132
  function sideColor(side: ScannerFlowEvent["side"]): string {
70
133
  if (side === "ask") return colors.positive;
71
134
  if (side === "bid") return colors.negative;
72
135
  return side === "mid" ? colors.text : colors.textDim;
73
136
  }
74
137
 
138
+ function signedColor(value: number): string {
139
+ return value > 0 ? colors.positive : value < 0 ? colors.negative : colors.textDim;
140
+ }
141
+
75
142
  // Stable table adapters so memoized rows survive feed ticks.
76
143
  const eventKey = (event: ScannerFlowEvent) => event.id;
144
+ const tideKey = (row: FlowTideRow) => String(row.at);
145
+ const tideDate = (row: FlowTideRow) => new Date(row.at);
146
+ const strikeKey = (row: FlowStrikeRow) => row.id;
77
147
 
78
148
  function renderCell(
79
149
  event: ScannerFlowEvent,
@@ -111,14 +181,84 @@ function renderCell(
111
181
  }
112
182
  }
113
183
 
184
+ function renderTideCell(row: FlowTideRow, column: DataTableColumn): DataTableCell {
185
+ if (column.id === "time") return { text: formatTideTime(row.at), value: new Date(row.at).toISOString(), color: colors.textDim };
186
+ const value = row[column.id as "netCalls" | "netPuts" | "net" | "change"];
187
+ return {
188
+ text: formatSignedFlowPremium(value),
189
+ value,
190
+ // Coloured by what it means for the name: put buying is bearish, put selling bullish.
191
+ color: signedColor(column.id === "netPuts" ? -value : value),
192
+ ...(column.id === "net" ? { attributes: TextAttributes.BOLD } : {}),
193
+ };
194
+ }
195
+
196
+ function renderStrikeCell(row: FlowStrikeRow, column: DataTableColumn): DataTableCell {
197
+ switch (column.id) {
198
+ case "expiry":
199
+ return { text: formatStrikeExpiry(row.expiry), value: row.expiry, color: colors.textDim };
200
+ case "strike":
201
+ return { text: formatNumber(row.strike, row.strike % 1 === 0 ? 0 : 2), value: row.strike, color: colors.textBright };
202
+ case "net":
203
+ return { text: formatSignedFlowPremium(row.net), value: row.net, color: signedColor(row.net), attributes: TextAttributes.BOLD };
204
+ default: {
205
+ const value = row[column.id as "callPremium" | "callAsk" | "callBid" | "putPremium" | "putAsk" | "putBid"];
206
+ const call = column.id.startsWith("call");
207
+ const total = column.id === "callPremium" || column.id === "putPremium";
208
+ return {
209
+ text: value > 0 ? formatFlowPremium(value) : MISSING_VALUE,
210
+ value,
211
+ color: value <= 0 ? colors.textDim : total ? (call ? colors.positive : colors.negative) : colors.text,
212
+ };
213
+ }
214
+ }
215
+ }
216
+
217
+ /** Amounts on the tide's axis, in one unit for the whole axis: `$12M`, `-$4.5M`. */
218
+ function formatPremiumAxis(value: number, domain: CompositeAxisDomain): string {
219
+ const text = formatCompactAxis(value, domain);
220
+ if (text === "0") return "$0";
221
+ return text.startsWith("-") ? `-$${text.slice(1)}` : `$${text}`;
222
+ }
223
+
224
+ /** The tide's lines read in the user's own clock, as the TIME columns do. */
225
+ const LOCAL_TIME_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
226
+
227
+ function tideSeries(rows: readonly FlowTideRow[], bucketMs: number): ResolvedSeries[] {
228
+ const points = flowTidePoints(rows, bucketMs);
229
+ const line = (id: string, label: string, color: string, value: (point: (typeof points)[number]) => number): ResolvedSeries => ({
230
+ ...staticSeries(points.map((point) => scalarPoint(new Date(point.at), value(point))), { id, label, color }),
231
+ timeBasis: { kind: "market", timeZone: LOCAL_TIME_ZONE, cadenceMs: bucketMs },
232
+ observationKind: "market",
233
+ });
234
+ // Named as the table's columns are; the net line is the tide itself.
235
+ return [
236
+ line("flow-net", "Net prem", colors.textBright, (point) => point.net),
237
+ line("flow-net-calls", "Net calls", colors.positive, (point) => point.netCalls),
238
+ line("flow-net-puts", "Net puts", colors.negative, (point) => point.netPuts),
239
+ ];
240
+ }
241
+
114
242
  function FlowPane({ focused, width, height }: PaneProps) {
115
243
  const feed = useFlowFeed();
116
244
  const { pinTicker } = usePluginTickerActions();
245
+ const { createPaneFromTemplate } = usePluginAppActions();
117
246
  const [selectedId, setSelectedId] = useState<string | null>(null);
118
247
  // Publish the selection on this pane, not the portfolio: moving the
119
248
  // portfolio's cursor scrolled it to a ticker the user never picked there.
120
249
  const [, setCursorSymbol] = usePaneStateValue<string | null>("cursorSymbol", null);
121
250
 
251
+ // `FLOW NVDA` is one name's flow; plain FLOW is the market.
252
+ const [symbolSetting] = usePaneSettingValue<string>("symbol", "");
253
+ const symbol = flowScopeSymbol(symbolSetting);
254
+ const tabItems = useMemo(() => flowTabs(symbol), [symbol]);
255
+ const [initialTab] = usePaneSettingValue<string>("tab", "prints");
256
+ const [savedTab, setTab] = usePluginPaneState<string>("flow:tab", initialTab);
257
+ const tab: FlowTab = tabItems.find((item) => item.value === savedTab)?.value ?? "prints";
258
+ // Signed out or without Pro, every tab is the same wall.
259
+ const { strip, rows: tabRows } = usePaneTabs(!feed.denied ? { tabs: tabItems, activeValue: tab, onSelect: setTab, focused, dense: true } : null);
260
+ const bodyHeight = Math.max(3, height - tabRows);
261
+
122
262
  const [minPremium, setMinPremium] = usePaneSettingValue<FlowMinPremium>("minPremium", DEFAULT_FLOW_FILTERS.minPremium);
123
263
  const [side, setSide] = usePaneSettingValue<FlowSide>("side", DEFAULT_FLOW_FILTERS.side);
124
264
  const [kind, setKind] = usePaneSettingValue<FlowKind>("kind", DEFAULT_FLOW_FILTERS.kind);
@@ -128,9 +268,10 @@ function FlowPane({ focused, width, height }: PaneProps) {
128
268
 
129
269
  const trackedSymbols = useAppSelector((state) => state.tickers);
130
270
  const watchlist = useMemo(
131
- () => new Set([...trackedSymbols.keys()].map((symbol) => symbol.toUpperCase())),
271
+ () => new Set([...trackedSymbols.keys()].map((entry) => entry.toUpperCase())),
132
272
  [trackedSymbols],
133
273
  );
274
+ const scope = useMemo<FlowScope>(() => ({ symbol, watchlist }), [symbol, watchlist]);
134
275
 
135
276
  const filters = useMemo<FlowFilters>(
136
277
  () => ({ minPremium, side, kind, volOi, expiry, universe }),
@@ -146,11 +287,11 @@ function FlowPane({ focused, width, height }: PaneProps) {
146
287
  return keptRef.current;
147
288
  }, [feed.payload?.events]);
148
289
  const liveRows = useMemo(
149
- () => filterFlowEvents(kept, filters, watchlist),
150
- [filters, kept, watchlist],
290
+ () => filterFlowEvents(kept, filters, scope),
291
+ [filters, kept, scope],
151
292
  );
152
293
 
153
- const historyQuery = useMemo(() => flowHistoryQuery(filters, watchlist), [filters, watchlist]);
294
+ const historyQuery = useMemo(() => flowHistoryQuery(filters, scope), [filters, scope]);
154
295
  const oldestKept = kept.at(-1);
155
296
  const oldestLive = useMemo(
156
297
  () => (oldestKept ? { at: oldestKept.at, id: oldestKept.id } : null),
@@ -159,32 +300,58 @@ function FlowPane({ focused, width, height }: PaneProps) {
159
300
  const history = useFlowHistory(historyQuery, oldestLive, !!feed.payload && !feed.denied);
160
301
  const events = useMemo(() => mergeFlowRows(liveRows, history.events), [history.events, liveRows]);
161
302
 
303
+ // The session summary is read only while Tide or Strikes is on screen: it
304
+ // loads when one first opens, follows the filters and refreshes while shown,
305
+ // and holds still behind Prints.
306
+ const sessionShown = tab !== "prints";
307
+ const sessionSearch = useMemo(() => flowSessionSearch(filters, scope), [filters, scope]);
308
+ const [shownSearch, setShownSearch] = useState<string | null>(sessionShown ? sessionSearch : null);
309
+ useEffect(() => {
310
+ if (sessionShown) setShownSearch(sessionSearch);
311
+ }, [sessionSearch, sessionShown]);
312
+ const sessionLoader = useMemo(
313
+ () => (shownSearch === null ? null : () => loadFlowSession(shownSearch)),
314
+ [shownSearch],
315
+ );
316
+ const session = useAsyncResource<ScannerFlowSession>(
317
+ sessionLoader && feed.payload && !feed.denied ? sessionLoader : null,
318
+ );
319
+ useAutoRefresh(session.updatedAt, () => void session.load(), {
320
+ intervalMs: sessionShown && feed.payload?.status === "live" ? FLOW_SESSION_LIVE_REFRESH_MS : null,
321
+ });
322
+
162
323
  const scrollRef = useRef<ScrollBoxRenderable | null>(null);
163
- const canLoadOlder = history.hasMore && !history.loading && !history.error && !!feed.payload && !feed.denied;
324
+ const canLoadOlder = tab === "prints" && history.hasMore && !history.loading && !history.error && !!feed.payload && !feed.denied;
164
325
  const onBodyScrollActivity = useTableLoadMore(scrollRef, canLoadOlder, history.loadMore);
165
326
  // A strict filter or a quiet tape should still fill the pane, including
166
327
  // before the open, when every row is recorded.
167
- const bodyRows = Math.max(1, height - 3);
328
+ const bodyRows = Math.max(1, bodyHeight - 3);
168
329
  useEffect(() => {
169
330
  if (canLoadOlder && events.length < bodyRows) history.loadMore();
170
331
  }, [bodyRows, canLoadOlder, events.length, history.loadMore]);
171
332
 
172
333
  const emptyState = useMemo(
173
- () => flowEmptyState(kept.length, liveRows.length, feed.payload?.status),
174
- [feed.payload?.status, kept.length, liveRows.length],
334
+ () => flowEmptyState(kept.length, liveRows.length, feed.payload?.status, symbol),
335
+ [feed.payload?.status, kept.length, liveRows.length, symbol],
175
336
  );
176
337
 
177
- const historyFooter = useMemo<PaneFooterSegment | null>(() => {
338
+ const statusFooter = useMemo<PaneFooterSegment | null>(() => {
339
+ if (tab !== "prints") {
340
+ // A failed refresh keeps the last summary on screen and says so here.
341
+ return session.error && session.data
342
+ ? { id: "flow-session", parts: [{ text: "session totals not refreshed", tone: "warning" }] }
343
+ : null;
344
+ }
178
345
  if (history.error) {
179
346
  return { id: "flow-history", parts: [{ text: "older prints unavailable", tone: "warning" }] };
180
347
  }
181
348
  if (history.loading) return { id: "flow-history", parts: [{ text: "loading older prints", tone: "muted" }] };
182
349
  return null;
183
- }, [history.error, history.loading]);
184
- useScannerStatusFooter("flow", feed, focused, historyFooter);
350
+ }, [history.error, history.loading, session.data, session.error, tab]);
351
+ useScannerStatusFooter("flow", feed, focused, statusFooter);
185
352
 
186
353
  const dated = useMemo(() => flowRowsSpanDays(events), [events]);
187
- const columns = useMemo(() => buildColumns(width, dated), [dated, width]);
354
+ const columns = useMemo(() => buildColumns(width, dated, !symbol), [dated, symbol, width]);
188
355
 
189
356
  const handleRootKeyDown = useCallback((event: DataTableKeyEvent) => {
190
357
  if (!isPlainKey(event, "r") || !history.error) return false;
@@ -193,6 +360,7 @@ function FlowPane({ focused, width, height }: PaneProps) {
193
360
  history.retry();
194
361
  return true;
195
362
  }, [history.error, history.retry]);
363
+ usePaneRefreshKey(() => void session.reload(), { focused, enabled: tab !== "prints" && !feed.denied });
196
364
 
197
365
  const handleSelect = useCallback((event: ScannerFlowEvent) => {
198
366
  setSelectedId(event.id);
@@ -203,57 +371,191 @@ function FlowPane({ focused, width, height }: PaneProps) {
203
371
  return <ScannerDeniedState reason={feed.deniedReason} placement="flow-wall" width={width} height={height} />;
204
372
  }
205
373
 
374
+ const queryBar = (
375
+ <QueryBar
376
+ width={width}
377
+ filters={[
378
+ { id: "premium", label: "Prem", value: minPremium, defaultValue: DEFAULT_FLOW_FILTERS.minPremium,
379
+ options: FLOW_FILTER_OPTIONS.minPremium, onChange: setMinPremium },
380
+ { id: "side", label: "Side", value: side, defaultValue: DEFAULT_FLOW_FILTERS.side,
381
+ options: FLOW_FILTER_OPTIONS.side, onChange: setSide },
382
+ { id: "kind", label: "Kind", value: kind, defaultValue: DEFAULT_FLOW_FILTERS.kind,
383
+ options: FLOW_FILTER_OPTIONS.kind, onChange: setKind },
384
+ { id: "voloi", label: "V/OI", value: volOi, defaultValue: DEFAULT_FLOW_FILTERS.volOi,
385
+ options: FLOW_FILTER_OPTIONS.volOi, onChange: setVolOi },
386
+ { id: "expiry", label: "Exp", value: expiry, defaultValue: DEFAULT_FLOW_FILTERS.expiry,
387
+ options: FLOW_FILTER_OPTIONS.expiry, onChange: setExpiry },
388
+ // One name's pane is already narrower than any universe.
389
+ ...(symbol ? [] : [{ id: "universe", label: "Univ", value: universe, defaultValue: DEFAULT_FLOW_FILTERS.universe,
390
+ options: FLOW_FILTER_OPTIONS.universe, onChange: setUniverse }]),
391
+ ]}
392
+ />
393
+ );
394
+
395
+ let body: ReactNode;
396
+ if (tab === "tide") {
397
+ body = (
398
+ <FlowTideView
399
+ focused={focused} width={width} height={bodyHeight} query={queryBar}
400
+ session={session.data} loading={session.loading} error={session.error}
401
+ onRetry={session.reload}
402
+ />
403
+ );
404
+ } else if (tab === "strikes" && symbol) {
405
+ body = (
406
+ <FlowStrikesView
407
+ focused={focused} width={width} height={bodyHeight} query={queryBar} symbol={symbol}
408
+ session={session.data} loading={session.loading} error={session.error}
409
+ onRetry={session.reload}
410
+ onOpenChain={(row) => createPaneFromTemplate("options-pane", { symbol, values: { expiration: row.expiry } })}
411
+ />
412
+ );
413
+ } else {
414
+ body = (
415
+ <>
416
+ {queryBar}
417
+ <DataTableView<ScannerFlowEvent>
418
+ focused={focused}
419
+ selection={{
420
+ kind: "id",
421
+ selectedId,
422
+ getId: (event) => event.id,
423
+ onChange: (_id, event) => handleSelect(event),
424
+ }}
425
+ rootWidth={width}
426
+ rootHeight={Math.max(2, bodyHeight - 1)}
427
+ columns={columns}
428
+ items={events}
429
+ sortColumnId={null}
430
+ sortDirection="desc"
431
+ getItemKey={eventKey}
432
+ onActivate={(event) => pinTicker(event.underlying, { floating: true, paneType: TICKER_RESEARCH_PANE_ID })}
433
+ renderCell={renderCell}
434
+ selectedTextOverridesCellColor
435
+ emptyContent={
436
+ !feed.payload
437
+ ? <PaneStatusBody loading loadingLabel="Waiting for the scanner..." />
438
+ : events.length === 0 && (history.loading || (!!symbol && history.hasMore && !history.error))
439
+ ? <PaneStatusBody loading loadingLabel="Loading recorded prints..." />
440
+ : undefined
441
+ }
442
+ emptyStateTitle={emptyState.title}
443
+ emptyStateHint={emptyState.hint}
444
+ onRootKeyDown={handleRootKeyDown}
445
+ onBodyScrollActivity={onBodyScrollActivity}
446
+ scrollRef={scrollRef}
447
+ />
448
+ </>
449
+ );
450
+ }
451
+
206
452
  return (
207
453
  <Box flexDirection="column" width={width} height={height}>
208
- <QueryBar
209
- width={width}
210
- filters={[
211
- { id: "premium", label: "Prem", value: minPremium, defaultValue: DEFAULT_FLOW_FILTERS.minPremium,
212
- options: FLOW_FILTER_OPTIONS.minPremium, onChange: setMinPremium },
213
- { id: "side", label: "Side", value: side, defaultValue: DEFAULT_FLOW_FILTERS.side,
214
- options: FLOW_FILTER_OPTIONS.side, onChange: setSide },
215
- { id: "kind", label: "Kind", value: kind, defaultValue: DEFAULT_FLOW_FILTERS.kind,
216
- options: FLOW_FILTER_OPTIONS.kind, onChange: setKind },
217
- { id: "voloi", label: "V/OI", value: volOi, defaultValue: DEFAULT_FLOW_FILTERS.volOi,
218
- options: FLOW_FILTER_OPTIONS.volOi, onChange: setVolOi },
219
- { id: "expiry", label: "Exp", value: expiry, defaultValue: DEFAULT_FLOW_FILTERS.expiry,
220
- options: FLOW_FILTER_OPTIONS.expiry, onChange: setExpiry },
221
- { id: "universe", label: "Univ", value: universe, defaultValue: DEFAULT_FLOW_FILTERS.universe,
222
- options: FLOW_FILTER_OPTIONS.universe, onChange: setUniverse },
223
- ]}
224
- />
225
- <DataTableView<ScannerFlowEvent>
454
+ {strip}
455
+ {body}
456
+ </Box>
457
+ );
458
+ }
459
+
460
+ interface SessionViewProps {
461
+ focused: boolean;
462
+ width: number;
463
+ height: number;
464
+ query: ReactNode;
465
+ session: ScannerFlowSession | null;
466
+ loading: boolean;
467
+ error: string | null;
468
+ onRetry: () => unknown;
469
+ }
470
+
471
+ /** Until the first summary answers, the body loads or says why it failed; after that a failed refresh is the footer's. */
472
+ function sessionStatus(props: SessionViewProps, subject: string) {
473
+ return props.session ? undefined : { loading: props.loading, error: props.error, subject, retry: { onRetry: props.onRetry } };
474
+ }
475
+
476
+ /**
477
+ * The net-premium tide through the session: call premium bought at the ask
478
+ * less call premium sold at the bid, less the same for puts, bucket by
479
+ * bucket. The chart draws the running totals; the table adds what each
480
+ * bucket moved them by.
481
+ */
482
+ function FlowTideView(props: SessionViewProps) {
483
+ const { focused, width, height, query, session } = props;
484
+ const rows = useMemo(() => flowTideRows(session), [session]);
485
+ // Newest first, like the prints; the chart keeps every bucket, the table the ones that moved it.
486
+ const tableRows = useMemo(() => flowTideMoves(rows).reverse(), [rows]);
487
+ const bucketMs = session?.bucketMs ?? 300_000;
488
+ const [selected, setSelected] = usePluginPaneState<string | null>("flow:tide-bucket", null);
489
+ const selectedId = tableRows.some((row) => tideKey(row) === selected) ? selected : tableRows[0] ? tideKey(tableRows[0]) : null;
490
+ const link = useChartTableSelection({
491
+ rows: tableRows, getId: tideKey, getDate: tideDate, selectedId, onSelect: setSelected, focused,
492
+ });
493
+ const series = useMemo(() => tideSeries(rows, bucketMs), [bucketMs, rows]);
494
+ const sessionDate = formatFlowSessionDate(session?.session);
495
+ // Two points are not a trend: under three buckets the table has the pane.
496
+ const chart: ChartTableChart | null = rows.length >= 3 ? {
497
+ series,
498
+ formatValue: (value) => formatSignedFlowPremium(value),
499
+ formatAxisValue: formatPremiumAxis,
500
+ viewport: { start: new Date(rows[0]!.at), end: new Date(rows.at(-1)!.at + bucketMs) },
501
+ ...(sessionDate ? { legendAccessory: <Text fg={colors.textMuted}>{sessionDate}</Text>, legendAccessoryWidth: sessionDate.length } : {}),
502
+ remoteKind: "flow-tide",
503
+ ...link,
504
+ } : !session && props.loading ? { loading: true } : null;
505
+ return (
506
+ <DataTableView<FlowTideRow>
507
+ focused={focused}
508
+ rootWidth={width}
509
+ rootHeight={height}
510
+ columns={TIDE_COLUMNS}
511
+ items={tableRows}
512
+ sortColumnId={null}
513
+ sortDirection="desc"
514
+ getItemKey={tideKey}
515
+ selection={{ kind: "id", selectedId, getId: tideKey, onChange: (id) => setSelected(id) }}
516
+ onActivate={(row) => setSelected(tideKey(row))}
517
+ renderCell={renderTideCell}
518
+ selectedTextOverridesCellColor
519
+ status={sessionStatus(props, "the session's net premium")}
520
+ emptyStateTitle="No prints under these filters this session."
521
+ rootBefore={<ChartTableHeader width={width} height={height} tableRows={tableRows.length} tableColumns={TIDE_COLUMNS} query={query} chart={chart} />}
522
+ />
523
+ );
524
+ }
525
+
526
+ /** One name's session premium by expiry and strike, split by side. Enter opens the chain at that expiry. */
527
+ function FlowStrikesView(props: SessionViewProps & { symbol: string; onOpenChain: (row: FlowStrikeRow) => void }) {
528
+ const { focused, width, height, query, session } = props;
529
+ const [sort, setSort] = usePluginPaneState<{ column: FlowStrikeSort | null; direction: "asc" | "desc" }>(
530
+ "flow:strike-sort", { column: null, direction: "desc" });
531
+ const rows = useMemo(() => sortFlowStrikes(flowStrikeRows(session), sort.column, sort.direction), [session, sort]);
532
+ const columns = useMemo(() => fitChartTableColumns(STRIKE_COLUMNS, width, STRIKE_DROP_ORDER), [width]);
533
+ const [selected, setSelected] = usePluginPaneState<string | null>("flow:strike", null);
534
+ const selectedId = rows.some((row) => row.id === selected) ? selected : rows[0]?.id ?? null;
535
+ return (
536
+ <>
537
+ {query}
538
+ <DataTableView<FlowStrikeRow, DataTableColumn & { id: FlowStrikeSort }>
226
539
  focused={focused}
227
- selection={{
228
- kind: "id",
229
- selectedId,
230
- getId: (event) => event.id,
231
- onChange: (_id, event) => handleSelect(event),
232
- }}
233
540
  rootWidth={width}
234
541
  rootHeight={Math.max(2, height - 1)}
235
542
  columns={columns}
236
- items={events}
237
- sortColumnId={null}
238
- sortDirection="desc"
239
- getItemKey={eventKey}
240
- onActivate={(event) => pinTicker(event.underlying, { floating: true, paneType: TICKER_RESEARCH_PANE_ID })}
241
- renderCell={renderCell}
543
+ items={rows}
544
+ sortColumnId={sort.column}
545
+ sortDirection={sort.direction}
546
+ onHeaderClick={(column) => setSort((current) => ({
547
+ column: column as FlowStrikeSort,
548
+ direction: current.column === column && current.direction === "desc" ? "asc" : "desc",
549
+ }))}
550
+ getItemKey={strikeKey}
551
+ selection={{ kind: "id", selectedId, getId: strikeKey, onChange: (id) => setSelected(id) }}
552
+ onActivate={props.onOpenChain}
553
+ renderCell={renderStrikeCell}
242
554
  selectedTextOverridesCellColor
243
- emptyContent={
244
- !feed.payload
245
- ? <PaneStatusBody loading loadingLabel="Waiting for the scanner..." />
246
- : events.length === 0 && history.loading
247
- ? <PaneStatusBody loading loadingLabel="Loading recorded prints..." />
248
- : undefined
249
- }
250
- emptyStateTitle={emptyState.title}
251
- emptyStateHint={emptyState.hint}
252
- onRootKeyDown={handleRootKeyDown}
253
- onBodyScrollActivity={onBodyScrollActivity}
254
- scrollRef={scrollRef}
555
+ status={sessionStatus(props, `${props.symbol} strikes`)}
556
+ emptyStateTitle={`No ${props.symbol} prints under these filters this session.`}
255
557
  />
256
- </Box>
558
+ </>
257
559
  );
258
560
  }
259
561