760 lines
32 KiB
C#
760 lines
32 KiB
C#
using Managing.Infrastructure.Databases.PostgreSql.Entities;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Managing.Infrastructure.Databases.PostgreSql;
|
|
|
|
public class ManagingDbContext : DbContext
|
|
{
|
|
private readonly ILogger<ManagingDbContext>? _logger;
|
|
private readonly SentrySqlMonitoringService? _sentryMonitoringService;
|
|
private readonly Dictionary<string, int> _queryExecutionCounts = new();
|
|
private readonly object _queryCountLock = new object();
|
|
|
|
public ManagingDbContext(DbContextOptions<ManagingDbContext> options) : base(options)
|
|
{
|
|
}
|
|
|
|
public ManagingDbContext(DbContextOptions<ManagingDbContext> options, ILogger<ManagingDbContext> logger, SentrySqlMonitoringService sentryMonitoringService)
|
|
: base(options)
|
|
{
|
|
_logger = logger;
|
|
_sentryMonitoringService = sentryMonitoringService;
|
|
}
|
|
|
|
public DbSet<AccountEntity> Accounts { get; set; }
|
|
public DbSet<UserEntity> Users { get; set; }
|
|
public DbSet<GeneticRequestEntity> GeneticRequests { get; set; }
|
|
public DbSet<BacktestEntity> Backtests { get; set; }
|
|
public DbSet<BundleBacktestRequestEntity> BundleBacktestRequests { get; set; }
|
|
|
|
// Trading entities
|
|
public DbSet<ScenarioEntity> Scenarios { get; set; }
|
|
public DbSet<IndicatorEntity> Indicators { get; set; }
|
|
public DbSet<ScenarioIndicatorEntity> ScenarioIndicators { get; set; }
|
|
public DbSet<SignalEntity> Signals { get; set; }
|
|
public DbSet<PositionEntity> Positions { get; set; }
|
|
public DbSet<TradeEntity> Trades { get; set; }
|
|
|
|
|
|
// Statistics entities
|
|
public DbSet<TopVolumeTickerEntity> TopVolumeTickers { get; set; }
|
|
public DbSet<SpotlightOverviewEntity> SpotlightOverviews { get; set; }
|
|
public DbSet<TraderEntity> Traders { get; set; }
|
|
public DbSet<FundingRateEntity> FundingRates { get; set; }
|
|
public DbSet<AgentSummaryEntity> AgentSummaries { get; set; }
|
|
|
|
// Bot entities
|
|
public DbSet<BotEntity> Bots { get; set; }
|
|
|
|
// Settings entities
|
|
public DbSet<MoneyManagementEntity> MoneyManagements { get; set; }
|
|
|
|
// Worker entities
|
|
public DbSet<WorkerEntity> Workers { get; set; }
|
|
|
|
public DbSet<SynthMinersLeaderboardEntity> SynthMinersLeaderboards { get; set; }
|
|
public DbSet<SynthPredictionEntity> SynthPredictions { get; set; }
|
|
public DbSet<WhitelistAccountEntity> WhitelistAccounts { get; set; }
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
// Configure schema for Orleans tables (if needed for future organization)
|
|
// Orleans tables will remain in the default schema for now
|
|
// This can be changed later if needed by configuring specific schemas
|
|
|
|
// Configure Account entity
|
|
modelBuilder.Entity<AccountEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.Key).HasMaxLength(500);
|
|
entity.Property(e => e.Secret).HasMaxLength(500);
|
|
entity.Property(e => e.Exchange)
|
|
.IsRequired()
|
|
.HasConversion<string>(); // Store enum as string
|
|
entity.Property(e => e.Type)
|
|
.IsRequired()
|
|
.HasConversion<string>(); // Store enum as string
|
|
entity.Property(e => e.IsGmxInitialized)
|
|
.IsRequired()
|
|
.HasDefaultValue(false); // Default value for new records
|
|
|
|
// Create unique index on account name
|
|
entity.HasIndex(e => e.Name).IsUnique();
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany(u => u.Accounts)
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
});
|
|
|
|
// Configure User entity
|
|
modelBuilder.Entity<UserEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.AgentName).HasMaxLength(255);
|
|
entity.Property(e => e.AvatarUrl).HasMaxLength(500);
|
|
entity.Property(e => e.TelegramChannel).HasMaxLength(255);
|
|
|
|
// Create indexes for performance
|
|
entity.HasIndex(e => e.Name).IsUnique();
|
|
entity.HasIndex(e => e.AgentName);
|
|
});
|
|
|
|
// Configure GeneticRequest entity
|
|
modelBuilder.Entity<GeneticRequestEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.Status).IsRequired().HasMaxLength(50);
|
|
entity.Property(e => e.Ticker)
|
|
.IsRequired()
|
|
.HasConversion<string>(); // Store enum as string
|
|
entity.Property(e => e.Timeframe)
|
|
.IsRequired()
|
|
.HasConversion<string>(); // Store enum as string
|
|
entity.Property(e => e.SelectionMethod)
|
|
.IsRequired()
|
|
.HasConversion<string>(); // Store enum as string
|
|
entity.Property(e => e.CrossoverMethod)
|
|
.IsRequired()
|
|
.HasConversion<string>(); // Store enum as string
|
|
entity.Property(e => e.MutationMethod)
|
|
.IsRequired()
|
|
.HasConversion<string>(); // Store enum as string
|
|
entity.Property(e => e.Balance).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.BestIndividual).HasMaxLength(4000);
|
|
entity.Property(e => e.ErrorMessage).HasMaxLength(2000);
|
|
entity.Property(e => e.ProgressInfo).HasMaxLength(4000);
|
|
entity.Property(e => e.BestChromosome).HasMaxLength(4000);
|
|
entity.Property(e => e.EligibleIndicatorsJson).HasMaxLength(2000);
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.RequestId).IsUnique();
|
|
entity.HasIndex(e => e.Status);
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
});
|
|
|
|
// Configure Backtest entity
|
|
modelBuilder.Entity<BacktestEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.UserId).IsRequired();
|
|
entity.Property(e => e.FinalPnl).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.GrowthPercentage).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.HodlPercentage).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.Fees).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.ConfigJson).HasColumnType("jsonb");
|
|
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.Ticker).HasMaxLength(32);
|
|
entity.Property(e => e.Timeframe).IsRequired();
|
|
entity.Property(e => e.IndicatorsCsv).HasColumnType("text");
|
|
entity.Property(e => e.IndicatorsCount).IsRequired();
|
|
entity.Property(e => e.PositionsJson).HasColumnType("jsonb");
|
|
entity.Property(e => e.SignalsJson).HasColumnType("jsonb");
|
|
entity.Property(e => e.MoneyManagementJson).HasColumnType("jsonb");
|
|
entity.Property(e => e.StatisticsJson).HasColumnType("jsonb");
|
|
entity.Property(e => e.SharpeRatio).HasColumnType("decimal(18,8)").HasDefaultValue(0m);
|
|
entity.Property(e => e.MaxDrawdown).HasColumnType("decimal(18,8)").HasDefaultValue(0m);
|
|
entity.Property(e => e.MaxDrawdownRecoveryTime).HasDefaultValue(TimeSpan.Zero);
|
|
entity.Property(e => e.Duration).HasDefaultValue(TimeSpan.Zero);
|
|
entity.Property(e => e.ScoreMessage).HasMaxLength(1000);
|
|
entity.Property(e => e.Metadata).HasColumnType("text");
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
// Create indexes for common queries
|
|
entity.HasIndex(e => e.Identifier).IsUnique();
|
|
entity.HasIndex(e => e.RequestId);
|
|
entity.HasIndex(e => e.UserId);
|
|
entity.HasIndex(e => e.Score);
|
|
|
|
// Composite indexes for efficient pagination and filtering
|
|
entity.HasIndex(e => new { e.UserId, e.Score });
|
|
entity.HasIndex(e => new { e.UserId, e.Name });
|
|
entity.HasIndex(e => new { e.RequestId, e.Score });
|
|
entity.HasIndex(e => new { e.UserId, e.Ticker });
|
|
entity.HasIndex(e => new { e.UserId, e.Timeframe });
|
|
});
|
|
|
|
// Configure BundleBacktestRequest entity
|
|
modelBuilder.Entity<BundleBacktestRequestEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.UserId);
|
|
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.Version).IsRequired().HasDefaultValue(1);
|
|
entity.Property(e => e.Status)
|
|
.IsRequired()
|
|
.HasConversion<string>(); // Store enum as string
|
|
entity.Property(e => e.UniversalConfigJson).HasColumnType("text");
|
|
entity.Property(e => e.DateTimeRangesJson).HasColumnType("text");
|
|
entity.Property(e => e.MoneyManagementVariantsJson).HasColumnType("text");
|
|
entity.Property(e => e.TickerVariantsJson).HasColumnType("text");
|
|
entity.Property(e => e.ErrorMessage).HasColumnType("text");
|
|
entity.Property(e => e.ProgressInfo).HasColumnType("text");
|
|
entity.Property(e => e.CurrentBacktest).HasMaxLength(500);
|
|
entity.Property(e => e.ResultsJson).HasColumnType("jsonb");
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
// Create indexes for common queries
|
|
entity.HasIndex(e => e.RequestId).IsUnique();
|
|
entity.HasIndex(e => e.UserId);
|
|
entity.HasIndex(e => e.Status);
|
|
|
|
// Composite index for user queries ordered by creation date
|
|
entity.HasIndex(e => new { e.UserId, e.CreatedAt });
|
|
|
|
// Composite index for user queries by name and version
|
|
entity.HasIndex(e => new { e.UserId, e.Name, e.Version });
|
|
});
|
|
|
|
// Configure Scenario entity
|
|
modelBuilder.Entity<ScenarioEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.UserId).IsRequired();
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.UserId);
|
|
|
|
// Composite index for user scenarios
|
|
entity.HasIndex(e => new { e.UserId, e.Name });
|
|
});
|
|
|
|
// Configure Indicator entity
|
|
modelBuilder.Entity<IndicatorEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.Type).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Timeframe).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.SignalType).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.UserId);
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.UserId);
|
|
|
|
// Composite index for user indicators
|
|
entity.HasIndex(e => new { e.UserId, e.Name });
|
|
});
|
|
|
|
// Configure ScenarioIndicator junction table
|
|
modelBuilder.Entity<ScenarioIndicatorEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
|
|
// Configure relationships
|
|
entity.HasOne(e => e.Scenario)
|
|
.WithMany(s => s.ScenarioIndicators)
|
|
.HasForeignKey(e => e.ScenarioId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
entity.HasOne(e => e.Indicator)
|
|
.WithMany(i => i.ScenarioIndicators)
|
|
.HasForeignKey(e => e.IndicatorId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => new { e.ScenarioId, e.IndicatorId }).IsUnique();
|
|
});
|
|
|
|
// Configure Signal entity
|
|
modelBuilder.Entity<SignalEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.Direction).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Confidence).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Timeframe).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Type).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.SignalType).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.IndicatorName).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.UserId);
|
|
entity.Property(e => e.CandleJson).HasColumnType("text");
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.Identifier);
|
|
entity.HasIndex(e => e.UserId);
|
|
entity.HasIndex(e => e.Date);
|
|
entity.HasIndex(e => e.Ticker);
|
|
entity.HasIndex(e => e.Status);
|
|
|
|
// Composite indexes for common queries
|
|
entity.HasIndex(e => new { e.UserId, e.Date });
|
|
entity.HasIndex(e => new { e.Identifier, e.Date, e.UserId }).IsUnique();
|
|
});
|
|
|
|
// Configure Position entity
|
|
modelBuilder.Entity<PositionEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Identifier);
|
|
entity.Property(e => e.ProfitAndLoss).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.NetPnL).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.OriginDirection).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Initiator).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.SignalIdentifier).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.UserId);
|
|
entity.Property(e => e.InitiatorIdentifier).IsRequired();
|
|
entity.Property(e => e.MoneyManagementJson).HasColumnType("text");
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
// Configure relationships with trades
|
|
entity.HasOne(e => e.OpenTrade)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.OpenTradeId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
entity.HasOne(e => e.StopLossTrade)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.StopLossTradeId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
entity.HasOne(e => e.TakeProfit1Trade)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.TakeProfit1TradeId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
entity.HasOne(e => e.TakeProfit2Trade)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.TakeProfit2TradeId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.Identifier).IsUnique();
|
|
entity.HasIndex(e => e.UserId);
|
|
entity.HasIndex(e => e.Status);
|
|
entity.HasIndex(e => e.InitiatorIdentifier);
|
|
|
|
// Composite indexes
|
|
entity.HasIndex(e => new { e.UserId, e.Identifier });
|
|
});
|
|
|
|
// Configure Trade entity
|
|
modelBuilder.Entity<TradeEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Direction).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.TradeType).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.Quantity).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.Price).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.Leverage).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.ExchangeOrderId).HasMaxLength(255);
|
|
entity.Property(e => e.Message).HasColumnType("text");
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.Date);
|
|
entity.HasIndex(e => e.Status);
|
|
entity.HasIndex(e => e.ExchangeOrderId);
|
|
});
|
|
|
|
|
|
// Configure TopVolumeTicker entity
|
|
modelBuilder.Entity<TopVolumeTickerEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Volume).HasPrecision(18, 8);
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.Ticker);
|
|
entity.HasIndex(e => e.Date);
|
|
entity.HasIndex(e => e.Exchange);
|
|
|
|
// Composite indexes for efficient queries
|
|
entity.HasIndex(e => new { e.Exchange, e.Date });
|
|
entity.HasIndex(e => new { e.Date, e.Rank });
|
|
});
|
|
|
|
// Configure SpotlightOverview entity
|
|
modelBuilder.Entity<SpotlightOverviewEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Identifier).IsRequired();
|
|
entity.Property(e => e.SpotlightsJson).HasColumnType("jsonb");
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.Identifier).IsUnique();
|
|
entity.HasIndex(e => e.DateTime);
|
|
|
|
// Composite index for efficient queries
|
|
entity.HasIndex(e => new { e.DateTime, e.ScenarioCount });
|
|
});
|
|
|
|
// Configure Trader entity
|
|
modelBuilder.Entity<TraderEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Address).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.Pnl).HasPrecision(18, 8);
|
|
entity.Property(e => e.AverageWin).HasPrecision(18, 8);
|
|
entity.Property(e => e.AverageLoss).HasPrecision(18, 8);
|
|
entity.Property(e => e.Roi).HasPrecision(18, 8);
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.Address);
|
|
entity.HasIndex(e => e.IsBestTrader);
|
|
entity.HasIndex(e => e.Winrate);
|
|
entity.HasIndex(e => e.Pnl);
|
|
entity.HasIndex(e => e.Roi);
|
|
|
|
// Composite indexes for efficient queries
|
|
entity.HasIndex(e => new { e.IsBestTrader, e.Winrate });
|
|
entity.HasIndex(e => new { e.IsBestTrader, e.Roi });
|
|
entity.HasIndex(e => new { e.Address, e.IsBestTrader }).IsUnique();
|
|
});
|
|
|
|
// Configure FundingRate entity
|
|
modelBuilder.Entity<FundingRateEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Rate).HasPrecision(18, 8);
|
|
entity.Property(e => e.OpenInterest).HasPrecision(18, 8);
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.Ticker);
|
|
entity.HasIndex(e => e.Exchange);
|
|
entity.HasIndex(e => e.Date);
|
|
|
|
// Composite indexes for efficient queries
|
|
entity.HasIndex(e => new { e.Ticker, e.Exchange });
|
|
entity.HasIndex(e => new { e.Exchange, e.Date });
|
|
entity.HasIndex(e => new { e.Ticker, e.Exchange, e.Date }).IsUnique();
|
|
});
|
|
|
|
// Configure BotBackup entity
|
|
modelBuilder.Entity<BotEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Identifier);
|
|
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.CreateDate).IsRequired();
|
|
entity.Property(e => e.StartupTime).IsRequired();
|
|
// Runtime tracking fields
|
|
entity.Property(e => e.LastStartTime);
|
|
entity.Property(e => e.LastStopTime);
|
|
entity.Property(e => e.AccumulatedRunTimeSeconds);
|
|
entity.Property(e => e.TradeWins).IsRequired();
|
|
entity.Property(e => e.TradeLosses).IsRequired();
|
|
entity.Property(e => e.Pnl).HasPrecision(18, 8);
|
|
entity.Property(e => e.NetPnL).HasPrecision(18, 8);
|
|
entity.Property(e => e.Roi).HasPrecision(18, 8);
|
|
entity.Property(e => e.Volume).HasPrecision(18, 8);
|
|
entity.Property(e => e.Fees).HasPrecision(18, 8);
|
|
entity.Property(e => e.LongPositionCount).IsRequired();
|
|
entity.Property(e => e.ShortPositionCount).IsRequired();
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.Identifier).IsUnique();
|
|
entity.HasIndex(e => e.Status);
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
});
|
|
|
|
// Configure MoneyManagement entity
|
|
modelBuilder.Entity<MoneyManagementEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.Timeframe).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.StopLoss).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.TakeProfit).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.Leverage).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.UserName).HasMaxLength(255);
|
|
|
|
// Create indexes
|
|
entity.HasIndex(e => e.UserName);
|
|
|
|
// Composite index for user money managements
|
|
entity.HasIndex(e => new { e.UserName, e.Name });
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
});
|
|
|
|
// Configure Worker entity
|
|
modelBuilder.Entity<WorkerEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.WorkerType).IsRequired().HasConversion<string>();
|
|
entity.Property(e => e.StartTime).IsRequired();
|
|
entity.Property(e => e.LastRunTime);
|
|
entity.Property(e => e.ExecutionCount).IsRequired();
|
|
entity.Property(e => e.DelayTicks).IsRequired();
|
|
entity.Property(e => e.IsActive).IsRequired();
|
|
entity.HasIndex(e => e.WorkerType).IsUnique();
|
|
});
|
|
|
|
// Configure SynthMinersLeaderboard entity
|
|
modelBuilder.Entity<SynthMinersLeaderboardEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Asset).IsRequired().HasMaxLength(32);
|
|
entity.Property(e => e.TimeIncrement).IsRequired();
|
|
entity.Property(e => e.SignalDate);
|
|
entity.Property(e => e.IsBacktest).IsRequired();
|
|
entity.Property(e => e.MinersData).HasColumnType("jsonb");
|
|
entity.Property(e => e.CacheKey).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.CreatedAt).IsRequired();
|
|
entity.HasIndex(e => e.CacheKey).IsUnique();
|
|
});
|
|
|
|
// Configure SynthPrediction entity
|
|
modelBuilder.Entity<SynthPredictionEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.Asset).IsRequired().HasMaxLength(32);
|
|
entity.Property(e => e.MinerUid).IsRequired();
|
|
entity.Property(e => e.TimeIncrement).IsRequired();
|
|
entity.Property(e => e.TimeLength).IsRequired();
|
|
entity.Property(e => e.SignalDate);
|
|
entity.Property(e => e.IsBacktest).IsRequired();
|
|
entity.Property(e => e.PredictionData).HasColumnType("jsonb");
|
|
entity.Property(e => e.CacheKey).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.CreatedAt).IsRequired();
|
|
entity.HasIndex(e => e.CacheKey).IsUnique();
|
|
});
|
|
|
|
// Configure WhitelistAccount entity
|
|
modelBuilder.Entity<WhitelistAccountEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.PrivyId).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.PrivyCreationDate).IsRequired();
|
|
entity.Property(e => e.EmbeddedWallet).IsRequired().HasMaxLength(42);
|
|
entity.Property(e => e.ExternalEthereumAccount).HasMaxLength(42);
|
|
entity.Property(e => e.TwitterAccount).HasMaxLength(255);
|
|
entity.Property(e => e.IsWhitelisted)
|
|
.IsRequired()
|
|
.HasDefaultValue(false);
|
|
entity.Property(e => e.CreatedAt).IsRequired();
|
|
entity.Property(e => e.UpdatedAt);
|
|
|
|
// Create indexes for search performance
|
|
entity.HasIndex(e => e.PrivyId).IsUnique();
|
|
entity.HasIndex(e => e.EmbeddedWallet).IsUnique();
|
|
entity.HasIndex(e => e.ExternalEthereumAccount);
|
|
entity.HasIndex(e => e.TwitterAccount);
|
|
entity.HasIndex(e => e.CreatedAt);
|
|
});
|
|
|
|
// Configure AgentSummary entity
|
|
modelBuilder.Entity<AgentSummaryEntity>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.UserId).IsRequired();
|
|
entity.Property(e => e.AgentName).IsRequired().HasMaxLength(255);
|
|
entity.Property(e => e.TotalPnL).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.TotalROI).HasColumnType("decimal(18,8)");
|
|
entity.Property(e => e.Wins).IsRequired();
|
|
entity.Property(e => e.Losses).IsRequired();
|
|
entity.Property(e => e.Runtime);
|
|
entity.Property(e => e.CreatedAt).IsRequired();
|
|
entity.Property(e => e.UpdatedAt).IsRequired();
|
|
entity.Property(e => e.ActiveStrategiesCount).IsRequired();
|
|
entity.Property(e => e.TotalVolume).HasPrecision(18, 8);
|
|
entity.Property(e => e.TotalBalance).HasPrecision(18, 8);
|
|
entity.Property(e => e.TotalFees).HasPrecision(18, 8);
|
|
entity.Property(e => e.NetPnL).HasPrecision(18, 8);
|
|
entity.Property(e => e.BacktestCount).IsRequired();
|
|
|
|
// Create indexes for common queries
|
|
entity.HasIndex(e => e.UserId).IsUnique();
|
|
entity.HasIndex(e => e.AgentName).IsUnique();
|
|
entity.HasIndex(e => e.TotalPnL);
|
|
|
|
// Configure relationship with User
|
|
entity.HasOne(e => e.User)
|
|
.WithMany()
|
|
.HasForeignKey(e => e.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures Orleans tables are properly initialized in the database.
|
|
/// This method can be called during application startup to verify Orleans infrastructure.
|
|
/// </summary>
|
|
public async Task EnsureOrleansTablesExistAsync()
|
|
{
|
|
// Orleans tables are automatically created by the Orleans framework
|
|
// when using AdoNetClustering and AdoNetReminderService.
|
|
// This method serves as a verification point and can be extended
|
|
// for custom Orleans table management if needed.
|
|
|
|
// For now, we just ensure the database is accessible
|
|
await Database.CanConnectAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets Orleans table statistics for monitoring purposes.
|
|
/// This helps track Orleans table sizes and performance.
|
|
/// </summary>
|
|
public async Task<Dictionary<string, long>> GetOrleansTableStatsAsync()
|
|
{
|
|
var stats = new Dictionary<string, long>();
|
|
|
|
// Orleans table names
|
|
var orleansTables = new[]
|
|
{
|
|
"orleansmembershiptable",
|
|
"orleansmembershipversiontable",
|
|
"orleansquery",
|
|
"orleansreminderstable",
|
|
"orleansstorage"
|
|
};
|
|
|
|
foreach (var tableName in orleansTables)
|
|
{
|
|
try
|
|
{
|
|
var count = await Database.SqlQueryRaw<long>($"SELECT COUNT(*) FROM \"{tableName}\"").FirstOrDefaultAsync();
|
|
stats[tableName] = count;
|
|
}
|
|
catch
|
|
{
|
|
// Table might not exist yet (normal during startup)
|
|
stats[tableName] = -1;
|
|
}
|
|
}
|
|
|
|
return stats;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Database organization strategy:
|
|
/// - Application tables: Default schema (public)
|
|
/// - Orleans tables: Default schema (public) - managed by Orleans framework
|
|
/// - Future consideration: Move Orleans tables to 'orleans' schema if needed
|
|
///
|
|
/// Benefits of current approach:
|
|
/// - Single database simplifies deployment and backup
|
|
/// - Orleans tables are automatically managed by the framework
|
|
/// - No additional configuration complexity
|
|
/// - Easier monitoring and maintenance
|
|
/// </summary>
|
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
{
|
|
base.OnConfiguring(optionsBuilder);
|
|
|
|
// Add any additional configuration here if needed
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tracks query execution for loop detection and performance monitoring
|
|
/// </summary>
|
|
/// <param name="queryPattern">Pattern or hash of the query</param>
|
|
/// <param name="executionTime">Time taken to execute the query</param>
|
|
/// <param name="repositoryName">Name of the repository executing the query</param>
|
|
/// <param name="methodName">Name of the method executing the query</param>
|
|
public void TrackQueryExecution(string queryPattern, TimeSpan executionTime, string repositoryName, string methodName)
|
|
{
|
|
if (_logger == null || _sentryMonitoringService == null) return;
|
|
|
|
// Track execution count for this query pattern
|
|
lock (_queryCountLock)
|
|
{
|
|
_queryExecutionCounts[queryPattern] = _queryExecutionCounts.GetValueOrDefault(queryPattern, 0) + 1;
|
|
}
|
|
|
|
// Check for potential loops with Sentry integration
|
|
var isLoopDetected = _sentryMonitoringService.TrackQueryExecution(repositoryName, methodName, queryPattern, executionTime);
|
|
|
|
// Log query execution details
|
|
var logLevel = executionTime.TotalMilliseconds > 1000 ? LogLevel.Warning : LogLevel.Debug;
|
|
_logger.Log(logLevel,
|
|
"[SQL-QUERY-TRACKED] {Repository}.{Method} | Pattern: {Pattern} | Time: {Time}ms | Count: {Count}",
|
|
repositoryName, methodName, queryPattern, executionTime.TotalMilliseconds,
|
|
_queryExecutionCounts[queryPattern]);
|
|
|
|
// Alert on potential loops
|
|
if (isLoopDetected)
|
|
{
|
|
_logger.LogError(
|
|
"[SQL-LOOP-ALERT] Potential infinite loop detected in {Repository}.{Method} with pattern '{Pattern}'",
|
|
repositoryName, methodName, queryPattern);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets current query execution statistics
|
|
/// </summary>
|
|
public Dictionary<string, int> GetQueryExecutionCounts()
|
|
{
|
|
lock (_queryCountLock)
|
|
{
|
|
return new Dictionary<string, int>(_queryExecutionCounts);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears query execution tracking data
|
|
/// </summary>
|
|
public void ClearQueryTracking()
|
|
{
|
|
lock (_queryCountLock)
|
|
{
|
|
_queryExecutionCounts.Clear();
|
|
}
|
|
_logger?.LogInformation("[SQL-TRACKING] Query execution counts cleared");
|
|
}
|
|
} |