gloomberb 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +169 -5
  5. package/src/account/auth-model.ts +7 -3
  6. package/src/api-client/auth.ts +9 -4
  7. package/src/api-client/catalysts.ts +2 -1
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +31 -6
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/index.ts +171 -11
  14. package/src/api-client/live-scan.ts +91 -0
  15. package/src/api-client/market-discovery.ts +10 -0
  16. package/src/api-client/market-movers.ts +7 -0
  17. package/src/api-client/members.ts +13 -1
  18. package/src/api-client/perps.ts +43 -0
  19. package/src/api-client/request.ts +222 -41
  20. package/src/api-client/research-activity.ts +6 -12
  21. package/src/api-client/types.ts +74 -0
  22. package/src/app/keybindings/index.ts +3 -0
  23. package/src/app/keybindings/labels.ts +18 -1
  24. package/src/app/keybindings/resolve.ts +14 -0
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +42 -27
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +1 -1
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/fred-series.ts +5 -0
  35. package/src/cli/commands/keybindings.ts +23 -0
  36. package/src/cli/commands/market.ts +138 -46
  37. package/src/cli/commands/options-chain.ts +107 -10
  38. package/src/cli/commands/options-leaps.ts +9 -3
  39. package/src/cli/commands/overview.ts +176 -39
  40. package/src/cli/commands/search.ts +39 -3
  41. package/src/cli/commands/themes.ts +11 -2
  42. package/src/cli/commands/ticker.ts +57 -35
  43. package/src/cli/company-name-hint.ts +41 -0
  44. package/src/cli/earnings-calendar.ts +1 -1
  45. package/src/cli/errors.ts +10 -2
  46. package/src/cli/export-source.ts +29 -1
  47. package/src/cli/glossary.ts +73 -1
  48. package/src/cli/history-rows.ts +87 -16
  49. package/src/cli/index.ts +2 -0
  50. package/src/cli/known-gaps.ts +5 -2
  51. package/src/cli/listing-arg.ts +195 -56
  52. package/src/cli/output-file.ts +68 -0
  53. package/src/cli/pane-functions/capabilities.ts +3 -0
  54. package/src/cli/pane-functions/catalog.ts +80 -7
  55. package/src/cli/pane-functions/dom.ts +13 -3
  56. package/src/cli/pane-functions/freshness.ts +111 -16
  57. package/src/cli/pane-functions/function-help.ts +23 -13
  58. package/src/cli/pane-functions/headless.ts +56 -12
  59. package/src/cli/pane-functions/index.ts +58 -16
  60. package/src/cli/pane-functions/market-session.ts +81 -9
  61. package/src/cli/pane-functions/options.ts +19 -1
  62. package/src/cli/pane-functions/report.ts +5 -1
  63. package/src/cli/pane-functions/resolver.ts +35 -8
  64. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  65. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  66. package/src/cli/quote-answer.ts +30 -13
  67. package/src/cli/report-tables.ts +18 -7
  68. package/src/cli/restore-plugins.ts +13 -1
  69. package/src/cli/result.ts +57 -5
  70. package/src/cli/ticker-resolution.ts +3 -2
  71. package/src/components/chart/composite/composite-chart.tsx +26 -4
  72. package/src/components/chart/composite/format.ts +27 -0
  73. package/src/components/chart/composite/interactions.ts +6 -0
  74. package/src/components/chart/composite/legend.tsx +45 -7
  75. package/src/components/chart/composite/levels.ts +2 -0
  76. package/src/components/chart/composite/panel-gestures.ts +33 -0
  77. package/src/components/chart/composite/panel-series.ts +1 -0
  78. package/src/components/chart/composite/panel-surface.tsx +61 -5
  79. package/src/components/chart/composite/range-statistics.ts +62 -0
  80. package/src/components/chart/composite/rasterizer.ts +24 -13
  81. package/src/components/chart/composite/scene.ts +46 -7
  82. package/src/components/chart/composite/series-axis-tags.tsx +16 -9
  83. package/src/components/chart/composite/series-cues.ts +14 -0
  84. package/src/components/chart/composite/text-renderer.ts +9 -2
  85. package/src/components/chart/composite/tool-catalog.ts +36 -0
  86. package/src/components/chart/composite/tools.ts +203 -44
  87. package/src/components/chart/composite/types.ts +4 -0
  88. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  89. package/src/components/command-bar/assist/inventory.ts +2 -1
  90. package/src/components/command-bar/commands/direct/index.ts +28 -13
  91. package/src/components/command-bar/pane-templates/items.ts +3 -2
  92. package/src/components/command-bar/panel/prompt-binding.ts +0 -8
  93. package/src/components/command-bar/workflow/alert.ts +1 -1
  94. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  95. package/src/components/layout/header.tsx +26 -40
  96. package/src/components/layout/pane/footer/index.tsx +1 -0
  97. package/src/components/layout/pane/footer/model.ts +27 -0
  98. package/src/components/onboarding/onboarding-frame.tsx +1 -1
  99. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  100. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
  101. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  102. package/src/components/onboarding/wizard-positions.ts +3 -3
  103. package/src/components/ticker/list-table-view.tsx +4 -1
  104. package/src/components/ui/choice-dialog.tsx +23 -18
  105. package/src/components/ui/choice-groups.ts +18 -0
  106. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  107. package/src/components/ui/data-table/opentui/model.ts +26 -1
  108. package/src/components/ui/data-table/types.ts +2 -0
  109. package/src/components/ui/display.tsx +5 -1
  110. package/src/components/ui/list-view.tsx +8 -4
  111. package/src/components/ui/query-bar-keys.tsx +2 -0
  112. package/src/components/ui/query-bar.tsx +1 -0
  113. package/src/components/ui/select-button.tsx +5 -1
  114. package/src/components/ui/select-field.tsx +1 -0
  115. package/src/components/ui/table-layout.ts +8 -5
  116. package/src/core/app-runtime.ts +2 -0
  117. package/src/core/app-services.ts +2 -2
  118. package/src/core/state/app/layout.ts +2 -0
  119. package/src/core/state/app/state.ts +9 -0
  120. package/src/core/state/app/types.ts +3 -0
  121. package/src/data/config/chart-settings.ts +1 -1
  122. package/src/data/json-ticker-repository.ts +17 -8
  123. package/src/data/saved-copies.ts +55 -16
  124. package/src/data/ticker-repository.ts +16 -9
  125. package/src/data/ticker-writes.ts +81 -0
  126. package/src/data/ttl-cache.ts +165 -0
  127. package/src/docs/snapshot.json +427 -216
  128. package/src/i18n/es.ts +19 -3
  129. package/src/i18n/ja.ts +19 -3
  130. package/src/i18n/ko.ts +19 -3
  131. package/src/i18n/zh-cn.ts +19 -3
  132. package/src/i18n/zh-tw.ts +19 -3
  133. package/src/market-data/coordinator/financials.ts +10 -2
  134. package/src/market-data/coordinator/index.ts +3 -1
  135. package/src/market-data/coordinator/quotes.ts +4 -2
  136. package/src/market-data/depositary-pair.ts +2 -2
  137. package/src/market-data/fund-facts.ts +50 -0
  138. package/src/market-data/market/change-basis.ts +48 -7
  139. package/src/market-data/market/dated-previous-close.ts +203 -0
  140. package/src/market-data/market/format.ts +25 -0
  141. package/src/market-data/market/freshness.ts +2 -1
  142. package/src/market-data/market/futures-week.ts +68 -0
  143. package/src/market-data/market/status.ts +16 -4
  144. package/src/market-data/options-alternatives.ts +141 -4
  145. package/src/market-data/quotes/contributions.ts +20 -2
  146. package/src/market-data/quotes/resolution.ts +3 -0
  147. package/src/market-data/selectors.ts +3 -0
  148. package/src/news/aggregator.ts +113 -9
  149. package/src/news/cloud-session.ts +21 -0
  150. package/src/news/hooks.ts +1 -0
  151. package/src/news/news-model.ts +1 -0
  152. package/src/news/types.ts +11 -0
  153. package/src/notifications/notification-log.ts +6 -4
  154. package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
  155. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  156. package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
  157. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  158. package/src/plugins/builtin/alerts/cli.ts +380 -82
  159. package/src/plugins/builtin/alerts/command.ts +31 -0
  160. package/src/plugins/builtin/alerts/constants.ts +1 -1
  161. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  162. package/src/plugins/builtin/alerts/fired-log.ts +113 -8
  163. package/src/plugins/builtin/alerts/format.ts +80 -4
  164. package/src/plugins/builtin/alerts/headless.ts +30 -7
  165. package/src/plugins/builtin/alerts/history-rows.ts +7 -3
  166. package/src/plugins/builtin/alerts/history.ts +2 -0
  167. package/src/plugins/builtin/alerts/index.tsx +135 -22
  168. package/src/plugins/builtin/alerts/live.ts +3 -3
  169. package/src/plugins/builtin/alerts/metric-command.ts +134 -0
  170. package/src/plugins/builtin/alerts/metric-data.ts +111 -0
  171. package/src/plugins/builtin/alerts/metric-model.ts +324 -0
  172. package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
  173. package/src/plugins/builtin/alerts/pane.tsx +90 -39
  174. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  175. package/src/plugins/builtin/alerts/storage.ts +37 -10
  176. package/src/plugins/builtin/alerts/types.ts +7 -0
  177. package/src/plugins/builtin/analytics/index.tsx +2 -1
  178. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  179. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  180. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  181. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  182. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  183. package/src/plugins/builtin/attention/index.tsx +2 -1
  184. package/src/plugins/builtin/attention/listing.ts +14 -0
  185. package/src/plugins/builtin/backtest/headless.ts +10 -5
  186. package/src/plugins/builtin/backtest/pane.tsx +9 -4
  187. package/src/plugins/builtin/backtest/presets.ts +29 -3
  188. package/src/plugins/builtin/broker-manager/index.tsx +10 -5
  189. package/src/plugins/builtin/broker-manager/model.ts +6 -1
  190. package/src/plugins/builtin/broker-trading/controller.ts +22 -12
  191. package/src/plugins/builtin/broker-trading/index.tsx +13 -1
  192. package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
  193. package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
  194. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  195. package/src/plugins/builtin/buildout/format.ts +38 -0
  196. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  197. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  198. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  199. package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
  200. package/src/plugins/builtin/catalysts/model.ts +2 -1
  201. package/src/plugins/builtin/changelog/index.tsx +9 -6
  202. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
  203. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
  204. package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
  205. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
  206. package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
  207. package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
  208. package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
  209. package/src/plugins/builtin/chart-composer/formula.ts +71 -18
  210. package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
  211. package/src/plugins/builtin/chart-composer/headless.ts +19 -3
  212. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  213. package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
  214. package/src/plugins/builtin/chart-composer/presets.ts +50 -15
  215. package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
  216. package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
  217. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  218. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
  219. package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
  220. package/src/plugins/builtin/chat/content/composer.tsx +13 -6
  221. package/src/plugins/builtin/chat/content/footer.tsx +6 -3
  222. package/src/plugins/builtin/chat/content/index.tsx +50 -21
  223. package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
  224. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
  225. package/src/plugins/builtin/chat/index.tsx +23 -0
  226. package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
  227. package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
  228. package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
  229. package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
  230. package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
  231. package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
  232. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
  233. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  234. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  235. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  236. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  237. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  238. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  239. package/src/plugins/builtin/company-attention/client.ts +8 -9
  240. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  241. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  242. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  243. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  244. package/src/plugins/builtin/correlation/headless.ts +13 -5
  245. package/src/plugins/builtin/correlation/history.ts +22 -0
  246. package/src/plugins/builtin/cot/client.ts +22 -18
  247. package/src/plugins/builtin/cot/headless.ts +15 -10
  248. package/src/plugins/builtin/cot/model.ts +14 -1
  249. package/src/plugins/builtin/cot/pane.tsx +22 -14
  250. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  251. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  252. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  253. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  254. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  255. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  256. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  257. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  258. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  259. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  260. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  261. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  262. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  263. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  265. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  266. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  267. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  268. package/src/plugins/builtin/docs/targets.ts +5 -1
  269. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  270. package/src/plugins/builtin/earnings/board.tsx +62 -71
  271. package/src/plugins/builtin/earnings/headless.ts +40 -4
  272. package/src/plugins/builtin/earnings/scope.ts +31 -43
  273. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  274. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  275. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  276. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  277. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  278. package/src/plugins/builtin/econ/calendar-model.ts +14 -9
  279. package/src/plugins/builtin/econ/figures.ts +72 -0
  280. package/src/plugins/builtin/econ/index.tsx +33 -3
  281. package/src/plugins/builtin/econ/types.ts +4 -0
  282. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  283. package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
  284. package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
  285. package/src/plugins/builtin/econ-statistics/view.ts +3 -0
  286. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  287. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  288. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  289. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  290. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  291. package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
  292. package/src/plugins/builtin/exposure/charts.tsx +2 -2
  293. package/src/plugins/builtin/exposure/model.ts +13 -1
  294. package/src/plugins/builtin/exposure/pane.tsx +4 -4
  295. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  296. package/src/plugins/builtin/futures/index.tsx +1 -0
  297. package/src/plugins/builtin/futures/model.ts +2 -1
  298. package/src/plugins/builtin/futures/table.tsx +2 -2
  299. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  300. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  301. package/src/plugins/builtin/fx-matrix/client.ts +11 -3
  302. package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
  303. package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
  304. package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
  305. package/src/plugins/builtin/gpu/headless.ts +8 -2
  306. package/src/plugins/builtin/help/function-index.ts +4 -1
  307. package/src/plugins/builtin/help/index.tsx +1 -0
  308. package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
  309. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  310. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  311. package/src/plugins/builtin/holders/headless.ts +18 -1
  312. package/src/plugins/builtin/iv-history/client.ts +52 -11
  313. package/src/plugins/builtin/iv-history/format.ts +36 -23
  314. package/src/plugins/builtin/iv-history/headless.ts +36 -18
  315. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  316. package/src/plugins/builtin/iv-history/model.ts +163 -27
  317. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  318. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  319. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  320. package/src/plugins/builtin/jobs/client.ts +25 -29
  321. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  322. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  323. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  324. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  325. package/src/plugins/builtin/macro-day/pane.tsx +7 -5
  326. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  327. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  328. package/src/plugins/builtin/market-movers/client.ts +25 -7
  329. package/src/plugins/builtin/market-movers/footer.ts +2 -2
  330. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  331. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  332. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  333. package/src/plugins/builtin/market-movers/model.ts +4 -1
  334. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  335. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  336. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  337. package/src/plugins/builtin/market-movers/table.ts +17 -5
  338. package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
  339. package/src/plugins/builtin/members/client.ts +19 -4
  340. package/src/plugins/builtin/members/headless.ts +20 -7
  341. package/src/plugins/builtin/members/index.tsx +9 -9
  342. package/src/plugins/builtin/members/model.ts +23 -4
  343. package/src/plugins/builtin/members/pane.tsx +25 -15
  344. package/src/plugins/builtin/news/index.tsx +1 -0
  345. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  346. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  347. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  348. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  349. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  350. package/src/plugins/builtin/openfda/client.ts +14 -2
  351. package/src/plugins/builtin/openfda/index.tsx +3 -2
  352. package/src/plugins/builtin/openfda/model.ts +9 -0
  353. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  354. package/src/plugins/builtin/options/analytics.ts +54 -6
  355. package/src/plugins/builtin/options/enrichment.ts +17 -5
  356. package/src/plugins/builtin/options/model-spot.ts +5 -2
  357. package/src/plugins/builtin/options/strike-window.ts +2 -1
  358. package/src/plugins/builtin/options/table.ts +6 -9
  359. package/src/plugins/builtin/options/types.ts +2 -0
  360. package/src/plugins/builtin/options/view.tsx +42 -12
  361. package/src/plugins/builtin/options-positioning/client.ts +30 -0
  362. package/src/plugins/builtin/options-positioning/headless.ts +74 -21
  363. package/src/plugins/builtin/options-positioning/index.tsx +4 -4
  364. package/src/plugins/builtin/options-positioning/model.ts +122 -14
  365. package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
  366. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  367. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  368. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  369. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  370. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  371. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  372. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  373. package/src/plugins/builtin/pe-band/model.ts +12 -49
  374. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  375. package/src/plugins/builtin/perps/cascade.ts +236 -0
  376. package/src/plugins/builtin/perps/client.ts +56 -2
  377. package/src/plugins/builtin/perps/freshness.ts +12 -10
  378. package/src/plugins/builtin/perps/headless.ts +65 -9
  379. package/src/plugins/builtin/perps/index.tsx +7 -5
  380. package/src/plugins/builtin/perps/live-rows.ts +10 -3
  381. package/src/plugins/builtin/perps/model.ts +18 -4
  382. package/src/plugins/builtin/perps/pane.tsx +32 -31
  383. package/src/plugins/builtin/perps/status-line.ts +25 -0
  384. package/src/plugins/builtin/plugin-module.ts +5 -1
  385. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  386. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  387. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  388. package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
  389. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  390. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  391. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  392. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  393. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  394. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  395. package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
  396. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  397. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  398. package/src/plugins/builtin/rate-path/pane.tsx +2 -2
  399. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  400. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  401. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  402. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  403. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  404. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  405. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  406. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  407. package/src/plugins/builtin/research/event-model.ts +10 -8
  408. package/src/plugins/builtin/research/headless.ts +1 -1
  409. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  410. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  411. package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
  412. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  413. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  414. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  415. package/src/plugins/builtin/scanner/feed.ts +2 -2
  416. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  417. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  418. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  419. package/src/plugins/builtin/scanner/headless.ts +204 -35
  420. package/src/plugins/builtin/scanner/index.ts +40 -6
  421. package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
  422. package/src/plugins/builtin/scanner/live-feed.ts +168 -0
  423. package/src/plugins/builtin/scanner/live-headless.ts +138 -0
  424. package/src/plugins/builtin/scanner/live-model.ts +362 -0
  425. package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
  426. package/src/plugins/builtin/scanner/live-track.ts +186 -0
  427. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  428. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  429. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  430. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  431. package/src/plugins/builtin/sec/headless.ts +2 -1
  432. package/src/plugins/builtin/series-inventory.ts +3 -0
  433. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  434. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  435. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  436. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  437. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  438. package/src/plugins/builtin/shared/macro-releases.ts +65 -0
  439. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  440. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  441. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  442. package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
  443. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  444. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  445. package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
  446. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  447. package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
  448. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  449. package/src/plugins/builtin/themes/headless.ts +6 -4
  450. package/src/plugins/builtin/themes/model.ts +17 -8
  451. package/src/plugins/builtin/themes/pane.tsx +7 -7
  452. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  453. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  456. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  457. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  458. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  459. package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
  460. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  461. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  462. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  463. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  464. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  465. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  466. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  467. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  468. package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
  469. package/src/plugins/builtin/vol-surface/client.ts +9 -17
  470. package/src/plugins/builtin/vol-surface/headless.ts +63 -14
  471. package/src/plugins/builtin/vol-surface/index.tsx +2 -2
  472. package/src/plugins/builtin/vol-surface/model.ts +22 -7
  473. package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
  474. package/src/plugins/builtin/vol-surface/tables.ts +31 -3
  475. package/src/plugins/builtin/volatility/dvol.ts +46 -0
  476. package/src/plugins/builtin/volatility/index.tsx +2 -2
  477. package/src/plugins/builtin/volatility/pane.tsx +4 -4
  478. package/src/plugins/builtin/volatility/series.ts +26 -0
  479. package/src/plugins/builtin/world-indices/footer.ts +1 -1
  480. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  481. package/src/plugins/catalog-browser.ts +13 -8
  482. package/src/plugins/catalog-manifest.ts +2 -1
  483. package/src/plugins/marketplace/model.ts +18 -5
  484. package/src/portfolio/column-values.ts +5 -5
  485. package/src/portfolio/futures-positions.ts +6 -3
  486. package/src/portfolio/position-quantity.ts +15 -9
  487. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  488. package/src/remote/resources.ts +2 -1
  489. package/src/renderers/browser/app-services.ts +1 -7
  490. package/src/renderers/browser/config-host.ts +2 -55
  491. package/src/renderers/browser/main.tsx +7 -57
  492. package/src/renderers/dom/data-table/row.tsx +26 -0
  493. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  494. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  495. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  496. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  497. package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
  498. package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
  499. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  500. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  501. package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
  502. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
  503. package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
  504. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  505. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
  506. package/src/renderers/electrobun/bun/index.ts +25 -1
  507. package/src/renderers/electrobun/shared/app-name.ts +30 -0
  508. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  509. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  510. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  511. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
  512. package/src/renderers/electrobun/view/main.tsx +24 -1
  513. package/src/renderers/electrobun/view/news-source.ts +3 -1
  514. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  515. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  516. package/src/renderers/opentui/start.tsx +5 -0
  517. package/src/renderers/opentui/toast-host.tsx +71 -43
  518. package/src/series/registry.ts +1 -0
  519. package/src/series/types.ts +6 -0
  520. package/src/series/validate.ts +1 -0
  521. package/src/sources/gloomberb-cloud/index.ts +1 -0
  522. package/src/sources/gloomberb-cloud/news.ts +47 -7
  523. package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
  524. package/src/sources/provider-router/batches.ts +10 -2
  525. package/src/sources/provider-router/financial-routes.ts +3 -3
  526. package/src/sources/provider-router/financials.ts +15 -3
  527. package/src/sources/provider-router/history.ts +28 -7
  528. package/src/sources/provider-router/index.ts +149 -10
  529. package/src/sources/provider-router/search.ts +13 -2
  530. package/src/state/app/context/index.tsx +3 -0
  531. package/src/state/ticker-cleanup.ts +50 -0
  532. package/src/sync/auth-transition.ts +19 -9
  533. package/src/sync/baseline.ts +10 -4
  534. package/src/sync/controller.ts +70 -19
  535. package/src/sync/core-contributors.ts +90 -31
  536. package/src/sync/react.ts +4 -1
  537. package/src/sync/types.ts +3 -0
  538. package/src/telemetry/stall-reports-dom.ts +38 -0
  539. package/src/telemetry/stall-reports-node.ts +75 -0
  540. package/src/telemetry/stall-reports.ts +214 -0
  541. package/src/telemetry/usage-counts.ts +0 -18
  542. package/src/test-support/preload.ts +14 -0
  543. package/src/theme/colors.ts +8 -0
  544. package/src/theme/themes.ts +10 -0
  545. package/src/tickers/company-name.ts +103 -3
  546. package/src/tickers/listing-venues.ts +38 -3
  547. package/src/tickers/search/bare-symbol.ts +36 -0
  548. package/src/tickers/search/index.ts +36 -7
  549. package/src/tickers/search/ranking.ts +10 -1
  550. package/src/tickers/search/types.ts +5 -0
  551. package/src/tickers/terminal-codes.ts +19 -0
  552. package/src/time-series/alignment.ts +1 -1
  553. package/src/time-series/base-series.ts +13 -5
  554. package/src/time-series/daily-valuation.ts +62 -0
  555. package/src/time-series/economic.ts +18 -4
  556. package/src/time-series/field-catalog.ts +2 -4
  557. package/src/time-series/filed-trailing-eps.ts +197 -0
  558. package/src/time-series/forward-valuation.ts +32 -0
  559. package/src/time-series/fundamentals.ts +198 -7
  560. package/src/time-series/hooks.ts +1 -0
  561. package/src/time-series/ice-credit.ts +3 -0
  562. package/src/time-series/price-comparison.ts +30 -2
  563. package/src/time-series/price-units.ts +72 -2
  564. package/src/time-series/resolve-context.ts +7 -1
  565. package/src/time-series/resolve.ts +116 -15
  566. package/src/time-series/series-presentation.ts +40 -6
  567. package/src/time-series/session-bar.ts +11 -0
  568. package/src/time-series/spec.ts +5 -3
  569. package/src/time-series/studies.ts +278 -74
  570. package/src/time-series/total-return.ts +160 -0
  571. package/src/time-series/transforms.ts +3 -3
  572. package/src/time-series/types.ts +27 -1
  573. package/src/time-series/valuation-releases.ts +39 -0
  574. package/src/types/data-provider.ts +9 -1
  575. package/src/types/desktop-window.ts +2 -0
  576. package/src/types/financials.ts +73 -2
  577. package/src/types/headless.ts +61 -5
  578. package/src/types/instrument.ts +3 -0
  579. package/src/types/news-source.ts +1 -0
  580. package/src/types/plugin.ts +10 -1
  581. package/src/ui/host.tsx +3 -0
  582. package/src/updater/command-names.ts +12 -7
  583. package/src/updater/failure.ts +4 -1
  584. package/src/utils/cli-output.ts +42 -1
  585. package/src/utils/crypto-pair.ts +44 -0
  586. package/src/utils/financial-statements.ts +6 -1
  587. package/src/utils/forward-dividend.ts +2 -0
  588. package/src/utils/fundamentals.ts +24 -3
  589. package/src/utils/fx-market-hours.ts +26 -3
  590. package/src/utils/main-thread-stall.ts +35 -2
  591. package/src/utils/market-capitalization.ts +24 -1
  592. package/src/utils/network-failure.ts +9 -6
  593. package/src/utils/option-expiry.ts +48 -1
  594. package/src/utils/options.ts +10 -0
  595. package/src/utils/plugin-proxy-hosts.json +1 -0
  596. package/src/utils/price-history.ts +12 -1
  597. package/src/utils/shell-word.ts +8 -0
  598. package/src/utils/trailing-dividend.ts +53 -4
  599. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
  600. package/src/renderers/browser/first-screen.ts +0 -193
@@ -1,3 +1,4 @@
1
+ import { logFailure } from "../../../../utils/log-failure";
1
2
  import { PerpEquityRow } from "../../perps/equity-row";
2
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
4
  import { Box, useUiCapabilities } from "../../../../ui";
@@ -173,7 +174,7 @@ export function QuoteMonitorPane({ paneId, focused, width, height }: PaneProps)
173
174
  useEffect(() => {
174
175
  const coordinator = getSharedMarketDataCoordinator();
175
176
  if (!coordinator || !liveStreaming || boardInstruments.length === 0) return;
176
- void coordinator.loadQuotesBatch(boardInstruments).catch(() => {});
177
+ void coordinator.loadQuotesBatch(boardInstruments).catch(logFailure("ticker-detail.quote-monitor", "Warm quote monitor board failed"));
177
178
  }, [boardInstrumentKey, liveStreaming]);
178
179
  const { pinTicker } = usePluginTickerActions();
179
180
  const { openPaneSettings } = usePluginAppActions();
@@ -119,6 +119,7 @@ export function getQuoteMonitorPaneSettings(
119
119
  try {
120
120
  symbols = parseTickerListInput(storedText, MAX_TICKER_LIST_SIZE);
121
121
  } catch {
122
+ // Intentional ignore: unparseable saved ticker text falls back to the legacy symbol or the pane's ticker.
122
123
  symbols = [];
123
124
  }
124
125
  }
@@ -1,7 +1,9 @@
1
1
  import { useEffect, useMemo, useRef, useState } from "react";
2
2
  import { Box } from "../../../ui";
3
3
  import { usePaneSettingValue, useShortcut } from "../../../public/react";
4
- import { splitDelayedStatus, DataTableStackView, KeyValueRow, PaneStatusBody, StatGrid, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, usePaneTicker, type DataTableColumn } from "../../../components";
4
+ import { createDataTableCsv } from "../../../components/data-table/export";
5
+ import { dropColumnsToFit } from "../../../components/ui/table-layout";
6
+ import { splitDelayedStatus, DataTableStackView, KeyValueRow, PaneStatusBody, StatGrid, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, usePaneTicker, type DataTableCell, type DataTableColumn } from "../../../components";
5
7
  import { useThemeColors } from "../../../theme/theme-context";
6
8
  import type { PaneProps } from "../../../types/plugin";
7
9
  import type { TapeQuote, TapeSnapshot, TapeTrade } from "../../../api-client/tape";
@@ -32,6 +34,12 @@ const QUOTES: DataTableColumn[] = [
32
34
  { id: "askExchange", label: "VENUE", width: 9, align: "left" },
33
35
  { id: "spread", label: "SPREAD BP", width: 10, align: "right" },
34
36
  ];
37
+ /**
38
+ * A narrow pane gives up the venues, then the lots, before the right edge cuts
39
+ * the spread: each pair together, since one side's alone would not say which
40
+ * side it is. The scrollbar keeps one cell, and exports keep every column.
41
+ */
42
+ const QUOTE_DROP_ORDER = [["bidExchange", "askExchange"], ["bidSize", "askSize"]];
35
43
  const rank = (value: number | null) => value == null ? "pctl --" : `${value.toFixed(0)} pctl`;
36
44
  type TapeRow = { id: string; trade?: TapeTrade; quote?: TapeQuote };
37
45
 
@@ -112,13 +120,32 @@ function TimeSalesView({ width, height, focused, symbol, exchange }: PaneProps &
112
120
  info: data ? [...splitDelayedStatus({ id: "feed", quoteStatus: true, parts: [{ text: `${data.feed === "sip" ? "real-time" : "15m delayed"} · ${tapeStampNewYork(data.asOf)}`, tone: "muted" }] }),
113
121
  ...(frozen ? [{ id: "paused", parts: [{ text: "paused", tone: "warning" as const }] }] : []),
114
122
  ...(!data.connected || resource.snapshotOnly ? [{ id: "snapshot", parts: [{ text: "snapshot", tone: "warning" as const }] }] : [])] : [] });
123
+ const quoteColumns = useMemo(() => dropColumnsToFit(QUOTES, width - 1, QUOTE_DROP_ORDER), [width]);
124
+ const columns = tab === "quotes" ? quoteColumns : TRADES;
115
125
  const selectTab = (value: string) => { setTab(value); setDetail(null); setSelected(null); };
116
126
  const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs: TABS, activeValue: tab, onSelect: selectTab, focused: focused && !detail, dense: true });
127
+ const renderCell = (row: TapeRow, column: DataTableColumn): DataTableCell => {
128
+ if (row.trade) {
129
+ const trade = row.trade;
130
+ const tick = ticks.get(trade);
131
+ return { text: column.id === "time" ? tapeClockNewYork(trade.timestamp) : column.id === "price" ? tapePrice(trade.price, tradeDigits)
132
+ : column.id === "size" ? tapeQuantity(trade.size) : column.id === "venue" ? tapeVenue(trade.exchange)
133
+ : column.id === "conditions" ? tapeConditionWords(trade.conditions) : trade.tape,
134
+ // Price by tick, as on a tape; a block print lights the rest of its row.
135
+ color: laterThanPin(row) ? colors.textMuted : column.id === "price" ? tick === "up" ? colors.positive : tick === "down" ? colors.negative : colors.text
136
+ : trade.size >= 10_000 ? colors.warning : colors.textMuted };
137
+ }
138
+ const quote = row.quote!, spread = quoteSpread(quote);
139
+ return { text: column.id === "time" ? tapeClockNewYork(quote.timestamp) : column.id === "bid" ? tapePrice(quote.bid, quoteDigits) : column.id === "ask" ? tapePrice(quote.ask, quoteDigits)
140
+ : column.id === "bidSize" ? tapeQuantity(quote.bidSize) : column.id === "askSize" ? tapeQuantity(quote.askSize)
141
+ : column.id === "bidExchange" ? tapeVenue(quote.bidExchange) : column.id === "askExchange" ? tapeVenue(quote.askExchange)
142
+ : spread.bps == null ? "--" : spread.bps.toFixed(2), color: laterThanPin(row) ? colors.textMuted : spread.state === "normal" ? colors.text : colors.warning };
143
+ };
117
144
  if (!data && isCloudSessionRequired(resource.error)) return <SignInWall placement="time-sales-signin" action="view time and sales" needsVerification={session.needsVerification} />;
118
145
  return <Box width={width} height={height} flexDirection="column">
119
146
  {tabStrip}
120
147
  <PaneStatusBody loading={resource.loading && !data} error={!data ? resource.error : null} empty={!!data && !rows.length} subject={tab === "quotes" ? "NBBO observations" : "trade observations"}>
121
- {data && stats ? <DataTableStackView<TapeRow> columns={tab === "quotes" ? QUOTES : TRADES} items={rows}
148
+ {data && stats ? <DataTableStackView<TapeRow> columns={columns} items={rows}
122
149
  focused={focused} rootWidth={width} rootHeight={Math.max(3, height - tabRows)}
123
150
  selection={{ kind: "id", selectedId: selected, getId: (row) => row.id, onChange: setSelected }}
124
151
  onActivate={(row) => { setPaused({ data, epoch: resource.epoch }); setDetail({ row, epoch: resource.epoch }); }} getItemKey={(row) => row.id}
@@ -136,23 +163,8 @@ function TimeSalesView({ width, height, focused, symbol, exchange }: PaneProps &
136
163
  ]),
137
164
  ...(data.session.high != null && data.session.low != null ? [{ id: "session", label: "Session", value: `${tapePrice(data.session.low, sessionDigits)} to ${tapePrice(data.session.high, sessionDigits)}` }] : []),
138
165
  ]} />}
139
- renderCell={(row, column) => {
140
- if (row.trade) {
141
- const trade = row.trade;
142
- const tick = ticks.get(trade);
143
- return { text: column.id === "time" ? tapeClockNewYork(trade.timestamp) : column.id === "price" ? tapePrice(trade.price, tradeDigits)
144
- : column.id === "size" ? tapeQuantity(trade.size) : column.id === "venue" ? tapeVenue(trade.exchange)
145
- : column.id === "conditions" ? tapeConditionWords(trade.conditions) : trade.tape,
146
- // Price by tick, as on a tape; a block print lights the rest of its row.
147
- color: laterThanPin(row) ? colors.textMuted : column.id === "price" ? tick === "up" ? colors.positive : tick === "down" ? colors.negative : colors.text
148
- : trade.size >= 10_000 ? colors.warning : colors.textMuted };
149
- }
150
- const quote = row.quote!, spread = quoteSpread(quote);
151
- return { text: column.id === "time" ? tapeClockNewYork(quote.timestamp) : column.id === "bid" ? tapePrice(quote.bid, quoteDigits) : column.id === "ask" ? tapePrice(quote.ask, quoteDigits)
152
- : column.id === "bidSize" ? tapeQuantity(quote.bidSize) : column.id === "askSize" ? tapeQuantity(quote.askSize)
153
- : column.id === "bidExchange" ? tapeVenue(quote.bidExchange) : column.id === "askExchange" ? tapeVenue(quote.askExchange)
154
- : spread.bps == null ? "--" : spread.bps.toFixed(2), color: laterThanPin(row) ? colors.textMuted : spread.state === "normal" ? colors.text : colors.warning };
155
- }} emptyStateTitle="No observations in this window." /> : null}
166
+ renderCell={renderCell} exportCsv={() => createDataTableCsv({ columns: tab === "quotes" ? QUOTES : TRADES, items: rows, renderCell })}
167
+ emptyStateTitle="No observations in this window." /> : null}
156
168
  </PaneStatusBody>
157
169
  </Box>;
158
170
  }
@@ -1,8 +1,16 @@
1
1
  import { httpFetch } from "../../../utils/http-transport";
2
2
  import { numericOrNull, scalarTextOrNull } from "../../../utils/guards";
3
- import type { CashAccount, DebtDay, OperatingCash } from "./model";
3
+ import { cashLineLabel, type CashAccount, type DebtDay, type OperatingCash, type TgaClose } from "./model";
4
4
 
5
5
  const CASH_URL = "https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v1/accounting/dts/operating_cash_balance?sort=-record_date&page[size]=40";
6
+ // One line of the statement, every day since it took its present form in 2022: about a thousand closes.
7
+ const TGA_CLOSING_ACCOUNT = "Treasury General Account (TGA) Closing Balance";
8
+ const TGA_HISTORY_URL = `https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v1/accounting/dts/operating_cash_balance?${new URLSearchParams({
9
+ filter: `account_type:eq:${TGA_CLOSING_ACCOUNT}`,
10
+ fields: "record_date,open_today_bal",
11
+ sort: "-record_date",
12
+ "page[size]": "3000",
13
+ })}`;
6
14
  const DEBT_URL = "https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v2/accounting/od/debt_to_penny?sort=-record_date&page[size]=20";
7
15
  const FETCH_TIMEOUT_MS = 15_000;
8
16
  const DEBT_DAYS = 20;
@@ -10,6 +18,9 @@ const DEBT_DAYS = 20;
10
18
  export interface TreasuryDaily {
11
19
  cash: OperatingCash;
12
20
  debt: DebtDay[];
21
+ /** The Treasury General Account's closing balance each day, oldest first. Empty when it could not be read, with the reason. */
22
+ tga: TgaClose[];
23
+ tgaError?: string;
13
24
  }
14
25
 
15
26
  function recordDate(value: unknown): string | null {
@@ -39,7 +50,7 @@ function parseOperatingCash(payload: unknown): OperatingCash {
39
50
  parsed.push({
40
51
  id: `${date}:${line ?? account}`,
41
52
  date,
42
- account,
53
+ account: cashLineLabel(account),
43
54
  // Since the 2022 statement format the day's figures are in open_today_bal
44
55
  // and close_today_bal is always "null".
45
56
  today: numericOrNull(row.open_today_bal),
@@ -60,6 +71,17 @@ function parseOperatingCash(payload: unknown): OperatingCash {
60
71
  };
61
72
  }
62
73
 
74
+ function parseTgaHistory(payload: unknown): TgaClose[] {
75
+ const byDate = new Map<string, number>();
76
+ for (const row of rowsOf(payload)) {
77
+ const date = recordDate(row.record_date);
78
+ // The closing balance is sent in open_today_bal, as the statement's other lines are.
79
+ const millions = numericOrNull(row.open_today_bal);
80
+ if (date && millions != null && !byDate.has(date)) byDate.set(date, millions);
81
+ }
82
+ return [...byDate].map(([date, millions]) => ({ date, millions })).sort((a, b) => a.date.localeCompare(b.date));
83
+ }
84
+
63
85
  function parseDebtToPenny(payload: unknown): DebtDay[] {
64
86
  const byDate = new Map<string, DebtDay>();
65
87
  for (const row of rowsOf(payload)) {
@@ -87,10 +109,24 @@ async function readJson(url: string, label: string, signal?: AbortSignal): Promi
87
109
  return response.json() as Promise<unknown>;
88
110
  }
89
111
 
90
- export async function fetchTreasuryDaily(signal?: AbortSignal): Promise<TreasuryDaily> {
91
- const [cash, debt] = await Promise.all([
112
+ /**
113
+ * The latest day's cash lines and the last 20 days of debt, with the account's
114
+ * closing balance history unless `history` is off (a report that does not draw it).
115
+ * A history that cannot be read leaves the figures as they are.
116
+ */
117
+ export async function fetchTreasuryDaily(signal?: AbortSignal, { history = true }: { history?: boolean } = {}): Promise<TreasuryDaily> {
118
+ const [cash, debt, tga] = await Promise.all([
92
119
  readJson(CASH_URL, "Treasury cash", signal),
93
120
  readJson(DEBT_URL, "Treasury debt", signal),
121
+ history ? readJson(TGA_HISTORY_URL, "Treasury account history", signal).then(parseTgaHistory).then(
122
+ (closes) => ({ closes }),
123
+ (error: unknown) => ({ closes: [] as TgaClose[], error: error instanceof Error ? error.message : String(error) }),
124
+ ) : { closes: [] as TgaClose[] },
94
125
  ]);
95
- return { cash: parseOperatingCash(cash), debt: parseDebtToPenny(debt) };
126
+ return {
127
+ cash: parseOperatingCash(cash),
128
+ debt: parseDebtToPenny(debt),
129
+ tga: tga.closes,
130
+ ...("error" in tga ? { tgaError: tga.error } : {}),
131
+ };
96
132
  }
@@ -3,7 +3,7 @@ import { fetchTreasuryDaily } from "./client";
3
3
  import { formatBillions, formatTrillions } from "./model";
4
4
 
5
5
  const CASH_COLUMNS: HeadlessPaneColumn[] = [
6
- { key: "account", header: "Account" },
6
+ { key: "account", header: "Treasury General Account" },
7
7
  { key: "today", header: "Today $bn", align: "right", description: "The day's figure, billions of dollars", format: formatBillions },
8
8
  { key: "month", header: "This month $bn", align: "right", description: "Month to date, billions of dollars", format: formatBillions },
9
9
  ];
@@ -32,7 +32,7 @@ export const treasuryDailyHeadless: HeadlessPaneDefinition<"bundle"> = {
32
32
  describe: "Treasury operating cash and public debt",
33
33
  freshness: { source: "US Treasury", status: "not-a-feed", basis: "daily statement" },
34
34
  async load(_args, ctx) {
35
- const { cash, debt } = await fetchTreasuryDaily(ctx.signal);
35
+ const { cash, debt } = await fetchTreasuryDaily(ctx.signal, { history: false });
36
36
  return {
37
37
  complete: cash.rows.length > 0 && debt.length > 0,
38
38
  sections: [
@@ -8,7 +8,7 @@ export const treasuryDailyModule: PluginModule = {
8
8
  id: "treasury-daily-pane",
9
9
  paneId: TREASURY_DAILY_PANE_ID,
10
10
  label: "Treasury Cash and Debt",
11
- description: "Treasury operating cash for the latest day, and debt held by the public for the last 20 business days.",
11
+ description: "Treasury operating cash for the latest day with the Treasury General Account's closing balance history, and debt held by the public for the last 20 business days.",
12
12
  keywords: ["treasury", "dts", "debt", "cash", "tga", "debt to the penny", "operating cash"],
13
13
  shortcut: { prefix: "DTS" },
14
14
  headless: treasuryDailyHeadless,
@@ -1,4 +1,5 @@
1
1
  import type { DataTableColumn } from "../../../components";
2
+ import { formatNumber } from "../../../utils/format";
2
3
 
3
4
  export const TREASURY_DAILY_PANE_ID = "treasury-daily";
4
5
 
@@ -23,6 +24,51 @@ export interface OperatingCash {
23
24
  rows: CashAccount[];
24
25
  }
25
26
 
27
+ /** One day's Treasury General Account closing balance, millions of dollars. */
28
+ export interface TgaClose {
29
+ date: string;
30
+ millions: number;
31
+ }
32
+
33
+ /** The longest stretch of closing balances the Cash tab charts, and the ranges it offers. */
34
+ export type TgaRange = "3M" | "6M" | "1Y" | "ALL";
35
+ export const TGA_RANGES: ReadonlyArray<{ value: TgaRange; label: string; hint: string; months: number | null }> = [
36
+ { value: "3M", label: "3M", hint: "1", months: 3 },
37
+ { value: "6M", label: "6M", hint: "2", months: 6 },
38
+ { value: "1Y", label: "1Y", hint: "3", months: 12 },
39
+ { value: "ALL", label: "All", hint: "4", months: null },
40
+ ];
41
+ const DEFAULT_TGA_RANGE: TgaRange = "1Y";
42
+
43
+ export function tgaRange(value: unknown): TgaRange {
44
+ return TGA_RANGES.find((range) => range.value === value)?.value ?? DEFAULT_TGA_RANGE;
45
+ }
46
+
47
+ /** The closes of the last `range`, counted back from the newest one, oldest first. */
48
+ export function tgaClosesInRange(closes: readonly TgaClose[], range: TgaRange): TgaClose[] {
49
+ const months = TGA_RANGES.find((entry) => entry.value === range)?.months ?? null;
50
+ const last = closes.at(-1);
51
+ if (months == null || !last) return [...closes];
52
+ const start = new Date(`${last.date}T00:00:00Z`);
53
+ start.setUTCMonth(start.getUTCMonth() - months);
54
+ const from = start.toISOString().slice(0, 10);
55
+ return closes.filter((close) => close.date >= from);
56
+ }
57
+
58
+ /**
59
+ * What the Treasury's statement calls a line of the Treasury General Account,
60
+ * said plainly: `Treasury General Account (TGA) Opening Balance` is the
61
+ * opening balance, `Total TGA Withdrawals (Table II) (-)` the withdrawals. A
62
+ * line the statement adds later keeps its own words, less the table reference.
63
+ */
64
+ export function cashLineLabel(account: string): string {
65
+ if (/^Treasury General Account \(TGA\) Opening Balance$/i.test(account)) return "Opening balance";
66
+ if (/^Treasury General Account \(TGA\) Closing Balance$/i.test(account)) return "Closing balance";
67
+ if (/^Total TGA Deposits\b/i.test(account)) return "Deposits";
68
+ if (/^Total TGA Withdrawals\b/i.test(account)) return "Withdrawals";
69
+ return account.replace(/\s*\(Table [IVX]+\)/g, "").replace(/\s*\(-\)$/, "").trim();
70
+ }
71
+
26
72
  export interface DebtDay {
27
73
  id: string;
28
74
  date: string;
@@ -40,7 +86,7 @@ export type CashColumn = DataTableColumn & { id: CashColumnId };
40
86
  export type DebtColumn = DataTableColumn & { id: DebtColumnId };
41
87
 
42
88
  export const CASH_COLUMNS: CashColumn[] = [
43
- { id: "account", label: "Account", width: 28, align: "left", flexGrow: 1 },
89
+ { id: "account", label: "Treasury General Account", width: 26, align: "left", flexGrow: 1 },
44
90
  { id: "today", label: "Today $bn", width: 12, align: "right" },
45
91
  { id: "month", label: "This month $bn", width: 15, align: "right" },
46
92
  ];
@@ -52,10 +98,11 @@ export const DEBT_COLUMNS: DebtColumn[] = [
52
98
  { id: "total", label: "Total $tn", width: 12, align: "right" },
53
99
  ];
54
100
 
101
+ /** Two decimals with thousands separators, as every money table writes a large figure: `2,594.63`. */
55
102
  function scaled(value: unknown, divisor: number): string {
56
103
  if (typeof value !== "number" || !Number.isFinite(value)) return "--";
57
- const text = (value / divisor).toFixed(2);
58
- return text === "-0.00" ? "0.00" : text;
104
+ const rounded = Number((value / divisor).toFixed(2));
105
+ return formatNumber(rounded === 0 ? 0 : rounded, 2);
59
106
  }
60
107
 
61
108
  /** Millions of dollars, drawn as billions. */
@@ -1,33 +1,52 @@
1
1
  import { useCallback, useMemo, useState } from "react";
2
2
  import {
3
+ ChartTableHeader,
3
4
  DataTableView,
4
5
  PaneStatusBody,
6
+ QueryBar,
7
+ usePaneNoticeFooter,
5
8
  usePaneStatusFooter,
6
9
  usePaneTabs,
10
+ type ChartTableChart,
7
11
  type DataTableCell,
12
+ type DataTableKeyEvent,
8
13
  } from "../../../components";
9
- import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
14
+ import { spanAxisFormatter } from "../../../components/chart-table";
15
+ import { useChartViewReset } from "../../../components/chart/core/view-reset";
16
+ import { scalarPoint, staticSeries } from "../../../components/chart/static/series";
17
+ import { handleRefreshKey, usePaneRefreshKey } from "../../../components/data-table/table-pane";
10
18
  import { useAsyncResource, useAutoRefresh, usePluginPaneState } from "../../../public/react";
11
19
  import { useThemeColors } from "../../../theme/theme-context";
12
20
  import type { PaneProps } from "../../../types/plugin";
13
21
  import { Box } from "../../../ui";
22
+ import { formatNumber } from "../../../utils/format";
23
+ import { isPlainKeyboardEvent } from "../../../utils/keyboard";
14
24
  import { fetchTreasuryDaily } from "./client";
15
25
  import {
16
26
  CASH_COLUMNS,
17
27
  DEBT_COLUMNS,
28
+ TGA_RANGES,
18
29
  TREASURY_DAILY_PANE_ID,
19
30
  TREASURY_TABS,
20
31
  formatBillions,
21
32
  formatTrillions,
33
+ tgaClosesInRange,
34
+ tgaRange,
22
35
  type CashAccount,
23
36
  type CashColumn,
24
37
  type DebtColumn,
25
38
  type DebtDay,
39
+ type TgaRange,
26
40
  type TreasuryTab,
27
41
  } from "./model";
28
42
 
29
43
  const EMPTY_CASH: CashAccount[] = [];
30
44
  const EMPTY_DEBT: DebtDay[] = [];
45
+ const RANGE_OPTIONS = TGA_RANGES.map(({ value, label, hint }) => ({ value, label, hint }));
46
+ const TGA_SERIES_LABEL = "TGA closing balance $bn";
47
+ /** Ticks in billions, with thousands separators and the decimals the plotted range needs. */
48
+ const formatBillionsAxis = spanAxisFormatter((value, digits) => formatNumber(value, digits));
49
+ const formatBillionsLevel = (value: number) => formatNumber(value, 2);
31
50
 
32
51
  function scaledCell(value: number | null, text: string, muted: string): DataTableCell {
33
52
  return value == null ? { text, value, color: muted } : { text, value };
@@ -44,6 +63,9 @@ export function TreasuryDailyPane({ width, height, focused }: PaneProps) {
44
63
  const debtRows = data?.debt ?? EMPTY_DEBT;
45
64
  const activeRows = tab === "cash" ? cashRows : debtRows;
46
65
  const recordDate = tab === "cash" ? data?.cash.recordDate ?? null : debtRows[0]?.date ?? null;
66
+ const [storedRange, setStoredRange] = usePluginPaneState<string>("range", "1Y");
67
+ const range = tgaRange(storedRange);
68
+ const resetChartView = useChartViewReset();
47
69
  const [selectedId, setSelectedId] = useState<string | null>(null);
48
70
  const resolvedId = activeRows.some((row) => row.id === selectedId) ? selectedId : activeRows[0]?.id ?? null;
49
71
  const info = useMemo(
@@ -58,13 +80,46 @@ export function TreasuryDailyPane({ width, height, focused }: PaneProps) {
58
80
  dense: true,
59
81
  });
60
82
  useAutoRefresh(resource.updatedAt, resource.load);
61
- usePaneRefreshKey(() => void resource.reload(), { focused });
83
+ const refresh = useCallback(() => void resource.reload(), [resource.reload]);
84
+ usePaneRefreshKey(refresh, { focused });
62
85
  usePaneStatusFooter({
63
86
  registrationId: TREASURY_DAILY_PANE_ID,
64
87
  loading: resource.loading,
65
88
  error: resource.error,
66
89
  info,
67
90
  });
91
+ usePaneNoticeFooter({
92
+ registrationId: `${TREASURY_DAILY_PANE_ID}:history`,
93
+ focused: focused && tab === "cash",
94
+ notices: data?.tgaError ? [`Closing balance history is unavailable: ${data.tgaError}`] : [],
95
+ });
96
+
97
+ // The closing balance the table's last line states for the day, drawn over the range chosen.
98
+ const series = useMemo(() => {
99
+ const closes = tgaClosesInRange(data?.tga ?? [], range);
100
+ return closes.length < 2 ? [] : [staticSeries(
101
+ closes.map((close) => scalarPoint(new Date(`${close.date}T00:00:00Z`), close.millions / 1_000)),
102
+ { id: "tga-closing-balance", label: TGA_SERIES_LABEL, color: colors.positive, calendarSpaced: true },
103
+ )];
104
+ }, [colors.positive, data?.tga, range]);
105
+ const chart: ChartTableChart | null = data?.tgaError ? null : {
106
+ series,
107
+ formatValue: formatBillionsLevel,
108
+ formatAxisValue: formatBillionsAxis,
109
+ remoteKind: "treasury-general-account",
110
+ empty: "No closing balance history.",
111
+ };
112
+ // The number keys pick the range, as the bar's hints say; picking the one showing sends the chart back to it.
113
+ const handleKeyDown = useCallback((event: DataTableKeyEvent) => {
114
+ if (handleRefreshKey(event, refresh, { stopPropagation: true })) return true;
115
+ if (!isPlainKeyboardEvent(event)) return false;
116
+ const picked = TGA_RANGES.find((option) => option.hint === event.name);
117
+ if (!picked) return false;
118
+ event.preventDefault?.();
119
+ if (picked.value === range) resetChartView();
120
+ else setStoredRange(picked.value);
121
+ return true;
122
+ }, [range, refresh, resetChartView, setStoredRange]);
68
123
 
69
124
  const bodyHeight = Math.max(1, height - tabRows);
70
125
  const renderCash = useCallback((row: CashAccount, column: CashColumn): DataTableCell => {
@@ -91,6 +146,30 @@ export function TreasuryDailyPane({ width, height, focused }: PaneProps) {
91
146
  focused={focused}
92
147
  rootWidth={width}
93
148
  rootHeight={bodyHeight}
149
+ rootBefore={chart ? (
150
+ <ChartTableHeader
151
+ width={width}
152
+ height={bodyHeight}
153
+ tableRows={cashRows.length}
154
+ tableColumns={CASH_COLUMNS}
155
+ query={(
156
+ <QueryBar
157
+ width={width}
158
+ filters={[{
159
+ id: "range",
160
+ label: "Range",
161
+ inline: true,
162
+ chartRange: true,
163
+ value: range,
164
+ options: RANGE_OPTIONS,
165
+ onChange: (value: string) => setStoredRange(tgaRange(value) satisfies TgaRange),
166
+ }]}
167
+ />
168
+ )}
169
+ chart={chart}
170
+ />
171
+ ) : undefined}
172
+ onRootKeyDown={handleKeyDown}
94
173
  columns={CASH_COLUMNS}
95
174
  items={cashRows}
96
175
  sortColumnId={null}
@@ -4,10 +4,15 @@ import type { ProjectedChartPoint } from "../../../components/chart/core/data";
4
4
  import { resolveChartPalette } from "../../../components/chart/core/palette";
5
5
  import { useThemeColors } from "../../../theme/theme-context";
6
6
  import { Box, ScrollBox, Text } from "../../../ui";
7
+ import { formatStrikeLabel } from "../../../utils/options";
7
8
  import { evaluateSmile, optionDelta } from "../shared/volatility";
8
- import { buildSurfaceGrid, evaluateSurfaceSmile, type SurfaceExpiry, type SurfaceSnapshot } from "./model";
9
+ import { buildSurfaceGrid, deltaBasis, evaluateSurfaceSmile, type SurfaceExpiry, type SurfaceSnapshot } from "./model";
9
10
  import { expiryLabel, formatPrice } from "./tables";
10
11
 
12
+ // A strike tick reads as OMON writes strikes; the moneyness and delta axes stay whole numbers.
13
+ const tickLabel = (value: number, axis: string) => axis === "strike" ? formatStrikeLabel(Number(value.toFixed(2)))
14
+ : `${value.toFixed(0)}${axis === "spot" || axis === "forward" ? "%" : ""}`;
15
+
11
16
  const point = (x: number, y: number): ProjectedChartPoint => ({ date: new Date(Math.round(x * 1_000_000)), open: y, high: y, low: y, close: y, volume: 0 });
12
17
 
13
18
  export function SmileChart({ snapshot, expiry, overlay, axis, width, height, focused = false }: {
@@ -25,8 +30,8 @@ export function SmileChart({ snapshot, expiry, overlay, axis, width, height, foc
25
30
  if (axis === "strike") return strike;
26
31
  if (axis === "delta") {
27
32
  const volatility = evaluateSmile(expiry.fit!, Math.log(strike / expiry.forward!));
28
- const delta = volatility == null ? null : optionDelta({ spot: snapshot.spot, years: expiry.years,
29
- volatility, rate: expiry.rate ?? 0, dividendYield: expiry.dividendYield ?? 0 }, strike, "call");
33
+ const delta = volatility == null ? null : optionDelta({ ...deltaBasis(expiry.rateMethod, snapshot.spot, expiry.forward!,
34
+ expiry.rate ?? 0, expiry.dividendYield ?? 0), years: expiry.years, volatility }, strike, "call");
30
35
  return delta == null ? NaN : (1 - delta) * 100;
31
36
  }
32
37
  return strike / (axis === "forward" ? expiry.forward! : snapshot.spot) * 100;
@@ -57,7 +62,7 @@ export function SmileChart({ snapshot, expiry, overlay, axis, width, height, foc
57
62
  }
58
63
  const left = observations[0]?.x ?? 0, right = observations.at(-1)?.x ?? 1;
59
64
  return { points: observations.map((entry) => point(entry.x, entry.iv * 100)), overlays,
60
- ticks: Array.from({ length: 5 }, (_, i) => ({ ratio: i / 4, label: `${(left + (right - left) * i / 4).toFixed(axis === "strike" ? 2 : 0)}${axis === "spot" || axis === "forward" ? "%" : ""}` })),
65
+ ticks: Array.from({ length: 5 }, (_, i) => ({ ratio: i / 4, label: tickLabel(left + (right - left) * i / 4, axis) })),
61
66
  left, right };
62
67
  }, [axis, colors, expiry, overlay, snapshot]);
63
68
  if (!model) return <EmptyState title="Smile unavailable." hint="No clean quoted smile for this expiry." />;
@@ -1,4 +1,5 @@
1
1
  import { apiClient } from "../../../api-client";
2
+ import { createTtlCache } from "../../../data/ttl-cache";
2
3
  import { getSharedMarketDataCoordinator, MarketDataCoordinator, resolveEntryValue } from "../../../market-data/coordinator";
3
4
  import type { InstrumentRef, OptionsRequest } from "../../../market-data/request-types";
4
5
  import type { QueryEntry } from "../../../market-data/result-types";
@@ -12,7 +13,7 @@ import {
12
13
  } from "./model";
13
14
  import { abortable, abortError } from "../../../utils/async-deadline";
14
15
  import { errorMessage } from "../../../utils/errors";
15
- import { expiredExpiryText, findListedExpiry, isLiveOptionExpiry, liveOptionExpiries, unlistedExpiryText } from "../../../utils/option-expiry";
16
+ import { expiredExpiryText, findListedExpiry, isLiveOptionExpiry, liveOptionExpiries, optionExpiryVenue, unlistedExpiryText } from "../../../utils/option-expiry";
16
17
  import { optionModelSpot } from "../options/model-spot";
17
18
 
18
19
  const DEFAULT_SURFACE_EXPIRY_LIMIT = 18;
@@ -73,19 +74,8 @@ export function withReusedYieldCurve(
73
74
  dependencies: SurfaceLoaderDependencies,
74
75
  reuseMs = TREASURY_CURVE_REUSE_MS,
75
76
  ): SurfaceLoaderDependencies {
76
- let held: { at: number; curve: Promise<YieldPoint[]> } | null = null;
77
- return {
78
- ...dependencies,
79
- loadYieldCurve: () => {
80
- const now = Date.now();
81
- if (held && now - held.at < reuseMs) return held.curve;
82
- const curve = dependencies.loadYieldCurve();
83
- const entry = { at: now, curve };
84
- held = entry;
85
- curve.catch(() => { if (held === entry) held = null; });
86
- return curve;
87
- },
88
- };
77
+ const curves = createTtlCache<YieldPoint[]>({ ttlMs: reuseMs });
78
+ return { ...dependencies, loadYieldCurve: () => curves.load("curve", () => dependencies.loadYieldCurve()) };
89
79
  }
90
80
 
91
81
  export interface SurfaceLoadRequest {
@@ -197,6 +187,8 @@ export async function loadVolatilitySurface(
197
187
  publish();
198
188
  });
199
189
 
190
+ // Expiries close on the listing's clock: a coin's options (Deribit) settle at 08:00 UTC on any day.
191
+ const expiryVenue = optionExpiryVenue(request.instrument.symbol, request.instrument.exchange);
200
192
  try {
201
193
  const initial = await abortable(dependencies.loadOptions({ instrument: request.instrument },
202
194
  { forceRefresh: request.expiries ? false : request.forceRefresh }), request.signal, CANCELLED);
@@ -217,7 +209,7 @@ export async function loadVolatilitySurface(
217
209
  catalogueError = initial.error?.message ?? null;
218
210
  catalogueReason = initial.error?.reasonCode;
219
211
  if (!chain) catalogueError ??= "Options expiry catalogue unavailable";
220
- catalogue = liveOptionExpiries(chain?.expirationDates ?? [], now, request.instrument.exchange);
212
+ catalogue = liveOptionExpiries(chain?.expirationDates ?? [], now, expiryVenue);
221
213
  // A typed date is that day's UTC midnight; the catalogue's stamp for the
222
214
  // same expiry is matched by calendar date, as OMON matches it.
223
215
  const required = [...new Set([...(request.requiredExpiries ?? []), ...(request.expiries ?? [])]
@@ -228,8 +220,8 @@ export async function loadVolatilitySurface(
228
220
  ...required.filter((expiration) => listed.has(expiration))])].sort((a, b) => a - b);
229
221
  // An expiry whose last session has closed is named as expired, with the nearest live one to pick.
230
222
  requiredFailures = required.filter((expiration) => !listed.has(expiration)).map((expiration) => ({
231
- expiration, message: isLiveOptionExpiry(expiration, now, request.instrument.exchange) ? unlistedExpiryText(expiration, catalogue)
232
- : expiredExpiryText(expiration, catalogue, now, { exchange: request.instrument.exchange }),
223
+ expiration, message: isLiveOptionExpiry(expiration, now, expiryVenue) ? unlistedExpiryText(expiration, catalogue)
224
+ : expiredExpiryText(expiration, catalogue, now, { exchange: expiryVenue }),
233
225
  }));
234
226
  if (chain && catalogue.length === 0) catalogueError ??= "No unexpired option expiries available";
235
227
  } catch (error) {