namespace FinlyticCore.Util; /// /// Single source of truth for every MQTT topic name and RPC channel name used across the Finlytic microservice /// fleet (FinlyticAssets, FinlyticNews, FinlyticSentiment, FinlyticFundamentals, FinlyticTechnicals, /// FinlyticEngine, FinlyticSimulation, FinlyticBot, and the FinlyticBackend aggregation bridge). /// Before this class existed, every service built topic strings via ad-hoc interpolation, so publishers and /// subscribers were only ever kept in sync by naming convention. Any new topic or RPC channel must be added /// here and referenced from call sites instead of being written as a literal. /// public static class MqttTopics { // --------------------------------------------------------------------------------------------------- // RPC envelope: services/request/{channel}/{correlationId} <-> services/response/{channel}/{correlationId} // See ManagedMqttClient.SendRpcRequestAsync / SubscribeRpcAsync for the runtime mechanics. // --------------------------------------------------------------------------------------------------- private const string RequestRoot = "services/request"; private const string ResponseRoot = "services/response"; /// /// Gets the literal prefix ("services/request/") that precedes every RPC channel name in a request topic. /// Used to strip the prefix back off when a caller passes a full topic instead of a bare channel name. /// public const string RequestPrefix = RequestRoot + "/"; /// /// Gets the literal prefix ("services/response/") that precedes every RPC channel name in a response topic. /// Used to detect whether an incoming message belongs to the RPC response convention. /// public const string ResponsePrefix = ResponseRoot + "/"; /// /// Gets the wildcard filter that matches every RPC response, regardless of channel or correlation ID. /// Every service subscribes to this once at startup so pending SendRpcRequestAsync calls can resolve. /// public const string ResponseWildcard = ResponseRoot + "/#"; /// /// Builds the concrete RPC request topic for a channel and correlation ID: services/request/{channel}/{correlationId}. /// public static string RequestTopic(string channel, string correlationId) => $"{RequestRoot}/{channel}/{correlationId}"; /// /// Builds the concrete RPC response topic for a channel and correlation ID: services/response/{channel}/{correlationId}. /// public static string ResponseTopic(string channel, string correlationId) => $"{ResponseRoot}/{channel}/{correlationId}"; /// /// Builds the subscription wildcard filter that matches every request on a given RPC channel: services/request/{channel}/#. /// public static string RequestFilter(string channel) => $"{RequestRoot}/{channel}/#"; /// /// Named RPC channel identifiers (the {channel} segment of the request/response envelope above), /// grouped by the service that owns/serves each channel. /// public static class Channels { /// /// Shared liveness-check channel implemented identically by every service. The request topic carries the /// target service name as an extra path segment so only the addressed service responds. /// public const string HealthPing = "health_Ping"; // ---- FinlyticAssets ---- /// Served by FinlyticAssets: resolves valid assets for an ISIN. public const string AssetsGet = "assets_Get"; /// Served by FinlyticAssets: returns the curated discovery/watchlist asset set. public const string AssetsGetDiscovery = "assets_GetDiscovery"; /// Served by FinlyticAssets: resolves derivative instruments for an underlying ISIN. public const string AssetsGetDerivatives = "assets_GetDerivatives"; /// Served by FinlyticAssets: returns a live Trade Republic price tick for an ISIN. public const string TrGetLivePrice = "tr_GetLivePrice"; /// Served by FinlyticAssets: returns all dynamic settings for the service. public const string AssetsSettingsGetAll = "assets_settings_GetAll"; /// Served by FinlyticAssets: applies dynamic setting updates for the service. public const string AssetsSettingsUpdate = "assets_settings_Update"; // ---- FinlyticNews ---- /// Served by FinlyticNews: returns filtered/paginated news articles. public const string NewsGet = "news_Get"; /// Served by FinlyticNews: returns a single article by ID. public const string NewsGetById = "news_GetById"; /// Served by FinlyticNews: returns articles awaiting downstream sentiment analysis. public const string NewsGetPending = "news_GetPending"; /// Served by FinlyticNews: updates the processing status of an article. public const string NewsUpdateStatus = "news_UpdateStatus"; /// Served by FinlyticNews: returns all dynamic settings for the service. public const string NewsSettingsGetAll = "news_settings_GetAll"; /// Served by FinlyticNews: applies dynamic setting updates for the service. public const string NewsSettingsUpdate = "news_settings_Update"; // ---- FinlyticSentiment ---- /// Served by FinlyticSentiment: returns the pre-aggregated sentiment summary for an ISIN. public const string SentimentGetIsin = "sentiment_GetIsin"; /// Served by FinlyticSentiment: returns the pre-aggregated sentiment summary for a sector. public const string SentimentGetSector = "sentiment_GetSector"; /// Served by FinlyticSentiment: returns the persisted FinBERT analysis entry for a single article. public const string SentimentGetArticle = "sentiment_GetArticle"; /// Served by FinlyticSentiment: returns paginated per-company sentiment summaries. public const string SentimentGetAll = "sentiment_GetAll"; /// Served by FinlyticSentiment: runs FinBERT analysis for an inline article payload or article ID. public const string SentimentAnalyze = "sentiment_Analyze"; /// Served by FinlyticSentiment: returns all dynamic settings for the service. public const string SentimentSettingsGetAll = "sentiment_settings_GetAll"; /// Served by FinlyticSentiment: applies dynamic setting updates for the service. public const string SentimentSettingsUpdate = "sentiment_settings_Update"; // ---- FinlyticFundamentals ---- /// Served by FinlyticFundamentals: returns fundamentals data for an ISIN/ticker. public const string FundamentalsGet = "fundamentals_Get"; /// Served by FinlyticFundamentals: returns all known calendar events. public const string EventsGetAll = "events_GetAll"; /// Served by FinlyticFundamentals: returns calendar events for a given year/month. public const string EventsGetByMonth = "events_GetByMonth"; /// Served by FinlyticFundamentals: returns all dynamic settings for the service. public const string FundamentalsSettingsGetAll = "fundamentals_settings_GetAll"; /// Served by FinlyticFundamentals: applies dynamic setting updates for the service. public const string FundamentalsSettingsUpdate = "fundamentals_settings_Update"; // ---- FinlyticTechnicals ---- /// Served by FinlyticTechnicals: returns the technical analysis DTO for an ISIN. public const string TaGetAnalysis = "ta_GetAnalysis"; /// Served by FinlyticTechnicals: returns active strategy setups for a single ISIN. public const string TaGetSetupsForIsin = "ta_GetSetupsForIsin"; /// Served by FinlyticTechnicals: returns active strategy setups across the universe. public const string TaGetSetups = "ta_GetSetups"; /// Served by FinlyticTechnicals: returns aggregated candles for an ISIN/timeframe. public const string TaGetCandles = "ta_GetCandles"; /// Served by FinlyticTechnicals: returns the current monitored scan universe ("watchlist"). public const string TaGetWatchlist = "ta_GetWatchlist"; /// Served by FinlyticTechnicals: returns an ISIN's recent setup/score history (see ). public const string TaGetRecentSetupHistory = "ta_GetRecentSetupHistory"; /// Served by FinlyticTechnicals: returns all dynamic settings for the service. public const string TaSettingsGetAll = "ta_settings_GetAll"; /// Served by FinlyticTechnicals: applies dynamic setting updates for the service. public const string TaSettingsUpdate = "ta_settings_Update"; // ---- FinlyticEngine ---- /// Served by FinlyticEngine: returns trade proposals. public const string EngineGetProposals = "engine_GetProposals"; /// Served by FinlyticEngine: returns active trades. public const string EngineGetTrades = "engine_GetTrades"; /// Served by FinlyticEngine: evaluates a single ISIN and returns a trade proposal if warranted. public const string EngineEvaluateIsin = "engine_EvaluateIsin"; /// Served by FinlyticEngine: records a fill against an active trade. public const string EngineAddFill = "engine_AddFill"; /// Served by FinlyticEngine: updates the stop-loss of an active trade. public const string EngineUpdateStopLoss = "engine_UpdateStopLoss"; /// Served by FinlyticEngine: closes an active trade. public const string EngineCloseTrade = "engine_CloseTrade"; /// /// Served by FinlyticEngine: accepts a proposal on behalf of one user and creates a trade owned by that /// user. Takes an . The proposal is NOT consumed — it stays /// available for other users until it expires. /// There is deliberately no counterpart channel for declining a proposal: declining has no server-side /// effect and is handled entirely in the client. /// public const string EngineAcceptProposal = "engine_AcceptProposal"; /// /// Served by FinlyticEngine: opens a trade owned by one user with no backing proposal (manual entry from /// the Web UI). Takes a . Unlike /// , the resulting trade's ProposalId is . /// public const string EngineCreateManualTrade = "engine_CreateManualTrade"; /// /// Served by FinlyticEngine: returns a paginated, filtered history of every persisted evaluation /// snapshot (EngineEvaluationSnapshotEntity) for the admin-only "why no proposals" Web UI tab. /// Takes a and returns a /// . /// public const string EngineGetEvaluationHistory = "engine_GetEvaluationHistory"; /// Served by FinlyticEngine: returns all dynamic settings for the service. public const string EngineSettingsGetAll = "engine_settings_GetAll"; /// Served by FinlyticEngine: applies dynamic setting updates for the service. public const string EngineSettingsUpdate = "engine_settings_Update"; // ---- FinlyticSimulation ---- /// Served by FinlyticSimulation: runs a quantitative backtest. public const string SimRunBacktest = "sim_RunBacktest"; /// Served by FinlyticSimulation: returns the reliability score for a strategy/asset/timeframe. public const string SimGetReliability = "sim_GetReliability"; /// Served by FinlyticSimulation: returns the full strategy reliability matrix for an asset. public const string SimGetMatrixForAsset = "sim_GetMatrixForAsset"; /// /// Served by FinlyticSimulation: returns a paginated, filterable summary history of past backtest runs /// for an ISIN - every run is already persisted (SimulationRunEntity) but was previously only /// reachable indirectly (it fed the reliability matrix), never queryable as a history in its own right. /// public const string SimGetBacktestHistory = "sim_GetBacktestHistory"; /// Served by FinlyticSimulation: returns the full, already-persisted report (trades + equity curve) for one past backtest run by its RunId. public const string SimGetBacktestRunDetail = "sim_GetBacktestRunDetail"; /// Served by FinlyticSimulation: returns a saved per-asset/per-strategy indicator parameter profile, or null if none was saved. public const string SimGetStrategyParameters = "sim_GetStrategyParameters"; /// Served by FinlyticSimulation: saves/updates a per-asset/per-strategy indicator parameter profile. public const string SimSaveStrategyParameters = "sim_SaveStrategyParameters"; /// Served by FinlyticSimulation: returns all dynamic settings for the service. public const string SimSettingsGetAll = "sim_settings_GetAll"; /// Served by FinlyticSimulation: applies dynamic setting updates for the service. public const string SimSettingsUpdate = "sim_settings_Update"; // ---- FinlyticBot ---- /// Served by FinlyticBot: returns the current paper-trading bot status. public const string BotGetStatus = "bot_GetStatus"; /// Served by FinlyticBot: returns currently open paper-trading positions. public const string BotGetPositions = "bot_GetPositions"; /// Served by FinlyticBot: returns the paper-trading account summary. public const string BotGetSummary = "bot_GetSummary"; /// Served by FinlyticBot: executes a trade proposal as a paper trade. public const string BotExecuteProposal = "bot_ExecuteProposal"; /// /// Served by FinlyticBot: emergency-closes every open paper-trading position (synthetic ledger /// positions are closed unconditionally; Alpaca positions are only closed if the broker confirms the /// liquidation and are otherwise left open and reported as skipped — see the handler for details). /// public const string BotPanicClose = "bot_PanicClose"; /// Served by FinlyticBot: returns all dynamic settings for the service. public const string BotSettingsGetAll = "bot_settings_GetAll"; /// Served by FinlyticBot: applies dynamic setting updates for the service. public const string BotSettingsUpdate = "bot_settings_Update"; // ---- FinlyticBackend ---- /// /// Served by FinlyticBackend: returns the aggregated favorites list across all users. Centralized here /// even though FinlyticBackend is outside this refactor's scope, so no future service hardcodes it again. /// public const string BackendGetAggregatedFavorites = "backend_GetAggregatedFavorites"; } // --------------------------------------------------------------------------------------------------- // Event / stream topics: plain fire-and-forget pub/sub outside the RPC envelope. // --------------------------------------------------------------------------------------------------- /// /// Published by FinlyticNews once an article finishes ingestion and asset matching. Consumed by /// FinlyticSentiment (to trigger analysis) and the FinlyticBackend bridge. /// public const string NewsCompleted = "services/news/completed"; /// /// Gets the literal prefix ("finlytic/news/") shared by every FinlyticNews event topic. /// and every per-ISIN topic are derived from this constant so a StartsWith check (as used by /// the FinlyticBackend bridge) can never drift from the wildcard subscription filter. /// public const string NewsPrefix = "finlytic/news/"; private const string NewsStreamTemplate = NewsPrefix + "stream/{0}"; /// /// Builds the per-ISIN topic that FinlyticNews publishes newly matched articles to: finlytic/news/stream/{isin}. /// The ISIN is normalized (trimmed, lower-cased) to match the convention already used by every publisher/subscriber pair. /// public static string NewsStream(string isin) => string.Format(NewsStreamTemplate, NormalizeIsin(isin)); /// /// Wildcard filter matching every FinlyticNews stream topic, used by the FinlyticBackend bridge. /// public const string NewsStreamWildcard = NewsPrefix + "#"; /// /// Gets the literal prefix ("finlytic/sentiment/") shared by every FinlyticSentiment event topic. Used to /// detect whether an incoming message on the subscription is a sentiment event. /// and every per-ISIN topic are derived from this /// constant so they cannot drift apart. /// public const string SentimentPrefix = "finlytic/sentiment/"; private const string SentimentStreamTemplate = SentimentPrefix + "stream/{0}"; /// /// Builds the per-ISIN topic that FinlyticSentiment publishes updated sentiment summaries to: finlytic/sentiment/stream/{isin}. /// public static string SentimentStream(string isin) => string.Format(SentimentStreamTemplate, NormalizeIsin(isin)); /// /// Wildcard filter matching every FinlyticSentiment topic (currently only the per-ISIN stream). Used by /// FinlyticTechnicals to detect sentiment spikes and by the FinlyticBackend bridge. /// public const string SentimentWildcard = SentimentPrefix + "#"; /// /// Gets the literal prefix ("finlytic/engine/") shared by every FinlyticEngine event topic (proposals and /// trade status changes). is derived from this constant, and /// / are namespaced sub-prefixes of it, so /// none of the three can drift apart from one another. /// public const string EnginePrefix = "finlytic/engine/"; /// /// Wildcard filter matching every FinlyticEngine event topic (proposals and trade status changes). Used by /// the FinlyticBackend bridge. /// public const string EngineWildcard = EnginePrefix + "#"; /// /// Gets the literal prefix ("finlytic/engine/proposals/") shared by every FinlyticEngine proposal event /// topic. Used by the FinlyticBackend bridge to distinguish proposal events from trade status events on the /// shared subscription. /// public const string EngineProposalsPrefix = EnginePrefix + "proposals/"; /// /// Published by FinlyticEngine whenever a new trade proposal is created. Consumed by FinlyticBot (to /// evaluate auto-execution) and the FinlyticBackend bridge. /// public const string EngineProposalsCreated = EngineProposalsPrefix + "created"; /// /// Gets the literal prefix ("finlytic/engine/trades/") shared by every FinlyticEngine trade lifecycle event /// topic. Used by the FinlyticBackend bridge to distinguish trade status events from proposal events on the /// shared subscription. /// public const string EngineTradesPrefix = EnginePrefix + "trades/"; /// /// Published by FinlyticEngine whenever an active trade's lifecycle status changes (fills, stop-loss /// updates, closes). Consumed by the FinlyticBackend bridge. /// public const string EngineTradesStatusChanged = EngineTradesPrefix + "status_changed"; /// /// Gets the literal prefix ("finlytic/bot/") shared by every FinlyticBot event topic. /// and are derived from this constant so they cannot drift apart. /// public const string BotPrefix = "finlytic/bot/"; /// /// Gets the literal prefix ("finlytic/bot/trades/") shared by every FinlyticBot trade lifecycle event topic. /// Used by the FinlyticBackend bridge to distinguish trade stream events from other bot events on the shared /// subscription. /// public const string BotTradesPrefix = BotPrefix + "trades/"; /// /// Published by FinlyticBot whenever a paper-trading position's lifecycle status changes. Consumed by the /// FinlyticBackend bridge. /// public const string BotTradesStream = BotTradesPrefix + "stream"; /// /// Wildcard filter matching every FinlyticBot event topic. Used by the FinlyticBackend bridge. /// public const string BotWildcard = BotPrefix + "#"; /// /// Gets the literal prefix ("finlytic/logs/") shared by every structured-log broadcast topic. /// and every per-service topic are derived from this constant. /// public const string LogsPrefix = "finlytic/logs/"; private const string LogsTemplate = LogsPrefix + "{0}"; /// /// Builds the structured-log broadcast topic for a given service name (e.g. finlytic/logs/FinlyticAssets), /// published by every service's hook and consumed by the FinlyticBackend bridge. /// public static string Logs(string serviceName) => string.Format(LogsTemplate, serviceName); /// /// Wildcard filter matching structured-log broadcasts from every service. Used by the FinlyticBackend bridge. /// public const string LogsWildcard = LogsPrefix + "#"; /// /// Normalizes an ISIN for use as an MQTT topic path segment. MQTT topics are case-sensitive and every known /// publisher/subscriber pair in this system agreed on trimmed, lower-case ISINs; this keeps that convention /// in one place instead of repeating .Trim().ToLowerInvariant() at every call site. /// /// Thrown when is null, empty, or whitespace. private static string NormalizeIsin(string isin) { if (string.IsNullOrWhiteSpace(isin)) throw new ArgumentException("ISIN must not be null or empty when building an MQTT topic.", nameof(isin)); return isin.Trim().ToLowerInvariant(); } }