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

128 lines
5.5 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.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);
}
}
}