{"spec_id":"drawdown-basic","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ndrawdown-basic: Drawdown Chart\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-23\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nfrom matplotlib.lines import Line2D\nfrom matplotlib.patches import Patch\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Semantic palette: red for drawdown (loss), green for recovery (gain)\nDRAWDOWN_COLOR = \"#AE3030\"  # Imprint palette position 3\nRECOVERY_COLOR = \"#009E73\"  # Imprint palette position 1\n\n# Data — 2 years of simulated daily portfolio values with multiple drawdown cycles\nnp.random.seed(42)\ndates = pd.date_range(\"2022-01-01\", periods=500, freq=\"B\")\nn_points = len(dates)\n\nprices = [10000]\ntrend = 0.0008\n\nfor i in range(1, n_points):\n    if 50 <= i < 85:\n        drift = -0.005\n    elif 85 <= i < 130:\n        drift = 0.004\n    elif 180 <= i < 230:\n        drift = -0.006\n    elif 230 <= i < 320:\n        drift = 0.003\n    elif 350 <= i < 380:\n        drift = -0.004\n    elif 380 <= i < 430:\n        drift = 0.003\n    elif 450 <= i < 470:\n        drift = -0.004\n    else:\n        drift = trend\n    noise = np.random.normal(0, 0.008)\n    prices.append(prices[-1] * (1 + drift + noise))\n\nportfolio_value = np.array(prices)\nrunning_max = np.maximum.accumulate(portfolio_value)\ndrawdown = (portfolio_value - running_max) / running_max * 100\n\n# Key stats\nmax_dd_idx = np.argmin(drawdown)\nmax_dd_value = drawdown[max_dd_idx]\nmax_dd_date = dates[max_dd_idx]\n\npeak_mask = portfolio_value[:max_dd_idx] == running_max[:max_dd_idx]\npeak_before_max_dd = np.where(peak_mask)[0][-1] if peak_mask.any() else 0\npeak_date = dates[peak_before_max_dd]\n\nrecovery_after_max = None\nfor i in range(max_dd_idx + 1, len(drawdown)):\n    if drawdown[i] >= 0:\n        recovery_after_max = dates[i]\n        break\nrecovery_days = (recovery_after_max - max_dd_date).days if recovery_after_max is not None else \"N/A\"\n\n# Recovery points: first bar where drawdown hits 0 after a meaningful drop\nrecovery_indices = []\nfor i in range(1, len(drawdown)):\n    if drawdown[i] >= 0 and drawdown[i - 1] < -0.5:\n        recovery_indices.append(i)\n\n# Plot\nfig, ax1 = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax1.set_facecolor(PAGE_BG)\n\n# Secondary y-axis: portfolio value rebased to 100\nax2 = ax1.twinx()\nrebased = portfolio_value / portfolio_value[0] * 100\nax2.plot(dates, rebased, color=INK_MUTED, linewidth=1.0, alpha=0.5, zorder=1)\nax2.set_ylabel(\"Portfolio Value (base 100)\", fontsize=10, color=INK_MUTED)\nax2.tick_params(axis=\"y\", labelsize=8, colors=INK_MUTED)\nax2.spines[\"top\"].set_visible(False)\nax2.spines[\"right\"].set_color(INK_MUTED)\nax2.spines[\"left\"].set_visible(False)\nax2.spines[\"bottom\"].set_visible(False)\n\n# Drawdown fill and line\nax1.fill_between(dates, drawdown, 0, where=(drawdown < 0), color=DRAWDOWN_COLOR, alpha=0.35, zorder=2)\nax1.plot(dates, drawdown, color=DRAWDOWN_COLOR, linewidth=1.5, zorder=3)\n\n# Zero baseline\nax1.axhline(y=0, color=INK_SOFT, linewidth=0.8, zorder=2)\n\n# Max drawdown marker and annotation\nax1.scatter([max_dd_date], [max_dd_value], color=DRAWDOWN_COLOR, s=100, zorder=6, edgecolors=PAGE_BG, linewidths=1.5)\nax1.annotate(\n    f\"Max DD: {max_dd_value:.1f}%\",\n    xy=(max_dd_date, max_dd_value),\n    xytext=(35, 18),\n    textcoords=\"offset points\",\n    fontsize=8,\n    fontweight=\"bold\",\n    color=DRAWDOWN_COLOR,\n    arrowprops={\"arrowstyle\": \"->\", \"color\": DRAWDOWN_COLOR, \"lw\": 1.2},\n    zorder=7,\n)\n\n# Recovery markers at actual drawdown values (new highs: drawdown == 0)\nfor idx in recovery_indices[:6]:\n    ax1.scatter(\n        [dates[idx]],\n        [drawdown[idx]],\n        color=RECOVERY_COLOR,\n        s=80,\n        marker=\"^\",\n        zorder=5,\n        edgecolors=PAGE_BG,\n        linewidths=1.0,\n    )\n\n# Statistics box\nstats_text = (\n    f\"Max Drawdown: {max_dd_value:.1f}%\\n\"\n    f\"Max DD Date: {max_dd_date.strftime('%Y-%m-%d')}\\n\"\n    f\"Peak to Trough: {(max_dd_date - peak_date).days} days\\n\"\n    f\"Recovery: {recovery_days} days\"\n)\nax1.text(\n    0.02,\n    0.04,\n    stats_text,\n    transform=ax1.transAxes,\n    fontsize=8,\n    verticalalignment=\"bottom\",\n    bbox={\"boxstyle\": \"round,pad=0.4\", \"facecolor\": ELEVATED_BG, \"alpha\": 0.9, \"edgecolor\": INK_SOFT},\n    color=INK_SOFT,\n)\n\n# Primary axis style\nax1.set_xlabel(\"Date\", fontsize=10, color=INK)\nax1.set_ylabel(\"Drawdown (%)\", fontsize=10, color=INK)\nax1.set_title(\"drawdown-basic · python · matplotlib · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax1.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax1.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK)\nax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f\"{x:.0f}%\"))\nax1.set_ylim(min(drawdown) * 1.15, 5)\nax1.spines[\"top\"].set_visible(False)\nax1.spines[\"right\"].set_visible(False)\nax1.spines[\"left\"].set_color(INK_SOFT)\nax1.spines[\"bottom\"].set_color(INK_SOFT)\n\n# Legend\ncustom_handles = [\n    Patch(facecolor=DRAWDOWN_COLOR, alpha=0.35),\n    Line2D([0], [0], marker=\"^\", color=\"w\", markerfacecolor=RECOVERY_COLOR, markersize=8),\n    Line2D([0], [0], color=INK_MUTED, linewidth=1.0, alpha=0.5),\n]\ncustom_labels = [\"Drawdown\", \"New High (Recovery)\", \"Portfolio Value\"]\nleg = ax1.legend(handles=custom_handles, labels=custom_labels, loc=\"upper right\", fontsize=8)\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n\nfig.subplots_adjust(left=0.08, right=0.85, top=0.92, bottom=0.12)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}