Ichimoku Cloud Technical Indicator Chart — Plotly

Displays the Ichimoku Kinko Hyo ("one glance equilibrium chart") overlay on a candlestick price chart. The indicator plots five lines — Tenkan-sen (conversion), Kijun-sen (base), Senkou Span A, Senkou Span B, and Chikou Span (lagging) — with the area between Senkou Span A and B filled as the "Kumo" (cloud). The cloud color changes depending on which span is on top, providing an at-a-glance view of trend direction, momentum, and support/resistance zones.

Ichimoku Cloud Technical Indicator Chart rendered with Plotly

Python source (Plotly)

""" anyplot.ai
indicator-ichimoku: Ichimoku Cloud Technical Indicator Chart
Library: plotly 6.8.0 | Python 3.13.13
Quality: 91/100 | Updated: 2026-06-08
"""

import os

import numpy as np
import pandas as pd
import plotly.graph_objects as go


# Theme tokens — Imprint palette, theme-adaptive chrome
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
GRID = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)"

# Imprint palette — semantic bull/bear anchor, Imprint positions 2–4 for indicator lines
BULL_COLOR = "#009E73"  # Imprint brand green — bullish/gain (semantic exception)
BEAR_COLOR = "#AE3030"  # Imprint matte red  — bearish/loss (semantic exception)
TENKAN_COLOR = "#C475FD"  # Imprint lavender (pos 2)
KIJUN_COLOR = "#4467A3"  # Imprint blue     (pos 3)
CHIKOU_COLOR = "#BD8233"  # Imprint ochre    (pos 4)
CLOUD_BULL = "rgba(0,158,115,0.20)"  # BULL at 20% opacity
CLOUD_BEAR = "rgba(174,48,48,0.25)"  # BEAR at 25% opacity — improved dark-theme visibility

# Data — 200 trading days of simulated stock prices
np.random.seed(42)
n_days = 200
dates = pd.date_range(start="2023-06-01", periods=n_days, freq="B")

price = 150.0
drift = np.concatenate(
    [np.full(50, 0.15), np.full(40, -0.10), np.full(30, 0.25), np.full(40, -0.05), np.full(40, 0.20)]
)
opens, highs, lows, closes = [], [], [], []

for i in range(n_days):
    open_price = price
    change = np.random.randn() * 1.8 + drift[i]
    close_price = open_price + change
    high_price = max(open_price, close_price) + abs(np.random.randn()) * 1.2
    low_price = min(open_price, close_price) - abs(np.random.randn()) * 1.2
    opens.append(open_price)
    highs.append(high_price)
    lows.append(low_price)
    closes.append(close_price)
    price = close_price

df = pd.DataFrame({"date": dates, "open": opens, "high": highs, "low": lows, "close": closes})

# Compute Ichimoku components (9, 26, 52 periods)
period_9_high = df["high"].rolling(window=9).max()
period_9_low = df["low"].rolling(window=9).min()
period_26_high = df["high"].rolling(window=26).max()
period_26_low = df["low"].rolling(window=26).min()
period_52_high = df["high"].rolling(window=52).max()
period_52_low = df["low"].rolling(window=52).min()

tenkan_sen = (period_9_high + period_9_low) / 2
kijun_sen = (period_26_high + period_26_low) / 2
senkou_span_a = ((tenkan_sen + kijun_sen) / 2).shift(26)
senkou_span_b = ((period_52_high + period_52_low) / 2).shift(26)
chikou_span = df["close"].shift(-26)

# Trim to valid data range (after 52-period lookback + 26-period shift); extra row removes isolated start dot
start_idx = 79
df = df.iloc[start_idx:].reset_index(drop=True)
tenkan_sen = tenkan_sen.iloc[start_idx:].reset_index(drop=True)
kijun_sen = kijun_sen.iloc[start_idx:].reset_index(drop=True)
senkou_span_a = senkou_span_a.iloc[start_idx:].reset_index(drop=True)
senkou_span_b = senkou_span_b.iloc[start_idx:].reset_index(drop=True)
chikou_span = chikou_span.iloc[start_idx:].reset_index(drop=True)

# Convert dates to strings — kaleido cannot JSON-serialize pandas Timestamps
df["date"] = df["date"].dt.strftime("%Y-%m-%d")

# Plot
fig = go.Figure()

# Kumo (cloud) — fill between Senkou Span A and B, colored by trend direction
span_a_vals = senkou_span_a.values
span_b_vals = senkou_span_b.values
date_vals = df["date"].values

valid_mask = ~(np.isnan(span_a_vals) | np.isnan(span_b_vals))
valid_dates = date_vals[valid_mask]
valid_a = span_a_vals[valid_mask]
valid_b = span_b_vals[valid_mask]

i = 0
while i < len(valid_dates):
    bullish = valid_a[i] >= valid_b[i]
    j = i + 1
    while j < len(valid_dates) and (valid_a[j] >= valid_b[j]) == bullish:
        j += 1
    if j < len(valid_dates):
        j += 1
    seg_dates = valid_dates[i:j]
    seg_a = valid_a[i:j]
    seg_b = valid_b[i:j]
    fig.add_trace(
        go.Scatter(
            x=np.concatenate([seg_dates, seg_dates[::-1]]),
            y=np.concatenate([seg_a, seg_b[::-1]]),
            fill="toself",
            fillcolor=CLOUD_BULL if bullish else CLOUD_BEAR,
            line={"width": 0},
            showlegend=False,
            hoverinfo="skip",
        )
    )
    i = j - 1 if j < len(valid_dates) else j

# Senkou Span A line
fig.add_trace(
    go.Scatter(
        x=df["date"],
        y=senkou_span_a,
        mode="lines",
        line={"color": BULL_COLOR, "width": 1.5, "dash": "dot"},
        name="Senkou Span A",
        hovertemplate="Span A: $%{y:.2f}<extra></extra>",
    )
)

# Senkou Span B line
fig.add_trace(
    go.Scatter(
        x=df["date"],
        y=senkou_span_b,
        mode="lines",
        line={"color": BEAR_COLOR, "width": 1.5, "dash": "dot"},
        name="Senkou Span B",
        hovertemplate="Span B: $%{y:.2f}<extra></extra>",
    )
)

# Candlestick chart
fig.add_trace(
    go.Candlestick(
        x=df["date"],
        open=df["open"],
        high=df["high"],
        low=df["low"],
        close=df["close"],
        increasing={"line": {"color": BULL_COLOR, "width": 1.5}, "fillcolor": BULL_COLOR},
        decreasing={"line": {"color": BEAR_COLOR, "width": 1.5}, "fillcolor": BEAR_COLOR},
        name="OHLC",
        hovertemplate=(
            "<b>%{x|%b %d, %Y}</b><br>O: $%{open:.2f} H: $%{high:.2f}<br>L: $%{low:.2f} C: $%{close:.2f}<extra></extra>"
        ),
    )
)

# Tenkan-sen (conversion line)
fig.add_trace(
    go.Scatter(
        x=df["date"],
        y=tenkan_sen,
        mode="lines",
        line={"color": TENKAN_COLOR, "width": 2},
        name="Tenkan-sen (9)",
        hovertemplate="Tenkan: $%{y:.2f}<extra></extra>",
    )
)

# Kijun-sen (base line)
fig.add_trace(
    go.Scatter(
        x=df["date"],
        y=kijun_sen,
        mode="lines",
        line={"color": KIJUN_COLOR, "width": 2},
        name="Kijun-sen (26)",
        hovertemplate="Kijun: $%{y:.2f}<extra></extra>",
    )
)

# Chikou Span (lagging line, shifted 26 periods into the past)
fig.add_trace(
    go.Scatter(
        x=df["date"],
        y=chikou_span,
        mode="lines",
        line={"color": CHIKOU_COLOR, "width": 1.5, "dash": "dash"},
        name="Chikou Span",
        hovertemplate="Chikou: $%{y:.2f}<extra></extra>",
    )
)

# Title — scale font size for total length
title_text = "Ichimoku Cloud Overlay · indicator-ichimoku · python · plotly · anyplot.ai"
title_fontsize = max(11, round(16 * 67 / len(title_text)))

fig.update_layout(
    autosize=False,
    paper_bgcolor=PAGE_BG,
    plot_bgcolor=PAGE_BG,
    font={"color": INK},
    title={"text": title_text, "font": {"size": title_fontsize, "color": INK}, "x": 0.5, "xanchor": "center"},
    xaxis={
        "title": {"text": "Date", "font": {"size": 12, "color": INK}},
        "tickfont": {"size": 10, "color": INK_SOFT},
        "tickformat": "%b %Y",
        "rangeslider": {"visible": False},
        "rangebreaks": [{"bounds": ["sat", "mon"]}],
        "showgrid": False,
        "linecolor": INK_SOFT,
        "linewidth": 1,
        "zeroline": False,
        "mirror": False,
    },
    yaxis={
        "title": {"text": "Price (USD)", "font": {"size": 12, "color": INK}},
        "tickfont": {"size": 10, "color": INK_SOFT},
        "tickprefix": "$",
        "gridcolor": GRID,
        "gridwidth": 1,
        "zeroline": False,
        "linecolor": INK_SOFT,
        "linewidth": 1,
        "mirror": False,
    },
    legend={
        "font": {"size": 10, "color": INK_SOFT},
        "bgcolor": ELEVATED_BG,
        "bordercolor": INK_SOFT,
        "borderwidth": 1,
        "x": 0.01,
        "y": 0.99,
        "xanchor": "left",
        "yanchor": "top",
        "orientation": "h",
    },
    margin={"l": 80, "r": 40, "t": 80, "b": 60},
    hoverlabel={"bgcolor": ELEVATED_BG, "font_size": 10, "bordercolor": INK_SOFT},
)

# Annotate the first bullish TK Cross (Tenkan crosses above Kijun)
tk_diff = tenkan_sen - kijun_sen
for idx in range(1, len(tk_diff)):
    if pd.notna(tk_diff.iloc[idx]) and pd.notna(tk_diff.iloc[idx - 1]):
        if tk_diff.iloc[idx - 1] < 0 and tk_diff.iloc[idx] >= 0:
            fig.add_annotation(
                x=df["date"].iloc[idx],
                y=tenkan_sen.iloc[idx],
                text="<b>Bullish TK Cross</b>",
                showarrow=True,
                arrowhead=2,
                arrowsize=1.2,
                arrowcolor=BULL_COLOR,
                ax=0,
                ay=-50,
                font={"size": 10, "color": BULL_COLOR},
                bgcolor=ELEVATED_BG,
                bordercolor=BULL_COLOR,
                borderwidth=1.5,
                borderpad=4,
            )
            break

# Save
fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4)
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")

Part of Ichimoku Cloud Technical Indicator Chart on anyplot.ai.

Other implementations