diff --git a/src/Managing.Api/Controllers/BotController.cs b/src/Managing.Api/Controllers/BotController.cs index c81aa26..d3cabe1 100644 --- a/src/Managing.Api/Controllers/BotController.cs +++ b/src/Managing.Api/Controllers/BotController.cs @@ -505,7 +505,9 @@ public class BotController : BaseController ProfitAndLoss = item.GetProfitAndLoss(), Identifier = item.Identifier, AgentName = item.User.AgentName, - Config = item.Config // Contains all configuration properties + Config = item.Config, + CreateDate = item.CreateDate, + StartupTime = item.StartupTime }); } diff --git a/src/Managing.Api/Models/Responses/TradingBotResponse.cs b/src/Managing.Api/Models/Responses/TradingBotResponse.cs index c13f742..73a1956 100644 --- a/src/Managing.Api/Models/Responses/TradingBotResponse.cs +++ b/src/Managing.Api/Models/Responses/TradingBotResponse.cs @@ -52,5 +52,15 @@ namespace Managing.Api.Models.Responses /// The full trading bot configuration /// [Required] public TradingBotConfig Config { get; internal set; } + + /// + /// The time when the bot was created + /// + [Required] public DateTime CreateDate { get; internal set; } + + /// + /// The time when the bot was started + /// + [Required] public DateTime StartupTime { get; internal set; } } } \ No newline at end of file diff --git a/src/Managing.Application/Abstractions/ITradingBot.cs b/src/Managing.Application/Abstractions/ITradingBot.cs index 78344f6..f862d5d 100644 --- a/src/Managing.Application/Abstractions/ITradingBot.cs +++ b/src/Managing.Application/Abstractions/ITradingBot.cs @@ -21,6 +21,7 @@ namespace Managing.Application.Abstractions Dictionary WalletBalances { get; set; } Dictionary IndicatorsValues { get; set; } DateTime StartupTime { get; set; } + DateTime CreateDate { get; } DateTime PreloadSince { get; set; } int PreloadedCandlesCount { get; set; } decimal Fee { get; set; } diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index 4022fd7..21f86e7 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -114,7 +114,12 @@ public class TradingBot : Bot, ITradingBot CancelAllOrders().GetAwaiter().GetResult(); // Send startup message only for fresh starts (not reboots) - var isReboot = Signals.Any() || Positions.Any(); + // Consider it a reboot if the bot was created more than 5 minutes ago + var timeSinceCreation = DateTime.UtcNow - CreateDate; + var isReboot = timeSinceCreation.TotalMinutes > 5; + + StartupTime = DateTime.UtcNow; + if (!isReboot) { try @@ -361,38 +366,37 @@ public class TradingBot : Bot, ITradingBot // TODO : remove this when Synth is stable // signal.Status = SignalStatus.Expired; signalText += $"\n\n🚫 *Synth Signal Filter*\n" + - $"Signal `{signal.Identifier}` blocked by Synth risk assessment\n\n" + - $"šŸ“Š *Risk Analysis Details*\n" + - $"SL Probability: `{signalValidationResult.StopLossProbability:P2}`\n" + - $"TP Probability: `{signalValidationResult.TakeProfitProbability:P2}`\n" + - $"TP/SL Ratio: `{signalValidationResult.TpSlRatio:F2}x`\n" + - $"Win/Loss: `{signalValidationResult.WinLossRatio:F2}:1`\n" + - $"Expected Value: `${signalValidationResult.ExpectedMonetaryValue:F2}`\n" + - $"Expected Utility: `{signalValidationResult.ExpectedUtility:F4}`\n" + - $"Kelly Criterion: `{signalValidationResult.KellyFraction:P2}`\n" + - $"Kelly Capped: `{signalValidationResult.KellyCappedFraction:P2}`\n" + - $"Risk Assessment: `{signalValidationResult.GetUtilityRiskAssessment()}`\n" + - $"Time Horizon: `{signalValidationResult.TimeHorizonSeconds / 3600:F1}h`\n\n" + - $"šŸ“‹ *Context*\n`{signalValidationResult.ValidationContext}`"; - + $"Signal `{signal.Identifier}` blocked by Synth risk assessment\n\n" + + $"šŸ“Š *Risk Analysis Details*\n" + + $"SL Probability: `{signalValidationResult.StopLossProbability:P2}`\n" + + $"TP Probability: `{signalValidationResult.TakeProfitProbability:P2}`\n" + + $"TP/SL Ratio: `{signalValidationResult.TpSlRatio:F2}x`\n" + + $"Win/Loss: `{signalValidationResult.WinLossRatio:F2}:1`\n" + + $"Expected Value: `${signalValidationResult.ExpectedMonetaryValue:F2}`\n" + + $"Expected Utility: `{signalValidationResult.ExpectedUtility:F4}`\n" + + $"Kelly Criterion: `{signalValidationResult.KellyFraction:P2}`\n" + + $"Kelly Capped: `{signalValidationResult.KellyCappedFraction:P2}`\n" + + $"Risk Assessment: `{signalValidationResult.GetUtilityRiskAssessment()}`\n" + + $"Time Horizon: `{signalValidationResult.TimeHorizonSeconds / 3600:F1}h`\n\n" + + $"šŸ“‹ *Context*\n`{signalValidationResult.ValidationContext}`"; } else { signal.SetConfidence(signalValidationResult.Confidence); signalText += $"\n\nāœ… *Synth Risk Assessment Passed*\n" + - $"Confidence: `{signalValidationResult.Confidence}`\n\n" + - $"šŸ“Š *Risk Analysis Details*\n" + - $"SL Probability: `{signalValidationResult.StopLossProbability:P2}`\n" + - $"TP Probability: `{signalValidationResult.TakeProfitProbability:P2}`\n" + - $"TP/SL Ratio: `{signalValidationResult.TpSlRatio:F2}x`\n" + - $"Win/Loss: `{signalValidationResult.WinLossRatio:F2}:1`\n" + - $"Expected Value: `${signalValidationResult.ExpectedMonetaryValue:F2}`\n" + - $"Expected Utility: `{signalValidationResult.ExpectedUtility:F4}`\n" + - $"Kelly Criterion: `{signalValidationResult.KellyFraction:P2}`\n" + - $"Kelly Capped: `{signalValidationResult.KellyCappedFraction:P2}`\n" + - $"Risk Assessment: `{signalValidationResult.GetUtilityRiskAssessment()}`\n" + - $"Time Horizon: `{signalValidationResult.TimeHorizonSeconds / 3600:F1}h`\n\n" + - $"šŸ“‹ *Context*\n`{signalValidationResult.ValidationContext}`"; + $"Confidence: `{signalValidationResult.Confidence}`\n\n" + + $"šŸ“Š *Risk Analysis Details*\n" + + $"SL Probability: `{signalValidationResult.StopLossProbability:P2}`\n" + + $"TP Probability: `{signalValidationResult.TakeProfitProbability:P2}`\n" + + $"TP/SL Ratio: `{signalValidationResult.TpSlRatio:F2}x`\n" + + $"Win/Loss: `{signalValidationResult.WinLossRatio:F2}:1`\n" + + $"Expected Value: `${signalValidationResult.ExpectedMonetaryValue:F2}`\n" + + $"Expected Utility: `{signalValidationResult.ExpectedUtility:F4}`\n" + + $"Kelly Criterion: `{signalValidationResult.KellyFraction:P2}`\n" + + $"Kelly Capped: `{signalValidationResult.KellyCappedFraction:P2}`\n" + + $"Risk Assessment: `{signalValidationResult.GetUtilityRiskAssessment()}`\n" + + $"Time Horizon: `{signalValidationResult.TimeHorizonSeconds / 3600:F1}h`\n\n" + + $"šŸ“‹ *Context*\n`{signalValidationResult.ValidationContext}`"; } } @@ -1429,7 +1433,8 @@ public class TradingBot : Bot, ITradingBot Signals = Signals, Positions = Positions, WalletBalances = WalletBalances, - StartupTime = StartupTime + StartupTime = StartupTime, + CreateDate = CreateDate }; BotService.SaveOrUpdateBotBackup(User, Identifier, Status, JsonConvert.SerializeObject(data)); } @@ -1449,6 +1454,7 @@ public class TradingBot : Bot, ITradingBot Positions = data.Positions ?? new List(); WalletBalances = data.WalletBalances ?? new Dictionary(); PreloadSince = data.StartupTime; + CreateDate = data.CreateDate; Identifier = backup.Identifier; User = backup.User; Status = backup.LastStatus; @@ -1718,7 +1724,6 @@ public class TradingBot : Bot, ITradingBot if (allowNameChange && !string.IsNullOrEmpty(newConfig.Name)) { Name = newConfig.Name; - Identifier = newConfig.Name; } // If account changed, reload it @@ -1893,4 +1898,9 @@ public class TradingBotBackup /// Runtime state: When the bot was started /// public DateTime StartupTime { get; set; } + + /// + /// Runtime state: When the bot was created + /// + public DateTime CreateDate { get; set; } } \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index 5690e22..cee6b69 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -244,12 +244,20 @@ namespace Managing.Application.ManageBot { if (botWrapper.BotInstance is IBot bot) { + // Stop the bot first to ensure clean state + bot.Stop(); + + // Small delay to ensure stop is complete + await Task.Delay(100); + + // Restart the bot (this will update StartupTime) bot.Restart(); var restartMessage = $"šŸ”„ **Bot Restarted**\n\n" + $"šŸŽÆ **Agent:** {bot.User.AgentName}\n" + $"šŸ¤– **Bot Name:** {bot.Name}\n" + - $"ā° **Restarted At:** {DateTime.UtcNow:MMM dd, yyyy • HH:mm:ss} UTC\n\n" + + $"ā° **Restarted At:** {DateTime.UtcNow:MMM dd, yyyy • HH:mm:ss} UTC\n" + + $"šŸ• **New Startup Time:** {bot.StartupTime:MMM dd, yyyy • HH:mm:ss} UTC\n\n" + $"šŸš€ **Bot has been successfully restarted and is now active.**"; await _messengerService.SendTradeMessage(restartMessage, false, bot.User); diff --git a/src/Managing.Application/Shared/MessengerService.cs b/src/Managing.Application/Shared/MessengerService.cs index c43fe5a..6e2c60b 100644 --- a/src/Managing.Application/Shared/MessengerService.cs +++ b/src/Managing.Application/Shared/MessengerService.cs @@ -207,7 +207,7 @@ public class MessengerService : IMessengerService var finalPnl = backtest.FinalPnl; var growthPercentage = backtest.GrowthPercentage; var maxDrawdown = backtest.Statistics?.MaxDrawdownPc ?? 0; - var sharpeRatio = backtest.Statistics?.SharpeRatio ?? 0; + var sharpeRatio = (backtest.Statistics?.SharpeRatio * 100) ?? 0; return $"šŸ“ˆ Performance Metrics:\n" + $"⭐ Score: {score:F1}/100 | šŸ† Win Rate: {winRate:F1}%\n" + diff --git a/src/Managing.Domain/Bots/Bot.cs b/src/Managing.Domain/Bots/Bot.cs index 18c3f8f..7f5194f 100644 --- a/src/Managing.Domain/Bots/Bot.cs +++ b/src/Managing.Domain/Bots/Bot.cs @@ -17,9 +17,14 @@ namespace Managing.Domain.Bots public User User { get; set; } /// - /// The time when the bot was started + /// The time when the bot was first started (creation date) /// - public DateTime StartupTime { get; private set; } + public DateTime StartupTime { get; protected set; } + + /// + /// The time when the bot was created + /// + public DateTime CreateDate { get; protected set; } private CancellationTokenSource CancellationToken { get; set; } @@ -31,13 +36,14 @@ namespace Managing.Domain.Bots CancellationToken = new CancellationTokenSource(); ExecutionCount = 0; Interval = 3000; - StartupTime = DateTime.MinValue; // Initialize with minimum value to indicate it hasn't been started yet + CreateDate = DateTime.UtcNow; // Set the creation time when the bot is instantiated + StartupTime = DateTime.UtcNow; // Set the startup time to creation date initially } public virtual void Start() { Status = BotStatus.Up; - StartupTime = DateTime.UtcNow; // Record the startup time when the bot is started + // StartupTime remains unchanged on first start (it's already set to creation date) } public async Task InitWorker(Func action) @@ -105,7 +111,7 @@ namespace Managing.Domain.Bots /// TimeSpan representing the runtime, or TimeSpan.Zero if the bot is not running public TimeSpan GetRuntime() { - if (Status != BotStatus.Up || StartupTime == DateTime.MinValue) + if (Status != BotStatus.Up) return TimeSpan.Zero; return DateTime.UtcNow - StartupTime; diff --git a/src/Managing.Domain/Bots/BotBackup.cs b/src/Managing.Domain/Bots/BotBackup.cs index 8b19f78..1f56f14 100644 --- a/src/Managing.Domain/Bots/BotBackup.cs +++ b/src/Managing.Domain/Bots/BotBackup.cs @@ -9,4 +9,5 @@ public class BotBackup public User User { get; set; } public string Data { get; set; } public BotStatus LastStatus { get; set; } + public DateTime CreateDate { get; set; } } \ No newline at end of file diff --git a/src/Managing.Domain/Bots/IBot.cs b/src/Managing.Domain/Bots/IBot.cs index d821d0d..0ef3c11 100644 --- a/src/Managing.Domain/Bots/IBot.cs +++ b/src/Managing.Domain/Bots/IBot.cs @@ -18,6 +18,11 @@ namespace Managing.Domain.Bots /// TimeSpan representing the runtime, or TimeSpan.Zero if the bot is not running TimeSpan GetRuntime(); + /// + /// The time when the bot was first started (creation date) + /// + DateTime StartupTime { get; } + string Identifier { get; set; } void SaveBackup(); void LoadBackup(BotBackup backup); diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts index a494417..ea16f34 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -3533,6 +3533,8 @@ export interface TradingBotResponse { identifier: string; agentName: string; config: TradingBotConfig; + createDate: Date; + startupTime: Date; } export interface OpenPositionManuallyRequest { diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index a9ddae1..b08726f 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -673,6 +673,8 @@ export interface TradingBotResponse { identifier: string; agentName: string; config: TradingBotConfig; + createDate: Date; + startupTime: Date; } export interface OpenPositionManuallyRequest {