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