opencandle 0.5.0 → 0.7.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.
- package/README.md +170 -186
- package/dist/analysts/contracts.d.ts +1 -3
- package/dist/analysts/contracts.js +1 -11
- package/dist/analysts/contracts.js.map +1 -1
- package/dist/analysts/orchestrator.d.ts +1 -3
- package/dist/analysts/orchestrator.js +1 -26
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/cli.js +66 -7
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +13 -3
- package/dist/config.js +25 -5
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infra/cache.d.ts +8 -11
- package/dist/infra/cache.js +17 -15
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/http-client.d.ts +4 -1
- package/dist/infra/http-client.js +59 -6
- package/dist/infra/http-client.js.map +1 -1
- package/dist/infra/index.d.ts +2 -3
- package/dist/infra/index.js +2 -3
- package/dist/infra/index.js.map +1 -1
- package/dist/infra/native-dependencies.js +2 -2
- package/dist/infra/native-dependencies.js.map +1 -1
- package/dist/infra/node-version.js.map +1 -1
- package/dist/infra/opencandle-paths.d.ts +0 -3
- package/dist/infra/opencandle-paths.js +4 -11
- package/dist/infra/opencandle-paths.js.map +1 -1
- package/dist/infra/rate-limiter.js +12 -9
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/market-state/alert-conditions.d.ts +34 -0
- package/dist/market-state/alert-conditions.js +23 -0
- package/dist/market-state/alert-conditions.js.map +1 -0
- package/dist/market-state/alert-runner.d.ts +55 -0
- package/dist/market-state/alert-runner.js +634 -0
- package/dist/market-state/alert-runner.js.map +1 -0
- package/dist/market-state/daily-report.d.ts +26 -0
- package/dist/market-state/daily-report.js +179 -0
- package/dist/market-state/daily-report.js.map +1 -0
- package/dist/market-state/local-automation-service.d.ts +25 -0
- package/dist/market-state/local-automation-service.js +119 -0
- package/dist/market-state/local-automation-service.js.map +1 -0
- package/dist/market-state/notification-delivery.d.ts +14 -0
- package/dist/market-state/notification-delivery.js +139 -0
- package/dist/market-state/notification-delivery.js.map +1 -0
- package/dist/market-state/resolve-for-mutation.d.ts +10 -0
- package/dist/market-state/resolve-for-mutation.js +15 -0
- package/dist/market-state/resolve-for-mutation.js.map +1 -0
- package/dist/market-state/resolve.d.ts +14 -0
- package/dist/market-state/resolve.js +89 -0
- package/dist/market-state/resolve.js.map +1 -0
- package/dist/market-state/service.d.ts +527 -0
- package/dist/market-state/service.js +1099 -0
- package/dist/market-state/service.js.map +1 -0
- package/dist/memory/index.d.ts +7 -7
- package/dist/memory/index.js +6 -6
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/manager.js +11 -11
- package/dist/memory/manager.js.map +1 -1
- package/dist/memory/retrieval.js +7 -4
- package/dist/memory/retrieval.js.map +1 -1
- package/dist/memory/sqlite.js +385 -3
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/memory/storage.js +1 -2
- package/dist/memory/storage.js.map +1 -1
- package/dist/memory/tool-defaults.js +64 -28
- package/dist/memory/tool-defaults.js.map +1 -1
- package/dist/memory/types.js.map +1 -1
- package/dist/monitor.d.ts +2 -0
- package/dist/monitor.js +104 -0
- package/dist/monitor.js.map +1 -0
- package/dist/onboarding/connect.d.ts +2 -2
- package/dist/onboarding/connect.js +13 -8
- package/dist/onboarding/connect.js.map +1 -1
- package/dist/onboarding/credential-interceptor.js +1 -1
- package/dist/onboarding/credential-interceptor.js.map +1 -1
- package/dist/onboarding/degradation-accumulator.js +1 -3
- package/dist/onboarding/degradation-accumulator.js.map +1 -1
- package/dist/onboarding/provider-status.d.ts +48 -0
- package/dist/onboarding/provider-status.js +285 -0
- package/dist/onboarding/provider-status.js.map +1 -0
- package/dist/onboarding/providers.d.ts +85 -8
- package/dist/onboarding/providers.js +83 -18
- package/dist/onboarding/providers.js.map +1 -1
- package/dist/onboarding/state.d.ts +1 -0
- package/dist/onboarding/state.js +5 -0
- package/dist/onboarding/state.js.map +1 -1
- package/dist/onboarding/tool-helpers.js +1 -1
- package/dist/onboarding/tool-helpers.js.map +1 -1
- package/dist/onboarding/tool-tags.d.ts +12 -1
- package/dist/onboarding/tool-tags.js +37 -5
- package/dist/onboarding/tool-tags.js.map +1 -1
- package/dist/onboarding/validation.d.ts +2 -2
- package/dist/onboarding/validation.js +1 -1
- package/dist/onboarding/validation.js.map +1 -1
- package/dist/pi/opencandle-extension.d.ts +8 -0
- package/dist/pi/opencandle-extension.js +502 -42
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session.d.ts +1 -1
- package/dist/pi/session.js +3 -1
- package/dist/pi/session.js.map +1 -1
- package/dist/pi/setup.js +8 -3
- package/dist/pi/setup.js.map +1 -1
- package/dist/pi/tool-adapter.d.ts +4 -1
- package/dist/pi/tool-adapter.js +10 -6
- package/dist/pi/tool-adapter.js.map +1 -1
- package/dist/prompts/context-builder.d.ts +1 -1
- package/dist/prompts/context-builder.js +20 -7
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/prompts/policy-cards.d.ts +1 -1
- package/dist/prompts/policy-cards.js +2 -2
- package/dist/prompts/policy-cards.js.map +1 -1
- package/dist/prompts/sections.d.ts +1 -1
- package/dist/prompts/symbol-preflight.d.ts +20 -0
- package/dist/prompts/symbol-preflight.js +49 -0
- package/dist/prompts/symbol-preflight.js.map +1 -0
- package/dist/prompts/workflow-prompts.d.ts +1 -1
- package/dist/prompts/workflow-prompts.js +54 -16
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/alpha-vantage.d.ts +1 -1
- package/dist/providers/alpha-vantage.js +26 -7
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/coingecko.js +1 -1
- package/dist/providers/coingecko.js.map +1 -1
- package/dist/providers/errors.d.ts +5 -0
- package/dist/providers/errors.js +11 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/exa-search.d.ts +2 -2
- package/dist/providers/exa-search.js +19 -11
- package/dist/providers/exa-search.js.map +1 -1
- package/dist/providers/external-tool-error.d.ts +10 -0
- package/dist/providers/external-tool-error.js +21 -0
- package/dist/providers/external-tool-error.js.map +1 -0
- package/dist/providers/fear-greed.js +1 -1
- package/dist/providers/fear-greed.js.map +1 -1
- package/dist/providers/finnhub.js +3 -5
- package/dist/providers/finnhub.js.map +1 -1
- package/dist/providers/fred.js +2 -2
- package/dist/providers/fred.js.map +1 -1
- package/dist/providers/index.d.ts +7 -6
- package/dist/providers/index.js +6 -5
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/reddit-cli.d.ts +36 -0
- package/dist/providers/reddit-cli.js +201 -0
- package/dist/providers/reddit-cli.js.map +1 -0
- package/dist/providers/reddit.d.ts +1 -1
- package/dist/providers/reddit.js +9 -37
- package/dist/providers/reddit.js.map +1 -1
- package/dist/providers/sec-edgar.d.ts +1 -0
- package/dist/providers/sec-edgar.js +12 -4
- package/dist/providers/sec-edgar.js.map +1 -1
- package/dist/providers/tradingview.d.ts +47 -0
- package/dist/providers/tradingview.js +275 -0
- package/dist/providers/tradingview.js.map +1 -0
- package/dist/providers/twitter-cli.d.ts +40 -0
- package/dist/providers/twitter-cli.js +153 -0
- package/dist/providers/twitter-cli.js.map +1 -0
- package/dist/providers/twitter.d.ts +0 -8
- package/dist/providers/twitter.js +8 -60
- package/dist/providers/twitter.js.map +1 -1
- package/dist/providers/web-search.js +26 -12
- package/dist/providers/web-search.js.map +1 -1
- package/dist/providers/with-fallback.js +4 -2
- package/dist/providers/with-fallback.js.map +1 -1
- package/dist/providers/wrap-provider.d.ts +2 -3
- package/dist/providers/wrap-provider.js +44 -8
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +1 -1
- package/dist/providers/yahoo-finance.js +153 -48
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.d.ts +6 -0
- package/dist/routing/classify-intent.js +78 -7
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/defaults.d.ts +1 -1
- package/dist/routing/entity-extractor.d.ts +1 -0
- package/dist/routing/entity-extractor.js +234 -29
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/fund-symbols.d.ts +2 -0
- package/dist/routing/fund-symbols.js +55 -0
- package/dist/routing/fund-symbols.js.map +1 -0
- package/dist/routing/horizon.d.ts +1 -0
- package/dist/routing/horizon.js +10 -0
- package/dist/routing/horizon.js.map +1 -0
- package/dist/routing/index.d.ts +10 -10
- package/dist/routing/index.js +6 -6
- package/dist/routing/index.js.map +1 -1
- package/dist/routing/planning.d.ts +2 -2
- package/dist/routing/planning.js +65 -34
- package/dist/routing/planning.js.map +1 -1
- package/dist/routing/route-manifest.d.ts +2 -2
- package/dist/routing/route-manifest.js +25 -4
- package/dist/routing/route-manifest.js.map +1 -1
- package/dist/routing/router-llm-client.js.map +1 -1
- package/dist/routing/router-prompt.js +7 -9
- package/dist/routing/router-prompt.js.map +1 -1
- package/dist/routing/router-types.d.ts +1 -0
- package/dist/routing/router.js +137 -22
- package/dist/routing/router.js.map +1 -1
- package/dist/routing/slot-resolver.d.ts +1 -1
- package/dist/routing/slot-resolver.js +2 -4
- package/dist/routing/slot-resolver.js.map +1 -1
- package/dist/routing/symbol-disambiguator.d.ts +11 -0
- package/dist/routing/symbol-disambiguator.js +52 -0
- package/dist/routing/symbol-disambiguator.js.map +1 -0
- package/dist/routing/turn-context.d.ts +1 -1
- package/dist/routing/turn-context.js +1 -1
- package/dist/routing/turn-context.js.map +1 -1
- package/dist/routing/types.d.ts +2 -0
- package/dist/runtime/answer-contracts.d.ts +1 -1
- package/dist/runtime/answer-contracts.js +48 -9
- package/dist/runtime/answer-contracts.js.map +1 -1
- package/dist/runtime/artifact-contracts.js.map +1 -1
- package/dist/runtime/planning-evidence.js +47 -26
- package/dist/runtime/planning-evidence.js.map +1 -1
- package/dist/runtime/prompt-step.d.ts +1 -9
- package/dist/runtime/prompt-step.js +0 -10
- package/dist/runtime/prompt-step.js.map +1 -1
- package/dist/runtime/run-context.d.ts +5 -2
- package/dist/runtime/run-context.js +8 -1
- package/dist/runtime/run-context.js.map +1 -1
- package/dist/runtime/session-coordinator.d.ts +13 -5
- package/dist/runtime/session-coordinator.js +160 -20
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/session-title.d.ts +14 -0
- package/dist/runtime/session-title.js +50 -0
- package/dist/runtime/session-title.js.map +1 -0
- package/dist/runtime/tool-defaults-wrapper.js +7 -5
- package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
- package/dist/runtime/validation.js.map +1 -1
- package/dist/runtime/workflow-events.js.map +1 -1
- package/dist/runtime/workflow-runner.d.ts +3 -3
- package/dist/runtime/workflow-runner.js +1 -1
- package/dist/runtime/workflow-runner.js.map +1 -1
- package/dist/sentiment/adapters/finnhub.d.ts +1 -1
- package/dist/sentiment/adapters/finnhub.js +6 -1
- package/dist/sentiment/adapters/finnhub.js.map +1 -1
- package/dist/sentiment/adapters/reddit.d.ts +2 -2
- package/dist/sentiment/adapters/twitter.d.ts +1 -1
- package/dist/sentiment/adapters/web.d.ts +1 -1
- package/dist/sentiment/index.d.ts +10 -11
- package/dist/sentiment/index.js +10 -20
- package/dist/sentiment/index.js.map +1 -1
- package/dist/sentiment/insights.d.ts +17 -0
- package/dist/sentiment/insights.js +206 -0
- package/dist/sentiment/insights.js.map +1 -0
- package/dist/sentiment/keywords.js +26 -4
- package/dist/sentiment/keywords.js.map +1 -1
- package/dist/sentiment/pipeline.d.ts +2 -2
- package/dist/sentiment/pipeline.js +14 -2
- package/dist/sentiment/pipeline.js.map +1 -1
- package/dist/sentiment/scorer.d.ts +2 -0
- package/dist/sentiment/scorer.js +11 -2
- package/dist/sentiment/scorer.js.map +1 -1
- package/dist/sentiment/store.d.ts +1 -1
- package/dist/sentiment/store.js +1 -1
- package/dist/sentiment/store.js.map +1 -1
- package/dist/sentiment/trends.d.ts +1 -1
- package/dist/sentiment/trends.js.map +1 -1
- package/dist/sentiment/types.d.ts +2 -0
- package/dist/sentiment/types.js.map +1 -1
- package/dist/system-prompt.js +6 -9
- package/dist/system-prompt.js.map +1 -1
- package/dist/tool-kit.d.ts +7 -7
- package/dist/tool-kit.js +4 -4
- package/dist/tool-kit.js.map +1 -1
- package/dist/tools/fundamentals/company-overview.js +11 -6
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.js +18 -9
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +23 -11
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.js +8 -3
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.js +8 -3
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.js +21 -6
- package/dist/tools/fundamentals/sec-filings.js.map +1 -1
- package/dist/tools/index.d.ts +27 -20
- package/dist/tools/index.js +55 -43
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.js +15 -3
- package/dist/tools/interaction/ask-user.js.map +1 -1
- package/dist/tools/macro/fear-greed.js.map +1 -1
- package/dist/tools/macro/fred-data.d.ts +1 -1
- package/dist/tools/macro/fred-data.js +17 -6
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/market/crypto-history.js +3 -1
- package/dist/tools/market/crypto-history.js.map +1 -1
- package/dist/tools/market/crypto-price.js +3 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/screen-stocks.d.ts +18 -0
- package/dist/tools/market/screen-stocks.js +252 -0
- package/dist/tools/market/screen-stocks.js.map +1 -0
- package/dist/tools/market/search-ticker.js +160 -8
- package/dist/tools/market/search-ticker.js.map +1 -1
- package/dist/tools/market/stock-history.d.ts +2 -2
- package/dist/tools/market/stock-history.js +26 -7
- package/dist/tools/market/stock-history.js.map +1 -1
- package/dist/tools/market/stock-quote.js +5 -3
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/greeks.js +1 -1
- package/dist/tools/options/greeks.js.map +1 -1
- package/dist/tools/options/option-chain.js +19 -6
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/alerts.d.ts +15 -0
- package/dist/tools/portfolio/alerts.js +357 -0
- package/dist/tools/portfolio/alerts.js.map +1 -0
- package/dist/tools/portfolio/correlation.d.ts +1 -1
- package/dist/tools/portfolio/correlation.js +33 -13
- package/dist/tools/portfolio/correlation.js.map +1 -1
- package/dist/tools/portfolio/daily-report.d.ts +8 -0
- package/dist/tools/portfolio/daily-report.js +83 -0
- package/dist/tools/portfolio/daily-report.js.map +1 -0
- package/dist/tools/portfolio/holdings-overlap.js +10 -3
- package/dist/tools/portfolio/holdings-overlap.js.map +1 -1
- package/dist/tools/portfolio/notifications.d.ts +7 -0
- package/dist/tools/portfolio/notifications.js +43 -0
- package/dist/tools/portfolio/notifications.js.map +1 -0
- package/dist/tools/portfolio/predictions.d.ts +12 -6
- package/dist/tools/portfolio/predictions.js +337 -87
- package/dist/tools/portfolio/predictions.js.map +1 -1
- package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
- package/dist/tools/portfolio/risk-analysis.js +45 -6
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/portfolio/tracker.d.ts +4 -3
- package/dist/tools/portfolio/tracker.js +246 -101
- package/dist/tools/portfolio/tracker.js.map +1 -1
- package/dist/tools/portfolio/watchlist.d.ts +6 -4
- package/dist/tools/portfolio/watchlist.js +208 -108
- package/dist/tools/portfolio/watchlist.js.map +1 -1
- package/dist/tools/sentiment/insight-format.d.ts +2 -0
- package/dist/tools/sentiment/insight-format.js +36 -0
- package/dist/tools/sentiment/insight-format.js.map +1 -0
- package/dist/tools/sentiment/query-match.d.ts +3 -0
- package/dist/tools/sentiment/query-match.js +113 -0
- package/dist/tools/sentiment/query-match.js.map +1 -0
- package/dist/tools/sentiment/reddit-sentiment.d.ts +12 -1
- package/dist/tools/sentiment/reddit-sentiment.js +266 -107
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.d.ts +9 -1
- package/dist/tools/sentiment/sentiment-summary.js +223 -205
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
- package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
- package/dist/tools/sentiment/sentiment-trend.js +12 -2
- package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
- package/dist/tools/sentiment/twitter-sentiment.d.ts +11 -1
- package/dist/tools/sentiment/twitter-sentiment.js +188 -58
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
- package/dist/tools/sentiment/untrusted-text.js +17 -0
- package/dist/tools/sentiment/untrusted-text.js.map +1 -0
- package/dist/tools/sentiment/web-search.js +9 -13
- package/dist/tools/sentiment/web-search.js.map +1 -1
- package/dist/tools/sentiment/web-sentiment.js +19 -3
- package/dist/tools/sentiment/web-sentiment.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +1 -1
- package/dist/tools/technical/backtest.js +27 -20
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +23 -5
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/market.d.ts +1 -0
- package/dist/types/portfolio.d.ts +14 -4
- package/dist/types/sentiment.d.ts +52 -0
- package/dist/workflows/compare-assets.d.ts +0 -3
- package/dist/workflows/compare-assets.js +20 -11
- package/dist/workflows/compare-assets.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -4
- package/dist/workflows/index.js +3 -3
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/options-screener.d.ts +0 -3
- package/dist/workflows/options-screener.js +4 -11
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.d.ts +0 -3
- package/dist/workflows/portfolio-builder.js +0 -8
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/ask-user-bridge.ts +1 -1
- package/gui/server/automation-heartbeat.ts +97 -0
- package/gui/server/background-quotes.ts +97 -1
- package/gui/server/chat-event-adapter.ts +32 -10
- package/gui/server/chat-run-session.ts +16 -0
- package/gui/server/invoke-tool.ts +160 -3
- package/gui/server/live-chat-event-adapter.ts +21 -6
- package/gui/server/market-state-api.ts +315 -0
- package/gui/server/model-setup.ts +156 -2
- package/gui/server/private-api-access.ts +62 -0
- package/gui/server/projector.ts +18 -9
- package/gui/server/prompt-observation.ts +4 -7
- package/gui/server/quote-snapshot-store.ts +50 -0
- package/gui/server/server.ts +218 -451
- package/gui/server/session-actions.ts +186 -1
- package/gui/server/shutdown.ts +47 -0
- package/gui/server/tool-invoke-ack.ts +49 -0
- package/gui/server/tool-metadata.ts +101 -24
- package/gui/server/websocket.ts +13 -3
- package/gui/server/writer-lock.ts +6 -2
- package/gui/server/ws-hub.ts +311 -0
- package/gui/shared/chat-events.ts +16 -1
- package/gui/shared/event-reducer.ts +24 -6
- package/gui/web/dist/assets/CatalogOverlay-CgeY5Pkp.js +1 -0
- package/gui/web/dist/assets/index-C6W_2eAn.js +69 -0
- package/gui/web/dist/assets/index-hwbx24a5.css +1 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +9 -6
- package/src/analysts/contracts.ts +10 -23
- package/src/analysts/orchestrator.ts +8 -43
- package/src/cli.ts +76 -12
- package/src/config.ts +44 -9
- package/src/index.ts +1 -1
- package/src/infra/cache.ts +41 -30
- package/src/infra/http-client.ts +72 -6
- package/src/infra/index.ts +6 -10
- package/src/infra/native-dependencies.ts +8 -3
- package/src/infra/node-version.ts +3 -1
- package/src/infra/opencandle-paths.ts +3 -14
- package/src/infra/rate-limiter.ts +22 -19
- package/src/market-state/alert-conditions.ts +82 -0
- package/src/market-state/alert-runner.ts +863 -0
- package/src/market-state/daily-report.ts +247 -0
- package/src/market-state/local-automation-service.ts +162 -0
- package/src/market-state/notification-delivery.ts +158 -0
- package/src/market-state/resolve-for-mutation.ts +24 -0
- package/src/market-state/resolve.ts +112 -0
- package/src/market-state/service.ts +2344 -0
- package/src/memory/index.ts +7 -7
- package/src/memory/manager.ts +14 -16
- package/src/memory/retrieval.ts +8 -7
- package/src/memory/sqlite.ts +407 -6
- package/src/memory/storage.ts +5 -15
- package/src/memory/tool-defaults.ts +60 -39
- package/src/memory/types.ts +3 -3
- package/src/monitor.ts +121 -0
- package/src/onboarding/connect.ts +24 -31
- package/src/onboarding/credential-interceptor.ts +3 -15
- package/src/onboarding/degradation-accumulator.ts +1 -3
- package/src/onboarding/provider-status.ts +410 -0
- package/src/onboarding/providers.ts +144 -45
- package/src/onboarding/state.ts +13 -15
- package/src/onboarding/tool-helpers.ts +2 -9
- package/src/onboarding/tool-tags.ts +51 -8
- package/src/onboarding/validation.ts +16 -22
- package/src/pi/opencandle-extension.ts +643 -101
- package/src/pi/session.ts +7 -5
- package/src/pi/setup.ts +61 -43
- package/src/pi/tool-adapter.ts +19 -6
- package/src/prompts/context-builder.ts +24 -13
- package/src/prompts/policy-cards.ts +3 -3
- package/src/prompts/sections.ts +1 -1
- package/src/prompts/symbol-preflight.ts +80 -0
- package/src/prompts/workflow-prompts.ts +77 -28
- package/src/providers/alpha-vantage.ts +58 -39
- package/src/providers/coingecko.ts +2 -5
- package/src/providers/errors.ts +9 -0
- package/src/providers/exa-search.ts +24 -22
- package/src/providers/external-tool-error.ts +20 -0
- package/src/providers/fear-greed.ts +1 -1
- package/src/providers/finnhub.ts +7 -6
- package/src/providers/fred.ts +3 -3
- package/src/providers/index.ts +14 -6
- package/src/providers/reddit-cli.ts +317 -0
- package/src/providers/reddit.ts +14 -59
- package/src/providers/sec-edgar.ts +20 -6
- package/src/providers/tradingview.ts +399 -0
- package/src/providers/twitter-cli.ts +233 -0
- package/src/providers/twitter.ts +8 -79
- package/src/providers/web-search.ts +30 -20
- package/src/providers/with-fallback.ts +8 -7
- package/src/providers/wrap-provider.ts +49 -10
- package/src/providers/yahoo-finance.ts +204 -66
- package/src/routing/classify-intent.ts +101 -10
- package/src/routing/defaults.ts +1 -1
- package/src/routing/entity-extractor.ts +287 -38
- package/src/routing/fund-symbols.ts +58 -0
- package/src/routing/horizon.ts +7 -0
- package/src/routing/index.ts +48 -48
- package/src/routing/planning.ts +145 -53
- package/src/routing/route-manifest.ts +37 -15
- package/src/routing/router-llm-client.ts +4 -4
- package/src/routing/router-prompt.ts +15 -19
- package/src/routing/router-types.ts +2 -5
- package/src/routing/router.ts +251 -53
- package/src/routing/slot-resolver.ts +34 -11
- package/src/routing/symbol-disambiguator.ts +72 -0
- package/src/routing/turn-context.ts +6 -9
- package/src/routing/types.ts +2 -0
- package/src/runtime/answer-contracts.ts +105 -45
- package/src/runtime/artifact-contracts.ts +2 -1
- package/src/runtime/planning-evidence.ts +157 -66
- package/src/runtime/prompt-step.ts +1 -16
- package/src/runtime/run-context.ts +12 -2
- package/src/runtime/session-coordinator.ts +238 -63
- package/src/runtime/session-title.ts +60 -0
- package/src/runtime/tool-defaults-wrapper.ts +13 -5
- package/src/runtime/validation.ts +1 -4
- package/src/runtime/workflow-events.ts +7 -7
- package/src/runtime/workflow-runner.ts +5 -11
- package/src/sentiment/adapters/finnhub.ts +7 -2
- package/src/sentiment/adapters/reddit.ts +2 -2
- package/src/sentiment/adapters/twitter.ts +1 -1
- package/src/sentiment/adapters/web.ts +1 -1
- package/src/sentiment/index.ts +17 -26
- package/src/sentiment/insights.ts +269 -0
- package/src/sentiment/keywords.ts +26 -4
- package/src/sentiment/pipeline.ts +28 -5
- package/src/sentiment/scorer.ts +13 -2
- package/src/sentiment/store.ts +2 -2
- package/src/sentiment/trends.ts +9 -3
- package/src/sentiment/types.ts +8 -4
- package/src/system-prompt.ts +6 -9
- package/src/tool-kit.ts +10 -9
- package/src/tools/fundamentals/company-overview.ts +19 -9
- package/src/tools/fundamentals/comps.ts +68 -55
- package/src/tools/fundamentals/dcf.ts +145 -95
- package/src/tools/fundamentals/earnings.ts +16 -6
- package/src/tools/fundamentals/financials.ts +16 -7
- package/src/tools/fundamentals/sec-filings.ts +37 -16
- package/src/tools/index.ts +56 -43
- package/src/tools/interaction/ask-user.ts +22 -10
- package/src/tools/macro/fear-greed.ts +1 -1
- package/src/tools/macro/fred-data.ts +58 -46
- package/src/tools/market/crypto-history.ts +8 -3
- package/src/tools/market/crypto-price.ts +6 -6
- package/src/tools/market/screen-stocks.ts +279 -0
- package/src/tools/market/search-ticker.ts +218 -17
- package/src/tools/market/stock-history.ts +37 -12
- package/src/tools/market/stock-quote.ts +10 -7
- package/src/tools/options/greeks.ts +5 -5
- package/src/tools/options/option-chain.ts +41 -17
- package/src/tools/portfolio/alerts.ts +457 -0
- package/src/tools/portfolio/correlation.ts +47 -20
- package/src/tools/portfolio/daily-report.ts +101 -0
- package/src/tools/portfolio/holdings-overlap.ts +31 -15
- package/src/tools/portfolio/notifications.ts +45 -0
- package/src/tools/portfolio/predictions.ts +406 -106
- package/src/tools/portfolio/risk-analysis.ts +46 -7
- package/src/tools/portfolio/tracker.ts +270 -109
- package/src/tools/portfolio/watchlist.ts +250 -121
- package/src/tools/sentiment/insight-format.ts +50 -0
- package/src/tools/sentiment/query-match.ts +117 -0
- package/src/tools/sentiment/reddit-sentiment.ts +360 -121
- package/src/tools/sentiment/sentiment-summary.ts +302 -235
- package/src/tools/sentiment/sentiment-trend.ts +24 -7
- package/src/tools/sentiment/twitter-sentiment.ts +264 -73
- package/src/tools/sentiment/untrusted-text.ts +21 -0
- package/src/tools/sentiment/web-search.ts +21 -18
- package/src/tools/sentiment/web-sentiment.ts +30 -10
- package/src/tools/technical/backtest.ts +32 -22
- package/src/tools/technical/indicators.ts +39 -14
- package/src/types/index.ts +8 -3
- package/src/types/market.ts +1 -0
- package/src/types/portfolio.ts +14 -4
- package/src/types/sentiment.ts +61 -2
- package/src/workflows/compare-assets.ts +33 -21
- package/src/workflows/index.ts +3 -4
- package/src/workflows/options-screener.ts +27 -29
- package/src/workflows/portfolio-builder.ts +34 -27
- package/dist/infra/browser.d.ts +0 -35
- package/dist/infra/browser.js +0 -103
- package/dist/infra/browser.js.map +0 -1
- package/dist/tools/interaction/twitter-login.d.ts +0 -8
- package/dist/tools/interaction/twitter-login.js +0 -77
- package/dist/tools/interaction/twitter-login.js.map +0 -1
- package/dist/workflows/types.d.ts +0 -4
- package/dist/workflows/types.js +0 -2
- package/dist/workflows/types.js.map +0 -1
- package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +0 -1
- package/gui/web/dist/assets/index-Bxt9QpLX.css +0 -1
- package/gui/web/dist/assets/index-CZ9DHZYy.js +0 -67
- package/src/infra/browser.ts +0 -111
- package/src/tools/interaction/twitter-login.ts +0 -93
- package/src/workflows/types.ts +0 -4
|
@@ -1,14 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import YahooFinance from "yahoo-finance2";
|
|
2
|
+
import type { OptionsResult as YahooFinance2OptionsResult } from "yahoo-finance2/modules/options";
|
|
3
|
+
import { cache, STALE_LIMIT, TTL } from "../infra/cache.js";
|
|
4
|
+
import { HttpError, httpGet } from "../infra/http-client.js";
|
|
3
5
|
import { rateLimiter } from "../infra/rate-limiter.js";
|
|
4
|
-
import { StealthBrowser } from "../infra/browser.js";
|
|
5
|
-
import type { StockQuote, OHLCV } from "../types/market.js";
|
|
6
|
-
import type { OptionsChain, OptionContract, OptionsMarketSession, OptionsQuoteStatus } from "../types/options.js";
|
|
7
|
-
import type { FundHoldings } from "../types/portfolio.js";
|
|
8
6
|
import { computeGreeks } from "../tools/options/greeks.js";
|
|
7
|
+
import type { OHLCV, StockQuote } from "../types/market.js";
|
|
8
|
+
import type {
|
|
9
|
+
OptionContract,
|
|
10
|
+
OptionsChain,
|
|
11
|
+
OptionsMarketSession,
|
|
12
|
+
OptionsQuoteStatus,
|
|
13
|
+
} from "../types/options.js";
|
|
14
|
+
import type { FundHoldings } from "../types/portfolio.js";
|
|
15
|
+
import { InvalidSymbolError } from "./errors.js";
|
|
9
16
|
|
|
10
17
|
const BASE_URL = "https://query1.finance.yahoo.com/v8/finance/chart";
|
|
11
18
|
const QUOTE_SUMMARY_URL = "https://query1.finance.yahoo.com/v10/finance/quoteSummary";
|
|
19
|
+
const STALE_QUOTE_MAX_RETRY_AFTER_MS = 1_000;
|
|
20
|
+
|
|
21
|
+
let yahooFinance2Client: InstanceType<typeof YahooFinance> | undefined;
|
|
22
|
+
|
|
23
|
+
function getYahooFinance2Client(): InstanceType<typeof YahooFinance> {
|
|
24
|
+
yahooFinance2Client ??= new YahooFinance({
|
|
25
|
+
suppressNotices: ["yahooSurvey", "ripHistorical"],
|
|
26
|
+
});
|
|
27
|
+
return yahooFinance2Client;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type YahooNumber = number | { raw?: number; fmt?: string };
|
|
12
31
|
|
|
13
32
|
interface YahooChartResponse {
|
|
14
33
|
chart: {
|
|
@@ -42,10 +61,10 @@ interface YahooQuoteSummaryResponse {
|
|
|
42
61
|
holdings?: Array<{
|
|
43
62
|
symbol?: string;
|
|
44
63
|
holdingName?: string;
|
|
45
|
-
holdingPercent?:
|
|
64
|
+
holdingPercent?: YahooNumber;
|
|
46
65
|
}>;
|
|
47
66
|
equityHoldings?: {
|
|
48
|
-
sectorWeightings?: Array<Record<string,
|
|
67
|
+
sectorWeightings?: Array<Record<string, YahooNumber>>;
|
|
49
68
|
};
|
|
50
69
|
};
|
|
51
70
|
}>;
|
|
@@ -64,6 +83,7 @@ export async function getQuote(symbol: string): Promise<StockQuote> {
|
|
|
64
83
|
const url = `${BASE_URL}/${encodeURIComponent(symbol)}?interval=1d&range=1d`;
|
|
65
84
|
const data = await httpGet<YahooChartResponse>(url, {
|
|
66
85
|
headers: { "User-Agent": "OpenCandle/1.0" },
|
|
86
|
+
maxRetryAfterMs: STALE_QUOTE_MAX_RETRY_AFTER_MS,
|
|
67
87
|
});
|
|
68
88
|
|
|
69
89
|
if (data.chart.error) {
|
|
@@ -97,8 +117,16 @@ export async function getQuote(symbol: string): Promise<StockQuote> {
|
|
|
97
117
|
week52High: meta.fiftyTwoWeekHigh ?? 0,
|
|
98
118
|
week52Low: meta.fiftyTwoWeekLow ?? 0,
|
|
99
119
|
timestamp: Date.now(),
|
|
120
|
+
currency:
|
|
121
|
+
typeof meta.currency === "string" && meta.currency.trim() !== ""
|
|
122
|
+
? meta.currency.trim().toUpperCase()
|
|
123
|
+
: null,
|
|
100
124
|
};
|
|
101
125
|
|
|
126
|
+
if (isZeroResultQuote(quote)) {
|
|
127
|
+
throw new InvalidSymbolError(symbol.toUpperCase(), "yahoo");
|
|
128
|
+
}
|
|
129
|
+
|
|
102
130
|
cache.set(cacheKey, quote, TTL.QUOTE);
|
|
103
131
|
return quote;
|
|
104
132
|
} catch (error) {
|
|
@@ -108,6 +136,16 @@ export async function getQuote(symbol: string): Promise<StockQuote> {
|
|
|
108
136
|
}
|
|
109
137
|
}
|
|
110
138
|
|
|
139
|
+
function isZeroResultQuote(quote: StockQuote): boolean {
|
|
140
|
+
return (
|
|
141
|
+
quote.price === 0 &&
|
|
142
|
+
quote.volume === 0 &&
|
|
143
|
+
quote.week52High === 0 &&
|
|
144
|
+
quote.week52Low === 0 &&
|
|
145
|
+
quote.marketCap === 0
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
111
149
|
export async function getHistory(
|
|
112
150
|
symbol: string,
|
|
113
151
|
range: string = "6mo",
|
|
@@ -162,14 +200,12 @@ export async function getFundHoldings(symbol: string): Promise<FundHoldings> {
|
|
|
162
200
|
try {
|
|
163
201
|
await rateLimiter.acquire("yahoo");
|
|
164
202
|
|
|
165
|
-
const
|
|
166
|
-
const url = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(normalizedSymbol)}?modules=${modules}`;
|
|
167
|
-
const data = await httpGet<YahooQuoteSummaryResponse>(url, {
|
|
168
|
-
headers: { "User-Agent": "OpenCandle/1.0" },
|
|
169
|
-
});
|
|
203
|
+
const data = await getFundHoldingsSummary(normalizedSymbol);
|
|
170
204
|
const result = data.quoteSummary.result?.[0];
|
|
171
205
|
if (data.quoteSummary.error) {
|
|
172
|
-
throw new Error(
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Yahoo Finance: ${data.quoteSummary.error.description ?? data.quoteSummary.error.code ?? "quoteSummary error"}`,
|
|
208
|
+
);
|
|
173
209
|
}
|
|
174
210
|
if (!result?.topHoldings?.holdings?.length) {
|
|
175
211
|
throw new Error(`Yahoo Finance: no fund holdings returned for ${normalizedSymbol}`);
|
|
@@ -183,11 +219,13 @@ export async function getFundHoldings(symbol: string): Promise<FundHoldings> {
|
|
|
183
219
|
const holdingSymbol = holding.symbol?.trim().toUpperCase();
|
|
184
220
|
const weight = normalizeHoldingWeight(holding.holdingPercent);
|
|
185
221
|
if (!holdingSymbol || weight === undefined) return [];
|
|
186
|
-
return [
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
222
|
+
return [
|
|
223
|
+
{
|
|
224
|
+
symbol: holdingSymbol,
|
|
225
|
+
name: holding.holdingName?.trim() || holdingSymbol,
|
|
226
|
+
weight,
|
|
227
|
+
},
|
|
228
|
+
];
|
|
191
229
|
}),
|
|
192
230
|
sectorWeights: normalizeSectorWeights(result.topHoldings.equityHoldings?.sectorWeightings),
|
|
193
231
|
};
|
|
@@ -204,13 +242,56 @@ export async function getFundHoldings(symbol: string): Promise<FundHoldings> {
|
|
|
204
242
|
}
|
|
205
243
|
}
|
|
206
244
|
|
|
207
|
-
function
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
async function getFundHoldingsSummary(symbol: string): Promise<YahooQuoteSummaryResponse> {
|
|
246
|
+
try {
|
|
247
|
+
return await fetchFundHoldingsSummary(symbol);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (!isYahooAuthError(error)) throw error;
|
|
250
|
+
return fetchFundHoldingsSummaryWithCrumb(symbol);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function fetchFundHoldingsSummary(symbol: string): Promise<YahooQuoteSummaryResponse> {
|
|
255
|
+
const modules = encodeURIComponent("price,topHoldings");
|
|
256
|
+
const url = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${modules}`;
|
|
257
|
+
return httpGet<YahooQuoteSummaryResponse>(url, {
|
|
258
|
+
headers: { "User-Agent": "OpenCandle/1.0" },
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function fetchFundHoldingsSummaryWithCrumb(
|
|
263
|
+
symbol: string,
|
|
264
|
+
): Promise<YahooQuoteSummaryResponse> {
|
|
265
|
+
const modules = encodeURIComponent("price,topHoldings");
|
|
266
|
+
const { crumb, cookie } = await getYahooCrumb();
|
|
267
|
+
const url = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${modules}&crumb=${encodeURIComponent(crumb)}`;
|
|
268
|
+
try {
|
|
269
|
+
return await httpGet<YahooQuoteSummaryResponse>(url, {
|
|
270
|
+
headers: { "User-Agent": BROWSER_UA, Cookie: cookie },
|
|
271
|
+
});
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (!isYahooAuthError(error)) throw error;
|
|
274
|
+
clearCrumbCache();
|
|
275
|
+
const fresh = await getYahooCrumb();
|
|
276
|
+
const retryUrl = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${modules}&crumb=${encodeURIComponent(fresh.crumb)}`;
|
|
277
|
+
return httpGet<YahooQuoteSummaryResponse>(retryUrl, {
|
|
278
|
+
headers: { "User-Agent": BROWSER_UA, Cookie: fresh.cookie },
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function isYahooAuthError(error: unknown): boolean {
|
|
284
|
+
return error instanceof HttpError && (error.status === 401 || error.status === 429);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function normalizeHoldingWeight(value: YahooNumber | undefined): number | undefined {
|
|
288
|
+
const numeric = typeof value === "number" ? value : value?.raw;
|
|
289
|
+
if (numeric === undefined || !Number.isFinite(numeric) || numeric <= 0) return undefined;
|
|
290
|
+
return numeric > 1 ? roundWeight(numeric / 100) : roundWeight(numeric);
|
|
210
291
|
}
|
|
211
292
|
|
|
212
293
|
function normalizeSectorWeights(
|
|
213
|
-
sectors: Array<Record<string,
|
|
294
|
+
sectors: Array<Record<string, YahooNumber>> | undefined,
|
|
214
295
|
): Record<string, number> | undefined {
|
|
215
296
|
if (!sectors?.length) return undefined;
|
|
216
297
|
const weights: Record<string, number> = {};
|
|
@@ -231,6 +312,7 @@ function roundWeight(value: number): number {
|
|
|
231
312
|
|
|
232
313
|
const BROWSER_UA =
|
|
233
314
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
315
|
+
const YAHOO_RAW_FETCH_TIMEOUT_MS = 10_000;
|
|
234
316
|
|
|
235
317
|
let cachedCrumb: { crumb: string; cookie: string; expiresAt: number } | null = null;
|
|
236
318
|
|
|
@@ -246,15 +328,30 @@ export async function getYahooCrumb(): Promise<{ crumb: string; cookie: string }
|
|
|
246
328
|
// Step 1: Hit fc.yahoo.com to get a session cookie
|
|
247
329
|
const cookieRes = await fetch("https://fc.yahoo.com/t", {
|
|
248
330
|
headers: { "User-Agent": BROWSER_UA },
|
|
331
|
+
signal: yahooRawFetchSignal(),
|
|
249
332
|
});
|
|
250
333
|
const setCookie = cookieRes.headers.get("set-cookie") ?? "";
|
|
251
334
|
const cookie = setCookie.split(";")[0]; // Extract just the cookie value
|
|
335
|
+
if (!cookie) {
|
|
336
|
+
if (!cookieRes.ok) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`Yahoo crumb cookie request failed: HTTP ${cookieRes.status} ${cookieRes.statusText}`.trim(),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
throw new Error("Yahoo crumb cookie request did not return a session cookie");
|
|
342
|
+
}
|
|
252
343
|
|
|
253
344
|
// Step 2: Use the cookie to get a crumb
|
|
254
345
|
const crumbRes = await fetch("https://query2.finance.yahoo.com/v1/test/getcrumb", {
|
|
255
346
|
headers: { "User-Agent": BROWSER_UA, Cookie: cookie },
|
|
347
|
+
signal: yahooRawFetchSignal(),
|
|
256
348
|
});
|
|
257
|
-
|
|
349
|
+
if (!crumbRes.ok) {
|
|
350
|
+
throw new Error(
|
|
351
|
+
`Yahoo crumb request failed: HTTP ${crumbRes.status} ${crumbRes.statusText}`.trim(),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
const crumb = (await crumbRes.text()).trim();
|
|
258
355
|
|
|
259
356
|
if (!crumb || crumb.includes("Unauthorized")) {
|
|
260
357
|
throw new Error("Failed to acquire Yahoo Finance crumb");
|
|
@@ -281,25 +378,23 @@ interface YahooOptionsResponse {
|
|
|
281
378
|
};
|
|
282
379
|
}
|
|
283
380
|
|
|
284
|
-
export async function getOptionsChain(
|
|
285
|
-
symbol: string,
|
|
286
|
-
expiration?: number,
|
|
287
|
-
): Promise<OptionsChain> {
|
|
381
|
+
export async function getOptionsChain(symbol: string, expiration?: number): Promise<OptionsChain> {
|
|
288
382
|
const cacheKey = `yahoo:options:${symbol}:${expiration ?? "nearest"}`;
|
|
289
383
|
const cached = cache.get<OptionsChain>(cacheKey);
|
|
290
384
|
if (cached) return cached;
|
|
291
385
|
|
|
292
386
|
await rateLimiter.acquire("yahoo");
|
|
293
387
|
|
|
294
|
-
const { crumb, cookie } = await getYahooCrumb();
|
|
295
388
|
const dateParam = expiration ? `&date=${expiration}` : "";
|
|
296
|
-
const url = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}?crumb=${encodeURIComponent(crumb)}${dateParam}`;
|
|
297
389
|
|
|
298
390
|
let res: Response | null = null;
|
|
299
391
|
let fetchError: unknown;
|
|
300
392
|
try {
|
|
393
|
+
const { crumb, cookie } = await getYahooCrumb();
|
|
394
|
+
const url = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}?crumb=${encodeURIComponent(crumb)}${dateParam}`;
|
|
301
395
|
res = await fetch(url, {
|
|
302
396
|
headers: { "User-Agent": BROWSER_UA, Cookie: cookie },
|
|
397
|
+
signal: yahooRawFetchSignal(),
|
|
303
398
|
});
|
|
304
399
|
} catch (error) {
|
|
305
400
|
fetchError = error;
|
|
@@ -313,6 +408,7 @@ export async function getOptionsChain(
|
|
|
313
408
|
const retryUrl = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}?crumb=${encodeURIComponent(fresh.crumb)}${dateParam}`;
|
|
314
409
|
res = await fetch(retryUrl, {
|
|
315
410
|
headers: { "User-Agent": BROWSER_UA, Cookie: fresh.cookie },
|
|
411
|
+
signal: yahooRawFetchSignal(),
|
|
316
412
|
});
|
|
317
413
|
} catch (error) {
|
|
318
414
|
fetchError = error;
|
|
@@ -324,9 +420,9 @@ export async function getOptionsChain(
|
|
|
324
420
|
if (!res?.ok) {
|
|
325
421
|
let browserError: unknown;
|
|
326
422
|
try {
|
|
327
|
-
const
|
|
328
|
-
if (
|
|
329
|
-
const chain = parseOptionsResponse(
|
|
423
|
+
const fallbackData = await fetchOptionsViaYahooFinance2(symbol, expiration);
|
|
424
|
+
if (fallbackData) {
|
|
425
|
+
const chain = parseOptionsResponse(fallbackData);
|
|
330
426
|
cache.set(cacheKey, chain, TTL.OPTIONS_CHAIN);
|
|
331
427
|
return chain;
|
|
332
428
|
}
|
|
@@ -339,15 +435,18 @@ export async function getOptionsChain(
|
|
|
339
435
|
if (res) {
|
|
340
436
|
const message = `Yahoo Finance options: HTTP ${res.status}`;
|
|
341
437
|
if (browserError instanceof Error) {
|
|
342
|
-
throw new Error(`${message};
|
|
438
|
+
throw new Error(`${message}; yahoo-finance2 fallback failed: ${browserError.message}`);
|
|
343
439
|
}
|
|
344
440
|
throw new Error(message);
|
|
345
441
|
}
|
|
346
442
|
if (browserError instanceof Error) {
|
|
347
|
-
const message =
|
|
348
|
-
|
|
443
|
+
const message =
|
|
444
|
+
fetchError instanceof Error ? fetchError.message : "Yahoo Finance options: fetch failed";
|
|
445
|
+
throw new Error(`${message}; yahoo-finance2 fallback failed: ${browserError.message}`);
|
|
349
446
|
}
|
|
350
|
-
throw fetchError instanceof Error
|
|
447
|
+
throw fetchError instanceof Error
|
|
448
|
+
? fetchError
|
|
449
|
+
: new Error("Yahoo Finance options: fetch failed");
|
|
351
450
|
}
|
|
352
451
|
|
|
353
452
|
const data: YahooOptionsResponse = await res.json();
|
|
@@ -356,6 +455,10 @@ export async function getOptionsChain(
|
|
|
356
455
|
return chain;
|
|
357
456
|
}
|
|
358
457
|
|
|
458
|
+
function yahooRawFetchSignal(): AbortSignal {
|
|
459
|
+
return AbortSignal.timeout(YAHOO_RAW_FETCH_TIMEOUT_MS);
|
|
460
|
+
}
|
|
461
|
+
|
|
359
462
|
/**
|
|
360
463
|
* Compute time to expiry in years from a Yahoo expiration timestamp (midnight UTC).
|
|
361
464
|
* US equity options expire at 4:00 PM ET. During EDT that is 20:00 UTC.
|
|
@@ -364,7 +467,7 @@ export async function getOptionsChain(
|
|
|
364
467
|
*/
|
|
365
468
|
export function computeTimeToExpiry(expirationTs: number, nowMs: number = Date.now()): number {
|
|
366
469
|
const MARKET_CLOSE_OFFSET_S = 21 * 3600; // 21:00 UTC ≈ 4 PM ET
|
|
367
|
-
const MIN_TIME_YEARS = 1 / (365 * 24);
|
|
470
|
+
const MIN_TIME_YEARS = 1 / (365 * 24); // ~1 hour floor
|
|
368
471
|
const SECONDS_PER_YEAR = 365 * 24 * 3600;
|
|
369
472
|
|
|
370
473
|
const expiryCloseTs = expirationTs + MARKET_CLOSE_OFFSET_S;
|
|
@@ -449,6 +552,9 @@ function parseOptionsResponse(data: YahooOptionsResponse): OptionsChain {
|
|
|
449
552
|
const quote = result.quote;
|
|
450
553
|
const underlyingPrice = quote.regularMarketPrice ?? 0;
|
|
451
554
|
const opts = result.options[0];
|
|
555
|
+
if (!opts && underlyingPrice === 0) {
|
|
556
|
+
throw new InvalidSymbolError(result.underlyingSymbol, "yahoo");
|
|
557
|
+
}
|
|
452
558
|
const riskFreeRate = 0.05;
|
|
453
559
|
|
|
454
560
|
const expirationTs = opts.expirationDate;
|
|
@@ -458,7 +564,14 @@ function parseOptionsResponse(data: YahooOptionsResponse): OptionsChain {
|
|
|
458
564
|
const mapContract = (c: any, type: "call" | "put"): OptionContract => {
|
|
459
565
|
const strike = c.strike ?? c.strike?.raw ?? 0;
|
|
460
566
|
const iv = c.impliedVolatility ?? c.impliedVolatility?.raw ?? 0;
|
|
461
|
-
const greeks = computeGreeks({
|
|
567
|
+
const greeks = computeGreeks({
|
|
568
|
+
type,
|
|
569
|
+
spot: underlyingPrice,
|
|
570
|
+
strike,
|
|
571
|
+
timeYears,
|
|
572
|
+
iv,
|
|
573
|
+
riskFreeRate,
|
|
574
|
+
});
|
|
462
575
|
return {
|
|
463
576
|
contractSymbol: c.contractSymbol ?? "",
|
|
464
577
|
type,
|
|
@@ -485,7 +598,9 @@ function parseOptionsResponse(data: YahooOptionsResponse): OptionsChain {
|
|
|
485
598
|
symbol: result.underlyingSymbol,
|
|
486
599
|
underlyingPrice,
|
|
487
600
|
expirationDate,
|
|
488
|
-
expirationDates: result.expirationDates.map(
|
|
601
|
+
expirationDates: result.expirationDates.map(
|
|
602
|
+
(ts) => new Date(ts * 1000).toISOString().split("T")[0],
|
|
603
|
+
),
|
|
489
604
|
calls,
|
|
490
605
|
puts,
|
|
491
606
|
totalCallVolume,
|
|
@@ -497,38 +612,61 @@ function parseOptionsResponse(data: YahooOptionsResponse): OptionsChain {
|
|
|
497
612
|
}
|
|
498
613
|
|
|
499
614
|
/**
|
|
500
|
-
* Fallback: fetch options data via
|
|
501
|
-
* Bypasses Yahoo's TLS fingerprinting and rate limiting.
|
|
615
|
+
* Fallback: fetch options data via yahoo-finance2 when the raw Yahoo path is blocked.
|
|
502
616
|
*/
|
|
503
|
-
async function
|
|
617
|
+
async function fetchOptionsViaYahooFinance2(
|
|
504
618
|
symbol: string,
|
|
505
619
|
expiration?: number,
|
|
506
620
|
): Promise<YahooOptionsResponse | null> {
|
|
507
621
|
try {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return await StealthBrowser.run(async (page) => {
|
|
514
|
-
await page.goto("https://query2.finance.yahoo.com/v1/test/getcrumb", {
|
|
515
|
-
waitUntil: "domcontentloaded",
|
|
516
|
-
timeout: 15000,
|
|
517
|
-
});
|
|
518
|
-
const crumb = (await page.locator("body").innerText()).trim();
|
|
519
|
-
if (!crumb) return null;
|
|
520
|
-
|
|
521
|
-
const url = `https://query1.finance.yahoo.com/v7/finance/options/${encodeURIComponent(symbol)}?crumb=${encodeURIComponent(crumb)}${dateParam}`;
|
|
522
|
-
const response = await page.goto(url, {
|
|
523
|
-
waitUntil: "domcontentloaded",
|
|
524
|
-
timeout: 15000,
|
|
525
|
-
});
|
|
526
|
-
if (!response?.ok()) return null;
|
|
527
|
-
|
|
528
|
-
const text = (await page.locator("body").innerText()).trim();
|
|
529
|
-
return JSON.parse(text) as YahooOptionsResponse;
|
|
530
|
-
});
|
|
622
|
+
const result = await getYahooFinance2Client().options(
|
|
623
|
+
symbol,
|
|
624
|
+
expiration ? { date: new Date(expiration * 1000) } : undefined,
|
|
625
|
+
);
|
|
626
|
+
return normalizeYahooFinance2OptionsResponse(result);
|
|
531
627
|
} catch (error) {
|
|
532
628
|
throw error instanceof Error ? error : new Error(String(error));
|
|
533
629
|
}
|
|
534
630
|
}
|
|
631
|
+
|
|
632
|
+
function normalizeYahooFinance2OptionsResponse(
|
|
633
|
+
data: YahooFinance2OptionsResult | YahooOptionsResponse,
|
|
634
|
+
): YahooOptionsResponse {
|
|
635
|
+
if ("optionChain" in data) return data as YahooOptionsResponse;
|
|
636
|
+
|
|
637
|
+
const options = data.options.map((option) => ({
|
|
638
|
+
expirationDate: toYahooUnixSeconds(option.expirationDate),
|
|
639
|
+
calls: option.calls,
|
|
640
|
+
puts: option.puts,
|
|
641
|
+
}));
|
|
642
|
+
const strikes = [
|
|
643
|
+
...new Set(
|
|
644
|
+
options
|
|
645
|
+
.flatMap((option) => [...option.calls, ...option.puts])
|
|
646
|
+
.map((contract) => Number((contract as { strike?: unknown }).strike))
|
|
647
|
+
.filter((strike) => Number.isFinite(strike)),
|
|
648
|
+
),
|
|
649
|
+
].sort((a, b) => a - b);
|
|
650
|
+
|
|
651
|
+
return {
|
|
652
|
+
optionChain: {
|
|
653
|
+
result: [
|
|
654
|
+
{
|
|
655
|
+
underlyingSymbol: data.underlyingSymbol,
|
|
656
|
+
expirationDates: data.expirationDates.map(toYahooUnixSeconds),
|
|
657
|
+
strikes,
|
|
658
|
+
quote: data.quote as Record<string, any>,
|
|
659
|
+
options,
|
|
660
|
+
},
|
|
661
|
+
],
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function toYahooUnixSeconds(value: Date | number | string): number {
|
|
667
|
+
if (value instanceof Date) return Math.floor(value.getTime() / 1000);
|
|
668
|
+
if (typeof value === "number") {
|
|
669
|
+
return value > 1_000_000_000_000 ? Math.floor(value / 1000) : value;
|
|
670
|
+
}
|
|
671
|
+
return Math.floor(new Date(value).getTime() / 1000);
|
|
672
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ClassificationResult, WorkflowType, ExtractedEntities } from "./types.js";
|
|
2
1
|
import { extractEntities } from "./entity-extractor.js";
|
|
2
|
+
import type { ClassificationResult, ExtractedEntities, WorkflowType } from "./types.js";
|
|
3
3
|
|
|
4
4
|
interface Rule {
|
|
5
5
|
workflow: WorkflowType;
|
|
@@ -43,7 +43,9 @@ const RULES: Rule[] = [
|
|
|
43
43
|
return (
|
|
44
44
|
entities.symbols.length >= 1 &&
|
|
45
45
|
(/\bi\s+own\b/.test(lower) || /\bmy\s+holdings\b/.test(lower)) &&
|
|
46
|
-
(/\bportfolio\s+risk\b/.test(lower) ||
|
|
46
|
+
(/\bportfolio\s+risk\b/.test(lower) ||
|
|
47
|
+
/\bbiggest\s+risk\b/.test(lower) ||
|
|
48
|
+
/\bconcentration\b/.test(lower))
|
|
47
49
|
);
|
|
48
50
|
},
|
|
49
51
|
},
|
|
@@ -91,9 +93,7 @@ const RULES: Rule[] = [
|
|
|
91
93
|
if (hasCompareKeywords && entities.symbols.length >= 2) return false;
|
|
92
94
|
|
|
93
95
|
return (
|
|
94
|
-
/\bbacktest\b/.test(lower) ||
|
|
95
|
-
/\bsentiment\b/.test(lower) ||
|
|
96
|
-
/\brate\s+cuts?\b/.test(lower)
|
|
96
|
+
/\bbacktest\b/.test(lower) || /\bsentiment\b/.test(lower) || /\brate\s+cuts?\b/.test(lower)
|
|
97
97
|
);
|
|
98
98
|
},
|
|
99
99
|
},
|
|
@@ -145,6 +145,14 @@ const RULES: Rule[] = [
|
|
|
145
145
|
return hasOptionKeywords && entities.symbols.length >= 1;
|
|
146
146
|
},
|
|
147
147
|
},
|
|
148
|
+
// Stateful portfolio/watchlist/alert/prediction mutations must not be
|
|
149
|
+
// mistaken for compare or portfolio-construction workflows just because a
|
|
150
|
+
// cost basis, target, or currency token is present.
|
|
151
|
+
{
|
|
152
|
+
workflow: "watchlist_or_tracking",
|
|
153
|
+
confidence: 0.95,
|
|
154
|
+
test: (input) => isStatefulTrackingRequest(input),
|
|
155
|
+
},
|
|
148
156
|
// Compare: keyword + 2+ symbols (uppercase)
|
|
149
157
|
{
|
|
150
158
|
workflow: "compare_assets",
|
|
@@ -165,8 +173,10 @@ const RULES: Rule[] = [
|
|
|
165
173
|
confidence: 0.85,
|
|
166
174
|
test: (input, entities) => {
|
|
167
175
|
const lower = input.toLowerCase();
|
|
168
|
-
return
|
|
169
|
-
|
|
176
|
+
return (
|
|
177
|
+
entities.symbols.length >= 2 &&
|
|
178
|
+
/\bcompare\s+[a-z]{1,5}\b(?:\s*,?\s*(?:and\s+)?[a-z]{1,5}\b)+/.test(lower)
|
|
179
|
+
);
|
|
170
180
|
},
|
|
171
181
|
},
|
|
172
182
|
// Compare: 2+ uppercase symbols without explicit keyword
|
|
@@ -275,11 +285,92 @@ export function classifyIntent(input: string): ClassificationResult {
|
|
|
275
285
|
function isPortfolioEvaluationRequest(input: string): boolean {
|
|
276
286
|
const lower = input.toLowerCase();
|
|
277
287
|
const hasEvaluationIntent =
|
|
278
|
-
/\b(?:evaluat(?:e|ion)|review|assess|analy[sz]e|prospects?|risks?|opportunities?|mitigat(?:e|ion)|adjustment)\b/.test(
|
|
288
|
+
/\b(?:evaluat(?:e|ion)|review|assess|analy[sz]e|prospects?|risks?|opportunities?|mitigat(?:e|ion)|adjustment)\b/.test(
|
|
289
|
+
lower,
|
|
290
|
+
);
|
|
279
291
|
const hasPortfolioObject =
|
|
280
|
-
/\b(?:portfolio|allocation|asset\s+allocation|60\/40|equity|fixed\s+income|bonds?)\b/.test(
|
|
292
|
+
/\b(?:portfolio|allocation|asset\s+allocation|60\/40|equity|fixed\s+income|bonds?)\b/.test(
|
|
293
|
+
lower,
|
|
294
|
+
);
|
|
281
295
|
const hasConstructionIntent =
|
|
282
296
|
/\b(?:build|create|construct|put\s+together|invest|allocate)\b/.test(lower) &&
|
|
283
|
-
|
|
297
|
+
/\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower);
|
|
284
298
|
return hasEvaluationIntent && hasPortfolioObject && !hasConstructionIntent;
|
|
285
299
|
}
|
|
300
|
+
|
|
301
|
+
function isStatefulTrackingRequest(input: string): boolean {
|
|
302
|
+
const lower = input.toLowerCase();
|
|
303
|
+
const hasPortfolioConstructionIntent =
|
|
304
|
+
/\b(?:build|create|construct|put\s+together)\b/.test(lower) &&
|
|
305
|
+
/\bportfolio\b/.test(lower) &&
|
|
306
|
+
/\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower);
|
|
307
|
+
const hasStateVerb =
|
|
308
|
+
/\b(?:add|remove|update|record|track|create|configure|check|show|list|view|cancel)\b/.test(
|
|
309
|
+
lower,
|
|
310
|
+
);
|
|
311
|
+
const hasStateObject =
|
|
312
|
+
/\b(?:watchlist|portfolio|holding|holdings|position|positions|prediction|predictions|alert|alerts|daily\s+report|watchlist\s+report|report\s+history)\b/.test(
|
|
313
|
+
lower,
|
|
314
|
+
);
|
|
315
|
+
const hasPortfolioLotShape =
|
|
316
|
+
/\b(?:add|record|track)\b/.test(lower) &&
|
|
317
|
+
/\b\d+(?:\.\d+)?\s+shares?\b/.test(lower) &&
|
|
318
|
+
/\b(?:portfolio|holding|holdings|position|positions)\b/.test(lower);
|
|
319
|
+
if (hasPortfolioConstructionIntent) return false;
|
|
320
|
+
return (hasStateVerb && hasStateObject) || hasPortfolioLotShape;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const FINANCE_SIGNAL_TERMS = [
|
|
324
|
+
"stock",
|
|
325
|
+
"stocks",
|
|
326
|
+
"shares",
|
|
327
|
+
"ticker",
|
|
328
|
+
"tickers",
|
|
329
|
+
"etf",
|
|
330
|
+
"etfs",
|
|
331
|
+
"ipo",
|
|
332
|
+
"earnings",
|
|
333
|
+
"dividend",
|
|
334
|
+
"dividends",
|
|
335
|
+
"valuation",
|
|
336
|
+
"stock market",
|
|
337
|
+
"invest",
|
|
338
|
+
"investing",
|
|
339
|
+
"investment",
|
|
340
|
+
"portfolio",
|
|
341
|
+
"watchlist",
|
|
342
|
+
"bond",
|
|
343
|
+
"bonds",
|
|
344
|
+
"bond yield",
|
|
345
|
+
"treasury",
|
|
346
|
+
"the fed",
|
|
347
|
+
"inflation",
|
|
348
|
+
"interest rates",
|
|
349
|
+
"crypto",
|
|
350
|
+
"bitcoin",
|
|
351
|
+
"ethereum",
|
|
352
|
+
"options chain",
|
|
353
|
+
"covered call",
|
|
354
|
+
"puts",
|
|
355
|
+
"bullish",
|
|
356
|
+
"bearish",
|
|
357
|
+
"hedge",
|
|
358
|
+
"price target",
|
|
359
|
+
"cost basis",
|
|
360
|
+
"nasdaq",
|
|
361
|
+
"s&p",
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
const FINANCE_SIGNAL_PATTERN = new RegExp(
|
|
365
|
+
`\\b(?:${FINANCE_SIGNAL_TERMS.map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`,
|
|
366
|
+
"i",
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Deterministic finance-vocabulary check for rules-mode fallback turns whose
|
|
371
|
+
* intent did not match a workflow and whose entities carry no symbols (for
|
|
372
|
+
* example theme prompts about private companies or sectors).
|
|
373
|
+
*/
|
|
374
|
+
export function hasFinanceSignals(input: string): boolean {
|
|
375
|
+
return FINANCE_SIGNAL_PATTERN.test(input);
|
|
376
|
+
}
|
package/src/routing/defaults.ts
CHANGED