diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index 18ade2a8..a8ee764f 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -338,6 +338,27 @@ namespace YLErp.Modules.SwapModule interests = GetInterests(td, tradeExtend, valueDate, unwindDate, lastEodPositions, positions, stockEqvNotional, posiLongNotionalValue, posiShortNotionalValue, posiNotionalValue, closePercent, eventType, tdClose, false, grossPrice ?? 0, orginPv, true, false,false, closeList); return interests; } + + /// + /// 获取利息腿"已通过历史互换结出的累计利息"(用于复利重算时扣除,类比分红的 CalcConsumedDividend)。 + /// 数据源为事件级 swap_flow_event.InterestAmount(互换/自动互换 完成态事件,互换当时即落库,不依赖日终归档)。 + /// + /// 交易id + /// 利息腿id + /// 结算日(不含,仅汇总此日之前的历史已结利息;当日事件由 closeList 去重逻辑单独处理) + /// 历史已结利息累计金额(绝对值) + public decimal GetConsumedInterest(int tradeId, long positionId, DateTime beforeDate) + { + List swapEventTypes = new List() { (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 }; + var consumed = DbContext.swap_flow_event + .Where(x => x.SwapTradeId == tradeId && x.PositionId == positionId + && swapEventTypes.Contains(x.EventType) + && x.DataState == (int)SwapFlowDateStateEnum.完成 + && x.EventDate < beforeDate) + .Sum(s => (decimal?)s.InterestAmount) ?? 0m; + return consumed; + } + /// /// 计算利息腿计息详细 /// @@ -412,7 +433,11 @@ namespace YLErp.Modules.SwapModule else { // 盘中互换场景,使用 CalcUnwindInterest - interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, calcLast||newCalcLast)); + // 取历史已结利息(事件级,互换当时落库),供复利重算扣除(仅复利需要;单利基于日终快照自带状态) + var consumedInterest = position.InterestType == (int)InterestTypeEnum.复利 + ? GetConsumedInterest(td.id, position.id, endDate) + : 0m; + interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, calcLast||newCalcLast, consumedInterest)); } } //当日有平仓或互换记录时,避免重复结算 @@ -592,7 +617,7 @@ namespace YLErp.Modules.SwapModule /// /// 计算盘中利息(平仓/互换) /// - private swap_flow_event CalcUnwindInterest(trade td, DateTime valueDate, DateTime endDate, swap_position position, decimal rate, decimal floatRate, decimal posiPrincipal, decimal closePrincipal, decimal closePercent, int annualDays, eod_swap_position preEod, int eventType, bool add, bool swap, decimal orginPv, bool calcFirst, bool calcLast) + private swap_flow_event CalcUnwindInterest(trade td, DateTime valueDate, DateTime endDate, swap_position position, decimal rate, decimal floatRate, decimal posiPrincipal, decimal closePrincipal, decimal closePercent, int annualDays, eod_swap_position preEod, int eventType, bool add, bool swap, decimal orginPv, bool calcFirst, bool calcLast, decimal consumedInterest = 0m) { if (preEod.id == 0) { @@ -606,7 +631,7 @@ namespace YLErp.Modules.SwapModule } } - return InitSwapDealInterest(td, valueDate, endDate, rate, position, add, swap, posiPrincipal, closePrincipal, closePercent, annualDays, eventType, preEod, false, orginPv, calcFirst, calcLast); + return InitSwapDealInterest(td, valueDate, endDate, rate, position, add, swap, posiPrincipal, closePrincipal, closePercent, annualDays, eventType, preEod, false, orginPv, calcFirst, calcLast, consumedInterest); } /// /// 初始化利息腿信息 @@ -638,8 +663,9 @@ namespace YLErp.Modules.SwapModule eod_swap_position preEodPosition, bool needPrice, decimal orginPv, - bool calcFirst, - bool calcLast + bool calcFirst, + bool calcLast, + decimal consumedInterest = 0m ) { decimal interestProfitSum = preEodPosition.InterestProfitSum; @@ -674,7 +700,7 @@ namespace YLErp.Modules.SwapModule var floateRate = preEodPosition.FloatRate; if (position.InterestType == (int)InterestTypeEnum.复利) { - CalcDailyCompoundInterest( endDate, position, closePosiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount); + CalcDailyCompoundInterest( endDate, position, closePosiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount, consumedInterest); } else { @@ -702,7 +728,7 @@ namespace YLErp.Modules.SwapModule /// 是否年化 /// 年化天数 /// - public void CalcDailyCompoundInterest( DateTime endDate, swap_position position, decimal principal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount) + public void CalcDailyCompoundInterest( DateTime endDate, swap_position position, decimal principal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m) { var startDate = position.PosiStartDate; decimal interestProfitSum = 0; @@ -761,6 +787,11 @@ namespace YLErp.Modules.SwapModule tdinterest += tdinterest1; } } + // 复利从头重放得到的是"假设从未结出"的整段总利息,需扣除历史已通过互换结出的利息, + // 否则已结部分会重复计息(类比分红 PosiDividendSum = totalToDate − RealizedDividend)。 + // consumedInterest 为绝对值口径(swap_flow_event.InterestAmount 之和),与 interest 口径一致。 + interest -= consumedInterest; + tdinterest -= consumedInterest; InterestAmount = Math.Round(interest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); TdInterestAmount = Math.Round(tdinterest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); }