Files
zszq-trs/UnitTestProject/Modules/SwapModule/SplitCorporateActionTddTest.cs
T
张名锐 aa3548e77f feat(bond): 支持债券与股票基金公司行为现金流计算的差异化处理 - init2
- 修改 CalcPayment 方法添加 useBondPriceScale 参数区分债券和股票/基金的金额计算口径
- 债券利息按每100元面值票息通过BondPriceConverter转为入库金额,股票基金分红直接计算
- 在BondPaymentService中添加详细的参数说明文档注释
- 更新SwapDealService中分红计算逻辑,根据标的类型自动选择合适的金额转换方式
- 新增CorporateActionEventLifecycleTest单元测试验证公司行为事件生命周期管理
- 添加SplitCorporateActionTddTest测试验证拆合股功能
- 优化FundCorporateActionRollbackAndUnwindTest扩展到股票类型测试
- 更新前端OperationHistory页面表格列宽和显示格式支持更长的说明信息
2026-08-19 17:48:34 +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.ApplyFundCorporateActionToPosition(
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.ApplyFundCorporateActionToPosition(
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.ApplyFundCorporateActionToPosition(
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.ApplyFundCorporateActionToPosition(
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.ApplyFundCorporateActionToPosition(
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.ApplyFundCorporateActionToPosition(
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.ApplyFundCorporateActionToPosition(
CreateFundPosition(), info, 100m, 0m));
}
[TestMethod]
public void NegativeSplitIsRejected()
{
var info = CreateCorporateAction(split: -1m);
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
SwapEodPositionService.ApplyFundCorporateActionToPosition(
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 });
}
}
}