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);
|
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 qty,
|
||||||
decimal longRatio,
|
decimal longRatio,
|
||||||
decimal payDirection)
|
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 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 补充的
|
// bond_payment_info 原生期间付息按每 100 份存储;由 ex_dividend_info 补充的
|
||||||
// 公司行为现金分红按每 10 份存储。Fund 标的可能同时命中两类记录,故必须逐条分流。
|
// 公司行为现金分红按每 10 份存储。Fund 标的可能同时命中两类记录,故必须逐条分流。
|
||||||
return payment.IsCorporateActionCashDividend
|
return payment.IsCorporateActionCashDividend
|
||||||
|
|||||||
@@ -357,6 +357,19 @@ namespace YLErp.Modules.SwapModule
|
|||||||
directionRatio);
|
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 分支)----
|
// ---- SwapPositionCompose 路径专用 seam(借鉴 testable 分支)----
|
||||||
|
|
||||||
/// <summary>查找收盘所需的活跃互换交易(生产: DbContext.trade.Where;测试: 内存列表)</summary>
|
/// <summary>查找收盘所需的活跃互换交易(生产: DbContext.trade.Where;测试: 内存列表)</summary>
|
||||||
@@ -611,6 +624,11 @@ namespace YLErp.Modules.SwapModule
|
|||||||
var corporateActionBeforePositions = BuildCorporateActionBeforePositions(
|
var corporateActionBeforePositions = BuildCorporateActionBeforePositions(
|
||||||
eodPositions, // 上一日终持仓
|
eodPositions, // 上一日终持仓
|
||||||
posiList);
|
posiList);
|
||||||
|
var corporateActionCashDividendBeforePositions = corporateActionBeforePositions
|
||||||
|
.Where(position => !string.IsNullOrWhiteSpace(position.UnderlyingCode)
|
||||||
|
&& exDividendByCode.TryGetValue(position.UnderlyingCode, out var dividend)
|
||||||
|
&& dividend.GiveCashAmount != 0m)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
// 交易首日恰逢 EffectiveDate 时,在内存克隆上生成除权后的开盘基线,应用生效日公司行为。
|
// 交易首日恰逢 EffectiveDate 时,在内存克隆上生成除权后的开盘基线,应用生效日公司行为。
|
||||||
// 有上一份 EOD 时沿用 PrepareFundOpeningEodPositions,避免重复套系数。
|
// 有上一份 EOD 时沿用 PrepareFundOpeningEodPositions,避免重复套系数。
|
||||||
@@ -627,7 +645,8 @@ namespace YLErp.Modules.SwapModule
|
|||||||
settleDate, // 收盘日期
|
settleDate, // 收盘日期
|
||||||
td, // 交易
|
td, // 交易
|
||||||
preSettleDate, // 上一交易日
|
preSettleDate, // 上一交易日
|
||||||
flowEvents); // 流水事件
|
flowEvents, // 流水事件
|
||||||
|
corporateActionCashDividendBeforePositions);
|
||||||
|
|
||||||
// 现金分红不在登记日直接累加;Copy/Update EOD 通过 CalcBondPayment
|
// 现金分红不在登记日直接累加;Copy/Update EOD 通过 CalcBondPayment
|
||||||
// 读取 EffectiveDate 命中的 ex_dividend_info,并生成 TdPosiDividend。
|
// 读取 EffectiveDate 命中的 ex_dividend_info,并生成 TdPosiDividend。
|
||||||
@@ -1435,7 +1454,8 @@ namespace YLErp.Modules.SwapModule
|
|||||||
DateTime settleDate,
|
DateTime settleDate,
|
||||||
trade td,
|
trade td,
|
||||||
DateTime preSettleDate,
|
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 settleDateStr = settleDate.ToString("yyyy-MM-dd");
|
||||||
string preSettleDateStr = preSettleDate.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 tdEodPosition = todyEodPositions.FirstOrDefault(x => x.PositionId == posi.id);//当前结算日日终持仓信息
|
||||||
var unwindEvents = flowEvents.Where(x => x.PositionId == posi.id).ToList();//当前日平仓信息
|
var unwindEvents = flowEvents.Where(x => x.PositionId == posi.id).ToList();//当前日平仓信息
|
||||||
var realPosition = realPosiList.FirstOrDefault(s => s.PositionId == posi.id);
|
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();
|
eod_swap_position eodPosi = new eod_swap_position();
|
||||||
if (eodPosition == null)
|
if (eodPosition == null)
|
||||||
{
|
{
|
||||||
eodPosi = SaveCurrentEodInitalPosi(posi, td, settleDate, preSettleDate, unwindEvents);
|
eodPosi = SaveCurrentEodInitalPosi(posi, td, settleDate, preSettleDate, unwindEvents, corporateActionBeforeQuantity);
|
||||||
}
|
}
|
||||||
else if (unwindEvents.Count() == 0)
|
else if (unwindEvents.Count() == 0)
|
||||||
{
|
{
|
||||||
eodPosi = CopyEodPosition(eodPosition, tdEodPosition, td, settleDate, preSettleDate);
|
eodPosi = CopyEodPosition(eodPosition, tdEodPosition, td, settleDate, preSettleDate, corporateActionBeforeQuantity);
|
||||||
}
|
}
|
||||||
else
|
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)}");
|
Log.Info($"eodPosi为:{JsonHelper.Serialize(eodPosi, false)}");
|
||||||
list.Add(eodPosi);
|
list.Add(eodPosi);
|
||||||
@@ -2655,7 +2677,7 @@ namespace YLErp.Modules.SwapModule
|
|||||||
/// <param name="todayPositions">当日日终归档信息</param>
|
/// <param name="todayPositions">当日日终归档信息</param>
|
||||||
/// <param name="swap_Deals">当日平仓/互换事件信息</param>
|
/// <param name="swap_Deals">当日平仓/互换事件信息</param>
|
||||||
/// <param name="td">交易信息</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)
|
if (curretEod == null)
|
||||||
{
|
{
|
||||||
@@ -2677,7 +2699,7 @@ namespace YLErp.Modules.SwapModule
|
|||||||
decimal tax = um.ValueAddedTax ?? 0;
|
decimal tax = um.ValueAddedTax ?? 0;
|
||||||
if (valueDate > td.StartDate.Value && curretEod.PosiQuantity > 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.TdPosiDividend = DividendCalc.AfterTax(payment, tax);
|
||||||
}
|
}
|
||||||
curretEod.PosiDividendSum = eod.PosiQuantity > 0 ? Math.Round(eod.PosiDividendSum + curretEod.TdPosiDividend, 2) : 0;
|
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="curretEod"></param>
|
||||||
/// <param name="td"></param>
|
/// <param name="td"></param>
|
||||||
/// <param name="valueDate"></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)
|
if (curretEod == null)
|
||||||
{
|
{
|
||||||
@@ -2773,7 +2795,7 @@ namespace YLErp.Modules.SwapModule
|
|||||||
// 修改,互换事件会影响待实现的分红的,现在要算上
|
// 修改,互换事件会影响待实现的分红的,现在要算上
|
||||||
if (valueDate > td.StartDate.Value && (curretEod.PosiQuantity > 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.TdPosiDividend = DividendCalc.AfterTax(payment, tax);
|
||||||
}
|
}
|
||||||
curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl;
|
curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl;
|
||||||
@@ -2900,7 +2922,8 @@ namespace YLErp.Modules.SwapModule
|
|||||||
/// <param name="position"></param>
|
/// <param name="position"></param>
|
||||||
/// <param name="td"></param>
|
/// <param name="td"></param>
|
||||||
/// <param name="settleDate"></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();
|
eod_swap_position curretEod = new eod_swap_position();
|
||||||
var um = GetUnderlyingData(position.UnderlyingCode);
|
var um = GetUnderlyingData(position.UnderlyingCode);
|
||||||
@@ -2953,7 +2976,7 @@ namespace YLErp.Modules.SwapModule
|
|||||||
if (!hasSwapEvent && settleDate > td.StartDate.Value && curretEod.PosiQuantity > 0)
|
if (!hasSwapEvent && settleDate > td.StartDate.Value && curretEod.PosiQuantity > 0)
|
||||||
{
|
{
|
||||||
decimal tax = um.ValueAddedTax ?? 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);
|
payment = DividendCalc.AfterTax(payment, tax);
|
||||||
//var consumedDividend = CalcConsumedDividend(curretEod, unwindEvents); 首日应该没有分红
|
//var consumedDividend = CalcConsumedDividend(curretEod, unwindEvents); 首日应该没有分红
|
||||||
curretEod.TdPosiDividend = payment;
|
curretEod.TdPosiDividend = payment;
|
||||||
|
|||||||
Reference in New Issue
Block a user