{"spec_id":"indicator-ichimoku","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nindicator-ichimoku: Ichimoku Cloud Technical Indicator Chart\nLibrary: plotly 6.8.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-08\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nimport plotly.graph_objects as go\n\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette — semantic bull/bear anchor, Imprint positions 2–4 for indicator lines\nBULL_COLOR = \"#009E73\"  # Imprint brand green — bullish/gain (semantic exception)\nBEAR_COLOR = \"#AE3030\"  # Imprint matte red  — bearish/loss (semantic exception)\nTENKAN_COLOR = \"#C475FD\"  # Imprint lavender (pos 2)\nKIJUN_COLOR = \"#4467A3\"  # Imprint blue     (pos 3)\nCHIKOU_COLOR = \"#BD8233\"  # Imprint ochre    (pos 4)\nCLOUD_BULL = \"rgba(0,158,115,0.20)\"  # BULL at 20% opacity\nCLOUD_BEAR = \"rgba(174,48,48,0.25)\"  # BEAR at 25% opacity — improved dark-theme visibility\n\n# Data — 200 trading days of simulated stock prices\nnp.random.seed(42)\nn_days = 200\ndates = pd.date_range(start=\"2023-06-01\", periods=n_days, freq=\"B\")\n\nprice = 150.0\ndrift = np.concatenate(\n    [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)]\n)\nopens, highs, lows, closes = [], [], [], []\n\nfor i in range(n_days):\n    open_price = price\n    change = np.random.randn() * 1.8 + drift[i]\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    opens.append(open_price)\n    highs.append(high_price)\n    lows.append(low_price)\n    closes.append(close_price)\n    price = close_price\n\ndf = pd.DataFrame({\"date\": dates, \"open\": opens, \"high\": highs, \"low\": lows, \"close\": closes})\n\n# Compute Ichimoku components (9, 26, 52 periods)\nperiod_9_high = df[\"high\"].rolling(window=9).max()\nperiod_9_low = df[\"low\"].rolling(window=9).min()\nperiod_26_high = df[\"high\"].rolling(window=26).max()\nperiod_26_low = df[\"low\"].rolling(window=26).min()\nperiod_52_high = df[\"high\"].rolling(window=52).max()\nperiod_52_low = df[\"low\"].rolling(window=52).min()\n\ntenkan_sen = (period_9_high + period_9_low) / 2\nkijun_sen = (period_26_high + period_26_low) / 2\nsenkou_span_a = ((tenkan_sen + kijun_sen) / 2).shift(26)\nsenkou_span_b = ((period_52_high + period_52_low) / 2).shift(26)\nchikou_span = df[\"close\"].shift(-26)\n\n# Trim to valid data range (after 52-period lookback + 26-period shift); extra row removes isolated start dot\nstart_idx = 79\ndf = df.iloc[start_idx:].reset_index(drop=True)\ntenkan_sen = tenkan_sen.iloc[start_idx:].reset_index(drop=True)\nkijun_sen = kijun_sen.iloc[start_idx:].reset_index(drop=True)\nsenkou_span_a = senkou_span_a.iloc[start_idx:].reset_index(drop=True)\nsenkou_span_b = senkou_span_b.iloc[start_idx:].reset_index(drop=True)\nchikou_span = chikou_span.iloc[start_idx:].reset_index(drop=True)\n\n# Convert dates to strings — kaleido cannot JSON-serialize pandas Timestamps\ndf[\"date\"] = df[\"date\"].dt.strftime(\"%Y-%m-%d\")\n\n# Plot\nfig = go.Figure()\n\n# Kumo (cloud) — fill between Senkou Span A and B, colored by trend direction\nspan_a_vals = senkou_span_a.values\nspan_b_vals = senkou_span_b.values\ndate_vals = df[\"date\"].values\n\nvalid_mask = ~(np.isnan(span_a_vals) | np.isnan(span_b_vals))\nvalid_dates = date_vals[valid_mask]\nvalid_a = span_a_vals[valid_mask]\nvalid_b = span_b_vals[valid_mask]\n\ni = 0\nwhile i < len(valid_dates):\n    bullish = valid_a[i] >= valid_b[i]\n    j = i + 1\n    while j < len(valid_dates) and (valid_a[j] >= valid_b[j]) == bullish:\n        j += 1\n    if j < len(valid_dates):\n        j += 1\n    seg_dates = valid_dates[i:j]\n    seg_a = valid_a[i:j]\n    seg_b = valid_b[i:j]\n    fig.add_trace(\n        go.Scatter(\n            x=np.concatenate([seg_dates, seg_dates[::-1]]),\n            y=np.concatenate([seg_a, seg_b[::-1]]),\n            fill=\"toself\",\n            fillcolor=CLOUD_BULL if bullish else CLOUD_BEAR,\n            line={\"width\": 0},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        )\n    )\n    i = j - 1 if j < len(valid_dates) else j\n\n# Senkou Span A line\nfig.add_trace(\n    go.Scatter(\n        x=df[\"date\"],\n        y=senkou_span_a,\n        mode=\"lines\",\n        line={\"color\": BULL_COLOR, \"width\": 1.5, \"dash\": \"dot\"},\n        name=\"Senkou Span A\",\n        hovertemplate=\"Span A: $%{y:.2f}<extra></extra>\",\n    )\n)\n\n# Senkou Span B line\nfig.add_trace(\n    go.Scatter(\n        x=df[\"date\"],\n        y=senkou_span_b,\n        mode=\"lines\",\n        line={\"color\": BEAR_COLOR, \"width\": 1.5, \"dash\": \"dot\"},\n        name=\"Senkou Span B\",\n        hovertemplate=\"Span B: $%{y:.2f}<extra></extra>\",\n    )\n)\n\n# Candlestick chart\nfig.add_trace(\n    go.Candlestick(\n        x=df[\"date\"],\n        open=df[\"open\"],\n        high=df[\"high\"],\n        low=df[\"low\"],\n        close=df[\"close\"],\n        increasing={\"line\": {\"color\": BULL_COLOR, \"width\": 1.5}, \"fillcolor\": BULL_COLOR},\n        decreasing={\"line\": {\"color\": BEAR_COLOR, \"width\": 1.5}, \"fillcolor\": BEAR_COLOR},\n        name=\"OHLC\",\n        hovertemplate=(\n            \"<b>%{x|%b %d, %Y}</b><br>O: $%{open:.2f} H: $%{high:.2f}<br>L: $%{low:.2f} C: $%{close:.2f}<extra></extra>\"\n        ),\n    )\n)\n\n# Tenkan-sen (conversion line)\nfig.add_trace(\n    go.Scatter(\n        x=df[\"date\"],\n        y=tenkan_sen,\n        mode=\"lines\",\n        line={\"color\": TENKAN_COLOR, \"width\": 2},\n        name=\"Tenkan-sen (9)\",\n        hovertemplate=\"Tenkan: $%{y:.2f}<extra></extra>\",\n    )\n)\n\n# Kijun-sen (base line)\nfig.add_trace(\n    go.Scatter(\n        x=df[\"date\"],\n        y=kijun_sen,\n        mode=\"lines\",\n        line={\"color\": KIJUN_COLOR, \"width\": 2},\n        name=\"Kijun-sen (26)\",\n        hovertemplate=\"Kijun: $%{y:.2f}<extra></extra>\",\n    )\n)\n\n# Chikou Span (lagging line, shifted 26 periods into the past)\nfig.add_trace(\n    go.Scatter(\n        x=df[\"date\"],\n        y=chikou_span,\n        mode=\"lines\",\n        line={\"color\": CHIKOU_COLOR, \"width\": 1.5, \"dash\": \"dash\"},\n        name=\"Chikou Span\",\n        hovertemplate=\"Chikou: $%{y:.2f}<extra></extra>\",\n    )\n)\n\n# Title — scale font size for total length\ntitle_text = \"Ichimoku Cloud Overlay · indicator-ichimoku · python · plotly · anyplot.ai\"\ntitle_fontsize = max(11, round(16 * 67 / len(title_text)))\n\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    title={\"text\": title_text, \"font\": {\"size\": title_fontsize, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"},\n    xaxis={\n        \"title\": {\"text\": \"Date\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"tickformat\": \"%b %Y\",\n        \"rangeslider\": {\"visible\": False},\n        \"rangebreaks\": [{\"bounds\": [\"sat\", \"mon\"]}],\n        \"showgrid\": False,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"zeroline\": False,\n        \"mirror\": False,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Price (USD)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"tickprefix\": \"$\",\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"mirror\": False,\n    },\n    legend={\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n        \"x\": 0.01,\n        \"y\": 0.99,\n        \"xanchor\": \"left\",\n        \"yanchor\": \"top\",\n        \"orientation\": \"h\",\n    },\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n    hoverlabel={\"bgcolor\": ELEVATED_BG, \"font_size\": 10, \"bordercolor\": INK_SOFT},\n)\n\n# Annotate the first bullish TK Cross (Tenkan crosses above Kijun)\ntk_diff = tenkan_sen - kijun_sen\nfor idx in range(1, len(tk_diff)):\n    if pd.notna(tk_diff.iloc[idx]) and pd.notna(tk_diff.iloc[idx - 1]):\n        if tk_diff.iloc[idx - 1] < 0 and tk_diff.iloc[idx] >= 0:\n            fig.add_annotation(\n                x=df[\"date\"].iloc[idx],\n                y=tenkan_sen.iloc[idx],\n                text=\"<b>Bullish TK Cross</b>\",\n                showarrow=True,\n                arrowhead=2,\n                arrowsize=1.2,\n                arrowcolor=BULL_COLOR,\n                ax=0,\n                ay=-50,\n                font={\"size\": 10, \"color\": BULL_COLOR},\n                bgcolor=ELEVATED_BG,\n                bordercolor=BULL_COLOR,\n                borderwidth=1.5,\n                borderpad=4,\n            )\n            break\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}