gloomberb 0.11.2 → 0.12.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 (531) hide show
  1. package/README.md +7 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -1
  4. package/src/api-client/auth.ts +10 -0
  5. package/src/api-client/data.ts +108 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +134 -1
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/types.ts +220 -0
  11. package/src/app/pane-runtime/index.tsx +15 -12
  12. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  13. package/src/app/pane-runtime/layout-placement.ts +13 -0
  14. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  15. package/src/app.tsx +60 -18
  16. package/src/architecture/browser-entry-graph.test.ts +96 -0
  17. package/src/brokers/profile-form.test.ts +25 -27
  18. package/src/brokers/test-broker.ts +68 -0
  19. package/src/capabilities/chart-series.ts +3 -2
  20. package/src/cli/commands/market.ts +2 -2
  21. package/src/cli/commands/plugins.ts +39 -11
  22. package/src/cli/desktop-pane-shot.ts +285 -16
  23. package/src/cli/index.ts +1 -1
  24. package/src/cli/pane-functions/capabilities.ts +61 -31
  25. package/src/cli/pane-functions/catalog.ts +16 -4
  26. package/src/cli/pane-functions/discovery.ts +1 -0
  27. package/src/cli/pane-functions/headless.test.ts +247 -0
  28. package/src/cli/pane-functions/headless.ts +423 -0
  29. package/src/cli/pane-functions/index.test.ts +9 -0
  30. package/src/cli/pane-functions/index.ts +6 -2
  31. package/src/cli/pane-functions/options.ts +27 -2
  32. package/src/cli/pane-functions/report.ts +4 -0
  33. package/src/cli/pane-functions/resolver.ts +26 -8
  34. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  35. package/src/cli/pane-functions/screenshot.ts +236 -20
  36. package/src/cli/registry.ts +0 -22
  37. package/src/cli/restore-plugins.ts +34 -0
  38. package/src/cli/result.ts +7 -10
  39. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  40. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  41. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  42. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  43. package/src/components/chart/composite/composite-chart.tsx +301 -204
  44. package/src/components/chart/composite/format.ts +8 -2
  45. package/src/components/chart/composite/interactions.test.ts +68 -102
  46. package/src/components/chart/composite/interactions.ts +214 -264
  47. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  48. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  49. package/src/components/chart/composite/scene.ts +57 -3
  50. package/src/components/chart/composite/text-renderer.ts +3 -2
  51. package/src/components/chart/composite/time-scale.ts +138 -57
  52. package/src/components/chart/composite/types.ts +25 -1
  53. package/src/components/chart/core/data.ts +9 -13
  54. package/src/components/chart/core/date-window.ts +7 -1
  55. package/src/components/chart/core/pointer.ts +3 -0
  56. package/src/components/chart/core/types.ts +0 -45
  57. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  58. package/src/components/chart/native/kitty/index.test.ts +0 -188
  59. package/src/components/chart/native/raster/crosshair.ts +6 -5
  60. package/src/components/chart/native/raster/primitives.ts +0 -23
  61. package/src/components/chart/native/renderer-selection.ts +81 -31
  62. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  63. package/src/components/chart/static/chart-surface.tsx +150 -0
  64. package/src/components/chart/static/index.ts +8 -0
  65. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  66. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  67. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  68. package/src/components/chart/static/series.ts +49 -0
  69. package/src/components/command-bar/action-runtime.ts +1 -3
  70. package/src/components/command-bar/assist/model.test.ts +17 -2
  71. package/src/components/command-bar/assist/model.ts +40 -9
  72. package/src/components/command-bar/commands/direct/index.ts +1 -1
  73. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  74. package/src/components/command-bar/commands/registry.ts +0 -11
  75. package/src/components/command-bar/helpers.test.ts +2 -2
  76. package/src/components/command-bar/helpers.ts +2 -6
  77. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  78. package/src/components/command-bar/list/badge.test.ts +54 -0
  79. package/src/components/command-bar/list/badge.ts +62 -0
  80. package/src/components/command-bar/list/model.test.ts +103 -0
  81. package/src/components/command-bar/list/model.ts +61 -4
  82. package/src/components/command-bar/list/view.test.tsx +113 -0
  83. package/src/components/command-bar/list/view.tsx +147 -131
  84. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  85. package/src/components/command-bar/panel/body.tsx +51 -65
  86. package/src/components/command-bar/panel/index.tsx +48 -13
  87. package/src/components/command-bar/panel/layout.test.ts +139 -0
  88. package/src/components/command-bar/panel/layout.ts +65 -28
  89. package/src/components/command-bar/panel/native-surface.ts +24 -0
  90. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  91. package/src/components/command-bar/panel/runtime.ts +6 -3
  92. package/src/components/command-bar/panel/state.ts +41 -7
  93. package/src/components/command-bar/panel/types.ts +7 -1
  94. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  95. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  96. package/src/components/command-bar/routes/root/results.ts +30 -28
  97. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  98. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  99. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  100. package/src/components/command-bar/routes/root/selection.ts +1 -12
  101. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  102. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  103. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  104. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  105. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  106. package/src/components/command-bar/routing/actions.ts +1 -81
  107. package/src/components/command-bar/routing/launch-request.ts +1 -1
  108. package/src/components/command-bar/routing/list-state.ts +6 -21
  109. package/src/components/command-bar/selection-runtime.ts +1 -1
  110. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  111. package/src/components/command-bar/surface/environment.ts +8 -1
  112. package/src/components/command-bar/surface/index.test.tsx +84 -68
  113. package/src/components/command-bar/surface/index.tsx +33 -36
  114. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  115. package/src/components/command-bar/theme-picker.tsx +43 -15
  116. package/src/components/command-bar/view-model.test.ts +53 -8
  117. package/src/components/command-bar/view-model.ts +43 -14
  118. package/src/components/command-bar/workflow/tickers.ts +1 -0
  119. package/src/components/command-bar/workflow/types.ts +1 -1
  120. package/src/components/data-table/export.test.ts +21 -0
  121. package/src/components/data-table/export.ts +21 -0
  122. package/src/components/data-table/view.test.tsx +45 -6
  123. package/src/components/data-table/view.tsx +55 -9
  124. package/src/components/index.ts +10 -2
  125. package/src/components/input-search-bar.test.tsx +36 -2
  126. package/src/components/input-search-bar.tsx +18 -1
  127. package/src/components/layout/header.test.tsx +57 -10
  128. package/src/components/layout/header.tsx +292 -149
  129. package/src/components/layout/market-summary.test.ts +81 -0
  130. package/src/components/layout/market-summary.ts +109 -0
  131. package/src/components/layout/shell/chrome.ts +96 -1
  132. package/src/components/layout/shell/index.tsx +11 -3
  133. package/src/components/layout/shell/menu.ts +9 -0
  134. package/src/components/layout/shell/pane/actions.ts +29 -0
  135. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  136. package/src/components/layout/shell/shortcuts.ts +3 -0
  137. package/src/components/layout/status-bar.test.tsx +9 -8
  138. package/src/components/layout/status-bar.tsx +85 -54
  139. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  140. package/src/components/price-sparkline/index.ts +61 -34
  141. package/src/components/price-sparkline/view.tsx +1 -1
  142. package/src/components/sign-in-gate.test.tsx +128 -0
  143. package/src/components/sign-in-gate.tsx +149 -0
  144. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  145. package/src/components/ticker/badge/list.tsx +17 -3
  146. package/src/components/ticker/list-table-view.tsx +6 -3
  147. package/src/components/ui/data-table/index.tsx +22 -4
  148. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  149. package/src/components/ui/frame.tsx +28 -0
  150. package/src/components/ui/index.ts +3 -0
  151. package/src/components/ui/native-select.tsx +20 -10
  152. package/src/components/ui/select-button.tsx +138 -0
  153. package/src/components/ui/status.tsx +2 -2
  154. package/src/components/ui/table-layout.ts +1 -1
  155. package/src/components/use-double-click-activation.ts +7 -3
  156. package/src/core/app-services.ts +3 -3
  157. package/src/data/config/chart-settings.ts +4 -4
  158. package/src/data/config/layout.ts +12 -6
  159. package/src/data/config/store/index.test.ts +97 -0
  160. package/src/data/config/store/migrations.ts +41 -0
  161. package/src/data/config/store/normalize.ts +5 -3
  162. package/src/i18n/es.ts +6 -2
  163. package/src/i18n/ja.ts +6 -2
  164. package/src/i18n/ko.ts +6 -2
  165. package/src/i18n/zh-cn.ts +6 -2
  166. package/src/i18n/zh-tw.ts +6 -2
  167. package/src/layout-marketplace/gallery-terminal.tsx +126 -96
  168. package/src/layout-marketplace/gallery.test.tsx +65 -1
  169. package/src/layout-marketplace/pane-imagery.ts +1 -0
  170. package/src/layout-marketplace/payload.test.ts +17 -0
  171. package/src/layout-marketplace/payload.ts +40 -5
  172. package/src/market-data/coordinator/index.ts +3 -2
  173. package/src/market-data/coordinator/quotes.ts +173 -66
  174. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  175. package/src/pane-settings.ts +1 -1
  176. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  177. package/src/plugins/builtin/analytics/view.tsx +1 -1
  178. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  179. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  180. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  181. package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
  182. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  183. package/src/plugins/builtin/chart-composer/settings.ts +2 -13
  184. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  185. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  186. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  188. package/src/plugins/builtin/composite-plugins.ts +16 -1
  189. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  190. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  191. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  192. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  193. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  194. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  195. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  196. package/src/plugins/builtin/earnings/index.tsx +1 -0
  197. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  198. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  199. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  200. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  201. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  202. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  203. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  204. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  205. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  206. package/src/plugins/builtin/econ/index.tsx +3 -1
  207. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  208. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  209. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  210. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  211. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  212. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  213. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  214. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  215. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  216. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  217. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  218. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  219. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  220. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  221. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  222. package/src/plugins/builtin/futures/index.tsx +1 -0
  223. package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
  224. package/src/plugins/builtin/help/index.tsx +4 -0
  225. package/src/plugins/builtin/holders/index.tsx +1 -0
  226. package/src/plugins/builtin/insider/index.tsx +1 -0
  227. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
  228. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  229. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  230. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  231. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  232. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  233. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  234. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  235. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  236. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  237. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  238. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  239. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  240. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  241. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  242. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  243. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  244. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  245. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  246. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  247. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  248. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  249. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  250. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  251. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  252. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  253. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  254. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  255. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  256. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  257. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  258. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  259. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  260. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  261. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  262. package/src/plugins/builtin/options/analytics.test.ts +83 -0
  263. package/src/plugins/builtin/options/analytics.ts +110 -0
  264. package/src/plugins/builtin/options/index.tsx +18 -2
  265. package/src/plugins/builtin/options/table.ts +121 -51
  266. package/src/plugins/builtin/options/types.ts +22 -15
  267. package/src/plugins/builtin/options/view.test.tsx +53 -1
  268. package/src/plugins/builtin/options/view.tsx +92 -41
  269. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  270. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  271. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  272. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  273. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  274. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  275. package/src/plugins/builtin/plugin-module.test.ts +2 -4
  276. package/src/plugins/builtin/plugin-module.ts +9 -8
  277. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  278. package/src/plugins/builtin/research/index.tsx +4 -0
  279. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  280. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  281. package/src/plugins/builtin/research-search/data.ts +109 -0
  282. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  283. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  284. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  285. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  286. package/src/plugins/builtin/research-search/index.tsx +85 -0
  287. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  288. package/src/plugins/builtin/research-search/model.ts +291 -0
  289. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  290. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  291. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  292. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  293. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  294. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  295. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  296. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  297. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  298. package/src/plugins/builtin/scanner/index.ts +2 -0
  299. package/src/plugins/builtin/sec/index.tsx +1 -0
  300. package/src/plugins/builtin/sectors/index.tsx +1 -0
  301. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  302. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  303. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  304. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  305. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
  306. package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  308. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  309. package/src/plugins/builtin/volatility/index.tsx +1 -1
  310. package/src/plugins/builtin/world-indices/index.tsx +1 -0
  311. package/src/plugins/builtin/world-venue-map/index.ts +27 -0
  312. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  313. package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
  314. package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
  315. package/src/plugins/builtin/world-venue-map/model.ts +268 -0
  316. package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
  317. package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
  318. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  319. package/src/plugins/bundle.test.ts +109 -0
  320. package/src/plugins/bundle.ts +139 -0
  321. package/src/plugins/catalog-backend.ts +6 -44
  322. package/src/plugins/catalog-browser.test.ts +1 -0
  323. package/src/plugins/catalog-browser.ts +12 -0
  324. package/src/plugins/catalog-manifest.test.ts +43 -0
  325. package/src/plugins/catalog-manifest.ts +92 -0
  326. package/src/plugins/catalog-ui.ts +20 -4
  327. package/src/plugins/catalog.ts +9 -2
  328. package/src/plugins/current-target.ts +28 -0
  329. package/src/plugins/host-contract.ts +37 -0
  330. package/src/plugins/host-link.ts +158 -0
  331. package/src/plugins/host-modules.ts +61 -0
  332. package/src/plugins/loader.ts +41 -22
  333. package/src/plugins/ownership.ts +1 -0
  334. package/src/plugins/prediction-markets/index.tsx +1 -0
  335. package/src/plugins/registry/context.ts +3 -0
  336. package/src/plugins/registry/contributions.ts +27 -0
  337. package/src/plugins/registry/index.test.ts +25 -0
  338. package/src/plugins/registry/index.ts +8 -0
  339. package/src/plugins/registry/pane-settings.test.ts +53 -0
  340. package/src/plugins/registry/pane-settings.ts +29 -3
  341. package/src/plugins/seed.test.ts +83 -0
  342. package/src/plugins/seed.ts +80 -0
  343. package/src/public/broker.ts +31 -0
  344. package/src/public/market-data.ts +9 -0
  345. package/src/public/pane-hooks.ts +57 -0
  346. package/src/public/public-api.test.ts +285 -0
  347. package/src/public/react.ts +62 -0
  348. package/src/public/test-support.ts +23 -0
  349. package/src/public/time-series.ts +8 -0
  350. package/src/public/utils.ts +77 -0
  351. package/src/remote/layout-helpers.ts +6 -8
  352. package/src/remote/resources.ts +2 -0
  353. package/src/remote/schema.ts +1 -0
  354. package/src/remote/semantic-tree.test.tsx +105 -0
  355. package/src/remote/semantic-tree.tsx +53 -35
  356. package/src/renderers/browser/main.tsx +6 -0
  357. package/src/renderers/browser/ui-host.tsx +11 -0
  358. package/src/renderers/cloudflare/worker.test.ts +19 -0
  359. package/src/renderers/cloudflare/worker.ts +1 -1
  360. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
  361. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  362. package/src/renderers/electrobun/bun/index.ts +6 -2
  363. package/src/renderers/electrobun/shared/protocol.ts +25 -0
  364. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  365. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  366. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  367. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  368. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  369. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  370. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  371. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  372. package/src/renderers/electrobun/view/main.tsx +27 -2
  373. package/src/renderers/electrobun/view/styles.css +5 -0
  374. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  375. package/src/renderers/electrobun/view/ui-host.tsx +3 -0
  376. package/src/renderers/opentui/chart-surface.tsx +5 -1
  377. package/src/renderers/opentui/host.tsx +20 -2
  378. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  379. package/src/renderers/opentui/interaction-performance.ts +188 -0
  380. package/src/renderers/opentui/start.tsx +26 -1
  381. package/src/renderers/opentui/test-utils.tsx +13 -0
  382. package/src/renderers/opentui/toast-host.tsx +26 -2
  383. package/src/renderers/opentui/ui-host.tsx +1 -0
  384. package/src/sources/provider-router/search.test.ts +191 -0
  385. package/src/sources/provider-router/search.ts +92 -39
  386. package/src/state/app/context/index.tsx +2 -0
  387. package/src/state/app/context/selector.test.tsx +33 -1
  388. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  389. package/src/state/hooks/quote-streaming.ts +106 -30
  390. package/src/state/pane-table-export-registry.ts +50 -0
  391. package/src/theme/color-utils.ts +1 -1
  392. package/src/theme/colors.ts +50 -0
  393. package/src/theme/themes.test.ts +15 -2
  394. package/src/theme/themes.ts +19 -1
  395. package/src/tickers/search/index.test.ts +26 -0
  396. package/src/tickers/search/index.ts +42 -17
  397. package/src/tickers/search/ranking.ts +14 -0
  398. package/src/time-series/chart-data.test.ts +216 -0
  399. package/src/time-series/range.ts +12 -0
  400. package/src/time-series/resolution.test.ts +25 -26
  401. package/src/time-series/resolution.ts +44 -25
  402. package/src/time-series/resolve.ts +7 -0
  403. package/src/time-series/spec.ts +5 -5
  404. package/src/time-series/types.ts +3 -1
  405. package/src/time-series/use-chart-resolution.ts +5 -0
  406. package/src/types/config.ts +123 -3
  407. package/src/types/data-provider.ts +11 -0
  408. package/src/types/headless.ts +161 -0
  409. package/src/types/plugin.ts +104 -20
  410. package/src/ui/host.tsx +19 -1
  411. package/src/ui/index.tsx +1 -0
  412. package/src/ui/toast.tsx +8 -4
  413. package/src/utils/csv.test.ts +13 -0
  414. package/src/utils/csv.ts +37 -0
  415. package/src/utils/format.ts +35 -0
  416. package/src/utils/fuzzy-search.test.ts +94 -0
  417. package/src/utils/fuzzy-search.ts +81 -22
  418. package/src/utils/save-text-file.test.ts +24 -0
  419. package/src/utils/save-text-file.ts +36 -0
  420. package/src/utils/shortcut-labels.ts +14 -0
  421. package/src/components/chart/core/axis-format.ts +0 -157
  422. package/src/components/chart/core/index.test.ts +0 -1075
  423. package/src/components/chart/core/projection.ts +0 -357
  424. package/src/components/chart/core/render-utils.ts +0 -4
  425. package/src/components/chart/core/renderer.ts +0 -244
  426. package/src/components/chart/core/scene.ts +0 -180
  427. package/src/components/chart/core/time-axis.ts +0 -330
  428. package/src/components/chart/cursor-motion.ts +0 -47
  429. package/src/components/chart/indicators/types.ts +0 -26
  430. package/src/components/chart/market-session.ts +0 -176
  431. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  432. package/src/components/chart/multi-line/renderer.ts +0 -161
  433. package/src/components/chart/multi-line/scene.ts +0 -188
  434. package/src/components/chart/native/raster/layout.ts +0 -129
  435. package/src/components/chart/native/raster/price-chart.ts +0 -244
  436. package/src/components/chart/rendering/chart-draw.ts +0 -270
  437. package/src/components/chart/rendering/geometry.ts +0 -95
  438. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  439. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  440. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  441. package/src/components/chart/static/chart/surface.tsx +0 -358
  442. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  443. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  444. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  445. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  446. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  447. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  448. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  449. package/src/plugins/builtin/substack/api/store.ts +0 -186
  450. package/src/plugins/builtin/substack/api/types.ts +0 -42
  451. package/src/plugins/builtin/substack/api.test.ts +0 -298
  452. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  453. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  454. package/src/plugins/builtin/substack/content.ts +0 -478
  455. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  456. package/src/plugins/builtin/substack/index.tsx +0 -49
  457. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  458. package/src/plugins/builtin/substack/model.test.ts +0 -155
  459. package/src/plugins/builtin/substack/normalize.ts +0 -343
  460. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  461. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  462. package/src/plugins/builtin/substack/pane.tsx +0 -547
  463. package/src/plugins/builtin/substack/read-state.ts +0 -30
  464. package/src/plugins/builtin/substack/table.ts +0 -111
  465. package/src/plugins/builtin/substack/types.ts +0 -71
  466. package/src/plugins/builtin/substack/utils.ts +0 -131
  467. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  468. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  469. package/src/plugins/ibkr/account-cache.ts +0 -24
  470. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  471. package/src/plugins/ibkr/config.test.ts +0 -79
  472. package/src/plugins/ibkr/config.ts +0 -250
  473. package/src/plugins/ibkr/dialogs.tsx +0 -40
  474. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  475. package/src/plugins/ibkr/flex/index.ts +0 -367
  476. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  477. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  478. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  479. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  480. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  481. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  482. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  483. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  484. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  485. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  486. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  487. package/src/plugins/ibkr/gateway/history.ts +0 -120
  488. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  489. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  490. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  491. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  492. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  493. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  494. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  495. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  496. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  497. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  498. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  499. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  500. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  501. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  502. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  503. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  504. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  505. package/src/plugins/ibkr/gateway/types.ts +0 -27
  506. package/src/plugins/ibkr/index.test.tsx +0 -162
  507. package/src/plugins/ibkr/index.tsx +0 -159
  508. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  509. package/src/plugins/ibkr/instance-selection.ts +0 -36
  510. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  511. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  512. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  513. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  514. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  515. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  516. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  517. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  518. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  519. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  520. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  521. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  522. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  523. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  524. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  525. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  526. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  527. package/src/plugins/ibkr/trade/utils.ts +0 -144
  528. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  529. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  530. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  531. package/src/plugins/ibkr/trading/state.ts +0 -335
@@ -0,0 +1,522 @@
1
+ import { formatNumber } from "../../../utils/format";
2
+ import type { StatDef } from "./defs";
3
+
4
+ const DAY_MS = 24 * 60 * 60 * 1000;
5
+ const DAILY = 6 * DAY_MS;
6
+ const WEEKLY = 21 * DAY_MS;
7
+ const MONTHLY = 75 * DAY_MS;
8
+ const QUARTERLY = 200 * DAY_MS;
9
+
10
+ const pct = (value: number) => `${formatNumber(value, 1)}%`;
11
+ const pct2 = (value: number) => `${formatNumber(value, 2)}%`;
12
+ const plain = (value: number) => formatNumber(value, 1);
13
+ /** FRED reports payrolls and claims in thousands already. */
14
+ const thousands = (value: number) => `${formatNumber(value / 1000, value >= 10_000 ? 1 : 2)}M`;
15
+ const signedThousands = (value: number) =>
16
+ `${value > 0 ? "+" : ""}${formatNumber(value, 0)}k`;
17
+ const billions = (value: number) => `${formatNumber(value, 0)}B`;
18
+
19
+ function stat(def: StatDef): StatDef {
20
+ return def;
21
+ }
22
+
23
+ export const STATS: readonly StatDef[] = [
24
+ // ---- Inflation -------------------------------------------------------
25
+ stat({
26
+ id: "cpi-yoy",
27
+ label: "CPI",
28
+ shortLabel: "CPI y/y",
29
+ category: "inflation",
30
+ seriesId: "CPIAUCSL",
31
+ transform: "yoy",
32
+ direction: "higher-is-bad",
33
+ axisUnit: "%",
34
+ formatValue: pct,
35
+ reference: { value: 2, label: "2%" },
36
+ staleAfterMs: MONTHLY,
37
+ note: "Headline consumer prices against a year earlier, including food and energy.",
38
+ limit: 900,
39
+ }),
40
+ stat({
41
+ id: "core-cpi-yoy",
42
+ label: "Core CPI",
43
+ shortLabel: "Core CPI y/y",
44
+ category: "inflation",
45
+ seriesId: "CPILFESL",
46
+ transform: "yoy",
47
+ direction: "higher-is-bad",
48
+ axisUnit: "%",
49
+ formatValue: pct,
50
+ reference: { value: 2, label: "2%" },
51
+ staleAfterMs: MONTHLY,
52
+ note: "Consumer prices excluding food and energy, the slower-moving trend.",
53
+ limit: 900,
54
+ }),
55
+ stat({
56
+ id: "pce-yoy",
57
+ label: "PCE Prices",
58
+ shortLabel: "PCE y/y",
59
+ category: "inflation",
60
+ seriesId: "PCEPI",
61
+ transform: "yoy",
62
+ direction: "higher-is-bad",
63
+ axisUnit: "%",
64
+ formatValue: pct,
65
+ reference: { value: 2, label: "2%" },
66
+ staleAfterMs: MONTHLY,
67
+ note: "The price index for personal consumption, broader in scope than CPI.",
68
+ limit: 900,
69
+ }),
70
+ stat({
71
+ id: "core-pce-yoy",
72
+ label: "Core PCE Prices",
73
+ shortLabel: "Core PCE y/y",
74
+ category: "inflation",
75
+ seriesId: "PCEPILFE",
76
+ transform: "yoy",
77
+ direction: "higher-is-bad",
78
+ axisUnit: "%",
79
+ formatValue: pct,
80
+ reference: { value: 2, label: "Fed target" },
81
+ staleAfterMs: MONTHLY,
82
+ note: "The measure the Federal Reserve actually targets at two percent.",
83
+ limit: 900,
84
+ }),
85
+ stat({
86
+ id: "ppi-yoy",
87
+ label: "Producer Prices",
88
+ shortLabel: "PPI y/y",
89
+ category: "inflation",
90
+ seriesId: "PPIACO",
91
+ transform: "yoy",
92
+ direction: "higher-is-bad",
93
+ axisUnit: "%",
94
+ formatValue: pct,
95
+ reference: null,
96
+ staleAfterMs: MONTHLY,
97
+ note: "What producers receive, which tends to lead consumer prices.",
98
+ limit: 900,
99
+ }),
100
+
101
+ // ---- Labor -----------------------------------------------------------
102
+ stat({
103
+ id: "unemployment",
104
+ label: "Unemployment Rate",
105
+ shortLabel: "Unemployment",
106
+ category: "labor",
107
+ seriesId: "UNRATE",
108
+ transform: "level",
109
+ direction: "higher-is-bad",
110
+ axisUnit: "%",
111
+ formatValue: pct,
112
+ reference: null,
113
+ staleAfterMs: MONTHLY,
114
+ note: "Share of the labour force without work and looking for it.",
115
+ limit: 900,
116
+ }),
117
+ stat({
118
+ id: "payrolls",
119
+ label: "Nonfarm Payrolls",
120
+ shortLabel: "Payrolls m/m",
121
+ category: "labor",
122
+ seriesId: "PAYEMS",
123
+ transform: "change",
124
+ direction: "higher-is-good",
125
+ axisUnit: "",
126
+ formatValue: signedThousands,
127
+ reference: { value: 0, label: "flat" },
128
+ staleAfterMs: MONTHLY,
129
+ note: "Jobs added or lost last month, the single most watched release.",
130
+ limit: 900,
131
+ }),
132
+ stat({
133
+ id: "initial-claims",
134
+ label: "Initial Jobless Claims",
135
+ shortLabel: "Claims",
136
+ category: "labor",
137
+ seriesId: "ICSA",
138
+ transform: "level",
139
+ direction: "higher-is-bad",
140
+ axisUnit: "",
141
+ formatValue: (value) => `${formatNumber(value / 1000, 0)}k`,
142
+ reference: null,
143
+ staleAfterMs: WEEKLY,
144
+ note: "Weekly filings for unemployment benefits, the fastest labour signal.",
145
+ limit: 2000,
146
+ }),
147
+ stat({
148
+ id: "job-openings",
149
+ label: "Job Openings",
150
+ shortLabel: "Openings",
151
+ category: "labor",
152
+ seriesId: "JTSJOL",
153
+ transform: "level",
154
+ direction: "higher-is-good",
155
+ axisUnit: "",
156
+ formatValue: thousands,
157
+ reference: null,
158
+ staleAfterMs: MONTHLY,
159
+ note: "Unfilled positions employers report, a read on labour demand.",
160
+ limit: 400,
161
+ }),
162
+
163
+ // ---- Growth ----------------------------------------------------------
164
+ stat({
165
+ id: "real-gdp",
166
+ label: "Real GDP",
167
+ shortLabel: "Real GDP q/q",
168
+ category: "growth",
169
+ seriesId: "GDPC1",
170
+ transform: "qoq-annualized",
171
+ direction: "higher-is-good",
172
+ axisUnit: "%",
173
+ formatValue: pct,
174
+ reference: { value: 0, label: "flat" },
175
+ staleAfterMs: QUARTERLY,
176
+ note: "Inflation-adjusted output, stated at the annual rate the quarter implies.",
177
+ limit: 400,
178
+ }),
179
+ stat({
180
+ id: "industrial-production",
181
+ label: "Industrial Production",
182
+ shortLabel: "Ind. prod.",
183
+ category: "growth",
184
+ seriesId: "INDPRO",
185
+ transform: "yoy",
186
+ direction: "higher-is-good",
187
+ axisUnit: "%",
188
+ formatValue: pct,
189
+ reference: { value: 0, label: "flat" },
190
+ staleAfterMs: MONTHLY,
191
+ note: "Output of factories, mines and utilities against a year earlier.",
192
+ limit: 900,
193
+ }),
194
+ stat({
195
+ id: "capacity-utilization",
196
+ label: "Capacity Utilization",
197
+ shortLabel: "Capacity use",
198
+ category: "growth",
199
+ seriesId: "TCU",
200
+ transform: "level",
201
+ direction: "higher-is-good",
202
+ axisUnit: "%",
203
+ formatValue: pct,
204
+ reference: null,
205
+ staleAfterMs: MONTHLY,
206
+ note: "How much of industry's capacity is in use, a slack and inflation gauge.",
207
+ limit: 900,
208
+ }),
209
+ stat({
210
+ id: "durable-goods",
211
+ label: "Durable Goods Orders",
212
+ shortLabel: "Durables m/m",
213
+ category: "growth",
214
+ seriesId: "DGORDER",
215
+ transform: "mom",
216
+ direction: "higher-is-good",
217
+ axisUnit: "%",
218
+ formatValue: pct,
219
+ reference: { value: 0, label: "flat" },
220
+ staleAfterMs: MONTHLY,
221
+ note: "New orders for goods meant to last, a forward read on investment.",
222
+ limit: 500,
223
+ }),
224
+ stat({
225
+ id: "factory-orders",
226
+ label: "Factory Orders",
227
+ shortLabel: "Factory m/m",
228
+ category: "growth",
229
+ seriesId: "AMTMNO",
230
+ transform: "mom",
231
+ direction: "higher-is-good",
232
+ axisUnit: "%",
233
+ formatValue: pct,
234
+ reference: { value: 0, label: "flat" },
235
+ staleAfterMs: MONTHLY,
236
+ note: "New orders across manufacturing, durable and not.",
237
+ limit: 500,
238
+ }),
239
+
240
+ // ---- Consumer --------------------------------------------------------
241
+ stat({
242
+ id: "retail-sales",
243
+ label: "Retail Sales",
244
+ shortLabel: "Retail y/y",
245
+ category: "consumer",
246
+ seriesId: "RSAFS",
247
+ transform: "yoy",
248
+ direction: "higher-is-good",
249
+ axisUnit: "%",
250
+ formatValue: pct,
251
+ reference: null,
252
+ staleAfterMs: MONTHLY,
253
+ note: "What households actually spent at retailers, not adjusted for prices.",
254
+ limit: 500,
255
+ }),
256
+ stat({
257
+ id: "core-retail-sales",
258
+ label: "Core Retail Sales",
259
+ shortLabel: "Core retail",
260
+ category: "consumer",
261
+ seriesId: "RSFSXMV",
262
+ transform: "yoy",
263
+ direction: "higher-is-good",
264
+ axisUnit: "%",
265
+ formatValue: pct,
266
+ reference: null,
267
+ staleAfterMs: MONTHLY,
268
+ note: "Retail sales excluding motor vehicles, which swing the headline around.",
269
+ limit: 500,
270
+ }),
271
+ stat({
272
+ id: "consumer-sentiment",
273
+ label: "Consumer Sentiment",
274
+ shortLabel: "Sentiment",
275
+ category: "consumer",
276
+ seriesId: "UMCSENT",
277
+ transform: "level",
278
+ direction: "higher-is-good",
279
+ axisUnit: "",
280
+ formatValue: plain,
281
+ reference: null,
282
+ staleAfterMs: MONTHLY,
283
+ note: "Michigan's survey of how households feel about their finances.",
284
+ limit: 900,
285
+ }),
286
+ stat({
287
+ id: "personal-income",
288
+ label: "Personal Income",
289
+ shortLabel: "Income m/m",
290
+ category: "consumer",
291
+ seriesId: "PI",
292
+ transform: "mom",
293
+ direction: "higher-is-good",
294
+ axisUnit: "%",
295
+ formatValue: pct,
296
+ reference: { value: 0, label: "flat" },
297
+ staleAfterMs: MONTHLY,
298
+ note: "What households received before tax, month on month.",
299
+ limit: 900,
300
+ }),
301
+ stat({
302
+ id: "personal-spending",
303
+ label: "Personal Spending",
304
+ shortLabel: "Spending m/m",
305
+ category: "consumer",
306
+ seriesId: "PCE",
307
+ transform: "mom",
308
+ direction: "higher-is-good",
309
+ axisUnit: "%",
310
+ formatValue: pct,
311
+ reference: { value: 0, label: "flat" },
312
+ staleAfterMs: MONTHLY,
313
+ note: "What households spent, the largest single component of GDP.",
314
+ limit: 900,
315
+ }),
316
+
317
+ // ---- Housing ---------------------------------------------------------
318
+ stat({
319
+ id: "housing-starts",
320
+ label: "Housing Starts",
321
+ shortLabel: "Starts",
322
+ category: "housing",
323
+ seriesId: "HOUST",
324
+ transform: "level",
325
+ direction: "higher-is-good",
326
+ axisUnit: "",
327
+ formatValue: thousands,
328
+ reference: null,
329
+ staleAfterMs: MONTHLY,
330
+ note: "Homes broken ground on, at an annual rate.",
331
+ limit: 900,
332
+ }),
333
+ stat({
334
+ id: "building-permits",
335
+ label: "Building Permits",
336
+ shortLabel: "Permits",
337
+ category: "housing",
338
+ seriesId: "PERMIT",
339
+ transform: "level",
340
+ direction: "higher-is-good",
341
+ axisUnit: "",
342
+ formatValue: thousands,
343
+ reference: null,
344
+ staleAfterMs: MONTHLY,
345
+ note: "Permits issued, which lead starts by a month or two.",
346
+ limit: 900,
347
+ }),
348
+ stat({
349
+ id: "new-home-sales",
350
+ label: "New Home Sales",
351
+ shortLabel: "New homes",
352
+ category: "housing",
353
+ seriesId: "HSN1F",
354
+ transform: "level",
355
+ direction: "higher-is-good",
356
+ axisUnit: "",
357
+ formatValue: thousands,
358
+ reference: null,
359
+ staleAfterMs: MONTHLY,
360
+ note: "Newly built homes sold, at an annual rate.",
361
+ limit: 900,
362
+ }),
363
+ // ---- Rates & money ---------------------------------------------------
364
+ stat({
365
+ id: "fed-funds",
366
+ label: "Federal Funds Rate",
367
+ shortLabel: "Fed funds",
368
+ category: "rates",
369
+ seriesId: "FEDFUNDS",
370
+ transform: "level",
371
+ direction: "neutral",
372
+ axisUnit: "%",
373
+ formatValue: pct2,
374
+ reference: null,
375
+ staleAfterMs: MONTHLY,
376
+ note: "The policy rate the Federal Reserve sets.",
377
+ limit: 900,
378
+ }),
379
+ stat({
380
+ id: "ten-year",
381
+ label: "10-Year Treasury",
382
+ shortLabel: "10Y yield",
383
+ category: "rates",
384
+ seriesId: "DGS10",
385
+ transform: "level",
386
+ direction: "neutral",
387
+ axisUnit: "%",
388
+ formatValue: pct2,
389
+ reference: null,
390
+ staleAfterMs: DAILY,
391
+ note: "The benchmark long rate that discounts almost everything else.",
392
+ limit: 3000,
393
+ }),
394
+ stat({
395
+ id: "two-year",
396
+ label: "2-Year Treasury",
397
+ shortLabel: "2Y yield",
398
+ category: "rates",
399
+ seriesId: "DGS2",
400
+ transform: "level",
401
+ direction: "neutral",
402
+ axisUnit: "%",
403
+ formatValue: pct2,
404
+ reference: null,
405
+ staleAfterMs: DAILY,
406
+ note: "The short end, which tracks where policy is expected to go.",
407
+ limit: 3000,
408
+ }),
409
+ stat({
410
+ id: "curve-spread",
411
+ label: "10Y minus 2Y Spread",
412
+ shortLabel: "2s10s",
413
+ category: "rates",
414
+ seriesId: "T10Y2Y",
415
+ transform: "level",
416
+ direction: "higher-is-good",
417
+ axisUnit: "%",
418
+ formatValue: pct2,
419
+ reference: { value: 0, label: "inversion" },
420
+ staleAfterMs: DAILY,
421
+ note: "The curve spread. Below zero it has preceded every modern recession.",
422
+ limit: 3000,
423
+ }),
424
+ stat({
425
+ id: "real-ten-year",
426
+ label: "10-Year Real Yield",
427
+ shortLabel: "10Y real",
428
+ category: "rates",
429
+ seriesId: "DFII10",
430
+ transform: "level",
431
+ direction: "neutral",
432
+ axisUnit: "%",
433
+ formatValue: pct2,
434
+ reference: { value: 0, label: "zero" },
435
+ staleAfterMs: DAILY,
436
+ note: "The inflation-protected long rate, the real cost of money.",
437
+ limit: 3000,
438
+ }),
439
+ stat({
440
+ id: "m2-yoy",
441
+ label: "M2 Money Stock",
442
+ shortLabel: "M2 y/y",
443
+ category: "rates",
444
+ seriesId: "M2SL",
445
+ transform: "yoy",
446
+ direction: "neutral",
447
+ axisUnit: "%",
448
+ formatValue: pct,
449
+ reference: { value: 0, label: "flat" },
450
+ staleAfterMs: MONTHLY,
451
+ note: "Money supply growth, which contracted outright for the first time in 2022.",
452
+ limit: 900,
453
+ }),
454
+
455
+ // ---- Trade & costs ---------------------------------------------------
456
+ stat({
457
+ id: "trade-balance",
458
+ label: "Trade Balance",
459
+ shortLabel: "Trade balance",
460
+ category: "trade",
461
+ seriesId: "BOPGSTB",
462
+ transform: "level",
463
+ scale: 0.001,
464
+ direction: "higher-is-good",
465
+ axisUnit: "",
466
+ formatValue: billions,
467
+ reference: { value: 0, label: "balanced" },
468
+ staleAfterMs: MONTHLY,
469
+ note: "Goods and services exported less imported, persistently negative.",
470
+ limit: 500,
471
+ }),
472
+ stat({
473
+ id: "productivity",
474
+ label: "Nonfarm Productivity",
475
+ shortLabel: "Productivity",
476
+ category: "trade",
477
+ seriesId: "OPHNFB",
478
+ transform: "qoq-annualized",
479
+ direction: "higher-is-good",
480
+ axisUnit: "%",
481
+ formatValue: pct,
482
+ reference: { value: 0, label: "flat" },
483
+ staleAfterMs: QUARTERLY,
484
+ note: "Output per hour worked. It is what lets pay rise without inflation.",
485
+ limit: 400,
486
+ }),
487
+ stat({
488
+ id: "unit-labor-costs",
489
+ label: "Unit Labor Costs",
490
+ shortLabel: "Labor costs",
491
+ category: "trade",
492
+ seriesId: "ULCNFB",
493
+ transform: "qoq-annualized",
494
+ direction: "higher-is-bad",
495
+ axisUnit: "%",
496
+ formatValue: pct,
497
+ reference: { value: 0, label: "flat" },
498
+ staleAfterMs: QUARTERLY,
499
+ note: "What a unit of output costs in labour, pay growth net of productivity.",
500
+ limit: 400,
501
+ }),
502
+ ];
503
+
504
+ export function findStat(id: string | null | undefined): StatDef {
505
+ const normalized = id?.trim().toLowerCase();
506
+ return STATS.find((entry) => entry.id === normalized) ?? STATS[0]!;
507
+ }
508
+
509
+ export const DEFAULT_STAT_ID = STATS[0]!.id;
510
+
511
+ /** Resolves shortcut arguments like `ECST cpi` onto a statistic. */
512
+ export function resolveStatArg(arg: string | null | undefined): StatDef | null {
513
+ const query = arg?.trim().toLowerCase();
514
+ if (!query) return null;
515
+ return STATS.find((entry) => entry.id === query)
516
+ ?? STATS.find((entry) => entry.seriesId.toLowerCase() === query)
517
+ ?? STATS.find((entry) =>
518
+ entry.id.startsWith(query)
519
+ || entry.label.toLowerCase().startsWith(query)
520
+ || entry.shortLabel.toLowerCase().startsWith(query))
521
+ ?? null;
522
+ }
@@ -0,0 +1,86 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { applyTransform, periodsPerYear } from "./transform";
3
+
4
+ function monthly(values: number[], startYear = 2020): Array<{ date: string; value: number }> {
5
+ return values.map((value, index) => {
6
+ const month = (index % 12) + 1;
7
+ const year = startYear + Math.floor(index / 12);
8
+ return { date: `${year}-${String(month).padStart(2, "0")}-01`, value };
9
+ });
10
+ }
11
+
12
+ describe("periodsPerYear", () => {
13
+ test("reads cadence off the gaps rather than being told", () => {
14
+ expect(periodsPerYear(monthly([1, 2, 3, 4, 5]))).toBe(12);
15
+ expect(periodsPerYear([
16
+ { date: "2024-01-01", value: 1 },
17
+ { date: "2024-04-01", value: 2 },
18
+ { date: "2024-07-01", value: 3 },
19
+ { date: "2024-10-01", value: 4 },
20
+ ])).toBe(4);
21
+ expect(periodsPerYear([
22
+ { date: "2024-01-06", value: 1 },
23
+ { date: "2024-01-13", value: 2 },
24
+ { date: "2024-01-20", value: 3 },
25
+ { date: "2024-01-27", value: 4 },
26
+ ])).toBe(52);
27
+ });
28
+ });
29
+
30
+ describe("applyTransform", () => {
31
+ test("year over year looks back a whole cadence, not one print", () => {
32
+ // 13 monthly points where the index rises 10% across the year.
33
+ const points = monthly([100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 110]);
34
+ const yoy = applyTransform(points, "yoy");
35
+ expect(yoy).toHaveLength(1);
36
+ expect(yoy[0]!.date).toBe("2021-01-01");
37
+ expect(yoy[0]!.value).toBeCloseTo(10, 6);
38
+ });
39
+
40
+ test("month over month uses the previous print", () => {
41
+ const mom = applyTransform(monthly([100, 101]), "mom");
42
+ expect(mom).toHaveLength(1);
43
+ expect(mom[0]!.value).toBeCloseTo(1, 6);
44
+ });
45
+
46
+ test("a quarterly move is stated at the annual rate it implies", () => {
47
+ // Cadence is inferred from the gaps, so it takes a few prints to see quarterly.
48
+ const quarterly = [
49
+ { date: "2024-01-01", value: 100 },
50
+ { date: "2024-04-01", value: 100 },
51
+ { date: "2024-07-01", value: 100 },
52
+ { date: "2024-10-01", value: 101 },
53
+ ];
54
+ // 1% in a quarter compounds to about 4.06% a year, not 4%.
55
+ const value = applyTransform(quarterly, "qoq-annualized").at(-1)!.value;
56
+ expect(value).toBeCloseTo((Math.pow(1.01, 4) - 1) * 100, 6);
57
+ expect(value).toBeGreaterThan(4);
58
+ });
59
+
60
+ test("too few prints to infer cadence falls back to monthly", () => {
61
+ // Two points cannot show a gap pattern, so a year-over-year lag is unknowable.
62
+ expect(applyTransform([
63
+ { date: "2024-01-01", value: 100 },
64
+ { date: "2024-04-01", value: 101 },
65
+ ], "yoy")).toHaveLength(0);
66
+ });
67
+
68
+ test("change is a difference, so it keeps the series' own units", () => {
69
+ const change = applyTransform(monthly([150_000, 150_120]), "change");
70
+ expect(change[0]!.value).toBeCloseTo(120, 6);
71
+ });
72
+
73
+ test("levels pass through and gaps are dropped, not interpolated", () => {
74
+ const level = applyTransform([
75
+ { date: "2024-03-01", value: 2 },
76
+ { date: "2024-01-01", value: null },
77
+ { date: "2024-02-01", value: 1 },
78
+ ], "level");
79
+ // Sorted ascending, with the null month absent rather than filled in.
80
+ expect(level.map((point) => point.date)).toEqual(["2024-02-01", "2024-03-01"]);
81
+ });
82
+
83
+ test("a zero base cannot produce a percent change", () => {
84
+ expect(applyTransform(monthly([0, 5]), "mom")).toHaveLength(0);
85
+ });
86
+ });
@@ -0,0 +1,85 @@
1
+ import type { DatedObservation } from "../shared/series-cache";
2
+
3
+ /**
4
+ * How a raw FRED series becomes the number people quote. An index level like CPI
5
+ * is meaningless on its own; the year-over-year change is the statistic.
6
+ */
7
+ export type StatTransform =
8
+ | "level"
9
+ | "yoy"
10
+ | "mom"
11
+ | "qoq-annualized"
12
+ | "change";
13
+
14
+ export interface StatPoint {
15
+ date: string;
16
+ value: number;
17
+ }
18
+
19
+ const MS_PER_DAY = 86_400_000;
20
+
21
+ /**
22
+ * Infers cadence from the median gap between prints, so a transform does not have
23
+ * to be told whether a series is daily, weekly, monthly or quarterly.
24
+ */
25
+ export function periodsPerYear(observations: readonly DatedObservation[]): number {
26
+ const dates = observations
27
+ .filter((entry) => entry.value != null)
28
+ .map((entry) => Date.parse(entry.date))
29
+ .filter((value) => Number.isFinite(value));
30
+ if (dates.length < 3) return 12;
31
+
32
+ const gaps: number[] = [];
33
+ for (let i = 1; i < dates.length; i += 1) {
34
+ const gap = (dates[i]! - dates[i - 1]!) / MS_PER_DAY;
35
+ if (gap > 0) gaps.push(gap);
36
+ }
37
+ if (gaps.length === 0) return 12;
38
+ gaps.sort((a, b) => a - b);
39
+ const median = gaps[Math.floor(gaps.length / 2)]!;
40
+
41
+ if (median <= 4) return 252;
42
+ if (median <= 10) return 52;
43
+ if (median <= 45) return 12;
44
+ if (median <= 135) return 4;
45
+ return 1;
46
+ }
47
+
48
+ /** Applies a transform, dropping the leading points it cannot look back from. */
49
+ export function applyTransform(
50
+ observations: readonly DatedObservation[],
51
+ transform: StatTransform,
52
+ ): StatPoint[] {
53
+ const clean = observations
54
+ .filter((entry): entry is { date: string; value: number } =>
55
+ entry.value != null && Number.isFinite(entry.value))
56
+ .slice()
57
+ .sort((a, b) => a.date.localeCompare(b.date));
58
+ if (clean.length === 0) return [];
59
+
60
+ if (transform === "level") {
61
+ return clean.map((entry) => ({ date: entry.date, value: entry.value }));
62
+ }
63
+
64
+ const perYear = periodsPerYear(clean);
65
+ const lag = transform === "yoy" ? perYear : 1;
66
+ const out: StatPoint[] = [];
67
+ for (let i = lag; i < clean.length; i += 1) {
68
+ const now = clean[i]!;
69
+ const before = clean[i - lag]!;
70
+ if (transform === "change") {
71
+ out.push({ date: now.date, value: now.value - before.value });
72
+ continue;
73
+ }
74
+ if (before.value === 0) continue;
75
+ const ratio = now.value / before.value;
76
+ if (transform === "qoq-annualized") {
77
+ // A quarterly move stated as the annual rate it implies, as the BEA reports it.
78
+ if (ratio <= 0) continue;
79
+ out.push({ date: now.date, value: (Math.pow(ratio, perYear) - 1) * 100 });
80
+ continue;
81
+ }
82
+ out.push({ date: now.date, value: (ratio - 1) * 100 });
83
+ }
84
+ return out;
85
+ }