Files
Finlytic/FinlyticAnalyzer/Controllers/ManualAnalysisController.cs
T

209 lines
8.7 KiB
C#

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<ManualAnalysisController> _logger;
public ManualAnalysisController(
IVixTrackerService vixTracker,
IN8nEvaluationService n8nService,
IWinRateCalculator winRateCalculator,
AnalyzerDbContext dbContext,
ILogger<ManualAnalysisController> logger)
{
_vixTracker = vixTracker;
_n8nService = n8nService;
_winRateCalculator = winRateCalculator;
_dbContext = dbContext;
_logger = logger;
}
/// <summary>
/// Runs a manual analysis based on the provided request.
/// </summary>
[HttpPost("manual")]
public async Task<IActionResult> 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 = 0,
AssetWinRate = winRate,
AvgReturnPercent = 0.0,
LastTradeResult = "UNKNOWN"
}
};
var n8nResponse = await _n8nService.EvaluateAssetAsync(n8nRequest, cancellationToken);
bool shouldProceed = n8nResponse != null && string.Equals(n8nResponse.AiDecision, "Proceed", StringComparison.OrdinalIgnoreCase);
double dynamicWinRate = _winRateCalculator.CalculateDynamicWinRate(
request.Sector,
request.Symbol,
regime,
n8nEvalScore: n8nResponse?.EvalScore,
signalType: n8nResponse?.SuggestedDirection ?? "BUY");
TradeProposalDto? proposal = null;
if (shouldProceed && n8nResponse != null)
{
proposal = new TradeProposalDto
{
TradeId = "PROP-" + Guid.NewGuid().ToString("N")[..10].ToUpperInvariant(),
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 = dynamicWinRate,
VixRegime = regime,
VixValue = currentVix,
TtlMinutes = 60,
Reasoning = $"Manual n8n Evaluation ({n8nResponse.AiDecision}): {n8nResponse.AiReasoning}",
StopLoss = n8nResponse.ExecutionPlan?.StopLoss ?? 0,
TakeProfit = n8nResponse.ExecutionPlan?.TakeProfitTargets != null && n8nResponse.ExecutionPlan.TakeProfitTargets.Count > 0 ? n8nResponse.ExecutionPlan.TakeProfitTargets[0] : 0,
EntryZoneMin = n8nResponse.ExecutionPlan?.EntryZone?.Min,
EntryZoneMax = n8nResponse.ExecutionPlan?.EntryZone?.Max,
TakeProfitTargets = n8nResponse.ExecutionPlan?.TakeProfitTargets,
RiskRewardRatio = n8nResponse.ExecutionPlan?.RiskRewardRatio,
MaxLeverage = n8nResponse.ExecutionPlan?.MaxLeverage,
TechnicalRationale = n8nResponse.DetailedAnalysis?.TechnicalRationale ?? string.Empty,
FundamentalRationale = n8nResponse.DetailedAnalysis?.FundamentalRationale ?? string.Empty,
RiskWarning = n8nResponse.DetailedAnalysis?.RiskWarning ?? string.Empty,
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 = dynamicWinRate,
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
});
}
}