using Qdp.Pricing.Base.Implementations; using System.Linq.Expressions; using YLErp.Abstract.DataProviders; using YLErp.BLL; using YLErp.Configuration; using YLErp.DBModels.Consts; using YLErp.DBModels.Enums; using YLErp.Enums; using YLErp.Modules.CalculationModule; using YLErp.Modules.DataProviderModule; using YLErp.Modules.EodModule.SettlementModule; using YLErp.Modules.ExchangeOptionTradeModule; using YLErp.QdpModule; using YLErp.QdpModule.Constants; namespace YLErp.Modules.ScenarioModule { /// /// 情景分析计算服务 /// public class ScenarioCalcService : YLBaseService { const string SpotPriceFormat = "0.00##"; const string RatePercentFormat = "0.00##%"; public ScenarioCalcService(OptUserInfo userInfo) : base(userInfo) { } //获取日终持仓 private IEnumerable GetEodPosition(TradeScenarioSearchModel searchModel) { var predicate = PredicateBuilder.Create( t => t.ValueDate == searchModel.PositionDate.Value && !ScenarioHelper.ExcludeTradeTypes.Contains(t.TradeType)); if (searchModel.UserAssets != null || searchModel.UserClients != null) { predicate = predicate.And(n => searchModel.UserAssets.Contains(n.BookId) || searchModel.UserClients.Contains(n.ClientId)); } if (searchModel.IsSelAll) { if (searchModel.ClientIds != null && searchModel.ClientIds.Any(n => n > 0)) { predicate = predicate.And(x => searchModel.ClientIds.Contains(x.ClientId)); } if (searchModel.UnderlyingIds != null && searchModel.UnderlyingIds.Any(n => n > 0)) { predicate = predicate.And(x => searchModel.UnderlyingIds.Contains(x.UnderlyingId)); } if (searchModel.AssetIds != null && searchModel.AssetIds.Any(n => n > 0)) { predicate = predicate.And(x => searchModel.AssetIds.Contains(x.BookId)); } if (searchModel.VarietyIds != null && searchModel.VarietyIds.Any(n => n > 0)) { predicate = predicate.And( x => DbContext.underlying_manager.Any(n => n.UnderlyingCode == x.UnderlyingCode && searchModel.VarietyIds.Contains(n.UnderlyingTypeId))); } if (searchModel.TradeTypes != null && searchModel.TradeTypes.Any(n => !string.IsNullOrEmpty(n))) { if (searchModel.TradeTypes.Contains("场外期权")) { predicate = predicate.And(x => x.TradeId > 0 || searchModel.TradeTypes.Contains(x.TradeType)); } else { predicate = predicate.And(x => searchModel.TradeTypes.Contains(x.TradeType)); } } } else if (searchModel.EodTradeIds == null || !searchModel.EodTradeIds.Any(n => n > 0)) { return Enumerable.Empty(); } else { predicate = predicate.And(x => searchModel.EodTradeIds.Contains(x.id)); } var query = from et in DbContext.eod_trade_position.Where(predicate) join td in DbContext.trade on et.TradeId equals td.id into temptd from td in temptd.DefaultIfEmpty() select new tradeDto { id = et.TradeId, ClientId = et.ClientId, AssetId = et.BookId, TradeType = et.TradeType, UnderlyingId = et.UnderlyingId, UnderlyingCode = et.UnderlyingCode, BuySell = td != null ? td.BuySell : et.PositionType == "long" ? "买入" : "卖出", ExerciseDate = td != null ? td.ExerciseDate : null, ExerciseMode = td != null ? td.ExerciseMode : null, OptionType = td != null ? td.OptionType : null, Strike = td != null ? td.Strike : null, IsMoneynessOption = td != null ? td.IsMoneynessOption : "否", Notional = td == null ? (et.PositionType == "long" ? et.Amount : -et.Amount) : et.Amount, TradeDate = td != null ? td.TradeDate : null, StartDate = td != null ? td.StartDate : null, SpotPrice = td != null ? td.SpotPrice : null, MaturityDate = td != null ? td.MaturityDate : null, ExchangeOptionCode = et.ExchangeOptionCode }; return query.ToArray(); } //获取日间持仓 private IEnumerable GetIntradayPosition(TradeScenarioSearchModel searchModel) { var blQuery = true; //场外交易持仓 var tdPredicate = PredicateBuilder.Create( t => t.ExerciseDate >= valuedateBLL.ValueDate && ConsTrade.NeedMarginTradeStatusList.Contains(t.TradeStatus) && !ScenarioHelper.ExcludeTradeTypes.Contains(t.TradeType) && t.ClientId > 0 && t.ValidState != "InValid"); if (searchModel.UserAssets != null || searchModel.UserClients != null) { tdPredicate = tdPredicate.And(n => searchModel.UserAssets.Contains(n.AssetId) || searchModel.UserClients.Contains(n.ClientId)); } if (searchModel.IsSelAll) { if (searchModel.TradeTypes != null && searchModel.TradeTypes.Any(n => !string.IsNullOrEmpty(n))) { if (blQuery = searchModel.TradeTypes.Contains("场外期权")) { tdPredicate = tdPredicate.And(x => searchModel.TradeTypes.Contains(x.TradeType)); } else { tdPredicate = null; } } if (blQuery) { if (searchModel.ClientIds != null && searchModel.ClientIds.Any(n => n > 0)) { tdPredicate = tdPredicate.And(x => searchModel.ClientIds.Contains(x.ClientId)); } if (searchModel.UnderlyingIds != null && searchModel.UnderlyingIds.Any(n => n > 0)) { tdPredicate = tdPredicate.And(x => searchModel.UnderlyingIds.Contains(x.UnderlyingId)); } if (searchModel.AssetIds != null && searchModel.AssetIds.Any(n => n > 0)) { tdPredicate = tdPredicate.And(x => searchModel.AssetIds.Contains(x.AssetId)); } if (searchModel.VarietyIds != null && searchModel.VarietyIds.Any(n => n > 0)) { tdPredicate = tdPredicate.And( x => DbContext.underlying_manager.Any(n => n.UnderlyingCode == x.UnderlyingCode && searchModel.VarietyIds.Contains(n.UnderlyingTypeId))); } } } else if (searchModel.OtcTradeIds != null && searchModel.OtcTradeIds.Any(n => n > 0)) { tdPredicate = tdPredicate.And(x => searchModel.OtcTradeIds.Contains(x.id)); } else { tdPredicate = null; } var tdList = tdPredicate == null ? Array.Empty() : DbContext.trade.Where(tdPredicate).ToArray(); //场内交易持仓 blQuery = true; var exPredicate = PredicateBuilder.True(); if (searchModel.IsSelAll) { if (searchModel.ClientIds != null && searchModel.ClientIds.Any(n => n > 0)) { blQuery = false; } if (blQuery && searchModel.TradeTypes != null && searchModel.TradeTypes.Any(n => !string.IsNullOrEmpty(n))) { var tradeTypes = searchModel.TradeTypes.Union(ConsTrade.TradeTypesForHedge).ToArray(); if (blQuery = tradeTypes.Any()) { exPredicate = exPredicate.And(x => tradeTypes.Contains(x.TradeType)); } } if (blQuery) { if (searchModel.UnderlyingIds != null && searchModel.UnderlyingIds.Any(n => n > 0)) { exPredicate = exPredicate.And(x => searchModel.UnderlyingIds.Contains(x.UnderlyingId)); } if (searchModel.AssetIds != null && searchModel.AssetIds.Any(n => n > 0)) { exPredicate = exPredicate.And(x => searchModel.AssetIds.Contains(x.BookId)); } if (searchModel.VarietyIds != null && searchModel.VarietyIds.Any(n => n > 0)) { exPredicate = exPredicate.And( x => DbContext.underlying_manager.Any(n => n.UnderlyingCode == x.UnderlyingCode && searchModel.VarietyIds.Contains(n.UnderlyingTypeId))); } } } else if (searchModel.ExchangeTradeIds != null && searchModel.ExchangeTradeIds.Any(n => n > 0)) { exPredicate = exPredicate.And(x => searchModel.ExchangeTradeIds.Contains(x.id)); } else { blQuery = false; } IEnumerable exTdList = null; if (blQuery) { exTdList = DbContext.TradePosition.Where(exPredicate).Select(n => new tradeDto { AssetId = n.BookId, BuySell = n.PositionType == PositionTypeFlag.Long ? "买入" : "卖出", ExerciseDate = n.MaturityDate, //OptionType, //Strike, IsMoneynessOption = "否", Notional = n.Position, TradeAmount = n.Position, TradeDate = n.CreateTime, StartDate = n.CreateTime, TradeType = n.TradeType, UnderlyingId = n.UnderlyingId, UnderlyingCode = n.UnderlyingCode, //UnderlyingAssetClass, //UnderlyingInstrumentType, MaturityDate = n.MaturityDate, ExchangeOptionCode = n.InstrumentCode }).ToArray(); } return exTdList != null ? tdList.Concat(exTdList).ToArray() : tdList; } /// /// 计算 /// public ScenarioCalcResult Calculate(ScenarioRequest req) { if (req is null) { throw new ArgumentNullException(nameof(req)); } var (xActions, yActions) = GetCalcActions(req.ConfigId, out var config); return Calculate(req, config, xActions, yActions); } /// /// 计算 /// public ScenarioCalcResult Calculate(ScenarioCalcApiRequest req) { if (req is null) { throw new ArgumentNullException(nameof(req)); } var config = req.ScenarioConfig; if (config == null) { throw new ServiceException("缺少情景分析配置"); } if (config.xType == config.yType) { throw new ServiceException("横纵坐标类型 不能一致"); } var req2 = new ScenarioRequest { SearchModel = new TradeScenarioSearchModel { IsSelAll = true, PositionDate = req?.ValueDate }, ValueDate = req.ValueDate, RiskFreeRate = req.RiskFreeRate, UseTradeDivendRate = req.UseTradeDivendRate, VolType = req.VolType, Underlyings = req.UnderlyingParams }; if (req.DataFilter != null) { var clientNames = req.DataFilter.ClientNames; var clientNumbers = req.DataFilter.ClientNumbers; if (clientNames != null && clientNames.All(n => string.IsNullOrEmpty(n))) { clientNames = null; } if (clientNumbers != null && clientNumbers.All(n => string.IsNullOrEmpty(n))) { clientNumbers = null; } if (clientNames != null || clientNumbers != null) { req2.SearchModel.ClientIds = DataCacheProvider.GetClientDataSource().AsQueryable() .Where(n => (clientNames == null || clientNames.Contains(n.Name)) && clientNumbers == null || clientNumbers.Contains(n.Number)) .Select(n => n.id).ToList(); } IEnumerable assetGroupIds = null; if (req.DataFilter.AssetGroupNames != null && req.DataFilter.AssetGroupNames.Any(n => !string.IsNullOrEmpty(n))) { assetGroupIds = DataCacheProvider.GetAssetUnitGroupDataSource().AsQueryable() .Where(n => req.DataFilter.AssetGroupNames.Contains(n.Name)).Select(n => n.id).ToList(); } var assetNames = req.DataFilter.AssetBookNames; if (assetNames != null && assetNames.All(n => string.IsNullOrEmpty(n))) { assetNames = null; } if (assetGroupIds != null || assetNames != null) { req2.SearchModel.AssetIds = DataCacheProvider.GetAssetUnitDataSource().AsQueryable() .Where(n => (assetGroupIds == null || assetGroupIds.Contains(n.GroupId)) && (assetNames == null || assetNames.Contains(n.Name))) .Select(n => n.id).ToList(); } if (req.DataFilter.UnderlyingCodes != null && req.DataFilter.UnderlyingCodes.Any(n => !string.IsNullOrEmpty(n))) { req2.SearchModel.UnderlyingIds = DataCacheProvider.GetUnderlyingDataSource().AsQueryable() .Where(n => req.DataFilter.UnderlyingCodes.Contains(n.UnderlyingCode)).Select(n => n.id).ToList(); } if (req.DataFilter.VarietyCodes != null && req.DataFilter.VarietyCodes.Any(n => !string.IsNullOrEmpty(n))) { req2.SearchModel.VarietyIds = DataCacheProvider.GetVarietyDataSource().AsQueryable() .Where(n => req.DataFilter.VarietyCodes.Contains(n.VarietyCode)).Select(n => n.id).ToList(); } req2.SearchModel.TradeTypes = req.DataFilter.TradeTypes; } var xActions = CalcDataActionHelper.ConvertToActions(config.xType, config.xRates); var yActions = CalcDataActionHelper.ConvertToActions(config.yType, config.yRates); return Calculate(req2, config, xActions, yActions); } /// /// 计算 /// public ScenarioCalcResult Calculate(ScenarioRequest req, ScenarioConfigV2 config, List xActions = null, List yActions = null) { if (req is null) { throw new ArgumentNullException(nameof(req)); } if (config is null) { throw new ArgumentNullException(nameof(config)); } if (xActions is null) { xActions = CalcDataActionHelper.ConvertToActions(config.xType, config.xRates); } if (yActions is null) { yActions = CalcDataActionHelper.ConvertToActions(config.yType, config.yRates); } var valueDate = req.ValueDate >= valuedateBLL.ValueDate ? valuedateBLL.ValueDate : req.ValueDate; //获取用于计算的交易列表 var tdList = req.SearchModel.PositionDate != null && req.SearchModel.PositionDate < valuedateBLL.ValueDate ? GetEodPosition(req.SearchModel) : GetIntradayPosition(req.SearchModel); var volProvider = EodVolProviderFactory.GetEodVolProvider(valueDate, req.VolType); var priceProvider = valueDate == valuedateBLL.ValueDate ? (IEodPriceProvider)new EodPriceProviderAs(DataCacheProvider.GetUnderlyingDataSource()) : new EodPriceProvider(valueDate); //合并计算结果和标的 var resultSumDic = new Dictionary(StringComparer.OrdinalIgnoreCase); //合并计算结果和交易 var tradeSumDic = new Dictionary(StringComparer.OrdinalIgnoreCase); var underlyingSumDic = req.Underlyings == null ? new Dictionary(StringComparer.OrdinalIgnoreCase) : req.Underlyings.ToDictionary(t => t.UnderlyingCode, v => { v.OriVol = v.ConstVol; v.ConstVol = null; v.OriDividenRate = v.DividenRate; v.DividenRate = null; return v; }, StringComparer.OrdinalIgnoreCase); var zeroResultSumDic = new Dictionary(StringComparer.OrdinalIgnoreCase); void setSumDic(trade td, CalcData calcData, underlying_manager underlying, TradeValueResult zeroResult, Dictionary sumDic, int x, int y, string key) { if (!sumDic.TryGetValue(key, out var sumResult)) { sumDic[key] = sumResult = new ScenarioCalcResultItem { TradeId = td.id, xIndex = x, yIndex = y, UnderlyingCode = td.UnderlyingCode, UnderlyingPrice = calcData.SpotPrice.ToString(SpotPriceFormat) }; } var clone = calcData.Clone(); xActions[x].UpdateCalcData(clone); yActions[y].UpdateCalcData(clone); var callResult = GetCallResult(td, clone); sumResult.AddValueResult(callResult, underlying.ContractSize, zeroResult.Pv, zeroResult.Delta); } // 矩阵计算 var matrixCalc = new Action((trade td, CalcData calcData, underlying_manager underlying, TradeValueResult zeroResult) => { for (var x = 0; x < xActions.Count; x++) { for (var y = 0; y < yActions.Count; y++) { var key = $"{td.UnderlyingCode}#{x}_{y}"; setSumDic(td, calcData, underlying, zeroResult, resultSumDic, x, y, key); if (td.id > 0) { key = $"{td.id}#{x}_{y}"; setSumDic(td, calcData, underlying, zeroResult, tradeSumDic, x, y, key); } } } }); var exchangeOptionPriceProvider = new Lazy(() => new EodExchangeOptionPriceProvider(valueDate, true)); foreach (var td in tdList) { var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode); if (underlying == null) { continue; } td.TradeAmount = td.Notional / underlying.CountRatio; td.UnderlyingAssetClass = underlying.UnderlyingType; td.UnderlyingInstrumentType = underlying.UnderlyingInstrumentType; if (td.MaturityDate == null) { td.MaturityDate = underlying.MaturityDate; } double? vol = null; double dividenRate; if (underlyingSumDic.TryGetValue(td.UnderlyingCode, out var scenarioUnderlying)) { vol = scenarioUnderlying.OriVol; if (scenarioUnderlying.OriDividenRate.HasValue) { dividenRate = scenarioUnderlying.OriDividenRate.Value; } else { dividenRate = req.UseTradeDivendRate && td.TradeType != "场内期权" ? (td.DividendRate ?? 0) : (underlying.DividendRate ?? 0); } if (!scenarioUnderlying.UnderlyingPrice.HasValue) { scenarioUnderlying.UnderlyingPrice = priceProvider.GetPrice(td.UnderlyingCode, td.SettlementType); } } else { var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId); underlyingSumDic[td.UnderlyingCode] = scenarioUnderlying = new ScenarioUnderlyingModel { UnderlyingCode = td.UnderlyingCode, //DividenRate = underlying.DividendRate ?? 0, UnderlyingPrice = priceProvider.GetPrice(td.UnderlyingCode, td.SettlementType), VarietyCode = variety?.VarietyCode ?? underlying.CommodityCode }; dividenRate = req.UseTradeDivendRate && td.TradeType != "场内期权" ? (td.DividendRate ?? 0) : (underlying.DividendRate ?? 0); } if (ConsTrade.TradeTypesForHedge.Contains(td.TradeType)) { if (td.TradeType == "场内期权") { var exchangeOption = DataCacheProvider.GetExchangeListOptionDataSource().GetData(td.ExchangeOptionCode); if (exchangeOption == null) { continue; } td.ExerciseDate = exchangeOption.MaturityDate; td.ExerciseMode = exchangeOption.ExerciseMode; td.OptionType = exchangeOption.OptionType; td.Strike = exchangeOption.Strike; td.TradeDate = td.StartDate = exchangeOption.OpenDate; if (!vol.HasValue) { string userGroup = null; if (ConsUserGroup.HasGroup) { userGroup = DataCacheProvider.GetAssetUnitDataSource().GetData(td.AssetId)?.UserGroup; } if (req.VolType == "对冲" || req.VolType == "持仓") { var request = new ExchangeOptionVolRequest { ValueDate = valueDate, OptionCode = td.ExchangeOptionCode, UnderlyingPrice = scenarioUnderlying.UnderlyingPrice.Value, ExchangeOptionPriceProvider = exchangeOptionPriceProvider, UserGroup = userGroup, VolType = req.VolType }; vol = new ExchangeOptionVolQueryService(OptUser).GetVolValue(request).volValue ?? ConsVolInfos.defVol; } else { vol = volProvider.GetVol(td, scenarioUnderlying.UnderlyingPrice.Value); } } } } else { tradeBLL.SetFieldsByTradeType(td); if (!vol.HasValue) { vol = volProvider.GetVol(td, scenarioUnderlying.UnderlyingPrice.Value); } } var calcData = new CalcData { ValueDate = req.ValueDate, SpotPrice = scenarioUnderlying.UnderlyingPrice ?? 0, DividenRate = dividenRate, RiskFreeRate = req.RiskFreeRate, Vol = vol ?? ConsGlobal.DefaultVol, PricingRequest = PricingRequest.Pv | PricingRequest.Delta | PricingRequest.Gamma | PricingRequest.Theta | PricingRequest.Vega }; scenarioUnderlying.SetVol(calcData.Vol); scenarioUnderlying.SetDividenRate(calcData.DividenRate); var clone = calcData.Clone(); clone.PricingRequest = PricingRequest.Pv | PricingRequest.Delta; var zeroResult = GetCallResult(td, clone) ?? new TradeValueResult(false) { ErrorMessage = "未计算出结果" }; // 合计zeroResult if (!zeroResultSumDic.TryGetValue(td.UnderlyingCode, out var zeroSumResult)) { zeroResultSumDic[td.UnderlyingCode] = zeroSumResult = new ScenarioCalcResultItem() { UnderlyingCode = td.UnderlyingCode, UnderlyingPrice = calcData.SpotPrice.ToString(SpotPriceFormat) }; } zeroSumResult.AddValueResult(zeroResult, underlying.ContractSize, zeroResult.Pv, zeroResult.Delta); // 矩阵计算 matrixCalc(td, calcData, underlying, zeroResult); //for (var x = 0; x < xActions.Count; x++) //{ // for (var y = 0; y < yActions.Count; y++) // { // var key = $"{td.UnderlyingCode}#{x}_{y}"; // if (!resultSumDic.TryGetValue(key, out var sumResult)) // { // resultSumDic[key] = sumResult = new ScenarioCalcResultItem // { // xIndex = x, // yIndex = y, // UnderlyingCode = td.UnderlyingCode, // UnderlyingPrice = calcData.SpotPrice.ToString(SpotPriceFormat) // }; // } // clone = calcData.Clone(); // xActions[x].UpdateCalcData(clone); // yActions[y].UpdateCalcData(clone); // var callResult = GetCallResult(td, clone); // sumResult.AddValueResult(callResult, underlying.ContractSize, zeroResult.Pv, zeroResult.Delta); // } //} } #region 宏源-完全对冲假设 var actionType = "标的价格"; var hedgeResuts = zeroResultSumDic.Values.Where(b => b.Delta != 0).Select(g => new { g.UnderlyingCode, g.Delta }).Distinct(); if (req.IsFullyHedged && PS.Config.Company == CompanyEnum.宏源 && hedgeResuts != null && hedgeResuts.Count() > 0 && (xActions.Exists(x => x.Type == actionType) || xActions.Exists(x => x.Type == actionType))) { foreach (var resultSum in hedgeResuts.ToArray()) { #region 添加对冲场内交易,并计算矩阵风险参数 string underlyingCode = resultSum.UnderlyingCode; var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode); var td = new trade() { TradeType = "股票", // 股票、商品期货、商品现货 都可以作为对冲类型 Notional = resultSum.Delta * (-1), UnderlyingCode = underlyingCode, }; underlyingSumDic.TryGetValue(underlyingCode, out var scenarioUnderlying); var calcData = new CalcData { SpotPrice = scenarioUnderlying?.UnderlyingPrice ?? 0, }; var clone = calcData.Clone(); var zeroResult = GetCallResult(td, clone) ?? new TradeValueResult(false) { ErrorMessage = "未计算出结果" }; matrixCalc(td, calcData, underlying, zeroResult); #endregion } } #endregion return new ScenarioCalcResult { ConfigName = config.ConfigName, ResultItems = resultSumDic.Values, Underlyings = underlyingSumDic.Values, Trades = tradeSumDic.Values, xType = config.xType, yType = config.yType, xRates = xActions.Select(x => x.GetRateShow()).ToArray(), yRates = yActions.Select(y => y.GetRateShow()).ToArray() }; } //情景分析配置转换为可执行的action private (List xActions, List yActions) GetCalcActions(int configId, out ScenarioConfigV2 config) { config = DbContext.ScenarioConfig.Find(configId); if (config == null) { throw new ServiceException("没有找到情景分析配置"); } var xActions = CalcDataActionHelper.ConvertToActions(config.xType, config.xRates); var yActions = CalcDataActionHelper.ConvertToActions(config.yType, config.yRates); return (xActions, yActions); } //获取计算结果 private TradeValueResult GetCallResult(trade td, CalcData calcData) { if (td.TradeType == "商品期货" || td.TradeType == "股票" || td.TradeType == "商品现货" || td.TradeType == "信用债") { var pv = td.Notional * calcData.SpotPrice; return new TradeValueResult { Pv = pv, Delta = td.Notional, DeltaCash = pv }; } if (td.TradeType == "彩虹期权") { throw new NotSupportedException("情景分析 暂不支持彩虹期权"); } if (td.TradeType == "自定义交易") { //这个错误不会报出因为数据选取中已经过滤掉此种类型的交易 throw new NotSupportedException("情景分析 暂不支持自定义交易"); } using (var mp = new MarketProxy(calcData.ValueDate, calcData.RiskFreeRate)) { var calcReq = new OptionValueCalcRequest(valuedateBLL.SysRiskFreeRate()) { pricingRequest = calcData.PricingRequest, spotPrices = new[] { calcData.SpotPrice }, vols = new[] { calcData.Vol }, calcScenario = CalcScenarioEnum.ScenarioCalc, ParamOverride = p => { p.riskFreeRate = calcData.RiskFreeRate; p.dividendRate = calcData.DividenRate; } }; var result = OptionCalculatorV2.GetOptionValueResult(mp, td, calcReq, out _); return result; } } /// /// 计算用的数据 /// public class CalcData { public DateTime ValueDate; public double Vol; public double SpotPrice; public double RiskFreeRate; public double DividenRate; public PricingRequest PricingRequest; public CalcData Clone() { return (CalcData)MemberwiseClone(); } public override string ToString() { return $"{ValueDate}--vol:{Vol}--spot:{SpotPrice}--riskfreerate:{RiskFreeRate}--dividen:{DividenRate}"; } } /// /// 情景分析配置转换为可执行的action /// public class CalcDataAction { string _value; readonly Action _action; public CalcDataAction(string type, string rate, Action action) { Type = type; Rate = rate; _action = action; } public string Type { get; } public string Rate { get; } public CalcData UpdateCalcData(CalcData data) { _action?.Invoke(data); switch (Type) { case "估值日期": if (_value == null) { _value = data.ValueDate.ToString("yyyy-MM-dd"); } else if (_value != "##" && _value != data.ValueDate.ToString("yyyy-MM-dd")) { _value = "##"; } break; case "标的价格": if (_value == null) { _value = data.SpotPrice.ToString(SpotPriceFormat); } else if (_value != "##" && _value != data.SpotPrice.ToString(SpotPriceFormat)) { _value = "##"; } break; case "波动率": if (_value == null) { _value = data.Vol.ToString(RatePercentFormat); } else if (_value != "##" && _value != data.Vol.ToString(RatePercentFormat)) { _value = "##"; } break; case "分红率": if (_value == null) { _value = data.DividenRate.ToString(RatePercentFormat); } else if (_value != "##" && _value != data.DividenRate.ToString(RatePercentFormat)) { _value = "##"; } break; case "无风险利率": if (_value == null) { _value = data.RiskFreeRate.ToString(RatePercentFormat); } else if (_value != "##" && _value != data.RiskFreeRate.ToString(RatePercentFormat)) { _value = "##"; } break; } return data; } public string GetRateShow() { return _value == "##" ? Rate : $"{_value}({Rate})"; } public override string ToString() { return GetRateShow(); } } /// /// 将情景分析配置转换为可执行的action /// class CalcDataActionHelper { public static List ConvertToActions(string type, string rates) { switch (type) { case "估值日期": return GetDateActions(type, rates); case "标的价格": return GetRateActions(type, rates, x => x.SpotPrice, false); case "波动率": return GetRateActions(type, rates, x => x.Vol, true); case "分红率": return GetRateActions(type, rates, x => x.DividenRate, true); case "无风险利率": return GetRateActions(type, rates, x => x.RiskFreeRate, true); default: return null; } } private static List GetDateActions(string type, string rates) { var strArr = (rates ?? string.Empty).Split(new[] { ',', ',' }); var list = new List(strArr.Length); foreach (var s in strArr) { if (int.TryParse(s, out var days)) { var strDays = (days < 0 ? string.Empty : "+") + days.ToString(); if (!list.Any(n => n.Rate == strDays)) { list.Add(new CalcDataAction(type, strDays, x => x.ValueDate = QdpCalendarHelper.BizDayShift(x.ValueDate, days))); } } } if (list.Count < 1) { list.Add(new CalcDataAction(type, "0", null)); } return list; } private static Action GetRateAction(double rate, Expression> propExp, bool rateAdd) { var assign = rateAdd ? Expression.AddAssign(propExp.Body, Expression.Constant(rate, typeof(double))) : Expression.MultiplyAssign(propExp.Body, Expression.Constant(1 + rate, typeof(double))); return Expression.Lambda>(assign, propExp.Parameters).Compile(); } private static List GetRateActions(string type, string rates , Expression> propExp, bool rateAdd) { var strArr = (rates ?? string.Empty).Split(new[] { ',', ',' }); var list = new List(strArr.Length); foreach (var s in strArr) { if (NumberHelper.TryParse(s, out var rate, out _)) { var strRate = (rate < 0 ? string.Empty : "+") + rate.ToString("0.####%"); if (!list.Any(n => n.Rate == strRate)) { var action = GetRateAction(rate, propExp, rateAdd); list.Add(new CalcDataAction(type, strRate, action)); } } } if (list.Count < 1) { list.Add(new CalcDataAction(type, "0", null)); } return list; } } } }