import { getLogger } from '@stock-bot/logger'; import { sleep } from '@stock-bot/di'; const logger = getLogger('symbol-search-util'); export interface SearchFunction { (query: string): Promise; } export class SymbolSearchUtil { private threshold: number; private searchFunction: SearchFunction; private maxDepth: number; private delay: number; constructor( searchFunction: SearchFunction, threshold: number = 50, maxDepth: number = 4, delay: number = 100 ) { this.searchFunction = searchFunction; this.threshold = threshold; this.maxDepth = maxDepth; this.delay = delay; } async searchAllSymbols(): Promise { logger.info('Starting comprehensive symbol search...'); const allSymbols: string[] = []; // Start with single letters A-Z for (let i = 0; i < 26; i++) { const singleLetter = String.fromCharCode(65 + i); try { const symbols = await this.searchRecursive(singleLetter, 1); allSymbols.push(...symbols); // Add delay between top-level searches if (this.delay > 0) { await sleep(this.delay); } } catch (error) { logger.error(`Failed to search for "${singleLetter}":`, error); // Continue with next letter } } // Remove duplicates const uniqueSymbols = [...new Set(allSymbols)]; logger.info(`Symbol search completed. Found ${uniqueSymbols.length} unique symbols`); return uniqueSymbols; } private async searchRecursive(prefix: string, depth: number): Promise { try { const symbols = await this.searchFunction(prefix); logger.debug(`Query "${prefix}" returned ${symbols.length} symbols`); // If we're at max depth or results are under threshold, return the symbols if (depth >= this.maxDepth || symbols.length < this.threshold) { logger.info(`Added ${symbols.length} symbols from query: ${prefix}`); return symbols; } // If we have too many results, go deeper logger.info( `Query "${prefix}" returned ${symbols.length} results (>= ${this.threshold}), going deeper...` ); const allSymbols: string[] = []; for (let i = 0; i < 26; i++) { const nextQuery = prefix + String.fromCharCode(65 + i); try { const deeperSymbols = await this.searchRecursive(nextQuery, depth + 1); allSymbols.push(...deeperSymbols); // Add delay between recursive calls if (this.delay > 0 && depth < 3) { // Only delay for first few levels await sleep(this.delay); } } catch (error) { logger.error(`Failed recursive search for "${nextQuery}":`, error); // Continue with next combination } } return allSymbols; } catch (error) { logger.error(`Error in recursive search for "${prefix}":`, error); return []; } } // Static method for one-off searches static async search( searchFunction: SearchFunction, threshold: number = 50, maxDepth: number = 4, delay: number = 100 ): Promise { const util = new SymbolSearchUtil(searchFunction, threshold, maxDepth, delay); return util.searchAllSymbols(); } }