Merge branch 'glms/feature/0812_zmr_divPower' into 'glms/feature/1.4.2'
feat(swap): 添加公司行为现金分红按登记日权益数量计算功能 See merge request otc-dev/zszq-trs!16
This commit is contained in:
@@ -66,5 +66,23 @@ namespace YLErp.Modules.EodModule
|
||||
|
||||
Assert.AreEqual(204000m, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CalcPayment_CorporateActionUsesPreCorporateActionQuantity()
|
||||
{
|
||||
var payments = new List<BondPayment>
|
||||
{
|
||||
new BondPayment { payment_interest = 2m },
|
||||
new BondPayment { payment_interest = 10m, IsCorporateActionCashDividend = true }
|
||||
};
|
||||
var method = typeof(BondPaymentService).GetMethod(
|
||||
nameof(BondPaymentService.CalcPayment),
|
||||
new[] { typeof(List<BondPayment>), typeof(decimal), typeof(decimal), typeof(decimal), typeof(decimal?) });
|
||||
|
||||
Assert.IsNotNull(method, "公司行为现金分红需要支持单独传入除权前数量。");
|
||||
var actual = (decimal)method.Invoke(CreateService(), new object[] { payments, 2000m, 1m, 1m, (decimal?)1000m });
|
||||
|
||||
Assert.AreEqual(1040m, actual, "原生付息按当前 2000 份计算为 40,公司行为分红按除权前 1000 份计算为 1000。");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,10 +189,26 @@ namespace YLErp.Modules.EodModule
|
||||
decimal qty,
|
||||
decimal longRatio,
|
||||
decimal payDirection)
|
||||
{
|
||||
return CalcPayment(payments, qty, longRatio, payDirection, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 公司行为现金分红按登记日权益数量计算;同一窗口中的原生债券付息仍按当前持仓数量计算。
|
||||
/// </summary>
|
||||
public decimal CalcPayment(
|
||||
List<BondPayment> payments,
|
||||
decimal qty,
|
||||
decimal longRatio,
|
||||
decimal payDirection,
|
||||
decimal? corporateActionQty)
|
||||
{
|
||||
var actualAmount = (payments ?? new List<BondPayment>()).Sum(payment =>
|
||||
{
|
||||
var paymentAmount = (payment.payment_interest ?? 0m) * qty;
|
||||
var paymentQty = payment.IsCorporateActionCashDividend
|
||||
? corporateActionQty ?? qty
|
||||
: qty;
|
||||
var paymentAmount = (payment.payment_interest ?? 0m) * paymentQty;
|
||||
// bond_payment_info 原生期间付息按每 100 份存储;由 ex_dividend_info 补充的
|
||||
// 公司行为现金分红按每 10 份存储。Fund 标的可能同时命中两类记录,故必须逐条分流。
|
||||
return payment.IsCorporateActionCashDividend
|
||||
|
||||
@@ -357,6 +357,19 @@ namespace YLErp.Modules.SwapModule
|
||||
directionRatio);
|
||||
}
|
||||
|
||||
protected virtual decimal CalcBondPayment(string underlyingCode, DateTime fromDate, DateTime toDate,
|
||||
decimal qty, int shortRatio, int directionRatio, decimal? corporateActionQty)
|
||||
{
|
||||
if (!corporateActionQty.HasValue)
|
||||
{
|
||||
return CalcBondPayment(underlyingCode, fromDate, toDate, qty, shortRatio, directionRatio);
|
||||
}
|
||||
|
||||
var service = new BondPaymentService(UserInfo);
|
||||
var payments = service.GetBondPayments(underlyingCode, fromDate, toDate);
|
||||
return service.CalcPayment(payments, qty, shortRatio, directionRatio, corporateActionQty);
|
||||
}
|
||||
|
||||
// ---- SwapPositionCompose 路径专用 seam(借鉴 testable 分支)----
|
||||
|
||||
/// <summary>查找收盘所需的活跃互换交易(生产: DbContext.trade.Where;测试: 内存列表)</summary>
|
||||
@@ -611,6 +624,11 @@ namespace YLErp.Modules.SwapModule
|
||||
var corporateActionBeforePositions = BuildCorporateActionBeforePositions(
|
||||
eodPositions, // 上一日终持仓
|
||||
posiList);
|
||||
var corporateActionCashDividendBeforePositions = corporateActionBeforePositions
|
||||
.Where(position => !string.IsNullOrWhiteSpace(position.UnderlyingCode)
|
||||
&& exDividendByCode.TryGetValue(position.UnderlyingCode, out var dividend)
|
||||
&& dividend.GiveCashAmount != 0m)
|
||||
.ToList();
|
||||
|
||||
// 交易首日恰逢 EffectiveDate 时,在内存克隆上生成除权后的开盘基线,应用生效日公司行为。
|
||||
// 有上一份 EOD 时沿用 PrepareFundOpeningEodPositions,避免重复套系数。
|
||||
@@ -627,7 +645,8 @@ namespace YLErp.Modules.SwapModule
|
||||
settleDate, // 收盘日期
|
||||
td, // 交易
|
||||
preSettleDate, // 上一交易日
|
||||
flowEvents); // 流水事件
|
||||
flowEvents, // 流水事件
|
||||
corporateActionCashDividendBeforePositions);
|
||||
|
||||
// 现金分红不在登记日直接累加;Copy/Update EOD 通过 CalcBondPayment
|
||||
// 读取 EffectiveDate 命中的 ex_dividend_info,并生成 TdPosiDividend。
|
||||
@@ -1435,7 +1454,8 @@ namespace YLErp.Modules.SwapModule
|
||||
DateTime settleDate,
|
||||
trade td,
|
||||
DateTime preSettleDate,
|
||||
List<swap_flow_event> flowEvents)
|
||||
List<swap_flow_event> flowEvents,
|
||||
IReadOnlyCollection<eod_swap_position> corporateActionBeforePositions = null)
|
||||
{
|
||||
string settleDateStr = settleDate.ToString("yyyy-MM-dd");
|
||||
string preSettleDateStr = preSettleDate.ToString("yyyy-MM-dd");
|
||||
@@ -1457,18 +1477,20 @@ namespace YLErp.Modules.SwapModule
|
||||
var tdEodPosition = todyEodPositions.FirstOrDefault(x => x.PositionId == posi.id);//当前结算日日终持仓信息
|
||||
var unwindEvents = flowEvents.Where(x => x.PositionId == posi.id).ToList();//当前日平仓信息
|
||||
var realPosition = realPosiList.FirstOrDefault(s => s.PositionId == posi.id);
|
||||
var corporateActionBeforeQuantity = corporateActionBeforePositions?
|
||||
.FirstOrDefault(x => x.PositionId == posi.id)?.PosiQuantity;
|
||||
eod_swap_position eodPosi = new eod_swap_position();
|
||||
if (eodPosition == null)
|
||||
{
|
||||
eodPosi = SaveCurrentEodInitalPosi(posi, td, settleDate, preSettleDate, unwindEvents);
|
||||
eodPosi = SaveCurrentEodInitalPosi(posi, td, settleDate, preSettleDate, unwindEvents, corporateActionBeforeQuantity);
|
||||
}
|
||||
else if (unwindEvents.Count() == 0)
|
||||
{
|
||||
eodPosi = CopyEodPosition(eodPosition, tdEodPosition, td, settleDate, preSettleDate);
|
||||
eodPosi = CopyEodPosition(eodPosition, tdEodPosition, td, settleDate, preSettleDate, corporateActionBeforeQuantity);
|
||||
}
|
||||
else
|
||||
{
|
||||
eodPosi = UpdateEodPosition(posi, eodPosition, tdEodPosition, td, settleDate, preSettleDate, unwindEvents);
|
||||
eodPosi = UpdateEodPosition(posi, eodPosition, tdEodPosition, td, settleDate, preSettleDate, unwindEvents, corporateActionBeforeQuantity);
|
||||
}
|
||||
Log.Info($"eodPosi为:{JsonHelper.Serialize(eodPosi, false)}");
|
||||
list.Add(eodPosi);
|
||||
@@ -2655,7 +2677,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <param name="todayPositions">当日日终归档信息</param>
|
||||
/// <param name="swap_Deals">当日平仓/互换事件信息</param>
|
||||
/// <param name="td">交易信息</param>
|
||||
protected eod_swap_position CopyEodPosition(eod_swap_position eod, eod_swap_position curretEod, trade td, DateTime valueDate, DateTime preSettleDate)
|
||||
protected eod_swap_position CopyEodPosition(eod_swap_position eod, eod_swap_position curretEod, trade td, DateTime valueDate, DateTime preSettleDate, decimal? corporateActionBeforeQuantity = null)
|
||||
{
|
||||
if (curretEod == null)
|
||||
{
|
||||
@@ -2677,7 +2699,7 @@ namespace YLErp.Modules.SwapModule
|
||||
decimal tax = um.ValueAddedTax ?? 0;
|
||||
if (valueDate > td.StartDate.Value && curretEod.PosiQuantity > 0)
|
||||
{
|
||||
decimal payment = CalcBondPayment(curretEod.UnderlyingCode, eod.ValueDate, valueDate, curretEod.PosiQuantity, shortRatio, directionRatio);
|
||||
decimal payment = CalcBondPayment(curretEod.UnderlyingCode, eod.ValueDate, valueDate, curretEod.PosiQuantity, shortRatio, directionRatio, corporateActionBeforeQuantity);
|
||||
curretEod.TdPosiDividend = DividendCalc.AfterTax(payment, tax);
|
||||
}
|
||||
curretEod.PosiDividendSum = eod.PosiQuantity > 0 ? Math.Round(eod.PosiDividendSum + curretEod.TdPosiDividend, 2) : 0;
|
||||
@@ -2739,7 +2761,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <param name="curretEod"></param>
|
||||
/// <param name="td"></param>
|
||||
/// <param name="valueDate"></param>
|
||||
protected eod_swap_position UpdateEodPosition(swap_position swapPosition, eod_swap_position eod, eod_swap_position curretEod, trade td, DateTime valueDate, DateTime preSettleDate, List<swap_flow_event> unwindEvents)
|
||||
protected eod_swap_position UpdateEodPosition(swap_position swapPosition, eod_swap_position eod, eod_swap_position curretEod, trade td, DateTime valueDate, DateTime preSettleDate, List<swap_flow_event> unwindEvents, decimal? corporateActionBeforeQuantity = null)
|
||||
{
|
||||
if (curretEod == null)
|
||||
{
|
||||
@@ -2773,7 +2795,7 @@ namespace YLErp.Modules.SwapModule
|
||||
// 修改,互换事件会影响待实现的分红的,现在要算上
|
||||
if (valueDate > td.StartDate.Value && (curretEod.PosiQuantity > 0))
|
||||
{
|
||||
decimal payment = CalcBondPayment(curretEod.UnderlyingCode, eod.ValueDate, valueDate, curretEod.PosiQuantity, shortRatio, directionRatio);
|
||||
decimal payment = CalcBondPayment(curretEod.UnderlyingCode, eod.ValueDate, valueDate, curretEod.PosiQuantity, shortRatio, directionRatio, corporateActionBeforeQuantity);
|
||||
curretEod.TdPosiDividend = DividendCalc.AfterTax(payment, tax);
|
||||
}
|
||||
curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl;
|
||||
@@ -2900,7 +2922,8 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <param name="position"></param>
|
||||
/// <param name="td"></param>
|
||||
/// <param name="settleDate"></param>
|
||||
protected eod_swap_position SaveCurrentEodInitalPosi(swap_position position, trade td, DateTime settleDate, DateTime preSettleDate, List<swap_flow_event> unwindEvents)
|
||||
protected eod_swap_position SaveCurrentEodInitalPosi(swap_position position, trade td, DateTime settleDate,
|
||||
DateTime preSettleDate, List<swap_flow_event> unwindEvents, decimal? corporateActionBeforeQuantity = null)
|
||||
{
|
||||
eod_swap_position curretEod = new eod_swap_position();
|
||||
var um = GetUnderlyingData(position.UnderlyingCode);
|
||||
@@ -2953,7 +2976,7 @@ namespace YLErp.Modules.SwapModule
|
||||
if (!hasSwapEvent && settleDate > td.StartDate.Value && curretEod.PosiQuantity > 0)
|
||||
{
|
||||
decimal tax = um.ValueAddedTax ?? 0;
|
||||
decimal payment = CalcBondPayment(curretEod.UnderlyingCode, td.StartDate.Value, settleDate, curretEod.PosiQuantity, shortRatio, directionRatio);
|
||||
decimal payment = CalcBondPayment(curretEod.UnderlyingCode, td.StartDate.Value, settleDate, curretEod.PosiQuantity, shortRatio, directionRatio, corporateActionBeforeQuantity);
|
||||
payment = DividendCalc.AfterTax(payment, tax);
|
||||
//var consumedDividend = CalcConsumedDividend(curretEod, unwindEvents); 首日应该没有分红
|
||||
curretEod.TdPosiDividend = payment;
|
||||
|
||||
Reference in New Issue
Block a user