From e59898bccb6917ddfa14096aefd301f25b7a6908 Mon Sep 17 00:00:00 2001 From: Crypto Od Date: Sun, 2 Mar 2025 18:24:50 +0100 Subject: [PATCH] Update bot workflow --- .../Managing.Api.Workers.csproj | 3 + src/Managing.Api.Workers/Program.cs | 8 +- .../Workers/PositionManagerWorker.cs | 90 ++++++++------- .../appsettings.ProdLocal.json | 24 ++++ src/Managing.Api/Managing.Api.csproj | 3 + src/Managing.Api/Program.cs | 1 + src/Managing.Api/appsettings.ProdLocal.json | 24 ++++ src/Managing.Application/Bots/TradingBot.cs | 20 ++-- .../ManageBot/BotService.cs | 11 +- .../ManageBot/LoadBackupBotCommandHandler.cs | 106 +++++++++--------- .../Trading/OpenPositionCommandHandler.cs | 6 +- src/Managing.Application/Users/UserService.cs | 9 +- .../Services/Gmx/GmxV2Service.cs | 5 +- 13 files changed, 198 insertions(+), 112 deletions(-) create mode 100644 src/Managing.Api.Workers/appsettings.ProdLocal.json create mode 100644 src/Managing.Api/appsettings.ProdLocal.json diff --git a/src/Managing.Api.Workers/Managing.Api.Workers.csproj b/src/Managing.Api.Workers/Managing.Api.Workers.csproj index 2e99aee..55d4c7a 100644 --- a/src/Managing.Api.Workers/Managing.Api.Workers.csproj +++ b/src/Managing.Api.Workers/Managing.Api.Workers.csproj @@ -41,5 +41,8 @@ Always + + Always + diff --git a/src/Managing.Api.Workers/Program.cs b/src/Managing.Api.Workers/Program.cs index 224046a..20d39be 100644 --- a/src/Managing.Api.Workers/Program.cs +++ b/src/Managing.Api.Workers/Program.cs @@ -107,16 +107,16 @@ builder.Services.AddSwaggerGen(options => }); builder.WebHost.SetupDiscordBot(); -builder.Services.AddHostedService(); +// builder.Services.AddHostedService(); // builder.Services.AddHostedService(); // builder.Services.AddHostedService(); // builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); -// builder.Services.AddHostedService(); +builder.Services.AddHostedService(); builder.Services.AddHostedService(); -builder.Services.AddHostedService(); -builder.Services.AddHostedService(); +// builder.Services.AddHostedService(); +// builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); diff --git a/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs b/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs index a0e60da..19f4483 100644 --- a/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs +++ b/src/Managing.Api.Workers/Workers/PositionManagerWorker.cs @@ -166,49 +166,63 @@ public class PositionManagerWorker : BaseWorker { var positions = GetPositions(PositionStatus.New); - _logger.LogInformation("New positions count : {0} ", positions.Count()); + _logger.LogInformation("Processing {NewPositionCount} new positions", positions.Count()); foreach (var position in positions) { - _logger.LogInformation("Managing position: {0} - Date: {1} - Direction: {2} - Ticker: {3}", - position.Identifier, position.Date, position.OriginDirection, position.Ticker); - position.Status = PositionStatus.Updating; - _tradingService.UpdatePosition(position); - - // Update status if position is open since to long - if (position.Date < DateTime.UtcNow.AddDays(-2)) + using (_logger.BeginScope("Position {SignalIdentifier}", position.SignalIdentifier)) { - position.Status = PositionStatus.Canceled; - _tradingService.UpdatePosition(position); - _logger.LogInformation($"|_ Position is now Canceled"); - continue; + try + { + // Immediate status update for concurrency protection + _logger.LogDebug("[{SignalIdentifier}] Acquiring position lock via status update", position.SignalIdentifier); + position.Status = PositionStatus.Updating; + _tradingService.UpdatePosition(position); + + var account = await GetAccount(position.AccountName); + var trade = await _exchangeService.GetTrade(account.Key, position.Open.ExchangeOrderId, position.Ticker); + var openTrade = position.Open; + + if (trade.Status == TradeStatus.PendingOpen || trade.Status == TradeStatus.Requested) + { + // Position staleness check + if (position.Date < DateTime.UtcNow.AddDays(-1)) + { + position.Status = PositionStatus.Canceled; + _tradingService.UpdatePosition(position); + _logger.LogWarning("[{SignalIdentifier}] Position canceled - stale since {PositionAge} days", + position.SignalIdentifier, + (DateTime.UtcNow - position.Date).TotalDays); + } + else + { + // Reset status for retry + position.Status = PositionStatus.New; + _tradingService.UpdatePosition(position); + _logger.LogInformation("[{SignalIdentifier}] Awaiting order fill - {Ticker} (0/{ExpectedQuantity})", + position.SignalIdentifier, + position.Ticker, openTrade.Quantity); + } + } + else + { + position.Status = PositionStatus.PartiallyFilled; + position.Open = openTrade; + // Position is now open, now waiting to open SLTP + _tradingService.UpdatePosition(position); + + _logger.LogInformation("[{SignalIdentifier}] Position now open ", + position.SignalIdentifier); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing position {SignalIdentifier}", position.SignalIdentifier); + // Consider resetting to New status for retry if needed + position.Status = PositionStatus.New; + _tradingService.UpdatePosition(position); + } } - - var account = await GetAccount(position.AccountName); - var currentOpenOrders = await _exchangeService.GetOpenOrders(account, position.Ticker); - // if (currentOpenOrders.Any()) - // { - // position.Status = PositionStatus.Canceled; - // _tradingService.UpdatePosition(position); - // _logger.LogInformation($"|_ Position is now Canceled - Position close from exchange"); - // continue; - // } - - var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker); - - if (quantityInPosition <= 0) - { - position.Status = PositionStatus.New; - _logger.LogInformation("|_ Position is currently waiting for filling"); - } - else - { - position.Open.SetStatus(TradeStatus.Filled); - position.Status = PositionStatus.PartiallyFilled; - _logger.LogInformation($"|_ Position is now PartiallyFilled"); - } - - _tradingService.UpdatePosition(position); } } diff --git a/src/Managing.Api.Workers/appsettings.ProdLocal.json b/src/Managing.Api.Workers/appsettings.ProdLocal.json new file mode 100644 index 0000000..bac1dba --- /dev/null +++ b/src/Managing.Api.Workers/appsettings.ProdLocal.json @@ -0,0 +1,24 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "https://influx-db.apps.managing.live", + "Organization": "managing-org", + "Token": "eOuXcXhH7CS13Iw4CTiDDpRjIjQtEVPOloD82pLPOejI4n0BsEj1YzUw0g3Cs1mdDG5m-RaxCavCMsVTtS5wIQ==" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Api/Managing.Api.csproj b/src/Managing.Api/Managing.Api.csproj index 843053a..950da1c 100644 --- a/src/Managing.Api/Managing.Api.csproj +++ b/src/Managing.Api/Managing.Api.csproj @@ -42,5 +42,8 @@ Always + + Always + diff --git a/src/Managing.Api/Program.cs b/src/Managing.Api/Program.cs index 9f28080..d3511c6 100644 --- a/src/Managing.Api/Program.cs +++ b/src/Managing.Api/Program.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using Managing.Api.Authorization; using Managing.Api.Exceptions; using Managing.Api.Filters; +using Managing.Api.Workers; using Managing.Application.Hubs; using Managing.Bootstrap; using Managing.Common; diff --git a/src/Managing.Api/appsettings.ProdLocal.json b/src/Managing.Api/appsettings.ProdLocal.json new file mode 100644 index 0000000..bac1dba --- /dev/null +++ b/src/Managing.Api/appsettings.ProdLocal.json @@ -0,0 +1,24 @@ +{ + "ManagingDatabase": { + "ConnectionString": "mongodb://admin:r8oJiDIKbsEi@mongo-db.apps.managing.live:27017/?authMechanism=SCRAM-SHA-256", + "DatabaseName": "ManagingDb" + }, + "InfluxDb": { + "Url": "https://influx-db.apps.managing.live", + "Organization": "managing-org", + "Token": "eOuXcXhH7CS13Iw4CTiDDpRjIjQtEVPOloD82pLPOejI4n0BsEj1YzUw0g3Cs1mdDG5m-RaxCavCMsVTtS5wIQ==" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "System": "Warning" + } + } + }, + "ElasticConfiguration": { + "Uri": "http://elasticsearch:9200" + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index ebca3f4..ff06e14 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -111,8 +111,10 @@ public class TradingBot : Bot, ITradingBot try { - await MessengerService.SendMessage( - $"Hi everyone, I'm going to run {Name}. \nI will send a message here everytime a signal is triggered by the {string.Join(",", Strategies.Select(s => s.Name))} strategies."); + // await MessengerService.SendMessage( + // $"Hey everyone! I'm about to start {Name}. 🚀\n" + + // $"I'll post an update here each time a signal is triggered by the following strategies: {string.Join(", ", Strategies.Select(s => s.Name))}." + // ); } catch (Exception ex) { @@ -185,7 +187,7 @@ public class TradingBot : Bot, ITradingBot if (!IsForWatchingOnly) await ManagePositions(); - + if (!IsForBacktest) { SaveBackup(); @@ -414,11 +416,11 @@ public class TradingBot : Bot, ITradingBot await LogWarning($"Open position trade is rejected for signal {signal.Identifier}"); // if position is not open // Re-open the trade for the signal only if signal still up - //if (signal.Status == SignalStatus.PositionOpen) - //{ - // Logger.LogInformation($"Try to re-open position"); - // OpenPosition(signal); - //} + if (signal.Status == SignalStatus.PositionOpen) + { + Logger.LogInformation($"Try to re-open position"); + await OpenPosition(signal); + } } } catch (Exception ex) @@ -495,7 +497,7 @@ public class TradingBot : Bot, ITradingBot MoneyManagement, signal.Direction, Ticker, - PositionInitiator.Bot, + IsForBacktest ? PositionInitiator.PaperTrading : PositionInitiator.Bot , signal.Date, IsForBacktest, lastPrice, diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index bd266eb..0f1c11e 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -131,7 +131,7 @@ namespace Managing.Application.ManageBot scalpingBotData.ScenarioName, scalpingBotData.Timeframe, scalpingBotData.IsForWatchingOnly); - botTask = Task.Run(() => ((ITradingBot)bot).Start()); + botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot)); break; case Enums.BotType.FlippingBot: var flippingBotData = JsonConvert.DeserializeObject(backupBot.Data); @@ -143,7 +143,7 @@ namespace Managing.Application.ManageBot flippingBotData.ScenarioName, flippingBotData.Timeframe, flippingBotData.IsForWatchingOnly); - botTask = Task.Run(() => ((ITradingBot)bot).Start()); + botTask = Task.Run(InitBot((ITradingBot)bot, backupBot)); break; } @@ -154,6 +154,13 @@ namespace Managing.Application.ManageBot } } + private static Action InitBot(ITradingBot bot, BotBackup backupBot) + { + bot.Start(); + bot.LoadBackup(backupBot); + return () => { }; + } + public IBot CreateSimpleBot(string botName, Workflow workflow) { return new SimpleBot(botName, _tradingBotLogger, workflow, this); diff --git a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs index 9039dd5..a068e3a 100644 --- a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs @@ -19,71 +19,67 @@ 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) { - BotStatus botStatus = BotStatus.Down; - var backupBots = _botService.GetSavedBots().ToList(); - var activeBots = _botService.GetActiveBots(); - var result = new Dictionary(); - - _logger.LogInformation($"Loading {backupBots.Count()} backup bots"); - - foreach (var backupBot in backupBots) + try { - // Check if bot is existing in cache - try + var activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.GetName() == backupBot.Name); + + if (activeBot == null) { - switch (backupBot.BotType) + _logger.LogInformation("No active instance found for bot {BotName}. Starting backup...", backupBot.Name); + _botService.StartBotFromBackup(backupBot); + + activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.GetName() == backupBot.Name); + if (activeBot != null) { - case BotType.SimpleBot: - var simpleBot = activeBots.FirstOrDefault(b => b.GetName() == backupBot.Name); - if (simpleBot == null) - { - _logger.LogInformation($"Starting backup bot {backupBot.Name}"); - _botService.StartBotFromBackup(backupBot); - result.Add(simpleBot.GetName(), BotStatus.Backup); - } - else - { - result.Add(simpleBot.GetName(), MiscExtensions.ParseEnum(simpleBot.GetStatus())); - } - - break; - case BotType.ScalpingBot: - case BotType.FlippingBot: - var scalpingBot = activeBots.FirstOrDefault(b => b.GetName() == backupBot.Name); - if (scalpingBot == null) - { - _logger.LogInformation($"Starting backup bot {backupBot.Name}"); - _botService.StartBotFromBackup(backupBot); - var bots = _botService.GetActiveBots(); - scalpingBot = bots.FirstOrDefault(b => b.GetName() == backupBot.Name); - result.Add(scalpingBot.GetName(), BotStatus.Backup); - } - else - { - result.Add(scalpingBot.GetName(), - MiscExtensions.ParseEnum(scalpingBot.GetStatus())); - } - - break; - default: - result.Add(backupBot.Name, BotStatus.Down); - break; + result[activeBot.GetName()] = BotStatus.Backup; + anyBackupStarted = true; + _logger.LogInformation("Backup bot {BotName} started successfully.", backupBot.Name); + } + else + { + result[backupBot.Name] = BotStatus.Down; + _logger.LogWarning("Backup bot {BotName} failed to start.", backupBot.Name); } } - catch (Exception ex) + else { - _logger.LogError($"Error loading bot {backupBot.Name}", ex.Message); - _botService.DeleteBotBackup(backupBot.Name); - result.Add(backupBot.Name, BotStatus.Down); + 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.LogInformation("Bot loading completed"); - _logger.LogInformation($"Bots: {result.Select(b => $"{b.Key} - {b.Value}")}"); - - return Task.FromResult(botStatus.ToString()); + 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()); +} + } public class LoadBackupBotCommand : IRequest diff --git a/src/Managing.Application/Trading/OpenPositionCommandHandler.cs b/src/Managing.Application/Trading/OpenPositionCommandHandler.cs index 57d58e6..7487349 100644 --- a/src/Managing.Application/Trading/OpenPositionCommandHandler.cs +++ b/src/Managing.Application/Trading/OpenPositionCommandHandler.cs @@ -108,7 +108,11 @@ namespace Managing.Application.Trading position.Status = IsOpenTradeHandled(position.Open.Status, account.Exchange) ? position.Status : PositionStatus.Rejected; - tradingService.InsertPosition(position); + + if (request.IsForPaperTrading) + { + tradingService.InsertPosition(position); + } return position; } diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs index 8d408f5..7c04f5c 100644 --- a/src/Managing.Application/Users/UserService.cs +++ b/src/Managing.Application/Users/UserService.cs @@ -11,7 +11,14 @@ public class UserService : IUserService private readonly IUserRepository _userRepository; private readonly IAccountService _accountService; - private string[] authorizedAddresses = ["0x6781920674dA695aa5120d95D80c4B1788046806", "0xA2B43AFF0992a47838DF2e6099A8439981f0B717", "0xAD4bcf258852e9d47E580798d312E1a52D59E721", "0xAd6D6c80910096b40e45690506a9f1052e072dCB", "0x309b9235edbe1C6f840816771c6C21aDa6c275EE"]; + private string[] authorizedAddresses = [ + "0x6781920674dA695aa5120d95D80c4B1788046806", // Macbook + "0xA2B43AFF0992a47838DF2e6099A8439981f0B717", // Phone + "0xAD4bcf258852e9d47E580798d312E1a52D59E721", // Razil + "0xAd6D6c80910096b40e45690506a9f1052e072dCB", // Teru + "0x309b9235edbe1C6f840816771c6C21aDa6c275EE", // Cowchain + "0x23AA99254cfaA2c374bE2bA5B55C68018cCdFCb3" // Local optiflex + ]; public UserService( IEvmManager evmManager, diff --git a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Service.cs b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Service.cs index 4a7877d..0bb3d16 100644 --- a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Service.cs +++ b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Service.cs @@ -1228,7 +1228,7 @@ public class GmxV2Service { var position = await GetGmxPositionsV2(web3, publicAddress); var positionsPerTicker = - position.First(p => GmxV2Helpers.SameAddress(p.MarketAddress, GmxV2Helpers.GetMarketAddress(ticker))); + position.First(p => GmxV2Helpers.SameAddress(p.Key, publicAddress)); return GmxV2Mappers.Map(positionsPerTicker, ticker); } @@ -1375,6 +1375,7 @@ public class GmxV2Service public async Task QuantityInPosition(Web3 web3, string publicAddress, Enums.Ticker ticker) { - return (await GetTrade(web3, publicAddress, ticker)).Status == Enums.TradeStatus.Filled ? 1 : 0; + var trade = await GetTrade(web3, publicAddress, ticker); + return trade.Status == Enums.TradeStatus.Filled ? trade.Quantity : 0; } } \ No newline at end of file