{"spec_id":"indicator-ichimoku","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nindicator-ichimoku: Ichimoku Cloud Technical Indicator Chart\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-06-08\n\"\"\"\n\nimport os\n\nimport matplotlib.dates as mdates\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as mticker\nimport numpy as np\nimport pandas as pd\n\n\n# Theme\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: finance up/down maps to green/red\nCOLOR_UP = \"#009E73\"  # brand green — bullish candles & cloud\nCOLOR_DOWN = \"#AE3030\"  # matte red — bearish candles & cloud\nCOLOR_TENKAN = \"#C475FD\"  # lavender — Tenkan-sen\nCOLOR_KIJUN = \"#4467A3\"  # blue — Kijun-sen\nCOLOR_CHIKOU = \"#BD8233\"  # ochre — Chikou Span\n\n# Data — 200 trading days with uptrend → consolidation → breakdown → recovery\nnp.random.seed(42)\nn_days = 200\ndates = pd.bdate_range(start=\"2024-01-02\", periods=n_days)\n\ntrend = np.concatenate(\n    [np.linspace(0, 0.12, 60), np.linspace(0.12, 0.10, 30), np.linspace(0.10, -0.08, 50), np.linspace(-0.08, 0.02, 60)]\n)\nnoise = np.random.randn(n_days) * 0.008\nreturns = np.diff(trend, prepend=trend[0]) + noise\nprice_series = 155 * np.exp(np.cumsum(returns))\n\nopen_prices = price_series * (1 + np.random.uniform(-0.005, 0.005, n_days))\nclose_prices = price_series * (1 + np.random.uniform(-0.012, 0.012, n_days))\nintraday_ranges = price_series * np.random.uniform(0.008, 0.025, n_days)\nlow_prices = np.minimum(open_prices, close_prices) - np.random.uniform(0.2, 0.8, n_days) * intraday_ranges\nhigh_prices = np.maximum(open_prices, close_prices) + np.random.uniform(0.2, 0.8, n_days) * intraday_ranges\n\ndf = pd.DataFrame({\"date\": dates, \"open\": open_prices, \"high\": high_prices, \"low\": low_prices, \"close\": close_prices})\n\n# Ichimoku components — standard parameters (9, 26, 52)\ntenkan_period, kijun_period, senkou_b_period, displacement = 9, 26, 52, 26\nhigh_s = df[\"high\"]\nlow_s = df[\"low\"]\n\ntenkan_sen = (high_s.rolling(tenkan_period).max() + low_s.rolling(tenkan_period).min()) / 2\nkijun_sen = (high_s.rolling(kijun_period).max() + low_s.rolling(kijun_period).min()) / 2\nsenkou_span_a = ((tenkan_sen + kijun_sen) / 2).shift(displacement)\nsenkou_span_b = ((high_s.rolling(senkou_b_period).max() + low_s.rolling(senkou_b_period).min()) / 2).shift(displacement)\nchikou_span = df[\"close\"].shift(-displacement)\n\ndf[\"tenkan_sen\"] = tenkan_sen\ndf[\"kijun_sen\"] = kijun_sen\ndf[\"senkou_span_a\"] = senkou_span_a\ndf[\"senkou_span_b\"] = senkou_span_b\ndf[\"chikou_span\"] = chikou_span\n\n# Trim to last 120 days for a clean view with enough indicator history\ndf_plot = df.iloc[80:].reset_index(drop=True)\n\n# Canvas — landscape 3200×1800 px (hard contract)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\ndate_nums = mdates.date2num(df_plot[\"date\"])\nbullish = df_plot[\"close\"] >= df_plot[\"open\"]\ncandle_colors = np.where(bullish, COLOR_UP, COLOR_DOWN)\nwidth = 0.65\n\n# Candlestick wicks\nax.vlines(date_nums, df_plot[\"low\"], df_plot[\"high\"], colors=candle_colors, linewidth=0.8, zorder=3)\n\n# Candlestick bodies — split bullish/bearish for CVD redundant encoding\nbody_bottoms = np.where(bullish, df_plot[\"open\"], df_plot[\"close\"])\nbody_heights = np.abs(df_plot[\"close\"] - df_plot[\"open\"])\nbody_heights = np.where(body_heights < 0.01, 0.01, body_heights)\nbullish_idx = bullish.values\n# Bullish: solid fill, no special edge\nax.bar(\n    date_nums[bullish_idx],\n    body_heights[bullish_idx],\n    bottom=body_bottoms[bullish_idx],\n    width=width,\n    color=COLOR_UP,\n    edgecolor=COLOR_UP,\n    linewidth=0.4,\n    zorder=4,\n)\n# Bearish: dark edge stroke provides shape-based CVD cue beyond color\nax.bar(\n    date_nums[~bullish_idx],\n    body_heights[~bullish_idx],\n    bottom=body_bottoms[~bullish_idx],\n    width=width,\n    color=COLOR_DOWN,\n    edgecolor=INK,\n    linewidth=0.9,\n    zorder=4,\n)\n\n# Kumo (cloud) — filled between Senkou Span A and B\nspan_a = df_plot[\"senkou_span_a\"]\nspan_b = df_plot[\"senkou_span_b\"]\nvalid_cloud = span_a.notna() & span_b.notna()\n\nif valid_cloud.any():\n    cloud_dates = date_nums[valid_cloud]\n    cloud_a = span_a[valid_cloud].values\n    cloud_b = span_b[valid_cloud].values\n\n    ax.fill_between(\n        cloud_dates, cloud_a, cloud_b, where=cloud_a >= cloud_b, color=COLOR_UP, alpha=0.18, interpolate=True, zorder=1\n    )\n    ax.fill_between(\n        cloud_dates, cloud_a, cloud_b, where=cloud_a < cloud_b, color=COLOR_DOWN, alpha=0.18, interpolate=True, zorder=1\n    )\n    ax.plot(cloud_dates, cloud_a, color=COLOR_UP, linewidth=0.8, alpha=0.5, zorder=2)\n    ax.plot(cloud_dates, cloud_b, color=COLOR_DOWN, linewidth=0.8, alpha=0.5, zorder=2)\n\n# Tenkan-sen\ntenkan_valid = df_plot[\"tenkan_sen\"].notna()\nax.plot(\n    date_nums[tenkan_valid],\n    df_plot[\"tenkan_sen\"][tenkan_valid],\n    color=COLOR_TENKAN,\n    linewidth=1.5,\n    alpha=0.9,\n    zorder=5,\n    label=\"Tenkan-sen (9)\",\n)\n\n# Kijun-sen\nkijun_valid = df_plot[\"kijun_sen\"].notna()\nax.plot(\n    date_nums[kijun_valid],\n    df_plot[\"kijun_sen\"][kijun_valid],\n    color=COLOR_KIJUN,\n    linewidth=1.5,\n    alpha=0.9,\n    zorder=5,\n    label=\"Kijun-sen (26)\",\n)\n\n# Chikou Span (lagging, plotted 26 periods in the past)\nchikou_valid = df_plot[\"chikou_span\"].notna()\nax.plot(\n    date_nums[chikou_valid],\n    df_plot[\"chikou_span\"][chikou_valid],\n    color=COLOR_CHIKOU,\n    linewidth=1.2,\n    alpha=0.65,\n    linestyle=\"--\",\n    zorder=2,\n    label=\"Chikou Span\",\n)\n\n# TK crossover signals\ntenkan_vals = df_plot[\"tenkan_sen\"].values\nkijun_vals = df_plot[\"kijun_sen\"].values\nfor i in range(1, len(df_plot)):\n    if np.isnan(tenkan_vals[i]) or np.isnan(kijun_vals[i]):\n        continue\n    if np.isnan(tenkan_vals[i - 1]) or np.isnan(kijun_vals[i - 1]):\n        continue\n    if tenkan_vals[i - 1] >= kijun_vals[i - 1] and tenkan_vals[i] < kijun_vals[i]:\n        ax.annotate(\n            \"TK↓\",\n            xy=(date_nums[i], kijun_vals[i]),\n            fontsize=6,\n            fontweight=\"bold\",\n            color=COLOR_DOWN,\n            ha=\"center\",\n            va=\"bottom\",\n            xytext=(0, 6),\n            textcoords=\"offset points\",\n            zorder=10,\n        )\n    elif tenkan_vals[i - 1] <= kijun_vals[i - 1] and tenkan_vals[i] > kijun_vals[i]:\n        ax.annotate(\n            \"TK↑\",\n            xy=(date_nums[i], kijun_vals[i]),\n            fontsize=6,\n            fontweight=\"bold\",\n            color=COLOR_UP,\n            ha=\"center\",\n            va=\"top\",\n            xytext=(0, -6),\n            textcoords=\"offset points\",\n            zorder=10,\n        )\n\n# Date axis formatting\nax.xaxis.set_major_locator(mdates.MonthLocator())\nax.xaxis.set_major_formatter(mdates.DateFormatter(\"%b %Y\"))\nax.xaxis.set_minor_locator(mdates.WeekdayLocator(byweekday=mdates.MO))\nax.tick_params(axis=\"x\", rotation=25)\n\n# Chrome — theme-adaptive\ntitle = \"indicator-ichimoku · python · matplotlib · anyplot.ai\"\ntitle_fontsize = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12\nax.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK, pad=8)\nax.set_xlabel(\"Date\", fontsize=10, color=INK)\nax.set_ylabel(\"Price (USD)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)\n\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_linewidth(0.5)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_linewidth(0.5)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nax.yaxis.set_major_formatter(mticker.FormatStrFormatter(\"$%.0f\"))\nax.yaxis.grid(True, alpha=0.15, linewidth=0.6, color=INK)\nax.set_axisbelow(True)\n\n# Y-axis limits with padding\ny_min = df_plot[[\"low\", \"senkou_span_a\", \"senkou_span_b\"]].min().min()\ny_max = df_plot[[\"high\", \"senkou_span_a\", \"senkou_span_b\"]].max().max()\ny_pad = (y_max - y_min) * 0.08\nax.set_ylim(y_min - y_pad, y_max + y_pad)\n\n# Legend\nlegend_handles = [\n    mpatches.Patch(color=COLOR_UP, label=\"Bullish candle / cloud\"),\n    mpatches.Patch(color=COLOR_DOWN, label=\"Bearish candle / cloud\"),\n    plt.Line2D([0], [0], color=COLOR_TENKAN, linewidth=1.5, label=\"Tenkan-sen (9)\"),\n    plt.Line2D([0], [0], color=COLOR_KIJUN, linewidth=1.5, label=\"Kijun-sen (26)\"),\n    plt.Line2D([0], [0], color=COLOR_CHIKOU, linewidth=1.2, linestyle=\"--\", alpha=0.65, label=\"Chikou Span\"),\n]\nleg = ax.legend(\n    handles=legend_handles,\n    fontsize=8,\n    loc=\"upper center\",\n    bbox_to_anchor=(0.5, -0.16),\n    framealpha=0.95,\n    edgecolor=INK_SOFT,\n    facecolor=ELEVATED_BG,\n    ncol=5,\n    columnspacing=1.2,\n    handletextpad=0.4,\n)\nif leg:\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n\nfig.subplots_adjust(left=0.07, right=0.97, top=0.93, bottom=0.18)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}