feat(engine): add FinlyticEngine microservice with trade lifecycle, AI reasoning gate, composite scoring, and unit tests

This commit is contained in:
2026-08-24 21:37:05 +02:00
parent a4959658a2
commit 5c95dd182c
49 changed files with 7709 additions and 0 deletions
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
namespace FinlyticEngine.Services.Ai;
/// <summary>
/// Outgoing request body for the n8n AI trade-validation webhook. Service-local (not FinlyticCore): this
/// shape is an integration detail of <see cref="AiReasoningGateService"/> only, not a cross-service MQTT
/// contract (Rules.md §3 - core placement applies to data shared across services, not to a single service's
/// own outbound HTTP integration). Serialized with <c>JsonNamingPolicy.CamelCase</c>, so every property here
/// reaches n8n as camelCase without needing per-property <c>[JsonPropertyName]</c> attributes.
/// </summary>
public record N8nValidationRequestPayload(
string Instructions,
N8nAssetSection Asset,
N8nTechnicalSetupSection TechnicalSetup,
N8nWatchlistContextSection? WatchlistContext,
N8nSentimentSection Sentiment,
N8nFundamentalsSection Fundamentals,
N8nReliabilitySection? Reliability,
N8nScoreBreakdownSection ScoreBreakdown
);
public record N8nAssetSection(string Isin, string Symbol, decimal CurrentPrice);
public record N8nPatternSection(string Type, string Bias, decimal QualityScore, string Description);
public record N8nTechnicalSetupSection(
string Strategy,
string StrategyName,
string Direction,
decimal Entry,
decimal StopLoss,
decimal TakeProfit1,
decimal RiskRewardRatio,
decimal QualityScore,
string Rationale,
string? MarketRegime,
List<N8nPatternSection> TriggeringPatterns,
Dictionary<string, decimal> IndicatorSnapshot
);
/// <summary>Why FinlyticTechnicals was even scanning this ISIN (see <c>TechnicalUniverseManager</c>).</summary>
public record N8nWatchlistContextSection(string Source, DateTime EnteredAtUtc);
public record N8nSentimentSection(string Label, double WeightedScore, string Trend, string LatestHighlight);
/// <summary>
/// Fundamental data + the two temporal suppression gates (<see cref="Scoring.ScoringResult.PassedEarningsLockout"/>/
/// <see cref="Scoring.ScoringResult.PassedDividendGate"/>). Ratio fields (<c>ReturnOnEquityRatio</c> etc.) are
/// forwarded as raw fractions (e.g. <c>0.15</c> = 15%) exactly as stored, rather than guessing a ×100
/// conversion that could silently misrepresent the source data (Rules.md §4). <see cref="OperatingMarginPercent"/>
/// is the one exception: an honest, explicitly computed ratio (OperatingIncome / TotalRevenue × 100), only
/// populated when both inputs are real numbers.
/// </summary>
public record N8nFundamentalsSection(
decimal? ForwardPe,
decimal? OperatingMarginPercent,
decimal? RevenueGrowthYoYRatio,
decimal? ReturnOnEquityRatio,
decimal? DebtToEquity,
decimal? FreeCashFlow,
string? ConsensusRating,
decimal? PriceTargetMean,
decimal? ShortPercentOfFloatRatio,
int? DaysToEarnings,
bool PassedEarningsLockout,
int? DaysToNextExDividend,
bool PassedDividendGate
);
/// <summary>FinlyticSimulation's backtest verdict for this exact (Isin, StrategyKey) - see <see cref="IAiReasoningGateService"/>.</summary>
public record N8nReliabilitySection(
decimal ReliabilityScore,
decimal WinRatePercent,
decimal ProfitFactor,
int SampleTradeCount,
bool IsStrategyApprovedForAsset,
string RecommendedAction
);
public record N8nScoreBreakdownSection(
decimal CompositeScore,
decimal TechnicalScore,
decimal SentimentScore,
decimal FundamentalScore,
decimal ReliabilityBonus
);
/// <summary>
/// Expected shape of a successful n8n webhook response - deliberately identical field-for-field to
/// <see cref="FinlyticCore.Dtos.Trading.AiValidationResultDto"/> (camelCase JSON) instead of the previous,
/// undocumented ad hoc vocabulary (<c>status</c>/<c>action_recommendation</c>/<c>raw_validation_result.*</c>)
/// that no prompt ever actually specified. <see cref="Confidence"/> is nullable because a validator that
/// declines to give a numeric confidence must not have one fabricated for it (Rules.md §4).
/// </summary>
public record N8nValidationResponsePayload(
bool? IsApproved,
decimal? Confidence,
string? ThesisSummary,
string? InvalidationReason,
List<string>? KeyCatalysts,
List<string>? IdentifiedRisks
);