{"spec_id":"indicator-ichimoku","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nindicator-ichimoku: Ichimoku Cloud Technical Indicator Chart\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-06-08\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\n\n\nLetsPlot.setup_html()\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\"\n\n# Imprint palette — semantic exception: profit/loss maps to green/red\nBULL_COLOR = \"#009E73\"  # Imprint green — bullish candles\nBEAR_COLOR = \"#AE3030\"  # Imprint matte red — bearish candles\nTENKAN_COLOR = \"#C475FD\"  # Imprint lavender (position 2) — Tenkan-sen\nKIJUN_COLOR = \"#4467A3\"  # Imprint blue (position 3) — Kijun-sen\nCHIKOU_COLOR = \"#BD8233\"  # Imprint ochre (position 4) — Chikou Span\nSIGNAL_COLOR = \"#DDCC77\"  # Imprint amber — TK Crossover signal marker\n\n# Data — 200 trading days of simulated OHLC with Ichimoku components\nnp.random.seed(42)\nn_days = 200\n\ndates = pd.date_range(start=\"2024-01-02\", periods=n_days, freq=\"B\")\n\n# Generate realistic OHLC via random walk\nprice = 150.0\nopens, highs, lows, closes = [], [], [], []\nfor _ in range(n_days):\n    open_price = price\n    change = np.random.randn() * 2.5\n    close_price = open_price + change\n    high_price = max(open_price, close_price) + abs(np.random.randn()) * 1.5\n    low_price = min(open_price, close_price) - abs(np.random.randn()) * 1.5\n    opens.append(open_price)\n    closes.append(close_price)\n    highs.append(high_price)\n    lows.append(low_price)\n    price = close_price\n\ndf = pd.DataFrame({\"date\": dates, \"open\": opens, \"high\": highs, \"low\": lows, \"close\": closes})\n\n# Compute Ichimoku components (standard parameters: 9, 26, 52)\nhigh_s = df[\"high\"]\nlow_s = df[\"low\"]\n\ntenkan_sen = (high_s.rolling(9).max() + low_s.rolling(9).min()) / 2\nkijun_sen = (high_s.rolling(26).max() + low_s.rolling(26).min()) / 2\nsenkou_span_a = ((tenkan_sen + kijun_sen) / 2).shift(26)\nsenkou_span_b = ((high_s.rolling(52).max() + low_s.rolling(52).min()) / 2).shift(26)\nchikou_span = df[\"close\"].shift(-26)\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# Numeric x-axis for precise candlestick positioning\ndf[\"x\"] = range(len(df))\n\n# Candlestick geometry columns\ndf[\"direction\"] = np.where(df[\"close\"] >= df[\"open\"], \"Bullish\", \"Bearish\")\ndf[\"body_low\"] = df[[\"open\", \"close\"]].min(axis=1)\ndf[\"body_high\"] = df[[\"open\", \"close\"]].max(axis=1)\ndf[\"xmin\"] = df[\"x\"] - 0.35\ndf[\"xmax\"] = df[\"x\"] + 0.35\n\n# Visible range (skip first 52 rows for full lookback)\nvisible_start = 52\ndf_visible = df.iloc[visible_start:].copy()\n\n# Cloud data — split by bullish/bearish polarity for color-coded fill\ndf_cloud = df_visible.dropna(subset=[\"senkou_span_a\", \"senkou_span_b\"]).copy()\ndf_cloud_bull = df_cloud[df_cloud[\"senkou_span_a\"] >= df_cloud[\"senkou_span_b\"]].copy()\ndf_cloud_bear = df_cloud[df_cloud[\"senkou_span_a\"] < df_cloud[\"senkou_span_b\"]].copy()\n\n# Indicator line data (dropped NaN for clean line rendering)\ndf_tenkan = df_visible.dropna(subset=[\"tenkan_sen\"]).copy()\ndf_kijun = df_visible.dropna(subset=[\"kijun_sen\"]).copy()\ndf_chikou = df_visible.dropna(subset=[\"chikou_span\"]).copy()\n\n# X-axis ticks (every 20 trading days)\ntick_pos = list(range(visible_start, len(df), 20))\ntick_labels = [dates[i].strftime(\"%b %d\") for i in tick_pos if i < len(dates)]\ntick_pos = tick_pos[: len(tick_labels)]\n\n# Tooltip columns\ndf_visible[\"date_str\"] = df_visible[\"date\"].dt.strftime(\"%b %d, %Y\")\ndf_visible[\"change_pct\"] = ((df_visible[\"close\"] - df_visible[\"open\"]) / df_visible[\"open\"] * 100).round(2)\n\n# Tenkan/Kijun crossover signal points\ndf_visible[\"tk_cross\"] = (df_visible[\"tenkan_sen\"] > df_visible[\"kijun_sen\"]) != (\n    df_visible[\"tenkan_sen\"].shift(1) > df_visible[\"kijun_sen\"].shift(1)\n)\ndf_crossovers = df_visible[df_visible[\"tk_cross\"] & df_visible[\"tenkan_sen\"].notna()].copy()\n\ntip_fmt = (\n    layer_tooltips()\n    .title(\"@date_str\")\n    .line(\"Open|$@{open}\")\n    .line(\"High|$@{high}\")\n    .line(\"Low|$@{low}\")\n    .line(\"Close|$@{close}\")\n    .line(\"Change|@{change_pct}%\")\n    .format(\"open\", \".2f\")\n    .format(\"high\", \".2f\")\n    .format(\"low\", \".2f\")\n    .format(\"close\", \".2f\")\n)\n\ntenkan_tip = (\n    layer_tooltips()\n    .title(\"Tenkan-sen\")\n    .line(\"Value|$@{tenkan_sen}\")\n    .format(\"tenkan_sen\", \".2f\")\n)\nkijun_tip = (\n    layer_tooltips()\n    .title(\"Kijun-sen\")\n    .line(\"Value|$@{kijun_sen}\")\n    .format(\"kijun_sen\", \".2f\")\n)\n\n# Title (51 chars — under 67 baseline, default size=16)\ntitle = \"indicator-ichimoku · python · letsplot · anyplot.ai\"\nsubtitle = \"Ichimoku Kinko Hyo — Kumo shifts green→red as trend reverses mid-year; amber diamonds mark TK crossovers\"\n\n# Plot\nplot = (\n    ggplot()\n    # Kumo cloud — bullish segment (green)\n    + geom_ribbon(\n        aes(x=\"x\", ymin=\"senkou_span_b\", ymax=\"senkou_span_a\"),\n        data=df_cloud_bull,\n        fill=BULL_COLOR,\n        alpha=0.15,\n        tooltips=\"none\",\n    )\n    # Kumo cloud — bearish segment (red)\n    + geom_ribbon(\n        aes(x=\"x\", ymin=\"senkou_span_a\", ymax=\"senkou_span_b\"),\n        data=df_cloud_bear,\n        fill=BEAR_COLOR,\n        alpha=0.15,\n        tooltips=\"none\",\n    )\n    # Senkou Span A boundary line\n    + geom_line(\n        aes(x=\"x\", y=\"senkou_span_a\"),\n        data=df_cloud,\n        color=BULL_COLOR,\n        size=0.6,\n        alpha=0.5,\n        tooltips=\"none\",\n    )\n    # Senkou Span B boundary line\n    + geom_line(\n        aes(x=\"x\", y=\"senkou_span_b\"),\n        data=df_cloud,\n        color=BEAR_COLOR,\n        size=0.6,\n        alpha=0.5,\n        tooltips=\"none\",\n    )\n    # Candlestick wicks\n    + geom_segment(\n        aes(x=\"x\", xend=\"x\", y=\"low\", yend=\"high\", color=\"direction\"),\n        data=df_visible,\n        size=0.7,\n        tooltips=tip_fmt,\n    )\n    # Candlestick bodies\n    + geom_rect(\n        aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"body_low\", ymax=\"body_high\", fill=\"direction\", color=\"direction\"),\n        data=df_visible,\n        size=0.4,\n        tooltips=tip_fmt,\n    )\n    # Tenkan-sen (conversion line)\n    + geom_line(\n        aes(x=\"x\", y=\"tenkan_sen\"),\n        data=df_tenkan,\n        color=TENKAN_COLOR,\n        size=1.2,\n        tooltips=tenkan_tip,\n        manual_key=layer_key(\"Tenkan-sen\"),\n    )\n    # Kijun-sen (base line)\n    + geom_line(\n        aes(x=\"x\", y=\"kijun_sen\"),\n        data=df_kijun,\n        color=KIJUN_COLOR,\n        size=1.2,\n        tooltips=kijun_tip,\n        manual_key=layer_key(\"Kijun-sen\"),\n    )\n    # Chikou Span (lagging line — shifted 26 bars back)\n    + geom_line(\n        aes(x=\"x\", y=\"chikou_span\"),\n        data=df_chikou,\n        color=CHIKOU_COLOR,\n        size=1.0,\n        alpha=0.85,\n        linetype=\"dashed\",\n        tooltips=\"none\",\n        manual_key=layer_key(\"Chikou Span\"),\n    )\n    # TK Crossover signal markers\n    + geom_point(\n        aes(x=\"x\", y=\"tenkan_sen\"),\n        data=df_crossovers,\n        color=SIGNAL_COLOR,\n        fill=SIGNAL_COLOR,\n        size=5,\n        shape=23,\n        stroke=1.5,\n        tooltips=layer_tooltips().title(\"TK Crossover\").line(\"@date_str\"),\n        manual_key=layer_key(\"TK Crossover\"),\n    )\n    # Scales\n    + scale_fill_manual(values={\"Bullish\": BULL_COLOR, \"Bearish\": BEAR_COLOR})\n    + scale_color_manual(values={\"Bullish\": BULL_COLOR, \"Bearish\": BEAR_COLOR})\n    + scale_x_continuous(breaks=tick_pos, labels=tick_labels, expand=[0.02, 0])\n    + labs(x=\"Trading Day (2024)\", y=\"Price ($)\", title=title, subtitle=subtitle)\n    + guides(fill=\"none\", color=\"none\")\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        plot_title=element_text(size=16, color=INK, face=\"bold\"),\n        plot_subtitle=element_text(size=11, color=INK_SOFT, face=\"italic\"),\n        panel_grid_major_x=element_blank(),\n        panel_grid_major_y=element_line(color=INK, size=0.3),\n        panel_grid_minor=element_blank(),\n        axis_ticks=element_blank(),\n        legend_position=[0.05, 0.95],\n        legend_justification=[0.0, 1.0],\n        legend_title=element_text(size=10, face=\"bold\", color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.5),\n    )\n    + ggsize(800, 450)\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}