feat(Analyzer): refactor analyzer and implement auto mode
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
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 = 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
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user