gloomberb 0.16.2 → 0.16.4

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 (527) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -1,12 +1,44 @@
1
+ import type { IssuerListingParams } from "../../../api-client/paths";
2
+ import type { DataProvider } from "../../../types/data-provider";
1
3
  import type { HeadlessPaneContext } from "../../../types/headless";
2
4
  import type { TimeRange } from "../../../time-series/range";
3
5
  import { clipPriceHistoryToRange } from "../../../time-series/history-window";
4
- import { parsePublicTickerKey } from "../../../utils/exchanges";
6
+ import { canonicalExchange, parsePublicTickerKey } from "../../../utils/exchanges";
7
+ import { nonUsSecListingVenue } from "../../../utils/sec";
5
8
 
6
9
  export async function resolveHeadlessInstrument(ctx: HeadlessPaneContext, symbol: string) {
7
10
  return ctx.resolveInstrument ? ctx.resolveInstrument(symbol) : parsePublicTickerKey(symbol);
8
11
  }
9
12
 
13
+ /**
14
+ * The venue and company an issuer lookup names for a listing abroad: the
15
+ * venue from the key or the saved listing, the company from the listing's own
16
+ * quote. A US listing or a symbol with no venue names neither.
17
+ */
18
+ export async function resolveIssuerListing(
19
+ key: string,
20
+ exchange: string | undefined,
21
+ marketData: Pick<DataProvider, "getQuote">,
22
+ ): Promise<IssuerListingParams> {
23
+ const venue = nonUsSecListingVenue(key, exchange);
24
+ if (!venue) return {};
25
+ try {
26
+ const quote = await marketData.getQuote(parsePublicTickerKey(key).symbol, venue);
27
+ const quoteVenue = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
28
+ // A quote that prices another venue names another listing's company.
29
+ const name = !quoteVenue || quoteVenue === venue ? quote.name?.trim() : undefined;
30
+ return name ? { exchange: venue, name } : { exchange: venue };
31
+ } catch {
32
+ return { exchange: venue };
33
+ }
34
+ }
35
+
36
+ /** `resolveIssuerListing` for a report's ticker, with the venue of the saved listing when the key has none. */
37
+ export async function resolveHeadlessIssuerListing(ctx: HeadlessPaneContext, key: string): Promise<IssuerListingParams> {
38
+ const { exchange } = await resolveHeadlessInstrument(ctx, key);
39
+ return resolveIssuerListing(key, exchange, ctx.marketData);
40
+ }
41
+
10
42
  export async function loadHeadlessFinancials(ctx: HeadlessPaneContext, key: string) {
11
43
  const { symbol, exchange } = await resolveHeadlessInstrument(ctx, key);
12
44
  return ctx.marketData.getTickerFinancials(symbol, exchange ?? "");
@@ -0,0 +1,260 @@
1
+ import { ChoiceDialog, TextPromptDialog, usePaneMenuItems } from "../../../components";
2
+ import { createFormCollectionActions } from "../../../components/form-modal/deps";
3
+ import { t, tf } from "../../../i18n";
4
+ import { usePluginAppActions } from "../../../public/react";
5
+ import { useAppDispatch, useAppGetState, useAppSelector } from "../../../state/app/context";
6
+ import { formatTickerListInput, MAX_TICKER_LIST_SIZE } from "../../../tickers/list";
7
+ import type { TickerMetadata, TickerRecord } from "../../../types/ticker";
8
+ import { useOptionalDialog, type PromptContext } from "../../../ui/dialog";
9
+ import { canonicalExchange, publicTickerKey } from "../../../utils/exchanges";
10
+ import { slugifyName } from "../../../utils/slugify";
11
+ import { getSharedRegistry } from "../../registry";
12
+ import { MAX_CORRELATION_TICKERS } from "../correlation/settings";
13
+ import { ROTATION_LIMIT } from "../relative-rotation/model";
14
+ import { SHORT_WATCH_LIMIT } from "../short-interest/watch-model";
15
+
16
+ /** One US-listed member of a theme or fund, in the order its list shows it. */
17
+ export interface MemberListing {
18
+ symbol: string;
19
+ name?: string | null;
20
+ exchange?: string | null;
21
+ }
22
+
23
+ /** The members a pane shows, and what to call them. */
24
+ export interface MemberList {
25
+ /** Names the members in dialog titles: a theme's name or a fund's ticker. */
26
+ title: string;
27
+ /** The name the save dialog suggests. */
28
+ watchlistName: string;
29
+ members: readonly MemberListing[];
30
+ /** A search narrowing the list: what was typed and how many members the whole list has. */
31
+ search?: { query: string; total: number } | null;
32
+ }
33
+
34
+ interface MemberDestination {
35
+ templateId: string;
36
+ label: string;
37
+ /** The most symbols the destination takes; more would be refused or cut off there. */
38
+ limit: number;
39
+ minimum: number;
40
+ }
41
+
42
+ /**
43
+ * Functions that take a list of tickers through `options.symbols`. CORR and
44
+ * RIPL resolve the list the way the command bar does, which takes ten
45
+ * tickers; RIPL's own setting takes more, but not through a new pane.
46
+ */
47
+ export const MEMBER_DESTINATIONS: readonly MemberDestination[] = [
48
+ { templateId: "relative-rotation-rrg", label: "Relative Rotation (RRG)", limit: ROTATION_LIMIT, minimum: 1 },
49
+ { templateId: "correlation-pane", label: "Correlation Matrix (CORR)", limit: MAX_CORRELATION_TICKERS, minimum: 2 },
50
+ { templateId: "short-watch-pane", label: "Short Squeeze Watch (SIW)", limit: SHORT_WATCH_LIMIT, minimum: 1 },
51
+ { templateId: "earnings-ripple-pane", label: "Earnings Ripple (RIPL)", limit: MAX_TICKER_LIST_SIZE, minimum: 1 },
52
+ ];
53
+
54
+ /**
55
+ * A watchlist is a list to read and follow; a whole Russell 2000 stays in
56
+ * MEMB. Larger lists save their first 100 as listed.
57
+ */
58
+ export const MEMBER_WATCHLIST_LIMIT = 100;
59
+
60
+ /**
61
+ * Distinct members in list order, as `SYMBOL:EXCHANGE` when the list names the
62
+ * listing: CORR and RIPL resolve each one, and a bare CCJ also matches Xetra.
63
+ */
64
+ export function memberSymbols(members: readonly MemberListing[]): string[] {
65
+ const seen = new Set<string>();
66
+ return members.flatMap((member) => {
67
+ const symbol = member.symbol.trim().toUpperCase();
68
+ if (!symbol || seen.has(symbol)) return [];
69
+ seen.add(symbol);
70
+ const exchange = member.exchange?.trim().toUpperCase();
71
+ return [exchange ? `${symbol}:${exchange}` : symbol];
72
+ });
73
+ }
74
+
75
+ /**
76
+ * What each destination would open: the first `limit` symbols as listed, or
77
+ * disabled below its minimum. A disabled row cannot be selected, so its reason
78
+ * sits beside the label rather than in the description under the list.
79
+ */
80
+ export function memberDestinationChoices(symbols: readonly string[], destinations: readonly MemberDestination[]) {
81
+ return destinations.map((destination) => {
82
+ const taken = symbols.slice(0, destination.limit);
83
+ const tooFew = taken.length < destination.minimum;
84
+ return {
85
+ id: destination.templateId,
86
+ label: destination.label,
87
+ symbols: taken,
88
+ disabled: tooFew,
89
+ detail: tooFew ? tf("needs {count}", { count: destination.minimum }) : undefined,
90
+ description: tooFew ? undefined
91
+ : taken.length < symbols.length ? tf("First {count} of {total} as listed", { count: taken.length, total: symbols.length })
92
+ : tf("All {count}", { count: taken.length }),
93
+ };
94
+ });
95
+ }
96
+
97
+ /**
98
+ * The dialog text for `count` distinct members. A search says what it matched
99
+ * and how many members the whole list has, so a saved or opened subset never
100
+ * reads as the whole fund.
101
+ */
102
+ function memberListText(list: Pick<MemberList, "title" | "search">, count: number) {
103
+ const query = list.search?.query.trim();
104
+ const search = query ? { query, total: list.search!.total } : null;
105
+ const saved = Math.min(count, MEMBER_WATCHLIST_LIMIT);
106
+ return {
107
+ openTitle: search ? tf("Open {title} members matching \"{query}\" in", { title: list.title, query: search.query })
108
+ : tf("Open {title} members in", { title: list.title }),
109
+ saveBody: count > MEMBER_WATCHLIST_LIMIT
110
+ ? search ? tf("The first {count} of the {matching} members matching \"{query}\", as listed.", { count: saved, matching: count, query: search.query })
111
+ : tf("The first {count} of {total} members, as listed.", { count: saved, total: count })
112
+ : search ? tf("{count} of {total} members match \"{query}\".", { count, total: search.total, query: search.query })
113
+ : tf("All {count} members.", { count }),
114
+ };
115
+ }
116
+
117
+ /** The requested name, numbered when a watchlist already has that name or id. */
118
+ export function uniqueWatchlistName(requested: string, watchlists: readonly { id: string; name: string }[]): string {
119
+ const base = requested.trim();
120
+ const taken = (name: string) => watchlists.some((list) =>
121
+ list.name.toLowerCase() === name.toLowerCase() || list.id === slugifyName(name, "watchlist"));
122
+ let name = base;
123
+ for (let suffix = 2; taken(name); suffix += 1) name = `${base} ${suffix}`;
124
+ return name;
125
+ }
126
+
127
+ /**
128
+ * Tickers to create on the watchlist and existing ones to add to it, first
129
+ * `MEMBER_WATCHLIST_LIMIT` members as listed. A saved record of the same
130
+ * symbol on another venue (CCJ on Xetra) stays as it is: the member's listing
131
+ * gets its own `SYMBOL:EXCHANGE` record, the way search opens a second listing.
132
+ */
133
+ export function planMemberWatchlist(
134
+ tickers: ReadonlyMap<string, TickerRecord>,
135
+ watchlistId: string,
136
+ members: readonly MemberListing[],
137
+ ): { create: TickerMetadata[]; update: TickerRecord[]; count: number } {
138
+ const seen = new Set<string>();
139
+ const create: TickerMetadata[] = [];
140
+ const update: TickerRecord[] = [];
141
+ for (const member of members) {
142
+ const symbol = member.symbol.trim().toUpperCase();
143
+ if (!symbol || seen.has(symbol)) continue;
144
+ if (seen.size >= MEMBER_WATCHLIST_LIMIT) break;
145
+ seen.add(symbol);
146
+ const exchange = member.exchange?.trim() ?? "";
147
+ let key = symbol;
148
+ let existing = tickers.get(symbol);
149
+ if (existing && exchange && existing.metadata.exchange
150
+ && canonicalExchange(existing.metadata.exchange) !== canonicalExchange(exchange)) {
151
+ key = publicTickerKey(symbol, exchange);
152
+ existing = tickers.get(key);
153
+ }
154
+ if (existing) {
155
+ if (!existing.metadata.watchlists.includes(watchlistId)) {
156
+ update.push({ ...existing, metadata: { ...existing.metadata, watchlists: [...existing.metadata.watchlists, watchlistId] } });
157
+ }
158
+ continue;
159
+ }
160
+ // Theme and fund members are US listings quoted in dollars. The sector is
161
+ // left to the ticker's profile, so a list never mixes two sector schemes.
162
+ create.push({ ticker: key, name: member.name || symbol, exchange, currency: "USD",
163
+ portfolios: [], watchlists: [watchlistId], positions: [], custom: {}, tags: [] });
164
+ }
165
+ return { create, update, count: seen.size };
166
+ }
167
+
168
+ /**
169
+ * Pane menu entries for a list of members (a theme's, a fund's): open them in
170
+ * RRG, CORR, SIW or RIPL, or save them as a watchlist. Functions of disabled
171
+ * plugins are left out; each destination gets as many as it takes.
172
+ */
173
+ export function useMembersMenu(registrationId: string, list: MemberList | null) {
174
+ const dialog = useOptionalDialog();
175
+ const { createPaneFromTemplate, notify } = usePluginAppActions();
176
+ const dispatch = useAppDispatch();
177
+ const getState = useAppGetState();
178
+ const disabledPlugins = useAppSelector((state) => state.config.disabledPlugins);
179
+ usePaneMenuItems(registrationId, () => {
180
+ const registry = getSharedRegistry();
181
+ const symbols = list ? memberSymbols(list.members) : [];
182
+ if (!list || !symbols.length || !dialog || !registry) return null;
183
+ const destinations = MEMBER_DESTINATIONS.filter((destination) => (
184
+ registry.paneTemplates.has(destination.templateId)
185
+ && !registry.getDisabledPaneTemplateOwner?.(destination.templateId, disabledPlugins)
186
+ ));
187
+ const choices = memberDestinationChoices(symbols, destinations);
188
+ const text = memberListText(list, symbols.length);
189
+ const openIn = () => {
190
+ void dialog.prompt<string>({
191
+ closeOnClickOutside: true,
192
+ content: (context: PromptContext<string>) => <ChoiceDialog {...context} title={text.openTitle}
193
+ choices={choices.map(({ id, label, description, detail, disabled }) => ({ id, label: t(label), description, detail, disabled }))} />,
194
+ }).then((templateId) => {
195
+ const choice = choices.find((item) => item.id === templateId && !item.disabled);
196
+ // CORR and RIPL resolve `arg` like the command bar; RRG and SIW read `symbols`.
197
+ if (choice) createPaneFromTemplate(choice.id, { symbols: choice.symbols, arg: formatTickerListInput(choice.symbols) });
198
+ }).catch(() => {});
199
+ };
200
+ const saveAsWatchlist = async (requested: string) => {
201
+ // The writes hold the app for about a second with a hundred names; let the dialog close on screen first.
202
+ await new Promise((resolve) => setTimeout(resolve, 0));
203
+ const name = uniqueWatchlistName(requested, getState().config.watchlists);
204
+ await createFormCollectionActions({ dataProvider: registry.marketData, dispatch, getState, pluginRegistry: registry,
205
+ tickerRepository: registry.tickerRepository }, (body, options) => {
206
+ if (options?.type === "error") notify({ body, type: "error" });
207
+ }).createWatchlist(name);
208
+ const watchlist = getState().config.watchlists.find((entry) => entry.name === name);
209
+ if (!watchlist) throw new Error(t("The watchlist was not created."));
210
+ const plan = planMemberWatchlist(getState().tickers, watchlist.id, list.members);
211
+ // One ticker that fails to save leaves the others on the list; the toast says how many made it.
212
+ let failed = 0;
213
+ const created: TickerRecord[] = [];
214
+ const saved: TickerRecord[] = [];
215
+ for (const metadata of plan.create) {
216
+ try {
217
+ const ticker = await registry.tickerRepository.createTicker(metadata);
218
+ created.push(ticker);
219
+ saved.push(ticker);
220
+ } catch {
221
+ failed += 1;
222
+ }
223
+ }
224
+ for (const ticker of plan.update) {
225
+ try {
226
+ await registry.tickerRepository.saveTicker(ticker);
227
+ saved.push(ticker);
228
+ } catch {
229
+ failed += 1;
230
+ }
231
+ }
232
+ // One state update for the whole list: an update per ticker redraws every pane a hundred times.
233
+ if (saved.length) {
234
+ const tickers = new Map(getState().tickers);
235
+ for (const ticker of saved) tickers.set(ticker.metadata.ticker, ticker);
236
+ dispatch({ type: "SET_TICKERS", tickers });
237
+ }
238
+ for (const ticker of created) registry.events.emit("ticker:added", { symbol: ticker.metadata.ticker, ticker });
239
+ notify(failed
240
+ ? { type: "error", body: tf("Saved {saved} of {count} members to {name}. {failed} could not be added.", { saved: plan.count - failed, count: plan.count, name, failed }) }
241
+ : { type: "success", body: tf("Saved {count} members to {name}.", { count: plan.count, name }) });
242
+ };
243
+ return [
244
+ ...(choices.length ? [{ id: "members-open-in", label: t("Open Members In…"), onSelect: openIn }] : []),
245
+ { id: "members-save-watchlist", label: t("Save as Watchlist…"), onSelect: () => {
246
+ // 54 columns fill the dialog; the prompt's default field is wider and runs past its frame.
247
+ void dialog.prompt<string>({
248
+ closeOnClickOutside: true,
249
+ content: (context: PromptContext<string>) => <TextPromptDialog {...context} title={t("Save as Watchlist")}
250
+ body={[text.saveBody]} label={t("Name")} initialValue={list.watchlistName} confirmLabel={t("Save")} width={54} />,
251
+ }).then((name) => {
252
+ if (!name?.trim()) return;
253
+ saveAsWatchlist(name).catch((error: unknown) => {
254
+ notify({ type: "error", body: error instanceof Error ? error.message : t("The watchlist was not saved.") });
255
+ });
256
+ }).catch(() => {});
257
+ } },
258
+ ];
259
+ }, [createPaneFromTemplate, dialog, disabledPlugins, dispatch, getState, list, notify]);
260
+ }
@@ -1,6 +1,9 @@
1
1
  import type { HeadlessPaneFreshness } from "../../../types/headless";
2
2
  import type { Quote } from "../../../types/financials";
3
3
  import { isQuoteStaleForCurrentSession } from "../../../market-data/quotes/freshness";
4
+ import { delayedFeedLagMs } from "../../../market-data/market/freshness";
5
+ import { apiClient } from "../../../api-client";
6
+ import { CLOUD_NEWS_DELAY_HOURS, hasProAccess } from "../../../api-client/plan-rules";
4
7
 
5
8
  /**
6
9
  * Freshness declarations shared by built-in reports. Each `fn` report ends
@@ -41,17 +44,62 @@ export const REFERENCE_DATA: HeadlessPaneFreshness = { status: "not-a-feed", bas
41
44
  /** Company fundamentals, estimates and calendars: reported figures, not a feed. */
42
45
  export const REPORTED_DATA: HeadlessPaneFreshness = { status: "not-a-feed", basis: "reported data" };
43
46
 
47
+ /** Gloom Cloud's delayed quotes run behind each venue by a lag the app knows (`delayedFeedLagMs`). */
48
+ const CLOUD_QUOTE_PROVIDER_ID = "gloomberb-cloud";
49
+
50
+ interface QuoteFreshnessFields {
51
+ dataSource?: "live" | "delayed";
52
+ stale?: boolean;
53
+ /** How far a delayed Gloom Cloud quote runs behind its venue's trades. */
54
+ delayMinutes?: number;
55
+ /** The venue whose sessions the quote follows, so the report can say whether its market is open. */
56
+ sessionExchange?: string;
57
+ marketState?: Quote["marketState"];
58
+ }
59
+
44
60
  /**
45
61
  * The standard freshness fields of one quote, spread into a report row so the
46
- * report can say whether that row is live, delayed or stale for its session.
62
+ * report can say whether that row is live, delayed (and by how much) or stale
63
+ * for its session, and where its market stands.
47
64
  */
48
65
  export function quoteFreshnessFields(
49
66
  quote: Quote | null | undefined,
50
67
  now = Date.now(),
51
- ): { dataSource?: "live" | "delayed"; stale?: boolean } {
68
+ ): QuoteFreshnessFields {
52
69
  if (!quote) return {};
70
+ const exchange = quote.listingExchangeName || quote.exchangeName;
71
+ const delayed = quote.dataSource === "delayed" && quote.providerId === CLOUD_QUOTE_PROVIDER_ID;
53
72
  return {
54
73
  ...(quote.dataSource === "live" || quote.dataSource === "delayed" ? { dataSource: quote.dataSource } : {}),
55
74
  stale: isQuoteStaleForCurrentSession(quote, now),
75
+ ...(delayed ? { delayMinutes: delayedFeedLagMs(exchange) / 60_000 } : {}),
76
+ ...(exchange ? { sessionExchange: exchange } : {}),
77
+ ...(quote.marketState ? { marketState: quote.marketState } : {}),
56
78
  };
57
79
  }
80
+
81
+ /**
82
+ * Whether the Gloom Cloud session a report reads with has real-time access:
83
+ * no session reads as a free account; a session is asked for its plan. Null
84
+ * when the plan cannot be read, so the report does not guess a delay.
85
+ */
86
+ export async function cloudRealtimeAccess(): Promise<boolean | null> {
87
+ if (!apiClient.isSignedIn()) return false;
88
+ try {
89
+ return hasProAccess(apiClient.getCurrentUser() ?? await apiClient.getSession());
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Gloom Cloud news stories, dated by the newest: held back
97
+ * `CLOUD_NEWS_DELAY_HOURS` without real-time access, the live wire with it.
98
+ * The list is a history, so its last story is not stale data.
99
+ */
100
+ export function cloudNewsFreshness(realtime: boolean | null): HeadlessPaneFreshness {
101
+ const stories: HeadlessPaneFreshness = { observedKey: "publishedAt", oldest: null };
102
+ if (realtime === true) return { ...stories, status: "live" };
103
+ if (realtime === false) return { ...stories, status: "delayed", delayMinutes: CLOUD_NEWS_DELAY_HOURS * 60 };
104
+ return { ...stories, status: "not-a-feed", basis: "published stories" };
105
+ }
@@ -1,5 +1,7 @@
1
- import { useCallback } from "react";
1
+ import { useCallback, useMemo } from "react";
2
2
  import { ApiRequestError } from "../../../api-client/errors";
3
+ import { listingAbroad, type IssuerListingParams } from "../../../api-client/paths";
4
+ import type { TickerRecord } from "../../../types/ticker";
3
5
  import { useAsyncResource } from "../../../react/async-resource";
4
6
  import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
5
7
  import { parsePublicTickerKey } from "../../../utils/exchanges";
@@ -22,6 +24,28 @@ export function listingIdentity(key: string | null | undefined, savedExchange =
22
24
  return { symbol: parsed.symbol.toUpperCase(), exchange: parsed.exchange ?? savedExchange };
23
25
  }
24
26
 
27
+ /**
28
+ * The venue and company an SEC issuer read names for the pane's listing, from
29
+ * the pane's ticker as the SEC filings pane reads it: the key's venue, else the
30
+ * saved one, and the company the listing was saved as (listingAbroad). Nothing
31
+ * for a US listing or a symbol with no venue. A name that is only the symbol
32
+ * is left out, so the server falls back to the name on a quote it holds.
33
+ */
34
+ function issuerListing(ticker: TickerRecord | null | undefined): IssuerListingParams {
35
+ if (!ticker) return {};
36
+ const key = ticker.metadata.ticker.trim().toUpperCase();
37
+ const name = ticker.metadata.name?.trim();
38
+ const known = !!name && ![key, parsePublicTickerKey(key).symbol].includes(name.toUpperCase());
39
+ const abroad = listingAbroad(key, ticker.metadata.exchange, known ? name : undefined);
40
+ return { exchange: abroad?.exchange, name: abroad?.name };
41
+ }
42
+
43
+ /** `issuerListing` as a stable value, for loader dependencies. */
44
+ export function useIssuerListing(ticker: TickerRecord | null | undefined): IssuerListingParams {
45
+ const { exchange, name } = issuerListing(ticker);
46
+ return useMemo(() => ({ exchange, name }), [exchange, name]);
47
+ }
48
+
25
49
  /**
26
50
  * The pane's symbol for research panes that load their own data. Identity only:
27
51
  * it does not observe the quote, so a price tick does not re-render the pane.
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import type { DataTableCell, DataTableVisibleRange, PaneFooterSegment } from "../../../components";
3
3
  import { useQuoteEntries } from "../../../market-data/hooks";
4
- import { marketStateColor, marketStateLabel } from "../../../market-data/market/status";
4
+ import { getRegularSessionDisplay, marketStateColor, marketStateLabel } from "../../../market-data/market/status";
5
5
  import type { InstrumentRef } from "../../../market-data/request-types";
6
6
  import type { QueryEntry } from "../../../market-data/result-types";
7
7
  import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors";
@@ -466,12 +466,31 @@ export type QuoteBoardCellKind = "status" | "price" | "change" | "changePercent"
466
466
  export interface QuoteBoardCellFormat {
467
467
  /** Spell out the session instead of a colored dot. */
468
468
  sessionText?: boolean;
469
- /** Called with a finite price. */
469
+ /** Called with the quote at its headline: a finite price. */
470
470
  formatPrice: (quote: Quote) => string;
471
- /** Called with a finite change; the text carries its sign. */
471
+ /** Called with the quote at its headline: a finite change; the text carries its sign. */
472
472
  formatChange: (quote: Quote) => string;
473
473
  }
474
474
 
475
+ /** Each quote's figures as the board shows them, cached so a held quote keeps one object. */
476
+ const boardHeadlines = new WeakMap<Quote, Quote>();
477
+
478
+ /**
479
+ * The quote with its regular session's price and move (getRegularSessionDisplay),
480
+ * as `ticker` reads it: after the close its close and move, never an extended
481
+ * print, which a board of these columns has nowhere to show.
482
+ */
483
+ export function boardHeadlineQuote(quote: Quote): Quote {
484
+ const cached = boardHeadlines.get(quote);
485
+ if (cached) return cached;
486
+ const display = getRegularSessionDisplay(quote);
487
+ const headline = !display || (display.price === quote.price && display.change === quote.change && display.changePercent === quote.changePercent)
488
+ ? quote
489
+ : { ...quote, price: display.price, change: display.change ?? Number.NaN, changePercent: display.changePercent ?? Number.NaN };
490
+ boardHeadlines.set(quote, headline);
491
+ return headline;
492
+ }
493
+
475
494
  /** A row is loading while it has no quote yet, so none of its cells shows a no-data dash. */
476
495
  export function isBoardRowLoading(state: BoardQuoteState | undefined): boolean {
477
496
  return !state?.quote && (state?.loading ?? true);
@@ -482,7 +501,7 @@ export function renderQuoteBoardCell(
482
501
  state: BoardQuoteState | undefined,
483
502
  format: QuoteBoardCellFormat,
484
503
  ): DataTableCell {
485
- const quote = state?.quote;
504
+ const quote = state?.quote ? boardHeadlineQuote(state.quote) : state?.quote;
486
505
  // One row must not mix a loading marker with a no-data marker.
487
506
  if (isBoardRowLoading(state)) return { text: kind === "status" ? "" : "…", color: colors.textDim };
488
507
 
@@ -517,7 +536,8 @@ export function renderQuoteBoardCell(
517
536
  case "time":
518
537
  return {
519
538
  text: formatQuoteTime(quote?.lastUpdated),
520
- value: quote?.lastUpdated ? new Date(quote.lastUpdated).toISOString().replace(".000Z", "Z") : null,
539
+ // A Date, so the export writes it whole and a rendered-view report dates the board by its quotes.
540
+ value: quote?.lastUpdated ? new Date(quote.lastUpdated) : null,
521
541
  color: colors.textDim,
522
542
  };
523
543
  }
@@ -189,6 +189,7 @@ export function expectedMove(
189
189
  sigmaPercent: sigma != null && Number.isFinite(sigma) ? sigma / spot * 100 : null, strike: atm?.strike ?? null };
190
190
  }
191
191
 
192
+ /** 25-delta and 90/110 skew in decimal annualized IV; OVDV reads them on each expiry's fitted smile. */
192
193
  export interface SkewMetrics {
193
194
  put25: number | null;
194
195
  call25: number | null;
@@ -199,52 +200,6 @@ export interface SkewMetrics {
199
200
  moneynessSkew: number | null;
200
201
  }
201
202
 
202
- /** Smile accepts absolute strike. All differences are in decimal annualized IV. */
203
- export function smileSkew(input: Omit<VolatilityInputs, "volatility">, smile: (strike: number) => number | null): SkewMetrics {
204
- if (!validInputs({ ...input, volatility: 1 })) {
205
- return { atm: null, put25: null, call25: null, putCallSkew: null,
206
- riskReversal: null, butterfly: null, moneynessSkew: null };
207
- }
208
- const atm = smile(input.spot);
209
- const deltaVol = (side: OptionSide, target: number): number | null => {
210
- if (!positive(input.spot) || !positive(input.years)) return null;
211
- const center = Math.log(input.spot);
212
- let radius = 1;
213
- let low = center - radius;
214
- let high = center + radius;
215
- const at = (logStrike: number) => {
216
- const strike = Math.exp(logStrike);
217
- const volatility = smile(strike);
218
- return volatility == null ? null : optionDelta({ ...input, volatility }, strike, side);
219
- };
220
- let a = at(low), b = at(high);
221
- // Wide or strongly carried smiles can put a wing beyond twenty times spot.
222
- // Expand until a root is bracketed; a missing smile observation stays missing.
223
- for (let i = 0; i < 10 && a != null && b != null && (a < target || b > target); i += 1) {
224
- radius *= 2;
225
- low = center - radius;
226
- high = center + radius;
227
- a = at(low);
228
- b = at(high);
229
- }
230
- if (a == null || b == null || a < target || b > target) return null;
231
- for (let i = 0; i < 80; i += 1) {
232
- const mid = (low + high) / 2;
233
- const delta = at(mid);
234
- if (delta == null) return null;
235
- if (delta > target) low = mid; else high = mid;
236
- }
237
- return smile(Math.exp((low + high) / 2));
238
- };
239
- const put25 = deltaVol("put", -0.25), call25 = deltaVol("call", 0.25);
240
- const low = smile(input.spot * 0.9), high = smile(input.spot * 1.1);
241
- const both = put25 != null && call25 != null;
242
- return { atm, put25, call25, putCallSkew: both ? put25 - call25 : null,
243
- riskReversal: both ? call25 - put25 : null,
244
- butterfly: both && atm != null ? (call25 + put25) / 2 - atm : null,
245
- moneynessSkew: low != null && high != null ? low - high : null };
246
- }
247
-
248
203
  /** Decimal IV change per year between two tenors. */
249
204
  export function volatilityTermSlope(front: { years: number; volatility: number }, back: { years: number; volatility: number }): number | null {
250
205
  return positive(front.years) && back.years > front.years && Number.isFinite(back.years)
@@ -9,7 +9,9 @@ import { shortInterestHeadless } from "./headless";
9
9
  import { ShortInterestResearchTab, ShortInterestSurface } from "./surface";
10
10
  import { shortWatchHeadless } from "./watch-headless";
11
11
  import { SHORT_WATCH_SCOPE_OPTIONS, ShortWatchPane } from "./watch-pane";
12
+ import { withCommandTab } from "../shared/command-tab";
12
13
  import { followsWithoutFinraOverride, shortVolumeSettings } from "../short-volume";
14
+ import { shortVolumeHeadless } from "../short-volume/headless";
13
15
  import { isKnownNonUsListing } from "../../../utils/sec";
14
16
 
15
17
 
@@ -22,7 +24,7 @@ export const shortInterestModule: PluginModule = {
22
24
  id: SHORT_INTEREST_CONNECTION_ID,
23
25
  name: "Gloom Short Interest",
24
26
  kind: "api",
25
- ownerId: "ticker-research",
27
+ ownerId: "ownership",
26
28
  detail: "api.gloom.sh",
27
29
  priority: 300,
28
30
  });
@@ -75,17 +77,30 @@ export const shortInterestModule: PluginModule = {
75
77
 
76
78
  paneTemplates: [
77
79
  {
78
- ...createTickerSurfacePaneTemplate({
80
+ ...withCommandTab(createTickerSurfacePaneTemplate({
79
81
  id: "short-interest-pane",
80
82
  paneId: "short-interest",
81
83
  label: "Short Interest",
82
84
  // The current snapshot only carries the current and prior settlement dates.
83
- description: "Bi-monthly short interest settlements from FINRA with days to cover and average daily volume.",
85
+ description: "Bi-monthly short interest settlements from FINRA with days to cover and average daily volume. Daily volume is the other tab.",
84
86
  keywords: ["short", "interest", "si", "shorts", "borrow", "days", "cover"],
85
87
  shortcut: "SI",
86
- }),
88
+ }), "interest"),
87
89
  headless: shortInterestHeadless,
88
90
  },
91
+ {
92
+ // The same pane on Daily volume. The template id stays, so an older share still restores.
93
+ ...withCommandTab(createTickerSurfacePaneTemplate({
94
+ id: "short-volume-pane",
95
+ paneId: "short-interest",
96
+ label: "Daily Short Volume",
97
+ description: "The short interest pane on Daily volume: FINRA daily off-exchange short-volume ratios, historical percentile and reported share quantities.",
98
+ keywords: ["daily", "short", "volume", "finra", "siv"],
99
+ shortcut: "SIV",
100
+ publicShare: true,
101
+ }), "volume"),
102
+ headless: shortVolumeHeadless,
103
+ },
89
104
  {
90
105
  id: "short-watch-pane",
91
106
  paneId: "short-watch",
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import { Box } from "../../../ui";
3
3
  import { PaneFooterScope, usePaneFooter, usePaneTabs } from "../../../components";
4
- import { usePluginPaneState } from "../../../public/react";
5
4
  import type { PaneProps } from "../../../types/plugin";
5
+ import { useCommandTab } from "../shared/command-tab";
6
6
  import { ShortVolumePane } from "../short-volume/pane";
7
7
  import { ShortInterestView } from "./pane";
8
8
 
@@ -11,14 +11,14 @@ export function ShortInterestSurface({ nested = false, ...props }: Pick<PaneProp
11
11
  /** Inside Ticker Research, whose own tab strip keeps h/l and the arrows. */
12
12
  nested?: boolean;
13
13
  }) {
14
- const [tab, setTab] = usePluginPaneState("short-interest:tab", "interest");
15
- const [mounted, setMounted] = useState(() => new Set([tab]));
14
+ const [tab, setTab] = useCommandTab("short-interest", "short-interest:tab", ["interest", "volume"], "interest");
15
+ const [mounted, setMounted] = useState<Set<string>>(() => new Set([tab]));
16
16
  useEffect(() => { setMounted((current) => current.has(tab) ? current : new Set([...current, tab])); }, [tab]);
17
17
  // Nested in Ticker Research the title bar belongs to the research tabs, so
18
18
  // the desktop switches views from the query bar and the terminal strip
19
19
  // leaves h/l to the research tabs.
20
20
  const { strip: tabStrip, rows: tabRows } = usePaneTabs({
21
- tabs: TABS, activeValue: tab, onSelect: setTab, focused: props.focused, keyboardNavigation: !nested, dense: true, queryBarWidth: props.width,
21
+ tabs: TABS, activeValue: tab, onSelect: (value) => setTab(value === "volume" ? "volume" : "interest"), focused: props.focused, keyboardNavigation: !nested, dense: true, queryBarWidth: props.width,
22
22
  });
23
23
  // The strip answers h/l only where it is the pane's own strip; `v` switches
24
24
  // views everywhere, including under Ticker Research's strip.
@@ -1,9 +1,6 @@
1
1
  import type { PaneInstanceConfig } from "../../../types/config";
2
2
  import type { PluginModule } from "../plugin-module";
3
- import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
4
3
  import { shortVolumeCache } from "./client";
5
- import { shortVolumeHeadless } from "./headless";
6
- import { ShortVolumePane } from "./pane";
7
4
 
8
5
  export const shortVolumeSettings = [
9
6
  { key: "shortVolumeScope", label: "Reporting scope", type: "select" as const,
@@ -14,14 +11,8 @@ export const shortVolumeSettings = [
14
11
  export const followsWithoutFinraOverride = (pane: PaneInstanceConfig) => (
15
12
  typeof pane.settings?.finraSymbol !== "string" || !pane.settings.finraSymbol.trim()
16
13
  );
14
+ /** The daily-volume cache. `SIV` opens the short interest pane, so this module has no pane of its own. */
17
15
  export const shortVolumeModule: PluginModule = {
18
- panes: [{ id: "short-volume", name: "Daily Short Volume", icon: "S", component: ShortVolumePane,
19
- defaultPosition: "right", tickerFollower: followsWithoutFinraOverride, defaultMode: "floating", defaultFloatingSize: { width: 92, height: 28 },
20
- tableExport: true, headless: shortVolumeHeadless, settings: { title: "Daily Short Volume", fields: shortVolumeSettings } }],
21
- paneTemplates: [{ ...createTickerSurfacePaneTemplate({ id: "short-volume-pane", paneId: "short-volume", label: "Daily Short Volume",
22
- description: "FINRA daily off-exchange short-volume ratios, historical percentile and reported share quantities.",
23
- shortcut: "SIV", keywords: ["daily", "short", "volume", "finra", "siv"], publicShare: true,
24
- }), headless: shortVolumeHeadless }],
25
16
  setup(ctx) { shortVolumeCache.attach(ctx.persistence); },
26
17
  dispose() { shortVolumeCache.reset(); },
27
18
  };