215 lines
No EOL
6.1 KiB
TypeScript
215 lines
No EOL
6.1 KiB
TypeScript
/**
|
|
* QM Symbol Actions - Symbol search and spider operations
|
|
*/
|
|
|
|
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers';
|
|
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
|
|
import { QMSessionManager } from '../shared/session-manager';
|
|
import { QMOperationTracker } from '../shared/operation-tracker';
|
|
import { initializeQMOperations } from '../shared/operation-registry';
|
|
import type { Exchange, SymbolSpiderJob } from '../shared/types';
|
|
|
|
/**
|
|
* Spider search for symbols - recursively searches QM API
|
|
* Root job (no prefix) creates A-Z jobs
|
|
* Each job searches its prefix and creates child jobs if needed
|
|
*/
|
|
export async function spiderSymbol(
|
|
this: BaseHandler,
|
|
input: SymbolSpiderJob,
|
|
_context: ExecutionContext
|
|
): Promise<{
|
|
message: string;
|
|
symbolsFound?: number;
|
|
jobsCreated?: number;
|
|
}> {
|
|
const { prefix, depth = 0, maxDepth = 4 } = input || {};
|
|
|
|
this.logger.info(`Spider symbol search ${prefix}`, { prefix, depth, maxDepth });
|
|
|
|
if (!prefix) {
|
|
// Root job - create A-Z jobs
|
|
let jobsCreated = 0;
|
|
for (let i = 0; i < 26; i++) {
|
|
const letter = String.fromCharCode(65 + i); // A-Z
|
|
await this.scheduleOperation('spider-symbols', {
|
|
prefix: letter,
|
|
depth: 1,
|
|
source: 'qm',
|
|
maxDepth
|
|
}, {
|
|
priority: 5
|
|
});
|
|
jobsCreated++;
|
|
}
|
|
|
|
this.logger.info('Created root spider jobs', { jobsCreated });
|
|
return {
|
|
message: `Queued ${jobsCreated} root jobs (A-Z)`,
|
|
jobsCreated
|
|
};
|
|
}
|
|
|
|
try {
|
|
// Search current prefix
|
|
const symbols = await searchSymbols.call(this, { query: prefix });
|
|
|
|
if (!symbols || symbols.length === 0) {
|
|
this.logger.debug('No symbols found for prefix', { prefix });
|
|
return {
|
|
message: `No symbols found for prefix: ${prefix}`,
|
|
symbolsFound: 0
|
|
};
|
|
}
|
|
|
|
await this.mongodb.batchUpsert('qmSymbols', symbols, ['qmSearchCode']);
|
|
|
|
this.logger.info(`Stored symbols from spider search ${prefix} - ${symbols.length}`, {
|
|
prefix,
|
|
count: symbols.length
|
|
});
|
|
|
|
// Extract and store unique exchanges
|
|
const exchanges: Exchange[] = [];
|
|
for (const symbol of symbols) {
|
|
if (symbol.exchange && !exchanges.some(ex => ex.exchange === symbol.exchange)) {
|
|
exchanges.push({
|
|
exchange: symbol.exchange,
|
|
exchangeCode: symbol.exchangeCode || '',
|
|
exchangeShortName: symbol.exchangeShortName || '',
|
|
countryCode: symbol.countryCode || '',
|
|
source: 'qm',
|
|
});
|
|
}
|
|
}
|
|
|
|
if (exchanges.length > 0) {
|
|
await this.mongodb.batchUpsert('qmExchanges', exchanges, ['exchange']);
|
|
this.logger.debug('Stored exchanges from spider search', {
|
|
count: exchanges.length
|
|
});
|
|
}
|
|
|
|
// If not at max depth and we found symbols, create child jobs
|
|
if (depth < maxDepth && symbols.length > 0) {
|
|
let jobsCreated = 0;
|
|
|
|
// Only create child jobs if we found a significant number of symbols
|
|
// This prevents excessive branching on sparse results
|
|
if (symbols.length >= 50) {
|
|
for (let i = 0; i < 26; i++) {
|
|
const nextPrefix = prefix + String.fromCharCode(65 + i);
|
|
await this.scheduleOperation('spider-symbols', {
|
|
prefix: nextPrefix,
|
|
depth: depth + 1,
|
|
source: 'qm',
|
|
maxDepth
|
|
}, {
|
|
priority: Math.max(1, 5 - depth)
|
|
});
|
|
jobsCreated++;
|
|
}
|
|
}
|
|
|
|
return {
|
|
message: `Found ${symbols.length} symbols for ${prefix}, queued ${jobsCreated} child jobs`,
|
|
symbolsFound: symbols.length,
|
|
jobsCreated
|
|
};
|
|
}
|
|
|
|
return {
|
|
message: `Found ${symbols.length} symbols for ${prefix} (at max depth or too few results)`,
|
|
symbolsFound: symbols.length
|
|
};
|
|
|
|
} catch (error) {
|
|
this.logger.error(`Spider search failed ${prefix}`, { prefix, error });
|
|
return {
|
|
message: `Spider search failed for prefix: ${prefix}`,
|
|
symbolsFound: 0
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search QM symbols API directly
|
|
*/
|
|
export async function searchSymbols(
|
|
this: BaseHandler,
|
|
input: { query: string },
|
|
_context?: ExecutionContext
|
|
): Promise<any[]> {
|
|
const { query } = input;
|
|
|
|
this.logger.debug('Searching QM symbols', { query });
|
|
|
|
const sessionManager = QMSessionManager.getInstance();
|
|
sessionManager.initialize(this.cache, this.logger);
|
|
|
|
// Get a session
|
|
const sessionId = QM_SESSION_IDS.LOOKUP;
|
|
const session = await sessionManager.getSession(sessionId);
|
|
|
|
if (!session || !session.uuid) {
|
|
throw new Error(`No active session found for QM LOOKUP`);
|
|
}
|
|
|
|
try {
|
|
// Build API request
|
|
const searchParams = new URLSearchParams({
|
|
marketType: 'equity',
|
|
pathName: '/demo/portal/company-summary.php',
|
|
q: query,
|
|
qmodTool: 'SmartSymbolLookup',
|
|
searchType: 'symbol',
|
|
showFree: 'false',
|
|
showHisa: 'false',
|
|
webmasterId: '500'
|
|
});
|
|
|
|
const apiUrl = `${QM_CONFIG.LOOKUP_URL}?${searchParams.toString()}`;
|
|
|
|
const response = await fetch(apiUrl, {
|
|
method: 'GET',
|
|
headers: session.headers,
|
|
proxy: session.proxy,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`QM API request failed: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
const symbols = await response.json();
|
|
|
|
// Update session success stats
|
|
await sessionManager.incrementSuccessfulCalls(sessionId, session.uuid);
|
|
|
|
// Process symbol data
|
|
const processedSymbols = Array.isArray(symbols) ? symbols.map((symbol: any) => ({
|
|
...symbol,
|
|
qmSearchCode: symbol.symbol || '',
|
|
symbol: (symbol.symbol as string)?.split(':')[0] || '',
|
|
})) : [];
|
|
|
|
this.logger.debug('QM API returned symbols ${query} - ${processedSymbols.length}', {
|
|
query,
|
|
count: processedSymbols.length
|
|
});
|
|
|
|
return processedSymbols;
|
|
|
|
} catch (error) {
|
|
// Update session failure stats
|
|
if (session.uuid) {
|
|
await sessionManager.incrementFailedCalls(sessionId, session.uuid);
|
|
}
|
|
|
|
this.logger.error('Error searching QM symbols', {
|
|
query,
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
});
|
|
|
|
throw error;
|
|
}
|
|
} |