Files
zszq-trs/YLErpDAL/Modules/ScenarioModule/ScenarioCalcService.cs
T

940 lines
39 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// 情景分析计算服务
/// </summary>
public class ScenarioCalcService : YLBaseService
{
const string SpotPriceFormat = "0.00##";
const string RatePercentFormat = "0.00##%";
public ScenarioCalcService(OptUserInfo userInfo) : base(userInfo)
{
}
//获取日终持仓
private IEnumerable<trade> GetEodPosition(TradeScenarioSearchModel searchModel)
{
var predicate = PredicateBuilder.Create<eod_trade_position>(
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<trade>();
}
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<trade> GetIntradayPosition(TradeScenarioSearchModel searchModel)
{
var blQuery = true;
//场外交易持仓
var tdPredicate = PredicateBuilder.Create<trade>(
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<trade>() : DbContext.trade.Where(tdPredicate).ToArray();
//场内交易持仓
blQuery = true;
var exPredicate = PredicateBuilder.True<TradePosition>();
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<trade> 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;
}
/// <summary>
/// 计算
/// </summary>
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);
}
/// <summary>
/// 计算
/// </summary>
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<int> 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);
}
/// <summary>
/// 计算
/// </summary>
public ScenarioCalcResult Calculate(ScenarioRequest req, ScenarioConfigV2 config, List<CalcDataAction> xActions = null, List<CalcDataAction> 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<string, ScenarioCalcResultItem>(StringComparer.OrdinalIgnoreCase);
//合并计算结果和交易
var tradeSumDic = new Dictionary<string, ScenarioCalcResultItem>(StringComparer.OrdinalIgnoreCase);
var underlyingSumDic = req.Underlyings == null
? new Dictionary<string, ScenarioUnderlyingModel>(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<string, ScenarioCalcResultItem>(StringComparer.OrdinalIgnoreCase);
void setSumDic(trade td, CalcData calcData, underlying_manager underlying, TradeValueResult zeroResult, Dictionary<string, ScenarioCalcResultItem> 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, CalcData, underlying_manager, TradeValueResult>((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<IPriceProvider>(() => 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<CalcDataAction> xActions, List<CalcDataAction> 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;
}
}
/// <summary>
/// 计算用的数据
/// </summary>
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}";
}
}
/// <summary>
/// 情景分析配置转换为可执行的action
/// </summary>
public class CalcDataAction
{
string _value;
readonly Action<CalcData> _action;
public CalcDataAction(string type, string rate, Action<CalcData> 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();
}
}
/// <summary>
/// 将情景分析配置转换为可执行的action
/// </summary>
class CalcDataActionHelper
{
public static List<CalcDataAction> 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<CalcDataAction> GetDateActions(string type, string rates)
{
var strArr = (rates ?? string.Empty).Split(new[] { ',', '' });
var list = new List<CalcDataAction>(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<CalcData> GetRateAction(double rate, Expression<Func<CalcData, double>> 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<Action<CalcData>>(assign, propExp.Parameters).Compile();
}
private static List<CalcDataAction> GetRateActions(string type, string rates
, Expression<Func<CalcData, double>> propExp, bool rateAdd)
{
var strArr = (rates ?? string.Empty).Split(new[] { ',', '' });
var list = new List<CalcDataAction>(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;
}
}
}
}