diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs index 13dbc964..98691d38 100644 --- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs @@ -145,7 +145,10 @@ namespace YLErp.Modules.SwapModule if (autoInterval != null) break; } - DealAutoInterests(autoInterests, td, settleDate, preDealDate, posiLongNotional + posiShortNotional, autoInterval, curEodPosis, tradeExtend); + // 自动互换(仅利息/预付金,不含分红) + DealAutoInterests(autoInterests, td, settleDate, preDealDate, posiLongNotional + posiShortNotional, autoInterval); + // 分红独立处理:只要当天有债券需要分红,则生成分红自动互换,与利息互换无关 + DealDividends(curEodPosis, td, settleDate, tradeExtend); //多空组合判断是否已到到期日且无持仓信息 if (longShort && td.ExerciseDate.Value == settleDate && allPositionQty == 0) { @@ -373,114 +376,121 @@ namespace YLErp.Modules.SwapModule /// 自动互换观察日信息,用于获取结算日期 /// 当日浮动端EOD持仓 /// 交易扩展信息 - private void DealAutoInterests(List autoInterests, trade td, DateTime settleDate, DateTime? preDealDate, decimal StockEqvNotional, IntervalModel interval, List curEodPositions, trade_extend tradeExtend) + /// + /// 自动互换(仅利息/预付金,不含分红) + /// + private void DealAutoInterests(List autoInterests, trade td, DateTime settleDate, DateTime? preDealDate, decimal StockEqvNotional, IntervalModel interval) { - if (autoInterests.Count == 0 ||curEodPositions == null ) - { - return; - } - //生成自动互换事件 + if (autoInterests.Count == 0) return; + UnwindData unwindData = new UnwindData(); unwindData.SwapTradeId = td.id; unwindData.ValueDate = settleDate; - if (preDealDate.HasValue) - { - unwindData.StartDate = preDealDate.Value; - } - else - { - unwindData.StartDate = td.StartDate.Value; - } + unwindData.StartDate = preDealDate ?? td.StartDate.Value; unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0); unwindData.PosiNotionalValue = StockEqvNotional; + unwindData.PayDate = settleDate; - // PayDate 统一用派息金额支付日 + 日历调整 - var dividendPayDateOffset = tradeExtend?.ExtendObj?.DividendPayDate ?? 1; - // 0到期结算日 1派息日+0 2派息日+1 3派息日+2 - var payDays = dividendPayDateOffset > 0 ? dividendPayDateOffset - 1 : 0; - var autoSwapPayDate = QdpCalendarHelper.GetNonHoliday(settleDate.AddDays(payDays)); - unwindData.PayDate = autoSwapPayDate; + autoInterests.ForEach(x => x.PayDate = settleDate); - // 给已有利息腿统一赋 PayDate - autoInterests.ForEach(x => x.PayDate = autoSwapPayDate); - - // 预付金腿类型列表:初始预付金、追加预付金 var premiumModes = new List() { (int)InterestModeEnum.初始预付金, (int)InterestModeEnum.追加预付金 }; - - // 分别计算预付金腿和利息腿的金额 var premiumInterests = autoInterests.Where(x => premiumModes.Contains(x.InterestMode)).ToList(); var interestLegs = autoInterests.Where(x => !premiumModes.Contains(x.InterestMode)).ToList(); - // 预付金腿金额 decimal premiumTotal = 0; premiumInterests.ForEach(x => { var ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? -1 : 1; premiumTotal += x.InterestClosePnL * ratio; }); - unwindData.SwapMarginRebatePnl = premiumTotal; // 预付金腿金额 + unwindData.SwapMarginRebatePnl = premiumTotal; - // 利息腿金额(总金额减去预付金腿金额) decimal interestTotal = 0; interestLegs.ForEach(x => { var ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1; interestTotal += x.InterestClosePnL * ratio; }); - unwindData.SwapCloseAmount = interestTotal; // 利息腿金额 + unwindData.SwapCloseAmount = interestTotal; + unwindData.SwapDividendPnl = 0; + unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount + unwindData.SwapMarginRebatePnl; + + SaveAutoSwapDeal(td, autoInterests, unwindData, interval); + } + /// + /// 分红独立处理:当天有债券需要分红时,生成独立的分红自动互换事件 + /// + private void DealDividends(List curEodPositions, trade td, DateTime settleDate, trade_extend tradeExtend) + { + if (curEodPositions == null) return; + var hasDividend = curEodPositions.Any(x => x.PosiDividendSum != 0); + if (!hasDividend) return; + + var dividendPayDateOffset = tradeExtend?.ExtendObj?.DividendPayDate ?? 1; + if (dividendPayDateOffset <= 0) return; + + var payDays = dividendPayDateOffset - 1; + var dividendPayDate = QdpCalendarHelper.GetNonHoliday(settleDate.AddDays(payDays)); - // 处理浮动端待实现分红:将其转为已实现(到期结算日不处理) List dividendEvents = new List(); decimal dividendTotal = 0; - if (curEodPositions != null && dividendPayDateOffset > 0) - { - foreach (var eodPosi in curEodPositions.Where(x => x.PosiDividendSum != 0)) - { - var dividendEvent = new swap_flow_event - { - SwapTradeId = td.id, - SwapTradeNo = td.TradeNumber, - EventType = (int)SwapEventTypeEnum.自动互换, - EventReason = "系统操作-自动互换", - EventDate = settleDate, - UnwindDate = settleDate, - PayDate = autoSwapPayDate, - PositionId = eodPosi.PositionId, - UnderlyingCode = eodPosi.UnderlyingCode, - PayDirection = eodPosi.PosiDirection, - PositionType = eodPosi.PositionType, - PositionQty = eodPosi.PosiQuantity, - Quantity = 0, - ContractSize = eodPosi.ContractSize, - TradingAmountAvg = eodPosi.PosiNetPrice, - PosiGrossPrice = eodPosi.PosiGrossPrice, - PosiNetPrice = eodPosi.PosiNetPrice, - MarkClosePnl = eodPosi.PosiDividendSum, - DividendIn = eodPosi.PosiDividendSum, - CloseFee = 0, - TradingFee = 0, - TradingFeePending = 0, - ClientId = td.ClientId, - DataState = (int)SwapFlowDateStateEnum.完成, - }; - dividendEvents.Add(dividendEvent); - dividendTotal += eodPosi.PosiDividendSum; - // 将EOD持仓的待实现分红转为已实现 - eodPosi.TdCloseDividend += eodPosi.PosiDividendSum; - eodPosi.TdPosiDividend = 0; - eodPosi.RealizedDividend += eodPosi.PosiDividendSum; - eodPosi.PosiDividendSum = 0; - } + foreach (var eodPosi in curEodPositions.Where(x => x.PosiDividendSum != 0)) + { + var dividendEvent = new swap_flow_event + { + SwapTradeId = td.id, + SwapTradeNo = td.TradeNumber, + EventType = (int)SwapEventTypeEnum.自动互换, + EventReason = "系统操作-分红", + EventDate = settleDate, + UnwindDate = settleDate, + PayDate = dividendPayDate, + PositionId = eodPosi.PositionId, + UnderlyingCode = eodPosi.UnderlyingCode, + PayDirection = eodPosi.PosiDirection, + PositionType = eodPosi.PositionType, + PositionQty = eodPosi.PosiQuantity, + Quantity = 0, + ContractSize = eodPosi.ContractSize, + TradingAmountAvg = eodPosi.PosiNetPrice, + PosiGrossPrice = eodPosi.PosiGrossPrice, + PosiNetPrice = eodPosi.PosiNetPrice, + MarkClosePnl = eodPosi.PosiDividendSum, + DividendIn = eodPosi.PosiDividendSum, + CloseFee = 0, + TradingFee = 0, + TradingFeePending = 0, + ClientId = td.ClientId, + DataState = (int)SwapFlowDateStateEnum.完成, + }; + dividendEvents.Add(dividendEvent); + dividendTotal += eodPosi.PosiDividendSum; + + eodPosi.TdCloseDividend += eodPosi.PosiDividendSum; + eodPosi.TdPosiDividend = 0; + eodPosi.RealizedDividend += eodPosi.PosiDividendSum; + eodPosi.PosiDividendSum = 0; + eodPosi.PosiProfitSum -= eodPosi.TdCloseDividend; + //互换持仓价值要去掉已实现的 + eodPosi.SwapPositionValue -= eodPosi.PosiDividendSum; + //已实现盈亏要加上已实现的 + eodPosi.RealizedPnl += eodPosi.PosiDividendSum; } - // 分红收支加入总实现盈亏 + UnwindData unwindData = new UnwindData(); + unwindData.SwapTradeId = td.id; + unwindData.ValueDate = settleDate; + unwindData.StartDate = td.StartDate.Value; + unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0); + unwindData.PosiNotionalValue = curEodPositions.Sum(x => x.PosiNotionalValue); + unwindData.PayDate = dividendPayDate; unwindData.SwapDividendPnl = dividendTotal; + unwindData.SwapCloseAmount = 0; + unwindData.SwapMarginRebatePnl = 0; + unwindData.SwapRealizedPnL = dividendTotal; - // 总实现盈亏 - unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount + unwindData.SwapMarginRebatePnl + dividendTotal; - - SaveAutoSwapDeal(td, autoInterests, unwindData, interval, dividendEvents); + SaveAutoSwapDeal(td,null , unwindData,null, dividendEvents:dividendEvents); } /// /// 保存自动互换数据信息 @@ -504,7 +514,7 @@ namespace YLErp.Modules.SwapModule // 预付金腿:单独插入一条资金记录(系统操作_预付金返息) if (unwindData.SwapMarginRebatePnl != 0) { - AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate); + clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate); } // 分红:使用派息支付日偏移记录资金记录 @@ -513,16 +523,21 @@ namespace YLErp.Modules.SwapModule var dividendPayDate = (dividendEvents != null && dividendEvents.Count > 0) ? dividendEvents.First().PayDate.Value : unwindData.ValueDate; - AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapDividendPnl), ClientCashInCashOut.系统操作_互换, dividendPayDate); + clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapDividendPnl), ClientCashInCashOut.系统操作_互换, dividendPayDate); } string data = JsonConvert.SerializeObject(unwindData); var swapEvent = new SwapEventService(this).AddSwapEventDate(unwindData.ValueDate, unwindData.SwapTradeId, (int)SwapEventTypeEnum.自动互换, data, clientCashId, true, "系统操作-自动互换");//将互换总额存入事件 - flowEvents.ForEach(x => + if (flowEvents!=null) { - x.EventId = swapEvent.id; - DbContext.swap_flow_event.Add(x); - }); + flowEvents.ForEach(x => + { + x.EventId = swapEvent.id; + DbContext.swap_flow_event.Add(x); + }); + UpdateInitalPostion(flowEvents, td.id); + } + // 保存分红事件 if (dividendEvents != null) { @@ -531,8 +546,8 @@ namespace YLErp.Modules.SwapModule x.EventId = swapEvent.id; DbContext.swap_flow_event.Add(x); }); + UpdateInitalPostion(dividendEvents, td.id); } - UpdateInitalPostion(flowEvents, td.id); return swapEvent.id; } /// @@ -1450,14 +1465,12 @@ namespace YLErp.Modules.SwapModule curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio; curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio; curretEod.TdPosiDividend = 0; - // 有互换事件时,分红已全量结算,不再查分红,TdPosiDividend和PosiDividendSum都归0 - var hasSwapEvent = unwindEvents.Any(e => e.EventType == (int)SwapFlowEventTypeEnum.互换 || e.EventType == (int)SwapFlowEventTypeEnum.自动互换); - if (!hasSwapEvent && valueDate > td.StartDate.Value && (curretEod.PosiQuantity > 0)) + // 分红与互换无关,只要持仓>0且起始日早于当前日,正常计算当日分红 + if (valueDate > td.StartDate.Value && (curretEod.PosiQuantity > 0)) { decimal tax = um.ValueAddedTax ?? 0; BondPaymentService bondPaymentService = new BondPaymentService(UserInfo); decimal payment = bondPaymentService.CalcPayment(curretEod.UnderlyingCode, eod.ValueDate, valueDate, curretEod.PosiQuantity, shortRatio, directionRatio); - // 考虑增值税 curretEod.TdPosiDividend = Math.Round(payment / (1 + tax) * (1 - tax), 2); } curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl; @@ -1466,16 +1479,12 @@ namespace YLErp.Modules.SwapModule 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); - // 当日浮动端平仓盈亏·分红 + // 当日浮动端平仓盈亏·分红(仅来自平仓事件中已实现的分红) curretEod.TdCloseDividend = unwindEvents.Sum(e => e.DividendIn); curretEod.RealizedDividend = curretEod.RealizedDividend + curretEod.TdCloseDividend; - // 浮动端待实现收益·分红(有互换事件时全量结算归0;否则用当前持仓从起始日重算) - if (hasSwapEvent) - { - curretEod.PosiDividendSum = 0; - } - else if (curretEod.PosiQuantity > 0) + // 分红与互换解耦:持仓>0时从起始日重算待实现分红,不再受互换事件影响 + if (curretEod.PosiQuantity > 0) { decimal tax = um.ValueAddedTax ?? 0; BondPaymentService bondPaymentService = new BondPaymentService(UserInfo); diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js index eea6de93..9cdd68fa 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js @@ -184,7 +184,7 @@ const vue = new Vue({ let totalDividend = parseFloat(thisObj.deal.PositionQty) * resp.obj.totalInterest * ratio * floatRatio; let consumedDividend = parseFloat(resp.obj.consumedDividend ?? 0); // 互换是全量消费,consumedDividend>0 表示分红已被当天互换消费,归0 - thisObj.floatPosition.DividendIn = Math.abs(consumedDividend) > 0 ? 0 : parseFloat(totalDividend.toFixed(2)); + thisObj.floatPosition.DividendIn = Math.abs(consumedDividend) > 0 ? parseFloat((totalDividend - consumedDividend).toFixed(2)) : parseFloat(totalDividend.toFixed(2)); thisObj.floatPosition.DividendPending = 0; thisObj.calcFloatClosePnl(); thisObj.dataFormat(); diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js index 7539f08b..21243e1b 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js @@ -283,7 +283,7 @@ const vue = new Vue({ let totalDividend = parseFloat(thisObj.deal.CloseQty) * resp.obj.totalInterest * ratio * floatRatio; let consumedDividend = parseFloat(resp.obj.consumedDividend ?? 0); // 互换是全量消费,consumedDividend>0 表示分红已被当天互换消费,归0 - thisObj.floatPosition.DividendIn = Math.abs(consumedDividend) > 0 ? 0 : parseFloat(totalDividend.toFixed(2)); + thisObj.floatPosition.DividendIn = Math.abs(consumedDividend) > 0 ? parseFloat((totalDividend - consumedDividend).toFixed(2)) : parseFloat(totalDividend.toFixed(2)); var posiQty = parseFloat(thisObj.floatPosition.Quantity) - parseFloat(thisObj.deal.CloseQty); thisObj.floatPosition.DividendPending = parseFloat((posiQty * resp.obj.totalInterest * ratio * floatRatio).toFixed(2)); thisObj.calcFloatClosePnl();