Add agent fees

This commit is contained in:
2025-09-28 20:57:42 +07:00
parent fd2387932e
commit 16a56bd26c
20 changed files with 108 additions and 166 deletions

View File

@@ -391,7 +391,7 @@ public class DataController : ControllerBase
try try
{ {
// Get all agent summaries // Get all agent summaries
var allAgentSummaries = await _mediator.Send(new GetAllAgentSummariesCommand("Total")); var allAgentSummaries = await _mediator.Send(new GetAllAgentSummariesCommand());
// Filter agents with valid PnL data and order by PnL // Filter agents with valid PnL data and order by PnL
var agentsWithPnL = allAgentSummaries var agentsWithPnL = allAgentSummaries
@@ -638,6 +638,7 @@ public class DataController : ControllerBase
ActiveStrategiesCount = agentSummary.ActiveStrategiesCount, ActiveStrategiesCount = agentSummary.ActiveStrategiesCount,
TotalVolume = agentSummary.TotalVolume, TotalVolume = agentSummary.TotalVolume,
TotalBalance = agentSummary.TotalBalance, TotalBalance = agentSummary.TotalBalance,
TotalFees = agentSummary.TotalFees,
}; };
agentSummaryViewModels.Add(agentSummaryViewModel); agentSummaryViewModels.Add(agentSummaryViewModel);

View File

@@ -47,6 +47,11 @@ namespace Managing.Api.Models.Responses
/// Total balance including USDC and open position values (without leverage, including PnL) /// Total balance including USDC and open position values (without leverage, including PnL)
/// </summary> /// </summary>
public decimal TotalBalance { get; set; } public decimal TotalBalance { get; set; }
/// <summary>
/// Total fees paid by this agent across all positions
/// </summary>
public decimal TotalFees { get; set; }
} }
/// <summary> /// <summary>

View File

@@ -26,6 +26,12 @@ public interface IPlatformSummaryGrain : IGrainWithStringKey
[OneWay] [OneWay]
Task UpdateActiveStrategyCountAsync(int newActiveCount); Task UpdateActiveStrategyCountAsync(int newActiveCount);
/// <summary>
/// Increments the total agent count when a new agent is activated
/// </summary>
[OneWay]
Task IncrementAgentCountAsync();
[OneWay] [OneWay]
Task OnPositionClosedAsync(PositionClosedEvent evt); Task OnPositionClosedAsync(PositionClosedEvent evt);

View File

@@ -1,14 +1,13 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Models;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Bots.Grains; using Managing.Application.Bots.Grains;
using Managing.Application.Bots.Models; using Managing.Application.Bots.Models;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Moq; using Moq;
using Xunit; using Xunit;
using static Managing.Common.Enums;
namespace Managing.Application.Tests; namespace Managing.Application.Tests;
@@ -22,6 +21,7 @@ public class AgentGrainTests
private readonly Mock<IUserService> _mockUserService; private readonly Mock<IUserService> _mockUserService;
private readonly Mock<IAccountService> _mockAccountService; private readonly Mock<IAccountService> _mockAccountService;
private readonly Mock<ITradingService> _mockTradingService; private readonly Mock<ITradingService> _mockTradingService;
private readonly Mock<IServiceScopeFactory> _mockScopeFactory;
public AgentGrainTests() public AgentGrainTests()
{ {
@@ -33,6 +33,7 @@ public class AgentGrainTests
_mockUserService = new Mock<IUserService>(); _mockUserService = new Mock<IUserService>();
_mockAccountService = new Mock<IAccountService>(); _mockAccountService = new Mock<IAccountService>();
_mockTradingService = new Mock<ITradingService>(); _mockTradingService = new Mock<ITradingService>();
_mockScopeFactory = new Mock<IServiceScopeFactory>();
// Setup default state // Setup default state
_mockState.Setup(x => x.State).Returns(new AgentGrainState _mockState.Setup(x => x.State).Returns(new AgentGrainState
@@ -42,51 +43,6 @@ public class AgentGrainTests
}); });
} }
[Fact]
public async Task OnAgentSummaryUpdateAsync_WithValidBotId_ShouldCallUpdateSummary()
{
// Arrange
var agentGrain = CreateAgentGrain();
var botId = _mockState.Object.State.BotIds.First();
var updateEvent = new AgentSummaryUpdateEvent
{
BotId = botId,
EventType = NotificationEventType.PositionOpened,
Timestamp = DateTime.UtcNow
};
// Setup mocks
_mockBotService.Setup(x => x.GetBotsByIdsAsync(It.IsAny<HashSet<Guid>>()))
.ReturnsAsync(new List<Bot>());
_mockAgentService.Setup(x => x.SaveOrUpdateAgentSummary(It.IsAny<AgentSummary>()))
.Returns(Task.CompletedTask);
// Act
await agentGrain.OnAgentSummaryUpdateAsync(updateEvent);
// Assert
_mockAgentService.Verify(x => x.SaveOrUpdateAgentSummary(It.IsAny<AgentSummary>()), Times.Once);
}
[Fact]
public async Task OnAgentSummaryUpdateAsync_WithInvalidBotId_ShouldNotCallUpdateSummary()
{
// Arrange
var agentGrain = CreateAgentGrain();
var updateEvent = new AgentSummaryUpdateEvent
{
BotId = Guid.NewGuid(), // Different bot ID
EventType = NotificationEventType.PositionOpened,
Timestamp = DateTime.UtcNow
};
// Act
await agentGrain.OnAgentSummaryUpdateAsync(updateEvent);
// Assert
_mockAgentService.Verify(x => x.SaveOrUpdateAgentSummary(It.IsAny<AgentSummary>()), Times.Never);
}
[Fact] [Fact]
public async Task RegisterBotAsync_ShouldUpdateSummary() public async Task RegisterBotAsync_ShouldUpdateSummary()
{ {
@@ -137,6 +93,7 @@ public class AgentGrainTests
_mockExchangeService.Object, _mockExchangeService.Object,
_mockUserService.Object, _mockUserService.Object,
_mockAccountService.Object, _mockAccountService.Object,
_mockTradingService.Object); _mockTradingService.Object,
_mockScopeFactory.Object);
} }
} }

View File

@@ -56,10 +56,10 @@ public class AgentGrain : Grain, IAgentGrain
_scopeFactory = scopeFactory; _scopeFactory = scopeFactory;
} }
public override Task OnActivateAsync(CancellationToken cancellationToken) public override async Task OnActivateAsync(CancellationToken cancellationToken)
{ {
_logger.LogInformation("AgentGrain activated for user {UserId}", this.GetPrimaryKeyLong()); _logger.LogInformation("AgentGrain activated for user {UserId}", this.GetPrimaryKeyLong());
return base.OnActivateAsync(cancellationToken); await base.OnActivateAsync(cancellationToken);
} }
public async Task InitializeAsync(int userId, string agentName) public async Task InitializeAsync(int userId, string agentName)
@@ -86,6 +86,14 @@ public class AgentGrain : Grain, IAgentGrain
await _agentService.SaveOrUpdateAgentSummary(emptySummary); await _agentService.SaveOrUpdateAgentSummary(emptySummary);
_logger.LogInformation("Agent {UserId} initialized with name {AgentName} and empty summary", userId, agentName); _logger.LogInformation("Agent {UserId} initialized with name {AgentName} and empty summary", userId, agentName);
// Notify platform summary about new agent activation
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
{
var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
await platformGrain.IncrementAgentCountAsync();
_logger.LogDebug("Notified platform summary about new agent activation for user {UserId}", userId);
});
} }
public async Task UpdateAgentNameAsync(string agentName) public async Task UpdateAgentNameAsync(string agentName)
@@ -163,13 +171,18 @@ public class AgentGrain : Grain, IAgentGrain
var collateral = positions.Sum(p => p.Open.Price * p.Open.Quantity); var collateral = positions.Sum(p => p.Open.Price * p.Open.Quantity);
var totalFees = positions.Sum(p => p.CalculateTotalFees()); var totalFees = positions.Sum(p => p.CalculateTotalFees());
// Store total fees in grain state for caching
_state.State.TotalFees = totalFees;
// Calculate wins/losses from position PnL // Calculate wins/losses from position PnL
var totalWins = positions.Count(p => (p.ProfitAndLoss?.Realized ?? 0) > 0); var totalWins = positions.Count(p => (p.ProfitAndLoss?.Realized ?? 0) > 0);
var totalLosses = positions.Count(p => (p.ProfitAndLoss?.Realized ?? 0) <= 0); var totalLosses = positions.Count(p => (p.ProfitAndLoss?.Realized ?? 0) <= 0);
// Calculate ROI based on PnL minus fees
var netPnL = totalPnL - totalFees;
var totalROI = collateral switch var totalROI = collateral switch
{ {
> 0 => (totalPnL / collateral) * 100, > 0 => (netPnL / collateral) * 100,
>= 0 => 0, >= 0 => 0,
_ => 0 _ => 0
}; };
@@ -223,7 +236,7 @@ public class AgentGrain : Grain, IAgentGrain
{ {
UserId = (int)this.GetPrimaryKeyLong(), UserId = (int)this.GetPrimaryKeyLong(),
AgentName = _state.State.AgentName, AgentName = _state.State.AgentName,
TotalPnL = totalPnL, TotalPnL = totalPnL, // Use net PnL without fees
Wins = totalWins, Wins = totalWins,
Losses = totalLosses, Losses = totalLosses,
TotalROI = totalROI, TotalROI = totalROI,
@@ -231,13 +244,14 @@ public class AgentGrain : Grain, IAgentGrain
ActiveStrategiesCount = activeStrategiesCount, ActiveStrategiesCount = activeStrategiesCount,
TotalVolume = totalVolume, TotalVolume = totalVolume,
TotalBalance = totalBalance, TotalBalance = totalBalance,
TotalFees = totalFees,
}; };
// Save summary to database // Save summary to database
await _agentService.SaveOrUpdateAgentSummary(summary); await _agentService.SaveOrUpdateAgentSummary(summary);
_logger.LogDebug("Updated agent summary from position data for user {UserId}: PnL={PnL}, Volume={Volume}, Wins={Wins}, Losses={Losses}", _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(), totalPnL, totalVolume, totalWins, totalLosses); this.GetPrimaryKeyLong(), netPnL, totalPnL, totalFees, totalVolume, totalWins, totalLosses);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -24,6 +24,12 @@ namespace Managing.Application.Bots.Models
/// </summary> /// </summary>
[Id(4)] [Id(4)]
public CachedBalanceData? CachedBalanceData { get; set; } = null; public CachedBalanceData? CachedBalanceData { get; set; } = null;
/// <summary>
/// Total fees paid by this agent across all positions
/// </summary>
[Id(5)]
public decimal TotalFees { get; set; } = 0;
} }
/// <summary> /// <summary>

View File

@@ -406,10 +406,13 @@ public class TradingBotBase : ITradingBot
await UpdatePositionDatabase(internalPosition); await UpdatePositionDatabase(internalPosition);
if (previousPositionStatus != PositionStatus.Filled && internalPosition.Status == PositionStatus.Filled) if (previousPositionStatus != PositionStatus.Filled &&
internalPosition.Status == PositionStatus.Filled)
{ {
await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionOpened, internalPosition); await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionOpened, internalPosition);
}else{ }
else
{
await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionUpdated, internalPosition); await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionUpdated, internalPosition);
} }
} }
@@ -750,8 +753,6 @@ public class TradingBotBase : ITradingBot
currentPrice, true); currentPrice, true);
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -763,10 +764,8 @@ public class TradingBotBase : ITradingBot
private async Task UpdatePositionDatabase(Position position) private async Task UpdatePositionDatabase(Position position)
{ {
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory, async tradingService => await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory,
{ async tradingService => { await tradingService.UpdatePositionAsync(position); });
await tradingService.UpdatePositionAsync(position);
});
} }
private async Task<Position> OpenPosition(LightSignal signal) private async Task<Position> OpenPosition(LightSignal signal)
@@ -1347,6 +1346,7 @@ public class TradingBotBase : ITradingBot
// Update position in database with all trade changes // Update position in database with all trade changes
if (!Config.IsForBacktest) if (!Config.IsForBacktest)
{ {
position.Status = PositionStatus.Finished;
await UpdatePositionDatabase(position); await UpdatePositionDatabase(position);
await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionClosed, position); await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionClosed, position);
} }
@@ -1468,10 +1468,13 @@ public class TradingBotBase : ITradingBot
{ {
if (Positions[identifier].ProfitAndLoss == null) if (Positions[identifier].ProfitAndLoss == null)
{ {
Positions[identifier].ProfitAndLoss = new ProfitAndLoss(){ Positions[identifier].ProfitAndLoss = new ProfitAndLoss()
{
Realized = realized Realized = realized
}; };
}else{ }
else
{
Positions[identifier].ProfitAndLoss.Realized = realized; Positions[identifier].ProfitAndLoss.Realized = realized;
} }
} }

View File

@@ -76,11 +76,16 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
_state.State.DailySnapshots.Add(initialSnapshot); _state.State.DailySnapshots.Add(initialSnapshot);
_state.State.LastSnapshot = initialSnapshot.Date; _state.State.LastSnapshot = initialSnapshot.Date;
_state.State.LastUpdated = initialSnapshot.Date; _state.State.LastUpdated = initialSnapshot.Date;
_logger.LogInformation("Created initial empty daily snapshot for {Date}", today); _logger.LogInformation("Created initial empty daily snapshot for {Date}", today);
} }
_state.State.TotalAgents = await _agentService.GetTotalAgentCount();
await RefreshDataAsync(); await RefreshDataAsync();
} }
await base.OnActivateAsync(cancellationToken);
} }
public async Task<PlatformSummaryGrainState> GetPlatformSummaryAsync() public async Task<PlatformSummaryGrainState> GetPlatformSummaryAsync()
@@ -142,6 +147,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
{ {
_state.State.VolumeByAsset[ticker] = 0; _state.State.VolumeByAsset[ticker] = 0;
} }
_state.State.VolumeByAsset[ticker] += positionVolume; _state.State.VolumeByAsset[ticker] += positionVolume;
// Position count breakdown by asset - update state directly // Position count breakdown by asset - update state directly
@@ -149,6 +155,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
{ {
_state.State.PositionCountByAsset[ticker] = 0; _state.State.PositionCountByAsset[ticker] = 0;
} }
_state.State.PositionCountByAsset[ticker]++; _state.State.PositionCountByAsset[ticker]++;
// Position count breakdown by direction - update state directly // Position count breakdown by direction - update state directly
@@ -156,10 +163,10 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
{ {
_state.State.PositionCountByDirection[direction] = 0; _state.State.PositionCountByDirection[direction] = 0;
} }
_state.State.PositionCountByDirection[direction]++; _state.State.PositionCountByDirection[direction]++;
} }
_state.State.TotalAgents = await _agentService.GetTotalAgentCount();
_state.State.TotalPlatformVolume = totalVolume; _state.State.TotalPlatformVolume = totalVolume;
_state.State.TotalPlatformFees = totalFees; _state.State.TotalPlatformFees = totalFees;
_state.State.TotalPlatformPnL = totalPnL; _state.State.TotalPlatformPnL = totalPnL;
@@ -207,6 +214,24 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
} }
} }
public async Task IncrementAgentCountAsync()
{
try
{
_logger.LogInformation("Incrementing agent count from {CurrentCount} to {NewCount}",
_state.State.TotalAgents, _state.State.TotalAgents + 1);
_state.State.TotalAgents++;
await _state.WriteStateAsync();
_logger.LogInformation("Agent count incremented to: {NewCount}", _state.State.TotalAgents);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error incrementing agent count");
}
}
public async Task OnPositionClosedAsync(PositionClosedEvent evt) public async Task OnPositionClosedAsync(PositionClosedEvent evt)
{ {
try try
@@ -282,20 +307,6 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
await _state.WriteStateAsync(); await _state.WriteStateAsync();
} }
private async Task RefreshPnLFromDatabaseAsync()
{
try
{
var totalPnL = await _tradingService.GetGlobalPnLFromPositionsAsync();
_state.State.TotalPlatformPnL = totalPnL;
_logger.LogDebug("Refreshed PnL from database: {TotalPnL}", totalPnL);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error refreshing PnL from database");
}
}
private bool IsDataStale() private bool IsDataStale()
{ {
var timeSinceLastUpdate = DateTime.UtcNow - _state.State.LastUpdated; var timeSinceLastUpdate = DateTime.UtcNow - _state.State.LastUpdated;

View File

@@ -8,14 +8,8 @@ namespace Managing.Application.ManageBot.Commands
/// </summary> /// </summary>
public class GetAllAgentSummariesCommand : IRequest<IEnumerable<AgentSummary>> public class GetAllAgentSummariesCommand : IRequest<IEnumerable<AgentSummary>>
{ {
/// <summary> public GetAllAgentSummariesCommand()
/// Optional time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)
/// </summary>
public string TimeFilter { get; }
public GetAllAgentSummariesCommand(string timeFilter = "Total")
{ {
TimeFilter = timeFilter;
} }
} }
} }

View File

@@ -9,14 +9,8 @@ namespace Managing.Application.ManageBot.Commands
/// </summary> /// </summary>
public class GetAllAgentsCommand : IRequest<Dictionary<User, List<Bot>>> public class GetAllAgentsCommand : IRequest<Dictionary<User, List<Bot>>>
{ {
/// <summary> public GetAllAgentsCommand()
/// Optional time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)
/// </summary>
public string TimeFilter { get; }
public GetAllAgentsCommand(string timeFilter = "Total")
{ {
TimeFilter = timeFilter;
} }
} }
} }

View File

@@ -24,31 +24,8 @@ namespace Managing.Application.ManageBot
// Get all agent summaries from the database // Get all agent summaries from the database
var allAgentSummaries = await _agentService.GetAllAgentSummaries(); var allAgentSummaries = await _agentService.GetAllAgentSummaries();
if (request.TimeFilter != "Total")
{
var cutoffDate = GetCutoffDate(request.TimeFilter);
allAgentSummaries = allAgentSummaries.Where(a =>
a.UpdatedAt >= cutoffDate ||
(a.Runtime.HasValue && a.Runtime.Value >= cutoffDate));
}
return allAgentSummaries; return allAgentSummaries;
} }
/// <summary>
/// Gets the cutoff date based on the time filter
/// </summary>
private DateTime GetCutoffDate(string timeFilter)
{
return timeFilter switch
{
"24H" => DateTime.UtcNow.AddHours(-24),
"3D" => DateTime.UtcNow.AddDays(-3),
"1W" => DateTime.UtcNow.AddDays(-7),
"1M" => DateTime.UtcNow.AddMonths(-1),
"1Y" => DateTime.UtcNow.AddYears(-1),
_ => DateTime.MinValue // Default to include all data
};
}
} }
} }

View File

@@ -35,35 +35,11 @@ namespace Managing.Application.ManageBot
var userBots = await _botService.GetBotsByUser(user.Id); var userBots = await _botService.GetBotsByUser(user.Id);
var botList = userBots.ToList(); var botList = userBots.ToList();
// Apply time filter if specified
if (request.TimeFilter != "Total")
{
var cutoffDate = GetCutoffDate(request.TimeFilter);
botList = botList.Where(bot =>
bot.StartupTime >= cutoffDate ||
bot.CreateDate >= cutoffDate).ToList();
}
result[user] = botList; result[user] = botList;
} }
return result; return result;
} }
/// <summary>
/// Gets the cutoff date based on the time filter
/// </summary>
private DateTime GetCutoffDate(string timeFilter)
{
return timeFilter switch
{
"24H" => DateTime.UtcNow.AddHours(-24),
"3D" => DateTime.UtcNow.AddDays(-3),
"1W" => DateTime.UtcNow.AddDays(-7),
"1M" => DateTime.UtcNow.AddMonths(-1),
"1Y" => DateTime.UtcNow.AddYears(-1),
_ => DateTime.MinValue // Default to include all data
};
}
} }
} }

View File

@@ -45,25 +45,19 @@ namespace Managing.Application.Trading.Handlers
} }
// Gas fee check for EVM exchanges // Gas fee check for EVM exchanges
decimal gasFeeUsd = 0;
if (!request.IsForPaperTrading) if (!request.IsForPaperTrading)
{ {
if (account.Exchange == TradingExchanges.Evm || account.Exchange == TradingExchanges.GmxV2) if (account.Exchange == TradingExchanges.Evm || account.Exchange == TradingExchanges.GmxV2)
{ {
gasFeeUsd = await exchangeService.GetFee(account); var currentGasFees = await exchangeService.GetFee(account);
if (gasFeeUsd > Constants.GMX.Config.MaximumGasFeeUsd) if (currentGasFees > Constants.GMX.Config.MaximumGasFeeUsd)
{ {
throw new InsufficientFundsException( throw new InsufficientFundsException(
$"Gas fee too high for position opening: {gasFeeUsd:F2} USD (threshold: {Constants.GMX.Config.MaximumGasFeeUsd} USD). Position opening cancelled.", $"Gas fee too high for position opening: {currentGasFees:F2} USD (threshold: {Constants.GMX.Config.MaximumGasFeeUsd} USD). Position opening cancelled.",
InsufficientFundsType.InsufficientEth); InsufficientFundsType.InsufficientEth);
} }
} }
} }
else
{
gasFeeUsd = Constants.GMX.Config.GasFeePerTransaction;
}
var price = request.IsForPaperTrading && request.Price.HasValue var price = request.IsForPaperTrading && request.Price.HasValue
? request.Price.Value ? request.Price.Value
@@ -94,19 +88,11 @@ namespace Managing.Application.Trading.Handlers
position.Open = trade; position.Open = trade;
// Calculate and set fees for the position // Calculate and set fees for the position
var positionSizeUsd = (position.Open.Price * position.Open.Quantity) * position.Open.Leverage;
// Set gas fees (only for EVM exchanges)
if (account.Exchange == TradingExchanges.Evm || account.Exchange == TradingExchanges.GmxV2)
{
position.GasFees = gasFeeUsd;
}
else
{
position.GasFees = TradingHelpers.CalculateOpeningGasFees(); position.GasFees = TradingHelpers.CalculateOpeningGasFees();
}
// Set UI fees for opening // Set UI fees for opening
var positionSizeUsd = TradingHelpers.GetVolumeForPosition(position);
position.UiFees = TradingHelpers.CalculateOpeningUiFees(positionSizeUsd); position.UiFees = TradingHelpers.CalculateOpeningUiFees(positionSizeUsd);
var closeDirection = request.Direction == TradeDirection.Long var closeDirection = request.Direction == TradeDirection.Long

View File

@@ -116,6 +116,8 @@ public class UserService : IUserService
var agentGrain = _grainFactory.GetGrain<IAgentGrain>(user.Id); var agentGrain = _grainFactory.GetGrain<IAgentGrain>(user.Id);
await agentGrain.InitializeAsync(user.Id, string.Empty); await agentGrain.InitializeAsync(user.Id, string.Empty);
_logger.LogInformation("AgentGrain initialized for new user {UserId}", user.Id); _logger.LogInformation("AgentGrain initialized for new user {UserId}", user.Id);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -55,7 +55,7 @@ services:
- managing-network - managing-network
restart: unless-stopped restart: unless-stopped
environment: environment:
- REDIS_PASSWORD=SuperSecretPassword - REDIS_PASSWORD=
command: > command: >
sh -c " sh -c "
if [ -n \"$$REDIS_PASSWORD\" ]; then if [ -n \"$$REDIS_PASSWORD\" ]; then
@@ -65,4 +65,4 @@ services:
redis-server --appendonly yes redis-server --appendonly yes
redis-cli redis-cli
fi fi
" "SuperSecretPassword

View File

@@ -47,4 +47,7 @@ public class AgentSummary
[Id(13)] [Id(13)]
public decimal TotalBalance { get; set; } public decimal TotalBalance { get; set; }
[Id(14)]
public decimal TotalFees { get; set; }
} }

View File

@@ -99,6 +99,9 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasPrecision(18, 8) .HasPrecision(18, 8)
.HasColumnType("numeric(18,8)"); .HasColumnType("numeric(18,8)");
b.Property<decimal>("TotalFees")
.HasColumnType("numeric");
b.Property<decimal>("TotalPnL") b.Property<decimal>("TotalPnL")
.HasColumnType("decimal(18,8)"); .HasColumnType("decimal(18,8)");

View File

@@ -249,7 +249,8 @@ public class AgentSummaryRepository : IAgentSummaryRepository
UpdatedAt = domain.UpdatedAt, UpdatedAt = domain.UpdatedAt,
ActiveStrategiesCount = domain.ActiveStrategiesCount, ActiveStrategiesCount = domain.ActiveStrategiesCount,
TotalVolume = domain.TotalVolume, TotalVolume = domain.TotalVolume,
TotalBalance = domain.TotalBalance TotalBalance = domain.TotalBalance,
TotalFees = domain.TotalFees
}; };
} }
@@ -265,6 +266,7 @@ public class AgentSummaryRepository : IAgentSummaryRepository
entity.ActiveStrategiesCount = domain.ActiveStrategiesCount; entity.ActiveStrategiesCount = domain.ActiveStrategiesCount;
entity.TotalVolume = domain.TotalVolume; entity.TotalVolume = domain.TotalVolume;
entity.TotalBalance = domain.TotalBalance; entity.TotalBalance = domain.TotalBalance;
entity.TotalFees = domain.TotalFees;
} }
private static AgentSummary MapToDomain(AgentSummaryEntity entity) private static AgentSummary MapToDomain(AgentSummaryEntity entity)
@@ -284,6 +286,7 @@ public class AgentSummaryRepository : IAgentSummaryRepository
ActiveStrategiesCount = entity.ActiveStrategiesCount, ActiveStrategiesCount = entity.ActiveStrategiesCount,
TotalVolume = entity.TotalVolume, TotalVolume = entity.TotalVolume,
TotalBalance = entity.TotalBalance, TotalBalance = entity.TotalBalance,
TotalFees = entity.TotalFees,
User = PostgreSqlMappers.Map(entity.User) User = PostgreSqlMappers.Map(entity.User)
}; };
} }

View File

@@ -15,6 +15,7 @@ public class AgentSummaryEntity
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; } public decimal TotalBalance { get; set; }
public decimal TotalFees { get; set; }
// Navigation property // Navigation property
public UserEntity User { get; set; } public UserEntity User { get; set; }

View File

@@ -82,7 +82,7 @@
"prettier-plugin-tailwind-css": "^1.5.0", "prettier-plugin-tailwind-css": "^1.5.0",
"tailwindcss": "^3.0.23", "tailwindcss": "^3.0.23",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"vite": "^6.3.5", "vite": "^6.3.6",
"whatwg-fetch": "^3.6.2" "whatwg-fetch": "^3.6.2"
}, },
"msw": { "msw": {