Fix concurrent on userStrategies
This commit is contained in:
@@ -12,6 +12,7 @@ using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using Managing.Domain.Trades;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -409,11 +410,17 @@ public class DataController : ControllerBase
|
||||
// Get all strategies for the specified user
|
||||
var userStrategies = await _mediator.Send(new GetUserStrategiesCommand(agentName));
|
||||
|
||||
// Convert to detailed view model with additional information
|
||||
var tasks = userStrategies.Select(strategy => MapStrategyToViewModelAsync(strategy));
|
||||
var result = await Task.WhenAll(tasks);
|
||||
// Get all positions for all strategies in a single database call to avoid DbContext concurrency issues
|
||||
var strategyIdentifiers = userStrategies.Select(s => s.Identifier).ToList();
|
||||
var allPositions = await _tradingService.GetPositionsByInitiatorIdentifiersAsync(strategyIdentifiers);
|
||||
var positionsByIdentifier = allPositions.GroupBy(p => p.InitiatorIdentifier)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
return Ok(result.ToList());
|
||||
// Convert to detailed view model with additional information
|
||||
var result = userStrategies.Select(strategy => MapStrategyToViewModel(strategy, positionsByIdentifier))
|
||||
.ToList();
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -439,6 +446,56 @@ public class DataController : ControllerBase
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a trading bot to a strategy view model with detailed statistics using pre-fetched positions
|
||||
/// </summary>
|
||||
/// <param name="strategy">The trading bot to map</param>
|
||||
/// <param name="positionsByIdentifier">Pre-fetched positions grouped by initiator identifier</param>
|
||||
/// <returns>A view model with detailed strategy information</returns>
|
||||
private UserStrategyDetailsViewModel MapStrategyToViewModel(Bot strategy, Dictionary<Guid, List<Position>> positionsByIdentifier)
|
||||
{
|
||||
// Calculate ROI percentage based on PnL relative to account value
|
||||
decimal pnl = strategy.Pnl;
|
||||
|
||||
// If we had initial investment amount, we could calculate ROI like:
|
||||
decimal initialInvestment = 1000; // Example placeholder, ideally should come from the account
|
||||
decimal roi = pnl != 0 ? (pnl / initialInvestment) * 100 : 0;
|
||||
|
||||
// Calculate volume statistics
|
||||
decimal totalVolume = strategy.Volume;
|
||||
decimal volumeLast24h = strategy.Volume;
|
||||
|
||||
// Calculate win/loss statistics
|
||||
(int wins, int losses) = (strategy.TradeWins, strategy.TradeLosses);
|
||||
|
||||
int winRate = wins + losses > 0 ? (wins * 100) / (wins + losses) : 0;
|
||||
// Calculate ROI for last 24h
|
||||
decimal roiLast24h = strategy.Roi;
|
||||
|
||||
// Get positions for this strategy from pre-fetched data
|
||||
var positions = positionsByIdentifier.TryGetValue(strategy.Identifier, out var strategyPositions)
|
||||
? strategyPositions
|
||||
: new List<Position>();
|
||||
|
||||
return new UserStrategyDetailsViewModel
|
||||
{
|
||||
Name = strategy.Name,
|
||||
State = strategy.Status,
|
||||
PnL = pnl,
|
||||
ROIPercentage = roi,
|
||||
ROILast24H = roiLast24h,
|
||||
Runtime = strategy.StartupTime,
|
||||
WinRate = winRate,
|
||||
TotalVolumeTraded = totalVolume,
|
||||
VolumeLast24H = volumeLast24h,
|
||||
Wins = wins,
|
||||
Losses = losses,
|
||||
Positions = positions,
|
||||
Identifier = strategy.Identifier,
|
||||
WalletBalances = new Dictionary<DateTime, decimal>(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a trading bot to a strategy view model with detailed statistics
|
||||
/// </summary>
|
||||
|
||||
@@ -26,6 +26,7 @@ public interface ITradingRepository
|
||||
Task<IEnumerable<Position>> GetPositionsAsync(PositionInitiator positionInitiator);
|
||||
Task<IEnumerable<Position>> GetPositionsByStatusAsync(PositionStatus positionStatus);
|
||||
Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifierAsync(Guid initiatorIdentifier);
|
||||
Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifiersAsync(IEnumerable<Guid> initiatorIdentifiers);
|
||||
Task<IEnumerable<Position>> GetAllPositionsAsync();
|
||||
|
||||
Task UpdateScenarioAsync(Scenario scenario);
|
||||
|
||||
@@ -37,6 +37,7 @@ public interface ITradingService
|
||||
Task<IEnumerable<Position>> GetBrokerPositions(Account account);
|
||||
Task<IEnumerable<Position>> GetAllDatabasePositionsAsync();
|
||||
Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifierAsync(Guid initiatorIdentifier);
|
||||
Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifiersAsync(IEnumerable<Guid> initiatorIdentifiers);
|
||||
Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress);
|
||||
|
||||
// Synth API integration methods
|
||||
|
||||
@@ -260,6 +260,11 @@ public class TradingService : ITradingService
|
||||
return await _tradingRepository.GetPositionsByInitiatorIdentifierAsync(initiatorIdentifier);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifiersAsync(IEnumerable<Guid> initiatorIdentifiers)
|
||||
{
|
||||
return await _tradingRepository.GetPositionsByInitiatorIdentifiersAsync(initiatorIdentifiers);
|
||||
}
|
||||
|
||||
private async Task ManageTrader(TraderFollowup a, List<Ticker> tickers)
|
||||
{
|
||||
var shortAddress = a.Account.Address.Substring(0, 6);
|
||||
|
||||
@@ -268,17 +268,26 @@ public class PostgreSqlTradingRepository : ITradingRepository
|
||||
|
||||
public async Task<Position> GetPositionByIdentifierAsync(Guid identifier)
|
||||
{
|
||||
var position = await _context.Positions
|
||||
.AsNoTracking()
|
||||
.Include(p => p.User)
|
||||
.Include(p => p.OpenTrade)
|
||||
.Include(p => p.StopLossTrade)
|
||||
.Include(p => p.TakeProfit1Trade)
|
||||
.Include(p => p.TakeProfit2Trade)
|
||||
.FirstOrDefaultAsync(p => p.Identifier == identifier)
|
||||
.ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
||||
|
||||
return PostgreSqlMappers.Map(position);
|
||||
var position = await _context.Positions
|
||||
.AsNoTracking()
|
||||
.Include(p => p.User)
|
||||
.Include(p => p.OpenTrade)
|
||||
.Include(p => p.StopLossTrade)
|
||||
.Include(p => p.TakeProfit1Trade)
|
||||
.Include(p => p.TakeProfit2Trade)
|
||||
.FirstOrDefaultAsync(p => p.Identifier == identifier)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return PostgreSqlMappers.Map(position);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Position> GetPositions(PositionInitiator positionInitiator)
|
||||
@@ -413,6 +422,28 @@ public class PostgreSqlTradingRepository : ITradingRepository
|
||||
return PostgreSqlMappers.Map(positions);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifiersAsync(IEnumerable<Guid> initiatorIdentifiers)
|
||||
{
|
||||
var identifiersList = initiatorIdentifiers.ToList();
|
||||
if (!identifiersList.Any())
|
||||
{
|
||||
return Enumerable.Empty<Position>();
|
||||
}
|
||||
|
||||
var positions = await _context.Positions
|
||||
.AsNoTracking()
|
||||
.Include(p => p.User)
|
||||
.Include(p => p.OpenTrade)
|
||||
.Include(p => p.StopLossTrade)
|
||||
.Include(p => p.TakeProfit1Trade)
|
||||
.Include(p => p.TakeProfit2Trade)
|
||||
.Where(p => identifiersList.Contains(p.InitiatorIdentifier))
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return PostgreSqlMappers.Map(positions);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Position>> GetAllPositionsAsync()
|
||||
{
|
||||
var positions = await _context.Positions
|
||||
|
||||
Reference in New Issue
Block a user