237 lines
9.5 KiB
C#
237 lines
9.5 KiB
C#
using Newtonsoft.Json;
|
|
using YLErp.DBModels.Enums;
|
|
|
|
namespace YLErp.Modules.SwapModule
|
|
{
|
|
/// <summary>
|
|
/// EQD-6968 FR007 不算尾平仓"上午未发布"误拦截 - RED 复现,修复后转绿(内存,不连库)
|
|
/// 任务编号 EQD-6968;现象报告日 2026-08-17。参照 GLMS20260703CloseInterestTest 内存 FR007 写法。
|
|
/// </summary>
|
|
[TestClass]
|
|
public class GLMS20260817Fr007UnwindMorningTest
|
|
{
|
|
private sealed class StubSwapDealService : SwapDealService
|
|
{
|
|
private readonly bool _includeCloseDate;
|
|
|
|
public StubSwapDealService(OptUserInfo optUser, bool includeCloseDate) : base(optUser)
|
|
{
|
|
_includeCloseDate = includeCloseDate;
|
|
}
|
|
|
|
public readonly List<(DateTime RequestDate, double Rate)> FloatRateCalls = new();
|
|
|
|
protected override bool TryGetFloatRate(DateTime valueDate, string underlyingCode, out double rate)
|
|
{
|
|
rate = 0d;
|
|
if (underlyingCode != "FR007") return false;
|
|
|
|
var fr007 = new Dictionary<DateTime, double>
|
|
{
|
|
[new DateTime(2026, 7, 6)] = 0.0142,
|
|
[new DateTime(2026, 7, 13)] = 0.01425,
|
|
};
|
|
if (_includeCloseDate)
|
|
fr007[new DateTime(2026, 7, 20)] = 0.0143;
|
|
|
|
if (fr007.TryGetValue(valueDate.Date, out rate))
|
|
{
|
|
FloatRateCalls.Add((valueDate.Date, rate));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private const decimal Notional = 279486108.21m;
|
|
private const int AnnualDays = 365;
|
|
private const decimal Spread = -0.0155m;
|
|
private static readonly DateTime StartDate = new(2026, 7, 6);
|
|
private static readonly DateTime TradeDate = new(2026, 7, 3);
|
|
private static readonly DateTime CloseDate = new(2026, 7, 20);
|
|
|
|
private SwapDealService MakeService(bool includeCloseDate)
|
|
{
|
|
return new StubSwapDealService(
|
|
new OptUserInfo(0, nameof(GLMS20260817Fr007UnwindMorningTest), OptUserFrom.UnitTest),
|
|
includeCloseDate);
|
|
}
|
|
|
|
private static trade CreateTrade()
|
|
{
|
|
var extend = new trade_extend
|
|
{
|
|
TradeId = 1,
|
|
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
|
|
{
|
|
AnnualDays = AnnualDays,
|
|
InterestCalcMode = "10",
|
|
SettlementRules = 0
|
|
})
|
|
};
|
|
return new trade
|
|
{
|
|
id = 1,
|
|
TradeNumber = "GLMS-20260817-FR007-MORNING",
|
|
ClientId = 999998,
|
|
TradeType = "债券TRS",
|
|
TradeDate = TradeDate,
|
|
StartDate = StartDate,
|
|
ExerciseDate = CloseDate.AddDays(1),
|
|
TradeStatus = "已平仓",
|
|
ValidState = "Valid",
|
|
trade_extend = extend
|
|
};
|
|
}
|
|
|
|
private static swap_position CreateBondPosition(InterestTypeEnum interestType, int interestRule = 0)
|
|
{
|
|
var intervalModels = new List<IntervalModel>
|
|
{
|
|
new IntervalModel { Date = CloseDate, Rate = Spread, Settlement = 0 }
|
|
};
|
|
return new swap_position
|
|
{
|
|
id = 1001,
|
|
SwapTradeId = 1,
|
|
PositionType = (int)PositionTypeFlag.Unknown,
|
|
InterestDirection = (int)SwapDirectionEnum.支付,
|
|
InterestMode = (int)InterestModeEnum.标的期初全价,
|
|
InterestRateDefault = Spread,
|
|
InterestPrincipalFix = Notional,
|
|
PosiStartDate = StartDate,
|
|
PosiMatuirityDate = CloseDate,
|
|
IsInitial = true,
|
|
Invalid = false,
|
|
InterestType = (int)interestType,
|
|
IsAnnualized = true,
|
|
interest_rest_days = 7,
|
|
interest_rule = interestRule,
|
|
FloatRateUnderlyingCode = "FR007",
|
|
FloatRate = 0m,
|
|
PosiNotionalValue = Notional,
|
|
UnderlyingCode = "2500002.IB",
|
|
InterestSwapInterval = JsonConvert.SerializeObject(intervalModels)
|
|
};
|
|
}
|
|
|
|
private swap_flow_event CalcCloseInterest(SwapDealService svc, InterestTypeEnum interestType, int interestRule = 0)
|
|
{
|
|
var td = CreateTrade();
|
|
var position = CreateBondPosition(interestType, interestRule);
|
|
var interests = svc.GetInterests(
|
|
td, td.trade_extend,
|
|
CloseDate, CloseDate,
|
|
new List<eod_swap_position>(),
|
|
new List<swap_position> { position },
|
|
Notional, Notional,
|
|
1m,
|
|
(int)SwapEventTypeEnum.平仓,
|
|
false, Notional,
|
|
false, settment: false, newCalcLast: false, closeList: null);
|
|
Assert.AreEqual(1, interests.Count);
|
|
return interests[0];
|
|
}
|
|
|
|
private static eod_swap_position CreatePreEod()
|
|
{
|
|
return new eod_swap_position
|
|
{
|
|
id = 5001,
|
|
PositionId = 1001,
|
|
ValueDate = new DateTime(2026, 7, 13),
|
|
FloatRate = 0.01425m,
|
|
InterestProfitSum = -100000m,
|
|
TdInterestPrincipal = Notional,
|
|
InterestIncomeSum = -150000m
|
|
};
|
|
}
|
|
|
|
private swap_flow_event CalcCloseInterestEod(SwapDealService svc, InterestTypeEnum interestType, List<eod_swap_position> eodPositions)
|
|
{
|
|
var td = CreateTrade();
|
|
var position = CreateBondPosition(interestType);
|
|
var interests = svc.GetInterests(
|
|
td, td.trade_extend,
|
|
CloseDate, CloseDate,
|
|
eodPositions,
|
|
new List<swap_position> { position },
|
|
Notional, Notional,
|
|
1m,
|
|
(int)SwapEventTypeEnum.平仓,
|
|
false, Notional,
|
|
false, settment: false, newCalcLast: false, closeList: null);
|
|
Assert.AreEqual(1, interests.Count);
|
|
return interests[0];
|
|
}
|
|
|
|
/// <summary>
|
|
/// [RED] 不算尾平仓,平仓日 FR007 未发布(内存缺失)→ 当前抛"获取不到FR007...价格"。
|
|
/// 期望:修复后应成功返回(不抛)。当前为 RED(测试失败)。
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Red_UnwindMorning_WithoutCloseDateFr007_ShouldSucceed()
|
|
{
|
|
var svc = MakeService(includeCloseDate: false);
|
|
try
|
|
{
|
|
var fe = CalcCloseInterest(svc, InterestTypeEnum.复利);
|
|
Assert.IsNotNull(fe);
|
|
Assert.IsFalse(fe.InterestAmount == 0 && fe.FloatRate == 0, "返回的利息不应全为零");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StringAssert.Contains(ex.Message, "FR007");
|
|
Assert.Fail($"RED 复现成功:不算尾平仓因平仓日 FR007 未发布被误拦截 —— {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// [Baseline] 同场景但提供平仓日 07-20 的 FR007 → 应成功(绿),隔离 stub/路径问题。
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Baseline_WithCloseDateFr007_Succeeds()
|
|
{
|
|
var svc = MakeService(includeCloseDate: true);
|
|
var fe = CalcCloseInterest(svc, InterestTypeEnum.复利);
|
|
Assert.IsNotNull(fe);
|
|
Assert.IsTrue(fe.InterestAmount != 0, "提供末日 FR007 时应正常算出利息");
|
|
}
|
|
|
|
/// <summary>
|
|
/// [RED-重放] 不算尾全平(带前日日终持仓 → 走重放分支 replayEndDate=平仓日+1),平仓日 FR007 未发布 → 当前抛。
|
|
/// 修复后(BuildSegmentRates 用 exclusionEndDate 排除真实平仓日)应成功,平仓日用上一重置日利率。
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Red_FullClose_WithPreEod_WithoutCloseDateFr007_Succeeds()
|
|
{
|
|
var svc = MakeService(includeCloseDate: false);
|
|
var preEod = CreatePreEod();
|
|
try
|
|
{
|
|
var fe = CalcCloseInterestEod(svc, InterestTypeEnum.复利, new List<eod_swap_position> { preEod });
|
|
Assert.IsNotNull(fe);
|
|
Assert.IsFalse(fe.InterestAmount == 0 && fe.FloatRate == 0, "返回的利息不应全为零");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StringAssert.Contains(ex.Message, "FR007");
|
|
Assert.Fail($"RED-重放 复现成功:不算尾全平因平仓日 FR007 未发布被误拦截 —— {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// [Baseline-重放] 同场景但提供平仓日 07-20 FR007 → 应成功(绿),隔离 stub/路径问题。
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Baseline_FullClose_WithPreEod_WithCloseDateFr007_Succeeds()
|
|
{
|
|
var svc = MakeService(includeCloseDate: true);
|
|
var preEod = CreatePreEod();
|
|
var fe = CalcCloseInterestEod(svc, InterestTypeEnum.复利, new List<eod_swap_position> { preEod });
|
|
Assert.IsNotNull(fe);
|
|
Assert.IsTrue(fe.InterestAmount != 0, "提供末日 FR007 时应正常算出利息");
|
|
}
|
|
}
|
|
}
|