198 lines
10 KiB
C#
198 lines
10 KiB
C#
using YLErp.BLL;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.BLL.Calculation.V2;
|
|
using YLErp.BLL.Calculation.V2.Parameter;
|
|
using YLErp.Modules.ClientModule;
|
|
using YLErp.Modules.VolatilityModule;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Modules.TQuoteModule
|
|
{
|
|
/// <summary>
|
|
/// WebApi-QdpCalculationController-helper
|
|
/// </summary>
|
|
public static class QdpCalculationApiHelpere
|
|
{
|
|
/// <summary>
|
|
/// 计算某一个期权的买卖价格
|
|
/// </summary>
|
|
public static ClientOptionQuoteResult ValueOptionV2(OptionValueRequest req, string userGroup = null)
|
|
{
|
|
try
|
|
{
|
|
var valueDate = req.ValueDate ?? CalculatorHelper.RealtimeQuoteValueDate();
|
|
|
|
if (req.MaturityDate < valueDate)
|
|
{
|
|
return new ClientOptionQuoteResult() { Info = "到期日小于估值日", StatusCode = -1 };
|
|
}
|
|
|
|
var qdpMarketId = Guid.NewGuid().ToString();
|
|
var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(qdpMarketId);
|
|
|
|
volatility bidVol = null, askVol = null;
|
|
underlying_manager underlying = null;
|
|
Variety variety = null;
|
|
using (var db = DbContextFactory.GetYLDbContext())
|
|
{
|
|
underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(req.UnderlyingCode);
|
|
if (underlying != null)
|
|
{
|
|
underlying.QuotationDate = valueDate;
|
|
if (PS.Config.ErpElement.SkewMapVolConstruction)
|
|
{
|
|
bidVol = askVol = VolatilityHelper.GetVol(valueDate.Date, "交易", underlying.UnderlyingCode, userGroup ?? "");
|
|
}
|
|
else
|
|
{
|
|
bidVol = VolatilityHelper.GetVol(valueDate.Date, "报价Bid", underlying.UnderlyingCode, userGroup ?? "");
|
|
askVol = VolatilityHelper.GetVol(valueDate.Date, "报价Ask", underlying.UnderlyingCode, userGroup ?? "");
|
|
}
|
|
variety = db.variety.FirstOrDefault(x => x.VarietyCode != null && x.id == underlying.UnderlyingTypeId);
|
|
}
|
|
}
|
|
|
|
if (underlying != null && bidVol != null && askVol != null)
|
|
{
|
|
//var maturityDate = req.MaturityDate.ToString("yyyy-MM-dd");
|
|
//根据用户设置的bid/ask天数调整规则来分别调整到期日
|
|
//TODO: bidMaturityShift和askMaturityShift应该从某数据库表读取
|
|
var bidMaturityShift = 0;
|
|
var askMaturityShift = 0;
|
|
var otherInfo = "";
|
|
var client_param = ClientPricingParamService.GetPricingParam(valueDate.Date, req.MaturityDate.Date);
|
|
if (client_param != null)
|
|
{
|
|
askMaturityShift = client_param.ask_tuning_day ?? 0;
|
|
bidMaturityShift = client_param.bid_tuning_day ?? 0;
|
|
otherInfo = client_param.ToJson();//$"ask到期日偏离{askMaturityShift}天,bid到期日偏离{bidMaturityShift}天";
|
|
}
|
|
var dayCount = CalculatorHelper.GetTradeDayCount();
|
|
var bidMaturityDate = QdpCalendarHelper.ShiftDate(req.MaturityDate, dayCount, bidMaturityShift).DateTime;
|
|
var askMaturityDate = QdpCalendarHelper.ShiftDate(req.MaturityDate, dayCount, askMaturityShift).DateTime;
|
|
|
|
//使用全局的DiscountCurve以提高计算效率
|
|
var discountCurveName = Guid.NewGuid().ToString();
|
|
var discountCurve = CalculatorHelper.CreateConstantRiskFreeCurve(discountCurveName, valuedateBLL.RiskFreeRate / 100.0);
|
|
marketProxy.AddYieldCurve(discountCurveName, valueDate.ToString("yyyy-MM-dd"), discountCurve);
|
|
|
|
var volSurfaceName = underlying.GenerateVolSurfaceName();
|
|
|
|
var parameter = new VanillaOptionParameter()
|
|
{
|
|
ValueDate = valueDate,
|
|
SpotPrices = new Dictionary<string, double>() { { req.UnderlyingCode, req.SpotPrice } },
|
|
DiscountCurveName = discountCurveName,
|
|
HasNightMarket = variety.HasNightMarket,
|
|
PreciseTimeMode = req.commodityFuturesPreciseTimeMode
|
|
};
|
|
|
|
var trade = new trade()
|
|
{
|
|
TradeType = "香草期权",
|
|
TradeDate = valueDate,
|
|
MaturityDate = bidMaturityDate,
|
|
ExerciseDate = bidMaturityDate,
|
|
OptionType = req.OptionType,
|
|
ExerciseMode = req.Exercise,
|
|
BuySell = "Buy",
|
|
Strike = req.Strike,
|
|
Notional = req.Notional,
|
|
UnderlyingCode = req.UnderlyingCode
|
|
};
|
|
parameter.Volatility = VolatilityHelper.GetInterpolatedVol(
|
|
volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal,
|
|
volSurface: bidVol,
|
|
valueDate: valueDate,
|
|
underlyingCode: req.UnderlyingCode,
|
|
exerciseDate: bidMaturityDate,
|
|
strike: req.Strike,
|
|
isBuy: true,
|
|
isCall: req.OptionType == "Call",
|
|
spotPrice: req.SpotPrice,
|
|
isMoneynessOption: false);
|
|
var bidResult = ValueCalculator.CalculateTradeValue(qdpMarketId, trade, underlying, parameter);
|
|
|
|
trade.MaturityDate = askMaturityDate;
|
|
trade.ExerciseDate = askMaturityDate;
|
|
parameter.Volatility = VolatilityHelper.GetInterpolatedVol(
|
|
volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal,
|
|
volSurface: askVol,
|
|
valueDate: valueDate,
|
|
underlyingCode: req.UnderlyingCode,
|
|
exerciseDate: askMaturityDate,
|
|
strike: req.Strike,
|
|
isBuy: false,
|
|
isCall: req.OptionType == "Call",
|
|
spotPrice: req.SpotPrice,
|
|
isMoneynessOption: false);
|
|
var askResult = ValueCalculator.CalculateTradeValue(qdpMarketId, trade, underlying, parameter);
|
|
|
|
var valueResult = new ClientOptionQuoteResult()
|
|
{
|
|
BuyQuote = Math.Abs(bidResult.Pv) / req.Notional,
|
|
BuyVol = bidResult.Vol,
|
|
BuyPv = Math.Abs(bidResult.Pv),
|
|
BuyPercentageQuote = Math.Abs(bidResult.Pv) / req.Notional / req.SpotPrice,
|
|
|
|
SellQuote = Math.Abs(askResult.Pv) / req.Notional,
|
|
SellVol = askResult.Vol,
|
|
SellPv = Math.Abs(askResult.Pv),
|
|
SellPercentageQuote = Math.Abs(askResult.Pv) / req.Notional / req.SpotPrice,
|
|
|
|
SellMargin =
|
|
CommodityFutureOptionMargin(
|
|
Math.Abs(askResult.Pv),
|
|
req.Notional,
|
|
req.Strike,
|
|
req.SpotPrice,
|
|
variety.Margin ?? 0.0, //避免品种预付金未设置而导致计算出错
|
|
req.OptionType)
|
|
};
|
|
|
|
QdpMarketManager.Instance.RemovePrebuiltMarketProxy(qdpMarketId);
|
|
return valueResult;
|
|
}
|
|
else
|
|
{
|
|
LogFactory.GetLogger().Error($"ValueOptionV2: 未找到标的{req.UnderlyingCode}或其波动率为空!");
|
|
return null;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogFactory.GetLogger().Error("ValueOptionV2", ex);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 期货期权卖方交易预付金的收取标准为下列两者中较大者:
|
|
///(1)权利金(期权合约结算价×标的期货合约交易单位)+标的期货合约交易预付金-期权合约虚值额的一半
|
|
///(2)权利金(期权合约结算价×标的期货合约交易单位)+标的期货合约交易预付金的一半
|
|
///看涨虚值额 = max(期权合约执行价格 - 标的期货合约当日结算价,0)*合约乘数;
|
|
///看跌虚值额=max(标的期货合约当日结算价 - 期权合约执行价格,0)*合约乘数。
|
|
///豆粕期权的合约乘数是10、标的期货合约的交易单位是10.
|
|
///所以以上2个公式可以结合成为一个公式:
|
|
///预付金=权利金+MAX(期货预付金-1/2虚值额,1/2期货预付金)
|
|
/// </summary>
|
|
/// <param name="premium">权利金</param>
|
|
/// <param name="amount">交易量</param>
|
|
/// <param name="strike">行权价</param>
|
|
/// <param name="spotPrice">现价</param>
|
|
/// <param name="futureMarginRatio">期货预付金率</param>
|
|
/// <param name="optionType">看涨看跌</param>
|
|
/// <returns></returns>
|
|
private static double CommodityFutureOptionMargin(double premium, double amount, double strike, double spotPrice, double futureMarginRatio, string optionType = "Call")
|
|
{
|
|
var futureMargin = futureMarginRatio * amount * spotPrice;
|
|
var outTheMoney =
|
|
(optionType.ToUpper() == "PUT")
|
|
? Math.Max(spotPrice - strike, 0) * amount //看跌虚值额
|
|
: Math.Max(strike - spotPrice, 0) * amount; //看涨虚值额
|
|
|
|
return premium + Math.Max(futureMargin - outTheMoney * 0.5, futureMargin * 0.5);
|
|
}
|
|
}
|
|
}
|