Programming/Kdb/Labs/Option pricing
Background: the Black-Scholes formulae
Recall the celebrated Black-Scholes equation
Here
- is a time in years; we generally use as now;
- is the value of the option;
- is the price of the underlying asset at time ;
- is the volatility — the standard deviation of the asset's returns;
- is the annualized risk-free interest rate, continuously compounded;
- is the annualized (continuous) dividend yield.
The solution of this equation depends on the payoff of the option — the terminal condition. In particular, if at the time of expiration, , the payoff is given by , in other words, the option is a European call option, then the value of the option at time is given by the Black-Scholes formula for the European call:
where is the time to maturity, is the forward price, and
and
Here we have used to denote the standard normal cumulative distribution function,
Similarly, if the payoff is given by , in other words, the option is a European put option, then the value of the option at time is given by the Black-Scholes formula for the European put:
We will implement the formulae for the European call and European put in q. However, our first task is to implement .
Task 1: Implementing the standard normal cumulative distribution function
As mentioned in the Handbook of Mathematical Functions, can be approximated by
where
Can you implement this function in q?
First, we need
pi:acos -1;
One (terse) implementation would be
normal_cdf:{abs(x>0)-(exp[-.5*x*x]%sqrt 2*pi)*t*.31938153+t*-.356563782+t*1.781477937+t*-1.821255978+1.330274429*t:1%1+.2316419*abs x};
Task 2: Implement the Black-Scholes formula for the European call and put
Equipped with our implementation of normal_cdf, can you implement the Black-Scholes formula for the European call?
First, we need
compute_d1:{[S;K;r;q;sigma;tau](log[S%K]+((r-q)+.5*sigma*sigma)*tau)%sigma*sqrt tau};
Then,
call_price:{[S;K;r;q;sigma;tau] d1:compute_d1[S;K;r;q;sigma;tau]; d2:d1-sigma*sqrt tau; F:S*exp tau*r-q; (exp neg r*tau)*(F*normal_cdf d1)-K*normal_cdf d2};
and
put_price:{[S;K;r;q;sigma;tau] d1:compute_d1[S;K;r;q;sigma;tau]; d2:d1-sigma*sqrt tau; F:S*exp tau*r-q; (exp neg r*tau)*(K*normal_cdf neg d2)-F*normal_cdf neg d1};
We can test these implementations on a few sets of parameters, for example
call_price[100f;105f;.05;.07;.1;.5]
gives 0.7991363, whereas
put_price[100f;105f;.05;.07;.1;.5]
gives 6.646135.
Background: a simple Monte Carlo model
The Black-Scholes equation may not have analytic solutions for all derivatives that we are interested in. However, it may still be possible to solve it numerically using Monte Carlo methods.
The model for stock price evolution is
where is the drift. The Black-Scholes pricing theory then tells us that the price of a vanilla option with pay-off is equal to
where the expectation is taken under the associated risk-neutral process,
We solve this equation by passing to the log and using Ito's lemma; we compute
As this process is constant-coefficient, it has the solution
Since is a Brownian motion, is distributed as a Gaussian with mean zero and variance , so we can write
and hence
or equivalently,
The price of a vanilla option is therefore equal to
The objective of our Monte Carlo simulation is to approximate this expectation by using the law of large numbers, which tells us that if are a sequence of identically distributed independent random variables, then with probability 1 the sequence
converges to .
So the algorithm to price a call option by Monte Carlo is clear. We draw a random variable, , from an distribution and compute
We do this many times and take the average. We then multiply this average by and we are done.
Task 3: Generating standard Gaussian random variates
To generate 10 standard uniform random variates in q, we can simply use
10?1f
How can we convert these standard uniform random variates into standard Gaussian random variates?
One approach is to use the Box-Muller transform. Suppose and are independent samples chosen from the uniform distribution on the unit interval . Let
and
Then and are independent random variables with a standard normal distribution.
Equipped with Box-Muller transform, implement a function in q that, given a number , will return standard Gaussian random variates.
normal_variates:{$[x=2*n:x div 2;raze sqrt[-2*log n?1f]*/:(sin;cos)@\:(2*pi)*n?1f;-1_.z.s 1+x]}
Task 4: Implement a Monte Carlo pricer
We are now ready to implement a Monte Carlo pricer. We can use it to price more complicated options than the European call and put. However, the European call and put are convenient — the analytic solutions enable us to test our Monte Carlo pricer.
mc:{[S;r;q;sigma;T;path_count;payoff] variance:sigma*sigma*T; root_variance:sqrt[variance]; ito_correction:-.5*variance; moved_spot:S*exp ito_correction+(r-q)*T; gaussians:normal_variates[path_count]; this_spot:moved_spot*exp[root_variance*gaussians]; exp[neg T*r]*avg payoff this_spot};
Let's define the European call and put payoffs:
call_payoff:{[K;S]0|S-K} put_payoff:{[K;S]0|K-S}
We can now test our Monte Carlo pricer on these payoffs and confirm that we get similar answers to those produced by the analytic formulae:
mc[100f;.05;.07;.1;.5;100000;call_payoff[105f]] mc[100f;.05;.07;.1;.5;100000;put_payoff[105f]]