feat(core): add shared DTOs, MqttTopics constants, DatabaseBootstrapper, and ManagedMqttClient extensions

This commit is contained in:
2026-08-24 21:35:24 +02:00
parent 6ab84fe1de
commit 44b161d509
39 changed files with 2545 additions and 709 deletions
@@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace FinlyticCore.Models.Analyzer;
public class AssetRecommendationDto
{
[JsonPropertyName("mode")]
public string Mode { get; set; } = "AUTO_SCREENER";
[JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
[JsonPropertyName("recommended_asset")]
public RecommendedAssetInfo RecommendedAsset { get; set; } = new();
[JsonPropertyName("rationale")]
public RecommendationRationaleInfo Rationale { get; set; } = new();
[JsonPropertyName("action_required")]
public string ActionRequired { get; set; } = "PROMPT_USER_FOR_MANUAL_TRADE"; // "PROMPT_USER_FOR_MANUAL_TRADE" | "NO_ACTION"
}
public class RecommendedAssetInfo
{
[JsonPropertyName("symbol")]
public string Symbol { get; set; } = string.Empty;
[JsonPropertyName("company_name")]
public string CompanyName { get; set; } = string.Empty;
[JsonPropertyName("isin")]
public string Isin { get; set; } = string.Empty;
[JsonPropertyName("market")]
public string Market { get; set; } = "US_EQUITIES";
[JsonPropertyName("bias")]
public string Bias { get; set; } = "BULLISH"; // "BULLISH" | "BEARISH" | "NEUTRAL"
[JsonPropertyName("confidence_score")]
public double ConfidenceScore { get; set; }
[JsonPropertyName("timeframe")]
public string Timeframe { get; set; } = "1D";
}
public class RecommendationRationaleInfo
{
[JsonPropertyName("pattern_detected")]
public string PatternDetected { get; set; } = string.Empty;
[JsonPropertyName("vix_context")]
public string VixContext { get; set; } = string.Empty;
[JsonPropertyName("key_technical_levels")]
public KeyTechnicalLevelsInfo KeyTechnicalLevels { get; set; } = new();
[JsonPropertyName("summary")]
public string Summary { get; set; } = string.Empty;
}
public class KeyTechnicalLevelsInfo
{
[JsonPropertyName("support")]
public List<double> Support { get; set; } = new();
[JsonPropertyName("resistance")]
public List<double> Resistance { get; set; } = new();
}
@@ -1,31 +0,0 @@
using System.Text.Json.Serialization;
using FinlyticCore.Models.Trades;
namespace FinlyticCore.Models.Analyzer;
/// <summary>
/// Response payload for manual AI analysis trigger RPC.
/// </summary>
public class ManualAnalysisResponseDto
{
[JsonPropertyName("analysisId")]
public string AnalysisId { get; set; } = string.Empty;
[JsonPropertyName("isTradeProposed")]
public bool IsTradeProposed { get; set; }
[JsonPropertyName("status")]
public string Status { get; set; } = "Success";
[JsonPropertyName("recommendation")]
public string Recommendation { get; set; } = "RECOMMENDED";
[JsonPropertyName("n8nResponse")]
public N8nAnalysisResponseDto? N8nResponse { get; set; }
[JsonPropertyName("proposal")]
public TradeProposalDto? Proposal { get; set; }
[JsonPropertyName("message")]
public string Message { get; set; } = string.Empty;
}
@@ -1,101 +0,0 @@
using System;
using System.Collections.Generic;
namespace FinlyticCore.Models.Analyzer;
public class TargetAssetInfo
{
public string Symbol { get; set; } = string.Empty; // e.g. "AAPL"
public string Name { get; set; } = string.Empty; // e.g. "Apple Inc."
public string Isin { get; set; } = string.Empty;
public string Sector { get; set; } = string.Empty;
}
public class MarketContextInfo
{
public decimal Vix { get; set; }
public string MarketRegime { get; set; } = string.Empty;
}
public class FilterContextInfo
{
public double ImpactScore { get; set; }
public string RawNewsHeadline { get; set; } = string.Empty;
}
public class UserPreferencesInfo
{
public int RiskScore { get; set; } = 50; // 0 to 100
public string RiskTolerance { get; set; } = "Balanced";
public int MinTimeframeValue { get; set; } = 1;
public int MaxTimeframeValue { get; set; } = 7;
public string TimeframeUnit { get; set; } = "Tage"; // "Stunden", "Tage", "Wochen", "Monate"
public string TimeframeFormatted { get; set; } = "1-7 Tage";
public string InstrumentType { get; set; } = "Stock"; // "Stock", "KnockOut", "Option", "CFD", "Future"
public string UserNotes { get; set; } = string.Empty;
}
public class TradeFeedbackInfo
{
public int TotalAssetTrades { get; set; }
public double AssetWinRate { get; set; }
public double AvgReturnPercent { get; set; }
public string LastTradeResult { get; set; } = "NONE"; // "WIN", "LOSS", "NONE"
}
public class PatternContextInfo
{
public string PatternName { get; set; } = string.Empty;
public string? BreakoutDirection { get; set; }
public double? TargetPrice { get; set; }
public double? PotentialPercent { get; set; }
}
public class TechnicalContextInfo
{
public string Rsi { get; set; } = "N/A";
public string SupertrendStatus { get; set; } = "N/A";
public string Atr { get; set; } = "N/A";
public double? Sma50 { get; set; }
public double? Sma200 { get; set; }
public List<PatternContextInfo> DetectedPatterns { get; set; } = new();
}
public class SentimentContextInfo
{
public double AssetSentimentScore { get; set; }
public double SectorSentimentScore { get; set; }
public string NewsSentimentSummary { get; set; } = "Neutral";
}
public class FundamentalContextInfo
{
public double? PeRatio { get; set; }
public double? ForwardPeRatio { get; set; }
public double? PegRatio { get; set; }
public double? MarketCap { get; set; }
public double? DebtToEquity { get; set; }
public double? GrossMargin { get; set; }
public double? NetProfitMargin { get; set; }
public double? ReturnOnEquity { get; set; }
public double? DividendYield { get; set; }
public double? ShortPercentOfFloat { get; set; }
public double? AnalystTargetMedian { get; set; }
public double? EvToEbitda { get; set; }
}
public class N8nAnalysisRequestDto
{
public string RequestId { get; set; } = string.Empty;
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
public string TriggerType { get; set; } = "AutomatedNews"; // "Manual" | "AutomatedNews"
public TargetAssetInfo TargetAsset { get; set; } = new();
public MarketContextInfo MarketContext { get; set; } = new();
public FilterContextInfo FilterContext { get; set; } = new();
public UserPreferencesInfo UserPreferences { get; set; } = new();
public TradeFeedbackInfo TradeFeedback { get; set; } = new();
public TechnicalContextInfo TechnicalContext { get; set; } = new();
public SentimentContextInfo SentimentContext { get; set; } = new();
public FundamentalContextInfo FundamentalContext { get; set; } = new();
}
@@ -1,39 +0,0 @@
using System.Collections.Generic;
namespace FinlyticCore.Models.Analyzer;
public class N8nAnalysisResponseDto
{
public string RequestId { get; set; } = string.Empty;
public double EvalScore { get; set; } // 0.00 to 1.00
public string AiDecision { get; set; } = "Proceed"; // "Proceed" | "Reject" | "Hold"
public string SuggestedDirection { get; set; } = "Long"; // "Long" | "Short"
public string AiReasoning { get; set; } = string.Empty;
public string SuggestedTimeframe { get; set; } = "Intraday"; // "Scalp" | "Intraday" | "Swing"
public string SuggestedRisk { get; set; } = "Medium"; // "Low" | "Medium" | "High"
public ExecutionPlanInfo? ExecutionPlan { get; set; }
public DetailedAnalysisInfo? DetailedAnalysis { get; set; }
}
public class ExecutionPlanInfo
{
public EntryZoneInfo? EntryZone { get; set; }
public decimal StopLoss { get; set; }
public List<decimal>? TakeProfitTargets { get; set; }
public decimal RiskRewardRatio { get; set; }
public decimal MaxLeverage { get; set; }
}
public class EntryZoneInfo
{
public decimal Min { get; set; }
public decimal Max { get; set; }
}
public class DetailedAnalysisInfo
{
public string TechnicalRationale { get; set; } = string.Empty;
public string FundamentalRationale { get; set; } = string.Empty;
public string RiskWarning { get; set; } = string.Empty;
}
-16
View File
@@ -1,16 +0,0 @@
using System;
using System.Threading.Tasks;
using FinlyticCore.Models.Trades;
namespace FinlyticCore.Models.Auth;
/// <summary>
/// Strongly typed SignalR client interface for real-time WebSocket/SSE streaming.
/// </summary>
public interface ITradeClient
{
Task OnTradeProposed(TradeProposalDto proposal);
Task OnTradeUpdated(TradeHourlyUpdateDto update);
Task OnTradeClosed(string tradeId, decimal exitPrice, string reason);
Task OnNewsReceived(object newsItem);
}
@@ -1,22 +0,0 @@
namespace FinlyticCore.Models.Auth;
/// <summary>
/// DTO representing a request for self-registration by a new user.
/// </summary>
public class RegisterRequestDto
{
/// <summary>
/// User email address.
/// </summary>
public string Email { get; set; } = string.Empty;
/// <summary>
/// User plain-text password.
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// User full name.
/// </summary>
public string FullName { get; set; } = string.Empty;
}
+50
View File
@@ -1,3 +1,6 @@
using System;
using Microsoft.Extensions.Configuration;
namespace FinlyticCore.Models;
/// <summary>
@@ -29,4 +32,51 @@ public class MqttConfiguration
/// Gets or sets the password for authentication (optional).
/// </summary>
public string? Password { get; set; }
/// <summary>
/// Builds an <see cref="MqttConfiguration"/> from application configuration, understanding both the
/// colon-separated key style (<c>MQTT:Host</c>, used by <c>appsettings.json</c>) and the double-underscore
/// style (<c>MQTT__Host</c>, used by container environment variables). Every one of the eight service MQTT
/// clients previously duplicated this lookup inline; centralizing it here means a new configuration key
/// (e.g. authentication) only has to be wired up once.
/// </summary>
/// <param name="configuration">The application configuration to read MQTT settings from.</param>
/// <param name="defaultClientId">
/// The service-specific client ID prefix to fall back to when no <c>MQTT:ClientId</c>/<c>MQTT__ClientId</c>
/// is configured (e.g. "FinlyticAssets"). A random suffix is always appended to the resolved client ID
/// (whether it came from configuration or from this default) to avoid the broker rejecting a duplicate
/// client ID when a service reconnects or runs multiple instances.
/// </param>
/// <returns>
/// A populated <see cref="MqttConfiguration"/>. <see cref="Username"/> and <see cref="Password"/> are left
/// <see langword="null"/> unless both are actually configured, so connections to brokers without
/// authentication enabled remain anonymous and continue to work unchanged.
/// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="configuration"/> is <see langword="null"/>.</exception>
public static MqttConfiguration FromConfiguration(IConfiguration configuration, string defaultClientId)
{
ArgumentNullException.ThrowIfNull(configuration);
var host = configuration["MQTT:Host"] ?? configuration["MQTT__Host"] ?? "localhost";
var portRaw = configuration["MQTT:Port"] ?? configuration["MQTT__Port"] ?? "1883";
var port = int.TryParse(portRaw, out var parsedPort) ? parsedPort : 1883;
var configuredClientId = configuration["MQTT:ClientId"] ?? configuration["MQTT__ClientId"];
var clientId = $"{(string.IsNullOrWhiteSpace(configuredClientId) ? defaultClientId : configuredClientId)}_{Guid.NewGuid():N}";
// Optional authentication: only set Username/Password when the broker actually requires them.
// The broker this system currently runs against has no authentication configured, so leaving both
// unset here must keep the connection anonymous (see ManagedMqttClient.ConnectAsync).
var username = configuration["MQTT:Username"] ?? configuration["MQTT__Username"];
var password = configuration["MQTT:Password"] ?? configuration["MQTT__Password"];
return new MqttConfiguration
{
Host = host,
Port = port,
ClientId = clientId,
Username = string.IsNullOrWhiteSpace(username) ? null : username,
Password = string.IsNullOrWhiteSpace(password) ? null : password
};
}
}
@@ -1,9 +0,0 @@
namespace FinlyticCore.Models.Settings;
public enum LogLevelEnum
{
None,
Debug,
Info,
Error
}
@@ -1,15 +0,0 @@
using System;
namespace FinlyticCore.Models.Trades;
/// <summary>
/// Request payload for manually closing an active trade via REST API.
/// </summary>
public class CloseTradeRequest
{
public decimal UserExitPrice { get; set; }
public DateTime? UserExitTimestamp { get; set; }
public decimal ExitFee { get; set; } = 1.0m;
public string CloseReason { get; set; } = "ManualClosure"; // "TakeProfitHit", "StopLossHit", "ManualClosure", "TimeExpired"
}
@@ -1,35 +0,0 @@
using System;
using FinlyticCore.Models.Analyzer;
namespace FinlyticCore.Models.Trades;
/// <summary>
/// Structured closed trade record exported to JSON/Parquet for AI win-rate calibration feedback loops.
/// </summary>
public class TradeFeedbackRecord
{
public string TradeId { get; set; } = string.Empty;
public string AnalysisId { get; set; } = string.Empty;
public string Sector { get; set; } = string.Empty;
public string Symbol { get; set; } = string.Empty;
public string Isin { get; set; } = string.Empty;
public decimal EntryPrice { get; set; }
public decimal StopLoss { get; set; }
public decimal TakeProfit { get; set; }
public decimal UserExitPrice { get; set; }
public decimal PnlAbsolute { get; set; }
public decimal PnlPercent { get; set; }
public bool IsWin { get; set; }
public string CloseReason { get; set; } = string.Empty;
public VixMarketRegime VixRegime { get; set; }
public decimal VixValue { get; set; }
public double ReactionDelayMinutes { get; set; }
public decimal SlippagePercent { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ClosedAt { get; set; }
}
@@ -1,170 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using FinlyticCore.Models.Analyzer;
namespace FinlyticCore.Models.Trades;
/// <summary>
/// Trade proposal generated by FinlyticAnalyzer and dispatched via MQTT QoS 2.
/// </summary>
public class TradeProposalDto
{
[JsonPropertyName("tradeId")]
public string TradeId { get; set; } = string.Empty;
[JsonPropertyName("userId")]
public string? UserId { get; set; }
[JsonPropertyName("isGlobalProposal")]
public bool IsGlobalProposal { get; set; } = true;
[JsonPropertyName("status")]
public string Status { get; set; } = "Proposed";
[JsonPropertyName("analysisId")]
public string AnalysisId { get; set; } = string.Empty;
[JsonPropertyName("eventId")]
public string EventId { get; set; } = string.Empty;
[JsonPropertyName("sector")]
public string Sector { get; set; } = string.Empty;
[JsonPropertyName("symbol")]
public string Symbol { get; set; } = string.Empty;
[JsonPropertyName("isin")]
public string Isin { get; set; } = string.Empty;
[JsonPropertyName("companyName")]
public string CompanyName { get; set; } = string.Empty;
[JsonPropertyName("entryPrice")]
public decimal EntryPrice { get; set; }
[JsonPropertyName("stopLoss")]
public decimal StopLoss { get; set; }
[JsonPropertyName("takeProfit")]
public decimal TakeProfit { get; set; }
[JsonPropertyName("signalType")]
public string SignalType { get; set; } = "BUY"; // "BUY", "SELL"
[JsonPropertyName("riskTolerance")]
public string RiskTolerance { get; set; } = "Moderate"; // "Conservative", "Moderate", "Aggressive"
[JsonPropertyName("timeframe")]
public string Timeframe { get; set; } = "1D"; // "1H", "4H", "1D", "1W"
[JsonPropertyName("instrumentType")]
public string InstrumentType { get; set; } = "Stock"; // "Stock", "Option", "CFD", "Crypto"
[JsonPropertyName("assetType")]
public string AssetType { get; set; } = "stock"; // "stock", "etf", "crypto", "bond"
[JsonPropertyName("hasCfd")]
public bool HasCfd { get; set; }
[JsonPropertyName("derivativeProductCategories")]
public List<string> DerivativeProductCategories { get; set; } = new();
[JsonPropertyName("derivativeIsin")]
public string? DerivativeIsin { get; set; }
[JsonPropertyName("winRate")]
public double WinRate { get; set; }
[JsonPropertyName("vixRegime")]
public VixMarketRegime VixRegime { get; set; }
[JsonPropertyName("vixValue")]
public decimal VixValue { get; set; }
[JsonPropertyName("ttlMinutes")]
public int TtlMinutes { get; set; } = 60;
[JsonPropertyName("reasoning")]
public string Reasoning { get; set; } = string.Empty;
// --- New Fields for Detailed Execution & Rationale ---
[JsonPropertyName("entryZoneMin")]
public decimal? EntryZoneMin { get; set; }
[JsonPropertyName("entryZoneMax")]
public decimal? EntryZoneMax { get; set; }
[JsonPropertyName("takeProfitTargets")]
public List<decimal>? TakeProfitTargets { get; set; }
[JsonPropertyName("riskRewardRatio")]
public decimal? RiskRewardRatio { get; set; }
[JsonPropertyName("maxLeverage")]
public decimal? MaxLeverage { get; set; }
[JsonPropertyName("technicalRationale")]
public string TechnicalRationale { get; set; } = string.Empty;
[JsonPropertyName("fundamentalRationale")]
public string FundamentalRationale { get; set; } = string.Empty;
[JsonPropertyName("riskWarning")]
public string RiskWarning { get; set; } = string.Empty;
// --- Real Trade Execution Data ---
[JsonPropertyName("actualEntryPrice")]
public decimal? ActualEntryPrice { get; set; }
[JsonPropertyName("positionSize")]
public decimal? PositionSize { get; set; }
[JsonPropertyName("leverageUsed")]
public decimal? LeverageUsed { get; set; }
[JsonPropertyName("entryFee")]
public decimal? EntryFee { get; set; }
[JsonPropertyName("exitFee")]
public decimal? ExitFee { get; set; }
[JsonPropertyName("executionTimestamp")]
public DateTime? ExecutionTimestamp { get; set; }
[JsonPropertyName("quantity")]
public decimal? Quantity { get; set; }
[JsonPropertyName("knockoutThreshold")]
public decimal? KnockoutThreshold { get; set; }
[JsonPropertyName("isRecurring")]
public bool IsRecurring { get; set; } = false;
[JsonPropertyName("currentPrice")]
public decimal? CurrentPrice { get; set; }
[JsonPropertyName("pnlAbsolute")]
public decimal? PnlAbsolute { get; set; }
[JsonPropertyName("pnlPercent")]
public decimal? PnlPercent { get; set; }
[JsonPropertyName("closeReason")]
public string? CloseReason { get; set; }
[JsonPropertyName("userExitTimestamp")]
public DateTime? UserExitTimestamp { get; set; }
[JsonPropertyName("hasPendingExitAlert")]
public bool HasPendingExitAlert { get; set; } = false;
[JsonPropertyName("pendingExitReason")]
public string? PendingExitReason { get; set; }
[JsonPropertyName("hourlyUpdates")]
public List<TradeHourlyUpdateDto>? HourlyUpdates { get; set; }
[JsonPropertyName("createdAt")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
-14
View File
@@ -1,14 +0,0 @@
namespace FinlyticCore.Models.Trades;
/// <summary>
/// Status of a proposed/active trade lifecycle.
/// </summary>
public enum TradeStatus
{
Proposed = 0,
Active = 1,
Closed = 2,
Expired = 3,
Rejected = 4,
Invalidated = 5
}