Annotated versions of SVPrice.html, SVPrice.java, and SVPriceEngine.java

This homework's main innovation is the use of simulation to compute option prices as expectations, and most of the comments are in the price engine that does this.

For Homework 5 of Computational Finance by P Dybvig

Quick overview

Before going into details, here is a quick look at how the program is structured.

file SVPrice.html: similar to previous homeworks 0-2 and 4;
     calls the applet and prints a warning

file SVPrice.java:

public class SVPrice extends Applet {
  ...}

--> This creates a button in the browser you press to get
    a new Frame (window) containing the input and output
    Panels.  This is almost identical to the one in homework
    4.

class ValuePlotFrame extends Frame {
  ...}

--> This creates the new Frame.  It contains a top Panel
    (called outputs) at the top that gives a table of
    option values as a function of volatility and a
    bottom Panel (called nputs) and input cells at the
    bottom.  Nothing in its structure should be surprising.

file SVPriceEngine.java:

public class SVPriceEngine {
  ...}

--> This is the option pricing engine.  Most of the comments
    are in this part, which is the only part that is much
    different from the other homeworks.

SVPrice.html

<HTML>
<HEAD>
<TITLE>Asset Allocation Simulation</TITLE>
</HEAD>
<BODY>
<P>
<APPLET CODE=SVPrice.class WIDTH=300 HEIGHT=50>
</APPLET>
</p>
<p> WARNING:  The number of simulations equals 100 for
testing only!  It takes many more draws (perhaps 100,000)
to obtain an accurate value.</p>

--> The <p> and </p> tags indicate the start and
    end of a paragraph.

</BODY>
</HTML>

SVPrice.java

import java.applet.*;
import java.awt.*;

public class SVPrice extends Applet {
  SVPriceFrame svpf;
  Button startASimu;
    public SVPrice() {
      setLayout(new GridLayout(1,1));
      add(startASimu = new Button("Compute Option Prices Now"));
      svpf = new SVPriceFrame();
      svpf.setTitle("Stochastic Volatility Option Pricing Simulation");
      svpf.pack();
      svpf.resize(500,400);}
    public boolean action(Event e, Object arg) {
      if(e.target == startASimu) {
        svpf.reset();
        return true;}
      return false;}}

class SVPriceFrame extends Frame {
  double[] initSigmas;
  Label[] opvals; 
  TextField r,sigmabar,kappa,sigmasigma,ttm,s0,strike,nsimu,nper;
  Button newRandomDraws,resetInputs;
  SVPriceEngine vroom;
  int i;
  public SVPriceFrame() {
    setLayout(new BorderLayout());
    Panel outputs = new Panel();
      outputs.setLayout(new GridLayout(6,2));
      outputs.add(new Label("Initial Sigma"));
      outputs.add(new Label("Call Price"));
      opvals = new Label[5];
      initSigmas = new double[5];
      for(i=0;i<5;i++) {
        initSigmas[i] = .3 + .05*((double) i);
        outputs.add(new Label(Double.toString(initSigmas[i])));
        outputs.add(opvals[i] = new Label("**********"));
        opvals[i].setForeground(Color.yellow);}

--> Each row in the outputs Panel contains a Label giving
    the starting standard deviation and a named Label (an
    element of opvals, an array of Labels) to contain the
    option price.  When no option price has been computed
    yet or the option price is not current, it is colored
    yellow.

    add("North",outputs);
    Panel inputs = new Panel();
      inputs.setLayout(new GridLayout(8,3));
      inputs.add(new Label("stock price")); 
      inputs.add(new Label("strike price")); 
      inputs.add(new Label("number of simus"));   
      inputs.add(s0=new TextField("50",10));
      inputs.add(strike=new TextField("60",10));  
      inputs.add(nsimu=new TextField("100",10));
      inputs.add(new Label("interest (%/yr)"));
      inputs.add(new Label("avg sigma (%/yr)"));
      inputs.add(new Label("kappa (%/yr)"));
      inputs.add(r=new TextField("5",10));
      inputs.add(sigmabar=new TextField("40",10));
      inputs.add(kappa=new TextField("3.0",10));
      inputs.add(new Label("vol of vol (%/yr)"));
      inputs.add(new Label("time to mat (yr)"));
      inputs.add(new Label("# subperiods"));
      inputs.add(sigmasigma=new TextField("10",10));
      inputs.add(ttm=new TextField("5",10));
      inputs.add(nper=new TextField("25",10));
      inputs.add(newRandomDraws = new Button("Compute Prices"));
      inputs.add(new Label(""));
      inputs.add(new Label(""));
      inputs.add(resetInputs = new Button("Reset Parameters"));
    add("South",inputs);
    vroom = new SVPriceEngine();}
  public void startSimu() {
    newRandomDraws.setLabel("-- Computing --");
    newRandomDraws.setForeground(Color.red);

--> Changing the color and text on the the button to indicate
    we are computing...

    for(i=0;i<5;i++) {
      opvals[i].setForeground(Color.yellow);}

--> and changing the color on each option value to indicate it
    is not current.

    for(i=0;i<5;i++) {
      vroom.newPars(text2double(ttm),(int) text2double(nper),
        text2double(r)/100.0,initSigmas[i],text2double(sigmabar)/100.0,
        text2double(kappa)/100.0,text2double(sigmasigma)/100.0);
      opvals[i].setText(String.valueOf((float)
        vroom.eurCall(text2double(s0),text2double(strike),
          (long) text2double(nsimu))));
      opvals[i].setForeground(Color.black);}

--> For each table entry, compute the option value, put it in the
    table, and make it black.

    newRandomDraws.setLabel("Compute Prices");
    newRandomDraws.setForeground(Color.black);}

--> Change the "Compute Prices" button back to its normal color and
    text.

  public void reset() {
    r.setText("5");
    sigmabar.setText("40");
    kappa.setText("3.0");
    sigmasigma.setText("10");
    ttm.setText("5");
    nper.setText("25");
    s0.setText("50");
    strike.setText("60");
    nsimu.setText("100");
    show();}

--> This reset() method sets the values back to their defaults
    and displays the Frame, but does not perform the simulations.

  public boolean action(Event e, Object arg) {
    if(e.target == newRandomDraws) {
      startSimu();
      return true;}
    if(e.target == resetInputs) {
      reset();
      return true;}
    return false;}
  public boolean handleEvent(Event event) {
    if(event.id == Event.WINDOW_DESTROY) {
      dispose();}
    return super.handleEvent(event);}

  double text2double(TextField tf) {
    return Double.valueOf(tf.getText()).doubleValue();}}

SVPriceEngine.java

public class SVPriceEngine {
  double npers, tinc, r1per, sigma0, meansigma, sigmasigma, kappa,
    c0, c1, c2, c3, c4;
  double stockP, sigma;

  public SVPriceEngine(){}

  public void newPars(double ttm,int nper,double r,double initstd,
    double meanstd, double k, double sigstd) {
    npers = nper;
    tinc = ttm/(double) nper;
    r1per = 1.0 + r*tinc;
    sigma0 = initstd;
    meansigma = meanstd;
    sigmasigma = sigstd;
    kappa = k;
    c0 = kappa * tinc * meansigma;
    c1 = 1.0 - kappa * tinc;
    c2 = 1.0 - sigmasigma * Math.sqrt(tinc)*0.5*Math.sqrt((double) 12);
    c3 = sigmasigma * Math.sqrt(tinc) * Math.sqrt((double) 12);
    c4 = Math.sqrt(tinc)*Math.sqrt((double) 12);}

--> As usual, the new parameters method converts the parameters
    into per-period versions that are more convenient to use below.
    The last five constants,  c0, c1, ... c4, are intermediate
    calculations used in computing stock returns. Doing these
    intermediate calculations once here instead of once in each
    period in each simulation (in stocktotret below) is a big
    time saver.

public double eurCall(double stock,double strike,long nsimu) {
  long i,n;
  double x;
  x=0.0;

--> The call price is the average over all the simulations (done
    in the outer for() loop below) of the terminal option value,
    discounted at the risk-free rate.  This works because (1) the
    interest rate is nonstochastic and (2) the mean under
    risk-neutral probabilities (equals the riskfree rate) was
    used in the simulations.

  for(n=0;n<nsimu;n++) {
    stockP = stock;
    sigma = sigma0;
    for(i=0;i<npers;i++) {
      stockP *= stocktotret();
      }

--> The stock price is stepped through the periods by this inner
    for() loop.  The stochastic volatility is computed and stored
    within the stocktotret() method.

    x += Math.max(stockP-strike,0);}

--> At this point, x contains the total across simulations of the
    option payoffs.

  return(x/((double) nsimu * Math.pow(r1per,(double) npers)));}

--> Dividing x by nsimu to get the average and discounting by the
    appropriate discount factor yields the option price.

double stocktotret() {
  double stockret;
//
//  The following straightforward computations are algebraically
//  the same as the ones used below but are much slower because
//  many more calculations are performed in each pass through the
//  nested for loops in eurCall.
//
//  stockret = r1per + sigma * Math.sqrt(tinc) *

--> Since we are in risk-neutral probabilities, the mean return
    we use is the riskfree rate (recall that r1per = 1 + r*tinc).
    sigma * Math.sqrt(tinc) is the one-period standard deviation,
    which multiplies a ...

//    (Math.random()-0.5) * Math.sqrt((double) 12);

--> uniform random variable with mean 0 and standard deviation 1.
    (Math.random() simulates a uniform random variable on [0,1]
    which has a mean of 0.5 and a standard deviation of
    1.0/Math.sqrt((double) 12);)

//  sigma = (kappa*tinc * meansigma + (1.0 - kappa * tinc) * sigma) *

--> These terms capture mean reversion of sigma to the long-run
    average value, meansigma, at a rate kappa per unit time.  This
    is multiplied by...

//    (1 + sigmasigma * Math.sqrt(tinc) * 

--> one plus a random part with standard deviation
    sigmasigma * Math.sqrt(tinc) which multiplies a...

//    (Math.random()-0.5) * Math.sqrt((double) 12));

--> simulated uniform random variable with mean 0 and standard
    deviation 1.

--> Having multiplicative noise means that the random part of
    the shock to volatility is large when volatility is large
    and small when volatility is small.  An additive shock
    would have the same size whatever the size of volatility.

//  return(stockret);}
//

--> The version of the stockret and sigma in the comment above are
    more intepretable, but the "reduced form" versions below are
    computed more quickly (since much of the work was already done
    in computing c0, c1, ... c4 in newPars).  Both versions are
    algebraically equivalent (as can be seen by matching
    coefficients).

  stockret = r1per + sigma * c4 * (Math.random() - 0.5);
  sigma = (c0 + c1 * sigma) * (c2 + c3 * Math.random());
  return(stockret);}}