From 279c6403450d981693659f04e1438cdd366f5a0a Mon Sep 17 00:00:00 2001 From: hjhan Date: Tue, 11 Aug 2026 10:36:38 +0800 Subject: [PATCH] =?UTF-8?q?refactor(return-leg):=20=E6=8A=BD=E5=8F=96=20Qu?= =?UTF-8?q?antityRollforward=20=E6=A0=87=E7=9A=84=E7=AB=AF=E6=95=B0?= =?UTF-8?q?=E9=87=8F=E9=80=92=E6=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把 SwapEodPositionService:1980 inline 的数量递推公式抽成独立纯函数: qty = 上一日终数量 + 开仓 - 平仓 + 公司行为调整 新增第4参数 corpActionDeltaQty(默认0,当前不影响行为), 预留给公司行为改造(送股/拆股/配股)。 原代码硬编码'数量只因交易变动'假设,改造时填入非零值即可。 ReturnLegs/QuantityRollforward.cs: 纯函数, 不依赖任何实例状态 ReturnLegs 测试: 8个(无交易/开仓/平仓/全平/超额/送股/拆股) SwapEodPositionService: inline 2行 → QuantityRollforward.Calc() 1行 验证: 编译0错误, 全量497测试7失败(基线一致)。 --- .../ReturnLegs/QuantityRollforwardTest.cs | 63 +++++++++++++++++++ .../ReturnLegs/QuantityRollforward.cs | 27 ++++++++ .../SwapModule/SwapEodPositionService.cs | 4 +- 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 UnitTestProject/Modules/SwapModule/ReturnLegs/QuantityRollforwardTest.cs create mode 100644 YLErpDAL/Modules/SwapModule/ReturnLegs/QuantityRollforward.cs diff --git a/UnitTestProject/Modules/SwapModule/ReturnLegs/QuantityRollforwardTest.cs b/UnitTestProject/Modules/SwapModule/ReturnLegs/QuantityRollforwardTest.cs new file mode 100644 index 00000000..7274b2c3 --- /dev/null +++ b/UnitTestProject/Modules/SwapModule/ReturnLegs/QuantityRollforwardTest.cs @@ -0,0 +1,63 @@ +using YLErp.Modules.SwapModule.ReturnLegs; + +namespace UnitTestProject.Modules.SwapModule.ReturnLegs +{ + /// + /// 标的端数量递推测试。验证开仓/平仓/公司行为场景下数量正确推进。 + /// + [TestClass] + public class QuantityRollforwardTest + { + [TestMethod] + public void 无交易_数量不变() + { + Assert.AreEqual(1000m, QuantityRollforward.Calc(1000m, 0m, 0m)); + } + + [TestMethod] + public void 开仓_数量增加() + { + Assert.AreEqual(1500m, QuantityRollforward.Calc(1000m, 500m, 0m)); + } + + [TestMethod] + public void 平仓_数量减少() + { + Assert.AreEqual(600m, QuantityRollforward.Calc(1000m, 0m, 400m)); + } + + [TestMethod] + public void 开仓加平仓_净值() + { + Assert.AreEqual(1200m, QuantityRollforward.Calc(1000m, 500m, 300m)); + } + + [TestMethod] + public void 全平_数量归零() + { + Assert.AreEqual(0m, QuantityRollforward.Calc(1000m, 0m, 1000m)); + } + + [TestMethod] + public void 超额平仓_不低于零() + { + Assert.AreEqual(0m, QuantityRollforward.Calc(1000m, 0m, 1500m)); + } + + [TestMethod] + public void 公司行为送股_数量增加_默认不影响() + { + // 默认 corpActionDeltaQty=0, 行为不变 + Assert.AreEqual(1000m, QuantityRollforward.Calc(1000m, 0m, 0m)); + // 送股 10%: 1000 × 10% = 100 + Assert.AreEqual(1100m, QuantityRollforward.Calc(1000m, 0m, 0m, corpActionDeltaQty: 100m)); + } + + [TestMethod] + public void 公司行为拆股_数量翻倍() + { + // 1拆2: 数量翻倍, corpActionDeltaQty = 当前数量 + Assert.AreEqual(2000m, QuantityRollforward.Calc(1000m, 0m, 0m, corpActionDeltaQty: 1000m)); + } + } +} diff --git a/YLErpDAL/Modules/SwapModule/ReturnLegs/QuantityRollforward.cs b/YLErpDAL/Modules/SwapModule/ReturnLegs/QuantityRollforward.cs new file mode 100644 index 00000000..c2352db4 --- /dev/null +++ b/YLErpDAL/Modules/SwapModule/ReturnLegs/QuantityRollforward.cs @@ -0,0 +1,27 @@ +namespace YLErp.Modules.SwapModule.ReturnLegs; + +/// +/// 标的端数量递推(quantity rollforward)。 +/// +/// 逐日推进持仓数量:今日数量 = 上一日终数量 + 今日开仓 - 今日平仓 + 公司行为调整。 +/// +/// 公司行为调整(corpActionDeltaQty)默认 0,当前不影响行为; +/// 预留给公司行为改造(送股/拆股/配股)——届时填入非零值。 +/// 原代码(SwapEodPositionService:1980)只有开仓/平仓两项,硬编码了"数量只因交易变动"假设。 +/// +public static class QuantityRollforward +{ + /// + /// 计算今日持仓数量。 + /// + /// 上一日终持仓数量。 + /// 今日开仓数量。 + /// 今日平仓数量。 + /// 公司行为导致数量变动(送股/拆股),默认 0。 + /// 今日持仓数量(不低于 0)。 + public static decimal Calc(decimal previousQty, decimal openQty, decimal unwindQty, decimal corpActionDeltaQty = 0m) + { + var qty = previousQty + openQty - unwindQty + corpActionDeltaQty; + return qty < 0 ? 0 : Math.Abs(qty); + } +} diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs index 776b88c4..ef3e7901 100644 --- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs @@ -13,6 +13,7 @@ using YLErp.Models; using YLErp.Modules.DataProviderModule; using YLErp.Modules.EodModule; using YLErp.Modules.SwapModule.Margin; +using YLErp.Modules.SwapModule.ReturnLegs; using YLErp.QdpModule; namespace YLErp.Modules.SwapModule @@ -1977,8 +1978,7 @@ namespace YLErp.Modules.SwapModule var openFlowEvents = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.开仓).ToList(); decimal unwindQty = unwindFlowEvents.Sum(s => s.Quantity); decimal openQty = openFlowEvents.Sum(s => s.Quantity); - var qty = eod.PosiQuantity + openQty - unwindQty; - curretEod.PosiQuantity = qty < 0 ? 0 : Math.Abs(qty); + curretEod.PosiQuantity = QuantityRollforward.Calc(eod.PosiQuantity, openQty, unwindQty); if (unwindEvents.Count == 0) { curretEod.PosiNetPrice = position.PosiNetPrice;