using YLErp.DBModels;
using YLErp.DBModels.Enums;
namespace YLErp.Modules.SwapModule.Margin
{
///
/// R2 区间追保结构(规则15)维持保证金纯函数计算(实现方案阶段三 §3.1)。
/// 数据来源约定(与阶段一 SpanConfig 结构、docx 确认书模板一致):
/// - 多头(客户看多):第1层 [Lower, +∞)(上不封顶),第n层 [Lower, Upper);各层 Lower 逐层严格递减;
/// - 空头(客户看空):第1层 (−∞, Upper](下不设限),第n层 (Lower, Upper];各层 Upper 逐层严格递增;
/// - 区间边界为"×期初净价的百分比"小数(0.95=95%),价格口径:债券用净价,指数/ETF 用收盘价;
/// - AmountRate 为累计到该层的追保金额比例(与确认书追保表每行"合计追保金额"同口径,直取不求和),
/// 追保金额 = AmountRate × 期初全价 × 券面总额(债券)/ × 期初价格 × 名义份额(指数/ETF);
/// - 维持保证金 = (初始保证金 + 总追加保证金) × 方向(我方净收取 +1 / 净支付 −1);
/// - 价格跌破最深一层(平仓线外)按最深层计(追保金额不再上升)。
/// 公式参数化(EQD-6948 待业务校验),所有口径集中在本类便于校验后调整。
///
public static class SwapSpanMarginCalc
{
///
/// 判断 SpanConfig 是否已按方案B录入新区间结构(任一方向有边界/比例或预警/平仓线)。
/// 全空视为存量 x/y 配置,引擎回落旧 名义×y 公式。
///
public static bool HasSpanConfig(SpanConfig cfg)
{
if (cfg == null)
{
return false;
}
return cfg.WarnLine.HasValue || cfg.CloseLine.HasValue
|| HasTierValue(cfg.LongSpans) || HasTierValue(cfg.ShortSpans);
}
private static bool HasTierValue(List tiers)
{
return tiers != null && tiers.Any(t => t != null && (t.Lower.HasValue || t.Upper.HasValue || t.AmountRate.HasValue));
}
///
/// 客户方向判定(与确认书 IsCustomerLong 同规则):
/// 我方方向 = 收取端(PosiDirection=收取)与 PositionType 同向、支付端反向;客户方向取反。
///
public static bool IsCustomerLong(int posiDirection, int positionType)
{
var isOurLong = posiDirection == (int)SwapDirectionEnum.支付
? positionType == (int)PositionTypeFlag.Short
: positionType == (int)PositionTypeFlag.Long;
return !isOurLong;
}
///
/// 按当前价格相对期初价的比例落档。返回命中的层;价格在所有层区间之外(平仓线外)返回最深一层,无可用层返回 null。
/// 多头:priceRatio ∈ [Lower, Upper)(第1层无上界);空头:priceRatio ∈ (Lower, Upper](第1层无下界)。
///
public static SpanTierConfig MatchTier(List tiers, bool isCustomerLong, double priceRatio)
{
if (tiers == null)
{
return null;
}
var valid = tiers.Where(t => t != null && (t.Lower.HasValue || t.Upper.HasValue)).ToList();
if (valid.Count == 0)
{
return null;
}
foreach (var tier in valid)
{
var inLower = !tier.Lower.HasValue || (isCustomerLong ? priceRatio >= tier.Lower.Value : priceRatio > tier.Lower.Value);
var inUpper = !tier.Upper.HasValue || (isCustomerLong ? priceRatio < tier.Upper.Value : priceRatio <= tier.Upper.Value);
if (inLower && inUpper)
{
return tier;
}
}
//未落任何层:价格已穿出最深一层边界(低于多头最深层下界/高于空头最深层上界),按最深层计;
//最深层按边界值取(多头=最小下界、空头=最大上界),不依赖配置数组顺序(BUG-25 引擎侧防御)
return isCustomerLong
? valid.OrderBy(t => t.Lower ?? double.MinValue).First()
: valid.OrderByDescending(t => t.Upper ?? double.MaxValue).First();
}
///
/// 总追加保证金 = 命中层 AmountRate × 期初全价 × 券面总额(债券)/ × 期初价格 × 名义份额(指数/ETF)。
/// 数据上两者同为 期初价(PosiGrossPrice) × 数量(PosiQuantity),口径差异由调用方注释说明。
///
public static double CalcAdditionalMargin(SpanTierConfig tier, double initPrice, double quantity)
{
if (tier == null || !tier.AmountRate.HasValue)
{
return 0;
}
return tier.AmountRate.Value * initPrice * quantity;
}
///
/// 维持保证金 = (初始保证金 + 总追加保证金) × 方向(我方净收取 +1 / 净支付 −1)。
///
public static double CalcMaintenanceMargin(double initialMargin, double additionalMargin, double direction)
{
return (initialMargin + additionalMargin) * direction;
}
///
/// 阶段三 §3.1 单笔交易引擎计算(纯函数,收盘价由调用方解析后传入——债券取中债估值净价、指数/ETF取收盘价)。
/// closePrice<=0 视为未取到收盘价:追加保证金按 0、维持保证金=初始保证金(不抛错,由调用方记日志)。
/// isInitialCalc=true(试算初始):只出初始项,追加保证金为收盘后口径不参与。
/// 返回 null 表示缺有效标的腿(期初价),调用方跳过该交易不产出 trade_span。
///
public static double? CalcTradeMaintenanceMargin(double? tradeInitialMargin, SpanConfig spanCfg,
List legs, bool isInitialCalc, double closePrice)
{
//标的腿(多空):期初价格、数量、客户方向
var underlyingLeg = legs?.FirstOrDefault(x => x.PositionType == (int)PositionTypeFlag.Long || x.PositionType == (int)PositionTypeFlag.Short);
if (underlyingLeg == null || underlyingLeg.PosiGrossPrice <= 0)
{
return null;
}
var isCustomerLong = IsCustomerLong(underlyingLeg.PosiDirection, underlyingLeg.PositionType);
//初始保证金与方向:初始预付金腿(InterestMode=5)收付净额(多腿按净收取定方向);无腿时回落交易录入值(客户应付常态)
var initMarginLegs = legs.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金).ToList();
double initialMargin;
double direction;
if (initMarginLegs.Any())
{
var netReceive = initMarginLegs.Sum(x => x.InterestDirection == (int)SwapDirectionEnum.收取 ? x.InterestPrincipalFix : -x.InterestPrincipalFix);
initialMargin = Math.Abs((double)netReceive);
direction = netReceive >= 0 ? 1 : -1;
}
else
{
initialMargin = tradeInitialMargin ?? 0;
direction = 1;
}
if (isInitialCalc)
{
return CalcMaintenanceMargin(initialMargin, 0, direction);
}
var additional = 0.0;
//期初价比基:债券/指数/ETF统一为期初净价(PosiNetNoFeePrice,确认书"参考标的期初净价"同源),缺省回落期初全价
var initNetPrice = (double)(underlyingLeg.PosiNetNoFeePrice ?? 0m);
if (initNetPrice <= 0)
{
initNetPrice = (double)underlyingLeg.PosiGrossPrice;
}
if (closePrice > 0 && initNetPrice > 0)
{
var tiers = isCustomerLong ? spanCfg.LongSpans : spanCfg.ShortSpans;
var tier = MatchTier(tiers, isCustomerLong, closePrice / initNetPrice);
//追保金额基数:期初全价×券面总额(债券)/ 期初价格×名义份额(指数/ETF),同为 期初价×数量
additional = CalcAdditionalMargin(tier, (double)underlyingLeg.PosiGrossPrice, (double)underlyingLeg.PosiQuantity);
}
return CalcMaintenanceMargin(initialMargin, additional, direction);
}
}
}