using Dapper; using YLErp.Modules.MarketRiskMoudule; using YLErp.Modules.MarketRiskMoudule.Dto; using YLErp.Modules.RiskExposure; using YLErp.Modules.RiskModule; namespace YLErp.Modules.EodModule { /// /// 累计总盈亏服务 /// public class AccruedTotalPnlService : YLBaseService where TEntity : EodTradePosition { public AccruedTotalPnlService(OptUserInfo userInfo) : base(userInfo) { } public AccruedTotalPnlService(YLBaseService baseService) : base(baseService) { } private DateTime GetPreDate(DateTime date) { var tmpDate = DbContext.Set().Where(n => n.ValueDate < date).Max(t => (DateTime?)t.ValueDate); return tmpDate.HasValue ? tmpDate.Value.Date : DateTime.MinValue; } /// /// 获取起算日前的盈亏数据(交易类型:场内期权+场外交易) /// (在总盈亏统计中根据标的代码减去此列表结果中的响应数据) /// public IEnumerable GetAccruedStartPnls(DateTime accruedStartDate, bool isIncludeHedge = false) { var preStartDate = GetPreDate(accruedStartDate); if (preStartDate.Year < 2000) { return Enumerable.Empty(); } //-------------------------------- // 获取起始累计盈亏收盘数据列表 //-------------------------------- var query1 = from n in DbContext.Set() where n.ValueDate == preStartDate where n.TradeType == "场内期权" select new { TraderId = 0, n.TradeType, n.UnderlyingCode, n.BookId, n.ClientId, n.TotalPnL, n.Commission, n.Amount }; if (isIncludeHedge) { var query = from n in DbContext.Set() where n.ValueDate == preStartDate where n.TradeType == "商品期货" select new { TraderId = 0, n.TradeType, n.UnderlyingCode, n.BookId, n.ClientId, n.TotalPnL, n.Commission, n.Amount }; query1 = query1.Concat(query); } var query2 = from n in DbContext.Set() join t in DbContext.trade on n.TradeId equals t.id where n.ValueDate == preStartDate && n.TradeId > 0 && t.ValidState != "InValid" && (t.UnWindDate > preStartDate || !ConsTrade.TradeCompleteStatus.Contains(t.TradeStatus)) select new { t.TraderId, n.TradeType, UnderlyingCode = t.TradeType == ConsGlobal.TradeType.CashFlow ? "现金流" : n.UnderlyingCode, n.BookId, n.ClientId, n.TotalPnL, n.Commission, n.Amount }; var list = query1.Concat(query2).ToList(); //------------------------------------------------------ // 过滤并且汇总求和 //------------------------------------------------------ var sumQuery = from n in list group n by new { n.UnderlyingCode, n.BookId, n.ClientId, n.TradeType, n.TraderId } into g select new EodPnlStaticsDto { BookId = g.Key.BookId, AssetType = g.Key.TradeType, ClientId = g.Key.ClientId, TraderId = g.Key.TraderId, UnderlyingCode = g.Key.UnderlyingCode, TotalPnl = g.Sum(t => t.TotalPnL), TotalCommission = g.Sum(t => t.Commission), TotalAmount = g.Sum(t => t.Amount) }; return sumQuery.ToArray(); } /// /// 获取历史了结交易总盈亏数据 /// /// 开始日期(包括) /// 结束日期(包括) /// 是否包括对冲交易 public IEnumerable GetFinishedTradePnls(DateTime startDate, DateTime endDate, bool isIncludeHedge = false) { var sumList = new List(); //SetDebugSqlLog(); //------------------------------------------------------ // 场内期权累计盈亏统计 //------------------------------------------------------ var exchangeOptionQuery = from a in DbContext.exchange_list_option where a.MaturityDate >= startDate && a.MaturityDate <= endDate select new { ValueDate = a.MaturityDate, ExchangeOptionCode = a.ContractCode }; var finishedExOptionPnlQuery = from exOption in exchangeOptionQuery join eodPnl in DbContext.Set() on new { exOption.ValueDate, exOption.ExchangeOptionCode } equals new { eodPnl.ValueDate, eodPnl.ExchangeOptionCode } where eodPnl.ValueDate >= startDate && eodPnl.TradeType == "场内期权" group eodPnl by new { eodPnl.UnderlyingCode, eodPnl.BookId } into g select new EodPnlStaticsDto { BookId = g.Key.BookId, UnderlyingCode = g.Key.UnderlyingCode, TotalPnl = g.Sum(n => n.TotalPnL), TotalCommission = g.Sum(n => n.Commission), TotalCount = g.Count() }; var exOptionSums = finishedExOptionPnlQuery.ToArray(); foreach (var data in exOptionSums) { data.ValueDate = endDate; data.AssetType = "场内期权"; } sumList.AddRange(exOptionSums); //------------------------------ // 商品期货累计盈亏统计(暂时不能支持股票和商品现货) //------------------------------ if (isIncludeHedge) { var futureQuery = from a in DbContext.underlying_manager where a.MaturityDate >= startDate && a.MaturityDate <= endDate select new { a.UnderlyingCode, ValueDate = a.MaturityDate.Value }; var finishedQuery = from underlying in futureQuery join eodPnl in DbContext.Set() on new { underlying.ValueDate, underlying.UnderlyingCode } equals new { eodPnl.ValueDate, eodPnl.UnderlyingCode } where eodPnl.ValueDate >= startDate && eodPnl.TradeType == "商品期货" group eodPnl by new { eodPnl.UnderlyingCode, eodPnl.BookId } into g select new EodPnlStaticsDto { BookId = g.Key.BookId, UnderlyingCode = g.Key.UnderlyingCode, TotalPnl = g.Sum(n => n.TotalPnL), TotalCommission = g.Sum(n => n.Commission), TotalCount = g.Count() }; var futureSums = finishedQuery.ToArray(); foreach (var data in futureSums) { data.ValueDate = endDate; data.AssetType = "商品期货"; } sumList.AddRange(futureSums); } //------------------------------------------------------ // 场外期权累计盈亏统计 //------------------------------------------------------ var tradQuery = from t in DbContext.trade where t.ClientId > 0 && t.UnWindDate >= startDate && t.UnWindDate <= endDate && t.UnderlyingCode != null && ConsTrade.TradeCompleteStatus.Contains(t.TradeStatus) && t.TradeType != "结构化交易" && t.ValidState != ConsGlobal.InValid select new { t.id, t.AssetId, t.ClientId, AssetType = t.TradeType, UnderlyingCode = t.TradeType == ConsGlobal.TradeType.CashFlow ? "现金流" : t.UnderlyingCode, t.TraderId }; var query = from t in tradQuery join tc in DbContext.trade_cash on t.id equals tc.TradeId where tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted group tc by new { t.UnderlyingCode, t.AssetId, t.ClientId, t.AssetType, t.TraderId } into g select new EodPnlStaticsDto { TraderId = g.Key.TraderId, BookId = g.Key.AssetId, ClientId = g.Key.ClientId, AssetType = g.Key.AssetType, UnderlyingCode = g.Key.UnderlyingCode, TotalPnl = g.Sum(t => t.Amount), TotalCount = g.Count() }; var otcSums = query.ToArray(); foreach (var data in otcSums) { data.ValueDate = endDate; } sumList.AddRange(otcSums); //返回结果 return sumList; } /// /// 获取区间内的场内期权pnl合计 /// /// 开始日期(包括) /// 结束日期(包括) public double GetTotalPnlOfExchangeOption(DateTime startDate, DateTime endDate) { //------------------------------ // 取值日的当前持仓统计 //------------------------------ var positionQuery = from eodPnl in DbContext.Set() where eodPnl.ValueDate == endDate && eodPnl.TradeType == "场内期权" group eodPnl by eodPnl.ExchangeOptionCode into g select new { g.Key, totalPnl = g.Sum(n => n.TotalPnL) }; //------------------------------ // 区间内的过期标的统计 //------------------------------ var exchangeOptionQuery = from a in DbContext.exchange_list_option where a.MaturityDate >= startDate && a.MaturityDate < endDate select new { ValueDate = a.MaturityDate, ExchangeOptionCode = a.ContractCode }; var finishedQuery = from exOption in exchangeOptionQuery join eodPnl in DbContext.Set() on new { exOption.ValueDate, exOption.ExchangeOptionCode } equals new { eodPnl.ValueDate, eodPnl.ExchangeOptionCode } where eodPnl.ValueDate >= startDate && eodPnl.TradeType == "场内期权" group eodPnl by eodPnl.ExchangeOptionCode into g select new { g.Key, totalPnl = g.Sum(n => n.TotalPnL) }; //------------------------------ // 起算日前一交易日的累计数据 //------------------------------ var preStartDate = GetPreDate(startDate); var preStartQuery = from n in DbContext.Set() where n.ValueDate == preStartDate && n.TradeType == "场内期权" select new { n.ExchangeOptionCode, TotalPnL = (double?)n.TotalPnL }; //------------------------------ // 区间内的累计数据(持仓+到期-起始) //------------------------------ var totalPnlQuery = from t1 in positionQuery.Concat(finishedQuery) join t2 in preStartQuery on t1.Key equals t2.ExchangeOptionCode into tt from t2 in tt.DefaultIfEmpty() select t1.totalPnl - (t2.TotalPnL ?? 0); return totalPnlQuery.Sum(n => (double?)n) ?? 0; } /// /// 获取区间内的商品期货pnl合计 /// /// 开始日期(包括) /// 结束日期(包括) public double GetTotalPnlSumOfFutures(DateTime startDate, DateTime endDate) { //------------------------------ // 取值日的持仓统计 //------------------------------ var positionQuery = from eodPnl in DbContext.Set() where eodPnl.ValueDate == endDate && eodPnl.TradeType == "商品期货" group eodPnl by eodPnl.UnderlyingCode into g select new { g.Key, totalPnl = g.Sum(n => n.TotalPnL) }; //------------------------------ // 区间内的过期标的统计 //------------------------------ var futureQuery = from a in DbContext.underlying_manager where a.MaturityDate >= startDate && a.MaturityDate < endDate select new { a.UnderlyingCode, ValueDate = a.MaturityDate.Value }; var finishedQuery = from underlying in futureQuery join eodPnl in DbContext.Set() on new { underlying.ValueDate, underlying.UnderlyingCode } equals new { eodPnl.ValueDate, eodPnl.UnderlyingCode } where eodPnl.ValueDate >= startDate && eodPnl.TradeType == "商品期货" group eodPnl by eodPnl.UnderlyingCode into g select new { g.Key, totalPnl = g.Sum(n => n.TotalPnL) }; //------------------------------ // 起算日前一交易日的累计数据 //------------------------------ var preStartDate = GetPreDate(startDate); var preStartQuery = from n in DbContext.Set() where n.ValueDate == preStartDate && n.TradeType == "商品期货" select new { n.UnderlyingCode, TotalPnL = (double?)n.TotalPnL }; //------------------------------ // 区间内的数据合计(持仓+到期-起始) //------------------------------ var totalPnlQuery = from t1 in positionQuery.Concat(finishedQuery) join t2 in preStartQuery on t1.Key equals t2.UnderlyingCode into tt from t2 in tt.DefaultIfEmpty() select t1.totalPnl - (t2.TotalPnL ?? 0); return totalPnlQuery.Sum(n => (double?)n) ?? 0; } /// /// 获取取值日的期权估值(公司角度) /// public double GetTotalPvOfOptions(DateTime valueDate) { //当前持仓-起算前 var query = from t1 in DbContext.Set().Where(n => n.ValueDate == valueDate && n.TradeId > 0) join t2 in DbContext.Set().Where(n => n.ValueDate == valueDate && n.TradeId > 0) on t1.TradeId equals t2.TradeId into tt from t2 in tt.DefaultIfEmpty() select t1.Pv - (t2 == null ? 0 : t2.Pv); return query.Sum(n => (double?)n) ?? 0; } /// /// 计算综合盈亏比例: ((收取的权利金 - 支付的权利金) + 对冲盈亏 - 期权估值)/对冲账户成本 /// /// 开始日期(包括) /// 结束日期(包括) public double CalcTotalPnlRate(DateTime startDate, DateTime endDate) { //对冲盈亏 var totalHedgePnl = GetTotalPnlOfExchangeOption(startDate, endDate) + GetTotalPnlSumOfFutures(startDate, endDate); //公司角度期权估值(场外期权) var totalPv = GetTotalPvOfOptions(endDate); //公司角度(收取的权利金 - 支付的权利金) var allTotalTradePrice = -(DbContext.ClientBalanceDaily.Where(x => x.BalanceDate == endDate) .Sum(x => x.OptionPremiumSum) ?? 0) - (DbContext.ClientBalanceDaily.Where(x => x.BalanceDate == endDate) .Sum(x => x.OptionPremiumSwapSum) ?? 0); var preStartDate = GetPreDate(startDate); var lastSettleDateStartDateTotalTradePrice = -(DbContext.ClientBalanceDaily.Where(x => x.BalanceDate == preStartDate) .Sum(x => x.OptionPremiumSum) ?? 0) - (DbContext.ClientBalanceDaily.Where(x => x.BalanceDate == preStartDate) .Sum(x => x.OptionPremiumSwapSum) ?? 0); var totalTradePrice = allTotalTradePrice - lastSettleDateStartDateTotalTradePrice; //对冲账户成本 var totalInitialCost = DbContext.exchange_account.Sum(x => x.InitialCost) ?? 0; if (totalInitialCost < 1) { totalInitialCost = 1; } return (totalTradePrice + totalHedgePnl - Convert.ToDouble(totalPv)) / totalInitialCost; } /// /// 计算估算日的场内交易,商品期货股票的手续费汇总 /// /// 开始日期(包括) /// 结束日期(包括) /// public IEnumerable GetCommissionForHedge(DateTime startDate, DateTime endDate) { List tradeTypes = new List() { "场内期权", "商品期货", "股票", "信用债" }; var sourceQuery = from t in DbContext.Set().Where(t => t.ValueDate >= startDate && t.ValueDate <= endDate && tradeTypes.Contains(t.TradeType)) join un in DbContext.underlying_manager on t.UnderlyingCode equals un.UnderlyingCode group t by new { t.TradeId, t.HedgeUniqueCode } into g select new { g.Key.TradeId, g.Key.HedgeUniqueCode, maxDate = g.Max(B => B.ValueDate) }; var finishedQuery = from source in sourceQuery join eodPnl in DbContext.Set() on new { ValueDate = source.maxDate, TradeId = source.TradeId + source.HedgeUniqueCode } equals new { eodPnl.ValueDate, TradeId = eodPnl.TradeId + eodPnl.HedgeUniqueCode } select new { eodPnl.UnderlyingCode, eodPnl.BookId, eodPnl.ClientId, eodPnl.TradeType, TraderId = 0, eodPnl.Commission }; //------------------------------------------------------ // 过滤并且汇总求和 //------------------------------------------------------ var sumQuery = from n in finishedQuery group n by new { n.UnderlyingCode, n.BookId, n.ClientId, n.TradeType, n.TraderId } into g select new EodPnlStaticsDto { BookId = g.Key.BookId, AssetType = g.Key.TradeType, ClientId = g.Key.ClientId, TraderId = g.Key.TraderId, UnderlyingCode = g.Key.UnderlyingCode, TotalCommission = g.Sum(t => t.Commission), ValueDate = endDate }; return sumQuery.ToArray(); } //todo public public List CalcuTotalPnl(out List t2PositionDataDtos, DateTime dateT1, DateTime dateT2, RiskRequestModel reqModel) { // 2、查询三点数据 t1到t2已了结持仓数据 var task1 = new MarketRiskService(UserInfo).CreateGetT1DatasTask(dateT1); string tradeWhere, onSiteTradeWhere, predicateWhere, inSiteWhere; new MarketRiskService(UserInfo).MakeCondition(reqModel, out tradeWhere, out onSiteTradeWhere, out predicateWhere, out inSiteWhere); var task2 = CreateGetT2DatasTask(dateT2, reqModel, predicateWhere); tradeWhere = " UnWindDate>@StartTime and UnWindDate<@EndTime and TradeStatus in @TradeCompletedStatus and " + tradeWhere; var task3 = new MarketRiskService(UserInfo).CreateGetT1ToT2DatasTask(reqModel, dateT1, dateT2, onSiteTradeWhere, tradeWhere, inSiteWhere); task1.Start(); task2.Start(); task3.Start(); System.Threading.Tasks.Task.WaitAll(task1, task2, task3); var t1DatasDic = task1.Result.ToDictionary(p => p.TradeId + "_" + (String.IsNullOrEmpty(p.HedgeUniqueCode) ? "" : p.HedgeUniqueCode), p => p); //数据整合 if (t1DatasDic == null) { t1DatasDic = new Dictionary(); } List tradePositionTotalPnLModel = new List(); t2PositionDataDtos = task2.Result; if (task2.Result != null && task2.Result.Count > 0) { task2.Result.ForEach(p => { var model = new TradePositionTotalPnLModel { ValueDate = p.ValueDate, TradeId = p.TradeId, ParentTradeId = p.ParentTradeId ?? 0, UnderlyingCode = p.UnderlyingCode, OptionCode = p.ExchangeOptionCode, UnderlyingId = p.UnderlyingId ?? 0, BookId = p.BookId ?? 0, TradeType = p.StructureType == null || p.StructureType == "" ? p.TradeType : p.StructureType, AccruedTotalPnL = p.TotalPnL, HedgeUniqueCode = p.HedgeUniqueCode, }; var key = p.TradeId + "_" + (String.IsNullOrEmpty(p.HedgeUniqueCode) ? "" : p.HedgeUniqueCode); var t1Data = t1DatasDic.ContainsKey(key) ? t1DatasDic[key] : null; if (t1Data != null) { model.AccruedTotalPnL = model.AccruedTotalPnL - (t1Data.TotalPnL ?? 0); } tradePositionTotalPnLModel.Add(model); }); } if (task3.Result != null && task3.Result.Count > 0) { task3.Result.ForEach(p => { var model = new TradePositionTotalPnLModel { ValueDate = p.ValueDate, TradeId = p.TradeId, ParentTradeId = p.ParentTradeId ?? 0, UnderlyingCode = p.UnderlyingCode, OptionCode = p.ExchangeOptionCode, UnderlyingId = p.UnderlyingId ?? 0, BookId = p.BookId, TradeType = p.StructureType == null || p.StructureType == "" ? p.TradeType : p.StructureType, AccruedTotalPnL = p.TotalPnL, HedgeUniqueCode = p.HedgeUniqueCode, }; var key = p.TradeId + "_" + (String.IsNullOrEmpty(p.HedgeUniqueCode) ? "" : p.HedgeUniqueCode); var t1Data = t1DatasDic.ContainsKey(key) ? t1DatasDic[key] : null; if (t1Data != null) { model.AccruedTotalPnL = model.AccruedTotalPnL - (t1Data.TotalPnL ?? 0); } tradePositionTotalPnLModel.Add(model); }); } return tradePositionTotalPnLModel; } private Task> CreateGetT2DatasTask(DateTime dateT2, RiskRequestModel reqModel, string predicateWhere) { return new Task>(() => { using (var db = DbContextFactory.GetYLDbContext()) { var t2PositionDateSql = string.Format("select id,ValueDate,TradeId,HedgeUniqueCode,ParentTradeId,UnderlyingCode,ExchangeOptionCode,UnderlyingId,BookId,StructureType,TradeType,Amount,TotalPnL,Pv,Cost,Commission,DailyPnL,RoundedPv,PositionPnL,RoundedPositionPnL,Margin from {0} where {1}", db.GetTableName(), predicateWhere); db.Database.SetCommandTimeout(1800); var conn = db.Database.GetDbConnection(); var t2PositionDatas = conn.Query(t2PositionDateSql, new { EndTime = dateT2, UserAssets = reqModel.UserAssets, UserClients = reqModel.UserClients, ClientIds = reqModel.ClientIds, BookIds = reqModel.BookIds, TradeTypes = reqModel.TradeTypes, TraderIds = reqModel.TraderIds, TagIds = reqModel.TagIds, UnderlyingIds = reqModel.UnderlyingIds, VarietyIds = reqModel.VarietyIds }, commandTimeout: 1800).ToList(); return t2PositionDatas; } }); } } } //如果数据未获取到,sum时报错,这时需要加nullable类型转换