gloomberb 0.18.3 → 0.18.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 (886) hide show
  1. package/README.md +8 -8
  2. package/README.zh-CN.md +11 -11
  3. package/bin/{gloomberb → gloom} +3 -1
  4. package/package.json +6 -2
  5. package/src/account/checkout-handoff.tsx +225 -0
  6. package/src/account/device-signin-dialog.tsx +2 -2
  7. package/src/account/device-signin.ts +2 -2
  8. package/src/account/upgrade-dialog.tsx +1 -1
  9. package/src/api-client/askg.ts +2 -2
  10. package/src/api-client/auth.ts +2 -2
  11. package/src/api-client/central-bank-rates.ts +6 -0
  12. package/src/api-client/collections.ts +5 -1
  13. package/src/api-client/cot.ts +2 -0
  14. package/src/api-client/crypto-etf-flows.ts +37 -0
  15. package/src/api-client/crypto-markets.ts +60 -0
  16. package/src/api-client/data.ts +84 -5
  17. package/src/api-client/doe.ts +15 -0
  18. package/src/api-client/equity-screener.ts +28 -0
  19. package/src/api-client/exchange-announcements.ts +38 -0
  20. package/src/api-client/fund-facts.ts +39 -0
  21. package/src/api-client/futures-curve.ts +9 -1
  22. package/src/api-client/hiring.ts +301 -0
  23. package/src/api-client/index.ts +22 -5
  24. package/src/api-client/market-discovery.ts +14 -0
  25. package/src/api-client/members.ts +6 -0
  26. package/src/api-client/mna.ts +6 -0
  27. package/src/api-client/paths.ts +18 -1
  28. package/src/api-client/request.ts +21 -4
  29. package/src/api-client/short-side-publications.ts +37 -0
  30. package/src/api-client/socket.ts +64 -4
  31. package/src/api-client/swap-curve.ts +35 -0
  32. package/src/api-client/themes.ts +7 -1
  33. package/src/api-client/types.ts +90 -2
  34. package/src/app/pane-runtime/plugin-bindings.ts +9 -2
  35. package/src/app/runtime/desktop-deeplink.ts +5 -5
  36. package/src/app/runtime/plugin-bindings.ts +2 -2
  37. package/src/app/runtime/startup.ts +13 -0
  38. package/src/app/star-prompt/status-line.tsx +2 -2
  39. package/src/architecture/core-builtin-allowlist.json +5 -2
  40. package/src/architecture/ui-literals-allowlist.json +3 -3
  41. package/src/architecture/ui-tokens-allowlist.json +1 -1
  42. package/src/brokers/broker-portfolio-sync.ts +37 -0
  43. package/src/brokers/directory.ts +11 -0
  44. package/src/brokers/logos.ts +3 -1
  45. package/src/brokers/signed-in/adapter.ts +304 -26
  46. package/src/brokers/signed-in/client.ts +14 -2
  47. package/src/brokers/signed-in/connect.ts +10 -2
  48. package/src/brokers/sync-broker-instance.ts +9 -1
  49. package/src/capabilities/chart-series.ts +22 -6
  50. package/src/cli/bank-section.ts +1 -1
  51. package/src/cli/base-converter.ts +23 -14
  52. package/src/cli/central-bank-events.ts +1 -1
  53. package/src/cli/command-options.ts +59 -4
  54. package/src/cli/commands/api.ts +9 -6
  55. package/src/cli/commands/broker.ts +2 -2
  56. package/src/cli/commands/command-utils.ts +2 -2
  57. package/src/cli/commands/crypto-hints.ts +7 -20
  58. package/src/cli/commands/fred-series.ts +4 -2
  59. package/src/cli/commands/keybindings.ts +2 -2
  60. package/src/cli/commands/market.ts +1117 -264
  61. package/src/cli/commands/news-hints.ts +2 -2
  62. package/src/cli/commands/news-search.ts +59 -6
  63. package/src/cli/commands/options-leaps.ts +41 -26
  64. package/src/cli/commands/overview.ts +121 -48
  65. package/src/cli/commands/plugins.ts +9 -9
  66. package/src/cli/commands/remote.ts +13 -10
  67. package/src/cli/commands/search.ts +64 -44
  68. package/src/cli/commands/system.ts +27 -19
  69. package/src/cli/commands/themes.ts +1 -1
  70. package/src/cli/commands/ticker.ts +192 -47
  71. package/src/cli/context.ts +3 -2
  72. package/src/cli/desktop-pane-shot-view.ts +2 -2
  73. package/src/cli/desktop-pane-shot.ts +1 -1
  74. package/src/cli/earnings-calendar.ts +4 -4
  75. package/src/cli/entry.ts +18 -1
  76. package/src/cli/errors.ts +20 -2
  77. package/src/cli/exit-codes.ts +1 -1
  78. package/src/cli/export-source.ts +10 -4
  79. package/src/cli/freshness.ts +2 -8
  80. package/src/cli/fx-forward.ts +130 -0
  81. package/src/cli/fx-pair.ts +10 -9
  82. package/src/cli/glossary.ts +64 -20
  83. package/src/cli/help.ts +19 -13
  84. package/src/cli/history-export.ts +126 -0
  85. package/src/cli/history-rows.ts +196 -43
  86. package/src/cli/index-roots.ts +5 -45
  87. package/src/cli/index.ts +24 -18
  88. package/src/cli/known-gaps.ts +118 -25
  89. package/src/cli/listing-arg.ts +300 -41
  90. package/src/cli/name-entries.ts +30 -18
  91. package/src/cli/native-smoke.ts +2 -0
  92. package/src/cli/not-a-ticker.ts +19 -9
  93. package/src/cli/opentui-libc.ts +5 -0
  94. package/src/cli/options.ts +12 -0
  95. package/src/cli/pane-functions/capabilities.ts +75 -20
  96. package/src/cli/pane-functions/catalog.ts +24 -23
  97. package/src/cli/pane-functions/discovery.ts +1 -1
  98. package/src/cli/pane-functions/dom.ts +10 -7
  99. package/src/cli/pane-functions/financial-ratio-shot.ts +2 -2
  100. package/src/cli/pane-functions/freshness.ts +174 -23
  101. package/src/cli/pane-functions/function-help.ts +66 -27
  102. package/src/cli/pane-functions/headless.ts +24 -11
  103. package/src/cli/pane-functions/index.ts +18 -16
  104. package/src/cli/pane-functions/options.ts +22 -2
  105. package/src/cli/pane-functions/report-notices.ts +12 -4
  106. package/src/cli/pane-functions/report.ts +5 -5
  107. package/src/cli/pane-functions/resolver.ts +47 -10
  108. package/src/cli/pane-functions/screenshot-assessment.ts +322 -0
  109. package/src/cli/pane-functions/screenshot-bridge.ts +152 -0
  110. package/src/cli/pane-functions/screenshot-chart-evidence.ts +396 -0
  111. package/src/cli/pane-functions/screenshot-data-evidence.ts +383 -0
  112. package/src/cli/pane-functions/screenshot-evidence.ts +1 -1
  113. package/src/cli/pane-functions/screenshot-payload.ts +453 -0
  114. package/src/cli/pane-functions/screenshot-render.ts +316 -0
  115. package/src/cli/pane-functions/screenshot-types.ts +162 -0
  116. package/src/cli/pane-functions/shot-options.ts +8 -3
  117. package/src/cli/peso-dollar-rates.ts +79 -0
  118. package/src/cli/quote-answer.ts +38 -3
  119. package/src/cli/report-tables.ts +61 -16
  120. package/src/cli/restore-plugins.ts +29 -14
  121. package/src/cli/result.ts +111 -22
  122. package/src/cli/series-search.ts +5 -5
  123. package/src/cli/statement-filings.ts +19 -0
  124. package/src/cli/suffixed-listing-rows.ts +49 -0
  125. package/src/cli/symbol-args.ts +50 -15
  126. package/src/cli/symbol-cells.ts +120 -0
  127. package/src/cli/symbol-list.ts +198 -68
  128. package/src/cli/symbol-rows.ts +131 -25
  129. package/src/cli/ticker-resolution.ts +108 -11
  130. package/src/cli/treasury-yields.ts +5 -5
  131. package/src/cli/types.ts +2 -0
  132. package/src/cli/uncovered-instrument.ts +3 -3
  133. package/src/cli/unknown-command.ts +6 -6
  134. package/src/components/chart/composite/chart-layout.ts +131 -0
  135. package/src/components/chart/composite/composite-chart.tsx +373 -16
  136. package/src/components/chart/composite/interactions.ts +7 -2
  137. package/src/components/chart/composite/legend.tsx +75 -3
  138. package/src/components/chart/composite/levels.ts +5 -0
  139. package/src/components/chart/composite/panel-gestures.ts +41 -0
  140. package/src/components/chart/composite/panel-labels.ts +2 -1
  141. package/src/components/chart/composite/panel-surface.tsx +305 -11
  142. package/src/components/chart/composite/price-axis-labels.tsx +27 -3
  143. package/src/components/chart/composite/rasterizer.ts +44 -19
  144. package/src/components/chart/composite/scene.ts +16 -4
  145. package/src/components/chart/composite/text-renderer.ts +48 -21
  146. package/src/components/chart/composite/toolbar.tsx +52 -5
  147. package/src/components/chart/composite/types.ts +13 -0
  148. package/src/components/chart/composite/use-composite-chart-keyboard.ts +120 -13
  149. package/src/components/chart/composite/use-composite-value-scales.ts +44 -0
  150. package/src/components/chart/composite/use-composite-viewport.ts +12 -3
  151. package/src/components/chart/composite/value-scale.ts +149 -0
  152. package/src/components/chart/composite/volume-profile-paint.ts +6 -3
  153. package/src/components/chart/core/pointer.ts +2 -0
  154. package/src/components/chart-table/index.ts +2 -0
  155. package/src/components/chart-table/read.tsx +74 -0
  156. package/src/components/command-bar/commands/collection-list.ts +41 -19
  157. package/src/components/command-bar/commands/collection.ts +9 -3
  158. package/src/components/command-bar/commands/direct/index.ts +1 -1
  159. package/src/components/command-bar/commands/direct/runtime.ts +3 -2
  160. package/src/components/command-bar/pane-templates/items.ts +59 -6
  161. package/src/components/command-bar/pane-templates/workflow.ts +10 -1
  162. package/src/components/command-bar/routes/root/plugin-install.ts +1 -1
  163. package/src/components/command-bar/routes/root/provider-search.ts +42 -11
  164. package/src/components/command-bar/routes/root/results.ts +18 -4
  165. package/src/components/command-bar/routes/root/runtime.ts +15 -3
  166. package/src/components/command-bar/routes/root/search-providers.ts +25 -2
  167. package/src/components/command-bar/routes/root/selection.ts +6 -1
  168. package/src/components/command-bar/routes/root/shortcut-feedback.ts +4 -1
  169. package/src/components/command-bar/routes/root/shortcuts.ts +30 -3
  170. package/src/components/command-bar/routes/ticker-search/results.ts +73 -4
  171. package/src/components/command-bar/selection-runtime.ts +50 -10
  172. package/src/components/command-bar/surface/index.tsx +24 -2
  173. package/src/components/command-bar/view-model.ts +7 -0
  174. package/src/components/command-bar/workflow/broker.ts +2 -2
  175. package/src/components/command-bar/workflow/collection-actions.ts +46 -7
  176. package/src/components/command-bar/workflow/collection-ticker.ts +32 -4
  177. package/src/components/command-bar/workflow/fields.ts +8 -2
  178. package/src/components/command-bar/workflow/ops.ts +18 -4
  179. package/src/components/command-bar/workflow/portfolio-position.ts +53 -4
  180. package/src/components/command-bar/workflow/submit.ts +3 -2
  181. package/src/components/command-bar/workflow/tickers.ts +5 -1
  182. package/src/components/company-logo.tsx +39 -0
  183. package/src/components/data-table/sections.ts +4 -2
  184. package/src/components/form-modal/confirm.tsx +2 -1
  185. package/src/components/form-modal/request.ts +5 -0
  186. package/src/components/form-modal/routes.ts +4 -1
  187. package/src/components/index.ts +12 -6
  188. package/src/components/layout/header.tsx +7 -6
  189. package/src/components/layout/market-summary.ts +9 -6
  190. package/src/components/layout/pane/capture-status.ts +1 -1
  191. package/src/components/layout/pane/footer/model.ts +11 -5
  192. package/src/components/layout/shell/chrome.ts +19 -6
  193. package/src/components/layout/shell/index.tsx +2 -1
  194. package/src/components/layout/shell/pane/layers.tsx +19 -1
  195. package/src/components/layout/status-bar.tsx +2 -2
  196. package/src/components/market-board/index.tsx +11 -3
  197. package/src/components/onboarding/onboarding-wizard.tsx +1 -0
  198. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +2 -2
  199. package/src/components/onboarding/wizard-broker-sync.ts +4 -1
  200. package/src/components/pane-settings-dialog/desktop.tsx +20 -7
  201. package/src/components/pane-settings-dialog/tui.tsx +17 -9
  202. package/src/components/pane-settings-dialog/value.ts +8 -1
  203. package/src/components/pane-settings-dialog.tsx +44 -13
  204. package/src/components/sign-in-code-panel.tsx +27 -19
  205. package/src/components/ui/column-grid.tsx +48 -4
  206. package/src/components/ui/country-flag-art.tsx +180 -0
  207. package/src/components/ui/country-flag.tsx +118 -12
  208. package/src/components/ui/data-table/opentui/index.tsx +61 -2
  209. package/src/components/ui/data-table/opentui/model.ts +109 -2
  210. package/src/components/ui/data-table/types.ts +1 -1
  211. package/src/components/ui/display.tsx +5 -1
  212. package/src/components/ui/index.ts +3 -1
  213. package/src/components/ui/logo-mark.tsx +11 -2
  214. package/src/components/ui/query-bar.tsx +1 -1
  215. package/src/components/ui/row-notes.ts +79 -0
  216. package/src/core/state/app/layout.ts +4 -4
  217. package/src/data/cached-query.ts +8 -1
  218. package/src/data/config/chart-settings.ts +3 -3
  219. package/src/data/config/home.ts +10 -2
  220. package/src/data/config/layout.ts +4 -1
  221. package/src/data/config/store/node.ts +4 -0
  222. package/src/data/config/store/normalize.ts +13 -3
  223. package/src/data/config/store/portfolio-as-of-column.ts +2 -2
  224. package/src/data/json-storage.ts +2 -0
  225. package/src/data/saved-copies.ts +84 -0
  226. package/src/docs/snapshot.json +65 -32
  227. package/src/i18n/es.ts +29 -12
  228. package/src/i18n/ja.ts +29 -12
  229. package/src/i18n/ko.ts +29 -12
  230. package/src/i18n/zh-cn.ts +29 -12
  231. package/src/i18n/zh-tw.ts +29 -12
  232. package/src/layout/desks.ts +1 -1
  233. package/src/layout/launch-layout.ts +1 -1
  234. package/src/layout/pane-manager/floating-actions.ts +5 -3
  235. package/src/layout/pane-manager/floating.ts +43 -1
  236. package/src/layout/pane-manager/index.ts +2 -0
  237. package/src/layout/selected-ticker.ts +74 -0
  238. package/src/market-data/depositary-pair.ts +251 -0
  239. package/src/market-data/futures-board.ts +1 -1
  240. package/src/market-data/futures-contract.ts +124 -40
  241. package/src/market-data/fx-closes.ts +17 -2
  242. package/src/market-data/hooks.tsx +19 -11
  243. package/src/market-data/market/change-basis.ts +49 -7
  244. package/src/market-data/market/freshness.ts +34 -5
  245. package/src/market-data/market/period-return.ts +163 -0
  246. package/src/market-data/market/trading-sessions.ts +13 -0
  247. package/src/market-data/market/year-to-date.ts +8 -13
  248. package/src/market-data/performance.ts +20 -14
  249. package/src/market-data/perpetual-premium.ts +32 -0
  250. package/src/market-data/published-apac-sessions.ts +2 -1
  251. package/src/market-data/published-futures-sessions.ts +69 -0
  252. package/src/market-data/quotes/contributions.ts +5 -1
  253. package/src/market-data/quotes/freshness.ts +42 -0
  254. package/src/market-data/quotes/intraday-bar.ts +37 -0
  255. package/src/market-data/quotes/regular-session-valuation.ts +13 -8
  256. package/src/market-data/quotes/resolution.ts +4 -2
  257. package/src/market-data/tax-equivalent.ts +1 -1
  258. package/src/market-data/ticker-price-returns.ts +3 -1
  259. package/src/news/aggregator.ts +5 -1
  260. package/src/news/news-model.ts +4 -0
  261. package/src/news/types.ts +11 -0
  262. package/src/notifications/app-notifier.ts +1 -1
  263. package/src/plugins/auto-update.ts +2 -2
  264. package/src/plugins/builtin/account-management/index.tsx +1 -0
  265. package/src/plugins/builtin/account-management/pane.tsx +9 -8
  266. package/src/plugins/builtin/alerts/alert-engine.ts +29 -19
  267. package/src/plugins/builtin/alerts/calendar-events.ts +378 -0
  268. package/src/plugins/builtin/alerts/cli.ts +485 -76
  269. package/src/plugins/builtin/alerts/command.ts +5 -10
  270. package/src/plugins/builtin/alerts/event-rows.ts +9 -5
  271. package/src/plugins/builtin/alerts/events-pane.tsx +71 -56
  272. package/src/plugins/builtin/alerts/events.ts +107 -5
  273. package/src/plugins/builtin/alerts/fired-log.ts +78 -0
  274. package/src/plugins/builtin/alerts/format.ts +2 -1
  275. package/src/plugins/builtin/alerts/headless.ts +100 -39
  276. package/src/plugins/builtin/alerts/history-pane.tsx +105 -62
  277. package/src/plugins/builtin/alerts/history-rows.ts +89 -0
  278. package/src/plugins/builtin/alerts/index.tsx +45 -18
  279. package/src/plugins/builtin/alerts/iv-level.ts +365 -0
  280. package/src/plugins/builtin/alerts/levels.ts +3 -3
  281. package/src/plugins/builtin/alerts/pane.tsx +97 -29
  282. package/src/plugins/builtin/alerts/price.ts +35 -0
  283. package/src/plugins/builtin/alerts/quotes.ts +24 -0
  284. package/src/plugins/builtin/alerts/research-builder.ts +84 -11
  285. package/src/plugins/builtin/alerts/research-rules.ts +76 -3
  286. package/src/plugins/builtin/alerts/scheduled-check.ts +206 -32
  287. package/src/plugins/builtin/alerts/scheduled-sources.ts +26 -3
  288. package/src/plugins/builtin/alerts/scheduled.ts +188 -29
  289. package/src/plugins/builtin/alerts/storage.ts +13 -10
  290. package/src/plugins/builtin/alerts/targets.ts +12 -3
  291. package/src/plugins/builtin/analytics/index.tsx +8 -2
  292. package/src/plugins/builtin/analytics/metrics.ts +1 -1
  293. package/src/plugins/builtin/analytics/risk-book.ts +215 -0
  294. package/src/plugins/builtin/analytics/risk-client.ts +13 -6
  295. package/src/plugins/builtin/analytics/risk-headless.ts +55 -21
  296. package/src/plugins/builtin/analytics/risk-math.ts +6 -3
  297. package/src/plugins/builtin/analytics/risk-model.ts +297 -104
  298. package/src/plugins/builtin/analytics/risk-options.ts +17 -2
  299. package/src/plugins/builtin/analytics/risk-pane.tsx +97 -36
  300. package/src/plugins/builtin/attention/pane.tsx +6 -2
  301. package/src/plugins/builtin/awards/headless.ts +7 -3
  302. package/src/plugins/builtin/awards/model.ts +18 -0
  303. package/src/plugins/builtin/awards/pane.tsx +6 -3
  304. package/src/plugins/builtin/backtest/headless.ts +3 -3
  305. package/src/plugins/builtin/backtest/rules.ts +41 -5
  306. package/src/plugins/builtin/broker-manager/add-flow.tsx +12 -7
  307. package/src/plugins/builtin/broker-manager/detail.tsx +47 -19
  308. package/src/plugins/builtin/broker-manager/footer.ts +27 -11
  309. package/src/plugins/builtin/broker-manager/index.tsx +21 -12
  310. package/src/plugins/builtin/broker-manager/keyboard.ts +1 -1
  311. package/src/plugins/builtin/broker-manager/model.ts +12 -2
  312. package/src/plugins/builtin/broker-manager/pane-actions.tsx +183 -57
  313. package/src/plugins/builtin/broker-manager/table.ts +28 -12
  314. package/src/plugins/builtin/broker-trading/account-picker.tsx +2 -1
  315. package/src/plugins/builtin/broker-trading/chart-levels.ts +82 -13
  316. package/src/plugins/builtin/broker-trading/controller.ts +45 -13
  317. package/src/plugins/builtin/broker-trading/dock.tsx +22 -4
  318. package/src/plugins/builtin/broker-trading/headless.ts +2 -1
  319. package/src/plugins/builtin/broker-trading/index.tsx +7 -1
  320. package/src/plugins/builtin/broker-trading/linked-orders.ts +148 -0
  321. package/src/plugins/builtin/broker-trading/order-feed.ts +75 -7
  322. package/src/plugins/builtin/broker-trading/orders.tsx +109 -31
  323. package/src/plugins/builtin/broker-trading/overview-orders.tsx +9 -3
  324. package/src/plugins/builtin/broker-trading/pane-model.ts +30 -2
  325. package/src/plugins/builtin/broker-trading/pane.tsx +110 -23
  326. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +36 -7
  327. package/src/plugins/builtin/broker-trading/ticket.tsx +112 -31
  328. package/src/plugins/builtin/buildout/cells.tsx +4 -0
  329. package/src/plugins/builtin/buildout/detail/index.tsx +5 -1
  330. package/src/plugins/builtin/buildout/detail/ui.tsx +7 -4
  331. package/src/plugins/builtin/buildout/pane/index.tsx +25 -12
  332. package/src/plugins/builtin/buildout/pane/status.tsx +0 -4
  333. package/src/plugins/builtin/builtin-plugin-meta.ts +1 -1
  334. package/src/plugins/builtin/catalysts/detail.tsx +3 -2
  335. package/src/plugins/builtin/catalysts/index.tsx +1 -1
  336. package/src/plugins/builtin/catalysts/model.ts +49 -4
  337. package/src/plugins/builtin/catalysts/pane.tsx +6 -8
  338. package/src/plugins/builtin/central-bank-rates/client.ts +5 -0
  339. package/src/plugins/builtin/central-bank-rates/headless.ts +36 -10
  340. package/src/plugins/builtin/central-bank-rates/model.ts +33 -8
  341. package/src/plugins/builtin/changelog/index.tsx +1 -0
  342. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +1 -0
  343. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +2 -1
  344. package/src/plugins/builtin/chart-composer/chart-spec.ts +7 -0
  345. package/src/plugins/builtin/chart-composer/cli-options.ts +2 -5
  346. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  347. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -0
  348. package/src/plugins/builtin/chart-composer/editor.tsx +4 -1
  349. package/src/plugins/builtin/chart-composer/headless-schema.ts +28 -26
  350. package/src/plugins/builtin/chart-composer/headless.ts +253 -35
  351. package/src/plugins/builtin/chart-composer/index.tsx +12 -2
  352. package/src/plugins/builtin/chart-composer/leverage-overlay.ts +142 -0
  353. package/src/plugins/builtin/chart-composer/pane.tsx +127 -28
  354. package/src/plugins/builtin/chart-composer/series-catalog.ts +1 -0
  355. package/src/plugins/builtin/chart-composer/series-expression.ts +65 -6
  356. package/src/plugins/builtin/chart-composer/session-history.ts +15 -21
  357. package/src/plugins/builtin/chart-composer/session-range.ts +4 -2
  358. package/src/plugins/builtin/chart-composer/settings.ts +31 -19
  359. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +5 -6
  360. package/src/plugins/builtin/chart-composer/viewport-labels.ts +18 -8
  361. package/src/plugins/builtin/chat/attachments/model.ts +3 -1
  362. package/src/plugins/builtin/chat/content/index.tsx +1 -1
  363. package/src/plugins/builtin/chat/pane-links/open.ts +1 -1
  364. package/src/plugins/builtin/chat/people.ts +1 -1
  365. package/src/plugins/builtin/clinical-trials/client.ts +5 -2
  366. package/src/plugins/builtin/clinical-trials/model.ts +13 -4
  367. package/src/plugins/builtin/clinical-trials/pane.tsx +32 -12
  368. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +17 -0
  369. package/src/plugins/builtin/cloud/askg/controller.ts +1 -1
  370. package/src/plugins/builtin/cloud/askg/lock-note.ts +1 -1
  371. package/src/plugins/builtin/cloud/askg/tool-display.ts +1 -1
  372. package/src/plugins/builtin/cloud/auth-commands.ts +3 -3
  373. package/src/plugins/builtin/cloud/team/pane-sections.tsx +105 -47
  374. package/src/plugins/builtin/cloud/team/pane-ui.tsx +42 -4
  375. package/src/plugins/builtin/cloud/team/pane.tsx +204 -64
  376. package/src/plugins/builtin/cloud/thesis/store.ts +45 -22
  377. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  378. package/src/plugins/builtin/commodities-energy/index.tsx +29 -2
  379. package/src/plugins/builtin/commodities-energy/view-state.ts +2 -0
  380. package/src/plugins/builtin/commodity-balances/pane.tsx +11 -4
  381. package/src/plugins/builtin/company-attention/app-rank.tsx +7 -5
  382. package/src/plugins/builtin/company-attention/building-model.ts +222 -0
  383. package/src/plugins/builtin/company-attention/building.tsx +172 -0
  384. package/src/plugins/builtin/company-attention/client.ts +117 -1
  385. package/src/plugins/builtin/company-attention/evidence.ts +16 -3
  386. package/src/plugins/builtin/company-attention/headless.ts +101 -5
  387. package/src/plugins/builtin/company-attention/index.tsx +6 -3
  388. package/src/plugins/builtin/company-attention/model.ts +33 -7
  389. package/src/plugins/builtin/company-attention/moves-model.ts +253 -0
  390. package/src/plugins/builtin/company-attention/moves.tsx +159 -0
  391. package/src/plugins/builtin/company-attention/pane.tsx +79 -23
  392. package/src/plugins/builtin/company-attention/signals-model.ts +229 -0
  393. package/src/plugins/builtin/company-attention/signals.tsx +160 -0
  394. package/src/plugins/builtin/company-kpis/headless.ts +3 -2
  395. package/src/plugins/builtin/company-kpis/model.ts +2 -2
  396. package/src/plugins/builtin/company-kpis/pane.tsx +1 -1
  397. package/src/plugins/builtin/composite-plugins.ts +2 -1
  398. package/src/plugins/builtin/congress-trades/headless.ts +1 -1
  399. package/src/plugins/builtin/connections/index.ts +1 -0
  400. package/src/plugins/builtin/correlation/headless-schema.ts +3 -2
  401. package/src/plugins/builtin/correlation/headless.ts +5 -3
  402. package/src/plugins/builtin/cot/headless.ts +28 -6
  403. package/src/plugins/builtin/cot/model.ts +12 -0
  404. package/src/plugins/builtin/cot/pane.tsx +3 -2
  405. package/src/plugins/builtin/cpi/headless.ts +5 -1
  406. package/src/plugins/builtin/cpi/pane.tsx +11 -9
  407. package/src/plugins/builtin/credit-boards/model.ts +2 -0
  408. package/src/plugins/builtin/credit-documents/headless.ts +1 -1
  409. package/src/plugins/builtin/crypto-board/client.ts +86 -0
  410. package/src/plugins/builtin/crypto-board/defi-view.tsx +218 -0
  411. package/src/plugins/builtin/crypto-board/defi.ts +239 -0
  412. package/src/plugins/builtin/crypto-board/headless.ts +117 -6
  413. package/src/plugins/builtin/crypto-board/index.tsx +8 -3
  414. package/src/plugins/builtin/crypto-board/model.ts +40 -8
  415. package/src/plugins/builtin/crypto-board/pane.tsx +55 -21
  416. package/src/plugins/builtin/crypto-board/series.ts +19 -1
  417. package/src/plugins/builtin/crypto-etf-flows/client.ts +60 -0
  418. package/src/plugins/builtin/crypto-etf-flows/headless.ts +132 -0
  419. package/src/plugins/builtin/crypto-etf-flows/index.tsx +44 -0
  420. package/src/plugins/builtin/crypto-etf-flows/model.ts +121 -0
  421. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +193 -0
  422. package/src/plugins/builtin/debug/index.tsx +1 -0
  423. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  424. package/src/plugins/builtin/docs/index.tsx +1 -1
  425. package/src/plugins/builtin/docs/pane.tsx +2 -2
  426. package/src/plugins/builtin/docs/targets.ts +1 -1
  427. package/src/plugins/builtin/doe/client.ts +5 -1
  428. package/src/plugins/builtin/doe/headless.ts +12 -6
  429. package/src/plugins/builtin/doe/model.ts +43 -6
  430. package/src/plugins/builtin/doe/pane.tsx +18 -11
  431. package/src/plugins/builtin/earnings/board-model.ts +13 -6
  432. package/src/plugins/builtin/earnings/board-table.ts +4 -1
  433. package/src/plugins/builtin/earnings/board.tsx +253 -50
  434. package/src/plugins/builtin/earnings/calendar-model.ts +188 -0
  435. package/src/plugins/builtin/earnings/calendar.tsx +338 -0
  436. package/src/plugins/builtin/earnings/client.ts +4 -3
  437. package/src/plugins/builtin/earnings/headless.ts +53 -15
  438. package/src/plugins/builtin/earnings/history-model.ts +53 -36
  439. package/src/plugins/builtin/earnings/index.tsx +15 -22
  440. package/src/plugins/builtin/earnings/model.ts +17 -12
  441. package/src/plugins/builtin/earnings/scope.ts +89 -0
  442. package/src/plugins/builtin/earnings-calls/calendar.tsx +310 -0
  443. package/src/plugins/builtin/earnings-calls/data.ts +146 -2
  444. package/src/plugins/builtin/earnings-calls/headless.ts +151 -9
  445. package/src/plugins/builtin/earnings-calls/index.tsx +3 -3
  446. package/src/plugins/builtin/earnings-calls/lock-note.ts +1 -1
  447. package/src/plugins/builtin/earnings-calls/pane.tsx +149 -31
  448. package/src/plugins/builtin/earnings-calls/upcoming.ts +195 -0
  449. package/src/plugins/builtin/earnings-ripple/pane.tsx +10 -5
  450. package/src/plugins/builtin/econ/calendar-model.ts +132 -20
  451. package/src/plugins/builtin/econ/detail-view.tsx +48 -10
  452. package/src/plugins/builtin/econ/fred-catalog.ts +23 -6
  453. package/src/plugins/builtin/econ/fred-series-map.ts +4 -0
  454. package/src/plugins/builtin/econ/index.tsx +112 -25
  455. package/src/plugins/builtin/econ/print-watch.ts +2 -1
  456. package/src/plugins/builtin/econ/types.ts +32 -1
  457. package/src/plugins/builtin/econ-statistics/client.ts +16 -3
  458. package/src/plugins/builtin/econ-statistics/defs.ts +4 -0
  459. package/src/plugins/builtin/econ-statistics/detail.tsx +2 -2
  460. package/src/plugins/builtin/econ-statistics/headless.ts +13 -2
  461. package/src/plugins/builtin/econ-statistics/index.tsx +3 -3
  462. package/src/plugins/builtin/econ-statistics/pane.tsx +2 -1
  463. package/src/plugins/builtin/econ-statistics/stats.ts +103 -0
  464. package/src/plugins/builtin/econ-statistics/view.ts +16 -1
  465. package/src/plugins/builtin/equity-screener/client.ts +21 -3
  466. package/src/plugins/builtin/equity-screener/headless.ts +68 -6
  467. package/src/plugins/builtin/equity-screener/live.ts +1 -1
  468. package/src/plugins/builtin/equity-screener/model.ts +6 -0
  469. package/src/plugins/builtin/equity-screener/pane.tsx +45 -10
  470. package/src/plugins/builtin/equity-screener/screen-check.ts +90 -0
  471. package/src/plugins/builtin/equity-screener/ticker-check.tsx +148 -0
  472. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -1
  473. package/src/plugins/builtin/estimate-revisions/lock-note.ts +3 -3
  474. package/src/plugins/builtin/estimate-revisions/model.ts +8 -10
  475. package/src/plugins/builtin/exposure/headless.ts +51 -6
  476. package/src/plugins/builtin/exposure/index.tsx +28 -2
  477. package/src/plugins/builtin/exposure/model.ts +7 -0
  478. package/src/plugins/builtin/exposure/pane.tsx +6 -1
  479. package/src/plugins/builtin/fear-greed/index.tsx +1 -1
  480. package/src/plugins/builtin/filing-events/index.tsx +1 -1
  481. package/src/plugins/builtin/futures/model.ts +10 -9
  482. package/src/plugins/builtin/futures/returns.ts +8 -36
  483. package/src/plugins/builtin/futures/table.tsx +1 -1
  484. package/src/plugins/builtin/futures-curve/client.ts +40 -9
  485. package/src/plugins/builtin/futures-curve/headless.ts +39 -17
  486. package/src/plugins/builtin/futures-curve/model.ts +258 -69
  487. package/src/plugins/builtin/futures-curve/pane.tsx +46 -17
  488. package/src/plugins/builtin/fx-matrix/client.ts +15 -8
  489. package/src/plugins/builtin/fx-matrix/headless.ts +32 -14
  490. package/src/plugins/builtin/fx-matrix/index.tsx +13 -11
  491. package/src/plugins/builtin/fx-matrix/live-legs.ts +22 -0
  492. package/src/plugins/builtin/fx-matrix/settings.ts +2 -1
  493. package/src/plugins/builtin/gpu/headless.ts +9 -4
  494. package/src/plugins/builtin/help/index.tsx +13 -3
  495. package/src/plugins/builtin/holders/beneficial-model.ts +8 -11
  496. package/src/plugins/builtin/holders/beneficial-report.ts +6 -5
  497. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  498. package/src/plugins/builtin/holders/client.ts +13 -1
  499. package/src/plugins/builtin/holders/format.ts +28 -17
  500. package/src/plugins/builtin/holders/headless.ts +46 -7
  501. package/src/plugins/builtin/holders/index-holders.ts +264 -0
  502. package/src/plugins/builtin/holders/pane.tsx +79 -15
  503. package/src/plugins/builtin/holders/report-rows.ts +35 -7
  504. package/src/plugins/builtin/holders/table-model.ts +2 -1
  505. package/src/plugins/builtin/holders/treemap.tsx +2 -2
  506. package/src/plugins/builtin/insider/headless.ts +1 -1
  507. package/src/plugins/builtin/insider/signals-client.ts +1 -1
  508. package/src/plugins/builtin/insider/signals.tsx +1 -1
  509. package/src/plugins/builtin/ipo-calendar/pane.tsx +7 -4
  510. package/src/plugins/builtin/iv-history/client.ts +20 -0
  511. package/src/plugins/builtin/iv-history/headless.ts +4 -4
  512. package/src/plugins/builtin/iv-history/index.tsx +1 -1
  513. package/src/plugins/builtin/iv-history/model.ts +13 -4
  514. package/src/plugins/builtin/iv-history/series.ts +3 -1
  515. package/src/plugins/builtin/jobs/lock-note.ts +1 -1
  516. package/src/plugins/builtin/jobs/model.ts +4 -15
  517. package/src/plugins/builtin/jobs/pane.tsx +7 -5
  518. package/src/plugins/builtin/jobs/share-bars.tsx +11 -5
  519. package/src/plugins/builtin/jodi/pane.tsx +10 -4
  520. package/src/plugins/builtin/layout-manager/index.tsx +1 -0
  521. package/src/plugins/builtin/macro-day/headless.ts +20 -7
  522. package/src/plugins/builtin/macro-day/index.tsx +3 -2
  523. package/src/plugins/builtin/macro-day/model.ts +29 -8
  524. package/src/plugins/builtin/macro-day/pane.tsx +46 -10
  525. package/src/plugins/builtin/macro-day/releases.ts +55 -13
  526. package/src/plugins/builtin/market-heatmap/data.ts +9 -2
  527. package/src/plugins/builtin/market-heatmap/evidence.ts +1 -1
  528. package/src/plugins/builtin/market-heatmap/headless.ts +9 -6
  529. package/src/plugins/builtin/market-heatmap/index.tsx +3 -3
  530. package/src/plugins/builtin/market-heatmap/model.ts +10 -7
  531. package/src/plugins/builtin/market-movers/client.ts +33 -19
  532. package/src/plugins/builtin/market-movers/headless.ts +8 -2
  533. package/src/plugins/builtin/market-movers/index.tsx +16 -10
  534. package/src/plugins/builtin/market-movers/screener.ts +29 -8
  535. package/src/plugins/builtin/market-movers/session-body.tsx +4 -2
  536. package/src/plugins/builtin/market-movers/session-table.ts +4 -2
  537. package/src/plugins/builtin/market-movers/table.ts +4 -1
  538. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  539. package/src/plugins/builtin/market-valuation/view.ts +5 -0
  540. package/src/plugins/builtin/members/headless.ts +6 -5
  541. package/src/plugins/builtin/members/pane.tsx +11 -4
  542. package/src/plugins/builtin/mna/client.ts +1 -0
  543. package/src/plugins/builtin/mna/detail.tsx +20 -32
  544. package/src/plugins/builtin/mna/distress/detail.tsx +16 -4
  545. package/src/plugins/builtin/mna/distress/model.ts +3 -2
  546. package/src/plugins/builtin/mna/distress/pane.tsx +7 -4
  547. package/src/plugins/builtin/mna/headless.ts +6 -13
  548. package/src/plugins/builtin/mna/model.ts +93 -15
  549. package/src/plugins/builtin/mna/pane.tsx +5 -4
  550. package/src/plugins/builtin/news/headless.ts +12 -6
  551. package/src/plugins/builtin/news/index.tsx +10 -1
  552. package/src/plugins/builtin/news/wire/index.tsx +3 -2
  553. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +26 -10
  554. package/src/plugins/builtin/news/wire/news/query-presets.ts +6 -2
  555. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -3
  556. package/src/plugins/builtin/notes/cli.ts +49 -14
  557. package/src/plugins/builtin/openfda/pane.tsx +1 -1
  558. package/src/plugins/builtin/options/carry.ts +2 -2
  559. package/src/plugins/builtin/options/index.tsx +1 -1
  560. package/src/plugins/builtin/options/view.tsx +10 -10
  561. package/src/plugins/builtin/options-calculator/headless.ts +46 -7
  562. package/src/plugins/builtin/options-scenario/client.ts +123 -28
  563. package/src/plugins/builtin/options-scenario/collar.ts +8 -6
  564. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +127 -0
  565. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +165 -0
  566. package/src/plugins/builtin/options-scenario/csp-screen.ts +258 -0
  567. package/src/plugins/builtin/options-scenario/csp.ts +101 -21
  568. package/src/plugins/builtin/options-scenario/headless.ts +36 -15
  569. package/src/plugins/builtin/options-scenario/hedge.ts +20 -14
  570. package/src/plugins/builtin/options-scenario/index.tsx +28 -3
  571. package/src/plugins/builtin/options-scenario/model.ts +13 -0
  572. package/src/plugins/builtin/options-scenario/pane.tsx +9 -5
  573. package/src/plugins/builtin/perps/equity-row.tsx +11 -3
  574. package/src/plugins/builtin/perps/freshness.ts +8 -5
  575. package/src/plugins/builtin/perps/headless.ts +3 -1
  576. package/src/plugins/builtin/perps/list-perpetuals.ts +25 -4
  577. package/src/plugins/builtin/perps/live-rows.ts +161 -0
  578. package/src/plugins/builtin/perps/markets-section.tsx +154 -0
  579. package/src/plugins/builtin/perps/pane.tsx +36 -17
  580. package/src/plugins/builtin/perps/venue-markets.ts +96 -0
  581. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -1
  582. package/src/plugins/builtin/plugin-marketplace/pane.tsx +143 -59
  583. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +72 -24
  584. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +158 -141
  585. package/src/plugins/builtin/portfolio-list/cli/render.ts +49 -20
  586. package/src/plugins/builtin/portfolio-list/cli/shared.ts +3 -3
  587. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +29 -24
  588. package/src/plugins/builtin/portfolio-list/headless.ts +6 -3
  589. package/src/plugins/builtin/portfolio-list/income.ts +13 -2
  590. package/src/plugins/builtin/portfolio-list/index.tsx +9 -2
  591. package/src/plugins/builtin/portfolio-list/pane/index.tsx +21 -8
  592. package/src/plugins/builtin/portfolio-list/pane/purchase-date-fx.ts +46 -0
  593. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +16 -9
  594. package/src/plugins/builtin/portfolio-list/summary/totals.ts +2 -15
  595. package/src/plugins/builtin/power/client.ts +4 -1
  596. package/src/plugins/builtin/power/evidence.ts +22 -7
  597. package/src/plugins/builtin/power/gas.tsx +5 -1
  598. package/src/plugins/builtin/power/pane.tsx +8 -1
  599. package/src/plugins/builtin/power/prices.tsx +10 -3
  600. package/src/plugins/builtin/research/analyst-headless.ts +25 -8
  601. package/src/plugins/builtin/research/analyst-model.ts +68 -3
  602. package/src/plugins/builtin/research/corporate-actions-pane.tsx +63 -14
  603. package/src/plugins/builtin/research/event-model.ts +3 -1
  604. package/src/plugins/builtin/research/event-sources.ts +12 -4
  605. package/src/plugins/builtin/research/index.tsx +2 -1
  606. package/src/plugins/builtin/research/relative-valuation-basis.ts +58 -0
  607. package/src/plugins/builtin/research/relative-valuation-columns.ts +31 -2
  608. package/src/plugins/builtin/research/relative-valuation-currency.ts +12 -5
  609. package/src/plugins/builtin/research/relative-valuation-headless.ts +84 -26
  610. package/src/plugins/builtin/research/relative-valuation-model.ts +88 -19
  611. package/src/plugins/builtin/research/relative-valuation-pane.tsx +76 -9
  612. package/src/plugins/builtin/research/relative-valuation-peers.ts +1 -1
  613. package/src/plugins/builtin/reverse-dcf/model.ts +6 -2
  614. package/src/plugins/builtin/risk-factors/index.tsx +2 -2
  615. package/src/plugins/builtin/scanner/lock-note.ts +1 -1
  616. package/src/plugins/builtin/sec/forms.ts +13 -0
  617. package/src/plugins/builtin/sec/fund-abroad.ts +32 -0
  618. package/src/plugins/builtin/sec/fund-facts.ts +68 -8
  619. package/src/plugins/builtin/sec/headless.ts +85 -8
  620. package/src/plugins/builtin/sec/index.tsx +58 -12
  621. package/src/plugins/builtin/sectors/client.ts +2 -1
  622. package/src/plugins/builtin/sectors/sector-model.ts +11 -15
  623. package/src/plugins/builtin/series-inventory.ts +2 -0
  624. package/src/plugins/builtin/shared/broker-reconnect.ts +107 -22
  625. package/src/plugins/builtin/shared/cloud-upgrade.ts +34 -6
  626. package/src/plugins/builtin/shared/earnings-calendar.ts +375 -0
  627. package/src/plugins/builtin/{earnings/estimate-basis.ts → shared/earnings-estimate-basis.ts} +1 -1
  628. package/src/plugins/builtin/shared/earnings-report.ts +3 -3
  629. package/src/plugins/builtin/shared/fx-rate-freshness.ts +23 -1
  630. package/src/plugins/builtin/shared/job-posting.ts +129 -0
  631. package/src/plugins/builtin/shared/pro-lock-note.ts +3 -3
  632. package/src/plugins/builtin/shared/report-freshness.ts +3 -0
  633. package/src/plugins/builtin/shared/research-trading.tsx +21 -0
  634. package/src/plugins/builtin/shared/series-cache.ts +2 -0
  635. package/src/plugins/builtin/shared/ticker-surface.ts +2 -1
  636. package/src/plugins/builtin/short-interest/client.ts +2 -0
  637. package/src/plugins/builtin/short-interest/headless.ts +41 -12
  638. package/src/plugins/builtin/short-interest/index.tsx +23 -1
  639. package/src/plugins/builtin/short-interest/model.ts +70 -19
  640. package/src/plugins/builtin/short-interest/pane.tsx +19 -2
  641. package/src/plugins/builtin/short-interest/publications-client.ts +82 -0
  642. package/src/plugins/builtin/short-interest/publications-headless.ts +29 -0
  643. package/src/plugins/builtin/short-interest/publications-model.ts +50 -0
  644. package/src/plugins/builtin/short-interest/publications-pane.tsx +66 -0
  645. package/src/plugins/builtin/short-interest/schedule.ts +35 -2
  646. package/src/plugins/builtin/short-interest/surface.tsx +11 -4
  647. package/src/plugins/builtin/short-interest/types.ts +9 -0
  648. package/src/plugins/builtin/short-interest/watch-client.ts +14 -8
  649. package/src/plugins/builtin/short-interest/watch-model.ts +12 -25
  650. package/src/plugins/builtin/short-interest/watch-pane.tsx +5 -1
  651. package/src/plugins/builtin/supply-chain/flow.tsx +33 -9
  652. package/src/plugins/builtin/supply-chain/model.ts +51 -3
  653. package/src/plugins/builtin/supply-chain/pane.tsx +78 -39
  654. package/src/plugins/builtin/supply-chain/row-evidence.tsx +1 -1
  655. package/src/plugins/builtin/themes/headless.ts +8 -5
  656. package/src/plugins/builtin/themes/model.ts +14 -4
  657. package/src/plugins/builtin/themes/pane.tsx +24 -5
  658. package/src/plugins/builtin/thirteenf/headless.ts +5 -5
  659. package/src/plugins/builtin/thirteenf/signals.ts +3 -1
  660. package/src/plugins/builtin/ticker-detail/adr-pair/headless.ts +123 -0
  661. package/src/plugins/builtin/ticker-detail/adr-pair/model.ts +132 -0
  662. package/src/plugins/builtin/ticker-detail/adr-pair/tab.tsx +87 -0
  663. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +40 -26
  664. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +8 -4
  665. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +67 -3
  666. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +112 -5
  667. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +17 -2
  668. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +21 -7
  669. package/src/plugins/builtin/ticker-detail/financials/model.ts +36 -1
  670. package/src/plugins/builtin/ticker-detail/financials/offerings.ts +56 -0
  671. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +62 -13
  672. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +30 -4
  673. package/src/plugins/builtin/ticker-detail/headless-schema.ts +14 -2
  674. package/src/plugins/builtin/ticker-detail/headless.ts +148 -35
  675. package/src/plugins/builtin/ticker-detail/index.tsx +37 -5
  676. package/src/plugins/builtin/ticker-detail/overview/model.ts +65 -10
  677. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +9 -1
  678. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +68 -11
  679. package/src/plugins/builtin/ticker-detail/pane.tsx +11 -0
  680. package/src/plugins/builtin/ticker-detail/quote-footer.ts +8 -3
  681. package/src/plugins/builtin/ticker-detail/research-tabs.ts +11 -0
  682. package/src/plugins/builtin/vol-surface/client.ts +5 -4
  683. package/src/plugins/builtin/vol-surface/headless.ts +12 -3
  684. package/src/plugins/builtin/vol-surface/model.ts +4 -2
  685. package/src/plugins/builtin/volatility/model.ts +5 -10
  686. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +4 -3
  687. package/src/plugins/builtin/world-venue-map/basemap.ts +3 -1
  688. package/src/plugins/builtin/world-venue-map/chokepoint-chart.ts +40 -0
  689. package/src/plugins/builtin/world-venue-map/evidence.ts +1 -1
  690. package/src/plugins/builtin/world-venue-map/geo-series.ts +90 -11
  691. package/src/plugins/builtin/world-venue-map/headless.ts +1 -1
  692. package/src/plugins/builtin/world-venue-map/index.ts +3 -26
  693. package/src/plugins/builtin/world-venue-map/layers.ts +1 -1
  694. package/src/plugins/builtin/world-venue-map/map.tsx +2 -1
  695. package/src/plugins/builtin/world-venue-map/pane.tsx +4 -2
  696. package/src/plugins/builtin/world-venue-map/venue-header.tsx +15 -4
  697. package/src/plugins/builtin/yield-curve/client.ts +25 -0
  698. package/src/plugins/builtin/yield-curve/curves.ts +7 -4
  699. package/src/plugins/builtin/yield-curve/headless.ts +38 -4
  700. package/src/plugins/builtin/yield-curve/history.ts +4 -3
  701. package/src/plugins/builtin/yield-curve/index.tsx +12 -6
  702. package/src/plugins/builtin/yield-curve/swaps-model.ts +93 -0
  703. package/src/plugins/builtin/yield-curve/swaps.tsx +99 -0
  704. package/src/plugins/bundle.ts +1 -1
  705. package/src/plugins/catalog-browser.ts +2 -1
  706. package/src/plugins/compat.ts +3 -3
  707. package/src/plugins/dependencies.ts +75 -14
  708. package/src/plugins/extracted-plugins.ts +132 -34
  709. package/src/plugins/host-link.ts +149 -34
  710. package/src/plugins/installer.ts +35 -10
  711. package/src/plugins/loader.ts +206 -38
  712. package/src/plugins/manager-node.ts +2 -2
  713. package/src/plugins/marketplace/deactivation.ts +22 -0
  714. package/src/plugins/marketplace/model.ts +2 -2
  715. package/src/plugins/marketplace/packs.ts +1 -1
  716. package/src/plugins/ownership.ts +1 -1
  717. package/src/plugins/peer-retry.ts +155 -0
  718. package/src/plugins/registry/contributions.ts +3 -1
  719. package/src/plugins/registry/index.ts +4 -3
  720. package/src/plugins/registry/plugin-state.ts +3 -0
  721. package/src/plugins/seed-notice.ts +20 -0
  722. package/src/plugins/shortcut-argument.ts +39 -0
  723. package/src/portfolio/allocation.ts +1 -1
  724. package/src/portfolio/collection-name.ts +31 -0
  725. package/src/portfolio/column-values.ts +10 -8
  726. package/src/portfolio/cost-fx.ts +296 -19
  727. package/src/portfolio/manual-lots.ts +75 -0
  728. package/src/portfolio/mutations.ts +60 -22
  729. package/src/portfolio/suffixed-listings.ts +209 -0
  730. package/src/portfolio/totals-currency.ts +24 -0
  731. package/src/public/__snapshots__/public-api.test.ts.snap +14 -0
  732. package/src/public/time-series.ts +11 -8
  733. package/src/remote/schema.ts +1 -1
  734. package/src/renderers/browser/config-host.ts +3 -0
  735. package/src/renderers/browser/main.tsx +23 -3
  736. package/src/renderers/dom/boot-frame-page.ts +187 -0
  737. package/src/renderers/dom/boot-frame.ts +237 -0
  738. package/src/renderers/dom/build-assets.ts +46 -8
  739. package/src/renderers/dom/dom-ui-host.tsx +4 -29
  740. package/src/renderers/dom/host/ascii-text.ts +16 -2
  741. package/src/renderers/dom/host/box.tsx +43 -2
  742. package/src/renderers/dom/host/chart-surface.tsx +2 -0
  743. package/src/renderers/dom/host/hls.chunk.ts +3 -0
  744. package/src/renderers/dom/host/image-surface.tsx +49 -0
  745. package/src/renderers/dom/host/media-surface.tsx +24 -18
  746. package/src/renderers/dom/host/mouse.ts +30 -0
  747. package/src/renderers/dom/host/size-observer.ts +1 -1
  748. package/src/renderers/dom/host/style.ts +1 -1
  749. package/src/renderers/dom/styles.css +76 -0
  750. package/src/renderers/dom/test-utils.tsx +2 -1
  751. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -3
  752. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -0
  753. package/src/renderers/electrobun/bun/desktop/backend.ts +46 -3
  754. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +1 -1
  755. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -3
  756. package/src/renderers/electrobun/bun/desktop/initialization.ts +81 -18
  757. package/src/renderers/electrobun/bun/external-plugins.ts +29 -7
  758. package/src/renderers/electrobun/bun/index.ts +11 -1
  759. package/src/renderers/electrobun/bun/window/boot-frame.ts +67 -0
  760. package/src/renderers/electrobun/bun/window/create.ts +4 -1
  761. package/src/renderers/electrobun/shared/boot-frame.ts +7 -0
  762. package/src/renderers/electrobun/shared/protocol.ts +6 -1
  763. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  764. package/src/renderers/electrobun/view/config-host.ts +3 -0
  765. package/src/renderers/electrobun/view/fatal-screen.tsx +1 -1
  766. package/src/renderers/electrobun/view/main.tsx +30 -4
  767. package/src/renderers/electrobun/view/news-source.ts +5 -5
  768. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -1
  769. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  770. package/src/renderers/opentui/browser-open.ts +88 -0
  771. package/src/renderers/opentui/host.tsx +5 -14
  772. package/src/renderers/opentui/image/loader.ts +3 -2
  773. package/src/renderers/opentui/native-smoke.ts +10 -1
  774. package/src/renderers/opentui/pane-image/index.ts +2 -1
  775. package/src/renderers/opentui/render-lib.ts +253 -0
  776. package/src/renderers/opentui/render-timing-smoke.ts +1 -1
  777. package/src/renderers/opentui/start.tsx +1 -1
  778. package/src/renderers/share/layout-view.tsx +2 -2
  779. package/src/renderers/share/main.tsx +4 -4
  780. package/src/renderers/share/view.tsx +1 -1
  781. package/src/series/resolve.ts +22 -6
  782. package/src/series/standalone.ts +30 -2
  783. package/src/series/types.ts +8 -1
  784. package/src/shares/api.ts +4 -1
  785. package/src/shares/pane.ts +1 -1
  786. package/src/sources/gloomberb-cloud/fred-series.ts +46 -1
  787. package/src/sources/gloomberb-cloud/index.ts +32 -16
  788. package/src/sources/gloomberb-cloud/news.ts +39 -0
  789. package/src/sources/gloomberb-cloud/normalizers.ts +14 -1
  790. package/src/sources/gloomberb-cloud/perpetuals.ts +49 -1
  791. package/src/sources/listing-symbols.ts +37 -0
  792. package/src/sources/provider-errors.ts +16 -2
  793. package/src/sources/provider-router/financial-routes.ts +21 -3
  794. package/src/sources/provider-router/financials.ts +15 -2
  795. package/src/sources/provider-router/history.ts +30 -8
  796. package/src/sources/provider-router/primary.ts +1 -1
  797. package/src/state/app/bootstrap.ts +23 -0
  798. package/src/state/hooks/inline-tickers.ts +30 -5
  799. package/src/sync/controller.ts +1 -1
  800. package/src/sync/core-contributors.ts +46 -14
  801. package/src/team/collections-sync.ts +2 -1
  802. package/src/team/collections.ts +178 -17
  803. package/src/telemetry/crash-reports.ts +17 -4
  804. package/src/test-support/builtin-panes.ts +1 -1
  805. package/src/theme/colors.ts +3 -30
  806. package/src/theme/ui-tokens.ts +37 -1
  807. package/src/tickers/company-name-listing.ts +210 -33
  808. package/src/tickers/index-roots.ts +47 -0
  809. package/src/tickers/instrument-label.ts +5 -1
  810. package/src/tickers/metadata.ts +10 -0
  811. package/src/tickers/perpetual-listing.ts +5 -2
  812. package/src/tickers/search/home-listing.ts +75 -0
  813. package/src/tickers/search/index.ts +76 -27
  814. package/src/tickers/search/ranking.ts +54 -4
  815. package/src/tickers/search/upsert.ts +29 -0
  816. package/src/tickers/terminal-codes.ts +416 -40
  817. package/src/time-series/bar-aggregation.ts +243 -0
  818. package/src/time-series/base-series.ts +307 -0
  819. package/src/time-series/chart-bounds.ts +349 -0
  820. package/src/time-series/chart-seed.ts +142 -0
  821. package/src/time-series/economic.ts +5 -0
  822. package/src/time-series/fundamentals.ts +18 -3
  823. package/src/time-series/history-acquisition.ts +499 -0
  824. package/src/time-series/leverage.ts +100 -0
  825. package/src/time-series/market-resolution.ts +4 -2
  826. package/src/time-series/market.ts +2 -1
  827. package/src/time-series/price-comparison.ts +175 -30
  828. package/src/time-series/range.ts +9 -1
  829. package/src/time-series/resolution.ts +174 -16
  830. package/src/time-series/resolve-context.ts +157 -0
  831. package/src/time-series/resolve.ts +126 -1577
  832. package/src/time-series/series-name.ts +38 -0
  833. package/src/time-series/series-presentation.ts +208 -0
  834. package/src/time-series/spec.ts +11 -6
  835. package/src/time-series/studies.ts +109 -12
  836. package/src/time-series/tail-reconcile.ts +121 -0
  837. package/src/time-series/types.ts +29 -2
  838. package/src/time-series/use-chart-resolution.ts +4 -5
  839. package/src/types/broker.ts +10 -0
  840. package/src/types/config.ts +14 -0
  841. package/src/types/data-provider.ts +14 -0
  842. package/src/types/exchange-rate.ts +6 -0
  843. package/src/types/financials.ts +104 -7
  844. package/src/types/headless.ts +76 -6
  845. package/src/types/instrument.ts +3 -0
  846. package/src/types/plugin.ts +65 -8
  847. package/src/types/price-history.ts +2 -0
  848. package/src/types/ticker.ts +13 -0
  849. package/src/types/trading.ts +59 -1
  850. package/src/ui/ascii-font.ts +13 -1
  851. package/src/ui/host.tsx +6 -1
  852. package/src/updater/command-names.ts +175 -0
  853. package/src/updater/index.ts +10 -5
  854. package/src/utils/calendar-date.ts +32 -15
  855. package/src/utils/cash-runway.ts +241 -0
  856. package/src/utils/central-banks.ts +42 -0
  857. package/src/utils/cli-output.ts +259 -80
  858. package/src/utils/contract-month.ts +16 -0
  859. package/src/utils/crypto-pair.ts +61 -3
  860. package/src/utils/currency-units.ts +29 -0
  861. package/src/utils/datetime-format.ts +10 -0
  862. package/src/utils/debug-log.ts +1 -1
  863. package/src/utils/decimal-comma.ts +39 -6
  864. package/src/utils/exchange-announcements.ts +71 -0
  865. package/src/utils/exchange-rate-snapshot.ts +10 -0
  866. package/src/utils/financial-statements.ts +83 -4
  867. package/src/utils/format.ts +34 -16
  868. package/src/utils/fundamentals.ts +150 -34
  869. package/src/utils/fx-market-hours.ts +10 -1
  870. package/src/utils/latest-financial-period.ts +8 -0
  871. package/src/utils/linux-libc.ts +54 -0
  872. package/src/utils/market-capitalization.ts +111 -15
  873. package/src/utils/max-drawdown.ts +64 -0
  874. package/src/utils/network-failure.ts +83 -0
  875. package/src/utils/option-expiry.ts +139 -7
  876. package/src/utils/price-earnings.ts +11 -5
  877. package/src/utils/price-history.ts +18 -8
  878. package/src/utils/price-window.ts +131 -0
  879. package/src/utils/release-date.ts +47 -0
  880. package/src/utils/sec.ts +3 -0
  881. package/src/utils/throttled-fetch.ts +11 -4
  882. package/src/utils/trailing-eps.ts +67 -0
  883. package/src/utils/typed-text.ts +21 -0
  884. package/src/utils/utc-time.ts +24 -1
  885. package/src/version.ts +6 -0
  886. package/src/cli/pane-functions/screenshot.ts +0 -2122
@@ -3,7 +3,7 @@ import type { OptionsChain } from "../../../types/financials";
3
3
  import type { HeadlessPaneEntry } from "../../../types/headless";
4
4
  import { numberErrorMessage } from "../../../utils/decimal-comma";
5
5
  import { formatCompact } from "../../../utils/format";
6
- import { UNKNOWN_CURRENCY, type ScenarioPosition } from "./model";
6
+ import { amountUnit, exportPercent, UNKNOWN_CURRENCY, type ScenarioPosition } from "./model";
7
7
 
8
8
  /**
9
9
  * Hedge budget sizing (docs/research-data.md#options-scenario-analysis): how
@@ -118,9 +118,10 @@ export interface HedgeQuote { bid: number; ask: number }
118
118
 
119
119
  /**
120
120
  * The bid and ask of each leg a strategy seeded from the chain, by leg id: the
121
- * chain's, or the leg's streamed quote when the pane follows the market.
121
+ * chain's, or the leg's streamed quote when the pane follows the market. A
122
+ * typed leg has none.
122
123
  */
123
- function hedgeLegQuotes(
124
+ export function seededLegQuotes(
124
125
  position: ScenarioPosition, chain: OptionsChain | null | undefined, liveQuotes?: ReadonlyMap<string, HedgeQuote>,
125
126
  ): Map<string, HedgeQuote> {
126
127
  const contracts = new Map([...(chain?.calls ?? []), ...(chain?.puts ?? [])].map((contract) => [contract.contractSymbol, contract]));
@@ -206,7 +207,7 @@ export function scenarioHedgeBudget(
206
207
  const inputs = hedgeInputsFromSettings(settings);
207
208
  if (!inputs) return null;
208
209
  if (!position?.legs.length) throw new Error("A hedge budget needs a position; add --legs or select --strategy.");
209
- return sizeHedgeBudget(position, inputs, { quotes: hedgeLegQuotes(position, market?.chain, market?.liveQuotes),
210
+ return sizeHedgeBudget(position, inputs, { quotes: seededLegQuotes(position, market?.chain, market?.liveQuotes),
210
211
  spot: hedgeSpot(position, market?.spot, supplied(settings.spot)) });
211
212
  }
212
213
 
@@ -246,21 +247,26 @@ function costText(cost: number, sizingPrice: number | null, multiplier: number,
246
247
  /** The report's Hedge budget section; `value` keeps full precision, `formatted` is what text output prints. */
247
248
  export function hedgeReportEntries(hedge: HedgeBudget): HeadlessPaneEntry[] {
248
249
  const unit = hedge.currency && hedge.currency !== UNKNOWN_CURRENCY ? ` ${hedge.currency}` : "";
250
+ // Each figure's `value` is in its `unit`, a share of NAV in percent, so an export writes the number and its unit.
251
+ const amount = { unit: amountUnit(hedge.currency) };
249
252
  return [
250
- { key: "nav", label: "NAV", value: hedge.nav, formatted: `${money(hedge.nav)}${unit}` },
251
- { key: "budget", label: "Budget", value: hedge.budget, formatted: `${money(hedge.budget)}${unit} (${formatBps(hedge.budgetBps)} of NAV)` },
252
- { key: "sizingPrice", label: "Sizing price", value: hedge.sizingPrice,
253
+ { key: "nav", label: "NAV", value: hedge.nav, ...amount, formatted: `${money(hedge.nav)}${unit}` },
254
+ { key: "budget", label: "Budget", value: hedge.budget, ...amount, formatted: `${money(hedge.budget)}${unit} (${formatBps(hedge.budgetBps)} of NAV)` },
255
+ { key: "sizingPrice", label: "Sizing price", value: hedge.sizingPrice, unit: `${amount.unit} per share`,
253
256
  formatted: hedge.sizingPrice == null ? "--" : describePrice(hedge.basis, hedge.singleLeg, hedge.sizingPrice) },
254
- { key: "costPerContract", label: "Cost per contract", value: hedge.costPerContract, formatted: costText(hedge.costPerContract, hedge.sizingPrice, hedge.multiplier, unit) },
255
- { key: "contracts", label: "Contracts", value: hedge.contracts, formatted: hedge.contracts.toLocaleString("en-US") },
256
- { key: "premium", label: "Premium spent", value: hedge.premium, formatted: `${money(hedge.premium)}${unit}` },
257
- { key: "premiumBps", label: "Premium / NAV", value: hedge.premiumBps, formatted: `${formatBps(hedge.premiumBps)} (${(hedge.premiumBps / 100).toFixed(2)}% of NAV)` },
258
- { key: "notional", label: "Notional protected", value: hedge.notional,
257
+ { key: "costPerContract", label: "Cost per contract", value: hedge.costPerContract, ...amount,
258
+ formatted: costText(hedge.costPerContract, hedge.sizingPrice, hedge.multiplier, unit) },
259
+ { key: "contracts", label: "Contracts", value: hedge.contracts, unit: "contracts", formatted: hedge.contracts.toLocaleString("en-US") },
260
+ { key: "premium", label: "Premium spent", value: hedge.premium, ...amount, formatted: `${money(hedge.premium)}${unit}` },
261
+ { key: "premiumBps", label: "Premium / NAV", value: hedge.premiumBps, unit: "bp",
262
+ formatted: `${formatBps(hedge.premiumBps)} (${(hedge.premiumBps / 100).toFixed(2)}% of NAV)` },
263
+ { key: "notional", label: "Notional protected", value: hedge.notional, ...amount,
259
264
  formatted: `${money(hedge.notional)}${unit} (${hedge.contracts.toLocaleString("en-US")} x ${hedge.multiplier} x ${formatHedgePrice(hedge.spot)})` },
260
- { key: "notionalOfNav", label: "Notional / NAV", value: hedge.notionalOfNav, formatted: percent(hedge.notionalOfNav) },
265
+ { key: "notionalOfNav", label: "Notional / NAV", value: exportPercent(hedge.notionalOfNav), unit: "%", formatted: percent(hedge.notionalOfNav) },
261
266
  hedge.coverage == null || hedge.sleeve == null
262
267
  ? { key: "coverage", label: "Coverage", value: null, formatted: "No equity sleeve given; add --sleeve <amount> to read notional against it" }
263
- : { key: "coverage", label: "Coverage", value: hedge.coverage, formatted: `${percent(hedge.coverage)} of a ${money(hedge.sleeve)}${unit} sleeve` },
268
+ : { key: "coverage", label: "Coverage", value: exportPercent(hedge.coverage), unit: "%",
269
+ formatted: `${percent(hedge.coverage)} of a ${money(hedge.sleeve)}${unit} sleeve` },
264
270
  ];
265
271
  }
266
272
 
@@ -2,8 +2,10 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
3
3
  import { OptionsScenarioPane } from "./pane";
4
4
  import { optionsScenarioHeadless } from "./headless";
5
+ import { CspScreenPane } from "./csp-screen-pane";
6
+ import { cspScreenHeadless } from "./csp-screen-headless";
5
7
 
6
- const scenarioSettingKeys = ["legs", "strategy", "strike", "putStrike", "callStrike", "shares", "expiration", "spot", "rate", "dividendYield", "currency", "asOf", "date", "volShift",
8
+ const scenarioSettingKeys = ["legs", "strategy", "strike", "delta", "putStrike", "callStrike", "shares", "expiration", "spot", "rate", "dividendYield", "currency", "asOf", "date", "volShift",
7
9
  "spotRange", "seedLeg", "nav", "budgetBps", "sleeve"];
8
10
 
9
11
  export const optionsScenarioModule: PluginModule = {
@@ -23,11 +25,34 @@ export const optionsScenarioModule: PluginModule = {
23
25
  description: "The equity the hedge protects, for coverage: notional protected over the sleeve." },
24
26
  ],
25
27
  },
28
+ }, {
29
+ id: "csp-screen", name: "Cash-Secured Put Screen", icon: "V", component: CspScreenPane,
30
+ defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 144, height: 24 },
31
+ tableExport: true, headless: cspScreenHeadless,
32
+ settings: {
33
+ title: "Cash-Secured Put Screen",
34
+ fields: [
35
+ { key: "symbols", label: "Symbols", type: "text", placeholder: "JPM, GS, KO" },
36
+ { key: "maxSpread", label: "Wide spread over (%)", type: "text", placeholder: "10",
37
+ description: "A put whose bid/ask spread is over this percent of its mid is marked wide. It is still ranked." },
38
+ ],
39
+ },
26
40
  }],
27
- paneTemplates: [createTickerSurfacePaneTemplate({
41
+ paneTemplates: [{
42
+ id: "csp-screen-pane", paneId: "csp-screen", label: "Cash-Secured Put Screen",
43
+ description: "Cash-secured puts across a list, ranked by annualised yield at the bid, with % OTM, delta, IV rank, spread and an earnings flag.",
44
+ // The put-selling phrases lead here first, then to OSA for one name.
45
+ keywords: ["csp", "cash secured put", "cash-secured puts", "put selling", "selling puts", "sell put", "sell puts", "short put", "wheel",
46
+ "put screen", "premium yield"],
47
+ shortcut: { prefix: "CSP", argKind: "ticker-list" as const }, headless: cspScreenHeadless,
48
+ createInstance: (_context, options) => ({
49
+ title: "CSP", placement: "floating" as const,
50
+ settings: options?.symbols?.length ? { symbols: options.symbols.join(",") } : {},
51
+ }),
52
+ }, createTickerSurfacePaneTemplate({
28
53
  id: "options-scenario-pane", paneId: "options-scenario", label: "Options Scenario",
29
54
  description: "Build multi-leg positions and compare scenario P&L, payoff and aggregate Greeks.",
30
- // OSA owns the phrases; OMON and VCA carry the same words without them, so a put-selling search finds all three, OSA first.
55
+ // OSA carries the put-selling phrases after CSP, which ranks names on them; OMON and VCA carry the same words, so a search finds all four.
31
56
  keywords: ["osa", "options", "scenario", "payoff", "strategy", "greeks", "hedge", "hedging", "collar", "protective put", "put spread",
32
57
  "cash secured put", "csp", "put selling", "sell put", "short put", "wheel"],
33
58
  shortcut: "OSA", publicShare: true,
@@ -160,6 +160,19 @@ export function validateScenarioInputs(position: ScenarioPosition): string | nul
160
160
  /** The stored stand-in for a currency nothing names; shown as `currency unknown`. */
161
161
  export const UNKNOWN_CURRENCY = "UNKNOWN";
162
162
  export const currencyLabel = (currency: string) => currency === UNKNOWN_CURRENCY ? "currency unknown" : currency;
163
+ /** The unit an export names beside an amount: the currency code, or `currency unknown`. */
164
+ export const amountUnit = (currency: string) => currency && currency !== UNKNOWN_CURRENCY ? currency : "currency unknown";
165
+
166
+ /**
167
+ * A fraction as the percent an export carries: a rate of 0.041 is 4.1, not
168
+ * the 4.1000000000000005 the product leaves. Twelve significant digits keep
169
+ * every digit a figure has; null stays null.
170
+ */
171
+ export function exportPercent(fraction: number): number;
172
+ export function exportPercent(fraction: number | null | undefined): number | null;
173
+ export function exportPercent(fraction: number | null | undefined): number | null {
174
+ return fraction == null ? null : Number((fraction * 100).toPrecision(12));
175
+ }
163
176
 
164
177
  /**
165
178
  * The position's currency: the one typed, else the underlying's quote
@@ -38,8 +38,8 @@ import { buildScenario, currencyLabel, scenarioCurrency, scenarioValueUnit, UNKN
38
38
  type ScenarioControls, validatePosition } from "./model";
39
39
  import { EMPTY_SAVED_STRATEGIES, restoreSavedStrategies, type SavedScenarioStrategy } from "./state";
40
40
  import { useScenarioEvidence } from "./evidence";
41
- import { hedgeStatItems, scenarioHedgeBudget, type HedgeQuote } from "./hedge";
42
- import { readOptionExpiration } from "../../../utils/option-expiry";
41
+ import { hedgeStatItems, scenarioHedgeBudget, seededLegQuotes, type HedgeQuote } from "./hedge";
42
+ import { isLiveOptionExpiry, readOptionExpiration } from "../../../utils/option-expiry";
43
43
  import { parsePublicTickerKey } from "../../../utils/exchanges";
44
44
 
45
45
  const EMPTY_ERRORS: string[] = [];
@@ -65,7 +65,9 @@ export function OptionsScenarioPane({ width, height, focused }: PaneProps) {
65
65
  const underlying = listed.symbol;
66
66
  const exchange = listed.exchange || target?.effectiveExchange || undefined;
67
67
  // A strategy seeded for an expiry (`OSA SPY --strategy put --expiration 2027-01-15`) opens that expiry's chain.
68
- const [expiration, setExpiration] = usePluginPaneState<number | null>("chainExpiration", readOptionExpiration(settings.expiration));
68
+ const [pickedExpiration, setExpiration] = usePluginPaneState<number | null>("chainExpiration", readOptionExpiration(settings.expiration));
69
+ // A picked expiry that has since lapsed gives way to the nearest live one, as OMON's does.
70
+ const expiration = pickedExpiration != null && isLiveOptionExpiry(pickedExpiration, Date.now(), exchange) ? pickedExpiration : null;
69
71
  const [tab, setTab] = usePluginPaneState("activeTabId", "payoff");
70
72
  const [detail, setDetail] = useState<"chain" | "leg" | "save" | "inputs" | null>(null);
71
73
  const [editingLeg, setEditingLeg] = useState<ScenarioLeg | null>(null);
@@ -166,9 +168,11 @@ export function OptionsScenarioPane({ width, height, focused }: PaneProps) {
166
168
  const earningsDate = frozenEarnings ? settings.scenarioEarningsDate as string | null : earnings.data;
167
169
  // A position following the market is at the live print, which the footer names with its feed.
168
170
  const spotBasis = scenario && !live ? scenarioSpotBasis(settings, scenario.position, market) : null;
171
+ // The put's bid and ask: its streamed quote while the pane follows the market, else the chain's.
169
172
  const csp = useMemo(() => cspStrategy && scenario && !earnings.loading
170
- ? cashSecuredPut(scenario.position, live?.basis ?? spotBasis ?? "last price", earningsDate) : null,
171
- [cspStrategy, scenario, earnings.loading, earningsDate, live?.basis, spotBasis]);
173
+ ? cashSecuredPut(scenario.position, live?.basis ?? spotBasis ?? "last price", earningsDate,
174
+ seededLegQuotes(scenario.position, market?.chain, live?.quotes).get(scenario.position.legs[0]?.id ?? "")) : null,
175
+ [cspStrategy, scenario, earnings.loading, earningsDate, live?.basis, live?.quotes, spotBasis, market?.chain]);
172
176
  // A collar seeded, or one typed with the shares it hedges, as the report reads it.
173
177
  const collarStrategy = settings.strategy === "collar";
174
178
  const collar = useMemo(() => scenario && (collarStrategy || scenario.position.underlying)
@@ -1,4 +1,4 @@
1
- import { useCallback } from "react";
1
+ import { useCallback, useMemo } from "react";
2
2
  import { isAccessDenied } from "../../../api-client/errors";
3
3
  import { usePlanAccess } from "../../../api-client/plan-access";
4
4
  import type { PerpBoardRow } from "../../../api-client/perps";
@@ -10,7 +10,9 @@ import { perpEquityIdentity } from "./equity-identity";
10
10
  import { perpetualListing } from "../../../tickers/perpetual-listing";
11
11
  import { loadPerpSelection, loadPerpsEquity } from "./client";
12
12
  import { compact, percent, perpDescription, price, readingTime } from "./model";
13
+ import { useLivePerpRows } from "./live-rows";
13
14
  import { openPerpLabel, useOpenPerpMarket } from "./open-market";
15
+ import { mergeVenueMarkets } from "./venue-markets";
14
16
 
15
17
  /**
16
18
  * A stock's perpetuals, or for a perpetual market itself its own contract: funding, open interest and when the venue
@@ -34,9 +36,15 @@ export function PerpEquityRow({ symbol, exchange, instrumentType, maxRows = 3, w
34
36
  : loadPerpsEquity({ symbol: listingSymbol!, exchange: listingExchange! }, accessKey, force), [marketId, listingSymbol, listingExchange, accessKey]);
35
37
  const resource = useAsyncResource(enabled ? loader : null, { clearOnError: isAccessDenied });
36
38
  useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
37
- const rows = resource.data?.payload.rows.filter((row) => !row.delisted && (!marketId || row.marketId === marketId)) ?? [];
39
+ // A stock's contracts one per venue, deepest first, as DES lists them; the ones shown follow the quote stream.
40
+ const fetched = useMemo(() => {
41
+ const rows = resource.data?.payload.rows ?? [];
42
+ return marketId ? rows.filter((row) => !row.delisted && row.marketId === marketId) : mergeVenueMarkets(rows).map((market) => market.primary);
43
+ }, [marketId, resource.data]);
44
+ const shown = useMemo(() => fetched.slice(0, maxRows), [fetched, maxRows]);
45
+ const { rows } = useLivePerpRows(shown, { surface: marketId ? "detail" : "monitor", enabled });
38
46
  if (!enabled || !rows.length) return null;
39
- return <>{rows.slice(0, maxRows).map((row) => <PerpRow key={row.marketId} row={row} stale={!!resource.data?.stale} width={width} own={!!marketId} />)}</>;
47
+ return <>{rows.map((row) => <PerpRow key={row.marketId} row={row} stale={!!resource.data?.stale} width={width} own={!!marketId} />)}</>;
40
48
  }
41
49
 
42
50
  /** The parts that fit `width`, dropped from the end; all of them when the width is not known. */
@@ -48,18 +48,21 @@ export function fundingNote(rows: readonly Funded[], now = Date.now()): string |
48
48
  }
49
49
 
50
50
  /**
51
- * How current a view of perpetual markets is: each venue's own newest observation, polled rather than streamed and
52
- * stale after an hour without a poll, and what the funding figures it shows (`funding`) are. The report, the capture
53
- * and the pane footer all read this, so they end with the same line.
51
+ * How current a view of perpetual markets is: each venue's own newest observation, read once a minute (live while
52
+ * every row followed has a price from its venue's socket within the minute) and stale after an hour without a read, and
53
+ * what the funding figures it shows (`funding`) are. The report, the capture and the pane footer all read this, so they
54
+ * end with the same line.
54
55
  */
55
- export function perpFreshness(rows: readonly Dated[], { funding = [], venue = "all", now = Date.now() }: {
56
+ export function perpFreshness(rows: readonly Dated[], { funding = [], venue = "all", now = Date.now(), live = false }: {
56
57
  funding?: readonly Funded[]; venue?: string; now?: number;
58
+ /** Every row followed moves with its venue's socket: the view is a live feed, not the minute's read. */
59
+ live?: boolean;
57
60
  } = {}): HeadlessPaneFreshness {
58
61
  const sources = venuesOf(rows).map((id) => ({ source: venueName(id), asOf: new Date(Math.max(...rows.filter((row) => row.venue === id)
59
62
  .map((row) => Date.parse(row.observedAt)))).toISOString() }));
60
63
  const source = sources.length ? sources.map((entry) => entry.source).join(", ") : venue !== "all" ? venueName(venue) : null;
61
64
  const note = fundingNote(funding, now);
62
- return { ...(source ? { source } : {}), sources, status: "delayed", maxAgeMinutes: 60, ...(note ? { note } : {}) };
65
+ return { ...(source ? { source } : {}), sources, status: live ? "live" : "delayed", maxAgeMinutes: 60, ...(note ? { note } : {}) };
63
66
  }
64
67
 
65
68
  /** What a capture reads of a row: when it was observed and whether the venue has stopped answering for it. */
@@ -89,8 +89,10 @@ const aprNote = (rows: readonly PerpBoardRow[]) => {
89
89
  const requestedOf = (args: HeadlessPaneLoadArgs) => String(Array.isArray(args.argument) ? args.argument.join(" ") : args.argument ?? "").trim();
90
90
  // No tab named: a market opens its history, nothing opens the board, as the pane does.
91
91
  const tabOf = (args: HeadlessPaneLoadArgs) => PERP_TABS.find((tab) => tab === args.options.tab) ?? openingTab(requestedOf(args));
92
+ // A free account's market opens its latest values under the title, so the title names the function, not the Pro history: a table the free view
93
+ // locks is left out and named on the report's one Pro line (`lockedTable`), never marked on the title over the free ones.
92
94
  const TITLES: Record<typeof PERP_TABS[number], string> = { board: "Perpetual markets", rankings: "Perpetual rankings", compare: "Perpetual contracts across venues",
93
- history: "Perpetual history (Pro)", evidence: "Perpetual evidence (Pro)" };
95
+ history: "Perpetuals", evidence: "Perpetual evidence" };
94
96
  const copy = (rows: readonly PerpBoardRow[]) => rows.map((row) => ({ ...row }));
95
97
  const more = (count: number, noun: string) => `${count.toLocaleString("en-US")} more ${noun} need Gloom Pro`;
96
98
  // A table the free view leaves empty is locked: the report leaves it out and its one Pro line says what Pro adds.
@@ -8,6 +8,7 @@ import { useResearchCloudSession } from "../shared/research-cloud-session";
8
8
  import { perpetualListing } from "../../../tickers/perpetual-listing";
9
9
  import { loadPerpSelection, loadPerpsEquities } from "./client";
10
10
  import { perpEquityIdentity } from "./equity-identity";
11
+ import { useLivePerpRows } from "./live-rows";
11
12
 
12
13
  const STOCK_TYPES = new Set(["STK", "EQUITY", "STOCK", "COMMON STOCK"]);
13
14
  const listingKey = (listing: PerpEquityListing) => `${listing.symbol}:${listing.exchange}`;
@@ -26,6 +27,7 @@ function rowListing(ticker: TickerRecord): PerpEquityListing | null {
26
27
  const rowMarket = (ticker: TickerRecord) => perpetualListing(ticker.metadata.ticker, ticker.metadata.exchange)?.marketId ?? null;
27
28
 
28
29
  const NO_PERPETUALS: ListPerpetuals = { markets: new Map(), version: 0, stale: false };
30
+ const NO_SYMBOLS: ReadonlySet<string> = new Set();
29
31
 
30
32
  export interface ListPerpetuals {
31
33
  /** Each row's contract by ticker, null for none; a row is missing while its first answer loads. */
@@ -55,7 +57,7 @@ async function loadOwnMarket(marketId: string, access: string, force: boolean):
55
57
  * A row's figures never wait on anything else: rows without a stock listing read as no perpetual at once, and a
56
58
  * failed or refused lookup leaves every row without one, as DES leaves out its perpetual row.
57
59
  */
58
- export function useListPerpetuals(tickers: readonly TickerRecord[], enabled: boolean): ListPerpetuals {
60
+ export function useListPerpetuals(tickers: readonly TickerRecord[], enabled: boolean, liveSymbols: ReadonlySet<string> = NO_SYMBOLS): ListPerpetuals {
59
61
  const session = useResearchCloudSession();
60
62
  const plan = usePlanAccess();
61
63
  const accessKey = `${session.requestKey}:${plan.hasProAccess ? "pro" : "preview"}`;
@@ -98,7 +100,7 @@ export function useListPerpetuals(tickers: readonly TickerRecord[], enabled: boo
98
100
  useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
99
101
  const versionRef = useRef(0);
100
102
  const { data, loading } = resource;
101
- return useMemo(() => {
103
+ const fetched = useMemo(() => {
102
104
  if (!enabled) return NO_PERPETUALS;
103
105
  const byListing = new Map<string, PerpBoardRow | null>();
104
106
  data?.listings.forEach((listing, index) => byListing.set(listingKey(listing), data.results[index]?.market ?? null));
@@ -112,7 +114,26 @@ export function useListPerpetuals(tickers: readonly TickerRecord[], enabled: boo
112
114
  // Still loading: the row's cell waits. Otherwise there is nothing to show.
113
115
  else if ((!key && !market) || !loading) markets.set(ticker.metadata.ticker, null);
114
116
  }
115
- versionRef.current += 1;
116
- return { markets, version: versionRef.current, stale: !!data?.stale };
117
+ return { markets, version: 0, stale: !!data?.stale };
117
118
  }, [data, enabled, loading, tickers]);
119
+ // The contracts of the rows in view follow the quote stream, as many as useLivePerpRows follows; each is subscribed
120
+ // once however many rows share it.
121
+ const liveRows = useMemo(() => {
122
+ const rows = new Map<string, PerpBoardRow>();
123
+ for (const symbol of liveSymbols) {
124
+ const row = fetched.markets.get(symbol);
125
+ if (row) rows.set(row.marketId, row);
126
+ }
127
+ return [...rows.values()];
128
+ }, [fetched, liveSymbols]);
129
+ const live = useLivePerpRows(liveRows, { surface: "watchlist", enabled: enabled && liveRows.length > 0 });
130
+ return useMemo(() => {
131
+ if (fetched === NO_PERPETUALS) return NO_PERPETUALS;
132
+ const byMarket = new Map(live.rows.map((row) => [row.marketId, row]));
133
+ const markets = byMarket.size
134
+ ? new Map([...fetched.markets].map(([symbol, row]) => [symbol, row ? byMarket.get(row.marketId) ?? row : row]))
135
+ : fetched.markets;
136
+ versionRef.current += 1;
137
+ return { markets, version: versionRef.current, stale: fetched.stale };
138
+ }, [fetched, live.rows]);
118
139
  }
@@ -0,0 +1,161 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import type { PerpBoardRow } from "../../../api-client/perps";
3
+ import { getServerClockOffsetMs } from "../../../market-data/quotes/clock";
4
+ import { buildQuoteKey, instrumentFromQuoteTarget } from "../../../market-data/selectors";
5
+ import { useLiveQuoteEntries } from "../../../state/hooks/quote-streaming";
6
+ import { perpetualListingOfMarket } from "../../../tickers/perpetual-listing";
7
+ import type { QuoteSubscriptionTarget } from "../../../types/data-provider";
8
+ import type { Quote } from "../../../types/financials";
9
+
10
+ /**
11
+ * Perpetual markets move with the app's quote stream like any listing: a
12
+ * market is quoted as the listing it is saved under (`BTC:HYPERLIQUID`), and
13
+ * its quote carries the market's whole row, live while Gloom Cloud follows the
14
+ * venue's own socket. A view that fetched rows (PERP's board, DES, a list's
15
+ * perpetual columns) lays each quote's newer row over its own, so the one
16
+ * subscription a watchlist row already holds feeds every view of the market.
17
+ */
18
+
19
+ type Surface = NonNullable<QuoteSubscriptionTarget["surface"]>;
20
+
21
+ /** The quote target of a market, as its saved listing is quoted; null for a venue the app has no listing for. */
22
+ function perpQuoteTarget(marketId: string, surface: Surface = "detail", visible = true): QuoteSubscriptionTarget | null {
23
+ const listing = perpetualListingOfMarket(marketId);
24
+ return listing ? { symbol: listing.key, exchange: listing.exchange, route: "auto", surface, ...(visible ? { visible: true } : {}) } : null;
25
+ }
26
+
27
+ /** A fetched row with the newer reading a quote of its market brought; the row itself when the quote has none newer. */
28
+ export function withLiveReading(row: PerpBoardRow, quote: Quote | null | undefined): PerpBoardRow {
29
+ const live = quote?.perpetual;
30
+ if (!live || live.marketId.toLowerCase() !== row.marketId.toLowerCase()) return row;
31
+ return Date.parse(live.observedAt) > Date.parse(row.observedAt) ? { ...row, ...live, marketId: row.marketId } : row;
32
+ }
33
+
34
+ /** As Gloom Cloud rules it: a market with no mark from its venue for a minute is no longer live. */
35
+ const LIVE_FOR_MS = 60_000;
36
+
37
+ /**
38
+ * Whether a quote of a market is fed by its venue's socket now, not the minute's read: streamed live, not stale, and
39
+ * marked within the last minute by the server's clock. The server says so too, but a socket that went quiet says
40
+ * nothing, so the app keeps its own count.
41
+ */
42
+ export function isLivePerpQuote(quote: Quote | null | undefined, now = Date.now() + (getServerClockOffsetMs() ?? 0)): boolean {
43
+ return quote?.dataSource === "live" && quote.stale !== true && Number.isFinite(quote.lastUpdated)
44
+ && now - quote.lastUpdated <= LIVE_FOR_MS;
45
+ }
46
+
47
+ /**
48
+ * How long until a view looks again at whether its live rows still are: when the first one's minute runs out, a beat
49
+ * after, never more than a minute away. A reading stamped ahead of the clock would otherwise ask for a timer past what
50
+ * `setTimeout` holds, which fires at once, again and again.
51
+ */
52
+ export function liveRecheckMs(expiresInMs: number): number {
53
+ return Math.min(LIVE_FOR_MS, Math.max(0, expiresInMs) + 250);
54
+ }
55
+
56
+ /** The most rows one view follows on the quote stream at once: as many as the app streams (the socket's budget). */
57
+ const LIVE_ROW_LIMIT = 60;
58
+
59
+ /** The markets of the rows a view follows on the quote stream: the first LIVE_ROW_LIMIT. */
60
+ export const followedPerpMarkets = (rows: readonly Pick<PerpBoardRow, "marketId">[]) =>
61
+ rows.slice(0, LIVE_ROW_LIMIT).map((row) => row.marketId);
62
+
63
+ export interface LivePerpRows {
64
+ rows: readonly PerpBoardRow[];
65
+ /** Every row followed is fed by its venue's socket now: the view may call itself live. */
66
+ live: boolean;
67
+ /** The markets fed by their venue's socket; the others are the minute's read. */
68
+ liveMarkets: ReadonlySet<string>;
69
+ }
70
+
71
+ const NONE: ReadonlySet<string> = new Set();
72
+
73
+ /**
74
+ * Fetched rows with the quote stream's newer readings laid over them. Only the
75
+ * first LIVE_ROW_LIMIT rows are subscribed: a list passes the rows in view.
76
+ */
77
+ export function useLivePerpRows(
78
+ rows: readonly PerpBoardRow[],
79
+ { surface = "detail", visible = true, enabled = true }: { surface?: Surface; visible?: boolean; enabled?: boolean } = {},
80
+ ): LivePerpRows {
81
+ const marketKey = enabled ? followedPerpMarkets(rows).join("\u001f") : "";
82
+ const targets = useMemo(() => marketKey ? marketKey.split("\u001f").flatMap((marketId) => {
83
+ const target = perpQuoteTarget(marketId, surface, visible);
84
+ return target ? [target] : [];
85
+ }) : [], [marketKey, surface, visible]);
86
+ const { entries } = useLiveQuoteEntries(targets);
87
+ // Ticks when a live row's minute runs out, so a feed that went quiet stops calling itself live.
88
+ const [expired, setExpired] = useState(0);
89
+ const result = useMemo(() => {
90
+ if (!targets.length) return { rows, live: false, liveMarkets: NONE, expiresAt: null };
91
+ const now = Date.now() + (getServerClockOffsetMs() ?? 0);
92
+ const followed = new Set(targets.map((target) => buildQuoteKey(instrumentFromQuoteTarget(target))));
93
+ const followedMarkets = new Set(marketKey.split("\u001f").filter((marketId) => perpQuoteTarget(marketId, surface, visible)));
94
+ const quoteOf = (marketId: string) => {
95
+ const target = perpQuoteTarget(marketId, surface, visible);
96
+ if (!target) return null;
97
+ const key = buildQuoteKey(instrumentFromQuoteTarget(target));
98
+ const entry = followed.has(key) ? entries.get(key) : undefined;
99
+ return entry?.data ?? entry?.lastGoodData ?? null;
100
+ };
101
+ const liveMarkets = new Set<string>();
102
+ let expiresAt: number | null = null;
103
+ const next = rows.map((row) => {
104
+ const quote = quoteOf(row.marketId);
105
+ if (quote && isLivePerpQuote(quote, now)) {
106
+ liveMarkets.add(row.marketId);
107
+ expiresAt = Math.min(expiresAt ?? Number.POSITIVE_INFINITY, quote.lastUpdated + LIVE_FOR_MS - now);
108
+ }
109
+ return withLiveReading(row, quote);
110
+ });
111
+ const live = [...followedMarkets].every((marketId) => liveMarkets.has(marketId));
112
+ return { rows: next, live, liveMarkets, expiresAt };
113
+ // `expired` is the clock that ends a quiet row's minute.
114
+ }, [entries, expired, marketKey, rows, surface, targets, visible]);
115
+ useEffect(() => {
116
+ if (result.expiresAt == null) return;
117
+ const timer = setTimeout(() => setExpired((count) => count + 1), liveRecheckMs(result.expiresAt));
118
+ return () => clearTimeout(timer);
119
+ }, [result]);
120
+ return result;
121
+ }
122
+
123
+ interface Range { start: number; end: number }
124
+ /** Rows a table starts with before it measures its viewport: a pane's height of them. */
125
+ const FIRST_VIEW: Range = { start: 0, end: 40 };
126
+
127
+ /**
128
+ * A table's rows with the quote stream laid over the ones in view: pass
129
+ * `onVisibleRangeChange` to the table, and its rows in view (and the row
130
+ * `pinned`, the one other tabs follow) are subscribed as it scrolls.
131
+ */
132
+ export function useLivePerpTableRows<T>(
133
+ items: readonly T[],
134
+ rowOf: (item: T) => PerpBoardRow | null,
135
+ withRow: (item: T, row: PerpBoardRow) => T,
136
+ { surface = "screener", pinned = null }: { surface?: Surface; pinned?: PerpBoardRow | null } = {},
137
+ ): { items: T[]; live: boolean; onVisibleRangeChange: (range: Range) => void } {
138
+ const [range, setRange] = useState<Range>(FIRST_VIEW);
139
+ const onVisibleRangeChange = useCallback((next: Range) => {
140
+ setRange((current) => current.start === next.start && current.end === next.end ? current : next);
141
+ }, []);
142
+ const inView = useMemo(() => {
143
+ const rows = new Map<string, PerpBoardRow>();
144
+ if (pinned) rows.set(pinned.marketId, pinned);
145
+ for (const item of items.slice(range.start, range.end)) {
146
+ const row = rowOf(item);
147
+ if (row) rows.set(row.marketId, row);
148
+ }
149
+ return [...rows.values()];
150
+ }, [items, pinned, range, rowOf]);
151
+ const live = useLivePerpRows(inView, { surface });
152
+ const next = useMemo(() => {
153
+ const byMarket = new Map(live.rows.map((row) => [row.marketId, row]));
154
+ return items.map((item) => {
155
+ const row = rowOf(item);
156
+ const fresh = row ? byMarket.get(row.marketId) : undefined;
157
+ return row && fresh && fresh !== row ? withRow(item, fresh) : item;
158
+ });
159
+ }, [items, live.rows, rowOf, withRow]);
160
+ return { items: next, live: live.live, onVisibleRangeChange };
161
+ }
@@ -0,0 +1,154 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+ import { isAccessDenied } from "../../../api-client/errors";
3
+ import { usePlanAccess } from "../../../api-client/plan-access";
4
+ import type { PerpBoardRow } from "../../../api-client/perps";
5
+ import { ActionRow, ColumnGrid, SectionHeading, type ColumnGridCell, type ColumnGridColumn } from "../../../components";
6
+ import { t, tf } from "../../../i18n";
7
+ import { useAsyncResource, useAutoRefresh } from "../../../public/react";
8
+ import { useThemeColors } from "../../../theme/theme-context";
9
+ import { perpetualListing } from "../../../tickers/perpetual-listing";
10
+ import { premiumToUnderlying } from "../../../market-data/perpetual-premium";
11
+ import type { Quote } from "../../../types/financials";
12
+ import { Box, Text } from "../../../ui";
13
+ import { isRoundTheClockCoin } from "../../../utils/crypto-pair";
14
+ import { displayWidth } from "../../../utils/format";
15
+ import { parsePublicTickerKey } from "../../../utils/exchanges";
16
+ import { useResearchCloudSession } from "../shared/research-cloud-session";
17
+ import { loadPerpsCompare, loadPerpsEquity } from "./client";
18
+ import { perpEquityIdentity } from "./equity-identity";
19
+ import { useLivePerpRows } from "./live-rows";
20
+ import { compact, perpCell, percent, price, readingTime, venueName } from "./model";
21
+ import { useOpenPerpMarket } from "./open-market";
22
+ import { contractText, foldSmallMarkets, mergeVenueMarkets, type PerpVenueMarket } from "./venue-markets";
23
+
24
+ const STOCK_TYPES = new Set(["STK", "EQUITY", "STOCK", "COMMON STOCK"]);
25
+ const COIN_PAIR = /^([A-Z0-9]{1,15})[-/](?:USD|USDT|USDC)$/;
26
+
27
+ /** What an asset's contracts are looked up by: a stock's listing, or a coin's base asset. Null for a contract itself. */
28
+ function perpAsset(symbol: string, exchange: string | undefined, instrumentType: string | undefined):
29
+ { kind: "stock"; symbol: string; exchange: string } | { kind: "coin"; base: string } | null {
30
+ if (perpetualListing(symbol, exchange)) return null;
31
+ if (isRoundTheClockCoin(symbol, exchange)) {
32
+ const bare = parsePublicTickerKey(symbol).symbol.toUpperCase();
33
+ const base = COIN_PAIR.exec(bare)?.[1] ?? (/^[A-Z0-9]{1,15}$/.test(bare) ? bare : null);
34
+ return base ? { kind: "coin", base } : null;
35
+ }
36
+ const identity = perpEquityIdentity(symbol, exchange);
37
+ return identity && STOCK_TYPES.has(instrumentType?.toUpperCase() ?? "") ? { kind: "stock", ...identity } : null;
38
+ }
39
+
40
+ const VENUE_MIN_WIDTH = 11;
41
+ /** Coinbase International, in full. */
42
+ const VENUE_MAX_WIDTH = 22;
43
+
44
+ interface Column { id: "venue" | "contract" | "mark" | "premium" | "funding" | "oi" | "volume"; label: string; width: number; align?: "right" }
45
+ // Reading order.
46
+ const COLUMNS: readonly Column[] = [
47
+ { id: "venue", label: "Venue", width: VENUE_MIN_WIDTH },
48
+ { id: "contract", label: "Contract", width: 11 },
49
+ { id: "mark", label: "Mark", width: 11, align: "right" },
50
+ { id: "premium", label: "Premium", width: 8, align: "right" },
51
+ { id: "funding", label: "Fund 8h", width: 9, align: "right" },
52
+ { id: "oi", label: "OI USD", width: 7, align: "right" },
53
+ { id: "volume", label: "Vol 24h", width: 7, align: "right" },
54
+ ];
55
+ /** What a narrow overview gives up first: the volume, then the contract, then the premium. */
56
+ const DROP_ORDER: readonly Column["id"][] = ["volume", "contract", "premium"];
57
+ const GAP = 2;
58
+ const span = (columns: readonly Column[]) => columns.reduce((sum, column) => sum + column.width, 0) + GAP * (columns.length - 1);
59
+
60
+ function fitColumns(width: number, venueWidth: number): Column[] {
61
+ let columns = COLUMNS.map((column) => column.id === "venue" ? { ...column, width: venueWidth } : column);
62
+ for (const id of DROP_ORDER) {
63
+ if (span(columns) <= width) break;
64
+ columns = columns.filter((column) => column.id !== id);
65
+ }
66
+ return columns;
67
+ }
68
+
69
+ /**
70
+ * DES's perpetual markets on a stock or a coin: one row per venue (its contracts merged, `venue-markets.ts`), the
71
+ * deepest first, markets with a small share of the open interest folded behind a row that shows them. A row opens
72
+ * PERP on its venue's deepest contract. The figures move with the quote stream while the venue streams; otherwise
73
+ * the section says when the venues were read.
74
+ */
75
+ export function PerpMarketsSection({ symbol, exchange, instrumentType, quote, width }: {
76
+ symbol: string;
77
+ exchange?: string;
78
+ instrumentType?: string;
79
+ /** The security's own quote: a stock's contracts are priced against it while it streams. */
80
+ quote?: Quote | null;
81
+ width: number;
82
+ }) {
83
+ const colors = useThemeColors();
84
+ const session = useResearchCloudSession();
85
+ const plan = usePlanAccess();
86
+ const openMarket = useOpenPerpMarket();
87
+ const [expanded, setExpanded] = useState(false);
88
+ const asset = perpAsset(symbol, exchange, instrumentType);
89
+ const assetKey = asset ? asset.kind === "coin" ? `coin:${asset.base}` : `stock:${asset.symbol}:${asset.exchange}` : null;
90
+ const accessKey = `${session.requestKey}:${plan.hasProAccess ? "pro" : "preview"}`;
91
+ const loader = useCallback((force: boolean) => {
92
+ const [kind, first, second] = assetKey!.split(":");
93
+ return kind === "coin" ? loadPerpsCompare(first!, accessKey, force) : loadPerpsEquity({ symbol: first!, exchange: second! }, accessKey, force);
94
+ }, [assetKey, accessKey]);
95
+ const resource = useAsyncResource(assetKey ? loader : null, { clearOnError: isAccessDenied });
96
+ useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
97
+ const markets = useMemo(() => mergeVenueMarkets(resource.data?.payload.rows ?? []), [resource.data]);
98
+ const { shown, folded } = useMemo(() => foldSmallMarkets(markets), [markets]);
99
+ const visible = expanded ? markets : shown;
100
+ // The deepest contract of each row on screen follows the quote stream.
101
+ const primaries = useMemo(() => visible.map((market) => market.primary), [visible]);
102
+ const live = useLivePerpRows(primaries, { surface: "detail" });
103
+ if (!assetKey || !markets.length) return null;
104
+ const rows = visible.map((market, index) => ({ market, row: live.rows[index] ?? market.primary }));
105
+ const premiumLabel = asset?.kind === "stock" ? "vs last" : "Premium";
106
+ // The venue column fits the longest venue name shown.
107
+ const venueWidth = Math.min(VENUE_MAX_WIDTH, Math.max(VENUE_MIN_WIDTH, ...visible.map((market) => displayWidth(venueName(market.venue)))));
108
+ const columns = fitColumns(width, venueWidth);
109
+ const fixed = span(columns);
110
+ // The last text column takes what the figures leave, on the desktop too, so a venue keeps its contract beside it.
111
+ const growing = columns.some((column) => column.id === "contract") ? "contract" : "venue";
112
+ const gridColumns: ColumnGridColumn[] = columns.map((column, index) => ({
113
+ width: column.id === growing ? Math.max(column.width, width - fixed + column.width) : column.width,
114
+ align: column.align,
115
+ gapBefore: index > 0 ? GAP : 0,
116
+ ...(column.id === growing ? { growFrom: column.width } : {}),
117
+ }));
118
+ const header: ColumnGridCell[] = columns.map((column) => ({ text: t(column.id === "premium" ? premiumLabel : column.label), color: colors.textDim }));
119
+ const cell = (column: Column, market: PerpVenueMarket, row: PerpBoardRow): ColumnGridCell => {
120
+ if (column.id === "venue") {
121
+ return { text: venueName(market.venue), color: row.stale ? colors.textDim : colors.textBright,
122
+ link: { label: tf("Open {label}", { label: `PERP ${row.baseAsset} · ${venueName(market.venue)}` }), onOpen: () => openMarket(row.marketId) } };
123
+ }
124
+ if (column.id === "contract") return { text: contractText(market), color: colors.textMuted };
125
+ if (column.id === "mark") return { text: price(row.markPrice ?? row.lastPrice), color: colors.text };
126
+ if (column.id === "premium") {
127
+ const value = asset?.kind === "stock" ? premiumToUnderlying(row, quote) : row.premium;
128
+ return { text: percent(value, 2), color: value == null || value === 0 ? colors.textMuted : value > 0 ? colors.positive : colors.negative };
129
+ }
130
+ if (column.id === "funding") {
131
+ const { text, color } = perpCell(row, "fundingRate8h", colors);
132
+ return { text, color };
133
+ }
134
+ // A venue's open interest and volume are its contracts' together.
135
+ return { text: compact(column.id === "oi" ? market.openInterestUsd : market.volume24hUsd), color: colors.text };
136
+ };
137
+ const body = rows.map(({ market, row }) => columns.map((column) => cell(column, market, row)));
138
+ // Streaming figures date themselves; the rows from the minute's read say when it was.
139
+ const read = rows.filter(({ row }) => !live.liveMarkets.has(row.marketId));
140
+ const readAt = read.length ? readingTime(read.reduce<string | null>((latest, { row }) => !latest || row.observedAt > latest ? row.observedAt : latest, null)) : null;
141
+ const stale = read.length > 0 && (!!resource.data?.stale || read.some(({ row }) => row.stale));
142
+ return (
143
+ <Box flexDirection="column" data-gloom-role="perp-markets">
144
+ <SectionHeading title={t("Perpetuals")} />
145
+ <ColumnGrid columns={gridColumns} rows={[header, ...body]} width={width} role="perp-markets-grid" />
146
+ {folded.length > 0 && (
147
+ <ActionRow expanded={expanded} width={width}
148
+ label={expanded ? t("Fewer markets") : folded.length === 1 ? t("1 smaller market") : tf("{count} smaller markets", { count: String(folded.length) })}
149
+ onPress={() => setExpanded((value) => !value)} />
150
+ )}
151
+ {readAt && <Text fg={stale ? colors.warning : colors.textDim}>{`${read.length < rows.length ? `${[...new Set(read.map(({ market }) => venueName(market.venue)))].join(", ")} ` : ""}${t("as of")} ${readAt}${stale ? ` · ${t("stale")}` : ""}`}</Text>}
152
+ </Box>
153
+ );
154
+ }