diff --git a/UnitTestProject/Modules/SwapModule/InterestEodTailSnapshotTest.cs b/UnitTestProject/Modules/SwapModule/InterestEodTailSnapshotTest.cs
new file mode 100644
index 00000000..01cec7cb
--- /dev/null
+++ b/UnitTestProject/Modules/SwapModule/InterestEodTailSnapshotTest.cs
@@ -0,0 +1,227 @@
+using Newtonsoft.Json;
+
+namespace YLErp.Modules.SwapModule
+{
+ ///
+ /// L1(类内去重)前置安全网:DealInterests 四分支中无 golden 语料的三格
+ /// (AutoSettle / AutoSettleWithClose / CloseOnly)尾部滚存字段特征化快照。
+ ///
+ /// - ManualSwap / RollForward 两格已由 DealInterestsGoldenReplayTest 语料钉住
+ /// (字段集见 GoldenReplayFramework.EodPositionToJson)。
+ /// - 本测试钉"现状行为":L1 抽共享助手(腿字段拷贝段 + 滚存收尾段)前后,
+ /// 以下字段必须逐字段不变。变化=去重改了口径。
+ /// - 同时断言接 seam 指纹(哪个计息接缝 + eventType)与 autoInterests 收集行为,
+ /// 兼作 L2(按腿拆类)的路由验收。
+ /// - 计息金额由受控 CalcResult 注入(不连库、不依赖真实计息引擎)。
+ ///
+ [TestClass]
+ public class InterestEodTailSnapshotTest
+ {
+ private const decimal Principal = 10000m;
+ private const decimal Rate = 0.03m;
+ private static readonly DateTime StartDate = new(2026, 4, 27);
+ private static readonly DateTime SettleDate = StartDate.AddDays(10); // 第10天收盘
+ private const decimal Accrued10d = 8.22m; // 受控:10天理论应结
+ private const decimal DailyNew = 0.82m; // 受控:当日新增
+ private const decimal ManualSettled = 3.5m; // 受控:盘中平仓已结
+ private const decimal Remaining = 7000m; // 平仓后剩余本金
+ private const decimal ClosedNotional = 3000m; // 本次平掉本金
+
+ private sealed class TailStubService : TestableSwapEodPositionService
+ {
+ public TailStubService() : base(nameof(InterestEodTailSnapshotTest)) { }
+
+ /// 受控计息结果:两个计息 seam 均返回它
+ public List CalcResult { get; set; } = new();
+
+ public string LastCalcSeam { get; private set; } = "";
+ public List CalcEventTypes { get; } = new();
+
+ protected override List CalcSwapInterests(
+ trade td, trade_extend tradeExtend,
+ DateTime valueDate, DateTime unwindDate,
+ List eodPositions, List positions,
+ decimal posiNotionalValue,
+ decimal closePosiNotionalValue, decimal closePrecent,
+ int eventType, bool tdClose,
+ decimal orginPv,
+ bool add = false, bool settment = true, bool newCalcLast = false,
+ List closeList = null)
+ {
+ LastCalcSeam = nameof(CalcSwapInterests);
+ CalcEventTypes.Add(eventType);
+ return CalcResult;
+ }
+
+ protected override List CalcEodPostCloseSettleInterests(InterestCalcRequest req)
+ {
+ LastCalcSeam = nameof(CalcEodPostCloseSettleInterests);
+ CalcEventTypes.Add(req.EventType);
+ return CalcResult;
+ }
+
+ public List ExecuteDealInterests(
+ List interestList, List eodPositions,
+ DateTime settleDate, trade td, List flowEvents,
+ decimal posiTotalNotional, decimal closeNational, decimal grossPrice, decimal orginPv)
+ {
+ var autoInterests = new List();
+ DealInterests(interestList, eodPositions, new List(),
+ settleDate, td, flowEvents, autoInterests, null,
+ posiTotalNotional, closeNational, grossPrice, orginPv);
+ return autoInterests;
+ }
+ }
+
+ private static trade CreateTrade() => new()
+ {
+ id = 1, TradeNumber = "TAIL-SNAP-001", ClientId = 999998,
+ TradeType = "收益互换", TradeDate = StartDate, StartDate = StartDate,
+ ExerciseDate = new DateTime(2027, 4, 27), TradeStatus = "确认成交",
+ ValidState = "Valid", StructureType = "单标的",
+ QuoteCurrency = "CNY", SettlementCurrency = "CNY",
+ trade_extend = new trade_extend
+ {
+ TradeId = 1,
+ ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson { AnnualDays = 365, InterestCalcMode = "10", SettlementRules = 0 })
+ }
+ };
+
+ /// true=当日观察日(Settlement=1);false=观察日在别日
+ private static swap_position CreateInterestPosition(bool observationDay)
+ {
+ var interval = observationDay
+ ? new IntervalModel { Date = SettleDate, Rate = Rate, Settlement = 1 }
+ : new IntervalModel { Date = StartDate, Rate = Rate, Settlement = 1 };
+ return new swap_position
+ {
+ id = 1001, SwapTradeId = 1, InterestDirection = (int)SwapDirectionEnum.收取,
+ InterestMode = (int)InterestModeEnum.标的期初全价, InterestRateDefault = Rate,
+ InterestPrincipalFix = Principal, PosiStartDate = StartDate,
+ PosiMatuirityDate = new DateTime(2027, 4, 27), IsInitial = true,
+ InterestType = (int)InterestTypeEnum.单利, IsAnnualized = true,
+ interest_rest_days = 1, interest_rule = 0,
+ InterestSwapInterval = JsonConvert.SerializeObject(new List { interval })
+ };
+ }
+
+ private static eod_swap_position CreatePreEod(decimal accumulated) => new()
+ {
+ id = 100, PositionId = 1001, ValueDate = SettleDate.AddDays(-1),
+ InterestDirection = (int)SwapDirectionEnum.收取, InterestMode = (int)InterestModeEnum.标的期初全价,
+ InterestIncomeSum = accumulated, InterestProfitSum = accumulated,
+ InterestRateDefault = Rate, TdInterestPrincipal = Principal,
+ InterestType = (int)InterestTypeEnum.单利, IsAnnualized = true, interest_rest_days = 1
+ };
+
+ private static swap_flow_event CreateCalcResult() => new()
+ {
+ EventType = (int)SwapEventTypeEnum.自动互换, PositionId = 1001,
+ InterestAmount = Accrued10d, TdInterestAmount = DailyNew,
+ InterestClosePnL = Accrued10d,
+ InterestPrincipal = Principal, InterestRate = Rate,
+ InterestDirection = (int)SwapDirectionEnum.收取
+ };
+
+ private static swap_flow_event CreateCloseEvent() => new()
+ {
+ EventType = (int)SwapEventTypeEnum.平仓, PositionId = 1001,
+ InterestAmount = ManualSettled, InterestClosePnL = ManualSettled,
+ InterestRate = Rate, InterestFee = 0m,
+ InterestPrincipal = ClosedNotional,
+ InterestDirection = (int)SwapDirectionEnum.收取,
+ DataState = (int)SwapFlowDateStateEnum.完成
+ };
+
+ /// AutoSettle 格:观察日无平仓 → SaveAutoEodInterestPosition,返回值收集进 autoInterests
+ [TestMethod]
+ public void AutoSettle_观察日无平仓_尾部快照()
+ {
+ var service = new TailStubService { CalcResult = new List { CreateCalcResult() } };
+ var autoInterests = service.ExecuteDealInterests(
+ new List { CreateInterestPosition(observationDay: true) },
+ new List { CreatePreEod(Accrued10d) },
+ SettleDate, CreateTrade(), new List(),
+ Principal, 0m, 100m, Principal);
+
+ Assert.AreEqual("CalcSwapInterests", service.LastCalcSeam, "观察日无平仓应走 CalcSwapInterests seam");
+ Assert.AreEqual((int)SwapEventTypeEnum.自动互换, service.CalcEventTypes.Single(), "eventType 应为自动互换");
+ Assert.AreEqual(1, autoInterests.Count, "观察日分支应收集返回值进 autoInterests(→资金记录)");
+
+ var p = service.PersistedPositions.Single();
+ // 钉值于 2026-08-17 现状行为(受控输入:应结8.22/新增0.82/本金10000)
+ Assert.AreEqual(8.22m, p.TdCloseInterest);
+ Assert.AreEqual(0.82m, p.TdInterestIncome);
+ Assert.AreEqual(10000m, p.TdInterestPrincipal);
+ Assert.AreEqual(0.03m, p.TdInterestRate);
+ Assert.AreEqual(0.00m, p.InterestIncomeSum, "应结=结算,待实现清零");
+ Assert.AreEqual(0m, p.InterestFeeSum);
+ Assert.AreEqual(0m, p.InterestProfitSum);
+ Assert.AreEqual(8.22m, p.RealizedInterest);
+ Assert.AreEqual(0m, p.RealizedInterestFee);
+ Assert.AreEqual(0m, p.SwapPositionValue);
+ Assert.AreEqual(1.0m, p.TdCurrency);
+ }
+
+ /// AutoSettleWithClose 格(TEST-MATRIX §6 最弱格):观察日+平仓 → SaveAutoEodWithCloseInterestPosition(autoSwap:true),补结差额=恒1全额−盘中已结
+ [TestMethod]
+ public void AutoSettleWithClose_观察日加平仓_尾部快照()
+ {
+ var service = new TailStubService { CalcResult = new List { CreateCalcResult() } };
+ var autoInterests = service.ExecuteDealInterests(
+ new List { CreateInterestPosition(observationDay: true) },
+ new List { CreatePreEod(Accrued10d) },
+ SettleDate, CreateTrade(), new List { CreateCloseEvent() },
+ Remaining, ClosedNotional, 100m, Remaining);
+
+ Assert.AreEqual("CalcEodPostCloseSettleInterests", service.LastCalcSeam, "观察日+平仓应走 EodPostCloseSettle seam");
+ Assert.AreEqual((int)SwapEventTypeEnum.自动互换, service.CalcEventTypes.Single(), "autoSwap=true → eventType=自动互换");
+ Assert.AreEqual(1, autoInterests.Count, "观察日分支应收集返回值进 autoInterests");
+ Assert.AreEqual(Accrued10d - ManualSettled, autoInterests[0].InterestAmount, "补结差额=恒1全额8.22−盘中已结3.50");
+
+ var p = service.PersistedPositions.Single();
+ // 钉值于 2026-08-17 现状行为(受控输入:恒1全额8.22/盘中已结3.5/剩余7000/平掉3000)
+ Assert.AreEqual(8.22m, p.TdCloseInterest, "TdCloseInterest=盘中已结3.50+补结4.72");
+ Assert.AreEqual(0.5753424657534246575342465753m, p.TdInterestIncome, "autoSwap 重算展示应计=剩余7000×3%/365");
+ Assert.AreEqual(7000m, p.TdInterestPrincipal, "单利部分平仓:跨日本金=剩余");
+ Assert.AreEqual(0.03m, p.TdInterestRate);
+ Assert.AreEqual(0.00m, p.InterestIncomeSum, "恒1口径:理论应结8.22−结算8.22=0");
+ Assert.AreEqual(0m, p.InterestFeeSum);
+ Assert.AreEqual(0m, p.InterestProfitSum);
+ Assert.AreEqual(8.22m, p.RealizedInterest);
+ Assert.AreEqual(0m, p.RealizedInterestFee);
+ Assert.AreEqual(0m, p.SwapPositionValue);
+ Assert.AreEqual(1.0m, p.TdCurrency);
+ }
+
+ /// CloseOnly 格:非观察日平仓 → SaveAutoEodWithCloseInterestPosition(autoSwap:false),返回值不收集,TdCloseInterest=盘中已结
+ [TestMethod]
+ public void CloseOnly_非观察日平仓_尾部快照()
+ {
+ var service = new TailStubService { CalcResult = new List { CreateCalcResult() } };
+ var autoInterests = service.ExecuteDealInterests(
+ new List { CreateInterestPosition(observationDay: false) },
+ new List { CreatePreEod(Accrued10d) },
+ SettleDate, CreateTrade(), new List { CreateCloseEvent() },
+ Remaining, ClosedNotional, 100m, Remaining);
+
+ Assert.AreEqual("CalcEodPostCloseSettleInterests", service.LastCalcSeam, "纯平仓应走 EodPostCloseSettle seam");
+ Assert.AreEqual((int)SwapEventTypeEnum.平仓, service.CalcEventTypes.Single(), "autoSwap=false → eventType=平仓");
+ Assert.AreEqual(0, autoInterests.Count, "纯平仓分支不收集返回值(结算已在盘中流水定格)");
+
+ var p = service.PersistedPositions.Single();
+ // 钉值于 2026-08-17 现状行为(受控输入:恒1重算8.22/盘中已结3.5/剩余7000/平掉3000)
+ Assert.AreEqual(ManualSettled, p.TdCloseInterest, "TdCloseInterest 应仅为盘中已结3.50,不叠加恒1重算值");
+ Assert.AreEqual(0.5753424657534246575342465753m, p.TdInterestIncome, "不算尾路径:剩余7000×3%/365");
+ Assert.AreEqual(7000m, p.TdInterestPrincipal, "单利部分平仓:跨日本金=剩余");
+ Assert.AreEqual(0.03m, p.TdInterestRate, "非观察日:利率取平仓流水 InterestRate");
+ Assert.AreEqual(5.295342465753m, p.InterestIncomeSum, "尾差递推:上日8.22+新增0.575342−已结3.50");
+ Assert.AreEqual(0m, p.InterestFeeSum);
+ Assert.AreEqual(5.295342465753m, p.InterestProfitSum);
+ Assert.AreEqual(3.5m, p.RealizedInterest);
+ Assert.AreEqual(0m, p.RealizedInterestFee);
+ Assert.AreEqual(5.295342465753m, p.SwapPositionValue);
+ Assert.AreEqual(1.0m, p.TdCurrency);
+ }
+ }
+}
diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
index a7992ee2..e29a7b81 100644
--- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
@@ -980,6 +980,46 @@ namespace YLErp.Modules.SwapModule
trans?.Dispose();
}
}
+ ///
+ /// 利息腿字段拷贝(SaveEodInterestPosition / SaveAutoEodInterestPosition / SaveAutoEodWithCloseInterestPosition 共用)。
+ /// FloatRate 来源随场景不同(手工互换=当日流水;自动互换/平仓=计息结果),由调用方算好传入,勿在本方法内统一。
+ /// 场景差异字段(PosiStatus / InterestFeePending / TdInterestPrincipal / TdInterestRate)留在各调用点。
+ ///
+ private static void CopyInterestLegFields(eod_swap_position newEodPayPosition, swap_position position, decimal floatRate)
+ {
+ newEodPayPosition.InterestDirection = position.InterestDirection;
+ newEodPayPosition.InterestMode = position.InterestMode;
+ newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
+ newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
+ newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
+ newEodPayPosition.IsAnnualized = position.IsAnnualized;
+ newEodPayPosition.HappenDate = position.HappenDate;
+ newEodPayPosition.Currency = position.Currency;
+ newEodPayPosition.InterestType = position.InterestType;
+ newEodPayPosition.interest_rest_days = position.interest_rest_days;
+ newEodPayPosition.interest_rule = position.interest_rule;
+ newEodPayPosition.FloatRate = floatRate;
+ newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
+ }
+
+ ///
+ /// 利息腿日终滚存收尾(四个 Save* 共用):RollRealized 滚累计已实现 → SetFixedLegRealizedPnl → 汇率 → TdCurrency。
+ /// RealizedInterest 只增不回滚:上日累计已实现 + 当日结息按方向后的金额。
+ /// interestDirection 是 RateType 的方向来源——三个方法取 position.InterestDirection,
+ /// SaveEodInterestPositionCopy 取 eodPayPosition.InterestDirection(现状差异,勿统一)。
+ /// PersistEodSwapPosition 与各自日志留在调用点(持久化边界 + 日志顺序各不相同)。
+ ///
+ private void FinalizeInterestEodRoll(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, int ratio, trade td, DateTime valueDate, int interestDirection)
+ {
+ var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
+ newEodPayPosition.RealizedInterest = rolled.Interest;
+ newEodPayPosition.RealizedInterestFee = rolled.Fee;
+ SetFixedLegRealizedPnl(newEodPayPosition);
+ var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
+ DirectionRatio.RateType(interestDirection));
+ newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
+ }
+
///
/// 产生互换用
///
@@ -1023,20 +1063,8 @@ namespace YLErp.Modules.SwapModule
UpdateDbOption(newEodPayPosition);
newEodPayPosition.PosiStatus = 0;
newEodPayPosition.Invalid = false;
- //持仓内容-利息腿
- newEodPayPosition.InterestDirection = position.InterestDirection;
- newEodPayPosition.InterestMode = position.InterestMode;
- newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
- newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
- newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
- newEodPayPosition.IsAnnualized = position.IsAnnualized;
- newEodPayPosition.HappenDate = position.HappenDate;
- newEodPayPosition.Currency = position.Currency;
- newEodPayPosition.InterestType = position.InterestType;
- newEodPayPosition.interest_rest_days = position.interest_rest_days;
- newEodPayPosition.interest_rule = position.interest_rule;
- newEodPayPosition.FloatRate = flowEvents.FirstOrDefault()?.FloatRate ?? 0;
- newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
+ //持仓内容-利息腿(FloatRate 取当日互换/平仓流水)
+ CopyInterestLegFields(newEodPayPosition, position, flowEvents.FirstOrDefault()?.FloatRate ?? 0);
newEodPayPosition.InterestFeePending = 0;
//利息端估值用信息
newEodPayPosition.TdInterestPrincipal = flowEvents.FirstOrDefault()?.InterestPrincipal ?? 0;
@@ -1074,14 +1102,8 @@ namespace YLErp.Modules.SwapModule
//持仓价值
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum, (int)ratio);
- //累计已实现
- var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
- newEodPayPosition.RealizedInterest = rolled.Interest;
- newEodPayPosition.RealizedInterestFee = rolled.Fee;
- SetFixedLegRealizedPnl(newEodPayPosition);
- var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
- DirectionRatio.RateType(position.InterestDirection));
- newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
+ //累计已实现(滚存收尾见 FinalizeInterestEodRoll)
+ FinalizeInterestEodRoll(eodPayPosition, newEodPayPosition, ratio, td, valueDate, position.InterestDirection);
PersistEodSwapPosition(newEodPayPosition);
}
///
@@ -1179,20 +1201,8 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.PositionId = position.id;
UpdateDbOption(newEodPayPosition);
newEodPayPosition.Invalid = false;
- //持仓内容-利息腿
- newEodPayPosition.InterestDirection = position.InterestDirection;
- newEodPayPosition.InterestMode = position.InterestMode;
- newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
- newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
- newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
- newEodPayPosition.IsAnnualized = position.IsAnnualized;
- newEodPayPosition.HappenDate = position.HappenDate;
- newEodPayPosition.Currency = position.Currency;
- newEodPayPosition.InterestType = position.InterestType;
- newEodPayPosition.interest_rest_days = position.interest_rest_days;
- newEodPayPosition.interest_rule = position.interest_rule;
- newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0;
- newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
+ //持仓内容-利息腿(FloatRate 取计息结果)
+ CopyInterestLegFields(newEodPayPosition, position, interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0);
newEodPayPosition.InterestFeePending = 0;
//利息端估值用信息
newEodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode) ? eodPayPosition.InterestPrincipalFix : posiNotionalValue;
@@ -1216,14 +1226,8 @@ namespace YLErp.Modules.SwapModule
//持仓价值
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum, (int)ratio);
- //累计已实现
- var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
- newEodPayPosition.RealizedInterest = rolled.Interest;
- newEodPayPosition.RealizedInterestFee = rolled.Fee;
- SetFixedLegRealizedPnl(newEodPayPosition);
- var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
- DirectionRatio.RateType(position.InterestDirection));
- newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
+ //累计已实现(滚存收尾见 FinalizeInterestEodRoll)
+ FinalizeInterestEodRoll(eodPayPosition, newEodPayPosition, ratio, td, valueDate, position.InterestDirection);
PersistEodSwapPosition(newEodPayPosition);
Log.Info($"the last newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
return interests;
@@ -1354,22 +1358,9 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.PositionId = position.id;
UpdateDbOption(newEodPayPosition);
newEodPayPosition.Invalid = false;
- //持仓内容-利息腿
- newEodPayPosition.InterestDirection = position.InterestDirection;
- newEodPayPosition.InterestMode = position.InterestMode;
- // ResolveInterestLegPositions 已提供平仓后的实时剩余本金,日终不再重复扣减。
- newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
- // newEodPayPosition.InterestPrincipalFix *= (1 - closePercent);
- newEodPayPosition.InterestRateDefault = position.InterestRateDefault;
- newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval;
- newEodPayPosition.IsAnnualized = position.IsAnnualized;
- newEodPayPosition.HappenDate = position.HappenDate;
- newEodPayPosition.Currency = position.Currency;
- newEodPayPosition.InterestType = position.InterestType;
- newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0;
- newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
- newEodPayPosition.interest_rest_days = position.interest_rest_days;
- newEodPayPosition.interest_rule = position.interest_rule;
+ //持仓内容-利息腿(FloatRate 取计息结果)。InterestPrincipalFix 保持腿现值:
+ // ResolveInterestLegPositions 已提供平仓后的实时剩余本金,日终不再重复扣减(勿恢复 *(1-closePercent))。
+ CopyInterestLegFields(newEodPayPosition, position, interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0);
//利息端估值用信息
// TdInterestPrincipal 是“下一日继续计息的收盘后本金”,不是原始合同规模,也不是本次平仓本金。
// 模式9单利直接取剩余名义本金;复利还要保留重置时已经并入本金的待实现利息。
@@ -1491,16 +1482,8 @@ namespace YLErp.Modules.SwapModule
$",TdCloseInterest is {newEodPayPosition.TdCloseInterest}");
Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" +
$",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}");
- //累计已实现
- // RealizedInterest 只增不回滚:上日累计已实现 + 当日结息按方向后的金额。
- // 收取腿的 -37119.14 会把累计已实现更新为 -37119.14;后续普通 EOD 保持该值。
- var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
- newEodPayPosition.RealizedInterest = rolled.Interest;
- newEodPayPosition.RealizedInterestFee = rolled.Fee;
- SetFixedLegRealizedPnl(newEodPayPosition);
- var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
- DirectionRatio.RateType(position.InterestDirection));
- newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
+ //累计已实现(滚存语义见 FinalizeInterestEodRoll:只增不回滚)
+ FinalizeInterestEodRoll(eodPayPosition, newEodPayPosition, ratio, td, valueDate, position.InterestDirection);
Log.Info($"即将插入数据库的 newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
PersistEodSwapPosition(newEodPayPosition);
return interests;
@@ -1614,14 +1597,8 @@ namespace YLErp.Modules.SwapModule
//持仓价值
newEodPayPosition.SwapPositionValue = PositionValueCalc.Calc(newEodPayPosition.InterestProfitSum, newEodPayPosition.PosiProfitSum, (int)ratio);
- //累计已实现
- var rolled = InterestIncomeCalc.RollRealized(eodPayPosition.RealizedInterest, eodPayPosition.RealizedInterestFee, newEodPayPosition.TdCloseInterest, newEodPayPosition.TdCloseInterestFee, ratio);
- newEodPayPosition.RealizedInterest = rolled.Interest;
- newEodPayPosition.RealizedInterestFee = rolled.Fee;
- SetFixedLegRealizedPnl(newEodPayPosition);
- var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true,
- DirectionRatio.RateType(eodPayPosition.InterestDirection));
- newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
+ //累计已实现(滚存收尾见 FinalizeInterestEodRoll;方向源=eodPayPosition,与其他三方法不同,勿统一)
+ FinalizeInterestEodRoll(eodPayPosition, newEodPayPosition, ratio, td, valueDate, eodPayPosition.InterestDirection);
Log.Info($"the last newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
PersistEodSwapPosition(newEodPayPosition);