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
);
///
/// Generic request payload carrying a user GUID to resolve user identity across microservices.
///
public record UserIdRequest(
[property: JsonPropertyName("userId")] Guid UserId
);
///
/// 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
);