using System.Collections; using YLErp.BLL.Calculation; using YLErp.DBModels; using YLErp.Enums; using YLErp.Helpers; using YLErp.Model.Enum; using YLErp.Modules.DataProviderModule; using YLErp.QdpModule; namespace YLErp.BLL.MarginCalculation { /// /// 湘财证券 /// public class XiangCaiMarginCalculation : MarginCalculationBase { public static readonly XiangCaiMarginCalculation Instance; static XiangCaiMarginCalculation() { Instance = new XiangCaiMarginCalculation(); } private XiangCaiMarginCalculation() { } public override List RunMarginCalculation(RunMarginCalculationReq req) { if (req?.tradeList == null || !req.tradeList.Any()) { return new List(); } var calcTradeList = req.tradeList.ToList(); var resultMap = new Dictionary(); var helper = new RunMarginCalculationHelper(req, _underlyingDataProvider); helper.SetFieldsByTradeType(); var mpProvider = helper.GetMarginParamProvider(); var upLimitPrices = new ManualPriceProvider(); var upLimitPricesTwoThirds = new ManualPriceProvider(); var upLimitPricesOneThird = new ManualPriceProvider(); var downLimitPrices = new ManualPriceProvider(); var downLimitPricesTwoThirds = new ManualPriceProvider(); var downLimitPricesOneThird = new ManualPriceProvider(); var normalLimitPrices = new ManualPriceProvider(); //根据涨跌幅限制以及当日结算价计算涨停价以及跌停价 foreach (var t in req.tradeList) { if (upLimitPrices.Contains(t.UnderlyingCode)) { continue; } if (!mpProvider.TryGetUpdownLimit(t.UnderlyingCode, out var updownLimit, out var isFixed)) { updownLimit = 0.05; } var price = 0.0; switch (req.CalcMarginType) { case CalcMarginTypeEnum.InitialMargin: price = t.SpotPrice ?? 0;//期初价 break; case CalcMarginTypeEnum.None: var lastDate = QdpCalendarHelper.GetNonHolidayDefore(req.settleDate.AddDays(-1)); price = new EodPriceProvider(lastDate, true).GetPrice(t.UnderlyingCode, SettlementTypeEnum.SettlePrice);//昨日结算价 break; case CalcMarginTypeEnum.EodMargin: price = req.PriceProvider.GetPrice(t.UnderlyingCode);//结算价 break; } //var eodpriceProvider = new YLErp.Modules.DataProviderModule.EodPriceProvider(req.settleDate); //var spotPrice = eodpriceProvider.GetPrice(t.UnderlyingCode, SettlementTypeEnum.ClosePrice);//收盘价 if (isFixed) { upLimitPrices.SetPrice(t.UnderlyingCode, price + updownLimit); upLimitPricesTwoThirds.SetPrice(t.UnderlyingCode, price + (updownLimit * 2.0 / 3.0)); upLimitPricesOneThird.SetPrice(t.UnderlyingCode, price + (updownLimit / 3.0)); downLimitPrices.SetPrice(t.UnderlyingCode, price - updownLimit); downLimitPricesTwoThirds.SetPrice(t.UnderlyingCode, price - (updownLimit * 2.0 / 3.0)); downLimitPricesOneThird.SetPrice(t.UnderlyingCode, price - (updownLimit / 3.0)); } else { upLimitPrices.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 + updownLimit) : (1 - updownLimit))); upLimitPricesTwoThirds.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 + (updownLimit * 2.0 / 3.0)) : (1 - (updownLimit * 2.0 / 3.0)))); upLimitPricesOneThird.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 + (updownLimit / 3.0)) : (1 - (updownLimit / 3.0)))); downLimitPrices.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 - updownLimit) : (1 + updownLimit))); downLimitPricesTwoThirds.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 - (updownLimit * 2.0 / 3.0)) : (1 + (updownLimit * 2.0 / 3.0)))); downLimitPricesOneThird.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 - (updownLimit / 3.0)) : (1 + (updownLimit / 3.0)))); } //Normal normalLimitPrices.SetPrice(t.UnderlyingCode, price); } var prices = new[] { ("up", upLimitPrices),("upTwoThirds", upLimitPricesTwoThirds),("upOneThird", upLimitPricesOneThird), ("down", downLimitPrices),("downTwoThirds", downLimitPricesTwoThirds),("downOneThird", downLimitPricesOneThird), ("normal", normalLimitPrices) }; foreach (var price in prices) { var key = $"{price.Item1}_0"; var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades( valueDate: req.settleDate, tradeList: calcTradeList, calcScenario: req.GetCalcScenario(), priceProvider: price.Item2, pricingRequest: QdpPricingRequest.PV_ONLY, volType: "开仓", isUseTradeVol: PS.Config.IsTradeVol, preciseTimeMode: req.CalcMarginType != CalcMarginTypeEnum.EodMargin); if (tradeRiskResult.Results == null || !tradeRiskResult.Results.Any()) { continue; } foreach (var item in tradeRiskResult.Results) { var prepayment = item.Trade.StockEqvNotional * (item.Trade.trade_snowball?.PrepaymentRatio ?? 0) * (item.Trade.BuySell == "卖出" ? -1 : 1); var pv = double.IsNaN(item.ValueResult.Pv) ? 0 : (item.ValueResult.Pv - prepayment); var client = helper.GetClient(item.Trade); if (item.Trade.TradeType == "收益互换" && key != "normal_0" && client.MarginOptionType == (int)MarginOptionEnum.双向追保) { continue; } var ratio = client == null ? 1 : (req.CalcMarginType == CalcMarginTypeEnum.InitialMargin ? client.Ratio1 : client.Ratio) ?? 1; if (!helper.GetSpecialMargin(item.Trade, pv, out var value)) { value = pv * ratio; } if (req.CalcMarginType != CalcMarginTypeEnum.InitialMargin && item.Trade.TradeType == "收益互换") { value = pv * ratio; } if (req.CalcMarginType != CalcMarginTypeEnum.InitialMargin && (item.Trade.TradeType == "雪球期权" && item.Trade.trade_snowball.PrepaymentUsed || item.Trade.TradeType == "收益互换")) { var initialMargin = item.Trade.TradeType == "雪球期权" ? -prepayment : ((item.Trade.trade_swap.GetMarginRate ?? 0) - (item.Trade.trade_swap.PayMarginRate ?? 0)) * item.Trade.StockEqvNotional; value = GetComparisonInitialMargin(value, client, item.Trade.TradeType, initialMargin); } var contains = resultMap.TryGetValue(item.Trade.id, out var tempTradeSpan); if (!contains) { resultMap[item.Trade.id] = tempTradeSpan = helper.CreateTradeSpan(item.Trade); } switch (key) { case "up_0": tempTradeSpan.Spv1 = value; break; case "upTwoThirds_0": tempTradeSpan.Spv2 = value; break; case "upOneThird_0": tempTradeSpan.Spv3 = value; break; case "normal_0": if (item.Trade.TradeType == "收益互换" && client.MarginOptionType == (int)MarginOptionEnum.双向追保) { tempTradeSpan.SetAllSpvAndWorst(value); } tempTradeSpan.Spv4 = value; break; case "down_0": tempTradeSpan.Spv5 = value; break; case "downTwoThirds_0": tempTradeSpan.Spv6 = value; break; case "downOneThird_0": tempTradeSpan.Spv7 = value; break; } if (contains) { tempTradeSpan.SetWorstCastClientPayable(); } resultMap[item.Trade.id].UnderlyingPrice = req.PriceProvider.GetPrice(item.Trade.UnderlyingCode); } } return resultMap.Values.ToList(); } public override List CalcClientMargin(CalcClientMarginReq req) { var clientSpanNews = new List(); using var db = new YLContext(); var tradeIds = req.tradeSpans.Select(t => t.TradeId).ToList(); var tradeList = db.trade.AsNoTracking().Where(t => tradeIds.Contains(t.id)).ToList(); var underlyingIdList = tradeList.Select(t => t.UnderlyingId).ToList(); var underlyingList = db.underlying_manager.AsNoTracking().Where(t => underlyingIdList.Contains(t.id)).ToList(); var tradeSpanInfo = (from tradeSpan in req.tradeSpans join trade in tradeList on tradeSpan.TradeId equals trade.id join um in underlyingList on trade.UnderlyingId equals um.id where tradeSpan.ValueDate == req.settleDate select new { trade, tradeSpan, um.UnderlyingTypeId, UnderlyingId = um.id }).ToList(); var clientGroups = tradeSpanInfo.GroupBy(t => t.trade.ClientId); foreach (var clientGroup in clientGroups) { var varietyGroups = clientGroup.GroupBy(t => t.UnderlyingTypeId).Select(t => new ClientSpan { VarietyId = t.Key, ClientId = clientGroup.Key, ValueDate = req.settleDate, Spv1 = t.Sum(g => g.tradeSpan.Spv1) * (-1), Spv2 = t.Sum(g => g.tradeSpan.Spv2) * (-1), Spv3 = t.Sum(g => g.tradeSpan.Spv3) * (-1), Spv4 = t.Sum(g => g.tradeSpan.Spv4) * (-1), Spv5 = t.Sum(g => g.tradeSpan.Spv5) * (-1), Spv6 = t.Sum(g => g.tradeSpan.Spv6) * (-1), Spv7 = t.Sum(g => g.tradeSpan.Spv7) * (-1), OptId = req.userId, OptName = req.userName, OptDate = DateTime.Now, SpanType = req.SpanType }).ToList(); foreach (var item in varietyGroups) { item.WorstCastClientPayable = Math.Min(Math.Min(Math.Min(Math.Min(Math.Min(Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0), item.Spv3 ?? 0), item.Spv4 ?? 0), item.Spv5 ?? 0), item.Spv6 ?? 0), item.Spv7 ?? 0); #region 更新tradeSpan,使得每笔交易的持仓预付金和客户预付金计算用的Spv组保持一致 var tradeIdList = clientGroup.Where(x => x.UnderlyingTypeId == item.VarietyId).Select(x => x.tradeSpan.TradeId); var tradeSpansUpdate = db.trade_span.Where(x => tradeIdList.Contains(x.TradeId) && x.ClientId == item.ClientId && x.ValueDate == req.settleDate).ToList(); var tradeSpansReq = req.tradeSpans.Where(x => tradeIdList.Contains(x.TradeId) && x.ClientId == item.ClientId && x.ValueDate == req.settleDate).ToList(); //单向追保,客户收取预付金为0 if (!HasTwoSideMargin(item.ClientId) && item.WorstCastClientPayable > 0) { item.WorstCastClientPayable = 0; tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = 0); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = 0); } else { if (item.WorstCastClientPayable == item.Spv1) { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv1); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv1); } else if (item.WorstCastClientPayable == item.Spv2) { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv2); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv2); } else if (item.WorstCastClientPayable == item.Spv3) { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv3); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv3); } else if (item.WorstCastClientPayable == item.Spv4) { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv4); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv4); } else if (item.WorstCastClientPayable == item.Spv5) { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv5); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv5); } else if (item.WorstCastClientPayable == item.Spv6) { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv6); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv6); } else { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv7); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv7); } } #endregion } var clientSpan = new ClientSpan { ClientId = clientGroup.Key, ValueDate = req.settleDate, Spv1 = varietyGroups.Sum(g => g.Spv1), Spv2 = varietyGroups.Sum(g => g.Spv2), Spv3 = varietyGroups.Sum(g => g.Spv3), Spv4 = varietyGroups.Sum(g => g.Spv4), Spv5 = varietyGroups.Sum(g => g.Spv5), Spv6 = varietyGroups.Sum(g => g.Spv6), Spv7 = varietyGroups.Sum(g => g.Spv7), //负数代表客户应缴预付金,正数代表客户应收预付金 WorstCastClientPayable = varietyGroups.Sum(g => g.WorstCastClientPayable), OptId = req.userId, OptName = req.userName, OptDate = DateTime.Now, SpanType = req.SpanType, AdditionalWorstCastClientPayable = req.clientAdditionalMarginDic != null && req.clientAdditionalMarginDic.TryGetValue(clientGroup.Key, out var dd) ? dd : 0 }; clientSpanNews.Add(clientSpan); } //span类型为实时删除所有实时计算的交易的预付金信息 if (req.SpanType == ClientSpan.SpanType_RealTime) { if (req.RefreshClientIds != null) { db.BulkDelete($"{nameof(ClientSpan.ClientId)} in @ids", new { ids = req.RefreshClientIds }); } else { db.BulkDelete($"{nameof(ClientSpan.SpanType)}=@SpanType", new { req.SpanType }); } } else { if (req.ClientIds != null) { var sql = $"{nameof(ClientSpan.ClientId)} in @ids and {nameof(ClientSpan.ValueDate)}='{req.settleDate.ToSqlDate()}' and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0"; db.BulkDelete(sql, new { ids = req.ClientIds }); } else { var sql = $"{nameof(ClientSpan.ValueDate)}='{req.settleDate.ToSqlDate()}' and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0"; db.BulkDelete(sql); } var clientSpanOldsWithFlag = db.client_span.Where(t => t.ValueDate == req.settleDate && t.SpanType == req.SpanType && t.ModifiedFlag) .Select(n => new { n.ValueDate, n.ClientId }).ToList(); //筛选出可以修改的clientSpan clientSpanNews = clientSpanNews.Where(c => !clientSpanOldsWithFlag.Any(t => t.ValueDate == c.ValueDate && t.ClientId == c.ClientId)).ToList(); } if (clientSpanNews.Count > 0) { MySqlBulkExtensions.BulkInsert(db, clientSpanNews); } db.SaveChanges(); return req.tradeSpans; } /// /// 根据追保方向判断和初始预付金比较逻辑 /// /// private double GetComparisonInitialMargin(double value, InnerClient client, string tradeType, double initialMargin) { if (client.MarginOptionType == (int)MarginOptionEnum.双向追保 || tradeType == "雪球期权") { if (initialMargin >= 0) { value = Math.Max(initialMargin, value); } else { value = Math.Min(initialMargin, value); } } else if (client.MarginOptionType == (int)MarginOptionEnum.单向追保) { if (initialMargin >= 0) { value = Math.Max(initialMargin, Math.Max(value, 0)); } else { value = Math.Min(initialMargin, Math.Min(value, 0)); } } return value; } public static double TradeMargin(RunMarginCalculationReq req, trade t) { var _underlyingDataProvider = new UnderlyingDataProvider(); var helper = new RunMarginCalculationHelper(req, _underlyingDataProvider); helper.SetFieldsByTradeType(); var mpProvider = helper.GetMarginParamProvider(); var upLimitPrices = new ManualPriceProvider(); var upLimitPricesTwoThirds = new ManualPriceProvider(); var upLimitPricesOneThird = new ManualPriceProvider(); var downLimitPrices = new ManualPriceProvider(); var downLimitPricesTwoThirds = new ManualPriceProvider(); var downLimitPricesOneThird = new ManualPriceProvider(); var normalLimitPrices = new ManualPriceProvider(); //根据涨跌幅限制以及当日结算价计算涨停价以及跌停价 //if (upLimitPrices.Contains(t.UnderlyingCode)) //{ // continue; //} if (!mpProvider.TryGetUpdownLimit(t.UnderlyingCode, out var updownLimit, out var isFixed)) { updownLimit = 0.05; } var price = 0.0; switch (req.CalcMarginType) { case CalcMarginTypeEnum.InitialMargin: price = t.SpotPrice ?? 0;//期初价 break; case CalcMarginTypeEnum.None: var lastDate = QdpCalendarHelper.GetNonHolidayDefore(req.settleDate.AddDays(-1)); price = new EodPriceProvider(lastDate, true).GetPrice(t.UnderlyingCode, SettlementTypeEnum.SettlePrice);//昨日结算价 break; case CalcMarginTypeEnum.EodMargin: price = req.PriceProvider.GetPrice(t.UnderlyingCode);//结算价 break; } if (isFixed) { upLimitPrices.SetPrice(t.UnderlyingCode, price + updownLimit); upLimitPricesTwoThirds.SetPrice(t.UnderlyingCode, price + (updownLimit * 2.0 / 3.0)); upLimitPricesOneThird.SetPrice(t.UnderlyingCode, price + (updownLimit / 3.0)); downLimitPrices.SetPrice(t.UnderlyingCode, price - updownLimit); downLimitPricesTwoThirds.SetPrice(t.UnderlyingCode, price - (updownLimit * 2.0 / 3.0)); downLimitPricesOneThird.SetPrice(t.UnderlyingCode, price - (updownLimit / 3.0)); } else { upLimitPrices.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 + updownLimit) : (1 - updownLimit))); upLimitPricesTwoThirds.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 + (updownLimit * 2.0 / 3.0)) : (1 - (updownLimit * 2.0 / 3.0)))); upLimitPricesOneThird.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 + (updownLimit / 3.0)) : (1 - (updownLimit / 3.0)))); downLimitPrices.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 - updownLimit) : (1 + updownLimit))); downLimitPricesTwoThirds.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 - (updownLimit * 2.0 / 3.0)) : (1 + (updownLimit * 2.0 / 3.0)))); downLimitPricesOneThird.SetPrice(t.UnderlyingCode, price * (price > 0 ? (1 - (updownLimit / 3.0)) : (1 + (updownLimit / 3.0)))); } //Normal normalLimitPrices.SetPrice(t.UnderlyingCode, price); var prices = new[] { ("up", upLimitPrices),("upTwoThirds", upLimitPricesTwoThirds),("upOneThird", upLimitPricesOneThird), ("down", downLimitPrices),("downTwoThirds", downLimitPricesTwoThirds),("downOneThird", downLimitPricesOneThird), ("normal", normalLimitPrices) }; var values = new ArrayList(); foreach (var p in prices) { var key = $"{p.Item1}_0"; var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades( valueDate: req.settleDate, tradeList: new[] { t }, calcScenario: req.GetCalcScenario(), priceProvider: p.Item2, pricingRequest: QdpPricingRequest.PV_ONLY, volType: "开仓", isUseTradeVol: PS.Config.IsTradeVol, preciseTimeMode: req.CalcMarginType != CalcMarginTypeEnum.EodMargin); if (tradeRiskResult.Results == null || !tradeRiskResult.Results.Any()) { continue; } foreach (var item in tradeRiskResult.Results) { var pv = double.IsNaN(item.ValueResult.Pv) ? 0 : (item.ValueResult.Pv - item.Trade.StockEqvNotional * (item.Trade.trade_snowball?.PrepaymentRatio ?? 0) * (item.Trade.BuySell == "卖出" ? -1 : 1)); double value = 0; if (item.Trade.TradeType == "收益互换") { var client = helper.GetClient(item.Trade); var ratio = client == null ? 1 : (req.CalcMarginType == CalcMarginTypeEnum.InitialMargin ? client.Ratio1 : client.Ratio) ?? 1; value = pv * ratio; } else { if (!helper.GetSpecialMargin(item.Trade, pv, out value)) { var client = helper.GetClient(item.Trade); var ratio = client == null ? 1 : (req.CalcMarginType == CalcMarginTypeEnum.InitialMargin ? client.Ratio1 : client.Ratio) ?? 1; value = pv * ratio; } } values.Add(value); } } return (double)values.ToArray().Max(); } private List RunTradeSpanMarginCalculation(RunMarginCalculationReq req) { //结果集 var resultMap = new List(); var subTradeList = req.tradeList.Where(x => x.ParentTradeId != 0 && x.IsGroup == 0).GroupBy(x => x.ParentTradeId); if (subTradeList.Count() > 0) { foreach (var item in subTradeList) { var spans = RunMarginCalculation(req.Clone(item.ToList())); var spv1 = spans.Sum(O => O.Spv1 ?? 0); var spv2 = spans.Sum(O => O.Spv2 ?? 0); var spv3 = spans.Sum(O => O.Spv3 ?? 0); var spv4 = spans.Sum(O => O.Spv4 ?? 0); var spv5 = spans.Sum(O => O.Spv5 ?? 0); var spv6 = spans.Sum(O => O.Spv6 ?? 0); var spv7 = spans.Sum(O => O.Spv7 ?? 0); var spvArr = new[] { spv1, spv2, spv3, spv4, spv5, spv6, spv7 }; var maxSpv = spvArr.Max(); var index = Array.IndexOf(spvArr, maxSpv); spans.ForEach(x => x.WorstCastClientPayable = new[] { x.Spv1, x.Spv2, x.Spv3, x.Spv4, x.Spv5, x.Spv6, x.Spv7 }[index]); resultMap.AddRange(spans); } } var singleTradeList = req.tradeList.Where(O => O.ParentTradeId == 0).ToList(); if (singleTradeList.Count > 0) { resultMap.AddRange(RunMarginCalculation(req.Clone(singleTradeList))); } return resultMap; } public override double GetTradeMargin(GetTradeMarginReq req) { using var db = new YLContext(); if (req.trade.TradeType == "结构化交易") { req.trade.SubTrades = db.trade.Where(x => x.ParentTradeId == req.realTradeId).ToList(); } var marginReq = req.GetRunMarginCalculationReq(); if (req.trade.IsGroup == 1) { marginReq.tradeList = db.trade.Where(x => x.ParentTradeId == req.realTradeId).ToList(); } var tradeMargin = RunMarginCalculation(marginReq); if (null != tradeMargin) { var margin = req.trade.IsGroup == 1 ? tradeMargin.Sum(x => x.WorstCastClientPayable ?? 0) : (tradeMargin.FirstOrDefault()?.WorstCastClientPayable ?? 0); return margin; } return 0.0; } } }