using System.Text.Json.Serialization; namespace FinlyticCore.Dtos; /// /// Generic request payload carrying only a limit parameter (e.g. news_GetPending). /// public record LimitRequest( [property: JsonPropertyName("limit")] int Limit ); /// /// Generic request payload for paginated queries with optional ISIN filter. /// public record PaginatedRequest( [property: JsonPropertyName("limit")] int Limit, [property: JsonPropertyName("offset")] int Offset, [property: JsonPropertyName("isin")] string? Isin = null ); /// /// Request payload for daily-news queries with optional filters. /// public record DailyNewsRequest( [property: JsonPropertyName("limit")] int Limit, [property: JsonPropertyName("offset")] int Offset, [property: JsonPropertyName("isin")] string? Isin = null, [property: JsonPropertyName("date")] DateTime? Date = null, [property: JsonPropertyName("status")] string? Status = null, [property: JsonPropertyName("query")] string? Query = null, [property: JsonPropertyName("hasSentiment")] bool? HasSentiment = null ); /// /// Request payload for fetching fundamentals or technical-analysis data by ISIN. /// public record IsinRequest( [property: JsonPropertyName("isin")] string Isin, [property: JsonPropertyName("ticker")] string? Ticker = "", [property: JsonPropertyName("forceRefresh")] bool ForceRefresh = false ); /// /// Request payload for fetching trades filtered by ISIN and/or status. /// public record GetTradesRequest( [property: JsonPropertyName("isin")] string? Isin = null, [property: JsonPropertyName("status")] string? Status = null, [property: JsonPropertyName("userId")] string? UserId = null ); /// /// Request payload for fetching sentiment by article ID. /// public record ArticleRequest( [property: JsonPropertyName("articleId")] string ArticleId, [property: JsonPropertyName("id")] string? Id = null ); /// /// Request payload for fetching sentiment by ISIN. /// public record GetSentimentByIsinRequest( [property: JsonPropertyName("isin")] string Isin ); /// /// Request payload for fetching sentiment by Sector. /// public record GetSectorSentimentRequest( [property: JsonPropertyName("sector")] string Sector ); /// /// Request payload for triggering a manual sentiment analysis for an article or ISIN. /// public record AnalyzeSentimentRequest( [property: JsonPropertyName("articleId")] string? ArticleId = null, [property: JsonPropertyName("isin")] string? Isin = null, [property: JsonPropertyName("forceReload")] bool ForceReload = false ); /// /// Empty request payload for MQTT RPCs that require no parameters (e.g. events_GetAll). /// public record EmptyRequest; /// /// Request payload for paginated/monthly calendar queries. /// public record GetEventsByMonthRequest( [property: JsonPropertyName("year")] int Year, [property: JsonPropertyName("month")] int Month ); /// /// Response payload returned by microservice health pings over MQTT. /// public record ServiceHealthResponse( [property: JsonPropertyName("serviceName")] string ServiceName, [property: JsonPropertyName("status")] string Status, [property: JsonPropertyName("timestamp")] DateTime Timestamp, [property: JsonPropertyName("dbStatus")] string DbStatus ); /// /// Response payload for assets_FetchLogo RPC request. /// public record FetchLogoResponse( [property: JsonPropertyName("isin")] string? Isin, [property: JsonPropertyName("path")] string? Path, [property: JsonPropertyName("success")] bool Success ); /// /// Payload published to MQTT when a live market tick is received. /// public record TickMessageDto( [property: JsonPropertyName("price")] decimal Price ); /// /// Request payload for fetching trade proposals from FinlyticEngine. /// public record GetTradeProposalsRequest( [property: JsonPropertyName("onlyActive")] bool OnlyActive = true, [property: JsonPropertyName("limit")] int Limit = 50 ); /// /// Request payload for fetching active trades from FinlyticEngine. is mandatory /// (not defaulted/optional) so FinlyticEngine always filters trades to their owner server-side; a caller /// can never accidentally list every user's trades by omitting it (see Rules.md multi-tenancy requirement). /// public record GetActiveTradesRequest( [property: JsonPropertyName("userId")] Guid UserId, [property: JsonPropertyName("mode")] FinlyticCore.Dtos.Trading.ExecutionMode? Mode = null ); /// /// Request payload for triggering an on-demand evaluation in FinlyticEngine. identifies /// the human caller for the resulting EngineEvaluationSnapshotEntity.TriggeredByUserId audit trail /// (this RPC channel is only ever reached from the manual Web UI flows - the autonomous /// OpportunityPollerBackgroundService calls ITradeLifecycleService.EvaluateAssetAsync directly /// in-process and never goes through this channel at all). Exactly like /// and its siblings, any value supplied by an untrusted client is discarded and overwritten server-side /// (FinlyticBackend) with the identity from the JWT before the request is forwarded over MQTT; the default of /// here only exists so / can keep /// their own defaults (C# requires optional parameters to trail). /// public record EvaluateAssetRequest( [property: JsonPropertyName("isin")] string Isin, [property: JsonPropertyName("userId")] Guid UserId = default, [property: JsonPropertyName("ticker")] string? Ticker = null, [property: JsonPropertyName("forceAiEvaluation")] bool ForceAiEvaluation = false ); /// /// Request payload for adding an executed fill to an active trade. is mandatory so /// FinlyticEngine can verify the caller owns before mutating it; a value supplied by an /// untrusted client must always be overwritten server-side (FinlyticBackend) with the identity from the JWT. /// public record AddTradeFillRequest( [property: JsonPropertyName("userId")] Guid UserId, [property: JsonPropertyName("tradeId")] Guid TradeId, [property: JsonPropertyName("executedPrice")] decimal ExecutedPrice, [property: JsonPropertyName("quantity")] decimal Quantity, [property: JsonPropertyName("fee")] decimal Fee = 0m, [property: JsonPropertyName("note")] string? Note = null ); /// /// Request payload for manually or algorithmically adjusting a trade's stop loss. is /// mandatory so FinlyticEngine can verify the caller owns before mutating it; a value /// supplied by an untrusted client must always be overwritten server-side (FinlyticBackend) with the identity /// from the JWT. /// public record UpdateTradeStopLossRequest( [property: JsonPropertyName("userId")] Guid UserId, [property: JsonPropertyName("tradeId")] Guid TradeId, [property: JsonPropertyName("newStopLoss")] decimal NewStopLoss, [property: JsonPropertyName("reason")] string Reason ); /// /// Request payload for closing an active trade. is mandatory so FinlyticEngine can verify /// the caller owns before closing it; a value supplied by an untrusted client must always /// be overwritten server-side (FinlyticBackend) with the identity from the JWT. /// public record CloseEngineTradeRequest( [property: JsonPropertyName("userId")] Guid UserId, [property: JsonPropertyName("tradeId")] Guid TradeId, [property: JsonPropertyName("closePrice")] decimal ClosePrice, [property: JsonPropertyName("reason")] string Reason ); /// /// Request payload for accepting an open trade proposal on behalf of a single user. A proposal is a /// system-wide opportunity, so accepting it does NOT consume or deactivate it — it creates one independent /// trade owned by , and other users may still accept the same proposal. Proposals /// disappear on their own once ExpiresAtUtc passes; there is deliberately no "reject" round trip, /// because declining a proposal has no server-side effect. /// must always be overwritten server-side (FinlyticBackend) with the identity from /// the JWT and never trusted from the client. /// public record AcceptTradeProposalRequest( [property: JsonPropertyName("userId")] Guid UserId, [property: JsonPropertyName("proposalId")] Guid ProposalId, [property: JsonPropertyName("executedPrice")] decimal? ExecutedPrice = null, [property: JsonPropertyName("quantity")] decimal? Quantity = null ); /// /// Request payload for manually opening a trade in FinlyticEngine with no backing proposal (e.g. a user /// enters a position in the Web UI that FinlyticEngine never evaluated or scored). is /// mandatory and must always be overwritten server-side (FinlyticBackend) with the identity from the JWT, /// exactly like every other engine trade-mutation request. /// There is deliberately no ProposalId field: EngineTradeEntity.ProposalId stays a /// non-nullable everywhere else in the codebase (grouping trades that share one accepted /// proposal), so FinlyticEngine substitutes for a manually created trade instead of /// widening that column to nullable for the sake of this single caller. /// public record CreateManualTradeRequest( [property: JsonPropertyName("userId")] Guid UserId, [property: JsonPropertyName("underlyingIsin")] string UnderlyingIsin, [property: JsonPropertyName("symbol")] string Symbol, [property: JsonPropertyName("direction")] FinlyticCore.Dtos.TechnicalAnalysis.SignalDirection Direction, [property: JsonPropertyName("entryPrice")] decimal EntryPrice, [property: JsonPropertyName("quantity")] decimal Quantity, [property: JsonPropertyName("initialStopLoss")] decimal InitialStopLoss, [property: JsonPropertyName("takeProfit1")] decimal TakeProfit1, [property: JsonPropertyName("takeProfit2")] decimal? TakeProfit2 = null, [property: JsonPropertyName("instrumentType")] FinlyticCore.Dtos.Trading.InstrumentCategoryType InstrumentType = FinlyticCore.Dtos.Trading.InstrumentCategoryType.Stock, [property: JsonPropertyName("derivativeIsin")] string? DerivativeIsin = null, [property: JsonPropertyName("derivativeWkn")] string? DerivativeWkn = null, [property: JsonPropertyName("fee")] decimal Fee = 0m ); /// /// Machine-readable classification of a server-side RPC fault, carried by so a /// caller can react to the specific failure mode instead of only learning "something went wrong" (or, before /// this error channel existed, learning nothing at all and simply timing out). The set is deliberately small and /// mirrors the handful of exception shapes actually thrown by SubscribeRpcAsync handlers across the /// fleet today (see ); it is not meant to be a full HTTP-status /// mirror. Each value has a corresponding standard .NET exception type that /// reconstructs client-side, so existing /// catch (InvalidOperationException) / catch (ArgumentException) blocks written against the /// service-layer methods' local exception types keep working unchanged across the MQTT boundary. /// [JsonConverter(typeof(JsonStringEnumConverter))] public enum RpcFaultCode { /// /// Uncategorized/unexpected server-side failure with no safe, specific detail to disclose over MQTT (the /// broker runs without authentication). The full exception is logged locally on the serving side only. /// Internal = 0, /// The request conflicts with current server-side state (e.g. a proposal already accepted by this same user). Conflict = 1, /// The request payload failed validation (e.g. a blank ISIN or a non-positive price/quantity). InvalidArgument = 2, /// /// The referenced resource does not exist, or exists but does not belong to the caller. The two cases are /// deliberately not distinguished (see the multi-tenancy note on ): a /// caller must never learn that a trade ID exists under another user's account. /// NotFound = 3, /// The caller's identity could not be established, or is not permitted to perform this operation. Unauthorized = 4 } /// /// Typed error envelope published by /// on a dedicated error sub-topic when an RPC handler throws, instead of silently dropping the request and /// leaving the caller to hit its request timeout. The message carries only a machine-readable /// and a short, safe, fully-formed ; internal details (stack traces, /// connection strings, etc.) are never placed on the wire and must be logged locally on the serving side instead /// (Rules.md §10/§11, and the MQTT broker currently has no authentication). /// public record RpcErrorResponse( [property: JsonPropertyName("code")] RpcFaultCode Code, [property: JsonPropertyName("message")] string Message );