Files
zszq-trs/YLErpDAL/Modules/PricingModule/PriceCalcService.cs
T

1222 lines
53 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.Text.RegularExpressions;
using YLErp.BLL;
using YLErp.BLL.Calculation.V2;
using YLErp.BLL.Eod;
using YLErp.BLL.MarginCalculation;
using YLErp.Commons;
using YLErp.Configuration;
using YLErp.DBModels.Helpers;
using YLErp.Enums;
using YLErp.Model;
using YLErp.Modules.CalculationModule;
using YLErp.Modules.DataProviderModule;
using YLErp.Modules.EodModule;
using YLErp.Modules.PricingModule.Models;
using YLErp.Modules.TradeModule;
using YLErp.QdpModule;
using static iTextSharp.text.pdf.AcroFields;
namespace YLErp.Modules.PricingModule
{
/// <summary>
/// 计算服务
/// </summary>
public class PriceCalcService : YLBaseService
{
public PriceCalcService(OptUserInfo userInfo) : base(userInfo)
{
}
/// <summary>
/// 定价计算调用
/// </summary>
public List<TradeValueResult> GetOptionCalculatorV2(trade trade, underlying_manager underlying, OptionCalcParams para, CalcScenarioEnum calcScenario)
{
if (trade is null)
{
throw new ArgumentNullException(nameof(trade));
}
if (underlying is null)
{
throw new ArgumentNullException(nameof(underlying));
}
if (para is null)
{
throw new ArgumentNullException(nameof(para));
}
if (trade.TradeType != "合成价差期权" && trade.TradeType != "现金流交易" && trade.SpotPrice <= 0)
{
throw new Exception("请输入正确的标的价格");
}
if (!trade.TradeDate.HasValue)
{
throw new Exception("请输入正确的交易日期");
}
if (PS.Config.Is润和)
{
if (trade.TTMDays.HasValue)
{
var days = Math.Floor(trade.TTMDays.Value - 0.625) + 1;
//underlying.QuotationDate = trade.ExerciseDate.Value.AddDays(-days);
underlying.QuotationDate = QdpCalendarHelper.AddDate(trade.ExerciseDate.Value, -(int)days);
}
else
{
underlying.QuotationDate = DateTime.Today;
}
}
else if (para.ValueDate.HasValue)
{
GetAccQuotationDate(trade, para);
underlying.QuotationDate = para.ValueDate.Value;
}
else
{
GetAccQuotationDate(trade, para);
underlying.QuotationDate = trade.TradeDate.Value;
if (underlying.QuotationDate != valuedateBLL.ValueDate.Date)
{
underlying.QuotationDate = AdjustQuotationDate(underlying.QuotationDate.Value, trade.TradeType);
}
}
var QRList = new List<TradeValueResult>();
var callputPara = new CallPutResultCalcParams(para);
try
{
//根据报价方式为波动率调整时,要用波动率ask,bid值计算callput
//波动率调整不需要rule
if (para.QuotationType == "波动率调整")
{
//补丁 波动率调整不需要rule
callputPara.rules = GetRules(0);
trade.BuySell = "卖出";
var askovrs = GetCallPutResultV2(trade, underlying, callputPara.SetVol(para.AskVol), CalcScenarioEnum.Pricing);
trade.BuySell = "买入";
var bidovrs = GetCallPutResultV2(trade, underlying, callputPara.SetVol(para.BidVol), CalcScenarioEnum.Pricing);
QRList.Add(bidovrs[0]);
QRList.Add(askovrs[0]);
//call 针对波动率调整,TradePriceBid 和 TradePriceAsk应该一样(因为rule为0)
QRList[0].TradePriceBid = bidovrs[0].TradePriceBid;//askovrs[0].TradePriceBid; //系统买入
QRList[0].RoundedTradePriceBid = bidovrs[0].RoundedTradePriceBid;
QRList[0].TradePriceAsk = askovrs[0].TradePriceAsk; //系统卖出
QRList[0].RoundedTradePriceAsk = askovrs[0].RoundedTradePriceAsk;
//put
QRList[1].TradePriceBid = bidovrs[1].TradePriceBid; //系统买入
QRList[1].RoundedTradePriceBid = bidovrs[1].RoundedTradePriceBid;
QRList[1].TradePriceAsk = askovrs[1].TradePriceAsk; //系统卖出
QRList[1].RoundedTradePriceAsk = askovrs[1].RoundedTradePriceAsk;
}
else
{
//报价参数
callputPara.rules = GetRules(underlying.id);
callputPara.callVol = PS.Config.IsTradeVol ? trade.TradeOpenVolatility ?? 0 : trade.Vol ?? 0;
trade.BuySell = "买入";
var bidovrs = GetCallPutResultV2(trade, underlying, callputPara, CalcScenarioEnum.Pricing);
//不用再计算一遍,直接把买入的计算结果取反
//trade.BuySell = "卖出";
//var askovrs = GetCallPutResultV2(trade, underlying, paraBuilder.Build(vol, vol));
var bid = bidovrs[0];
if (bid.Vega != 0 && bid.VegaCash == 0)
{
bid.VegaCash = bid.Vega * (trade.SpotPrice ?? 0);
}
var ask = new TradeValueResult
{
Pv = -bid.Pv,
Delta = -bid.Delta,
Gamma = -bid.Gamma,
GammaCash = -bid.GammaCash,
DeltaCash = -bid.DeltaCash,
VegaCash = -bid.VegaCash,
Vega = -bid.Vega,
CalendarDayTheta = -bid.CalendarDayTheta,
TradingDayTheta = -bid.TradingDayTheta,
Rho = -bid.Rho,
UnderlyingCode = bid.UnderlyingCode,
Strike = bid.Strike,
Vol = bid.Vol,
RoundedPv = -bid.RoundedPv,
KnockOutPayoff = -bid.KnockOutPayoff,
IsKnockOut = bid.IsKnockOut,
};
QRList.AddRange(new[] { bid, ask });
}
return QRList;
}
catch (Exception ex)
{
LogFactory.GetLogger("定价计算").Error(ex);
var message = Regex.Replace(ex.Message, "exercise date (.*?) is not in KOObsDates", "到期日 $1 不在敲出观察日列表中");
throw new Exception("计算出错," + message);
}
}
/// <summary>
/// 获取累计包含首日定价日期
/// </summary>
/// <param name="trade"></param>
/// <param name="para"></param>
private static void GetAccQuotationDate(trade trade, OptionCalcParams para)
{
if (trade.TradeType == "累计期权" && trade.trade_accumulator_option != null && !string.IsNullOrWhiteSpace(trade.trade_accumulator_option.KOObservationDates))
{
var ko = QdpHelper.ParseAutocallCustomizedInfo(trade.trade_accumulator_option.KOObservationDates);
if (ko.Item1.Length > 0 && ko.Item1.Select(l => l.DateTime).ToArray().Contains(trade.TradeDate.Value))
{
para.ValueDate = QdpCalendarHelper.GetNonHolidayDefore((para.ValueDate ?? trade.TradeDate).Value.AddDays(-1));
}
}
}
private static List<TradeValueResult> GetCallPutResultV2(trade trade, underlying_manager underlying, CallPutResultCalcParams para, CalcScenarioEnum calcScenario)
{
if (trade is null)
{
throw new ArgumentNullException(nameof(trade));
}
if (underlying is null)
{
throw new ArgumentNullException(nameof(underlying));
}
if (para is null)
{
throw new ArgumentNullException(nameof(para));
}
//交易日等于系统日期,是当天报价,要使用精确时间模式
//trade.TradeDate必然有值,在外部调用的方法中判断
var isPreciseTimeMode = underlying.QuotationDate.Value.Date == valuedateBLL.ValueDate.Date;
if (trade.TradeType == "气囊结构" || trade.TradeType == "收益增强结构" || trade.TradeType == "区间累积期权" || (trade.TradeType == "二元期权" && trade.ExerciseMode == "American"))
{
trade.OptionType = "看涨";
}
TradeValueResult pricingResult;
var valueDate = underlying.QuotationDate.Value;
var spotPrice = para.UnderlyingPrice ?? trade.SpotPrice ?? 0;
if (trade.TradeType == "自定义交易")
{
var result = TradeRiskCalcUtil.GetManualOptionValue(valueDate, trade, spotPrice, para.callVol, true, calcScenario);
if (result.manual == null)
{
throw new Exception(PS.Config.ErpElement.ExternalAPIForCustomCalcEnable ? "失败:接口计算失败;" : "失败:无法调用计算接口,请开启相应配置");
}
return new List<TradeValueResult> { result.optionValue, result.optionValue };
}
string fixing = null;
if (trade.TradeType == "亚式期权" || trade.StructureType == "亚式熊市价差")
{
if (!string.IsNullOrEmpty(para.Fixings))
{
fixing = para.Fixings;
}
else
{
var avgStartDate = trade.trade_asian_option.AveragingPeriodStartDate ?? trade.TradeDate.Value;
if (valueDate > avgStartDate)
{
fixing = FixingService.GetFixingString(valueDate, trade, avgStartDate, trade.trade_asian_option.ObservationDates);
fixing = FixingService.AddOrReplaceLastDateSpotPrice(fixing, valueDate, spotPrice);
if (PS.Config.Is润和 && DateTime.Now.Hour < 15)
{
var index = fixing.IndexOf(valueDate.ToString("yyyy-MM-dd"));
if (index >= 0)
{
fixing = fixing.Remove(index).TrimEnd(';');
}
}
}
}
}
else if (trade.TradeType == "区间累积期权")
{
fixing = !string.IsNullOrEmpty(para.Fixings)
? para.Fixings
: FixingService.GetFixingString(valueDate: valueDate.AddDays(-1), otcTrade: trade, startDate: trade.StartDate ?? trade.TradeDate.Value, observationDates: trade.trade_rangeaccrual.ObservationDates);
}
if ("V2".Equals(para.CalcVersion, StringComparison.OrdinalIgnoreCase) || trade.TradeType == "累计期权" || "结构化产品".Equals(trade.TradeType) || trade.IsSnowballSpecialist())
{
var request = new OptionValueCalcRequest(valuedateBLL.SysRiskFreeRate())
{
vols = new[] { para.callVol },
spotPrices = new[] { spotPrice },
engineName = para.EngineName,
preciseTimeMode = isPreciseTimeMode,
pricingRequest = para.IsCalcGreeks ? QdpPricingRequest.BASIC_GREEKS : QdpPricingRequest.PRICE_GREEKS,
quadratureFastMode = true,
calcScenario = CalcScenarioEnum.Pricing,
fixings = fixing,
timeToMaturityDays = trade.TTMDays
};
pricingResult = OptionCalculatorV2.GetOptionValueResult(valueDate, trade, request, out _);
}
else
{
pricingResult = ValueCalculator.GetOptionValueResultV2(
para.UserId,
underlying,
trade,
new double[] { para.callVol },
new double[] { spotPrice },
fixing: fixing,
preciseTimeMode: isPreciseTimeMode,
engineName: para.EngineName,
request: para.IsCalcGreeks ? QdpPricingRequest.BASIC_GREEKS : QdpPricingRequest.PRICE_GREEKS,
calcScenario: CalcScenarioEnum.Pricing,
quadratureFastMode: true);
}
if (pricingResult == null)
{
throw new Exception("失败:此交易无法计算 ");
}
//报价结果
var qr = new QuotationResult();
OptionQuote(ref pricingResult, para.rules.Where(r => underlying_parameter.callQuoteTypes.Contains(r.Type)).ToList(), trade.Notional, trade.TradeType, ref qr);
var payoffService = new TradeKnockOutPayoffCalcService(valueDate);
var payoffResult = payoffService.GetKnockOutPayoff(trade, spotPrice);
if (payoffResult != null)
{
pricingResult.IsKnockOut = payoffResult.IsKnockOut;
pricingResult.KnockOutPayoff = payoffResult.Payoff;
}
return new List<TradeValueResult> { pricingResult, pricingResult };
}
/// <summary>
/// 计算期权权利金
/// </summary>
public IEnumerable<CalcOptionPriceResult> CalcOptionPrice(IEnumerable<OtcOptionTradeFull> trades, bool calcMargin, CalcScenarioEnum calcScenario
, Func<OtcTrade, bool> calcGreeks = null, string calcVersion = null)
{
var results = new List<CalcOptionPriceResult>();
var tdGroups = trades.GroupBy(n => n.CalcId.Split('-')[0]).ToArray();
var priceProvider = new ManualPriceProvider();
var req = new Lazy<RunMarginCalculationReq>(() => new RunMarginCalculationReq(UserInfo)
{
forOtherSide = false,
hasOptionInfo = false,
CalcMarginType = CalcMarginTypeEnum.InitialMargin,
settleDate = DateTime.Now,
PriceProvider = priceProvider
});
foreach (var tdGroup in tdGroups)
{
var tdList = tdGroup.Select(td =>
{
if (!td.TradeDate.HasValue)
{
throw new ServiceException("交易日期 必须填写");
}
if (!td.ExerciseDate.HasValue)
{
throw new ServiceException("到期日期 必须填写");
}
var tdConv = TradeConverter.ConvertOptionTrade(td);
tdConv.CalcId = td.CalcId;
return new
{
tdConv,
td.IsTTMSystem,
td.ValueDate,
td.UnderlyingPrice,
EngineName = ConsTrade.GetEngineName(td.EngineName),
};
}).ToArray();
//结构化交易(用于预付金计算)
if (tdList.Length > 1)
{
var index = -2;
foreach (var item in tdList)
{
item.tdConv.id = index--;
item.tdConv.ParentTradeId = -1;
}
}
var tradeValueDict = new Dictionary<int, (TradeValueResult, underlying_manager)>();
//计算pv
foreach (var item in tdList)
{
var td = item.tdConv;
var buySell = td.BuySell;
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
if (td.TradeType == "现金流交易")
{
underlying = new underlying_manager();
}
if (underlying == null)
{
throw new ServiceException($"没有找到标的信息,标的代码:{td.UnderlyingCode}");
}
if (PS.Config.Is润和 && item.IsTTMSystem)
{
td.TTMDays = TradeCalcHelper.CalculateTTMDays(DateTime.Today, td.ExerciseDate.Value, underlying.UnderlyingTypeId, PS.Config.ErpElement.PrecisionOfMinuteInQuote);
}
else if (!td.TTMDays.HasValue && td.TradeType != "现金流交易")
{
td.TTMDays = TradeCalcHelper.CalculateTTMDays(td.TradeDate.Value, td.ExerciseDate.Value, underlying.UnderlyingTypeId, PS.Config.ErpElement.PrecisionOfMinuteInQuote);
}
//这个地方会把交易方向改变
var QRList = GetOptionCalculatorV2(td, underlying, new OptionCalcParams
{
UserId = UserId.ToString(),
IsCalcGreeks = calcGreeks == null || calcGreeks(td),
EngineName = item.EngineName,
CalcVersion = calcVersion,
UnderlyingPrice = item.UnderlyingPrice,
ValueDate = item.ValueDate,
}, calcScenario);
var calcResult = buySell == "卖出" ? QRList[1] : QRList[0];
calcResult.BuySell = buySell;
//期权本身的价值,不包含买卖方向
td.TradePrice = QRList[0].Pv;
td.AccurateTradePrice = QRList[0].Pv;
if (PS.Config.Is润和)
{
var tdmid = td.Clone();
tdmid.Vol = tdmid.MidVol;
tdmid.TradeOpenVolatility = tdmid.MidVol;
//这个地方会把交易方向改变
var QRList2 = GetOptionCalculatorV2(tdmid, underlying, new OptionCalcParams
{
UserId = UserId.ToString(),
IsCalcGreeks = calcGreeks == null || calcGreeks(tdmid),
EngineName = item.EngineName,
CalcVersion = calcVersion,
UnderlyingPrice = item.UnderlyingPrice,
ValueDate = item.ValueDate
}, calcScenario);
var calcResult2 = buySell == "卖出" ? QRList2[1] : QRList2[0];
td.Day1Pnl = calcResult2.Pv - calcResult.Pv;
calcResult2.Pv = calcResult.Pv;
tradeValueDict[td.id] = (calcResult2, underlying);
}
else
{
tradeValueDict[td.id] = (calcResult, underlying);
}
//if (PS.Config.Company == Configuration.CompanyEnum.天示)
//{
// calcResult.Pv = calcResult.Pv + td.PrincipalSum * (buySell == "卖出" ? -1 : 1);
//}
//恢复原有的交易方向,确保预付金计算正确
td.BuySell = buySell;
}
List<trade_span> tradeSpans = null;
//计算预付金
if (calcMargin)
{
var tdCloneList = tdList.Where(n => n.tdConv.TradeType != "现金流交易").Select(n =>
{
var tdClone = n.tdConv.Clone();
tdClone.OptId = UserId;
tdClone.VolType = "报价Bid";
tdClone.TradeCloseVolatility = null;
priceProvider.SetPrice(tdClone.UnderlyingCode, tdClone.SpotPrice ?? 0);
return tdClone;
}).ToList();
//结构化交易预付金计算可能出现的问题:结算日期或者期初标的价格不一致
//要正确处理上述问题,需要对现有代码做重新设计和开发,故暂时忽略上述情况
if (tdCloneList.Count() > 0)
{
req.Value.settleDate = tdCloneList[0].TradeDate ?? valuedateBLL.ValueDate;
req.Value.CalcMarginType = CalcMarginTypeEnum.InitialMargin;
tradeSpans = MarginDefault.RunMarginCalculation(req.Value.Clone(tdCloneList)).ToList();
if (PS.Config.Is国投 || PS.Config.Is润和 || PS.Config.Is华安 || PS.Config.Is招证)
{
GroupSpansCalc(tradeSpans);
}
if (PS.Config.Company == CompanyEnum.东吴)
{
foreach (var ts in tradeSpans)
{
if (ts.WorstCastClientPayable < 0)
{
ts.WorstCastClientPayable = 0;
}
}
}
}
}
var greeksHandleService = new GLMSGreeksHandleService();
foreach (var item in tdList)
{
var td = item.tdConv;
var initialMargin = 0d;
if (tradeSpans != null)
{
initialMargin = tradeSpans.FirstOrDefault(n => n.TradeId == td.id)?.WorstCastClientPayable ?? 0;
}
var (calcResult, underlying) = tradeValueDict[td.id];
if (calcResult != null)
{
if (calcResult.IsKnockOut)
{
calcResult.DeltaContainsKnockOut = 0;
calcResult.GammaContainsKnockOut = 0;
calcResult.PvContainsKnockOut = calcResult.KnockOutPayoff;
}
else
{
calcResult.PvContainsKnockOut = calcResult.Pv;
calcResult.DeltaContainsKnockOut = calcResult.Delta;
calcResult.GammaContainsKnockOut = calcResult.Gamma;
}
}
greeksHandleService.InitData(item.ValueDate ?? td.TradeDate ?? DateTime.Today, new List<string> { td.UnderlyingCode });
greeksHandleService.Handle(td, calcResult, underlying);
results.Add(new CalcOptionPriceResult
{
BuySell = td.BuySell,
CalcId = td.CalcId,
calcResult = calcResult,
initialMargin = initialMargin,
countRatio = underlying.CountRatio,
contractSize = underlying.ContractSize,
Day1Pnl = td.Day1Pnl ?? 0,
TradePrice = td.TradePrice,
AccurateTradePrice = td.AccurateTradePrice,
TTMDays = td.TTMDays ?? 0
});
}
if (PS.Config.Is华安)
{
if (tradeSpans != null)
{
if (tradeSpans.Sum(x => x.WorstCastClientPayable) < 0 || results.Where(r => tdList.Select(td => td.tdConv.CalcId).Contains(r.CalcId)).Sum(t => t.TradePrice * (t.BuySell == "卖出" ? 1 : -1)) > 0)
{
tradeSpans.ForEach(x => x.WorstCastClientPayable = 0);
results.ForEach(x => x.initialMargin = 0);
}
}
}
}
if (PS.Config.Is润和)
{
var TotalMarginDic = results.GroupBy(l => l.CalcId.Split('-')[0]).ToDictionary(l => l.Key, l => new { totalMargin = l.Sum(i => i.initialMargin), totalTradePrice = l.Sum(i => i.AccurateTradePrice * (i.BuySell == "卖出" ? 1 : -1)) });
foreach (var item in TotalMarginDic)
{
if (item.Value.totalMargin > 0 && item.Value.totalTradePrice + item.Value.totalMargin > 0)
{
var modifyHasNeedOffsetModel = results.Where(l => l.CalcId.Split('-')[0] == item.Key);
modifyHasNeedOffsetModel.ToList().ForEach(l => l.hasInitialMargin = true);
}
else
{
var modifyHasNeedOffsetModel = results.Where(l => l.CalcId.Split('-')[0] == item.Key);
modifyHasNeedOffsetModel.ToList().ForEach(l => l.hasInitialMargin = false);
}
}
}
return results;
}
/// <summary>
/// 分组取最大
/// </summary>
/// <param name="tradeSpans"></param>
public static void GroupSpansCalc(List<trade_span> tradeSpans)
{
double? spv1 = tradeSpans.Sum(O => O.Spv1 ?? 0);
double? spv2 = tradeSpans.Sum(O => O.Spv2 ?? 0);
double? spv3 = tradeSpans.Sum(O => O.Spv3 ?? 0);
double? spv4 = tradeSpans.Sum(O => O.Spv4 ?? 0);
double? spv5 = tradeSpans.Sum(O => O.Spv5 ?? 0);
double? spv6 = tradeSpans.Sum(O => O.Spv6 ?? 0);
double? spv7 = tradeSpans.Sum(O => O.Spv7 ?? 0);
double? spv8 = tradeSpans.Sum(O => O.Spv8 ?? 0);
double? deltaMargin = tradeSpans.Sum(O => O.DeltaMargin ?? 0);
//默认使用了某个spv不会赋 null
if (tradeSpans.Any(x => x.Spv1 == null)) spv1 = null;
if (tradeSpans.Any(x => x.Spv2 == null)) spv2 = null;
if (tradeSpans.Any(x => x.Spv3 == null)) spv3 = null;
if (tradeSpans.Any(x => x.Spv4 == null)) spv4 = null;
if (tradeSpans.Any(x => x.Spv5 == null)) spv5 = null;
if (tradeSpans.Any(x => x.Spv6 == null)) spv6 = null;
if (tradeSpans.Any(x => x.Spv7 == null)) spv7 = null;
if (tradeSpans.Any(x => x.Spv8 == null)) spv8 = null;
if (tradeSpans.Any(x => x.DeltaMargin == null)) deltaMargin = null;
var spvArr = new[] { spv1, spv2, spv3, spv4, spv5, spv6, spv7, spv8, deltaMargin };
var maxSpv = spvArr.Max();
var index = Array.IndexOf(spvArr, maxSpv);
tradeSpans.ForEach(x =>
{
var clientRatio = 1.0;
if (PS.Config.Company == CompanyEnum.招证 && x.ClientId != 0)
{
var client = DataCacheProvider.GetClientDataSource().GetData(x?.ClientId.Value ?? 0);
var clientLevel = DataCacheProvider.GetClientLevelDataSource().GetData(client.LevelId ?? 0);
clientRatio = clientLevel?.Ratio1 ?? 1.0;
}
x.WorstCastClientPayable = new[] { x.Spv1, x.Spv2, x.Spv3, x.Spv4, x.Spv5, x.Spv6, x.Spv7, x.Spv8, x.DeltaMargin }[index] * clientRatio;
});
}
/// <summary>
/// 计算期权权利金
/// </summary>
public CalcOptionPriceResult CalcOptionPrice(OtcOptionTradeFull trade, bool calcMargin, CalcScenarioEnum calcScenario
, bool calcGreeks = true, string calcVersion = "V1", string fixings = null)
{
if (!trade.TradeDate.HasValue)
{
throw new ServiceException("交易日期 必须填写");
}
if (!trade.ExerciseDate.HasValue)
{
throw new ServiceException("到期日期 必须填写");
}
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode);
if (trade.TradeType == "现金流交易")
{
underlying = new underlying_manager();
}
if (underlying == null)
{
throw new ServiceException($"没有找到标的信息,标的代码:{trade.UnderlyingCode}");
}
var tdConv = TradeConverter.ConvertOptionTrade(trade);
tdConv.EnableSetFieldsByTradeType = false;
//由于新增交易时,页面交易数量还未根据沽购数量进行观察日倍数乘积处理,所以,如果后台处理了,得到的PV页面处理成权利金时会除以数量,就会有问题,需要和前台同步
if (tdConv.TradeType == "累计期权" && tdConv.id > 0)
{
var customObservDates = QdpHelper.ParseObservationDate(tdConv.trade_accumulator_option.KOObservationDates);
customObservDates ??= CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(tdConv.TradeDate.Value, tdConv.ExerciseDate.Value).ToArray();
tdConv.TradeAmount = tdConv.trade_accumulator_option.AccumuTradeAmount * customObservDates.Length;
tdConv.Notional = tdConv.TradeAmount * underlying.CountRatio;
tdConv.StockEqvNotionalReal = tdConv.Notional * (tdConv.SpotPrice ?? 0);
tdConv.StockEqvNotional = TradeHelper.GetStockEqvNotional(tdConv.StockEqvNotionalReal, tdConv.ParticipationRate, tdConv.AnnualizeFactor);
}
var buySell = tdConv.BuySell;
if (PS.Config.Is润和)
{
tdConv.TTMDays = TradeCalcHelper.CalculateTTMDays(DateTime.Today, tdConv.ExerciseDate.Value, underlying.UnderlyingTypeId, PS.Config.ErpElement.PrecisionOfMinuteInQuote);
}
else if (!tdConv.TTMDays.HasValue)
{
tdConv.TTMDays = TradeCalcHelper.CalculateTTMDays(trade.TradeDate.Value, trade.ExerciseDate.Value, underlying.UnderlyingTypeId, PS.Config.ErpElement.PrecisionOfMinuteInQuote);
}
var initialMargin = 0d;
if (calcMargin)
{
var tdClone = tdConv.Clone();
tdClone.OptId = UserId;
tdClone.BuySell = "买入";
tdClone.VolType = "报价Bid";
tdClone.TradeCloseVolatility = null;
initialMargin = MarginDefault.GetInitialMargin(tdClone, tdConv.id, true);
}
var QRList = GetOptionCalculatorV2(tdConv, underlying, new OptionCalcParams
{
UserId = UserId.ToString(),
IsCalcGreeks = calcGreeks,
EngineName = ConsTrade.GetEngineName(trade.EngineName),
UnderlyingPrice = trade.UnderlyingPrice,
ValueDate = trade.ValueDate,
CalcVersion = calcVersion ?? "V1",
Fixings = fixings
}, calcScenario);
var calcResult = buySell == "卖出" ? QRList[1] : QRList[0];
calcResult.BuySell = buySell;
if (PS.Config.Is润和)
{
var tdmid = tdConv.Clone();
tdmid.Vol = tdmid.MidVol;
tdmid.TradeOpenVolatility = tdmid.MidVol;
//这个地方会把交易方向改变
var QRList2 = GetOptionCalculatorV2(tdmid, underlying, new OptionCalcParams
{
UserId = UserId.ToString(),
IsCalcGreeks = calcGreeks,
EngineName = ConsTrade.GetEngineName(trade.EngineName),
UnderlyingPrice = trade.UnderlyingPrice,
ValueDate = trade.ValueDate,
CalcVersion = calcVersion ?? "V1"
}, calcScenario);
var calcResult2 = buySell == "卖出" ? QRList2[1] : QRList2[0];
trade.Day1Pnl = calcResult2.Pv - calcResult.Pv;
}
return new CalcOptionPriceResult
{
CalcId = trade.CalcId,
calcResult = calcResult,
initialMargin = initialMargin,
countRatio = underlying.CountRatio,
contractSize = underlying.ContractSize,
Day1Pnl = trade.Day1Pnl ?? 0,
};
}
/// <summary>
/// 根据标的价格和波动率对交易进行试算
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public CalcTradesResult CalcTrades(CalcTradesRequest request)
{
var result = new CalcTradesResult();
var eodTrades = DbContext.eod_trade.Where(x => request.TradeIds.Contains(x.TradeId) && x.ValueDate == request.ValueDate).ToList();
var trades = DbContext.trade.Where(x => request.TradeIds.Contains(x.id)).ToList();
request.TradeIds.ForEach(x =>
{
trade trade = new trade();
var eodTrade = eodTrades.FirstOrDefault(y => y.TradeId == x);
if(eodTrade != null)
{
trade = eodTrade.trade;
}
else
{
trade = trades.FirstOrDefault(y => y.id == x);
new TradeExtendService(OptUser, DbContext).SetTradeExtend(new[] { trade }, tracking: true);
}
if(trade.TradeType != "自定义交易" && trade.TradeType != "现金流交易")
{
double? price = 0;
if (request.TradePricesDic.ContainsKey(x))
{
price = request.TradePricesDic.FirstOrDefault(y => y.Key == x).Value;
}
else if (request.PricesDic.ContainsKey(trade.UnderlyingCode))
{
price = request.PricesDic.FirstOrDefault(y => y.Key == trade.UnderlyingCode).Value;
}
TradeValueResult optionValue = new TradeValueResult();
if (trade.TradeType == "远期")
{
if (price != null)
{
optionValue = ForwardradeCalcService.CalcValue(trade, price.Value);
}
}
else
{
double? vol = 0;
if (request.VolsDic.ContainsKey(x))
{
vol = request.VolsDic.FirstOrDefault(z => z.Key == x).Value;
}
if (price != null && vol != null)
{
double ttm;
var udm = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode);
if (PS.Config.Is厦门象屿 && trade.SettlementType == SettlementTypeEnum.ReferencePrice)
{
ttm = TradeCalcHelper.CalculateTTMDaysForXiangYu(request.ValueDate, trade.ExerciseDate.Value, udm.UnderlyingTypeId, trade.ExerciseDate.Value == valuedateBLL.ValueDate);
}
else
{
ttm = TradeCalcHelper.CalculateTTMDays(request.ValueDate, trade.ExerciseDate.Value, udm.UnderlyingTypeId, trade.ExerciseDate.Value == valuedateBLL.ValueDate);
}
var calcRequest = new OptionValueCalcRequest(valuedateBLL.SysRiskFreeRate())
{
vols = new[] { vol.Value },
spotPrices = new[] { price.Value },
engineName = null,
preciseTimeMode = request.ValueDate == valuedateBLL.ValueDate.Date,
pricingRequest = QdpPricingRequest.BASIC_PRICING,
timeToMaturityDays = ttm,
calcScenario = CalcScenarioEnum.Pricing
};
optionValue = OptionCalculatorV2.GetOptionValueResult(request.ValueDate, trade, calcRequest, out _);
}
}
var pnl = EodOperationBase.GetPositionPnl(optionValue.Pv, trade.TradePrice ?? 0.0, trade.Notional, trade.OriginalNotional ?? 0, trade.BuySell);
var singlePV = TradeHelper.GetTradeSinglePriceByTradePrice(optionValue.Pv, trade.Notional, PS.Config.ErpElement.IsPVIncludePrincipal ? trade.PrincipalSum() : 0, trade.BuySell, trade.TradeType, false);
result.TradeOptionValueDic.Add(x, new CalcTradeResult() { Pv = optionValue.Pv, SinglePv = singlePV, Pnl = pnl, Delta = optionValue.Delta });
}
});
return result;
}
public CalcTradesResult CalcSwapTrades(CalcTradesRequest request)
{
var result = new CalcTradesResult();
var eodTrades = DbContext.eod_swap.Where(x => request.TradeIds.Contains(x.SwapTradeId) && x.ValueDate == request.ValueDate).ToList();
var trades = DbContext.trade.Where(x => request.TradeIds.Contains(x.id)).ToList();
request.TradeIds.ForEach(x =>
{
trade trade = new trade();
trade = trades.FirstOrDefault(y => y.id == x);
double? price = request.PricesDic.FirstOrDefault(y => y.Key == trade.UnderlyingCode).Value;
if (price != null)
{
var optionValue = PayoffSwapCalcService.CalcValueSingle(trade, request.ValueDate, null, false, price.Value);
var pnl = EodOperationBase.GetPositionPnl(optionValue.Pv, trade.TradePrice ?? 0.0, trade.Notional, trade.OriginalNotional ?? 0, trade.BuySell);
var singlePV = TradeHelper.GetTradeSinglePriceByTradePrice(optionValue.Pv, trade.Notional, PS.Config.ErpElement.IsPVIncludePrincipal ? trade.PrincipalSum() : 0, trade.BuySell, trade.TradeType, false);
result.TradeOptionValueDic.Add(x, new CalcTradeResult() { Pv = optionValue.Pv, SinglePv = singlePV, Pnl = pnl, Delta = optionValue.Delta });
}
});
return result;
}
//报价参数
private underlying_parameter[] GetRules(int underlyingId)
{
underlying_parameter[] rules = null;
if (underlyingId > 0)
{
rules = DbContext.underlying_parameter.Where(u => u.UnderlyingId == underlyingId).ToArray();
}
//补丁 波动率调整不需要rule
if (rules == null || rules.Length == 0)
{
var arr = new string[] {
underlying_parameter.CallAsk,
underlying_parameter.CallBid,
underlying_parameter.PutAsk,
underlying_parameter.PutBid
};
rules = arr.Select(n => new underlying_parameter()
{
Type = n,
Delta = 0.0,
Gamma = 0.0,
Vega = 0.0,
Theta = 0.0,
Rho = 0.0,
Other = 0.0
}).ToArray();
}
return rules;
}
/// <summary>
/// 计算保本雪球的年化期权费率
/// </summary>
public double CalcSnowballAnnualPremium(OtcOptionTradeFull trade)
{
if (!trade.TradeDate.HasValue)
{
throw new ServiceException("交易日期 必须填写");
}
if (!trade.ExerciseDate.HasValue)
{
throw new ServiceException("到期日期 必须填写");
}
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode);
if (underlying == null)
{
throw new ServiceException($"没有找到标的信息,标的代码:{trade.UnderlyingCode}");
}
var tdConv = TradeConverter.ConvertOptionTrade(trade);
tdConv.EnableSetFieldsByTradeType = false;
if (!tdConv.TTMDays.HasValue)
{
tdConv.TTMDays = TradeCalcHelper.CalculateTTMDays(trade.TradeDate.Value, trade.ExerciseDate.Value, underlying.UnderlyingTypeId, PS.Config.ErpElement.PrecisionOfMinuteInQuote);
}
if (tdConv.SpotPrice <= 0)
{
throw new Exception("请输入正确的标的价格");
}
underlying.QuotationDate = trade.TradeDate;
if (underlying.QuotationDate != valuedateBLL.ValueDate.Date)
{
underlying.QuotationDate = AdjustQuotationDate(underlying.QuotationDate.Value, tdConv.TradeType);
}
//交易日等于系统日期,是当天报价,要使用精确时间模式
var isPreciseTimeMode = tdConv.TradeDate.Value.Date == valuedateBLL.ValueDate.Date;
var callVol = PS.Config.IsTradeVol ? trade.TradeOpenVolatility ?? 0 : trade.Vol ?? 0;
return ValueCalculator.CalculateSnowballAnnualPremium(
UserId.ToString(),
underlying,
tdConv,
new double[] { callVol },
new double[] { tdConv.SpotPrice ?? 0 },
preciseTimeMode: isPreciseTimeMode,
engineName: trade.EngineName,
quadratureFastMode: true);
}
/// <summary>
/// 反算雪球票息
/// </summary>
public double CalcSnowballKORebate(OtcOptionTradeFull trade)
{
if (!trade.TradeDate.HasValue)
{
throw new ServiceException("交易日期 必须填写");
}
if (!trade.ExerciseDate.HasValue)
{
throw new ServiceException("到期日期 必须填写");
}
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode);
if (underlying == null)
{
throw new ServiceException($"没有找到标的信息,标的代码:{trade.UnderlyingCode}");
}
var tdConv = TradeConverter.ConvertOptionTrade(trade);
tdConv.EnableSetFieldsByTradeType = false;
if (!tdConv.TTMDays.HasValue)
{
tdConv.TTMDays = TradeCalcHelper.CalculateTTMDays(trade.TradeDate.Value, trade.ExerciseDate.Value, underlying.UnderlyingTypeId, PS.Config.ErpElement.PrecisionOfMinuteInQuote);
}
if (tdConv.SpotPrice <= 0)
{
throw new Exception("请输入正确的标的价格");
}
var valueDate = trade.TradeDate.Value;
if (valueDate != valuedateBLL.ValueDate.Date)
{
valueDate = AdjustQuotationDate(valueDate, tdConv.TradeType);
}
//交易日等于系统日期,是当天报价,要使用精确时间模式
var isPreciseTimeMode = tdConv.TradeDate.Value.Date == valuedateBLL.ValueDate.Date;
var callVol = PS.Config.IsTradeVol ? trade.TradeOpenVolatility ?? 0 : trade.Vol ?? 0;
var koRebate = ValueCalculator.CalculateSnowballKORebateV2(
trade: tdConv,
snowball: tdConv.trade_snowball,
valueDate: valueDate,
vols: new double[] { callVol },
spotPrices: new double[] { tdConv.SpotPrice ?? 0 },
preciseTimeMode: isPreciseTimeMode,
timeToMaturityDays: tdConv.TTMDays.Value,
initialMarginRate: trade.InitialAdvance.HasValue ? trade.InitialAdvance.Value : 0.0); // 预付预付金比例
return koRebate;
}
/// <summary>
/// 反算专业版雪球票息、波动率等
/// </summary>
public double CalcSnowballSpecialistTargetValue(OtcOptionTradeFull trade, int calcTarget)
{
if (!trade.TradeDate.HasValue)
{
throw new ServiceException("交易日期 必须填写");
}
if (!trade.ExerciseDate.HasValue)
{
throw new ServiceException("到期日期 必须填写");
}
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode);
if (underlying == null)
{
throw new ServiceException($"没有找到标的信息,标的代码:{trade.UnderlyingCode}");
}
var snowballSpecialistOptionCalculator = new SnowballSpecialistOptionCalculator();
var tdConv = TradeConverter.ConvertOptionTrade(trade);
var breakevenSnowballTrade = snowballSpecialistOptionCalculator.GetBreakevenTrade(tdConv);
var QRList = GetOptionCalculatorV2(breakevenSnowballTrade, underlying, new OptionCalcParams
{
UserId = UserId.ToString(),
IsCalcGreeks = false,
EngineName = ConsTrade.GetEngineName(trade.EngineName),
UnderlyingPrice = trade.UnderlyingPrice,
ValueDate = trade.ValueDate,
CalcVersion = "V1",
Fixings = null
}, CalcScenarioEnum.Pricing);
var breakevenSnowballResult = QRList[0];
tdConv.TradePrice -= breakevenSnowballResult.Pv;
tdConv.trade_snowball.PrepaymentRatio = 0;
if (tdConv.SpotPrice <= 0)
{
throw new Exception("请输入正确的标的价格");
}
var valueDate = trade.TradeDate.Value;
if (valueDate != valuedateBLL.ValueDate.Date)
{
valueDate = AdjustQuotationDate(valueDate, tdConv.TradeType);
}
//交易日等于系统日期,是当天报价,要使用精确时间模式
var callVol = PS.Config.IsTradeVol ? trade.TradeOpenVolatility ?? 0 : trade.Vol ?? 0;
var targetValue = snowballSpecialistOptionCalculator.CalcTargets(valueDate, trade.UnderlyingPrice ?? 0, callVol, calcTarget, tdConv);
//var koRebate = ValueCalculator.CalculateSnowballKORebateV2(
// trade: tdConv,
// snowball: tdConv.trade_snowball,
// valueDate: valueDate,
// vols: new double[] { callVol },
// spotPrices: new double[] { tdConv.SpotPrice ?? 0 },
// preciseTimeMode: isPreciseTimeMode,
// timeToMaturityDays: tdConv.TTMDays.Value,
// initialMarginRate: trade.InitialAdvance.HasValue ? trade.InitialAdvance.Value : 0.0); // 预付预付金比例
return targetValue;
}
/// <summary>
/// 反算凤凰票息
/// </summary>
/// <param name="trade"></param>
/// <returns></returns>
public double CalcPhoenixCouponRate(OtcOptionTradeFull trade)
{
if (!trade.TradeDate.HasValue)
{
throw new ServiceException("交易日期 必须填写");
}
if (!trade.ExerciseDate.HasValue)
{
throw new ServiceException("到期日期 必须填写");
}
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode);
if (underlying == null)
{
throw new ServiceException($"没有找到标的信息,标的代码:{trade.UnderlyingCode}");
}
var tdConv = TradeConverter.ConvertOptionTrade(trade);
tdConv.EnableSetFieldsByTradeType = false;
if (!tdConv.TTMDays.HasValue)
{
tdConv.TTMDays = TradeCalcHelper.CalculateTTMDays(trade.TradeDate.Value, trade.ExerciseDate.Value, underlying.UnderlyingTypeId, PS.Config.ErpElement.PrecisionOfMinuteInQuote);
}
if (tdConv.SpotPrice <= 0)
{
throw new Exception("请输入正确的标的价格");
}
underlying.QuotationDate = trade.TradeDate;
if (underlying.QuotationDate != valuedateBLL.ValueDate.Date)
{
underlying.QuotationDate = AdjustQuotationDate(underlying.QuotationDate.Value, tdConv.TradeType);
}
//交易日等于系统日期,是当天报价,要使用精确时间模式
var isPreciseTimeMode = tdConv.TradeDate.Value.Date == valuedateBLL.ValueDate.Date;
var callVol = PS.Config.IsTradeVol ? trade.TradeOpenVolatility ?? 0 : trade.Vol ?? 0;
return ValueCalculator.CalculatePhoenixCouponRate(
underlying,
tdConv,
new double[] { callVol },
new double[] { tdConv.SpotPrice ?? 0 },
preciseTimeMode: isPreciseTimeMode,
engineName: trade.EngineName);
}
/// <summary>
/// 在期权定价时调整定价日
/// </summary>
private DateTime AdjustQuotationDate(DateTime quotationDate, string tradeType)
{
// 对凤凰雪球不做报价日调整
if (tradeType == "雪球期权" || tradeType == "凤凰期权")
{
return quotationDate;
}
//解决定价、新增、修改定价不一致的问题
if (tradeType == "区间累积期权" || tradeType == "累计期权")
{
return quotationDate;
}
//为了暂时修复一个QDP计算方式与客户需求不匹配的情况
//在非精确模式下,QDP不包括交易日当天的时间价值,但根据报价需求,需要将交易日当天的时间价值计算在内,
//所以要将交易日向前移动一天。未来QDP支持传递TTM来计算时,可以直接在TTM上加1,而不用移动交易日
//注意:当前这个临时修改必须在波动率插值之前调用,这样才能正确设置波动率日期
var calendar = CalendarImpl.Get("chn");
return calendar.PrevBizDay(quotationDate).DateTime;
}
/// <summary>
/// 获取根据rule的补充数据
/// </summary>
private static void OptionQuote(ref TradeValueResult valueResult, List<underlying_parameter> quoteRules, double notional, string tradeType, ref QuotationResult qr)
{
foreach (var rule in quoteRules)
{
OptionQuote(ref valueResult, rule, notional, tradeType, ref qr);
}
}
/// <summary>
/// 根据规则 调整报价数据
/// </summary>
private static double OptionQuote(ref TradeValueResult valueResult, underlying_parameter quoteRule, double notional, string tradeType, ref QuotationResult qr)
{
var result = double.NaN;
if (valueResult != null && quoteRule != null)
{
result = valueResult.Pv +
((quoteRule.Delta ?? 0.0) * NumberHelper.Normalize(valueResult.Delta)) +
((quoteRule.Gamma ?? 0.0) * NumberHelper.Normalize(valueResult.Gamma)) +
((quoteRule.Vega ?? 0.0) * NumberHelper.Normalize(valueResult.Vega)) +
(quoteRule.Other ?? 0.0);
}
else if (valueResult != null && quoteRule == null)
{
result = valueResult.Pv;
}
var roundResult = notional == 0 ? OtcFormatHelper.FormatValue(result, 2) : (OtcFormatHelper.FormatValue(result / notional, 2) * notional);
//valueResult.TradePrice = result;
var tempRet = Math.Abs(result);
var tempRoundRet = Math.Abs(roundResult);
if (tradeType == "凤凰期权" || tradeType == "雪球期权")
{
tempRet = result;
tempRoundRet = roundResult;
}
//设置 报价结果
switch (quoteRule.Type)
{
case underlying_parameter.CallAsk:
qr.CallAsk = tempRet;
valueResult.TradePriceAsk = tempRet;
valueResult.RoundedTradePriceAsk = Math.Abs(tempRoundRet);
break;
case underlying_parameter.CallBid:
qr.CallBid = tempRet;
valueResult.TradePriceBid = tempRet;
valueResult.RoundedTradePriceBid = Math.Abs(tempRoundRet);
break;
case underlying_parameter.PutAsk:
qr.PutAsk = tempRet;
valueResult.TradePriceAsk = tempRet;
valueResult.RoundedTradePriceAsk = Math.Abs(tempRoundRet);
break;
case underlying_parameter.PutBid:
qr.PutBid = tempRet;
valueResult.TradePriceBid = tempRet;
valueResult.RoundedTradePriceBid = Math.Abs(tempRoundRet);
break;
}
return result;
}
}
}