public void GetForwardRateTest1() { var target = new ForwardRatesMatrix(_assetExpiries, _assetTenors, _assetData, _id); // Check for valid expiry/tenor pair Period expiry = PeriodHelper.Parse("1y"); Period tenor = PeriodHelper.Parse("4y"); const decimal expected = 6.8227m / 100.0m; decimal actual; try { actual = target.GetAssetPrice(expiry, tenor); Assert.AreEqual(expected, actual); } catch (ArgumentException ex) { Assert.AreEqual("Invalid Expiry/Tenor pair supplied.", ex.Message); } // Check for invalid expiry/tenor pair expiry = PeriodHelper.Parse("6y"); tenor = PeriodHelper.Parse("4y"); try { actual = target.GetAssetPrice(expiry, tenor); Assert.AreEqual(expected, actual); } catch (ArgumentException ex) { Assert.AreEqual("Invalid Expiry/Tenor pair supplied.", ex.Message); } }
/// <summary> /// Generate a new set of full calibration engines for the supplied data. /// Add or overwrite the engine store for the new engine. /// Each engineId will point to a set of engines indexed by swap tenor and option expiry /// Calibration assumes that the format of the grid data is as follows: /// /// + XXXX | lbl0 | lbl1 | ... | lbln + /// | lbl0 | d[0,0] | d[0,1] | ... | d[0,n] | /// | lbl1 | d[1,0] | d[1,1] | ... | d[1,n] | /// | ... | ... | ... | ... | ... | /// + lbln | d[n,0] | d[n,1] | ... | d[n,n] + /// /// </summary> /// <param name="engineHandle">Calibration Engine handle</param> /// <param name="settingsHandle">Calibartion settings handle</param> /// <param name="rawVols">A grid of volatilities (with row/column labels)</param> /// <param name="rawAssets">A grid of asset values</param> /// <param name="optionEx">The ption expiry to index against</param> /// <returns></returns> public string SABRCalibrateModel(string engineHandle, string settingsHandle, object[,] rawVols, object[,] rawAssets, string optionEx) { // Create the asset and volatility data grids SwaptionDataMatrix volatilityGrid = ParseVolatilityInput(rawVols, optionEx); ForwardRatesMatrix assetGrid = ParseAssetInputWithInterpolation(rawAssets); // Retrieve the calibration settings to use with this calibration engine if (!_sabrSettings.ContainsKey(settingsHandle)) { throw new ArgumentException($"Configuration '{settingsHandle}' has not been set up."); } SABRCalibrationSettings settings = _sabrSettings[settingsHandle]; // Generate the CalibrationEngine Id string calibrationEngineId = engineHandle; string optionExpiry = GenerateTenorLabel(optionEx); // Create a new engine holder object SortedDictionary <SABRKey, SABRCalibrationEngine> sabrEngine = BuildEngineCollection(volatilityGrid, assetGrid, settings, calibrationEngineId, optionExpiry); // We have an asset grid (forward rates) with this engine type so we should keep it // for future reference (that is during the lifetime of this session) if (_engineRatesGrid.ContainsKey(engineHandle)) { _engineRatesGrid[engineHandle] = assetGrid; } else { _engineRatesGrid.Add(engineHandle, assetGrid); } // Add the SABREngine to the persistent store if (_sabrEngines.ContainsKey(calibrationEngineId)) { _sabrEngines[calibrationEngineId] = sabrEngine; } else { _sabrEngines.Add(calibrationEngineId, sabrEngine); } return(engineHandle); }
/// <summary> /// Parse the raw assets grid to create the labels and values arrays /// required by a <see cref="SwaptionDataMatrix"/> used to store the data /// for use by the SABR calibration/calulation routines /// This version uses Interpolation to include Swap Tenors that are not /// included as part of the grid but fall within the minimum and maximum tenor /// values of the grid. /// </summary> /// <param name="expiry">An array of expiries.</param> /// <param name="rawAsset">The raw volatility data</param> /// <param name="tenors">An array of tenors.</param> /// <returns>The rawdata as a SwaptionDataGrid</returns> private static ForwardRatesMatrix ParseAssetInputWithInterpolation(String[] tenors, String[] expiry, Decimal[,] rawAsset) { // Set the upper/lower bounds of the converted array //var lo1D = rawAsset.GetLowerBound(0); int hi1D = rawAsset.GetUpperBound(0) + 1; //var lo2D = rawAsset.GetLowerBound(1); int hi2D = rawAsset.GetUpperBound(1) + 1; // Add arrays for the values var values = new decimal[hi1D][]; for (int idx = 0; idx < hi1D; idx++) { values[idx] = new decimal[hi2D]; for (int jdx = 0; jdx < hi2D; jdx++) { values[idx][jdx] = rawAsset[idx, jdx]; } } // Set up the lists we shall use to insert any interpolated column entries var fullTenors = new List <string>(); var fullValues = new List <List <decimal> >(); // Convert the tenors to doubles var tenorsAsDouble = new decimal[tenors.Length];//Tenor must be years. for (int idx = 0; idx < tenors.Length; idx++) { tenorsAsDouble[idx] = (decimal)(PeriodHelper.Parse(tenors[idx]).ToYearFraction()); } // Set up the min and max column (tenor) values var min = (int)(tenorsAsDouble[0]); var max = (int)(tenorsAsDouble[tenorsAsDouble.Length - 1]); // Loop through the expiry values testing the tenor values // Copy existing tenors or add new tenor values using interpolation for (int expiryIdx = 0; expiryIdx < expiry.Length; expiryIdx++) { int tenorIdx = 0; var inner = new List <decimal>(); var lin = new LinearInterpolation(tenorsAsDouble, values[expiryIdx]); // Loop from the min to the max testing for valid tenor entries // and inserting new values where necessary for (int idx = min; idx <= max; idx++) { if (idx == int.Parse(PeriodHelper.Parse(tenors[tenorIdx]).periodMultiplier)) { // Add an existing tenor and its values array - Remember to check we haven't already added the tenor if (!fullTenors.Contains(tenors[tenorIdx])) { fullTenors.Add(tenors[tenorIdx]); } inner.Add(values[expiryIdx][tenorIdx++]); } else { // Add the interpolated tenor and its value - Remember to check we haven't already added the tenor if (!fullTenors.Contains(tenors[tenorIdx])) { fullTenors.Add(idx.ToString(CultureInfo.InvariantCulture) + 'Y'); } inner.Add(lin.ValueAt((decimal)idx)); } } fullValues.Add(inner); } // Regenerate the arrays to use in the grid string[] interpolatedTenors = fullTenors.ToArray(); var interpolatedValues = new decimal[fullValues.Count][]; for (int idx = 0; idx < fullValues.Count; idx++) { interpolatedValues[idx] = fullValues[idx].ToArray(); } // Create the grid var grid = new ForwardRatesMatrix(expiry, interpolatedTenors, interpolatedValues); return(grid); }
/// <summary> /// Create a full calibration model.This version uses the volatility grid to generate an engine /// for each swap tenor (row values) /// </summary> /// <param name="volatilityGrid">The vols grid</param> /// <param name="assetGrid">The asset grid</param> /// <param name="settings">The SABR settings</param> /// <param name="calibrationEngineId">The id of this engine</param> /// <param name="optionExpiry">The ATM pointer</param> private static SortedDictionary <SABRKey, SABRCalibrationEngine> BuildEngineCollection(SwaptionDataMatrix volatilityGrid, ForwardRatesMatrix assetGrid, SABRCalibrationSettings settings, string calibrationEngineId, string optionExpiry) { var engineCollection = new SortedDictionary <SABRKey, SABRCalibrationEngine>(new SABRKey()); // Generate a new entry in the engineCollection for each row in the volatility grid foreach (string tenor in volatilityGrid.GetTenors()) { var assetPrice = assetGrid.GetAssetPrice(optionExpiry, tenor); var exerciseTime = (decimal)SABRHelper.GenerateDayValue(optionExpiry, 365.0d); // Generate the Vols and Strikes lists for the engine List <decimal> vols = volatilityGrid.GetVolatility(tenor).ToList(); List <decimal> strikes = volatilityGrid.GetStrikes().Select(strike => assetPrice + strike).ToList(); // Only add a new Calibration Engine (and Calibrate it) if the vols are greater than 0 if (!SABRHelper.ValidateData(vols)) { continue; } // Create a new instance of the engine var calibrationEngine = new SABRCalibrationEngine(calibrationEngineId, settings, strikes, vols, assetPrice, exerciseTime); // Calibrate the engine calibrationEngine.CalibrateSABRModel(); // Add the new engine to our collection var key = new SABRKey(optionExpiry, tenor); engineCollection.Add(key, calibrationEngine); } return(engineCollection); }
/// <summary> /// Parse the raw assets grid to create the labels and values arrays /// required by a SwaptionDataGrid{T,U} used to store the data /// for use by the SABR calibration/calulation routines /// This version uses Interpolation to include Swap Tenors that are not /// included as part of the grid but fall within the minimum and maximum tenor /// values of the grid. /// </summary> /// <param name="rawAsset">The raw volatility data</param> /// <returns>The rawdata as a SwaptionDataGrid</returns> private static ForwardRatesMatrix ParseAssetInputWithInterpolation(object[,] rawAsset) { // Set the upper/lower bounds of the converted array int hi1D = rawAsset.GetUpperBound(0); int hi2D = rawAsset.GetUpperBound(1); // Create and populate the data arrays used to build the SwaptionDataGrid // The columns represent tenors, the rows option expiries var tenors = new string[hi2D]; var expiry = new string[hi1D]; for (int idx = 1; idx <= hi2D; idx++) { tenors[idx - 1] = rawAsset[0, idx].ToString(); } for (int idx = 1; idx <= hi1D; idx++) { expiry[idx - 1] = rawAsset[idx, 0].ToString(); } // Add arrays for the values var values = new decimal[hi1D][]; for (int idx = 1; idx <= hi1D; idx++) { values[idx - 1] = new decimal[hi2D]; for (int jdx = 1; jdx <= hi2D; jdx++) { values[idx - 1][jdx - 1] = decimal.Parse(rawAsset[idx, jdx].ToString()); } } // Set up the lists we shall use to insert any interpolated column entries var fullTenors = new List <string>(); var fullValues = new List <List <decimal> >(); // Convert the tenors to doubles var tenorsAsDouble = new decimal[tenors.Length]; for (int idx = 0; idx < tenors.Length; idx++) { tenorsAsDouble[idx] = decimal.Parse(tenors[idx]); } // Set up the min and max column (tenor) values int min = int.Parse(tenors[0]); int max = int.Parse(tenors[tenors.Length - 1]); // Loop through the expiry values testing the tenor values // Copy existing tenors or add new tenor values using interpolation for (int expiryIdx = 0; expiryIdx < expiry.Length; expiryIdx++) { int tenorIdx = 0; var inner = new List <decimal>(); var lin = new LinearInterpolation(tenorsAsDouble, values[expiryIdx]); // Loop from the min to the max testing for valid tenor entries // and inserting new values where necessary for (int idx = min; idx <= max; idx++) { if (idx == int.Parse(tenors[tenorIdx])) { // Add an existing tenor and its values array - Remember to check we haven't already added the tenor if (!fullTenors.Contains(tenors[tenorIdx])) { fullTenors.Add(tenors[tenorIdx]); } inner.Add(values[expiryIdx][tenorIdx++]); } else { // Add the interpolated tenor and its value - Remember to check we haven't already added the tenor if (!fullTenors.Contains(tenors[tenorIdx])) { fullTenors.Add(idx.ToString(CultureInfo.InvariantCulture)); } inner.Add(lin.ValueAt((decimal)idx)); } } fullValues.Add(inner); } // Regenerate the arrays to use in the grid string[] interpolatedTenors = fullTenors.Select(a => a.Last() == 'y' ? a : a + "y").ToArray(); var interpolatedValues = new decimal[fullValues.Count][]; for (int idx = 0; idx < fullValues.Count; idx++) { interpolatedValues[idx] = fullValues[idx].ToArray(); } // Create the grid var grid = new ForwardRatesMatrix(expiry, interpolatedTenors, interpolatedValues); return(grid); }