- 重命名 MergeNonZeroDividendValues 方法为 MergeDividendValues - 修改合并逻辑,数值字段(包括 0)都作为有效覆盖值进行合并 - 更新注释说明合并规则的变化 - 添加零现金分红覆盖现有值的单元测试 - 修复数据库记录合并时的字段覆盖逻辑
209 lines
7.3 KiB
C#
209 lines
7.3 KiB
C#
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<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 });
|
||
}
|
||
}
|
||
}
|