{"spec_id":"candlestick-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\ncandlestick-basic: Basic Candlestick Chart\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-05-30\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove the script's own directory from sys.path so `import altair` finds the\n# installed package, not this file (which is named altair.py).\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p and os.path.abspath(p) != _this_dir]\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Theme-adaptive chrome tokens (Imprint palette style guide)\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# Imprint palette — finance semantic exception: green=profit/bullish, red=loss/bearish\nBULLISH = \"#009E73\"  # Imprint position 1, brand green\nBEARISH = \"#AE3030\"  # Imprint position 5, matte red (loss/error semantic anchor)\nSMA_COLOR = \"#4467A3\"  # Imprint position 3, blue\n\n# Simulated 30 business days of stock price data\nnp.random.seed(42)\nn_days = 30\ndates = pd.date_range(start=\"2024-01-01\", periods=n_days, freq=\"B\")\n\nprices = [100.0]\nfor _ in range(n_days - 1):\n    change = np.random.randn() * 2\n    prices.append(prices[-1] + change)\n\ndata = []\nfor i, date in enumerate(dates):\n    base = prices[i]\n    volatility = np.random.uniform(1, 3)\n    open_price = base + np.random.uniform(-volatility, volatility)\n    close_price = base + np.random.uniform(-volatility, volatility)\n    high_price = max(open_price, close_price) + np.random.uniform(0.5, volatility)\n    low_price = min(open_price, close_price) - np.random.uniform(0.5, volatility)\n    data.append(\n        {\n            \"date\": date,\n            \"open\": round(open_price, 2),\n            \"high\": round(high_price, 2),\n            \"low\": round(low_price, 2),\n            \"close\": round(close_price, 2),\n        }\n    )\n\ndf = pd.DataFrame(data)\ndf[\"direction\"] = np.where(df[\"close\"] >= df[\"open\"], \"Bullish\", \"Bearish\")\ndf[\"sma5\"] = df[\"close\"].rolling(window=5).mean()\n\n# Imprint color scale — finance semantic: green=bullish, red=bearish\ncolor_scale = alt.Scale(domain=[\"Bullish\", \"Bearish\"], range=[BULLISH, BEARISH])\n\n# Wicks: high-low lines, thinner than bodies\nwicks = (\n    alt.Chart(df)\n    .mark_rule(strokeWidth=1.5)\n    .encode(\n        x=alt.X(\"date:T\", title=\"Date\", axis=alt.Axis(format=\"%b %d\")),\n        y=alt.Y(\"low:Q\", title=\"Price ($)\", scale=alt.Scale(zero=False)),\n        y2=\"high:Q\",\n        color=alt.Color(\"direction:N\", scale=color_scale, legend=None),\n    )\n)\n\n# Bodies: open-close bars — thin ink stroke aids CVD accessibility (shape outline)\nbodies = (\n    alt.Chart(df)\n    .mark_bar(size=12, stroke=INK, strokeWidth=0.7)\n    .encode(\n        x=\"date:T\",\n        y=\"open:Q\",\n        y2=\"close:Q\",\n        color=alt.Color(\n            \"direction:N\", scale=color_scale, legend=alt.Legend(title=\"Direction\", labelFontSize=10, titleFontSize=10)\n        ),\n        tooltip=[\n            alt.Tooltip(\"date:T\", title=\"Date\", format=\"%b %d, %Y\"),\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        ],\n    )\n)\n\n# 5-day simple moving average overlay\nsma_df = df.dropna(subset=[\"sma5\"])\nsma_line = (\n    alt.Chart(sma_df)\n    .mark_line(strokeWidth=2.0, strokeDash=[6, 3], opacity=0.85)\n    .encode(x=\"date:T\", y=\"sma5:Q\", color=alt.value(SMA_COLOR))\n)\n\n# SMA inline label — positioned early (before the peak) to avoid overlap with tall candles\nsma_mid = sma_df.iloc[[2]]\nsma_label = (\n    alt.Chart(sma_mid)\n    .mark_text(align=\"left\", dy=-10, fontSize=10, fontWeight=\"bold\", fontStyle=\"italic\")\n    .encode(x=\"date:T\", y=\"sma5:Q\", text=alt.value(\"5-day MA\"), color=alt.value(SMA_COLOR))\n)\n\nchart = (\n    alt.layer(wicks, bodies, sma_line, sma_label)\n    .resolve_scale(color=\"independent\")\n    .properties(\n        width=620,\n        height=320,\n        background=PAGE_BG,\n        title=alt.Title(\n            \"candlestick-basic · python · altair · anyplot.ai\",\n            fontSize=16,\n            anchor=\"middle\",\n            color=INK,\n            subtitle=\"30-day price action with 5-day moving average\",\n            subtitleFontSize=12,\n            subtitleColor=INK_MUTED,\n        ),\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.15,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=12,\n    )\n    .configure_axisX(grid=False)\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=10,\n    )\n    .interactive()\n)\n\n# Save PNG at canonical landscape target: 3200 × 1800\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\n# PAD-only to canonical target — never crop (cropping clips title/labels, triggers AR-09)\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}