gloomberb 0.15.7 → 0.16.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 (524) hide show
  1. package/README.md +11 -2
  2. package/package.json +1 -1
  3. package/src/api-client/app-attention.ts +147 -0
  4. package/src/api-client/attention.ts +26 -0
  5. package/src/api-client/awards.ts +215 -0
  6. package/src/api-client/catalysts.ts +160 -0
  7. package/src/api-client/chat.ts +137 -14
  8. package/src/api-client/company-kpis.ts +153 -0
  9. package/src/api-client/cpi.ts +52 -0
  10. package/src/api-client/credit-documents.ts +107 -0
  11. package/src/api-client/data.ts +192 -3
  12. package/src/api-client/distress.ts +164 -0
  13. package/src/api-client/doe.ts +75 -0
  14. package/src/api-client/equity-screener.ts +11 -0
  15. package/src/api-client/exposure.ts +202 -0
  16. package/src/api-client/gpu.ts +93 -0
  17. package/src/api-client/hiring.ts +110 -0
  18. package/src/api-client/index.ts +78 -3
  19. package/src/api-client/normalizers.ts +31 -1
  20. package/src/api-client/paths.ts +3 -0
  21. package/src/api-client/perps.ts +127 -0
  22. package/src/api-client/power.ts +148 -0
  23. package/src/api-client/request.ts +58 -1
  24. package/src/api-client/research-activity.ts +172 -13
  25. package/src/api-client/socket.ts +75 -27
  26. package/src/api-client/supply-chain-graph.ts +40 -0
  27. package/src/api-client/supply-chain.ts +98 -0
  28. package/src/api-client/telemetry.ts +29 -0
  29. package/src/api-client/themes.ts +28 -0
  30. package/src/api-client/types.ts +33 -1
  31. package/src/api-client/wall-experiments.ts +100 -0
  32. package/src/api-client/wall-summary.ts +49 -0
  33. package/src/api-client/web-experiments.ts +136 -0
  34. package/src/app/pane-runtime/plugin-bindings.ts +5 -0
  35. package/src/app/pane-runtime/ticker-open-runtime.ts +8 -2
  36. package/src/app/runtime/update.ts +13 -1
  37. package/src/cli/commands/system.ts +2 -0
  38. package/src/cli/desktop-pane-shot.ts +3 -4
  39. package/src/cli/pane-functions/data.ts +78 -0
  40. package/src/cli/pane-functions/financial-ratio-shot.ts +160 -0
  41. package/src/cli/pane-functions/function-help.ts +220 -8
  42. package/src/cli/pane-functions/headless.ts +4 -0
  43. package/src/cli/pane-functions/screenshot-evidence.ts +21 -0
  44. package/src/cli/pane-functions/screenshot.ts +49 -5
  45. package/src/components/chart/composite/composite-chart.tsx +15 -2
  46. package/src/components/chart/composite/format.ts +24 -11
  47. package/src/components/chart/composite/rasterizer.ts +67 -16
  48. package/src/components/chart/composite/scene.ts +53 -2
  49. package/src/components/chart/composite/session-shading.ts +3 -2
  50. package/src/components/chart/composite/text-renderer.ts +52 -1
  51. package/src/components/chart/composite/time-axis.ts +100 -30
  52. package/src/components/chart/composite/tools.ts +2 -1
  53. package/src/components/chart/composite/types.ts +4 -0
  54. package/src/components/chart-table/index.ts +1 -0
  55. package/src/components/chart-table/layout.ts +15 -0
  56. package/src/components/command-bar/action-runtime.ts +1 -0
  57. package/src/components/command-bar/commands/direct/index.ts +32 -0
  58. package/src/components/command-bar/commands/registry.ts +7 -0
  59. package/src/components/command-bar/pane-templates/items.ts +11 -0
  60. package/src/components/command-bar/pane-templates/workflow.ts +19 -4
  61. package/src/components/command-bar/routes/root/command-items.ts +6 -0
  62. package/src/components/command-bar/routes/root/provider-search.ts +2 -0
  63. package/src/components/command-bar/routes/root/results.ts +64 -0
  64. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  65. package/src/components/command-bar/routes/ticker-search/results.ts +89 -24
  66. package/src/components/command-bar/routes/ticker-search/route.ts +2 -0
  67. package/src/components/command-bar/surface/index.tsx +15 -0
  68. package/src/components/command-bar/view-model.ts +3 -0
  69. package/src/components/command-bar/workflow/collection-actions.ts +9 -7
  70. package/src/components/command-bar/workflow/ops.ts +19 -0
  71. package/src/components/command-bar/workflow/tickers.ts +5 -0
  72. package/src/components/company-logo.tsx +15 -2
  73. package/src/components/data-table/table-pane.ts +2 -1
  74. package/src/components/index.ts +1 -0
  75. package/src/components/layout/detached-pane-shell.tsx +12 -1
  76. package/src/components/layout/missing-pane.tsx +87 -23
  77. package/src/components/layout/pane/footer/index.tsx +64 -18
  78. package/src/components/layout/pane/footer/model.ts +85 -0
  79. package/src/components/layout/pane/sidebar.tsx +19 -1
  80. package/src/components/layout/shell/index.tsx +38 -28
  81. package/src/components/layout/shell/pane/management-shortcuts.ts +13 -1
  82. package/src/components/layout/status-bar.tsx +93 -28
  83. package/src/components/layout/status-widget-space.ts +72 -0
  84. package/src/components/onboarding/first-run-workspace.ts +8 -2
  85. package/src/components/onboarding/onboarding-wizard.tsx +35 -16
  86. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -5
  87. package/src/components/onboarding/wizard-model.ts +13 -5
  88. package/src/components/onboarding/wizard-positions.ts +56 -7
  89. package/src/components/price-performance.tsx +31 -34
  90. package/src/components/ui/data-table/index.tsx +11 -8
  91. package/src/components/ui/display.tsx +15 -6
  92. package/src/components/ui/fields.tsx +5 -0
  93. package/src/components/ui/figure.tsx +94 -0
  94. package/src/components/ui/header-case.ts +25 -3
  95. package/src/components/ui/icon.tsx +3 -1
  96. package/src/components/ui/message-composer.tsx +8 -1
  97. package/src/components/ui/query-bar.tsx +8 -1
  98. package/src/components/ui/ratio-bar.tsx +84 -0
  99. package/src/components/ui/toggle.tsx +3 -1
  100. package/src/components/use-scrollbox-layout.ts +17 -0
  101. package/src/core/state/app/layout.ts +35 -1
  102. package/src/core/state/app/state.ts +15 -0
  103. package/src/core/state/app/types.ts +1 -0
  104. package/src/data/config/store/normalize.ts +24 -0
  105. package/src/i18n/es.ts +2 -0
  106. package/src/i18n/ja.ts +2 -0
  107. package/src/i18n/ko.ts +2 -0
  108. package/src/i18n/zh-cn.ts +2 -0
  109. package/src/i18n/zh-tw.ts +2 -0
  110. package/src/layout-marketplace/pane-imagery.ts +1 -0
  111. package/src/market-data/coordinator/entries.ts +13 -0
  112. package/src/market-data/coordinator/financials.ts +33 -8
  113. package/src/market-data/market/freshness.ts +16 -5
  114. package/src/market-data/market/trading-sessions.ts +65 -1
  115. package/src/market-data/published-cn-sessions.ts +25 -0
  116. package/src/market-data/published-nse-sessions.ts +20 -0
  117. package/src/news/aggregator.ts +4 -0
  118. package/src/plugins/builtin/account-management/model.ts +6 -1
  119. package/src/plugins/builtin/account-management/pane.tsx +36 -25
  120. package/src/plugins/builtin/alerts/catalysts.ts +77 -0
  121. package/src/plugins/builtin/alerts/events-pane.tsx +3 -0
  122. package/src/plugins/builtin/alerts/events.ts +10 -4
  123. package/src/plugins/builtin/alerts/history-pane.tsx +1 -1
  124. package/src/plugins/builtin/alerts/history.ts +1 -0
  125. package/src/plugins/builtin/alerts/index.tsx +6 -0
  126. package/src/plugins/builtin/alerts/research-builder.ts +10 -3
  127. package/src/plugins/builtin/alerts/research-rules.ts +15 -1
  128. package/src/plugins/builtin/analytics/risk-client.ts +15 -3
  129. package/src/plugins/builtin/analytics/risk-headless.ts +123 -18
  130. package/src/plugins/builtin/attention/client.ts +80 -0
  131. package/src/plugins/builtin/attention/evidence.ts +30 -0
  132. package/src/plugins/builtin/attention/headless.ts +33 -0
  133. package/src/plugins/builtin/attention/index.tsx +18 -0
  134. package/src/plugins/builtin/attention/model.ts +59 -0
  135. package/src/plugins/builtin/attention/pane.tsx +211 -0
  136. package/src/plugins/builtin/attention/trending.tsx +59 -0
  137. package/src/plugins/builtin/awards/alerts.ts +32 -0
  138. package/src/plugins/builtin/awards/client.ts +82 -0
  139. package/src/plugins/builtin/awards/detail.tsx +107 -0
  140. package/src/plugins/builtin/awards/evidence.ts +36 -0
  141. package/src/plugins/builtin/awards/headless.ts +55 -0
  142. package/src/plugins/builtin/awards/index.tsx +20 -0
  143. package/src/plugins/builtin/awards/model.ts +42 -0
  144. package/src/plugins/builtin/awards/pane.tsx +263 -0
  145. package/src/plugins/builtin/catalysts/client.ts +28 -0
  146. package/src/plugins/builtin/catalysts/detail.tsx +88 -0
  147. package/src/plugins/builtin/catalysts/evidence.ts +47 -0
  148. package/src/plugins/builtin/catalysts/headless.ts +42 -0
  149. package/src/plugins/builtin/catalysts/history.ts +26 -0
  150. package/src/plugins/builtin/catalysts/index.tsx +29 -0
  151. package/src/plugins/builtin/catalysts/model.ts +135 -0
  152. package/src/plugins/builtin/catalysts/pane.tsx +159 -0
  153. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  154. package/src/plugins/builtin/chart-composer/headless-schema.ts +6 -0
  155. package/src/plugins/builtin/chart-composer/index.tsx +8 -1
  156. package/src/plugins/builtin/chart-composer/pane.tsx +28 -1
  157. package/src/plugins/builtin/chat/attachments/desktop.tsx +610 -0
  158. package/src/plugins/builtin/chat/attachments/files.ts +45 -0
  159. package/src/plugins/builtin/chat/attachments/model.ts +164 -0
  160. package/src/plugins/builtin/chat/attachments/terminal.tsx +72 -0
  161. package/src/plugins/builtin/chat/attachments/transfer.ts +165 -0
  162. package/src/plugins/builtin/chat/content/channel-navigation.ts +13 -12
  163. package/src/plugins/builtin/chat/content/composer-runtime.ts +25 -4
  164. package/src/plugins/builtin/chat/content/composer.tsx +46 -4
  165. package/src/plugins/builtin/chat/content/footer.tsx +46 -8
  166. package/src/plugins/builtin/chat/content/index.tsx +263 -62
  167. package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -2
  168. package/src/plugins/builtin/chat/content/open-targets.ts +17 -3
  169. package/src/plugins/builtin/chat/content/scroll.ts +7 -1
  170. package/src/plugins/builtin/chat/content/shortcuts.ts +36 -11
  171. package/src/plugins/builtin/chat/content/snapshot.ts +3 -0
  172. package/src/plugins/builtin/chat/content/transcript.tsx +17 -1
  173. package/src/plugins/builtin/chat/content/types.ts +7 -0
  174. package/src/plugins/builtin/chat/controller/attachments.ts +213 -0
  175. package/src/plugins/builtin/chat/controller/connections.ts +3 -1
  176. package/src/plugins/builtin/chat/controller/index.ts +114 -2
  177. package/src/plugins/builtin/chat/controller/lifecycle.ts +9 -0
  178. package/src/plugins/builtin/chat/controller/messages.ts +35 -2
  179. package/src/plugins/builtin/chat/controller/send.ts +128 -25
  180. package/src/plugins/builtin/chat/controller/state.ts +12 -1
  181. package/src/plugins/builtin/chat/controller/utils.ts +7 -5
  182. package/src/plugins/builtin/chat/controller/view.ts +2 -0
  183. package/src/plugins/builtin/chat/index.tsx +21 -0
  184. package/src/plugins/builtin/chat/layout.ts +9 -4
  185. package/src/plugins/builtin/chat/message/desktop.tsx +55 -20
  186. package/src/plugins/builtin/chat/message/header.tsx +30 -5
  187. package/src/plugins/builtin/chat/message/inline-tokens.tsx +9 -0
  188. package/src/plugins/builtin/chat/message/pending-send.ts +35 -0
  189. package/src/plugins/builtin/chat/message/profile-popover.tsx +5 -1
  190. package/src/plugins/builtin/chat/message/render-state.ts +12 -2
  191. package/src/plugins/builtin/chat/message/terminal.tsx +51 -4
  192. package/src/plugins/builtin/chat/message/types.ts +6 -1
  193. package/src/plugins/builtin/chat/pane-state.ts +59 -1
  194. package/src/plugins/builtin/chat/profile-popover.ts +50 -1
  195. package/src/plugins/builtin/chat/sidebar.tsx +8 -3
  196. package/src/plugins/builtin/chat/status-widget.tsx +29 -12
  197. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +143 -0
  198. package/src/plugins/builtin/chat/unread-inbox.ts +91 -0
  199. package/src/plugins/builtin/clinical-trials/client.ts +237 -0
  200. package/src/plugins/builtin/clinical-trials/index.tsx +74 -0
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +225 -0
  202. package/src/plugins/builtin/clinical-trials/types.ts +2 -0
  203. package/src/plugins/builtin/cloud/askg/controller.ts +37 -6
  204. package/src/plugins/builtin/cloud/askg/executor.ts +59 -44
  205. package/src/plugins/builtin/cloud/askg/host.ts +8 -1
  206. package/src/plugins/builtin/cloud/askg/manifest.ts +29 -2
  207. package/src/plugins/builtin/cloud/askg/model.ts +47 -13
  208. package/src/plugins/builtin/cloud/askg/notes.ts +100 -0
  209. package/src/plugins/builtin/cloud/askg/pane.tsx +50 -14
  210. package/src/plugins/builtin/cloud/askg/protocol.ts +39 -1
  211. package/src/plugins/builtin/cloud/askg/result-budget.ts +156 -0
  212. package/src/plugins/builtin/cloud/askg/user-data.ts +89 -0
  213. package/src/plugins/builtin/cloud/auth-actions.tsx +55 -31
  214. package/src/plugins/builtin/cloud/plugin.tsx +4 -1
  215. package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -1
  216. package/src/plugins/builtin/cloud/team/pane.tsx +3 -2
  217. package/src/plugins/builtin/cloud/thesis/board-pane.tsx +2 -2
  218. package/src/plugins/builtin/cloud/thesis/detail.tsx +1 -1
  219. package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +2 -2
  220. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +202 -0
  221. package/src/plugins/builtin/cloud/upgrade-command.ts +1 -1
  222. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +29 -3
  223. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +1 -1
  224. package/src/plugins/builtin/cloud-tweets/table.tsx +1 -1
  225. package/src/plugins/builtin/comment-letters/client.ts +342 -0
  226. package/src/plugins/builtin/comment-letters/index.tsx +74 -0
  227. package/src/plugins/builtin/comment-letters/pane.tsx +295 -0
  228. package/src/plugins/builtin/comment-letters/types.ts +43 -0
  229. package/src/plugins/builtin/company-attention/app-rank.tsx +79 -0
  230. package/src/plugins/builtin/company-attention/client.ts +69 -0
  231. package/src/plugins/builtin/company-attention/evidence.ts +45 -0
  232. package/src/plugins/builtin/company-attention/headless.ts +47 -0
  233. package/src/plugins/builtin/company-attention/index.tsx +30 -0
  234. package/src/plugins/builtin/company-attention/model.ts +129 -0
  235. package/src/plugins/builtin/company-attention/pane.tsx +183 -0
  236. package/src/plugins/builtin/company-kpis/client.ts +92 -0
  237. package/src/plugins/builtin/company-kpis/headless.ts +32 -0
  238. package/src/plugins/builtin/company-kpis/index.tsx +26 -0
  239. package/src/plugins/builtin/company-kpis/model.ts +124 -0
  240. package/src/plugins/builtin/company-kpis/pane.tsx +226 -0
  241. package/src/plugins/builtin/company-kpis/related.ts +11 -0
  242. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +44 -0
  243. package/src/plugins/builtin/composite-plugins.ts +34 -0
  244. package/src/plugins/builtin/correlation/history.ts +4 -1
  245. package/src/plugins/builtin/correlation/index.tsx +139 -80
  246. package/src/plugins/builtin/correlation/matrix/pair-chart.tsx +48 -0
  247. package/src/plugins/builtin/correlation/matrix/selection.ts +77 -0
  248. package/src/plugins/builtin/correlation/relationship/model.ts +2 -2
  249. package/src/plugins/builtin/cpi/client.ts +66 -0
  250. package/src/plugins/builtin/cpi/headless.ts +78 -0
  251. package/src/plugins/builtin/cpi/index.tsx +31 -0
  252. package/src/plugins/builtin/cpi/model.ts +198 -0
  253. package/src/plugins/builtin/cpi/pane.tsx +157 -0
  254. package/src/plugins/builtin/credit-documents/client.ts +99 -0
  255. package/src/plugins/builtin/credit-documents/detail.tsx +109 -0
  256. package/src/plugins/builtin/credit-documents/evidence.ts +64 -0
  257. package/src/plugins/builtin/credit-documents/gauge.tsx +55 -0
  258. package/src/plugins/builtin/credit-documents/headless.ts +40 -0
  259. package/src/plugins/builtin/credit-documents/index.tsx +19 -0
  260. package/src/plugins/builtin/credit-documents/model.ts +336 -0
  261. package/src/plugins/builtin/credit-documents/pane.tsx +185 -0
  262. package/src/plugins/builtin/custom-view/view-spec.ts +14 -2
  263. package/src/plugins/builtin/debt-maturities/pane.tsx +1 -0
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  265. package/src/plugins/builtin/doe/client.ts +73 -0
  266. package/src/plugins/builtin/doe/headless.ts +93 -0
  267. package/src/plugins/builtin/doe/index.tsx +41 -0
  268. package/src/plugins/builtin/doe/model.ts +215 -0
  269. package/src/plugins/builtin/doe/pane.tsx +148 -0
  270. package/src/plugins/builtin/doe/range-bar.tsx +45 -0
  271. package/src/plugins/builtin/earnings/board.tsx +3 -0
  272. package/src/plugins/builtin/earnings/index.tsx +2 -0
  273. package/src/plugins/builtin/earnings-calls/pane.tsx +7 -1
  274. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +7 -7
  275. package/src/plugins/builtin/earnings-ripple/client.ts +60 -0
  276. package/src/plugins/builtin/earnings-ripple/headless.ts +35 -0
  277. package/src/plugins/builtin/earnings-ripple/index.tsx +35 -0
  278. package/src/plugins/builtin/earnings-ripple/model.ts +98 -0
  279. package/src/plugins/builtin/earnings-ripple/pane.tsx +103 -0
  280. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  281. package/src/plugins/builtin/equity-screener/criterion-editor.tsx +1 -1
  282. package/src/plugins/builtin/equity-screener/model.ts +74 -9
  283. package/src/plugins/builtin/equity-screener/pane.tsx +2 -0
  284. package/src/plugins/builtin/estimate-revisions/pane.tsx +3 -0
  285. package/src/plugins/builtin/executives/pane.tsx +7 -5
  286. package/src/plugins/builtin/exposure/charts.tsx +66 -0
  287. package/src/plugins/builtin/exposure/client.ts +53 -0
  288. package/src/plugins/builtin/exposure/drivers.ts +29 -0
  289. package/src/plugins/builtin/exposure/editor.tsx +61 -0
  290. package/src/plugins/builtin/exposure/evidence.ts +39 -0
  291. package/src/plugins/builtin/exposure/headless.ts +51 -0
  292. package/src/plugins/builtin/exposure/holdings.ts +56 -0
  293. package/src/plugins/builtin/exposure/index.tsx +13 -0
  294. package/src/plugins/builtin/exposure/model.ts +104 -0
  295. package/src/plugins/builtin/exposure/pane.tsx +247 -0
  296. package/src/plugins/builtin/fear-greed/charts.tsx +1 -1
  297. package/src/plugins/builtin/fear-greed/pane.tsx +2 -9
  298. package/src/plugins/builtin/filing-events/pane.tsx +2 -1
  299. package/src/plugins/builtin/gpu/board.tsx +163 -0
  300. package/src/plugins/builtin/gpu/changes.tsx +94 -0
  301. package/src/plugins/builtin/gpu/client.ts +130 -0
  302. package/src/plugins/builtin/gpu/equities.tsx +112 -0
  303. package/src/plugins/builtin/gpu/headless.ts +67 -0
  304. package/src/plugins/builtin/gpu/history.tsx +140 -0
  305. package/src/plugins/builtin/gpu/index.tsx +18 -0
  306. package/src/plugins/builtin/gpu/ladder.tsx +127 -0
  307. package/src/plugins/builtin/gpu/model.ts +317 -0
  308. package/src/plugins/builtin/gpu/pane.tsx +63 -0
  309. package/src/plugins/builtin/help/index.tsx +19 -2
  310. package/src/plugins/builtin/holders/pane.tsx +5 -9
  311. package/src/plugins/builtin/insider/detail.tsx +119 -0
  312. package/src/plugins/builtin/insider/display.ts +221 -0
  313. package/src/plugins/builtin/insider/headless.ts +24 -3
  314. package/src/plugins/builtin/insider/index.tsx +206 -108
  315. package/src/plugins/builtin/insider/insider-data.ts +31 -3
  316. package/src/plugins/builtin/insider/model.ts +53 -4
  317. package/src/plugins/builtin/insider/table-model.ts +226 -0
  318. package/src/plugins/builtin/insider/table.tsx +79 -0
  319. package/src/plugins/builtin/iv-history/screen-pane.tsx +1 -1
  320. package/src/plugins/builtin/jobs/pane.tsx +14 -7
  321. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  322. package/src/plugins/builtin/macro-day/client.ts +50 -0
  323. package/src/plugins/builtin/macro-day/headless.ts +51 -0
  324. package/src/plugins/builtin/macro-day/index.tsx +21 -0
  325. package/src/plugins/builtin/macro-day/model.ts +137 -0
  326. package/src/plugins/builtin/macro-day/pane.tsx +118 -0
  327. package/src/plugins/builtin/macro-day/releases.ts +58 -0
  328. package/src/plugins/builtin/market-halts/model.ts +30 -17
  329. package/src/plugins/builtin/market-halts/pane.tsx +15 -21
  330. package/src/plugins/builtin/market-heatmap/data.ts +1 -1
  331. package/src/plugins/builtin/market-heatmap/index.tsx +198 -50
  332. package/src/plugins/builtin/market-heatmap/portfolio.ts +220 -0
  333. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  334. package/src/plugins/builtin/market-movers/session-body.tsx +5 -2
  335. package/src/plugins/builtin/mna/distress/client.ts +326 -0
  336. package/src/plugins/builtin/mna/distress/detail.tsx +247 -0
  337. package/src/plugins/builtin/mna/distress/headless.ts +257 -0
  338. package/src/plugins/builtin/mna/distress/model.ts +427 -0
  339. package/src/plugins/builtin/mna/distress/pane.tsx +606 -0
  340. package/src/plugins/builtin/mna/index.tsx +15 -0
  341. package/src/plugins/builtin/mna/pane.tsx +42 -1
  342. package/src/plugins/builtin/news/index.tsx +62 -16
  343. package/src/plugins/builtin/news/wire/filter-articles.ts +54 -0
  344. package/src/plugins/builtin/news/wire/index.tsx +37 -3
  345. package/src/plugins/builtin/news/wire/industry-pane.tsx +29 -13
  346. package/src/plugins/builtin/news/wire/mutes.ts +253 -0
  347. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +117 -0
  348. package/src/plugins/builtin/news/wire/news/footer.ts +4 -0
  349. package/src/plugins/builtin/news/wire/news/list-search.ts +34 -0
  350. package/src/plugins/builtin/news/wire/news/pop-out.tsx +158 -0
  351. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +64 -19
  352. package/src/plugins/builtin/openfda/client.ts +300 -0
  353. package/src/plugins/builtin/openfda/index.tsx +71 -0
  354. package/src/plugins/builtin/openfda/pane.tsx +382 -0
  355. package/src/plugins/builtin/openfda/types.ts +76 -0
  356. package/src/plugins/builtin/options/footer.ts +1 -0
  357. package/src/plugins/builtin/options/view.tsx +1 -1
  358. package/src/plugins/builtin/options-calculator/pane.tsx +0 -1
  359. package/src/plugins/builtin/options-positioning/index.tsx +4 -2
  360. package/src/plugins/builtin/options-positioning/pane.tsx +1 -1
  361. package/src/plugins/builtin/pe-band/client.ts +61 -0
  362. package/src/plugins/builtin/pe-band/headless.ts +51 -0
  363. package/src/plugins/builtin/pe-band/index.tsx +20 -0
  364. package/src/plugins/builtin/pe-band/model.ts +267 -0
  365. package/src/plugins/builtin/pe-band/pane.tsx +159 -0
  366. package/src/plugins/builtin/perps/client.ts +66 -0
  367. package/src/plugins/builtin/perps/equity-row.tsx +28 -0
  368. package/src/plugins/builtin/perps/headless.ts +34 -0
  369. package/src/plugins/builtin/perps/index.tsx +14 -0
  370. package/src/plugins/builtin/perps/model.ts +58 -0
  371. package/src/plugins/builtin/perps/pane.tsx +185 -0
  372. package/src/plugins/builtin/plugin-marketplace/pane.tsx +0 -1
  373. package/src/plugins/builtin/portfolio-list/cli/render.ts +177 -87
  374. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +6 -20
  375. package/src/plugins/builtin/portfolio-list/command-bar.ts +6 -2
  376. package/src/plugins/builtin/portfolio-list/headless.ts +237 -0
  377. package/src/plugins/builtin/portfolio-list/index.tsx +2 -0
  378. package/src/plugins/builtin/portfolio-list/mutations.ts +70 -1
  379. package/src/plugins/builtin/portfolio-list/pane/data.ts +17 -0
  380. package/src/plugins/builtin/portfolio-list/pane/index.tsx +88 -5
  381. package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +85 -0
  382. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +18 -12
  383. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -0
  384. package/src/plugins/builtin/power/client.ts +100 -0
  385. package/src/plugins/builtin/power/detail.tsx +82 -0
  386. package/src/plugins/builtin/power/evidence.ts +56 -0
  387. package/src/plugins/builtin/power/generation.tsx +37 -0
  388. package/src/plugins/builtin/power/headless.ts +43 -0
  389. package/src/plugins/builtin/power/index.tsx +16 -0
  390. package/src/plugins/builtin/power/model.ts +89 -0
  391. package/src/plugins/builtin/power/pane.tsx +209 -0
  392. package/src/plugins/builtin/power/query.ts +25 -0
  393. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -3
  394. package/src/plugins/builtin/research/analyst-pane.tsx +1 -1
  395. package/src/plugins/builtin/research/corporate-actions-pane.tsx +13 -4
  396. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +11 -9
  397. package/src/plugins/builtin/research-search/pane.tsx +21 -7
  398. package/src/plugins/builtin/revenue-breakdown/pane.tsx +1 -1
  399. package/src/plugins/builtin/reverse-dcf/client.ts +35 -0
  400. package/src/plugins/builtin/reverse-dcf/headless.ts +44 -0
  401. package/src/plugins/builtin/reverse-dcf/index.tsx +20 -0
  402. package/src/plugins/builtin/reverse-dcf/model.ts +87 -0
  403. package/src/plugins/builtin/reverse-dcf/pane.tsx +107 -0
  404. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  405. package/src/plugins/builtin/scanner/denied.tsx +4 -2
  406. package/src/plugins/builtin/scanner/feed.ts +1 -0
  407. package/src/plugins/builtin/scanner/flow-pane.tsx +3 -2
  408. package/src/plugins/builtin/scanner/hilo-pane.tsx +1 -1
  409. package/src/plugins/builtin/seasonality/client.ts +4 -1
  410. package/src/plugins/builtin/sec/command-bar-search.ts +217 -0
  411. package/src/plugins/builtin/sec/forms.ts +54 -0
  412. package/src/plugins/builtin/sec/headless.ts +31 -5
  413. package/src/plugins/builtin/sec/index.tsx +67 -11
  414. package/src/plugins/builtin/sec/model.ts +35 -5
  415. package/src/plugins/builtin/sectors/headless.ts +1 -0
  416. package/src/plugins/builtin/sectors/index.tsx +72 -26
  417. package/src/plugins/builtin/shared/cloud-upgrade.ts +143 -39
  418. package/src/plugins/builtin/shared/pro-feature-wall.tsx +8 -4
  419. package/src/plugins/builtin/shared/research-cells.tsx +107 -0
  420. package/src/plugins/builtin/shared/series-list-detail.tsx +5 -1
  421. package/src/plugins/builtin/shared/ticker-request.ts +1 -1
  422. package/src/plugins/builtin/shared/wall-teaser-catalog.ts +80 -0
  423. package/src/plugins/builtin/shared/wall-teaser.tsx +192 -0
  424. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +43 -0
  425. package/src/plugins/builtin/short-interest/index.tsx +31 -0
  426. package/src/plugins/builtin/short-interest/watch-client.ts +70 -0
  427. package/src/plugins/builtin/short-interest/watch-headless.ts +49 -0
  428. package/src/plugins/builtin/short-interest/watch-model.ts +185 -0
  429. package/src/plugins/builtin/short-interest/watch-pane.tsx +169 -0
  430. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  431. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  432. package/src/plugins/builtin/supply-chain/client.ts +104 -0
  433. package/src/plugins/builtin/supply-chain/disclosure-quote.tsx +17 -0
  434. package/src/plugins/builtin/supply-chain/evidence.ts +88 -0
  435. package/src/plugins/builtin/supply-chain/flow-layout.ts +133 -0
  436. package/src/plugins/builtin/supply-chain/flow.tsx +323 -0
  437. package/src/plugins/builtin/supply-chain/graph-client.ts +83 -0
  438. package/src/plugins/builtin/supply-chain/graph-evidence.ts +25 -0
  439. package/src/plugins/builtin/supply-chain/graph-model.ts +45 -0
  440. package/src/plugins/builtin/supply-chain/graph-pane.tsx +218 -0
  441. package/src/plugins/builtin/supply-chain/graph.tsx +163 -0
  442. package/src/plugins/builtin/supply-chain/headless.ts +105 -0
  443. package/src/plugins/builtin/supply-chain/index.tsx +17 -0
  444. package/src/plugins/builtin/supply-chain/model.ts +145 -0
  445. package/src/plugins/builtin/supply-chain/pane.tsx +276 -0
  446. package/src/plugins/builtin/supply-chain/row-evidence.tsx +96 -0
  447. package/src/plugins/builtin/supply-chain/trust.ts +34 -0
  448. package/src/plugins/builtin/themes/client.ts +39 -0
  449. package/src/plugins/builtin/themes/headless.ts +38 -0
  450. package/src/plugins/builtin/themes/model.ts +92 -0
  451. package/src/plugins/builtin/themes/pane.tsx +125 -0
  452. package/src/plugins/builtin/thirteenf/pane.tsx +6 -15
  453. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -6
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +201 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +327 -0
  456. package/src/plugins/builtin/ticker-detail/financials/period-end-history.ts +5 -2
  457. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +60 -12
  458. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +2 -1
  459. package/src/plugins/builtin/ticker-detail/headless.ts +7 -2
  460. package/src/plugins/builtin/ticker-detail/index.tsx +22 -0
  461. package/src/plugins/builtin/ticker-detail/overview/components.tsx +53 -30
  462. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -0
  463. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +112 -47
  464. package/src/plugins/builtin/ticker-detail/pane.tsx +50 -6
  465. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +21 -42
  466. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +3 -0
  467. package/src/plugins/builtin/time-sales/pane.tsx +1 -1
  468. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  469. package/src/plugins/catalog-browser.ts +40 -0
  470. package/src/plugins/catalog-manifest.ts +3 -0
  471. package/src/plugins/catalog-ui.ts +6 -0
  472. package/src/plugins/current-target.ts +0 -9
  473. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  474. package/src/renderers/browser/cloud-transport.ts +66 -1
  475. package/src/renderers/browser/main.tsx +2 -0
  476. package/src/renderers/dom/data-table/row.tsx +2 -0
  477. package/src/renderers/dom/desktop/controls.tsx +38 -25
  478. package/src/renderers/dom/desktop/icons.tsx +8 -0
  479. package/src/renderers/dom/host/chart-surface.tsx +36 -2
  480. package/src/renderers/dom/host/focus-scope.ts +11 -1
  481. package/src/renderers/dom/styles.css +28 -0
  482. package/src/renderers/electrobun/view/backend-rpc.ts +3 -2
  483. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  484. package/src/renderers/electrobun/view/http-fetch.ts +2 -0
  485. package/src/renderers/electrobun/view/main.tsx +3 -1
  486. package/src/renderers/electrobun/view/rpc-timeout.ts +59 -0
  487. package/src/renderers/opentui/start.tsx +3 -0
  488. package/src/shares/api.ts +3 -1
  489. package/src/sources/gloomberb-cloud/index.ts +47 -13
  490. package/src/sources/history-result.ts +1 -0
  491. package/src/sources/history-retention.ts +16 -0
  492. package/src/sources/provider-router/history.ts +4 -2
  493. package/src/sources/sec-edgar/identity.ts +36 -0
  494. package/src/sources/sec-edgar.ts +1 -34
  495. package/src/state/app/context/index.tsx +8 -0
  496. package/src/sync/core-contributors.ts +20 -2
  497. package/src/telemetry/attention-counts.ts +164 -0
  498. package/src/telemetry/live-config.ts +7 -0
  499. package/src/test-support/rpc-loopback.ts +7 -3
  500. package/src/test-support/shell-pane-keys.tsx +34 -0
  501. package/src/tickers/instrument-kind.ts +12 -6
  502. package/src/tickers/open-target.ts +1 -1
  503. package/src/tickers/search/asset-classes.ts +91 -0
  504. package/src/tickers/search/index.ts +62 -15
  505. package/src/tickers/search/ranking.ts +22 -3
  506. package/src/tickers/search/upsert.ts +4 -1
  507. package/src/time-series/live-bars.ts +8 -3
  508. package/src/time-series/resolve.ts +224 -17
  509. package/src/time-series/spec.ts +1 -0
  510. package/src/time-series/types.ts +9 -1
  511. package/src/types/config.ts +23 -1
  512. package/src/types/data-provider.ts +2 -0
  513. package/src/types/headless.ts +17 -0
  514. package/src/types/price-history.ts +2 -0
  515. package/src/ui/host.tsx +6 -0
  516. package/src/utils/double-escape-close.ts +20 -0
  517. package/src/utils/exchanges.ts +1 -0
  518. package/src/utils/html-entities.ts +15 -0
  519. package/src/utils/http-proxy-response.ts +44 -7
  520. package/src/utils/plugin-proxy-hosts.json +3 -0
  521. package/src/utils/price-history.ts +35 -1
  522. package/src/utils/sec.ts +21 -2
  523. package/src/utils/text-wrap.ts +13 -8
  524. package/src/utils/zoned-date-time.ts +67 -0
@@ -0,0 +1,226 @@
1
+ import type { DataTableColumn } from "../../../components";
2
+ import { displayWidth, formatCompact, formatCurrency } from "../../../utils/format";
3
+ import { isAmendedInsiderFiling } from "./amendments";
4
+ import { insiderSecurityTag, insiderTypeLabel, insiderTypeTone, isOpenMarketTrade, type InsiderTypeTone } from "./display";
5
+ import {
6
+ insiderDisplayName,
7
+ insiderRole,
8
+ insiderTransactionId,
9
+ isInsiderDisclosureOnly,
10
+ type ParsedInsiderFiling,
11
+ } from "./model";
12
+
13
+ export type InsiderColumnId = "date" | "insider" | "role" | "type" | "security" | "shares" | "price" | "value";
14
+
15
+ export interface InsiderColumn extends DataTableColumn {
16
+ id: InsiderColumnId;
17
+ /** The type column has room for the 10b5-1 marker. */
18
+ plan?: boolean;
19
+ }
20
+
21
+ /** One table row: a Form 4 line, or a filing without one (loading, unread, a disclosure). */
22
+ export interface InsiderTableRow {
23
+ id: string;
24
+ entry: ParsedInsiderFiling;
25
+ date: Date | null;
26
+ name: string;
27
+ role: string;
28
+ type: string;
29
+ tone: InsiderTypeTone;
30
+ /** An amended filing (Form 4/A), marked after the type at every width. */
31
+ amended: boolean;
32
+ /** An open-market trade on a form with the Rule 10b5-1 box checked, marked after the type when there is room. */
33
+ plan: boolean;
34
+ security: string | null;
35
+ shares: number | null;
36
+ price: number | null;
37
+ value: number | null;
38
+ }
39
+
40
+ function validDate(value: Date | string | number | null | undefined): Date | null {
41
+ if (value == null) return null;
42
+ const date = value instanceof Date ? value : new Date(value);
43
+ return Number.isFinite(date.getTime()) ? date : null;
44
+ }
45
+
46
+ /** Joint filers (a fund and its general partners) read as the first one and how many more. */
47
+ function tableName(entry: ParsedInsiderFiling): string | null {
48
+ const name = insiderDisplayName(entry);
49
+ const owners = name?.split("; ") ?? [];
50
+ return owners.length > 1 ? `${owners[0]} +${owners.length - 1}` : name;
51
+ }
52
+
53
+ export function buildInsiderTableRows(parsed: readonly ParsedInsiderFiling[]): InsiderTableRow[] {
54
+ return parsed.map((entry) => {
55
+ const { filing, transaction } = entry;
56
+ const amendment = isAmendedInsiderFiling(entry);
57
+ const name = tableName(entry);
58
+ if (!transaction) {
59
+ const disclosure = !entry.isLoading && isInsiderDisclosureOnly(entry);
60
+ return {
61
+ id: insiderTransactionId(entry),
62
+ entry,
63
+ date: validDate(filing.filingDate),
64
+ name: name ?? (entry.isLoading ? "Loading..." : "Unknown filer"),
65
+ role: insiderRole(entry),
66
+ type: entry.isLoading ? "" : disclosure ? "NOTICE" : "—",
67
+ tone: "neutral",
68
+ amended: amendment,
69
+ plan: false,
70
+ security: null,
71
+ shares: null,
72
+ price: null,
73
+ value: null,
74
+ };
75
+ }
76
+ return {
77
+ id: insiderTransactionId(entry),
78
+ entry,
79
+ date: validDate(transaction.filingDate ?? filing.filingDate),
80
+ name: name ?? "Unknown filer",
81
+ role: insiderRole(entry),
82
+ type: insiderTypeLabel(transaction.transactionType),
83
+ tone: insiderTypeTone(transaction.transactionType),
84
+ amended: amendment,
85
+ // The box covers the whole form; it is about the trade, not the vesting or tax lines beside it.
86
+ plan: transaction.rule10b51 === true && isOpenMarketTrade(transaction.transactionType),
87
+ security: insiderSecurityTag(transaction),
88
+ shares: transaction.shares,
89
+ price: transaction.pricePerShare,
90
+ value: transaction.totalValue,
91
+ };
92
+ });
93
+ }
94
+
95
+ /** A transaction date as m/d/yy, read in UTC because it names a calendar day. */
96
+ export function formatInsiderDate(date: Date | null): string {
97
+ if (!date) return "—";
98
+ return `${date.getUTCMonth() + 1}/${date.getUTCDate()}/${String(date.getUTCFullYear()).slice(-2)}`;
99
+ }
100
+
101
+ export function formatInsiderShares(value: number | null): string {
102
+ return value == null ? "—" : formatCompact(value);
103
+ }
104
+
105
+ export function formatInsiderPrice(value: number | null): string {
106
+ return value == null ? "—" : formatCurrency(value);
107
+ }
108
+
109
+ export function formatInsiderValue(value: number | null): string {
110
+ return value == null ? "—" : `$${formatCompact(value)}`;
111
+ }
112
+
113
+ /** The quieter words after the type: "4/A" always, "10b5-1" when the column has room. */
114
+ export function insiderTypeMarkers(row: InsiderTableRow, plan: boolean): string[] {
115
+ return [...(row.amended ? ["4/A"] : []), ...(plan && row.plan ? ["10b5-1"] : [])];
116
+ }
117
+
118
+ export function insiderTypeText(row: InsiderTableRow, plan: boolean): string {
119
+ return [row.type, ...insiderTypeMarkers(row, plan)].filter(Boolean).join(" ");
120
+ }
121
+
122
+ /** Cells left and right of the columns, and between two of them. */
123
+ const TABLE_PADDING = 2;
124
+ const COLUMN_GAP = 1;
125
+ /** The kit widens a column to fit its header plus the sort arrow. */
126
+ const HEADER_EXTRA = 2;
127
+
128
+ function fitWidth(texts: readonly string[], label: string, max: number): number {
129
+ const widest = texts.reduce((width, text) => Math.max(width, displayWidth(text)), 0);
130
+ return Math.max(displayWidth(label) + HEADER_EXTRA, Math.min(max, widest));
131
+ }
132
+
133
+ /**
134
+ * The columns that fit `width` terminal cells (the desktop passes its width
135
+ * in the same cells). Every column takes the width its widest value needs.
136
+ * When the pane runs short, the role goes first, then the price, the
137
+ * 10b5-1 marker, the security and the share count, and only then does the name
138
+ * column shrink below 16 cells, so a narrow pane still reads date, name,
139
+ * type and value.
140
+ */
141
+ export function buildInsiderColumns(width: number, rows: readonly InsiderTableRow[]): InsiderColumn[] {
142
+ const anyPlan = rows.some((row) => row.plan);
143
+ const anySecurity = rows.some((row) => row.security);
144
+ const names = rows.map((row) => row.name);
145
+ const widths = {
146
+ date: fitWidth(rows.map((row) => formatInsiderDate(row.date)), "Date", 8),
147
+ insider: fitWidth(names, "Insider", 28),
148
+ role: fitWidth(rows.map((row) => row.role), "Role", 12),
149
+ typeBase: fitWidth(rows.map((row) => insiderTypeText(row, false)), "Type", 12),
150
+ typePlan: fitWidth(rows.map((row) => insiderTypeText(row, true)), "Type", 19),
151
+ security: fitWidth(rows.map((row) => row.security ?? ""), "Security", 12),
152
+ shares: fitWidth(rows.map((row) => formatInsiderShares(row.shares)), "Shares", 9),
153
+ price: fitWidth(rows.map((row) => formatInsiderPrice(row.price)), "Price", 10),
154
+ value: fitWidth(rows.map((row) => formatInsiderValue(row.value)), "Value", 9),
155
+ };
156
+ const show = { role: true, price: true, plan: anyPlan, security: anySecurity, shares: true };
157
+ let insider = widths.insider;
158
+ const available = Math.max(0, Math.floor(width) - TABLE_PADDING);
159
+ const total = () => [
160
+ widths.date,
161
+ insider,
162
+ show.role ? widths.role : 0,
163
+ show.plan ? widths.typePlan : widths.typeBase,
164
+ show.security ? widths.security : 0,
165
+ show.shares ? widths.shares : 0,
166
+ show.price ? widths.price : 0,
167
+ widths.value,
168
+ ].reduce((sum, columnWidth) => sum + (columnWidth > 0 ? columnWidth + COLUMN_GAP : 0), 0);
169
+ const shrinkInsider = (floor: number) => {
170
+ insider = Math.max(Math.min(insider, floor), insider - Math.max(0, total() - available));
171
+ };
172
+ shrinkInsider(16);
173
+ for (const drop of ["role", "price", "plan", "security", "shares"] as const) {
174
+ if (total() <= available) break;
175
+ show[drop] = false;
176
+ shrinkInsider(16);
177
+ }
178
+ shrinkInsider(6);
179
+ // A dropped column can leave room for more of the names.
180
+ insider = Math.min(widths.insider, insider + Math.max(0, available - total()));
181
+
182
+ const typeWidth = show.plan ? widths.typePlan : widths.typeBase;
183
+ const columns: InsiderColumn[] = [
184
+ { id: "date", label: "Date", width: widths.date, align: "left" },
185
+ { id: "insider", label: "Insider", width: insider, align: "left" },
186
+ ...(show.role ? [{ id: "role" as const, label: "Role", width: widths.role, align: "left" as const }] : []),
187
+ { id: "type", label: "Type", width: typeWidth, align: "left", plan: show.plan },
188
+ ...(show.security ? [{ id: "security" as const, label: "Security", width: widths.security, align: "left" as const }] : []),
189
+ ...(show.shares ? [{ id: "shares" as const, label: "Shares", width: widths.shares, align: "right" as const }] : []),
190
+ ...(show.price ? [{ id: "price" as const, label: "Price", width: widths.price, align: "right" as const }] : []),
191
+ { id: "value", label: "Value", width: widths.value, align: "right" },
192
+ ];
193
+ // Spare cells spread over the columns, so the value column ends at the
194
+ // pane's edge in the terminal as it does on the desktop.
195
+ const used = columns.reduce((sum, column) => sum + column.width + COLUMN_GAP, 0);
196
+ const spare = Math.max(0, available - used);
197
+ const each = Math.floor(spare / columns.length);
198
+ return columns.map((column) => ({
199
+ ...column,
200
+ width: column.width + each + (column.id === "insider" ? spare - each * columns.length : 0),
201
+ }));
202
+ }
203
+
204
+ function compareNullable(a: number | null, b: number | null): number {
205
+ if (a == null && b == null) return 0;
206
+ if (a == null) return 1;
207
+ if (b == null) return -1;
208
+ return a - b;
209
+ }
210
+
211
+ function compareText(a: string, b: string): number {
212
+ return a.localeCompare(b, "en-US", { sensitivity: "base" });
213
+ }
214
+
215
+ export function compareInsiderRows(a: InsiderTableRow, b: InsiderTableRow, columnId: InsiderColumnId): number {
216
+ switch (columnId) {
217
+ case "date": return compareNullable(a.date?.getTime() ?? null, b.date?.getTime() ?? null);
218
+ case "insider": return compareText(a.name, b.name);
219
+ case "role": return compareText(a.role, b.role);
220
+ case "type": return compareText(a.type, b.type);
221
+ case "security": return compareText(a.security ?? "", b.security ?? "");
222
+ case "shares": return compareNullable(a.shares, b.shares);
223
+ case "price": return compareNullable(a.price, b.price);
224
+ case "value": return compareNullable(a.value, b.value);
225
+ }
226
+ }
@@ -0,0 +1,79 @@
1
+ import { Box, Text } from "../../../ui";
2
+ import type { DataTableCell } from "../../../components";
3
+ import { colors } from "../../../theme/colors";
4
+ import { blendForContrast } from "../../../theme/color-utils";
5
+ import { clipToDisplayWidth, displayWidth } from "../../../utils/format";
6
+ import type { InsiderTypeTone } from "./display";
7
+ import {
8
+ formatInsiderDate,
9
+ formatInsiderPrice,
10
+ formatInsiderShares,
11
+ formatInsiderValue,
12
+ insiderTypeMarkers,
13
+ insiderTypeText,
14
+ type InsiderColumn,
15
+ type InsiderTableRow,
16
+ } from "./table-model";
17
+
18
+ /** The contrast a buy or sell keeps against the selected row. */
19
+ const SELECTED_TONE_MIN_CONTRAST = 3.6;
20
+
21
+ /** The theme's gain and loss colours, as every pane draws a buy and a sale. */
22
+ export function insiderToneColor(tone: InsiderTypeTone): string {
23
+ return tone === "buy" ? colors.positive : tone === "sell" ? colors.negative : colors.textDim;
24
+ }
25
+
26
+ function typeColor(tone: InsiderTypeTone, selected: boolean): string {
27
+ if (tone === "neutral") return selected ? colors.selectedText : colors.textDim;
28
+ const color = insiderToneColor(tone);
29
+ return selected ? blendForContrast(color, colors.selected, colors.selectedText, SELECTED_TONE_MIN_CONTRAST) : color;
30
+ }
31
+
32
+ function isoDay(date: Date | null): string | null {
33
+ return date ? date.toISOString().slice(0, 10) : null;
34
+ }
35
+
36
+ export function renderInsiderCell(
37
+ row: InsiderTableRow,
38
+ column: InsiderColumn,
39
+ _index: number,
40
+ rowState: { selected: boolean },
41
+ ): DataTableCell {
42
+ // Awards, exercises and tax lines are context; buys and sales carry the figures.
43
+ const figureColor = row.tone === "neutral" ? colors.textDim : colors.text;
44
+ switch (column.id) {
45
+ case "date":
46
+ return { text: formatInsiderDate(row.date), value: isoDay(row.date), color: colors.textDim };
47
+ case "insider":
48
+ return { text: row.name, color: row.entry.isLoading ? colors.textDim : colors.text };
49
+ case "role":
50
+ return { text: row.role, color: colors.textMuted };
51
+ case "type": {
52
+ const color = typeColor(row.tone, rowState.selected);
53
+ const text = insiderTypeText(row, column.plan === true);
54
+ const markers = insiderTypeMarkers(row, column.plan === true);
55
+ if (markers.length === 0) return { text, color, keepColorWhenSelected: true };
56
+ // The markers read after the type in a quieter colour.
57
+ const markerColor = rowState.selected ? colors.selectedText : colors.textMuted;
58
+ const label = clipToDisplayWidth(row.type, column.width);
59
+ const room = Math.max(0, column.width - displayWidth(label) - (label ? 1 : 0));
60
+ return {
61
+ text,
62
+ content: (
63
+ <Box flexDirection="row" width={column.width} overflow="hidden">
64
+ {label ? <Text fg={color}>{label}</Text> : null}
65
+ {room > 0 ? <Text fg={markerColor}>{`${label ? " " : ""}${clipToDisplayWidth(markers.join(" "), room)}`}</Text> : null}
66
+ </Box>
67
+ ),
68
+ };
69
+ }
70
+ case "security":
71
+ return { text: row.security ?? "", value: row.entry.transaction?.securityTitle || null, color: colors.textMuted };
72
+ case "shares":
73
+ return { text: formatInsiderShares(row.shares), value: row.shares, color: figureColor };
74
+ case "price":
75
+ return { text: formatInsiderPrice(row.price), value: row.price, color: figureColor };
76
+ case "value":
77
+ return { text: formatInsiderValue(row.value), value: row.value, color: figureColor };
78
+ }
79
+ }
@@ -35,7 +35,7 @@ export function IvScreenPane({ width, height, focused }: PaneProps) {
35
35
  const { collectionId } = usePaneCollection();
36
36
  const tickers = useTickers();
37
37
  const { createPaneFromTemplate } = usePluginAppActions();
38
- const universe = useMemo(() => vcaUniverse(scope, symbolsText, collectionId, Object.values(tickers)), [scope, symbolsText, collectionId, tickers]);
38
+ const universe = useMemo(() => vcaUniverse(scope, symbolsText, collectionId, [...tickers.values()]), [scope, symbolsText, collectionId, tickers]);
39
39
  const key = universe.instruments.map((instrument) => instrument.symbol).join(",");
40
40
  const controller = useRef<AbortController | null>(null);
41
41
  const [hv, setHv] = useState<Map<string, number | null>>(new Map());
@@ -70,6 +70,7 @@ import {
70
70
  } from "./model";
71
71
  import { ShareBars } from "./share-bars";
72
72
  import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
73
+ import { listingIdentity } from "../shared/ticker-request";
73
74
 
74
75
  export const JOBS_PANE_ID = "jobs";
75
76
 
@@ -79,9 +80,11 @@ const MOVERS_PAGE = 200;
79
80
 
80
81
  type DetailTab = "roles" | "locations" | "seniority" | "salary";
81
82
 
82
- function HiringProWall({ symbol }: { symbol: string | null }) {
83
+ function HiringProWall({ symbol, exchange, width, height }: { symbol: string | null; exchange?: string; width: number; height: number }) {
83
84
  return (
84
85
  <ProWall
86
+ placement="jobs-wall"
87
+ symbol={symbol} exchange={exchange} width={width} height={height}
85
88
  title="Hiring data is part of Gloom Cloud Pro."
86
89
  message={`Gloomberb reads every listed company's own careers system daily: open roles over time, hiring by function and location, new roles, and pay ranges.${symbol ? ` Open ${symbol}'s hiring picture with Pro.` : ""}`}
87
90
  />
@@ -511,6 +514,7 @@ function CompanyView({
511
514
  */
512
515
  function CompanyPanel({
513
516
  symbol,
517
+ exchange = "US",
514
518
  companyName,
515
519
  width,
516
520
  height,
@@ -519,6 +523,7 @@ function CompanyPanel({
519
523
  embedded = false,
520
524
  }: {
521
525
  symbol: string;
526
+ exchange?: string;
522
527
  companyName: string | null;
523
528
  width: number;
524
529
  height: number;
@@ -544,8 +549,8 @@ function CompanyPanel({
544
549
  }, [data, reload]);
545
550
 
546
551
  if (data?.kind === "denied") {
547
- if (data.status === 402) return <HiringProWall symbol={symbol} />;
548
- return <SignInWall action="see who is hiring" needsVerification={data.status === 403} />;
552
+ if (data.status === 402) return <HiringProWall symbol={symbol} exchange={exchange} width={width} height={height} />;
553
+ return <SignInWall placement="jobs-detail-signin" width={width} height={height} symbol={symbol} exchange={exchange} action="see who is hiring" needsVerification={data.status === 403} />;
549
554
  }
550
555
  if ((status === "idle" || status === "loading") && !data) {
551
556
  return <PaneStatusBody loading align="center" loadingLabel={`Loading ${symbol} hiring...`} />;
@@ -733,15 +738,16 @@ interface JobsViewProps {
733
738
  }
734
739
 
735
740
  function JobsView({ width, height, focused, companyOnly = false }: JobsViewProps) {
736
- const { ticker } = usePaneTickerIdentity();
741
+ const { ticker, symbol: boundSymbol } = usePaneTickerIdentity();
737
742
  const symbol = ticker?.metadata.ticker ?? null;
743
+ const exchange = listingIdentity(boundSymbol, ticker?.metadata.exchange)?.exchange;
738
744
  const companyName = ticker?.metadata.name ?? null;
739
745
  const access = usePlanAccess();
740
746
  const registrationId = JOBS_PANE_ID;
741
747
 
742
- if (!access.signedIn) return <SignInWall action="see who is hiring" />;
743
- if (!access.emailVerified) return <SignInWall action="see who is hiring" needsVerification />;
744
- if (!access.hasProAccess) return <HiringProWall symbol={symbol} />;
748
+ if (!access.signedIn) return <SignInWall placement="jobs-signin" width={width} height={height} symbol={symbol} exchange={exchange} action="see who is hiring" />;
749
+ if (!access.emailVerified) return <SignInWall placement="jobs-signin" width={width} height={height} symbol={symbol} exchange={exchange} action="see who is hiring" needsVerification />;
750
+ if (!access.hasProAccess) return <HiringProWall symbol={symbol} exchange={exchange} width={width} height={height} />;
745
751
 
746
752
  if (!symbol) {
747
753
  if (companyOnly) return <EmptyState title="No ticker selected." message="Select a ticker to see its hiring." />;
@@ -750,6 +756,7 @@ function JobsView({ width, height, focused, companyOnly = false }: JobsViewProps
750
756
  return (
751
757
  <CompanyPanel
752
758
  symbol={symbol}
759
+ exchange={exchange}
753
760
  companyName={companyName}
754
761
  width={width}
755
762
  height={height}
@@ -449,7 +449,7 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
449
449
  const bodyRows = Math.max(0, height - 1 - tabRows - gridRows - resultRows);
450
450
  const chartHeight = showSensitivity ? 0 : bodyRows;
451
451
  const showChart = !showSensitivity && chartHeight >= 6 && curvePoints.length > 0;
452
- const quoteCurrency = positionFinancials?.quote?.currency ?? ticker?.metadata.currency ?? config.baseCurrency;
452
+ const quoteCurrency = positionFinancials?.quote?.currency || ticker?.metadata.currency || config.baseCurrency;
453
453
  // The search shows the ticker and the Portfolio filter the account, so the
454
454
  // context is the price first, then the account only when there is no filter.
455
455
  const meta = [
@@ -0,0 +1,50 @@
1
+ import { getSharedMarketDataCoordinator, MarketDataCoordinator, resolveEntryValue } from "../../../market-data/coordinator";
2
+ import type { InstrumentRef } from "../../../market-data/request-types";
3
+ import { tickerHasListingSuffix } from "../../../sources/listing-symbols";
4
+ import type { DataProvider } from "../../../types/data-provider";
5
+ import type { PricePoint } from "../../../types/financials";
6
+ import { abortable, abortError } from "../../../utils/async-deadline";
7
+ import { errorMessage } from "../../../utils/errors";
8
+ import { resolveExchangeTimeZone } from "../../../utils/exchanges";
9
+
10
+ export interface MacroDayHistory {
11
+ history: PricePoint[];
12
+ stale: boolean;
13
+ error: string | null;
14
+ fetchedAt: number;
15
+ }
16
+
17
+ const CANCELLED = "Macro-day history load was cancelled";
18
+ const US_LISTINGS_ONLY = "Macro-day moves are for US listings.";
19
+
20
+ /**
21
+ * A listing whose venue sits outside New York trades US releases on a different clock.
22
+ * A bare symbol such as 7203.T carries no exchange, so its venue suffix names it.
23
+ */
24
+ function isNonUsVenue(instrument: InstrumentRef): boolean {
25
+ if (tickerHasListingSuffix(instrument.symbol)) return true;
26
+ const zone = resolveExchangeTimeZone(instrument.exchange);
27
+ return !!zone && zone !== "America/New_York";
28
+ }
29
+
30
+ /** Five years of daily closes, the longest daily history served; the lookback is cut in the model. */
31
+ export async function loadMacroDayHistory(
32
+ request: { instrument: InstrumentRef; signal?: AbortSignal; forceRefresh?: boolean },
33
+ marketData?: DataProvider,
34
+ ): Promise<MacroDayHistory> {
35
+ if (request.signal?.aborted) throw abortError(CANCELLED);
36
+ const now = Date.now();
37
+ if (isNonUsVenue(request.instrument)) return { history: [], stale: false, error: US_LISTINGS_ONLY, fetchedAt: now };
38
+ const coordinator = marketData ? new MarketDataCoordinator(marketData) : getSharedMarketDataCoordinator();
39
+ try {
40
+ if (!coordinator) throw new Error("Market data coordinator unavailable");
41
+ const entry = await abortable(coordinator.loadChart({ instrument: request.instrument, bufferRange: "5Y",
42
+ granularity: "resolution", resolution: "1d" }, { forceRefresh: request.forceRefresh }), request.signal, CANCELLED);
43
+ const history = resolveEntryValue(entry) ?? [];
44
+ return { history, stale: !!entry.error || (entry.staleAt != null && entry.staleAt <= now),
45
+ error: entry.error?.message ?? (history.length ? null : "Daily price history unavailable"), fetchedAt: entry.fetchedAt ?? now };
46
+ } catch (error) {
47
+ if (request.signal?.aborted || (error instanceof Error && error.name === "AbortError")) throw abortError(CANCELLED);
48
+ return { history: [], stale: false, error: errorMessage(error), fetchedAt: now };
49
+ }
50
+ }
@@ -0,0 +1,51 @@
1
+ import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
+ import { resolveHeadlessInstrument } from "../shared/headless-market-data";
3
+ import { loadMacroDayHistory } from "./client";
4
+ import { projectMacroDays, type MacroDayStats } from "./model";
5
+ import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, MACRO_RELEASE_BASIS } from "./releases";
6
+
7
+ // A mean that rounds to zero reads 0.00%, never -0.00%.
8
+ const percent = (value: unknown) => typeof value !== "number" ? "--" : /[1-9]/.test((value * 100).toFixed(2)) ? `${(value * 100).toFixed(2)}%` : "0.00%";
9
+ const multiple = (value: unknown) => typeof value === "number" ? `${value.toFixed(2)}x` : "--";
10
+ const share = (value: unknown) => typeof value === "number" ? `${Math.round(value * 100)}%` : "--";
11
+
12
+ export const macroDayHeadless: HeadlessPaneDefinition<"bundle"> = {
13
+ shape: "bundle", argument: { kind: "ticker", description: "Ticker" },
14
+ describe: (args) => `MDAY ${args.symbols[0] ?? ""}`,
15
+ discovery: { screenshotReadiness: "live-dom", limitations: [
16
+ "US listings; close-to-close on the release day.",
17
+ `Published CPI, jobs and FOMC days through ${MACRO_RELEASE_BASIS.coveredThrough}.`,
18
+ ] },
19
+ options: [
20
+ { key: "release", type: "enum", values: [{ value: "all" }, ...MACRO_EVENT_KINDS.map((value) => ({ value }))], defaultValue: "all",
21
+ description: "Release the pane lists", pluginState: { pluginId: "ticker-research", key: "release" } },
22
+ { key: "lookbackYears", type: "integer", minimum: 1, maximum: 5, defaultValue: 5, description: "Years of daily closes" },
23
+ ],
24
+ async load(args, ctx) {
25
+ const instrument = await resolveHeadlessInstrument(ctx, args.symbols[0]!);
26
+ const history = await loadMacroDayHistory({ instrument, signal: ctx.signal }, ctx.marketData);
27
+ ctx.signal.throwIfAborted();
28
+ const model = projectMacroDays(history.history, { symbol: instrument.symbol, lookbackYears: Number(args.options.lookbackYears) || 5 });
29
+ const row = (label: string, stats: MacroDayStats) => ({ label, ...stats });
30
+ const release = String(args.options.release ?? "all");
31
+ const events = release === "all" ? model.events : model.events.filter((event) => event.kind === release);
32
+ return {
33
+ sections: [
34
+ { title: "By release", columns: [{ key: "label", header: "Day" }, { key: "count", header: "Days" },
35
+ { key: "meanAbs", header: "Avg |move|", format: percent }, { key: "multiple", header: "vs normal", format: multiple },
36
+ { key: "mean", header: "Avg move", format: percent }, { key: "hitRate", header: "Up", format: share }],
37
+ rows: [...MACRO_EVENT_KINDS.map((kind) => row(MACRO_EVENT_LABELS[kind], model.byKind[kind])),
38
+ row("Any release", model.allEvents), row("Normal day", model.normal)] },
39
+ { title: "Release days", columns: [{ key: "date", header: "Date" }, { key: "event", header: "Release" },
40
+ { key: "session", header: "Session" }, { key: "move", header: "Move", format: percent }, { key: "multiple", header: "vs normal", format: multiple }],
41
+ rows: events.map((event) => ({ ...event, event: MACRO_EVENT_LABELS[event.kind] })) },
42
+ ],
43
+ complete: !history.stale && !history.error && model.events.length > 0,
44
+ unavailableSymbols: model.events.length ? [] : [instrument.symbol],
45
+ errors: history.error ? [history.error] : [],
46
+ metadata: { unit: "decimal return, close to close on the release day", start: model.start, asOf: model.asOf,
47
+ releasesThrough: MACRO_RELEASE_BASIS.coveredThrough, releaseSources: MACRO_RELEASE_BASIS.sources,
48
+ stale: history.stale, fetchedAt: history.fetchedAt, methodology: "docs/research-data.md#macro-day-reaction" },
49
+ };
50
+ },
51
+ };
@@ -0,0 +1,21 @@
1
+ import type { PluginModule } from "../plugin-module";
2
+ import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
3
+ import { macroDayHeadless } from "./headless";
4
+ import { LOOKBACK_OPTIONS, MacroDayPane } from "./pane";
5
+
6
+ export const macroDayModule: PluginModule = {
7
+ panes: [{
8
+ id: "macro-day", name: "Macro-Day Reaction", icon: "M", component: MacroDayPane,
9
+ defaultPosition: "right", tickerFollower: true, defaultMode: "floating", defaultFloatingSize: { width: 76, height: 26 },
10
+ tableExport: true,
11
+ settings: { title: "Macro-Day Reaction Settings", fields: [
12
+ { key: "lookbackYears", label: "Lookback", type: "select", options: LOOKBACK_OPTIONS },
13
+ ] },
14
+ }],
15
+ paneTemplates: [{ ...createTickerSurfacePaneTemplate({
16
+ id: "macro-day-pane", paneId: "macro-day", label: "Macro-Day Reaction",
17
+ description: "How a name moves on CPI, jobs and FOMC days against a normal day: average move, direction, hit rate and every release day.",
18
+ keywords: ["mday", "macro day", "cpi day", "fomc day", "jobs day", "nfp", "payrolls", "event study", "release reaction"],
19
+ shortcut: "MDAY", publicShare: true,
20
+ }), headless: macroDayHeadless }],
21
+ };
@@ -0,0 +1,137 @@
1
+ import type { PricePoint } from "../../../types/financials";
2
+ import { zonedDateTimeParts } from "../../../utils/zoned-date-time";
3
+ import { MACRO_EVENT_KINDS, MACRO_RELEASE_BASIS, macroReleases, type MacroEventKind, type MacroRelease } from "./releases";
4
+
5
+ const DAY_MS = 86_400_000;
6
+ /** Longest calendar gap between two closes that is still one session apart: Thursday to Monday over a Friday holiday. */
7
+ const MAX_SESSION_GAP_DAYS = 4;
8
+
9
+ export interface MacroDayEvent {
10
+ kind: MacroEventKind;
11
+ /** Release day, New York date. */
12
+ date: string;
13
+ /** The session whose close carries the reaction: the release day, or the next session when the market was shut. */
14
+ session: string;
15
+ /** Close before the release to the session's close. */
16
+ move: number;
17
+ /** |move| over the normal day's average |move|. */
18
+ multiple: number | null;
19
+ }
20
+
21
+ export interface MacroDayStats {
22
+ count: number;
23
+ meanAbs: number | null;
24
+ mean: number | null;
25
+ /** Share of days that closed up. */
26
+ hitRate: number | null;
27
+ /** meanAbs over the normal day's. */
28
+ multiple: number | null;
29
+ }
30
+
31
+ export interface MacroDayModel {
32
+ symbol: string;
33
+ /** Newest first. */
34
+ events: MacroDayEvent[];
35
+ byKind: Record<MacroEventKind, MacroDayStats>;
36
+ /** Every release day of the three kinds together; a day with two releases counts once. */
37
+ allEvents: MacroDayStats;
38
+ /** Sessions with no release of the three kinds. */
39
+ normal: MacroDayStats;
40
+ start: string | null;
41
+ asOf: string | null;
42
+ }
43
+
44
+ /** Daily labels at UTC midnight name that date; any other stamp reads in New York. */
45
+ function sessionDate(time: number): string {
46
+ if (time % DAY_MS === 0) return new Date(time).toISOString().slice(0, 10);
47
+ const { year, month, day } = zonedDateTimeParts(time, "America/New_York");
48
+ return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
49
+ }
50
+
51
+ const dayNumber = (date: string) => Date.parse(`${date}T00:00:00Z`) / DAY_MS;
52
+ const isWeekday = (date: string) => { const day = new Date(`${date}T00:00:00Z`).getUTCDay(); return day > 0 && day < 6; };
53
+
54
+ function stats(moves: number[], normalAbs: number | null): MacroDayStats {
55
+ if (!moves.length) return { count: 0, meanAbs: null, mean: null, hitRate: null, multiple: null };
56
+ const meanAbs = moves.reduce((sum, move) => sum + Math.abs(move), 0) / moves.length;
57
+ return { count: moves.length, meanAbs, mean: moves.reduce((sum, move) => sum + move, 0) / moves.length,
58
+ hitRate: moves.filter((move) => move > 0).length / moves.length,
59
+ multiple: normalAbs ? meanAbs / normalAbs : null };
60
+ }
61
+
62
+ /**
63
+ * Close to close on the release day: 8:30 ET releases land before the open
64
+ * and 14:00 FOMC statements before the close, so the release day's close
65
+ * carries either. A release on a weekday the market was shut (Good Friday)
66
+ * is read on the next session, from the close before the release. A release
67
+ * whose close-to-close would span a gap in the history is left out rather
68
+ * than read across it. A lone missing bar on the release day itself cannot
69
+ * be told from a holiday and is read on the next session.
70
+ */
71
+ export function projectMacroDays(history: readonly PricePoint[], options: {
72
+ symbol: string;
73
+ lookbackYears: number;
74
+ releases?: readonly MacroRelease[];
75
+ coveredThrough?: string;
76
+ }): MacroDayModel {
77
+ const coveredThrough = options.coveredThrough ?? MACRO_RELEASE_BASIS.coveredThrough;
78
+ const byDate = new Map<string, number>();
79
+ for (const point of history) {
80
+ const time = new Date(point.date).getTime();
81
+ if (!Number.isFinite(time) || !Number.isFinite(point.close) || point.close <= 0) continue;
82
+ byDate.set(sessionDate(time), point.close);
83
+ }
84
+ const sessions = [...byDate.keys()].sort();
85
+ const last = sessions.at(-1) ?? null;
86
+ const empty = (): MacroDayStats => stats([], null);
87
+ if (!last) return { symbol: options.symbol, events: [], byKind: { cpi: empty(), jobs: empty(), fomc: empty() },
88
+ allEvents: empty(), normal: empty(), start: null, asOf: null };
89
+
90
+ // Sessions after the published lists end may be unlisted release days, so they are left out of both sides.
91
+ const end = last < coveredThrough ? last : coveredThrough;
92
+ const startYear = Number(end.slice(0, 4)) - Math.max(1, options.lookbackYears);
93
+ const start = `${startYear}${end.slice(4)}`;
94
+ /** Session date -> close-to-close move from the session before, when the two are one session apart. */
95
+ const moves = new Map<string, number>();
96
+ for (let index = 1; index < sessions.length; index++) {
97
+ const date = sessions[index]!, previous = sessions[index - 1]!;
98
+ if (date <= start || date > end || dayNumber(date) - dayNumber(previous) > MAX_SESSION_GAP_DAYS) continue;
99
+ moves.set(date, byDate.get(date)! / byDate.get(previous)! - 1);
100
+ }
101
+
102
+ const reactionSessions = new Set<string>();
103
+ const found: Omit<MacroDayEvent, "multiple">[] = [];
104
+ for (const release of options.releases ?? macroReleases()) {
105
+ if (release.date <= start || release.date > end || !isWeekday(release.date)) continue;
106
+ const at = sessions.findIndex((date) => date >= release.date);
107
+ if (at < 1) continue;
108
+ const session = sessions[at]!;
109
+ const move = moves.get(session);
110
+ if (move == null || (session !== release.date && skipsAnotherWeekday(release.date, sessions[at - 1]!, session))) continue;
111
+ reactionSessions.add(session);
112
+ found.push({ kind: release.kind, date: release.date, session, move });
113
+ }
114
+
115
+ const normalMoves = [...moves].filter(([date]) => !reactionSessions.has(date)).map(([, move]) => move);
116
+ const normal = stats(normalMoves, null);
117
+ const normalAbs = normal.meanAbs;
118
+ const events = found.map((event) => ({ ...event, multiple: normalAbs ? Math.abs(event.move) / normalAbs : null }))
119
+ .sort((left, right) => right.date.localeCompare(left.date) || left.kind.localeCompare(right.kind));
120
+ const byKind = Object.fromEntries(MACRO_EVENT_KINDS.map((kind) =>
121
+ [kind, stats(events.filter((event) => event.kind === kind).map((event) => event.move), normalAbs)])) as Record<MacroEventKind, MacroDayStats>;
122
+ const allEvents = stats([...reactionSessions].map((session) => moves.get(session)!), normalAbs);
123
+ return { symbol: options.symbol, events, byKind, allEvents, normal, start, asOf: end };
124
+ }
125
+
126
+ /**
127
+ * A release weekday with no close of its own reads as a holiday only when the
128
+ * closes either side have nothing but it and a weekend between them; any other
129
+ * weekday between them is a gap in the data, and the move would span it.
130
+ */
131
+ function skipsAnotherWeekday(release: string, previous: string, next: string): boolean {
132
+ for (let day = dayNumber(previous) + 1; day < dayNumber(next); day++) {
133
+ const date = new Date(day * DAY_MS).toISOString().slice(0, 10);
134
+ if (date !== release && isWeekday(date)) return true;
135
+ }
136
+ return false;
137
+ }