using System; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using FinlyticAnalyzer.Database; using FinlyticAnalyzer.Entities; using FinlyticAnalyzer.Services; using FinlyticCore.Models.Analyzer; using FinlyticCore.Models.Trades; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace FinlyticAnalyzer.Controllers; public class ManualAnalysisRequest { public string Symbol { get; set; } = string.Empty; public string Isin { get; set; } = string.Empty; public string Sector { get; set; } = "Technology"; public string Headline { get; set; } = "Manual User Request"; public decimal CurrentPrice { get; set; } = 100.0m; public int RiskScore { get; set; } = 50; // 0 to 100 public int MinTimeframeValue { get; set; } = 4; public int MaxTimeframeValue { get; set; } = 6; public string TimeframeUnit { get; set; } = "Tage"; public string InstrumentType { get; set; } = "Stock"; public string UserNotes { get; set; } = string.Empty; } [ApiController] [Route("api/v1/analyze")] public class ManualAnalysisController : ControllerBase { private readonly IVixTrackerService _vixTracker; private readonly IN8nEvaluationService _n8nService; private readonly IWinRateCalculator _winRateCalculator; private readonly AnalyzerDbContext _dbContext; private readonly ILogger _logger; public ManualAnalysisController( IVixTrackerService vixTracker, IN8nEvaluationService n8nService, IWinRateCalculator winRateCalculator, AnalyzerDbContext dbContext, ILogger logger) { _vixTracker = vixTracker; _n8nService = n8nService; _winRateCalculator = winRateCalculator; _dbContext = dbContext; _logger = logger; } /// /// Runs a manual analysis based on the provided request. /// [HttpPost("manual")] public async Task RunManualAnalysis([FromBody] ManualAnalysisRequest request, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(request.Symbol) && string.IsNullOrWhiteSpace(request.Isin)) { return BadRequest(new { error = "Symbol or ISIN is required." }); } var regime = _vixTracker.GetCurrentRegime(); var currentVix = _vixTracker.GetCurrentVix(); string analysisId = Guid.NewGuid().ToString("N"); double winRate = _winRateCalculator.CalculateWinRate(request.Sector, request.Symbol, regime); string riskLabel = request.RiskScore > 70 ? $"Aggressiv ({request.RiskScore}/100)" : (request.RiskScore > 30 ? $"Balanced ({request.RiskScore}/100)" : $"Konservativ ({request.RiskScore}/100)"); string timeframeFormatted = $"{request.MinTimeframeValue}-{request.MaxTimeframeValue} {request.TimeframeUnit}"; var n8nRequest = new N8nAnalysisRequestDto { RequestId = analysisId, Timestamp = DateTime.UtcNow, TriggerType = "Manual", TargetAsset = new TargetAssetInfo { Symbol = request.Symbol.ToUpperInvariant(), Isin = request.Isin.ToUpperInvariant(), Sector = request.Sector }, MarketContext = new MarketContextInfo { Vix = currentVix, MarketRegime = regime.ToString() }, FilterContext = new FilterContextInfo { ImpactScore = 1.0, RawNewsHeadline = string.IsNullOrWhiteSpace(request.Headline) ? "Manual User Trigger" : request.Headline }, UserPreferences = new UserPreferencesInfo { RiskScore = request.RiskScore, RiskTolerance = riskLabel, MinTimeframeValue = request.MinTimeframeValue, MaxTimeframeValue = request.MaxTimeframeValue, TimeframeUnit = request.TimeframeUnit, TimeframeFormatted = timeframeFormatted, InstrumentType = request.InstrumentType, UserNotes = request.UserNotes }, TradeFeedback = new TradeFeedbackInfo { TotalAssetTrades = 12, AssetWinRate = winRate, AvgReturnPercent = 3.4, LastTradeResult = "WIN" } }; var n8nResponse = await _n8nService.EvaluateAssetAsync(n8nRequest, cancellationToken); bool shouldProceed = n8nResponse != null && string.Equals(n8nResponse.AiDecision, "Proceed", StringComparison.OrdinalIgnoreCase); TradeProposalDto? proposal = null; if (shouldProceed && n8nResponse != null) { proposal = new TradeProposalDto { AnalysisId = analysisId, EventId = analysisId, Sector = request.Sector, Symbol = request.Symbol.ToUpperInvariant(), Isin = request.Isin.ToUpperInvariant(), CompanyName = request.Symbol, EntryPrice = request.CurrentPrice, SignalType = string.Equals(n8nResponse.SuggestedDirection, "Short", StringComparison.OrdinalIgnoreCase) ? "SELL" : "BUY", RiskTolerance = n8nResponse.SuggestedRisk, Timeframe = timeframeFormatted, InstrumentType = request.InstrumentType, WinRate = winRate, VixRegime = regime, VixValue = currentVix, TtlMinutes = 60, Reasoning = $"Manual n8n Evaluation ({n8nResponse.AiDecision}): {n8nResponse.AiReasoning}", CreatedAt = DateTime.UtcNow }; } var analysisEntity = new AnalysisEntity { AnalysisId = analysisId, EventId = analysisId, Sector = request.Sector, Symbol = request.Symbol.ToUpperInvariant(), Isin = request.Isin.ToUpperInvariant(), VixRegime = regime, VixValue = currentVix, ImpactScore = 1.0, WinRate = winRate, RawDataJson = JsonSerializer.Serialize(request), AiOutputJson = proposal != null ? JsonSerializer.Serialize(proposal) : "{}", N8nResponseJson = n8nResponse != null ? JsonSerializer.Serialize(n8nResponse) : "{}", N8nEvalScore = n8nResponse?.EvalScore ?? 0, N8nDecision = n8nResponse?.AiDecision ?? "Rejected", IsTradeProposed = shouldProceed, CreatedAt = DateTime.UtcNow }; _dbContext.Analyses.Add(analysisEntity); await _dbContext.SaveChangesAsync(cancellationToken); if (!shouldProceed) { return Ok(new { analysisId, isTradeProposed = false, status = "Rejected", recommendation = "NOT_RECOMMENDED", reasoning = n8nResponse?.AiReasoning ?? "Die KI stuft diesen Trade als zu riskant ein und empfiehlt keine Positionierung.", n8nResponse, proposal = (object?)null }); } return Ok(new { analysisId, isTradeProposed = true, status = "Success", recommendation = "RECOMMENDED", n8nResponse, proposal }); } }