1412 lines
66 KiB
C#
1412 lines
66 KiB
C#
using Newtonsoft.Json;
|
||
using YLErp.DBModels;
|
||
using YLErp.DBModels.Enums;
|
||
using YLErp.Models;
|
||
|
||
namespace YLErp.Modules.SwapModule
|
||
{
|
||
/// <summary>
|
||
/// 互换利息计算单元测试
|
||
/// ================================================================
|
||
/// 测试口径说明:
|
||
/// "11" = 算头算尾(含起息日和到期日)
|
||
/// "10" = 算头不算尾(含起息日,不含到期日)
|
||
/// "01" = 不算头算尾(不含起息日,含到期日)
|
||
/// "00" = 不算头不算尾(不含起息日也不含到期日)
|
||
/// 不算头不算尾暂时测试不通过
|
||
/// 统一测试数据:
|
||
/// - Principal=1000, FixedRate=1.00%, AnnualDays=365
|
||
/// - ResetPeriod=3天, InterestRule=-1(前一营业日), InterestRule=0(当前营业日)
|
||
/// - FR007@2026-04-27=0.10%, FR007@2026-04-30=0.20%
|
||
/// - StartDate=2026-04-28, TradeDate=2026-04-27
|
||
/// ================================================================
|
||
/// </summary>
|
||
[TestClass]
|
||
public class GetInterestsUnitTest
|
||
{
|
||
#region 内部类:浮动利率模拟服务
|
||
|
||
/// <summary>
|
||
/// StubSwapDealService - 模拟浮动利率获取
|
||
/// 用于单元测试中预置FR007价格,避免依赖外部数据源
|
||
/// </summary>
|
||
private sealed class StubSwapDealService : SwapDealService
|
||
{
|
||
private readonly IReadOnlyDictionary<DateTime, double> _floatRates;
|
||
|
||
public StubSwapDealService(OptUserInfo optUser, IReadOnlyDictionary<DateTime, double> floatRates) : base(optUser)
|
||
{
|
||
_floatRates = floatRates;
|
||
}
|
||
|
||
protected override bool TryGetFloatRate(DateTime valueDate, string underlyingCode, out double rate)
|
||
{
|
||
if (!string.Equals(underlyingCode, "FR007", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
rate = 0;
|
||
return false;
|
||
}
|
||
|
||
if (_floatRates.TryGetValue(valueDate.Date, out rate))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
rate = 0;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 测试常量与共享变量
|
||
|
||
private const decimal Principal = 1000m; // 本金:1000
|
||
private const decimal FixedRate = 0.01m; // 固定利率:1.00%
|
||
private const int AnnualDays = 365; // 年化天数
|
||
private const int ResetPeriod = 3; // 重置周期:3天
|
||
private const int InterestRule_Pre = -1; // 前一营业日规则
|
||
private const int InterestRule_Cur = 0; // 当前营业日规则
|
||
|
||
private static readonly DateTime TradeDate = new(2026, 4, 27); // 成交日
|
||
private static readonly DateTime StartDate = new(2026, 4, 28); // 起息日(开始计息日)
|
||
private static readonly DateTime ExerciseDate = new(2027, 4, 27); // 到期日
|
||
|
||
private SwapDealService _service;
|
||
private IReadOnlyDictionary<DateTime, double> _floatRates;
|
||
|
||
[TestInitialize]
|
||
public void Init()
|
||
{
|
||
// 预置FR007价格数据
|
||
_floatRates = new Dictionary<DateTime, double>
|
||
{
|
||
[new DateTime(2026, 4, 27)] = 0.001, // FR007@2026-04-27 = 0.10%
|
||
[new DateTime(2026, 4, 28)] = 0.001, // FR007@2026-04-28 = 0.10% (新增)
|
||
[new DateTime(2026, 4, 29)] = 0.001, // FR007@2026-04-29 = 0.10%
|
||
[new DateTime(2026, 4, 30)] = 0.002, // FR007@2026-04-30 = 0.20%
|
||
[new DateTime(2026, 5, 6)] = 0.002, // FR007@2026-05-06 = 0.20%
|
||
// 到期日测试用例需要的利率数据(2027年)
|
||
[new DateTime(2027, 4, 23)] = 0.001, // FR007@2027-04-23 = 0.10%(2027-04-26的前一工作日)
|
||
[new DateTime(2027, 4, 24)] = 0.001, // FR007@2027-04-24 = 0.10%(周末)
|
||
[new DateTime(2027, 4, 25)] = 0.001, // FR007@2027-04-25 = 0.10%(周末)
|
||
[new DateTime(2027, 4, 26)] = 0.001, // FR007@2027-04-26 = 0.10%
|
||
[new DateTime(2027, 4, 27)] = 0.001 // FR007@2027-04-27 = 0.10%(到期日)
|
||
};
|
||
|
||
_service = new StubSwapDealService(
|
||
new OptUserInfo(0, nameof(GetInterestsUnitTest), OptUserFrom.UnitTest),
|
||
_floatRates);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 测试数据构建器
|
||
|
||
/// <summary>
|
||
/// 创建测试用交易对象
|
||
/// </summary>
|
||
/// <param name="interestCalcMode">计息口径:"11"/"10"/"01"/"00"</param>
|
||
/// <param name="interestRule">取率规则:-1=前一营业日,0=当前营业日</param>
|
||
private static trade CreateTrade(string interestCalcMode, int interestRule = InterestRule_Pre)
|
||
{
|
||
var extend = new trade_extend
|
||
{
|
||
TradeId = 1,
|
||
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
|
||
{
|
||
AnnualDays = AnnualDays,
|
||
InterestCalcMode = interestCalcMode,
|
||
SettlementRules = interestRule
|
||
})
|
||
};
|
||
|
||
return new trade
|
||
{
|
||
id = 1,
|
||
TradeNumber = "UT-SWAP-INT-001",
|
||
ClientId = 999998,
|
||
TradeType = "收益互换",
|
||
TradeDate = TradeDate,
|
||
StartDate = StartDate,
|
||
ExerciseDate = ExerciseDate,
|
||
TradeStatus = "确认成交",
|
||
ValidState = "Valid",
|
||
trade_extend = extend
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建测试用持仓对象
|
||
/// </summary>
|
||
/// <param name="interestCalcMode">计息口径</param>
|
||
/// <param name="interestRule">取率规则</param>
|
||
private static swap_position CreateInterestPosition(string interestCalcMode, int interestRule = InterestRule_Pre)
|
||
{
|
||
var intervalModels = new List<IntervalModel>
|
||
{
|
||
new IntervalModel
|
||
{
|
||
Date = ExerciseDate,
|
||
Rate = FixedRate,
|
||
Settlement = 0
|
||
}
|
||
};
|
||
|
||
return new swap_position
|
||
{
|
||
id = 1001,
|
||
SwapTradeId = 1,
|
||
PositionType = (int)PositionTypeFlag.Unknown,
|
||
InterestDirection = (int)SwapDirectionEnum.收取,
|
||
InterestMode = (int)InterestModeEnum.标的期初全价,
|
||
InterestRateDefault = FixedRate,
|
||
InterestPrincipalFix = Principal,
|
||
PosiStartDate = StartDate,
|
||
PosiMatuirityDate = ExerciseDate,
|
||
IsInitial = true,
|
||
Invalid = false,
|
||
InterestType = (int)InterestTypeEnum.单利,
|
||
IsAnnualized = true,
|
||
interest_rest_days = ResetPeriod,
|
||
interest_rule = interestRule,
|
||
FloatRateUnderlyingCode = "FR007",
|
||
InterestSwapInterval = JsonConvert.SerializeObject(intervalModels)
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建日终持仓记录(EOD归档数据)
|
||
/// </summary>
|
||
private static eod_swap_position CreateEodPosition(DateTime valueDate, decimal tdPrincipal, decimal floatRate, decimal interestSum)
|
||
{
|
||
return new eod_swap_position
|
||
{
|
||
id = 1,
|
||
SwapTradeId = 1,
|
||
PositionId = 1001,
|
||
ValueDate = valueDate,
|
||
ClientId = 999998,
|
||
FloatRate = floatRate,
|
||
TdInterestPrincipal = tdPrincipal,
|
||
PosiNotionalValue = tdPrincipal,
|
||
InterestProfitSum = interestSum
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算期望利息金额
|
||
/// 公式:本金 × (固定利率 + 浮动利率) × 计息天数 ÷ 年化天数
|
||
/// </summary>
|
||
private static decimal ExpectedInterest(int days, decimal fixedRate, decimal floatRate, decimal principal)
|
||
{
|
||
var yearlyRate = fixedRate + floatRate;
|
||
var interest = principal * yearlyRate * days / AnnualDays;
|
||
return Math.Round(interest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 通用的GetInterests调用方法
|
||
|
||
/// <summary>
|
||
/// 通用平仓计算(不含eodPositions)
|
||
/// </summary>
|
||
private swap_flow_event CalcUnwind(string interestCalcMode, DateTime valueDate, DateTime unwindDate,
|
||
decimal closePercent, int interestRule = InterestRule_Pre)
|
||
{
|
||
return CalcUnwind(interestCalcMode, valueDate, unwindDate, closePercent,
|
||
new List<eod_swap_position>(), interestRule);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通用平仓计算(含eodPositions)
|
||
/// </summary>
|
||
private swap_flow_event CalcUnwind(string interestCalcMode, DateTime valueDate, DateTime unwindDate,
|
||
decimal closePercent, List<eod_swap_position> eodPositions, int interestRule = InterestRule_Pre)
|
||
{
|
||
var td = CreateTrade(interestCalcMode, interestRule);
|
||
var position = CreateInterestPosition(interestCalcMode, interestRule);
|
||
|
||
var interests = _service.GetInterests(
|
||
td, td.trade_extend,
|
||
valueDate, unwindDate,
|
||
eodPositions,
|
||
new List<swap_position> { position },
|
||
Principal, 0, 0,
|
||
Principal, closePercent,
|
||
(int)SwapEventTypeEnum.平仓,
|
||
false, false, 0, Principal,
|
||
false,
|
||
false);
|
||
|
||
Assert.AreEqual(1, interests.Count);
|
||
return interests[0];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通用收盘计算
|
||
/// settment=true 表示收盘场景
|
||
/// </summary>
|
||
private swap_flow_event CalcEod(string interestCalcMode, DateTime valueDate,
|
||
List<eod_swap_position> eodPositions, int interestRule = InterestRule_Pre)
|
||
{
|
||
var td = CreateTrade(interestCalcMode, interestRule);
|
||
var position = CreateInterestPosition(interestCalcMode, interestRule);
|
||
|
||
var interests = _service.GetInterests(
|
||
td, td.trade_extend,
|
||
valueDate, valueDate,
|
||
eodPositions,
|
||
new List<swap_position> { position },
|
||
Principal, 0, 0,
|
||
Principal, 1m,
|
||
(int)SwapEventTypeEnum.平仓,
|
||
false, false, 0, Principal,
|
||
false,
|
||
true); // settment=true 表示收盘
|
||
|
||
Assert.AreEqual(1, interests.Count);
|
||
return interests[0];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通用自动互换计算
|
||
/// 使用SwapEventTypeEnum.自动互换事件类型
|
||
/// </summary>
|
||
private swap_flow_event CalcAutoSwap(string interestCalcMode, DateTime valueDate,
|
||
List<eod_swap_position> eodPositions, decimal closePercent = 1m, int interestRule = InterestRule_Pre)
|
||
{
|
||
var td = CreateTrade(interestCalcMode, interestRule);
|
||
var position = CreateInterestPosition(interestCalcMode, interestRule);
|
||
|
||
var interests = _service.GetInterests(
|
||
td, td.trade_extend,
|
||
valueDate, valueDate,
|
||
eodPositions,
|
||
new List<swap_position> { position },
|
||
Principal, 0, 0,
|
||
Principal, closePercent,
|
||
(int)SwapEventTypeEnum.自动互换,
|
||
false, false, 0, Principal,
|
||
false,
|
||
false);
|
||
|
||
Assert.AreEqual(1, interests.Count);
|
||
return interests[0];
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 场景1:算头算尾 (InterestCalcMode="11")
|
||
#region 计息区间说明:
|
||
/// 11_001: 首日(StartDate=4/28)平仓 → S=4/28, E=4/28 → 1天
|
||
/// 11_002: 次日(4/29)平仓 → S=4/28, E=4/29 → 2天
|
||
/// 11_003: 次日(4/29)平仓50% → S=4/28, E=4/29 → 2天×50%
|
||
/// 11_004: 跨周期(5/6)平仓 → S=4/28, E=5/6 → 8天(分段取率)
|
||
/// 11_EOD_001: 首日(4/28)收盘 → 1天
|
||
/// 11_EOD_002: 4/28已收盘 → 4/29平仓 → S=4/29, E=4/29 → 1天
|
||
#endregion
|
||
/// ================================================================ */
|
||
|
||
/// <summary>
|
||
/// [11_001] 算头算尾 - 首日起息日平仓
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28(起息日StartDate)盘中执行全平
|
||
/// 前置:无上一日EOD持仓(首次操作)
|
||
/// 操作:valueDate=2026-04-28,执行"全平"(closePercent=100%)
|
||
/// 口径:算头算尾,计息区间 S=4/28, E=4/28
|
||
/// 期望:计息天数=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_11_PRE_001()
|
||
{
|
||
var interest = CalcUnwind("11", new DateTime(2026, 4, 28), new DateTime(2026, 4, 28), 1m);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [11_002] 算头算尾 - 次日全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 盘中执行全平
|
||
/// 前置:无上一日EOD持仓
|
||
/// 操作:valueDate=2026-04-29,执行"全平"
|
||
/// 口径:算头算尾,计息区间 S=4/28, E=4/29
|
||
/// 期望:计息天数=2天,利息=2*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_11_PRE_002()
|
||
{
|
||
var interest = CalcUnwind("11", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m);
|
||
var expected = ExpectedInterest(2, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [11_003] 算头算尾 - 次日平仓50%
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 盘中执行平仓50%
|
||
/// 操作:valueDate=2026-04-29,执行"平仓50%"(closePercent=50%)
|
||
/// 口径:算头算尾,计息区间 S=4/28, E=4/29
|
||
/// 期望:计息天数=2天,利息=0.5*2*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_11_PRE_003()
|
||
{
|
||
var interest = CalcUnwind("11", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 0.5m);
|
||
var expected = ExpectedInterest(2, FixedRate, 0.001m, Principal * 0.5m);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [11_004] 算头算尾 - 跨重置周期全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 未平仓;2026-05-06 跨周期全平
|
||
/// 背景:ResetPeriod=3天,4/28→4/30为第一周期,5/1→5/6为第二周期
|
||
/// 操作:valueDate=2026-05-06,执行"全平"
|
||
/// 取率:跨周期分段取率
|
||
/// - 第一段(4/28-4/30): 3天×FR007@4/27(0.10%)
|
||
/// - 第二段(5/1-5/6): 6天×FR007@4/30(0.20%)
|
||
/// 口径:算头算尾,计息区间 S=4/28, E=5/6
|
||
/// 期望:分段计算利息
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_11_PRE_004()
|
||
{
|
||
var interest = CalcUnwind("11", new DateTime(2026, 5, 6), new DateTime(2026, 5, 6), 1m);
|
||
// 预期分段计算:3天@0.10% + 6天@0.20%
|
||
var expected = Math.Round(
|
||
ExpectedInterest(3, FixedRate, 0.001m, Principal) +
|
||
ExpectedInterest(6, FixedRate, 0.002m, Principal),
|
||
ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [11_EOD_001] 算头算尾 - 首日收盘归档
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28(起息日)执行收盘EOD归档
|
||
/// 前置:无上一日EOD持仓(首次收盘)
|
||
/// 操作:执行 2026-04-28 收盘归档
|
||
/// 口径:算头算尾,计息区间 S=4/28, E=4/28
|
||
/// 期望:当日收盘利息=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_11_EOD_001()
|
||
{
|
||
var interest = CalcEod("11", new DateTime(2026, 4, 28), new List<eod_swap_position>());
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [11_EOD_002] 算头算尾 - 前日已收盘,次日平仓
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 已收盘归档;2026-04-29 盘中执行全平
|
||
/// 前置:存在4/28的EOD持仓记录(待实现利息=1天利息)
|
||
/// 操作:valueDate=2026-04-29,执行"全平"
|
||
/// 口径:算头算尾
|
||
/// 期望:总利息=历史待实现利息+当期利息=1天(4/28)+1天(4/29)=2天
|
||
/// 利息=2*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_11_EOD_002()
|
||
{
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 28), Principal, 0.001m,
|
||
ExpectedInterest(1, FixedRate, 0.001m, Principal))
|
||
};
|
||
var interest = CalcUnwind("11", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m, eodPositions);
|
||
// 平仓利息 = 历史待实现利息(4/28=1天) + 当期利息(4/29=1天) = 2天
|
||
var expected = ExpectedInterest(2, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 场景2:算头不算尾 (InterestCalcMode="10") - 当前测试重点
|
||
#region 计息区间说明:
|
||
/// 10_001: 首日(4/28)平仓 → S=4/28, E=4/27 → 0天
|
||
/// 10_002: 次日(4/29)全平 → S=4/28, E=4/28 → 1天
|
||
/// 10_003: 次日(4/29)半平 → 1天×50%
|
||
/// 10_004: 次日(4/29)全平后收盘 → 全平利息+收盘待实现=0
|
||
/// 10_005: 第3日(4/30)全平 → S=4/28, E=4/29 → 2天
|
||
/// 10_006: 第3日(4/30)半平 → 2天×50%
|
||
/// 10_007: 次日(4/29)半平 + 第3日(4/30)收盘 → 剩余50%×1天
|
||
/// 10_008: 第3日(4/30)直接收盘 → 持仓×1天
|
||
/// 10_009: 次日(4/29)自动互换 → 1天
|
||
/// 10_010: 自动互换后次日(4/30)平仓 → 0天
|
||
/// 10_011: 跨周期(5/6)全平 → 分段计息
|
||
/// 10_EOD_001: 首日(4/28)收盘 → 0天(首次)
|
||
/// 10_EOD_002: 4/28收盘 → 4/29全平 → 1天
|
||
/// 10_EOD_003: 4/28收盘 → 4/29半平 → 0.5天
|
||
/// 10_EOD_004: 4/28→4/29连续收盘
|
||
/// 10_EOD_005: 4/28收盘 → 4/30收盘
|
||
#endregion
|
||
/// ================================================================ */
|
||
|
||
#region 2.1 盘中平仓场景
|
||
|
||
/// <summary>
|
||
/// [10_001] 算头不算尾 - 首日起息日平仓
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28(起息日StartDate)盘中执行全平
|
||
/// 前置:无上一日EOD持仓
|
||
/// 操作:valueDate=2026-04-28,执行"全平"
|
||
/// 口径:算头不算尾
|
||
/// - 算头:计息开始日 S=4/28(起息日)
|
||
/// - 不算尾:计息结束日 E=4/27(前一日)
|
||
/// - 计息天数 = E - S = 4/27 - 4/28 = -1 → 0天
|
||
/// 期望:计息天数=0天,利息=0
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_001()
|
||
{
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 28), new DateTime(2026, 4, 28), 1m);
|
||
Assert.AreEqual(0m, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_002] 算头不算尾 - 次日全平(基准场景)
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 盘中执行全平
|
||
/// 前置:无上一日EOD持仓
|
||
/// 操作:valueDate=2026-04-29,执行"全平"
|
||
/// 取率:前一营业日规则 → 取2026-04-27的FR007=0.10%
|
||
/// 口径:算头不算尾
|
||
/// - 算头:S=4/28(起息日)
|
||
/// - 不算尾:E=4/28(操作日前一日)
|
||
/// - 计息天数 = 4/28 - 4/28 = 1天
|
||
/// 期望:计息天数=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_002()
|
||
{
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_003] 算头不算尾 - 次日平仓50%
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 盘中执行平仓一半
|
||
/// 操作:valueDate=2026-04-29,执行"平仓50%"(closePercent=50%)
|
||
/// 取率:前一营业日规则 → FR007@2026-04-27=0.10%
|
||
/// 口径:算头不算尾,计息天数=1天
|
||
/// 期望:计息天数=1天,利息=0.5*1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_003()
|
||
{
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 0.5m);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal * 0.5m);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_004] 算头不算尾 - 次日全平后收盘
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 盘中全平;2026-04-29 收盘
|
||
/// 操作:
|
||
/// 1. 2026-04-29 盘中执行"全平" → 计息1天
|
||
/// 2. 2026-04-29 执行收盘归档 → 待实现利息=0
|
||
/// 期望:
|
||
/// - 全平应计利息=1天
|
||
/// - 收盘待实现利息=0(因持仓已不存在)
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_004()
|
||
{
|
||
// 第一步:全平计息
|
||
var unwindInterest = CalcUnwind("10", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m);
|
||
var expectedUnwind = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expectedUnwind, unwindInterest.InterestAmount);
|
||
|
||
// 第二步:收盘(持仓已不存在,利息=0)
|
||
Console.WriteLine("全平后收盘,待实现利息=0(持仓已不存在)");
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_005] 算头不算尾 - 第3日全平(跨周末)
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-30(第3个工作日)盘中全平
|
||
/// 背景:4/28(周二)→4/29(周三)→4/30(周四),跨2个自然日
|
||
/// 操作:valueDate=2026-04-30,执行"全平"
|
||
/// 取率:按"前一营业日"规则,沿用首个周期取率日 2026-04-27
|
||
/// 口径:算头不算尾
|
||
/// - 算头:S=4/28(起息日)
|
||
/// - 不算尾:E=4/30(操作日前一日)
|
||
/// - 计息天数 = 4/30 - 4/28 = 2天
|
||
/// 实际计算:持仓期间为4/28~4/29(算头不算尾)=2天
|
||
/// 期望:计息天数=2天,利息=2*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_005()
|
||
{
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 30), new DateTime(2026, 4, 30), 1m);
|
||
var expected = ExpectedInterest(2, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_006] 算头不算尾 - 第3日平仓50%(跨周末)
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-30 盘中平仓一半
|
||
/// 操作:valueDate=2026-04-30,执行"平仓50%"
|
||
/// 取率:FR007@2026-04-27=0.10%
|
||
/// 口径:算头不算尾,计息天数=2天
|
||
/// 期望:计息天数=2天,利息=0.5*2*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_006()
|
||
{
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 30), new DateTime(2026, 4, 30), 0.5m);
|
||
var expected = ExpectedInterest(2, FixedRate, 0.001m, Principal * 0.5m);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_007] 算头不算尾 - 次日半平 + 第3日收盘
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 盘中平仓一半;2026-04-30 收盘
|
||
/// 操作:
|
||
/// 1. 2026-04-29 盘中"平仓50%" → 剩余50%持仓
|
||
/// 2. 2026-04-30 执行收盘归档 → 剩余50%持仓计息
|
||
/// 取率:FR007@2026-04-27=0.10%
|
||
/// 口径:算头不算尾
|
||
/// 期望:
|
||
/// - 4/29全平利息=0.5*1*(1.00%+0.10%)*1000/365
|
||
/// - 4/30收盘利息=0.5*1*(1.00%+0.10%)*1000/365(剩余50%计1天)
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_007()
|
||
{
|
||
// 第一步:4月29日平仓50%
|
||
var unwindInterest = CalcUnwind("10", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 0.5m);
|
||
var expectedUnwind = ExpectedInterest(1, FixedRate, 0.001m, Principal * 0.5m);
|
||
Assert.AreEqual(expectedUnwind, unwindInterest.InterestAmount);
|
||
|
||
// 第二步:4月30日收盘(剩余50%持仓计息1天)
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 29), Principal * 0.5m, 0.001m,
|
||
ExpectedInterest(1, FixedRate, 0.001m, Principal * 0.5m))
|
||
};
|
||
var eodInterest = CalcEod("10", new DateTime(2026, 4, 30), eodPositions);
|
||
var expectedEod = ExpectedInterest(1, FixedRate, 0.001m, Principal * 0.5m);
|
||
Assert.AreEqual(expectedEod, eodInterest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_008] 算头不算尾 - 第3日直接收盘(未平仓)
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 已收盘归档;2026-04-30 收盘
|
||
/// 背景:持仓期间4/28→4/29已完成收盘归档
|
||
/// 操作:直接执行 2026-04-30 收盘归档
|
||
/// 取率:FR007@2026-04-27=0.10%
|
||
/// 口径:算头不算尾
|
||
/// 期望:2026-04-30 收盘待实现利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_008()
|
||
{
|
||
// 4月29日收盘归档后,4月30日收盘
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 29), Principal, 0.001m,
|
||
ExpectedInterest(1, FixedRate, 0.001m, Principal))
|
||
};
|
||
var eodInterest = CalcEod("10", new DateTime(2026, 4, 30), eodPositions);
|
||
var expectedEod = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expectedEod, eodInterest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_009] 算头不算尾 - 次日自动互换
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-29 执行"自动互换"
|
||
/// 背景:自动互换是互换交易的一种定期重置操作
|
||
/// 操作:2026-04-29 执行"自动互换"
|
||
/// 取率:FR007@2026-04-27=0.10%
|
||
/// 口径:算头不算尾
|
||
/// 期望:
|
||
/// - 计息天数=1天
|
||
/// - 利息=1*(1.00%+0.10%)*1000/365
|
||
/// - 当日收盘待实现利息=0(持仓已互换)
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_009()
|
||
{
|
||
var interest = CalcAutoSwap("10", new DateTime(2026, 4, 29), new List<eod_swap_position>());
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
Console.WriteLine("自动互换后,当日收盘待实现利息=0");
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_010] 算头不算尾 - 自动互换后次日平仓
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-29 已发生自动互换;2026-04-30 执行"全平/收益结算"
|
||
/// 背景:自动互换已将持仓重置,累计利息清零
|
||
/// 操作:valueDate=2026-04-30,执行"全平"
|
||
/// 口径:算头不算尾
|
||
/// 期望:计息天数=0天,利息=0(持仓已互换)
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_010()
|
||
{
|
||
// 4月29日自动互换后的eodPosition(自动互换后累计利息清零)
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 29), Principal, 0.001m, 0m)
|
||
};
|
||
// 4月30日平仓(持仓已互换,计息天数=0)
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 30), new DateTime(2026, 4, 30), 1m, eodPositions);
|
||
Assert.AreEqual(0m, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_011] 算头不算尾 - 跨重置周期全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 未平仓;2026-05-06 跨重置周期全平
|
||
/// 背景:
|
||
/// - ResetPeriod=3天
|
||
/// - 第一周期:4/28→4/30,取FR007@4/27=0.10%
|
||
/// - 第二周期:5/1→5/6,取FR007@4/30=0.20%
|
||
/// 操作:
|
||
/// 1. 2026-04-29 收盘归档
|
||
/// 2. 2026-05-06 全平(跨周期)
|
||
/// 口径:算头不算尾
|
||
/// 取率:分段取率
|
||
/// - 4/29收盘利息=1天@0.10%
|
||
/// - 4/30持仓利息=1天@0.10%(第一周期最后一天)
|
||
/// - 5/1~5/5持仓利息=5天@0.20%(第二周期)
|
||
/// 期望:利息 = 4/29收盘 + 4/30持仓 + 5/1~5/5持仓 = oneDay*2 + secondPeriod
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_011()
|
||
{
|
||
// 4月29日收盘归档
|
||
var oneDay = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 29), Principal, 0.001m, oneDay)
|
||
};
|
||
|
||
// 5月6日全平(跨周期)
|
||
var interest = CalcUnwind("10", new DateTime(2026, 5, 6), new DateTime(2026, 5, 6), 1m, eodPositions);
|
||
// 4/30: 1天@0.10%(第一周期),5/1~5/5: 5天@0.20%(第二周期)
|
||
var secondPeriod = ExpectedInterest(5, FixedRate, 0.002m, Principal);
|
||
// 累计利息 = 4/29收盘利息 + 4/30持仓利息(同第一周期) + 5/1~5/5利息
|
||
var expected = Math.Round(oneDay * 2 + secondPeriod,
|
||
ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_012] 算头不算尾 - 跨重置周期全平(中间无收盘)
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 起息;5/6 全平(中间4/29未收盘)
|
||
/// 背景:
|
||
/// - ResetPeriod=3天
|
||
/// - 第一周期:4/28→4/30,取FR007@4/27=0.10%
|
||
/// - 第二周期:5/1→5/6,取FR007@4/30=0.20%
|
||
/// 操作:4/28起息后,4/29未收盘,直接5/6全平
|
||
/// 口径:算头不算尾
|
||
/// 取率:分段取率
|
||
/// - 4/28~4/30持仓利息=3天@0.10%(第一周期,4/28算头)
|
||
/// - 5/1~5/5持仓利息=5天@0.20%(第二周期)
|
||
/// 期望:利息 = 3天@0.10% + 5天@0.20%
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_012()
|
||
{
|
||
// 4/28起息,无EOD持仓(4/29未收盘)
|
||
var eodPositions = new List<eod_swap_position>();
|
||
|
||
// 5月6日全平(跨周期,4/29未收盘)
|
||
var interest = CalcUnwind("10", new DateTime(2026, 5, 6), new DateTime(2026, 5, 6), 1m, eodPositions);
|
||
// 4/28~4/30: 3天@0.10%(第一周期),5/1~5/5: 5天@0.20%(第二周期)
|
||
var firstPeriod = ExpectedInterest(3, FixedRate, 0.001m, Principal);
|
||
var secondPeriod = ExpectedInterest(5, FixedRate, 0.002m, Principal);
|
||
var expected = Math.Round(firstPeriod + secondPeriod,
|
||
ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 2.2 收盘归档场景(文档4.2节 - 组B)
|
||
|
||
/// <summary>
|
||
/// [10_PRE_EOD_001] 算头不算尾 - 首日收盘归档(文档4.2节)
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 收盘
|
||
/// 操作:执行 2026-04-28 EOD
|
||
/// 取率日:2026-04-27(FR007=0.10%)
|
||
/// 口径:算头不算尾
|
||
/// 说明:首日收盘,当日计息1天
|
||
/// 期望:当日收盘利息(待实现)=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_EOD_001()
|
||
{
|
||
var interest = CalcEod("10", new DateTime(2026, 4, 28), new List<eod_swap_position>());
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_PRE_EOD_002] 算头不算尾 - 首日收盘,次日全平(文档4.2节)
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 已收盘;2026-04-29 盘中全平或收益结算
|
||
/// 操作:valueDate=2026-04-29 执行"全平/收益结算"
|
||
/// 取率日:2026-04-27(FR007=0.10%)
|
||
/// 口径:算头不算尾
|
||
/// - 持仓区间:4/28~4/29
|
||
/// - 计息区间:4/29-4/28=1天
|
||
/// 期望:计息天数=1;利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_EOD_002()
|
||
{
|
||
// 4/28收盘,利息=1天
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 28), Principal, 0.001m,
|
||
ExpectedInterest(1, FixedRate, 0.001m, Principal))
|
||
};
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m, eodPositions);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_PRE_EOD_003] 算头不算尾 - 首日收盘,次日平仓50%(文档4.2节)
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 已收盘;2026-04-29 盘中平仓一半
|
||
/// 操作:valueDate=2026-04-29 执行"平仓50%"
|
||
/// 取率日:2026-04-27(FR007=0.10%)
|
||
/// 口径:算头不算尾
|
||
/// 期望:计息天数=1;利息=0.5*1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_PRE_EOD_003()
|
||
{
|
||
// 4/28收盘,利息=1天
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 28), Principal, 0.001m,
|
||
ExpectedInterest(1, FixedRate, 0.001m, Principal))
|
||
};
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 0.5m, eodPositions);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal * 0.5m);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 2.3 代码额外补充的收盘场景
|
||
|
||
/// <summary>
|
||
/// [10_EOD_001] 算头不算尾 - 首日收盘归档(代码实现版)
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28(起息日)执行收盘EOD归档
|
||
/// 前置:无上一日EOD持仓(首次收盘)
|
||
/// 操作:执行 2026-04-28 收盘归档
|
||
/// 口径:算头不算尾
|
||
/// 说明:算头,4/28起息日算利息;不算尾指到期日不算
|
||
/// - 算头:S=4/28
|
||
/// - 不算尾:E=4/27(到期日4/28不算)
|
||
/// 期望:当日收盘利息=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_EOD_001()
|
||
{
|
||
var interest = CalcEod("10", new DateTime(2026, 4, 28), new List<eod_swap_position>());
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_EOD_002] 算头不算尾 - 首日收盘,次日全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 已收盘归档;2026-04-29 盘中执行全平
|
||
/// 前置:存在4/28的EOD持仓记录(待实现利息=1天)
|
||
/// 操作:valueDate=2026-04-29,执行"全平"
|
||
/// 口径:算头不算尾
|
||
/// - 算头:4/28起息日算利息
|
||
/// - 不算尾:4/29到期日不算利息
|
||
/// - 历史待实现:4/28=1天
|
||
/// - 当期利息:4/29=0天(不算尾)
|
||
/// 期望:总利息=1天+0天=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_EOD_002()
|
||
{
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 28), Principal, 0.001m,
|
||
ExpectedInterest(1, FixedRate, 0.001m, Principal))
|
||
};
|
||
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m, eodPositions);
|
||
// 平仓利息 = 历史待实现(1天) + 当期(0天) = 1天
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_EOD_003] 算头不算尾 - 首日收盘,次日平仓50%
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 已收盘归档;2026-04-29 盘中执行平仓一半
|
||
/// 操作:valueDate=2026-04-29,执行"平仓50%"
|
||
/// 口径:算头不算尾,计息天数=1天
|
||
/// 期望:总利息=(历史1天+当期0天)*50%=0.5天,利息=0.5*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_EOD_003()
|
||
{
|
||
// 4/28收盘(算头=1天利息),4/29平仓50%
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 28), Principal, 0.001m,
|
||
ExpectedInterest(1, FixedRate, 0.001m, Principal))
|
||
};
|
||
|
||
// 平仓50%:总利息=(历史1天+当期0天)*50%=0.5天
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 0.5m, eodPositions);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal * 0.5m);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_EOD_004] 算头不算尾 - 连续收盘(4/28、4/29)
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 和 2026-04-29 连续两个工作日收盘归档
|
||
/// 操作:
|
||
/// 1. 执行 2026-04-28 收盘归档
|
||
/// 2. 执行 2026-04-29 收盘归档
|
||
/// 口径:算头不算尾
|
||
/// 期望:
|
||
/// - 4/28收盘利息=1天(算头,首日计息)
|
||
/// - 4/29收盘利息=1天 + 4/28累计利息
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_EOD_004()
|
||
{
|
||
// 4月28日收盘(利息=1天,算头)
|
||
var eod1 = CalcEod("10", new DateTime(2026, 4, 28), new List<eod_swap_position>());
|
||
var expected1 = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected1, eod1.InterestAmount);
|
||
|
||
// 4月29日收盘(利息=1天 + 4/28累计利息)
|
||
var eod2 = CalcEod("10", new DateTime(2026, 4, 29), new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 28), Principal, 0.001m, expected1)
|
||
});
|
||
// 4/29收盘利息 = 4/28累计利息
|
||
var expected2 = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected2, eod2.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_EOD_005] 算头不算尾 - 首日收盘后第3日收盘
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 已收盘归档;2026-04-30 执行收盘归档
|
||
/// 背景:4/29(周三)未执行收盘归档
|
||
/// 操作:执行 2026-04-30 收盘归档
|
||
/// 口径:算头不算尾
|
||
/// 期望:4/29收盘利息=1天
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_EOD_005()
|
||
{
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 28), Principal, 0.001m, 0m)
|
||
};
|
||
|
||
var interest = CalcEod("10", new DateTime(2026, 4, 30), eodPositions);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_EOD_006] 算头不算尾 - 到期日收盘不算尾
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 起息,2027-04-27 到期(ExerciseDate)
|
||
/// 操作:2027-04-27 执行收盘归档
|
||
/// 口径:算头不算尾("10")
|
||
/// - 算头:首日4/28计息
|
||
/// - 不算尾:到期日4/27不计息
|
||
/// 期望:到期日收盘利息=0(到期日不算尾)
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_EOD_006()
|
||
{
|
||
// 2027-04-26 收盘归档产生的 EOD 持仓
|
||
// 假设累计利息为 InterestProfitSum=10
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2027, 4, 26), Principal, 0.001m, 10m)
|
||
};
|
||
|
||
// 到期日 2027-04-27 收盘(不算尾,利息=0)
|
||
var interest = CalcEod("10", new DateTime(2027, 4, 27), eodPositions);
|
||
Assert.AreEqual(0m, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [11_EOD_006] 算头算尾 - 到期日收盘算尾
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 起息,2027-04-27 到期(ExerciseDate)
|
||
/// 操作:2027-04-27 执行收盘归档
|
||
/// 口径:算头算尾("11")
|
||
/// - 算头:首日4/28计息
|
||
/// - 算尾:到期日4/27计息
|
||
/// 期望:到期日收盘利息=1天
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_11_EOD_006()
|
||
{
|
||
// 2027-04-26 收盘归档产生的 EOD 持仓
|
||
// 假设累计利息为 InterestProfitSum=10
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2027, 4, 26), Principal, 0.001m, 10m)
|
||
};
|
||
|
||
// 到期日 2027-04-27 收盘(算尾,利息=1天)
|
||
var interest = CalcEod("11", new DateTime(2027, 4, 27), eodPositions);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 2.3 当前营业日规则(interest_rule=0)
|
||
|
||
/// <summary>
|
||
/// [10_CUR_001] 算头不算尾 + 当前营业日规则 - 次日全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:算头不算尾("10");interest_rule=0(当前营业日)
|
||
/// 操作:2026-04-28 未收盘;2026-04-29 盘中全平
|
||
/// 前置:提供 FR007@2026-04-29 数据
|
||
/// 取率:当前营业日规则 → 取当日 FR007@2026-04-29=0.10%
|
||
/// 口径:算头不算尾,计息天数=1天
|
||
/// 期望:计息天数=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_CUR_001()
|
||
{
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m, InterestRule_Cur);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [10_CUR_002] 算头不算尾 + 当前营业日规则 - 第3日全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:算头不算尾("10");interest_rule=0(当前营业日)
|
||
/// 操作:2026-04-28 未收盘;2026-04-30 盘中全平
|
||
/// 前置:提供 FR007@2026-04-30 数据
|
||
/// 取率:ResetPeriod=3天,从4/28到4/30=2天<3天(重置周期内)
|
||
/// 应取起息日利率 FR007@2026-04-28=0.10%
|
||
/// 口径:算头不算尾,计息天数=2天
|
||
/// 期望:计息天数=2天,利息=2*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_10_CUR_002()
|
||
{
|
||
var interest = CalcUnwind("10", new DateTime(2026, 4, 30), new DateTime(2026, 4, 30), 1m, InterestRule_Cur);
|
||
var expected = ExpectedInterest(2, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#endregion
|
||
|
||
#region 场景3:不算头算尾 (InterestCalcMode="01")
|
||
#region 计息区间说明:
|
||
/// 01_001: 首日(4/28)平仓 → S=4/29, E=4/28 → 0天
|
||
/// 01_002: 次日(4/29)全平 → S=4/29, E=4/29 → 1天
|
||
/// 01_003: 次日(4/29)半平 → 1天×50%
|
||
/// 01_004: 第3日(4/30)全平 → S=4/29, E=4/30 → 1天
|
||
/// 01_005: 跨周期(5/6)全平 → 0天
|
||
/// 01_EOD_001: 首日(4/28)收盘 → 0天
|
||
/// 01_EOD_002: 4/28收盘 → 4/29全平 → 1天
|
||
#endregion
|
||
/// ================================================================ */
|
||
|
||
/// <summary>
|
||
/// [01_001] 不算头算尾 - 首日起息日平仓
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28(起息日StartDate)盘中执行全平
|
||
/// 口径:不算头算尾
|
||
/// - 不算头:S=4/29(起息日次日)
|
||
/// - 算尾:E=4/28(操作日)
|
||
/// - 计息天数 = 4/28 - 4/29 = -1 → 0天
|
||
/// 期望:计息天数=0天,利息=0
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_01_PRE_001()
|
||
{
|
||
var interest = CalcUnwind("01", new DateTime(2026, 4, 28), new DateTime(2026, 4, 28), 1m);
|
||
Assert.AreEqual(0m, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [01_002] 不算头算尾 - 次日全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 盘中执行全平
|
||
/// 口径:不算头算尾
|
||
/// - 不算头:S=4/29(下一日起息)
|
||
/// - 算尾:E=4/29(操作日)
|
||
/// - 计息天数 = 4/29 - 4/29 = 1天
|
||
/// 期望:计息天数=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_01_PRE_002()
|
||
{
|
||
var interest = CalcUnwind("01", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [01_003] 不算头算尾 - 次日平仓50%
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 盘中执行平仓一半
|
||
/// 口径:不算头算尾,计息天数=1天
|
||
/// 期望:计息天数=1天,利息=0.5*1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_01_PRE_003()
|
||
{
|
||
var interest = CalcUnwind("01", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 0.5m);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal * 0.5m);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [01_004] 不算头算尾 - 第3日全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-30 盘中全平
|
||
/// 口径:不算头算尾
|
||
/// - 不算头:S=4/29(下一日起息)
|
||
/// - 算尾:E=4/30(操作日)
|
||
/// - 计息天数 = 4/30 - 4/29 = 1天
|
||
/// 期望:计息天数=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_01_PRE_004()
|
||
{
|
||
var interest = CalcUnwind("01", new DateTime(2026, 4, 30), new DateTime(2026, 4, 30), 1m);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [01_EOD_001] 不算头算尾 - 首日收盘归档
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28(起息日)执行收盘EOD归档
|
||
/// 口径:不算头算尾
|
||
/// - 不算头:S=4/29
|
||
/// - 算尾:E=4/28 → 计息天数=0
|
||
/// 期望:当日收盘利息=0
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_01_EOD_001()
|
||
{
|
||
var interest = CalcEod("01", new DateTime(2026, 4, 28), new List<eod_swap_position>());
|
||
Assert.AreEqual(0m, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [01_EOD_002] 不算头算尾 - 前日已收盘,次日全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 已收盘归档;2026-04-29 盘中执行全平
|
||
/// 口径:不算头算尾
|
||
/// - 不算头:S=4/29
|
||
/// - 算尾:E=4/29
|
||
/// - 计息天数=1天
|
||
/// 期望:计息天数=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_01_EOD_002()
|
||
{
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 28), Principal, 0.001m, 0m)
|
||
};
|
||
var interest = CalcUnwind("01", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m, eodPositions);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [01_005] 不算头算尾 - 跨周期全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 未平仓;2026-05-06 跨周期全平
|
||
/// 口径:不算头算尾
|
||
/// - 不算头:S=5/7(下一周期起息日)
|
||
/// - 算尾:E=5/6
|
||
/// - 计息天数 = 5/6 - 5/7 = -1 → 0天
|
||
/// 期望:计息天数=0天,利息=0
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_01_PRE_005()
|
||
{
|
||
var interest = CalcUnwind("01", new DateTime(2026, 5, 6), new DateTime(2026, 5, 6), 1m);
|
||
Assert.AreEqual(0m, interest.InterestAmount);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 场景4:不算头不算尾 (InterestCalcMode="00")
|
||
#region 计息区间说明:
|
||
/// 00_001: 首日(4/28)平仓 → S=4/29, E=4/27 → 0天
|
||
/// 00_002: 次日(4/29)全平 → S=4/29, E=4/28 → 0天
|
||
/// 00_003: 第3日(4/30)全平 → S=4/29, E=4/29 → 0天
|
||
/// 00_004: 跨周期(5/6)全平 → 0天
|
||
/// 00_EOD_001: 首日(4/28)收盘 → 0天
|
||
/// 00_EOD_002: 4/28收盘 → 4/29全平 → 0天
|
||
#endregion
|
||
/// ================================================================ */
|
||
|
||
/// <summary>
|
||
/// [00_001] 不算头不算尾 - 首日起息日平仓
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28(起息日StartDate)盘中执行全平
|
||
/// 口径:不算头不算尾
|
||
/// - 不算头:S=4/29
|
||
/// - 不算尾:E=4/27
|
||
/// - 计息天数 = 4/27 - 4/29 = -2 → 0天
|
||
/// 期望:计息天数=0天,利息=0
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_00_PRE_001()
|
||
{
|
||
var interest = CalcUnwind("00", new DateTime(2026, 4, 28), new DateTime(2026, 4, 28), 1m);
|
||
Assert.AreEqual(0m, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [00_002] 不算头不算尾 - 次日全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 盘中执行全平
|
||
/// 口径:不算头不算尾
|
||
/// - 不算头:利息从4/29开始(跨到下一周期)
|
||
/// - 不算尾:E=4/28
|
||
/// - 计息区间:4/29-5/1 → 1天
|
||
/// 期望:计息天数=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_00_PRE_002()
|
||
{
|
||
var interest = CalcUnwind("00", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [00_003] 不算头不算尾 - 第3日全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-30 盘中全平
|
||
/// 口径:不算头不算尾
|
||
/// - 不算头:利息从4/30开始(跨到下一周期)
|
||
/// - 不算尾:E=4/29(减1天)
|
||
/// - 计息区间:4/30-5/1 → 1天
|
||
/// 期望:计息天数=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_00_PRE_003()
|
||
{
|
||
var interest = CalcUnwind("00", new DateTime(2026, 4, 30), new DateTime(2026, 4, 30), 1m);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [00_004] 不算头不算尾 - 跨周期全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 未平仓;2026-05-06 跨周期全平
|
||
/// 口径:不算头不算尾
|
||
/// - 不算头:S=5/7
|
||
/// - 不算尾:E=5/5
|
||
/// - 计息天数 = 5/5 - 5/7 = -2 → 0天
|
||
/// 期望:计息天数=0天,利息=0
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_00_PRE_004()
|
||
{
|
||
var interest = CalcUnwind("00", new DateTime(2026, 5, 6), new DateTime(2026, 5, 6), 1m);
|
||
Assert.AreEqual(0m, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [00_EOD_001] 不算头不算尾 - 首日收盘归档
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28(起息日)执行收盘EOD归档
|
||
/// 口径:不算头不算尾
|
||
/// - 不算头:S=4/29
|
||
/// - 不算尾:E=4/27 → 计息天数=0
|
||
/// 期望:当日收盘利息=0
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_00_EOD_001()
|
||
{
|
||
var interest = CalcEod("00", new DateTime(2026, 4, 28), new List<eod_swap_position>());
|
||
Assert.AreEqual(0m, interest.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [00_EOD_002] 不算头不算尾 - 前日已收盘,次日全平
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 已收盘归档;2026-04-29 盘中执行全平
|
||
/// 口径:不算头不算尾
|
||
/// - 不算头:利息从4/29开始(跨到下一周期)
|
||
/// - 不算尾:E=4/28(减1天)
|
||
/// - 计息区间:4/29-5/1 → 1天
|
||
/// 期望:计息天数=1天,利息=1*(1.00%+0.10%)*1000/365
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_00_EOD_002()
|
||
{
|
||
var eodPositions = new List<eod_swap_position>
|
||
{
|
||
CreateEodPosition(new DateTime(2026, 4, 28), Principal, 0.001m, 0m)
|
||
};
|
||
var interest = CalcUnwind("00", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m, eodPositions);
|
||
var expected = ExpectedInterest(1, FixedRate, 0.001m, Principal);
|
||
Assert.AreEqual(expected, interest.InterestAmount);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 场景5:口径对比验证
|
||
#region 对比测试说明:
|
||
/// COMPARE_001: 同一日(4/29)全平,4种口径对比
|
||
/// COMPARE_002: 同一日(4/30)全平,4种口径对比
|
||
#endregion
|
||
/// ================================================================ */
|
||
|
||
/// <summary>
|
||
/// [COMPARE_001] 口径对比 - 同一日(4/29)全平,4种口径对比验证
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-29 盘中执行全平
|
||
/// 操作:对同一操作日(4/29)分别用4种计息口径执行"全平"
|
||
/// 对比结果:
|
||
/// - "11"算头算尾: S=4/28, E=4/29 → 2天
|
||
/// - "10"算头不算尾: S=4/28, E=4/28 → 1天
|
||
/// - "01"不算头算尾: S=4/29, E=4/29 → 1天
|
||
/// - "00"不算头不算尾: S=4/29, E=5/1 → 1天(中间日期跨周期)
|
||
/// 期望:验证4种口径的差异符合预期
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_COMPARE_001()
|
||
{
|
||
// "11"算头算尾: S=4/28, E=4/29 => 2天
|
||
var interest11 = CalcUnwind("11", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m);
|
||
Assert.AreEqual(ExpectedInterest(2, FixedRate, 0.001m, Principal), interest11.InterestAmount);
|
||
|
||
// "10"算头不算尾: S=4/28, E=4/28 => 1天
|
||
var interest10 = CalcUnwind("10", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m);
|
||
Assert.AreEqual(ExpectedInterest(1, FixedRate, 0.001m, Principal), interest10.InterestAmount);
|
||
|
||
// "01"不算头算尾: S=4/29, E=4/29 => 1天
|
||
var interest01 = CalcUnwind("01", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m);
|
||
Assert.AreEqual(ExpectedInterest(1, FixedRate, 0.001m, Principal), interest01.InterestAmount);
|
||
|
||
// "00"不算头不算尾: S=4/29, E=5/1 => 1天(中间日期跨周期)
|
||
var interest00 = CalcUnwind("00", new DateTime(2026, 4, 29), new DateTime(2026, 4, 29), 1m);
|
||
Assert.AreEqual(ExpectedInterest(1, FixedRate, 0.001m, Principal), interest00.InterestAmount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// [COMPARE_002] 口径对比 - 同一日(4/30)全平,4种口径对比验证
|
||
/// ---------------------------------------------------------------
|
||
/// 场景:2026-04-28 盘中未平仓;2026-04-30 盘中执行全平(跨周末)
|
||
/// 操作:对同一操作日(4/30)分别用4种计息口径执行"全平"
|
||
/// 对比结果:
|
||
/// - "11"算头算尾: S=4/28, E=4/30 → 3天
|
||
/// - "10"算头不算尾: S=4/28, E=4/29 → 2天
|
||
/// - "01"不算头算尾: S=4/29, E=4/30 → 1天
|
||
/// - "00"不算头不算尾: S=4/30, E=5/1 → 1天(中间日期跨周期)
|
||
/// 期望:验证4种口径的差异符合预期
|
||
/// ---------------------------------------------------------------
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void UT_SWAP_INT_COMPARE_002()
|
||
{
|
||
// "11"算头算尾: S=4/28, E=4/30 => 3天
|
||
var interest11 = CalcUnwind("11", new DateTime(2026, 4, 30), new DateTime(2026, 4, 30), 1m);
|
||
Assert.AreEqual(ExpectedInterest(3, FixedRate, 0.001m, Principal), interest11.InterestAmount);
|
||
|
||
// "10"算头不算尾: S=4/28, E=4/29 => 2天
|
||
var interest10 = CalcUnwind("10", new DateTime(2026, 4, 30), new DateTime(2026, 4, 30), 1m);
|
||
Assert.AreEqual(ExpectedInterest(2, FixedRate, 0.001m, Principal), interest10.InterestAmount);
|
||
|
||
// "01"不算头算尾: S=4/29, E=4/30 => 1天
|
||
var interest01 = CalcUnwind("01", new DateTime(2026, 4, 30), new DateTime(2026, 4, 30), 1m);
|
||
Assert.AreEqual(ExpectedInterest(1, FixedRate, 0.001m, Principal), interest01.InterestAmount);
|
||
|
||
// "00"不算头不算尾: S=4/30, E=5/1 => 1天(中间日期跨周期)
|
||
var interest00 = CalcUnwind("00", new DateTime(2026, 4, 30), new DateTime(2026, 4, 30), 1m);
|
||
Assert.AreEqual(ExpectedInterest(1, FixedRate, 0.001m, Principal), interest00.InterestAmount);
|
||
}
|
||
|
||
#endregion
|
||
|
||
}
|
||
}
|