Floating Strike Lookback Option Pricing with C++ via Analytic Formulae

In this article we are going to consider the analytical price of a Floating-Strike European Lookback Option, which is a type of exotic path-dependent option. A Lookback Option (with Floating Strike) has a pay-off $f$ which is the difference between the asset price at maturity, $S_T$, and the minimum value (resp. maximum value) $S_{\min}$ (resp. $S_{\max}$) of the asset price over the duration of the option lifetime, depending upon whether the option is a call or put:

\begin{eqnarray*} f_C (S) &=& \max(S_T - S_{\min}, 0) \\ f_P (S) &=& \max(S_{\max} - S_T, 0) \end{eqnarray*}

The option is European since it can only be exercised at the expiry time, $T$.

We won't delve into the theoretical aspects of its pricing in this article, instead I'll refer you to Musiela[1], which makes use of the Black-Scholes arbitrage-free pricing methodology in order to price the options.

We will make the usual assumptions of the Black-Scholes world. That is, we will assume the existence of the continuously-compounding risk-free interest rate $r > 0$. The underlying will also possess a constant volatility, $\sigma > 0$. We will also set $M$ and $m$ to be the maximum and minimum prices of the underlying asset over the option duration:

\begin{eqnarray*} M &=& \smash{\displaystyle\max_{0 \leq \tau \leq T}} S_{\tau} \end{eqnarray*} \begin{eqnarray*} m &=& \smash{\displaystyle\min_{0 \leq \tau \leq T}} S_{\tau} \end{eqnarray*}

Then the prices of Floating Strike European Lookback Calls and Puts is given by:

\begin{eqnarray*} L_C (T) &=& SN(a_1(S,m)) - me^{-rT}N(a_2(S,m)) - \frac{S \sigma^2}{2r}\left( N(-a_1(S,m)) - e^{-rT}(m/S)^{\frac{2r}{\sigma^2}}N(-a_3(S,m)) \right) \\ L_P (T) &=& -SN(-a_1(S,M)) + Me^{-rT}N(-a_2(S,M)) + \frac{S \sigma^2}{2r}\left( N(a_1(S,M)) - e^{-rT}(M/S)^{\frac{2r}{\sigma^2}}N(a_3(S,M)) \right) \end{eqnarray*}

Where $N(.)$ is the cumulative distribution function of the standard normal distribution. The formula for $N$ is given by:

\begin{eqnarray*} N(x) = \frac{1}{\sqrt{2 \pi}} \int^x_{-\infty} e^{-t^{2} /2} dt \end{eqnarray*}

And $a_1$, $a_2$ and $a_3$ are given by (with $H > 0$, $S > 0$):

\begin{eqnarray*} a_1(S,H) &=& \frac{log(S/H)+(r+\frac{1}{2}\sigma^2)T}{\sigma \sqrt{T}} \\ a_2(S,H) &=& a_1(S,H) - \sigma \sqrt{T} \\ a_3(S,H) &=& a_1(S,H) - \frac{2r\sqrt{T}}{\sigma} \end{eqnarray*}

C++ Implementation

We can now implement these formulae in C++. The full listing is given below:

#include <iostream>
#include <cmath>

// An approximation to the cumulative distribution function
// for the standard normal distribution
// Note: This is a recursive function
double norm_cdf(const double& x) {
    double k = 1.0/(1.0 + 0.2316419*x);
    double k_sum = k*(0.319381530 + k*(-0.356563782 + k*(1.781477937 + k*(-1.821255978 + 1.330274429*k))));

    if (x >= 0.0) {
        return (1.0 - (1.0/(pow(2*M_PI,0.5)))*exp(-0.5*x*x) * k_sum);
    } else {
        return 1.0 - norm_cdf(-x);
    }
}

// The function "a_1" which forms part of the analytical price
double a_1(const double& S,    // Spot price
           const double& H,    // Min/max of asset price over period
           const double& r,    // Risk free rate
           const double& v,    // Volatility of underlying asset
           const double& T) {  // Time to expiry
  double num = log(S/H) + (r + 0.5*v*v) * T;
  double denom = v * sqrt(T);
  return num/denom;
}

// The function "a_2" which forms part of the analytical price
double a_2(const double& S, 
           const double& H,
           const double& r,
           const double& v,
           const double& T) {
  return a_1(S, H, r, v, T) - v * sqrt(T);
}

// The function "a_3" which forms part of the analytical price
double a_3(const double& S, 
           const double& H,
           const double& r,
           const double& v,
           const double& T) {
  return a_1(S, H, r, v, T) - (2.0*r*sqrt(T)/v);
}

// Pricing a Lookback European Call option
double lookback_call(const double& S,
                     const double& m,  // Minimum price of asset over period
                     const double& r,
                     const double& v,
                     const double& T) {
  double a1 = a_1(S,m,r,v,T);
  double a2 = a_2(S,m,r,v,T);
  double a3 = a_3(S,m,r,v,T);

  double term1 = S * norm_cdf(a1);
  double term2 = m * exp(-r*T) * norm_cdf(a2);
  double mult = S*v*v/(2.0*r);
  double term3 = norm_cdf(-a1) - exp(-r*T) * pow((m/S),((2*r)/(v*v))) * norm_cdf(-a3);
  
  return term1 - term2 - mult * term3;
}

// Pricing a Lookback European Put option
double lookback_put(const double& S,
                    const double& M,  // Maximum price of asset over period
                    const double& r,
                    const double& v,
                    const double& T) {
  double a1 = a_1(S,M,r,v,T);
  double a2 = a_2(S,M,r,v,T);
  double a3 = a_3(S,M,r,v,T);
  
  double term1 = -S * norm_cdf(-a1);
  double term2 = M * exp(-r*T) * norm_cdf(-a2);
  double mult = S*v*v/(2.0*r);
  double term3 = norm_cdf(a1) - exp(-r*T) * pow((M/S),((2*r)/(v*v))) * norm_cdf(a3);

  return term1 + term2 + mult * term3;
}

int main(int argc, char **argv) {
  double S = 100.0;    // Spot price
  double m = 90.0;     // Minimum spot over period 
  double M = 110.0;    // Maximum spot over period
  double r = 0.1;      // Risk-free rate (10%)
  double v = 0.3;      // Volatility of the underlying (30%)
  double T = 1.0;      // One year until expiry

  // Calculate the lookback prices for calls and puts
  double lookback_call_price = lookback_call(S, m, r, v, T);
  double lookback_put_price = lookback_put(S, M, r, v, T);

  // Finally we output the parameters and prices
  std::cout << "Underlying:      " << S << std::endl;
  std::cout << "Min spot:        " << m << std::endl;
  std::cout << "Max spot:        " << M << std::endl;
  std::cout << "Risk-Free Rate:  " << r << std::endl;
  std::cout << "Volatility:      " << v << std::endl;
  std::cout << "Maturity:        " << T << std::endl;

  std::cout << "Lookback Call Price:     " << lookback_call_price << std::endl;
  std::cout << "Lookback Put Price:      " << lookback_put_price << std::endl;

  return 0;
}

We need to include iostream for input/output and cmath for the log, exp, pow and sqrt mathematical functions:

#include <iostream>
#include <cmath>

Now we make use of an approximation to the cumulative distribution function of the standard normal distribution. This can be found in Joshi[2]:

double norm_cdf(const double& x) {
    double k = 1.0/(1.0 + 0.2316419*x);
    double k_sum = k*(0.319381530 + k*(-0.356563782 + k*(1.781477937 + k*(-1.821255978 + 1.330274429*k))));

    if (x >= 0.0) {
        return (1.0 - (1.0/(pow(2*M_PI,0.5)))*exp(-0.5*x*x) * k_sum);
    } else {
        return 1.0 - norm_cdf(-x);
    }
}

We then implement the $a_i$ functions. Notice that they each require the risk-free rate $r$, the volatility $\sigma$ (which is written as v in the code) and $T$, the time to maturity:

// The function "a_1" which forms part of the analytical price
double a_1(const double& S,    // Spot price
           const double& H,    // Min/max of asset price over period
           const double& r,    // Risk free rate
           const double& v,    // Volatility of underlying asset
           const double& T) {  // Time to expiry
  double num = log(S/H) + (r + 0.5*v*v) * T;
  double denom = v * sqrt(T);
  return num/denom;
}

// The function "a_2" which forms part of the analytical price
double a_2(const double& S, 
           const double& H,
           const double& r,
           const double& v,
           const double& T) {
  return a_1(S, H, r, v, T) - v * sqrt(T);
}

// The function "a_3" which forms part of the analytical price
double a_3(const double& S, 
           const double& H,
           const double& r,
           const double& v,
           const double& T) {
  return a_1(S, H, r, v, T) - (2.0*r*sqrt(T)/v);
}

Then we price the options themselves. Here is the code for the call option (the put is similar, so won't be outlined here). I've split the complicated formula into its constituent terms, in order to make it readable within the code:

double lookback_call(const double& S,
                     const double& m,  // Minimum price of asset over period
                     const double& r,
                     const double& v,
                     const double& T) {
  double a1 = a_1(S,m,r,v,T);
  double a2 = a_2(S,m,r,v,T);
  double a3 = a_3(S,m,r,v,T);

  double term1 = S * norm_cdf(a1);
  double term2 = m * exp(-r*T) * norm_cdf(a2);
  double mult = S*v*v/(2.0*r);
  double term3 = norm_cdf(-a1) - exp(-r*T) * pow((m/S),((2*r)/(v*v))) * norm_cdf(-a3);
  
  return term1 - term2 - mult * term3;
}

Finally, the main function carries out the actual pricing and outputs the results:

int main(int argc, char **argv) {
  double S = 100.0;    // Spot price
  double m = 90.0;     // Minimum spot over period 
  double M = 110.0;    // Maximum spot over period
  double r = 0.1;      // Risk-free rate (10%)
  double v = 0.3;      // Volatility of the underlying (30%)
  double T = 1.0;      // One year until expiry

  // Calculate the lookback prices for calls and puts
  double lookback_call_price = lookback_call(S, m, r, v, T);
  double lookback_put_price = lookback_put(S, M, r, v, T);

  // Finally we output the parameters and prices
  std::cout << "Underlying:      " << S << std::endl;
  std::cout << "Min spot:        " << m << std::endl;
  std::cout << "Max spot:        " << M << std::endl;
  std::cout << "Risk-Free Rate:  " << r << std::endl;
  std::cout << "Volatility:      " << v << std::endl;
  std::cout << "Maturity:        " << T << std::endl;

  std::cout << "Lookback Call Price:     " << lookback_call_price << std::endl;
  std::cout << "Lookback Put Price:      " << lookback_put_price << std::endl;

  return 0;
}

The output of the code is as follows:

Underlying:      100
Min spot:        90
Max spot:        110
Risk-Free Rate:  0.1
Volatility:      0.3
Maturity:        1
Lookback Call Price:     27.382
Lookback Put Price:      21.6149

The next article to follow on from this one on Lookback Options will consider the pricing via Monte Carlo rather than the analytical formulae.

References

comments powered by Disqus

Just Getting Started with Quantitative Trading?

3 Reasons to Subscribe to the QuantStart Email List:

No Thanks, I'll Pass For Now

1. Quant Trading Lessons

You'll get instant access to a free 10-part email course packed with hints and tips to help you get started in quantitative trading!

2. All The Latest Content

Every week I'll send you a wrap of all activity on QuantStart so you'll never miss a post again.

3. No Spam

Real, actionable quant trading tips with no nonsense.