feat(core): update DTOs, Trade Republic client, Yahoo scrapers, and dynamic settings

This commit is contained in:
2026-08-14 23:55:02 +02:00
parent 3d8af3940b
commit 4f733bf8c3
511 changed files with 1990 additions and 1080548 deletions
@@ -25,4 +25,7 @@ public class ManualAnalysisResponseDto
[JsonPropertyName("proposal")]
public TradeProposalDto? Proposal { get; set; }
[JsonPropertyName("message")]
public string Message { get; set; } = string.Empty;
}
@@ -0,0 +1,20 @@
namespace FinlyticCore.Models.Settings;
/// <summary>
/// Globale, mikroservice-übergreifende SettingKeys in FinlyticCore.
/// </summary>
public static class CoreSettingKeys
{
// --- Logging-Kanäle ---
public static readonly SettingKey<bool> HealthPingChannel = new("Logging.Channel.Health", true);
public static readonly SettingKey<bool> MqttChannel = new("Logging.Channel.MQTT", true);
public static readonly SettingKey<bool> HtmlScrapperChannel = new("Logging.Channel.HtmlScrapper", true);
public static readonly SettingKey<bool> YahooClientChannel = new("Logging.Channel.YahooClient", true);
public static readonly SettingKey<bool> FundamentalsChannel = new("Logging.Channel.Fundamentals", true);
// --- Scraper & Feature-Toggles ---
public static readonly SettingKey<bool> EnableHtmlFallback = new("Feature.EnableHtmlFallback", true);
public static readonly SettingKey<bool> AllowForceRefresh = new("Feature.AllowForceRefresh", true);
public static readonly SettingKey<int> ScraperTimeoutSeconds = new("Scraper.TimeoutSeconds", 30);
public static readonly SettingKey<int> ScraperMaxRetries = new("Scraper.MaxRetries", 2);
}
@@ -0,0 +1,9 @@
namespace FinlyticCore.Models.Settings;
public enum LogLevelEnum
{
None,
Debug,
Info,
Error
}
@@ -0,0 +1,6 @@
namespace FinlyticCore.Models.Settings;
/// <summary>
/// Verknüpft einen Setting-Key typsicher mit seinem Rückgabetyp T und einem Standardwert.
/// </summary>
public record SettingKey<T>(string Name, T DefaultValue);
@@ -1,139 +0,0 @@
namespace FinlyticCore.Models.TradeRepublic;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
// 1. Der Response-Wrapper
public record TradeRepublicAssetResponse(
[property: JsonPropertyName("correlationId")] string CorrelationId,
[property: JsonPropertyName("resultCount")] int ResultCount,
[property: JsonPropertyName("results")] IList<TradeRepublicAsset> Results
);
// 2. Das Tag-Objekt
public record TradeRepublicTag
{
[JsonPropertyName("id")] public string Id { get; init; } = "";
[JsonPropertyName("name")] public string Name { get; init; } = "";
[JsonPropertyName("type")] public string Type { get; init; } = "";
}
// 3. Die Basisklasse MIT UNSEREM CUSTOM CONVERTER (Kein [JsonPolymorphic] mehr!)
[JsonConverter(typeof(TradeRepublicAssetConverter))]
public record TradeRepublicAsset
{
[JsonPropertyName("isin")] public string Isin { get; init; } = "";
[JsonPropertyName("name")] public string Name { get; init; } = "";
[JsonPropertyName("type")] public string Type { get; init; } = "";
[JsonPropertyName("instrumentCategory")] public string InstrumentCategory { get; init; } = "";
[JsonPropertyName("hasCfd")] public bool HasCfd { get; init; }
[JsonPropertyName("imageId")] public string? ImageId { get; init; }
[JsonPropertyName("tags")]
public IReadOnlyList<TradeRepublicTag> Tags { get; init; } = Array.Empty<TradeRepublicTag>();
}
// 4. Die spezifischen Klassen (inklusive Bond und Derivative aus deinem JSON!)
public record TradeRepublicStock : TradeRepublicAsset
{
[JsonPropertyName("derivativeProductCategories")]
public IReadOnlyList<string> DerivativeProductCategories { get; init; } = Array.Empty<string>();
}
public record TradeRepublicCrypto : TradeRepublicAsset
{
[JsonPropertyName("subtitle")] public string Subtitle { get; init; } = "";
[JsonPropertyName("searchSubtitle")] public string SearchSubtitle { get; init; } = "";
}
public record TradeRepublicEtf : TradeRepublicAsset
{
[JsonPropertyName("derivativeProductCategories")]
public IReadOnlyList<string> DerivativeProductCategories { get; init; } = Array.Empty<string>();
[JsonPropertyName("etfDescription")] public string EtfDescription { get; init; } = "";
[JsonPropertyName("mappedEtfIndexName")] public string MappedEtfIndexName { get; init; } = "";
[JsonPropertyName("subtitle")] public string Subtitle { get; init; } = "";
[JsonPropertyName("searchSubtitle")] public string SearchSubtitle { get; init; } = "";
}
public record TradeRepublicSynthetic : TradeRepublicAsset
{
[JsonPropertyName("derivativeProductCategories")]
public IReadOnlyList<string> DerivativeProductCategories { get; init; } = Array.Empty<string>();
}
// NEU: Anleihen
public record TradeRepublicBond : TradeRepublicAsset
{
[JsonPropertyName("bondIssuerName")] public string BondIssuerName { get; init; } = "";
[JsonPropertyName("searchSubtitle")] public string SearchSubtitle { get; init; } = "";
}
// NEU: Derivate (Hebeleffekte etc.)
public record TradeRepublicDerivative : TradeRepublicAsset
{
[JsonPropertyName("derivativeProductCategories")]
public IReadOnlyList<string> DerivativeProductCategories { get; init; } = Array.Empty<string>();
[JsonIgnore]
public string? UnderlyingIsin
{
get
{
// Wenn die ImageId z.B. "logos/US0378331005/v2" ist...
if (!string.IsNullOrEmpty(ImageId) && ImageId.StartsWith("logos/"))
{
var parts = ImageId.Split('/');
if (parts.Length >= 2)
{
return parts[1]; // Gibt "US0378331005" zurück
}
}
return null; // Falls das Format mal anders ist
}
}
}
// 5. Der Custom Converter - Die Maschine, die das JSON scannt und verteilt
public class TradeRepublicAssetConverter : JsonConverter<TradeRepublicAsset>
{
public override TradeRepublicAsset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var doc = JsonDocument.ParseValue(ref reader);
var root = doc.RootElement;
// Wir scannen nach instrumentType, egal wo im JSON es steht!
string? instrumentType = null;
if (root.TryGetProperty("instrumentType", out var typeElement))
{
instrumentType = typeElement.GetString();
}
// Wir werfen das JSON gezielt in die richtige Klasse
TradeRepublicAsset? result = instrumentType switch
{
"stock" => JsonSerializer.Deserialize<TradeRepublicStock>(root.GetRawText(), options),
"crypto" => JsonSerializer.Deserialize<TradeRepublicCrypto>(root.GetRawText(), options),
"fund" => JsonSerializer.Deserialize<TradeRepublicEtf>(root.GetRawText(), options),
"synthetic" => JsonSerializer.Deserialize<TradeRepublicSynthetic>(root.GetRawText(), options),
"bond" => JsonSerializer.Deserialize<TradeRepublicBond>(root.GetRawText(), options),
"derivative" => JsonSerializer.Deserialize<TradeRepublicDerivative>(root.GetRawText(), options),
// Wenn TR einen Typ schickt, den wir noch nicht kennen: Fallback nutzen!
_ => JsonSerializer.Deserialize<TradeRepublicAssetFallback>(root.GetRawText(), options)
};
return result ?? new TradeRepublicAssetFallback();
}
public override void Write(Utf8JsonWriter writer, TradeRepublicAsset value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
// Ein reiner Fallback-Record, der nur intern vom Converter genutzt wird
file record TradeRepublicAssetFallback : TradeRepublicAsset;
@@ -1,16 +0,0 @@
using System.Text.Json.Serialization;
namespace FinlyticCore.Models.TradeRepublic;
public record TradeRepublicConnectRequest(
[property: JsonPropertyName("clientId")] string ClientId = "app.traderepublic.com",
[property: JsonPropertyName("clientVersion")] string ClientVersion = "15.65.6",
[property: JsonPropertyName("locale")] string Locale = "en",
[property: JsonPropertyName("platformId")] string PlatformId = "webtrading",
[property: JsonPropertyName("platformVersion")] string PlatformVersion = "chrome - 149.0.0",
TradeRepublicHeaders? Headers = null
)
{
[JsonPropertyName("__headers")]
public TradeRepublicHeaders Headers { get; init; } = Headers ?? new TradeRepublicHeaders();
}
@@ -1,12 +0,0 @@
using System.Text.Json.Serialization;
using FinlyticCore.Util;
namespace FinlyticCore.Models.TradeRepublic;
public record TradeRepublicHeaders(
[property: JsonPropertyName("traceparent")] string Traceparent
)
{
public TradeRepublicHeaders() : this(StringCodeGenerator.GenerateTraceparent())
{}
}
@@ -1,29 +0,0 @@
using System.Text.Json.Serialization;
namespace FinlyticCore.Models.TradeRepublic;
public record TradeRepublicFilter(
[property: JsonPropertyName("key")] string Key,
[property: JsonPropertyName("value")] string Value
);
public record TradeRepublicSearchData(
[property: JsonPropertyName("q")] string Query = "",
[property: JsonPropertyName("page")] int Page = 1,
[property: JsonPropertyName("pageSize")] int PageSize = 50,
IReadOnlyList<TradeRepublicFilter>? Filter = null
)
{
[JsonPropertyName("filter")]
public IReadOnlyList<TradeRepublicFilter> Filter { get; init; } = Filter ?? Array.Empty<TradeRepublicFilter>();
}
public record TradeRepublicSearchRequest(
[property: JsonPropertyName("data")] TradeRepublicSearchData Data,
[property: JsonPropertyName("type")] string Type = "neonSearch",
TradeRepublicHeaders? Headers = null
)
{
[JsonPropertyName("__headers")]
public TradeRepublicHeaders Headers { get; init; } = Headers ?? new TradeRepublicHeaders();
}
@@ -1,13 +0,0 @@
using System.Text.Json.Serialization;
namespace FinlyticCore.Models.TradeRepublic;
public record TradeRepublicTickerRequest(
[property: JsonPropertyName("id")] string Id, // e.g. "US5398301094.TIB"
[property: JsonPropertyName("type")] string Type = "ticker",
TradeRepublicHeaders? Headers = null
)
{
[JsonPropertyName("__headers")]
public TradeRepublicHeaders Headers { get; init; } = Headers ?? new TradeRepublicHeaders();
}
@@ -1,26 +0,0 @@
using System;
using System.Globalization;
using System.Text.Json.Serialization;
namespace FinlyticCore.Models.TradeRepublic;
public record TradeRepublicPriceTick(
[property: JsonPropertyName("time")] long Time,
[property: JsonPropertyName("price")] string Price,
[property: JsonPropertyName("size")] decimal Size
)
{
public decimal PriceValue => decimal.TryParse(Price, NumberStyles.Any, CultureInfo.InvariantCulture, out var v) ? v : 0m;
public DateTime DateTimeUtc => DateTimeOffset.FromUnixTimeMilliseconds(Time).UtcDateTime;
}
public record TradeRepublicTickerResponse(
[property: JsonPropertyName("bid")] TradeRepublicPriceTick? Bid,
[property: JsonPropertyName("ask")] TradeRepublicPriceTick? Ask,
[property: JsonPropertyName("last")] TradeRepublicPriceTick? Last,
[property: JsonPropertyName("pre")] TradeRepublicPriceTick? Pre,
[property: JsonPropertyName("open")] TradeRepublicPriceTick? Open,
[property: JsonPropertyName("qualityId")] string? QualityId,
[property: JsonPropertyName("leverage")] decimal? Leverage,
[property: JsonPropertyName("delta")] decimal? Delta
);
@@ -1,31 +1,79 @@
using System;
using System.Text.Json.Serialization;
namespace FinlyticCore.Models.Trades;
public class TradeAcceptanceDto
{
[JsonPropertyName("tradeId")]
public string TradeId { get; set; } = string.Empty;
[JsonPropertyName("analysisId")]
public string AnalysisId { get; set; } = string.Empty;
[JsonPropertyName("isin")]
public string Isin { get; set; } = string.Empty;
[JsonPropertyName("userId")]
public string? UserId { get; set; } = "default_user";
[JsonPropertyName("companyName")]
public string? CompanyName { get; set; }
[JsonPropertyName("sector")]
public string? Sector { get; set; }
[JsonPropertyName("actualEntryPrice")]
public decimal? ActualEntryPrice { get; set; }
[JsonPropertyName("positionSize")]
public decimal? PositionSize { get; set; }
[JsonPropertyName("leverageUsed")]
public decimal? LeverageUsed { get; set; } = 1;
[JsonPropertyName("entryFee")]
public decimal? EntryFee { get; set; } = 0;
[JsonPropertyName("exitFee")]
public decimal? ExitFee { get; set; } = 0;
[JsonPropertyName("symbol")]
public string? Symbol { get; set; }
[JsonPropertyName("signalType")]
public string? SignalType { get; set; }
[JsonPropertyName("entryPrice")]
public decimal? EntryPrice { get; set; }
[JsonPropertyName("stopLoss")]
public decimal? StopLoss { get; set; }
[JsonPropertyName("takeProfit")]
public decimal? TakeProfit { get; set; }
[JsonPropertyName("instrumentType")]
public string? InstrumentType { get; set; }
[JsonPropertyName("derivativeIsin")]
public string? DerivativeIsin { get; set; }
[JsonPropertyName("timeframe")]
public string? Timeframe { get; set; }
[JsonPropertyName("reasoning")]
public string? Reasoning { get; set; }
[JsonPropertyName("executionTimestamp")]
public DateTime? ExecutionTimestamp { get; set; }
[JsonPropertyName("quantity")]
public decimal? Quantity { get; set; }
[JsonPropertyName("knockoutThreshold")]
public decimal? KnockoutThreshold { get; set; }
[JsonPropertyName("isRecurring")]
public bool IsRecurring { get; set; } = false;
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using FinlyticCore.Models.Analyzer;
namespace FinlyticCore.Models.Trades;
@@ -9,55 +10,137 @@ namespace FinlyticCore.Models.Trades;
/// </summary>
public class TradeProposalDto
{
[JsonPropertyName("tradeId")]
public string TradeId { get; set; } = string.Empty;
[JsonPropertyName("userId")]
public string? UserId { get; set; }
[JsonPropertyName("isGlobalProposal")]
public bool IsGlobalProposal { get; set; } = true;
[JsonPropertyName("status")]
public string Status { get; set; } = "Proposed";
[JsonPropertyName("analysisId")]
public string AnalysisId { get; set; } = string.Empty;
[JsonPropertyName("eventId")]
public string EventId { get; set; } = string.Empty;
[JsonPropertyName("sector")]
public string Sector { get; set; } = string.Empty;
[JsonPropertyName("symbol")]
public string Symbol { get; set; } = string.Empty;
[JsonPropertyName("isin")]
public string Isin { get; set; } = string.Empty;
[JsonPropertyName("companyName")]
public string CompanyName { get; set; } = string.Empty;
[JsonPropertyName("entryPrice")]
public decimal EntryPrice { get; set; }
[JsonPropertyName("stopLoss")]
public decimal StopLoss { get; set; }
[JsonPropertyName("takeProfit")]
public decimal TakeProfit { get; set; }
[JsonPropertyName("signalType")]
public string SignalType { get; set; } = "BUY"; // "BUY", "SELL"
[JsonPropertyName("riskTolerance")]
public string RiskTolerance { get; set; } = "Moderate"; // "Conservative", "Moderate", "Aggressive"
[JsonPropertyName("timeframe")]
public string Timeframe { get; set; } = "1D"; // "1H", "4H", "1D", "1W"
[JsonPropertyName("instrumentType")]
public string InstrumentType { get; set; } = "Stock"; // "Stock", "Option", "CFD", "Crypto"
[JsonPropertyName("derivativeIsin")]
public string? DerivativeIsin { get; set; }
[JsonPropertyName("winRate")]
public double WinRate { get; set; }
[JsonPropertyName("vixRegime")]
public VixMarketRegime VixRegime { get; set; }
[JsonPropertyName("vixValue")]
public decimal VixValue { get; set; }
[JsonPropertyName("ttlMinutes")]
public int TtlMinutes { get; set; } = 60;
[JsonPropertyName("reasoning")]
public string Reasoning { get; set; } = string.Empty;
// --- New Fields for Detailed Execution & Rationale ---
[JsonPropertyName("entryZoneMin")]
public decimal? EntryZoneMin { get; set; }
[JsonPropertyName("entryZoneMax")]
public decimal? EntryZoneMax { get; set; }
[JsonPropertyName("takeProfitTargets")]
public List<decimal>? TakeProfitTargets { get; set; }
[JsonPropertyName("riskRewardRatio")]
public decimal? RiskRewardRatio { get; set; }
[JsonPropertyName("maxLeverage")]
public decimal? MaxLeverage { get; set; }
[JsonPropertyName("technicalRationale")]
public string TechnicalRationale { get; set; } = string.Empty;
[JsonPropertyName("fundamentalRationale")]
public string FundamentalRationale { get; set; } = string.Empty;
[JsonPropertyName("riskWarning")]
public string RiskWarning { get; set; } = string.Empty;
// --- Real Trade Execution Data ---
[JsonPropertyName("actualEntryPrice")]
public decimal? ActualEntryPrice { get; set; }
[JsonPropertyName("positionSize")]
public decimal? PositionSize { get; set; }
[JsonPropertyName("leverageUsed")]
public decimal? LeverageUsed { get; set; }
[JsonPropertyName("entryFee")]
public decimal? EntryFee { get; set; }
[JsonPropertyName("exitFee")]
public decimal? ExitFee { get; set; }
[JsonPropertyName("executionTimestamp")]
public DateTime? ExecutionTimestamp { get; set; }
[JsonPropertyName("quantity")]
public decimal? Quantity { get; set; }
[JsonPropertyName("knockoutThreshold")]
public decimal? KnockoutThreshold { get; set; }
[JsonPropertyName("isRecurring")]
public bool IsRecurring { get; set; } = false;
[JsonPropertyName("currentPrice")]
public decimal? CurrentPrice { get; set; }
[JsonPropertyName("pnlAbsolute")]
public decimal? PnlAbsolute { get; set; }
[JsonPropertyName("pnlPercent")]
public decimal? PnlPercent { get; set; }
[JsonPropertyName("createdAt")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}