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;