Files
zszq-trs/UnitTestProject/Modules/SwapModule/SplitCorporateActionTddTest.cs
T
张名锐 b2f4e16782 feat(bond): 支持股票和基金现金分红纳入债券支付计算 - 收紧过度开发行为
- 实现股票/基金现金分红数据从 ex_dividend_info 同步到 BondPayment
- 新增公司行为去重机制,避免镜像任务完成后重复计息
- 统一现金分红存储口径为"每 10 份派现金额",保持与同步任务一致性
- 修改 CalcPayment 方法,股票/基金分红需除以 10 转换实际现金金额
- 添加单元测试验证债券票息和股票/基金分红的不同计算方式
- 更新文档注释说明"每 10 份派现金额"存储规范
- 修复公司行为生效日处理逻辑,确保正确应用除权系数
- 扩展测试覆盖股票类证券的公司行为处理场景
2026-08-20 13:30:36 +08:00

198 lines
7.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 = 3100 份/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-NL=(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<ArgumentOutOfRangeException>(() =>
SwapEodPositionService.ApplyCorporateActionToPosition(
CreateFundPosition(), info, 100m, 0m));
}
[TestMethod]
public void NegativeSplitIsRejected()
{
var info = CreateCorporateAction(split: -1m);
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
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 });
}
}
}