File Coverage

lib/Math/Business/BlackScholesMerton/Binaries.pm
Criterion Covered Total %
statement 169 172 98.2
branch 50 54 92.5
condition 16 24 66.6
subroutine 25 25 100.0
pod 15 15 100.0
total 275 290 94.8


line stmt bran cond sub pod time code
1             package Math::Business::BlackScholesMerton::Binaries;
2 11     11   1322168 use strict;
  11         138  
  11         343  
3 11     11   62 use warnings;
  11         20  
  11         507  
4              
5             our $VERSION = '1.25'; ## VERSION
6              
7 11     11   77 use constant PI => 3.14159265359;
  11         24  
  11         975  
8              
9             my $SMALLTIME = 1 / (60 * 60 * 24 * 365); # 1 second in years;
10              
11 11     11   116 use List::Util qw(max);
  11         35  
  11         1301  
12 11     11   5425 use Math::CDF qw(pnorm);
  11         32526  
  11         728  
13 11     11   5677 use POSIX qw(ceil);
  11         71640  
  11         60  
14 11     11   21968 use Math::Trig;
  11         154096  
  11         1784  
15 11     11   5413 use Machine::Epsilon;
  11         4077  
  11         31177  
16              
17             # ABSTRACT: Algorithm of Math::Business::BlackScholesMerton::Binaries
18              
19             =head1 NAME
20              
21             Math::Business::BlackScholesMerton::Binaries
22              
23             =head1 SYNOPSIS
24              
25             use Math::Business::BlackScholesMerton::Binaries;
26              
27             # price of a Call option
28             my $price_call_option = Math::Business::BlackScholesMerton::Binaries::call(
29             1.35, # stock price
30             1.36, # barrier
31             (7/365), # time
32             0.002, # payout currency interest rate (0.05 = 5%)
33             0.001, # quanto drift adjustment (0.05 = 5%)
34             0.11, # volatility (0.3 = 30%)
35             );
36              
37             =head1 DESCRIPTION
38              
39             Prices options using the GBM model, all closed formulas.
40              
41             Important(a): Basically, onetouch, upordown and doubletouch have two cases of
42             payoff either at end or at hit. We treat them differently. We use parameter
43             $w to differ them.
44              
45             $w = 0: payoff at hit time.
46             $w = 1: payoff at end.
47              
48             Our current contracts pay rebate at hit time, so we set $w = 0 by default.
49              
50             Important(b) :Furthermore, for all contracts, we allow a different
51             payout currency (Quantos).
52              
53             Paying domestic currency (JPY if for USDJPY) = correlation coefficient is ZERO.
54             Paying foreign currency (USD if for USDJPY) = correlation coefficient is ONE.
55             Paying another currency = correlation is between negative ONE and positive ONE.
56              
57             See [3] for Quanto formulas and examples
58              
59             =head1 SUBROUTINES
60              
61             =head2 call
62              
63             USAGE
64             my $price = call($S, $K, $t, $r_q, $mu, $sigma)
65              
66             PARAMS
67             $S => stock price
68             $K => barrier
69             $t => time (1 = 1 year)
70             $r_q => payout currency interest rate (0.05 = 5%)
71             $mu => quanto drift adjustment (0.05 = 5%)
72             $sigma => volatility (0.3 = 30%)
73              
74             DESCRIPTION
75             Price a Call and remove the N(d2) part if the time is too small
76              
77             EXPLANATION
78             The definition of the contract is that if S > K, it gives
79             full payout (1). However the formula DC(T,K) = e**(-rT) N(d2) will not be
80             correct when T->0 and K=S. The value of DC(T,K) for this case will be 0.5.
81              
82             The formula is actually "correct" because when T->0 and S=K, the probability
83             should just be 0.5 that the contract wins, moving up or down is equally
84             likely in that very small amount of time left. Thus the only problem is
85             that the math cannot evaluate at T=0, where divide by 0 error occurs. Thus,
86             we need this check that throws away the N(d2) part (N(d2) will evaluate
87             "wrongly" to 0.5 if S=K).
88              
89             NOTE
90             Note that we have call = - dCall/dStrike
91             pair Foreign/Domestic
92              
93             see [3] for $r_q and $mu for quantos
94              
95             =cut
96              
97             sub call {
98 13     13 1 10661 my ($S, $K, $t, $r_q, $mu, $sigma) = @_;
99              
100 13 100       48 if ($t < $SMALLTIME) {
101 2 100       12 return ($S > $K) ? exp(-$r_q * $t) : 0;
102             }
103              
104 11         53 return exp(-$r_q * $t) * pnorm(d2($S, $K, $t, $r_q, $mu, $sigma));
105             }
106              
107             =head2 put
108              
109             USAGE
110             my $price = put($S, $K, $t, $r_q, $mu, $sigma)
111              
112             PARAMS
113             $S => stock price
114             $K => barrier
115             $t => time (1 = 1 year)
116             $r_q => payout currency interest rate (0.05 = 5%)
117             $mu => quanto drift adjustment (0.05 = 5%)
118             $sigma => volatility (0.3 = 30%)
119              
120             DESCRIPTION
121             Price a standard Digital Put
122              
123             =cut
124              
125             sub put {
126 13     13 1 3419 my ($S, $K, $t, $r_q, $mu, $sigma) = @_;
127              
128 13 100       43 if ($t < $SMALLTIME) {
129 2 100       18 return ($S < $K) ? exp(-$r_q * $t) : 0;
130             }
131              
132 11         34 return exp(-$r_q * $t) * pnorm(-1 * d2($S, $K, $t, $r_q, $mu, $sigma));
133             }
134              
135             =head2 d2
136              
137             returns the DS term common to many BlackScholesMerton formulae.
138              
139             =cut
140              
141             sub d2 {
142 22     22 1 83 my ($S, $K, $t, undef, $mu, $sigma) = @_;
143              
144 22         257 return (log($S / $K) + ($mu - $sigma * $sigma / 2.0) * $t) / ($sigma * sqrt($t));
145             }
146              
147             =head2 expirymiss
148              
149             USAGE
150             my $price = expirymiss($S, $U, $D, $t, $r_q, $mu, $sigma)
151              
152             PARAMS
153             $S => stock price
154             $t => time (1 = 1 year)
155             $U => barrier
156             $D => barrier
157             $r_q => payout currency interest rate (0.05 = 5%)
158             $mu => quanto drift adjustment (0.05 = 5%)
159             $sigma => volatility (0.3 = 30%)
160              
161             DESCRIPTION
162             Price an expiry miss contract (1 Call + 1 Put)
163              
164             [3] for $r_q and $mu for quantos
165              
166             =cut
167              
168             sub expirymiss {
169 6     6 1 1071 my ($S, $U, $D, $t, $r_q, $mu, $sigma) = @_;
170              
171 6         16 my ($call_price) = call($S, $U, $t, $r_q, $mu, $sigma);
172 6         16 my ($put_price) = put($S, $D, $t, $r_q, $mu, $sigma);
173              
174 6         18 return $call_price + $put_price;
175             }
176              
177             =head2 expiryrange
178              
179             USAGE
180             my $price = expiryrange($S, $U, $D, $t, $r_q, $mu, $sigma)
181              
182             PARAMS
183             $S => stock price
184             $U => barrier
185             $D => barrier
186             $t => time (1 = 1 year)
187             $r_q => payout currency interest rate (0.05 = 5%)
188             $mu => quanto drift adjustment (0.05 = 5%)
189             $sigma => volatility (0.3 = 30%)
190              
191             DESCRIPTION
192             Price an Expiry Range contract as Foreign/Domestic.
193              
194             [3] for $r_q and $mu for quantos
195              
196             =cut
197              
198             sub expiryrange {
199 3     3 1 1414 my ($S, $U, $D, $t, $r_q, $mu, $sigma) = @_;
200              
201 3         15 return exp(-$r_q * $t) - expirymiss($S, $U, $D, $t, $r_q, $mu, $sigma);
202             }
203              
204             =head2 onetouch
205              
206             PARAMS
207             $S => stock price
208             $U => barrier
209             $t => time (1 = 1 year)
210             $r_q => payout currency interest rate (0.05 = 5%)
211             $mu => quanto drift adjustment (0.05 = 5%)
212             $sigma => volatility (0.3 = 30%)
213              
214             [3] for $r_q and $mu for quantos
215              
216             =cut
217              
218             sub onetouch {
219 39     39 1 4892 my ($S, $U, $t, $r_q, $mu, $sigma, $w) = @_;
220              
221             # w = 0, rebate paid at hit (good way to remember is that waiting
222             # time to get paid = 0)
223             # w = 1, rebate paid at end.
224              
225             # When the contract already reached it expiry and not yet reach it
226             # settlement time, it is consider an unexpired contract but will come to
227             # here with t=0 and it will caused the formula to die hence set it to the
228             # SMALLTIME which is 1 second
229 39         86 $t = max($SMALLTIME, $t);
230              
231 39   100     125 $w ||= 0;
232              
233             # eta = -1, one touch up
234             # eta = 1, one touch down
235 39 100       98 my $eta = ($S < $U) ? -1 : 1;
236              
237 39         60 my $sqrt_t = sqrt($t);
238              
239 39         76 my $theta_ = (($mu) / $sigma) - (0.5 * $sigma);
240              
241             # Floor v_ squared at zero in case negative interest rates push it negative.
242             # See: Barrier Options under Negative Rates in Black-Scholes (Le Floc’h and Pruell, 2014)
243 39         102 my $v_ = sqrt(max(0, ($theta_ * $theta_) + (2 * (1 - $w) * $r_q)));
244              
245 39         131 my $e = (log($S / $U) - ($sigma * $v_ * $t)) / ($sigma * $sqrt_t);
246 39         76 my $e_ = (-log($S / $U) - ($sigma * $v_ * $t)) / ($sigma * $sqrt_t);
247              
248 39         300 my $price = (($U / $S)**(($theta_ + $v_) / $sigma)) * pnorm(-$eta * $e) + (($U / $S)**(($theta_ - $v_) / $sigma)) * pnorm($eta * $e_);
249              
250 39         128 return exp(-$w * $r_q * $t) * $price;
251             }
252              
253             =head2 notouch
254              
255             USAGE
256             my $price = notouch($S, $U, $t, $r_q, $mu, $sigma, $w)
257              
258             PARAMS
259             $S => stock price
260             $U => barrier
261             $t => time (1 = 1 year)
262             $r_q => payout currency interest rate (0.05 = 5%)
263             $mu => quanto drift adjustment (0.05 = 5%)
264             $sigma => volatility (0.3 = 30%)
265              
266             DESCRIPTION
267             Price a No touch contract.
268              
269             Payoff with domestic currency
270             Identity:
271             price of notouch = exp(- r t) - price of onetouch(rebate paid at end)
272              
273             [3] for $r_q and $mu for quantos
274              
275             =cut
276              
277             sub notouch {
278 7     7 1 1684 my ($S, $U, $t, $r_q, $mu, $sigma) = @_;
279              
280             # No touch contract always pay out at end
281 7         11 my $w = 1;
282              
283 7         25 return exp(-$r_q * $t) - onetouch($S, $U, $t, $r_q, $mu, $sigma, $w);
284             }
285              
286             # These variables require 'our' only because they need to be
287             # accessed by a test script.
288             our $MAX_ITERATIONS_UPORDOWN_PELSSER_1997 = 1000;
289             our $MIN_ITERATIONS_UPORDOWN_PELSSER_1997 = 16;
290              
291             #
292             # This variable requires 'our' only because it needs to be
293             # accessed via test script.
294             # Min accuracy. Accurate to 1 dollar for 100,000 notional
295             #
296             our $MIN_ACCURACY_UPORDOWN_PELSSER_1997 = 1.0 / 100000.0;
297             our $SMALL_VALUE_MU = 1e-10;
298              
299             # The smallest (in magnitude) floating-point number which,
300             # when added to the floating-point number 1.0, produces a
301             # floating-point result different from 1.0 is termed the
302             # machine accuracy, e.
303             #
304             # This value is very important for knowing stability to
305             # certain formulas used. e.g. Pelsser formula for UPORDOWN
306             # and RANGE contracts.
307             #
308             my $MACHINE_EPSILON = machine_epsilon();
309              
310             =head2 upordown
311              
312             USAGE
313             my $price = upordown(($S, $U, $D, $t, $r_q, $mu, $sigma, $w))
314              
315             PARAMS
316             $S stock price
317             $U barrier
318             $D barrier
319             $t time (1 = 1 year)
320             $r_q payout currency interest rate (0.05 = 5%)
321             $mu quanto drift adjustment (0.05 = 5%)
322             $sigma volatility (0.3 = 30%)
323              
324             see [3] for $r_q and $mu for quantos
325              
326             DESCRIPTION
327             Price an Up or Down contract
328              
329             =cut
330              
331             sub upordown {
332 16     16 1 6754 my ($S, $U, $D, $t, $r_q, $mu, $sigma, $w) = @_;
333              
334             # When the contract already reached it's expiry and not yet reach it
335             # settlement time, it is considered an unexpired contract but will come to
336             # here with t=0 and it will caused the formula to die hence set it to the
337             # SMALLTIME whiich is 1 second
338 16         49 $t = max($t, $SMALLTIME);
339              
340             # $w = 0, paid at hit
341             # $w = 1, paid at end
342 16 100       47 if (not defined $w) { $w = 0; }
  8         26  
343              
344             # spot is outside [$D, $U] --> contract is expired with full payout,
345             # one barrier is already hit (can happen due to shift markup):
346 16 100 100     85 if ($S >= $U or $S <= $D) {
347 4 100       25 return $w ? exp(-$t * $r_q) : 1;
348             }
349              
350             #
351             # SANITY CHECKS
352             #
353             # For extreme cases, the price will be wrong due the values in the
354             # infinite series getting too large or too small, which causes
355             # roundoff errors in the computer. Thus no matter how many iterations
356             # you make, the errors will never go away.
357             #
358             # For example try this:
359             #
360             # my ($S, $U, $D, $t, $r, $q, $vol, $w)
361             # = (100.00, 118.97, 99.00, 30/365, 0.1, 0.02, 0.01, 1);
362             # $up_price = Math::Business::BlackScholesMerton::Binaries::ot_up_ko_down_pelsser_1997(
363             # $S,$U,$D,$t,$r,$q,$vol,$w);
364             # $down_price= Math::Business::BlackScholesMerton::Binaries::ot_down_ko_up_pelsser_1997(
365             # $S,$U,$D,$t,$r,$q,$vol,$w);
366             #
367             # Thus we put a sanity checks here such that
368             #
369             # CONDITION 1: UPORDOWN[U,D] < ONETOUCH[U] + ONETOUCH[D]
370             # CONDITION 2: UPORDOWN[U,D] > ONETOUCH[U]
371             # CONDITION 3: UPORDOWN[U,D] > ONETOUCH[D]
372             # CONDITION 4: ONETOUCH[U] + ONETOUCH[D] >= $MIN_ACCURACY_UPORDOWN_PELSSER_1997
373             #
374 12         62 my $onetouch_up_prob = onetouch($S, $U, $t, $r_q, $mu, $sigma, $w);
375 12         39 my $onetouch_down_prob = onetouch($S, $D, $t, $r_q, $mu, $sigma, $w);
376              
377 12         26 my $upordown_prob;
378              
379 12 100 75     96 if ($onetouch_up_prob + $onetouch_down_prob < $MIN_ACCURACY_UPORDOWN_PELSSER_1997) {
    100          
380              
381             # CONDITION 4:
382             # The probability is too small for the Pelsser formula to be correct.
383             # Do this check first to avoid PELSSER stability condition to be
384             # triggered.
385             # Here we assume that the ONETOUCH formula is perfect and never give
386             # wrong values (e.g. negative).
387 1         4 return 0;
388             } elsif ($onetouch_up_prob xor $onetouch_down_prob) {
389              
390             # One of our ONETOUCH probabilities is 0.
391             # That means our upordown prob is equivalent to the other one.
392             # Pelsser recompute will either be the same or wrong.
393             # Continuing to assume the ONETOUCH is perfect.
394 4         13 $upordown_prob = max($onetouch_up_prob, $onetouch_down_prob);
395             } else {
396              
397             # THIS IS THE ONLY PLACE IT SHOULD BE!
398 7         27 $upordown_prob =
399             ot_up_ko_down_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w) + ot_down_ko_up_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w);
400             }
401              
402             # CONDITION 4:
403             # Now check on the other end, when the contract is too close to payout.
404             # Not really needed to check for payout at hit, because RANGE is
405             # always at end, and thus the value (DISCOUNT - UPORDOWN) is not
406             # evaluated.
407 11 100       36 if ($w == 1) {
408              
409             # Since the difference is already less than the min accuracy,
410             # the value [payout - upordown], which is the RANGE formula
411             # can become negative.
412 5 50       22 if (abs(exp(-$r_q * $t) - $upordown_prob) < $MIN_ACCURACY_UPORDOWN_PELSSER_1997) {
413 0         0 $upordown_prob = exp(-$r_q * $t);
414             }
415             }
416              
417             # CONDITION 1-3
418             # We use hardcoded small value of $SMALL_TOLERANCE, because if we were to increase
419             # the minimum accuracy, and this small value uses that min accuracy, it is
420             # very hard for the conditions to pass.
421 11         18 my $SMALL_TOLERANCE = 0.00001;
422 11 50 33     100 if ( not($upordown_prob < $onetouch_up_prob + $onetouch_down_prob + $SMALL_TOLERANCE)
      33        
423             or not($upordown_prob + $SMALL_TOLERANCE > $onetouch_up_prob)
424             or not($upordown_prob + $SMALL_TOLERANCE > $onetouch_down_prob))
425             {
426 0         0 die "UPORDOWN price sanity checks failed for S=$S, U=$U, "
427             . "D=$D, t=$t, r_q=$r_q, mu=$mu, sigma=$sigma, w=$w. "
428             . "UPORDOWN PROB=$upordown_prob , "
429             . "ONETOUCH_UP PROB=$onetouch_up_prob , "
430             . "ONETOUCH_DOWN PROB=$onetouch_down_prob";
431             }
432              
433 11         47 return $upordown_prob;
434             }
435              
436             =head2 common_function_pelsser_1997
437              
438             USAGE
439             my $c = common_function_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $eta)
440              
441             DESCRIPTION
442             Return the common function from Pelsser's Paper (1997)
443              
444             =cut
445              
446             sub common_function_pelsser_1997 {
447              
448             # h: normalized high barrier, log(U/L)
449             # x: normalized spot, log(S/L)
450 16     16 1 2999 my ($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $eta) = @_;
451              
452 16         27 my $pi = Math::Trig::pi;
453              
454 16         36 my $h = log($U / $D);
455 16         35 my $x = log($S / $D);
456              
457             # $eta = 1, onetouch up knockout down
458             # $eta = 0, onetouch down knockout up
459             # This variable used to check stability
460 16 100       42 if (not defined $eta) {
461 1         36 die "Wrong usage of this function for S=$S, U=$U, D=$D, " . "t=$t, r_q=$r_q, mu=$mu, sigma=$sigma, w=$w, eta not defined.";
462             }
463 15 100       46 if ($eta == 0) { $x = $h - $x; }
  8         28  
464              
465             # $w = 0, paid at hit
466             # $w = 1, paid at end
467              
468 15         31 my $mu_new = $mu - (0.5 * $sigma * $sigma);
469 15         59 my $mu_dash = sqrt(max(0, ($mu_new * $mu_new) + (2 * $sigma * $sigma * $r_q * (1 - $w))));
470              
471 15         20 my $series_part = 0;
472 15         20 my $hyp_part = 0;
473              
474             # These constants will determine whether or not this contract can be
475             # evaluated to a predefined accuracy. It is VERY IMPORTANT because
476             # if these conditions are not met, the prices can be complete nonsense!!
477 15         35 my $stability_constant = get_stability_constant_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $eta, 1);
478              
479             # The number of iterations is important when recommending the
480             # range of the upper/lower barriers on our site. If we recommend
481             # a range that is too big and our iteration is too small, the
482             # price will be wrong! We must know the rate of convergence of
483             # the formula used.
484 15         32 my $iterations_required = get_min_iterations_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w);
485              
486 15         47 for (my $k = 1; $k < $iterations_required; $k++) {
487 253         404 my $lambda_k_dash = (0.5 * (($mu_dash * $mu_dash) / ($sigma * $sigma) + ($k * $k * $pi * $pi * $sigma * $sigma) / ($h * $h)));
488              
489 253         457 my $phi = ($sigma * $sigma) / ($h * $h) * exp(-$lambda_k_dash * $t) * $k / $lambda_k_dash;
490              
491 253         482 $series_part += $phi * $pi * sin($k * $pi * ($h - $x) / $h);
492              
493             #
494             # Note that greeks may also call this function, and their
495             # stability constant will differ. However, for simplicity
496             # we will not bother (else the code will get messy), and
497             # just use the price stability constant.
498             #
499 253 50 66     585 if ($k == 1 and (not(abs($phi) < $stability_constant))) {
500 0         0 die "PELSSER VALUATION formula for S=$S, U=$U, D=$D, t=$t, r_q=$r_q, "
501             . "mu=$mu, vol=$sigma, w=$w, eta=$eta, cannot be evaluated because"
502             . "PELSSER VALUATION stability conditions ($phi less than "
503             . "$stability_constant) not met. This could be due to barriers "
504             . "too big, volatilities too low, interest/dividend rates too high, "
505             . "or machine accuracy too low. Machine accuracy is "
506             . $MACHINE_EPSILON . ".";
507             }
508             }
509              
510             #
511             # Some math basics: When A -> 0,
512             #
513             # sinh(A) -> 0.5 * [ (1 + A) - (1 - A) ] = 0.5 * 2A = A
514             # cosh(A) -> 0.5 * [ (1 + A) + (1 - A) ] = 0.5 * 2 = 1
515             #
516             # Thus for sinh(A)/sinh(B) when A & B -> 0, we have
517             #
518             # sinh(A) / sinh(B) -> A / B
519             #
520             # Since the check of the spot == lower/upper barrier has been done in the
521             # _upordown subroutine, we can assume that $x and $h will never be 0.
522             # So we only need to check that $mu_dash is too small. Also note that
523             # $mu_dash is always positive.
524             #
525             # For example, even at 0.0001 the error becomes small enough
526             #
527             # 0.0001 - Math::Trig::sinh(0.0001) = -1.66688941837835e-13
528             #
529             # Since h > x, we only check for (mu_dash * h) / (vol * vol)
530             #
531 15 100       48 if (abs($mu_dash * $h / ($sigma * $sigma)) < $SMALL_VALUE_MU) {
532 3         6 $hyp_part = $x / $h;
533             } else {
534 12         38 $hyp_part = Math::Trig::sinh($mu_dash * $x / ($sigma * $sigma)) / Math::Trig::sinh($mu_dash * $h / ($sigma * $sigma));
535             }
536              
537 15         285 return ($hyp_part - $series_part) * exp(-$r_q * $t * $w);
538             }
539              
540             =head2 get_stability_constant_pelsser_1997
541              
542             USAGE
543             my $constant = get_stability_constant_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $eta, $p)
544              
545             DESCRIPTION
546             Get the stability constant (Pelsser 1997)
547              
548             =cut
549              
550             sub get_stability_constant_pelsser_1997 {
551 17     17 1 3279 my ($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $eta, $p) = @_;
552              
553             # $eta = 1, onetouch up knockout down
554             # $eta = 0, onetouch down knockout up
555              
556 17 100       41 if (not defined $eta) {
557 1         44 die "Wrong usage of this function for S=$S, U=$U, D=$D, t=$t, " . "r_q=$r_q, mu=$mu, sigma=$sigma, w=$w, Eta not defined.";
558             }
559              
560             # p is the power of pi
561             # p=1 for price/theta/vega/vanna/volga
562             # p=2 for delta
563             # p=3 for gamma
564 16 50 66     64 if ($p != 1 and $p != 2 and $p != 3) {
      66        
565 1         26 die "Wrong usage of this function for S=$S, U=$U, D=$D, t=$t, "
566             . "r_q=$r_q, mu=$mu, sigma=$sigma, w=$w, Power of PI must "
567             . "be 1, 2 or 3. Given $p.";
568             }
569              
570 15         31 my $h = log($U / $D);
571 15         19 my $x = log($S / $D);
572 15         24 my $mu_new = $mu - (0.5 * $sigma * $sigma);
573              
574 15         43 my $numerator = $MIN_ACCURACY_UPORDOWN_PELSSER_1997 * exp(1.0 - $mu_new * (($eta * $h) - $x) / ($sigma * $sigma));
575 15         62 my $denominator = (exp(1) * (Math::Trig::pi + $p)) + (max($mu_new * (($eta * $h) - $x), 0.0) * Math::Trig::pi / ($sigma**2));
576 15         37 $denominator *= (Math::Trig::pi**($p - 1)) * $MACHINE_EPSILON;
577              
578 15         24 my $stability_condition = $numerator / $denominator;
579              
580 15         26 return $stability_condition;
581             }
582              
583             =head2 ot_up_ko_down_pelsser_1997
584              
585             USAGE
586             my $price = ot_up_ko_down_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w)
587              
588             DESCRIPTION
589             This is V_{RAHU} in paper [5], or ONETOUCH-UP-KNOCKOUT-DOWN,
590             a contract that wins if it touches upper barrier, but expires
591             worthless if it touches the lower barrier first.
592              
593             =cut
594              
595             sub ot_up_ko_down_pelsser_1997 {
596 7     7 1 24 my ($S, $U, $D, $t, $r_q, $mu, $sigma, $w) = @_;
597              
598 7         16 my $mu_new = $mu - (0.5 * $sigma * $sigma);
599 7         14 my $h = log($U / $D);
600 7         17 my $x = log($S / $D);
601              
602 7         30 return exp($mu_new * ($h - $x) / ($sigma * $sigma)) * common_function_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w, 1);
603             }
604              
605             =head2 ot_down_ko_up_pelsser_1997
606              
607             USAGE
608             my $price = ot_down_ko_up_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w)
609              
610             DESCRIPTION
611             This is V_{RAHL} in paper [5], or ONETOUCH-DOWN-KNOCKOUT-UP,
612             a contract that wins if it touches lower barrier, but expires
613             worthless if it touches the upper barrier first.
614              
615             =cut
616              
617             sub ot_down_ko_up_pelsser_1997 {
618 7     7 1 27 my ($S, $U, $D, $t, $r_q, $mu, $sigma, $w) = @_;
619              
620 7         30 my $mu_new = $mu - (0.5 * $sigma * $sigma);
621 7         15 my $x = log($S / $D);
622              
623 7         33 return exp(-$mu_new * $x / ($sigma * $sigma)) * common_function_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w, 0);
624             }
625              
626             =head2 get_min_iterations_pelsser_1997
627              
628             USAGE
629             my $min = get_min_iterations_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $accuracy)
630              
631             DESCRIPTION
632             An estimate of the number of iterations required to achieve a certain
633             level of accuracy in the price.
634              
635             =cut
636              
637             sub get_min_iterations_pelsser_1997 {
638 18     18 1 3306 my ($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $accuracy) = @_;
639              
640 18 100       44 if (not defined $accuracy) {
641 16         37 $accuracy = $MIN_ACCURACY_UPORDOWN_PELSSER_1997;
642             }
643              
644 18 100       89 if ($accuracy > $MIN_ACCURACY_UPORDOWN_PELSSER_1997) {
    100          
645 1         2 $accuracy = $MIN_ACCURACY_UPORDOWN_PELSSER_1997;
646             } elsif ($accuracy <= 0) {
647 1         2 $accuracy = $MIN_ACCURACY_UPORDOWN_PELSSER_1997;
648             }
649              
650 18         57 my $it_up = _get_min_iterations_ot_up_ko_down_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $accuracy);
651 18         59 my $it_down = _get_min_iterations_ot_down_ko_up_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $accuracy);
652              
653 18         51 my $min = max($it_up, $it_down);
654              
655 18         35 return $min;
656             }
657              
658             =head2 _get_min_iterations_ot_up_ko_down_pelsser_1997
659              
660             USAGE
661             my $k_min = _get_min_iterations_ot_up_ko_down_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $accuracy)
662              
663             DESCRIPTION
664             An estimate of the number of iterations required to achieve a certain
665             level of accuracy in the price for ONETOUCH-UP-KNOCKOUT-DOWN.
666              
667             =cut
668              
669             sub _get_min_iterations_ot_up_ko_down_pelsser_1997 {
670 39     39   1117 my ($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $accuracy) = @_;
671              
672 39 100       81 if (!defined $accuracy) {
673 1         12 die "accuracy required";
674             }
675              
676 38         53 my $pi = Math::Trig::pi;
677              
678 38         63 my $h = log($U / $D);
679 38         54 my $x = log($S / $D);
680 38         59 my $mu_new = $mu - (0.5 * $sigma * $sigma);
681 38         117 my $mu_dash = sqrt(max(0, ($mu_new * $mu_new) + (2 * $sigma * $sigma * $r_q * (1 - $w))));
682              
683 38         75 my $A = ($mu_dash * $mu_dash) / (2 * $sigma * $sigma);
684 38         63 my $B = ($pi * $pi * $sigma * $sigma) / (2 * $h * $h);
685              
686 38         50 my $delta_dash = $accuracy;
687 38         83 my $delta = $delta_dash * exp(-$mu_new * ($h - $x) / ($sigma * $sigma)) * (($h * $h) / ($pi * $sigma * $sigma));
688              
689             # This can happen when stability condition fails
690 38 100       82 if ($delta * $B <= 0) {
691 1         32 die "(_get_min_iterations_ot_up_ko_down_pelsser_1997) Cannot "
692             . "evaluate minimum iterations because too many iterations "
693             . "required!! delta=$delta, B=$B for input parameters S=$S, "
694             . "U=$U, D=$D, t=$t, r_q=$r_q, mu=$mu, sigma=$sigma, w=$w, "
695             . "accuracy=$accuracy";
696             }
697              
698             # Check that condition is satisfied
699 37         81 my $condition = max(exp(-$A * $t) / ($B * $delta), 1);
700              
701 37         85 my $k_min = log($condition) / ($B * $t);
702 37         53 $k_min = sqrt($k_min);
703              
704 37 100       85 if ($k_min < $MIN_ITERATIONS_UPORDOWN_PELSSER_1997) {
    100          
705              
706 32         75 return $MIN_ITERATIONS_UPORDOWN_PELSSER_1997;
707             } elsif ($k_min > $MAX_ITERATIONS_UPORDOWN_PELSSER_1997) {
708              
709 1         3 return $MAX_ITERATIONS_UPORDOWN_PELSSER_1997;
710             }
711              
712 4         10 return int($k_min);
713             }
714              
715             =head2 _get_min_iterations_ot_down_ko_up_pelsser_1997
716              
717             USAGE
718              
719             DESCRIPTION
720             An estimate of the number of iterations required to achieve a certain
721             level of accuracy in the price for ONETOUCH-UP-KNOCKOUT-UP.
722              
723             =cut
724              
725             sub _get_min_iterations_ot_down_ko_up_pelsser_1997 {
726 18     18   43 my ($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $accuracy) = @_;
727              
728 18         30 my $h = log($U / $D);
729 18         40 my $mu_new = $mu - (0.5 * $sigma * $sigma);
730              
731 18         32 $accuracy = $accuracy * exp($mu_new * $h / ($sigma * $sigma));
732              
733 18         39 return _get_min_iterations_ot_up_ko_down_pelsser_1997($S, $U, $D, $t, $r_q, $mu, $sigma, $w, $accuracy);
734             }
735              
736             =head2 range
737              
738             USAGE
739             my $price = range($S, $U, $D, $t, $r_q, $mu, $sigma, $w)
740              
741             PARAMS
742             $S stock price
743             $t time (1 = 1 year)
744             $U barrier
745             $D barrier
746             $r_q payout currency interest rate (0.05 = 5%)
747             $mu quanto drift adjustment (0.05 = 5%)
748             $sigma volatility (0.3 = 30%)
749              
750             see [3] for $r_q and $mu for quantos
751              
752             DESCRIPTION
753             Price a range contract.
754              
755             =cut
756              
757             sub range {
758              
759             # payout time $w is only a dummy. range contracts always payout at end.
760 7     7 1 3525 my ($S, $U, $D, $t, $r_q, $mu, $sigma, $w) = @_;
761              
762             # range always pay out at end
763 7         11 $w = 1;
764              
765 7         31 return exp(-$r_q * $t) - upordown($S, $U, $D, $t, $r_q, $mu, $sigma, $w);
766             }
767              
768             =head2 americanknockout
769              
770             American Binary Knockout
771              
772             Description of parameters:
773              
774             $S - spot
775             $H1 - lower barrier
776             $H2 - upper barrier
777             $K - payout strike
778             $tiy - time in years
779             $sigma - volatility
780             $mu - mean
781             $r - interest rate
782             $type - 'c' for buy or 'p' for sell
783              
784             Reference:
785             http://www.phy.cuhk.edu.hk/~cflo/Finance/chohoi/afe.pdf
786              
787             =cut
788              
789             sub americanknockout {
790 4     4 1 5870 my ($S, $H2, $H1, $K, $tiy, $sigma, $mu, $type) = @_;
791              
792 4 100       16 if ($type eq 'c') {
793 3         9 ($H1, $H2) = ($H2, $H1);
794             }
795              
796 4         29 my $k1 = 2 * $mu / $sigma**2;
797 4         13 my $alpha = -0.5 * ($k1 - 1);
798 4         16 my $beta = -0.25 * ($k1 - 1)**2 - 2 * $mu / $sigma**2;
799 4         13 my $L = log($H2 / $H1);
800 4         52 my $n = ceil(sqrt(((-2 * log($MACHINE_EPSILON) / $tiy) - ($mu / $sigma)**2) / ((PI * $sigma / $L)**2)));
801              
802 4         11 my $z = 0;
803 4         18 for (my $i = 1; $i <= $n; $i++) {
804 123         167 my $zeta = $i * PI / $L;
805 123         391 $z +=
806             (2 / ($i * PI)) *
807             (($beta - ($zeta**2) * exp(-0.5 * $tiy * ($sigma**2) * ($zeta**2 - $beta))) / ($zeta**2 - $beta)) *
808             sin($zeta * log($S / $H1));
809             }
810              
811             # For smaller durations or barriers farther apart, the value could converge to a small negative value
812 4         46 return max(0, $K * (($S / $H1)**$alpha) * ($z + (1 - log($S / $H1) / $L)));
813             }
814              
815             1;
816