chore: update root config, scripts, and remaining files

This commit is contained in:
2026-08-09 21:01:48 +02:00
parent a708d2977c
commit fdf4b6efcb
6 changed files with 386 additions and 50 deletions
+94 -1
View File
@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticAssets", "FinlyticAssets\FinlyticAssets.csproj", "{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticAssets", "FinlyticAssets\FinlyticAssets.csproj", "{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}"
EndProject EndProject
@@ -11,23 +11,116 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticCore", "FinlyticCor
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticNews", "FinlyticNews\FinlyticNews.csproj", "{03B4D920-6173-44E8-A1E2-8945D8393CEA}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticNews", "FinlyticNews\FinlyticNews.csproj", "{03B4D920-6173-44E8-A1E2-8945D8393CEA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticFundamentals", "FinlyticFundamentals\FinlyticFundamentals.csproj", "{D458A1B3-16CF-45E8-859D-87542A4A83A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticSentiment", "FinlyticSentiment\FinlyticSentiment.csproj", "{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticTechnicalAnalysis", "FinlyticTechnicalAnalysis\FinlyticTechnicalAnalysis.csproj", "{A1C82F63-4482-4E99-9231-1184FA2E001F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticAnalyzer", "FinlyticAnalyzer\FinlyticAnalyzer.csproj", "{E9F7C091-62C4-417A-B981-8977DF82A1B0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticTrades", "FinlyticTrades\FinlyticTrades.csproj", "{57D84C2E-73E1-4231-A91B-6B620FCE5289}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinlyticBackend", "FinlyticBackend\FinlyticBackend.csproj", "{C1A924B8-904E-436D-B07E-4E621F51C1AA}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Debug|x64.ActiveCfg = Debug|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Debug|x64.Build.0 = Debug|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Debug|x86.ActiveCfg = Debug|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Debug|x86.Build.0 = Debug|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Release|Any CPU.Build.0 = Release|Any CPU {B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Release|Any CPU.Build.0 = Release|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Release|x64.ActiveCfg = Release|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Release|x64.Build.0 = Release|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Release|x86.ActiveCfg = Release|Any CPU
{B5AC2C29-44D5-4538-815F-F02CDDE9D01F}.Release|x86.Build.0 = Release|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU {03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Debug|x64.ActiveCfg = Debug|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Debug|x64.Build.0 = Debug|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Debug|x86.ActiveCfg = Debug|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Debug|x86.Build.0 = Debug|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Release|Any CPU.ActiveCfg = Release|Any CPU {03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Release|Any CPU.Build.0 = Release|Any CPU {03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Release|Any CPU.Build.0 = Release|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Release|x64.ActiveCfg = Release|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Release|x64.Build.0 = Release|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Release|x86.ActiveCfg = Release|Any CPU
{03E2ECBB-0FF9-43E4-994E-F6A522860AD5}.Release|x86.Build.0 = Release|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {03B4D920-6173-44E8-A1E2-8945D8393CEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Debug|Any CPU.Build.0 = Debug|Any CPU {03B4D920-6173-44E8-A1E2-8945D8393CEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Debug|x64.ActiveCfg = Debug|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Debug|x64.Build.0 = Debug|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Debug|x86.ActiveCfg = Debug|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Debug|x86.Build.0 = Debug|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Release|Any CPU.ActiveCfg = Release|Any CPU {03B4D920-6173-44E8-A1E2-8945D8393CEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Release|Any CPU.Build.0 = Release|Any CPU {03B4D920-6173-44E8-A1E2-8945D8393CEA}.Release|Any CPU.Build.0 = Release|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Release|x64.ActiveCfg = Release|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Release|x64.Build.0 = Release|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Release|x86.ActiveCfg = Release|Any CPU
{03B4D920-6173-44E8-A1E2-8945D8393CEA}.Release|x86.Build.0 = Release|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Debug|x64.ActiveCfg = Debug|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Debug|x64.Build.0 = Debug|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Debug|x86.ActiveCfg = Debug|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Debug|x86.Build.0 = Debug|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Release|Any CPU.Build.0 = Release|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Release|x64.ActiveCfg = Release|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Release|x64.Build.0 = Release|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Release|x86.ActiveCfg = Release|Any CPU
{999DB199-69C5-46D2-BDAD-59C84F5769F1}.Release|x86.Build.0 = Release|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Debug|x64.ActiveCfg = Debug|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Debug|x64.Build.0 = Debug|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Debug|x86.ActiveCfg = Debug|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Debug|x86.Build.0 = Debug|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Release|Any CPU.Build.0 = Release|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Release|x64.ActiveCfg = Release|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Release|x64.Build.0 = Release|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Release|x86.ActiveCfg = Release|Any CPU
{D458A1B3-16CF-45E8-859D-87542A4A83A7}.Release|x86.Build.0 = Release|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Debug|x64.ActiveCfg = Debug|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Debug|x64.Build.0 = Debug|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Debug|x86.ActiveCfg = Debug|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Debug|x86.Build.0 = Debug|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Release|Any CPU.Build.0 = Release|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Release|x64.ActiveCfg = Release|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Release|x64.Build.0 = Release|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Release|x86.ActiveCfg = Release|Any CPU
{9C3BB705-86AD-4A89-AA0F-A52C87A4950B}.Release|x86.Build.0 = Release|Any CPU
{A1C82F63-4482-4E99-9231-1184FA2E001F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1C82F63-4482-4E99-9231-1184FA2E001F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9F7C091-62C4-417A-B981-8977DF82A1B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9F7C091-62C4-417A-B981-8977DF82A1B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9F7C091-62C4-417A-B981-8977DF82A1B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9F7C091-62C4-417A-B981-8977DF82A1B0}.Release|Any CPU.Build.0 = Release|Any CPU
{57D84C2E-73E1-4231-A91B-6B620FCE5289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{57D84C2E-73E1-4231-A91B-6B620FCE5289}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57D84C2E-73E1-4231-A91B-6B620FCE5289}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57D84C2E-73E1-4231-A91B-6B620FCE5289}.Release|Any CPU.Build.0 = Release|Any CPU
{C1A924B8-904E-436D-B07E-4E621F51C1AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1A924B8-904E-436D-B07E-4E621F51C1AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C1A924B8-904E-436D-B07E-4E621F51C1AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1A924B8-904E-436D-B07E-4E621F51C1AA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal
+58 -46
View File
@@ -1,66 +1,78 @@
# Finlytic Assets Service # Finlytic Enterprise System Architecture
Finlytic Assets is a scalable C# microservice designed for asset discovery, metadata ingestion, and quick querying. It interfaces directly with the Trade Republic API via WebSockets and exposes a high-performance RPC interface over MQTT to other services in the Finlytic ecosystem. Finlytic is an enterprise financial intelligence platform composed of high-performance C# .NET 8 microservices, a web gateway (`FinlyticBackend`), a Flutter application (`FinlyticApp`), a React web interface (`FinlyticWeb`), and a real-time MQTT event mesh.
--- ---
## Architecture Overview ## Ecosystem Architecture Overview
```mermaid ```mermaid
graph TD graph TD
TR[Trade Republic WebSocket API] <-->|WS Protocol| TRS[TradeRepublicService] App[FinlyticApp (Flutter)] -->|HTTP REST & SignalR| Backend[FinlyticBackend]
TRS <-->|Ingest| ADS[AssetsFullScanService] Web[FinlyticWeb (React)] -->|HTTP REST & SignalR| Backend
ADS <-->|Save / Update| DB[PostgreSQL Database]
DB -->|Trigger Index Update| AIS[AssetsIndexService]
AIS -->|Write Cache| Index[assets/index/index.json]
MS[Other Finlytic Services] <-->|MQTT Request-Reply| MqttClient[AssetsMqttClient] Backend <-->|MQTT Pub/Sub & RPC| Broker[MQTT Broker (EMQX / Mosquitto)]
MqttClient <-->|Query Cache| DB
News[FinlyticNews Service] <-->|MQTT| Broker
Sentiment[FinlyticSentiment Service] <-->|MQTT| Broker
Assets[FinlyticAssets Service] <-->|MQTT| Broker
Fundamentals[FinlyticFundamentals Service] <-->|MQTT| Broker
TA[FinlyticTechnicalAnalysis Service] <-->|MQTT| Broker
Trades[FinlyticTrades Service] <-->|MQTT| Broker
Analyzer[FinlyticAnalyzer Service] <-->|MQTT| Broker
TR[Trade Republic WS API] <--> Assets
N8N[n8n Webhook / FinBERT] <--> News
N8N <--> Sentiment
``` ```
--- ---
## Core Features & Workflows ## Core System Principles & Rules
### 1. Automated WebSocket scraping 1. **Single Web Gateway (`FinlyticBackend`)**:
- **Continuous Scan**: The background worker (`AssetsFullScanService`) iterates through all support asset types: - `FinlyticBackend` is the **only** microservice hosting HTTP REST and SignalR WebSocket endpoints for external clients (`FinlyticApp`, `FinlyticWeb`).
- **Stocks** (`StockEntity`) - All background worker microservices (`FinlyticNews`, `FinlyticSentiment`, `FinlyticAssets`, `FinlyticFundamentals`, `FinlyticTechnicalAnalysis`, `FinlyticTrades`, `FinlyticAnalyzer`) operate strictly as `IHostedService` worker engines with zero Kestrel HTTP webservers.
- **Crypto** (`CryptoEntity`)
- **Derivatives** (`DerivativeEntity`)
- **Bonds** (`BondEntity`)
- **Funds/ETFs** (`EtfEntity`)
- **Resilient Reconnection**: Uses a robust custom socket wrapper (`TradeRepublicClient`) that closes automatically after 5 minutes of inactivity to mimic human interaction profiles and reconnects dynamically when a request is made.
### 2. Stealth Scheduling with Randomized Jitter 2. **Exclusive Inter-Service Communication via MQTT**:
- **Dynamic Delays**: Scanner wait times are read dynamically from database configuration (`Settings`). - All background microservices communicate strictly over MQTT topics (Pub/Sub & RPC).
- **Jitter Offset**: High/low jitter values (randomized offsets between batches and asset types) are injected to avoid predictable traffic patterns and prevent Trade Republic rate limiting. - High-performance, zero-allocation serialization is enforced using `.NET 8 JSON Source Generators` (`FinlyticJsonSerializerContext`).
### 3. Active vs. Dead Asset Tracking 3. **Absolute Prohibition of Mock/Demo Data**:
- **Recency Filter**: Assets are continuously updated with their latest metadata. If an asset has not been updated within **14 days**, it is considered inactive, de-listed, or "dead". - No mock data, hardcoded fallback arrays, or fake dummy responses are permitted in any microservice or frontend client.
- **Implicit Filtration**: Queries by other services only return active/valid records unless explicitly requested otherwise. - Either real data is queried from database contexts (PostgreSQL) / external APIs, or empty result sets / explicit exceptions are returned.
### 4. MQTT RPC Interface
- **Get Asset by ISIN**:
- Subscribes to: `services/request/assets_Get/#`
- Returns a list of all active assets matching the specified ISIN across different instrument types (e.g., matching both a stock and its derivative tracker).
- **Omnibox Search**:
- Subscribes to: `services/request/assets_Search/#`
- Evaluates search keywords against local records (matching `ISIN`, `Name`, or tags like `Region`, `Country`, `Sector`, etc.).
- **JIT Fallback**: If a search query is formatted as an unknown valid ISIN, it performs a JIT-lookup directly against the Trade Republic API and registers the discovered asset before replying.
### 5. Local Indexing File (`index.json`)
- **Automatic Regeneration**: On database modifications, `AssetsIndexService` is triggered to regenerate `assets/index/index.json`.
- **Pre-Filtering**: This lean index (containing only `ISIN` and `Name` properties) is written to a shared volume to allow other services to perform instant pre-filtering without querying the database or sending network requests.
--- ---
## Technical Components ## Microservices Breakdown
| Component | Class / Interface | Responsibility | | Project | Type | Description |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| **Ingestion Worker** | `AssetsFullScanService` | Runs the main cron loop, scraping and paginating all Trade Republic asset types. | | **`FinlyticCore`** | Class Library | Shared DTOs, domain models, MQTT infrastructure (`ManagedMqttClient`), and JSON Source Generator context. |
| **Database Store** | [IAssetsDbService](file:///e:/Projects/FinlyticAssets/FinlyticAssets/Services/AssetsDbService.cs) | Handles CRUD operations, stateful merges, JIT lookups, and active record filtering. | | **`FinlyticNews`** | Worker Service | Scraping (Playwright/RSS), deduplication, n8n AI enrichment, and news state persistence. |
| **WebSocket Handler** | [ITradeRepublicService](file:///e:/Projects/FinlyticAssets/FinlyticAssets/Services/TradeRepublicService.cs) | Wraps the connection and request dispatching for the Trade Republic API. | | **`FinlyticSentiment`** | Worker Service | FinBERT AI sentiment evaluation, ISIN/Sector sentiment aggregation over MQTT. |
| **Local Indexer** | [IAssetsIndexService](file:///e:/Projects/FinlyticAssets/FinlyticAssets/Services/AssetsIndexService.cs) | Serializes the active assets list to `index.json` for external service indexing. | | **`FinlyticAssets`** | Worker Service | Trade Republic WebSocket full-scan ingestion, asset metadata indexing (`index.json`), and ISIN JIT lookup. |
| **Messaging Broker** | `AssetsMqttClient` | Listens for incoming MQTT requests, queries the database, and responds to RPC channels. | | **`FinlyticFundamentals`** | Worker Service | Financial fundamentals scraping, SEC/Financial Modeling Prep integration, and corporate calendar events. |
| **Config Service** | [ISettingsDbService](file:///e:/Projects/FinlyticAssets/FinlyticAssets/Services/SettingsDbService.cs) | Manages scanner configurations (scan intervals, active pointers, offsets). | | **`FinlyticTechnicalAnalysis`** | Worker Service | Real-time technical indicators (RSI, MACD, EMA, Supertrend) and chart pattern detection. |
| **`FinlyticTrades`** | Worker Service | Trade lifecycle management (Active, Closed, TTL worker, Feedback exporter). |
| **`FinlyticAnalyzer`** | Worker Service | 3-layer filter engine, VIX regime tracking, win-rate calculator, and trade signal generation. |
| **`FinlyticBackend`** | Web API / Gateway | ASP.NET Core REST API, JWT authentication, SignalR Hubs (`NewsHub`, `TradeHub`), and MQTT bridge. |
| **`FinlyticWeb`** | Web Application | React/Next.js dashboard web application. |
| **`FinlyticApp`** | Mobile/Cross-Platform App | Flutter application built with Clean Architecture (`models/`, `repositories/`, `bloc/`). |
---
## Status of Implemented & Planned Features
### Implemented Features
- [x] Zero-Allocation MQTT RPC & Event Mesh across all .NET 8 microservices.
- [x] Removal of Kestrel HTTP servers from all background worker services (`FinlyticAnalyzer`, `FinlyticTrades`, etc.).
- [x] Complete removal of all mock/demo fallbacks in backend and frontend.
- [x] PostgreSQL database indexes on `PublishedAt`, `Status`, `SourceUrl`, `Isin`, `CreatedAt`.
- [x] Clean Architecture migration across all 7 modules in `FinlyticApp` (Trades, Assets, Favorites, Auth, Admin, Calendar, Search).
- [x] SignalR Real-Time Hubs (`NewsHub`, `TradeHub`) with MQTT-to-SignalR broadcasting.
### Planned Features
- [ ] Automated backtesting engine for multi-year strategy evaluation in `FinlyticAnalyzer`.
- [ ] Order Execution Integration (automated broker API order routing).
- [ ] Push Notifications for iOS/Android via Firebase Cloud Messaging in production deployment.
+56 -1
View File
@@ -1,6 +1,6 @@
# Development Rules & Guidelines # Development Rules & Guidelines
To ensure scalability, readability, and consistency across all microservices, the following architectural and coding rules must be adhered to: To ensure scalability, readability, and consistency across all microservices and frontend clients, the following architectural and coding rules must be adhered to:
## 1. Service Interfaces and Implementations ## 1. Service Interfaces and Implementations
- **Single File Co-location**: Every service interface (e.g., `IAssetsDbService`) and its corresponding implementation class (e.g., `AssetsDbService`) **must** reside in the same file. - **Single File Co-location**: Every service interface (e.g., `IAssetsDbService`) and its corresponding implementation class (e.g., `AssetsDbService`) **must** reside in the same file.
@@ -16,5 +16,60 @@ To ensure scalability, readability, and consistency across all microservices, th
- **Implementation Inheritdoc**: For class methods implementing interfaces or overriding base methods, use `/// <inheritdoc />` to inherit documentation unless customization is needed. Note that any custom helper or private methods in the implementation must still have their own explicit XML comments. - **Implementation Inheritdoc**: For class methods implementing interfaces or overriding base methods, use `/// <inheritdoc />` to inherit documentation unless customization is needed. Note that any custom helper or private methods in the implementation must still have their own explicit XML comments.
## 3. Data Class & Model Architecture ## 3. Data Class & Model Architecture
- **Strict Data Class Usage**: ALL data processing, state passing, API payloads, MQTT message schemas, and internal data transfers **must strictly use strongly-typed Data Classes** (e.g., C# `record`, `class`, or immutable DTOs / Dart Data Classes).
- **Prohibition of Loose Types**: The use of untyped data containers such as `Dictionary<string, object>`, `dynamic`, raw `JObject`/`JsonDocument`, or unstructured string-based JSON passing within internal logic is strictly forbidden.
- **Core Placement**: All data classes, DTOs, and shared enums (e.g., `AssetType`) that are or could be used by multiple services must be defined in the `FinlyticCore` project. - **Core Placement**: All data classes, DTOs, and shared enums (e.g., `AssetType`) that are or could be used by multiple services must be defined in the `FinlyticCore` project.
- **Service-Specific Exception**: Data classes and models may only reside in a specific service project (e.g., `FinlyticAssets`) if they strictly concern the internal operations of that service (e.g., raw API request/response structures for an integration that only that service manages). - **Service-Specific Exception**: Data classes and models may only reside in a specific service project (e.g., `FinlyticAssets`) if they strictly concern the internal operations of that service (e.g., raw API request/response structures for an integration that only that service manages).
## 4. Absolute Prohibition of Demo Data & Mandatory Empty-State Transparency
- **No Mocks or Fallbacks**: Mock data, demo fallbacks, hardcoded fake arrays, placeholder graphs, or dummy fallback responses are strictly forbidden across all backend microservices and frontend clients.
- **Strict Data Reality**: Either real results are available from database queries/API responses or the system must return empty result sets / raise explicit exceptions. Silent mock fallbacks are prohibited.
- **Explicit UI Zero-Data Handling**: In the frontend (Flutter/Dart), if an API call returns no data, **never show placeholder content or fake metrics**. The UI must explicitly inform the user via an unambiguous Empty State view (e.g., *"No active recommendations available"* or *"No trades found for this period"*). The user must always clearly know when data is missing versus when real data is present.
## 5. Inter-Service Communication via MQTT Only
- **Exclusive Protocol**: All internal communication between backend microservices **must** run exclusively over MQTT (Pub/Sub & RPC).
- **Single Web Gateway**: `FinlyticBackend` is the **only** microservice allowed to host Kestrel HTTP/WebSocket endpoints for external client access (`FinlyticApp`). Background microservices must never expose HTTP webservers or REST controllers.
## 6. EF Core Migrations Management
- **Command-Only Creation**: Database migrations (EF Core Migrations) must strictly be created via CLI commands (e.g., `dotnet ef migrations add <MigrationName>`). Manual hand-crafted creation of migration files is strictly prohibited.
## 7. Security & API Authorization (Backend Gateway)
- **Mandatory Route Authorization**: Every single HTTP controller endpoint or WebSocket route in `FinlyticBackend` **must** be protected with `[Authorize]` attributes (or explicitly scoped authorization policies).
- **Explicit Whitelisting Only**: Endpoints without authentication (e.g., `/api/v1/auth/login`) are the *only* allowed exception and must be explicitly marked with `[AllowAnonymous]`. Unprotected endpoints without explicit anonymous authorization are forbidden.
## 8. Dart / Flutter Frontend Architecture & JWT Handling
- **Mandatory JWT Injection**: Every outgoing HTTP/WebSocket request from the Flutter app (`FinlyticApp`) **must** include the JWT Bearer Token in the `Authorization` header (`Bearer <token>`). This must be handled centrally via HTTP Interceptors (e.g., `Dio` interceptor or custom `HttpClient`).
- **Automated Authentication Invalidation (Auto-Logout)**:
- If any API request returns an unauthenticated response (`401 Unauthorized` or `403 Forbidden`), the frontend interceptor **must immediately invalidate** the stored local JWT.
- The application state must instantly trigger an automated logout, clear user tokens/cache, and redirect the user back to the Login screen.
- Under no circumstances should the client remain in an authenticated state after receiving an invalid or expired JWT error from the gateway.
## 9. Asynchronous Programming Guidelines
- **Async All the Way**: Avoid blocking asynchronous code using `.Result`, `.Wait()`, or `.GetAwaiter().GetResult()`. Use `async`/`await` consistently throughout the call stack to prevent thread pool starvation.
- **Cancellation Tokens**: All asynchronous methods interacting with DBs, network/MQTT calls, or external APIs **must** accept a `CancellationToken` as their last parameter and pass it down to underlying async calls.
- **ValueTask Usage**: Prefer `ValueTask<T>` over `Task<T>` for hot-path methods that frequently complete synchronously (e.g., cached database lookups).
## 10. Logging and Diagnostics
- **Channel-Based Logging**: Logging must be organized into logical, domain-specific channels (e.g., `MqttChannel`, `DatabaseChannel`, `AnalyzerChannel`, `StrategyChannel`, `RiskEngineChannel`).
- **Dynamic Toggle Control**: Every log channel must be configurable at runtime. Microservices must react to channel configuration changes without requiring a service restart (e.g., via `IOptionsMonitor<T>` or MQTT configuration broadcast events).
- **Admin Panel Integration**: The state of each logging channel (Enabled / Disabled / LogLevel) must be exposed to and manageable from the `FinlyticBackend` Admin Panel.
- **Structured Logging**: Always use structured logging with named placeholders (e.g., `_logger.LogInformation("[{Channel}] Processing recommendation for asset {Symbol}", "AnalyzerChannel", symbol)` instead of string interpolation `$"{symbol}"`).
- **Log Levels Rules**:
- `Trace`/`Debug`: Fine-grained internal flow, payload dumps, and low-level MQTT events.
- `Information`: Business-relevant milestones (e.g., recommendation generated, manual trade validated).
- `Warning`: Recoverable issues or expected unexpected behavior (e.g., transient network retries, rate limits hit).
- `Error`: Unhandled exceptions, failed DB queries, or lost service connections requiring attention.
- **No Sensitive Data**: Never log raw authorization tokens, passwords, or personal credentials.
## 11. Error Handling and Resilience
- **Explicit Exception Handling**: Catch specific exceptions rather than `System.Exception`. Always log the caught exception with full stack trace using `_logger.LogError(ex, "Message")`.
- **Global Error Handling in Gateway**: `FinlyticBackend` must handle failures gracefully using standardized error responses (e.g., Problem Details). Background services must fail fast or retry safely using resilience pipelines (e.g., Polly for MQTT reconnection/API retries).
- **Result Pattern**: For domain operations where failure is an expected outcome (e.g., trade validation failed due to missing inputs), prefer returning a typed Result object (`Result<T>`) instead of throwing control-flow exceptions.
## 12. Configuration & Environment Management
- **Strongly-Typed Settings**: All service configurations (e.g., MQTT broker settings, API keys, database connection strings) must be bound to strongly-typed options classes using `IOptions<T>` or `IOptionsMonitor<T>`.
- **No Hardcoded Secrets**: Secrets, connection strings, and private keys must never be hardcoded or checked into source control. Use environment variables or local user secrets (`appsettings.Local.json` excluded via `.gitignore`).
## 13. Testing and Verification Standards
- **No Production Code Mocks**: Unit tests and integration tests must be located in dedicated test projects (e.g., `FinlyticCore.Tests`). Do not include test-only mock classes inside production assemblies.
- **Deterministic Testing**: Integration tests connecting to real databases or MQTT brokers must run against isolated local containers (e.g., Testcontainers or isolated local DB instances) to ensure non-destructive and predictable test execution.
+101 -1
View File
@@ -1,4 +1,4 @@
services: services:
finlyticassets: finlyticassets:
image: finlyticassets image: finlyticassets
build: build:
@@ -15,6 +15,7 @@
- MQTT__ClientId=finlytic_assets - MQTT__ClientId=finlytic_assets
volumes: volumes:
- C:\Users\larsh\Documents\docker\finlytic\assets\index:/app/assets/index - C:\Users\larsh\Documents\docker\finlytic\assets\index:/app/assets/index
- C:\Users\larsh\Documents\docker\finlytic\assets\logos:/app/assets/logos
finlyticnews: finlyticnews:
image: finlyticnews image: finlyticnews
@@ -30,6 +31,105 @@
#- MQTT__Username=admin #- MQTT__Username=admin
#- MQTT__Password=${MQTT_PASSWORD} #- MQTT__Password=${MQTT_PASSWORD}
- MQTT__ClientId=finlytic_news - MQTT__ClientId=finlytic_news
- N8N__ArticleExtractionUrl=${ARTICLE_EXTRACTION_URL}
volumes:
- C:\Users\larsh\Documents\docker\finlytic\assets\index:/app/assets/index:ro
- C:\Users\larsh\Documents\docker\finlytic\data\summaries:/app/data/summaries:ro
finlyticfundamentals:
image: finlyticfundamentals
build:
context: .
dockerfile: FinlyticFundamentals/Dockerfile
networks:
- postgres-network
environment:
- ConnectionStrings__DefaultConnection=Host=OmniDB;Database=finlytic_fundamentals;Username=admin;Password=${DB_PASSWORD}
- MQTT__Host=host.docker.internal
- MQTT__Port=4545
- MQTT__ClientId=finlytic_fundamentals
finlyticsentiment:
image: finlyticsentiment
build:
context: .
dockerfile: FinlyticSentiment/Dockerfile
networks:
- postgres-network
environment:
- ConnectionStrings__DefaultConnection=Host=OmniDB;Database=finlytic_sentimental;Username=admin;Password=${DB_PASSWORD}
- MQTT__Host=host.docker.internal
- MQTT__Port=4545
- MQTT__ClientId=finlytic_sentiment
- Webhooks__German=https://n8n.kleidukos.me/webhook/sentiment/de
- Webhooks__English=https://n8n.kleidukos.me/webhook/sentiment/en
volumes:
- C:\Users\larsh\Documents\docker\finlytic\data\summaries:/app/data/summaries
finlytictechnicalanalysis:
image: finlytictechnicalanalysis
build:
context: .
dockerfile: FinlyticTechnicalAnalysis/Dockerfile
networks:
- postgres-network
environment:
- ConnectionStrings__DefaultConnection=Host=OmniDB;Database=finlytic_ta;Username=admin;Password=${DB_PASSWORD}
- MQTT__Host=host.docker.internal
- MQTT__Port=4545
- MQTT__ClientId=finlytic_ta
finlyticanalyzer:
image: finlyticanalyzer
build:
context: .
dockerfile: FinlyticAnalyzer/Dockerfile
networks:
- postgres-network
environment:
- ConnectionStrings__DefaultConnection=Host=OmniDB;Database=finlytic_analyzer;Username=admin;Password=${DB_PASSWORD}
- MQTT__Host=host.docker.internal
- MQTT__Port=4545
- MQTT__ClientId=finlytic_analyzer
volumes:
- C:\Users\larsh\Documents\docker\finlytic\data\feedback:/app/data/feedback:ro
finlytictrades:
image: finlytictrades
build:
context: .
dockerfile: FinlyticTrades/Dockerfile
networks:
- postgres-network
environment:
- ConnectionStrings__DefaultConnection=Host=OmniDB;Database=finlytic_trades;Username=admin;Password=${DB_PASSWORD}
- MQTT__Host=host.docker.internal
- MQTT__Port=4545
- MQTT__ClientId=finlytic_trades
volumes:
- C:\Users\larsh\Documents\docker\finlytic\data\feedback:/app/data/feedback
finlyticbackend:
image: finlyticbackend
build:
context: .
dockerfile: FinlyticBackend/Dockerfile
ports:
- "5000:8080"
networks:
- postgres-network
environment:
- ConnectionStrings__DefaultConnection=Host=OmniDB;Database=finlytic_backend;Username=admin;Password=${DB_PASSWORD}
- MQTT__Host=host.docker.internal
- MQTT__Port=4545
- MQTT__ClientId=finlytic_backend
- JWT__SecretKey=${JWT_SECRET_KEY:-FinlyticEnterpriseUltraSecureJwtSecretKey_2026_AtLeast32Chars!}
- ADMIN__DefaultPassword=${ADMIN_DEFAULT_PASSWORD:-AdminDefaultPassword2026!}
- Services__TradesServiceUrl=http://finlytictrades:8080/api/v1/trades/active
volumes:
- C:\Users\larsh\Documents\docker\finlytic\assets\index:/app/assets/index
networks: networks:
postgres-network: postgres-network:
external: true external: true
+47
View File
@@ -0,0 +1,47 @@
# rebuild-playwright-base.ps1
# ─────────────────────────────────────────────────────────────────────────────
# Rebuild the Playwright base image for FinlyticNews.
#
# Run this script ONLY when you update the Playwright NuGet package version.
# After running this, a normal `docker compose build` will be fast again.
#
# Usage:
# .\rebuild-playwright-base.ps1
# .\rebuild-playwright-base.ps1 -Version 1.50.0 # when upgrading Playwright
# ─────────────────────────────────────────────────────────────────────────────
param(
[string]$Version = "1.49.0"
)
$ImageName = "finlytic-playwright-base:$Version"
$Dockerfile = "FinlyticNews/Dockerfile.playwright-base"
$Context = "FinlyticNews"
Write-Host ""
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
Write-Host " Building Playwright base image: $ImageName" -ForegroundColor Cyan
Write-Host " This only needs to run when the Playwright version changes." -ForegroundColor DarkGray
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
Write-Host ""
docker build `
-f $Dockerfile `
--build-arg PLAYWRIGHT_VERSION=$Version `
-t $ImageName `
$Context
if ($LASTEXITCODE -eq 0) {
Write-Host ""
Write-Host "✅ Base image '$ImageName' built and cached locally." -ForegroundColor Green
Write-Host " You can now run 'docker compose build' as usual." -ForegroundColor Green
Write-Host ""
# Also tag as 'latest' for convenience
docker tag $ImageName "finlytic-playwright-base:latest"
Write-Host " Tagged as 'finlytic-playwright-base:latest' as well." -ForegroundColor DarkGray
} else {
Write-Host ""
Write-Host "❌ Build failed. See errors above." -ForegroundColor Red
exit 1
}
+29
View File
@@ -0,0 +1,29 @@
import re
file_path = r"E:\Projects\Finlytic\FinlyticCore\Dtos\Fundamentals\AssetFundamentalsDto.cs"
with open(file_path, "r", encoding="utf-8") as f:
lines = f.readlines()
out_lines = []
has_using = any("using System.Text.Json.Serialization;" in l for l in lines)
if not has_using:
for i, line in enumerate(lines):
if "using System;" in line:
out_lines.append(line)
out_lines.append("using System.Text.Json.Serialization;\n")
lines = lines[i+1:]
break
for i, line in enumerate(lines):
match = re.search(r'^(\s*)public (.+?) ([A-Z][a-zA-Z0-9_]*)( \{.*)$', line)
if match and " record " not in line and " class " not in line:
# Check if previous line has JsonPropertyName
if i == 0 or "JsonPropertyName" not in lines[i-1]:
indent = match.group(1)
prop_name = match.group(3)
camel_name = prop_name[0].lower() + prop_name[1:]
out_lines.append(f'{indent}[JsonPropertyName("{camel_name}")]\n')
out_lines.append(line)
with open(file_path, "w", encoding="utf-8") as f:
f.writelines(out_lines)