From 8f8bdf8d692f27e2191ac37a9d2153a990dcb403 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Sun, 1 Jun 2025 14:59:55 +0700 Subject: [PATCH] Add average time --- src/Managing.Application/Bots/TradingBot.cs | 20 ++++++ src/Managing.Domain/Trades/Trade.cs | 7 +- .../organism/Backtest/backtestRowDetails.tsx | 67 ++++++++++++++++++- 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index 9d6fa63..679a4e4 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -799,6 +799,26 @@ public class TradingBot : Bot, ITradingBot { if (Positions.Any(p => p.Identifier == position.Identifier)) { + // Update the close date for the trade that actually closed the position + var currentCandle = Config.IsForBacktest + ? OptimizedCandles.LastOrDefault() + : ExchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow); + + if (currentCandle != null && position.ProfitAndLoss != null) + { + // Determine which trade closed the position based on realized P&L + if (position.ProfitAndLoss.Realized > 0) + { + // Profitable close = Take Profit + position.TakeProfit1.SetDate(currentCandle.Date); + } + else + { + // Loss or breakeven close = Stop Loss + position.StopLoss.SetDate(currentCandle.Date); + } + } + await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished); Logger.LogInformation( $"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss?.Realized}"); diff --git a/src/Managing.Domain/Trades/Trade.cs b/src/Managing.Domain/Trades/Trade.cs index 3166a82..0917d5f 100644 --- a/src/Managing.Domain/Trades/Trade.cs +++ b/src/Managing.Domain/Trades/Trade.cs @@ -23,7 +23,7 @@ namespace Managing.Domain.Trades public decimal Fee { get; set; } [Required] - public DateTime Date { get; } + public DateTime Date { get; private set; } [Required] public TradeDirection Direction { get; } [Required] @@ -46,6 +46,11 @@ namespace Managing.Domain.Trades Status = status; } + public void SetDate(DateTime date) + { + Date = date; + } + public void SetExchangeOrderId(string exchangeOrderId) { ExchangeOrderId = exchangeOrderId; diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx index dbd328c..a086692 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestRowDetails.tsx @@ -24,6 +24,63 @@ const BacktestRowDetails: React.FC = ({ config } = backtest; + // Helper function to calculate position open time in hours + const calculateOpenTimeInHours = (position: any) => { + const openDate = new Date(position.open.date); + let closeDate: Date | null = null; + + // Determine close date based on realized P&L (matching backend logic) + if (position.profitAndLoss?.realized != null) { + if (position.profitAndLoss.realized > 0) { + // Profitable close = Take Profit + closeDate = new Date(position.takeProfit1.date); + } else { + // Loss or breakeven close = Stop Loss + closeDate = new Date(position.stopLoss.date); + } + } + + if (closeDate) { + const diffInMs = closeDate.getTime() - openDate.getTime(); + return diffInMs / (1000 * 60 * 60); // Convert to hours + } + return 0; + }; + + // Calculate average open time for winning positions + const getAverageOpenTimeWinning = () => { + const winningPositions = positions.filter((p) => { + const realized = p.profitAndLoss?.realized ?? 0; + return realized > 0; + }); + + if (winningPositions.length === 0) return "0.00"; + + const totalHours = winningPositions.reduce((sum, position) => { + return sum + calculateOpenTimeInHours(position); + }, 0); + + const averageHours = totalHours / winningPositions.length; + return averageHours.toFixed(2); + }; + + // Calculate average open time for losing positions + const getAverageOpenTimeLosing = () => { + const losingPositions = positions.filter((p) => { + const realized = p.profitAndLoss?.realized ?? 0; + return realized <= 0; + }); + + if (losingPositions.length === 0) return "0.00"; + + const totalHours = losingPositions.reduce((sum, position) => { + return sum + calculateOpenTimeInHours(position); + }, 0); + + const averageHours = totalHours / losingPositions.length; + return averageHours.toFixed(2); + }; + return ( <>
@@ -81,8 +138,14 @@ const BacktestRowDetails: React.FC = ({ "SL: " + backtest.optimizedMoneyManagement?.stopLoss.toFixed(2) + "% TP: " + backtest.optimizedMoneyManagement?.takeProfit.toFixed(2) + "%" } > - - + +