From 7c13ad5f06351e930944cd239817371877aec598 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Fri, 3 Oct 2025 15:30:39 +0700 Subject: [PATCH] Update Agent balance tracking --- .../Controllers/DataController.cs | 9 +- .../Abstractions/IBotService.cs | 1 + .../Bots/Grains/AgentGrain.cs | 79 ++++++-- .../ManageBot/BotService.cs | 22 +++ .../Workers/BalanceTrackingWorker.cs | 171 ------------------ src/Managing.Bootstrap/ApiBootstrap.cs | 6 - .../AgentBalance.cs | 3 +- .../Statistics/AgentBalance.cs | 3 +- .../InfluxDb/AgentBalanceRepository.cs | 13 +- .../InfluxDb/Models/AgentBalanceDto.cs | 4 +- 10 files changed, 101 insertions(+), 210 deletions(-) delete mode 100644 src/Managing.Application/Workers/BalanceTrackingWorker.cs diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs index 2d0105e6..8ba731dc 100644 --- a/src/Managing.Api/Controllers/DataController.cs +++ b/src/Managing.Api/Controllers/DataController.cs @@ -413,10 +413,11 @@ public class DataController : ControllerBase // Convert to detailed view model with additional information using separate scopes to avoid DbContext concurrency var result = await Task.WhenAll( - userStrategies.Select(strategy => + userStrategies.Select(strategy => ServiceScopeHelpers.WithScopedService( - _serviceScopeFactory, - async tradingService => await MapStrategyToViewModelAsync(strategy, agentBalanceHistory, tradingService))) + _serviceScopeFactory, + async tradingService => + await MapStrategyToViewModelAsync(strategy, agentBalanceHistory, tradingService))) ); return Ok(result); @@ -487,7 +488,7 @@ public class DataController : ControllerBase // Convert agent balance history to wallet balances dictionary var walletBalances = agentBalanceHistory?.AgentBalances? - .ToDictionary(b => b.Time, b => b.TotalValue) ?? new Dictionary(); + .ToDictionary(b => b.Time, b => b.TotalBalanceValue) ?? new Dictionary(); return new UserStrategyDetailsViewModel { diff --git a/src/Managing.Application/Abstractions/IBotService.cs b/src/Managing.Application/Abstractions/IBotService.cs index f78e00e4..20c145ac 100644 --- a/src/Managing.Application/Abstractions/IBotService.cs +++ b/src/Managing.Application/Abstractions/IBotService.cs @@ -22,6 +22,7 @@ public interface IBotService Task OpenPositionManuallyAsync(Guid identifier, TradeDirection direction); Task ClosePositionAsync(Guid identifier, Guid positionId); Task GetBotConfig(Guid identifier); + Task> GetBotConfigsByIdsAsync(IEnumerable botIds); Task UpdateBotStatisticsAsync(Guid identifier); Task SaveBotStatisticsAsync(Bot bot); diff --git a/src/Managing.Application/Bots/Grains/AgentGrain.cs b/src/Managing.Application/Bots/Grains/AgentGrain.cs index c617216b..11bc2c8e 100644 --- a/src/Managing.Application/Bots/Grains/AgentGrain.cs +++ b/src/Managing.Application/Bots/Grains/AgentGrain.cs @@ -1,6 +1,7 @@ #nullable enable using Managing.Application.Abstractions; using Managing.Application.Abstractions.Grains; +using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Application.Bots.Models; using Managing.Application.Orleans; @@ -31,6 +32,7 @@ public class AgentGrain : Grain, IAgentGrain private readonly IUserService _userService; private readonly IAccountService _accountService; private readonly ITradingService _tradingService; + private readonly IAgentBalanceRepository _agentBalanceRepository; private readonly IServiceScopeFactory _scopeFactory; public AgentGrain( @@ -43,6 +45,7 @@ public class AgentGrain : Grain, IAgentGrain IUserService userService, IAccountService accountService, ITradingService tradingService, + IAgentBalanceRepository agentBalanceRepository, IServiceScopeFactory scopeFactory) { _state = state; @@ -53,6 +56,7 @@ public class AgentGrain : Grain, IAgentGrain _userService = userService; _accountService = accountService; _tradingService = tradingService; + _agentBalanceRepository = agentBalanceRepository; _scopeFactory = scopeFactory; } @@ -88,7 +92,7 @@ public class AgentGrain : Grain, IAgentGrain await _agentService.SaveOrUpdateAgentSummary(emptySummary); _logger.LogInformation("Agent {UserId} initialized with name {AgentName} and empty summary", userId, agentName); - // Notify platform summary about new agent activation + // Notify platform summary about new agent activation await ServiceScopeHelpers.WithScopedService(_scopeFactory, async grainFactory => { var platformGrain = grainFactory.GetGrain("platform-summary"); @@ -101,7 +105,7 @@ public class AgentGrain : Grain, IAgentGrain { _state.State.AgentName = agentName; await _state.WriteStateAsync(); - + // Use the efficient method to update only the agent name in the summary await _agentService.UpdateAgentSummaryNameAsync((int)this.GetPrimaryKeyLong(), agentName); _logger.LogInformation("Agent {UserId} updated with name {AgentName}", this.GetPrimaryKeyLong(), agentName); @@ -127,7 +131,8 @@ public class AgentGrain : Grain, IAgentGrain { try { - _logger.LogInformation("Position closed event received for user {UserId}, position: {PositionId}, PnL: {PnL}", + _logger.LogInformation( + "Position closed event received for user {UserId}, position: {PositionId}, PnL: {PnL}", this.GetPrimaryKeyLong(), evt.PositionIdentifier, evt.RealizedPnL); await UpdateSummary(); @@ -171,7 +176,7 @@ public class AgentGrain : Grain, IAgentGrain var totalVolume = positions.Sum(p => p.Open.Price * p.Open.Quantity * p.Open.Leverage); var collateral = positions.Sum(p => p.Open.Price * p.Open.Quantity); var totalFees = positions.Sum(p => p.CalculateTotalFees()); - + // Store total fees in grain state for caching _state.State.TotalFees = totalFees; @@ -220,11 +225,10 @@ public class AgentGrain : Grain, IAgentGrain totalBalance = 0; // Set to 0 if calculation fails } - var bots = await ServiceScopeHelpers.WithScopedService> (_scopeFactory, async (botService) => { - return await botService.GetBotsByUser((int)this.GetPrimaryKeyLong()); - }); + var bots = await ServiceScopeHelpers.WithScopedService>(_scopeFactory, + async (botService) => { return await botService.GetBotsByUser((int)this.GetPrimaryKeyLong()); }); - // Calculate Runtime based on the earliest position date + // Calculate Runtime based on the earliest position date DateTime? runtime = null; if (positions.Any()) { @@ -233,6 +237,18 @@ public class AgentGrain : Grain, IAgentGrain var activeStrategiesCount = bots.Count(b => b.Status == BotStatus.Running); + // Calculate bots allocation USD value from bot configurations + var botsAllocationUsdValue = 0m; + if (bots.Any()) + { + var botIds = bots.Select(b => b.Identifier); + var botConfigs = + await ServiceScopeHelpers.WithScopedService>( + _scopeFactory, + async (botService) => { return await botService.GetBotConfigsByIdsAsync(botIds); }); + botsAllocationUsdValue = botConfigs.Sum(config => config.BotTradingBalance); + } + var summary = new AgentSummary { UserId = (int)this.GetPrimaryKeyLong(), @@ -251,8 +267,12 @@ public class AgentGrain : Grain, IAgentGrain // Save summary to database await _agentService.SaveOrUpdateAgentSummary(summary); - - _logger.LogDebug("Updated agent summary from position data for user {UserId}: NetPnL={NetPnL}, TotalPnL={TotalPnL}, Fees={Fees}, Volume={Volume}, Wins={Wins}, Losses={Losses}", + + // Insert balance tracking data + InsertBalanceTrackingData(totalBalance, botsAllocationUsdValue, netPnL); + + _logger.LogDebug( + "Updated agent summary from position data for user {UserId}: NetPnL={NetPnL}, TotalPnL={TotalPnL}, Fees={Fees}, Volume={Volume}, Wins={Wins}, Losses={Losses}", this.GetPrimaryKeyLong(), netPnL, totalPnL, totalFees, totalVolume, totalWins, totalLosses); } catch (Exception ex) @@ -512,17 +532,18 @@ public class AgentGrain : Grain, IAgentGrain { var botGrain = GrainFactory.GetGrain(botEntry.Identifier); var hasOpenPositions = await botGrain.HasOpenPositionsAsync(); - + if (hasOpenPositions) { - _logger.LogInformation("Bot {BotId} has open positions, blocking autoswap for user {UserId}", + _logger.LogInformation("Bot {BotId} has open positions, blocking autoswap for user {UserId}", botEntry.Identifier, this.GetPrimaryKeyLong()); return true; } } catch (Exception ex) { - _logger.LogWarning(ex, "Error checking open positions for bot {BotId}, skipping", botEntry.Identifier); + _logger.LogWarning(ex, "Error checking open positions for bot {BotId}, skipping", + botEntry.Identifier); // Continue checking other bots even if one fails } } @@ -532,7 +553,8 @@ public class AgentGrain : Grain, IAgentGrain } catch (Exception ex) { - _logger.LogError(ex, "Error checking for open positions across all bots for user {UserId}", this.GetPrimaryKeyLong()); + _logger.LogError(ex, "Error checking for open positions across all bots for user {UserId}", + this.GetPrimaryKeyLong()); return false; // Default to false on error to avoid blocking autoswap } } @@ -600,4 +622,33 @@ public class AgentGrain : Grain, IAgentGrain return null; } } + + /// + /// Inserts balance tracking data into the AgentBalanceRepository + /// + private void InsertBalanceTrackingData(decimal totalAccountUsdValue, decimal botsAllocationUsdValue, decimal pnl) + { + try + { + var agentBalance = new AgentBalance + { + AgentName = _state.State.AgentName, + TotalBalanceValue = totalAccountUsdValue, + BotsAllocationUsdValue = botsAllocationUsdValue, + PnL = pnl, + Time = DateTime.UtcNow + }; + + _agentBalanceRepository.InsertAgentBalance(agentBalance); + + _logger.LogDebug( + "Inserted balance tracking data for agent {AgentName}: TotalBalanceValue={TotalBalanceValue}, BotsAllocationUsdValue={BotsAllocationUsdValue}, PnL={PnL}", + agentBalance.AgentName, agentBalance.TotalBalanceValue, agentBalance.BotsAllocationUsdValue, + agentBalance.PnL); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error inserting balance tracking data for agent {AgentName}", _state.State.AgentName); + } + } } \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index ea48082a..cd687d03 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -208,6 +208,28 @@ namespace Managing.Application.ManageBot return await grain.GetConfiguration(); } + public async Task> GetBotConfigsByIdsAsync(IEnumerable botIds) + { + var configs = new List(); + + foreach (var botId in botIds) + { + try + { + var grain = _grainFactory.GetGrain(botId); + var config = await grain.GetConfiguration(); + configs.Add(config); + } + catch (Exception ex) + { + _tradingBotLogger.LogWarning(ex, "Failed to get configuration for bot {BotId}", botId); + // Continue with other bots even if one fails + } + } + + return configs; + } + public async Task> GetActiveBotsNamesAsync() { var bots = await _botRepository.GetBotsByStatusAsync(BotStatus.Running); diff --git a/src/Managing.Application/Workers/BalanceTrackingWorker.cs b/src/Managing.Application/Workers/BalanceTrackingWorker.cs deleted file mode 100644 index a9e3901b..00000000 --- a/src/Managing.Application/Workers/BalanceTrackingWorker.cs +++ /dev/null @@ -1,171 +0,0 @@ -using Managing.Application.Abstractions.Repositories; -using Managing.Application.Abstractions.Services; -using Managing.Application.ManageBot.Commands; -using Managing.Domain.Bots; -using Managing.Domain.Statistics; -using MediatR; -using Microsoft.Extensions.Logging; -using static Managing.Common.Enums; - -namespace Managing.Application.Workers; - -public class BalanceTrackingWorker : BaseWorker -{ - private readonly IMediator _mediator; - private readonly IAccountService _accountService; - private readonly IAgentBalanceRepository _agentBalanceRepository; - private bool _isInitialized; - - public BalanceTrackingWorker( - ILogger logger, - IServiceProvider serviceProvider, - IMediator mediator, - IAccountService accountService, - IAgentBalanceRepository agentBalanceRepository) - : base( - WorkerType.BalanceTracking, - logger, - TimeSpan.FromHours(1), - serviceProvider) - { - _mediator = mediator; - _accountService = accountService; - _agentBalanceRepository = agentBalanceRepository; - _isInitialized = false; - } - - protected override async Task Run(CancellationToken cancellationToken) - { - if (!_isInitialized) - { - _logger.LogInformation("Waiting 5 minutes for bots to initialize before starting balance tracking..."); - await Task.Delay(TimeSpan.FromMinutes(3), cancellationToken); - _isInitialized = true; - } - - _logger.LogInformation("Starting balance tracking..."); - - // Get all active bots - var bots = await _mediator.Send(new GetBotsByStatusCommand(BotStatus.Running)); - - var botCount = bots.Count(); - if (botCount == 0) - { - _logger.LogWarning("No active bots found. Skipping balance tracking."); - return; - } - - _logger.LogInformation($"Found {botCount} active bots. Proceeding with balance tracking."); - await TrackBalances(bots); - _logger.LogInformation("Completed balance tracking"); - } - - private async Task TrackBalances(IEnumerable bots) - { - // Group bots by agent/user - var botsByAgent = bots - .Where(b => b.User != null) - .GroupBy(b => b.User.AgentName) - .ToDictionary(g => g.Key, g => g.ToList()); - - foreach (var agentEntry in botsByAgent) - { - try - { - var agentName = agentEntry.Key; - var agentBots = agentEntry.Value; - - // Check if we need to update this agent's balance - var lastBalance = (await _agentBalanceRepository.GetAgentBalances( - agentName, - DateTime.UtcNow.AddDays(-1), - DateTime.UtcNow)).OrderByDescending(b => b.Time).FirstOrDefault(); - - if (lastBalance != null && DateTime.UtcNow.Subtract(lastBalance.Time).TotalHours < 24) - { - _logger.LogInformation( - $"Skipping agent {agentName} - Last balance update was {lastBalance.Time:g} UTC"); - continue; - } - - decimal totalAgentValue = 0; - decimal totalBotAllocatedBalance = 0; - decimal totalAccountUsdValue = 0; - decimal botsAllocationUsdValue = 0; - decimal totalPnL = 0; - - _logger.LogInformation($"Processing agent: {agentName} with {agentBots.Count} bots"); - - // Calculate total allocated balance for all bots - foreach (var bot in agentBots) - { - totalBotAllocatedBalance += bot.Volume; - _logger.LogInformation( - $"Bot {bot.Name} allocated balance: {bot.Volume} USD"); - } - - // Get account balances for this agent (only once per agent) - var agent = agentBots.First().User; // Get the user object from the first bot - var accountBalances = _accountService.GetAccountsBalancesByUser(agent, true); - foreach (var accountBalance in accountBalances) - { - if (accountBalance.Balances != null) - { - var accountTotalValue = accountBalance.Balances.Sum(b => b.Value); - - // If this is the account that holds the bot balances (USDC), subtract the allocated amounts - var usdcBalance = accountBalance.Balances.FirstOrDefault(b => b.TokenName == "USDC"); - if (usdcBalance != null) - { - _logger.LogInformation( - $"Account {accountBalance.Name} USDC balance before bot allocation: {usdcBalance.Value} USD"); - usdcBalance.Value -= totalBotAllocatedBalance; - _logger.LogInformation( - $"Account {accountBalance.Name} USDC balance after bot allocation: {usdcBalance.Value} USD"); - } - - totalAccountUsdValue += accountTotalValue; - - _logger.LogInformation( - $"Account {accountBalance.Name} total value: {accountTotalValue} USD"); - - // Log individual token balances for debugging - foreach (var balance in accountBalance.Balances) - { - _logger.LogInformation( - $" - {balance.TokenName}: {balance.Amount} (Value: {balance.Value} USD)"); - } - } - } - - // Process all bots in a single iteration - foreach (var bot in agentBots) - { - totalPnL += bot.Pnl; - } - - totalAgentValue = totalAccountUsdValue + botsAllocationUsdValue; - - _logger.LogInformation( - $"Agent {agentName} total aggregated value: {totalAgentValue} USD (Account: {totalAccountUsdValue} USD, Bot Wallet: {botsAllocationUsdValue} USD)"); - - // Create and save the agent balance - var agentBalance = new AgentBalance - { - AgentName = agentName, - TotalValue = totalAgentValue, - TotalAccountUsdValue = totalAccountUsdValue, - BotsAllocationUsdValue = botsAllocationUsdValue, - PnL = totalPnL, - Time = DateTime.UtcNow - }; - - _agentBalanceRepository.InsertAgentBalance(agentBalance); - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error processing agent {agentEntry.Key}"); - } - } - } -} \ No newline at end of file diff --git a/src/Managing.Bootstrap/ApiBootstrap.cs b/src/Managing.Bootstrap/ApiBootstrap.cs index 4b61785c..65954629 100644 --- a/src/Managing.Bootstrap/ApiBootstrap.cs +++ b/src/Managing.Bootstrap/ApiBootstrap.cs @@ -443,12 +443,6 @@ public static class ApiBootstrap private static IServiceCollection AddWorkers(this IServiceCollection services, IConfiguration configuration) { - // Balance Workers - if (configuration.GetValue("WorkerBalancesTracking", false)) - { - services.AddHostedService(); - } - if (configuration.GetValue("WorkerNotifyBundleBacktest", false)) { services.AddHostedService(); diff --git a/src/Managing.Domain.Statistics/AgentBalance.cs b/src/Managing.Domain.Statistics/AgentBalance.cs index 33de6771..c1966379 100644 --- a/src/Managing.Domain.Statistics/AgentBalance.cs +++ b/src/Managing.Domain.Statistics/AgentBalance.cs @@ -1,8 +1,7 @@ public class AgentBalance { public string AgentName { get; set; } - public decimal TotalValue { get; set; } - public decimal TotalAccountUsdValue { get; set; } + public decimal TotalBalanceValue { get; set; } public decimal BotsAllocationUsdValue { get; set; } public decimal PnL { get; set; } public DateTime Time { get; set; } diff --git a/src/Managing.Domain/Statistics/AgentBalance.cs b/src/Managing.Domain/Statistics/AgentBalance.cs index 6f9a3d1a..d8d395d4 100644 --- a/src/Managing.Domain/Statistics/AgentBalance.cs +++ b/src/Managing.Domain/Statistics/AgentBalance.cs @@ -3,8 +3,7 @@ namespace Managing.Domain.Statistics; public class AgentBalance { public string AgentName { get; set; } - public decimal TotalValue { get; set; } - public decimal TotalAccountUsdValue { get; set; } + public decimal TotalBalanceValue { get; set; } public decimal BotsAllocationUsdValue { get; set; } public decimal PnL { get; set; } public DateTime Time { get; set; } diff --git a/src/Managing.Infrastructure.Database/InfluxDb/AgentBalanceRepository.cs b/src/Managing.Infrastructure.Database/InfluxDb/AgentBalanceRepository.cs index 68d155ab..cb5e4d3e 100644 --- a/src/Managing.Infrastructure.Database/InfluxDb/AgentBalanceRepository.cs +++ b/src/Managing.Infrastructure.Database/InfluxDb/AgentBalanceRepository.cs @@ -5,7 +5,7 @@ using Managing.Infrastructure.Databases.InfluxDb.Abstractions; using Managing.Infrastructure.Databases.InfluxDb.Models; using Microsoft.Extensions.Logging; -namespace Managing.Infrastructure.Databases; +namespace Managing.Infrastructure.Databases.InfluxDb; public class AgentBalanceRepository : IAgentBalanceRepository { @@ -26,8 +26,7 @@ public class AgentBalanceRepository : IAgentBalanceRepository var balanceDto = new AgentBalanceDto { AgentName = balance.AgentName, - TotalValue = balance.TotalValue, - TotalAccountUsdValue = balance.TotalAccountUsdValue, + TotalBalanceValue = balance.TotalBalanceValue, BotsAllocationUsdValue = balance.BotsAllocationUsdValue, PnL = balance.PnL, Time = balance.Time @@ -57,8 +56,7 @@ public class AgentBalanceRepository : IAgentBalanceRepository return result.Select(balance => new AgentBalance { AgentName = balance.AgentName, - TotalValue = balance.TotalValue, - TotalAccountUsdValue = balance.TotalAccountUsdValue, + TotalBalanceValue = balance.TotalBalanceValue, BotsAllocationUsdValue = balance.BotsAllocationUsdValue, PnL = balance.PnL, Time = balance.Time @@ -89,11 +87,10 @@ public class AgentBalanceRepository : IAgentBalanceRepository .Select(g => new AgentBalanceHistory { AgentName = g.Key, - AgentBalances = g.Select(b => new AgentBalance + AgentBalances = g.Select(b => new AgentBalance { AgentName = b.AgentName, - TotalValue = b.TotalValue, - TotalAccountUsdValue = b.TotalAccountUsdValue, + TotalBalanceValue = b.TotalBalanceValue, BotsAllocationUsdValue = b.BotsAllocationUsdValue, PnL = b.PnL, Time = b.Time diff --git a/src/Managing.Infrastructure.Database/InfluxDb/Models/AgentBalanceDto.cs b/src/Managing.Infrastructure.Database/InfluxDb/Models/AgentBalanceDto.cs index 718da69e..2497a37a 100644 --- a/src/Managing.Infrastructure.Database/InfluxDb/Models/AgentBalanceDto.cs +++ b/src/Managing.Infrastructure.Database/InfluxDb/Models/AgentBalanceDto.cs @@ -7,9 +7,7 @@ public class AgentBalanceDto { [Column("agent_name", IsTag = true)] public string AgentName { get; set; } - [Column("total_value")] public decimal TotalValue { get; set; } - - [Column("total_account_usd_value")] public decimal TotalAccountUsdValue { get; set; } + [Column("total_balance_value")] public decimal TotalBalanceValue { get; set; } [Column("bots_allocation_usd_value")] public decimal BotsAllocationUsdValue { get; set; }