Add agentbalance

This commit is contained in:
2025-08-15 19:35:01 +07:00
parent f58d1cea3b
commit cd93dede4e
11 changed files with 100 additions and 18 deletions

View File

@@ -345,7 +345,7 @@ public class DataController : ControllerBase
// Calculate PnL for each bot once and store in a list of tuples // Calculate PnL for each bot once and store in a list of tuples
var botsWithPnL = activeBots 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) .OrderByDescending(item => item.PnL)
.Take(3) .Take(3)
.ToList(); .ToList();
@@ -357,7 +357,8 @@ public class DataController : ControllerBase
.Select(item => new StrategyPerformance .Select(item => new StrategyPerformance
{ {
StrategyName = item.Bot.Name, StrategyName = item.Bot.Name,
PnL = item.PnL PnL = item.PnL,
AgentName = item.agentName,
}) })
.ToList() .ToList()
}; };
@@ -452,7 +453,8 @@ public class DataController : ControllerBase
/// <param name="strategy">The trading bot to map</param> /// <param name="strategy">The trading bot to map</param>
/// <param name="positionsByIdentifier">Pre-fetched positions grouped by initiator identifier</param> /// <param name="positionsByIdentifier">Pre-fetched positions grouped by initiator identifier</param>
/// <returns>A view model with detailed strategy information</returns> /// <returns>A view model with detailed strategy information</returns>
private UserStrategyDetailsViewModel MapStrategyToViewModel(Bot strategy, Dictionary<Guid, List<Position>> positionsByIdentifier) private UserStrategyDetailsViewModel MapStrategyToViewModel(Bot strategy,
Dictionary<Guid, List<Position>> positionsByIdentifier)
{ {
// Calculate ROI percentage based on PnL relative to account value // Calculate ROI percentage based on PnL relative to account value
decimal pnl = strategy.Pnl; decimal pnl = strategy.Pnl;
@@ -473,8 +475,8 @@ public class DataController : ControllerBase
decimal roiLast24h = strategy.Roi; decimal roiLast24h = strategy.Roi;
// Get positions for this strategy from pre-fetched data // Get positions for this strategy from pre-fetched data
var positions = positionsByIdentifier.TryGetValue(strategy.Identifier, out var strategyPositions) var positions = positionsByIdentifier.TryGetValue(strategy.Identifier, out var strategyPositions)
? strategyPositions ? strategyPositions
: new List<Position>(); : new List<Position>();
return new UserStrategyDetailsViewModel return new UserStrategyDetailsViewModel
@@ -645,6 +647,7 @@ public class DataController : ControllerBase
Losses = agentSummary.Losses, Losses = agentSummary.Losses,
ActiveStrategiesCount = agentSummary.ActiveStrategiesCount, ActiveStrategiesCount = agentSummary.ActiveStrategiesCount,
TotalVolume = agentSummary.TotalVolume, TotalVolume = agentSummary.TotalVolume,
TotalBalance = agentSummary.TotalBalance,
}; };
agentSummaryViewModels.Add(agentSummaryViewModel); agentSummaryViewModels.Add(agentSummaryViewModel);

View File

@@ -42,6 +42,11 @@ namespace Managing.Api.Models.Responses
/// Total volume traded by this agent in USD /// Total volume traded by this agent in USD
/// </summary> /// </summary>
public decimal TotalVolume { get; set; } public decimal TotalVolume { get; set; }
/// <summary>
/// Total balance including USDC and open position values (without leverage, including PnL)
/// </summary>
public decimal TotalBalance { get; set; }
} }
/// <summary> /// <summary>

View File

@@ -9,11 +9,13 @@ namespace Managing.Api.Models.Responses
/// Name of the strategy bot /// Name of the strategy bot
/// </summary> /// </summary>
public string StrategyName { get; set; } public string StrategyName { get; set; }
/// <summary> /// <summary>
/// Profit and Loss value of the strategy /// Profit and Loss value of the strategy
/// </summary> /// </summary>
public decimal PnL { get; set; } public decimal PnL { get; set; }
public string AgentName { get; set; }
} }
/// <summary> /// <summary>
@@ -25,17 +27,17 @@ namespace Managing.Api.Models.Responses
/// Name of the strategy bot /// Name of the strategy bot
/// </summary> /// </summary>
public string StrategyName { get; set; } public string StrategyName { get; set; }
/// <summary> /// <summary>
/// Return on Investment percentage of the strategy /// Return on Investment percentage of the strategy
/// </summary> /// </summary>
public decimal Roi { get; set; } public decimal Roi { get; set; }
/// <summary> /// <summary>
/// Profit and Loss value of the strategy /// Profit and Loss value of the strategy
/// </summary> /// </summary>
public decimal PnL { get; set; } public decimal PnL { get; set; }
/// <summary> /// <summary>
/// Volume traded by the strategy /// Volume traded by the strategy
/// </summary> /// </summary>
@@ -52,7 +54,7 @@ namespace Managing.Api.Models.Responses
/// </summary> /// </summary>
public List<StrategyPerformance> TopStrategies { get; set; } = new List<StrategyPerformance>(); public List<StrategyPerformance> TopStrategies { get; set; } = new List<StrategyPerformance>();
} }
/// <summary> /// <summary>
/// View model containing the top performing strategies by ROI /// View model containing the top performing strategies by ROI
/// </summary> /// </summary>
@@ -63,4 +65,4 @@ namespace Managing.Api.Models.Responses
/// </summary> /// </summary>
public List<StrategyRoiPerformance> TopStrategiesByRoi { get; set; } = new List<StrategyRoiPerformance>(); public List<StrategyRoiPerformance> TopStrategiesByRoi { get; set; } = new List<StrategyRoiPerformance>();
} }
} }

View File

@@ -9,13 +9,13 @@ public interface IAccountService
Task<Account> CreateAccount(User user, Account account); Task<Account> CreateAccount(User user, Account account);
bool DeleteAccount(User user, string name); bool DeleteAccount(User user, string name);
IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true); IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true);
Task<IEnumerable<Account>> GetAccountsByUserAsync(User user, bool hideSecrets = true); Task<IEnumerable<Account>> GetAccountsByUserAsync(User user, bool hideSecrets = true, bool getBalance = false);
Task<IEnumerable<Account>> GetAccounts(bool hideSecrets, bool getBalance); Task<IEnumerable<Account>> GetAccounts(bool hideSecrets, bool getBalance);
Task<IEnumerable<Account>> GetAccountsAsync(bool hideSecrets, bool getBalance); Task<IEnumerable<Account>> GetAccountsAsync(bool hideSecrets, bool getBalance);
Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance); Task<Account> GetAccount(string name, bool hideSecrets, bool getBalance);
public Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance); public Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance);
public Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance); public Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance);
/// <summary> /// <summary>
/// Gets an account by name directly from the repository. /// Gets an account by name directly from the repository.
/// </summary> /// </summary>
@@ -24,7 +24,7 @@ public interface IAccountService
/// <param name="getBalance">Whether to fetch the current balance</param> /// <param name="getBalance">Whether to fetch the current balance</param>
/// <returns>The found account or null if not found</returns> /// <returns>The found account or null if not found</returns>
Task<Account> GetAccountByAccountName(string accountName, bool hideSecrets = true, bool getBalance = false); Task<Account> GetAccountByAccountName(string accountName, bool hideSecrets = true, bool getBalance = false);
IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true); IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true);
Task<IEnumerable<Account>> GetAccountsBalancesByUserAsync(User user, bool hideSecrets = true); Task<IEnumerable<Account>> GetAccountsBalancesByUserAsync(User user, bool hideSecrets = true);
Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName); Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName);

View File

@@ -11,5 +11,6 @@ public interface IUserService
Task<User> UpdateTelegramChannel(User user, string telegramChannel); Task<User> UpdateTelegramChannel(User user, string telegramChannel);
Task<User> GetUserByName(string name); Task<User> GetUserByName(string name);
Task<User> GetUserByAgentName(string agentName); Task<User> GetUserByAgentName(string agentName);
Task<User> GetUserByIdAsync(int userId);
Task<IEnumerable<User>> GetAllUsersAsync(); Task<IEnumerable<User>> GetAllUsersAsync();
} }

View File

@@ -173,13 +173,14 @@ public class AccountService : IAccountService
return GetAccountsByUserAsync(user, hideSecrets).Result; return GetAccountsByUserAsync(user, hideSecrets).Result;
} }
public async Task<IEnumerable<Account>> GetAccountsByUserAsync(User user, bool hideSecrets = true) public async Task<IEnumerable<Account>> GetAccountsByUserAsync(User user, bool hideSecrets = true,
bool getBalance = false)
{ {
var cacheKey = $"user-account-{user.Name}"; var cacheKey = $"user-account-{user.Name}";
// For now, we'll get fresh data since caching async operations requires more complex logic // For now, we'll get fresh data since caching async operations requires more complex logic
// This can be optimized later with proper async caching // This can be optimized later with proper async caching
return await GetAccountsAsync(user, hideSecrets, false); return await GetAccountsAsync(user, hideSecrets, getBalance);
} }
private async Task<IEnumerable<Account>> GetAccountsAsync(User user, bool hideSecrets, bool getBalance) private async Task<IEnumerable<Account>> GetAccountsAsync(User user, bool hideSecrets, bool getBalance)

View File

@@ -14,6 +14,10 @@ public class AgentGrain : Grain, IAgentGrain, IRemindable
private readonly ILogger<AgentGrain> _logger; private readonly ILogger<AgentGrain> _logger;
private readonly IBotService _botService; private readonly IBotService _botService;
private readonly IAgentService _agentService; 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"; private const string _updateSummaryReminderName = "UpdateAgentSummary";
public AgentGrain( public AgentGrain(
@@ -21,12 +25,20 @@ public class AgentGrain : Grain, IAgentGrain, IRemindable
IPersistentState<AgentGrainState> state, IPersistentState<AgentGrainState> state,
ILogger<AgentGrain> logger, ILogger<AgentGrain> logger,
IBotService botService, IBotService botService,
IAgentService agentService) IAgentService agentService,
IExchangeService exchangeService,
IUserService userService,
IAccountService accountService,
ITradingService tradingService)
{ {
_state = state; _state = state;
_logger = logger; _logger = logger;
_botService = botService; _botService = botService;
_agentService = agentService; _agentService = agentService;
_exchangeService = exchangeService;
_userService = userService;
_accountService = accountService;
_tradingService = tradingService;
} }
public override Task OnActivateAsync(CancellationToken cancellationToken) public override Task OnActivateAsync(CancellationToken cancellationToken)
@@ -122,6 +134,43 @@ public class AgentGrain : Grain, IAgentGrain, IRemindable
runtime = bots.Max(b => b.StartupTime); 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 var summary = new AgentSummary
{ {
UserId = (int)this.GetPrimaryKeyLong(), UserId = (int)this.GetPrimaryKeyLong(),
@@ -133,6 +182,7 @@ public class AgentGrain : Grain, IAgentGrain, IRemindable
Runtime = runtime, Runtime = runtime,
ActiveStrategiesCount = bots.Count(b => b.Status == BotStatus.Running), ActiveStrategiesCount = bots.Count(b => b.Status == BotStatus.Running),
TotalVolume = totalVolume, TotalVolume = totalVolume,
TotalBalance = totalBalance,
}; };
// Save summary to database // Save summary to database

View File

@@ -251,4 +251,17 @@ public class UserService : IUserService
{ {
return await _userRepository.GetAllUsersAsync(); return await _userRepository.GetAllUsersAsync();
} }
public async Task<User> 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;
}
} }

View File

@@ -44,4 +44,7 @@ public class AgentSummary
[Id(12)] [Id(12)]
public decimal TotalVolume { get; set; } public decimal TotalVolume { get; set; }
[Id(13)]
public decimal TotalBalance { get; set; }
} }

View File

@@ -193,7 +193,8 @@ public class AgentSummaryRepository : IAgentSummaryRepository
CreatedAt = domain.CreatedAt, CreatedAt = domain.CreatedAt,
UpdatedAt = domain.UpdatedAt, UpdatedAt = domain.UpdatedAt,
ActiveStrategiesCount = domain.ActiveStrategiesCount, 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.Runtime = domain.Runtime;
entity.ActiveStrategiesCount = domain.ActiveStrategiesCount; entity.ActiveStrategiesCount = domain.ActiveStrategiesCount;
entity.TotalVolume = domain.TotalVolume; entity.TotalVolume = domain.TotalVolume;
entity.TotalBalance = domain.TotalBalance;
} }
private static AgentSummary MapToDomain(AgentSummaryEntity entity) private static AgentSummary MapToDomain(AgentSummaryEntity entity)
@@ -226,6 +228,7 @@ public class AgentSummaryRepository : IAgentSummaryRepository
UpdatedAt = entity.UpdatedAt, UpdatedAt = entity.UpdatedAt,
ActiveStrategiesCount = entity.ActiveStrategiesCount, ActiveStrategiesCount = entity.ActiveStrategiesCount,
TotalVolume = entity.TotalVolume, TotalVolume = entity.TotalVolume,
TotalBalance = entity.TotalBalance,
User = PostgreSqlMappers.Map(entity.User) User = PostgreSqlMappers.Map(entity.User)
}; };
} }

View File

@@ -14,6 +14,7 @@ public class AgentSummaryEntity
public DateTime UpdatedAt { get; set; } public DateTime UpdatedAt { get; set; }
public int ActiveStrategiesCount { get; set; } public int ActiveStrategiesCount { get; set; }
public decimal TotalVolume { get; set; } public decimal TotalVolume { get; set; }
public decimal TotalBalance { get; set; }
// Navigation property // Navigation property
public UserEntity User { get; set; } public UserEntity User { get; set; }