@gloom-sh/terminal 0.18.5 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +140 -7
  3. package/src/api-client/cot.ts +6 -2
  4. package/src/api-client/credit-documents.ts +6 -0
  5. package/src/api-client/data.ts +17 -6
  6. package/src/api-client/equity-screener.ts +24 -0
  7. package/src/api-client/fund-facts.ts +45 -4
  8. package/src/api-client/index.ts +11 -0
  9. package/src/api-client/market-discovery.ts +10 -0
  10. package/src/api-client/market-movers.ts +7 -0
  11. package/src/api-client/members.ts +1 -1
  12. package/src/api-client/perps.ts +43 -0
  13. package/src/api-client/research-activity.ts +3 -2
  14. package/src/api-client/types.ts +70 -2
  15. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  16. package/src/app/runtime/broker-import.ts +0 -2
  17. package/src/app/runtime/plugin-bindings.ts +73 -56
  18. package/src/architecture/pane-tabs-allowlist.json +3 -0
  19. package/src/architecture/ttl-caches-allowlist.json +19 -0
  20. package/src/brokers/sync-broker-instance.ts +2 -8
  21. package/src/capabilities/chart-series.ts +1 -1
  22. package/src/cli/commands/crypto-hints.ts +40 -24
  23. package/src/cli/commands/market.ts +72 -26
  24. package/src/cli/commands/options-chain.ts +48 -7
  25. package/src/cli/commands/options-leaps.ts +9 -3
  26. package/src/cli/commands/overview.ts +61 -12
  27. package/src/cli/commands/search.ts +21 -1
  28. package/src/cli/commands/themes.ts +11 -2
  29. package/src/cli/commands/ticker.ts +55 -34
  30. package/src/cli/company-name-hint.ts +41 -0
  31. package/src/cli/earnings-calendar.ts +1 -1
  32. package/src/cli/errors.ts +8 -0
  33. package/src/cli/glossary.ts +25 -2
  34. package/src/cli/history-rows.ts +57 -9
  35. package/src/cli/index.ts +2 -0
  36. package/src/cli/listing-arg.ts +180 -52
  37. package/src/cli/output-file.ts +68 -0
  38. package/src/cli/pane-functions/capabilities.ts +3 -0
  39. package/src/cli/pane-functions/catalog.ts +80 -7
  40. package/src/cli/pane-functions/dom.ts +13 -3
  41. package/src/cli/pane-functions/freshness.ts +86 -3
  42. package/src/cli/pane-functions/function-help.ts +13 -10
  43. package/src/cli/pane-functions/headless.ts +17 -7
  44. package/src/cli/pane-functions/index.ts +49 -14
  45. package/src/cli/pane-functions/market-session.ts +66 -4
  46. package/src/cli/pane-functions/options.ts +19 -1
  47. package/src/cli/pane-functions/resolver.ts +19 -8
  48. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  49. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  50. package/src/cli/quote-answer.ts +22 -11
  51. package/src/cli/ticker-resolution.ts +3 -2
  52. package/src/components/chart/composite/composite-chart.tsx +4 -0
  53. package/src/components/chart/composite/interactions.ts +6 -0
  54. package/src/components/chart/composite/legend.tsx +45 -7
  55. package/src/components/chart/composite/panel-gestures.ts +33 -0
  56. package/src/components/chart/composite/panel-surface.tsx +52 -3
  57. package/src/components/chart/composite/rasterizer.ts +24 -13
  58. package/src/components/chart/composite/scene.ts +39 -4
  59. package/src/components/chart/composite/series-cues.ts +14 -0
  60. package/src/components/chart/composite/text-renderer.ts +9 -2
  61. package/src/components/chart/composite/tool-catalog.ts +36 -0
  62. package/src/components/chart/composite/tools.ts +203 -44
  63. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  64. package/src/components/command-bar/assist/inventory.ts +2 -1
  65. package/src/components/command-bar/commands/direct/index.ts +28 -13
  66. package/src/components/command-bar/pane-templates/items.ts +3 -2
  67. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  68. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  69. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  70. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  71. package/src/components/onboarding/wizard-positions.ts +3 -3
  72. package/src/components/ticker/list-table-view.tsx +4 -1
  73. package/src/components/ui/choice-dialog.tsx +23 -18
  74. package/src/components/ui/choice-groups.ts +18 -0
  75. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  76. package/src/components/ui/data-table/opentui/model.ts +26 -1
  77. package/src/components/ui/data-table/types.ts +2 -0
  78. package/src/components/ui/list-view.tsx +8 -4
  79. package/src/components/ui/query-bar-keys.tsx +2 -0
  80. package/src/components/ui/query-bar.tsx +1 -0
  81. package/src/components/ui/select-button.tsx +5 -1
  82. package/src/components/ui/select-field.tsx +1 -0
  83. package/src/components/ui/table-layout.ts +8 -5
  84. package/src/core/app-runtime.ts +2 -0
  85. package/src/core/app-services.ts +2 -2
  86. package/src/data/config/chart-settings.ts +1 -1
  87. package/src/data/json-ticker-repository.ts +17 -8
  88. package/src/data/ticker-repository.ts +16 -9
  89. package/src/data/ticker-writes.ts +81 -0
  90. package/src/data/ttl-cache.ts +165 -0
  91. package/src/docs/snapshot.json +71 -137
  92. package/src/i18n/es.ts +18 -2
  93. package/src/i18n/ja.ts +18 -2
  94. package/src/i18n/ko.ts +18 -2
  95. package/src/i18n/zh-cn.ts +18 -2
  96. package/src/i18n/zh-tw.ts +18 -2
  97. package/src/market-data/depositary-pair.ts +2 -2
  98. package/src/market-data/fund-facts.ts +50 -0
  99. package/src/market-data/market/change-basis.ts +16 -3
  100. package/src/market-data/market/format.ts +25 -0
  101. package/src/market-data/market/freshness.ts +2 -1
  102. package/src/market-data/options-alternatives.ts +81 -4
  103. package/src/news/aggregator.ts +113 -9
  104. package/src/news/cloud-session.ts +21 -0
  105. package/src/news/hooks.ts +1 -0
  106. package/src/news/news-model.ts +1 -0
  107. package/src/news/types.ts +11 -0
  108. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  109. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  110. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  111. package/src/plugins/builtin/alerts/cli.ts +336 -77
  112. package/src/plugins/builtin/alerts/command.ts +25 -1
  113. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  114. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  115. package/src/plugins/builtin/alerts/format.ts +75 -5
  116. package/src/plugins/builtin/alerts/headless.ts +24 -6
  117. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  118. package/src/plugins/builtin/alerts/history.ts +1 -1
  119. package/src/plugins/builtin/alerts/index.tsx +79 -16
  120. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  121. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  122. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  123. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  124. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  125. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  126. package/src/plugins/builtin/alerts/storage.ts +12 -8
  127. package/src/plugins/builtin/alerts/types.ts +4 -0
  128. package/src/plugins/builtin/analytics/index.tsx +2 -1
  129. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  130. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  131. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  132. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  134. package/src/plugins/builtin/attention/index.tsx +2 -1
  135. package/src/plugins/builtin/attention/listing.ts +14 -0
  136. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  137. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  138. package/src/plugins/builtin/buildout/format.ts +38 -0
  139. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  140. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  141. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  142. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  143. package/src/plugins/builtin/changelog/index.tsx +9 -6
  144. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  145. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  146. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  147. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  148. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  149. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  150. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  151. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  152. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  153. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  154. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  155. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  156. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  157. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  158. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  159. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  160. package/src/plugins/builtin/company-attention/client.ts +8 -9
  161. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  162. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  163. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  164. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  165. package/src/plugins/builtin/correlation/headless.ts +13 -5
  166. package/src/plugins/builtin/correlation/history.ts +22 -0
  167. package/src/plugins/builtin/cot/client.ts +22 -18
  168. package/src/plugins/builtin/cot/headless.ts +15 -10
  169. package/src/plugins/builtin/cot/model.ts +14 -1
  170. package/src/plugins/builtin/cot/pane.tsx +22 -14
  171. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  172. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  173. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  174. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  175. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  176. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  177. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  178. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  179. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  180. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  181. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  182. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  183. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  184. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  185. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  186. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  187. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  188. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  189. package/src/plugins/builtin/docs/targets.ts +5 -1
  190. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  191. package/src/plugins/builtin/earnings/board.tsx +62 -71
  192. package/src/plugins/builtin/earnings/headless.ts +40 -4
  193. package/src/plugins/builtin/earnings/scope.ts +31 -43
  194. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  195. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  196. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  197. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  198. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  199. package/src/plugins/builtin/econ/types.ts +2 -0
  200. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  201. package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
  202. package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
  203. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  204. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  205. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  206. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  207. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  208. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  209. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  210. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  211. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  212. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  213. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  214. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  215. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  216. package/src/plugins/builtin/gpu/headless.ts +8 -2
  217. package/src/plugins/builtin/help/function-index.ts +4 -1
  218. package/src/plugins/builtin/help/index.tsx +1 -0
  219. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  220. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  221. package/src/plugins/builtin/holders/headless.ts +18 -1
  222. package/src/plugins/builtin/iv-history/client.ts +52 -11
  223. package/src/plugins/builtin/iv-history/format.ts +36 -23
  224. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  225. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  226. package/src/plugins/builtin/iv-history/model.ts +163 -27
  227. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  228. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  229. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  230. package/src/plugins/builtin/jobs/client.ts +25 -29
  231. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  232. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  233. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  234. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  235. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  236. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  237. package/src/plugins/builtin/market-movers/client.ts +25 -7
  238. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  239. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  240. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  241. package/src/plugins/builtin/market-movers/model.ts +4 -1
  242. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  243. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  244. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  245. package/src/plugins/builtin/market-movers/table.ts +17 -5
  246. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  247. package/src/plugins/builtin/news/index.tsx +1 -0
  248. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  249. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  250. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  251. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  252. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  253. package/src/plugins/builtin/openfda/client.ts +14 -2
  254. package/src/plugins/builtin/openfda/index.tsx +3 -2
  255. package/src/plugins/builtin/openfda/model.ts +9 -0
  256. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  257. package/src/plugins/builtin/options/analytics.ts +29 -5
  258. package/src/plugins/builtin/options/enrichment.ts +17 -5
  259. package/src/plugins/builtin/options/table.ts +5 -2
  260. package/src/plugins/builtin/options/types.ts +2 -0
  261. package/src/plugins/builtin/options/view.tsx +17 -4
  262. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  263. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  264. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  265. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  266. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  267. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  268. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  269. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  270. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  271. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  272. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  273. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  274. package/src/plugins/builtin/pe-band/model.ts +12 -49
  275. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  276. package/src/plugins/builtin/perps/cascade.ts +236 -0
  277. package/src/plugins/builtin/perps/client.ts +56 -2
  278. package/src/plugins/builtin/perps/headless.ts +65 -9
  279. package/src/plugins/builtin/perps/index.tsx +7 -5
  280. package/src/plugins/builtin/perps/model.ts +18 -4
  281. package/src/plugins/builtin/perps/pane.tsx +19 -22
  282. package/src/plugins/builtin/perps/status-line.ts +25 -0
  283. package/src/plugins/builtin/plugin-module.ts +5 -1
  284. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  285. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  286. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  287. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  288. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  289. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  290. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  291. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  292. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  293. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  294. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  295. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  296. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  297. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  298. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  299. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  300. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  301. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  302. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  303. package/src/plugins/builtin/research/event-model.ts +10 -8
  304. package/src/plugins/builtin/research/headless.ts +1 -1
  305. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  306. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  307. package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
  308. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  309. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  310. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  311. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  312. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  313. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  314. package/src/plugins/builtin/scanner/headless.ts +204 -35
  315. package/src/plugins/builtin/scanner/index.ts +16 -5
  316. package/src/plugins/builtin/scanner/live-headless.ts +27 -3
  317. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  318. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  319. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  320. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  321. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  322. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  323. package/src/plugins/builtin/sec/headless.ts +2 -1
  324. package/src/plugins/builtin/series-inventory.ts +2 -0
  325. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  326. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  327. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  328. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  329. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  330. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  331. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  332. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  333. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  334. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  335. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  336. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  337. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  338. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  339. package/src/plugins/builtin/themes/model.ts +16 -7
  340. package/src/plugins/builtin/themes/pane.tsx +5 -5
  341. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  342. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  343. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  344. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  345. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  346. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  347. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  348. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  349. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  350. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  351. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  352. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  353. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  354. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  355. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  357. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  358. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  359. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  360. package/src/plugins/builtin/volatility/index.tsx +2 -2
  361. package/src/plugins/builtin/volatility/pane.tsx +2 -2
  362. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  363. package/src/plugins/catalog-manifest.ts +2 -1
  364. package/src/plugins/marketplace/model.ts +18 -5
  365. package/src/portfolio/futures-positions.ts +6 -3
  366. package/src/portfolio/position-quantity.ts +15 -9
  367. package/src/remote/resources.ts +2 -1
  368. package/src/renderers/dom/data-table/row.tsx +26 -0
  369. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  370. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  371. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  372. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  373. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  374. package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
  375. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  376. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  377. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  378. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  379. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  380. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  381. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  382. package/src/renderers/electrobun/view/main.tsx +24 -1
  383. package/src/renderers/electrobun/view/news-source.ts +3 -1
  384. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  385. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  386. package/src/renderers/opentui/start.tsx +5 -0
  387. package/src/series/registry.ts +1 -0
  388. package/src/series/types.ts +6 -0
  389. package/src/series/validate.ts +1 -0
  390. package/src/sources/gloomberb-cloud/index.ts +1 -0
  391. package/src/sources/gloomberb-cloud/news.ts +47 -7
  392. package/src/sources/provider-router/financials.ts +15 -3
  393. package/src/sources/provider-router/index.ts +8 -5
  394. package/src/state/ticker-cleanup.ts +50 -0
  395. package/src/sync/controller.ts +4 -3
  396. package/src/sync/core-contributors.ts +90 -31
  397. package/src/telemetry/stall-reports-dom.ts +38 -0
  398. package/src/telemetry/stall-reports-node.ts +75 -0
  399. package/src/telemetry/stall-reports.ts +214 -0
  400. package/src/test-support/preload.ts +14 -0
  401. package/src/theme/themes.ts +10 -0
  402. package/src/tickers/company-name.ts +44 -3
  403. package/src/tickers/listing-venues.ts +15 -3
  404. package/src/tickers/terminal-codes.ts +19 -0
  405. package/src/time-series/alignment.ts +1 -1
  406. package/src/time-series/economic.ts +18 -4
  407. package/src/time-series/field-catalog.ts +1 -1
  408. package/src/time-series/filed-trailing-eps.ts +197 -0
  409. package/src/time-series/fundamentals.ts +44 -1
  410. package/src/time-series/ice-credit.ts +3 -0
  411. package/src/time-series/price-comparison.ts +30 -2
  412. package/src/time-series/resolve-context.ts +3 -1
  413. package/src/time-series/resolve.ts +56 -4
  414. package/src/time-series/session-bar.ts +11 -0
  415. package/src/time-series/spec.ts +3 -2
  416. package/src/time-series/studies.ts +8 -2
  417. package/src/time-series/total-return.ts +160 -0
  418. package/src/time-series/transforms.ts +2 -3
  419. package/src/time-series/types.ts +22 -1
  420. package/src/types/financials.ts +32 -1
  421. package/src/types/headless.ts +21 -3
  422. package/src/types/news-source.ts +1 -0
  423. package/src/types/plugin.ts +9 -0
  424. package/src/ui/host.tsx +3 -0
  425. package/src/updater/failure.ts +4 -1
  426. package/src/utils/cli-output.ts +42 -1
  427. package/src/utils/crypto-pair.ts +44 -0
  428. package/src/utils/forward-dividend.ts +2 -0
  429. package/src/utils/fundamentals.ts +24 -3
  430. package/src/utils/fx-market-hours.ts +26 -3
  431. package/src/utils/main-thread-stall.ts +35 -2
  432. package/src/utils/market-capitalization.ts +24 -1
  433. package/src/utils/price-history.ts +7 -1
  434. package/src/utils/shell-word.ts +8 -0
  435. package/src/utils/trailing-dividend.ts +53 -4
  436. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -1,5 +1,5 @@
1
1
  import { apiClient } from "../../../api-client";
2
- import type { CotBoardPayload, CotClass, CotClassSummary, CotContractPayload, CotFamily, CotPayloadBase } from "../../../api-client/cot";
2
+ import type { CotBoardPayload, CotClass, CotClassSummary, CotContractPayload, CotFamily, CotPayloadBase, CotPositions } from "../../../api-client/cot";
3
3
  import { createPluginCache } from "../../../data/plugin-cache";
4
4
  import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
5
5
  import type { PricePoint } from "../../../types/financials";
@@ -12,8 +12,8 @@ const date = (value: unknown): value is string => typeof value === "string" && /
12
12
  const number = (value: unknown) => value === null || typeof value === "number" && Number.isFinite(value);
13
13
  const count = (value: unknown) => value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
14
14
  const code = (value: unknown) => typeof value === "string" && /^[0-9A-Z]{5}[0-9A-Z+]$/.test(value);
15
- function base(data: CotPayloadBase, family: CotFamily) {
16
- if (!data || data.source !== "CFTC" || data.scope !== "futures-only" || data.reportFamily !== family
15
+ function base(data: CotPayloadBase, family: CotFamily, positions: CotPositions) {
16
+ if (!data || data.source !== "CFTC" || data.scope !== positions || data.reportFamily !== family
17
17
  || !Array.isArray(data.gaps) || !data.gaps.every((gap) => typeof gap === "string")
18
18
  || !Array.isArray(data.classes) || data.classes.some((row) => !COT_CLASSES[family].some((option) => option.value === row.id))
19
19
  || data.asOf !== null && !date(data.asOf) || !Number.isFinite(Date.parse(data.generatedAt))
@@ -33,8 +33,8 @@ function summary(row: CotClassSummary, family: CotFamily) {
33
33
  }
34
34
  }
35
35
  }
36
- function validateCotBoard(data: CotBoardPayload, family: CotFamily, traderClass: CotClass) {
37
- base(data, family);
36
+ function validateCotBoard(data: CotBoardPayload, family: CotFamily, traderClass: CotClass, positions: CotPositions) {
37
+ base(data, family, positions);
38
38
  if (!Array.isArray(data.rows) || data.traderClass !== traderClass) throw new Error("The server returned an invalid COT board");
39
39
  const seen = new Set<string>();
40
40
  for (const row of data.rows) {
@@ -44,8 +44,8 @@ function validateCotBoard(data: CotBoardPayload, family: CotFamily, traderClass:
44
44
  }
45
45
  return data;
46
46
  }
47
- export function validateCotContract(data: CotContractPayload, family: CotFamily, contractCode: string) {
48
- base(data, family);
47
+ export function validateCotContract(data: CotContractPayload, family: CotFamily, contractCode: string, positions: CotPositions = "futures-only") {
48
+ base(data, family, positions);
49
49
  if (data.contract !== null && data.contract.contractCode !== contractCode || !Array.isArray(data.positions) || !Array.isArray(data.history)) {
50
50
  throw new Error("The server returned an invalid COT contract");
51
51
  }
@@ -63,30 +63,34 @@ export function validateCotContract(data: CotContractPayload, family: CotFamily,
63
63
  return data;
64
64
  }
65
65
  const unavailable = (error: unknown) => unavailableOnServer(error, "COT history is not available yet.", [404, 503]);
66
- export async function fetchCotBoard(family: CotFamily, traderClass: CotClass, client: Pick<typeof apiClient, "getCloudCotBoard"> = apiClient) {
67
- try { return validateCotBoard(await client.getCloudCotBoard(family, traderClass), family, traderClass); }
66
+ export async function fetchCotBoard(family: CotFamily, traderClass: CotClass, client: Pick<typeof apiClient, "getCloudCotBoard"> = apiClient, positions: CotPositions = "futures-only") {
67
+ try { return validateCotBoard(await client.getCloudCotBoard(family, traderClass, positions), family, traderClass, positions); }
68
68
  catch (error) { throw unavailable(error); }
69
69
  }
70
- const cotBoardKey = (family: CotFamily, traderClass: CotClass) => `${family}:${traderClass}`;
70
+ // Futures-only boards keep the key they always had, so a cached board still opens.
71
+ const cotBoardKey = (family: CotFamily, traderClass: CotClass, positions: CotPositions) =>
72
+ positions === "combined" ? `${family}:${traderClass}:combined` : `${family}:${traderClass}`;
71
73
  const presentCotBoard = (payload: CotBoardPayload, stale: boolean, refreshError: string | null) => ({
72
74
  ...payload, gaps: [...payload.gaps, ...(refreshError ? [refreshError] : [])],
73
75
  status: stale && payload.status === "available" ? "partial" as const : payload.status,
74
76
  });
75
- export function cachedCotBoard(family: CotFamily, traderClass: CotClass) {
76
- const cached = cachedCloudResource(cotBoardCache, cotBoardKey(family, traderClass), (payload) => validateCotBoard(payload, family, traderClass));
77
+ export function cachedCotBoard(family: CotFamily, traderClass: CotClass, positions: CotPositions = "futures-only") {
78
+ const cached = cachedCloudResource(cotBoardCache, cotBoardKey(family, traderClass, positions), (payload) => validateCotBoard(payload, family, traderClass, positions));
77
79
  return cached ? presentCotBoard(cached.payload, false, null) : null;
78
80
  }
79
- export async function loadCotBoard(family: CotFamily, traderClass: CotClass, force = false) {
80
- const { payload, stale, refreshError } = await loadCloudResource(cotBoardCache, cotBoardKey(family, traderClass), () => fetchCotBoard(family, traderClass), { force });
81
+ export async function loadCotBoard(family: CotFamily, traderClass: CotClass, force = false, positions: CotPositions = "futures-only") {
82
+ const { payload, stale, refreshError } = await loadCloudResource(cotBoardCache, cotBoardKey(family, traderClass, positions),
83
+ () => fetchCotBoard(family, traderClass, apiClient, positions), { force });
81
84
  return presentCotBoard(payload, stale, refreshError);
82
85
  }
83
- export async function fetchCotContract(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract"> = apiClient) {
84
- try { return validateCotContract(await client.getCloudCotContract(contractCode, family), family, contractCode); }
86
+ export async function fetchCotContract(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract"> = apiClient, positions: CotPositions = "futures-only") {
87
+ try { return validateCotContract(await client.getCloudCotContract(contractCode, family, positions), family, contractCode, positions); }
85
88
  catch (error) { throw unavailable(error); }
86
89
  }
87
90
  export interface CotDetailData { payload: CotContractPayload; price: PricePoint[]; priceSymbol: string | null; priceAsOf: string | null; priceWarning: string | null }
88
- export async function loadCotDetail(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract" | "getCloudHistory"> = apiClient): Promise<CotDetailData> {
89
- const payload = await fetchCotContract(contractCode, family, client);
91
+ export async function loadCotDetail(contractCode: string, family: CotFamily, client: Pick<typeof apiClient, "getCloudCotContract" | "getCloudHistory"> = apiClient,
92
+ positions: CotPositions = "futures-only"): Promise<CotDetailData> {
93
+ const payload = await fetchCotContract(contractCode, family, client, positions);
90
94
  const mapping = cotPriceMapping(contractCode);
91
95
  // An unmapped market simply has no price panel; that is not a data failure.
92
96
  if (!mapping?.priceSymbol) return { payload, price: [], priceSymbol: null, priceAsOf: null, priceWarning: null };
@@ -1,7 +1,7 @@
1
1
  import type { CotClass, CotClassSummary, CotContractPayload, CotHistoryPoint } from "../../../api-client/cot";
2
2
  import type { HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/plugin";
3
3
  import { fetchCotBoard, fetchCotContract } from "./client";
4
- import { COT_MAJOR_CODES, COT_SCOPES, cotClass, cotContractCode, cotContractUnitText, cotInteger, cotMarketName, cotScope, cotUnknownMarketMessage } from "./model";
4
+ import { COT_CLASSES, COT_FAMILIES, COT_MAJOR_CODES, COT_POSITIONS, COT_SCOPES, cotClass, cotContractCode, cotContractUnitText, cotFamily, cotInteger, cotMarketName, cotPositions, cotPositionsText, cotScope, cotUnknownMarketMessage } from "./model";
5
5
  import { COMMODITIES_VIEW_STATE } from "../commodities-energy/view-state";
6
6
  import { REPORT_ROWS } from "../shared/headless-options";
7
7
 
@@ -24,9 +24,11 @@ function cotPositionRow(row: CotClassSummary) {
24
24
  */
25
25
  function cotContractTitle(data: CotContractPayload, code: string): string {
26
26
  const units = cotContractUnitText(data.contract?.contractUnits);
27
- return [data.contract ? cotMarketName(data.contract.marketName) : code, data.asOf ? `report date ${data.asOf}` : null, units]
28
- .filter(Boolean).join(" · ");
27
+ return [data.contract ? cotMarketName(data.contract.marketName) : code, data.asOf ? `report date ${data.asOf}` : null, units,
28
+ data.scope === "combined" ? cotPositionsText(data.scope) : null].filter(Boolean).join(" · ");
29
29
  }
30
+ /** Every trader class of every report, each once, in the reports' order. */
31
+ const TRADER_CLASSES = [...new Set(Object.values(COT_CLASSES).flat().map(({ value }) => value))];
30
32
 
31
33
  /** The weekly reports newest first, the newest `limit` of them when given. */
32
34
  function cotWeeklyRows(history: readonly CotHistoryPoint[], limit: number | null): CotHistoryPoint[] {
@@ -47,22 +49,25 @@ export const cotHeadless: HeadlessPaneDefinition<"bundle"> = {
47
49
  // Tuesday positions published the Friday after; two weeks without a new report means one was missed.
48
50
  freshness: { source: "CFTC", status: "not-a-feed", basis: "weekly report", maxAgeMinutes: 14 * 24 * 60 },
49
51
  argument: { kind: "free-text", optional: true, placeholder: "code or name", description: "Futures root (KC), commodity name (coffee) or CFTC market code; omit for the extremes board." },
50
- options: [{ key: "report", type: "enum", values: [{ value: "legacy" }, { value: "disaggregated" }], defaultValue: "legacy", description: "CFTC report family." },
51
- { key: "traderClass", type: "enum", values: ["noncommercial", "commercial", "producer", "swap", "managed-money", "other-reportable", "nonreportable"].map((value) => ({ value })), description: "Trader class for the extremes board; defaults by report family.",
52
+ options: [{ key: "report", type: "enum", values: COT_FAMILIES.map(({ value }) => ({ value })), defaultValue: "legacy", description: "CFTC report: legacy, disaggregated, or tff (Traders in Financial Futures)." },
53
+ { key: "traderClass", type: "enum", values: TRADER_CLASSES.map((value) => ({ value })), description: "Trader class for the extremes board; defaults by report family.",
52
54
  pluginState: { pluginId: COMMODITIES_VIEW_STATE.cot, key: "class" } },
55
+ { key: "positions", type: "enum", values: COT_POSITIONS.map(({ value }) => ({ value })), defaultValue: "futures-only", description: "Futures positions only, or futures and options combined, as in the pane.",
56
+ pluginState: { pluginId: COMMODITIES_VIEW_STATE.cot, key: "positions" } },
53
57
  { key: "scope", type: "enum", values: COT_SCOPES.map(({ value }) => ({ value })), defaultValue: "major", description: "Markets on the extremes board, as in the pane.",
54
58
  pluginState: { pluginId: COMMODITIES_VIEW_STATE.cot, key: "scope" } },
55
59
  // Weekly reports come newest first, so --tail asks for the same newest n as --limit.
56
60
  { key: "limit", type: "integer", aliases: ["tail", "weeks", "rows"], minimum: 1, maximum: 2000, shot: REPORT_ROWS,
57
61
  description: "Newest weekly reports a market lists; every one by default." }],
58
- discovery: { aliases: ["COT", "CFTC"], dataRequirements: ["Gloom Cloud CFTC history"], limitations: ["Futures-only positions aggregate all maturities.", "Legacy and disaggregated classes differ.", "Observation date is not publication date.", "Some markets have short or suppressed histories."] },
62
+ discovery: { aliases: ["COT", "CFTC"], dataRequirements: ["Gloom Cloud CFTC history"], limitations: ["Positions aggregate all maturities; combined adds options, delta-adjusted.", "Legacy, disaggregated and TFF classes differ.", "Observation date is not publication date.", "Some markets have short or suppressed histories."] },
59
63
  describe: "CFTC positioning",
60
64
  async load(args, ctx) {
61
- const family = args.options.report === "disaggregated" ? "disaggregated" : "legacy";
65
+ const family = cotFamily(args.options.report);
66
+ const positions = cotPositions(args.options.positions);
62
67
  if (args.argument) {
63
68
  const code = cotContractCode(args.argument);
64
69
  if (!code) throw new Error(cotUnknownMarketMessage(args.argument));
65
- const data = await fetchCotContract(code, family, ctx.apiClient);
70
+ const data = await fetchCotContract(code, family, ctx.apiClient, positions);
66
71
  const classes = data.positions.map((row) => row.id);
67
72
  const limit = args.options.limit == null ? null : Number(args.options.limit);
68
73
  const weeks = cotWeeklyRows(data.history, limit);
@@ -80,9 +85,9 @@ export const cotHeadless: HeadlessPaneDefinition<"bundle"> = {
80
85
  const traderClass = cotClass(family, args.options.traderClass);
81
86
  if (args.options.traderClass && traderClass !== args.options.traderClass) throw new Error("Trader class does not belong to this report family");
82
87
  const scope = cotScope(args.options.scope);
83
- const data = await fetchCotBoard(family, traderClass, ctx.apiClient);
88
+ const data = await fetchCotBoard(family, traderClass, ctx.apiClient, positions);
84
89
  const rows = data.rows.filter((row) => scope === "all" || COT_MAJOR_CODES.has(row.contractCode));
85
- return { sections: [{ title: "Positioning extremes",
90
+ return { sections: [{ title: positions === "combined" ? `Positioning extremes · ${cotPositionsText(positions)}` : "Positioning extremes",
86
91
  columns: [{ key: "marketName", header: "Market" }, { key: "contractCode", header: "Code" }, ...POSITION_COLUMNS,
87
92
  { key: "openInterest", header: "Open interest", align: "right", format: count }, { key: "reportDate", header: "As of" }],
88
93
  rows: rows.map((row) => ({ contractCode: row.contractCode, marketName: cotMarketName(row.marketName), reportDate: row.reportDate, openInterest: row.openInterest, ...cotPositionRow(row.position) })) }],
@@ -1,4 +1,4 @@
1
- import type { CotClass, CotContractPayload, CotFamily, CotHistoryPoint } from "../../../api-client/cot";
1
+ import type { CotClass, CotContractPayload, CotFamily, CotHistoryPoint, CotPositions } from "../../../api-client/cot";
2
2
  import { staticSeries } from "../../../components/chart/static/series";
3
3
  import type { ResolvedSeries } from "../../../time-series/types";
4
4
  import type { PricePoint } from "../../../types/financials";
@@ -10,7 +10,20 @@ import { bestNameMatch, FUTURES_CONTRACTS, futuresBoardContract, futuresBoardCon
10
10
  export const COT_CLASSES: Record<CotFamily, Array<{ value: CotClass; label: string }>> = {
11
11
  legacy: [{ value: "noncommercial", label: "Noncommercial" }, { value: "commercial", label: "Commercial" }, { value: "nonreportable", label: "Nonreportable" }],
12
12
  disaggregated: [{ value: "managed-money", label: "Managed Money" }, { value: "producer", label: "Producer/Merchant" }, { value: "swap", label: "Swap Dealers" }, { value: "other-reportable", label: "Other Reportables" }, { value: "nonreportable", label: "Nonreportable" }],
13
+ tff: [{ value: "leveraged-funds", label: "Leveraged Funds" }, { value: "asset-manager", label: "Asset Manager/Institutional" }, { value: "dealer", label: "Dealer/Intermediary" }, { value: "other-reportable", label: "Other Reportables" }, { value: "nonreportable", label: "Nonreportable" }],
13
14
  };
15
+ /** The three CFTC reports, as the pane's tabs and `--report` name them. */
16
+ export const COT_FAMILIES: ReadonlyArray<{ value: CotFamily; label: string }> = [
17
+ { value: "legacy", label: "Legacy" }, { value: "disaggregated", label: "Disaggregated" }, { value: "tff", label: "Financial (TFF)" },
18
+ ];
19
+ export function cotFamily(value: unknown): CotFamily { return value === "disaggregated" || value === "tff" ? value : "legacy"; }
20
+ /** Futures alone, as before, or futures and options combined. */
21
+ export const COT_POSITIONS: ReadonlyArray<{ value: CotPositions; label: string }> = [
22
+ { value: "futures-only", label: "Futures only" }, { value: "combined", label: "Futures + options" },
23
+ ];
24
+ export function cotPositions(value: unknown): CotPositions { return value === "combined" ? "combined" : "futures-only"; }
25
+ /** How a status line names the positions counted: "futures only" or "futures + options". */
26
+ export const cotPositionsText = (positions: CotPositions) => positions === "combined" ? "futures + options" : "futures only";
14
27
  const ROOTS: Record<string, { code: string; exchange: string; priceSymbol: string | null; name: string; crypto?: true }> = {
15
28
  ZN: { code: "043602", exchange: "CBT", priceSymbol: "ZN=F", name: "10-Year T-Note" },
16
29
  ZQ: { code: "045601", exchange: "CBT", priceSymbol: "ZQ=F", name: "30-Day Fed Funds" },
@@ -6,16 +6,15 @@ import { useThemeColors } from "../../../theme/theme-context";
6
6
  import type { PaneProps } from "../../../types/plugin";
7
7
  import type { ResolvedSeries } from "../../../time-series/types";
8
8
  import type { CompositeAxisDomain } from "../../../components/chart/composite/types";
9
- import type { CotBoardRow, CotClass, CotClassSummary, CotFamily } from "../../../api-client/cot";
9
+ import type { CotBoardRow, CotClass, CotClassSummary, CotFamily, CotPositions } from "../../../api-client/cot";
10
10
  import { isAccessDenied } from "../../../api-client/errors";
11
11
  import { Box } from "../../../ui";
12
12
  import { useAutoRefresh } from "../../../react/auto-refresh";
13
13
  import { compareSortValues, nextHeaderSort, type SortPreference } from "../../../utils/sort-values";
14
14
  import { useResearchCloudSession } from "../shared/research-cloud-session";
15
15
  import { cachedCotBoard, loadCotBoard, loadCotDetail } from "./client";
16
- import { COT_CLASSES, COT_MAJOR_CODES, COT_SCOPES, cotChartSeries, cotClass, cotContractUnitText, cotInteger, cotLegendValue, cotMarketName, cotScope, type CotScope } from "./model";
16
+ import { COT_CLASSES, COT_FAMILIES, COT_MAJOR_CODES, COT_POSITIONS, COT_SCOPES, cotChartSeries, cotClass, cotContractUnitText, cotFamily, cotInteger, cotLegendValue, cotMarketName, cotPositions, cotPositionsText, cotScope, type CotScope } from "./model";
17
17
 
18
- const FAMILIES = [{ value: "legacy", label: "Legacy" }, { value: "disaggregated", label: "Disaggregated" }];
19
18
  const BOARD_COLUMNS: DataTableColumn[] = [
20
19
  { id: "name", label: "MARKET", width: 30, align: "left", flexGrow: 1 },
21
20
  { id: "code", label: "CODE", width: 7, align: "left" },
@@ -44,8 +43,9 @@ const cotDetailTitle = (name: string | undefined, code: string | null) => name ?
44
43
 
45
44
  export function CotPane(props: PaneProps) {
46
45
  const pane = usePaneInstance();
47
- const [family, setFamily] = usePluginPaneState<CotFamily>("report", pane?.settings?.report === "disaggregated" ? "disaggregated" : "legacy");
48
- const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs: FAMILIES, activeValue: family, onSelect: (value) => setFamily(value as CotFamily), focused: props.focused, dense: true });
46
+ const [storedFamily, setFamily] = usePluginPaneState<CotFamily>("report", cotFamily(pane?.settings?.report));
47
+ const family = cotFamily(storedFamily);
48
+ const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs: [...COT_FAMILIES], activeValue: family, onSelect: (value) => setFamily(value as CotFamily), focused: props.focused, dense: true });
49
49
  return <Box width={props.width} height={props.height} flexDirection="column">
50
50
  {tabStrip}
51
51
  <CotBoard key={family} {...props} height={Math.max(3, props.height - tabRows)} family={family} initialCode={pane?.params?.code ?? null} />
@@ -63,14 +63,18 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
63
63
  const open = storedOpen || null;
64
64
  const [storedScope, setScope] = usePluginPaneState<CotScope>("scope", "major");
65
65
  const scope = cotScope(storedScope);
66
+ // Futures only, as the pane always read, unless futures and options combined is picked.
67
+ const [storedPositions, setPositions] = usePluginPaneState<CotPositions>("positions", "futures-only");
68
+ const positions = cotPositions(storedPositions);
66
69
  const [query, setQuery] = useState("");
67
70
  const { active: searching, focus: focusSearch, searchProps } = useQueryBarSearch();
68
71
  const [sort, setSort] = useState<SortPreference<string>>({ columnId: null, direction: "desc" });
69
72
  const control = useRef<SelectControl>(null);
70
73
  const scopeControl = useRef<SelectControl>(null);
71
- const loader = useCallback((force: boolean) => loadCotBoard(family, traderClass, force), [family, traderClass, session.requestKey]);
72
- const resource = useAsyncResource(loader, { initialData: () => cachedCotBoard(family, traderClass), clearOnError: isAccessDenied });
73
- const data = resource.data?.traderClass === traderClass ? resource.data : null;
74
+ const positionsControl = useRef<SelectControl>(null);
75
+ const loader = useCallback((force: boolean) => loadCotBoard(family, traderClass, force, positions), [family, traderClass, positions, session.requestKey]);
76
+ const resource = useAsyncResource(loader, { initialData: () => cachedCotBoard(family, traderClass, positions), clearOnError: isAccessDenied });
77
+ const data = resource.data?.traderClass === traderClass && resource.data.scope === positions ? resource.data : null;
74
78
  const rows = useMemo(() => {
75
79
  // A typed query searches every market; the scope only shapes the unsearched board.
76
80
  const result = (data?.rows ?? []).filter((row) => query ? `${row.marketName} ${row.contractCode}`.toLowerCase().includes(query.toLowerCase())
@@ -90,15 +94,17 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
90
94
  if (!boardShown) return;
91
95
  if (event.name === "c") { event.preventDefault(); control.current?.open(); }
92
96
  if (event.name === "s") { event.preventDefault(); scopeControl.current?.open(); }
97
+ if (event.name === "p") { event.preventDefault(); positionsControl.current?.open(); }
93
98
  });
94
99
  usePaneFooter("cot:actions", () => ({ hints: boardShown ? [
95
100
  { id: "class", key: "c", label: "class", onPress: () => control.current?.open() },
96
101
  { id: "scope", key: "s", label: "cope", onPress: () => scopeControl.current?.open() },
102
+ { id: "positions", key: "p", label: "ositions", onPress: () => positionsControl.current?.open() },
97
103
  { id: "search", key: "/", label: "search", onPress: focusSearch },
98
104
  ] : [] }), [boardShown, focusSearch]);
99
105
  usePaneNoticeFooter({ registrationId: "cot:board-notices", focused, enabled: !open, notices: data?.gaps ?? [] });
100
106
  usePaneStatusFooter({ registrationId: "cot:board", enabled: !open, loading: resource.loading, error: resource.error,
101
- info: data ? [{ id: "as-of", parts: [{ text: `CFTC futures only · ${data.asOf ?? "--"} · contracts`, tone: "muted" }] }] : [] });
107
+ info: data ? [{ id: "as-of", parts: [{ text: `CFTC ${cotPositionsText(positions)} · ${data.asOf ?? "--"} · contracts`, tone: "muted" }] }] : [] });
102
108
  // A class change reloads under the query bar, which stays.
103
109
  return <DataTableStackView<CotBoardRow> columns={BOARD_COLUMNS} items={rows} focused={focused && !searching}
104
110
  status={data ? undefined : { loading: resource.loading, error: resource.error, empty: !resource.loading && !resource.error, subject: "COT history" }}
@@ -118,11 +124,12 @@ function CotBoard({ width, height, focused, family, initialCode }: PaneProps & {
118
124
  filters={[
119
125
  { id: "class", label: "Class", value: traderClass, options: COT_CLASSES[family], onChange: setClass, controlRef: control },
120
126
  { id: "scope", label: "Scope", value: scope, defaultValue: "major", options: [...COT_SCOPES], onChange: setScope, controlRef: scopeControl },
127
+ { id: "positions", label: "Positions", value: positions, defaultValue: "futures-only", options: [...COT_POSITIONS], onChange: setPositions, controlRef: positionsControl },
121
128
  ]} />}
122
129
  emptyStateTitle={data ? query ? "No matching COT markets." : "No major markets in this report; switch the scope to all markets." : ""} detailOpen={!!open} onBack={() => setOpen("")}
123
130
  detailTitle={cotDetailTitle(data?.rows.find((row) => row.contractCode === open)?.marketName, open)}
124
- detailContent={open ? <CotDetail key={`${family}:${open}`} width={width} height={Math.max(3, height - 1)} focused={focused}
125
- code={open} family={family} traderClass={traderClass} onClassChange={setClass} /> : null} />;
131
+ detailContent={open ? <CotDetail key={`${family}:${positions}:${open}`} width={width} height={Math.max(3, height - 1)} focused={focused}
132
+ code={open} family={family} positions={positions} traderClass={traderClass} onClassChange={setClass} /> : null} />;
126
133
  }
127
134
 
128
135
  /**
@@ -147,10 +154,10 @@ function CotChart({ series, width, height }: { series: ResolvedSeries[]; width:
147
154
  remoteKind="cot-history" />;
148
155
  }
149
156
 
150
- function CotDetail({ width, height, focused, code, family, traderClass, onClassChange }: Pick<PaneProps, "width" | "height" | "focused"> & { code: string; family: CotFamily; traderClass: CotClass; onClassChange: (value: CotClass) => void }) {
157
+ function CotDetail({ width, height, focused, code, family, positions, traderClass, onClassChange }: Pick<PaneProps, "width" | "height" | "focused"> & { code: string; family: CotFamily; positions: CotPositions; traderClass: CotClass; onClassChange: (value: CotClass) => void }) {
151
158
  const colors = useThemeColors();
152
159
  const session = useResearchCloudSession();
153
- const loader = useCallback(() => loadCotDetail(code, family), [code, family, session.requestKey]);
160
+ const loader = useCallback(() => loadCotDetail(code, family, undefined, positions), [code, family, positions, session.requestKey]);
154
161
  const resource = useAsyncResource(loader, { clearOnError: isAccessDenied });
155
162
  const data = resource.data;
156
163
  const payload = data?.payload;
@@ -178,7 +185,8 @@ function CotDetail({ width, height, focused, code, family, traderClass, onClassC
178
185
  usePaneNoticeFooter({ registrationId: "cot:detail-notices", focused, notices: [...(payload?.gaps ?? []), ...(data?.priceWarning ? [data.priceWarning] : []), ...partialRanks] });
179
186
  usePaneStatusFooter({ registrationId: "cot:detail", loading: resource.loading, error: resource.error,
180
187
  // The unit the net is counted in, as `fn COT` titles it: what one contract is, where the report says.
181
- info: payload ? [{ id: "as-of", parts: [{ text: `CFTC ${payload.asOf ?? "--"} · ${cotContractUnitText(payload.contract?.contractUnits) ?? "contracts"}${data?.priceAsOf ? ` · ${data.priceSymbol} ${data.priceAsOf}` : ""}`, tone: "muted" }] }] : [] });
188
+ // Futures and options combined says so; futures only reads as it always has.
189
+ info: payload ? [{ id: "as-of", parts: [{ text: `CFTC ${positions === "combined" ? `${cotPositionsText(positions)} · ` : ""}${payload.asOf ?? "--"} · ${cotContractUnitText(payload.contract?.contractUnits) ?? "contracts"}${data?.priceAsOf ? ` · ${data.priceSymbol} ${data.priceAsOf}` : ""}`, tone: "muted" }] }] : [] });
182
190
  return <PaneStatusBody loading={resource.loading && !data} error={!data ? resource.error : null} empty={!!payload && !payload.contract} subject="COT contract">
183
191
  {payload && data ? <DataTableView columns={POSITION_COLUMNS} items={payload.positions} focused={focused} rootWidth={width} rootHeight={height}
184
192
  rootBefore={<ChartTableHeader width={width} height={height} tableRows={payload.positions.length} chart={chart}
@@ -5,7 +5,9 @@ import type {
5
5
  SovrBoardPayload,
6
6
  } from "../../../api-client/credit-boards";
7
7
  import { createPluginCache } from "../../../data/plugin-cache";
8
+ import { createTtlCache } from "../../../data/ttl-cache";
8
9
  import type { PricePoint } from "../../../types/financials";
10
+ import { logFailure } from "../../../utils/log-failure";
9
11
  import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
10
12
  import { currencyMoveFromHistory, currencyPair } from "./model";
11
13
 
@@ -84,18 +86,12 @@ export type PriceHistoryLoader = (symbol: string) => Promise<PricePoint[]>;
84
86
  const FX_HISTORY_TTL_MS = 30 * 60_000;
85
87
  /** Twenty-odd currencies at once; a few requests at a time keep the feed from refusing them. */
86
88
  const FX_HISTORY_CONCURRENCY = 4;
87
- const fxMoves = new Map<string, { at: number; move: Promise<number | null> }>();
89
+ const fxMoves = createTtlCache<number | null>({ ttlMs: FX_HISTORY_TTL_MS });
88
90
 
91
+ /** A load that fails reads as unknown for everyone waiting on it and is asked again next time, never remembered as unknown. */
89
92
  function currencyMove(symbol: string, dollarsPerUnit: boolean, loadHistory: PriceHistoryLoader): Promise<number | null> {
90
- const cached = fxMoves.get(symbol);
91
- if (cached && Date.now() - cached.at <= FX_HISTORY_TTL_MS) return cached.move;
92
- const move = loadHistory(symbol).then((history) => currencyMoveFromHistory(history, dollarsPerUnit));
93
- fxMoves.set(symbol, { at: Date.now(), move });
94
- // A failed load is retried next time rather than remembered as unknown.
95
- return move.catch(() => {
96
- fxMoves.delete(symbol);
97
- return null;
98
- });
93
+ return fxMoves.load(symbol, async () => currencyMoveFromHistory(await loadHistory(symbol), dollarsPerUnit))
94
+ .catch(logFailure("credit-boards", "Read a currency's price history failed", null));
99
95
  }
100
96
 
101
97
  /**
@@ -7,7 +7,7 @@ import {
7
7
  type CreditConditionsLoadResult,
8
8
  type CreditSeriesLoader,
9
9
  } from "./client";
10
- import { creditRegion, creditSeriesIn } from "./model";
10
+ import { creditRegion, creditSeriesIn, type CreditConditionRow } from "./model";
11
11
 
12
12
  const COLUMNS = [
13
13
  { key: "label", header: "Index" },
@@ -22,6 +22,11 @@ const COLUMNS = [
22
22
  { key: "date", header: "As of" },
23
23
  ];
24
24
 
25
+ /** What the publisher caps the indexes' history at, said once for the report. */
26
+ function historyLimits(rows: readonly CreditConditionRow[]): string[] {
27
+ return [...new Set(rows.flatMap((row) => row.historyLimit ? [row.historyLimit] : []))];
28
+ }
29
+
25
30
  interface CreditConditionsHeadlessDependencies {
26
31
  load(args: HeadlessPaneLoadArgs, loader: CreditSeriesLoader): Promise<CreditConditionsLoadResult>;
27
32
  }
@@ -57,7 +62,8 @@ function createCreditConditionsHeadless(
57
62
  );
58
63
  return {
59
64
  // The year of history feeds the pane's chart and rank, not the report rows.
60
- rows: result.rows.map(({ history: _history, ...row }) => row),
65
+ rows: result.rows.map(({ history: _history, historyLimit: _historyLimit, ...row }) => row),
66
+ notes: historyLimits(result.rows),
61
67
  errors: result.errors,
62
68
  metadata: { stale: result.stale },
63
69
  };
@@ -110,10 +110,14 @@ export function CreditConditionsPane({ paneId, focused, width, height }: PanePro
110
110
  const partial = rows.length > 0 && rows.length < series.length;
111
111
  // Each row carries its own date in the AS OF column; a spread between
112
112
  // indexes from different sessions is a limitation behind the warning.
113
+ const historyLimits = useMemo(() => [...new Set(rows.flatMap((row) => row.historyLimit ? [row.historyLimit] : []))], [rows]);
113
114
  usePaneNoticeFooter({
114
115
  registrationId: "credit-conditions:notices",
115
116
  focused,
116
- notices: mixedDates ? ["The indexes carry different observation dates, so the spreads are not one session."] : [],
117
+ notices: [
118
+ ...mixedDates ? ["The indexes carry different observation dates, so the spreads are not one session."] : [],
119
+ ...historyLimits,
120
+ ],
117
121
  });
118
122
  const footerInfo = useMemo<PaneFooterSegment[]>(() => [
119
123
  ...(rows.length > 0 ? [DELAYED_SEGMENT] : []),
@@ -1,5 +1,6 @@
1
1
  import type { CloudFredSeriesPayload } from "../../../api-client";
2
2
  import { historyStatistics } from "../../../components/chart/curve/model";
3
+ import { fredHistoryLimitNotice } from "../../../time-series/economic";
3
4
  import { ICE_SPREAD_SERIES, type IceCreditSeries } from "../../../time-series/ice-credit";
4
5
 
5
6
  export type CreditSeriesDefinition = Pick<IceCreditSeries, "seriesId" | "label" | "region">;
@@ -30,6 +31,8 @@ export interface CreditConditionRow {
30
31
  dailyChangeBp: number | null;
31
32
  date: string;
32
33
  stale: boolean;
34
+ /** The publisher's cap on how far back the series goes, when FRED states one. */
35
+ historyLimit: string | null;
33
36
  /** Observations in the year to `date`, in basis points, oldest first. */
34
37
  history: Array<{ date: string; valueBp: number }>;
35
38
  /** Midrank of the latest spread within that year. */
@@ -85,6 +88,7 @@ export function normalizeCreditSeries(
85
88
  dailyChangeBp: previous ? roundTenth((latest.value - previous.value) * 100) : null,
86
89
  date: latest.date,
87
90
  stale,
91
+ historyLimit: fredHistoryLimitNotice(definition.seriesId, info),
88
92
  history,
89
93
  // One observation ranks nothing.
90
94
  percentile1Y: year.count >= 2 ? year.percentile : null,
@@ -1,6 +1,6 @@
1
1
  import { fredSeriesUrl } from "../../../sources/gloomberb-cloud/fred-series";
2
2
  import type { SeriesDefinition } from "../../../series/types";
3
- import { ICE_CREDIT_SERIES } from "../../../time-series/ice-credit";
3
+ import { ICE_CREDIT_SERIES, ICE_HISTORY_LIMIT_NOTICE } from "../../../time-series/ice-credit";
4
4
 
5
5
  /** Corporate bond option-adjusted spreads by rating and region, and the effective yields of the US indexes. */
6
6
  export const CREDIT_SPREAD_SERIES: readonly SeriesDefinition[] = ICE_CREDIT_SERIES.map((series) => ({
@@ -11,6 +11,8 @@ export const CREDIT_SPREAD_SERIES: readonly SeriesDefinition[] = ICE_CREDIT_SERI
11
11
  group: "Credit",
12
12
  tags: [series.measure, ...series.tags, "corporate bonds"],
13
13
  publisher: "FRED",
14
+ // A fixed note on every ICE series. G and CRD read the same sentence from each series' FRED notes instead, so there it lapses if FRED lifts the limit.
15
+ limitation: ICE_HISTORY_LIMIT_NOTICE,
14
16
  url: fredSeriesUrl(series.seriesId),
15
17
  expression: `FRED:${series.seriesId}`,
16
18
  }));
@@ -6,7 +6,7 @@ import { useThemeColors } from "../../../theme/theme-context";
6
6
  import { Box, Text, TextAttributes, useRendererHost, useUiCapabilities, type ScrollBoxRenderable } from "../../../ui";
7
7
  import { quoteBlockStyle } from "../../../components/ui/quote";
8
8
  import { fetchCreditInstrument } from "./client";
9
- import { comparatorSymbol, covenantNumber, covenantTerms, creditPercent, factColumns, factValue, fieldLabel, hasAmendments, headroomTone, NO_VALUE, sentence, structuredFields } from "./model";
9
+ import { comparatorSymbol, covenantNumber, covenantReason, covenantTerms, creditPercent, factColumns, factValue, fieldLabel, hasAmendments, headroomTone, NO_VALUE, sentence, structuredFields } from "./model";
10
10
 
11
11
  const LABEL_WIDTH = 16;
12
12
  const MATCH = { exact: "Exact", whitespace: "Whitespace normalized", nfkc_whitespace: "Unicode and whitespace normalized" } as Record<string, string>;
@@ -83,7 +83,7 @@ export function CreditInstrumentDetail({ symbol, instrument, width, height, focu
83
83
  const columns = useMemo(() => factColumns(view, width), [view, width]);
84
84
  const query = <QueryBar width={width} view={{ value: view, options: [{ value: "terms", label: "Current terms" }, { value: "history", label: "Amendment history" }], onChange: (value) => { setView(value); setOpen(null); } }} />;
85
85
  // Why headroom is missing stays beside the figures it explains.
86
- const reason = covenant?.reason && covenant.status !== "compliant" && covenant.status !== "breach" ? covenant.reason : null;
86
+ const reason = covenant ? covenantReason(covenant, data.facts) : null;
87
87
  const header = covenant ? <Box flexDirection="column">
88
88
  <ChartTableHeader width={width} height={height - (reason ? 2 : 0)} tableRows={rows.length} tableColumns={columns} query={query} figures={covenantFigures(covenant, data.facts.find((fact) => fact.id === covenant.id))} />
89
89
  {reason ? <Box paddingX={1} width={width}><Notice tone="muted">{reason}</Notice></Box> : null}