{"spec_id":"candlestick-volume","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ncandlestick-volume: Stock Candlestick Chart with Volume\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-16\n\"\"\"\n\nimport os\n\nimport matplotlib.dates as mdates\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\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\"\n\n# imprint semantic anchors\nUP_COLOR = \"#009E73\"  # green — up days\nDOWN_COLOR = \"#AE3030\"  # red — down days\n\n# Data - Generate realistic 60 trading days of OHLC data with volume\nnp.random.seed(42)\nn_days = 60\ndates = pd.date_range(\"2024-01-02\", periods=n_days, freq=\"B\")  # Business days\n\n# Generate price path with realistic movement\nbase_price = 150.0\nreturns = np.random.normal(0.001, 0.02, n_days)\nprices = base_price * np.cumprod(1 + returns)\n\n# Create OHLC data\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\ncloses[0] = prices[0]\nfor i in range(1, n_days):\n    opens[i] = closes[i - 1] * (1 + np.random.normal(0, 0.005))\n    closes[i] = prices[i]\n\n# High/low based on open/close with some variation\nfor i in range(n_days):\n    oc_max = max(opens[i], closes[i])\n    oc_min = min(opens[i], closes[i])\n    highs[i] = oc_max + np.random.uniform(0.5, 2.0)\n    lows[i] = oc_min - np.random.uniform(0.5, 2.0)\n\n# Volume with higher volume on big moves\nbase_volume = 5_000_000\nvolume_multiplier = 1 + np.abs(closes - opens) / opens * 20\nvolumes = base_volume * volume_multiplier * np.random.uniform(0.7, 1.3, n_days)\nvolumes = volumes.astype(int)\n\n# Colors for up/down days\nis_up = closes >= opens\n\n# Create figure with two subplots sharing x-axis (75% price, 25% volume)\nfig, (ax_price, ax_volume) = plt.subplots(\n    2, 1, figsize=(16, 9), gridspec_kw={\"height_ratios\": [3, 1]}, sharex=True, facecolor=PAGE_BG\n)\nax_price.set_facecolor(PAGE_BG)\nax_volume.set_facecolor(PAGE_BG)\n\n# Candlestick chart - Price pane\ncandle_width = 0.6\nfor i in range(n_days):\n    color = UP_COLOR if is_up[i] else DOWN_COLOR\n    # Draw wick (high-low line)\n    ax_price.plot([dates[i], dates[i]], [lows[i], highs[i]], color=color, linewidth=2.5, solid_capstyle=\"round\")\n    # Draw body (open-close rectangle)\n    body_bottom = min(opens[i], closes[i])\n    body_height = abs(closes[i] - opens[i])\n    ax_price.bar(\n        dates[i], body_height, width=candle_width, bottom=body_bottom, color=color, edgecolor=color, linewidth=0.5\n    )\n\n# Volume bars with matching colors\nfor i in range(n_days):\n    color = UP_COLOR if is_up[i] else DOWN_COLOR\n    ax_volume.bar(dates[i], volumes[i], width=candle_width, color=color, alpha=0.8)\n\n# Price pane styling\nax_price.set_ylabel(\"Price ($)\", fontsize=20, color=INK)\nax_price.tick_params(axis=\"both\", labelsize=16, colors=INK_SOFT, labelcolor=INK_SOFT)\nax_price.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nax_price.set_title(\"candlestick-volume · matplotlib · anyplot.ai\", fontsize=24, fontweight=\"medium\", color=INK, pad=15)\n\n# Add legend\nlegend_elements = [\n    Patch(facecolor=UP_COLOR, label=\"Up (Close ≥ Open)\"),\n    Patch(facecolor=DOWN_COLOR, label=\"Down (Close < Open)\"),\n]\nleg = ax_price.legend(handles=legend_elements, loc=\"upper left\", fontsize=16)\nleg.get_frame().set_facecolor(ELEVATED_BG)\nleg.get_frame().set_edgecolor(INK_SOFT)\nleg.get_frame().set_linewidth(0.8)\nfor text in leg.get_texts():\n    text.set_color(INK_SOFT)\n\n# Volume pane styling\nax_volume.set_xlabel(\"Date\", fontsize=20, color=INK)\nax_volume.set_ylabel(\"Volume (shares)\", fontsize=20, color=INK)\nax_volume.tick_params(axis=\"both\", labelsize=16, colors=INK_SOFT, labelcolor=INK_SOFT)\nax_volume.grid(True, alpha=0.15, linewidth=0.8, color=INK)\n\n# Format y-axis for volume (in millions)\nax_volume.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f\"{x / 1e6:.1f}M\"))\n\n# Format x-axis dates\nax_volume.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MO, interval=2))\nax_volume.xaxis.set_major_formatter(mdates.DateFormatter(\"%b %d\"))\nplt.setp(ax_volume.xaxis.get_majorticklabels(), rotation=45, ha=\"right\", color=INK_SOFT)\n\n# Ensure y-axis starts at 0 for volume\nax_volume.set_ylim(bottom=0)\n\n# Crosshair cursor: vertical line at mouse position spanning both panes\n# Static crosshair at the midpoint for visual alignment\nmidpoint_date = dates[n_days // 2]\nfor ax in [ax_price, ax_volume]:\n    ax.axvline(x=midpoint_date, color=INK_SOFT, linestyle=\"--\", linewidth=1, alpha=0.4)\n\n# Spine styling for both panes\nfor ax in [ax_price, ax_volume]:\n    ax.spines[\"top\"].set_visible(False)\n    ax.spines[\"right\"].set_visible(False)\n    for s in (\"left\", \"bottom\"):\n        ax.spines[s].set_color(INK_SOFT)\n        ax.spines[s].set_linewidth(0.8)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}