diff --git a/Framework/YLErp.Core/DBModels/EodSwap.cs b/Framework/YLErp.Core/DBModels/EodSwap.cs
index f0cacf4e..d268cd50 100644
--- a/Framework/YLErp.Core/DBModels/EodSwap.cs
+++ b/Framework/YLErp.Core/DBModels/EodSwap.cs
@@ -186,7 +186,7 @@ namespace YLErp.DBModels
public decimal PeriodAmount { get; set; }
///
- /// 合约浮动端待实现收益,仅取浮动腿盯市收益,
+ /// 合约浮动端待实现收益,取浮动腿盯市收益及未结交易费用,
/// 不包含期间付息/分红,避免与 重复。
///
public decimal FloatingUnrealizedPnl { get; set; }
@@ -209,12 +209,14 @@ namespace YLErp.DBModels
public decimal? PeriodPaymentValuation { get; set; }
///
- /// 我方收取的保证金利息累计额,按保证金腿方向归集并取绝对值展示。
+ /// 我方收取的保证金利息累计额。保证金腿原始“支付”方向表示
+ /// 对手方向我方支付保证金,利息现金流方向与保证金本金方向相反。
///
public decimal MarginInterestGain { get; set; }
///
- /// 我方支付的保证金利息累计额,按保证金腿方向归集并取绝对值展示。
+ /// 我方支付的保证金利息累计额。保证金腿原始“收取”方向表示
+ /// 我方收取对手方保证金,应向对手方支付利息;支付金额以负数展示。
///
public decimal MarginInterestLoss { get; set; }
}
diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
index d982c917..ce8e0eda 100644
--- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
@@ -1964,7 +1964,10 @@ namespace YLErp.Modules.SwapModule
});
eod_Swap.InterestPnL = interestPnL;
eod_Swap.PostionValue = eodSwapPositions.Sum(s => s.SwapPositionValue);
- eod_Swap.RealizedPnL = eodSwapPositions.Sum(s => s.RealizedPnl);
+ // 保证金腿的利息现金流方向与保证金本金方向相反。
+ // 不能直接汇总 RealizedPnl,否则“收取客户保证金”的腿会把应支付给客户的
+ // 利息作为收益相加。逐腿按利息方向转换后再生成框架合约已实现收益。
+ eod_Swap.RealizedPnL = eodSwapPositions.Sum(CalculateSwapRealizedPnl);
eod_Swap.TdRealizedPnL = eod_Swap.RealizedPnL - (preEodSwap?.RealizedPnL ?? 0);
eod_Swap.TdCloseQty = positions.Sum(s => s.TdCloseQty);
var initMargin = Convert.ToDecimal(tradeSpan?.InitialMargin ?? 0);
@@ -2037,7 +2040,7 @@ namespace YLErp.Modules.SwapModule
eod_Swap.TdRealizedPnL += x.TdCloseMtmPnl + x.TdCloseDividend + x.TdCloseFee + x.TdCloseInterest * ratio + x.TdCloseInterestFee;
});
eod_Swap.PostionValue = eodSwapPositions.Sum(s => s.SwapPositionValue);
- eod_Swap.RealizedPnL = eodSwapPositions.Sum(s => s.RealizedMtmPnL + s.RealizedDividend + s.RealizedFee + s.RealizedInterest + s.RealizedInterestFee);
+ eod_Swap.RealizedPnL = eodSwapPositions.Sum(CalculateSwapRealizedPnl);
eod_Swap.TdCloseQty = positions.Sum(s => s.TdCloseQty);
var tradeInitMarginObj = DbContext.trade_initial_margin.FirstOrDefault(x => x.TradeId == td.id);
var initMarginList = interestPositions.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金 && x.HappenDate == settleDate).ToList();
@@ -2050,6 +2053,27 @@ namespace YLErp.Modules.SwapModule
DbContext.SaveChanges();
}
+ ///
+ /// 汇总单条日终腿的我方已实现收益。
+ /// 浮动腿及普通利息腿维持数据库记录的方向;初始/追加预付金腿的利息
+ /// 则与保证金本金方向相反。这样“收取对手方保证金”产生的利息会作为
+ /// 我方支付给对手方的成本计入,而不会错误增加框架合约已实现收益。
+ ///
+ private decimal CalculateSwapRealizedPnl(eod_swap_position position)
+ {
+ var interestRatio = position.InterestDirection == (int)SwapDirectionEnum.收取 ? 1m : -1m;
+ if (marginTypes.Contains(position.InterestMode))
+ {
+ interestRatio = -interestRatio;
+ }
+
+ return position.RealizedMtmPnL
+ + position.RealizedDividend
+ + position.RealizedFee
+ + position.RealizedInterest * interestRatio
+ + position.RealizedInterestFee;
+ }
+
///
/// 获取多空组合 平仓详细
///
@@ -2241,7 +2265,11 @@ namespace YLErp.Modules.SwapModule
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct());
item.PeriodAmount = floatingLegs.Sum(x => x.RealizedDividend + x.PosiDividendSum);
- item.FloatingUnrealizedPnl = floatingLegs.Sum(x => x.PosiMtmPnL);
+ // PosiProfitSum = 标的盯市收益 + 未结交易费用 + 待实现付息/分红。
+ // 风险页的“合约浮动端待实现收益”需要保留未结交易费用,
+ // 但期间付息/分红由 PeriodAmount 单列展示并参与对应估值口径,
+ // 因此仅扣除 PosiDividendSum,不能直接使用 PosiMtmPnL。
+ item.FloatingUnrealizedPnl = floatingLegs.Sum(x => x.PosiProfitSum - x.PosiDividendSum);
item.InterestPaymentMethod = dividendPayDate == 0 ? "到期轧差" : "派息日支付";
if (dividendPayDate == 0)
{
@@ -2251,12 +2279,34 @@ namespace YLErp.Modules.SwapModule
{
item.PeriodPaymentValuation = item.FloatingUnrealizedPnl + item.position.InterestPnL;
}
+ // eod_swap 的保证金本金来自 trade_span;缺少 span 数据时会被保存为 0。
+ // 本风险页改按日终保证金腿展示,且该页面保证金本金采用原始本金的 1/10 口径。
+ // 利息仍使用原始本金累积值,不能同步缩放,否则会破坏保证金利息金额。
+ item.position.InitMarginGain = marginLegs
+ .Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金
+ && x.InterestDirection == (int)SwapDirectionEnum.收取)
+ .Sum(x => Math.Abs(x.InterestPrincipalFix)) / 10m;
+ item.position.InitMarginLoss = marginLegs
+ .Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金
+ && x.InterestDirection == (int)SwapDirectionEnum.支付)
+ .Sum(x => Math.Abs(x.InterestPrincipalFix)) / 10m;
+ item.position.PostionMarginGain = marginLegs
+ .Where(x => x.InterestMode == (int)InterestModeEnum.追加预付金
+ && x.InterestDirection == (int)SwapDirectionEnum.收取)
+ .Sum(x => Math.Abs(x.InterestPrincipalFix)) / 10m;
+ item.position.PostionMarginLoss = marginLegs
+ .Where(x => x.InterestMode == (int)InterestModeEnum.追加预付金
+ && x.InterestDirection == (int)SwapDirectionEnum.支付)
+ .Sum(x => Math.Abs(x.InterestPrincipalFix)) / 10m;
+
+ // 保证金本金方向与我方的利息现金流方向相反:原始“收取”保证金
+ // 表示我方占用客户资金,应向客户支付利息;支付金额按负数展示。
item.MarginInterestGain = marginLegs
- .Where(x => x.InterestDirection == (int)SwapDirectionEnum.收取)
- .Sum(x => Math.Abs(x.InterestIncomeSum));
- item.MarginInterestLoss = marginLegs
.Where(x => x.InterestDirection == (int)SwapDirectionEnum.支付)
.Sum(x => Math.Abs(x.InterestIncomeSum));
+ item.MarginInterestLoss = marginLegs
+ .Where(x => x.InterestDirection == (int)SwapDirectionEnum.收取)
+ .Sum(x => -Math.Abs(x.InterestIncomeSum));
}
var dv01 = query.Sum(O => O.position.dv01??0);