using System; using System.Collections.Generic; using System.Text.Json; using FinlyticCore.Database; using FinlyticCore.Dtos.TechnicalAnalysis; using FinlyticCore.Entities.Settings; using FinlyticTechnicals.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace FinlyticTechnicals.Database; public class TechnicalAnalysisDbContext : DbContext, ISettingsDbContext { private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false }; public TechnicalAnalysisDbContext(DbContextOptions options) : base(options) { } public DbSet DynamicSettings => Set(); public DbSet FtaCandles => Set(); public DbSet FtaTechnicalSetups => Set(); public DbSet FtaDetectedPatterns => Set(); public DbSet MonitoredUniverseAssets => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 1. Settings Table modelBuilder.Entity(entity => { entity.HasKey(e => e.Id); entity.HasIndex(e => e.Key).IsUnique(); }); // 2. FTA Candles Table & Composite Time-Series Index modelBuilder.Entity(entity => { entity.HasKey(e => e.Id); entity.HasIndex(e => new { e.Isin, e.Timeframe, e.TimestampUtc }); entity.HasIndex(e => e.TimestampUtc); }); // 3. JSONB Value Converters for Complex Types var exitPlanConverter = new ValueConverter( v => JsonSerializer.Serialize(v, JsonOptions), v => JsonSerializer.Deserialize(v, JsonOptions) ?? new ExitPlan(ExitStrategyType.FixedSingleTarget, 0m, new List(), null, null, null, null) ); var patternsConverter = new ValueConverter, string>( v => JsonSerializer.Serialize(v, JsonOptions), v => JsonSerializer.Deserialize>(v, JsonOptions) ?? new List() ); var indicatorSnapshotConverter = new ValueConverter, string>( v => JsonSerializer.Serialize(v, JsonOptions), v => JsonSerializer.Deserialize>(v, JsonOptions) ?? new Dictionary() ); var extraDataConverter = new ValueConverter?, string>( v => v == null ? "{}" : JsonSerializer.Serialize(v, JsonOptions), v => string.IsNullOrWhiteSpace(v) ? null : JsonSerializer.Deserialize>(v, JsonOptions) ); // 4. FTA Technical Setups Table & JSONB mappings modelBuilder.Entity(entity => { entity.HasKey(e => e.SetupId); entity.HasIndex(e => new { e.Isin, e.IsActive, e.ExpiresAtUtc }); entity.HasIndex(e => e.CreatedAtUtc); entity.HasIndex(e => e.QualityScore); entity.HasIndex(e => e.IsTopPick); entity.HasIndex(e => new { e.IsActive, e.IsTopPick, e.QualityScore }); entity.Property(e => e.ExitPlan) .HasColumnType("jsonb") .HasConversion(exitPlanConverter); entity.Property(e => e.TriggeringPatterns) .HasColumnType("jsonb") .HasConversion(patternsConverter); entity.Property(e => e.IndicatorSnapshot) .HasColumnType("jsonb") .HasConversion(indicatorSnapshotConverter); }); // 5. FTA Detected Patterns Table & JSONB mappings modelBuilder.Entity(entity => { entity.HasKey(e => e.Id); entity.HasIndex(e => new { e.Isin, e.DetectedAtUtc }); entity.HasIndex(e => e.PatternType); entity.HasIndex(e => e.QualityScore); entity.Property(e => e.ExtraData) .HasColumnType("jsonb") .HasConversion(extraDataConverter); }); // 6. Monitored Universe Assets Table (persisted backing store for TechnicalUniverseManager, cleared on // every service startup - see FtaMonitoredUniverseAssetEntity's doc comment) modelBuilder.Entity(entity => { entity.HasKey(e => e.Isin); entity.HasIndex(e => e.Source); entity.HasIndex(e => e.ExpiresAtUtc); }); } } public class TechnicalAnalysisDbContextFactory : IDesignTimeDbContextFactory { public TechnicalAnalysisDbContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseNpgsql("Host=localhost;Database=finlytic_ta;Username=postgres;Password=postgres"); return new TechnicalAnalysisDbContext(optionsBuilder.Options); } }