feat(greeks): 新增 risk-factor bump 计算与注册
ParameterBase.Clone() 保留运行时类型深拷贝; ValueCalculator 两个薄接入方法; GreeksBumpCalculator/GreeksRiskFactor 引擎。加法性重定价桥,不动现有定价输出。
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
using YLErp.BLL.Calculation.V2;
|
||||
|
||||
namespace UnitTestProject.Modules.SwapModule
|
||||
{
|
||||
/// <summary>
|
||||
/// GreeksBumpCalculator 纯数学契约测试(先写,锁定 ε / 差分逻辑)。
|
||||
/// 不碰 DB / QDP:用已知解析导数的函数(f(x)=x²,d/dx=2x,d²/dx²=2)验证中心差分本身正确。
|
||||
/// 业务侧集成测试(真实定价路径 + 真实 FR007)见后续 ValueCalculator 薄接入落地后再补。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class GreeksBumpCalculatorTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 一阶中心差分对 f(x)=x² 在 x=3 应精确等于 2x=6。
|
||||
/// 二次多项式的中心差分对任意 ε 都精确(截断误差为 0),因此用相对步长 0.05% 仍得 6。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void CentralDelta_of_x2_equals_2x()
|
||||
{
|
||||
var calc = new GreeksBumpCalculator();
|
||||
Func<decimal, decimal> f = x => x * x; // d/dx = 2x
|
||||
var delta = calc.Delta(f, 3m, BumpSpec.Relative(0.0005m));
|
||||
Assert.AreEqual(6m, delta);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 二阶中心差分对 f(x)=x² 在 x=3 应精确等于 2(二阶导数恒为 2)。
|
||||
/// 验证 Gamma 的二阶差分算子正确。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void CentralGamma_of_x2_equals_2()
|
||||
{
|
||||
var calc = new GreeksBumpCalculator();
|
||||
Func<decimal, decimal> f = x => x * x; // d²/dx² = 2
|
||||
var gamma = calc.Gamma(f, 3m, BumpSpec.Relative(0.0005m));
|
||||
Assert.AreEqual(2m, gamma);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 1BP 变体(Dollar Greek)对 f(x)=x² 在 x=3、bump 1bp 应等于 (3.0001)² - 3²。
|
||||
/// 验证 BumpPv1Bp 直接前向 bump 的 PV 差逻辑。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void BumpPv1Bp_of_x2_forward()
|
||||
{
|
||||
var calc = new GreeksBumpCalculator();
|
||||
Func<decimal, decimal> f = x => x * x;
|
||||
var bumped = calc.BumpPv1Bp(f, 3m);
|
||||
Assert.AreEqual(3.0001m * 3.0001m - 3m * 3m, bumped);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 线性函数 f(x)=2x+1:一阶中心差分应精确等于斜率 2,二阶中心差分应精确为 0(线性无曲率)。
|
||||
/// 验证算子对"一次/零次"函数的精确性(二次之外另一种精确情形)。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void CentralDelta_of_linear_is_slope()
|
||||
{
|
||||
var calc = new GreeksBumpCalculator();
|
||||
Func<decimal, decimal> f = x => 2m * x + 1m; // d/dx = 2, d²/dx² = 0
|
||||
Assert.AreEqual(2m, calc.Delta(f, 5m, BumpSpec.Relative(0.0005m)));
|
||||
Assert.AreEqual(0m, calc.Gamma(f, 5m, BumpSpec.Relative(0.0005m)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三次函数 f(x)=x³:中心差分对任意 ε 不精确(仅二次及以下精确),结果逼近解析导 3x² 但有 O(ε²) 误差。
|
||||
/// 验证 Layer B 集成测试必须带容差,不能 Assert.AreEqual 死等精确值。x=2 解析导=12。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void CentralDelta_of_cubic_is_approx_analytic_with_tolerance()
|
||||
{
|
||||
var calc = new GreeksBumpCalculator();
|
||||
Func<decimal, decimal> f = x => x * x * x; // d/dx = 3x² = 12 at x=2
|
||||
var delta = calc.Delta(f, 2m, BumpSpec.Relative(0.0005m));
|
||||
Assert.IsTrue(Math.Abs(delta - 12m) < 0.001m, $"中心差分三次函数应有 O(ε²) 误差,实际={delta}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扭结点测试:看涨 payoff f(x)=max(x-3,0) 在行权价 x=3 处不可导。
|
||||
/// 中心差分跨扭结取到左右斜率的平均 (0+1)/2 = 0.5,说明对障碍/美式等扭结结构必须用单边差分。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void CentralDelta_at_kink_is_average_of_one_sided()
|
||||
{
|
||||
var calc = new GreeksBumpCalculator();
|
||||
Func<decimal, decimal> f = x => x > 3m ? x - 3m : 0m; // call payoff K=3
|
||||
var delta = calc.Delta(f, 3m, BumpSpec.Relative(0.0005m));
|
||||
Assert.AreEqual(0.5m, delta); // 中心差分给出左右斜率平均,非真实单边 Greek
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 边界 x=0(相对步长会触到 Floor):f(x)=x² 在 0 处对称,Δ 应精确为 0,证明 Floor 兜底不产生噪声。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void CentralDelta_at_zero_uses_floor_but_stays_correct()
|
||||
{
|
||||
var calc = new GreeksBumpCalculator();
|
||||
Func<decimal, decimal> f = x => x * x;
|
||||
Assert.AreEqual(0m, calc.Delta(f, 0m, BumpSpec.Relative(0.0005m)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 利率量级小值 x=0.0001(1bp 量级)用相对步长:Resolve 返回 max(0.0001·0.0005, 1e-8)=5e-8,
|
||||
/// 远大于 Floor,证明利率类小量级不会被舍入噪声吞掉。f=x² 在 0.0001 解析导=2·0.0001=0.0002。
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void CentralDelta_of_tiny_rate_like_x_is_stable()
|
||||
{
|
||||
var calc = new GreeksBumpCalculator();
|
||||
Func<decimal, decimal> f = x => x * x;
|
||||
Assert.AreEqual(0.0002m, calc.Delta(f, 0.0001m, BumpSpec.Relative(0.0005m)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using YLErp.BLL.Calculation.V2;
|
||||
using YLErp.BLL.Calculation.V2.Parameter;
|
||||
|
||||
namespace UnitTestProject.Modules.SwapModule
|
||||
{
|
||||
/// <summary>
|
||||
/// RiskFactor(② 风险因子抽象)+ ParameterBase.Clone 的纯单测(Layer A,无 DB / QDP)。
|
||||
/// 用假 reprice 委托验证:克隆类型保持、字典深拷、三类因子落点正确、波动率因子施于非期权参数抛异常,
|
||||
/// 以及经 BuildPvFunction 喂入 GreeksBumpCalculator 后 DeltaR / Delta / Vega / BumpPv1Bp 数值正确(线性函数精确)。
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class GreeksRiskFactorTests
|
||||
{
|
||||
// —— Clone 行为与类型保持 ——
|
||||
|
||||
[TestMethod]
|
||||
public void Clone_PreservesRuntimeType_And_CopiesOptionFields()
|
||||
{
|
||||
var src = new VanillaOptionParameter
|
||||
{
|
||||
Volatility = 0.2,
|
||||
RiskFreeRate = 0.03,
|
||||
SpotPrices = new Dictionary<string, double> { { "X", 100 } }
|
||||
};
|
||||
ParameterBase clone = src.Clone();
|
||||
// MemberwiseClone 必须保留运行时类型,否则 ValueCalculator 内 parameter as VanillaOptionParameter 会 cast 成 null
|
||||
Assert.IsInstanceOfType(clone, typeof(VanillaOptionParameter));
|
||||
Assert.AreEqual(0.2, ((BaseOptionParameter)clone).Volatility);
|
||||
Assert.AreEqual(100, clone.SpotPrices["X"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Clone_DeepCopiesSpotPrices_So_Bump_Does_Not_Pollute_Original()
|
||||
{
|
||||
var src = new ParameterBase
|
||||
{
|
||||
SpotPrices = new Dictionary<string, double> { { "X", 100 } }
|
||||
};
|
||||
ParameterBase clone = src.Clone();
|
||||
clone.SpotPrices["X"] = 999; // 改克隆体
|
||||
Assert.AreEqual(100, src.SpotPrices["X"], "原参数的 SpotPrices 不应被克隆体的 bump 污染");
|
||||
}
|
||||
|
||||
// —— 三类因子 ApplyTo 落点正确 ——
|
||||
|
||||
[TestMethod]
|
||||
public void RateFactor_ApplyTo_Sets_RiskFreeRate()
|
||||
{
|
||||
var p = new ParameterBase();
|
||||
RiskFactor.Rate("CNY-OIS-2Y").ApplyTo(p, 0.025m);
|
||||
Assert.AreEqual(0.025, p.RiskFreeRate);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PriceFactor_ApplyTo_Sets_SpotPrices_By_TargetKey()
|
||||
{
|
||||
var p = new ParameterBase();
|
||||
RiskFactor.Price("000300.SH").ApplyTo(p, 3500m);
|
||||
Assert.AreEqual(3500, p.SpotPrices["000300.SH"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VolFactor_ApplyTo_On_OptionParameter_Sets_Volatility()
|
||||
{
|
||||
var p = new BaseOptionParameter();
|
||||
RiskFactor.Volatility("X").ApplyTo(p, 0.18m);
|
||||
Assert.AreEqual(0.18, p.Volatility);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(System.InvalidOperationException))]
|
||||
public void VolFactor_ApplyTo_On_PlainParameter_Throws()
|
||||
{
|
||||
// 波动率不在 ParameterBase 基类上,只能施于期权参数
|
||||
RiskFactor.Volatility("X").ApplyTo(new ParameterBase(), 0.1m);
|
||||
}
|
||||
|
||||
// —— 端到端:假 reprice 验证 中心差分 / 1bp 数值正确(线性函数精确) ——
|
||||
|
||||
[TestMethod]
|
||||
public void RateFactor_Through_Engine_DeltaR_Equals_Slope()
|
||||
{
|
||||
var baseParam = new ParameterBase { RiskFreeRate = 0.03 };
|
||||
Func<ParameterBase, decimal> reprice = p => (decimal)((p.RiskFreeRate ?? 0) * 1000); // PV = 1000 * r
|
||||
var factor = RiskFactor.Rate("r"); // 标准步长:绝对 1bp
|
||||
Func<decimal, decimal> pv = factor.BuildPvFunction(reprice, baseParam);
|
||||
|
||||
var calc = new GreeksBumpCalculator();
|
||||
decimal deltaR = calc.DeltaR(pv, 0.03m, factor.Shift); // 中心差分对线性函数精确
|
||||
Assert.AreEqual(1000m, deltaR, 1e-4m);
|
||||
|
||||
decimal bump1bp = calc.BumpPv1Bp(pv, 0.03m); // 前向 1bp PV 差
|
||||
Assert.AreEqual(1000m * 0.0001m, bump1bp, 1e-9m);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PriceFactor_Through_Engine_Delta_Equals_One()
|
||||
{
|
||||
var baseParam = new ParameterBase
|
||||
{
|
||||
SpotPrices = new Dictionary<string, double> { { "X", 100 } }
|
||||
};
|
||||
Func<ParameterBase, decimal> reprice = p => (decimal)p.SpotPrices["X"]; // PV = S
|
||||
var factor = RiskFactor.Price("X"); // 标准步长:相对 1% → ε = 1
|
||||
Func<decimal, decimal> pv = factor.BuildPvFunction(reprice, baseParam);
|
||||
|
||||
var calc = new GreeksBumpCalculator();
|
||||
decimal delta = calc.Delta(pv, 100m, factor.Shift); // (101 - 99) / 2 = 1 精确
|
||||
Assert.AreEqual(1m, delta, 1e-6m);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VolFactor_Through_Engine_Vega_Equals_Slope()
|
||||
{
|
||||
var baseParam = new BaseOptionParameter { Volatility = 0.2 };
|
||||
Func<ParameterBase, decimal> reprice = p => (decimal)(((BaseOptionParameter)p).Volatility ?? 0) * 50; // PV = 50 * σ
|
||||
var factor = RiskFactor.Volatility("X"); // 标准步长:绝对 1bp vol
|
||||
Func<decimal, decimal> pv = factor.BuildPvFunction(reprice, baseParam);
|
||||
|
||||
var calc = new GreeksBumpCalculator();
|
||||
decimal vega = calc.Vega(pv, 0.2m, factor.Shift); // 中心差分对线性函数精确
|
||||
Assert.AreEqual(50m, vega, 1e-4m);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
|
||||
namespace YLErp.BLL.Calculation.V2
|
||||
{
|
||||
/// <summary>
|
||||
/// 风险因子种类,用于后续 ValueCalculator 薄接入层签名(CalcPvAtBumpedRiskFactor)。
|
||||
/// 当前 GreeksBumpCalculator 纯类本身不依赖它,仅作为对外契约的一部分。
|
||||
/// </summary>
|
||||
public enum RiskFactorKind
|
||||
{
|
||||
/// <summary>标的价 S</summary>
|
||||
Price,
|
||||
/// <summary>无风险利率 r</summary>
|
||||
Rate,
|
||||
/// <summary>波动率 σ</summary>
|
||||
Volatility
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扰动步长规格:决定有限差分用的 ε。
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Relative:ε = max(|x| * Value, Floor),适合指数等大量级标的,避免绝对 1bp 落入浮点舍入区。</description></item>
|
||||
/// <item><description>Absolute:ε = Value,等价系统现行“标的价格偏移绝对 1bp”的做法。</description></item>
|
||||
/// </list>
|
||||
/// 默认 Floor 极小,仅防止 x=0 时 ε=0 导致除零。
|
||||
/// </summary>
|
||||
public readonly struct BumpSpec
|
||||
{
|
||||
/// <summary>步长模式</summary>
|
||||
public enum Mode
|
||||
{
|
||||
/// <summary>相对步长(推荐,量级自适应)</summary>
|
||||
Relative,
|
||||
/// <summary>绝对步长</summary>
|
||||
Absolute
|
||||
}
|
||||
|
||||
/// <summary>步长模式</summary>
|
||||
public Mode Kind { get; }
|
||||
|
||||
/// <summary>步长数值(Relative 时为相对比例,Absolute 时为绝对量)</summary>
|
||||
public decimal Value { get; }
|
||||
|
||||
/// <summary>相对步长下限(防止 |x| 过小导致 ε→0)</summary>
|
||||
public decimal Floor { get; }
|
||||
|
||||
/// <summary>构造步长规格</summary>
|
||||
public BumpSpec(Mode kind, decimal value, decimal floor = 0.00000001m)
|
||||
{
|
||||
Kind = kind;
|
||||
Value = value;
|
||||
Floor = floor;
|
||||
}
|
||||
|
||||
/// <summary>相对步长(value 为相对比例,如 0.0005m = 0.05%)</summary>
|
||||
public static BumpSpec Relative(decimal value) => new(Mode.Relative, value);
|
||||
|
||||
/// <summary>绝对步长(value 为绝对量,如 0.0001m = 1bp)</summary>
|
||||
public static BumpSpec Absolute(decimal value) => new(Mode.Absolute, value);
|
||||
|
||||
/// <summary>把规格解析成实际 ε(用 decimal,避免 double 精度漂移)</summary>
|
||||
public decimal Resolve(decimal x)
|
||||
{
|
||||
if (Kind == Mode.Absolute) return Value;
|
||||
var eps = Math.Abs(x) * Value;
|
||||
return eps < Floor ? Floor : eps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 有限差分希腊字母计算引擎(纯函数,不依赖 QDP / DB)。
|
||||
/// <para>
|
||||
/// 通过委托 <c>pv(bumpedFactor)</c> 取得“风险因子被扰动到某值时的衍生品价值”,
|
||||
/// 再用中心差分估算一阶 / 二阶导。这是仓库层对 Greeks 口径的显式控制点,
|
||||
/// 可绕开 QDP 内部黑盒的步长 / 差分选择,并解决指数类标的使用绝对 1bp 步长失真的口径问题。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 用法:业务侧把“价格 / 利率 / 波动率”各自封装成一个 <c>Func<decimal,decimal></c> 委托传给本类;
|
||||
/// Delta/Gamma/Vega/Rho 等命名方法数学上都是同一差分算子,仅扰动的风险因子不同。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class GreeksBumpCalculator
|
||||
{
|
||||
/// <summary>一阶中心差分 Δ = [PV(x+ε) - PV(x-ε)] / (2ε),误差 O(ε²)</summary>
|
||||
public decimal FirstOrderCentral(Func<decimal, decimal> pv, decimal x, BumpSpec bump)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pv);
|
||||
var eps = bump.Resolve(x);
|
||||
return (pv(x + eps) - pv(x - eps)) / (2m * eps);
|
||||
}
|
||||
|
||||
/// <summary>二阶中心差分 Γ = [PV(x+ε) - 2·PV(x) + PV(x-ε)] / ε²,误差 O(ε²)</summary>
|
||||
public decimal SecondOrderCentral(Func<decimal, decimal> pv, decimal x, BumpSpec bump)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pv);
|
||||
var eps = bump.Resolve(x);
|
||||
return (pv(x + eps) - 2m * pv(x) + pv(x - eps)) / (eps * eps);
|
||||
}
|
||||
|
||||
// —— 以下为按业务希腊字母命名的暴露,数学上都是上面两个差分算子,仅扰动因子不同 ——
|
||||
|
||||
/// <summary>Delta:对标的价 S 的一阶中心差分</summary>
|
||||
public decimal Delta(Func<decimal, decimal> pv, decimal s, BumpSpec bump) => FirstOrderCentral(pv, s, bump);
|
||||
|
||||
/// <summary>Gamma:对标的价 S 的二阶中心差分</summary>
|
||||
public decimal Gamma(Func<decimal, decimal> pv, decimal s, BumpSpec bump) => SecondOrderCentral(pv, s, bump);
|
||||
|
||||
/// <summary>Vega:对波动率 σ 的一阶中心差分</summary>
|
||||
public decimal Vega(Func<decimal, decimal> pv, decimal sigma, BumpSpec bump) => FirstOrderCentral(pv, sigma, bump);
|
||||
|
||||
/// <summary>Vega_r:对波动率 σ 的一阶中心差分(需求命名变体,等价于 Vega)</summary>
|
||||
public decimal VegaR(Func<decimal, decimal> pv, decimal sigma, BumpSpec bump) => FirstOrderCentral(pv, sigma, bump);
|
||||
|
||||
/// <summary>Rho:对无风险利率 r 的一阶中心差分</summary>
|
||||
public decimal Rho(Func<decimal, decimal> pv, decimal r, BumpSpec bump) => FirstOrderCentral(pv, r, bump);
|
||||
|
||||
/// <summary>Delta_r:对无风险利率 r 的一阶中心差分(需求命名变体,等价于 Rho)</summary>
|
||||
public decimal DeltaR(Func<decimal, decimal> pv, decimal r, BumpSpec bump) => FirstOrderCentral(pv, r, bump);
|
||||
|
||||
/// <summary>Gamma_r:对无风险利率 r 的二阶中心差分</summary>
|
||||
public decimal GammaR(Func<decimal, decimal> pv, decimal r, BumpSpec bump) => SecondOrderCentral(pv, r, bump);
|
||||
|
||||
/// <summary>
|
||||
/// 1BP 变体(Dollar Greek):直接前向 bump 1bp 的 PV 差 = PV(x+1bp) - PV(x),不除 ε,量纲为金额。
|
||||
/// 对应需求中的 Delta_r(1BP) / Gamma_r(1BP) / Vega_r(1BP)。
|
||||
/// </summary>
|
||||
public decimal BumpPv1Bp(Func<decimal, decimal> pv, decimal x, decimal oneBp = 0.0001m)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pv);
|
||||
return pv(x + oneBp) - pv(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
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 为 UnderlyingCode;Rate 为曲线/期限标识(如 "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 → RiskFreeRate;Price → 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,22 @@ namespace YLErp.BLL.Calculation.V2.Parameter
|
||||
public bool HasNightMarket { get; set; }
|
||||
public bool PreciseTimeMode { get; set; }
|
||||
public int maturityShift { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 深拷贝(保留运行时类型)。
|
||||
/// <para>
|
||||
/// 用 MemberwiseClone 保证克隆对象与 <c>this</c> 运行时类型一致——
|
||||
/// 例如 <c>VanillaOptionParameter</c> 克隆后仍是 <c>VanillaOptionParameter</c>,
|
||||
/// 否则 ValueCalculator 内 <c>parameter as VanillaOptionParameter</c> 会因类型退化为基类而得到 null。
|
||||
/// 引用型字段 SpotPrices/Dividends 单独深拷,避免对克隆体 bump 时污染原参数。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public virtual ParameterBase Clone()
|
||||
{
|
||||
var clone = (ParameterBase)MemberwiseClone();
|
||||
clone.SpotPrices = SpotPrices == null ? null : new Dictionary<string, double>(SpotPrices);
|
||||
clone.Dividends = Dividends == null ? null : new Dictionary<Date, double>(Dividends);
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2300,6 +2300,38 @@ namespace YLErp.BLL.Calculation.V2
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
#region Greeks 薄接入层(③ 重定价桥,加法性,不改现有定价输出)
|
||||
|
||||
/// <summary>
|
||||
/// 把某风险因子扰动到指定值后重定价,返回 PV(decimal)。
|
||||
/// 克隆参数后施加扰动,不污染入参;现有 CalculateTradeValue 输出一行不变。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 这是把 GreeksBumpCalculator(有限差分引擎)接上真实定价路径的唯一接入点:
|
||||
/// 业务侧拿到返回的 PV 后,配合 <see cref="RiskFactor"/> 自带的标准步长,
|
||||
/// 用 GreeksBumpCalculator.DeltaR/Delta/GammaR/VegaR 等即可算统一的希腊字母。
|
||||
/// </remarks>
|
||||
public static decimal CalcPvAtBumpedRiskFactor(
|
||||
string userId, trade t, underlying_manager u,
|
||||
RiskFactor factor, decimal bumpedValue, ParameterBase baseParam)
|
||||
{
|
||||
if (factor == null) throw new ArgumentNullException(nameof(factor));
|
||||
var p = baseParam.Clone();
|
||||
factor.ApplyTo(p, bumpedValue);
|
||||
return (decimal)CalculateTradeValue(userId, t, u, p).Pv;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造"PV 作为某风险因子值的函数"委托,供 GreeksBumpCalculator 中心差分使用。
|
||||
/// 内部用真实 CalculateTradeValue 重定价;因子类别自带 FRTB SBA 标准步长(见 RiskFactor 工厂方法)。
|
||||
/// </summary>
|
||||
public static Func<decimal, decimal> PvAsFunctionOf(
|
||||
string userId, trade t, underlying_manager u,
|
||||
RiskFactor factor, ParameterBase baseParam)
|
||||
=> factor.BuildPvFunction(p => (decimal)CalculateTradeValue(userId, t, u, p).Pv, baseParam);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
class OptionMarketObjectName
|
||||
|
||||
Reference in New Issue
Block a user