diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs
index d806dfb..92199fb 100644
--- a/src/Managing.Api/Controllers/DataController.cs
+++ b/src/Managing.Api/Controllers/DataController.cs
@@ -345,7 +345,7 @@ public class DataController : ControllerBase
// Calculate PnL for each bot once and store in a list of tuples
var botsWithPnL = activeBots
- .Select(bot => new { Bot = bot, PnL = bot.Pnl })
+ .Select(bot => new { Bot = bot, PnL = bot.Pnl, agentName = bot.User.AgentName })
.OrderByDescending(item => item.PnL)
.Take(3)
.ToList();
@@ -357,7 +357,8 @@ public class DataController : ControllerBase
.Select(item => new StrategyPerformance
{
StrategyName = item.Bot.Name,
- PnL = item.PnL
+ PnL = item.PnL,
+ AgentName = item.agentName,
})
.ToList()
};
@@ -452,7 +453,8 @@ public class DataController : ControllerBase
/// The trading bot to map
/// Pre-fetched positions grouped by initiator identifier
/// A view model with detailed strategy information
- private UserStrategyDetailsViewModel MapStrategyToViewModel(Bot strategy, Dictionary> positionsByIdentifier)
+ private UserStrategyDetailsViewModel MapStrategyToViewModel(Bot strategy,
+ Dictionary> positionsByIdentifier)
{
// Calculate ROI percentage based on PnL relative to account value
decimal pnl = strategy.Pnl;
@@ -473,8 +475,8 @@ public class DataController : ControllerBase
decimal roiLast24h = strategy.Roi;
// Get positions for this strategy from pre-fetched data
- var positions = positionsByIdentifier.TryGetValue(strategy.Identifier, out var strategyPositions)
- ? strategyPositions
+ var positions = positionsByIdentifier.TryGetValue(strategy.Identifier, out var strategyPositions)
+ ? strategyPositions
: new List();
return new UserStrategyDetailsViewModel
@@ -645,6 +647,7 @@ public class DataController : ControllerBase
Losses = agentSummary.Losses,
ActiveStrategiesCount = agentSummary.ActiveStrategiesCount,
TotalVolume = agentSummary.TotalVolume,
+ TotalBalance = agentSummary.TotalBalance,
};
agentSummaryViewModels.Add(agentSummaryViewModel);
diff --git a/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs b/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs
index 56d970b..72dba06 100644
--- a/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs
+++ b/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs
@@ -42,6 +42,11 @@ namespace Managing.Api.Models.Responses
/// Total volume traded by this agent in USD
///
public decimal TotalVolume { get; set; }
+
+ ///
+ /// Total balance including USDC and open position values (without leverage, including PnL)
+ ///
+ public decimal TotalBalance { get; set; }
}
///
diff --git a/src/Managing.Api/Models/Responses/TopStrategiesViewModel.cs b/src/Managing.Api/Models/Responses/TopStrategiesViewModel.cs
index 03789ac..4a9aaf0 100644
--- a/src/Managing.Api/Models/Responses/TopStrategiesViewModel.cs
+++ b/src/Managing.Api/Models/Responses/TopStrategiesViewModel.cs
@@ -9,11 +9,13 @@ namespace Managing.Api.Models.Responses
/// Name of the strategy bot
///
public string StrategyName { get; set; }
-
+
///
/// Profit and Loss value of the strategy
///
public decimal PnL { get; set; }
+
+ public string AgentName { get; set; }
}
///
@@ -25,17 +27,17 @@ namespace Managing.Api.Models.Responses
/// Name of the strategy bot
///
public string StrategyName { get; set; }
-
+
///
/// Return on Investment percentage of the strategy
///
public decimal Roi { get; set; }
-
+
///
/// Profit and Loss value of the strategy
///
public decimal PnL { get; set; }
-
+
///
/// Volume traded by the strategy
///
@@ -52,7 +54,7 @@ namespace Managing.Api.Models.Responses
///
public List TopStrategies { get; set; } = new List();
}
-
+
///
/// View model containing the top performing strategies by ROI
///
@@ -63,4 +65,4 @@ namespace Managing.Api.Models.Responses
///
public List TopStrategiesByRoi { get; set; } = new List();
}
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Managing.Application.Abstractions/Services/IAccountService.cs b/src/Managing.Application.Abstractions/Services/IAccountService.cs
index 4d42ebf..e18a921 100644
--- a/src/Managing.Application.Abstractions/Services/IAccountService.cs
+++ b/src/Managing.Application.Abstractions/Services/IAccountService.cs
@@ -9,13 +9,13 @@ public interface IAccountService
Task CreateAccount(User user, Account account);
bool DeleteAccount(User user, string name);
IEnumerable GetAccountsByUser(User user, bool hideSecrets = true);
- Task> GetAccountsByUserAsync(User user, bool hideSecrets = true);
+ Task> GetAccountsByUserAsync(User user, bool hideSecrets = true, bool getBalance = false);
Task> GetAccounts(bool hideSecrets, bool getBalance);
Task> GetAccountsAsync(bool hideSecrets, bool getBalance);
Task GetAccount(string name, bool hideSecrets, bool getBalance);
public Task GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
public Task GetAccountByKey(string key, bool hideSecrets, bool getBalance);
-
+
///
/// Gets an account by name directly from the repository.
///
@@ -24,7 +24,7 @@ public interface IAccountService
/// Whether to fetch the current balance
/// The found account or null if not found
Task GetAccountByAccountName(string accountName, bool hideSecrets = true, bool getBalance = false);
-
+
IEnumerable GetAccountsBalancesByUser(User user, bool hideSecrets = true);
Task> GetAccountsBalancesByUserAsync(User user, bool hideSecrets = true);
Task GetGmxClaimableSummaryAsync(User user, string accountName);
diff --git a/src/Managing.Application.Abstractions/Services/IUserService.cs b/src/Managing.Application.Abstractions/Services/IUserService.cs
index 520e0be..0a42672 100644
--- a/src/Managing.Application.Abstractions/Services/IUserService.cs
+++ b/src/Managing.Application.Abstractions/Services/IUserService.cs
@@ -11,5 +11,6 @@ public interface IUserService
Task UpdateTelegramChannel(User user, string telegramChannel);
Task GetUserByName(string name);
Task GetUserByAgentName(string agentName);
+ Task GetUserByIdAsync(int userId);
Task> GetAllUsersAsync();
}
\ No newline at end of file
diff --git a/src/Managing.Application/Accounts/AccountService.cs b/src/Managing.Application/Accounts/AccountService.cs
index 55b01a5..1afa7b3 100644
--- a/src/Managing.Application/Accounts/AccountService.cs
+++ b/src/Managing.Application/Accounts/AccountService.cs
@@ -173,13 +173,14 @@ public class AccountService : IAccountService
return GetAccountsByUserAsync(user, hideSecrets).Result;
}
- public async Task> GetAccountsByUserAsync(User user, bool hideSecrets = true)
+ public async Task> GetAccountsByUserAsync(User user, bool hideSecrets = true,
+ bool getBalance = false)
{
var cacheKey = $"user-account-{user.Name}";
// For now, we'll get fresh data since caching async operations requires more complex logic
// This can be optimized later with proper async caching
- return await GetAccountsAsync(user, hideSecrets, false);
+ return await GetAccountsAsync(user, hideSecrets, getBalance);
}
private async Task> GetAccountsAsync(User user, bool hideSecrets, bool getBalance)
diff --git a/src/Managing.Application/Bots/Grains/AgentGrain.cs b/src/Managing.Application/Bots/Grains/AgentGrain.cs
index 30f9847..f90f069 100644
--- a/src/Managing.Application/Bots/Grains/AgentGrain.cs
+++ b/src/Managing.Application/Bots/Grains/AgentGrain.cs
@@ -14,6 +14,10 @@ public class AgentGrain : Grain, IAgentGrain, IRemindable
private readonly ILogger _logger;
private readonly IBotService _botService;
private readonly IAgentService _agentService;
+ private readonly IExchangeService _exchangeService;
+ private readonly IUserService _userService;
+ private readonly IAccountService _accountService;
+ private readonly ITradingService _tradingService;
private const string _updateSummaryReminderName = "UpdateAgentSummary";
public AgentGrain(
@@ -21,12 +25,20 @@ public class AgentGrain : Grain, IAgentGrain, IRemindable
IPersistentState state,
ILogger logger,
IBotService botService,
- IAgentService agentService)
+ IAgentService agentService,
+ IExchangeService exchangeService,
+ IUserService userService,
+ IAccountService accountService,
+ ITradingService tradingService)
{
_state = state;
_logger = logger;
_botService = botService;
_agentService = agentService;
+ _exchangeService = exchangeService;
+ _userService = userService;
+ _accountService = accountService;
+ _tradingService = tradingService;
}
public override Task OnActivateAsync(CancellationToken cancellationToken)
@@ -122,6 +134,43 @@ public class AgentGrain : Grain, IAgentGrain, IRemindable
runtime = bots.Max(b => b.StartupTime);
}
+ // Calculate total balance (USDC + open positions value)
+ decimal totalBalance = 0;
+ try
+ {
+ var userId = (int)this.GetPrimaryKeyLong();
+ var user = await _userService.GetUserByIdAsync(userId);
+
+ if (user != null)
+ {
+ var userAccounts = await _accountService.GetAccountsByUserAsync(user, hideSecrets: true, true);
+
+ foreach (var account in userAccounts)
+ {
+ // Get USDC balance
+ var usdcBalances = await _exchangeService.GetBalances(account);
+ var usdcBalance = usdcBalances.FirstOrDefault(b => b.TokenName?.ToUpper() == "USDC")?.Amount ??
+ 0;
+ totalBalance += usdcBalance;
+ }
+
+ // Get positions for all bots using their GUIDs as InitiatorIdentifier
+ var botPositions =
+ await _tradingService.GetPositionsByInitiatorIdentifiersAsync(_state.State.BotIds);
+
+ foreach (var position in botPositions.Where(p => !p.IsFinished()))
+ {
+ totalBalance += position.Open.Price * position.Open.Quantity;
+ totalBalance += position.ProfitAndLoss?.Realized ?? 0;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error calculating total balance for agent {UserId}", this.GetPrimaryKeyLong());
+ totalBalance = 0; // Set to 0 if calculation fails
+ }
+
var summary = new AgentSummary
{
UserId = (int)this.GetPrimaryKeyLong(),
@@ -133,6 +182,7 @@ public class AgentGrain : Grain, IAgentGrain, IRemindable
Runtime = runtime,
ActiveStrategiesCount = bots.Count(b => b.Status == BotStatus.Running),
TotalVolume = totalVolume,
+ TotalBalance = totalBalance,
};
// Save summary to database
diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs
index 707e5d1..3e7516e 100644
--- a/src/Managing.Application/Users/UserService.cs
+++ b/src/Managing.Application/Users/UserService.cs
@@ -251,4 +251,17 @@ public class UserService : IUserService
{
return await _userRepository.GetAllUsersAsync();
}
+
+ public async Task GetUserByIdAsync(int userId)
+ {
+ var allUsers = await _userRepository.GetAllUsersAsync();
+ var user = allUsers.FirstOrDefault(u => u.Id == userId);
+
+ if (user == null)
+ {
+ throw new Exception($"User with ID {userId} not found");
+ }
+
+ return user;
+ }
}
\ No newline at end of file
diff --git a/src/Managing.Domain/Statistics/AgentSummary.cs b/src/Managing.Domain/Statistics/AgentSummary.cs
index b56f794..615987a 100644
--- a/src/Managing.Domain/Statistics/AgentSummary.cs
+++ b/src/Managing.Domain/Statistics/AgentSummary.cs
@@ -44,4 +44,7 @@ public class AgentSummary
[Id(12)]
public decimal TotalVolume { get; set; }
+
+ [Id(13)]
+ public decimal TotalBalance { get; set; }
}
\ No newline at end of file
diff --git a/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs
index 02ef88e..e87aa00 100644
--- a/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs
+++ b/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs
@@ -193,7 +193,8 @@ public class AgentSummaryRepository : IAgentSummaryRepository
CreatedAt = domain.CreatedAt,
UpdatedAt = domain.UpdatedAt,
ActiveStrategiesCount = domain.ActiveStrategiesCount,
- TotalVolume = domain.TotalVolume
+ TotalVolume = domain.TotalVolume,
+ TotalBalance = domain.TotalBalance
};
}
@@ -208,6 +209,7 @@ public class AgentSummaryRepository : IAgentSummaryRepository
entity.Runtime = domain.Runtime;
entity.ActiveStrategiesCount = domain.ActiveStrategiesCount;
entity.TotalVolume = domain.TotalVolume;
+ entity.TotalBalance = domain.TotalBalance;
}
private static AgentSummary MapToDomain(AgentSummaryEntity entity)
@@ -226,6 +228,7 @@ public class AgentSummaryRepository : IAgentSummaryRepository
UpdatedAt = entity.UpdatedAt,
ActiveStrategiesCount = entity.ActiveStrategiesCount,
TotalVolume = entity.TotalVolume,
+ TotalBalance = entity.TotalBalance,
User = PostgreSqlMappers.Map(entity.User)
};
}
diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/AgentSummaryEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/AgentSummaryEntity.cs
index af9a258..67681af 100644
--- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/AgentSummaryEntity.cs
+++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/AgentSummaryEntity.cs
@@ -14,6 +14,7 @@ public class AgentSummaryEntity
public DateTime UpdatedAt { get; set; }
public int ActiveStrategiesCount { get; set; }
public decimal TotalVolume { get; set; }
+ public decimal TotalBalance { get; set; }
// Navigation property
public UserEntity User { get; set; }