Add agent fees
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
@@ -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)");
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
Reference in New Issue
Block a user