#EQD-6948 国联民生-实现保证金规则(2)追保金额的产生与收盘计算 ETF 子类分档(转债ETF/科创债ETF)

This commit is contained in:
锦麟 王
2026-08-24 18:23:26 +08:00
parent 13e13fa9a3
commit d43e77ee6f
9 changed files with 398 additions and 52 deletions
@@ -108,7 +108,7 @@ namespace YLErp.Modules.MarginModule
var latestValueDate = detailQuery.Max(x => x.ValueDate);
var details = detailQuery.Where(x => x.ValueDate == latestValueDate).ToList();
//4.利率债期限档匹配:精确档 → "全部"BondTerm 为空)兜底
//4.利率债/分档ETF 期限档匹配:精确档 → "全部"BondTerm 为空)兜底
var term = UnderlyingHelper.GetApplicableMarginTerm(underlyingCode, valueDate);
var matched = details.Where(x => x.SpanConfig != null && x.SpanConfig.BondTerm == term).ToList();
if (!matched.Any())
@@ -120,13 +120,12 @@ namespace YLErp.Modules.MarginModule
return null;
}
//5.标的资产类型匹配(仅当模板选了"按资产类型分类"):精确标志位 → 通配行(None/All)兜底
//标的细分分类判定钩子:本期默认返回 null → 走下方原有标志位匹配/通配行兜底,行为与现状一致;
//后续需求(转债ETF/科创债ETF/中债指数等细分)实现判定规则后,此处按 (BondTerm, EtfKind) 元组匹配明细行。
//5.ETF 子类行优先(子类区分度高于期限):标的有 EtfSubType(基金类)时优先取 EtfKind=子类 的行——
//期限档匹配未命中子类行时再单独尝试"子类 + BondTerm 空"(子类不分档通配);无子类行维持原 matched(基金通配兜底)
var underlyingCategory = GetUnderlyingCategory(underlyingCode, underlyingInstrumentType);
if (underlyingCategory != null)
{
//TODO: 后续需求实现:按 (SpanConfig.BondTerm, SpanConfig.EtfKind) 元组匹配明细行,替代/补充下方标志位匹配
matched = PreferCategoryRows(matched, details, underlyingCategory);
}
if (template.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType
@@ -157,6 +156,26 @@ namespace YLErp.Modules.MarginModule
};
}
/// <summary>
/// ETF 子类行优先匹配(纯函数,单测覆盖):
/// termMatched 为期限档匹配结果(含精确档/BondTerm 空兜底),allDetails 为该模板当日全部明细行。
/// 优先取 termMatched 中 EtfKind=category 的行;没有则取"allDetails 中 EtfKind=category 且 BondTerm 空"的子类不分档行;
/// 都没有时原样返回 termMatched(基金通配行兜底)。
/// </summary>
public static List<margin_template_detail> PreferCategoryRows(List<margin_template_detail> termMatched,
List<margin_template_detail> allDetails, string category)
{
var byCategory = termMatched.Where(x => x.SpanConfig != null && x.SpanConfig.EtfKind == category).ToList();
if (byCategory.Any())
{
return byCategory;
}
var categoryWildcard = allDetails
.Where(x => x.SpanConfig != null && x.SpanConfig.EtfKind == category && string.IsNullOrEmpty(x.SpanConfig.BondTerm))
.ToList();
return categoryWildcard.Count > 0 ? categoryWildcard : termMatched;
}
/// <summary>
/// R1 模板三层级找到即停解析(public 供集成测试与后续消费方复用):
/// 1.交易绑定 trade_margin_template(自定义/录入时显式选择,ValueDate 最新 ≤ 业务日)——找到即停,
@@ -348,12 +367,19 @@ namespace YLErp.Modules.MarginModule
}
/// <summary>
/// 标的细分分类判定钩子(转债ETF/科创债ETF/中债指数等),public 供单元测试回归默认行为。
/// 本期默认返回 null → 走通配行兜底,行为与现状一致;后续需求按业务给的判定规则(代码段/标的维护字段)实现。
/// 标的细分分类判定钩子(ETF 子类,2026-08-24 实装):基金类标的返回标的维护页维护的
/// underlying_manager.EtfSubType(取值来自数据字典"ETF 子类":国债 ETF/政金债 ETF/…/科创债 ETF/可转债 ETF
/// 基金类必填);非基金或未维护返回 null → 走标志位匹配/通配行兜底(债券/股票等行为与现状一致)。
/// 后续其他细分(中债指数 IndexKind 等)在本方法内扩展。
/// </summary>
public static string GetUnderlyingCategory(string underlyingCode, string underlyingInstrumentType)
{
return null;
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode);
if (underlying == null || underlying.UnderlyingInstrumentType != ConsGlobal.InstrumentType.Fund)
{
return null;
}
return string.IsNullOrWhiteSpace(underlying.EtfSubType) ? null : underlying.EtfSubType.Trim();
}
/// <summary>