public static RollResultContractItem CalculateFutureResult(IContract contract, Func<FutureSeries, FutureStatic> getFutureFuc, Func<FutureSeries, string> getContractFunc, Func<FutureSeries, double> getBondPriceFunc) { var market = AsFuture(contract).BbgBase.Market(); var country = (BondAnalytics.Country) Enum.Parse(typeof(BondAnalytics.Country), market); // byran lim config coded in symmetry.net. we use this until we have a centralised place for the curve static var fcstCfg = new SpreadTimeSeriesConfigs(market, bForecastCurve: true); var discCfg = new SpreadTimeSeriesConfigs(market, bForecastCurve: false); var rollresults = contract.ContractSeries.Select(c => { var s = c as FutureSeries; if (s == null) return null; var forCurve = SymmetryDataModel.GetDiscountCurve(fcstCfg.SymForecastCurveName, c.Date); var disCurve = SymmetryDataModel.GetDiscountCurve(fcstCfg.SymDiscountCurveName, c.Date); var mms = BondCalc.CalcMeasure(CalcMeasure.MMS, getBondPriceFunc(s), country, getFutureFuc(s).FirstNoticeDate, disCurve, forCurve, DateTime.MinValue, s.CurrentFuture.Expiry, DateTime.MinValue, 0, 6, true, BondAnalytics.DayCountType.Act360, 12,12,null); var mmsSeries = new Tuple<DateTime, RollResultItem>(c.Date, new RollResultItem { Name = getContractFunc(s), Value = mms, }); return new {m = mmsSeries}; }).Where(x => x != null).ToArray(); return new RollResultContractItem { MMS = new DatedDataCollectionGen<RollResultItem>(rollresults.Select(r => r.m.Item1).ToArray(), rollresults.Select(r => r.m.Item2).ToArray()), }; }
//----------------------------------------------------------------------------------- // function to return carry adjusted smooth series, together with Bond ISIN, series price, convfac public static Frame<DateTime, String> GenerateCarryAdjSeries( string ticker, DateTime start, DateTime end, string countryCode, ICarbonClient client = null ) //----------------------------------------------------------------------------------- { //variables used for futureSeriesBuilder var eRollMethod = TimeSeriesUtilities.GetRollMethods("FirstNoticeDate"); var eInterpolationTypes = TimeSeriesUtilities.GetInterpolationType("None"); var eSmoothingTypes = TimeSeriesUtilities.GetSmoothingTypes("None"); #region Configs SpreadTimeSeriesConfigs config = new SpreadTimeSeriesConfigs(countryCode, false); string ccy = config.ccy; string calendarCode = config.calendarcode; string futuresStaticMoniker = config.futuresStaticMoniker; string carbonEnv = config.carbonEnv; BondAnalytics.Country country = config.country; BondAnalytics.DayCountType dct = config.dct; List<string> contractSortOrder = config.contractSortOrder; List<DateTime> holidays = TimeSeriesUtilities.GetHolidays(calendarCode, client); #endregion // in rolling series US is following calendar of USGS rather than USNY var dateRange = TimeSeriesUtilities.GetAllDatesInDateime(start, end, countryCode.ToUpper() == "US" ? "USGS" : calendarCode, client, "1b"); //Get Rolling Series HashSet<string> futuresList = new HashSet<string> { "TU", "FV", "TY", "UXY", "US", "WN", "DU", "OE", "RX", "UB", "IK", "BTS", "OAT" }; bool tickerIsFuture = futuresList.Contains(ticker); bool isCT = ticker.ToUpper().Contains("CT"); var rollType = isCT ? "ct" : "ctd"; string moniker = string.Format("symapp.roll.{0}.{1}.{2}", rollType, ticker.Market(), ticker).ToLower(); string fmoniker; // Carbon frame // var frame = client.GetFrameAsync<DateTime>(moniker).Result; if (!frame.ColumnKeys.Contains("price")) return null;// need better null value checker here Series<DateTime, string> bondISIN = null; Series<DateTime, double> bondPrice = null; Dictionary<DateTime, string> futureTicker = new Dictionary<DateTime, string>(); Dictionary<DateTime, double> convFac = new Dictionary<DateTime, double>(); Dictionary<DateTime, double> futurePrice = new Dictionary<DateTime, double>(); Dictionary<DateTime, double> smoothSeries = new Dictionary<DateTime, double>(); Series<DateTime, double> futureConvertedPrice = null; //Repo series and carry series Dictionary<DateTime, double> repoSeries = new Dictionary<DateTime, double>(); Dictionary<DateTime, double> carrySeries = new Dictionary<DateTime, double>(); //DV01 series (Future DV01 = CTD DV01/ConvFac; CT DV01 = Bond DV01) Dictionary<DateTime, double> DV01Series = new Dictionary<DateTime, double>(); //Variables needed for futures Frame<string, string> combinedStatic = null; int RowNum = 0; try { bondISIN = frame.GetColumn<string>("ticker").Between(start, end);//Need check on sorted by date bondPrice = frame.GetColumn<double>("price").Between(start, end); } catch (Exception) { //Error handling } //Future tickers and Future Price as well as conversion factors if (tickerIsFuture) { fmoniker = string.Format("symapp.roll.{0}.{1}.{2}", "f" + rollType, ticker.Market(), ticker).ToLower(); frame = client.GetFrameAsync<DateTime>(fmoniker).Result; if (!frame.ColumnIndex.KeySequence.Contains("price")) return null;//need better null value checker here try { futureConvertedPrice = frame.GetColumn<double>("price").Between(start, end).Sort(); } catch (Exception) { //Error handling } //Get the future static table //Futures List<string> tickers = new List<string>(); tickers.Add(ticker.ToUpper() + ".Front"); FuturesSeriesBuilder seriesBuilder = new FuturesSeriesBuilder(tickers, dateRange, holidays, futuresStaticMoniker, client, carbonEnv, rollMthd: eRollMethod, interpType: eInterpolationTypes, contractSortOrder: config.contractSortOrder, smoothingType: eSmoothingTypes); var futStatic = seriesBuilder.FuturesStatic; //Bond Static Dictionary<String, TimeSeriesBondStatic> bndStatics = TimeSeriesUtilities.GetBondStaticsFromFuturesBuilder(seriesBuilder, config, client); combinedStatic = SpreadSeriesBuilder.combineFuturesAndBondStatic(bndStatics, futStatic); RowNum = combinedStatic.RowCount; } double futureConvertedPx = 0.0; double lastRepo = 0.0; double fwdPrice = 0.0; object dailyRepo = null; bool bHaveEverthing = true; DateTime prevD8 = DateTime.MinValue; //loop through each date to find the corresponding future ticker and conversion factors // and add repo and carry for bond series // can we get the sorted key list #region buildSeries foreach (DateTime d8 in bondISIN.Keys.OfType<DateTime>()) { // DV01Series string currBondISIN = bondISIN.TryGet(d8).Value; Double bondDV01 = 0.0; DateTime settleDate = d8; var currBondStatic = AnalyticsUtilities.GetBondStaticData(client, currBondISIN); if (currBondStatic == null) { bHaveEverthing = false; DV01Series.Add(d8, 0.0);//Need better handling on empty bond static } else { if (config.bondSettleLag > 0) { settleDate = BondAnalytics.NextBusDay(settleDate.AddDays(1), holidays); } if (config.bondSettleLag > 1) { settleDate = BondAnalytics.NextBusDay(settleDate.AddDays(1), holidays); } bondDV01 = BondAnalytics.SolveYield(country, settleDate, bondPrice.TryGet(d8).Value, currBondStatic.EffectiveDate, currBondStatic.FirstCouponDate, currBondStatic.MaturityDate, currBondStatic.Coupon, currBondStatic.CpnFreq)[1]; DV01Series.Add(d8, bondDV01); } if (tickerIsFuture) { //Need gaurantee that the table is sorted int i = 0; while (combinedStatic.GetColumn<DateTime>("fut_notice_first").GetAt(i) <= d8 && i < RowNum) { i++; } if (i == RowNum) { futureTicker.Add(d8, string.Empty); convFac.Add(d8, double.NaN); futurePrice.Add(d8, double.NaN); } else { futureTicker.Add(d8, combinedStatic.GetColumn<string>("ticker").GetAt(i)); convFac.Add(d8, combinedStatic.GetColumn<double>("fut_cnvs_factor").GetAt(i)); futureConvertedPx = futureConvertedPrice.TryGet(d8).Value; futurePrice.Add(d8, futureConvertedPx / combinedStatic.GetColumn<double>("fut_cnvs_factor").GetAt(i)); //update CTD DV01 to be future DV01 DV01Series[d8] = DV01Series[d8] / combinedStatic.GetColumn<double>("fut_cnvs_factor").GetAt(i); } //Add SmoothSeries if (prevD8 == DateTime.MinValue) { smoothSeries.Add(d8, futurePrice[d8]); } else { //check if there is ticker change if (futureTicker[d8] == futureTicker[prevD8]) { smoothSeries.Add(d8, smoothSeries[prevD8] + futurePrice[d8] - futurePrice[prevD8]); } else { //find the old future price on current date string oldFutureTicker = futureTicker[prevD8]; string full_ticker = oldFutureTicker + " COMDTY"; var oldFuturePrice = client.GetCbnTimeSeriesDataCell(full_ticker, "eod", d8, "settle"); if (oldFuturePrice == null) { full_ticker = AnalyticsUtilities.GetAlternativeFuturesName(full_ticker); oldFuturePrice = client.GetCbnTimeSeriesDataCell(full_ticker, "eod", d8, "settle"); } //Otherwise report error: assume no price change a good way? if (oldFuturePrice == null) oldFuturePrice = futurePrice[prevD8]; smoothSeries.Add(d8, smoothSeries[prevD8] + (oldFuturePrice as double? ?? double.NaN) - futurePrice[prevD8]);// potential problem if current res is NaN, all following value would be NaN } } } else { //repo and carry series for bond try { dailyRepo = client.GetCbnTimeSeriesDataCell(currBondISIN, "eod-mlp", d8, "wtdRate"); // repoSeries.Add(d8, dailyRepo as double?); } catch { //Missing Data //need to convert it } lastRepo = (dailyRepo as double?) ?? lastRepo; repoSeries.Add(d8, lastRepo); //need bond static info if (currBondStatic == null) { bHaveEverthing = false; carrySeries.Add(d8, 0.0);//Need better handling for missing bond static smoothSeries.Add(d8, 0.0);//Need better handling for missing bond static } else { //calculate one-day fwdPrice //Current next business day function doesn't handle number of days so get settleDate the hard way. //one day carry DateTime fwdDate = settleDate; fwdDate = BondAnalytics.NextBusDay(fwdDate.AddDays(1), holidays); if (fwdDate <= settleDate) { //error handling here : fwdDate must be bigger than settleDate } fwdPrice = BondAnalytics.CalcBondFwd(country, settleDate, bondPrice.TryGet(d8).Value, currBondStatic.EffectiveDate, currBondStatic.FirstCouponDate, currBondStatic.MaturityDate, currBondStatic.Coupon, currBondStatic.CpnFreq, fwdDate, lastRepo)[0]; carrySeries.Add(d8, bondPrice.TryGet(d8).Value - fwdPrice); //Smooth series for bond if (prevD8 == DateTime.MinValue) { smoothSeries.Add(d8, bondPrice.TryGet(d8).Value);//assume no carry added on day 1 } else { if (bondISIN.TryGet(prevD8).Value == bondISIN.TryGet(d8).Value) { //no change in series smoothSeries.Add(d8, smoothSeries[prevD8] + bondPrice.TryGet(d8).Value - bondPrice.TryGet(prevD8).Value); } else { string oldBondISIN = bondISIN.TryGet(prevD8).Value; var oldBondPrice = client.GetCbnTimeSeriesDataCell(oldBondISIN, "eod-mlp", d8, "close"); if (oldBondPrice == null) oldBondPrice = bondPrice.TryGet(prevD8).Value;// May need better way to handle this error smoothSeries.Add(d8, smoothSeries[prevD8] + (oldBondPrice as double? ?? double.NaN) - bondPrice.TryGet(prevD8).Value); } } } } //update prevD8 prevD8 = d8; } #endregion //Sort the dictionary and package into frame for output if (tickerIsFuture) { //building using dictionary Dictionary<string, Series<DateTime, string>> resHolder = new Dictionary<string, Series<DateTime, string>>(); resHolder["futureTicker"] = futureTicker.ToSeries(); var res = Frame.FromColumns(resHolder); res.AddColumn("futruePrice", futurePrice.ToSeries()); res.AddColumn("CTD_ISIN", bondISIN); res.AddColumn("ConvFac", convFac.ToSeries()); res.AddColumn("DV01", DV01Series.ToSeries()); res.AddColumn("smoothSeries", smoothSeries.ToSeries()); res.SortRowsByKey(); return res; } else { //if bond Dictionary<string, Series<DateTime, string>> resHolder = new Dictionary<string, Series<DateTime, string>>(); resHolder["BondISIN"] = bondISIN; var res = Frame.FromColumns(resHolder); res.AddColumn("BondPrice", bondPrice); res.AddColumn("repo", repoSeries.ToSeries()); res.AddColumn("carry", carrySeries.ToSeries()); res.AddColumn("DV01", DV01Series.ToSeries()); res.AddColumn("smoothSeries", smoothSeries.ToSeries()); res.SortRowsByKey(); return res; } }
//---------------------------------------------------------------------------------------- public static Frame<DateTime, String> GenerateInvoiceSpreadTRS( List<string> tickers, DateTime start, DateTime end, string countryCode, string pnlType = "InvoiceSpread", bool bCumulative = true, bool bOIS = true, string samplingFreq= "1b", string rollMethod = "FirstNoticeDate", string holcal = "", double notional = double.NaN, ICarbonClient client = null) //---------------------------------------------------------------------------------------- { var eRollMethod = TimeSeriesUtilities.GetRollMethods(rollMethod); var eInterpolationTypes = TimeSeriesUtilities.GetInterpolationType("None"); var eSmoothingTypes = TimeSeriesUtilities.GetSmoothingTypes("None"); // Don't smooth, doesn't make sense for PnL bool bForecastCurve = !bOIS; /* CARBON REQUIRES UTC TIME - MODIFY DATES TO ACCOMODATE */ start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, DateTimeKind.Utc); end = new DateTime(end.Year, end.Month, end.Day, 0, 0, 0, DateTimeKind.Utc); #region cache string all_tickers = ";"; foreach (var s in tickers) all_tickers += s; string cacheKey = "GenerateInvoiceSpread" + tickers.ToString() + start.ToString() + end.ToString() + countryCode + pnlType + bCumulative.ToString() + bOIS.ToString() + samplingFreq + rollMethod + holcal + notional.ToString(); if (TimeSeriesCache.ContainsKey(cacheKey)) { return TimeSeriesCache[cacheKey]; } #endregion if (client == null) { throw new ArgumentException("No Carbon Client!"); } #region MeasureSetup string swapMeasure = ""; string futMeasure = ""; string ratio = ""; bool bDefaultNoCumulative = false; switch (pnlType) { case "InvoiceSpread": if (double.IsNaN(notional)) // work in DV01 terms { swapMeasure = "SwapPnlDV01Adj"; futMeasure = "FuturesPnlDV01Adj"; } else { swapMeasure = "SwapPnl"; futMeasure = "FuturesPnl"; //ratio = "SpreadHedgeRatio"; // required to construct the correct pnl } break; case "Futures": if (double.IsNaN(notional)) // work in DV01 terms { swapMeasure = ""; futMeasure = "FuturesPnlDV01Adj"; } else { swapMeasure = ""; futMeasure = "FuturesPnl"; } break; case "MMS": if (double.IsNaN(notional)) // work in DV01 terms { swapMeasure = "SwapPnlDV01Adj"; futMeasure = ""; } else { swapMeasure = "SwapPnl"; futMeasure = ""; } break; case "MMSDV01": swapMeasure = "MMSDV01"; futMeasure = ""; bDefaultNoCumulative = true; break; case "FuturesDV01": swapMeasure = "FuturesDV01"; futMeasure = ""; bDefaultNoCumulative = true; break; case "HedgeRatio": swapMeasure = "SpreadHedgeRatio"; futMeasure = ""; bDefaultNoCumulative = true; break; default: throw new ArgumentException(""); } #endregion #region Configs SpreadTimeSeriesConfigs config = new SpreadTimeSeriesConfigs(countryCode, !bOIS); string ccy = config.ccy; string calendarcode = (holcal != "") ? holcal : config.calendarcode; string futuresStaticMoniker = config.futuresStaticMoniker; string carbonEnv = config.carbonEnv; BondAnalytics.Country country = config.country; BondAnalytics.DayCountType dct = config.dct; long swpfixfreq = config.swpfixfreq; long swpfloatfreq = config.swpfloatfreq; long bndCouponFreq = config.bndCouponFreq; int BlendIndex = config.BlendIndex; string symForecastCurveName = config.SymForecastCurveName; string symDiscountCurveName = config.SymDiscountCurveName; List<string> contractSortOrder = config.contractSortOrder; List<DateTime> holidays = TimeSeriesUtilities.GetHolidays(calendarcode, client); #endregion var dateRange = TimeSeriesUtilities.GetAllDatesInDateime(start, end, calendarcode, client, samplingFreq); // Spread Static. SpreadConventions spreadconventions = new SpreadConventions(country, dct, swpfixfreq, swpfloatfreq, bndCouponFreq, bForecastCurve, BlendIndex); // Futures. FuturesSeriesBuilder seriesBuilder = new FuturesSeriesBuilder(tickers, dateRange, holidays, futuresStaticMoniker, client, carbonEnv, rollMthd: eRollMethod, interpType: eInterpolationTypes, contractSortOrder: contractSortOrder, smoothingType: eSmoothingTypes); // Bond Static var bndStatics = TimeSeriesUtilities.GetBondStaticsFromFuturesBuilder(seriesBuilder, config, client); // Swap Curves var curves = TimeSeriesUtilities.GetCurvesFromCarbon(dateRange, ccy, client); if (curves == null || bndStatics == null) return null; var forecastCurves = curves.Item1; var discountCurves = curves.Item2; SwapCurveSeriesBuilder swapBuilder = new SwapCurveSeriesBuilder(dateRange, symForecastCurveName, symDiscountCurveName, ccy, holidays, forecastCurves, discountCurves, client); // Generate Pnls Frame<DateTime,string> swapPnl = null; Frame<DateTime, string> futPnl = null; if (swapMeasure != "") { SpreadSeriesBuilder swapPnlBuilder = new SpreadSeriesBuilder(swapMeasure, seriesBuilder, swapBuilder, bndStatics, spreadconventions, holidays); swapPnl = swapPnlBuilder.SpreadValues; } if (futMeasure != "") { SpreadSeriesBuilder futPnlBuilder = new SpreadSeriesBuilder(futMeasure, seriesBuilder, swapBuilder, bndStatics, spreadconventions, holidays); futPnl = futPnlBuilder.SpreadValues; } // Setup pnl correctly Frame<DateTime, string> pnl; if ( (swapPnl != null) && (futPnl != null)) { // This allows it to match movements on the invoice spread chart. // Long TRS = pay fixed, long bond // Spread increases, mms increases, yield decreases -> swap/bond +ve pnl pnl = futPnl - swapPnl; } else if (swapPnl != null) { pnl = swapPnl; } else { pnl = futPnl; } // Make it cumulative if flagged, but not for hedge ratios etc if (bCumulative && !bDefaultNoCumulative) { pnl = pnl.SortRowsByKey(); // in case pnl = TimeSeriesUtilities.DeedleFrameCumsum(pnl); } //Notional Adjust this thing if (!double.IsNaN(notional) && pnlType!="HedgeRatio") { pnl *= notional/100; // Default 100 } TimeSeriesCache[cacheKey] = pnl; return pnl; }
//---------------------------------------------------------------------------------------- public static Frame<DateTime, String> GenerateRollingBondForwardSeries( List<string> tickers, DateTime start, DateTime end, string tenor, string countryCode, string measure, string holidayCode, bool bOIS = true, int optExDivDays = 0, bool bSpreadOrRepo = true, List<double> reposOrSpreads = null, ICarbonClient client = null) //---------------------------------------------------------------------------------------- { // Checks if (client == null) { throw new ArgumentException("No Carbon Client!"); } #region cache string all_tickers = ";"; foreach (var s in tickers) all_tickers += s; string allRepoOis = ""; if (reposOrSpreads != null) foreach (var s in reposOrSpreads) allRepoOis+= s; string cacheKey = "GenerateRollingBondForwardSeries" + tickers.ToString() + start.ToString() + end.ToString() + tenor + countryCode + measure + holidayCode + bOIS.ToString() + optExDivDays.ToString() + bSpreadOrRepo.ToString() + allRepoOis; if (TimeSeriesCache.ContainsKey(cacheKey)) { return TimeSeriesCache[cacheKey]; } #endregion #region Checks if (start >end) throw new ArgumentException("#Error: start date cannot be after end date"); #endregion var settings = new List<Tuple<string/*moniker*/, string/*tickercol*/, string/*pricecol*/>>(); // Get Configs foreach (var ticker in tickers) { if (ticker.Contains('.')) // it's a CTD series! { settings.Add(MonikerConfigs.getCTDMoniker(countryCode,ticker,client)); } else if (ticker.ToLower().Contains("ct")) { settings.Add(MonikerConfigs.getCTMoniker(countryCode, ticker, client)); } else { throw new ArgumentException("#Error: Invalid ticker found - " + ticker); } } #region DATA bool bHaveEverything = true; var configs = new SpreadTimeSeriesConfigs(countryCode, !bOIS); var spreadConventions = new SpreadConventions(configs); var dateRange = TimeSeriesUtilities.GetAllDatesInDateime(start, end, configs.calendarcode, client, "1b"); // Force daily sampling var fwdDates = dateRange.Select(dte => dte.AddTenor(Tenor.FromString(tenor))).ToList(); var holidays = TimeSeriesUtilities.GetHolidays(holidayCode,client); // Parcel input repo rates or spreads Series<DateTime, double> inputSrs = null; if (reposOrSpreads != null ) if (reposOrSpreads.Count != dateRange.Count && reposOrSpreads.Count != 1) { throw new ArgumentException("#Error: number of spreads or rates do not much the days in range"); } else { var sb = new SeriesBuilder<DateTime, double>(); double scale_factor = bSpreadOrRepo ? 10000 : 1; for (int i = 0; i < dateRange.Count; ++i) { if (reposOrSpreads.Count == 1) { sb.Add(dateRange[i], reposOrSpreads[0]/scale_factor); } else { sb.Add(dateRange[i], reposOrSpreads[i]/scale_factor); } } inputSrs = sb.Series; } // Swap Curves var curves = TimeSeriesUtilities.GetCurvesFromCarbon(dateRange, configs.ccy, client); if (curves == null) bHaveEverything = false; // Prices and tickers from ad-hoc persistence var frames = new Dictionary<string, Frame<DateTime, string>>(); for (int i = 0; i < tickers.Count; ++i) { var setting = settings[i]; var ticker = tickers[i]; var moniker = setting.Item1; var tickercol = setting.Item2; var pricecol = setting.Item3; var tmp_df = client.GetFrameAsync<DateTime>(moniker, "AdHocData").Result; if (tmp_df == null) { bHaveEverything = false; } else { // Extract price and ticker cols only var colsOfInterest = new Dictionary<string, Series<DateTime,object>>(); // Filter data to contain date range tmp_df = tmp_df.Where(kvp => (kvp.Key >= start) && (kvp.Key <= end)); colsOfInterest["price"] = tmp_df.GetColumn<object>(pricecol); colsOfInterest["isin"] = tmp_df.GetColumn<object>(tickercol); frames[ticker] = Frame.FromColumns(colsOfInterest); } } //Bond static var bondIsinSet = new HashSet<string>(); foreach(var kvp in frames) { var ticker = kvp.Key; var data = kvp.Value; var isins = data.GetColumn<string>("isin").Values; //isins.Select(isin => bondIsinSet.Add(isin)); foreach (string isin in isins) { bondIsinSet.Add(isin); } } var bondStatics = client.BulkGetBondStaticData( bondIsinSet.ToList()); if (curves == null || bondStatics == null ) return null; #endregion #region RepoRegion Series<DateTime, double> repoSrs = null; if (reposOrSpreads == null) { // Load default repo-OIS Spread double[,] repoRates = client.GetRepoRateFromSavedRepoOisSpread(BondAnalytics.CountryMappings[countryCode], dateRange, fwdDates, holidays); // Break out if no data if (repoRates == null) return null; var repoSrsBlder = new SeriesBuilder<DateTime, double>(); for (int i = 0; i < dateRange.Count; ++i) { repoSrsBlder.Add(dateRange[i], repoRates[i, 1]); } repoSrs = repoSrsBlder.Series; } else { // User-defined repo-OIS Spread or repo rates if (bSpreadOrRepo) { //Inputs are spreads double[,] repoRates = client.CalcRepoRatesFromOisSpread(inputSrs, BondAnalytics.CountryMappings[countryCode], dateRange, fwdDates, holidays); // Break out if no data if (repoRates == null) return null; var repoSrsBlder = new SeriesBuilder<DateTime, double>(); for (int i = 0; i < dateRange.Count; ++i) { repoSrsBlder.Add(dateRange[i], repoRates[i, 1]); } repoSrs = repoSrsBlder.Series; } else { //Inputs are repo rates repoSrs = inputSrs; } } #endregion // Calculation loop var forecastCurves = curves.Item1; var discountCurves = curves.Item2; // Overwrite forecast curve if we want OIS if (bOIS) { forecastCurves = discountCurves; } var outputs = new Dictionary<string, Series<DateTime, double>> (); foreach (string ticker in tickers) { var sb = new SeriesBuilder<DateTime, double>(); var df = frames[ticker]; var idx = df.RowKeys; var isins = df.GetColumn<string>("isin"); var prices = df.GetColumn<double>("price"); foreach (var dte in idx) { var tryisin = isins.TryGet(dte); var tryprice = prices.TryGet(dte); var tryrepo = repoSrs.TryGet(dte); if (!tryprice.HasValue || !tryisin.HasValue || !tryrepo.HasValue) continue; var isin = tryisin.Value; var price = tryprice.Value; var repo = tryrepo.Value; var fwddate = dte.AddTenor(Tenor.FromString(tenor)); var bondstatic = bondStatics[isin]; double value = double.NaN; if (measure == "Repo") { value = repo; } else { var fwdprice = BondAnalytics.CalcBondFwd(configs.country, dte, price, bondstatic.EffectiveDate, bondstatic.FirstCouponDate, bondstatic.MaturityDate, bondstatic.Coupon, bondstatic.CpnFreq, fwddate, repo, optExDivDays)[0]; if (measure == "Price") { value = fwdprice; } else { value = MeasureCalculator.calcFwdBondMeasure(measure, bondstatic, fwdprice, dte, fwddate, spreadConventions, forecastCurves, discountCurves, holidays); } } sb.Add(dte,value); } outputs[ticker] = sb.Series; } var consolidatedMeasures = Frame.FromColumns(outputs); // Cache results TimeSeriesCache[cacheKey] = consolidatedMeasures; return consolidatedMeasures; }
public static double Calculate(CalcMeasure Measure, DateTime settle, double price, Bond bondStatic, BondAnalytics.Country eCountry, DateTime asOfDate, List<DateTime> hols) { // byran lim config coded in symmetry.net. we use this until we have a centralised place for the curve static // different country has different setting of using forcast and discount curve bool bForecastCurve; switch (eCountry) { case BondAnalytics.Country.DE: bForecastCurve = false; break; default: bForecastCurve = true; break; } var fcstCfg = new SpreadTimeSeriesConfigs(eCountry.ToString(), bForecastCurve); int trial = 0; DiscountCurve forCurve = null; DiscountCurve disCurve = null; if (discountCurveCache.ContainsKey(fcstCfg.SymForecastCurveName)) { var forTup = discountCurveCache[fcstCfg.SymForecastCurveName].FirstOrDefault(i => i.Item1 == asOfDate); if (forTup != null) forCurve = forTup.Item2; } if (discountCurveCache.ContainsKey(fcstCfg.SymDiscountCurveName)) { var disTup = discountCurveCache[fcstCfg.SymDiscountCurveName].FirstOrDefault(i => i.Item1 == asOfDate); if (disTup != null) disCurve = disTup.Item2; } if (forCurve == null || disCurve == null) { while (trial < 3) { try { forCurve = SymmetryDataModel.GetDiscountCurve(fcstCfg.SymForecastCurveName, asOfDate); disCurve = SymmetryDataModel.GetDiscountCurve(fcstCfg.SymDiscountCurveName, asOfDate); trial = 5; break; } catch (Exception) { trial++; continue; } } if (forCurve != null && disCurve != null) { var forTup = new Tuple<DateTime, DiscountCurve>(asOfDate, forCurve); var disTup = new Tuple<DateTime, DiscountCurve>(asOfDate, disCurve); if (discountCurveCache.ContainsKey(fcstCfg.SymForecastCurveName)) discountCurveCache[fcstCfg.SymForecastCurveName].Add(forTup); else discountCurveCache[fcstCfg.SymForecastCurveName] = new[] {forTup}.ToList(); if (discountCurveCache.ContainsKey(fcstCfg.SymDiscountCurveName)) discountCurveCache[fcstCfg.SymDiscountCurveName].Add(disTup); else discountCurveCache[fcstCfg.SymDiscountCurveName] = new[] { disTup }.ToList(); } } if (forCurve == null || disCurve == null) return double.NaN; DateTime effectiveDate = bondStatic.EffectiveDate ?? DateTime.MinValue; DateTime maturity = bondStatic.Maturity ?? DateTime.MinValue; DateTime firstCpnDate = bondStatic.FirstCouponDate ?? DateTime.MinValue; double coupon = bondStatic.Coupon; long bondCpnFreq = fcstCfg.bndCouponFreq; bool bOIS = !fcstCfg.bForecastCurve; BondAnalytics.DayCountType dct = fcstCfg.dct; long swapFixedFreq = fcstCfg.swpfixfreq; long swapFloatFreq = fcstCfg.swpfloatfreq; return CalcMeasure(Measure, price, eCountry, settle, disCurve, forCurve, effectiveDate, maturity, firstCpnDate, coupon, bondCpnFreq, bOIS, dct, swapFixedFreq, swapFloatFreq, hols); }
//--------------------------------------------------------------------------------------------------------- //[Test] public void SpreadSeries() //--------------------------------------------------------------------------------------------------------- { // Params. DateTime start = new DateTime(2016, 2, 28); DateTime end = new DateTime(2016, 3, 18); List<String> tickers = new List<string>() { "TUH6", "TUM6", "TUU6","TU.Front" }; // Default Configs SpreadTimeSeriesConfigs config = new SpreadTimeSeriesConfigs("US"); string forecastCurveName = config.forecastCurveName; string discountCurveName = config.discountCurveName; string ccy = config.ccy; string calendarcode = config.calendarcode; string futuresStaticMoniker = config.futuresStaticMoniker; string carbonEnv = config.carbonEnv; BondAnalytics.Country country = config.country; BondAnalytics.DayCountType dct = config.dct; long swpfixfreq = config.swpfixfreq; long swpfloatfreq = config.swpfloatfreq; long bndCouponFreq = config.bndCouponFreq; int BlendIndex = config.BlendIndex; bool bForecastCurve = true; // Bond Static Dictionary<string, TimeSeriesBondStatic> bondstatic = new Dictionary<string, TimeSeriesBondStatic>() { {"US912828G799", new TimeSeriesBondStatic("US912828G799", new DateTime(2017,12,15), new DateTime(2015,12,15),DateTime.MinValue,1.0)}, {"US912828J686", new TimeSeriesBondStatic("US912828J686", new DateTime(2018,03,15), new DateTime(2015,03,16),DateTime.MinValue,1.0)}, {"US912828VK31", new TimeSeriesBondStatic("US912828VK31", new DateTime(2018,06,30), new DateTime(2015,07,01),DateTime.MinValue,1.0)} }; //Spread Static SpreadConventions spreadconventions = new SpreadConventions(country,dct,swpfixfreq,swpfloatfreq,bndCouponFreq,bForecastCurve,BlendIndex); //Swap. var swapBuilder = new SwapCurveSeriesBuilder(start, end, forecastCurveName, discountCurveName, ccy, mCarbonClient, calendarcode); swapBuilder.buildAllCurves(); //swapBuilder.DiscountCurves[new DateTime(2016, 03, 18)].PrintDump(); swapBuilder.ForecastCurves[new DateTime(2016, 03, 18)].PrintDump(); // Futures. var seriesBuilder = new FuturesSeriesBuilder(tickers, start, end, calendarcode, futuresStaticMoniker, mCarbonClient,carbonEnv); // Spread Series. Dictionary<string, double> testVals = new Dictionary<string, double>() { {"Price", 109.1015625}, {"Yield", 0.009007303 * 10000}, {"TrueSpread",6.7788999}, {"MMS", 0.009689381* 10000}, {"Spread", 6.820787365} }; Dictionary<string /*metric*/, SpreadSeriesBuilder /*builder*/> spreadbuildermap = new Dictionary<string, SpreadSeriesBuilder>(); SpreadSeriesBuilder output = null; foreach (var metric in testVals.Keys.ToList()) { output = new SpreadSeriesBuilder(metric, seriesBuilder, swapBuilder, bondstatic, spreadconventions); Console.WriteLine(metric + " :"); output.SpreadValues.Print(); var row = output.SpreadValues.GetRow<double>(new DateTime(2016, 03, 18)); double TUM6 = row.Get("TUM6"); double error = Math.Abs(TUM6 - testVals[metric]); Assert.LessOrEqual(error, Math.Pow(10, -3) * 5, "Error found in " + metric + ", test: " + testVals[metric].ToString() + ", actual: " + TUM6.ToString()); // 0.005bps Console.WriteLine("Error :{0}", error); } // Now let's try to index 1 Mar if (output != null) { var spreadVals = output.SpreadValues; var myList = spreadVals.RowKeys.ToList(); Console.WriteLine("Key List: {0}", myList); // Try on individual series Dictionary<string/*colname*/, Series<DateTime,object>/*actualsrs*/> tmp = new Dictionary<string, Series<DateTime, object>>(); foreach (string colname in spreadVals.ColumnKeys) { tmp[colname] = spreadVals.Columns[colname].FillMissing("#N/A"); } spreadVals = Frame.FromColumns(tmp); //spreadVals.FillMissing("#N/A").Print(); var row = spreadVals.GetRow<string>(new DateTime(2016, 03, 01)); Console.WriteLine("Row values"); row.Print(); } }
//--------------------------------------------------------------------------------------------------------- //[Test] public void SpreadSeriesToArrayTests() //--------------------------------------------------------------------------------------------------------- { // Params. DateTime start = new DateTime(2016, 2, 28); DateTime end = new DateTime(2016, 3, 18); List<String> tickers = new List<string>() { "TUH6", "TUM6", "TUU6" }; string samplingFreq = "1b"; // Default Configs SpreadTimeSeriesConfigs config = new SpreadTimeSeriesConfigs("US"); string forecastCurveName = config.forecastCurveName; string discountCurveName = config.discountCurveName; string ccy = config.ccy; string calendarcode = config.calendarcode; string futuresStaticMoniker = config.futuresStaticMoniker; string carbonEnv = config.carbonEnv; BondAnalytics.Country country = config.country; BondAnalytics.DayCountType dct = config.dct; long swpfixfreq = config.swpfixfreq; long swpfloatfreq = config.swpfloatfreq; long bndCouponFreq = config.bndCouponFreq; int BlendIndex = config.BlendIndex; bool bForecastCurve = true; // Bond Static Dictionary<string, TimeSeriesBondStatic> bondstatic = new Dictionary<string, TimeSeriesBondStatic>() { {"US912828G799", new TimeSeriesBondStatic("US912828G799", new DateTime(2017,12,15), new DateTime(2015,12,15),DateTime.MinValue,1.0)}, {"US912828J686", new TimeSeriesBondStatic("US912828J686", new DateTime(2018,03,15), new DateTime(2015,03,16),DateTime.MinValue,1.0)}, {"US912828VK31", new TimeSeriesBondStatic("US912828VK31", new DateTime(2018,06,30), new DateTime(2015,07,01),DateTime.MinValue,1.0)} }; //Spread Static SpreadConventions spreadconventions = new SpreadConventions(country, dct, swpfixfreq, swpfloatfreq, bndCouponFreq, bForecastCurve, BlendIndex); // Dates var holidays = new List<DateTime>(); var dateRange = new List<DateTime>(); using (var client = new CarbonClient(carbonEnv)) { // Holidays if (calendarcode != "") { var nodaHols = client.GetCalendarAsync(calendarcode).Result.Dates; foreach (LocalDate hol in nodaHols) { holidays.Add(hol.ToDateTime()); } } // DateRange DateTime startDate = start.AddTenor(Tenor.FromString("1b"), "").AddTenor(Tenor.FromString("-1b"), ""); DateTime nextDate = end.AddTenor(Tenor.FromString("1b"), "").AddTenor(Tenor.FromString("-1b"), ""); //TimeSpan EOD = new TimeSpan(23, 59, 59); /*if (startDate > nextDate) { return "Error! Bad Inputs!"; }*/ var tenor = Tenor.FromString("-" + samplingFreq); while (nextDate >= startDate) { dateRange.Add(nextDate); nextDate = BondAnalytics.PrevBusDay(nextDate.AddTenor(tenor, ""), holidays); } } //Swap. var swapBuilder = new SwapCurveSeriesBuilder(dateRange, forecastCurveName, discountCurveName, ccy, holidays, mCarbonClient); swapBuilder.buildAllCurves(); // Futures. var seriesBuilder = new FuturesSeriesBuilder(tickers, dateRange, holidays, futuresStaticMoniker, mCarbonClient, carbonEnv); // Spread Series. Dictionary<string, double> testVals = new Dictionary<string, double>() { {"Price", 109.1015625}, {"Yield", 0.009007303 * 10000}, {"TrueSpread",6.7788999}, {"MMS", 0.009689381* 10000}, {"Spread", 6.820787365} }; Dictionary<string /*metric*/, SpreadSeriesBuilder /*builder*/> spreadbuildermap = new Dictionary<string, SpreadSeriesBuilder>(); SpreadSeriesBuilder output = null; foreach (var metric in testVals.Keys.ToList()) { output = new SpreadSeriesBuilder(metric, seriesBuilder, swapBuilder, bondstatic, spreadconventions); Console.WriteLine(metric + " :"); output.SpreadValues.Print(); var row = output.SpreadValues.GetRow<double>(new DateTime(2016, 03, 18)); double TUM6 = row.Get("TUM6"); double error = Math.Abs(TUM6 - testVals[metric]); Assert.LessOrEqual(error, Math.Pow(10, -3) * 5, "Error found in " + metric + ", test: " + testVals[metric].ToString() + ", actual: " + TUM6.ToString()); // 0.005bps Console.WriteLine("Error :{0}", error); } // Now let's look at converting this to an array if (output != null) { var objArr = output.getSpreadAsObjArr(); } }
//------------------------------------------------------------------------------------------------ public SpreadConventions(SpreadTimeSeriesConfigs configs) //------------------------------------------------------------------------------------------------ { Country = configs.country; DayCount = configs.dct; SwapFixedFrequency = configs.swpfixfreq; SwapFloatFrequency = configs.swpfloatfreq; BondCouponFrequency = configs.bndCouponFreq; bForecastCurve = configs.bForecastCurve; BlendIndex = configs.BlendIndex; }
// GBP is only currently setup to get spot ASW terms. Will move on to futures only if it becomes necessary. //---------------------------------------------------------------------------------------- public static void run(DateTime start, DateTime end, string samplingFreq, string outputFolderPath) //---------------------------------------------------------------------------------------- { Console.WriteLine("Commencing GBP Run"); // INIT BondAnalytics.Country eCountry = BondAnalytics.Country.UK; List<string> SwapMeasures = new List<string>() { "MMS", "Spread", "TrueSpread" }; // List<string> BondMeasures = new List<string>() { "Price", "Yield"}; // FIND ALL SECURITIES. DateTime fDate = DateTime.Today.AddDays(30); List<string> rawmlptickers = DataHandler.GetAllSecurities("UKT", "Term", fDate); // Remove everything with AuC in it List<string> mlptickers = new List<string>(); foreach (string tic in rawmlptickers) { if (!tic.ToLower().Contains("auc")) { mlptickers.Add(tic); } } Console.WriteLine(); Console.WriteLine("Found UKT tickers"); foreach (string tic in mlptickers) { Console.WriteLine(tic); } // GET THE RIGHT STATICS var fcstCfg = new SpreadTimeSeriesConfigs("UK", bForecastCurve:true); var discCfg = new SpreadTimeSeriesConfigs("UK", bForecastCurve:false); var bondStaticMap = new Dictionary<string, BondStatic>(); foreach (string tic in mlptickers) { bondStaticMap[tic] = DataHandler.GetBondStatic(tic); } // GET DATES. Console.WriteLine(); Console.WriteLine("Setting Dates"); var res = DataHandler.GetCarbonClient().GetCalendarAsync("GBLO"); res.Wait(); var holsInLocalDate = res.Result.Dates; List<DateTime> hols = holsInLocalDate.Select(d => d.ToDateTime()).ToList(); DateTime startDate = start.AddTenor(Tenor.FromString("-1b"), "").AddTenor(Tenor.FromString("1b"), ""); DateTime nextDate = end.AddTenor(Tenor.FromString("1b"), "").AddTenor(Tenor.FromString("-1b"), ""); List<DateTime> dateRange = new List<DateTime>(); if (startDate > nextDate) { throw new ArgumentException("Start date cannot be after end date"); } var tenor = Tenor.FromString("-" + samplingFreq); while (nextDate >= startDate) { dateRange.Add(nextDate); nextDate = BondAnalytics.PrevBusDay(nextDate.AddTenor(tenor, ""), hols); } // GET ALL PRICES. Console.WriteLine(); Console.WriteLine("Setting Prices"); var result = DataHandler.GetBondPricesAsync(mlptickers, dateRange, eCountry); Frame<DateTime, string> bondFrame; if (result.IsCompleted) bondFrame = result.Result; else throw new ArgumentNullException("No bond prices came back!"); bondFrame.Print(); // GET ALL SWAP CURVES. Console.WriteLine(); Console.WriteLine("Getting Swap Cruves"); var fCurves = DataHandler.getDiscountCurvesInDateRangeAsync(fcstCfg.SymForecastCurveName, dateRange).Result; var dCurves = DataHandler.getDiscountCurvesInDateRangeAsync(fcstCfg.SymDiscountCurveName, dateRange).Result; // GET ALL MEASURES. var output = new Dictionary<string/*measure*/,Frame<DateTime,string>>(); var configs = new List<SpreadTimeSeriesConfigs>(){fcstCfg, discCfg}; // Swap measures foreach (string measure in SwapMeasures) { Console.WriteLine("Calculating {0}", measure); foreach (var config in configs) { Console.WriteLine("Forecast curve ? {0}", config); var valueMap = new Dictionary<string/*id*/, Series<DateTime,double>>(); bool bForecast = config.bForecastCurve; foreach (var ticker in mlptickers) { var srs = bondFrame.Columns[ticker]; var bndStatic = bondStaticMap[ticker]; var sb = new SeriesBuilder<DateTime, double>(); foreach (DateTime dte in srs.Keys) { // Skip if we're missing data somewhere if (!(dCurves.ContainsKey(dte) && srs.ContainsKey(dte))) continue; if (config.bForecastCurve && !fCurves.ContainsKey(dte)) // only press on if we need forecast curves continue; // Proceed. DiscountCurve discount = dCurves[dte]; DiscountCurve forecast = bForecast ? fCurves[dte] : discount; DateTime settle = BondAnalytics.NextBusDay(dte.Date.AddDays(config.bondSettleLag), hols); double price = srs.TryGetAs<double>(dte).OrDefault(double.NaN); if (double.IsNaN(price)) continue; double value = BondASWCalc.calcMeasure( measure, price, eCountry, dte, settle, discount, forecast, bndStatic, config, hols); if (double.IsNaN(value)) continue; sb.Add(dte, value); } valueMap[ticker] = sb.Series; Console.WriteLine("Completed for {0}", ticker); valueMap[ticker].Print(); } var df = Frame.FromColumns(valueMap); string prefix = bForecast ? "L" : "OIS"; output[prefix + measure] = df; Console.WriteLine(); Console.WriteLine("Swap Measures complete."); } } //Bond Measure foreach(string measure in BondMeasures) { var config = fcstCfg; Console.WriteLine("Calculating {0}", measure); var valueMap = new Dictionary<string/*id*/, Series<DateTime, double>>(); foreach (var ticker in mlptickers) { var srs = bondFrame.Columns[ticker]; var bndStatic = bondStaticMap[ticker]; var sb = new SeriesBuilder<DateTime, double>(); foreach (DateTime dte in srs.Keys) { // Skip if we're missing data somewhere if (!srs.ContainsKey(dte)) continue; // Proceed. DateTime settle = BondAnalytics.NextBusDay(dte.Date.AddDays(config.bondSettleLag), hols); double price = srs.TryGetAs<double>(dte).OrDefault(double.NaN); if (double.IsNaN(price)) continue; double value = BondASWCalc.calcMeasure(measure, price, eCountry, dte, settle, null, null, bndStatic, config, hols); if (double.IsNaN(value)) continue; sb.Add(dte, value); } valueMap[ticker] = sb.Series; Console.WriteLine("Completed for {0}", ticker); valueMap[ticker].Print(); } var df = Frame.FromColumns(valueMap); output[ measure] = df; Console.WriteLine(); Console.WriteLine("Bond Measures complete."); } // SAVE OUTPUT. Console.WriteLine("Saving CSV Files"); var renameMap = new Dictionary<string, string>() { {"LTrueSpread", "LOAS"}, {"OISTrueSpread", "OISOAS"}, {"LSpread", "LASW"}, {"OISSpread", "OISASW"} }; foreach (var kvp in output) { var measure = kvp.Key; var df = convertFrameToString(kvp.Value); if (renameMap.ContainsKey(measure)) { measure = renameMap[measure]; } var fileName = outputFolderPath + "\\" + eCountry.ToString()+ "_" + measure + ".csv"; df = df.FillMissing(""); df.SaveCsv(fileName,includeRowKeys:true); Console.WriteLine("Saved {0}", fileName); } }
//---------------------------------------------------------------------------------------- public static double calcMeasure( string Measure, double price, BondAnalytics.Country eCountry, DateTime asof, DateTime settle, DiscountCurve discCurve, DiscountCurve fcstCurve, BondStatic bondStatic, SpreadTimeSeriesConfigs configs, List<DateTime> hols) //---------------------------------------------------------------------------------------- { DateTime effectiveDate = DateTime.FromOADate( bondStatic.EffectiveDate); DateTime maturity = DateTime.FromOADate( bondStatic.Maturity); DateTime firstCpnDate = DateTime.FromOADate(bondStatic.FirstCoupon); double coupon = bondStatic.Coupon; long bondCpnFreq = configs.bndCouponFreq; bool bOIS = !configs.bForecastCurve; BondAnalytics.DayCountType dct = configs.dct; long fixedFreq = configs.swpfixfreq; long floatFreq = configs.swpfloatfreq; double output = double.NaN; switch (Measure) { case "Price": { output= price; break; } case "Yield": { output= 10000.0 * BondAnalytics.SolveYield(eCountry, settle, price, effectiveDate, firstCpnDate, maturity, coupon, bondCpnFreq)[0]; break; } case "TrueSpread": { DateTime[] dfDates = (bOIS ? discCurve.AsTuples().Select(x => x.Item1).ToArray() : fcstCurve.AsTuples().Select(x => x.Item1).ToArray()); double[] dfs = (bOIS ? discCurve.AsTuples().Select(x => x.Item2).ToArray() : fcstCurve.AsTuples().Select(x => x.Item2).ToArray()); // Check. Sometimes (holidays) the first date on the curve can be repeated, which will cause an exception in the pricer. if (dfDates[0] == dfDates[1]) { dfDates = dfDates.GetSubArray(1, dfDates.GetLength(0)).ToArray(); dfs = dfs.GetSubArray(1, dfs.GetLength(0)).ToArray(); } output= -10000.0 * BondAnalytics.SolveZSpread(eCountry, settle, price, effectiveDate, firstCpnDate, maturity, coupon, bondCpnFreq, dfDates, dfs,hols ); break; } case "MMS": { DateTime[] discDfDates = discCurve.AsTuples().Select(x => x.Item1).ToArray(); DateTime[] fcstDfDates = (bOIS ? discCurve.AsTuples().Select(x => x.Item1).ToArray() : fcstCurve.AsTuples().Select(x => x.Item1).ToArray()); double[] discDfs = discCurve.AsTuples().Select(x => x.Item2).ToArray(); double[] fcstDfs =(bOIS ? discCurve.AsTuples().Select(x => x.Item2).ToArray() : fcstCurve.AsTuples().Select(x => x.Item2).ToArray()); double mms = BondAnalytics.CalcMMS(settle, maturity, dct, fixedFreq, floatFreq, discDfDates, discDfs, fcstDfDates, fcstDfs, hols, null, null, null, null, null, firstCpnDate, (bOIS ? 5 : 0)); output= 10000.0 * mms; break; } case "Spread": { DateTime[] discDfDates = discCurve.AsTuples().Select(x => x.Item1).ToArray(); DateTime[] fcstDfDates = (bOIS ? discCurve.AsTuples().Select(x => x.Item1).ToArray() : fcstCurve.AsTuples().Select(x => x.Item1).ToArray()); double[] discDfs = discCurve.AsTuples().Select(x => x.Item2).ToArray(); double[] fcstDfs = (bOIS ? discCurve.AsTuples().Select(x => x.Item2).ToArray() : fcstCurve.AsTuples().Select(x => x.Item2).ToArray()); double mms = BondAnalytics.CalcMMS(settle, maturity, dct, fixedFreq, floatFreq, discDfDates, discDfs, fcstDfDates, fcstDfs, hols, null, null, null, null, null, firstCpnDate, (bOIS ? 5 : 0)); double yield = BondAnalytics.SolveYield(eCountry,settle ,price, effectiveDate, firstCpnDate, maturity, coupon, bondCpnFreq)[0]; output= 10000.0 * (mms - yield); break; } } return output; }
//---------------------------------------------------------------------------------------- public static Frame<DateTime, string> SymBondFwdTimeSeries( string[] BondId, DateTime StartDate, DateTime EndDate, String TenorString, DateTime FwdDate, object[,] SpreadOrPrice, String Measure, Boolean bOIS, Boolean bSpread, List<DateTime> Holidays) //---------------------------------------------------------------------------------------- { bool bHaveEverything = true; CarbonClient cbnClient = DataHandler.GetCarbonClient(); try { int nBonds = BondId.GetLength(0); // Holidays can be given as holiday code (string) or as array of dates. // Get Bond static data #region Bond Static Data object o = null; DateTime[] maturity = new DateTime[nBonds], effectiveDate = new DateTime[nBonds], firstCpnDate = new DateTime[nBonds]; double[] coupon = new double[nBonds]; bool bEUR = false; long bondCpnFreq = 6; BondAnalytics.Country eCountry = BondAnalytics.Country.US, prevCountry = BondAnalytics.Country.US; for (int i = 0; i < nBonds; i++) { var lid = ((String) BondId[i]).Trim().ToLower(); BondStatic bndStatic = DataHandler.GetBondStatic(lid); maturity[i] = DateTime.FromOADate(bndStatic.Maturity); effectiveDate[i] = DateTime.FromOADate(bndStatic.EffectiveDate); firstCpnDate[i] = DateTime.FromOADate(bndStatic.FirstCoupon); coupon[i] = bndStatic.Coupon; BondAnalytics.CountryMappings.TryGetValue(((String) BondId[i]).Substring(0, 2), out eCountry); if (i > 0 && eCountry != prevCountry) { throw new ArgumentException("#Error! All bonds must be same country!"); } prevCountry = eCountry; } // Let's use spreadseries config string sCountry = ((String) BondId[0]).Substring(0, 2); // since all countries are the same var fcstConfig = new SpreadTimeSeriesConfigs(sCountry, bForecastCurve: true); var discConfig = new SpreadTimeSeriesConfigs(sCountry, bForecastCurve: false); bEUR = (eCountry == BondAnalytics.Country.DE || eCountry == BondAnalytics.Country.FR || eCountry == BondAnalytics.Country.IT || eCountry == BondAnalytics.Country.ES || eCountry == BondAnalytics.Country.BE ? true : false); bondCpnFreq = fcstConfig.bndCouponFreq; //(eCountry == BondAnalytics.Country.US || eCountry == BondAnalytics.Country.UK || eCountry == BondAnalytics.Country.JP || eCountry == BondAnalytics.Country.CA ? 6 : 12); #endregion // Build timeseries calendar #region DateSeries List<DateTime> dateSeries = new List<DateTime>(); // Ensure start and end date are good business days. Move dates forward then back to minimise chance of future dates in series. DateTime startDate = StartDate.AddTenor(Tenor.FromString("1b"), "") .AddTenor(Tenor.FromString("-1b"), ""); DateTime nextDate = EndDate.AddTenor(Tenor.FromString("1b"), "").AddTenor(Tenor.FromString("-1b"), ""); if (startDate > nextDate) { throw new ArgumentException("nextDate must be after start date"); } var tenor = Tenor.FromString("-" + TenorString); while (nextDate >= startDate) { dateSeries.Add(nextDate); nextDate = BondAnalytics.PrevBusDay(nextDate.AddTenor(tenor, ""), Holidays); } int M = dateSeries.Count(); // Keep a track of pricing errors so that a failure for one bond / date does not have to cause a failure of the whole function. Boolean[,] bPricingCheck = new Boolean[M, nBonds]; #endregion // Need discount curve before calculating repo series. #region InterestCurves String discCurveName = discConfig.SymDiscountCurveName; String fcstCurveName = fcstConfig.SymForecastCurveName; String ccy = fcstConfig.ccy; // Convert DiscountCurve Object to tuples. var DiscCurves = new SortedDictionary<DateTime, Tuple<DateTime, double>[]>(); var FcstCurves = new SortedDictionary<DateTime, Tuple<DateTime, double>[]>(); // DISC. SortedDictionary<DateTime, DiscountCurve> DiscCurvesRaw = DataHandler.getDiscountCurvesInDateRange(discCurveName, dateSeries); foreach (var dte in DiscCurvesRaw.Keys) { DiscCurves[dte] = DiscCurvesRaw[dte].AsTuples(); } // FCST. if (!bOIS && (Measure != "Repo" && Measure != "Yield" && Measure != "Price")) // only get fcst curves here { SortedDictionary<DateTime, DiscountCurve> FcstCurvesRaw = DataHandler.getDiscountCurvesInDateRange(fcstCurveName, dateSeries); foreach (var dte in FcstCurvesRaw.Keys) { FcstCurves[dte] = FcstCurvesRaw[dte].AsTuples(); } } #endregion InterestCurves // Get repo spread. Three cases: default is 20 day moving average of GC-OIS, alternatives are input flat rate or interpolation from input table. #region RepoHistory double[,] fwdPrices = new double[M, nBonds]; double[,] repoSpread = new double[M, nBonds]; // Two column array - dates / spreads. Linear interpolation in date. if ((SpreadOrPrice.GetLength(1) == 2 || SpreadOrPrice.GetLength(1) == nBonds + 1) && SpreadOrPrice.GetLength(0) > 1) { int nRepos = SpreadOrPrice.GetLength(0); int nCols = SpreadOrPrice.GetLength(1); DateTime[] rDates = new DateTime[nRepos]; double[,] rSpreads = new double[nRepos, nBonds]; for (int j = 0; j < nBonds; j++) { for (int i = 0; i < nRepos; i++) { if (SpreadOrPrice[i, 0] is double) { rDates[i] = DateTime.FromOADate((double)SpreadOrPrice[i, 0]).Date; if (nCols > 1 + j && SpreadOrPrice[i, 1 + j] is double) rSpreads[i, j] = (double)SpreadOrPrice[i, 1 + j]; else if (1 + j >= nCols) { rSpreads[i, j] = rSpreads[i, j - 1]; } else { // Spread override is not a double, pricing fails. Use -1e8 as an error warning. rSpreads[i, j] = -1e8; } } else { rDates[i] = DateTime.MinValue; } } } // Not very efficient, but need to identify matching dates when not necessarily in order because of input errors. if (bSpread) { int k; for (int i = 0; i < M; i++) { for (k = 0; k < nRepos; k++) { if (rDates[k].Date == dateSeries[i].Date) { for (int j = 0; j < nBonds; j++) { if (rSpreads[i, j] < -1e7) bPricingCheck[i, j] = true; else repoSpread[i, j] = rSpreads[i, j] / 10000.0; } break; } } if (k == nRepos) { for (int j = 0; j < nBonds; j++) { bPricingCheck[i, j] = true; } } } } else { int k; for (int i = 0; i < M; i++) { for (k = 0; k < nRepos; k++) { if (rDates[k].Date == dateSeries[i].Date) { for (int j = 0; j < nBonds; j++) { if (rSpreads[i, j] < -1e7) bPricingCheck[i, j] = true; else fwdPrices[i, j] = rSpreads[i, j]; } break; } } if (k == nRepos) { for (int j = 0; j < nBonds; j++) { bPricingCheck[i, j] = true; } } } } } // Cases: Single entry, double then this is the flat spread. else if (SpreadOrPrice.GetLength(0) == 1 && SpreadOrPrice.GetLength(1) == 1 && SpreadOrPrice[0, 0] is double) { if (bSpread) { for (int j = 0; j < nBonds; j++) { for (int i = 0; i < M; i++) { repoSpread[i, j] = (double)SpreadOrPrice[0, 0] / 10000.0; } } } else { for (int j = 0; j < nBonds; j++) { for (int i = 0; i < M; i++) { fwdPrices[i, j] = (double)SpreadOrPrice[0, 0]; } } } } // Vector, length nBonds else if (((SpreadOrPrice.GetLength(0) == nBonds && SpreadOrPrice.GetLength(1) == 1) || (SpreadOrPrice.GetLength(1) == nBonds && SpreadOrPrice.GetLength(0) == 1)) && SpreadOrPrice[0, 0] is double) { bool bRow = SpreadOrPrice.GetLength(1) == 1; if (bSpread) { for (int j = 0; j < nBonds; j++) { for (int i = 0; i < M; i++) repoSpread[i, j] = (double)(bRow ? SpreadOrPrice[j, 0] : SpreadOrPrice[0, j]) / 10000.0; } } else { for (int j = 0; j < nBonds; j++) { for (int i = 0; i < M; i++) fwdPrices[i, j] = (double)(bRow ? SpreadOrPrice[j, 0] : SpreadOrPrice[0, j]); } } } // Otherwise (empty, or not a double or a two column array). else { bSpread = true; // Repo spread is 20 day moving average of GC - OIS double[] gcHist = new double[M + 19]; double[] oisHist = new double[M + 19]; var opGC = DataHandler.GetRepoRates(eCountry, dateSeries); for (int i = 0; i < M; i++) { // Copy GC values over (we need to extend this later) gcHist[i] = opGC[i]; if (DiscCurves.ContainsKey(dateSeries[i])) { oisHist[i] = Math.Round((1.0 / DiscCurves[dateSeries[i]].ElementAt(1).Item2 - 1.0) * 360.0 / (DiscCurves[dateSeries[i]].ElementAt(1).Item1 - DiscCurves[dateSeries[i]].ElementAt(0).Item1).TotalDays, 5); } else { bHaveEverything = false; } } // Dates prior to dateSeries[0] List<DateTime> prevDates = new List<DateTime>(); for (int i = 0; i < 19; i++) { prevDates.Add(BondAnalytics.PrevBusDay((i == 0 ? dateSeries[M - 1] : prevDates.Last()).AddDays(-1), Holidays)); } // Get old curves var PrevDiscCurves = new SortedDictionary<DateTime, Tuple<DateTime, double>[]>(); var prevDiscCurvesRaw = DataHandler.getDiscountCurvesInDateRange(discCurveName, prevDates); foreach (var kvp in prevDiscCurvesRaw) { PrevDiscCurves[kvp.Key] = kvp.Value.AsTuples(); } // Get old rates double[] oldRepoRates = DataHandler.GetRepoRates(eCountry, prevDates); for (int i = 0; i < 19; i++) { o = oldRepoRates[i]; if (o is double) { gcHist[M + i] = (double)o / 100.0; } else if (i > 0) { gcHist[M + i] = gcHist[M + i]; } else gcHist[M + i] = 0.0; if (PrevDiscCurves.ContainsKey(prevDates[i])) { oisHist[M + i] = Math.Round((1.0 / PrevDiscCurves[prevDates[i]].ElementAt(1).Item2 - 1.0) * 360.0 / (PrevDiscCurves[prevDates[i]].ElementAt(1).Item1 - PrevDiscCurves[prevDates[i]].ElementAt(0).Item1).TotalDays, 5); } else { bHaveEverything = false; } } for (int i = 0; i < M; i++) { for (int j = i; j < i + 20; j++) { repoSpread[i, 0] += (gcHist[j] - oisHist[j]) / 20.0; } } for (int i = 0; i < M; i++) for (int j = 1; j < nBonds; j++) repoSpread[i, j] = repoSpread[i, 0]; } #endregion // Get Price Data (Clean Prices, Repo rates - i.e. term OIS rates + repo spreads) #region GetPricingData List<double[]> priceSeries = new List<double[]>(); List<double[]> repoSeries = new List<double[]>(); // Let's get price data from symmetry service if (bSpread) { for (int i = 0; i < M; i++) { priceSeries.Add(new double[nBonds]); repoSeries.Add(new double[nBonds]); DateTime settleDate = BondAnalytics.NextBusDay(dateSeries[i].Date.AddDays(1), Holidays); if (eCountry != BondAnalytics.Country.US) { settleDate = BondAnalytics.NextBusDay(dateSeries[i].Date.AddDays(2), Holidays); } for (int j = 0; j < nBonds; j++) { if (settleDate.Date > FwdDate.Date) { bPricingCheck[i, j] = true; continue; } var priceMonikerName = ((String)BondId[j]).ToLower().Trim(); var priceMonikerDate = dateSeries[i]; var closeTup = DataHandler.GetCloseSnap(priceMonikerDate, eCountry); var priceMonikerClose = closeTup.Item1; var priceMonikerSource = closeTup.Item2; try { o = DataHandler.GetHistoryPrice(priceMonikerName, priceMonikerDate, priceMonikerClose, priceMonikerSource); } catch { } if (o is double) priceSeries[i][j] = (double)o; else if (dateSeries[i].Date < effectiveDate[j].Date || !DiscCurves.ContainsKey(dateSeries[i])) { // No price information available bPricingCheck[i, j] = true; } else bHaveEverything = false; } double oisSwap = 0.0; if (DiscCurves.ContainsKey(dateSeries[i])) { DateTime[] discDfDates = DiscCurves[dateSeries[i]].Select(x => x.Item1).ToArray(); double[] discDfs = DiscCurves[dateSeries[i]].Select(x => x.Item2).ToArray(); oisSwap = settleDate.Date < FwdDate.Date ? BondAnalytics.CalcMMS(settleDate, FwdDate, BondAnalytics.DayCountType.Act360, 12, 12, discDfDates, discDfs, discDfDates, discDfs, Holidays, null, null, null, null, null, DateTime.MinValue, 5) : 0.0; } for (int j = 0; j < nBonds; j++) { repoSeries[i][j] = oisSwap + repoSpread[i, j]; } } } #endregion // Calculate measures #region CalcMeasures List<double[]> outputSeries = new List<double[]>(); for (int i = 0; i < M; i++) { outputSeries.Add(new double[nBonds + (Measure == "Repo" ? 1 : 0)]); DateTime settleDate = BondAnalytics.NextBusDay(dateSeries[i].Date.AddDays(1), Holidays); if (eCountry != BondAnalytics.Country.US) { settleDate = BondAnalytics.NextBusDay(dateSeries[i].Date.AddDays(2), Holidays); } for (int j = 0; j < nBonds; j++) { // Only attempt to price if there is a chance of success! if (bPricingCheck[i, j]) continue; double fwdPrice = bSpread ? BondAnalytics.CalcBondFwd(eCountry, settleDate, priceSeries[i][j], effectiveDate[j], firstCpnDate[j], maturity[j], coupon[j], bondCpnFreq, FwdDate, repoSeries[i][j])[0] : fwdPrices[i, j]; switch (Measure) { case "Price": { outputSeries[i][j] = fwdPrice; break; } case "Yield": { outputSeries[i][j] = 10000.0 * BondAnalytics.SolveYield(eCountry, FwdDate, fwdPrice, effectiveDate[j], firstCpnDate[j], maturity[j], coupon[j], bondCpnFreq)[0]; break; } case "TrueSpread": { if ((bOIS && DiscCurves.ContainsKey(dateSeries[i])) || FcstCurves.ContainsKey(dateSeries[i])) { DateTime[] dfDates = (bOIS ? DiscCurves[dateSeries[i]].Select(x => x.Item1).ToArray() : FcstCurves[dateSeries[i]].Select(x => x.Item1).ToArray()); double[] dfs = (bOIS ? DiscCurves[dateSeries[i]].Select(x => x.Item2).ToArray() : FcstCurves[dateSeries[i]].Select(x => x.Item2).ToArray()); // Check. Sometimes (holidays) the first date on the curve can be repeated, which will cause an exception in the pricer. if (dfDates[0] == dfDates[1]) { dfDates = dfDates.GetSubArray(1, dfDates.GetLength(0)).ToArray(); dfs = dfs.GetSubArray(1, dfs.GetLength(0)).ToArray(); } outputSeries[i][j] = -10000.0 * BondAnalytics.SolveZSpread(eCountry, FwdDate, fwdPrice, effectiveDate[j], firstCpnDate[j], maturity[j], coupon[j], bondCpnFreq, dfDates, dfs, Holidays); } else { bPricingCheck[i, j] = true; } break; } case "MMS": { if (DiscCurves.ContainsKey(dateSeries[i]) && (bOIS || FcstCurves.ContainsKey(dateSeries[i]))) { DateTime[] discDfDates = DiscCurves[dateSeries[i]].Select(x => x.Item1).ToArray(); DateTime[] fcstDfDates = (bOIS ? discDfDates : FcstCurves[dateSeries[i]].Select(x => x.Item1).ToArray()); double[] discDfs = DiscCurves[dateSeries[i]].Select(x => x.Item2).ToArray(); double[] fcstDfs = (bOIS ? discDfs : FcstCurves[dateSeries[i]].Select(x => x.Item2).ToArray()); BondAnalytics.DayCountType dct = (bOIS ? BondAnalytics.DayCountType.Act360 : BondAnalytics.DayCountType.E30360); int fixedFreq = (bEUR || bOIS ? 12 : 6); int floatFreq = (bOIS ? 12 : (bEUR ? 6 : 3)); double mms = BondAnalytics.CalcMMS(FwdDate, maturity[j], dct, fixedFreq, floatFreq, discDfDates, discDfs, fcstDfDates, fcstDfs, Holidays, null, null, null, null, null, firstCpnDate[j], (bOIS ? 5 : 0)); outputSeries[i][j] = 10000.0 * mms; } else { bPricingCheck[i, j] = true; } break; } case "Spread": { if (DiscCurves.ContainsKey(dateSeries[i]) && (bOIS || FcstCurves.ContainsKey(dateSeries[i]))) { DateTime[] discDfDates = DiscCurves[dateSeries[i]].Select(x => x.Item1).ToArray(); DateTime[] fcstDfDates = (bOIS ? discDfDates : FcstCurves[dateSeries[i]].Select(x => x.Item1).ToArray()); double[] discDfs = DiscCurves[dateSeries[i]].Select(x => x.Item2).ToArray(); double[] fcstDfs = (bOIS ? discDfs : FcstCurves[dateSeries[i]].Select(x => x.Item2).ToArray()); BondAnalytics.DayCountType dct = (bOIS ? BondAnalytics.DayCountType.Act360 : BondAnalytics.DayCountType.E30360); int fixedFreq = (bEUR || bOIS ? 12 : 6); int floatFreq = (bOIS ? 12 : (bEUR ? 6 : 3)); double mms = BondAnalytics.CalcMMS(FwdDate, maturity[j], dct, fixedFreq, floatFreq, discDfDates, discDfs, fcstDfDates, fcstDfs, Holidays, null, null, null, null, null, firstCpnDate[j], (bOIS ? 5 : 0)); double yield = BondAnalytics.SolveYield(eCountry, FwdDate, fwdPrice, effectiveDate[j], firstCpnDate[j], maturity[j], coupon[j], bondCpnFreq)[0]; outputSeries[i][j] = 10000.0 * (mms - yield); } else { bPricingCheck[i, j] = true; } break; } case "Repo": { if (bSpread) { outputSeries[i][j] = 10000 * repoSpread[i, j]; if (j == 0) outputSeries[i][nBonds] = 10000 * (repoSeries[i][0] - repoSpread[i, 0]); } break; } } } } #endregion Dictionary<DateTime, Series<string,double>> output = new Dictionary<DateTime, Series<string, double>>(); for (int i = 0; i < M; i++) { SeriesBuilder<string, double> sb = new SeriesBuilder<string, double>(); for (int j = 0; j < nBonds + (Measure == "Repo" ? 1 : 0); j++) { if (j < nBonds && bPricingCheck[i, j]) { sb.Add(BondId[j], double.NaN); } else { if (j >= nBonds) { sb.Add("Repo", outputSeries[i][j]); } else { sb.Add(BondId[j], outputSeries[i][j]); } } } output[dateSeries[i].Date] = sb.Series; } var result = Frame.FromRows(output); return result; } catch (Exception ex) { Log_.ErrorFormat("Error in BondASWCalc: {0}", ex.Message); throw ex; } }
public void ShouldGetSpreadSeriesConfigs() { var config = new SpreadTimeSeriesConfigs("fr", true); }