Delete backtest by id and with filters

This commit is contained in:
2025-07-11 22:05:46 +07:00
parent 9714da1eb9
commit 79f0cd20c1
9 changed files with 239 additions and 69 deletions

View File

@@ -498,6 +498,50 @@ export class BacktestClient extends AuthorizedApiBase {
return Promise.resolve<Backtest>(null as any);
}
backtest_DeleteBacktests(request: DeleteBacktestsRequest): Promise<FileResponse> {
let url_ = this.baseUrl + "/Backtest/multiple";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(request);
let options_: RequestInit = {
body: content_,
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Accept": "application/octet-stream"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processBacktest_DeleteBacktests(_response);
});
}
protected processBacktest_DeleteBacktests(response: Response): Promise<FileResponse> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) {
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
if (fileName) {
fileName = decodeURIComponent(fileName);
} else {
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
}
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<FileResponse>(null as any);
}
backtest_GetBacktestsByRequestId(requestId: string): Promise<Backtest[]> {
let url_ = this.baseUrl + "/Backtest/ByRequestId/{requestId}";
if (requestId === undefined || requestId === null)
@@ -3648,6 +3692,10 @@ export interface SuperTrendResult extends ResultBase {
lowerBand?: number | null;
}
export interface DeleteBacktestsRequest {
backtestIds: string[];
}
export interface RunBacktestRequest {
config?: TradingBotConfigRequest | null;
startDate?: Date;

View File

@@ -595,6 +595,10 @@ export interface SuperTrendResult extends ResultBase {
lowerBand?: number | null;
}
export interface DeleteBacktestsRequest {
backtestIds: string[];
}
export interface RunBacktestRequest {
config?: TradingBotConfigRequest | null;
startDate?: Date;

View File

@@ -5,7 +5,7 @@ import React, {useEffect, useState} from 'react'
import 'react-toastify/dist/ReactToastify.css'
import useApiUrlStore from '../../app/store/apiStore'
import useBacktestStore from '../../app/store/backtestStore'
import {Loader} from '../../components/atoms'
import {Loader, Slider} from '../../components/atoms'
import {Modal, Toast} from '../../components/mollecules'
import {BacktestTable, UnifiedTradingModal} from '../../components/organism'
import {BacktestClient} from '../../generated/ManagingApi'
@@ -13,6 +13,11 @@ import {BacktestClient} from '../../generated/ManagingApi'
const BacktestScanner: React.FC = () => {
const [showModal, setShowModal] = useState(false)
const [showModalRemoveBacktest, setShowModalRemoveBacktest] = useState(false)
const [filteredCount, setFilteredCount] = useState(0)
const [filterValues, setFilterValues] = useState({
winRate: 50,
score: 50
})
const { apiUrl } = useApiUrlStore()
const { backtests: backtestingResult, setBacktests, setLoading } = useBacktestStore()
const client = new BacktestClient({}, apiUrl)
@@ -32,8 +37,55 @@ const BacktestScanner: React.FC = () => {
setLoading(isLoading)
}, [isLoading, setLoading])
useEffect(() => {
if (backtestingResult && showModalRemoveBacktest) {
calculateFilteredCount()
}
}, [backtestingResult, showModalRemoveBacktest])
const openModalRemoveBacktests = () => {
setShowModalRemoveBacktest(true)
// Calculate initial filtered count
calculateFilteredCount()
}
const calculateFilteredCount = (formData?: any) => {
if (!backtestingResult) {
setFilteredCount(0)
return
}
const filters = formData || filterValues
const filteredBacktests = backtestingResult.filter((backtest: any) => {
// Ensure values are numbers and handle potential null/undefined values
const backtestWinRate = Number(backtest.winRate) || 0
const backtestScore = Number(backtest.score) || 0
// Debug logging to check the data structure
console.log('Backtest:', {
id: backtest.id,
winRate: backtest.winRate,
winRateNumber: backtestWinRate,
score: backtest.score,
scoreNumber: backtestScore,
filters: filters
})
return (
backtestWinRate <= filters.winRate &&
backtestScore <= filters.score
)
})
console.log('Filtered count:', filteredBacktests.length, 'Total:', backtestingResult.length)
setFilteredCount(filteredBacktests.length)
}
const updateFilterValue = (field: string, value: number) => {
const newValues = { ...filterValues, [field]: value }
setFilterValues(newValues)
calculateFilteredCount(newValues)
}
const closeModalRemoveBacktest = () => {
@@ -44,12 +96,11 @@ const BacktestScanner: React.FC = () => {
event.preventDefault()
const form = {
finalPnl: Number(event.target.finalPnl.value),
hp: Number(event.target.hp.value),
winRate: Number(event.target.winRate.value),
winRate: filterValues.winRate,
score: filterValues.score,
}
const notify = new Toast(`Deleting Backtests...`)
const client = new BacktestClient({}, apiUrl)
closeModalRemoveBacktest()
if (!backtestingResult) {
@@ -57,22 +108,31 @@ const BacktestScanner: React.FC = () => {
}
const backTestToDelete = backtestingResult.filter((backtest: any) => {
const H_P = backtest.growthPercentage - backtest.hodlPercentage
// Ensure values are numbers and handle potential null/undefined values
const backtestWinRate = Number(backtest.winRate) || 0
const backtestScore = Number(backtest.score) || 0
return (
backtest.winRate <= form.winRate &&
backtest.finalPnl <= form.finalPnl &&
H_P <= form.hp
backtestWinRate <= form.winRate &&
backtestScore <= form.score
)
})
backTestToDelete.forEach(async (backtest) => {
return await client
.backtest_DeleteBacktest(backtest.id)
.then(() => {})
.catch((err) => {
notify.update('error', err)
})
})
notify.update('success', 'Backtest deleted')
if (backTestToDelete.length === 0) {
notify.update('warning', 'No backtests match the criteria')
return
}
try {
const backtestIds = backTestToDelete.map((backtest: any) => backtest.id)
await client.backtest_DeleteBacktests({ backtestIds })
notify.update('success', `${backTestToDelete.length} backtests deleted successfully`)
// Refetch backtests to update the list
refetch()
} catch (err: any) {
notify.update('error', err?.message || 'An error occurred while deleting backtests')
}
}
function openModal() {
@@ -118,56 +178,76 @@ const BacktestScanner: React.FC = () => {
showModal={showModalRemoveBacktest}
onSubmit={onSubmitRemoveBacktest}
onClose={closeModalRemoveBacktest}
titleHeader={'Remove Backtest'}
titleHeader={'Delete Backtests by Filters'}
>
<div className="form-control">
<div className="input-group">
<label htmlFor="finalPnl" className="label mr-6">
PnL{'<'}
</label>
<label className="input-group">
<input
type="number"
className="input"
name="finalPnl"
defaultValue={0}
/>
</label>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="winRate" className="label mr-6">
WinRate{'<'}
WinRate
</label>
<label className="input-group">
<div className="flex items-center gap-2">
<input
type="number"
className="input"
className="input input-sm w-20"
name="winRate"
defaultValue={50}
value={filterValues.winRate}
min="0"
max="100"
onChange={(e) => updateFilterValue('winRate', Number(e.target.value))}
/>
</label>
<Slider
id="winRate-slider"
value={filterValues.winRate}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateFilterValue('winRate', Number(e.target.value))}
min={0}
max={100}
step={1}
prefixValue=""
suffixValue="%"
/>
</div>
</div>
</div>
<div className="form-control">
<div className="input-group">
<label htmlFor="hp" className="label mr-6">
H/P{'<'}
<label htmlFor="score" className="label mr-6">
Score
</label>
<label className="input-group">
<div className="flex items-center gap-2">
<input
type="number"
className="input"
name="hp"
defaultValue={3}
className="input input-sm w-20"
name="score"
value={filterValues.score}
min="0"
max={100}
onChange={(e) => updateFilterValue('score', Number(e.target.value))}
/>
</label>
<Slider
id="score-slider"
value={filterValues.score}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateFilterValue('score', Number(e.target.value))}
min={0}
max={100}
step={1}
prefixValue=""
suffixValue=""
/>
</div>
</div>
</div>
<div className="alert alert-info mb-4">
<div>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" className="stroke-current shrink-0 w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>This will delete {filteredCount} backtest{filteredCount !== 1 ? 's' : ''} that match the criteria</span>
</div>
</div>
<div className="modal-action">
<button type="submit" className="btn">
Run
<button type="submit" className="btn btn-error" disabled={filteredCount === 0}>
Delete {filteredCount} Backtest{filteredCount !== 1 ? 's' : ''}
</button>
</div>
</Modal>