{"spec_id":"candlestick-volume","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ncandlestick-volume: Stock Candlestick Chart with Volume\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-16\n\"\"\"\n\nimport os\nimport sys\nfrom pathlib import Path\n\n\n# Remove current directory from sys.path before importing to avoid local matplotlib.py conflict\noriginal_path = sys.path.copy()\nsys.path = [p for p in sys.path if p not in (\"\", \".\", str(Path(__file__).parent))]\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.patches import Patch\n\n\n# Restore sys.path for potential relative imports later\nsys.path = original_path\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\"\nBRAND = \"#009E73\"  # imprint green — bullish\nSECONDARY = \"#AE3030\"  # imprint red — bearish\n\n# Apply theme-aware seaborn styling\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.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Generate realistic stock data for 60 trading days\nnp.random.seed(42)\nn_days = 60\n\ndates = pd.date_range(\"2024-01-02\", periods=n_days, freq=\"B\")\nbase_price = 150.0\n\n# Generate price series with trends and volatility\nreturns = np.random.normal(0.001, 0.02, n_days)\nprices = base_price * np.cumprod(1 + returns)\n\n# Generate OHLC from the price series\nopens = np.zeros(n_days)\nhighs = np.zeros(n_days)\nlows = np.zeros(n_days)\ncloses = np.zeros(n_days)\n\nopens[0] = base_price\nfor i in range(n_days):\n    if i > 0:\n        opens[i] = closes[i - 1] + np.random.normal(0, 0.5)\n    closes[i] = prices[i]\n    daily_range = abs(closes[i] - opens[i]) + np.random.uniform(1.0, 3.0)\n    highs[i] = max(opens[i], closes[i]) + np.random.uniform(0.5, daily_range * 0.6)\n    lows[i] = min(opens[i], closes[i]) - np.random.uniform(0.5, daily_range * 0.6)\n\n# Generate volume with correlation to price movements\nbase_volume = 5_000_000\nvolume = base_volume + np.random.normal(0, 1_000_000, n_days)\nprice_change = np.abs(closes - opens)\nvolume = volume + price_change * 500_000\nvolume = np.clip(volume, 1_000_000, 15_000_000).astype(int)\n\n# Create DataFrame\ndf = pd.DataFrame({\"date\": dates, \"open\": opens, \"high\": highs, \"low\": lows, \"close\": closes, \"volume\": volume})\n\n# Determine bullish vs bearish candles\ndf[\"bullish\"] = df[\"close\"] >= df[\"open\"]\ndf[\"day_idx\"] = range(len(df))\n\n# Color scheme: imprint semantic anchors\nbullish_color = BRAND  # #009E73 green\nbearish_color = SECONDARY  # #AE3030 red\n\n# Create figure with two subplots (75% price, 25% volume)\nfig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 9), height_ratios=[3, 1], sharex=True, gridspec_kw={\"hspace\": 0.05})\n\n# Set grid below chart elements\nfor ax in [ax1, ax2]:\n    ax.set_axisbelow(True)\n    ax.set_facecolor(PAGE_BG)\n\n# === Upper pane: Candlestick chart ===\n# Prepare data for wicks\ndf[\"wick_min\"] = df[[\"open\", \"close\"]].min(axis=1)\ndf[\"wick_max\"] = df[[\"open\", \"close\"]].max(axis=1)\ndf[\"body_height\"] = (df[\"wick_max\"] - df[\"wick_min\"]).clip(lower=0.5)\ndf[\"direction\"] = df[\"bullish\"].map({True: \"Bullish\", False: \"Bearish\"})\n\n# Draw high-low wicks using seaborn lineplot\nwick_long = pd.melt(\n    df[[\"day_idx\", \"high\", \"low\", \"direction\"]],\n    id_vars=[\"day_idx\", \"direction\"],\n    value_vars=[\"low\", \"high\"],\n    var_name=\"price_type\",\n    value_name=\"price\",\n).sort_values([\"day_idx\", \"price_type\"])\n\nsns.lineplot(\n    data=wick_long,\n    x=\"day_idx\",\n    y=\"price\",\n    hue=\"direction\",\n    palette={\"Bullish\": bullish_color, \"Bearish\": bearish_color},\n    linewidth=2,\n    units=\"day_idx\",\n    estimator=None,\n    legend=False,\n    ax=ax1,\n    zorder=2,\n)\n\n# Draw candle bodies\nfor _, row in df.iterrows():\n    color = bullish_color if row[\"bullish\"] else bearish_color\n    body_low = row[\"wick_min\"]\n    body_high = body_low + row[\"body_height\"]\n    ax1.fill_between(\n        [row[\"day_idx\"] - 0.35, row[\"day_idx\"] + 0.35],\n        [body_low] * 2,\n        [body_high] * 2,\n        color=color,\n        alpha=1.0,\n        linewidth=0,\n        zorder=3,\n    )\n\n# Style the price axis\nax1.set_ylabel(\"Price ($)\", fontsize=20, color=INK)\nax1.set_xlabel(\"\")\nax1.tick_params(axis=\"both\", labelsize=16, colors=INK_SOFT)\nax1.set_title(\"candlestick-volume · seaborn · anyplot.ai\", fontsize=24, color=INK, pad=15)\n\n# Set y-axis range with padding\nprice_min = df[\"low\"].min()\nprice_max = df[\"high\"].max()\nprice_padding = (price_max - price_min) * 0.05\nax1.set_ylim(price_min - price_padding, price_max + price_padding)\n\n# Remove top and right spines\nax1.spines[\"top\"].set_visible(False)\nax1.spines[\"right\"].set_visible(False)\nfor spine in [\"left\", \"bottom\"]:\n    ax1.spines[spine].set_color(INK_SOFT)\n\n# === Lower pane: Volume bars ===\nbar_colors = [bullish_color if b else bearish_color for b in df[\"bullish\"]]\nax2.bar(df[\"day_idx\"], df[\"volume\"], color=bar_colors, width=0.7, alpha=0.8, zorder=2)\n\n# Style the volume axis\nax2.set_ylabel(\"Volume (M shares)\", fontsize=20, color=INK)\nax2.set_xlabel(\"Date\", fontsize=20, color=INK)\nax2.tick_params(axis=\"both\", labelsize=16, colors=INK_SOFT)\n\n# Format y-axis for volume (millions)\nax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f\"{x / 1e6:.1f}M\"))\n\n# Remove top and right spines from volume pane\nax2.spines[\"top\"].set_visible(False)\nax2.spines[\"right\"].set_visible(False)\nfor spine in [\"left\", \"bottom\"]:\n    ax2.spines[spine].set_color(INK_SOFT)\n\n# === Grid lines ===\nax1.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nax2.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\n\n# Configure x-axis with date labels\nn_ticks = 6\ntick_positions = np.linspace(0, len(df) - 1, n_ticks, dtype=int)\ntick_labels = [df.iloc[i][\"date\"].strftime(\"%b %d\") for i in tick_positions]\nax2.set_xticks(tick_positions)\nax2.set_xticklabels(tick_labels, rotation=45, ha=\"right\")\n\n# Add legend in upper right area to avoid data overlap\nlegend_elements = [\n    Patch(facecolor=bullish_color, label=\"Bullish (Close ≥ Open)\"),\n    Patch(facecolor=bearish_color, label=\"Bearish (Close < Open)\"),\n]\nax1.legend(\n    handles=legend_elements, loc=\"upper right\", fontsize=14, framealpha=0.95, facecolor=ELEVATED_BG, edgecolor=INK_SOFT\n)\n\n# Adjust layout and save\nfig.subplots_adjust(left=0.08, right=0.95, top=0.92, bottom=0.12, hspace=0.05)\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\nplt.close()\n"}