File Coverage

blib/lib/Math/Business/BlackScholesMerton/NonBinaries.pm
Criterion Covered Total %
statement 111 115 96.5
branch 13 16 81.2
condition 2 3 66.6
subroutine 21 23 91.3
pod 12 12 100.0
total 159 169 94.0


line stmt bran cond sub pod time code
1             package Math::Business::BlackScholesMerton::NonBinaries;
2              
3 3     3   249760 use strict;
  3         22  
  3         97  
4 3     3   19 use warnings;
  3         5  
  3         133  
5              
6 3     3   26 use List::Util qw(min max);
  3         6  
  3         298  
7 3     3   921 use Math::CDF qw(pnorm);
  3         5842  
  3         161  
8 3     3   1045 use POSIX qw(ceil);
  3         13022  
  3         18  
9 3     3   3829 use Machine::Epsilon;
  3         781  
  3         155  
10              
11 3     3   29 use constant PI => 3.14159265359;
  3         8  
  3         6559  
12              
13             our $VERSION = '1.25'; ## VERSION
14              
15             =head1 NAME
16              
17             Math::Business::BlackScholesMerton::NonBinaries
18              
19             =head1 SYNOPSIS
20              
21             use Math::Business::BlackScholesMerton::NonBinaries;
22              
23             # price of a Call spread option
24             my $price_call_option = Math::Business::BlackScholesMerton::NonBinaries::vanilla_call(
25             1.35, # stock price
26             1.34, # barrier
27             (7/365), # time
28             0.002, # payout currency interest rate (0.05 = 5%)
29             0.001, # quanto drift adjustment (0.05 = 5%)
30             0.11, # volatility (0.3 = 30%)
31             );
32              
33             =head1 DESCRIPTION
34              
35             Contains non-binary option pricing formula.
36              
37             =cut
38              
39             =head2 vanilla_call
40              
41             USAGE
42             my $price = vanilla_call($S, $K, $t, $r_q, $mu, $sigma);
43              
44             DESCRIPTION
45             Price of a Vanilla Call
46              
47             =cut
48              
49             sub vanilla_call {
50 2     2 1 1060 my ($S, $K, $t, $r_q, $mu, $sigma) = @_;
51              
52 2         9 my $d1 = (log($S / $K) + ($mu + $sigma * $sigma / 2.0) * $t) / ($sigma * sqrt($t));
53 2         3 my $d2 = $d1 - ($sigma * sqrt($t));
54              
55 2         16 return exp(-$r_q * $t) * ($S * exp($mu * $t) * pnorm($d1) - $K * pnorm($d2));
56             }
57              
58             =head2 vanilla_put
59              
60             USAGE
61             my $price = vanilla_put($S, $K, $t, $r_q, $mu, sigma)
62              
63             DESCRIPTION
64             Price a standard Vanilla Put
65              
66             =cut
67              
68             sub vanilla_put {
69 2     2 1 1076 my ($S, $K, $t, $r_q, $mu, $sigma) = @_;
70              
71 2         11 my $d1 = (log($S / $K) + ($mu + $sigma * $sigma / 2.0) * $t) / ($sigma * sqrt($t));
72 2         4 my $d2 = $d1 - ($sigma * sqrt($t));
73              
74 2         16 return -1 * exp(-$r_q * $t) * ($S * exp($mu * $t) * pnorm(-$d1) - $K * pnorm(-$d2));
75             }
76              
77             =head2 lbfloatcall
78              
79             USAGE
80             my $price = lbfloatcall($S, $K, $t, $r_q, $mu, $sigma, $S_max, $S_min)
81              
82             DESCRIPTION
83             Price of a Lookback Float Call
84              
85             =cut
86              
87             sub lbfloatcall {
88 3     3 1 2760 my ($S, $K, $t, $r_q, $mu, $sigma, $S_max, $S_min) = @_;
89              
90 3         8 $S_max = undef;
91 3         11 my $d1 = _d1_function($S, $S_min, $t, $r_q, $mu, $sigma);
92 3         11 my $d2 = $d1 - ($sigma * sqrt($t));
93              
94 3         62 my $value = exp(-$r_q * $t) * ($S * exp($mu * $t) * pnorm($d1) - $S_min * pnorm($d2) + _l_min($S, $S_min, $t, $r_q, $mu, $sigma));
95              
96 3         11 return $value;
97             }
98              
99             =head2 lbfloatput
100              
101             USAGE
102             my $price = lbfloatcall($S, $K, $t, $r_q, $mu, $sigma, $S_max, $S_min)
103              
104             DESCRIPTION
105             Price of a Lookback Float Put
106              
107             =cut
108              
109             sub lbfloatput { # Floating Strike Put
110 3     3 1 1869 my ($S, $K, $t, $r_q, $mu, $sigma, $S_max, $S_min) = @_;
111              
112 3         8 $S_min = undef;
113 3         9 my $d1 = _d1_function($S, $S_max, $t, $r_q, $mu, $sigma);
114 3         9 my $d2 = $d1 - ($sigma * sqrt($t));
115              
116 3         29 my $value = exp(-$r_q * $t) * ($S_max * pnorm(-$d2) - $S * exp($mu * $t) * pnorm(-$d1) + _l_max($S, $S_max, $t, $r_q, $mu, $sigma));
117              
118 3         8 return $value;
119             }
120              
121             =head2 lbfixedcall
122              
123             USAGE
124             my $price = lbfixedcall($S, $K, $t, $r_q, $mu, $sigma, $S_max, $S_min)
125              
126             DESCRIPTION
127             Price of a Lookback Fixed Call
128              
129             =cut
130              
131             sub lbfixedcall {
132 2     2 1 2304 my ($S, $K, $t, $r_q, $mu, $sigma, $S_max, $S_min) = @_;
133              
134 2         6 $S_min = undef;
135 2         15 my $K_max = max($S_max, $K);
136 2         9 my $d1 = _d1_function($S, $K_max, $t, $r_q, $mu, $sigma);
137 2         7 my $d2 = $d1 - ($sigma * sqrt($t));
138              
139 2         61 my $value =
140             exp(-$r_q * $t) * (max($S_max - $K, 0.0) + $S * exp($mu * $t) * pnorm($d1) - $K_max * pnorm($d2) + _l_max($S, $K_max, $t, $r_q, $mu, $sigma));
141              
142 2         9 return $value;
143             }
144              
145             =head2 lbfixedput
146              
147             USAGE
148             my $price = lbfixedput($S, $K, $t, $r_q, $mu, $sigma, $S_max, $S_min)
149              
150             DESCRIPTION
151             Price of a Lookback Fixed Put
152              
153             =cut
154              
155             sub lbfixedput {
156 2     2 1 1885 my ($S, $K, $t, $r_q, $mu, $sigma, $S_max, $S_min) = @_;
157              
158 2         5 $S_max = undef;
159 2         12 my $K_min = min($S_min, $K);
160 2         9 my $d1 = _d1_function($S, $K_min, $t, $r_q, $mu, $sigma);
161 2         8 my $d2 = $d1 - ($sigma * sqrt($t));
162              
163 2         29 my $value = exp(-$r_q * $t) *
164             (max($K - $S_min, 0.0) + $K_min * pnorm(-$d2) - $S * exp($mu * $t) * pnorm(-$d1) + _l_min($S, $K_min, $t, $r_q, $mu, $sigma));
165              
166 2         6 return $value;
167             }
168              
169             =head2 lbhighlow
170              
171             USAGE
172             my $price = lbhighlow($S, $K, $t, $r_q, $mu, $sigma, $S_max, $S_min)
173              
174             DESCRIPTION
175             Price of a Lookback High Low
176              
177             =cut
178              
179             sub lbhighlow {
180 1     1 1 653 my ($S, $K, $t, $r_q, $mu, $sigma, $S_max, $S_min) = @_;
181              
182 1         4 my $value = lbfloatcall($S, $S_min, $t, $r_q, $mu, $sigma, $S_max, $S_min) + lbfloatput($S, $S_max, $t, $r_q, $mu, $sigma, $S_max, $S_min);
183              
184 1         3 return $value;
185             }
186              
187             =head2 _d1_function
188              
189             returns the d1 term common to many BlackScholesMerton formulae.
190              
191             =cut
192              
193             sub _d1_function {
194 20     20   60 my ($S, $K, $t, $r_q, $mu, $sigma) = @_;
195              
196 20         82 my $value = (log($S / $K) + ($mu + $sigma * $sigma * 0.5) * $t) / ($sigma * sqrt($t));
197              
198 20         48 return $value;
199             }
200              
201             =head2 _l_max
202              
203             This is a common function use to calculate the lookbacks options price. See [5] for details.
204              
205             =cut
206              
207             sub _l_max {
208 5     5   26 my ($S, $K, $t, $r_q, $mu, $sigma) = @_;
209              
210 5         14 my $d1 = _d1_function($S, $K, $t, $r_q, $mu, $sigma);
211 5         10 my $value;
212              
213 5 100       16 if ($mu) {
214 3         25 $value =
215             $S *
216             ($sigma**2) /
217             (2.0 * $mu) *
218             (-($S / $K)**(-2.0 * $mu / ($sigma**2)) * pnorm($d1 - 2.0 * $mu / $sigma * sqrt($t)) + exp($mu * $t) * pnorm($d1));
219             } else {
220 2         6 $value = $S * ($sigma * sqrt($t)) * (dnorm($d1) + $d1 * pnorm($d1));
221             }
222              
223 5         25 return $value;
224             }
225              
226             =head2 _l_min
227              
228             This is a common function use to calculate the lookbacks options price. See [5] for details.
229              
230             =cut
231              
232             sub _l_min {
233 5     5   19 my ($S, $K, $t, $r_q, $mu, $sigma) = @_;
234              
235 5         13 my $d1 = _d1_function($S, $K, $t, $r_q, $mu, $sigma);
236 5         9 my $value;
237              
238 5 100       52 if ($mu) {
239 3         24 $value =
240             $S *
241             ($sigma**2) /
242             (2.0 * $mu) *
243             (($S / $K)**(-2.0 * $mu / ($sigma**2)) * pnorm(-$d1 + 2.0 * $mu / $sigma * sqrt($t)) - exp($mu * $t) * pnorm(-$d1));
244             } else {
245 2         14 $value = $S * ($sigma * sqrt($t)) * (dnorm($d1) + $d1 * (pnorm($d1) - 1));
246             }
247              
248 5         14 return $value;
249             }
250              
251             =head2 dnorm
252              
253             Standard normal density function
254              
255             =cut
256              
257             sub dnorm { # Standard normal density function
258 4     4 1 8 my $x = shift;
259              
260 4         20 my $value = exp(-$x**2 / 2) / sqrt(2.0 * PI);
261              
262 4         17 return $value;
263             }
264              
265             =head2 callspread
266              
267             USAGE
268             my $price = callspread($S, $U, $D, $t, $r_q, $mu, $sigmaU, $sigmaD);
269              
270             DESCRIPTION
271             Price of a CALL SPREAD
272              
273             =cut
274              
275             sub callspread {
276 0     0 1 0 my ($S, $U, $D, $t, $r_q, $mu, $sigmaU, $sigmaD) = @_;
277              
278 0         0 return vanilla_call($S, $D, $t, $r_q, $mu, $sigmaD) - vanilla_call($S, $U, $t, $r_q, $mu, $sigmaU);
279             }
280              
281             =head2 putspread
282              
283             USAGE
284             my $price = putspread($S, $U, $D, $t, $r_q, $mu, $sigmaU, $sigmaD);
285              
286             DESCRIPTION
287             Price of a PUT SPREAD
288              
289             =cut
290              
291             sub putspread {
292 0     0 1 0 my ($S, $U, $D, $t, $r_q, $mu, $sigmaU, $sigmaD) = @_;
293              
294 0         0 return vanilla_put($S, $U, $t, $r_q, $mu, $sigmaU) - vanilla_put($S, $D, $t, $r_q, $mu, $sigmaD);
295             }
296              
297             =head2 standardbarrier
298              
299             A function implemented by Diethelm Wuertz.
300              
301             Description of parameters:
302              
303             $S - starting spot
304             $H - barrier
305             $X - exercise price
306             $K - cash rebate
307              
308             References:
309             Haug, Chapter 2.10.1
310              
311             =cut
312              
313             sub standardbarrier {
314 2     2 1 2071 my ($S, $H, $X, $K, $tiy, $r, $q, $sigma, $type) = @_;
315              
316 2 50 66     14 die 'wrong type[' . $type . ']' unless $type eq 'c' or $type eq 'p';
317              
318 2         8 my $mu = ($q - $sigma**2 / 2) / $sigma**2;
319 2         8 my $lambda = sqrt($mu**2 + 2 * $r / $sigma**2);
320 2         7 my $X1 = log($S / $X) / ($sigma * sqrt($tiy)) + (1 + $mu) * $sigma * sqrt($tiy);
321 2         7 my $X2 = log($S / $H) / ($sigma * sqrt($tiy)) + (1 + $mu) * $sigma * sqrt($tiy);
322 2         8 my $y1 = (log($H**2 / ($S * $X)) / ($sigma * sqrt($tiy)) + (1 + $mu) * $sigma * sqrt($tiy));
323 2         7 my $y2 = log($H / $S) / ($sigma * sqrt($tiy)) + (1 + $mu) * $sigma * sqrt($tiy);
324 2         4 my $Z = log($H / $S) / ($sigma * sqrt($tiy)) + $lambda * $sigma * sqrt($tiy);
325 2 100       8 my ($eta, $phi) = $type eq 'c' ? (1, 1) : (-1, -1);
326              
327 2         20 my $f1 = ($phi * $S * exp(($q - $r) * $tiy) * pnorm($phi * $X1) - $phi * $X * exp(-$r * $tiy) * pnorm($phi * $X1 - $phi * $sigma * sqrt($tiy)));
328              
329 2         14 my $f2 = ($phi * $S * exp(($q - $r) * $tiy) * pnorm($phi * $X2) - $phi * $X * exp(-$r * $tiy) * pnorm($phi * $X2 - $phi * $sigma * sqrt($tiy)));
330              
331 2         16 my $f3 = ($phi * $S * exp(($q - $r) * $tiy) * ($H / $S)**(2 * ($mu + 1)) * pnorm($eta * $y1) -
332             $phi * $X * exp(-$r * $tiy) * ($H / $S)**(2 * $mu) * pnorm($eta * $y1 - $eta * $sigma * sqrt($tiy)));
333              
334 2         12 my $f4 = ($phi * $S * exp(($q - $r) * $tiy) * ($H / $S)**(2 * ($mu + 1)) * pnorm($eta * $y2) -
335             $phi * $X * exp(-$r * $tiy) * ($H / $S)**(2 * $mu) * pnorm($eta * $y2 - $eta * $sigma * sqrt($tiy)));
336              
337 2         26 my $f6 = (
338             $K * (
339             ($H / $S)**($mu + $lambda) * pnorm($eta * $Z) + ($H / $S)**($mu - $lambda) * pnorm($eta * $Z - 2 * $eta * $lambda * $sigma * sqrt($tiy)))
340             );
341              
342 2 100       7 if ($X >= $H) {
343 1 50       10 return $type eq 'c' ? $f1 - $f3 + $f6 : $f2 - $f4 + $f6;
344             }
345              
346 1 50       7 return $type eq 'c' ? $f2 + $f6 - $f4 : $f1 - $f3 + $f6;
347             }
348              
349             =head2 doubleknockout
350              
351             Description of parameters:
352              
353             $S - spot
354             $H2 - high barrier
355             $H1 - low barrier
356             $K - payout strike
357             $tiy - time in years
358             $sigma - volatility
359             $mu - mean
360             $r - interest rate
361             $type - 'c' for buy or 'p' for sell
362              
363             Reference:
364             https://core.ac.uk/download/pdf/19187200.pdf
365              
366             =cut
367              
368             sub doubleknockout {
369 2     2 1 2441 my ($S, $H2, $H1, $K, $tiy, $mu, $sigma, $r, $type) = @_;
370              
371 2         9 my $eps = machine_epsilon();
372 2         12 my $l = log($H2 / $H1);
373 2         4 my $x = log($S / $H1);
374 2         5 my $d = log($K / $H1);
375              
376 2         12 my $k = ceil(sqrt(((-2 * log($eps) / $tiy) - ($mu / $sigma)**2) / ((PI * $sigma / $l)**2)));
377              
378 2 100       7 if ($type eq 'c') {
379             return
380 1         7 exp(-$r * $tiy) *
381             ($H1 * (_calculate_q(1, $l, $l, $mu, $sigma, $x, $k, $tiy) - _calculate_q(1, $d, $l, $mu, $sigma, $x, $k, $tiy)) -
382             $K * (_calculate_q(0, $l, $l, $mu, $sigma, $x, $k, $tiy) - _calculate_q(0, $d, $l, $mu, $sigma, $x, $k, $tiy)));
383             }
384              
385             return
386 1         6 exp(-$r * $tiy) *
387             ($K * (_calculate_q(0, $d, $l, $mu, $sigma, $x, $k, $tiy) - _calculate_q(0, 0, $l, $mu, $sigma, $x, $k, $tiy)) -
388             $H1 * (_calculate_q(1, $d, $l, $mu, $sigma, $x, $k, $tiy) - _calculate_q(1, 0, $l, $mu, $sigma, $x, $k, $tiy)));
389             }
390              
391             sub _calculate_q {
392 8     8   19 my ($alpha, $y, $l, $mu, $sigma, $x, $k, $tiy) = @_;
393              
394 8         10 my $z = 0;
395 8         18 for (my $i = 1; $i <= $k; $i++) {
396 256         382 my $lambda = 0.5 * (($mu / $sigma)**2 + ($i * PI * $sigma / $l)**2);
397 256         858 $z +=
398             exp(-$lambda * $tiy) *
399             sin($i * PI * $x / $l) *
400             ((($mu / ($sigma)**2 + $alpha) * sin($i * PI * $y / $l) - ($i * PI / $l) * cos($i * PI * $y / $l)) /
401             (($mu / ($sigma)**2 + $alpha)**2 + ($i * PI / $l)**2));
402             }
403              
404 8         55 return (2 / $l) * exp(($mu / $sigma**2) * ($y - $x)) * exp($alpha * $y) * $z;
405             }
406              
407             1;