opencandle 0.3.0 → 0.4.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/assets/logo.svg +187 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +38 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -1
- package/dist/infra/browser.d.ts +10 -0
- package/dist/infra/browser.js +1 -0
- 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/memory/index.d.ts +2 -0
- package/dist/memory/index.js +1 -0
- package/dist/memory/index.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 +6 -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/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 +186 -10
- 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 +1 -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 +22 -0
- package/dist/prompts/context-builder.js +45 -10
- 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/workflow-prompts.d.ts +8 -0
- package/dist/prompts/workflow-prompts.js +39 -5
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/yahoo-finance.js +70 -33
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/defaults.js +1 -1
- package/dist/routing/defaults.js.map +1 -1
- package/dist/routing/index.d.ts +4 -0
- package/dist/routing/index.js +3 -0
- package/dist/routing/index.js.map +1 -1
- 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 +138 -0
- package/dist/routing/router-prompt.js.map +1 -0
- package/dist/routing/router-types.d.ts +62 -0
- package/dist/routing/router-types.js +2 -0
- package/dist/routing/router-types.js.map +1 -0
- package/dist/routing/router.d.ts +10 -0
- package/dist/routing/router.js +194 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/runtime/session-coordinator.d.ts +63 -3
- package/dist/runtime/session-coordinator.js +155 -4
- 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 +20 -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/comps.d.ts +1 -1
- package/dist/tools/fundamentals/dcf.d.ts +1 -1
- package/dist/tools/fundamentals/earnings.d.ts +1 -1
- package/dist/tools/fundamentals/financials.d.ts +1 -1
- package/dist/tools/fundamentals/sec-filings.d.ts +1 -1
- package/dist/tools/index.d.ts +28 -1
- package/dist/tools/index.js +27 -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/fred-data.d.ts +1 -1
- package/dist/tools/market/crypto-history.d.ts +1 -1
- package/dist/tools/market/crypto-price.d.ts +1 -1
- package/dist/tools/market/search-ticker.d.ts +1 -1
- package/dist/tools/market/stock-history.d.ts +1 -1
- package/dist/tools/market/stock-quote.d.ts +1 -1
- package/dist/tools/options/option-chain.d.ts +1 -1
- package/dist/tools/options/option-chain.js +4 -1
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/correlation.d.ts +1 -1
- package/dist/tools/portfolio/predictions.d.ts +1 -1
- package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
- package/dist/tools/portfolio/tracker.d.ts +1 -1
- package/dist/tools/portfolio/watchlist.d.ts +1 -1
- package/dist/tools/sentiment/reddit-sentiment.d.ts +1 -1
- package/dist/tools/sentiment/sentiment-summary.d.ts +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/web-search.d.ts +1 -1
- package/dist/tools/sentiment/web-sentiment.d.ts +1 -1
- package/dist/tools/technical/backtest.d.ts +1 -1
- package/dist/tools/technical/indicators.d.ts +1 -1
- package/dist/tools/technical/indicators.js +7 -1
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/workflows/options-screener.js +7 -2
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.js +3 -3
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/background-quotes.ts +31 -0
- package/gui/server/chat-event-adapter.ts +142 -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 +212 -0
- package/gui/server/server.ts +592 -0
- package/gui/server/session-actions.ts +31 -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-D1ImSJTe.js +1 -0
- package/gui/web/dist/assets/index-DBrWq43L.css +1 -0
- package/gui/web/dist/assets/index-RflHaj0y.js +67 -0
- package/gui/web/dist/assets/logo-CWpt6Y2a.svg +187 -0
- package/gui/web/dist/index.html +17 -0
- package/package.json +44 -18
- package/src/analysts/contracts.ts +189 -0
- package/src/analysts/orchestrator.ts +300 -0
- package/src/cli.ts +205 -0
- package/src/config.ts +161 -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 +64 -0
- package/src/memory/index.ts +10 -0
- package/src/memory/manager.ts +159 -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 +204 -0
- package/src/memory/tool-defaults.ts +87 -0
- package/src/memory/types.ts +67 -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 +724 -0
- package/src/pi/session-storage.ts +5 -0
- package/src/pi/session.ts +81 -0
- package/src/pi/setup.ts +371 -0
- package/src/pi/tool-adapter.ts +36 -0
- package/src/prompts/context-builder.ts +204 -0
- package/src/prompts/disclaimer.ts +9 -0
- package/src/prompts/sections.ts +46 -0
- package/src/prompts/workflow-prompts.ts +279 -0
- package/src/providers/alpha-vantage.ts +292 -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 +96 -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 +367 -0
- package/src/routing/classify-intent.ts +194 -0
- package/src/routing/defaults.ts +29 -0
- package/src/routing/entity-extractor.ts +140 -0
- package/src/routing/index.ts +26 -0
- package/src/routing/router-llm-client.ts +51 -0
- package/src/routing/router-prompt.ts +159 -0
- package/src/routing/router-types.ts +66 -0
- package/src/routing/router.ts +213 -0
- package/src/routing/slot-resolver.ts +152 -0
- package/src/routing/types.ts +63 -0
- package/src/runtime/evidence.ts +77 -0
- package/src/runtime/index.ts +55 -0
- package/src/runtime/prompt-step.ts +75 -0
- package/src/runtime/provider-ids.ts +15 -0
- package/src/runtime/provider-tracker.ts +40 -0
- package/src/runtime/run-context.ts +22 -0
- package/src/runtime/session-coordinator.ts +406 -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 +115 -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 +61 -0
- package/src/tools/index.ts +88 -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 +54 -0
- package/src/tools/market/crypto-history.ts +51 -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 +82 -0
- package/src/tools/options/option-chain.ts +91 -0
- package/src/tools/portfolio/correlation.ts +162 -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 +153 -0
- package/src/tools/sentiment/reddit-sentiment.ts +164 -0
- package/src/tools/sentiment/sentiment-summary.ts +256 -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 +150 -0
- package/src/tools/sentiment/web-sentiment.ts +76 -0
- package/src/tools/technical/backtest.ts +246 -0
- package/src/tools/technical/indicators.ts +258 -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 +35 -0
- package/src/types/portfolio.ts +41 -0
- package/src/types/sentiment.ts +70 -0
- package/src/workflows/compare-assets.ts +39 -0
- package/src/workflows/index.ts +4 -0
- package/src/workflows/options-screener.ts +49 -0
- package/src/workflows/portfolio-builder.ts +52 -0
- package/src/workflows/types.ts +4 -0
|
@@ -0,0 +1,79 @@
|
|
|
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 { getDailyHistory } from "../../providers/alpha-vantage.js";
|
|
5
|
+
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
6
|
+
import { withFallback } from "../../providers/with-fallback.js";
|
|
7
|
+
import { getConfig } from "../../config.js";
|
|
8
|
+
import type { OHLCV } from "../../types/market.js";
|
|
9
|
+
import type { ProviderResult } from "../../runtime/evidence.js";
|
|
10
|
+
|
|
11
|
+
const DAILY_INTERVALS = new Set(["1d", "1wk", "1mo"]);
|
|
12
|
+
|
|
13
|
+
const params = Type.Object({
|
|
14
|
+
symbol: Type.String({ description: "Stock ticker symbol (e.g. AAPL, MSFT)" }),
|
|
15
|
+
range: Type.Optional(
|
|
16
|
+
Type.String({
|
|
17
|
+
description: "Time range: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, max. Default: 6mo",
|
|
18
|
+
}),
|
|
19
|
+
),
|
|
20
|
+
interval: Type.Optional(
|
|
21
|
+
Type.String({
|
|
22
|
+
description: "Data interval: 1m, 5m, 15m, 1h, 1d, 1wk, 1mo. Default: 1d",
|
|
23
|
+
}),
|
|
24
|
+
),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const stockHistoryTool: AgentTool<typeof params, OHLCV[]> = {
|
|
28
|
+
name: "get_stock_history",
|
|
29
|
+
label: "Stock History",
|
|
30
|
+
description: "Get historical OHLCV (open, high, low, close, volume) data for a stock",
|
|
31
|
+
parameters: params,
|
|
32
|
+
async execute(toolCallId, args) {
|
|
33
|
+
const symbol = args.symbol.toUpperCase();
|
|
34
|
+
const range = args.range ?? "6mo";
|
|
35
|
+
const interval = args.interval ?? "1d";
|
|
36
|
+
const apiKey = getConfig().alphaVantageApiKey;
|
|
37
|
+
|
|
38
|
+
let result: ProviderResult<OHLCV[]>;
|
|
39
|
+
|
|
40
|
+
if (DAILY_INTERVALS.has(interval) && apiKey) {
|
|
41
|
+
// Daily or above — can fall back to Alpha Vantage
|
|
42
|
+
result = await withFallback([
|
|
43
|
+
{ provider: "yahoo", fn: () => getHistory(symbol, range, interval) },
|
|
44
|
+
{ provider: "alphavantage", fn: () => getDailyHistory(symbol, apiKey, range) },
|
|
45
|
+
]);
|
|
46
|
+
} else {
|
|
47
|
+
// Intraday — no cross-provider fallback
|
|
48
|
+
result = await wrapProvider("yahoo", () => getHistory(symbol, range, interval));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (result.status === "unavailable") {
|
|
52
|
+
const intradayNote = !DAILY_INTERVALS.has(interval)
|
|
53
|
+
? ` No alternate source for ${interval} data.`
|
|
54
|
+
: "";
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: "text", text: `⚠ Stock history unavailable for ${symbol} (${result.reason}).${intradayNote}` }],
|
|
57
|
+
details: [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const bars = result.data;
|
|
61
|
+
|
|
62
|
+
const summary = [
|
|
63
|
+
`${symbol} — ${bars.length} bars (${range}, ${interval})`,
|
|
64
|
+
`Period: ${bars[0]?.date} to ${bars[bars.length - 1]?.date}`,
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// Include last 10 bars as sample
|
|
68
|
+
const recent = bars.slice(-10);
|
|
69
|
+
const table = recent
|
|
70
|
+
.map(
|
|
71
|
+
(b) =>
|
|
72
|
+
`${b.date} | O:${b.open.toFixed(2)} H:${b.high.toFixed(2)} L:${b.low.toFixed(2)} C:${b.close.toFixed(2)} V:${b.volume.toLocaleString()}`,
|
|
73
|
+
)
|
|
74
|
+
.join("\n");
|
|
75
|
+
|
|
76
|
+
const text = [...summary, "", "Recent bars:", table].join("\n");
|
|
77
|
+
return { content: [{ type: "text", text }], details: bars };
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import { getQuote } from "../../providers/yahoo-finance.js";
|
|
4
|
+
import { getGlobalQuote } from "../../providers/alpha-vantage.js";
|
|
5
|
+
import { withFallback } from "../../providers/with-fallback.js";
|
|
6
|
+
import { getConfig } from "../../config.js";
|
|
7
|
+
import type { StockQuote } from "../../types/market.js";
|
|
8
|
+
|
|
9
|
+
const params = Type.Object({
|
|
10
|
+
symbol: Type.String({ description: "Stock ticker symbol (e.g. AAPL, MSFT, TSLA)" }),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const stockQuoteTool: AgentTool<typeof params, StockQuote> = {
|
|
14
|
+
name: "get_stock_quote",
|
|
15
|
+
label: "Stock Quote",
|
|
16
|
+
description:
|
|
17
|
+
"Get real-time stock price, volume, market cap, and 52-week range for a ticker symbol",
|
|
18
|
+
parameters: params,
|
|
19
|
+
async execute(toolCallId, args) {
|
|
20
|
+
const symbol = args.symbol.toUpperCase();
|
|
21
|
+
const apiKey = getConfig().alphaVantageApiKey;
|
|
22
|
+
|
|
23
|
+
const entries = [
|
|
24
|
+
{ provider: "yahoo" as const, fn: () => getQuote(symbol) },
|
|
25
|
+
...(apiKey
|
|
26
|
+
? [{ provider: "alphavantage" as const, fn: () => getGlobalQuote(symbol, apiKey) }]
|
|
27
|
+
: []),
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const result = await withFallback(entries);
|
|
31
|
+
if (result.status === "unavailable") {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `⚠ Stock quote unavailable for ${symbol} (${result.reason}).` }],
|
|
34
|
+
details: null as any,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const quote = result.data;
|
|
38
|
+
const sign = quote.changePercent >= 0 ? "+" : "";
|
|
39
|
+
|
|
40
|
+
const week52 = quote.week52High > 0 && quote.week52Low > 0
|
|
41
|
+
? `$${quote.week52Low.toFixed(2)} - $${quote.week52High.toFixed(2)}`
|
|
42
|
+
: "N/A";
|
|
43
|
+
const marketCapStr = quote.marketCap > 0 ? `$${formatLargeNumber(quote.marketCap)}` : "N/A";
|
|
44
|
+
|
|
45
|
+
const text = [
|
|
46
|
+
`${quote.symbol}: $${quote.price.toFixed(2)} (${sign}${quote.changePercent.toFixed(2)}%)`,
|
|
47
|
+
`Open: $${quote.open.toFixed(2)} | High: $${quote.high.toFixed(2)} | Low: $${quote.low.toFixed(2)}`,
|
|
48
|
+
`Volume: ${quote.volume.toLocaleString()} | Market Cap: ${marketCapStr}`,
|
|
49
|
+
`52W Range: ${week52}`,
|
|
50
|
+
].join("\n");
|
|
51
|
+
|
|
52
|
+
const prefix = result.stale
|
|
53
|
+
? `⚠ Using cached quote from ${result.timestamp} (provider rate limited)\n`
|
|
54
|
+
: "";
|
|
55
|
+
return { content: [{ type: "text", text: prefix + text }], details: quote };
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function formatLargeNumber(n: number): string {
|
|
60
|
+
if (n >= 1e12) return `${(n / 1e12).toFixed(2)}T`;
|
|
61
|
+
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
62
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(2)}M`;
|
|
63
|
+
return n.toLocaleString();
|
|
64
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Greeks } from "../../types/options.js";
|
|
2
|
+
|
|
3
|
+
interface GreeksInput {
|
|
4
|
+
type: "call" | "put";
|
|
5
|
+
spot: number; // underlying price
|
|
6
|
+
strike: number; // strike price
|
|
7
|
+
timeYears: number; // time to expiration in years
|
|
8
|
+
iv: number; // implied volatility (e.g. 0.30 for 30%)
|
|
9
|
+
riskFreeRate: number; // risk-free rate (e.g. 0.05 for 5%)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Compute option Greeks using the Black-Scholes model.
|
|
14
|
+
*/
|
|
15
|
+
export function computeGreeks(input: GreeksInput): Greeks {
|
|
16
|
+
const { type, spot, strike, timeYears, iv, riskFreeRate: r } = input;
|
|
17
|
+
|
|
18
|
+
// At expiration: return intrinsic values
|
|
19
|
+
if (timeYears <= 0) {
|
|
20
|
+
const isCall = type === "call";
|
|
21
|
+
const itm = isCall ? spot >= strike : spot <= strike;
|
|
22
|
+
return {
|
|
23
|
+
delta: itm ? (isCall ? 1 : -1) : 0,
|
|
24
|
+
gamma: 0,
|
|
25
|
+
theta: 0,
|
|
26
|
+
vega: 0,
|
|
27
|
+
rho: 0,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const sqrtT = Math.sqrt(timeYears);
|
|
32
|
+
const d1 = (Math.log(spot / strike) + (r + (iv * iv) / 2) * timeYears) / (iv * sqrtT);
|
|
33
|
+
const d2 = d1 - iv * sqrtT;
|
|
34
|
+
|
|
35
|
+
const nd1 = cdf(d1);
|
|
36
|
+
const nd2 = cdf(d2);
|
|
37
|
+
const npd1 = pdf(d1);
|
|
38
|
+
|
|
39
|
+
const expRT = Math.exp(-r * timeYears);
|
|
40
|
+
|
|
41
|
+
if (type === "call") {
|
|
42
|
+
return {
|
|
43
|
+
delta: nd1,
|
|
44
|
+
gamma: npd1 / (spot * iv * sqrtT),
|
|
45
|
+
theta: (-(spot * npd1 * iv) / (2 * sqrtT) - r * strike * expRT * nd2) / 365,
|
|
46
|
+
vega: (spot * npd1 * sqrtT) / 100, // per 1% change in IV
|
|
47
|
+
rho: (strike * timeYears * expRT * nd2) / 100, // per 1% change in rate
|
|
48
|
+
};
|
|
49
|
+
} else {
|
|
50
|
+
const nMinusD1 = cdf(-d1);
|
|
51
|
+
const nMinusD2 = cdf(-d2);
|
|
52
|
+
return {
|
|
53
|
+
delta: nd1 - 1,
|
|
54
|
+
gamma: npd1 / (spot * iv * sqrtT),
|
|
55
|
+
theta: (-(spot * npd1 * iv) / (2 * sqrtT) + r * strike * expRT * nMinusD2) / 365,
|
|
56
|
+
vega: (spot * npd1 * sqrtT) / 100,
|
|
57
|
+
rho: -(strike * timeYears * expRT * nMinusD2) / 100,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Standard normal cumulative distribution function */
|
|
63
|
+
function cdf(x: number): number {
|
|
64
|
+
// Abramowitz and Stegun approximation 26.2.17
|
|
65
|
+
const a1 = 0.254829592;
|
|
66
|
+
const a2 = -0.284496736;
|
|
67
|
+
const a3 = 1.421413741;
|
|
68
|
+
const a4 = -1.453152027;
|
|
69
|
+
const a5 = 1.061405429;
|
|
70
|
+
const p = 0.3275911;
|
|
71
|
+
|
|
72
|
+
const sign = x < 0 ? -1 : 1;
|
|
73
|
+
const absX = Math.abs(x);
|
|
74
|
+
const t = 1 / (1 + p * absX);
|
|
75
|
+
const y = 1 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-absX * absX / 2);
|
|
76
|
+
return 0.5 * (1 + sign * y);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Standard normal probability density function */
|
|
80
|
+
function pdf(x: number): number {
|
|
81
|
+
return Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI);
|
|
82
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import { getOptionsChain } from "../../providers/yahoo-finance.js";
|
|
4
|
+
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
5
|
+
import type { OptionsChain, OptionContract } from "../../types/options.js";
|
|
6
|
+
|
|
7
|
+
const params = Type.Object({
|
|
8
|
+
symbol: Type.String({ description: "Stock ticker symbol (e.g. AAPL, TSLA, SPY, MSFT)" }),
|
|
9
|
+
expiration: Type.Optional(
|
|
10
|
+
Type.String({
|
|
11
|
+
description:
|
|
12
|
+
"Expiration date as YYYY-MM-DD. If omitted, uses the nearest expiration.",
|
|
13
|
+
}),
|
|
14
|
+
),
|
|
15
|
+
type: Type.Optional(
|
|
16
|
+
Type.Union([Type.Literal("call"), Type.Literal("put"), Type.Literal("CALL"), Type.Literal("PUT")], {
|
|
17
|
+
description: "Filter by option type. Omit for both calls and puts.",
|
|
18
|
+
}),
|
|
19
|
+
),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const optionChainTool: AgentTool<typeof params, OptionsChain> = {
|
|
23
|
+
name: "get_option_chain",
|
|
24
|
+
label: "Options Chain",
|
|
25
|
+
description:
|
|
26
|
+
"Get the full options chain for a stock with strikes, bids, asks, volume, open interest, implied volatility, and computed Greeks (Delta, Gamma, Theta, Vega, Rho via Black-Scholes). No API key required.",
|
|
27
|
+
parameters: params,
|
|
28
|
+
async execute(toolCallId, args) {
|
|
29
|
+
const symbol = args.symbol.toUpperCase();
|
|
30
|
+
const normalizedType = args.type?.toLowerCase();
|
|
31
|
+
const expirationTs = args.expiration
|
|
32
|
+
? Math.floor(new Date(args.expiration).getTime() / 1000)
|
|
33
|
+
: undefined;
|
|
34
|
+
|
|
35
|
+
const result = await wrapProvider("yahoo", () => getOptionsChain(symbol, expirationTs));
|
|
36
|
+
if (result.status === "unavailable") {
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: `⚠ Options chain unavailable for ${symbol} (${result.reason}).` }],
|
|
39
|
+
details: null as any,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const chain = result.data;
|
|
43
|
+
|
|
44
|
+
const lines: string[] = [
|
|
45
|
+
`**${chain.symbol} Options Chain** — Expiry: ${chain.expirationDate}`,
|
|
46
|
+
`Underlying: $${chain.underlyingPrice.toFixed(2)}`,
|
|
47
|
+
`Available expirations: ${formatAvailableExpirations(chain.expirationDates)}`,
|
|
48
|
+
"",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const showCalls = !normalizedType || normalizedType === "call";
|
|
52
|
+
const showPuts = !normalizedType || normalizedType === "put";
|
|
53
|
+
|
|
54
|
+
if (showCalls && chain.calls.length > 0) {
|
|
55
|
+
lines.push(`**CALLS** (${chain.calls.length} contracts, volume: ${chain.totalCallVolume.toLocaleString()})`);
|
|
56
|
+
lines.push("Strike | Bid/Ask | Last | Vol | OI | IV | Delta | Theta");
|
|
57
|
+
const topCalls = sortByVolume(chain.calls).slice(0, 10);
|
|
58
|
+
for (const c of topCalls) {
|
|
59
|
+
lines.push(formatContract(c));
|
|
60
|
+
}
|
|
61
|
+
lines.push("");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (showPuts && chain.puts.length > 0) {
|
|
65
|
+
lines.push(`**PUTS** (${chain.puts.length} contracts, volume: ${chain.totalPutVolume.toLocaleString()})`);
|
|
66
|
+
lines.push("Strike | Bid/Ask | Last | Vol | OI | IV | Delta | Theta");
|
|
67
|
+
const topPuts = sortByVolume(chain.puts).slice(0, 10);
|
|
68
|
+
for (const c of topPuts) {
|
|
69
|
+
lines.push(formatContract(c));
|
|
70
|
+
}
|
|
71
|
+
lines.push("");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
lines.push(`Put/Call Ratio: ${chain.putCallRatio.toFixed(2)}`);
|
|
75
|
+
|
|
76
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: chain };
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function sortByVolume(contracts: OptionContract[]): OptionContract[] {
|
|
81
|
+
return [...contracts].sort((a, b) => b.volume - a.volume);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function formatAvailableExpirations(expirationDates: string[]): string {
|
|
85
|
+
return expirationDates.join(", ");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function formatContract(c: OptionContract): string {
|
|
89
|
+
const itm = c.inTheMoney ? "*" : " ";
|
|
90
|
+
return `${itm}$${c.strike.toFixed(2)} | $${c.bid.toFixed(2)}/$${c.ask.toFixed(2)} | $${c.lastPrice.toFixed(2)} | ${c.volume} | ${c.openInterest} | ${(c.impliedVolatility * 100).toFixed(1)}% | ${c.greeks.delta.toFixed(3)} | ${c.greeks.theta.toFixed(3)}`;
|
|
91
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
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 { computeDailyReturns } from "./risk-analysis.js";
|
|
6
|
+
import type { OHLCV } from "../../types/market.js";
|
|
7
|
+
|
|
8
|
+
export function computeCorrelation(returnsA: number[], returnsB: number[]): number {
|
|
9
|
+
const n = Math.min(returnsA.length, returnsB.length);
|
|
10
|
+
if (n === 0) return 0;
|
|
11
|
+
|
|
12
|
+
let sumA = 0, sumB = 0;
|
|
13
|
+
for (let i = 0; i < n; i++) {
|
|
14
|
+
sumA += returnsA[i];
|
|
15
|
+
sumB += returnsB[i];
|
|
16
|
+
}
|
|
17
|
+
const meanA = sumA / n;
|
|
18
|
+
const meanB = sumB / n;
|
|
19
|
+
|
|
20
|
+
let cov = 0, varA = 0, varB = 0;
|
|
21
|
+
for (let i = 0; i < n; i++) {
|
|
22
|
+
const dA = returnsA[i] - meanA;
|
|
23
|
+
const dB = returnsB[i] - meanB;
|
|
24
|
+
cov += dA * dB;
|
|
25
|
+
varA += dA * dA;
|
|
26
|
+
varB += dB * dB;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (varA === 0 || varB === 0) return 0;
|
|
30
|
+
return cov / Math.sqrt(varA * varB);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DEFAULT_MIN_OVERLAP = 20;
|
|
34
|
+
|
|
35
|
+
export function alignReturnsByDate(
|
|
36
|
+
historiesBySymbol: Map<string, OHLCV[]>,
|
|
37
|
+
minOverlap: number = DEFAULT_MIN_OVERLAP,
|
|
38
|
+
): Map<string, number[]> {
|
|
39
|
+
// Build date → close price maps for each symbol
|
|
40
|
+
const priceByDate = new Map<string, Map<string, number>>();
|
|
41
|
+
for (const [symbol, bars] of historiesBySymbol) {
|
|
42
|
+
const dateMap = new Map<string, number>();
|
|
43
|
+
for (const bar of bars) {
|
|
44
|
+
dateMap.set(bar.date, bar.close);
|
|
45
|
+
}
|
|
46
|
+
priceByDate.set(symbol, dateMap);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Find common dates across all symbols
|
|
50
|
+
const symbols = [...historiesBySymbol.keys()];
|
|
51
|
+
const firstDates = priceByDate.get(symbols[0])!;
|
|
52
|
+
const commonDates = [...firstDates.keys()].filter((date) =>
|
|
53
|
+
symbols.every((s) => priceByDate.get(s)!.has(date)),
|
|
54
|
+
).sort();
|
|
55
|
+
|
|
56
|
+
if (commonDates.length < minOverlap) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Insufficient date overlap for correlation: ${commonDates.length} common dates (need ${minOverlap}+). Symbols may trade on different exchanges or have sparse history.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Extract aligned close prices, then compute returns
|
|
63
|
+
const result = new Map<string, number[]>();
|
|
64
|
+
for (const symbol of symbols) {
|
|
65
|
+
const dateMap = priceByDate.get(symbol)!;
|
|
66
|
+
const alignedCloses = commonDates.map((d) => dateMap.get(d)!);
|
|
67
|
+
result.set(symbol, computeDailyReturns(alignedCloses));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const params = Type.Object({
|
|
74
|
+
symbols: Type.Array(Type.String(), {
|
|
75
|
+
description: "Array of 2+ ticker symbols to compute correlation matrix (e.g. ['AAPL','MSFT','GOOGL'])",
|
|
76
|
+
minItems: 2,
|
|
77
|
+
}),
|
|
78
|
+
period: Type.Optional(
|
|
79
|
+
Type.String({ description: "Historical period: 6mo, 1y, 2y. Default: 1y" }),
|
|
80
|
+
),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const correlationTool: AgentTool<typeof params> = {
|
|
84
|
+
name: "analyze_correlation",
|
|
85
|
+
label: "Correlation Matrix",
|
|
86
|
+
description:
|
|
87
|
+
"Compute pairwise return correlations between 2+ stocks. Identifies highly correlated positions (|r| > 0.7) as concentration risk. Useful for portfolio diversification analysis.",
|
|
88
|
+
parameters: params,
|
|
89
|
+
async execute(toolCallId, args) {
|
|
90
|
+
const symbols = args.symbols.map((s) => s.toUpperCase());
|
|
91
|
+
const period = args.period ?? "1y";
|
|
92
|
+
|
|
93
|
+
if (symbols.length < 2) {
|
|
94
|
+
throw new Error("Need at least 2 symbols for correlation analysis.");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Fetch history for all symbols in parallel
|
|
98
|
+
const results = await Promise.all(
|
|
99
|
+
symbols.map(async (s) => ({
|
|
100
|
+
symbol: s,
|
|
101
|
+
result: await wrapProvider("yahoo", () => getHistory(s, period, "1d")),
|
|
102
|
+
})),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const unavailable = results.filter((r) => r.result.status === "unavailable");
|
|
106
|
+
if (unavailable.length === results.length) {
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: "text", text: `⚠ Correlation analysis unavailable — could not fetch history for any symbol.` }],
|
|
109
|
+
details: null as any,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const historiesBySymbol = new Map<string, OHLCV[]>();
|
|
114
|
+
for (const { symbol: sym, result: r } of results) {
|
|
115
|
+
if (r.status === "ok") historiesBySymbol.set(sym, r.data);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const returnsBySymbol = alignReturnsByDate(historiesBySymbol);
|
|
119
|
+
|
|
120
|
+
// Build correlation matrix
|
|
121
|
+
const matrix: Record<string, Record<string, number>> = {};
|
|
122
|
+
const warnings: string[] = [];
|
|
123
|
+
|
|
124
|
+
for (const a of symbols) {
|
|
125
|
+
matrix[a] = {};
|
|
126
|
+
for (const b of symbols) {
|
|
127
|
+
if (a === b) {
|
|
128
|
+
matrix[a][b] = 1.0;
|
|
129
|
+
} else if (matrix[b]?.[a] != null) {
|
|
130
|
+
matrix[a][b] = matrix[b][a];
|
|
131
|
+
} else {
|
|
132
|
+
const r = computeCorrelation(returnsBySymbol.get(a)!, returnsBySymbol.get(b)!);
|
|
133
|
+
matrix[a][b] = r;
|
|
134
|
+
if (Math.abs(r) > 0.7 && a < b) {
|
|
135
|
+
warnings.push(`${a}/${b}: r=${r.toFixed(2)} — high correlation, concentration risk`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Format output
|
|
142
|
+
const header = `**Correlation Matrix** (${period} daily returns)`;
|
|
143
|
+
const colHeader = `${"".padEnd(8)} ${symbols.map((s) => s.padStart(8)).join("")}`;
|
|
144
|
+
const rows = symbols.map((a) => {
|
|
145
|
+
const cells = symbols.map((b) => matrix[a][b].toFixed(2).padStart(8));
|
|
146
|
+
return `${a.padEnd(8)} ${cells.join("")}`;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const lines = [header, "", colHeader, ...rows];
|
|
150
|
+
if (warnings.length > 0) {
|
|
151
|
+
lines.push("", "**Concentration Warnings:**");
|
|
152
|
+
for (const w of warnings) lines.push(` - ${w}`);
|
|
153
|
+
} else {
|
|
154
|
+
lines.push("", "No high-correlation pairs detected. Portfolio appears diversified.");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
159
|
+
details: { matrix, warnings },
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
};
|