gloomberb 0.16.2 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (527) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { Box, useUiCapabilities } from "../../../ui";
3
3
  import { useShortcut } from "../../../react/input";
4
+ import { useRemoteUiNode } from "../../../remote/semantic-tree";
4
5
  import { colors } from "../../../theme/colors";
5
6
  import {
6
7
  EmptyState,
@@ -17,7 +18,9 @@ import {
17
18
  type QueryBarFilter,
18
19
  } from "../../../components";
19
20
  import type { PaneProps } from "../../../types/plugin";
20
- import { useFxRatesMap } from "../../../market-data/hooks";
21
+ import { useChartQueries, useFxRatesMap, useResolvedEntryValue } from "../../../market-data/hooks";
22
+ import { buildChartKey } from "../../../market-data/selectors";
23
+ import { clipPriceHistoryToRange } from "../../../time-series/history-window";
21
24
  import { useLiveTickerFinancials, useLiveTickerFinancialsMap } from "../../../state/hooks/live-ticker-financials";
22
25
  import { buildPortfolioFinancialsMap } from "../../../market-data/portfolio-financials";
23
26
  import { convertCurrency, formatCurrency } from "../../../utils/format";
@@ -27,6 +30,7 @@ import {
27
30
  getFocusedTickerSymbol,
28
31
  useAppSelector,
29
32
  usePaneInstance,
33
+ usePaneSettingValue,
30
34
  usePaneStateValue,
31
35
  usePaneAppConfig,
32
36
  } from "../../../state/app/context";
@@ -35,6 +39,7 @@ import { getSharedRegistry } from "../../registry";
35
39
  import { resolveTickerOpenTarget } from "../../../tickers/open-target";
36
40
  import { calculatePortfolioSummaryTotals } from "../portfolio-list/metrics";
37
41
  import { resolvePortfolioNetLiquidation } from "../portfolio-list/account-metrics";
42
+ import { resolvePortfolioCash } from "../portfolio-list/allocation";
38
43
  import {
39
44
  buildTrackedCurrencies,
40
45
  getCollectionTickersFromConfig,
@@ -50,12 +55,20 @@ import {
50
55
  } from "./model";
51
56
  import {
52
57
  DEFAULT_KELLY_DRAFTS,
53
- KELLY_MODES,
58
+ KELLY_PANE_MODES,
59
+ type KellyPaneMode,
54
60
  type KellySizerDraft,
55
61
  type KellySizerModeDrafts,
56
- type KellySizingMode,
57
62
  type PredictionMarketKellyAssumptions,
58
63
  } from "./types";
64
+ import {
65
+ clampKellyHistoryYears,
66
+ estimateKellyHistoryInputs,
67
+ KELLY_HISTORY_DEFAULT_YEARS,
68
+ KELLY_HISTORY_MIN_PERIODS,
69
+ kellyHistoryRange,
70
+ kellyHistoryRequest,
71
+ } from "./history";
59
72
  import { KELLY_PANE_ID } from "./constants";
60
73
  import {
61
74
  buildKellyCurveXAxisLabels,
@@ -69,9 +82,10 @@ import type { StaticChartXMarker } from "../../../components/chart/static";
69
82
  import {
70
83
  KellyCurveSection,
71
84
  KellySensitivitySection,
85
+ buildKellyHistoryItems,
72
86
  buildKellyResultItems,
73
87
  } from "./sections";
74
- import { getPortfolioPositionValue, resolveActivePortfolioId } from "./portfolio";
88
+ import { getPortfolioPositionValue, resolveActivePortfolioId, resolveKellyBankroll } from "./portfolio";
75
89
  import { useKellyCommonAssumptions } from "./state";
76
90
 
77
91
  export function KellySizerPane({ focused, width, height }: PaneProps) {
@@ -94,7 +108,13 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
94
108
  const commandBarOpen = useAppSelector((state) => state.commandBarOpen);
95
109
  const { nativePaneChrome } = useUiCapabilities();
96
110
 
97
- const [mode, setMode] = usePaneStateValue<KellySizingMode>("mode", "binary");
111
+ // A pane opened from a KELLY report (fn, shot) sizes from the ticker's own history, as the report does.
112
+ const [reportYears] = usePaneSettingValue<number | null>("historyYears", null);
113
+ const [reportBankroll] = usePaneSettingValue<number | null>("bankroll", null);
114
+ const [mode, setMode] = usePaneStateValue<KellyPaneMode>("mode", reportYears != null ? "history" : "binary");
115
+ const [historyYears, setHistoryYears] = usePaneStateValue<number>("historyYears", clampKellyHistoryYears(reportYears ?? KELLY_HISTORY_DEFAULT_YEARS));
116
+ // History reads inputs off the ticker's monthly returns and sizes them as a binary bet.
117
+ const sizingMode = mode === "history" ? "binary" : mode;
98
118
  // A narrow terminal bar has no room for the view switch beside the ticker,
99
119
  // price and account (plus the Side and Portfolio filters when shown), so
100
120
  // there `s` stays a footer hint.
@@ -104,7 +124,10 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
104
124
  const [drafts, setDrafts] = usePaneStateValue<KellySizerModeDrafts>("drafts", cloneKellyDrafts());
105
125
  const [showSensitivity, setShowSensitivity] = usePaneStateValue<boolean>("showSensitivity", false);
106
126
  const [selectedPortfolioId, setSelectedPortfolioId] = usePaneStateValue<string | null>("portfolioId", null);
107
- const [bankrollOverride, setBankrollOverride] = usePaneStateValue<number | null>("bankrollOverride", null);
127
+ const [bankrollOverride, setBankrollOverride] = usePaneStateValue<number | null>(
128
+ "bankrollOverride",
129
+ typeof reportBankroll === "number" && reportBankroll > 0 ? reportBankroll : null,
130
+ );
108
131
  const [currentValueOverride, setCurrentValueOverride] = usePaneStateValue<number | null>("currentValueOverride", null);
109
132
  const [selectedFieldIndex, setSelectedFieldIndex] = useState(0);
110
133
  const [activeInputId, setActiveInputId] = useState<string | null>(null);
@@ -214,9 +237,11 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
214
237
  ? portfolioFinancials.get(requestedSymbol!) ?? null
215
238
  : financials;
216
239
  const { accountState } = usePortfolioAccountState(activePortfolio, { brokerAccounts, config });
240
+ // Cash entered by hand counts toward a manual portfolio's bankroll; a broker's is in its Net Liq.
241
+ const manualCash = accountState ? null : resolvePortfolioCash(activePortfolio, null);
217
242
  const trackedCurrencies = useMemo(
218
- () => buildTrackedCurrencies(portfolioTickers, portfolioFinancials, accountState, config.baseCurrency),
219
- [accountState, config.baseCurrency, portfolioFinancials, portfolioTickers],
243
+ () => buildTrackedCurrencies(portfolioTickers, portfolioFinancials, accountState, config.baseCurrency, manualCash?.currency),
244
+ [accountState, config.baseCurrency, manualCash?.currency, portfolioFinancials, portfolioTickers],
220
245
  );
221
246
  const exchangeRates = useFxRatesMap(trackedCurrencies);
222
247
  const portfolioSummary = useMemo(
@@ -237,9 +262,12 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
237
262
  (value) => convertCurrency(value, accountState?.account.currency ?? "", config.baseCurrency, exchangeRates),
238
263
  accountState?.snapshotBasis,
239
264
  );
240
- const sourceBankroll = netLiquidation != null && Number.isFinite(netLiquidation)
241
- ? netLiquidation
242
- : portfolioSummary.totalMktValue ?? 0;
265
+ const sourceBankroll = resolveKellyBankroll({
266
+ netLiquidation,
267
+ brokerPortfolio: !!activePortfolio?.brokerInstanceId,
268
+ holdings: portfolioSummary.allocationHoldings ?? [],
269
+ cashValue: manualCash ? convertCurrency(manualCash.amount, manualCash.currency, config.baseCurrency, exchangeRates) : null,
270
+ });
243
271
  const sourceCurrentValue = getPortfolioPositionValue({
244
272
  ticker,
245
273
  financials: positionFinancials,
@@ -250,26 +278,57 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
250
278
  const bankroll = bankrollOverride ?? sourceBankroll;
251
279
  const currentValue = currentValueOverride ?? sourceCurrentValue;
252
280
  const price = positionFinancials?.quote?.price ?? null;
253
- const rawActiveDraft = drafts[mode] ?? DEFAULT_KELLY_DRAFTS[mode];
254
- const { commonAssumptions, updateCommon } = useKellyCommonAssumptions(rawActiveDraft);
255
- const activeDraft = useMemo(
256
- () => applyKellyCommonAssumptions(rawActiveDraft, commonAssumptions),
257
- [commonAssumptions, rawActiveDraft],
281
+ // The same daily closes, through the same request, as the KELLY report reads.
282
+ const historyRequest = useMemo(
283
+ () => mode === "history" && ticker
284
+ ? kellyHistoryRequest({ symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange ?? "" }, historyYears)
285
+ : null,
286
+ [historyYears, mode, ticker?.metadata.exchange, ticker?.metadata.ticker],
287
+ );
288
+ const historyEntries = useChartQueries(historyRequest ? [historyRequest] : []);
289
+ const historyEntry = historyRequest ? historyEntries.get(buildChartKey(historyRequest)) ?? null : null;
290
+ const historyPoints = useResolvedEntryValue(historyEntry);
291
+ const historyInputs = useMemo(
292
+ () => historyPoints
293
+ ? estimateKellyHistoryInputs(clipPriceHistoryToRange(historyPoints, kellyHistoryRange(historyYears)), historyYears)
294
+ : null,
295
+ [historyPoints, historyYears],
258
296
  );
297
+ const rawActiveDraft = drafts[sizingMode] ?? DEFAULT_KELLY_DRAFTS[sizingMode];
298
+ const { commonAssumptions, updateCommon } = useKellyCommonAssumptions(rawActiveDraft);
299
+ const activeDraft = useMemo(() => {
300
+ const draft = applyKellyCommonAssumptions(rawActiveDraft, commonAssumptions);
301
+ if (mode !== "history") return draft;
302
+ // Until the history loads there is no edge to size.
303
+ return {
304
+ ...draft,
305
+ winProbability: historyInputs?.winProbability ?? 0,
306
+ upsideReturn: historyInputs?.upsideReturn ?? 0,
307
+ downsideReturn: historyInputs?.downsideReturn ?? 0,
308
+ };
309
+ }, [commonAssumptions, historyInputs, mode, rawActiveDraft]);
259
310
 
260
311
  const updateDraft = useCallback((patch: Partial<KellySizerDraft>) => {
261
312
  setDrafts((current) => ({
262
313
  ...cloneKellyDrafts(current),
263
- [mode]: {
264
- ...(current[mode] ?? DEFAULT_KELLY_DRAFTS[mode]),
314
+ [sizingMode]: {
315
+ ...(current[sizingMode] ?? DEFAULT_KELLY_DRAFTS[sizingMode]),
265
316
  ...patch,
266
317
  } as KellySizerDraft,
267
318
  }));
268
- }, [mode, setDrafts]);
319
+ }, [sizingMode, setDrafts]);
269
320
 
270
- const fields = useMemo(
271
- () => buildModeFields({ mode, draft: activeDraft, updateDraft }),
272
- [activeDraft, mode, updateDraft],
321
+ const fields = useMemo<GridField[]>(
322
+ () => mode === "history"
323
+ ? [{
324
+ id: "history:years",
325
+ label: "Years",
326
+ value: historyYears,
327
+ valueText: String(historyYears),
328
+ onValue: (value) => setHistoryYears(clampKellyHistoryYears(value)),
329
+ }]
330
+ : buildModeFields({ mode: sizingMode, draft: activeDraft, updateDraft }),
331
+ [activeDraft, historyYears, mode, setHistoryYears, sizingMode, updateDraft],
273
332
  );
274
333
  const commonFields = useMemo(
275
334
  () => buildCommonFields({ common: commonAssumptions, updateCommon }),
@@ -301,35 +360,35 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
301
360
  const selectedField = gridFields[Math.min(selectedFieldIndex, Math.max(0, gridFields.length - 1))] ?? null;
302
361
  const result = useMemo(
303
362
  () => calculateKellySizing({
304
- mode,
363
+ mode: sizingMode,
305
364
  draft: activeDraft,
306
365
  bankroll,
307
366
  currentValue,
308
367
  price,
309
368
  }),
310
- [activeDraft, bankroll, currentValue, mode, price],
369
+ [activeDraft, bankroll, currentValue, price, sizingMode],
311
370
  );
312
- const sensitivity = useMemo(() => buildSensitivityGrid(mode, activeDraft), [activeDraft, mode]);
371
+ const sensitivity = useMemo(() => buildSensitivityGrid(sizingMode, activeDraft), [activeDraft, sizingMode]);
313
372
  const curveMaxFraction = useMemo(
314
- () => getKellyCurveMaxFraction(mode, activeDraft, [
373
+ () => getKellyCurveMaxFraction(sizingMode, activeDraft, [
315
374
  result.currentFraction,
316
375
  result.clippedFraction,
317
376
  result.fullKellyFraction,
318
377
  ]),
319
- [activeDraft, mode, result.clippedFraction, result.currentFraction, result.fullKellyFraction],
378
+ [activeDraft, sizingMode, result.clippedFraction, result.currentFraction, result.fullKellyFraction],
320
379
  );
321
380
  const curvePoints = useMemo(
322
- () => buildKellyCurvePoints(mode, activeDraft, curveMaxFraction),
323
- [activeDraft, curveMaxFraction, mode],
381
+ () => buildKellyCurvePoints(sizingMode, activeDraft, curveMaxFraction),
382
+ [activeDraft, curveMaxFraction, sizingMode],
324
383
  );
325
384
  const curveXAxisLabels = useMemo(() => buildKellyCurveXAxisLabels(curveMaxFraction), [curveMaxFraction]);
326
385
  const currentGrowth = useMemo(
327
- () => calculateExpectedLogGrowthAtFraction(mode, activeDraft, result.currentFraction),
328
- [activeDraft, mode, result.currentFraction],
386
+ () => calculateExpectedLogGrowthAtFraction(sizingMode, activeDraft, result.currentFraction),
387
+ [activeDraft, sizingMode, result.currentFraction],
329
388
  );
330
389
  const targetGrowth = useMemo(
331
- () => calculateExpectedLogGrowthAtFraction(mode, activeDraft, result.clippedFraction),
332
- [activeDraft, mode, result.clippedFraction],
390
+ () => calculateExpectedLogGrowthAtFraction(sizingMode, activeDraft, result.clippedFraction),
391
+ [activeDraft, sizingMode, result.clippedFraction],
333
392
  );
334
393
  const curveMarkers = useMemo<StaticChartXMarker[]>(() => {
335
394
  if (!Number.isFinite(curveMaxFraction) || curveMaxFraction <= 0) return [];
@@ -400,8 +459,21 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
400
459
  }
401
460
  }, { enabled: focused });
402
461
 
462
+ // A report or shot of the History tab is as of the last daily close it read, not when it ran.
463
+ useRemoteUiNode(mode === "history" && historyInputs ? {
464
+ role: "report-freshness",
465
+ label: "History freshness",
466
+ getMetadata: () => ({ asOf: historyInputs.end }),
467
+ } : null);
468
+ // History mode says what it is waiting for, or why it has no inputs, before any sizing warning.
469
+ const historyStatus = mode !== "history" || historyInputs ? null
470
+ : historyEntry?.error ? { text: historyEntry.error.message, tone: "negative" as const }
471
+ : historyPoints ? { text: `under ${KELLY_HISTORY_MIN_PERIODS} monthly returns`, tone: "warning" as const }
472
+ : { text: "loading history", tone: "muted" as const };
403
473
  usePaneFooter(KELLY_PANE_ID, () => ({
404
- info: bankroll <= 0 && ticker
474
+ info: historyStatus
475
+ ? [{ id: "history", parts: [historyStatus] }]
476
+ : bankroll <= 0 && ticker
405
477
  // Every size reads 0% until there is a bankroll; say so where status lives.
406
478
  ? [{ id: "bankroll", parts: [{ text: "no bankroll", tone: "warning" as const }] }]
407
479
  : result.warnings.length > 0
@@ -415,15 +487,15 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
415
487
  { id: "search", key: "/", label: "search", onPress: focusTickerSearch },
416
488
  ...(viewInBar ? [] : [{ id: "sensitivity", key: "s", label: showSensitivity ? "ensitivity off" : "ensitivity", onPress: toggleSensitivity }]),
417
489
  ],
418
- }), [activeInputId, bankroll, editSelectedField, focusTickerSearch, result.clipReasons, result.warnings, selectedField?.id, selectedField?.label, showSensitivity, ticker, toggleSensitivity, viewInBar]);
490
+ }), [activeInputId, bankroll, editSelectedField, focusTickerSearch, historyStatus?.text, historyStatus?.tone, result.clipReasons, result.warnings, selectedField?.id, selectedField?.label, showSensitivity, ticker, toggleSensitivity, viewInBar]);
419
491
 
420
492
  const portfolioTabs = useMemo(
421
493
  () => config.portfolios.map((portfolio) => ({ label: portfolio.name, value: portfolio.id })),
422
494
  [config.portfolios],
423
495
  );
424
- const modeTabs = useMemo(() => KELLY_MODES.map((entry) => ({ label: entry.label, value: entry.id })), []);
496
+ const modeTabs = useMemo(() => KELLY_PANE_MODES.map((entry) => ({ label: entry.label, value: entry.id })), []);
425
497
  const selectMode = (nextMode: string) => {
426
- setMode(nextMode as KellySizingMode);
498
+ setMode(nextMode as KellyPaneMode);
427
499
  setSelectedFieldIndex(0);
428
500
  activateInput(null);
429
501
  };
@@ -436,14 +508,18 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
436
508
  });
437
509
  const gridColumns = fieldGridColumns(width);
438
510
  const gridRows = fieldGridRows(gridFields, gridColumns);
439
- const resultItems = buildKellyResultItems({
440
- result,
441
- baseCurrency: config.baseCurrency,
442
- currentGrowth,
443
- targetGrowth,
444
- width,
445
- columns: gridColumns,
446
- });
511
+ const resultItems = [
512
+ // History's inputs lead: the window and the monthly figures the sizes come from.
513
+ ...(mode === "history" && historyInputs ? buildKellyHistoryItems(historyInputs) : []),
514
+ ...buildKellyResultItems({
515
+ result,
516
+ baseCurrency: config.baseCurrency,
517
+ currentGrowth,
518
+ targetGrowth,
519
+ width,
520
+ columns: gridColumns,
521
+ }),
522
+ ];
447
523
  const resultRows = statGridRows(resultItems, width, gridColumns);
448
524
  // Query bar, the mode strip when it is not in the title bar, inputs, results.
449
525
  const bodyRows = Math.max(0, height - 1 - tabRows - gridRows - resultRows);
@@ -2,6 +2,7 @@ import type { AppConfig } from "../../../types/config";
2
2
  import type { TickerFinancials } from "../../../types/financials";
3
3
  import type { TickerRecord } from "../../../types/ticker";
4
4
  import { convertCurrency } from "../../../utils/format";
5
+ import { allocationTotal, type AllocationHolding } from "../portfolio-list/allocation";
5
6
  import { getCollectionTypeFromConfig } from "../portfolio-list/pane/data";
6
7
  import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue } from "../portfolio-list/position-metrics";
7
8
 
@@ -41,6 +42,30 @@ export function getPortfolioPositionValue({
41
42
  ?? (metrics.positionCount > 0 ? Number.NaN : 0);
42
43
  }
43
44
 
45
+ /**
46
+ * What the portfolio is worth to bet with: its equity. A broker account's is
47
+ * its Net Liq. Otherwise it is the priced holdings at net value plus the cash
48
+ * entered for the portfolio, the portfolio header's Total. A broker portfolio
49
+ * with neither has no known equity: its positions alone are its gross, which
50
+ * margin puts above the equity, so it gives no bankroll.
51
+ */
52
+ export function resolveKellyBankroll({
53
+ netLiquidation,
54
+ brokerPortfolio,
55
+ holdings,
56
+ cashValue,
57
+ }: {
58
+ netLiquidation: number | null;
59
+ brokerPortfolio: boolean;
60
+ holdings: readonly AllocationHolding[];
61
+ /** Cash entered by hand, in the bankroll currency; NaN when it cannot be converted. */
62
+ cashValue: number | null;
63
+ }): number {
64
+ if (netLiquidation != null && Number.isFinite(netLiquidation)) return netLiquidation;
65
+ if (brokerPortfolio && cashValue == null) return 0;
66
+ return allocationTotal(holdings, cashValue) ?? 0;
67
+ }
68
+
44
69
  export function resolveActivePortfolioId({
45
70
  requestedPortfolioId,
46
71
  activeCollectionId,
@@ -13,11 +13,25 @@ import { colors, priceColor } from "../../../theme/colors";
13
13
  import { Box } from "../../../ui";
14
14
  import { displayWidth, formatCurrency, formatNumber } from "../../../utils/format";
15
15
  import type { KellySizingResult, SensitivityGrid } from "./types";
16
+ import type { KellyHistoryInputs } from "./history";
16
17
  import { formatPct, formatSignedPct } from "./view";
17
18
 
18
19
  /** The longest result label ("Full growth"), which sets the band's label column. */
19
20
  const RESULT_LABEL_CHARS = 11;
20
21
 
22
+ /** The inputs History reads off the ticker's monthly returns, ahead of the sizes. */
23
+ export function buildKellyHistoryItems(inputs: KellyHistoryInputs): StatItem[] {
24
+ const payoff = inputs.downsideReturn < 0 ? inputs.upsideReturn / -inputs.downsideReturn : null;
25
+ return [
26
+ // The dates take a row of their own so a narrow pane never cuts them.
27
+ { id: "window", label: "Window", value: `${inputs.start} to ${inputs.end}`, detail: `${inputs.periods} monthly returns`, wide: true },
28
+ { id: "winRate", label: "Win rate", value: formatPct(inputs.winProbability, 1), detail: `${inputs.wins}/${inputs.periods}` },
29
+ { id: "avgGain", label: "Avg gain", value: formatSignedPct(inputs.upsideReturn), color: priceColor(inputs.upsideReturn) },
30
+ { id: "avgLoss", label: "Avg loss", value: formatSignedPct(inputs.downsideReturn), color: priceColor(inputs.downsideReturn) },
31
+ { id: "payoff", label: "Payoff", value: payoff == null ? "—" : `${formatNumber(payoff, 2)}x` },
32
+ ];
33
+ }
34
+
21
35
  /**
22
36
  * The sizing result as one band under the inputs, laid out in the FieldGrid's
23
37
  * columns: the Kelly fractions, then the move in money, then risk, then log
@@ -105,7 +105,7 @@ export interface SensitivityGrid {
105
105
  cells: SensitivityGridCell[][];
106
106
  }
107
107
 
108
- export const KELLY_MODES: Array<{ id: KellySizingMode; label: string }> = [
108
+ const KELLY_MODES: Array<{ id: KellySizingMode; label: string }> = [
109
109
  { id: "binary", label: "Binary" },
110
110
  { id: "scenario", label: "Scenario" },
111
111
  { id: "risk-budget", label: "Risk" },
@@ -113,6 +113,14 @@ export const KELLY_MODES: Array<{ id: KellySizingMode; label: string }> = [
113
113
  { id: "asymmetric", label: "Asym" },
114
114
  ];
115
115
 
116
+ /** The pane's tabs: the sizing modes, and History, which reads a binary bet off the ticker's monthly returns. */
117
+ export type KellyPaneMode = KellySizingMode | "history";
118
+
119
+ export const KELLY_PANE_MODES: Array<{ id: KellyPaneMode; label: string }> = [
120
+ ...KELLY_MODES,
121
+ { id: "history", label: "History" },
122
+ ];
123
+
116
124
  export const DEFAULT_KELLY_COMMON_ASSUMPTIONS: KellyCommonAssumptions = {
117
125
  kellyFraction: 0.25,
118
126
  maxNameFraction: 0.08,
@@ -27,24 +27,32 @@ import {
27
27
 
28
28
  let dispatchRef: ((action: AppAction) => void) | null = null;
29
29
  let getStateRef: (() => { layout: LayoutConfig; termWidth: number; termHeight: number; focusedPaneId: string | null }) | null = null;
30
+ let restoreHiddenRef: ((layout: LayoutConfig) => LayoutConfig) | null = null;
30
31
 
32
+ /**
33
+ * `getState().layout` is the layout on screen. `restoreHidden` puts the panes
34
+ * it leaves out back into an edit of it before the edit is saved.
35
+ */
31
36
  export function setLayoutManagerDispatch(
32
37
  dispatch: (action: AppAction) => void,
33
38
  getState: () => { layout: LayoutConfig; termWidth: number; termHeight: number; focusedPaneId: string | null },
39
+ restoreHidden: (layout: LayoutConfig) => LayoutConfig,
34
40
  ) {
35
41
  dispatchRef = dispatch;
36
42
  getStateRef = getState;
43
+ restoreHiddenRef = restoreHidden;
37
44
  }
38
45
 
39
46
  function clearLayoutManagerDispatch() {
40
47
  dispatchRef = null;
41
48
  getStateRef = null;
49
+ restoreHiddenRef = null;
42
50
  }
43
51
 
44
52
  function persistLayout(layout: LayoutConfig) {
45
53
  if (!dispatchRef) return;
46
54
  dispatchRef({ type: "PUSH_LAYOUT_HISTORY" });
47
- dispatchRef({ type: "UPDATE_LAYOUT", layout });
55
+ dispatchRef({ type: "UPDATE_LAYOUT", layout: restoreHiddenRef ? restoreHiddenRef(layout) : layout });
48
56
  }
49
57
 
50
58
  function getFocusedPane(layout: LayoutConfig, focusedPaneId: string | null) {
@@ -1,3 +1,5 @@
1
+ import { apiClient, type CloudMacroReleaseDaysPayload } from "../../../api-client";
2
+ import { createPluginCache } from "../../../data/plugin-cache";
1
3
  import { getSharedMarketDataCoordinator, MarketDataCoordinator, resolveEntryValue } from "../../../market-data/coordinator";
2
4
  import type { InstrumentRef } from "../../../market-data/request-types";
3
5
  import { tickerHasListingSuffix } from "../../../sources/listing-symbols";
@@ -6,6 +8,28 @@ import type { PricePoint } from "../../../types/financials";
6
8
  import { abortable, abortError } from "../../../utils/async-deadline";
7
9
  import { errorMessage } from "../../../utils/errors";
8
10
  import { resolveExchangeTimeZone } from "../../../utils/exchanges";
11
+ import { bundledMacroReleases, mergeMacroReleases, type MacroReleaseList } from "./releases";
12
+
13
+ export const macroReleaseCache = createPluginCache<CloudMacroReleaseDaysPayload>({
14
+ kind: "macro-release-days", source: "gloom-cloud", schemaVersion: 1,
15
+ // A release lands at 8:30 or 14:00 ET; the last list read is kept for a terminal that goes offline.
16
+ policy: { staleMs: 30 * 60_000, expireMs: 90 * 86_400_000 },
17
+ });
18
+
19
+ /**
20
+ * The bundled list, extended by the server's when it reaches further. A
21
+ * failed read keeps the last list read; with none, the bundled list alone.
22
+ */
23
+ export async function loadMacroReleases(options: { force?: boolean } = {},
24
+ read: () => Promise<CloudMacroReleaseDaysPayload> = () => apiClient.getCloudMacroReleaseDays()): Promise<MacroReleaseList> {
25
+ const floor = bundledMacroReleases();
26
+ try {
27
+ const result = await macroReleaseCache.load("release-days", read, { force: options.force });
28
+ return mergeMacroReleases(floor, result.data);
29
+ } catch {
30
+ return floor;
31
+ }
32
+ }
9
33
 
10
34
  export interface MacroDayHistory {
11
35
  history: PricePoint[];
@@ -1,8 +1,8 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
2
  import { resolveHeadlessInstrument } from "../shared/headless-market-data";
3
- import { loadMacroDayHistory } from "./client";
3
+ import { loadMacroDayHistory, loadMacroReleases } from "./client";
4
4
  import { projectMacroDays, type MacroDayStats } from "./model";
5
- import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, MACRO_RELEASE_BASIS } from "./releases";
5
+ import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, MACRO_RELEASE_SOURCES, staleMacroReleasesNotice } from "./releases";
6
6
 
7
7
  // A mean that rounds to zero reads 0.00%, never -0.00%.
8
8
  const percent = (value: unknown) => typeof value !== "number" ? "--" : /[1-9]/.test((value * 100).toFixed(2)) ? `${(value * 100).toFixed(2)}%` : "0.00%";
@@ -11,12 +11,12 @@ const share = (value: unknown) => typeof value === "number" ? `${Math.round(valu
11
11
 
12
12
  export const macroDayHeadless: HeadlessPaneDefinition<"bundle"> = {
13
13
  shape: "bundle", argument: { kind: "ticker", description: "Ticker" },
14
- // Dated by the maintained release list as much as by the closes, so no session schedule applies.
14
+ // Dated by the release list (releasesThrough) as much as by the closes, so no session schedule applies.
15
15
  freshness: { status: "not-a-feed", basis: "release-day study on daily closes" },
16
16
  describe: (args) => `MDAY ${args.symbols[0] ?? ""}`,
17
17
  discovery: { screenshotReadiness: "live-dom", limitations: [
18
18
  "US listings; close-to-close on the release day.",
19
- `Published CPI, jobs and FOMC days through ${MACRO_RELEASE_BASIS.coveredThrough}.`,
19
+ "Published CPI, jobs and FOMC days; sessions after the list's last covered day (releasesThrough) are left out.",
20
20
  ] },
21
21
  options: [
22
22
  { key: "release", type: "enum", values: [{ value: "all" }, ...MACRO_EVENT_KINDS.map((value) => ({ value }))], defaultValue: "all",
@@ -25,9 +25,11 @@ export const macroDayHeadless: HeadlessPaneDefinition<"bundle"> = {
25
25
  ],
26
26
  async load(args, ctx) {
27
27
  const instrument = await resolveHeadlessInstrument(ctx, args.symbols[0]!);
28
- const history = await loadMacroDayHistory({ instrument, signal: ctx.signal }, ctx.marketData);
28
+ const [history, releases] = await Promise.all([loadMacroDayHistory({ instrument, signal: ctx.signal }, ctx.marketData), loadMacroReleases()]);
29
29
  ctx.signal.throwIfAborted();
30
- const model = projectMacroDays(history.history, { symbol: instrument.symbol, lookbackYears: Number(args.options.lookbackYears) || 5 });
30
+ const model = projectMacroDays(history.history, { symbol: instrument.symbol, lookbackYears: Number(args.options.lookbackYears) || 5,
31
+ releases: releases.releases, coveredThrough: releases.coveredThrough });
32
+ const releasesNotice = staleMacroReleasesNotice(releases);
31
33
  const row = (label: string, stats: MacroDayStats) => ({ label, ...stats });
32
34
  const release = String(args.options.release ?? "all");
33
35
  const events = release === "all" ? model.events : model.events.filter((event) => event.kind === release);
@@ -42,11 +44,12 @@ export const macroDayHeadless: HeadlessPaneDefinition<"bundle"> = {
42
44
  { key: "session", header: "Session" }, { key: "move", header: "Move", format: percent }, { key: "multiple", header: "vs normal", format: multiple }],
43
45
  rows: events.map((event) => ({ ...event, event: MACRO_EVENT_LABELS[event.kind] })) },
44
46
  ],
45
- complete: !history.stale && !history.error && model.events.length > 0,
47
+ complete: !history.stale && !history.error && !releasesNotice && model.events.length > 0,
46
48
  unavailableSymbols: model.events.length ? [] : [instrument.symbol],
47
49
  errors: history.error ? [history.error] : [],
50
+ ...(releasesNotice ? { notes: [releasesNotice] } : {}),
48
51
  metadata: { unit: "decimal return, close to close on the release day", start: model.start, asOf: model.asOf,
49
- releasesThrough: MACRO_RELEASE_BASIS.coveredThrough, releaseSources: MACRO_RELEASE_BASIS.sources,
52
+ releasesThrough: releases.coveredThrough, releaseSources: Object.values(MACRO_RELEASE_SOURCES),
50
53
  stale: history.stale, fetchedAt: history.fetchedAt, methodology: "docs/research-data.md#macro-day-reaction" },
51
54
  };
52
55
  },
@@ -1,5 +1,6 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
3
+ import { macroReleaseCache } from "./client";
3
4
  import { macroDayHeadless } from "./headless";
4
5
  import { LOOKBACK_OPTIONS, MacroDayPane } from "./pane";
5
6
 
@@ -18,4 +19,6 @@ export const macroDayModule: PluginModule = {
18
19
  keywords: ["mday", "macro day", "cpi day", "fomc day", "jobs day", "nfp", "payrolls", "event study", "release reaction"],
19
20
  shortcut: "MDAY", publicShare: true,
20
21
  }), headless: macroDayHeadless }],
22
+ setup(ctx) { macroReleaseCache.attach(ctx.persistence); },
23
+ dispose() { macroReleaseCache.reset(); },
21
24
  };
@@ -1,6 +1,6 @@
1
1
  import type { PricePoint } from "../../../types/financials";
2
2
  import { zonedDateTimeParts } from "../../../utils/zoned-date-time";
3
- import { MACRO_EVENT_KINDS, MACRO_RELEASE_BASIS, macroReleases, type MacroEventKind, type MacroRelease } from "./releases";
3
+ import { MACRO_EVENT_KINDS, type MacroEventKind, type MacroRelease } from "./releases";
4
4
 
5
5
  const DAY_MS = 86_400_000;
6
6
  /** Longest calendar gap between two closes that is still one session apart: Thursday to Monday over a Friday holiday. */
@@ -71,10 +71,11 @@ function stats(moves: number[], normalAbs: number | null): MacroDayStats {
71
71
  export function projectMacroDays(history: readonly PricePoint[], options: {
72
72
  symbol: string;
73
73
  lookbackYears: number;
74
- releases?: readonly MacroRelease[];
75
- coveredThrough?: string;
74
+ releases: readonly MacroRelease[];
75
+ /** The release list's last covered day. */
76
+ coveredThrough: string;
76
77
  }): MacroDayModel {
77
- const coveredThrough = options.coveredThrough ?? MACRO_RELEASE_BASIS.coveredThrough;
78
+ const { coveredThrough } = options;
78
79
  const byDate = new Map<string, number>();
79
80
  for (const point of history) {
80
81
  const time = new Date(point.date).getTime();
@@ -90,7 +91,11 @@ export function projectMacroDays(history: readonly PricePoint[], options: {
90
91
  // Sessions after the published lists end may be unlisted release days, so they are left out of both sides.
91
92
  const end = last < coveredThrough ? last : coveredThrough;
92
93
  const startYear = Number(end.slice(0, 4)) - Math.max(1, options.lookbackYears);
93
- const start = `${startYear}${end.slice(4)}`;
94
+ const nominalStart = `${startYear}${end.slice(4)}`;
95
+ // The history can begin after the nominal start (daily bars reach back five years from today, the list's end is older).
96
+ // The first bar has no close before it, so the window opens there: a release before it falls outside the stated start.
97
+ const firstSession = sessions[0]!;
98
+ const start = firstSession > nominalStart ? firstSession : nominalStart;
94
99
  /** Session date -> close-to-close move from the session before, when the two are one session apart. */
95
100
  const moves = new Map<string, number>();
96
101
  for (let index = 1; index < sessions.length; index++) {
@@ -101,7 +106,7 @@ export function projectMacroDays(history: readonly PricePoint[], options: {
101
106
 
102
107
  const reactionSessions = new Set<string>();
103
108
  const found: Omit<MacroDayEvent, "multiple">[] = [];
104
- for (const release of options.releases ?? macroReleases()) {
109
+ for (const release of options.releases) {
105
110
  if (release.date <= start || release.date > end || !isWeekday(release.date)) continue;
106
111
  const at = sessions.findIndex((date) => date >= release.date);
107
112
  if (at < 1) continue;
@@ -9,9 +9,9 @@ import { useAutoRefresh } from "../../../react/auto-refresh";
9
9
  import { useThemeColors } from "../../../theme/theme-context";
10
10
  import type { PaneProps } from "../../../types/plugin";
11
11
  import { Box } from "../../../ui";
12
- import { loadMacroDayHistory } from "./client";
12
+ import { loadMacroDayHistory, loadMacroReleases } from "./client";
13
13
  import { projectMacroDays, type MacroDayEvent, type MacroDayModel } from "./model";
14
- import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, type MacroEventKind } from "./releases";
14
+ import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, staleMacroReleasesNotice, type MacroEventKind } from "./releases";
15
15
 
16
16
  export const LOOKBACK_OPTIONS = [{ value: "1", label: "1Y" }, { value: "2", label: "2Y" }, { value: "3", label: "3Y" }, { value: "5", label: "5Y" }];
17
17
  const RELEASE_OPTIONS = [{ value: "all", label: "All" }, ...MACRO_EVENT_KINDS.map((kind) => ({ value: kind, label: MACRO_EVENT_LABELS[kind] }))];
@@ -64,22 +64,26 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
64
64
  const loader = useCallback(async (force: boolean) => {
65
65
  controller.current?.abort();
66
66
  controller.current = new AbortController();
67
- const snapshot = await loadMacroDayHistory({ instrument: instrument!, forceRefresh: force, signal: controller.current.signal });
67
+ const [snapshot, releases] = await Promise.all([
68
+ loadMacroDayHistory({ instrument: instrument!, forceRefresh: force, signal: controller.current.signal }),
69
+ loadMacroReleases({ force }),
70
+ ]);
68
71
  if (!snapshot.history.length && snapshot.error) throw new Error(snapshot.error);
69
- return snapshot;
72
+ return { ...snapshot, releases };
70
73
  }, [instrumentKey]);
71
74
  const history = useAsyncResource(instrument ? loader : null);
72
75
  useEffect(() => () => controller.current?.abort(), [loader]);
73
76
  useAutoRefresh(history.updatedAt, history.load);
74
77
  usePaneRefreshKey(() => { void history.reload(); }, { focused });
75
78
 
76
- const model = useMemo(() => history.data ? projectMacroDays(history.data.history, { symbol: symbol ?? "", lookbackYears: Number(lookback) || 5 }) : null,
77
- [history.data, lookback, symbol]);
79
+ const model = useMemo(() => history.data ? projectMacroDays(history.data.history, { symbol: symbol ?? "", lookbackYears: Number(lookback) || 5,
80
+ releases: history.data.releases.releases, coveredThrough: history.data.releases.coveredThrough }) : null, [history.data, lookback, symbol]);
81
+ const releasesNotice = history.data ? staleMacroReleasesNotice(history.data.releases) : null;
78
82
  const rows = useMemo(() => model ? model.events.filter((event) => release === "all" || event.kind === release) : [], [model, release]);
79
83
  const stats = useMemo(() => model?.events.length ? figures(model, release) : [], [model, release]);
80
84
 
81
85
  usePaneNoticeFooter({ registrationId: "macro-day-notices", focused,
82
- notices: [...new Set([identityError, history.error, history.data?.error].filter((value): value is string => !!value))] });
86
+ notices: [...new Set([identityError, history.error, history.data?.error, releasesNotice].filter((value): value is string => !!value))] });
83
87
  usePaneFooter("macro-day", () => ({ info: [
84
88
  ...(history.loading ? [{ id: "loading", parts: [{ text: "loading history", tone: "muted" as const }] }] : []),
85
89
  ...(history.data?.stale ? [{ id: "stale", parts: [{ text: "stale history", tone: "warning" as const }] }] : []),
@@ -102,7 +106,7 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
102
106
  { id: "release", label: "Release", value: release, options: RELEASE_OPTIONS, onChange: setRelease, inline: true },
103
107
  { id: "lookback", label: "Lookback", value: String(lookback), options: LOOKBACK_OPTIONS, onChange: setLookback },
104
108
  ]} />
105
- {!symbol ? <EmptyState title="Choose a ticker." /> : <PaneStatusBody subject="macro-day moves" loading={history.loading && !model}
109
+ {!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="macro-day moves" loading={history.loading && !model}
106
110
  error={!model ? history.error ?? identityError ?? null : null} empty={!!model && !model.events.length}>
107
111
  {model ? <>
108
112
  <StatGrid items={stats} width={width} />