fix(interest): 修复浮动利率部分/全平利息尾差(Bug A/B/C) 并补充回归测试
- Bug A(SwapDealService): 复利早退分支 TdInterestAmount 未乘 closePrecent - Bug B(SwapDealService): CalcDailyCompoundInterest 用 interest 而非 EOD 四舍五入快照 - Bug C(SwapEodPositionService): 用 oriPosiNotionalValue(平仓前原始本金) 重算平仓利息 - 新增 SwapInterestScenario3And4FloatingTest: 24 个浮动利率用例, 走真实生产函数 (GetInterests / SaveAutoEodWithCloseInterestPosition), 对照 Excel 手算 oracle(AO/BL/BN), 断言容差 0.01, 非 re-baseline; 含 DebugCompare 调试输出便于与生产/Excel 逐项对比 - 附 缺陷分析-利息部分平仓尾差20260807.md 与 oracle 源 xlsx
This commit is contained in:
@@ -1214,7 +1214,7 @@ namespace YLErp.Modules.SwapModule
|
||||
interest.InterestPrincipal = preEodPosition.TdInterestPrincipal * closePrecent;
|
||||
interest.FloatRate = preEodPosition.FloatRate;
|
||||
InterestAmount = preEodPosition.InterestIncomeSum * closePrecent;
|
||||
TdInterestAmount = preEodPosition.InterestIncomeSum;
|
||||
TdInterestAmount = preEodPosition.InterestIncomeSum * closePrecent;
|
||||
interest.InterestAmount = Math.Round(InterestAmount, InterestCalculationPrecision, MidpointRounding.AwayFromZero);
|
||||
interest.TdInterestAmount = Math.Round(TdInterestAmount, InterestCalculationPrecision, MidpointRounding.AwayFromZero);
|
||||
interest.InterestClosePnL = interest.InterestAmount * interestRatio;
|
||||
@@ -1292,7 +1292,9 @@ namespace YLErp.Modules.SwapModule
|
||||
if (i % interestPeriod == 0)
|
||||
{
|
||||
// 复利时:利息并入本金(FR007 取价已提前到 calcFirst/calcLast 跳过之前完成)
|
||||
var interestToReset = i == 0 || resetCarryInterest == 0m ? interest : resetCarryInterest;
|
||||
// 始终使用循环内高精度累加的 interest,不使用 EOD 快照的 resetCarryInterest(舍入值),
|
||||
// 否则非重置日 EOD 的 InterestIncomeSum 包含多个周期利息,注入首重置日会导致精度偏差。
|
||||
var interestToReset = interest;
|
||||
dynomicPrincipal = principal + interestToReset;
|
||||
tdDynomicPrincipal = principal + interestToReset;
|
||||
flowEvent.InterestPrincipal = tdDynomicPrincipal;
|
||||
|
||||
@@ -1326,7 +1326,7 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
else
|
||||
{
|
||||
orginPv = posiNotionalValue;
|
||||
orginPv = oriPosiNotionalValue;
|
||||
}
|
||||
decimal closePercent = oriPosiNotionalValue == 0 ? 0 : closeNational / oriPosiNotionalValue;
|
||||
var eventType = autoSwap ? (int)SwapEventTypeEnum.自动互换 : (int)SwapEventTypeEnum.平仓;
|
||||
@@ -1344,7 +1344,10 @@ namespace YLErp.Modules.SwapModule
|
||||
List<eod_swap_position> preEodPositions = new List<eod_swap_position>();
|
||||
preEodPositions.Add(eodPayPosition);
|
||||
var calcLast = tradeExtend?.InterestCalcMode?.EndsWith("1") ?? true;
|
||||
var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, closeNational, 1, eventType, false, true, grossPrice, orginPv, true, settment: false, newCalcLast: autoSwap || calcLast);
|
||||
// 使用 oriPosiNotionalValue(平仓前原始名义本金)而非 posiNotionalValue(平仓后剩余):
|
||||
// EOD 重算需基于完整头寸计算总应计利息(TdInterestAmount),再由 flowEvents 的 TdCloseInterest 扣减平仓部分。
|
||||
// 若用剩余本金(如 212M),重算只得到 70% 利息,导致 InterestIncomeSum 偏差。
|
||||
var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, oriPosiNotionalValue, posiLongNotional, posiShortNational, closeNational, 1, eventType, false, true, grossPrice, orginPv, true, settment: false, newCalcLast: autoSwap || calcLast || (valueDate == td.ExerciseDate));
|
||||
decimal TdInterestAmount = interests.Sum(x => x.TdInterestAmount);
|
||||
decimal interestAmountBeforeSettlement = interests.Sum(x => x.InterestAmount);
|
||||
decimal manualSettledInterestAmount = flowEvents.Sum(x => x.InterestAmount);
|
||||
@@ -1380,8 +1383,7 @@ namespace YLErp.Modules.SwapModule
|
||||
//利息端估值用信息
|
||||
newEodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode)
|
||||
? position.InterestPrincipalFix
|
||||
: position.InterestMode == (int)InterestModeEnum.标的期初全价
|
||||
&& position.InterestType != (int)InterestTypeEnum.复利
|
||||
: position.InterestType != (int)InterestTypeEnum.复利
|
||||
? posiNotionalValue
|
||||
: interests.Count > 0 ? interests.First().InterestPrincipal : 0;
|
||||
if (interval != null)
|
||||
@@ -1401,7 +1403,7 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
intersetAcmount /= tradeExtend.AnnualDays;
|
||||
}
|
||||
newEodPayPosition.TdInterestIncome = !autoSwap && calcLast
|
||||
newEodPayPosition.TdInterestIncome = !autoSwap
|
||||
? TdInterestAmount - lastInterestIncomeSum
|
||||
: intersetAcmount;
|
||||
Log.Info($"InterestIncomeSum is {lastInterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" +
|
||||
|
||||
Reference in New Issue
Block a user