File Coverage

blib/lib/VolSurface/Utils.pm
Criterion Covered Total %
statement 173 200 86.5
branch 39 62 62.9
condition 3 6 50.0
subroutine 20 21 95.2
pod 7 7 100.0
total 242 296 81.7


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