Update account/position and platform summary
This commit is contained in:
@@ -96,7 +96,8 @@ public class TradingController : BaseController
|
||||
public async Task<ActionResult<Position>> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Managing.Application.Abstractions.Repositories;
|
||||
public interface IAccountRepository
|
||||
{
|
||||
Task<Account> GetAccountByNameAsync(string name);
|
||||
Task<Account> GetAccountByIdAsync(int id);
|
||||
Task<Account> GetAccountByKeyAsync(string key);
|
||||
Task InsertAccountAsync(Account account);
|
||||
Task UpdateAccountAsync(Account account);
|
||||
|
||||
@@ -24,6 +24,15 @@ public interface IAccountService
|
||||
/// <returns>The found account or null if not found</returns>
|
||||
Task<Account> GetAccountByAccountName(string accountName, bool hideSecrets = true, bool getBalance = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an account by ID directly from the repository.
|
||||
/// </summary>
|
||||
/// <param name="accountId">The ID of the account to find</param>
|
||||
/// <param name="hideSecrets">Whether to hide sensitive information</param>
|
||||
/// <param name="getBalance">Whether to fetch the current balance</param>
|
||||
/// <returns>The found account or null if not found</returns>
|
||||
Task<Account> GetAccountById(int accountId, bool hideSecrets = true, bool getBalance = false);
|
||||
|
||||
IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets = true);
|
||||
Task<IEnumerable<Account>> GetAccountsBalancesByUserAsync(User user, bool hideSecrets = true);
|
||||
Task<GmxClaimableSummary> GetGmxClaimableSummaryAsync(User user, string accountName);
|
||||
|
||||
@@ -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<Guid>())).ReturnsAsync(position);
|
||||
_ = _tradingService.Setup(m => m.GetPositionByIdentifierAsync(It.IsAny<Guid>())).ReturnsAsync(position);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -149,6 +149,25 @@ public class AccountService : IAccountService
|
||||
return account;
|
||||
}
|
||||
|
||||
public async Task<Account> 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<IEnumerable<Account>> GetAccounts(bool hideSecrets, bool getBalance)
|
||||
{
|
||||
return await GetAccountsAsync(hideSecrets, getBalance);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns>Returns the total fees paid as a decimal value.</returns>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the total fees for a specific position based on GMX V2 fee structure
|
||||
/// </summary>
|
||||
/// <param name="position">The position to calculate fees for</param>
|
||||
/// <returns>The total fees for the position</returns>
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}";
|
||||
|
||||
@@ -5,14 +5,16 @@ namespace Managing.Application.Trading.Commands
|
||||
{
|
||||
public class ClosePositionCommand : IRequest<Position>
|
||||
{
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Balance> Balances { get; set; }
|
||||
public User User { get; set; }
|
||||
|
||||
[Id(7)]
|
||||
public List<Balance> Balances { get; set; }
|
||||
|
||||
[Id(8)]
|
||||
public bool IsGmxInitialized { get; set; } = false;
|
||||
|
||||
public bool IsPrivyWallet => Type == AccountType.Privy;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the total fees for a position based on GMX V2 fee structure
|
||||
/// </summary>
|
||||
/// <param name="position">The position to calculate fees for</param>
|
||||
/// <returns>The total fees for the position</returns>
|
||||
public static decimal CalculatePositionFees(Position position)
|
||||
{
|
||||
var (uiFees, gasFees) = CalculatePositionFeesBreakdown(position);
|
||||
return uiFees + gasFees;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the UI and Gas fees breakdown for a position based on GMX V2 fee structure
|
||||
/// </summary>
|
||||
/// <param name="position">The position to calculate fees for</param>
|
||||
/// <returns>A tuple containing (uiFees, gasFees)</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates UI fees for opening a position
|
||||
/// </summary>
|
||||
/// <param name="positionSizeUsd">The position size in USD</param>
|
||||
/// <returns>The UI fees for opening</returns>
|
||||
public static decimal CalculateOpeningUiFees(decimal positionSizeUsd)
|
||||
{
|
||||
return positionSizeUsd * Constants.GMX.Config.UiFeeRate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates UI fees for closing a position
|
||||
/// </summary>
|
||||
/// <param name="positionSizeUsd">The position size in USD</param>
|
||||
/// <returns>The UI fees for closing</returns>
|
||||
public static decimal CalculateClosingUiFees(decimal positionSizeUsd)
|
||||
{
|
||||
return positionSizeUsd * Constants.GMX.Config.UiFeeRate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates gas fees for opening a position
|
||||
/// </summary>
|
||||
/// <returns>The gas fees for opening (fixed at $0.15)</returns>
|
||||
public static decimal CalculateOpeningGasFees()
|
||||
{
|
||||
return Constants.GMX.Config.GasFeePerTransaction;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
/// <summary>
|
||||
/// Identifier of the bot or entity that initiated this position
|
||||
/// </summary>
|
||||
[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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the total fees for this position based on GMX V2 fee structure
|
||||
/// </summary>
|
||||
/// <returns>The total fees for the position</returns>
|
||||
public decimal CalculateTotalFees()
|
||||
{
|
||||
return UiFees + GasFees;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the net PnL after deducting fees
|
||||
/// </summary>
|
||||
/// <returns>The net PnL after fees</returns>
|
||||
public decimal GetNetPnL()
|
||||
{
|
||||
if (ProfitAndLoss?.Realized == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ProfitAndLoss.Realized - CalculateTotalFees();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the UI fees for this position
|
||||
/// </summary>
|
||||
/// <param name="uiFees">The UI fees to add</param>
|
||||
public void AddUiFees(decimal uiFees)
|
||||
{
|
||||
UiFees += uiFees;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the gas fees for this position
|
||||
/// </summary>
|
||||
/// <param name="gasFees">The gas fees to add</param>
|
||||
public void AddGasFees(decimal gasFees)
|
||||
{
|
||||
GasFees += gasFees;
|
||||
}
|
||||
}
|
||||
}
|
||||
1436
src/Managing.Infrastructure.Database/Migrations/20250925173424_RemoveAccountNameFromPositions.Designer.cs
generated
Normal file
1436
src/Managing.Infrastructure.Database/Migrations/20250925173424_RemoveAccountNameFromPositions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Managing.Infrastructure.Databases.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveAccountNameFromPositions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Fee",
|
||||
table: "Trades");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AccountName",
|
||||
table: "Positions");
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "GasFees",
|
||||
table: "Positions",
|
||||
type: "numeric(18,8)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "UiFees",
|
||||
table: "Positions",
|
||||
type: "numeric(18,8)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GasFees",
|
||||
table: "Positions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UiFees",
|
||||
table: "Positions");
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "Fee",
|
||||
table: "Trades",
|
||||
type: "numeric(18,8)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "AccountName",
|
||||
table: "Positions",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
}
|
||||
}
|
||||
1439
src/Managing.Infrastructure.Database/Migrations/20250925174620_AddAccountIdToPositions.Designer.cs
generated
Normal file
1439
src/Managing.Infrastructure.Database/Migrations/20250925174620_AddAccountIdToPositions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Managing.Infrastructure.Databases.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAccountIdToPositions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "AccountId",
|
||||
table: "Positions",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AccountId",
|
||||
table: "Positions");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -666,10 +666,8 @@ namespace Managing.Infrastructure.Databases.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AccountName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
b.Property<int>("AccountId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
@@ -677,6 +675,9 @@ namespace Managing.Infrastructure.Databases.Migrations
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<decimal>("GasFees")
|
||||
.HasColumnType("decimal(18,8)");
|
||||
|
||||
b.Property<string>("Initiator")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
@@ -719,6 +720,9 @@ namespace Managing.Infrastructure.Databases.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("UiFees")
|
||||
.HasColumnType("decimal(18,8)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
@@ -1083,9 +1087,6 @@ namespace Managing.Infrastructure.Databases.Migrations
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<decimal>("Fee")
|
||||
.HasColumnType("decimal(18,8)");
|
||||
|
||||
b.Property<decimal>("Leverage")
|
||||
.HasColumnType("decimal(18,8)");
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -301,7 +301,6 @@ public class ManagingDbContext : DbContext
|
||||
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
|
||||
entity.Property(e => e.Initiator).IsRequired().HasConversion<string>();
|
||||
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<string>();
|
||||
entity.Property(e => e.TradeType).IsRequired().HasConversion<string>();
|
||||
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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<Dictionary<string, long>> GetOrleansTableStatsAsync()
|
||||
{
|
||||
var stats = new Dictionary<string, long>();
|
||||
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,27 @@ public class PostgreSqlAccountRepository : IAccountRepository
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Account> 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<IEnumerable<Account>> GetAccountsAsync()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<IEnumerable<Position>> GetPositionsByInitiatorIdentifiersAsync(IEnumerable<Guid> initiatorIdentifiers)
|
||||
public async Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifiersAsync(
|
||||
IEnumerable<Guid> 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ internal static class GmxV2Mappers
|
||||
{
|
||||
var direction = MiscExtensions.ParseEnum<TradeDirection>(gmxPosition.Direction);
|
||||
var ticker = MiscExtensions.ParseEnum<Ticker>(gmxPosition.Ticker);
|
||||
var position = new Position(Guid.NewGuid(), "",
|
||||
var position = new Position(Guid.NewGuid(), 1,
|
||||
direction,
|
||||
ticker,
|
||||
new MoneyManagement(),
|
||||
|
||||
23
src/Managing.Web3Proxy/package-lock.json
generated
23
src/Managing.Web3Proxy/package-lock.json
generated
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user