using System; using System.Collections.Generic; using YLErp.BLL.Calculation.V2.Parameter; namespace YLErp.BLL.Calculation.V2 { /// /// 命名风险因子——业界"按因子建模、而非按资产类别建模"的核心抽象。 /// /// 任意衍生品都可视为 PV = Pricer(MarketData)。希腊字母 = 把某个命名风险因子 /// 在 MarketData 上偏移一个标准步长、重定价、再差分。股票/债券指数/债券收益率期权 /// 只是依赖不同的因子(spot / 收益率曲线 / 波动率面),天然被同一套引擎覆盖, /// 无需为每种资产类别各写一套 Greek 代码。这是 OpenGamma/Strata、QuantLib、FRTB SBA 的同源做法。 /// /// /// 每个因子自带:Id、类别()、标准扰动步长()、 /// 作用目标键(TargetKey)。因子与 ParameterBase 的映射由 完成; /// 与定价引擎的桥接由 ValueCalculator 薄接入层(CalcPvAtBumpedRiskFactor / PvAsFunctionOf)提供。 /// /// /// 工厂方法 / / 即"步长注册表": /// 按因子类别给定 FRTB SBA 标准偏移,统一全系统的 ε 与差分口径,解决需求 #2 中 /// "绝对 1bp vs 相对 0.05%"的口径分歧(股指用绝对 1bp 会因量级大落入浮点舍入区,须相对步长)。 /// /// public sealed class RiskFactor { /// 因子唯一标识 public string Id { get; } /// 因子类别(决定施加到 ParameterBase 的哪个字段、用哪种标准步长) public RiskFactorKind Category { get; } /// 标准扰动步长(FRTB SBA 口径,由工厂方法按类别给定) public BumpSpec Shift { get; } /// /// 作用目标键:Price / Volatility 为 UnderlyingCode;Rate 为曲线/期限标识(如 "CNY-OIS-2Y")。 /// public string TargetKey { get; } public RiskFactor(string id, RiskFactorKind category, BumpSpec shift, string targetKey) { Id = id ?? throw new ArgumentNullException(nameof(id)); Category = category; Shift = shift; TargetKey = targetKey ?? string.Empty; } // —— 步长注册表:按因子类别给定 FRTB SBA 标准偏移 —— // 股票 / 指数 spot:相对 1%(指数量级大,绝对 1bp 会落入浮点舍入区,必须相对) // 利率 / FX:绝对 1bp // 波动率面:绝对 1bp vol /// 标的价/指数 spot 因子,默认相对 1% 步长(指数类用此可避免绝对 1bp 失真) public static RiskFactor Price(string underlyingCode, decimal relativeStep = 0.01m) => new($"{underlyingCode}-SPOT", RiskFactorKind.Price, BumpSpec.Relative(relativeStep), underlyingCode); /// 利率/贴现曲线因子,默认绝对 1bp 步长 public static RiskFactor Rate(string curveTenorId, decimal oneBp = 0.0001m) => new($"{curveTenorId}-RATE", RiskFactorKind.Rate, BumpSpec.Absolute(oneBp), curveTenorId); /// 波动率面因子,默认绝对 1bp vol 步长 public static RiskFactor Volatility(string underlyingCode, decimal oneBp = 0.0001m) => new($"{underlyingCode}-VOL", RiskFactorKind.Volatility, BumpSpec.Absolute(oneBp), underlyingCode); /// /// 把因子扰动到 ,施加到参数副本上(不修改入参)。 /// Rate → RiskFreeRate;Price → SpotPrices[TargetKey];Volatility → cast 期权参数设 Volatility。 /// public void ApplyTo(ParameterBase p, decimal value) { if (p == null) throw new ArgumentNullException(nameof(p)); switch (Category) { case RiskFactorKind.Rate: p.RiskFreeRate = (double)value; break; case RiskFactorKind.Price: if (p.SpotPrices == null) p.SpotPrices = new Dictionary(); p.SpotPrices[TargetKey] = (double)value; break; case RiskFactorKind.Volatility: if (p is BaseOptionParameter bo) bo.Volatility = (double)value; else throw new InvalidOperationException( $"波动率风险因子[{Id}]只能施加于期权类参数(BaseOptionParameter),当前为 {p.GetType().Name}"); break; default: throw new ArgumentOutOfRangeException(nameof(Category), Category, "未支持的风险因子类别"); } } /// /// 构造"PV 作为本因子值的函数":克隆 baseParam → 施加扰动值 → 调 reprice 得 PV。 /// /// reprice 生产环境即 ValueCalculator 的真实定价(见 PvAsFunctionOf);测试可注入假函数,无需 DB / QDP。 /// 返回的委托直接喂给 GreeksBumpCalculator 的中心差分方法。 /// /// public Func BuildPvFunction(Func reprice, ParameterBase baseParam) { if (reprice == null) throw new ArgumentNullException(nameof(reprice)); if (baseParam == null) throw new ArgumentNullException(nameof(baseParam)); return bumpedValue => { var p = baseParam.Clone(); ApplyTo(p, bumpedValue); return reprice(p); }; } } }