diff --git a/src/Managing.Application/Bots/FuturesBot.cs b/src/Managing.Application/Bots/FuturesBot.cs index 6d986805..ea12c84d 100644 --- a/src/Managing.Application/Bots/FuturesBot.cs +++ b/src/Managing.Application/Bots/FuturesBot.cs @@ -209,37 +209,6 @@ public class FuturesBot : TradingBotBase, ITradingBot }); } - protected override async Task VerifyAndUpdateBalanceAsync() - { - // Live trading: verify real USDC balance - if (Config.TradingType == TradingType.BacktestFutures) return; - - try - { - var actualBalance = await ServiceScopeHelpers.WithScopedService(_scopeFactory, - async exchangeService => - { - var balances = await exchangeService.GetBalances(Account); - var usdcBalance = balances.FirstOrDefault(b => b.TokenName?.ToUpper() == "USDC"); - return usdcBalance?.Amount ?? 0; - }); - - if (actualBalance < Config.BotTradingBalance) - { - Logger.LogWarning( - "Actual USDC balance ({ActualBalance:F2}) is less than configured balance ({ConfiguredBalance:F2}). Updating configuration.", - actualBalance, Config.BotTradingBalance); - - var newConfig = Config; - newConfig.BotTradingBalance = actualBalance; - await UpdateConfiguration(newConfig); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error verifying and updating balance"); - } - } protected override async Task SynchronizeWithBrokerPositions(Position internalPosition, Position positionForSignal, List brokerPositions) diff --git a/src/Managing.Application/Bots/SpotBot.cs b/src/Managing.Application/Bots/SpotBot.cs index ea7b04b7..7f9dc8d2 100644 --- a/src/Managing.Application/Bots/SpotBot.cs +++ b/src/Managing.Application/Bots/SpotBot.cs @@ -233,37 +233,6 @@ public class SpotBot : TradingBotBase, ITradingBot }); } - protected override async Task VerifyAndUpdateBalanceAsync() - { - // Live trading: verify real USDC balance for spot trading - if (Config.TradingType == TradingType.BacktestSpot) return; - - try - { - var actualBalance = await ServiceScopeHelpers.WithScopedService(_scopeFactory, - async exchangeService => - { - var balances = await exchangeService.GetBalances(Account); - var usdcBalance = balances.FirstOrDefault(b => b.TokenName?.ToUpper() == "USDC"); - return usdcBalance?.Amount ?? 0; - }); - - if (actualBalance < Config.BotTradingBalance) - { - Logger.LogWarning( - "Actual USDC balance ({ActualBalance:F2}) is less than configured balance ({ConfiguredBalance:F2}). Updating configuration.", - actualBalance, Config.BotTradingBalance); - - var newConfig = Config; - newConfig.BotTradingBalance = actualBalance; - await UpdateConfiguration(newConfig); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error verifying and updating balance"); - } - } protected override async Task SynchronizeWithBrokerPositions(Position internalPosition, Position positionForSignal, List brokerPositions) @@ -282,7 +251,31 @@ public class SpotBot : TradingBotBase, ITradingBot if (tokenBalance != null && tokenBalance.Amount > 0) { - // Token balance exists - verify position is filled + // Verify that the token balance matches the position amount with 0.1% tolerance + var positionQuantity = internalPosition.Open.Quantity; + var tokenBalanceAmount = tokenBalance.Amount; + + if (positionQuantity > 0) + { + var tolerance = positionQuantity * 0.001m; // 0.1% tolerance + var difference = Math.Abs(tokenBalanceAmount - positionQuantity); + + if (difference > tolerance) + { + await LogWarningAsync( + $"⚠️ Token Balance Mismatch - Position Verification Failed\n" + + $"Position: `{internalPosition.Identifier}`\n" + + $"Position Quantity: `{positionQuantity:F5}`\n" + + $"Token Balance: `{tokenBalanceAmount:F5}`\n" + + $"Difference: `{difference:F5}`\n" + + $"Tolerance (0.1%): `{tolerance:F5}`\n" + + $"Token balance does not match position amount within tolerance\n" + + $"Skipping position synchronization"); + return; // Skip processing if amounts don't match + } + } + + // Token balance exists and matches position - verify position is filled var previousPositionStatus = internalPosition.Status; // Position found on broker (token balance exists), means the position is filled diff --git a/src/Managing.Application/Bots/TradingBotBase.cs b/src/Managing.Application/Bots/TradingBotBase.cs index d7c39374..01c82599 100644 --- a/src/Managing.Application/Bots/TradingBotBase.cs +++ b/src/Managing.Application/Bots/TradingBotBase.cs @@ -168,7 +168,7 @@ public abstract class TradingBotBase : ITradingBot public async Task LoadAccount() { - if (Config.TradingType == TradingType.BacktestFutures) return; + if (TradingBox.IsBacktestTrading(Config.TradingType)) return; await ServiceScopeHelpers.WithScopedService(_scopeFactory, async accountService => { var account = await accountService.GetAccountByAccountName(Config.AccountName, false, false); @@ -182,7 +182,7 @@ public abstract class TradingBotBase : ITradingBot /// public async Task VerifyAndUpdateBalance() { - if (Config.TradingType == TradingType.BacktestFutures) return; + if (TradingBox.IsBacktestTrading(Config.TradingType)) return; if (Account == null) { Logger.LogWarning("Cannot verify balance: Account is null"); @@ -367,12 +367,8 @@ public abstract class TradingBotBase : ITradingBot return; // First, process all existing positions that are not finished - // Optimized: Inline the filter to avoid LINQ overhead - foreach (var position in Positions.Values) + foreach (var position in Positions.Values.Where(p => !p.IsFinished())) { - if (position.IsFinished()) - continue; - var signalForPosition = Signals[position.SignalIdentifier]; if (signalForPosition == null) { @@ -436,7 +432,7 @@ public abstract class TradingBotBase : ITradingBot protected void UpdateWalletBalances() { - var date = Config.TradingType == TradingType.BacktestFutures + var date = TradingBox.IsBacktestTrading(Config.TradingType) ? LastCandle?.Date ?? DateTime.UtcNow : DateTime.UtcNow; @@ -485,13 +481,13 @@ public abstract class TradingBotBase : ITradingBot Candle lastCandle = null; await ServiceScopeHelpers.WithScopedService(_scopeFactory, async exchangeService => { - lastCandle = Config.TradingType == TradingType.BacktestFutures + lastCandle = TradingBox.IsBacktestTrading(Config.TradingType) ? LastCandle : await exchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow); }); - var currentTime = Config.TradingType == TradingType.BacktestFutures ? lastCandle.Date : DateTime.UtcNow; + var currentTime = TradingBox.IsBacktestTrading(Config.TradingType) ? lastCandle.Date : DateTime.UtcNow; var currentPnl = positionForSignal.ProfitAndLoss?.Net ?? 0; var pnlPercentage = TradingBox.CalculatePnLPercentage(currentPnl, positionForSignal.Open.Price, positionForSignal.Open.Quantity); @@ -1020,7 +1016,7 @@ public abstract class TradingBotBase : ITradingBot signal.Date, Account.User, Config.BotTradingBalance, - Config.TradingType == TradingType.BacktestFutures, + TradingBox.IsBacktestTrading(Config.TradingType), lastPrice, signalIdentifier: signal.Identifier, initiatorIdentifier: Identifier, @@ -1245,7 +1241,7 @@ public abstract class TradingBotBase : ITradingBot // Update the last position closing time for cooldown period tracking // Only update if position was actually filled - LastPositionClosingTime = Config.TradingType == TradingType.BacktestFutures + LastPositionClosingTime = TradingBox.IsBacktestTrading(Config.TradingType) ? currentCandle.Date : DateTime.UtcNow; } @@ -1498,7 +1494,7 @@ public abstract class TradingBotBase : ITradingBot signal, currentPrice, Config, - Config.TradingType == TradingType.BacktestFutures); + TradingBox.IsBacktestTrading(Config.TradingType)); if (signalValidationResult.Confidence == Confidence.None || signalValidationResult.Confidence == Confidence.Low || @@ -1859,7 +1855,7 @@ public abstract class TradingBotBase : ITradingBot // Calculate cooldown end time based on last position closing time var cooldownEndTime = TradingBox.CalculateCooldownEndTime(LastPositionClosingTime.Value, Config.Timeframe, Config.CooldownPeriod); - var isInCooldown = (Config.TradingType == TradingType.BacktestFutures ? LastCandle.Date : DateTime.UtcNow) < + var isInCooldown = (TradingBox.IsBacktestTrading(Config.TradingType) ? LastCandle.Date : DateTime.UtcNow) < cooldownEndTime; if (isInCooldown) @@ -1917,7 +1913,7 @@ public abstract class TradingBotBase : ITradingBot private async Task NotifyAgentAndPlatformGrainAsync(NotificationEventType eventType, Position position) { - if (Config.TradingType == TradingType.BacktestFutures) + if (TradingBox.IsBacktestTrading(Config.TradingType)) { return; // Skip notifications for backtest } @@ -2064,7 +2060,7 @@ public abstract class TradingBotBase : ITradingBot protected virtual async Task LogInformationAsync(string message) { - if (Config.TradingType == TradingType.BacktestFutures) + if (TradingBox.IsBacktestTrading(Config.TradingType)) return; Logger.LogInformation(message); @@ -2081,7 +2077,7 @@ public abstract class TradingBotBase : ITradingBot protected virtual async Task LogWarningAsync(string message) { - if (Config.TradingType == TradingType.BacktestFutures) + if (TradingBox.IsBacktestTrading(Config.TradingType)) return; message = $"[{Config.Name}] {message}"; @@ -2098,7 +2094,7 @@ public abstract class TradingBotBase : ITradingBot protected virtual async Task LogDebugAsync(string message) { - if (Config.TradingType == TradingType.BacktestFutures) + if (TradingBox.IsBacktestTrading(Config.TradingType)) return; Logger.LogDebug(message); diff --git a/src/Managing.Domain/Shared/Helpers/TradingBox.cs b/src/Managing.Domain/Shared/Helpers/TradingBox.cs index 9c181653..717a10f6 100644 --- a/src/Managing.Domain/Shared/Helpers/TradingBox.cs +++ b/src/Managing.Domain/Shared/Helpers/TradingBox.cs @@ -1348,9 +1348,15 @@ public static class TradingBox }; } + public static bool IsBacktestTrading(TradingType tradingType) + { + return !IsLiveTrading(tradingType); + } + public static TradingType GetLiveTradingType(TradingType tradingType) { - return tradingType switch { + return tradingType switch + { TradingType.BacktestFutures => TradingType.Futures, TradingType.BacktestSpot => TradingType.Spot, _ => throw new InvalidOperationException($"Unsupported TradingType for live trading: {tradingType}")