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);