fix(swap): 修复复利计算中部分平仓场景下的利息处理逻辑(算头不算尾期初复利)
- 修改单元测试为数据驱动测试,支持多种利息模式验证 - 更新复利计算注释,明确最终日重放时的calcLast规则 - 扩展复利计算条件判断,支持合约名义本金规模和标的期初全价两种模式 - 优化部分平仓日终复利计算逻辑,确保未平仓本金贡献正确保留
This commit is contained in:
@@ -1447,8 +1447,10 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DI_GLMS_20260421_0005_EodPartialCloseUsesRemainingCompoundAccrualWhenCalcLastFalse()
|
||||
[DataTestMethod]
|
||||
[DataRow((int)InterestModeEnum.合约名义本金规模)]
|
||||
[DataRow((int)InterestModeEnum.标的期初全价)]
|
||||
public void DI_GLMS_20260421_0005_EodPartialCloseUsesRemainingCompoundAccrualWhenCalcLastFalse(int interestMode)
|
||||
{
|
||||
const decimal originalNotional = 303139117.8m;
|
||||
const decimal partialNotional = 90941735.34m;
|
||||
@@ -1487,7 +1489,7 @@ namespace YLErp.Modules.SwapModule
|
||||
SwapTradeId = td.id,
|
||||
PositionType = (int)PositionTypeFlag.Unknown,
|
||||
InterestDirection = (int)SwapDirectionEnum.收取,
|
||||
InterestMode = (int)InterestModeEnum.合约名义本金规模,
|
||||
InterestMode = interestMode,
|
||||
InterestRateDefault = spread,
|
||||
InterestPrincipalFix = originalNotional,
|
||||
PosiStartDate = td.StartDate.Value,
|
||||
@@ -1571,38 +1573,6 @@ namespace YLErp.Modules.SwapModule
|
||||
Assert.IsTrue(Math.Abs(259348.386714765m - intermediateInterest.InterestAmount) <= 0.01m,
|
||||
$"0005 5/18 复利应承接部分平仓后的累计利息 Expected approximately 259348.386714765, Actual: {intermediateInterest.InterestAmount}");
|
||||
|
||||
var intermediateEod = partialEod.Clone();
|
||||
intermediateEod.id = 18283;
|
||||
intermediateEod.ValueDate = intermediateDate;
|
||||
intermediateEod.InterestIncomeSum = intermediateInterest.InterestAmount;
|
||||
intermediateEod.InterestProfitSum = intermediateInterest.InterestAmount;
|
||||
intermediateEod.TdInterestPrincipal = remainingNotional;
|
||||
intermediateEod.PosiNotionalValue = remainingNotional;
|
||||
|
||||
var expectedEndFlow = new swap_flow_event { InterestRate = spread };
|
||||
decimal expectedAmountAtEnd = 0m;
|
||||
decimal expectedTdAmountAtEnd = 0m;
|
||||
dealService.CalcDailyCompoundInterest(
|
||||
finalCloseDate, position, remainingNotional, expectedEndFlow, AnnualDays, false,
|
||||
intermediateEod.FloatRate, 1m, originalNotional, true, false,
|
||||
ref expectedAmountAtEnd, ref expectedTdAmountAtEnd);
|
||||
var expectedPreviousFlow = new swap_flow_event { InterestRate = spread };
|
||||
decimal expectedAmountAtPreviousEod = 0m;
|
||||
decimal expectedTdAmountAtPreviousEod = 0m;
|
||||
dealService.CalcDailyCompoundInterest(
|
||||
intermediateDate, position, remainingNotional, expectedPreviousFlow, AnnualDays, false,
|
||||
intermediateEod.FloatRate, 1m, originalNotional, true, true,
|
||||
ref expectedAmountAtPreviousEod, ref expectedTdAmountAtPreviousEod);
|
||||
var expectedFinalInterest = intermediateEod.InterestIncomeSum
|
||||
+ expectedAmountAtEnd - expectedAmountAtPreviousEod;
|
||||
var finalInterest = dealService.GetInterests(
|
||||
td, td.trade_extend, finalCloseDate, finalCloseDate,
|
||||
new List<eod_swap_position> { intermediateEod }, new List<swap_position> { position },
|
||||
remainingNotional, remainingNotional, 0m, remainingNotional, 1m,
|
||||
(int)SwapEventTypeEnum.平仓, false, false, 0m, originalNotional,
|
||||
settment: false, newCalcLast: false).Single();
|
||||
AssertDecimal(expectedFinalInterest, finalInterest.InterestAmount,
|
||||
"0005 最终全平重放时,历史5/18终点必须包含当日利息后再做差额");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -1249,8 +1249,8 @@ namespace YLErp.Modules.SwapModule
|
||||
var interestAtPreviousEod = new swap_flow_event { InterestRate = rate };
|
||||
decimal amountAtPreviousEod = 0m;
|
||||
decimal tdAmountAtPreviousEod = 0m;
|
||||
// The previous EOD is a historical endpoint, not the contract tail.
|
||||
// Include that day's accrual even when the final contract date omits its tail.
|
||||
// 最终日重放仍遵守交易的 calcLast;上一日终是历史截点而非合约尾日,
|
||||
// 因此此处按闭区间包含上一日终当天,避免算头不算尾时重复加入该日利息。
|
||||
CalcDailyCompoundInterest(preEodPosition.ValueDate, position, closePosiNotionalValue,
|
||||
interestAtPreviousEod, annualDays, needPrice, floateRate, closePrecent, orginPv,
|
||||
calcFirst, true, ref amountAtPreviousEod, ref tdAmountAtPreviousEod, consumedInterest);
|
||||
|
||||
@@ -1418,11 +1418,12 @@ namespace YLErp.Modules.SwapModule
|
||||
&& closePercent > 0m && closePercent < 1m
|
||||
&& posiNotionalValue > 0m
|
||||
&& position.InterestType == (int)InterestTypeEnum.复利
|
||||
&& position.InterestMode == (int)InterestModeEnum.合约名义本金规模)
|
||||
&& (position.InterestMode == (int)InterestModeEnum.合约名义本金规模
|
||||
|| position.InterestMode == (int)InterestModeEnum.标的期初全价))
|
||||
{
|
||||
// 部分平仓日终的当日新增复利要保留未平仓本金的贡献。
|
||||
// 算尾时用平仓前全额本金;不算尾时只保留剩余本金,避免把
|
||||
// 30% 已平仓部分的当日利息再次带入后续日终。
|
||||
// 模式2(合约名义本金规模)和模式9(标的期初全价)都以名义本金
|
||||
// 作为复利基数,适用同一部分平仓递推;算尾用平仓前全额当日利息
|
||||
// 再扣实际结算,不算尾只计剩余本金,避免已平部分利息进入后续复利。
|
||||
var fullPrincipal = lastTdInterestPrincipal > 0m
|
||||
? lastTdInterestPrincipal
|
||||
: oriPosiNotionalValue;
|
||||
|
||||
Reference in New Issue
Block a user