using Qdp.Foundation.Implementations; using Qdp.Pricing.Library.Options.Products.Autocall.Snowball; using YLErp.Abstract.DataProviders; using YLErp.BLL; using YLErp.Commons; using YLErp.DBModels.Consts; using YLErp.DBModels.Helpers; using YLErp.Modules.CalculationModule; using YLErp.Modules.DataProviderModule; using YLErp.Modules.SalesModule; using YLErp.Modules.TradeModule.DealModule; using YLErp.Modules.TradeModule.ExoticOptionModule; namespace YLErp.Modules.TradeModule { public class TradeSnowballBLL : ExoticOptionModule.TradeCashServiceEx { public TradeSnowballBLL(YLBaseService baseService) : base(baseService) { } public TradeSnowballBLL(OptUserInfo userInfo) : base(userInfo) { } public void CheckStatus(DateTime valueDate, IEodPriceProviderV2 priceProvider, DateTime? startDate = null , Action afterKnowInOut = null, IEnumerable clienIds = null) { priceProvider ??= new EodPriceProvider(valueDate); if (startDate == null) { startDate = valueDate.AddYears(-5); } var query = from trade in DbContext.trade join snowball in DbContext.trade_snowball on trade.id equals snowball.TradeId join underlying in DbContext.underlying_manager on trade.UnderlyingId equals underlying.id where trade.TradeDate > startDate.Value && trade.TradeDate <= valueDate && trade.ExerciseDate >= valueDate && ConsTrade.确认成交 == trade.TradeStatus && (trade.TradeType == "雪球期权") && trade.ValidState != ConsGlobal.InValid && (snowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut || (snowball.KnockInOutStatus == ConsTrade.KnockState.KnockedOut && snowball.KnockInOutDate >= valueDate)) && trade.DividendDate < valueDate select new { underlying = underlying, trade = trade, trade_snowball = snowball }; #region 增加客户筛选 tw if (clienIds != null) { query = query.Where(l => clienIds.Contains(l.trade.ClientId)); } #endregion //未敲出的,以及已敲出但敲出日期大于等于当前收盘日的(为了历史收盘) var trades = query.ToList(); var tradeIds = trades.Select(x => x.trade.id).ToList(); var manuallyTradeObservationPrices = DbContext.manually_trade_observation_price .Where(x => tradeIds.Contains(x.TradeId) && x.ValueDate == valueDate).ToDictionary(n => n.TradeId); trades.ForEach(tr => { if (tr.trade.ExerciseDate < valueDate) { return;//已到期交易不再观察; } var tradeStatus = tr.trade.TradeStatus; double closePrice; double? SettlementAmount = null; if (manuallyTradeObservationPrices.TryGetValue(tr.trade.id, out var manuallyTradeObservationPrice)) { closePrice = manuallyTradeObservationPrice.Price ?? 0; SettlementAmount = manuallyTradeObservationPrice.SettlementAmount; } 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; } var oldKnockInOutStatus = tr.trade_snowball.KnockInOutStatus; CheckSnowballKnockInOutStatus(tr.trade, tr.trade_snowball, valueDate, closePrice, SettlementAmount); if (oldKnockInOutStatus != tr.trade_snowball.KnockInOutStatus) { var KnockInOutStatus = tr.trade_snowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn ? "敲入" : "敲出"; LogFactory.GetLogger("收盘检查雪球").Info($"{tr.trade.TradeNumber}--{KnockInOutStatus}--调试"); AddTradeOperationHistoryAndSetParentTradeInfo(false, tr.trade, KnockInOutStatus, KnockInOutStatus); } if (tradeStatus != tr.trade.TradeStatus || oldKnockInOutStatus != tr.trade_snowball.KnockInOutStatus) { //删除E/Bod_Trade记录 RemoveEodTradeAndFutureInfo(false, tr.trade.id, valueDate); } if (afterKnowInOut != null && DbContext.Entry(tr.trade_snowball).State == EntityState.Modified) { afterKnowInOut(tr.trade, tr.trade_snowball); } }); DbContext.SaveChanges(); } //获取雪球期权敲出时要准备的信息 public (DateTime koSettleDate, double koBarrier) GetKoSettleInfo(DateTime valueDate, OtcTradeBase otcTrade, trade_snowball tradeSnowball, SimpleSnowball snowball) { double koBarrier; var koSettleDate = valueDate; var datesStr = tradeSnowball.KOObservationSettleDates; var KOObsSettleDates = string.IsNullOrWhiteSpace(datesStr) ? null : datesStr.Split(new char[] { ',', ';', ',', ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => DateTime.Parse(x)).ToArray(); if (snowball.CustomizedKOBarriers != null && snowball.CustomizedKOBarriers.Length > 0) { var index = snowball.KOObsDates.Select(x => x.DateTime).ToList().IndexOf(valueDate); koBarrier = snowball.CustomizedKOBarriers[index]; if (KOObsSettleDates != null && KOObsSettleDates.Length > index) { koSettleDate = KOObsSettleDates[index]; } } else { koBarrier = tradeSnowball.KOBarrier; if (KOObsSettleDates != null && KOObsSettleDates.Any()) { koSettleDate = KOObsSettleDates[0]; } } if (tradeSnowball.KORebateType == RebateTypeEnum.AtEnd) { koSettleDate = otcTrade.ExerciseDate.Value; } else if (koSettleDate < valueDate) { koSettleDate = valueDate; } if (otcTrade.IsMoneynessOptionData) { koBarrier *= otcTrade.SpotPrice ?? 1.0; } return (koSettleDate, koBarrier); } public SnowballObservationResult GetDefaultAmountForSpecialSnowball(OtcTradeBase otcTrade, trade_snowball tradeSnowball, DateTime valueDate, double closePrice) { var tradeCashs = DbContext.trade_cash.Where(x => x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.TradeId == otcTrade.id && x.Action == "系统操作-平仓费" && x.ValueDate > valueDate && (x.ConfirmDate > valueDate || x.ConfirmDate == DateTime.MinValue) && x.UnwindNotional < x.Notional).ToList(); var tradeNotional = (ConsTrade.TradeCompleteStatus.Contains(otcTrade.TradeStatus) && otcTrade.UnWindDate <= valueDate ? 0 : otcTrade.Notional) + tradeCashs.Sum(x => x.UnwindNotional).Value; return tradeSnowball.PrepaymentUsed ? new SpecialSnowballObservationHelper(otcTrade, tradeSnowball).GetObservationResultForTraderSide(valueDate, closePrice, tradeNotional) : throw new ServiceFaultException("不支持非预付金形式的雪球"); } public double GetDefaultAmount(OtcTradeBase otcTrade, trade_snowball tradeSnowball, DateTime valueDate, double closePrice) { var tradeCashs = DbContext.trade_cash.Where(x => x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.TradeId == otcTrade.id && x.Action == "系统操作-平仓费" && x.ValueDate > valueDate && (x.ConfirmDate > valueDate || x.ConfirmDate == DateTime.MinValue) && x.UnwindNotional < x.Notional).ToList(); var tradeNotional = (ConsTrade.TradeCompleteStatus.Contains(otcTrade.TradeStatus) && otcTrade.UnWindDate <= valueDate ? 0 : otcTrade.Notional) + tradeCashs.Sum(x => x.UnwindNotional).Value; if (tradeSnowball.PrepaymentUsed) { var result = new SpecialSnowballObservationHelper(otcTrade, tradeSnowball).GetObservationResultForTraderSide(valueDate, closePrice, tradeNotional); return result.PaymentAmount; } var defaultAmount = 0d; var request = new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate()) { ParamOverride = p => p.notional = tradeNotional }; var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(otcTrade, tradeSnowball, request); var snowball = (SimpleSnowball)optionTrade.Instrument; var isCall = ConsGlobal.CallPut.IsCall(otcTrade.CallPut); //只在敲出观察日检查敲出和票息情况 //如果交易已经是敲出状态了,不用再做票息和敲出检查 if (snowball.KOObsDates.Select(x => x.DateTime).Contains(valueDate) && tradeSnowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut) { #region 敲出检查 (var koSettleDate, var koBarrier) = GetKoSettleInfo(valueDate, otcTrade, tradeSnowball, snowball); // 发生敲出事件(看涨 - 向上敲出支付票息,看跌 - 向下敲出支付票息) if (isCall ? closePrice >= koBarrier : closePrice <= koBarrier) { tradeSnowball.KnockInOutStatus = ConsTrade.KnockState.KnockedOut; if (snowball.UseOptionPayoffAtKO) { var koOptionCashflows = snowball.GetKOPayoff(new Date(valueDate), closePrice); //koOptionCashflows[0]..PaymentAmount包含了买卖方向的处理了 defaultAmount = TradeHelper.GetAmountByPaymentAmount(koOptionCashflows[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell); } else { var CouponPayment = snowball.CouponPayment(valueDate, includeStartDate: tradeSnowball.CouponIncludeStartDate == true && tradeSnowball.CouponDayCount != "Monthly"); defaultAmount = TradeHelper.GetAmountByPaymentAmount(CouponPayment, otcTrade.PrincipalSum(), otcTrade.BuySell); if (tradeSnowball.AnnualizedPremiumRate.HasValue && tradeSnowball.AnnualizedPremiumRate != 0) { var tradePrice = (otcTrade.StockEqvNotional * otcTrade.ParticipationRate * tradeSnowball.AnnualizedPremiumRate * snowball.CouponDayCount.CalcDayCountFraction(snowball.StartDate, new Date(valueDate))) ?? 0; if (tradePrice != 0) { defaultAmount += (otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice; } } } return defaultAmount; //已经敲出了,不需要再继续走下去了 } #endregion } #region 敲入检查 //在当前结算日之前未敲出且未敲入: // !((KO || KI) && KOKIDate < valueDate) if (!((tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedOut || tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn) && tradeSnowball.KnockInOutDate < valueDate) && snowball.KIObsDates.Select(x => x.DateTime).Contains(valueDate) && tradeSnowball.KIPayoffType != KIPayoffTypeEnum.None) { var kiBarrier = otcTrade.IsMoneynessOptionData ? tradeSnowball.KIBarrier * otcTrade.SpotPrice : tradeSnowball.KIBarrier; // 发生敲入事件(看涨 - 向下敲入,看跌 - 向上敲入) if (isCall ? closePrice <= kiBarrier : closePrice >= kiBarrier) { // 更新观察状态 tradeSnowball.KnockInOutStatus = ConsTrade.KnockState.KnockedIn; } } #endregion #region 到期检查 if (valueDate == snowball.ExerciseDates.Last().DateTime) { if (snowball.UseOptionPayoffAtMaturity && (tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn || tradeSnowball.KIBarrier <= 0)) { //已敲入,到期时计算期权收益 var optionPayoffPayment = snowball.GetPayoff(new double[] { closePrice }); //optionPayoffPayment[0].PaymentAmount包含了买卖方向的处理了 defaultAmount = TradeHelper.GetAmountByPaymentAmount(optionPayoffPayment[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell); } else if (tradeSnowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut) { var startDate = tradeSnowball.CouponIncludeStartDate == true ? snowball.StartDate.AddDays(-1) : snowball.StartDate; var maturityCouponRate = snowball.Coupon * snowball.InitialSpotPrice; var maturityCouponPayment = snowball.FixedCoupon ? maturityCouponRate * snowball.Notional : maturityCouponRate * snowball.Notional * snowball.CouponDayCount.CalcDayCountFraction(startDate, snowball.ExerciseDates.Last()); maturityCouponPayment = TradeHelper.GetAmountByPaymentAmount(maturityCouponPayment, otcTrade.PrincipalSum(), otcTrade.BuySell); defaultAmount = maturityCouponPayment; if (tradeSnowball.AnnualizedPremiumRate.HasValue && tradeSnowball.AnnualizedPremiumRate != 0) { var tradePrice = (otcTrade.StockEqvNotional * otcTrade.ParticipationRate * tradeSnowball.AnnualizedPremiumRate * snowball.CouponDayCount.CalcDayCountFraction(snowball.StartDate, new Date(valueDate))) ?? 0; if (tradePrice != 0) { defaultAmount += (otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice; } } } } #endregion return defaultAmount; } public void CheckSnowballKnockInOutStatus(OtcTradeBase otcTrade, trade_snowball tradeSnowball, DateTime valueDate, double closePrice, double? settlementAmount) { if (otcTrade is null) { throw new ArgumentNullException(nameof(otcTrade)); } if (tradeSnowball is null) { throw new ArgumentNullException(nameof(tradeSnowball)); } try { var tradeStatusOld = otcTrade.TradeStatus; InnerCheckSnowballKnockInOutStatus(otcTrade, tradeSnowball, valueDate, closePrice, settlementAmount); //生成确认书 if (PS.Config.IsAutoGenerateContracts && tradeStatusOld == ConsTrade.确认成交 && ConsTrade.TradeCompleteStatus.Contains(otcTrade.TradeStatus)) { //修改销售提成的状态 new SalesCommissionDataService(this).SetCommissionVaild(otcTrade.id); new TradeContractGenerateService(this).GenerateContractsAsync(new List { otcTrade.id }); } } catch (Exception ex) { throw new ServiceException($"[检查敲入敲出]交易编号:{otcTrade.TradeNumber},{ex.Message}", ex); } } private void InnerCheckSnowballKnockInOutStatus( OtcTradeBase otcTrade, trade_snowball tradeSnowball, DateTime valueDate, double closePrice, double? SettlementAmount) { var tradeCashs = DbContext.trade_cash.Where(x => x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.TradeId == otcTrade.id && x.Action == "系统操作-平仓费" && x.ValueDate > valueDate && (x.ConfirmDate > valueDate || x.ConfirmDate == DateTime.MinValue) && x.UnwindNotional < x.Notional).ToList(); var tradeNotional = (ConsTrade.TradeCompleteStatus.Contains(otcTrade.TradeStatus) && otcTrade.UnWindDate <= valueDate ? 0 : otcTrade.Notional) + tradeCashs.Sum(x => x.UnwindNotional).Value; if (tradeSnowball.PrepaymentUsed) { ProcessSpecialSnowball(otcTrade, tradeSnowball, valueDate, closePrice, tradeNotional, SettlementAmount); return; } var request = new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate()) { ParamOverride = p => p.notional = tradeNotional }; var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(otcTrade, tradeSnowball, request); var snowball = (SimpleSnowball)optionTrade.Instrument; var isCall = ConsGlobal.CallPut.IsCall(otcTrade.CallPut); //只在敲出观察日检查敲出和票息情况 //如果交易已经是敲出状态了,不用再做票息和敲出检查 if (snowball.KOObsDates.Select(x => x.DateTime).Contains(valueDate) && tradeSnowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut) { #region 敲出检查 (var koSettleDate, var koBarrier) = GetKoSettleInfo(valueDate, otcTrade, tradeSnowball, snowball); // 发生敲出事件(看涨 - 向上敲出支付票息,看跌 - 向下敲出支付票息) if (isCall ? closePrice >= koBarrier : closePrice <= koBarrier) { // 更新观察状态 tradeSnowball.KnockInOutStatus = ConsTrade.KnockState.KnockedOut; tradeSnowball.KnockInOutDate = valueDate; // 更新交易状态 otcTrade.TradeStatus = ConsTrade.已平仓; otcTrade.UnWindDate = valueDate; if (snowball.UseOptionPayoffAtKO) { double paymentAmount = 0; var koOptionCashflows = snowball.GetKOPayoff(new Date(valueDate), closePrice); if (SettlementAmount != null) { paymentAmount = SettlementAmount.Value; } else { paymentAmount = TradeHelper.GetAmountByPaymentAmount(koOptionCashflows[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell); } var parentTradeId = 0; var parentTradeCashId = 0; if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0) { var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成"); if (groupAction != null) { groupAction.Status = "已完成"; parentTradeCashId = groupAction.ParentTradeCashId; parentTradeId = groupAction.ParentTradeId; } else { parentTradeId = otcTrade.ParentTradeId; parentTradeCashId = SaveGroupUnwindCash(otcTrade, valueDate, paymentAmount, closePrice, out var continueTradeCashHandle).id; } } SaveOptionPayoffCash(otcTrade, paymentAmount, koSettleDate, closePrice, ClientCashInCashOut.系统操作_平仓费, TradeCashExerciseWayEnum.提前终止行权, valueDate, true, parentTradeId, parentTradeCashId); //koOptionCashflows[0].PaymentAmount包含了买卖方向的处理了 } else { if (SettlementAmount != null) { double tradePrice = 0; var _settlementAmount = SettlementAmount.Value; var parentTradeId = 0; var parentTradeCashId = 0; if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0) { var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成"); if (groupAction != null) { groupAction.Status = "已完成"; parentTradeCashId = groupAction.ParentTradeCashId; parentTradeId = groupAction.ParentTradeId; } else { parentTradeId = otcTrade.ParentTradeId; parentTradeCashId = SaveGroupUnwindCash(otcTrade, valueDate, _settlementAmount, closePrice, out var continueTradeCashHandle).id; } } //观察日价格页面的结算金额包含了年化期权费,该处逻辑需要先按照扣除年化期权费来算,后面逻辑会补上年化期权费,否则会重复运算 if (tradeSnowball.AnnualizedPremiumRate.HasValue && tradeSnowball.AnnualizedPremiumRate != 0) { tradePrice = otcTrade.StockEqvNotional * otcTrade.ParticipationRate * tradeSnowball.AnnualizedPremiumRate * snowball.CouponDayCount.CalcDayCountFraction(snowball.StartDate, new Date(valueDate)) ?? 0; if (tradePrice != 0) { _settlementAmount -= (otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice; } } var tradeCash = SaveCouponAmountCash(otcTrade, _settlementAmount, koSettleDate, closePrice, valueDate, parentTradeId, parentTradeCashId); if (tradePrice != 0 && otcTrade.PremiumPayDate <= valueDate) { SaveTradePrice(otcTrade, tradeCash, tradePrice, koSettleDate); } } else { var CouponPayment = snowball.CouponPayment(valueDate, includeStartDate: tradeSnowball.CouponIncludeStartDate == true && tradeSnowball.CouponDayCount != "Monthly"); var couponPayment = TradeHelper.GetAmountByPaymentAmount(CouponPayment, otcTrade.PrincipalSum(), otcTrade.BuySell); var parentTradeId = 0; var parentTradeCashId = 0; var hasUnfinishedGroupAction = false; var continueTradeCashHandle = false; var groupTradeCash = new trade_cash(); if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0) { var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成"); if (groupAction != null) { hasUnfinishedGroupAction = true; groupAction.Status = "已完成"; parentTradeCashId = groupAction.ParentTradeCashId; parentTradeId = groupAction.ParentTradeId; } else { parentTradeId = otcTrade.ParentTradeId; groupTradeCash = SaveGroupUnwindCash(otcTrade, valueDate, couponPayment, closePrice, out continueTradeCashHandle); parentTradeCashId = groupTradeCash.id; } } var tradeCash = SaveCouponAmountCash(otcTrade, couponPayment, koSettleDate, closePrice, valueDate, parentTradeId, parentTradeCashId); if (tradeSnowball.AnnualizedPremiumRate.HasValue && tradeSnowball.AnnualizedPremiumRate != 0) { var tradePrice = (otcTrade.StockEqvNotional * otcTrade.ParticipationRate * tradeSnowball.AnnualizedPremiumRate * snowball.CouponDayCount.CalcDayCountFraction(snowball.StartDate, new Date(valueDate))) ?? 0; if (tradePrice != 0 && otcTrade.PremiumPayDate <= valueDate) { SaveTradePrice(otcTrade, tradeCash, tradePrice, koSettleDate); if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0 && !hasUnfinishedGroupAction && continueTradeCashHandle) { groupTradeCash.Amount += (otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice; DbContext.SaveChanges(); } } } } } LogFactory.GetLogger("收盘检查雪球").Info($"{otcTrade.TradeNumber}--敲出"); return; //已经敲出了,不需要再继续走下去了 } #endregion } #region 敲入检查 //在当前结算日之前未敲出且未敲入: // !((KO || KI) && KOKIDate < valueDate) if (!((tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedOut || tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn) && tradeSnowball.KnockInOutDate < valueDate) && snowball.KIObsDates.Select(x => x.DateTime).Contains(valueDate) && tradeSnowball.KIPayoffType != KIPayoffTypeEnum.None) { var kiBarrier = otcTrade.IsMoneynessOptionData ? tradeSnowball.KIBarrier * otcTrade.SpotPrice : tradeSnowball.KIBarrier; // 发生敲入事件(看涨时向下敲入,看跌时向上敲入) if (isCall ? closePrice <= kiBarrier : closePrice >= kiBarrier) { // 更新观察状态 tradeSnowball.KnockInOutStatus = ConsTrade.KnockState.KnockedIn; tradeSnowball.KnockInOutDate = valueDate; } } #endregion #region 到期检查 if (valueDate == snowball.ExerciseDates.Last().DateTime) { if (snowball.UseOptionPayoffAtMaturity && (tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn || tradeSnowball.KIBarrier <= 0)) { otcTrade.TradeStatus = ConsTrade.已执行; otcTrade.UnWindDate = valueDate; double paymentAmount = 0; //已敲入,到期时计算期权收益 var optionPayoffPayment = snowball.GetPayoff(new double[] { closePrice }); if (SettlementAmount != null) { paymentAmount = SettlementAmount.Value; } else { paymentAmount = TradeHelper.GetAmountByPaymentAmount(optionPayoffPayment[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell); } var parentTradeId = 0; var parentTradeCashId = 0; if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0) { var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成"); if (groupAction != null) { groupAction.Status = "已完成"; parentTradeCashId = groupAction.ParentTradeCashId; parentTradeId = groupAction.ParentTradeId; } else { parentTradeId = otcTrade.ParentTradeId; parentTradeCashId = SaveGroupUnwindCash(otcTrade, valueDate, paymentAmount, closePrice, out var continueTradeCashHandle).id; } } //optionPayoffPayment[0].PaymentAmount包含了买卖方向的处理了 SaveOptionPayoffCash(otcTrade, paymentAmount, valueDate, closePrice, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, valueDate, false, parentTradeId, parentTradeCashId); } else if (tradeSnowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut) { var startDate = tradeSnowball.CouponIncludeStartDate == true ? snowball.StartDate.AddDays(-1) : snowball.StartDate; otcTrade.TradeStatus = ConsTrade.已到期; otcTrade.UnWindDate = valueDate; var maturityCouponRate = snowball.Coupon * snowball.InitialSpotPrice; var maturityCouponPayment = snowball.FixedCoupon ? maturityCouponRate * snowball.Notional : maturityCouponRate * snowball.Notional * snowball.CouponDayCount.CalcDayCountFraction(startDate, snowball.ExerciseDates.Last()); maturityCouponPayment = TradeHelper.GetAmountByPaymentAmount(maturityCouponPayment, otcTrade.PrincipalSum(), otcTrade.BuySell); double tradePrice = 0; if (tradeSnowball.AnnualizedPremiumRate.HasValue && tradeSnowball.AnnualizedPremiumRate != 0) { tradePrice = otcTrade.StockEqvNotional * otcTrade.ParticipationRate * tradeSnowball.AnnualizedPremiumRate * snowball.CouponDayCount.CalcDayCountFraction(snowball.StartDate, new Date(valueDate)) ?? 0; } var parentTradeId = 0; var parentTradeCashId = 0; var groupTradeCash = new trade_cash(); if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0) { var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成"); if (groupAction != null) { groupAction.Status = "已完成"; parentTradeCashId = groupAction.ParentTradeCashId; parentTradeId = groupAction.ParentTradeId; } else { var paymentAmount = SettlementAmount != null ? SettlementAmount.Value : (maturityCouponPayment + ((otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice)); parentTradeId = otcTrade.ParentTradeId; parentTradeCashId = SaveGroupUnwindCash(otcTrade, valueDate, paymentAmount, closePrice, out var continueTradeCashHandle).id; } } var tradeCash = new trade_cash(); if (SettlementAmount != null) { //观察日价格页面的结算金额包含了年化期权费,该处逻辑需要先按照扣除年化期权费来算,后面逻辑会补上年化期权费,否则会重复运算 var _settlementAmount = SettlementAmount.Value; if (tradePrice != 0) { _settlementAmount -= (otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice; } tradeCash = SaveOptionPayoffCash(otcTrade, _settlementAmount, valueDate, closePrice, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, valueDate, false, parentTradeId, parentTradeCashId); } else { tradeCash = SaveOptionPayoffCash(otcTrade, maturityCouponPayment, valueDate, closePrice, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, valueDate, false, parentTradeId, parentTradeCashId); } if (tradePrice != 0 && otcTrade.PremiumPayDate <= valueDate) { SaveTradePrice(otcTrade, tradeCash, tradePrice, valueDate); } } } #endregion } private trade_cash SaveOptionPayoffCash(OtcTradeBase trade, double paymentAmount, DateTime valueDate, double underlyingPrice, string cashAction, string exerciseWay, DateTime happenDate, bool isKnockOut, int parentTradeId, int parentTradeCashId) { return SaveCash(trade, cashAction, exerciseWay, paymentAmount, valueDate, underlyingPrice, happenDate, isKnockOut, isLastAction: true, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId); } private trade_cash SaveCouponAmountCash(OtcTradeBase trade, double couponAmount, DateTime valueDate, double underlyingPrice, DateTime happenDate, int parentTradeId, int parentTradeCashId) { return SaveCash(trade, ClientCashInCashOut.系统操作_票息, null, couponAmount, valueDate, underlyingPrice, happenDate, true, isLastAction: true, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId); } public void SaveTradePrice(OtcTradeBase trade, trade_cash tradeCash, double tradePrice, DateTime valueDate) { const string cashAction = ClientCashInCashOut.系统操作_期权费; // 保存trade_cash var tcd = new trade_cash_detail(); DbContext.trade_cash_detail.Add(tcd); tcd.TradeId = tradeCash.TradeId; tcd.TradeCashId = tradeCash.id; tcd.Amount = (trade.BuySell == "买入" ? -1 : 1) * tradePrice; tcd.Action = cashAction; tcd.ValueDate = tradeCash.ValueDate; tcd.OptDate = DateTime.Now; tcd.OptId = tradeCash.OptId; tcd.OptName = tradeCash.OptName; var tradeCashUpDate = DbContext.trade_cash.Find(tradeCash.id); tradeCashUpDate.Amount += tcd.Amount ?? 0; // 保存ClientCashInCashOut var client = DbContextFactory.GetClientDbContext(UserInfo).client.Where(n => n.id == trade.ClientId) .Select(n => new { n.id, n.Number, n.Name }).FirstOrDefault(); if (client == null) { throw new Exception($"{trade.TradeType}'{trade.TradeNumber}'找不到客户信息,客户id:{trade.ClientId}"); } var cashInOutRecord = DbContext.ClientCashInCashOut.FirstOrDefault(c => c.TradeId == trade.id && c.Action == cashAction && c.HappenDate == valueDate); if (cashInOutRecord == null) { cashInOutRecord = new ClientCashInCashOut(); cashInOutRecord.CreateDate = tradeCash.OptDate; cashInOutRecord.CreatorId = tradeCash.OptId; cashInOutRecord.CreatorName = tradeCash.OptName; DbContext.ClientCashInCashOut.Add(cashInOutRecord); } cashInOutRecord.Direction = "应收"; cashInOutRecord.Number = UniqueTimeId.GetStr(); cashInOutRecord.ClientId = client.id; cashInOutRecord.ClientNumber = client.Number; cashInOutRecord.ClientName = client.Name; cashInOutRecord.Money = -tcd.Amount; cashInOutRecord.HappenDate = valueDate; cashInOutRecord.ValidState = "Valid"; cashInOutRecord.State = ClientCashInCashOut.已确认; cashInOutRecord.OptDate = DateTime.Now; cashInOutRecord.OptId = tradeCash.OptId; cashInOutRecord.OptName = tradeCash.OptName; cashInOutRecord.TradeId = trade.id; cashInOutRecord.TradeCashId = tradeCash.id; cashInOutRecord.Action = cashAction; cashInOutRecord.TradeNumber = trade.TradeNumber; cashInOutRecord.IsGroup = trade.IsGroup; DbContext.SaveChanges(); } /// /// 处理专业版雪球 /// private void ProcessSpecialSnowball(OtcTradeBase otcTrade, trade_snowball snowball , DateTime valueDate, double closePrice, double tradeNotional, double? settlementAmount) { var obResult = new SpecialSnowballObservationHelper(otcTrade, snowball) .GetObservationResultForTraderSide(valueDate, closePrice, tradeNotional); if (obResult.ResultType == SnowballObservationResultType.NonObservationDay || obResult.ResultType == SnowballObservationResultType.Monitoring) { return; } //期权敲入 if (obResult.ResultType == SnowballObservationResultType.KnockedIn) { // 更新期权敲入状态 if (snowball.KnockInOutDate == null) { snowball.KnockInOutDate = snowball.IsInitialKnockedIn ? otcTrade.StartDate.Value : valueDate; } snowball.KnockInOutStatus = ConsTrade.KnockState.KnockedIn; return; } var paymentAmount = settlementAmount != null ? settlementAmount.Value : TradeHelper.GetAmountByPaymentAmount(obResult.PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell); if (obResult.ResultType == SnowballObservationResultType.KoPayoff) { // 更新期权敲出状态 snowball.KnockInOutDate = valueDate; snowball.KnockInOutStatus = ConsTrade.KnockState.KnockedOut; // 更新交易了结状态 otcTrade.UnWindDate = valueDate; otcTrade.TradeStatus = ConsTrade.已平仓; //写入资金记录 (var parentTradeId, var parentTradeCashId) = SaveGroupCash(otcTrade, obResult.PaymentDate, paymentAmount, closePrice); SaveCouponAmountCash(otcTrade, paymentAmount, obResult.PaymentDate, closePrice, valueDate, parentTradeId, parentTradeCashId); LogFactory.GetLogger("收盘检查雪球").Info($"{otcTrade.TradeNumber}--敲出"); } else if (obResult.ResultType == SnowballObservationResultType.KiPayoffAtEndDate) { // 更新期权敲入状态 if (snowball.KnockInOutDate == null) { snowball.KnockInOutDate = snowball.IsInitialKnockedIn ? otcTrade.StartDate.Value : valueDate; } snowball.KnockInOutStatus = ConsTrade.KnockState.KnockedIn; // 更新交易了结状态 otcTrade.UnWindDate = valueDate; otcTrade.TradeStatus = ConsTrade.已执行; (var parentTradeId, var parentTradeCashId) = SaveGroupCash(otcTrade, obResult.PaymentDate, paymentAmount, closePrice); SaveOptionPayoffCash(otcTrade, paymentAmount, obResult.PaymentDate, closePrice, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, valueDate, false, parentTradeId, parentTradeCashId); } else if (obResult.ResultType == SnowballObservationResultType.NkiPayoffAtEndDate) { otcTrade.UnWindDate = valueDate; otcTrade.TradeStatus = ConsTrade.已到期; (var parentTradeId, var parentTradeCashId) = SaveGroupCash(otcTrade, obResult.PaymentDate, paymentAmount, closePrice); SaveOptionPayoffCash(otcTrade, paymentAmount, obResult.PaymentDate, closePrice, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, valueDate, false, parentTradeId, parentTradeCashId); } else { throw new ServiceException("系统错误,未处理的观察结果类型:" + obResult.ResultType) { IsFaultError = true }; } } //保存组合交易资金 private (int parentTradeId, int parentTradeCashId) SaveGroupCash(OtcTradeBase otcTrade , DateTime paymentDate, double paymentAmount, double closePrice) { var parentTradeId = 0; var parentTradeCashId = 0; if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0) { var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成"); if (groupAction != null) { groupAction.Status = "已完成"; parentTradeCashId = groupAction.ParentTradeCashId; parentTradeId = groupAction.ParentTradeId; } else { parentTradeId = otcTrade.ParentTradeId; parentTradeCashId = SaveGroupUnwindCash(otcTrade, paymentDate, paymentAmount, closePrice, out var continueTradeCashHandle).id; } } return (parentTradeId, parentTradeCashId); } } }