{"spec_id":"candlestick-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ncandlestick-basic: Basic Candlestick Chart\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-30\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\n\n\nLetsPlot.setup_html()\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Theme-adaptive chrome tokens\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: finance uses green=profit/up, red=loss/down\nBULL_COLOR = \"#009E73\"  # Imprint position 1 — bullish / up\nBEAR_COLOR = \"#AE3030\"  # Imprint position 5 — bearish / down\n\n# Data — simulated 30 trading days of OHLC prices\nnp.random.seed(42)\nn_days = 30\n\ndates = pd.date_range(start=\"2024-01-02\", periods=n_days, freq=\"B\")\n\nprice = 100.0\nopens, highs, lows, closes = [], [], [], []\n\nfor _ in range(n_days):\n    open_price = price\n    change = np.random.randn() * 2\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    closes.append(close_price)\n    highs.append(high_price)\n    lows.append(low_price)\n    price = close_price\n\ndf = pd.DataFrame({\"date\": dates, \"open\": opens, \"high\": highs, \"low\": lows, \"close\": closes})\n\n# Derived candlestick columns\ndf[\"direction\"] = np.where(df[\"close\"] >= df[\"open\"], \"Bullish\", \"Bearish\")\ndf[\"body_low\"] = df[[\"open\", \"close\"]].min(axis=1)\ndf[\"body_high\"] = df[[\"open\", \"close\"]].max(axis=1)\ndf[\"x\"] = range(len(df))\ndf[\"xmin\"] = df[\"x\"] - 0.35\ndf[\"xmax\"] = df[\"x\"] + 0.35\ndf[\"date_str\"] = df[\"date\"].dt.strftime(\"%b %d\")\n\n# 5-day simple moving average\ndf[\"sma5\"] = df[\"close\"].rolling(window=5).mean()\nsma_df = df.dropna(subset=[\"sma5\"]).copy()\n\n# Peak price annotation\npeak_idx = int(df[\"high\"].idxmax())\npeak_x = df.loc[peak_idx, \"x\"]\npeak_y = df.loc[peak_idx, \"high\"]\npeak_df = pd.DataFrame({\"x\": [peak_x], \"y\": [peak_y], \"label\": [f\"Peak ${peak_y:.0f}\"]})\n\n# Tick positions: every 5th trading day\ntick_pos = list(range(0, n_days, 5))\ntick_labels = [dates[i].strftime(\"%b %d\") for i in tick_pos]\n\n# Interactive tooltip template\ntip_fmt = (\n    layer_tooltips()\n    .line(\"@date_str\")\n    .line(\"Open|$@open\")\n    .line(\"High|$@high\")\n    .line(\"Low|$@low\")\n    .line(\"Close|$@close\")\n)\n\nplot = (\n    ggplot(df)\n    # Wicks (high-low lines) — thinner than body\n    + geom_segment(\n        aes(x=\"x\", xend=\"x\", y=\"low\", yend=\"high\", color=\"direction\"),\n        size=0.7,\n        tooltips=tip_fmt,\n    )\n    # Candle bodies (open-close range)\n    + geom_rect(\n        aes(\n            xmin=\"xmin\", xmax=\"xmax\", ymin=\"body_low\", ymax=\"body_high\", fill=\"direction\", color=\"direction\"\n        ),\n        size=0.4,\n        tooltips=tip_fmt,\n    )\n    # 5-day SMA trend line\n    + geom_line(\n        aes(x=\"x\", y=\"sma5\"),\n        data=sma_df,\n        color=INK_MUTED,\n        size=0.8,\n        alpha=0.7,\n        linetype=\"dashed\",\n        tooltips=\"none\",\n    )\n    # Peak diamond marker\n    + geom_point(\n        aes(x=\"x\", y=\"y\"),\n        data=peak_df,\n        size=4,\n        shape=18,\n        color=INK,\n    )\n    # Peak label\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=peak_df,\n        size=4,\n        color=INK,\n        nudge_y=1.8,\n        fontface=\"bold\",\n    )\n    + scale_fill_manual(values={\"Bullish\": BULL_COLOR, \"Bearish\": BEAR_COLOR})\n    + scale_color_manual(values={\"Bullish\": BULL_COLOR, \"Bearish\": BEAR_COLOR})\n    + scale_x_continuous(breaks=tick_pos, labels=tick_labels, expand=[0.02, 0])\n    + scale_y_continuous(expand=[0.14, 0])\n    + labs(\n        x=\"Trading Day (Jan–Feb 2024)\",\n        y=\"Price ($)\",\n        title=\"candlestick-basic · python · letsplot · anyplot.ai\",\n        subtitle=\"Simulated 30-day equity prices — 5-day moving average (dashed)\",\n        color=\"\",\n        fill=\"\",\n    )\n    + theme_minimal()\n    + theme(\n        plot_title=element_text(size=16, color=INK, face=\"bold\"),\n        plot_subtitle=element_text(size=11, color=INK_SOFT),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_title=element_text(size=10, color=INK),\n        panel_grid_major_x=element_blank(),\n        panel_grid_major_y=element_line(color=INK_SOFT, size=0.2),\n        panel_grid_minor=element_blank(),\n        axis_ticks=element_blank(),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_position=\"right\",\n    )\n    + ggsize(800, 450)\n)\n\n# Save — theme-suffixed outputs (pipeline runs this script twice)\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}