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:
Oda
2025-08-04 23:07:06 +02:00
committed by GitHub
parent cd378587aa
commit 082ae8714b
215 changed files with 9562 additions and 14028 deletions

View File

@@ -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)
};
}
}