fix: backfill symbol_demand for sidebar-added symbols + analyst ratings schema fix
- Add await ctx.cache.subscribe() to addSymbol mutation so symbols added via the sidebar get registered in symbol_demand and yfinance jobs are queued immediately - Backfill PEP, WYNN, STZ, CELH into symbol_demand + adapter_queue - Upgrade yahoo-finance2 3.15.3 -> 3.15.4 and pass validateResult:false to quoteSummary() to handle Yahoo schema drift - Add error detail logging for analyst ratings schema failures - Update .gitignore with common ignores
This commit is contained in:
@@ -100,13 +100,11 @@ export class OptionsAdapter implements SourceFetch {
|
||||
const yf = await this.yf();
|
||||
|
||||
if (kind === 'expiry_dates') {
|
||||
// yahoo-finance2 v3: options() returns expirationDates as Date objects
|
||||
const result = await yf.options(id);
|
||||
const dates = (result.expirationDates ?? [])
|
||||
.map((d: Date) => d.toISOString().slice(0, 10))
|
||||
.sort() as OptionExpiryDate[];
|
||||
// Use optionsExpiryDates() which returns string[] directly.
|
||||
const dates = await yf.optionsExpiryDates(id);
|
||||
const sorted = [...dates].sort() as OptionExpiryDate[];
|
||||
return {
|
||||
value: dates,
|
||||
value: sorted,
|
||||
ttlClass: 'intraday',
|
||||
provenance: { fetchedAt, sourceKind: 'yfinance', rawSourceId: `options:expiry:${id}` },
|
||||
};
|
||||
@@ -114,14 +112,26 @@ export class OptionsAdapter implements SourceFetch {
|
||||
|
||||
if (kind === 'chain') {
|
||||
const [symbol, expiry] = id.split(':');
|
||||
const rawChain = await yf.options(symbol, { date: expiry });
|
||||
// v3: options is indexed by number, extract calls/puts from first entry
|
||||
const optionsObj = rawChain.options;
|
||||
const firstKey = Object.keys(optionsObj)[0];
|
||||
const expiryData = firstKey ? optionsObj[firstKey] : null;
|
||||
const rawResult = await yf.options(symbol, expiry);
|
||||
// Handle multiple response shapes:
|
||||
// v3: { options: { '1234567890': { calls: [...], puts: [...] } } }
|
||||
// flat: { calls: [...], puts: [...] }
|
||||
let expiryData: Record<string, unknown> | null = null;
|
||||
if (rawResult.options && typeof rawResult.options === 'object' && !Array.isArray(rawResult.options)) {
|
||||
const optObj = rawResult.options as Record<string, unknown>;
|
||||
const dateKeys = Object.keys(optObj).filter((k) => /^\d+$/.test(k) || /\d{4}-\d{2}-\d{2}/.test(k));
|
||||
if (dateKeys.length > 0) {
|
||||
expiryData = optObj[dateKeys[0]] as Record<string, unknown>;
|
||||
} else {
|
||||
expiryData = optObj;
|
||||
}
|
||||
} else if (Array.isArray(rawResult.calls) || Array.isArray(rawResult.puts)) {
|
||||
// Flat format: { calls: [...], puts: [...] }
|
||||
expiryData = rawResult as Record<string, unknown>;
|
||||
}
|
||||
const allRows = [
|
||||
...(expiryData?.calls ?? []).map((c: Record<string, unknown>) => ({ ...c, right: 'call' as const })),
|
||||
...(expiryData?.puts ?? []).map((p: Record<string, unknown>) => ({ ...p, right: 'put' as const })),
|
||||
...((expiryData?.calls as Array<Record<string, unknown>> | undefined) ?? []).map((c) => ({ ...c, right: 'call' as const })),
|
||||
...((expiryData?.puts as Array<Record<string, unknown>> | undefined) ?? []).map((p) => ({ ...p, right: 'put' as const })),
|
||||
];
|
||||
const rows = parseOptionChainRows(symbol, allRows);
|
||||
return {
|
||||
@@ -136,13 +146,23 @@ export class OptionsAdapter implements SourceFetch {
|
||||
const symbol = parts[0];
|
||||
const expiry = parts[1];
|
||||
const strike = parseFloat(parts[2] ?? '0');
|
||||
const rawChain = await yf.options(symbol, { date: expiry });
|
||||
const optionsObj = rawChain.options;
|
||||
const firstKey = Object.keys(optionsObj)[0];
|
||||
const expiryData = firstKey ? optionsObj[firstKey] : null;
|
||||
const rawResult = await yf.options(symbol, expiry);
|
||||
// Handle multiple response shapes (same logic as chain kind).
|
||||
let expiryData: Record<string, unknown> | null = null;
|
||||
if (rawResult.options && typeof rawResult.options === 'object' && !Array.isArray(rawResult.options)) {
|
||||
const optObj = rawResult.options as Record<string, unknown>;
|
||||
const dateKeys = Object.keys(optObj).filter((k) => /^\d+$/.test(k) || /\d{4}-\d{2}-\d{2}/.test(k));
|
||||
if (dateKeys.length > 0) {
|
||||
expiryData = optObj[dateKeys[0]] as Record<string, unknown>;
|
||||
} else {
|
||||
expiryData = optObj;
|
||||
}
|
||||
} else if (Array.isArray(rawResult.calls) || Array.isArray(rawResult.puts)) {
|
||||
expiryData = rawResult as Record<string, unknown>;
|
||||
}
|
||||
const allRows = [
|
||||
...(expiryData?.calls ?? []).map((c: Record<string, unknown>) => ({ ...c, right: 'call' as const })),
|
||||
...(expiryData?.puts ?? []).map((p: Record<string, unknown>) => ({ ...p, right: 'put' as const })),
|
||||
...((expiryData?.calls as Array<Record<string, unknown>> | undefined) ?? []).map((c) => ({ ...c, right: 'call' as const })),
|
||||
...((expiryData?.puts as Array<Record<string, unknown>> | undefined) ?? []).map((p) => ({ ...p, right: 'put' as const })),
|
||||
];
|
||||
const rows = parseOptionChainRows(symbol, allRows);
|
||||
const target = strike > 0
|
||||
|
||||
Reference in New Issue
Block a user