Add netPnl in db for position
This commit is contained in:
@@ -11,6 +11,7 @@ using Managing.Domain.Scenarios;
|
|||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
using Managing.Domain.Strategies.Base;
|
using Managing.Domain.Strategies.Base;
|
||||||
|
using Managing.Domain.Trades;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -481,6 +482,9 @@ public class DataController : ControllerBase
|
|||||||
// Fetch positions associated with this bot using the provided trading service
|
// Fetch positions associated with this bot using the provided trading service
|
||||||
var positions = await tradingService.GetPositionsByInitiatorIdentifierAsync(strategy.Identifier);
|
var positions = await tradingService.GetPositionsByInitiatorIdentifierAsync(strategy.Identifier);
|
||||||
|
|
||||||
|
// Convert positions to view models
|
||||||
|
var positionViewModels = positions.Select(MapPositionToViewModel).ToList();
|
||||||
|
|
||||||
// Convert agent balance history to wallet balances dictionary
|
// Convert agent balance history to wallet balances dictionary
|
||||||
var walletBalances = agentBalanceHistory?.AgentBalances?
|
var walletBalances = agentBalanceHistory?.AgentBalances?
|
||||||
.ToDictionary(b => b.Time, b => b.TotalValue) ?? new Dictionary<DateTime, decimal>();
|
.ToDictionary(b => b.Time, b => b.TotalValue) ?? new Dictionary<DateTime, decimal>();
|
||||||
@@ -498,7 +502,7 @@ public class DataController : ControllerBase
|
|||||||
VolumeLast24H = volumeLast24h,
|
VolumeLast24H = volumeLast24h,
|
||||||
Wins = wins,
|
Wins = wins,
|
||||||
Losses = losses,
|
Losses = losses,
|
||||||
Positions = positions.ToList(),
|
Positions = positionViewModels,
|
||||||
Identifier = strategy.Identifier,
|
Identifier = strategy.Identifier,
|
||||||
WalletBalances = walletBalances,
|
WalletBalances = walletBalances,
|
||||||
Ticker = strategy.Ticker
|
Ticker = strategy.Ticker
|
||||||
@@ -783,4 +787,29 @@ public class DataController : ControllerBase
|
|||||||
PositionCountByDirection = state.PositionCountByDirection ?? new Dictionary<TradeDirection, int>()
|
PositionCountByDirection = state.PositionCountByDirection ?? new Dictionary<TradeDirection, int>()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps a Position domain object to a PositionViewModel
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">The position domain object to map</param>
|
||||||
|
/// <returns>A PositionViewModel with the same properties</returns>
|
||||||
|
private static PositionViewModel MapPositionToViewModel(Position position)
|
||||||
|
{
|
||||||
|
return new PositionViewModel
|
||||||
|
{
|
||||||
|
Date = position.Date,
|
||||||
|
AccountId = position.AccountId,
|
||||||
|
OriginDirection = position.OriginDirection,
|
||||||
|
Ticker = position.Ticker,
|
||||||
|
Open = position.Open,
|
||||||
|
StopLoss = position.StopLoss,
|
||||||
|
TakeProfit1 = position.TakeProfit1,
|
||||||
|
ProfitAndLoss = position.ProfitAndLoss,
|
||||||
|
UiFees = position.UiFees,
|
||||||
|
GasFees = position.GasFees,
|
||||||
|
Status = position.Status,
|
||||||
|
SignalIdentifier = position.SignalIdentifier,
|
||||||
|
Identifier = position.Identifier,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
147
src/Managing.Api/Models/Responses/PositionViewModel.cs
Normal file
147
src/Managing.Api/Models/Responses/PositionViewModel.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Managing.Common;
|
||||||
|
using Managing.Domain.Trades;
|
||||||
|
|
||||||
|
namespace Managing.Api.Models.Responses
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// View model for Position data, matching the Position domain model structure
|
||||||
|
/// </summary>
|
||||||
|
public class PositionViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Date when the position was created
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Account ID associated with this position
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public int AccountId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Original direction of the trade (Long/Short)
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public Enums.TradeDirection OriginDirection { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trading pair ticker
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public Enums.Ticker Ticker { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opening trade for this position
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[JsonPropertyName("Open")]
|
||||||
|
public Trade Open { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop loss trade for this position
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[JsonPropertyName("StopLoss")]
|
||||||
|
public Trade StopLoss { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First take profit trade for this position
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[JsonPropertyName("TakeProfit1")]
|
||||||
|
public Trade TakeProfit1 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Profit and loss information for this position
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("ProfitAndLoss")]
|
||||||
|
public ProfitAndLoss ProfitAndLoss { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UI fees associated with this position
|
||||||
|
/// </summary>
|
||||||
|
public decimal UiFees { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gas fees associated with this position
|
||||||
|
/// </summary>
|
||||||
|
public decimal GasFees { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current status of the position
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public Enums.PositionStatus Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signal identifier that triggered this position
|
||||||
|
/// </summary>
|
||||||
|
public string SignalIdentifier { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unique identifier for this position
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public Guid Identifier { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the position is finished
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the position is finished or flipped</returns>
|
||||||
|
public bool IsFinished()
|
||||||
|
{
|
||||||
|
return Status switch
|
||||||
|
{
|
||||||
|
Enums.PositionStatus.Finished => true,
|
||||||
|
Enums.PositionStatus.Flipped => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Domain.Trades;
|
|
||||||
|
|
||||||
namespace Managing.Api.Models.Responses
|
namespace Managing.Api.Models.Responses
|
||||||
{
|
{
|
||||||
@@ -66,7 +65,7 @@ namespace Managing.Api.Models.Responses
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dictionary of all positions executed by this strategy, keyed by position identifier
|
/// Dictionary of all positions executed by this strategy, keyed by position identifier
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Position> Positions { get; set; } = new List<Position>();
|
public List<PositionViewModel> Positions { get; set; } = new List<PositionViewModel>();
|
||||||
|
|
||||||
public Guid Identifier { get; set; }
|
public Guid Identifier { get; set; }
|
||||||
|
|
||||||
|
|||||||
1458
src/Managing.Infrastructure.Database/Migrations/20251002195251_AddNetPnLToPositionEntity.Designer.cs
generated
Normal file
1458
src/Managing.Infrastructure.Database/Migrations/20251002195251_AddNetPnLToPositionEntity.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 AddNetPnLToPositionEntity : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<decimal>(
|
||||||
|
name: "NetPnL",
|
||||||
|
table: "Positions",
|
||||||
|
type: "numeric(18,8)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "NetPnL",
|
||||||
|
table: "Positions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -704,6 +704,9 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
b.Property<string>("MoneyManagementJson")
|
b.Property<string>("MoneyManagementJson")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("NetPnL")
|
||||||
|
.HasColumnType("decimal(18,8)");
|
||||||
|
|
||||||
b.Property<int?>("OpenTradeId")
|
b.Property<int?>("OpenTradeId")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
|||||||
@@ -55,4 +55,6 @@ public class PositionEntity
|
|||||||
[ForeignKey("TakeProfit1TradeId")] public virtual TradeEntity? TakeProfit1Trade { get; set; }
|
[ForeignKey("TakeProfit1TradeId")] public virtual TradeEntity? TakeProfit1Trade { get; set; }
|
||||||
|
|
||||||
[ForeignKey("TakeProfit2TradeId")] public virtual TradeEntity? TakeProfit2Trade { get; set; }
|
[ForeignKey("TakeProfit2TradeId")] public virtual TradeEntity? TakeProfit2Trade { get; set; }
|
||||||
|
|
||||||
|
[Column(TypeName = "decimal(18,8)")] public decimal NetPnL { get; set; }
|
||||||
}
|
}
|
||||||
@@ -296,6 +296,7 @@ public class ManagingDbContext : DbContext
|
|||||||
{
|
{
|
||||||
entity.HasKey(e => e.Identifier);
|
entity.HasKey(e => e.Identifier);
|
||||||
entity.Property(e => e.ProfitAndLoss).HasColumnType("decimal(18,8)");
|
entity.Property(e => e.ProfitAndLoss).HasColumnType("decimal(18,8)");
|
||||||
|
entity.Property(e => e.NetPnL).HasColumnType("decimal(18,8)");
|
||||||
entity.Property(e => e.OriginDirection).IsRequired().HasConversion<string>();
|
entity.Property(e => e.OriginDirection).IsRequired().HasConversion<string>();
|
||||||
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
|
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
|
||||||
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
|
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
|
||||||
|
|||||||
@@ -570,7 +570,7 @@ public static class PostgreSqlMappers
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Set ProfitAndLoss with proper type
|
// Set ProfitAndLoss with proper type
|
||||||
position.ProfitAndLoss = new ProfitAndLoss { Realized = entity.ProfitAndLoss, Net = entity.ProfitAndLoss - entity.UiFees - entity.GasFees };
|
position.ProfitAndLoss = new ProfitAndLoss { Realized = entity.ProfitAndLoss, Net = entity.NetPnL };
|
||||||
|
|
||||||
// Set fee properties
|
// Set fee properties
|
||||||
position.UiFees = entity.UiFees;
|
position.UiFees = entity.UiFees;
|
||||||
@@ -610,7 +610,8 @@ public static class PostgreSqlMappers
|
|||||||
InitiatorIdentifier = position.InitiatorIdentifier,
|
InitiatorIdentifier = position.InitiatorIdentifier,
|
||||||
MoneyManagementJson = position.MoneyManagement != null
|
MoneyManagementJson = position.MoneyManagement != null
|
||||||
? JsonConvert.SerializeObject(position.MoneyManagement)
|
? JsonConvert.SerializeObject(position.MoneyManagement)
|
||||||
: null
|
: null,
|
||||||
|
NetPnL = position.ProfitAndLoss?.Net ?? (position.ProfitAndLoss?.Realized - position.UiFees - position.GasFees ?? 0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user