feat(stock-widget): use names and ticker

This commit is contained in:
ItzCrazyKns
2025-11-29 12:46:01 +05:30
parent f83bd06e89
commit e2a371936b

View File

@@ -8,16 +8,16 @@ const yf = new YahooFinance({
const schema = z.object({ const schema = z.object({
type: z.literal('stock'), type: z.literal('stock'),
ticker: z name: z
.string() .string()
.describe( .describe(
"The stock ticker symbol in uppercase (e.g., 'AAPL' for Apple Inc., 'TSLA' for Tesla, 'GOOGL' for Google). Use the primary exchange ticker.", "The stock name for example Nvidia, Google, Apple, Microsoft etc. You can also return ticker if you're aware of it otherwise just use the name.",
), ),
comparisonTickers: z comparisonNames: z
.array(z.string()) .array(z.string())
.max(3) .max(3)
.describe( .describe(
"Optional array of up to 3 ticker symbols to compare against the base ticker (e.g., ['MSFT', 'GOOGL', 'META']). Charts will show percentage change comparison.", "Optional array of up to 3 stock names to compare against the base name (e.g., ['Microsoft', 'GOOGL', 'Meta']). Charts will show percentage change comparison.",
), ),
}); });
@@ -50,31 +50,38 @@ You can set skipSearch to true if the stock widget can fully answer the user's q
**Example calls:** **Example calls:**
{ {
"type": "stock", "type": "stock",
"ticker": "AAPL" "name": "AAPL"
} }
{ {
"type": "stock", "type": "stock",
"ticker": "TSLA", "name": "TSLA",
"comparisonTickers": ["RIVN", "LCID"] "comparisonNames": ["RIVN", "LCID"]
} }
{ {
"type": "stock", "type": "stock",
"ticker": "GOOGL", "name": "Google",
"comparisonTickers": ["MSFT", "META", "AMZN"] "comparisonNames": ["Microsoft", "Meta", "Amazon"]
} }
**Important:** **Important:**
- Use the correct ticker symbol (uppercase preferred: AAPL not aapl) - You can use both tickers and names (prefer name when you're not aware of the ticker).
- For companies with multiple share classes, use the most common one (e.g., GOOGL for Google Class A shares) - For companies with multiple share classes, use the most common one.
- The widget works for stocks listed on major exchanges (NYSE, NASDAQ, etc.) - The widget works for stocks listed on major exchanges (NYSE, NASDAQ, etc.)
- Returns comprehensive data; the UI will display relevant metrics based on availability - Returns comprehensive data; the UI will display relevant metrics based on availability
- Market data may be delayed by 15-20 minutes for free data sources during trading hours`, - Market data may be delayed by 15-20 minutes for free data sources during trading hours`,
schema: schema, schema: schema,
execute: async (params, _) => { execute: async (params, _) => {
try { try {
const ticker = params.ticker.toUpperCase(); const name = params.name;
const findings = await yf.search(name);
if (findings.quotes.length === 0)
throw new Error(`Failed to find quote for name/symbol: ${name}`);
const ticker = findings.quotes[0].symbol as string;
const quote: any = await yf.quote(ticker); const quote: any = await yf.quote(ticker);
@@ -143,11 +150,16 @@ You can set skipSearch to true if the stock widget can fully answer the user's q
} }
let comparisonData: any = null; let comparisonData: any = null;
if (params.comparisonTickers.length > 0) { if (params.comparisonNames.length > 0) {
const comparisonPromises = params.comparisonTickers const comparisonPromises = params.comparisonNames
.slice(0, 3) .slice(0, 3)
.map(async (compTicker) => { .map(async (compName) => {
try { try {
const compFindings = await yf.search(compName);
if (compFindings.quotes.length === 0) return null;
const compTicker = compFindings.quotes[0].symbol as string;
const compQuote = await yf.quote(compTicker); const compQuote = await yf.quote(compTicker);
const compCharts = await Promise.all([ const compCharts = await Promise.all([
yf yf
@@ -204,7 +216,7 @@ You can set skipSearch to true if the stock widget can fully answer the user's q
}; };
} catch (error) { } catch (error) {
console.error( console.error(
`Failed to fetch comparison ticker ${compTicker}:`, `Failed to fetch comparison ticker ${compName}:`,
error, error,
); );
return null; return null;
@@ -286,123 +298,125 @@ You can set skipSearch to true if the stock widget can fully answer the user's q
chartData: { chartData: {
'1D': chart1D '1D': chart1D
? { ? {
timestamps: chart1D.quotes.map((q: any) => q.date.getTime()), timestamps: chart1D.quotes.map((q: any) => q.date.getTime()),
prices: chart1D.quotes.map((q: any) => q.close), prices: chart1D.quotes.map((q: any) => q.close),
} }
: null, : null,
'5D': chart5D '5D': chart5D
? { ? {
timestamps: chart5D.quotes.map((q: any) => q.date.getTime()), timestamps: chart5D.quotes.map((q: any) => q.date.getTime()),
prices: chart5D.quotes.map((q: any) => q.close), prices: chart5D.quotes.map((q: any) => q.close),
} }
: null, : null,
'1M': chart1M '1M': chart1M
? { ? {
timestamps: chart1M.quotes.map((q: any) => q.date.getTime()), timestamps: chart1M.quotes.map((q: any) => q.date.getTime()),
prices: chart1M.quotes.map((q: any) => q.close), prices: chart1M.quotes.map((q: any) => q.close),
} }
: null, : null,
'3M': chart3M '3M': chart3M
? { ? {
timestamps: chart3M.quotes.map((q: any) => q.date.getTime()), timestamps: chart3M.quotes.map((q: any) => q.date.getTime()),
prices: chart3M.quotes.map((q: any) => q.close), prices: chart3M.quotes.map((q: any) => q.close),
} }
: null, : null,
'6M': chart6M '6M': chart6M
? { ? {
timestamps: chart6M.quotes.map((q: any) => q.date.getTime()), timestamps: chart6M.quotes.map((q: any) => q.date.getTime()),
prices: chart6M.quotes.map((q: any) => q.close), prices: chart6M.quotes.map((q: any) => q.close),
} }
: null, : null,
'1Y': chart1Y '1Y': chart1Y
? { ? {
timestamps: chart1Y.quotes.map((q: any) => q.date.getTime()), timestamps: chart1Y.quotes.map((q: any) => q.date.getTime()),
prices: chart1Y.quotes.map((q: any) => q.close), prices: chart1Y.quotes.map((q: any) => q.close),
} }
: null, : null,
MAX: chartMAX MAX: chartMAX
? { ? {
timestamps: chartMAX.quotes.map((q: any) => q.date.getTime()), timestamps: chartMAX.quotes.map((q: any) => q.date.getTime()),
prices: chartMAX.quotes.map((q: any) => q.close), prices: chartMAX.quotes.map((q: any) => q.close),
} }
: null, : null,
}, },
comparisonData: comparisonData comparisonData: comparisonData
? comparisonData.map((comp: any) => ({ ? comparisonData.map((comp: any) => ({
ticker: comp.ticker, ticker: comp.ticker,
name: comp.name, name: comp.name,
chartData: { chartData: {
'1D': comp.charts[0] '1D': comp.charts[0]
? { ? {
timestamps: comp.charts[0].quotes.map((q: any) => timestamps: comp.charts[0].quotes.map((q: any) =>
q.date.getTime(), q.date.getTime(),
), ),
prices: comp.charts[0].quotes.map((q: any) => q.close), prices: comp.charts[0].quotes.map((q: any) => q.close),
} }
: null, : null,
'5D': comp.charts[1] '5D': comp.charts[1]
? { ? {
timestamps: comp.charts[1].quotes.map((q: any) => timestamps: comp.charts[1].quotes.map((q: any) =>
q.date.getTime(), q.date.getTime(),
), ),
prices: comp.charts[1].quotes.map((q: any) => q.close), prices: comp.charts[1].quotes.map((q: any) => q.close),
} }
: null, : null,
'1M': comp.charts[2] '1M': comp.charts[2]
? { ? {
timestamps: comp.charts[2].quotes.map((q: any) => timestamps: comp.charts[2].quotes.map((q: any) =>
q.date.getTime(), q.date.getTime(),
), ),
prices: comp.charts[2].quotes.map((q: any) => q.close), prices: comp.charts[2].quotes.map((q: any) => q.close),
} }
: null, : null,
'3M': comp.charts[3] '3M': comp.charts[3]
? { ? {
timestamps: comp.charts[3].quotes.map((q: any) => timestamps: comp.charts[3].quotes.map((q: any) =>
q.date.getTime(), q.date.getTime(),
), ),
prices: comp.charts[3].quotes.map((q: any) => q.close), prices: comp.charts[3].quotes.map((q: any) => q.close),
} }
: null, : null,
'6M': comp.charts[4] '6M': comp.charts[4]
? { ? {
timestamps: comp.charts[4].quotes.map((q: any) => timestamps: comp.charts[4].quotes.map((q: any) =>
q.date.getTime(), q.date.getTime(),
), ),
prices: comp.charts[4].quotes.map((q: any) => q.close), prices: comp.charts[4].quotes.map((q: any) => q.close),
} }
: null, : null,
'1Y': comp.charts[5] '1Y': comp.charts[5]
? { ? {
timestamps: comp.charts[5].quotes.map((q: any) => timestamps: comp.charts[5].quotes.map((q: any) =>
q.date.getTime(), q.date.getTime(),
), ),
prices: comp.charts[5].quotes.map((q: any) => q.close), prices: comp.charts[5].quotes.map((q: any) => q.close),
} }
: null, : null,
MAX: comp.charts[6] MAX: comp.charts[6]
? { ? {
timestamps: comp.charts[6].quotes.map((q: any) => timestamps: comp.charts[6].quotes.map((q: any) =>
q.date.getTime(), q.date.getTime(),
), ),
prices: comp.charts[6].quotes.map((q: any) => q.close), prices: comp.charts[6].quotes.map((q: any) => q.close),
} }
: null, : null,
}, },
})) }))
: null, : null,
}; };
return { return {
type: 'stock', type: 'stock',
llmContext: `Current price of ${stockData.shortName} (${stockData.symbol}) is ${stockData.regularMarketPrice} ${stockData.currency}. Other details: ${JSON.stringify({ llmContext: `Current price of ${stockData.shortName} (${stockData.symbol}) is ${stockData.regularMarketPrice} ${stockData.currency}. Other details: ${JSON.stringify(
marketState: stockData.marketState, {
regularMarketChange: stockData.regularMarketChange, marketState: stockData.marketState,
regularMarketChangePercent: stockData.regularMarketChangePercent, regularMarketChange: stockData.regularMarketChange,
marketCap: stockData.marketCap, regularMarketChangePercent: stockData.regularMarketChangePercent,
peRatio: stockData.trailingPE, marketCap: stockData.marketCap,
dividendYield: stockData.dividendYield, peRatio: stockData.trailingPE,
})}`, dividendYield: stockData.dividendYield,
},
)}`,
data: stockData, data: stockData,
}; };
} catch (error: any) { } catch (error: any) {
@@ -411,7 +425,7 @@ You can set skipSearch to true if the stock widget can fully answer the user's q
llmContext: 'Failed to fetch stock data.', llmContext: 'Failed to fetch stock data.',
data: { data: {
error: `Error fetching stock data: ${error.message || error}`, error: `Error fetching stock data: ${error.message || error}`,
ticker: params.ticker, ticker: params.name,
}, },
}; };
} }