fix(swap): 修复复利计算和部分平仓处理中的多个问题(算头算尾合约复利平仓)
- 修复合约名义本金规模模式下部分平仓时名义本金计算错误 - 修复最终全平时复利利息计算中历史平仓尾差处理问题 - 修复复利重置日处理中利息重复注入历史本金的错误 - 修复部分平仓后日终待实现利息计算中本金比例应用问题 - 修复全平且实际金额覆盖应结利息后待实现利息清零逻辑 - 新增复合复利交换服务用于单元测试验证 - 添加多个测试用例覆盖复利计算边界场景
This commit is contained in:
@@ -48,6 +48,8 @@ namespace YLErp.Modules.SwapModule
|
||||
/// </summary>
|
||||
public List<swap_flow_event> AutoInterests { get; set; }
|
||||
|
||||
public SwapDealService DealService { get; set; }
|
||||
|
||||
public eod_swap_position LastInterestCalculationEodPosition { get; private set; }
|
||||
|
||||
public StubEodPositionService() : base(nameof(DealInterestsScenarioTest))
|
||||
@@ -74,7 +76,7 @@ namespace YLErp.Modules.SwapModule
|
||||
return AutoInterests;
|
||||
}
|
||||
|
||||
return new SwapDealService(this).GetInterests(td, tradeExtend, valueDate, unwindDate,
|
||||
return (DealService ?? new SwapDealService(this)).GetInterests(td, tradeExtend, valueDate, unwindDate,
|
||||
eodPositions, positions, posiNotionalValue, posiLongNotionalValue, posiShortNotionalValue,
|
||||
closePosiNotionalValue, closePrecent, eventType, tdClose, needPrice,
|
||||
grossPrice, orginPv, add, settment, newCalcLast, closeList);
|
||||
@@ -126,6 +128,27 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StubCompoundSwapDealService : SwapDealService
|
||||
{
|
||||
private readonly IReadOnlyDictionary<DateTime, double> _floatRates;
|
||||
|
||||
public StubCompoundSwapDealService(IReadOnlyDictionary<DateTime, double> floatRates = null)
|
||||
: base(new OptUserInfo(0, nameof(StubCompoundSwapDealService), OptUserFrom.UnitTest))
|
||||
{
|
||||
_floatRates = floatRates ?? new Dictionary<DateTime, double>();
|
||||
}
|
||||
|
||||
protected override bool TryGetFloatRate(DateTime valueDate, string underlyingCode, out double rate)
|
||||
{
|
||||
return _floatRates.TryGetValue(valueDate.Date, out rate);
|
||||
}
|
||||
|
||||
public override decimal GetConsumedInterest(int tradeId, long positionId, DateTime beforeDate)
|
||||
{
|
||||
return 0m;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 数据构建器
|
||||
@@ -1085,6 +1108,437 @@ namespace YLErp.Modules.SwapModule
|
||||
MidpointRounding.AwayFromZero), "Final close cash interest must be 0.82");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DI_MANUAL_CLOSE_007_CompoundFinalCloseSettlesFirstPartialCloseRoundingTail()
|
||||
{
|
||||
const decimal originalNotional = 10012.35m;
|
||||
const decimal remainingNotional = originalNotional / 2m;
|
||||
const decimal rate = 0.0299m;
|
||||
var firstCloseDate = StartDate.AddDays(6);
|
||||
var finalCloseDate = firstCloseDate.AddDays(6);
|
||||
var td = CreateTrade();
|
||||
td.trade_extend.ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
|
||||
{
|
||||
AnnualDays = AnnualDays,
|
||||
InterestCalcMode = "11",
|
||||
SettlementRules = 0
|
||||
});
|
||||
var position = CreateInterestPosition();
|
||||
position.InterestType = (int)InterestTypeEnum.复利;
|
||||
position.InterestRateDefault = rate;
|
||||
position.InterestPrincipalFix = originalNotional;
|
||||
position.interest_rest_days = 1;
|
||||
position.InterestSwapInterval = null;
|
||||
var dealService = new StubCompoundSwapDealService();
|
||||
var eodService = new StubEodPositionService { DealService = dealService };
|
||||
|
||||
var firstCloseInterest = dealService.GetInterests(
|
||||
td, td.trade_extend, firstCloseDate, firstCloseDate,
|
||||
new List<eod_swap_position>(), new List<swap_position> { position },
|
||||
originalNotional, originalNotional, 0m, remainingNotional, 0.5m,
|
||||
(int)SwapEventTypeEnum.平仓, false, false, 1m, originalNotional,
|
||||
settment: false).Single();
|
||||
var firstCloseCash = Math.Round(firstCloseInterest.InterestAmount, ConsGlobal.MoneyRound,
|
||||
MidpointRounding.AwayFromZero);
|
||||
var firstCloseRoundingTail = firstCloseInterest.InterestAmount - firstCloseCash;
|
||||
Assert.AreNotEqual(0m, firstCloseRoundingTail,
|
||||
$"首次部分平仓高精度利息 {firstCloseInterest.InterestAmount:F12} 按两位实际结算 {firstCloseCash:F2},尾差 {firstCloseRoundingTail:F12}");
|
||||
var firstCloseFlow = CreateSwapFlowEvent(firstCloseDate, firstCloseCash);
|
||||
firstCloseFlow.EventType = (int)SwapFlowEventTypeEnum.平仓;
|
||||
firstCloseFlow.InterestPrincipal = remainingNotional;
|
||||
var firstCloseEod = eodService.ExecuteSaveAutoEodWithCloseInterestPosition(
|
||||
null, position, td, firstCloseDate, null, remainingNotional, 0m,
|
||||
new List<swap_flow_event> { firstCloseFlow }, remainingNotional, false);
|
||||
|
||||
var replayAtPreviousEod = dealService.GetInterests(
|
||||
td, td.trade_extend, firstCloseDate, firstCloseDate,
|
||||
new List<eod_swap_position>(), new List<swap_position> { position },
|
||||
remainingNotional, remainingNotional, 0m, remainingNotional, 1m,
|
||||
(int)SwapEventTypeEnum.平仓, false, false, 1m, originalNotional,
|
||||
settment: false).Single();
|
||||
var replayAtFinalClose = dealService.GetInterests(
|
||||
td, td.trade_extend, finalCloseDate, finalCloseDate,
|
||||
new List<eod_swap_position>(), new List<swap_position> { position },
|
||||
remainingNotional, remainingNotional, 0m, remainingNotional, 1m,
|
||||
(int)SwapEventTypeEnum.平仓, false, false, 1m, originalNotional,
|
||||
settment: false).Single();
|
||||
var expectedFinalInterest = firstCloseEod.InterestIncomeSum
|
||||
+ replayAtFinalClose.InterestAmount - replayAtPreviousEod.InterestAmount;
|
||||
var expectedFinalCash = Math.Round(expectedFinalInterest, ConsGlobal.MoneyRound,
|
||||
MidpointRounding.AwayFromZero);
|
||||
var expectedTotalCash = Math.Round(firstCloseCash + firstCloseEod.InterestIncomeSum
|
||||
+ replayAtFinalClose.InterestAmount - replayAtPreviousEod.InterestAmount,
|
||||
ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
|
||||
var finalCloseInterest = dealService.GetInterests(
|
||||
td, td.trade_extend, finalCloseDate, finalCloseDate,
|
||||
new List<eod_swap_position> { firstCloseEod }, new List<swap_position> { position },
|
||||
remainingNotional, remainingNotional, 0m, remainingNotional, 1m,
|
||||
(int)SwapEventTypeEnum.平仓, false, false, 1m, originalNotional,
|
||||
settment: false).Single();
|
||||
var finalCloseCash = Math.Round(finalCloseInterest.InterestAmount, ConsGlobal.MoneyRound,
|
||||
MidpointRounding.AwayFromZero);
|
||||
var finalCloseFlow = CreateSwapFlowEvent(finalCloseDate, finalCloseCash);
|
||||
finalCloseFlow.EventType = (int)SwapFlowEventTypeEnum.平仓;
|
||||
finalCloseFlow.InterestPrincipal = remainingNotional;
|
||||
var finalCloseEod = eodService.ExecuteSaveAutoEodWithCloseInterestPosition(
|
||||
firstCloseEod, position, td, finalCloseDate, null, 0m, 0m,
|
||||
new List<swap_flow_event> { finalCloseFlow }, remainingNotional, false);
|
||||
|
||||
AssertDecimal(expectedFinalCash, finalCloseCash,
|
||||
"最终全平现金必须带走上一日日终的待实现利息尾差");
|
||||
AssertDecimal(expectedTotalCash,
|
||||
firstCloseCash + finalCloseCash,
|
||||
"两次实际结算现金必须守恒");
|
||||
AssertDecimal(0m, finalCloseEod.InterestIncomeSum, "全平且实际金额覆盖应结利息后待实现应清零");
|
||||
AssertDecimal(firstCloseCash + finalCloseCash, finalCloseEod.RealizedInterest,
|
||||
"累计已实现利息必须等于历次实际结算金额之和");
|
||||
|
||||
var incompleteFinalCloseFlow = CreateSwapFlowEvent(finalCloseDate, finalCloseCash - 0.01m);
|
||||
incompleteFinalCloseFlow.EventType = (int)SwapFlowEventTypeEnum.平仓;
|
||||
incompleteFinalCloseFlow.InterestPrincipal = remainingNotional;
|
||||
var incompleteFinalCloseEod = eodService.ExecuteSaveAutoEodWithCloseInterestPosition(
|
||||
firstCloseEod, position, td, finalCloseDate, null, 0m, 0m,
|
||||
new List<swap_flow_event> { incompleteFinalCloseFlow }, remainingNotional, false);
|
||||
AssertDecimal(expectedFinalInterest - incompleteFinalCloseFlow.InterestAmount,
|
||||
incompleteFinalCloseEod.InterestIncomeSum,
|
||||
"最终全平流水少结 0.01 时,日终必须保留未结利息而非清零");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DI_GLMS_20260421_0007_ContractNotionalCompoundPartialCloseScalesAndFinalCloseUsesRemainingPrincipal()
|
||||
{
|
||||
const decimal notional = 303139117.8m;
|
||||
const decimal partialPercent = 0.3m;
|
||||
const decimal partialNotional = notional * partialPercent;
|
||||
const decimal remainingNotional = notional - partialNotional;
|
||||
const decimal spread = 0.0025m;
|
||||
var startDate = new DateTime(2026, 4, 21);
|
||||
var maturityDate = new DateTime(2026, 5, 19);
|
||||
var partialCloseDate = new DateTime(2026, 5, 11);
|
||||
var td = new trade
|
||||
{
|
||||
id = 7007,
|
||||
TradeNumber = "GLMS-20260421-0007",
|
||||
ClientId = 999998,
|
||||
TradeType = "收益互换",
|
||||
TradeDate = startDate,
|
||||
StartDate = startDate,
|
||||
ExerciseDate = maturityDate,
|
||||
TradeStatus = "确认成交",
|
||||
ValidState = "Valid",
|
||||
trade_extend = new trade_extend
|
||||
{
|
||||
TradeId = 7007,
|
||||
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
|
||||
{
|
||||
AnnualDays = AnnualDays,
|
||||
InterestCalcMode = "11",
|
||||
SettlementRules = 0
|
||||
})
|
||||
}
|
||||
};
|
||||
var position = new swap_position
|
||||
{
|
||||
id = 70071,
|
||||
SwapTradeId = td.id,
|
||||
PositionType = (int)PositionTypeFlag.Unknown,
|
||||
InterestDirection = (int)SwapDirectionEnum.收取,
|
||||
InterestMode = (int)InterestModeEnum.合约名义本金规模,
|
||||
InterestRateDefault = spread,
|
||||
InterestPrincipalFix = notional,
|
||||
PosiStartDate = startDate,
|
||||
PosiMatuirityDate = maturityDate,
|
||||
IsInitial = true,
|
||||
Invalid = false,
|
||||
InterestType = (int)InterestTypeEnum.复利,
|
||||
IsAnnualized = true,
|
||||
interest_rest_days = 7,
|
||||
interest_rule = 0,
|
||||
FloatRateUnderlyingCode = "FR007",
|
||||
InterestSwapInterval = JsonConvert.SerializeObject(new List<IntervalModel>
|
||||
{
|
||||
new IntervalModel { Date = maturityDate, Rate = spread, Settlement = 0 }
|
||||
})
|
||||
};
|
||||
var service = new StubCompoundSwapDealService(new Dictionary<DateTime, double>
|
||||
{
|
||||
[new DateTime(2026, 4, 20)] = 0.0132,
|
||||
[new DateTime(2026, 4, 21)] = 0.0132,
|
||||
[new DateTime(2026, 4, 22)] = 0.0132,
|
||||
[new DateTime(2026, 4, 23)] = 0.0132,
|
||||
[new DateTime(2026, 4, 24)] = 0.0131,
|
||||
[new DateTime(2026, 4, 27)] = 0.013502,
|
||||
[new DateTime(2026, 4, 28)] = 0.0136,
|
||||
[new DateTime(2026, 4, 29)] = 0.0138,
|
||||
[new DateTime(2026, 4, 30)] = 0.0139,
|
||||
[new DateTime(2026, 5, 4)] = 0.0139,
|
||||
[new DateTime(2026, 5, 5)] = 0.0139,
|
||||
[new DateTime(2026, 5, 6)] = 0.0136,
|
||||
[new DateTime(2026, 5, 7)] = 0.0136,
|
||||
[new DateTime(2026, 5, 8)] = 0.0135,
|
||||
[new DateTime(2026, 5, 9)] = 0.0131,
|
||||
[new DateTime(2026, 5, 11)] = 0.0134,
|
||||
[new DateTime(2026, 5, 12)] = 0.013,
|
||||
[new DateTime(2026, 5, 13)] = 0.0129,
|
||||
[new DateTime(2026, 5, 14)] = 0.013,
|
||||
[new DateTime(2026, 5, 15)] = 0.013,
|
||||
[new DateTime(2026, 5, 18)] = 0.0132,
|
||||
[new DateTime(2026, 5, 19)] = 0.0131
|
||||
});
|
||||
var previousEod = new eod_swap_position
|
||||
{
|
||||
id = 70072,
|
||||
SwapTradeId = td.id,
|
||||
PositionId = position.id,
|
||||
ValueDate = new DateTime(2026, 5, 10),
|
||||
TdInterestPrincipal = 303324019.3183441374m,
|
||||
InterestIncomeSum = 266674.349853521170m,
|
||||
InterestProfitSum = 266674.349853521170m,
|
||||
FloatRate = 0.0139m
|
||||
};
|
||||
|
||||
var partial = service.GetInterests(
|
||||
td, td.trade_extend, partialCloseDate, partialCloseDate,
|
||||
new List<eod_swap_position> { previousEod }, new List<swap_position> { position },
|
||||
notional, notional, 0m, partialNotional, partialPercent,
|
||||
(int)SwapEventTypeEnum.平仓, false, false, 0m, notional,
|
||||
settment: false).Single();
|
||||
AssertDecimal(84090.95m, Math.Round(partial.InterestAmount, ConsGlobal.MoneyRound,
|
||||
MidpointRounding.AwayFromZero),
|
||||
"GLMS-20260421-0007 30% 复利合约名义本金平仓必须按比例结算");
|
||||
|
||||
var final = service.GetInterests(
|
||||
td, td.trade_extend, maturityDate, maturityDate,
|
||||
new List<eod_swap_position>(), new List<swap_position> { position },
|
||||
remainingNotional, remainingNotional, 0m, remainingNotional, 1m,
|
||||
(int)SwapEventTypeEnum.平仓, false, false, 0m, remainingNotional,
|
||||
settment: false, newCalcLast: true).Single();
|
||||
AssertDecimal(268428.73m, Math.Round(final.InterestAmount, ConsGlobal.MoneyRound,
|
||||
MidpointRounding.AwayFromZero),
|
||||
"后续全平必须只结算剩余70%本金的复利,不重复结算原始全额");
|
||||
Assert.AreNotEqual(280303.16m,
|
||||
Math.Round(final.InterestAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero),
|
||||
"后续全平不得再次使用原始全额本金");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DI_GLMS_20260421_0007_EodPartialCloseCarriesRemainingCompoundAccrual()
|
||||
{
|
||||
const decimal originalNotional = 303139117.8m;
|
||||
const decimal partialNotional = 90941735.34m;
|
||||
const decimal remainingNotional = 212197382.46m;
|
||||
const decimal spread = 0.0025m;
|
||||
var partialCloseDate = new DateTime(2026, 5, 11);
|
||||
var intermediateDate = new DateTime(2026, 5, 18);
|
||||
var finalCloseDate = new DateTime(2026, 5, 19);
|
||||
var td = new trade
|
||||
{
|
||||
id = 7007,
|
||||
TradeNumber = "GLMS-20260421-0007-EOD",
|
||||
ClientId = 999998,
|
||||
TradeType = "收益互换",
|
||||
TradeDate = new DateTime(2026, 4, 21),
|
||||
StartDate = new DateTime(2026, 4, 21),
|
||||
ExerciseDate = finalCloseDate,
|
||||
TradeStatus = "确认成交",
|
||||
ValidState = "Valid",
|
||||
StructureType = "单标的",
|
||||
QuoteCurrency = "CNY",
|
||||
SettlementCurrency = "CNY",
|
||||
trade_extend = new trade_extend
|
||||
{
|
||||
TradeId = 7007,
|
||||
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
|
||||
{
|
||||
AnnualDays = AnnualDays,
|
||||
InterestCalcMode = "11",
|
||||
SettlementRules = 0
|
||||
})
|
||||
}
|
||||
};
|
||||
var position = new swap_position
|
||||
{
|
||||
id = 70071,
|
||||
SwapTradeId = td.id,
|
||||
PositionType = (int)PositionTypeFlag.Unknown,
|
||||
InterestDirection = (int)SwapDirectionEnum.收取,
|
||||
InterestMode = (int)InterestModeEnum.合约名义本金规模,
|
||||
InterestRateDefault = spread,
|
||||
InterestPrincipalFix = originalNotional,
|
||||
PosiStartDate = td.StartDate.Value,
|
||||
PosiMatuirityDate = finalCloseDate,
|
||||
IsInitial = true,
|
||||
Invalid = false,
|
||||
InterestType = (int)InterestTypeEnum.复利,
|
||||
IsAnnualized = true,
|
||||
interest_rest_days = 7,
|
||||
interest_rule = 0,
|
||||
FloatRateUnderlyingCode = "FR007",
|
||||
InterestSwapInterval = JsonConvert.SerializeObject(new List<IntervalModel>
|
||||
{
|
||||
new IntervalModel { Date = finalCloseDate, Rate = spread, Settlement = 0 }
|
||||
})
|
||||
};
|
||||
var dealService = new StubCompoundSwapDealService(new Dictionary<DateTime, double>
|
||||
{
|
||||
[new DateTime(2026, 4, 21)] = 0.0132,
|
||||
[new DateTime(2026, 4, 28)] = 0.0136,
|
||||
[new DateTime(2026, 4, 30)] = 0.0139,
|
||||
[new DateTime(2026, 5, 4)] = 0.0139,
|
||||
[new DateTime(2026, 5, 5)] = 0.0139,
|
||||
[new DateTime(2026, 5, 6)] = 0.0136,
|
||||
[new DateTime(2026, 5, 11)] = 0.0134,
|
||||
[new DateTime(2026, 5, 12)] = 0.013,
|
||||
[new DateTime(2026, 5, 13)] = 0.0129,
|
||||
[new DateTime(2026, 5, 19)] = 0.0131
|
||||
});
|
||||
var eodService = new StubEodPositionService { DealService = dealService };
|
||||
var previousEod = new eod_swap_position
|
||||
{
|
||||
id = 70072,
|
||||
SwapTradeId = td.id,
|
||||
PositionId = position.id,
|
||||
ValueDate = new DateTime(2026, 5, 10),
|
||||
InterestDirection = position.InterestDirection,
|
||||
InterestMode = position.InterestMode,
|
||||
InterestType = position.InterestType,
|
||||
InterestRateDefault = spread,
|
||||
InterestIncomeSum = 266674.349853521170m,
|
||||
InterestProfitSum = 266674.349853521170m,
|
||||
TdInterestPrincipal = 303324019.3183441374m,
|
||||
PosiNotionalValue = originalNotional,
|
||||
FloatRate = 0.0139m,
|
||||
IsAnnualized = true,
|
||||
interest_rest_days = 7,
|
||||
interest_rule = 0
|
||||
};
|
||||
var partialCloseFlow = new swap_flow_event
|
||||
{
|
||||
SwapTradeId = td.id,
|
||||
PositionId = position.id,
|
||||
EventType = (int)SwapFlowEventTypeEnum.平仓,
|
||||
EventDate = partialCloseDate,
|
||||
UnwindDate = partialCloseDate,
|
||||
InterestDirection = position.InterestDirection,
|
||||
InterestRate = spread,
|
||||
InterestPrincipal = partialNotional,
|
||||
InterestAmount = 84090.95m,
|
||||
InterestClosePnL = 84090.95m,
|
||||
DataState = (int)SwapFlowDateStateEnum.完成
|
||||
};
|
||||
|
||||
var partialEod = eodService.ExecuteSaveAutoEodWithCloseInterestPosition(
|
||||
previousEod, position, td, partialCloseDate, null, remainingNotional, 0m,
|
||||
new List<swap_flow_event> { partialCloseFlow }, partialNotional, false);
|
||||
AssertDecimal(13628.805251563956m, partialEod.TdInterestIncome,
|
||||
"5/11 EOD 当日新增复利必须按平仓前全额本金计提");
|
||||
AssertDecimal(196212.205105085126m, partialEod.InterestIncomeSum,
|
||||
"5/11 EOD 应保留部分平仓后的待实现复利");
|
||||
|
||||
var intermediateInterest = dealService.GetInterests(
|
||||
td, td.trade_extend, intermediateDate, intermediateDate,
|
||||
new List<eod_swap_position> { partialEod }, new List<swap_position> { position },
|
||||
remainingNotional, remainingNotional, 0m, remainingNotional, 1m,
|
||||
(int)SwapEventTypeEnum.平仓, false, false, 0m, originalNotional,
|
||||
settment: false, newCalcLast: true).Single();
|
||||
Assert.IsTrue(Math.Abs(259348.386714765m - intermediateInterest.InterestAmount) <= 0.01m,
|
||||
$"5/18 复利平仓应承接 5/11 日终剩余本金的累计利息 Expected approximately 259348.386714765, Actual: {intermediateInterest.InterestAmount}");
|
||||
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DI_GLMS_20260421_0006_FinalCloseCarriesPreviousEodInterest()
|
||||
{
|
||||
const decimal remainingNotional = 212197382.46m;
|
||||
const decimal expectedInterest = -119386.71m;
|
||||
var finalCloseDate = new DateTime(2026, 5, 19);
|
||||
var td = new trade
|
||||
{
|
||||
id = 6006,
|
||||
TradeNumber = "GLMS-20260421-0006",
|
||||
ClientId = 999998,
|
||||
TradeType = "收益互换",
|
||||
TradeDate = new DateTime(2026, 4, 21),
|
||||
StartDate = new DateTime(2026, 4, 22),
|
||||
ExerciseDate = finalCloseDate,
|
||||
TradeStatus = "确认成交",
|
||||
ValidState = "Valid",
|
||||
StructureType = "单标的",
|
||||
QuoteCurrency = "CNY",
|
||||
SettlementCurrency = "CNY",
|
||||
trade_extend = new trade_extend
|
||||
{
|
||||
TradeId = 6006,
|
||||
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
|
||||
{
|
||||
AnnualDays = AnnualDays,
|
||||
InterestCalcMode = "10",
|
||||
SettlementRules = 0
|
||||
})
|
||||
}
|
||||
};
|
||||
var position = new swap_position
|
||||
{
|
||||
id = 60061,
|
||||
SwapTradeId = td.id,
|
||||
PositionType = (int)PositionTypeFlag.Unknown,
|
||||
InterestDirection = (int)SwapDirectionEnum.收取,
|
||||
InterestMode = (int)InterestModeEnum.标的期初全价,
|
||||
InterestRateDefault = -0.021m,
|
||||
InterestPrincipalFix = remainingNotional,
|
||||
PosiStartDate = td.StartDate.Value,
|
||||
PosiMatuirityDate = td.ExerciseDate.Value,
|
||||
IsInitial = true,
|
||||
Invalid = false,
|
||||
InterestType = (int)InterestTypeEnum.复利,
|
||||
IsAnnualized = true,
|
||||
interest_rest_days = 7,
|
||||
interest_rule = 0,
|
||||
FloatRateUnderlyingCode = "FR007",
|
||||
InterestSwapInterval = null
|
||||
};
|
||||
var previousEod = new eod_swap_position
|
||||
{
|
||||
id = 60062,
|
||||
SwapTradeId = td.id,
|
||||
PositionId = position.id,
|
||||
ValueDate = new DateTime(2026, 5, 18),
|
||||
InterestDirection = position.InterestDirection,
|
||||
InterestMode = position.InterestMode,
|
||||
InterestType = position.InterestType,
|
||||
InterestRateDefault = position.InterestRateDefault,
|
||||
FloatRate = 0.0132m,
|
||||
InterestIncomeSum = expectedInterest,
|
||||
InterestProfitSum = expectedInterest,
|
||||
TdInterestPrincipal = remainingNotional,
|
||||
PosiNotionalValue = remainingNotional,
|
||||
IsAnnualized = true,
|
||||
interest_rest_days = 7,
|
||||
interest_rule = 0
|
||||
};
|
||||
var dealService = new StubCompoundSwapDealService(new Dictionary<DateTime, double>
|
||||
{
|
||||
[new DateTime(2026, 4, 22)] = 0.0132,
|
||||
[new DateTime(2026, 4, 29)] = 0.0138,
|
||||
[new DateTime(2026, 5, 6)] = 0.0136,
|
||||
[new DateTime(2026, 5, 13)] = 0.0129,
|
||||
[new DateTime(2026, 5, 19)] = 0.0131
|
||||
});
|
||||
|
||||
var result = dealService.GetInterests(
|
||||
td, td.trade_extend, finalCloseDate, finalCloseDate,
|
||||
new List<eod_swap_position> { previousEod }, new List<swap_position> { position },
|
||||
remainingNotional, remainingNotional, 0m, remainingNotional, 1m,
|
||||
(int)SwapEventTypeEnum.平仓, false, false, 0m, remainingNotional,
|
||||
settment: false).Single();
|
||||
|
||||
AssertDecimal(expectedInterest, result.InterestAmount,
|
||||
"GLMS-20260421-0006 最终全平应承接 5/18 日终待实现利息");
|
||||
Assert.AreNotEqual(-123072.67m, result.InterestAmount,
|
||||
"不得回归旧库错误的 -123072.67 最终利息");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [DI_MATURITY_SETTLEMENT_001] 到期日存在手动互换但未带齐待实现时不能清零;
|
||||
/// 当前事件按两位覆盖全部可结金额后,才可视为最终结算并清零。
|
||||
|
||||
Reference in New Issue
Block a user