Hello, I calibrated the Hull-White model to market data from earlier in the year, in order to test my monte carlo code. However, I’m getting a very large variance when calculating the PV of a bullet bond. I’m using the Jamshidian model from QuantLib to calculate implied vols for swaptions and then using the HullWhite::calibrate method. I’m using Sobol numbers and the HullWhiteProcess::evolve method to produce paths. I put these paths, one by one, into our existing bullet bond pricer and then average the results. I’ve already validated the existing bond pricer, so I know it’s producing the correct results. I tried a flat yield curve and volatility, and am getting pretty good results. However, using actual market data gives me a very large difference for the same bullet bond. Increasing the number of simulated paths didn’t help. I also monitored the actual interest rates along the paths and the variation is between 23% and -13% for 5,000 or 50,000 paths. I’ve followed the BermudanSwaption example in the QuantLib project for some of my work, and checked the unit tests in QuantLib for further examples or guidance but didn’t find anything I could use. Here’s the relevant code section I’m using to generate paths: Handle< YieldTermStructure > hts(ts_); boost::shared_ptr <HullWhiteProcess> hwProcess(new HullWhiteProcess(hts, alpha, sigma)); Size numVals = grid_.size(); ublas::matrix<double> myPath(numPaths, numVals); // TODO Assumes months here; we may need to change this in the future. Time t = 0.0, dt = 1.0/12.0; // assume months! Real dw = 0.0; Real ir = 0.0; Real irSaved = 0.0; Real rr = 0.0; Real rr2 = 0.0; SobolRsg sobol(numVals, defaultSeed_); InverseCumulativeRsg<SobolRsg, InverseCumulativeNormal> generator(sobol); vector<Real> sample(numVals); Real rateLimitUp = 0.0; Real rateLimitDn = 0.0; for (QuantLib::Size i = 0; i < numPaths; i++) { ir = ts_->zeroRate(0.0, Continuous); // Note the compounding sample = generator.nextSequence().value; for (QuantLib::Size j = 0; j < numVals; j++) { t = grid_[j]; dw = sample[j]; irSaved = ir; ir = hwProcess->evolve(t, ir, dt, dw); rr = (ir*dt + std::log(A(t, t + dt)))/B(t, t + dt); // use the interest rate from the tree myPath(i, j) = rr; } } return myPath; ts_ is a boost::shared_ptr<YieldTermStructure>. I’m using LogLinear interpolation to generate an InterpolatedDiscountCurve. The above calculation of rr follows Hull & White suggestion from equation 4 - 6 of “Using Hull-White Interest Rate Trees”. At this point, I am wondering if I should take a different approach. I expect some negative rates, given the current low rates in the market, but I think what I’m getting is not quite right. Any suggestions are welcome. Thanks, Dale Smith, Ph.D. Senior Financial Quantitative Analyst Risk & Compliance Fiserv. 107 Technology Park Norcross, GA 30092 Office: 678-375-5315 Mail: [hidden email] ------------------------------------------------------------------------------ Live Security Virtual Conference Exclusive live event will cover all the ways today's security and threat landscape has changed and how IT managers can respond. Discussions will include endpoint security, mobile security and the latest in malware threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/ _______________________________________________ QuantLib-users mailing list [hidden email] https://lists.sourceforge.net/lists/listinfo/quantlib-users |
Hi Dale, I suspect this email would be better
directed towards the quantlib-dev mailing list (as it contains code). However, having briefly looked at your
code, I suspect that your Sobol generator isn’t implemented correctly. 1) Most people – when generating
low-discrepancy Brownian variables – use Sobol sequencing with Brownian
bridging to span between specific maturities (see SobolBrownianGenerator as an
example), so the first Sobol sequence is normally used for generating the
variable at the final maturity, second Sobol sequence is used for forming a
Brownian bridge for an intermediate date (such as an exercise date) and so
forth. This is because the first few dimensions of a Sobol sequence are more
reliable (and independent) than the later dimensions. 2) Sobol sequences absolutely need to have
the correct number of paths for them to behave properly! You can’t simply
take an arbitrary number of paths and expect to have an unbiased sample of the
distribution. A simple 1D Sobol sequence may look like {0.5, 0.25, 0.75, 0.125,
0.875, 0.375, 0.625…}, so if you choose any number of paths other than 1,
3, 7 etc. then you can’t expect an unbiased sample. It gets more
complicated in larger dimensionality (in your case, more than one time-step). Have you tried using a different random number
generator – such as MersenneTwisterUniformRng for comparison? PseudoRandom::rsg_type generator(PseudoRandom::make_sequence_generator(
numvals, seed ); Regards, Simon From:
Smith, Dale [mailto:[hidden email]] Hello, I calibrated the Hull-White model to market data from earlier in the
year, in order to test my monte carlo code. However, I’m getting a very
large variance when calculating the PV of a bullet bond. I’m using the Jamshidian model from QuantLib to calculate implied
vols for swaptions and then using the HullWhite::calibrate method. I’m
using Sobol numbers and the HullWhiteProcess::evolve method to produce paths. I
put these paths, one by one, into our existing bullet bond pricer and then
average the results. I’ve already validated the existing bond pricer, so
I know it’s producing the correct results. I tried a flat yield curve and volatility, and am getting pretty good
results. However, using actual market data gives me a very large difference for
the same bullet bond. Increasing the number of simulated paths didn’t
help. I also monitored the actual interest rates along the paths and the
variation is between 23% and -13% for 5,000 or 50,000 paths. I’ve followed the BermudanSwaption example in the QuantLib
project for some of my work, and checked the unit tests in QuantLib for further
examples or guidance but didn’t find anything I could use. Here’s
the relevant code section I’m using to generate paths:
Handle< YieldTermStructure > hts(ts_);
boost::shared_ptr <HullWhiteProcess> hwProcess(new HullWhiteProcess(hts, alpha, sigma));
Size numVals = grid_.size();
ublas::matrix<double>
myPath(numPaths, numVals);
// TODO Assumes months here; we may
need to change this in the future.
Time t = 0.0, dt = 1.0/12.0; //
assume months!
Real dw = 0.0;
Real ir = 0.0;
Real irSaved = 0.0;
Real rr = 0.0;
Real rr2 = 0.0;
SobolRsg sobol(numVals, defaultSeed_);
InverseCumulativeRsg<SobolRsg,
InverseCumulativeNormal> generator(sobol);
vector<Real> sample(numVals);
Real rateLimitUp = 0.0;
Real rateLimitDn = 0.0;
for (QuantLib::Size i =
0; i < numPaths; i++) {
ir = ts_->zeroRate(0.0, Continuous); // Note the compounding
sample = generator.nextSequence().value;
for (QuantLib::Size j =
0; j < numVals; j++)
{
t = grid_[j];
dw = sample[j];
irSaved = ir;
ir = hwProcess->evolve(t, ir, dt, dw);
rr = (ir
myPath(i, j) = rr;
}
}
return myPath; ts_ is a
boost::shared_ptr<YieldTermStructure>. I’m using LogLinear
interpolation to generate an InterpolatedDiscountCurve. The above calculation
of rr follows Hull & White suggestion from equation 4 - 6 of
“Using Hull-White Interest Rate Trees”. At this point, I am wondering if I should
take a different approach. I expect some negative rates, given the current low
rates in the market, but I think what I’m getting is not quite right. Any
suggestions are welcome. Thanks, Dale Smith, Ph.D. Senior Financial Quantitative Analyst Risk & Compliance Fiserv. 107
Technology Park Office: 678-375-5315 Mail:
[hidden email]
This email is not intended to nor should it be taken to create any legal relations or contractual relationships. This email has originated from ------------------------------------------------------------------------------ Live Security Virtual Conference Exclusive live event will cover all the ways today's security and threat landscape has changed and how IT managers can respond. Discussions will include endpoint security, mobile security and the latest in malware threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/ _______________________________________________ QuantLib-users mailing list [hidden email] https://lists.sourceforge.net/lists/listinfo/quantlib-users |
Simon, Thanks for the response. My first inclination was to go back to first principles, as you have suggested. However, my next step was to use Halton. Obviously, there’s more to the low-discrepancy sequences than I suspected. I’m switching back to what I was using before – Mersenne Twister with Box-Mueller to get normal random variates. I will have to do more reading if I need to use low-discrepancy sequences. Thanks, Dale Smith, Ph.D. Senior Financial Quantitative Analyst Risk & Compliance Fiserv. 107 Technology Park Norcross, GA 30092 Office: 678-375-5315 Mail: [hidden email] From: Simon Ibbotson [mailto:[hidden email]] Hi Dale, I suspect this email would be better directed towards the quantlib-dev mailing list (as it contains code). However, having briefly looked at your code, I suspect that your Sobol generator isn’t implemented correctly. 1) Most people – when generating low-discrepancy Brownian variables – use Sobol sequencing with Brownian bridging to span between specific maturities (see SobolBrownianGenerator as an example), so the first Sobol sequence is normally used for generating the variable at the final maturity, second Sobol sequence is used for forming a Brownian bridge for an intermediate date (such as an exercise date) and so forth. This is because the first few dimensions of a Sobol sequence are more reliable (and independent) than the later dimensions. 2) Sobol sequences absolutely need to have the correct number of paths for them to behave properly! You can’t simply take an arbitrary number of paths and expect to have an unbiased sample of the distribution. A simple 1D Sobol sequence may look like {0.5, 0.25, 0.75, 0.125, 0.875, 0.375, 0.625…}, so if you choose any number of paths other than 1, 3, 7 etc. then you can’t expect an unbiased sample. It gets more complicated in larger dimensionality (in your case, more than one time-step). Have you tried using a different random number generator – such as MersenneTwisterUniformRng for comparison? PseudoRandom::rsg_type generator(PseudoRandom::make_sequence_generator( numvals, seed ); Regards, Simon From: Smith, Dale [[hidden email]] Hello, I calibrated the Hull-White model to market data from earlier in the year, in order to test my monte carlo code. However, I’m getting a very large variance when calculating the PV of a bullet bond. I’m using the Jamshidian model from QuantLib to calculate implied vols for swaptions and then using the HullWhite::calibrate method. I’m using Sobol numbers and the HullWhiteProcess::evolve method to produce paths. I put these paths, one by one, into our existing bullet bond pricer and then average the results. I’ve already validated the existing bond pricer, so I know it’s producing the correct results. I tried a flat yield curve and volatility, and am getting pretty good results. However, using actual market data gives me a very large difference for the same bullet bond. Increasing the number of simulated paths didn’t help. I also monitored the actual interest rates along the paths and the variation is between 23% and -13% for 5,000 or 50,000 paths. I’ve followed the BermudanSwaption example in the QuantLib project for some of my work, and checked the unit tests in QuantLib for further examples or guidance but didn’t find anything I could use. Here’s the relevant code section I’m using to generate paths: Handle< YieldTermStructure > hts(ts_); boost::shared_ptr <HullWhiteProcess> hwProcess(new HullWhiteProcess(hts, alpha, sigma)); Size numVals = grid_.size(); ublas::matrix<double> myPath(numPaths, numVals); // TODO Assumes months here; we may need to change this in the future. Time t = 0.0, dt = 1.0/12.0; // assume months! Real dw = 0.0; Real ir = 0.0; Real irSaved = 0.0; Real rr = 0.0; Real rr2 = 0.0; SobolRsg sobol(numVals, defaultSeed_); InverseCumulativeRsg<SobolRsg, InverseCumulativeNormal> generator(sobol); vector<Real> sample(numVals); Real rateLimitUp = 0.0; Real rateLimitDn = 0.0; for (QuantLib::Size i = 0; i < numPaths; i++) { ir = ts_->zeroRate(0.0, Continuous); // Note the compounding sample = generator.nextSequence().value; for (QuantLib::Size j = 0; j < numVals; j++) { t = grid_[j]; dw = sample[j]; irSaved = ir; ir = hwProcess->evolve(t, ir, dt, dw); rr = (ir*dt + std::log(A(t, t + dt)))/B(t, t + dt); // use the interest rate from the tree myPath(i, j) = rr; } } return myPath; ts_ is a boost::shared_ptr<YieldTermStructure>. I’m using LogLinear interpolation to generate an InterpolatedDiscountCurve. The above calculation of rr follows Hull & White suggestion from equation 4 - 6 of “Using Hull-White Interest Rate Trees”. At this point, I am wondering if I should take a different approach. I expect some negative rates, given the current low rates in the market, but I think what I’m getting is not quite right. Any suggestions are welcome. Thanks, Dale Smith, Ph.D. Senior Financial Quantitative Analyst Risk & Compliance Fiserv. 107 Technology Park Norcross, GA 30092 Office: 678-375-5315 Mail: [hidden email]
This email is not intended to nor should it be taken to create any legal relations or contractual relationships. This email has originated from ------------------------------------------------------------------------------ Live Security Virtual Conference Exclusive live event will cover all the ways today's security and threat landscape has changed and how IT managers can respond. Discussions will include endpoint security, mobile security and the latest in malware threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/ _______________________________________________ QuantLib-users mailing list [hidden email] https://lists.sourceforge.net/lists/listinfo/quantlib-users |
Free forum by Nabble | Edit this page |