opencandle 0.11.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 +5 -4
- package/dist/analysts/orchestrator.d.ts +2 -0
- package/dist/analysts/orchestrator.js +19 -0
- package/dist/analysts/orchestrator.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/summaries.d.ts +8 -0
- package/dist/market-state/summaries.js +70 -0
- package/dist/market-state/summaries.js.map +1 -0
- 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 +44 -5
- package/dist/pi/opencandle-extension.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/entity-extractor.js +16 -0
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/route-manifest.js +1 -1
- 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.d.ts +1 -1
- package/dist/routing/router.js +274 -5
- 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 +352 -49
- 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/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.js +183 -112
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +3 -0
- 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/risk-analysis.js +1 -1
- package/dist/tools/portfolio/risk-analysis.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 +285 -37
- package/gui/server/invoke-tool.ts +14 -13
- package/gui/server/live-chat-event-adapter.ts +37 -2
- package/gui/server/market-state-api.ts +63 -5
- package/gui/server/projector.ts +132 -3
- package/gui/server/session-actions.ts +32 -40
- package/gui/server/session-entry-wait.ts +22 -3
- package/gui/server/ws-hub.ts +16 -4
- 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 +8 -13
- package/src/analysts/orchestrator.ts +20 -0
- 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/summaries.ts +75 -0
- package/src/onboarding/providers.ts +29 -1
- package/src/pi/opencandle-extension.ts +55 -5
- 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/entity-extractor.ts +17 -0
- package/src/routing/route-manifest.ts +1 -1
- package/src/routing/router-llm-client.ts +31 -19
- package/src/routing/router.ts +340 -5
- 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 +462 -57
- 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/tools/fundamentals/comps.ts +25 -14
- package/src/tools/fundamentals/dcf.ts +204 -124
- package/src/tools/index.ts +3 -0
- 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/risk-analysis.ts +1 -1
- 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/gui/server/chat-run-session.ts +0 -16
- package/gui/web/dist/assets/CatalogOverlay-ChRTKNlf.js +0 -1
- package/gui/web/dist/assets/index-BzyqyVnd.css +0 -2
- package/gui/web/dist/assets/index-DvMjkOP9.js +0 -65
|
@@ -4,6 +4,7 @@ import { cache, STALE_LIMIT, TTL } from "../infra/cache.js";
|
|
|
4
4
|
import { HttpError, httpGet } from "../infra/http-client.js";
|
|
5
5
|
import { rateLimiter } from "../infra/rate-limiter.js";
|
|
6
6
|
import { computeGreeks } from "../tools/options/greeks.js";
|
|
7
|
+
import type { CompanyOverview, FinancialStatement } from "../types/fundamentals.js";
|
|
7
8
|
import type { OHLCV, StockQuote } from "../types/market.js";
|
|
8
9
|
import type {
|
|
9
10
|
OptionContract,
|
|
@@ -51,27 +52,107 @@ interface YahooChartResponse {
|
|
|
51
52
|
|
|
52
53
|
interface YahooQuoteSummaryResponse {
|
|
53
54
|
quoteSummary: {
|
|
54
|
-
result?: Array<
|
|
55
|
-
price?: {
|
|
56
|
-
symbol?: string;
|
|
57
|
-
shortName?: string;
|
|
58
|
-
longName?: string;
|
|
59
|
-
};
|
|
60
|
-
topHoldings?: {
|
|
61
|
-
holdings?: Array<{
|
|
62
|
-
symbol?: string;
|
|
63
|
-
holdingName?: string;
|
|
64
|
-
holdingPercent?: YahooNumber;
|
|
65
|
-
}>;
|
|
66
|
-
equityHoldings?: {
|
|
67
|
-
sectorWeightings?: Array<Record<string, YahooNumber>>;
|
|
68
|
-
};
|
|
69
|
-
};
|
|
70
|
-
}>;
|
|
55
|
+
result?: Array<YahooQuoteSummaryResult>;
|
|
71
56
|
error?: { code?: string; description?: string } | null;
|
|
72
57
|
};
|
|
73
58
|
}
|
|
74
59
|
|
|
60
|
+
interface YahooQuoteSummaryResult {
|
|
61
|
+
price?: {
|
|
62
|
+
symbol?: string;
|
|
63
|
+
shortName?: string;
|
|
64
|
+
longName?: string;
|
|
65
|
+
exchangeName?: string;
|
|
66
|
+
marketCap?: YahooNumber;
|
|
67
|
+
};
|
|
68
|
+
assetProfile?: {
|
|
69
|
+
longBusinessSummary?: string;
|
|
70
|
+
sector?: string;
|
|
71
|
+
industry?: string;
|
|
72
|
+
};
|
|
73
|
+
summaryProfile?: {
|
|
74
|
+
longBusinessSummary?: string;
|
|
75
|
+
sector?: string;
|
|
76
|
+
industry?: string;
|
|
77
|
+
};
|
|
78
|
+
summaryDetail?: {
|
|
79
|
+
trailingPE?: YahooNumber;
|
|
80
|
+
forwardPE?: YahooNumber;
|
|
81
|
+
dividendYield?: YahooNumber;
|
|
82
|
+
beta?: YahooNumber;
|
|
83
|
+
fiftyTwoWeekHigh?: YahooNumber;
|
|
84
|
+
fiftyTwoWeekLow?: YahooNumber;
|
|
85
|
+
averageVolume?: YahooNumber;
|
|
86
|
+
};
|
|
87
|
+
defaultKeyStatistics?: {
|
|
88
|
+
trailingEps?: YahooNumber;
|
|
89
|
+
forwardEps?: YahooNumber;
|
|
90
|
+
beta?: YahooNumber;
|
|
91
|
+
};
|
|
92
|
+
financialData?: {
|
|
93
|
+
profitMargins?: YahooNumber;
|
|
94
|
+
revenueGrowth?: YahooNumber;
|
|
95
|
+
totalRevenue?: YahooNumber;
|
|
96
|
+
};
|
|
97
|
+
topHoldings?: {
|
|
98
|
+
holdings?: Array<{
|
|
99
|
+
symbol?: string;
|
|
100
|
+
holdingName?: string;
|
|
101
|
+
holdingPercent?: YahooNumber;
|
|
102
|
+
}>;
|
|
103
|
+
equityHoldings?: {
|
|
104
|
+
sectorWeightings?: Array<Record<string, YahooNumber>>;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function getYahooCompanyOverview(symbol: string): Promise<CompanyOverview> {
|
|
110
|
+
const normalizedSymbol = symbol.toUpperCase();
|
|
111
|
+
const cacheKey = `yahoo:overview:${normalizedSymbol}`;
|
|
112
|
+
const cached = cache.get<CompanyOverview>(cacheKey);
|
|
113
|
+
if (cached) return cached;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await rateLimiter.acquire("yahoo");
|
|
117
|
+
const result = await getYahooCompanySummary(normalizedSymbol);
|
|
118
|
+
if (!result?.price?.symbol) {
|
|
119
|
+
throw new Error(`Yahoo Finance: no company fundamentals returned for ${normalizedSymbol}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const profile = result.assetProfile ?? result.summaryProfile;
|
|
123
|
+
const overview: CompanyOverview = {
|
|
124
|
+
symbol: result.price.symbol.toUpperCase(),
|
|
125
|
+
name: result.price.longName ?? result.price.shortName ?? result.price.symbol,
|
|
126
|
+
description: profile?.longBusinessSummary ?? "",
|
|
127
|
+
exchange: result.price.exchangeName ?? "",
|
|
128
|
+
sector: profile?.sector ?? "",
|
|
129
|
+
industry: profile?.industry ?? "",
|
|
130
|
+
marketCap: yahooNumber(result.price.marketCap) ?? 0,
|
|
131
|
+
pe: yahooNullableNumber(result.summaryDetail?.trailingPE),
|
|
132
|
+
forwardPe: yahooNullableNumber(result.summaryDetail?.forwardPE),
|
|
133
|
+
eps:
|
|
134
|
+
yahooNullableNumber(result.defaultKeyStatistics?.trailingEps) ??
|
|
135
|
+
yahooNullableNumber(result.defaultKeyStatistics?.forwardEps),
|
|
136
|
+
dividendYield: yahooNullableNumber(result.summaryDetail?.dividendYield),
|
|
137
|
+
beta:
|
|
138
|
+
yahooNullableNumber(result.summaryDetail?.beta) ??
|
|
139
|
+
yahooNullableNumber(result.defaultKeyStatistics?.beta),
|
|
140
|
+
week52High: yahooNumber(result.summaryDetail?.fiftyTwoWeekHigh) ?? 0,
|
|
141
|
+
week52Low: yahooNumber(result.summaryDetail?.fiftyTwoWeekLow) ?? 0,
|
|
142
|
+
avgVolume: yahooNumber(result.summaryDetail?.averageVolume) ?? 0,
|
|
143
|
+
profitMargin: yahooNullableNumber(result.financialData?.profitMargins),
|
|
144
|
+
revenueGrowth: yahooNullableNumber(result.financialData?.revenueGrowth),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
cache.set(cacheKey, overview, TTL.FUNDAMENTALS);
|
|
148
|
+
return overview;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
const stale = cache.getStale<CompanyOverview>(cacheKey, STALE_LIMIT.FUNDAMENTALS);
|
|
151
|
+
if (stale) return stale.value;
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
75
156
|
export async function getQuote(symbol: string): Promise<StockQuote> {
|
|
76
157
|
const cacheKey = `yahoo:quote:${symbol}`;
|
|
77
158
|
const cached = cache.get<StockQuote>(cacheKey);
|
|
@@ -117,6 +198,7 @@ export async function getQuote(symbol: string): Promise<StockQuote> {
|
|
|
117
198
|
week52High: meta.fiftyTwoWeekHigh ?? 0,
|
|
118
199
|
week52Low: meta.fiftyTwoWeekLow ?? 0,
|
|
119
200
|
timestamp: Date.now(),
|
|
201
|
+
asOf: yahooMarketTimeToIso(meta.regularMarketTime),
|
|
120
202
|
currency:
|
|
121
203
|
typeof meta.currency === "string" && meta.currency.trim() !== ""
|
|
122
204
|
? meta.currency.trim().toUpperCase()
|
|
@@ -136,6 +218,174 @@ export async function getQuote(symbol: string): Promise<StockQuote> {
|
|
|
136
218
|
}
|
|
137
219
|
}
|
|
138
220
|
|
|
221
|
+
type YahooFundamentalsRow = {
|
|
222
|
+
date?: Date | string | number;
|
|
223
|
+
totalRevenue?: number;
|
|
224
|
+
annualTotalRevenue?: number;
|
|
225
|
+
grossProfit?: number;
|
|
226
|
+
annualGrossProfit?: number;
|
|
227
|
+
operatingIncome?: number;
|
|
228
|
+
annualOperatingIncome?: number;
|
|
229
|
+
netIncome?: number;
|
|
230
|
+
annualNetIncome?: number;
|
|
231
|
+
netIncomeCommonStockholders?: number;
|
|
232
|
+
annualNetIncomeCommonStockholders?: number;
|
|
233
|
+
basicEPS?: number;
|
|
234
|
+
annualBasicEPS?: number;
|
|
235
|
+
dilutedEPS?: number;
|
|
236
|
+
annualDilutedEPS?: number;
|
|
237
|
+
totalAssets?: number;
|
|
238
|
+
annualTotalAssets?: number;
|
|
239
|
+
totalLiabilitiesNetMinorityInterest?: number;
|
|
240
|
+
annualTotalLiabilitiesNetMinorityInterest?: number;
|
|
241
|
+
totalLiabilities?: number;
|
|
242
|
+
annualTotalLiabilities?: number;
|
|
243
|
+
stockholdersEquity?: number;
|
|
244
|
+
annualStockholdersEquity?: number;
|
|
245
|
+
commonStockEquity?: number;
|
|
246
|
+
annualCommonStockEquity?: number;
|
|
247
|
+
cashFlowFromContinuingOperatingActivities?: number;
|
|
248
|
+
annualCashFlowFromContinuingOperatingActivities?: number;
|
|
249
|
+
operatingCashFlow?: number;
|
|
250
|
+
annualOperatingCashFlow?: number;
|
|
251
|
+
freeCashFlow?: number;
|
|
252
|
+
annualFreeCashFlow?: number;
|
|
253
|
+
capitalExpenditure?: number;
|
|
254
|
+
annualCapitalExpenditure?: number;
|
|
255
|
+
capitalExpenditures?: number;
|
|
256
|
+
annualCapitalExpenditures?: number;
|
|
257
|
+
totalDebt?: number;
|
|
258
|
+
annualTotalDebt?: number;
|
|
259
|
+
cashAndCashEquivalents?: number;
|
|
260
|
+
annualCashAndCashEquivalents?: number;
|
|
261
|
+
ordinarySharesNumber?: number;
|
|
262
|
+
annualOrdinarySharesNumber?: number;
|
|
263
|
+
shareIssued?: number;
|
|
264
|
+
annualShareIssued?: number;
|
|
265
|
+
dilutedAverageShares?: number;
|
|
266
|
+
annualDilutedAverageShares?: number;
|
|
267
|
+
basicAverageShares?: number;
|
|
268
|
+
annualBasicAverageShares?: number;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export async function getYahooFinancials(symbol: string): Promise<FinancialStatement[]> {
|
|
272
|
+
const normalizedSymbol = symbol.toUpperCase();
|
|
273
|
+
const cacheKey = `yahoo:financials:${normalizedSymbol}`;
|
|
274
|
+
const cached = cache.get<FinancialStatement[]>(cacheKey);
|
|
275
|
+
if (cached) return cached;
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
await rateLimiter.acquire("yahoo");
|
|
279
|
+
|
|
280
|
+
const period1 = new Date();
|
|
281
|
+
period1.setUTCFullYear(period1.getUTCFullYear() - 6);
|
|
282
|
+
const rows = (await getYahooFinance2Client().fundamentalsTimeSeries(
|
|
283
|
+
normalizedSymbol,
|
|
284
|
+
{
|
|
285
|
+
period1,
|
|
286
|
+
period2: new Date(),
|
|
287
|
+
type: "annual",
|
|
288
|
+
module: "all",
|
|
289
|
+
},
|
|
290
|
+
{ validateResult: false },
|
|
291
|
+
)) as YahooFundamentalsRow[];
|
|
292
|
+
|
|
293
|
+
const statements = rows
|
|
294
|
+
.map((row) => yahooFundamentalsRowToStatement(row))
|
|
295
|
+
.filter((statement): statement is FinancialStatement => statement !== null)
|
|
296
|
+
.sort((a, b) => b.fiscalDate.localeCompare(a.fiscalDate))
|
|
297
|
+
.slice(0, 4);
|
|
298
|
+
|
|
299
|
+
if (statements.length === 0) {
|
|
300
|
+
throw new Error(`Yahoo Finance: no financial statements returned for ${normalizedSymbol}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
cache.set(cacheKey, statements, TTL.FUNDAMENTALS);
|
|
304
|
+
return statements;
|
|
305
|
+
} catch (error) {
|
|
306
|
+
const stale = cache.getStale<FinancialStatement[]>(cacheKey, STALE_LIMIT.FUNDAMENTALS);
|
|
307
|
+
if (stale) return stale.value;
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function yahooFundamentalsRowToStatement(row: YahooFundamentalsRow): FinancialStatement | null {
|
|
313
|
+
const fiscalDate = normalizeYahooDate(row.date);
|
|
314
|
+
if (!fiscalDate) return null;
|
|
315
|
+
|
|
316
|
+
const operatingCashFlowValue =
|
|
317
|
+
row.annualCashFlowFromContinuingOperatingActivities ??
|
|
318
|
+
row.cashFlowFromContinuingOperatingActivities ??
|
|
319
|
+
row.annualOperatingCashFlow ??
|
|
320
|
+
row.operatingCashFlow;
|
|
321
|
+
const capitalExpenditureValue =
|
|
322
|
+
row.annualCapitalExpenditure ??
|
|
323
|
+
row.capitalExpenditure ??
|
|
324
|
+
row.annualCapitalExpenditures ??
|
|
325
|
+
row.capitalExpenditures;
|
|
326
|
+
const operatingCashFlow = operatingCashFlowValue ?? 0;
|
|
327
|
+
const capitalExpenditure = capitalExpenditureValue ?? 0;
|
|
328
|
+
const freeCashFlow =
|
|
329
|
+
row.annualFreeCashFlow ??
|
|
330
|
+
row.freeCashFlow ??
|
|
331
|
+
(operatingCashFlowValue !== undefined && capitalExpenditureValue !== undefined
|
|
332
|
+
? operatingCashFlow + capitalExpenditure
|
|
333
|
+
: 0);
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
fiscalDate,
|
|
337
|
+
revenue: row.annualTotalRevenue ?? row.totalRevenue ?? 0,
|
|
338
|
+
grossProfit: row.annualGrossProfit ?? row.grossProfit ?? 0,
|
|
339
|
+
operatingIncome: row.annualOperatingIncome ?? row.operatingIncome ?? 0,
|
|
340
|
+
netIncome:
|
|
341
|
+
row.annualNetIncome ??
|
|
342
|
+
row.netIncome ??
|
|
343
|
+
row.annualNetIncomeCommonStockholders ??
|
|
344
|
+
row.netIncomeCommonStockholders ??
|
|
345
|
+
0,
|
|
346
|
+
eps: row.annualDilutedEPS ?? row.dilutedEPS ?? row.annualBasicEPS ?? row.basicEPS ?? 0,
|
|
347
|
+
totalAssets: row.annualTotalAssets ?? row.totalAssets ?? 0,
|
|
348
|
+
totalLiabilities:
|
|
349
|
+
row.annualTotalLiabilitiesNetMinorityInterest ??
|
|
350
|
+
row.totalLiabilitiesNetMinorityInterest ??
|
|
351
|
+
row.annualTotalLiabilities ??
|
|
352
|
+
row.totalLiabilities ??
|
|
353
|
+
0,
|
|
354
|
+
totalEquity:
|
|
355
|
+
row.annualStockholdersEquity ??
|
|
356
|
+
row.stockholdersEquity ??
|
|
357
|
+
row.annualCommonStockEquity ??
|
|
358
|
+
row.commonStockEquity ??
|
|
359
|
+
0,
|
|
360
|
+
operatingCashFlow,
|
|
361
|
+
freeCashFlow,
|
|
362
|
+
totalDebt: row.annualTotalDebt ?? row.totalDebt,
|
|
363
|
+
cashAndEquivalents: row.annualCashAndCashEquivalents ?? row.cashAndCashEquivalents,
|
|
364
|
+
sharesOutstanding:
|
|
365
|
+
row.annualOrdinarySharesNumber ??
|
|
366
|
+
row.ordinarySharesNumber ??
|
|
367
|
+
row.annualShareIssued ??
|
|
368
|
+
row.shareIssued ??
|
|
369
|
+
row.annualDilutedAverageShares ??
|
|
370
|
+
row.dilutedAverageShares ??
|
|
371
|
+
row.annualBasicAverageShares ??
|
|
372
|
+
row.basicAverageShares,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function normalizeYahooDate(value: Date | string | number | undefined): string | null {
|
|
377
|
+
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
|
378
|
+
if (typeof value === "number") {
|
|
379
|
+
if (!Number.isFinite(value)) return null;
|
|
380
|
+
const timestampMs = Math.abs(value) < 1_000_000_000_000 ? value * 1000 : value;
|
|
381
|
+
const date = new Date(timestampMs);
|
|
382
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString().slice(0, 10);
|
|
383
|
+
}
|
|
384
|
+
if (typeof value !== "string" || value.trim() === "") return null;
|
|
385
|
+
const date = new Date(value);
|
|
386
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString().slice(0, 10);
|
|
387
|
+
}
|
|
388
|
+
|
|
139
389
|
function isZeroResultQuote(quote: StockQuote): boolean {
|
|
140
390
|
return (
|
|
141
391
|
quote.price === 0 &&
|
|
@@ -243,28 +493,65 @@ export async function getFundHoldings(symbol: string): Promise<FundHoldings> {
|
|
|
243
493
|
}
|
|
244
494
|
|
|
245
495
|
async function getFundHoldingsSummary(symbol: string): Promise<YahooQuoteSummaryResponse> {
|
|
496
|
+
return getYahooQuoteSummary(symbol, "price,topHoldings");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async function getYahooCompanySummary(symbol: string): Promise<YahooQuoteSummaryResult> {
|
|
500
|
+
const modules = [
|
|
501
|
+
"price",
|
|
502
|
+
"assetProfile",
|
|
503
|
+
"summaryProfile",
|
|
504
|
+
"summaryDetail",
|
|
505
|
+
"defaultKeyStatistics",
|
|
506
|
+
"financialData",
|
|
507
|
+
] as const;
|
|
246
508
|
try {
|
|
247
|
-
return await
|
|
509
|
+
return (await getYahooFinance2Client().quoteSummary(
|
|
510
|
+
symbol,
|
|
511
|
+
{ modules: [...modules] },
|
|
512
|
+
{ validateResult: false },
|
|
513
|
+
)) as YahooQuoteSummaryResult;
|
|
514
|
+
} catch (error) {
|
|
515
|
+
const data = await getYahooQuoteSummary(symbol, modules.join(","));
|
|
516
|
+
if (data.quoteSummary.error) {
|
|
517
|
+
throw new Error(
|
|
518
|
+
`Yahoo Finance: ${data.quoteSummary.error.description ?? data.quoteSummary.error.code ?? "quoteSummary error"}`,
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
return data.quoteSummary.result?.[0] ?? {};
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async function getYahooQuoteSummary(
|
|
526
|
+
symbol: string,
|
|
527
|
+
modules: string,
|
|
528
|
+
): Promise<YahooQuoteSummaryResponse> {
|
|
529
|
+
try {
|
|
530
|
+
return await fetchYahooQuoteSummary(symbol, modules);
|
|
248
531
|
} catch (error) {
|
|
249
532
|
if (!isYahooAuthError(error)) throw error;
|
|
250
|
-
return
|
|
533
|
+
return fetchYahooQuoteSummaryWithCrumb(symbol, modules);
|
|
251
534
|
}
|
|
252
535
|
}
|
|
253
536
|
|
|
254
|
-
async function
|
|
255
|
-
|
|
256
|
-
|
|
537
|
+
async function fetchYahooQuoteSummary(
|
|
538
|
+
symbol: string,
|
|
539
|
+
modules: string,
|
|
540
|
+
): Promise<YahooQuoteSummaryResponse> {
|
|
541
|
+
const encodedModules = encodeURIComponent(modules);
|
|
542
|
+
const url = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${encodedModules}`;
|
|
257
543
|
return httpGet<YahooQuoteSummaryResponse>(url, {
|
|
258
544
|
headers: { "User-Agent": "OpenCandle/1.0" },
|
|
259
545
|
});
|
|
260
546
|
}
|
|
261
547
|
|
|
262
|
-
async function
|
|
548
|
+
async function fetchYahooQuoteSummaryWithCrumb(
|
|
263
549
|
symbol: string,
|
|
550
|
+
modules: string,
|
|
264
551
|
): Promise<YahooQuoteSummaryResponse> {
|
|
265
|
-
const
|
|
552
|
+
const encodedModules = encodeURIComponent(modules);
|
|
266
553
|
const { crumb, cookie } = await getYahooCrumb();
|
|
267
|
-
const url = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${
|
|
554
|
+
const url = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${encodedModules}&crumb=${encodeURIComponent(crumb)}`;
|
|
268
555
|
try {
|
|
269
556
|
return await httpGet<YahooQuoteSummaryResponse>(url, {
|
|
270
557
|
headers: { "User-Agent": BROWSER_UA, Cookie: cookie },
|
|
@@ -273,7 +560,7 @@ async function fetchFundHoldingsSummaryWithCrumb(
|
|
|
273
560
|
if (!isYahooAuthError(error)) throw error;
|
|
274
561
|
clearCrumbCache();
|
|
275
562
|
const fresh = await getYahooCrumb();
|
|
276
|
-
const retryUrl = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${
|
|
563
|
+
const retryUrl = `${QUOTE_SUMMARY_URL}/${encodeURIComponent(symbol)}?modules=${encodedModules}&crumb=${encodeURIComponent(fresh.crumb)}`;
|
|
277
564
|
return httpGet<YahooQuoteSummaryResponse>(retryUrl, {
|
|
278
565
|
headers: { "User-Agent": BROWSER_UA, Cookie: fresh.cookie },
|
|
279
566
|
});
|
|
@@ -285,11 +572,20 @@ function isYahooAuthError(error: unknown): boolean {
|
|
|
285
572
|
}
|
|
286
573
|
|
|
287
574
|
function normalizeHoldingWeight(value: YahooNumber | undefined): number | undefined {
|
|
288
|
-
const numeric =
|
|
575
|
+
const numeric = yahooNumber(value);
|
|
289
576
|
if (numeric === undefined || !Number.isFinite(numeric) || numeric <= 0) return undefined;
|
|
290
577
|
return numeric > 1 ? roundWeight(numeric / 100) : roundWeight(numeric);
|
|
291
578
|
}
|
|
292
579
|
|
|
580
|
+
function yahooNumber(value: YahooNumber | undefined): number | undefined {
|
|
581
|
+
const numeric = typeof value === "number" ? value : value?.raw;
|
|
582
|
+
return numeric !== undefined && Number.isFinite(numeric) ? numeric : undefined;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function yahooNullableNumber(value: YahooNumber | undefined): number | null {
|
|
586
|
+
return yahooNumber(value) ?? null;
|
|
587
|
+
}
|
|
588
|
+
|
|
293
589
|
function normalizeSectorWeights(
|
|
294
590
|
sectors: Array<Record<string, YahooNumber>> | undefined,
|
|
295
591
|
): Record<string, number> | undefined {
|
|
@@ -608,6 +904,7 @@ function parseOptionsResponse(data: YahooOptionsResponse): OptionsChain {
|
|
|
608
904
|
putCallRatio: totalCallVolume > 0 ? totalPutVolume / totalCallVolume : 0,
|
|
609
905
|
quoteStatus,
|
|
610
906
|
fetchedAt: new Date().toISOString(),
|
|
907
|
+
asOf: yahooMarketTimeToIso(quote.regularMarketTime),
|
|
611
908
|
};
|
|
612
909
|
}
|
|
613
910
|
|
|
@@ -670,3 +967,18 @@ function toYahooUnixSeconds(value: Date | number | string): number {
|
|
|
670
967
|
}
|
|
671
968
|
return Math.floor(new Date(value).getTime() / 1000);
|
|
672
969
|
}
|
|
970
|
+
|
|
971
|
+
function yahooMarketTimeToIso(value: unknown): string | undefined {
|
|
972
|
+
let date: Date | undefined;
|
|
973
|
+
if (value instanceof Date) {
|
|
974
|
+
date = value;
|
|
975
|
+
} else if (typeof value === "number" && Number.isFinite(value)) {
|
|
976
|
+
date = new Date(value < 10_000_000_000 ? value * 1000 : value);
|
|
977
|
+
} else if (typeof value === "string" && value.trim() !== "") {
|
|
978
|
+
const numeric = Number(value);
|
|
979
|
+
date = Number.isFinite(numeric)
|
|
980
|
+
? new Date(numeric < 10_000_000_000 ? numeric * 1000 : numeric)
|
|
981
|
+
: new Date(value);
|
|
982
|
+
}
|
|
983
|
+
return date && Number.isFinite(date.getTime()) ? date.toISOString() : undefined;
|
|
984
|
+
}
|
|
@@ -189,6 +189,15 @@ const LOWERCASE_FINANCE_TERMS = new Set([
|
|
|
189
189
|
"rate",
|
|
190
190
|
"rates",
|
|
191
191
|
"cuts",
|
|
192
|
+
// Trading verbs written in lowercase are actions, not tickers — "sell
|
|
193
|
+
// calls" must not mint a SELL symbol. Uppercase ticker extraction is
|
|
194
|
+
// unaffected, so a genuine $SELL / "SELL" ticker still resolves.
|
|
195
|
+
"sell",
|
|
196
|
+
"buy",
|
|
197
|
+
"hold",
|
|
198
|
+
"write",
|
|
199
|
+
"roll",
|
|
200
|
+
"trim",
|
|
192
201
|
"gold",
|
|
193
202
|
"oil",
|
|
194
203
|
"stock",
|
|
@@ -478,6 +487,14 @@ function extractDteHint(input: string): string | undefined {
|
|
|
478
487
|
const lower = input.toLowerCase();
|
|
479
488
|
const explicitDays = lower.match(/\b(\d+)\s*(?:-|to|or)\s*(\d+)\s*(?:dte|days?)\b/);
|
|
480
489
|
if (explicitDays) return `${explicitDays[1]}-${explicitDays[2]} days`;
|
|
490
|
+
// An explicit user week range ("1-2 weeks out") must outrank the
|
|
491
|
+
// catalyst-derived event_week inference below; a same-day earnings
|
|
492
|
+
// mention otherwise collapses the requested horizon to 0-7 days
|
|
493
|
+
// (historical "1-2 weeks DTE preservation" loss class).
|
|
494
|
+
const explicitWeeks = lower.match(
|
|
495
|
+
/\b(\d+|one|two|three|four)\s*(?:[-–]|to|or)\s*(\d+|one|two|three|four)\s*weeks?\b/,
|
|
496
|
+
);
|
|
497
|
+
if (explicitWeeks) return `${explicitWeeks[1]}-${explicitWeeks[2]} weeks`;
|
|
481
498
|
if (/\bmonth\b/.test(lower)) return "month";
|
|
482
499
|
if (
|
|
483
500
|
/\bearnings?\b.*\b(?:today|tonight|this\s+week)\b|\b(?:today|tonight|this\s+week)\b.*\bearnings?\b/.test(
|
|
@@ -35,7 +35,7 @@ export const TOOL_BUNDLE_TOOLS: Record<ToolBundleName, readonly string[]> = {
|
|
|
35
35
|
"search_web",
|
|
36
36
|
],
|
|
37
37
|
options: ["get_option_chain", "get_stock_quote", "search_ticker", "search_web"],
|
|
38
|
-
macro: ["get_economic_data", "get_fear_greed", "search_web"],
|
|
38
|
+
macro: ["get_economic_data", "get_event_probabilities", "get_fear_greed", "search_web"],
|
|
39
39
|
sentiment: [
|
|
40
40
|
"get_reddit_sentiment",
|
|
41
41
|
"get_twitter_sentiment",
|
|
@@ -2,6 +2,8 @@ import type { Model } from "@earendil-works/pi-ai";
|
|
|
2
2
|
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
3
3
|
import type { RouterLlmClient } from "./router-types.js";
|
|
4
4
|
|
|
5
|
+
type CompleteSimpleResponse = Awaited<ReturnType<typeof completeSimple>>;
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Build a router LLM client backed by pi-ai's `completeSimple`. The client
|
|
7
9
|
* is intentionally thin: prompt in, raw text out. Schema validation and
|
|
@@ -15,25 +17,30 @@ export function createPiAiRouterClient(
|
|
|
15
17
|
): RouterLlmClient {
|
|
16
18
|
return {
|
|
17
19
|
async complete(prompt: string): Promise<string> {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
const request = {
|
|
21
|
+
messages: [
|
|
22
|
+
{
|
|
23
|
+
role: "user" as const,
|
|
24
|
+
content: prompt,
|
|
25
|
+
timestamp: Date.now(),
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
// Explicitly no tools — spec requirement.
|
|
29
|
+
tools: [],
|
|
30
|
+
};
|
|
31
|
+
const options = {
|
|
32
|
+
temperature: 0,
|
|
33
|
+
maxTokens: 2000,
|
|
34
|
+
reasoning: "minimal" as const,
|
|
35
|
+
};
|
|
36
|
+
let response: CompleteSimpleResponse;
|
|
37
|
+
try {
|
|
38
|
+
response = await completeSimple(model, request, options);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (!isUnsupportedTemperatureError(error)) throw error;
|
|
41
|
+
const { temperature: _temperature, ...retryOptions } = options;
|
|
42
|
+
response = await completeSimple(model, request, retryOptions);
|
|
43
|
+
}
|
|
37
44
|
|
|
38
45
|
if (response.stopReason === "error" || response.stopReason === "aborted") {
|
|
39
46
|
throw new Error(`router LLM call failed: ${response.errorMessage ?? response.stopReason}`);
|
|
@@ -50,3 +57,8 @@ export function createPiAiRouterClient(
|
|
|
50
57
|
},
|
|
51
58
|
};
|
|
52
59
|
}
|
|
60
|
+
|
|
61
|
+
function isUnsupportedTemperatureError(error: unknown): boolean {
|
|
62
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
63
|
+
return /unsupported parameter/i.test(message) && /temperature/i.test(message);
|
|
64
|
+
}
|