Files
zszq-trs/UnitTestProject/Modules/SwapModule/SplitCorporateActionTddTest.cs
T
张名锐 1c879c0b65 fix(trade): 标的除权0覆盖旧值
- 重命名 MergeNonZeroDividendValues 方法为 MergeDividendValues
- 修改合并逻辑,数值字段(包括 0)都作为有效覆盖值进行合并
- 更新注释说明合并规则的变化
- 添加零现金分红覆盖现有值的单元测试
- 修复数据库记录合并时的字段覆盖逻辑
2026-08-26 11:09:12 +08:00

209 lines
7.3 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));
}
[TestMethod]
public void ZeroCashDividendOverridesExistingValueDuringMerge()
{
var target = CreateCorporateAction(cash: 10m);
var source = CreateCorporateAction(cash: 0m);
InvokeMerge(target, source);
Assert.AreEqual(0m, target.GiveCashAmount);
}
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(
"MergeDividendValues",
BindingFlags.Static | BindingFlags.NonPublic);
Assert.IsNotNull(method, "公司行为存量合并方法不存在");
method.Invoke(null, new object[] { target, source });
}
}
}