{"spec_id":"candlestick-volume","library":"altair","language":"python","code":"\"\"\" anyplot.ai\ncandlestick-volume: Stock Candlestick Chart with Volume\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-16\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\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# Okabe-Ito palette\nBULLISH = \"#009E73\"  # Okabe-Ito position 1 (bluish green)\nBEARISH = \"#AE3030\"  # imprint red — bearish\n\n# Data: Generate 60 days of realistic stock 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 data with realistic movements\nprice = 150.0  # Starting price\nopens, highs, lows, closes, volumes = [], [], [], [], []\n\nfor _ in range(n_days):\n    # Daily return with slight upward drift\n    daily_return = np.random.normal(0.001, 0.02)\n    volatility = np.random.uniform(0.01, 0.03)\n\n    open_price = price\n    close_price = price * (1 + daily_return)\n\n    # High and low based on volatility\n    intraday_high = max(open_price, close_price) * (1 + np.random.uniform(0, volatility))\n    intraday_low = min(open_price, close_price) * (1 - np.random.uniform(0, volatility))\n\n    opens.append(round(open_price, 2))\n    highs.append(round(intraday_high, 2))\n    lows.append(round(intraday_low, 2))\n    closes.append(round(close_price, 2))\n\n    # Volume with some variation (higher on volatile days)\n    base_volume = 5000000\n    vol_multiplier = 1 + abs(daily_return) * 20\n    volumes.append(int(base_volume * vol_multiplier * np.random.uniform(0.7, 1.3)))\n\n    price = close_price\n\ndf = pd.DataFrame({\"date\": dates, \"open\": opens, \"high\": highs, \"low\": lows, \"close\": closes, \"volume\": volumes})\n\n# Determine if day is bullish (close >= open) or bearish\ndf[\"direction\"] = np.where(df[\"close\"] >= df[\"open\"], \"bullish\", \"bearish\")\n\n# Color scheme for candlesticks and volume (Okabe-Ito palette)\ncolor_scale = alt.Scale(domain=[\"bullish\", \"bearish\"], range=[BULLISH, BEARISH])\n\n# Candlestick wicks (high-low lines)\nwicks = (\n    alt.Chart(df)\n    .mark_rule(strokeWidth=2)\n    .encode(\n        x=alt.X(\n            \"date:T\",\n            axis=alt.Axis(\n                labelFontSize=18,\n                titleFontSize=22,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n                domainColor=INK_SOFT,\n                tickColor=INK_SOFT,\n                gridColor=INK_MUTED,\n                gridOpacity=0.15,\n            ),\n        ),\n        y=alt.Y(\n            \"low:Q\",\n            scale=alt.Scale(zero=False),\n            axis=alt.Axis(\n                title=\"Price ($)\",\n                labelFontSize=18,\n                titleFontSize=22,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n                domainColor=INK_SOFT,\n                tickColor=INK_SOFT,\n                gridColor=INK_MUTED,\n                gridOpacity=0.15,\n            ),\n        ),\n        y2=\"high:Q\",\n        color=alt.Color(\"direction:N\", scale=color_scale, legend=None),\n    )\n)\n\n# Candlestick bodies (open-close bars)\nbodies = (\n    alt.Chart(df)\n    .mark_bar(size=12)\n    .encode(\n        x=alt.X(\"date:T\"),\n        y=alt.Y(\"open:Q\", scale=alt.Scale(zero=False)),\n        y2=\"close:Q\",\n        color=alt.Color(\"direction:N\", scale=color_scale, legend=None),\n        tooltip=[\n            alt.Tooltip(\"date:T\", title=\"Date\", format=\"%Y-%m-%d\"),\n            alt.Tooltip(\"open:Q\", title=\"Open\", format=\"$.2f\"),\n            alt.Tooltip(\"high:Q\", title=\"High\", format=\"$.2f\"),\n            alt.Tooltip(\"low:Q\", title=\"Low\", format=\"$.2f\"),\n            alt.Tooltip(\"close:Q\", title=\"Close\", format=\"$.2f\"),\n            alt.Tooltip(\"volume:Q\", title=\"Volume\", format=\",\"),\n        ],\n    )\n)\n\n# Combine wicks and bodies for candlestick chart\ncandlestick = (wicks + bodies).properties(width=1600, height=600, title=\"\")\n\n# Volume chart\nvolume = (\n    alt.Chart(df)\n    .mark_bar(size=12)\n    .encode(\n        x=alt.X(\n            \"date:T\",\n            axis=alt.Axis(\n                labelFontSize=18,\n                titleFontSize=22,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n                domainColor=INK_SOFT,\n                tickColor=INK_SOFT,\n                gridColor=INK_MUTED,\n                gridOpacity=0.15,\n            ),\n        ),\n        y=alt.Y(\n            \"volume:Q\",\n            axis=alt.Axis(\n                title=\"Volume\",\n                labelFontSize=18,\n                titleFontSize=22,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n                domainColor=INK_SOFT,\n                tickColor=INK_SOFT,\n                gridColor=INK_MUTED,\n                gridOpacity=0.15,\n                format=\"~s\",\n            ),\n        ),\n        color=alt.Color(\"direction:N\", scale=color_scale, legend=None),\n        tooltip=[\n            alt.Tooltip(\"date:T\", title=\"Date\", format=\"%Y-%m-%d\"),\n            alt.Tooltip(\"volume:Q\", title=\"Volume\", format=\",\"),\n        ],\n    )\n    .properties(width=1600, height=200)\n)\n\n# Combine candlestick and volume vertically with interactive features\ncombined = (\n    alt.vconcat(candlestick, volume, spacing=10)\n    .properties(\n        title=alt.Title(\"candlestick-volume · altair · anyplot.ai\", fontSize=28, anchor=\"middle\", color=INK),\n        background=PAGE_BG,\n    )\n    .interactive()\n)\n\n# Configure chart with theme-adaptive styling\nchart = (\n    combined.configure_axis(\n        labelFontSize=18,\n        titleFontSize=22,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK_MUTED,\n        gridOpacity=0.15,\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_title(color=INK, fontSize=28)\n)\n\n# Save as PNG with theme suffix (scale_factor=3 for 4800x2700)\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\n\n# Save interactive HTML version with theme suffix\nchart.save(f\"plot-{THEME}.html\")\n"}