Zum Inhalt springen
stagflation

Monte-Carlo-Simulator für Wertpapier-Portfolios

Empfohlene Beiträge

stagflation
· bearbeitet von stagflation

Ich habe einen Monte-Carlo-Simulator für Wertpapier-Portfolios geschrieben. Hier ist den Source-Code (mcs.c)! Ich möchte Euch ermuntern, damit zu experimentieren und eigene Portfolios durchzurechnen.

 

Das Programm ist aber kein fertiges Windows-Programm mit Installer, GUI und grafischer Ausgabe. Sondern es ist ein kurzes C-Programm, das die Ergebnisse der Simulation in eine CSV-Datei schreibt. Die Grafiken und weitere Auswertungen kann man dann mit einer geeigneten Tabellenkalkulation (Excel, LibreOffice Calc) erstellen. Es ist also hauptsächlich für Anwender mit etwas Programmiererfahrung interessant. Weil das Programm so kurz ist, kann man es sehr schnell an andere Anforderungen anpassen - oder in eine andere Programmiersprache übertragen.

 

1)  Einführung

 

1.1)  Worum geht es?

 

Siehe Blog-Post von Dr. Gerd Kommer: https://gerd-kommer.de/monte-carlo-simulation-als-prognoseverfahren

 

1.2)  Was simuliert das Programm?

 

Man gibt folgende Werte für sein Portfolio vor:

  • Erwartete Rendite (z.B. 8%)
  • Volatilität (z.B. 14%)
  • Anfangsbetrag (z.B. 100.000 €)
  • Monatliche Zuzahlung oder Entnahme, auch mit jährlicher Steigerung (z.B 500 € monatlich mit 2% Steigerung pro Jahr)
  • Anlagedauer (z.B. 20 Jahre)
  • Anzahl der Iterationen (z.B. 1 Million)

Das Programm simuliert die Entwicklung des Portfolios auf Monatsbasis und mit log-normalverteilten Monatsrenditen (Wiener-Prozess). Als Ergebnis wird ein Histogramm erstellt und in eine CSV-Datei ausgegeben.

 

1.2)  Beispiele

 

Diese gezeigten Ergebnisse gelten für das Modell "Monte-Carlo-Simulation mit Log-normalverteilten Monatsrenditen".

 

Beispiel 1: Man möchte 10000 Euro in ein 80/20 Portfolio investieren. Wie wird sich des Portfolio nach 10, 20 und 30 Jahren entwickelt haben?

  • Startkapital: 100 k€
  • Keine Sparraten
  • Angenommene Erwartete Rendite: 7.5 %
  • Angenommene Volatilität: 12%

 

image.png.4bd7be99d6b1f331d212c1586fbd16de.png

(Klicken für Vergrößerung)

 

Man sieht schön, dass die Ergebnisverteilung nach 10, 20 und 30 Jahren sehr breit ist. Je länger die Anlagedauer, desto breiter die Verteilung.

 

Beispiel 2: Ein Anleger plant folgende Anlage:

  • Startkapital: 100 k€
  • Monatliche Zuzahlungen: 500 €, steigend mit 2% pro Jahr
  • Anlagedauer: 25 Jahre
  • Insgesamt eingezahltes Kapital: 292 k€

Er möchte für folgende Portfolios

  • Erwartete Rendite: 8.4 %, Volatilität 14% (100/00)
  • Erwartete Rendite: 7.5 %, Volatilität 12% (80/20)
  • Erwartete Rendite: 6.6 %, Volatilität 10% (60/40)

wissen, wie sich das Kapital entwickeln wird - und wie hoch die Wahrscheinlichkeit ist, dass am Ende

  • weniger als das eingezahlte Kapital zur Verfügung steht
  • weniger als der Wunschbetrag von 500 k€ zur Verfügung steht.

 

image.png.79474cd67ae27cd565b803385aafffc4.png

(Klicken für Vergrößerung)

 

Wenn man die berechneten Häufigkeiten so normiert, dass die Fläche unter den Kurven 1 ist, kann man direkt die Wahrscheinlichkeiten ablesen. Um eine höhere Genauigkeit zu erreichen, zoomen wir in den linken Bereich des Diagramms hinein - und erhöhen die Anzahl der Iterationen auf 20 Millionen:

 

image.png.ffac1e1eb8b5ecb305e24da4ce7cb074.png

(Klicken für Vergrößerung)

 

Man erhält:

 

Wahrscheinlichkeit, dass nach 25 Jahren weniger als das eingezahlte Kapital von 292 k€ verfügbar ist:

  • 100/0-Portfolio: 0.6 %
  • 80/20-Portfolio: 0.4 %
  • 60/40-Portfolio: 0.2 %

Wahrscheinlichkeit, dass nach 25 Jahren nicht mindestens das gewünschte Kapital von 500 k€ verfügbar ist:

  • 100/0-Portfolio: 8.8 %
  • 80/20-Portfolio: 7.9 %
  • 60/40-Portfolio: 8.4 %

 

2)  Bedienung des Programms

 

Man trägt die gewünschten Simulations-Parameter direkt in das Programm ein und compiliert es. Unter Linux kann man folgende Anweisung verwenden:

gcc -O2 -D DSFMT_MEXP=19937 -lm -ldSFMT -o mcs mcs.c

Unter Windows und MacOS sollte es ähnlich funktionieren.

 

Wenn man den Zufallszahlengenerator dSFMT verwenden will, muss man vorher dieses Paket installieren. Für einen ersten Test kann man aber auch den Zufallszahlengenerator des Betriebssystems bzw. der Programmierumgebung verwenden.

 

Danach führt man das Programm aus. Nach ein paar Sekunden oder Minuten (abhängig von der Zahl der gewählten Iterationen) erhält man eine Ergebnis Datei "result.txt". Die Datei "result.txt" kann man in eine Tabellenkalkulation importieren - und dort Diagramme oder weitere Auswertungen erstellen.

 

 

3)  Wie funktioniert das Programm?

 

Die eigentliche Monte-Carlo Simulation ist recht kurz:

// Simulations-Steuerung

const int    num_years                 = 25;            // Anzahl der Jahre, die simuliert werden
const int    num_iterations            = 1000000;       // Anzahl der Iterationen

const double return_year_pct           = 8.4;           // Jahres-Rendite in Prozent
const double volatility_year_pct       = 14.0;          // Jahres-Volatilität in Prozent

const double balance_start             = 100000;        // Anfangskapital
const double balance_add_month         = 500;           // Monatliche Zuzahlung
const double balance_add_year_incr_pct = 2;             // Zuzahlung erhöht sich jedes Jahr um X Prozent

void mc_simulator() {

    // Umrechnung auf stetige Monatswerte

    const double volatility_month = ( volatility_year_pct / 100 ) / sqrt( 12 );
    const double drift_month      = log( 1 + return_year_pct / 100 ) / 12 - volatility_month * volatility_month / 2;

    // Schleife über Iterationen

    for ( int iter = 0; iter < num_iterations; iter++ ) {

        double balance     = balance_start;
        double balance_add = balance_add_month;
        double log_return  = 0;

        // Schleife über Jahre

        for ( int year = 0; year < num_years; year++ ) {

            // Schleife über Monate eines Jahres

            for ( int month = 0; month < 12; month++ ) {
                log_return = drift_month + volatility_month * RAND_NORMAL();
                balance = balance * exp( log_return ) + balance_add;
            }

            balance_add *= 1 + balance_add_year_incr_pct / 100;
        }

        // Das Ergebnis der Iteration steht in balance
        // Hier steht der Code, mit dem Balance in ein Histogram eingetragen
        // und am Programmende ausgegeben wird

    }
}

 

Bei Monte-Carlo-Simulationen sollte man ein besonderes Augenmerk auf die Erzeugung der Zufallszahlen richten. Mit einem schlechten Zufallszahlen-Generator erhält man schlechte Ergebnisse!

 

Zunächst braucht man einen Zufallszahlen-Generator, der gleichmäßig verteilte Zufallszahlen im Bereich von 0 bis 1 berechnet. Die meisten Betriebssysteme bzw. Programmierumgebungen liefern einen solchen Generator mit (z.B. drand48). Für Monte-Carlo-Simulationen sind sie i.d.R. unbrauchbar.

 

Ich verwende deshalb den Zufallszahlengenerator dSFMT. Der ist zwar nicht perfekt - aber hinreichend gut.

 

Weiterhin braucht man eine Abbildungsfunktion, die gleichmäßig verteilte Zufallszahlen in normalverteilte Zufallszahlen konvertiert. In Excel gibt es hierfür die Funktion NORMINV. Für diese gibt es auch einen Nachbau in C. Alternativ kann man auch das Box-Muller Verfahren verwenden.

 

Die log-normalverteilten Zufallszahlen werden nicht explizit ausgerechnet. Die Log-Normalverteilung der Monatsrenditen ergibt sich automatisch über die e-Funktion im oben gezeigten Code, wenn der neue Portfolio-Wert am Ende des Monats berechnet wird:

balance = balance * exp( log_return ) + balance_add;

In Monte-Carlo-Simulationen implementiere ich gerne mehrere unterschiedliche Zufallsalgorithmen - und probiere sie der Reihe nach durch. Wenn sie die gleichen Ergebnisse liefern, weiß ich, dass alles in Ordnung ist. Wenn sie unterschiedliche Ergebnisse liefern, ist es ein Hinweis auf einen Fehler.

// ***********************************************************************
// Auswahl Zufallszahlen-Generator
// ***********************************************************************

// Zufallszahlen-Generator für Zufallszahlen mit
// gleichförmiger Verteilung zwischen 0 und 1
// Eines der folgenden Verfahren muss gewählt werden

// #define RAND_UNIFORM() dsfmt_genrand_close_open( &dsfmt )
// #define RAND_UNIFORM() drand48()

#define RAND_UNIFORM() dsfmt_genrand_open_open( &dsfmt )

// Zufallszahlen-Generator für Zufallszahlen mit
// Normalverteilung mit µ=0, sigma=1
// Eines der folgenden Verfahren muss gewählt werden

// #define RAND_NORMAL() rand_normal_1()
// #define RAND_NORMAL() rand_normal_2()

#define RAND_NORMAL() rand_normal_1()

// ***********************************************************************
// Zufallszahlen-Generatoren
// ***********************************************************************

// Zufallszahlen-Generator nach Box-Muller
// https://www.tspi.at/2021/08/17/gaussianrng.html

// ergibt Zufallszahlen mit Normalverteilung (µ=0, sigma=1)

inline static double rand_normal_1() {

    double a = RAND_UNIFORM();
    double b = RAND_UNIFORM();

    return sqrt( -2.0 * log( a ) ) * cos( 2 * M_PI * b );
}

...

inline static double rand_normal_2() {

    ...
}

 

3)  Literatur

  1. Einführungsartikel von Gerd Kommer: https://gerd-kommer.de/monte-carlo-simulation-als-prognoseverfahren/
  2. Lehrbuch mit der Theorie und Rechenbeispielen: Dietmar Ernst, Marc Schurer: "Portfolio-Management. Theorie und Praxis mit Excel und Matlab"
  3. Homepage der SFMT und dSFMT Zufallszahlen-Generatoren:  http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/SFMT, http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/SFMT/#dSFMT
  4. Box-Muller: https://www.tspi.at/2021/08/17/gaussianrng.html
  5. Nachbau Excel-Funktion NORMINV: https://www.johndcook.com/blog/cpp_phi_inverse
  6. Entnahmerechner des BVI (mit MC-Simulation): https://www.bvi.de/service/rechner/entnahmerechner

Diesen Beitrag teilen


Link zum Beitrag
Glory_Days
· bearbeitet von Glory_Days

Danke für deine Arbeit und die Bereitstellung des Source Codes.

 

Dass die Verteilungen mit längerer Anlagedauer breiter werden, ist eine triviale Einsicht. Interessanter wäre die Angabe des coefficient of variation (CV) bzw. des geometric CV (GCV), die die Breite der Verteilung normalisiert und damit für verschiedene Anlagezeiträume vergleichbar macht.

Weiterhin wäre eine Worst- und Best-Case Betrachtung sowie eine Perzentil-Betrachtung wünschenswert.

Diesen Beitrag teilen


Link zum Beitrag
s1lv3r

@stagflation Kannst du nochmal schreiben, was man mit dem heruntergeladenen dSFMT-Sourcecode (unter Linux) machen muss, damit gcc den findet?

Diesen Beitrag teilen


Link zum Beitrag
stagflation
· bearbeitet von stagflation
vor einer Stunde von s1lv3r:

@stagflation Kannst du nochmal schreiben, was man mit dem heruntergeladenen dSFMT-Sourcecode (unter Linux) machen muss, damit gcc den findet?

 

Bei meiner Linux-Distribution gibt es dsfmt als Paket. Nach der Installation gibt es auf meinem System im Wesentlichen folgende Dateien:

/usr/include/dSFMT.c
/usr/include/dSFMT-common.h
/usr/include/dSFMT-params.h
/usr/include/dSFMT-params11213.h
/usr/include/dSFMT-params1279.h
/usr/include/dSFMT-params132049.h
/usr/include/dSFMT-params19937.h
/usr/include/dSFMT-params216091.h
/usr/include/dSFMT-params2203.h
/usr/include/dSFMT-params4253.h
/usr/include/dSFMT-params44497.h
/usr/include/dSFMT-params521.h
/usr/include/dSFMT-params86243.h
/usr/include/dSFMT.h
/usr/lib64/libdSFMT.so

Dann funktioniert der GCC-Befehl wie angegeben.

 

Man kann dSFMT aber auch ohne Installation nutzen.

  1. Man erstellt ein neues Verzeichnis
     
  2. Man kopiert mcs.c (also den Monte-Carlo-Simulator) in das Verzeichnis
     
  3. Man entpackt den dsfmt Quellcode und kopiert von dort folgende Dateien in das Verzeichnis:
    dSFMT.c dSFMT.h  dSFMT-*.h
  4. In dem Verzeichnis sollten jetzt folgende Dateien stehen:
    dSFMT-common.h
    dSFMT-params.h
    dSFMT-params11213.h
    dSFMT-params1279.h
    dSFMT-params132049.h
    dSFMT-params19937.h
    dSFMT-params216091.h
    dSFMT-params2203.h
    dSFMT-params4253.h
    dSFMT-params44497.h
    dSFMT-params521.h
    dSFMT-params86243.h
    dSFMT.c
    dSFMT.h
    mcs.c
  5. Jetzt kann man in das Verzeichnis gehen und mcs.c folgendermaßen kompilieren:
    gcc -O2 -D DSFMT_MEXP=19937 -I . -lm -o mcs mcs.c dSFMT.c

Diesen Beitrag teilen


Link zum Beitrag
Maciej
vor 7 Stunden von stagflation:

Zunächst braucht man einen Zufallszahlen-Generator, der gleichmäßig verteilte Zufallszahlen im Bereich von 0 bis 1 berechnet. Die meisten Betriebssysteme bzw. Programmierumgebungen liefern einen solchen Generator mit (z.B. drand48). Für Monte-Carlo-Simulationen sind sie i.d.R. unbrauchbar.

 

Ich verwende deshalb den Zufallszahlengenerator dSFMT. Der ist zwar nicht perfekt - aber hinreichend gut.

Könntest du diesen Punkt etwas näher erklären? Welche Nachteile haben die Standard-PRNGs und weshalb sind sie für diese Art der Simulation unbrauchbar? Die SFMT-Variante scheint auch nur gerade so gut genug zu sein. Vielleicht könntest du hier auch kurz erklären, worin sich die Ergebnisse zwischen verschiedenen PRNGs genau unterscheiden.

Diesen Beitrag teilen


Link zum Beitrag
etherial
vor einer Stunde von Maciej:

Könntest du diesen Punkt etwas näher erklären? Welche Nachteile haben die Standard-PRNGs und weshalb sind sie für diese Art der Simulation unbrauchbar? Die SFMT-Variante scheint auch nur gerade so gut genug zu sein. Vielleicht könntest du hier auch kurz erklären, worin sich die Ergebnisse zwischen verschiedenen PRNGs genau unterscheiden.

Würde mich auch interessieren (reine Neugier) ... Ich wäre naiv davon ausgegangen, dass gerade bei Tests mit großen Stichproben überhaupt kein Unterschied besteht ...

Diesen Beitrag teilen


Link zum Beitrag
chirlu
vor 3 Stunden von Maciej:

Welche Nachteile haben die Standard-PRNGs?

 

Bei vielen einfachen (Pseudo-)Zufallszahlengeneratoren gibt es problematische Abhängigkeiten zwischen aufeinanderfolgenden Ausgabewerten. Zum Beispiel kann es sein, daß jeweils drei Werte, als Koordinaten eines Punkts betrachtet, sich nicht zufällig über den Raum verteilen, sondern immer auf einer bestimmten Ebene liegen.

 

Ob und wie sich solche Unzulänglichkeiten in dieser konkreten Simulation auswirken, weiß ich nicht.

Diesen Beitrag teilen


Link zum Beitrag
stagflation
· bearbeitet von stagflation
Am 3.9.2022 um 04:27 von chirlu:

Bei vielen einfachen (Pseudo-)Zufallszahlengeneratoren gibt es problematische Abhängigkeiten zwischen aufeinanderfolgenden Ausgabewerten. Zum Beispiel kann es sein, daß jeweils drei Werte, als Koordinaten eines Punkts betrachtet, sich nicht zufällig über den Raum verteilen, sondern immer auf einer bestimmten Ebene liegen.

 

Ja, genau.

 

In dem Buch "Don L. McLeish: Monte Carlo Simulations and Finance" (Vorabversion hier) gibt es ein Kapitel, in dem Zufallszahlengeneratoren für MC-Simulationen besprochen werden (S. 98ff). Dort gibt es auch ein paar Beispiele für problematische Zufallszahlengeneratoren. Auf Seite 114 findet man folgende Grafik:

 

image.png.1dabfc2937edba9f6ab5e469c776d094.png

 

Mit Hilfe des uralten Zufallszahlengenerators "RANDU" wurden zufällige Punkte in einem 3D-Würfel ausgewählt, vermutlich ungefähr so:

for ( i = 1; i < 100000; i++ ) {
    X = random();
    Y = random();
    Z = random();
    setze_punkt_bei( X, Y, Z );                        
}

Man würde erwarten, dass die Punkte zufällig im Raum verteilt sind. In der obigen Grafik sieht man aber mit bloßem Auge, dass sie es nicht sind. Wenn man den 3D-Würfel mit diesem Verfahren abtastet, erreicht man einige Punkte sehr häufig, andere Regionen dafür überhaupt nicht. 

Diesen Beitrag teilen


Link zum Beitrag
Maciej
vor 14 Stunden von stagflation:

In dem Buch "Don L. McLeish: Monte Carlo Simulations and Finance" (Vorabversion hier) gibt es ein Kapitel, in dem Zufallszahlengeneratoren für MC-Simulationen besprochen werden (S. 98ff). Dort gibt es auch ein paar Beispiele für problematische Zufallszahlengeneratoren. Auf Seite 104 findet man folgende Grafik:

Sehr interessant, danke. Hätte nicht gedacht, dass es da so deutliche Effekte geben kann.

Diesen Beitrag teilen


Link zum Beitrag
stagflation
· bearbeitet von stagflation
Am 3.9.2022 um 10:35 von Jennerwein:

@stagflation,

was ist mit  https://retirementplans.vanguard.com/VGApp/pe/pubeducation/calculators/RetirementNestEggCalc.jsf 

oder https://ficalc.app/

 

verwenden die auch ungeeignete Zufallsgeneratoren?

 

Ich habe mir die beiden Seiten angeschaut. Sie sind hübsch gemacht. Es ist schön, dass sie nicht nur den Erwartungswert ausrechnen, sondern auch eine Häufigkeitsverteilung ausgeben. Die Vanguard Seite verwendet offenbar eine Monte-Carlo-Simulation. Wie die ficalc-Seite rechnet, weiß ich nicht.

 

Beide Seiten rechnen nicht mit log-normalverteilten Zufallszahlen, sondern mit historischen Daten. Das ist die „Bootstrapping“ Methode, die ja auch im Blog-Artikel von Gerd Kommer erklärt wird. Sie hat ein paar Vorteile - aber auch ein paar Nachteile.

 

Ob die Seiten geeignete Zufallszahlengeneratoren verwenden? Das kann ich nicht sagen, da ich den Source-Code nicht kenne. 

 

Was mir auffällt: bei der ficalc-Seite erscheinen die Ergebnisse recht schnell. Entweder nutzen sie keine MC-Simulation, oder sie verwenden vorausberechnete Werte - oder sie arbeiten mit nur wenigen Iterationen. Letzteres würde bedeuten, dass die Häufigkeitsverteilungs-Kurve vermutlich einigermaßen stimmt. Aber ich kann mir nicht vorstellen, dass eine MC-Simulation die ausgegebenen Wahrscheinlichkeiten (z.B. "96.82 % Success") in der kurzen Zeit mit dieser Genauigkeit berechnen kann.

Diesen Beitrag teilen


Link zum Beitrag

Erstelle ein Benutzerkonto oder melde dich an, um zu kommentieren

Du musst ein Benutzerkonto haben, um einen Kommentar verfassen zu können

Benutzerkonto erstellen

Neues Benutzerkonto für unsere Community erstellen. Es ist einfach!

Neues Benutzerkonto erstellen

Anmelden

Du hast bereits ein Benutzerkonto? Melde dich hier an.

Jetzt anmelden

×
×
  • Neu erstellen...