diff --git a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs
index c9c226d3..22c41d27 100644
--- a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs
+++ b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs
@@ -125,7 +125,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
swap_flow_event newEvt;
try
{
- newEvt = svc.CalcMarginInterest(td, valueDate, posClone, rate,
+ newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, posClone, rate,
pos.InterestPrincipalFix, pos.InterestPrincipalFix, 1.0m,
annualDays, calcFirst, calcLast, preEod,
(int)SwapEventTypeEnum.自动互换, add: false, settment: true, swap: false);
diff --git a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs
index 603c49c0..4347a29e 100644
--- a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs
+++ b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs
@@ -90,7 +90,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, 1m, ref oldI, ref oldTd);
// 新方法(独立 preEod,相同初始值)
- var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m,
+ var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m,
AnnualDays, calcFirst: true, calcLast: true,
CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: true, swap: false);
@@ -114,7 +114,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
valueDate, td.StartDate.Value, position, Principal, Principal,
new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, 1m, ref oldI, ref oldTd);
- var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m,
+ var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m,
AnnualDays, calcFirst: true, calcLast: true,
new eod_swap_position { id = 0 }, 0, add: false, settment: true, swap: false);
@@ -143,7 +143,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
calcFirst: true, calcLast: false, ref oldI, ref oldTd);
// 新方法:notional = posiPrincipal(无差分、无 orginPv)
- var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m,
+ var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m,
AnnualDays, calcFirst: true, calcLast: false,
CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: false, swap: false);
@@ -171,7 +171,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, closePct, orginPv,
calcFirst: true, calcLast: false, ref oldI, ref oldTd);
- var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate,
+ var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate,
Principal * closePct, Principal, closePct,
AnnualDays, calcFirst: true, calcLast: false,
CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: false, swap: false);
@@ -191,7 +191,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin
var valueDate = StartDate.AddDays(5);
var svc = new StubSvc();
- var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m,
+ var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m,
AnnualDays, calcFirst: true, calcLast: false,
CreatePreEod(StartDate.AddDays(4), 820m), 0, add: false, settment: false, swap: true);
diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
index fec4dfaf..2a2072ae 100644
--- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
@@ -697,7 +697,13 @@ namespace YLErp.Modules.SwapModule
decimal floatRate = GetFloatRate(position, preEodPosition, td.StartDate.Value, endDate, interestPeriod, swap, positionClone);
// 根据场景计算利息
- if (settment)
+ if (MarginModes.Contains(position.InterestMode))
+ {
+ // 保证金腿(5/6):专属计息,notional 直接取保证金余额,无融资腿差分公式与 orginPv 维度 hack
+ interests.Add(CalcMarginInterest(td, valueDate, endDate, positionClone, rate, closePrincipal, posiPrincipal,
+ newClosePercent, annualDays, calcFirst, calcLast||newCalcLast, preEodPosition, eventType, add, settment, swap));
+ }
+ else if (settment)
{
// 收盘归档场景,使用 CalcEodInterest
interests.Add(CalcEodInterest(td, valueDate, positionClone, rate, floatRate, closePrincipal, posiPrincipal, annualDays, calcFirst, calcLast, preEodPosition, eventType, add));
@@ -921,17 +927,18 @@ namespace YLErp.Modules.SwapModule
///
/// 保证金是纯固定利率单利:浮动利率(FR007)/分段利率/复利对其均为死分支(前端无入口、
/// 确认书不含、FundingLegRate.Build 对空 FloatRateUnderlyingCode 恒返回 Fixed)。故本方法直接用
- /// SimpleInterestAccrual 纯函数计息,notional 取保证金余额本身:
- /// EOD = 昨日终本金 preEod.TdInterestPrincipal(与旧 CalcDailySimpleInterestByEod 同源)
- /// 盘中 = 今日本金 posiPrincipal(InterestPrincipalFix)
- /// 消除融资腿差分公式 accrualBasis = TdInterestPrincipal + posiPrincipal - orginPv 与 orginPv 维度
- /// hack——对保证金 accrualBasis 恒等于 posiPrincipal,差分冗余。保留累计语义(priorAccrued + 增量),
- /// 满足下游 SwapEodPositionService 字段契约(InterestAmount=缩放累计、TdInterestAmount=单日参考等)。
+ /// SimpleInterestAccrual 纯函数计息,本金取保证金余额:
+ /// EOD = 昨日终本金 preEod.TdInterestPrincipal(与旧 CalcDailySimpleInterestByEod 同源,无差分)
+ /// 盘中 = accrualBasis(preEod.TdInterestPrincipal + posiPrincipal - orginPv)
+ /// 盘中保留差分是必要的:posiPrincipal 是否经 ResolveInterestLegPositions 对齐到实时剩余是路径相关的
+ /// (生产对齐 / 诊断测试用原始腿),单一本金变量无法覆盖两种状态,差分经 orginPv 自适应。orginPv 在
+ /// 本方法内部按保证金维度计算(PreviousBalance),消除原 InitSwapDealInterest 的外部维度 hack
+ /// (融资腿 orginPv=浮动端名义本金)。保留累计语义(priorAccrued + 增量),满足下游字段契约。
///
/// true=收盘归档(EOD),false=盘中平仓/互换。
/// 互换事件(仅盘中生效,true 时利息归零,同 InitSwapDealInterest)。
public swap_flow_event CalcMarginInterest(
- trade td, DateTime valueDate, swap_position position, decimal rate,
+ trade td, DateTime valueDate, DateTime endDate, swap_position position, decimal rate,
decimal closePrincipal, decimal posiPrincipal, decimal closePercent,
int annualDays, bool calcFirst, bool calcLast,
eod_swap_position preEod, int eventType, bool add, bool settment, bool swap)
@@ -967,7 +974,7 @@ namespace YLErp.Modules.SwapModule
FloatRate = 0m,
DataState = (int)SwapFlowDateStateEnum.完成,
ClientId = td.ClientId,
- UnwindDate = valueDate
+ UnwindDate = settment ? valueDate : endDate
};
// 互换事件:利息归零(同 InitSwapDealInterest)
@@ -1000,22 +1007,27 @@ namespace YLErp.Modules.SwapModule
}
else
{
- // 盘中:notional = 今日本金 posiPrincipal(无差分、无 orginPv)
+ // 盘中:accrualBasis 自适应"实时剩余本金"——posiPrincipal 已对齐(ResolveInterestLegPositions)
+ // 时 = posiPrincipal;未对齐的原始腿经 orginPv(=PreviousBalance 昨日终) 修正回昨日终剩余。
+ // 单一本金变量无法覆盖两种 position 状态,故保留差分(与 EOD 直接用 preEod.TdInterestPrincipal 不同)。
+ // orginPv 在此内部按保证金维度计算,消除原 InitSwapDealInterest 的外部维度 hack。
+ var orginPv = MarginCalc.PreviousBalance(preEod, posiPrincipal);
+ var accrualBasis = preEod.TdInterestPrincipal + posiPrincipal - orginPv;
var segmentRates = new List<(DateTime, decimal)> { (position.PosiStartDate, rate) };
var r = SimpleInterestAccrual.AccruePeriod(
priorAccrued: preEod.InterestProfitSum * closePercent,
- notional: posiPrincipal,
+ notional: accrualBasis,
unwindFraction: closePercent,
segmentRates: segmentRates,
startDate: position.PosiStartDate,
- endDate: valueDate,
+ endDate: endDate,
priorValueDate: preEod.ValueDate,
boundary: AccrualBoundary.Of(calcFirst, calcLast),
annualDays: annualDays,
isAnnualized: position.IsAnnualized);
interestAmount = r.Accrued;
tdInterestAmount = r.AccruedToday;
- interest.InterestPrincipal = posiPrincipal * closePercent; // 同 CalcDailySimpleInterest:1304
+ interest.InterestPrincipal = accrualBasis * closePercent; // 同 CalcDailySimpleInterest:1304
}
}
@@ -1135,11 +1147,8 @@ namespace YLErp.Modules.SwapModule
interest.ClientId = td.ClientId;
interest.UnwindDate = endDate;
- // 保证金腿的 orginPv 对齐到保证金本金维度,避免差分公式维度不匹配算出巨负值
- if (MarginModes.Contains(position.InterestMode))
- {
- orginPv = MarginCalc.PreviousBalance(preEodPosition, position.InterestPrincipalFix);
- }
+ // 保证金腿已走 CalcMarginInterest(不经过本方法),orginPv 维度重映射不再需要;
+ // orginPv 此处仅对融资腿生效(差分公式 accrualBasis = TdInterestPrincipal + posiPrincipal - orginPv)。
if (swap)
{