From eaa4ed735a2c07c03020c49db206ede3bfe275dd Mon Sep 17 00:00:00 2001 From: hjhan Date: Thu, 2 Jul 2026 09:02:43 +0800 Subject: [PATCH] =?UTF-8?q?test(swap):=20DealFloatPositions=E5=88=86?= =?UTF-8?q?=E7=BA=A2=E5=9C=BA=E6=99=AF=E6=B5=8B=E8=AF=95(=E7=9B=AF?= =?UTF-8?q?=E5=B8=82=E7=9B=88=E4=BA=8F+=E5=88=86=E7=BA=A2+=E5=A2=9E?= =?UTF-8?q?=E5=80=BC=E7=A8=8E)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 扩展StubEodService override 3个外部依赖虚方法: - GetUnderlyingPrice: 注入固定标的价格 - GetUnderlyingData: 注入标的缓存(增值税率) - CalcBondPayment: 注入固定债券付息 新增3个分红场景测试: - DF_005: CopyEodPosition盯市盈亏=(price-grossPrice)×qty×方向 - DF_006: 分红计算 TdPosiDividend=payment/(1+tax)×(1-tax) - DF_007: 增值税调整(tax=6%): 100/1.06×0.94≈88.68 覆盖了cf2f8b6e分红重复计算修复所在的浮动腿归档链路。 验证: 108+3=111全通过。 --- .../DealFloatPositionsScenarioTest.cs | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs b/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs index 1b6f6b7b..073d6240 100644 --- a/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs @@ -27,10 +27,39 @@ namespace YLErp.Modules.SwapModule private sealed class StubEodService : SwapEodPositionService { + // 可注入的外部数据 + public decimal UnderlyingPrice { get; set; } = 1.00m; + public decimal Vobp { get; set; } = 0m; + public decimal BondPayment { get; set; } = 0m; + public decimal TaxRate { get; set; } = 0m; + public string UnderlyingCode { get; set; } = "210210.IB"; + public StubEodService() : base(new OptUserInfo(0, nameof(DealFloatPositionsScenarioTest), OptUserFrom.UnitTest)) { } + // override 外部依赖 + protected override decimal GetUnderlyingPrice(string code, DateTime settleDate, out decimal vobp) + { + vobp = Vobp; + return UnderlyingPrice; + } + + protected override underlying_manager GetUnderlyingData(string underlyingCode) + { + return new underlying_manager { ValueAddedTax = TaxRate }; + } + + protected override decimal CalcBondPayment(string underlyingCode, DateTime fromDate, DateTime toDate, decimal qty, int shortRatio, int directionRatio) + { + return BondPayment; + } + + protected override void SaveAllChanges() { } + + protected override double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType) + => 1.0; + // DealFloatPositions 和子方法都是 protected,通过 public 包装暴露 public List ExecuteDealFloatPositions( List posiList, List realPosiList, @@ -47,6 +76,12 @@ namespace YLErp.Modules.SwapModule { return SaveCurrentEodInitalPosi(position, td, settleDate, preSettleDate, unwindEvents); } + + public eod_swap_position ExecuteCopyEodPosition( + eod_swap_position eod, eod_swap_position curretEod, trade td, DateTime valueDate, DateTime preSettleDate) + { + return CopyEodPosition(eod, curretEod, td, valueDate, preSettleDate); + } } #endregion @@ -221,5 +256,132 @@ namespace YLErp.Modules.SwapModule Assert.Inconclusive($"CopyEodPosition 依赖外部数据源,需额外接缝。异常: {ex.Message}"); } } + + // ================================================================ + // 场景5:CopyEodPosition 盯市盈亏计算(注入固定标的价格) + // ================================================================ + + /// + /// [DF_005] 无平仓日,标的价格变动 → PosiMtmPnL 正确反映浮动盈亏 + /// --------------------------------------------------------------- + /// 前日持仓全价=1.002,当日标的价格=1.010(涨了)。 + /// 多头收取方向,PosiMtmPnL = (1.010 - 1.002) × 10000 × 1(ContractSize) × 1(shortRatio) × 1(directionRatio) + /// = 0.008 × 10000 = 80 + /// + [TestMethod] + public void DF_005_Copy分支盯市盈亏计算() + { + var service = new StubEodService(); + service.UnderlyingPrice = 1.010m; // 当日标的价格 + service.TaxRate = 0m; + service.BondPayment = 0m; + + var td = CreateTrade(); + var position = CreateFloatPosition(); + + var preEod = new eod_swap_position + { + id = 5001, SwapTradeId = SwapTradeId, PositionId = position.id, + ValueDate = PreSettleDate, PosiQuantity = 10000m, + PosiGrossPrice = 1.002m, PosiNetPrice = 1.005m, + UnderlyingCode = "210210.IB", ContractSize = 1m, + PositionType = (int)PositionTypeFlag.Long, PosiDirection = (int)SwapDirectionEnum.收取, + PosiDividendSum = 0m, PosiFeePending = 0m, PosiProfitSum = 0m + }; + + var result = service.ExecuteCopyEodPosition(preEod, null, td, TradeDate, PreSettleDate); + + // PosiMtmPnL = (price - grossPrice) × qty × contractSize × shortRatio × directionRatio + // 收取方向 directionRatio=1, 多头 shortRatio=1 + // = (1.010 - 1.002) × 10000 × 1 × 1 × 1 = 80 + AssertDecimalEqual(80m, result.PosiMtmPnL, 0.01m, "盯市盈亏"); + Console.WriteLine($"Copy分支盯市: PosiMtmPnL={result.PosiMtmPnL}((1.010-1.002)×10000=80)✅"); + } + + // ================================================================ + // 场景6:CopyEodPosition 分红计算(注入固定付息) + // ================================================================ + + /// + /// [DF_006] 无平仓日,债券付息 → TdPosiDividend 和 PosiDividendSum 正确 + /// --------------------------------------------------------------- + /// 注入 BondPayment=100(付息总额),增值税率=0。 + /// TdPosiDividend = 100 / (1+0) × (1-0) = 100。 + /// PosiDividendSum = preEod.PosiDividendSum(0) + TdPosiDividend(100) = 100。 + /// + [TestMethod] + public void DF_006_Copy分支分红计算() + { + var service = new StubEodService(); + service.UnderlyingPrice = 1.002m; // 价格不变 + service.TaxRate = 0m; + service.BondPayment = 100m; // 付息100 + + var td = CreateTrade(); + var position = CreateFloatPosition(); + + var preEod = new eod_swap_position + { + id = 5001, SwapTradeId = SwapTradeId, PositionId = position.id, + ValueDate = PreSettleDate, PosiQuantity = 10000m, + PosiGrossPrice = 1.002m, PosiNetPrice = 1.005m, + UnderlyingCode = "210210.IB", ContractSize = 1m, + PositionType = (int)PositionTypeFlag.Long, PosiDirection = (int)SwapDirectionEnum.收取, + PosiDividendSum = 0m, PosiFeePending = 0m, PosiProfitSum = 0m + }; + + var result = service.ExecuteCopyEodPosition(preEod, null, td, TradeDate, PreSettleDate); + + // TdPosiDividend = payment / (1+tax) × (1-tax) = 100 / 1 × 1 = 100 + AssertDecimalEqual(100m, result.TdPosiDividend, 0.01m, "当日分红"); + // PosiDividendSum = 前日(0) + 当日(100) = 100 + AssertDecimalEqual(100m, result.PosiDividendSum, 0.01m, "待实现分红累计"); + Console.WriteLine($"Copy分支分红: TdPosiDividend={result.TdPosiDividend}, PosiDividendSum={result.PosiDividendSum} ✅"); + } + + // ================================================================ + // 场景7:分红增值税调整(税率≠0) + // ================================================================ + + /// + /// [DF_007] 分红含增值税调整:BondPayment=100, tax=6%(0.06) + /// --------------------------------------------------------------- + /// TdPosiDividend = 100 / (1+0.06) × (1-0.06) = 100/1.06×0.94 ≈ 88.68 + /// 验证增值税调整公式 cs:1508 payment / (1+tax) * (1-tax)。 + /// + [TestMethod] + public void DF_007_分红增值税调整() + { + var service = new StubEodService(); + service.UnderlyingPrice = 1.002m; + service.TaxRate = 0.06m; // 增值税率6% + service.BondPayment = 100m; + + var td = CreateTrade(); + var position = CreateFloatPosition(); + + var preEod = new eod_swap_position + { + id = 5001, SwapTradeId = SwapTradeId, PositionId = position.id, + ValueDate = PreSettleDate, PosiQuantity = 10000m, + PosiGrossPrice = 1.002m, PosiNetPrice = 1.005m, + UnderlyingCode = "210210.IB", ContractSize = 1m, + PositionType = (int)PositionTypeFlag.Long, PosiDirection = 2, + PosiDividendSum = 0m, PosiFeePending = 0m, PosiProfitSum = 0m + }; + + var result = service.ExecuteCopyEodPosition(preEod, null, td, TradeDate, PreSettleDate); + + // TdPosiDividend = 100 / 1.06 × 0.94 = 88.6792... + decimal expected = Math.Round(100m / 1.06m * 0.94m, 2); + AssertDecimalEqual(expected, result.TdPosiDividend, 0.01m, "增值税调整后分红"); + Console.WriteLine($"分红增值税调整: 付息100, 税率6% → TdPosiDividend={result.TdPosiDividend}(期望{expected})✅"); + } + + private static void AssertDecimalEqual(decimal expected, decimal actual, decimal tolerance, string message = "") + { + Assert.IsTrue(Math.Abs(expected - actual) <= tolerance, + $"{message} Expected: {expected}, Actual: {actual}, Diff: {expected - actual}"); + } } }