gloomberb 0.7.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1293) 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 +110 -1461
  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 +1 -46
  606. package/src/plugins/builtin/holders/index.tsx +3 -955
  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 +110 -4
  817. package/src/plugins/pane-manager.ts +57 -1035
  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 +191 -947
  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 +9 -2
  888. package/src/renderers/electrobun/view/app-services.ts +4 -375
  889. package/src/renderers/electrobun/view/backend-rpc.ts +5 -0
  890. package/src/renderers/electrobun/view/build-assets.ts +117 -0
  891. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +398 -0
  892. package/src/renderers/electrobun/view/config-host.ts +1 -1
  893. package/src/renderers/electrobun/view/context-menu.test.ts +10 -4
  894. package/src/renderers/electrobun/view/context-menu.ts +1 -1
  895. package/src/renderers/electrobun/view/data-table/dom.ts +211 -0
  896. package/src/renderers/electrobun/view/data-table/index.tsx +358 -0
  897. package/src/renderers/electrobun/view/data-table/row.tsx +335 -0
  898. package/src/renderers/electrobun/view/desktop/control-styles.ts +77 -0
  899. package/src/renderers/electrobun/view/desktop/controls.tsx +421 -0
  900. package/src/renderers/electrobun/view/desktop/list-view.tsx +200 -0
  901. package/src/renderers/electrobun/view/desktop/window/bridge.ts +70 -0
  902. package/src/renderers/electrobun/view/desktop/window/snapshot.test.ts +57 -0
  903. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +41 -0
  904. package/src/renderers/electrobun/view/fatal-screen.tsx +118 -0
  905. package/src/renderers/electrobun/view/host/box.tsx +160 -0
  906. package/src/renderers/electrobun/view/host/chart-surface.tsx +142 -0
  907. package/src/renderers/electrobun/view/host/input.tsx +294 -0
  908. package/src/renderers/electrobun/view/host/mouse.ts +139 -0
  909. package/src/renderers/electrobun/view/host/native.ts +49 -0
  910. package/src/renderers/electrobun/view/host/scroll-box.tsx +276 -0
  911. package/src/renderers/electrobun/view/host/style.ts +124 -0
  912. package/src/renderers/electrobun/view/host/tabs.tsx +255 -0
  913. package/src/renderers/electrobun/view/host/text.tsx +121 -0
  914. package/src/renderers/electrobun/view/http-fetch.ts +1 -1
  915. package/src/renderers/electrobun/view/input-host.tsx +12 -1
  916. package/src/renderers/electrobun/view/main.tsx +45 -26
  917. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +59 -0
  918. package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +9 -0
  919. package/src/renderers/electrobun/view/remote/asset-data-client.ts +201 -0
  920. package/src/renderers/electrobun/view/remote/persistence.ts +158 -0
  921. package/src/renderers/electrobun/view/remote/ticker-repository.ts +26 -0
  922. package/src/renderers/electrobun/view/styles.css +91 -4
  923. package/src/renderers/electrobun/view/ui-host.tsx +22 -1469
  924. package/src/renderers/opentui/chart-surface.tsx +4 -5
  925. package/src/renderers/opentui/host.tsx +30 -4
  926. package/src/renderers/opentui/image/loader.test.ts +25 -0
  927. package/src/renderers/opentui/image/loader.ts +105 -0
  928. package/src/renderers/opentui/image/surface.tsx +266 -0
  929. package/src/renderers/opentui/input-host.tsx +12 -1
  930. package/src/renderers/opentui/start.tsx +2 -2
  931. package/src/renderers/opentui/test-utils.tsx +2 -2
  932. package/src/renderers/opentui/ui-host.tsx +1 -1
  933. package/src/sources/gloomberb-cloud/index.test.ts +660 -0
  934. package/src/sources/gloomberb-cloud/index.ts +444 -0
  935. package/src/sources/gloomberb-cloud/news.ts +127 -0
  936. package/src/sources/gloomberb-cloud/normalizers.ts +354 -0
  937. package/src/sources/provider-errors.ts +8 -2
  938. package/src/sources/provider-router/batches.ts +170 -0
  939. package/src/sources/provider-router/brokers.ts +114 -0
  940. package/src/sources/provider-router/cache.ts +171 -0
  941. package/src/sources/provider-router/cached-financials.test.ts +498 -0
  942. package/src/sources/provider-router/document-routes.ts +153 -0
  943. package/src/sources/provider-router/financial-routes.ts +225 -0
  944. package/src/sources/provider-router/financials.ts +259 -0
  945. package/src/sources/provider-router/history.test.ts +383 -0
  946. package/src/sources/provider-router/history.ts +448 -0
  947. package/src/sources/provider-router/index.test.ts +1438 -0
  948. package/src/sources/provider-router/index.ts +389 -0
  949. package/src/sources/provider-router/news.ts +36 -0
  950. package/src/sources/provider-router/primary.ts +164 -0
  951. package/src/sources/provider-router/route-types.ts +34 -0
  952. package/src/sources/provider-router/routing.ts +58 -0
  953. package/src/sources/provider-router/search.ts +183 -0
  954. package/src/sources/provider-router/sources.ts +55 -0
  955. package/src/sources/provider-router/streaming.ts +92 -0
  956. package/src/sources/provider-router/supplemental.ts +373 -0
  957. package/src/sources/provider-router/test-support.ts +136 -0
  958. package/src/sources/sec-edgar/content.ts +211 -0
  959. package/src/sources/sec-edgar.ts +9 -217
  960. package/src/sources/yahoo-finance/financials.ts +339 -0
  961. package/src/sources/yahoo-finance/history.ts +106 -0
  962. package/src/sources/yahoo-finance/http.ts +100 -0
  963. package/src/sources/yahoo-finance/mappers.ts +409 -0
  964. package/src/sources/yahoo-finance/options.ts +134 -0
  965. package/src/sources/yahoo-finance/quote-summary.ts +210 -0
  966. package/src/sources/yahoo-finance/requests.ts +192 -0
  967. package/src/sources/yahoo-finance/snapshots.ts +236 -0
  968. package/src/sources/yahoo-finance/symbols.ts +102 -0
  969. package/src/sources/yahoo-finance/types.ts +167 -0
  970. package/src/sources/yahoo-finance.test.ts +116 -12
  971. package/src/sources/yahoo-finance.ts +115 -1408
  972. package/src/state/app/activity.test.ts +41 -0
  973. package/src/state/app/activity.ts +106 -0
  974. package/src/state/app/bootstrap.test.ts +498 -0
  975. package/src/state/app/bootstrap.ts +412 -0
  976. package/src/state/app/context/index.test.ts +564 -0
  977. package/src/state/app/context/index.test.tsx +193 -0
  978. package/src/state/app/context/index.tsx +476 -0
  979. package/src/state/app/context/selector.test.tsx +291 -0
  980. package/src/state/config-save-scheduler.ts +2 -6
  981. package/src/state/hooks/inline-tickers.test.tsx +123 -0
  982. package/src/state/hooks/inline-tickers.ts +212 -0
  983. package/src/state/hooks/quote-streaming.test.ts +31 -0
  984. package/src/state/hooks/quote-streaming.test.tsx +70 -0
  985. package/src/state/hooks/quote-streaming.ts +92 -0
  986. package/src/state/hooks/session-snapshot.ts +99 -0
  987. package/src/state/pane-scroll-registry.tsx +1 -1
  988. package/src/state/selectors-ui.ts +1 -1
  989. package/src/state/selectors.ts +1 -1
  990. package/src/test-support/plugin-persistence.ts +112 -0
  991. package/src/test-support/plugin-runtime.ts +2 -1
  992. package/src/theme/colors.ts +23 -9
  993. package/src/theme/themes.test.ts +15 -0
  994. package/src/tickers/list.test.ts +16 -0
  995. package/src/tickers/open-target.ts +77 -0
  996. package/src/tickers/search/index.test.ts +252 -0
  997. package/src/tickers/search/index.ts +367 -0
  998. package/src/tickers/search/ranking.ts +387 -0
  999. package/src/tickers/search/result.ts +27 -0
  1000. package/src/tickers/search/types.ts +41 -0
  1001. package/src/tickers/search/upsert.ts +91 -0
  1002. package/src/tickers/tokenizer.test.ts +31 -0
  1003. package/src/tickers/tokenizer.ts +37 -0
  1004. package/src/types/broker.ts +12 -4
  1005. package/src/types/config.ts +42 -23
  1006. package/src/types/context-menu.ts +1 -1
  1007. package/src/types/data-provider.ts +2 -2
  1008. package/src/types/desktop-window.ts +1 -1
  1009. package/src/types/financials.ts +109 -4
  1010. package/src/types/news-source.ts +0 -1
  1011. package/src/types/persistence.ts +1 -1
  1012. package/src/types/plugin.ts +37 -21
  1013. package/src/types/ticker.ts +1 -0
  1014. package/src/types/trading.ts +23 -1
  1015. package/src/ui/context-menu.tsx +6 -5
  1016. package/src/ui/dialog.tsx +6 -3
  1017. package/src/ui/host.tsx +10 -10
  1018. package/src/ui/index.tsx +14 -33
  1019. package/src/updater/github-releases.test.ts +41 -0
  1020. package/src/updater/github-releases.ts +73 -0
  1021. package/src/updater.test.ts +21 -0
  1022. package/src/updater.ts +10 -3
  1023. package/src/utils/back-navigation.ts +2 -2
  1024. package/src/utils/cli-output.ts +4 -4
  1025. package/src/utils/currency-units.test.ts +23 -1
  1026. package/src/utils/currency-units.ts +56 -0
  1027. package/src/utils/datetime-format.ts +40 -0
  1028. package/src/utils/dom-screenshot.ts +2 -2
  1029. package/src/utils/double-escape-close.ts +1 -1
  1030. package/src/utils/exchanges.ts +2 -2
  1031. package/src/utils/format.ts +0 -7
  1032. package/src/utils/fuzzy-search.ts +1 -1
  1033. package/src/utils/inline-content-tokenizer.test.ts +11 -2
  1034. package/src/utils/inline-content-tokenizer.ts +33 -12
  1035. package/src/utils/keyboard.ts +1 -1
  1036. package/src/utils/link-tokenizer.ts +2 -2
  1037. package/src/utils/options.ts +1 -1
  1038. package/src/utils/price-history.test.ts +20 -0
  1039. package/src/utils/price-history.ts +24 -2
  1040. package/src/utils/relative-time.ts +8 -0
  1041. package/src/utils/selection-clipboard.ts +1 -1
  1042. package/src/utils/shortcut-labels.ts +2 -2
  1043. package/src/utils/sort-values.ts +17 -0
  1044. package/src/utils/terminal-input-reset.ts +1 -1
  1045. package/src/utils/text-wrap.ts +20 -0
  1046. package/src/version.ts +1 -1
  1047. package/src/components/chart/chart-controller.test.ts +0 -110
  1048. package/src/components/chart/chart-controller.ts +0 -650
  1049. package/src/components/chart/chart-data.ts +0 -350
  1050. package/src/components/chart/chart-indicator-selector.tsx +0 -75
  1051. package/src/components/chart/chart-pane-settings.ts +0 -37
  1052. package/src/components/chart/chart-renderer.ts +0 -1562
  1053. package/src/components/chart/chart-resolution.test.ts +0 -87
  1054. package/src/components/chart/chart-resolution.ts +0 -405
  1055. package/src/components/chart/chart-scroll.test.ts +0 -70
  1056. package/src/components/chart/chart-scroll.ts +0 -104
  1057. package/src/components/chart/chart-types.ts +0 -119
  1058. package/src/components/chart/chart-viewport.test.ts +0 -29
  1059. package/src/components/chart/chart-viewport.ts +0 -50
  1060. package/src/components/chart/chart.test.ts +0 -922
  1061. package/src/components/chart/comparison-chart-data.test.ts +0 -105
  1062. package/src/components/chart/comparison-chart-data.ts +0 -292
  1063. package/src/components/chart/comparison-chart-renderer.test.ts +0 -187
  1064. package/src/components/chart/comparison-chart-renderer.ts +0 -437
  1065. package/src/components/chart/native/kitty-adapter.ts +0 -9
  1066. package/src/components/chart/native/kitty-manager.ts +0 -142
  1067. package/src/components/chart/native/kitty-support.ts +0 -71
  1068. package/src/components/chart/native/kitty.test.ts +0 -728
  1069. package/src/components/chart/native/surface-manager.ts +0 -225
  1070. package/src/components/chart/native/surface-sync.ts +0 -34
  1071. package/src/components/chart/native/surface-visibility.ts +0 -48
  1072. package/src/components/chart/static-chart-surface.test.tsx +0 -51
  1073. package/src/components/chart/static-chart-surface.tsx +0 -217
  1074. package/src/components/chart/stock-chart-renderer-switch.test.tsx +0 -1323
  1075. package/src/components/chart/stock-chart.test.ts +0 -330
  1076. package/src/components/chart/stock-chart.tsx +0 -4148
  1077. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +0 -109
  1078. package/src/components/command-bar/command-bar.test.tsx +0 -3244
  1079. package/src/components/command-bar/command-bar.tsx +0 -5868
  1080. package/src/components/command-bar/command-registry.ts +0 -267
  1081. package/src/components/command-bar/confirm-body.tsx +0 -51
  1082. package/src/components/command-bar/list-model.ts +0 -80
  1083. package/src/components/command-bar/pane-template-display.ts +0 -8
  1084. package/src/components/command-bar/root-shortcuts.ts +0 -188
  1085. package/src/components/command-bar/workflow-ops.test.ts +0 -144
  1086. package/src/components/command-bar/workflow-ops.ts +0 -459
  1087. package/src/components/command-bar/workflow-types.ts +0 -127
  1088. package/src/components/data-table-stack-view.test.tsx +0 -241
  1089. package/src/components/data-table-stack-view.tsx +0 -59
  1090. package/src/components/data-table-view.test.tsx +0 -211
  1091. package/src/components/data-table-view.tsx +0 -244
  1092. package/src/components/feed-data-table-stack-view.test.tsx +0 -225
  1093. package/src/components/feed-data-table-stack-view.tsx +0 -359
  1094. package/src/components/layout/pane-content.test.tsx +0 -64
  1095. package/src/components/layout/pane-content.tsx +0 -44
  1096. package/src/components/layout/pane-cycle-order.test.ts +0 -30
  1097. package/src/components/layout/pane-cycle-order.ts +0 -32
  1098. package/src/components/layout/pane-footer.test.tsx +0 -195
  1099. package/src/components/layout/pane-footer.tsx +0 -432
  1100. package/src/components/layout/pane-header.test.tsx +0 -39
  1101. package/src/components/layout/pane-header.tsx +0 -248
  1102. package/src/components/layout/pane-keyboard-scroll.test.tsx +0 -226
  1103. package/src/components/layout/pane-sizing.ts +0 -34
  1104. package/src/components/layout/pane-title.ts +0 -38
  1105. package/src/components/layout/pane.tsx +0 -129
  1106. package/src/components/layout/shell.test.tsx +0 -1427
  1107. package/src/components/layout/shell.tsx +0 -1912
  1108. package/src/components/price-sparkline-view.tsx +0 -463
  1109. package/src/components/price-sparkline.ts +0 -46
  1110. package/src/components/speedometer-gauge.test.tsx +0 -45
  1111. package/src/components/speedometer-gauge.tsx +0 -562
  1112. package/src/components/ticker-badge-list.tsx +0 -53
  1113. package/src/components/ticker-badge-text.test.tsx +0 -253
  1114. package/src/components/ticker-badge-text.tsx +0 -113
  1115. package/src/components/ticker-badge.tsx +0 -109
  1116. package/src/components/ticker-list-table-view.tsx +0 -233
  1117. package/src/components/ticker-list-table.test.tsx +0 -210
  1118. package/src/components/ticker-list-table.tsx +0 -458
  1119. package/src/components/ui/data-table.tsx +0 -522
  1120. package/src/components/ui/multi-select-dialog.tsx +0 -336
  1121. package/src/core/state/app-state.ts +0 -1009
  1122. package/src/data/config-layout.ts +0 -225
  1123. package/src/data/config-store-node.ts +0 -478
  1124. package/src/data/config-store.test.ts +0 -481
  1125. package/src/data/config-store.ts +0 -64
  1126. package/src/data/sqlite-cache.test.ts +0 -151
  1127. package/src/data/sqlite-database.ts +0 -68
  1128. package/src/data/sqlite-json.ts +0 -16
  1129. package/src/data/sqlite-retry.test.ts +0 -63
  1130. package/src/market-data/coordinator-subscriptions.test.ts +0 -209
  1131. package/src/market-data/coordinator.test.ts +0 -830
  1132. package/src/market-data/coordinator.ts +0 -1155
  1133. package/src/plugins/builtin/ai/screener-contract.test.ts +0 -117
  1134. package/src/plugins/builtin/ai/screener-contract.ts +0 -174
  1135. package/src/plugins/builtin/ai/screener-pane.test.tsx +0 -348
  1136. package/src/plugins/builtin/ai/screener-pane.tsx +0 -1118
  1137. package/src/plugins/builtin/ai/utils.ts +0 -1
  1138. package/src/plugins/builtin/ask-ai.test.tsx +0 -348
  1139. package/src/plugins/builtin/chat-controller.test.ts +0 -1376
  1140. package/src/plugins/builtin/chat-controller.ts +0 -1248
  1141. package/src/plugins/builtin/chat.test.tsx +0 -2245
  1142. package/src/plugins/builtin/chat.tsx +0 -2267
  1143. package/src/plugins/builtin/cloud-auth-actions.tsx +0 -72
  1144. package/src/plugins/builtin/cloud-tweets.tsx +0 -896
  1145. package/src/plugins/builtin/comparison-chart.test.tsx +0 -532
  1146. package/src/plugins/builtin/comparison-chart.tsx +0 -194
  1147. package/src/plugins/builtin/debug.tsx +0 -365
  1148. package/src/plugins/builtin/earnings/earnings-cache.test.ts +0 -181
  1149. package/src/plugins/builtin/earnings/earnings-cache.ts +0 -145
  1150. package/src/plugins/builtin/earnings/earnings-data.test.ts +0 -90
  1151. package/src/plugins/builtin/earnings/earnings-data.ts +0 -115
  1152. package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +0 -72
  1153. package/src/plugins/builtin/fear-greed/fear-greed-data.ts +0 -366
  1154. package/src/plugins/builtin/help.test.tsx +0 -166
  1155. package/src/plugins/builtin/help.tsx +0 -450
  1156. package/src/plugins/builtin/layout-manager.tsx +0 -247
  1157. package/src/plugins/builtin/manual-entry.tsx +0 -25
  1158. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +0 -98
  1159. package/src/plugins/builtin/news-wire/breaking-notifications.ts +0 -110
  1160. package/src/plugins/builtin/news-wire/breaking-pane.test.tsx +0 -156
  1161. package/src/plugins/builtin/news-wire/breaking-pane.tsx +0 -68
  1162. package/src/plugins/builtin/news-wire/categories.ts +0 -114
  1163. package/src/plugins/builtin/news-wire/default-feeds.ts +0 -3
  1164. package/src/plugins/builtin/news-wire/feed-config.test.ts +0 -90
  1165. package/src/plugins/builtin/news-wire/feed-config.ts +0 -216
  1166. package/src/plugins/builtin/news-wire/index.tsx +0 -129
  1167. package/src/plugins/builtin/news-wire/industry-pane.tsx +0 -126
  1168. package/src/plugins/builtin/news-wire/news-detail-view.tsx +0 -299
  1169. package/src/plugins/builtin/news-wire/news-footer.ts +0 -28
  1170. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +0 -98
  1171. package/src/plugins/builtin/news-wire/news-query-presets.ts +0 -40
  1172. package/src/plugins/builtin/news-wire/news-table.test.tsx +0 -206
  1173. package/src/plugins/builtin/news-wire/news-table.tsx +0 -296
  1174. package/src/plugins/builtin/news-wire/persisted-articles.ts +0 -98
  1175. package/src/plugins/builtin/news-wire/read-state.ts +0 -76
  1176. package/src/plugins/builtin/news-wire/rss-parser.test.ts +0 -184
  1177. package/src/plugins/builtin/news-wire/rss-parser.ts +0 -213
  1178. package/src/plugins/builtin/news-wire/rss-source.test.ts +0 -159
  1179. package/src/plugins/builtin/news-wire/rss-source.ts +0 -181
  1180. package/src/plugins/builtin/news.tsx +0 -194
  1181. package/src/plugins/builtin/notes.tsx +0 -561
  1182. package/src/plugins/builtin/options.test.tsx +0 -241
  1183. package/src/plugins/builtin/options.tsx +0 -542
  1184. package/src/plugins/builtin/portfolio-list/cli/commands.ts +0 -657
  1185. package/src/plugins/builtin/portfolio-list/pane.tsx +0 -724
  1186. package/src/plugins/builtin/portfolio-list/quick-add.tsx +0 -422
  1187. package/src/plugins/builtin/portfolio-list/summary.tsx +0 -275
  1188. package/src/plugins/builtin/portfolio-list.test.ts +0 -133
  1189. package/src/plugins/builtin/portfolio-list.test.tsx +0 -1535
  1190. package/src/plugins/builtin/portfolio-metrics.test.ts +0 -236
  1191. package/src/plugins/builtin/quote-monitor.test.tsx +0 -222
  1192. package/src/plugins/builtin/sec-filing-display.tsx +0 -84
  1193. package/src/plugins/builtin/sec.tsx +0 -342
  1194. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +0 -461
  1195. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +0 -479
  1196. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +0 -269
  1197. package/src/plugins/builtin/ticker-detail.test.tsx +0 -888
  1198. package/src/plugins/builtin/ticker-surface.ts +0 -68
  1199. package/src/plugins/builtin/yahoo.ts +0 -72
  1200. package/src/plugins/catalog-ui.test.ts +0 -24
  1201. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +0 -35
  1202. package/src/plugins/ibkr/flex.test.ts +0 -162
  1203. package/src/plugins/ibkr/flex.ts +0 -371
  1204. package/src/plugins/ibkr/gateway-helpers.ts +0 -33
  1205. package/src/plugins/ibkr/gateway-selection.ts +0 -51
  1206. package/src/plugins/ibkr/gateway-service-native.ts +0 -2377
  1207. package/src/plugins/ibkr/gateway-service.test.ts +0 -584
  1208. package/src/plugins/ibkr/gateway-service.ts +0 -433
  1209. package/src/plugins/ibkr/gateway-types.ts +0 -27
  1210. package/src/plugins/ibkr/price-normalization.test.ts +0 -33
  1211. package/src/plugins/ibkr/price-normalization.ts +0 -60
  1212. package/src/plugins/ibkr/trade-badge.tsx +0 -28
  1213. package/src/plugins/ibkr/trade-tab.test.tsx +0 -276
  1214. package/src/plugins/ibkr/trade-tab.tsx +0 -1117
  1215. package/src/plugins/ibkr/trade-utils.test.ts +0 -175
  1216. package/src/plugins/ibkr/trade-utils.ts +0 -150
  1217. package/src/plugins/ibkr/trading-pane.tsx +0 -474
  1218. package/src/plugins/ibkr/trading-state.test.ts +0 -109
  1219. package/src/plugins/ibkr/trading-state.ts +0 -339
  1220. package/src/plugins/plugin-runtime.test.tsx +0 -323
  1221. package/src/plugins/plugin-runtime.tsx +0 -433
  1222. package/src/plugins/prediction-markets/controller-data.ts +0 -785
  1223. package/src/plugins/prediction-markets/controller.ts +0 -659
  1224. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +0 -572
  1225. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +0 -696
  1226. package/src/plugins/prediction-markets/services/polymarket-ws.ts +0 -128
  1227. package/src/plugins/registry.test.ts +0 -362
  1228. package/src/plugins/registry.ts +0 -854
  1229. package/src/renderers/electrobun/bun/application-menu-click.test.ts +0 -50
  1230. package/src/renderers/electrobun/bun/application-menu-click.ts +0 -58
  1231. package/src/renderers/electrobun/bun/application-menu.ts +0 -123
  1232. package/src/renderers/electrobun/bun/context-menu-click.test.ts +0 -67
  1233. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +0 -52
  1234. package/src/renderers/electrobun/bun/context-menu-normalize.ts +0 -67
  1235. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +0 -63
  1236. package/src/renderers/electrobun/bun/desktop-workspace.ts +0 -180
  1237. package/src/renderers/electrobun/bun/window-focus.ts +0 -37
  1238. package/src/renderers/electrobun/bun/window-frame.test.ts +0 -40
  1239. package/src/renderers/electrobun/view/data-table.tsx +0 -810
  1240. package/src/renderers/electrobun/view/desktop-controls.tsx +0 -673
  1241. package/src/renderers/electrobun/view/desktop-window-bridge.ts +0 -70
  1242. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +0 -57
  1243. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +0 -41
  1244. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +0 -9
  1245. package/src/renderers/opentui/image-loader.test.ts +0 -25
  1246. package/src/renderers/opentui/image-loader.ts +0 -105
  1247. package/src/renderers/opentui/image-surface.tsx +0 -266
  1248. package/src/sources/gloomberb-cloud.test.ts +0 -637
  1249. package/src/sources/gloomberb-cloud.ts +0 -828
  1250. package/src/sources/provider-router.test.ts +0 -2600
  1251. package/src/sources/provider-router.ts +0 -2231
  1252. package/src/state/app-activity.test.ts +0 -41
  1253. package/src/state/app-activity.ts +0 -106
  1254. package/src/state/app-bootstrap.test.ts +0 -498
  1255. package/src/state/app-bootstrap.ts +0 -411
  1256. package/src/state/app-context-selector.test.tsx +0 -369
  1257. package/src/state/app-context.test.ts +0 -670
  1258. package/src/state/app-context.test.tsx +0 -193
  1259. package/src/state/app-context.tsx +0 -485
  1260. package/src/state/use-inline-tickers.test.tsx +0 -73
  1261. package/src/state/use-inline-tickers.ts +0 -188
  1262. package/src/state/use-quote-streaming.test.ts +0 -31
  1263. package/src/state/use-quote-streaming.test.tsx +0 -70
  1264. package/src/state/use-quote-streaming.ts +0 -92
  1265. package/src/state/use-session-snapshot.ts +0 -99
  1266. package/src/utils/api-client.test.ts +0 -603
  1267. package/src/utils/api-client.ts +0 -1379
  1268. package/src/utils/market-format.test.ts +0 -109
  1269. package/src/utils/market-format.ts +0 -299
  1270. package/src/utils/market-status.ts +0 -141
  1271. package/src/utils/quote-freshness.test.ts +0 -150
  1272. package/src/utils/quote-freshness.ts +0 -145
  1273. package/src/utils/quote-resolution.test.ts +0 -277
  1274. package/src/utils/quote-resolution.ts +0 -642
  1275. package/src/utils/quote-time.test.ts +0 -34
  1276. package/src/utils/quote-time.ts +0 -59
  1277. package/src/utils/ticker-list.test.ts +0 -16
  1278. package/src/utils/ticker-search.test.ts +0 -227
  1279. package/src/utils/ticker-search.ts +0 -855
  1280. package/src/utils/ticker-tokenizer.test.ts +0 -31
  1281. package/src/utils/ticker-tokenizer.ts +0 -37
  1282. /package/src/components/chart/native/{kitty-protocol.ts → kitty/protocol.ts} +0 -0
  1283. /package/src/components/ui/{multi-select.ts → multi-select/index.ts} +0 -0
  1284. /package/src/data/{sqlite-retry.ts → sqlite/retry.ts} +0 -0
  1285. /package/src/plugins/builtin/{news-wire → news/wire}/hash.ts +0 -0
  1286. /package/src/plugins/builtin/{news-wire → news/wire}/read-state.test.ts +0 -0
  1287. /package/src/plugins/builtin/{notes-files.ts → notes/files.ts} +0 -0
  1288. /package/src/renderers/electrobun/bun/{context-menu-click.ts → context-menu/click.ts} +0 -0
  1289. /package/src/renderers/electrobun/bun/{window-frame.ts → window/frame.ts} +0 -0
  1290. /package/src/renderers/electrobun/view/native-stubs/{chart-surface-manager.ts → chart/surface-manager.ts} +0 -0
  1291. /package/src/renderers/electrobun/view/native-stubs/{chart-surface-sync.ts → chart/surface-sync.ts} +0 -0
  1292. /package/src/{utils/ticker-list.ts → tickers/list.ts} +0 -0
  1293. /package/src/{utils/ticker-metadata.ts → tickers/metadata.ts} +0 -0
@@ -1,2231 +0,0 @@
1
- import type { CachedResourceRecord, ResourceStore } from "../data/resource-store";
2
- import type { PluginRegistry } from "../plugins/registry";
3
- import type { AssetDataCapability, NewsCapability } from "../capabilities";
4
- import type { BrokerAdapter } from "../types/broker";
5
- import type { AppConfig } from "../types/config";
6
- import { createDefaultConfig } from "../types/config";
7
- import type {
8
- CachedFinancialsTarget,
9
- DataProvider,
10
- MarketDataRequestContext,
11
- QuoteBatchResult,
12
- QuoteSubscriptionTarget,
13
- SearchRequestContext,
14
- SecFilingItem,
15
- TickerFinancialsBatchResult,
16
- } from "../types/data-provider";
17
- import type { CapabilityRouteSource } from "../types/capability-route-source";
18
- import { routeSourcePriority } from "../types/capability-route-source";
19
- import type { AnalystResearchData, CorporateActionsData, HolderData, OptionsChain, PricePoint, Quote, TickerFinancials } from "../types/financials";
20
- import type { NewsArticle, NewsQuery } from "../news/types";
21
- import type { BrokerContractRef, InstrumentSearchResult } from "../types/instrument";
22
- import type { CachePolicy, CachePolicyMap } from "../types/persistence";
23
- import type { TimeRange } from "../components/chart/chart-types";
24
- import {
25
- isIntradayResolution,
26
- normalizeChartResolutionSupport,
27
- type ChartResolutionSupport,
28
- type ManualChartResolution,
29
- } from "../components/chart/chart-resolution";
30
- import { hasLikelyQuoteUnitMismatch } from "../utils/currency-units";
31
- import { debugLog } from "../utils/debug-log";
32
- import { canonicalExchange } from "../utils/exchanges";
33
- import { isPriceHistoryStaleForCurrentWindow, normalizePriceHistory, normalizeTickerFinancialsPriceHistory } from "../utils/price-history";
34
- import { parseOptionSymbol } from "../utils/options";
35
- import { isQuoteStaleForCurrentSession } from "../utils/quote-freshness";
36
- import {
37
- mergeQuoteContributionMaps,
38
- resolveCanonicalQuote,
39
- resolveTickerFinancialsQuoteState,
40
- seedQuoteContributions,
41
- } from "../utils/quote-resolution";
42
- import { isProviderMiss } from "./provider-errors";
43
-
44
- const providerLog = debugLog.createLogger("asset-data-router");
45
-
46
- const BROKER_ATTEMPT_TIMEOUT = 10_000;
47
- const EXPECTED_PROVIDER_MISS = /No data found|symbol may be delisted|"code":"Not Found"|No history for /i;
48
- const MARKET_NAMESPACE = "market";
49
- const SEARCH_CACHE_TTL_MS = 30_000;
50
- const SEARCH_PROVIDER_TIMEOUT_MS = 5_000;
51
-
52
- const DEFAULT_CACHE_POLICIES: Record<string, CachePolicy> = {
53
- brokerQuote: { staleMs: 15_000, expireMs: 15 * 60_000 },
54
- quote: { staleMs: 5 * 60_000, expireMs: 24 * 60 * 60_000 },
55
- financials: { staleMs: 60 * 60_000, expireMs: 7 * 24 * 60 * 60_000 },
56
- priceHistoryIntraday: { staleMs: 5 * 60_000, expireMs: 2 * 24 * 60 * 60_000 },
57
- priceHistoryDaily: { staleMs: 24 * 60 * 60_000, expireMs: 30 * 24 * 60 * 60_000 },
58
- news: { staleMs: 15 * 60_000, expireMs: 2 * 24 * 60 * 60_000 },
59
- holders: { staleMs: 24 * 60 * 60_000, expireMs: 7 * 24 * 60 * 60_000 },
60
- analystResearch: { staleMs: 24 * 60 * 60_000, expireMs: 7 * 24 * 60 * 60_000 },
61
- corporateActions: { staleMs: 24 * 60 * 60_000, expireMs: 14 * 24 * 60 * 60_000 },
62
- secFilings: { staleMs: 15 * 60_000, expireMs: 2 * 24 * 60 * 60_000 },
63
- secFilingContent: { staleMs: 30 * 24 * 60 * 60_000, expireMs: 365 * 24 * 60 * 60_000 },
64
- articleSummary: { staleMs: 30 * 24 * 60 * 60_000, expireMs: 90 * 24 * 60 * 60_000 },
65
- optionsChain: { staleMs: 5 * 60_000, expireMs: 2 * 24 * 60 * 60_000 },
66
- exchangeRate: { staleMs: 60 * 60_000, expireMs: 7 * 24 * 60 * 60_000 },
67
- };
68
-
69
- function withBrokerTimeout<T>(promise: Promise<T>): Promise<T | null> {
70
- return new Promise<T | null>((resolve) => {
71
- const timer = setTimeout(() => resolve(null), BROKER_ATTEMPT_TIMEOUT);
72
- promise.then(
73
- (value) => { clearTimeout(timer); resolve(value); },
74
- () => { clearTimeout(timer); resolve(null); },
75
- );
76
- });
77
- }
78
-
79
- function withSearchTimeout<T>(promise: Promise<T>): Promise<T | null> {
80
- return new Promise<T | null>((resolve) => {
81
- const timer = setTimeout(() => resolve(null), SEARCH_PROVIDER_TIMEOUT_MS);
82
- promise.then(
83
- (value) => { clearTimeout(timer); resolve(value); },
84
- () => { clearTimeout(timer); resolve(null); },
85
- );
86
- });
87
- }
88
-
89
- function shouldLogProviderError(error: unknown): boolean {
90
- if (isProviderMiss(error)) return false;
91
- const message = error instanceof Error ? error.message : String(error);
92
- return !EXPECTED_PROVIDER_MISS.test(message);
93
- }
94
-
95
- function normalizeTicker(ticker: string): string {
96
- return ticker.trim().toUpperCase();
97
- }
98
-
99
- function deriveMarketCapFromShares(
100
- financials: TickerFinancials,
101
- options: { replaceExisting?: boolean } = {},
102
- ): TickerFinancials {
103
- const quote = financials.quote;
104
- const sharesOutstanding = financials.fundamentals?.sharesOutstanding;
105
- if (
106
- !quote
107
- || (quote.marketCap != null && !options.replaceExisting)
108
- || !Number.isFinite(quote.price)
109
- || !Number.isFinite(sharesOutstanding)
110
- || quote.price <= 0
111
- || !sharesOutstanding
112
- || sharesOutstanding <= 0
113
- ) {
114
- return financials;
115
- }
116
-
117
- return {
118
- ...financials,
119
- quote: {
120
- ...quote,
121
- marketCap: quote.price * sharesOutstanding,
122
- },
123
- };
124
- }
125
-
126
- function sanitizeCachedFinancials(
127
- financials: TickerFinancials,
128
- options: { includeStaleQuotes?: boolean } = {},
129
- ): TickerFinancials {
130
- const enriched = deriveMarketCapFromShares(financials);
131
- if (options.includeStaleQuotes || !isQuoteStaleForCurrentSession(enriched.quote)) return enriched;
132
- return {
133
- ...enriched,
134
- quote: undefined,
135
- quoteContributions: undefined,
136
- };
137
- }
138
-
139
- function sanitizeCachedQuote(
140
- quote: Quote,
141
- exchange: string | undefined,
142
- options: { includeStaleQuotes?: boolean } = {},
143
- ): Quote | null {
144
- const normalized = quoteWithFreshnessExchange(quote, exchange);
145
- return options.includeStaleQuotes || !isQuoteStaleForCurrentSession(normalized)
146
- ? normalized
147
- : null;
148
- }
149
-
150
- function quoteWithFreshnessExchange(quote: Quote, exchange?: string): Quote {
151
- if (!exchange || quote.listingExchangeName || quote.exchangeName) return quote;
152
- return {
153
- ...quote,
154
- listingExchangeName: exchange,
155
- exchangeName: exchange,
156
- };
157
- }
158
-
159
- function compactUrl(url: string): string {
160
- return url.trim();
161
- }
162
-
163
- function compactDate(date: Date): string {
164
- return date.toISOString().slice(0, 10);
165
- }
166
-
167
- function normalizeSearchKeyPart(value?: string): string {
168
- return (value ?? "").trim().toUpperCase();
169
- }
170
-
171
- function buildSearchResultKey(item: InstrumentSearchResult): string {
172
- return [
173
- normalizeSearchKeyPart(item.symbol),
174
- normalizeSearchKeyPart(item.exchange),
175
- normalizeSearchKeyPart(item.type),
176
- normalizeSearchKeyPart(item.primaryExchange),
177
- normalizeSearchKeyPart(item.currency),
178
- ].join("|");
179
- }
180
-
181
- function getSearchResultRichness(item: InstrumentSearchResult, context?: SearchRequestContext): number {
182
- let score = 0;
183
- if (item.brokerContract) score += 500;
184
- if (item.brokerInstanceId) score += 250;
185
- if (item.brokerLabel) score += 100;
186
- if (item.name) score += Math.min(80, item.name.length);
187
- if (context?.brokerInstanceId && item.brokerInstanceId === context.brokerInstanceId) score += 800;
188
- if (context?.brokerId && item.brokerContract?.brokerId === context.brokerId) score += 400;
189
- return score;
190
- }
191
-
192
- function buildVariantKey(parts: Array<[string, string | number | undefined | null]>): string {
193
- return parts
194
- .filter(([, value]) => value !== undefined && value !== null && String(value).length > 0)
195
- .map(([key, value]) => `${key}=${String(value)}`)
196
- .join(";");
197
- }
198
-
199
- function isIntradayRange(range: TimeRange): boolean {
200
- return range === "1D" || range === "1W" || range === "1M" || range === "3M";
201
- }
202
-
203
- function isStaleIntradayHistory(points: PricePoint[], enabled: boolean): boolean {
204
- return enabled && isPriceHistoryStaleForCurrentWindow(points);
205
- }
206
-
207
- function isCurrentHistoryWindow(endDate?: Date): boolean {
208
- if (!endDate) return true;
209
- const endMs = endDate.getTime();
210
- return Number.isFinite(endMs) && Date.now() - endMs < 60 * 60_000;
211
- }
212
-
213
- function hasMeaningfulProfile(data: TickerFinancials | null | undefined): boolean {
214
- return !!data && !!(
215
- data.profile?.description
216
- || data.profile?.sector
217
- || data.profile?.industry
218
- );
219
- }
220
-
221
- function hasAnalystResearchValue(data: AnalystResearchData): boolean {
222
- return !!data.priceTarget
223
- || data.recommendations.length > 0
224
- || data.ratings.length > 0
225
- || data.earningsEstimates.length > 0
226
- || data.revenueEstimates.length > 0;
227
- }
228
-
229
- function hasAnalystRatingPriceTargets(data: AnalystResearchData): boolean {
230
- return data.ratings.some((rating) => (
231
- rating.currentPriceTarget != null || rating.priorPriceTarget != null
232
- ));
233
- }
234
-
235
- function isAnalystResearchMissingRatingTargets(data: AnalystResearchData): boolean {
236
- return data.ratings.length > 0 && !hasAnalystRatingPriceTargets(data);
237
- }
238
-
239
- function hasCorporateActionsValue(data: CorporateActionsData): boolean {
240
- return data.dividends.length > 0
241
- || data.splits.length > 0
242
- || data.earnings.length > 0;
243
- }
244
-
245
- function hasLikelyPriceUnitMismatch(primary: TickerFinancials, fallback: TickerFinancials): boolean {
246
- return hasLikelyQuoteUnitMismatch(primary.quote, fallback.quote);
247
- }
248
-
249
- function mergeDefinedObject<T extends object>(preferred: T | null | undefined, fallback: T | null | undefined): T | undefined {
250
- const mergedEntries: Array<[string, unknown]> = [];
251
-
252
- for (const source of [fallback, preferred]) {
253
- if (!source) continue;
254
- for (const [key, value] of Object.entries(source)) {
255
- if (value !== undefined) {
256
- mergedEntries.push([key, value]);
257
- }
258
- }
259
- }
260
-
261
- if (mergedEntries.length === 0) return undefined;
262
- return Object.fromEntries(mergedEntries) as T;
263
- }
264
-
265
- function mergeFinancials(primary: TickerFinancials | null, fallback: TickerFinancials | null): TickerFinancials | null {
266
- if (!primary || !fallback) {
267
- const single = primary ?? fallback;
268
- const resolved = single ? resolveTickerFinancialsQuoteState(normalizeTickerFinancialsPriceHistory(single)) : null;
269
- return resolved ? deriveMarketCapFromShares(resolved) : null;
270
- }
271
-
272
- const preferFallbackPriceData = hasLikelyPriceUnitMismatch(primary, fallback);
273
- const dominant = preferFallbackPriceData ? fallback : primary;
274
- const secondary = preferFallbackPriceData ? primary : fallback;
275
- const quoteContributions = mergeQuoteContributionMaps(
276
- seedQuoteContributions(primary),
277
- seedQuoteContributions(fallback),
278
- );
279
- const resolvedQuote = resolveCanonicalQuote(quoteContributions).quote;
280
-
281
- return deriveMarketCapFromShares({
282
- ...fallback,
283
- ...primary,
284
- quote: resolvedQuote,
285
- quoteContributions,
286
- profile: mergeDefinedObject(primary.profile, fallback.profile),
287
- fundamentals: mergeDefinedObject(primary.fundamentals, fallback.fundamentals),
288
- priceHistory: normalizePriceHistory(dominant.priceHistory.length > 0 ? dominant.priceHistory : secondary.priceHistory),
289
- annualStatements: primary.annualStatements.length > 0 ? primary.annualStatements : fallback.annualStatements,
290
- quarterlyStatements: primary.quarterlyStatements.length > 0 ? primary.quarterlyStatements : fallback.quarterlyStatements,
291
- });
292
- }
293
-
294
- function mergeCachedFinancialRecords(
295
- records: CachedResourceRecord<TickerFinancials>[],
296
- options: { includeStaleQuotes?: boolean } = {},
297
- ): {
298
- value: TickerFinancials | null;
299
- stale: boolean;
300
- } {
301
- const seenSources = new Set<string>();
302
- let merged: TickerFinancials | null = null;
303
- let stale = false;
304
-
305
- for (const record of records) {
306
- if (seenSources.has(record.sourceKey)) continue;
307
- seenSources.add(record.sourceKey);
308
- merged = mergeFinancials(merged, sanitizeCachedFinancials(record.value, options));
309
- stale = stale || record.stale === true;
310
- }
311
-
312
- return { value: merged, stale };
313
- }
314
-
315
- function selectCachedQuoteRecord(
316
- records: CachedResourceRecord<Quote>[],
317
- exchange: string | undefined,
318
- options: { includeStaleQuotes?: boolean } = {},
319
- ): CachedQuoteSelection {
320
- let stale = false;
321
-
322
- for (const record of records) {
323
- stale ||= record.stale === true;
324
- const quote = sanitizeCachedQuote(record.value, exchange, options);
325
- if (quote) return { quote, stale };
326
- }
327
-
328
- return { quote: null, stale };
329
- }
330
-
331
- interface CachedFinancialsSelection {
332
- brokerRecord: CachedResourceRecord<TickerFinancials> | null;
333
- providerValue: TickerFinancials | null;
334
- value: TickerFinancials | null;
335
- stale: boolean;
336
- }
337
-
338
- interface CachedFinancialsReadOptions {
339
- includeStaleQuotes?: boolean;
340
- includeSymbolProviderFallback?: boolean;
341
- }
342
-
343
- interface CachedQuoteSelection {
344
- quote: Quote | null;
345
- stale: boolean;
346
- }
347
-
348
- interface BrokerCandidate {
349
- brokerId: string;
350
- brokerInstanceId: string;
351
- brokerLabel: string;
352
- broker: BrokerAdapter;
353
- instance: AppConfig["brokerInstances"][number];
354
- }
355
-
356
- interface SourceResult<T> {
357
- sourceKey: string;
358
- value: T;
359
- }
360
-
361
- type RouteSourceCapability = AssetDataCapability | NewsCapability;
362
-
363
- function routeSourceFromMarketProvider(provider: DataProvider): CapabilityRouteSource {
364
- return {
365
- id: provider.id,
366
- name: provider.name,
367
- priority: provider.priority,
368
- cachePolicy: provider.cachePolicy,
369
- market: provider,
370
- };
371
- }
372
-
373
- function normalizeRouteSource(source: CapabilityRouteSource | DataProvider): CapabilityRouteSource {
374
- return "market" in source || "news" in source
375
- ? source as CapabilityRouteSource
376
- : routeSourceFromMarketProvider(source as DataProvider);
377
- }
378
-
379
- function capabilityRouteSourceId(capability: RouteSourceCapability): string {
380
- return capability.sourceId ?? capability.id;
381
- }
382
-
383
- function mergeCapabilityRouteSource(current: CapabilityRouteSource | undefined, capability: RouteSourceCapability): CapabilityRouteSource {
384
- const priority = Math.min(
385
- current?.priority ?? Number.MAX_SAFE_INTEGER,
386
- capability.priority ?? 1000,
387
- );
388
- return {
389
- id: capabilityRouteSourceId(capability),
390
- name: current?.name ?? capability.name,
391
- priority,
392
- cachePolicy: capability.cachePolicy ?? current?.cachePolicy,
393
- isEnabled: capability.isEnabled ?? current?.isEnabled,
394
- market: capability.kind === "asset-data" ? capability.provider : current?.market,
395
- news: capability.kind === "news" ? capability.provider : current?.news,
396
- };
397
- }
398
-
399
- export class AssetDataRouter implements DataProvider {
400
- readonly id = "asset-data-router";
401
- readonly name = "Asset Data Router";
402
- readonly priority = Number.MAX_SAFE_INTEGER;
403
-
404
- private registry: PluginRegistry | null = null;
405
- private readonly revalidationInFlight = new Map<string, Promise<unknown>>();
406
- private readonly searchCache = new Map<string, { expiresAt: number; results: InstrumentSearchResult[] }>();
407
- private readonly searchInFlight = new Map<string, Promise<InstrumentSearchResult[]>>();
408
- private getConfigFn: () => AppConfig = () => createDefaultConfig("");
409
- private readonly fallbackSource: CapabilityRouteSource | null;
410
- private readonly extraSources: CapabilityRouteSource[];
411
-
412
- constructor(
413
- fallbackSource: CapabilityRouteSource | DataProvider | null = null,
414
- extraSources: Array<CapabilityRouteSource | DataProvider> = [],
415
- private readonly resources?: ResourceStore,
416
- ) {
417
- this.fallbackSource = fallbackSource ? normalizeRouteSource(fallbackSource) : null;
418
- this.extraSources = extraSources.map(normalizeRouteSource);
419
- }
420
-
421
- attachRegistry(registry: PluginRegistry): void {
422
- this.registry = registry;
423
- }
424
-
425
- setConfigAccessor(getConfig: () => AppConfig): void {
426
- this.getConfigFn = getConfig;
427
- }
428
-
429
- async canProvide(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<boolean> {
430
- const brokerQuote = await withBrokerTimeout(this.fetchBrokerQuote(ticker, exchange, context));
431
- if (brokerQuote) return true;
432
- for (const provider of this.providersInPriorityOrder()) {
433
- try {
434
- if (!provider.canProvide || await provider.canProvide(ticker, exchange, context)) {
435
- return true;
436
- }
437
- } catch {
438
- // continue through provider chain
439
- }
440
- }
441
- return false;
442
- }
443
-
444
- getCachedFinancialsForTargets(
445
- targets: CachedFinancialsTarget[],
446
- options: { allowExpired?: boolean; includeStaleQuotes?: boolean } = {},
447
- ): Map<string, TickerFinancials> {
448
- const results = new Map<string, TickerFinancials>();
449
- for (const target of targets) {
450
- const cached = this.readCachedMergedFinancials(target.symbol, target.exchange, {
451
- brokerId: target.brokerId,
452
- brokerInstanceId: target.brokerInstanceId,
453
- instrument: target.instrument ?? undefined,
454
- }, options.allowExpired ?? true, {
455
- includeStaleQuotes: options.includeStaleQuotes,
456
- includeSymbolProviderFallback: true,
457
- });
458
- if (cached) results.set(target.symbol.toUpperCase(), cached);
459
- }
460
- return results;
461
- }
462
-
463
- getCachedExchangeRates(currencies: string[], options: { allowExpired?: boolean } = {}): Map<string, number> {
464
- const results = new Map<string, number>();
465
- for (const currency of currencies) {
466
- const cached = this.readCachedExchangeRate(currency, options.allowExpired ?? true);
467
- if (cached != null) results.set(currency, cached);
468
- }
469
- return results;
470
- }
471
-
472
- async getQuotesBatch(
473
- targets: QuoteSubscriptionTarget[],
474
- options: { forceRefresh?: boolean } = {},
475
- ): Promise<QuoteBatchResult[]> {
476
- const forceRefresh = options.forceRefresh === true;
477
- const results = new Array<QuoteBatchResult | null>(targets.length).fill(null);
478
- const misses: Array<{ index: number; target: QuoteSubscriptionTarget }> = [];
479
-
480
- targets.forEach((target, index) => {
481
- const context = target.context;
482
- const entityKey = this.getEntityKey(target.symbol, target.exchange, context?.instrument);
483
- const variantKeys = this.getTickerVariantCandidates(target.exchange);
484
- const sourceKeys = [
485
- ...this.getBrokerCandidatesForContext(context, false).map((candidate) => this.brokerSourceKey(candidate)),
486
- ...this.getProviderSourceKeys(),
487
- ];
488
- const rawCached = this.selectCachedResource<Quote>("quote", entityKey, variantKeys, sourceKeys, false);
489
- const cached = rawCached && !isQuoteStaleForCurrentSession(quoteWithFreshnessExchange(rawCached.value, target.exchange))
490
- ? rawCached
491
- : null;
492
- if (cached && !forceRefresh && !cached.stale) {
493
- results[index] = { target, quote: cached.value };
494
- return;
495
- }
496
- misses.push({ index, target });
497
- });
498
-
499
- const batchProvider = this.providersInPriorityOrder().find((provider) => provider.getQuotesBatch);
500
- const providerMisses = misses.filter(({ target }) => !this.hasBrokerContext(target.context));
501
- const providerIndexes = new Map<string, Array<{ index: number; target: QuoteSubscriptionTarget }>>();
502
- if (batchProvider && providerMisses.length > 0) {
503
- for (const entry of providerMisses) {
504
- const key = this.quoteBatchKey(entry.target);
505
- const bucket = providerIndexes.get(key) ?? [];
506
- bucket.push(entry);
507
- providerIndexes.set(key, bucket);
508
- }
509
- const uniqueTargets = [...providerIndexes.values()].map((bucket) => bucket[0]!.target);
510
- const batchResults = await batchProvider.getQuotesBatch!(uniqueTargets, options).catch(() => []);
511
- for (const item of batchResults) {
512
- if (!item.quote || isQuoteStaleForCurrentSession(item.quote)) continue;
513
- const key = this.quoteBatchKey(item.target);
514
- const sourceKey = this.providerSourceKey(batchProvider);
515
- for (const entry of providerIndexes.get(key) ?? []) {
516
- const entityKey = this.getEntityKey(entry.target.symbol, entry.target.exchange, entry.target.context?.instrument);
517
- const variantKey = this.getTickerVariantCandidates(entry.target.exchange)[0] ?? "";
518
- this.cacheResource("quote", entityKey, variantKey, sourceKey, item.quote, this.resolveProviderPolicy("quote", batchProvider));
519
- results[entry.index] = { target: entry.target, quote: item.quote };
520
- }
521
- }
522
- }
523
-
524
- await Promise.all(misses.map(async ({ index, target }) => {
525
- if (results[index]) return;
526
- try {
527
- const quote = await this.getQuote(target.symbol, target.exchange, {
528
- ...target.context,
529
- cacheMode: forceRefresh ? "refresh" : "default",
530
- });
531
- results[index] = { target, quote };
532
- } catch (error) {
533
- results[index] = { target, quote: null, error };
534
- }
535
- }));
536
-
537
- return results.map((result, index) => result ?? { target: targets[index]!, quote: null });
538
- }
539
-
540
- async getTickerFinancialsBatch(
541
- targets: CachedFinancialsTarget[],
542
- options: { forceRefresh?: boolean } = {},
543
- ): Promise<TickerFinancialsBatchResult[]> {
544
- const forceRefresh = options.forceRefresh === true;
545
- const results = new Array<TickerFinancialsBatchResult | null>(targets.length).fill(null);
546
- const misses: Array<{ index: number; target: CachedFinancialsTarget }> = [];
547
-
548
- targets.forEach((target, index) => {
549
- const context = this.contextFromCachedTarget(target);
550
- const cached = this.readCachedMergedFinancialsSelection(target.symbol, target.exchange, context, true);
551
- if (cached.value && !forceRefresh) {
552
- results[index] = { target, financials: cached.value };
553
- return;
554
- }
555
- misses.push({ index, target });
556
- });
557
-
558
- const batchProvider = this.providersInPriorityOrder().find((provider) => provider.getTickerFinancialsBatch);
559
- const providerMisses = misses.filter(({ target }) => !this.hasCachedTargetBrokerContext(target));
560
- const providerIndexes = new Map<string, Array<{ index: number; target: CachedFinancialsTarget }>>();
561
- if (batchProvider && providerMisses.length > 0) {
562
- for (const entry of providerMisses) {
563
- const key = this.cachedFinancialsBatchKey(entry.target);
564
- const bucket = providerIndexes.get(key) ?? [];
565
- bucket.push(entry);
566
- providerIndexes.set(key, bucket);
567
- }
568
- const uniqueTargets = [...providerIndexes.values()].map((bucket) => bucket[0]!.target);
569
- const batchResults = await batchProvider.getTickerFinancialsBatch!(uniqueTargets, options).catch(() => []);
570
- for (const item of batchResults) {
571
- if (!item.financials) continue;
572
- const key = this.cachedFinancialsBatchKey(item.target);
573
- const value = resolveTickerFinancialsQuoteState(normalizeTickerFinancialsPriceHistory(item.financials));
574
- if (!value) continue;
575
- const sourceKey = this.providerSourceKey(batchProvider);
576
- for (const entry of providerIndexes.get(key) ?? []) {
577
- const entityKey = this.getEntityKey(entry.target.symbol, entry.target.exchange, entry.target.instrument ?? undefined);
578
- const variantKey = this.getTickerVariantCandidates(entry.target.exchange)[0] ?? "";
579
- this.cacheResource("financials", entityKey, variantKey, sourceKey, value, this.resolveProviderPolicy("financials", batchProvider));
580
- results[entry.index] = { target: entry.target, financials: value };
581
- }
582
- }
583
- }
584
-
585
- await Promise.all(misses.map(async ({ index, target }) => {
586
- if (results[index]) return;
587
- try {
588
- const financials = await this.getTickerFinancials(target.symbol, target.exchange, {
589
- ...this.contextFromCachedTarget(target),
590
- cacheMode: forceRefresh ? "refresh" : "default",
591
- });
592
- results[index] = { target, financials };
593
- } catch (error) {
594
- results[index] = { target, financials: null, error };
595
- }
596
- }));
597
-
598
- return results.map((result, index) => result ?? { target: targets[index]!, financials: null });
599
- }
600
-
601
- async getTickerFinancials(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<TickerFinancials> {
602
- const isOptionTicker =
603
- parseOptionSymbol(ticker) != null ||
604
- parseOptionSymbol(context?.instrument?.localSymbol ?? "") != null ||
605
- context?.instrument?.secType === "OPT";
606
- const quoteOnlyFinancials = async (base?: TickerFinancials | null): Promise<TickerFinancials> => ({
607
- ...base,
608
- quote: await this.getQuote(ticker, exchange, context),
609
- annualStatements: base?.annualStatements ?? [],
610
- quarterlyStatements: base?.quarterlyStatements ?? [],
611
- priceHistory: base?.priceHistory ?? [],
612
- });
613
- const cached = this.readCachedMergedFinancialsSelection(ticker, exchange, context, false);
614
- const forceRefresh = context?.cacheMode === "refresh";
615
- if (cached.value && !forceRefresh) {
616
- if (isOptionTicker && !cached.value.quote) {
617
- return quoteOnlyFinancials(cached.value);
618
- }
619
- if (!cached.stale && hasMeaningfulProfile(cached.value)) {
620
- return cached.value;
621
- }
622
- if (!hasMeaningfulProfile(cached.value) && !cached.stale) {
623
- const providerResult = await this.fetchProviderFinancials(ticker, exchange, context);
624
- return mergeFinancials(cached.value, providerResult?.value ?? null) ?? cached.value;
625
- }
626
- }
627
-
628
- if (cached.value) {
629
- const brokerResult = await withBrokerTimeout(this.fetchBrokerFinancials(ticker, exchange, context));
630
- const providerResult = await this.fetchProviderFinancials(ticker, exchange, context);
631
- const merged = mergeFinancials(
632
- brokerResult?.value ?? cached.brokerRecord?.value ?? null,
633
- providerResult?.value ?? cached.providerValue ?? null,
634
- );
635
- if (isOptionTicker && !merged?.quote) {
636
- return quoteOnlyFinancials(merged ?? cached.value);
637
- }
638
- return merged ?? cached.value;
639
- }
640
-
641
- const brokerResult = await withBrokerTimeout(this.fetchBrokerFinancials(ticker, exchange, context));
642
- const fallback = await this.fetchProviderFinancials(ticker, exchange, context);
643
- const merged = mergeFinancials(brokerResult?.value ?? null, fallback?.value ?? null);
644
- if (isOptionTicker && !merged?.quote) {
645
- return quoteOnlyFinancials(merged);
646
- }
647
- if (!merged) {
648
- throw new Error(`No provider available for ${ticker}`);
649
- }
650
- return merged;
651
- }
652
-
653
- async getQuote(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<Quote> {
654
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
655
- const variantKeys = this.getTickerVariantCandidates(exchange);
656
- const sourceKeys = [
657
- ...this.getBrokerCandidatesForContext(context, false).map((candidate) => this.brokerSourceKey(candidate)),
658
- ...this.getProviderSourceKeys(),
659
- ];
660
- const rawCached = this.selectCachedResource<Quote>("quote", entityKey, variantKeys, sourceKeys, false);
661
- const cached = rawCached && !isQuoteStaleForCurrentSession(quoteWithFreshnessExchange(rawCached.value, exchange))
662
- ? rawCached
663
- : null;
664
- const forceRefresh = context?.cacheMode === "refresh";
665
- if (cached && !forceRefresh && !cached.stale) {
666
- return cached.value;
667
- }
668
-
669
- const brokerQuote = await withBrokerTimeout(this.fetchBrokerQuote(ticker, exchange, context));
670
- if (brokerQuote && !isQuoteStaleForCurrentSession(quoteWithFreshnessExchange(brokerQuote.value, exchange))) {
671
- return brokerQuote.value;
672
- }
673
-
674
- const providerQuote = await this.fetchProviderQuote(ticker, exchange, context);
675
- if (providerQuote) {
676
- return providerQuote.value;
677
- }
678
- if (cached) return cached.value;
679
- throw new Error(`No quote provider available for ${ticker}`);
680
- }
681
-
682
- async getExchangeRate(fromCurrency: string): Promise<number> {
683
- const normalizedCurrency = fromCurrency.trim().toUpperCase();
684
- if (normalizedCurrency === "USD") return 1;
685
-
686
- const cached = this.readCachedExchangeRate(normalizedCurrency, false);
687
- if (cached != null) {
688
- this.scheduleRevalidation(`exchange-rate:${normalizedCurrency}`, async () => {
689
- await this.revalidateExchangeRate(normalizedCurrency);
690
- });
691
- return cached;
692
- }
693
-
694
- const result = await this.firstProvider(async (provider) => {
695
- const rate = await provider.getExchangeRate(normalizedCurrency);
696
- return { provider, rate };
697
- });
698
- if (!result) {
699
- throw new Error(`No exchange rate provider available for ${normalizedCurrency}`);
700
- }
701
- this.cacheExchangeRate(normalizedCurrency, result.value.rate, result.value.provider);
702
- return result.value.rate;
703
- }
704
-
705
- async search(query: string, context?: SearchRequestContext): Promise<InstrumentSearchResult[]> {
706
- const cacheKey = JSON.stringify([
707
- query.trim().toUpperCase(),
708
- context?.preferBroker ?? true,
709
- context?.brokerInstanceId ?? "",
710
- context?.brokerId ?? "",
711
- ]);
712
- const cached = this.searchCache.get(cacheKey);
713
- if (cached && cached.expiresAt > Date.now()) return cached.results;
714
- const inFlight = this.searchInFlight.get(cacheKey);
715
- if (inFlight) return inFlight;
716
-
717
- const task = (async () => {
718
- const results: InstrumentSearchResult[] = [];
719
- const resultIndexByKey = new Map<string, number>();
720
-
721
- const push = (items: InstrumentSearchResult[]) => {
722
- for (const item of items) {
723
- const key = buildSearchResultKey(item);
724
- const existingIndex = resultIndexByKey.get(key);
725
- if (existingIndex == null) {
726
- resultIndexByKey.set(key, results.length);
727
- results.push(item);
728
- continue;
729
- }
730
-
731
- const existing = results[existingIndex]!;
732
- if (getSearchResultRichness(item, context) > getSearchResultRichness(existing, context)) {
733
- results[existingIndex] = item;
734
- }
735
- }
736
- };
737
-
738
- if (context?.preferBroker !== false) {
739
- for (const candidate of this.getBrokerCandidates(
740
- context?.brokerInstanceId,
741
- context?.brokerId,
742
- )) {
743
- if (!candidate.broker.searchInstruments) continue;
744
- try {
745
- const items = await withSearchTimeout(candidate.broker.searchInstruments(query, candidate.instance));
746
- if (!items) continue;
747
- push(this.annotateSearchResults(items, candidate));
748
- if (results.length > 0) {
749
- this.searchCache.set(cacheKey, {
750
- expiresAt: Date.now() + SEARCH_CACHE_TTL_MS,
751
- results,
752
- });
753
- return results;
754
- }
755
- } catch {
756
- // continue through broker candidates
757
- }
758
- }
759
- }
760
-
761
- for (const provider of this.providersInPriorityOrder()) {
762
- try {
763
- const items = await withSearchTimeout(provider.search(query, context));
764
- if (!items) continue;
765
- push(items);
766
- if (results.length > 0) {
767
- this.searchCache.set(cacheKey, {
768
- expiresAt: Date.now() + SEARCH_CACHE_TTL_MS,
769
- results,
770
- });
771
- return results;
772
- }
773
- } catch (error) {
774
- if (shouldLogProviderError(error)) {
775
- providerLog.error(`${provider.id} failed: ${error}`);
776
- }
777
- }
778
- }
779
-
780
- this.searchCache.set(cacheKey, {
781
- expiresAt: Date.now() + Math.min(SEARCH_CACHE_TTL_MS, 5_000),
782
- results,
783
- });
784
- return results;
785
- })().finally(() => {
786
- this.searchInFlight.delete(cacheKey);
787
- });
788
-
789
- this.searchInFlight.set(cacheKey, task);
790
- return task;
791
- }
792
-
793
- async getNews(query: NewsQuery): Promise<NewsArticle[]> {
794
- const sources = this.newsSourcesInPriorityOrder()
795
- .filter((source) => source.news?.supports?.(query) ?? true);
796
- const feed = query.feed ?? (query.scope === "ticker" ? "ticker" : "latest");
797
- if (feed === "ticker") {
798
- let firstEmpty: NewsArticle[] | null = null;
799
- for (const source of sources) {
800
- try {
801
- const articles = await source.news!.fetchNews(query);
802
- if (articles.length > 0) return articles;
803
- firstEmpty ??= articles;
804
- } catch (error) {
805
- if (shouldLogProviderError(error)) {
806
- providerLog.error(`${source.id} failed: ${error}`);
807
- }
808
- }
809
- }
810
- return firstEmpty ?? [];
811
- }
812
-
813
- const settled = await Promise.allSettled(sources.map((source) => source.news!.fetchNews(query)));
814
- return settled.flatMap((result) => result.status === "fulfilled" ? result.value : []);
815
- }
816
-
817
- async getHolders(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<HolderData> {
818
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
819
- const variantKeys = this.getTickerVariantCandidates(exchange);
820
- const cached = this.selectCachedResource<HolderData>("holders", entityKey, variantKeys, this.getProviderSourceKeys(), false);
821
- const forceRefresh = context?.cacheMode === "refresh";
822
- if (cached && !forceRefresh) {
823
- this.scheduleRevalidation(this.makeRevalidationKey("holders", ticker, exchange, context), async () => {
824
- await this.revalidateHolders(ticker, exchange, context);
825
- });
826
- return cached.value;
827
- }
828
-
829
- const result = await this.fetchProviderHolders(ticker, exchange, context);
830
- if (result) return result.value;
831
- if (cached) return cached.value;
832
- throw new Error(`No holder data provider available for ${ticker}`);
833
- }
834
-
835
- async getAnalystResearch(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<AnalystResearchData> {
836
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
837
- const variantKeys = this.getTickerVariantCandidates(exchange);
838
- const cached = this.selectCachedAnalystResearch(entityKey, variantKeys, this.getProviderSourceKeys(), false);
839
- const forceRefresh = context?.cacheMode === "refresh";
840
- if (cached && !forceRefresh && !isAnalystResearchMissingRatingTargets(cached.value)) {
841
- this.scheduleRevalidation(this.makeRevalidationKey("analystResearch", ticker, exchange, context), async () => {
842
- await this.revalidateAnalystResearch(ticker, exchange, context);
843
- });
844
- return cached.value;
845
- }
846
-
847
- const result = await this.fetchProviderAnalystResearch(ticker, exchange, context);
848
- if (result) return result.value;
849
- if (cached) return cached.value;
850
- throw new Error(`No analyst research provider available for ${ticker}`);
851
- }
852
-
853
- async getCorporateActions(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<CorporateActionsData> {
854
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
855
- const variantKeys = this.getTickerVariantCandidates(exchange);
856
- const cached = this.selectCachedResource<CorporateActionsData>("corporateActions", entityKey, variantKeys, this.getProviderSourceKeys(), false);
857
- const forceRefresh = context?.cacheMode === "refresh";
858
- if (cached && !forceRefresh) {
859
- this.scheduleRevalidation(this.makeRevalidationKey("corporateActions", ticker, exchange, context), async () => {
860
- await this.revalidateCorporateActions(ticker, exchange, context);
861
- });
862
- return cached.value;
863
- }
864
-
865
- const result = await this.fetchProviderCorporateActions(ticker, exchange, context);
866
- if (result) return result.value;
867
- if (cached) return cached.value;
868
- throw new Error(`No corporate actions provider available for ${ticker}`);
869
- }
870
-
871
- async getSecFilings(ticker: string, count = 15, exchange?: string, context?: MarketDataRequestContext): Promise<SecFilingItem[]> {
872
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
873
- const variantKeys = [
874
- buildVariantKey([["exchange", canonicalExchange(exchange)], ["count", count]]),
875
- buildVariantKey([["count", count]]),
876
- "",
877
- ];
878
- const cached = this.selectCachedResource<SecFilingItem[]>("sec-filings", entityKey, variantKeys, this.getProviderSourceKeys(), false);
879
- if (cached) {
880
- this.scheduleRevalidation(this.makeRevalidationKey("sec-filings", ticker, exchange, context, count), async () => {
881
- await this.revalidateSecFilings(ticker, count, exchange, context);
882
- });
883
- return cached.value;
884
- }
885
-
886
- const result = await this.fetchProviderSecFilings(ticker, count, exchange, context);
887
- if (!result) {
888
- throw new Error(`No SEC filings provider available for ${ticker}`);
889
- }
890
- return result.value;
891
- }
892
-
893
- async getSecFilingContent(filing: SecFilingItem): Promise<string | null> {
894
- const entityKey = compactUrl(filing.primaryDocumentUrl ?? filing.filingUrl);
895
- const cached = this.selectCachedResource<string | null>("sec-filing-content", entityKey, [""], this.getProviderSourceKeys(), false);
896
- if (cached) {
897
- this.scheduleRevalidation(`sec-filing-content:${entityKey}`, async () => {
898
- await this.revalidateSecFilingContent(filing);
899
- });
900
- return cached.value;
901
- }
902
-
903
- const result = await this.fetchProviderSecFilingContent(filing);
904
- if (!result) {
905
- throw new Error("No SEC filing content provider available");
906
- }
907
- return result.value;
908
- }
909
-
910
- async getArticleSummary(url: string): Promise<string | null> {
911
- const entityKey = compactUrl(url);
912
- const cached = this.selectCachedResource<string>("article-summary", entityKey, [""], this.getProviderSourceKeys(), false);
913
- if (cached) {
914
- this.scheduleRevalidation(`article-summary:${entityKey}`, async () => {
915
- await this.revalidateArticleSummary(url);
916
- });
917
- return cached.value;
918
- }
919
-
920
- const result = await this.firstProvider((provider) => provider.getArticleSummary(url));
921
- if (!result) {
922
- return null;
923
- }
924
- const provider = this.resolveProviderBySourceKey(result.sourceKey);
925
- if (provider && result.value) {
926
- this.cacheResource("article-summary", entityKey, "", result.sourceKey, result.value, this.resolveProviderPolicy("articleSummary", provider));
927
- }
928
- return result.value;
929
- }
930
-
931
- async getPriceHistory(ticker: string, exchange: string, range: TimeRange, context?: MarketDataRequestContext): Promise<PricePoint[]> {
932
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
933
- const variantKeys = [
934
- buildVariantKey([["exchange", canonicalExchange(exchange)], ["range", range]]),
935
- buildVariantKey([["range", range]]),
936
- ];
937
- const sourceKeys = [
938
- ...this.getBrokerCandidatesForContext(context, false).map((candidate) => this.brokerSourceKey(candidate)),
939
- ...this.getProviderSourceKeys(),
940
- ];
941
- const cached = this.selectCachedArrayResource<PricePoint>("price-history", entityKey, variantKeys, sourceKeys, false);
942
- const cachedValue = cached ? normalizePriceHistory(cached.value) : [];
943
- const cachedHistoryStale = isStaleIntradayHistory(cachedValue, isIntradayRange(range));
944
- const forceRefresh = context?.cacheMode === "refresh";
945
- if (cachedValue.length > 0 && !forceRefresh && cached && !cached.stale && !cachedHistoryStale) {
946
- return cachedValue;
947
- }
948
-
949
- const brokerHistory = await withBrokerTimeout(this.fetchBrokerPriceHistory(ticker, exchange, range, context));
950
- if (brokerHistory && brokerHistory.value.length > 0) return brokerHistory.value;
951
-
952
- const providerHistory = await this.fetchProviderPriceHistory(ticker, exchange, range, context);
953
- if (providerHistory && providerHistory.value.length > 0) {
954
- return providerHistory.value;
955
- }
956
- if (cachedValue.length > 0 && !cachedHistoryStale) return cachedValue;
957
- if (!providerHistory) {
958
- throw new Error(`No history provider available for ${ticker}`);
959
- }
960
- return providerHistory.value;
961
- }
962
-
963
- async getPriceHistoryForResolution(
964
- ticker: string,
965
- exchange: string,
966
- bufferRange: TimeRange,
967
- resolution: ManualChartResolution,
968
- context?: MarketDataRequestContext,
969
- ): Promise<PricePoint[]> {
970
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
971
- const variantKeys = [
972
- buildVariantKey([["exchange", canonicalExchange(exchange)], ["range", bufferRange], ["resolution", resolution]]),
973
- buildVariantKey([["range", bufferRange], ["resolution", resolution]]),
974
- ];
975
- const sourceKeys = [
976
- ...this.getBrokerCandidatesForContext(context, false).map((candidate) => this.brokerSourceKey(candidate)),
977
- ...this.getProviderSourceKeys(),
978
- ];
979
- const cached = this.selectCachedArrayResource<PricePoint>("price-history", entityKey, variantKeys, sourceKeys, false);
980
- const cachedValue = cached ? normalizePriceHistory(cached.value) : [];
981
- const cachedHistoryStale = isStaleIntradayHistory(cachedValue, isIntradayResolution(resolution));
982
- const forceRefresh = context?.cacheMode === "refresh";
983
- if (cachedValue.length > 0 && !forceRefresh && cached && !cached.stale && !cachedHistoryStale) {
984
- return cachedValue;
985
- }
986
-
987
- const brokerHistory = await withBrokerTimeout(this.fetchBrokerPriceHistoryForResolution(ticker, exchange, bufferRange, resolution, context));
988
- if (brokerHistory && brokerHistory.value.length > 0) return brokerHistory.value;
989
-
990
- const providerHistory = await this.fetchProviderPriceHistoryForResolution(ticker, exchange, bufferRange, resolution, context);
991
- if (providerHistory && providerHistory.value.length > 0) {
992
- return providerHistory.value;
993
- }
994
- if (cachedValue.length > 0 && !cachedHistoryStale) return cachedValue;
995
- if (!providerHistory) {
996
- throw new Error(`No resolution-aware history provider available for ${ticker}`);
997
- }
998
- return providerHistory.value;
999
- }
1000
-
1001
- async getChartResolutionSupport(
1002
- ticker: string,
1003
- exchange?: string,
1004
- context?: MarketDataRequestContext,
1005
- ): Promise<ChartResolutionSupport[]> {
1006
- const brokerSupport = await withBrokerTimeout(this.fetchBrokerChartResolutionSupport(ticker, exchange, context));
1007
- if (brokerSupport && brokerSupport.value.length > 0) {
1008
- return brokerSupport.value;
1009
- }
1010
- const providerSupport = await this.fetchProviderChartResolutionSupport(ticker, exchange, context);
1011
- return providerSupport?.value ?? [];
1012
- }
1013
-
1014
- async getChartResolutionCapabilities(
1015
- ticker: string,
1016
- exchange?: string,
1017
- context?: MarketDataRequestContext,
1018
- ): Promise<ManualChartResolution[]> {
1019
- const support = await this.getChartResolutionSupport(ticker, exchange, context);
1020
- return support.map((entry) => entry.resolution);
1021
- }
1022
-
1023
- async getDetailedPriceHistory(
1024
- ticker: string,
1025
- exchange: string,
1026
- startDate: Date,
1027
- endDate: Date,
1028
- barSize: string,
1029
- context?: MarketDataRequestContext,
1030
- ): Promise<PricePoint[]> {
1031
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1032
- const variantKeys = [
1033
- buildVariantKey([["exchange", canonicalExchange(exchange)], ["start", compactDate(startDate)], ["end", compactDate(endDate)], ["bar", barSize]]),
1034
- buildVariantKey([["start", compactDate(startDate)], ["end", compactDate(endDate)], ["bar", barSize]]),
1035
- ];
1036
- const sourceKeys = [
1037
- ...this.getBrokerCandidatesForContext(context, false).map((candidate) => this.brokerSourceKey(candidate)),
1038
- ...this.getProviderSourceKeys(),
1039
- ];
1040
- const cached = this.selectCachedArrayResource<PricePoint>("detailed-price-history", entityKey, variantKeys, sourceKeys, false);
1041
- const cachedValue = cached ? normalizePriceHistory(cached.value) : [];
1042
- const isCurrentWindow = isCurrentHistoryWindow(endDate);
1043
- const cachedHistoryStale = isCurrentWindow && isPriceHistoryStaleForCurrentWindow(cachedValue);
1044
- const forceRefresh = context?.cacheMode === "refresh";
1045
- if (cachedValue.length > 0 && !forceRefresh && cached && !cached.stale && !cachedHistoryStale) {
1046
- return cachedValue;
1047
- }
1048
-
1049
- const brokerResult = await withBrokerTimeout(this.fetchBrokerDetailedPriceHistory(ticker, exchange, startDate, endDate, barSize, context));
1050
- if (brokerResult && brokerResult.value.length > 0) return brokerResult.value;
1051
-
1052
- const providerResult = await this.fetchProviderDetailedPriceHistory(ticker, exchange, startDate, endDate, barSize, context);
1053
- if (providerResult && providerResult.value.length > 0) {
1054
- return providerResult.value;
1055
- }
1056
- return cachedValue.length > 0 && !cachedHistoryStale ? cachedValue : (providerResult?.value ?? []);
1057
- }
1058
-
1059
- async getOptionsChain(ticker: string, exchange?: string, expirationDate?: number, context?: MarketDataRequestContext): Promise<OptionsChain> {
1060
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1061
- const variantKeys = [
1062
- buildVariantKey([["exchange", canonicalExchange(exchange)], ["expiration", expirationDate ?? "default"]]),
1063
- buildVariantKey([["expiration", expirationDate ?? "default"]]),
1064
- "",
1065
- ];
1066
- const sourceKeys = [
1067
- ...this.getBrokerCandidatesForContext(context, false).map((candidate) => this.brokerSourceKey(candidate)),
1068
- ...this.getProviderSourceKeys(),
1069
- ];
1070
- const cached = this.selectCachedResource<OptionsChain>("options-chain", entityKey, variantKeys, sourceKeys, false);
1071
- if (cached) {
1072
- this.scheduleRevalidation(this.makeRevalidationKey("options-chain", ticker, exchange, context, expirationDate ?? "default"), async () => {
1073
- await this.revalidateOptionsChain(ticker, exchange, expirationDate, context);
1074
- });
1075
- return cached.value;
1076
- }
1077
-
1078
- const brokerChain = await withBrokerTimeout(this.fetchBrokerOptionsChain(ticker, exchange, expirationDate, context));
1079
- if (brokerChain) return brokerChain.value;
1080
-
1081
- const providerChain = await this.fetchProviderOptionsChain(ticker, exchange, expirationDate, context);
1082
- if (!providerChain) {
1083
- throw new Error(`No options provider available for ${ticker}`);
1084
- }
1085
- return providerChain.value;
1086
- }
1087
-
1088
- private makeRevalidationKey(kind: string, ticker: string, exchange?: string, context?: MarketDataRequestContext, extra?: string | number): string {
1089
- return [
1090
- kind,
1091
- this.getEntityKey(ticker, exchange, context?.instrument),
1092
- extra != null ? String(extra) : "",
1093
- ].join("|");
1094
- }
1095
-
1096
- private scheduleRevalidation(key: string, task: () => Promise<void>): void {
1097
- if (this.revalidationInFlight.has(key)) return;
1098
- const promise = task()
1099
- .catch(() => {})
1100
- .finally(() => {
1101
- this.revalidationInFlight.delete(key);
1102
- });
1103
- this.revalidationInFlight.set(key, promise);
1104
- }
1105
-
1106
- private getEntityKey(ticker: string, exchange?: string, instrument?: BrokerContractRef | null): string {
1107
- if (instrument?.conId != null) return `contract:${instrument.conId}`;
1108
- if (instrument?.localSymbol) return `contract:${instrument.localSymbol.toUpperCase()}`;
1109
- if (instrument?.symbol) return `contract:${instrument.symbol.toUpperCase()}`;
1110
- return normalizeTicker(ticker);
1111
- }
1112
-
1113
- private getTickerVariantCandidates(exchange?: string): string[] {
1114
- const normalizedExchange = canonicalExchange(exchange);
1115
- return [
1116
- buildVariantKey([["exchange", normalizedExchange]]),
1117
- "",
1118
- ].filter((value, index, array) => value.length > 0 || array.indexOf(value) === index);
1119
- }
1120
-
1121
- private providerSourceKey(provider: DataProvider): string {
1122
- return `provider:${provider.id}`;
1123
- }
1124
-
1125
- private brokerSourceKey(candidate: BrokerCandidate): string {
1126
- return `broker:${candidate.brokerId}:${candidate.brokerInstanceId}`;
1127
- }
1128
-
1129
- private getProviderSourceKeys(): string[] {
1130
- return this.providersInPriorityOrder().map((provider) => this.providerSourceKey(provider));
1131
- }
1132
-
1133
- private resolvePolicy(overrides: CachePolicyMap | undefined, key: keyof typeof DEFAULT_CACHE_POLICIES): CachePolicy {
1134
- return overrides?.[key] ?? DEFAULT_CACHE_POLICIES[key]!;
1135
- }
1136
-
1137
- private resolveProviderPolicy(key: keyof typeof DEFAULT_CACHE_POLICIES, provider: DataProvider): CachePolicy {
1138
- return this.resolvePolicy(provider.cachePolicy, key);
1139
- }
1140
-
1141
- private resolveBrokerPolicy(key: keyof typeof DEFAULT_CACHE_POLICIES, broker: BrokerAdapter): CachePolicy {
1142
- return this.resolvePolicy(broker.cachePolicy, key);
1143
- }
1144
-
1145
- private cacheResource<T>(kind: string, entityKey: string, variantKey: string, sourceKey: string, value: T, cachePolicy: CachePolicy): void {
1146
- this.resources?.set(
1147
- {
1148
- namespace: MARKET_NAMESPACE,
1149
- kind,
1150
- entityKey,
1151
- variantKey,
1152
- sourceKey,
1153
- },
1154
- value,
1155
- {
1156
- cachePolicy,
1157
- },
1158
- );
1159
- }
1160
-
1161
- private listCachedResources<T>(
1162
- kind: string,
1163
- entityKey: string,
1164
- variantKeys: string[],
1165
- sourceKeys: string[],
1166
- allowExpired: boolean,
1167
- ): CachedResourceRecord<T>[] {
1168
- if (!this.resources) return [];
1169
- const records = this.resources.list<T>({
1170
- namespace: MARKET_NAMESPACE,
1171
- kind,
1172
- entityKey,
1173
- }, {
1174
- variantKeys,
1175
- sourceKeys,
1176
- allowExpired,
1177
- });
1178
- if (records.length === 0) return [];
1179
-
1180
- return this.sortCachedRecords(records, variantKeys, sourceKeys);
1181
- }
1182
-
1183
- private sortCachedRecords<T>(
1184
- records: CachedResourceRecord<T>[],
1185
- variantKeys: string[],
1186
- sourceKeys: string[],
1187
- ): CachedResourceRecord<T>[] {
1188
- const sourceRank = new Map(sourceKeys.map((sourceKey, index) => [sourceKey, index]));
1189
- const variantRank = new Map(variantKeys.map((variantKey, index) => [variantKey, index]));
1190
- return [...records].sort((a, b) => {
1191
- if (a.expired !== b.expired) return a.expired ? 1 : -1;
1192
- if (a.stale !== b.stale) return a.stale ? 1 : -1;
1193
- const sourceDelta = (sourceRank.get(a.sourceKey) ?? Number.MAX_SAFE_INTEGER) - (sourceRank.get(b.sourceKey) ?? Number.MAX_SAFE_INTEGER);
1194
- if (sourceDelta !== 0) return sourceDelta;
1195
- const variantDelta = (variantRank.get(a.variantKey ?? "") ?? Number.MAX_SAFE_INTEGER) - (variantRank.get(b.variantKey ?? "") ?? Number.MAX_SAFE_INTEGER);
1196
- if (variantDelta !== 0) return variantDelta;
1197
- return b.fetchedAt - a.fetchedAt;
1198
- });
1199
- }
1200
-
1201
- private selectCachedResource<T>(
1202
- kind: string,
1203
- entityKey: string,
1204
- variantKeys: string[],
1205
- sourceKeys: string[],
1206
- allowExpired: boolean,
1207
- ): CachedResourceRecord<T> | null {
1208
- return this.listCachedResources<T>(kind, entityKey, variantKeys, sourceKeys, allowExpired)[0] ?? null;
1209
- }
1210
-
1211
- private selectCachedAnalystResearch(
1212
- entityKey: string,
1213
- variantKeys: string[],
1214
- sourceKeys: string[],
1215
- allowExpired: boolean,
1216
- ): CachedResourceRecord<AnalystResearchData> | null {
1217
- const records = this.listCachedResources<AnalystResearchData>("analystResearch", entityKey, variantKeys, sourceKeys, allowExpired);
1218
- return records.find((record) => !isAnalystResearchMissingRatingTargets(record.value)) ?? records[0] ?? null;
1219
- }
1220
-
1221
- private selectCachedArrayResource<T>(
1222
- kind: string,
1223
- entityKey: string,
1224
- variantKeys: string[],
1225
- sourceKeys: string[],
1226
- allowExpired: boolean,
1227
- ): CachedResourceRecord<T[]> | null {
1228
- const records = this.listCachedResources<T[]>(kind, entityKey, variantKeys, sourceKeys, allowExpired);
1229
- return records.find((record) => record.value.length > 0) ?? records[0] ?? null;
1230
- }
1231
-
1232
- private readCachedMergedFinancials(
1233
- ticker: string,
1234
- exchange?: string,
1235
- context?: MarketDataRequestContext,
1236
- allowExpired = false,
1237
- options: CachedFinancialsReadOptions = {},
1238
- ): TickerFinancials | null {
1239
- return this.readCachedMergedFinancialsSelection(ticker, exchange, context, allowExpired, options).value;
1240
- }
1241
-
1242
- private readCachedMergedFinancialsSelection(
1243
- ticker: string,
1244
- exchange?: string,
1245
- context?: MarketDataRequestContext,
1246
- allowExpired = false,
1247
- options: CachedFinancialsReadOptions = {},
1248
- ): CachedFinancialsSelection {
1249
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1250
- const variantKeys = this.getTickerVariantCandidates(exchange);
1251
- const brokerSourceKeys = this.getBrokerCandidatesForContext(context, false).map((candidate) => this.brokerSourceKey(candidate));
1252
- const brokerRecord = brokerSourceKeys.length > 0
1253
- ? this.selectCachedResource<TickerFinancials>("financials", entityKey, variantKeys, brokerSourceKeys, allowExpired)
1254
- : null;
1255
- const sanitizedBrokerRecord = brokerRecord
1256
- ? { ...brokerRecord, value: sanitizeCachedFinancials(brokerRecord.value, options) }
1257
- : null;
1258
- const providerSourceKeys = this.getProviderSourceKeys();
1259
- const providerEntityKeys = options.includeSymbolProviderFallback
1260
- ? [...new Set([entityKey, normalizeTicker(ticker)])]
1261
- : [entityKey];
1262
- const providerRecords = this.sortCachedRecords(
1263
- providerEntityKeys.flatMap((providerEntityKey) => this.listCachedResources<TickerFinancials>(
1264
- "financials",
1265
- providerEntityKey,
1266
- variantKeys,
1267
- providerSourceKeys,
1268
- allowExpired,
1269
- )),
1270
- variantKeys,
1271
- providerSourceKeys,
1272
- );
1273
- const providerSelection = mergeCachedFinancialRecords(
1274
- providerRecords,
1275
- options,
1276
- );
1277
- const quoteSourceKeys = [...brokerSourceKeys, ...providerSourceKeys];
1278
- const quoteRecords = this.sortCachedRecords(
1279
- providerEntityKeys.flatMap((quoteEntityKey) => this.listCachedResources<Quote>(
1280
- "quote",
1281
- quoteEntityKey,
1282
- variantKeys,
1283
- quoteSourceKeys,
1284
- allowExpired,
1285
- )),
1286
- variantKeys,
1287
- quoteSourceKeys,
1288
- );
1289
- const quoteSelection = selectCachedQuoteRecord(quoteRecords, exchange, options);
1290
- const mergedValue = mergeFinancials(sanitizedBrokerRecord?.value ?? null, providerSelection.value);
1291
- const value = quoteSelection.quote
1292
- ? resolveTickerFinancialsQuoteState(mergedValue, quoteSelection.quote)
1293
- : mergedValue;
1294
- return {
1295
- brokerRecord: sanitizedBrokerRecord,
1296
- providerValue: providerSelection.value,
1297
- value: value
1298
- ? deriveMarketCapFromShares(value, { replaceExisting: !!quoteSelection.quote && quoteSelection.quote.marketCap == null })
1299
- : null,
1300
- stale: (sanitizedBrokerRecord?.stale ?? false) || providerSelection.stale || quoteSelection.stale,
1301
- };
1302
- }
1303
-
1304
- private readCachedExchangeRate(currency: string, allowExpired = false): number | null {
1305
- const normalizedCurrency = currency.toUpperCase();
1306
- if (normalizedCurrency === "USD") return 1;
1307
- const entityKey = `${normalizedCurrency}/USD`;
1308
- const cached = this.selectCachedResource<{ rate: number }>("exchange-rate", entityKey, [""], this.getProviderSourceKeys(), allowExpired);
1309
- return cached?.value.rate ?? null;
1310
- }
1311
-
1312
- private cacheExchangeRate(currency: string, rate: number, provider: DataProvider): void {
1313
- const normalizedCurrency = currency.toUpperCase();
1314
- if (normalizedCurrency === "USD") return;
1315
- this.cacheResource(
1316
- "exchange-rate",
1317
- `${normalizedCurrency}/USD`,
1318
- "",
1319
- this.providerSourceKey(provider),
1320
- { rate },
1321
- this.resolveProviderPolicy("exchangeRate", provider),
1322
- );
1323
- }
1324
-
1325
- private getBrokerCandidates(
1326
- preferredBrokerInstanceId?: string,
1327
- preferredBrokerId?: string,
1328
- includeFallbackInstances = true,
1329
- ): BrokerCandidate[] {
1330
- if (!this.registry) return [];
1331
- const config = this.getConfigFn();
1332
- const candidates: BrokerCandidate[] = [];
1333
-
1334
- const pushCandidate = (instance: AppConfig["brokerInstances"][number]) => {
1335
- if (instance.enabled === false) return;
1336
- if (preferredBrokerId && instance.brokerType !== preferredBrokerId && instance.id !== preferredBrokerInstanceId) return;
1337
- const broker = this.registry?.brokers.get(instance.brokerType);
1338
- if (!broker) return;
1339
- candidates.push({
1340
- brokerId: instance.brokerType,
1341
- brokerInstanceId: instance.id,
1342
- brokerLabel: instance.label,
1343
- broker,
1344
- instance,
1345
- });
1346
- };
1347
-
1348
- const preferredInstance = preferredBrokerInstanceId
1349
- ? config.brokerInstances.find((instance) => instance.id === preferredBrokerInstanceId)
1350
- : undefined;
1351
- if (preferredInstance) {
1352
- pushCandidate(preferredInstance);
1353
- }
1354
-
1355
- if (!includeFallbackInstances && preferredInstance) {
1356
- return candidates;
1357
- }
1358
-
1359
- for (const instance of config.brokerInstances) {
1360
- if (instance.id === preferredBrokerInstanceId) continue;
1361
- pushCandidate(instance);
1362
- }
1363
-
1364
- return candidates;
1365
- }
1366
-
1367
- private getBrokerCandidatesForContext(
1368
- context?: MarketDataRequestContext,
1369
- includeFallbackInstances = true,
1370
- ): BrokerCandidate[] {
1371
- return this.getBrokerCandidates(
1372
- context?.instrument?.brokerInstanceId ?? context?.brokerInstanceId,
1373
- context?.instrument?.brokerId ?? context?.brokerId,
1374
- includeFallbackInstances,
1375
- );
1376
- }
1377
-
1378
- private hasBrokerContext(context?: MarketDataRequestContext): boolean {
1379
- return !!(
1380
- context?.brokerId ||
1381
- context?.brokerInstanceId ||
1382
- context?.instrument?.brokerId ||
1383
- context?.instrument?.brokerInstanceId
1384
- );
1385
- }
1386
-
1387
- private hasCachedTargetBrokerContext(target: CachedFinancialsTarget): boolean {
1388
- return !!(
1389
- target.brokerId ||
1390
- target.brokerInstanceId ||
1391
- target.instrument?.brokerId ||
1392
- target.instrument?.brokerInstanceId
1393
- );
1394
- }
1395
-
1396
- private contextFromCachedTarget(target: CachedFinancialsTarget): MarketDataRequestContext {
1397
- return {
1398
- brokerId: target.brokerId,
1399
- brokerInstanceId: target.brokerInstanceId,
1400
- instrument: target.instrument ?? null,
1401
- };
1402
- }
1403
-
1404
- private quoteBatchKey(target: QuoteSubscriptionTarget): string {
1405
- return `${target.symbol.trim().toUpperCase()}:${canonicalExchange(target.exchange ?? "")}`;
1406
- }
1407
-
1408
- private cachedFinancialsBatchKey(target: CachedFinancialsTarget): string {
1409
- return `${target.symbol.trim().toUpperCase()}:${canonicalExchange(target.exchange ?? "")}`;
1410
- }
1411
-
1412
- private getStreamingBrokerCandidate(target: QuoteSubscriptionTarget): BrokerCandidate | null {
1413
- if (!this.hasBrokerContext(target.context)) return null;
1414
- return this.getBrokerCandidatesForContext(target.context, false)
1415
- .find((candidate) => typeof candidate.broker.subscribeQuotes === "function") ?? null;
1416
- }
1417
-
1418
- private annotateSearchResults(items: InstrumentSearchResult[], candidate: BrokerCandidate): InstrumentSearchResult[] {
1419
- return items.map((item) => ({
1420
- ...item,
1421
- brokerInstanceId: item.brokerInstanceId ?? candidate.brokerInstanceId,
1422
- brokerLabel: item.brokerLabel ?? candidate.brokerLabel,
1423
- brokerContract: item.brokerContract
1424
- ? {
1425
- ...item.brokerContract,
1426
- brokerId: item.brokerContract.brokerId || candidate.brokerId,
1427
- brokerInstanceId: item.brokerContract.brokerInstanceId ?? candidate.brokerInstanceId,
1428
- }
1429
- : undefined,
1430
- }));
1431
- }
1432
-
1433
- private sortedSources(): CapabilityRouteSource[] {
1434
- const sources = [...this.extraSources];
1435
- if (this.registry) {
1436
- const capabilitySources = new Map<string, CapabilityRouteSource>();
1437
- for (const capability of this.registry.getEnabledCapabilities("asset-data") as AssetDataCapability[]) {
1438
- const sourceId = capabilityRouteSourceId(capability);
1439
- capabilitySources.set(sourceId, mergeCapabilityRouteSource(capabilitySources.get(sourceId), capability));
1440
- }
1441
- for (const capability of this.registry.getEnabledCapabilities("news") as NewsCapability[]) {
1442
- const sourceId = capabilityRouteSourceId(capability);
1443
- capabilitySources.set(sourceId, mergeCapabilityRouteSource(capabilitySources.get(sourceId), capability));
1444
- }
1445
- sources.push(...capabilitySources.values());
1446
- }
1447
- return sources
1448
- .filter((source) => source.id !== this.id && source.id !== this.fallbackSource?.id)
1449
- .sort((a, b) => routeSourcePriority(a) - routeSourcePriority(b));
1450
- }
1451
-
1452
- private sortedProviders(): DataProvider[] {
1453
- return this.sortedSources()
1454
- .map((source) => source.market)
1455
- .filter((provider): provider is DataProvider => !!provider);
1456
- }
1457
-
1458
- private providersInPriorityOrder(): DataProvider[] {
1459
- const providers = [...this.sortedProviders()];
1460
- const fallbackProvider = this.fallbackSource?.market ?? null;
1461
- if (fallbackProvider && !providers.some((provider) => provider.id === fallbackProvider.id)) {
1462
- providers.push(fallbackProvider);
1463
- }
1464
- return providers;
1465
- }
1466
-
1467
- private newsSourcesInPriorityOrder(): CapabilityRouteSource[] {
1468
- const sources = this.sortedSources().filter((source) => !!source.news);
1469
- if (this.fallbackSource?.news && !sources.some((source) => source.id === this.fallbackSource?.id)) {
1470
- sources.push(this.fallbackSource);
1471
- }
1472
- return sources;
1473
- }
1474
-
1475
- private async firstProvider<T>(fn: (provider: DataProvider) => Promise<T | null | undefined>): Promise<SourceResult<T> | null> {
1476
- for (const provider of this.providersInPriorityOrder()) {
1477
- try {
1478
- const result = await fn(provider);
1479
- if (result != null) return { sourceKey: this.providerSourceKey(provider), value: result };
1480
- } catch (err) {
1481
- if (shouldLogProviderError(err)) {
1482
- providerLog.error(`${provider.id} failed: ${err}`);
1483
- }
1484
- }
1485
- }
1486
- return null;
1487
- }
1488
-
1489
- private async fetchBrokerFinancials(
1490
- ticker: string,
1491
- exchange?: string,
1492
- context?: MarketDataRequestContext,
1493
- ): Promise<SourceResult<TickerFinancials> | null> {
1494
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1495
- const variantKey = this.getTickerVariantCandidates(exchange)[0] ?? "";
1496
- for (const candidate of this.getBrokerCandidatesForContext(context, false)) {
1497
- if (!candidate.broker.getTickerFinancials) continue;
1498
- try {
1499
- const rawResult = await candidate.broker.getTickerFinancials(
1500
- ticker,
1501
- candidate.instance,
1502
- exchange,
1503
- context?.instrument ?? null,
1504
- );
1505
- const result = resolveTickerFinancialsQuoteState(normalizeTickerFinancialsPriceHistory(rawResult));
1506
- if (!result) continue;
1507
- this.cacheResource(
1508
- "financials",
1509
- entityKey,
1510
- variantKey,
1511
- this.brokerSourceKey(candidate),
1512
- result,
1513
- this.resolveBrokerPolicy("financials", candidate.broker),
1514
- );
1515
- return { sourceKey: this.brokerSourceKey(candidate), value: result };
1516
- } catch {
1517
- // continue
1518
- }
1519
- }
1520
- return null;
1521
- }
1522
-
1523
- private async fetchProviderFinancials(
1524
- ticker: string,
1525
- exchange?: string,
1526
- context?: MarketDataRequestContext,
1527
- ): Promise<SourceResult<TickerFinancials> | null> {
1528
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1529
- const variantKey = this.getTickerVariantCandidates(exchange)[0] ?? "";
1530
-
1531
- for (const provider of this.providersInPriorityOrder()) {
1532
- try {
1533
- const rawValue = await provider.getTickerFinancials(ticker, exchange, context);
1534
- const value = resolveTickerFinancialsQuoteState(normalizeTickerFinancialsPriceHistory(rawValue));
1535
- if (!value) continue;
1536
- const sourceKey = this.providerSourceKey(provider);
1537
- this.cacheResource(
1538
- "financials",
1539
- entityKey,
1540
- variantKey,
1541
- sourceKey,
1542
- value,
1543
- this.resolveProviderPolicy("financials", provider),
1544
- );
1545
- return { sourceKey, value };
1546
- } catch (error) {
1547
- if (shouldLogProviderError(error)) {
1548
- providerLog.error(`${provider.id} failed: ${error}`);
1549
- }
1550
- }
1551
- }
1552
-
1553
- return null;
1554
- }
1555
-
1556
- private async fetchBrokerQuote(
1557
- ticker: string,
1558
- exchange?: string,
1559
- context?: MarketDataRequestContext,
1560
- ): Promise<SourceResult<Quote> | null> {
1561
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1562
- const variantKey = this.getTickerVariantCandidates(exchange)[0] ?? "";
1563
- for (const candidate of this.getBrokerCandidatesForContext(context, false)) {
1564
- if (!candidate.broker.getQuote) continue;
1565
- try {
1566
- const result = await candidate.broker.getQuote(
1567
- ticker,
1568
- candidate.instance,
1569
- exchange,
1570
- context?.instrument ?? null,
1571
- );
1572
- this.cacheResource(
1573
- "quote",
1574
- entityKey,
1575
- variantKey,
1576
- this.brokerSourceKey(candidate),
1577
- result,
1578
- this.resolveBrokerPolicy("brokerQuote", candidate.broker),
1579
- );
1580
- return { sourceKey: this.brokerSourceKey(candidate), value: result };
1581
- } catch {
1582
- // continue
1583
- }
1584
- }
1585
- return null;
1586
- }
1587
-
1588
- private async fetchProviderQuote(
1589
- ticker: string,
1590
- exchange?: string,
1591
- context?: MarketDataRequestContext,
1592
- ): Promise<SourceResult<Quote> | null> {
1593
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1594
- const variantKey = this.getTickerVariantCandidates(exchange)[0] ?? "";
1595
- for (const provider of this.providersInPriorityOrder()) {
1596
- try {
1597
- const quote = await provider.getQuote(ticker, exchange, context);
1598
- if (quote == null || isQuoteStaleForCurrentSession(quote)) continue;
1599
- const sourceKey = this.providerSourceKey(provider);
1600
- this.cacheResource("quote", entityKey, variantKey, sourceKey, quote, this.resolveProviderPolicy("quote", provider));
1601
- return { sourceKey, value: quote };
1602
- } catch (err) {
1603
- if (shouldLogProviderError(err)) {
1604
- providerLog.error(`${provider.id} failed: ${err}`);
1605
- }
1606
- }
1607
- }
1608
- return null;
1609
- }
1610
-
1611
- private async fetchBrokerPriceHistory(
1612
- ticker: string,
1613
- exchange: string,
1614
- range: TimeRange,
1615
- context?: MarketDataRequestContext,
1616
- ): Promise<SourceResult<PricePoint[]> | null> {
1617
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1618
- const variantKey = buildVariantKey([["exchange", canonicalExchange(exchange)], ["range", range]]);
1619
- for (const candidate of this.getBrokerCandidatesForContext(context, false)) {
1620
- if (!candidate.broker.getPriceHistory) continue;
1621
- try {
1622
- const result = normalizePriceHistory(await candidate.broker.getPriceHistory(
1623
- ticker,
1624
- candidate.instance,
1625
- exchange,
1626
- range,
1627
- context?.instrument ?? null,
1628
- ));
1629
- if (isStaleIntradayHistory(result, isIntradayRange(range))) continue;
1630
- this.cacheResource(
1631
- "price-history",
1632
- entityKey,
1633
- variantKey,
1634
- this.brokerSourceKey(candidate),
1635
- result,
1636
- this.resolveBrokerPolicy(isIntradayRange(range) ? "priceHistoryIntraday" : "priceHistoryDaily", candidate.broker),
1637
- );
1638
- return { sourceKey: this.brokerSourceKey(candidate), value: result };
1639
- } catch {
1640
- // continue
1641
- }
1642
- }
1643
- return null;
1644
- }
1645
-
1646
- private async fetchBrokerPriceHistoryForResolution(
1647
- ticker: string,
1648
- exchange: string,
1649
- bufferRange: TimeRange,
1650
- resolution: ManualChartResolution,
1651
- context?: MarketDataRequestContext,
1652
- ): Promise<SourceResult<PricePoint[]> | null> {
1653
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1654
- const variantKey = buildVariantKey([["exchange", canonicalExchange(exchange)], ["range", bufferRange], ["resolution", resolution]]);
1655
- for (const candidate of this.getBrokerCandidatesForContext(context, false)) {
1656
- if (!candidate.broker.getPriceHistoryForResolution) continue;
1657
- try {
1658
- const result = normalizePriceHistory(await candidate.broker.getPriceHistoryForResolution(
1659
- ticker,
1660
- candidate.instance,
1661
- exchange,
1662
- bufferRange,
1663
- resolution,
1664
- context?.instrument ?? null,
1665
- ));
1666
- if (isStaleIntradayHistory(result, isIntradayResolution(resolution))) continue;
1667
- this.cacheResource(
1668
- "price-history",
1669
- entityKey,
1670
- variantKey,
1671
- this.brokerSourceKey(candidate),
1672
- result,
1673
- this.resolveBrokerPolicy(isIntradayResolution(resolution) ? "priceHistoryIntraday" : "priceHistoryDaily", candidate.broker),
1674
- );
1675
- return { sourceKey: this.brokerSourceKey(candidate), value: result };
1676
- } catch {
1677
- // continue
1678
- }
1679
- }
1680
- return null;
1681
- }
1682
-
1683
- private async fetchProviderPriceHistory(
1684
- ticker: string,
1685
- exchange: string,
1686
- range: TimeRange,
1687
- context?: MarketDataRequestContext,
1688
- ): Promise<SourceResult<PricePoint[]> | null> {
1689
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1690
- const variantKey = buildVariantKey([["exchange", canonicalExchange(exchange)], ["range", range]]);
1691
- let firstEmptyResult: SourceResult<PricePoint[]> | null = null;
1692
-
1693
- for (const provider of this.providersInPriorityOrder()) {
1694
- try {
1695
- const value = normalizePriceHistory(await provider.getPriceHistory(ticker, exchange, range, context));
1696
- if (isStaleIntradayHistory(value, isIntradayRange(range))) continue;
1697
- const sourceKey = this.providerSourceKey(provider);
1698
- this.cacheResource(
1699
- "price-history",
1700
- entityKey,
1701
- variantKey,
1702
- sourceKey,
1703
- value,
1704
- this.resolveProviderPolicy(isIntradayRange(range) ? "priceHistoryIntraday" : "priceHistoryDaily", provider),
1705
- );
1706
- if (value.length > 0) {
1707
- return { sourceKey, value };
1708
- }
1709
- firstEmptyResult ??= { sourceKey, value };
1710
- } catch (error) {
1711
- if (shouldLogProviderError(error)) {
1712
- providerLog.error(`${provider.id} failed: ${error}`);
1713
- }
1714
- }
1715
- }
1716
-
1717
- return firstEmptyResult;
1718
- }
1719
-
1720
- private async fetchProviderPriceHistoryForResolution(
1721
- ticker: string,
1722
- exchange: string,
1723
- bufferRange: TimeRange,
1724
- resolution: ManualChartResolution,
1725
- context?: MarketDataRequestContext,
1726
- ): Promise<SourceResult<PricePoint[]> | null> {
1727
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1728
- const variantKey = buildVariantKey([["exchange", canonicalExchange(exchange)], ["range", bufferRange], ["resolution", resolution]]);
1729
- let firstEmptyResult: SourceResult<PricePoint[]> | null = null;
1730
-
1731
- for (const provider of this.providersInPriorityOrder()) {
1732
- if (!provider.getPriceHistoryForResolution) continue;
1733
- try {
1734
- const value = normalizePriceHistory(await provider.getPriceHistoryForResolution(ticker, exchange, bufferRange, resolution, context));
1735
- if (isStaleIntradayHistory(value, isIntradayResolution(resolution))) continue;
1736
- const sourceKey = this.providerSourceKey(provider);
1737
- this.cacheResource(
1738
- "price-history",
1739
- entityKey,
1740
- variantKey,
1741
- sourceKey,
1742
- value,
1743
- this.resolveProviderPolicy(isIntradayResolution(resolution) ? "priceHistoryIntraday" : "priceHistoryDaily", provider),
1744
- );
1745
- if (value.length > 0) {
1746
- return { sourceKey, value };
1747
- }
1748
- firstEmptyResult ??= { sourceKey, value };
1749
- } catch (error) {
1750
- if (shouldLogProviderError(error)) {
1751
- providerLog.error(`${provider.id} failed: ${error}`);
1752
- }
1753
- }
1754
- }
1755
-
1756
- return firstEmptyResult;
1757
- }
1758
-
1759
- private async fetchBrokerChartResolutionSupport(
1760
- ticker: string,
1761
- exchange?: string,
1762
- context?: MarketDataRequestContext,
1763
- ): Promise<SourceResult<ChartResolutionSupport[]> | null> {
1764
- for (const candidate of this.getBrokerCandidatesForContext(context, false)) {
1765
- if (!candidate.broker.getChartResolutionSupport && !candidate.broker.getChartResolutionCapabilities) continue;
1766
- try {
1767
- const result = candidate.broker.getChartResolutionSupport
1768
- ? normalizeChartResolutionSupport(await candidate.broker.getChartResolutionSupport(
1769
- ticker,
1770
- candidate.instance,
1771
- exchange,
1772
- context?.instrument ?? null,
1773
- ))
1774
- : normalizeChartResolutionSupport(
1775
- (await candidate.broker.getChartResolutionCapabilities!(
1776
- ticker,
1777
- candidate.instance,
1778
- exchange,
1779
- context?.instrument ?? null,
1780
- )).map((resolution) => ({ resolution, maxRange: "ALL" })),
1781
- );
1782
- if (result.length === 0) continue;
1783
- return { sourceKey: this.brokerSourceKey(candidate), value: result };
1784
- } catch {
1785
- // continue
1786
- }
1787
- }
1788
- return null;
1789
- }
1790
-
1791
- private async fetchProviderChartResolutionSupport(
1792
- ticker: string,
1793
- exchange?: string,
1794
- context?: MarketDataRequestContext,
1795
- ): Promise<SourceResult<ChartResolutionSupport[]> | null> {
1796
- for (const provider of this.providersInPriorityOrder()) {
1797
- if (!provider.getChartResolutionSupport && !provider.getChartResolutionCapabilities) continue;
1798
- try {
1799
- const result = provider.getChartResolutionSupport
1800
- ? normalizeChartResolutionSupport(await provider.getChartResolutionSupport(ticker, exchange, context))
1801
- : normalizeChartResolutionSupport(
1802
- (await provider.getChartResolutionCapabilities!(ticker, exchange, context)).map((resolution) => ({ resolution, maxRange: "ALL" })),
1803
- );
1804
- if (result.length === 0) continue;
1805
- return { sourceKey: this.providerSourceKey(provider), value: result };
1806
- } catch (error) {
1807
- if (shouldLogProviderError(error)) {
1808
- providerLog.error(`${provider.id} failed: ${error}`);
1809
- }
1810
- }
1811
- }
1812
-
1813
- return null;
1814
- }
1815
-
1816
- private async fetchBrokerDetailedPriceHistory(
1817
- ticker: string,
1818
- exchange: string,
1819
- startDate: Date,
1820
- endDate: Date,
1821
- barSize: string,
1822
- context?: MarketDataRequestContext,
1823
- ): Promise<SourceResult<PricePoint[]> | null> {
1824
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1825
- const variantKey = buildVariantKey([["exchange", canonicalExchange(exchange)], ["start", compactDate(startDate)], ["end", compactDate(endDate)], ["bar", barSize]]);
1826
- for (const candidate of this.getBrokerCandidatesForContext(context, false)) {
1827
- if (!candidate.broker.getDetailedPriceHistory) continue;
1828
- try {
1829
- const result = normalizePriceHistory(await candidate.broker.getDetailedPriceHistory(
1830
- ticker,
1831
- candidate.instance,
1832
- exchange,
1833
- startDate,
1834
- endDate,
1835
- barSize,
1836
- context?.instrument ?? null,
1837
- ));
1838
- if (isCurrentHistoryWindow(endDate) && isPriceHistoryStaleForCurrentWindow(result)) continue;
1839
- this.cacheResource(
1840
- "detailed-price-history",
1841
- entityKey,
1842
- variantKey,
1843
- this.brokerSourceKey(candidate),
1844
- result,
1845
- this.resolveBrokerPolicy("priceHistoryIntraday", candidate.broker),
1846
- );
1847
- return { sourceKey: this.brokerSourceKey(candidate), value: result };
1848
- } catch {
1849
- // continue
1850
- }
1851
- }
1852
- return null;
1853
- }
1854
-
1855
- private async fetchProviderDetailedPriceHistory(
1856
- ticker: string,
1857
- exchange: string,
1858
- startDate: Date,
1859
- endDate: Date,
1860
- barSize: string,
1861
- context?: MarketDataRequestContext,
1862
- ): Promise<SourceResult<PricePoint[]> | null> {
1863
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1864
- const variantKey = buildVariantKey([["exchange", canonicalExchange(exchange)], ["start", compactDate(startDate)], ["end", compactDate(endDate)], ["bar", barSize]]);
1865
- let firstEmptyResult: SourceResult<PricePoint[]> | null = null;
1866
-
1867
- for (const provider of this.providersInPriorityOrder()) {
1868
- if (!provider.getDetailedPriceHistory) continue;
1869
- try {
1870
- const value = normalizePriceHistory(await provider.getDetailedPriceHistory(ticker, exchange, startDate, endDate, barSize, context));
1871
- if (isCurrentHistoryWindow(endDate) && isPriceHistoryStaleForCurrentWindow(value)) continue;
1872
- const sourceKey = this.providerSourceKey(provider);
1873
- this.cacheResource(
1874
- "detailed-price-history",
1875
- entityKey,
1876
- variantKey,
1877
- sourceKey,
1878
- value,
1879
- this.resolveProviderPolicy("priceHistoryIntraday", provider),
1880
- );
1881
- if (value.length > 0) {
1882
- return { sourceKey, value };
1883
- }
1884
- firstEmptyResult ??= { sourceKey, value };
1885
- } catch (error) {
1886
- if (shouldLogProviderError(error)) {
1887
- providerLog.error(`${provider.id} failed: ${error}`);
1888
- }
1889
- }
1890
- }
1891
-
1892
- return firstEmptyResult;
1893
- }
1894
-
1895
- private async fetchBrokerOptionsChain(
1896
- ticker: string,
1897
- exchange?: string,
1898
- expirationDate?: number,
1899
- context?: MarketDataRequestContext,
1900
- ): Promise<SourceResult<OptionsChain> | null> {
1901
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1902
- const variantKey = buildVariantKey([["exchange", canonicalExchange(exchange)], ["expiration", expirationDate ?? "default"]]);
1903
- for (const candidate of this.getBrokerCandidatesForContext(context, false)) {
1904
- if (!candidate.broker.getOptionsChain) continue;
1905
- try {
1906
- const result = await candidate.broker.getOptionsChain(
1907
- ticker,
1908
- candidate.instance,
1909
- exchange,
1910
- expirationDate,
1911
- context?.instrument ?? null,
1912
- );
1913
- this.cacheResource(
1914
- "options-chain",
1915
- entityKey,
1916
- variantKey,
1917
- this.brokerSourceKey(candidate),
1918
- result,
1919
- this.resolveBrokerPolicy("optionsChain", candidate.broker),
1920
- );
1921
- return { sourceKey: this.brokerSourceKey(candidate), value: result };
1922
- } catch {
1923
- // continue
1924
- }
1925
- }
1926
- return null;
1927
- }
1928
-
1929
- private async fetchProviderOptionsChain(
1930
- ticker: string,
1931
- exchange?: string,
1932
- expirationDate?: number,
1933
- context?: MarketDataRequestContext,
1934
- ): Promise<SourceResult<OptionsChain> | null> {
1935
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1936
- const variantKey = buildVariantKey([["exchange", canonicalExchange(exchange)], ["expiration", expirationDate ?? "default"]]);
1937
- const result = await this.firstProvider(async (provider) => {
1938
- if (!provider.getOptionsChain) return null;
1939
- return provider.getOptionsChain(ticker, exchange, expirationDate, context);
1940
- });
1941
- if (!result) return null;
1942
- const provider = this.resolveProviderBySourceKey(result.sourceKey);
1943
- if (provider) {
1944
- this.cacheResource("options-chain", entityKey, variantKey, result.sourceKey, result.value, this.resolveProviderPolicy("optionsChain", provider));
1945
- }
1946
- return result;
1947
- }
1948
-
1949
- private async fetchProviderHolders(
1950
- ticker: string,
1951
- exchange?: string,
1952
- context?: MarketDataRequestContext,
1953
- ): Promise<SourceResult<HolderData> | null> {
1954
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1955
- const variantKey = this.getTickerVariantCandidates(exchange)[0] ?? "";
1956
- let firstEmptyResult: SourceResult<HolderData> | null = null;
1957
- let lastError: unknown = null;
1958
-
1959
- for (const provider of this.providersInPriorityOrder()) {
1960
- if (!provider.getHolders) continue;
1961
- try {
1962
- const value = await provider.getHolders(ticker, exchange, context);
1963
- const sourceKey = this.providerSourceKey(provider);
1964
- this.cacheResource("holders", entityKey, variantKey, sourceKey, value, this.resolveProviderPolicy("holders", provider));
1965
- if (value.holders.length > 0) return { sourceKey, value };
1966
- firstEmptyResult ??= { sourceKey, value };
1967
- } catch (error) {
1968
- lastError = error;
1969
- if (shouldLogProviderError(error)) {
1970
- providerLog.error(`${provider.id} failed: ${error}`);
1971
- }
1972
- }
1973
- }
1974
-
1975
- if (firstEmptyResult) return firstEmptyResult;
1976
- if (lastError) throw lastError;
1977
- return null;
1978
- }
1979
-
1980
- private async fetchProviderAnalystResearch(
1981
- ticker: string,
1982
- exchange?: string,
1983
- context?: MarketDataRequestContext,
1984
- ): Promise<SourceResult<AnalystResearchData> | null> {
1985
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
1986
- const variantKey = this.getTickerVariantCandidates(exchange)[0] ?? "";
1987
- let firstEmptyResult: SourceResult<AnalystResearchData> | null = null;
1988
- let firstIncompleteResult: SourceResult<AnalystResearchData> | null = null;
1989
- let lastError: unknown = null;
1990
-
1991
- for (const provider of this.providersInPriorityOrder()) {
1992
- if (!provider.getAnalystResearch) continue;
1993
- try {
1994
- const value = await provider.getAnalystResearch(ticker, exchange, context);
1995
- const sourceKey = this.providerSourceKey(provider);
1996
- this.cacheResource("analystResearch", entityKey, variantKey, sourceKey, value, this.resolveProviderPolicy("analystResearch", provider));
1997
- if (hasAnalystResearchValue(value)) {
1998
- if (isAnalystResearchMissingRatingTargets(value)) {
1999
- firstIncompleteResult ??= { sourceKey, value };
2000
- continue;
2001
- }
2002
- return { sourceKey, value };
2003
- }
2004
- firstEmptyResult ??= { sourceKey, value };
2005
- } catch (error) {
2006
- lastError = error;
2007
- if (shouldLogProviderError(error)) {
2008
- providerLog.error(`${provider.id} failed: ${error}`);
2009
- }
2010
- }
2011
- }
2012
-
2013
- if (firstIncompleteResult) return firstIncompleteResult;
2014
- if (firstEmptyResult) return firstEmptyResult;
2015
- if (lastError) throw lastError;
2016
- return null;
2017
- }
2018
-
2019
- private async fetchProviderCorporateActions(
2020
- ticker: string,
2021
- exchange?: string,
2022
- context?: MarketDataRequestContext,
2023
- ): Promise<SourceResult<CorporateActionsData> | null> {
2024
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
2025
- const variantKey = this.getTickerVariantCandidates(exchange)[0] ?? "";
2026
- let firstEmptyResult: SourceResult<CorporateActionsData> | null = null;
2027
- let lastError: unknown = null;
2028
-
2029
- for (const provider of this.providersInPriorityOrder()) {
2030
- if (!provider.getCorporateActions) continue;
2031
- try {
2032
- const value = await provider.getCorporateActions(ticker, exchange, context);
2033
- const sourceKey = this.providerSourceKey(provider);
2034
- this.cacheResource("corporateActions", entityKey, variantKey, sourceKey, value, this.resolveProviderPolicy("corporateActions", provider));
2035
- if (hasCorporateActionsValue(value)) return { sourceKey, value };
2036
- firstEmptyResult ??= { sourceKey, value };
2037
- } catch (error) {
2038
- lastError = error;
2039
- if (shouldLogProviderError(error)) {
2040
- providerLog.error(`${provider.id} failed: ${error}`);
2041
- }
2042
- }
2043
- }
2044
-
2045
- if (firstEmptyResult) return firstEmptyResult;
2046
- if (lastError) throw lastError;
2047
- return null;
2048
- }
2049
-
2050
- private async fetchProviderSecFilings(
2051
- ticker: string,
2052
- count = 15,
2053
- exchange?: string,
2054
- context?: MarketDataRequestContext,
2055
- ): Promise<SourceResult<SecFilingItem[]> | null> {
2056
- const entityKey = this.getEntityKey(ticker, exchange, context?.instrument);
2057
- const variantKey = buildVariantKey([["exchange", canonicalExchange(exchange)], ["count", count]]);
2058
- let lastError: unknown = null;
2059
-
2060
- for (const provider of this.providersInPriorityOrder()) {
2061
- if (!provider.getSecFilings) continue;
2062
- try {
2063
- const value = await provider.getSecFilings(ticker, count, exchange, context);
2064
- const sourceKey = this.providerSourceKey(provider);
2065
- this.cacheResource("sec-filings", entityKey, variantKey, sourceKey, value, this.resolveProviderPolicy("secFilings", provider));
2066
- return { sourceKey, value };
2067
- } catch (error) {
2068
- lastError = error;
2069
- if (shouldLogProviderError(error)) {
2070
- providerLog.error(`${provider.id} failed: ${error}`);
2071
- }
2072
- }
2073
- }
2074
-
2075
- if (lastError) throw lastError;
2076
- return null;
2077
- }
2078
-
2079
- private async fetchProviderSecFilingContent(filing: SecFilingItem): Promise<SourceResult<string | null> | null> {
2080
- const entityKey = compactUrl(filing.primaryDocumentUrl ?? filing.filingUrl);
2081
- let lastError: unknown = null;
2082
-
2083
- for (const provider of this.providersInPriorityOrder()) {
2084
- if (!provider.getSecFilingContent) continue;
2085
- try {
2086
- const value = await provider.getSecFilingContent(filing);
2087
- const sourceKey = this.providerSourceKey(provider);
2088
- this.cacheResource("sec-filing-content", entityKey, "", sourceKey, value, this.resolveProviderPolicy("secFilingContent", provider));
2089
- return { sourceKey, value };
2090
- } catch (error) {
2091
- lastError = error;
2092
- if (shouldLogProviderError(error)) {
2093
- providerLog.error(`${provider.id} failed: ${error}`);
2094
- }
2095
- }
2096
- }
2097
-
2098
- if (lastError) throw lastError;
2099
- return null;
2100
- }
2101
-
2102
- private resolveProviderBySourceKey(sourceKey: string): DataProvider | null {
2103
- for (const provider of this.providersInPriorityOrder()) {
2104
- if (this.providerSourceKey(provider) === sourceKey) return provider;
2105
- }
2106
- return null;
2107
- }
2108
-
2109
- private async revalidateExchangeRate(fromCurrency: string): Promise<void> {
2110
- const normalizedCurrency = fromCurrency.trim().toUpperCase();
2111
- if (normalizedCurrency === "USD") return;
2112
-
2113
- const result = await this.firstProvider(async (provider) => {
2114
- const rate = await provider.getExchangeRate(normalizedCurrency);
2115
- return { provider, rate };
2116
- });
2117
- if (!result) return;
2118
- this.cacheExchangeRate(normalizedCurrency, result.value.rate, result.value.provider);
2119
- }
2120
-
2121
- private async revalidateSecFilings(ticker: string, count = 15, exchange?: string, context?: MarketDataRequestContext): Promise<void> {
2122
- await this.fetchProviderSecFilings(ticker, count, exchange, context);
2123
- }
2124
-
2125
- private async revalidateHolders(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<void> {
2126
- await this.fetchProviderHolders(ticker, exchange, context);
2127
- }
2128
-
2129
- private async revalidateAnalystResearch(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<void> {
2130
- await this.fetchProviderAnalystResearch(ticker, exchange, context);
2131
- }
2132
-
2133
- private async revalidateCorporateActions(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<void> {
2134
- await this.fetchProviderCorporateActions(ticker, exchange, context);
2135
- }
2136
-
2137
- private async revalidateSecFilingContent(filing: SecFilingItem): Promise<void> {
2138
- await this.fetchProviderSecFilingContent(filing);
2139
- }
2140
-
2141
- private async revalidateArticleSummary(url: string): Promise<void> {
2142
- const result = await this.firstProvider((provider) => provider.getArticleSummary(url));
2143
- if (!result || !result.value) return;
2144
- const provider = this.resolveProviderBySourceKey(result.sourceKey);
2145
- if (provider) {
2146
- this.cacheResource("article-summary", compactUrl(url), "", result.sourceKey, result.value, this.resolveProviderPolicy("articleSummary", provider));
2147
- }
2148
- }
2149
-
2150
- private async revalidateOptionsChain(
2151
- ticker: string,
2152
- exchange?: string,
2153
- expirationDate?: number,
2154
- context?: MarketDataRequestContext,
2155
- ): Promise<void> {
2156
- const brokerChain = await withBrokerTimeout(this.fetchBrokerOptionsChain(ticker, exchange, expirationDate, context));
2157
- if (!brokerChain) {
2158
- await this.fetchProviderOptionsChain(ticker, exchange, expirationDate, context);
2159
- }
2160
- }
2161
-
2162
- subscribeQuotes(
2163
- targets: QuoteSubscriptionTarget[],
2164
- onQuote: (target: QuoteSubscriptionTarget, quote: Quote) => void,
2165
- ): () => void {
2166
- const streamingProvider = this.providersInPriorityOrder().find((provider) => typeof provider.subscribeQuotes === "function") ?? null;
2167
- const brokerGroups = new Map<string, { candidate: BrokerCandidate; targets: QuoteSubscriptionTarget[] }>();
2168
- const providerTargets: QuoteSubscriptionTarget[] = [];
2169
- const addBrokerTarget = (brokerCandidate: BrokerCandidate, target: QuoteSubscriptionTarget) => {
2170
- const key = this.brokerSourceKey(brokerCandidate);
2171
- const group = brokerGroups.get(key) ?? { candidate: brokerCandidate, targets: [] };
2172
- group.targets.push(target);
2173
- brokerGroups.set(key, group);
2174
- };
2175
-
2176
- for (const target of targets) {
2177
- if (target.route === "provider") {
2178
- const brokerCandidate = this.getStreamingBrokerCandidate(target);
2179
- if (streamingProvider) {
2180
- providerTargets.push(target);
2181
- } else if (brokerCandidate) {
2182
- addBrokerTarget(brokerCandidate, target);
2183
- }
2184
- continue;
2185
- }
2186
- const brokerCandidate = this.getStreamingBrokerCandidate(target);
2187
- if (target.route === "broker" && brokerCandidate) {
2188
- addBrokerTarget(brokerCandidate, target);
2189
- continue;
2190
- }
2191
- if (!brokerCandidate || streamingProvider) {
2192
- providerTargets.push(target);
2193
- continue;
2194
- }
2195
-
2196
- addBrokerTarget(brokerCandidate, target);
2197
- }
2198
-
2199
- const unsubscribers: Array<() => void> = [];
2200
-
2201
- for (const { candidate, targets: brokerTargets } of brokerGroups.values()) {
2202
- providerLog.info("Delegating broker quote stream", {
2203
- brokerId: candidate.brokerId,
2204
- brokerInstanceId: candidate.brokerInstanceId,
2205
- targetCount: brokerTargets.length,
2206
- });
2207
- unsubscribers.push(candidate.broker.subscribeQuotes!(candidate.instance, brokerTargets, onQuote));
2208
- }
2209
-
2210
- if (providerTargets.length > 0 && streamingProvider?.subscribeQuotes) {
2211
- providerLog.info("Delegating provider quote stream", {
2212
- providerId: streamingProvider.id,
2213
- targetCount: providerTargets.length,
2214
- });
2215
- unsubscribers.push(streamingProvider.subscribeQuotes(providerTargets, onQuote));
2216
- }
2217
-
2218
- if (unsubscribers.length === 0) {
2219
- providerLog.warn("No provider supports quote streaming", {
2220
- targetCount: targets.length,
2221
- });
2222
- return () => {};
2223
- }
2224
-
2225
- return () => {
2226
- for (const unsubscribe of unsubscribers) {
2227
- unsubscribe();
2228
- }
2229
- };
2230
- }
2231
- }