From 3e680ab815a35e08e29efec8abb7d591659f4217 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Wed, 1 Oct 2025 12:31:53 +0700 Subject: [PATCH] Do not stop strategy if position open --- .../Grains/ILiveTradingBotGrain.cs | 6 ++ .../Bots/Grains/AgentGrain.cs | 65 +++++++++++++++++++ .../Bots/Grains/LiveTradingBotGrain.cs | 30 +++++++++ .../Bots/Models/AgentGrainState.cs | 3 +- 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/Managing.Application.Abstractions/Grains/ILiveTradingBotGrain.cs b/src/Managing.Application.Abstractions/Grains/ILiveTradingBotGrain.cs index 5f6ee58d..71a762ab 100644 --- a/src/Managing.Application.Abstractions/Grains/ILiveTradingBotGrain.cs +++ b/src/Managing.Application.Abstractions/Grains/ILiveTradingBotGrain.cs @@ -46,4 +46,10 @@ public interface ILiveTradingBotGrain : IGrainWithGuidKey /// Returns true if the ping was successful, false otherwise /// Task PingAsync(); + + /// + /// Checks if the bot has any open positions + /// Returns true if there are open positions, false otherwise + /// + Task HasOpenPositionsAsync(); } \ No newline at end of file diff --git a/src/Managing.Application/Bots/Grains/AgentGrain.cs b/src/Managing.Application/Bots/Grains/AgentGrain.cs index d15da937..c617216b 100644 --- a/src/Managing.Application/Bots/Grains/AgentGrain.cs +++ b/src/Managing.Application/Bots/Grains/AgentGrain.cs @@ -388,6 +388,22 @@ public class AgentGrain : Grain, IAgentGrain }; } + // Check if any bot has open positions before executing autoswap + var hasOpenPositions = await HasAnyBotWithOpenPositionsAsync(); + if (hasOpenPositions) + { + _logger.LogWarning( + "Cannot execute autoswap - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (bots have open positions)", + balanceData.EthValueInUsd, balanceData.UsdcValue); + return new BalanceCheckResult + { + IsSuccessful = false, + FailureReason = BalanceCheckFailureReason.BotsHaveOpenPositions, + Message = "Cannot execute autoswap while bots have open positions", + ShouldStopBot = false // Don't stop the bot, just skip this execution cycle + }; + } + // Mark swap as in progress _state.State.IsSwapInProgress = true; await _state.WriteStateAsync(); @@ -472,6 +488,55 @@ public class AgentGrain : Grain, IAgentGrain } } + /// + /// Checks if any of the user's bots have open positions + /// + private async Task HasAnyBotWithOpenPositionsAsync() + { + try + { + // Get all bot IDs for this user from the registry + var botRegistry = GrainFactory.GetGrain(0); + var userBots = await botRegistry.GetBotsForUser((int)this.GetPrimaryKeyLong()); + + if (!userBots.Any()) + { + _logger.LogDebug("No bots found for user {UserId}", this.GetPrimaryKeyLong()); + return false; + } + + // Check each bot for open positions + foreach (var botEntry in userBots) + { + try + { + var botGrain = GrainFactory.GetGrain(botEntry.Identifier); + var hasOpenPositions = await botGrain.HasOpenPositionsAsync(); + + if (hasOpenPositions) + { + _logger.LogInformation("Bot {BotId} has open positions, blocking autoswap for user {UserId}", + botEntry.Identifier, this.GetPrimaryKeyLong()); + return true; + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error checking open positions for bot {BotId}, skipping", botEntry.Identifier); + // Continue checking other bots even if one fails + } + } + + _logger.LogDebug("No bots with open positions found for user {UserId}", this.GetPrimaryKeyLong()); + return false; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error checking for open positions across all bots for user {UserId}", this.GetPrimaryKeyLong()); + return false; // Default to false on error to avoid blocking autoswap + } + } + /// /// Gets cached balance data or fetches fresh data if cache is invalid/expired /// diff --git a/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs b/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs index ba48298d..509e3d50 100644 --- a/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs +++ b/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs @@ -835,4 +835,34 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable return Task.FromResult(false); } } + + /// + /// Checks if the bot has any open positions + /// Returns true if there are open positions, false otherwise + /// + public Task HasOpenPositionsAsync() + { + try + { + if (_tradingBot == null) + { + // For non-running bots, check grain state positions + var hasOpenPositions = _state.State.Positions?.Values.Any(p => !p.IsFinished()) ?? false; + _logger.LogDebug("Bot {GrainId} has open positions: {HasOpenPositions} (from grain state)", + this.GetPrimaryKey(), hasOpenPositions); + return Task.FromResult(hasOpenPositions); + } + + // For running bots, check live positions + var hasLiveOpenPositions = _tradingBot.Positions?.Values.Any(p => !p.IsFinished()) ?? false; + _logger.LogDebug("Bot {GrainId} has open positions: {HasOpenPositions} (from live data)", + this.GetPrimaryKey(), hasLiveOpenPositions); + return Task.FromResult(hasLiveOpenPositions); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error checking open positions for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey()); + return Task.FromResult(false); // Default to false on error to avoid blocking autoswap + } + } } \ No newline at end of file diff --git a/src/Managing.Application/Bots/Models/AgentGrainState.cs b/src/Managing.Application/Bots/Models/AgentGrainState.cs index dd5ef2a2..75c12188 100644 --- a/src/Managing.Application/Bots/Models/AgentGrainState.cs +++ b/src/Managing.Application/Bots/Models/AgentGrainState.cs @@ -111,6 +111,7 @@ namespace Managing.Application.Bots.Models SwapCooldownActive, BalanceFetchError, SwapExecutionError, - InsufficientEthBelowMinimum + InsufficientEthBelowMinimum, + BotsHaveOpenPositions } } \ No newline at end of file