{"spec_id":"kagi-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nkagi-basic: Basic Kagi Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_segment,\n    ggplot,\n    labs,\n    scale_color_manual,\n    scale_size_manual,\n    theme,\n    theme_minimal,\n)\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\"\nRULE = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# imprint semantic anchors (green for yang/bullish, red for yin/bearish)\nYANG_COLOR = \"#009E73\"\nYIN_COLOR = \"#AE3030\"\n\n# Generate synthetic stock price data\nnp.random.seed(42)\nn_days = 250\n\n# Create random walk with trend\nreturns = np.random.normal(0.0005, 0.02, n_days)\nprices = 100 * np.exp(np.cumsum(returns))\ndates = pd.date_range(\"2024-01-01\", periods=n_days, freq=\"B\")\n\ndf_prices = pd.DataFrame({\"date\": dates, \"close\": prices})\n\n\n# Kagi chart algorithm with reversal threshold\ndef build_kagi_data(prices, reversal_pct=0.04):\n    \"\"\"Build Kagi chart segments from price series.\"\"\"\n    kagi_segments = []\n\n    if len(prices) < 2:\n        return pd.DataFrame()\n\n    # Initialize\n    direction = 1 if prices[1] > prices[0] else -1\n    high = prices[0]\n    low = prices[0]\n    x_idx = 0\n    segment_start_price = prices[0]\n\n    # Track yang/yin state (thick/thin)\n    is_yang = direction == 1\n\n    for i in range(1, len(prices)):\n        price = prices[i]\n\n        if direction == 1:\n            if price > high:\n                high = price\n                is_yang = True\n            elif price < high * (1 - reversal_pct):\n                kagi_segments.append(\n                    {\n                        \"x\": x_idx,\n                        \"x_end\": x_idx,\n                        \"y\": segment_start_price,\n                        \"y_end\": high,\n                        \"trend\": \"yang\" if is_yang else \"yin\",\n                    }\n                )\n                kagi_segments.append(\n                    {\"x\": x_idx, \"x_end\": x_idx + 1, \"y\": high, \"y_end\": high, \"trend\": \"yang\" if is_yang else \"yin\"}\n                )\n                x_idx += 1\n                direction = -1\n                segment_start_price = high\n                low = price\n                high = price\n\n        else:\n            if price < low:\n                low = price\n                is_yang = False\n            elif price > low * (1 + reversal_pct):\n                kagi_segments.append(\n                    {\n                        \"x\": x_idx,\n                        \"x_end\": x_idx,\n                        \"y\": segment_start_price,\n                        \"y_end\": low,\n                        \"trend\": \"yin\" if not is_yang else \"yang\",\n                    }\n                )\n                kagi_segments.append(\n                    {\"x\": x_idx, \"x_end\": x_idx + 1, \"y\": low, \"y_end\": low, \"trend\": \"yin\" if not is_yang else \"yang\"}\n                )\n                x_idx += 1\n                direction = 1\n                segment_start_price = low\n                high = price\n                low = price\n\n    # Add final segment\n    final_price = prices[-1]\n    final_trend = \"yang\" if is_yang else \"yin\"\n    if direction == 1:\n        kagi_segments.append(\n            {\n                \"x\": x_idx,\n                \"x_end\": x_idx,\n                \"y\": segment_start_price,\n                \"y_end\": max(high, final_price),\n                \"trend\": final_trend,\n            }\n        )\n    else:\n        kagi_segments.append(\n            {\"x\": x_idx, \"x_end\": x_idx, \"y\": segment_start_price, \"y_end\": min(low, final_price), \"trend\": final_trend}\n        )\n\n    return pd.DataFrame(kagi_segments)\n\n\n# Build Kagi data with 4% reversal threshold\nkagi_df = build_kagi_data(df_prices[\"close\"].values, reversal_pct=0.04)\n\n# Create plot with trend layers and legend\nplot = (\n    ggplot(kagi_df, aes(x=\"x\", xend=\"x_end\", y=\"y\", yend=\"y_end\", color=\"trend\", size=\"trend\"))\n    + geom_segment(lineend=\"round\")\n    + scale_color_manual(\n        values={\"yang\": YANG_COLOR, \"yin\": YIN_COLOR}, labels={\"yang\": \"Yang (Bullish)\", \"yin\": \"Yin (Bearish)\"}\n    )\n    + scale_size_manual(values={\"yang\": 2, \"yin\": 0.7})\n    + labs(x=\"Kagi Line Index\", y=\"Price ($)\", title=\"kagi-basic · plotnine · anyplot.ai\", color=\"Trend\")\n    + theme_minimal()\n    + theme(\n        figure_size=(16, 9),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10),\n        panel_grid_minor=element_blank(),\n        panel_border=element_line(color=INK_SOFT),\n        axis_title=element_text(size=20, color=INK),\n        axis_text=element_text(size=16, color=INK_SOFT),\n        axis_line=element_line(color=INK_SOFT),\n        plot_title=element_text(size=24, color=INK),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_text=element_text(size=16, color=INK_SOFT),\n        legend_title=element_text(size=18, color=INK),\n        legend_position=\"right\",\n        text=element_text(size=14),\n    )\n)\n\n# Save plot\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}