feat(core): add internal crypto isin resolution and subtitle mapping

This commit is contained in:
2026-08-15 19:29:32 +02:00
parent 5a6a50a609
commit 067f1bfdd5
9 changed files with 258 additions and 11 deletions
@@ -125,7 +125,7 @@ public class TechnicalAnalysisDbService : ITechnicalAnalysisDbService
var macroTask = FetchMacroDataAsync(cancellationToken);
string? ticker = requestedTicker;
if (string.IsNullOrWhiteSpace(ticker))
if (string.IsNullOrWhiteSpace(ticker) || string.Equals(ticker.Trim(), cleanIsin, StringComparison.OrdinalIgnoreCase))
{
ticker = await _yahooScraper.ResolveTickerFromIsinAsync(cleanIsin, cancellationToken);
}
@@ -326,7 +326,9 @@ public class TechnicalAnalysisDbService : ITechnicalAnalysisDbService
if (cached != null && DateTime.UtcNow - cached.CalculatedAt < DbCacheTtl)
{
if (!string.IsNullOrWhiteSpace(requestedTicker) && !string.Equals(cached.Ticker, requestedTicker, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(requestedTicker) &&
!string.Equals(requestedTicker.Trim(), cleanIsin, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(cached.Ticker, requestedTicker, StringComparison.OrdinalIgnoreCase))
{
return null; // Ticker mismatch, force refresh required
}
@@ -40,16 +40,21 @@ public interface IYahooMarketDataScraper
public class YahooMarketDataScraper : IYahooMarketDataScraper
{
private readonly YahooFinanceClient _yahooClient;
private readonly Microsoft.Extensions.Configuration.IConfiguration _configuration;
private readonly ILogger<YahooMarketDataScraper> _logger;
public YahooMarketDataScraper(YahooFinanceClient yahooClient, ILogger<YahooMarketDataScraper> logger)
public YahooMarketDataScraper(
YahooFinanceClient yahooClient,
Microsoft.Extensions.Configuration.IConfiguration configuration,
ILogger<YahooMarketDataScraper> logger)
{
_yahooClient = yahooClient;
_configuration = configuration;
_logger = logger;
}
/// <summary>
/// Resolves ticker from ISIN using Yahoo Search API.
/// Resolves ticker from ISIN using Yahoo Search API or Crypto Subtitle resolution for internal ISINs.
/// </summary>
public async Task<string?> ResolveTickerFromIsinAsync(string isin, CancellationToken cancellationToken = default)
{
@@ -61,6 +66,34 @@ public class YahooMarketDataScraper : IYahooMarketDataScraper
return cleanIsin;
}
// Crypto / Trade Republic interne ISINs (beginnend mit 'X', z. B. XF000BTC0017)
if (cleanIsin.StartsWith("X", StringComparison.OrdinalIgnoreCase))
{
var (cryptoSubtitle, cryptoName) = await FinlyticCore.Utils.CryptoSubtitleResolver.ResolveCryptoInfoAsync(
cleanIsin, _configuration.GetConnectionString("DefaultConnection"), cancellationToken);
if (!string.IsNullOrWhiteSpace(cryptoSubtitle))
{
var candidates = new[] { $"{cryptoSubtitle}-EUR", $"{cryptoSubtitle}-USD", cryptoSubtitle };
foreach (var candidate in candidates)
{
try
{
var res = await FetchHistoricalCandlesWithCurrencyAsync(candidate, "5d", "1d", cancellationToken);
if (res.Candles.Count > 0)
{
_logger.LogInformation("[{Channel}] Resolved Crypto ISIN {Isin} to {Symbol} using Subtitle {Sub}",
"TechnicalAnalysisChannel", cleanIsin, candidate, cryptoSubtitle);
return candidate;
}
}
catch { }
}
return $"{cryptoSubtitle}-EUR";
}
}
try
{
var searchResult = await _yahooClient.SearchAsync(cleanIsin, quotesCount: 10, newsCount: 0, cancellationToken);