546 lines
21 KiB
C#
546 lines
21 KiB
C#
using FinlyticAssets.Database;
|
|
using FinlyticAssets.Entities;
|
|
using FinlyticCore.Dtos.TradeRepublic;
|
|
using FinlyticCore.Services.TradeRepublic;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace FinlyticAssets.Services;
|
|
|
|
/// <summary>
|
|
/// Defines database operations for managing Trade Republic asset entities.
|
|
/// </summary>
|
|
public interface IAssetsDbService
|
|
{
|
|
public Task<List<AssetEntity>> GetAllValidAssetsAsync();
|
|
public Task<bool> AddOrUpdateAssetAsync(TradeRepublicAsset dtoAsset);
|
|
public Task<int> AddOrUpdateAssetsAsync(IEnumerable<TradeRepublicAsset> dtoAssets);
|
|
public Task<List<AssetEntity>> GetAssetsByIsinAsync(string isin);
|
|
public Task<List<AssetEntity>> GetValidAssetsByIsinAsync(string isin);
|
|
public Task<List<AssetEntity>> FindAffectedActiveAssetsAsync(string searchQuery);
|
|
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 />
|
|
public class AssetsDbService : IAssetsDbService
|
|
{
|
|
private readonly AssetsDbContext _context;
|
|
private readonly ITradeRepublicService _tradeRepublicService;
|
|
private readonly ILogger<AssetsDbService> _logger;
|
|
|
|
public AssetsDbService(AssetsDbContext context, ILogger<AssetsDbService> logger, ITradeRepublicService tradeRepublicService)
|
|
{
|
|
_context = context;
|
|
_logger = logger;
|
|
_tradeRepublicService = tradeRepublicService;
|
|
}
|
|
|
|
/// <summary>Inherits documentation from interface.</summary>
|
|
public async Task<List<AssetEntity>> GetDiscoveryAssetsAsync(int limit = 15)
|
|
{
|
|
var cutoff = DateTime.UtcNow.AddDays(-90);
|
|
|
|
var validAssets = await _context.TradeRepublicAssets
|
|
.AsNoTracking()
|
|
.Include(a => a.Tags)
|
|
.Where(a => a.LastUpdatedAt >= cutoff && !string.IsNullOrEmpty(a.Name))
|
|
.ToListAsync();
|
|
|
|
if (validAssets.Count == 0) return [];
|
|
|
|
var scored = validAssets
|
|
.Select(a => new
|
|
{
|
|
Asset = a,
|
|
Score = (a.Tags?.Count ?? 0) * 10
|
|
+ (a.HasCfd ? 5 : 0)
|
|
+ (string.IsNullOrEmpty(a.ImageId) ? 0 : 15)
|
|
+ (a.Name.Length > 3 ? 5 : 0)
|
|
})
|
|
.OrderByDescending(x => x.Score)
|
|
.ThenByDescending(x => x.Asset.LastUpdatedAt)
|
|
.ToList();
|
|
|
|
var result = new List<AssetEntity>();
|
|
var grouped = scored.GroupBy(x => x.Asset.Type).ToList();
|
|
|
|
int index = 0;
|
|
while (result.Count < limit && grouped.Any(g => g.Any()))
|
|
{
|
|
bool addedAny = false;
|
|
foreach (var group in grouped)
|
|
{
|
|
var item = group.Skip(index).FirstOrDefault();
|
|
if (item != null)
|
|
{
|
|
result.Add(item.Asset);
|
|
addedAny = true;
|
|
if (result.Count >= limit) break;
|
|
}
|
|
}
|
|
index++;
|
|
if (!addedAny) break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>Inherits documentation from interface.</summary>
|
|
public async Task<List<AssetEntity>> GetAllValidAssetsAsync()
|
|
{
|
|
var cutoff = DateTime.UtcNow.AddDays(-90);
|
|
|
|
return await _context.TradeRepublicAssets
|
|
.AsNoTracking()
|
|
.Include(a => a.Tags)
|
|
.Where(a => a.LastUpdatedAt >= cutoff)
|
|
.ToListAsync();
|
|
}
|
|
|
|
/// <summary>Inherits documentation from interface.</summary>
|
|
public async Task<bool> AddOrUpdateAssetAsync(TradeRepublicAsset dtoAsset)
|
|
{
|
|
var existingEntity = await _context.TradeRepublicAssets
|
|
.Include(a => a.Tags)
|
|
.FirstOrDefaultAsync(a => a.Isin == dtoAsset.Isin);
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
var mappedTags = new List<TagEntity>();
|
|
foreach (var tagDto in dtoAsset.Tags ?? Array.Empty<TradeRepublicTag>())
|
|
{
|
|
var existingTag = await _context.TradeRepublicTags.FirstOrDefaultAsync(t => t.Id == tagDto.Id);
|
|
if (existingTag == null)
|
|
{
|
|
_logger.LogTrace("Creating missing asset tag object in storage cache context. ID: {TagId}", tagDto.Id);
|
|
existingTag = new TagEntity { Id = tagDto.Id, Name = tagDto.Name, Type = tagDto.Type };
|
|
await _context.TradeRepublicTags.AddAsync(existingTag);
|
|
}
|
|
|
|
mappedTags.Add(existingTag);
|
|
}
|
|
|
|
if (existingEntity == null)
|
|
{
|
|
_logger.LogDebug("Asset with ISIN {Isin} not found. Mapping and creating a new entity.", dtoAsset.Isin);
|
|
|
|
var newEntity = MapDtoToEntity(dtoAsset);
|
|
newEntity.LastUpdatedAt = now;
|
|
newEntity.Tags = mappedTags;
|
|
|
|
await _context.TradeRepublicAssets.AddAsync(newEntity);
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
_logger.LogDebug("Asset with ISIN {Isin} exists. Merging properties and updating database record.", dtoAsset.Isin);
|
|
|
|
existingEntity.Name = dtoAsset.Name;
|
|
existingEntity.Type = dtoAsset.Type;
|
|
existingEntity.InstrumentCategory = dtoAsset.InstrumentCategory;
|
|
existingEntity.HasCfd = dtoAsset.HasCfd;
|
|
existingEntity.ImageId = dtoAsset.ImageId;
|
|
existingEntity.LastUpdatedAt = now;
|
|
|
|
UpdateSubtypeProperties(existingEntity, dtoAsset);
|
|
existingEntity.Tags = mappedTags;
|
|
|
|
_context.TradeRepublicAssets.Update(existingEntity);
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
/// <summary>Inherits documentation from interface.</summary>
|
|
public async Task<int> AddOrUpdateAssetsAsync(IEnumerable<TradeRepublicAsset> dtoAssets)
|
|
{
|
|
var assetsList = dtoAssets.ToList();
|
|
if (assetsList.Count == 0) return 0;
|
|
|
|
var now = DateTime.UtcNow;
|
|
int changedCount = 0;
|
|
|
|
var isins = assetsList.Select(a => a.Isin).Distinct().ToList();
|
|
var existingAssets = await _context.TradeRepublicAssets
|
|
.Include(a => a.Tags)
|
|
.Where(a => isins.Contains(a.Isin))
|
|
.ToDictionaryAsync(a => a.Isin);
|
|
|
|
var tagIds = assetsList
|
|
.SelectMany(a => a.Tags ?? Array.Empty<TradeRepublicTag>())
|
|
.Select(t => t.Id)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
var tagCache = await _context.TradeRepublicTags
|
|
.Where(t => tagIds.Contains(t.Id))
|
|
.ToDictionaryAsync(t => t.Id);
|
|
|
|
foreach (var dto in assetsList)
|
|
{
|
|
var isChanged = false;
|
|
var existingEntity = existingAssets.GetValueOrDefault(dto.Isin);
|
|
|
|
var mappedTags = new List<TagEntity>();
|
|
foreach (var tagDto in dto.Tags ?? Array.Empty<TradeRepublicTag>())
|
|
{
|
|
if (!tagCache.TryGetValue(tagDto.Id, out var tagEntity))
|
|
{
|
|
_logger.LogTrace("Creating missing asset tag object in storage cache context. ID: {TagId}", tagDto.Id);
|
|
tagEntity = new TagEntity { Id = tagDto.Id, Name = tagDto.Name, Type = tagDto.Type };
|
|
await _context.TradeRepublicTags.AddAsync(tagEntity);
|
|
tagCache.Add(tagDto.Id, tagEntity);
|
|
}
|
|
mappedTags.Add(tagEntity);
|
|
}
|
|
|
|
if (existingEntity == null)
|
|
{
|
|
_logger.LogDebug("Asset with ISIN {Isin} not found. Mapping and creating a new entity.", dto.Isin);
|
|
|
|
var newEntity = MapDtoToEntity(dto);
|
|
newEntity.LastUpdatedAt = now;
|
|
newEntity.Tags = mappedTags;
|
|
|
|
await _context.TradeRepublicAssets.AddAsync(newEntity);
|
|
isChanged = true;
|
|
}
|
|
else
|
|
{
|
|
_logger.LogDebug("Asset with ISIN {Isin} exists. Merging properties and updating database record.", dto.Isin);
|
|
|
|
if (existingEntity.Name != dto.Name ||
|
|
existingEntity.Type != dto.Type ||
|
|
existingEntity.InstrumentCategory != dto.InstrumentCategory ||
|
|
existingEntity.HasCfd != dto.HasCfd ||
|
|
existingEntity.ImageId != dto.ImageId ||
|
|
!existingEntity.Tags.SequenceEqual(mappedTags))
|
|
{
|
|
existingEntity.Name = dto.Name;
|
|
existingEntity.Type = dto.Type;
|
|
existingEntity.InstrumentCategory = dto.InstrumentCategory;
|
|
existingEntity.HasCfd = dto.HasCfd;
|
|
existingEntity.ImageId = dto.ImageId;
|
|
existingEntity.LastUpdatedAt = now;
|
|
|
|
UpdateSubtypeProperties(existingEntity, dto);
|
|
existingEntity.Tags = mappedTags;
|
|
|
|
_context.TradeRepublicAssets.Update(existingEntity);
|
|
isChanged = true;
|
|
}
|
|
}
|
|
|
|
if (isChanged)
|
|
{
|
|
changedCount++;
|
|
}
|
|
}
|
|
|
|
if (changedCount > 0)
|
|
{
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
|
|
return changedCount;
|
|
}
|
|
|
|
/// <summary>Inherits documentation from interface.</summary>
|
|
public async Task<List<AssetEntity>> GetAssetsByIsinAsync(string isin)
|
|
{
|
|
var localAssets = await _context.TradeRepublicAssets
|
|
.Include(a => a.Tags)
|
|
.Where(a => a.Isin == isin)
|
|
.ToListAsync();
|
|
|
|
if (localAssets.Count > 0)
|
|
{
|
|
return localAssets;
|
|
}
|
|
|
|
// JIT-Fetch via API
|
|
var trAssetDto = await _tradeRepublicService.GetAsset(isin);
|
|
if (trAssetDto?.Results != null && trAssetDto.Results.Count > 0)
|
|
{
|
|
foreach (var asset in trAssetDto.Results)
|
|
{
|
|
await AddOrUpdateAssetAsync(asset);
|
|
}
|
|
|
|
return await _context.TradeRepublicAssets
|
|
.Include(a => a.Tags)
|
|
.Where(a => a.Isin == isin)
|
|
.ToListAsync();
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/// <summary>Inherits documentation from interface.</summary>
|
|
public async Task<List<AssetEntity>> GetValidAssetsByIsinAsync(string isin)
|
|
{
|
|
var cutoff = DateTime.UtcNow.AddDays(-14);
|
|
|
|
return await _context.TradeRepublicAssets
|
|
.AsNoTracking()
|
|
.Include(a => a.Tags)
|
|
.Where(a => a.Isin == isin && a.LastUpdatedAt >= cutoff)
|
|
.ToListAsync();
|
|
}
|
|
|
|
/// <summary>Inherits documentation from interface.</summary>
|
|
public async Task<List<AssetEntity>> FindAffectedActiveAssetsAsync(string searchQuery)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(searchQuery)) return [];
|
|
|
|
var cutoff = DateTime.UtcNow.AddDays(-14);
|
|
|
|
var searchTerms = searchQuery
|
|
.Split(',')
|
|
.Select(t => t.Trim())
|
|
.Where(t => !string.IsNullOrEmpty(t))
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
if (searchTerms.Count == 0) return [];
|
|
|
|
var query = _context.TradeRepublicAssets
|
|
.AsNoTracking()
|
|
.Where(a => a.LastUpdatedAt >= cutoff)
|
|
.Include(a => a.Tags)
|
|
.AsQueryable();
|
|
|
|
foreach (var term in searchTerms)
|
|
{
|
|
var lowerTerm = term.ToLower();
|
|
query = query.Where(a =>
|
|
a.Isin.ToLower().Contains(lowerTerm) ||
|
|
a.Name.ToLower().Contains(lowerTerm) ||
|
|
a.Tags.Any(tag => tag.Name.ToLower().Contains(lowerTerm)));
|
|
}
|
|
|
|
var localAssets = await query.ToListAsync();
|
|
|
|
var possibleIsins = searchTerms
|
|
.Where(t => t.Length == 12 && char.IsLetter(t[0]) && char.IsLetter(t[1]))
|
|
.Select(t => t.ToUpper())
|
|
.ToList();
|
|
|
|
if (possibleIsins.Count > 0)
|
|
{
|
|
var foundIsins = localAssets.Select(a => a.Isin).ToHashSet();
|
|
var missingIsins = possibleIsins.Where(isin => !foundIsins.Contains(isin)).ToList();
|
|
|
|
if (missingIsins.Count > 0)
|
|
{
|
|
var fetchedNewAsset = false;
|
|
foreach (var missingIsin in missingIsins)
|
|
{
|
|
var trAssetDto = await _tradeRepublicService.GetAsset(missingIsin);
|
|
if (trAssetDto?.Results != null)
|
|
{
|
|
foreach (var asset in trAssetDto.Results)
|
|
{
|
|
await AddOrUpdateAssetAsync(asset);
|
|
}
|
|
|
|
fetchedNewAsset = true;
|
|
}
|
|
}
|
|
|
|
if (fetchedNewAsset)
|
|
{
|
|
return await query.ToListAsync();
|
|
}
|
|
}
|
|
}
|
|
|
|
return localAssets;
|
|
}
|
|
|
|
/// <summary>Inherits documentation from interface.</summary>
|
|
public async Task UpdateAssetImageIdAsync(string isin, string imageId)
|
|
{
|
|
var existingAssets = await _context.TradeRepublicAssets
|
|
.Where(a => a.Isin == isin)
|
|
.ToListAsync();
|
|
|
|
if (existingAssets.Count > 0)
|
|
{
|
|
foreach (var asset in existingAssets)
|
|
{
|
|
asset.ImageId = imageId;
|
|
}
|
|
await _context.SaveChangesAsync();
|
|
_logger.LogInformation("[{Channel}] Updated ImageId for ISIN {Isin} in database to '{ImageId}'", "AssetsChannel", isin, imageId);
|
|
}
|
|
}
|
|
|
|
/// <summary>Inherits documentation from interface.</summary>
|
|
public async Task<bool> DeleteAssetAsync(string isin)
|
|
{
|
|
var asset = await _context.TradeRepublicAssets.FirstOrDefaultAsync(a => a.Isin == isin);
|
|
if (asset == null)
|
|
{
|
|
_logger.LogWarning("[{Channel}] Delete execution cancelled. Asset with ISIN {Isin} does not exist.", "AssetsChannel", isin);
|
|
return false;
|
|
}
|
|
|
|
_context.TradeRepublicAssets.Remove(asset);
|
|
await _context.SaveChangesAsync();
|
|
_logger.LogInformation("[{Channel}] Asset with ISIN {Isin} has been successfully deleted.", "AssetsChannel", isin);
|
|
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)
|
|
{
|
|
AssetEntity entity = dto switch
|
|
{
|
|
TradeRepublicStock stock => new StockEntity
|
|
{ Isin = stock.Isin, DerivativeProductCategories = stock.DerivativeProductCategories.ToList() },
|
|
TradeRepublicCrypto crypto => new CryptoEntity
|
|
{ Isin = crypto.Isin, Subtitle = crypto.Subtitle, SearchSubtitle = crypto.SearchSubtitle },
|
|
TradeRepublicEtf etf => new EtfEntity
|
|
{
|
|
Isin = etf.Isin, EtfDescription = etf.EtfDescription, MappedEtfIndexName = etf.MappedEtfIndexName,
|
|
Subtitle = etf.Subtitle, SearchSubtitle = etf.SearchSubtitle,
|
|
DerivativeProductCategories = etf.DerivativeProductCategories.ToList()
|
|
},
|
|
TradeRepublicSynthetic synth => new SyntheticEntity
|
|
{ Isin = synth.Isin, DerivativeProductCategories = synth.DerivativeProductCategories.ToList() },
|
|
TradeRepublicBond bond => new BondEntity
|
|
{ Isin = bond.Isin, BondIssuerName = bond.BondIssuerName, SearchSubtitle = bond.SearchSubtitle },
|
|
TradeRepublicDerivative deriv => new DerivativeEntity
|
|
{
|
|
Isin = deriv.Isin, UnderlyingIsin = deriv.UnderlyingIsin,
|
|
DerivativeProductCategories = deriv.DerivativeProductCategories.ToList()
|
|
},
|
|
_ => throw new NotSupportedException($"Type {dto.GetType().Name} is not supported.")
|
|
};
|
|
|
|
return PopulateBaseProperties(entity, dto);
|
|
}
|
|
|
|
private AssetEntity PopulateBaseProperties(AssetEntity entity, TradeRepublicAsset dto)
|
|
{
|
|
entity.Name = dto.Name;
|
|
entity.Type = dto.Type;
|
|
entity.InstrumentCategory = dto.InstrumentCategory;
|
|
entity.HasCfd = dto.HasCfd;
|
|
entity.ImageId = dto.ImageId;
|
|
return entity;
|
|
}
|
|
|
|
private void UpdateSubtypeProperties(AssetEntity entity, TradeRepublicAsset dto)
|
|
{
|
|
switch (entity)
|
|
{
|
|
case StockEntity stockEntity when dto is TradeRepublicStock stockDto:
|
|
stockEntity.DerivativeProductCategories = stockDto.DerivativeProductCategories.ToList();
|
|
break;
|
|
case CryptoEntity cryptoEntity when dto is TradeRepublicCrypto cryptoDto:
|
|
cryptoEntity.Subtitle = cryptoDto.Subtitle;
|
|
cryptoEntity.SearchSubtitle = cryptoDto.SearchSubtitle;
|
|
break;
|
|
case EtfEntity etfEntity when dto is TradeRepublicEtf etfDto:
|
|
etfEntity.DerivativeProductCategories = etfDto.DerivativeProductCategories.ToList();
|
|
etfEntity.EtfDescription = etfDto.EtfDescription;
|
|
etfEntity.MappedEtfIndexName = etfDto.MappedEtfIndexName;
|
|
etfEntity.Subtitle = etfDto.Subtitle;
|
|
etfEntity.SearchSubtitle = etfDto.SearchSubtitle;
|
|
break;
|
|
case SyntheticEntity synthEntity when dto is TradeRepublicSynthetic synthDto:
|
|
synthEntity.DerivativeProductCategories = synthDto.DerivativeProductCategories.ToList();
|
|
break;
|
|
case BondEntity bondEntity when dto is TradeRepublicBond bondDto:
|
|
bondEntity.BondIssuerName = bondDto.BondIssuerName;
|
|
bondEntity.SearchSubtitle = bondDto.SearchSubtitle;
|
|
break;
|
|
case DerivativeEntity derivEntity when dto is TradeRepublicDerivative derivDto:
|
|
derivEntity.DerivativeProductCategories = derivDto.DerivativeProductCategories.ToList();
|
|
derivEntity.UnderlyingIsin = derivDto.UnderlyingIsin;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
} |