Add agent fees
This commit is contained in:
@@ -56,10 +56,10 @@ public class AgentGrain : Grain, IAgentGrain
|
||||
_scopeFactory = scopeFactory;
|
||||
}
|
||||
|
||||
public override Task OnActivateAsync(CancellationToken cancellationToken)
|
||||
public override async Task OnActivateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_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)
|
||||
@@ -86,6 +86,14 @@ public class AgentGrain : Grain, IAgentGrain
|
||||
|
||||
await _agentService.SaveOrUpdateAgentSummary(emptySummary);
|
||||
_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)
|
||||
@@ -162,14 +170,19 @@ public class AgentGrain : Grain, IAgentGrain
|
||||
var totalVolume = positions.Sum(p => p.Open.Price * p.Open.Quantity * p.Open.Leverage);
|
||||
var collateral = positions.Sum(p => p.Open.Price * p.Open.Quantity);
|
||||
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
|
||||
var totalWins = 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
|
||||
{
|
||||
> 0 => (totalPnL / collateral) * 100,
|
||||
> 0 => (netPnL / collateral) * 100,
|
||||
>= 0 => 0,
|
||||
_ => 0
|
||||
};
|
||||
@@ -223,7 +236,7 @@ public class AgentGrain : Grain, IAgentGrain
|
||||
{
|
||||
UserId = (int)this.GetPrimaryKeyLong(),
|
||||
AgentName = _state.State.AgentName,
|
||||
TotalPnL = totalPnL,
|
||||
TotalPnL = totalPnL, // Use net PnL without fees
|
||||
Wins = totalWins,
|
||||
Losses = totalLosses,
|
||||
TotalROI = totalROI,
|
||||
@@ -231,13 +244,14 @@ public class AgentGrain : Grain, IAgentGrain
|
||||
ActiveStrategiesCount = activeStrategiesCount,
|
||||
TotalVolume = totalVolume,
|
||||
TotalBalance = totalBalance,
|
||||
TotalFees = totalFees,
|
||||
};
|
||||
|
||||
// Save summary to database
|
||||
await _agentService.SaveOrUpdateAgentSummary(summary);
|
||||
|
||||
_logger.LogDebug("Updated agent summary from position data for user {UserId}: PnL={PnL}, Volume={Volume}, Wins={Wins}, Losses={Losses}",
|
||||
this.GetPrimaryKeyLong(), totalPnL, totalVolume, totalWins, totalLosses);
|
||||
_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(), netPnL, totalPnL, totalFees, totalVolume, totalWins, totalLosses);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,12 @@ namespace Managing.Application.Bots.Models
|
||||
/// </summary>
|
||||
[Id(4)]
|
||||
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>
|
||||
|
||||
@@ -406,10 +406,13 @@ public class TradingBotBase : ITradingBot
|
||||
|
||||
await UpdatePositionDatabase(internalPosition);
|
||||
|
||||
if (previousPositionStatus != PositionStatus.Filled && internalPosition.Status == PositionStatus.Filled)
|
||||
if (previousPositionStatus != PositionStatus.Filled &&
|
||||
internalPosition.Status == PositionStatus.Filled)
|
||||
{
|
||||
await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionOpened, internalPosition);
|
||||
}else{
|
||||
}
|
||||
else
|
||||
{
|
||||
await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionUpdated, internalPosition);
|
||||
}
|
||||
}
|
||||
@@ -750,8 +753,6 @@ public class TradingBotBase : ITradingBot
|
||||
currentPrice, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -763,10 +764,8 @@ public class TradingBotBase : ITradingBot
|
||||
|
||||
private async Task UpdatePositionDatabase(Position position)
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory, async tradingService =>
|
||||
{
|
||||
await tradingService.UpdatePositionAsync(position);
|
||||
});
|
||||
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory,
|
||||
async tradingService => { await tradingService.UpdatePositionAsync(position); });
|
||||
}
|
||||
|
||||
private async Task<Position> OpenPosition(LightSignal signal)
|
||||
@@ -1347,6 +1346,7 @@ public class TradingBotBase : ITradingBot
|
||||
// Update position in database with all trade changes
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
position.Status = PositionStatus.Finished;
|
||||
await UpdatePositionDatabase(position);
|
||||
await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionClosed, position);
|
||||
}
|
||||
@@ -1468,10 +1468,13 @@ public class TradingBotBase : ITradingBot
|
||||
{
|
||||
if (Positions[identifier].ProfitAndLoss == null)
|
||||
{
|
||||
Positions[identifier].ProfitAndLoss = new ProfitAndLoss(){
|
||||
Positions[identifier].ProfitAndLoss = new ProfitAndLoss()
|
||||
{
|
||||
Realized = realized
|
||||
};
|
||||
}else{
|
||||
}
|
||||
else
|
||||
{
|
||||
Positions[identifier].ProfitAndLoss.Realized = realized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,11 +76,16 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
_state.State.DailySnapshots.Add(initialSnapshot);
|
||||
_state.State.LastSnapshot = initialSnapshot.Date;
|
||||
_state.State.LastUpdated = initialSnapshot.Date;
|
||||
|
||||
_logger.LogInformation("Created initial empty daily snapshot for {Date}", today);
|
||||
}
|
||||
|
||||
_state.State.TotalAgents = await _agentService.GetTotalAgentCount();
|
||||
|
||||
await RefreshDataAsync();
|
||||
}
|
||||
|
||||
await base.OnActivateAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<PlatformSummaryGrainState> GetPlatformSummaryAsync()
|
||||
@@ -129,7 +134,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
// Calculate fees and PnL for all positions
|
||||
totalFees += position.CalculateTotalFees();
|
||||
totalPnL += position.ProfitAndLoss?.Realized ?? 0;
|
||||
|
||||
|
||||
// Count all positions
|
||||
totalPositionCount++;
|
||||
|
||||
@@ -142,6 +147,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
{
|
||||
_state.State.VolumeByAsset[ticker] = 0;
|
||||
}
|
||||
|
||||
_state.State.VolumeByAsset[ticker] += positionVolume;
|
||||
|
||||
// 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]++;
|
||||
|
||||
// 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]++;
|
||||
}
|
||||
|
||||
_state.State.TotalAgents = await _agentService.GetTotalAgentCount();
|
||||
_state.State.TotalPlatformVolume = totalVolume;
|
||||
_state.State.TotalPlatformFees = totalFees;
|
||||
_state.State.TotalPlatformPnL = totalPnL;
|
||||
@@ -169,7 +176,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
|
||||
_logger.LogDebug(
|
||||
"Updated position breakdown from positions: {AssetCount} assets, Long={LongPositions}, Short={ShortPositions}",
|
||||
_state.State.PositionCountByAsset.Count,
|
||||
_state.State.PositionCountByAsset.Count,
|
||||
_state.State.PositionCountByDirection.GetValueOrDefault(TradeDirection.Long, 0),
|
||||
_state.State.PositionCountByDirection.GetValueOrDefault(TradeDirection.Short, 0));
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
try
|
||||
@@ -282,20 +307,6 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
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()
|
||||
{
|
||||
var timeSinceLastUpdate = DateTime.UtcNow - _state.State.LastUpdated;
|
||||
|
||||
@@ -8,14 +8,8 @@ namespace Managing.Application.ManageBot.Commands
|
||||
/// </summary>
|
||||
public class GetAllAgentSummariesCommand : IRequest<IEnumerable<AgentSummary>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)
|
||||
/// </summary>
|
||||
public string TimeFilter { get; }
|
||||
|
||||
public GetAllAgentSummariesCommand(string timeFilter = "Total")
|
||||
public GetAllAgentSummariesCommand()
|
||||
{
|
||||
TimeFilter = timeFilter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,8 @@ namespace Managing.Application.ManageBot.Commands
|
||||
/// </summary>
|
||||
public class GetAllAgentsCommand : IRequest<Dictionary<User, List<Bot>>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)
|
||||
/// </summary>
|
||||
public string TimeFilter { get; }
|
||||
|
||||
public GetAllAgentsCommand(string timeFilter = "Total")
|
||||
public GetAllAgentsCommand()
|
||||
{
|
||||
TimeFilter = timeFilter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,31 +24,8 @@ namespace Managing.Application.ManageBot
|
||||
// Get all agent summaries from the database
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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 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;
|
||||
}
|
||||
|
||||
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
|
||||
decimal gasFeeUsd = 0;
|
||||
if (!request.IsForPaperTrading)
|
||||
{
|
||||
if (account.Exchange == TradingExchanges.Evm || account.Exchange == TradingExchanges.GmxV2)
|
||||
{
|
||||
gasFeeUsd = await exchangeService.GetFee(account);
|
||||
if (gasFeeUsd > Constants.GMX.Config.MaximumGasFeeUsd)
|
||||
var currentGasFees = await exchangeService.GetFee(account);
|
||||
if (currentGasFees > Constants.GMX.Config.MaximumGasFeeUsd)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gasFeeUsd = Constants.GMX.Config.GasFeePerTransaction;
|
||||
}
|
||||
|
||||
|
||||
var price = request.IsForPaperTrading && request.Price.HasValue
|
||||
? request.Price.Value
|
||||
@@ -94,19 +88,11 @@ namespace Managing.Application.Trading.Handlers
|
||||
position.Open = trade;
|
||||
|
||||
// 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
|
||||
var positionSizeUsd = TradingHelpers.GetVolumeForPosition(position);
|
||||
position.UiFees = TradingHelpers.CalculateOpeningUiFees(positionSizeUsd);
|
||||
|
||||
var closeDirection = request.Direction == TradeDirection.Long
|
||||
|
||||
@@ -116,6 +116,8 @@ public class UserService : IUserService
|
||||
var agentGrain = _grainFactory.GetGrain<IAgentGrain>(user.Id);
|
||||
await agentGrain.InitializeAsync(user.Id, string.Empty);
|
||||
_logger.LogInformation("AgentGrain initialized for new user {UserId}", user.Id);
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user