using Qdp.Foundation.Implementations; using Qdp.Pricing.Base.Implementations; using YLErp.Abstract.DataProviders; using YLErp.Modules.DataProviderModule; using YLErp.Modules.SalesModule; using YLErp.Modules.TradeModule.DealModule; using YLErp.QdpModule; namespace YLErp.Modules.TradeModule { /// /// /// public class TradeRangeAccrualService : ExoticOptionModule.TradeCashServiceEx { public TradeRangeAccrualService(OptUserInfo userInfo) : base(userInfo) { } public TradeRangeAccrualService(YLBaseService baseService) : base(baseService) { } public void CheckStatus(DateTime valueDate, IEodPriceProviderV2 priceProvider, DateTime? startDate = null , Action afterKnowInOut = null, IEnumerable clienIds = null) { if (priceProvider is null) { priceProvider = new EodPriceProvider(valueDate); } if (startDate == null) { startDate = valueDate.AddYears(-5); } var query = from trade in DbContext.trade join rangeaccrual in DbContext.trade_rangeaccrual on trade.id equals rangeaccrual.TradeId where trade.TradeDate > startDate.Value && trade.TradeDate <= valueDate && trade.ExerciseDate >= valueDate && (trade.TradeType == "区间累积期权") && ConsTrade.确认成交 == trade.TradeStatus && trade.ValidState != ConsGlobal.InValid && trade.DividendDate < valueDate select new { trade, tradeRange = rangeaccrual }; #region 增加客户筛选 tw if (clienIds != null) { query = query.Where(l => clienIds.Contains(l.trade.ClientId)); } #endregion var trades = query.ToList(); if (trades == null || !trades.Any()) { return; } var tradeIds = trades.Select(x => x.trade.id).ToArray(); var manuallyTradeObservationPrices = DbContext.manually_trade_observation_price .Where(x => tradeIds.Contains(x.TradeId) && x.ValueDate == valueDate).ToDictionary(n => n.TradeId); foreach (var tr in trades) { if (tr.trade.ExerciseDate < valueDate) { continue;//已到期交易不再观察; } var tradeStatus = tr.trade.TradeStatus; double closePrice; if (manuallyTradeObservationPrices.TryGetValue(tr.trade.id, out var manuallyTradeObservationPrice)) { closePrice = manuallyTradeObservationPrice.Price ?? 0; } else if (!priceProvider.TryGetEodPrice(tr.trade.UnderlyingCode, out var eodprice)) { throw new Exception($"[{tr.trade.TradeType}:{tr.trade.TradeNumber}]标的:{tr.trade.UnderlyingCode} 未找到结算价"); } else { closePrice = eodprice.ClosePrice; } CheckRangeAccrualBonus(tr.trade, tr.tradeRange, valueDate, closePrice); if (tradeStatus != tr.trade.TradeStatus) { //删除E/Bod_Trade记录 RemoveEodTradeAndFutureInfo(false, tr.trade.id, valueDate); } if (afterKnowInOut != null && DbContext.Entry(tr.tradeRange).State == EntityState.Modified) { afterKnowInOut(tr.trade, tr.tradeRange); } } DbContext.SaveChanges(); } public void CheckRangeAccrualBonus(OtcTradeBase trade, trade_rangeaccrual tradeRange, DateTime valueDate, double closePrice) { var tcQuery = from x in DbContext.trade_cash where x.TradeId == trade.id && x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.Action == "系统操作-平仓费" && x.ValueDate > valueDate && (x.ConfirmDate > valueDate || x.ConfirmDate == DateTime.MinValue) && x.UnwindNotional < x.Notional select x; var notional = (ConsTrade.TradeCompleteStatus.Contains(trade.TradeStatus) && trade.UnWindDate <= valueDate ? 0 : trade.Notional) + (tcQuery.Sum(x => x.UnwindNotional) ?? 0); if (CheckRangeAccrualBonus(trade, tradeRange, valueDate, closePrice, notional, out var couponCash)) { SaveObservation(trade, tradeRange, valueDate, couponCash, notional); //这里要保存,否则下面查询的时候,查不到最后一天的票息记录. DbContext.SaveChanges(); } //到期日生成票息资金记录 if (valueDate.Date == trade.ExerciseDate.Value.Date) { trade.UnWindDate = valueDate.Date; trade.TradeStatus = ConsTrade.已到期; var happenedObservations = DbContext.autocall_observation.Where(o => o.TradeId == trade.id).ToList(); var totalPaymentAmount = happenedObservations?.Sum(x => x.PaymentAmount) ?? 0; totalPaymentAmount += trade.PrincipalSum() * (trade.BuySell == "卖出" ? -1 : 1); var parentTradeId = 0; var parentTradeCashId = 0; if (trade.IsGroup == 2 && trade.ParentTradeId > 0) { var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == trade.id && x.Status != "已完成"); if (groupAction != null) { groupAction.Status = "已完成"; parentTradeCashId = groupAction.ParentTradeCashId; parentTradeId = groupAction.ParentTradeId; } else { parentTradeId = trade.ParentTradeId; parentTradeCashId = SaveGroupUnwindCash(trade, valueDate, totalPaymentAmount, closePrice, out bool continueTradeCashHandle).id; } } SaveCash(trade, ClientCashInCashOut.系统操作_票息, null, totalPaymentAmount, valueDate, closePrice, valueDate, isLastAction: true, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId); //生成确认书 if (PS.Config.IsAutoGenerateContracts && ConsTrade.TradeCompleteStatus.Contains(trade.TradeStatus)) { //修改销售提成的状态 new SalesCommissionDataService(this).SetCommissionVaild(trade.id); new TradeContractGenerateService(this).GenerateContractsAsync(new List { trade.id }); } } } public static bool CheckRangeAccrualBonus(OtcTradeBase trade, trade_rangeaccrual tradeRange, DateTime valueDate, double closePrice, double notional, out double couponCash) { couponCash = 0; if (trade is null || tradeRange is null) { return false; } var observDates = QdpHelper.ParseObservationDate(tradeRange.ObservationDates)?.ToArray(); if (observDates == null) { observDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(trade.TradeDate.Value, trade.ExerciseDate.Value).ToArray(); } if (observDates != null && observDates.Contains(new Date(valueDate))) { var upperRange = trade.IsMoneynessOptionData ? tradeRange.UpperRange * trade.SpotPrice.Value : tradeRange.UpperRange; var lowerRange = trade.IsMoneynessOptionData ? tradeRange.LowerRange * trade.SpotPrice.Value : tradeRange.LowerRange; //有区间收益 if (closePrice < upperRange && closePrice > lowerRange) { couponCash = tradeRange.BonusRate * notional * (trade.SpotPrice ?? 0) / observDates.Length * (trade.BuySell == "卖出" ? -1 : 1); return true; } } return false; } public autocall_observation SaveObservation(OtcTradeBase trade, trade_rangeaccrual rangeaccrual, DateTime valueDate, double couponCash, double notional, bool saveChanges = true) { var observationRecord = DbContext.autocall_observation.FirstOrDefault(o => o.TradeId == trade.id && o.EndDate == valueDate.Date); if (observationRecord == null) { observationRecord = new autocall_observation() { TradeId = trade.id, StartDate = valueDate.Date, EndDate = valueDate.Date, CouponRate = rangeaccrual.BonusRate, StockEqvNotional = notional * (trade.SpotPrice ?? 0), PaymentAmount = couponCash, PaymentDate = trade.ExerciseDate.Value.Date }; if (saveChanges) { DbContext.autocall_observation.Add(observationRecord); } } else { observationRecord.StartDate = valueDate.Date; observationRecord.EndDate = valueDate.Date; observationRecord.CouponRate = rangeaccrual.BonusRate; observationRecord.StockEqvNotional = notional * (trade.SpotPrice ?? 0); observationRecord.PaymentAmount = couponCash; observationRecord.PaymentDate = trade.ExerciseDate.Value.Date; } return observationRecord; } public double GetRangeCoupon(int tradeId, DateTime valueDate, double price) { var trade = DbContext.trade.AsNoTracking().FirstOrDefault(t => t.id == tradeId); var tradeRangeAccrual = DbContext.trade_rangeaccrual.AsNoTracking().FirstOrDefault(t => t.TradeId == tradeId); return GetRangeCoupon(trade, tradeRangeAccrual, valueDate, price); } public double GetRangeCoupon(OtcTradeBase trade, trade_rangeaccrual tradeRangeAccrual, DateTime valueDate, double price) { if (trade == null || tradeRangeAccrual == null) { return 0.0; } var tradeCashs = DbContext.trade_cash.AsNoTracking().Where(x => x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.TradeId == trade.id && x.Action == "系统操作-平仓费" && (x.ValueDate > valueDate && (x.ConfirmDate > valueDate || x.ConfirmDate == DateTime.MinValue)) && x.UnwindNotional < x.Notional).ToList(); var notional = (ConsTrade.TradeCompleteStatus.Contains(trade.TradeStatus) && trade.UnWindDate <= valueDate ? 0 : trade.Notional) + tradeCashs.Sum(x => x.UnwindNotional).Value; var observDates = QdpHelper.ParseObservationDate(tradeRangeAccrual.ObservationDates)?.ToArray(); if (observDates == null) { observDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay( new Date(trade.TradeDate.Value), new Date(trade.ExerciseDate.Value)).ToArray(); } var upperRange = trade.IsMoneynessOptionData ? tradeRangeAccrual.UpperRange * (trade.SpotPrice ?? 0) : tradeRangeAccrual.UpperRange; var lowerRange = trade.IsMoneynessOptionData ? tradeRangeAccrual.LowerRange * (trade.SpotPrice ?? 0) : tradeRangeAccrual.LowerRange; if (price < upperRange && price > lowerRange) { return (observDates != null && observDates.Length > 0) ? tradeRangeAccrual.BonusRate * notional * (trade.SpotPrice ?? 0) / observDates.Length : tradeRangeAccrual.BonusRate * notional * (trade.SpotPrice ?? 0); } else { return 0.0; } } } }