phase-6: typescript introduction

This commit is contained in:
Kazuma
2026-06-04 22:16:48 -04:00
parent 16bd95aa85
commit 69d13c3dbe
69 changed files with 2323 additions and 1036 deletions
+72
View File
@@ -0,0 +1,72 @@
import { YahooClient } from '../market/YahooClient.js';
import type { Logger } from '../types.js';
interface Story {
title: string;
publisher: string;
link: string;
relatedTickers: string[];
}
interface CatalystResult {
tickers: string[];
stories: Story[];
}
const NEWS_QUERIES = ['stock market today', 'earnings report', 'market news'];
const MAX_STORIES = 15;
const TICKER_REGEX = /^[A-Z]{1,6}$/;
export class CatalystAnalyst {
private client: YahooClient;
private logger: Pick<Logger, 'write'>;
constructor({ logger }: { logger?: Pick<Logger, 'write'> } = {}) {
this.client = new YahooClient();
this.logger = logger ?? { write: (msg: string) => process.stdout.write(msg) };
}
async run(): Promise<CatalystResult> {
this.logger.write('🔍 Fetching market news...');
const stories = await this._fetchNews();
const tickers = this._extractTickers(stories);
this.logger.write(` ${stories.length} stories, ${tickers.length} tickers\n`);
return { tickers, stories };
}
private async _fetchNews(): Promise<Story[]> {
const seen = new Map<string, Story>();
for (const query of NEWS_QUERIES) {
try {
const { news = [] } = await (this.client as any).yf.search(query, {
newsCount: 8,
quotesCount: 0,
});
for (const s of news as any[]) {
if (!seen.has(s.title)) {
seen.set(s.title, {
title: s.title,
publisher: s.publisher,
link: s.link,
relatedTickers: s.relatedTickers ?? [],
});
}
}
} catch {
/* skip failed query */
}
}
return [...seen.values()].slice(0, MAX_STORIES);
}
private _extractTickers(stories: Story[]): string[] {
const tickers = new Set<string>();
for (const { relatedTickers } of stories) {
for (const t of relatedTickers) {
const clean = t.split(':')[0].toUpperCase();
if (TICKER_REGEX.test(clean)) tickers.add(clean);
}
}
return [...tickers];
}
}