940 lines
39 KiB
C#
940 lines
39 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|
||
}
|