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