296 lines
14 KiB
C#
296 lines
14 KiB
C#
using System.Text.Json.Serialization;
|
|
|
|
namespace FinlyticCore.Dtos;
|
|
|
|
/// <summary>
|
|
/// Generic request payload carrying only a limit parameter (e.g. news_GetPending).
|
|
/// </summary>
|
|
public record LimitRequest(
|
|
[property: JsonPropertyName("limit")] int Limit
|
|
);
|
|
|
|
/// <summary>
|
|
/// Generic request payload for paginated queries with optional ISIN filter.
|
|
/// </summary>
|
|
public record PaginatedRequest(
|
|
[property: JsonPropertyName("limit")] int Limit,
|
|
[property: JsonPropertyName("offset")] int Offset,
|
|
[property: JsonPropertyName("isin")] string? Isin = null
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for daily-news queries with optional filters.
|
|
/// </summary>
|
|
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
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for fetching fundamentals or technical-analysis data by ISIN.
|
|
/// </summary>
|
|
public record IsinRequest(
|
|
[property: JsonPropertyName("isin")] string Isin,
|
|
[property: JsonPropertyName("ticker")] string? Ticker = "",
|
|
[property: JsonPropertyName("forceRefresh")]
|
|
bool ForceRefresh = false
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for fetching trades filtered by ISIN and/or status.
|
|
/// </summary>
|
|
public record GetTradesRequest(
|
|
[property: JsonPropertyName("isin")] string? Isin = null,
|
|
[property: JsonPropertyName("status")] string? Status = null,
|
|
[property: JsonPropertyName("userId")] string? UserId = null
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for fetching sentiment by article ID.
|
|
/// </summary>
|
|
public record ArticleRequest(
|
|
[property: JsonPropertyName("articleId")]
|
|
string ArticleId,
|
|
[property: JsonPropertyName("id")] string? Id = null
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for fetching sentiment by ISIN.
|
|
/// </summary>
|
|
public record GetSentimentByIsinRequest(
|
|
[property: JsonPropertyName("isin")] string Isin
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for fetching sentiment by Sector.
|
|
/// </summary>
|
|
public record GetSectorSentimentRequest(
|
|
[property: JsonPropertyName("sector")] string Sector
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for triggering a manual sentiment analysis for an article or ISIN.
|
|
/// </summary>
|
|
public record AnalyzeSentimentRequest(
|
|
[property: JsonPropertyName("articleId")]
|
|
string? ArticleId = null,
|
|
[property: JsonPropertyName("isin")] string? Isin = null,
|
|
[property: JsonPropertyName("forceReload")]
|
|
bool ForceReload = false
|
|
);
|
|
|
|
/// <summary>
|
|
/// Empty request payload for MQTT RPCs that require no parameters (e.g. events_GetAll).
|
|
/// </summary>
|
|
public record EmptyRequest;
|
|
|
|
/// <summary>
|
|
/// Request payload for paginated/monthly calendar queries.
|
|
/// </summary>
|
|
public record GetEventsByMonthRequest(
|
|
[property: JsonPropertyName("year")] int Year,
|
|
[property: JsonPropertyName("month")] int Month
|
|
);
|
|
|
|
/// <summary>
|
|
/// Response payload returned by microservice health pings over MQTT.
|
|
/// </summary>
|
|
public record ServiceHealthResponse(
|
|
[property: JsonPropertyName("serviceName")]
|
|
string ServiceName,
|
|
[property: JsonPropertyName("status")] string Status,
|
|
[property: JsonPropertyName("timestamp")]
|
|
DateTime Timestamp,
|
|
[property: JsonPropertyName("dbStatus")]
|
|
string DbStatus
|
|
);
|
|
|
|
/// <summary>
|
|
/// Response payload for assets_FetchLogo RPC request.
|
|
/// </summary>
|
|
public record FetchLogoResponse(
|
|
[property: JsonPropertyName("isin")] string? Isin,
|
|
[property: JsonPropertyName("path")] string? Path,
|
|
[property: JsonPropertyName("success")]
|
|
bool Success
|
|
);
|
|
|
|
/// <summary>
|
|
/// Payload published to MQTT when a live market tick is received.
|
|
/// </summary>
|
|
public record TickMessageDto(
|
|
[property: JsonPropertyName("price")] decimal Price
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for fetching trade proposals from FinlyticEngine.
|
|
/// </summary>
|
|
public record GetTradeProposalsRequest(
|
|
[property: JsonPropertyName("onlyActive")] bool OnlyActive = true,
|
|
[property: JsonPropertyName("limit")] int Limit = 50
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for fetching active trades from FinlyticEngine. <see cref="UserId"/> 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).
|
|
/// </summary>
|
|
public record GetActiveTradesRequest(
|
|
[property: JsonPropertyName("userId")] Guid UserId,
|
|
[property: JsonPropertyName("mode")] FinlyticCore.Dtos.Trading.ExecutionMode? Mode = null
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for triggering an on-demand evaluation in FinlyticEngine. <see cref="UserId"/> identifies
|
|
/// the human caller for the resulting <c>EngineEvaluationSnapshotEntity.TriggeredByUserId</c> audit trail
|
|
/// (this RPC channel is only ever reached from the manual Web UI flows - the autonomous
|
|
/// <c>OpportunityPollerBackgroundService</c> calls <c>ITradeLifecycleService.EvaluateAssetAsync</c> directly
|
|
/// in-process and never goes through this channel at all). Exactly like <see cref="AddTradeFillRequest.UserId"/>
|
|
/// 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
|
|
/// <see cref="Guid.Empty"/> here only exists so <see cref="Ticker"/>/<see cref="ForceAiEvaluation"/> can keep
|
|
/// their own defaults (C# requires optional parameters to trail).
|
|
/// </summary>
|
|
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
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for adding an executed fill to an active trade. <see cref="UserId"/> is mandatory so
|
|
/// FinlyticEngine can verify the caller owns <see cref="TradeId"/> before mutating it; a value supplied by an
|
|
/// untrusted client must always be overwritten server-side (FinlyticBackend) with the identity from the JWT.
|
|
/// </summary>
|
|
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
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for manually or algorithmically adjusting a trade's stop loss. <see cref="UserId"/> is
|
|
/// mandatory so FinlyticEngine can verify the caller owns <see cref="TradeId"/> before mutating it; a value
|
|
/// supplied by an untrusted client must always be overwritten server-side (FinlyticBackend) with the identity
|
|
/// from the JWT.
|
|
/// </summary>
|
|
public record UpdateTradeStopLossRequest(
|
|
[property: JsonPropertyName("userId")] Guid UserId,
|
|
[property: JsonPropertyName("tradeId")] Guid TradeId,
|
|
[property: JsonPropertyName("newStopLoss")] decimal NewStopLoss,
|
|
[property: JsonPropertyName("reason")] string Reason
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request payload for closing an active trade. <see cref="UserId"/> is mandatory so FinlyticEngine can verify
|
|
/// the caller owns <see cref="TradeId"/> before closing it; a value supplied by an untrusted client must always
|
|
/// be overwritten server-side (FinlyticBackend) with the identity from the JWT.
|
|
/// </summary>
|
|
public record CloseEngineTradeRequest(
|
|
[property: JsonPropertyName("userId")] Guid UserId,
|
|
[property: JsonPropertyName("tradeId")] Guid TradeId,
|
|
[property: JsonPropertyName("closePrice")] decimal ClosePrice,
|
|
[property: JsonPropertyName("reason")] string Reason
|
|
);
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="UserId"/>, and other users may still accept the same proposal. Proposals
|
|
/// disappear on their own once <c>ExpiresAtUtc</c> passes; there is deliberately no "reject" round trip,
|
|
/// because declining a proposal has no server-side effect.
|
|
/// <see cref="UserId"/> must always be overwritten server-side (FinlyticBackend) with the identity from
|
|
/// the JWT and never trusted from the client.
|
|
/// </summary>
|
|
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
|
|
);
|
|
|
|
/// <summary>
|
|
/// 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). <see cref="UserId"/> 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 <c>ProposalId</c> field: <c>EngineTradeEntity.ProposalId</c> stays a
|
|
/// non-nullable <see cref="Guid"/> everywhere else in the codebase (grouping trades that share one accepted
|
|
/// proposal), so FinlyticEngine substitutes <see cref="Guid.Empty"/> for a manually created trade instead of
|
|
/// widening that column to nullable for the sake of this single caller.
|
|
/// </summary>
|
|
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
|
|
);
|
|
|
|
/// <summary>
|
|
/// Machine-readable classification of a server-side RPC fault, carried by <see cref="RpcErrorResponse"/> 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 <c>SubscribeRpcAsync</c> handlers across the
|
|
/// fleet today (see <see cref="FinlyticCore.Util.ManagedMqttClient"/>); it is not meant to be a full HTTP-status
|
|
/// mirror. Each value has a corresponding standard .NET exception type that
|
|
/// <see cref="FinlyticCore.Util.ManagedMqttClient"/> reconstructs client-side, so existing
|
|
/// <c>catch (InvalidOperationException)</c> / <c>catch (ArgumentException)</c> blocks written against the
|
|
/// service-layer methods' local exception types keep working unchanged across the MQTT boundary.
|
|
/// </summary>
|
|
[JsonConverter(typeof(JsonStringEnumConverter<RpcFaultCode>))]
|
|
public enum RpcFaultCode
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
Internal = 0,
|
|
|
|
/// <summary>The request conflicts with current server-side state (e.g. a proposal already accepted by this same user).</summary>
|
|
Conflict = 1,
|
|
|
|
/// <summary>The request payload failed validation (e.g. a blank ISIN or a non-positive price/quantity).</summary>
|
|
InvalidArgument = 2,
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="GetActiveTradesRequest"/>): a
|
|
/// caller must never learn that a trade ID exists under another user's account.
|
|
/// </summary>
|
|
NotFound = 3,
|
|
|
|
/// <summary>The caller's identity could not be established, or is not permitted to perform this operation.</summary>
|
|
Unauthorized = 4
|
|
}
|
|
|
|
/// <summary>
|
|
/// Typed error envelope published by <see cref="FinlyticCore.Util.ManagedMqttClient.SubscribeRpcAsync{TRequest,TResponse}"/>
|
|
/// 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
|
|
/// <see cref="Code"/> and a short, safe, fully-formed <see cref="Message"/>; 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).
|
|
/// </summary>
|
|
public record RpcErrorResponse(
|
|
[property: JsonPropertyName("code")] RpcFaultCode Code,
|
|
[property: JsonPropertyName("message")] string Message
|
|
); |