using System.Text; using YLErp.DBModels.Consts; using YLErp.Models; using YLErp.Modules.TradeModule.DealModule; using YLErp.QdpModule; namespace YLErp.Modules.TradeModule.AccumulatorOptionModule { public class TradeAccumulatorChangeService : TradeActionDataService { public TradeAccumulatorChangeService(OptUserInfo userInfo) : base(userInfo) { } private static Func BasePredicate(int tradeId) => n => n.TradeId == tradeId && n.IsValid && n.ActionType == TradeActionType.AccumulatorChange; /// /// 获取历史操作数据 /// public IEnumerable GetHisDatas(int tradeId) { var hisPredicate = BasePredicate(tradeId); var hisDatas = new TradeActionQueryService(this).GetDatas(hisPredicate); return hisDatas.Select(n => { var data = JsonHelper.Deserialize(n.ActionData); data.ValueDate = n.ValueDate.OtcFormatDate(); return data; }).ToArray(); } #region----保存/删除换月数据---- /// /// 保存换月数据 /// /// /// /// 是否删除累计收益记录 public TradeAccumulatorChangeResult SaveChangeData(int tradeId, TradeAccumulatorChangeData changeData, bool removeObCheckDatas) { if (!DateTime.TryParse(changeData.ValueDate, out var valueDate)) { throw new ServiceException("解析换月日期失败:" + changeData.ValueDate); } var td = DbContext.trade.Find(tradeId); if (td == null) { throw new ServiceException("找不到交易信息,tradeId:" + tradeId); } if (td.TradeStatus != "确认成交") { throw new ServiceException("当前交易状态不符合设置换月的条件"); } if (valueDate <= td.TradeDate.Value) { throw new ServiceException($"换月日期'需大于交易日期'{td.TradeDate:yyyy-MM-dd}'"); } if (valueDate >= td.ExerciseDate.Value) { throw new ServiceException($"换月日期需小于到期日期'{td.ExerciseDate:yyyy-MM-dd}'"); } if (valueDate > BLL.valuedateBLL.ValueDate) { throw new ServiceException("换月日期不能大于系统日期:" + changeData.ValueDate); } var tdAcc = DbContext.trade_accumulator_option.Where(n => n.TradeId == tradeId).FirstOrDefault(); if (tdAcc == null) { throw new ServiceException("找不到交易扩展信息,交易编号:" + td.TradeNumber); } var um = DataCacheProvider.GetUnderlyingDataSource().GetData(changeData.UnderlyingCode); if (um == null) { throw new ServiceException("找不到标的信息:" + changeData.UnderlyingCode); } if (um.IsFutures() && um.MaturityDate.HasValue && um.MaturityDate < valueDate) { throw new ServiceException($"所选标的'{changeData.UnderlyingCode}'的到期日'{um.MaturityDate:yyyy-MM-dd}'应大于换月日期'{valueDate:yyyy-MM-dd}'"); } //按换月日期正序排序获取换月设置集合 var basePredicate = BasePredicate(td.id); var dbActions = DbContext.TradeAction.Where(basePredicate).OrderBy(n => n.ValueDate).ToArray(); //将已有Action数据无效 foreach (var exist in dbActions.Where(x => x.ValueDate == valueDate)) { exist.IsValid = false; } //更新交易数据(如果当前存入的换月数据是最新一条数据) var tradeUpdated = false; TradeAction firstDayAction = null; var nextAction = dbActions.FirstOrDefault(a => a.ValueDate > valueDate); if (nextAction == null)//没有大于当前换月日期的数据则需要更新交易要素 { if (valueDate != td.TradeDate.Value && !dbActions.Any(a => a.ValueDate == td.TradeDate.Value)) { firstDayAction = GetFirstDayChangeData(td, tdAcc); } tradeUpdated = true; RestoreAccumulatorChangeData(td, tdAcc, changeData, um); } else { tradeUpdated = true; UpdateTradeObDates(tdAcc, changeData, nextAction.ValueDate); } //新增Action数据 var curAction = CreateTradeAction(tradeId, valueDate, TradeActionType.AccumulatorChange); curAction.ActionRemark = "换月"; curAction.ActionData = JsonHelper.Serialize(changeData); var addActions = firstDayAction != null ? new[] { firstDayAction, curAction } : new[] { curAction }; //移除累计收益记录 if (removeObCheckDatas) { var nextDate = dbActions.Where(n => n.ValueDate > valueDate).FirstOrDefault()?.ValueDate; if (!nextDate.HasValue) { nextDate = td.ExerciseDate.Value.AddYears(1); } var obDatas = DbContext.autocall_observation.Where(n => n.TradeId == td.id && n.EndDate >= valueDate && n.EndDate < nextDate).ToArray(); if (obDatas.Any()) { tradeUpdated = true; DbContext.autocall_observation.RemoveRange(obDatas); //var cashIds = obDatas.Where(n => n.CashId > 0).Select(n => n.CashId ?? 0).ToArray(); //RemoveEodTradeAndFutureInfo(saveChanges: false, tradeId: td.id, removeStartDate: valueDate, tradeCashIds: cashIds, deleteOtherCashs: false); } //移除自定义观察数据 var manualPriceDatas = DbContext.manually_trade_observation_price.Where(n => n.TradeId == td.id && n.ValueDate >= valueDate).ToArray(); DbContext.manually_trade_observation_price.RemoveRange(manualPriceDatas); new TradeBackService(this).Execute(tradeId, valueDate); } DbContext.TradeAction.AddRange(addActions); AddTradeOperationHistoryAndSetParentTradeInfo(saveChanges: false, trade: td, optType: "设置累计换月", comments: $"{changeData.ValueDate},{changeData.UnderlyingCode}"); #region 更新metadic的观察频率数据 var tMeta = DbContext.TradeMeta.FirstOrDefault(n => n.TradeId == tradeId && n.MetaKey == ConsTradeMetaKey.koObservationRate); if (tMeta != null) { tMeta.MetaValue = changeData.KOObservation; } else { tMeta = new TradeMeta { TradeId = tradeId, MetaKey = ConsTradeMetaKey.koObservationRate, MetaValue = changeData.KOObservation, CreateTime = DateTime.Now }; DbContext.TradeMeta.Add(tMeta); } #endregion DbContext.SaveChanges(); return new TradeAccumulatorChangeResult { Actions = addActions, TradeUpdated = tradeUpdated }; } //还原或更新更新交易要素 private void RestoreAccumulatorChangeData(OtcTradeBase td, trade_accumulator_option tdAcc, TradeAccumulatorChangeData changeData, underlying_manager um = null) { var clone = changeData.Clone(); var isFixed = tdAcc.PayoffType == "固定"; var preList = ParseKoObservation(tdAcc.KOObservationDates, isFixed ? null : tdAcc.KOObservationSettleDates, td.TradeDate.Value, DateTime.Parse(changeData.ValueDate)); var curList = ParseKoObservation(changeData.KOObservationDates, isFixed ? null : changeData.KOObservationSettleDates, DateTime.MinValue); preList.AddRange(curList); (string KOObservationDates, string KOObservationSettleDates) = BuildKOObservationDates(preList, true); clone.KOObservationDates = KOObservationDates; clone.KOObservationSettleDates = KOObservationSettleDates; TradeAccumulatorHelper.RestoreAccumulatorChangeData(td, tdAcc, clone, um); } //更新交易要素--观察频率 private void UpdateTradeObDates(trade_accumulator_option tdAcc, TradeAccumulatorChangeData curChangeData, DateTime nextValueDate) { var isFixed = tdAcc.PayoffType == "固定"; var curValueDate = DateTime.Parse(curChangeData.ValueDate); var preList = ParseKoObservation(tdAcc.KOObservationDates, isFixed ? null : tdAcc.KOObservationSettleDates, DateTime.MinValue, curValueDate); var curList = ParseKoObservation(curChangeData.KOObservationDates, isFixed ? null : curChangeData.KOObservationSettleDates, DateTime.MinValue, nextValueDate); var nextList = ParseKoObservation(tdAcc.KOObservationDates, isFixed ? null : tdAcc.KOObservationSettleDates, nextValueDate); preList.AddRange(curList); preList.AddRange(nextList); (string KOObservationDates, string KOObservationSettleDates) = BuildKOObservationDates(preList, true); tdAcc.KOObservationDates = KOObservationDates; tdAcc.KOObservationSettleDates = KOObservationSettleDates; } //获取交易开始日期的换月存储数据 private TradeAction GetFirstDayChangeData(OtcTradeBase td, trade_accumulator_option tdAcc) { var action = CreateTradeAction(td.id, td.TradeDate.Value, TradeActionType.AccumulatorChange); action.ActionRemark = "换月"; var data = new TradeAccumulatorChangeData { KOBarrier = tdAcc.KOBarrier, KOObservationDates = tdAcc.KOObservationDates, KOObservationSettleDates = tdAcc.KOObservationSettleDates, Strike = td.Strike ?? 0, UnderlyingCode = td.UnderlyingCode, ValueDate = td.TradeDate.Value.ToString("yyyy-MM-dd") }; action.ActionData = JsonHelper.Serialize(data); return action; } /// /// 删除换月操作 /// public TradeAccumulatorChangeResult RemoveChangeData(DateTime valueDate, int tradeId) { var td = DbContext.trade.Find(tradeId); if (td == null) { throw new ServiceException("交易信息不存在"); } if (valueDate == td.TradeDate.Value) { throw new ServiceException("初始记录不允许删除"); } var predicate = BasePredicate(tradeId); var dbActions = DbContext.TradeAction.Where(predicate).OrderBy(n => n.ValueDate).ToList(); var index = dbActions.FindIndex(n => n.ValueDate == valueDate); if (index < 0) { throw new ServiceException("操作记录已经不存在"); } if (index == 0) { //不应该出现的情况,如果出现了还是报错吧 throw new ServiceException("初始记录丢失,操作不能继续"); } var removeAction = dbActions[index]; var tradeUpdated = false; var invalidActions = new List { removeAction }; var tdAcc = DbContext.trade_accumulator_option.Where(n => n.TradeId == tradeId).FirstOrDefault(); if (tdAcc == null) { throw new ServiceException("找不到交易扩展信息,交易编号:" + td.TradeNumber); } //交易数据还原到上条操作状态 if (index == dbActions.Count - 1) { if (index == 0) { throw new ServiceException("未找到初始记录,操作不能继续"); } var preAction = dbActions[index - 1]; if (string.IsNullOrWhiteSpace(preAction.ActionData)) { throw new ServiceException("上条操作记录备份数据已经被损坏,交易数据还原失败,操作不能继续"); } var changeData = JsonHelper.Deserialize(preAction.ActionData); var um = DataCacheProvider.GetUnderlyingDataSource().GetData(changeData.UnderlyingCode); if (um == null) { throw new ServiceException("交易数据还原失败,找不到标的信息:" + changeData.UnderlyingCode); } RestoreAccumulatorChangeData(td, tdAcc, changeData, um); if (preAction.ValueDate == td.TradeDate.Value) { preAction.IsValid = false; invalidActions.Insert(0, preAction); } tradeUpdated = true; } else { var preAction = dbActions[index - 1]; var preData = JsonHelper.Deserialize(preAction.ActionData); UpdateTradeObDates(tdAcc, preData, dbActions[index + 1].ValueDate); tradeUpdated = true; } removeAction.IsValid = false; var curChangeData = JsonHelper.Deserialize(removeAction.ActionData); AddTradeOperationHistoryAndSetParentTradeInfo(saveChanges: false, trade: td, optType: "移除累计换月", comments: $"{valueDate:yyyy-MM-dd},{curChangeData.UnderlyingCode}"); DbContext.SaveChanges(); return new TradeAccumulatorChangeResult { Actions = invalidActions, TradeUpdated = tradeUpdated }; } #endregion /// /// 根据提供的数据获取换期数据 /// public TradeAccumulatorChangeDataEx GetChangeData(DateTime valueDate, OtcTradeBase td, trade_accumulator_option tdAcc = null) { if (td is null) { throw new ArgumentNullException(nameof(td)); } if (valueDate > td.ExerciseDate.Value) { valueDate = td.ExerciseDate.Value; } var predicate = BasePredicate(td.id); var query = from a in DbContext.TradeAction.Where(predicate) orderby a.ValueDate descending select new { a.ValueDate, a.ActionData }; var dbActions = query.ToArray(); TradeAccumulatorChangeDataEx changeData; var dbAction = query.Where(x => x.ValueDate <= valueDate).FirstOrDefault(); if (dbAction == null) { if (tdAcc == null) { tdAcc = DbContext.trade_accumulator_option.AsNoTracking().FirstOrDefault(n => n.TradeId == td.id); } changeData = TradeAccumulatorHelper.GetDefaultChangeData(valueDate, td, tdAcc); var list = ParseKoObservation(changeData.KOObservationDates, changeData.KOObservationSettleDates, valueDate); (string KOObservationDates, string KOObservationSettleDates) = BuildKOObservationDates(list, false); changeData.KOObservationDates = KOObservationDates; changeData.KOObservationSettleDates = KOObservationSettleDates; } else { changeData = JsonHelper.Deserialize(dbAction.ActionData); changeData.ValueDate = valueDate.OtcFormatDate(); if (dbAction.ValueDate < valueDate) { var list = ParseKoObservation(changeData.KOObservationDates, changeData.KOObservationSettleDates, valueDate); (string KOObservationDates, string KOObservationSettleDates) = BuildKOObservationDates(list, false); changeData.KOObservationDates = KOObservationDates; changeData.KOObservationSettleDates = KOObservationSettleDates; } } var nextDate = dbActions.Where(x => x.ValueDate > valueDate).LastOrDefault()?.ValueDate; changeData.NextDate = (nextDate ?? td.ExerciseDate.Value).OtcFormatDate(); if (!nextDate.HasValue) { nextDate = td.ExerciseDate.Value.AddDays(1); } changeData.HasObCheck = DbContext.autocall_observation.Any(n => n.TradeId == td.id && n.EndDate >= valueDate && n.EndDate < nextDate); return changeData; } #region----观察数据静态方法处理---- //解析敲出观察数据(包含startDate但不包含endDate) private static List ParseKoObservation(string KOObservationDates, string KOObservationSettleDates , DateTime startDate, DateTime? endDate = null) { var customObservDates = QdpHelper.ParseAutocallCustomizedInfo(KOObservationDates); var obDates = customObservDates.Item1; if (!endDate.HasValue) { endDate = DateTime.MaxValue; } if (obDates != null) { var list = new List(obDates.Length); var customKOBarriers = customObservDates.Item2; var customCoupons = customObservDates.Item3; var settleDates = QdpHelper.ParseObservationDate(KOObservationSettleDates); for (var i = 0; i < obDates.Length; i++) { var dt = obDates[i].DateTime; if (dt >= startDate && dt < endDate) { var item = new KoObservationItem { date = dt }; if (customKOBarriers != null && customKOBarriers.Length > i) { item.barrier = customKOBarriers[i]; } if (customCoupons != null && customCoupons.Length > i) { item.coupon = customCoupons[i]; } if (settleDates != null && settleDates.Length > i) { item.settleDate = settleDates[i].DateTime; } list.Add(item); } } return list; } return new List(); } /// /// 获取累计期权平仓定价日的观察日期 /// /// 观察日期 /// 定价日期或平仓日期 /// 交易日期 /// 到期日期 public int GetAccTradeUnwindDayCount(string kOObservationDates, DateTime? valueDate, DateTime? tradeDate, DateTime? exerciseDate) { var dayCount = 0; var startDate = (valueDate ?? tradeDate).GetValueOrDefault(); if (kOObservationDates != null) { var koModel = QdpHelper.ParseAutocallCustomizedInfo(kOObservationDates); if (valueDate != null) { dayCount = koModel.Item1.Count(O => O.DateTime >= valueDate); } else { dayCount = koModel.Item1.Count(O => O.DateTime >= startDate); } } else { if (startDate == tradeDate)//默认观察日列表不包括首日观察 { startDate = startDate.AddDays(1); } dayCount = QdpCalendarHelper.GetWorkDayCount(startDate, exerciseDate.GetValueOrDefault()) + 1; } return dayCount; } /// /// 组合观察数据项到字符串 /// /// /// 如果结算日为空是否使用观察日 private static (string KOObservationDates, string KOObservationSettleDates) BuildKOObservationDates(List itemList , bool useObDateAsSettleDateIfEmpty) { if (itemList == null || !itemList.Any()) { return (string.Empty, string.Empty); } var sb_obdates = new StringBuilder(512); var sb_barrier = new StringBuilder(512); var sb_coupon = new StringBuilder(512); var sb_stdates = new StringBuilder(512); foreach (var item in itemList) { sb_obdates.Append(item.date.ToString("yyyy-MM-dd")).Append(','); if (item.barrier.HasValue) { sb_barrier.Append(item.barrier).Append(','); } if (item.coupon.HasValue) { sb_coupon.Append(item.coupon).Append(','); } if (item.settleDate.HasValue) { sb_stdates.Append(item.settleDate?.ToString("yyyy-MM-dd")).Append(','); } else if (useObDateAsSettleDateIfEmpty) { sb_stdates.Append(item.date.ToString("yyyy-MM-dd")).Append(','); } } sb_obdates.Remove(sb_obdates.Length - 1, 1).Append(';'); if (sb_barrier.Length > 0) { sb_barrier.Remove(sb_barrier.Length - 1, 1); sb_obdates.Append(sb_barrier.ToString()).Append(';'); } if (sb_coupon.Length > 0) { sb_coupon.Remove(sb_coupon.Length - 1, 1); sb_obdates.Append(sb_coupon.ToString()); } if (sb_stdates.Length > 0) { sb_stdates.Remove(sb_stdates.Length - 1, 1); } return (sb_obdates.ToString(), sb_stdates.ToString()); } #endregion } public class TradeAccumulatorChangeData { /// /// 执行日期 /// public string ValueDate { get; set; } /// /// 标的代码 /// public string UnderlyingCode { get; set; } /// /// 执行价格 /// public double Strike { get; set; } /// /// 敲出障碍价格 /// public double? KOBarrier { get; set; } /// /// 观察日期 /// public string KOObservationDates { get; set; } /// /// 结算日期 /// public string KOObservationSettleDates { get; set; } /// /// 观察频率 /// public string KOObservation { get; set; } public TradeAccumulatorChangeData Clone() { return (TradeAccumulatorChangeData)MemberwiseClone(); } public override string ToString() { return $"{ValueDate:yyyy-MM-dd}--{UnderlyingCode}"; } } public class TradeAccumulatorChangeDataEx : TradeAccumulatorChangeData { /// /// 下一日 /// public string NextDate { get; set; } /// /// 存在累计收益 /// public bool HasObCheck { get; set; } } /// /// 换月操作结果数据 /// public class TradeAccumulatorChangeResult { /// /// 是否交易数据被更新 /// public bool TradeUpdated { get; set; } /// /// 产生的或受影响的actions /// public IEnumerable Actions { get; set; } } }