DiscreteHedging.cpp

This is an example of using the QuantLib Monte Carlo framework.
/* This example computes profit and loss of a discrete interval hedging strategy and compares with the results of Derman & Kamal's (Goldman Sachs Equity Derivatives Research) Research Note: "When You Cannot Hedge Continuously: The Corrections to Black-Scholes" (http://www.gs.com/qs/doc/when_you_cannot_hedge.pdf) Suppose an option hedger sells an European option and receives the Black-Scholes value as the options premium. Then he follows a Black-Scholes hedging strategy, rehedging at discrete, evenly spaced time intervals as the underlying stock changes. At expiration, the hedger delivers the option payoff to the option holder, and unwinds the hedge. We are interested in understanding the final profit or loss of this strategy. If the hedger had followed the exact Black-Scholes replication strategy, re-hedging continuously as the underlying stock evolved towards its final value at expiration, then, no matter what path the stock took, the final P&L would be exactly zero. When the replication strategy deviates from the exact Black-Scholes method, the final P&L may deviate from zero. This deviation is called the replication error. When the hedger rebalances at discrete rather than continuous intervals, the hedge is imperfect and the replication is inexact. The more often hedging occurs, the smaller the replication error. We examine the range of possibilities, computing the replication error. */ // the only header you need to use QuantLib #include <ql/quantlib.hpp> using namespace QuantLib; /* The ReplicationError class carries out Monte Carlo simulations to evaluate the outcome (the replication error) of the discrete hedging strategy over different, randomly generated scenarios of future stock price evolution. */ class ReplicationError { public: ReplicationError(Option::Type type, Time maturity, Real strike, Real s0, Volatility sigma, Rate r) : maturity_(maturity), payoff_(type, strike), s0_(s0), sigma_(sigma), r_(r) { // value of the option DiscountFactor rDiscount = QL_EXP(-r_*maturity_); DiscountFactor qDiscount = 1.0; Real forward = s0_*qDiscount/rDiscount; Real variance = sigma_*sigma_*maturity_; boost::shared_ptr<StrikedTypePayoff> payoff( new PlainVanillaPayoff(payoff_)); BlackFormula black(forward,rDiscount,variance,payoff); std::cout << "Option value: " << black.value() << std::endl; // store option's vega, since Derman and Kamal's formula needs it vega_ = black.vega(maturity_); std::cout << std::endl; std::cout << " | | P&L \t| P&L | Derman&Kamal | P&L" " \t| P&L" << std::endl; std::cout << "samples | trades | Mean \t| Std Dev | Formula |" " skewness \t| kurt." << std::endl; std::cout << "---------------------------------" "----------------------------------------------" << std::endl; } // the actual replication error computation void compute(Size nTimeSteps, Size nSamples); private: Time maturity_; PlainVanillaPayoff payoff_; Real s0_; Volatility sigma_; Rate r_; Real vega_; }; // The key for the MonteCarlo simulation is to have a PathPricer that // implements a value(const Path& path) method. // This method prices the portfolio for each Path of the random variable class ReplicationPathPricer : public PathPricer<Path> { public: // real constructor ReplicationPathPricer(Option::Type type, Real underlying, Real strike, Rate r, Time maturity, Volatility sigma) : type_(type), underlying_(underlying), strike_(strike), r_(r), maturity_(maturity), sigma_(sigma) { QL_REQUIRE(strike_ > 0.0, "strike must be positive"); QL_REQUIRE(underlying_ > 0.0, "underlying must be positive"); QL_REQUIRE(r_ >= 0.0, "risk free rate (r) must be positive or zero"); QL_REQUIRE(maturity_ > 0.0, "maturity must be positive"); QL_REQUIRE(sigma_ >= 0.0, "volatility (sigma) must be positive or zero"); } // The value() method encapsulates the pricing code Real operator()(const Path& path) const; private: Option::Type type_; Real underlying_, strike_; Rate r_; Time maturity_; Volatility sigma_; }; // Compute Replication Error as in the Derman and Kamal's research note int main(int, char* []) { try { QL_IO_INIT Time maturity = 1./12.; // 1 month Real strike = 100; Real underlying = 100; Volatility volatility = 0.20; // 20% Rate riskFreeRate = 0.05; // 5% ReplicationError rp(Option::Call, maturity, strike, underlying, volatility, riskFreeRate); Size scenarios = 50000; Size hedgesNum; hedgesNum = 21; rp.compute(hedgesNum, scenarios); hedgesNum = 84; rp.compute(hedgesNum, scenarios); return 0; } catch (std::exception& e) { std::cout << e.what() << std::endl; return 1; } catch (...) { std::cout << "unknown error" << std::endl; return 1; } } /* The actual computation of the Profit&Loss for each single path. In each scenario N rehedging trades spaced evenly in time over the life of the option are carried out, using the Black-Scholes hedge ratio. */ Real ReplicationPathPricer::operator()(const Path& path) const { // path is an instance of QuantLib::Path // It contains the list of variations. // It can be used as an array: it has a size() method Size n = path.size(); QL_REQUIRE(n>0, "the path cannot be empty"); // discrete hedging interval Time dt = maturity_/n; // For simplicity, we assume the stock pays no dividends. Rate stockDividendYield = 0.0; // let's start Time t = 0; // stock value at t=0 Real stock = underlying_; Real stockLogGrowth = 0.0; // money account at t=0 Real money_account = 0.0; /************************/ /*** the initial deal ***/ /************************/ // option fair price (Black-Scholes) at t=0 DiscountFactor rDiscount = QL_EXP(-r_*maturity_); DiscountFactor qDiscount = QL_EXP(-stockDividendYield*maturity_); Real forward = stock*qDiscount/rDiscount; Real variance = sigma_*sigma_*maturity_; boost::shared_ptr<StrikedTypePayoff> payoff( new PlainVanillaPayoff(type_,strike_)); BlackFormula black(forward,rDiscount,variance,payoff); // sell the option, cash in its premium money_account += black.value(); // compute delta Real delta = black.delta(stock); // delta-hedge the option buying stock Real stockAmount = delta; money_account -= stockAmount*stock; /**********************************/ /*** hedging during option life ***/ /**********************************/ for (Size step = 0; step < n-1; step++){ // time flows t += dt; // accruing on the money account money_account *= QL_EXP( r_*dt ); // stock growth: // path contains the list of Gaussian variations // and path[n] is the n-th variation stockLogGrowth += path[step]; stock = underlying_*QL_EXP(stockLogGrowth); // recalculate option value at the current stock value, // and the current time to maturity rDiscount = QL_EXP(-r_*(maturity_-t)); qDiscount = QL_EXP(-stockDividendYield*(maturity_-t)); forward = stock*qDiscount/rDiscount; variance = sigma_*sigma_*(maturity_-t); BlackFormula black(forward,rDiscount,variance,payoff); // recalculate delta delta = black.delta(stock); // re-hedging money_account -= (delta - stockAmount)*stock; stockAmount = delta; } /*************************/ /*** option expiration ***/ /*************************/ // last accrual on my money account money_account *= QL_EXP( r_*dt ); // last stock growth stockLogGrowth += path[n-1]; stock = underlying_*QL_EXP(stockLogGrowth); // the hedger delivers the option payoff to the option holder Real optionPayoff = PlainVanillaPayoff(type_, strike_)(stock); money_account -= optionPayoff; // and unwinds the hedge selling his stock position money_account += stockAmount*stock; // final Profit&Loss return money_account; } // The computation over nSamples paths of the P&L distribution void ReplicationError::compute(Size nTimeSteps, Size nSamples) { QL_REQUIRE(nTimeSteps>0, "the number of steps must be > 0"); // hedging interval // Time tau = maturity_ / nTimeSteps; /* Black-Scholes framework: the underlying stock price evolves lognormally with a fixed known volatility that stays constant throughout time. */ Date today = Date::todaysDate(); RelinkableHandle<Quote> stateVariable( boost::shared_ptr<Quote>(new SimpleQuote(s0_))); RelinkableHandle<TermStructure> riskFreeRate( boost::shared_ptr<TermStructure>( new FlatForward(today, today, r_))); RelinkableHandle<TermStructure> dividendYield( boost::shared_ptr<TermStructure>( new FlatForward(today, today, 0.0))); RelinkableHandle<BlackVolTermStructure> volatility( boost::shared_ptr<BlackVolTermStructure>( new BlackConstantVol(today, sigma_))); boost::shared_ptr<StochasticProcess> diffusion( new BlackScholesProcess(stateVariable, dividendYield, riskFreeRate, volatility)); // Black Scholes equation rules the path generator: // at each step the log of the stock // will have drift and sigma^2 variance PseudoRandom::rsg_type rsg = PseudoRandom::make_sequence_generator(nTimeSteps, 0); typedef SingleAsset<PseudoRandom>::path_generator_type generator_type; boost::shared_ptr<generator_type> myPathGenerator( new generator_type(diffusion, maturity_, nTimeSteps, rsg, false)); // The replication strategy's Profit&Loss is computed for each path // of the stock. The path pricer knows how to price a path using its // value() method boost::shared_ptr<PathPricer<Path> > myPathPricer(new ReplicationPathPricer(payoff_.optionType(), s0_, payoff_.strike(), r_, maturity_, sigma_)); // a statistics accumulator for the path-dependant Profit&Loss values Statistics statisticsAccumulator; // The OneFactorMontecarloModel generates paths using myPathGenerator // each path is priced using myPathPricer // prices will be accumulated into statisticsAccumulator OneFactorMonteCarloOption MCSimulation(myPathGenerator, myPathPricer, statisticsAccumulator, false); // the model simulates nSamples paths MCSimulation.addSamples(nSamples); // the sampleAccumulator method of OneFactorMonteCarloOption_old // gives access to all the methods of statisticsAccumulator Real PLMean = MCSimulation.sampleAccumulator().mean(); Real PLStDev = MCSimulation.sampleAccumulator().standardDeviation(); Real PLSkew = MCSimulation.sampleAccumulator().skewness(); Real PLKurt = MCSimulation.sampleAccumulator().kurtosis(); // Derman and Kamal's formula Real theorStD = QL_SQRT(M_PI/4/nTimeSteps)*vega_*sigma_; std::cout << nSamples << "\t| " << nTimeSteps << "\t | " << DecimalFormatter::toString(PLMean, 3) << " \t| " << DecimalFormatter::toString(PLStDev, 2) << " \t | " << DecimalFormatter::toString(theorStD, 2) << " \t | " << DecimalFormatter::toString(PLSkew, 2) << " \t| " << DecimalFormatter::toString(PLKurt, 2) << std::endl; }
00001 00018 /* This example computes profit and loss of a discrete interval hedging 00019 strategy and compares with the results of Derman & Kamal's (Goldman Sachs 00020 Equity Derivatives Research) Research Note: "When You Cannot Hedge 00021 Continuously: The Corrections to Black-Scholes" 00022 (http://www.gs.com/qs/doc/when_you_cannot_hedge.pdf) 00023 00024 Suppose an option hedger sells an European option and receives the 00025 Black-Scholes value as the options premium. 00026 Then he follows a Black-Scholes hedging strategy, rehedging at discrete, 00027 evenly spaced time intervals as the underlying stock changes. At 00028 expiration, the hedger delivers the option payoff to the option holder, 00029 and unwinds the hedge. We are interested in understanding the final 00030 profit or loss of this strategy. 00031 00032 If the hedger had followed the exact Black-Scholes replication strategy, 00033 re-hedging continuously as the underlying stock evolved towards its final 00034 value at expiration, then, no matter what path the stock took, the final 00035 P&L would be exactly zero. When the replication strategy deviates from 00036 the exact Black-Scholes method, the final P&L may deviate from zero. This 00037 deviation is called the replication error. When the hedger rebalances at 00038 discrete rather than continuous intervals, the hedge is imperfect and the 00039 replication is inexact. The more often hedging occurs, the smaller the 00040 replication error. 00041 00042 We examine the range of possibilities, computing the replication error. 00043 */ 00044 00045 // the only header you need to use QuantLib 00046 #include <ql/quantlib.hpp> 00047 00048 using namespace QuantLib; 00049 00050 /* The ReplicationError class carries out Monte Carlo simulations to evaluate 00051 the outcome (the replication error) of the discrete hedging strategy over 00052 different, randomly generated scenarios of future stock price evolution. 00053 */ 00054 class ReplicationError 00055 { 00056 public: 00057 ReplicationError(Option::Type type, 00058 Time maturity, 00059 Real strike, 00060 Real s0, 00061 Volatility sigma, 00062 Rate r) 00063 : maturity_(maturity), payoff_(type, strike), s0_(s0), 00064 sigma_(sigma), r_(r) { 00065 00066 // value of the option 00067 DiscountFactor rDiscount = QL_EXP(-r_*maturity_); 00068 DiscountFactor qDiscount = 1.0; 00069 Real forward = s0_*qDiscount/rDiscount; 00070 Real variance = sigma_*sigma_*maturity_; 00071 boost::shared_ptr<StrikedTypePayoff> payoff( 00072 new PlainVanillaPayoff(payoff_)); 00073 BlackFormula black(forward,rDiscount,variance,payoff); 00074 std::cout << "Option value: " << black.value() << std::endl; 00075 00076 // store option's vega, since Derman and Kamal's formula needs it 00077 vega_ = black.vega(maturity_); 00078 00079 std::cout << std::endl; 00080 std::cout << 00081 " | | P&L \t| P&L | Derman&Kamal | P&L" 00082 " \t| P&L" << std::endl; 00083 00084 std::cout << 00085 "samples | trades | Mean \t| Std Dev | Formula |" 00086 " skewness \t| kurt." << std::endl; 00087 00088 std::cout << "---------------------------------" 00089 "----------------------------------------------" << std::endl; 00090 } 00091 00092 // the actual replication error computation 00093 void compute(Size nTimeSteps, Size nSamples); 00094 private: 00095 Time maturity_; 00096 PlainVanillaPayoff payoff_; 00097 Real s0_; 00098 Volatility sigma_; 00099 Rate r_; 00100 Real vega_; 00101 }; 00102 00103 // The key for the MonteCarlo simulation is to have a PathPricer that 00104 // implements a value(const Path& path) method. 00105 // This method prices the portfolio for each Path of the random variable 00106 class ReplicationPathPricer : public PathPricer<Path> 00107 { 00108 public: 00109 // real constructor 00110 ReplicationPathPricer(Option::Type type, 00111 Real underlying, 00112 Real strike, 00113 Rate r, 00114 Time maturity, 00115 Volatility sigma) 00116 : type_(type), underlying_(underlying), 00117 strike_(strike), r_(r), maturity_(maturity), sigma_(sigma) { 00118 QL_REQUIRE(strike_ > 0.0, "strike must be positive"); 00119 QL_REQUIRE(underlying_ > 0.0, "underlying must be positive"); 00120 QL_REQUIRE(r_ >= 0.0, 00121 "risk free rate (r) must be positive or zero"); 00122 QL_REQUIRE(maturity_ > 0.0, "maturity must be positive"); 00123 QL_REQUIRE(sigma_ >= 0.0, 00124 "volatility (sigma) must be positive or zero"); 00125 00126 } 00127 // The value() method encapsulates the pricing code 00128 Real operator()(const Path& path) const; 00129 00130 private: 00131 Option::Type type_; 00132 Real underlying_, strike_; 00133 Rate r_; 00134 Time maturity_; 00135 Volatility sigma_; 00136 }; 00137 00138 00139 // Compute Replication Error as in the Derman and Kamal's research note 00140 int main(int, char* []) 00141 { 00142 try { 00143 QL_IO_INIT 00144 00145 Time maturity = 1./12.; // 1 month 00146 Real strike = 100; 00147 Real underlying = 100; 00148 Volatility volatility = 0.20; // 20% 00149 Rate riskFreeRate = 0.05; // 5% 00150 ReplicationError rp(Option::Call, maturity, strike, underlying, 00151 volatility, riskFreeRate); 00152 00153 Size scenarios = 50000; 00154 Size hedgesNum; 00155 00156 hedgesNum = 21; 00157 rp.compute(hedgesNum, scenarios); 00158 00159 hedgesNum = 84; 00160 rp.compute(hedgesNum, scenarios); 00161 00162 return 0; 00163 } catch (std::exception& e) { 00164 std::cout << e.what() << std::endl; 00165 return 1; 00166 } catch (...) { 00167 std::cout << "unknown error" << std::endl; 00168 return 1; 00169 } 00170 } 00171 00172 00173 /* The actual computation of the Profit&Loss for each single path. 00174 00175 In each scenario N rehedging trades spaced evenly in time over 00176 the life of the option are carried out, using the Black-Scholes 00177 hedge ratio. 00178 */ 00179 Real ReplicationPathPricer::operator()(const Path& path) const 00180 { 00181 00182 // path is an instance of QuantLib::Path 00183 // It contains the list of variations. 00184 // It can be used as an array: it has a size() method 00185 Size n = path.size(); 00186 QL_REQUIRE(n>0, "the path cannot be empty"); 00187 00188 // discrete hedging interval 00189 Time dt = maturity_/n; 00190 00191 // For simplicity, we assume the stock pays no dividends. 00192 Rate stockDividendYield = 0.0; 00193 00194 // let's start 00195 Time t = 0; 00196 00197 // stock value at t=0 00198 Real stock = underlying_; 00199 Real stockLogGrowth = 0.0; 00200 00201 // money account at t=0 00202 Real money_account = 0.0; 00203 00204 /************************/ 00205 /*** the initial deal ***/ 00206 /************************/ 00207 // option fair price (Black-Scholes) at t=0 00208 DiscountFactor rDiscount = QL_EXP(-r_*maturity_); 00209 DiscountFactor qDiscount = QL_EXP(-stockDividendYield*maturity_); 00210 Real forward = stock*qDiscount/rDiscount; 00211 Real variance = sigma_*sigma_*maturity_; 00212 boost::shared_ptr<StrikedTypePayoff> payoff( 00213 new PlainVanillaPayoff(type_,strike_)); 00214 BlackFormula black(forward,rDiscount,variance,payoff); 00215 // sell the option, cash in its premium 00216 money_account += black.value(); 00217 // compute delta 00218 Real delta = black.delta(stock); 00219 // delta-hedge the option buying stock 00220 Real stockAmount = delta; 00221 money_account -= stockAmount*stock; 00222 00223 /**********************************/ 00224 /*** hedging during option life ***/ 00225 /**********************************/ 00226 for (Size step = 0; step < n-1; step++){ 00227 00228 // time flows 00229 t += dt; 00230 00231 // accruing on the money account 00232 money_account *= QL_EXP( r_*dt ); 00233 00234 // stock growth: 00235 // path contains the list of Gaussian variations 00236 // and path[n] is the n-th variation 00237 stockLogGrowth += path[step]; 00238 stock = underlying_*QL_EXP(stockLogGrowth); 00239 00240 // recalculate option value at the current stock value, 00241 // and the current time to maturity 00242 rDiscount = QL_EXP(-r_*(maturity_-t)); 00243 qDiscount = QL_EXP(-stockDividendYield*(maturity_-t)); 00244 forward = stock*qDiscount/rDiscount; 00245 variance = sigma_*sigma_*(maturity_-t); 00246 BlackFormula black(forward,rDiscount,variance,payoff); 00247 00248 // recalculate delta 00249 delta = black.delta(stock); 00250 00251 // re-hedging 00252 money_account -= (delta - stockAmount)*stock; 00253 stockAmount = delta; 00254 } 00255 00256 /*************************/ 00257 /*** option expiration ***/ 00258 /*************************/ 00259 // last accrual on my money account 00260 money_account *= QL_EXP( r_*dt ); 00261 // last stock growth 00262 stockLogGrowth += path[n-1]; 00263 stock = underlying_*QL_EXP(stockLogGrowth); 00264 00265 // the hedger delivers the option payoff to the option holder 00266 Real optionPayoff = PlainVanillaPayoff(type_, strike_)(stock); 00267 money_account -= optionPayoff; 00268 00269 // and unwinds the hedge selling his stock position 00270 money_account += stockAmount*stock; 00271 00272 // final Profit&Loss 00273 return money_account; 00274 } 00275 00276 00277 // The computation over nSamples paths of the P&L distribution 00278 void ReplicationError::compute(Size nTimeSteps, Size nSamples) 00279 { 00280 QL_REQUIRE(nTimeSteps>0, "the number of steps must be > 0"); 00281 00282 // hedging interval 00283 // Time tau = maturity_ / nTimeSteps; 00284 00285 /* Black-Scholes framework: the underlying stock price evolves 00286 lognormally with a fixed known volatility that stays constant 00287 throughout time. 00288 */ 00289 Date today = Date::todaysDate(); 00290 RelinkableHandle<Quote> stateVariable( 00291 boost::shared_ptr<Quote>(new SimpleQuote(s0_))); 00292 RelinkableHandle<TermStructure> riskFreeRate( 00293 boost::shared_ptr<TermStructure>( 00294 new FlatForward(today, today, r_))); 00295 RelinkableHandle<TermStructure> dividendYield( 00296 boost::shared_ptr<TermStructure>( 00297 new FlatForward(today, today, 0.0))); 00298 RelinkableHandle<BlackVolTermStructure> volatility( 00299 boost::shared_ptr<BlackVolTermStructure>( 00300 new BlackConstantVol(today, sigma_))); 00301 boost::shared_ptr<StochasticProcess> diffusion( 00302 new BlackScholesProcess(stateVariable, dividendYield, 00303 riskFreeRate, volatility)); 00304 00305 // Black Scholes equation rules the path generator: 00306 // at each step the log of the stock 00307 // will have drift and sigma^2 variance 00308 PseudoRandom::rsg_type rsg = 00309 PseudoRandom::make_sequence_generator(nTimeSteps, 0); 00310 00311 typedef SingleAsset<PseudoRandom>::path_generator_type generator_type; 00312 boost::shared_ptr<generator_type> myPathGenerator( 00313 new generator_type(diffusion, maturity_, nTimeSteps, 00314 rsg, false)); 00315 00316 // The replication strategy's Profit&Loss is computed for each path 00317 // of the stock. The path pricer knows how to price a path using its 00318 // value() method 00319 boost::shared_ptr<PathPricer<Path> > myPathPricer(new 00320 ReplicationPathPricer(payoff_.optionType(), s0_, 00321 payoff_.strike(), r_, maturity_, sigma_)); 00322 00323 // a statistics accumulator for the path-dependant Profit&Loss values 00324 Statistics statisticsAccumulator; 00325 00326 // The OneFactorMontecarloModel generates paths using myPathGenerator 00327 // each path is priced using myPathPricer 00328 // prices will be accumulated into statisticsAccumulator 00329 OneFactorMonteCarloOption MCSimulation(myPathGenerator, 00330 myPathPricer, 00331 statisticsAccumulator, 00332 false); 00333 00334 // the model simulates nSamples paths 00335 MCSimulation.addSamples(nSamples); 00336 00337 // the sampleAccumulator method of OneFactorMonteCarloOption_old 00338 // gives access to all the methods of statisticsAccumulator 00339 Real PLMean = MCSimulation.sampleAccumulator().mean(); 00340 Real PLStDev = MCSimulation.sampleAccumulator().standardDeviation(); 00341 Real PLSkew = MCSimulation.sampleAccumulator().skewness(); 00342 Real PLKurt = MCSimulation.sampleAccumulator().kurtosis(); 00343 00344 // Derman and Kamal's formula 00345 Real theorStD = QL_SQRT(M_PI/4/nTimeSteps)*vega_*sigma_; 00346 00347 00348 std::cout << nSamples << "\t| " 00349 << nTimeSteps << "\t | " 00350 << DecimalFormatter::toString(PLMean, 3) << " \t| " 00351 << DecimalFormatter::toString(PLStDev, 2) << " \t | " 00352 << DecimalFormatter::toString(theorStD, 2) << " \t | " 00353 << DecimalFormatter::toString(PLSkew, 2) << " \t| " 00354 << DecimalFormatter::toString(PLKurt, 2) << std::endl; 00355 }

QuantLib.org
QuantLib
Hosted by
SourceForge.net Logo
Documentation generated by
doxygen