refactor(return-leg): 抽取 QuantityRollforward 标的端数量递推

把 SwapEodPositionService:1980 inline 的数量递推公式抽成独立纯函数:
  qty = 上一日终数量 + 开仓 - 平仓 + 公司行为调整

新增第4参数 corpActionDeltaQty(默认0,当前不影响行为),
预留给公司行为改造(送股/拆股/配股)。
原代码硬编码'数量只因交易变动'假设,改造时填入非零值即可。

ReturnLegs/QuantityRollforward.cs: 纯函数, 不依赖任何实例状态
ReturnLegs 测试: 8个(无交易/开仓/平仓/全平/超额/送股/拆股)
SwapEodPositionService: inline 2行 → QuantityRollforward.Calc() 1行

验证: 编译0错误, 全量497测试7失败(基线一致)。
This commit is contained in:
hjhan
2026-08-11 10:36:38 +08:00
parent 82fb5e5916
commit 279c640345
3 changed files with 92 additions and 2 deletions
@@ -0,0 +1,63 @@
using YLErp.Modules.SwapModule.ReturnLegs;
namespace UnitTestProject.Modules.SwapModule.ReturnLegs
{
/// <summary>
/// 标的端数量递推测试。验证开仓/平仓/公司行为场景下数量正确推进。
/// </summary>
[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));
}
}
}
@@ -0,0 +1,27 @@
namespace YLErp.Modules.SwapModule.ReturnLegs;
/// <summary>
/// 标的端数量递推(quantity rollforward)。
///
/// 逐日推进持仓数量:今日数量 = 上一日终数量 + 今日开仓 - 今日平仓 + 公司行为调整。
///
/// 公司行为调整(corpActionDeltaQty)默认 0,当前不影响行为;
/// 预留给公司行为改造(送股/拆股/配股)——届时填入非零值。
/// 原代码(SwapEodPositionService:1980)只有开仓/平仓两项,硬编码了"数量只因交易变动"假设。
/// </summary>
public static class QuantityRollforward
{
/// <summary>
/// 计算今日持仓数量。
/// </summary>
/// <param name="previousQty">上一日终持仓数量。</param>
/// <param name="openQty">今日开仓数量。</param>
/// <param name="unwindQty">今日平仓数量。</param>
/// <param name="corpActionDeltaQty">公司行为导致数量变动(送股/拆股),默认 0。</param>
/// <returns>今日持仓数量(不低于 0)。</returns>
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);
}
}
@@ -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;