From 652c01b8bbeff2231b8ba29845ab53cb5eec5b94 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Fri, 10 Oct 2025 16:08:50 +0700 Subject: [PATCH] Fix AgentName --- .../Repositories/IUserRepository.cs | 2 +- .../Bots/TradingBotBase.cs | 123 +++++++++++++++++- .../PostgreSql/PostgreSqlUserRepository.cs | 4 +- .../src/pages/botsPage/botList.tsx | 2 +- 4 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs b/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs index 87e6929f..fb2dc21b 100644 --- a/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs @@ -4,7 +4,7 @@ namespace Managing.Application.Abstractions.Repositories; public interface IUserRepository { - Task GetUserByAgentNameAsync(string agentName); + Task GetUserByAgentNameAsync(string agentName); Task GetUserByNameAsync(string name, bool fetchAccounts = false); Task> GetAllUsersAsync(); Task SaveOrUpdateUserAsync(User user); diff --git a/src/Managing.Application/Bots/TradingBotBase.cs b/src/Managing.Application/Bots/TradingBotBase.cs index 649c7523..4cc2c040 100644 --- a/src/Managing.Application/Bots/TradingBotBase.cs +++ b/src/Managing.Application/Bots/TradingBotBase.cs @@ -1,4 +1,5 @@ -using Managing.Application.Abstractions; +using System.Diagnostics; +using Managing.Application.Abstractions; using Managing.Application.Abstractions.Grains; using Managing.Application.Abstractions.Services; using Managing.Application.Trading.Commands; @@ -26,6 +27,7 @@ public class TradingBotBase : ITradingBot { public readonly ILogger Logger; private readonly IServiceScopeFactory _scopeFactory; + private const int NEW_POSITION_GRACE_SECONDS = 45; // grace window before evaluating missing orders public TradingBotConfig Config { get; set; } public Account Account { get; set; } @@ -492,8 +494,18 @@ public class TradingBotBase : ITradingBot } } - if (internalPosition.Status == PositionStatus.New) + if (internalPosition.Status == PositionStatus.New) { + // Grace period: give the broker time to register open orders before we evaluate + var now = Config.IsForBacktest ? (LastCandle?.Date ?? DateTime.UtcNow) : DateTime.UtcNow; + var secondsSinceOpenRequest = (now - positionForSignal.Open.Date).TotalSeconds; + if (secondsSinceOpenRequest < NEW_POSITION_GRACE_SECONDS) + { + var remaining = NEW_POSITION_GRACE_SECONDS - secondsSinceOpenRequest; + await LogInformation($"⏳ Waiting for broker confirmation\nElapsed: `{secondsSinceOpenRequest:F0}s`\nGrace left: `{remaining:F0}s`"); + return; // skip early checks until grace period elapses + } + var orders = await ServiceScopeHelpers.WithScopedService>(_scopeFactory, async exchangeService => { @@ -609,11 +621,30 @@ public class TradingBotBase : ITradingBot else { await LogWarning( - $"❌ Position Never Filled\nNo position on exchange and no orders\nPosition was never filled and will be marked as canceled."); + $"❌ Position Never Filled\nNo position on exchange and no orders\nChecking position history before marking as canceled."); - // Position was never filled (still in New status), so just mark it as canceled - // Don't call HandleClosedPosition as that would incorrectly add volume/PnL - await SetPositionStatus(signal.Identifier, PositionStatus.Canceled); + // Check if position exists in exchange history with PnL before canceling + bool positionFoundInHistory = await CheckPositionInExchangeHistory(positionForSignal); + + if (positionFoundInHistory) + { + // Position was actually filled and closed, process it properly + await HandleClosedPosition(positionForSignal); + await LogInformation( + $"✅ Position Found in Exchange History\n" + + $"Position was actually filled and closed\n" + + $"Processing with HandleClosedPosition"); + } + else + { + // Position was never filled, just mark as canceled without processing PnL + positionForSignal.Status = PositionStatus.Canceled; + await SetPositionStatus(signal.Identifier, PositionStatus.Canceled); + await UpdatePositionDatabase(positionForSignal); + await LogWarning( + $"❌ Position Confirmed Never Filled\nNo position in exchange history\nMarking as canceled without PnL processing"); + } + SetSignalStatus(signal.Identifier, SignalStatus.Expired); } } @@ -1346,6 +1377,8 @@ public class TradingBotBase : ITradingBot // Skip the candle-based PnL calculation since we have actual GMX data goto SkipCandleBasedCalculation; + }else{ + } } @@ -1728,6 +1761,24 @@ public class TradingBotBase : ITradingBot try { var position = Positions.Values.First(p => p.SignalIdentifier == signalIdentifier); + + if (positionStatus.Equals(PositionStatus.Canceled)){ + var stackTrace = new StackTrace(true); + var callingMethod = stackTrace.GetFrame(1)?.GetMethod(); + var callingMethodName = callingMethod?.DeclaringType?.Name + "." + callingMethod?.Name; + + var exception = new InvalidOperationException($"Position {signalIdentifier} is already canceled for User {Account.User.Name}"); + exception.Data["SignalIdentifier"] = signalIdentifier; + exception.Data["PositionId"] = position.Identifier; + exception.Data["CurrentStatus"] = position.Status.ToString(); + exception.Data["RequestedStatus"] = positionStatus.ToString(); + exception.Data["AccountName"] = Account.Name; + exception.Data["BotName"] = Config.Name; + exception.Data["CallingMethod"] = callingMethodName; + exception.Data["CallStack"] = Environment.StackTrace; + SentrySdk.CaptureException(exception); + } + if (!position.Status.Equals(positionStatus)) { Positions.Values.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus; @@ -2480,4 +2531,64 @@ public class TradingBotBase : ITradingBot Logger.LogError(ex, "Failed to send notifications: {EventType} for bot {BotId}", eventType, Identifier); } } + + /// + /// Checks if a position exists in the exchange history with PnL data. + /// This helps determine if a position was actually filled and closed on the exchange + /// even if the bot's internal tracking shows it as never filled. + /// + /// The position to check + /// True if position found in exchange history with PnL, false otherwise + private async Task CheckPositionInExchangeHistory(Position position) + { + if (Config.IsForBacktest) + { + // For backtests, we don't have exchange history, so return false + return false; + } + + try + { + Logger.LogDebug( + $"🔍 Checking Position History for Position: `{position.Identifier}`\nTicker: `{Config.Ticker}`"); + + List positionHistory = null; + await ServiceScopeHelpers.WithScopedService(_scopeFactory, + async exchangeService => + { + // Get position history from the last 24 hours + var fromDate = DateTime.UtcNow.AddHours(-24); + var toDate = DateTime.UtcNow; + positionHistory = + await exchangeService.GetPositionHistory(Account, Config.Ticker, fromDate, toDate); + }); + + // Check if there's a recent position with PnL data + if (positionHistory != null && positionHistory.Any()) + { + var recentPosition = positionHistory + .OrderByDescending(p => p.Open?.Date ?? DateTime.MinValue) + .FirstOrDefault(); + + if (recentPosition != null && recentPosition.ProfitAndLoss != null) + { + Logger.LogDebug( + $"✅ Position Found in Exchange History\n" + + $"Position: `{position.Identifier}`\n" + + $"Exchange PnL: `${recentPosition.ProfitAndLoss.Realized:F2}`\n" + + $"Position was actually filled and closed"); + return true; + } + } + + Logger.LogDebug( + $"❌ No Position Found in Exchange History\nPosition: `{position.Identifier}`\nPosition was never filled"); + return false; + } + catch (Exception ex) + { + Logger.LogError(ex, "Error checking position history for position {PositionId}", position.Identifier); + return false; + } + } } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs index 41ba0408..cf27c691 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs @@ -19,7 +19,7 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito _cacheService = cacheService; } - public async Task GetUserByAgentNameAsync(string agentName) + public async Task GetUserByAgentNameAsync(string agentName) { return await ExecuteWithLoggingAsync(async () => { @@ -51,7 +51,7 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito .ConfigureAwait(false); if (userEntity == null) - throw new InvalidOperationException($"User with agent name '{agentName}' not found"); + return null; var user = PostgreSqlMappers.Map(userEntity); diff --git a/src/Managing.WebApp/src/pages/botsPage/botList.tsx b/src/Managing.WebApp/src/pages/botsPage/botList.tsx index 1de57ad6..97e34892 100644 --- a/src/Managing.WebApp/src/pages/botsPage/botList.tsx +++ b/src/Managing.WebApp/src/pages/botsPage/botList.tsx @@ -249,7 +249,7 @@ const BotList: React.FC = ({ list }) => {
{/* Action Badges - Only show for bot owners */} - {checkIsBotOwner(bot.agentName) && ( + { (
{getToggleBotStatusBadge(bot.status as BotStatus, bot.identifier)} {getUpdateBotBadge(bot)}