opencandle 0.10.0 → 0.11.1
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 +12 -9
- package/dist/analysts/orchestrator.d.ts +3 -4
- package/dist/analysts/orchestrator.js +34 -44
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/cli-main.js +242 -9
- package/dist/cli-main.js.map +1 -1
- package/dist/config.d.ts +4 -9
- package/dist/config.js +7 -10
- package/dist/config.js.map +1 -1
- package/dist/doctor/report.js +1 -1
- package/dist/doctor/report.js.map +1 -1
- package/dist/infra/cache.d.ts +6 -0
- package/dist/infra/cache.js +16 -6
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/freshness.d.ts +21 -0
- package/dist/infra/freshness.js +119 -0
- package/dist/infra/freshness.js.map +1 -0
- package/dist/infra/market-calendar.d.ts +14 -0
- package/dist/infra/market-calendar.js +137 -0
- package/dist/infra/market-calendar.js.map +1 -0
- package/dist/infra/rate-limiter.js +1 -0
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/market-state/daily-report.d.ts +1 -1
- package/dist/market-state/daily-report.js +5 -16
- package/dist/market-state/daily-report.js.map +1 -1
- package/dist/market-state/service.d.ts +0 -35
- package/dist/market-state/service.js +0 -63
- package/dist/market-state/service.js.map +1 -1
- package/dist/market-state/summaries.d.ts +8 -0
- package/dist/market-state/summaries.js +70 -0
- package/dist/market-state/summaries.js.map +1 -0
- package/dist/memory/sqlite.js +20 -19
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/onboarding/providers.d.ts +25 -1
- package/dist/onboarding/providers.js +26 -0
- package/dist/onboarding/providers.js.map +1 -1
- package/dist/pi/opencandle-extension.js +57 -226
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session-action-dedupe.d.ts +6 -0
- package/dist/pi/session-action-dedupe.js +126 -0
- package/dist/pi/session-action-dedupe.js.map +1 -0
- package/dist/pi/session-writer-lock.d.ts +26 -2
- package/dist/pi/session-writer-lock.js +230 -18
- package/dist/pi/session-writer-lock.js.map +1 -1
- package/dist/pi/setup.js +5 -5
- package/dist/pi/setup.js.map +1 -1
- package/dist/pi/tui-session-coordinator.d.ts +15 -0
- package/dist/pi/tui-session-coordinator.js +283 -0
- package/dist/pi/tui-session-coordinator.js.map +1 -0
- package/dist/prompts/context-builder.js +1 -1
- package/dist/prompts/policy-cards.js +1 -1
- package/dist/prompts/policy-cards.js.map +1 -1
- package/dist/providers/alpha-vantage.js +6 -0
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/coingecko.js +1 -0
- package/dist/providers/coingecko.js.map +1 -1
- package/dist/providers/polymarket.d.ts +2 -0
- package/dist/providers/polymarket.js +163 -0
- package/dist/providers/polymarket.js.map +1 -0
- package/dist/providers/wrap-provider.js +5 -3
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +3 -0
- package/dist/providers/yahoo-finance.js +206 -10
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.js +4 -5
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/entity-extractor.js +16 -0
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/route-manifest.js +1 -2
- package/dist/routing/route-manifest.js.map +1 -1
- package/dist/routing/router-llm-client.js +18 -3
- package/dist/routing/router-llm-client.js.map +1 -1
- package/dist/routing/router-prompt.js +1 -1
- package/dist/routing/router.d.ts +1 -1
- package/dist/routing/router.js +309 -9
- package/dist/routing/router.js.map +1 -1
- package/dist/routing/slot-resolver.d.ts +1 -0
- package/dist/routing/slot-resolver.js +1 -1
- package/dist/routing/slot-resolver.js.map +1 -1
- package/dist/runtime/evidence.d.ts +1 -0
- package/dist/runtime/evidence.js.map +1 -1
- package/dist/runtime/numeric-claims.d.ts +23 -0
- package/dist/runtime/numeric-claims.js +100 -0
- package/dist/runtime/numeric-claims.js.map +1 -0
- package/dist/runtime/planning-evidence.js +1 -76
- package/dist/runtime/planning-evidence.js.map +1 -1
- package/dist/runtime/prompt-step.d.ts +10 -1
- package/dist/runtime/prompt-step.js +116 -2
- package/dist/runtime/prompt-step.js.map +1 -1
- package/dist/runtime/session-coordinator.d.ts +11 -1
- package/dist/runtime/session-coordinator.js +354 -62
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/validation.d.ts +3 -1
- package/dist/runtime/workflow-events.d.ts +1 -1
- package/dist/runtime/workflow-events.js.map +1 -1
- package/dist/runtime/workflow-types.d.ts +3 -0
- package/dist/runtime/workflow-types.js.map +1 -1
- package/dist/system-prompt.js +1 -1
- package/dist/tools/fundamentals/comps.d.ts +1 -0
- package/dist/tools/fundamentals/comps.js +20 -9
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.d.ts +1 -1
- package/dist/tools/fundamentals/dcf.js +190 -76
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/macro/event-probabilities.d.ts +8 -0
- package/dist/tools/macro/event-probabilities.js +121 -0
- package/dist/tools/macro/event-probabilities.js.map +1 -0
- package/dist/tools/market/crypto-price.d.ts +4 -1
- package/dist/tools/market/crypto-price.js +10 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/screen-stocks.js +14 -3
- package/dist/tools/market/screen-stocks.js.map +1 -1
- package/dist/tools/market/stock-quote.d.ts +4 -1
- package/dist/tools/market/stock-quote.js +9 -4
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/option-chain.d.ts +4 -1
- package/dist/tools/options/option-chain.js +12 -1
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/daily-report.js +10 -4
- package/dist/tools/portfolio/daily-report.js.map +1 -1
- package/dist/tools/portfolio/risk-analysis.js +1 -1
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +23 -5
- package/dist/tools/technical/backtest.js +131 -94
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +1 -1
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/fundamentals.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/market.d.ts +2 -0
- package/dist/types/options.d.ts +1 -0
- package/dist/types/prediction-markets.d.ts +13 -0
- package/dist/types/prediction-markets.js +2 -0
- package/dist/types/prediction-markets.js.map +1 -0
- package/gui/server/chat-event-adapter.ts +89 -5
- package/gui/server/http-routes.ts +661 -37
- package/gui/server/invoke-tool.ts +165 -16
- package/gui/server/live-chat-event-adapter.ts +37 -2
- package/gui/server/local-session-coordinator.ts +97 -0
- package/gui/server/market-state-api.ts +64 -48
- package/gui/server/projector.ts +132 -3
- package/gui/server/server.ts +51 -6
- package/gui/server/session-actions.ts +146 -45
- package/gui/server/session-entry-wait.ts +22 -3
- package/gui/server/tool-metadata.ts +3 -1
- package/gui/server/ws-hub.ts +77 -10
- package/gui/shared/chat-events.ts +10 -1
- package/gui/shared/event-reducer.ts +1 -0
- package/gui/web/dist/assets/CatalogOverlay-DZ1niyQm.js +1 -0
- package/gui/web/dist/assets/index-D4F9AJnn.css +2 -0
- package/gui/web/dist/assets/index-DtqMMBTr.js +65 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +13 -15
- package/src/analysts/orchestrator.ts +35 -56
- package/src/cli-main.ts +253 -13
- package/src/config.ts +12 -20
- package/src/doctor/report.ts +1 -1
- package/src/infra/cache.ts +24 -11
- package/src/infra/freshness.ts +165 -0
- package/src/infra/market-calendar.ts +193 -0
- package/src/infra/rate-limiter.ts +1 -0
- package/src/market-state/daily-report.ts +6 -16
- package/src/market-state/service.ts +0 -136
- package/src/market-state/summaries.ts +75 -0
- package/src/memory/sqlite.ts +23 -19
- package/src/onboarding/providers.ts +29 -1
- package/src/pi/opencandle-extension.ts +67 -270
- package/src/pi/session-action-dedupe.ts +155 -0
- package/src/pi/session-writer-lock.ts +290 -20
- package/src/pi/setup.ts +6 -6
- package/src/pi/tui-session-coordinator.ts +351 -0
- package/src/prompts/context-builder.ts +1 -1
- package/src/prompts/policy-cards.ts +1 -1
- package/src/providers/alpha-vantage.ts +6 -0
- package/src/providers/coingecko.ts +2 -0
- package/src/providers/polymarket.ts +214 -0
- package/src/providers/wrap-provider.ts +5 -3
- package/src/providers/yahoo-finance.ts +339 -27
- package/src/routing/classify-intent.ts +4 -5
- package/src/routing/entity-extractor.ts +17 -0
- package/src/routing/route-manifest.ts +1 -2
- package/src/routing/router-llm-client.ts +31 -19
- package/src/routing/router-prompt.ts +1 -1
- package/src/routing/router.ts +379 -8
- package/src/routing/slot-resolver.ts +1 -1
- package/src/runtime/evidence.ts +1 -0
- package/src/runtime/numeric-claims.ts +108 -0
- package/src/runtime/planning-evidence.ts +6 -91
- package/src/runtime/prompt-step.ts +127 -2
- package/src/runtime/session-coordinator.ts +464 -74
- package/src/runtime/validation.ts +1 -1
- package/src/runtime/workflow-events.ts +2 -0
- package/src/runtime/workflow-types.ts +3 -0
- package/src/system-prompt.ts +1 -1
- package/src/tools/AGENTS.md +1 -1
- package/src/tools/fundamentals/comps.ts +25 -14
- package/src/tools/fundamentals/dcf.ts +217 -87
- package/src/tools/index.ts +3 -3
- package/src/tools/macro/event-probabilities.ts +141 -0
- package/src/tools/market/crypto-price.ts +14 -2
- package/src/tools/market/screen-stocks.ts +14 -3
- package/src/tools/market/stock-quote.ts +49 -43
- package/src/tools/options/option-chain.ts +16 -2
- package/src/tools/portfolio/daily-report.ts +10 -4
- package/src/tools/portfolio/risk-analysis.ts +1 -1
- package/src/tools/technical/backtest.ts +167 -108
- package/src/tools/technical/indicators.ts +1 -1
- package/src/types/fundamentals.ts +1 -0
- package/src/types/index.ts +1 -0
- package/src/types/market.ts +2 -0
- package/src/types/options.ts +1 -0
- package/src/types/prediction-markets.ts +13 -0
- package/dist/tools/portfolio/predictions.d.ts +0 -55
- package/dist/tools/portfolio/predictions.js +0 -422
- package/dist/tools/portfolio/predictions.js.map +0 -1
- package/gui/server/chat-run-session.ts +0 -16
- package/gui/web/dist/assets/CatalogOverlay-CYptsda-.js +0 -1
- package/gui/web/dist/assets/index-B7QAjY5g.js +0 -65
- package/gui/web/dist/assets/index-D5dbWPfM.css +0 -2
- package/src/tools/portfolio/predictions.ts +0 -553
package/src/system-prompt.ts
CHANGED
|
@@ -19,7 +19,7 @@ You are an analyst, not a fiduciary advisor. When asked for entry levels, price
|
|
|
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,
|
|
22
|
+
- **Portfolio**: track_portfolio, analyze_risk, manage_watchlist, analyze_correlation, manage_alerts, daily_watchlist_report, manage_notifications — position tracking, P&L, Sharpe ratio, VaR, watchlist tracking, durable local alerts, daily watchlist reports, notification history, and correlation matrix
|
|
23
23
|
- **User Interaction**: ask_user — ask clarification questions and setup confirmations
|
|
24
24
|
|
|
25
25
|
## Analytical Framework
|
package/src/tools/AGENTS.md
CHANGED
|
@@ -10,7 +10,7 @@ src/tools/
|
|
|
10
10
|
├── options/ # Options chains, Greeks computation
|
|
11
11
|
├── macro/ # FRED economic data, fear & greed index
|
|
12
12
|
├── sentiment/ # Reddit sentiment, news sentiment
|
|
13
|
-
├── portfolio/ # Tracker, risk analysis, watchlist, correlation
|
|
13
|
+
├── portfolio/ # Tracker, risk analysis, watchlist, correlation
|
|
14
14
|
├── market/ # Stock quotes, history, crypto, ticker search
|
|
15
15
|
└── index.ts # getAllTools() registry — add new tools here
|
|
16
16
|
```
|
|
@@ -4,6 +4,7 @@ import { getConfig } from "../../config.js";
|
|
|
4
4
|
import { withCredentialCheck } from "../../onboarding/tool-helpers.js";
|
|
5
5
|
import { getOverview } from "../../providers/alpha-vantage.js";
|
|
6
6
|
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
7
|
+
import { getYahooCompanyOverview } from "../../providers/yahoo-finance.js";
|
|
7
8
|
import type { CompanyOverview } from "../../types/fundamentals.js";
|
|
8
9
|
|
|
9
10
|
export interface CompsMetric {
|
|
@@ -20,6 +21,7 @@ export interface CompsResult {
|
|
|
20
21
|
companies: CompanyOverview[];
|
|
21
22
|
metrics: CompsMetric[];
|
|
22
23
|
unavailableSymbols: string[];
|
|
24
|
+
unavailableReasons: Record<string, string>;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
type MetricDef = {
|
|
@@ -65,7 +67,7 @@ export function computeComps(companies: CompanyOverview[]): CompsResult {
|
|
|
65
67
|
return { metric: def.name, values, median, p25, p75, best, worst };
|
|
66
68
|
});
|
|
67
69
|
|
|
68
|
-
return { companies, metrics, unavailableSymbols: [] };
|
|
70
|
+
return { companies, metrics, unavailableSymbols: [], unavailableReasons: {} };
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
function computeMedian(sorted: number[]): number | null {
|
|
@@ -103,23 +105,26 @@ export const compsTool: AgentTool<typeof params> = {
|
|
|
103
105
|
const config = getConfig();
|
|
104
106
|
const symbols = args.symbols.map((s) => s.toUpperCase());
|
|
105
107
|
|
|
106
|
-
const results = await Promise.all(
|
|
107
|
-
symbols.map(async (s) => ({
|
|
108
|
-
symbol: s,
|
|
109
|
-
result: await wrapProvider("alphavantage", () =>
|
|
110
|
-
getOverview(s, config.alphaVantageApiKey!),
|
|
111
|
-
),
|
|
112
|
-
})),
|
|
113
|
-
);
|
|
114
|
-
|
|
115
108
|
const companies: CompanyOverview[] = [];
|
|
116
109
|
const unavailableSymbols: string[] = [];
|
|
110
|
+
const unavailableReasons: Record<string, string> = {};
|
|
111
|
+
|
|
112
|
+
for (const sym of symbols) {
|
|
113
|
+
const alphaResult = await wrapProvider("alphavantage", () =>
|
|
114
|
+
getOverview(sym, config.alphaVantageApiKey!),
|
|
115
|
+
);
|
|
116
|
+
if (alphaResult.status === "ok") {
|
|
117
|
+
companies.push(alphaResult.data);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
companies.push(
|
|
121
|
+
const yahooResult = await wrapProvider("yahoo", () => getYahooCompanyOverview(sym));
|
|
122
|
+
if (yahooResult.status === "ok") {
|
|
123
|
+
companies.push(yahooResult.data);
|
|
121
124
|
} else {
|
|
122
125
|
unavailableSymbols.push(sym);
|
|
126
|
+
unavailableReasons[sym] =
|
|
127
|
+
`Alpha Vantage: ${alphaResult.reason}; Yahoo Finance: ${yahooResult.reason}`;
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
130
|
|
|
@@ -137,6 +142,7 @@ export const compsTool: AgentTool<typeof params> = {
|
|
|
137
142
|
|
|
138
143
|
const result = computeComps(companies);
|
|
139
144
|
result.unavailableSymbols = unavailableSymbols;
|
|
145
|
+
result.unavailableReasons = unavailableReasons;
|
|
140
146
|
|
|
141
147
|
const availableSymbols = companies.map((company) => company.symbol);
|
|
142
148
|
const header = `**Comparable Company Analysis**: ${availableSymbols.join(" vs ")}`;
|
|
@@ -161,9 +167,14 @@ export const compsTool: AgentTool<typeof params> = {
|
|
|
161
167
|
unavailableSymbols.length > 0
|
|
162
168
|
? ["", `Unavailable fundamentals: ${unavailableSymbols.join(", ")}`]
|
|
163
169
|
: [];
|
|
170
|
+
const reasonLines =
|
|
171
|
+
unavailableSymbols.length > 0
|
|
172
|
+
? unavailableSymbols.map((symbol) => ` - ${symbol} (${unavailableReasons[symbol]})`)
|
|
173
|
+
: [];
|
|
164
174
|
const text = [header, "", symHeader, ...rows, ...noteLines].join("\n");
|
|
175
|
+
const textWithReasons = reasonLines.length > 0 ? `${text}\n${reasonLines.join("\n")}` : text;
|
|
165
176
|
|
|
166
|
-
return { content: [{ type: "text", text }], details: result };
|
|
177
|
+
return { content: [{ type: "text", text: textWithReasons }], details: result };
|
|
167
178
|
});
|
|
168
179
|
},
|
|
169
180
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
3
|
import { getConfig } from "../../config.js";
|
|
4
|
-
import { withCredentialCheck } from "../../onboarding/tool-helpers.js";
|
|
5
4
|
import { getFinancials, getOverview } from "../../providers/alpha-vantage.js";
|
|
5
|
+
import { ProviderCredentialError } from "../../providers/provider-credential-error.js";
|
|
6
6
|
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
7
|
-
import { getQuote } from "../../providers/yahoo-finance.js";
|
|
7
|
+
import { getQuote, getYahooFinancials } from "../../providers/yahoo-finance.js";
|
|
8
|
+
import type { ProviderResult } from "../../runtime/evidence.js";
|
|
8
9
|
import type { FinancialStatement } from "../../types/fundamentals.js";
|
|
9
10
|
|
|
10
11
|
export interface DCFResult {
|
|
@@ -45,6 +46,14 @@ export function computeDCF(params: DCFParams): DCFResult {
|
|
|
45
46
|
sharesOutstanding,
|
|
46
47
|
} = params;
|
|
47
48
|
|
|
49
|
+
// Gordon Growth requires discount > terminal growth; a non-positive spread
|
|
50
|
+
// divides by zero or flips the terminal value's sign.
|
|
51
|
+
if (discountRate <= terminalGrowth) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Gordon Growth constraint violated: terminal growth (${(terminalGrowth * 100).toFixed(1)}%) must be below the discount rate (${(discountRate * 100).toFixed(1)}%).`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
// Project future cash flows (mid-year convention: discount at year-0.5)
|
|
49
58
|
const projectedCashFlows: Array<{ year: number; fcf: number; presentValue: number }> = [];
|
|
50
59
|
for (let y = 1; y <= years; y++) {
|
|
@@ -152,12 +161,36 @@ function computeDCFSimple(
|
|
|
152
161
|
return (sumPV + pvTV - debt) / shares;
|
|
153
162
|
}
|
|
154
163
|
|
|
155
|
-
export function computeNetDebt(f: FinancialStatement): number {
|
|
164
|
+
export function computeNetDebt(f: FinancialStatement): number | null {
|
|
156
165
|
if (f.totalDebt != null && f.cashAndEquivalents != null) {
|
|
157
166
|
return f.totalDebt - f.cashAndEquivalents;
|
|
158
167
|
}
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function optionalAlphaVantage<T>(
|
|
172
|
+
apiKey: string | undefined,
|
|
173
|
+
fn: (apiKey: string) => Promise<T>,
|
|
174
|
+
): Promise<ProviderResult<T>> {
|
|
175
|
+
if (!apiKey) {
|
|
176
|
+
return {
|
|
177
|
+
status: "unavailable",
|
|
178
|
+
reason: "Alpha Vantage API key not configured",
|
|
179
|
+
provider: "alphavantage",
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
return await wrapProvider("alphavantage", () => fn(apiKey));
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (error instanceof ProviderCredentialError) {
|
|
186
|
+
return {
|
|
187
|
+
status: "unavailable",
|
|
188
|
+
reason: `Alpha Vantage credential ${error.reason}`,
|
|
189
|
+
provider: "alphavantage",
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
161
194
|
}
|
|
162
195
|
|
|
163
196
|
const params = Type.Object({
|
|
@@ -186,113 +219,210 @@ export const dcfTool: AgentTool<typeof params> = {
|
|
|
186
219
|
"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.",
|
|
187
220
|
parameters: params,
|
|
188
221
|
async execute(_toolCallId, args) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
222
|
+
const symbol = args.symbol.toUpperCase();
|
|
223
|
+
const config = getConfig();
|
|
224
|
+
const alphaVantageApiKey = config.alphaVantageApiKey;
|
|
225
|
+
|
|
226
|
+
const [alphaVantageFinancialsResult, quoteResult] = await Promise.all([
|
|
227
|
+
optionalAlphaVantage(alphaVantageApiKey, (apiKey) => getFinancials(symbol, apiKey)),
|
|
228
|
+
wrapProvider("yahoo", () => getQuote(symbol)),
|
|
229
|
+
]);
|
|
230
|
+
|
|
231
|
+
const missing: string[] = [];
|
|
232
|
+
if (quoteResult.status === "unavailable") missing.push(`stock quote (${quoteResult.reason})`);
|
|
233
|
+
|
|
234
|
+
if (quoteResult.status === "unavailable") {
|
|
235
|
+
return {
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: "text",
|
|
239
|
+
text: `⚠ DCF valuation unavailable for ${symbol}. Missing: ${missing.join(", ")}. Both financials and current price are required.`,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
details: null,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const quote = quoteResult.data;
|
|
247
|
+
if (quote.price <= 0) {
|
|
248
|
+
return {
|
|
249
|
+
content: [
|
|
250
|
+
{
|
|
251
|
+
type: "text",
|
|
252
|
+
text: `⚠ DCF valuation unavailable for ${symbol}. Missing: current stock price. DCF output requires a real current quote price.`,
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
details: null,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let financialsSource = "Alpha Vantage";
|
|
260
|
+
let financials: FinancialStatement[];
|
|
261
|
+
if (alphaVantageFinancialsResult.status === "ok" && !alphaVantageFinancialsResult.stale) {
|
|
262
|
+
financials = alphaVantageFinancialsResult.data;
|
|
263
|
+
} else {
|
|
264
|
+
const alphaVantageReason =
|
|
265
|
+
alphaVantageFinancialsResult.status === "ok"
|
|
266
|
+
? `stale cached financial statements${
|
|
267
|
+
alphaVantageFinancialsResult.timestamp
|
|
268
|
+
? ` from ${alphaVantageFinancialsResult.timestamp}`
|
|
269
|
+
: ""
|
|
270
|
+
}`
|
|
271
|
+
: alphaVantageFinancialsResult.reason;
|
|
272
|
+
const yahooFinancialsResult = await wrapProvider("yahoo", () => getYahooFinancials(symbol));
|
|
273
|
+
if (yahooFinancialsResult.status === "unavailable") {
|
|
207
274
|
return {
|
|
208
275
|
content: [
|
|
209
276
|
{
|
|
210
277
|
type: "text",
|
|
211
|
-
text: `⚠ DCF valuation unavailable for ${symbol}. Missing: ${
|
|
278
|
+
text: `⚠ DCF valuation unavailable for ${symbol}. Missing: financial statements (Alpha Vantage: ${alphaVantageReason}; Yahoo Finance: ${yahooFinancialsResult.reason}). Both financials and current price are required.`,
|
|
212
279
|
},
|
|
213
280
|
],
|
|
214
281
|
details: null,
|
|
215
282
|
};
|
|
216
283
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const latestFCF = financials[0]?.freeCashFlow ?? 0;
|
|
223
|
-
if (latestFCF <= 0) {
|
|
284
|
+
if (yahooFinancialsResult.stale) {
|
|
285
|
+
const cachedAt = yahooFinancialsResult.timestamp
|
|
286
|
+
? ` from ${yahooFinancialsResult.timestamp}`
|
|
287
|
+
: "";
|
|
224
288
|
return {
|
|
225
289
|
content: [
|
|
226
290
|
{
|
|
227
291
|
type: "text",
|
|
228
|
-
text:
|
|
292
|
+
text: `⚠ DCF valuation unavailable for ${symbol}. Yahoo Finance returned stale cached financial statements${cachedAt} after Alpha Vantage could not provide fresh financial statements (${alphaVantageReason}). DCF output requires fresh financial statements and a current price.`,
|
|
229
293
|
},
|
|
230
294
|
],
|
|
231
295
|
details: null,
|
|
232
296
|
};
|
|
233
297
|
}
|
|
298
|
+
financials = yahooFinancialsResult.data;
|
|
299
|
+
financialsSource = "Yahoo Finance";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const latestFCF = financials[0]?.freeCashFlow ?? 0;
|
|
303
|
+
if (latestFCF <= 0) {
|
|
304
|
+
return {
|
|
305
|
+
content: [
|
|
306
|
+
{
|
|
307
|
+
type: "text",
|
|
308
|
+
text: `${symbol} has negative or zero free cash flow ($${latestFCF.toLocaleString()}). DCF is not meaningful for companies without positive FCF.`,
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
details: null,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
234
314
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
315
|
+
// Estimate growth from historical FCF if not provided
|
|
316
|
+
let growthRate = args.growth_rate ?? 0.1;
|
|
317
|
+
if (!args.growth_rate && financials.length >= 2) {
|
|
318
|
+
const olderFCF = financials[1]?.freeCashFlow;
|
|
319
|
+
if (olderFCF && olderFCF > 0) {
|
|
320
|
+
growthRate = Math.max(0.02, Math.min(0.25, (latestFCF - olderFCF) / olderFCF));
|
|
242
321
|
}
|
|
322
|
+
}
|
|
243
323
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
netDebt: Math.max(0, netDebt),
|
|
258
|
-
sharesOutstanding,
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
const marginOfSafety = (result.intrinsicValue - quote.price) / result.intrinsicValue;
|
|
262
|
-
const upside = (result.intrinsicValue - quote.price) / quote.price;
|
|
263
|
-
|
|
264
|
-
const lines = [
|
|
265
|
-
`**${symbol} DCF Valuation**`,
|
|
266
|
-
``,
|
|
267
|
-
`Current Price: $${quote.price.toFixed(2)}`,
|
|
268
|
-
`Intrinsic Value: $${result.intrinsicValue.toFixed(2)}`,
|
|
269
|
-
`Margin of Safety: ${(marginOfSafety * 100).toFixed(1)}%`,
|
|
270
|
-
`Upside/Downside: ${upside >= 0 ? "+" : ""}${(upside * 100).toFixed(1)}%`,
|
|
271
|
-
``,
|
|
272
|
-
`**Assumptions**`,
|
|
273
|
-
`Free Cash Flow: $${(latestFCF / 1e9).toFixed(2)}B`,
|
|
274
|
-
`Growth Rate: ${(growthRate * 100).toFixed(1)}%`,
|
|
275
|
-
`Discount Rate (WACC): ${(discountRate * 100).toFixed(1)}%`,
|
|
276
|
-
`Terminal Growth: ${(terminalGrowth * 100).toFixed(1)}%`,
|
|
277
|
-
`Projection: ${years} years`,
|
|
278
|
-
``,
|
|
279
|
-
`**Projected Cash Flows**`,
|
|
280
|
-
...result.projectedCashFlows.map(
|
|
281
|
-
(cf) =>
|
|
282
|
-
` Year ${cf.year}: FCF $${(cf.fcf / 1e9).toFixed(2)}B → PV $${(cf.presentValue / 1e9).toFixed(2)}B`,
|
|
283
|
-
),
|
|
284
|
-
` Terminal Value: $${(result.terminalValue / 1e9).toFixed(2)}B`,
|
|
285
|
-
` Enterprise Value: $${(result.enterpriseValue / 1e9).toFixed(2)}B`,
|
|
286
|
-
``,
|
|
287
|
-
`**Sensitivity Table** (Intrinsic Value at different Growth/Discount rates)`,
|
|
288
|
-
...formatSensitivityTable(result.sensitivityTable),
|
|
289
|
-
];
|
|
324
|
+
const discountRate = args.discount_rate ?? 0.1;
|
|
325
|
+
const terminalGrowth = args.terminal_growth ?? 0.03;
|
|
326
|
+
if (discountRate <= terminalGrowth) {
|
|
327
|
+
return {
|
|
328
|
+
content: [
|
|
329
|
+
{
|
|
330
|
+
type: "text",
|
|
331
|
+
text: `⚠ Invalid DCF assumptions for ${symbol}: terminal growth (${(terminalGrowth * 100).toFixed(1)}%) must be below the discount rate (${(discountRate * 100).toFixed(1)}%) for the Gordon Growth terminal value to be meaningful. Lower terminal_growth or raise discount_rate.`,
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
details: null,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
290
337
|
|
|
338
|
+
const years = args.projection_years ?? 5;
|
|
339
|
+
// Prefer an actual reported share count, then derive from market cap
|
|
340
|
+
// only when needed. Never substitute a placeholder share count.
|
|
341
|
+
const statementSharesOutstanding = financials[0]?.sharesOutstanding;
|
|
342
|
+
let marketCap = quote.marketCap > 0 ? quote.marketCap : 0;
|
|
343
|
+
let sharesOutstanding =
|
|
344
|
+
statementSharesOutstanding && statementSharesOutstanding > 0 ? statementSharesOutstanding : 0;
|
|
345
|
+
if (sharesOutstanding <= 0 && marketCap <= 0) {
|
|
346
|
+
const overviewResult = await optionalAlphaVantage(alphaVantageApiKey, (apiKey) =>
|
|
347
|
+
getOverview(symbol, apiKey),
|
|
348
|
+
);
|
|
349
|
+
if (overviewResult.status === "ok" && overviewResult.data.marketCap > 0) {
|
|
350
|
+
marketCap = overviewResult.data.marketCap;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (sharesOutstanding <= 0 && quote.price > 0 && marketCap > 0) {
|
|
354
|
+
sharesOutstanding = marketCap / quote.price;
|
|
355
|
+
}
|
|
356
|
+
if (sharesOutstanding <= 0) {
|
|
291
357
|
return {
|
|
292
|
-
content: [
|
|
293
|
-
|
|
358
|
+
content: [
|
|
359
|
+
{
|
|
360
|
+
type: "text",
|
|
361
|
+
text: `⚠ Cannot compute a per-share DCF for ${symbol}: shares outstanding are unavailable from financial statements and cannot be derived because ${quote.price <= 0 ? "the current quote price is unavailable" : "market capitalization is unavailable from the overview and quote providers"}. Per-share intrinsic value requires a real share count.`,
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
details: null,
|
|
294
365
|
};
|
|
366
|
+
}
|
|
367
|
+
// Signed on purpose: net cash (negative net debt) adds to equity value.
|
|
368
|
+
// Omit the adjustment if debt/cash fields are missing; assets minus
|
|
369
|
+
// liabilities is book equity, not a net-cash proxy.
|
|
370
|
+
const computedNetDebt = financials[0] ? computeNetDebt(financials[0]) : null;
|
|
371
|
+
const netDebt = computedNetDebt ?? 0;
|
|
372
|
+
|
|
373
|
+
const result = computeDCF({
|
|
374
|
+
freeCashFlow: latestFCF,
|
|
375
|
+
growthRate,
|
|
376
|
+
discountRate,
|
|
377
|
+
terminalGrowth,
|
|
378
|
+
years,
|
|
379
|
+
netDebt,
|
|
380
|
+
sharesOutstanding,
|
|
295
381
|
});
|
|
382
|
+
if (computedNetDebt == null) {
|
|
383
|
+
result.warnings.push(
|
|
384
|
+
"Net debt adjustment omitted because total debt and cash equivalents were unavailable.",
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const marginOfSafety = (result.intrinsicValue - quote.price) / result.intrinsicValue;
|
|
389
|
+
const upside = (result.intrinsicValue - quote.price) / quote.price;
|
|
390
|
+
|
|
391
|
+
const lines = [
|
|
392
|
+
`**${symbol} DCF Valuation**`,
|
|
393
|
+
``,
|
|
394
|
+
`Current Price: $${quote.price.toFixed(2)}`,
|
|
395
|
+
`Intrinsic Value: $${result.intrinsicValue.toFixed(2)}`,
|
|
396
|
+
`Margin of Safety: ${(marginOfSafety * 100).toFixed(1)}%`,
|
|
397
|
+
`Upside/Downside: ${upside >= 0 ? "+" : ""}${(upside * 100).toFixed(1)}%`,
|
|
398
|
+
``,
|
|
399
|
+
`**Assumptions**`,
|
|
400
|
+
`Financial statements source: ${financialsSource}`,
|
|
401
|
+
`Free Cash Flow: $${(latestFCF / 1e9).toFixed(2)}B`,
|
|
402
|
+
`Growth Rate: ${(growthRate * 100).toFixed(1)}%`,
|
|
403
|
+
`Discount Rate (WACC): ${(discountRate * 100).toFixed(1)}%`,
|
|
404
|
+
`Terminal Growth: ${(terminalGrowth * 100).toFixed(1)}%`,
|
|
405
|
+
`Projection: ${years} years`,
|
|
406
|
+
``,
|
|
407
|
+
`**Projected Cash Flows**`,
|
|
408
|
+
...result.projectedCashFlows.map(
|
|
409
|
+
(cf) =>
|
|
410
|
+
` Year ${cf.year}: FCF $${(cf.fcf / 1e9).toFixed(2)}B → PV $${(cf.presentValue / 1e9).toFixed(2)}B`,
|
|
411
|
+
),
|
|
412
|
+
` Terminal Value: $${(result.terminalValue / 1e9).toFixed(2)}B`,
|
|
413
|
+
` Enterprise Value: $${(result.enterpriseValue / 1e9).toFixed(2)}B`,
|
|
414
|
+
``,
|
|
415
|
+
`**Sensitivity Table** (Intrinsic Value at different Growth/Discount rates)`,
|
|
416
|
+
...formatSensitivityTable(result.sensitivityTable),
|
|
417
|
+
...(result.warnings.length > 0
|
|
418
|
+
? ["", "**Warnings**", ...result.warnings.map((warning) => `- ${warning}`)]
|
|
419
|
+
: []),
|
|
420
|
+
];
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
424
|
+
details: { ...result, currentPrice: quote.price, marginOfSafety, upside },
|
|
425
|
+
};
|
|
296
426
|
},
|
|
297
427
|
};
|
|
298
428
|
|
package/src/tools/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { dcfTool } from "./fundamentals/dcf.js";
|
|
|
6
6
|
import { earningsTool } from "./fundamentals/earnings.js";
|
|
7
7
|
import { financialsTool } from "./fundamentals/financials.js";
|
|
8
8
|
import { secFilingsTool } from "./fundamentals/sec-filings.js";
|
|
9
|
+
import { eventProbabilitiesTool } from "./macro/event-probabilities.js";
|
|
9
10
|
import { fearGreedTool } from "./macro/fear-greed.js";
|
|
10
11
|
import { fredDataTool } from "./macro/fred-data.js";
|
|
11
12
|
import { cryptoHistoryTool } from "./market/crypto-history.js";
|
|
@@ -20,7 +21,6 @@ import { correlationTool } from "./portfolio/correlation.js";
|
|
|
20
21
|
import { dailyReportTool } from "./portfolio/daily-report.js";
|
|
21
22
|
import { holdingsOverlapTool } from "./portfolio/holdings-overlap.js";
|
|
22
23
|
import { notificationsTool } from "./portfolio/notifications.js";
|
|
23
|
-
import { predictionsTool } from "./portfolio/predictions.js";
|
|
24
24
|
import { riskAnalysisTool } from "./portfolio/risk-analysis.js";
|
|
25
25
|
import { portfolioTrackerTool } from "./portfolio/tracker.js";
|
|
26
26
|
import { watchlistTool } from "./portfolio/watchlist.js";
|
|
@@ -39,6 +39,7 @@ export { dcfTool } from "./fundamentals/dcf.js";
|
|
|
39
39
|
export { earningsTool } from "./fundamentals/earnings.js";
|
|
40
40
|
export { financialsTool } from "./fundamentals/financials.js";
|
|
41
41
|
export { secFilingsTool } from "./fundamentals/sec-filings.js";
|
|
42
|
+
export { eventProbabilitiesTool } from "./macro/event-probabilities.js";
|
|
42
43
|
export { fearGreedTool } from "./macro/fear-greed.js";
|
|
43
44
|
export { fredDataTool } from "./macro/fred-data.js";
|
|
44
45
|
export { cryptoHistoryTool } from "./market/crypto-history.js";
|
|
@@ -53,7 +54,6 @@ export { correlationTool } from "./portfolio/correlation.js";
|
|
|
53
54
|
export { dailyReportTool } from "./portfolio/daily-report.js";
|
|
54
55
|
export { holdingsOverlapTool } from "./portfolio/holdings-overlap.js";
|
|
55
56
|
export { notificationsTool } from "./portfolio/notifications.js";
|
|
56
|
-
export { predictionsTool } from "./portfolio/predictions.js";
|
|
57
57
|
export { riskAnalysisTool } from "./portfolio/risk-analysis.js";
|
|
58
58
|
export { portfolioTrackerTool } from "./portfolio/tracker.js";
|
|
59
59
|
export { watchlistTool } from "./portfolio/watchlist.js";
|
|
@@ -80,6 +80,7 @@ export function getAllTools(options: { askUserHandler?: AskUserHandler } = {}):
|
|
|
80
80
|
dcfTool,
|
|
81
81
|
compsTool,
|
|
82
82
|
secFilingsTool,
|
|
83
|
+
eventProbabilitiesTool,
|
|
83
84
|
fredDataTool,
|
|
84
85
|
fearGreedTool,
|
|
85
86
|
createRedditSentimentTool({ askUserHandler: options.askUserHandler }),
|
|
@@ -91,7 +92,6 @@ export function getAllTools(options: { askUserHandler?: AskUserHandler } = {}):
|
|
|
91
92
|
watchlistTool,
|
|
92
93
|
correlationTool,
|
|
93
94
|
holdingsOverlapTool,
|
|
94
|
-
predictionsTool,
|
|
95
95
|
alertsTool,
|
|
96
96
|
dailyReportTool,
|
|
97
97
|
notificationsTool,
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { searchPredictionMarkets } from "../../providers/polymarket.js";
|
|
4
|
+
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
5
|
+
import type { PredictionMarketQuote } from "../../types/prediction-markets.js";
|
|
6
|
+
import { renderUntrustedText, untrustedContentHeader } from "../sentiment/untrusted-text.js";
|
|
7
|
+
|
|
8
|
+
const LOW_LIQUIDITY_VOLUME_USD = 10_000;
|
|
9
|
+
|
|
10
|
+
const params = Type.Object({
|
|
11
|
+
query: Type.String({
|
|
12
|
+
minLength: 1,
|
|
13
|
+
description: "Free-text event question to search on Polymarket.",
|
|
14
|
+
}),
|
|
15
|
+
limit: Type.Optional(
|
|
16
|
+
Type.Integer({
|
|
17
|
+
minimum: 1,
|
|
18
|
+
maximum: 50,
|
|
19
|
+
description: "Maximum number of outcome quotes to return. Default: 8.",
|
|
20
|
+
}),
|
|
21
|
+
),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const eventProbabilitiesTool: AgentTool<typeof params, PredictionMarketQuote[]> = {
|
|
25
|
+
name: "get_event_probabilities",
|
|
26
|
+
label: "Event Probabilities",
|
|
27
|
+
description:
|
|
28
|
+
"Search Polymarket for market-implied event probabilities. Fetches and formats probabilities only; it does not analyze or forecast outcomes.",
|
|
29
|
+
parameters: params,
|
|
30
|
+
async execute(_toolCallId, args) {
|
|
31
|
+
const query = args.query.trim();
|
|
32
|
+
const limit = args.limit ?? 8;
|
|
33
|
+
const result = await wrapProvider("polymarket", () => searchPredictionMarkets(query, limit));
|
|
34
|
+
|
|
35
|
+
if (result.status === "unavailable") {
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: [
|
|
41
|
+
`Polymarket prediction markets unavailable for "${query}" (${result.reason}).`,
|
|
42
|
+
"",
|
|
43
|
+
caveatText(),
|
|
44
|
+
].join("\n"),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
details: [] as PredictionMarketQuote[],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const quotes = result.data;
|
|
52
|
+
const cacheDisclosure =
|
|
53
|
+
result.stale && result.timestamp
|
|
54
|
+
? [
|
|
55
|
+
`Using cached Polymarket data from ${new Date(result.timestamp).toISOString()} because the provider fallback returned stale cache.`,
|
|
56
|
+
"",
|
|
57
|
+
]
|
|
58
|
+
: [];
|
|
59
|
+
const text =
|
|
60
|
+
quotes.length === 0
|
|
61
|
+
? [
|
|
62
|
+
...cacheDisclosure,
|
|
63
|
+
`No Polymarket prediction markets found for "${query}".`,
|
|
64
|
+
"",
|
|
65
|
+
caveatText(),
|
|
66
|
+
].join("\n")
|
|
67
|
+
: [
|
|
68
|
+
`**Polymarket event probabilities for "${query}"**`,
|
|
69
|
+
"",
|
|
70
|
+
...cacheDisclosure,
|
|
71
|
+
untrustedContentHeader("Polymarket market text"),
|
|
72
|
+
"",
|
|
73
|
+
...quotes.map(formatQuote),
|
|
74
|
+
"",
|
|
75
|
+
caveatText(),
|
|
76
|
+
].join("\n");
|
|
77
|
+
|
|
78
|
+
return { content: [{ type: "text", text }], details: quotes.map(sanitizeQuoteDetails) };
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function sanitizeQuoteDetails(quote: PredictionMarketQuote): PredictionMarketQuote {
|
|
83
|
+
return {
|
|
84
|
+
source: quote.source,
|
|
85
|
+
marketId: renderUntrustedText(quote.marketId, 120),
|
|
86
|
+
title: renderUntrustedText(quote.title, 220),
|
|
87
|
+
outcome: renderUntrustedText(quote.outcome, 80),
|
|
88
|
+
probability: quote.probability,
|
|
89
|
+
...(quote.volumeUsd !== undefined && { volumeUsd: quote.volumeUsd }),
|
|
90
|
+
...(quote.liquidityUsd !== undefined && { liquidityUsd: quote.liquidityUsd }),
|
|
91
|
+
url: renderUntrustedText(quote.url, 500),
|
|
92
|
+
...(quote.asOf && { asOf: renderUntrustedText(quote.asOf, 80) }),
|
|
93
|
+
...(quote.closeDate && { closeDate: renderUntrustedText(quote.closeDate, 80) }),
|
|
94
|
+
...(quote.resolutionCriteria && {
|
|
95
|
+
resolutionCriteria: renderUntrustedText(quote.resolutionCriteria, 1000),
|
|
96
|
+
}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function formatQuote(quote: PredictionMarketQuote): string {
|
|
101
|
+
return [
|
|
102
|
+
`- ${formatPercent(quote.probability)} - ${renderUntrustedText(quote.outcome, 80)}: ${renderUntrustedText(quote.title, 220)}`,
|
|
103
|
+
` Volume: ${formatUsd(quote.volumeUsd)} | Liquidity: ${formatUsd(quote.liquidityUsd)} | Close: ${
|
|
104
|
+
quote.closeDate ? renderUntrustedText(quote.closeDate, 80) : "unknown"
|
|
105
|
+
}`,
|
|
106
|
+
` Updated: ${quote.asOf ? renderUntrustedText(quote.asOf, 80) : "unavailable from Polymarket"}`,
|
|
107
|
+
quote.volumeUsd !== undefined && quote.volumeUsd < LOW_LIQUIDITY_VOLUME_USD
|
|
108
|
+
? " LOW-LIQUIDITY: volume under $10,000"
|
|
109
|
+
: undefined,
|
|
110
|
+
` Resolution criteria: ${
|
|
111
|
+
quote.resolutionCriteria
|
|
112
|
+
? renderUntrustedText(quote.resolutionCriteria, 1000)
|
|
113
|
+
: "Resolution criteria unavailable from Polymarket."
|
|
114
|
+
}`,
|
|
115
|
+
` URL: ${renderUntrustedText(quote.url, 500)}`,
|
|
116
|
+
]
|
|
117
|
+
.filter(Boolean)
|
|
118
|
+
.join("\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function formatPercent(value: number): string {
|
|
122
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatUsd(value: number | undefined): string {
|
|
126
|
+
if (value === undefined) return "unavailable";
|
|
127
|
+
return new Intl.NumberFormat("en-US", {
|
|
128
|
+
style: "currency",
|
|
129
|
+
currency: "USD",
|
|
130
|
+
maximumFractionDigits: 2,
|
|
131
|
+
}).format(value);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function caveatText(): string {
|
|
135
|
+
return [
|
|
136
|
+
"Caveats:",
|
|
137
|
+
"- These are market-implied probabilities from trader positioning, not calibrated forecasts.",
|
|
138
|
+
"- Thin markets can show noisy or stale prices; results with volume under $10,000 are explicitly flagged low-liquidity.",
|
|
139
|
+
"- Polymarket is a crypto-settled venue with platform and settlement risks distinct from regulated exchanges.",
|
|
140
|
+
].join("\n");
|
|
141
|
+
}
|