using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using YLErp; using YLErp.DBModels; using YLErp.DBModels.Enums; using YLErp.Modules.SwapModule; using YLErp.Modules.SwapModule.Accrual; using YLErp.Modules.SwapModule.Margin; using YLErp.Derivatives.Interest; namespace UnitTestProject.Modules.SwapModule.Margin { /// /// 影子对账:保证金腿新方法 CalcMarginInterest(无 orginPv/差分)vs /// 旧通用管线 CalcDailySimpleInterestByEod/CalcDailySimpleInterest(带差分 + orginPv hack)。 /// /// 保证金是纯固定利率单利(FloatRateUnderlyingCode 恒空、InterestType 恒单利、SwapIntervalList 单段), /// 旧管线差分公式 accrualBasis = TdInterestPrincipal + posiPrincipal - orginPv 对保证金恒等于 posiPrincipal /// (因 orginPv 经 PreviousBalance 对齐到昨日终保证金余额),故新方法直接用 posiPrincipal/昨日终本金作 /// notional 应与旧管线严格数值一致。本测试即在多种场景下证明这一等价,为提交2 切换生产路径提供安全网。 /// [TestClass] public class MarginInterestShadowTest { private const decimal Principal = 2_000_000m; // 保证金本金(InterestPrincipalFix) private const decimal Rate = 0.03m; // 3% 年化固定利率 private const int AnnualDays = 365; private static readonly DateTime StartDate = new(2026, 7, 1); private static readonly DateTime ExerciseDate = new(2027, 6, 30); private sealed class StubSvc : SwapDealService { public StubSvc() : base(new OptUserInfo(0, nameof(MarginInterestShadowTest), OptUserFrom.UnitTest)) { } } private static trade CreateTrade() => new trade { id = 1, TradeNumber = "UT-MARGIN-SHADOW", ClientId = 999998, TradeType = "收益互换", TradeDate = StartDate, StartDate = StartDate, ExerciseDate = ExerciseDate, TradeStatus = "确认成交", ValidState = "Valid", trade_extend = new trade_extend { TradeId = 1, ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson { AnnualDays = AnnualDays, InterestCalcMode = "10", SettlementRules = 0 }) } }; /// 保证金腿(初始预付金 mode 5):固定利率、单利、年化、无浮动标的。 private static swap_position CreateMarginPosition() => new swap_position { id = 2001, SwapTradeId = 1, PosiDirection = 0, InterestDirection = (int)SwapDirectionEnum.收取, InterestMode = (int)InterestModeEnum.初始预付金, InterestRateDefault = Rate, InterestPrincipalFix = Principal, PosiStartDate = StartDate, PosiMatuirityDate = ExerciseDate, IsInitial = true, Invalid = false, InterestType = (int)InterestTypeEnum.单利, IsAnnualized = true, interest_rest_days = 1, interest_rule = 0, FloatRateUnderlyingCode = null, InterestSwapInterval = "[]" }; /// 构造昨日终 eod_swap_position(已含累计利息 InterestProfitSum 与昨日终本金)。 private static eod_swap_position CreatePreEod(DateTime valueDate, decimal profitSum) => new eod_swap_position { id = 1, SwapTradeId = 1, PositionId = 2001, ValueDate = valueDate, TdInterestPrincipal = Principal, InterestPrincipalFix = Principal, InterestProfitSum = profitSum, PosiNotionalValue = Principal, FloatRate = 0m }; // ──────────────────────────── EOD 路径 ──────────────────────────── /// EOD 续接单日:有历史归档,notional=昨日终本金。 [TestMethod] public void 影子_EOD续接单日_新旧一致() { var td = CreateTrade(); var position = CreateMarginPosition(); var valueDate = StartDate.AddDays(5); const decimal profitSum = 820m; // 旧方法 decimal oldI = 0, oldTd = 0; var svc = new StubSvc(); svc.CalcDailySimpleInterestByEod(CreatePreEod(StartDate.AddDays(4), profitSum), valueDate, td.StartDate.Value, position, Principal, Principal, new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, 1m, ref oldI, ref oldTd); // 新方法(独立 preEod,相同初始值) 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); Console.WriteLine($"旧: I={oldI} Td={oldTd}"); Console.WriteLine($"新: I={newEvt.InterestAmount} Td={newEvt.TdInterestAmount} ClosePnL={newEvt.InterestClosePnL}"); Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致"); Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致"); } /// EOD 首日(preEod.id==0):首日初始化 notional=posiPrincipal。 [TestMethod] public void 影子_EOD首日_新旧一致() { var td = CreateTrade(); var position = CreateMarginPosition(); var valueDate = StartDate; decimal oldI = 0, oldTd = 0; var svc = new StubSvc(); svc.CalcDailySimpleInterestByEod(new eod_swap_position { id = 0 }, 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, valueDate, position, Rate, Principal, Principal, 1m, AnnualDays, calcFirst: true, calcLast: true, new eod_swap_position { id = 0 }, 0, add: false, settment: true, swap: false); Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致"); Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致"); } // ──────────────────────────── 盘中路径 ──────────────────────────── /// 盘中全平(closePercent=1):新方法 notional=posiPrincipal,旧方法差分 accrualBasis 恒=posiPrincipal。 [TestMethod] public void 影子_盘中全平_新旧一致() { var td = CreateTrade(); var position = CreateMarginPosition(); var valueDate = StartDate.AddDays(5); const decimal profitSum = 820m; // 旧方法:orginPv 经 PreviousBalance 对齐到昨日终保证金余额 → accrualBasis 恒= Principal decimal oldI = 0, oldTd = 0; var svc = new StubSvc(); var preEodOld = CreatePreEod(StartDate.AddDays(4), profitSum); decimal orginPv = MarginCalc.PreviousBalance(preEodOld, Principal); svc.CalcDailySimpleInterest(preEodOld, valueDate, position, Principal, new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, 1m, orginPv, calcFirst: true, calcLast: false, ref oldI, ref oldTd); // 新方法:notional = posiPrincipal(无差分、无 orginPv) 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); Console.WriteLine($"旧: I={oldI} Td={oldTd}"); Console.WriteLine($"新: I={newEvt.InterestAmount} Td={newEvt.TdInterestAmount}"); Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致"); Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致"); } /// 盘中部分平仓(closePercent=0.5):缩放累计,新旧线性等价。 [TestMethod] public void 影子_盘中部分平仓_新旧一致() { var td = CreateTrade(); var position = CreateMarginPosition(); var valueDate = StartDate.AddDays(5); const decimal profitSum = 820m; const decimal closePct = 0.5m; decimal oldI = 0, oldTd = 0; var svc = new StubSvc(); var preEodOld = CreatePreEod(StartDate.AddDays(4), profitSum); decimal orginPv = MarginCalc.PreviousBalance(preEodOld, Principal); svc.CalcDailySimpleInterest(preEodOld, valueDate, position, Principal, new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, closePct, orginPv, calcFirst: true, calcLast: false, ref oldI, ref oldTd); 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); Console.WriteLine($"旧: I={oldI} Td={oldTd}"); Console.WriteLine($"新: I={newEvt.InterestAmount} Td={newEvt.TdInterestAmount}"); Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致"); Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致"); } /// 互换事件(swap=true,盘中):利息应归零。 [TestMethod] public void 影子_盘中互换_利息归零() { var td = CreateTrade(); var position = CreateMarginPosition(); var valueDate = StartDate.AddDays(5); var svc = new StubSvc(); 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); Assert.AreEqual(0m, newEvt.InterestAmount, "互换利息归零"); Assert.AreEqual(0m, newEvt.TdInterestAmount, "互换 TdInterestAmount 归零"); Assert.AreEqual(0m, newEvt.InterestClosePnL, "互换 InterestClosePnL 归零"); } } }