diff --git a/ql/pricingengines/lookback/analyticcontinuousfixedlookback.cpp b/ql/pricingengines/lookback/analyticcontinuousfixedlookback.cpp index 71b8892d191..f40799c92a4 100644 --- a/ql/pricingengines/lookback/analyticcontinuousfixedlookback.cpp +++ b/ql/pricingengines/lookback/analyticcontinuousfixedlookback.cpp @@ -20,6 +20,7 @@ #include #include +#include #include namespace QuantLib { @@ -123,9 +124,7 @@ namespace QuantLib { Real powss = std::pow(ss, -lambda); return eta*(underlying() * dividendDiscount() * N1 - minmax() * riskFreeDiscount() * N2 - - underlying() * riskFreeDiscount() * - (powss * N3 - dividendDiscount()* N4/riskFreeDiscount())/ - lambda); + underlying() * riskFreeDiscount() * ratio(lambda, d1, N3, N4, powss, eta)); } Real AnalyticContinuousFixedLookbackEngine::B(Real eta) const { @@ -141,9 +140,16 @@ namespace QuantLib { Real powss = std::pow(ss, -lambda); return eta*(underlying() * dividendDiscount() * N1 - strike() * riskFreeDiscount() * N2 - - underlying() * riskFreeDiscount() * - (powss * N3 - dividendDiscount()* N4/riskFreeDiscount())/ - lambda); + underlying() * riskFreeDiscount() * ratio(lambda, d1, N3, N4, powss, eta)); + } + + Real AnalyticContinuousFixedLookbackEngine::ratio(Real lambda, Real d1, Real N3, Real N4, + Real powss, Real eta) const { + // with no carry lambda vanishes and the ratio is 0/0; its limit + // is -stdDev * (d1 * N(eta * d1) + eta * n(d1)) + if (std::fabs(lambda) < 1.0e-7) + return -stdDeviation() * (d1 * N4 + eta * f_.derivative(d1)); + return (powss * N3 - dividendDiscount() * N4 / riskFreeDiscount()) / lambda; } Real AnalyticContinuousFixedLookbackEngine::C(Real eta) const { diff --git a/ql/pricingengines/lookback/analyticcontinuousfixedlookback.hpp b/ql/pricingengines/lookback/analyticcontinuousfixedlookback.hpp index a00c598c499..b5ae86de241 100644 --- a/ql/pricingengines/lookback/analyticcontinuousfixedlookback.hpp +++ b/ql/pricingengines/lookback/analyticcontinuousfixedlookback.hpp @@ -63,6 +63,7 @@ namespace QuantLib { Real A(Real eta) const; Real B(Real eta) const; Real C(Real eta) const; + Real ratio(Real lambda, Real d1, Real N3, Real N4, Real powss, Real eta) const; }; } diff --git a/ql/pricingengines/lookback/analyticcontinuousfloatinglookback.cpp b/ql/pricingengines/lookback/analyticcontinuousfloatinglookback.cpp index 130fdb0ce8c..da85c64421e 100644 --- a/ql/pricingengines/lookback/analyticcontinuousfloatinglookback.cpp +++ b/ql/pricingengines/lookback/analyticcontinuousfloatinglookback.cpp @@ -20,6 +20,7 @@ #include #include +#include #include namespace QuantLib { @@ -100,11 +101,14 @@ namespace QuantLib { Real n3 = f_(eta*(-d1+lambda*stdDeviation())); Real n4 = f_(eta*-d1); Real pow_s = std::pow(s, -lambda); + // with no carry lambda vanishes and the ratio below is 0/0; its limit + // is stdDev * (eta * n(d1) - d1 * N(-eta * d1)) + Real ratio = std::fabs(lambda) < 1.0e-7 + ? stdDeviation() * (eta * f_.derivative(d1) - d1 * n4) + : (pow_s * n3 - dividendDiscount() * n4 / riskFreeDiscount()) / lambda; return eta*((underlying() * dividendDiscount() * n1 - minmax() * riskFreeDiscount() * n2) + - (underlying() * riskFreeDiscount() * - (pow_s * n3 - dividendDiscount()* n4/riskFreeDiscount())/ - lambda)); + underlying() * riskFreeDiscount() * ratio); } } diff --git a/test-suite/lookbackoptions.cpp b/test-suite/lookbackoptions.cpp index 4878e45e351..1a9dec35c1a 100644 --- a/test-suite/lookbackoptions.cpp +++ b/test-suite/lookbackoptions.cpp @@ -271,6 +271,92 @@ BOOST_AUTO_TEST_CASE(testAnalyticContinuousFixedLookback) { } } +BOOST_AUTO_TEST_CASE(testAnalyticContinuousLookbackZeroCarry) { + + BOOST_TEST_MESSAGE( + "Testing analytic continuous lookback options with zero carry..."); + + // with r equal to q the formulas are 0/0; the expected values are their + // limit, evaluated in 50-digit arithmetic + + LookbackOptionData floatingValues[] = { + // type, strike, minmax, s, q, r, t, v, l, t1, result, tol + { Option::Call, 0, 90, 100.0, 0.05, 0.05, 1.00, 0.30, 0, 0, 21.817075601551579, 1.0e-8}, + { Option::Put, 0, 110, 100.0, 0.05, 0.05, 1.00, 0.30, 0, 0, 26.407720137984153, 1.0e-8}, + }; + + LookbackOptionData fixedValues[] = { + // type, strike, minmax, s, q, r, t, v, l, t1, result, tol + { Option::Call, 95, 110, 100.0, 0.05, 0.05, 1.00, 0.30, 0, 0, 31.163867260487723, 1.0e-8}, + { Option::Call, 105, 100, 100.0, 0.05, 0.05, 1.00, 0.30, 0, 0, 20.604933499041011, 1.0e-8}, + { Option::Put, 105, 90, 100.0, 0.05, 0.05, 1.00, 0.30, 0, 0, 26.573222724055149, 1.0e-8}, + { Option::Put, 95, 100, 100.0, 0.05, 0.05, 1.00, 0.30, 0, 0, 16.226584162874658, 1.0e-8}, + }; + + DayCounter dc = Actual360(); + Date today = Date::todaysDate(); + + ext::shared_ptr spot(new SimpleQuote(0.0)); + ext::shared_ptr qRate(new SimpleQuote(0.0)); + ext::shared_ptr qTS = flatRate(today, qRate, dc); + ext::shared_ptr rRate(new SimpleQuote(0.0)); + ext::shared_ptr rTS = flatRate(today, rRate, dc); + ext::shared_ptr vol(new SimpleQuote(0.0)); + ext::shared_ptr volTS = flatVol(today, vol, dc); + + ext::shared_ptr stochProcess( + new BlackScholesMertonProcess( + Handle(spot), + Handle(qTS), + Handle(rTS), + Handle(volTS))); + + for (auto& value : floatingValues) { + ext::shared_ptr exercise( + new EuropeanExercise(today + timeToDays(value.t))); + spot->setValue(value.s); + qRate->setValue(value.q); + rRate->setValue(value.r); + vol->setValue(value.v); + + ext::shared_ptr payoff(new FloatingTypePayoff(value.type)); + ContinuousFloatingLookbackOption option(value.minmax, payoff, exercise); + option.setPricingEngine(ext::shared_ptr( + new AnalyticContinuousFloatingLookbackEngine(stochProcess))); + + Real calculated = option.NPV(); + Real error = std::fabs(calculated - value.result); + if (!(error <= value.tol)) { + REPORT_FAILURE_FLOATING("value", value.minmax, payoff, exercise, value.s, value.q, + value.r, today, value.v, value.result, calculated, error, + value.tol); + } + } + + for (auto& value : fixedValues) { + ext::shared_ptr exercise( + new EuropeanExercise(today + timeToDays(value.t))); + spot->setValue(value.s); + qRate->setValue(value.q); + rRate->setValue(value.r); + vol->setValue(value.v); + + ext::shared_ptr payoff( + new PlainVanillaPayoff(value.type, value.strike)); + ContinuousFixedLookbackOption option(value.minmax, payoff, exercise); + option.setPricingEngine(ext::shared_ptr( + new AnalyticContinuousFixedLookbackEngine(stochProcess))); + + Real calculated = option.NPV(); + Real error = std::fabs(calculated - value.result); + if (!(error <= value.tol)) { + REPORT_FAILURE_FIXED("value", value.minmax, payoff, exercise, value.s, value.q, + value.r, today, value.v, value.result, calculated, error, + value.tol); + } + } +} + BOOST_AUTO_TEST_CASE(testAnalyticContinuousPartialFloatingLookback) { BOOST_TEST_MESSAGE(