From bcfeb693ce775452b152b12d9bee7fc38506fdd6 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Fri, 26 Sep 2025 01:18:59 +0700 Subject: [PATCH] Update account/position and platform summary --- .../Controllers/TradingController.cs | 3 +- .../Repositories/IAccountRepository.cs | 1 + .../Services/IAccountService.cs | 9 + .../PositionTests.cs | 4 +- .../ProfitAndLossTests.cs | 6 +- .../Accounts/AccountService.cs | 19 + .../Bots/TradingBotBase.cs | 84 +- .../ManageBot/BotService.cs | 17 +- .../Shared/MessengerService.cs | 1 - .../Trading/Commands/ClosePositionCommand.cs | 4 +- .../Handlers/ClosePositionCommandHandler.cs | 13 +- .../Handlers/OpenPositionCommandHandler.cs | 21 +- src/Managing.Common/Constants.cs | 4 + src/Managing.Domain/Accounts/Account.cs | 17 +- .../Shared/Helpers/TradingHelpers.cs | 92 ++ src/Managing.Domain/Trades/Position.cs | 65 +- ...RemoveAccountNameFromPositions.Designer.cs | 1436 ++++++++++++++++ ...25173424_RemoveAccountNameFromPositions.cs | 63 + ...174620_AddAccountIdToPositions.Designer.cs | 1439 +++++++++++++++++ .../20250925174620_AddAccountIdToPositions.cs | 29 + .../ManagingDbContextModelSnapshot.cs | 15 +- .../PostgreSql/Entities/PositionEntity.cs | 10 +- .../PostgreSql/Entities/TradeEntity.cs | 3 - .../PostgreSql/ManagingDbContext.cs | 12 +- .../PostgreSql/PostgreSqlAccountRepository.cs | 21 + .../PostgreSql/PostgreSqlMappers.cs | 18 +- .../PostgreSql/PostgreSqlTradingRepository.cs | 12 +- .../Discord/DiscordService.cs | 5 +- .../Services/Gmx/GmxV2Mappers.cs | 2 +- src/Managing.Web3Proxy/package-lock.json | 23 +- src/Managing.Web3Proxy/package.json | 2 +- .../src/plugins/custom/gmx.ts | 2 +- 32 files changed, 3301 insertions(+), 151 deletions(-) create mode 100644 src/Managing.Infrastructure.Database/Migrations/20250925173424_RemoveAccountNameFromPositions.Designer.cs create mode 100644 src/Managing.Infrastructure.Database/Migrations/20250925173424_RemoveAccountNameFromPositions.cs create mode 100644 src/Managing.Infrastructure.Database/Migrations/20250925174620_AddAccountIdToPositions.Designer.cs create mode 100644 src/Managing.Infrastructure.Database/Migrations/20250925174620_AddAccountIdToPositions.cs diff --git a/src/Managing.Api/Controllers/TradingController.cs b/src/Managing.Api/Controllers/TradingController.cs index ea33b155..55e33eae 100644 --- a/src/Managing.Api/Controllers/TradingController.cs +++ b/src/Managing.Api/Controllers/TradingController.cs @@ -96,7 +96,8 @@ public class TradingController : BaseController public async Task> ClosePosition(Guid identifier) { var position = await _tradingService.GetPositionByIdentifierAsync(identifier); - var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position)); + + var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position, position.AccountId)); return Ok(result); } diff --git a/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs b/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs index 743118e3..60c5e8a4 100644 --- a/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IAccountRepository.cs @@ -5,6 +5,7 @@ namespace Managing.Application.Abstractions.Repositories; public interface IAccountRepository { Task GetAccountByNameAsync(string name); + Task GetAccountByIdAsync(int id); Task GetAccountByKeyAsync(string key); Task InsertAccountAsync(Account account); Task UpdateAccountAsync(Account account); diff --git a/src/Managing.Application.Abstractions/Services/IAccountService.cs b/src/Managing.Application.Abstractions/Services/IAccountService.cs index c8171b04..88bc9722 100644 --- a/src/Managing.Application.Abstractions/Services/IAccountService.cs +++ b/src/Managing.Application.Abstractions/Services/IAccountService.cs @@ -24,6 +24,15 @@ public interface IAccountService /// The found account or null if not found Task GetAccountByAccountName(string accountName, bool hideSecrets = true, bool getBalance = false); + /// + /// Gets an account by ID directly from the repository. + /// + /// The ID of the account to find + /// Whether to hide sensitive information + /// Whether to fetch the current balance + /// The found account or null if not found + Task GetAccountById(int accountId, bool hideSecrets = true, bool getBalance = false); + IEnumerable GetAccountsBalancesByUser(User user, bool hideSecrets = true); Task> GetAccountsBalancesByUserAsync(User user, bool hideSecrets = true); Task GetGmxClaimableSummaryAsync(User user, string accountName); diff --git a/src/Managing.Application.Tests/PositionTests.cs b/src/Managing.Application.Tests/PositionTests.cs index 20cd7c8f..7b004aad 100644 --- a/src/Managing.Application.Tests/PositionTests.cs +++ b/src/Managing.Application.Tests/PositionTests.cs @@ -46,13 +46,13 @@ public class PositionTests : BaseTests // _ = new GetAccountPositioqwnInfoListOutputDTO().DecodeOutput(hexPositions).d // var openTrade = await _exchangeService.GetTrade(_account, "", Ticker.GMX); - var position = new Position(Guid.NewGuid(), "", TradeDirection.Long, Ticker.GMX, MoneyManagement, + var position = new Position(Guid.NewGuid(), 1, TradeDirection.Long, Ticker.GMX, MoneyManagement, PositionInitiator.User, DateTime.UtcNow, new User()) { Open = openTrade }; - var command = new ClosePositionCommand(position); + var command = new ClosePositionCommand(position, 1); _ = _tradingService.Setup(m => m.GetPositionByIdentifierAsync(It.IsAny())).ReturnsAsync(position); _ = _tradingService.Setup(m => m.GetPositionByIdentifierAsync(It.IsAny())).ReturnsAsync(position); diff --git a/src/Managing.Application.Tests/ProfitAndLossTests.cs b/src/Managing.Application.Tests/ProfitAndLossTests.cs index 6b4a6c26..23073aef 100644 --- a/src/Managing.Application.Tests/ProfitAndLossTests.cs +++ b/src/Managing.Application.Tests/ProfitAndLossTests.cs @@ -214,7 +214,7 @@ namespace Managing.Application.Tests private static Position GetFakeShortPosition() { - return new Position(Guid.NewGuid(), "FakeAccount", TradeDirection.Short, Ticker.BTC, null, + return new Position(Guid.NewGuid(), 1, TradeDirection.Short, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow, new User()) { Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled, @@ -230,7 +230,7 @@ namespace Managing.Application.Tests private static Position GetSolanaLongPosition() { - return new Position(Guid.NewGuid(), "FakeAccount", TradeDirection.Long, Ticker.BTC, null, + return new Position(Guid.NewGuid(), 1, TradeDirection.Long, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow, new User()) { Open = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.Filled, @@ -250,7 +250,7 @@ namespace Managing.Application.Tests private static Position GetFakeLongPosition() { - return new Position(Guid.NewGuid(), "FakeAccount", TradeDirection.Long, Ticker.BTC, null, + return new Position(Guid.NewGuid(), 1, TradeDirection.Long, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow, new User()) { Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled, diff --git a/src/Managing.Application/Accounts/AccountService.cs b/src/Managing.Application/Accounts/AccountService.cs index f199f4f4..d83a9634 100644 --- a/src/Managing.Application/Accounts/AccountService.cs +++ b/src/Managing.Application/Accounts/AccountService.cs @@ -149,6 +149,25 @@ public class AccountService : IAccountService return account; } + public async Task GetAccountById(int accountId, bool hideSecrets = true, bool getBalance = false) + { + var account = await _accountRepository.GetAccountByIdAsync(accountId); + + if (account == null) + { + throw new ArgumentException($"Account with ID '{accountId}' not found"); + } + + await ManagePropertiesAsync(hideSecrets, getBalance, account); + + if (account.User != null) + { + account.User = await _userRepository.GetUserByNameAsync(account.User.Name); + } + + return account; + } + public async Task> GetAccounts(bool hideSecrets, bool getBalance) { return await GetAccountsAsync(hideSecrets, getBalance); diff --git a/src/Managing.Application/Bots/TradingBotBase.cs b/src/Managing.Application/Bots/TradingBotBase.cs index 84bfced6..a9d85fb8 100644 --- a/src/Managing.Application/Bots/TradingBotBase.cs +++ b/src/Managing.Application/Bots/TradingBotBase.cs @@ -398,8 +398,10 @@ public class TradingBotBase : ITradingBot var brokerPosition = brokerPositions.FirstOrDefault(p => p.Ticker == Config.Ticker); if (brokerPosition != null) { - UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized); - internalPosition.ProfitAndLoss = brokerPosition.ProfitAndLoss; + // Calculate net PnL after fees for broker position + var brokerNetPnL = brokerPosition.GetNetPnL(); + UpdatePositionPnl(positionForSignal.Identifier, brokerNetPnL); + internalPosition.ProfitAndLoss = new ProfitAndLoss { Realized = brokerNetPnL }; internalPosition.Status = PositionStatus.Filled; // Update Open trade status when position is found on broker @@ -510,7 +512,9 @@ public class TradingBotBase : ITradingBot await LogInformation( $"✅ **Position Found on Broker**\nPosition is already open on broker\nUpdating position status to Filled"); - UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized); + // Calculate net PnL after fees for broker position + var brokerNetPnL = brokerPosition.GetNetPnL(); + UpdatePositionPnl(positionForSignal.Identifier, brokerNetPnL); // Update Open trade status when position is found on broker with 2 orders if (internalPosition.Open != null) @@ -779,7 +783,12 @@ public class TradingBotBase : ITradingBot { // Update the internal position with broker data internalPosition.Status = PositionStatus.Filled; - internalPosition.ProfitAndLoss = internalPosition.ProfitAndLoss; + // Apply fees to the internal position PnL before saving + if (internalPosition.ProfitAndLoss != null) + { + var totalFees = internalPosition.CalculateTotalFees(); + internalPosition.ProfitAndLoss.Realized = internalPosition.ProfitAndLoss.Realized - totalFees; + } // Update Open trade status when position is updated to Filled if (internalPosition.Open != null) @@ -1129,7 +1138,7 @@ public class TradingBotBase : ITradingBot } else { - var command = new ClosePositionCommand(position, lastPrice, isForBacktest: Config.IsForBacktest); + var command = new ClosePositionCommand(position, position.AccountId, lastPrice, isForBacktest: Config.IsForBacktest); try { Position closedPosition = null; @@ -1375,6 +1384,9 @@ public class TradingBotBase : ITradingBot { position.ProfitAndLoss.Realized = pnl; } + + // Fees are now tracked separately in UiFees and GasFees properties + // No need to subtract fees from PnL as they're tracked separately } await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished); @@ -1536,14 +1548,15 @@ public class TradingBotBase : ITradingBot public decimal GetProfitAndLoss() { - var pnl = Positions.Values.Where(p => p.ProfitAndLoss != null && p.IsFinished()) - .Sum(p => p.ProfitAndLoss.Realized); - return pnl - GetTotalFees(); + // Calculate net PnL after deducting fees for each position + var netPnl = Positions.Values.Where(p => p.ProfitAndLoss != null && p.IsFinished()) + .Sum(p => p.GetNetPnL()); + return netPnl; } /// /// Calculates the total fees paid by the trading bot for each position. - /// Includes UI fees (0.1% of position size) and network fees ($0.50 for opening). + /// Includes UI fees (0.1% of position size) and network fees ($0.15 for opening). /// Closing fees are handled by oracle, so no network fee for closing. /// /// Returns the total fees paid as a decimal value. @@ -1553,63 +1566,12 @@ public class TradingBotBase : ITradingBot foreach (var position in Positions.Values.Where(p => p.Open.Price > 0 && p.Open.Quantity > 0)) { - totalFees += CalculatePositionFees(position); + totalFees += TradingHelpers.CalculatePositionFees(position); } return totalFees; } - /// - /// Calculates the total fees for a specific position based on GMX V2 fee structure - /// - /// The position to calculate fees for - /// The total fees for the position - private decimal CalculatePositionFees(Position position) - { - decimal fees = 0; - - // Calculate position size in USD (leverage is already included in quantity calculation) - var positionSizeUsd = (position.Open.Price * position.Open.Quantity) * position.Open.Leverage; - - // UI Fee: 0.1% of position size paid on opening - var uiFeeRate = 0.001m; // 0.1% - var uiFeeOpen = positionSizeUsd * uiFeeRate; // Fee paid on opening - fees += uiFeeOpen; - - // UI Fee: 0.1% of position size paid on closing - only if position was actually closed - // Check which closing trade was executed (StopLoss, TakeProfit1, or TakeProfit2) - // Calculate closing fee based on the actual executed trade's price and quantity - if (position.StopLoss?.Status == TradeStatus.Filled) - { - var stopLossPositionSizeUsd = - (position.StopLoss.Price * position.StopLoss.Quantity) * position.StopLoss.Leverage; - var uiFeeClose = stopLossPositionSizeUsd * uiFeeRate; // Fee paid on closing via StopLoss - fees += uiFeeClose; - } - else if (position.TakeProfit1?.Status == TradeStatus.Filled) - { - var takeProfit1PositionSizeUsd = (position.TakeProfit1.Price * position.TakeProfit1.Quantity) * - position.TakeProfit1.Leverage; - var uiFeeClose = takeProfit1PositionSizeUsd * uiFeeRate; // Fee paid on closing via TakeProfit1 - fees += uiFeeClose; - } - else if (position.TakeProfit2?.Status == TradeStatus.Filled) - { - var takeProfit2PositionSizeUsd = (position.TakeProfit2.Price * position.TakeProfit2.Quantity) * - position.TakeProfit2.Leverage; - var uiFeeClose = takeProfit2PositionSizeUsd * uiFeeRate; // Fee paid on closing via TakeProfit2 - fees += uiFeeClose; - } - - // Network Fee: $0.50 for opening position only - // Closing is handled by oracle, so no network fee for closing - var networkFeeForOpening = 0.15m; - fees += networkFeeForOpening; - - return fees; - } - - public async Task ToggleIsForWatchOnly() { Config.IsForWatchingOnly = !Config.IsForWatchingOnly; diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index 05f57552..ea48082a 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -265,26 +265,15 @@ namespace Managing.Application.ManageBot // Calculate statistics using TradingBox helpers var (tradeWins, tradeLosses) = TradingBox.GetWinLossCount(botData.Positions); var pnl = botData.ProfitAndLoss; - var fees = botData.Positions.Values.Sum(p => - { - if (p.Open.Price > 0 && p.Open.Quantity > 0) - { - var positionSizeUsd = (p.Open.Price * p.Open.Quantity) * p.Open.Leverage; - var uiFeeRate = 0.001m; // 0.1% - var uiFeeOpen = positionSizeUsd * uiFeeRate; - var networkFeeForOpening = 0.50m; - return uiFeeOpen + networkFeeForOpening; - } - - return 0; - }); + var fees = botData.Positions.Values.Sum(p => p.CalculateTotalFees()); var volume = TradingBox.GetTotalVolumeTraded(botData.Positions); // Calculate ROI based on total investment var totalInvestment = botData.Positions.Values .Where(p => p.IsFinished()) .Sum(p => p.Open.Quantity * p.Open.Price); - var roi = totalInvestment > 0 ? (pnl / totalInvestment) * 100 : 0; + var netPnl = pnl - fees; + var roi = totalInvestment > 0 ? (netPnl / totalInvestment) * 100 : 0; // Update bot statistics existingBot.TradeWins = tradeWins; diff --git a/src/Managing.Application/Shared/MessengerService.cs b/src/Managing.Application/Shared/MessengerService.cs index 5dd65f01..c3d988fc 100644 --- a/src/Managing.Application/Shared/MessengerService.cs +++ b/src/Managing.Application/Shared/MessengerService.cs @@ -94,7 +94,6 @@ public class MessengerService : IMessengerService var message = $"🎯 Position {status}\n" + $"Symbol: {position.Ticker}\n" + $"Direction: {direction}\n" + - $"Account: {position.AccountName}\n" + $"Identifier: {position.Identifier}\n" + $"Initiator: {position.Initiator}\n" + $"Date: {position.Date:yyyy-MM-dd HH:mm:ss}"; diff --git a/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs b/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs index 7939c401..1cb016c0 100644 --- a/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs +++ b/src/Managing.Application/Trading/Commands/ClosePositionCommand.cs @@ -5,14 +5,16 @@ namespace Managing.Application.Trading.Commands { public class ClosePositionCommand : IRequest { - public ClosePositionCommand(Position position, decimal? executionPrice = null, bool isForBacktest = false) + public ClosePositionCommand(Position position, int accountId, decimal? executionPrice = null, bool isForBacktest = false) { Position = position; + AccountId = accountId; ExecutionPrice = executionPrice; IsForBacktest = isForBacktest; } public Position Position { get; } + public int AccountId { get; } public decimal? ExecutionPrice { get; set; } public bool IsForBacktest { get; set; } } diff --git a/src/Managing.Application/Trading/Handlers/ClosePositionCommandHandler.cs b/src/Managing.Application/Trading/Handlers/ClosePositionCommandHandler.cs index 8fe37953..adf91655 100644 --- a/src/Managing.Application/Trading/Handlers/ClosePositionCommandHandler.cs +++ b/src/Managing.Application/Trading/Handlers/ClosePositionCommandHandler.cs @@ -22,7 +22,7 @@ public class ClosePositionCommandHandler( try { // Get Trade - var account = await accountService.GetAccount(request.Position.AccountName, false, false); + var account = await accountService.GetAccountById(request.AccountId, false, false); if (request.Position == null) { _ = await exchangeService.CancelOrder(account, request.Position.Ticker); @@ -48,6 +48,12 @@ public class ClosePositionCommandHandler( request.Position.ProfitAndLoss = TradingBox.GetProfitAndLoss(request.Position, request.Position.Open.Quantity, lastPrice, request.Position.Open.Leverage); + + // Add UI fees for closing the position (broker closed it) + var closingPositionSizeUsd = (lastPrice * request.Position.Open.Quantity) * request.Position.Open.Leverage; + var closingUiFees = TradingHelpers.CalculateClosingUiFees(closingPositionSizeUsd); + request.Position.AddUiFees(closingUiFees); + await tradingService.UpdatePositionAsync(request.Position); return request.Position; } @@ -68,6 +74,11 @@ public class ClosePositionCommandHandler( TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice, request.Position.Open.Leverage); + // Add UI fees for closing the position + var closingPositionSizeUsd = (lastPrice * closedPosition.Quantity) * request.Position.Open.Leverage; + var closingUiFees = TradingHelpers.CalculateClosingUiFees(closingPositionSizeUsd); + request.Position.AddUiFees(closingUiFees); + if (!request.IsForBacktest) await tradingService.UpdatePositionAsync(request.Position); } diff --git a/src/Managing.Application/Trading/Handlers/OpenPositionCommandHandler.cs b/src/Managing.Application/Trading/Handlers/OpenPositionCommandHandler.cs index e7eec14d..d8655a91 100644 --- a/src/Managing.Application/Trading/Handlers/OpenPositionCommandHandler.cs +++ b/src/Managing.Application/Trading/Handlers/OpenPositionCommandHandler.cs @@ -21,7 +21,7 @@ namespace Managing.Application.Trading.Handlers 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(), request.AccountName, request.Direction, + var position = new Position(Guid.NewGuid(), account.Id, request.Direction, request.Ticker, request.MoneyManagement, initiator, request.Date, request.User); @@ -45,9 +45,10 @@ namespace Managing.Application.Trading.Handlers } // Gas fee check for EVM exchanges + decimal gasFeeUsd = 0; if (account.Exchange == TradingExchanges.Evm || account.Exchange == TradingExchanges.GmxV2) { - var gasFeeUsd = await exchangeService.GetFee(account); + gasFeeUsd = await exchangeService.GetFee(account); if (gasFeeUsd > Constants.GMX.Config.MaximumGasFeeUsd) { throw new InsufficientFundsException( @@ -84,6 +85,22 @@ namespace Managing.Application.Trading.Handlers position.Open = trade; + // Calculate and set fees for the position + var positionSizeUsd = (position.Open.Price * position.Open.Quantity) * position.Open.Leverage; + + // Set gas fees (only for EVM exchanges) + if (account.Exchange == TradingExchanges.Evm || account.Exchange == TradingExchanges.GmxV2) + { + position.GasFees = gasFeeUsd; + } + else + { + position.GasFees = TradingHelpers.CalculateOpeningGasFees(); + } + + // Set UI fees for opening + position.UiFees = TradingHelpers.CalculateOpeningUiFees(positionSizeUsd); + var closeDirection = request.Direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long; diff --git a/src/Managing.Common/Constants.cs b/src/Managing.Common/Constants.cs index c973bdd8..29e934c7 100644 --- a/src/Managing.Common/Constants.cs +++ b/src/Managing.Common/Constants.cs @@ -112,6 +112,10 @@ namespace Managing.Common public static decimal MinimumSwapEthBalanceUsd = 1m; public const decimal MaximumGasFeeUsd = 1.5m; public const double AutoSwapAmount = 3; + + // Fee Configuration + public const decimal UiFeeRate = 0.001m; // 0.1% UI fee rate + public const decimal GasFeePerTransaction = 0.15m; // $0.15 gas fee per transaction } public class TokenAddress diff --git a/src/Managing.Domain/Accounts/Account.cs b/src/Managing.Domain/Accounts/Account.cs index fead50f2..82926feb 100644 --- a/src/Managing.Domain/Accounts/Account.cs +++ b/src/Managing.Domain/Accounts/Account.cs @@ -9,27 +9,30 @@ namespace Managing.Domain.Accounts; public class Account { [Id(0)] - [Required] public string Name { get; set; } + public int Id { get; set; } [Id(1)] - [Required] public TradingExchanges Exchange { get; set; } + [Required] public string Name { get; set; } [Id(2)] - [Required] public AccountType Type { get; set; } + [Required] public TradingExchanges Exchange { get; set; } [Id(3)] - public string Key { get; set; } + [Required] public AccountType Type { get; set; } [Id(4)] - public string Secret { get; set; } + public string Key { get; set; } [Id(5)] - public User User { get; set; } + public string Secret { get; set; } [Id(6)] - public List Balances { get; set; } + public User User { get; set; } [Id(7)] + public List Balances { get; set; } + + [Id(8)] public bool IsGmxInitialized { get; set; } = false; public bool IsPrivyWallet => Type == AccountType.Privy; diff --git a/src/Managing.Domain/Shared/Helpers/TradingHelpers.cs b/src/Managing.Domain/Shared/Helpers/TradingHelpers.cs index 28813c0f..7accdfd5 100644 --- a/src/Managing.Domain/Shared/Helpers/TradingHelpers.cs +++ b/src/Managing.Domain/Shared/Helpers/TradingHelpers.cs @@ -1,7 +1,9 @@ using Exilion.TradingAtomics; +using Managing.Common; using Managing.Domain.Accounts; using Managing.Domain.Candles; using Managing.Domain.Statistics; +using Managing.Domain.Trades; using static Managing.Common.Enums; namespace Managing.Domain.Shared.Helpers; @@ -97,4 +99,94 @@ public static class TradingHelpers return traders; } + + /// + /// Calculates the total fees for a position based on GMX V2 fee structure + /// + /// The position to calculate fees for + /// The total fees for the position + public static decimal CalculatePositionFees(Position position) + { + var (uiFees, gasFees) = CalculatePositionFeesBreakdown(position); + return uiFees + gasFees; + } + + /// + /// Calculates the UI and Gas fees breakdown for a position based on GMX V2 fee structure + /// + /// The position to calculate fees for + /// A tuple containing (uiFees, gasFees) + public static (decimal uiFees, decimal gasFees) CalculatePositionFeesBreakdown(Position position) + { + decimal uiFees = 0; + decimal gasFees = 0; + + if (position?.Open?.Price <= 0 || position?.Open?.Quantity <= 0) + { + return (uiFees, gasFees); // Return 0 if position data is invalid + } + + // Calculate position size in USD (leverage is already included in quantity calculation) + var positionSizeUsd = (position.Open.Price * position.Open.Quantity) * position.Open.Leverage; + + // UI Fee: 0.1% of position size paid on opening + var uiFeeOpen = positionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on opening + uiFees += uiFeeOpen; + + // UI Fee: 0.1% of position size paid on closing - only if position was actually closed + // Check which closing trade was executed (StopLoss, TakeProfit1, or TakeProfit2) + if (position.StopLoss?.Status == TradeStatus.Filled) + { + var stopLossPositionSizeUsd = (position.StopLoss.Price * position.StopLoss.Quantity) * position.StopLoss.Leverage; + var uiFeeClose = stopLossPositionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on closing via StopLoss + uiFees += uiFeeClose; + } + else if (position.TakeProfit1?.Status == TradeStatus.Filled) + { + var takeProfit1PositionSizeUsd = (position.TakeProfit1.Price * position.TakeProfit1.Quantity) * position.TakeProfit1.Leverage; + var uiFeeClose = takeProfit1PositionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on closing via TakeProfit1 + uiFees += uiFeeClose; + } + else if (position.TakeProfit2?.Status == TradeStatus.Filled) + { + var takeProfit2PositionSizeUsd = (position.TakeProfit2.Price * position.TakeProfit2.Quantity) * position.TakeProfit2.Leverage; + var uiFeeClose = takeProfit2PositionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on closing via TakeProfit2 + uiFees += uiFeeClose; + } + + // Gas Fee: $0.15 for opening position only + // Closing is handled by oracle, so no gas fee for closing + gasFees += Constants.GMX.Config.GasFeePerTransaction; + + return (uiFees, gasFees); + } + + /// + /// Calculates UI fees for opening a position + /// + /// The position size in USD + /// The UI fees for opening + public static decimal CalculateOpeningUiFees(decimal positionSizeUsd) + { + return positionSizeUsd * Constants.GMX.Config.UiFeeRate; + } + + /// + /// Calculates UI fees for closing a position + /// + /// The position size in USD + /// The UI fees for closing + public static decimal CalculateClosingUiFees(decimal positionSizeUsd) + { + return positionSizeUsd * Constants.GMX.Config.UiFeeRate; + } + + /// + /// Calculates gas fees for opening a position + /// + /// The gas fees for opening (fixed at $0.15) + public static decimal CalculateOpeningGasFees() + { + return Constants.GMX.Config.GasFeePerTransaction; + } } \ No newline at end of file diff --git a/src/Managing.Domain/Trades/Position.cs b/src/Managing.Domain/Trades/Position.cs index 637566c0..c8cdb64f 100644 --- a/src/Managing.Domain/Trades/Position.cs +++ b/src/Managing.Domain/Trades/Position.cs @@ -9,11 +9,11 @@ namespace Managing.Domain.Trades [GenerateSerializer] public class Position { - public Position(Guid identifier, string accountName, TradeDirection originDirection, Ticker ticker, + public Position(Guid identifier, int accountId, TradeDirection originDirection, Ticker ticker, LightMoneyManagement moneyManagement, PositionInitiator initiator, DateTime date, User user) { Identifier = identifier; - AccountName = accountName; + AccountId = accountId; OriginDirection = originDirection; Ticker = ticker; MoneyManagement = moneyManagement; @@ -23,9 +23,9 @@ namespace Managing.Domain.Trades User = user; } - [Id(0)] [Required] public string AccountName { get; set; } + [Id(0)] [Required] public DateTime Date { get; set; } - [Id(1)] [Required] public DateTime Date { get; set; } + [Id(1)] [Required] public int AccountId { get; set; } [Id(2)] [Required] public TradeDirection OriginDirection { get; set; } @@ -56,20 +56,24 @@ namespace Managing.Domain.Trades [JsonPropertyName("ProfitAndLoss")] public ProfitAndLoss ProfitAndLoss { get; set; } - [Id(10)] [Required] public PositionStatus Status { get; set; } + [Id(10)] public decimal UiFees { get; set; } - [Id(11)] public string SignalIdentifier { get; set; } + [Id(11)] public decimal GasFees { get; set; } - [Id(12)] [Required] public Guid Identifier { get; set; } + [Id(12)] [Required] public PositionStatus Status { get; set; } - [Id(13)] [Required] public PositionInitiator Initiator { get; set; } + [Id(13)] public string SignalIdentifier { get; set; } - [Id(14)] [Required] public User User { get; set; } + [Id(14)] [Required] public Guid Identifier { get; set; } + + [Id(15)] [Required] public PositionInitiator Initiator { get; set; } + + [Id(16)] [Required] public User User { get; set; } /// /// Identifier of the bot or entity that initiated this position /// - [Id(15)] [Required] public Guid InitiatorIdentifier { get; set; } + [Id(17)] [Required] public Guid InitiatorIdentifier { get; set; } public bool IsFinished() { @@ -80,5 +84,46 @@ namespace Managing.Domain.Trades _ => false }; } + + /// + /// Calculates the total fees for this position based on GMX V2 fee structure + /// + /// The total fees for the position + public decimal CalculateTotalFees() + { + return UiFees + GasFees; + } + + /// + /// Gets the net PnL after deducting fees + /// + /// The net PnL after fees + public decimal GetNetPnL() + { + if (ProfitAndLoss?.Realized == null) + { + return 0; + } + + return ProfitAndLoss.Realized - CalculateTotalFees(); + } + + /// + /// Updates the UI fees for this position + /// + /// The UI fees to add + public void AddUiFees(decimal uiFees) + { + UiFees += uiFees; + } + + /// + /// Updates the gas fees for this position + /// + /// The gas fees to add + public void AddGasFees(decimal gasFees) + { + GasFees += gasFees; + } } } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/Migrations/20250925173424_RemoveAccountNameFromPositions.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250925173424_RemoveAccountNameFromPositions.Designer.cs new file mode 100644 index 00000000..98bfc7d7 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250925173424_RemoveAccountNameFromPositions.Designer.cs @@ -0,0 +1,1436 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250925173424_RemoveAccountNameFromPositions")] + partial class RemoveAccountNameFromPositions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsGmxInitialized") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Key") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActiveStrategiesCount") + .HasColumnType("integer"); + + b.Property("AgentName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Losses") + .HasColumnType("integer"); + + b.Property("Runtime") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalBalance") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("TotalPnL") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalROI") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalVolume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Wins") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AgentName"); + + b.HasIndex("TotalPnL"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("AgentSummaries"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("UserId"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserId", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("LongPositionCount") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("ShortPositionCount") + .HasColumnType("integer"); + + b.Property("StartupTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("TradeLosses") + .HasColumnType("integer"); + + b.Property("TradeWins") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.HasKey("Identifier"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("Bots"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("GasFees") + .HasColumnType("decimal(18,8)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("InitiatorIdentifier") + .HasColumnType("uuid"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UiFees") + .HasColumnType("decimal(18,8)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Identifier"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("InitiatorIdentifier"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Date"); + + b.HasIndex("Identifier", "Date", "UserId") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250925173424_RemoveAccountNameFromPositions.cs b/src/Managing.Infrastructure.Database/Migrations/20250925173424_RemoveAccountNameFromPositions.cs new file mode 100644 index 00000000..a9db765c --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250925173424_RemoveAccountNameFromPositions.cs @@ -0,0 +1,63 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class RemoveAccountNameFromPositions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Fee", + table: "Trades"); + + migrationBuilder.DropColumn( + name: "AccountName", + table: "Positions"); + + migrationBuilder.AddColumn( + name: "GasFees", + table: "Positions", + type: "numeric(18,8)", + nullable: false, + defaultValue: 0m); + + migrationBuilder.AddColumn( + name: "UiFees", + table: "Positions", + type: "numeric(18,8)", + nullable: false, + defaultValue: 0m); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "GasFees", + table: "Positions"); + + migrationBuilder.DropColumn( + name: "UiFees", + table: "Positions"); + + migrationBuilder.AddColumn( + name: "Fee", + table: "Trades", + type: "numeric(18,8)", + nullable: false, + defaultValue: 0m); + + migrationBuilder.AddColumn( + name: "AccountName", + table: "Positions", + type: "character varying(255)", + maxLength: 255, + nullable: false, + defaultValue: ""); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250925174620_AddAccountIdToPositions.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250925174620_AddAccountIdToPositions.Designer.cs new file mode 100644 index 00000000..3a8d358b --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250925174620_AddAccountIdToPositions.Designer.cs @@ -0,0 +1,1439 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250925174620_AddAccountIdToPositions")] + partial class AddAccountIdToPositions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsGmxInitialized") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Key") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActiveStrategiesCount") + .HasColumnType("integer"); + + b.Property("AgentName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Losses") + .HasColumnType("integer"); + + b.Property("Runtime") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalBalance") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("TotalPnL") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalROI") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalVolume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Wins") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AgentName"); + + b.HasIndex("TotalPnL"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("AgentSummaries"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("UserId"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserId", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("LongPositionCount") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("ShortPositionCount") + .HasColumnType("integer"); + + b.Property("StartupTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("TradeLosses") + .HasColumnType("integer"); + + b.Property("TradeWins") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.HasKey("Identifier"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("Bots"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("GasFees") + .HasColumnType("decimal(18,8)"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("InitiatorIdentifier") + .HasColumnType("uuid"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UiFees") + .HasColumnType("decimal(18,8)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Identifier"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("InitiatorIdentifier"); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Date"); + + b.HasIndex("Identifier", "Date", "UserId") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250925174620_AddAccountIdToPositions.cs b/src/Managing.Infrastructure.Database/Migrations/20250925174620_AddAccountIdToPositions.cs new file mode 100644 index 00000000..06c2545c --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250925174620_AddAccountIdToPositions.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class AddAccountIdToPositions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AccountId", + table: "Positions", + type: "integer", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AccountId", + table: "Positions"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs b/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs index 7f27d2c4..a6928b7d 100644 --- a/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs +++ b/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs @@ -666,10 +666,8 @@ namespace Managing.Infrastructure.Databases.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); - b.Property("AccountName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("AccountId") + .HasColumnType("integer"); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); @@ -677,6 +675,9 @@ namespace Managing.Infrastructure.Databases.Migrations b.Property("Date") .HasColumnType("timestamp with time zone"); + b.Property("GasFees") + .HasColumnType("decimal(18,8)"); + b.Property("Initiator") .IsRequired() .HasColumnType("text"); @@ -719,6 +720,9 @@ namespace Managing.Infrastructure.Databases.Migrations .IsRequired() .HasColumnType("text"); + b.Property("UiFees") + .HasColumnType("decimal(18,8)"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); @@ -1083,9 +1087,6 @@ namespace Managing.Infrastructure.Databases.Migrations .HasMaxLength(255) .HasColumnType("character varying(255)"); - b.Property("Fee") - .HasColumnType("decimal(18,8)"); - b.Property("Leverage") .HasColumnType("decimal(18,8)"); diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs index 366f86b5..5d7ab2df 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs @@ -11,16 +11,20 @@ public class PositionEntity public DateTime Date { get; set; } + [Required] public int AccountId { get; set; } + [Column(TypeName = "decimal(18,8)")] public decimal ProfitAndLoss { get; set; } + [Column(TypeName = "decimal(18,8)")] public decimal UiFees { get; set; } + + [Column(TypeName = "decimal(18,8)")] public decimal GasFees { get; set; } + public TradeDirection OriginDirection { get; set; } public PositionStatus Status { get; set; } public Ticker Ticker { get; set; } public PositionInitiator Initiator { get; set; } - [MaxLength(255)] public string SignalIdentifier { get; set; } - - [MaxLength(255)] public string AccountName { get; set; } + [MaxLength(255)] public string? SignalIdentifier { get; set; } public int? UserId { get; set; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/TradeEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/TradeEntity.cs index 719d12b2..036b24bd 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/TradeEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/TradeEntity.cs @@ -16,9 +16,6 @@ public class TradeEntity public TradeType TradeType { get; set; } public Ticker Ticker { get; set; } - [Column(TypeName = "decimal(18,8)")] - public decimal Fee { get; set; } - [Column(TypeName = "decimal(18,8)")] public decimal Quantity { get; set; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs b/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs index 455e6334..c2a4f8ac 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs @@ -301,7 +301,6 @@ public class ManagingDbContext : DbContext entity.Property(e => e.Ticker).IsRequired().HasConversion(); entity.Property(e => e.Initiator).IsRequired().HasConversion(); entity.Property(e => e.SignalIdentifier).IsRequired().HasMaxLength(255); - entity.Property(e => e.AccountName).IsRequired().HasMaxLength(255); entity.Property(e => e.UserId); entity.Property(e => e.InitiatorIdentifier).IsRequired(); entity.Property(e => e.MoneyManagementJson).HasColumnType("text"); @@ -351,7 +350,6 @@ public class ManagingDbContext : DbContext entity.Property(e => e.Status).IsRequired().HasConversion(); entity.Property(e => e.TradeType).IsRequired().HasConversion(); entity.Property(e => e.Ticker).IsRequired().HasConversion(); - entity.Property(e => e.Fee).HasColumnType("decimal(18,8)"); entity.Property(e => e.Quantity).HasColumnType("decimal(18,8)"); entity.Property(e => e.Price).HasColumnType("decimal(18,8)"); entity.Property(e => e.Leverage).HasColumnType("decimal(18,8)"); @@ -561,8 +559,6 @@ public class ManagingDbContext : DbContext .HasForeignKey(e => e.UserId) .OnDelete(DeleteBehavior.Cascade); }); - - } /// @@ -575,7 +571,7 @@ public class ManagingDbContext : DbContext // when using AdoNetClustering and AdoNetReminderService. // This method serves as a verification point and can be extended // for custom Orleans table management if needed. - + // For now, we just ensure the database is accessible await Database.CanConnectAsync(); } @@ -587,12 +583,12 @@ public class ManagingDbContext : DbContext public async Task> GetOrleansTableStatsAsync() { var stats = new Dictionary(); - + // Orleans table names var orleansTables = new[] { "orleansmembershiptable", - "orleansmembershipversiontable", + "orleansmembershipversiontable", "orleansquery", "orleansreminderstable", "orleansstorage" @@ -630,7 +626,7 @@ public class ManagingDbContext : DbContext protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - + // Add any additional configuration here if needed } } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs index f8d67c5b..2184907f 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs @@ -82,6 +82,27 @@ public class PostgreSqlAccountRepository : IAccountRepository } } + public async Task GetAccountByIdAsync(int id) + { + try + { + await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context); + + var accountEntity = await _context.Accounts + .AsNoTracking() + .Include(a => a.User) + .FirstOrDefaultAsync(a => a.Id == id) + .ConfigureAwait(false); + + return PostgreSqlMappers.Map(accountEntity); + } + finally + { + // Always ensure the connection is closed after the operation + await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context); + } + } + public async Task> GetAccountsAsync() { try diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs index b39ae485..decf336e 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs @@ -28,6 +28,7 @@ public static class PostgreSqlMappers return new Account { + Id = entity.Id, Name = entity.Name, Exchange = entity.Exchange, Type = entity.Type, @@ -45,6 +46,7 @@ public static class PostgreSqlMappers return new AccountEntity { + Id = account.Id, Name = account.Name, Exchange = account.Exchange, Type = account.Type, @@ -554,7 +556,7 @@ public static class PostgreSqlMappers var position = new Position( entity.Identifier, - entity.AccountName, + entity.AccountId, entity.OriginDirection, entity.Ticker, moneyManagement, @@ -569,6 +571,10 @@ public static class PostgreSqlMappers // Set ProfitAndLoss with proper type position.ProfitAndLoss = new ProfitAndLoss { Realized = entity.ProfitAndLoss }; + + // Set fee properties + position.UiFees = entity.UiFees; + position.GasFees = entity.GasFees; // Map related trades if (entity.OpenTrade != null) @@ -591,13 +597,15 @@ public static class PostgreSqlMappers { Identifier = position.Identifier, Date = position.Date, + AccountId = position.AccountId, ProfitAndLoss = position.ProfitAndLoss?.Realized ?? 0, + UiFees = position.UiFees, + GasFees = position.GasFees, OriginDirection = position.OriginDirection, Status = position.Status, Ticker = position.Ticker, Initiator = position.Initiator, SignalIdentifier = position.SignalIdentifier, - AccountName = position.AccountName, UserId = position.User?.Id ?? 0, InitiatorIdentifier = position.InitiatorIdentifier, MoneyManagementJson = position.MoneyManagement != null @@ -621,10 +629,7 @@ public static class PostgreSqlMappers entity.Price, entity.Leverage, entity.ExchangeOrderId, - entity.Message) - { - Fee = entity.Fee - }; + entity.Message); } public static TradeEntity Map(Trade trade) @@ -638,7 +643,6 @@ public static class PostgreSqlMappers Status = trade.Status, TradeType = trade.TradeType, Ticker = trade.Ticker, - Fee = trade.Fee, Quantity = trade.Quantity, Price = trade.Price, Leverage = trade.Leverage, diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs index d4a54b5d..27130e01 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs @@ -106,7 +106,7 @@ public class PostgreSqlTradingRepository : ITradingRepository public async Task InsertScenarioAsync(Scenario scenario) { var userId = scenario.User?.Id ?? 0; - + // Check if scenario already exists for the same user var existingScenario = await _context.Scenarios .AsNoTracking() @@ -271,7 +271,7 @@ public class PostgreSqlTradingRepository : ITradingRepository try { await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context); - + var position = await _context.Positions .AsNoTracking() .Include(p => p.User) @@ -395,6 +395,8 @@ public class PostgreSqlTradingRepository : ITradingRepository { entity.Date = position.Date; entity.ProfitAndLoss = position.ProfitAndLoss?.Realized ?? 0; + entity.UiFees = position.UiFees; + entity.GasFees = position.GasFees; entity.Status = position.Status; entity.SignalIdentifier = position.SignalIdentifier; entity.MoneyManagementJson = position.MoneyManagement != null @@ -440,7 +442,6 @@ public class PostgreSqlTradingRepository : ITradingRepository entity.Status = trade.Status; entity.TradeType = trade.TradeType; entity.Ticker = trade.Ticker; - entity.Fee = trade.Fee; entity.Quantity = trade.Quantity; entity.Price = trade.Price; entity.Leverage = trade.Leverage; @@ -468,7 +469,8 @@ public class PostgreSqlTradingRepository : ITradingRepository return PostgreSqlMappers.Map(positions); } - public async Task> GetPositionsByInitiatorIdentifiersAsync(IEnumerable initiatorIdentifiers) + public async Task> GetPositionsByInitiatorIdentifiersAsync( + IEnumerable initiatorIdentifiers) { var identifiersList = initiatorIdentifiers.ToList(); if (!identifiersList.Any()) @@ -510,7 +512,7 @@ public class PostgreSqlTradingRepository : ITradingRepository try { await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context); - + // Calculate total PnL from all finished positions (closed positions) // Only include positions that are Finished or Flipped (closed positions) var totalPnL = await _context.Positions diff --git a/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs b/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs index a1338952..60acac2e 100644 --- a/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs +++ b/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs @@ -305,7 +305,8 @@ namespace Managing.Infrastructure.Messengers.Discord await component.RespondAsync("Alright, let met few seconds to close this position"); var position = await tradingService.GetPositionByIdentifierAsync(Guid.Parse(parameters[1])); - var command = new ClosePositionCommand(position); + + var command = new ClosePositionCommand(position, position.AccountId); var result = await new ClosePositionCommandHandler(exchangeService, accountService, tradingService, scopeFactory) .Handle(command); @@ -336,7 +337,7 @@ namespace Managing.Infrastructure.Messengers.Discord IsInline = true }, }; - var embed = DiscordHelpers.GetEmbed(position.AccountName, $"Position status is now {result.Status}", fields, + var embed = DiscordHelpers.GetEmbed($"Position {position.Identifier}", $"Position status is now {result.Status}", fields, position.ProfitAndLoss.Net > 0 ? Color.Green : Color.Red); await component.Channel.SendMessageAsync("", embed: embed); } diff --git a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Mappers.cs b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Mappers.cs index 14ca91b4..c3a04b79 100644 --- a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Mappers.cs +++ b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Mappers.cs @@ -164,7 +164,7 @@ internal static class GmxV2Mappers { var direction = MiscExtensions.ParseEnum(gmxPosition.Direction); var ticker = MiscExtensions.ParseEnum(gmxPosition.Ticker); - var position = new Position(Guid.NewGuid(), "", + var position = new Position(Guid.NewGuid(), 1, direction, ticker, new MoneyManagement(), diff --git a/src/Managing.Web3Proxy/package-lock.json b/src/Managing.Web3Proxy/package-lock.json index 53b3b1d9..8459beb3 100644 --- a/src/Managing.Web3Proxy/package-lock.json +++ b/src/Managing.Web3Proxy/package-lock.json @@ -27,7 +27,7 @@ "@sentry/node": "^8.55.0", "@sinclair/typebox": "^0.34.11", "canonicalize": "^2.0.0", - "concurrently": "^9.0.1", + "concurrently": "^9.2.1", "cross-fetch": "^4.1.0", "csv-stringify": "^6.5.2", "ethers": "^6.13.5", @@ -2854,16 +2854,17 @@ "license": "MIT" }, "node_modules/concurrently": { - "version": "9.1.2", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", "license": "MIT", "dependencies": { - "chalk": "^4.1.2", - "lodash": "^4.17.21", - "rxjs": "^7.8.1", - "shell-quote": "^1.8.1", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.2" + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", @@ -7059,7 +7060,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.2", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "license": "MIT", "engines": { "node": ">= 0.4" diff --git a/src/Managing.Web3Proxy/package.json b/src/Managing.Web3Proxy/package.json index 5e80ba9d..b3edc78e 100644 --- a/src/Managing.Web3Proxy/package.json +++ b/src/Managing.Web3Proxy/package.json @@ -46,7 +46,7 @@ "@sentry/node": "^8.55.0", "@sinclair/typebox": "^0.34.11", "canonicalize": "^2.0.0", - "concurrently": "^9.0.1", + "concurrently": "^9.2.1", "cross-fetch": "^4.1.0", "csv-stringify": "^6.5.2", "ethers": "^6.13.5", diff --git a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts index fc9e4462..fe424a85 100644 --- a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts +++ b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts @@ -56,7 +56,7 @@ const MAX_CACHE_SIZE = 5; // Limit cache size to prevent memory issues const OPERATION_TIMEOUT = 30000; // 30 seconds timeout for operations const MEMORY_WARNING_THRESHOLD = 0.8; // Warn when memory usage exceeds 80% -const MAX_GAS_FEE_USD = 1; // Maximum gas fee in USD (1 USDC) +const MAX_GAS_FEE_USD = 1.5; // Maximum gas fee in USD (1 USDC) // Memory monitoring function function checkMemoryUsage() {