test(swap): 扩展DealInterests测试-多次互换守恒+预付金ratio翻转
新增2个测试场景: - DI_SWAP_MULTI_001: 攒10天→互换结清→再攒5天→再互换结清 守恒约束: RealizedInterest+InterestIncomeSum=全程利息(17天) 验证多次互换结算后利息不丢失/不重复 - DI_MARGIN_001: 预付金腿(初始预付金,marginTypes)互换结清后 RealizedInterest应为负(ratio翻转:收取→-1→支付方向) 验证cs:789-793的marginTypes ratio翻转逻辑 验证: 89(T0/T1)+7(DealInterests)=96全通过,无回归。
This commit is contained in:
@@ -422,6 +422,174 @@ namespace YLErp.Modules.SwapModule
|
||||
Console.WriteLine($"连续5天归档:InterestIncomeSum 从0递增到{runningIncomeSum:F6} = 5×{DailyInterest:F6} ✅");
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 场景4:互换→收盘→再攒→再互换 守恒验证
|
||||
// ================================================================
|
||||
|
||||
#region 场景4:多次互换结算守恒
|
||||
|
||||
/// <summary>
|
||||
/// [DI_SWAP_MULTI_001] 攒10天→互换结清→再攒5天→再互换结清
|
||||
/// ---------------------------------------------------------------
|
||||
/// 验证:第一次互换后 InterestIncomeSum≈当天新计(攒的10天付了),
|
||||
/// 再攒5天后 InterestIncomeSum≈6天(5天新攒+1天当天新计),
|
||||
/// 第二次互换后 InterestIncomeSum≈当天新计(攒的6天又付了)。
|
||||
///
|
||||
/// 守恒约束:两次互换结算的 TdCloseInterest 之和 = 全程利息(15天+2天新计)。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DI_SWAP_MULTI_001_多次互换结算守恒()
|
||||
{
|
||||
var td = CreateTrade();
|
||||
var position = CreateInterestPosition();
|
||||
|
||||
// --- Phase 1: 攒10天(4/27~5/6),到5/6 ---
|
||||
var date10 = StartDate.AddDays(10); // 5/7
|
||||
decimal sum10days = Math.Round(Principal * FixedRate * 10 / AnnualDays,
|
||||
ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||||
|
||||
var preEod10 = CreatePreEod(date10.AddDays(-1), sum10days - DailyInterest); // 前日=9天
|
||||
// 当天新计让它到10天
|
||||
var svc1 = new StubEodPositionService();
|
||||
svc1.ExecuteDealInterests(new List<swap_position> { position },
|
||||
new List<eod_swap_position> { preEod10 }, date10, td,
|
||||
new List<swap_flow_event>(), Principal, 0m, 0m, 1m, Principal);
|
||||
var eod10days = svc1.PersistedPositions[0];
|
||||
AssertDecimal(sum10days, eod10days.InterestIncomeSum, "10天后待实现应=10天利息");
|
||||
Console.WriteLine($"Phase1: 攒10天 InterestIncomeSum={eod10days.InterestIncomeSum:F6}");
|
||||
|
||||
// --- Phase 2: 5/7 互换结清 ---
|
||||
var swapDate1 = date10; // 同天互换
|
||||
var svc2 = new StubEodPositionService();
|
||||
var swapEvt1 = CreateSwapFlowEvent(swapDate1, sum10days);
|
||||
var swapResult1 = svc2.ExecuteSaveEodInterestPosition(
|
||||
eod10days, null, position, td, swapDate1, new List<swap_flow_event> { swapEvt1 });
|
||||
|
||||
// 互换后待实现≈当天新计(攒的10天付了,但当天又产生1天新计)
|
||||
decimal dailyInc = Math.Round(Principal * FixedRate / AnnualDays,
|
||||
ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||||
AssertDecimal(dailyInc, swapResult1.InterestIncomeSum, "第一次互换后待实现≈当天新计");
|
||||
decimal firstRealized = swapResult1.TdCloseInterest;
|
||||
Console.WriteLine($"Phase2: 第一次互换 TdCloseInterest={firstRealized:F6}, 待实现={swapResult1.InterestIncomeSum:F6}");
|
||||
|
||||
// --- Phase 3: 再攒5天 ---
|
||||
decimal runningSum = swapResult1.InterestIncomeSum;
|
||||
var runningDate = swapDate1.AddDays(1);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var svc = new StubEodPositionService();
|
||||
var preEod = CreatePreEod(runningDate.AddDays(-1), runningSum);
|
||||
// 需要 preEod.RealizedInterest 累积
|
||||
preEod.RealizedInterest = swapResult1.RealizedInterest;
|
||||
svc.ExecuteDealInterests(new List<swap_position> { position },
|
||||
new List<eod_swap_position> { preEod }, runningDate, td,
|
||||
new List<swap_flow_event>(), Principal, 0m, 0m, 1m, Principal);
|
||||
runningSum = svc.PersistedPositions[0].InterestIncomeSum;
|
||||
runningDate = runningDate.AddDays(1);
|
||||
}
|
||||
Console.WriteLine($"Phase3: 再攒5天后 InterestIncomeSum={runningSum:F6}");
|
||||
|
||||
// --- Phase 4: 再互换结清 ---
|
||||
var svc4 = new StubEodPositionService();
|
||||
var preEodFinal = CreatePreEod(runningDate.AddDays(-1), runningSum);
|
||||
preEodFinal.RealizedInterest = swapResult1.RealizedInterest;
|
||||
var swapEvt2 = CreateSwapFlowEvent(runningDate, runningSum);
|
||||
var swapResult2 = svc4.ExecuteSaveEodInterestPosition(
|
||||
preEodFinal, null, position, td, runningDate, new List<swap_flow_event> { swapEvt2 });
|
||||
decimal secondRealized = swapResult2.TdCloseInterest;
|
||||
Console.WriteLine($"Phase4: 第二次互换 TdCloseInterest={secondRealized:F6}, 待实现={swapResult2.InterestIncomeSum:F6}");
|
||||
|
||||
// 守恒:两次互换实现的 + 最终待实现 = 全程天数 × dailyInc
|
||||
// 全程天数 = 10天(Phase1) + 1天(第一次互换当天新计) + 5天(Phase3) + 1天(第二次互换当天新计) = 17天
|
||||
// 但第一次互换的当天新计进了 InterestIncomeSum 没进 TdCloseInterest,
|
||||
// 第二次互换同理。所以守恒 = RealizedInterest合计 + 最终InterestIncomeSum = 全程利息
|
||||
decimal totalDays = 10 + 1 + 5 + 1; // 17天
|
||||
decimal expectedTotalInterest = Math.Round(Principal * FixedRate * totalDays / AnnualDays,
|
||||
ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||||
decimal actualTotal = swapResult2.RealizedInterest + swapResult2.InterestIncomeSum;
|
||||
Console.WriteLine($"守恒: RealizedInterest({swapResult2.RealizedInterest:F6}) + InterestIncomeSum({swapResult2.InterestIncomeSum:F6}) = {actualTotal:F6}");
|
||||
Console.WriteLine($"期望: {totalDays}天 × {dailyInc:F6} = {expectedTotalInterest:F6}");
|
||||
AssertDecimal(expectedTotalInterest, actualTotal,
|
||||
"已实现+待实现 应=全程利息(守恒)");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// ================================================================
|
||||
// 场景5:预付金腿(marginTypes ratio 翻转)符号验证
|
||||
// ================================================================
|
||||
|
||||
#region 场景5:预付金腿 ratio 翻转
|
||||
|
||||
/// <summary>
|
||||
/// [DI_MARGIN_001] 预付金腿互换结清后 RealizedInterest 应为负(支付方向)
|
||||
/// ---------------------------------------------------------------
|
||||
/// 预付金腿 InterestDirection=收取(1),但 marginTypes 会把 ratio 翻转为 -1。
|
||||
/// SwapPositionValue 应为负(负债),RealizedInterest 也应为负(券商支付)。
|
||||
/// 验证 cs:789-793 的 ratio 翻转逻辑。
|
||||
/// ---------------------------------------------------------------
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DI_MARGIN_001_预付金腿RealizedInterest为负()
|
||||
{
|
||||
var service = new StubEodPositionService();
|
||||
var td = CreateTrade();
|
||||
var settleDate = new DateTime(2026, 5, 10);
|
||||
|
||||
// 预付金腿(初始预付金 InterestMode=5,InterestDirection=收取)
|
||||
var marginPosition = new swap_position
|
||||
{
|
||||
id = 2001, SwapTradeId = 1, PositionType = (int)PositionTypeFlag.Unknown,
|
||||
InterestDirection = (int)SwapDirectionEnum.收取, InterestMode = (int)InterestModeEnum.初始预付金,
|
||||
InterestRateDefault = 0.005m, InterestPrincipalFix = 500m,
|
||||
PosiStartDate = StartDate, PosiMatuirityDate = ExerciseDate,
|
||||
IsInitial = true, Invalid = false, InterestType = (int)InterestTypeEnum.单利,
|
||||
IsAnnualized = true, interest_rest_days = 1, interest_rule = 0,
|
||||
FloatRateUnderlyingCode = null,
|
||||
InterestSwapInterval = null
|
||||
};
|
||||
|
||||
// 攒10天的预付金利息
|
||||
decimal marginDaily = Math.Round(500m * 0.005m / AnnualDays,
|
||||
ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||||
decimal margin10days = marginDaily * 10;
|
||||
var preEod = new eod_swap_position
|
||||
{
|
||||
id = 200, SwapTradeId = 1, PositionId = 2001, ValueDate = settleDate.AddDays(-1),
|
||||
ClientId = 999998, InterestDirection = (int)SwapDirectionEnum.收取,
|
||||
InterestMode = (int)InterestModeEnum.初始预付金,
|
||||
InterestIncomeSum = margin10days, InterestProfitSum = margin10days,
|
||||
RealizedInterest = 0m, InterestRateDefault = 0.005m,
|
||||
TdInterestPrincipal = 500m, PosiNotionalValue = 500m,
|
||||
InterestType = (int)InterestTypeEnum.单利, IsAnnualized = true,
|
||||
interest_rest_days = 1, FloatRate = 0m
|
||||
};
|
||||
|
||||
// 互换结清
|
||||
var swapEvent = new swap_flow_event
|
||||
{
|
||||
id = 3001, SwapTradeId = 1, EventType = (int)SwapFlowEventTypeEnum.互换,
|
||||
EventDate = settleDate, UnwindDate = settleDate, PositionId = 2001,
|
||||
InterestDirection = (int)SwapDirectionEnum.收取,
|
||||
InterestAmount = margin10days, InterestClosePnL = -margin10days, // 预付金 ratio 翻转后为负
|
||||
InterestRate = 0.005m, InterestMode = (int)InterestModeEnum.初始预付金,
|
||||
InterestPrincipal = 500m, FloatRate = 0m,
|
||||
DataState = (int)SwapFlowDateStateEnum.完成
|
||||
};
|
||||
|
||||
var result = service.ExecuteSaveEodInterestPosition(
|
||||
preEod, null, marginPosition, td, settleDate, new List<swap_flow_event> { swapEvent });
|
||||
|
||||
// 预付金 marginTypes 翻转 ratio=-1
|
||||
// RealizedInterest = 0 + TdCloseInterest(margin10days) * ratio(-1) = -margin10days
|
||||
AssertDecimal(-margin10days, result.RealizedInterest,
|
||||
"预付金腿 RealizedInterest 应为负(ratio翻转后支付方向)");
|
||||
Console.WriteLine($"预付金腿 RealizedInterest={result.RealizedInterest:F6}(负=支付)✅");
|
||||
Console.WriteLine($"SwapPositionValue={result.SwapPositionValue:F6}(应≈当天新计×ratio=-正)");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user