using YLErp.DBModels; using YLErp.Enums; using YLErp.Modules.MarginModule; namespace YLErp.Modules.CalcModules { /// /// ETF 子类分档取数(MarginTemplateV2RateHelper.PreferCategoryRows 纯函数): /// 标的有 EtfSubType(基金类,分类判定钩子实装=读 underlying_manager.EtfSubType)时, /// 期限档匹配结果内优先取 EtfKind=子类 的行;未命中时回落"子类+BondTerm 空"行; /// 均无则维持原期限档匹配结果(基金通配行兜底)。 /// [TestClass] public class MarginTemplateV2EtfKindMatchTest { private static margin_template_detail Row(int id, UnderlyingTypeEnum ut, string etfKind, string bondTerm) => new() { id = id, UnderlyingType = ut, SpanConfigJson = null, SpanConfig = new SpanConfig { EtfKind = etfKind, BondTerm = bondTerm } }; private const string CB = "可转债 ETF"; private const string STAR = "科创债 ETF"; [TestMethod] public void EK_001_期限档命中子类行_优先取子类() { //期限档匹配结果内既有 子类行又有 通配行:取子类行 var termMatched = new List { Row(1, UnderlyingTypeEnum.Fund, CB, "<5y"), //子类+精确档 Row(2, UnderlyingTypeEnum.Fund, null, "<5y"), //通配+精确档 }; var all = new List(termMatched); var matched = MarginTemplateV2RateHelper.PreferCategoryRows(termMatched, all, CB); Assert.AreEqual(1, matched.Count); Assert.AreEqual(1, matched[0].id); } [TestMethod] public void EK_002_期限档未命中子类行_回落子类空档行() { //期限档匹配(>30y)结果内无子类行 → 取"子类+BondTerm 空"的未分档行 var termMatched = new List { Row(2, UnderlyingTypeEnum.Fund, null, null), //通配(BondTerm 空兜底命中) }; var all = new List { Row(1, UnderlyingTypeEnum.Fund, CB, "<5y"), Row(3, UnderlyingTypeEnum.Fund, CB, null), //子类未分档行(兜底候选) Row(2, UnderlyingTypeEnum.Fund, null, null), }; var matched = MarginTemplateV2RateHelper.PreferCategoryRows(termMatched, all, CB); Assert.AreEqual(1, matched.Count); Assert.AreEqual(3, matched[0].id); } [TestMethod] public void EK_003_无子类行_维持原期限档匹配结果() { //模板无该子类任何行:维持原结果(通配兜底) var termMatched = new List { Row(2, UnderlyingTypeEnum.Fund, null, null), }; var all = new List(termMatched) { Row(4, UnderlyingTypeEnum.Fund, STAR, "5y-10y"), //别的子类不相关 }; var matched = MarginTemplateV2RateHelper.PreferCategoryRows(termMatched, all, CB); Assert.AreEqual(1, matched.Count); Assert.AreEqual(2, matched[0].id); } [TestMethod] public void EK_004_多子类并存_各自命中不串() { //转债ETF 与 科创债ETF 各自成组:转债标的只命中转债行 var termMatched = new List { Row(1, UnderlyingTypeEnum.Fund, CB, "<5y"), Row(4, UnderlyingTypeEnum.Fund, STAR, "<5y"), Row(2, UnderlyingTypeEnum.Fund, null, "<5y"), }; var all = new List(termMatched); var cbMatched = MarginTemplateV2RateHelper.PreferCategoryRows(termMatched, all, CB); Assert.AreEqual(1, cbMatched.Count); Assert.AreEqual(1, cbMatched[0].id); var starMatched = MarginTemplateV2RateHelper.PreferCategoryRows(termMatched, all, STAR); Assert.AreEqual(1, starMatched.Count); Assert.AreEqual(4, starMatched[0].id); } [TestMethod] public void EK_005_子类行但期限档不带BondTerm_不视为未分档兜底() { //子类+分档行(EtfKind=CB + BondTerm 非空)不能当"子类+空档"兜底候选 var termMatched = new List { Row(2, UnderlyingTypeEnum.Fund, null, null), }; var all = new List { Row(1, UnderlyingTypeEnum.Fund, CB, "<5y"), //分档行不是兜底候选 Row(2, UnderlyingTypeEnum.Fund, null, null), }; var matched = MarginTemplateV2RateHelper.PreferCategoryRows(termMatched, all, CB); Assert.AreEqual(1, matched.Count); Assert.AreEqual(2, matched[0].id); } } }