Update account/position and platform summary
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user