Files
zszq-trs/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs
T
hjhan decbd83d0b refactor(swap): 误导布尔 swap 改名 interestWindowEmpty——按"计息窗口为空"本质命名
InitInterestDate 返回 true 的语义是"计息窗口为空→不计利息"(利率与金额归零),
判定只看日期窗口(interestStart>interestEnd)、与事件类型无关;旧名 swap 把
典型原因(互换当日已结息)当成了效果的名字,读者会误读为事件类型判断。
- SwapDealService: GetInterests 局部变量 + GetFloatRate/CalcUnwindInterest/
  InitSwapDealInterest/CalcMarginInterest 四个签名参数及分支/日志/注释同步
- SwapTradeBaseService.InitInterestDate 返回值语义补入 XML 文档
- MarginInterestGoldenReplay/Shadow 测试命名参数同步
零行为变更。

验证:相关套件 51/51;全量 978 例 145 败与基线 diff=0
2026-08-19 11:31:03 +08:00

202 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
namespace UnitTestProject.Modules.SwapModule.Margin
{
/// <summary>
/// 影子对账:保证金腿方法 CalcMarginInterestEOD 用昨日终本金、盘中用 accrualBasis 差分)vs
/// 旧通用管线 CalcDailySimpleInterestByEod/CalcDailySimpleInterest。
///
/// 保证金是纯固定利率单利(FloatRateUnderlyingCode 恒空、InterestType 恒单利、SwapIntervalList 单段)。
/// 本测试在生产切到 CalcMarginInterest 后作为回归守护,确认其 InterestAmount/TdInterestAmount
/// 与旧纯函数(SimpleInterestAccrual)数值一致。覆盖 EOD 续接/首日、盘中全平/部分平仓/互换。
/// </summary>
[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 })
}
};
/// <summary>保证金腿(初始预付金 mode 5):固定利率、单利、年化、无浮动标的。</summary>
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 = "[]"
};
/// <summary>构造昨日终 eod_swap_position(已含累计利息 InterestProfitSum 与昨日终本金)。</summary>
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 路径 ────────────────────────────
/// <summary>EOD 续接单日:有历史归档,notional=昨日终本金。</summary>
[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, interestWindowEmpty: 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 一致");
}
/// <summary>EOD 首日(preEod.id==0):首日初始化 notional=posiPrincipal。</summary>
[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, interestWindowEmpty: false);
Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致");
Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致");
}
// ──────────────────────────── 盘中路径 ────────────────────────────
/// <summary>盘中全平(closePercent=1):新方法 notional=posiPrincipal,旧方法差分 accrualBasis 恒=posiPrincipal。</summary>
[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, interestWindowEmpty: 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 一致");
}
/// <summary>盘中部分平仓(closePercent=0.5):缩放累计,新旧线性等价。</summary>
[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, interestWindowEmpty: 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 一致");
}
/// <summary>互换事件(swap=true,盘中):利息应归零。</summary>
[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, interestWindowEmpty: true);
Assert.AreEqual(0m, newEvt.InterestAmount, "互换利息归零");
Assert.AreEqual(0m, newEvt.TdInterestAmount, "互换 TdInterestAmount 归零");
Assert.AreEqual(0m, newEvt.InterestClosePnL, "互换 InterestClosePnL 归零");
}
}
}