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); } } }