Add name to backtest filters

This commit is contained in:
2025-10-14 18:38:27 +07:00
parent 74adad5834
commit a462fc9948
6 changed files with 32 additions and 2 deletions

View File

@@ -243,7 +243,8 @@ public class BacktestController : BaseController
[FromQuery] string? tickers = null, [FromQuery] string? tickers = null,
[FromQuery] string? indicators = null, [FromQuery] string? indicators = null,
[FromQuery] double? durationMinDays = null, [FromQuery] double? durationMinDays = null,
[FromQuery] double? durationMaxDays = null) [FromQuery] double? durationMaxDays = null,
[FromQuery] string? name = null)
{ {
var user = await GetUser(); var user = await GetUser();
@@ -302,6 +303,7 @@ public class BacktestController : BaseController
: indicators.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); : indicators.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var filter = new BacktestsFilter var filter = new BacktestsFilter
{ {
NameContains = string.IsNullOrWhiteSpace(name) ? null : name.Trim(),
ScoreMin = scoreMin, ScoreMin = scoreMin,
ScoreMax = scoreMax, ScoreMax = scoreMax,
WinrateMin = winrateMin, WinrateMin = winrateMin,

View File

@@ -2,6 +2,7 @@ namespace Managing.Application.Abstractions.Shared;
public class BacktestsFilter public class BacktestsFilter
{ {
public string? NameContains { get; set; }
public double? ScoreMin { get; set; } public double? ScoreMin { get; set; }
public double? ScoreMax { get; set; } public double? ScoreMax { get; set; }
public int? WinrateMin { get; set; } public int? WinrateMin { get; set; }

View File

@@ -395,6 +395,11 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
if (filter != null) if (filter != null)
{ {
if (!string.IsNullOrWhiteSpace(filter.NameContains))
{
var nameLike = $"%{filter.NameContains.Trim()}%";
baseQuery = baseQuery.Where(b => EF.Functions.ILike(b.Name, nameLike));
}
if (filter.ScoreMin.HasValue) if (filter.ScoreMin.HasValue)
baseQuery = baseQuery.Where(b => b.Score >= filter.ScoreMin.Value); baseQuery = baseQuery.Where(b => b.Score >= filter.ScoreMin.Value);
if (filter.ScoreMax.HasValue) if (filter.ScoreMax.HasValue)

View File

@@ -137,6 +137,7 @@ interface BacktestTableProps {
currentSort?: { sortBy: BacktestSortableColumn; sortOrder: 'asc' | 'desc' } currentSort?: { sortBy: BacktestSortableColumn; sortOrder: 'asc' | 'desc' }
onBacktestDeleted?: () => void // Callback when a backtest is deleted onBacktestDeleted?: () => void // Callback when a backtest is deleted
onFiltersChange?: (filters: { onFiltersChange?: (filters: {
nameContains?: string | null
scoreMin?: number | null scoreMin?: number | null
scoreMax?: number | null scoreMax?: number | null
winrateMin?: number | null winrateMin?: number | null
@@ -148,6 +149,7 @@ interface BacktestTableProps {
durationMaxDays?: number | null durationMaxDays?: number | null
}) => void }) => void
filters?: { filters?: {
nameContains?: string | null
scoreMin?: number | null scoreMin?: number | null
scoreMax?: number | null scoreMax?: number | null
winrateMin?: number | null winrateMin?: number | null
@@ -185,6 +187,7 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
const [scoreMax, setScoreMax] = useState<number>(100) const [scoreMax, setScoreMax] = useState<number>(100)
const [winMin, setWinMin] = useState<number>(0) const [winMin, setWinMin] = useState<number>(0)
const [winMax, setWinMax] = useState<number>(100) const [winMax, setWinMax] = useState<number>(100)
const [nameContains, setNameContains] = useState<string>('')
const [maxDrawdownMax, setMaxDrawdownMax] = useState<number | ''>('') const [maxDrawdownMax, setMaxDrawdownMax] = useState<number | ''>('')
const [tickersInput, setTickersInput] = useState<string>('') const [tickersInput, setTickersInput] = useState<string>('')
const [selectedIndicators, setSelectedIndicators] = useState<string[]>([]) const [selectedIndicators, setSelectedIndicators] = useState<string[]>([])
@@ -194,6 +197,7 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
const applyFilters = () => { const applyFilters = () => {
if (!onFiltersChange) return if (!onFiltersChange) return
onFiltersChange({ onFiltersChange({
nameContains: nameContains.trim() || null,
scoreMin, scoreMin,
scoreMax, scoreMax,
winrateMin: winMin, winrateMin: winMin,
@@ -223,6 +227,7 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
// Sync incoming filters prop to local sidebar state // Sync incoming filters prop to local sidebar state
useEffect(() => { useEffect(() => {
if (!filters) return if (!filters) return
setNameContains(filters.nameContains ?? '')
if (typeof filters.scoreMin === 'number') setScoreMin(filters.scoreMin) if (typeof filters.scoreMin === 'number') setScoreMin(filters.scoreMin)
if (typeof filters.scoreMax === 'number') setScoreMax(filters.scoreMax) if (typeof filters.scoreMax === 'number') setScoreMax(filters.scoreMax)
if (typeof filters.winrateMin === 'number') setWinMin(filters.winrateMin) if (typeof filters.winrateMin === 'number') setWinMin(filters.winrateMin)
@@ -600,6 +605,18 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
</div> </div>
</div> </div>
{/* Name contains */}
<div className="mb-6">
<div className="mb-2 font-medium">Name contains</div>
<input
type="text"
placeholder="e.g. MyBundleTest"
className="input input-bordered input-sm w-full"
value={nameContains}
onChange={e => setNameContains(e.target.value)}
/>
</div>
{/* Tickers */} {/* Tickers */}
<div className="mb-6"> <div className="mb-6">
<div className="mb-2 font-medium">Tickers</div> <div className="mb-2 font-medium">Tickers</div>

View File

@@ -665,7 +665,7 @@ export class BacktestClient extends AuthorizedApiBase {
return Promise.resolve<PaginatedBacktestsResponse>(null as any); return Promise.resolve<PaginatedBacktestsResponse>(null as any);
} }
backtest_GetBacktestsPaginated(page: number | undefined, pageSize: number | undefined, sortBy: BacktestSortableColumn | undefined, sortOrder: string | null | undefined, scoreMin: number | null | undefined, scoreMax: number | null | undefined, winrateMin: number | null | undefined, winrateMax: number | null | undefined, maxDrawdownMax: number | null | undefined, tickers: string | null | undefined, indicators: string | null | undefined, durationMinDays: number | null | undefined, durationMaxDays: number | null | undefined): Promise<PaginatedBacktestsResponse> { backtest_GetBacktestsPaginated(page: number | undefined, pageSize: number | undefined, sortBy: BacktestSortableColumn | undefined, sortOrder: string | null | undefined, scoreMin: number | null | undefined, scoreMax: number | null | undefined, winrateMin: number | null | undefined, winrateMax: number | null | undefined, maxDrawdownMax: number | null | undefined, tickers: string | null | undefined, indicators: string | null | undefined, durationMinDays: number | null | undefined, durationMaxDays: number | null | undefined, name: string | null | undefined): Promise<PaginatedBacktestsResponse> {
let url_ = this.baseUrl + "/Backtest/Paginated?"; let url_ = this.baseUrl + "/Backtest/Paginated?";
if (page === null) if (page === null)
throw new Error("The parameter 'page' cannot be null."); throw new Error("The parameter 'page' cannot be null.");
@@ -699,6 +699,8 @@ export class BacktestClient extends AuthorizedApiBase {
url_ += "durationMinDays=" + encodeURIComponent("" + durationMinDays) + "&"; url_ += "durationMinDays=" + encodeURIComponent("" + durationMinDays) + "&";
if (durationMaxDays !== undefined && durationMaxDays !== null) if (durationMaxDays !== undefined && durationMaxDays !== null)
url_ += "durationMaxDays=" + encodeURIComponent("" + durationMaxDays) + "&"; url_ += "durationMaxDays=" + encodeURIComponent("" + durationMaxDays) + "&";
if (name !== undefined && name !== null)
url_ += "name=" + encodeURIComponent("" + name) + "&";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = { let options_: RequestInit = {

View File

@@ -28,6 +28,7 @@ const BacktestScanner: React.FC = () => {
// Filters state coming from BacktestTable sidebar // Filters state coming from BacktestTable sidebar
const [filters, setFilters] = useState<{ const [filters, setFilters] = useState<{
nameContains?: string | null
scoreMin?: number | null scoreMin?: number | null
scoreMax?: number | null scoreMax?: number | null
winrateMin?: number | null winrateMin?: number | null
@@ -57,6 +58,7 @@ const BacktestScanner: React.FC = () => {
PAGE_SIZE, PAGE_SIZE,
currentSort.sortBy, currentSort.sortBy,
currentSort.sortOrder, currentSort.sortOrder,
// filters
filters.scoreMin ?? null, filters.scoreMin ?? null,
filters.scoreMax ?? null, filters.scoreMax ?? null,
filters.winrateMin ?? null, filters.winrateMin ?? null,
@@ -66,6 +68,7 @@ const BacktestScanner: React.FC = () => {
(filters.indicators && filters.indicators.length ? filters.indicators.join(',') : null), (filters.indicators && filters.indicators.length ? filters.indicators.join(',') : null),
filters.durationMinDays ?? null, filters.durationMinDays ?? null,
filters.durationMaxDays ?? null, filters.durationMaxDays ?? null,
filters.nameContains ?? null,
) )
return { return {
backtests: (response.backtests as LightBacktestResponse[]) || [], backtests: (response.backtests as LightBacktestResponse[]) || [],