{"spec_id":"candlestick-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\ncandlestick-basic: Basic Candlestick Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-30\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent self-import: script filename matches the library name, so remove script dir from path\nif sys.path and sys.path[0] not in (None,):\n    del sys.path[0]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    coord_cartesian,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_hline,\n    geom_rect,\n    geom_segment,\n    ggplot,\n    labs,\n    scale_color_manual,\n    scale_fill_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens (Imprint palette + adaptive chrome)\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# Imprint palette — semantic exception: green=bullish (gain/profit), red=bearish (loss/decline)\nBULL_COLOR = \"#009E73\"  # Imprint pos 1 — profit / gain / up\nBEAR_COLOR = \"#AE3030\"  # Imprint pos 5 — loss / decline (deferred semantic red anchor)\npalette = {\"Bullish\": BULL_COLOR, \"Bearish\": BEAR_COLOR}\n\n# Data — 30 trading days, random walk starting at $150\nnp.random.seed(42)\nn_days = 30\ndates = pd.date_range(start=\"2024-01-02\", periods=n_days, freq=\"B\")\n\nprice = 150.0\nopens, highs, lows, closes = [], [], [], []\nfor _ in range(n_days):\n    open_price = price\n    change = np.random.randn() * 3\n    close_price = open_price + change\n    high_price = max(open_price, close_price) + abs(np.random.randn() * 1.5)\n    low_price = min(open_price, close_price) - abs(np.random.randn() * 1.5)\n    opens.append(open_price)\n    highs.append(high_price)\n    lows.append(low_price)\n    closes.append(close_price)\n    price = close_price + np.random.randn() * 0.5\n\ndf = pd.DataFrame({\"date\": dates, \"open\": opens, \"high\": highs, \"low\": lows, \"close\": closes})\ndf[\"day\"] = np.arange(len(df))\ndf[\"direction\"] = pd.Categorical(\n    np.where(df[\"close\"] >= df[\"open\"], \"Bullish\", \"Bearish\"), categories=[\"Bullish\", \"Bearish\"]\n)\ndf[\"body_top\"] = df[[\"open\", \"close\"]].max(axis=1)\ndf[\"body_bottom\"] = df[[\"open\", \"close\"]].min(axis=1)\n\n# X-axis tick labels (every 5th trading day)\ntick_indices = list(range(0, n_days, 5))\ntick_labels = [dates[i].strftime(\"%b %d\") for i in tick_indices]\n\n# Reference prices for storytelling annotations\nopen_first = df[\"open\"].iloc[0]\nclose_last = df[\"close\"].iloc[-1]\nclose_dir_color = BULL_COLOR if close_last >= open_first else BEAR_COLOR\nnet_pct = (close_last - open_first) / open_first * 100\n\n# Title — 50 chars < 67 baseline, so default 12pt applies\ntitle = \"candlestick-basic · python · plotnine · anyplot.ai\"\ntitle_size = max(8, round(12 * 67 / len(title)))\n\n# Plot\nplot = (\n    ggplot(df)\n    # Reference line at period opening price\n    + geom_hline(yintercept=open_first, linetype=\"dashed\", color=INK_SOFT, size=0.5)\n    # Wicks colored by direction for visual coherence\n    + geom_segment(aes(x=\"day\", xend=\"day\", y=\"low\", yend=\"high\", color=\"direction\"), size=0.8)\n    # Candle bodies — fill by direction, static INK_SOFT edge for definition\n    + geom_rect(\n        aes(xmin=\"day - 0.35\", xmax=\"day + 0.35\", ymin=\"body_bottom\", ymax=\"body_top\", fill=\"direction\"),\n        color=INK_SOFT,\n        size=0.3,\n    )\n    + scale_fill_manual(values=palette, name=\"Direction\")\n    + scale_color_manual(values=palette, guide=None)\n    # Annotate reference line label\n    + annotate(\n        \"text\", x=n_days - 0.5, y=open_first + 0.8, label=f\"Open ${open_first:.0f}\", size=3, color=INK_MUTED, ha=\"right\"\n    )\n    # Annotate net change at bottom\n    + annotate(\n        \"text\",\n        x=n_days - 0.5,\n        y=df[\"low\"].min() - 1.2,\n        label=f\"Close ${close_last:.0f}  ({net_pct:+.1f}%)\",\n        size=3,\n        color=close_dir_color,\n        ha=\"right\",\n    )\n    + scale_x_continuous(breaks=tick_indices, labels=tick_labels, expand=(0.02, 0.5))\n    + scale_y_continuous(labels=lambda vals: [f\"${v:,.0f}\" for v in vals])\n    + coord_cartesian(ylim=(df[\"low\"].min() - 2, df[\"high\"].max() + 2))\n    + labs(x=\"\", y=\"Price ($)\", title=title)\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(size=7),\n        plot_title=element_text(size=title_size, color=INK),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor_x=element_blank(),\n        panel_grid_major_y=element_line(color=INK_SOFT, size=0.3, alpha=0.15),\n        panel_grid_minor_y=element_blank(),\n        axis_line=element_line(color=INK_SOFT),\n        legend_position=\"top\",\n        legend_title=element_text(size=9, color=INK),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}