diff --git a/apps/dashboard/src/app/pages/strategies/strategy-details/strategy-details.component.ts b/apps/dashboard/src/app/pages/strategies/strategy-details/strategy-details.component.ts index a45dcc0..b9bff5e 100644 --- a/apps/dashboard/src/app/pages/strategies/strategy-details/strategy-details.component.ts +++ b/apps/dashboard/src/app/pages/strategies/strategy-details/strategy-details.component.ts @@ -162,7 +162,7 @@ export class StrategyDetailsComponent implements OnChanges { .subscribe((update: any) => { if (update.strategyId === this.strategy?.id) { // Update strategy status if changed - if (update.status && this.strategy.status !== update.status) { + if (update.status && this.strategy && this.strategy.status !== update.status) { this.strategy.status = update.status; } diff --git a/apps/data-service/src/index.ts b/apps/data-service/src/index.ts index ec14a64..9527ba9 100644 --- a/apps/data-service/src/index.ts +++ b/apps/data-service/src/index.ts @@ -188,6 +188,21 @@ app.get('/api/providers', async (c) => { } }); +// Add new endpoint to see scheduled jobs +app.get('/api/scheduled-jobs', async (c) => { + try { + const jobs = queueManager.getScheduledJobsInfo(); + return c.json({ + status: 'success', + count: jobs.length, + jobs + }); + } catch (error) { + logger.error('Failed to get scheduled jobs info', { error }); + return c.json({ status: 'error', message: 'Failed to get scheduled jobs' }, 500); + } +}); + // Initialize services async function initializeServices() { logger.info('Initializing data service...'); diff --git a/apps/data-service/src/providers/proxy.provider.ts b/apps/data-service/src/providers/proxy.provider.ts index 0e4d5fe..ad55764 100644 --- a/apps/data-service/src/providers/proxy.provider.ts +++ b/apps/data-service/src/providers/proxy.provider.ts @@ -9,34 +9,25 @@ export const proxyProvider: ProviderConfig = { return await proxyService.fetchProxiesFromSources(); }, - // 'check-specific': async (payload: { proxies: any[] }) => { - // const { proxyService } = await import('../services/proxy.service'); - // return await proxyService.checkProxies(payload.proxies); - // }, + 'check-specific': async (payload: { proxies: any[] }) => { + const { proxyService } = await import('../services/proxy.service'); + return await proxyService.checkProxies(payload.proxies); + }, - // 'get-stats': async (payload: { includeDetails?: boolean }) => { - // const { proxyService } = await import('../services/proxy.service'); - // return await proxyService.getProxyStats(payload.includeDetails); - // }, - - // 'cleanup-old-data': async (payload: { daysToKeep?: number }) => { - // const { proxyService } = await import('../services/proxy.service'); - // return await proxyService.cleanupOldData(payload.daysToKeep || 7); - // }, - - // 'get-working-proxy': async (payload: { protocol?: string; country?: string; timeout?: number }) => { - // const { proxyService } = await import('../services/proxy.service'); - // return await proxyService.getWorkingProxy(payload); - // }, - - // 'validate-proxy': async (payload: { proxy: any; testUrl?: string }) => { - // const { proxyService } = await import('../services/proxy.service'); - // return await proxyService.validateProxy(payload.proxy, payload.testUrl); - // }, - - // 'rotate-proxies': async (payload: { count?: number }) => { - // const { proxyService } = await import('../services/proxy.service'); - // return await proxyService.rotateProxies(payload.count || 5); - // } - } + 'get-working-proxy': async (payload: { protocol?: string; country?: string; timeout?: number }) => { + const { proxyService } = await import('../services/proxy.service'); + return await proxyService.getWorkingProxy(); + } + }, + + scheduledJobs: [ + { + type: 'proxy-maintenance', + operation: 'fetch-and-check', + payload: {}, + cronPattern: '*/15 * * * *', // Every 15 minutes + priority: 5, + description: 'Fetch and validate proxy list from sources' + } + ] }; diff --git a/apps/data-service/src/providers/quotemedia.provider.ts b/apps/data-service/src/providers/quotemedia.provider.ts index e892b5e..79afbe0 100644 --- a/apps/data-service/src/providers/quotemedia.provider.ts +++ b/apps/data-service/src/providers/quotemedia.provider.ts @@ -155,8 +155,7 @@ export const quotemediaProvider: ProviderConfig = { })); await new Promise(resolve => setTimeout(resolve, 400 + Math.random() * 300)); - - return { + return { symbol: payload.symbol, expiration: payload.expiration || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], calls, @@ -164,5 +163,32 @@ export const quotemediaProvider: ProviderConfig = { source: 'quotemedia' }; } - } + }, + + scheduledJobs: [ + { + type: 'quotemedia-premium-refresh', + operation: 'batch-quotes', + payload: { symbols: ['AAPL', 'GOOGL', 'MSFT'] }, + cronPattern: '*/2 * * * *', // Every 2 minutes + priority: 7, + description: 'Refresh premium quotes with detailed market data' + }, + { + type: 'quotemedia-options-update', + operation: 'options-chain', + payload: { symbol: 'SPY' }, + cronPattern: '*/10 * * * *', // Every 10 minutes + priority: 5, + description: 'Update options chain data for SPY ETF' + }, + { + type: 'quotemedia-profiles', + operation: 'company-profile', + payload: { symbol: 'AAPL' }, + cronPattern: '0 9 * * 1-5', // Weekdays at 9 AM + priority: 3, + description: 'Update company profile data' + } + ] }; diff --git a/apps/data-service/src/providers/yahoo.provider.ts b/apps/data-service/src/providers/yahoo.provider.ts index 560ff57..402269d 100644 --- a/apps/data-service/src/providers/yahoo.provider.ts +++ b/apps/data-service/src/providers/yahoo.provider.ts @@ -222,8 +222,34 @@ export const yahooProvider: ProviderConfig = { }; await new Promise(resolve => setTimeout(resolve, 180 + Math.random() * 120)); - - return recommendations; + return recommendations; } - } + }, + + scheduledJobs: [ + { + type: 'yahoo-market-refresh', + operation: 'live-data', + payload: { symbol: 'AAPL' }, + cronPattern: '*/1 * * * *', // Every minute + priority: 8, + description: 'Refresh Apple stock price from Yahoo Finance' + }, + { + type: 'yahoo-sp500-update', + operation: 'live-data', + payload: { symbol: 'SPY' }, + cronPattern: '*/2 * * * *', // Every 2 minutes + priority: 9, + description: 'Update S&P 500 ETF price' + }, + { + type: 'yahoo-earnings-check', + operation: 'earnings', + payload: { symbol: 'AAPL' }, + cronPattern: '0 16 * * 1-5', // Weekdays at 4 PM (market close) + priority: 6, + description: 'Check earnings data for Apple' + } + ] }; diff --git a/apps/data-service/src/services/provider-registry.service.ts b/apps/data-service/src/services/provider-registry.service.ts index 73b0d37..d7e0f59 100644 --- a/apps/data-service/src/services/provider-registry.service.ts +++ b/apps/data-service/src/services/provider-registry.service.ts @@ -4,10 +4,20 @@ export interface JobHandler { (payload: any): Promise; } +export interface ScheduledJob { + type: string; + operation: string; + payload: any; + cronPattern: string; + priority?: number; + description?: string; +} + export interface ProviderConfig { name: string; service: string; operations: Record; + scheduledJobs?: ScheduledJob[]; } export class ProviderRegistry { @@ -16,12 +26,12 @@ export class ProviderRegistry { /** * Register a provider with its operations - */ - registerProvider(config: ProviderConfig): void { + */ registerProvider(config: ProviderConfig): void { const key = `${config.service}:${config.name}`; this.providers.set(key, config); this.logger.info(`Registered provider: ${key}`, { - operations: Object.keys(config.operations) + operations: Object.keys(config.operations), + scheduledJobs: config.scheduledJobs?.length || 0 }); } @@ -49,6 +59,28 @@ export class ProviderRegistry { /** * Get all registered providers */ + getAllScheduledJobs(): Array<{ + service: string; + provider: string; + job: ScheduledJob; + }> { + const allJobs: Array<{ service: string; provider: string; job: ScheduledJob }> = []; + + for (const [key, config] of this.providers) { + if (config.scheduledJobs) { + for (const job of config.scheduledJobs) { + allJobs.push({ + service: config.service, + provider: config.name, + job + }); + } + } + } + + return allJobs; + } + getProviders(): Array<{ key: string; config: ProviderConfig }> { return Array.from(this.providers.entries()).map(([key, config]) => ({ key, diff --git a/apps/data-service/src/services/queue.service.ts b/apps/data-service/src/services/queue.service.ts index 5dff892..650e0b8 100644 --- a/apps/data-service/src/services/queue.service.ts +++ b/apps/data-service/src/services/queue.service.ts @@ -140,46 +140,50 @@ export class QueueService { this.logger.debug('Job progress', { id: job.id, progress }); }); } - private async setupScheduledTasks() { try { - // Market data refresh every minute using Yahoo Finance - await this.addRecurringJob({ - type: 'market-data-refresh', - service: 'market-data', - provider: 'yahoo-finance', - operation: 'live-data', - payload: { symbol: 'AAPL' } - }, '*/1 * * * *'); + this.logger.info('Setting up scheduled tasks from providers...'); + + // Get all scheduled jobs from all providers + const allScheduledJobs = providerRegistry.getAllScheduledJobs(); + + if (allScheduledJobs.length === 0) { + this.logger.warn('No scheduled jobs found in providers'); + return; + } - // Market data refresh using QuoteMedia every 2 minutes - await this.addRecurringJob({ - type: 'market-data-quotemedia', - service: 'market-data', - provider: 'quotemedia', - operation: 'batch-quotes', - payload: { symbols: ['GOOGL', 'MSFT', 'TSLA'] } - }, '*/2 * * * *'); + // Register each scheduled job + for (const { service, provider, job } of allScheduledJobs) { + try { + await this.addRecurringJob({ + type: job.type, + service: service, + provider: provider, + operation: job.operation, + payload: job.payload, + priority: job.priority + }, job.cronPattern); - // Proxy fetch every 15 minutes - await this.addRecurringJob({ - type: 'proxy-maintenance', - service: 'proxy', - provider: 'proxy-service', - operation: 'fetch-and-check', - payload: {} - }, '*/15 * * * *'); + this.logger.info('Scheduled job registered', { + type: job.type, + service, + provider, + operation: job.operation, + cronPattern: job.cronPattern, + description: job.description + }); + } catch (error) { + this.logger.error('Failed to register scheduled job', { + type: job.type, + service, + provider, + error: error instanceof Error ? error.message : String(error) + }); + } + } - // Proxy cleanup daily at 2 AM - await this.addRecurringJob({ - type: 'proxy-cleanup', - service: 'proxy', - provider: 'proxy-service', - operation: 'cleanup-old-data', - payload: { daysToKeep: 7 } - }, '0 2 * * *'); - - this.logger.info('Scheduled tasks configured'); + this.logger.info(`Successfully configured ${allScheduledJobs.length} scheduled tasks`); + } catch (error) { this.logger.error('Failed to setup scheduled tasks', error); } @@ -256,13 +260,26 @@ export class QueueService { } return this.worker.opts.concurrency || 1; } - getRegisteredProviders() { return providerRegistry.getProviders().map(({ key, config }) => ({ key, name: config.name, service: config.service, - operations: Object.keys(config.operations) + operations: Object.keys(config.operations), + scheduledJobs: config.scheduledJobs?.length || 0 + })); + } + + getScheduledJobsInfo() { + return providerRegistry.getAllScheduledJobs().map(({ service, provider, job }) => ({ + id: `${service}-${provider}-${job.type}`, + service, + provider, + type: job.type, + operation: job.operation, + cronPattern: job.cronPattern, + priority: job.priority, + description: job.description })); }