Homework 5
Black-Scholes Hedge Simulation

Computational Finance

Copyright © Philip H. Dybvig 1996

Scenario You are providing support to a trading group that will be selling customized call options. They are looking for some quantitative analysis that will give them a handle on how often to rebalance the underlying hedge.

Action You will be given some new programs for computing Black-Scholes prices and hedges, which you will combine with a modified version of the existing asset allocation program. The resultant simulations should give you a handle on the optimal frequency of hedging.

Concept This is a capstone assignment that combines both option pricing and simulation of a trading strategy. You will be given new programs that compute the Black-Scholes formula and the normal (Gaussian) distribution and density required for the Black-Scholes formula. You will use these programs in conjunction with the hedge simulation program to evaluate the quality of the hedge at different rebalancing frequencies. (This sort of modular construction of programs simplifies debugging and makes it easier to reuse the pieces.) Rebalancing is more important for options than for a fixed-proportions portfolio strategy (since the theoretical hedge ratio varies much more with small changes in the underlying).

Instructions

  1. Put the program files (bstest.cc, bs.cc, bs.h, gausstest.cc, gaussian.cc, gaussian.h , simu1test.cc, simu1.cc, and simu1.h ) in a directory on your machine where you want to work. Rename them as necessary.
  2. When compiling the programs, you will need to limit yourself at any point in time to one and only one of the files with a main() program, and in each case, you will require the files containing the functions called by main(), the files containing the functions called by functions called by main(), etc. Compile each of the following files containing main() separately: gausstest.cc, bstest.cc, and simu1test.cc, and check that the output makes sense.
  3. Starting with the main program in simu1test.cc, design a simulation of a trading strategy that attempts to replicate a call option using the Black-Scholes formula, starting with an initial wealth equal to the Black-Scholes price. Plot the terminal value of the trading strategy along with the terminal call value as a function of the terminal stock price. How much better does the simulation perform as the frequency of rebalancing is increased?
Extra for Experts
  1. Run the simulations again using the stochastic volatility model for stock returns (as in Homework 4).
  2. Include transaction costs in the analysis.
Challenger
  1. Analyze a strategy of partial rebalancing to keep the hedge ratio within some tolerance (m%, say) of its ideal value. Include transaction costs.
Exhibit A: the test file simu1test.cc
//
// simu1test.cc Asset Allocation test file
//
#include <iostream.h>
#include "simu1.h"

main() {
  int i;
  aa sim1;
  termvals y;
  for(i=0;i<100;i++) {
    y = sim1.fixprops(.5);
    cout << y.termstock << " " << y.termwealth << endl;}
  return(0);}
Exhibit B: the header file simu1.h
//
// simu1.h Asset Allocation Header
//

struct termvals {
  double termstock;
  double termwealth;};

class aa {
  public:
    aa(double ttm=1.0,int nper=120,double r=.05,double mean=.15,double stddev=.2);
    int restart(double ttm,int nper,double r,double mean,double stddev);
    termvals fixprops(double inrisky,double initstock=0.0,double initcash=100.0);
  private:
    int npers;
    double tinc, r1per, mean1per, std1persqrt12, wealth, stockpos, cash, stockP, stockret;
    double stocktotret();};
Exhibit C: the implementation file simu1.cc
//
// simu1.cc Asset Allocation Simulation
//
#include <math.h>
#include <stdlib.h>
#include <iostream.h>
#include "simu1.h"
#define unifrand() ((double) rand()/((double) RAND_MAX))

aa::aa(double ttm,int nper,double r,double mean,double stddev) {
  restart(ttm,nper,r,mean,stddev);}

int aa::restart(double ttm,int nper,double r,double mean,double stddev) {
  npers = nper;
  tinc = ttm/(double) nper;
  r1per = 1.0 + r*tinc;
  mean1per = 1.0 + mean*tinc;
  std1persqrt12 = sqrt((double) 12)*stddev*sqrt(tinc);
//  cout << " " << npers << " " << tinc << " " << r1per << " "
//    << mean1per << " " << std1persqrt12 << endl;
  return(0);}

termvals aa::fixprops(double inrisky,double initstock,double initcash) {
  int i;
  termvals x;
  stockpos = initstock;
  cash = initcash;
  stockP = 100.0;
//  cout << npers << endl;
  for(i=0;i<npers;i++) {
    wealth = stockpos + cash;
    stockpos = inrisky * wealth;
    cash = wealth - stockpos;
    stockret = stocktotret();
//cout << stockret << " " << tinc << " " << mean1per << " " << std1persqrt12 <<
//" " << unifrand() <<endl;
    stockP *= stockret;
    stockpos *= stockret;
    cash *= r1per;
//    cout << stockret << " " << stockP << " " << stockpos << " " << cash << endl;
    }
  x.termstock = stockP;
  x.termwealth = stockpos + cash;
  return(x);}

double aa::stocktotret() {
  return(mean1per + std1persqrt12 * (unifrand()-0.5));}
Exhibit D: the test file bstest.cc
//
// bstest.cc Black-Scholes test program
//
#include <iostream.h>
#include "bs.h"
main() {
  double S,X,r,sigma,ttm;
  valhedge callinfo;
  bs bspr;
  while(1) {
    cin >> S >> X >> r >> sigma >> ttm;
    bspr.newpars(ttm,r,sigma);
    callinfo = bspr.call(S,X);
    cout << callinfo.value << " " << callinfo.delta << endl;}}
    
Exhibit E: the header file bs.h
//
// bs.h Black-Scholes header file
//
#include "gaussian.h"
struct valhedge {
  double value;
  double delta;};
class bs {
public:
  bs(double ttm=.25,double r=.05,double sigma=.3);
  void newpars(double ttm,double r,double sigma);
  valhedge call(double S0,double X);

private:
  valhedge ezcall(double stock,double bond,double vol);
  double disc,sigsqttm;};
Exhibit F: the implementation file bs.cc
//
// bs.cc Black-Scholes Pricing Engine
//
#include <math.h>
#include "bs.h"

bs::bs(double ttm,double r,double sigma) {
  sigsqttm = sigma * sigma * ttm;
  disc = exp(-r * ttm);}

void bs::newpars(double ttm,double r,double sigma) {
  sigsqttm = sigma * sigma * ttm;
  disc = exp(-r * ttm);}

valhedge bs::ezcall(double stock,double bond,double vol) {
  distdens Nn1,Nn2;
  valhedge toreturn;
  Nn1 = Gaussian(log(stock/bond)/vol+vol/2.0);
  Nn2 = Gaussian(log(stock/bond)/vol-vol/2.0);
  toreturn.value = stock * Nn1.dist - bond * Nn2.dist;
  toreturn.delta = Nn1.dist;
  return(toreturn);}

valhedge bs::call(double S0,double X) {
  return(ezcall(S0,X*disc,sqrt(sigsqttm)));}
Exhibit G: the testtest file gausstest.cc
//
//  Gaussian distribution test file
//
#include <iostream.h>
#include "gaussian.h"
main() {
  double quantity, mean, variance;
  distdens y;
  while(1) {
    cin >> quantity >> mean >> variance;
    y = Gaussian(quantity, mean, variance);
    cout << y.dist << " " << y.dens << endl;}}
Exhibit H: the header file gaussian.h
//
// gaussian.h
//
// declarations for normal computations
//
struct distdens {
  double dist;
  double dens;};
distdens Gaussian(double quantity,double mean=0.0,double variance=1.0);
Exhibit I: the implementation file gaussian.cc
//
// gaussian.cc
//
// C++ program for normal computations
// based on the algorithm 26.2.17 in
// Abromowitz and Stegun, Handbook of Mathematical 
// Functions, Dover, NY 
//
#include <math.h>
#include "gaussian.h"
distdens Gaussian(double quantity,double mean,double variance) {
  distdens toreturn;
  double q,sigma,t;
  sigma = sqrt(variance);
  q = (quantity - mean)/sigma;
  toreturn.dens = exp(-q*q/2.0)*.3989422804014327/sigma;
  if(q>0.0) {
    t = 1.0/(1.0+0.2316419*q);
    toreturn.dist = 1.0-exp(-q*q/2.0)*.3989422804014327*
      t * (0.319381530 +
      t * (-0.356563782 +
      t * (1.781477937 +
      t * (-1.821255978 +
      t * 1.330274429))));}
  else {
    t = 1.0/(1.0-0.2316419*q);
    toreturn.dist = exp(-q*q/2.0)*.3989422804014327*
      t * (0.319381530 +
      t * (-0.356563782 +
      t * (1.781477937 +
      t * (-1.821255978 +
      t * 1.330274429))));}
  return(toreturn);}