Files
zszq-trs/YLErpDAL/Modules/SwapModule/SwapEventService.cs
T
张名锐 b2f4e16782 feat(bond): 支持股票和基金现金分红纳入债券支付计算 - 收紧过度开发行为
- 实现股票/基金现金分红数据从 ex_dividend_info 同步到 BondPayment
- 新增公司行为去重机制,避免镜像任务完成后重复计息
- 统一现金分红存储口径为"每 10 份派现金额",保持与同步任务一致性
- 修改 CalcPayment 方法,股票/基金分红需除以 10 转换实际现金金额
- 添加单元测试验证债券票息和股票/基金分红的不同计算方式
- 更新文档注释说明"每 10 份派现金额"存储规范
- 修复公司行为生效日处理逻辑,确保正确应用除权系数
- 扩展测试覆盖股票类证券的公司行为处理场景
2026-08-20 13:30:36 +08:00

309 lines
13 KiB
C#

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using YLErp.DBModels;
namespace YLErp.Modules.SwapModule
{
public class SwapEventService : SwapTradeBaseService
{
public SwapEventService(OptUserInfo optUser) : base(optUser)
{
}
public SwapEventService(YLBaseService baseService) : base(baseService)
{
}
/// <summary>
/// 生成事件
/// </summary>
/// <param name="tradeDate">日期</param>
/// <param name="tradeId">互换交易码</param>
/// <param name="eventType">事件类型</param>
/// <param name="data">json数据</param>
/// <param name="save">是否保存</param>
/// <param name="clientCashId"></param>
/// <param name="eventRession"></param>
/// <param name="invalid">是否无效</param>
/// <returns></returns>
public swap_event AddSwapEventDate(DateTime tradeDate, int tradeId, int eventType, string data, int clientCashId, bool save = false,string eventRession="",bool invalid=false)
{
var swap_Event = new swap_event()
{
EventType = eventType,
EventData = data,
EventReason= eventRession,
SwapTradeId = tradeId,
ClientCashId = clientCashId,
Invalid = invalid,
ValueDate = tradeDate,
};
UpdateDbOption(swap_Event);
DbContext.swap_event.Add(swap_Event);
if (save)
{
DbContext.SaveChanges();
}
return swap_Event;
}
public swap_event UpdateSwapEvent()
{
var swapEvent= DbContext.swap_event.Where(x=>x.EventType== (int)SwapEventTypeEnum.新增交易 || x.EventType == (int)SwapEventTypeEnum.修改交易).OrderByDescending(o=>o.id).FirstOrDefault();
if (swapEvent!=null)
{
swapEvent.Invalid= false;
DbContext.SaveChanges();
}
return swapEvent;
}
/// <summary>
/// 获取上一日终归档日期
/// </summary>
/// <param name="tradeDate">当前清算日期</param>
/// <returns></returns>
public DateTime GetSwapEventDate(DateTime tradeDate, int tradeId, int? eventType)
{
Expression<Func<swap_event, bool>> expression = x => x.SwapTradeId == tradeId && x.ValueDate < tradeDate && !x.Invalid;
if (eventType.HasValue)
{
expression = expression.And(x => x.EventType == eventType);
}
var swapEvent = DbContext.swap_event.Where(expression).OrderByDescending(o => o.ValueDate).FirstOrDefault();
if (swapEvent != null)
{
return swapEvent.ValueDate;//上一日终归档日期
}
else//不存在归档日期
{
return tradeDate;
}
}
/// <summary>
/// 保存展期信息
/// </summary>
/// <param name="swap_Event"></param>
/// <returns></returns>
/// <exception cref="ServiceException"></exception>
public swap_event SaveExtensionTime(swap_event swap_Event)
{
var obj = DbContext.swap_event.Where(O => O.id == swap_Event.id).FirstOrDefault();
var trade = DbContext.trade.Where(O => O.id == swap_Event.SwapTradeId).FirstOrDefault();
if (trade == null)
{
throw new ServiceException("未找到交易,请刷新页面后再次尝试!");
}
var oldMaturityDate = trade.ExerciseDate.Value;
if (obj != null && !string.IsNullOrEmpty(obj.EventData))
{
oldMaturityDate = JsonConvert.DeserializeObject<ExtenstionData>(obj.EventData).OldMaturityDate;
}
if (swap_Event.extenstionData.NewMaturityDate <= oldMaturityDate)
{
throw new ServiceException("新到期日应晚于原到期日!");
}
if (obj != null && obj.ValueDate >= swap_Event.ValueDate)
{
throw new ServiceException("新展期日应晚于原展期日!");
}
if (swap_Event.extenstionData.NewMaturityDate< trade.StartDate)
{
throw new ServiceException("新到期日不应早于交易开始日期!");
}
swap_Event.extenstionData.OldMaturityDate = oldMaturityDate;
string data = JsonConvert.SerializeObject(swap_Event.extenstionData);
if (obj != null)
{
obj.Invalid = true;
}
swap_Event.EventReason = "交易展期";
trade.ExerciseDate = swap_Event.extenstionData.NewMaturityDate;
//修改互换观察日到期日
new SwapTradeService(this).UpdateObservationDay(trade.id, swap_Event.extenstionData.OldMaturityDate, trade.ExerciseDate.Value);
swap_Event = AddSwapEventDate(swap_Event.ValueDate, swap_Event.SwapTradeId, (int)SwapEventTypeEnum.展期, data, 0,false, swap_Event.EventReason);
DbContext.SaveChanges();
return swap_Event;
}
/// <summary>
/// 删除 展期
/// </summary>
/// <param name="id"></param>
/// <exception cref="ServiceException"></exception>
public void DeleteExtensionTime(long id)
{
var obj = DbContext.swap_event.Where(O => O.id == id).FirstOrDefault();
if (obj==null)
{
throw new ServiceException("未找到展期记录,请刷新页面后再次尝试!");
}
var trade = DbContext.trade.Where(O => O.id == obj.SwapTradeId).FirstOrDefault();
obj.Invalid = true;
obj.EventReason = "交易展期撤销";
if (!string.IsNullOrEmpty(obj.EventData))
{
obj.extenstionData = JsonConvert.DeserializeObject<ExtenstionData>(obj.EventData);
//修改到期日,互换观察日
new SwapTradeService(this).UpdateObservationDay(trade.id, trade.ExerciseDate.Value, obj.extenstionData.OldMaturityDate);
trade.ExerciseDate = obj.extenstionData.OldMaturityDate;
}
DbContext.SaveChanges();
}
/// <summary>
/// 获取当前展期记录
/// </summary>
/// <param name="tradeId"></param>
/// <returns></returns>
public swap_event GetExtensionTime(int tradeId)
{
var trade = DbContext.trade.Where(O => O.id == tradeId).FirstOrDefault();
if (trade == null)
{
throw new ServiceException("未找到交易,请刷新页面后再次尝试!");
}
var extension = DbContext.swap_event.Where(x => !x.Invalid && x.SwapTradeId == tradeId && x.EventType == (int)SwapEventTypeEnum.展期).FirstOrDefault();
if (extension==null)
{
extension = new swap_event();
extension.SwapTradeId = tradeId;
extension.ValueDate = DateTime.Now;
extension.extenstionData = new ExtenstionData()
{
OldMaturityDate = trade.ExerciseDate.Value,
NewMaturityDate=DateTime.Now
};
}
else
{
if (!string.IsNullOrEmpty(extension.EventData))
{
extension.extenstionData = JsonConvert.DeserializeObject<ExtenstionData>(extension.EventData);
}
}
extension.SwapTradeNumber = trade.TradeNumber;
return extension;
}
/// <summary>
/// 获取历史展期信息
/// </summary>
/// <param name="tradeId"></param>
/// <returns></returns>
public List<swap_event> GetExtenstionTimeEvents(int tradeId)
{
var events= DbContext.swap_event.Where(x => x.Invalid && x.SwapTradeId == tradeId&&x.EventType==(int)SwapEventTypeEnum.展期).OrderByDescending(o => o.ValueDate).ToList();
events.ForEach(x =>
{
if (!string.IsNullOrEmpty(x.EventData))
{
x.extenstionData = JsonConvert.DeserializeObject<ExtenstionData>(x.EventData);
}
});
return events;
}
/// <summary>
/// 获取交易操作历史。登记日创建但尚未到 EffectiveDate 的公司行为事件也保留,
/// 由 EventData.Applied=false 表示“待生效”,保证审计日志完整可追溯。
/// </summary>
/// <param name="tradeId">交易id</param>
/// <returns></returns>
public List<swap_event> GetOpreationHistorys(int tradeId)
{
List<swap_event> list = DbContext.swap_event
.Where(x => x.SwapTradeId == tradeId)
.OrderByDescending(o => o.id)
.ToList();
return list;
}
/// <summary>
/// 将 swap_event.EventData 安全反序列化为公司行为快照。事件为空、EventData
/// 为空白或 JSON 格式不匹配时返回 false 并将 data 置 null,调用方据此保留旧格式记录。
/// </summary>
public static bool TryDeserializeCorporateActionEventData(
swap_event swapEvent,
out CorporateActionEventData data)
{
data = null;
if (swapEvent == null || string.IsNullOrWhiteSpace(swapEvent.EventData))
{
return false;
}
try
{
data = JsonConvert.DeserializeObject<CorporateActionEventData>(swapEvent.EventData);
return data != null;
}
catch (JsonException)
{
return false;
}
}
/// <summary>
/// 公司行为说明使用稳定的键值格式,完整保留调整前后名义本金、价格、数量、
/// 待实现分红和现金流变化,操作历史无需重新计算即可核对。
/// </summary>
public static string BuildCorporateActionEventReason(CorporateActionEventData data)
{
if (data == null)
{
return "公司行为快照为空";
}
// 使用 InvariantCulture 固定小数与日期格式,说明文本不随服务器区域设置变化。
string D(decimal value) => value.ToString(CultureInfo.InvariantCulture);
string Date(DateTime? value) => value.HasValue
? value.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
: "";
return string.Join("; ", new[]
{
$"公司行为[{data.UnderlyingCode}]",
$"ExDividendDate={Date(data.ExDividendDate)}",
$"EffectiveDate={Date(data.EffectiveDate)}",
$"ExDividendInfoId={data.ExDividendInfoId}",
$"PositionId={data.PositionId}",
$"GiveCashAmount={D(data.GiveCashAmount)}",
$"GiveShareAmount={D(data.GiveShareAmount)}",
$"Split={(data.Split.HasValue ? D(data.Split.Value) : "")}",
$"RationedSharesAmount={D(data.RationedSharesAmount)}",
$"RationedSharesPrice={D(data.RationedSharesPrice)}",
"调整前",
$"BeforeNotional={D(data.BeforeNotional)}",
$"BeforePrice={D(data.BeforePrice)}",
$"BeforeQuantity={D(data.BeforeQuantity)}",
$"BeforePendingDividend={D(data.BeforePendingDividend)}",
"调整后",
$"AfterNotional={D(data.AfterNotional)}",
$"AfterPrice={D(data.AfterPrice)}",
$"AfterQuantity={D(data.AfterQuantity)}",
$"AfterPendingDividend={D(data.AfterPendingDividend)}",
$"CashFlowChange={D(data.CashFlowChange)}",
$"Applied={data.Applied}"
});
}
public void DeleteEvent(int tradeId)
{
var events = DbContext.swap_event.Where(x => x.Invalid && x.SwapTradeId == tradeId).ToList();
var flowEvents= DbContext.swap_flow_event.Where(x => x.DataState>0 && x.SwapTradeId == tradeId).ToList();
events.ForEach((x) =>
{
x.Invalid = true;
});
flowEvents.ForEach((x) =>
{
x.DataState = (int)SwapFlowDateStateEnum.废弃;
});
DbContext.SaveChanges();
}
}
}