feat(greeks): 新增 risk-factor bump 计算与注册

ParameterBase.Clone() 保留运行时类型深拷贝; ValueCalculator 两个薄接入方法; GreeksBumpCalculator/GreeksRiskFactor 引擎。加法性重定价桥,不动现有定价输出。
This commit is contained in:
hjhan
2026-07-21 15:28:15 +08:00
parent a179675efd
commit 985162242d
6 changed files with 535 additions and 0 deletions
@@ -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.00011bp 量级)用相对步长: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&lt;decimal,decimal&gt;</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 为 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);
};
}
}
}
@@ -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