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
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.
<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>
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();}}
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);}}