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