114 lines
5.1 KiB
C#
114 lines
5.1 KiB
C#
using System;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.ComponentModel.DataAnnotations.Schema;
|
|
using FinlyticCore.Dtos.TechnicalAnalysis;
|
|
using FinlyticCore.Dtos.Trading;
|
|
|
|
namespace FinlyticEngine.Database.Entities;
|
|
|
|
/// <summary>
|
|
/// Persists the full outcome of a single <c>TradeLifecycleService.EvaluateAssetAsync</c> run - one row per
|
|
/// evaluated asset, whether or not it produced a trade proposal. This is the append-only audit trail the
|
|
/// admin-only "why no proposals" Web UI tab (<c>AdminEvaluationHistoryController</c>) reads from via
|
|
/// <c>MqttTopics.Channels.EngineGetEvaluationHistory</c>.
|
|
/// </summary>
|
|
[Table("engine_evaluation_snapshots")]
|
|
public class EngineEvaluationSnapshotEntity
|
|
{
|
|
[Key]
|
|
public Guid Id { get; set; } = Guid.NewGuid();
|
|
|
|
[Required]
|
|
[MaxLength(20)]
|
|
public string Isin { get; set; } = string.Empty;
|
|
|
|
[MaxLength(30)]
|
|
public string Symbol { get; set; } = string.Empty;
|
|
|
|
[Column(TypeName = "decimal(6,2)")]
|
|
public decimal TechnicalScore { get; set; }
|
|
|
|
[Column(TypeName = "decimal(6,2)")]
|
|
public decimal SentimentScore { get; set; }
|
|
|
|
[Column(TypeName = "decimal(6,2)")]
|
|
public decimal FundamentalScore { get; set; }
|
|
|
|
[Column(TypeName = "decimal(6,2)")]
|
|
public decimal CompositeOpportunityScore { get; set; }
|
|
|
|
/// <summary>
|
|
/// Bonus points <c>CompositeOpportunityScorer</c> added to the raw weighted score based on
|
|
/// FinlyticSimulation's backtest-reliability matrix (see <c>ScoringResult.ReliabilityBonus</c>). Always
|
|
/// <c>0</c> when no reliability data was available or no bonus applied - never fabricated (Rules.md §4).
|
|
/// </summary>
|
|
[Column(TypeName = "decimal(6,2)")]
|
|
public decimal ReliabilityBonus { get; set; }
|
|
|
|
public bool PassedEarningsLockout { get; set; }
|
|
|
|
public int? DaysToNextEarnings { get; set; }
|
|
|
|
/// <summary>Whether the ex-dividend gate (<c>Engine.DividendGateDays</c>) passed. See <see cref="DaysToNextExDividend"/>.</summary>
|
|
public bool PassedDividendGate { get; set; } = true;
|
|
|
|
public int? DaysToNextExDividend { get; set; }
|
|
|
|
/// <summary>
|
|
/// Which FinlyticTechnicals universe-selection mechanism was responsible for this ISIN being scanned in
|
|
/// the first place (favorite/discovery/sentiment-spike), captured from
|
|
/// <c>StrategyResultDto.UniverseSource</c> at evaluation time. <see langword="null"/> when the evaluated
|
|
/// setup did not originate from FinlyticTechnicals' continuously-scanned universe (e.g. a manual "Analyze
|
|
/// now" call for an ISIN nobody favorited/discovered/spiked) - never a fabricated guess (Rules.md §4).
|
|
/// </summary>
|
|
public UniverseSource? UniverseSource { get; set; }
|
|
|
|
/// <summary>When the ISIN above entered that scan universe, alongside <see cref="UniverseSource"/>.</summary>
|
|
public DateTime? UniverseEnteredAtUtc { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether FinlyticSimulation's backtest-reliability matrix vetoed this strategy/asset combination (see
|
|
/// <c>ScoringResult.PassedSimulationVeto</c>). Defaults to <see langword="true"/> (matching
|
|
/// <c>ScoringResult</c>'s own default) so a row where this gate was never actually evaluated - e.g. the
|
|
/// <see cref="OutcomeReason.NoTechnicalSetups"/> early-return case - never reads as "vetoed".
|
|
/// </summary>
|
|
public bool PassedSimulationVeto { get; set; } = true;
|
|
|
|
public bool PassedAiValidation { get; set; }
|
|
|
|
[MaxLength(2048)]
|
|
public string AiThesisSummary { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Whether this evaluation was fired by the autonomous <c>OpportunityPollerBackgroundService</c> scan loop
|
|
/// or by an on-demand human request. See <see cref="TriggerSource"/> for why <see cref="TriggerSource.Unknown"/>
|
|
/// (not <see cref="TriggerSource.Automatic"/>) is the default/zero value.
|
|
/// </summary>
|
|
public TriggerSource TriggerSource { get; set; } = TriggerSource.Unknown;
|
|
|
|
/// <summary>
|
|
/// Identity of the human caller who triggered this evaluation, resolved server-side from the JWT in
|
|
/// FinlyticBackend. Only ever set when <see cref="TriggerSource"/> is <see cref="TriggerSource.Manual"/> -
|
|
/// the autonomous scanner never carries a user identity, so this stays <see langword="null"/> for every
|
|
/// <see cref="TriggerSource.Automatic"/> row.
|
|
/// </summary>
|
|
public Guid? TriggeredByUserId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Classifies why this evaluation did or did not produce a proposal. See
|
|
/// <c>TradeLifecycleService.DetermineOutcomeReason</c> for the exact priority order used when multiple
|
|
/// gates failed at once.
|
|
/// </summary>
|
|
public OutcomeReason OutcomeReason { get; set; } = OutcomeReason.Unknown;
|
|
|
|
/// <summary>
|
|
/// The <c>EngineTradeProposalEntity.Id</c> created by this evaluation, set if and only if
|
|
/// <see cref="OutcomeReason"/> is <see cref="OutcomeReason.Approved"/>. <see langword="null"/> for every
|
|
/// rejected/no-setup evaluation - a proposal was never fabricated for those (Rules.md §4).
|
|
/// </summary>
|
|
public Guid? ProposalId { get; set; }
|
|
|
|
[Required]
|
|
public DateTime EvaluatedAtUtc { get; set; } = DateTime.UtcNow;
|
|
}
|