refactor(assets): update asset entity mappings, database migrations, and MQTT RPC handlers
This commit is contained in:
@@ -14,7 +14,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
namespace FinlyticAssets.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Defines database operations for managing Trade Republic asset entities.
|
||||
/// Defines database operations for managing Trade Republic asset entities and on-demand derivatives.
|
||||
/// </summary>
|
||||
public interface IAssetsDbService
|
||||
{
|
||||
@@ -24,7 +24,6 @@ public interface IAssetsDbService
|
||||
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", decimal? targetLeverage = null, string? after = null, int? page = null, bool forceRefresh = false, CancellationToken cancellationToken = default);
|
||||
@@ -63,7 +62,6 @@ public class AssetsDbService : IAssetsDbService
|
||||
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)
|
||||
@@ -147,7 +145,6 @@ public class AssetsDbService : IAssetsDbService
|
||||
existingEntity.Type = dtoAsset.Type;
|
||||
existingEntity.InstrumentCategory = dtoAsset.InstrumentCategory;
|
||||
existingEntity.HasCfd = dtoAsset.HasCfd;
|
||||
existingEntity.ImageId = dtoAsset.ImageId;
|
||||
existingEntity.LastUpdatedAt = now;
|
||||
|
||||
UpdateSubtypeProperties(existingEntity, dtoAsset);
|
||||
@@ -220,14 +217,13 @@ public class AssetsDbService : IAssetsDbService
|
||||
existingEntity.Type != dto.Type ||
|
||||
existingEntity.InstrumentCategory != dto.InstrumentCategory ||
|
||||
existingEntity.HasCfd != dto.HasCfd ||
|
||||
existingEntity.ImageId != dto.ImageId ||
|
||||
!existingEntity.Tags.SequenceEqual(mappedTags))
|
||||
!existingEntity.Tags.Select(t => t.Id).Order().SequenceEqual(mappedTags.Select(t => t.Id).Order()) ||
|
||||
HasSubtypeChanges(existingEntity, dto))
|
||||
{
|
||||
existingEntity.Name = dto.Name;
|
||||
existingEntity.Type = dto.Type;
|
||||
existingEntity.InstrumentCategory = dto.InstrumentCategory;
|
||||
existingEntity.HasCfd = dto.HasCfd;
|
||||
existingEntity.ImageId = dto.ImageId;
|
||||
existingEntity.LastUpdatedAt = now;
|
||||
existingEntity.Tags = mappedTags;
|
||||
|
||||
@@ -255,18 +251,8 @@ public class AssetsDbService : IAssetsDbService
|
||||
Type = stock.Type,
|
||||
InstrumentCategory = stock.InstrumentCategory,
|
||||
HasCfd = stock.HasCfd,
|
||||
ImageId = stock.ImageId,
|
||||
DerivativeProductCategories = stock.DerivativeProductCategories?.ToList() ?? new List<string>()
|
||||
},
|
||||
TradeRepublicCrypto crypto => new CryptoEntity
|
||||
{
|
||||
Isin = crypto.Isin,
|
||||
Name = crypto.Name,
|
||||
Type = crypto.Type,
|
||||
InstrumentCategory = crypto.InstrumentCategory,
|
||||
HasCfd = crypto.HasCfd,
|
||||
ImageId = crypto.ImageId
|
||||
},
|
||||
TradeRepublicEtf etf => new EtfEntity
|
||||
{
|
||||
Isin = etf.Isin,
|
||||
@@ -274,7 +260,6 @@ public class AssetsDbService : IAssetsDbService
|
||||
Type = etf.Type,
|
||||
InstrumentCategory = etf.InstrumentCategory,
|
||||
HasCfd = etf.HasCfd,
|
||||
ImageId = etf.ImageId,
|
||||
DerivativeProductCategories = etf.DerivativeProductCategories?.ToList() ?? new List<string>()
|
||||
},
|
||||
TradeRepublicSynthetic syn => new SyntheticEntity
|
||||
@@ -284,39 +269,15 @@ public class AssetsDbService : IAssetsDbService
|
||||
Type = syn.Type,
|
||||
InstrumentCategory = syn.InstrumentCategory,
|
||||
HasCfd = syn.HasCfd,
|
||||
ImageId = syn.ImageId,
|
||||
DerivativeProductCategories = syn.DerivativeProductCategories?.ToList() ?? new List<string>()
|
||||
},
|
||||
TradeRepublicBond bond => new BondEntity
|
||||
{
|
||||
Isin = bond.Isin,
|
||||
Name = bond.Name,
|
||||
Type = bond.Type,
|
||||
InstrumentCategory = bond.InstrumentCategory,
|
||||
HasCfd = bond.HasCfd,
|
||||
ImageId = bond.ImageId,
|
||||
BondIssuerName = bond.BondIssuerName,
|
||||
SearchSubtitle = bond.SearchSubtitle
|
||||
},
|
||||
TradeRepublicDerivative deriv => new DerivativeEntity
|
||||
{
|
||||
Isin = deriv.Isin,
|
||||
Name = deriv.Name,
|
||||
Type = deriv.Type,
|
||||
InstrumentCategory = deriv.InstrumentCategory,
|
||||
HasCfd = deriv.HasCfd,
|
||||
ImageId = deriv.ImageId,
|
||||
UnderlyingIsin = deriv.UnderlyingIsin,
|
||||
DerivativeProductCategories = deriv.DerivativeProductCategories?.ToList() ?? new List<string>()
|
||||
},
|
||||
_ => new StockEntity
|
||||
{
|
||||
Isin = dto.Isin,
|
||||
Name = dto.Name,
|
||||
Type = dto.Type,
|
||||
InstrumentCategory = dto.InstrumentCategory,
|
||||
HasCfd = dto.HasCfd,
|
||||
ImageId = dto.ImageId
|
||||
HasCfd = dto.HasCfd
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -334,14 +295,21 @@ public class AssetsDbService : IAssetsDbService
|
||||
case SyntheticEntity syn when dto is TradeRepublicSynthetic synDto:
|
||||
syn.DerivativeProductCategories = synDto.DerivativeProductCategories?.ToList() ?? new List<string>();
|
||||
break;
|
||||
case BondEntity bond when dto is TradeRepublicBond b:
|
||||
bond.BondIssuerName = b.BondIssuerName;
|
||||
bond.SearchSubtitle = b.SearchSubtitle;
|
||||
break;
|
||||
case DerivativeEntity deriv when dto is TradeRepublicDerivative d:
|
||||
deriv.UnderlyingIsin = d.UnderlyingIsin;
|
||||
deriv.DerivativeProductCategories = d.DerivativeProductCategories?.ToList() ?? new List<string>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasSubtypeChanges(AssetEntity entity, TradeRepublicAsset dto)
|
||||
{
|
||||
switch (entity)
|
||||
{
|
||||
case StockEntity stock when dto is TradeRepublicStock s:
|
||||
return !(stock.DerivativeProductCategories?.SequenceEqual(s.DerivativeProductCategories ?? Array.Empty<string>()) ?? (s.DerivativeProductCategories == null || !s.DerivativeProductCategories.Any()));
|
||||
case EtfEntity etf when dto is TradeRepublicEtf e:
|
||||
return !(etf.DerivativeProductCategories?.SequenceEqual(e.DerivativeProductCategories ?? Array.Empty<string>()) ?? (e.DerivativeProductCategories == null || !e.DerivativeProductCategories.Any()));
|
||||
case SyntheticEntity syn when dto is TradeRepublicSynthetic synDto:
|
||||
return !(syn.DerivativeProductCategories?.SequenceEqual(synDto.DerivativeProductCategories ?? Array.Empty<string>()) ?? (synDto.DerivativeProductCategories == null || !synDto.DerivativeProductCategories.Any()));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,24 +330,6 @@ public class AssetsDbService : IAssetsDbService
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <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();
|
||||
await _finlyticLogger.LogInfoAsync(SettingKeys.AssetsChannel, "[AssetsDbService] Updated ImageId for ISIN {Isin} in database to '{ImageId}'", isin, imageId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task<bool> DeleteAssetAsync(string isin)
|
||||
{
|
||||
@@ -412,7 +362,6 @@ public class AssetsDbService : IAssetsDbService
|
||||
int pageIndex = Math.Max(0, page ?? 0);
|
||||
|
||||
decimal levQuery = targetLeverage.HasValue && targetLeverage.Value > 0 ? targetLeverage.Value : 0m;
|
||||
|
||||
string trAfter = !string.IsNullOrEmpty(after) ? after : (pageIndex > 0 ? pageIndex.ToString() : "0");
|
||||
|
||||
await _finlyticLogger.LogInfoAsync(SettingKeys.AssetsChannel, "[AssetsDbService] Fetching derivatives for {Isin} (OptionType: {Option}, Leverage: {Lev}, Page: {Page}, TR-After: {After})",
|
||||
@@ -438,8 +387,7 @@ public class AssetsDbService : IAssetsDbService
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var isins = fetchedItems.Select(r => r.Isin).ToList();
|
||||
var existingDerivatives = await _context.TradeRepublicAssets
|
||||
.OfType<DerivativeEntity>()
|
||||
var existingDerivatives = await _context.Derivatives
|
||||
.Where(d => isins.Contains(d.Isin))
|
||||
.ToDictionaryAsync(d => d.Isin, cancellationToken);
|
||||
|
||||
@@ -466,14 +414,13 @@ public class AssetsDbService : IAssetsDbService
|
||||
existing.NextGenProductCategoryName = item.NextGenProductCategoryName;
|
||||
existing.Issuer = item.Issuer;
|
||||
existing.IssuerDisplayName = item.IssuerDisplayName;
|
||||
existing.IssuerImageId = item.IssuerImageId;
|
||||
existing.Size = item.Size;
|
||||
existing.Factor = item.Factor;
|
||||
existing.Delta = item.Delta;
|
||||
existing.Currency = item.Currency;
|
||||
existing.LastUpdatedAt = now;
|
||||
|
||||
_context.TradeRepublicAssets.Update(existing);
|
||||
_context.Derivatives.Update(existing);
|
||||
resultEntities.Add(existing);
|
||||
}
|
||||
else
|
||||
@@ -482,8 +429,6 @@ public class AssetsDbService : IAssetsDbService
|
||||
{
|
||||
Isin = item.Isin,
|
||||
Name = !string.IsNullOrWhiteSpace(item.ProductCategoryName) ? item.ProductCategoryName : item.Isin,
|
||||
Type = "derivative",
|
||||
InstrumentCategory = "derivative",
|
||||
UnderlyingIsin = underlyingIsin,
|
||||
Strike = item.Strike ?? 0m,
|
||||
Barrier = item.Barrier ?? 0m,
|
||||
@@ -494,27 +439,33 @@ public class AssetsDbService : IAssetsDbService
|
||||
NextGenProductCategoryName = item.NextGenProductCategoryName,
|
||||
Issuer = item.Issuer,
|
||||
IssuerDisplayName = item.IssuerDisplayName,
|
||||
IssuerImageId = item.IssuerImageId,
|
||||
Size = item.Size,
|
||||
Factor = item.Factor,
|
||||
Delta = item.Delta,
|
||||
Currency = item.Currency,
|
||||
LastUpdatedAt = now
|
||||
LastUpdatedAt = now,
|
||||
CreatedAt = now
|
||||
};
|
||||
|
||||
await _context.TradeRepublicAssets.AddAsync(newDeriv, cancellationToken);
|
||||
await _context.Derivatives.AddAsync(newDeriv, cancellationToken);
|
||||
resultEntities.Add(newDeriv);
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return resultEntities;
|
||||
return resultEntities.Where(d => d.Barrier > 0 && d.Leverage > 0).ToList();
|
||||
}
|
||||
|
||||
return await _context.TradeRepublicAssets
|
||||
.OfType<DerivativeEntity>()
|
||||
var dbQuery = _context.Derivatives
|
||||
.AsNoTracking()
|
||||
.Where(d => d.UnderlyingIsin == underlyingIsin)
|
||||
.Where(d => d.UnderlyingIsin == underlyingIsin && d.OptionType == targetOptionType && d.Barrier > 0 && d.Leverage > 0);
|
||||
|
||||
if (targetLeverage.HasValue && targetLeverage.Value > 0)
|
||||
{
|
||||
dbQuery = dbQuery.OrderBy(d => Math.Abs(d.Leverage - targetLeverage.Value));
|
||||
}
|
||||
|
||||
return await dbQuery
|
||||
.Take(pageSize)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user