1221 lines
53 KiB
C#
1221 lines
53 KiB
C#
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值计算call,put
|
||
//波动率调整不需要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.Handle(td, calcResult);
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|