using Microsoft.EntityFrameworkCore.Storage; using Qdp.Foundation.Utilities; using YLErp.Commons; using YLErp.CustomizedBizLogic; using YLErp.DBModels.Consts; using YLErp.DBModels.Enums; namespace YLErp.Modules.TradeModule { /// /// 期权交易敲出操作服务 /// public class OptionTradeKnockoutService : YLBaseService { public OptionTradeKnockoutService(YLBaseService baseService) : base(baseService) { } /// /// 保存敲出操作资金记录 /// public OptionTradeKnockoutResult Execute(OtcTradeBase td, trade_accumulator_option tdAcc, OptionTradeKnockoutRequest req, IDbContextTransaction trans = null) { var innerTrans = trans == null; try { if (innerTrans) { trans = DbContext.Database.BeginTransaction(); } var result = InnerExecute(td, tdAcc, req); //执行事务 if (innerTrans) { trans.Commit(); } return result; } finally { if (innerTrans) { trans?.Dispose(); } } } private OptionTradeKnockoutResult InnerExecute(OtcTradeBase td, trade_accumulator_option tdAcc, OptionTradeKnockoutRequest req) { const string tcAction = ClientCashInCashOut.系统操作_平仓费; //trade action var action = CheckAndAddAction(td, req, out var needResettle); //client info var client = DbContextFactory.GetClientDbContext(UserInfo).client.Where(n => n.id == td.ClientId) .Select(n => new { n.id, n.Number, n.Name }).FirstOrDefault(); if (client == null) { throw new ServiceException($"{td.TradeType}'{td.TradeNumber}'找不到客户信息,客户id:{td.ClientId}"); } var tradeCashs = DbContext.trade_cash.Where(t => t.TradeId == td.id && t.Action == "系统操作-平仓费" && t.UnwindType == "部分行权" && !t.IsDeleted && t.HappenedDate == req.ValueDate); DbContext.trade_cash.RemoveRange(tradeCashs); //trade cash var tc = new trade_cash { TradeId = td.id, ValueDate = req.ValueDate, HappenedDate = req.ValueDate, Action = tcAction, IsLastAction = true, ExerciseWay = TradeCashExerciseWayEnum.提前终止行权, Amount = req.PaymentAmount, ValidState = null, ExceciseType = "现金", TradeType = td.BuySell, CallPut = td.CallPut, Notional = td.Notional, TradeAmount = td.TradeAmount, UnwindPrice = Math.Abs(td.Notional != 0 ? req.PaymentAmount / td.Notional : 0), UnwindPricePercentRate = Math.Abs(td.Notional != 0 && td.SpotPrice != null && td.SpotPrice != 0 ? req.PaymentAmount / td.Notional / td.SpotPrice.Value : 0), OptId = UserId, OptName = UserName, OptDate = DateTime.Now, ParentTradeId = 0, ParentTradeCashId = 0, Status = TradeCashStatusEnum.已执行, FinalPrice = req.UnderlyingPrice, BarrierPrice = tdAcc.KOBarrier, UnwindType = null, UnwindNotional = td.Notional, UnwindTradeAmount = td.TradeAmount, UnwindPercentRate = td.Notional / td.OriginalNotional, NotionalPercentRate = td.Notional / td.OriginalNotional, AdvanceMoney = 0, IsDeleted = false, SettleDate = req.ValueDate, Comments = null, ConfirmDate = DateTime.MinValue, CurrencyRate = null, ExtraAmount = null, Strike = td.Strike, SpotPrice = td.SpotPrice, TradePremium = 0, QuoteAmount = 0, UnwindVol = null, VolType = null, Number = null }; DbContext.trade_cash.Add(tc); DbContext.SaveChanges(); if (PS.Config.SalesCommissionCalculation == "公式1") { //销售提成 new SalesModule.SalesCommissionDetailDataService(UserInfo).CalcuSalesCommissionDetail(tc); } if (PS.Config.Company == Configuration.CompanyEnum.招证) { new BizLogicZhaoZheng().GenerateZhaoZhengDealNumber(td, tc); } if (PS.Config.Company == Configuration.CompanyEnum.物产中大) { new BizLogicWCZD().GenerateWCZDNumber(DbContext, td, tc.ValueDate, tc.id); } //trade_cash_detail var tcd = new trade_cash_detail { TradeId = tc.TradeId, TradeCashId = tc.id, Action = tc.Action, Amount = tc.Amount, ExtraAmount = tc.ExtraAmount, SinglePrice = tc.UnwindPrice, SinglePricePercentRate = tc.UnwindPricePercentRate, ValueDate = tc.ValueDate, OptId = tc.OptId, OptName = tc.OptName, OptDate = DateTime.Now }; DbContext.trade_cash_detail.Add(tcd); //client cash var cashInOutRecord = new ClientCashInCashOut { Direction = "应收", Number = UniqueTimeId.GetStr(), ClientId = client.id, ClientNumber = client.Number, ClientName = client.Name, Money = -tc.Amount, HappenDate = req.ValueDate, State = ClientCashInCashOut.已确认, OptDate = tc.OptDate, OptId = tc.OptId, OptName = tc.OptName, CreateDate = tc.OptDate, CreatorId = tc.OptId, CreatorName = tc.OptName, TradeId = td.id, TradeCashId = tc.id, Action = tcAction, TradeNumber = td.TradeNumber, IsGroup = td.IsGroup }; DbContext.ClientCashInCashOut.Add(cashInOutRecord); //end action.CashId = tc.id; DbContext.SaveChanges(); return new OptionTradeKnockoutResult { NeedResettle = needResettle, TradeCash = tc, TradeAction = action }; } //检查已有的action并在数据库上下文中添加敲出action(此时action还没有cashid) private TradeAction CheckAndAddAction(OtcTradeBase td, OptionTradeKnockoutRequest req, out bool needResettle) { var tradeId = td.id; var sysDate = BLL.valuedateBLL.ValueDate; var actions = DbContext.TradeAction.Where(n => n.TradeId == tradeId && n.IsValid).ToArray(); //存在未审核通过的操作 if (actions.Any(n => n.CheckFlag > 0 && n.CheckFlag < byte.MaxValue)) { throw new ServiceException($"{td.TradeType}交易'{td.TradeNumber}'存在未审核通过的操作,执行敲出操作失败"); } //在这里敲出了结已经被检查 if (actions.Any(n => n.ActionFlag == TradeActionHelper.Flag_FullClosed)) { throw new ServiceException($"{td.TradeType}交易'{td.TradeNumber}'已完全了结,执行敲出操作失败"); } //存在后续的资金记录(观察日资金或者了结资金) if (!req.IgnoreLaterCashRecords && actions.Any(n => n.ValueDate > req.ValueDate && n.CashId > 0)) { throw new ServiceException($"{td.TradeType}交易'{td.TradeNumber}'存在后续的资金记录,执行敲出操作失败"); } needResettle = false; //如果观察日存在转远期的操作行为,这里暂时不进行处理 //检查观察日是否已经存在资金记录,如果有的话说明曾进行过观察,此时需要进行无效处理 var tdObActions = actions.Where(n => n.ValueDate == sysDate && n.CashId > 0).ToArray(); if (tdObActions.Any()) { var existsTradeCashs = new Lazy>( () => DbContext.trade_cash.Where(t => t.TradeId == tradeId && t.ValueDate == req.ValueDate && t.ValidState != ConsGlobal.InValid && !t.IsDeleted)); var existsTradeCashDetails = new Lazy>( () => DbContext.trade_cash_detail.Where(t => t.TradeId == tradeId && t.ValueDate == req.ValueDate)); var existsClientCashs = new Lazy>( () => DbContext.ClientCashInCashOut.Where(t => t.ClientId == td.ClientId && t.TradeId == tradeId && t.HappenDate >= req.ValueDate && t.ValidState != ConsGlobal.InValid)); foreach (var a in tdObActions) { if ((a.ActionType == TradeActionType.ObCoupon || a.ActionType == TradeActionType.ObCash) && a.ActionFlag == TradeActionHelper.Flag_CashAtHit) { a.IsValid = false; var tc = existsTradeCashs.Value.FirstOrDefault(n => n.id == a.CashId.Value); if (tc != null) { needResettle = true; tc.ValidState = ConsGlobal.InValid; //client cash var cc = existsClientCashs.Value.FirstOrDefault(n => n.TradeCashId == tc.id); if (cc != null) { cc.ValidState = ConsGlobal.InValid; } //trade_cash_detail var details = existsTradeCashDetails.Value.Where(n => n.TradeCashId == tc.id).ToArray(); DbContext.trade_cash_detail.RemoveRange(details); } } } } return DbContext.TradeAction.Add(new TradeAction { TradeId = tradeId, ValueDate = req.ValueDate, ActionRemark = "敲出", ActionFlag = TradeActionHelper.Flag_FullClosed, ActionType = TradeActionType.KnockOut, CashId = 0, CreateTime = DateTime.Now, IsValid = true, OptId = UserId, ActionData = null, BackId = null, CheckFlag = 0 }).Entity; } } /// /// 期权交易敲出执行请求 /// public class OptionTradeKnockoutRequest { /// /// 操作日期 /// public DateTime ValueDate { get; set; } /// /// 标的价格 /// public double UnderlyingPrice { get; set; } /// /// 支付金额 /// public double PaymentAmount { get; set; } /// /// 是否忽略后续资金记录的检查 /// public bool IgnoreLaterCashRecords { get; set; } } /// /// 期权交易敲出执行结果 /// public class OptionTradeKnockoutResult { /// /// 是否需要重新收盘 /// public bool NeedResettle { get; set; } /// /// /// public TradeAction TradeAction { get; set; } /// /// /// public trade_cash TradeCash { get; set; } } }