323 lines
11 KiB
C#
323 lines
11 KiB
C#
using Managing.Application.Abstractions.Repositories;
|
|
using Managing.Domain.Bots;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using static Managing.Common.Enums;
|
|
|
|
namespace Managing.Infrastructure.Databases.PostgreSql;
|
|
|
|
public class PostgreSqlBotRepository : IBotRepository
|
|
{
|
|
private readonly ManagingDbContext _context;
|
|
|
|
public PostgreSqlBotRepository(ManagingDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task InsertBotAsync(Bot bot)
|
|
{
|
|
bot.CreateDate = DateTime.UtcNow;
|
|
var entity = PostgreSqlMappers.Map(bot);
|
|
// Set the UserId if user is provided
|
|
if (bot.User != null)
|
|
{
|
|
var userEntity = await _context.Users
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync(u => u.Id == bot.User.Id)
|
|
.ConfigureAwait(false);
|
|
|
|
if (userEntity == null)
|
|
{
|
|
throw new InvalidOperationException($"User with id '{bot.User.Id}' not found");
|
|
}
|
|
|
|
entity.UserId = userEntity.Id;
|
|
}
|
|
|
|
await _context.Bots.AddAsync(entity).ConfigureAwait(false);
|
|
await _context.SaveChangesAsync().ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task<IEnumerable<Bot>> GetBotsAsync()
|
|
{
|
|
var entities = await _context.Bots
|
|
.AsNoTracking()
|
|
.Include(m => m.User)
|
|
.Include(m => m.MasterBotUser)
|
|
.ToListAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
return PostgreSqlMappers.Map(entities);
|
|
}
|
|
|
|
public async Task UpdateBot(Bot bot)
|
|
{
|
|
var existingEntity = await _context.Bots
|
|
.AsTracking()
|
|
.FirstOrDefaultAsync(b => b.Identifier == bot.Identifier)
|
|
.ConfigureAwait(false);
|
|
|
|
if (existingEntity == null)
|
|
{
|
|
throw new InvalidOperationException($"Bot backup with identifier '{bot.Identifier}' not found");
|
|
}
|
|
|
|
// Update the existing entity properties directly instead of creating a new one
|
|
existingEntity.Name = bot.Name;
|
|
existingEntity.Ticker = bot.Ticker;
|
|
existingEntity.Status = bot.Status;
|
|
existingEntity.StartupTime = bot.StartupTime;
|
|
existingEntity.TradeWins = bot.TradeWins;
|
|
existingEntity.TradeLosses = bot.TradeLosses;
|
|
existingEntity.Pnl = bot.Pnl;
|
|
existingEntity.NetPnL = bot.NetPnL;
|
|
existingEntity.Roi = bot.Roi;
|
|
existingEntity.Volume = bot.Volume;
|
|
existingEntity.Fees = bot.Fees;
|
|
existingEntity.UpdatedAt = DateTime.UtcNow;
|
|
existingEntity.LongPositionCount = bot.LongPositionCount;
|
|
existingEntity.ShortPositionCount = bot.ShortPositionCount;
|
|
existingEntity.LastStartTime = bot.LastStartTime;
|
|
existingEntity.LastStopTime = bot.LastStopTime;
|
|
existingEntity.AccumulatedRunTimeSeconds = bot.AccumulatedRunTimeSeconds;
|
|
existingEntity.MasterBotUserId =
|
|
bot.MasterBotUserId ?? existingEntity.MasterBotUserId;
|
|
|
|
await _context.SaveChangesAsync().ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task DeleteBot(Guid identifier)
|
|
{
|
|
var entity = await _context.Bots
|
|
.AsTracking()
|
|
.FirstOrDefaultAsync(b => b.Identifier == identifier)
|
|
.ConfigureAwait(false);
|
|
|
|
if (entity == null)
|
|
{
|
|
throw new InvalidOperationException($"Bot backup with identifier '{identifier}' not found");
|
|
}
|
|
|
|
_context.Bots.Remove(entity);
|
|
await _context.SaveChangesAsync().ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task<Bot> GetBotByIdentifierAsync(Guid identifier)
|
|
{
|
|
var entity = await _context.Bots
|
|
.AsNoTracking()
|
|
.Include(m => m.User)
|
|
.Include(m => m.MasterBotUser)
|
|
.FirstOrDefaultAsync(b => b.Identifier == identifier)
|
|
.ConfigureAwait(false);
|
|
return PostgreSqlMappers.Map(entity);
|
|
}
|
|
|
|
public async Task<IEnumerable<Bot>> GetBotsByUserIdAsync(int id)
|
|
{
|
|
var entities = await _context.Bots
|
|
.AsNoTracking()
|
|
.Include(m => m.User)
|
|
.Include(m => m.MasterBotUser)
|
|
.Where(b => b.UserId == id)
|
|
.ToListAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
// Map entities to domain objects
|
|
var bots = PostgreSqlMappers.Map(entities).ToList();
|
|
|
|
// Attach master bot users to domain objects
|
|
foreach (var entity in entities.Where(e => e.MasterBotUser != null))
|
|
{
|
|
var bot = bots.FirstOrDefault(b => b.MasterBotUserId == entity.MasterBotUserId);
|
|
if (bot != null)
|
|
{
|
|
// Convert UserEntity to User domain object
|
|
bot.MasterBotUser = PostgreSqlMappers.Map(entity.MasterBotUser);
|
|
}
|
|
}
|
|
|
|
return bots;
|
|
}
|
|
|
|
public async Task<IEnumerable<Bot>> GetBotsByStatusAsync(BotStatus status)
|
|
{
|
|
var entities = await _context.Bots
|
|
.AsNoTracking()
|
|
.Include(m => m.User)
|
|
.Include(m => m.MasterBotUser)
|
|
.Where(b => b.Status == status)
|
|
.ToListAsync()
|
|
.ConfigureAwait(false);
|
|
return PostgreSqlMappers.Map(entities);
|
|
}
|
|
|
|
public async Task<Bot> GetBotByNameAsync(string name)
|
|
{
|
|
var entity = await _context.Bots
|
|
.AsNoTracking()
|
|
.Include(m => m.User)
|
|
.Include(m => m.MasterBotUser)
|
|
.FirstOrDefaultAsync(b => b.Name == name)
|
|
.ConfigureAwait(false);
|
|
return PostgreSqlMappers.Map(entity);
|
|
}
|
|
|
|
public async Task<Bot> GetBotByUserIdAndNameAsync(int userId, string name)
|
|
{
|
|
var entity = await _context.Bots
|
|
.AsNoTracking()
|
|
.Include(m => m.User)
|
|
.Include(m => m.MasterBotUser)
|
|
.FirstOrDefaultAsync(b => b.UserId == userId && b.Name == name)
|
|
.ConfigureAwait(false);
|
|
return PostgreSqlMappers.Map(entity);
|
|
}
|
|
|
|
public async Task<IEnumerable<Bot>> GetBotsByIdsAsync(IEnumerable<Guid> identifiers)
|
|
{
|
|
try
|
|
{
|
|
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
|
|
|
var entities = await _context.Bots
|
|
.AsNoTracking()
|
|
.Include(m => m.User)
|
|
.Include(m => m.MasterBotUser)
|
|
.Where(b => identifiers.Contains(b.Identifier))
|
|
.ToListAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
return PostgreSqlMappers.Map(entities);
|
|
}
|
|
finally
|
|
{
|
|
// Always ensure the connection is closed after the operation
|
|
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
|
}
|
|
}
|
|
|
|
public async Task<(IEnumerable<Bot> Bots, int TotalCount)> GetBotsPaginatedAsync(
|
|
int pageNumber,
|
|
int pageSize,
|
|
BotStatus? status = null,
|
|
string? name = null,
|
|
string? ticker = null,
|
|
string? agentName = null,
|
|
BotSortableColumn sortBy = BotSortableColumn.CreateDate,
|
|
string sortDirection = "Desc",
|
|
bool showOnlyProfitable = false)
|
|
{
|
|
// Build the query with filters
|
|
var query = _context.Bots
|
|
.AsNoTracking()
|
|
.Include(m => m.User)
|
|
.Include(m => m.MasterBotUser)
|
|
.AsQueryable();
|
|
|
|
// Apply filters
|
|
if (status.HasValue)
|
|
{
|
|
query = query.Where(b => b.Status == status.Value);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(name))
|
|
{
|
|
query = query.Where(b => EF.Functions.ILike(b.Name, $"%{name}%"));
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(ticker))
|
|
{
|
|
query = query.Where(b => EF.Functions.ILike(b.Ticker.ToString(), $"%{ticker}%"));
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(agentName))
|
|
{
|
|
query = query.Where(b => b.User != null && EF.Functions.ILike(b.User.AgentName, $"%{agentName}%"));
|
|
}
|
|
|
|
// Apply profitable bots filtering if specified
|
|
if (showOnlyProfitable)
|
|
{
|
|
query = query.Where(b => b.Roi > 0);
|
|
}
|
|
|
|
// Get total count before applying pagination
|
|
var totalCount = await query.CountAsync().ConfigureAwait(false);
|
|
|
|
// Apply sorting
|
|
query = sortBy switch
|
|
{
|
|
BotSortableColumn.Name => sortDirection.ToLower() == "asc"
|
|
? query.OrderBy(b => b.Name)
|
|
: query.OrderByDescending(b => b.Name),
|
|
BotSortableColumn.Ticker => sortDirection.ToLower() == "asc"
|
|
? query.OrderBy(b => b.Ticker)
|
|
: query.OrderByDescending(b => b.Ticker),
|
|
BotSortableColumn.Status => sortDirection.ToLower() == "asc"
|
|
? query.OrderBy(b => b.Status)
|
|
: query.OrderByDescending(b => b.Status),
|
|
BotSortableColumn.StartupTime => sortDirection.ToLower() == "asc"
|
|
? query.OrderBy(b => b.StartupTime)
|
|
: query.OrderByDescending(b => b.StartupTime),
|
|
BotSortableColumn.Roi => sortDirection.ToLower() == "asc"
|
|
? query.OrderBy(b => b.Roi)
|
|
: query.OrderByDescending(b => b.Roi),
|
|
BotSortableColumn.Pnl => sortDirection.ToLower() == "asc"
|
|
? query.OrderBy(b => b.Pnl)
|
|
: query.OrderByDescending(b => b.Pnl),
|
|
BotSortableColumn.WinRate => sortDirection.ToLower() == "asc"
|
|
? query.OrderBy(b =>
|
|
(b.TradeWins + b.TradeLosses) > 0 ? (double)b.TradeWins / (b.TradeWins + b.TradeLosses) : 0)
|
|
: query.OrderByDescending(b =>
|
|
(b.TradeWins + b.TradeLosses) > 0 ? (double)b.TradeWins / (b.TradeWins + b.TradeLosses) : 0),
|
|
BotSortableColumn.AgentName => sortDirection.ToLower() == "asc"
|
|
? query.OrderBy(b => b.User.AgentName)
|
|
: query.OrderByDescending(b => b.User.AgentName),
|
|
_ => sortDirection.ToLower() == "asc"
|
|
? query.OrderBy(b => b.CreateDate)
|
|
: query.OrderByDescending(b => b.CreateDate)
|
|
};
|
|
|
|
// Apply pagination
|
|
var skip = (pageNumber - 1) * pageSize;
|
|
var entities = await query
|
|
.Skip(skip)
|
|
.Take(pageSize)
|
|
.ToListAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
var bots = PostgreSqlMappers.Map(entities);
|
|
return (bots, totalCount);
|
|
}
|
|
|
|
public async Task<IEnumerable<Bot>> GetTopBotsByPnLAsync(IEnumerable<BotStatus> statuses, int count = 3)
|
|
{
|
|
var entities = await _context.Bots
|
|
.AsNoTracking()
|
|
.Include(m => m.User)
|
|
.Include(m => m.MasterBotUser)
|
|
.Where(b => statuses.Contains(b.Status))
|
|
.OrderByDescending(b => b.Pnl)
|
|
.Take(count)
|
|
.ToListAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
return PostgreSqlMappers.Map(entities);
|
|
}
|
|
|
|
public async Task<IEnumerable<Bot>> GetTopBotsByRoiAsync(IEnumerable<BotStatus> statuses, int count = 3)
|
|
{
|
|
var entities = await _context.Bots
|
|
.AsNoTracking()
|
|
.Include(m => m.User)
|
|
.Include(m => m.MasterBotUser)
|
|
.Where(b => statuses.Contains(b.Status))
|
|
.OrderByDescending(b => b.Roi)
|
|
.Take(count)
|
|
.ToListAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
return PostgreSqlMappers.Map(entities);
|
|
}
|
|
} |