Add name to backtest filters
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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[]) || [],
|
||||||
|
|||||||
Reference in New Issue
Block a user