- BondPayment 实体补映射 reg_date(债权登记日,对应 bond_payment_info.reg_date 列) - BondPaymentService.GetBondPayments 改按 reg_date 过滤(原错用 pay_date_PL/支付日;pay_date_PL 是理论付息日,仍属支付日,非登记日) - SwapDealService.GetPreEodPositionByDate 由 ValueDate < dealDate 改 <=,登记日当天手动互换读当日EOD分红而非T-1 - 新增 GLMS20260105_0006_RegisterDateDividendTest 回归测试(手工合成内存,先RED复现后GREEN 2/2) 根因:bond_payment_info 表仅有 pay_date_PL(理论付息日)/pay_date_act(实际付息日) 两个支付日,缺登记日; 代码用支付日判定票息归属,且手动互换只读 T-1 EOD 快照;登记日≠支付日(恰差一工作日)时缺陷被掩盖, 导致对话框分红收益显示 0。
180 lines
7.6 KiB
C#
180 lines
7.6 KiB
C#
using BaseOUDAL;
|
|
using DocumentFormat.OpenXml.Bibliography;
|
|
using ExcelDataReader.Log;
|
|
using YLErp.DBModels;
|
|
using YLErp.Helpers;
|
|
|
|
namespace YLErp.Modules.EodModule
|
|
{
|
|
/// <summary>
|
|
/// 债券期间付息服务
|
|
/// </summary>
|
|
public class BondPaymentService : YLBaseService
|
|
{
|
|
private static IYcLogger Log = LogFactory.GetLogger(nameof(BondPaymentService));
|
|
public BondPaymentService(OptUserInfo userInfo) : base(userInfo)
|
|
{
|
|
|
|
}
|
|
|
|
public SearchListResult<BondPaymentDto> SearchList(BondPaymentReq req)
|
|
{
|
|
var valueDtStart = req.ValueDateStart.Year > 2000 ? req.ValueDateStart : DateTime.Today.AddYears(-1);
|
|
var valueDtEnd = req.ValueDateEnd.Year > 2000 ? req.ValueDateEnd.AddDays(1) : DateTime.Today.AddYears(1);
|
|
|
|
var predicatUn = PredicateBuilder.Create<underlying_manager>(d => d.LaunchState == "1");
|
|
var predicatEoc = PredicateBuilder.Create<BondPayment>(source => source.payment_date >= valueDtStart && source.payment_date < valueDtEnd);
|
|
|
|
if (!string.IsNullOrEmpty(req.DataSource))
|
|
{
|
|
predicatEoc = predicatEoc.And(d => d.channel_source.Contains(req.DataSource));
|
|
}
|
|
if (!string.IsNullOrEmpty(req.MarketName))
|
|
{
|
|
predicatUn = predicatUn.And(d => d.MarketName == req.MarketName);
|
|
}
|
|
if (!string.IsNullOrEmpty(req.UnderlyingCode))
|
|
{
|
|
predicatEoc = predicatEoc.And(d => d.underlyingCode.Contains(req.UnderlyingCode));
|
|
}
|
|
if (string.IsNullOrEmpty(req.sidx))
|
|
{
|
|
req.sidx = "payment_date";
|
|
req.sord = "desc";
|
|
}
|
|
var queryUn = DbContext.underlying_manager.Where(predicatUn).Select(n => new { n.id, n.MarketName, n.UnderlyingCode, n.UnderlyingName, n.UnderlyingInstrumentType, n.InnerCode });
|
|
var query = from un in queryUn
|
|
join source in DbContext.bondPayment.Where(predicatEoc) on un.UnderlyingCode equals source.underlyingCode
|
|
select new BondPaymentDto
|
|
{
|
|
id = source.id,
|
|
channel_source = source.channel_source,
|
|
MarketName = un.MarketName,
|
|
security_id = un.UnderlyingCode,
|
|
symbol = un.UnderlyingName,
|
|
coupon_rate = source.coupon_rate,
|
|
payment_date = source.payment_date,
|
|
payment_interest = source.payment_interest,
|
|
payment_parvalue = source.payment_parvalue,
|
|
create_time = source.create_time,
|
|
update_time = source.update_time
|
|
};
|
|
var result = query.ToSearchList(req);
|
|
return result;
|
|
}
|
|
|
|
public BondPayment SaveBondPayment(BondPayment req)
|
|
{
|
|
if (req is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(req));
|
|
}
|
|
BondPayment dbmodel;
|
|
|
|
if (req.id == 0)
|
|
{
|
|
DbContext.bondPayment.Add(dbmodel = req);
|
|
}
|
|
else
|
|
{
|
|
dbmodel = DbContext.bondPayment.Find(req.id);
|
|
if (dbmodel == null)
|
|
{
|
|
throw new ServiceException("数据不存在");
|
|
}
|
|
UpdateChanges(dbmodel, req);
|
|
}
|
|
dbmodel.update_time = DateTime.Now;
|
|
DbContext.SaveChanges();
|
|
|
|
return dbmodel;
|
|
}
|
|
/// <summary>
|
|
/// 获取某债券的期间付息情况集合
|
|
/// </summary>
|
|
/// <param name="underylingCode"></param>
|
|
/// <param name="startDate"></param>
|
|
/// <param name="endDate"></param>
|
|
/// <returns></returns>
|
|
public List<BondPayment> GetBondPayments(string underlyingCode, DateTime startDate, DateTime endDate)
|
|
{
|
|
// GLMS-20260105-0006:票息归属按债权登记日(reg_date)判定,而非支付日(pay_date_PL/pay_date_act)。
|
|
// 登记日当天 EOD 即应计提;原按支付日口径会让"登记日≠支付日"的债券漏计(二者恰差一工作日时缺陷被掩盖)。
|
|
var result = QueryBondPayments(underlyingCode)
|
|
.Where(x => x.reg_date > startDate && x.reg_date <= endDate)
|
|
.AsNoTracking().ToList();
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 可测性 seam:返回某债券的全部付息记录(未做日期过滤)。测试可 override 注入内存数据,
|
|
/// 以验证日期口径(GLMS-20260105-0006:应按债权登记日 reg_date 而非支付日 pay_date_PL/pay_date_act 判定)。
|
|
/// </summary>
|
|
protected virtual IQueryable<BondPayment> QueryBondPayments(string underlyingCode)
|
|
=> DbContext.bondPayment.Where(x => x.underlyingCode == underlyingCode);
|
|
|
|
|
|
public List<BondPayment> GetTargetDatePayments(string underlyingCode, DateTime targetDate)
|
|
{
|
|
var startDate = targetDate.Date;
|
|
var endDate = startDate.AddDays(1);
|
|
return DbContext.bondPayment.AsNoTracking().Where(x => x.underlyingCode == underlyingCode && x.payment_date >= startDate && x.payment_date < endDate).ToList();
|
|
}
|
|
/// <summary>
|
|
/// 计算某债券某段时间的期间付息
|
|
/// </summary>
|
|
/// <param name="underylingCode">债券代码</param>
|
|
/// <param name="startDate">计息开始日</param>
|
|
/// <param name="endDate">计息结束日</param>
|
|
/// <param name="qty">持仓数量</param>
|
|
/// <param name="longRatio">多空方向</param>
|
|
/// <param name="payDirection">收支方向</param>
|
|
/// <returns></returns>
|
|
public decimal CalcPayment(string underlyingCode, DateTime startDate, DateTime endDate, decimal qty, decimal longRatio, decimal payDirection)
|
|
{
|
|
var payments = GetBondPayments(underlyingCode, startDate, endDate);
|
|
return CalcPayment(payments, qty, longRatio, payDirection);
|
|
}
|
|
/// <summary>
|
|
/// 计算某债券期间付息
|
|
/// </summary>
|
|
/// <param name="payments">期间付息集合</param>
|
|
/// <param name="qty">持仓数量</param>
|
|
/// <param name="longRatio">多空方向</param>
|
|
/// <param name="payDirection">收支方向</param>
|
|
/// <returns></returns>
|
|
public decimal CalcPayment(List<BondPayment> payments, decimal qty, decimal longRatio, decimal payDirection)
|
|
{
|
|
var interest = payments.Sum(s => s.payment_interest ?? 0);
|
|
// interest 为每 100 元面值的票息,×qty 后需 ÷100 转为实际金额(与入库价格 bondPriceMultiple 同口径)
|
|
return BondPriceConverter.ToStorage(interest * qty) * longRatio * payDirection;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class BondPaymentReq : BaseSearchReq
|
|
{
|
|
/// <summary>
|
|
/// 数据来源
|
|
/// </summary>
|
|
public string DataSource { get; set; }
|
|
|
|
/// <summary>
|
|
/// 标的代码
|
|
/// </summary>
|
|
public string UnderlyingCode { get; set; }
|
|
|
|
public DateTime ValueDateStart { get; set; }
|
|
|
|
public DateTime ValueDateEnd { get; set; }
|
|
// 市场
|
|
public string MarketName { get; set; }
|
|
}
|
|
public class BondPaymentDto : BondPayment
|
|
{
|
|
public string MarketName { get; set; }
|
|
}
|
|
}
|