moved jobs to provider config

This commit is contained in:
Bojan Kucera 2025-06-08 15:03:30 -04:00
parent f9c2860ff4
commit 52c2f08db2
7 changed files with 183 additions and 76 deletions

View file

@ -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;
}

View file

@ -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...');

View file

@ -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'
}
]
};

View file

@ -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'
}
]
};

View file

@ -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'
}
]
};

View file

@ -4,10 +4,20 @@ export interface JobHandler {
(payload: any): Promise<any>;
}
export interface ScheduledJob {
type: string;
operation: string;
payload: any;
cronPattern: string;
priority?: number;
description?: string;
}
export interface ProviderConfig {
name: string;
service: string;
operations: Record<string, JobHandler>;
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,

View file

@ -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
}));
}