feat(technicals): add technical analysis microservice with indicator engines, pattern detectors, and strategies
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FinlyticCore.Services;
|
||||
using FinlyticTechnicals.Util;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace FinlyticTechnicals.Services;
|
||||
|
||||
public class TechnicalScannerBackgroundService : BackgroundService
|
||||
{
|
||||
private readonly ITechnicalUniverseManager _universeManager;
|
||||
private readonly ITechnicalScoringEngine _scoringEngine;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IFinlyticLogger<TechnicalScannerBackgroundService> _logger;
|
||||
|
||||
public TechnicalScannerBackgroundService(
|
||||
ITechnicalUniverseManager universeManager,
|
||||
ITechnicalScoringEngine scoringEngine,
|
||||
ISettingsService settingsService,
|
||||
IFinlyticLogger<TechnicalScannerBackgroundService> logger)
|
||||
{
|
||||
_universeManager = universeManager;
|
||||
_scoringEngine = scoringEngine;
|
||||
_settingsService = settingsService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await _logger.LogInfoAsync(SettingKeys.TechnicalAnalysisChannel,
|
||||
"[TechnicalScanner] Starting Technical Universe Scanner Background Service...");
|
||||
|
||||
// Initial delay for MQTT connections to stabilize
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
|
||||
|
||||
// Initial synchronization of Universe
|
||||
await _universeManager.RefreshFavoritesAsync(stoppingToken);
|
||||
await _universeManager.RefreshDiscoveryAsync(stoppingToken);
|
||||
|
||||
DateTime lastFavoritesSyncUtc = DateTime.UtcNow;
|
||||
DateTime lastDiscoverySyncUtc = DateTime.UtcNow;
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
|
||||
// 1. Check Periodic Sync Timers
|
||||
if (now - lastFavoritesSyncUtc >= TimeSpan.FromMinutes(15))
|
||||
{
|
||||
await _universeManager.RefreshFavoritesAsync(stoppingToken);
|
||||
lastFavoritesSyncUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if (now - lastDiscoverySyncUtc >= TimeSpan.FromMinutes(30))
|
||||
{
|
||||
await _universeManager.RefreshDiscoveryAsync(stoppingToken);
|
||||
lastDiscoverySyncUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
// 2. Retrieve Active Prioritized Scan Universe
|
||||
var universe = await _universeManager.GetActiveUniverseAsync(stoppingToken);
|
||||
if (universe.Count > 0)
|
||||
{
|
||||
await _logger.LogInfoAsync(SettingKeys.TechnicalAnalysisChannel,
|
||||
"[TechnicalScanner] Scanning {Count} assets in active universe across all strategies...", universe.Count);
|
||||
|
||||
foreach (var entry in universe)
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested) break;
|
||||
|
||||
try
|
||||
{
|
||||
var setups = await _scoringEngine.AnalyzeIsinAsync(entry.Isin, entry.Symbol, entry.Source, entry.AddedAtUtc, stoppingToken);
|
||||
if (setups.Count > 0)
|
||||
{
|
||||
await _logger.LogInfoAsync(SettingKeys.TechnicalAnalysisChannel,
|
||||
"[TechnicalScanner] Found {Count} active setup(s) for ISIN {Isin} (Top Score: {Score:F1})",
|
||||
setups.Count, entry.Isin, setups[0].QualityScore);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _logger.LogWarningAsync(SettingKeys.TechnicalAnalysisChannel, ex,
|
||||
"[TechnicalScanner] Error analyzing ISIN {Isin}", entry.Isin);
|
||||
}
|
||||
|
||||
// Gentle throttle between asset analysis runs
|
||||
await Task.Delay(250, stoppingToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _logger.LogInfoAsync(SettingKeys.TechnicalAnalysisChannel,
|
||||
"[TechnicalScanner] Scan universe is currently empty. Waiting for next cycle.");
|
||||
}
|
||||
|
||||
// Wait 60 seconds before next full universe evaluation pass
|
||||
await Task.Delay(TimeSpan.FromSeconds(60), stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _logger.LogErrorAsync(SettingKeys.TechnicalAnalysisChannel, ex,
|
||||
"[TechnicalScanner] Unexpected error in scanner loop. Retrying in 30 seconds.");
|
||||
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
await _logger.LogInfoAsync(SettingKeys.TechnicalAnalysisChannel,
|
||||
"[TechnicalScanner] Technical Universe Scanner Background Service stopped.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user