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

@@ -1,6 +1,7 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Bots;
using Microsoft.EntityFrameworkCore;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql;
@@ -13,7 +14,7 @@ public class PostgreSqlBotRepository : IBotRepository
_context = context;
}
public async Task InsertBotAsync(BotBackup bot)
public async Task InsertBotAsync(Bot bot)
{
bot.CreateDate = DateTime.UtcNow;
var entity = PostgreSqlMappers.Map(bot);
@@ -22,18 +23,24 @@ public class PostgreSqlBotRepository : IBotRepository
{
var userEntity = await _context.Users
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Name == bot.User.Name)
.FirstOrDefaultAsync(u => u.Id == bot.User.Id)
.ConfigureAwait(false);
entity.UserId = userEntity?.Id;
if (userEntity == null)
{
throw new InvalidOperationException($"User with id '{bot.User.Id}' not found");
}
entity.UserId = userEntity.Id;
}
await _context.BotBackups.AddAsync(entity).ConfigureAwait(false);
await _context.Bots.AddAsync(entity).ConfigureAwait(false);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task<IEnumerable<BotBackup>> GetBotsAsync()
public async Task<IEnumerable<Bot>> GetBotsAsync()
{
var entities = await _context.BotBackups
var entities = await _context.Bots
.AsNoTracking()
.Include(m => m.User)
.ToListAsync()
@@ -42,9 +49,9 @@ public class PostgreSqlBotRepository : IBotRepository
return PostgreSqlMappers.Map(entities);
}
public async Task UpdateBackupBot(BotBackup bot)
public async Task UpdateBot(Bot bot)
{
var existingEntity = await _context.BotBackups
var existingEntity = await _context.Bots
.AsTracking()
.FirstOrDefaultAsync(b => b.Identifier == bot.Identifier)
.ConfigureAwait(false);
@@ -54,18 +61,25 @@ public class PostgreSqlBotRepository : IBotRepository
throw new InvalidOperationException($"Bot backup with identifier '{bot.Identifier}' not found");
}
// Update the entity properties
existingEntity.Data = bot.SerializeData(); // Use the serialized data string
existingEntity.LastStatus = bot.LastStatus;
// 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.Roi = bot.Roi;
existingEntity.Volume = bot.Volume;
existingEntity.Fees = bot.Fees;
existingEntity.UpdatedAt = DateTime.UtcNow;
existingEntity.UserName = bot.User?.Name;
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task DeleteBotBackup(string identifier)
public async Task DeleteBot(Guid identifier)
{
var entity = await _context.BotBackups
var entity = await _context.Bots
.AsTracking()
.FirstOrDefaultAsync(b => b.Identifier == identifier)
.ConfigureAwait(false);
@@ -75,17 +89,142 @@ public class PostgreSqlBotRepository : IBotRepository
throw new InvalidOperationException($"Bot backup with identifier '{identifier}' not found");
}
_context.BotBackups.Remove(entity);
_context.Bots.Remove(entity);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task<BotBackup?> GetBotByIdentifierAsync(string identifier)
public async Task<Bot> GetBotByIdentifierAsync(Guid identifier)
{
var entity = await _context.BotBackups
var entity = await _context.Bots
.AsNoTracking()
.Include(m => m.User)
.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)
.Where(b => b.UserId == id)
.ToListAsync()
.ConfigureAwait(false);
return PostgreSqlMappers.Map(entities);
}
public async Task<IEnumerable<Bot>> GetBotsByStatusAsync(BotStatus status)
{
var entities = await _context.Bots
.AsNoTracking()
.Include(m => m.User)
.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)
.FirstOrDefaultAsync(b => b.Name == name)
.ConfigureAwait(false);
return PostgreSqlMappers.Map(entity);
}
public async Task<IEnumerable<Bot>> GetBotsByIdsAsync(IEnumerable<Guid> identifiers)
{
var entities = await _context.Bots
.AsNoTracking()
.Include(m => m.User)
.Where(b => identifiers.Contains(b.Identifier))
.ToListAsync()
.ConfigureAwait(false);
return PostgreSqlMappers.Map(entities);
}
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,
string sortBy = "CreateDate",
string sortDirection = "Desc")
{
// Build the query with filters
var query = _context.Bots
.AsNoTracking()
.Include(m => m.User)
.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}%"));
}
// Get total count before applying pagination
var totalCount = await query.CountAsync().ConfigureAwait(false);
// Apply sorting
query = sortBy.ToLower() switch
{
"name" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.Name)
: query.OrderByDescending(b => b.Name),
"ticker" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.Ticker)
: query.OrderByDescending(b => b.Ticker),
"status" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.Status)
: query.OrderByDescending(b => b.Status),
"startuptime" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.StartupTime)
: query.OrderByDescending(b => b.StartupTime),
"pnl" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.Pnl)
: query.OrderByDescending(b => b.Pnl),
"winrate" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.TradeWins / (b.TradeWins + b.TradeLosses))
: query.OrderByDescending(b => b.TradeWins / (b.TradeWins + b.TradeLosses)),
"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) // Default to 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);
}
}