Add strategies paginated
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using Managing.Api.Models.Requests;
|
using Managing.Api.Models.Requests;
|
||||||
using Managing.Api.Models.Responses;
|
using Managing.Api.Models.Responses;
|
||||||
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.Abstractions.Grains;
|
using Managing.Application.Abstractions.Grains;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
@@ -38,6 +39,7 @@ public class DataController : ControllerBase
|
|||||||
private readonly ITradingService _tradingService;
|
private readonly ITradingService _tradingService;
|
||||||
private readonly IGrainFactory _grainFactory;
|
private readonly IGrainFactory _grainFactory;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
private readonly IBotService _botService;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DataController"/> class.
|
/// Initializes a new instance of the <see cref="DataController"/> class.
|
||||||
@@ -52,6 +54,7 @@ public class DataController : ControllerBase
|
|||||||
/// <param name="tradingService">Service for trading operations.</param>
|
/// <param name="tradingService">Service for trading operations.</param>
|
||||||
/// <param name="grainFactory">Orleans grain factory for accessing grains.</param>
|
/// <param name="grainFactory">Orleans grain factory for accessing grains.</param>
|
||||||
/// <param name="serviceScopeFactory">Service scope factory for creating scoped services.</param>
|
/// <param name="serviceScopeFactory">Service scope factory for creating scoped services.</param>
|
||||||
|
/// <param name="botService">Service for bot operations.</param>
|
||||||
public DataController(
|
public DataController(
|
||||||
IExchangeService exchangeService,
|
IExchangeService exchangeService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
@@ -61,7 +64,8 @@ public class DataController : ControllerBase
|
|||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
ITradingService tradingService,
|
ITradingService tradingService,
|
||||||
IGrainFactory grainFactory,
|
IGrainFactory grainFactory,
|
||||||
IServiceScopeFactory serviceScopeFactory)
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
|
IBotService botService)
|
||||||
{
|
{
|
||||||
_exchangeService = exchangeService;
|
_exchangeService = exchangeService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
@@ -72,6 +76,7 @@ public class DataController : ControllerBase
|
|||||||
_tradingService = tradingService;
|
_tradingService = tradingService;
|
||||||
_grainFactory = grainFactory;
|
_grainFactory = grainFactory;
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
|
_botService = botService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -765,6 +770,86 @@ public class DataController : ControllerBase
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a paginated list of strategies (bots) excluding those with Saved status
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pageNumber">Page number (1-based, defaults to 1)</param>
|
||||||
|
/// <param name="pageSize">Number of items per page (defaults to 10, max 100)</param>
|
||||||
|
/// <param name="name">Filter by name (partial match, case-insensitive)</param>
|
||||||
|
/// <param name="ticker">Filter by ticker (partial match, case-insensitive)</param>
|
||||||
|
/// <param name="agentName">Filter by agent name (partial match, case-insensitive)</param>
|
||||||
|
/// <param name="sortBy">Sort field (defaults to CreateDate)</param>
|
||||||
|
/// <param name="sortDirection">Sort direction - "Asc" or "Desc" (defaults to "Desc")</param>
|
||||||
|
/// <returns>A paginated list of strategies excluding Saved status bots</returns>
|
||||||
|
[HttpGet("GetStrategiesPaginated")]
|
||||||
|
public async Task<ActionResult<PaginatedResponse<TradingBotResponse>>> GetStrategiesPaginated(
|
||||||
|
int pageNumber = 1,
|
||||||
|
int pageSize = 10,
|
||||||
|
string? name = null,
|
||||||
|
string? ticker = null,
|
||||||
|
string? agentName = null,
|
||||||
|
BotSortableColumn sortBy = BotSortableColumn.CreateDate,
|
||||||
|
string sortDirection = "Desc")
|
||||||
|
{
|
||||||
|
// Validate pagination parameters
|
||||||
|
if (pageNumber < 1)
|
||||||
|
{
|
||||||
|
return BadRequest("Page number must be greater than 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageSize < 1 || pageSize > 100)
|
||||||
|
{
|
||||||
|
return BadRequest("Page size must be between 1 and 100");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate sort direction
|
||||||
|
if (sortDirection != "Asc" && sortDirection != "Desc")
|
||||||
|
{
|
||||||
|
return BadRequest("Sort direction must be 'Asc' or 'Desc'");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get paginated bots excluding Saved status
|
||||||
|
var (bots, totalCount) = await _botService.GetBotsPaginatedAsync(
|
||||||
|
pageNumber,
|
||||||
|
pageSize,
|
||||||
|
null, // No specific status filter - we'll exclude Saved in the service call
|
||||||
|
name,
|
||||||
|
ticker,
|
||||||
|
agentName,
|
||||||
|
sortBy,
|
||||||
|
sortDirection);
|
||||||
|
|
||||||
|
// Filter out Saved status bots
|
||||||
|
var filteredBots = bots.Where(bot => bot.Status != BotStatus.Saved).ToList();
|
||||||
|
var filteredCount = totalCount - bots.Count(bot => bot.Status == BotStatus.Saved);
|
||||||
|
|
||||||
|
// Map to response objects
|
||||||
|
var tradingBotResponses = MapBotsToTradingBotResponse(filteredBots);
|
||||||
|
|
||||||
|
// Calculate pagination metadata
|
||||||
|
var totalPages = (int)Math.Ceiling((double)filteredCount / pageSize);
|
||||||
|
|
||||||
|
var response = new PaginatedResponse<TradingBotResponse>
|
||||||
|
{
|
||||||
|
Items = tradingBotResponses.ToList(),
|
||||||
|
TotalCount = filteredCount,
|
||||||
|
PageNumber = pageNumber,
|
||||||
|
PageSize = pageSize,
|
||||||
|
TotalPages = totalPages,
|
||||||
|
HasPreviousPage = pageNumber > 1,
|
||||||
|
HasNextPage = pageNumber < totalPages
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(response);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, $"Error retrieving strategies: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps a Position domain object to a PositionViewModel
|
/// Maps a Position domain object to a PositionViewModel
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -789,4 +874,35 @@ public class DataController : ControllerBase
|
|||||||
Identifier = position.Identifier,
|
Identifier = position.Identifier,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps a collection of Bot entities to TradingBotResponse objects.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bots">The collection of bots to map</param>
|
||||||
|
/// <returns>A list of TradingBotResponse objects</returns>
|
||||||
|
private static List<TradingBotResponse> MapBotsToTradingBotResponse(IEnumerable<Bot> bots)
|
||||||
|
{
|
||||||
|
var list = new List<TradingBotResponse>();
|
||||||
|
|
||||||
|
foreach (var item in bots)
|
||||||
|
{
|
||||||
|
list.Add(new TradingBotResponse
|
||||||
|
{
|
||||||
|
Status = item.Status.ToString(),
|
||||||
|
WinRate = (item.TradeWins + item.TradeLosses) != 0
|
||||||
|
? item.TradeWins / (item.TradeWins + item.TradeLosses)
|
||||||
|
: 0,
|
||||||
|
ProfitAndLoss = item.Pnl,
|
||||||
|
Roi = item.Roi,
|
||||||
|
Identifier = item.Identifier.ToString(),
|
||||||
|
AgentName = item.User.AgentName,
|
||||||
|
CreateDate = item.CreateDate,
|
||||||
|
StartupTime = item.StartupTime,
|
||||||
|
Name = item.Name,
|
||||||
|
Ticker = item.Ticker,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -46,11 +46,48 @@ public class AgentBalanceRepository : IAgentBalanceRepository
|
|||||||
{
|
{
|
||||||
var results = await _influxDbRepository.QueryAsync(async query =>
|
var results = await _influxDbRepository.QueryAsync(async query =>
|
||||||
{
|
{
|
||||||
|
var effectiveEnd = end ?? DateTime.UtcNow;
|
||||||
|
var timeRange = effectiveEnd - start;
|
||||||
|
|
||||||
|
// Determine sampling interval based on time range to limit data points
|
||||||
|
string samplingInterval;
|
||||||
|
if (timeRange.TotalDays <= 1)
|
||||||
|
{
|
||||||
|
// Less than 1 day: 5-minute intervals (max ~288 points)
|
||||||
|
samplingInterval = "5m";
|
||||||
|
}
|
||||||
|
else if (timeRange.TotalDays <= 7)
|
||||||
|
{
|
||||||
|
// 1-7 days: 30-minute intervals (max ~336 points)
|
||||||
|
samplingInterval = "30m";
|
||||||
|
}
|
||||||
|
else if (timeRange.TotalDays <= 30)
|
||||||
|
{
|
||||||
|
// 1-30 days: 2-hour intervals (max ~360 points)
|
||||||
|
samplingInterval = "2h";
|
||||||
|
}
|
||||||
|
else if (timeRange.TotalDays <= 90)
|
||||||
|
{
|
||||||
|
// 1-3 months: 6-hour intervals (max ~360 points)
|
||||||
|
samplingInterval = "6h";
|
||||||
|
}
|
||||||
|
else if (timeRange.TotalDays <= 365)
|
||||||
|
{
|
||||||
|
// 3-12 months: 1-day intervals (max ~365 points)
|
||||||
|
samplingInterval = "1d";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// More than 1 year: 7-day intervals (max ~52 points)
|
||||||
|
samplingInterval = "7d";
|
||||||
|
}
|
||||||
|
|
||||||
var flux = $"from(bucket:\"{_balanceBucket}\") " +
|
var flux = $"from(bucket:\"{_balanceBucket}\") " +
|
||||||
$"|> range(start: {start:s}Z" +
|
$"|> range(start: {start:s}Z" +
|
||||||
(end.HasValue ? $", stop: {end.Value:s}Z" : "") +
|
(end.HasValue ? $", stop: {end.Value:s}Z" : "") +
|
||||||
$") " +
|
$") " +
|
||||||
$"|> filter(fn: (r) => r[\"user_id\"] == \"{userId}\") " +
|
$"|> filter(fn: (r) => r[\"user_id\"] == \"{userId}\") " +
|
||||||
|
$"|> aggregateWindow(every: {samplingInterval}, fn: last, createEmpty: false) " +
|
||||||
$"|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")";
|
$"|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")";
|
||||||
|
|
||||||
var result = await query.QueryAsync<AgentBalanceDto>(flux, _influxDbRepository.Organization);
|
var result = await query.QueryAsync<AgentBalanceDto>(flux, _influxDbRepository.Organization);
|
||||||
@@ -69,52 +106,4 @@ public class AgentBalanceRepository : IAgentBalanceRepository
|
|||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(IList<AgentBalanceHistory> result, int totalCount)> GetAllAgentBalancesWithHistory(
|
|
||||||
DateTime start, DateTime? end)
|
|
||||||
{
|
|
||||||
var results = await _influxDbRepository.QueryAsync(async query =>
|
|
||||||
{
|
|
||||||
// Get all balances within the time range, pivoted so each row is a full AgentBalanceDto
|
|
||||||
var flux = $@"
|
|
||||||
from(bucket: ""{_balanceBucket}"")
|
|
||||||
|> range(start: {start:s}Z{(end.HasValue ? $", stop: {end.Value:s}Z" : "")})
|
|
||||||
|> filter(fn: (r) => r._measurement == ""agent_balance"")
|
|
||||||
|> pivot(rowKey: [""_time""], columnKey: [""_field""], valueColumn: ""_value"")
|
|
||||||
";
|
|
||||||
|
|
||||||
var balances = await query.QueryAsync<AgentBalanceDto>(flux, _influxDbRepository.Organization);
|
|
||||||
|
|
||||||
// Group balances by user ID
|
|
||||||
var agentGroups = balances
|
|
||||||
.GroupBy(b => b.UserId)
|
|
||||||
.Select(g =>
|
|
||||||
{
|
|
||||||
var userBalances = g.Select(b => new AgentBalance
|
|
||||||
{
|
|
||||||
UserId = b.UserId,
|
|
||||||
TotalBalanceValue = b.TotalBalanceValue,
|
|
||||||
UsdcWalletValue = b.UsdcWalletValue,
|
|
||||||
UsdcInPositionsValue = b.UsdcInPositionsValue,
|
|
||||||
BotsAllocationUsdValue = b.BotsAllocationUsdValue,
|
|
||||||
PnL = b.PnL,
|
|
||||||
Time = b.Time
|
|
||||||
}).OrderBy(b => b.Time).ToList();
|
|
||||||
|
|
||||||
// Use a default agent name since we don't store it in AgentBalance anymore
|
|
||||||
var mostRecentAgentName = $"User_{g.Key}";
|
|
||||||
|
|
||||||
return new AgentBalanceHistory
|
|
||||||
{
|
|
||||||
UserId = g.Key,
|
|
||||||
AgentName = mostRecentAgentName,
|
|
||||||
AgentBalances = userBalances
|
|
||||||
};
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
return (agentGroups, agentGroups.Count);
|
|
||||||
});
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user