diff --git a/src/Managing.Api/Controllers/MoneyManagementController.cs b/src/Managing.Api/Controllers/MoneyManagementController.cs index 8f4da960..fc63dfd0 100644 --- a/src/Managing.Api/Controllers/MoneyManagementController.cs +++ b/src/Managing.Api/Controllers/MoneyManagementController.cs @@ -1,5 +1,4 @@ -using Managing.Application.Abstractions; -using Managing.Application.Abstractions.Services; +using Managing.Application.Abstractions.Services; using Managing.Domain.MoneyManagements; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -84,12 +83,12 @@ public class MoneyManagementController : BaseController { var user = await GetUser(); var result = await _moneyManagementService.GetMoneyMangement(user, name); - + if (result == null) { return NotFound($"Money management strategy '{name}' not found"); } - + return Ok(result); } catch (Exception ex) @@ -110,12 +109,12 @@ public class MoneyManagementController : BaseController { var user = await GetUser(); var result = await _moneyManagementService.DeleteMoneyManagement(user, name); - + if (!result) { return NotFound($"Money management strategy '{name}' not found or could not be deleted"); } - + return Ok(new { success = true, message = $"Money management strategy '{name}' deleted successfully" }); } catch (Exception ex) diff --git a/src/Managing.Api/Controllers/SentryTestController.cs b/src/Managing.Api/Controllers/SentryTestController.cs index 13517f20..0d8b480d 100644 --- a/src/Managing.Api/Controllers/SentryTestController.cs +++ b/src/Managing.Api/Controllers/SentryTestController.cs @@ -1,6 +1,4 @@ using Microsoft.AspNetCore.Mvc; -using Sentry; -using System; namespace Managing.Api.Controllers { @@ -26,22 +24,22 @@ namespace Managing.Api.Controllers { // Add breadcrumbs for context SentrySdk.AddBreadcrumb("About to capture test exception", "test"); - + // Add context to the error SentrySdk.ConfigureScope(scope => { scope.SetTag("test_type", "manual_exception"); scope.SetExtra("timestamp", DateTime.Now); }); - + // Log to both Serilog and Sentry _logger.LogError(ex, "Test exception captured in SentryTestController"); - + // Explicitly capture exception SentrySdk.CaptureException(ex); - - return Ok(new - { + + return Ok(new + { message = "Exception manually captured and sent to Sentry", exceptionMessage = ex.Message, timestamp = DateTime.Now @@ -53,7 +51,7 @@ namespace Managing.Api.Controllers public IActionResult ThrowException() { _logger.LogInformation("About to throw an uncaught exception"); - + // This should be automatically captured by Sentry middleware throw new InvalidOperationException($"Uncaught exception from ThrowException endpoint - {DateTime.Now}"); } @@ -63,12 +61,12 @@ namespace Managing.Api.Controllers { // Send a simple message to Sentry SentrySdk.CaptureMessage("Test message from Managing API", SentryLevel.Info); - - return Ok(new - { + + return Ok(new + { message = "Test message sent to Sentry", timestamp = DateTime.Now }); } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Managing.Api/Exceptions/SentryErrorCapture.cs b/src/Managing.Api/Exceptions/SentryErrorCapture.cs index 9c603dbc..91ed6594 100644 --- a/src/Managing.Api/Exceptions/SentryErrorCapture.cs +++ b/src/Managing.Api/Exceptions/SentryErrorCapture.cs @@ -1,5 +1,3 @@ -using Sentry; - namespace Managing.Api.Exceptions; /// @@ -14,14 +12,15 @@ public static class SentryErrorCapture /// A descriptive name for where the error occurred /// Optional dictionary of additional data to include /// The Sentry event ID - public static SentryId CaptureException(Exception exception, string contextName, IDictionary extraData = null) + public static SentryId CaptureException(Exception exception, string contextName, + IDictionary extraData = null) { return SentrySdk.CaptureException(exception, scope => { // Add context information scope.SetTag("context", contextName); scope.SetTag("error_type", exception.GetType().Name); - + // Add any extra data provided if (extraData != null) { @@ -30,7 +29,7 @@ public static class SentryErrorCapture scope.SetExtra(kvp.Key, kvp.Value?.ToString() ?? "null"); } } - + // Add extra info from the exception's Data dictionary if available foreach (var key in exception.Data.Keys) { @@ -39,7 +38,7 @@ public static class SentryErrorCapture scope.SetExtra($"exception_data_{keyStr}", exception.Data[key].ToString()); } } - + // Add a breadcrumb for context scope.AddBreadcrumb( message: $"Exception in {contextName}", @@ -48,7 +47,7 @@ public static class SentryErrorCapture ); }); } - + /// /// Enriches an exception with additional context data before throwing /// @@ -64,10 +63,10 @@ public static class SentryErrorCapture exception.Data[item.Key] = item.Value; } } - + return exception; } - + /// /// Captures a message in Sentry with additional context /// @@ -76,17 +75,18 @@ public static class SentryErrorCapture /// A descriptive name for where the message originated /// Optional dictionary of additional data to include /// The Sentry event ID - public static SentryId CaptureMessage(string message, SentryLevel level, string contextName, IDictionary extraData = null) + public static SentryId CaptureMessage(string message, SentryLevel level, string contextName, + IDictionary extraData = null) { // First capture the message with the specified level var id = SentrySdk.CaptureMessage(message, level); - + // Then add context via a scope SentrySdk.ConfigureScope(scope => { // Add context information scope.SetTag("context", contextName); - + // Add any extra data provided if (extraData != null) { @@ -95,7 +95,7 @@ public static class SentryErrorCapture scope.SetExtra(kvp.Key, kvp.Value?.ToString() ?? "null"); } } - + // Add a breadcrumb for context scope.AddBreadcrumb( message: $"Message from {contextName}", @@ -103,7 +103,7 @@ public static class SentryErrorCapture level: BreadcrumbLevel.Info ); }); - + return id; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Managing.Api/Middleware/SentryDiagnosticsMiddleware.cs b/src/Managing.Api/Middleware/SentryDiagnosticsMiddleware.cs index eee21b5c..616b7e32 100644 --- a/src/Managing.Api/Middleware/SentryDiagnosticsMiddleware.cs +++ b/src/Managing.Api/Middleware/SentryDiagnosticsMiddleware.cs @@ -1,4 +1,3 @@ -using Sentry; using System.Text; namespace Managing.Api.Middleware @@ -37,17 +36,20 @@ namespace Managing.Api.Middleware // Check if Sentry is initialized response.AppendLine("## Sentry SDK Status"); response.AppendLine($"Sentry Enabled: {SentrySdk.IsEnabled}"); - response.AppendLine($"Application Environment: {Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}"); + response.AppendLine( + $"Application Environment: {Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}"); response.AppendLine(); - + // Send a test event response.AppendLine("## Test Event"); try { - var id = SentrySdk.CaptureMessage($"Diagnostics test from {context.Request.Host} at {DateTime.Now}", SentryLevel.Info); + var id = SentrySdk.CaptureMessage($"Diagnostics test from {context.Request.Host} at {DateTime.Now}", + SentryLevel.Info); response.AppendLine($"Test Event ID: {id}"); - response.AppendLine("Test event was sent to Sentry. Check your Sentry dashboard to confirm it was received."); - + response.AppendLine( + "Test event was sent to Sentry. Check your Sentry dashboard to confirm it was received."); + // Try to send an exception too try { @@ -69,10 +71,11 @@ namespace Managing.Api.Middleware response.AppendLine("## Connectivity Check"); response.AppendLine("If events are not appearing in Sentry, check the following:"); response.AppendLine("1. Verify your DSN is correct in appsettings.json"); - response.AppendLine("2. Ensure your network allows outbound HTTPS connections to sentry.apps.managing.live"); + response.AppendLine( + "2. Ensure your network allows outbound HTTPS connections to sentry.apps.managing.live"); response.AppendLine("3. Check Sentry server logs for any ingestion issues"); response.AppendLine("4. Verify your Sentry project is correctly configured to receive events"); - + // Return the diagnostic information context.Response.ContentType = "text/plain"; await context.Response.WriteAsync(response.ToString()); @@ -87,4 +90,4 @@ namespace Managing.Api.Middleware return builder.UseMiddleware(); } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Managing.Api/Models/Requests/OpenPositionManuallyRequest.cs b/src/Managing.Api/Models/Requests/OpenPositionManuallyRequest.cs index 3b3dae2c..860e5d98 100644 --- a/src/Managing.Api/Models/Requests/OpenPositionManuallyRequest.cs +++ b/src/Managing.Api/Models/Requests/OpenPositionManuallyRequest.cs @@ -1,4 +1,3 @@ -using Managing.Common; using static Managing.Common.Enums; namespace Managing.Api.Models.Requests; @@ -7,4 +6,4 @@ public class OpenPositionManuallyRequest { public string BotName { get; set; } public TradeDirection Direction { get; set; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Managing.Application.Tests/LightMoneyManagementTests.cs b/src/Managing.Application.Tests/LightMoneyManagementTests.cs index 3e6027cf..eba8ae17 100644 --- a/src/Managing.Application.Tests/LightMoneyManagementTests.cs +++ b/src/Managing.Application.Tests/LightMoneyManagementTests.cs @@ -1,4 +1,3 @@ -using Managing.Domain.MoneyManagements; using Xunit; using static Managing.Common.Enums; @@ -14,8 +13,8 @@ namespace Managing.Application.Tests { Name = "Test1", Timeframe = Timeframe.FifteenMinutes, - StopLoss = 10, // 10% - TakeProfit = 20, // 20% + StopLoss = 10, // 10% + TakeProfit = 20, // 20% Leverage = 1 }; @@ -35,7 +34,7 @@ namespace Managing.Application.Tests { Name = "Test2", Timeframe = Timeframe.FifteenMinutes, - StopLoss = 0.1m, // Already 0.1 (10%) + StopLoss = 0.1m, // Already 0.1 (10%) TakeProfit = 0.2m, // Already 0.2 (20%) Leverage = 1 }; @@ -56,7 +55,7 @@ namespace Managing.Application.Tests { Name = "Test3", Timeframe = Timeframe.FifteenMinutes, - StopLoss = 15, // 15% (should be formatted) + StopLoss = 15, // 15% (should be formatted) TakeProfit = 0.25m, // Already 0.25 (25%) (should NOT be formatted) Leverage = 1 }; @@ -77,7 +76,7 @@ namespace Managing.Application.Tests { Name = "Test4a", Timeframe = Timeframe.FifteenMinutes, - StopLoss = 0.01m, // 1% as decimal + StopLoss = 0.01m, // 1% as decimal TakeProfit = 0.02m, // 2% as decimal Leverage = 1 }; @@ -98,8 +97,8 @@ namespace Managing.Application.Tests { Name = "Test4b", Timeframe = Timeframe.FifteenMinutes, - StopLoss = 1m, // 1% as percentage - TakeProfit = 2m, // 2% as percentage + StopLoss = 1m, // 1% as percentage + TakeProfit = 2m, // 2% as percentage Leverage = 1 }; @@ -119,8 +118,8 @@ namespace Managing.Application.Tests { Name = "Test5", Timeframe = Timeframe.FifteenMinutes, - StopLoss = 1.0m, // Exactly 1.0 (boundary) - TakeProfit = 0.5m, // 0.5% (should not be formatted) + StopLoss = 1.0m, // Exactly 1.0 (boundary) + TakeProfit = 0.5m, // 0.5% (should not be formatted) Leverage = 1 }; @@ -134,7 +133,7 @@ namespace Managing.Application.Tests [Theory] [InlineData(0.01, 0.01)] // 1% as decimal - should not change - [InlineData(0.5, 0.5)] // 0.5% as decimal - should not change + [InlineData(0.5, 0.5)] // 0.5% as decimal - should not change [InlineData(0.25, 0.25)] // 0.25% as decimal - should not change public void FormatPercentage_WithDecimalValuesLessThanOne_ShouldNotFormat(decimal input, decimal expected) { @@ -157,11 +156,12 @@ namespace Managing.Application.Tests } [Theory] - [InlineData(1, 0.01)] // 1% as percentage - should format to 0.01 - [InlineData(5, 0.05)] // 5% as percentage - should format to 0.05 - [InlineData(10, 0.1)] // 10% as percentage - should format to 0.1 - [InlineData(50, 0.5)] // 50% as percentage - should format to 0.5 - public void FormatPercentage_WithPercentageValuesGreaterThanOrEqualToOne_ShouldFormat(decimal input, decimal expected) + [InlineData(1, 0.01)] // 1% as percentage - should format to 0.01 + [InlineData(5, 0.05)] // 5% as percentage - should format to 0.05 + [InlineData(10, 0.1)] // 10% as percentage - should format to 0.1 + [InlineData(50, 0.5)] // 50% as percentage - should format to 0.5 + public void FormatPercentage_WithPercentageValuesGreaterThanOrEqualToOne_ShouldFormat(decimal input, + decimal expected) { // Arrange var moneyManagement = new LightMoneyManagement @@ -189,7 +189,7 @@ namespace Managing.Application.Tests { Name = "Test8a", Timeframe = Timeframe.FifteenMinutes, - StopLoss = 0.01m, // 1% as decimal + StopLoss = 0.01m, // 1% as decimal TakeProfit = 0.0075m, // 0.75% as decimal Leverage = 1 }; @@ -211,8 +211,8 @@ namespace Managing.Application.Tests { Name = "Test8b", Timeframe = Timeframe.FifteenMinutes, - StopLoss = 1m, // 1% as percentage - TakeProfit = 0.75m, // 0.75% as percentage + StopLoss = 1m, // 1% as percentage + TakeProfit = 0.75m, // 0.75% as percentage Leverage = 1 }; @@ -224,4 +224,4 @@ namespace Managing.Application.Tests Assert.Equal(0.75m, moneyManagement.TakeProfit); // Not formatted because < 1 } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/Bots/FuturesBot.cs b/src/Managing.Application/Bots/FuturesBot.cs index 56e80063..30ec37e7 100644 --- a/src/Managing.Application/Bots/FuturesBot.cs +++ b/src/Managing.Application/Bots/FuturesBot.cs @@ -1,4 +1,3 @@ -using Managing.Application.Abstractions; using Managing.Application.Abstractions.Grains; using Managing.Application.Abstractions.Services; using Managing.Application.Trading.Commands; @@ -19,7 +18,7 @@ using static Managing.Common.Enums; namespace Managing.Application.Bots; -public class FuturesBot : TradingBotBase, ITradingBot +public class FuturesBot : TradingBotBase { public FuturesBot( ILogger logger, @@ -72,24 +71,14 @@ public class FuturesBot : TradingBotBase, ITradingBot // For live trading, get position from database via trading service return await ServiceScopeHelpers.WithScopedService( _scopeFactory, - async tradingService => { return await tradingService.GetPositionByIdentifierAsync(position.Identifier); }); - } - - protected override async Task UpdatePositionWithBrokerData(Position position, List brokerPositions) - { - // Live trading broker position synchronization logic is handled in the base UpdatePosition method - // This override allows for any futures-specific synchronization if needed - await base.UpdatePositionWithBrokerData(position, brokerPositions); + async tradingService => await tradingService.GetPositionByIdentifierAsync(position.Identifier)); } protected override async Task GetCurrentCandleForPositionClose(Account account, string ticker) { // For live trading, get real-time candle from exchange return await ServiceScopeHelpers.WithScopedService(_scopeFactory, - async exchangeService => - { - return await exchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow); - }); + async exchangeService => await exchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow)); } protected override async Task CheckBrokerPositions() @@ -170,7 +159,7 @@ public class FuturesBot : TradingBotBase, ITradingBot } else { - // Broker has a position but we don't have any internal tracking + // Broker has a position, but we don't have any internal tracking Logger.LogWarning( $"⚠️ Orphaned Broker Position Detected\n" + $"Broker has position for {Config.Ticker} ({brokerPositionForTicker.OriginDirection})\n" + @@ -196,7 +185,7 @@ public class FuturesBot : TradingBotBase, ITradingBot if (Config.TradingType == TradingType.BacktestFutures) return; await ServiceScopeHelpers.WithScopedService(_scopeFactory, async accountService => { - var account = await accountService.GetAccountByAccountName(Config.AccountName, false, false); + var account = await accountService.GetAccountByAccountName(Config.AccountName, false); Account = account; }); } @@ -297,7 +286,6 @@ public class FuturesBot : TradingBotBase, ITradingBot $"Cannot verify if position is closed\n" + $"Will retry on next execution cycle"); // Don't change position status, wait for next cycle - return; } else if (existsInHistory) { @@ -309,7 +297,6 @@ public class FuturesBot : TradingBotBase, ITradingBot internalPosition.Status = PositionStatus.Finished; await HandleClosedPosition(internalPosition); - return; } else { @@ -347,11 +334,11 @@ public class FuturesBot : TradingBotBase, ITradingBot } var orders = await ServiceScopeHelpers.WithScopedService>(_scopeFactory, - async exchangeService => { return [.. await exchangeService.GetOpenOrders(Account, Config.Ticker)]; }); + async exchangeService => [.. await exchangeService.GetOpenOrders(Account, Config.Ticker)]); - if (orders.Any()) + if (orders.Count != 0) { - var ordersCount = orders.Count(); + var ordersCount = orders.Count; if (ordersCount >= 3) { var currentTime = DateTime.UtcNow; @@ -386,7 +373,6 @@ public class FuturesBot : TradingBotBase, ITradingBot positionForSignal.TakeProfit1.SetStatus(TradeStatus.Cancelled); await UpdatePositionDatabase(positionForSignal); - return; } else { @@ -538,7 +524,7 @@ public class FuturesBot : TradingBotBase, ITradingBot /// /// The position to check /// True if position found in exchange history with PnL, false otherwise; hadError indicates Web3/infra issues - protected async Task<(bool found, bool hadError)> CheckPositionInExchangeHistory(Position position) + private async Task<(bool found, bool hadError)> CheckPositionInExchangeHistory(Position position) { try { @@ -564,7 +550,7 @@ public class FuturesBot : TradingBotBase, ITradingBot .OrderByDescending(p => p.Open?.Date ?? DateTime.MinValue) .FirstOrDefault(); - if (recentPosition != null && recentPosition.ProfitAndLoss != null) + if (recentPosition is { ProfitAndLoss: not null }) { await LogDebugAsync( $"✅ Position Found in Exchange History\n" + @@ -757,7 +743,7 @@ public class FuturesBot : TradingBotBase, ITradingBot .OrderByDescending(p => p.Open?.Date ?? DateTime.MinValue) .FirstOrDefault(); - if (brokerPosition != null && brokerPosition.ProfitAndLoss != null) + if (brokerPosition is { ProfitAndLoss: not null }) { await LogDebugAsync( $"✅ Broker Position History Found\n" + @@ -934,7 +920,7 @@ public class FuturesBot : TradingBotBase, ITradingBot // Fallback to current candle if available if (currentCandle != null) { - recentCandles = new List { currentCandle }; + recentCandles = [currentCandle]; } else { @@ -950,8 +936,8 @@ public class FuturesBot : TradingBotBase, ITradingBot var minPriceRecent = recentCandles.Min(c => c.Low); var maxPriceRecent = recentCandles.Max(c => c.High); - bool wasStopLossHit = false; - bool wasTakeProfitHit = false; + var wasStopLossHit = false; + var wasTakeProfitHit = false; if (position.OriginDirection == TradeDirection.Long) { @@ -1193,7 +1179,7 @@ public class FuturesBot : TradingBotBase, ITradingBot signal.Date, Account.User, Config.BotTradingBalance, - isForPaperTrading: false, // Futures is live trading + isForPaperTrading: false, lastPrice, signalIdentifier: signal.Identifier, initiatorIdentifier: Identifier, @@ -1203,10 +1189,8 @@ public class FuturesBot : TradingBotBase, ITradingBot .WithScopedServices( _scopeFactory, async (exchangeService, accountService, tradingService) => - { - return await new OpenPositionCommandHandler(exchangeService, accountService, tradingService) - .Handle(command); - }); + await new OpenPositionCommandHandler(exchangeService, accountService, tradingService) + .Handle(command)); return position; } @@ -1230,7 +1214,7 @@ public class FuturesBot : TradingBotBase, ITradingBot if (quantity == 0) { await LogDebugAsync($"✅ Trade already closed on exchange for position: `{position.Identifier}`"); - await HandleClosedPosition(position, forceMarketClose ? lastPrice : (decimal?)null, forceMarketClose); + await HandleClosedPosition(position, forceMarketClose ? lastPrice : null, forceMarketClose); } else { @@ -1258,7 +1242,7 @@ public class FuturesBot : TradingBotBase, ITradingBot await SetPositionStatus(signal.Identifier, PositionStatus.Finished); } - await HandleClosedPosition(closedPosition, forceMarketClose ? lastPrice : (decimal?)null, + await HandleClosedPosition(closedPosition, forceMarketClose ? lastPrice : null, forceMarketClose); } else @@ -1275,7 +1259,7 @@ public class FuturesBot : TradingBotBase, ITradingBot // Trade close on exchange => Should close trade manually await SetPositionStatus(signal.Identifier, PositionStatus.Finished); // Ensure trade dates are properly updated even for canceled/rejected positions - await HandleClosedPosition(position, forceMarketClose ? lastPrice : (decimal?)null, + await HandleClosedPosition(position, forceMarketClose ? lastPrice : null, forceMarketClose); } } diff --git a/src/Managing.Application/Bots/SpotBot.cs b/src/Managing.Application/Bots/SpotBot.cs index 4788ce47..f7fdc658 100644 --- a/src/Managing.Application/Bots/SpotBot.cs +++ b/src/Managing.Application/Bots/SpotBot.cs @@ -94,7 +94,7 @@ public class SpotBot : TradingBotBase // Try to get current price from exchange currentPrice = await ServiceScopeHelpers.WithScopedService( _scopeFactory, - async exchangeService => { return await exchangeService.GetCurrentPrice(Account, Config.Ticker); }); + async exchangeService => await exchangeService.GetCurrentPrice(Account, Config.Ticker)); } if (currentPrice == 0) @@ -144,10 +144,7 @@ public class SpotBot : TradingBotBase { // For live trading, get real-time candle from exchange return await ServiceScopeHelpers.WithScopedService(_scopeFactory, - async exchangeService => - { - return await exchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow); - }); + async exchangeService => await exchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow)); } protected override async Task CheckBrokerPositions() @@ -164,7 +161,7 @@ public class SpotBot : TradingBotBase if (hasOpenPosition) { // We have an internal position - verify it matches broker balance - if (tokenBalance != null && tokenBalance.Amount > 0) + if (tokenBalance is { Amount: > 0 }) { await LogDebugAsync( $"✅ Spot Position Verified\n" + @@ -174,17 +171,16 @@ public class SpotBot : TradingBotBase $"Position matches broker balance"); return false; // Position already open, cannot open new one } - else - { - await LogWarningAsync( - $"⚠️ Position Mismatch\n" + - $"Ticker: {Config.Ticker}\n" + - $"Internal position exists but no token balance found\n" + - $"Position may need synchronization"); - return false; // Don't allow opening new position until resolved - } + + await LogWarningAsync( + $"⚠️ Position Mismatch\n" + + $"Ticker: {Config.Ticker}\n" + + $"Internal position exists but no token balance found\n" + + $"Position may need synchronization"); + return false; // Don't allow opening new position until resolved } - else if (tokenBalance != null && tokenBalance.Value > 1m) + + if (tokenBalance is { Value: > 1m }) { // We have a token balance but no internal position - orphaned position await LogWarningAsync( @@ -212,7 +208,7 @@ public class SpotBot : TradingBotBase if (Config.TradingType == TradingType.BacktestSpot) return; await ServiceScopeHelpers.WithScopedService(_scopeFactory, async accountService => { - var account = await accountService.GetAccountByAccountName(Config.AccountName, false, false); + var account = await accountService.GetAccountByAccountName(Config.AccountName, false); Account = account; }); } @@ -227,7 +223,7 @@ public class SpotBot : TradingBotBase _scopeFactory, async exchangeService => await exchangeService.GetBalance(Account, Config.Ticker)); - if (tokenBalance != null && tokenBalance.Amount > 0) + if (tokenBalance is { Amount: > 0 }) { // Verify that the token balance matches the position amount with 0.1% tolerance var positionQuantity = internalPosition.Open.Quantity; @@ -281,7 +277,7 @@ public class SpotBot : TradingBotBase // Calculate and update PnL based on current price var currentPrice = await ServiceScopeHelpers.WithScopedService( _scopeFactory, - async exchangeService => { return await exchangeService.GetCurrentPrice(Account, Config.Ticker); }); + async exchangeService => await exchangeService.GetCurrentPrice(Account, Config.Ticker)); if (currentPrice > 0) { @@ -353,17 +349,16 @@ public class SpotBot : TradingBotBase await LogDebugAsync( $"🔍 Checking Spot Position History for Position: `{position.Identifier}`\nTicker: `{Config.Ticker}`"); - List positionHistory = null; - await ServiceScopeHelpers.WithScopedService(_scopeFactory, + var positionHistory = await ServiceScopeHelpers.WithScopedService>( + _scopeFactory, async exchangeService => { var fromDate = DateTime.UtcNow.AddHours(-24); var toDate = DateTime.UtcNow; - positionHistory = - await exchangeService.GetSpotPositionHistory(Account, Config.Ticker, fromDate, toDate); + return await exchangeService.GetSpotPositionHistory(Account, Config.Ticker, fromDate, toDate); }); - if (positionHistory != null && positionHistory.Any()) + if (positionHistory != null && positionHistory.Count != 0) { var recentPosition = positionHistory .OrderByDescending(p => p.Date) @@ -415,28 +410,28 @@ public class SpotBot : TradingBotBase } } - protected override async Task MonitorSynthRisk(LightSignal signal, Position position) + protected override Task MonitorSynthRisk(LightSignal signal, Position position) { // Spot trading doesn't use Synth risk monitoring (futures-specific feature) - return; + return Task.CompletedTask; } - protected override async Task RecoverOpenPositionFromBroker(LightSignal signal, Position positionForSignal) + protected override Task RecoverOpenPositionFromBroker(LightSignal signal, Position positionForSignal) { // Spot trading doesn't have broker positions to recover // Positions are token balances, not tracked positions - return false; + return Task.FromResult(false); } - protected override async Task ReconcileWithBrokerHistory(Position position, Candle currentCandle) + protected override Task ReconcileWithBrokerHistory(Position position, Candle currentCandle) { // Spot trading doesn't have broker position history like futures // Return false to continue with candle-based calculation - return false; + return Task.FromResult(false); } - protected override async Task<(decimal closingPrice, bool pnlCalculated)> CalculatePositionClosingFromCandles( - Position position, Candle currentCandle, bool forceMarketClose, decimal? forcedClosingPrice) + protected override Task<(decimal closingPrice, bool pnlCalculated)> CalculatePositionClosingFromCandles( + Position position, Candle? currentCandle, bool forceMarketClose, decimal? forcedClosingPrice) { decimal closingPrice = 0; bool pnlCalculated = false; @@ -529,7 +524,7 @@ public class SpotBot : TradingBotBase ? closingPrice > position.Open.Price : closingPrice < position.Open.Price; - if (isManualCloseProfitable) + if (isManualCloseProfitable && position.TakeProfit1 != null) { position.TakeProfit1.SetPrice(closingPrice, 2); position.TakeProfit1.SetDate(currentCandle.Date); @@ -542,9 +537,9 @@ public class SpotBot : TradingBotBase } else { - position.StopLoss.SetPrice(closingPrice, 2); - position.StopLoss.SetDate(currentCandle.Date); - position.StopLoss.SetStatus(TradeStatus.Filled); + position.StopLoss?.SetPrice(closingPrice, 2); + position.StopLoss?.SetDate(currentCandle.Date); + position.StopLoss?.SetStatus(TradeStatus.Filled); if (position.TakeProfit1 != null) { @@ -561,11 +556,11 @@ public class SpotBot : TradingBotBase pnlCalculated = true; } - return (closingPrice, pnlCalculated); + return Task.FromResult((closingPrice, pnlCalculated)); } protected override async Task UpdateSignalsCore(IReadOnlyList candles, - Dictionary preCalculatedIndicatorValues = null) + Dictionary? preCalculatedIndicatorValues = null) { // For spot trading, always fetch signals regardless of open positions // Check if we're in cooldown period @@ -625,7 +620,7 @@ public class SpotBot : TradingBotBase return !await IsInCooldownPeriodAsync() && await CheckLossStreak(signal); } - protected override async Task HandleFlipPosition(LightSignal signal, Position openedPosition, + protected override async Task HandleFlipPosition(LightSignal signal, Position openedPosition, LightSignal previousSignal, decimal lastPrice) { // For spot trading, SHORT signals should close the open LONG position @@ -694,10 +689,8 @@ public class SpotBot : TradingBotBase .WithScopedServices( _scopeFactory, async (exchangeService, accountService, tradingService) => - { - return await new OpenSpotPositionCommandHandler(exchangeService, accountService, tradingService) - .Handle(command); - }); + await new OpenSpotPositionCommandHandler(exchangeService, accountService, tradingService) + .Handle(command)); return position; } @@ -725,7 +718,7 @@ public class SpotBot : TradingBotBase await SetPositionStatus(signal.Identifier, PositionStatus.Finished); } - await HandleClosedPosition(closedPosition, forceMarketClose ? lastPrice : (decimal?)null, + await HandleClosedPosition(closedPosition, forceMarketClose ? lastPrice : null, forceMarketClose); } else @@ -742,7 +735,7 @@ public class SpotBot : TradingBotBase // Trade close on exchange => Should close trade manually await SetPositionStatus(signal.Identifier, PositionStatus.Finished); // Ensure trade dates are properly updated even for canceled/rejected positions - await HandleClosedPosition(position, forceMarketClose ? lastPrice : (decimal?)null, + await HandleClosedPosition(position, forceMarketClose ? lastPrice : null, forceMarketClose); } } diff --git a/src/Managing.Application/Trading/Handlers/OpenSpotPositionCommandHandler.cs b/src/Managing.Application/Trading/Handlers/OpenSpotPositionCommandHandler.cs index 38ffc4af..a1b5bcd5 100644 --- a/src/Managing.Application/Trading/Handlers/OpenSpotPositionCommandHandler.cs +++ b/src/Managing.Application/Trading/Handlers/OpenSpotPositionCommandHandler.cs @@ -2,183 +2,152 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; using Managing.Application.Trading.Commands; using Managing.Common; -using Managing.Core.Exceptions; using Managing.Domain.Accounts; using Managing.Domain.Shared.Helpers; using Managing.Domain.Trades; using static Managing.Common.Enums; -namespace Managing.Application.Trading.Handlers +namespace Managing.Application.Trading.Handlers; + +public class OpenSpotPositionCommandHandler( + IExchangeService exchangeService, + IAccountService accountService, + ITradingService tradingService) + : ICommandHandler { - public class OpenSpotPositionCommandHandler( - IExchangeService exchangeService, - IAccountService accountService, - ITradingService tradingService) - : ICommandHandler + public async Task Handle(OpenSpotPositionRequest request) { - public async Task Handle(OpenSpotPositionRequest request) + var account = await accountService.GetAccount(request.AccountName, hideSecrets: false, getBalance: false); + + var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator; + var position = new Position(Guid.NewGuid(), account.Id, request.Direction, + request.Ticker, + request.MoneyManagement, + initiator, request.Date, request.User); + + if (!string.IsNullOrEmpty(request.SignalIdentifier)) { - var account = await accountService.GetAccount(request.AccountName, hideSecrets: false, getBalance: false); - - var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator; - var position = new Position(Guid.NewGuid(), account.Id, request.Direction, - request.Ticker, - request.MoneyManagement, - initiator, request.Date, request.User); - - if (!string.IsNullOrEmpty(request.SignalIdentifier)) - { - position.SignalIdentifier = request.SignalIdentifier; - } - - position.InitiatorIdentifier = request.InitiatorIdentifier; - position.TradingType = request.TradingType; - - // Always use BotTradingBalance directly as the balance to risk - // Round to 2 decimal places to prevent precision errors - decimal balanceToRisk = Math.Round(request.AmountToTrade, 0, MidpointRounding.ToZero); - - // Minimum check - if (balanceToRisk < Constants.GMX.Config.MinimumPositionAmount) - { - throw new Exception( - $"Bot trading balance of {balanceToRisk} USD is less than the minimum {Constants.GMX.Config.MinimumPositionAmount} USD required to trade"); - } - - var price = request.IsForPaperTrading && request.Price.HasValue - ? request.Price.Value - : await exchangeService.GetPrice(account, request.Ticker, DateTime.Now); - var quantity = balanceToRisk / price; - - var openPrice = request.IsForPaperTrading || request.Price.HasValue - ? request.Price.Value - : price; - - // For spot trading, determine swap direction - // Long: Swap USDC -> Token (buy token with USDC) - // Short: Swap Token -> USDC (sell token for USDC) - Ticker fromTicker; - Ticker toTicker; - double swapAmount; - - if (request.Direction == TradeDirection.Long) - { - fromTicker = Ticker.USDC; - toTicker = request.Ticker; - swapAmount = (double)balanceToRisk; - } - else - { - fromTicker = request.Ticker; - toTicker = Ticker.USDC; - swapAmount = (double)quantity; - } - - // For backtest/paper trading, simulate the swap without calling the exchange - SwapInfos swapResult; - if (request.IsForPaperTrading) - { - // Simulate successful swap for backtest - swapResult = new SwapInfos - { - Success = true, - Hash = Guid.NewGuid().ToString(), - Message = "Backtest spot position opened successfully" - }; - } - else - { - // For live trading, call SwapGmxTokensAsync - swapResult = await tradingService.SwapGmxTokensAsync( - request.User, - request.AccountName, - fromTicker, - toTicker, - swapAmount, - "market", - null, - 0.5); - } - - if (!swapResult.Success) - { - position.Status = PositionStatus.Rejected; - throw new InvalidOperationException($"Failed to open spot position: {swapResult.Error ?? swapResult.Message}"); - } - - // Build the opening trade - var trade = exchangeService.BuildEmptyTrade( - request.Ticker, - openPrice, - quantity, - request.Direction, - 1, // Spot trading has no leverage - TradeType.Market, - request.Date, - TradeStatus.Filled); - - position.Open = trade; - - // Calculate and set fees for the position - position.GasFees = TradingBox.CalculateOpeningGasFees(); - - // Set UI fees for opening - var positionSizeUsd = TradingBox.GetVolumeForPosition(position); - position.UiFees = TradingBox.CalculateOpeningUiFees(positionSizeUsd); - - var closeDirection = request.Direction == TradeDirection.Long - ? TradeDirection.Short - : TradeDirection.Long; - - // Determine SL/TP Prices - var stopLossPrice = RiskHelpers.GetStopLossPrice(request.Direction, openPrice, request.MoneyManagement); - var takeProfitPrice = RiskHelpers.GetTakeProfitPrice(request.Direction, openPrice, request.MoneyManagement); - - // Stop loss - position.StopLoss = exchangeService.BuildEmptyTrade( - request.Ticker, - stopLossPrice, - position.Open.Quantity, - closeDirection, - 1, // Spot trading has no leverage - TradeType.StopLoss, - request.Date, - TradeStatus.Requested); - - // Take profit - position.TakeProfit1 = exchangeService.BuildEmptyTrade( - request.Ticker, - takeProfitPrice, - quantity, - closeDirection, - 1, // Spot trading has no leverage - TradeType.TakeProfit, - request.Date, - TradeStatus.Requested); - - position.Status = IsOpenTradeHandled(position.Open.Status) - ? position.Status - : PositionStatus.Rejected; - - if (position.Status == PositionStatus.Rejected) - { - SentrySdk.CaptureException( - new Exception($"Position {position.Identifier} for {request.SignalIdentifier} rejected")); - } - - if (!request.IsForPaperTrading) - { - await tradingService.InsertPositionAsync(position); - } - - return position; + position.SignalIdentifier = request.SignalIdentifier; } - private static bool IsOpenTradeHandled(TradeStatus tradeStatus) + position.InitiatorIdentifier = request.InitiatorIdentifier; + position.TradingType = request.TradingType; + + // Always use BotTradingBalance directly as the balance to risk + // Round to 2 decimal places to prevent precision errors + decimal balanceToRisk = Math.Round(request.AmountToTrade, 0, MidpointRounding.ToZero); + + // Minimum check + if (balanceToRisk < Constants.GMX.Config.MinimumPositionAmount) { - return tradeStatus == TradeStatus.Filled - || tradeStatus == TradeStatus.Requested; + throw new Exception( + $"Bot trading balance of {balanceToRisk} USD is less than the minimum {Constants.GMX.Config.MinimumPositionAmount} USD required to trade"); } + + var price = request.IsForPaperTrading && request.Price.HasValue + ? request.Price.Value + : await exchangeService.GetPrice(account, request.Ticker, DateTime.Now); + var quantity = balanceToRisk / price; + + var openPrice = request.IsForPaperTrading + ? request.Price ?? price + : price; + + SwapInfos swapResult; + if (request.IsForPaperTrading) + { + // Simulate successful swap for backtest + swapResult = new SwapInfos + { + Success = true, + Hash = Guid.NewGuid().ToString(), + Message = "Backtest spot position opened successfully" + }; + } + else + { + // For live trading, call SwapGmxTokensAsync + swapResult = await tradingService.SwapGmxTokensAsync( + request.User, + request.AccountName, + Ticker.USDC, + request.Ticker, + (double)balanceToRisk); + } + + if (!swapResult.Success) + { + position.Status = PositionStatus.Rejected; + throw new InvalidOperationException( + $"Failed to open spot position: {swapResult.Error ?? swapResult.Message}"); + } + + // Build the opening trade + var trade = exchangeService.BuildEmptyTrade( + request.Ticker, + openPrice, + quantity, + request.Direction, + 1, // Spot trading has no leverage + TradeType.Market, + request.Date, + TradeStatus.Filled); + + position.Open = trade; + + // Calculate and set fees for the position + position.GasFees = TradingBox.CalculateOpeningGasFees(); + + // Set UI fees for opening + var positionSizeUsd = position.Open.Quantity * position.Open.Price; + position.UiFees = TradingBox.CalculateOpeningUiFees(positionSizeUsd); + + // Determine SL/TP Prices + var stopLossPrice = RiskHelpers.GetStopLossPrice(request.Direction, openPrice, request.MoneyManagement); + var takeProfitPrice = RiskHelpers.GetTakeProfitPrice(request.Direction, openPrice, request.MoneyManagement); + + // Stop loss + position.StopLoss = exchangeService.BuildEmptyTrade( + request.Ticker, + stopLossPrice, + position.Open.Quantity, + TradeDirection.Short, + 1, // Spot trading has no leverage + TradeType.StopLoss, + request.Date, + TradeStatus.Requested); + + // Take profit + position.TakeProfit1 = exchangeService.BuildEmptyTrade( + request.Ticker, + takeProfitPrice, + quantity, + TradeDirection.Short, + 1, // Spot trading has no leverage + TradeType.TakeProfit, + request.Date, + TradeStatus.Requested); + + position.Status = IsOpenTradeHandled(position.Open.Status) + ? position.Status + : PositionStatus.Rejected; + + if (position.Status == PositionStatus.Rejected) + { + SentrySdk.CaptureException( + new Exception($"Position {position.Identifier} for {request.SignalIdentifier} rejected")); + } + + if (!request.IsForPaperTrading) + { + await tradingService.InsertPositionAsync(position); + } + + return position; } -} + private static bool IsOpenTradeHandled(TradeStatus tradeStatus) + { + return tradeStatus is TradeStatus.Filled or TradeStatus.Requested; + } +} \ No newline at end of file diff --git a/src/Managing.Aspire.ServiceDefaults/Extensions.cs b/src/Managing.Aspire.ServiceDefaults/Extensions.cs index aab5715e..229c194b 100644 --- a/src/Managing.Aspire.ServiceDefaults/Extensions.cs +++ b/src/Managing.Aspire.ServiceDefaults/Extensions.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.ServiceDiscovery; using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -111,4 +110,4 @@ public static class Extensions return app; } -} +} \ No newline at end of file diff --git a/src/Managing.Core/Exceptions/SentryErrorCapture.cs b/src/Managing.Core/Exceptions/SentryErrorCapture.cs index 0c13abcd..a29f7f1c 100644 --- a/src/Managing.Core/Exceptions/SentryErrorCapture.cs +++ b/src/Managing.Core/Exceptions/SentryErrorCapture.cs @@ -1,5 +1,3 @@ -using Sentry; - namespace Managing.Core.Exceptions; /// @@ -14,14 +12,15 @@ public static class SentryErrorCapture /// A descriptive name for where the error occurred /// Optional dictionary of additional data to include /// The Sentry event ID - public static SentryId CaptureException(Exception exception, string contextName, IDictionary extraData = null) + public static SentryId CaptureException(Exception exception, string contextName, + IDictionary extraData = null) { return SentrySdk.CaptureException(exception, scope => { // Add context information scope.SetTag("context", contextName); scope.SetTag("error_type", exception.GetType().Name); - + // Add any extra data provided if (extraData != null) { @@ -30,7 +29,7 @@ public static class SentryErrorCapture scope.SetExtra(kvp.Key, kvp.Value?.ToString() ?? "null"); } } - + // Add extra info from the exception's Data dictionary if available foreach (var key in exception.Data.Keys) { @@ -39,7 +38,7 @@ public static class SentryErrorCapture scope.SetExtra($"exception_data_{keyStr}", exception.Data[key].ToString()); } } - + // Add a breadcrumb for context scope.AddBreadcrumb( message: $"Exception in {contextName}", @@ -48,7 +47,7 @@ public static class SentryErrorCapture ); }); } - + /// /// Enriches an exception with additional context data before throwing /// @@ -64,10 +63,10 @@ public static class SentryErrorCapture exception.Data[item.Key] = item.Value; } } - + return exception; } - + /// /// Captures a message in Sentry with additional context /// @@ -76,17 +75,18 @@ public static class SentryErrorCapture /// A descriptive name for where the message originated /// Optional dictionary of additional data to include /// The Sentry event ID - public static SentryId CaptureMessage(string message, SentryLevel level, string contextName, IDictionary extraData = null) + public static SentryId CaptureMessage(string message, SentryLevel level, string contextName, + IDictionary extraData = null) { // First capture the message with the specified level var id = SentrySdk.CaptureMessage(message, level); - + // Then add context via a scope SentrySdk.ConfigureScope(scope => { // Add context information scope.SetTag("context", contextName); - + // Add any extra data provided if (extraData != null) { @@ -95,7 +95,7 @@ public static class SentryErrorCapture scope.SetExtra(kvp.Key, kvp.Value?.ToString() ?? "null"); } } - + // Add a breadcrumb for context scope.AddBreadcrumb( message: $"Message from {contextName}", @@ -103,7 +103,7 @@ public static class SentryErrorCapture level: BreadcrumbLevel.Info ); }); - + return id; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Managing.Core/Middleawares/GlobalErrorHandlingMiddleware.cs b/src/Managing.Core/Middleawares/GlobalErrorHandlingMiddleware.cs index 04029066..84d8f628 100644 --- a/src/Managing.Core/Middleawares/GlobalErrorHandlingMiddleware.cs +++ b/src/Managing.Core/Middleawares/GlobalErrorHandlingMiddleware.cs @@ -1,9 +1,8 @@ using System.Net; using System.Text.Json; +using Managing.Core.Exceptions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using Sentry; -using Managing.Core.Exceptions; namespace Managing.Core.Middleawares; @@ -11,13 +10,13 @@ public class GlobalErrorHandlingMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; - + public GlobalErrorHandlingMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } - + public async Task Invoke(HttpContext context) { try @@ -29,12 +28,12 @@ public class GlobalErrorHandlingMiddleware await HandleExceptionAsync(context, ex); } } - + private Task HandleExceptionAsync(HttpContext context, Exception exception) { HttpStatusCode status; string errorMessage; - + // Determine the appropriate status code based on exception type status = exception switch { @@ -43,41 +42,41 @@ public class GlobalErrorHandlingMiddleware ValidationException => HttpStatusCode.BadRequest, FormatException => HttpStatusCode.BadRequest, InvalidOperationException => HttpStatusCode.BadRequest, - + // 401 Unauthorized UnauthorizedAccessException => HttpStatusCode.Unauthorized, - + // 403 Forbidden ForbiddenException => HttpStatusCode.Forbidden, - + // 404 Not Found KeyNotFoundException => HttpStatusCode.NotFound, FileNotFoundException => HttpStatusCode.NotFound, DirectoryNotFoundException => HttpStatusCode.NotFound, NotFoundException => HttpStatusCode.NotFound, - + // 408 Request Timeout TimeoutException => HttpStatusCode.RequestTimeout, - + // 409 Conflict ConflictException => HttpStatusCode.Conflict, - + // 429 Too Many Requests RateLimitExceededException => HttpStatusCode.TooManyRequests, - + // 501 Not Implemented NotImplementedException => HttpStatusCode.NotImplemented, - + // 503 Service Unavailable ServiceUnavailableException => HttpStatusCode.ServiceUnavailable, - + // 500 Internal Server Error (default) _ => HttpStatusCode.InternalServerError }; - + // Log the error with appropriate severity based on status code var isServerError = (int)status >= 500; - + if (isServerError) { _logger.LogError(exception, "Server Error: {StatusCode} on {Path}", (int)status, context.Request.Path); @@ -86,29 +85,29 @@ public class GlobalErrorHandlingMiddleware { _logger.LogWarning(exception, "Client Error: {StatusCode} on {Path}", (int)status, context.Request.Path); } - + // Capture exception in Sentry with request context var sentryId = SentrySdk.CaptureException(exception, scope => { // Add HTTP request information scope.SetTag("http.method", context.Request.Method); scope.SetTag("http.url", context.Request.Path); - + // Add request details scope.SetExtra("query_string", context.Request.QueryString.ToString()); - + // Add custom tags to help with filtering scope.SetTag("error_type", exception.GetType().Name); scope.SetTag("status_code", ((int)status).ToString()); scope.SetTag("host", context.Request.Host.ToString()); scope.SetTag("path", context.Request.Path.ToString()); - + // Add any correlation IDs if available if (context.Request.Headers.TryGetValue("X-Correlation-ID", out var correlationId)) { scope.SetTag("correlation_id", correlationId.ToString()); } - + // Additional context based on exception type if (exception is ValidationException) { @@ -118,7 +117,7 @@ public class GlobalErrorHandlingMiddleware { scope.SetTag("error_category", "not_found"); } - + // Add additional context from exception data if available foreach (var key in exception.Data.Keys) { @@ -127,7 +126,7 @@ public class GlobalErrorHandlingMiddleware scope.SetExtra(keyStr, exception.Data[key].ToString()); } } - + // Add breadcrumb for the request scope.AddBreadcrumb( message: $"Request to {context.Request.Path}", @@ -135,7 +134,7 @@ public class GlobalErrorHandlingMiddleware level: BreadcrumbLevel.Info ); }); - + // Use a more user-friendly error message in production if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production") { @@ -154,7 +153,7 @@ public class GlobalErrorHandlingMiddleware { errorMessage = exception.Message; } - + // Create the error response var errorResponse = new ErrorResponse { @@ -162,23 +161,23 @@ public class GlobalErrorHandlingMiddleware Message = errorMessage, TraceId = sentryId.ToString() }; - + // Only include stack trace in development environment if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "Production") { errorResponse.StackTrace = exception.StackTrace; } - + var result = JsonSerializer.Serialize(errorResponse, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - + context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)status; return context.Response.WriteAsync(result); } - + // Custom error response class private class ErrorResponse { diff --git a/src/Managing.Core/Middleawares/SentryDiagnosticsMiddleware.cs b/src/Managing.Core/Middleawares/SentryDiagnosticsMiddleware.cs index c7bfd805..fb018ea6 100644 --- a/src/Managing.Core/Middleawares/SentryDiagnosticsMiddleware.cs +++ b/src/Managing.Core/Middleawares/SentryDiagnosticsMiddleware.cs @@ -2,7 +2,6 @@ using System.Text; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using Sentry; namespace Managing.Core.Middleawares; diff --git a/src/Managing.Domain.Tests/Managing.Domain.SimpleTests/UnitTest1.cs b/src/Managing.Domain.Tests/Managing.Domain.SimpleTests/UnitTest1.cs index f7a95149..6b909003 100644 --- a/src/Managing.Domain.Tests/Managing.Domain.SimpleTests/UnitTest1.cs +++ b/src/Managing.Domain.Tests/Managing.Domain.SimpleTests/UnitTest1.cs @@ -1,7 +1,7 @@ using FluentAssertions; using Managing.Common; +using Managing.Domain.Candles; using Managing.Domain.Shared.Helpers; -using static Managing.Common.Enums; using Xunit; namespace Managing.Domain.SimpleTests; @@ -16,12 +16,12 @@ public class SimpleTradingBoxTests public void GetHodlPercentage_WithPriceIncrease_CalculatesCorrectPercentage() { // Arrange - var candle1 = new Managing.Domain.Candles.Candle + var candle1 = new Candle { Close = 100m, Date = DateTime.UtcNow }; - var candle2 = new Managing.Domain.Candles.Candle + var candle2 = new Candle { Close = 110m, Date = DateTime.UtcNow.AddHours(1) @@ -38,12 +38,12 @@ public class SimpleTradingBoxTests public void GetHodlPercentage_WithPriceDecrease_CalculatesNegativePercentage() { // Arrange - var candle1 = new Managing.Domain.Candles.Candle + var candle1 = new Candle { Close = 100m, Date = DateTime.UtcNow }; - var candle2 = new Managing.Domain.Candles.Candle + var candle2 = new Candle { Close = 90m, Date = DateTime.UtcNow.AddHours(1) @@ -97,4 +97,4 @@ public class SimpleTradingBoxTests // Assert result.Should().Be(fee); } -} +} \ No newline at end of file diff --git a/src/Managing.Domain.Tests/SimpleTradingBoxTests.cs b/src/Managing.Domain.Tests/SimpleTradingBoxTests.cs index 241afbae..529e4d1c 100644 --- a/src/Managing.Domain.Tests/SimpleTradingBoxTests.cs +++ b/src/Managing.Domain.Tests/SimpleTradingBoxTests.cs @@ -1,15 +1,6 @@ using FluentAssertions; -using Managing.Common; -using Managing.Domain.Accounts; using Managing.Domain.Candles; -using Managing.Domain.Indicators; -using Managing.Domain.MoneyManagements; -using Managing.Domain.Scenarios; using Managing.Domain.Shared.Helpers; -using Managing.Domain.Statistics; -using Managing.Domain.Strategies; -using Managing.Domain.Strategies.Base; -using Managing.Domain.Trades; using Xunit; using static Managing.Common.Enums; @@ -25,12 +16,12 @@ public class SimpleTradingBoxTests public void GetHodlPercentage_WithPriceIncrease_CalculatesCorrectPercentage() { // Arrange - var candle1 = new Managing.Domain.Candles.Candle + var candle1 = new Candle { Close = 100m, Date = DateTime.UtcNow }; - var candle2 = new Managing.Domain.Candles.Candle + var candle2 = new Candle { Close = 110m, Date = DateTime.UtcNow.AddHours(1) @@ -47,12 +38,12 @@ public class SimpleTradingBoxTests public void GetHodlPercentage_WithPriceDecrease_CalculatesNegativePercentage() { // Arrange - var candle1 = new Managing.Domain.Candles.Candle + var candle1 = new Candle { Close = 100m, Date = DateTime.UtcNow }; - var candle2 = new Managing.Domain.Candles.Candle + var candle2 = new Candle { Close = 90m, Date = DateTime.UtcNow.AddHours(1) @@ -106,4 +97,4 @@ public class SimpleTradingBoxTests // Assert result.Should().Be(fee); } -} +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Tests/PrivyTradingTests.cs b/src/Managing.Infrastructure.Tests/PrivyTradingTests.cs index 06064a74..8a6fe6a7 100644 --- a/src/Managing.Infrastructure.Tests/PrivyTradingTests.cs +++ b/src/Managing.Infrastructure.Tests/PrivyTradingTests.cs @@ -1,11 +1,6 @@ using Managing.Common; using Managing.Domain.Trades; -using Managing.Infrastructure.Evm; -using Managing.Infrastructure.Evm.Models.Privy; -using Managing.Infrastructure.Evm.Services; using Xunit; -using Managing.Infrastructure.Evm.Abstractions; -using Microsoft.Extensions.Options; namespace Managing.Infrastructure.Tests; diff --git a/src/Managing.Infrastructure.Web3/Models/Proxy/GetGmxTradesResponse.cs b/src/Managing.Infrastructure.Web3/Models/Proxy/GetGmxTradesResponse.cs index f1e79b2e..e6e65f66 100644 --- a/src/Managing.Infrastructure.Web3/Models/Proxy/GetGmxTradesResponse.cs +++ b/src/Managing.Infrastructure.Web3/Models/Proxy/GetGmxTradesResponse.cs @@ -1,12 +1,10 @@ using System.Text.Json.Serialization; -using Managing.Domain.Trades; using Newtonsoft.Json; namespace Managing.Infrastructure.Evm.Models.Proxy; public class GetGmxTradesResponse : Web3ProxyBaseResponse { - [JsonProperty("trades")] [JsonPropertyName("trades")] public List Trades { get; set; } @@ -53,4 +51,4 @@ public class GmxTrade [JsonProperty("exchangeOrderId")] [JsonPropertyName("exchangeOrderId")] public string ExchangeOrderId { get; set; } -} +} \ No newline at end of file