Trading bot grain (#33)
* Trading bot Grain * Fix a bit more of the trading bot * Advance on the tradingbot grain * Fix build * Fix db script * Fix user login * Fix a bit backtest * Fix cooldown and backtest * start fixing bot start * Fix startup * Setup local db * Fix build and update candles and scenario * Add bot registry * Add reminder * Updateing the grains * fix bootstraping * Save stats on tick * Save bot data every tick * Fix serialization * fix save bot stats * Fix get candles * use dict instead of list for position * Switch hashset to dict * Fix a bit * Fix bot launch and bot view * add migrations * Remove the tolist * Add agent grain * Save agent summary * clean * Add save bot * Update get bots * Add get bots * Fix stop/restart * fix Update config * Update scanner table on new backtest saved * Fix backtestRowDetails.tsx * Fix agentIndex * Update agentIndex * Fix more things * Update user cache * Fix * Fix account load/start/restart/run
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Infrastructure.Databases.PostgreSql;
|
||||
using Managing.Infrastructure.Databases.PostgreSql.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Infrastructure.Database.PostgreSql;
|
||||
|
||||
public class AgentSummaryRepository : IAgentSummaryRepository
|
||||
{
|
||||
private readonly ManagingDbContext _context;
|
||||
private readonly ILogger<AgentSummaryRepository> _logger;
|
||||
|
||||
public AgentSummaryRepository(ManagingDbContext context, ILogger<AgentSummaryRepository> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AgentSummary?> GetByUserIdAsync(int userId)
|
||||
{
|
||||
var entity = await _context.AgentSummaries
|
||||
.Include(a => a.User)
|
||||
.FirstOrDefaultAsync(a => a.UserId == userId);
|
||||
|
||||
return entity != null ? MapToDomain(entity) : null;
|
||||
}
|
||||
|
||||
public async Task<AgentSummary?> GetByAgentNameAsync(string agentName)
|
||||
{
|
||||
var entity = await _context.AgentSummaries
|
||||
.Include(a => a.User)
|
||||
.FirstOrDefaultAsync(a => a.AgentName == agentName);
|
||||
|
||||
return entity != null ? MapToDomain(entity) : null;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AgentSummary>> GetAllAsync()
|
||||
{
|
||||
var entities = await _context.AgentSummaries
|
||||
.Include(a => a.User)
|
||||
.ToListAsync();
|
||||
|
||||
return entities.Select(MapToDomain);
|
||||
}
|
||||
|
||||
public async Task InsertAsync(AgentSummary agentSummary)
|
||||
{
|
||||
var entity = MapToEntity(agentSummary);
|
||||
entity.CreatedAt = DateTime.UtcNow;
|
||||
entity.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _context.AgentSummaries.AddAsync(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("AgentSummary inserted for user {UserId} with agent name {AgentName}",
|
||||
agentSummary.UserId, agentSummary.AgentName);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(AgentSummary agentSummary)
|
||||
{
|
||||
var entity = await _context.AgentSummaries
|
||||
.FirstOrDefaultAsync(a => a.UserId == agentSummary.UserId);
|
||||
|
||||
if (entity != null)
|
||||
{
|
||||
MapToEntity(agentSummary, entity);
|
||||
entity.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// No need to call Update() since the entity is already being tracked
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("AgentSummary updated for user {UserId} with agent name {AgentName}",
|
||||
agentSummary.UserId, agentSummary.AgentName);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveOrUpdateAsync(AgentSummary agentSummary)
|
||||
{
|
||||
// Ensure the User entity exists and is saved
|
||||
if (agentSummary.User != null)
|
||||
{
|
||||
var existingUser = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == agentSummary.UserId);
|
||||
|
||||
if (existingUser == null)
|
||||
{
|
||||
// User doesn't exist, save it first
|
||||
var userEntity = PostgreSqlMappers.Map(agentSummary.User);
|
||||
await _context.Users.AddAsync(userEntity);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("User created for AgentSummary with ID {UserId}", agentSummary.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
var existing = await _context.AgentSummaries
|
||||
.FirstOrDefaultAsync(a => a.UserId == agentSummary.UserId);
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
await InsertAsync(agentSummary);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update existing record - modify the tracked entity directly
|
||||
MapToEntity(agentSummary, existing);
|
||||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// No need to call Update() since the entity is already being tracked
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("AgentSummary updated for user {UserId} with agent name {AgentName}",
|
||||
agentSummary.UserId, agentSummary.AgentName);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<AgentSummary> Results, int TotalCount)> GetPaginatedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
SortableFields sortBy,
|
||||
string sortOrder,
|
||||
IEnumerable<string>? agentNames = null)
|
||||
{
|
||||
// Start with base query
|
||||
var query = _context.AgentSummaries.Include(a => a.User).AsQueryable();
|
||||
|
||||
// Apply agent name filtering if specified
|
||||
if (agentNames != null && agentNames.Any())
|
||||
{
|
||||
query = query.Where(a => agentNames.Contains(a.AgentName));
|
||||
}
|
||||
|
||||
// Get total count before applying pagination
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
// Apply sorting
|
||||
var isDescending = sortOrder.ToLowerInvariant() == "desc";
|
||||
query = sortBy switch
|
||||
{
|
||||
SortableFields.TotalPnL => isDescending
|
||||
? query.OrderByDescending(a => a.TotalPnL)
|
||||
: query.OrderBy(a => a.TotalPnL),
|
||||
SortableFields.TotalROI => isDescending
|
||||
? query.OrderByDescending(a => a.TotalROI)
|
||||
: query.OrderBy(a => a.TotalROI),
|
||||
SortableFields.Wins => isDescending
|
||||
? query.OrderByDescending(a => a.Wins)
|
||||
: query.OrderBy(a => a.Wins),
|
||||
SortableFields.Losses => isDescending
|
||||
? query.OrderByDescending(a => a.Losses)
|
||||
: query.OrderBy(a => a.Losses),
|
||||
SortableFields.AgentName => isDescending
|
||||
? query.OrderByDescending(a => a.AgentName)
|
||||
: query.OrderBy(a => a.AgentName),
|
||||
SortableFields.CreatedAt => isDescending
|
||||
? query.OrderByDescending(a => a.CreatedAt)
|
||||
: query.OrderBy(a => a.CreatedAt),
|
||||
SortableFields.UpdatedAt => isDescending
|
||||
? query.OrderByDescending(a => a.UpdatedAt)
|
||||
: query.OrderBy(a => a.UpdatedAt),
|
||||
_ => isDescending
|
||||
? query.OrderByDescending(a => a.TotalPnL) // Default to TotalPnL desc
|
||||
: query.OrderBy(a => a.TotalPnL)
|
||||
};
|
||||
|
||||
// Apply pagination
|
||||
var results = await query
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
// Map to domain objects
|
||||
var domainResults = results.Select(MapToDomain);
|
||||
|
||||
return (domainResults, totalCount);
|
||||
}
|
||||
|
||||
private static AgentSummaryEntity MapToEntity(AgentSummary domain)
|
||||
{
|
||||
return new AgentSummaryEntity
|
||||
{
|
||||
Id = domain.Id,
|
||||
UserId = domain.UserId,
|
||||
AgentName = domain.AgentName,
|
||||
TotalPnL = domain.TotalPnL,
|
||||
TotalROI = domain.TotalROI,
|
||||
Wins = domain.Wins,
|
||||
Losses = domain.Losses,
|
||||
Runtime = domain.Runtime,
|
||||
CreatedAt = domain.CreatedAt,
|
||||
UpdatedAt = domain.UpdatedAt,
|
||||
ActiveStrategiesCount = domain.ActiveStrategiesCount,
|
||||
TotalVolume = domain.TotalVolume
|
||||
};
|
||||
}
|
||||
|
||||
private static void MapToEntity(AgentSummary domain, AgentSummaryEntity entity)
|
||||
{
|
||||
entity.UserId = domain.UserId;
|
||||
entity.AgentName = domain.AgentName;
|
||||
entity.TotalPnL = domain.TotalPnL;
|
||||
entity.TotalROI = domain.TotalROI;
|
||||
entity.Wins = domain.Wins;
|
||||
entity.Losses = domain.Losses;
|
||||
entity.Runtime = domain.Runtime;
|
||||
entity.ActiveStrategiesCount = domain.ActiveStrategiesCount;
|
||||
entity.TotalVolume = domain.TotalVolume;
|
||||
}
|
||||
|
||||
private static AgentSummary MapToDomain(AgentSummaryEntity entity)
|
||||
{
|
||||
return new AgentSummary
|
||||
{
|
||||
Id = entity.Id,
|
||||
UserId = entity.UserId,
|
||||
AgentName = entity.AgentName,
|
||||
TotalPnL = entity.TotalPnL,
|
||||
TotalROI = entity.TotalROI,
|
||||
Wins = entity.Wins,
|
||||
Losses = entity.Losses,
|
||||
Runtime = entity.Runtime,
|
||||
CreatedAt = entity.CreatedAt,
|
||||
UpdatedAt = entity.UpdatedAt,
|
||||
ActiveStrategiesCount = entity.ActiveStrategiesCount,
|
||||
TotalVolume = entity.TotalVolume,
|
||||
User = PostgreSqlMappers.Map(entity.User)
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user