Historische Volatilität von Wertpapieren mit Math::Business::BlackScholes berechnen

Veröffentlicht von Thomas Fahle am (Permalink)

 

Math::Business::BlackScholes

Das CPAN-Modul Math::Business::BlackScholes - Black-Scholes option price model functions von Anders Johnson bietet neben Funktionen zur Berechnung der Preise von klassischen Put- bzw. Call-Optionen nach dem Black-Scholes-Modell auch eine Funktion zur Berechnung der historischen Volatilität von Wertpapieren.

In diesem Beitrag geht es ausschließlich um die historische Volatilität, alle anderen Funktionen des Moduls werden nicht besprochen.

Zentrierte historische Volatilität

Math::Business::BlackScholes berechnet die annualisierte historische Volatilität als Standardabweichung der logarithmischen Renditen (stetige Verzinsung), die auch als zentrierte historische Volatilität bezeichnet wird.

 

Beispiel: Historische Volatilität

Das folgende Beispiel ruft zunächst die historischen Börsenkurse der letzen 30 Tage mittels Finance::QuoteHist, das bereits hier beschrieben wurde, ab.

Im nächsten Schritt wird mittels der Funktion historical_volatility die annualisierte historische Volatilität berechnet. Die Funktion historical_volatility erwartet zwei Parameter: eine Referenz auf eine Liste der Schlusskurse und optional die Anzahl der Tradingtage pro Jahr (default 250 Tage).

Die Anzahl der Elemente in der Liste der Schlusskurse wird von historical_volatility als Anzahl der Tage gewertet, für welche die historische Volatilität berechnet werden soll. Wenn die Liste 30 Elemente enthält, wird die Vola über 30 Tradingtage berechnet - bei 50 Elementen über 50 Tradingtage. Wenn weniger als 10 Elemente angeliefert werden, bricht die Funktion ab.

#!/usr/bin/perl
use strict;
use warnings;

use feature 'say';

use Finance::QuoteHist;
use Math::Business::BlackScholes qw/historical_volatility/;

my @symbols       = qw/^DJI ^GDAXI/; # Dow Jones and DAX indices
my $days_per_year = 252; # Trading days per year

# Defaults to Finance::QuoteHist::Yahoo
my $q = Finance::QuoteHist->new(
    symbols    => [@symbols],
    start_date => '30 business days ago',
    end_date   => 'today',
);

foreach my $symbol (@symbols) {
    my @closing_prices;
    # Iterate over quotes and collect closing prices
    foreach my $quote ( $q->quotes() ) {
        my ( $sym, $date, $open, $high, $low, $close, $volume ) = @$quote;
        next unless $sym eq $symbol;
        push @closing_prices, $close;
    }
    my $days_in_scope         = scalar @closing_prices;
    my $historical_volatility = historical_volatility( \@closing_prices, $days_per_year );
    say "Centered historical volatility ($days_in_scope days) for $symbol: $historical_volatility";
}

Das Programm erzeugt z.B. folgende Ausgabe:

Centered historical volatility (30 days) for ^DJI: 0.11825075886597
Centered historical volatility (30 days) for ^GDAXI: 0.147215875305835

 

Beispiel: Rollende Historische Volatilität

In diesem Beispiel soll die historische Volatilität über die letzten 30 Tradingtage für historische Kursdaten berechnet werden.

Da mindestens 30 Schlusskurse (Array Index 29) zur Berechnung der historischen Volatilität benötigt werden, kann an den ersten 29 Tagen (Array Index von 0 bis 28) keine Vola berechnet werden.

Wie oben bereits erwähnt erwartet historical_volatility() einen genau passenden Array als ersten Parameter. Daher wird die Liste der Schlusskurse, die an historical_volatility() übergeben wird, mittels Array Slices während des Loops über alle Schlusskurse passend erzeugt.

#!/usr/bin/perl
use strict;
use warnings;

use feature 'say';

use Finance::QuoteHist;
use Math::Business::BlackScholes qw/historical_volatility/;

my @symbols       = qw/^DJI ^GDAXI/;    # Dow Jones and DAX indices
my $days_per_year = 252;                # Trading days per year
my $days_in_scope = 30;

# Defaults to Finance::QuoteHist::Yahoo
my $q = Finance::QuoteHist->new(
    symbols    => [@symbols],
    start_date => '45 business days ago',
    end_date   => 'today',
);

foreach my $symbol (@symbols) {
    # 3 parallel arrays
    my @closing_prices;
    my @dates;
    my @historical_vola;
    # Iterate over quotes and collect closing prices and dates
    foreach my $quote ( $q->quotes() ) {
        my ( $sym, $date, $open, $high, $low, $close, $volume ) = @$quote;
        next unless $sym eq $symbol;
        push @closing_prices, $close;
        push @dates,          $date;
    }
    # Loop over closing_prices,
    # slice array @closing_prices to suit historical_volatility()
    # and collect dates and hist vola
    for ( my $i = 0 ; $i <= $#closing_prices ; $i++ ) {
        $historical_vola[$i] = 'N/A';
        if ( $i >= $days_in_scope - 1 ) {
            # Array slice from current index (upper) back down to lookup period (days in scope) -1 (lower)
            my $lower                   = $i - ( $days_in_scope - 1 );
            my @closing_prices_in_scope = @closing_prices[ $lower .. $i ];
            $historical_vola[$i] = historical_volatility( \@closing_prices_in_scope, $days_per_year );
        }
    }
    # Loop over closing_prices and display index, current symbol, closing price and computed vola
    for ( my $i = 0 ; $i <= $#closing_prices ; $i++ ) {
        say "$i $symbol $dates[$i] $closing_prices[$i] $historical_vola[$i]";
    }
}

Das Programm erzeugt z.B. folgende Ausgabe:

0 ^DJI 2021/06/07 34630.2383 N/A
1 ^DJI 2021/06/08 34599.8203 N/A
2 ^DJI 2021/06/09 34447.1406 N/A
3 ^DJI 2021/06/10 34466.2383 N/A
4 ^DJI 2021/06/11 34479.6016 N/A
5 ^DJI 2021/06/14 34393.7500 N/A
6 ^DJI 2021/06/15 34299.3281 N/A
7 ^DJI 2021/06/16 34033.6719 N/A
8 ^DJI 2021/06/17 33823.4492 N/A
9 ^DJI 2021/06/18 33290.0781 N/A
10 ^DJI 2021/06/21 33876.9688 N/A
11 ^DJI 2021/06/22 33945.5781 N/A
12 ^DJI 2021/06/23 33874.2383 N/A
13 ^DJI 2021/06/24 34196.8203 N/A
14 ^DJI 2021/06/25 34433.8398 N/A
15 ^DJI 2021/06/28 34283.2695 N/A
16 ^DJI 2021/06/29 34292.2891 N/A
17 ^DJI 2021/06/30 34502.5117 N/A
18 ^DJI 2021/07/01 34633.5312 N/A
19 ^DJI 2021/07/02 34786.3516 N/A
20 ^DJI 2021/07/06 34577.3711 N/A
21 ^DJI 2021/07/07 34681.7891 N/A
22 ^DJI 2021/07/08 34421.9297 N/A
23 ^DJI 2021/07/09 34870.1602 N/A
24 ^DJI 2021/07/12 34996.1797 N/A
25 ^DJI 2021/07/13 34888.7891 N/A
26 ^DJI 2021/07/14 34933.2305 N/A
27 ^DJI 2021/07/15 34987.0195 N/A
28 ^DJI 2021/07/16 34687.8516 N/A
29 ^DJI 2021/07/19 33962.0391 0.124834226102961
30 ^DJI 2021/07/20 34511.9883 0.134218934103578
31 ^DJI 2021/07/21 34798.0000 0.135727160336629
32 ^DJI 2021/07/22 34823.3516 0.135730492963977
33 ^DJI 2021/07/23 35061.5508 0.137060882957344
34 ^DJI 2021/07/26 35144.3086 0.136828649663936
35 ^DJI 2021/07/27 35058.5195 0.136761489180455
36 ^DJI 2021/07/28 34930.9297 0.1349734387388
37 ^DJI 2021/07/29 35084.5312 0.133565951288527
38 ^DJI 2021/07/30 34935.4688 0.124188328961976
39 ^DJI 2021/08/02 34838.1602 0.11499320795688
40 ^DJI 2021/08/03 35116.3984 0.116799789157168
41 ^DJI 2021/08/04 34792.6719 0.120453610376238
42 ^DJI 2021/08/05 35064.2500 0.119465773449191
43 ^DJI 2021/08/06 35211.3086 0.118492022837183
0 ^GDAXI 2021/06/07 15677.1504 N/A
1 ^GDAXI 2021/06/08 15640.5996 N/A
2 ^GDAXI 2021/06/09 15581.1396 N/A
3 ^GDAXI 2021/06/10 15571.2197 N/A
4 ^GDAXI 2021/06/11 15693.2695 N/A
5 ^GDAXI 2021/06/14 15673.6396 N/A
6 ^GDAXI 2021/06/15 15729.5195 N/A
7 ^GDAXI 2021/06/16 15710.5703 N/A
8 ^GDAXI 2021/06/17 15727.6699 N/A
9 ^GDAXI 2021/06/18 15448.0400 N/A
10 ^GDAXI 2021/06/21 15603.2402 N/A
11 ^GDAXI 2021/06/22 15636.3301 N/A
12 ^GDAXI 2021/06/23 15456.3896 N/A
13 ^GDAXI 2021/06/24 15589.2305 N/A
14 ^GDAXI 2021/06/25 15607.9697 N/A
15 ^GDAXI 2021/06/28 15554.1797 N/A
16 ^GDAXI 2021/06/29 15690.5898 N/A
17 ^GDAXI 2021/06/30 15531.0400 N/A
18 ^GDAXI 2021/07/01 15603.8096 N/A
19 ^GDAXI 2021/07/02 15650.0898 N/A
20 ^GDAXI 2021/07/05 15661.9697 N/A
21 ^GDAXI 2021/07/06 15511.3799 N/A
22 ^GDAXI 2021/07/07 15692.7100 N/A
23 ^GDAXI 2021/07/08 15420.6396 N/A
24 ^GDAXI 2021/07/09 15687.9297 N/A
25 ^GDAXI 2021/07/12 15790.5098 N/A
26 ^GDAXI 2021/07/13 15789.6396 N/A
27 ^GDAXI 2021/07/14 15788.9805 N/A
28 ^GDAXI 2021/07/15 15629.6602 N/A
29 ^GDAXI 2021/07/16 15540.3096 0.132886029847589
30 ^GDAXI 2021/07/19 15133.2002 0.153747543860939
31 ^GDAXI 2021/07/20 15216.2695 0.154727885312354
32 ^GDAXI 2021/07/21 15422.5000 0.160355346073778
33 ^GDAXI 2021/07/22 15514.5400 0.159598602711704
34 ^GDAXI 2021/07/23 15669.2900 0.162434221185135
35 ^GDAXI 2021/07/26 15618.9805 0.162322368614483
36 ^GDAXI 2021/07/27 15519.1299 0.163323138451846
37 ^GDAXI 2021/07/28 15570.3604 0.163636303169993
38 ^GDAXI 2021/07/29 15640.4697 0.155067342214758
39 ^GDAXI 2021/07/30 15544.3896 0.153399408966577
40 ^GDAXI 2021/08/02 15568.7305 0.153335017048669
41 ^GDAXI 2021/08/03 15555.0801 0.149352162877445
42 ^GDAXI 2021/08/04 15692.1299 0.149461080971496
43 ^GDAXI 2021/08/05 15744.6699 0.149719787680581
44 ^GDAXI 2021/08/06 15778.0996 0.149363427544281

 

Siehe auch

 

Source-Code der Beispiele im Github Repo perl-howto-code.

 

Weitere Posts