{"spec_id":"drawdown-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ndrawdown-basic: Drawdown Chart\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-23\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.gridspec import GridSpec\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\"\n\n# Semantic palette: loss/drawdown → red, recovery/new high → green, emphasis → purple\nDRAWDOWN_COLOR = \"#AE3030\"  # anyplot position 3 — loss (semantic)\nRECOVERY_COLOR = \"#009E73\"  # anyplot position 1 — gain/new high (semantic)\nMAX_DD_COLOR = \"#C475FD\"  # anyplot position 2 — max drawdown emphasis\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.13,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data: Synthetic energy commodity index with demand-shock crash and recovery\nnp.random.seed(99)\nn_days = 750\ndates = pd.date_range(\"2020-01-01\", periods=n_days, freq=\"B\")\n\ndaily_returns = np.random.normal(0.0003, 0.018, n_days)\ndaily_returns[20:80] -= 0.008  # demand shock crash\ndaily_returns[80:130] += 0.004  # partial bounce\ndaily_returns[130:200] -= 0.004  # extended weakness\ndaily_returns[200:330] += 0.009  # supply-cut driven recovery\ndaily_returns[330:420] -= 0.005  # demand uncertainty\ndaily_returns[420:600] += 0.005  # sustained recovery\n\nprice = 100 * np.cumprod(1 + daily_returns)\n\n# Calculate drawdown\nrunning_max = np.maximum.accumulate(price)\ndrawdown = (price - running_max) / running_max * 100\n\ndf = pd.DataFrame({\"Date\": dates, \"Price\": price, \"Drawdown\": drawdown})\n\n# Max drawdown statistics\nmax_dd_idx = df[\"Drawdown\"].idxmin()\nmax_dd_value = df[\"Drawdown\"].min()\nmax_dd_date = df.loc[max_dd_idx, \"Date\"]\n\n# Max drawdown duration: days from most recent peak to the trough\npeak_idx = df.loc[:max_dd_idx, \"Price\"].idxmax()\npeak_date = df.loc[peak_idx, \"Date\"]\nmax_dd_duration = (max_dd_date - peak_date).days\n\n# Recovery time from max drawdown trough to first new high\ndf_after_max = df.loc[max_dd_idx + 1 :]\nfirst_new_high = df_after_max[df_after_max[\"Drawdown\"] >= 0]\nrecovery_days = None\nif len(first_new_high) > 0:\n    recovery_date = df.loc[first_new_high.index[0], \"Date\"]\n    recovery_days = (recovery_date - max_dd_date).days\n\n# Recovery markers: one per distinct drawdown period (not per crossing)\n# Find end of each contiguous negative-drawdown block, then first date where DD >= 0\nin_dd = df[\"Drawdown\"] < 0\nperiod_end_mask = in_dd & (~in_dd.shift(-1).fillna(False))\nperiod_end_indices = df.index[period_end_mask].tolist()\ndistinct_recovery_dates = []\nfor end_idx in period_end_indices:\n    after = df.loc[end_idx + 1 :]\n    recovery_after = after[after[\"Drawdown\"] >= 0]\n    if len(recovery_after) > 0:\n        distinct_recovery_dates.append(df.loc[recovery_after.index[0], \"Date\"])\n\n# Layout: main drawdown chart (wide) + seaborn KDE marginal strip (wider than before)\nfig = plt.figure(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\ngs = GridSpec(1, 2, figure=fig, width_ratios=[4, 1], wspace=0.03)\nax = fig.add_subplot(gs[0])\nax_kde = fig.add_subplot(gs[1], sharey=ax)\n\n# Filled drawdown area\nax.fill_between(df[\"Date\"], df[\"Drawdown\"], 0, color=DRAWDOWN_COLOR, alpha=0.30)\nsns.lineplot(x=\"Date\", y=\"Drawdown\", data=df, ax=ax, color=DRAWDOWN_COLOR, linewidth=1.8, label=\"Drawdown\")\n\n# Zero baseline\nax.axhline(y=0, color=INK_SOFT, linewidth=1.0)\n\n# Max drawdown marker\nax.scatter(\n    [max_dd_date], [max_dd_value], color=MAX_DD_COLOR, s=120, zorder=5, marker=\"v\", label=f\"Max DD: {max_dd_value:.1f}%\"\n)\n\n# Recovery (new high) markers — one per distinct drawdown period to avoid clutter\nif len(distinct_recovery_dates) > 0:\n    ax.scatter(\n        distinct_recovery_dates,\n        [0.0] * len(distinct_recovery_dates),\n        color=RECOVERY_COLOR,\n        s=80,\n        zorder=5,\n        marker=\"^\",\n        label=\"New High\",\n    )\n\n# Dual annotation: recovery time + max drawdown duration (both required by spec)\nif recovery_days is not None:\n    ax.annotate(\n        f\"Recovery: {recovery_days}d\\nDuration: {max_dd_duration}d\",\n        xy=(max_dd_date, max_dd_value),\n        xytext=(28, 32),\n        textcoords=\"offset points\",\n        fontsize=9.5,\n        color=INK_SOFT,\n        arrowprops={\"arrowstyle\": \"->\", \"color\": INK_SOFT, \"lw\": 0.8},\n    )\n\n# Style main axes\nax.set_xlabel(\"Trading Date\", fontsize=10, color=INK)\nax.set_ylabel(\"Drawdown (%)\", fontsize=10, color=INK)\nax.set_title(\"drawdown-basic · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=8)\nax.set_ylim(min(df[\"Drawdown\"]) * 1.15, max(df[\"Drawdown\"]) + 3)\n\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor s in (\"left\", \"bottom\"):\n    ax.spines[s].set_color(INK_SOFT)\n\nax.yaxis.grid(True, alpha=0.13, linewidth=0.8)\nax.xaxis.grid(True, alpha=0.08, linewidth=0.6)\nax.legend(loc=\"lower right\", fontsize=7)\n\n# Seaborn KDE marginal: distribution of drawdown depth values (seaborn-distinctive feature)\nsns.kdeplot(\n    y=df[\"Drawdown\"],\n    ax=ax_kde,\n    fill=True,\n    color=DRAWDOWN_COLOR,\n    alpha=0.40,\n    linewidth=1.2,\n    clip=(df[\"Drawdown\"].min() * 1.05, 0.5),\n)\nax_kde.axhline(y=0, color=INK_SOFT, linewidth=0.8, linestyle=\"--\", alpha=0.6)\nax_kde.axhline(y=max_dd_value, color=MAX_DD_COLOR, linewidth=0.8, linestyle=\"--\", alpha=0.7)\n\nax_kde.set_xlabel(\"Density\", fontsize=7, color=INK_SOFT)\nax_kde.set_ylabel(\"\")\nax_kde.set_title(\"Dist.\", fontsize=8, color=INK_SOFT)\nax_kde.tick_params(labelleft=False, labelsize=6)\nax_kde.tick_params(axis=\"x\", labelsize=6)\nax_kde.set_xlim(left=0)\nax_kde.spines[\"top\"].set_visible(False)\nax_kde.spines[\"right\"].set_visible(False)\nfor s in (\"left\", \"bottom\"):\n    ax_kde.spines[s].set_color(INK_SOFT)\nax_kde.yaxis.grid(True, alpha=0.10, linewidth=0.6)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}