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
+9 -2
View File
@@ -1,6 +1,5 @@
using FinlyticAssets.Entities; using FinlyticAssets.Entities;
using FinlyticCore.Entities.Assets; using FinlyticCore.Entities.Settings;
using FinlyticCore.Models.Assets;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
@@ -12,6 +11,7 @@ public class AssetsDbContext : DbContext
{ {
} }
public DbSet<SettingEntity> DynamicSettings => Set<SettingEntity>();
public DbSet<Settings> Settings { get; set; } public DbSet<Settings> Settings { get; set; }
public DbSet<AssetEntity> TradeRepublicAssets { get; set; } public DbSet<AssetEntity> TradeRepublicAssets { get; set; }
public DbSet<TagEntity> TradeRepublicTags { get; set; } public DbSet<TagEntity> TradeRepublicTags { get; set; }
@@ -19,6 +19,13 @@ public class AssetsDbContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<SettingEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.Key);
});
modelBuilder.Entity<AssetEntity>(entity => modelBuilder.Entity<AssetEntity>(entity =>
{ {
entity.HasKey(e => new {e.Isin, e.InstrumentCategory}); entity.HasKey(e => new {e.Isin, e.InstrumentCategory});
+15
View File
@@ -0,0 +1,15 @@
namespace FinlyticAssets.Entities;
public abstract class AssetEntity
{
public string Isin { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty;
public string InstrumentCategory { get; set; } = string.Empty;
public bool HasCfd { get; set; }
public string? ImageId { get; set; }
public DateTime LastUpdatedAt { get; set; } = DateTime.UtcNow;
public List<TagEntity> Tags { get; set; } = new();
}
+9
View File
@@ -0,0 +1,9 @@
using FinlyticAssets.Entities;
namespace FinlyticAssets.Entities;
public class BondEntity : AssetEntity
{
public string BondIssuerName { get; set; } = string.Empty;
public string SearchSubtitle { get; set; } = string.Empty;
}
+7
View File
@@ -0,0 +1,7 @@
namespace FinlyticAssets.Entities;
public class CryptoEntity : AssetEntity
{
public string Subtitle { get; set; } = string.Empty;
public string SearchSubtitle { get; set; } = string.Empty;
}
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FinlyticAssets.Entities;
public enum OptionType
{
Long,
Short
}
public class DerivativeEntity : AssetEntity
{
// --- Verknüpfung zum Basiswert (Underlying) ---
[StringLength(12, MinimumLength = 12)]
public string? UnderlyingIsin { get; set; }
// --- Derivat-Spezifikationen ---
public OptionType OptionType { get; set; } // Long / Short
[MaxLength(100)]
public string ProductCategoryName { get; set; } = string.Empty; // Z.B. "Best Turbo"
[MaxLength(100)]
public string NextGenProductCategoryName { get; set; } = string.Empty; // Z.B. "Turbo"
// --- Kennzahlen (Präzise decimals für Finanzwerte) ---
[Column(TypeName = "numeric(18,6)")]
public decimal Strike { get; set; } // Basispreis
[Column(TypeName = "numeric(18,6)")]
public decimal Barrier { get; set; } // Knock-Out-Schwelle
[Column(TypeName = "numeric(10,4)")]
public decimal Leverage { get; set; } // Hebel (z. B. 1.01)
public decimal? Size { get; set; }
public decimal? Factor { get; set; }
public decimal? Delta { get; set; }
[MaxLength(10)]
public string Currency { get; set; } = "EUR";
public DateTime? Expiry { get; set; } // Verfallsdatum (null bei Open-End / Best Turbo)
// --- Emittent (Issuer) Daten ---
[MaxLength(150)]
public string Issuer { get; set; } = string.Empty; // Z.B. "Société Générale"
[MaxLength(150)]
public string IssuerDisplayName { get; set; } = string.Empty;
public string? IssuerImageId { get; set; }
// PostgreSQL Mapped Array für TR-Kategorien
public List<string> DerivativeProductCategories { get; set; } = new();
}
+10
View File
@@ -0,0 +1,10 @@
namespace FinlyticAssets.Entities;
public class EtfEntity : AssetEntity
{
public List<string> DerivativeProductCategories { get; set; } = new();
public string EtfDescription { get; set; } = string.Empty;
public string MappedEtfIndexName { get; set; } = string.Empty;
public string Subtitle { get; set; } = string.Empty;
public string SearchSubtitle { get; set; } = string.Empty;
}
+6
View File
@@ -0,0 +1,6 @@
namespace FinlyticAssets.Entities;
public class StockEntity : AssetEntity
{
public List<string> DerivativeProductCategories { get; set; } = new();
}
@@ -0,0 +1,6 @@
namespace FinlyticAssets.Entities;
public class SyntheticEntity : AssetEntity
{
public List<string> DerivativeProductCategories { get; set; } = new();
}
+10
View File
@@ -0,0 +1,10 @@
namespace FinlyticAssets.Entities;
public class TagEntity
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty;
public List<AssetEntity> Assets { get; set; } = new();
}
@@ -0,0 +1,335 @@
// <auto-generated />
using System;
using FinlyticAssets.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace FinlyticAssets.Migrations
{
[DbContext(typeof(AssetsDbContext))]
[Migration("20260813202348_UpdateDerivativeAssets")]
partial class UpdateDerivativeAssets
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("AssetEntityTagEntity", b =>
{
b.Property<string>("TagsId")
.HasColumnType("text");
b.Property<string>("AssetsIsin")
.HasColumnType("text");
b.Property<string>("AssetsInstrumentCategory")
.HasColumnType("text");
b.HasKey("TagsId", "AssetsIsin", "AssetsInstrumentCategory");
b.HasIndex("AssetsIsin", "AssetsInstrumentCategory");
b.ToTable("AssetEntityTagEntity");
});
modelBuilder.Entity("FinlyticAssets.Entities.AssetEntity", b =>
{
b.Property<string>("Isin")
.HasColumnType("text");
b.Property<string>("InstrumentCategory")
.HasColumnType("text");
b.Property<string>("AssetType")
.IsRequired()
.HasMaxLength(13)
.HasColumnType("character varying(13)");
b.Property<bool>("HasCfd")
.HasColumnType("boolean");
b.Property<string>("ImageId")
.HasColumnType("text");
b.Property<DateTime>("LastUpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.HasKey("Isin", "InstrumentCategory");
b.HasIndex("LastUpdatedAt");
b.ToTable("TradeRepublicAssets");
b.HasDiscriminator<string>("AssetType").HasValue("AssetEntity");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("FinlyticAssets.Entities.Settings", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("AssetUpdateTypeDelay")
.HasColumnType("integer");
b.Property<int>("BatchAssetUpdateDelay")
.HasColumnType("integer");
b.Property<int>("CurrentScanningPage")
.HasColumnType("integer");
b.Property<string>("CurrentScanningType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<bool>("FinishedInitialScan")
.HasColumnType("boolean");
b.Property<int>("InitAssetUpdateTypeDelay")
.HasColumnType("integer");
b.Property<int>("InitBatchAssetUpdateDelay")
.HasColumnType("integer");
b.Property<int>("TradeRepublicMaxRequestPageSize")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Settings");
});
modelBuilder.Entity("FinlyticAssets.Entities.TagEntity", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("TradeRepublicTags");
});
modelBuilder.Entity("FinlyticAssets.Entities.BondEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("BondIssuerName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("SearchSubtitle")
.IsRequired()
.HasColumnType("text");
b.HasDiscriminator().HasValue("Bond");
});
modelBuilder.Entity("FinlyticAssets.Entities.CryptoEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("SearchSubtitle")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Subtitle")
.IsRequired()
.HasColumnType("text");
b.ToTable("TradeRepublicAssets", t =>
{
t.Property("SearchSubtitle")
.HasColumnName("CryptoEntity_SearchSubtitle");
});
b.HasDiscriminator().HasValue("Crypto");
});
modelBuilder.Entity("FinlyticAssets.Entities.DerivativeEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<decimal>("Barrier")
.HasColumnType("numeric(18,6)");
b.Property<string>("Currency")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<decimal?>("Delta")
.HasColumnType("numeric");
b.Property<string>("DerivativeProductCategories")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime?>("Expiry")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("Factor")
.HasColumnType("numeric");
b.Property<string>("Issuer")
.IsRequired()
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.Property<string>("IssuerDisplayName")
.IsRequired()
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.Property<string>("IssuerImageId")
.HasColumnType("text");
b.Property<decimal>("Leverage")
.HasColumnType("numeric(10,4)");
b.Property<string>("NextGenProductCategoryName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<int>("OptionType")
.HasColumnType("integer");
b.Property<string>("ProductCategoryName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<decimal?>("Size")
.HasColumnType("numeric");
b.Property<decimal>("Strike")
.HasColumnType("numeric(18,6)");
b.Property<string>("UnderlyingIsin")
.HasMaxLength(12)
.HasColumnType("character varying(12)");
b.HasDiscriminator().HasValue("Derivative");
});
modelBuilder.Entity("FinlyticAssets.Entities.EtfEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("DerivativeProductCategories")
.IsRequired()
.HasColumnType("text");
b.Property<string>("EtfDescription")
.IsRequired()
.HasColumnType("text");
b.Property<string>("MappedEtfIndexName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("SearchSubtitle")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Subtitle")
.IsRequired()
.HasColumnType("text");
b.ToTable("TradeRepublicAssets", t =>
{
t.Property("DerivativeProductCategories")
.HasColumnName("EtfEntity_DerivativeProductCategories");
t.Property("SearchSubtitle")
.HasColumnName("EtfEntity_SearchSubtitle");
t.Property("Subtitle")
.HasColumnName("EtfEntity_Subtitle");
});
b.HasDiscriminator().HasValue("Etf");
});
modelBuilder.Entity("FinlyticAssets.Entities.StockEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("DerivativeProductCategories")
.IsRequired()
.HasColumnType("text");
b.ToTable("TradeRepublicAssets", t =>
{
t.Property("DerivativeProductCategories")
.HasColumnName("StockEntity_DerivativeProductCategories");
});
b.HasDiscriminator().HasValue("Stock");
});
modelBuilder.Entity("FinlyticAssets.Entities.SyntheticEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("DerivativeProductCategories")
.IsRequired()
.HasColumnType("text");
b.ToTable("TradeRepublicAssets", t =>
{
t.Property("DerivativeProductCategories")
.HasColumnName("SyntheticEntity_DerivativeProductCategories");
});
b.HasDiscriminator().HasValue("Synthetic");
});
modelBuilder.Entity("AssetEntityTagEntity", b =>
{
b.HasOne("FinlyticAssets.Entities.TagEntity", null)
.WithMany()
.HasForeignKey("TagsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FinlyticAssets.Entities.AssetEntity", null)
.WithMany()
.HasForeignKey("AssetsIsin", "AssetsInstrumentCategory")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,184 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace FinlyticAssets.Migrations
{
/// <inheritdoc />
public partial class UpdateDerivativeAssets : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "UnderlyingIsin",
table: "TradeRepublicAssets",
type: "character varying(12)",
maxLength: 12,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AddColumn<decimal>(
name: "Barrier",
table: "TradeRepublicAssets",
type: "numeric(18,6)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Currency",
table: "TradeRepublicAssets",
type: "character varying(10)",
maxLength: 10,
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "Delta",
table: "TradeRepublicAssets",
type: "numeric",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "Expiry",
table: "TradeRepublicAssets",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "Factor",
table: "TradeRepublicAssets",
type: "numeric",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Issuer",
table: "TradeRepublicAssets",
type: "character varying(150)",
maxLength: 150,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "IssuerDisplayName",
table: "TradeRepublicAssets",
type: "character varying(150)",
maxLength: 150,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "IssuerImageId",
table: "TradeRepublicAssets",
type: "text",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "Leverage",
table: "TradeRepublicAssets",
type: "numeric(10,4)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "NextGenProductCategoryName",
table: "TradeRepublicAssets",
type: "character varying(100)",
maxLength: 100,
nullable: true);
migrationBuilder.AddColumn<int>(
name: "OptionType",
table: "TradeRepublicAssets",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ProductCategoryName",
table: "TradeRepublicAssets",
type: "character varying(100)",
maxLength: 100,
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "Size",
table: "TradeRepublicAssets",
type: "numeric",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "Strike",
table: "TradeRepublicAssets",
type: "numeric(18,6)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Barrier",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "Currency",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "Delta",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "Expiry",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "Factor",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "Issuer",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "IssuerDisplayName",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "IssuerImageId",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "Leverage",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "NextGenProductCategoryName",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "OptionType",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "ProductCategoryName",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "Size",
table: "TradeRepublicAssets");
migrationBuilder.DropColumn(
name: "Strike",
table: "TradeRepublicAssets");
migrationBuilder.AlterColumn<string>(
name: "UnderlyingIsin",
table: "TradeRepublicAssets",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(12)",
oldMaxLength: 12,
oldNullable: true);
}
}
}
@@ -0,0 +1,365 @@
// <auto-generated />
using System;
using FinlyticAssets.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace FinlyticAssets.Migrations
{
[DbContext(typeof(AssetsDbContext))]
[Migration("20260813205059_AddDynamicSettings")]
partial class AddDynamicSettings
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("AssetEntityTagEntity", b =>
{
b.Property<string>("TagsId")
.HasColumnType("text");
b.Property<string>("AssetsIsin")
.HasColumnType("text");
b.Property<string>("AssetsInstrumentCategory")
.HasColumnType("text");
b.HasKey("TagsId", "AssetsIsin", "AssetsInstrumentCategory");
b.HasIndex("AssetsIsin", "AssetsInstrumentCategory");
b.ToTable("AssetEntityTagEntity");
});
modelBuilder.Entity("FinlyticAssets.Entities.AssetEntity", b =>
{
b.Property<string>("Isin")
.HasColumnType("text");
b.Property<string>("InstrumentCategory")
.HasColumnType("text");
b.Property<string>("AssetType")
.IsRequired()
.HasMaxLength(13)
.HasColumnType("character varying(13)");
b.Property<bool>("HasCfd")
.HasColumnType("boolean");
b.Property<string>("ImageId")
.HasColumnType("text");
b.Property<DateTime>("LastUpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.HasKey("Isin", "InstrumentCategory");
b.HasIndex("LastUpdatedAt");
b.ToTable("TradeRepublicAssets");
b.HasDiscriminator<string>("AssetType").HasValue("AssetEntity");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("FinlyticAssets.Entities.Settings", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("AssetUpdateTypeDelay")
.HasColumnType("integer");
b.Property<int>("BatchAssetUpdateDelay")
.HasColumnType("integer");
b.Property<int>("CurrentScanningPage")
.HasColumnType("integer");
b.Property<string>("CurrentScanningType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<bool>("FinishedInitialScan")
.HasColumnType("boolean");
b.Property<int>("InitAssetUpdateTypeDelay")
.HasColumnType("integer");
b.Property<int>("InitBatchAssetUpdateDelay")
.HasColumnType("integer");
b.Property<int>("TradeRepublicMaxRequestPageSize")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Settings");
});
modelBuilder.Entity("FinlyticAssets.Entities.TagEntity", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("TradeRepublicTags");
});
modelBuilder.Entity("FinlyticCore.Entities.Settings.SettingEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.Property<DateTime>("LastUpdatedUtc")
.HasColumnType("timestamp with time zone");
b.Property<string>("ServiceIdentifier")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("ValueJson")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Key");
b.ToTable("DynamicSettings");
});
modelBuilder.Entity("FinlyticAssets.Entities.BondEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("BondIssuerName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("SearchSubtitle")
.IsRequired()
.HasColumnType("text");
b.HasDiscriminator().HasValue("Bond");
});
modelBuilder.Entity("FinlyticAssets.Entities.CryptoEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("SearchSubtitle")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Subtitle")
.IsRequired()
.HasColumnType("text");
b.ToTable("TradeRepublicAssets", t =>
{
t.Property("SearchSubtitle")
.HasColumnName("CryptoEntity_SearchSubtitle");
});
b.HasDiscriminator().HasValue("Crypto");
});
modelBuilder.Entity("FinlyticAssets.Entities.DerivativeEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<decimal>("Barrier")
.HasColumnType("numeric(18,6)");
b.Property<string>("Currency")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<decimal?>("Delta")
.HasColumnType("numeric");
b.Property<string>("DerivativeProductCategories")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime?>("Expiry")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("Factor")
.HasColumnType("numeric");
b.Property<string>("Issuer")
.IsRequired()
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.Property<string>("IssuerDisplayName")
.IsRequired()
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.Property<string>("IssuerImageId")
.HasColumnType("text");
b.Property<decimal>("Leverage")
.HasColumnType("numeric(10,4)");
b.Property<string>("NextGenProductCategoryName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<int>("OptionType")
.HasColumnType("integer");
b.Property<string>("ProductCategoryName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<decimal?>("Size")
.HasColumnType("numeric");
b.Property<decimal>("Strike")
.HasColumnType("numeric(18,6)");
b.Property<string>("UnderlyingIsin")
.HasMaxLength(12)
.HasColumnType("character varying(12)");
b.HasDiscriminator().HasValue("Derivative");
});
modelBuilder.Entity("FinlyticAssets.Entities.EtfEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("DerivativeProductCategories")
.IsRequired()
.HasColumnType("text");
b.Property<string>("EtfDescription")
.IsRequired()
.HasColumnType("text");
b.Property<string>("MappedEtfIndexName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("SearchSubtitle")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Subtitle")
.IsRequired()
.HasColumnType("text");
b.ToTable("TradeRepublicAssets", t =>
{
t.Property("DerivativeProductCategories")
.HasColumnName("EtfEntity_DerivativeProductCategories");
t.Property("SearchSubtitle")
.HasColumnName("EtfEntity_SearchSubtitle");
t.Property("Subtitle")
.HasColumnName("EtfEntity_Subtitle");
});
b.HasDiscriminator().HasValue("Etf");
});
modelBuilder.Entity("FinlyticAssets.Entities.StockEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("DerivativeProductCategories")
.IsRequired()
.HasColumnType("text");
b.ToTable("TradeRepublicAssets", t =>
{
t.Property("DerivativeProductCategories")
.HasColumnName("StockEntity_DerivativeProductCategories");
});
b.HasDiscriminator().HasValue("Stock");
});
modelBuilder.Entity("FinlyticAssets.Entities.SyntheticEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("DerivativeProductCategories")
.IsRequired()
.HasColumnType("text");
b.ToTable("TradeRepublicAssets", t =>
{
t.Property("DerivativeProductCategories")
.HasColumnName("SyntheticEntity_DerivativeProductCategories");
});
b.HasDiscriminator().HasValue("Synthetic");
});
modelBuilder.Entity("AssetEntityTagEntity", b =>
{
b.HasOne("FinlyticAssets.Entities.TagEntity", null)
.WithMany()
.HasForeignKey("TagsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FinlyticAssets.Entities.AssetEntity", null)
.WithMany()
.HasForeignKey("AssetsIsin", "AssetsInstrumentCategory")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace FinlyticAssets.Migrations
{
/// <inheritdoc />
public partial class AddDynamicSettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "DynamicSettings",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Key = table.Column<string>(type: "character varying(150)", maxLength: 150, nullable: false),
ValueJson = table.Column<string>(type: "text", nullable: false),
ServiceIdentifier = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
LastUpdatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DynamicSettings", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_DynamicSettings_Key",
table: "DynamicSettings",
column: "Key");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DynamicSettings");
}
}
}
@@ -1,4 +1,4 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using FinlyticAssets.Database; using FinlyticAssets.Database;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -40,44 +40,7 @@ namespace FinlyticAssets.Migrations
b.ToTable("AssetEntityTagEntity"); b.ToTable("AssetEntityTagEntity");
}); });
modelBuilder.Entity("FinlyticAssets.Entities.Settings", b => modelBuilder.Entity("FinlyticAssets.Entities.AssetEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("AssetUpdateTypeDelay")
.HasColumnType("integer");
b.Property<int>("BatchAssetUpdateDelay")
.HasColumnType("integer");
b.Property<int>("CurrentScanningPage")
.HasColumnType("integer");
b.Property<string>("CurrentScanningType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<bool>("FinishedInitialScan")
.HasColumnType("boolean");
b.Property<int>("InitAssetUpdateTypeDelay")
.HasColumnType("integer");
b.Property<int>("InitBatchAssetUpdateDelay")
.HasColumnType("integer");
b.Property<int>("TradeRepublicMaxRequestPageSize")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Settings");
});
modelBuilder.Entity("FinlyticCore.Entities.Assets.AssetEntity", b =>
{ {
b.Property<string>("Isin") b.Property<string>("Isin")
.HasColumnType("text"); .HasColumnType("text");
@@ -118,7 +81,44 @@ namespace FinlyticAssets.Migrations
b.UseTphMappingStrategy(); b.UseTphMappingStrategy();
}); });
modelBuilder.Entity("FinlyticCore.Entities.Assets.TagEntity", b => modelBuilder.Entity("FinlyticAssets.Entities.Settings", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("AssetUpdateTypeDelay")
.HasColumnType("integer");
b.Property<int>("BatchAssetUpdateDelay")
.HasColumnType("integer");
b.Property<int>("CurrentScanningPage")
.HasColumnType("integer");
b.Property<string>("CurrentScanningType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<bool>("FinishedInitialScan")
.HasColumnType("boolean");
b.Property<int>("InitAssetUpdateTypeDelay")
.HasColumnType("integer");
b.Property<int>("InitBatchAssetUpdateDelay")
.HasColumnType("integer");
b.Property<int>("TradeRepublicMaxRequestPageSize")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Settings");
});
modelBuilder.Entity("FinlyticAssets.Entities.TagEntity", b =>
{ {
b.Property<string>("Id") b.Property<string>("Id")
.HasColumnType("text"); .HasColumnType("text");
@@ -136,9 +136,39 @@ namespace FinlyticAssets.Migrations
b.ToTable("TradeRepublicTags"); b.ToTable("TradeRepublicTags");
}); });
modelBuilder.Entity("FinlyticCore.Entities.Assets.BondEntity", b => modelBuilder.Entity("FinlyticCore.Entities.Settings.SettingEntity", b =>
{ {
b.HasBaseType("FinlyticCore.Entities.Assets.AssetEntity"); b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.Property<DateTime>("LastUpdatedUtc")
.HasColumnType("timestamp with time zone");
b.Property<string>("ServiceIdentifier")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("ValueJson")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Key");
b.ToTable("DynamicSettings");
});
modelBuilder.Entity("FinlyticAssets.Entities.BondEntity", b =>
{
b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("BondIssuerName") b.Property<string>("BondIssuerName")
.IsRequired() .IsRequired()
@@ -151,9 +181,9 @@ namespace FinlyticAssets.Migrations
b.HasDiscriminator().HasValue("Bond"); b.HasDiscriminator().HasValue("Bond");
}); });
modelBuilder.Entity("FinlyticCore.Entities.Assets.CryptoEntity", b => modelBuilder.Entity("FinlyticAssets.Entities.CryptoEntity", b =>
{ {
b.HasBaseType("FinlyticCore.Entities.Assets.AssetEntity"); b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("SearchSubtitle") b.Property<string>("SearchSubtitle")
.IsRequired() .IsRequired()
@@ -172,23 +202,76 @@ namespace FinlyticAssets.Migrations
b.HasDiscriminator().HasValue("Crypto"); b.HasDiscriminator().HasValue("Crypto");
}); });
modelBuilder.Entity("FinlyticCore.Entities.Assets.DerivativeEntity", b => modelBuilder.Entity("FinlyticAssets.Entities.DerivativeEntity", b =>
{ {
b.HasBaseType("FinlyticCore.Entities.Assets.AssetEntity"); b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<decimal>("Barrier")
.HasColumnType("numeric(18,6)");
b.Property<string>("Currency")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<decimal?>("Delta")
.HasColumnType("numeric");
b.Property<string>("DerivativeProductCategories") b.Property<string>("DerivativeProductCategories")
.IsRequired() .IsRequired()
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("UnderlyingIsin") b.Property<DateTime?>("Expiry")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("Factor")
.HasColumnType("numeric");
b.Property<string>("Issuer")
.IsRequired()
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.Property<string>("IssuerDisplayName")
.IsRequired()
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.Property<string>("IssuerImageId")
.HasColumnType("text"); .HasColumnType("text");
b.Property<decimal>("Leverage")
.HasColumnType("numeric(10,4)");
b.Property<string>("NextGenProductCategoryName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<int>("OptionType")
.HasColumnType("integer");
b.Property<string>("ProductCategoryName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<decimal?>("Size")
.HasColumnType("numeric");
b.Property<decimal>("Strike")
.HasColumnType("numeric(18,6)");
b.Property<string>("UnderlyingIsin")
.HasMaxLength(12)
.HasColumnType("character varying(12)");
b.HasDiscriminator().HasValue("Derivative"); b.HasDiscriminator().HasValue("Derivative");
}); });
modelBuilder.Entity("FinlyticCore.Entities.Assets.EtfEntity", b => modelBuilder.Entity("FinlyticAssets.Entities.EtfEntity", b =>
{ {
b.HasBaseType("FinlyticCore.Entities.Assets.AssetEntity"); b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("DerivativeProductCategories") b.Property<string>("DerivativeProductCategories")
.IsRequired() .IsRequired()
@@ -225,9 +308,9 @@ namespace FinlyticAssets.Migrations
b.HasDiscriminator().HasValue("Etf"); b.HasDiscriminator().HasValue("Etf");
}); });
modelBuilder.Entity("FinlyticCore.Entities.Assets.StockEntity", b => modelBuilder.Entity("FinlyticAssets.Entities.StockEntity", b =>
{ {
b.HasBaseType("FinlyticCore.Entities.Assets.AssetEntity"); b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("DerivativeProductCategories") b.Property<string>("DerivativeProductCategories")
.IsRequired() .IsRequired()
@@ -242,9 +325,9 @@ namespace FinlyticAssets.Migrations
b.HasDiscriminator().HasValue("Stock"); b.HasDiscriminator().HasValue("Stock");
}); });
modelBuilder.Entity("FinlyticCore.Entities.Assets.SyntheticEntity", b => modelBuilder.Entity("FinlyticAssets.Entities.SyntheticEntity", b =>
{ {
b.HasBaseType("FinlyticCore.Entities.Assets.AssetEntity"); b.HasBaseType("FinlyticAssets.Entities.AssetEntity");
b.Property<string>("DerivativeProductCategories") b.Property<string>("DerivativeProductCategories")
.IsRequired() .IsRequired()
@@ -261,13 +344,13 @@ namespace FinlyticAssets.Migrations
modelBuilder.Entity("AssetEntityTagEntity", b => modelBuilder.Entity("AssetEntityTagEntity", b =>
{ {
b.HasOne("FinlyticCore.Entities.Assets.TagEntity", null) b.HasOne("FinlyticAssets.Entities.TagEntity", null)
.WithMany() .WithMany()
.HasForeignKey("TagsId") .HasForeignKey("TagsId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("FinlyticCore.Entities.Assets.AssetEntity", null) b.HasOne("FinlyticAssets.Entities.AssetEntity", null)
.WithMany() .WithMany()
.HasForeignKey("AssetsIsin", "AssetsInstrumentCategory") .HasForeignKey("AssetsIsin", "AssetsInstrumentCategory")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@@ -4,8 +4,8 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FinlyticAssets.Models; using FinlyticAssets.Models;
using FinlyticCore.Dtos.TradeRepublic;
using FinlyticCore.Models.Assets; using FinlyticCore.Models.Assets;
using FinlyticCore.Models.TradeRepublic;
using FinlyticCore.Services.TradeRepublic; using FinlyticCore.Services.TradeRepublic;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
+78 -2
View File
@@ -1,7 +1,6 @@
using FinlyticAssets.Database; using FinlyticAssets.Database;
using FinlyticAssets.Entities; using FinlyticAssets.Entities;
using FinlyticCore.Entities.Assets; using FinlyticCore.Dtos.TradeRepublic;
using FinlyticCore.Models.TradeRepublic;
using FinlyticCore.Services.TradeRepublic; using FinlyticCore.Services.TradeRepublic;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -21,6 +20,7 @@ public interface IAssetsDbService
public Task UpdateAssetImageIdAsync(string isin, string imageId); public Task UpdateAssetImageIdAsync(string isin, string imageId);
public Task<bool> DeleteAssetAsync(string isin); public Task<bool> DeleteAssetAsync(string isin);
public Task<List<AssetEntity>> GetDiscoveryAssetsAsync(int limit = 15); 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 /> /// <inheritdoc />
@@ -393,6 +393,82 @@ public class AssetsDbService : IAssetsDbService
return true; 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 #region Helper & Mapping Methods
private AssetEntity MapDtoToEntity(TradeRepublicAsset dto) private AssetEntity MapDtoToEntity(TradeRepublicAsset dto)
+17 -5
View File
@@ -1,12 +1,10 @@
using FinlyticAssets.Entities;
using FinlyticCore.Dtos.Assets; using FinlyticCore.Dtos.Assets;
namespace FinlyticCore.Util; namespace FinlyticAssets.Util;
public static class AssetMapper public static class AssetMapper
{ {
public static AssetDto ToDto(this AssetEntity entity) public static AssetDto ToDto(this AssetEntity entity)
{ {
var dtoTags = entity.Tags.Select(t => new TagDto var dtoTags = entity.Tags.Select(t => new TagDto
@@ -83,7 +81,21 @@ public static class AssetMapper
LastUpdatedAt = deriv.LastUpdatedAt, LastUpdatedAt = deriv.LastUpdatedAt,
Tags = dtoTags, Tags = dtoTags,
DerivativeProductCategories = deriv.DerivativeProductCategories, DerivativeProductCategories = deriv.DerivativeProductCategories,
UnderlyingIsin = deriv.UnderlyingIsin UnderlyingIsin = deriv.UnderlyingIsin,
OptionType = deriv.OptionType.ToString(),
ProductCategoryName = deriv.ProductCategoryName,
NextGenProductCategoryName = deriv.NextGenProductCategoryName,
Strike = deriv.Strike,
Barrier = deriv.Barrier,
Leverage = deriv.Leverage,
Size = deriv.Size,
Factor = deriv.Factor,
Delta = deriv.Delta,
Currency = deriv.Currency,
Expiry = deriv.Expiry,
Issuer = deriv.Issuer,
IssuerDisplayName = deriv.IssuerDisplayName,
IssuerImageId = deriv.IssuerImageId
}, },
SyntheticEntity synth => new SyntheticDto SyntheticEntity synth => new SyntheticDto
{ {
+25 -6
View File
@@ -1,13 +1,8 @@
using System.Text.Json; using System.Text.Json;
using FinlyticAssets.Entities;
using FinlyticAssets.Services; using FinlyticAssets.Services;
using FinlyticCore.Entities.Assets;
using FinlyticCore.Models; using FinlyticCore.Models;
using FinlyticCore.Models.Assets;
using FinlyticCore.Util; using FinlyticCore.Util;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace FinlyticAssets.Util; namespace FinlyticAssets.Util;
@@ -61,6 +56,7 @@ public class AssetsMqttClient(
await SubscribeAsync("services/request/assets_Get/#"); await SubscribeAsync("services/request/assets_Get/#");
await SubscribeAsync("services/request/assets_Search/#"); await SubscribeAsync("services/request/assets_Search/#");
await SubscribeAsync("services/request/assets_GetDiscovery/#"); await SubscribeAsync("services/request/assets_GetDiscovery/#");
await SubscribeAsync("services/request/assets_GetDerivatives/#");
await SubscribeAsync("services/request/assets_FetchLogo/#"); await SubscribeAsync("services/request/assets_FetchLogo/#");
await SubscribeAsync("services/request/health_Ping/#"); await SubscribeAsync("services/request/health_Ping/#");
await SubscribeAsync("services/config/updated/#"); await SubscribeAsync("services/config/updated/#");
@@ -119,6 +115,9 @@ public class AssetsMqttClient(
case "assets_GetDiscovery": case "assets_GetDiscovery":
responseData = await HandleAssetsGetDiscoveryAsync(payload, dbService); responseData = await HandleAssetsGetDiscoveryAsync(payload, dbService);
break; break;
case "assets_GetDerivatives":
responseData = (await HandleAssetsGetDerivativesAsync(payload, dbService)).Cast<AssetEntity>().ToList();
break;
} }
string defaultResponseTopic = $"services/response/{channel}/{correlationId}"; string defaultResponseTopic = $"services/response/{channel}/{correlationId}";
@@ -216,4 +215,24 @@ public class AssetsMqttClient(
} }
return await dbService.GetDiscoveryAssetsAsync(limit); return await dbService.GetDiscoveryAssetsAsync(limit);
} }
private async Task<List<DerivativeEntity>> HandleAssetsGetDerivativesAsync(string payload, IAssetsDbService dbService)
{
if (string.IsNullOrWhiteSpace(payload)) return [];
try
{
var req = JsonSerializer.Deserialize(payload, FinlyticJsonSerializerContext.Default.GetDerivativesRequest);
if (req != null && !string.IsNullOrEmpty(req.UnderlyingIsin))
{
return await dbService.GetDerivativesByUnderlyingAsync(req.UnderlyingIsin, req.OptionType, req.ShouldForceRefresh);
}
}
catch (Exception ex)
{
logger.LogError(ex, "[{Channel}] Error parsing GetDerivativesRequest payload.", "AssetsChannel");
}
return [];
}
} }