Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
b7af2f1d9a
468 changed files with 262141 additions and 0 deletions
368
OR_HTML_04/Notebooks_04/dynamische-programmierung.ipynb
Normal file
368
OR_HTML_04/Notebooks_04/dynamische-programmierung.ipynb
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Kapitel 13: Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution\n",
|
||||
"\n",
|
||||
"Begleitnotebook zu *Optimierte Entscheidungsfindung mit Python*. Die Codezellen sind identisch mit den im Buch abgedruckten Programmen.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Einmalig ausfuehren: installiert alle im Buch verwendeten Pakete.\n",
|
||||
"# Lokal in einer virtuellen Umgebung genauso gueltig wie in Google Colab.\n",
|
||||
"%pip install --quiet ortools highspy cvxpy scipy numpy pandas polars \\\n",
|
||||
" scikit-learn matplotlib plotly pyomo linopy pymoo pydantic openpyxl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Die Bellman-Gleichung\n",
|
||||
"\n",
|
||||
"`Bellman_Minimalbeispiel.py`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#!/usr/bin/env python3\n",
|
||||
"\n",
|
||||
"# Bellman_Minimalbeispiel.py\n",
|
||||
"\"\"\"\n",
|
||||
"Kapitel Dynamische Programmierung: Die Handrechnung zur Rueckwaertsinduktion als Code.\n",
|
||||
"Zeigt die Wertfunktionstabelle und die optimale Politik Schritt fuer Schritt.\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"GESAMT = 3 # zu verkaufende Einheiten\n",
|
||||
"PERIODEN = 2 # Anzahl Verkaufsperioden\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def kosten(menge: int) -> float:\n",
|
||||
" \"\"\"Ueberproportionale Marktauswirkung: doppelte Menge kostet vierfach.\"\"\"\n",
|
||||
" return float(menge ** 2)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def loese_rueckwaerts():\n",
|
||||
" # V[t, x] = minimale Restkosten, wenn zu Beginn von Periode t noch x Stueck offen sind\n",
|
||||
" V = np.full((PERIODEN + 1, GESAMT + 1), np.inf)\n",
|
||||
" politik = np.zeros((PERIODEN, GESAMT + 1), dtype=int)\n",
|
||||
"\n",
|
||||
" # Endbedingung: nach der letzten Periode darf nichts mehr offen sein\n",
|
||||
" V[PERIODEN, 0] = 0.0\n",
|
||||
"\n",
|
||||
" print(\"=\" * 70)\n",
|
||||
" print(\" RUECKWAERTSINDUKTION SCHRITT FUER SCHRITT\")\n",
|
||||
" print(\"=\" * 70)\n",
|
||||
"\n",
|
||||
" for t in range(PERIODEN - 1, -1, -1):\n",
|
||||
" letzte_periode = (t == PERIODEN - 1)\n",
|
||||
" print(f\"\\nStufe t = {t}\" + (\" (letzte Periode: alles muss weg)\" if letzte_periode\n",
|
||||
" else \" (freie Wahl der Menge)\"))\n",
|
||||
" print(f\" {'Zustand x':>10} | {'beste Aktion':>12} | {'Sofortkosten':>13} | \"\n",
|
||||
" f\"{'V[t+1]':>9} | {'V[t]':>8}\")\n",
|
||||
" print(\" \" + \"-\" * 62)\n",
|
||||
"\n",
|
||||
" for x in range(GESAMT + 1):\n",
|
||||
" aktionen = [x] if letzte_periode else range(x + 1)\n",
|
||||
" bester_wert, beste_aktion, beste_teile = np.inf, 0, (0.0, 0.0)\n",
|
||||
"\n",
|
||||
" for n in aktionen:\n",
|
||||
" rest = x - n\n",
|
||||
" sofort = kosten(n)\n",
|
||||
" zukunft = V[t + 1, rest]\n",
|
||||
" gesamt = sofort + zukunft\n",
|
||||
" if gesamt < bester_wert:\n",
|
||||
" bester_wert, beste_aktion = gesamt, n\n",
|
||||
" beste_teile = (sofort, zukunft)\n",
|
||||
"\n",
|
||||
" V[t, x] = bester_wert\n",
|
||||
" politik[t, x] = beste_aktion\n",
|
||||
" print(f\" {x:>10} | {beste_aktion:>12} | {beste_teile[0]:>13.1f} | \"\n",
|
||||
" f\"{beste_teile[1]:>9.1f} | {bester_wert:>8.1f}\")\n",
|
||||
"\n",
|
||||
" return V, politik\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" V, politik = loese_rueckwaerts()\n",
|
||||
"\n",
|
||||
" # --- Vorwaertspfad: der optimalen Politik folgen ---------------------\n",
|
||||
" print(\"\\n\" + \"=\" * 70)\n",
|
||||
" print(\" OPTIMALER PFAD (Vorwaertssimulation)\")\n",
|
||||
" print(\"=\" * 70)\n",
|
||||
" bestand = GESAMT\n",
|
||||
" gesamtkosten = 0.0\n",
|
||||
" for t in range(PERIODEN):\n",
|
||||
" aktion = politik[t, bestand]\n",
|
||||
" gesamtkosten += kosten(aktion)\n",
|
||||
" print(f\" Periode {t}: Bestand {bestand} -> verkaufe {aktion} \"\n",
|
||||
" f\"(Kosten {kosten(aktion):.1f}) -> Rest {bestand - aktion}\")\n",
|
||||
" bestand -= aktion\n",
|
||||
"\n",
|
||||
" print(f\"\\n Gesamtkosten: {gesamtkosten:.1f} (V[0, {GESAMT}] = {V[0, GESAMT]:.1f})\")\n",
|
||||
" assert abs(gesamtkosten - V[0, GESAMT]) < 1e-9, \"Pfadkosten != Wertfunktion!\"\n",
|
||||
"\n",
|
||||
" # --- Vergleich mit naiven Strategien ---------------------------------\n",
|
||||
" alles_sofort = kosten(GESAMT)\n",
|
||||
" print(f\"\\n Zum Vergleich - alles in Periode 0 verkaufen: {alles_sofort:.1f}\")\n",
|
||||
" print(f\" Ersparnis durch Stueckelung: {alles_sofort - gesamtkosten:.1f} \"\n",
|
||||
" f\"({(1 - gesamtkosten/alles_sofort)*100:.0f} %)\")\n",
|
||||
" print(\"=\" * 70)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Das Almgren-Chriss-Problem: optimale Orderausführung\n",
|
||||
"\n",
|
||||
"`Mehrperiodige_Order_Execution.py`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#!/usr/bin/env python3\n",
|
||||
"\n",
|
||||
"# Mehrperiodige_Order_Execution.py\n",
|
||||
"\"\"\"\n",
|
||||
"Kapitel Dynamische Programmierung: Dynamische Programmierung fuer optimale Orderausfuehrung\n",
|
||||
"(Almgren-Chriss-Rahmen, geloest per Rueckwaertsinduktion).\n",
|
||||
"\n",
|
||||
"Eigenschaften:\n",
|
||||
" * Risikoterm sauber hergeleitet ueber den Aktienkurs P0 (Einheiten: EUR),\n",
|
||||
" ohne undokumentierte Skalierungsfaktoren\n",
|
||||
" * Vergleich mit der analytischen Almgren-Chriss-Loesung\n",
|
||||
" * Vergleich mit naiven Strategien (alles sofort / gleichmaessig)\n",
|
||||
" * Sensitivitaet gegenueber der Risikoaversion\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"import numpy as np\n",
|
||||
"import pandas as pd\n",
|
||||
"import matplotlib\n",
|
||||
"matplotlib.use(\"Agg\")\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"\n",
|
||||
"OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"output\")\n",
|
||||
"os.makedirs(OUTPUT_DIR, exist_ok=True)\n",
|
||||
"\n",
|
||||
"# --- Parameter -------------------------------------------------------------\n",
|
||||
"GESAMTBESTAND = 100_000 # X_0, zu verkaufende Aktien\n",
|
||||
"PERIODEN = 5 # T Handelsperioden\n",
|
||||
"KURS = 50.0 # P_0 in EUR, zur Skalierung des Risikoterms\n",
|
||||
"ETA = 2.5e-6 # EUR je Stueck^2 (Slippage-Koeffizient)\n",
|
||||
"VOLA_JAHR = 0.30 # 30 % p.a.\n",
|
||||
"HANDELSSTUNDEN_JAHR = 252 * 6.5\n",
|
||||
"RISIKOAVERSION = 1e-5 # 1/EUR; kalibriert, siehe Kommentar unten\n",
|
||||
"SCHRITTWEITE = 1000 # Diskretisierung des Zustandsraums\n",
|
||||
"\n",
|
||||
"# Kalibrierungshinweis: Die dimensionslose Kennzahl des Modells ist\n",
|
||||
"# kappa_tilde^2 = lambda * sigma_periode^2 * P0^2 / eta\n",
|
||||
"# Sie entscheidet ueber den Charakter der Loesung:\n",
|
||||
"# << 1 -> praktisch gleichmaessige Aufteilung (Risiko spielt keine Rolle)\n",
|
||||
"# ~ 1 -> ausgewogener Kompromiss <- hier: 0.55\n",
|
||||
"# >> 1 -> fast alles sofort verkaufen\n",
|
||||
"# Genau diese Interpretierbarkeit geht mit einem undokumentierten\n",
|
||||
"# Skalierungsfaktor verloren.\n",
|
||||
"\n",
|
||||
"VOLA_PERIODE = VOLA_JAHR / np.sqrt(HANDELSSTUNDEN_JAHR)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def periodenkosten(verkauf: float, restbestand: float) -> float:\n",
|
||||
" \"\"\"\n",
|
||||
" Sofortkosten einer Periode in EUR:\n",
|
||||
" (1) Marktauswirkung: eta * n^2\n",
|
||||
" (2) Risiko des Restbestands: lambda/2 * sigma^2 * P0^2 * X^2\n",
|
||||
" -> P0^2 macht aus \"Stueck^2\" einen EUR^2-Wert; lambda hat damit\n",
|
||||
" die Einheit 1/EUR und ist interpretierbar.\n",
|
||||
" \"\"\"\n",
|
||||
" marktauswirkung = ETA * verkauf ** 2\n",
|
||||
" wertvarianz = (VOLA_PERIODE ** 2) * (KURS ** 2) * (restbestand ** 2)\n",
|
||||
" risiko = 0.5 * RISIKOAVERSION * wertvarianz\n",
|
||||
" return marktauswirkung + risiko\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def loese_dp():\n",
|
||||
" \"\"\"Rueckwaertsinduktion ueber den diskretisierten Zustandsraum.\"\"\"\n",
|
||||
" zustaende = np.arange(0, GESAMTBESTAND + SCHRITTWEITE, SCHRITTWEITE)\n",
|
||||
" anzahl = len(zustaende)\n",
|
||||
"\n",
|
||||
" V = np.full((PERIODEN + 1, anzahl), np.inf)\n",
|
||||
" politik = np.zeros((PERIODEN, anzahl), dtype=int)\n",
|
||||
" V[PERIODEN, 0] = 0.0 # am Ende muss alles verkauft sein\n",
|
||||
"\n",
|
||||
" for t in range(PERIODEN - 1, -1, -1):\n",
|
||||
" for idx, bestand in enumerate(zustaende):\n",
|
||||
" if t == PERIODEN - 1:\n",
|
||||
" moegliche = [bestand] # letzte Periode: Rest muss weg\n",
|
||||
" else:\n",
|
||||
" moegliche = zustaende[zustaende <= bestand]\n",
|
||||
"\n",
|
||||
" bester_wert, beste_aktion = np.inf, 0\n",
|
||||
" for verkauf in moegliche:\n",
|
||||
" rest = bestand - verkauf\n",
|
||||
" rest_idx = int(round(rest / SCHRITTWEITE))\n",
|
||||
" gesamt = periodenkosten(verkauf, rest) + V[t + 1, rest_idx]\n",
|
||||
" if gesamt < bester_wert:\n",
|
||||
" bester_wert, beste_aktion = gesamt, int(verkauf)\n",
|
||||
"\n",
|
||||
" V[t, idx] = bester_wert\n",
|
||||
" politik[t, idx] = beste_aktion\n",
|
||||
"\n",
|
||||
" return zustaende, V, politik\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def analytische_loesung():\n",
|
||||
" \"\"\"\n",
|
||||
" Geschlossene Almgren-Chriss-Loesung fuer den kontinuierlichen Fall.\n",
|
||||
" Der optimale Pfad ist X_t = X_0 * sinh(kappa*(T-t)) / sinh(kappa*T)\n",
|
||||
" mit kappa = arccosh(tilde_kappa^2/2 + 1), tilde_kappa^2 = lambda*sigma^2*P0^2/eta.\n",
|
||||
" Dient hier als unabhaengige Kontrolle des DP-Ergebnisses.\n",
|
||||
" \"\"\"\n",
|
||||
" kappa_tilde_quadrat = (RISIKOAVERSION * (VOLA_PERIODE ** 2) * (KURS ** 2)) / ETA\n",
|
||||
" kappa = np.arccosh(kappa_tilde_quadrat / 2.0 + 1.0)\n",
|
||||
" if kappa < 1e-12: # Grenzfall: risikoneutral -> linear\n",
|
||||
" return np.linspace(GESAMTBESTAND, 0, PERIODEN + 1)\n",
|
||||
" t = np.arange(PERIODEN + 1)\n",
|
||||
" return GESAMTBESTAND * np.sinh(kappa * (PERIODEN - t)) / np.sinh(kappa * PERIODEN)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def bewerte_pfad(bestaende):\n",
|
||||
" \"\"\"Gesamtkosten eines beliebigen Bestandspfades.\"\"\"\n",
|
||||
" summe = 0.0\n",
|
||||
" for t in range(len(bestaende) - 1):\n",
|
||||
" verkauf = bestaende[t] - bestaende[t + 1]\n",
|
||||
" summe += periodenkosten(verkauf, bestaende[t + 1])\n",
|
||||
" return summe\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" zustaende, V, politik = loese_dp()\n",
|
||||
"\n",
|
||||
" # --- Vorwaertspfad der optimalen Politik -----------------------------\n",
|
||||
" bestand = GESAMTBESTAND\n",
|
||||
" verlauf = [bestand]\n",
|
||||
" verkaeufe = []\n",
|
||||
" for t in range(PERIODEN):\n",
|
||||
" idx = int(round(bestand / SCHRITTWEITE))\n",
|
||||
" verkauf = politik[t, idx]\n",
|
||||
" verkaeufe.append(verkauf)\n",
|
||||
" bestand -= verkauf\n",
|
||||
" verlauf.append(bestand)\n",
|
||||
"\n",
|
||||
" print(\"=\" * 84)\n",
|
||||
" print(\" OPTIMALE MEHRPERIODIGE ORDER-EXECUTION (BELLMAN DP)\")\n",
|
||||
" print(\"=\" * 84)\n",
|
||||
" print(f\"Gesamtvolumen: {GESAMTBESTAND:,} Stueck zu {KURS:.2f} EUR \"\n",
|
||||
" f\"= {GESAMTBESTAND*KURS:,.0f} EUR Positionswert\")\n",
|
||||
" print(f\"Zeithorizont: {PERIODEN} Handelsperioden\")\n",
|
||||
" print(f\"Volatilitaet: {VOLA_JAHR*100:.0f} % p.a. \"\n",
|
||||
" f\"= {VOLA_PERIODE*100:.3f} % je Periode\")\n",
|
||||
" print(f\"Slippage eta: {ETA:.2e} EUR/Stueck^2\")\n",
|
||||
" print(f\"Risikoaversion: {RISIKOAVERSION:.2e} 1/EUR\")\n",
|
||||
" print(f\"Erwartete Gesamtreibung: {V[0, -1]:,.2f} EUR \"\n",
|
||||
" f\"({V[0, -1]/(GESAMTBESTAND*KURS)*10000:.1f} Basispunkte)\\n\")\n",
|
||||
"\n",
|
||||
" plan = pd.DataFrame([{\n",
|
||||
" \"Periode\": f\"t = {t} -> {t+1}\",\n",
|
||||
" \"Startbestand\": f\"{verlauf[t]:,}\",\n",
|
||||
" \"Verkauf n_t\": f\"{verkaeufe[t]:,}\",\n",
|
||||
" \"Restbestand\": f\"{verlauf[t+1]:,}\",\n",
|
||||
" \"Anteil\": f\"{verkaeufe[t]/GESAMTBESTAND*100:5.1f} %\",\n",
|
||||
" \"Kosten (EUR)\": f\"{periodenkosten(verkaeufe[t], verlauf[t+1]):,.0f}\",\n",
|
||||
" } for t in range(PERIODEN)])\n",
|
||||
" print(plan.to_string(index=False))\n",
|
||||
"\n",
|
||||
" # --- Vergleich mit Alternativen und der analytischen Loesung ---------\n",
|
||||
" sofort = [GESAMTBESTAND] + [0] * PERIODEN\n",
|
||||
" gleichmaessig = [GESAMTBESTAND * (1 - t / PERIODEN) for t in range(PERIODEN + 1)]\n",
|
||||
" analytisch = analytische_loesung()\n",
|
||||
"\n",
|
||||
" print(\"\\n\" + \"-\" * 84)\n",
|
||||
" print(f\"{'Strategie':<34} {'Kosten (EUR)':>15} {'Basispunkte':>13} \"\n",
|
||||
" f\"{'ggue. Optimum':>16}\")\n",
|
||||
" print(\"-\" * 84)\n",
|
||||
" optimum = V[0, -1]\n",
|
||||
" for name, pfad in [(\"DP-Optimum\", verlauf),\n",
|
||||
" (\"Analytisch (Almgren-Chriss)\", list(analytisch)),\n",
|
||||
" (\"Gleichmaessig (TWAP)\", gleichmaessig),\n",
|
||||
" (\"Alles sofort\", sofort)]:\n",
|
||||
" kosten = bewerte_pfad(pfad)\n",
|
||||
" bp = kosten / (GESAMTBESTAND * KURS) * 10000\n",
|
||||
" print(f\"{name:<34} {kosten:>15,.0f} {bp:>12.1f} \"\n",
|
||||
" f\"{kosten - optimum:>+15,.0f}\")\n",
|
||||
"\n",
|
||||
" print(\"-\" * 84)\n",
|
||||
" abweichung = abs(bewerte_pfad(list(analytisch)) - optimum) / optimum\n",
|
||||
" print(f\"Abweichung DP zur analytischen Loesung: {abweichung*100:.3f} % \"\n",
|
||||
" f\"(Diskretisierung: {SCHRITTWEITE} Stueck)\")\n",
|
||||
"\n",
|
||||
" # --- Sensitivitaet gegenueber der Risikoaversion ---------------------\n",
|
||||
" print(\"\\n--- Wie wirkt die Risikoaversion? ---\")\n",
|
||||
" print(f\"{'lambda':>10} | {'Verkauf in Periode 0':>22} | {'Charakter':<28}\")\n",
|
||||
" print(\"-\" * 70)\n",
|
||||
" for lam in [1e-7, 1e-6, 1e-5, 1e-4, 1e-3]:\n",
|
||||
" globals()[\"RISIKOAVERSION\"] = lam\n",
|
||||
" _, V_l, pol_l = loese_dp()\n",
|
||||
" erste = pol_l[0, -1]\n",
|
||||
" anteil = erste / GESAMTBESTAND * 100\n",
|
||||
" charakter = (\"nahezu gleichmaessig\" if anteil < 25 else\n",
|
||||
" \"front-loaded\" if anteil < 60 else \"fast alles sofort\")\n",
|
||||
" print(f\"{lam:>10.0e} | {erste:>13,} ({anteil:5.1f} %) | {charakter:<28}\")\n",
|
||||
" globals()[\"RISIKOAVERSION\"] = 1e-5 # zuruecksetzen\n",
|
||||
"\n",
|
||||
" # --- Diagramm ---------------------------------------------------------\n",
|
||||
" plt.figure(figsize=(10, 5.5))\n",
|
||||
" plt.plot(range(PERIODEN + 1), verlauf, \"o-\", lw=2.5, label=\"DP-Optimum\")\n",
|
||||
" plt.plot(range(PERIODEN + 1), analytisch, \"s--\", lw=1.8, alpha=0.8,\n",
|
||||
" label=\"Analytisch (Almgren-Chriss)\")\n",
|
||||
" plt.plot(range(PERIODEN + 1), gleichmaessig, \"^:\", lw=1.8, alpha=0.8,\n",
|
||||
" label=\"Gleichmaessig (TWAP)\")\n",
|
||||
" plt.bar(range(PERIODEN), verkaeufe, alpha=0.25, color=\"orange\", width=0.45,\n",
|
||||
" label=\"Verkaufstranche $n_t$\")\n",
|
||||
" plt.title(\"Optimaler Liquidationspfad ueber diskrete Perioden\", fontsize=12)\n",
|
||||
" plt.xlabel(\"Handelsperiode $t$\")\n",
|
||||
" plt.ylabel(\"Verbleibender Bestand $X_t$\")\n",
|
||||
" plt.xticks(range(PERIODEN + 1))\n",
|
||||
" plt.grid(True, linestyle=\":\", alpha=0.6)\n",
|
||||
" plt.legend()\n",
|
||||
" plt.tight_layout()\n",
|
||||
" ziel = os.path.join(OUTPUT_DIR, \"optimal_execution_dp.png\")\n",
|
||||
" plt.savefig(ziel, dpi=150)\n",
|
||||
" print(f\"\\nDiagramm gespeichert unter '{ziel}'\")\n",
|
||||
" print(\"=\" * 84)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.11"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Loading…
Reference in a new issue