gloomberb 0.16.4 → 0.16.5

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 (554) hide show
  1. package/README.md +14 -91
  2. package/package.json +4 -1
  3. package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
  4. package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
  5. package/src/account/billing.ts +57 -0
  6. package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
  7. package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
  8. package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
  9. package/src/account/session.ts +16 -0
  10. package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
  11. package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
  12. package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
  13. package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
  14. package/src/api-client/data.ts +24 -2
  15. package/src/api-client/earnings.ts +6 -0
  16. package/src/api-client/futures-curve.ts +56 -0
  17. package/src/api-client/index.ts +4 -1
  18. package/src/api-client/market-discovery.ts +10 -1
  19. package/src/api-client/paths.ts +7 -0
  20. package/src/api-client/research-activity.ts +15 -10
  21. package/src/api-client/types.ts +17 -1
  22. package/src/api-client/yield-curves.ts +8 -0
  23. package/src/app/global-shortcuts.ts +15 -1
  24. package/src/app/pane-runtime/ticker-open-runtime.ts +7 -3
  25. package/src/architecture/core-builtin-allowlist.json +4 -13
  26. package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
  27. package/src/capabilities/chart-series.ts +11 -1
  28. package/src/cli/commands/crypto-hints.ts +7 -0
  29. package/src/cli/commands/fred-series.ts +61 -0
  30. package/src/cli/commands/market.ts +407 -104
  31. package/src/cli/commands/news-hints.ts +62 -0
  32. package/src/cli/commands/news-search.ts +53 -0
  33. package/src/cli/commands/options-leaps.ts +5 -2
  34. package/src/cli/commands/overview.ts +93 -20
  35. package/src/cli/commands/remote.ts +2 -1
  36. package/src/cli/commands/search.ts +82 -9
  37. package/src/cli/commands/system.ts +55 -8
  38. package/src/cli/commands/ticker.ts +88 -11
  39. package/src/cli/context.ts +45 -14
  40. package/src/cli/desktop-pane-shot.ts +59 -7
  41. package/src/cli/earnings-calendar.ts +71 -0
  42. package/src/cli/freshness.ts +18 -7
  43. package/src/cli/glossary.ts +429 -0
  44. package/src/cli/history-rows.ts +48 -7
  45. package/src/cli/index-roots.ts +7 -0
  46. package/src/cli/index.ts +21 -7
  47. package/src/cli/known-gaps.ts +27 -0
  48. package/src/cli/listing-arg.ts +417 -56
  49. package/src/cli/not-a-ticker.ts +12 -5
  50. package/src/cli/pane-functions/access-gate.ts +5 -0
  51. package/src/cli/pane-functions/capabilities.ts +3 -2
  52. package/src/cli/pane-functions/catalog.ts +35 -5
  53. package/src/cli/pane-functions/data.ts +12 -8
  54. package/src/cli/pane-functions/discovery.ts +1 -0
  55. package/src/cli/pane-functions/dom.ts +20 -6
  56. package/src/cli/pane-functions/freshness.ts +363 -43
  57. package/src/cli/pane-functions/function-help.ts +12 -5
  58. package/src/cli/pane-functions/headless.ts +45 -17
  59. package/src/cli/pane-functions/index.ts +63 -10
  60. package/src/cli/pane-functions/market-session.ts +22 -8
  61. package/src/cli/pane-functions/options.ts +63 -22
  62. package/src/cli/pane-functions/report-notices.ts +25 -7
  63. package/src/cli/pane-functions/report.ts +15 -8
  64. package/src/cli/pane-functions/resolver.ts +49 -12
  65. package/src/cli/pane-functions/screenshot.ts +110 -23
  66. package/src/cli/report-tables.ts +3 -0
  67. package/src/cli/result.ts +4 -1
  68. package/src/cli/staged-tickers.ts +52 -0
  69. package/src/cli/symbol-args.ts +9 -0
  70. package/src/cli/symbol-list.ts +172 -0
  71. package/src/cli/ticker-resolution.ts +223 -30
  72. package/src/cli/types.ts +5 -0
  73. package/src/components/chart/composite/axis-overlays.tsx +6 -2
  74. package/src/components/chart/composite/composite-chart.tsx +40 -6
  75. package/src/components/chart/composite/legend.tsx +49 -2
  76. package/src/components/chart/composite/panel-surface.tsx +44 -17
  77. package/src/components/chart/composite/price-axis-labels.tsx +3 -1
  78. package/src/components/command-bar/panel/index.tsx +11 -3
  79. package/src/components/command-bar/panel/runtime.ts +3 -0
  80. package/src/components/command-bar/panel/types.ts +2 -0
  81. package/src/components/command-bar/routes/root/provider-search.ts +4 -3
  82. package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
  83. package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
  84. package/src/components/command-bar/workflow/tickers.ts +23 -4
  85. package/src/components/layout/detached-pane-shell.tsx +9 -0
  86. package/src/components/layout/floating-pane.tsx +3 -0
  87. package/src/components/layout/header.tsx +20 -5
  88. package/src/components/layout/pane/content.tsx +7 -13
  89. package/src/components/layout/pane/footer/index.tsx +6 -2
  90. package/src/components/layout/pane/footer/model.ts +35 -1
  91. package/src/components/layout/pane/header.tsx +24 -1
  92. package/src/components/layout/pane/index.tsx +12 -2
  93. package/src/components/layout/pane/sidebar.tsx +78 -10
  94. package/src/components/layout/shell/active-drag.ts +21 -5
  95. package/src/components/layout/shell/drag/index.ts +39 -9
  96. package/src/components/layout/shell/drag/live.ts +61 -51
  97. package/src/components/layout/shell/drag/runtime.ts +54 -12
  98. package/src/components/layout/shell/index.tsx +25 -7
  99. package/src/components/layout/shell/menu.ts +5 -0
  100. package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
  101. package/src/components/layout/shell/pane/layers.tsx +192 -45
  102. package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
  103. package/src/components/markdown-text.tsx +62 -20
  104. package/src/components/metric-treemap/heat-surface.tsx +10 -1
  105. package/src/components/onboarding/account-step/form-panel.tsx +1 -1
  106. package/src/components/onboarding/account-step/index.tsx +1 -1
  107. package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
  108. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  109. package/src/components/onboarding/onboarding-keyboard.ts +1 -1
  110. package/src/components/onboarding/onboarding-wizard.tsx +2 -2
  111. package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
  112. package/src/components/onboarding/wizard-account.ts +2 -2
  113. package/src/components/pane-template-wizard.tsx +9 -1
  114. package/src/components/price-selector-dialog.tsx +11 -2
  115. package/src/components/sign-in-gate.tsx +3 -3
  116. package/src/components/ticker/list-table-view.tsx +24 -1
  117. package/src/components/ui/action-row.tsx +2 -1
  118. package/src/components/ui/button.tsx +7 -1
  119. package/src/components/ui/choice-dialog.tsx +6 -4
  120. package/src/components/ui/confirm-dialog.tsx +28 -18
  121. package/src/components/ui/data-table/index.tsx +17 -6
  122. package/src/components/ui/data-table/opentui/index.tsx +37 -6
  123. package/src/components/ui/data-table/types.ts +12 -0
  124. package/src/components/ui/dialog-actions.tsx +64 -0
  125. package/src/components/ui/display.tsx +17 -3
  126. package/src/components/ui/frame.tsx +35 -2
  127. package/src/components/ui/icon.tsx +2 -0
  128. package/src/components/ui/text-prompt-dialog.tsx +11 -9
  129. package/src/data/cached-query.ts +2 -0
  130. package/src/data/config/store/normalize.ts +7 -2
  131. package/src/data/ticker-repository.ts +5 -0
  132. package/src/data/ticker-store.ts +15 -0
  133. package/src/docs/content.ts +215 -0
  134. package/src/docs/load.ts +139 -0
  135. package/src/docs/requests.ts +39 -0
  136. package/src/docs/snapshot.chunk.ts +8 -0
  137. package/src/docs/snapshot.json +7416 -0
  138. package/src/docs/topics.ts +216 -0
  139. package/src/i18n/es.ts +61 -4
  140. package/src/i18n/ja.ts +61 -4
  141. package/src/i18n/ko.ts +61 -4
  142. package/src/i18n/zh-cn.ts +61 -4
  143. package/src/i18n/zh-tw.ts +61 -4
  144. package/src/layout/desks.ts +1 -1
  145. package/src/layout/pane-manager/dock-tree.ts +18 -0
  146. package/src/layout/pane-manager/index.ts +1 -0
  147. package/src/market-data/coordinator/chart.ts +10 -4
  148. package/src/market-data/coordinator/index.ts +6 -3
  149. package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
  150. package/src/market-data/futures-contract.ts +151 -0
  151. package/src/market-data/hooks.tsx +10 -2
  152. package/src/market-data/market/format.ts +39 -21
  153. package/src/market-data/market/freshness.ts +81 -18
  154. package/src/market-data/market/status.ts +19 -23
  155. package/src/market-data/published-apac-sessions.ts +23 -1
  156. package/src/market-data/quotes/contributions.ts +3 -0
  157. package/src/market-data/quotes/metadata.ts +7 -24
  158. package/src/market-data/quotes/resolution.ts +2 -0
  159. package/src/market-data/quotes/screener-live-quotes.ts +5 -4
  160. package/src/market-data/result-types.ts +2 -0
  161. package/src/plugins/builtin/account-management/model.ts +1 -56
  162. package/src/plugins/builtin/account-management/pane.tsx +6 -4
  163. package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
  164. package/src/plugins/builtin/alerts/cli.ts +313 -47
  165. package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
  166. package/src/plugins/builtin/alerts/events.ts +23 -2
  167. package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
  168. package/src/plugins/builtin/alerts/history.ts +2 -0
  169. package/src/plugins/builtin/alerts/index.tsx +41 -9
  170. package/src/plugins/builtin/alerts/pane.tsx +8 -0
  171. package/src/plugins/builtin/alerts/research-builder.ts +8 -3
  172. package/src/plugins/builtin/alerts/research-rules.ts +18 -2
  173. package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
  174. package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
  175. package/src/plugins/builtin/alerts/scheduled.ts +254 -0
  176. package/src/plugins/builtin/alerts/targets.ts +92 -0
  177. package/src/plugins/builtin/analytics/index.tsx +4 -2
  178. package/src/plugins/builtin/analytics/risk-headless.ts +30 -12
  179. package/src/plugins/builtin/analytics/risk-math.ts +91 -27
  180. package/src/plugins/builtin/analytics/risk-model.ts +35 -5
  181. package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
  182. package/src/plugins/builtin/attention/index.tsx +2 -0
  183. package/src/plugins/builtin/attention/pane.tsx +3 -1
  184. package/src/plugins/builtin/attention/series.ts +26 -0
  185. package/src/plugins/builtin/awards/index.tsx +2 -0
  186. package/src/plugins/builtin/awards/pane.tsx +3 -1
  187. package/src/plugins/builtin/awards/series.ts +29 -0
  188. package/src/plugins/builtin/bond-calculator/model.ts +2 -1
  189. package/src/plugins/builtin/catalysts/pane.tsx +3 -1
  190. package/src/plugins/builtin/cds/client.ts +70 -13
  191. package/src/plugins/builtin/cds/headless.ts +116 -19
  192. package/src/plugins/builtin/cds/index.tsx +3 -1
  193. package/src/plugins/builtin/cds/model.ts +193 -31
  194. package/src/plugins/builtin/cds/pane.tsx +56 -14
  195. package/src/plugins/builtin/cds/series.ts +19 -0
  196. package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
  197. package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
  198. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
  199. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
  200. package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
  201. package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
  202. package/src/plugins/builtin/chart-composer/headless.ts +8 -3
  203. package/src/plugins/builtin/chart-composer/index.tsx +9 -2
  204. package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
  205. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
  206. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
  207. package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
  208. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
  209. package/src/plugins/builtin/chat/controller/index.ts +2 -0
  210. package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
  211. package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
  212. package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
  213. package/src/plugins/builtin/cloud/askg/model.ts +27 -8
  214. package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
  215. package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
  216. package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
  217. package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
  218. package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
  219. package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
  220. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  221. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
  222. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
  223. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
  224. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
  225. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
  226. package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
  227. package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
  228. package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
  229. package/src/plugins/builtin/company-attention/headless.ts +19 -5
  230. package/src/plugins/builtin/company-attention/index.tsx +2 -0
  231. package/src/plugins/builtin/company-attention/model.ts +26 -7
  232. package/src/plugins/builtin/company-attention/pane.tsx +12 -9
  233. package/src/plugins/builtin/company-attention/series.ts +52 -0
  234. package/src/plugins/builtin/company-kpis/pane.tsx +4 -2
  235. package/src/plugins/builtin/composite-plugins.ts +19 -13
  236. package/src/plugins/builtin/cot/index.tsx +3 -1
  237. package/src/plugins/builtin/cot/model.ts +40 -11
  238. package/src/plugins/builtin/cot/series.ts +27 -0
  239. package/src/plugins/builtin/credit-boards/index.tsx +2 -0
  240. package/src/plugins/builtin/credit-boards/series.ts +66 -0
  241. package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
  242. package/src/plugins/builtin/credit-conditions/series.ts +26 -0
  243. package/src/plugins/builtin/credit-documents/headless.ts +31 -1
  244. package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
  245. package/src/plugins/builtin/crypto-board/headless.ts +2 -3
  246. package/src/plugins/builtin/crypto-board/index.tsx +2 -0
  247. package/src/plugins/builtin/crypto-board/model.ts +12 -0
  248. package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
  249. package/src/plugins/builtin/crypto-board/series.ts +48 -0
  250. package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
  251. package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
  252. package/src/plugins/builtin/debt-maturities/series.ts +45 -0
  253. package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
  254. package/src/plugins/builtin/dividend-yield/index.tsx +2 -0
  255. package/src/plugins/builtin/dividend-yield/series.ts +20 -0
  256. package/src/plugins/builtin/docs/blocks.tsx +181 -0
  257. package/src/plugins/builtin/docs/index.tsx +68 -0
  258. package/src/plugins/builtin/docs/pane.tsx +426 -0
  259. package/src/plugins/builtin/docs/targets.ts +72 -0
  260. package/src/plugins/builtin/docs/use-docs.ts +60 -0
  261. package/src/plugins/builtin/doe/index.tsx +2 -0
  262. package/src/plugins/builtin/doe/series.ts +62 -0
  263. package/src/plugins/builtin/earnings/client.ts +3 -2
  264. package/src/plugins/builtin/earnings/format.ts +1 -5
  265. package/src/plugins/builtin/earnings/headless.ts +58 -6
  266. package/src/plugins/builtin/earnings/history-model.ts +15 -1
  267. package/src/plugins/builtin/earnings/history-table.ts +13 -2
  268. package/src/plugins/builtin/earnings/history.tsx +7 -3
  269. package/src/plugins/builtin/econ/fred-catalog.ts +189 -0
  270. package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
  271. package/src/plugins/builtin/econ/index.tsx +2 -0
  272. package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
  273. package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
  274. package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
  276. package/src/plugins/builtin/exposure/holdings.ts +1 -1
  277. package/src/plugins/builtin/exposure/pane.tsx +3 -1
  278. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  279. package/src/plugins/builtin/fear-greed/series.ts +52 -0
  280. package/src/plugins/builtin/futures/index.tsx +43 -3
  281. package/src/plugins/builtin/futures/model.ts +1 -1
  282. package/src/plugins/builtin/futures/returns.ts +1 -1
  283. package/src/plugins/builtin/futures/series.ts +13 -0
  284. package/src/plugins/builtin/futures/table.tsx +1 -1
  285. package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
  286. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  287. package/src/plugins/builtin/futures-curve/headless.ts +70 -9
  288. package/src/plugins/builtin/futures-curve/index.tsx +1 -1
  289. package/src/plugins/builtin/futures-curve/model.ts +66 -11
  290. package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
  291. package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
  292. package/src/plugins/builtin/gpu/board.tsx +13 -10
  293. package/src/plugins/builtin/gpu/changes.tsx +4 -4
  294. package/src/plugins/builtin/gpu/client.ts +19 -1
  295. package/src/plugins/builtin/gpu/equities.tsx +14 -10
  296. package/src/plugins/builtin/gpu/headless.ts +56 -25
  297. package/src/plugins/builtin/gpu/history.tsx +3 -3
  298. package/src/plugins/builtin/gpu/index.tsx +2 -0
  299. package/src/plugins/builtin/gpu/model.ts +101 -16
  300. package/src/plugins/builtin/gpu/pane.tsx +28 -12
  301. package/src/plugins/builtin/gpu/series.ts +38 -0
  302. package/src/plugins/builtin/help/function-card.tsx +25 -6
  303. package/src/plugins/builtin/help/index.tsx +17 -8
  304. package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
  305. package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
  306. package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
  307. package/src/plugins/builtin/holders/client.ts +22 -3
  308. package/src/plugins/builtin/holders/format.ts +18 -3
  309. package/src/plugins/builtin/holders/headless.ts +36 -133
  310. package/src/plugins/builtin/holders/pane.tsx +11 -4
  311. package/src/plugins/builtin/holders/report-header.ts +40 -5
  312. package/src/plugins/builtin/holders/report-rows.ts +240 -0
  313. package/src/plugins/builtin/insider/client.ts +35 -20
  314. package/src/plugins/builtin/insider/headless.ts +41 -11
  315. package/src/plugins/builtin/insider/index.tsx +26 -9
  316. package/src/plugins/builtin/insider/insider-data.ts +10 -0
  317. package/src/plugins/builtin/insider/issuer.ts +49 -0
  318. package/src/plugins/builtin/insider/model.ts +3 -0
  319. package/src/plugins/builtin/iv-history/client.ts +14 -4
  320. package/src/plugins/builtin/iv-history/headless.ts +7 -4
  321. package/src/plugins/builtin/iv-history/index.tsx +2 -0
  322. package/src/plugins/builtin/iv-history/model.ts +111 -7
  323. package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
  324. package/src/plugins/builtin/iv-history/series.ts +29 -0
  325. package/src/plugins/builtin/jobs/headless.ts +28 -3
  326. package/src/plugins/builtin/jobs/index.tsx +3 -1
  327. package/src/plugins/builtin/jobs/series.ts +19 -0
  328. package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
  329. package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
  330. package/src/plugins/builtin/macro-day/client.ts +44 -3
  331. package/src/plugins/builtin/macro-day/headless.ts +13 -10
  332. package/src/plugins/builtin/macro-day/model.ts +56 -10
  333. package/src/plugins/builtin/macro-day/pane.tsx +30 -20
  334. package/src/plugins/builtin/market-heatmap/model.ts +19 -11
  335. package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
  336. package/src/plugins/builtin/market-movers/client.ts +7 -5
  337. package/src/plugins/builtin/market-movers/index.tsx +3 -2
  338. package/src/plugins/builtin/market-movers/model.ts +30 -0
  339. package/src/plugins/builtin/market-movers/screener.ts +31 -0
  340. package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
  341. package/src/plugins/builtin/market-valuation/index.tsx +3 -3
  342. package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
  343. package/src/plugins/builtin/members/headless.ts +7 -2
  344. package/src/plugins/builtin/members/model.ts +31 -3
  345. package/src/plugins/builtin/members/pane.tsx +3 -6
  346. package/src/plugins/builtin/mna/headless.ts +15 -3
  347. package/src/plugins/builtin/money-markets/index.tsx +2 -0
  348. package/src/plugins/builtin/money-markets/series.ts +41 -0
  349. package/src/plugins/builtin/notes/cli.ts +12 -7
  350. package/src/plugins/builtin/options/view.tsx +1 -1
  351. package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
  352. package/src/plugins/builtin/options-positioning/client.ts +7 -3
  353. package/src/plugins/builtin/options-positioning/headless.ts +1 -0
  354. package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
  355. package/src/plugins/builtin/options-scenario/client.ts +2 -1
  356. package/src/plugins/builtin/options-scenario/headless.ts +1 -0
  357. package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
  358. package/src/plugins/builtin/perps/client.ts +40 -6
  359. package/src/plugins/builtin/perps/equity-row.tsx +33 -11
  360. package/src/plugins/builtin/perps/freshness.ts +66 -0
  361. package/src/plugins/builtin/perps/headless.ts +45 -30
  362. package/src/plugins/builtin/perps/index.tsx +18 -1
  363. package/src/plugins/builtin/perps/model.ts +20 -5
  364. package/src/plugins/builtin/perps/pane.tsx +76 -20
  365. package/src/plugins/builtin/perps/series.ts +76 -0
  366. package/src/plugins/builtin/plugin-marketplace/pane.tsx +67 -61
  367. package/src/plugins/builtin/plugin-module.ts +3 -0
  368. package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
  369. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
  370. package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
  371. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +331 -65
  372. package/src/plugins/builtin/portfolio-list/cli/render.ts +200 -31
  373. package/src/plugins/builtin/portfolio-list/cli/shared.ts +24 -0
  374. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
  375. package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
  376. package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
  377. package/src/plugins/builtin/portfolio-list/headless.ts +34 -4
  378. package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
  379. package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
  380. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
  381. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
  382. package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
  383. package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
  384. package/src/plugins/builtin/power/client.ts +16 -0
  385. package/src/plugins/builtin/power/evidence.ts +6 -2
  386. package/src/plugins/builtin/power/headless.ts +71 -7
  387. package/src/plugins/builtin/power/index.tsx +4 -1
  388. package/src/plugins/builtin/power/model.ts +72 -1
  389. package/src/plugins/builtin/power/pane.tsx +32 -9
  390. package/src/plugins/builtin/rate-path/headless.ts +11 -3
  391. package/src/plugins/builtin/realized-vol/headless.ts +5 -0
  392. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
  393. package/src/plugins/builtin/research/event-model.ts +68 -24
  394. package/src/plugins/builtin/research/events-headless.ts +2 -1
  395. package/src/plugins/builtin/research/headless.ts +2 -1
  396. package/src/plugins/builtin/seasonality/client.ts +15 -7
  397. package/src/plugins/builtin/seasonality/headless.ts +36 -12
  398. package/src/plugins/builtin/seasonality/index.tsx +2 -2
  399. package/src/plugins/builtin/seasonality/model.ts +119 -0
  400. package/src/plugins/builtin/seasonality/pane.tsx +108 -37
  401. package/src/plugins/builtin/series-inventory.ts +91 -0
  402. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
  403. package/src/plugins/builtin/shared/command-tab.ts +30 -12
  404. package/src/plugins/builtin/shared/earnings-report.ts +55 -0
  405. package/src/plugins/builtin/shared/fred-points.ts +20 -0
  406. package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
  407. package/src/plugins/builtin/shared/open-description.ts +29 -0
  408. package/src/plugins/builtin/shared/report-freshness.ts +9 -0
  409. package/src/plugins/builtin/shared/series-warning.ts +5 -0
  410. package/src/plugins/builtin/short-interest/headless.ts +1 -0
  411. package/src/plugins/builtin/short-interest/index.tsx +2 -0
  412. package/src/plugins/builtin/short-interest/series.ts +36 -0
  413. package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
  414. package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
  415. package/src/plugins/builtin/short-volume/headless.ts +1 -0
  416. package/src/plugins/builtin/short-volume/index.tsx +2 -0
  417. package/src/plugins/builtin/short-volume/series.ts +23 -0
  418. package/src/plugins/builtin/social-mentions/index.tsx +2 -0
  419. package/src/plugins/builtin/social-mentions/series.ts +23 -0
  420. package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
  421. package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
  422. package/src/plugins/builtin/thirteenf/api.ts +1 -0
  423. package/src/plugins/builtin/thirteenf/data.ts +6 -4
  424. package/src/plugins/builtin/thirteenf/headless.ts +217 -36
  425. package/src/plugins/builtin/thirteenf/model.ts +39 -1
  426. package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
  427. package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
  428. package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
  429. package/src/plugins/builtin/thirteenf/types.ts +2 -0
  430. package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
  431. package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
  432. package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
  433. package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
  434. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
  435. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
  436. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
  437. package/src/plugins/builtin/time-sales/headless.ts +1 -0
  438. package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
  439. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  440. package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
  441. package/src/plugins/builtin/vol-surface/headless.ts +1 -0
  442. package/src/plugins/builtin/volatility/index.tsx +2 -0
  443. package/src/plugins/builtin/volatility/series.ts +84 -0
  444. package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
  445. package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
  446. package/src/plugins/builtin/world-venue-map/index.ts +2 -1
  447. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
  448. package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
  449. package/src/plugins/builtin/yield-curve/chart.ts +2 -2
  450. package/src/plugins/builtin/yield-curve/curves.ts +21 -8
  451. package/src/plugins/builtin/yield-curve/headless.ts +71 -18
  452. package/src/plugins/builtin/yield-curve/index.tsx +3 -1
  453. package/src/plugins/builtin/yield-curve/series.ts +86 -0
  454. package/src/plugins/catalog-browser.ts +2 -1
  455. package/src/plugins/registry/index.ts +89 -4
  456. package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
  457. package/src/portfolio/position-value.ts +40 -0
  458. package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
  459. package/src/public/time-series.ts +17 -0
  460. package/src/react/auto-refresh.ts +13 -3
  461. package/src/remote/app-host.tsx +20 -4
  462. package/src/remote/confirm-change.tsx +85 -0
  463. package/src/remote/controller-utils.ts +8 -1
  464. package/src/remote/controller.ts +123 -6
  465. package/src/remote/pane-screenshot.ts +47 -0
  466. package/src/remote/schema.ts +62 -0
  467. package/src/remote/types.ts +30 -0
  468. package/src/remote/watchlist.ts +306 -0
  469. package/src/renderers/browser/main.tsx +3 -2
  470. package/src/renderers/cloudflare/worker.ts +1 -1
  471. package/src/renderers/dom/data-table/frozen-column.ts +3 -1
  472. package/src/renderers/dom/data-table/index.tsx +33 -6
  473. package/src/renderers/dom/data-table/row.tsx +27 -1
  474. package/src/renderers/dom/desktop/control-styles.ts +63 -1
  475. package/src/renderers/dom/desktop/controls.tsx +30 -23
  476. package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
  477. package/src/renderers/dom/desktop/icons.tsx +7 -0
  478. package/src/renderers/dom/dom-ui-host.tsx +3 -0
  479. package/src/renderers/dom/host/box.tsx +3 -0
  480. package/src/renderers/dom/host/chart-surface.tsx +6 -2
  481. package/src/renderers/dom/host/live-frame.ts +6 -2
  482. package/src/renderers/dom/host/overflow-fade.ts +14 -1
  483. package/src/renderers/dom/host/size-observer.ts +26 -0
  484. package/src/renderers/dom/host/surface-3d.tsx +7 -2
  485. package/src/renderers/dom/host/text.tsx +1 -1
  486. package/src/renderers/dom/styles.css +61 -0
  487. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +17 -2
  488. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +19 -11
  489. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
  490. package/src/renderers/electrobun/view/main.tsx +2 -0
  491. package/src/series/capability.ts +75 -0
  492. package/src/series/csv.ts +160 -0
  493. package/src/series/files.ts +107 -0
  494. package/src/series/registry.ts +198 -0
  495. package/src/series/resolve.ts +107 -0
  496. package/src/series/standalone.ts +48 -0
  497. package/src/series/types.ts +145 -0
  498. package/src/series/validate.ts +171 -0
  499. package/src/sources/gloomberb-cloud/index.ts +7 -7
  500. package/src/sources/gloomberb-cloud/normalizers.ts +1 -0
  501. package/src/sources/history-result.ts +1 -0
  502. package/src/sources/listing-symbols.ts +4 -1
  503. package/src/sources/provider-router/batches.ts +4 -1
  504. package/src/sources/provider-router/cache.ts +4 -2
  505. package/src/sources/provider-router/cached-routes.ts +1 -1
  506. package/src/sources/provider-router/financial-routes.ts +1 -0
  507. package/src/sources/provider-router/financials.ts +13 -0
  508. package/src/sources/provider-router/history.ts +43 -25
  509. package/src/sources/provider-router/search.ts +1 -0
  510. package/src/sync/core-contributors.ts +1 -0
  511. package/src/tickers/company-name.ts +75 -0
  512. package/src/tickers/instrument-kind.ts +25 -0
  513. package/src/tickers/open-target.ts +3 -2
  514. package/src/tickers/search/index.ts +45 -7
  515. package/src/tickers/search/ranking.ts +33 -17
  516. package/src/tickers/search/types.ts +7 -1
  517. package/src/tickers/us-listing.ts +34 -0
  518. package/src/time-series/hooks.ts +9 -0
  519. package/src/time-series/resolve.ts +95 -16
  520. package/src/time-series/types.ts +4 -2
  521. package/src/time-series/use-chart-resolution.ts +32 -0
  522. package/src/types/config.ts +6 -0
  523. package/src/types/data-provider.ts +5 -0
  524. package/src/types/financials.ts +52 -1
  525. package/src/types/headless.ts +61 -0
  526. package/src/types/plugin.ts +19 -0
  527. package/src/types/price-history.ts +7 -0
  528. package/src/types/ticker.ts +14 -0
  529. package/src/ui/host.tsx +18 -0
  530. package/src/ui/live-resize.ts +81 -0
  531. package/src/ui/live-stretch.ts +121 -0
  532. package/src/ui/measured-cells.ts +58 -0
  533. package/src/ui/pane-fill.ts +11 -0
  534. package/src/utils/basis-points.ts +17 -0
  535. package/src/utils/cli-output.ts +7 -1
  536. package/src/utils/concurrency.ts +25 -0
  537. package/src/utils/crypto-pair.ts +20 -0
  538. package/src/utils/currency-pair.ts +19 -0
  539. package/src/utils/decimal-comma.ts +19 -0
  540. package/src/utils/dom-screenshot-css.ts +265 -0
  541. package/src/utils/dom-screenshot-rasters.ts +276 -0
  542. package/src/utils/dom-screenshot.ts +437 -52
  543. package/src/utils/exchange-rate-snapshot.ts +6 -2
  544. package/src/utils/exchanges.ts +107 -1
  545. package/src/utils/format.ts +16 -3
  546. package/src/utils/fundamentals.ts +66 -0
  547. package/src/utils/futures-generic.ts +6 -12
  548. package/src/utils/fx-market-hours.ts +15 -5
  549. package/src/utils/price-history.ts +99 -42
  550. package/src/utils/sec-eps-basis.ts +24 -11
  551. package/src/utils/sec.ts +61 -7
  552. package/src/utils/utc-time.ts +51 -4
  553. package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
  554. /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
@@ -3,6 +3,7 @@ import { scalarPoint, staticSeries } from "../../../components/chart/static/seri
3
3
  import type { HiringBoard, HiringPayload, HiringSummary } from "../../../api-client/hiring";
4
4
  import type { AppAttentionPayload, AppCompany } from "../../../api-client/app-attention";
5
5
  import { humanLabel } from "../shared/research-cells";
6
+ import { formatNumber } from "../../../utils/format";
6
7
 
7
8
  export type AttentionKind = "hiring" | "apps";
8
9
  export type AttentionTab = "table" | "chart" | "mix" | "peers" | "evidence";
@@ -28,16 +29,33 @@ export interface AttentionModel {
28
29
  notices: string[];
29
30
  figures: StatItem[];
30
31
  sections: Record<AttentionTab, AttentionSection>;
31
- chart: { label: string; unit: string; points: Array<{ date: string; value: number | null }> };
32
+ /** `since`: the first capture, so a chart still collecting says when it began. */
33
+ chart: { label: string; unit: string; points: Array<{ date: string; value: number | null }>; since?: string | null };
32
34
  }
33
35
  export const count = (value: number | null | undefined) => value == null ? "--" : value.toLocaleString("en-US", { maximumFractionDigits: 0 });
36
+ /** Decimals per column: percentages and scores to one place, ratings and z-scores to two, counts and remote shares whole. */
37
+ const DECIMALS: Record<string, number> = { score: 1, remote: 0, share: 1, confidence: 0, velocity: 2, rating: 2, drift: 2, z: 2, change: 1 };
38
+ export const SIGNED_COLUMNS = new Set(["net", "change", "velocity", "drift", "growth"]);
39
+ /** A column's number as the pane and the text report print it. */
40
+ export const attentionNumber = (columnId: string, value: number) =>
41
+ `${SIGNED_COLUMNS.has(columnId) && value > 0 ? "+" : ""}${formatNumber(value, DECIMALS[columnId] ?? 0)}`;
42
+ /**
43
+ * A remote share with the roles it is measured on. Most postings never state a
44
+ * work mode, so the share counts only those that do: 100% of 5 is not 100% of
45
+ * a company's 300 roles. A capture that did not count them says "of known".
46
+ */
47
+ export function remoteText(percentRemote: number | null | undefined, known: number | null | undefined): string | null {
48
+ if (percentRemote == null) return null;
49
+ return `${formatNumber(percentRemote, 0)}% of ${known == null ? "" : `${formatNumber(known, 0)} `}known`;
50
+ }
34
51
  const signed = (value: number | null | undefined, decimals = 0) => value == null ? "--" : `${value > 0 ? "+" : ""}${value.toLocaleString("en-US", { maximumFractionDigits: decimals })}`;
35
52
  const percent = (value: number | null | undefined) => value == null ? "--" : `${(value * 100).toFixed(1)}%`;
36
53
  const column = (id: string, label: string, width: number, numeric = false, flex = false): DataTableColumn => ({ id, label, width, align: numeric ? "right" : "left", ...(flex ? { flexGrow: 1 } : {}) });
37
54
  const roleFamily = (value: string | null) => value === "data_ai" || value === "data ai" ? "Data / AI" : value ? humanLabel(value) : null;
38
- const COMPANY_COLUMNS = [column("symbol", "Ticker", 12), column("name", "Company", 24, false, true), column("open", "Open roles", 12, true), column("net", "Net / week", 12, true), column("remote", "Remote %", 10, true), column("z", "Z-score", 10, true), column("signal", "Signal", 12), column("date", "As of", 12)];
39
- const HISTORY_COLUMNS = [column("date", "Week", 12), column("open", "Open roles", 12, true), column("added", "Added", 9, true), column("removed", "Removed", 9, true), column("net", "Net", 9, true), column("change", "Change %", 11, true), column("remote", "Remote %", 10, true), column("coverage", "Capture", 13, false, true)];
40
- const companyRow = (row: HiringSummary): AttentionRow => ({ id: row.symbol, symbol: row.symbol, values: { symbol: row.symbol, name: row.name, open: row.latest?.openCount ?? null, net: row.latest?.netChange ?? null, remote: row.latest?.remoteShare == null ? null : row.latest.remoteShare * 100, z: row.zScore, signal: humanLabel(row.signal ?? row.status), date: row.latest?.observedAt.slice(0, 10) ?? null } });
55
+ const COMPANY_COLUMNS = [column("symbol", "Ticker", 12), column("name", "Company", 24, false, true), column("open", "Open roles", 12, true), column("net", "Net / week", 12, true), column("remote", "Remote %", 17, true), column("z", "Z-score", 10, true), column("signal", "Signal", 12), column("date", "As of", 12)];
56
+ const HISTORY_COLUMNS = [column("date", "Week", 12), column("open", "Open roles", 12, true), column("added", "Added", 9, true), column("removed", "Removed", 9, true), column("net", "Net", 9, true), column("change", "Change %", 11, true), column("remote", "Remote %", 17, true), column("coverage", "Capture", 13, false, true)];
57
+ /** `remoteKnown` rides along for the remote cell; it is not a column of its own. */
58
+ const companyRow = (row: HiringSummary): AttentionRow => ({ id: row.symbol, symbol: row.symbol, values: { symbol: row.symbol, name: row.name, open: row.latest?.openCount ?? null, net: row.latest?.netChange ?? null, remote: row.latest?.remoteShare == null ? null : row.latest.remoteShare * 100, remoteKnown: row.latest?.remoteKnown ?? null, z: row.zScore, signal: humanLabel(row.signal ?? row.status), date: row.latest?.observedAt.slice(0, 10) ?? null } });
41
59
 
42
60
  export function hiringModel(data: HiringBoard | HiringPayload, mix = "functions"): AttentionModel {
43
61
  const detail = "series" in data ? data : null;
@@ -57,18 +75,19 @@ export function hiringModel(data: HiringBoard | HiringPayload, mix = "functions"
57
75
  figures: latest ? [
58
76
  { label: "Open roles", value: count(latest.openCount) },
59
77
  ...(latest.netChange != null ? [{ label: "Net / week", value: signed(latest.netChange), tone: latest.netChange === 0 ? "muted" as const : latest.netChange > 0 ? "positive" as const : "negative" as const }] : []),
60
- ...(latest.remoteShare != null ? [{ label: "Remote", value: percent(latest.remoteShare), detail: "known work modes" }] : []),
78
+ ...(latest.remoteShare != null ? [{ label: "Remote", value: `${formatNumber(latest.remoteShare * 100, 0)}%`,
79
+ detail: latest.remoteKnown == null ? "of known work modes" : `of ${count(latest.remoteKnown)} known` }] : []),
61
80
  ...(detail?.zScore != null ? [{ label: "Z-score", value: detail.zScore.toFixed(2), detail: detail.signal ? `hiring ${detail.signal}` : undefined,
62
81
  tone: detail.signal === "surge" ? "positive" as const : detail.signal === "freeze" ? "negative" as const : undefined }] : []),
63
82
  ] : [],
64
83
  sections: {
65
84
  table: { columns: COMPANY_COLUMNS, rows: companies.map(companyRow), empty: "Hiring observations are not available yet." },
66
- chart: { columns: HISTORY_COLUMNS, rows: [...history].reverse().map((row): AttentionRow => ({ id: row.week, date: row.week, values: { date: row.week, open: row.openCount, added: row.added, removed: row.removed, net: row.netChange, change: row.changePct, remote: row.remoteShare == null ? null : row.remoteShare * 100, coverage: row.completeness }, details: [{ label: "Observed", value: row.observedAt }, { label: "Confidence", value: percent(row.confidence) }, { label: "Capture", value: row.kind }, { label: "Evergreen roles", value: count(row.evergreenCount) }] })), empty: data.preview ? "Full hiring history requires Pro." : "History is accumulating from the first capture." },
85
+ chart: { columns: HISTORY_COLUMNS, rows: [...history].reverse().map((row): AttentionRow => ({ id: row.week, date: row.week, values: { date: row.week, open: row.openCount, added: row.added, removed: row.removed, net: row.netChange, change: row.changePct, remote: row.remoteShare == null ? null : row.remoteShare * 100, remoteKnown: row.remoteKnown, coverage: row.completeness }, details: [{ label: "Observed", value: row.observedAt }, { label: "Confidence", value: percent(row.confidence) }, { label: "Capture", value: row.kind }, { label: "Evergreen roles", value: count(row.evergreenCount) }] })), empty: data.preview ? "Full hiring history requires Pro." : "History is accumulating from the first capture." },
67
86
  mix: { columns: [column("label", mixKey === "signals" ? "Signal" : "Group", 28, false, true), column("count", mixKey === "signals" ? "Value" : "Roles", 12, true), column("share", mixKey === "signals" ? "Confidence %" : "Share %", mixKey === "signals" ? 14 : 30, true), ...(mixKey === "signals" ? [column("date", "Observed", 12)] : [])], rows, empty: "No classified observations for this group yet." },
68
87
  peers: { columns: COMPANY_COLUMNS, rows: (detail?.peers ?? []).map(companyRow), empty: "No comparable mapped peers yet." },
69
88
  evidence: { columns: [column("title", "Role", 32, false, true), column("location", "Location", 24), column("function", "Function", 18), column("seniority", "Seniority", 13), column("date", "Observed", 12), column("confidence", "Confidence %", 13, true)], rows: (detail?.evidence ?? []).map((row): AttentionRow => ({ id: `${row.snapshotId}:${row.id}`, url: row.url ?? row.sourceUrl, values: { title: row.title, location: row.location, function: roleFamily(row.jobFunction), seniority: row.seniority ? humanLabel(row.seniority) : null, date: row.observedAt.slice(0, 10), confidence: row.confidence * 100 }, details: [{ label: "Posting title", value: row.title }, { label: "Location", value: row.location ?? "--" }, { label: "Country", value: row.country ?? "Unknown" }, { label: "Function", value: row.jobFunction ?? "Unclassified" }, { label: "Seniority", value: row.seniority ?? "Unclassified" }, { label: "Remote", value: row.remote === null ? "Unknown" : row.remote ? "Yes" : "No" }, { label: "Observed", value: row.observedAt }, { label: "Revision", value: String(row.revision) }, { label: "Confidence", value: percent(row.confidence) }, { label: "Reporting ticker", value: row.sourceTicker }] })), empty: "No primary posting evidence is available yet." },
70
89
  },
71
- chart: { label: "Open roles", unit: "roles", points: history.map((point) => ({ date: point.week, value: point.openCount })) },
90
+ chart: { label: "Open roles", unit: "roles", points: history.map((point) => ({ date: point.week, value: point.openCount })), since: detail?.coverage.firstObservedAt ?? null },
72
91
  };
73
92
  if (detail) {
74
93
  model.sections.table = { ...model.sections.evidence, columns: model.sections.evidence.columns.filter((entry) => entry.id !== "confidence"), empty: "No current role evidence has been captured yet." };
@@ -12,20 +12,18 @@ import type { PaneProps } from "../../../types/plugin";
12
12
  import { Box, ScrollBox, Text, useRendererHost, useUiCapabilities, type ScrollBoxRenderable } from "../../../ui";
13
13
  import { SignInWall } from "../cloud/auth-actions";
14
14
  import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
15
+ import { useOpenDescription } from "../shared/open-description";
15
16
  import { Blurred, LockedOverlay, UpgradeLabel } from "../shared/locked-rows";
16
17
  import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
17
18
  import { humanLabel, listingCell, missingCell, shareCell, signedColor, toneColor, withoutQuietColumns, type StateTone } from "../shared/research-cells";
18
19
  import { AppRankView } from "./app-rank";
19
20
  import { useAttentionEvidence } from "./evidence";
20
21
  import { cachedAttention, fetchAttention, loadAttention, validateAttention, type AttentionPayload } from "./client";
21
- import { appsModel, attentionSeries, count, hiringModel, sortAttentionRows, type AttentionKind, type AttentionRow, type AttentionTab } from "./model";
22
+ import { appsModel, attentionNumber, attentionSeries, count, hiringModel, remoteText, SIGNED_COLUMNS, sortAttentionRows, type AttentionKind, type AttentionRow, type AttentionTab } from "./model";
22
23
 
23
24
  type Size = Pick<PaneProps, "width" | "height" | "focused">;
24
25
  const rowId = (row: AttentionRow) => row.id;
25
26
  const rowDate = (row: AttentionRow) => row.date ? new Date(row.date) : null;
26
- /** Decimals per column: percentages and scores to one place, ratings and z-scores to two, counts whole. */
27
- const DECIMALS: Record<string, number> = { score: 1, remote: 1, share: 1, confidence: 0, velocity: 2, rating: 2, drift: 2, z: 2, change: 1 };
28
- const SIGNED_COLUMNS = new Set(["net", "change", "velocity", "drift", "growth"]);
29
27
  const SIGNAL_TONES: Record<string, StateTone> = { Surge: "positive", Freeze: "negative", Stale: "warning", Collecting: "muted", Uncovered: "muted", Ok: "text" };
30
28
  /** Columns left out while every row reads the same default: a first capture has no weekly change yet. */
31
29
  const QUIET_COLUMNS: Record<string, readonly string[]> = { net: [""], z: [""], change: [""], velocity: [""], drift: [""], growth: [""], added: [""], removed: [""], coverage: ["complete"], spread: [""] };
@@ -62,6 +60,7 @@ function AttentionView({ kind, symbol, width, height, focused }: Size & { kind:
62
60
  const desktop = !!useUiCapabilities().nativePaneChrome;
63
61
  const host = useRendererHost();
64
62
  const { createPaneFromTemplate } = usePluginAppActions();
63
+ const openDescription = useOpenDescription();
65
64
  const session = useResearchCloudSession();
66
65
  const access = usePlanAccess();
67
66
  const accessKey = `${session.requestKey}:${access.hasProAccess ? "full" : "preview"}`;
@@ -131,7 +130,7 @@ function AttentionView({ kind, symbol, width, height, focused }: Size & { kind:
131
130
  ...(selected && !evidence && selected.details ? [{ id: "evidence", key: "e", label: "vidence", onPress: () => setOpenEvidence(selected.id) }] : []),
132
131
  ...(selected?.app && selectedSymbol && !symbol ? [{ id: "company", key: "c", label: "ompany", onPress: () => setOpenCompany(selectedSymbol) }] : []),
133
132
  ...(selectedSymbol ? [
134
- { id: "description", key: "d", label: "es", onPress: () => createPaneFromTemplate("new-ticker-detail-pane", { symbol: selectedSymbol }) },
133
+ { id: "description", key: "d", label: "es", onPress: () => openDescription(selectedSymbol) },
135
134
  { id: "financials", key: "f", label: "a", onPress: () => createPaneFromTemplate("financial-analysis-pane", { symbol: selectedSymbol }) },
136
135
  { id: "chart", key: "g", label: "raph", onPress: () => createPaneFromTemplate("chart-composer-pane", { arg: selectedSymbol }) },
137
136
  { id: "related", key: "a", label: kind === "hiring" ? "pps" : " hiring", title: kind === "hiring" ? "App attention" : "Hiring momentum", onPress: () => createPaneFromTemplate(kind === "hiring" ? "apps-pane" : "hiring-pane", { symbol: selectedSymbol }) },
@@ -139,7 +138,7 @@ function AttentionView({ kind, symbol, width, height, focused }: Size & { kind:
139
138
  ...(model?.preview ? [{ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: upgrade }] : []),
140
139
  ];
141
140
  usePaneStatusFooter({ registrationId: `${kind}:status`, enabled: !openCompany && !app, loading: resource.loading || paged.loadingMore, error: data ? resource.error ?? paged.moreError?.message ?? paged.error?.message : null, stale: resource.data?.stale,
142
- info: [...(model?.asOf ? [{ id: "observed", parts: [{ text: `as of ${model.asOf.slice(0, 10)}`, tone: "muted" as const }] }] : []), ...(model?.preview ? [{ id: "preview", parts: [{ text: "Pro preview", tone: "warning" as const }] }] : [])], hints });
141
+ info: [...(model?.asOf ? [{ id: "observed", asOf: true, parts: [{ text: `as of ${model.asOf.slice(0, 10)}`, tone: "muted" as const }] }] : []), ...(model?.preview ? [{ id: "preview", parts: [{ text: "Pro preview", tone: "warning" as const }] }] : [])], hints });
143
142
  usePaneNoticeFooter({ registrationId: `${kind}:notices`, enabled: !openCompany && !app, focused, notices: [...(model?.notices ?? []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] });
144
143
  if (!data && isCloudSessionRequired(resource.error)) return <SignInWall placement={kind === "hiring" ? "hiring-momentum-signin" : "app-attention-signin"} action={kind === "hiring" ? "view hiring momentum" : "view app attention"} needsVerification={session.needsVerification} />;
145
144
  const countryOptions = data && kind === "apps" ? (data as AppAttentionPayload).coverage.configuredCountries : [];
@@ -156,7 +155,8 @@ function AttentionView({ kind, symbol, width, height, focused }: Size & { kind:
156
155
  detailContent={openCompany ? <PaneFooterScope active><AttentionView key={openCompany} kind={kind} symbol={openCompany} width={width} height={bodyHeight - 1} focused={focused} /></PaneFooterScope> : app ? <AppRankView focus={app} accessKey={accessKey} width={width} height={bodyHeight - 1} focused={focused} /> : evidence ? <Evidence row={evidence} width={width} height={bodyHeight - 1} focused={focused} /> : null}
157
156
  rootContent={<DataTableView columns={columns} items={rows} rootWidth={width} rootHeight={bodyHeight} focused={focused && !detailOpen} scrollRef={scrollRef} onBodyScrollActivity={scrollMore}
158
157
  rootBefore={<ChartTableHeader width={width} height={bodyHeight} tableRows={rows.length} tableColumns={columns} query={query} figures={model.figures}
159
- chart={tab === "chart" ? { series, ...chartSelection, formatValue: (value) => kind === "hiring" ? count(value) : value.toFixed(1), empty: series.length ? undefined : model.preview ? "Full history requires Pro." : "Collecting history", remoteKind: `${kind}-history` } : null} />}
158
+ chart={tab === "chart" ? { series, ...chartSelection, formatValue: (value) => kind === "hiring" ? count(value) : value.toFixed(1), empty: series.length ? undefined : model.preview ? "Full history requires Pro."
159
+ : model.chart.since ? `Collecting history since ${model.chart.since.slice(0, 10)}` : "Collecting history", remoteKind: `${kind}-history` } : null} />}
160
160
  selection={{ kind: "id", selectedId: selected?.id ?? null, getId: rowId, onChange: setSelected }} getItemKey={rowId}
161
161
  sortColumnId={sort.column} sortDirection={sort.direction} onHeaderClick={(column) => setSort((old) => ({ column, direction: old.column === column && old.direction === "desc" ? "asc" : "desc" }))}
162
162
  onActivate={(row) => row.id.startsWith("locked:") ? void upgrade() : row.app ? setOpenApp(row.id) : row.symbol && row.symbol !== symbol && (tab === "peers" || !symbol) ? setOpenCompany(row.symbol) : row.details ? setOpenEvidence(row.id) : row.url ? void host.openExternal(row.url) : undefined}
@@ -170,8 +170,11 @@ function AttentionView({ kind, symbol, width, height, focused }: Size & { kind:
170
170
  if (column.id === "symbol" && typeof value === "string") return listingCell(value, colors, state.selected);
171
171
  if (column.id === "signal" && typeof value === "string") return { text: value, color: toneColor(SIGNAL_TONES[value] ?? "text", colors), keepColorWhenSelected: value === "Surge" || value === "Freeze" };
172
172
  const numeric = typeof value === "number";
173
- const decimals = DECIMALS[column.id] ?? 0;
174
- const text = numeric ? `${SIGNED_COLUMNS.has(column.id) && value > 0 ? "+" : ""}${value.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals })}` : LABEL_COLUMNS.has(column.id) ? humanLabel(value) : column.id === "title" ? value.trim() : value;
173
+ if (column.id === "remote" && numeric) {
174
+ const known = row.values.remoteKnown;
175
+ return { text: remoteText(value, typeof known === "number" ? known : null) ?? "", value, color: colors.text };
176
+ }
177
+ const text = numeric ? attentionNumber(column.id, value) : LABEL_COLUMNS.has(column.id) ? humanLabel(value) : column.id === "title" ? value.trim() : value;
175
178
  if (column.id === "share" && numeric && tab === "mix") return { ...shareCell(text, maxShare ? value / maxShare : null, column.width, colors, state.selected), value };
176
179
  return { text, value, color: numeric && SIGNED_COLUMNS.has(column.id) ? signedColor(value, colors)
177
180
  : column.id === "name" || column.id === "title" ? colors.textBright : column.id === "date" || column.id === "observed" || column.id === "confidence" ? colors.textDim : colors.text };
@@ -0,0 +1,52 @@
1
+ import type { AppAttentionPayload } from "../../../api-client/app-attention";
2
+ import type { HiringPayload } from "../../../api-client/hiring";
3
+ import type { SeriesDefinition } from "../../../series/types";
4
+ import { fetchAttention } from "./client";
5
+
6
+ const DAY_MS = 86_400_000;
7
+
8
+ /** The app history window a chart asks for, in the days the server takes (7 to 730). */
9
+ function appHistoryDays(start: Date | undefined): number {
10
+ if (!start) return 730;
11
+ return Math.min(730, Math.max(7, Math.ceil((Date.now() - start.getTime()) / DAY_MS)));
12
+ }
13
+
14
+ /**
15
+ * The company signals HIRE and APPS chart: observed open roles each week and
16
+ * the app attention score each day. Pro readings: the free preview carries no
17
+ * history, so they fail with the pane's own words.
18
+ */
19
+ export const COMPANY_ATTENTION_SERIES: readonly SeriesDefinition[] = [
20
+ {
21
+ id: "hiring:open-roles",
22
+ name: "Observed open roles",
23
+ unit: "jobs",
24
+ frequency: "weekly",
25
+ group: "Alternative data",
26
+ tags: ["hiring", "hiring momentum", "roles", "requisitions", "careers"],
27
+ description: "Requisitions observed on public career boards each week, subsidiaries rolled up through evidenced ownership, as HIRE charts them. Needs Pro.",
28
+ input: "ticker",
29
+ load: async ({ ticker, signal }) => {
30
+ const data = await fetchAttention("hiring", ticker!, {}, undefined, signal) as HiringPayload;
31
+ if (data.preview) throw new Error("Full hiring history requires Pro.");
32
+ if (!data.series.length) throw new Error("History is accumulating from the first capture.");
33
+ return data.series.map((point) => ({ date: point.week, value: point.openCount }));
34
+ },
35
+ },
36
+ {
37
+ id: "app-attention:score",
38
+ name: "App attention score",
39
+ unit: "score",
40
+ frequency: "daily",
41
+ group: "Alternative data",
42
+ tags: ["apps", "app attention", "mobile", "app ranks", "ratings"],
43
+ description: "A 0 to 100 summary of the public chart ranks of the company's apps across countries, as APPS charts it. Needs Pro.",
44
+ input: "ticker",
45
+ load: async ({ ticker, start, signal }) => {
46
+ const data = await fetchAttention("apps", ticker!, { days: appHistoryDays(start) }, undefined, signal) as AppAttentionPayload;
47
+ if (data.access === "preview") throw new Error("Full app history requires Pro.");
48
+ if (!data.history.length) throw new Error("History is accumulating from the first capture.");
49
+ return data.history.map((point) => ({ date: point.date, value: point.attentionScore }));
50
+ },
51
+ },
52
+ ];
@@ -13,6 +13,7 @@ import type { PaneProps } from "../../../types/plugin";
13
13
  import { Box, Text, useRendererHost, useUiCapabilities, type ScrollBoxRenderable } from "../../../ui";
14
14
  import { SignInWall } from "../cloud/auth-actions";
15
15
  import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
16
+ import { useOpenDescription } from "../shared/open-description";
16
17
  import { Blurred, LockedOverlay, UpgradeLabel } from "../shared/locked-rows";
17
18
  import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
18
19
  import { cachedCompanyData, loadCompanyData, KPI_UNAVAILABLE, validateCompanyData, type CompanyDataset, type CompanyMode } from "./client";
@@ -77,6 +78,7 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
77
78
  const host = useRendererHost();
78
79
  const desktop = !!useUiCapabilities().nativePaneChrome;
79
80
  const { createPaneFromTemplate } = usePluginAppActions();
81
+ const openDescription = useOpenDescription();
80
82
  const session = useResearchCloudSession();
81
83
  const access = usePlanAccess();
82
84
  const accessKey = `${session.requestKey}:${access.hasProAccess ? "full" : "preview"}`;
@@ -165,10 +167,10 @@ function CompanyView({ symbol, exchange, name, mode, width, height, focused }: P
165
167
  ...(preview ? [{ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: openUpgrade }] : []),
166
168
  ];
167
169
  usePaneMenuItems("company-disclosures:research", () => symbol ? [
168
- { id: "description", label: "Description (DES)", onSelect: () => createPaneFromTemplate("new-ticker-detail-pane", { symbol }) },
170
+ { id: "description", label: "Description (DES)", onSelect: () => openDescription(symbol) },
169
171
  { id: "financials", label: "Financial Analysis (FA)", onSelect: () => createPaneFromTemplate("financial-analysis-pane", { symbol }) },
170
172
  { id: "price-chart", label: "Price Chart (G)", onSelect: () => createPaneFromTemplate("chart-composer-pane", { arg: symbol }) },
171
- ] : null, [symbol, createPaneFromTemplate]);
173
+ ] : null, [symbol, createPaneFromTemplate, openDescription]);
172
174
  usePaneStatusFooter({ registrationId: "company-kpis", loading: resource.loading, error: data ? resource.error : null, stale: resource.data?.stale,
173
175
  info: data?.asOf ? [{ id: "as-of", parts: [{ text: `as of ${data.asOf.slice(0, 10)}`, tone: "muted" as const }] }] : [], hints });
174
176
  usePaneNoticeFooter({ registrationId: "company-kpis:notices", focused, notices: [resource.data?.refreshError,
@@ -32,6 +32,7 @@ import { cotModule } from "./cot";
32
32
  import { doeModule } from "./doe";
33
33
  import { gpuModule } from "./gpu";
34
34
  import { fxMatrixModule } from "./fx-matrix";
35
+ import { docsModule } from "./docs";
35
36
  import { helpModule } from "./help";
36
37
  import { positionSizerModule } from "./kelly-sizer";
37
38
  import { layoutManagerModule } from "./layout-manager";
@@ -57,6 +58,7 @@ import { socialMentionsModule } from "./social-mentions";
57
58
  import { timeSalesModule } from "./time-sales";
58
59
  import { estimateRevisionsModule } from "./estimate-revisions";
59
60
  import { chartComposerModule } from "./chart-composer";
61
+ import { userSeriesModule } from "./chart-composer/user-series";
60
62
  import { congressResearchModule } from "./congress-trades";
61
63
  import { dividendYieldModule } from "./dividend-yield";
62
64
  import { executivesModule } from "./executives";
@@ -104,7 +106,7 @@ import {
104
106
 
105
107
  export const applicationPlugin = composeBuiltinPlugin({
106
108
  ...applicationPluginMeta,
107
- modules: [layoutManagerModule, pluginMarketplaceModule, helpModule, changelogModule, connectionsModule],
109
+ modules: [layoutManagerModule, pluginMarketplaceModule, helpModule, changelogModule, connectionsModule, docsModule],
108
110
  });
109
111
 
110
112
  export const portfolioPlugin = composeBuiltinPlugin({
@@ -121,18 +123,22 @@ const tickerResearchModule = (module: PluginModule) => ({ module, stateId: "tick
121
123
  export const tickerCorePlugin = composeBuiltinPlugin({
122
124
  ...tickerCorePluginMeta,
123
125
  modules: [
124
- tickerDetailModule,
125
- chartComposerModule,
126
- researchModule,
127
- estimateRevisionsModule,
128
- companyKpisModule,
129
- revenueBreakdownModule,
130
- dividendYieldModule,
131
- reverseDcfModule,
132
- peBandModule,
133
- executivesModule,
134
- timeSalesModule,
135
- ].map(rendererOnly),
126
+ ...[
127
+ tickerDetailModule,
128
+ chartComposerModule,
129
+ researchModule,
130
+ estimateRevisionsModule,
131
+ companyKpisModule,
132
+ revenueBreakdownModule,
133
+ dividendYieldModule,
134
+ reverseDcfModule,
135
+ peBandModule,
136
+ executivesModule,
137
+ timeSalesModule,
138
+ ].map(rendererOnly),
139
+ // Reads the profile's series folder, so it runs where the disk is: the desktop backend too.
140
+ userSeriesModule,
141
+ ],
136
142
  });
137
143
 
138
144
  export const optionsVolatilityPlugin = composeBuiltinPlugin({
@@ -3,14 +3,16 @@ import { cotBoardCache } from "./client";
3
3
  import { cotHeadless } from "./headless";
4
4
  import { COT_ROOT_OPTIONS, cotContractCode, cotContractCodeForTicker, cotRoot, cotUnknownMarketMessage } from "./model";
5
5
  import { CotPane } from "./pane";
6
+ import { COT_SERIES } from "./series";
6
7
 
7
8
  export const cotModule: PluginModule = {
9
+ series: COT_SERIES,
8
10
  panes: [{ id: "cot", name: "CFTC Positioning", icon: "P", component: CotPane,
9
11
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 106, height: 30 },
10
12
  tableExport: true, headless: cotHeadless }],
11
13
  paneTemplates: [{ id: "cot-cot", paneId: "cot", label: "CFTC Positioning",
12
14
  description: "Weekly futures positioning, changes, historical percentiles and cross-market extremes.",
13
- keywords: ["cot", "cftc", "positioning", "commitments", "managed money", "net spec"],
15
+ keywords: ["cot", "cftc", "positioning", "commitments", "managed money", "net spec", "crude oil", "natural gas"],
14
16
  shortcut: { prefix: "COT", aliases: ["CFTC"], argKind: "text" as const, argPlaceholder: "code or root", argOptional: true,
15
17
  argOptions: () => COT_ROOT_OPTIONS }, headless: cotHeadless,
16
18
  createInstance: (context, options) => {
@@ -5,6 +5,7 @@ import type { PricePoint } from "../../../types/financials";
5
5
  import { formatPriceObservation } from "../../../market-data/market/format";
6
6
  import { cleanFloat32Price } from "../../../cli/history-rows";
7
7
  import { cryptoPairCoin, isFuturesSymbol } from "../shared/crypto-pair";
8
+ import { FUTURES_CONTRACTS, futuresBoardContract, futuresBoardContractForTicker, type FuturesContract } from "../../../market-data/futures-board";
8
9
 
9
10
  export const COT_CLASSES: Record<CotFamily, Array<{ value: CotClass; label: string }>> = {
10
11
  legacy: [{ value: "noncommercial", label: "Noncommercial" }, { value: "commercial", label: "Commercial" }, { value: "nonreportable", label: "Nonreportable" }],
@@ -29,7 +30,7 @@ const ROOTS: Record<string, { code: string; exchange: string; priceSymbol: strin
29
30
  SOL: { code: "177741", exchange: "CME", priceSymbol: null, name: "Solana", crypto: true },
30
31
  XRP: { code: "176740", exchange: "CME", priceSymbol: null, name: "XRP", crypto: true },
31
32
  };
32
- /** The roots `cotContractCode` accepts, labeled "ZQ 30-Day Fed Funds". */
33
+ /** The verified root aliases, labeled "ZQ 30-Day Fed Funds"; `cotContractCode` also takes every FUT board root the CFTC reports. */
33
34
  export const COT_ROOT_OPTIONS = Object.entries(ROOTS).map(([root, row]) => ({ value: root, label: root === row.name ? root : `${root} ${row.name}` }));
34
35
  /** The crypto roots named for a coin, which a coin pair (BTC-USD) can stand for. The micros have no pair. */
35
36
  const COIN_PAIR_ROOTS: ReadonlySet<string> = new Set(["BTC", "ETH", "SOL", "XRP"]);
@@ -37,9 +38,12 @@ const COIN_PAIR_ROOTS: ReadonlySet<string> = new Set(["BTC", "ETH", "SOL", "XRP"
37
38
  * Front-month price overlays for major markets without a root alias, each
38
39
  * checked to return five years of daily history (2026-09-24). Keyed by CFTC
39
40
  * code, never added to ROOTS: an alias would also claim the equity ticker under
40
- * the cursor (ZM, ZS, PL). No cash index stands in, so the dollar index has none.
41
- * Bitcoin and ether are in ROOTS, where `cotContractCodeForTicker` keeps the
42
- * same guard for their aliases.
41
+ * the cursor (ZM, ZS, PL). A FUT board root typed as an argument finds its
42
+ * market here through its price symbol (NG is NG=F, so 023651). No cash index
43
+ * stands in, so the dollar index has none. Orange juice and lumber were added
44
+ * on 2026-10-11 so their board roots open a market; lumber's contract (LBR)
45
+ * started in August 2022, so its price begins there. Bitcoin and ether are in
46
+ * ROOTS, where `cotContractCodeForTicker` keeps the same guard for their aliases.
43
47
  */
44
48
  const PRICE_OVERLAYS: Record<string, { exchange: string; priceSymbol: string }> = {
45
49
  "209742": { exchange: "CME", priceSymbol: "NQ=F" }, "239742": { exchange: "CME", priceSymbol: "RTY=F" },
@@ -60,9 +64,16 @@ const PRICE_OVERLAYS: Record<string, { exchange: string; priceSymbol: string }>
60
64
  "007601": { exchange: "CBT", priceSymbol: "ZL=F" }, "026603": { exchange: "CBT", priceSymbol: "ZM=F" },
61
65
  "083731": { exchange: "NYB", priceSymbol: "KC=F" }, "080732": { exchange: "NYB", priceSymbol: "SB=F" },
62
66
  "033661": { exchange: "NYB", priceSymbol: "CT=F" }, "073732": { exchange: "NYB", priceSymbol: "CC=F" },
67
+ "040701": { exchange: "NYB", priceSymbol: "OJ=F" }, "058644": { exchange: "CME", priceSymbol: "LBR=F" },
63
68
  "057642": { exchange: "CME", priceSymbol: "LE=F" }, "054642": { exchange: "CME", priceSymbol: "HE=F" },
64
69
  "061641": { exchange: "CME", priceSymbol: "GF=F" },
65
70
  };
71
+ /** The CFTC market a FUT board root reports under, by its price symbol; null for one the CFTC does not report (TTF). */
72
+ function boardMarketCode(contract: FuturesContract): string | null {
73
+ return Object.values(ROOTS).find((row) => row.priceSymbol === contract.symbol)?.code
74
+ ?? Object.entries(PRICE_OVERLAYS).find(([, row]) => row.priceSymbol === contract.symbol)?.[0]
75
+ ?? null;
76
+ }
66
77
  /**
67
78
  * The markets most readers mean by "positioning", verified against the live
68
79
  * CFTC boards on 2026-09-22. A code missing from a report family simply does
@@ -90,14 +101,26 @@ export const COT_SCOPES = [{ value: "major", label: "Major markets" }, { value:
90
101
  export type CotScope = typeof COT_SCOPES[number]["value"];
91
102
  export function cotScope(value: unknown): CotScope { return value === "all" ? "all" : "major"; }
92
103
 
93
- /** A CFTC code or root typed as an argument or stored in a pane: every alias, crypto included. */
104
+ const rootAlias = (input: string) => ROOTS[input === "VIX" ? "VX" : input]?.code ?? null;
105
+ const marketCode = (input: string) => /^[0-9A-Z]{5}[0-9A-Z+]$/.test(input) ? input : null;
106
+ /**
107
+ * A CFTC code or root typed as an argument or stored in a pane: every alias,
108
+ * crypto included, and every FUT board root the CFTC reports. A typed root is
109
+ * always the future, so ZM here is soybean meal, never Zoom.
110
+ */
94
111
  export function cotContractCode(value: unknown): string | null {
95
112
  if (typeof value !== "string") return null;
96
113
  const input = value.trim().toUpperCase().replace(/=F$/, "");
97
- return ROOTS[input === "VIX" ? "VX" : input]?.code ?? (/^[0-9A-Z]{5}[0-9A-Z+]$/.test(input) ? input : null);
114
+ const board = futuresBoardContract(value);
115
+ return rootAlias(input) ?? (board ? boardMarketCode(board) : null) ?? marketCode(input);
98
116
  }
99
- /** What a market that is neither a root nor a CFTC code is told, with ones that work. */
117
+ /**
118
+ * What a market `cotContractCode` does not know is told: a FUT board root the
119
+ * CFTC does not report says so; anything else gets roots and a code that work.
120
+ */
100
121
  export function cotUnknownMarketMessage(value: unknown): string {
122
+ const board = futuresBoardContract(value);
123
+ if (board) return `${board.code} (${board.name}) is not reported in the CFTC COT.`;
101
124
  const roots = ["ES", "CL", "GC", "ZN"].filter((root) => ROOTS[root]);
102
125
  return `Unknown market "${String(value)}". Try ${roots.join(", ")} or a CFTC market code such as ${ROOTS.GC!.code}.`;
103
126
  }
@@ -106,18 +129,24 @@ export function cotUnknownMarketMessage(value: unknown): string {
106
129
  * argument. The crypto aliases are also US ticker symbols (Grayscale's mini
107
130
  * trusts are BTC and ETH, MetLife is MET), so a bare one is the equity's and
108
131
  * never claims the market: only a futures symbol (BTC=F) or a coin pair
109
- * (BTC-USD) does.
132
+ * (BTC-USD) does. A FUT board root claims it the same way: as its futures
133
+ * symbol (NG=F) or a code no security uses (ZW), never a bare code a security
134
+ * keeps (ZM is Zoom).
110
135
  */
111
136
  export function cotContractCodeForTicker(ticker: unknown): string | null {
112
137
  if (typeof ticker !== "string") return null;
113
138
  const coin = cryptoPairCoin(ticker);
114
139
  if (coin) return COIN_PAIR_ROOTS.has(coin) ? ROOTS[coin]!.code : null;
115
140
  const input = ticker.trim().toUpperCase().replace(/=F$/, "");
116
- return ROOTS[input]?.crypto && !isFuturesSymbol(ticker) ? null : cotContractCode(ticker);
141
+ if (ROOTS[input]?.crypto && !isFuturesSymbol(ticker)) return null;
142
+ const board = futuresBoardContractForTicker(ticker);
143
+ return rootAlias(input) ?? (board ? boardMarketCode(board) : null) ?? marketCode(input);
117
144
  }
118
- /** The verified futures root a CFTC code belongs to, for titles readers recognize. */
145
+ /** The futures root a CFTC code belongs to, for titles readers recognize: a verified alias, else its FUT board root. */
119
146
  export function cotRoot(code: string): string | null {
120
- return Object.entries(ROOTS).find(([, row]) => row.code === code)?.[0] ?? null;
147
+ return Object.entries(ROOTS).find(([, row]) => row.code === code)?.[0]
148
+ ?? FUTURES_CONTRACTS.find((contract) => boardMarketCode(contract) === code)?.code
149
+ ?? null;
121
150
  }
122
151
  export function cotPriceMapping(code: string) { return Object.values(ROOTS).find((row) => row.code === code) ?? PRICE_OVERLAYS[code] ?? null; }
123
152
  export function cotClass(family: CotFamily, value: unknown): CotClass {
@@ -0,0 +1,27 @@
1
+ import type { SeriesDefinition } from "../../../series/types";
2
+ import { fetchCotContract } from "./client";
3
+ import { COT_ROOT_OPTIONS, cotContractCode, cotNetPoints } from "./model";
4
+
5
+ /**
6
+ * Speculative (noncommercial) net positioning for each market the COT pane
7
+ * names by root, from the legacy report every market is in. The pane's other
8
+ * trader classes and markets stay in the pane.
9
+ */
10
+ export const COT_SERIES: readonly SeriesDefinition[] = COT_ROOT_OPTIONS.flatMap((option): SeriesDefinition[] => {
11
+ const code = cotContractCode(option.value);
12
+ if (!code) return [];
13
+ const market = option.label.startsWith(`${option.value} `) ? option.label.slice(option.value.length + 1) : option.label;
14
+ return [{
15
+ id: `cot:${option.value.toLowerCase()}-noncommercial-net`,
16
+ name: `${market}: noncommercial net position`,
17
+ unit: "contracts",
18
+ frequency: "weekly",
19
+ group: "Positioning",
20
+ tags: ["COT", "commitments of traders", "speculators", "net spec", option.value],
21
+ publisher: "CFTC",
22
+ load: async () => {
23
+ const payload = await fetchCotContract(code, "legacy");
24
+ return { points: cotNetPoints(payload.history, "noncommercial").map((point) => ({ date: point.date, value: point.value })) };
25
+ },
26
+ }];
27
+ });
@@ -5,6 +5,7 @@ import { cdxBoardCache, sovrBoardCache } from "./client";
5
5
  import { cdxHeadless, sovrHeadless } from "./headless";
6
6
  import { CDX_PANE_ID } from "./model";
7
7
  import { CreditBoardsPane } from "./pane";
8
+ import { CREDIT_BOARD_SERIES } from "./series";
8
9
 
9
10
  /** One board. A later command retargets this instance; a fresh id would open a second window. */
10
11
  function openCreditBoard(context: PaneTemplateContext, tab: "index" | "sovereign"): PaneTemplateInstanceConfig {
@@ -16,6 +17,7 @@ function openCreditBoard(context: PaneTemplateContext, tab: "index" | "sovereign
16
17
  }
17
18
 
18
19
  export const creditBoardsModule: PluginModule = {
20
+ series: CREDIT_BOARD_SERIES,
19
21
  panes: [
20
22
  {
21
23
  id: CDX_PANE_ID,
@@ -0,0 +1,66 @@
1
+ import type { CreditBoardPoint } from "../../../api-client/credit-boards";
2
+ import type { SeriesDefinition, SeriesLoadResult } from "../../../series/types";
3
+ import { cachedSeriesWarning } from "../shared/series-warning";
4
+ import { loadCdxBoard, loadSovrBoard } from "./client";
5
+
6
+ /** The five on-the-run 5Y index contracts the CDX tab lists; IG and iTraxx quote spread, HY and EM price. */
7
+ const INDEXES = [
8
+ { code: "IG", name: "CDX IG 5Y", quote: "spread" },
9
+ { code: "HY", name: "CDX HY 5Y", quote: "price" },
10
+ { code: "EM", name: "CDX EM 5Y", quote: "price" },
11
+ { code: "MAIN", name: "iTraxx Main 5Y", quote: "spread" },
12
+ { code: "XOVER", name: "iTraxx Crossover 5Y", quote: "spread" },
13
+ ] as const;
14
+
15
+ /** The sovereigns the SOVR tab follows, by ISO code. */
16
+ const SOVEREIGNS: ReadonlyArray<readonly [code: string, name: string]> = [
17
+ ["BR", "Brazil"], ["MX", "Mexico"], ["CO", "Colombia"], ["TR", "Turkey"], ["ZA", "South Africa"],
18
+ ["CN", "China"], ["ID", "Indonesia"], ["CL", "Chile"], ["PE", "Peru"], ["PH", "Philippines"],
19
+ ["AR", "Argentina"], ["PA", "Panama"], ["SA", "Saudi Arabia"], ["MY", "Malaysia"], ["EG", "Egypt"],
20
+ ["KR", "Korea"], ["IT", "Italy"], ["FR", "France"], ["ES", "Spain"], ["GR", "Greece"],
21
+ ["IE", "Ireland"], ["JP", "Japan"], ["IL", "Israel"], ["PL", "Poland"], ["QA", "Qatar"],
22
+ ["AD", "Abu Dhabi"], ["BH", "Bahrain"], ["OM", "Oman"], ["MA", "Morocco"], ["NG", "Nigeria"],
23
+ ["DO", "Dominican Republic"], ["CI", "Ivory Coast"],
24
+ ];
25
+
26
+ function boardPoints(
27
+ rows: ReadonlyArray<{ id: string; points: readonly CreditBoardPoint[] }>,
28
+ id: string,
29
+ label: string,
30
+ resource: { stale?: boolean; refreshError?: string | null },
31
+ ): SeriesLoadResult {
32
+ const row = rows.find((entry) => entry.id === id);
33
+ if (!row) throw new Error(`${label} is not on the board right now.`);
34
+ const warning = cachedSeriesWarning(resource);
35
+ return {
36
+ points: row.points.map((point) => ({ date: point.date, value: point.level })),
37
+ ...(warning ? { warning } : {}),
38
+ };
39
+ }
40
+
41
+ export const CREDIT_BOARD_SERIES: readonly SeriesDefinition[] = [
42
+ ...INDEXES.map((index): SeriesDefinition => ({
43
+ id: `cdx:${index.code.toLowerCase()}`,
44
+ name: index.name,
45
+ unit: index.quote === "spread" ? "bp" : "points",
46
+ frequency: "daily",
47
+ group: "Credit",
48
+ tags: ["CDS", "index CDS", index.quote, "credit default swap"],
49
+ load: async () => {
50
+ const resource = await loadCdxBoard();
51
+ return boardPoints(resource.payload.indexes, index.code, index.name, resource);
52
+ },
53
+ })),
54
+ ...SOVEREIGNS.map(([code, name]): SeriesDefinition => ({
55
+ id: `sovereign-cds:${code.toLowerCase()}`,
56
+ name: `${name} 5Y sovereign CDS`,
57
+ unit: "bp",
58
+ frequency: "daily",
59
+ group: "Credit",
60
+ tags: ["CDS", "sovereign", code, "credit default swap"],
61
+ load: async () => {
62
+ const resource = await loadSovrBoard();
63
+ return boardPoints(resource.payload.sovereigns, code, name, resource);
64
+ },
65
+ })),
66
+ ];
@@ -20,6 +20,7 @@ import type { PaneProps } from "../../../types/plugin";
20
20
  import { formatPercentileRank } from "../../../utils/format";
21
21
  import { usePluginPaneState } from "../../runtime";
22
22
  import type { PluginModule } from "../plugin-module";
23
+ import { CREDIT_SPREAD_SERIES } from "./series";
23
24
  import { useAutoRefresh } from "../../../react/auto-refresh";
24
25
  import { getCachedCreditConditions, loadCreditConditions } from "./client";
25
26
  import { creditConditionsHeadless } from "./headless";
@@ -137,6 +138,7 @@ export function CreditConditionsPane({ paneId, focused, width, height }: PanePro
137
138
  }
138
139
 
139
140
  export const creditConditionsModule: PluginModule = {
141
+ series: CREDIT_SPREAD_SERIES,
140
142
  panes: [{
141
143
  id: "credit-conditions",
142
144
  name: "Credit Spreads",
@@ -0,0 +1,26 @@
1
+ import { fredSeriesUrl } from "../../../sources/gloomberb-cloud/fred-series";
2
+ import type { SeriesDefinition } from "../../../series/types";
3
+
4
+ /** Corporate bond option-adjusted spreads by rating, and the effective yields of both indexes. */
5
+ const ROWS: ReadonlyArray<readonly [seriesId: string, name: string, tags: readonly string[]]> = [
6
+ ["BAMLC0A0CM", "US investment grade corporate OAS", ["spread", "IG", "investment grade"]],
7
+ ["BAMLC0A1CAAA", "AAA corporate OAS", ["spread", "AAA", "investment grade"]],
8
+ ["BAMLC0A2CAA", "AA corporate OAS", ["spread", "AA", "investment grade"]],
9
+ ["BAMLC0A3CA", "A corporate OAS", ["spread", "single A", "investment grade"]],
10
+ ["BAMLC0A4CBBB", "BBB corporate OAS", ["spread", "BBB", "investment grade"]],
11
+ ["BAMLH0A0HYM2", "US high yield OAS", ["spread", "HY", "high yield", "junk"]],
12
+ ["BAMLC0A0CMEY", "US investment grade corporate effective yield", ["yield", "IG", "investment grade"]],
13
+ ["BAMLH0A0HYM2EY", "US high yield effective yield", ["yield", "HY", "high yield", "junk"]],
14
+ ];
15
+
16
+ export const CREDIT_SPREAD_SERIES: readonly SeriesDefinition[] = ROWS.map(([seriesId, name, tags]) => ({
17
+ id: `fred:${seriesId}`,
18
+ name,
19
+ unit: "%",
20
+ frequency: "daily",
21
+ group: "Credit",
22
+ tags: [...tags, "corporate bonds"],
23
+ publisher: "FRED",
24
+ url: fredSeriesUrl(seriesId),
25
+ expression: `FRED:${seriesId}`,
26
+ }));