opencandle 0.3.0 → 0.5.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/LICENSE +1 -1
- package/README.md +106 -14
- package/assets/logo.svg +187 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +40 -3
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +25 -0
- package/dist/config.js +72 -0
- package/dist/config.js.map +1 -1
- package/dist/infra/browser.d.ts +11 -3
- package/dist/infra/browser.js +2 -1
- package/dist/infra/browser.js.map +1 -1
- package/dist/infra/native-dependencies.d.ts +1 -0
- package/dist/infra/native-dependencies.js +10 -0
- package/dist/infra/native-dependencies.js.map +1 -0
- package/dist/infra/node-version.d.ts +2 -0
- package/dist/infra/node-version.js +23 -0
- package/dist/infra/node-version.js.map +1 -0
- package/dist/infra/rate-limiter.d.ts +4 -0
- package/dist/infra/rate-limiter.js +5 -1
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/memory/index.d.ts +2 -0
- package/dist/memory/index.js +1 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/manager.d.ts +9 -0
- package/dist/memory/manager.js +28 -11
- package/dist/memory/manager.js.map +1 -1
- package/dist/memory/sqlite.js +42 -4
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/memory/storage.d.ts +7 -0
- package/dist/memory/storage.js +3 -3
- package/dist/memory/storage.js.map +1 -1
- package/dist/memory/tool-defaults.d.ts +8 -0
- package/dist/memory/tool-defaults.js +59 -0
- package/dist/memory/tool-defaults.js.map +1 -0
- package/dist/memory/types.js +4 -0
- package/dist/memory/types.js.map +1 -1
- package/dist/onboarding/connect.d.ts +13 -1
- package/dist/onboarding/connect.js +21 -10
- package/dist/onboarding/connect.js.map +1 -1
- package/dist/onboarding/prompt-user.d.ts +1 -1
- package/dist/onboarding/providers.d.ts +7 -0
- package/dist/onboarding/providers.js +6 -3
- package/dist/onboarding/providers.js.map +1 -1
- package/dist/onboarding/tool-helpers.d.ts +1 -1
- package/dist/pi/opencandle-extension.d.ts +7 -1
- package/dist/pi/opencandle-extension.js +391 -21
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session-storage.d.ts +2 -0
- package/dist/pi/session-storage.js +5 -0
- package/dist/pi/session-storage.js.map +1 -0
- package/dist/pi/session.d.ts +4 -1
- package/dist/pi/session.js +25 -3
- package/dist/pi/session.js.map +1 -1
- package/dist/pi/setup.d.ts +1 -1
- package/dist/pi/setup.js +11 -1
- package/dist/pi/setup.js.map +1 -1
- package/dist/pi/tool-adapter.d.ts +2 -2
- package/dist/pi/tool-adapter.js +14 -1
- package/dist/pi/tool-adapter.js.map +1 -1
- package/dist/prompts/context-builder.d.ts +40 -3
- package/dist/prompts/context-builder.js +140 -19
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/prompts/disclaimer.d.ts +6 -0
- package/dist/prompts/disclaimer.js +9 -0
- package/dist/prompts/disclaimer.js.map +1 -0
- package/dist/prompts/policy-cards.d.ts +13 -0
- package/dist/prompts/policy-cards.js +197 -0
- package/dist/prompts/policy-cards.js.map +1 -0
- package/dist/prompts/sections.js +3 -3
- package/dist/prompts/sections.js.map +1 -1
- package/dist/prompts/workflow-prompts.d.ts +8 -0
- package/dist/prompts/workflow-prompts.js +208 -22
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/alpha-vantage.js +23 -1
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/sec-edgar.d.ts +8 -1
- package/dist/providers/sec-edgar.js +172 -5
- package/dist/providers/sec-edgar.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +2 -0
- package/dist/providers/yahoo-finance.js +203 -35
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.d.ts +3 -0
- package/dist/routing/classify-intent.js +82 -3
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/defaults.js +4 -4
- package/dist/routing/defaults.js.map +1 -1
- package/dist/routing/entity-extractor.d.ts +1 -0
- package/dist/routing/entity-extractor.js +158 -12
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/index.d.ts +10 -0
- package/dist/routing/index.js +7 -0
- package/dist/routing/index.js.map +1 -1
- package/dist/routing/legacy-rule-router.d.ts +9 -0
- package/dist/routing/legacy-rule-router.js +12 -0
- package/dist/routing/legacy-rule-router.js.map +1 -0
- package/dist/routing/planning.d.ts +54 -0
- package/dist/routing/planning.js +531 -0
- package/dist/routing/planning.js.map +1 -0
- package/dist/routing/route-manifest.d.ts +35 -0
- package/dist/routing/route-manifest.js +221 -0
- package/dist/routing/route-manifest.js.map +1 -0
- package/dist/routing/router-llm-client.d.ts +11 -0
- package/dist/routing/router-llm-client.js +42 -0
- package/dist/routing/router-llm-client.js.map +1 -0
- package/dist/routing/router-prompt.d.ts +2 -0
- package/dist/routing/router-prompt.js +141 -0
- package/dist/routing/router-prompt.js.map +1 -0
- package/dist/routing/router-types.d.ts +71 -0
- package/dist/routing/router-types.js +2 -0
- package/dist/routing/router-types.js.map +1 -0
- package/dist/routing/router.d.ts +11 -0
- package/dist/routing/router.js +638 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/routing/slot-resolver.js +46 -6
- package/dist/routing/slot-resolver.js.map +1 -1
- package/dist/routing/turn-context.d.ts +44 -0
- package/dist/routing/turn-context.js +45 -0
- package/dist/routing/turn-context.js.map +1 -0
- package/dist/routing/types.d.ts +13 -1
- package/dist/runtime/answer-contracts.d.ts +82 -0
- package/dist/runtime/answer-contracts.js +414 -0
- package/dist/runtime/answer-contracts.js.map +1 -0
- package/dist/runtime/artifact-contracts.d.ts +14 -0
- package/dist/runtime/artifact-contracts.js +57 -0
- package/dist/runtime/artifact-contracts.js.map +1 -0
- package/dist/runtime/planning-evidence.d.ts +99 -0
- package/dist/runtime/planning-evidence.js +445 -0
- package/dist/runtime/planning-evidence.js.map +1 -0
- package/dist/runtime/session-coordinator.d.ts +81 -3
- package/dist/runtime/session-coordinator.js +201 -17
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/tool-defaults-wrapper.d.ts +3 -0
- package/dist/runtime/tool-defaults-wrapper.js +25 -0
- package/dist/runtime/tool-defaults-wrapper.js.map +1 -0
- package/dist/sentiment/store.js +5 -0
- package/dist/sentiment/store.js.map +1 -1
- package/dist/system-prompt.js +23 -12
- package/dist/system-prompt.js.map +1 -1
- package/dist/tool-kit.d.ts +4 -4
- package/dist/tools/fundamentals/company-overview.d.ts +1 -1
- package/dist/tools/fundamentals/company-overview.js +1 -1
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.d.ts +1 -1
- package/dist/tools/fundamentals/comps.js +1 -1
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.d.ts +1 -1
- package/dist/tools/fundamentals/dcf.js +1 -1
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.d.ts +1 -1
- package/dist/tools/fundamentals/earnings.js +1 -1
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.d.ts +1 -1
- package/dist/tools/fundamentals/financials.js +1 -1
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.d.ts +2 -1
- package/dist/tools/fundamentals/sec-filings.js +19 -2
- package/dist/tools/fundamentals/sec-filings.js.map +1 -1
- package/dist/tools/index.d.ts +29 -1
- package/dist/tools/index.js +30 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.d.ts +1 -1
- package/dist/tools/interaction/twitter-login.d.ts +1 -1
- package/dist/tools/macro/fear-greed.d.ts +1 -1
- package/dist/tools/macro/fear-greed.js +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 +29 -5
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/market/crypto-history.d.ts +1 -1
- package/dist/tools/market/crypto-history.js +18 -2
- package/dist/tools/market/crypto-history.js.map +1 -1
- package/dist/tools/market/crypto-price.d.ts +1 -1
- package/dist/tools/market/crypto-price.js +1 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/search-ticker.d.ts +1 -1
- package/dist/tools/market/search-ticker.js +1 -1
- package/dist/tools/market/search-ticker.js.map +1 -1
- package/dist/tools/market/stock-history.d.ts +1 -1
- package/dist/tools/market/stock-history.js +1 -1
- package/dist/tools/market/stock-history.js.map +1 -1
- package/dist/tools/market/stock-quote.d.ts +1 -1
- package/dist/tools/market/stock-quote.js +1 -1
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/greeks.js +0 -1
- package/dist/tools/options/greeks.js.map +1 -1
- package/dist/tools/options/option-chain.d.ts +1 -1
- package/dist/tools/options/option-chain.js +13 -5
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/correlation.d.ts +1 -1
- package/dist/tools/portfolio/correlation.js +1 -1
- package/dist/tools/portfolio/correlation.js.map +1 -1
- package/dist/tools/portfolio/holdings-overlap.d.ts +8 -0
- package/dist/tools/portfolio/holdings-overlap.js +105 -0
- package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
- package/dist/tools/portfolio/predictions.d.ts +1 -1
- package/dist/tools/portfolio/predictions.js +1 -1
- 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 +1 -1
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/portfolio/tracker.d.ts +1 -1
- package/dist/tools/portfolio/tracker.js +1 -1
- package/dist/tools/portfolio/tracker.js.map +1 -1
- package/dist/tools/portfolio/watchlist.d.ts +1 -1
- package/dist/tools/portfolio/watchlist.js +12 -4
- package/dist/tools/portfolio/watchlist.js.map +1 -1
- package/dist/tools/sentiment/reddit-sentiment.d.ts +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.d.ts +1 -1
- package/dist/tools/sentiment/sentiment-summary.js +57 -2
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
- package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
- package/dist/tools/sentiment/twitter-sentiment.d.ts +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/web-search.d.ts +1 -1
- package/dist/tools/sentiment/web-search.js +32 -3
- package/dist/tools/sentiment/web-search.js.map +1 -1
- package/dist/tools/sentiment/web-sentiment.d.ts +1 -1
- package/dist/tools/sentiment/web-sentiment.js +1 -1
- package/dist/tools/sentiment/web-sentiment.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +3 -3
- package/dist/tools/technical/backtest.js +41 -27
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.d.ts +1 -1
- package/dist/tools/technical/indicators.js +8 -4
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/options.d.ts +10 -0
- package/dist/types/portfolio.d.ts +27 -0
- package/dist/workflows/compare-assets.js +38 -2
- package/dist/workflows/compare-assets.js.map +1 -1
- package/dist/workflows/options-screener.js +94 -8
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.js +9 -5
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/ask-user-bridge.ts +82 -0
- package/gui/server/background-quotes.ts +31 -0
- package/gui/server/chat-event-adapter.ts +142 -0
- package/gui/server/gui-session-manager.ts +5 -0
- package/gui/server/invoke-tool.ts +89 -0
- package/gui/server/live-chat-event-adapter.ts +181 -0
- package/gui/server/model-setup.ts +100 -0
- package/gui/server/package.json +5 -0
- package/gui/server/projector.ts +254 -0
- package/gui/server/prompt-observation.ts +61 -0
- package/gui/server/server.ts +703 -0
- package/gui/server/session-actions.ts +31 -0
- package/gui/server/session-entry-wait.ts +81 -0
- package/gui/server/tool-metadata.ts +88 -0
- package/gui/server/websocket.ts +128 -0
- package/gui/server/writer-lock.ts +118 -0
- package/gui/shared/chat-events.ts +118 -0
- package/gui/shared/event-reducer.ts +186 -0
- package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +1 -0
- package/gui/web/dist/assets/index-Bxt9QpLX.css +1 -0
- package/gui/web/dist/assets/index-CZ9DHZYy.js +67 -0
- package/gui/web/dist/assets/logo-CWpt6Y2a.svg +187 -0
- package/gui/web/dist/index.html +17 -0
- package/package.json +50 -18
- package/src/analysts/contracts.ts +189 -0
- package/src/analysts/orchestrator.ts +300 -0
- package/src/cli.ts +206 -0
- package/src/config.ts +245 -0
- package/src/index.ts +5 -0
- package/src/infra/browser.ts +111 -0
- package/src/infra/cache.ts +103 -0
- package/src/infra/http-client.ts +68 -0
- package/src/infra/index.ts +18 -0
- package/src/infra/native-dependencies.ts +12 -0
- package/src/infra/node-version.ts +24 -0
- package/src/infra/open-url.ts +28 -0
- package/src/infra/opencandle-paths.ts +64 -0
- package/src/infra/rate-limiter.ts +73 -0
- package/src/memory/index.ts +10 -0
- package/src/memory/manager.ts +192 -0
- package/src/memory/preference-extractor.ts +106 -0
- package/src/memory/retrieval.ts +70 -0
- package/src/memory/sqlite.ts +172 -0
- package/src/memory/storage.ts +205 -0
- package/src/memory/tool-defaults.ts +87 -0
- package/src/memory/types.ts +71 -0
- package/src/onboarding/connect.ts +184 -0
- package/src/onboarding/credential-interceptor.ts +134 -0
- package/src/onboarding/degradation-accumulator.ts +79 -0
- package/src/onboarding/prompt-user.ts +85 -0
- package/src/onboarding/providers.ts +315 -0
- package/src/onboarding/state.ts +218 -0
- package/src/onboarding/tool-helpers.ts +111 -0
- package/src/onboarding/tool-tags.ts +201 -0
- package/src/onboarding/validation.ts +158 -0
- package/src/pi/opencandle-extension.ts +955 -0
- package/src/pi/session-storage.ts +5 -0
- package/src/pi/session.ts +81 -0
- package/src/pi/setup.ts +381 -0
- package/src/pi/tool-adapter.ts +36 -0
- package/src/prompts/context-builder.ts +315 -0
- package/src/prompts/disclaimer.ts +9 -0
- package/src/prompts/policy-cards.ts +220 -0
- package/src/prompts/sections.ts +46 -0
- package/src/prompts/workflow-prompts.ts +433 -0
- package/src/providers/alpha-vantage.ts +315 -0
- package/src/providers/coingecko.ts +96 -0
- package/src/providers/exa-search.ts +373 -0
- package/src/providers/fear-greed.ts +45 -0
- package/src/providers/finnhub.ts +124 -0
- package/src/providers/fred.ts +83 -0
- package/src/providers/index.ts +9 -0
- package/src/providers/provider-credential-error.ts +23 -0
- package/src/providers/reddit.ts +151 -0
- package/src/providers/sec-edgar.ts +312 -0
- package/src/providers/twitter.ts +173 -0
- package/src/providers/web-search.ts +293 -0
- package/src/providers/with-fallback.ts +41 -0
- package/src/providers/wrap-provider.ts +64 -0
- package/src/providers/yahoo-finance.ts +534 -0
- package/src/routing/classify-intent.ts +285 -0
- package/src/routing/defaults.ts +29 -0
- package/src/routing/entity-extractor.ts +291 -0
- package/src/routing/index.ts +70 -0
- package/src/routing/legacy-rule-router.ts +13 -0
- package/src/routing/planning.ts +732 -0
- package/src/routing/route-manifest.ts +287 -0
- package/src/routing/router-llm-client.ts +51 -0
- package/src/routing/router-prompt.ts +163 -0
- package/src/routing/router-types.ts +87 -0
- package/src/routing/router.ts +712 -0
- package/src/routing/slot-resolver.ts +190 -0
- package/src/routing/turn-context.ts +111 -0
- package/src/routing/types.ts +75 -0
- package/src/runtime/answer-contracts.ts +633 -0
- package/src/runtime/artifact-contracts.ts +76 -0
- package/src/runtime/evidence.ts +77 -0
- package/src/runtime/planning-evidence.ts +591 -0
- package/src/runtime/prompt-step.ts +75 -0
- package/src/runtime/provider-tracker.ts +40 -0
- package/src/runtime/run-context.ts +22 -0
- package/src/runtime/session-coordinator.ts +472 -0
- package/src/runtime/tool-defaults-wrapper.ts +35 -0
- package/src/runtime/validation.ts +214 -0
- package/src/runtime/workflow-events.ts +75 -0
- package/src/runtime/workflow-runner.ts +188 -0
- package/src/runtime/workflow-types.ts +102 -0
- package/src/sentiment/adapters/finnhub.ts +44 -0
- package/src/sentiment/adapters/reddit.ts +65 -0
- package/src/sentiment/adapters/twitter.ts +36 -0
- package/src/sentiment/adapters/web.ts +44 -0
- package/src/sentiment/index.ts +58 -0
- package/src/sentiment/keywords.ts +9 -0
- package/src/sentiment/pipeline.ts +68 -0
- package/src/sentiment/scorer.ts +78 -0
- package/src/sentiment/store.ts +260 -0
- package/src/sentiment/trends.ts +90 -0
- package/src/sentiment/types.ts +108 -0
- package/src/system-prompt.ts +118 -0
- package/src/tool-kit.ts +68 -0
- package/src/tools/AGENTS.md +36 -0
- package/src/tools/fundamentals/company-overview.ts +54 -0
- package/src/tools/fundamentals/comps.ts +156 -0
- package/src/tools/fundamentals/dcf.ts +267 -0
- package/src/tools/fundamentals/earnings.ts +47 -0
- package/src/tools/fundamentals/financials.ts +54 -0
- package/src/tools/fundamentals/sec-filings.ts +84 -0
- package/src/tools/index.ts +91 -0
- package/src/tools/interaction/ask-user.ts +81 -0
- package/src/tools/interaction/twitter-login.ts +93 -0
- package/src/tools/macro/fear-greed.ts +41 -0
- package/src/tools/macro/fred-data.ts +80 -0
- package/src/tools/market/crypto-history.ts +67 -0
- package/src/tools/market/crypto-price.ts +53 -0
- package/src/tools/market/search-ticker.ts +53 -0
- package/src/tools/market/stock-history.ts +79 -0
- package/src/tools/market/stock-quote.ts +64 -0
- package/src/tools/options/greeks.ts +81 -0
- package/src/tools/options/option-chain.ts +96 -0
- package/src/tools/portfolio/correlation.ts +162 -0
- package/src/tools/portfolio/holdings-overlap.ts +123 -0
- package/src/tools/portfolio/predictions.ts +253 -0
- package/src/tools/portfolio/risk-analysis.ts +134 -0
- package/src/tools/portfolio/tracker.ts +147 -0
- package/src/tools/portfolio/watchlist.ts +159 -0
- package/src/tools/sentiment/reddit-sentiment.ts +164 -0
- package/src/tools/sentiment/sentiment-summary.ts +316 -0
- package/src/tools/sentiment/sentiment-trend.ts +58 -0
- package/src/tools/sentiment/twitter-sentiment.ts +96 -0
- package/src/tools/sentiment/web-search.ts +183 -0
- package/src/tools/sentiment/web-sentiment.ts +76 -0
- package/src/tools/technical/backtest.ts +267 -0
- package/src/tools/technical/indicators.ts +256 -0
- package/src/types/fundamentals.ts +46 -0
- package/src/types/index.ts +20 -0
- package/src/types/macro.ts +27 -0
- package/src/types/market.ts +43 -0
- package/src/types/options.ts +52 -0
- package/src/types/portfolio.ts +73 -0
- package/src/types/sentiment.ts +70 -0
- package/src/workflows/compare-assets.ts +75 -0
- package/src/workflows/index.ts +4 -0
- package/src/workflows/options-screener.ts +127 -0
- package/src/workflows/portfolio-builder.ts +56 -0
- package/src/workflows/types.ts +4 -0
- package/dist/runtime/index.d.ts +0 -16
- package/dist/runtime/index.js +0 -10
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/provider-ids.d.ts +0 -14
- package/dist/runtime/provider-ids.js +0 -14
- package/dist/runtime/provider-ids.js.map +0 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import { searchWeb } from "../../providers/web-search.js";
|
|
4
|
+
import { WebAdapter } from "../../sentiment/adapters/web.js";
|
|
5
|
+
import { getSentimentPipeline } from "../../sentiment/index.js";
|
|
6
|
+
|
|
7
|
+
const params = Type.Object({
|
|
8
|
+
query: Type.String({ description: "Ticker or topic to search for web/news sentiment" }),
|
|
9
|
+
freshness: Type.Optional(
|
|
10
|
+
Type.Union([Type.Literal("day"), Type.Literal("week"), Type.Literal("month")], {
|
|
11
|
+
description: "Time window for results. Default: day",
|
|
12
|
+
}),
|
|
13
|
+
),
|
|
14
|
+
limit: Type.Optional(
|
|
15
|
+
Type.Number({ description: "Max results. Default: 10, max: 20" }),
|
|
16
|
+
),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const webSentimentTool: AgentTool<typeof params> = {
|
|
20
|
+
name: "get_web_sentiment",
|
|
21
|
+
label: "Web/News Sentiment",
|
|
22
|
+
description:
|
|
23
|
+
"Analyze sentiment from web and news search results for a ticker or topic. Returns scored results with aggregate sentiment.",
|
|
24
|
+
parameters: params,
|
|
25
|
+
async execute(_toolCallId, args) {
|
|
26
|
+
const freshness = args.freshness ?? "day";
|
|
27
|
+
const limit = Math.min(args.limit ?? 10, 20);
|
|
28
|
+
|
|
29
|
+
const providerResult = await searchWeb(args.query, { freshness, limit, category: "news" });
|
|
30
|
+
|
|
31
|
+
if (providerResult.status === "unavailable") {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `⚠ Web sentiment unavailable for "${args.query}" (${providerResult.reason}).` }],
|
|
34
|
+
details: null as any,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const adapter = new WebAdapter();
|
|
39
|
+
const records = adapter.mapToRecords(providerResult.data, args.query);
|
|
40
|
+
const pipeline = getSentimentPipeline();
|
|
41
|
+
const result = await pipeline.processRecords(records, args.query);
|
|
42
|
+
|
|
43
|
+
const lines: string[] = [];
|
|
44
|
+
if (result.fresh.length === 0) {
|
|
45
|
+
lines.push(`No web results found for "${args.query}".`);
|
|
46
|
+
} else {
|
|
47
|
+
const avgScore = result.fresh.reduce((s, r) => s + r.sentiment.score, 0) / result.fresh.length;
|
|
48
|
+
const label = sentimentLabel(avgScore);
|
|
49
|
+
lines.push(`**Web sentiment for "${args.query}"** — ${result.fresh.length} results (${label}, ${avgScore.toFixed(2)})`);
|
|
50
|
+
lines.push("");
|
|
51
|
+
|
|
52
|
+
for (const rec of result.fresh.slice(0, limit)) {
|
|
53
|
+
const indicator = rec.sentiment.score > 0 ? "🟢" : rec.sentiment.score < 0 ? "🔴" : "⚪";
|
|
54
|
+
lines.push(`${indicator} [${rec.title}](${rec.url}) — *${rec.author}*`);
|
|
55
|
+
lines.push(` ${rec.text.slice(0, 150)}`);
|
|
56
|
+
lines.push(` Score: ${rec.sentiment.score.toFixed(2)} | Confidence: ${rec.sentiment.confidence.toFixed(2)}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (result.trend) {
|
|
60
|
+
lines.push("");
|
|
61
|
+
const t = result.trend[0];
|
|
62
|
+
lines.push(`Trend: ${t.sparkline} ${t.direction} (${t.count} records)`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: result };
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function sentimentLabel(score: number): string {
|
|
71
|
+
if (score > 0.3) return "Bullish";
|
|
72
|
+
if (score < -0.3) return "Bearish";
|
|
73
|
+
if (score > 0) return "Leaning Bullish";
|
|
74
|
+
if (score < 0) return "Leaning Bearish";
|
|
75
|
+
return "Neutral";
|
|
76
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import { getHistory } from "../../providers/yahoo-finance.js";
|
|
4
|
+
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
5
|
+
import { computeSMA, computeRSI } from "./indicators.js";
|
|
6
|
+
import type { OHLCV } from "../../types/market.js";
|
|
7
|
+
|
|
8
|
+
export type Strategy = "sma_crossover" | "sma_50_200_crossover" | "rsi_mean_reversion";
|
|
9
|
+
|
|
10
|
+
export interface BacktestResult {
|
|
11
|
+
strategy: string;
|
|
12
|
+
totalReturn: number;
|
|
13
|
+
buyAndHoldReturn: number;
|
|
14
|
+
trades: number;
|
|
15
|
+
wins: number;
|
|
16
|
+
winRate: number;
|
|
17
|
+
maxDrawdown: number;
|
|
18
|
+
tradeLog: Array<{ type: "buy" | "sell"; date: string; price: number; pnl?: number }>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function runBacktest(bars: OHLCV[], strategy: Strategy): BacktestResult {
|
|
22
|
+
const closes = bars.map((b) => b.close);
|
|
23
|
+
|
|
24
|
+
if (strategy === "sma_crossover") {
|
|
25
|
+
return backtestSMACrossover(bars, closes, 20, 50, strategy);
|
|
26
|
+
}
|
|
27
|
+
if (strategy === "sma_50_200_crossover") {
|
|
28
|
+
return backtestSMACrossover(bars, closes, 50, 200, strategy);
|
|
29
|
+
}
|
|
30
|
+
return backtestRSIMeanReversion(bars, closes);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function backtestSMACrossover(
|
|
34
|
+
bars: OHLCV[],
|
|
35
|
+
closes: number[],
|
|
36
|
+
shortWindow: number,
|
|
37
|
+
longWindow: number,
|
|
38
|
+
strategyName: Strategy,
|
|
39
|
+
): BacktestResult {
|
|
40
|
+
const shortSma = computeSMA(closes, shortWindow);
|
|
41
|
+
const longSma = computeSMA(closes, longWindow);
|
|
42
|
+
|
|
43
|
+
if (longSma.length === 0) {
|
|
44
|
+
return emptyResult(strategyName, closes);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const shortOffset = shortWindow - 1;
|
|
48
|
+
const longOffset = longWindow - 1;
|
|
49
|
+
|
|
50
|
+
let position = false;
|
|
51
|
+
let entryPrice = 0;
|
|
52
|
+
const tradeLog: BacktestResult["tradeLog"] = [];
|
|
53
|
+
let equity = 1.0;
|
|
54
|
+
let peak = 1.0;
|
|
55
|
+
let maxDd = 0;
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < longSma.length; i++) {
|
|
58
|
+
const barIdx = i + longOffset;
|
|
59
|
+
const shortSmaIdx = i + (longOffset - shortOffset);
|
|
60
|
+
const sShort = shortSma[shortSmaIdx];
|
|
61
|
+
const sLong = longSma[i];
|
|
62
|
+
const price = closes[barIdx];
|
|
63
|
+
|
|
64
|
+
if (!position && sShort > sLong) {
|
|
65
|
+
// Buy signal
|
|
66
|
+
position = true;
|
|
67
|
+
entryPrice = price;
|
|
68
|
+
tradeLog.push({ type: "buy", date: bars[barIdx].date, price });
|
|
69
|
+
} else if (position && sShort < sLong) {
|
|
70
|
+
// Sell signal
|
|
71
|
+
const pnl = (price - entryPrice) / entryPrice;
|
|
72
|
+
equity *= 1 + pnl;
|
|
73
|
+
tradeLog.push({ type: "sell", date: bars[barIdx].date, price, pnl });
|
|
74
|
+
position = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Track mark-to-market equity for accurate drawdown
|
|
78
|
+
const currentEquity = position
|
|
79
|
+
? equity * (1 + (price - entryPrice) / entryPrice)
|
|
80
|
+
: equity;
|
|
81
|
+
if (currentEquity > peak) peak = currentEquity;
|
|
82
|
+
const dd = (peak - currentEquity) / peak;
|
|
83
|
+
if (dd > maxDd) maxDd = dd;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Close open position at end
|
|
87
|
+
if (position) {
|
|
88
|
+
const lastPrice = closes[closes.length - 1];
|
|
89
|
+
const pnl = (lastPrice - entryPrice) / entryPrice;
|
|
90
|
+
equity *= 1 + pnl;
|
|
91
|
+
tradeLog.push({ type: "sell", date: bars[bars.length - 1].date, price: lastPrice, pnl });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return buildResult(strategyName, equity - 1, closes, tradeLog, maxDd);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function backtestRSIMeanReversion(bars: OHLCV[], closes: number[]): BacktestResult {
|
|
98
|
+
const rsi = computeRSI(closes, 14);
|
|
99
|
+
|
|
100
|
+
if (rsi.length === 0) {
|
|
101
|
+
return emptyResult("rsi_mean_reversion", closes);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// RSI starts at index 14 (after 14 periods of data)
|
|
105
|
+
const rsiOffset = 14;
|
|
106
|
+
let position = false;
|
|
107
|
+
let entryPrice = 0;
|
|
108
|
+
const tradeLog: BacktestResult["tradeLog"] = [];
|
|
109
|
+
let equity = 1.0;
|
|
110
|
+
let peak = 1.0;
|
|
111
|
+
let maxDd = 0;
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < rsi.length; i++) {
|
|
114
|
+
const barIdx = i + rsiOffset;
|
|
115
|
+
const r = rsi[i];
|
|
116
|
+
const price = closes[barIdx];
|
|
117
|
+
|
|
118
|
+
if (!position && r < 30) {
|
|
119
|
+
// RSI oversold → buy
|
|
120
|
+
position = true;
|
|
121
|
+
entryPrice = price;
|
|
122
|
+
tradeLog.push({ type: "buy", date: bars[barIdx].date, price });
|
|
123
|
+
} else if (position && r > 70) {
|
|
124
|
+
// RSI overbought → sell
|
|
125
|
+
const pnl = (price - entryPrice) / entryPrice;
|
|
126
|
+
equity *= 1 + pnl;
|
|
127
|
+
tradeLog.push({ type: "sell", date: bars[barIdx].date, price, pnl });
|
|
128
|
+
position = false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Track mark-to-market equity for accurate drawdown
|
|
132
|
+
const currentEquity = position
|
|
133
|
+
? equity * (1 + (price - entryPrice) / entryPrice)
|
|
134
|
+
: equity;
|
|
135
|
+
if (currentEquity > peak) peak = currentEquity;
|
|
136
|
+
const dd = (peak - currentEquity) / peak;
|
|
137
|
+
if (dd > maxDd) maxDd = dd;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Close open position at end
|
|
141
|
+
if (position) {
|
|
142
|
+
const lastPrice = closes[closes.length - 1];
|
|
143
|
+
const pnl = (lastPrice - entryPrice) / entryPrice;
|
|
144
|
+
equity *= 1 + pnl;
|
|
145
|
+
tradeLog.push({ type: "sell", date: bars[bars.length - 1].date, price: lastPrice, pnl });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return buildResult("rsi_mean_reversion", equity - 1, closes, tradeLog, maxDd);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildResult(
|
|
152
|
+
strategy: string,
|
|
153
|
+
totalReturn: number,
|
|
154
|
+
closes: number[],
|
|
155
|
+
tradeLog: BacktestResult["tradeLog"],
|
|
156
|
+
maxDrawdown: number,
|
|
157
|
+
): BacktestResult {
|
|
158
|
+
const sellTrades = tradeLog.filter((t) => t.type === "sell" && t.pnl != null);
|
|
159
|
+
const wins = sellTrades.filter((t) => t.pnl! > 0).length;
|
|
160
|
+
const buyAndHoldReturn = closes.length > 1
|
|
161
|
+
? (closes[closes.length - 1] - closes[0]) / closes[0]
|
|
162
|
+
: 0;
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
strategy,
|
|
166
|
+
totalReturn,
|
|
167
|
+
buyAndHoldReturn,
|
|
168
|
+
trades: sellTrades.length,
|
|
169
|
+
wins,
|
|
170
|
+
winRate: sellTrades.length > 0 ? wins / sellTrades.length : 0,
|
|
171
|
+
maxDrawdown,
|
|
172
|
+
tradeLog,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function emptyResult(strategy: string, closes: number[]): BacktestResult {
|
|
177
|
+
return {
|
|
178
|
+
strategy,
|
|
179
|
+
totalReturn: 0,
|
|
180
|
+
buyAndHoldReturn: closes.length > 1
|
|
181
|
+
? (closes[closes.length - 1] - closes[0]) / closes[0]
|
|
182
|
+
: 0,
|
|
183
|
+
trades: 0,
|
|
184
|
+
wins: 0,
|
|
185
|
+
winRate: 0,
|
|
186
|
+
maxDrawdown: 0,
|
|
187
|
+
tradeLog: [],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const params = Type.Object({
|
|
192
|
+
symbol: Type.String({ description: "Stock ticker symbol (e.g. AAPL, MSFT, SPY)" }),
|
|
193
|
+
strategy: Type.Union(
|
|
194
|
+
[Type.Literal("sma_crossover"), Type.Literal("sma_50_200_crossover"), Type.Literal("rsi_mean_reversion")],
|
|
195
|
+
{ description: "Strategy: sma_crossover (buy when SMA20 > SMA50, sell on reverse), sma_50_200_crossover (buy when SMA50 > SMA200, sell on reverse), or rsi_mean_reversion (buy when RSI < 30, sell when RSI > 70)" },
|
|
196
|
+
),
|
|
197
|
+
period: Type.Optional(
|
|
198
|
+
Type.String({ description: "Historical period to backtest: 1y, 2y, 5y. Default: 2y" }),
|
|
199
|
+
),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
export const backtestTool: AgentTool<typeof params> = {
|
|
203
|
+
name: "backtest_strategy",
|
|
204
|
+
label: "Backtest Strategy",
|
|
205
|
+
description:
|
|
206
|
+
"Backtest a simple trading strategy against historical data. Supported strategies: SMA crossover (SMA20/SMA50), standard long-term SMA crossover (SMA50/SMA200), and RSI mean-reversion (buy <30, sell >70). Returns total return, win rate, max drawdown, and comparison to buy-and-hold.",
|
|
207
|
+
parameters: params,
|
|
208
|
+
async execute(_toolCallId, args) {
|
|
209
|
+
const symbol = args.symbol.toUpperCase();
|
|
210
|
+
const period = args.period ?? "2y";
|
|
211
|
+
const historyResult = await wrapProvider("yahoo", () => getHistory(symbol, period, "1d"));
|
|
212
|
+
if (historyResult.status === "unavailable") {
|
|
213
|
+
return {
|
|
214
|
+
content: [{ type: "text", text: `⚠ Backtest unavailable for ${symbol} (${historyResult.reason}).` }],
|
|
215
|
+
details: null as any,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const bars = historyResult.data;
|
|
219
|
+
|
|
220
|
+
const minBars = requiredBarsForStrategy(args.strategy);
|
|
221
|
+
if (bars.length < minBars) {
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: "text", text: `Insufficient data for backtesting ${symbol} (need ${minBars}+ days, got ${bars.length})` }],
|
|
224
|
+
details: null,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const result = runBacktest(bars, args.strategy);
|
|
229
|
+
|
|
230
|
+
const outperformance = result.totalReturn - result.buyAndHoldReturn;
|
|
231
|
+
const lines = [
|
|
232
|
+
`**${symbol} Backtest: ${strategyLabel(args.strategy)}** (${bars[0].date} to ${bars[bars.length - 1].date}, ${bars.length} days)`,
|
|
233
|
+
``,
|
|
234
|
+
`Strategy Return: ${(result.totalReturn * 100).toFixed(2)}%`,
|
|
235
|
+
`Buy & Hold Return: ${(result.buyAndHoldReturn * 100).toFixed(2)}%`,
|
|
236
|
+
`Outperformance: ${outperformance >= 0 ? "+" : ""}${(outperformance * 100).toFixed(2)}%`,
|
|
237
|
+
``,
|
|
238
|
+
`Trades: ${result.trades} | Wins: ${result.wins} | Win Rate: ${(result.winRate * 100).toFixed(0)}%`,
|
|
239
|
+
`Max Drawdown: ${(result.maxDrawdown * 100).toFixed(2)}%`,
|
|
240
|
+
``,
|
|
241
|
+
result.totalReturn > result.buyAndHoldReturn
|
|
242
|
+
? `Strategy outperformed buy-and-hold by ${(outperformance * 100).toFixed(2)}%.`
|
|
243
|
+
: `Buy-and-hold outperformed the strategy by ${(-outperformance * 100).toFixed(2)}%.`,
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
248
|
+
details: result,
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
function requiredBarsForStrategy(strategy: Strategy): number {
|
|
254
|
+
if (strategy === "sma_50_200_crossover") return 200;
|
|
255
|
+
return 60;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function strategyLabel(strategy: Strategy): string {
|
|
259
|
+
switch (strategy) {
|
|
260
|
+
case "sma_crossover":
|
|
261
|
+
return "SMA 20/50 Crossover";
|
|
262
|
+
case "sma_50_200_crossover":
|
|
263
|
+
return "SMA 50/200 Crossover";
|
|
264
|
+
case "rsi_mean_reversion":
|
|
265
|
+
return "RSI Mean Reversion";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import { getHistory } from "../../providers/yahoo-finance.js";
|
|
4
|
+
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
5
|
+
import type { OHLCV } from "../../types/market.js";
|
|
6
|
+
|
|
7
|
+
// --- Volume-based indicators ---
|
|
8
|
+
|
|
9
|
+
export function computeOBV(bars: OHLCV[]): number[] {
|
|
10
|
+
const obv: number[] = [0];
|
|
11
|
+
for (let i = 1; i < bars.length; i++) {
|
|
12
|
+
if (bars[i].close > bars[i - 1].close) {
|
|
13
|
+
obv.push(obv[i - 1] + bars[i].volume);
|
|
14
|
+
} else if (bars[i].close < bars[i - 1].close) {
|
|
15
|
+
obv.push(obv[i - 1] - bars[i].volume);
|
|
16
|
+
} else {
|
|
17
|
+
obv.push(obv[i - 1]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return obv;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function computeVWAP(bars: OHLCV[]): number[] {
|
|
24
|
+
const vwap: number[] = [];
|
|
25
|
+
let cumPV = 0;
|
|
26
|
+
let cumVol = 0;
|
|
27
|
+
for (const bar of bars) {
|
|
28
|
+
const tp = (bar.high + bar.low + bar.close) / 3;
|
|
29
|
+
cumPV += tp * bar.volume;
|
|
30
|
+
cumVol += bar.volume;
|
|
31
|
+
vwap.push(cumVol === 0 ? 0 : cumPV / cumVol);
|
|
32
|
+
}
|
|
33
|
+
return vwap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const params = Type.Object({
|
|
37
|
+
symbol: Type.String({ description: "Stock ticker symbol (e.g. AAPL, MSFT)" }),
|
|
38
|
+
range: Type.Optional(
|
|
39
|
+
Type.String({ description: "Time range for data: 3mo, 6mo, 1y, 2y. Default: 1y" }),
|
|
40
|
+
),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const technicalIndicatorsTool: AgentTool<typeof params> = {
|
|
44
|
+
name: "get_technical_indicators",
|
|
45
|
+
label: "Technical Indicators",
|
|
46
|
+
description:
|
|
47
|
+
"Compute technical indicators (SMA, EMA, RSI, MACD, Bollinger Bands) from historical price data. All computed locally — no API dependency.",
|
|
48
|
+
parameters: params,
|
|
49
|
+
async execute(_toolCallId, args) {
|
|
50
|
+
const symbol = args.symbol.toUpperCase();
|
|
51
|
+
const range = args.range ?? "1y";
|
|
52
|
+
const result = await wrapProvider("yahoo", () => getHistory(symbol, range, "1d"));
|
|
53
|
+
if (result.status === "unavailable") {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: `⚠ Technical indicators unavailable for ${symbol} (${result.reason}).` }],
|
|
56
|
+
details: null as any,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const bars = result.data;
|
|
60
|
+
const closes = bars.map((b) => b.close);
|
|
61
|
+
|
|
62
|
+
if (closes.length < 26) {
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: "text", text: `Insufficient data for ${symbol} (need 26+ bars, got ${closes.length})` }],
|
|
65
|
+
details: null,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const sma20 = computeSMA(closes, 20);
|
|
70
|
+
const sma50 = computeSMA(closes, 50);
|
|
71
|
+
const rsi = computeRSI(closes, 14);
|
|
72
|
+
const macd = computeMACD(closes);
|
|
73
|
+
const bb = computeBollingerBands(closes, 20, 2);
|
|
74
|
+
const obv = computeOBV(bars);
|
|
75
|
+
const vwap = computeVWAP(bars);
|
|
76
|
+
|
|
77
|
+
const latest = closes[closes.length - 1];
|
|
78
|
+
const latestRsi = rsi[rsi.length - 1];
|
|
79
|
+
const latestMacd = macd[macd.length - 1];
|
|
80
|
+
const latestBB = bb[bb.length - 1];
|
|
81
|
+
const latestVwap = vwap[vwap.length - 1];
|
|
82
|
+
const obvTrend = obv.length >= 20
|
|
83
|
+
? (obv[obv.length - 1] > obv[obv.length - 20] ? "Rising" : "Falling")
|
|
84
|
+
: "N/A";
|
|
85
|
+
|
|
86
|
+
const lines = [
|
|
87
|
+
`**${symbol} Technical Analysis** (${bars[0].date} to ${bars[bars.length - 1].date})`,
|
|
88
|
+
`Price: $${latest.toFixed(2)}`,
|
|
89
|
+
``,
|
|
90
|
+
`SMA(20): $${sma20[sma20.length - 1]?.toFixed(2) ?? "N/A"} | SMA(50): $${sma50[sma50.length - 1]?.toFixed(2) ?? "N/A"}`,
|
|
91
|
+
`RSI(14): ${latestRsi?.toFixed(1) ?? "N/A"} ${rsiSignal(latestRsi)}`,
|
|
92
|
+
`MACD: ${latestMacd?.macd.toFixed(2) ?? "N/A"} | Signal: ${latestMacd?.signal.toFixed(2) ?? "N/A"} | Histogram: ${latestMacd?.histogram.toFixed(2) ?? "N/A"}`,
|
|
93
|
+
`Bollinger Bands: Upper $${latestBB?.upper.toFixed(2) ?? "N/A"} | Mid $${latestBB?.middle.toFixed(2) ?? "N/A"} | Lower $${latestBB?.lower.toFixed(2) ?? "N/A"}`,
|
|
94
|
+
`OBV Trend: ${obvTrend} | VWAP (cumulative): $${latestVwap?.toFixed(2) ?? "N/A"}`,
|
|
95
|
+
``,
|
|
96
|
+
trendSummary(latest, sma20, sma50, latestRsi, latestMacd, obvTrend, latestVwap),
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
101
|
+
details: {
|
|
102
|
+
symbol,
|
|
103
|
+
range,
|
|
104
|
+
prices: closes,
|
|
105
|
+
dates: bars.map((b) => b.date),
|
|
106
|
+
sma20, sma50, rsi, macd, bb, obv, vwap,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// --- Indicator computations (all pure functions) ---
|
|
113
|
+
|
|
114
|
+
export function computeSMA(data: number[], period: number): number[] {
|
|
115
|
+
const result: number[] = [];
|
|
116
|
+
for (let i = period - 1; i < data.length; i++) {
|
|
117
|
+
let sum = 0;
|
|
118
|
+
for (let j = i - period + 1; j <= i; j++) sum += data[j];
|
|
119
|
+
result.push(sum / period);
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function computeEMA(data: number[], period: number): number[] {
|
|
125
|
+
const k = 2 / (period + 1);
|
|
126
|
+
const result: number[] = [];
|
|
127
|
+
// Seed with SMA of first `period` values
|
|
128
|
+
let sum = 0;
|
|
129
|
+
for (let i = 0; i < period; i++) sum += data[i];
|
|
130
|
+
let ema = sum / period;
|
|
131
|
+
result.push(ema);
|
|
132
|
+
for (let i = period; i < data.length; i++) {
|
|
133
|
+
ema = data[i] * k + ema * (1 - k);
|
|
134
|
+
result.push(ema);
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function computeRSI(data: number[], period: number = 14): number[] {
|
|
140
|
+
const result: number[] = [];
|
|
141
|
+
const gains: number[] = [];
|
|
142
|
+
const losses: number[] = [];
|
|
143
|
+
|
|
144
|
+
for (let i = 1; i < data.length; i++) {
|
|
145
|
+
const diff = data[i] - data[i - 1];
|
|
146
|
+
gains.push(diff > 0 ? diff : 0);
|
|
147
|
+
losses.push(diff < 0 ? -diff : 0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (gains.length < period) return result;
|
|
151
|
+
|
|
152
|
+
let avgGain = gains.slice(0, period).reduce((a, b) => a + b, 0) / period;
|
|
153
|
+
let avgLoss = losses.slice(0, period).reduce((a, b) => a + b, 0) / period;
|
|
154
|
+
|
|
155
|
+
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
|
156
|
+
result.push(100 - 100 / (1 + rs));
|
|
157
|
+
|
|
158
|
+
for (let i = period; i < gains.length; i++) {
|
|
159
|
+
avgGain = (avgGain * (period - 1) + gains[i]) / period;
|
|
160
|
+
avgLoss = (avgLoss * (period - 1) + losses[i]) / period;
|
|
161
|
+
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
|
162
|
+
result.push(100 - 100 / (1 + rs));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function computeMACD(
|
|
169
|
+
data: number[],
|
|
170
|
+
fastPeriod = 12,
|
|
171
|
+
slowPeriod = 26,
|
|
172
|
+
signalPeriod = 9,
|
|
173
|
+
): { macd: number; signal: number; histogram: number }[] {
|
|
174
|
+
if (data.length < slowPeriod + signalPeriod) return [];
|
|
175
|
+
|
|
176
|
+
const emaFast = computeEMA(data, fastPeriod);
|
|
177
|
+
const emaSlow = computeEMA(data, slowPeriod);
|
|
178
|
+
|
|
179
|
+
// Align: emaFast starts at index fastPeriod-1, emaSlow at slowPeriod-1
|
|
180
|
+
const offset = slowPeriod - fastPeriod;
|
|
181
|
+
const macdLine: number[] = [];
|
|
182
|
+
for (let i = 0; i < emaSlow.length; i++) {
|
|
183
|
+
macdLine.push(emaFast[i + offset] - emaSlow[i]);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (macdLine.length < signalPeriod) return [];
|
|
187
|
+
|
|
188
|
+
const signalLine = computeEMA(macdLine, signalPeriod);
|
|
189
|
+
const signalOffset = signalPeriod - 1;
|
|
190
|
+
|
|
191
|
+
const result: { macd: number; signal: number; histogram: number }[] = [];
|
|
192
|
+
for (let i = 0; i < signalLine.length; i++) {
|
|
193
|
+
const m = macdLine[i + signalOffset];
|
|
194
|
+
const s = signalLine[i];
|
|
195
|
+
result.push({ macd: m, signal: s, histogram: m - s });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function computeBollingerBands(
|
|
202
|
+
data: number[],
|
|
203
|
+
period: number = 20,
|
|
204
|
+
stdDev: number = 2,
|
|
205
|
+
): { upper: number; middle: number; lower: number }[] {
|
|
206
|
+
const result: { upper: number; middle: number; lower: number }[] = [];
|
|
207
|
+
for (let i = period - 1; i < data.length; i++) {
|
|
208
|
+
const slice = data.slice(i - period + 1, i + 1);
|
|
209
|
+
const mean = slice.reduce((a, b) => a + b, 0) / period;
|
|
210
|
+
const variance = slice.reduce((a, b) => a + (b - mean) ** 2, 0) / period;
|
|
211
|
+
const sd = Math.sqrt(variance);
|
|
212
|
+
result.push({
|
|
213
|
+
upper: mean + stdDev * sd,
|
|
214
|
+
middle: mean,
|
|
215
|
+
lower: mean - stdDev * sd,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function rsiSignal(rsi: number | undefined): string {
|
|
222
|
+
if (rsi == null) return "";
|
|
223
|
+
if (rsi >= 70) return "(Overbought)";
|
|
224
|
+
if (rsi <= 30) return "(Oversold)";
|
|
225
|
+
return "(Neutral)";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function trendSummary(
|
|
229
|
+
price: number,
|
|
230
|
+
sma20: number[],
|
|
231
|
+
sma50: number[],
|
|
232
|
+
rsi: number | undefined,
|
|
233
|
+
macd: { macd: number; signal: number; histogram: number } | undefined,
|
|
234
|
+
obvTrend?: string,
|
|
235
|
+
vwap?: number,
|
|
236
|
+
): string {
|
|
237
|
+
const signals: string[] = [];
|
|
238
|
+
|
|
239
|
+
const latestSma20 = sma20[sma20.length - 1];
|
|
240
|
+
const latestSma50 = sma50[sma50.length - 1];
|
|
241
|
+
|
|
242
|
+
if (latestSma20 && price > latestSma20) signals.push("Price above SMA(20) — short-term bullish");
|
|
243
|
+
if (latestSma20 && price < latestSma20) signals.push("Price below SMA(20) — short-term bearish");
|
|
244
|
+
if (latestSma20 && latestSma50 && latestSma20 > latestSma50) signals.push("Golden cross pattern (SMA20 > SMA50)");
|
|
245
|
+
if (latestSma20 && latestSma50 && latestSma20 < latestSma50) signals.push("Death cross pattern (SMA20 < SMA50)");
|
|
246
|
+
if (rsi != null && rsi >= 70) signals.push("RSI overbought — potential reversal");
|
|
247
|
+
if (rsi != null && rsi <= 30) signals.push("RSI oversold — potential bounce");
|
|
248
|
+
if (macd && macd.histogram > 0) signals.push("MACD bullish (histogram positive)");
|
|
249
|
+
if (macd && macd.histogram < 0) signals.push("MACD bearish (histogram negative)");
|
|
250
|
+
if (obvTrend === "Rising" && price > (latestSma20 ?? 0)) signals.push("Volume confirming price advance (OBV rising)");
|
|
251
|
+
if (obvTrend === "Falling" && price < (latestSma20 ?? Infinity)) signals.push("Volume confirming price decline (OBV falling)");
|
|
252
|
+
if (vwap != null && price > vwap) signals.push("Price above cumulative VWAP — bullish volume-weighted bias");
|
|
253
|
+
if (vwap != null && price < vwap) signals.push("Price below cumulative VWAP — bearish volume-weighted bias");
|
|
254
|
+
|
|
255
|
+
return signals.length > 0 ? "Signals: " + signals.join(" | ") : "No strong signals";
|
|
256
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface CompanyOverview {
|
|
2
|
+
symbol: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
exchange: string;
|
|
6
|
+
sector: string;
|
|
7
|
+
industry: string;
|
|
8
|
+
marketCap: number;
|
|
9
|
+
pe: number | null;
|
|
10
|
+
forwardPe: number | null;
|
|
11
|
+
eps: number | null;
|
|
12
|
+
dividendYield: number | null;
|
|
13
|
+
beta: number | null;
|
|
14
|
+
week52High: number;
|
|
15
|
+
week52Low: number;
|
|
16
|
+
avgVolume: number;
|
|
17
|
+
profitMargin: number | null;
|
|
18
|
+
revenueGrowth: number | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface EarningsData {
|
|
22
|
+
symbol: string;
|
|
23
|
+
quarterly: Array<{
|
|
24
|
+
date: string;
|
|
25
|
+
reportedEPS: number;
|
|
26
|
+
estimatedEPS: number;
|
|
27
|
+
surprise: number;
|
|
28
|
+
surprisePercent: number;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FinancialStatement {
|
|
33
|
+
fiscalDate: string;
|
|
34
|
+
revenue: number;
|
|
35
|
+
grossProfit: number;
|
|
36
|
+
operatingIncome: number;
|
|
37
|
+
netIncome: number;
|
|
38
|
+
eps: number;
|
|
39
|
+
totalAssets: number;
|
|
40
|
+
totalLiabilities: number;
|
|
41
|
+
totalEquity: number;
|
|
42
|
+
operatingCashFlow: number;
|
|
43
|
+
freeCashFlow: number;
|
|
44
|
+
totalDebt?: number;
|
|
45
|
+
cashAndEquivalents?: number;
|
|
46
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type { StockQuote, OHLCV, CryptoPrice } from "./market.js";
|
|
2
|
+
export type { CompanyOverview, EarningsData, FinancialStatement } from "./fundamentals.js";
|
|
3
|
+
export type { FredObservation, FredSeries } from "./macro.js";
|
|
4
|
+
export { FRED_SERIES } from "./macro.js";
|
|
5
|
+
export type { Greeks, OptionContract, OptionsChain } from "./options.js";
|
|
6
|
+
export type { Position, PortfolioSummary, RiskMetrics, TechnicalIndicators } from "./portfolio.js";
|
|
7
|
+
export type { FearGreedData, RedditSentimentResult, WebSearchResult, WebSearchEnvelope } from "./sentiment.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handler for `ask_user` tool invocations in non-UI contexts (e.g. test harness).
|
|
11
|
+
* When provided to `createOpenCandleSession`, the ask-user tool calls this handler
|
|
12
|
+
* instead of `ctx.ui.*` methods.
|
|
13
|
+
*/
|
|
14
|
+
export type AskUserHandler = (params: {
|
|
15
|
+
question: string;
|
|
16
|
+
questionType: "select" | "text" | "confirm";
|
|
17
|
+
options?: string[];
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
reason?: string;
|
|
20
|
+
}) => Promise<{ answer: string | null; cancelled: boolean }>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface FredObservation {
|
|
2
|
+
date: string;
|
|
3
|
+
value: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface FredSeries {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
observations: FredObservation[];
|
|
10
|
+
units: string;
|
|
11
|
+
frequency: string;
|
|
12
|
+
lastUpdated: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Common FRED series IDs
|
|
16
|
+
export const FRED_SERIES = {
|
|
17
|
+
FED_FUNDS: "FEDFUNDS",
|
|
18
|
+
TREASURY_10Y: "DGS10",
|
|
19
|
+
TREASURY_2Y: "DGS2",
|
|
20
|
+
TREASURY_30Y: "DGS30",
|
|
21
|
+
CPI: "CPIAUCSL",
|
|
22
|
+
UNEMPLOYMENT: "UNRATE",
|
|
23
|
+
GDP: "GDP",
|
|
24
|
+
YIELD_SPREAD_10Y2Y: "T10Y2Y",
|
|
25
|
+
INFLATION_EXPECTATION: "T5YIE",
|
|
26
|
+
MORTGAGE_30Y: "MORTGAGE30US",
|
|
27
|
+
} as const;
|