using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Managing.Domain.Users;
using Orleans;
using static Managing.Common.Enums;
namespace Managing.Domain.Trades
{
[GenerateSerializer]
public class Position
{
public Position(Guid identifier, int accountId, TradeDirection originDirection, Ticker ticker,
LightMoneyManagement moneyManagement, PositionInitiator initiator, DateTime date, User user)
{
Identifier = identifier;
AccountId = accountId;
OriginDirection = originDirection;
Ticker = ticker;
MoneyManagement = moneyManagement;
Initiator = initiator;
Date = date;
Status = Initiator == PositionInitiator.PaperTrading ? PositionStatus.Filled : PositionStatus.New;
User = user;
}
[Id(0)] [Required] public DateTime Date { get; set; }
[Id(1)] [Required] public int AccountId { get; set; }
[Id(2)] [Required] public TradeDirection OriginDirection { get; set; }
[Id(3)] [Required] public Ticker Ticker { get; set; }
[Id(4)] [Required] public LightMoneyManagement MoneyManagement { get; set; }
[Id(5)]
[Required]
[JsonPropertyName("Open")]
public Trade Open { get; set; }
[Id(6)]
[Required]
[JsonPropertyName("StopLoss")]
public Trade StopLoss { get; set; }
[Id(7)]
[Required]
[JsonPropertyName("TakeProfit1")]
public Trade TakeProfit1 { get; set; }
[Id(8)]
[JsonPropertyName("TakeProfit2")]
public Trade TakeProfit2 { get; set; }
[Id(9)]
[JsonPropertyName("ProfitAndLoss")]
public ProfitAndLoss ProfitAndLoss { get; set; }
[Id(10)] public decimal UiFees { get; set; }
[Id(11)] public decimal GasFees { get; set; }
[Id(12)] [Required] public PositionStatus Status { get; set; }
[Id(13)] public string SignalIdentifier { 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(17)]
[Required]
public Guid InitiatorIdentifier { get; set; }
///
/// Indicates whether position recovery has been attempted for this cancelled position
/// Used to prevent repeated recovery attempts for positions that were never filled
///
[Id(18)]
public bool RecoveryAttempted { get; set; }
///
/// Return true if position is finished even if the position was canceled or rejected
///
///
public bool IsFinished()
{
return Status switch
{
PositionStatus.Finished => true,
PositionStatus.Canceled => true,
PositionStatus.Rejected => true,
PositionStatus.Flipped => true,
_ => false
};
}
public bool IsInProfit()
{
if (ProfitAndLoss?.Net == null)
{
return false;
}
return ProfitAndLoss.Net > 0;
}
public bool IsOpen()
{
return Status switch
{
PositionStatus.Filled => true,
_ => false
};
}
///
/// Return true if position is valid for metrics calculation (PnL, WinRate, etc.)
/// Only positions with status Filled, Finished or Flipped are considered valid
///
///
public bool IsValidForMetrics()
{
return Status switch
{
PositionStatus.Filled => true,
PositionStatus.Finished => true,
PositionStatus.Flipped => true,
_ => 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 GetPnLBeforeFees()
{
if (ProfitAndLoss?.Realized == null)
{
return 0;
}
return ProfitAndLoss.Realized;
}
public decimal GetNetPnl()
{
if (ProfitAndLoss?.Net == null)
{
return 0;
}
return ProfitAndLoss.Realized - UiFees - GasFees;
}
///
/// 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;
}
}
}