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 |