diff --git a/src/Managing.Application/Abstractions/IBotService.cs b/src/Managing.Application/Abstractions/IBotService.cs index 4e26d00..8ad7358 100644 --- a/src/Managing.Application/Abstractions/IBotService.cs +++ b/src/Managing.Application/Abstractions/IBotService.cs @@ -1,6 +1,7 @@ using Managing.Common; using Managing.Domain.Bots; using Managing.Domain.MoneyManagements; +using Managing.Domain.Users; using Managing.Domain.Workflows; namespace Managing.Application.Abstractions; @@ -8,7 +9,7 @@ namespace Managing.Application.Abstractions; public interface IBotService { void SaveOrUpdateBotBackup(BotBackup botBackup); - void SaveOrUpdateBotBackup(string name, Enums.BotType botType, string data); + void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data); void AddSimpleBotToCache(IBot bot); void AddTradingBotToCache(ITradingBot bot); List GetActiveBots(); diff --git a/src/Managing.Application/Abstractions/ITradingBot.cs b/src/Managing.Application/Abstractions/ITradingBot.cs index b7ce598..3bc5ee6 100644 --- a/src/Managing.Application/Abstractions/ITradingBot.cs +++ b/src/Managing.Application/Abstractions/ITradingBot.cs @@ -1,5 +1,4 @@ using Managing.Core.FixedSizedQueue; -using Managing.Domain.Accounts; using Managing.Domain.Bots; using Managing.Domain.Candles; using Managing.Domain.MoneyManagements; @@ -28,6 +27,7 @@ namespace Managing.Application.Abstractions Dictionary WalletBalances { get; set; } Dictionary StrategiesValues { get; set; } User User { get; set; } + string Identifier { get; set; } Task Run(); Task ToggleIsForWatchOnly(); diff --git a/src/Managing.Application/Bots/SimpleBot.cs b/src/Managing.Application/Bots/SimpleBot.cs index c26b264..a392d25 100644 --- a/src/Managing.Application/Bots/SimpleBot.cs +++ b/src/Managing.Application/Bots/SimpleBot.cs @@ -45,7 +45,7 @@ namespace Managing.Application.Bots public override void SaveBackup() { var data = JsonConvert.SerializeObject(_workflow); - _botService.SaveOrUpdateBotBackup(Name, BotType.SimpleBot, data); + _botService.SaveOrUpdateBotBackup(User, Identifier, BotType.SimpleBot, data); } public override void LoadBackup(BotBackup backup) diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index 2ec66db..9fa4e08 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -146,7 +146,7 @@ public class TradingBot : Bot, ITradingBot public async Task LoadAccount() { - var account = await AccountService.GetAccount(AccountName, false, true); + var account = await AccountService.GetAccount(AccountName, false, false); if (account == null) { Logger.LogWarning($"No account found for this {AccountName}"); @@ -192,7 +192,7 @@ public class TradingBot : Bot, ITradingBot if (balance < Constants.GMX.Config.MinimumPositionAmount) { await LogWarning( - $"Balance on broker is below {Constants.GMX.Config.MinimumPositionAmount} USD (actual: {balance}). Stopping bot and saving backup."); + $"Balance on broker is below {Constants.GMX.Config.MinimumPositionAmount} USD (actual: {balance}). Stopping bot {Identifier} and saving backup."); SaveBackup(); Stop(); return; @@ -587,8 +587,8 @@ public class TradingBot : Bot, ITradingBot lastPrice, signalIdentifier: signal.Identifier); - var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService) - .Handle(command); + var position = (new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService) + .Handle(command)).Result; if (position != null) { @@ -665,12 +665,11 @@ public class TradingBot : Bot, ITradingBot } else { - var command = new ClosePositionCommand(position, lastPrice); + var command = new ClosePositionCommand(position, lastPrice, isForBacktest: IsForBacktest); try { - var closedPosition = - (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService) - .Handle(command)).Result; + var closedPosition = (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService) + .Handle(command)).Result; if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped)) { @@ -909,7 +908,7 @@ public class TradingBot : Bot, ITradingBot BotTradingBalance = BotTradingBalance, StartupTime = StartupTime, }; - BotService.SaveOrUpdateBotBackup(Name, BotType, JsonConvert.SerializeObject(data)); + BotService.SaveOrUpdateBotBackup(User, Identifier, BotType, JsonConvert.SerializeObject(data)); } public override void LoadBackup(BotBackup backup) @@ -925,6 +924,8 @@ public class TradingBot : Bot, ITradingBot AccountName = data.AccountName; IsForWatchingOnly = data.IsForWatchingOnly; BotTradingBalance = data.BotTradingBalance; + Identifier = backup.Identifier; + User = backup.User; // Restore the startup time if it was previously saved if (data.StartupTime != DateTime.MinValue) diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index 7c801d9..6590e18 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -6,6 +6,7 @@ using Managing.Application.Bots; using Managing.Common; using Managing.Domain.Bots; using Managing.Domain.MoneyManagements; +using Managing.Domain.Users; using Managing.Domain.Workflows; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -43,14 +44,14 @@ namespace Managing.Application.ManageBot await _botRepository.InsertBotAsync(botBackup); } - public BotBackup GetBotBackup(string name) + public BotBackup GetBotBackup(string identifier) { - return _botRepository.GetBots().FirstOrDefault(b => b.Name == name); + return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier); } - public void SaveOrUpdateBotBackup(string name, Enums.BotType botType, string data) + public void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data) { - var backup = GetBotBackup(name); + var backup = GetBotBackup(identifier); if (backup != null) { @@ -61,7 +62,8 @@ namespace Managing.Application.ManageBot { var botBackup = new BotBackup { - Name = name, + User = user, + Identifier = identifier, BotType = botType, Data = data }; @@ -88,13 +90,13 @@ namespace Managing.Application.ManageBot { var botTask = new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot); // Pass bot as the instance - _botTasks.AddOrUpdate(bot.GetName(), botTask, (key, existingVal) => botTask); + _botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask); } public void AddTradingBotToCache(ITradingBot bot) { var botTask = new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot); - _botTasks.AddOrUpdate(bot.GetName(), botTask, (key, existingVal) => botTask); + _botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask); } public List GetActiveBots() @@ -120,11 +122,11 @@ namespace Managing.Application.ManageBot switch (backupBot.BotType) { - case Enums.BotType.SimpleBot: - bot = CreateSimpleBot(backupBot.Name, - null); // Assuming null is an acceptable parameter for workflow - botTask = Task.Run(() => ((IBot)bot).Start()); - break; + // case Enums.BotType.SimpleBot: + // bot = CreateSimpleBot(backupBot.Name, + // null); // Assuming null is an acceptable parameter for workflow + // botTask = Task.Run(() => ((IBot)bot).Start()); + // break; case Enums.BotType.ScalpingBot: var scalpingBotData = JsonConvert.DeserializeObject(backupBot.Data); var scalpingMoneyManagement = @@ -132,7 +134,7 @@ namespace Managing.Application.ManageBot bot = CreateScalpingBot( scalpingBotData.AccountName, scalpingMoneyManagement, - backupBot.Name, + scalpingBotData.Name, scalpingBotData.Ticker, scalpingBotData.ScenarioName, scalpingBotData.Timeframe, @@ -147,7 +149,7 @@ namespace Managing.Application.ManageBot bot = CreateFlippingBot( flippingBotData.AccountName, flippingMoneyManagement, - backupBot.Name, + flippingBotData.Name, flippingBotData.Ticker, flippingBotData.ScenarioName, flippingBotData.Timeframe, @@ -160,7 +162,7 @@ namespace Managing.Application.ManageBot if (bot != null && botTask != null) { var botWrapper = new BotTaskWrapper(botTask, bot.GetType(), bot); - _botTasks.AddOrUpdate(backupBot.Name, botWrapper, (key, existingVal) => botWrapper); + _botTasks.AddOrUpdate(backupBot.Identifier, botWrapper, (key, existingVal) => botWrapper); } } diff --git a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs index a068e3a..ba88a1a 100644 --- a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs @@ -1,8 +1,8 @@ -using MediatR; -using static Managing.Common.Enums; -using Managing.Application.Abstractions; +using Managing.Application.Abstractions; using Managing.Core; +using MediatR; using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; namespace Managing.Application.ManageBot; @@ -19,67 +19,71 @@ public class LoadBackupBotCommandHandler : IRequestHandler Handle(LoadBackupBotCommand request, CancellationToken cancellationToken) -{ - var backupBots = _botService.GetSavedBots().ToList(); - _logger.LogInformation("Loading {Count} backup bots.", backupBots.Count); - - var result = new Dictionary(); - bool anyBackupStarted = false; - bool anyBotActive = false; - - foreach (var backupBot in backupBots) { - try + var backupBots = _botService.GetSavedBots().ToList(); + _logger.LogInformation("Loading {Count} backup bots.", backupBots.Count); + + var result = new Dictionary(); + bool anyBackupStarted = false; + bool anyBotActive = false; + + foreach (var backupBot in backupBots) { - var activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.GetName() == backupBot.Name); - - if (activeBot == null) + try { - _logger.LogInformation("No active instance found for bot {BotName}. Starting backup...", backupBot.Name); - _botService.StartBotFromBackup(backupBot); + var activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.Identifier == backupBot.Identifier); - activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.GetName() == backupBot.Name); - if (activeBot != null) + if (activeBot == null) { - result[activeBot.GetName()] = BotStatus.Backup; - anyBackupStarted = true; - _logger.LogInformation("Backup bot {BotName} started successfully.", backupBot.Name); + _logger.LogInformation("No active instance found for bot {Identifier}. Starting backup...", + backupBot.Identifier); + _botService.StartBotFromBackup(backupBot); + + activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.Identifier == backupBot.Identifier); + if (activeBot != null) + { + result[activeBot.Identifier] = BotStatus.Backup; + anyBackupStarted = true; + _logger.LogInformation("Backup bot {Identifier} started successfully.", backupBot.Identifier); + } + else + { + result[backupBot.Identifier] = BotStatus.Down; + _logger.LogWarning("Backup bot {Identifier} failed to start.", backupBot.Identifier); + } } else { - result[backupBot.Name] = BotStatus.Down; - _logger.LogWarning("Backup bot {BotName} failed to start.", backupBot.Name); + var status = MiscExtensions.ParseEnum(activeBot.GetStatus()); + result[activeBot.Identifier] = status; + anyBotActive = true; + _logger.LogInformation("Bot {Identifier} is already active with status {Status}.", + activeBot.Identifier, + status); } } - else + catch (Exception ex) { - var status = MiscExtensions.ParseEnum(activeBot.GetStatus()); - result[activeBot.GetName()] = status; - anyBotActive = true; - _logger.LogInformation("Bot {BotName} is already active with status {Status}.", activeBot.GetName(), status); + _logger.LogError(ex, "Error loading bot {Identifier}. Deleting its backup.", backupBot.Identifier); + _botService.DeleteBotBackup(backupBot.Identifier); + result[backupBot.Identifier] = BotStatus.Down; } } - catch (Exception ex) - { - _logger.LogError(ex, "Error loading bot {BotName}. Deleting its backup.", backupBot.Name); - _botService.DeleteBotBackup(backupBot.Name); - result[backupBot.Name] = BotStatus.Down; - } + + var summary = string.Join(", ", result.Select(b => $"{b.Key}: {b.Value}")); + _logger.LogInformation("Bot loading completed. Summary: {Summary}", summary); + + // Determine final status + BotStatus finalStatus = anyBackupStarted + ? BotStatus.Backup + : anyBotActive + ? BotStatus.Up + : BotStatus.Down; + + _logger.LogInformation("Final aggregate bot status: {FinalStatus}", finalStatus); + + return Task.FromResult(finalStatus.ToString()); } - - var summary = string.Join(", ", result.Select(b => $"{b.Key}: {b.Value}")); - _logger.LogInformation("Bot loading completed. Summary: {Summary}", summary); - - // Determine final status - BotStatus finalStatus = anyBackupStarted - ? BotStatus.Backup - : anyBotActive ? BotStatus.Up : BotStatus.Down; - - _logger.LogInformation("Final aggregate bot status: {FinalStatus}", finalStatus); - - return Task.FromResult(finalStatus.ToString()); -} - } public class LoadBackupBotCommand : IRequest diff --git a/src/Managing.Application/Trading/ClosePositionCommandHandler.cs b/src/Managing.Application/Trading/ClosePositionCommandHandler.cs index e38b0f2..f7daa05 100644 --- a/src/Managing.Application/Trading/ClosePositionCommandHandler.cs +++ b/src/Managing.Application/Trading/ClosePositionCommandHandler.cs @@ -27,14 +27,14 @@ public class ClosePositionCommandHandler( return request.Position; } - var isForPaperTrading = request.Position.Initiator == PositionInitiator.PaperTrading; + var isForPaperTrading = request.IsForBacktest; var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading ? request.ExecutionPrice.GetValueOrDefault() : exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow); // Check if position still open - if (!isForPaperTrading) + if (!request.IsForBacktest) { var p = (await exchangeService.GetBrokerPositions(account)) .FirstOrDefault(x => x.Ticker == request.Position.Ticker); @@ -65,17 +65,19 @@ public class ClosePositionCommandHandler( request.Position.ProfitAndLoss = TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice, request.Position.Open.Leverage); - tradingService.UpdatePosition(request.Position); + + if (!request.IsForBacktest) + tradingService.UpdatePosition(request.Position); } return request.Position; } catch (Exception ex) { - // Log the error - regardless of the error type - logger?.LogError(ex, "Error closing position: {Message}", ex.Message); + logger?.LogError(ex, "Error closing position: {Message} \n Stacktrace : {StackTrace}", ex.Message, + ex.StackTrace); - throw new Exception(ex.Message); + throw; } } } \ No newline at end of file diff --git a/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs b/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs index ee70784..7939c40 100644 --- a/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs +++ b/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs @@ -5,13 +5,15 @@ namespace Managing.Application.Trading.Commands { public class ClosePositionCommand : IRequest { - public ClosePositionCommand(Position position, decimal? executionPrice = null) + public ClosePositionCommand(Position position, decimal? executionPrice = null, bool isForBacktest = false) { Position = position; ExecutionPrice = executionPrice; + IsForBacktest = isForBacktest; } - public Position Position { get; } + public Position Position { get; } public decimal? ExecutionPrice { get; set; } + public bool IsForBacktest { get; set; } } -} +} \ No newline at end of file diff --git a/src/Managing.Domain/Bots/BotBackup.cs b/src/Managing.Domain/Bots/BotBackup.cs index b90221a..63313b3 100644 --- a/src/Managing.Domain/Bots/BotBackup.cs +++ b/src/Managing.Domain/Bots/BotBackup.cs @@ -1,10 +1,12 @@ +using Managing.Domain.Users; using static Managing.Common.Enums; namespace Managing.Domain.Bots; public class BotBackup { - public string Name { get; set; } public BotType BotType { get; set; } + public string Identifier { get; set; } + public User User { get; set; } public string Data { 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 b69bc11..679efb0 100644 --- a/src/Managing.Domain/Bots/IBot.cs +++ b/src/Managing.Domain/Bots/IBot.cs @@ -8,12 +8,15 @@ void Restart(); string GetStatus(); string GetName(); + /// /// Gets the total runtime of the bot since it was started /// /// TimeSpan representing the runtime, or TimeSpan.Zero if the bot is not running TimeSpan GetRuntime(); + + string Identifier { get; set; } void SaveBackup(); void LoadBackup(BotBackup backup); } -} +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/BotRepository.cs b/src/Managing.Infrastructure.Database/BotRepository.cs index b511f9a..dcfa87c 100644 --- a/src/Managing.Infrastructure.Database/BotRepository.cs +++ b/src/Managing.Infrastructure.Database/BotRepository.cs @@ -28,7 +28,7 @@ public class BotRepository : IBotRepository public async Task UpdateBackupBot(BotBackup bot) { - var b = await _botRepository.FindOneAsync(b => b.Name == bot.Name); + var b = await _botRepository.FindOneAsync(b => b.Identifier == bot.Identifier); var dto = MongoMappers.Map(bot); dto.Id = b.Id; _botRepository.Update(dto); diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs index ab7303a..51ad164 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs @@ -10,4 +10,6 @@ public class BotDto : Document public string Name { get; set; } public string Data { get; set; } public BotType BotType { get; set; } + public string Identifier { get; set; } + public UserDto User { get; set; } } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs index 1083227..9c941bf 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs @@ -720,7 +720,8 @@ public static class MongoMappers return new BotDto { - Name = bot.Name, + User = Map(bot.User), + Identifier = bot.Identifier, BotType = bot.BotType, Data = bot.Data, }; @@ -732,7 +733,8 @@ public static class MongoMappers return new BotBackup { - Name = b.Name, + User = Map(b.User), + Identifier = b.Identifier, BotType = b.BotType, Data = b.Data }; diff --git a/src/Managing.Infrastructure.Database/TradingRepository.cs b/src/Managing.Infrastructure.Database/TradingRepository.cs index 71c2050..e3f07c0 100644 --- a/src/Managing.Infrastructure.Database/TradingRepository.cs +++ b/src/Managing.Infrastructure.Database/TradingRepository.cs @@ -197,7 +197,7 @@ public class TradingRepository : ITradingRepository public void UpdatePosition(Position position) { - var p = _positionRepository.FindOne(p => p.Open.ExchangeOrderId == position.Open.ExchangeOrderId); + var p = _positionRepository.FindOne(p => p.Identifier.Equals(position.Identifier)); var dto = MongoMappers.Map(position); dto.Id = p.Id; _positionRepository.Update(dto);