Files
zszq-trs/YLErpDAL/BLL/Calculation/V2/GreeksRiskFactor.cs
T
hjhan 985162242d feat(greeks): 新增 risk-factor bump 计算与注册
ParameterBase.Clone() 保留运行时类型深拷贝; ValueCalculator 两个薄接入方法; GreeksBumpCalculator/GreeksRiskFactor 引擎。加法性重定价桥,不动现有定价输出。
2026-07-21 15:28:15 +08:00

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