File Coverage

blib/lib/VolSurface/Utils.pm
Criterion Covered Total %
statement 177 204 86.7
branch 51 76 67.1
condition 3 6 50.0
subroutine 20 21 95.2
pod 7 7 100.0
total 258 314 82.1


line stmt bran cond sub pod time code
1             package VolSurface::Utils;
2              
3 1     1   99235 use 5.006;
  1         16  
4 1     1   6 use strict;
  1         2  
  1         18  
5 1     1   4 use warnings;
  1         8  
  1         42  
6              
7             =head1 NAME
8              
9             VolSurface::Utils - A class that handles several volatility related methods
10              
11             =cut
12              
13             our $VERSION = '1.05';
14              
15 1     1   15 use Carp;
  1         5  
  1         58  
16 1     1   8 use List::Util qw(notall);
  1         2  
  1         137  
17 1     1   488 use Math::CDF qw(pnorm qnorm);
  1         2968  
  1         59  
18 1     1   523 use Math::Business::BlackScholesMerton::NonBinaries;
  1         3960  
  1         31  
19 1     1   494 use Math::Business::BlackScholes::Binaries::Greeks::Delta;
  1         21633  
  1         34  
20 1     1   6 use base qw( Exporter );
  1         11  
  1         2701  
21              
22             =head1 SYNOPSIS
23              
24             A class that handles several volatility related methods such as gets strikes from a certain delta point, gets delta from a certain vol point etc.
25              
26             use VolSurface::Utils;
27              
28             my ($strike, $atm_vol, $t, $spot, $r_rate, $q_rate, $premium_adjusted) = (); ## initialize args
29             my $delta = get_delta_for_strike({
30             strike => $strike,
31             atm_vol => $atm_vol,
32             t => $t,
33             spot => $spot,
34             r_rate => $r_rate,
35             q_rate => $q_rate,
36             premium_adjusted => $premium_adjusted
37             });
38              
39              
40             =head1 EXPORT
41              
42             get_delta_for_strike
43              
44             get_strike_for_spot_delta
45              
46             get_ATM_strike_for_spot_delta
47              
48             get_moneyness_for_strike
49              
50             get_strike_for_moneyness
51              
52             get_1vol_butterfly
53              
54             get_2vol_butterfly
55              
56             =cut
57              
58             our @EXPORT_OK =
59             qw( get_delta_for_strike get_strike_for_spot_delta get_ATM_strike_for_spot_delta get_moneyness_for_strike get_strike_for_moneyness get_1vol_butterfly get_2vol_butterfly);
60              
61             =head1 METHODS
62              
63             =head2 get_delta_for_strike
64              
65             Returns the delta (spot delta or premium adjusted spot delta) correspond to a particular strike with set of parameters such as atm volatility, time in year, spot level, rates
66              
67             my $delta = get_delta_for_strike({
68             strike => $strike,
69             atm_vol => $atm_vol,
70             t => $t,
71             spot => $spot,
72             r_rate => $r_rate,
73             q_rate => $q_rate,
74             premium_adjusted => $premium_adjusted,
75             forward => $forward
76             });
77              
78             Spot delta of an option is the percentage of the foreign notional one must buy when selling the option to hold a hedged position in the spot markets.
79              
80             Premium adjusted spot delta is the spot delta which adjusted to take care of the correction induced by payment of the premium in foreign currency, which is the amount by which the delta hedge in foreign currency has to be corrected.
81              
82             =cut
83              
84             sub get_delta_for_strike {
85 3     3 1 3285 my $args = shift;
86              
87 3         18 my %new_args = %$args;
88 3         14 my @required = qw(strike atm_vol t spot r_rate q_rate premium_adjusted forward);
89 3         7 for (@required) {
90 23 100       60 croak "Arg $_ is undef at get_delta_for_strike" unless defined $args->{$_};
91             }
92              
93             my ($K, $sigma, $t, $S, $r, $q, $premium_adjusted) =
94 2         15 ($new_args{strike}, $new_args{atm_vol}, $new_args{t}, $new_args{spot}, $new_args{r_rate}, $new_args{q_rate}, $new_args{premium_adjusted});
95              
96 2         4 my $delta;
97 2 100       22 if ($premium_adjusted) {
98 1 50       5 my $forward_adj = $new_args{forward} ? exp($new_args{q_rate} * $new_args{t}) : 1;
99 1         12 my $d2 = (log($S / $K) + ($r - $q - ($sigma**2) / 2) * $t) / ($sigma * sqrt($t));
100 1         31 $delta = ($K / $S) * $forward_adj * exp(-1 * $new_args{r_rate} * $new_args{t}) * pnorm($d2);
101             } else {
102 1 50       9 my $forward_adj = $new_args{forward} ? 1 : exp(-1 * $new_args{q_rate} * $new_args{t});
103 1         6 my $d1 = (log($S / $K) + ($r - $q + ($sigma**2) / 2) * $t) / ($sigma * sqrt($t));
104 1         18 $delta = $forward_adj * pnorm($d1);
105             }
106              
107 2         16 return $delta;
108             }
109              
110             =head2 get_strike_for_spot_delta
111              
112             Returns the strike corresponds to a particular delta (spot delta or premium adjusted spot delta) with a set of parameters such as option type, atm vol, time in year, rates and spot level.
113              
114             my $strike = get_strike_for_spot_delta({
115             delta => $delta,
116             option_type => $option_type,
117             atm_vol => $atm_vol,
118             t => $t,
119             r_rate => $r_rate,
120             q_rate => $q_rate,
121             spot => $spot,
122             premium_adjusted => $premium_adjusted
123             });
124              
125             Calculation of strike depends on which type of delta we have. Delta provided must be on [0,1].
126              
127             =cut
128              
129             sub get_strike_for_spot_delta {
130 309     309 1 26252 my $args = shift;
131              
132 309         1315 my %new_args = %$args;
133 309         843 my @required = qw(delta option_type atm_vol t r_rate q_rate spot premium_adjusted);
134 309         540 for (@required) {
135 2472 100       4485 croak "Arg $_ is undef at get_strike_for_spot_delta" unless defined $args->{$_};
136             }
137              
138 308 100       466 if (!grep { $new_args{option_type} eq $_ } qw(VANILLA_CALL VANILLA_PUT)) {
  616         1564  
139 1         13 croak 'Wrong option type [' . $new_args{option_type} . ']';
140             }
141              
142 307 100 66     1188 if ($new_args{delta} < 0 or $new_args{delta} > 1) {
143 1         12 croak 'Provided delta [' . $new_args{delta} . '] must be on [0,1]';
144             }
145              
146 306 100       563 if ($new_args{forward}) {
147 224         474 $new_args{delta} = $new_args{delta} * exp(-$new_args{q_rate} * $new_args{t});
148             }
149              
150 306         1784 $new_args{normalInv} = qnorm($new_args{delta} / exp(-$new_args{q_rate} * $new_args{t}));
151 306         483 my $k;
152 306 50       561 if ($new_args{normalInv}) {
153             $k =
154 306 100       723 ($new_args{option_type} eq 'VANILLA_CALL')
155             ? _calculate_strike_for_vanilla_call(\%new_args)
156             : _calculate_strike_for_vanilla_put(\%new_args);
157             }
158              
159 306         1164 return $k;
160             }
161              
162             sub _calculate_strike_for_vanilla_put {
163 154     154   224 my $args = shift;
164              
165             my ($normalInv, $delta, $sigma, $time_in_years, $r, $d, $S, $premium_adjusted) = (
166 154         424 $args->{normalInv}, $args->{delta}, $args->{atm_vol}, $args->{t}, $args->{r_rate}, $args->{q_rate}, $args->{spot}, $args->{premium_adjusted});
167              
168             #Step 1: Set initial k level with corresponding to spot delta without premium_adjusted
169 154         378 my $k = $S * exp(($normalInv * $sigma * sqrt($time_in_years)) + ($r - $d + ($sigma * $sigma / 2)) * $time_in_years);
170              
171 154         1919 for (my $i = 1; $i <= 5 * $premium_adjusted; $i++) {
172 623         848 my $k1 = $k;
173              
174             # Step 2: Calculate option price and the corresponding delta
175 623         1171 my $option_price_1 = Math::Business::BlackScholesMerton::NonBinaries::vanilla_put($S, $k1, $time_in_years, $r, $r - $d, $sigma);
176 623         6690 my $delta_1 = Math::Business::BlackScholes::Binaries::Greeks::Delta::vanilla_put($S, $k1, $time_in_years, $r, $r - $d, $sigma);
177              
178             # Step 3: Numerically evaluate option at slightly different strike and calculate its corresponding delta
179 623         5246 my $option_price_2 = Math::Business::BlackScholesMerton::NonBinaries::vanilla_put($S, $k1 * 1.000001, $time_in_years, $r, $r - $d, $sigma);
180 623         6425 my $delta_2 = Math::Business::BlackScholes::Binaries::Greeks::Delta::vanilla_put($S, $k1 * 1.000001, $time_in_years, $r, $r - $d, $sigma);
181             # Option's premium adjusted delta derivatives with respect to strike
182 623         5012 my $d_delta = ($delta_2 - $option_price_2 / $S - $delta_1 + $option_price_1 / $S) / ($k1 * 0.000001);
183              
184             # Step 4: Calcuate strike, k for i+1
185 623         940 $k = $k1 - (($delta_1 + $delta) - $option_price_1 / $S) / $d_delta;
186              
187             # This is because we cant take log of negative in BS pricer.
188 623 50       1100 if ($k <= 0) {
189 0         0 $k = 0;
190 0         0 last;
191             }
192              
193 623 100       1510 last if (abs($k - $k1) <= 0.0000000000000000000001);
194             }
195              
196 154         279 return $k;
197             }
198              
199             sub _calculate_strike_for_vanilla_call {
200 152     152   249 my $args = shift;
201              
202             my ($normalInv, $delta, $sigma, $time_in_years, $r, $d, $S, $premium_adjusted) = (
203 152         413 $args->{normalInv}, $args->{delta}, $args->{atm_vol}, $args->{t}, $args->{r_rate}, $args->{q_rate}, $args->{spot}, $args->{premium_adjusted});
204              
205             #Step 1: Set initial k level with corresponding to spot delta without premium_adjusted.
206 152         390 my $k = $S * exp(-($normalInv * $sigma * sqrt($time_in_years)) + ($r - $d + ($sigma * $sigma / 2)) * $time_in_years);
207              
208 152         340 for (my $i = 1; $i <= 5 * $premium_adjusted; $i++) {
209 665         915 my $k1 = $k;
210              
211             # Step 2: Calculate option price and the corresponding delta
212 665         1349 my $option_price_1 = Math::Business::BlackScholesMerton::NonBinaries::vanilla_call($S, $k1, $time_in_years, $r, $r - $d, $sigma);
213 665         7043 my $delta_1 = Math::Business::BlackScholes::Binaries::Greeks::Delta::vanilla_call($S, $k1, $time_in_years, $r, $r - $d, $sigma);
214              
215             # Step 3: Numerically evaluate option at slightly different strike and calculate its corresponding delta
216 665         5541 my $option_price_2 = Math::Business::BlackScholesMerton::NonBinaries::vanilla_call($S, $k1 * 1.000001, $time_in_years, $r, $r - $d, $sigma);
217 665         6684 my $delta_2 = Math::Business::BlackScholes::Binaries::Greeks::Delta::vanilla_call($S, $k1 * 1.000001, $time_in_years, $r, $r - $d, $sigma);
218              
219             # Option's premium adjusted delta derivatives with respect to strike
220 665         5236 my $d_delta = ($delta_2 - $option_price_2 / $S - $delta_1 + $option_price_1 / $S) / ($k1 * 0.000001);
221              
222             # Step 4: Calcuate strike, k for i+1. If $d_delta is zero, then $k will be a negative infinity number
223             # Instead of giving it a negative infinity number, we'll assign zero to it
224 665 50       1251 $k = ($d_delta) ? $k1 - (($delta_1 - $delta) - $option_price_1 / $S) / $d_delta : 0;
225              
226             # This is because we cant take log of negative in BS pricer.
227 665 50       1184 if ($k <= 0) {
228 0         0 $k = 0;
229 0         0 last;
230             }
231              
232 665 100       1603 last if (abs($k - $k1) <= 0.0000000000000000000001);
233             }
234              
235 152         278 return $k;
236             }
237              
238             =head2 get_ATM_strike_for_spot_delta
239              
240             Returns the ATM strike that satisifies straddle Delta neutral.
241              
242             my $atm_strike = get_ATM_strike_for_spot_delta({
243             atm_vol => $atm_vol,
244             t => $t,
245             r_rate => $r_rate,
246             q_rate => $q_rate,
247             spot => $spot,
248             premium_adjusted => $premium_adjusted,
249             });
250              
251             The ATM volatility quoted in the market is that of a zero delta
252             straddle, whose strike, for each given expiry, is chosen so that
253             a put and a call have the SAME delta but with different signs.
254             No delta hedge is needed when trading this straddle.
255              
256             The ATM volatility for the expiry T is the volatility where the ATM strike K must satisfy the following condition:
257             Delta Call = - Delta Put
258              
259             The ATM strike is the strike correspond to this ATM volatility.
260              
261             =cut
262              
263             sub get_ATM_strike_for_spot_delta {
264 28     28 1 60631 my $args = shift;
265              
266 28         125 my %new_args = %$args;
267 28         90 my @required = qw(atm_vol t r_rate q_rate spot premium_adjusted);
268 28         61 for (@required) {
269 168 100       388 croak "Arg $_ is undef at get_ATM_strike_for_spot_delta" unless defined $args->{$_};
270             }
271              
272             my ($sigma, $time_in_years, $r, $d, $S, $premium_adjusted) =
273 27         70 ($new_args{atm_vol}, $new_args{t}, $new_args{r_rate}, $new_args{q_rate}, $new_args{spot}, $new_args{premium_adjusted});
274              
275 27 100       63 my $constant = ($premium_adjusted) ? -0.5 : 0.5;
276 27         90 my $strike = $S * exp(($r - $d + $constant * $sigma * $sigma) * $time_in_years);
277              
278 27         247 return $strike;
279             }
280              
281             =head2 get_moneyness_for_strike
282              
283             Returns the corresponding moneyness point for a given strike.
284              
285             my $moneyness = get_moneyness_for_strike({
286             strike => $strike,
287             spot => $spot,
288             });
289              
290             =cut
291              
292             sub get_moneyness_for_strike {
293 2     2 1 3284 my $args = shift;
294              
295 2         13 for (qw(spot strike)) {
296 3 100       21 croak "$_ is undef when you convert strike to moneyness" unless defined $args->{$_};
297             }
298              
299 1         7 return $args->{strike} / $args->{spot} * 100;
300             }
301              
302             =head2 get_strike_for_moneyness
303              
304             Returns the corresponding strike value for a given moneyness point.
305              
306              
307             my $strike = get_strike_for_moneyness({
308             spot => $spot,
309             moneyness => $moneyness
310             });
311              
312             =cut
313              
314             sub get_strike_for_moneyness {
315 2     2 1 3993 my $args = shift;
316              
317 2         7 for (qw(spot moneyness)) {
318 3 100       21 croak "$_ is not defined at get_strike_for_moneyness" unless defined $args->{$_};
319             }
320              
321 1         4 my $moneyness = $args->{moneyness};
322 1 50       7 $moneyness = $args->{moneyness} / 100 if $moneyness > 3;
323              
324 1         6 return $moneyness * $args->{spot};
325             }
326              
327             =head2 get_2vol_butterfly
328              
329             Returns the two vol butterfly that satisfy the abitrage free constraint.
330              
331             my $bf = get_2vol_butterfly($spot, $tiy,$delta, $atm, $rr, $bf, $r, $d, $premium_adjusted, $bf_style);
332              
333             DESCRIPTION:
334             There are two different butterfly vol:
335              
336             -The first one is 2 vol butterfly which is the quoted butterfly that appear in interbank market (vwb= 0.5(Sigma(call)+SigmaP(Put))- Sigma(ATM)).
337              
338             -The second one is 1 vol butterfly which is the butterfly volatility that consistent with market standard conventions of trading the butterfly strategies (some paper called it market strnagle volatility).
339              
340             The market standard conventions for trading the butterfly is price the strangle with one unique volatility whereas with the first butterfly convention(ie the quoted butterfly vol), we will price the strangle with two volatility.
341              
342             There is possible arbitrage opportunities that might result from the inconsistency caused by the above quoting mechanism.
343              
344             Hence, in practice, we need to build a volatility smile so that the price of the two options strangle based on the volatility surface that we build will have same price as the one from the market conventional butterfly trading(ie with one unique volatility).
345              
346             The consistent constraint that need to hold in building surface is as shown as follow :
347             C(K_25C, Vol_K_25C) + P(K_25P, Vol_K_25P) = C(K_25C, Vol_market_conventional_bf) + P(K_25P, Vol_market_conventional_bf)
348              
349             The first step in building the abitrage free volatility smile is to determine an equivalent butterfly which will combines with all the ATM and RR vol to yields a volatility smile that satisfies the above constraint .
350              
351             This equivalent butterfly which is also named as two vol butterfly or smiled butterfly can be found numerically.
352              
353             This is only needed if the butterfly is the 1 vol butterfly from the market without any adjustment yet. If vol smile is abitrage free, hence their BF is already adjusted accordingly to fullfill the abitrage free constraints, hence no adjustment needed on the BF.
354              
355             As this process gone through a numerical procedures, hence the result might be slightly different when compare with other vendor as they might used different approach to get the relevant result.
356              
357             =cut
358              
359             sub get_2vol_butterfly {
360 8     8 1 8048 my ($S, $tiy, $delta, $atm, $rr, $bf, $r, $d, $premium_adjusted, $bf_style) = @_;
361              
362             # If the bf is not 1 vol butterfly, no need to do adjustment.
363 8 100       23 if ($bf_style ne '1_vol') {
364 1         4 return $bf;
365             }
366              
367 7         32 my $strike_atm = get_ATM_strike_for_spot_delta({
368             atm_vol => $atm,
369             t => $tiy,
370             r_rate => $r,
371             q_rate => $d,
372             spot => $S,
373             premium_adjusted => $premium_adjusted
374             });
375              
376             # Step 1: Obtain the market conventional volatility of butterfly (ie the unique butterfly volatility used in price strangle)
377 7         22 my $market_conventional_bf = $atm + $bf;
378              
379             # Step 2 to 5 is mainly to obtain the difference between the two strangles (one valued with market quoted volatility and one with market conventional butterfly volatility)
380 7         15 my $strangle_difference = _strangle_difference($S, $tiy, $delta, $atm, $rr, $bf, $market_conventional_bf, $r, $d, $strike_atm, $premium_adjusted);
381              
382             # Step 6.1 : just return the quoted butterfly if the difference is too small
383 7 50       18 return $bf if (abs($strangle_difference) < 0.0000001 * $S);
384              
385             # 6.2 : Increase the bf by one basic point of 0.0001 and go through iteration to perform the same calculation from step 1 to 5 with new incremented bf
386 7         12 $bf = $bf + 0.0001;
387 7         10 my $diff_bf = 0.0001;
388              
389 7         33 while (abs($strangle_difference) > 0.0000001 * $S) {
390 58         129 my $new_strangle_difference =
391             _strangle_difference($S, $tiy, $delta, $atm, $rr, $bf, $market_conventional_bf, $r, $d, $strike_atm, $premium_adjusted);
392             # Step 7: Calculate the numerical derivatives of the strangle diffrences with respect to the new bf
393 58         89 my $D_strangle_difference = ($new_strangle_difference - $strangle_difference) / $diff_bf;
394             # Step 8: Calculate the new bf and the
395 58         77 $bf = $bf - $new_strangle_difference / $D_strangle_difference;
396 58         85 $diff_bf = -$new_strangle_difference / $D_strangle_difference;
397 58         157 $strangle_difference = $new_strangle_difference;
398             }
399 7         48 return $bf;
400             }
401              
402             =head2 get_1vol_butterfly
403              
404             Returns the 1 vol butterfly which is the butterfly volatility that consistent with market standard conventions of trading the butterfly strategies (some paper called it market strnagle volatility)
405              
406             my $bf_1vol = get_1vol_butterfly({
407             spot => $volsurface->underlying->spot,
408             tiy => $tiy,
409             delta => 0.25,
410             call_vol => $smile->{25},
411             put_vol => $smile->{75},
412             atm_vol => $smile->{50},
413             bf_1vol => 0,
414             r => $volsurface->underlying->interest_rate_for($tiy),
415             q => $volsurface->underlying->dividend_rate_for($tiy),
416             premium_adjusted => $volsurface->underlying->{market_convention}->{delta_premium_adjusted},
417             bf_style => '2_vol',
418             });
419              
420             =cut
421              
422             sub get_1vol_butterfly {
423 3     3 1 5075 my $args = shift;
424             my ($S, $tiy, $delta, $call_vol, $put_vol, $atm_vol, $bf_1vol, $r, $d, $premium_adjusted, $bf_style) =
425 3         27 @{$args}{'spot', 'tiy', 'delta', 'call_vol', 'put_vol', 'atm_vol', 'bf_1vol', 'r', 'q', 'premium_adjusted', 'bf_style'};
  3         14  
426              
427 3 100       10 if ($bf_style ne '2_vol') {
428 2         8 return $bf_1vol;
429             }
430              
431 1         12 my $smile_rr = $call_vol - $put_vol;
432 1         4 my $smile_bf = ($call_vol + $put_vol) / 2 - $atm_vol;
433             # set initial guess for 1 vol
434 1 50       4 if (not $bf_1vol) {
435 1         2 $bf_1vol = $smile_bf - 0.0001;
436             }
437              
438 1         5 my $bf_2vol = get_2vol_butterfly($S, $tiy, $delta, $atm_vol, $smile_rr, $bf_1vol, $r, $d, $premium_adjusted, '1_vol');
439              
440 1         5 my $differences_between_two_bf = $smile_bf - $bf_2vol;
441              
442 1 50       6 return $bf_1vol if ($differences_between_two_bf > 0.0001);
443              
444 1         6 while ($differences_between_two_bf < 0.0001) {
445 3         8 $bf_1vol = $bf_1vol - 0.0001;
446 3         8 $bf_2vol = get_2vol_butterfly($S, $tiy, $delta, $atm_vol, $smile_rr, $bf_1vol, $r, $d, $premium_adjusted, '1_vol');
447              
448 3         11 $differences_between_two_bf = $smile_bf - $bf_2vol;
449              
450             }
451 1         13 return $bf_1vol;
452             }
453              
454             sub _strangle_difference {
455              
456 65     65   141 my ($S, $tiy, $delta, $atm, $rr, $bf, $market_conventional_bf, $r, $d, $strike_atm, $premium_adjusted) = @_;
457              
458             #Step 2: Retrieve the two call and put volatility in a "consistent" way which means from the market quoted ATM, BF and RR volatility.
459 65         124 my $put_sigma = $atm + $bf - $rr / 2;
460 65         97 my $call_sigma = $atm + $bf + $rr / 2;
461              
462             #Step 3: Calculate the two call and put strikes with the "consistent" volatilities obtained from step 2
463 65 100       367 my $consistent_call_strike = get_strike_for_spot_delta({
464             delta => $delta,
465             option_type => 'VANILLA_CALL',
466             atm_vol => $call_sigma,
467             t => $tiy,
468             r_rate => $r,
469             q_rate => $d,
470             spot => $S,
471             premium_adjusted => $premium_adjusted,
472             forward => $tiy >= 1 ? 1 : 0
473             });
474 65 100       404 my $consistent_put_strike = get_strike_for_spot_delta({
475             delta => $delta,
476             option_type => 'VANILLA_PUT',
477             atm_vol => $put_sigma,
478             t => $tiy,
479             r_rate => $r,
480             q_rate => $d,
481             spot => $S,
482             premium_adjusted => $premium_adjusted,
483             forward => $tiy >= 1 ? 1 : 0
484             });
485              
486             #Step 4: Calculate the two call and put strikes for the market traded butterfly (ie with market conventional volatility of butterfly obtain on step 1.)
487 65 100       428 my $market_conventional_call_strike = get_strike_for_spot_delta({
488             delta => $delta,
489             option_type => 'VANILLA_CALL',
490             atm_vol => $market_conventional_bf,
491             t => $tiy,
492             r_rate => $r,
493             q_rate => $d,
494             spot => $S,
495             premium_adjusted => $premium_adjusted,
496             forward => $tiy >= 1 ? 1 : 0
497             });
498 65 100       424 my $market_conventional_put_strike = get_strike_for_spot_delta({
499             delta => $delta,
500             option_type => 'VANILLA_PUT',
501             atm_vol => $market_conventional_bf,
502             t => $tiy,
503             r_rate => $r,
504             q_rate => $d,
505             spot => $S,
506             premium_adjusted => $premium_adjusted,
507             forward => $tiy >= 1 ? 1 : 0
508             });
509              
510             #Step 5: Calculate the difference between the strangle struck at the market traded butterfly strikes(those obtained on step 4) valued with the smile volatility( ie the one build with market quoted volatilities), and the same strangle struck at same market traded butterfly strikes but valued with market conventional volatility of butterfly(ie. the one obtained on step 1).
511              
512             # 5.1:To obtain the corresponding volatilities for market traded butterfly strikes (ie those obtained on step 4.)
513 65         251 my $market_conventional_call_sigma = _smile_approximation($S, $tiy, 2, $market_conventional_call_strike,
514             $consistent_put_strike, $strike_atm, $consistent_call_strike, $put_sigma, $atm, $call_sigma, $d, $r);
515              
516 65         123 my $market_conventional_put_sigma = _smile_approximation($S, $tiy, 2, $market_conventional_put_strike,
517             $consistent_put_strike, $strike_atm, $consistent_call_strike, $put_sigma, $atm, $call_sigma, $d, $r);
518              
519             # 5.2: Strangle struck at market traded butterfly strikes (ie those obtained on step 4) with smile volatility builds with market quoted volatilities
520 65         160 my $call_with_consistent_vol = Math::Business::BlackScholesMerton::NonBinaries::vanilla_call($S, $market_conventional_call_strike,
521             $tiy, $r, $r - $d, $market_conventional_call_sigma);
522              
523 65         731 my $put_with_consistent_vol = Math::Business::BlackScholesMerton::NonBinaries::vanilla_put($S, $market_conventional_put_strike,
524             $tiy, $r, $r - $d, $market_conventional_put_sigma);
525 65         654 my $strangle_with_consistent_vol = $call_with_consistent_vol + $put_with_consistent_vol;
526             # 5.3: Strangle struck at market traded butterfly strikes (ie those obtained on step 4) with market coventional volatility of butterfly.
527 65         127 my $call_with_market_conventional_bf = Math::Business::BlackScholesMerton::NonBinaries::vanilla_call($S, $market_conventional_call_strike,
528             $tiy, $r, $r - $d, $market_conventional_bf);
529 65         655 my $put_with_market_conventional_bf =
530             Math::Business::BlackScholesMerton::NonBinaries::vanilla_put($S, $market_conventional_put_strike, $tiy, $r, $r - $d, $market_conventional_bf);
531 65         655 my $strangle_with_market_conventional_bf = $call_with_market_conventional_bf + $put_with_market_conventional_bf;
532              
533             # 5.4: Calculate differences between strangle from 5.2 and 5.3
534              
535 65         116 my $strangle_difference = $strangle_with_consistent_vol - $strangle_with_market_conventional_bf;
536 65         115 return $strangle_difference;
537              
538             }
539              
540             sub _smile_approximation {
541 130     130   273 my ($S, $tiy, $order_approx, $k, $k1, $k2, $k3, $vol_k1, $vol_k2, $vol_k3, $d, $r) = @_;
542              
543 130 50 33     407 if ($order_approx < 1 or $order_approx > 2) {
544 0         0 croak "$0: Supported order 1 and 2. Not supported order [$order_approx].";
545             }
546 130         168 my $vol;
547 130         238 my $F = $S * exp(($r - $d) * $tiy);
548 130         296 my $Y_1 = (log($k2 / $k) * log($k3 / $k)) / (log($k2 / $k1) * log($k3 / $k1));
549             # At grid points k3 or k1, this is zero.
550 130         262 my $Y_2 = (log($k3 / $k) * log($k / $k1)) / (log($k3 / $k2) * log($k2 / $k1));
551             # At grid points k1 or k2, this is zero.
552 130         254 my $Y_3 = (log($k / $k1) * log($k / $k2)) / (log($k3 / $k1) * log($k3 / $k2));
553              
554 130         260 my $d1_k = ((0.5 * $vol_k2 * $vol_k2 * $tiy) + log($F / $k)) / ($vol_k2 * sqrt($tiy));
555 130         205 my $d2_k = $d1_k - ($vol_k2 * sqrt($tiy));
556              
557 130         1978 my $d1_k1 = ((0.5 * $vol_k2 * $vol_k2 * $tiy) + log($F / $k1)) / ($vol_k2 * sqrt($tiy));
558 130         200 my $d2_k1 = $d1_k1 - ($vol_k2 * sqrt($tiy));
559              
560 130         242 my $d1_k3 = ((0.5 * $vol_k2 * $vol_k2 * $tiy) + log($F / $k3)) / ($vol_k2 * sqrt($tiy));
561 130         223 my $d2_k3 = $d1_k3 - ($vol_k2 * sqrt($tiy));
562              
563             # For the 1st order approximation, what happens at market grid points is that it
564             # will be equal to the grid point volatility.
565 130         194 my $vol_1st_order = ($Y_1 * $vol_k1) + ($Y_2 * $vol_k2) + ($Y_3 * $vol_k3);
566              
567 130 50       245 return $vol_1st_order if ($order_approx == 1);
568              
569             # 2nd order approximation
570 130         162 my $D1_k = $vol_1st_order - $vol_k2;
571 130         273 my $D2_k = $Y_1 * $d1_k1 * $d2_k1 * (($vol_k1 - $vol_k2)**2) + $Y_3 * $d1_k3 * $d2_k3 * (($vol_k3 - $vol_k2)**2);
572 130         219 my $temp1 = ($vol_k2 * $vol_k2) + ($d1_k * $d2_k * (2 * $vol_k2 * $D1_k + $D2_k));
573              
574             # default to first-order approximation, if 2nd order would lead to imaginary numbers
575 130 50       218 if ($temp1 < 0) {
576 0 0       0 my $implied_vol_method = ($k > $k2) ? 'VANILLA_CALL' : 'VANILLA_PUT';
577 0         0 $vol = _implied_vol($S, $tiy, $k, $S * 0.00001, $r, $d, $implied_vol_method);
578             } else {
579 130         166 $temp1 = sqrt($temp1);
580 130         204 $vol = $vol_k2 + ((-$vol_k2 + $temp1) / ($d1_k * $d2_k));
581             }
582              
583 130         231 return $vol;
584             }
585              
586             sub _implied_vol {
587 0     0     my ($S, $tiy, $k, $price, $r, $d, $type) = @_;
588              
589 0 0         return 0 if ($price < 0);
590 0           my $F = $S * exp(($r - $d) * $tiy);
591              
592             # The starting point setting
593 0           my $vol = sqrt(2 / $tiy * $F / $k);
594              
595 0 0         $vol = 0.15 if ($vol == 0);
596 0           my $esc = 1;
597 0           my $i = 1;
598 0           my $option_price;
599             my $vega;
600              
601 0           while (abs($esc) > 0.000001) {
602 0 0         return 0 if ($i > 35);
603 0 0         if ($type eq 'VANILLA_CALL') {
604 0           $option_price = Math::Business::BlackScholesMerton::NonBinaries::vanilla_call($S, $k, $tiy, $r, $r - $d, $vol);
605 0           $vega = Math::Business::BlackScholes::Binaries::Greeks::Vega::vanilla_call($S, $k, $tiy, $r, $r - $d, $vol);
606             } else {
607 0           $option_price = Math::Business::BlackScholesMerton::NonBinaries::vanilla_put($S, $k, $tiy, $r, $r - $d, $vol);
608 0           $vega = Math::Business::BlackScholes::Binaries::Greeks::Vega::vanilla_put($S, $k, $tiy, $r, $r - $d, $vol);
609             }
610              
611 0 0         return 0 if ($vega <= 0.00000001);
612 0           $esc = $option_price - $price;
613 0           $vol = $vol - $esc / $vega;
614 0           $i++;
615             }
616              
617 0           return $vol;
618             }
619              
620             =head1 AUTHOR
621              
622             Binary.com, C<< <support at binary.com> >>
623              
624             =head1 BUGS
625              
626             Please report any bugs or feature requests to C<bug-volsurface-utils at rt.cpan.org>, or through
627             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=VolSurface-Utils>. I will be notified, and then you'll
628             automatically be notified of progress on your bug as I make changes.
629              
630              
631              
632              
633             =head1 SUPPORT
634              
635             You can find documentation for this module with the perldoc command.
636              
637             perldoc VolSurface::Utils
638              
639              
640             You can also look for information at:
641              
642             =over 4
643              
644             =item * RT: CPAN's request tracker (report bugs here)
645              
646             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=VolSurface-Utils>
647              
648             =item * AnnoCPAN: Annotated CPAN documentation
649              
650             L<http://annocpan.org/dist/VolSurface-Utils>
651              
652             =item * CPAN Ratings
653              
654             L<http://cpanratings.perl.org/d/VolSurface-Utils>
655              
656             =item * Search CPAN
657              
658             L<http://search.cpan.org/dist/VolSurface-Utils/>
659              
660             =back
661              
662              
663             =head1 ACKNOWLEDGEMENTS
664              
665              
666             =head1 LICENSE AND COPYRIGHT
667              
668             Copyright 2015 Binary.com.
669              
670             =cut
671              
672             1; # End of VolSurface::Utils