Files
zszq-trs/YLErpDAL/Modules/MarginModule/MarginTemplateV2RateHelper.cs
T

486 lines
29 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using YLErp.BLL;
using YLErp.DBModels;
using YLErp.Enums;
using YLErp.Helpers;
using YLErp.Models;
using YLErp.Modules.UnderlyingModule;
namespace YLErp.Modules.MarginModule
{
/// <summary>
/// 预付金模板V2 取数帮助类:按交易绑定的模板(trade_margin_template → margin_template_v2 → margin_template_detail
/// 取 初始预付金率x / 维持预付金率y,供交易确认书、预付金计算等消费点统一使用。
/// 匹配维度:簿记账户范围(BookIds 显式限定且匹配者优先于空=全部的通配,同层多候选 2026-08-28 裁定)→
/// 生效日期(ValueDate 最新)→ 利率债期限档(SpanConfig.BondTerm,四档,空=全部兜底)→ 标的资产类型(UnderlyingType 标志位)。
/// </summary>
public static class MarginTemplateV2RateHelper
{
private static readonly IYcLogger logger = LogFactory.GetLogger("预付金模板取数");
private sealed class ClientTemplateCandidate
{
public int ClientId { get; set; }
public string ClientLevel { get; set; }
public margin_template_v2 Template { get; set; }
}
/// <summary>
/// 取数结果。率为小数(如 0.1 表示 10%,与数据库一致),调用方直接用。
/// </summary>
public class MarginRateResult
{
/// <summary>
/// 命中的模板
/// </summary>
public margin_template_v2 Template { get; set; }
/// <summary>
/// 命中的明细参数行(无预付金规则时为 null)
/// </summary>
public margin_template_detail Detail { get; set; }
/// <summary>
/// 初始预付金率 x(小数)
/// </summary>
public decimal? InitRate { get; set; }
/// <summary>
/// 维持预付金率 y(小数)
/// </summary>
public decimal? MaintainRate { get; set; }
}
internal static int? GetTradeAssetId(int? tradeId, YLContext db)
{
if (!tradeId.HasValue || tradeId.Value <= 0)
{
return null;
}
return db.trade.AsNoTracking()
.Where(x => x.id == tradeId.Value)
.Select(x => (int?)x.AssetId)
.FirstOrDefault();
}
/// <summary>
/// 按交易绑定的模板取 初始/维持预付金率。
/// </summary>
/// <param name="tradeId">交易ID</param>
/// <param name="underlyingCode">标的代码(用于计算利率债期限档)</param>
/// <param name="underlyingInstrumentType">标的资产类型(trade/swap_position 的 UnderlyingInstrumentType,如 TBonds</param>
/// <param name="valueDate">业务日期</param>
/// <returns>命中返回结果;交易未绑定模板、模板无效、规则非 无预付金/区间追保结构、或明细无匹配行时返回 null(由调用方决定兜底)</returns>
public static MarginRateResult GetTradeMarginRate(int tradeId, string underlyingCode, string underlyingInstrumentType, DateTime valueDate)
{
using (var db = new YLContext())
{
return GetTradeMarginRate(tradeId, underlyingCode, underlyingInstrumentType, valueDate, db);
}
}
/// <summary>
/// 按交易绑定的模板取 初始/维持预付金率(调用方传入 DbContext,供批量场景复用连接)。
/// R1 三层级找到即停(2026-08-21 补齐客户/全局两级):交易绑定(自定义)→ 客户默认(client_margin_template
/// 按客户或客户等级)→ 全局默认(margin_template_v2 IsDefault&&!IsForClient,适用结构需含收益互换);
/// 三级均未命中返回 null(由调用方兜底,此处留日志:视为无预付金要求)。
/// </summary>
public static MarginRateResult GetTradeMarginRate(int tradeId, string underlyingCode, string underlyingInstrumentType, DateTime valueDate, YLContext db)
{
//R1 三层级找到即停:交易绑定(自定义)→ 客户默认 → 全局默认
var clientId = db.trade.AsNoTracking().Where(t => t.id == tradeId).Select(t => (int?)t.ClientId).FirstOrDefault() ?? 0;
var template = ResolveTieredTemplate(tradeId, clientId, valueDate, db);
if (template == null)
{
logger.Info($"【警告】预付金模板取数:交易{tradeId}(客户{clientId})三级(交易绑定/客户默认/全局默认)均未命中有效模板,视为无预付金要求");
return null;
}
return GetRateByTemplate(template, underlyingCode, underlyingInstrumentType, valueDate, db);
}
/// <summary>
/// 已解析模板的取率(GetTradeMarginRate 去掉三层级解析的部分;引擎批量解析后逐交易调用,避免二次解析)。
/// 规则非 无预付金/区间追保结构 或明细无匹配行时返回 null(由调用方兜底)。
/// </summary>
public static MarginRateResult GetRateByTemplate(margin_template_v2 template, string underlyingCode, string underlyingInstrumentType, DateTime valueDate, YLContext db)
{
//2.无预付金规则:率直接为 0
if (template.RuleType == (int)MarginRuleTypeEnum.无预付金)
{
logger.Info($"预付金模板取数:模板{template.id}(规则=无预付金)标的{underlyingCode}x=y=0");
return new MarginRateResult { Template = template, InitRate = 0m, MaintainRate = 0m };
}
if (template.RuleType != (int)MarginRuleTypeEnum.区间追保结构)
{
//其他规则不在本帮助类支持范围,显式返回 null
logger.Info($"【警告】预付金模板取数:模板{template.id} 规则{template.RuleType}不在取数支持范围(仅 无预付金/区间追保结构),返回null由调用方兜底");
return null;
}
//3.区间追保结构:取 ValueDate 最新生效的一组明细(同一 ValueDate 下有多行参数组)
var detailQuery = db.margin_template_detail.AsNoTracking()
.Where(x => x.MarginTemplateId == template.id && x.ValueDate <= valueDate);
if (!detailQuery.Any())
{
logger.Info($"【警告】预付金模板取数:模板{template.id} 无生效明细行(ValueDate≤{valueDate:yyyy-MM-dd}),返回null由调用方兜底");
return null;
}
var latestValueDate = detailQuery.Max(x => x.ValueDate);
var details = detailQuery.Where(x => x.ValueDate == latestValueDate).ToList();
//4.资产类型先行(2026-08-27 顺序裁定:先品种后期限):按资产类型分档的模板先按标的品种缩小行集——
//品种行 → 通配行(None/All)→ 均无则不缩小(回落到与旧序一致的期限匹配,模板未配该品种的既有兜底不变)。
//期限档仅利率债允许配置(ConsMarginTerm),品种匹配必须在期限之前:期限精确匹配对任何标的恒有 term
//GetApplicableMarginTerm 兜底 <5y),非利率债标的会被利率债期限档行截胡、本品种行(BondTerm 空)永远不参与
//(2026-08-27 交易2567 实证:信用债标的按 "<5y" 命中利率债行多收追保)
var candidates = details;
if (template.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType
&& Enum.TryParse<UnderlyingTypeEnum>(underlyingInstrumentType, out var instrumentFlag))
{
var byInstrument = details.Where(x => (x.UnderlyingType & instrumentFlag) > 0).ToList();
if (!byInstrument.Any())
{
byInstrument = details.Where(x => x.UnderlyingType == UnderlyingTypeEnum.None || x.UnderlyingType == UnderlyingTypeEnum.All).ToList();
}
if (byInstrument.Any())
{
candidates = byInstrument;
}
}
//5.期限档匹配(利率债四档):精确档 → "全部"BondTerm 为空)兜底
var term = UnderlyingHelper.GetApplicableMarginTerm(underlyingCode, valueDate);
var matched = candidates.Where(x => x.SpanConfig != null && x.SpanConfig.BondTerm == term).ToList();
if (!matched.Any())
{
matched = candidates.Where(x => x.SpanConfig == null || string.IsNullOrEmpty(x.SpanConfig.BondTerm)).ToList();
}
if (!matched.Any())
{
logger.Info($"【警告】预付金模板取数:模板{template.id}(生效日{latestValueDate:yyyy-MM-dd})标的{underlyingCode}(品种{underlyingInstrumentType},期限档{term})无匹配明细行,返回null由调用方兜底");
return null;
}
//6.ETF 子类行优先(子类区分度高于期限):标的有 EtfSubType(基金类)时优先取 EtfKind=子类 的行——
//期限档匹配未命中子类行时再单独尝试"子类 + BondTerm 空"(子类不分档通配);无子类行维持原 matched(基金通配兜底)
var underlyingCategory = GetUnderlyingCategory(underlyingCode, underlyingInstrumentType);
if (underlyingCategory != null)
{
matched = PreferCategoryRows(matched, details, underlyingCategory);
}
var detail = matched.First();
var initRate = ToDecimalRate(detail.MarginRatio1);
var maintainRate = ToDecimalRate(detail.MarginRatio2);
logger.Info($"预付金模板取数:模板{template.id}(生效日{latestValueDate:yyyy-MM-dd})标的{underlyingCode}(品种{underlyingInstrumentType},期限档{term})命中明细行{detail.id}:初始x={(initRate?.ToString("0.####") ?? "未配")},维持y={(maintainRate?.ToString("0.####") ?? "未配")}");
return new MarginRateResult
{
Template = template,
Detail = detail,
InitRate = initRate,
MaintainRate = maintainRate
};
}
/// <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 最新 ≤ 业务日)——找到即停,
/// 绑定指向已失效模板时同样停止(不向下回退,避免"改绑定失效后悄悄换成别的规则"),返回 null 并留日志;
/// 2.客户默认 client_margin_template(按客户 ClientId 或按客户等级 ClientId=0+ClientLevel
/// 客户等级经 client.LevelId → clientlevel.LevelName 解析;ValueDate 最新 ≤ 业务日),
/// 模板须有效、适用结构含收益互换且适用当前交易簿记账户;
/// 3.全局默认 margin_template_v2IsDefault && !IsForClient && IsValid && 适用收益互换,且适用当前簿记账户,ValueDate 最新)。
/// 2/3 层多候选并存时按 PickByBookScopePreference 取舍:簿记条件满足者优先,其次生效日最新(2026-08-28)。
/// </summary>
public static margin_template_v2 ResolveTieredTemplate(int? tradeId, int clientId, DateTime valueDate, YLContext db)
{
var tradeAssetId = GetTradeAssetId(tradeId, db);
//1.交易绑定(找到即停)
if (tradeId > 0)
{
var bindingTemplateId = db.trade_margin_template.AsNoTracking()
.Where(x => x.TradeId == tradeId.Value && x.ValueDate <= valueDate)
.OrderByDescending(x => x.ValueDate)
.ThenByDescending(x => x.id)
.Select(x => (int?)x.MarginTemplateId)
.FirstOrDefault();
if (bindingTemplateId > 0)
{
var bound = db.margin_template_v2.AsNoTracking().FirstOrDefault(x => x.id == bindingTemplateId.Value && x.IsValid);
if (bound != null && bound.IsApplicableToBook(tradeAssetId))
{
logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId})命中层级=交易绑定 → 模板{bound.id}");
return bound;
}
if (bound != null)
{
logger.Info($"【警告】预付金模板取数:交易{tradeId}绑定的模板{bindingTemplateId}不适用簿记账户{tradeAssetId},按找到即停不向下回退");
return null;
}
logger.Info($"【警告】预付金模板取数:交易{tradeId}绑定的模板{bindingTemplateId}已失效或不存在,按找到即停不向下回退");
return null;
}
}
//2.客户默认(client_margin_template:按客户 或 按客户等级;客户表在独立客户库)
//先按 有效+适用收益互换 过滤再取绑定日期最新——同日并存"互换绑定+期权绑定"(页面互斥只挡适用结构重叠)时,
//若先取绑定再过滤会随机命中期权绑定而漏掉互换绑定、误穿透到全局
if (clientId > 0)
{
string levelName = null;
using (var clientDb = DbContextFactory.GetClientDbContext(OptUserInfo.SystemUser))
{
var levelId = clientDb.client.AsNoTracking().Where(c => c.id == clientId).Select(c => c.LevelId).FirstOrDefault();
if (levelId > 0)
{
levelName = clientDb.clientlevel.AsNoTracking().Where(l => l.id == levelId).Select(l => l.LevelName).FirstOrDefault();
}
}
var clientTemplate = (from cmt in db.client_margin_template.AsNoTracking()
join mt in db.margin_template_v2.AsNoTracking() on cmt.MarginTemplateId equals mt.id
where cmt.ValueDate <= valueDate && cmt.MarginTemplateId > 0
&& (cmt.ClientId == clientId || (cmt.ClientId == 0 && cmt.ClientLevel != "" && cmt.ClientLevel == levelName))
&& mt.IsValid && mt.TradeTypes.Contains("收益互换")
orderby cmt.ValueDate descending, cmt.id descending
select mt).ToList();
//同层多候选:簿记条件满足者优先,其次绑定生效日最新(2026-08-28 裁定)
var ret = PickByBookScopePreference(clientTemplate, tradeAssetId, out var clientByBook);
if (ret != null)
{
logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId},等级{levelName ?? ""})命中层级=客户默认 → 模板{ret.id}{(clientByBook ? "(簿记账户匹配优先)" : "")}");
return ret;
}
}
//3.全局默认(同层多候选:簿记条件满足者优先,其次模板生效日最新——双全局默认并存时通配模板不再凭 id 最新恒胜,2026-08-28
var globalDefault = PickByBookScopePreference(db.margin_template_v2.AsNoTracking()
.Where(x => x.IsDefault && !x.IsForClient && x.IsValid && x.TradeTypes.Contains("收益互换") && x.ValueDate <= valueDate)
.OrderByDescending(x => x.ValueDate)
.ThenByDescending(x => x.id)
.ToList(), tradeAssetId, out var globalByBook);
if (globalDefault != null)
{
logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId})命中层级=全局默认 → 模板{globalDefault.id}{(globalByBook ? "(簿记账户匹配优先)" : "")}");
}
return globalDefault;
}
/// <summary>
/// R1 三层级找到即停解析(批量版,BUG-02 修正:EOD 引擎与结算查询按交易列表解析,
/// 只配了客户默认/全局默认模板的交易与交易级绑定同权生效):
/// 语义与单笔 ResolveTieredTemplate 一致——
/// 1.交易绑定(ValueDate 最新 ≤ 业务日,同日多条按 id 最新兜底)找到即停,绑定指向失效模板同样停止(不回退);
/// 2.客户默认(client_margin_template 按客户或按客户等级,模板须有效、适用结构含收益互换且适用当前簿记账户,先过滤有效再取绑定最新);
/// 3.全局默认(IsDefault&&!IsForClient&&IsValid&&适用收益互换,按每笔交易簿记账户取ValueDate/id 最新)。
/// 2/3 层多候选并存时按 PickByBookScopePreference 取舍:簿记条件满足者优先,其次生效日最新(2026-08-28)。
/// 返回 tradeId → 命中模板;未命中或因失效绑定停止的交易不在结果中。
/// </summary>
public static Dictionary<int, margin_template_v2> ResolveTieredTemplates(List<trade> trades, DateTime valueDate, YLContext db)
{
var result = new Dictionary<int, margin_template_v2>();
if (trades == null || trades.Count == 0)
{
return result;
}
var tradeIds = trades.Select(t => t.id).Distinct().ToList();
//1.交易绑定(批量取回后内存取每交易最新一条,同日多条按 id 最新兜底——与单笔解析一致)
var bindingRows = db.trade_margin_template.AsNoTracking()
.Where(x => tradeIds.Contains(x.TradeId) && x.ValueDate <= valueDate)
.Select(x => new { x.TradeId, x.MarginTemplateId, x.ValueDate, x.id })
.ToList();
var bindingByTrade = bindingRows
.GroupBy(x => x.TradeId)
.ToDictionary(g => g.Key,
g => g.OrderByDescending(x => x.ValueDate).ThenByDescending(x => x.id).First().MarginTemplateId);
var boundTemplates = new Dictionary<int, margin_template_v2>();
if (bindingByTrade.Values.Distinct().Any())
{
var boundTemplateIds = bindingByTrade.Values.Distinct().ToList();
boundTemplates = db.margin_template_v2.AsNoTracking()
.Where(x => boundTemplateIds.Contains(x.id) && x.IsValid)
.ToDictionary(x => x.id);
}
//2.客户默认(client_margin_template:按客户 或 按客户等级;客户表在独立客户库,等级名批量解析)
var clientIds = trades.Select(t => t.ClientId).Where(c => c > 0).Distinct().ToList();
var levelNames = new Dictionary<int, string>();
var clientTemplateCandidates = new List<ClientTemplateCandidate>();
if (clientIds.Any())
{
using (var clientDb = DbContextFactory.GetClientDbContext(OptUserInfo.SystemUser))
{
var clientLevels = clientDb.client.AsNoTracking()
.Where(c => clientIds.Contains(c.id) && c.LevelId > 0)
.Select(c => new { c.id, LevelId = c.LevelId ?? 0 })
.ToList();
var levelIds = clientLevels.Select(c => c.LevelId).Distinct().ToList();
var levelNameById = clientDb.clientlevel.AsNoTracking()
.Where(l => levelIds.Contains(l.id))
.Select(l => new { l.id, l.LevelName })
.ToList()
.ToDictionary(l => l.id, l => l.LevelName);
foreach (var c in clientLevels)
{
levelNames[c.id] = levelNameById.TryGetValue(c.LevelId, out var name) ? name : null;
}
}
//先按 有效+适用收益互换 过滤再按绑定取最新(与单笔解析一致,同日互换/期权并存不误穿透)
clientTemplateCandidates = (from cmt in db.client_margin_template.AsNoTracking()
join mt in db.margin_template_v2.AsNoTracking() on cmt.MarginTemplateId equals mt.id
where cmt.ValueDate <= valueDate && cmt.MarginTemplateId > 0
&& (clientIds.Contains(cmt.ClientId) || (cmt.ClientId == 0 && cmt.ClientLevel != ""))
&& mt.IsValid && mt.TradeTypes.Contains("收益互换")
orderby cmt.ValueDate descending, cmt.id descending
select new { cmt.ClientId, cmt.ClientLevel, Template = mt }).ToList()
.Select(x => new ClientTemplateCandidate
{
ClientId = x.ClientId,
ClientLevel = x.ClientLevel,
Template = x.Template
})
.ToList();
}
//3.全局默认(候选模板一次查询,每笔交易按簿记账户取最新适用模板)
var globalDefaults = db.margin_template_v2.AsNoTracking()
.Where(x => x.IsDefault && !x.IsForClient && x.IsValid && x.TradeTypes.Contains("收益互换") && x.ValueDate <= valueDate)
.OrderByDescending(x => x.ValueDate)
.ThenByDescending(x => x.id)
.ToList();
foreach (var trade in trades)
{
if (result.ContainsKey(trade.id))
{
continue;
}
if (bindingByTrade.TryGetValue(trade.id, out var templateId))
{
//一级找到即停:绑定指向失效模板时不向下回退(与单笔解析一致,仅留日志)
if (boundTemplates.TryGetValue(templateId, out var bound) && bound.IsApplicableToBook(trade.AssetId))
{
result[trade.id] = bound;
logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId})命中层级=交易绑定 → 模板{bound.id}");
}
else if (boundTemplates.ContainsKey(templateId))
{
logger.Info($"【警告】预付金模板取数:交易{trade.id}绑定的模板{templateId}不适用簿记账户{trade.AssetId},按找到即停不向下回退");
}
else
{
logger.Info($"【警告】预付金模板取数:交易{trade.id}绑定的模板{templateId}已失效或不存在,按找到即停不向下回退");
}
continue;
}
var levelName = levelNames.TryGetValue(trade.ClientId, out var clientLevelName) ? clientLevelName : null;
//同层多候选:先按客户/等级筛出候选(保持绑定生效日降序),再簿记条件满足者优先、其次生效日最新(与单笔解析一致)
var clientMatchCandidates = clientTemplateCandidates
.Where(x => x.ClientId == trade.ClientId || (x.ClientId == 0 && x.ClientLevel == levelName))
.Select(x => x.Template)
.ToList();
var clientTemplate = PickByBookScopePreference(clientMatchCandidates, trade.AssetId, out var clientByBook);
if (clientTemplate != null)
{
result[trade.id] = clientTemplate;
logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId},等级{levelName ?? ""})命中层级=客户默认 → 模板{clientTemplate.id}{(clientByBook ? "(簿记账户匹配优先)" : "")}");
continue;
}
var globalDefault = PickByBookScopePreference(globalDefaults, trade.AssetId, out var globalByBook);
if (globalDefault != null)
{
result[trade.id] = globalDefault;
logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId})命中层级=全局默认 → 模板{globalDefault.id}{(globalByBook ? "(簿记账户匹配优先)" : "")}");
}
else
{
logger.Info($"【警告】预付金模板取数:交易{trade.id}(客户{trade.ClientId})三层级(交易绑定/客户默认/全局默认)均未命中有效模板");
}
}
return result;
}
/// <summary>
/// 同层级多候选并存时的取舍(2026-08-28 裁定,纯函数单测覆盖):
/// 入参候选序须已按生效日最新在前(客户层=绑定生效日,全局层=模板生效日,同日按 id 最新)。
/// 1.存在"簿记条件满足"的候选(BookIds 显式限定且经 IsApplicableToBook 命中当前交易簿记账户)→ 优先取之,
/// 多个命中时仍按传入顺序取生效日最新;
/// 2.否则取顺序第一个(生效日最新)。
/// 背景:双全局默认并存(如 无预付金限定簿记 + 区间追保通配)时,原"先按生效日/id 取最新再验簿记"
/// 使通配模板恒胜、限定簿记模板永不生效(交易2572/2573 实证)。
/// preferredByBookScope=true 表示本次由簿记匹配压过了顺序更前的通配候选(供日志辨识)。
/// </summary>
public static margin_template_v2 PickByBookScopePreference(List<margin_template_v2> candidatesByLatestFirst, int? tradeAssetId, out bool preferredByBookScope)
{
preferredByBookScope = false;
if (candidatesByLatestFirst == null || candidatesByLatestFirst.Count == 0)
{
return null;
}
var applicable = candidatesByLatestFirst.Where(x => x.IsApplicableToBook(tradeAssetId)).ToList();
var bookMatched = applicable.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.BookIds));
if (bookMatched == null)
{
return applicable.FirstOrDefault();
}
preferredByBookScope = applicable[0].id != bookMatched.id;
return bookMatched;
}
/// <summary>
/// 标的细分分类判定钩子(ETF 子类,2026-08-24 实装):基金类标的返回标的维护页维护的
/// underlying_manager.EtfSubType(取值来自数据字典"ETF 子类":国债 ETF/政金债 ETF/…/科创债 ETF/可转债 ETF
/// 基金类必填);非基金或未维护返回 null → 走标志位匹配/通配行兜底(债券/股票等行为与现状一致)。
/// 后续其他细分(中债指数 IndexKind 等)在本方法内扩展。
/// </summary>
public static string GetUnderlyingCategory(string underlyingCode, string underlyingInstrumentType)
{
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>
/// 明细率值换算:页面上 vue-number-input 以 % 展示,数据库存的就是小数(如 0.05 表示 5%),直接取用
/// </summary>
private static decimal? ToDecimalRate(double? ratio)
{
return ratio.HasValue ? (decimal)ratio.Value : (decimal?)null;
}
}
}