using Hangfire; using QuantEngine.Application.Services; using QuantEngine.Infrastructure.Data; namespace QuantEngine.Web.Services; /// /// Scheduler Service for managing background jobs with Hangfire /// public class SchedulerService { private readonly ILogger _logger; private readonly IBackgroundJobClient _jobClient; private readonly IRecurringJobManager _recurringJobManager; private readonly IKisApiPriceSource _kisApi; public SchedulerService( ILogger logger, IBackgroundJobClient jobClient, IRecurringJobManager recurringJobManager, IKisApiPriceSource kisApi) { _logger = logger; _jobClient = jobClient; _recurringJobManager = recurringJobManager; _kisApi = kisApi; } /// /// Initialize scheduled jobs /// public void InitializeSchedules() { try { _logger.LogInformation("Initializing Hangfire schedules..."); // Daily data collection at 9:00 AM _recurringJobManager.AddOrUpdate( "daily-collection", () => RunDailyCollectionAsync(), "0 9 * * *", // Every day at 9:00 AM new RecurringJobOptions { TimeZone = TimeZoneInfo.Local } ); // Hourly price update (during market hours 9 AM - 4 PM) _recurringJobManager.AddOrUpdate( "hourly-price-update", () => UpdatePricesAsync(), "0 9-15 * * 1-5", // Every hour, 9 AM to 3 PM, Mon-Fri new RecurringJobOptions { TimeZone = TimeZoneInfo.Local } ); // Weekly report generation (Friday at 5:00 PM) _recurringJobManager.AddOrUpdate( "weekly-report", () => GenerateWeeklyReportAsync(), "0 17 * * 5", // Every Friday at 5:00 PM new RecurringJobOptions { TimeZone = TimeZoneInfo.Local } ); // Monthly optimization (First day of month at 2:00 AM) _recurringJobManager.AddOrUpdate( "monthly-optimization", () => RunMonthlyOptimizationAsync(), "0 2 1 * *", // First day of month at 2:00 AM new RecurringJobOptions { TimeZone = TimeZoneInfo.Local } ); _logger.LogInformation("Hangfire schedules initialized successfully"); } catch (Exception ex) { _logger.LogError(ex, "Error initializing Hangfire schedules"); } } /// /// Run daily data collection /// public async Task RunDailyCollectionAsync() { try { _logger.LogInformation("Starting daily data collection job at {Time}", DateTime.Now); // List of tickers to collect var tickers = new[] { "005930", "000660", "051910", "005380", "010140", "005490" }; foreach (var ticker in tickers) { // Simulate data collection await Task.Delay(100); _logger.LogInformation("Collected data for ticker: {Ticker}", ticker); } _logger.LogInformation("Daily data collection completed at {Time}", DateTime.Now); } catch (Exception ex) { _logger.LogError(ex, "Error during daily collection"); } } /// /// Update prices hourly /// public async Task UpdatePricesAsync() { try { _logger.LogInformation("Starting hourly price update at {Time}", DateTime.Now); var tickers = new[] { "005930", "000660", "051910" }; foreach (var ticker in tickers) { try { // Enqueue price update as background job _jobClient.Enqueue(() => FetchPriceAsync(ticker)); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to enqueue price update for {Ticker}", ticker); } } _logger.LogInformation("Hourly price update completed"); } catch (Exception ex) { _logger.LogError(ex, "Error during price update"); } } /// /// Fetch price for specific ticker /// public async Task FetchPriceAsync(string ticker) { try { _logger.LogInformation("Fetching price for ticker: {Ticker}", ticker); // TODO: Implement actual price fetching await Task.Delay(50); _logger.LogInformation("Price fetched successfully for {Ticker}", ticker); } catch (Exception ex) { _logger.LogError(ex, "Error fetching price for {Ticker}", ticker); } } /// /// Generate weekly report /// public async Task GenerateWeeklyReportAsync() { try { _logger.LogInformation("Starting weekly report generation at {Time}", DateTime.Now); // TODO: Implement report generation logic await Task.Delay(500); _logger.LogInformation("Weekly report generated successfully"); } catch (Exception ex) { _logger.LogError(ex, "Error generating weekly report"); } } /// /// Run monthly optimization /// public async Task RunMonthlyOptimizationAsync() { try { _logger.LogInformation("Starting monthly optimization at {Time}", DateTime.Now); // TODO: Implement optimization logic await Task.Delay(1000); _logger.LogInformation("Monthly optimization completed"); } catch (Exception ex) { _logger.LogError(ex, "Error during monthly optimization"); } } /// /// Enqueue one-time job /// public string EnqueueJob(string jobName, Func job) { var jobId = _jobClient.Enqueue(job); _logger.LogInformation("Enqueued job {JobName} with ID {JobId}", jobName, jobId); return jobId; } /// /// Get job status /// public JobState GetJobStatus(string jobId) { return JobStorage.Current.GetConnection().GetJobData(jobId)?.State; } /// /// Cancel scheduled job /// public void CancelScheduledJob(string jobName) { _recurringJobManager.RemoveIfExists(jobName); _logger.LogInformation("Cancelled scheduled job: {JobName}", jobName); } } /// /// Extension methods for Hangfire registration /// public static class HangfireServiceExtensions { /// /// Register Hangfire with SQL Server storage /// public static IServiceCollection AddHangfireServices( this IServiceCollection services, string connectionString) { // Add Hangfire services services.AddHangfire(configuration => configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() .UseSqlServerStorage(connectionString, new SqlServerStorageOptions { CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), QueuePollInterval = TimeSpan.FromSeconds(15), UsePageLocks = true, DisableGlobalLocks = true })); // Add Hangfire server services.AddHangfireServer(options => { options.WorkerCount = Environment.ProcessorCount * 2; options.Queues = new[] { "default" }; }); // Register scheduler service services.AddScoped(); return services; } /// /// Use Hangfire dashboard and initialize schedules /// public static IApplicationBuilder UseHangfireSetup( this IApplicationBuilder app, IServiceProvider serviceProvider) { // Use Hangfire Dashboard app.UseHangfireDashboard("/hangfire", new DashboardOptions { Authorization = new[] { new HangfireAuthorizationFilter() } }); // Initialize schedules var schedulerService = serviceProvider.GetRequiredService(); schedulerService.InitializeSchedules(); return app; } } /// /// Simple authorization filter for Hangfire Dashboard /// public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter { public bool Authorize(DashboardContext context) { // TODO: Implement proper authorization check // For now, allow all in development return true; } }