feat(core): add shared DTOs, MqttTopics constants, DatabaseBootstrapper, and ManagedMqttClient extensions
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using FinlyticCore.Dtos.TechnicalAnalysis;
|
||||
|
||||
namespace FinlyticCore.Dtos.Trading;
|
||||
|
||||
public record DerivativeSelectionDto(
|
||||
[property: JsonPropertyName("derivativeIsin")] string DerivativeIsin,
|
||||
// Trade Republic liefert für Derivate keine WKN, nur die ISIN (siehe TradeRepublicDerivativeItemDto).
|
||||
// Daher ist dieses Feld nullable: eine ISIN darf hier NICHT als Ersatz-WKN eingetragen werden (Rules.md §4).
|
||||
[property: JsonPropertyName("derivativeWkn")] string? DerivativeWkn,
|
||||
[property: JsonPropertyName("issuer")] string Issuer,
|
||||
[property: JsonPropertyName("optionType")] string OptionType, // "LONG" oder "SHORT"
|
||||
[property: JsonPropertyName("strike")] decimal Strike,
|
||||
[property: JsonPropertyName("barrier")] decimal Barrier,
|
||||
[property: JsonPropertyName("leverage")] decimal Leverage,
|
||||
[property: JsonPropertyName("safetyBufferPercent")] decimal SafetyBufferPercent,
|
||||
[property: JsonPropertyName("spreadPercentage")] decimal SpreadPercentage,
|
||||
[property: JsonPropertyName("size")] decimal Size
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Kennzeichnet die Herkunft einer <see cref="AiValidationResultDto"/>-Entscheidung, damit
|
||||
/// Konsumenten (Frontend, Logs) eine echte KI-Analyse von einer regelbasierten Ersatzentscheidung
|
||||
/// unterscheiden können. Der Enum-Wert <see cref="Ai"/> ist absichtlich der Default (0), damit ein
|
||||
/// vom N8N-Webhook geliefertes JSON, das dieses Feld (noch) nicht setzt, korrekt als KI-Ergebnis
|
||||
/// interpretiert wird.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<ValidationSource>))]
|
||||
public enum ValidationSource
|
||||
{
|
||||
Ai,
|
||||
RuleBased
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ergebnis des AI-Reasoning-Gates. <see cref="Confidence"/> ist nur gesetzt, wenn <see cref="Source"/>
|
||||
/// den Wert <see cref="ValidationSource.Ai"/> hat, da eine Konfidenz ohne tatsächliche KI-Bewertung
|
||||
/// erfunden wäre (Rules.md §4).
|
||||
/// </summary>
|
||||
public record AiValidationResultDto(
|
||||
[property: JsonPropertyName("isApproved")] bool IsApproved,
|
||||
[property: JsonPropertyName("confidence")] decimal? Confidence,
|
||||
[property: JsonPropertyName("validationSource")] ValidationSource Source,
|
||||
[property: JsonPropertyName("thesisSummary")] string ThesisSummary,
|
||||
[property: JsonPropertyName("invalidationReason")] string InvalidationReason,
|
||||
[property: JsonPropertyName("keyCatalysts")] List<string> KeyCatalysts,
|
||||
[property: JsonPropertyName("identifiedRisks")] List<string> IdentifiedRisks
|
||||
);
|
||||
|
||||
public record TradeProposalDto(
|
||||
[property: JsonPropertyName("proposalId")] Guid ProposalId,
|
||||
[property: JsonPropertyName("underlyingIsin")] string UnderlyingIsin,
|
||||
[property: JsonPropertyName("symbol")] string Symbol,
|
||||
[property: JsonPropertyName("strategyKey")] string StrategyKey,
|
||||
[property: JsonPropertyName("direction")] SignalDirection Direction,
|
||||
[property: JsonPropertyName("qualityScore")] decimal QualityScore,
|
||||
[property: JsonPropertyName("compositeScore")] decimal CompositeScore,
|
||||
[property: JsonPropertyName("currentPrice")] decimal CurrentPrice,
|
||||
[property: JsonPropertyName("entryPrice")] decimal EntryPrice,
|
||||
[property: JsonPropertyName("invalidationPrice")] decimal InvalidationPrice,
|
||||
[property: JsonPropertyName("exitPlan")] ExitPlan ExitPlan,
|
||||
[property: JsonPropertyName("selectedDerivative")] DerivativeSelectionDto? SelectedDerivative,
|
||||
[property: JsonPropertyName("aiValidation")] AiValidationResultDto AiValidation,
|
||||
[property: JsonPropertyName("createdAtUtc")] DateTime CreatedAtUtc,
|
||||
[property: JsonPropertyName("expiresAtUtc")] DateTime ExpiresAtUtc
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Full result of <c>ITradeLifecycleService.EvaluateAssetAsync</c>, carrying both possible outcomes of the
|
||||
/// evaluation pipeline (technicals, sentiment, fundamentals, simulation-reliability, AI reasoning gate):
|
||||
/// an accepted opportunity (<see cref="Proposal"/> is set) or a rejection, in which case <see cref="Proposal"/>
|
||||
/// is <see langword="null"/> but every score/reasoning field below is still populated with the real,
|
||||
/// already-computed values instead of leaving the caller with silence (Rules.md §4).
|
||||
/// <para>
|
||||
/// When the pipeline could not even produce a score (no technical setups available for the ISIN, or the
|
||||
/// ISIN was blank), the score fields are <c>0</c> and <see cref="AiThesisSummary"/> carries a
|
||||
/// "<c>[Regelbasiert]</c>"-prefixed explanation — the same prefix <see cref="AiValidationResultDto"/> uses for
|
||||
/// its <see cref="ValidationSource.RuleBased"/> fallback — so a caller/UI can recognize this is not a real
|
||||
/// AI verdict, just as it already must for a rule-based <see cref="AiValidationResultDto"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public record AssetEvaluationResultDto(
|
||||
[property: JsonPropertyName("proposal")] TradeProposalDto? Proposal,
|
||||
[property: JsonPropertyName("compositeScore")] decimal CompositeScore,
|
||||
[property: JsonPropertyName("technicalScore")] decimal TechnicalScore,
|
||||
[property: JsonPropertyName("sentimentScore")] decimal SentimentScore,
|
||||
[property: JsonPropertyName("fundamentalScore")] decimal FundamentalScore,
|
||||
[property: JsonPropertyName("passedEarningsLockout")] bool PassedEarningsLockout,
|
||||
[property: JsonPropertyName("daysToNextEarnings")] int? DaysToNextEarnings,
|
||||
[property: JsonPropertyName("passedDividendGate")] bool PassedDividendGate,
|
||||
[property: JsonPropertyName("daysToNextExDividend")] int? DaysToNextExDividend,
|
||||
[property: JsonPropertyName("aiApproved")] bool AiApproved,
|
||||
[property: JsonPropertyName("aiThesisSummary")] string AiThesisSummary,
|
||||
[property: JsonPropertyName("aiIdentifiedRisks")] List<string> AiIdentifiedRisks
|
||||
);
|
||||
|
||||
public record TradeFillDto(
|
||||
[property: JsonPropertyName("fillId")] Guid FillId,
|
||||
[property: JsonPropertyName("executedAtUtc")] DateTime ExecutedAtUtc,
|
||||
[property: JsonPropertyName("price")] decimal Price,
|
||||
[property: JsonPropertyName("quantity")] decimal Quantity,
|
||||
[property: JsonPropertyName("fee")] decimal Fee,
|
||||
[property: JsonPropertyName("note")] string? Note
|
||||
);
|
||||
|
||||
public record ActiveTradeDto(
|
||||
[property: JsonPropertyName("tradeId")] Guid TradeId,
|
||||
[property: JsonPropertyName("proposalId")] Guid ProposalId,
|
||||
[property: JsonPropertyName("underlyingIsin")] string UnderlyingIsin,
|
||||
[property: JsonPropertyName("symbol")] string Symbol,
|
||||
[property: JsonPropertyName("derivativeIsin")] string? DerivativeIsin,
|
||||
[property: JsonPropertyName("derivativeWkn")] string? DerivativeWkn,
|
||||
[property: JsonPropertyName("executionMode")] ExecutionMode ExecutionMode,
|
||||
[property: JsonPropertyName("instrumentType")] InstrumentCategoryType InstrumentType,
|
||||
[property: JsonPropertyName("direction")] SignalDirection Direction,
|
||||
[property: JsonPropertyName("status")] TradeStatus Status,
|
||||
[property: JsonPropertyName("averageBuyIn")] decimal AverageBuyIn,
|
||||
[property: JsonPropertyName("totalQuantity")] decimal TotalQuantity,
|
||||
[property: JsonPropertyName("initialStopLoss")] decimal InitialStopLoss,
|
||||
[property: JsonPropertyName("currentStopLoss")] decimal CurrentStopLoss,
|
||||
[property: JsonPropertyName("currentPrice")] decimal CurrentPrice,
|
||||
[property: JsonPropertyName("unrealizedPnlEur")] decimal UnrealizedPnlEur,
|
||||
[property: JsonPropertyName("unrealizedPnlPercent")] decimal UnrealizedPnlPercent,
|
||||
[property: JsonPropertyName("realizedPnlEur")] decimal RealizedPnlEur,
|
||||
[property: JsonPropertyName("exitPlan")] ExitPlan ExitPlan,
|
||||
[property: JsonPropertyName("fills")] List<TradeFillDto> Fills,
|
||||
[property: JsonPropertyName("openedAtUtc")] DateTime OpenedAtUtc,
|
||||
[property: JsonPropertyName("closedAtUtc")] DateTime? ClosedAtUtc
|
||||
);
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using FinlyticCore.Dtos.TechnicalAnalysis;
|
||||
|
||||
namespace FinlyticCore.Dtos.Trading;
|
||||
|
||||
/// <summary>
|
||||
/// Filter/pagination request for the admin-only evaluation-history RPC channel
|
||||
/// (<c>MqttTopics.Channels.EngineGetEvaluationHistory</c>), served by FinlyticEngine and exposed to the Web UI
|
||||
/// via <c>FinlyticBackend/Controllers/AdminEvaluationHistoryController</c>. All filters are optional and are
|
||||
/// combined with logical AND; <see langword="null"/> means "do not filter on this field".
|
||||
/// </summary>
|
||||
/// <param name="FromUtc">Inclusive lower bound on <c>EngineEvaluationSnapshotEntity.EvaluatedAtUtc</c>.</param>
|
||||
/// <param name="ToUtc">Inclusive upper bound on <c>EngineEvaluationSnapshotEntity.EvaluatedAtUtc</c>.</param>
|
||||
/// <param name="OutcomeFilter">Restricts results to a single <see cref="OutcomeReason"/>.</param>
|
||||
/// <param name="TriggerSourceFilter">Restricts results to a single <see cref="TriggerSource"/>.</param>
|
||||
/// <param name="IsinOrSymbolSearch">
|
||||
/// Case-sensitive substring search against both <c>Isin</c> and <c>Symbol</c> (matches either). Trimmed
|
||||
/// server-side; blank/whitespace-only values are treated as "no search".
|
||||
/// </param>
|
||||
/// <param name="Page">1-based page number. Values below 1 are treated as 1 server-side.</param>
|
||||
/// <param name="PageSize">
|
||||
/// Requested page size. Server-side clamped to at least 1 and at most 200 (see
|
||||
/// <c>EvaluationHistoryService.MaxPageSize</c>) so a caller cannot force FinlyticEngine to materialize/transmit
|
||||
/// an unbounded result set in a single response.
|
||||
/// </param>
|
||||
public record GetEvaluationHistoryRequest(
|
||||
[property: JsonPropertyName("fromUtc")] System.DateTime? FromUtc = null,
|
||||
[property: JsonPropertyName("toUtc")] System.DateTime? ToUtc = null,
|
||||
[property: JsonPropertyName("outcomeFilter")] OutcomeReason? OutcomeFilter = null,
|
||||
[property: JsonPropertyName("triggerSourceFilter")] TriggerSource? TriggerSourceFilter = null,
|
||||
[property: JsonPropertyName("isinOrSymbolSearch")] string? IsinOrSymbolSearch = null,
|
||||
[property: JsonPropertyName("page")] int Page = 1,
|
||||
[property: JsonPropertyName("pageSize")] int PageSize = 50
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// One row of the evaluation history: the full, already-persisted record of a single
|
||||
/// <c>TradeLifecycleService.EvaluateAssetAsync</c> run, mapped 1:1 from <c>EngineEvaluationSnapshotEntity</c>.
|
||||
/// Every score field is the real, already-computed value - including the honest "0/default" values recorded
|
||||
/// for the <see cref="OutcomeReason.NoTechnicalSetups"/> case, never a fabricated placeholder (Rules.md §4).
|
||||
/// </summary>
|
||||
public record EvaluationHistoryEntryDto(
|
||||
[property: JsonPropertyName("id")] System.Guid Id,
|
||||
[property: JsonPropertyName("isin")] string Isin,
|
||||
[property: JsonPropertyName("symbol")] string Symbol,
|
||||
[property: JsonPropertyName("technicalScore")] decimal TechnicalScore,
|
||||
[property: JsonPropertyName("sentimentScore")] decimal SentimentScore,
|
||||
[property: JsonPropertyName("fundamentalScore")] decimal FundamentalScore,
|
||||
[property: JsonPropertyName("compositeOpportunityScore")] decimal CompositeOpportunityScore,
|
||||
[property: JsonPropertyName("reliabilityBonus")] decimal ReliabilityBonus,
|
||||
[property: JsonPropertyName("passedEarningsLockout")] bool PassedEarningsLockout,
|
||||
[property: JsonPropertyName("daysToNextEarnings")] int? DaysToNextEarnings,
|
||||
[property: JsonPropertyName("passedDividendGate")] bool PassedDividendGate,
|
||||
[property: JsonPropertyName("daysToNextExDividend")] int? DaysToNextExDividend,
|
||||
[property: JsonPropertyName("universeSource")] UniverseSource? UniverseSource,
|
||||
[property: JsonPropertyName("universeEnteredAtUtc")] System.DateTime? UniverseEnteredAtUtc,
|
||||
[property: JsonPropertyName("passedSimulationVeto")] bool PassedSimulationVeto,
|
||||
[property: JsonPropertyName("passedAiValidation")] bool PassedAiValidation,
|
||||
[property: JsonPropertyName("aiThesisSummary")] string AiThesisSummary,
|
||||
[property: JsonPropertyName("outcomeReason")] OutcomeReason OutcomeReason,
|
||||
[property: JsonPropertyName("triggerSource")] TriggerSource TriggerSource,
|
||||
[property: JsonPropertyName("triggeredByUserId")] System.Guid? TriggeredByUserId,
|
||||
[property: JsonPropertyName("proposalId")] System.Guid? ProposalId,
|
||||
[property: JsonPropertyName("evaluatedAtUtc")] System.DateTime EvaluatedAtUtc
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Number of evaluation-history rows matching a given filter set that carry a specific <see cref="OutcomeReason"/>.
|
||||
/// A typed list of these (rather than a <c>Dictionary<OutcomeReason,int></c>) is used on
|
||||
/// <see cref="EvaluationHistorySummaryDto.CountsByOutcome"/> purely so this DTO round-trips through
|
||||
/// System.Text.Json (including the AOT source-generated <c>FinlyticJsonSerializerContext</c>) without needing a
|
||||
/// custom enum-keyed dictionary converter.
|
||||
/// </summary>
|
||||
public record OutcomeReasonCountDto(
|
||||
[property: JsonPropertyName("outcomeReason")] OutcomeReason OutcomeReason,
|
||||
[property: JsonPropertyName("count")] int Count
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Pre-aggregated headline numbers for the admin evaluation-history tab (e.g. "1.847 Analysen letzte 24h ·
|
||||
/// 0 Vorschläge seit 14h · Ø-Score 66,7"), computed server-side so the Web UI never has to aggregate the full,
|
||||
/// unpaginated result set itself. Every field except <see cref="LastProposalCreatedAtUtc"/> is scoped to
|
||||
/// exactly the same filters as the paginated <see cref="EvaluationHistoryEntryDto"/> list it accompanies (see
|
||||
/// <see cref="GetEvaluationHistoryResponse"/>) - only pagination (<c>Page</c>/<c>PageSize</c>) does not apply,
|
||||
/// since these are totals over the whole filtered set, not just the current page.
|
||||
/// </summary>
|
||||
/// <param name="TotalEvaluations">Total number of snapshot rows matching the request's filters (unpaginated).</param>
|
||||
/// <param name="CountsByOutcome">Breakdown of <see cref="TotalEvaluations"/> by <see cref="OutcomeReason"/>.</param>
|
||||
/// <param name="AverageCompositeScore">
|
||||
/// Average <c>CompositeOpportunityScore</c> across the filtered set; <c>0</c> when <see cref="TotalEvaluations"/> is 0.
|
||||
/// </param>
|
||||
/// <param name="ProposalsCreated">
|
||||
/// Number of filtered rows whose <see cref="EvaluationHistoryEntryDto.OutcomeReason"/> is
|
||||
/// <see cref="OutcomeReason.Approved"/> - i.e. the same value as the <see cref="OutcomeReason.Approved"/> entry
|
||||
/// in <see cref="CountsByOutcome"/>, exposed directly so the UI does not need to search that list.
|
||||
/// </param>
|
||||
/// <param name="LastProposalCreatedAtUtc">
|
||||
/// Timestamp of the most recently created <c>EngineTradeProposalEntity</c> across the ENTIRE proposals table -
|
||||
/// deliberately NOT scoped to this request's <c>FromUtc</c>/<c>ToUtc</c> filters, because "how long since the
|
||||
/// last real proposal" is a single wall-clock fact the admin wants regardless of which historical window they
|
||||
/// are currently browsing. <see langword="null"/> only if no proposal has ever been created.
|
||||
/// </param>
|
||||
public record EvaluationHistorySummaryDto(
|
||||
[property: JsonPropertyName("totalEvaluations")] int TotalEvaluations,
|
||||
[property: JsonPropertyName("countsByOutcome")] List<OutcomeReasonCountDto> CountsByOutcome,
|
||||
[property: JsonPropertyName("averageCompositeScore")] decimal AverageCompositeScore,
|
||||
[property: JsonPropertyName("proposalsCreated")] int ProposalsCreated,
|
||||
[property: JsonPropertyName("lastProposalCreatedAtUtc")] System.DateTime? LastProposalCreatedAtUtc
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Full response for the evaluation-history RPC channel: a page of matching rows, the total match count (for
|
||||
/// pagination), and a pre-aggregated <see cref="Summary"/> so the Web UI never needs a second round trip (and a
|
||||
/// second, potentially-inconsistent set of filters) just to render a header line above the table.
|
||||
/// </summary>
|
||||
public record GetEvaluationHistoryResponse(
|
||||
[property: JsonPropertyName("totalCount")] int TotalCount,
|
||||
[property: JsonPropertyName("entries")] List<EvaluationHistoryEntryDto> Entries,
|
||||
[property: JsonPropertyName("summary")] EvaluationHistorySummaryDto Summary
|
||||
);
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace FinlyticCore.Dtos.Trading;
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<ExecutionMode>))]
|
||||
public enum ExecutionMode
|
||||
{
|
||||
SignalProposal, // Reines Signal zur manuellen Ansicht
|
||||
ManualTradeRepublic, // Händisch bei Trade Republic ausgeführt
|
||||
PaperTradingBot // Vollautomatisch im Paper-Trading-Modus
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<TradeStatus>))]
|
||||
public enum TradeStatus
|
||||
{
|
||||
Proposed, // KI-geprüfter Vorschlag, wartet auf Ausführung
|
||||
Active, // Mindestens 1 Fill ausgeführt, Trade läuft
|
||||
BreakEvenTriggered, // Kurs hat TP1 erreicht, SL liegt auf Mischkurs
|
||||
Tp1Hit, // Teilverkauf 1 ausgeführt
|
||||
Tp2Hit, // Teilverkauf 2 ausgeführt
|
||||
Closed, // Vollständig mit Gewinn glattgestellt
|
||||
StoppedOut, // Durch Stop-Loss beendet
|
||||
Invalidated, // Kurs hat Invalidation erreicht, bevor Einstieg erfolgte
|
||||
Expired // Gültigkeitsfenster abgelaufen
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<InstrumentCategoryType>))]
|
||||
public enum InstrumentCategoryType
|
||||
{
|
||||
Stock,
|
||||
Etf,
|
||||
TurboLong,
|
||||
TurboShort,
|
||||
FactorCertificate
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies whether an <c>EngineEvaluationSnapshotEntity</c> row was produced by the autonomous
|
||||
/// <c>OpportunityPollerBackgroundService</c> scan loop or by an on-demand, human-initiated call (Web UI
|
||||
/// "Analyze now" / <c>EngineController.EvaluateAsset</c> / <c>AnalyzeController.TriggerManualAnalysis</c>).
|
||||
/// <see cref="Unknown"/> is deliberately value <c>0</c> (the default) so that snapshot rows written before
|
||||
/// this field existed - and any future row where the caller genuinely failed to specify a source - are never
|
||||
/// silently mis-reported as one of the two real sources (Rules.md §4: no fabricated data, an honest
|
||||
/// "we don't know" beats a fabricated default of <see cref="Automatic"/>).
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<TriggerSource>))]
|
||||
public enum TriggerSource
|
||||
{
|
||||
Unknown = 0,
|
||||
Automatic = 1,
|
||||
Manual = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Classifies why a single asset evaluation in <c>TradeLifecycleService.EvaluateAssetAsync</c> did or did not
|
||||
/// result in a trade proposal. <see cref="Unknown"/> is deliberately value <c>0</c> (the default) so snapshot
|
||||
/// rows persisted before this field existed read honestly as "reason unknown" rather than fabricating a
|
||||
/// specific-looking cause (Rules.md §4). See the "DetermineOutcomeReason" doc comment in
|
||||
/// <c>TradeLifecycleService</c> for the exact priority order applied when more than one gate failed at once.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<OutcomeReason>))]
|
||||
public enum OutcomeReason
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>The AI reasoning gate approved the opportunity and a <c>EngineTradeProposalEntity</c> was created.</summary>
|
||||
Approved = 1,
|
||||
|
||||
/// <summary>
|
||||
/// <c>ScoringResult.CompositeScore</c> stayed below <c>Engine.MinCompositeScore</c> and the evaluation was
|
||||
/// not forced, so the AI reasoning gate was never even consulted (a synthetic rule-based rejection was
|
||||
/// recorded instead).
|
||||
/// </summary>
|
||||
BelowScoreThreshold = 2,
|
||||
|
||||
/// <summary>The asset is within the earnings blackout window (<c>Engine.EarningsLockoutDays</c>).</summary>
|
||||
EarningsLockout = 3,
|
||||
|
||||
/// <summary>FinlyticSimulation's backtest-reliability matrix vetoed this strategy/asset combination.</summary>
|
||||
SimulationVeto = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The composite score cleared the minimum threshold (or the evaluation was forced) and neither the
|
||||
/// earnings-lockout nor the simulation-veto gate blocked it, but the AI reasoning gate itself - whether a
|
||||
/// real AI call or one of its own rule-based fallbacks (gate disabled, webhook unreachable) - still declined.
|
||||
/// </summary>
|
||||
AiRejected = 5,
|
||||
|
||||
/// <summary>
|
||||
/// No technical setup could be produced for the ISIN at all (FinlyticTechnicals returned nothing), or the
|
||||
/// ISIN itself was blank - in both cases the pipeline never reached scoring, so every score field on the
|
||||
/// snapshot is <c>0</c>/default rather than fabricated.
|
||||
/// </summary>
|
||||
NoTechnicalSetups = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Not a real rejection: the evaluation genuinely cleared every gate and the AI reasoning gate approved the
|
||||
/// opportunity (<c>PassedAiValidation</c> on this same row is <see langword="true"/>), but an active,
|
||||
/// non-expired <c>EngineTradeProposalEntity</c> for the same <c>UnderlyingIsin</c> already exists, so no
|
||||
/// second, near-identical proposal row was created and no <c>finlytic/engine/proposals/created</c> MQTT
|
||||
/// event was re-broadcast. Exists specifically to stop the autonomous scanner from spamming a fresh
|
||||
/// proposal (and a fresh push event to every connected client) every single poll cycle for as long as one
|
||||
/// asset stays above the approval threshold - the underlying bug this value was introduced to fix.
|
||||
/// </summary>
|
||||
DuplicateActiveProposal = 7,
|
||||
|
||||
/// <summary>The asset is within the ex-dividend blackout window (<c>Engine.DividendGateDays</c>).</summary>
|
||||
DividendGate = 8
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user