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:
@@ -188,7 +188,7 @@ export class EdgarAdapter implements SourceFetch {
|
||||
} else if (typeof recentObj === 'object') {
|
||||
// Parallel arrays — convert to array of objects.
|
||||
const keys = Object.keys(recentObj);
|
||||
const len = (recentObj[keys[0]] as unknown[])?.length ?? 0;
|
||||
const len = ((recentObj as Record<string, unknown>)[keys[0]] as unknown[])?.length ?? 0;
|
||||
recentFilings = [];
|
||||
for (let i = 0; i < len; i++) {
|
||||
const row: Record<string, unknown> = {};
|
||||
@@ -205,7 +205,7 @@ export class EdgarAdapter implements SourceFetch {
|
||||
let filings = recentFilings;
|
||||
if (opts?.formTypes && opts.formTypes.length > 0) {
|
||||
const allowed = new Set(opts.formTypes.map((f) => f.toUpperCase()));
|
||||
filings = filings.filter((f) => allowed.has((f.form ?? '').toUpperCase()));
|
||||
filings = filings.filter((f) => allowed.has(String(f.form ?? '').toUpperCase()));
|
||||
}
|
||||
|
||||
// Filter by date range.
|
||||
@@ -213,7 +213,7 @@ export class EdgarAdapter implements SourceFetch {
|
||||
const from = opts.dateRange.from ? new Date(opts.dateRange.from).getTime() : null;
|
||||
const to = opts.dateRange.to ? new Date(opts.dateRange.to).getTime() : null;
|
||||
filings = filings.filter((f) => {
|
||||
const ts = new Date(f.reportDate ?? f.filingDate ?? f.dateReporter ?? '').getTime();
|
||||
const ts = new Date(String(f.reportDate ?? f.filingDate ?? f.dateReporter ?? '')).getTime();
|
||||
if (Number.isNaN(ts)) return false;
|
||||
if (from !== null && ts < from) return false;
|
||||
if (to !== null && ts > to) return false;
|
||||
@@ -352,6 +352,7 @@ export class EdgarAdapter implements SourceFetch {
|
||||
async form13f_holdings(
|
||||
cik: string,
|
||||
accession: string,
|
||||
opts?: { limit?: number; offset?: number },
|
||||
): Promise<FetchResult> {
|
||||
const filerCik = accession.split('-')[0];
|
||||
const padded = padCik(filerCik);
|
||||
@@ -390,7 +391,7 @@ export class EdgarAdapter implements SourceFetch {
|
||||
|
||||
// Try each candidate until we find one with holdings; fall back to any non-HTML doc
|
||||
let docText = '';
|
||||
let parsed: Array<{ cusip: string; issuerName: string; value: number; sshPrnamt: number }> = [];
|
||||
let parsed: Array<{ cusip: string; issuerName: string; value: number; sshPrnamt: number; putCall: string }> = [];
|
||||
for (const cand of xmlCandidates) {
|
||||
const docUrl = `https://www.sec.gov/Archives/edgar/data/${padded}/${accessionNoDashes}/${cand.name}`;
|
||||
docText = await this.edgarXmlFetch(docUrl);
|
||||
@@ -410,10 +411,14 @@ export class EdgarAdapter implements SourceFetch {
|
||||
}
|
||||
}
|
||||
|
||||
const holdings = parsed;
|
||||
const allHoldings = parsed;
|
||||
const total = allHoldings.length;
|
||||
const offset = Math.max(0, opts?.offset ?? 0);
|
||||
const limit = opts?.limit;
|
||||
const holdings = limit != null ? allHoldings.slice(offset, offset + limit) : allHoldings;
|
||||
|
||||
return {
|
||||
value: { holdings, accession: `${padded}/${accessionNoDashes}` },
|
||||
value: { holdings, total, accession: `${padded}/${accessionNoDashes}` },
|
||||
ttlClass: 'daily_permanent' as const,
|
||||
provenance: {
|
||||
fetchedAt: new Date().toISOString(),
|
||||
@@ -581,10 +586,11 @@ function parse13fHoldings(text: string): Array<{
|
||||
issuerName: string;
|
||||
value: number;
|
||||
sshPrnamt: number;
|
||||
putCall: string;
|
||||
}> {
|
||||
const holdings: Array<{ cusip: string; issuerName: string; value: number; sshPrnamt: number }> = [];
|
||||
const holdings: Array<{ cusip: string; issuerName: string; value: number; sshPrnamt: number; putCall: string }> = [];
|
||||
|
||||
// Try 13F XML format: <infoTable> (possibly namespaced) with <nameOfIssuer>, <cusip>, <value>, <sshPrnamt>
|
||||
// Try 13F XML format: <infoTable> (possibly namespaced) with <nameOfIssuer>, <cusip>, <value>, <sshPrnamt>, <putCall>
|
||||
// SEC 13F XML uses namespaces like <ns1:infoTable><ns1:nameOfIssuer>...
|
||||
const infoTableMatches = text.match(/<(?:\w+:)?infoTable[^>]*>([\s\S]*?)<\/(?:\w+:)?infoTable>/gi);
|
||||
if (infoTableMatches && infoTableMatches.length > 0) {
|
||||
@@ -593,6 +599,7 @@ function parse13fHoldings(text: string): Array<{
|
||||
const cusipMatch = block.match(/<(?:\w+:)?cusip>\s*([\s\S]*?)\s*<\/(?:\w+:)?cusip>/i);
|
||||
const valueMatch = block.match(/<(?:\w+:)?value>\s*([\s\S]*?)\s*<\/(?:\w+:)?value>/i);
|
||||
const sharesMatch = block.match(/<(?:\w+:)?sshPrnamt>\s*([\s\S]*?)\s*<\/(?:\w+:)?sshPrnamt>/i);
|
||||
const putCallMatch = block.match(/<(?:\w+:)?putCall>\s*([\s\S]*?)\s*<\/(?:\w+:)?putCall>/i);
|
||||
|
||||
if (!cusipMatch) continue;
|
||||
|
||||
@@ -600,8 +607,9 @@ function parse13fHoldings(text: string): Array<{
|
||||
const cusip = cusipMatch ? cusipMatch[1].replace(/<[^>]+>/g, '').trim() : '';
|
||||
const value = valueMatch ? parseFloat(valueMatch[1].replace(/<[^>]+>/g, '').replace(/,/g, '')) || 0 : 0;
|
||||
const sshPrnamt = sharesMatch ? parseFloat(sharesMatch[1].replace(/<[^>]+>/g, '').replace(/,/g, '')) || 0 : 0;
|
||||
const putCall = putCallMatch ? putCallMatch[1].replace(/<[^>]+>/g, '').trim() : '';
|
||||
|
||||
holdings.push({ cusip, issuerName, value, sshPrnamt });
|
||||
holdings.push({ cusip, issuerName, value, sshPrnamt, putCall });
|
||||
}
|
||||
|
||||
if (holdings.length > 0) return holdings;
|
||||
@@ -638,7 +646,7 @@ function parse13fHoldings(text: string): Array<{
|
||||
const sharesMatch = cells[3].match(/([\d,.]+)/);
|
||||
const sshPrnamt = sharesMatch ? parseFloat(sharesMatch[1].replace(/,/g, '')) : 0;
|
||||
|
||||
holdings.push({ cusip: cusipMatch[1], issuerName: issuerRaw, value, sshPrnamt });
|
||||
holdings.push({ cusip: cusipMatch[1], issuerName: issuerRaw, value, sshPrnamt, putCall: '' });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -657,7 +665,7 @@ function parse13fHoldings(text: string): Array<{
|
||||
if (cusipLine && !/Class|CUSIP|Name|Issuer/i.test(line)) {
|
||||
// Save previous holding if we have one.
|
||||
if (currentCusip) {
|
||||
holdings.push({ cusip: currentCusip, issuerName: currentIssuer, value: currentValue, sshPrnamt: currentShares });
|
||||
holdings.push({ cusip: currentCusip, issuerName: currentIssuer, value: currentValue, sshPrnamt: currentShares, putCall: '' });
|
||||
}
|
||||
currentCusip = cusipLine[1];
|
||||
currentIssuer = '';
|
||||
@@ -681,7 +689,7 @@ function parse13fHoldings(text: string): Array<{
|
||||
}
|
||||
// Push last holding.
|
||||
if (currentCusip) {
|
||||
holdings.push({ cusip: currentCusip, issuerName: currentIssuer.trim(), value: currentValue, sshPrnamt: currentShares });
|
||||
holdings.push({ cusip: currentCusip, issuerName: currentIssuer.trim(), value: currentValue, sshPrnamt: currentShares, putCall: '' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user