170 lines
7.7 KiB
C#
170 lines
7.7 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using YLErp.BLL;
|
||
using YLErp.DBModels;
|
||
using YLErp.Enums;
|
||
using YLErp.Modules.UnderlyingModule;
|
||
|
||
namespace YLErp.Modules.MarginModule
|
||
{
|
||
/// <summary>
|
||
/// 预付金模板V2 取数帮助类:按交易绑定的模板(trade_margin_template → margin_template_v2 → margin_template_detail)
|
||
/// 取 初始预付金率x / 维持预付金率y,供交易确认书、预付金计算等消费点统一使用。
|
||
/// 匹配维度:生效日期(ValueDate 最新)→ 利率债期限档(SpanConfig.BondTerm,四档,空=全部兜底)→ 标的资产类型(UnderlyingType 标志位)。
|
||
/// </summary>
|
||
public static class MarginTemplateV2RateHelper
|
||
{
|
||
/// <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; }
|
||
}
|
||
|
||
/// <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,供批量场景复用连接)。
|
||
/// </summary>
|
||
public static MarginRateResult GetTradeMarginRate(int tradeId, string underlyingCode, string underlyingInstrumentType, DateTime valueDate, YLContext db)
|
||
{
|
||
//1.交易绑定(ValueDate 最新)
|
||
var binding = db.trade_margin_template.AsNoTracking()
|
||
.Where(x => x.TradeId == tradeId && x.ValueDate <= valueDate)
|
||
.OrderByDescending(x => x.ValueDate)
|
||
.FirstOrDefault();
|
||
if (binding == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var template = db.margin_template_v2.AsNoTracking().FirstOrDefault(x => x.id == binding.MarginTemplateId);
|
||
if (template == null || !template.IsValid)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
//2.无预付金规则:率直接为 0
|
||
if (template.RuleType == (int)MarginRuleTypeEnum.无预付金)
|
||
{
|
||
return new MarginRateResult { Template = template, InitRate = 0m, MaintainRate = 0m };
|
||
}
|
||
|
||
if (template.RuleType != (int)MarginRuleTypeEnum.区间追保结构)
|
||
{
|
||
//其他规则不在本帮助类支持范围,显式返回 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())
|
||
{
|
||
return null;
|
||
}
|
||
var latestValueDate = detailQuery.Max(x => x.ValueDate);
|
||
var details = detailQuery.Where(x => x.ValueDate == latestValueDate).ToList();
|
||
|
||
//4.利率债期限档匹配:精确档 → "全部"(BondTerm 为空)兜底
|
||
var term = UnderlyingHelper.GetApplicableMarginTerm(underlyingCode, valueDate);
|
||
var matched = details.Where(x => x.SpanConfig != null && x.SpanConfig.BondTerm == term).ToList();
|
||
if (!matched.Any())
|
||
{
|
||
matched = details.Where(x => x.SpanConfig == null || string.IsNullOrEmpty(x.SpanConfig.BondTerm)).ToList();
|
||
}
|
||
if (!matched.Any())
|
||
{
|
||
return null;
|
||
}
|
||
|
||
//5.标的资产类型匹配(仅当模板选了"按资产类型分类"):精确标志位 → 通配行(None/All)兜底
|
||
//标的细分分类判定钩子:本期默认返回 null → 走下方原有标志位匹配/通配行兜底,行为与现状一致;
|
||
//后续需求(转债ETF/科创债ETF/中债指数等细分)实现判定规则后,此处按 (BondTerm, EtfKind) 元组匹配明细行。
|
||
var underlyingCategory = GetUnderlyingCategory(underlyingCode, underlyingInstrumentType);
|
||
if (underlyingCategory != null)
|
||
{
|
||
//TODO: 后续需求实现:按 (SpanConfig.BondTerm, SpanConfig.EtfKind) 元组匹配明细行,替代/补充下方标志位匹配
|
||
}
|
||
|
||
if (template.UnderlyingSeperateType == (int)UnderlyingSeperateTypeEnum.CustomInstrumentType
|
||
&& Enum.TryParse<UnderlyingTypeEnum>(underlyingInstrumentType, out var instrumentFlag))
|
||
{
|
||
var byInstrument = matched.Where(x => (x.UnderlyingType & instrumentFlag) > 0).ToList();
|
||
if (byInstrument.Any())
|
||
{
|
||
matched = byInstrument;
|
||
}
|
||
else
|
||
{
|
||
var wildcard = matched.Where(x => x.UnderlyingType == UnderlyingTypeEnum.None || x.UnderlyingType == UnderlyingTypeEnum.All).ToList();
|
||
if (wildcard.Any())
|
||
{
|
||
matched = wildcard;
|
||
}
|
||
}
|
||
}
|
||
|
||
var detail = matched.First();
|
||
return new MarginRateResult
|
||
{
|
||
Template = template,
|
||
Detail = detail,
|
||
InitRate = ToDecimalRate(detail.MarginRatio1),
|
||
MaintainRate = ToDecimalRate(detail.MarginRatio2)
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 标的细分分类判定钩子(转债ETF/科创债ETF/中债指数等)。
|
||
/// 本期默认返回 null → 走通配行兜底,行为与现状一致;后续需求按业务给的判定规则(代码段/标的维护字段)实现。
|
||
/// </summary>
|
||
private static string GetUnderlyingCategory(string underlyingCode, string underlyingInstrumentType)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 明细率值换算:页面上 vue-number-input 以 % 展示,数据库存的就是小数(如 0.05 表示 5%),直接取用
|
||
/// </summary>
|
||
private static decimal? ToDecimalRate(double? ratio)
|
||
{
|
||
return ratio.HasValue ? (decimal)ratio.Value : (decimal?)null;
|
||
}
|
||
}
|
||
}
|