fix(swap): 修复浮动端已实现盈亏计算逻辑

- 添加了SetFloatingRealizedPnl方法统一计算已实现盈亏
- 已实现盈亏现在正确包含盯市、分红和费用三个组成部分
- 修复了多处RealizedPnl计算错误,确保数值准确性
- 为DF_008测试场景添加了专门的单元测试验证
- 移除了重复的盈亏计算代码,提高代码可维护性
This commit is contained in:
张名锐
2026-07-17 10:02:06 +08:00
parent 059867424d
commit ce0908c53b
2 changed files with 49 additions and 5 deletions
@@ -378,6 +378,39 @@ namespace YLErp.Modules.SwapModule
Console.WriteLine($"分红增值税调整: 付息100, 税率6% → TdPosiDividend={result.TdPosiDividend}(期望{expected})✅");
}
[TestMethod]
public void DF_008_CopyBranch_RealizedPnlIncludesRealizedFee()
{
var service = new StubEodService
{
UnderlyingPrice = 1.002m,
TaxRate = 0m,
BondPayment = 0m
};
var preEod = new eod_swap_position
{
id = 5001,
SwapTradeId = SwapTradeId,
PositionId = 3001,
ValueDate = PreSettleDate,
PosiQuantity = 10000m,
PosiGrossPrice = 1.002m,
PosiNetPrice = 1.005m,
UnderlyingCode = "210210.IB",
ContractSize = 1m,
PositionType = (int)PositionTypeFlag.Long,
PosiDirection = (int)SwapDirectionEnum.,
RealizedMtmPnL = 98000000m,
RealizedDividend = 0m,
RealizedFee = 100m,
RealizedPnl = 98000000m
};
var result = service.ExecuteCopyEodPosition(preEod, null, CreateTrade(), TradeDate, PreSettleDate);
Assert.AreEqual(98000100m, result.RealizedPnl);
}
private static void AssertDecimalEqual(decimal expected, decimal actual, decimal tolerance, string message = "")
{
Assert.IsTrue(Math.Abs(expected - actual) <= tolerance,
@@ -1510,7 +1510,7 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.RealizedFee = closeFee;
newEodPayPosition.RealizedMtmPnL = newEodPayPosition.TdCloseMtmPnl;
newEodPayPosition.RealizedDividend = newEodPayPosition.TdCloseDividend;
newEodPayPosition.RealizedPnl = newEodPayPosition.TdCloseMtmPnl;
SetFloatingRealizedPnl(newEodPayPosition);
newEodPayPosition.PosiStatus = payQty == 0 ? 1 : 0;
UpdateDbOption(newEodPayPosition);
@@ -1585,7 +1585,7 @@ namespace YLErp.Modules.SwapModule
curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl;
curretEod.RealizedDividend = eod.RealizedDividend + curretEod.TdCloseDividend;
curretEod.RealizedFee = eod.RealizedFee + curretEod.TdCloseFee;
curretEod.RealizedPnl = eod.RealizedPnl + curretEod.TdCloseMtmPnl;
SetFloatingRealizedPnl(curretEod);
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, td.StartDate.Value
, seekPreday: true, currencyRateType: curretEod.PosiDirection == (int)SwapDirectionEnum. ? CurrencyRateType.Buy : CurrencyRateType.Sell);
curretEod.TdCurrency = Convert.ToDecimal(currencyRate);
@@ -1599,6 +1599,18 @@ namespace YLErp.Modules.SwapModule
}
return curretEod;
}
/// <summary>
/// 浮动腿累计已实现盈亏由盯市、分红和费用三个已实现组成项汇总。
/// 各组成项已经按本方视角落库,此处不再额外转换方向。
/// </summary>
private static void SetFloatingRealizedPnl(eod_swap_position position)
{
position.RealizedPnl = position.RealizedMtmPnL
+ position.RealizedDividend
+ position.RealizedFee;
}
/// <summary>
/// 更新虚拟交易费用
/// </summary>
@@ -1659,7 +1671,6 @@ namespace YLErp.Modules.SwapModule
// 当日浮动端平仓盈亏·分红(仅来自平仓事件 和 互换 中已实现的分红)
curretEod.TdCloseDividend = unwindEvents.Sum(e => e.DividendIn);
curretEod.RealizedFee = eod.RealizedFee + curretEod.TdCloseFee;
curretEod.RealizedPnl = eod.RealizedPnl + curretEod.TdCloseMtmPnl;
curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0;
var closeQty = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.).ToList().Sum(s => s.Quantity);
@@ -1675,7 +1686,7 @@ namespace YLErp.Modules.SwapModule
{
curretEod.PosiDividendSum = 0;
}
curretEod.RealizedPnl += curretEod.TdCloseDividend;
SetFloatingRealizedPnl(curretEod);
curretEod.SwapPositionValue -= curretEod.TdCloseDividend;
curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending;
@@ -1853,7 +1864,7 @@ namespace YLErp.Modules.SwapModule
curretEod.RealizedMtmPnL = curretEod.TdCloseMtmPnl;
curretEod.RealizedDividend = curretEod.TdCloseDividend;
curretEod.RealizedFee = curretEod.TdCloseFee;
curretEod.RealizedPnl = curretEod.TdCloseMtmPnl;
SetFloatingRealizedPnl(curretEod);
curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0;
if (curretEod.PosiStatus == 1)
{