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,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import type { SentinelRecord, SentimentAdapter } from "../types.js";
|
|
3
2
|
import type { WebSearchEnvelope } from "../../types/sentiment.js";
|
|
3
|
+
import type { SentimentAdapter, SentinelRecord } from "../types.js";
|
|
4
4
|
|
|
5
5
|
export class WebAdapter implements SentimentAdapter {
|
|
6
6
|
readonly source = "web" as const;
|
package/src/sentiment/index.ts
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
+
export { RedditAdapter } from "./adapters/reddit.js";
|
|
2
|
+
export { TwitterAdapter } from "./adapters/twitter.js";
|
|
3
|
+
export { WebAdapter } from "./adapters/web.js";
|
|
4
|
+
export { buildSentimentInsight, labelForScore } from "./insights.js";
|
|
5
|
+
export { BEARISH_TERMS, BULLISH_TERMS } from "./keywords.js";
|
|
6
|
+
export { SentimentPipeline } from "./pipeline.js";
|
|
7
|
+
export { keywordScore, scoreRecords } from "./scorer.js";
|
|
8
|
+
export { SentimentStore } from "./store.js";
|
|
9
|
+
export { computeDivergence, computeTrend, renderSparkline } from "./trends.js";
|
|
1
10
|
export type {
|
|
2
|
-
|
|
11
|
+
DivergenceResult,
|
|
12
|
+
ScorerOptions,
|
|
13
|
+
SentimentAdapter,
|
|
14
|
+
SentimentSource,
|
|
15
|
+
SentimentSummary,
|
|
3
16
|
SentinelEngagement,
|
|
17
|
+
SentinelRecord,
|
|
4
18
|
SentinelSentiment,
|
|
5
|
-
SentimentAdapter,
|
|
6
|
-
ScorerOptions,
|
|
7
19
|
TrendBucket,
|
|
8
20
|
TrendResult,
|
|
9
|
-
DivergenceResult,
|
|
10
|
-
SentimentSummary,
|
|
11
|
-
SentimentSource,
|
|
12
21
|
} from "./types.js";
|
|
13
|
-
|
|
14
22
|
export { isSentinelRecord, SENTIMENT_SOURCES } from "./types.js";
|
|
15
|
-
export { SentimentStore } from "./store.js";
|
|
16
|
-
export { scoreRecords, keywordScore } from "./scorer.js";
|
|
17
|
-
export { SentimentPipeline } from "./pipeline.js";
|
|
18
|
-
export { renderSparkline, computeTrend, computeDivergence } from "./trends.js";
|
|
19
|
-
export { BULLISH_TERMS, BEARISH_TERMS } from "./keywords.js";
|
|
20
|
-
export { TwitterAdapter } from "./adapters/twitter.js";
|
|
21
|
-
export { RedditAdapter } from "./adapters/reddit.js";
|
|
22
|
-
export { WebAdapter } from "./adapters/web.js";
|
|
23
23
|
|
|
24
|
-
import { SentimentStore } from "./store.js";
|
|
25
|
-
import { SentimentPipeline } from "./pipeline.js";
|
|
26
24
|
import { getConfig } from "../config.js";
|
|
27
25
|
import { resolveOpenCandlePath } from "../infra/opencandle-paths.js";
|
|
26
|
+
import { SentimentPipeline } from "./pipeline.js";
|
|
27
|
+
import { SentimentStore } from "./store.js";
|
|
28
28
|
|
|
29
29
|
let _pipeline: SentimentPipeline | null = null;
|
|
30
30
|
let _store: SentimentStore | null = null;
|
|
@@ -47,12 +47,3 @@ export function getSentimentPipeline(): SentimentPipeline {
|
|
|
47
47
|
}
|
|
48
48
|
return _pipeline;
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
/** For testing: reset singletons */
|
|
52
|
-
export function _resetSentimentSingletons(): void {
|
|
53
|
-
if (_store) {
|
|
54
|
-
try { _store.close(); } catch { /* ignore */ }
|
|
55
|
-
}
|
|
56
|
-
_pipeline = null;
|
|
57
|
-
_store = null;
|
|
58
|
-
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { SentimentConfig } from "../config.js";
|
|
2
|
+
import type {
|
|
3
|
+
SentimentInsight,
|
|
4
|
+
SentimentInsightConfidence,
|
|
5
|
+
SentimentInsightDriver,
|
|
6
|
+
SentimentRepresentativeItem,
|
|
7
|
+
} from "../types/sentiment.js";
|
|
8
|
+
import type { SentimentSource, SentinelRecord } from "./types.js";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_MIN_SAMPLE = 10;
|
|
11
|
+
const DEFAULT_DRIVER_CAP = 3;
|
|
12
|
+
const DEFAULT_SOURCE_ITEM_CAP = 5;
|
|
13
|
+
const DEFAULT_AGGREGATE_ITEM_CAP = 8;
|
|
14
|
+
const DEFAULT_CLAIM_CAP = 5;
|
|
15
|
+
|
|
16
|
+
export interface BuildSentimentInsightOptions {
|
|
17
|
+
query: string;
|
|
18
|
+
records: SentinelRecord[];
|
|
19
|
+
score?: number;
|
|
20
|
+
label?: string;
|
|
21
|
+
source?: SentimentSource | "aggregate";
|
|
22
|
+
missingSources?: string[];
|
|
23
|
+
sourceNotes?: string[];
|
|
24
|
+
config?: Partial<SentimentConfig>;
|
|
25
|
+
aggregate?: boolean;
|
|
26
|
+
extraCaveats?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface TermBucket {
|
|
30
|
+
term: string;
|
|
31
|
+
count: number;
|
|
32
|
+
sourceIds: Set<string>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function buildSentimentInsight(options: BuildSentimentInsightOptions): SentimentInsight {
|
|
36
|
+
const records = options.records;
|
|
37
|
+
const scored = records.filter((record) => Math.abs(record.sentiment.score) > 0);
|
|
38
|
+
const score =
|
|
39
|
+
typeof options.score === "number"
|
|
40
|
+
? options.score
|
|
41
|
+
: records.length > 0
|
|
42
|
+
? records.reduce((sum, record) => sum + record.sentiment.score, 0) / records.length
|
|
43
|
+
: 0;
|
|
44
|
+
const sourceCounts = countBySource(records);
|
|
45
|
+
const positiveDrivers = driversFromTerms(records, "positive", options.config);
|
|
46
|
+
const negativeDrivers = driversFromTerms(records, "negative", options.config);
|
|
47
|
+
const caveats = buildCaveats(records, scored, sourceCounts, options);
|
|
48
|
+
const confidence = buildConfidence(records, scored, sourceCounts, caveats, options.config);
|
|
49
|
+
const representativeItems = representativeItemsFor(records, options);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
label: options.label ?? labelForScore(score),
|
|
53
|
+
score,
|
|
54
|
+
sampleSize: records.length,
|
|
55
|
+
scoredSampleSize: scored.length,
|
|
56
|
+
confidence,
|
|
57
|
+
positiveDrivers,
|
|
58
|
+
negativeDrivers,
|
|
59
|
+
mixedDrivers: buildMixedDrivers(positiveDrivers, negativeDrivers, score),
|
|
60
|
+
notableClaims: notableClaimsFor(records, options),
|
|
61
|
+
representativeItems,
|
|
62
|
+
sourceCoverage: {
|
|
63
|
+
sources: Object.keys(sourceCounts),
|
|
64
|
+
counts: sourceCounts,
|
|
65
|
+
missingSources: options.missingSources?.filter(Boolean),
|
|
66
|
+
notes: options.sourceNotes?.filter(Boolean),
|
|
67
|
+
},
|
|
68
|
+
caveats,
|
|
69
|
+
method: "deterministic-keyword-v1",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function labelForScore(score: number): string {
|
|
74
|
+
if (score > 0.3) return "Bullish";
|
|
75
|
+
if (score < -0.3) return "Bearish";
|
|
76
|
+
if (score > 0) return "Leaning Bullish";
|
|
77
|
+
if (score < 0) return "Leaning Bearish";
|
|
78
|
+
return "Neutral";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function driversFromTerms(
|
|
82
|
+
records: SentinelRecord[],
|
|
83
|
+
polarity: "positive" | "negative",
|
|
84
|
+
config: Partial<SentimentConfig> | undefined,
|
|
85
|
+
): SentimentInsightDriver[] {
|
|
86
|
+
const buckets = new Map<string, TermBucket>();
|
|
87
|
+
const metadataKey = polarity === "positive" ? "matchedBullishTerms" : "matchedBearishTerms";
|
|
88
|
+
|
|
89
|
+
for (const record of records) {
|
|
90
|
+
const terms = stringArray(record.metadata[metadataKey]);
|
|
91
|
+
for (const term of terms) {
|
|
92
|
+
const existing = buckets.get(term) ?? { term, count: 0, sourceIds: new Set<string>() };
|
|
93
|
+
existing.count += 1;
|
|
94
|
+
existing.sourceIds.add(record.sourceId);
|
|
95
|
+
buckets.set(term, existing);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return [...buckets.values()]
|
|
100
|
+
.sort((a, b) => b.count - a.count || a.term.localeCompare(b.term))
|
|
101
|
+
.slice(0, config?.maxInsightDriversPerPolarity ?? DEFAULT_DRIVER_CAP)
|
|
102
|
+
.map((bucket) => ({
|
|
103
|
+
label: bucket.term,
|
|
104
|
+
count: bucket.count,
|
|
105
|
+
polarity,
|
|
106
|
+
terms: [bucket.term],
|
|
107
|
+
sourceIds: [...bucket.sourceIds],
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildMixedDrivers(
|
|
112
|
+
positiveDrivers: SentimentInsightDriver[],
|
|
113
|
+
negativeDrivers: SentimentInsightDriver[],
|
|
114
|
+
score: number,
|
|
115
|
+
): SentimentInsightDriver[] {
|
|
116
|
+
if (positiveDrivers.length === 0 || negativeDrivers.length === 0) return [];
|
|
117
|
+
if (Math.abs(score) > 0.3) return [];
|
|
118
|
+
return [
|
|
119
|
+
{
|
|
120
|
+
label: "offsetting bullish and bearish evidence",
|
|
121
|
+
count: Math.min(sumDriverCounts(positiveDrivers), sumDriverCounts(negativeDrivers)),
|
|
122
|
+
polarity: "mixed",
|
|
123
|
+
terms: [
|
|
124
|
+
...positiveDrivers.flatMap((d) => d.terms),
|
|
125
|
+
...negativeDrivers.flatMap((d) => d.terms),
|
|
126
|
+
],
|
|
127
|
+
sourceIds: [
|
|
128
|
+
...new Set([
|
|
129
|
+
...positiveDrivers.flatMap((d) => d.sourceIds),
|
|
130
|
+
...negativeDrivers.flatMap((d) => d.sourceIds),
|
|
131
|
+
]),
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function representativeItemsFor(
|
|
138
|
+
records: SentinelRecord[],
|
|
139
|
+
options: BuildSentimentInsightOptions,
|
|
140
|
+
): SentimentRepresentativeItem[] {
|
|
141
|
+
const cap = options.aggregate
|
|
142
|
+
? (options.config?.maxAggregateRepresentativeItems ?? DEFAULT_AGGREGATE_ITEM_CAP)
|
|
143
|
+
: (options.config?.maxRepresentativeItemsPerSource ?? DEFAULT_SOURCE_ITEM_CAP);
|
|
144
|
+
|
|
145
|
+
return [...records]
|
|
146
|
+
.sort((a, b) => itemRank(b) - itemRank(a))
|
|
147
|
+
.slice(0, cap)
|
|
148
|
+
.map((record) => ({
|
|
149
|
+
source: record.source,
|
|
150
|
+
sourceId: record.sourceId,
|
|
151
|
+
title: record.title,
|
|
152
|
+
excerpt: excerptFor(record),
|
|
153
|
+
url: record.url || null,
|
|
154
|
+
author: record.author,
|
|
155
|
+
publishedAt: record.publishedAt,
|
|
156
|
+
engagement: Number.isFinite(record.engagement.score) ? record.engagement.score : null,
|
|
157
|
+
score: record.sentiment.score,
|
|
158
|
+
matchedTerms: [
|
|
159
|
+
...stringArray(record.metadata.matchedBullishTerms),
|
|
160
|
+
...stringArray(record.metadata.matchedBearishTerms),
|
|
161
|
+
],
|
|
162
|
+
metadata: record.metadata,
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function notableClaimsFor(
|
|
167
|
+
records: SentinelRecord[],
|
|
168
|
+
options: BuildSentimentInsightOptions,
|
|
169
|
+
): string[] {
|
|
170
|
+
const cap = options.config?.maxNotableClaims ?? DEFAULT_CLAIM_CAP;
|
|
171
|
+
const claims = new Set<string>();
|
|
172
|
+
for (const record of representativeItemsFor(records, { ...options, aggregate: false })) {
|
|
173
|
+
const claim = record.title ?? record.excerpt;
|
|
174
|
+
if (claim) claims.add(claim);
|
|
175
|
+
if (claims.size >= cap) break;
|
|
176
|
+
}
|
|
177
|
+
return [...claims];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildConfidence(
|
|
181
|
+
records: SentinelRecord[],
|
|
182
|
+
scored: SentinelRecord[],
|
|
183
|
+
sourceCounts: Record<string, number>,
|
|
184
|
+
caveats: string[],
|
|
185
|
+
config: Partial<SentimentConfig> | undefined,
|
|
186
|
+
): SentimentInsightConfidence {
|
|
187
|
+
const minSample = config?.minUsefulSampleSize ?? DEFAULT_MIN_SAMPLE;
|
|
188
|
+
const sampleRatio = Math.min(records.length / minSample, 1);
|
|
189
|
+
const scoredRatio = records.length === 0 ? 0 : scored.length / records.length;
|
|
190
|
+
const coverageRatio = Math.min(Object.keys(sourceCounts).length / 3, 1);
|
|
191
|
+
let score = sampleRatio * 0.4 + scoredRatio * 0.4 + coverageRatio * 0.2;
|
|
192
|
+
const reasons: string[] = [];
|
|
193
|
+
|
|
194
|
+
if (records.length < minSample) {
|
|
195
|
+
reasons.push(`low sample size (${records.length}/${minSample})`);
|
|
196
|
+
}
|
|
197
|
+
if (scoredRatio < 0.5) {
|
|
198
|
+
reasons.push("most records were neutral or had no keyword sentiment match");
|
|
199
|
+
}
|
|
200
|
+
if (Object.keys(sourceCounts).length <= 1) {
|
|
201
|
+
reasons.push("single-source sentiment coverage");
|
|
202
|
+
}
|
|
203
|
+
if (caveats.length > 0) {
|
|
204
|
+
score -= Math.min(0.2, caveats.length * 0.05);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
score = clamp01(score);
|
|
208
|
+
return {
|
|
209
|
+
level: score >= 0.7 ? "high" : score >= 0.4 ? "medium" : "low",
|
|
210
|
+
score,
|
|
211
|
+
reasons,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function buildCaveats(
|
|
216
|
+
records: SentinelRecord[],
|
|
217
|
+
scored: SentinelRecord[],
|
|
218
|
+
sourceCounts: Record<string, number>,
|
|
219
|
+
options: BuildSentimentInsightOptions,
|
|
220
|
+
): string[] {
|
|
221
|
+
const minSample = options.config?.minUsefulSampleSize ?? DEFAULT_MIN_SAMPLE;
|
|
222
|
+
const caveats = [...(options.extraCaveats ?? [])];
|
|
223
|
+
if (records.length < minSample) caveats.push(`Low sample size: ${records.length} records.`);
|
|
224
|
+
if (records.length > 0 && scored.length === 0)
|
|
225
|
+
caveats.push("No records contained keyword sentiment matches.");
|
|
226
|
+
if (records.length > 0 && scored.length / records.length < 0.5) {
|
|
227
|
+
caveats.push("Most records were neutral or unscored by keyword matching.");
|
|
228
|
+
}
|
|
229
|
+
if (Object.keys(sourceCounts).length <= 1) caveats.push("Single-source sentiment coverage.");
|
|
230
|
+
if (options.missingSources && options.missingSources.length > 0) {
|
|
231
|
+
caveats.push(`Missing sources: ${options.missingSources.join(", ")}.`);
|
|
232
|
+
}
|
|
233
|
+
return [...new Set(caveats)];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function countBySource(records: SentinelRecord[]): Record<string, number> {
|
|
237
|
+
const counts: Record<string, number> = {};
|
|
238
|
+
for (const record of records) {
|
|
239
|
+
counts[record.source] = (counts[record.source] ?? 0) + 1;
|
|
240
|
+
}
|
|
241
|
+
return counts;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function stringArray(value: unknown): string[] {
|
|
245
|
+
return Array.isArray(value)
|
|
246
|
+
? value.filter((item): item is string => typeof item === "string")
|
|
247
|
+
: [];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function sumDriverCounts(drivers: SentimentInsightDriver[]): number {
|
|
251
|
+
return drivers.reduce((sum, driver) => sum + driver.count, 0);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function itemRank(record: SentinelRecord): number {
|
|
255
|
+
const sentimentWeight = Math.abs(record.sentiment.score) * 100;
|
|
256
|
+
const confidenceWeight = record.sentiment.confidence * 20;
|
|
257
|
+
const engagementWeight = Math.log10(Math.max(0, record.engagement.score) + 1);
|
|
258
|
+
return sentimentWeight + confidenceWeight + engagementWeight;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function excerptFor(record: SentinelRecord): string {
|
|
262
|
+
const raw = record.title ? `${record.title} ${record.text}` : record.text;
|
|
263
|
+
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
264
|
+
return normalized.length > 220 ? `${normalized.slice(0, 219)}…` : normalized;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function clamp01(value: number): number {
|
|
268
|
+
return Math.max(0, Math.min(1, value));
|
|
269
|
+
}
|
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
export const BULLISH_TERMS = [
|
|
2
|
-
"moon",
|
|
3
|
-
"
|
|
2
|
+
"moon",
|
|
3
|
+
"buy",
|
|
4
|
+
"undervalued",
|
|
5
|
+
"breakout",
|
|
6
|
+
"calls",
|
|
7
|
+
"bullish",
|
|
8
|
+
"rocket",
|
|
9
|
+
"diamond hands",
|
|
10
|
+
"accumulate",
|
|
11
|
+
"dip buy",
|
|
12
|
+
"long",
|
|
13
|
+
"rip",
|
|
14
|
+
"squeeze",
|
|
4
15
|
] as const;
|
|
5
16
|
|
|
6
17
|
export const BEARISH_TERMS = [
|
|
7
|
-
"crash",
|
|
8
|
-
"
|
|
18
|
+
"crash",
|
|
19
|
+
"overvalued",
|
|
20
|
+
"sell",
|
|
21
|
+
"puts",
|
|
22
|
+
"bearish",
|
|
23
|
+
"bubble",
|
|
24
|
+
"dump",
|
|
25
|
+
"short",
|
|
26
|
+
"bagholding",
|
|
27
|
+
"exit",
|
|
28
|
+
"drill",
|
|
29
|
+
"tank",
|
|
30
|
+
"rug",
|
|
9
31
|
] as const;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import type { SentinelRecord, SentimentSummary, TrendResult, DivergenceResult, SentimentSource } from "./types.js";
|
|
2
1
|
import type { SentimentConfig } from "../config.js";
|
|
2
|
+
import { buildSentimentInsight } from "./insights.js";
|
|
3
3
|
import { scoreRecords } from "./scorer.js";
|
|
4
|
-
import { SentimentStore } from "./store.js";
|
|
5
|
-
import {
|
|
4
|
+
import type { SentimentStore } from "./store.js";
|
|
5
|
+
import { computeDivergence, computeTrend, type SourceStats } from "./trends.js";
|
|
6
|
+
import type {
|
|
7
|
+
DivergenceResult,
|
|
8
|
+
SentimentSource,
|
|
9
|
+
SentimentSummary,
|
|
10
|
+
SentinelRecord,
|
|
11
|
+
TrendResult,
|
|
12
|
+
} from "./types.js";
|
|
6
13
|
|
|
7
14
|
export class SentimentPipeline {
|
|
8
15
|
constructor(
|
|
@@ -39,7 +46,12 @@ export class SentimentPipeline {
|
|
|
39
46
|
// Compute divergence from fresh records
|
|
40
47
|
let divergence: DivergenceResult | null = null;
|
|
41
48
|
const sourceGroups = groupBySource(scored);
|
|
42
|
-
const sourceStats: {
|
|
49
|
+
const sourceStats: {
|
|
50
|
+
twitter?: SourceStats;
|
|
51
|
+
reddit?: SourceStats;
|
|
52
|
+
web?: SourceStats;
|
|
53
|
+
finnhub?: SourceStats;
|
|
54
|
+
} = {};
|
|
43
55
|
|
|
44
56
|
for (const [source, recs] of Object.entries(sourceGroups)) {
|
|
45
57
|
// Exclude comments from divergence calculation
|
|
@@ -54,7 +66,18 @@ export class SentimentPipeline {
|
|
|
54
66
|
divergence = computeDivergence(sourceStats, this.config.divergenceThreshold);
|
|
55
67
|
}
|
|
56
68
|
|
|
57
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
fresh: scored,
|
|
71
|
+
trend,
|
|
72
|
+
divergence,
|
|
73
|
+
warnings,
|
|
74
|
+
insight: buildSentimentInsight({
|
|
75
|
+
query,
|
|
76
|
+
records: scored,
|
|
77
|
+
config: this.config,
|
|
78
|
+
aggregate: true,
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
58
81
|
}
|
|
59
82
|
}
|
|
60
83
|
|
package/src/sentiment/scorer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BEARISH_TERMS, BULLISH_TERMS } from "./keywords.js";
|
|
2
2
|
import type { SentinelRecord } from "./types.js";
|
|
3
3
|
|
|
4
4
|
const TICKER_REGEX = /\$([A-Z]{1,5})\b/g;
|
|
@@ -7,6 +7,8 @@ interface ScoreResult {
|
|
|
7
7
|
score: number;
|
|
8
8
|
confidence: number;
|
|
9
9
|
tickers: string[];
|
|
10
|
+
bullishTerms: string[];
|
|
11
|
+
bearishTerms: string[];
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export function keywordScore(record: SentinelRecord): ScoreResult {
|
|
@@ -17,11 +19,14 @@ export function keywordScore(record: SentinelRecord): ScoreResult {
|
|
|
17
19
|
let bearishWeight = 0;
|
|
18
20
|
let bullishCount = 0;
|
|
19
21
|
let bearishCount = 0;
|
|
22
|
+
const bullishTerms: string[] = [];
|
|
23
|
+
const bearishTerms: string[] = [];
|
|
20
24
|
|
|
21
25
|
for (const term of BULLISH_TERMS) {
|
|
22
26
|
if (lower.includes(term)) {
|
|
23
27
|
bullishCount++;
|
|
24
28
|
bullishWeight += engagement;
|
|
29
|
+
bullishTerms.push(term);
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
|
|
@@ -29,6 +34,7 @@ export function keywordScore(record: SentinelRecord): ScoreResult {
|
|
|
29
34
|
if (lower.includes(term)) {
|
|
30
35
|
bearishCount++;
|
|
31
36
|
bearishWeight += engagement;
|
|
37
|
+
bearishTerms.push(term);
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
40
|
|
|
@@ -59,7 +65,7 @@ export function keywordScore(record: SentinelRecord): ScoreResult {
|
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
return { score, confidence, tickers };
|
|
68
|
+
return { score, confidence, tickers, bullishTerms, bearishTerms };
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
export function scoreRecords(records: SentinelRecord[]): SentinelRecord[] {
|
|
@@ -73,6 +79,11 @@ export function scoreRecords(records: SentinelRecord[]): SentinelRecord[] {
|
|
|
73
79
|
method: "keyword" as const,
|
|
74
80
|
tickers: result.tickers,
|
|
75
81
|
},
|
|
82
|
+
metadata: {
|
|
83
|
+
...record.metadata,
|
|
84
|
+
matchedBullishTerms: result.bullishTerms,
|
|
85
|
+
matchedBearishTerms: result.bearishTerms,
|
|
86
|
+
},
|
|
76
87
|
};
|
|
77
88
|
});
|
|
78
89
|
}
|
package/src/sentiment/store.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import Database from "better-sqlite3";
|
|
2
1
|
import { mkdirSync } from "node:fs";
|
|
3
2
|
import { dirname } from "node:path";
|
|
4
|
-
import
|
|
3
|
+
import Database from "better-sqlite3";
|
|
4
|
+
import type { SentimentSource, SentinelRecord, TrendBucket } from "./types.js";
|
|
5
5
|
|
|
6
6
|
const SCHEMA_VERSION = 1;
|
|
7
7
|
|
package/src/sentiment/trends.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DivergenceResult, SentimentSource, TrendBucket, TrendResult } from "./types.js";
|
|
2
2
|
|
|
3
3
|
const SPARKLINE_CHARS = "▁▂▃▄▅▆▇█";
|
|
4
4
|
|
|
@@ -24,7 +24,8 @@ export function computeTrend(
|
|
|
24
24
|
): TrendResult {
|
|
25
25
|
const scores = buckets.map((b) => b.avgScore);
|
|
26
26
|
const totalCount = buckets.reduce((sum, b) => sum + b.count, 0);
|
|
27
|
-
const avgScore =
|
|
27
|
+
const avgScore =
|
|
28
|
+
totalCount === 0 ? 0 : buckets.reduce((sum, b) => sum + b.avgScore * b.count, 0) / totalCount;
|
|
28
29
|
|
|
29
30
|
const sparkline = renderSparkline(scores);
|
|
30
31
|
|
|
@@ -45,7 +46,12 @@ export interface SourceStats {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export function computeDivergence(
|
|
48
|
-
sources: {
|
|
49
|
+
sources: {
|
|
50
|
+
twitter?: SourceStats;
|
|
51
|
+
reddit?: SourceStats;
|
|
52
|
+
web?: SourceStats;
|
|
53
|
+
finnhub?: SourceStats;
|
|
54
|
+
},
|
|
49
55
|
threshold: number,
|
|
50
56
|
): DivergenceResult {
|
|
51
57
|
const retailSources: SourceStats[] = [];
|
package/src/sentiment/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { SentimentInsight } from "../types/sentiment.js";
|
|
2
|
+
|
|
1
3
|
export const SENTIMENT_SOURCES = ["twitter", "reddit", "web", "finnhub"] as const;
|
|
2
4
|
export type SentimentSource = (typeof SENTIMENT_SOURCES)[number];
|
|
3
5
|
|
|
@@ -9,9 +11,9 @@ export interface SentinelEngagement {
|
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export interface SentinelSentiment {
|
|
12
|
-
score: number;
|
|
13
|
-
confidence: number;
|
|
14
|
-
method: "keyword";
|
|
14
|
+
score: number; // -1.0 to +1.0
|
|
15
|
+
confidence: number; // 0.0 to 1.0
|
|
16
|
+
method: "keyword"; // v1 is keyword-only; future: "llm"
|
|
15
17
|
tickers: string[];
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -36,7 +38,8 @@ export function isSentinelRecord(val: unknown): val is SentinelRecord {
|
|
|
36
38
|
const r = val as Record<string, unknown>;
|
|
37
39
|
|
|
38
40
|
if (typeof r.id !== "string") return false;
|
|
39
|
-
if (typeof r.source !== "string" || !SENTIMENT_SOURCES.includes(r.source as SentimentSource))
|
|
41
|
+
if (typeof r.source !== "string" || !SENTIMENT_SOURCES.includes(r.source as SentimentSource))
|
|
42
|
+
return false;
|
|
40
43
|
if (typeof r.sourceId !== "string") return false;
|
|
41
44
|
if (typeof r.query !== "string") return false;
|
|
42
45
|
if (r.title !== null && typeof r.title !== "string") return false;
|
|
@@ -105,4 +108,5 @@ export interface SentimentSummary {
|
|
|
105
108
|
trend: TrendResult[] | null;
|
|
106
109
|
divergence: DivergenceResult | null;
|
|
107
110
|
warnings: string[];
|
|
111
|
+
insight?: SentimentInsight;
|
|
108
112
|
}
|
package/src/system-prompt.ts
CHANGED
|
@@ -13,14 +13,14 @@ ${memoryContext}`
|
|
|
13
13
|
You are an analyst, not a fiduciary advisor. When asked for entry levels, price targets, stops, position sizes, or allocations, you COMMIT to specific numbers backed by the data you fetched. Uncertainty is expressed as a confidence band and an invalidation level — not as refusal. Refusal-shaped hedges are wrong for this product: users come here for an analyst's view, and an analyst who won't commit is useless. For conceptual education questions, teach the concept directly, do not name tool functions, and do not append analyst-view, confidence-band, or invalidation boilerplate. For valuation-metric education, start with "Bottom line", use a heading exactly named "Practical workflow" with numbered question-driven application steps, explain where the metric misleads, include a compact cross-check table with why/when each metric helps, include relevant trailing, forward, normalized, or cyclically adjusted variants when useful, and end with a heading exactly named "Quick checklist".
|
|
14
14
|
|
|
15
15
|
## Available Tools
|
|
16
|
-
- **Market Data**: get_stock_quote, get_stock_history, get_crypto_price, get_crypto_history —
|
|
16
|
+
- **Market Data**: screen_stocks, get_stock_quote, get_stock_history, get_crypto_price, get_crypto_history — use screen_stocks for breadth and screening prompts such as large-cap lists, oversold stocks, market movers, and filtered market scans; use get_stock_quote/get_stock_history for a single-security quote or historical OHLCV
|
|
17
17
|
- **Fundamentals**: get_company_overview, get_financials, get_earnings, compute_dcf, compare_companies, get_sec_filings — company financials, valuation metrics, DCF intrinsic value, peer comparison, and SEC EDGAR filings (10-K, 10-Q, 8-K)
|
|
18
18
|
- **Technical Analysis**: get_technical_indicators, backtest_strategy — SMA, EMA, RSI, MACD, Bollinger Bands, OBV, VWAP computed from price data, plus simple strategy backtesting
|
|
19
19
|
- **Macro**: get_economic_data, get_fear_greed — FRED economic indicators and market sentiment
|
|
20
20
|
- **Sentiment**: get_reddit_sentiment, get_twitter_sentiment, get_web_sentiment, get_sentiment_trend, get_sentiment_summary — retail and news sentiment from Reddit, Twitter/X, and web sources with historical trends
|
|
21
21
|
- **Options**: get_option_chain — full options chain with strikes, bids/asks, volume, OI, IV, and computed Greeks (delta, gamma, theta, vega, rho)
|
|
22
|
-
- **Portfolio**: track_portfolio, analyze_risk, manage_watchlist, analyze_correlation, track_prediction — position tracking, P&L, Sharpe ratio, VaR, watchlist
|
|
23
|
-
- **User Interaction**: ask_user — ask clarification questions
|
|
22
|
+
- **Portfolio**: track_portfolio, analyze_risk, manage_watchlist, analyze_correlation, track_prediction, manage_alerts, daily_watchlist_report, manage_notifications — position tracking, P&L, Sharpe ratio, VaR, watchlist tracking, durable local alerts, daily watchlist reports, notification history, correlation matrix, and prediction tracking with accuracy scoring
|
|
23
|
+
- **User Interaction**: ask_user — ask clarification questions and setup confirmations
|
|
24
24
|
|
|
25
25
|
## Analytical Framework
|
|
26
26
|
When analyzing a stock, follow these steps in order:
|
|
@@ -45,6 +45,7 @@ Calibrate explanation depth from conversational signals: the user's vocabulary i
|
|
|
45
45
|
|
|
46
46
|
## Guidelines
|
|
47
47
|
- Always fetch data with tools before stating prices, ratios, or metrics. Never guess financial numbers. Every substantive response should be backed by at least one tool call — if you find yourself writing a response with zero tool calls, stop and think about what data would make it better.
|
|
48
|
+
- Use screen_stocks for breadth and screening prompts ("which large caps...", "find oversold stocks...", "market movers by filter"). Use get_stock_quote, get_stock_history, get_option_chain, fundamentals, and analysis workflows for single-security quote, history, options, DCF, or company-analysis prompts.
|
|
48
49
|
- For current single-stock recommendations, state the quote or tool-output date in the final answer. If tool output says the market is closed, the quote is delayed, or this is the last available quote, carry that freshness note into the final answer. If DCF or another valuation model is unavailable or not meaningful, do not let that tool failure become the whole valuation view; use supported fallback valuation lenses such as relative multiples, growth-adjusted multiples, cash-flow quality, balance-sheet risk, and historical range context. Do not make missing fundamentals the main thesis when quote, earnings, technicals, sentiment, or news are available; use those data points plus structural business risks to give a clear call, position sizing, and entry strategy.
|
|
49
50
|
- For ticker-specific sentiment prompts, call get_stock_quote before the final answer and state whether sentiment diverges from price action. For sentiment-only prompts, include source-coverage risk, low sample counts, missing sources, and how those gaps downgrade confidence.
|
|
50
51
|
- For options analysis, use get_option_chain to see the full chain with Greeks. Pay attention to put/call ratio, unusual volume, and IV levels.
|
|
@@ -79,12 +80,8 @@ Do NOT ask clarifying questions when:
|
|
|
79
80
|
|
|
80
81
|
Keep questions concise and offer specific options when possible. Prefer select-type questions over open-ended text input to minimize user effort.
|
|
81
82
|
|
|
82
|
-
## Twitter
|
|
83
|
-
get_twitter_sentiment
|
|
84
|
-
1. Use ask_user (confirm) to ask: "Twitter sentiment requires a one-time login. A browser will open — want to proceed?"
|
|
85
|
-
2. If confirmed, call trigger_twitter_login. It opens a browser, waits for the user to log in, and returns success/failure.
|
|
86
|
-
3. On success, retry get_twitter_sentiment with the original query.
|
|
87
|
-
If the user declines, skip Twitter sentiment and continue with other available data sources.
|
|
83
|
+
## Twitter/X External Tool Setup
|
|
84
|
+
get_twitter_sentiment uses the external twitter-cli command and the user's normal browser session. If the tool says twitter-cli is missing, ask the user whether they want to install it with \`uv tool install twitter-cli\`, skip X for this query, or always skip X. If the tool says browser cookies or the X session are missing or stale, ask the user to log into or refresh x.com in a supported browser, then retry only after they confirm. Do not use the retired browser-login tool.
|
|
88
85
|
|
|
89
86
|
## After Clarification: Fetch Data Immediately
|
|
90
87
|
CRITICAL: After ask_user answers come back, your NEXT action MUST be tool calls — not a text response. You are a data agent, not a chatbot. Never respond with generic investment categories or tell the user to come back with tickers. YOU pick the relevant assets and indicators based on what you learned, then fetch the data.
|
package/src/tool-kit.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type { TSchema } from "@sinclair/typebox";
|
|
2
1
|
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
3
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
4
4
|
import { agentToolToPiTool } from "./pi/tool-adapter.js";
|
|
5
5
|
|
|
6
|
+
export type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
7
|
+
export type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
export { Type } from "@sinclair/typebox";
|
|
6
9
|
// Re-exports for tool authors — import from "opencandle/tool-kit"
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
+
export { Cache, cache, TTL } from "./infra/cache.js";
|
|
11
|
+
export { type HttpClientOptions, HttpError, httpGet } from "./infra/http-client.js";
|
|
12
|
+
export { RateLimiter, rateLimiter } from "./infra/rate-limiter.js";
|
|
10
13
|
export { agentToolToPiTool } from "./pi/tool-adapter.js";
|
|
11
|
-
export { Type } from "@sinclair/typebox";
|
|
12
|
-
export type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
export type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
14
14
|
|
|
15
15
|
// Module-level registry — all extensions run in the same Node.js process,
|
|
16
16
|
// so keep a deduped index keyed by tool name.
|
|
@@ -20,7 +20,8 @@ export function getAddonToolDescriptions(): ReadonlyArray<{ name: string; descri
|
|
|
20
20
|
return Array.from(addonToolRegistry.values());
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const SNAKE_CASE_VERB_RE =
|
|
23
|
+
const SNAKE_CASE_VERB_RE =
|
|
24
|
+
/^(get|analyze|search|calculate|compare|compute|track|manage|backtest|list|fetch|check)_[a-z][a-z0-9_]*$/;
|
|
24
25
|
|
|
25
26
|
export interface ToolConfig<TParams extends TSchema, TDetails = unknown> {
|
|
26
27
|
name: string;
|
|
@@ -36,7 +37,7 @@ export function createTool<TParams extends TSchema, TDetails = unknown>(
|
|
|
36
37
|
if (!config.name || !SNAKE_CASE_VERB_RE.test(config.name)) {
|
|
37
38
|
throw new Error(
|
|
38
39
|
`Invalid tool name "${config.name}": must be snake_case and start with a verb prefix ` +
|
|
39
|
-
|
|
40
|
+
`(get_, analyze_, search_, calculate_, compare_, compute_, track_, manage_, backtest_, list_, fetch_, check_)`,
|
|
40
41
|
);
|
|
41
42
|
}
|
|
42
43
|
if (!config.description || config.description.trim().length === 0) {
|