diff --git a/UnitTestProject/Modules/SwapModule/Accrual/CompoundEodShadowTest.cs b/UnitTestProject/Modules/SwapModule/Accrual/CompoundEodShadowTest.cs
index a904af3a..625d2254 100644
--- a/UnitTestProject/Modules/SwapModule/Accrual/CompoundEodShadowTest.cs
+++ b/UnitTestProject/Modules/SwapModule/Accrual/CompoundEodShadowTest.cs
@@ -92,7 +92,7 @@ namespace UnitTestProject.Modules.SwapModule.Accrual
decimal oldInterest = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterestByEod(preEod, EodDate, TradeDate, position,
- Notional, Notional, flowEvent, AnnualDays, false, 0m, 1m,
+ Notional, Notional, flowEvent, AnnualDays, 0m, 1m,
ref oldInterest, ref oldTd);
// 新方法
@@ -125,7 +125,7 @@ namespace UnitTestProject.Modules.SwapModule.Accrual
decimal oldInterest = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterestByEod(preEod, nonResetDate, TradeDate, position,
- Notional, Notional, flowEvent, AnnualDays, false, 0m, 1m,
+ Notional, Notional, flowEvent, AnnualDays, 0m, 1m,
ref oldInterest, ref oldTd);
// 新方法
diff --git a/UnitTestProject/Modules/SwapModule/Accrual/CompoundPeriodShadowTest.cs b/UnitTestProject/Modules/SwapModule/Accrual/CompoundPeriodShadowTest.cs
index 855419c5..305fe802 100644
--- a/UnitTestProject/Modules/SwapModule/Accrual/CompoundPeriodShadowTest.cs
+++ b/UnitTestProject/Modules/SwapModule/Accrual/CompoundPeriodShadowTest.cs
@@ -74,7 +74,7 @@ namespace UnitTestProject.Modules.SwapModule.Accrual
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterest(EndDate, position, Notional, flowEvent,
- AnnualDays, false, 0m, 1m, true, false,
+ AnnualDays, 0m, 1m, true, false,
ref oldI, ref oldTd);
// 新方法:固定利率全段相同,分段点 = PosiStartDate + k×7
@@ -122,7 +122,7 @@ namespace UnitTestProject.Modules.SwapModule.Accrual
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterest(EndDate, position, Notional * closePct, flowEvent,
- AnnualDays, false, 0m, closePct, true, false,
+ AnnualDays, 0m, closePct, true, false,
ref oldI, ref oldTd, consumedInterest: consumed, resetCarryInterest: carry);
// 新方法
@@ -166,7 +166,7 @@ namespace UnitTestProject.Modules.SwapModule.Accrual
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterest(EndDate, position, Notional, flowEvent,
- AnnualDays, false, 0m, 1m, true, true,
+ AnnualDays, 0m, 1m, true, true,
ref oldI, ref oldTd);
// 新方法
diff --git a/UnitTestProject/Modules/SwapModule/Accrual/SimplePeriodShadowTest.cs b/UnitTestProject/Modules/SwapModule/Accrual/SimplePeriodShadowTest.cs
index 283ca087..a1a8a516 100644
--- a/UnitTestProject/Modules/SwapModule/Accrual/SimplePeriodShadowTest.cs
+++ b/UnitTestProject/Modules/SwapModule/Accrual/SimplePeriodShadowTest.cs
@@ -100,7 +100,7 @@ namespace UnitTestProject.Modules.SwapModule.Accrual
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailySimpleInterest(preEod, EndDate, position, Notional, flowEvent,
- AnnualDays, false, 0m, 1m, Notional, true, false, ref oldI, ref oldTd);
+ AnnualDays, 0m, 1m, Notional, true, false, ref oldI, ref oldTd);
// 新方法:固定利率全段相同
// 旧代码差分: dynomicPrincipal = preEod.TdInterestPrincipal(=0) + posiPrincipal - orginPv = 0
@@ -146,7 +146,7 @@ namespace UnitTestProject.Modules.SwapModule.Accrual
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailySimpleInterest(preEod, EndDate, position, Notional, flowEvent,
- AnnualDays, false, 0m, 0.5m, Notional, true, false, ref oldI, ref oldTd);
+ AnnualDays, 0m, 0.5m, Notional, true, false, ref oldI, ref oldTd);
// 新方法
// 差分本金 = preEod.TdInterestPrincipal + posiPrincipal - orginPv
@@ -200,7 +200,7 @@ namespace UnitTestProject.Modules.SwapModule.Accrual
decimal oldI = 0, oldTd = 0;
var svc = new FloatStubSvc(new StubIndexFixer(fixingAtReset));
svc.CalcDailySimpleInterest(preEod, EndDate, position, Notional, flowEvent,
- AnnualDays, false, floatRateIn, closePct, Notional, true, false, ref oldI, ref oldTd);
+ AnnualDays, floatRateIn, closePct, Notional, true, false, ref oldI, ref oldTd);
// 新方法:手算 segmentRates(对齐旧代码取价循环的逻辑)
// 4/21 <= preEodDate(4/30) → 跳过取价,currentFloat 保持入参 floatRateIn
diff --git a/UnitTestProject/Modules/SwapModule/Accrual/TdCarryInCharacterizationTest.cs b/UnitTestProject/Modules/SwapModule/Accrual/TdCarryInCharacterizationTest.cs
new file mode 100644
index 00000000..4e9540cd
--- /dev/null
+++ b/UnitTestProject/Modules/SwapModule/Accrual/TdCarryInCharacterizationTest.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using YLErp.Derivatives.Interest; // InterestResult, AccrualBoundary, SwapInterest
+using YLErp.Modules.SwapModule.Accrual; // SimpleInterestAccrual
+
+namespace UnitTestProject.Modules.SwapModule.Accrual
+{
+ ///
+ /// 性格化测试:部分平仓时 TdInterestAmount 的 carry-in 是否被错误缩放。
+ /// 调用真实纯函数 SimpleInterestAccrual.AccruePeriod,参数与线上 SwapDealService.cs:1290 完全一致
+ /// (priorAccrued = InterestProfitSum * closePercent)。先钉死“当前行为”,修复后再改断言。
+ ///
+ [TestClass]
+ public class TdCarryInCharacterizationTest
+ {
+ [TestMethod]
+ public void 部分平仓_历史累计利息_carryIn被缩放_复现当前行为()
+ {
+ // ── 例子(教学用整数,非市场真实利率)──
+ // 昨日(上一EOD)全腿累计利息 InterestProfitSum = 100(不缩放口径,下游 EOD :1300/:1370 当累计用)
+ // 今日部分平仓 closePercent = 0.3(平 30%)
+ // 计息基数 notional = 1000(全腿)
+ // 单段、年化=false、利率 0.10、区间 1 天 → 今日未缩放增量 = 1000*0.10*1 = 100
+ decimal interestProfitSum = 100m;
+ decimal closePercent = 0.3m;
+ decimal notional = 1000m;
+
+ var segmentRates = new List<(DateTime, decimal)> { (new DateTime(2026, 6, 1), 0.10m) };
+ var startDate = new DateTime(2026, 6, 1);
+ var endDate = new DateTime(2026, 6, 2); // 区间 1 天(StartOnly 边界 → days=1)
+ var priorValueDate = new DateTime(2026, 5, 31);
+ var boundary = AccrualBoundary.StartOnly;
+
+ // 线上真实调用(SwapDealService.cs:1290):priorAccrued = InterestProfitSum * closePercent
+ var result = SimpleInterestAccrual.AccruePeriod(
+ priorAccrued: interestProfitSum * closePercent, // = 30 ← 已缩放
+ notional: notional,
+ unwindFraction: closePercent,
+ segmentRates: segmentRates,
+ startDate: startDate,
+ endDate: endDate,
+ priorValueDate: priorValueDate,
+ boundary: boundary,
+ annualDays: 365,
+ isAnnualized: false);
+
+ Console.WriteLine("==== 当前代码(buggy)实际输出 ====");
+ Console.WriteLine($"InterestAmount (Accrued) = {result.Accrued}");
+ Console.WriteLine($"TdInterestAmount (AccruedToday) = {result.AccruedToday}");
+
+ // ── 推演(当前代码)──
+ // InterestAmount = 30 + 300*0.10*1(=30) = 60 (缩放累计,正确)
+ // TdInterestAmount= 30 + 1000*0.10*1(=100) = 130 ← 应为 200
+ // 差距 = 100*(1-0.3) = 70,即历史累计被砍掉的那一截。
+ Assert.AreEqual(60m, result.Accrued);
+ Assert.AreEqual(130m, result.AccruedToday); // 当前 buggy 值,先钉死现状
+
+ // ── 期望值(修复后)──
+ // TdInterestAmount 应为:InterestProfitSum(100, 不缩放) + 今日未缩放增量(100) = 200
+ // 修复 = SwapDealService.cs:1290 改传 InterestProfitSum(去 *closePercent)
+ // + SimpleInterestAccrual.cs:68 accrued = priorAccrued * unwindFraction
+ // 修复后 InterestAmount 仍 = 60(不变,正确),TdInterestAmount = 200。
+ }
+ }
+}
diff --git a/UnitTestProject/Modules/SwapModule/ConsumedInterestScenarioTest.cs b/UnitTestProject/Modules/SwapModule/ConsumedInterestScenarioTest.cs
index 9b70262f..866b4f3a 100644
--- a/UnitTestProject/Modules/SwapModule/ConsumedInterestScenarioTest.cs
+++ b/UnitTestProject/Modules/SwapModule/ConsumedInterestScenarioTest.cs
@@ -509,7 +509,7 @@ namespace YLErp.Modules.SwapModule
decimal tdInterestAmount = 0m;
service.CalcDailyCompoundInterestByEod(preEod, resetDate, startDate, position,
- principal, principal, flowEvent, AnnualDays, false, 0.013502m, 1m,
+ principal, principal, flowEvent, AnnualDays, 0.013502m, 1m,
ref interestAmount, ref tdInterestAmount);
AssertDecimal(principal + pendingInterest, flowEvent.InterestPrincipal,
diff --git a/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs b/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs
index ad142e51..07f9b289 100644
--- a/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs
+++ b/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs
@@ -1724,14 +1724,14 @@ namespace YLErp.Modules.SwapModule
decimal expectedAmountAtEnd = 0m;
decimal expectedTdAmountAtEnd = 0m;
dealService.CalcDailyCompoundInterest(
- finalCloseDate, position, remainingNotional, expectedEndFlow, AnnualDays, false,
+ finalCloseDate, position, remainingNotional, expectedEndFlow, AnnualDays,
intermediateEod.FloatRate, 1m, 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,
+ intermediateDate, position, remainingNotional, expectedPreviousFlow, AnnualDays,
intermediateEod.FloatRate, 1m, true, true,
ref expectedAmountAtPreviousEod, ref expectedTdAmountAtPreviousEod);
var expectedFinalInterest = intermediateEod.InterestIncomeSum
diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
index 8a3f0796..bf9bbaa4 100644
--- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
@@ -946,12 +946,12 @@ namespace YLErp.Modules.SwapModule
if (position.InterestType == (int)InterestTypeEnum.复利)
{
// 复利计算
- CalcDailyCompoundInterestByEod(preEod, valueDate, td.StartDate.Value, position, closePrincipal, posiPrincipal, interest, annualDays, false, eodFloatRate, 1m, ref interestAmount, ref tdInterestAmount);
+ CalcDailyCompoundInterestByEod(preEod, valueDate, td.StartDate.Value, position, closePrincipal, posiPrincipal, interest, annualDays, eodFloatRate, 1m, ref interestAmount, ref tdInterestAmount);
}
else
{
// 单利计算
- CalcDailySimpleInterestByEod(preEod, valueDate, td.StartDate.Value, position, closePrincipal, posiPrincipal, interest, annualDays, false, eodFloatRate, 1m, ref interestAmount, ref tdInterestAmount);
+ CalcDailySimpleInterestByEod(preEod, valueDate, td.StartDate.Value, position, closePrincipal, posiPrincipal, interest, annualDays, eodFloatRate, 1m, ref interestAmount, ref tdInterestAmount);
}
}
@@ -1121,7 +1121,7 @@ namespace YLErp.Modules.SwapModule
// 把上日尚未实现的的利息 按本次平掉的这部分计息基数分给本次平仓 并在重置日并入计息基数
// 它只在当前 endDate 恰好为重置日时使用,避免把同一笔历史利息重复资本化。
var resetCarryInterest = preEodPosition.InterestIncomeSum * remainingPercent;
- CalcDailyCompoundInterest(endDate, position, closePosiNotionalValue, interest, annualDays, needPrice,
+ CalcDailyCompoundInterest(endDate, position, closePosiNotionalValue, interest, annualDays,
floateRate, closePrecent, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount,
consumedInterest, resetCarryInterest);
if (preEodPosition.id != 0 && closePrecent == 1m)
@@ -1149,7 +1149,7 @@ namespace YLErp.Modules.SwapModule
}
// 计算截至本次平仓日的累计利息 amountAtEnd
CalcDailyCompoundInterest(replayEndDate, position, closePosiNotionalValue,
- interestAtEnd, annualDays, needPrice, floateRate, closePrecent,
+ interestAtEnd, annualDays, floateRate, closePrecent,
calcFirst, calcLast, ref amountAtEnd, ref tdAmountAtEnd, consumedInterest);
var interestAtPreviousEod = new swap_flow_event { InterestRate = rate };
decimal amountAtPreviousEod = 0m;
@@ -1158,7 +1158,7 @@ namespace YLErp.Modules.SwapModule
// 因此此处按闭区间包含上一日终当天,避免算头不算尾时重复加入该日利息。
// 计算截至上一日终累积的利息 amountAtPreviousEod
CalcDailyCompoundInterest(preEodPosition.ValueDate, position, closePosiNotionalValue,
- interestAtPreviousEod, annualDays, needPrice, floateRate, closePrecent,
+ interestAtPreviousEod, annualDays, floateRate, closePrecent,
calcFirst, true, ref amountAtPreviousEod, ref tdAmountAtPreviousEod, consumedInterest);
// 例如 0004:5/18 待实现 -118631.261797,加 5/19 新增约 -4648.912760,
// 得到最终应结 -123280.174557,按金额两位落为 Excel BN 的 -123280.17。
@@ -1170,7 +1170,7 @@ namespace YLErp.Modules.SwapModule
}
else
{
- CalcDailySimpleInterest(preEodPosition, endDate, position, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount);
+ CalcDailySimpleInterest(preEodPosition, endDate, position, posiNotionalValue, interest, annualDays, floateRate, closePrecent, orginPv, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount);
}
interest.InterestAmount = Math.Round(InterestAmount, InterestCalculationPrecision, MidpointRounding.AwayFromZero);
@@ -1231,7 +1231,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, bool calcFirst, bool calcLast,
+ int annualDays, decimal floateRate, decimal closePercent, bool calcFirst, bool calcLast,
ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m, decimal resetCarryInterest = 0m)
{
var startDate = position.PosiStartDate;
@@ -1270,7 +1270,7 @@ namespace YLErp.Modules.SwapModule
///
/// 计算单利 盘中(按重置天数分段,每段使用对应浮动利率)
///
- public void CalcDailySimpleInterest(eod_swap_position preEodPosition, DateTime endDate, swap_position position, decimal posiPrincipal, 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)
+ public void CalcDailySimpleInterest(eod_swap_position preEodPosition, DateTime endDate, swap_position position, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m)
{
var startDate = position.PosiStartDate;
int interestPeriod = position.interest_rest_days ?? 1;
@@ -1320,7 +1320,7 @@ namespace YLErp.Modules.SwapModule
/// 是否年化
/// 年化天数
///
- public void CalcDailyCompoundInterestByEod(eod_swap_position preEodPosition, DateTime endDate, DateTime tradeDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, ref decimal InterestAmount, ref decimal TdInterestAmount)
+ public void CalcDailyCompoundInterestByEod(eod_swap_position preEodPosition, DateTime endDate, DateTime tradeDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, decimal floateRate, decimal closePercent, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
int interestPeriod = position.interest_rest_days ?? 1;
var isResetDay = (endDate - tradeDate).Days % interestPeriod == 0;
@@ -1367,7 +1367,7 @@ namespace YLErp.Modules.SwapModule
///
/// 计算单利 收盘(按重置天数分段,每段使用对应浮动利率)
///
- public void CalcDailySimpleInterestByEod(eod_swap_position preEodPosition, DateTime endDate, DateTime tradeDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, ref decimal InterestAmount, ref decimal TdInterestAmount)
+ public void CalcDailySimpleInterestByEod(eod_swap_position preEodPosition, DateTime endDate, DateTime tradeDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, decimal floateRate, decimal closePercent, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
// 首次操作(preEod.id == 0):计息基数按存量本金初始化——保留旧行为(含对 preEod 的就地修正)。
if (preEodPosition.id == 0)