{"spec_id":"indicator-ichimoku","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' indicator-ichimoku: Ichimoku Cloud Technical Indicator Chart\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 88/100 | Created: 2026-06-08\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(tidyr)\nlibrary(scales)\nlibrary(ragg)\n\nset.seed(42)\n\n# Theme tokens\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nELEVATED_BG <- if (THEME == \"light\") \"#FFFDF6\" else \"#242420\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nINK_MUTED   <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\n\n# Imprint palette\nIMPRINT_PALETTE <- c(\n    \"#009E73\",  # 1 — brand green\n    \"#C475FD\",  # 2 — lavender\n    \"#4467A3\",  # 3 — blue\n    \"#BD8233\",  # 4 — ochre\n    \"#AE3030\",  # 5 — matte red\n    \"#2ABCCD\",  # 6 — cyan\n    \"#954477\",  # 7 — rose\n    \"#99B314\"   # 8 — lime\n)\n\n# Data — synthetic Nikkei 225 daily OHLC, ~160 trading days\nn_periods <- 200\ndates     <- seq.Date(as.Date(\"2023-01-03\"), by = \"day\", length.out = n_periods)\ndates     <- dates[weekdays(dates) %in% c(\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\")]\ndates     <- dates[seq_len(min(160, length(dates)))]\nn         <- length(dates)\n\nlog_returns  <- cumsum(rnorm(n, mean = 0.0005, sd = 0.012))\nclose_prices <- 150 * exp(log_returns)\ndaily_range  <- abs(rnorm(n, mean = 0.008, sd = 0.004)) * close_prices\nopen_prices  <- close_prices - rnorm(n, 0, 0.003) * close_prices\nhigh_prices  <- pmax(open_prices, close_prices) + daily_range * 0.6\nlow_prices   <- pmin(open_prices, close_prices) - daily_range * 0.4\n\n# Ichimoku helpers (standard 9 / 26 / 52 parameters)\nroll_high <- function(x, k) {\n    sapply(seq_along(x), function(i) if (i < k) NA else max(x[(i - k + 1):i]))\n}\nroll_low <- function(x, k) {\n    sapply(seq_along(x), function(i) if (i < k) NA else min(x[(i - k + 1):i]))\n}\n\ntenkan_sen    <- (roll_high(high_prices, 9)  + roll_low(low_prices, 9))  / 2\nkijun_sen     <- (roll_high(high_prices, 26) + roll_low(low_prices, 26)) / 2\nspan_a_raw    <- (tenkan_sen + kijun_sen) / 2\nspan_b_raw    <- (roll_high(high_prices, 52) + roll_low(low_prices, 52)) / 2\nsenkou_span_a <- c(rep(NA, 26), span_a_raw)[seq_len(n)]\nsenkou_span_b <- c(rep(NA, 26), span_b_raw)[seq_len(n)]\nchikou_span   <- c(close_prices[27:n], rep(NA, 26))\n\ndf <- data.frame(\n    date          = dates,\n    open          = open_prices,\n    high          = high_prices,\n    low           = low_prices,\n    close         = close_prices,\n    tenkan_sen    = tenkan_sen,\n    kijun_sen     = kijun_sen,\n    senkou_span_a = senkou_span_a,\n    senkou_span_b = senkou_span_b,\n    chikou_span   = chikou_span\n)\n\ndf$up     <- df$close >= df$open\ndf$up_chr <- as.character(df$up)  # \"TRUE\"/\"FALSE\" strings for color scale\n\nCANDLE_UP   <- IMPRINT_PALETTE[1]\nCANDLE_DOWN <- IMPRINT_PALETTE[5]\n\n# Cloud data split by bullish/bearish for two-color fill\ndf_cloud <- df |>\n    filter(!is.na(senkou_span_a) & !is.na(senkou_span_b)) |>\n    mutate(\n        upper      = pmax(senkou_span_a, senkou_span_b),\n        lower      = pmin(senkou_span_a, senkou_span_b),\n        cloud_type = if_else(senkou_span_a >= senkou_span_b, \"Bullish Cloud\", \"Bearish Cloud\")\n    )\n\n# Indicator lines in long format — enables merged color+linetype legend\ndf_lines <- df |>\n    pivot_longer(\n        cols      = c(tenkan_sen, kijun_sen, chikou_span, senkou_span_a, senkou_span_b),\n        names_to  = \"indicator\",\n        values_to = \"value\"\n    ) |>\n    mutate(\n        indicator = factor(\n            indicator,\n            levels = c(\"tenkan_sen\", \"kijun_sen\", \"chikou_span\", \"senkou_span_a\", \"senkou_span_b\"),\n            labels = c(\"Tenkan-sen\", \"Kijun-sen\", \"Chikou Span\", \"Senkou Span A\", \"Senkou Span B\")\n        )\n    )\n\n# Scale definitions\nIND_BREAKS <- c(\"Tenkan-sen\", \"Kijun-sen\", \"Chikou Span\", \"Senkou Span A\", \"Senkou Span B\")\n\n# Combined color: candle wicks (\"TRUE\"/\"FALSE\") + indicator lines — wicks hidden via breaks\nALL_COLORS <- c(\n    \"TRUE\"          = CANDLE_UP,\n    \"FALSE\"         = CANDLE_DOWN,\n    \"Tenkan-sen\"    = IMPRINT_PALETTE[1],\n    \"Kijun-sen\"     = IMPRINT_PALETTE[3],\n    \"Chikou Span\"   = IMPRINT_PALETTE[4],\n    \"Senkou Span A\" = IMPRINT_PALETTE[1],\n    \"Senkou Span B\" = IMPRINT_PALETTE[5]\n)\n\n# Combined fill: candle bodies + cloud — bodies hidden via breaks\nALL_FILLS <- c(\n    \"TRUE\"          = CANDLE_UP,\n    \"FALSE\"         = CANDLE_DOWN,\n    \"Bullish Cloud\" = IMPRINT_PALETTE[1],\n    \"Bearish Cloud\" = IMPRINT_PALETTE[5]\n)\n\nLINE_TYPES <- c(\n    \"Tenkan-sen\"    = \"solid\",\n    \"Kijun-sen\"     = \"solid\",\n    \"Chikou Span\"   = \"dotted\",\n    \"Senkou Span A\" = \"dashed\",\n    \"Senkou Span B\" = \"dashed\"\n)\n\n# Find first Kumo twist (cloud color flip) for annotation\ncloud_flips <- df_cloud |>\n    arrange(date) |>\n    mutate(prev_type = lag(cloud_type)) |>\n    filter(!is.na(prev_type), cloud_type != prev_type)\n\n# Title\ntitle_str   <- \"Nikkei 225 · indicator-ichimoku · r · ggplot2 · anyplot.ai\"\nn_title     <- nchar(title_str)\ntitle_fs    <- max(8, round(12 * 67 / n_title))\nprice_range <- range(df$low, df$high, na.rm = TRUE)\n\np <- ggplot(df, aes(x = date)) +\n\n    # Kumo (cloud) — bearish layer first, bullish on top; fill → Cloud legend\n    geom_ribbon(\n        data  = filter(df_cloud, cloud_type == \"Bearish Cloud\"),\n        aes(ymin = lower, ymax = upper, fill = cloud_type),\n        alpha = 0.25\n    ) +\n    geom_ribbon(\n        data  = filter(df_cloud, cloud_type == \"Bullish Cloud\"),\n        aes(ymin = lower, ymax = upper, fill = cloud_type),\n        alpha = 0.25\n    ) +\n\n    # Candlestick wicks — color mapped for correct coloring, excluded from legend\n    geom_segment(\n        aes(xend = date, y = low, yend = high, color = up_chr),\n        linewidth = 0.4\n    ) +\n\n    # Candlestick bodies — fill mapped for correct coloring, excluded from legend\n    geom_rect(\n        aes(\n            xmin = date - 0.3, xmax = date + 0.3,\n            ymin = pmin(open, close), ymax = pmax(open, close),\n            fill = up_chr\n        ),\n        color = NA\n    ) +\n\n    # Tenkan-sen + Kijun-sen (thick solid momentum lines)\n    geom_line(\n        data      = filter(df_lines, indicator %in% c(\"Tenkan-sen\", \"Kijun-sen\")),\n        aes(y = value, color = indicator, linetype = indicator),\n        linewidth = 0.9, na.rm = TRUE\n    ) +\n    # Chikou Span — thicker dotted line for visibility through candlestick forest\n    geom_line(\n        data      = filter(df_lines, indicator == \"Chikou Span\"),\n        aes(y = value, color = indicator, linetype = indicator),\n        linewidth = 1.0, na.rm = TRUE\n    ) +\n    # Senkou Span A + B (thin dashed cloud boundaries)\n    geom_line(\n        data      = filter(df_lines, indicator %in% c(\"Senkou Span A\", \"Senkou Span B\")),\n        aes(y = value, color = indicator, linetype = indicator),\n        linewidth = 0.5, na.rm = TRUE\n    ) +\n\n    # Color scale: only indicator lines appear in \"Lines\" legend\n    scale_color_manual(\n        name   = \"Lines\",\n        values = ALL_COLORS,\n        breaks = IND_BREAKS\n    ) +\n    # Fill scale: only cloud fills appear in \"Cloud\" legend\n    scale_fill_manual(\n        name   = \"Cloud\",\n        values = ALL_FILLS,\n        breaks = c(\"Bullish Cloud\", \"Bearish Cloud\")\n    ) +\n    # Linetype scale merged with color into one \"Lines\" legend box\n    scale_linetype_manual(\n        name   = \"Lines\",\n        values = LINE_TYPES,\n        breaks = IND_BREAKS\n    ) +\n\n    scale_x_date(\n        date_breaks = \"1 month\",\n        date_labels = \"%b %Y\",\n        expand      = expansion(mult = 0.01)\n    ) +\n    scale_y_continuous(\n        labels = label_dollar(),\n        expand = expansion(mult = c(0.03, 0.10))\n    ) +\n\n    # Cloud legend keys show semi-transparent swatches (no border)\n    guides(fill = guide_legend(override.aes = list(alpha = 0.3, color = NA))) +\n\n    labs(\n        title = title_str,\n        x     = NULL,\n        y     = \"Price (USD)\"\n    ) +\n\n    theme_minimal(base_size = 8) +\n    theme(\n        plot.background   = element_rect(fill = PAGE_BG,    color = PAGE_BG),\n        panel.background  = element_rect(fill = PAGE_BG,    color = NA),\n        panel.grid.major  = element_line(color = INK_MUTED, linewidth = 0.2),\n        panel.grid.minor  = element_blank(),\n        panel.border      = element_blank(),\n        axis.title.y      = element_text(color = INK,      size = 10),\n        axis.text         = element_text(color = INK_SOFT, size = 8),\n        axis.text.x       = element_text(angle = 30, hjust = 1),\n        axis.line         = element_line(color = INK_SOFT, linewidth = 0.4),\n        axis.ticks        = element_line(color = INK_SOFT, linewidth = 0.3),\n        plot.title        = element_text(color = INK, size = title_fs, face = \"bold\",\n                                         margin = margin(b = 8)),\n        plot.margin       = margin(12, 16, 10, 12),\n        legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.3),\n        legend.text       = element_text(color = INK_SOFT, size = 8),\n        legend.title      = element_text(color = INK,      size = 9, face = \"bold\"),\n        legend.key.size   = unit(1.0, \"lines\"),\n        legend.position   = \"right\",\n        legend.margin     = margin(4, 6, 4, 6)\n    )\n\n# Annotate first Kumo twist (cloud color flip = trend-change signal)\nif (nrow(cloud_flips) > 0) {\n    flip_date  <- cloud_flips$date[1]\n    flip_upper <- df_cloud |> filter(date == flip_date) |> pull(upper)\n    if (length(flip_upper) == 1 && !is.na(flip_upper)) {\n        rng      <- diff(price_range)\n        annot_y  <- flip_upper + rng * 0.07\n        arrow_y1 <- annot_y   - rng * 0.015\n        arrow_y2 <- flip_upper + rng * 0.015\n        p <- p +\n            annotate(\"text\",\n                     x = flip_date, y = annot_y,\n                     label = \"Kumo twist\", color = INK_SOFT,\n                     size = 2.5, hjust = 0.5) +\n            annotate(\"segment\",\n                     x = flip_date, xend = flip_date,\n                     y = arrow_y1, yend = arrow_y2,\n                     color = INK_SOFT, linewidth = 0.3,\n                     arrow = arrow(length = unit(0.08, \"cm\"), type = \"open\"))\n    }\n}\n\nggsave(\n    filename = sprintf(\"plot-%s.png\", THEME),\n    plot     = p,\n    device   = ragg::agg_png,\n    width    = 8,\n    height   = 4.5,\n    units    = \"in\",\n    dpi      = 400\n)\n"}