jopt.java


/*

  JOPT.JAVA
  =========
  (c) Paul Griffiths 1999
  Email: mail@paulgriffiths.net

  Java applet to chart option prices over a given range,
  in relation to a given independent variable.

*/


import java.awt.*;
import java.applet.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;


/*  Main application class  */

public class jopt extends Applet {
    int xExtent, yExtent;
    Option option;
    dialogOption dialog;


    /*  Initialize applet  */

    public void init() {
	option = new Option();
	dialog = new dialogOption("Properties");
	xExtent = 300;
	yExtent = 300;
    }


    /*  paint() method  */

    public void paint( Graphics g ) {
	g.setColor(Color.white);
	g.fillRect(0, 0, xExtent, yExtent);
	option.paint(g);
    }


    /*  Update option from HTML form values  */

    public void updateOpt(float stock, float strike, float sd, float rate, float time, int type, int iv) {
	option.update((double)stock, (double)strike, (double)sd, (double)rate, (double)time, type, iv);
	repaint();
    }


    /*  Invoke properties dialog box on right mouse button down  */

    public boolean mouseDown(Event evt, int x, int y) {
	option = dialog.show(option);
	repaint();
	return true;
    }
}


/*  option class  */

class Option {
    double stock, strike, sd, rate, time, price;
    double startX, startY, scaleX, scaleY;
    int type, iv;
    Color color;
    boolean manScale;


    /*  Constructor, populate data members with default values  */

    public Option() {
	stock  = 42;
	strike = 40;
	sd     = .2;
	rate   = .10;
	time   = 0.5;
	type   = 1;
	iv     = 2;
	price  = price();
	color  = Color.red;
	manScale = false;
    }


    /*  Update data members  */
    
    public void update(double s, double x, double standev, double r, double t, int tp, int v) {
	stock  = s;
	strike = x;
	sd     = standev;
	rate   = r;
	time   = t;
	type   = tp;
	iv     = v;
	price  = price();
    }

    
    /*  Draw chart of option prices  */

    public void paint( Graphics g ) {
	int height = 190;
	int width  = 240;
	int orgX = 30;
	int orgY = 30;
	String axisX = "";
	Font font = g.getFont();
	FontMetrics fontMetrics = g.getFontMetrics(font);
	orgX = 15 + fontMetrics.stringWidth("8.88");


	/*  Print price  */

	g.setColor(Color.black);
	g.drawString("Price: " + round(price, 3), orgX, 
		     orgY + height + 10 + fontMetrics.getAscent() * 3);


	/*  If we are not manually scaling the chart,
	    'guess' at a sensible default scale        */

	if ( ! manScale ) {
	    startY = 0;
	    scaleY = price * 3;


	    /*  Calculate according to independent variable  */
	    
	    switch ( iv ) {
	    case 0:
		scaleX = 16 * Math.abs(stock - strike);
		startX = stock - (scaleX / 2);
		axisX = "S";
		break;
		
	    case 1:
		scaleX = 4 * Math.abs(stock - strike);
		startX = strike - scaleX / 2;
		axisX = "X";
		break;
		
	    case 2:
		scaleX = 2 * sd;
		startX = 0;
		axisX = "s";
		break;
		
	    case 3:
		scaleX = 2 * rate;
		startX = 0;
		axisX = "r";
		break;
		
	    case 4:
		scaleX = 2 * time;
		startX = 0;
		axisX = "t";
		break;
		
	    default:
		startX = startY = scaleX = scaleY = 0;
		break;
	    }
	}
	
	/*  Draw axes and label them  */

	g.drawLine(orgX, orgY, orgX, orgY + height);
	g.drawLine(orgX, orgY+ height, orgX + width, orgY + height);
	g.drawString(axisX, orgX + width + fontMetrics.stringWidth("A"), 
		     orgY + height + (fontMetrics.getAscent() / 2));
	g.drawString("P", orgX - (fontMetrics.stringWidth("P") / 2), 
		     orgY - (int)((double)fontMetrics.getAscent() / 2));


	/*  Draw scales on X axis  */
 
        double nLabel;
	String strLabel;
	int n = 0;
	for ( int i = orgX; i <= orgX + width; i += width/5 ) {
	    g.drawLine(i, orgY + height, i, orgY + height + 5);

	    nLabel = ( startX + ( ( scaleX / 5 ) * n++ ) );
	    if ( iv == 2 || iv == 3 ) {
		nLabel *= 100;
	    }
	    strLabel = round( nLabel, 1);

	    g.drawString(strLabel, i - fontMetrics.stringWidth(strLabel) / 2,
			 orgY + height + 10 + fontMetrics.getAscent());
	    
	}

	/*  Draw scales on Y axis  */

	n = 0;
	for ( int i = orgY + height; i >= orgY; i -= height/5 ) {
	    g.drawLine(orgX - 5, i, orgX, i);

	    nLabel = ( startY + ( ( scaleY / 5 ) * n++ ) );
	    strLabel = round( nLabel, 1);

	    g.drawString(strLabel, orgX - 10 - (fontMetrics.stringWidth(strLabel)), 
			 i + (int)(fontMetrics.getAscent() / 2));
	    
	}


	/*  Draw graph  */

	g.setColor(color);
	double oldValue = 0;
	boolean first = true;
	int y, oldX, oldY;
	y = oldY = oldX = 0;

	
	/*  Loop for all x-axis values  */

	for ( int x = orgX; x <= orgX + width; x++ ) {


	    /*  Set independent variable according
		to position on x-axis               */

	    switch ( iv ) {
	    case 0:
		if ( first ) oldValue = stock;
		stock = startX + (((double)( x - orgX ) / width) * scaleX);
		break;

	    case 1:
		if ( first ) oldValue = strike;
		strike = startX + (((double)( x - orgX ) / width) * scaleX);
		break;

	    case 2:
		if ( first ) oldValue = sd;
		sd = startX + (((double)( x - orgX ) / width) * scaleX);
		break;

	    case 3:
		if ( first ) oldValue = rate;
		rate = startX + (((double)( x - orgX ) / width) * scaleX);
		break;

	    case 4:
		if ( first ) oldValue = time;
		time = startX + (((double)( x - orgX ) / width) * scaleX);
		break;

	    default:
		oldValue = 0;
	    }


	    /*  Calculate y-axis value by calculating new price  */

	    y = orgY + height - (int)((price() / scaleY) * height);


	    /*  Don't draw line if first point, or outside chart area  */
	    
	    if ( first || y < orgY ) {
		oldX = x;
		oldY = y;
		first = false;
	    }
	    else {
		g.drawLine(oldX, oldY, x, y);
		oldX = x;
		oldY = y;
	    }
	}

	
	/*  Reset independent variable to original value  */

	switch ( iv ) {
	case 0:
	    stock = oldValue;
	    break;
	
	case 1:
	    strike = oldValue;
	    break;
	
	case 2:
	    sd = oldValue;
	    break;
	
	case 3:
	    rate = oldValue;
	    break;
	
	case 4:
	    time = oldValue;
	    break;
	}
	
    }


    /*  Function returns a rounded double value in String format  */

    private String round( double n, int places ) {
	int res = (int) ( n * Math.pow(10, places) + 0.5 );
	String r = String.valueOf(res);
	String result = r.substring(0, r.length() - places);
	result += ".";
	result += r.substring(r.length() - places, r.length());
	if ( result.charAt(0) == '.' ) {
	    result = "0" + result;
	}
	return result;
    }


    /*  Function calculates the option price  */

    private double price() {
	double result;

	if ( type == 1 ) {
	    result  = stock * calcN( calcD(1) );
	    result -= strike * Math.exp( -rate * time ) * calcN( calcD(2) );
	    return result;
	}
	else if ( type == 2 ) {
	    result  = strike * Math.exp( -rate * time ) * calcN( -(calcD(2)) );
	    result -= stock * calcN( -(calcD(1) ));
	    return result;
	}
	else {
	    return 0;
	}
    }


    /*  Calculates d1 and d2 in Black-Scholes formula  */

    private double calcD( int D ) {
	double result;

	if ( D == 1 ) {
	    result = ( rate + ( Math.pow(sd, 2) / 2 ) ) * time;
	}
	else if ( D == 2 ) {
	    result = ( rate - ( Math.pow(sd, 2) / 2 ) ) * time;
	}
	else {
	    return 0;
	}

	result += Math.log( stock / strike );
	result /= sd * Math.sqrt( time );

	return result;
    }

    
    /*  Calculates cumulative normal probability distribution for
	variable with mean of zero and standard deviation of one   */

    private double calcN(  double n ) {
	double result;

	double x = Math.abs( n );

	double l  = 0.33267d;
	double a1 = 0.4361836d;
	double a2 = -0.1201676d;
	double a3 = 0.9372980d;
	double k  = 1 / ( 1 + ( x * l ) );

	double N = ( 1 / Math.sqrt( Math.PI * 2 ) );
	N *= Math.exp( - (Math.pow(x, 2) / 2));

	result = N * ((a1*k) + (a2*Math.pow(k,2)) + (a3*Math.pow(k,3)));
	result = 1 - result;

	if ( n >= 0 ) {
	    return result;
	}
	else {
	    return ( 1 - result );
	}
    }


    /*  Functions return left/right/upper/lower
	extents of X and Y axes                  */

    public String getXL() {
	if ( iv == 2 || iv == 3 ) {
	    return round(startX * 100, 1);
	}
	else {
	    return round(startX, 1);
	}
    }

    public String getXR() {
	if ( iv == 2 || iv == 3 ) {
	    return round((startX + scaleX) * 100, 1);
	}
	else {
	    return round(startX + scaleX, 1);
	}
    }

    public String getYD() {
	return round(startY, 1);
    }

    public String getYU() {
	return round(startY + scaleY, 1);
    }


    /*  Manually set the scale according to
	values supplied from the dialog box  */

    public void setScale(double x1, double x2, double y1, double y2) {
	manScale = true;
	startX = x1;
	scaleX = x2 - x1;
	startY = y1;
	scaleY = y2 - y1;
	if ( iv ==2 || iv == 3 ) {
	    startX /= 100;
	    scaleX /= 100;
	}
    }


    /*  Revert to automatic scaling  */

    public void resetScale() {
	manScale = false;
    }

}


/*  Class for properties dialog box  */

class dialogOption extends Frame implements ActionListener {
    Dialog dialog;
    TextField textField[];
    Label label[];
    Button OKButton;
    Checkbox manScale;
    boolean badInput;
    Option option, oldOption;


    /*  Constructor  */

    dialogOption( String title ) {
	dialog = new Dialog(this, title, true);
	GridBagLayout layout = new GridBagLayout();
	manScale = new Checkbox("Set scale manually?", null, true);
	dialog.setLayout(layout);
	badInput = false;


	/*  Create and layout controls  */

	GridBagConstraints cons = new GridBagConstraints();
	cons.fill = GridBagConstraints.BOTH;

	textField = new TextField[4];
	label = new Label[6];
	
	textField[0] = new TextField("", 10);
	textField[1] = new TextField("", 10);
	textField[2] = new TextField("", 10);
	textField[3] = new TextField("", 10);
	label[0] = new Label("X left:", Label.LEFT);
	label[1] = new Label("X right:", Label.LEFT);
	label[2] = new Label("Y bottom:", Label.LEFT);
	label[3] = new Label("Y up:", Label.LEFT);
	label[4] = new Label("", Label.LEFT);
	label[5] = new Label("", Label.LEFT);
	OKButton = new Button("OK");
	OKButton.addActionListener(this);

	cons.gridwidth = GridBagConstraints.REMAINDER;
	layout.setConstraints(manScale,cons);
	dialog.add(manScale);
	cons.gridwidth = GridBagConstraints.REMAINDER;
	layout.setConstraints(label[4],cons);
	dialog.add(label[4]);
	
	for ( int i = 0; i < 4; i++ ) {
	    cons.gridwidth = GridBagConstraints.RELATIVE;
	    layout.setConstraints(label[i],cons);
	    dialog.add(label[i]);
	    cons.gridwidth = GridBagConstraints.REMAINDER;
	    layout.setConstraints(textField[i],cons);
	    dialog.add(textField[i]);
	}
	cons.gridwidth = GridBagConstraints.REMAINDER;
	layout.setConstraints(label[5],cons);
	dialog.add(label[5]);
	cons.gridwidth = GridBagConstraints.REMAINDER;
	layout.setConstraints(OKButton,cons);
	dialog.add(OKButton);
    }


    /*  Populate dialog boxes with values
	from existing Option object        */

    public Option show( Option opt ) {
	option = opt;
	oldOption = option;
	textField[0].setText(option.getXL());
	textField[1].setText(option.getXR());
	textField[2].setText(option.getYD());
	textField[3].setText(option.getYU());
	dialog.setSize(300, 300);
	dialog.setVisible(true);
	if ( badInput ) {
	    return oldOption;
	}
	else {
	    return option;
	}
    }


    /*  Hide the dialog box  */

    public void hide() {
	dialog.setVisible(false);
    }


    /*  Respond to events  */

    public void actionPerformed(ActionEvent e){
	Option tempOption = option;
	double results[];
	results = new double[4];

	for ( int i = 0; i < 4; i++ ) {
	    try {
		results[i] = Double.valueOf(textField[i].getText()).doubleValue();
	    }
	    catch (NumberFormatException ex) {
		badInput = true;
	    }
	}

	
	/*  Set chart scale according to input  */

	if ( manScale.getState() && ! badInput ) {
	    option.setScale(results[0], results[1], results[2], results[3]);
	}
	else {
	    option.resetScale();
	}
	
	hide();
    }

}
  



Please send all comments, suggestions, bug reports etc to mail@paulgriffiths.net