using System.Reflection; using YLErp.DBModels; using YLErp.DBModels.Enums; using YLErp.Modules.TradeModule.DealModule; namespace YLErp.Modules.SwapModule { [TestClass] public class SplitCorporateActionTddTest { [TestMethod] public void SplitTenScalesQuantityAndPriceByTen() { var position = CreateFundPosition(); var info = CreateCorporateAction(split: 10m); Assert.IsTrue(SwapEodPositionService.ApplyCorporateActionToPosition( position, info, 100m, 0m)); Assert.AreEqual(1000m, position.PosiQuantity); Assert.AreEqual(10m, position.PosiGrossPrice); } [TestMethod] public void SplitPointOneScalesQuantityAndPriceByPointOne() { var position = CreateFundPosition(); var info = CreateCorporateAction(split: 0.1m); Assert.IsTrue(SwapEodPositionService.ApplyCorporateActionToPosition( position, info, 100m, 0m)); Assert.AreEqual(10m, position.PosiQuantity); Assert.AreEqual(1000m, position.PosiGrossPrice); } [TestMethod] public void GiveShareTenWithNullSplitUsesCompatibleFactorTwo() { var position = CreateFundPosition(); var info = CreateCorporateAction(giveShare: 10m, split: null); Assert.IsTrue(SwapEodPositionService.ApplyCorporateActionToPosition( position, info, 100m, 0m)); Assert.AreEqual(200m, position.PosiQuantity); Assert.AreEqual(50m, position.PosiGrossPrice); } [TestMethod] public void GiveShareFiveAndSplitTwoHaveCombinedFactorThree() { var position = CreateFundPosition(); var info = CreateCorporateAction(giveShare: 5m, split: 2m); // (1 + 5 / 10) * 2 = 3:100 份/100 元变为 300 份/约 33.333333333 元。 Assert.IsTrue(SwapEodPositionService.ApplyCorporateActionToPosition( position, info, 100m, 0m)); Assert.AreEqual(300m, position.PosiQuantity); Assert.IsTrue(Math.Abs(position.PosiGrossPrice - 33.333333333m) < 0.000000001m); } [TestMethod] public void CashAmountDoesNotChangeTrsFundInitialPriceFactor() { var position = CreateFundPosition(); var info = CreateCorporateAction(cash: 10m); Assert.IsTrue(SwapEodPositionService.ApplyCorporateActionToPosition( position, info, 100m, 0m)); Assert.AreEqual(100m, position.PosiQuantity); Assert.AreEqual(100m, position.PosiGrossPrice); } [TestMethod] public void RationedSharesUseExcelPriceRatioForTrsQuantity() { var position = CreateFundPosition(); var info = CreateCorporateAction( rationedSharesAmount: 1m, rationedSharesPrice: 50m); Assert.IsTrue(SwapEodPositionService.ApplyCorporateActionToPosition( position, info, 100m, 0m)); // Excel L-N:L=(100*10+1*50)/(10+1)=95.4545...,M=100/L; // 因此数量和价格分别按 Q'=Q*M、P'=P/M 调整。 Assert.IsTrue(Math.Abs(position.PosiQuantity - 104.761904761905m) < 0.000000000001m); Assert.IsTrue(Math.Abs(position.PosiGrossPrice - 95.454545455m) < 0.000000001m); } [TestMethod] public void ZeroSplitIsRejected() { var info = CreateCorporateAction(split: 0m); Assert.ThrowsException(() => SwapEodPositionService.ApplyCorporateActionToPosition( CreateFundPosition(), info, 100m, 0m)); } [TestMethod] public void NegativeSplitIsRejected() { var info = CreateCorporateAction(split: -1m); Assert.ThrowsException(() => SwapEodPositionService.ApplyCorporateActionToPosition( CreateFundPosition(), info, 100m, 0m)); } [TestMethod] public void MissingSplitDoesNotClearExistingSplitDuringMerge() { var target = CreateCorporateAction(split: 10m); var source = CreateCorporateAction(split: null); InvokeMerge(target, source); Assert.AreEqual(10m, GetSplit(target)); } [TestMethod] public void ExplicitSplitOneOverridesExistingSplitDuringMerge() { var target = CreateCorporateAction(split: 10m); var source = CreateCorporateAction(split: 1m); InvokeMerge(target, source); Assert.AreEqual(1m, GetSplit(target)); } private static ex_dividend_info CreateCorporateAction( decimal cash = 0m, decimal giveShare = 0m, decimal? split = null, decimal rationedSharesAmount = 0m, decimal rationedSharesPrice = 0m) { var info = new ex_dividend_info { UnderlyingCode = "FUND.TEST", ExDividendDate = new DateTime(2026, 8, 14), EffectiveDate = new DateTime(2026, 8, 17), GiveCashAmount = cash, GiveShareAmount = giveShare, RationedSharesAmount = rationedSharesAmount, RationedSharesPrice = rationedSharesPrice, ValidStatus = true }; SetSplit(info, split); return info; } private static swap_position CreateFundPosition() { return new swap_position { PosiDirection = 1, UnderlyingInstrumentType = ConsGlobal.InstrumentType.Fund, UnderlyingCode = "FUND.TEST", PosiQuantity = 100m, PosiGrossPrice = 100m, PosiNetPrice = 100m, PosiNetFeePrice = 100m, PosiNetNoFeePrice = 100m, ContractSize = 1m }; } private static void SetSplit(ex_dividend_info info, decimal? value) { var property = typeof(ex_dividend_info).GetProperty("Split"); Assert.IsNotNull(property, "ex_dividend_info.Split 尚未实现"); property.SetValue(info, value); } private static decimal? GetSplit(ex_dividend_info info) { var property = typeof(ex_dividend_info).GetProperty("Split"); Assert.IsNotNull(property, "ex_dividend_info.Split 尚未实现"); return (decimal?)property.GetValue(info); } private static void InvokeMerge(ex_dividend_info target, ex_dividend_info source) { var method = typeof(DividendService).GetMethod( "MergeNonZeroDividendValues", BindingFlags.Static | BindingFlags.NonPublic); Assert.IsNotNull(method, "公司行为存量合并方法不存在"); method.Invoke(null, new object[] { target, source }); } } }