{"spec_id":"indicator-ichimoku","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nindicator-ichimoku: Ichimoku Cloud Technical Indicator Chart\nLibrary: plotnine 0.15.5 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-06-08\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_line,\n    geom_point,\n    geom_rect,\n    geom_ribbon,\n    geom_segment,\n    ggplot,\n    guide_legend,\n    guides,\n    labs,\n    scale_color_manual,\n    scale_fill_identity,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme-adaptive chrome tokens (Imprint palette)\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 categorical palette — 8 hues, theme-independent, hybrid-v3 sort\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Conventional green/red for candlesticks — semantic exception (finance: up/down)\nBULL_COLOR = \"#2E7D32\"\nBEAR_COLOR = \"#C62828\"\n\n# Data: 200 trading days of simulated ACME Corp stock prices\nnp.random.seed(42)\nn_days = 200\n\ndates = pd.date_range(start=\"2023-06-01\", periods=n_days, freq=\"B\")\n\nprice = 145.0\nopens, highs, lows, closes = [], [], [], []\n\nfor i in range(n_days):\n    open_price = price\n    trend = 0.05 * np.sin(2 * np.pi * i / 120)\n    change = np.random.randn() * 2.5 + trend\n    close_price = open_price + change\n    high_price = max(open_price, close_price) + abs(np.random.randn() * 1.2)\n    low_price = min(open_price, close_price) - abs(np.random.randn() * 1.2)\n\n    opens.append(open_price)\n    highs.append(high_price)\n    lows.append(low_price)\n    closes.append(close_price)\n\n    price = close_price + np.random.randn() * 0.3\n\ndf = pd.DataFrame({\"date\": dates, \"open\": opens, \"high\": highs, \"low\": lows, \"close\": closes})\n\n# Compute Ichimoku components (standard 9, 26, 52 parameters)\nhigh_9 = df[\"high\"].rolling(window=9).max()\nlow_9 = df[\"low\"].rolling(window=9).min()\ndf[\"tenkan_sen\"] = (high_9 + low_9) / 2\n\nhigh_26 = df[\"high\"].rolling(window=26).max()\nlow_26 = df[\"low\"].rolling(window=26).min()\ndf[\"kijun_sen\"] = (high_26 + low_26) / 2\n\nsenkou_a = (df[\"tenkan_sen\"] + df[\"kijun_sen\"]) / 2\nsenkou_b_raw = (df[\"high\"].rolling(window=52).max() + df[\"low\"].rolling(window=52).min()) / 2\n\n# Chikou Span: close shifted 26 periods back\ndf[\"chikou_span\"] = df[\"close\"].shift(-26)\n\n# Integer day index for plotting\ndf[\"day\"] = np.arange(n_days)\n\ndf[\"body_top\"] = df[[\"open\", \"close\"]].max(axis=1)\ndf[\"body_bottom\"] = df[[\"open\", \"close\"]].min(axis=1)\ndf[\"candle_fill\"] = np.where(df[\"close\"] >= df[\"open\"], BULL_COLOR, BEAR_COLOR)\n\n# Cloud DataFrame shifted 26 periods ahead\ncloud_df = pd.DataFrame(\n    {\"day\": np.arange(n_days) + 26, \"span_a\": senkou_a.values, \"span_b\": senkou_b_raw.values}\n).dropna()\n\ncloud_df[\"cloud_top\"] = np.maximum(cloud_df[\"span_a\"], cloud_df[\"span_b\"])\ncloud_df[\"cloud_bottom\"] = np.minimum(cloud_df[\"span_a\"], cloud_df[\"span_b\"])\n\nvisible_start = 52\ndf_vis = df[df[\"day\"] >= visible_start].copy()\n\n# Indicator lines in long format — Imprint palette assignments\n# Tenkan-sen → Imprint #1 (brand green), Kijun-sen → Imprint #2 (lavender)\n# Chikou Span → Imprint #7 (rose), Senkou A → Imprint #4 (ochre), Senkou B → Imprint #5 (red)\nindicator_colors = {\n    \"Tenkan-sen\": IMPRINT_PALETTE[0],  # #009E73 brand green\n    \"Kijun-sen\": IMPRINT_PALETTE[1],  # #C475FD lavender\n    \"Chikou Span\": IMPRINT_PALETTE[6],  # #954477 rose\n    \"Senkou Span A\": IMPRINT_PALETTE[3],  # #BD8233 ochre\n    \"Senkou Span B\": IMPRINT_PALETTE[4],  # #AE3030 matte red\n}\n\nlines_long = pd.concat(\n    [\n        df[[\"day\", \"tenkan_sen\"]]\n        .dropna()\n        .query(\"day >= @visible_start\")\n        .rename(columns={\"tenkan_sen\": \"value\"})\n        .assign(indicator=\"Tenkan-sen\"),\n        df[[\"day\", \"kijun_sen\"]]\n        .dropna()\n        .query(\"day >= @visible_start\")\n        .rename(columns={\"kijun_sen\": \"value\"})\n        .assign(indicator=\"Kijun-sen\"),\n        df[[\"day\", \"chikou_span\"]]\n        .dropna()\n        .query(\"day >= @visible_start\")\n        .rename(columns={\"chikou_span\": \"value\"})\n        .assign(indicator=\"Chikou Span\"),\n        cloud_df[[\"day\", \"span_a\"]]\n        .query(\"day >= @visible_start\")\n        .rename(columns={\"span_a\": \"value\"})\n        .assign(indicator=\"Senkou Span A\"),\n        cloud_df[[\"day\", \"span_b\"]]\n        .query(\"day >= @visible_start\")\n        .rename(columns={\"span_b\": \"value\"})\n        .assign(indicator=\"Senkou Span B\"),\n    ],\n    ignore_index=True,\n)\n\nvisible_end = n_days + 26\ncloud_vis = cloud_df[(cloud_df[\"day\"] >= visible_start) & (cloud_df[\"day\"] <= visible_end)].copy()\n\n# TK crossover markers for data storytelling\ntk_data = df[[\"day\", \"tenkan_sen\", \"kijun_sen\"]].dropna().query(\"day >= @visible_start\").copy()\ntk_data[\"tk_diff\"] = tk_data[\"tenkan_sen\"] - tk_data[\"kijun_sen\"]\ntk_data[\"prev_diff\"] = tk_data[\"tk_diff\"].shift(1)\ncrossovers = tk_data[(tk_data[\"tk_diff\"] * tk_data[\"prev_diff\"]) < 0].copy()\ncrossovers[\"cross_price\"] = (crossovers[\"tenkan_sen\"] + crossovers[\"kijun_sen\"]) / 2\n\n# X-axis ticks every ~20 trading days\ntick_indices = list(range(visible_start, n_days, 20))\ntick_labels = [dates[i].strftime(\"%b '%y\") for i in tick_indices]\n\nplot = (\n    ggplot()\n    # Kumo cloud — bullish (Span A > Span B): green tint\n    + geom_ribbon(\n        aes(x=\"day\", ymin=\"cloud_bottom\", ymax=\"cloud_top\"),\n        data=cloud_vis[cloud_vis[\"span_a\"] >= cloud_vis[\"span_b\"]],\n        fill=BULL_COLOR,\n        alpha=0.38,\n    )\n    # Kumo cloud — bearish (Span B > Span A): red tint\n    + geom_ribbon(\n        aes(x=\"day\", ymin=\"cloud_bottom\", ymax=\"cloud_top\"),\n        data=cloud_vis[cloud_vis[\"span_a\"] < cloud_vis[\"span_b\"]],\n        fill=BEAR_COLOR,\n        alpha=0.38,\n    )\n    # Candlestick wicks\n    + geom_segment(\n        aes(x=\"day\", xend=\"day\", y=\"low\", yend=\"high\"),\n        data=df_vis[df_vis[\"close\"] >= df_vis[\"open\"]],\n        color=\"#1B5E20\",\n        size=0.6,\n    )\n    + geom_segment(\n        aes(x=\"day\", xend=\"day\", y=\"low\", yend=\"high\"),\n        data=df_vis[df_vis[\"close\"] < df_vis[\"open\"]],\n        color=\"#8E0000\",\n        size=0.6,\n    )\n    # Candlestick bodies\n    + geom_rect(\n        aes(xmin=\"day - 0.42\", xmax=\"day + 0.42\", ymin=\"body_bottom\", ymax=\"body_top\", fill=\"candle_fill\"),\n        data=df_vis[df_vis[\"close\"] >= df_vis[\"open\"]],\n        color=\"#1B5E20\",\n        size=0.25,\n    )\n    + geom_rect(\n        aes(xmin=\"day - 0.42\", xmax=\"day + 0.42\", ymin=\"body_bottom\", ymax=\"body_top\", fill=\"candle_fill\"),\n        data=df_vis[df_vis[\"close\"] < df_vis[\"open\"]],\n        color=\"#8E0000\",\n        size=0.25,\n    )\n    # TK crossover markers\n    + geom_point(\n        aes(x=\"day\", y=\"cross_price\"),\n        data=crossovers,\n        shape=\"D\",\n        size=2.5,\n        color=INK,\n        fill=\"#DDCC77\",  # Imprint amber — warning/signal role\n        stroke=0.5,\n    )\n    # Indicator lines with legend\n    + geom_line(aes(x=\"day\", y=\"value\", color=\"indicator\"), data=lines_long, size=1.1)\n    + scale_fill_identity()\n    + scale_color_manual(values=indicator_colors, name=\"Ichimoku Indicators\", breaks=list(indicator_colors.keys()))\n    + guides(color=guide_legend(override_aes={\"size\": 2.5}))\n    + scale_x_continuous(breaks=tick_indices, labels=tick_labels, expand=(0.01, 0))\n    + scale_y_continuous(labels=lambda vals: [f\"${v:,.0f}\" for v in vals])\n    + labs(x=\"\", y=\"Price ($)\", title=\"indicator-ichimoku · python · plotnine · anyplot.ai\")\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(size=7, family=\"sans-serif\", color=INK_SOFT),\n        axis_title_y=element_text(size=10, color=INK, margin={\"r\": 8}),\n        axis_title_x=element_blank(),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        axis_text_x=element_text(margin={\"t\": 4}),\n        plot_title=element_text(size=12, weight=\"bold\", color=INK, margin={\"b\": 8}),\n        legend_title=element_text(size=8, weight=\"bold\", color=INK),\n        legend_text=element_text(size=7, color=INK_SOFT),\n        legend_position=\"right\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.4),\n        legend_key_size=12,\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor_x=element_blank(),\n        panel_grid_major_y=element_line(color=INK, size=0.2, alpha=0.15),\n        panel_grid_minor_y=element_blank(),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=\"none\"),\n        plot_margin=0.02,\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}