feat: Phase 2 NASDAQ adapter for days-to-cover + 24mo history
- Add nasdaq SourceKind + nasdaqShortinterest KindHandler (kv_cache with nasdaq: prefix) - Create NasdaqAdapter: calls api.nasdaq.com/quote/SYM/short-interest, parses settlementDate/sharesShort/daysToCover/avgVolume, returns 24mo history; handles non-NASDAq gracefully - Register NasdaqAdapter in server index.ts adapters map - Update shortInterest procedure: reads both yfinance + nasdaq caches in parallel, reconciles (NASDAQ for daysToCover/sharesShort, Yahoo for %float/shortRatio and NYSE fallback) - Update ShortInterestPanel: display daysToCover, settlement date, source badges, 6-month history table, updated disclaimer - Update trpc.ts client type for merged response shape
This commit is contained in:
@@ -1900,27 +1900,48 @@ const institutionalRouter = router({
|
||||
return { symbol, ratings: result.ratings, consensus: result.consensus };
|
||||
}),
|
||||
|
||||
/** Short interest (shares short, % float, short ratio) from yahoo-finance2 defaultKeyStatistics. */
|
||||
shortInterest: publicProcedure
|
||||
.input(z.object({ symbol: z.string().min(1) }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const symbol = input.symbol.toUpperCase();
|
||||
const k = `yfinance:shortinterest:${symbol}`;
|
||||
const entry = await ctx.cache.get<unknown>(k);
|
||||
const value = entry.value as Record<string, unknown> | null;
|
||||
return {
|
||||
symbol,
|
||||
sharesShort: value ? (value.sharesShort as number | null) : null,
|
||||
sharesShortPriorMonth: value ? (value.sharesShortPriorMonth as number | null) : null,
|
||||
sharesPercentSharesOut: value ? (value.sharesPercentSharesOut as number | null) : null,
|
||||
shortRatio: value ? (value.shortRatio as number | null) : null,
|
||||
shortPercentOfFloat: value ? (value.shortPercentOfFloat as number | null) : null,
|
||||
dateShortInterest: value ? (value.dateShortInterest as string | null) : null,
|
||||
floatShares: value ? (value.floatShares as number | null) : null,
|
||||
sharesOutstanding: value ? (value.sharesOutstanding as number | null) : null,
|
||||
stale: entry.isStale,
|
||||
provenance: entry.provenance,
|
||||
};
|
||||
/** Short interest reconciled across Yahoo Finance + NASDAQ API.
|
||||
* Yahoo covers all US-listed tickers (basic: shares short, % float, short ratio).
|
||||
* NASDAQ covers NASDAQ-listed only but adds days-to-cover + 24mo history.
|
||||
* Reconciliation: prefer NASDAQ's sharesShort/daysToCover when available (richer),
|
||||
* fall back to Yahoo for NYSE/other tickers. */
|
||||
shortInterest: publicProcedure
|
||||
.input(z.object({ symbol: z.string().min(1) }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const symbol = input.symbol.toUpperCase();
|
||||
const [yfEntry, ndqEntry] = await Promise.all([
|
||||
ctx.cache.get<Record<string, unknown>>(`yfinance:shortinterest:${symbol}`),
|
||||
ctx.cache.get<Record<string, unknown>>(`nasdaq:nasdaqShortinterest:${symbol}`),
|
||||
]);
|
||||
const yf = yfEntry.value;
|
||||
const ndq = ndqEntry.value;
|
||||
|
||||
// Prefer NASDAQ's sharesShort + daysToCover when available (NASDAQ-listed);
|
||||
// otherwise fall back to Yahoo (covers NYSE/other tickers).
|
||||
const sharesShort = ndq?.sharesShort ?? yf?.sharesShort ?? null;
|
||||
const daysToCover = ndq?.daysToCover ?? null;
|
||||
const settlementDate = ndq?.settlementDate ?? null;
|
||||
const history = ndq?.history ?? null;
|
||||
|
||||
return {
|
||||
symbol,
|
||||
sharesShort,
|
||||
sharesShortPriorMonth: yf?.sharesShortPriorMonth ?? null,
|
||||
sharesPercentSharesOut: yf?.sharesPercentSharesOut ?? null,
|
||||
shortRatio: yf?.shortRatio ?? null,
|
||||
shortPercentOfFloat: yf?.shortPercentOfFloat ?? null,
|
||||
dateShortInterest: yf?.dateShortInterest ?? null,
|
||||
floatShares: yf?.floatShares ?? null,
|
||||
sharesOutstanding: yf?.sharesOutstanding ?? null,
|
||||
daysToCover,
|
||||
settlementDate,
|
||||
history,
|
||||
sources: {
|
||||
yfinance: { available: !!yf, stale: yfEntry.isStale },
|
||||
nasdaq: { available: !!ndq, stale: ndqEntry.isStale },
|
||||
},
|
||||
stale: yfEntry.isStale && ndqEntry.isStale,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user