feat(assets): update scanner background service, derivative entities, and dynamic settings

This commit is contained in:
2026-08-14 23:55:47 +02:00
parent 2151fd89f0
commit 4d5ab09bbd
18 changed files with 1318 additions and 71 deletions
+78 -2
View File
@@ -1,7 +1,6 @@
using FinlyticAssets.Database;
using FinlyticAssets.Entities;
using FinlyticCore.Entities.Assets;
using FinlyticCore.Models.TradeRepublic;
using FinlyticCore.Dtos.TradeRepublic;
using FinlyticCore.Services.TradeRepublic;
using Microsoft.EntityFrameworkCore;
@@ -21,6 +20,7 @@ public interface IAssetsDbService
public Task UpdateAssetImageIdAsync(string isin, string imageId);
public Task<bool> DeleteAssetAsync(string isin);
public Task<List<AssetEntity>> GetDiscoveryAssetsAsync(int limit = 15);
public Task<List<DerivativeEntity>> GetDerivativesByUnderlyingAsync(string underlyingIsin, string optionType = "long", bool forceRefresh = false, CancellationToken cancellationToken = default);
}
/// <inheritdoc />
@@ -393,6 +393,82 @@ public class AssetsDbService : IAssetsDbService
return true;
}
/// <summary>Inherits documentation from interface.</summary>
public async Task<List<DerivativeEntity>> GetDerivativesByUnderlyingAsync(string underlyingIsin, string optionType = "long", bool forceRefresh = false, CancellationToken cancellationToken = default)
{
var targetOptionType = optionType.Equals("short", StringComparison.OrdinalIgnoreCase) ? OptionType.Short : OptionType.Long;
var cutoff = DateTime.UtcNow.AddDays(-7);
if (!forceRefresh)
{
var cached = await _context.TradeRepublicAssets
.OfType<DerivativeEntity>()
.AsNoTracking()
.Include(a => a.Tags)
.Where(d => d.UnderlyingIsin == underlyingIsin && d.OptionType == targetOptionType && d.LastUpdatedAt >= cutoff)
.ToListAsync(cancellationToken);
if (cached.Count > 0)
{
return cached;
}
}
var trReq = new TradeRepublicDerivativesRequest(Underlying: underlyingIsin, OptionType: optionType, ProductCategory: "knockOutProduct", PageSize: 50, After: "0");
var trResponse = await _tradeRepublicService.GetDerivativesAsync(trReq, cancellationToken);
if (trResponse?.Results != null && trResponse.Results.Count > 0)
{
var now = DateTime.UtcNow;
var isins = trResponse.Results.Select(r => r.Isin).Distinct().ToList();
var existingDerivatives = await _context.TradeRepublicAssets
.OfType<DerivativeEntity>()
.Where(d => isins.Contains(d.Isin))
.ToDictionaryAsync(d => d.Isin, cancellationToken);
foreach (var item in trResponse.Results)
{
if (!existingDerivatives.TryGetValue(item.Isin, out var entity))
{
entity = new DerivativeEntity
{
Isin = item.Isin,
InstrumentCategory = "derivative",
Type = "derivative"
};
await _context.TradeRepublicAssets.AddAsync(entity, cancellationToken);
}
entity.UnderlyingIsin = underlyingIsin;
entity.OptionType = item.OptionType.Equals("short", StringComparison.OrdinalIgnoreCase) ? OptionType.Short : OptionType.Long;
entity.ProductCategoryName = item.ProductCategoryName;
entity.NextGenProductCategoryName = item.NextGenProductCategoryName;
entity.Strike = item.Strike ?? 0m;
entity.Barrier = item.Barrier ?? 0m;
entity.Leverage = item.Leverage ?? 0m;
entity.Size = item.Size;
entity.Factor = item.Factor;
entity.Delta = item.Delta;
entity.Currency = item.Currency ?? "EUR";
entity.Expiry = DateTime.TryParse(item.Expiry, out var exp) ? exp : null;
entity.Issuer = item.Issuer;
entity.IssuerDisplayName = item.IssuerDisplayName;
entity.IssuerImageId = item.IssuerImageId;
entity.ImageId = item.ImageId;
entity.Name = $"{item.IssuerDisplayName} {item.NextGenProductCategoryName} ({item.OptionType.ToUpper()})";
entity.LastUpdatedAt = now;
}
await _context.SaveChangesAsync(cancellationToken);
}
return await _context.TradeRepublicAssets
.OfType<DerivativeEntity>()
.AsNoTracking()
.Include(a => a.Tags)
.Where(d => d.UnderlyingIsin == underlyingIsin && d.OptionType == targetOptionType)
.ToListAsync(cancellationToken);
}
#region Helper & Mapping Methods
private AssetEntity MapDtoToEntity(TradeRepublicAsset dto)