Add filters and sorting for backtests

This commit is contained in:
2025-10-14 18:06:36 +07:00
parent 49b0f7b696
commit 74adad5834
21 changed files with 4028 additions and 81 deletions

View File

@@ -1,6 +1,7 @@
using System.Text.Json;
using Managing.Api.Models.Requests;
using Managing.Application.Abstractions.Services;
using Managing.Application.Abstractions.Shared;
using Managing.Application.Hubs;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
@@ -10,6 +11,7 @@ using Managing.Domain.Strategies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using static Managing.Common.Enums;
using MoneyManagementRequest = Managing.Domain.Backtests.MoneyManagementRequest;
namespace Managing.Api.Controllers;
@@ -231,8 +233,17 @@ public class BacktestController : BaseController
public async Task<ActionResult<PaginatedBacktestsResponse>> GetBacktestsPaginated(
int page = 1,
int pageSize = 50,
string sortBy = "score",
string sortOrder = "desc")
BacktestSortableColumn sortBy = BacktestSortableColumn.Score,
string sortOrder = "desc",
[FromQuery] double? scoreMin = null,
[FromQuery] double? scoreMax = null,
[FromQuery] int? winrateMin = null,
[FromQuery] int? winrateMax = null,
[FromQuery] decimal? maxDrawdownMax = null,
[FromQuery] string? tickers = null,
[FromQuery] string? indicators = null,
[FromQuery] double? durationMinDays = null,
[FromQuery] double? durationMaxDays = null)
{
var user = await GetUser();
@@ -251,8 +262,65 @@ public class BacktestController : BaseController
return BadRequest("Sort order must be 'asc' or 'desc'");
}
// Validate score and winrate ranges [0,100]
if (scoreMin.HasValue && (scoreMin < 0 || scoreMin > 100))
{
return BadRequest("scoreMin must be between 0 and 100");
}
if (scoreMax.HasValue && (scoreMax < 0 || scoreMax > 100))
{
return BadRequest("scoreMax must be between 0 and 100");
}
if (winrateMin.HasValue && (winrateMin < 0 || winrateMin > 100))
{
return BadRequest("winrateMin must be between 0 and 100");
}
if (winrateMax.HasValue && (winrateMax < 0 || winrateMax > 100))
{
return BadRequest("winrateMax must be between 0 and 100");
}
if (scoreMin.HasValue && scoreMax.HasValue && scoreMin > scoreMax)
{
return BadRequest("scoreMin must be less than or equal to scoreMax");
}
if (winrateMin.HasValue && winrateMax.HasValue && winrateMin > winrateMax)
{
return BadRequest("winrateMin must be less than or equal to winrateMax");
}
if (maxDrawdownMax.HasValue && maxDrawdownMax < 0)
{
return BadRequest("maxDrawdownMax must be greater than or equal to 0");
}
// Parse multi-selects if provided (comma-separated). Currently unused until repository wiring.
var tickerList = string.IsNullOrWhiteSpace(tickers)
? Array.Empty<string>()
: tickers.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var indicatorList = string.IsNullOrWhiteSpace(indicators)
? Array.Empty<string>()
: indicators.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var filter = new BacktestsFilter
{
ScoreMin = scoreMin,
ScoreMax = scoreMax,
WinrateMin = winrateMin,
WinrateMax = winrateMax,
MaxDrawdownMax = maxDrawdownMax,
Tickers = tickerList,
Indicators = indicatorList,
DurationMin = durationMinDays.HasValue ? TimeSpan.FromDays(durationMinDays.Value) : (TimeSpan?)null,
DurationMax = durationMaxDays.HasValue ? TimeSpan.FromDays(durationMaxDays.Value) : (TimeSpan?)null
};
var (backtests, totalCount) =
await _backtester.GetBacktestsByUserPaginatedAsync(user, page, pageSize, sortBy, sortOrder);
await _backtester.GetBacktestsByUserPaginatedAsync(
user,
page,
pageSize,
sortBy,
sortOrder,
filter);
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
var response = new PaginatedBacktestsResponse