gloomberb 0.7.5 → 0.8.1

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 (1292) hide show
  1. package/README.md +27 -6
  2. package/package.json +2 -3
  3. package/src/api-client/auth.ts +167 -0
  4. package/src/api-client/chat.ts +116 -0
  5. package/src/api-client/data.ts +177 -0
  6. package/src/api-client/errors.ts +28 -0
  7. package/src/api-client/index.test.ts +675 -0
  8. package/src/api-client/index.ts +384 -0
  9. package/src/api-client/normalizers.ts +61 -0
  10. package/src/api-client/paths.ts +173 -0
  11. package/src/api-client/request.ts +124 -0
  12. package/src/api-client/socket.ts +483 -0
  13. package/src/api-client/types.ts +490 -0
  14. package/src/app/app-bootstrap-state.test.ts +157 -0
  15. package/src/app/app-bootstrap-state.ts +134 -0
  16. package/src/app/global-shortcuts.test.tsx +166 -0
  17. package/src/app/global-shortcuts.ts +126 -0
  18. package/src/app/pane-runtime/index.tsx +297 -0
  19. package/src/app/pane-runtime/layout-placement.ts +73 -0
  20. package/src/app/pane-runtime/pane-settings-runtime.tsx +67 -0
  21. package/src/app/pane-runtime/pane-template-runtime.ts +110 -0
  22. package/src/app/pane-runtime/plugin-bindings.ts +231 -0
  23. package/src/app/pane-runtime/ticker-inspector-runtime.ts +167 -0
  24. package/src/app/pane-runtime/ticker-open-runtime.ts +144 -0
  25. package/src/app/pane-template-dialog-wizard.tsx +52 -0
  26. package/src/app/runtime/broker-import.ts +109 -0
  27. package/src/app/runtime/desktop-menu.ts +74 -0
  28. package/src/app/runtime/plugin-bindings.ts +246 -0
  29. package/src/app/runtime/startup.ts +126 -0
  30. package/src/app/runtime/ticker-refresh.ts +249 -0
  31. package/src/app/runtime/update.ts +86 -0
  32. package/src/app.tsx +107 -1487
  33. package/src/brokers/broker-portfolio-sync.ts +186 -0
  34. package/src/brokers/broker-ticker-sync.ts +141 -0
  35. package/src/brokers/profile-form.ts +2 -2
  36. package/src/brokers/remote-broker-adapter.ts +3 -2
  37. package/src/brokers/sync-broker-instance.test.ts +140 -0
  38. package/src/brokers/sync-broker-instance.ts +60 -181
  39. package/src/capabilities/types.ts +4 -4
  40. package/src/cli/commands/search.ts +1 -1
  41. package/src/cli/commands/ticker.ts +3 -3
  42. package/src/cli/context.ts +1 -1
  43. package/src/cli/desktop-pane-shot.ts +305 -0
  44. package/src/cli/helpers.ts +1 -1
  45. package/src/cli/index.ts +34 -0
  46. package/src/cli/pane-functions/catalog.ts +243 -0
  47. package/src/cli/pane-functions/data.ts +78 -0
  48. package/src/cli/pane-functions/discovery.ts +113 -0
  49. package/src/cli/pane-functions/index.test.ts +114 -0
  50. package/src/cli/pane-functions/index.ts +112 -0
  51. package/src/cli/pane-functions/options.ts +249 -0
  52. package/src/cli/pane-functions/report.ts +180 -0
  53. package/src/cli/pane-functions/resolver.ts +96 -0
  54. package/src/cli/pane-functions/screenshot.ts +113 -0
  55. package/src/cli/registry.ts +3 -2
  56. package/src/cli/ticker-resolution.ts +1 -1
  57. package/src/cli.test.ts +1 -1
  58. package/src/components/chart/bar-chart-renderer.test.ts +53 -0
  59. package/src/components/chart/bar-chart-renderer.ts +190 -0
  60. package/src/components/chart/comparison/data.test.ts +105 -0
  61. package/src/components/chart/comparison/data.ts +279 -0
  62. package/src/components/chart/comparison/renderer.test.ts +187 -0
  63. package/src/components/chart/comparison/renderer.ts +283 -0
  64. package/src/components/chart/comparison/scene.ts +178 -0
  65. package/src/components/chart/comparison-stock-chart/axis-width.ts +99 -0
  66. package/src/components/chart/comparison-stock-chart/canvas.ts +148 -0
  67. package/src/components/chart/comparison-stock-chart/controls.ts +252 -0
  68. package/src/components/chart/comparison-stock-chart/helpers.ts +133 -0
  69. package/src/components/chart/comparison-stock-chart/interactions.ts +202 -0
  70. package/src/components/chart/comparison-stock-chart/layout-metrics.ts +62 -0
  71. package/src/components/chart/comparison-stock-chart/layout.tsx +155 -0
  72. package/src/components/chart/comparison-stock-chart/legend.tsx +73 -0
  73. package/src/components/chart/comparison-stock-chart/native-surfaces.ts +89 -0
  74. package/src/components/chart/comparison-stock-chart/render-data.ts +198 -0
  75. package/src/components/chart/comparison-stock-chart/render-output.ts +145 -0
  76. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +111 -0
  77. package/src/components/chart/comparison-stock-chart/sources.ts +47 -0
  78. package/src/components/chart/comparison-stock-chart/surface-runtime.ts +159 -0
  79. package/src/components/chart/comparison-stock-chart/toolbar.tsx +107 -0
  80. package/src/components/chart/comparison-stock-chart/types.ts +22 -0
  81. package/src/components/chart/comparison-stock-chart/view.tsx +524 -0
  82. package/src/components/chart/comparison-stock-chart/viewport-runtime.ts +239 -0
  83. package/src/components/chart/comparison-stock-chart.tsx +12 -2028
  84. package/src/components/chart/core/axis-format.ts +147 -0
  85. package/src/components/chart/core/axis-measure.ts +33 -0
  86. package/src/components/chart/core/controller.test.ts +157 -0
  87. package/src/components/chart/core/controller.ts +349 -0
  88. package/src/components/chart/core/data.ts +94 -0
  89. package/src/components/chart/core/date-window.ts +392 -0
  90. package/src/components/chart/core/index.test.ts +1007 -0
  91. package/src/components/chart/core/indicator-selector.tsx +75 -0
  92. package/src/components/chart/core/keyboard.ts +15 -0
  93. package/src/components/chart/core/palette.ts +60 -0
  94. package/src/components/chart/core/pane-settings.test.ts +25 -0
  95. package/src/components/chart/core/pane-settings.ts +82 -0
  96. package/src/components/chart/core/pointer.ts +292 -0
  97. package/src/components/chart/core/projection.ts +333 -0
  98. package/src/components/chart/core/render-utils.ts +4 -0
  99. package/src/components/chart/core/renderer.ts +249 -0
  100. package/src/components/chart/core/resolution.test.ts +99 -0
  101. package/src/components/chart/core/resolution.ts +380 -0
  102. package/src/components/chart/core/scene.ts +180 -0
  103. package/src/components/chart/core/scroll.test.ts +70 -0
  104. package/src/components/chart/core/scroll.ts +104 -0
  105. package/src/components/chart/core/time-axis.ts +330 -0
  106. package/src/components/chart/core/types.ts +124 -0
  107. package/src/components/chart/core/viewport.test.ts +29 -0
  108. package/src/components/chart/core/viewport.ts +51 -0
  109. package/src/components/chart/core/window-viewport.test.ts +69 -0
  110. package/src/components/chart/core/window-viewport.ts +162 -0
  111. package/src/components/chart/display-cursor.ts +153 -0
  112. package/src/components/chart/market-session.ts +1 -1
  113. package/src/components/chart/multi-line/renderer.test.ts +83 -0
  114. package/src/components/chart/multi-line/renderer.ts +160 -0
  115. package/src/components/chart/multi-line/scene.ts +188 -0
  116. package/src/components/chart/native/canvas-bitmaps.ts +140 -0
  117. package/src/components/chart/native/chart-rasterizer.ts +18 -865
  118. package/src/components/chart/native/kitty/adapter.ts +9 -0
  119. package/src/components/chart/native/kitty/index.test.ts +752 -0
  120. package/src/components/chart/native/kitty/manager.ts +142 -0
  121. package/src/components/chart/native/kitty/support.ts +70 -0
  122. package/src/components/chart/native/raster/comparison-chart.ts +162 -0
  123. package/src/components/chart/native/raster/crosshair.ts +51 -0
  124. package/src/components/chart/native/raster/layout.ts +136 -0
  125. package/src/components/chart/native/raster/placement.ts +132 -0
  126. package/src/components/chart/native/raster/price-chart.ts +244 -0
  127. package/src/components/chart/native/raster/primitives.ts +154 -0
  128. package/src/components/chart/native/raster/types.ts +41 -0
  129. package/src/components/chart/native/renderer-selection.ts +81 -1
  130. package/src/components/chart/native/surface/hooks.ts +137 -0
  131. package/src/components/chart/native/surface/manager.ts +225 -0
  132. package/src/components/chart/native/surface/rendering.ts +211 -0
  133. package/src/components/chart/native/surface/sync.ts +34 -0
  134. package/src/components/chart/native/surface/visibility.ts +48 -0
  135. package/src/components/chart/price-axis-labels.tsx +4 -4
  136. package/src/components/chart/rendering/chart-draw.ts +270 -0
  137. package/src/components/chart/rendering/geometry.ts +95 -0
  138. package/src/components/chart/rendering/pixel-buffer.ts +267 -0
  139. package/src/components/chart/scatter-chart-renderer.test.ts +31 -0
  140. package/src/components/chart/scatter-chart-renderer.ts +223 -0
  141. package/src/components/chart/static/bar-chart-surface.tsx +85 -0
  142. package/src/components/chart/static/chart/bitmap.ts +50 -0
  143. package/src/components/chart/static/chart/surface.test.tsx +51 -0
  144. package/src/components/chart/static/chart/surface.tsx +181 -0
  145. package/src/components/chart/static/multi-line-chart-surface.tsx +180 -0
  146. package/src/components/chart/static/scatter-chart-surface.tsx +83 -0
  147. package/src/components/chart/stock-chart/auto.ts +216 -0
  148. package/src/components/chart/stock-chart/axis-width.ts +146 -0
  149. package/src/components/chart/stock-chart/bitmaps.ts +22 -0
  150. package/src/components/chart/stock-chart/canvas.ts +202 -0
  151. package/src/components/chart/stock-chart/controls.tsx +149 -0
  152. package/src/components/chart/stock-chart/cursor.ts +60 -0
  153. package/src/components/chart/stock-chart/data-runtime.ts +129 -0
  154. package/src/components/chart/stock-chart/footer.ts +248 -0
  155. package/src/components/chart/stock-chart/geometry.ts +160 -0
  156. package/src/components/chart/stock-chart/index.test.ts +500 -0
  157. package/src/components/chart/stock-chart/index.tsx +29 -0
  158. package/src/components/chart/stock-chart/indicators.ts +226 -0
  159. package/src/components/chart/stock-chart/interaction-runtime.ts +44 -0
  160. package/src/components/chart/stock-chart/interactions.ts +300 -0
  161. package/src/components/chart/stock-chart/keyboard.ts +373 -0
  162. package/src/components/chart/stock-chart/layout-metrics.ts +62 -0
  163. package/src/components/chart/stock-chart/layout.tsx +177 -0
  164. package/src/components/chart/stock-chart/native-surfaces.ts +121 -0
  165. package/src/components/chart/stock-chart/presentation.ts +67 -0
  166. package/src/components/chart/stock-chart/renderer-switch.test.tsx +263 -0
  167. package/src/components/chart/stock-chart/rendering/bounds.ts +116 -0
  168. package/src/components/chart/stock-chart/rendering/candidates.ts +145 -0
  169. package/src/components/chart/stock-chart/rendering/data.ts +370 -0
  170. package/src/components/chart/stock-chart/rendering/invalidation.ts +68 -0
  171. package/src/components/chart/stock-chart/rendering/output.ts +304 -0
  172. package/src/components/chart/stock-chart/rendering/planning.ts +122 -0
  173. package/src/components/chart/stock-chart/rendering/projection.test.tsx +98 -0
  174. package/src/components/chart/stock-chart/rendering/projection.ts +112 -0
  175. package/src/components/chart/stock-chart/rendering/state.test.ts +70 -0
  176. package/src/components/chart/stock-chart/rendering/state.ts +283 -0
  177. package/src/components/chart/stock-chart/requests.ts +337 -0
  178. package/src/components/chart/stock-chart/resolution-support.ts +96 -0
  179. package/src/components/chart/stock-chart/resolved/runtime.ts +289 -0
  180. package/src/components/chart/stock-chart/resolved.tsx +345 -0
  181. package/src/components/chart/stock-chart/settings.ts +77 -0
  182. package/src/components/chart/stock-chart/surface-runtime.ts +62 -0
  183. package/src/components/chart/stock-chart/types.ts +25 -0
  184. package/src/components/chart/stock-chart/view.tsx +155 -0
  185. package/src/components/chart/stock-chart/viewport/effects.ts +296 -0
  186. package/src/components/chart/stock-chart/viewport/index.ts +144 -0
  187. package/src/components/chart/stock-chart/viewport/runtime.ts +375 -0
  188. package/src/components/chart/time-axis-label.tsx +4 -3
  189. package/src/components/command-bar/action-runtime.ts +326 -0
  190. package/src/components/command-bar/commands/collection.ts +214 -0
  191. package/src/components/command-bar/commands/direct/index.ts +266 -0
  192. package/src/components/command-bar/commands/direct/runtime.ts +168 -0
  193. package/src/components/command-bar/commands/plugin/actions.ts +171 -0
  194. package/src/components/command-bar/commands/plugin/items.ts +166 -0
  195. package/src/components/command-bar/commands/registry.ts +263 -0
  196. package/src/components/command-bar/helpers.test.ts +13 -21
  197. package/src/components/command-bar/helpers.ts +51 -272
  198. package/src/components/command-bar/keyboard-handlers.ts +306 -0
  199. package/src/components/command-bar/keyboard-shortcuts.ts +204 -0
  200. package/src/components/command-bar/layout-items/current-layout.ts +120 -0
  201. package/src/components/command-bar/layout-items/focused-pane.ts +143 -0
  202. package/src/components/command-bar/layout-items/types.ts +25 -0
  203. package/src/components/command-bar/layout-items/window-mode.ts +72 -0
  204. package/src/components/command-bar/layout-items.ts +93 -0
  205. package/src/components/command-bar/list/model.ts +81 -0
  206. package/src/components/command-bar/list/navigation.ts +125 -0
  207. package/src/components/command-bar/list/view.tsx +316 -0
  208. package/src/components/command-bar/multi-select-picker.tsx +219 -0
  209. package/src/components/command-bar/multi-select-runtime.ts +91 -0
  210. package/src/components/command-bar/pane-actions.ts +124 -0
  211. package/src/components/command-bar/pane-settings.ts +227 -0
  212. package/src/components/command-bar/pane-templates/items.ts +203 -0
  213. package/src/components/command-bar/pane-templates/workflow-route.ts +81 -0
  214. package/src/components/command-bar/pane-templates/workflow.ts +282 -0
  215. package/src/components/command-bar/panel/body.tsx +311 -0
  216. package/src/components/command-bar/panel/index.tsx +196 -0
  217. package/src/components/command-bar/panel/layout.ts +117 -0
  218. package/src/components/command-bar/panel/runtime.ts +280 -0
  219. package/src/components/command-bar/panel/state.ts +138 -0
  220. package/src/components/command-bar/panel/types.ts +78 -0
  221. package/src/components/command-bar/picker-activation.ts +126 -0
  222. package/src/components/command-bar/routes/root/command-items.ts +176 -0
  223. package/src/components/command-bar/routes/root/provider-search.ts +200 -0
  224. package/src/components/command-bar/routes/root/results.ts +233 -0
  225. package/src/components/command-bar/routes/root/runtime.ts +315 -0
  226. package/src/components/command-bar/routes/root/selection.ts +228 -0
  227. package/src/components/command-bar/routes/root/shortcut-feedback.ts +108 -0
  228. package/src/components/command-bar/routes/root/shortcut-items.ts +159 -0
  229. package/src/components/command-bar/routes/root/shortcuts.test.ts +151 -0
  230. package/src/components/command-bar/routes/root/shortcuts.ts +188 -0
  231. package/src/components/command-bar/routes/ticker-search/actions.ts +142 -0
  232. package/src/components/command-bar/routes/ticker-search/results.ts +94 -0
  233. package/src/components/command-bar/routes/ticker-search/route.ts +151 -0
  234. package/src/components/command-bar/routing/actions.ts +299 -0
  235. package/src/components/command-bar/routing/confirm.ts +91 -0
  236. package/src/components/command-bar/routing/effects.ts +82 -0
  237. package/src/components/command-bar/routing/launch-request.ts +59 -0
  238. package/src/components/command-bar/routing/list-state.ts +242 -0
  239. package/src/components/command-bar/routing/navigation-state.ts +147 -0
  240. package/src/components/command-bar/selection-runtime.ts +291 -0
  241. package/src/components/command-bar/surface/environment.ts +124 -0
  242. package/src/components/command-bar/surface/index.test.tsx +864 -0
  243. package/src/components/command-bar/surface/index.tsx +344 -0
  244. package/src/components/command-bar/surface/pane.test.tsx +703 -0
  245. package/src/components/command-bar/surface/portfolio.test.tsx +452 -0
  246. package/src/components/command-bar/surface/test-harness.tsx +403 -0
  247. package/src/components/command-bar/theme-preview.ts +84 -0
  248. package/src/components/command-bar/view-model.test.ts +3 -3
  249. package/src/components/command-bar/view-model.ts +7 -6
  250. package/src/components/command-bar/workflow/alert.ts +164 -0
  251. package/src/components/command-bar/workflow/body.tsx +179 -0
  252. package/src/components/command-bar/workflow/broker.ts +158 -0
  253. package/src/components/command-bar/workflow/builtin.ts +134 -0
  254. package/src/components/command-bar/workflow/collection-actions.ts +257 -0
  255. package/src/components/command-bar/workflow/coordinator.ts +193 -0
  256. package/src/components/command-bar/workflow/field-row.tsx +214 -0
  257. package/src/components/command-bar/workflow/fields.ts +230 -0
  258. package/src/components/command-bar/workflow/ops.test.ts +197 -0
  259. package/src/components/command-bar/workflow/ops.ts +268 -0
  260. package/src/components/command-bar/workflow/route-actions.ts +232 -0
  261. package/src/components/command-bar/workflow/route-builder.ts +56 -0
  262. package/src/components/command-bar/workflow/runtime.ts +234 -0
  263. package/src/components/command-bar/workflow/submit.ts +213 -0
  264. package/src/components/command-bar/workflow/tickers.ts +214 -0
  265. package/src/components/command-bar/workflow/types.ts +127 -0
  266. package/src/components/data-table/stack-view.test.tsx +218 -0
  267. package/src/components/data-table/stack-view.tsx +59 -0
  268. package/src/components/data-table/view.test.tsx +254 -0
  269. package/src/components/data-table/view.tsx +306 -0
  270. package/src/components/feed-data-table/stack-view.test.tsx +148 -0
  271. package/src/components/feed-data-table/stack-view.tsx +359 -0
  272. package/src/components/index.ts +14 -14
  273. package/src/components/input-search-bar.tsx +94 -0
  274. package/src/components/layout/detached-pane-shell.tsx +22 -16
  275. package/src/components/layout/floating-pane.tsx +19 -24
  276. package/src/components/layout/header.tsx +9 -21
  277. package/src/components/layout/pane/content.test.tsx +64 -0
  278. package/src/components/layout/pane/content.tsx +44 -0
  279. package/src/components/layout/pane/cycle-order.test.ts +30 -0
  280. package/src/components/layout/pane/cycle-order.ts +32 -0
  281. package/src/components/layout/pane/footer/index.test.tsx +131 -0
  282. package/src/components/layout/pane/footer/index.tsx +246 -0
  283. package/src/components/layout/pane/footer/model.ts +99 -0
  284. package/src/components/layout/pane/footer/registration.tsx +114 -0
  285. package/src/components/layout/pane/frame.tsx +55 -0
  286. package/src/components/layout/pane/header.test.tsx +39 -0
  287. package/src/components/layout/pane/header.tsx +252 -0
  288. package/src/components/layout/pane/index.tsx +105 -0
  289. package/src/components/layout/pane/keyboard-scroll.test.tsx +146 -0
  290. package/src/components/layout/pane/sizing.ts +66 -0
  291. package/src/components/layout/pane/title.ts +38 -0
  292. package/src/components/layout/shell/action-menu-overlay.tsx +72 -0
  293. package/src/components/layout/shell/active-drag.ts +221 -0
  294. package/src/components/layout/shell/chrome.ts +8 -0
  295. package/src/components/layout/shell/drag/index.ts +432 -0
  296. package/src/components/layout/shell/drag/overlays.tsx +78 -0
  297. package/src/components/layout/shell/drag/runtime.ts +264 -0
  298. package/src/components/layout/shell/index.test.tsx +788 -0
  299. package/src/components/layout/shell/index.tsx +451 -0
  300. package/src/components/layout/shell/layout-state.ts +97 -0
  301. package/src/components/layout/shell/menu.ts +152 -0
  302. package/src/components/layout/shell/native/pointer-runtime.ts +184 -0
  303. package/src/components/layout/shell/native/surfaces.ts +127 -0
  304. package/src/components/layout/shell/native/window-state.ts +208 -0
  305. package/src/components/layout/shell/pane/actions.ts +129 -0
  306. package/src/components/layout/shell/pane/layers.tsx +234 -0
  307. package/src/components/layout/shell/pane/management-shortcuts.ts +114 -0
  308. package/src/components/layout/shell/shortcuts.ts +47 -0
  309. package/src/components/layout/shell/terminal-pointer-runtime.ts +326 -0
  310. package/src/components/layout/shell/window-mode/index.ts +253 -0
  311. package/src/components/layout/shell/window-mode/overlays.tsx +208 -0
  312. package/src/components/layout/status-bar.test.tsx +1 -101
  313. package/src/components/layout/status-bar.tsx +1 -1
  314. package/src/components/layout/window-edit/mode.test.ts +55 -0
  315. package/src/components/layout/window-edit/mode.ts +422 -0
  316. package/src/components/layout/window-edit/presentation.ts +123 -0
  317. package/src/components/layout/window-edit/status.tsx +101 -0
  318. package/src/components/markdown-text.tsx +3 -3
  319. package/src/components/onboarding/onboarding-steps.tsx +248 -0
  320. package/src/components/onboarding/onboarding-wizard.tsx +97 -1035
  321. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +144 -0
  322. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +97 -0
  323. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +42 -0
  324. package/src/components/onboarding/portfolio-step/choose-panel.tsx +42 -0
  325. package/src/components/onboarding/portfolio-step/index.tsx +70 -0
  326. package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +62 -0
  327. package/src/components/onboarding/portfolio-step/types.ts +25 -0
  328. package/src/components/onboarding/portfolio-step/utils.ts +15 -0
  329. package/src/components/onboarding/wizard-broker-sync.ts +230 -0
  330. package/src/components/onboarding/wizard-keyboard.ts +264 -0
  331. package/src/components/onboarding/wizard-model.ts +21 -0
  332. package/src/components/pane-settings-dialog/desktop.tsx +315 -0
  333. package/src/components/pane-settings-dialog/field-dialogs.tsx +213 -0
  334. package/src/components/pane-settings-dialog/tui.tsx +73 -0
  335. package/src/components/pane-settings-dialog/value.ts +36 -0
  336. package/src/components/pane-settings-dialog.tsx +18 -622
  337. package/src/components/pane-template-wizard.tsx +1 -1
  338. package/src/components/price-selector-dialog.tsx +2 -2
  339. package/src/components/price-sparkline/bitmap.ts +150 -0
  340. package/src/components/price-sparkline/index.ts +46 -0
  341. package/src/components/price-sparkline/model.ts +119 -0
  342. package/src/components/price-sparkline/view.tsx +214 -0
  343. package/src/components/quote-flash.ts +103 -0
  344. package/src/components/speedometer-gauge/desktop.tsx +151 -0
  345. package/src/components/speedometer-gauge/index.tsx +44 -0
  346. package/src/components/speedometer-gauge/model.ts +60 -0
  347. package/src/components/speedometer-gauge/terminal.tsx +261 -0
  348. package/src/components/ticker/badge/format.ts +42 -0
  349. package/src/components/ticker/badge/index.tsx +100 -0
  350. package/src/components/ticker/badge/list.tsx +57 -0
  351. package/src/components/ticker/badge/text.test.tsx +223 -0
  352. package/src/components/ticker/badge/text.tsx +156 -0
  353. package/src/components/ticker/list-table-view.tsx +297 -0
  354. package/src/components/ticker/list-table.test.tsx +177 -0
  355. package/src/components/toggle-list.tsx +1 -1
  356. package/src/components/ui/data-table/index.tsx +26 -0
  357. package/src/components/ui/data-table/opentui/index.tsx +467 -0
  358. package/src/components/ui/data-table/opentui/model.test.ts +47 -0
  359. package/src/components/ui/data-table/opentui/model.ts +66 -0
  360. package/src/components/ui/data-table/types.ts +81 -0
  361. package/src/components/ui/index.ts +1 -3
  362. package/src/components/ui/multi-select/dialog.tsx +336 -0
  363. package/src/components/ui/native-select.tsx +1 -1
  364. package/src/components/ui/shortcut-hint.tsx +1 -1
  365. package/src/components/ui/tabs.tsx +11 -5
  366. package/src/components/ui/toggle.tsx +1 -1
  367. package/src/components/ui/ui.test.tsx +3 -75
  368. package/src/components/use-external-link-footer.ts +1 -1
  369. package/src/core/app-services.ts +3 -0
  370. package/src/core/state/app/layout-reducer.ts +196 -0
  371. package/src/core/state/app/layout.ts +406 -0
  372. package/src/core/state/app/state.ts +340 -0
  373. package/src/core/state/app/types.ts +116 -0
  374. package/src/core/state/session-persistence.ts +1 -1
  375. package/src/data/app-persistence.ts +1 -1
  376. package/src/data/config/layout.ts +227 -0
  377. package/src/data/config/store/index.test.ts +500 -0
  378. package/src/data/config/store/index.ts +64 -0
  379. package/src/data/config/store/node.ts +101 -0
  380. package/src/data/config/store/normalize.ts +403 -0
  381. package/src/data/plugin-state-store.ts +3 -3
  382. package/src/data/resource-store.ts +3 -3
  383. package/src/data/session-store.ts +3 -3
  384. package/src/data/sqlite/cache.test.ts +151 -0
  385. package/src/data/sqlite/database.ts +68 -0
  386. package/src/data/sqlite/json.ts +16 -0
  387. package/src/data/sqlite/retry.test.ts +63 -0
  388. package/src/data/ticker-repository.ts +1 -1
  389. package/src/data/ticker-store.ts +2 -2
  390. package/src/market-data/coordinator/auxiliary.ts +252 -0
  391. package/src/market-data/coordinator/chart.ts +141 -0
  392. package/src/market-data/coordinator/entries.ts +129 -0
  393. package/src/market-data/coordinator/events.ts +74 -0
  394. package/src/market-data/coordinator/financials.ts +281 -0
  395. package/src/market-data/coordinator/index.test.ts +840 -0
  396. package/src/market-data/coordinator/index.ts +388 -0
  397. package/src/market-data/coordinator/quotes.ts +329 -0
  398. package/src/market-data/coordinator/subscriptions.test.ts +209 -0
  399. package/src/market-data/hooks.test.tsx +42 -1
  400. package/src/market-data/hooks.tsx +83 -18
  401. package/src/market-data/market/format.test.ts +109 -0
  402. package/src/market-data/market/format.ts +293 -0
  403. package/src/market-data/market/freshness.ts +140 -0
  404. package/src/market-data/market/status.ts +112 -0
  405. package/src/market-data/quotes/contributions.ts +194 -0
  406. package/src/market-data/quotes/freshness.test.ts +150 -0
  407. package/src/market-data/quotes/freshness.ts +36 -0
  408. package/src/market-data/quotes/resolution.test.ts +277 -0
  409. package/src/market-data/quotes/resolution.ts +468 -0
  410. package/src/market-data/quotes/time.test.ts +34 -0
  411. package/src/market-data/quotes/time.ts +59 -0
  412. package/src/market-data/request-types.ts +10 -3
  413. package/src/market-data/result-types.ts +3 -3
  414. package/src/market-data/selectors.ts +2 -2
  415. package/src/news/aggregator.ts +29 -219
  416. package/src/news/news-model.ts +208 -0
  417. package/src/news/ticker-symbols.ts +1 -1
  418. package/src/news/types.ts +3 -3
  419. package/src/plugins/builtin/account-management/footer.ts +39 -0
  420. package/src/plugins/builtin/account-management/form-components.tsx +162 -0
  421. package/src/plugins/builtin/account-management/keyboard.ts +90 -0
  422. package/src/plugins/builtin/account-management/model.ts +97 -0
  423. package/src/plugins/builtin/account-management/pane.tsx +389 -0
  424. package/src/plugins/builtin/account-management/password-dialog.tsx +125 -0
  425. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +6 -6
  426. package/src/plugins/builtin/ai/ask-ai.test.tsx +305 -0
  427. package/src/plugins/builtin/ai/index.tsx +4 -4
  428. package/src/plugins/builtin/ai/providers.ts +1 -1
  429. package/src/plugins/builtin/ai/runner.ts +1 -0
  430. package/src/plugins/builtin/ai/screener/action-bar.tsx +58 -0
  431. package/src/plugins/builtin/ai/screener/contract.test.ts +117 -0
  432. package/src/plugins/builtin/ai/screener/contract.ts +174 -0
  433. package/src/plugins/builtin/ai/screener/editor-runtime.ts +110 -0
  434. package/src/plugins/builtin/ai/screener/editor.tsx +125 -0
  435. package/src/plugins/builtin/ai/screener/footer.ts +93 -0
  436. package/src/plugins/builtin/ai/screener/keyboard.ts +90 -0
  437. package/src/plugins/builtin/ai/screener/market-runtime.ts +99 -0
  438. package/src/plugins/builtin/ai/screener/model.ts +107 -0
  439. package/src/plugins/builtin/ai/screener/pane.test.tsx +348 -0
  440. package/src/plugins/builtin/ai/screener/pane.tsx +412 -0
  441. package/src/plugins/builtin/ai/screener/results-view.tsx +169 -0
  442. package/src/plugins/builtin/ai/screener/results.ts +139 -0
  443. package/src/plugins/builtin/ai/screener/runner.ts +144 -0
  444. package/src/plugins/builtin/ai/screener/tabs-bar.tsx +65 -0
  445. package/src/plugins/builtin/ai/settings.ts +3 -3
  446. package/src/plugins/builtin/ai/ticker-context.ts +1 -1
  447. package/src/plugins/builtin/alerts/command.ts +86 -0
  448. package/src/plugins/builtin/alerts/constants.ts +3 -0
  449. package/src/plugins/builtin/alerts/format.ts +68 -0
  450. package/src/plugins/builtin/alerts/index.test.tsx +5 -4
  451. package/src/plugins/builtin/alerts/index.tsx +18 -473
  452. package/src/plugins/builtin/alerts/pane.tsx +304 -0
  453. package/src/plugins/builtin/alerts/quotes.ts +57 -0
  454. package/src/plugins/builtin/alerts/storage.ts +20 -0
  455. package/src/plugins/builtin/analytics/broker-performance.ts +142 -0
  456. package/src/plugins/builtin/analytics/display.ts +44 -0
  457. package/src/plugins/builtin/analytics/index.test.tsx +144 -5
  458. package/src/plugins/builtin/analytics/index.tsx +108 -468
  459. package/src/plugins/builtin/analytics/metrics.ts +1 -1
  460. package/src/plugins/builtin/analytics/pane-model.ts +297 -0
  461. package/src/plugins/builtin/analytics/portfolio-selection.ts +10 -0
  462. package/src/plugins/builtin/analytics/sector-model.ts +172 -0
  463. package/src/plugins/builtin/analytics/view.tsx +207 -0
  464. package/src/plugins/builtin/broker-manager/detail.tsx +232 -0
  465. package/src/plugins/builtin/broker-manager/footer.ts +62 -0
  466. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -3
  467. package/src/plugins/builtin/broker-manager/index.tsx +125 -527
  468. package/src/plugins/builtin/broker-manager/keyboard.ts +104 -0
  469. package/src/plugins/builtin/broker-manager/model.test.ts +39 -1
  470. package/src/plugins/builtin/broker-manager/model.ts +14 -8
  471. package/src/plugins/builtin/broker-manager/pane-actions.tsx +181 -0
  472. package/src/plugins/builtin/broker-manager/table.ts +86 -0
  473. package/src/plugins/builtin/buildout/cells.tsx +203 -0
  474. package/src/plugins/builtin/buildout/data-runtime.ts +238 -0
  475. package/src/plugins/builtin/buildout/detail/company.tsx +219 -0
  476. package/src/plugins/builtin/buildout/detail/index.tsx +105 -0
  477. package/src/plugins/builtin/buildout/detail/intel.tsx +61 -0
  478. package/src/plugins/builtin/buildout/detail/satellite-images.tsx +80 -0
  479. package/src/plugins/builtin/buildout/detail/site.tsx +142 -0
  480. package/src/plugins/builtin/buildout/detail/ui.tsx +355 -0
  481. package/src/plugins/builtin/buildout/detail/values.ts +67 -0
  482. package/src/plugins/builtin/buildout/format.ts +163 -0
  483. package/src/plugins/builtin/buildout/model/index.ts +135 -0
  484. package/src/plugins/builtin/buildout/model/normalizers.ts +396 -0
  485. package/src/plugins/builtin/buildout/model/types.ts +318 -0
  486. package/src/plugins/builtin/buildout/pane/header.tsx +55 -0
  487. package/src/plugins/builtin/buildout/pane/index.tsx +405 -0
  488. package/src/plugins/builtin/buildout/pane/status.tsx +84 -0
  489. package/src/plugins/builtin/buildout/table-model.ts +367 -0
  490. package/src/plugins/builtin/changelog/index.tsx +370 -0
  491. package/src/plugins/builtin/chat/channels.ts +157 -0
  492. package/src/plugins/builtin/chat/chat.test.tsx +1048 -0
  493. package/src/plugins/builtin/chat/composer-commands.test.ts +30 -0
  494. package/src/plugins/builtin/chat/composer-commands.ts +34 -0
  495. package/src/plugins/builtin/chat/content/channel-navigation.ts +157 -0
  496. package/src/plugins/builtin/chat/content/composer-runtime.ts +251 -0
  497. package/src/plugins/builtin/chat/content/composer.tsx +148 -0
  498. package/src/plugins/builtin/chat/content/index.tsx +407 -0
  499. package/src/plugins/builtin/chat/content/layout-metrics.ts +72 -0
  500. package/src/plugins/builtin/chat/content/scroll.ts +278 -0
  501. package/src/plugins/builtin/chat/content/selection-runtime.ts +66 -0
  502. package/src/plugins/builtin/chat/content/shortcuts.ts +212 -0
  503. package/src/plugins/builtin/chat/content/snapshot.ts +154 -0
  504. package/src/plugins/builtin/chat/content/transcript.tsx +155 -0
  505. package/src/plugins/builtin/chat/content/types.ts +26 -0
  506. package/src/plugins/builtin/chat/content/user-map.ts +21 -0
  507. package/src/plugins/builtin/chat/controller/channel-actions.ts +87 -0
  508. package/src/plugins/builtin/chat/controller/channels.ts +159 -0
  509. package/src/plugins/builtin/chat/controller/connections.ts +90 -0
  510. package/src/plugins/builtin/chat/controller/draft-sync.ts +41 -0
  511. package/src/plugins/builtin/chat/controller/fetch.ts +83 -0
  512. package/src/plugins/builtin/chat/controller/index.ts +488 -0
  513. package/src/plugins/builtin/chat/controller/lifecycle.ts +141 -0
  514. package/src/plugins/builtin/chat/controller/message-loading.ts +97 -0
  515. package/src/plugins/builtin/chat/controller/messages.ts +173 -0
  516. package/src/plugins/builtin/chat/controller/notifications.ts +56 -0
  517. package/src/plugins/builtin/chat/controller/persistence.ts +166 -0
  518. package/src/plugins/builtin/chat/controller/realtime.ts +91 -0
  519. package/src/plugins/builtin/chat/controller/send.ts +89 -0
  520. package/src/plugins/builtin/chat/controller/session-runtime.ts +156 -0
  521. package/src/plugins/builtin/chat/controller/session.ts +39 -0
  522. package/src/plugins/builtin/chat/controller/state.ts +162 -0
  523. package/src/plugins/builtin/chat/controller/storage.ts +136 -0
  524. package/src/plugins/builtin/chat/controller/utils.ts +75 -0
  525. package/src/plugins/builtin/chat/controller/view.ts +96 -0
  526. package/src/plugins/builtin/chat/controller.test.ts +1302 -0
  527. package/src/plugins/builtin/chat/index.tsx +4 -0
  528. package/src/plugins/builtin/chat/layout.ts +330 -0
  529. package/src/plugins/builtin/chat/message/action-chip.tsx +38 -0
  530. package/src/plugins/builtin/chat/message/desktop.tsx +158 -0
  531. package/src/plugins/builtin/chat/message/inline-tokens.tsx +118 -0
  532. package/src/plugins/builtin/chat/message/profile-popover.tsx +72 -0
  533. package/src/plugins/builtin/chat/message/render-state.ts +67 -0
  534. package/src/plugins/builtin/chat/message/terminal.tsx +132 -0
  535. package/src/plugins/builtin/chat/message/types.ts +18 -0
  536. package/src/plugins/builtin/chat/pane.tsx +100 -0
  537. package/src/plugins/builtin/chat/profile-popover.ts +49 -0
  538. package/src/plugins/builtin/chat/sidebar.test.tsx +455 -0
  539. package/src/plugins/builtin/chat/sidebar.tsx +296 -0
  540. package/src/plugins/builtin/chat/status-widget.tsx +103 -0
  541. package/src/plugins/builtin/chat/test-harness.tsx +212 -0
  542. package/src/plugins/builtin/cloud/auth-actions.tsx +72 -0
  543. package/src/plugins/builtin/cloud/auth-commands.ts +115 -0
  544. package/src/plugins/builtin/cloud/index.tsx +11 -0
  545. package/src/plugins/builtin/cloud/plugin.tsx +185 -0
  546. package/src/plugins/builtin/cloud-tweets/footer.ts +40 -0
  547. package/src/plugins/builtin/cloud-tweets/index.ts +1 -0
  548. package/src/plugins/builtin/cloud-tweets/keyboard.ts +65 -0
  549. package/src/plugins/builtin/cloud-tweets/model.ts +257 -0
  550. package/src/plugins/builtin/cloud-tweets/pane.tsx +318 -0
  551. package/src/plugins/builtin/cloud-tweets/registration.ts +87 -0
  552. package/src/plugins/builtin/cloud-tweets/search-bar.tsx +49 -0
  553. package/src/plugins/builtin/cloud-tweets/table.tsx +331 -0
  554. package/src/plugins/builtin/comparison-chart/index.test.tsx +645 -0
  555. package/src/plugins/builtin/comparison-chart/index.tsx +194 -0
  556. package/src/plugins/builtin/congress-trades/detail.tsx +154 -0
  557. package/src/plugins/builtin/congress-trades/footer.ts +67 -0
  558. package/src/plugins/builtin/congress-trades/keyboard.ts +112 -0
  559. package/src/plugins/builtin/congress-trades/model.ts +224 -0
  560. package/src/plugins/builtin/congress-trades/pane.tsx +331 -0
  561. package/src/plugins/builtin/congress-trades/table.tsx +84 -0
  562. package/src/plugins/builtin/correlation/compute.ts +0 -7
  563. package/src/plugins/builtin/correlation/index.test.tsx +5 -5
  564. package/src/plugins/builtin/correlation/index.tsx +36 -208
  565. package/src/plugins/builtin/correlation/matrix/model.ts +155 -0
  566. package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +56 -0
  567. package/src/plugins/builtin/correlation/relationship/controls.tsx +53 -0
  568. package/src/plugins/builtin/correlation/relationship/history.ts +72 -0
  569. package/src/plugins/builtin/correlation/relationship/model.ts +279 -0
  570. package/src/plugins/builtin/correlation/relationship/pane.tsx +359 -0
  571. package/src/plugins/builtin/correlation/relationship/view-model.ts +120 -0
  572. package/src/plugins/builtin/correlation/settings.ts +6 -6
  573. package/src/plugins/builtin/debug/index.tsx +365 -0
  574. package/src/plugins/builtin/earnings/data/cache.test.ts +94 -0
  575. package/src/plugins/builtin/earnings/data/cache.ts +145 -0
  576. package/src/plugins/builtin/earnings/data/data.test.ts +90 -0
  577. package/src/plugins/builtin/earnings/data/data.ts +115 -0
  578. package/src/plugins/builtin/earnings/index.test.ts +12 -0
  579. package/src/plugins/builtin/earnings/index.tsx +74 -266
  580. package/src/plugins/builtin/earnings/model.ts +82 -0
  581. package/src/plugins/builtin/earnings/table.ts +206 -0
  582. package/src/plugins/builtin/econ/calendar-model.test.ts +48 -0
  583. package/src/plugins/builtin/econ/calendar-model.ts +217 -0
  584. package/src/plugins/builtin/econ/calendar-source.ts +1 -1
  585. package/src/plugins/builtin/econ/detail-view.tsx +319 -0
  586. package/src/plugins/builtin/econ/fred-cache.test.ts +58 -0
  587. package/src/plugins/builtin/econ/fred-cache.ts +106 -0
  588. package/src/plugins/builtin/econ/fred-series-map.ts +0 -4
  589. package/src/plugins/builtin/econ/index.tsx +46 -474
  590. package/src/plugins/builtin/fear-greed/cache.test.ts +75 -0
  591. package/src/plugins/builtin/fear-greed/cache.ts +142 -0
  592. package/src/plugins/builtin/fear-greed/charts.tsx +305 -0
  593. package/src/plugins/builtin/fear-greed/data.test.ts +72 -0
  594. package/src/plugins/builtin/fear-greed/data.ts +370 -0
  595. package/src/plugins/builtin/fear-greed/format.ts +96 -0
  596. package/src/plugins/builtin/fear-greed/index.tsx +13 -555
  597. package/src/plugins/builtin/fear-greed/pane.tsx +169 -0
  598. package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
  599. package/src/plugins/builtin/fx-matrix/pairs.ts +0 -22
  600. package/src/plugins/builtin/help/components.tsx +92 -0
  601. package/src/plugins/builtin/help/index.test.tsx +167 -0
  602. package/src/plugins/builtin/help/index.tsx +256 -0
  603. package/src/plugins/builtin/help/shortcut-model.ts +145 -0
  604. package/src/plugins/builtin/holders/format.ts +44 -0
  605. package/src/plugins/builtin/holders/index.test.ts +2 -76
  606. package/src/plugins/builtin/holders/index.tsx +3 -959
  607. package/src/plugins/builtin/holders/pane.tsx +331 -0
  608. package/src/plugins/builtin/holders/table-model.ts +84 -0
  609. package/src/plugins/builtin/holders/thirteenf-match.ts +76 -0
  610. package/src/plugins/builtin/holders/treemap-layout.ts +231 -0
  611. package/src/plugins/builtin/holders/treemap.tsx +318 -0
  612. package/src/plugins/builtin/holders/types.ts +51 -0
  613. package/src/plugins/builtin/insider/index.tsx +5 -5
  614. package/src/plugins/builtin/layout-manager/index.tsx +247 -0
  615. package/src/plugins/builtin/market-movers/index.tsx +44 -282
  616. package/src/plugins/builtin/market-movers/model.ts +146 -0
  617. package/src/plugins/builtin/market-movers/screener.test.ts +31 -1
  618. package/src/plugins/builtin/market-movers/screener.ts +106 -12
  619. package/src/plugins/builtin/market-movers/table.ts +95 -0
  620. package/src/plugins/builtin/news/index.tsx +194 -0
  621. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +98 -0
  622. package/src/plugins/builtin/news/wire/breaking/notifications.ts +110 -0
  623. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +156 -0
  624. package/src/plugins/builtin/news/wire/breaking/pane.tsx +68 -0
  625. package/src/plugins/builtin/news/wire/categories.ts +114 -0
  626. package/src/plugins/builtin/news/wire/default-feeds.ts +3 -0
  627. package/src/plugins/builtin/news/wire/feed-config.test.ts +90 -0
  628. package/src/plugins/builtin/news/wire/feed-config.ts +216 -0
  629. package/src/plugins/builtin/news/wire/index.tsx +129 -0
  630. package/src/plugins/builtin/news/wire/industry-pane.tsx +126 -0
  631. package/src/plugins/builtin/news/wire/news/detail-view.tsx +306 -0
  632. package/src/plugins/builtin/news/wire/news/footer.ts +77 -0
  633. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +98 -0
  634. package/src/plugins/builtin/news/wire/news/query-presets.ts +40 -0
  635. package/src/plugins/builtin/news/wire/news/table.test.tsx +157 -0
  636. package/src/plugins/builtin/news/wire/news/table.tsx +270 -0
  637. package/src/plugins/builtin/news/wire/persisted-articles.ts +98 -0
  638. package/src/plugins/builtin/news/wire/read-state.ts +76 -0
  639. package/src/plugins/builtin/news/wire/rss/parser.test.ts +116 -0
  640. package/src/plugins/builtin/news/wire/rss/parser.ts +213 -0
  641. package/src/plugins/builtin/news/wire/rss/source.test.ts +104 -0
  642. package/src/plugins/builtin/news/wire/rss/source.ts +181 -0
  643. package/src/plugins/builtin/notes/index.tsx +49 -0
  644. package/src/plugins/builtin/notes/markdown-note-preview.tsx +39 -0
  645. package/src/plugins/builtin/notes/model.ts +36 -0
  646. package/src/plugins/builtin/notes/quick-notes-pane.tsx +334 -0
  647. package/src/plugins/builtin/notes/text-state.ts +11 -0
  648. package/src/plugins/builtin/notes/ticker-notes-tab.tsx +112 -0
  649. package/src/plugins/builtin/options/index.tsx +44 -0
  650. package/src/plugins/builtin/options/table.ts +209 -0
  651. package/src/plugins/builtin/options/types.ts +33 -0
  652. package/src/plugins/builtin/options/view.test.tsx +185 -0
  653. package/src/plugins/builtin/options/view.tsx +264 -0
  654. package/src/plugins/builtin/plugin-groups.ts +25 -5
  655. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +50 -0
  656. package/src/plugins/builtin/portfolio-list/account-metrics.ts +46 -0
  657. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +224 -0
  658. package/src/plugins/builtin/portfolio-list/cli/render.ts +203 -0
  659. package/src/plugins/builtin/portfolio-list/cli/shared.ts +21 -0
  660. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +224 -0
  661. package/src/plugins/builtin/portfolio-list/column-values.ts +284 -0
  662. package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
  663. package/src/plugins/builtin/portfolio-list/header.tsx +7 -98
  664. package/src/plugins/builtin/portfolio-list/index.test.ts +133 -0
  665. package/src/plugins/builtin/portfolio-list/index.tsx +2 -1
  666. package/src/plugins/builtin/portfolio-list/metrics.test.ts +242 -0
  667. package/src/plugins/builtin/portfolio-list/metrics.ts +5 -462
  668. package/src/plugins/builtin/portfolio-list/pane/data.ts +171 -0
  669. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1267 -0
  670. package/src/plugins/builtin/portfolio-list/pane/index.tsx +385 -0
  671. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +159 -0
  672. package/src/plugins/builtin/portfolio-list/position-metrics.ts +123 -0
  673. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +326 -0
  674. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +135 -0
  675. package/src/plugins/builtin/portfolio-list/settings.ts +5 -3
  676. package/src/plugins/builtin/portfolio-list/summary/index.test.ts +126 -0
  677. package/src/plugins/builtin/portfolio-list/summary/index.tsx +392 -0
  678. package/src/plugins/builtin/portfolio-list/summary/totals.ts +86 -0
  679. package/src/plugins/builtin/portfolio-list/table.tsx +1 -1
  680. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  681. package/src/plugins/builtin/research/analyst-pane.tsx +383 -0
  682. package/src/plugins/builtin/research/corporate-actions-pane.tsx +413 -0
  683. package/src/plugins/builtin/research/index.test.ts +248 -2
  684. package/src/plugins/builtin/research/index.tsx +30 -760
  685. package/src/plugins/builtin/research/relative-valuation-pane.tsx +189 -0
  686. package/src/plugins/builtin/sec/filing-display.tsx +84 -0
  687. package/src/plugins/builtin/sec/index.tsx +342 -0
  688. package/src/plugins/builtin/sectors/index.tsx +27 -234
  689. package/src/plugins/builtin/sectors/sector-data.ts +0 -2
  690. package/src/plugins/builtin/sectors/sector-model.ts +221 -0
  691. package/src/plugins/builtin/shared/table-pane.ts +31 -0
  692. package/src/plugins/builtin/shared/ticker-request.ts +70 -0
  693. package/src/plugins/builtin/shared/ticker-surface.test.ts +57 -0
  694. package/src/plugins/builtin/shared/ticker-surface.ts +113 -0
  695. package/src/plugins/builtin/thirteenf/api.test.ts +58 -0
  696. package/src/plugins/builtin/thirteenf/api.ts +375 -0
  697. package/src/plugins/builtin/thirteenf/data.test.ts +95 -0
  698. package/src/plugins/builtin/thirteenf/data.ts +210 -0
  699. package/src/plugins/builtin/thirteenf/format.ts +55 -0
  700. package/src/plugins/builtin/thirteenf/index.tsx +80 -0
  701. package/src/plugins/builtin/thirteenf/model.test.ts +156 -0
  702. package/src/plugins/builtin/thirteenf/model.ts +618 -0
  703. package/src/plugins/builtin/thirteenf/pane.test.tsx +328 -0
  704. package/src/plugins/builtin/thirteenf/pane.tsx +826 -0
  705. package/src/plugins/builtin/thirteenf/table.ts +212 -0
  706. package/src/plugins/builtin/thirteenf/types.ts +187 -0
  707. package/src/plugins/builtin/ticker-detail/chart-pane.tsx +91 -0
  708. package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +282 -0
  709. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -5
  710. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +158 -0
  711. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/data.ts +62 -0
  712. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +234 -0
  713. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +44 -0
  714. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +51 -0
  715. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +135 -0
  716. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +192 -0
  717. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +172 -0
  718. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +110 -0
  719. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +203 -0
  720. package/src/plugins/builtin/ticker-detail/financials/model.test.ts +84 -0
  721. package/src/plugins/builtin/ticker-detail/financials/model.ts +364 -0
  722. package/src/plugins/builtin/ticker-detail/financials/pane.tsx +27 -0
  723. package/src/plugins/builtin/ticker-detail/financials/schema.ts +392 -0
  724. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +438 -0
  725. package/src/plugins/builtin/ticker-detail/index.test.tsx +780 -0
  726. package/src/plugins/builtin/ticker-detail/index.tsx +174 -17
  727. package/src/plugins/builtin/ticker-detail/overview/components.tsx +240 -0
  728. package/src/plugins/builtin/ticker-detail/overview/model.ts +168 -0
  729. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +14 -0
  730. package/src/plugins/builtin/ticker-detail/overview/types.ts +17 -0
  731. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +27 -392
  732. package/src/plugins/builtin/ticker-detail/pane.tsx +24 -136
  733. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +330 -0
  734. package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +292 -0
  735. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +180 -0
  736. package/src/plugins/builtin/ticker-detail/settings.ts +39 -33
  737. package/src/plugins/builtin/world-indices/footer.ts +28 -0
  738. package/src/plugins/builtin/world-indices/index.tsx +25 -218
  739. package/src/plugins/builtin/world-indices/model.ts +120 -0
  740. package/src/plugins/builtin/world-indices/table.tsx +96 -0
  741. package/src/plugins/builtin/yahoo/index.ts +72 -0
  742. package/src/plugins/builtin/yield-curve/index.tsx +5 -5
  743. package/src/plugins/builtin/yield-curve/treasury-data.ts +1 -1
  744. package/src/plugins/catalog-ui.ts +7 -9
  745. package/src/plugins/catalog.ts +1 -1
  746. package/src/plugins/ibkr/account-cache.ts +2 -10
  747. package/src/plugins/ibkr/broker-adapter.ts +10 -4
  748. package/src/plugins/ibkr/config.ts +5 -24
  749. package/src/plugins/ibkr/dialogs.tsx +0 -1
  750. package/src/plugins/ibkr/flex/index.test.ts +236 -0
  751. package/src/plugins/ibkr/flex/index.ts +337 -0
  752. package/src/plugins/ibkr/flex/statement-client.ts +181 -0
  753. package/src/plugins/ibkr/gateway/account-loaders.ts +266 -0
  754. package/src/plugins/ibkr/gateway/account-summary.ts +163 -0
  755. package/src/plugins/ibkr/gateway/client-lock.ts +154 -0
  756. package/src/plugins/ibkr/gateway/connection-events.ts +114 -0
  757. package/src/plugins/ibkr/gateway/connection.ts +152 -0
  758. package/src/plugins/ibkr/gateway/contracts.ts +132 -0
  759. package/src/plugins/ibkr/gateway/fundamentals.ts +83 -0
  760. package/src/plugins/ibkr/gateway/helpers.ts +33 -0
  761. package/src/plugins/ibkr/gateway/history-requests.ts +158 -0
  762. package/src/plugins/ibkr/gateway/history.ts +120 -0
  763. package/src/plugins/ibkr/gateway/instrument-lookup.ts +117 -0
  764. package/src/plugins/ibkr/gateway/market-data.ts +338 -0
  765. package/src/plugins/ibkr/gateway/order-execution.ts +218 -0
  766. package/src/plugins/ibkr/gateway/orders.ts +63 -0
  767. package/src/plugins/ibkr/gateway/price-normalization.test.ts +44 -0
  768. package/src/plugins/ibkr/gateway/price-normalization.ts +50 -0
  769. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +203 -0
  770. package/src/plugins/ibkr/gateway/quote-stream.ts +171 -0
  771. package/src/plugins/ibkr/gateway/selection.ts +51 -0
  772. package/src/plugins/ibkr/gateway/service/index.test.ts +614 -0
  773. package/src/plugins/ibkr/gateway/service/index.ts +432 -0
  774. package/src/plugins/ibkr/gateway/service/lifecycle.ts +191 -0
  775. package/src/plugins/ibkr/gateway/service/manager.ts +52 -0
  776. package/src/plugins/ibkr/gateway/service/market-data.ts +48 -0
  777. package/src/plugins/ibkr/gateway/service/native.ts +543 -0
  778. package/src/plugins/ibkr/gateway/timeouts.ts +13 -0
  779. package/src/plugins/ibkr/gateway/types.ts +27 -0
  780. package/src/plugins/ibkr/hash.ts +8 -0
  781. package/src/plugins/ibkr/index.test.tsx +1 -12
  782. package/src/plugins/ibkr/index.tsx +8 -9
  783. package/src/plugins/ibkr/instance-selection.test.ts +2 -2
  784. package/src/plugins/ibkr/instance-selection.ts +1 -1
  785. package/src/plugins/ibkr/portfolio-performance.test.ts +32 -0
  786. package/src/plugins/ibkr/portfolio-performance.ts +129 -0
  787. package/src/plugins/ibkr/trade/badge.tsx +28 -0
  788. package/src/plugins/ibkr/trade/dialogs.tsx +44 -0
  789. package/src/plugins/ibkr/trade/preview-panel.tsx +130 -0
  790. package/src/plugins/ibkr/trade/tab/actions.tsx +443 -0
  791. package/src/plugins/ibkr/trade/tab/field-editors.tsx +79 -0
  792. package/src/plugins/ibkr/trade/tab/footer.ts +67 -0
  793. package/src/plugins/ibkr/trade/tab/header.tsx +167 -0
  794. package/src/plugins/ibkr/trade/tab/index.test.tsx +178 -0
  795. package/src/plugins/ibkr/trade/tab/index.tsx +302 -0
  796. package/src/plugins/ibkr/trade/tab/model.ts +159 -0
  797. package/src/plugins/ibkr/trade/tab/order-request.ts +64 -0
  798. package/src/plugins/ibkr/trade/tab/shortcuts.ts +89 -0
  799. package/src/plugins/ibkr/trade/tab/state-sync.ts +86 -0
  800. package/src/plugins/ibkr/trade/ticket-panel.tsx +301 -0
  801. package/src/plugins/ibkr/trade/utils.test.ts +175 -0
  802. package/src/plugins/ibkr/trade/utils.ts +150 -0
  803. package/src/plugins/ibkr/trading/pane-view.tsx +175 -0
  804. package/src/plugins/ibkr/trading/pane.tsx +332 -0
  805. package/src/plugins/ibkr/trading/state.test.ts +109 -0
  806. package/src/plugins/ibkr/trading/state.ts +335 -0
  807. package/src/plugins/pane-manager/dock-tree.ts +282 -0
  808. package/src/plugins/pane-manager/docking.ts +202 -0
  809. package/src/plugins/pane-manager/floating-actions.ts +158 -0
  810. package/src/plugins/pane-manager/floating.ts +76 -0
  811. package/src/plugins/pane-manager/gridlock-inference.ts +126 -0
  812. package/src/plugins/pane-manager/gridlock.ts +34 -0
  813. package/src/plugins/pane-manager/layout-state.ts +160 -0
  814. package/src/plugins/pane-manager/queries.ts +75 -0
  815. package/src/plugins/pane-manager/types.ts +24 -0
  816. package/src/plugins/pane-manager.test.ts +79 -4
  817. package/src/plugins/pane-manager.ts +57 -1061
  818. package/src/plugins/prediction-markets/chart.test.tsx +2 -2
  819. package/src/plugins/prediction-markets/controller/catalog.ts +317 -0
  820. package/src/plugins/prediction-markets/controller/data.ts +171 -0
  821. package/src/plugins/prediction-markets/controller/detail.ts +243 -0
  822. package/src/plugins/prediction-markets/controller/effects.ts +253 -0
  823. package/src/plugins/prediction-markets/controller/index.ts +374 -0
  824. package/src/plugins/prediction-markets/controller/keyboard.ts +220 -0
  825. package/src/plugins/prediction-markets/controller/live-updates.ts +85 -0
  826. package/src/plugins/prediction-markets/controller/selection.ts +59 -0
  827. package/src/plugins/prediction-markets/controller/status.ts +104 -0
  828. package/src/plugins/prediction-markets/detail/outcomes.tsx +1 -1
  829. package/src/plugins/prediction-markets/metrics.ts +4 -14
  830. package/src/plugins/prediction-markets/navigation.ts +0 -13
  831. package/src/plugins/prediction-markets/pane.test.tsx +2 -198
  832. package/src/plugins/prediction-markets/plugin.test.tsx +2 -2
  833. package/src/plugins/prediction-markets/services/fetch.ts +1 -5
  834. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +299 -0
  835. package/src/plugins/prediction-markets/services/kalshi/normalize.ts +209 -0
  836. package/src/plugins/prediction-markets/services/kalshi/types.ts +89 -0
  837. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +134 -0
  838. package/src/plugins/prediction-markets/services/polymarket/detail.ts +307 -0
  839. package/src/plugins/prediction-markets/services/polymarket/normalize.ts +239 -0
  840. package/src/plugins/prediction-markets/services/polymarket/types.ts +66 -0
  841. package/src/plugins/prediction-markets/services/polymarket/ws.ts +128 -0
  842. package/src/plugins/prediction-markets/test-helpers.tsx +12 -76
  843. package/src/plugins/prediction-markets/types.ts +2 -2
  844. package/src/plugins/registry/context-menu.ts +36 -0
  845. package/src/plugins/registry/context.ts +196 -0
  846. package/src/plugins/registry/contributions.ts +174 -0
  847. package/src/plugins/registry/index.test.ts +362 -0
  848. package/src/plugins/registry/index.ts +474 -0
  849. package/src/plugins/registry/pane-settings.ts +105 -0
  850. package/src/plugins/registry/plugin-state.ts +123 -0
  851. package/src/plugins/registry/shared.ts +34 -0
  852. package/src/plugins/registry/slots.ts +70 -0
  853. package/src/plugins/runtime/context.tsx +115 -0
  854. package/src/plugins/runtime/index.test.tsx +328 -0
  855. package/src/plugins/runtime/index.tsx +111 -0
  856. package/src/plugins/runtime/state.ts +275 -0
  857. package/src/plugins/ticker-navigation.test.ts +23 -2
  858. package/src/plugins/ticker-navigation.ts +9 -3
  859. package/src/react/input.test.ts +29 -0
  860. package/src/react/input.ts +9 -0
  861. package/src/renderers/electrobun/bun/application-menu/click.test.ts +50 -0
  862. package/src/renderers/electrobun/bun/application-menu/click.ts +58 -0
  863. package/src/renderers/electrobun/bun/application-menu/index.ts +123 -0
  864. package/src/renderers/electrobun/bun/context-menu/click.test.ts +67 -0
  865. package/src/renderers/electrobun/bun/context-menu/normalize.test.ts +52 -0
  866. package/src/renderers/electrobun/bun/context-menu/normalize.ts +67 -0
  867. package/src/renderers/electrobun/bun/core-capabilities.ts +3 -0
  868. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +163 -0
  869. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +93 -0
  870. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +263 -0
  871. package/src/renderers/electrobun/bun/desktop/host-requests.ts +114 -0
  872. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +56 -0
  873. package/src/renderers/electrobun/bun/desktop/initialization.ts +139 -0
  874. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +92 -0
  875. package/src/renderers/electrobun/bun/desktop/relaunch.ts +48 -0
  876. package/src/renderers/electrobun/bun/desktop/rpc-registry.ts +43 -0
  877. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +76 -0
  878. package/src/renderers/electrobun/bun/desktop/update.ts +162 -0
  879. package/src/renderers/electrobun/bun/desktop/window-events.ts +49 -0
  880. package/src/renderers/electrobun/bun/desktop/workspace/index.test.ts +63 -0
  881. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +180 -0
  882. package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +97 -0
  883. package/src/renderers/electrobun/bun/index.ts +165 -1001
  884. package/src/renderers/electrobun/bun/tui-entry.ts +6 -0
  885. package/src/renderers/electrobun/bun/window/focus.ts +37 -0
  886. package/src/renderers/electrobun/bun/window/frame.test.ts +40 -0
  887. package/src/renderers/electrobun/shared/protocol.ts +1 -1
  888. package/src/renderers/electrobun/view/app-services.ts +4 -375
  889. package/src/renderers/electrobun/view/build-assets.ts +117 -0
  890. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +398 -0
  891. package/src/renderers/electrobun/view/config-host.ts +1 -1
  892. package/src/renderers/electrobun/view/context-menu.test.ts +10 -4
  893. package/src/renderers/electrobun/view/context-menu.ts +1 -1
  894. package/src/renderers/electrobun/view/data-table/dom.ts +211 -0
  895. package/src/renderers/electrobun/view/data-table/index.tsx +358 -0
  896. package/src/renderers/electrobun/view/data-table/row.tsx +335 -0
  897. package/src/renderers/electrobun/view/desktop/control-styles.ts +77 -0
  898. package/src/renderers/electrobun/view/desktop/controls.tsx +421 -0
  899. package/src/renderers/electrobun/view/desktop/list-view.tsx +200 -0
  900. package/src/renderers/electrobun/view/desktop/window/bridge.ts +70 -0
  901. package/src/renderers/electrobun/view/desktop/window/snapshot.test.ts +57 -0
  902. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +41 -0
  903. package/src/renderers/electrobun/view/fatal-screen.tsx +2 -2
  904. package/src/renderers/electrobun/view/host/box.tsx +160 -0
  905. package/src/renderers/electrobun/view/host/chart-surface.tsx +142 -0
  906. package/src/renderers/electrobun/view/host/input.tsx +294 -0
  907. package/src/renderers/electrobun/view/host/mouse.ts +139 -0
  908. package/src/renderers/electrobun/view/host/native.ts +49 -0
  909. package/src/renderers/electrobun/view/host/scroll-box.tsx +276 -0
  910. package/src/renderers/electrobun/view/host/style.ts +124 -0
  911. package/src/renderers/electrobun/view/host/tabs.tsx +255 -0
  912. package/src/renderers/electrobun/view/host/text.tsx +121 -0
  913. package/src/renderers/electrobun/view/http-fetch.ts +1 -1
  914. package/src/renderers/electrobun/view/input-host.tsx +12 -1
  915. package/src/renderers/electrobun/view/main.tsx +8 -3
  916. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +59 -0
  917. package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +9 -0
  918. package/src/renderers/electrobun/view/remote/asset-data-client.ts +201 -0
  919. package/src/renderers/electrobun/view/remote/persistence.ts +158 -0
  920. package/src/renderers/electrobun/view/remote/ticker-repository.ts +26 -0
  921. package/src/renderers/electrobun/view/styles.css +43 -0
  922. package/src/renderers/electrobun/view/ui-host.tsx +22 -1469
  923. package/src/renderers/opentui/chart-surface.tsx +4 -5
  924. package/src/renderers/opentui/host.tsx +30 -4
  925. package/src/renderers/opentui/image/loader.test.ts +25 -0
  926. package/src/renderers/opentui/image/loader.ts +105 -0
  927. package/src/renderers/opentui/image/surface.tsx +266 -0
  928. package/src/renderers/opentui/input-host.tsx +12 -1
  929. package/src/renderers/opentui/start.tsx +2 -2
  930. package/src/renderers/opentui/test-utils.tsx +2 -2
  931. package/src/renderers/opentui/ui-host.tsx +1 -1
  932. package/src/sources/gloomberb-cloud/index.test.ts +660 -0
  933. package/src/sources/gloomberb-cloud/index.ts +444 -0
  934. package/src/sources/gloomberb-cloud/news.ts +127 -0
  935. package/src/sources/gloomberb-cloud/normalizers.ts +354 -0
  936. package/src/sources/provider-errors.ts +8 -2
  937. package/src/sources/provider-router/batches.ts +170 -0
  938. package/src/sources/provider-router/brokers.ts +114 -0
  939. package/src/sources/provider-router/cache.ts +171 -0
  940. package/src/sources/provider-router/cached-financials.test.ts +498 -0
  941. package/src/sources/provider-router/document-routes.ts +153 -0
  942. package/src/sources/provider-router/financial-routes.ts +225 -0
  943. package/src/sources/provider-router/financials.ts +259 -0
  944. package/src/sources/provider-router/history.test.ts +383 -0
  945. package/src/sources/provider-router/history.ts +448 -0
  946. package/src/sources/provider-router/index.test.ts +1438 -0
  947. package/src/sources/provider-router/index.ts +389 -0
  948. package/src/sources/provider-router/news.ts +36 -0
  949. package/src/sources/provider-router/primary.ts +164 -0
  950. package/src/sources/provider-router/route-types.ts +34 -0
  951. package/src/sources/provider-router/routing.ts +58 -0
  952. package/src/sources/provider-router/search.ts +183 -0
  953. package/src/sources/provider-router/sources.ts +55 -0
  954. package/src/sources/provider-router/streaming.ts +92 -0
  955. package/src/sources/provider-router/supplemental.ts +373 -0
  956. package/src/sources/provider-router/test-support.ts +136 -0
  957. package/src/sources/sec-edgar/content.ts +211 -0
  958. package/src/sources/sec-edgar.ts +9 -217
  959. package/src/sources/yahoo-finance/financials.ts +339 -0
  960. package/src/sources/yahoo-finance/history.ts +106 -0
  961. package/src/sources/yahoo-finance/http.ts +100 -0
  962. package/src/sources/yahoo-finance/mappers.ts +409 -0
  963. package/src/sources/yahoo-finance/options.ts +134 -0
  964. package/src/sources/yahoo-finance/quote-summary.ts +210 -0
  965. package/src/sources/yahoo-finance/requests.ts +192 -0
  966. package/src/sources/yahoo-finance/snapshots.ts +236 -0
  967. package/src/sources/yahoo-finance/symbols.ts +102 -0
  968. package/src/sources/yahoo-finance/types.ts +167 -0
  969. package/src/sources/yahoo-finance.test.ts +116 -12
  970. package/src/sources/yahoo-finance.ts +115 -1408
  971. package/src/state/app/activity.test.ts +41 -0
  972. package/src/state/app/activity.ts +106 -0
  973. package/src/state/app/bootstrap.test.ts +498 -0
  974. package/src/state/app/bootstrap.ts +412 -0
  975. package/src/state/app/context/index.test.ts +564 -0
  976. package/src/state/app/context/index.test.tsx +193 -0
  977. package/src/state/app/context/index.tsx +476 -0
  978. package/src/state/app/context/selector.test.tsx +291 -0
  979. package/src/state/config-save-scheduler.ts +2 -6
  980. package/src/state/hooks/inline-tickers.test.tsx +123 -0
  981. package/src/state/hooks/inline-tickers.ts +212 -0
  982. package/src/state/hooks/quote-streaming.test.ts +31 -0
  983. package/src/state/hooks/quote-streaming.test.tsx +70 -0
  984. package/src/state/hooks/quote-streaming.ts +92 -0
  985. package/src/state/hooks/session-snapshot.ts +99 -0
  986. package/src/state/pane-scroll-registry.tsx +1 -1
  987. package/src/state/selectors-ui.ts +1 -1
  988. package/src/state/selectors.ts +1 -1
  989. package/src/test-support/plugin-persistence.ts +112 -0
  990. package/src/test-support/plugin-runtime.ts +2 -1
  991. package/src/theme/colors.ts +23 -9
  992. package/src/theme/themes.test.ts +15 -0
  993. package/src/tickers/list.test.ts +16 -0
  994. package/src/tickers/open-target.ts +77 -0
  995. package/src/tickers/search/index.test.ts +252 -0
  996. package/src/tickers/search/index.ts +367 -0
  997. package/src/tickers/search/ranking.ts +387 -0
  998. package/src/tickers/search/result.ts +27 -0
  999. package/src/tickers/search/types.ts +41 -0
  1000. package/src/tickers/search/upsert.ts +91 -0
  1001. package/src/tickers/tokenizer.test.ts +31 -0
  1002. package/src/tickers/tokenizer.ts +37 -0
  1003. package/src/types/broker.ts +12 -4
  1004. package/src/types/config.ts +42 -23
  1005. package/src/types/context-menu.ts +1 -1
  1006. package/src/types/data-provider.ts +2 -2
  1007. package/src/types/desktop-window.ts +1 -1
  1008. package/src/types/financials.ts +109 -4
  1009. package/src/types/news-source.ts +0 -1
  1010. package/src/types/persistence.ts +1 -1
  1011. package/src/types/plugin.ts +37 -21
  1012. package/src/types/ticker.ts +1 -0
  1013. package/src/types/trading.ts +23 -1
  1014. package/src/ui/context-menu.tsx +6 -5
  1015. package/src/ui/dialog.tsx +6 -3
  1016. package/src/ui/host.tsx +10 -10
  1017. package/src/ui/index.tsx +14 -33
  1018. package/src/updater/github-releases.test.ts +41 -0
  1019. package/src/updater/github-releases.ts +73 -0
  1020. package/src/updater.test.ts +21 -0
  1021. package/src/updater.ts +10 -3
  1022. package/src/utils/back-navigation.ts +2 -2
  1023. package/src/utils/cli-output.ts +4 -4
  1024. package/src/utils/currency-units.test.ts +23 -1
  1025. package/src/utils/currency-units.ts +56 -0
  1026. package/src/utils/datetime-format.ts +40 -0
  1027. package/src/utils/dom-screenshot.ts +2 -2
  1028. package/src/utils/double-escape-close.ts +1 -1
  1029. package/src/utils/exchanges.ts +2 -2
  1030. package/src/utils/format.ts +0 -7
  1031. package/src/utils/fuzzy-search.ts +1 -1
  1032. package/src/utils/inline-content-tokenizer.test.ts +11 -2
  1033. package/src/utils/inline-content-tokenizer.ts +33 -12
  1034. package/src/utils/keyboard.ts +1 -1
  1035. package/src/utils/link-tokenizer.ts +2 -2
  1036. package/src/utils/options.ts +1 -1
  1037. package/src/utils/price-history.test.ts +20 -0
  1038. package/src/utils/price-history.ts +24 -2
  1039. package/src/utils/relative-time.ts +8 -0
  1040. package/src/utils/selection-clipboard.ts +1 -1
  1041. package/src/utils/shortcut-labels.ts +2 -2
  1042. package/src/utils/sort-values.ts +17 -0
  1043. package/src/utils/terminal-input-reset.ts +1 -1
  1044. package/src/utils/text-wrap.ts +20 -0
  1045. package/src/version.ts +1 -1
  1046. package/src/components/chart/chart-controller.test.ts +0 -110
  1047. package/src/components/chart/chart-controller.ts +0 -650
  1048. package/src/components/chart/chart-data.ts +0 -350
  1049. package/src/components/chart/chart-indicator-selector.tsx +0 -75
  1050. package/src/components/chart/chart-pane-settings.ts +0 -37
  1051. package/src/components/chart/chart-renderer.ts +0 -1562
  1052. package/src/components/chart/chart-resolution.test.ts +0 -87
  1053. package/src/components/chart/chart-resolution.ts +0 -405
  1054. package/src/components/chart/chart-scroll.test.ts +0 -70
  1055. package/src/components/chart/chart-scroll.ts +0 -104
  1056. package/src/components/chart/chart-types.ts +0 -119
  1057. package/src/components/chart/chart-viewport.test.ts +0 -29
  1058. package/src/components/chart/chart-viewport.ts +0 -50
  1059. package/src/components/chart/chart.test.ts +0 -922
  1060. package/src/components/chart/comparison-chart-data.test.ts +0 -105
  1061. package/src/components/chart/comparison-chart-data.ts +0 -292
  1062. package/src/components/chart/comparison-chart-renderer.test.ts +0 -187
  1063. package/src/components/chart/comparison-chart-renderer.ts +0 -437
  1064. package/src/components/chart/native/kitty-adapter.ts +0 -9
  1065. package/src/components/chart/native/kitty-manager.ts +0 -142
  1066. package/src/components/chart/native/kitty-support.ts +0 -71
  1067. package/src/components/chart/native/kitty.test.ts +0 -728
  1068. package/src/components/chart/native/surface-manager.ts +0 -225
  1069. package/src/components/chart/native/surface-sync.ts +0 -34
  1070. package/src/components/chart/native/surface-visibility.ts +0 -48
  1071. package/src/components/chart/static-chart-surface.test.tsx +0 -51
  1072. package/src/components/chart/static-chart-surface.tsx +0 -217
  1073. package/src/components/chart/stock-chart-renderer-switch.test.tsx +0 -1323
  1074. package/src/components/chart/stock-chart.test.ts +0 -330
  1075. package/src/components/chart/stock-chart.tsx +0 -4148
  1076. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +0 -109
  1077. package/src/components/command-bar/command-bar.test.tsx +0 -3244
  1078. package/src/components/command-bar/command-bar.tsx +0 -5886
  1079. package/src/components/command-bar/command-registry.ts +0 -267
  1080. package/src/components/command-bar/confirm-body.tsx +0 -51
  1081. package/src/components/command-bar/list-model.ts +0 -80
  1082. package/src/components/command-bar/pane-template-display.ts +0 -8
  1083. package/src/components/command-bar/root-shortcuts.ts +0 -188
  1084. package/src/components/command-bar/workflow-ops.test.ts +0 -144
  1085. package/src/components/command-bar/workflow-ops.ts +0 -459
  1086. package/src/components/command-bar/workflow-types.ts +0 -127
  1087. package/src/components/data-table-stack-view.test.tsx +0 -241
  1088. package/src/components/data-table-stack-view.tsx +0 -59
  1089. package/src/components/data-table-view.test.tsx +0 -211
  1090. package/src/components/data-table-view.tsx +0 -244
  1091. package/src/components/feed-data-table-stack-view.test.tsx +0 -225
  1092. package/src/components/feed-data-table-stack-view.tsx +0 -359
  1093. package/src/components/layout/pane-content.test.tsx +0 -64
  1094. package/src/components/layout/pane-content.tsx +0 -44
  1095. package/src/components/layout/pane-cycle-order.test.ts +0 -30
  1096. package/src/components/layout/pane-cycle-order.ts +0 -32
  1097. package/src/components/layout/pane-footer.test.tsx +0 -195
  1098. package/src/components/layout/pane-footer.tsx +0 -432
  1099. package/src/components/layout/pane-header.test.tsx +0 -39
  1100. package/src/components/layout/pane-header.tsx +0 -248
  1101. package/src/components/layout/pane-keyboard-scroll.test.tsx +0 -226
  1102. package/src/components/layout/pane-sizing.ts +0 -34
  1103. package/src/components/layout/pane-title.ts +0 -38
  1104. package/src/components/layout/pane.tsx +0 -129
  1105. package/src/components/layout/shell.test.tsx +0 -1453
  1106. package/src/components/layout/shell.tsx +0 -1960
  1107. package/src/components/price-sparkline-view.tsx +0 -463
  1108. package/src/components/price-sparkline.ts +0 -46
  1109. package/src/components/speedometer-gauge.test.tsx +0 -45
  1110. package/src/components/speedometer-gauge.tsx +0 -562
  1111. package/src/components/ticker-badge-list.tsx +0 -53
  1112. package/src/components/ticker-badge-text.test.tsx +0 -253
  1113. package/src/components/ticker-badge-text.tsx +0 -113
  1114. package/src/components/ticker-badge.tsx +0 -109
  1115. package/src/components/ticker-list-table-view.tsx +0 -233
  1116. package/src/components/ticker-list-table.test.tsx +0 -210
  1117. package/src/components/ticker-list-table.tsx +0 -458
  1118. package/src/components/ui/data-table.tsx +0 -522
  1119. package/src/components/ui/multi-select-dialog.tsx +0 -336
  1120. package/src/core/state/app-state.ts +0 -1009
  1121. package/src/data/config-layout.ts +0 -225
  1122. package/src/data/config-store-node.ts +0 -478
  1123. package/src/data/config-store.test.ts +0 -481
  1124. package/src/data/config-store.ts +0 -64
  1125. package/src/data/sqlite-cache.test.ts +0 -151
  1126. package/src/data/sqlite-database.ts +0 -68
  1127. package/src/data/sqlite-json.ts +0 -16
  1128. package/src/data/sqlite-retry.test.ts +0 -63
  1129. package/src/market-data/coordinator-subscriptions.test.ts +0 -209
  1130. package/src/market-data/coordinator.test.ts +0 -830
  1131. package/src/market-data/coordinator.ts +0 -1155
  1132. package/src/plugins/builtin/ai/screener-contract.test.ts +0 -117
  1133. package/src/plugins/builtin/ai/screener-contract.ts +0 -174
  1134. package/src/plugins/builtin/ai/screener-pane.test.tsx +0 -348
  1135. package/src/plugins/builtin/ai/screener-pane.tsx +0 -1118
  1136. package/src/plugins/builtin/ai/utils.ts +0 -1
  1137. package/src/plugins/builtin/ask-ai.test.tsx +0 -348
  1138. package/src/plugins/builtin/chat-controller.test.ts +0 -1376
  1139. package/src/plugins/builtin/chat-controller.ts +0 -1248
  1140. package/src/plugins/builtin/chat.test.tsx +0 -2245
  1141. package/src/plugins/builtin/chat.tsx +0 -2267
  1142. package/src/plugins/builtin/cloud-auth-actions.tsx +0 -72
  1143. package/src/plugins/builtin/cloud-tweets.tsx +0 -896
  1144. package/src/plugins/builtin/comparison-chart.test.tsx +0 -532
  1145. package/src/plugins/builtin/comparison-chart.tsx +0 -194
  1146. package/src/plugins/builtin/debug.tsx +0 -365
  1147. package/src/plugins/builtin/earnings/earnings-cache.test.ts +0 -181
  1148. package/src/plugins/builtin/earnings/earnings-cache.ts +0 -145
  1149. package/src/plugins/builtin/earnings/earnings-data.test.ts +0 -90
  1150. package/src/plugins/builtin/earnings/earnings-data.ts +0 -115
  1151. package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +0 -72
  1152. package/src/plugins/builtin/fear-greed/fear-greed-data.ts +0 -366
  1153. package/src/plugins/builtin/help.test.tsx +0 -166
  1154. package/src/plugins/builtin/help.tsx +0 -450
  1155. package/src/plugins/builtin/layout-manager.tsx +0 -247
  1156. package/src/plugins/builtin/manual-entry.tsx +0 -25
  1157. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +0 -98
  1158. package/src/plugins/builtin/news-wire/breaking-notifications.ts +0 -110
  1159. package/src/plugins/builtin/news-wire/breaking-pane.test.tsx +0 -156
  1160. package/src/plugins/builtin/news-wire/breaking-pane.tsx +0 -68
  1161. package/src/plugins/builtin/news-wire/categories.ts +0 -114
  1162. package/src/plugins/builtin/news-wire/default-feeds.ts +0 -3
  1163. package/src/plugins/builtin/news-wire/feed-config.test.ts +0 -90
  1164. package/src/plugins/builtin/news-wire/feed-config.ts +0 -216
  1165. package/src/plugins/builtin/news-wire/index.tsx +0 -129
  1166. package/src/plugins/builtin/news-wire/industry-pane.tsx +0 -126
  1167. package/src/plugins/builtin/news-wire/news-detail-view.tsx +0 -299
  1168. package/src/plugins/builtin/news-wire/news-footer.ts +0 -28
  1169. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +0 -98
  1170. package/src/plugins/builtin/news-wire/news-query-presets.ts +0 -40
  1171. package/src/plugins/builtin/news-wire/news-table.test.tsx +0 -206
  1172. package/src/plugins/builtin/news-wire/news-table.tsx +0 -296
  1173. package/src/plugins/builtin/news-wire/persisted-articles.ts +0 -98
  1174. package/src/plugins/builtin/news-wire/read-state.ts +0 -76
  1175. package/src/plugins/builtin/news-wire/rss-parser.test.ts +0 -184
  1176. package/src/plugins/builtin/news-wire/rss-parser.ts +0 -213
  1177. package/src/plugins/builtin/news-wire/rss-source.test.ts +0 -159
  1178. package/src/plugins/builtin/news-wire/rss-source.ts +0 -181
  1179. package/src/plugins/builtin/news.tsx +0 -194
  1180. package/src/plugins/builtin/notes.tsx +0 -561
  1181. package/src/plugins/builtin/options.test.tsx +0 -241
  1182. package/src/plugins/builtin/options.tsx +0 -542
  1183. package/src/plugins/builtin/portfolio-list/cli/commands.ts +0 -657
  1184. package/src/plugins/builtin/portfolio-list/pane.tsx +0 -724
  1185. package/src/plugins/builtin/portfolio-list/quick-add.tsx +0 -422
  1186. package/src/plugins/builtin/portfolio-list/summary.tsx +0 -275
  1187. package/src/plugins/builtin/portfolio-list.test.ts +0 -133
  1188. package/src/plugins/builtin/portfolio-list.test.tsx +0 -1535
  1189. package/src/plugins/builtin/portfolio-metrics.test.ts +0 -236
  1190. package/src/plugins/builtin/quote-monitor.test.tsx +0 -222
  1191. package/src/plugins/builtin/sec-filing-display.tsx +0 -84
  1192. package/src/plugins/builtin/sec.tsx +0 -342
  1193. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +0 -461
  1194. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +0 -479
  1195. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +0 -269
  1196. package/src/plugins/builtin/ticker-detail.test.tsx +0 -888
  1197. package/src/plugins/builtin/ticker-surface.ts +0 -68
  1198. package/src/plugins/builtin/yahoo.ts +0 -72
  1199. package/src/plugins/catalog-ui.test.ts +0 -24
  1200. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +0 -35
  1201. package/src/plugins/ibkr/flex.test.ts +0 -162
  1202. package/src/plugins/ibkr/flex.ts +0 -371
  1203. package/src/plugins/ibkr/gateway-helpers.ts +0 -33
  1204. package/src/plugins/ibkr/gateway-selection.ts +0 -51
  1205. package/src/plugins/ibkr/gateway-service-native.ts +0 -2377
  1206. package/src/plugins/ibkr/gateway-service.test.ts +0 -584
  1207. package/src/plugins/ibkr/gateway-service.ts +0 -433
  1208. package/src/plugins/ibkr/gateway-types.ts +0 -27
  1209. package/src/plugins/ibkr/price-normalization.test.ts +0 -33
  1210. package/src/plugins/ibkr/price-normalization.ts +0 -60
  1211. package/src/plugins/ibkr/trade-badge.tsx +0 -28
  1212. package/src/plugins/ibkr/trade-tab.test.tsx +0 -276
  1213. package/src/plugins/ibkr/trade-tab.tsx +0 -1117
  1214. package/src/plugins/ibkr/trade-utils.test.ts +0 -175
  1215. package/src/plugins/ibkr/trade-utils.ts +0 -150
  1216. package/src/plugins/ibkr/trading-pane.tsx +0 -474
  1217. package/src/plugins/ibkr/trading-state.test.ts +0 -109
  1218. package/src/plugins/ibkr/trading-state.ts +0 -339
  1219. package/src/plugins/plugin-runtime.test.tsx +0 -323
  1220. package/src/plugins/plugin-runtime.tsx +0 -433
  1221. package/src/plugins/prediction-markets/controller-data.ts +0 -785
  1222. package/src/plugins/prediction-markets/controller.ts +0 -659
  1223. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +0 -572
  1224. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +0 -696
  1225. package/src/plugins/prediction-markets/services/polymarket-ws.ts +0 -128
  1226. package/src/plugins/registry.test.ts +0 -362
  1227. package/src/plugins/registry.ts +0 -854
  1228. package/src/renderers/electrobun/bun/application-menu-click.test.ts +0 -50
  1229. package/src/renderers/electrobun/bun/application-menu-click.ts +0 -58
  1230. package/src/renderers/electrobun/bun/application-menu.ts +0 -123
  1231. package/src/renderers/electrobun/bun/context-menu-click.test.ts +0 -67
  1232. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +0 -52
  1233. package/src/renderers/electrobun/bun/context-menu-normalize.ts +0 -67
  1234. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +0 -63
  1235. package/src/renderers/electrobun/bun/desktop-workspace.ts +0 -180
  1236. package/src/renderers/electrobun/bun/window-focus.ts +0 -37
  1237. package/src/renderers/electrobun/bun/window-frame.test.ts +0 -40
  1238. package/src/renderers/electrobun/view/data-table.tsx +0 -810
  1239. package/src/renderers/electrobun/view/desktop-controls.tsx +0 -673
  1240. package/src/renderers/electrobun/view/desktop-window-bridge.ts +0 -70
  1241. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +0 -57
  1242. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +0 -41
  1243. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +0 -9
  1244. package/src/renderers/opentui/image-loader.test.ts +0 -25
  1245. package/src/renderers/opentui/image-loader.ts +0 -105
  1246. package/src/renderers/opentui/image-surface.tsx +0 -266
  1247. package/src/sources/gloomberb-cloud.test.ts +0 -637
  1248. package/src/sources/gloomberb-cloud.ts +0 -828
  1249. package/src/sources/provider-router.test.ts +0 -2600
  1250. package/src/sources/provider-router.ts +0 -2231
  1251. package/src/state/app-activity.test.ts +0 -41
  1252. package/src/state/app-activity.ts +0 -106
  1253. package/src/state/app-bootstrap.test.ts +0 -498
  1254. package/src/state/app-bootstrap.ts +0 -411
  1255. package/src/state/app-context-selector.test.tsx +0 -369
  1256. package/src/state/app-context.test.ts +0 -670
  1257. package/src/state/app-context.test.tsx +0 -193
  1258. package/src/state/app-context.tsx +0 -485
  1259. package/src/state/use-inline-tickers.test.tsx +0 -73
  1260. package/src/state/use-inline-tickers.ts +0 -188
  1261. package/src/state/use-quote-streaming.test.ts +0 -31
  1262. package/src/state/use-quote-streaming.test.tsx +0 -70
  1263. package/src/state/use-quote-streaming.ts +0 -92
  1264. package/src/state/use-session-snapshot.ts +0 -99
  1265. package/src/utils/api-client.test.ts +0 -603
  1266. package/src/utils/api-client.ts +0 -1379
  1267. package/src/utils/market-format.test.ts +0 -109
  1268. package/src/utils/market-format.ts +0 -299
  1269. package/src/utils/market-status.ts +0 -141
  1270. package/src/utils/quote-freshness.test.ts +0 -150
  1271. package/src/utils/quote-freshness.ts +0 -145
  1272. package/src/utils/quote-resolution.test.ts +0 -277
  1273. package/src/utils/quote-resolution.ts +0 -642
  1274. package/src/utils/quote-time.test.ts +0 -34
  1275. package/src/utils/quote-time.ts +0 -59
  1276. package/src/utils/ticker-list.test.ts +0 -16
  1277. package/src/utils/ticker-search.test.ts +0 -227
  1278. package/src/utils/ticker-search.ts +0 -926
  1279. package/src/utils/ticker-tokenizer.test.ts +0 -31
  1280. package/src/utils/ticker-tokenizer.ts +0 -37
  1281. /package/src/components/chart/native/{kitty-protocol.ts → kitty/protocol.ts} +0 -0
  1282. /package/src/components/ui/{multi-select.ts → multi-select/index.ts} +0 -0
  1283. /package/src/data/{sqlite-retry.ts → sqlite/retry.ts} +0 -0
  1284. /package/src/plugins/builtin/{news-wire → news/wire}/hash.ts +0 -0
  1285. /package/src/plugins/builtin/{news-wire → news/wire}/read-state.test.ts +0 -0
  1286. /package/src/plugins/builtin/{notes-files.ts → notes/files.ts} +0 -0
  1287. /package/src/renderers/electrobun/bun/{context-menu-click.ts → context-menu/click.ts} +0 -0
  1288. /package/src/renderers/electrobun/bun/{window-frame.ts → window/frame.ts} +0 -0
  1289. /package/src/renderers/electrobun/view/native-stubs/{chart-surface-manager.ts → chart/surface-manager.ts} +0 -0
  1290. /package/src/renderers/electrobun/view/native-stubs/{chart-surface-sync.ts → chart/surface-sync.ts} +0 -0
  1291. /package/src/{utils/ticker-list.ts → tickers/list.ts} +0 -0
  1292. /package/src/{utils/ticker-metadata.ts → tickers/metadata.ts} +0 -0
@@ -1,887 +1,95 @@
1
- import type { Quote, Fundamentals, FinancialStatement, PricePoint, TickerFinancials, MarketState, OptionContract, OptionsChain, CompanyProfile, HolderData, HolderRecord, AnalystResearchData, AnalystEstimateRecord } from "../types/financials";
1
+ import type { Quote, PricePoint, TickerFinancials, OptionsChain, CompanyProfile, HolderData, AnalystResearchData, CorporateActionsData } from "../types/financials";
2
2
  import type { DataProvider, EarningsEvent, MarketDataRequestContext, NewsItem, SecFilingItem } from "../types/data-provider";
3
- import type { TimeRange } from "../components/chart/chart-types";
3
+ import type { TimeRange } from "../components/chart/core/types";
4
4
  import {
5
- normalizeChartResolutionSupport,
6
5
  type ChartResolutionSupport,
7
6
  type ManualChartResolution,
8
- } from "../components/chart/chart-resolution";
7
+ } from "../components/chart/core/resolution";
9
8
  import type { InstrumentSearchResult } from "../types/instrument";
10
9
  import { parseOptionSymbol } from "../utils/options";
11
10
  import { SecEdgarClient } from "./sec-edgar";
12
-
13
- // Exchange suffix mapping for Yahoo Finance ticker symbols
14
- // Includes both canonical codes and common IBKR listing exchange aliases
15
- const EXCHANGE_SUFFIX_MAP: Record<string, string> = {
16
- // US exchanges
17
- NASDAQ: "", NMS: "", NYSE: "", AMEX: "", ARCA: "", NYSEArca: "", BATS: "", BYX: "", IEX: "", PINK: "", OTC: "",
18
- // Canada
19
- TSX: ".TO", VENTURE: ".V", CSE2: ".CN", CNSX: ".CN",
20
- // Japan (note: TSE is ambiguous — handled via EXCHANGE_FALLBACKS)
21
- TYO: ".T", JPX: ".T", TSEJ: ".T",
22
- // Hong Kong
23
- HKEX: ".HK", SEHK: ".HK", HKG: ".HK",
24
- // China
25
- SSE: ".SS", SHG: ".SS", SZSE: ".SZ", SHE: ".SZ",
26
- // Taiwan
27
- TWSE: ".TW", TPE: ".TW", TPEX: ".TWO",
28
- // Korea
29
- KRX: ".KS", KSE: ".KS", KOSDAQ: ".KQ",
30
- // Singapore
31
- SGX: ".SI", SES: ".SI",
32
- // Indonesia
33
- IDX: ".JK",
34
- // India
35
- NSE: ".NS", BSE: ".BO",
36
- // Australia & New Zealand
37
- ASX: ".AX", NZE: ".NZ",
38
- // Thailand, Malaysia, Philippines, Vietnam
39
- SET: ".BK", BKK: ".BK", KLSE: ".KL", MYX: ".KL", PSE: ".PS", HOSE: ".VN", HNX: ".VN",
40
- // UK
41
- LSE: ".L", LSEETF: ".L",
42
- // Germany
43
- XETRA: ".DE", XETR: ".DE", IBIS: ".DE", IBIS2: ".DE", FWB: ".F", FWB2: ".F", GETTEX: ".DE", TGATE: ".DE", SWB: ".SG",
44
- // France, Netherlands, Belgium, Portugal (Euronext)
45
- EURONEXT: ".AS", AEB: ".AS", SBF: ".PA", "ENEXT.BE": ".BR", BVL: ".LS",
46
- // Italy & Spain
47
- BVME: ".MI", BM: ".MC",
48
- // Switzerland
49
- SIX: ".SW", EBS: ".SW", SWX: ".SW",
50
- // Nordics
51
- SFB: ".ST", Stockholm: ".ST", OMX: ".ST", CPH: ".CO", HEX: ".HE", OSE: ".OL", OMXNO: ".OL", ICEX: ".IC",
52
- // Central/Eastern Europe
53
- VSE: ".VI", WSE: ".WA", PRA: ".PR", BUX: ".BD", ATHEX: ".AT", BVB: ".RO", BIST: ".IS",
54
- // Israel
55
- TASE: ".TA",
56
- // South Africa
57
- JSE: ".JO",
58
- // Brazil & Latin America
59
- BVMF: ".SA", MEXI: ".MX", BYMA: ".BA", BCS: ".SN",
60
- // Middle East
61
- TADAWUL: ".SAU", QSE: ".QA", DFM: ".AE",
62
- };
63
-
64
- const EXCHANGE_FALLBACKS: Record<string, string[]> = {
65
- // TSE is ambiguous: Toronto (.TO) vs Tokyo (.T) — try both
66
- TSE: [".TO", ".T"],
67
- KRX: [".KS", ".KQ"], KSE: [".KS", ".KQ"],
68
- TWSE: [".TW", ".TWO"], TPE: [".TW", ".TWO"], TPEX: [".TWO", ".TW"],
69
- FWB2: [".F", ".DE"],
70
- EURONEXT: [".AS", ".PA", ".BR"], AEB: [".AS", ".PA", ".BR"], SBF: [".PA", ".AS", ".BR"],
71
- };
72
-
73
- const GENERIC_SUFFIX_FALLBACKS = [
74
- "", ".HK", ".T", ".TO", ".KS", ".KQ", ".TW", ".TWO", ".SS", ".SZ",
75
- ".AS", ".PA", ".BR", ".DE", ".F", ".L", ".MI", ".MC", ".SW", ".AX",
76
- ".SI", ".JK", ".OL", ".ST", ".CO", ".HE", ".NS", ".BO", ".SA",
77
- ".BK", ".KL", ".NZ", ".JO", ".TA", ".WA", ".VI",
78
- ];
79
-
80
- const MAX_RETRIES = 3;
81
- const RETRY_BASE_MS = 1500;
82
- const FETCH_TIMEOUT_MS = 20_000;
83
- const RETRYABLE_ERROR = /429|403|401|Too Many Requests|Forbidden|Unauthorized|ECONNRESET|ETIMEDOUT|ENOTFOUND|fetch failed|Failed to get crumb|socket hang up|503|502|504/i;
84
-
85
- const TIMESERIES_TYPES = {
86
- annual: [
87
- // Income Statement
88
- "annualTotalRevenue", "annualCostOfRevenue", "annualGrossProfit",
89
- "annualSellingGeneralAndAdministration", "annualResearchAndDevelopment",
90
- "annualOperatingExpense", "annualOperatingIncome",
91
- "annualInterestExpense", "annualTaxProvision",
92
- "annualNetIncome", "annualEBITDA",
93
- "annualBasicEPS", "annualDilutedEPS", "annualDilutedAverageShares",
94
- // Cash Flow
95
- "annualOperatingCashFlow", "annualCapitalExpenditure", "annualFreeCashFlow",
96
- "annualInvestingCashFlow", "annualFinancingCashFlow",
97
- "annualIssuanceOfDebt", "annualRepurchaseOfCapitalStock", "annualCashDividendsPaid",
98
- // Balance Sheet
99
- "annualTotalAssets", "annualCurrentAssets", "annualCashAndCashEquivalents",
100
- "annualTotalLiabilitiesNetMinorityInterest", "annualCurrentLiabilities",
101
- "annualLongTermDebt", "annualTotalDebt",
102
- "annualStockholdersEquity", "annualRetainedEarnings",
103
- ],
104
- quarterly: [
105
- // Income Statement
106
- "quarterlyTotalRevenue", "quarterlyCostOfRevenue", "quarterlyGrossProfit",
107
- "quarterlySellingGeneralAndAdministration", "quarterlyResearchAndDevelopment",
108
- "quarterlyOperatingExpense", "quarterlyOperatingIncome",
109
- "quarterlyInterestExpense", "quarterlyTaxProvision",
110
- "quarterlyNetIncome", "quarterlyEBITDA",
111
- "quarterlyBasicEPS", "quarterlyDilutedEPS", "quarterlyDilutedAverageShares",
112
- // Cash Flow
113
- "quarterlyOperatingCashFlow", "quarterlyCapitalExpenditure", "quarterlyFreeCashFlow",
114
- "quarterlyInvestingCashFlow", "quarterlyFinancingCashFlow",
115
- "quarterlyIssuanceOfDebt", "quarterlyRepurchaseOfCapitalStock", "quarterlyCashDividendsPaid",
116
- // Balance Sheet
117
- "quarterlyTotalAssets", "quarterlyCurrentAssets", "quarterlyCashAndCashEquivalents",
118
- "quarterlyTotalLiabilitiesNetMinorityInterest", "quarterlyCurrentLiabilities",
119
- "quarterlyLongTermDebt", "quarterlyTotalDebt",
120
- "quarterlyStockholdersEquity", "quarterlyRetainedEarnings",
121
- ],
122
- trailing: [
123
- "trailingMarketCap", "trailingPeRatio", "trailingForwardPeRatio",
124
- "trailingPegRatio", "trailingEnterpriseValue", "trailingOperatingCashFlow",
125
- "trailingFreeCashFlow", "trailingDividendYield",
126
- ],
127
- };
128
-
129
- type TradingPeriod = { start?: number; end?: number; gmtoffset?: number; timezone?: string };
130
-
131
- type ChartResult = {
132
- meta?: {
133
- currency?: string; longName?: string; shortName?: string;
134
- regularMarketPrice?: number; chartPreviousClose?: number;
135
- fiftyTwoWeekHigh?: number; fiftyTwoWeekLow?: number;
136
- exchangeName?: string; fullExchangeName?: string;
137
- regularMarketTime?: number;
138
- currentTradingPeriod?: { pre?: TradingPeriod; regular?: TradingPeriod; post?: TradingPeriod };
139
- preMarketPrice?: number; postMarketPrice?: number;
140
- };
141
- timestamp?: number[];
142
- indicators?: { quote?: Array<{ open?: (number | null)[]; high?: (number | null)[]; low?: (number | null)[]; close?: (number | null)[]; volume?: (number | null)[] }> };
143
- };
144
-
145
- type ChartResponse = { chart?: { result?: ChartResult[]; error?: { description?: string } | null } };
146
- type TimeseriesResponse = { timeseries?: { result?: Array<Record<string, any>>; error?: { description?: string } | null } };
147
- type QuoteSummaryResponse = {
148
- quoteSummary?: {
149
- result?: Array<{
150
- price?: {
151
- symbol?: string;
152
- currency?: string;
153
- shortName?: string;
154
- longName?: string;
155
- exchangeName?: string;
156
- };
157
- financialData?: {
158
- currentPrice?: { raw?: number } | number | null;
159
- targetHighPrice?: { raw?: number } | number | null;
160
- targetLowPrice?: { raw?: number } | number | null;
161
- targetMeanPrice?: { raw?: number } | number | null;
162
- targetMedianPrice?: { raw?: number } | number | null;
163
- recommendationMean?: { raw?: number } | number | null;
164
- };
165
- recommendationTrend?: {
166
- trend?: Array<{
167
- period?: string;
168
- strongBuy?: number;
169
- buy?: number;
170
- hold?: number;
171
- sell?: number;
172
- strongSell?: number;
173
- }>;
174
- };
175
- upgradeDowngradeHistory?: {
176
- history?: Array<{
177
- epochGradeDate?: number;
178
- firm?: string;
179
- action?: string;
180
- priceTargetAction?: string;
181
- toGrade?: string;
182
- fromGrade?: string;
183
- currentPriceTarget?: { raw?: number } | number | null;
184
- priorPriceTarget?: { raw?: number } | number | null;
185
- }>;
186
- };
187
- earningsTrend?: {
188
- trend?: Array<{
189
- period?: string;
190
- endDate?: string;
191
- earningsEstimate?: {
192
- avg?: { raw?: number } | number | null;
193
- low?: { raw?: number } | number | null;
194
- high?: { raw?: number } | number | null;
195
- yearAgoEps?: { raw?: number } | number | null;
196
- numberOfAnalysts?: { raw?: number } | number | null;
197
- growth?: { raw?: number } | number | null;
198
- };
199
- revenueEstimate?: {
200
- avg?: { raw?: number } | number | null;
201
- low?: { raw?: number } | number | null;
202
- high?: { raw?: number } | number | null;
203
- yearAgoRevenue?: { raw?: number } | number | null;
204
- numberOfAnalysts?: { raw?: number } | number | null;
205
- growth?: { raw?: number } | number | null;
206
- };
207
- }>;
208
- };
209
- assetProfile?: {
210
- longBusinessSummary?: string;
211
- sector?: string;
212
- industry?: string;
213
- };
214
- summaryDetail?: {
215
- bid?: { raw?: number } | number | null;
216
- ask?: { raw?: number } | number | null;
217
- bidSize?: { raw?: number } | number | null;
218
- askSize?: { raw?: number } | number | null;
219
- previousClose?: { raw?: number } | number | null;
220
- open?: { raw?: number } | number | null;
221
- dayHigh?: { raw?: number } | number | null;
222
- dayLow?: { raw?: number } | number | null;
223
- };
224
- majorHoldersBreakdown?: {
225
- insidersPercentHeld?: { raw?: number } | number | null;
226
- institutionsPercentHeld?: { raw?: number } | number | null;
227
- institutionsFloatPercentHeld?: { raw?: number } | number | null;
228
- institutionsCount?: { raw?: number } | number | null;
229
- };
230
- institutionOwnership?: {
231
- ownershipList?: Array<{
232
- organization?: string;
233
- reportDate?: { raw?: number; fmt?: string } | number | string | null;
234
- position?: { raw?: number } | number | null;
235
- value?: { raw?: number } | number | null;
236
- pctHeld?: { raw?: number } | number | null;
237
- pctChange?: { raw?: number } | number | null;
238
- }>;
239
- };
240
- }>;
241
- error?: { description?: string } | null;
242
- };
243
- };
244
- type YahooQuoteSummaryResult = NonNullable<NonNullable<QuoteSummaryResponse["quoteSummary"]>["result"]>[number];
245
- type YahooEarningsTrend = NonNullable<NonNullable<YahooQuoteSummaryResult["earningsTrend"]>["trend"]>[number];
246
-
247
- const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
248
-
249
- /**
250
- * Sub-unit currency normalization.
251
- * Yahoo Finance returns prices for some exchanges in sub-units (e.g. pence instead of pounds).
252
- * IBKR and most brokers report in the main currency unit, so we normalize here to avoid mismatches.
253
- */
254
- const SUB_UNIT_CURRENCIES: Record<string, { main: string; divisor: number }> = {
255
- GBp: { main: "GBP", divisor: 100 },
256
- GBX: { main: "GBP", divisor: 100 },
257
- ILA: { main: "ILS", divisor: 100 },
258
- ZAc: { main: "ZAR", divisor: 100 },
259
- };
260
-
261
- function normalizeSubUnitCurrency(currency: string): { currency: string; divisor: number } {
262
- const sub = SUB_UNIT_CURRENCIES[currency];
263
- if (sub) return { currency: sub.main, divisor: sub.divisor };
264
- return { currency, divisor: 1 };
265
- }
266
-
267
- function financeRawNumber(value: unknown): number | undefined {
268
- if (typeof value === "number" && Number.isFinite(value)) return value;
269
- if (value && typeof value === "object") {
270
- const raw = (value as { raw?: unknown }).raw;
271
- if (typeof raw === "number" && Number.isFinite(raw)) return raw;
272
- }
273
- return undefined;
274
- }
275
-
276
- function financeRawNumberOrNull(value: unknown): number | null {
277
- return financeRawNumber(value) ?? null;
278
- }
279
-
280
- function hasAnalystResearchValue(data: AnalystResearchData): boolean {
281
- return !!data.priceTarget
282
- || data.recommendations.length > 0
283
- || data.ratings.length > 0
284
- || data.earningsEstimates.length > 0
285
- || data.revenueEstimates.length > 0;
286
- }
287
-
288
- function normalizeYahooRecommendationPeriod(period?: string): string {
289
- switch (period) {
290
- case "0m":
291
- return "current month";
292
- case "-1m":
293
- return "previous month";
294
- case "-2m":
295
- return "previous 2 months";
296
- case "-3m":
297
- return "previous 3 months";
298
- case "0q":
299
- return "current quarter";
300
- case "+1q":
301
- return "next quarter";
302
- case "0y":
303
- return "current year";
304
- case "+1y":
305
- return "next year";
306
- default:
307
- return period ?? "";
308
- }
309
- }
310
-
311
- function normalizeYahooRatingAction(action?: string, priceTargetAction?: string): string | undefined {
312
- const targetAction = priceTargetAction?.trim();
313
- switch ((action ?? "").toLowerCase()) {
314
- case "up":
315
- return "Upgrade";
316
- case "down":
317
- return "Downgrade";
318
- case "init":
319
- return "Initiated";
320
- case "reit":
321
- return targetAction || "Reiterated";
322
- case "main":
323
- return targetAction || "Maintained";
324
- default:
325
- return action?.trim() || undefined;
326
- }
327
- }
328
-
329
- function yahooRecommendationMeanToRating(value: number | undefined): number | undefined {
330
- if (value == null) return undefined;
331
- return Math.max(0, Math.min(10, ((5 - value) / 4) * 10));
332
- }
333
-
334
- function mapYahooEstimate(
335
- trend: YahooEarningsTrend,
336
- estimate: unknown,
337
- yearAgoKey: "yearAgoEps" | "yearAgoRevenue",
338
- ): AnalystEstimateRecord | null {
339
- if (!estimate || typeof estimate !== "object") return null;
340
- const record = estimate as Record<string, unknown>;
341
- const average = financeRawNumber(record.avg);
342
- const low = financeRawNumber(record.low);
343
- const high = financeRawNumber(record.high);
344
- const analysts = financeRawNumber(record.numberOfAnalysts);
345
- const yearAgo = financeRawNumber(record[yearAgoKey]);
346
- const growth = financeRawNumber(record.growth);
347
- if (
348
- average == null
349
- && low == null
350
- && high == null
351
- && analysts == null
352
- && yearAgo == null
353
- && growth == null
354
- ) {
355
- return null;
356
- }
357
- return {
358
- date: trend.endDate ?? "",
359
- period: normalizeYahooRecommendationPeriod(trend.period),
360
- analysts,
361
- average,
362
- low,
363
- high,
364
- yearAgo,
365
- growth,
366
- };
367
- }
368
-
369
- function mapYahooAnalystResearchResponse(
370
- result: YahooQuoteSummaryResult,
371
- fallbackSymbol: string,
372
- ): AnalystResearchData {
373
- const financialData = result.financialData;
374
- const targetHigh = financeRawNumber(financialData?.targetHighPrice);
375
- const targetLow = financeRawNumber(financialData?.targetLowPrice);
376
- const targetMean = financeRawNumber(financialData?.targetMeanPrice);
377
- const targetMedian = financeRawNumber(financialData?.targetMedianPrice);
378
- const currentPrice = financeRawNumber(financialData?.currentPrice);
379
- const trend = result.earningsTrend?.trend ?? [];
380
-
381
- return {
382
- providerId: "yahoo",
383
- symbol: result.price?.symbol ?? fallbackSymbol,
384
- name: result.price?.shortName ?? result.price?.longName,
385
- currency: result.price?.currency,
386
- exchange: result.price?.exchangeName,
387
- priceTarget: targetHigh != null || targetLow != null || targetMean != null || targetMedian != null
388
- ? {
389
- high: targetHigh,
390
- median: targetMedian,
391
- low: targetLow,
392
- average: targetMean,
393
- current: currentPrice,
394
- currency: result.price?.currency,
395
- }
396
- : undefined,
397
- recommendationRating: yahooRecommendationMeanToRating(financeRawNumber(financialData?.recommendationMean)),
398
- recommendations: (result.recommendationTrend?.trend ?? [])
399
- .filter((row) => row.period)
400
- .map((row) => ({
401
- period: normalizeYahooRecommendationPeriod(row.period),
402
- strongBuy: row.strongBuy,
403
- buy: row.buy,
404
- hold: row.hold,
405
- sell: row.sell,
406
- strongSell: row.strongSell,
407
- })),
408
- ratings: (result.upgradeDowngradeHistory?.history ?? [])
409
- .map((rating): AnalystResearchData["ratings"][number] | null => {
410
- const firm = rating.firm?.trim();
411
- const date = rating.epochGradeDate
412
- ? new Date(rating.epochGradeDate * 1000).toISOString().slice(0, 10)
413
- : "";
414
- if (!firm || !date) return null;
415
- const action = normalizeYahooRatingAction(rating.action, rating.priceTargetAction);
416
- const current = rating.toGrade?.trim();
417
- const prior = rating.fromGrade?.trim();
418
- const currentPriceTarget = financeRawNumber(rating.currentPriceTarget);
419
- const priorPriceTarget = financeRawNumber(rating.priorPriceTarget);
420
- return {
421
- date,
422
- firm,
423
- ...(action ? { action } : {}),
424
- ...(current ? { current } : {}),
425
- ...(prior ? { prior } : {}),
426
- ...(currentPriceTarget != null ? { currentPriceTarget } : {}),
427
- ...(priorPriceTarget != null ? { priorPriceTarget } : {}),
428
- };
429
- })
430
- .filter((rating): rating is AnalystResearchData["ratings"][number] => rating !== null)
431
- .sort((left, right) => right.date.localeCompare(left.date))
432
- .slice(0, 100),
433
- earningsEstimates: trend
434
- .map((row) => mapYahooEstimate(row, row.earningsEstimate, "yearAgoEps"))
435
- .filter((estimate): estimate is AnalystEstimateRecord => estimate !== null),
436
- revenueEstimates: trend
437
- .map((row) => mapYahooEstimate(row, row.revenueEstimate, "yearAgoRevenue"))
438
- .filter((estimate): estimate is AnalystEstimateRecord => estimate !== null),
439
- };
440
- }
441
-
442
- function normalizePositiveMarketValue(value: number | undefined, divisor = 1): number | undefined {
443
- if (value == null || !Number.isFinite(value) || value <= 0) return undefined;
444
- return value / divisor;
445
- }
446
-
447
- function normalizeMarketValue(value: number | undefined, divisor = 1): number | undefined {
448
- if (value == null || !Number.isFinite(value)) return undefined;
449
- return value / divisor;
450
- }
451
-
452
- function yahooRawDateTime(value: unknown): Date | null {
453
- const raw = financeRawNumber(value);
454
- if (raw == null) return null;
455
- const date = new Date(raw * 1000);
456
- return Number.isNaN(date.getTime()) ? null : date;
457
- }
458
-
459
- function inferEarningsTiming(date: Date): EarningsEvent["timing"] {
460
- const hour = date.getUTCHours();
461
- if (hour >= 20) return "AMC";
462
- if (hour <= 13) return "BMO";
463
- return "";
464
- }
465
-
466
- function yahooRawDate(value: unknown): string | undefined {
467
- if (value && typeof value === "object") {
468
- const fmt = (value as { fmt?: unknown }).fmt;
469
- if (typeof fmt === "string" && /^\d{4}-\d{2}-\d{2}$/.test(fmt)) return fmt;
470
- }
471
- if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
472
- const raw = financeRawNumber(value);
473
- if (raw == null) return undefined;
474
- const date = new Date(raw * 1000);
475
- return Number.isNaN(date.getTime()) ? undefined : date.toISOString().slice(0, 10);
476
- }
477
-
478
- function deriveShareChange(position: number | undefined, changePercent: number | undefined): number | undefined {
479
- if (position == null || changePercent == null || !Number.isFinite(position) || !Number.isFinite(changePercent)) return undefined;
480
- const denominator = 1 + changePercent;
481
- if (denominator <= 0) return undefined;
482
- return Math.round(position - position / denominator);
483
- }
484
-
485
- function deriveMarketState(meta: NonNullable<ChartResult["meta"]>): MarketState {
486
- const ctp = meta.currentTradingPeriod;
487
- if (!ctp) return "CLOSED";
488
- const now = Math.floor(Date.now() / 1000);
489
- if (ctp.regular?.start && ctp.regular?.end && now >= ctp.regular.start && now < ctp.regular.end) return "REGULAR";
490
- if (ctp.pre?.start && ctp.pre?.end && now >= ctp.pre.start && now < ctp.pre.end) return "PRE";
491
- if (ctp.post?.start && ctp.post?.end && now >= ctp.post.start && now < ctp.post.end) return "POST";
492
- return "CLOSED";
493
- }
494
-
495
- function computeExtendedHoursChange(extPrice: number | undefined, regularPrice: number | undefined): { change?: number; changePct?: number } {
496
- if (extPrice == null || regularPrice == null || regularPrice === 0) return {};
497
- const change = extPrice - regularPrice;
498
- return { change, changePct: (change / regularPrice) * 100 };
499
- }
500
-
501
- type ExtendedHoursData = {
502
- preMarketPrice?: number;
503
- preMarketChange?: number;
504
- preMarketChangePercent?: number;
505
- postMarketPrice?: number;
506
- postMarketChange?: number;
507
- postMarketChangePercent?: number;
508
- };
509
-
510
- /**
511
- * Extract extended hours prices from a 1d intraday chart with includePrePost=true.
512
- * Pre/post market data points sit outside the regular trading period timestamps.
513
- */
514
- function extractExtendedHoursPrices(
515
- meta: NonNullable<ChartResult["meta"]>,
516
- timestamps: number[],
517
- closes: (number | null)[],
518
- marketState: MarketState,
519
- ): ExtendedHoursData {
520
- const ctp = meta.currentTradingPeriod;
521
- if (!ctp || !timestamps.length) return {};
522
-
523
- const regStart = ctp.regular?.start ?? 0;
524
- const regEnd = ctp.regular?.end ?? Infinity;
525
- const regularClose = meta.regularMarketPrice ?? meta.chartPreviousClose;
526
-
527
- if (marketState === "PRE") {
528
- // Find the last pre-market data point (before regular open)
529
- for (let i = timestamps.length - 1; i >= 0; i--) {
530
- if (timestamps[i]! < regStart && closes[i] != null) {
531
- const ext = computeExtendedHoursChange(closes[i]!, regularClose);
532
- return { preMarketPrice: closes[i]!, preMarketChange: ext.change, preMarketChangePercent: ext.changePct };
533
- }
534
- }
535
- } else if (marketState === "POST") {
536
- // Find the last post-market data point (after regular close)
537
- for (let i = timestamps.length - 1; i >= 0; i--) {
538
- if (timestamps[i]! >= regEnd && closes[i] != null) {
539
- const ext = computeExtendedHoursChange(closes[i]!, regularClose);
540
- return { postMarketPrice: closes[i]!, postMarketChange: ext.change, postMarketChangePercent: ext.changePct };
541
- }
542
- }
543
- }
544
- return {};
545
- }
546
-
547
- // Cache TTLs
548
- const QUOTE_TTL = 5 * 60_000; // 5 min
549
- const HISTORY_TTL = 24 * 60 * 60_000; // 24 hours
550
- const INTRADAY_HISTORY_TTL = 5 * 60_000; // 5 min for intraday ranges
551
-
552
- // Maps TimeRange → Yahoo API { range, interval } for optimal granularity
553
- const RANGE_PARAMS: Record<TimeRange, { range: string; interval: string; ttl: number }> = {
554
- "1D": { range: "1d", interval: "5m", ttl: INTRADAY_HISTORY_TTL },
555
- "1W": { range: "5d", interval: "5m", ttl: INTRADAY_HISTORY_TTL },
556
- "1M": { range: "1mo", interval: "15m", ttl: INTRADAY_HISTORY_TTL },
557
- "3M": { range: "3mo", interval: "1h", ttl: QUOTE_TTL },
558
- "6M": { range: "6mo", interval: "1d", ttl: HISTORY_TTL },
559
- "1Y": { range: "1y", interval: "1d", ttl: HISTORY_TTL },
560
- "5Y": { range: "5y", interval: "1d", ttl: HISTORY_TTL },
561
- "ALL": { range: "max", interval: "1wk", ttl: HISTORY_TTL },
562
- };
563
-
564
- const YAHOO_RESOLUTION_SUPPORT = normalizeChartResolutionSupport([
565
- { resolution: "5m", maxRange: "1W" },
566
- { resolution: "15m", maxRange: "1M" },
567
- { resolution: "1h", maxRange: "3M" },
568
- { resolution: "1d", maxRange: "5Y" },
569
- { resolution: "1wk", maxRange: "ALL" },
570
- { resolution: "1mo", maxRange: "ALL" },
571
- ]);
11
+ import { YahooHttpClient } from "./yahoo-finance/http";
12
+ import {
13
+ normalizeSubUnitCurrency,
14
+ } from "./yahoo-finance/mappers";
15
+ import { getYahooSymbol, getYahooSymbolsToTry } from "./yahoo-finance/symbols";
16
+ import type { ChartResult } from "./yahoo-finance/types";
17
+ import {
18
+ fetchYahooAssetProfile,
19
+ fetchYahooChart,
20
+ fetchYahooExtendedHoursData,
21
+ fetchYahooQuoteSupplement,
22
+ fetchYahooTimeseries,
23
+ } from "./yahoo-finance/requests";
24
+ import {
25
+ getYahooChartResolutionCapabilities,
26
+ getYahooChartResolutionSupport,
27
+ loadYahooPriceHistory,
28
+ loadYahooPriceHistoryForResolution,
29
+ } from "./yahoo-finance/history";
30
+ import {
31
+ getYahooOptionQuote,
32
+ loadYahooOptionsChain,
33
+ } from "./yahoo-finance/options";
34
+ import {
35
+ loadYahooAnalystResearch,
36
+ loadYahooCorporateActions,
37
+ loadYahooEarningsCalendar,
38
+ loadYahooHolders,
39
+ } from "./yahoo-finance/quote-summary";
40
+ import {
41
+ loadYahooQuote,
42
+ loadYahooTickerFinancials,
43
+ } from "./yahoo-finance/snapshots";
572
44
 
573
45
  export class YahooFinanceClient implements DataProvider {
574
46
  readonly id = "yahoo";
575
47
  readonly name = "Yahoo Finance";
576
48
 
577
- private crumb: string | null = null;
578
- private cookie: string | null = null;
579
- private crumbPromise: Promise<void> | null = null;
580
49
  private readonly secClient = new SecEdgarClient();
581
50
 
582
- constructor() {}
583
-
584
- private defaultHeaders() {
585
- return {
586
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
587
- Accept: "application/json,text/plain,*/*",
588
- "Accept-Language": "en-US,en;q=0.9",
589
- Referer: "https://finance.yahoo.com/",
590
- };
591
- }
592
-
593
- /** Fetch a crumb + cookie pair needed by some Yahoo endpoints (e.g. options). */
594
- private async ensureCrumb(): Promise<void> {
595
- if (this.crumb && this.cookie) return;
596
- if (this.crumbPromise) return this.crumbPromise;
597
- this.crumbPromise = (async () => {
598
- try {
599
- // Step 1: get a cookie from Yahoo
600
- const cookieResp = await fetch("https://fc.yahoo.com/", {
601
- headers: this.defaultHeaders(),
602
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
603
- redirect: "manual",
604
- });
605
- const setCookie = cookieResp.headers.get("set-cookie");
606
- if (!setCookie) throw new Error("Failed to get Yahoo cookie");
607
- // Extract just the cookie key=value pairs
608
- this.cookie = setCookie.split(",").map((c) => c.split(";")[0]!.trim()).join("; ");
609
-
610
- // Step 2: get the crumb value
611
- const crumbResp = await fetch("https://query2.finance.yahoo.com/v1/test/getcrumb", {
612
- headers: { ...this.defaultHeaders(), Cookie: this.cookie },
613
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
614
- });
615
- if (!crumbResp.ok) throw new Error(`Failed to get crumb: ${crumbResp.status}`);
616
- this.crumb = await crumbResp.text();
617
- if (!this.crumb) throw new Error("Empty crumb response");
618
- } catch (err) {
619
- this.crumb = null;
620
- this.cookie = null;
621
- throw err;
622
- } finally {
623
- this.crumbPromise = null;
624
- }
625
- })();
626
- return this.crumbPromise;
627
- }
628
-
629
- /** Fetch JSON from a Yahoo endpoint that requires crumb authentication. */
630
- private async fetchJsonWithCrumb<T>(label: string, url: string): Promise<T> {
631
- return this.withRetry(label, async () => {
632
- await this.ensureCrumb();
633
- const separator = url.includes("?") ? "&" : "?";
634
- const fullUrl = `${url}${separator}crumb=${encodeURIComponent(this.crumb!)}`;
635
- const resp = await fetch(fullUrl, {
636
- headers: { ...this.defaultHeaders(), Cookie: this.cookie! },
637
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
638
- });
639
- if (resp.status === 401) {
640
- // Crumb expired — clear and let retry logic re-fetch
641
- this.crumb = null;
642
- this.cookie = null;
643
- throw new Error(`[401] Invalid Crumb`);
644
- }
645
- if (!resp.ok) throw new Error(`[${resp.status}] ${(await resp.text()).slice(0, 200)}`);
646
- return resp.json() as Promise<T>;
647
- });
648
- }
649
-
650
- private async withRetry<T>(label: string, fn: () => Promise<T>): Promise<T> {
651
- let lastError: any;
652
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
653
- try {
654
- return await fn();
655
- } catch (error: any) {
656
- lastError = error;
657
- if (!RETRYABLE_ERROR.test(error?.message || String(error)) || attempt === MAX_RETRIES) throw error;
658
- const delay = Math.min(30_000, RETRY_BASE_MS * Math.pow(2, attempt)) + Math.round(Math.random() * 300);
659
- await sleep(delay);
660
- }
661
- }
662
- throw lastError;
663
- }
664
-
665
- private async fetchJson<T>(label: string, url: string): Promise<T> {
666
- return this.withRetry(label, async () => {
667
- const resp = await fetch(url, {
668
- headers: this.defaultHeaders(),
669
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
670
- });
671
- if (!resp.ok) throw new Error(`[${resp.status}] ${(await resp.text()).slice(0, 200)}`);
672
- return resp.json() as Promise<T>;
673
- });
674
- }
675
-
676
- /** All known Yahoo Finance suffixes — used to detect tickers that already include one */
677
- private static KNOWN_SUFFIXES = new Set(
678
- Object.values(EXCHANGE_SUFFIX_MAP).filter(Boolean)
679
- .concat(GENERIC_SUFFIX_FALLBACKS.filter(Boolean)),
680
- );
681
-
682
- /** Check if the ticker already ends with a known Yahoo suffix (e.g. "6324.T") */
683
- private tickerHasSuffix(ticker: string): boolean {
684
- const dot = ticker.indexOf(".");
685
- if (dot < 0) return false;
686
- return YahooFinanceClient.KNOWN_SUFFIXES.has(ticker.slice(dot));
687
- }
688
-
689
- private isHKExchange(exchange: string): boolean {
690
- return exchange === "HKEX" || exchange === "SEHK" || exchange === "HKG";
691
- }
692
-
693
- private normalizeTicker(ticker: string, exchange: string): string {
694
- // Hong Kong stocks need 4-digit codes with leading zeros
695
- if (this.isHKExchange(exchange) && /^\d+$/.test(ticker)) {
696
- return ticker.padStart(4, "0");
697
- }
698
- // Yahoo Finance uses hyphens where IBKR uses spaces (e.g. "HEXA B" → "HEXA-B")
699
- return ticker.replace(/ /g, "-");
700
- }
701
-
702
- private getSymbol(ticker: string, exchange: string): string {
703
- // If ticker already contains a Yahoo suffix, use it as-is
704
- if (this.tickerHasSuffix(ticker)) return ticker;
705
- const suffix = EXCHANGE_SUFFIX_MAP[exchange] ?? "";
706
- return `${this.normalizeTicker(ticker, exchange)}${suffix}`;
707
- }
708
-
709
- private getSymbolsToTry(ticker: string, exchange: string): string[] {
710
- // If ticker already contains a Yahoo suffix, use it as-is
711
- if (this.tickerHasSuffix(ticker)) return [ticker];
712
-
713
- const normalized = this.normalizeTicker(ticker, exchange);
714
-
715
- // For tickers with dots that aren't Yahoo suffixes (e.g. "BTC.USD"),
716
- // also try replacing the dot with a hyphen (Yahoo uses "BTC-USD" for crypto pairs)
717
- const dotVariant = normalized.includes(".") ? normalized.replace(/\./g, "-") : null;
718
-
719
- const ex = (exchange || "").toUpperCase();
720
- if (!ex) {
721
- const symbols = new Set<string>();
722
- // For numeric tickers with no exchange, also try HK-padded variant
723
- const candidates = [normalized];
724
- if (dotVariant) candidates.unshift(dotVariant); // Try hyphen variant first
725
- if (/^\d+$/.test(normalized) && normalized.length < 4) {
726
- candidates.push(normalized.padStart(4, "0"));
727
- }
728
- for (const candidate of candidates) {
729
- for (const suffix of GENERIC_SUFFIX_FALLBACKS) symbols.add(`${candidate}${suffix}`);
730
- }
731
- return Array.from(symbols);
732
- }
733
- const fallbacks = EXCHANGE_FALLBACKS[exchange];
734
- if (fallbacks) {
735
- const results = fallbacks.map((s) => `${normalized}${s}`);
736
- if (dotVariant) results.unshift(...fallbacks.map((s) => `${dotVariant}${s}`));
737
- return results;
738
- }
739
- const primary = this.getSymbol(ticker, exchange);
740
- if (dotVariant) {
741
- const suffix = EXCHANGE_SUFFIX_MAP[exchange] ?? "";
742
- return [`${dotVariant}${suffix}`, primary];
743
- }
744
- return [primary];
745
- }
51
+ constructor(private readonly http = new YahooHttpClient()) {}
746
52
 
747
53
  private async fetchChart(symbol: string, range: string, interval = "1d", includePrePost = false) {
748
- const params = new URLSearchParams({ interval, range, includePrePost: String(includePrePost), events: "div,split" });
749
- const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(symbol)}?${params}`;
750
- const data = await this.fetchJson<ChartResponse>(`chart ${symbol}`, url);
751
- const result = data.chart?.result?.[0];
752
- if (!result?.timestamp?.length) throw new Error(data.chart?.error?.description || `No chart data for ${symbol}`);
753
- const quote = result.indicators?.quote?.[0];
754
- if (!quote) throw new Error(`Missing indicators for ${symbol}`);
755
-
756
- const history: PricePoint[] = [];
757
- for (let i = 0; i < result.timestamp.length; i++) {
758
- const close = quote.close?.[i];
759
- if (close == null || !Number.isFinite(close) || close <= 0) continue;
760
- history.push({
761
- date: new Date((result.timestamp[i]!) * 1000),
762
- open: quote.open?.[i] ?? undefined,
763
- high: quote.high?.[i] ?? undefined,
764
- low: quote.low?.[i] ?? undefined,
765
- close,
766
- volume: quote.volume?.[i] ?? undefined,
767
- });
768
- }
769
- return { meta: result.meta || {}, history };
54
+ return fetchYahooChart(this.http, symbol, range, interval, includePrePost);
770
55
  }
771
56
 
772
57
  /** Fetch extended hours data using 1d intraday chart with pre/post market included */
773
- private async fetchExtendedHoursData(symbol: string, regularPrice: number, meta: NonNullable<ChartResult["meta"]>): Promise<ExtendedHoursData> {
774
- const marketState = deriveMarketState(meta);
775
- if (marketState !== "PRE" && marketState !== "POST") return {};
776
-
777
- try {
778
- const params = new URLSearchParams({ interval: "5m", range: "1d", includePrePost: "true" });
779
- const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(symbol)}?${params}`;
780
- const data = await this.fetchJson<ChartResponse>(`ext-hours ${symbol}`, url);
781
- const result = data.chart?.result?.[0];
782
- if (!result?.timestamp?.length) return {};
783
- const closes = result.indicators?.quote?.[0]?.close || [];
784
- return extractExtendedHoursPrices(meta, result.timestamp, closes, marketState);
785
- } catch {
786
- return {};
787
- }
58
+ private async fetchExtendedHoursData(symbol: string, meta: NonNullable<ChartResult["meta"]>) {
59
+ return fetchYahooExtendedHoursData(this.http, symbol, meta);
788
60
  }
789
61
 
790
62
  private async fetchTimeseries(symbol: string, types: string[], period1 = "2010-01-01") {
791
- const p1 = Math.floor(new Date(period1).getTime() / 1000);
792
- const p2 = Math.floor(Date.now() / 1000);
793
- const params = new URLSearchParams({ type: types.join(","), period1: String(p1), period2: String(p2) });
794
- const url = `https://query1.finance.yahoo.com/ws/fundamentals-timeseries/v1/finance/timeseries/${encodeURIComponent(symbol)}?${params}`;
795
- const data = await this.fetchJson<TimeseriesResponse>(`timeseries ${symbol}`, url);
796
- return data.timeseries?.result || [];
63
+ return fetchYahooTimeseries(this.http, symbol, types, period1);
797
64
  }
798
65
 
799
66
  private async fetchAssetProfile(symbol: string): Promise<CompanyProfile | undefined> {
800
- const params = new URLSearchParams({ modules: "assetProfile" });
801
- const url = `https://query1.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(symbol)}?${params}`;
802
- const data = await this.fetchJsonWithCrumb<QuoteSummaryResponse>(`asset profile ${symbol}`, url);
803
- const profile = data.quoteSummary?.result?.[0]?.assetProfile;
804
- if (!profile) return undefined;
805
-
806
- const normalized: CompanyProfile = {
807
- description: profile.longBusinessSummary?.trim() || undefined,
808
- sector: profile.sector?.trim() || undefined,
809
- industry: profile.industry?.trim() || undefined,
810
- };
811
-
812
- return normalized.description || normalized.sector || normalized.industry ? normalized : undefined;
67
+ return fetchYahooAssetProfile(this.http, symbol);
813
68
  }
814
69
 
815
70
  private async fetchQuoteSupplement(
816
71
  symbol: string,
817
72
  currencyDivisor = 1,
818
73
  ): Promise<Pick<Quote, "bid" | "ask" | "bidSize" | "askSize" | "previousClose" | "open" | "high" | "low">> {
819
- try {
820
- const params = new URLSearchParams({ modules: "summaryDetail" });
821
- const url = `https://query1.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(symbol)}?${params}`;
822
- const data = await this.fetchJsonWithCrumb<QuoteSummaryResponse>(`quote supplement ${symbol}`, url);
823
- const summaryDetail = data.quoteSummary?.result?.[0]?.summaryDetail;
824
- if (!summaryDetail) return {};
825
-
826
- const bid = normalizePositiveMarketValue(financeRawNumber(summaryDetail.bid), currencyDivisor);
827
- const ask = normalizePositiveMarketValue(financeRawNumber(summaryDetail.ask), currencyDivisor);
828
- const bidSize = financeRawNumber(summaryDetail.bidSize);
829
- const askSize = financeRawNumber(summaryDetail.askSize);
830
- const previousClose = normalizeMarketValue(financeRawNumber(summaryDetail.previousClose), currencyDivisor);
831
- const open = normalizeMarketValue(financeRawNumber(summaryDetail.open), currencyDivisor);
832
- const high = normalizeMarketValue(financeRawNumber(summaryDetail.dayHigh), currencyDivisor);
833
- const low = normalizeMarketValue(financeRawNumber(summaryDetail.dayLow), currencyDivisor);
834
-
835
- return {
836
- bid,
837
- ask,
838
- bidSize,
839
- askSize,
840
- previousClose,
841
- open,
842
- high,
843
- low,
844
- };
845
- } catch {
846
- return {};
847
- }
848
- }
849
-
850
- private parseTimeseries(results: Array<Record<string, any>>) {
851
- const parsed: Record<string, Array<{ asOfDate: string; periodType?: string; value: number }>> = {};
852
- for (const result of results) {
853
- const type = result?.meta?.type?.[0];
854
- if (!type) continue;
855
- const key = Object.keys(result).find((k) => k !== "meta" && k !== "timestamp");
856
- if (!key) continue;
857
- parsed[type] = (Array.isArray(result[key]) ? result[key] : [])
858
- .map((p: any) => ({ asOfDate: p?.asOfDate, periodType: p?.periodType, value: p?.reportedValue?.raw }))
859
- .filter((p: any) => typeof p.asOfDate === "string" && typeof p.value === "number" && Number.isFinite(p.value));
860
- }
861
- return parsed;
862
- }
863
-
864
- private computeReturn(history: PricePoint[], days: number): number | undefined {
865
- if (history.length < 2) return undefined;
866
- const latest = history[history.length - 1]!;
867
- const cutoff = new Date(latest.date.getTime() - days * 86400_000);
868
- let baseline = history[0]!;
869
- for (const p of history) {
870
- if (p.date <= cutoff) baseline = p;
871
- else break;
872
- }
873
- if (!baseline.close) return undefined;
874
- return (latest.close - baseline.close) / baseline.close;
74
+ return fetchYahooQuoteSupplement(this.http, symbol, currencyDivisor);
875
75
  }
876
76
 
877
77
  /** Fetch full financials for a ticker */
878
78
  async getTickerFinancials(ticker: string, exchange = "", _context?: MarketDataRequestContext): Promise<TickerFinancials> {
879
- const symbolsToTry = this.getSymbolsToTry(ticker, exchange);
79
+ const symbolsToTry = getYahooSymbolsToTry(ticker, exchange);
880
80
  let lastError: any;
881
81
 
882
82
  for (const symbol of symbolsToTry) {
883
83
  try {
884
- const result = await this.fetchFullFinancials(symbol);
84
+ const result = await loadYahooTickerFinancials(symbol, {
85
+ fetchAssetProfile: (targetSymbol) => this.fetchAssetProfile(targetSymbol),
86
+ fetchChart: (targetSymbol, range, interval) => this.fetchChart(targetSymbol, range, interval),
87
+ fetchExtendedHoursData: (targetSymbol, meta) => this.fetchExtendedHoursData(targetSymbol, meta),
88
+ fetchQuoteSupplement: (targetSymbol, currencyDivisor) =>
89
+ this.fetchQuoteSupplement(targetSymbol, currencyDivisor),
90
+ fetchTimeseries: (targetSymbol, types, period1) => this.fetchTimeseries(targetSymbol, types, period1),
91
+ providerId: this.id,
92
+ });
885
93
  return result;
886
94
  } catch (err) {
887
95
  lastError = err;
@@ -890,274 +98,31 @@ export class YahooFinanceClient implements DataProvider {
890
98
  throw lastError || new Error(`No data for ${ticker}`);
891
99
  }
892
100
 
893
- private async fetchFullFinancials(symbol: string): Promise<TickerFinancials> {
894
- const [chart, tsRaw, profile] = await Promise.all([
895
- this.fetchChart(symbol, "5y"),
896
- this.fetchTimeseries(symbol, [
897
- ...TIMESERIES_TYPES.annual,
898
- ...TIMESERIES_TYPES.quarterly,
899
- ...TIMESERIES_TYPES.trailing,
900
- ]),
901
- this.fetchAssetProfile(symbol).catch(() => undefined),
902
- ]);
903
-
904
- const { meta, history } = chart;
905
- if (!history.length) throw new Error(`No history for ${symbol}`);
906
-
907
- const metrics = this.parseTimeseries(tsRaw);
908
- const latest = (type: string) => {
909
- const pts = metrics[type];
910
- return pts?.length ? pts[pts.length - 1]!.value : undefined;
911
- };
912
-
913
- // Normalize sub-unit currencies (e.g. GBp → GBP, dividing prices by 100)
914
- const rawCurrency = meta.currency || "USD";
915
- const { currency: normalizedCurrency, divisor: currencyDivisor } = normalizeSubUnitCurrency(rawCurrency);
916
-
917
- if (currencyDivisor !== 1) {
918
- // Normalize price history points
919
- for (const point of history) {
920
- point.close /= currencyDivisor;
921
- if (point.open != null) point.open /= currencyDivisor;
922
- if (point.high != null) point.high /= currencyDivisor;
923
- if (point.low != null) point.low /= currencyDivisor;
924
- }
925
- // Normalize meta prices
926
- if (meta.regularMarketPrice != null) meta.regularMarketPrice /= currencyDivisor;
927
- if (meta.chartPreviousClose != null) meta.chartPreviousClose /= currencyDivisor;
928
- if (meta.fiftyTwoWeekHigh != null) meta.fiftyTwoWeekHigh /= currencyDivisor;
929
- if (meta.fiftyTwoWeekLow != null) meta.fiftyTwoWeekLow /= currencyDivisor;
930
- }
931
-
932
- const quoteSupplement = await this.fetchQuoteSupplement(symbol, currencyDivisor);
933
-
934
- const currentPrice = meta.regularMarketPrice ?? history[history.length - 1]!.close;
935
- const prev = history.length > 1 ? history[history.length - 2]!.close : meta.chartPreviousClose;
936
- const change = prev != null ? currentPrice - prev : 0;
937
- const changePct = prev ? (change / prev) * 100 : 0;
938
-
939
- const marketState = deriveMarketState(meta);
940
- const extHours = await this.fetchExtendedHoursData(symbol, currentPrice, meta);
941
-
942
- // Normalize extended hours prices too
943
- if (currencyDivisor !== 1) {
944
- if (extHours.preMarketPrice != null) extHours.preMarketPrice /= currencyDivisor;
945
- if (extHours.preMarketChange != null) extHours.preMarketChange /= currencyDivisor;
946
- if (extHours.postMarketPrice != null) extHours.postMarketPrice /= currencyDivisor;
947
- if (extHours.postMarketChange != null) extHours.postMarketChange /= currencyDivisor;
948
- }
949
-
950
- const quote: Quote = {
951
- symbol,
952
- providerId: this.id,
953
- price: currentPrice,
954
- currency: normalizedCurrency,
955
- change,
956
- changePercent: changePct,
957
- high52w: meta.fiftyTwoWeekHigh,
958
- low52w: meta.fiftyTwoWeekLow,
959
- marketCap: latest("trailingMarketCap"),
960
- name: meta.shortName || meta.longName,
961
- lastUpdated: Date.now(),
962
- exchangeName: meta.exchangeName,
963
- fullExchangeName: meta.fullExchangeName,
964
- listingExchangeName: meta.exchangeName,
965
- listingExchangeFullName: meta.fullExchangeName,
966
- marketState,
967
- sessionConfidence: "derived",
968
- dataSource: "delayed",
969
- ...quoteSupplement,
970
- ...extHours,
971
- };
972
-
973
- const revenue = latest("annualTotalRevenue");
974
- const netIncome = latest("annualNetIncome");
975
-
976
- const fundamentals: Fundamentals = {
977
- trailingPE: latest("trailingPeRatio"),
978
- forwardPE: latest("trailingForwardPeRatio"),
979
- pegRatio: latest("trailingPegRatio"),
980
- enterpriseValue: latest("trailingEnterpriseValue"),
981
- operatingCashFlow: latest("trailingOperatingCashFlow"),
982
- freeCashFlow: latest("trailingFreeCashFlow"),
983
- dividendYield: latest("trailingDividendYield"),
984
- revenue,
985
- netIncome,
986
- eps: latest("annualDilutedEPS"),
987
- operatingMargin: revenue && latest("annualEBITDA") != null ? latest("annualEBITDA")! / revenue : undefined,
988
- profitMargin: revenue && netIncome != null ? netIncome / revenue : undefined,
989
- return1Y: this.computeReturn(history, 365),
990
- return3Y: this.computeReturn(history, 3 * 365),
991
- sharesOutstanding: latest("annualDilutedAverageShares"),
992
- };
993
-
994
- // Build statement arrays
995
- const buildStatements = (prefix: "annual" | "quarterly"): FinancialStatement[] => {
996
- const byDate = new Map<string, FinancialStatement>();
997
- const assign = (type: string, field: keyof FinancialStatement) => {
998
- for (const pt of metrics[type] || []) {
999
- const row = byDate.get(pt.asOfDate) || { date: pt.asOfDate };
1000
- (row as any)[field] = pt.value;
1001
- byDate.set(pt.asOfDate, row);
1002
- }
1003
- };
1004
- // Income Statement
1005
- assign(`${prefix}TotalRevenue`, "totalRevenue");
1006
- assign(`${prefix}CostOfRevenue`, "costOfRevenue");
1007
- assign(`${prefix}GrossProfit`, "grossProfit");
1008
- assign(`${prefix}SellingGeneralAndAdministration`, "sellingGeneralAndAdministration");
1009
- assign(`${prefix}ResearchAndDevelopment`, "researchAndDevelopment");
1010
- assign(`${prefix}OperatingExpense`, "operatingExpense");
1011
- assign(`${prefix}OperatingIncome`, "operatingIncome");
1012
- assign(`${prefix}InterestExpense`, "interestExpense");
1013
- assign(`${prefix}TaxProvision`, "taxProvision");
1014
- assign(`${prefix}NetIncome`, "netIncome");
1015
- assign(`${prefix}EBITDA`, "ebitda");
1016
- assign(`${prefix}BasicEPS`, "basicEps");
1017
- assign(`${prefix}DilutedEPS`, "eps");
1018
- assign(`${prefix}DilutedAverageShares`, "dilutedShares");
1019
- // Cash Flow
1020
- assign(`${prefix}OperatingCashFlow`, "operatingCashFlow");
1021
- assign(`${prefix}CapitalExpenditure`, "capitalExpenditure");
1022
- assign(`${prefix}FreeCashFlow`, "freeCashFlow");
1023
- assign(`${prefix}InvestingCashFlow`, "investingCashFlow");
1024
- assign(`${prefix}FinancingCashFlow`, "financingCashFlow");
1025
- assign(`${prefix}IssuanceOfDebt`, "issuanceOfDebt");
1026
- assign(`${prefix}RepurchaseOfCapitalStock`, "repurchaseOfCapitalStock");
1027
- assign(`${prefix}CashDividendsPaid`, "cashDividendsPaid");
1028
- // Balance Sheet
1029
- assign(`${prefix}TotalAssets`, "totalAssets");
1030
- assign(`${prefix}CurrentAssets`, "currentAssets");
1031
- assign(`${prefix}CashAndCashEquivalents`, "cashAndCashEquivalents");
1032
- assign(`${prefix}TotalLiabilitiesNetMinorityInterest`, "totalLiabilities");
1033
- assign(`${prefix}CurrentLiabilities`, "currentLiabilities");
1034
- assign(`${prefix}LongTermDebt`, "longTermDebt");
1035
- assign(`${prefix}TotalDebt`, "totalDebt");
1036
- assign(`${prefix}StockholdersEquity`, "totalEquity");
1037
- assign(`${prefix}RetainedEarnings`, "retainedEarnings");
1038
- return Array.from(byDate.values()).sort((a, b) => a.date.localeCompare(b.date));
1039
- };
1040
-
1041
- return {
1042
- quote,
1043
- fundamentals,
1044
- profile,
1045
- annualStatements: buildStatements("annual"),
1046
- quarterlyStatements: buildStatements("quarterly"),
1047
- priceHistory: history,
1048
- };
1049
- }
1050
-
1051
- private async getOptionQuote(ticker: string, context?: MarketDataRequestContext): Promise<Quote> {
1052
- const parsed = parseOptionSymbol(ticker);
1053
- if (!parsed) throw new Error(`Unsupported option symbol ${ticker}`);
1054
- const chain = await this.getOptionsChain(parsed.underlying, "", parsed.expTs, context);
1055
- const contracts = parsed.side === "C" ? chain.calls : chain.puts;
1056
- const contract = contracts.find((candidate) =>
1057
- Math.abs(candidate.strike - parsed.strike) < 0.001 &&
1058
- candidate.expiration === parsed.expTs
1059
- );
1060
- if (!contract) throw new Error(`No option contract for ${ticker}`);
1061
-
1062
- const mark = contract.bid > 0 && contract.ask > 0
1063
- ? (contract.bid + contract.ask) / 2
1064
- : contract.bid > 0
1065
- ? contract.bid
1066
- : contract.ask > 0
1067
- ? contract.ask
1068
- : undefined;
1069
- return {
1070
- symbol: ticker,
1071
- providerId: this.id,
1072
- price: mark ?? contract.lastPrice,
1073
- currency: contract.currency || "USD",
1074
- change: contract.change,
1075
- changePercent: contract.percentChange,
1076
- volume: contract.volume,
1077
- bid: contract.bid,
1078
- ask: contract.ask,
1079
- mark,
1080
- name: contract.contractSymbol,
1081
- lastUpdated: contract.lastTradeDate > 0
1082
- ? contract.lastTradeDate * 1000
1083
- : Date.now(),
1084
- exchangeName: "OPTIONS",
1085
- fullExchangeName: "OPTIONS",
1086
- listingExchangeName: "OPTIONS",
1087
- listingExchangeFullName: "OPTIONS",
1088
- dataSource: "delayed",
1089
- };
1090
- }
1091
-
1092
101
  /** Fetch just a quote (lighter weight) */
1093
102
  async getQuote(ticker: string, exchange = "", context?: MarketDataRequestContext): Promise<Quote> {
1094
103
  if (parseOptionSymbol(ticker)) {
1095
- return this.getOptionQuote(ticker, context);
104
+ return getYahooOptionQuote({
105
+ context,
106
+ getOptionsChain: (underlying, requestExchange, expirationDate, requestContext) => (
107
+ this.getOptionsChain(underlying, requestExchange, expirationDate, requestContext)
108
+ ),
109
+ providerId: this.id,
110
+ ticker,
111
+ });
1096
112
  }
1097
113
 
1098
- const symbolsToTry = this.getSymbolsToTry(ticker, exchange);
114
+ const symbolsToTry = getYahooSymbolsToTry(ticker, exchange);
1099
115
  let lastError: any;
1100
116
 
1101
117
  for (const symbol of symbolsToTry) {
1102
118
  try {
1103
- const { meta, history } = await this.fetchChart(symbol, "1mo");
1104
-
1105
- // Normalize sub-unit currencies
1106
- const rawCurrency = meta.currency || "USD";
1107
- const { currency: normalizedCurrency, divisor: currencyDivisor } = normalizeSubUnitCurrency(rawCurrency);
1108
-
1109
- if (currencyDivisor !== 1) {
1110
- for (const point of history) {
1111
- point.close /= currencyDivisor;
1112
- if (point.open != null) point.open /= currencyDivisor;
1113
- if (point.high != null) point.high /= currencyDivisor;
1114
- if (point.low != null) point.low /= currencyDivisor;
1115
- }
1116
- if (meta.regularMarketPrice != null) meta.regularMarketPrice /= currencyDivisor;
1117
- if (meta.chartPreviousClose != null) meta.chartPreviousClose /= currencyDivisor;
1118
- if (meta.fiftyTwoWeekHigh != null) meta.fiftyTwoWeekHigh /= currencyDivisor;
1119
- if (meta.fiftyTwoWeekLow != null) meta.fiftyTwoWeekLow /= currencyDivisor;
1120
- }
1121
-
1122
- const quoteSupplement = await this.fetchQuoteSupplement(symbol, currencyDivisor);
1123
- const latest = history[history.length - 1]!;
1124
- const prev = history.length > 1 ? history[history.length - 2]!.close : meta.chartPreviousClose;
1125
- const price = meta.regularMarketPrice ?? latest.close;
1126
- const change = prev != null ? price - prev : 0;
1127
-
1128
- const marketState = deriveMarketState(meta);
1129
- const extHours = await this.fetchExtendedHoursData(symbol, price, meta);
1130
-
1131
- if (currencyDivisor !== 1) {
1132
- if (extHours.preMarketPrice != null) extHours.preMarketPrice /= currencyDivisor;
1133
- if (extHours.preMarketChange != null) extHours.preMarketChange /= currencyDivisor;
1134
- if (extHours.postMarketPrice != null) extHours.postMarketPrice /= currencyDivisor;
1135
- if (extHours.postMarketChange != null) extHours.postMarketChange /= currencyDivisor;
1136
- }
1137
-
1138
- const quote: Quote = {
1139
- symbol,
119
+ return await loadYahooQuote(symbol, {
120
+ fetchChart: (targetSymbol, range, interval) => this.fetchChart(targetSymbol, range, interval),
121
+ fetchExtendedHoursData: (targetSymbol, meta) => this.fetchExtendedHoursData(targetSymbol, meta),
122
+ fetchQuoteSupplement: (targetSymbol, currencyDivisor) =>
123
+ this.fetchQuoteSupplement(targetSymbol, currencyDivisor),
1140
124
  providerId: this.id,
1141
- price,
1142
- currency: normalizedCurrency,
1143
- change,
1144
- changePercent: prev ? (change / prev) * 100 : 0,
1145
- high52w: meta.fiftyTwoWeekHigh,
1146
- low52w: meta.fiftyTwoWeekLow,
1147
- name: meta.shortName || meta.longName,
1148
- lastUpdated: Date.now(),
1149
- exchangeName: meta.exchangeName,
1150
- fullExchangeName: meta.fullExchangeName,
1151
- listingExchangeName: meta.exchangeName,
1152
- listingExchangeFullName: meta.fullExchangeName,
1153
- marketState,
1154
- sessionConfidence: "derived",
1155
- dataSource: "delayed",
1156
- ...quoteSupplement,
1157
- ...extHours,
1158
- };
1159
-
1160
- return quote;
125
+ });
1161
126
  } catch (err) {
1162
127
  lastError = err;
1163
128
  }
@@ -1186,7 +151,7 @@ export class YahooFinanceClient implements DataProvider {
1186
151
  const url = `https://query1.finance.yahoo.com/v1/finance/search?q=${encodeURIComponent(query)}&quotesCount=10&newsCount=0`;
1187
152
  try {
1188
153
  const resp = await fetch(url, {
1189
- headers: this.defaultHeaders(),
154
+ headers: this.http.defaultHeaders(),
1190
155
  signal: AbortSignal.timeout(5000),
1191
156
  });
1192
157
  if (!resp.ok) return [];
@@ -1206,11 +171,11 @@ export class YahooFinanceClient implements DataProvider {
1206
171
  /** Fetch news for a ticker */
1207
172
  async getNews(ticker: string, count = 10, exchange = "", _context?: MarketDataRequestContext): Promise<NewsItem[]> {
1208
173
  // Use the Yahoo symbol for better search results on international tickers
1209
- const symbol = this.getSymbol(ticker, exchange);
174
+ const symbol = getYahooSymbol(ticker, exchange);
1210
175
  const url = `https://query1.finance.yahoo.com/v1/finance/search?q=${encodeURIComponent(symbol)}&quotesCount=0&newsCount=${count}`;
1211
176
  try {
1212
177
  const resp = await fetch(url, {
1213
- headers: this.defaultHeaders(),
178
+ headers: this.http.defaultHeaders(),
1214
179
  signal: AbortSignal.timeout(5000),
1215
180
  });
1216
181
  if (!resp.ok) return [];
@@ -1230,92 +195,31 @@ export class YahooFinanceClient implements DataProvider {
1230
195
  }
1231
196
 
1232
197
  async getHolders(ticker: string, exchange = "", _context?: MarketDataRequestContext): Promise<HolderData> {
1233
- const symbolsToTry = this.getSymbolsToTry(ticker, exchange);
1234
- let lastError: any;
1235
-
1236
- for (const symbol of symbolsToTry) {
1237
- try {
1238
- const params = new URLSearchParams({
1239
- modules: "price,majorHoldersBreakdown,institutionOwnership",
1240
- });
1241
- const url = `https://query1.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(symbol)}?${params}`;
1242
- const data = await this.fetchJsonWithCrumb<QuoteSummaryResponse>(`holders ${symbol}`, url);
1243
- const result = data.quoteSummary?.result?.[0];
1244
- if (!result) throw new Error(`No holder data for ${symbol}`);
1245
-
1246
- const holders: HolderRecord[] = (result.institutionOwnership?.ownershipList ?? [])
1247
- .map((item): HolderRecord | null => {
1248
- const name = item.organization?.trim();
1249
- if (!name) return null;
1250
- const shares = financeRawNumber(item.position);
1251
- const changePercent = financeRawNumber(item.pctChange);
1252
- return {
1253
- providerId: this.id,
1254
- ownerType: "institution",
1255
- name,
1256
- reportDate: yahooRawDate(item.reportDate),
1257
- shares,
1258
- value: financeRawNumber(item.value),
1259
- percentHeld: financeRawNumber(item.pctHeld),
1260
- changePercent,
1261
- changeShares: deriveShareChange(shares, changePercent),
1262
- };
1263
- })
1264
- .filter((holder): holder is HolderRecord => holder !== null);
1265
- const asOf = holders
1266
- .map((holder) => holder.reportDate)
1267
- .filter((date): date is string => !!date)
1268
- .sort()
1269
- .at(-1);
1270
-
1271
- return {
1272
- providerId: this.id,
1273
- symbol: result.price?.symbol ?? symbol,
1274
- name: result.price?.shortName ?? result.price?.longName,
1275
- currency: result.price?.currency,
1276
- exchange: result.price?.exchangeName,
1277
- asOf,
1278
- summary: {
1279
- insidersPercentHeld: financeRawNumber(result.majorHoldersBreakdown?.insidersPercentHeld),
1280
- institutionsPercentHeld: financeRawNumber(result.majorHoldersBreakdown?.institutionsPercentHeld),
1281
- institutionsFloatPercentHeld: financeRawNumber(result.majorHoldersBreakdown?.institutionsFloatPercentHeld),
1282
- institutionsCount: financeRawNumber(result.majorHoldersBreakdown?.institutionsCount),
1283
- },
1284
- holders,
1285
- };
1286
- } catch (err) {
1287
- lastError = err;
1288
- }
1289
- }
1290
-
1291
- throw lastError || new Error(`No holder data for ${ticker}`);
198
+ return loadYahooHolders({
199
+ exchange,
200
+ fetchJsonWithCrumb: (url) => this.http.fetchJsonWithCrumb(url),
201
+ providerId: this.id,
202
+ ticker,
203
+ });
1292
204
  }
1293
205
 
1294
206
  async getAnalystResearch(ticker: string, exchange = "", _context?: MarketDataRequestContext): Promise<AnalystResearchData> {
1295
- const symbolsToTry = this.getSymbolsToTry(ticker, exchange);
1296
- let firstEmpty: AnalystResearchData | null = null;
1297
- let lastError: any;
1298
-
1299
- for (const symbol of symbolsToTry) {
1300
- try {
1301
- const params = new URLSearchParams({
1302
- modules: "price,financialData,recommendationTrend,upgradeDowngradeHistory,earningsTrend",
1303
- });
1304
- const url = `https://query1.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(symbol)}?${params}`;
1305
- const data = await this.fetchJsonWithCrumb<QuoteSummaryResponse>(`analyst research ${symbol}`, url);
1306
- const result = data.quoteSummary?.result?.[0];
1307
- if (!result) throw new Error(`No analyst data for ${symbol}`);
1308
-
1309
- const research = mapYahooAnalystResearchResponse(result, symbol);
1310
- if (hasAnalystResearchValue(research)) return research;
1311
- firstEmpty ??= research;
1312
- } catch (err) {
1313
- lastError = err;
1314
- }
1315
- }
207
+ return loadYahooAnalystResearch({
208
+ exchange,
209
+ fetchJsonWithCrumb: (url) => this.http.fetchJsonWithCrumb(url),
210
+ providerId: this.id,
211
+ ticker,
212
+ });
213
+ }
1316
214
 
1317
- if (firstEmpty) return firstEmpty;
1318
- throw lastError || new Error(`No analyst data for ${ticker}`);
215
+ async getCorporateActions(ticker: string, exchange = "", _context?: MarketDataRequestContext): Promise<CorporateActionsData> {
216
+ return loadYahooCorporateActions({
217
+ exchange,
218
+ fetchChart: (symbol, range, interval) => this.fetchChart(symbol, range, interval),
219
+ fetchJsonWithCrumb: (url) => this.http.fetchJsonWithCrumb(url),
220
+ providerId: this.id,
221
+ ticker,
222
+ });
1319
223
  }
1320
224
 
1321
225
  async getSecFilings(ticker: string, count = 10, _exchange = "", _context?: MarketDataRequestContext): Promise<SecFilingItem[]> {
@@ -1331,7 +235,7 @@ export class YahooFinanceClient implements DataProvider {
1331
235
  try {
1332
236
  const resp = await fetch(url, {
1333
237
  headers: {
1334
- ...this.defaultHeaders(),
238
+ ...this.http.defaultHeaders(),
1335
239
  Accept: "text/html",
1336
240
  },
1337
241
  signal: AbortSignal.timeout(8000),
@@ -1357,48 +261,20 @@ export class YahooFinanceClient implements DataProvider {
1357
261
 
1358
262
  /** Fetch price history with appropriate granularity for the given time range */
1359
263
  getChartResolutionSupport(): ChartResolutionSupport[] {
1360
- return YAHOO_RESOLUTION_SUPPORT;
264
+ return getYahooChartResolutionSupport();
1361
265
  }
1362
266
 
1363
267
  getChartResolutionCapabilities(): ManualChartResolution[] {
1364
- return YAHOO_RESOLUTION_SUPPORT.map((entry) => entry.resolution);
1365
- }
1366
-
1367
- private async loadPriceHistory(
1368
- ticker: string,
1369
- exchange: string,
1370
- range: string,
1371
- interval: ManualChartResolution,
1372
- ): Promise<PricePoint[]> {
1373
- const symbolsToTry = this.getSymbolsToTry(ticker, exchange);
1374
- let lastError: any;
1375
-
1376
- for (const symbol of symbolsToTry) {
1377
- try {
1378
- const { meta, history } = await this.fetchChart(symbol, range, interval);
1379
-
1380
- // Normalize sub-unit currencies in price history
1381
- const { divisor } = normalizeSubUnitCurrency(meta.currency || "USD");
1382
- if (divisor !== 1) {
1383
- for (const point of history) {
1384
- point.close /= divisor;
1385
- if (point.open != null) point.open /= divisor;
1386
- if (point.high != null) point.high /= divisor;
1387
- if (point.low != null) point.low /= divisor;
1388
- }
1389
- }
1390
-
1391
- return history;
1392
- } catch (err) {
1393
- lastError = err;
1394
- }
1395
- }
1396
- throw lastError || new Error(`No history for ${ticker}`);
268
+ return getYahooChartResolutionCapabilities();
1397
269
  }
1398
270
 
1399
271
  async getPriceHistory(ticker: string, exchange = "", range: TimeRange, _context?: MarketDataRequestContext): Promise<PricePoint[]> {
1400
- const params = RANGE_PARAMS[range];
1401
- return this.loadPriceHistory(ticker, exchange, params.range, params.interval as ManualChartResolution);
272
+ return loadYahooPriceHistory({
273
+ ticker,
274
+ exchange,
275
+ range,
276
+ fetchChart: (symbol, chartRange, interval) => this.fetchChart(symbol, chartRange, interval),
277
+ });
1402
278
  }
1403
279
 
1404
280
  async getPriceHistoryForResolution(
@@ -1408,196 +284,27 @@ export class YahooFinanceClient implements DataProvider {
1408
284
  resolution: ManualChartResolution,
1409
285
  _context?: MarketDataRequestContext,
1410
286
  ): Promise<PricePoint[]> {
1411
- const params = RANGE_PARAMS[bufferRange];
1412
- return this.loadPriceHistory(ticker, exchange, params.range, resolution);
287
+ return loadYahooPriceHistoryForResolution({
288
+ ticker,
289
+ exchange,
290
+ bufferRange,
291
+ resolution,
292
+ fetchChart: (symbol, chartRange, interval) => this.fetchChart(symbol, chartRange, interval),
293
+ });
1413
294
  }
1414
295
 
1415
296
  // ── Options Chain ──────────────────────────────────────────────────
1416
297
 
1417
- private mapContract(raw: Record<string, any>): OptionContract {
1418
- return {
1419
- contractSymbol: raw.contractSymbol ?? "",
1420
- strike: raw.strike ?? 0,
1421
- currency: raw.currency ?? "USD",
1422
- lastPrice: raw.lastPrice ?? 0,
1423
- change: raw.change ?? 0,
1424
- percentChange: raw.percentChange ?? 0,
1425
- volume: raw.volume ?? 0,
1426
- openInterest: raw.openInterest ?? 0,
1427
- bid: raw.bid ?? 0,
1428
- ask: raw.ask ?? 0,
1429
- impliedVolatility: raw.impliedVolatility ?? 0,
1430
- inTheMoney: raw.inTheMoney ?? false,
1431
- expiration: raw.expiration ?? 0,
1432
- lastTradeDate: raw.lastTradeDate ?? 0,
1433
- };
1434
- }
1435
-
1436
298
  async getOptionsChain(ticker: string, exchange = "", expirationDate?: number, _context?: MarketDataRequestContext): Promise<OptionsChain> {
1437
- const symbolsToTry = this.getSymbolsToTry(ticker, exchange);
1438
- let lastError: any;
1439
-
1440
- for (const symbol of symbolsToTry) {
1441
- try {
1442
- let url = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}`;
1443
- if (expirationDate != null) url += `?date=${expirationDate}`;
1444
-
1445
- const data = await this.fetchJsonWithCrumb<{
1446
- optionChain?: {
1447
- result?: Array<{
1448
- underlyingSymbol?: string;
1449
- expirationDates?: number[];
1450
- options?: Array<{
1451
- calls?: Array<Record<string, any>>;
1452
- puts?: Array<Record<string, any>>;
1453
- }>;
1454
- }>;
1455
- };
1456
- }>("options " + symbol, url);
1457
-
1458
- const result = data.optionChain?.result?.[0];
1459
- if (!result) throw new Error("No options data");
1460
-
1461
- const opts = result.options?.[0];
1462
- const chain: OptionsChain = {
1463
- underlyingSymbol: result.underlyingSymbol ?? symbol,
1464
- expirationDates: result.expirationDates ?? [],
1465
- calls: (opts?.calls ?? []).map((c) => this.mapContract(c)),
1466
- puts: (opts?.puts ?? []).map((p) => this.mapContract(p)),
1467
- };
1468
-
1469
- return chain;
1470
- } catch (err) {
1471
- lastError = err;
1472
- }
1473
- }
1474
- throw lastError || new Error(`No options chain for ${ticker}`);
299
+ return loadYahooOptionsChain({
300
+ exchange,
301
+ expirationDate,
302
+ fetchJsonWithCrumb: (url) => this.http.fetchJsonWithCrumb(url),
303
+ ticker,
304
+ });
1475
305
  }
1476
306
 
1477
307
  async getEarningsCalendar(symbols: string[], _context?: MarketDataRequestContext): Promise<EarningsEvent[]> {
1478
- const results: EarningsEvent[] = [];
1479
- const BATCH_SIZE = 5;
1480
-
1481
- for (let i = 0; i < symbols.length; i += BATCH_SIZE) {
1482
- if (i > 0) await new Promise((r) => setTimeout(r, 200));
1483
- const batch = symbols.slice(i, i + BATCH_SIZE);
1484
-
1485
- const settled = await Promise.allSettled(
1486
- batch.map(async (symbol) => {
1487
- const params = new URLSearchParams({ modules: "calendarEvents,earningsTrend,quoteType" });
1488
- const url = `https://query1.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(symbol)}?${params}`;
1489
- const data = await this.fetchJsonWithCrumb<{
1490
- quoteSummary?: {
1491
- result?: Array<{
1492
- calendarEvents?: {
1493
- earnings?: {
1494
- earningsDate?: Array<{ raw: number }>;
1495
- earningsCallDate?: Array<{ raw: number }>;
1496
- isEarningsDateEstimate?: boolean;
1497
- earningsAverage?: { raw: number };
1498
- earningsLow?: { raw: number };
1499
- earningsHigh?: { raw: number };
1500
- revenueAverage?: { raw: number };
1501
- revenueLow?: { raw: number };
1502
- revenueHigh?: { raw: number };
1503
- };
1504
- };
1505
- earningsTrend?: {
1506
- trend?: Array<{
1507
- period: string;
1508
- earningsEstimate?: {
1509
- avg?: { raw: number };
1510
- low?: { raw: number };
1511
- high?: { raw: number };
1512
- yearAgoEps?: { raw: number };
1513
- numberOfAnalysts?: { raw: number };
1514
- growth?: { raw: number };
1515
- };
1516
- revenueEstimate?: {
1517
- avg?: { raw: number };
1518
- low?: { raw: number };
1519
- high?: { raw: number };
1520
- yearAgoRevenue?: { raw: number };
1521
- numberOfAnalysts?: { raw: number };
1522
- growth?: { raw: number };
1523
- };
1524
- epsTrend?: {
1525
- current?: { raw: number };
1526
- "7daysAgo"?: { raw: number };
1527
- "30daysAgo"?: { raw: number };
1528
- };
1529
- epsRevisions?: {
1530
- upLast7days?: { raw: number };
1531
- upLast30days?: { raw: number };
1532
- downLast7Days?: { raw: number };
1533
- downLast30days?: { raw: number };
1534
- };
1535
- }>;
1536
- };
1537
- quoteType?: {
1538
- shortName?: string;
1539
- longName?: string;
1540
- };
1541
- }>;
1542
- };
1543
- }>(`earnings ${symbol}`, url);
1544
-
1545
- const mod = data.quoteSummary?.result?.[0];
1546
- if (!mod) return null;
1547
-
1548
- const cal = mod.calendarEvents?.earnings;
1549
- if (!cal?.earningsDate?.length) return null;
1550
-
1551
- const earningsDate = new Date(cal.earningsDate[0]!.raw * 1000);
1552
- if (isNaN(earningsDate.getTime())) return null;
1553
-
1554
- const currentQtr = mod.earningsTrend?.trend?.find((t) => t.period === "0q");
1555
- const earningsEstimate = currentQtr?.earningsEstimate;
1556
- const revenueEstimate = currentQtr?.revenueEstimate;
1557
- const epsTrend = currentQtr?.epsTrend;
1558
- const epsRevisions = currentQtr?.epsRevisions;
1559
- const name = mod.quoteType?.shortName || mod.quoteType?.longName || symbol;
1560
-
1561
- return {
1562
- symbol,
1563
- name,
1564
- earningsDate,
1565
- earningsCallDate: yahooRawDateTime(cal.earningsCallDate?.[0]),
1566
- isDateEstimate: cal.isEarningsDateEstimate ?? null,
1567
- epsEstimate: financeRawNumberOrNull(earningsEstimate?.avg ?? cal.earningsAverage),
1568
- epsLow: financeRawNumberOrNull(earningsEstimate?.low ?? cal.earningsLow),
1569
- epsHigh: financeRawNumberOrNull(earningsEstimate?.high ?? cal.earningsHigh),
1570
- epsYearAgo: financeRawNumberOrNull(earningsEstimate?.yearAgoEps),
1571
- epsGrowth: financeRawNumberOrNull(earningsEstimate?.growth),
1572
- epsAnalysts: financeRawNumberOrNull(earningsEstimate?.numberOfAnalysts),
1573
- epsTrend7dAgo: financeRawNumberOrNull(epsTrend?.["7daysAgo"]),
1574
- epsTrend30dAgo: financeRawNumberOrNull(epsTrend?.["30daysAgo"]),
1575
- epsRevisionUp7d: financeRawNumberOrNull(epsRevisions?.upLast7days),
1576
- epsRevisionUp30d: financeRawNumberOrNull(epsRevisions?.upLast30days),
1577
- epsRevisionDown7d: financeRawNumberOrNull(epsRevisions?.downLast7Days),
1578
- epsRevisionDown30d: financeRawNumberOrNull(epsRevisions?.downLast30days),
1579
- epsActual: null,
1580
- revenueEstimate: financeRawNumberOrNull(revenueEstimate?.avg ?? cal.revenueAverage),
1581
- revenueLow: financeRawNumberOrNull(revenueEstimate?.low ?? cal.revenueLow),
1582
- revenueHigh: financeRawNumberOrNull(revenueEstimate?.high ?? cal.revenueHigh),
1583
- revenueYearAgo: financeRawNumberOrNull(revenueEstimate?.yearAgoRevenue),
1584
- revenueGrowth: financeRawNumberOrNull(revenueEstimate?.growth),
1585
- revenueAnalysts: financeRawNumberOrNull(revenueEstimate?.numberOfAnalysts),
1586
- revenueActual: null,
1587
- surprise: null,
1588
- timing: inferEarningsTiming(earningsDate),
1589
- };
1590
- }),
1591
- );
1592
-
1593
- for (const result of settled) {
1594
- if (result.status === "fulfilled" && result.value) {
1595
- results.push(result.value);
1596
- }
1597
- }
1598
- }
1599
-
1600
- results.sort((a, b) => a.earningsDate.getTime() - b.earningsDate.getTime());
1601
- return results;
308
+ return loadYahooEarningsCalendar(symbols, (url) => this.http.fetchJsonWithCrumb(url));
1602
309
  }
1603
310
  }