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
package/src/routing/router.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
extractEntities,
|
|
3
|
+
isAmbiguousConceptUsage,
|
|
4
|
+
isCurrencyCodeUsage,
|
|
5
|
+
} from "./entity-extractor.js";
|
|
2
6
|
import { classifyWithLegacyRules } from "./legacy-rule-router.js";
|
|
3
|
-
import { buildRouterPrompt } from "./router-prompt.js";
|
|
4
7
|
import {
|
|
5
8
|
computeMissingRequiredSlots,
|
|
6
9
|
isDispatchableWorkflow,
|
|
@@ -10,6 +13,7 @@ import {
|
|
|
10
13
|
routeKindFromLegacyRoute,
|
|
11
14
|
selectToolBundles,
|
|
12
15
|
} from "./route-manifest.js";
|
|
16
|
+
import { buildRouterPrompt } from "./router-prompt.js";
|
|
13
17
|
import type {
|
|
14
18
|
RouterDiagnostic,
|
|
15
19
|
RouterInputContext,
|
|
@@ -21,6 +25,7 @@ import type {
|
|
|
21
25
|
RouterSlot,
|
|
22
26
|
ToolBundleName,
|
|
23
27
|
} from "./router-types.js";
|
|
28
|
+
import { disambiguateSymbols } from "./symbol-disambiguator.js";
|
|
24
29
|
import type { ExtractedEntities, WorkflowType } from "./types.js";
|
|
25
30
|
|
|
26
31
|
const VALID_ROUTES: readonly RouterRoute[] = ["workflow", "fallback"];
|
|
@@ -105,11 +110,19 @@ export function validateRouterOutput(raw: string): RouterOutput {
|
|
|
105
110
|
: routeKindFromLegacyRoute(route, rawMissingRequired);
|
|
106
111
|
|
|
107
112
|
if (route === "workflow" || routeKind === "workflow_dispatch") {
|
|
108
|
-
if (
|
|
109
|
-
|
|
113
|
+
if (
|
|
114
|
+
typeof obj.workflow !== "string" ||
|
|
115
|
+
!VALID_WORKFLOWS.includes(obj.workflow as Exclude<WorkflowType, "unclassified">)
|
|
116
|
+
) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`workflow route requires a valid workflow; got ${JSON.stringify(obj.workflow)}`,
|
|
119
|
+
);
|
|
110
120
|
}
|
|
111
121
|
workflow = obj.workflow as Exclude<WorkflowType, "unclassified">;
|
|
112
|
-
} else if (
|
|
122
|
+
} else if (
|
|
123
|
+
typeof obj.workflow === "string" &&
|
|
124
|
+
VALID_WORKFLOWS.includes(obj.workflow as Exclude<WorkflowType, "unclassified">)
|
|
125
|
+
) {
|
|
113
126
|
workflow = obj.workflow as Exclude<WorkflowType, "unclassified">;
|
|
114
127
|
}
|
|
115
128
|
|
|
@@ -119,8 +132,7 @@ export function validateRouterOutput(raw: string): RouterOutput {
|
|
|
119
132
|
const missing_required = rawMissingRequired;
|
|
120
133
|
const tool_bundles = validateToolBundles(obj.tool_bundles);
|
|
121
134
|
const diagnostics = validateDiagnostics(obj.diagnostics);
|
|
122
|
-
const reasoning =
|
|
123
|
-
typeof obj.reasoning === "string" ? obj.reasoning : "";
|
|
135
|
+
const reasoning = typeof obj.reasoning === "string" ? obj.reasoning : "";
|
|
124
136
|
|
|
125
137
|
return {
|
|
126
138
|
routeKind,
|
|
@@ -156,9 +168,7 @@ function validateEntities(raw: unknown): ExtractedEntities {
|
|
|
156
168
|
throw new Error("entities must be an object");
|
|
157
169
|
}
|
|
158
170
|
const e = raw as Record<string, unknown>;
|
|
159
|
-
const symbols = validateStringArray(e.symbols, "entities.symbols").map((s) =>
|
|
160
|
-
s.toUpperCase(),
|
|
161
|
-
);
|
|
171
|
+
const symbols = validateStringArray(e.symbols, "entities.symbols").map((s) => s.toUpperCase());
|
|
162
172
|
|
|
163
173
|
const out: ExtractedEntities = { symbols };
|
|
164
174
|
if (typeof e.budget === "number") out.budget = e.budget;
|
|
@@ -169,10 +179,11 @@ function validateEntities(raw: unknown): ExtractedEntities {
|
|
|
169
179
|
if (typeof e.riskProfile === "string") out.riskProfile = e.riskProfile;
|
|
170
180
|
if (e.direction === "bullish" || e.direction === "bearish") out.direction = e.direction;
|
|
171
181
|
if (typeof e.dteHint === "string") out.dteHint = e.dteHint;
|
|
172
|
-
if (e.optionStrategy === "covered_call" || e.optionStrategy === "protective_put")
|
|
182
|
+
if (e.optionStrategy === "covered_call" || e.optionStrategy === "protective_put")
|
|
183
|
+
out.optionStrategy = e.optionStrategy;
|
|
173
184
|
if (typeof e.heldSymbol === "string") out.heldSymbol = e.heldSymbol.toUpperCase();
|
|
174
|
-
const catalystSymbols = validateStringArray(e.catalystSymbols, "entities.catalystSymbols").map(
|
|
175
|
-
s.toUpperCase(),
|
|
185
|
+
const catalystSymbols = validateStringArray(e.catalystSymbols, "entities.catalystSymbols").map(
|
|
186
|
+
(s) => s.toUpperCase(),
|
|
176
187
|
);
|
|
177
188
|
if (catalystSymbols.length > 0) out.catalystSymbols = catalystSymbols;
|
|
178
189
|
const compareMetrics = validateStringArray(e.compareMetrics, "entities.compareMetrics");
|
|
@@ -188,9 +199,7 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
188
199
|
...output,
|
|
189
200
|
entities: {
|
|
190
201
|
...output.entities,
|
|
191
|
-
symbols: output.entities.symbols.filter((symbol) =>
|
|
192
|
-
!isAmbiguousConceptUsage(text, symbol),
|
|
193
|
-
),
|
|
202
|
+
symbols: output.entities.symbols.filter((symbol) => !isAmbiguousConceptUsage(text, symbol)),
|
|
194
203
|
budget: output.entities.budget ?? extracted.budget,
|
|
195
204
|
maxPremium: output.entities.maxPremium ?? extracted.maxPremium,
|
|
196
205
|
timeHorizon: output.entities.timeHorizon ?? extracted.timeHorizon,
|
|
@@ -203,21 +212,30 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
203
212
|
shareQuantity: output.entities.shareQuantity ?? extracted.shareQuantity,
|
|
204
213
|
heldSymbol: output.entities.heldSymbol ?? extracted.heldSymbol,
|
|
205
214
|
catalystSymbols: output.entities.catalystSymbols ?? extracted.catalystSymbols,
|
|
206
|
-
dteHint:
|
|
215
|
+
dteHint:
|
|
216
|
+
output.entities.dteHint ??
|
|
217
|
+
(output.workflow === "options_screener" ? extracted.dteHint : undefined),
|
|
207
218
|
},
|
|
208
219
|
diagnostics,
|
|
209
220
|
};
|
|
210
221
|
|
|
211
|
-
if (
|
|
222
|
+
if (
|
|
223
|
+
next.workflow === "options_screener" &&
|
|
224
|
+
isExistingPositionOptionRequest(text, extracted) &&
|
|
225
|
+
extracted.heldSymbol
|
|
226
|
+
) {
|
|
212
227
|
const reorderedSymbols = [
|
|
213
228
|
extracted.heldSymbol,
|
|
214
|
-
...mergeSymbols(next.entities.symbols, extracted.symbols).filter(
|
|
229
|
+
...mergeSymbols(next.entities.symbols, extracted.symbols).filter(
|
|
230
|
+
(symbol) => symbol !== extracted.heldSymbol,
|
|
231
|
+
),
|
|
215
232
|
];
|
|
216
233
|
if (next.entities.symbols[0] !== extracted.heldSymbol) {
|
|
217
234
|
diagnostics.push({
|
|
218
|
-
code:
|
|
219
|
-
|
|
220
|
-
|
|
235
|
+
code:
|
|
236
|
+
extracted.optionStrategy === "protective_put"
|
|
237
|
+
? "existing_position_underlying_corrected"
|
|
238
|
+
: "covered_call_underlying_corrected",
|
|
221
239
|
message: `using owned position ${extracted.heldSymbol} as the option-chain underlying`,
|
|
222
240
|
});
|
|
223
241
|
}
|
|
@@ -245,7 +263,8 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
245
263
|
) {
|
|
246
264
|
diagnostics.push({
|
|
247
265
|
code: "options_workflow_corrected_to_policy_task",
|
|
248
|
-
message:
|
|
266
|
+
message:
|
|
267
|
+
"options education or suitability prompt should use policy-card synthesis, not contract-screen workflow dispatch",
|
|
249
268
|
});
|
|
250
269
|
next = {
|
|
251
270
|
...next,
|
|
@@ -278,7 +297,10 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
278
297
|
timeHorizon: deterministic.entities.timeHorizon ?? extracted.timeHorizon,
|
|
279
298
|
riskProfile: deterministic.entities.riskProfile ?? extracted.riskProfile,
|
|
280
299
|
assetScope: deterministic.entities.assetScope ?? extracted.assetScope,
|
|
281
|
-
compareMetrics: mergeStringArrays(
|
|
300
|
+
compareMetrics: mergeStringArrays(
|
|
301
|
+
deterministic.entities.compareMetrics,
|
|
302
|
+
extracted.compareMetrics,
|
|
303
|
+
),
|
|
282
304
|
direction: deterministic.entities.direction ?? extracted.direction,
|
|
283
305
|
costBasis: deterministic.entities.costBasis ?? extracted.costBasis,
|
|
284
306
|
shareQuantity: deterministic.entities.shareQuantity ?? extracted.shareQuantity,
|
|
@@ -314,6 +336,30 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
314
336
|
};
|
|
315
337
|
}
|
|
316
338
|
|
|
339
|
+
if (
|
|
340
|
+
(next.workflow === "compare_assets" || next.workflow === "portfolio_builder") &&
|
|
341
|
+
isStatefulTrackingRequest(text)
|
|
342
|
+
) {
|
|
343
|
+
diagnostics.push({
|
|
344
|
+
code: "stateful_tracking_corrected_to_agent_task",
|
|
345
|
+
message:
|
|
346
|
+
"portfolio/watchlist tracking mutation should use stateful tracking tools, not compare or construction workflow dispatch",
|
|
347
|
+
});
|
|
348
|
+
next = {
|
|
349
|
+
...next,
|
|
350
|
+
routeKind: "agent_task",
|
|
351
|
+
route: "fallback",
|
|
352
|
+
workflow: "watchlist_or_tracking",
|
|
353
|
+
missing_required: [],
|
|
354
|
+
entities: {
|
|
355
|
+
...next.entities,
|
|
356
|
+
symbols: filterCurrencyUnitSymbols(text, next.entities.symbols),
|
|
357
|
+
},
|
|
358
|
+
slots: removeCurrencyUnitSymbolSlots(text, next.slots),
|
|
359
|
+
diagnostics,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
317
363
|
if (next.routeKind === "agent_task" && isDispatchableWorkflow(next.workflow)) {
|
|
318
364
|
diagnostics.push({
|
|
319
365
|
code: "dispatchable_workflow_corrected_to_workflow_dispatch",
|
|
@@ -349,7 +395,8 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
349
395
|
if (next.workflow === "compare_assets" && isPortfolioEvaluationRequest(text)) {
|
|
350
396
|
diagnostics.push({
|
|
351
397
|
code: "portfolio_evaluation_corrected_to_agent_task",
|
|
352
|
-
message:
|
|
398
|
+
message:
|
|
399
|
+
"existing portfolio/allocation risk review should not be reduced to asset comparison",
|
|
353
400
|
});
|
|
354
401
|
next = {
|
|
355
402
|
...next,
|
|
@@ -381,7 +428,8 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
381
428
|
if (next.workflow === "portfolio_builder" && isCryptoSizingRequest(text)) {
|
|
382
429
|
diagnostics.push({
|
|
383
430
|
code: "crypto_sizing_corrected_to_agent_task",
|
|
384
|
-
message:
|
|
431
|
+
message:
|
|
432
|
+
"crypto allocation-range and drawdown questions are advisory tradeoffs, not portfolio construction",
|
|
385
433
|
});
|
|
386
434
|
next = {
|
|
387
435
|
...next,
|
|
@@ -396,7 +444,8 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
396
444
|
if (next.workflow === "portfolio_builder" && isPortfolioEvaluationRequest(text)) {
|
|
397
445
|
diagnostics.push({
|
|
398
446
|
code: "portfolio_evaluation_corrected_to_agent_task",
|
|
399
|
-
message:
|
|
447
|
+
message:
|
|
448
|
+
"existing portfolio/allocation evaluation does not require portfolio-construction budget",
|
|
400
449
|
});
|
|
401
450
|
next = {
|
|
402
451
|
...next,
|
|
@@ -415,7 +464,8 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
415
464
|
) {
|
|
416
465
|
diagnostics.push({
|
|
417
466
|
code: "portfolio_tradeoff_corrected_to_compare_assets",
|
|
418
|
-
message:
|
|
467
|
+
message:
|
|
468
|
+
"explicit multi-asset tradeoff question should compare the requested assets before constructing a portfolio",
|
|
419
469
|
});
|
|
420
470
|
next = {
|
|
421
471
|
...next,
|
|
@@ -427,10 +477,7 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
427
477
|
};
|
|
428
478
|
}
|
|
429
479
|
|
|
430
|
-
if (
|
|
431
|
-
next.workflow === "single_asset_analysis" &&
|
|
432
|
-
isSpecializedSingleAssetPolicyRequest(text)
|
|
433
|
-
) {
|
|
480
|
+
if (next.workflow === "single_asset_analysis" && isSpecializedSingleAssetPolicyRequest(text)) {
|
|
434
481
|
diagnostics.push({
|
|
435
482
|
code: "single_asset_workflow_corrected_to_general_policy_task",
|
|
436
483
|
message: "prompt asks for policy-card planning outside a single-asset buy/sell analysis",
|
|
@@ -442,6 +489,34 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
442
489
|
};
|
|
443
490
|
}
|
|
444
491
|
|
|
492
|
+
const disambiguated = disambiguateSymbols(next.entities.symbols, text);
|
|
493
|
+
if (disambiguated.dropped.length > 0) {
|
|
494
|
+
for (const drop of disambiguated.dropped) {
|
|
495
|
+
diagnostics.push({
|
|
496
|
+
code: "symbol_dropped",
|
|
497
|
+
message: `${drop.token} dropped: ${drop.reason}`,
|
|
498
|
+
details: {
|
|
499
|
+
token: drop.token,
|
|
500
|
+
reason: drop.reason,
|
|
501
|
+
signalsChecked: drop.signalsChecked,
|
|
502
|
+
source: "llm",
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
next = {
|
|
507
|
+
...next,
|
|
508
|
+
entities: {
|
|
509
|
+
...next.entities,
|
|
510
|
+
symbols: disambiguated.kept,
|
|
511
|
+
},
|
|
512
|
+
slots: removeDroppedSymbolSlots(
|
|
513
|
+
next.slots,
|
|
514
|
+
disambiguated.dropped.map((drop) => drop.token),
|
|
515
|
+
),
|
|
516
|
+
diagnostics,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
445
520
|
const missingRequired = computeMissingRequiredSlots(
|
|
446
521
|
next.workflow,
|
|
447
522
|
next.entities,
|
|
@@ -473,7 +548,9 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
473
548
|
message: "conceptual education prompt does not need live finance tools",
|
|
474
549
|
});
|
|
475
550
|
}
|
|
476
|
-
const emittedUnsupported = next.tool_bundles.filter(
|
|
551
|
+
const emittedUnsupported = next.tool_bundles.filter(
|
|
552
|
+
(bundle) => !selectedToolBundles.includes(bundle),
|
|
553
|
+
);
|
|
477
554
|
if (emittedUnsupported.length > 0) {
|
|
478
555
|
diagnostics.push({
|
|
479
556
|
code: "tool_bundles_corrected",
|
|
@@ -490,22 +567,34 @@ export function postProcessRouterOutput(text: string, output: RouterOutput): Rou
|
|
|
490
567
|
}
|
|
491
568
|
|
|
492
569
|
function isExplicitMacroDataRequest(text: string): boolean {
|
|
493
|
-
return /\b(?:get_economic_data|fred|cpi|inflation|fed\s+funds?|unemployment|gdp|macro)\b/i.test(
|
|
570
|
+
return /\b(?:get_economic_data|fred|cpi|inflation|fed\s+funds?|unemployment|gdp|macro)\b/i.test(
|
|
571
|
+
text,
|
|
572
|
+
);
|
|
494
573
|
}
|
|
495
574
|
|
|
496
575
|
function isConceptualEducationRequest(text: string, output: RouterOutput): boolean {
|
|
497
576
|
if (output.routeKind !== "agent_task") return false;
|
|
498
577
|
if (output.entities.symbols.length > 0) return false;
|
|
499
578
|
if (isForwardLookingMacroContextRequest(text)) return false;
|
|
500
|
-
if (
|
|
579
|
+
if (
|
|
580
|
+
/\b(?:current|recent|today|right now|latest|news|sentiment|build|portfolio|buy|sell|allocate|compare)\b/i.test(
|
|
581
|
+
text,
|
|
582
|
+
)
|
|
583
|
+
) {
|
|
501
584
|
return false;
|
|
502
585
|
}
|
|
503
|
-
return /\b(?:explain|what is|define|how (?:do|should|to)|teach me|help me understand)\b/i.test(
|
|
586
|
+
return /\b(?:explain|what is|define|how (?:do|should|to)|teach me|help me understand)\b/i.test(
|
|
587
|
+
text,
|
|
588
|
+
);
|
|
504
589
|
}
|
|
505
590
|
|
|
506
591
|
function isForwardLookingMacroContextRequest(text: string): boolean {
|
|
507
|
-
return
|
|
508
|
-
/\b(?:
|
|
592
|
+
return (
|
|
593
|
+
/\b(?:rates?|rate\s*cuts?|fed|inflation|macro)\b/i.test(text) &&
|
|
594
|
+
/\b(?:next\s+(?:year|12\s*months?)|over\s+the\s+next|outlook|affect|impact|falling|rising)\b/i.test(
|
|
595
|
+
text,
|
|
596
|
+
)
|
|
597
|
+
);
|
|
509
598
|
}
|
|
510
599
|
|
|
511
600
|
function isCoveredCallRequest(text: string): boolean {
|
|
@@ -515,19 +604,75 @@ function isCoveredCallRequest(text: string): boolean {
|
|
|
515
604
|
function isPortfolioEvaluationRequest(text: string): boolean {
|
|
516
605
|
const lower = text.toLowerCase();
|
|
517
606
|
const hasEvaluationIntent =
|
|
518
|
-
/\b(?:evaluat(?:e|ion)|review|assess|analy[sz]e|prospects?|risks?|risky|opportunities?|mitigat(?:e|ion)|adjustment|rebalance|diversify|concentration|overweight|underweight|target\s+bands?|drift|worried|crash|protect|protection|missing\s+out\s+on\s+growth)\b/.test(
|
|
607
|
+
/\b(?:evaluat(?:e|ion)|review|assess|analy[sz]e|prospects?|risks?|risky|opportunities?|mitigat(?:e|ion)|adjustment|rebalance|diversify|concentration|overweight|underweight|target\s+bands?|drift|worried|crash|protect|protection|missing\s+out\s+on\s+growth)\b/.test(
|
|
608
|
+
lower,
|
|
609
|
+
);
|
|
519
610
|
const hasPortfolioObject =
|
|
520
|
-
/\b(?:portfolio|allocation|asset\s+allocation|60\/40|equity|fixed\s+income|bonds?)\b/.test(
|
|
611
|
+
/\b(?:portfolio|allocation|asset\s+allocation|60\/40|equity|fixed\s+income|bonds?)\b/.test(
|
|
612
|
+
lower,
|
|
613
|
+
);
|
|
521
614
|
const hasConstructionIntent =
|
|
522
615
|
/\b(?:build|create|construct|put\s+together|invest|allocate)\b/.test(lower) &&
|
|
523
|
-
|
|
616
|
+
/\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower);
|
|
524
617
|
return hasEvaluationIntent && hasPortfolioObject && !hasConstructionIntent;
|
|
525
618
|
}
|
|
526
619
|
|
|
620
|
+
function isStatefulTrackingRequest(text: string): boolean {
|
|
621
|
+
const lower = text.toLowerCase();
|
|
622
|
+
const hasPortfolioConstructionIntent =
|
|
623
|
+
/\b(?:build|create|construct|put\s+together)\b/.test(lower) &&
|
|
624
|
+
/\bportfolio\b/.test(lower) &&
|
|
625
|
+
/\$\s*\d|\b\d+(?:\.\d+)?\s*k\b|\bbudget\b|\bcapital\b/.test(lower);
|
|
626
|
+
const hasStateVerb =
|
|
627
|
+
/\b(?:add|remove|update|record|track|create|configure|check|show|list|view|cancel)\b/.test(
|
|
628
|
+
lower,
|
|
629
|
+
);
|
|
630
|
+
const hasStateObject =
|
|
631
|
+
/\b(?:watchlist|portfolio|holding|holdings|position|positions|prediction|predictions|alert|alerts|daily\s+report|watchlist\s+report|report\s+history)\b/.test(
|
|
632
|
+
lower,
|
|
633
|
+
);
|
|
634
|
+
const hasPortfolioLotShape =
|
|
635
|
+
/\b(?:add|record|track)\b/.test(lower) &&
|
|
636
|
+
/\b\d+(?:\.\d+)?\s+shares?\b/.test(lower) &&
|
|
637
|
+
/\b(?:portfolio|holding|holdings|position|positions)\b/.test(lower);
|
|
638
|
+
if (hasPortfolioConstructionIntent) return false;
|
|
639
|
+
return (hasStateVerb && hasStateObject) || hasPortfolioLotShape;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function filterCurrencyUnitSymbols(text: string, symbols: string[]): string[] {
|
|
643
|
+
return symbols.filter((symbol) => !isCurrencyCodeUsage(text, symbol));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function removeCurrencyUnitSymbolSlots(
|
|
647
|
+
text: string,
|
|
648
|
+
slots: Record<string, RouterSlot>,
|
|
649
|
+
): Record<string, RouterSlot> {
|
|
650
|
+
const next = { ...slots };
|
|
651
|
+
for (const key of ["symbol", "symbols"]) {
|
|
652
|
+
const slot = next[key];
|
|
653
|
+
if (!slot) continue;
|
|
654
|
+
if (Array.isArray(slot.value)) {
|
|
655
|
+
const value = slot.value.filter(
|
|
656
|
+
(item) => typeof item !== "string" || !isCurrencyCodeUsage(text, item.toUpperCase()),
|
|
657
|
+
);
|
|
658
|
+
if (value.length === 0) delete next[key];
|
|
659
|
+
else next[key] = { ...slot, value };
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (typeof slot.value === "string" && isCurrencyCodeUsage(text, slot.value.toUpperCase())) {
|
|
663
|
+
delete next[key];
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return next;
|
|
667
|
+
}
|
|
668
|
+
|
|
527
669
|
function isPortfolioTradeoffComparisonRequest(text: string): boolean {
|
|
528
670
|
const lower = text.toLowerCase();
|
|
529
|
-
return
|
|
530
|
-
/\b(?:
|
|
671
|
+
return (
|
|
672
|
+
/\b(?:prioritize|tradeoffs?|growth[-\s]?oriented|dividend|income|which\s+(?:one|is)\s+better|should\s+i)\b/.test(
|
|
673
|
+
lower,
|
|
674
|
+
) && /\b(?:or|vs\.?|versus|compare)\b/.test(lower)
|
|
675
|
+
);
|
|
531
676
|
}
|
|
532
677
|
|
|
533
678
|
function isCryptoSizingRequest(text: string): boolean {
|
|
@@ -536,16 +681,20 @@ function isCryptoSizingRequest(text: string): boolean {
|
|
|
536
681
|
/\b(?:build|create|construct|put\s+together)\b/.test(lower) &&
|
|
537
682
|
/\b(?:portfolio|allocation)\b/.test(lower);
|
|
538
683
|
if (hasPortfolioConstructionIntent) return false;
|
|
539
|
-
return
|
|
540
|
-
/\b(?:
|
|
684
|
+
return (
|
|
685
|
+
/\b(?:btc|bitcoin|crypto)\b/.test(lower) &&
|
|
686
|
+
/\b(?:allocation|range|position\s+size|sizing|exposure|drawdown)\b/.test(lower)
|
|
687
|
+
);
|
|
541
688
|
}
|
|
542
689
|
|
|
543
690
|
function isSpecializedSingleAssetPolicyRequest(text: string): boolean {
|
|
544
691
|
const lower = text.toLowerCase();
|
|
545
|
-
return
|
|
692
|
+
return (
|
|
693
|
+
/\b(?:ticker|symbol|formerly|old ticker|earnings are|earnings tonight)\b/.test(lower) ||
|
|
546
694
|
/\b(?:today|right now|this morning|after close|moved|catalyst)\b/.test(lower) ||
|
|
547
695
|
/\b(?:sentiment|mood|reddit|twitter|x\/twitter)\b/.test(lower) ||
|
|
548
|
-
/\b(?:filing|10-k|10-q|8-k|sec)\b/.test(lower)
|
|
696
|
+
/\b(?:filing|10-k|10-q|8-k|sec)\b/.test(lower)
|
|
697
|
+
);
|
|
549
698
|
}
|
|
550
699
|
|
|
551
700
|
function isExistingPositionOptionRequest(text: string, extracted: ExtractedEntities): boolean {
|
|
@@ -554,14 +703,23 @@ function isExistingPositionOptionRequest(text: string, extracted: ExtractedEntit
|
|
|
554
703
|
|
|
555
704
|
function isOptionsEducationOrSuitabilityRequest(text: string): boolean {
|
|
556
705
|
const lower = text.toLowerCase();
|
|
557
|
-
return
|
|
558
|
-
/\b(?:
|
|
706
|
+
return (
|
|
707
|
+
/\b(?:how\s+does|how\s+do|explain|what\s+is|good\s+idea|make\s+sense|suitable|suitability|is\s+it\s+(?:good|worth|smart))\b/.test(
|
|
708
|
+
lower,
|
|
709
|
+
) &&
|
|
710
|
+
/\b(?:covered\s+calls?|protective\s+puts?|options?|selling\s+calls?|option\s+income)\b/.test(
|
|
711
|
+
lower,
|
|
712
|
+
)
|
|
713
|
+
);
|
|
559
714
|
}
|
|
560
715
|
|
|
561
716
|
function isSpecificOptionContractSelectionRequest(text: string): boolean {
|
|
562
717
|
const lower = text.toLowerCase();
|
|
563
|
-
return
|
|
564
|
-
/\b(?:
|
|
718
|
+
return (
|
|
719
|
+
/\b(?:best|which|what\s+(?:strike|contract|option)|rank|screen|specific|right\s+now|today|around\s+earnings|expiration|dte|premium\s+under)\b/.test(
|
|
720
|
+
lower,
|
|
721
|
+
) && /\b(?:sell|buy|trade|contract|strike|expiration|premium|call|put)\b/.test(lower)
|
|
722
|
+
);
|
|
565
723
|
}
|
|
566
724
|
|
|
567
725
|
function mergeSymbols(primary: string[], secondary: string[]): string[] {
|
|
@@ -639,7 +797,9 @@ function validatePreferenceUpdates(raw: unknown): RouterPreferenceUpdate[] {
|
|
|
639
797
|
// (normalized), but any explicit non-"inferred" value is an invariant
|
|
640
798
|
// violation the caller should see rather than silently lose.
|
|
641
799
|
if (p.source !== undefined && p.source !== "inferred") {
|
|
642
|
-
throw new Error(
|
|
800
|
+
throw new Error(
|
|
801
|
+
`preference_updates[${idx}].source must be "inferred" (got ${JSON.stringify(p.source)})`,
|
|
802
|
+
);
|
|
643
803
|
}
|
|
644
804
|
return {
|
|
645
805
|
key: p.key,
|
|
@@ -655,6 +815,36 @@ function validateToolBundles(raw: unknown): ToolBundleName[] {
|
|
|
655
815
|
return bundles.filter(isToolBundleName);
|
|
656
816
|
}
|
|
657
817
|
|
|
818
|
+
function removeDroppedSymbolSlots(
|
|
819
|
+
slots: Record<string, RouterSlot>,
|
|
820
|
+
droppedTokens: string[],
|
|
821
|
+
): Record<string, RouterSlot> {
|
|
822
|
+
if (droppedTokens.length === 0) return slots;
|
|
823
|
+
const dropped = new Set(droppedTokens.map((token) => token.toUpperCase()));
|
|
824
|
+
const next = { ...slots };
|
|
825
|
+
|
|
826
|
+
for (const key of ["symbol", "symbols"]) {
|
|
827
|
+
const slot = next[key];
|
|
828
|
+
if (!slot) continue;
|
|
829
|
+
if (Array.isArray(slot.value)) {
|
|
830
|
+
const value = slot.value.filter(
|
|
831
|
+
(item) => typeof item !== "string" || !dropped.has(item.toUpperCase()),
|
|
832
|
+
);
|
|
833
|
+
if (value.length === 0) {
|
|
834
|
+
delete next[key];
|
|
835
|
+
} else {
|
|
836
|
+
next[key] = { ...slot, value };
|
|
837
|
+
}
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (typeof slot.value === "string" && dropped.has(slot.value.toUpperCase())) {
|
|
841
|
+
delete next[key];
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return next;
|
|
846
|
+
}
|
|
847
|
+
|
|
658
848
|
function validateDiagnostics(raw: unknown): RouterDiagnostic[] {
|
|
659
849
|
if (raw === undefined || raw === null) return [];
|
|
660
850
|
if (!Array.isArray(raw)) {
|
|
@@ -671,10 +861,18 @@ function validateDiagnostics(raw: unknown): RouterDiagnostic[] {
|
|
|
671
861
|
if (typeof diagnostic.message !== "string") {
|
|
672
862
|
throw new Error(`diagnostics[${idx}].message must be a string`);
|
|
673
863
|
}
|
|
674
|
-
|
|
864
|
+
const out: RouterDiagnostic = {
|
|
675
865
|
code: diagnostic.code,
|
|
676
866
|
message: diagnostic.message,
|
|
677
867
|
};
|
|
868
|
+
if (
|
|
869
|
+
diagnostic.details &&
|
|
870
|
+
typeof diagnostic.details === "object" &&
|
|
871
|
+
!Array.isArray(diagnostic.details)
|
|
872
|
+
) {
|
|
873
|
+
out.details = diagnostic.details as Record<string, unknown>;
|
|
874
|
+
}
|
|
875
|
+
return out;
|
|
678
876
|
});
|
|
679
877
|
}
|
|
680
878
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { OPTIONS_SCREENER_DEFAULTS, PORTFOLIO_DEFAULTS } from "./defaults.js";
|
|
1
2
|
import type {
|
|
2
3
|
ExtractedEntities,
|
|
3
|
-
PortfolioSlots,
|
|
4
4
|
OptionsScreenerSlots,
|
|
5
|
+
PortfolioSlots,
|
|
5
6
|
SlotResolution,
|
|
6
7
|
SlotSource,
|
|
7
8
|
} from "./types.js";
|
|
8
|
-
import { PORTFOLIO_DEFAULTS, OPTIONS_SCREENER_DEFAULTS } from "./defaults.js";
|
|
9
9
|
|
|
10
10
|
interface Preferences {
|
|
11
11
|
riskProfile?: string;
|
|
@@ -85,11 +85,19 @@ export function resolvePortfolioSlots(
|
|
|
85
85
|
sources.budget = "default";
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const risk = resolve(
|
|
88
|
+
const risk = resolve(
|
|
89
|
+
entities.riskProfile,
|
|
90
|
+
preferences.riskProfile,
|
|
91
|
+
PORTFOLIO_DEFAULTS.riskProfile,
|
|
92
|
+
);
|
|
89
93
|
sources.riskProfile = risk.source;
|
|
90
94
|
if (risk.source === "default") defaultsUsed.push("riskProfile");
|
|
91
95
|
|
|
92
|
-
const horizon = resolve(
|
|
96
|
+
const horizon = resolve(
|
|
97
|
+
entities.timeHorizon,
|
|
98
|
+
preferences.timeHorizon,
|
|
99
|
+
PORTFOLIO_DEFAULTS.timeHorizon,
|
|
100
|
+
);
|
|
93
101
|
sources.timeHorizon = horizon.source;
|
|
94
102
|
if (horizon.source === "default") defaultsUsed.push("timeHorizon");
|
|
95
103
|
|
|
@@ -101,7 +109,11 @@ export function resolvePortfolioSlots(
|
|
|
101
109
|
sources.positionCount = count.source;
|
|
102
110
|
if (count.source === "default") defaultsUsed.push("positionCount");
|
|
103
111
|
|
|
104
|
-
const maxPct = resolve(
|
|
112
|
+
const maxPct = resolve(
|
|
113
|
+
undefined,
|
|
114
|
+
preferences.maxSinglePositionPct,
|
|
115
|
+
PORTFOLIO_DEFAULTS.maxSinglePositionPct,
|
|
116
|
+
);
|
|
105
117
|
sources.maxSinglePositionPct = maxPct.source;
|
|
106
118
|
if (maxPct.source === "default") defaultsUsed.push("maxSinglePositionPct");
|
|
107
119
|
|
|
@@ -140,14 +152,17 @@ export function resolveOptionsScreenerSlots(
|
|
|
140
152
|
}
|
|
141
153
|
|
|
142
154
|
// Direction: default to bullish unless the user specified a protective put hedge.
|
|
143
|
-
const inferredDirection =
|
|
144
|
-
? "bearish"
|
|
145
|
-
: entities.direction;
|
|
155
|
+
const inferredDirection =
|
|
156
|
+
entities.optionStrategy === "protective_put" ? "bearish" : entities.direction;
|
|
146
157
|
const dir = resolve(inferredDirection, undefined, "bullish" as const);
|
|
147
158
|
sources.direction = dir.source;
|
|
148
159
|
if (dir.source === "default") defaultsUsed.push("direction");
|
|
149
160
|
|
|
150
|
-
const dte = resolve(
|
|
161
|
+
const dte = resolve(
|
|
162
|
+
mapDteHintToTarget(entities.dteHint),
|
|
163
|
+
preferences.dteTarget,
|
|
164
|
+
OPTIONS_SCREENER_DEFAULTS.dteTarget,
|
|
165
|
+
);
|
|
151
166
|
sources.dteTarget = dte.source;
|
|
152
167
|
if (dte.source === "default") defaultsUsed.push("dteTarget");
|
|
153
168
|
|
|
@@ -155,11 +170,19 @@ export function resolveOptionsScreenerSlots(
|
|
|
155
170
|
sources.objective = obj.source;
|
|
156
171
|
if (obj.source === "default") defaultsUsed.push("objective");
|
|
157
172
|
|
|
158
|
-
const moneyness = resolve(
|
|
173
|
+
const moneyness = resolve(
|
|
174
|
+
undefined,
|
|
175
|
+
preferences.moneynessPreference,
|
|
176
|
+
OPTIONS_SCREENER_DEFAULTS.moneynessPreference,
|
|
177
|
+
);
|
|
159
178
|
sources.moneynessPreference = moneyness.source;
|
|
160
179
|
if (moneyness.source === "default") defaultsUsed.push("moneynessPreference");
|
|
161
180
|
|
|
162
|
-
const liquidity = resolve(
|
|
181
|
+
const liquidity = resolve(
|
|
182
|
+
undefined,
|
|
183
|
+
preferences.liquidityMinimum,
|
|
184
|
+
OPTIONS_SCREENER_DEFAULTS.liquidityMinimum,
|
|
185
|
+
);
|
|
163
186
|
sources.liquidityMinimum = liquidity.source;
|
|
164
187
|
if (liquidity.source === "default") defaultsUsed.push("liquidityMinimum");
|
|
165
188
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const FINANCE_ACRONYM_DICTIONARY = new Set([
|
|
2
|
+
"IV",
|
|
3
|
+
"HV",
|
|
4
|
+
"ITM",
|
|
5
|
+
"OTM",
|
|
6
|
+
"ATM",
|
|
7
|
+
"IPO",
|
|
8
|
+
"SEC",
|
|
9
|
+
"FED",
|
|
10
|
+
"FOMC",
|
|
11
|
+
"IRS",
|
|
12
|
+
"ECB",
|
|
13
|
+
"BOE",
|
|
14
|
+
"BOJ",
|
|
15
|
+
"GDP",
|
|
16
|
+
"CPI",
|
|
17
|
+
"PPI",
|
|
18
|
+
"FX",
|
|
19
|
+
"NDA",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export interface DroppedSymbol {
|
|
23
|
+
token: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
signalsChecked: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SymbolDisambiguation {
|
|
29
|
+
kept: string[];
|
|
30
|
+
dropped: DroppedSymbol[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function disambiguateSymbols(candidates: string[], rawInput: string): SymbolDisambiguation {
|
|
34
|
+
const kept: string[] = [];
|
|
35
|
+
const dropped: DroppedSymbol[] = [];
|
|
36
|
+
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
const token = candidate.toUpperCase();
|
|
39
|
+
if (!FINANCE_ACRONYM_DICTIONARY.has(token) || hasPositiveTickerSignal(token, rawInput)) {
|
|
40
|
+
if (!kept.includes(token)) kept.push(token);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
dropped.push({
|
|
45
|
+
token,
|
|
46
|
+
reason: "no positive ticker signal",
|
|
47
|
+
signalsChecked: ["cashtag", "local ticker phrase"],
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { kept, dropped };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hasPositiveTickerSignal(token: string, rawInput: string): boolean {
|
|
55
|
+
return hasCashtag(token, rawInput) || hasLocalTickerPhrase(token, rawInput);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hasCashtag(token: string, rawInput: string): boolean {
|
|
59
|
+
return new RegExp(`\\$${escapeRegExp(token)}\\b`, "i").test(rawInput);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function hasLocalTickerPhrase(token: string, rawInput: string): boolean {
|
|
63
|
+
const escaped = escapeRegExp(token);
|
|
64
|
+
return new RegExp(
|
|
65
|
+
`\\b${escaped}\\s+(?:ticker|stock|symbol)\\b|\\b(?:ticker|stock|symbol)\\s+${escaped}\\b`,
|
|
66
|
+
"i",
|
|
67
|
+
).test(rawInput);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function escapeRegExp(value: string): string {
|
|
71
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
72
|
+
}
|