using Qdp.ComputeService.Data.CommonModels.MarketInfos; using Qdp.ComputeService.Data.CommonModels.MarketInfos.CurveDefinitions; using Qdp.Foundation.Implementations; using Qdp.Pricing.Base.Enums; using Qdp.Pricing.Base.Implementations; using Qdp.Pricing.Base.Interfaces; using Qdp.Pricing.Base.Utilities; using Qdp.Pricing.Library.Options.Utilities; using YLErp.Configuration; namespace YLErp.QdpModule { /// /// QDP类库帮助类 /// public static class QdpHelper { /// /// 默认到期日计算 /// /// /// public static DateTime defaultMaturityDateFromContract(string contractCode) { //上海规则 //合约交割月份的15日(遇法定假日顺延) var shCodes = new string[] { "CU", "AL", "ZN", "PB", "NI", "AU", "AG", "RB", "WR", "HC", "BU", "RU" }; //合约交割月份前一月份的最后一个交易日, var shPreMonthLastDay = new string[] { "FU", "SC" }; var shPreMonthFifthLastDay = new string[] { "IM" }; //大连规则 //合约月份第10个交易日 var dalianCodes = new string[] { "M", "Y", "A", "B", "P", "C", "CS", "JD", "BB", "FB", "L", "V", "PP", "J", "JM", "I" }; //special dalian rule, EGG //合约交割月份前一月份的倒数第4个交易日 var dalianEggCodes = new string[] { "JD" }; //zhenzhou rule //合约月份第10个交易日 var zhenzhouCodes = new string[] { "SR", "CF", "ZC", "FG", "TA", "MA", "WH", "PM", "SM", "RM", "RI", "LR", "JR", "RS", "OI", "SF", "CY", "AP" }; var calendar = CalendarImpl.Get("chn"); Date adjsuted; if (strStartWith(contractCode, shCodes, out var estimate)) { //SH rule: 15th of delivery date, modified following var unAdjusted = new Date(estimate.Year, estimate.Month, 15); adjsuted = calendar.Adjust(unAdjusted, BusinessDayConvention.ModifiedFollowing); } else if (strStartWith(contractCode, shPreMonthLastDay, out estimate)) { //SH special rule, last biz day of previous month; var unAdjusted = new Date(estimate.Year, estimate.Month, 1); adjsuted = calendar.Adjust(unAdjusted, BusinessDayConvention.Previous); } else if (strStartWith(contractCode, shPreMonthFifthLastDay, out estimate)) { //SH rule, 5th last biz day of previous month var unAdjusted = new Date(estimate.Year, estimate.Month, 1); for (var i = 1; i <= 5; i++) { unAdjusted = calendar.Adjust(unAdjusted.AddDays(-1), BusinessDayConvention.Previous); } adjsuted = unAdjusted; } else if (strStartWith(contractCode, dalianCodes, out estimate) || strStartWith(contractCode, zhenzhouCodes, out estimate)) { //Dalian rule, 10th biz day of contract maturity month var unAdjusted = new Date(estimate.Year, estimate.Month, 1); for (var i = 1; i <= 10; i++) { unAdjusted = calendar.Adjust(unAdjusted.AddDays(1), BusinessDayConvention.ModifiedFollowing); } adjsuted = unAdjusted; } else if (strStartWith(contractCode, dalianEggCodes, out estimate)) { //Dalian Egg rule, 4th last biz day of previous month var unAdjusted = new Date(estimate.Year, estimate.Month, 1); for (var i = 1; i <= 4; i++) { unAdjusted = calendar.Adjust(unAdjusted.AddDays(-1), BusinessDayConvention.Previous); } adjsuted = unAdjusted; } else { adjsuted = calendar.NextBizDay(new Term("1Y").Next(new Date(DateTime.Now))); } return adjsuted.DateTime; } private static bool strStartWith(string str, string[] InThisSet, out Date estimate) { var found = InThisSet.Where(x => str.StartsWith(x)); //default estimate = new Date(DateTime.Today); //locate delivery day from delivery code //RB1803 => 20180301 if (found.Count() > 0) { try { var head = InThisSet.First(x => str.StartsWith(x)); var yearDates = ""; string[] zhenzhouCodes = { "SR", "CF", "ZC", "FGM", "TA", "MA", "WH", "PM" }; if (zhenzhouCodes.Contains(head)) // 郑州商品交易所的默认到期日做特殊处理 { yearDates = "1" + str.Remove(0, head.Length); } else { yearDates = str.Remove(0, head.Length); } //var yearDates = str.Split(head)[1]; var year = Convert.ToInt32("20" + yearDates[0] + yearDates[1]); try { var month = Convert.ToInt32(yearDates[2].ToString() + yearDates[3].ToString()); estimate = new Date(year, month, 1); } catch (Exception) { estimate = new Date(year, 1, 1); } } catch (Exception) { } } return found.Count() > 0; } /// /// 根据term和valueDate计算到期日 /// /// /// /// public static DateTime getMaturityDate(DateTime valueDate, string term) { return new Term(term).Next(new Date(valueDate)).DateTime; } /// /// 从字符串中解析观察日列表 /// 如:2019-07-16,2019-08-16 /// public static Date[] ParseObservationDate(string observationDateStr) { if (string.IsNullOrWhiteSpace(observationDateStr)) { return null; } var fields = observationDateStr.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); var dateStrs = fields[0].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (dateStrs == null || dateStrs.Length == 0) { return null; } return dateStrs.Select(x => new Date(DateTime.Parse(x))).ToArray(); } public static DateTime[] GetObservationDatesFromString(string str) { var dates = ParseObservationDate(str); if (dates == null) { return null; } return dates.Select(x => x.DateTime).ToArray(); } /// /// 解析凤凰、雪球的自定义信息字符串,其格式为 /// {自定义敲出观察日序列};{敲出障碍价格序列};{票息序列} /// 每个序列内部都以逗号分隔多个元素 /// /// /// 返回的元组,第一个元素为敲出观察日列表,第二个元素为敲出障碍价格列表,第三个元素为票息列表 public static Tuple ParseAutocallCustomizedInfo(string str) { Date[] dates = null; double[] customizedKOBarriers = null; double[] customizedCoupons = null; if (!string.IsNullOrWhiteSpace(str)) { //if (!str.Contains(";")) //{ // str = ConvertObservationFromTableToString(str); //} var fields = str.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); if (fields != null && fields.Length > 0) { dates = ParseObservationDate(fields[0]); } if (fields != null && fields.Length > 1) { customizedKOBarriers = ParseDoubleNumbers(fields[1]); } if (fields != null && fields.Length > 2) { customizedCoupons = ParseDoubleNumbers(fields[2]); } } return Tuple.Create(dates, customizedKOBarriers, customizedCoupons); } /// /// 用于qdp 凤凰期权 看涨时,障碍价格为空,该障碍价格等于期初价格的100倍;看跌时,障碍价格为空,该障碍价格等于期初价格的百分之一 /// public static Tuple ParseAutocallCustomizedInfoV2(string str, OptionType callput, double spotPrice, bool isMoneynessOption) { Date[] dates = null; double[] customizedKOBarriers = null; double[] customizedCoupons = null; if (!string.IsNullOrWhiteSpace(str)) { var fields = str.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); if (fields != null && fields.Length > 0) { dates = ParseObservationDate(fields[0]); } if (fields != null && fields.Length > 1) { customizedKOBarriers = ParseDoubleNumbersV2(fields[1], callput, spotPrice, isMoneynessOption); } if (fields != null && fields.Length > 2) { customizedCoupons = ParseDoubleNumbers(fields[2]); } } return Tuple.Create(dates, customizedKOBarriers, customizedCoupons); } /// /// 不用于用于qdp 凤凰期权 /// {自定义敲出观察日序列};{敲出障碍价格序列}; 只有解析两列 /// /// /// 返回的元组,第一个元素为敲出观察日列表,第二个元素为敲出障碍价格列表 public static Tuple ParseAutocallCustomizedInfoV3(string str) { Date[] dates = null; double[] customizedKOBarriers = null; if (!string.IsNullOrWhiteSpace(str)) { //if (!str.Contains(";")) //{ // str = ConvertObservationFromTableToString(str); //} var fields = str.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); if (fields != null && fields.Length > 0) { dates = ParseObservationDate(fields[0]); } if (fields != null && fields.Length > 1) { customizedKOBarriers = ParseDoubleNumbers(fields[1]); } } return Tuple.Create(dates, customizedKOBarriers); } private static double[] ParseDoubleNumbers(string str) { var values = str.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (values == null || values.Length == 0) { return null; } //try //{ // return values.Select(x => double.Parse(x)).ToArray(); //} //catch //{ // return null; //} var dvalues = new double[values.Length]; for (var i = 0; i < values.Length; i++) { if (double.TryParse(values[i], out var d)) { dvalues[i] = d; } else { return null; } } return dvalues; } /// /// 凤凰期权 看涨时,障碍价格为空,该障碍价格等于期初价格的100倍;看跌时,障碍价格为空,该障碍价格等于期初价格的百分之一 /// private static double[] ParseDoubleNumbersV2(string str, OptionType callput, double spotPrice, bool isMoneynessOption) { var values = str.Split(new char[] { ',' }); if (values == null || values.Length == 0) { return null; } double[] newVs = new double[values.Length]; int i = 0; foreach (var v in values) { double newV = 0; if (string.IsNullOrEmpty(v)) { switch (callput) { case OptionType.Call: newV = isMoneynessOption ? 100 : (spotPrice * 100); break; case OptionType.Put: newV = isMoneynessOption ? 0.01 : (spotPrice / 100); break; default: break; } } else { newV = double.Parse(v); } newVs[i] = newV; i++; } return newVs; } /// /// 解析亚式期权的Fixing价格序列 /// /// /// public static Dictionary ParseFixingsFromString(string fixings) { return string.IsNullOrWhiteSpace(fixings) ? new Dictionary() : fixings.Split(QdpConsts.Semilicon) .Select(x => { var splits = x.Split(QdpConsts.Comma); return Tuple.Create(splits[0].ToDate(), double.Parse(splits[1])); }).ToDictionary(x => x.Item1, x => x.Item2); } /// /// 将期限字符串,转换成一组日期 /// /// /// /// /// /// public static List ConvertTenorsToDates(Date valueDate, string[] expireTenors, VolSurfaceKeyDateShiftEnum volSurfaceKeyDateShift, bool excludeEndDate) { var dates = new List(); var calendar = CalendarImpl.Get("chn"); //解析到期日 for (var i = 0; i < expireTenors.Length; ++i) { var expectedTermString = expireTenors[i]; if (Term.IsTerm(expectedTermString)) { var keyPointDate = new Term(expectedTermString).Next(valueDate); if (excludeEndDate) { keyPointDate = keyPointDate.AddDays(-1); } if (calendar.IsHoliday(keyPointDate)) { switch (volSurfaceKeyDateShift) { case VolSurfaceKeyDateShiftEnum.NextBizDay: keyPointDate = calendar.NextBizDay(keyPointDate); break; case VolSurfaceKeyDateShiftEnum.PrevBizDay: keyPointDate = calendar.PrevBizDay(keyPointDate); break; default: break; } } dates.Add(keyPointDate); } } return dates; } /// /// /// public static bool IsCall(string callPut) { if (string.IsNullOrEmpty(callPut)) { return false; } return (OptionType)Enum.Parse(typeof(OptionType), callPut) == OptionType.Call; } public static double AnnualizeFactor(DateTime startDate, DateTime endDate, IDayCount dayCount) { return dayCount.CalcDayCountFraction(new Date(startDate), new Date(endDate)); } public static double AnnualizeFactor(double ttmDays, IDayCount dayCount) { return ttmDays / dayCount.DaysInYear(); } /// /// 计算精确的TTM /// /// 开始日期 /// 结束日期 /// 当前系统交易日 /// 当前服务器时间 /// 使用的DayCount /// 是否有夜盘 /// 是否精确到分钟 /// public static double CalculateTTMDays(DateTime from, DateTime to, DateTime systemDate, DateTime serverDateTime, IDayCount dayCount, bool hasNightMarket, bool precisionOfMinute) { var tempFrom = new Date(from); var tempTo = new Date(to); var days = dayCount.DaysInPeriod(tempFrom, tempTo); //当交易日期不等于系统日期时,或者系统日期小于服务器日期时,默认加一天 double addDays = 1; if (PS.Config.Is润和) { //东证润和 addDays = GetAddDaysWithPhysicalPrecisionOfMinute(serverDateTime); } else { //当交易日期等于系统日期时,算出当天剩余的小数天数,然后加上去 if (from.Date == systemDate && systemDate.Date >= serverDateTime.Date) { addDays = GetAddDays(serverDateTime, hasNightMarket, precisionOfMinute); //如果系统日期大于服务器日期(自然日期),而时间大于收盘时间(15点)且小于夜盘开盘时间(21点) //说明系统在夜盘开始前切换了交易日,此时应将TTM增加完整的一个交易日 if (systemDate.Date > serverDateTime.Date && serverDateTime.Hour >= 15 && serverDateTime.Hour < 21) { addDays += 1; } } } var ttmDays = days + addDays; return ttmDays < 0 ? 0 : ttmDays; } /// /// 计算精确的TTM /// /// 开始日期 /// 结束日期 /// 当前系统交易日 /// 当前服务器时间 /// 使用的DayCount /// 是否有夜盘 /// 是否精确到分钟 /// public static double CalculateTTMDaysForXiangYu(DateTime from, DateTime to, DateTime systemDate, DateTime serverDateTime, IDayCount dayCount, bool hasNightMarket, bool precisionOfMinute) { //最后一天平仓时,ttm按分钟算 //平仓日和系统交易日相等时,服务器时间的判断才有意义 if (from == to && from <= systemDate) { if (serverDateTime.TimeOfDay < new TimeSpan(10, 15, 0))//判断当前时间是否是十点十五分前 { //计算从前一天21点到现在经过的时间; double Secondsum = (serverDateTime.Hour + 3) * 60 + serverDateTime.Minute; var value = (795 - Secondsum) / 795d; return value; } else if (systemDate.Date > serverDateTime.Date && serverDateTime.Hour >= 15)//当系统交易日大于当前服务器时间且系统时间大于15点时,说明系统切过日期,且最后一个交易日的夜盘开始了. { //夜盘时段计算从21点到现在经过的分钟; double Secondsum = serverDateTime.Hour < 21 ? 0 : ((serverDateTime.Hour - 21) * 60 + serverDateTime.Minute); //795 = 13 * 60 + 15 var value = (795 - Secondsum) / 795d; return value; } else//否则就是已经过期了; { return 0; } } return CalculateTTMDays(from, to, systemDate, serverDateTime, dayCount, hasNightMarket, precisionOfMinute); } /// /// 获取交易当天需要添加的小数天数 /// /// 品种 /// 是否要精确到分钟级别 /// public static double GetAddDays(DateTime now, bool hasNightMarket, bool precisionOfMinute = false) { return precisionOfMinute ? GetAddDaysWithPrecisionOfMinute(now, hasNightMarket) : GetAddDaysWithPrecisionOfHalfDay(now, hasNightMarket); } /// /// 获取交易当天需要添加的小数天数,精确到半天,夜盘精确到三分之一天 /// /// 品种 /// private static double GetAddDaysWithPrecisionOfHalfDay(DateTime now, bool hasNightMarket) { if (hasNightMarket) { if (now.Hour >= 21) { return 1; } else if (now.Hour >= 0 && now.Hour < 12) { return 0.6666; } else if (now.Hour >= 12 && now.Hour < 15) { return 0.3333; } else { return 0; } } else { if (now.Hour >= 21 || (now.Hour >= 0 && now.Hour < 12)) { return 1; } else if (now.Hour >= 12 && now.Hour < 15) { return 0.5; } else { return 0; } } } /// /// 在日内计算精确到分钟级别的TTM调整 /// 假设所有夜盘交易时间都为2小时 /// 上午交易时间为2.5小时,下午为1.5小时 /// /// 单元测试: GetAddDaysWithPrecisionOfMinuteTest /// /// /// private static double GetAddDaysWithPrecisionOfMinute(DateTime now, bool hasNightMarket) { return AnalyticalOptionPricerUtil.timeToMaturityFractionOnMaturityDate(now.TimeOfDay, hasNightMarket, false); //double past; //var totalTradingMiniutes = hasNightMarket ? (2 + 2.5 + 1.5) * 60 : (2.5 + 1.5) * 60; //if (hasNightMarket) //{ // if (now.Hour >= 21 && now.Hour < 23) // { // past = (now.Hour - 21) * 60 + now.Minute; // } // else if (now.Hour >= 23) // { // past = 2 * 60; // } // else if (now.Hour < 9) // { // past = 2 * 60; // } // else if ((now.Hour >= 9 && now.Hour <= 10) || (now.Hour == 11 && now.Minute <= 30)) // { // past = 2 * 60 + (now.Hour - 9) * 60 + now.Minute; // } // else if ((now.Hour > 11 || (now.Hour == 11 && now.Minute > 30)) && (now.Hour < 13 || (now.Hour == 13 && now.Minute < 30))) // { // past = (2 + 2.5) * 60; // } // else if (((now.Hour == 13 && now.Minute >= 30) || now.Hour > 13) && (now.Hour < 15)) // { // past = (2 + 2.5) * 60 + (now.Hour - 13.5) * 60 + now.Minute; // } // else if (now.Hour >= 15 && now.Hour < 21) // { // past = (2 + 2.5 + 1.5) * 60; // } // else // { // //should never be here // throw new Exception("精确到分钟级别的TTM计算错误"); // } //} //else //{ // if ((now.Hour >= 9 && now.Hour <= 10) || (now.Hour == 11 && now.Minute <= 30)) // { // past = (now.Hour - 9) * 60 + now.Minute; // } // else if ((now.Hour > 11 || (now.Hour == 11 && now.Minute > 30)) && (now.Hour < 13 || (now.Hour == 13 && now.Minute < 30))) // { // past = 2.5 * 60; // } // else if (((now.Hour == 13 && now.Minute >= 30) || now.Hour > 13) && (now.Hour < 15)) // { // past = 2.5 * 60 + (now.Hour - 13.5) * 60 + now.Minute; // } // else if (now.Hour >= 15 && now.Hour < 21) // { // past = (2.5 + 1.5) * 60; // } // else if (now.Hour >= 21 || now.Hour < 9) // { // past = 0; // } // else // { // //should never be here // throw new Exception("精确到分钟级别的TTM计算错误"); // } //} //if (hasNightMarket) //{ // if (now.Hour >= 21 && now.Hour < 23) // { // past = (now.Hour - 21) * 60 + now.Minute; // } // else if (now.Hour >= 23) // { // past = 2 * 60; // } // else if (now.Hour < 9) // { // past = 2 * 60; // } // else if (now.Hour >= 9 && (now.Hour <= 11 && now.Minute <= 30)) // { // past = 2 * 60 + (now.Hour - 9) * 60 + now.Minute; // } // else if ((now.Hour >= 11 || (now.Hour == 11 && now.Minute > 30)) && (now.Hour <= 13 || (now.Hour == 13 && now.Minute < 30))) // { // past = (2 + 2.5) * 60; // } // else if ((now.Hour >= 13 && now.Minute >= 30) && now.Hour < 15) // { // past = (2 + 2.5) * 60 + (now.Hour - 13.5) * 60 + now.Minute; // } // else if (now.Hour >= 15 && now.Hour < 21) // { // past = (2 + 2.5 + 1.5) * 60; // } // else // { // //should never be here // throw new Exception("精确到分钟级别的TTM计算错误"); // } //} //else //{ // if (now.Hour >= 9 && (now.Hour <= 11 && now.Minute <= 30)) // { // past = (now.Hour - 9) * 60 + now.Minute; // } // else if ((now.Hour >= 11 || (now.Hour == 11 && now.Minute > 30)) && (now.Hour <= 13 || (now.Hour == 13 && now.Minute < 30))) // { // past = 2.5 * 60; // } // else if ((now.Hour >= 13 && now.Minute >= 30) && (now.Hour < 15)) // { // past = 2.5 * 60 + (now.Hour - 13.5) * 60 + now.Minute; // } // else if (now.Hour >= 15 && now.Hour < 21) // { // past = (2.5 + 1.5) * 60; // } // else if (now.Hour >= 21 || now.Hour < 9) // { // past = 0; // } // else // { // //should never be here // throw new Exception("精确到分钟级别的TTM计算错误"); // } //} //return (totalTradingMiniutes - past) / totalTradingMiniutes; } /// /// 当天剩余的分钟数 / 一天总的分钟数 /// /// /// private static double GetAddDaysWithPhysicalPrecisionOfMinute(DateTime now) { double totalTradingMiniutes = 24 * 60; double past = now.Hour * 60 + now.Minute; //到期日按照3点来算,所以需要把剩余的9个小时扣除 double minusNineHour = 9 * 60; return (totalTradingMiniutes - past - minusNineHour) / totalTradingMiniutes; } /// /// 创建一个无风险利率曲线 /// public static InstrumentCurveDefinition CreateRiskFreeCurve(string curveName, double riskFreeRate, string curveDayCount = "Act365") { //创建一个Flat curve var rateMktDataList = new List { new RateMktData("1D", riskFreeRate, "Spot", "None", curveName), new RateMktData("3Y", riskFreeRate, "Spot", "None", curveName) }; if (string.IsNullOrWhiteSpace(curveDayCount)) { curveDayCount = "Act365"; } var curveConvention = new CurveConvention(Guid.NewGuid().ToString(), "CNY", "ModifiedFollowing", "chn", curveDayCount, "Continuous", "Linear"); return new InstrumentCurveDefinition(curveName, curveConvention, rateMktDataList.ToArray(), "SpotCurve"); } } }