test(swap): EQD-6968 补算尾依赖守卫——重置日缺价仍拦+newCalcLast覆盖算尾仍拦
防'不算尾放宽'误扩到依赖场景(方案一场景4): - Guard_TailCalced×2:算尾(11)+当日重置日+当日缺价 → 必须仍抛(复利/单利) - Baseline_TailCalced:算尾+当日有价 → 成功(对照) - Guard_NewCalcLast:交易10不算尾但本次平仓指定算尾 → effectiveCalcLast=true 仍拦 - Baseline_NewCalcLast:指定算尾+有价 → 成功 Run 运行器扩参:calcMode/interestRule/newCalcLast/market(本提交仅用 calcMode/newCalcLast) 验证:GLMS20260817Fr007UnwindMorningTest 17/17 内存全绿
This commit is contained in:
@@ -14,6 +14,11 @@ namespace YLErp.Modules.SwapModule
|
||||
///
|
||||
/// 核心语义:算头不算尾(calcLast=false)时 endDate 当天不计息,其 FR007 利率不参与计息。
|
||||
/// 缺价时跳过取价(currentFloat 保持不变),不回退取其他日期利率,不告警。
|
||||
///
|
||||
/// 守卫矩阵(防"放宽过头",对应 EQD-6968 方案一四场景):
|
||||
/// Guard_*:算尾("11"或newCalcLast=true)+当日重置日+当日缺价 → 必须仍拦截(正确依赖);
|
||||
/// PrevBizDay_*:interest_rule=-1(前一营业日基准)→ 取价日回拨,当日未发布也放行(场景2);
|
||||
/// TailCalced_NonResetDay_*:算尾+当日非重置日 → 当日价未消费,缺价放行(场景3)。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class GLMS20260817Fr007UnwindMorningTest
|
||||
@@ -38,20 +43,23 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
private readonly HashSet<DateTime> _omit;
|
||||
private readonly double _closeRate;
|
||||
private readonly Dictionary<DateTime, double> _market;
|
||||
public readonly List<DateTime> PricedDates = new();
|
||||
|
||||
public StubSwapDealService(OptUserInfo optUser, IEnumerable<DateTime> omit, double closeRate = 0.0143)
|
||||
public StubSwapDealService(OptUserInfo optUser, IEnumerable<DateTime> omit, double closeRate = 0.0143,
|
||||
Dictionary<DateTime, double> market = null)
|
||||
: base(optUser)
|
||||
{
|
||||
_omit = new HashSet<DateTime>(omit.Select(d => d.Date));
|
||||
_closeRate = closeRate;
|
||||
_market = market;
|
||||
}
|
||||
|
||||
protected override bool TryGetFloatRate(DateTime valueDate, string underlyingCode, out double rate)
|
||||
{
|
||||
rate = 0d;
|
||||
if (underlyingCode != "FR007") return false;
|
||||
var map = new Dictionary<DateTime, double>(Fr007Market) { [CloseDate] = _closeRate };
|
||||
var map = new Dictionary<DateTime, double>(_market ?? Fr007Market) { [CloseDate] = _closeRate };
|
||||
if (_omit.Contains(valueDate.Date)) return false;
|
||||
if (map.TryGetValue(valueDate.Date, out rate))
|
||||
{
|
||||
@@ -75,7 +83,7 @@ namespace YLErp.Modules.SwapModule
|
||||
private static OptUserInfo MakeOptUser() =>
|
||||
new(0, nameof(GLMS20260817Fr007UnwindMorningTest), OptUserFrom.UnitTest);
|
||||
|
||||
private static trade BuildTrade(DateTime closeDate)
|
||||
private static trade BuildTrade(DateTime closeDate, string calcMode = "10")
|
||||
{
|
||||
var extend = new trade_extend
|
||||
{
|
||||
@@ -83,7 +91,7 @@ namespace YLErp.Modules.SwapModule
|
||||
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
|
||||
{
|
||||
AnnualDays = AnnualDays,
|
||||
InterestCalcMode = "10",
|
||||
InterestCalcMode = calcMode,
|
||||
SettlementRules = 0
|
||||
})
|
||||
};
|
||||
@@ -102,7 +110,8 @@ namespace YLErp.Modules.SwapModule
|
||||
};
|
||||
}
|
||||
|
||||
private static swap_position BuildPosition(InterestTypeEnum interestType, DateTime closeDate, int restDays = 7)
|
||||
private static swap_position BuildPosition(InterestTypeEnum interestType, DateTime closeDate, int restDays = 7,
|
||||
int interestRule = 0)
|
||||
{
|
||||
var intervalModels = new List<IntervalModel>
|
||||
{
|
||||
@@ -124,7 +133,7 @@ namespace YLErp.Modules.SwapModule
|
||||
InterestType = (int)interestType,
|
||||
IsAnnualized = true,
|
||||
interest_rest_days = restDays,
|
||||
interest_rule = 0,
|
||||
interest_rule = interestRule,
|
||||
FloatRateUnderlyingCode = "FR007",
|
||||
FloatRate = 0m,
|
||||
PosiNotionalValue = Notional,
|
||||
@@ -155,16 +164,20 @@ namespace YLErp.Modules.SwapModule
|
||||
eod_swap_position preEod = null,
|
||||
decimal closePrecent = 1m,
|
||||
DateTime? omitDate = null,
|
||||
double closeRate = 0.0143)
|
||||
double closeRate = 0.0143,
|
||||
string calcMode = "10",
|
||||
int interestRule = 0,
|
||||
bool newCalcLast = false,
|
||||
Dictionary<DateTime, double> market = null)
|
||||
{
|
||||
var cd = closeDate ?? CloseDate;
|
||||
var omit = new HashSet<DateTime>();
|
||||
if (omitDate.HasValue) omit.Add(omitDate.Value.Date);
|
||||
else if (!includeCloseDate) omit.Add(cd.Date);
|
||||
|
||||
var svc = new StubSwapDealService(MakeOptUser(), omit, closeRate);
|
||||
var td = BuildTrade(cd);
|
||||
var position = BuildPosition(interestType, cd, restDays);
|
||||
var svc = new StubSwapDealService(MakeOptUser(), omit, closeRate, market);
|
||||
var td = BuildTrade(cd, calcMode);
|
||||
var position = BuildPosition(interestType, cd, restDays, interestRule);
|
||||
var eodList = preEod == null
|
||||
? new List<eod_swap_position>()
|
||||
: new List<eod_swap_position> { preEod };
|
||||
@@ -175,7 +188,7 @@ namespace YLErp.Modules.SwapModule
|
||||
new List<swap_position> { position },
|
||||
Notional, Notional, closePrecent,
|
||||
(int)SwapEventTypeEnum.平仓, false, Notional,
|
||||
false, settment: false, newCalcLast: false, closeList: null);
|
||||
false, settment: false, newCalcLast: newCalcLast, closeList: null);
|
||||
Assert.AreEqual(1, interests.Count, "应返回恰好 1 条利息事件");
|
||||
return new Outcome { Fe = interests[0] };
|
||||
}
|
||||
@@ -283,5 +296,53 @@ namespace YLErp.Modules.SwapModule
|
||||
AssertNoThrow(Run(InterestTypeEnum.单利, includeCloseDate: true, closeDate: NonIntCloseDate, preEod: BuildPreEod(StartDate)),
|
||||
"单利-非整倍数-有7/20价-应成功");
|
||||
}
|
||||
|
||||
// ── 算尾守卫(EQD-6968 方案一场景4:算尾+当前营业日+当日重置日+当日缺价 → 必须仍拦截)──
|
||||
// 放宽只针对"该日利率不参与计息"的场景;算尾时当日利率被消费,缺价拦截是正确依赖,不得误放。
|
||||
|
||||
[TestMethod]
|
||||
public void Guard_TailCalced_ResetDayFr007Missing_StillThrows()
|
||||
{
|
||||
var o = Run(InterestTypeEnum.复利, includeCloseDate: false, calcMode: "11",
|
||||
preEod: BuildPreEod(new DateTime(2026, 7, 13)));
|
||||
Assert.IsTrue(o.Threw, "算尾(11)+当日重置日+当日缺价 → 应拦截(该日利率被消费)");
|
||||
StringAssert.Contains(o.Ex.Message, "FR007");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Guard_TailCalced_Simple_ResetDayFr007Missing_StillThrows()
|
||||
{
|
||||
var o = Run(InterestTypeEnum.单利, includeCloseDate: false, calcMode: "11",
|
||||
preEod: BuildPreEod(StartDate));
|
||||
Assert.IsTrue(o.Threw, "单利 算尾(11)+当日重置日+当日缺价 → 应拦截");
|
||||
StringAssert.Contains(o.Ex.Message, "FR007");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Baseline_TailCalced_ResetDayFr007Present_Succeeds()
|
||||
{
|
||||
AssertNoThrow(Run(InterestTypeEnum.复利, includeCloseDate: true, calcMode: "11",
|
||||
preEod: BuildPreEod(new DateTime(2026, 7, 13))),
|
||||
"算尾(11)+当日重置日+当日有价 → 应成功");
|
||||
}
|
||||
|
||||
// ── newCalcLast 守卫:交易本身"10"不算尾,但本次平仓显式指定算尾 → effectiveCalcLast=true → 缺价仍拦截 ──
|
||||
|
||||
[TestMethod]
|
||||
public void Guard_NewCalcLast_OverridesToTail_MissingPrice_StillThrows()
|
||||
{
|
||||
var o = Run(InterestTypeEnum.复利, includeCloseDate: false, newCalcLast: true,
|
||||
preEod: BuildPreEod(new DateTime(2026, 7, 13)));
|
||||
Assert.IsTrue(o.Threw, "不算尾(10)+本次平仓指定算尾+当日缺价 → 应按算尾拦截");
|
||||
StringAssert.Contains(o.Ex.Message, "FR007");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Baseline_NewCalcLast_OverridesToTail_WithPrice_Succeeds()
|
||||
{
|
||||
AssertNoThrow(Run(InterestTypeEnum.复利, includeCloseDate: true, newCalcLast: true,
|
||||
preEod: BuildPreEod(new DateTime(2026, 7, 13))),
|
||||
"不算尾(10)+本次平仓指定算尾+当日有价 → 应成功");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user