diff --git a/src/Managing.Application/Agents/AgentService.cs b/src/Managing.Application/Agents/AgentService.cs index f5d7a9be..948a48f3 100644 --- a/src/Managing.Application/Agents/AgentService.cs +++ b/src/Managing.Application/Agents/AgentService.cs @@ -1,9 +1,16 @@ using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; +using Managing.Application.Abstractions; using Managing.Core; using Managing.Domain.Statistics; +using Managing.Domain.Trades; +using Managing.Domain.Users; +using Managing.Domain.Accounts; +using Managing.Domain.Bots; +using Managing.Domain.Shared.Helpers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; namespace Managing.Application.Agents; @@ -76,6 +83,26 @@ public class AgentService : IAgentService var balances = await _agentBalanceRepository.GetAgentBalancesByUserId(userId, start, end); + // Check if we need to calculate fresh balance data + var needsFreshData = await ShouldCalculateFreshBalanceData(userId, balances, effectiveEnd); + + if (needsFreshData) + { + _logger.LogInformation("Calculating fresh balance data for user {UserId} - last balance is missing or older than 2 minutes", userId); + var freshBalance = await CalculateFreshBalanceData(userId); + if (freshBalance != null) + { + // Insert the fresh balance data into InfluxDB + _agentBalanceRepository.InsertAgentBalance(freshBalance); + + // Add the fresh balance to our results if it falls within the requested time range + if (freshBalance.Time >= start && freshBalance.Time <= effectiveEnd) + { + balances.Add(freshBalance); + } + } + } + // Create a single AgentBalanceHistory with all balances var result = new AgentBalanceHistory { @@ -158,4 +185,152 @@ public class AgentService : IAgentService throw; } } + + /// + /// Determines if fresh balance data should be calculated based on the last balance timestamp + /// + private Task ShouldCalculateFreshBalanceData(int userId, IList existingBalances, DateTime effectiveEnd) + { + try + { + // If no balances exist, we need fresh data + if (!existingBalances.Any()) + { + _logger.LogDebug("No existing balances found for user {UserId}, calculating fresh data", userId); + return Task.FromResult(true); + } + + // Get the most recent balance + var lastBalance = existingBalances.OrderByDescending(b => b.Time).First(); + var timeSinceLastBalance = effectiveEnd - lastBalance.Time; + + // If the last balance is older than 2 minutes, calculate fresh data + if (timeSinceLastBalance > TimeSpan.FromMinutes(2)) + { + _logger.LogDebug("Last balance for user {UserId} is {TimeAgo} old, calculating fresh data", + userId, timeSinceLastBalance); + return Task.FromResult(true); + } + + return Task.FromResult(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error checking if fresh balance data is needed for user {UserId}", userId); + // Default to calculating fresh data on error to ensure we have current information + return Task.FromResult(true); + } + } + + /// + /// Calculates fresh balance data for a user by aggregating from positions and account balances + /// + private async Task CalculateFreshBalanceData(int userId) + { + try + { + // Get all positions for this user's bots as initiator + var positions = await ServiceScopeHelpers.WithScopedService>( + _serviceScopeFactory, + async tradingService => + { + var userPositions = await tradingService.GetPositionByUserIdAsync(userId); + return userPositions.Where(p => p.IsValidForMetrics()).ToList(); + }); + + // Calculate PnL from positions + var totalPnL = positions.Sum(p => p.ProfitAndLoss?.Realized ?? 0); + var totalFees = positions.Sum(p => p.CalculateTotalFees()); + var netPnL = totalPnL - totalFees; + + // Calculate USDC wallet value and USDC in positions + decimal usdcWalletValue = 0; + decimal usdcInPositionsValue = 0; + decimal totalBalance = 0; + + try + { + // Get user and accounts + var user = await ServiceScopeHelpers.WithScopedService( + _serviceScopeFactory, + async userService => await userService.GetUserByIdAsync(userId)); + + if (user == null) + { + _logger.LogError("User {UserId} not found for balance calculation", userId); + return null; + } + + var userAccounts = await ServiceScopeHelpers.WithScopedService>( + _serviceScopeFactory, + async accountService => await accountService.GetAccountsByUserAsync(user, hideSecrets: true, true)); + + // Calculate USDC wallet value from all accounts + foreach (var account in userAccounts) + { + var balances = await ServiceScopeHelpers.WithScopedService>( + _serviceScopeFactory, + async exchangeService => await exchangeService.GetBalances(account)); + + var usdcBalance = balances.FirstOrDefault(b => b.TokenName?.ToUpper() == "USDC"); + usdcWalletValue += usdcBalance?.Amount ?? 0; + } + + // Calculate USDC in open positions + foreach (var position in positions.Where(p => p.IsOpen())) + { + var positionUsd = position.Open.Price * position.Open.Quantity; + var realized = position.ProfitAndLoss?.Realized ?? 0; + usdcInPositionsValue += positionUsd + realized; + } + + totalBalance = usdcWalletValue + usdcInPositionsValue; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error calculating wallet balances for user {UserId}", userId); + // Continue with zero values if balance calculation fails + } + + // Calculate bots allocation USD value + var activeStrategies = await ServiceScopeHelpers.WithScopedService>( + _serviceScopeFactory, + async botService => + { + var userBots = await botService.GetBotsByUser(userId); + return userBots.Where(b => b.Status == BotStatus.Running).ToList(); + }); + + var botsAllocationUsdValue = 0m; + if (activeStrategies.Any()) + { + var botIds = activeStrategies.Select(b => b.Identifier); + var botConfigs = await ServiceScopeHelpers.WithScopedService>( + _serviceScopeFactory, + async botService => await botService.GetBotConfigsByIdsAsync(botIds)); + botsAllocationUsdValue = botConfigs.Sum(config => config.BotTradingBalance); + } + + var freshBalance = new AgentBalance + { + UserId = userId, + TotalBalanceValue = totalBalance, + UsdcWalletValue = usdcWalletValue, + UsdcInPositionsValue = usdcInPositionsValue, + BotsAllocationUsdValue = botsAllocationUsdValue, + PnL = netPnL, + Time = DateTime.UtcNow + }; + + _logger.LogDebug("Calculated fresh balance data for user {UserId}: TotalBalance={TotalBalance}, UsdcWallet={UsdcWallet}, UsdcInPositions={UsdcInPositions}, BotsAllocation={BotsAllocation}, PnL={PnL}", + userId, totalBalance, usdcWalletValue, usdcInPositionsValue, botsAllocationUsdValue, netPnL); + + return freshBalance; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error calculating fresh balance data for user {UserId}", userId); + return null; + } + } } \ No newline at end of file