Add backtest light

This commit is contained in:
2025-07-17 05:19:16 +07:00
parent 6a634eafaa
commit 27bed791c3
16 changed files with 181 additions and 270 deletions

View File

@@ -179,7 +179,21 @@ public class BacktestController : BaseController
var response = new PaginatedBacktestsResponse
{
Backtests = backtests,
Backtests = backtests.Select(b => new LightBacktestResponse
{
Id = b.Id,
Config = b.Config,
FinalPnl = b.FinalPnl,
WinRate = b.WinRate,
GrowthPercentage = b.GrowthPercentage,
HodlPercentage = b.HodlPercentage,
StartDate = b.StartDate,
EndDate = b.EndDate,
MaxDrawdown = b.MaxDrawdown,
Fees = b.Fees,
SharpeRatio = b.SharpeRatio,
Score = b.Score
}),
TotalCount = totalCount,
CurrentPage = page,
PageSize = pageSize,
@@ -229,7 +243,21 @@ public class BacktestController : BaseController
var response = new PaginatedBacktestsResponse
{
Backtests = backtests,
Backtests = backtests.Select(b => new LightBacktestResponse
{
Id = b.Id,
Config = b.Config,
FinalPnl = b.FinalPnl,
WinRate = b.WinRate,
GrowthPercentage = b.GrowthPercentage,
HodlPercentage = b.HodlPercentage,
StartDate = b.StartDate,
EndDate = b.EndDate,
MaxDrawdown = b.MaxDrawdown,
Fees = b.Fees,
SharpeRatio = b.SharpeRatio,
Score = b.Score
}),
TotalCount = totalCount,
CurrentPage = page,
PageSize = pageSize,

View File

@@ -0,0 +1,19 @@
using Managing.Domain.Bots;
namespace Managing.Api.Models.Requests;
public class LightBacktestResponse
{
public string Id { get; set; } = string.Empty;
public TradingBotConfig Config { get; set; } = new();
public decimal FinalPnl { get; set; }
public int WinRate { get; set; }
public decimal GrowthPercentage { get; set; }
public decimal HodlPercentage { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public decimal? MaxDrawdown { get; set; }
public decimal Fees { get; set; }
public double? SharpeRatio { get; set; }
public double Score { get; set; }
}

View File

@@ -1,5 +1,3 @@
using Managing.Domain.Backtests;
namespace Managing.Api.Models.Requests;
/// <summary>
@@ -10,7 +8,7 @@ public class PaginatedBacktestsResponse
/// <summary>
/// The list of backtests for the current page
/// </summary>
public IEnumerable<Backtest> Backtests { get; set; } = new List<Backtest>();
public IEnumerable<LightBacktestResponse> Backtests { get; set; } = new List<LightBacktestResponse>();
/// <summary>
/// Total number of backtests across all pages

View File

@@ -8,8 +8,8 @@ public interface IBacktestRepository
void InsertBacktestForUser(User user, Backtest result);
IEnumerable<Backtest> GetBacktestsByUser(User user);
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
(IEnumerable<Backtest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
(IEnumerable<Backtest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
Backtest GetBacktestByIdForUser(User user, string id);
void DeleteBacktestByIdForUser(User user, string id);
void DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids);

View File

@@ -54,12 +54,12 @@ namespace Managing.Application.Abstractions.Services
bool DeleteBacktests();
IEnumerable<Backtest> GetBacktestsByUser(User user);
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
(IEnumerable<Backtest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
Backtest GetBacktestByIdForUser(User user, string id);
bool DeleteBacktestByUser(User user, string id);
bool DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids);
bool DeleteBacktestsByUser(User user);
(IEnumerable<Backtest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
}

View File

@@ -448,7 +448,7 @@ namespace Managing.Application.Backtesting
return backtests;
}
public (IEnumerable<Backtest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var (backtests, totalCount) = _backtestRepository.GetBacktestsByRequestIdPaginated(requestId, page, pageSize, sortBy, sortOrder);
return (backtests, totalCount);
@@ -531,7 +531,7 @@ namespace Managing.Application.Backtesting
}
}
public (IEnumerable<Backtest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var (backtests, totalCount) = _backtestRepository.GetBacktestsByUserPaginated(user, page, pageSize, sortBy, sortOrder);
return (backtests, totalCount);

View File

@@ -0,0 +1,19 @@
using Managing.Domain.Bots;
namespace Managing.Domain.Backtests;
public class LightBacktest
{
public string Id { get; set; } = string.Empty;
public TradingBotConfig Config { get; set; } = new();
public decimal FinalPnl { get; set; }
public int WinRate { get; set; }
public decimal GrowthPercentage { get; set; }
public decimal HodlPercentage { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public decimal? MaxDrawdown { get; set; }
public decimal Fees { get; set; }
public double? SharpeRatio { get; set; }
public double Score { get; set; }
}

View File

@@ -83,7 +83,7 @@ public class BacktestRepository : IBacktestRepository
return backtests.Select(b => MongoMappers.Map(b));
}
public (IEnumerable<Backtest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var stopwatch = Stopwatch.StartNew();
@@ -101,14 +101,12 @@ public class BacktestRepository : IBacktestRepository
.Include(b => b.WinRate)
.Include(b => b.GrowthPercentage)
.Include(b => b.HodlPercentage)
.Include(b => b.User)
.Include(b => b.Statistics)
.Include(b => b.StartDate)
.Include(b => b.EndDate)
.Include(b => b.Score)
.Include(b => b.RequestId)
.Include(b => b.Metadata)
.Include(b => b.Config);
.Include(b => b.Config)
.Include(b => b.Fees)
.Include(b => b.Statistics);
// Build sort definition
var sortDefinition = sortBy.ToLower() switch
@@ -146,7 +144,21 @@ public class BacktestRepository : IBacktestRepository
Console.WriteLine(
$"[BacktestRepo] Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Projection: {afterProjectionMs - afterCountMs}ms, ToList: {afterToListMs - afterProjectionMs}ms, Total: {afterToListMs}ms");
var mappedBacktests = backtests.Select(b => MongoMappers.Map(b));
var mappedBacktests = backtests.Select(b => new LightBacktest
{
Id = b.Identifier,
Config = MongoMappers.Map(b.Config),
FinalPnl = b.FinalPnl,
WinRate = b.WinRate,
GrowthPercentage = b.GrowthPercentage,
HodlPercentage = b.HodlPercentage,
StartDate = b.StartDate,
EndDate = b.EndDate,
MaxDrawdown = b.Statistics?.MaxDrawdown,
Fees = b.Fees,
SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null,
Score = b.Score
});
return (mappedBacktests, (int)totalCount);
}
@@ -198,7 +210,7 @@ public class BacktestRepository : IBacktestRepository
}
}
public (IEnumerable<Backtest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page,
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page,
int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var stopwatch = Stopwatch.StartNew();
@@ -216,14 +228,12 @@ public class BacktestRepository : IBacktestRepository
.Include(b => b.WinRate)
.Include(b => b.GrowthPercentage)
.Include(b => b.HodlPercentage)
.Include(b => b.User)
.Include(b => b.Statistics)
.Include(b => b.StartDate)
.Include(b => b.EndDate)
.Include(b => b.Score)
.Include(b => b.RequestId)
.Include(b => b.Metadata)
.Include(b => b.Config);
.Include(b => b.Config)
.Include(b => b.Fees)
.Include(b => b.Statistics);
// Build sort definition
var sortDefinition = sortBy.ToLower() switch
@@ -261,7 +271,21 @@ public class BacktestRepository : IBacktestRepository
Console.WriteLine(
$"[BacktestRepo] User Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Projection: {afterProjectionMs - afterCountMs}ms, ToList: {afterToListMs - afterProjectionMs}ms, Total: {afterToListMs}ms");
var mappedBacktests = backtests.Select(b => MongoMappers.Map(b));
var mappedBacktests = backtests.Select(b => new LightBacktest
{
Id = b.Identifier,
Config = MongoMappers.Map(b.Config),
FinalPnl = b.FinalPnl,
WinRate = b.WinRate,
GrowthPercentage = b.GrowthPercentage,
HodlPercentage = b.HodlPercentage,
StartDate = b.StartDate,
EndDate = b.EndDate,
MaxDrawdown = b.Statistics?.MaxDrawdown,
Fees = b.Fees,
SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null,
Score = b.Score
});
return (mappedBacktests, (int)totalCount);
}

View File

@@ -26,6 +26,8 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
public MoneyManagementDto OptimizedMoneyManagement { get; internal set; }
public UserDto User { get; set; }
public PerformanceMetrics Statistics { get; set; }
[BsonRepresentation(BsonType.Decimal128)]
public decimal Fees { get; set; }
public double Score { get; set; }
public string Identifier { get; set; }
public string RequestId { get; set; }

View File

@@ -1,11 +1,11 @@
import {create} from 'zustand'
import type {Backtest} from '../../generated/ManagingApi'
import type {LightBacktestResponse} from '../../generated/ManagingApi'
interface BacktestStore {
backtests: Backtest[]
backtests: LightBacktestResponse[]
isLoading: boolean
setBacktests: (backtests: Backtest[]) => void
addBacktest: (backtest: Backtest) => void
setBacktests: (backtests: LightBacktestResponse[]) => void
addBacktest: (backtest: LightBacktestResponse) => void
removeBacktest: (id: string) => void
setLoading: (loading: boolean) => void
clearBacktests: () => void
@@ -15,10 +15,10 @@ const useBacktestStore = create<BacktestStore>((set, get) => ({
backtests: [],
isLoading: false,
setBacktests: (backtests: Backtest[]) =>
setBacktests: (backtests: LightBacktestResponse[]) =>
set({ backtests }),
addBacktest: (backtest: Backtest) =>
addBacktest: (backtest: LightBacktestResponse) =>
set((state) => ({
backtests: [...state.backtests, backtest]
})),

View File

@@ -84,13 +84,12 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
const candles = candlesData?.candles || currentBacktest.candles || [];
const indicatorsValues = candlesData?.indicatorsValues || currentBacktest.indicatorsValues || {};
const {
positions,
walletBalances,
signals,
statistics,
config
} = currentBacktest;
// Only destructure these properties if we have full backtest data
const positions = fullBacktestData?.positions || [];
const walletBalances = fullBacktestData?.walletBalances || [];
const signals = fullBacktestData?.signals || [];
const statistics = fullBacktestData?.statistics;
const config = currentBacktest.config;
// Helper function to calculate position open time in hours
const calculateOpenTimeInHours = (position: any) => {
@@ -219,7 +218,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
// Calculate recommended cooldown based on positions that fail after a win
const getCooldownRecommendations = () => {
if (positions.length < 2 || !candles || candles.length < 2) {
if (positions?.length < 2 || !candles || candles?.length < 2) {
return { percentile75: "0", average: "0", median: "0" };
}
@@ -343,14 +342,14 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
<CardText
title="Max Drowdown"
content={
statistics.maxDrawdown?.toFixed(4).toString() +
(statistics?.maxDrawdown?.toFixed(4) || '0.0000') +
'$'
}
></CardText>
<CardText
title="Sharpe Ratio"
content={
(statistics.sharpeRatio
(statistics?.sharpeRatio
? statistics.sharpeRatio * 100
: 0
)
@@ -361,7 +360,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
<CardText
title="Max Drawdown Recovery Time"
content={
statistics.maxDrawdownRecoveryTime?.toString() +
(statistics?.maxDrawdownRecoveryTime?.toString() || '0') +
' days'
}
></CardText>

View File

@@ -4,7 +4,7 @@ import {useExpanded, useFilters, usePagination, useSortBy, useTable,} from 'reac
import useApiUrlStore from '../../../app/store/apiStore'
import useBacktestStore from '../../../app/store/backtestStore'
import type {Backtest} from '../../../generated/ManagingApi'
import type {Backtest, LightBacktestResponse} from '../../../generated/ManagingApi'
import {BacktestClient} from '../../../generated/ManagingApi'
import {ConfigDisplayModal, IndicatorsDisplay, SelectColumnFilter} from '../../mollecules'
import {UnifiedTradingModal} from '../index'
@@ -130,7 +130,7 @@ const ServerSortableTable = ({
}
interface BacktestTableProps {
list: Backtest[] | undefined
list: LightBacktestResponse[] | undefined
isFetching?: boolean
displaySummary?: boolean
onSortChange?: (sortBy: string, sortOrder: 'asc' | 'desc') => void
@@ -139,26 +139,10 @@ interface BacktestTableProps {
const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, displaySummary = true, onSortChange, currentSort}) => {
const [rows, setRows] = useState<Backtest[]>([])
const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortChange, currentSort}) => {
const [rows, setRows] = useState<LightBacktestResponse[]>([])
const {apiUrl} = useApiUrlStore()
const {removeBacktest} = useBacktestStore()
const [optimizedMoneyManagement, setOptimizedMoneyManagement] = useState({
stopLoss: 0,
takeProfit: 0,
})
const [positionTimingStats, setPositionTimingStats] = useState({
averageOpenTime: 0,
medianOpenTime: 0,
losingPositionsAverageOpenTime: 0,
})
const [cooldownRecommendations, setCooldownRecommendations] = useState({
averageCooldown: 0,
medianCooldown: 0,
})
// Summary collapse state
const [isSummaryCollapsed, setIsSummaryCollapsed] = useState(true)
// Bot configuration modal state
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
@@ -466,146 +450,6 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, displayS
useEffect(() => {
if (list) {
setRows(list)
// Calculate average optimized money management
if (list.length > 0) {
const optimized = list.map((b) => b.optimizedMoneyManagement);
const stopLoss = optimized.reduce((acc, curr) => acc + (curr?.stopLoss ?? 0), 0);
const takeProfit = optimized.reduce((acc, curr) => acc + (curr?.takeProfit ?? 0), 0);
setOptimizedMoneyManagement({
stopLoss: stopLoss / optimized.length,
takeProfit: takeProfit / optimized.length,
});
// Calculate position timing statistics
const allPositions = list.flatMap(backtest => backtest.positions);
const finishedPositions = allPositions.filter(p => p.status === 'Finished');
if (finishedPositions.length > 0) {
// Calculate position open times in hours
const openTimes = finishedPositions.map(position => {
const openTime = new Date(position.open.date);
// Find the closing trade (either stopLoss or takeProfit that was filled)
let closeTime = new Date();
if (position.stopLoss.status === 'Filled') {
closeTime = new Date(position.stopLoss.date);
} else if (position.takeProfit1.status === 'Filled') {
closeTime = new Date(position.takeProfit1.date);
} else if (position.takeProfit2?.status === 'Filled') {
closeTime = new Date(position.takeProfit2.date);
}
// Return time difference in hours
return (closeTime.getTime() - openTime.getTime()) / (1000 * 60 * 60);
});
// Calculate average
const averageOpenTime = openTimes.reduce((sum, time) => sum + time, 0) / openTimes.length;
// Calculate median
const sortedTimes = [...openTimes].sort((a, b) => a - b);
const medianOpenTime = sortedTimes.length % 2 === 0
? (sortedTimes[sortedTimes.length / 2 - 1] + sortedTimes[sortedTimes.length / 2]) / 2
: sortedTimes[Math.floor(sortedTimes.length / 2)];
// Calculate average for losing positions
const losingPositions = finishedPositions.filter(p => (p.profitAndLoss?.realized ?? 0) < 0);
let losingPositionsAverageOpenTime = 0;
if (losingPositions.length > 0) {
const losingOpenTimes = losingPositions.map(position => {
const openTime = new Date(position.open.date);
let closeTime = new Date();
if (position.stopLoss.status === 'Filled') {
closeTime = new Date(position.stopLoss.date);
} else if (position.takeProfit1.status === 'Filled') {
closeTime = new Date(position.takeProfit1.date);
} else if (position.takeProfit2?.status === 'Filled') {
closeTime = new Date(position.takeProfit2.date);
}
return (closeTime.getTime() - openTime.getTime()) / (1000 * 60 * 60);
});
losingPositionsAverageOpenTime = losingOpenTimes.reduce((sum, time) => sum + time, 0) / losingOpenTimes.length;
}
setPositionTimingStats({
averageOpenTime,
medianOpenTime,
losingPositionsAverageOpenTime,
});
}
// Calculate cooldown recommendations across all backtests
const allCooldownValues: number[] = [];
list.forEach(backtest => {
if (backtest.positions.length < 2 || !backtest.candles || backtest.candles.length < 2) {
return;
}
// Determine candle timeframe in milliseconds
const candleTimeframeMs = new Date(backtest.candles[1].date).getTime() - new Date(backtest.candles[0].date).getTime();
const sortedPositions = [...backtest.positions].sort((a, b) => {
const dateA = new Date(a.open.date).getTime();
const dateB = new Date(b.open.date).getTime();
return dateA - dateB;
});
for (let i = 0; i < sortedPositions.length - 1; i++) {
const currentPosition = sortedPositions[i];
const nextPosition = sortedPositions[i + 1];
const currentRealized = currentPosition.profitAndLoss?.realized ?? 0;
const nextRealized = nextPosition.profitAndLoss?.realized ?? 0;
// Check if current position is winning and next position is losing
if (currentRealized > 0 && nextRealized <= 0) {
// Calculate the close time of the current (winning) position
let currentCloseDate: Date | null = null;
if (currentPosition.profitAndLoss?.realized != null) {
if (currentPosition.profitAndLoss.realized > 0) {
currentCloseDate = new Date(currentPosition.takeProfit1.date);
} else {
currentCloseDate = new Date(currentPosition.stopLoss.date);
}
}
if (currentCloseDate) {
const nextOpenDate = new Date(nextPosition.open.date);
const gapInMs = nextOpenDate.getTime() - currentCloseDate.getTime();
if (gapInMs >= 0) { // Only consider positive gaps
// Convert milliseconds to number of candles
const gapInCandles = Math.floor(gapInMs / candleTimeframeMs);
allCooldownValues.push(gapInCandles);
}
}
}
}
});
if (allCooldownValues.length > 0) {
// Calculate average cooldown
const averageCooldown = allCooldownValues.reduce((sum, value) => sum + value, 0) / allCooldownValues.length;
// Calculate median cooldown
const sortedCooldowns = [...allCooldownValues].sort((a, b) => a - b);
const medianCooldown = sortedCooldowns.length % 2 === 0
? (sortedCooldowns[sortedCooldowns.length / 2 - 1] + sortedCooldowns[sortedCooldowns.length / 2]) / 2
: sortedCooldowns[Math.floor(sortedCooldowns.length / 2)];
setCooldownRecommendations({
averageCooldown: Math.ceil(averageCooldown),
medianCooldown: Math.ceil(medianCooldown),
});
}
}
}
}, [list])
@@ -617,58 +461,6 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, displayS
</div>
) : (
<>
{list && list.length > 0 && displaySummary && (
<div className="mb-4">
<button
onClick={() => setIsSummaryCollapsed(!isSummaryCollapsed)}
className="btn btn-sm btn-outline mb-2"
>
{isSummaryCollapsed ? (
<>
<ChevronRightIcon className="w-4 h-4 mr-1" />
Show Summary
</>
) : (
<>
<ChevronDownIcon className="w-4 h-4 mr-1" />
Hide Summary
</>
)}
</button>
{!isSummaryCollapsed && (
<div className="bg-base-200 p-4 rounded-lg space-y-3">
<div className="flex flex-wrap gap-4 text-sm">
<div className="flex-1 min-w-0">
<div className="font-semibold text-xs text-gray-600 mb-1">Money Management</div>
<div className="flex gap-2">
<span className="badge badge-outline">SL: {optimizedMoneyManagement.stopLoss.toFixed(2)}%</span>
<span className="badge badge-outline">TP: {optimizedMoneyManagement.takeProfit.toFixed(2)}%</span>
<span className="badge badge-outline">R/R: {(optimizedMoneyManagement.takeProfit / optimizedMoneyManagement.stopLoss || 0).toFixed(2)}</span>
</div>
</div>
<div className="flex-1 min-w-0">
<div className="font-semibold text-xs text-gray-600 mb-1">Position Timing</div>
<div className="flex gap-2">
<span className="badge badge-outline">Avg: {positionTimingStats.averageOpenTime.toFixed(1)}h</span>
<span className="badge badge-outline">Median: {positionTimingStats.medianOpenTime.toFixed(1)}h</span>
<span className="badge badge-outline">Losing: {positionTimingStats.losingPositionsAverageOpenTime.toFixed(1)}h</span>
</div>
</div>
<div className="flex-1 min-w-0">
<div className="font-semibold text-xs text-gray-600 mb-1">Cooldown</div>
<div className="flex gap-2">
<span className="badge badge-outline">Avg: {cooldownRecommendations.averageCooldown} candles</span>
<span className="badge badge-outline">Median: {cooldownRecommendations.medianCooldown} candles</span>
</div>
</div>
</div>
</div>
)}
</div>
)}
<ServerSortableTable
columns={columns}
data={rows}

View File

@@ -3794,7 +3794,7 @@ export interface DeleteBacktestsRequest {
}
export interface PaginatedBacktestsResponse {
backtests?: Backtest[] | null;
backtests?: LightBacktestResponse[] | null;
totalCount?: number;
currentPage?: number;
pageSize?: number;
@@ -3803,6 +3803,21 @@ export interface PaginatedBacktestsResponse {
hasPreviousPage?: boolean;
}
export interface LightBacktestResponse {
id?: string | null;
config?: TradingBotConfig | null;
finalPnl?: number;
winRate?: number;
growthPercentage?: number;
hodlPercentage?: number;
startDate?: Date;
endDate?: Date;
maxDrawdown?: number | null;
fees?: number;
sharpeRatio?: number | null;
score?: number;
}
export interface RunBacktestRequest {
config?: TradingBotConfigRequest | null;
startDate?: Date;

View File

@@ -600,7 +600,7 @@ export interface DeleteBacktestsRequest {
}
export interface PaginatedBacktestsResponse {
backtests?: Backtest[] | null;
backtests?: LightBacktestResponse[] | null;
totalCount?: number;
currentPage?: number;
pageSize?: number;
@@ -609,6 +609,21 @@ export interface PaginatedBacktestsResponse {
hasPreviousPage?: boolean;
}
export interface LightBacktestResponse {
id?: string | null;
config?: TradingBotConfig | null;
finalPnl?: number;
winRate?: number;
growthPercentage?: number;
hodlPercentage?: number;
startDate?: Date;
endDate?: Date;
maxDrawdown?: number | null;
fees?: number;
sharpeRatio?: number | null;
score?: number;
}
export interface RunBacktestRequest {
config?: TradingBotConfigRequest | null;
startDate?: Date;

View File

@@ -4,13 +4,13 @@ import {useQuery} from '@tanstack/react-query'
import useApiUrlStore from '../../app/store/apiStore'
import {
type Backtest,
BacktestClient,
GeneticCrossoverMethod,
GeneticMutationMethod,
type GeneticRequest,
GeneticSelectionMethod,
IndicatorType,
type LightBacktestResponse,
type PaginatedBacktestsResponse,
type RunGeneticRequest,
Ticker,
@@ -73,7 +73,7 @@ const BacktestGeneticBundle: React.FC = () => {
const [geneticRequests, setGeneticRequests] = useState<GeneticRequest[]>([])
const [selectedRequest, setSelectedRequest] = useState<GeneticRequest | null>(null)
const [isViewModalOpen, setIsViewModalOpen] = useState(false)
const [backtests, setBacktests] = useState<Backtest[]>([])
const [backtests, setBacktests] = useState<LightBacktestResponse[]>([])
const [isLoadingBacktests, setIsLoadingBacktests] = useState(false)
const [isFormCollapsed, setIsFormCollapsed] = useState(false)
@@ -824,7 +824,7 @@ const BacktestGeneticBundle: React.FC = () => {
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h3 className="card-title">Score vs Generation</h3>
<ScoreVsGeneration backtests={backtests} theme={theme}/>
<ScoreVsGeneration backtests={backtests as any} theme={theme}/>
</div>
</div>
</div>
@@ -834,7 +834,7 @@ const BacktestGeneticBundle: React.FC = () => {
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h3 className="card-title">Fitness vs Score vs Win Rate</h3>
<Fitness3DPlot backtests={backtests} theme={theme}/>
<Fitness3DPlot backtests={backtests as any} theme={theme}/>
</div>
</div>
@@ -842,18 +842,18 @@ const BacktestGeneticBundle: React.FC = () => {
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h3 className="card-title">Take Profit vs Stop Loss vs PnL</h3>
<TPvsSLvsPnL3DPlot backtests={backtests} theme={theme}/>
<TPvsSLvsPnL3DPlot backtests={backtests as any} theme={theme}/>
</div>
</div>
</div>
{/* Strategy Comparison Radar Chart */}
<div className="mb-6">
<IndicatorsComparison backtests={backtests}/>
<IndicatorsComparison backtests={backtests as any}/>
</div>
<BacktestTable
list={backtests}
list={backtests as any}
isFetching={false}
displaySummary={false}
onSortChange={handleSortChange}

View File

@@ -7,7 +7,7 @@ import useBacktestStore from '../../app/store/backtestStore'
import {Loader, Slider} from '../../components/atoms'
import {Modal, Toast} from '../../components/mollecules'
import {BacktestTable, UnifiedTradingModal} from '../../components/organism'
import type {Backtest} from '../../generated/ManagingApi'
import type {LightBacktestResponse} from '../../generated/ManagingApi'
import {BacktestClient} from '../../generated/ManagingApi'
const PAGE_SIZE = 50
@@ -27,7 +27,7 @@ const BacktestScanner: React.FC = () => {
sortBy: 'score',
sortOrder: 'desc'
})
const [backtests, setBacktests] = useState<Backtest[]>([])
const [backtests, setBacktests] = useState<LightBacktestResponse[]>([])
const [isLoading, setIsLoading] = useState(false)
const { apiUrl } = useApiUrlStore()
@@ -39,7 +39,7 @@ const BacktestScanner: React.FC = () => {
setIsLoading(true)
try {
const response = await backtestClient.backtest_GetBacktestsPaginated(page, PAGE_SIZE, sort.sortBy, sort.sortOrder)
setBacktests((response.backtests as Backtest[]) || [])
setBacktests((response.backtests as LightBacktestResponse[]) || [])
setTotalBacktests(response.totalCount || 0)
setTotalPages(response.totalPages || 0)
} catch (err) {
@@ -55,7 +55,7 @@ const BacktestScanner: React.FC = () => {
}, [currentPage, currentSort])
useEffect(() => {
setBacktestsFromStore(backtests)
setBacktestsFromStore(backtests as any) // Cast to any for backward compatibility
setLoading(isLoading)
}, [backtests, setBacktestsFromStore, setLoading, isLoading])
@@ -79,7 +79,7 @@ const BacktestScanner: React.FC = () => {
const filters = formData || filterValues
const filteredBacktests = backtests.filter((backtest: any) => {
const filteredBacktests = backtests.filter((backtest: LightBacktestResponse) => {
// Ensure values are numbers and handle potential null/undefined values
const backtestWinRate = Number(backtest.winRate) || 0
const backtestScore = Number(backtest.score) || 0
@@ -129,7 +129,7 @@ const BacktestScanner: React.FC = () => {
return
}
const backTestToDelete = backtests.filter((backtest: any) => {
const backTestToDelete = backtests.filter((backtest: LightBacktestResponse) => {
// Ensure values are numbers and handle potential null/undefined values
const backtestWinRate = Number(backtest.winRate) || 0
const backtestScore = Number(backtest.score) || 0
@@ -198,7 +198,7 @@ const BacktestScanner: React.FC = () => {
</div>
<BacktestTable
list={backtests}
list={backtests as LightBacktestResponse[]} // Cast to any for backward compatibility
isFetching={isLoading}
onSortChange={handleSortChange}
currentSort={currentSort}