opencandle 0.2.0 → 0.3.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 +110 -87
- package/dist/analysts/orchestrator.js +1 -2
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/config.d.ts +25 -5
- package/dist/config.js +16 -8
- package/dist/config.js.map +1 -1
- package/dist/infra/cache.d.ts +4 -0
- package/dist/infra/cache.js +4 -0
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/rate-limiter.js +6 -0
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/onboarding/connect.d.ts +23 -0
- package/dist/onboarding/connect.js +107 -0
- package/dist/onboarding/connect.js.map +1 -0
- package/dist/onboarding/credential-interceptor.d.ts +44 -0
- package/dist/onboarding/credential-interceptor.js +72 -0
- package/dist/onboarding/credential-interceptor.js.map +1 -0
- package/dist/onboarding/degradation-accumulator.d.ts +21 -0
- package/dist/onboarding/degradation-accumulator.js +55 -0
- package/dist/onboarding/degradation-accumulator.js.map +1 -0
- package/dist/onboarding/prompt-user.d.ts +23 -0
- package/dist/onboarding/prompt-user.js +61 -0
- package/dist/onboarding/prompt-user.js.map +1 -0
- package/dist/onboarding/providers.d.ts +109 -0
- package/dist/onboarding/providers.js +236 -0
- package/dist/onboarding/providers.js.map +1 -0
- package/dist/onboarding/state.d.ts +31 -2
- package/dist/onboarding/state.js +141 -13
- package/dist/onboarding/state.js.map +1 -1
- package/dist/onboarding/tool-helpers.d.ts +34 -0
- package/dist/onboarding/tool-helpers.js +80 -0
- package/dist/onboarding/tool-helpers.js.map +1 -0
- package/dist/onboarding/tool-tags.d.ts +37 -0
- package/dist/onboarding/tool-tags.js +149 -0
- package/dist/onboarding/tool-tags.js.map +1 -0
- package/dist/onboarding/validation.d.ts +19 -0
- package/dist/onboarding/validation.js +117 -0
- package/dist/onboarding/validation.js.map +1 -0
- package/dist/pi/opencandle-extension.js +303 -4
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/setup.d.ts +0 -1
- package/dist/pi/setup.js +66 -119
- package/dist/pi/setup.js.map +1 -1
- package/dist/prompts/context-builder.js +2 -1
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/providers/alpha-vantage.js +20 -1
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/exa-search.d.ts +39 -0
- package/dist/providers/exa-search.js +276 -0
- package/dist/providers/exa-search.js.map +1 -0
- package/dist/providers/finnhub.d.ts +17 -0
- package/dist/providers/finnhub.js +94 -0
- package/dist/providers/finnhub.js.map +1 -0
- package/dist/providers/fred.js +13 -1
- package/dist/providers/fred.js.map +1 -1
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/provider-credential-error.d.ts +8 -0
- package/dist/providers/provider-credential-error.js +22 -0
- package/dist/providers/provider-credential-error.js.map +1 -0
- package/dist/providers/reddit.d.ts +8 -0
- package/dist/providers/reddit.js +36 -9
- package/dist/providers/reddit.js.map +1 -1
- package/dist/providers/twitter.js +2 -8
- package/dist/providers/twitter.js.map +1 -1
- package/dist/providers/web-search.d.ts +17 -0
- package/dist/providers/web-search.js +224 -0
- package/dist/providers/web-search.js.map +1 -0
- package/dist/providers/wrap-provider.d.ts +7 -0
- package/dist/providers/wrap-provider.js +15 -0
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/routing/classify-intent.js +22 -0
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/runtime/session-coordinator.d.ts +0 -1
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/sentiment/adapters/finnhub.d.ts +7 -0
- package/dist/sentiment/adapters/finnhub.js +39 -0
- package/dist/sentiment/adapters/finnhub.js.map +1 -0
- package/dist/sentiment/adapters/reddit.d.ts +11 -0
- package/dist/sentiment/adapters/reddit.js +54 -0
- package/dist/sentiment/adapters/reddit.js.map +1 -0
- package/dist/sentiment/adapters/twitter.d.ts +9 -0
- package/dist/sentiment/adapters/twitter.js +32 -0
- package/dist/sentiment/adapters/twitter.js.map +1 -0
- package/dist/sentiment/adapters/web.d.ts +9 -0
- package/dist/sentiment/adapters/web.js +40 -0
- package/dist/sentiment/adapters/web.js.map +1 -0
- package/dist/sentiment/index.d.ts +16 -0
- package/dist/sentiment/index.js +44 -0
- package/dist/sentiment/index.js.map +1 -0
- package/dist/sentiment/keywords.d.ts +2 -0
- package/dist/sentiment/keywords.js +9 -0
- package/dist/sentiment/keywords.js.map +1 -0
- package/dist/sentiment/pipeline.d.ts +9 -0
- package/dist/sentiment/pipeline.js +57 -0
- package/dist/sentiment/pipeline.js.map +1 -0
- package/dist/sentiment/scorer.d.ts +9 -0
- package/dist/sentiment/scorer.js +64 -0
- package/dist/sentiment/scorer.js.map +1 -0
- package/dist/sentiment/store.d.ts +24 -0
- package/dist/sentiment/store.js +177 -0
- package/dist/sentiment/store.js.map +1 -0
- package/dist/sentiment/trends.d.ts +13 -0
- package/dist/sentiment/trends.js +73 -0
- package/dist/sentiment/trends.js.map +1 -0
- package/dist/sentiment/types.d.ts +66 -0
- package/dist/sentiment/types.js +54 -0
- package/dist/sentiment/types.js.map +1 -0
- package/dist/system-prompt.js +9 -1
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/fundamentals/company-overview.d.ts +3 -1
- package/dist/tools/fundamentals/company-overview.js +27 -27
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.js +45 -45
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +82 -82
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.d.ts +3 -1
- package/dist/tools/fundamentals/earnings.js +25 -25
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.d.ts +3 -1
- package/dist/tools/fundamentals/financials.js +23 -23
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/index.js +8 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.js +28 -64
- package/dist/tools/interaction/ask-user.js.map +1 -1
- package/dist/tools/macro/fred-data.d.ts +3 -1
- package/dist/tools/macro/fred-data.js +26 -26
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/sentiment/reddit-sentiment.d.ts +3 -1
- package/dist/tools/sentiment/reddit-sentiment.js +107 -22
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.d.ts +7 -0
- package/dist/tools/sentiment/sentiment-summary.js +230 -0
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -0
- package/dist/tools/sentiment/sentiment-trend.d.ts +22 -0
- package/dist/tools/sentiment/sentiment-trend.js +39 -0
- package/dist/tools/sentiment/sentiment-trend.js.map +1 -0
- package/dist/tools/sentiment/twitter-sentiment.js +17 -0
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/web-search.d.ts +11 -0
- package/dist/tools/sentiment/web-search.js +115 -0
- package/dist/tools/sentiment/web-search.js.map +1 -0
- package/dist/tools/sentiment/web-sentiment.d.ts +8 -0
- package/dist/tools/sentiment/web-sentiment.js +66 -0
- package/dist/tools/sentiment/web-sentiment.js.map +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/sentiment.d.ts +21 -0
- package/package.json +19 -3
- package/dist/tools/sentiment/news-sentiment.d.ts +0 -7
- package/dist/tools/sentiment/news-sentiment.js +0 -55
- package/dist/tools/sentiment/news-sentiment.js.map +0 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export declare const SENTIMENT_SOURCES: readonly ["twitter", "reddit", "web", "finnhub"];
|
|
2
|
+
export type SentimentSource = (typeof SENTIMENT_SOURCES)[number];
|
|
3
|
+
export interface SentinelEngagement {
|
|
4
|
+
score: number;
|
|
5
|
+
replies: number | null;
|
|
6
|
+
shares: number | null;
|
|
7
|
+
views: number | null;
|
|
8
|
+
}
|
|
9
|
+
export interface SentinelSentiment {
|
|
10
|
+
score: number;
|
|
11
|
+
confidence: number;
|
|
12
|
+
method: "keyword";
|
|
13
|
+
tickers: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface SentinelRecord {
|
|
16
|
+
id: string;
|
|
17
|
+
source: SentimentSource;
|
|
18
|
+
sourceId: string;
|
|
19
|
+
query: string;
|
|
20
|
+
title: string | null;
|
|
21
|
+
text: string;
|
|
22
|
+
author: string | null;
|
|
23
|
+
url: string;
|
|
24
|
+
publishedAt: string | null;
|
|
25
|
+
fetchedAt: string;
|
|
26
|
+
engagement: SentinelEngagement;
|
|
27
|
+
sentiment: SentinelSentiment;
|
|
28
|
+
metadata: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export declare function isSentinelRecord(val: unknown): val is SentinelRecord;
|
|
31
|
+
export interface SentimentAdapter {
|
|
32
|
+
source: SentimentSource;
|
|
33
|
+
fetch(query: string, options?: {
|
|
34
|
+
hours?: number;
|
|
35
|
+
}): Promise<SentinelRecord[]>;
|
|
36
|
+
}
|
|
37
|
+
export interface ScorerOptions {
|
|
38
|
+
/** Minimum confidence for a keyword score to be considered "high confidence" */
|
|
39
|
+
confidenceThreshold?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface TrendBucket {
|
|
42
|
+
timestamp: string;
|
|
43
|
+
avgScore: number;
|
|
44
|
+
count: number;
|
|
45
|
+
}
|
|
46
|
+
export interface TrendResult {
|
|
47
|
+
source: SentimentSource | "aggregate";
|
|
48
|
+
sparkline: string;
|
|
49
|
+
avgScore: number;
|
|
50
|
+
count: number;
|
|
51
|
+
direction: "rising" | "falling" | "stable";
|
|
52
|
+
delta: number;
|
|
53
|
+
}
|
|
54
|
+
export interface DivergenceResult {
|
|
55
|
+
detected: boolean;
|
|
56
|
+
retailAvg: number | null;
|
|
57
|
+
newsAvg: number | null;
|
|
58
|
+
gap: number | null;
|
|
59
|
+
message: string;
|
|
60
|
+
}
|
|
61
|
+
export interface SentimentSummary {
|
|
62
|
+
fresh: SentinelRecord[];
|
|
63
|
+
trend: TrendResult[] | null;
|
|
64
|
+
divergence: DivergenceResult | null;
|
|
65
|
+
warnings: string[];
|
|
66
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const SENTIMENT_SOURCES = ["twitter", "reddit", "web", "finnhub"];
|
|
2
|
+
export function isSentinelRecord(val) {
|
|
3
|
+
if (val === null || typeof val !== "object")
|
|
4
|
+
return false;
|
|
5
|
+
const r = val;
|
|
6
|
+
if (typeof r.id !== "string")
|
|
7
|
+
return false;
|
|
8
|
+
if (typeof r.source !== "string" || !SENTIMENT_SOURCES.includes(r.source))
|
|
9
|
+
return false;
|
|
10
|
+
if (typeof r.sourceId !== "string")
|
|
11
|
+
return false;
|
|
12
|
+
if (typeof r.query !== "string")
|
|
13
|
+
return false;
|
|
14
|
+
if (r.title !== null && typeof r.title !== "string")
|
|
15
|
+
return false;
|
|
16
|
+
if (typeof r.text !== "string")
|
|
17
|
+
return false;
|
|
18
|
+
if (r.author !== null && typeof r.author !== "string")
|
|
19
|
+
return false;
|
|
20
|
+
if (typeof r.url !== "string")
|
|
21
|
+
return false;
|
|
22
|
+
if (r.publishedAt !== null && typeof r.publishedAt !== "string")
|
|
23
|
+
return false;
|
|
24
|
+
if (typeof r.fetchedAt !== "string")
|
|
25
|
+
return false;
|
|
26
|
+
const eng = r.engagement;
|
|
27
|
+
if (eng === null || typeof eng !== "object")
|
|
28
|
+
return false;
|
|
29
|
+
const e = eng;
|
|
30
|
+
if (typeof e.score !== "number")
|
|
31
|
+
return false;
|
|
32
|
+
if (e.replies !== null && typeof e.replies !== "number")
|
|
33
|
+
return false;
|
|
34
|
+
if (e.shares !== null && typeof e.shares !== "number")
|
|
35
|
+
return false;
|
|
36
|
+
if (e.views !== null && typeof e.views !== "number")
|
|
37
|
+
return false;
|
|
38
|
+
const sent = r.sentiment;
|
|
39
|
+
if (sent === null || typeof sent !== "object")
|
|
40
|
+
return false;
|
|
41
|
+
const s = sent;
|
|
42
|
+
if (typeof s.score !== "number" || s.score < -1 || s.score > 1)
|
|
43
|
+
return false;
|
|
44
|
+
if (typeof s.confidence !== "number" || s.confidence < 0 || s.confidence > 1)
|
|
45
|
+
return false;
|
|
46
|
+
if (s.method !== "keyword")
|
|
47
|
+
return false;
|
|
48
|
+
if (!Array.isArray(s.tickers))
|
|
49
|
+
return false;
|
|
50
|
+
if (r.metadata === null || typeof r.metadata !== "object")
|
|
51
|
+
return false;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sentiment/types.ts"],"names":[],"mappings":"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"}
|
package/dist/system-prompt.js
CHANGED
|
@@ -16,7 +16,7 @@ You provide data-driven analysis for stocks, crypto, macro economics, and portfo
|
|
|
16
16
|
- **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)
|
|
17
17
|
- **Technical Analysis**: get_technical_indicators, backtest_strategy — SMA, EMA, RSI, MACD, Bollinger Bands, OBV, VWAP computed from price data, plus simple strategy backtesting
|
|
18
18
|
- **Macro**: get_economic_data, get_fear_greed — FRED economic indicators and market sentiment
|
|
19
|
-
- **Sentiment**: get_reddit_sentiment,
|
|
19
|
+
- **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
|
|
20
20
|
- **Options**: get_option_chain — full options chain with strikes, bids/asks, volume, OI, IV, and computed Greeks (delta, gamma, theta, vega, rho)
|
|
21
21
|
- **Portfolio**: track_portfolio, analyze_risk, manage_watchlist, analyze_correlation, track_prediction — position tracking, P&L, Sharpe ratio, VaR, watchlist with price alerts, correlation matrix, and prediction tracking with accuracy scoring
|
|
22
22
|
- **User Interaction**: ask_user — ask clarification questions; trigger_twitter_login — open a browser for Twitter/X login
|
|
@@ -40,6 +40,14 @@ When analyzing a stock, follow these steps in order:
|
|
|
40
40
|
- Reuse prior tool outputs when they already answer the question. Do not re-fetch the same symbol and parameters unless you need a missing field or fresher timestamp.
|
|
41
41
|
- If one provider is missing data, continue with the remaining tools and clearly label unavailable metrics instead of aborting the entire response.
|
|
42
42
|
|
|
43
|
+
## Handling skipped data sources
|
|
44
|
+
Tool results may include a tagged line beginning with \`[OPENCANDLE_SKIPPED ...]\`, \`[OPENCANDLE_CREDENTIAL_REQUIRED ...]\`, or \`[OPENCANDLE_SOFT_DEGRADED ...]\`. These signal that a data source was either skipped at the user's request, not configured, or fell back to a keyless alternative (e.g. Brave → DuckDuckGo, Exa → keyless MCP). When you see one or more of these in your tool results:
|
|
45
|
+
1. Continue the analysis using whatever other data you have. Do NOT apologize, do NOT treat it as an error, do NOT suggest the user fix something they already declined.
|
|
46
|
+
2. At the end of your final answer, add a \`**Data gaps**\` section listing each affected provider as one bullet, quoting the \`remediation\` string verbatim (e.g. \`run /connect financials to unlock\`). Aggregate \`[OPENCANDLE_SKIPPED ...]\` and \`[OPENCANDLE_SOFT_DEGRADED ...]\` tags together — both are "you didn't get the keyed source" signals from the user's perspective.
|
|
47
|
+
3. For \`[OPENCANDLE_SOFT_DEGRADED ...]\` tags, briefly name the fallback that was used so the user understands where the data actually came from (e.g. "Web search used DuckDuckGo instead of Brave").
|
|
48
|
+
4. EXCEPTION: if the \`remediation\` string contains the literal text \`(silenced)\`, the user has explicitly asked not to be pestered about this provider. Still describe the omission but OMIT the \`/connect\` remediation text for that bullet. Something like "Finnhub news was omitted (silenced)" is enough.
|
|
49
|
+
5. A \`[OPENCANDLE_CONNECTED ...]\` tag means a credential was JUST saved mid-turn. Acknowledge it briefly ("Alpha Vantage just connected") and tell the user to re-run the previous request to fetch the data. Pi does not currently support re-dispatching the original tool call automatically.
|
|
50
|
+
|
|
43
51
|
## When to Ask for Clarification
|
|
44
52
|
Use the ask_user tool BEFORE proceeding when:
|
|
45
53
|
- The request is broad or vague (e.g., "analyze the market" without specifying which asset or sector)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../src/system-prompt.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,aAAsB;IACtD,MAAM,aAAa,GAAG,aAAa;QACjC,CAAC,CAAC;;;;EAIJ,aAAa,EAAE;QACb,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO
|
|
1
|
+
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../src/system-prompt.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,aAAsB;IACtD,MAAM,aAAa,GAAG,aAAa;QACjC,CAAC,CAAC;;;;EAIJ,aAAa,EAAE;QACb,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6FP,aAAa;;;mNAGoM,CAAC;AACpN,CAAC"}
|
|
@@ -3,5 +3,7 @@ import type { CompanyOverview } from "../../types/fundamentals.js";
|
|
|
3
3
|
declare const params: import("@sinclair/typebox").TObject<{
|
|
4
4
|
symbol: import("@sinclair/typebox").TString;
|
|
5
5
|
}>;
|
|
6
|
-
export declare const companyOverviewTool: AgentTool<typeof params, CompanyOverview
|
|
6
|
+
export declare const companyOverviewTool: AgentTool<typeof params, CompanyOverview | {
|
|
7
|
+
credentialRequired: unknown;
|
|
8
|
+
}>;
|
|
7
9
|
export {};
|
|
@@ -2,41 +2,41 @@ import { Type } from "@sinclair/typebox";
|
|
|
2
2
|
import { getOverview } from "../../providers/alpha-vantage.js";
|
|
3
3
|
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
4
4
|
import { getConfig } from "../../config.js";
|
|
5
|
+
import { withCredentialCheck } from "../../onboarding/tool-helpers.js";
|
|
5
6
|
const params = Type.Object({
|
|
6
7
|
symbol: Type.String({ description: "Stock ticker symbol (e.g. AAPL, MSFT)" }),
|
|
7
8
|
});
|
|
8
9
|
export const companyOverviewTool = {
|
|
9
10
|
name: "get_company_overview",
|
|
10
11
|
label: "Company Overview",
|
|
11
|
-
description: "Get company fundamentals: P/E ratio, EPS, market cap, sector, dividend yield, profit margin, beta, and description. Requires
|
|
12
|
+
description: "Get company fundamentals: P/E ratio, EPS, market cap, sector, dividend yield, profit margin, beta, and description. Requires Alpha Vantage.",
|
|
12
13
|
parameters: params,
|
|
13
14
|
async execute(toolCallId, args) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return { content: [{ type: "text", text: prefix + text }], details: ov };
|
|
15
|
+
return withCredentialCheck("alpha_vantage", async () => {
|
|
16
|
+
const apiKey = getConfig().alphaVantageApiKey;
|
|
17
|
+
const result = await wrapProvider("alphavantage", () => getOverview(args.symbol.toUpperCase(), apiKey));
|
|
18
|
+
if (result.status === "unavailable") {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: `⚠ Company overview unavailable for ${args.symbol.toUpperCase()} (${result.reason}). Analysis will proceed without fundamentals.` }],
|
|
21
|
+
details: null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const ov = result.data;
|
|
25
|
+
const text = [
|
|
26
|
+
`**${ov.name}** (${ov.symbol}) — ${ov.exchange}`,
|
|
27
|
+
`Sector: ${ov.sector} | Industry: ${ov.industry}`,
|
|
28
|
+
`Market Cap: $${formatLargeNumber(ov.marketCap)} | P/E: ${ov.pe ?? "N/A"} | Fwd P/E: ${ov.forwardPe ?? "N/A"}`,
|
|
29
|
+
`EPS: $${ov.eps ?? "N/A"} | Div Yield: ${ov.dividendYield ? (ov.dividendYield * 100).toFixed(2) + "%" : "N/A"}`,
|
|
30
|
+
`Beta: ${ov.beta ?? "N/A"} | Profit Margin: ${ov.profitMargin ? (ov.profitMargin * 100).toFixed(1) + "%" : "N/A"}`,
|
|
31
|
+
`52W: $${ov.week52Low.toFixed(2)} - $${ov.week52High.toFixed(2)}`,
|
|
32
|
+
``,
|
|
33
|
+
ov.description.slice(0, 300) + (ov.description.length > 300 ? "..." : ""),
|
|
34
|
+
].join("\n");
|
|
35
|
+
const prefix = result.stale
|
|
36
|
+
? `⚠ Using cached fundamentals from ${result.timestamp} (Alpha Vantage rate limited)\n`
|
|
37
|
+
: "";
|
|
38
|
+
return { content: [{ type: "text", text: prefix + text }], details: ov };
|
|
39
|
+
});
|
|
40
40
|
},
|
|
41
41
|
};
|
|
42
42
|
function formatLargeNumber(n) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"company-overview.js","sourceRoot":"","sources":["../../../src/tools/fundamentals/company-overview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"company-overview.js","sourceRoot":"","sources":["../../../src/tools/fundamentals/company-overview.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
2
2
|
import { getOverview } from "../../providers/alpha-vantage.js";
|
|
3
3
|
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
4
4
|
import { getConfig } from "../../config.js";
|
|
5
|
+
import { withCredentialCheck } from "../../onboarding/tool-helpers.js";
|
|
5
6
|
const METRIC_DEFS = [
|
|
6
7
|
{ name: "P/E", extract: (c) => c.pe, lowerIsBetter: true },
|
|
7
8
|
{ name: "Forward P/E", extract: (c) => c.forwardPe, lowerIsBetter: true },
|
|
@@ -64,55 +65,54 @@ export const compsTool = {
|
|
|
64
65
|
description: "Compare 2-6 companies side-by-side on key valuation and financial metrics: P/E, Forward P/E, EPS, Profit Margin, Revenue Growth, Dividend Yield, Beta. Identifies the cheapest and most expensive on each metric.",
|
|
65
66
|
parameters: params,
|
|
66
67
|
async execute(toolCallId, args) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
return withCredentialCheck("alpha_vantage", async () => {
|
|
69
|
+
const config = getConfig();
|
|
70
|
+
const symbols = args.symbols.map((s) => s.toUpperCase());
|
|
71
|
+
const results = await Promise.all(symbols.map(async (s) => ({
|
|
72
|
+
symbol: s,
|
|
73
|
+
result: await wrapProvider("alphavantage", () => getOverview(s, config.alphaVantageApiKey)),
|
|
74
|
+
})));
|
|
75
|
+
const companies = [];
|
|
76
|
+
const unavailableSymbols = [];
|
|
77
|
+
for (const { symbol: sym, result: r } of results) {
|
|
78
|
+
if (r.status === "ok") {
|
|
79
|
+
companies.push(r.data);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
unavailableSymbols.push(sym);
|
|
83
|
+
}
|
|
81
84
|
}
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
if (companies.length === 0) {
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text: `⚠ Company fundamentals unavailable for all symbols: ${symbols.join(", ")}. Alpha Vantage may be rate limited.` }],
|
|
88
|
+
details: null,
|
|
89
|
+
};
|
|
84
90
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return `${(v * 100).toFixed(1)}%`.padStart(10);
|
|
103
|
-
return v.toFixed(2).padStart(10);
|
|
91
|
+
const result = computeComps(companies);
|
|
92
|
+
result.unavailableSymbols = unavailableSymbols;
|
|
93
|
+
const availableSymbols = companies.map((company) => company.symbol);
|
|
94
|
+
const header = `**Comparable Company Analysis**: ${availableSymbols.join(" vs ")}`;
|
|
95
|
+
const rows = result.metrics.map((m) => {
|
|
96
|
+
const vals = availableSymbols.map((s) => {
|
|
97
|
+
const v = m.values[s];
|
|
98
|
+
if (v == null)
|
|
99
|
+
return "N/A".padStart(10);
|
|
100
|
+
if (Math.abs(v) < 1)
|
|
101
|
+
return `${(v * 100).toFixed(1)}%`.padStart(10);
|
|
102
|
+
return v.toFixed(2).padStart(10);
|
|
103
|
+
});
|
|
104
|
+
const medStr = m.median != null
|
|
105
|
+
? (Math.abs(m.median) < 1 ? `${(m.median * 100).toFixed(1)}%` : m.median.toFixed(2))
|
|
106
|
+
: "N/A";
|
|
107
|
+
return ` ${m.metric.padEnd(16)} ${vals.join("")} Med: ${medStr} Best: ${m.best}`;
|
|
104
108
|
});
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
: "
|
|
108
|
-
|
|
109
|
+
const symHeader = ` ${"Metric".padEnd(16)} ${availableSymbols.map((s) => s.padStart(10)).join("")}`;
|
|
110
|
+
const noteLines = unavailableSymbols.length > 0
|
|
111
|
+
? ["", `Unavailable fundamentals: ${unavailableSymbols.join(", ")}`]
|
|
112
|
+
: [];
|
|
113
|
+
const text = [header, "", symHeader, ...rows, ...noteLines].join("\n");
|
|
114
|
+
return { content: [{ type: "text", text }], details: result };
|
|
109
115
|
});
|
|
110
|
-
const symHeader = ` ${"Metric".padEnd(16)} ${availableSymbols.map((s) => s.padStart(10)).join("")}`;
|
|
111
|
-
const noteLines = unavailableSymbols.length > 0
|
|
112
|
-
? ["", `Unavailable fundamentals: ${unavailableSymbols.join(", ")}`]
|
|
113
|
-
: [];
|
|
114
|
-
const text = [header, "", symHeader, ...rows, ...noteLines].join("\n");
|
|
115
|
-
return { content: [{ type: "text", text }], details: result };
|
|
116
116
|
},
|
|
117
117
|
};
|
|
118
118
|
//# sourceMappingURL=comps.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comps.js","sourceRoot":"","sources":["../../../src/tools/fundamentals/comps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"comps.js","sourceRoot":"","sources":["../../../src/tools/fundamentals/comps.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { getOverview, getFinancials } from "../../providers/alpha-vantage.js";
|
|
|
3
3
|
import { getQuote } from "../../providers/yahoo-finance.js";
|
|
4
4
|
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
5
5
|
import { getConfig } from "../../config.js";
|
|
6
|
+
import { withCredentialCheck } from "../../onboarding/tool-helpers.js";
|
|
6
7
|
export function computeDCF(params) {
|
|
7
8
|
const { freeCashFlow, growthRate, discountRate, terminalGrowth, years, netDebt, sharesOutstanding } = params;
|
|
8
9
|
// Project future cash flows (mid-year convention: discount at year-0.5)
|
|
@@ -97,91 +98,90 @@ export const dcfTool = {
|
|
|
97
98
|
description: "Compute a Discounted Cash Flow (DCF) intrinsic value estimate for a stock. Uses free cash flow, growth projections, and a discount rate to estimate what the stock is worth. Returns intrinsic value per share, margin of safety vs current price, and a sensitivity table.",
|
|
98
99
|
parameters: params,
|
|
99
100
|
async execute(toolCallId, args) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
101
|
+
return withCredentialCheck("alpha_vantage", async () => {
|
|
102
|
+
const symbol = args.symbol.toUpperCase();
|
|
103
|
+
const config = getConfig();
|
|
104
|
+
const [overviewResult, financialsResult, quoteResult] = await Promise.all([
|
|
105
|
+
wrapProvider("alphavantage", () => getOverview(symbol, config.alphaVantageApiKey)),
|
|
106
|
+
wrapProvider("alphavantage", () => getFinancials(symbol, config.alphaVantageApiKey)),
|
|
107
|
+
wrapProvider("yahoo", () => getQuote(symbol)),
|
|
108
|
+
]);
|
|
109
|
+
const missing = [];
|
|
110
|
+
if (overviewResult.status === "unavailable")
|
|
111
|
+
missing.push(`company overview (${overviewResult.reason})`);
|
|
112
|
+
if (financialsResult.status === "unavailable")
|
|
113
|
+
missing.push(`financial statements (${financialsResult.reason})`);
|
|
114
|
+
if (quoteResult.status === "unavailable")
|
|
115
|
+
missing.push(`stock quote (${quoteResult.reason})`);
|
|
116
|
+
if (financialsResult.status === "unavailable" || quoteResult.status === "unavailable") {
|
|
117
|
+
return {
|
|
118
|
+
content: [{ type: "text", text: `⚠ DCF valuation unavailable for ${symbol}. Missing: ${missing.join(", ")}. Both financials and current price are required.` }],
|
|
119
|
+
details: null,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const overview = overviewResult.status === "ok" ? overviewResult.data : null;
|
|
123
|
+
const financials = financialsResult.data;
|
|
124
|
+
const quote = quoteResult.data;
|
|
125
|
+
const latestFCF = financials[0]?.freeCashFlow ?? 0;
|
|
126
|
+
if (latestFCF <= 0) {
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: `${symbol} has negative or zero free cash flow ($${latestFCF.toLocaleString()}). DCF is not meaningful for companies without positive FCF.` }],
|
|
129
|
+
details: null,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// Estimate growth from historical FCF if not provided
|
|
133
|
+
let growthRate = args.growth_rate ?? 0.10;
|
|
134
|
+
if (!args.growth_rate && financials.length >= 2) {
|
|
135
|
+
const olderFCF = financials[1]?.freeCashFlow;
|
|
136
|
+
if (olderFCF && olderFCF > 0) {
|
|
137
|
+
growthRate = Math.max(0.02, Math.min(0.25, (latestFCF - olderFCF) / olderFCF));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const discountRate = args.discount_rate ?? 0.10;
|
|
141
|
+
const terminalGrowth = args.terminal_growth ?? 0.03;
|
|
142
|
+
const years = args.projection_years ?? 5;
|
|
143
|
+
const marketCap = overview?.marketCap ?? 0;
|
|
144
|
+
const sharesOutstanding = quote.price > 0 && marketCap > 0 ? marketCap / quote.price : 1;
|
|
145
|
+
const netDebt = financials[0] ? computeNetDebt(financials[0]) : 0;
|
|
146
|
+
const result = computeDCF({
|
|
147
|
+
freeCashFlow: latestFCF,
|
|
148
|
+
growthRate,
|
|
149
|
+
discountRate,
|
|
150
|
+
terminalGrowth,
|
|
151
|
+
years,
|
|
152
|
+
netDebt: Math.max(0, netDebt),
|
|
153
|
+
sharesOutstanding,
|
|
154
|
+
});
|
|
155
|
+
const marginOfSafety = (result.intrinsicValue - quote.price) / result.intrinsicValue;
|
|
156
|
+
const upside = (result.intrinsicValue - quote.price) / quote.price;
|
|
157
|
+
const lines = [
|
|
158
|
+
`**${symbol} DCF Valuation**`,
|
|
159
|
+
``,
|
|
160
|
+
`Current Price: $${quote.price.toFixed(2)}`,
|
|
161
|
+
`Intrinsic Value: $${result.intrinsicValue.toFixed(2)}`,
|
|
162
|
+
`Margin of Safety: ${(marginOfSafety * 100).toFixed(1)}%`,
|
|
163
|
+
`Upside/Downside: ${upside >= 0 ? "+" : ""}${(upside * 100).toFixed(1)}%`,
|
|
164
|
+
``,
|
|
165
|
+
`**Assumptions**`,
|
|
166
|
+
`Free Cash Flow: $${(latestFCF / 1e9).toFixed(2)}B`,
|
|
167
|
+
`Growth Rate: ${(growthRate * 100).toFixed(1)}%`,
|
|
168
|
+
`Discount Rate (WACC): ${(discountRate * 100).toFixed(1)}%`,
|
|
169
|
+
`Terminal Growth: ${(terminalGrowth * 100).toFixed(1)}%`,
|
|
170
|
+
`Projection: ${years} years`,
|
|
171
|
+
``,
|
|
172
|
+
`**Projected Cash Flows**`,
|
|
173
|
+
...result.projectedCashFlows.map((cf) => ` Year ${cf.year}: FCF $${(cf.fcf / 1e9).toFixed(2)}B → PV $${(cf.presentValue / 1e9).toFixed(2)}B`),
|
|
174
|
+
` Terminal Value: $${(result.terminalValue / 1e9).toFixed(2)}B`,
|
|
175
|
+
` Enterprise Value: $${(result.enterpriseValue / 1e9).toFixed(2)}B`,
|
|
176
|
+
``,
|
|
177
|
+
`**Sensitivity Table** (Intrinsic Value at different Growth/Discount rates)`,
|
|
178
|
+
...formatSensitivityTable(result.sensitivityTable),
|
|
179
|
+
];
|
|
128
180
|
return {
|
|
129
|
-
content: [{ type: "text", text:
|
|
130
|
-
details:
|
|
181
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
182
|
+
details: { ...result, currentPrice: quote.price, marginOfSafety, upside },
|
|
131
183
|
};
|
|
132
|
-
}
|
|
133
|
-
// Estimate growth from historical FCF if not provided
|
|
134
|
-
let growthRate = args.growth_rate ?? 0.10;
|
|
135
|
-
if (!args.growth_rate && financials.length >= 2) {
|
|
136
|
-
const olderFCF = financials[1]?.freeCashFlow;
|
|
137
|
-
if (olderFCF && olderFCF > 0) {
|
|
138
|
-
growthRate = Math.max(0.02, Math.min(0.25, (latestFCF - olderFCF) / olderFCF));
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
const discountRate = args.discount_rate ?? 0.10;
|
|
142
|
-
const terminalGrowth = args.terminal_growth ?? 0.03;
|
|
143
|
-
const years = args.projection_years ?? 5;
|
|
144
|
-
const marketCap = overview?.marketCap ?? 0;
|
|
145
|
-
const sharesOutstanding = quote.price > 0 && marketCap > 0 ? marketCap / quote.price : 1;
|
|
146
|
-
const netDebt = financials[0] ? computeNetDebt(financials[0]) : 0;
|
|
147
|
-
const result = computeDCF({
|
|
148
|
-
freeCashFlow: latestFCF,
|
|
149
|
-
growthRate,
|
|
150
|
-
discountRate,
|
|
151
|
-
terminalGrowth,
|
|
152
|
-
years,
|
|
153
|
-
netDebt: Math.max(0, netDebt),
|
|
154
|
-
sharesOutstanding,
|
|
155
184
|
});
|
|
156
|
-
const marginOfSafety = (result.intrinsicValue - quote.price) / result.intrinsicValue;
|
|
157
|
-
const upside = (result.intrinsicValue - quote.price) / quote.price;
|
|
158
|
-
const lines = [
|
|
159
|
-
`**${symbol} DCF Valuation**`,
|
|
160
|
-
``,
|
|
161
|
-
`Current Price: $${quote.price.toFixed(2)}`,
|
|
162
|
-
`Intrinsic Value: $${result.intrinsicValue.toFixed(2)}`,
|
|
163
|
-
`Margin of Safety: ${(marginOfSafety * 100).toFixed(1)}%`,
|
|
164
|
-
`Upside/Downside: ${upside >= 0 ? "+" : ""}${(upside * 100).toFixed(1)}%`,
|
|
165
|
-
``,
|
|
166
|
-
`**Assumptions**`,
|
|
167
|
-
`Free Cash Flow: $${(latestFCF / 1e9).toFixed(2)}B`,
|
|
168
|
-
`Growth Rate: ${(growthRate * 100).toFixed(1)}%`,
|
|
169
|
-
`Discount Rate (WACC): ${(discountRate * 100).toFixed(1)}%`,
|
|
170
|
-
`Terminal Growth: ${(terminalGrowth * 100).toFixed(1)}%`,
|
|
171
|
-
`Projection: ${years} years`,
|
|
172
|
-
``,
|
|
173
|
-
`**Projected Cash Flows**`,
|
|
174
|
-
...result.projectedCashFlows.map((cf) => ` Year ${cf.year}: FCF $${(cf.fcf / 1e9).toFixed(2)}B → PV $${(cf.presentValue / 1e9).toFixed(2)}B`),
|
|
175
|
-
` Terminal Value: $${(result.terminalValue / 1e9).toFixed(2)}B`,
|
|
176
|
-
` Enterprise Value: $${(result.enterpriseValue / 1e9).toFixed(2)}B`,
|
|
177
|
-
``,
|
|
178
|
-
`**Sensitivity Table** (Intrinsic Value at different Growth/Discount rates)`,
|
|
179
|
-
...formatSensitivityTable(result.sensitivityTable),
|
|
180
|
-
];
|
|
181
|
-
return {
|
|
182
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
183
|
-
details: { ...result, currentPrice: quote.price, marginOfSafety, upside },
|
|
184
|
-
};
|
|
185
185
|
},
|
|
186
186
|
};
|
|
187
187
|
function formatSensitivityTable(table) {
|