diff --git a/UnitTestProject/Modules/SwapModule/InterestLegs/InterestLegStrategyTest.cs b/UnitTestProject/Modules/SwapModule/InterestLegs/InterestLegStrategyTest.cs index 72f14a20..995f3d59 100644 --- a/UnitTestProject/Modules/SwapModule/InterestLegs/InterestLegStrategyTest.cs +++ b/UnitTestProject/Modules/SwapModule/InterestLegs/InterestLegStrategyTest.cs @@ -8,6 +8,7 @@ namespace UnitTestProject.Modules.SwapModule.InterestLegs /// /// 利息腿策略单测。验证每个策略的 CalcNotional 与现有 CalcNotionalByMode switch 完全一致。 /// 这组测试是后续"迁移调用点"的安全网——迁移前后行为必须不变。 + /// 注:原预付金(PrepayLeg)用例已迁出,见 MarginLegTest(保证金独立限界上下文)。 /// [TestClass] public class InterestLegStrategyTest @@ -99,37 +100,6 @@ namespace UnitTestProject.Modules.SwapModule.InterestLegs #endregion - #region 预付金(mode 5/6):按Fix缩放,posiPrincipal=Fix全额 - - [TestMethod] - public void 预付金_初始_部分平仓_本金按Fix缩放() - { - var leg = new PrepayLeg(InterestModeEnum.初始预付金); - var r = leg.CalcNotional(Fix, Notional, LongNotional, ShortNotional, 0.5m); - - Assert.AreEqual(1_000_000m, r.ClosePrincipal, "平仓本金=Fix×50%"); - Assert.AreEqual(Fix, r.PosiPrincipal, "持仓本金=Fix全额"); - Assert.AreEqual(0.5m, r.ClosePercent); - } - - [TestMethod] - public void 预付金_追加_全平_本金等于Fix() - { - var leg = new PrepayLeg(InterestModeEnum.追加预付金); - var r = leg.CalcNotional(Fix, Notional, LongNotional, ShortNotional, 1m); - - Assert.AreEqual(Fix, r.ClosePrincipal, "全平本金=Fix"); - } - - [TestMethod] - public void 预付金_构造非法mode应抛异常() - { - Assert.ThrowsException(() => - new PrepayLeg(InterestModeEnum.固定值)); - } - - #endregion - #region 守卫:策略对应各自mode [TestMethod] @@ -138,8 +108,6 @@ namespace UnitTestProject.Modules.SwapModule.InterestLegs Assert.AreEqual(InterestModeEnum.固定值, new FixedNotionalLeg().Mode); Assert.AreEqual(InterestModeEnum.合约名义本金规模, new ContractNotionalLeg().Mode); Assert.AreEqual(InterestModeEnum.标的期初全价, new UnderlyingFullPriceLeg().Mode); - Assert.AreEqual(InterestModeEnum.初始预付金, new PrepayLeg(InterestModeEnum.初始预付金).Mode); - Assert.AreEqual(InterestModeEnum.追加预付金, new PrepayLeg(InterestModeEnum.追加预付金).Mode); } #endregion @@ -152,8 +120,6 @@ namespace UnitTestProject.Modules.SwapModule.InterestLegs Assert.IsInstanceOfType(InterestLegStrategyFactory.Get(InterestModeEnum.固定值), typeof(FixedNotionalLeg)); Assert.IsInstanceOfType(InterestLegStrategyFactory.Get(InterestModeEnum.合约名义本金规模), typeof(ContractNotionalLeg)); Assert.IsInstanceOfType(InterestLegStrategyFactory.Get(InterestModeEnum.标的期初全价), typeof(UnderlyingFullPriceLeg)); - Assert.IsInstanceOfType(InterestLegStrategyFactory.Get(InterestModeEnum.初始预付金), typeof(PrepayLeg)); - Assert.IsInstanceOfType(InterestLegStrategyFactory.Get(InterestModeEnum.追加预付金), typeof(PrepayLeg)); } [TestMethod] @@ -163,6 +129,11 @@ namespace UnitTestProject.Modules.SwapModule.InterestLegs InterestLegStrategyFactory.Get(InterestModeEnum.持仓名义本金)); Assert.ThrowsException(() => InterestLegStrategyFactory.Get(InterestModeEnum.持仓市值)); + // 预付金 5/6 已不再是利息腿,工厂不再注册,按未注册处理 + Assert.ThrowsException(() => + InterestLegStrategyFactory.Get(InterestModeEnum.初始预付金)); + Assert.ThrowsException(() => + InterestLegStrategyFactory.Get(InterestModeEnum.追加预付金)); } [TestMethod] diff --git a/UnitTestProject/Modules/SwapModule/Margin/MarginLegTest.cs b/UnitTestProject/Modules/SwapModule/Margin/MarginLegTest.cs new file mode 100644 index 00000000..69677e2b --- /dev/null +++ b/UnitTestProject/Modules/SwapModule/Margin/MarginLegTest.cs @@ -0,0 +1,55 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using YLErp.Modules.SwapModule.Margin; + +namespace UnitTestProject.Modules.SwapModule.Margin +{ + /// + /// 保证金利息计提(MarginLeg)单测。验证计息余额计算,以及三种形态解析器可互换产余额。 + /// 替换原 InterestLegStrategyTest 里的预付金(PrepayLeg)用例——预付金已迁出利息腿限界上下文。 + /// + [TestClass] + public class MarginLegTest + { + private const decimal Balance = 2_000_000m; + + [TestMethod] + public void 计息余额_默认不缩放_等于保证金全额() + { + var leg = new MarginLeg(); + var r = leg.CalcBalance(Balance); + Assert.AreEqual(Balance, r.Balance, "默认不缩放,余额=保证金全额"); + } + + [TestMethod] + public void 计息余额_部分释放_按适用比例缩放() + { + var leg = new MarginLeg(); + var r = leg.CalcBalance(Balance, 0.5m); + Assert.AreEqual(1_000_000m, r.Balance, "部分释放:余额×适用比例"); + } + + [TestMethod] + public void 计息余额_零适用比例_为零() + { + var leg = new MarginLeg(); + var r = leg.CalcBalance(Balance, 0m); + Assert.AreEqual(0m, r.Balance); + } + + [TestMethod] + public void 三种形态解析器_均可互换产出余额() + { + IMarginResolver cash = new CashMargin(); + IMarginResolver credit = new CreditMargin(); + IMarginResolver guarantee = new GuaranteeMargin(); + + Assert.AreEqual(MarginForm.Cash, cash.Form); + Assert.AreEqual(MarginForm.Credit, credit.Form); + Assert.AreEqual(MarginForm.Guarantee, guarantee.Form); + + Assert.AreEqual(1_000_000m, cash.Resolve(Balance, 0.5m).Balance); + Assert.AreEqual(1_000_000m, credit.Resolve(Balance, 0.5m).Balance); + Assert.AreEqual(1_000_000m, guarantee.Resolve(Balance, 0.5m).Balance); + } + } +} diff --git a/YLErpDAL/Modules/SwapModule/InterestLegs/InterestLegStrategyFactory.cs b/YLErpDAL/Modules/SwapModule/InterestLegs/InterestLegStrategyFactory.cs index a74bfb46..0826023e 100644 --- a/YLErpDAL/Modules/SwapModule/InterestLegs/InterestLegStrategyFactory.cs +++ b/YLErpDAL/Modules/SwapModule/InterestLegs/InterestLegStrategyFactory.cs @@ -8,7 +8,8 @@ namespace YLErp.Modules.SwapModule.InterestLegs; /// 利息腿策略工厂。按 InterestMode 返回对应策略实例。 /// 替换原 SwapDealService.CalcNotionalByMode 的 switch,收敛 mode 分发逻辑到一处。 /// -/// 当前界面活跃 mode:1 固定值 / 2 合约名义本金规模 / 9 标的期初全价 / 5/6 预付金。 +/// 当前界面活跃 mode:1 固定值 / 2 合约名义本金规模 / 9 标的期初全价。 +/// 注:mode 5/6 预付金已迁出本上下文(见 YLErp.Modules.SwapModule.Margin / MarginLeg),不再作为利息腿注册。 /// 死代码 mode 3/4 不注册;半死 mode 7/8 不注册(界面已注释)。 /// 传入未注册的 mode 会抛异常,防止静默走默认分支。 /// @@ -19,8 +20,6 @@ public static class InterestLegStrategyFactory [InterestModeEnum.固定值] = new FixedNotionalLeg(), [InterestModeEnum.合约名义本金规模] = new ContractNotionalLeg(), [InterestModeEnum.标的期初全价] = new UnderlyingFullPriceLeg(), - [InterestModeEnum.初始预付金] = new PrepayLeg(InterestModeEnum.初始预付金), - [InterestModeEnum.追加预付金] = new PrepayLeg(InterestModeEnum.追加预付金), }; /// 按 mode 返回对应策略。未注册的 mode 抛 ArgumentException。 diff --git a/YLErpDAL/Modules/SwapModule/InterestLegs/PrepayLeg.cs b/YLErpDAL/Modules/SwapModule/InterestLegs/PrepayLeg.cs deleted file mode 100644 index 8cb9ca08..00000000 --- a/YLErpDAL/Modules/SwapModule/InterestLegs/PrepayLeg.cs +++ /dev/null @@ -1,33 +0,0 @@ -using YLErp.DBModels; - -namespace YLErp.Modules.SwapModule.InterestLegs; - -/// -/// 预付金(保证金)利息腿。mode 5 初始预付金 / mode 6 追加预付金 共用。 -/// -/// 业务本质:预付金是客户"存"在券商的钱,券商对其付息(方向与普通利息腿相反)。 -/// 计息基数 = InterestPrincipalFix(预付金余额),随平仓递减。 -/// -/// 本类只封装 CalcNotionalByMode 的计息基数计算部分: -/// closePrincipal = Fix × closePercent -/// posiPrincipal = Fix -/// -/// 预付金腿的其它独有逻辑(方向翻转、orginPv 重映射、衡泰路径返还置0)不在此处, -/// 后续分别抽成独立方法,保持每个类/方法最小。 -/// -public sealed class PrepayLeg : IInterestLegStrategy -{ - // 预付金两个 mode(初始/追加)共用同一套计息基数公式,构造时指定 - private readonly InterestModeEnum _mode; - public InterestModeEnum Mode => _mode; - - public PrepayLeg(InterestModeEnum mode) - { - if (mode != InterestModeEnum.初始预付金 && mode != InterestModeEnum.追加预付金) - throw new ArgumentException($"PrepayLeg 仅支持 初始预付金/追加预付金,收到 {mode}", nameof(mode)); - _mode = mode; - } - - public NotionalResult CalcNotional(decimal fix, decimal posiNotional, decimal posiLong, decimal posiShort, decimal closePercent) - => new(fix * closePercent, fix, closePercent); -} diff --git a/YLErpDAL/Modules/SwapModule/Margin/CashMargin.cs b/YLErpDAL/Modules/SwapModule/Margin/CashMargin.cs new file mode 100644 index 00000000..cdb86677 --- /dev/null +++ b/YLErpDAL/Modules/SwapModule/Margin/CashMargin.cs @@ -0,0 +1,10 @@ +namespace YLErp.Modules.SwapModule.Margin; + +/// 现金保证金:计息余额 = 现金余额。 +public sealed class CashMargin : IMarginResolver +{ + public MarginForm Form => MarginForm.Cash; + + public MarginBalance Resolve(decimal postedAmount, decimal accrualFactor = 1m) + => new(postedAmount * accrualFactor); +} diff --git a/YLErpDAL/Modules/SwapModule/Margin/CreditMargin.cs b/YLErpDAL/Modules/SwapModule/Margin/CreditMargin.cs new file mode 100644 index 00000000..5524bb81 --- /dev/null +++ b/YLErpDAL/Modules/SwapModule/Margin/CreditMargin.cs @@ -0,0 +1,10 @@ +namespace YLErp.Modules.SwapModule.Margin; + +/// 授信保证:计息余额 = 已用授信额度。 +public sealed class CreditMargin : IMarginResolver +{ + public MarginForm Form => MarginForm.Credit; + + public MarginBalance Resolve(decimal postedAmount, decimal accrualFactor = 1m) + => new(postedAmount * accrualFactor); +} diff --git a/YLErpDAL/Modules/SwapModule/Margin/GuaranteeMargin.cs b/YLErpDAL/Modules/SwapModule/Margin/GuaranteeMargin.cs new file mode 100644 index 00000000..41cf30f6 --- /dev/null +++ b/YLErpDAL/Modules/SwapModule/Margin/GuaranteeMargin.cs @@ -0,0 +1,10 @@ +namespace YLErp.Modules.SwapModule.Margin; + +/// 担保品:计息余额 = 担保品市值。 +public sealed class GuaranteeMargin : IMarginResolver +{ + public MarginForm Form => MarginForm.Guarantee; + + public MarginBalance Resolve(decimal postedAmount, decimal accrualFactor = 1m) + => new(postedAmount * accrualFactor); +} diff --git a/YLErpDAL/Modules/SwapModule/Margin/IMarginResolver.cs b/YLErpDAL/Modules/SwapModule/Margin/IMarginResolver.cs new file mode 100644 index 00000000..b542de8a --- /dev/null +++ b/YLErpDAL/Modules/SwapModule/Margin/IMarginResolver.cs @@ -0,0 +1,24 @@ +namespace YLErp.Modules.SwapModule.Margin; + +/// 保证金形态:现金 / 授信 / 担保。预留扩展。 +public enum MarginForm +{ + /// 现金保证金:计息余额 = 现金余额。 + Cash, + /// 授信保证:计息余额 = 已用授信额度。 + Credit, + /// 担保品:计息余额 = 担保品市值。 + Guarantee, +} + +/// +/// 按保证金形态解析计息余额。三种形态可互换地产出一个 MarginBalance(满足 LSP), +/// 这是保证金领域唯一合理的多态点(差异仅在"余额如何取得"),不同于错误地套用利息腿接口。 +/// 当前为铺设骨架,具体余额来源(资金流水 / 授信占用 / 担保估值)后续按形态填充。 +/// +public interface IMarginResolver +{ + MarginForm Form { get; } + + MarginBalance Resolve(decimal postedAmount, decimal accrualFactor = 1m); +} diff --git a/YLErpDAL/Modules/SwapModule/Margin/MarginBalance.cs b/YLErpDAL/Modules/SwapModule/Margin/MarginBalance.cs new file mode 100644 index 00000000..46791c6f --- /dev/null +++ b/YLErpDAL/Modules/SwapModule/Margin/MarginBalance.cs @@ -0,0 +1,14 @@ +namespace YLErp.Modules.SwapModule.Margin; + +/// +/// 保证金计息余额。兼容现金、授信、担保等多种保证金形态,不承载 swap 平仓语义。 +/// 仅表达"本期计息的余额"这一计息基数,与利息腿的 NotionalResult(平仓/持仓本金拆分)无关。 +/// +public readonly struct MarginBalance +{ + /// 计息余额:现金余额 / 授信占用 / 担保品市值。 + public decimal Balance { get; } + + public MarginBalance(decimal balance) + => Balance = balance; +} diff --git a/YLErpDAL/Modules/SwapModule/Margin/MarginLeg.cs b/YLErpDAL/Modules/SwapModule/Margin/MarginLeg.cs new file mode 100644 index 00000000..d9b43fbd --- /dev/null +++ b/YLErpDAL/Modules/SwapModule/Margin/MarginLeg.cs @@ -0,0 +1,22 @@ +namespace YLErp.Modules.SwapModule.Margin; + +/// +/// 保证金(预付金)利息计提。独立限界上下文,不继承也不关联任何利息腿 +/// (IInterestLegStrategy / FixedNotionalLeg 等)——保证金是客户存款/担保,并非互换腿, +/// 套用利息腿抽象是此前的类别错误(category error)。 +/// +/// 本类只负责把"保证金余额"这一计息基数正确建模与计算。方向翻转、orginPv 维度纠正、 +/// 持仓对齐等预付金独有逻辑不在本类(原散落在 SwapDealService / SwapEodPositionService), +/// 后续按各自独立方法迁移,保持本类最小、可单测。 +/// +public sealed class MarginLeg +{ + /// + /// 计算计息余额。 + /// + /// 保证金余额(现金余额 / 授信占用 / 担保品市值)。 + /// 本期计息适用比例,默认 1(不缩放)。仅部分释放等场景小于 1。 + /// 计息基数,不含利率与日计数。 + public MarginBalance CalcBalance(decimal marginBalance, decimal accrualFactor = 1m) + => new(marginBalance * accrualFactor); +}