{"spec_id":"indicator-macd","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' indicator-macd: MACD Technical Indicator Chart\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 89/100 | Created: 2026-09-05\n\nlibrary(ggplot2)\nlibrary(ragg)\n\nset.seed(42)\n\n# --- Theme tokens -------------------------------------------------------------\nTHEME    <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG  <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nINK      <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nGRID_FAINT <- grDevices::adjustcolor(INK, alpha.f = 0.18)  # ggplot2 has no grid alpha; bake it into the color\n\nGAIN <- \"#009E73\"  # Imprint brand green — semantic: histogram above zero\nLOSS <- \"#AE3030\"  # Imprint matte red   — semantic: histogram below zero\nMACD_COLOR   <- \"#4467A3\"  # Imprint blue\nSIGNAL_COLOR <- \"#BD8233\"  # Imprint ochre\n\n# --- Data ----------------------------------------------------------------------\nn_days <- 120\ndates <- seq(as.Date(\"2024-03-01\"), by = \"day\", length.out = n_days)\n\ndaily_return <- rnorm(n_days, mean = 0.0006, sd = 0.016)\nclose_price <- 100 * cumprod(1 + daily_return)\n\nema <- function(x, span) {\n  alpha <- 2 / (span + 1)\n  out <- numeric(length(x))\n  out[1] <- x[1]\n  for (i in 2:length(x)) {\n    out[i] <- alpha * x[i] + (1 - alpha) * out[i - 1]\n  }\n  out\n}\n\nema_fast <- ema(close_price, 12)\nema_slow <- ema(close_price, 26)\nmacd_line <- ema_fast - ema_slow\nsignal_line <- ema(macd_line, 9)\nhistogram <- macd_line - signal_line\n\nmacd_df <- tibble::tibble(\n  date      = dates,\n  macd      = macd_line,\n  signal    = signal_line,\n  histogram = histogram,\n  hist_sign = factor(\n    ifelse(histogram >= 0, \"Positive\", \"Negative\"),\n    levels = c(\"Positive\", \"Negative\")\n  )\n)\n\nlines_df <- tibble::tibble(\n  date  = rep(dates, 2),\n  value = c(macd_line, signal_line),\n  line  = rep(c(\"MACD (12, 26)\", \"Signal (9)\"), each = n_days)\n)\n\n# Highlight the first clear MACD/signal crossover after the EMA warm-up\n# window so the chart's core \"signal moment\" is called out explicitly.\ncross_idx <- which(diff(sign(histogram)) != 0) + 1\ncross_idx <- cross_idx[cross_idx > 30]\nhighlight_idx <- cross_idx[1]\nis_bullish <- histogram[highlight_idx] > 0\ncross_label <- if (is_bullish) \"Bullish crossover\" else \"Bearish crossover\"\ncross_date  <- dates[highlight_idx]\ncross_value <- macd_line[highlight_idx]\nlabel_offset <- diff(range(c(macd_line, signal_line))) * 0.18\nlabel_y <- if (is_bullish) cross_value + label_offset else cross_value - label_offset\nlabel_vjust <- if (is_bullish) 0 else 1\n\n# --- Plot ------------------------------------------------------------------\ntitle_text <- \"indicator-macd · r · ggplot2 · anyplot.ai\"\n\np <- ggplot() +\n  geom_col(\n    data = macd_df,\n    aes(x = date, y = histogram, fill = hist_sign),\n    width = 0.8, alpha = 0.75\n  ) +\n  geom_hline(yintercept = 0, color = INK, linewidth = 0.6, linetype = \"dashed\") +\n  geom_line(\n    data = lines_df,\n    aes(x = date, y = value, color = line),\n    linewidth = 1.0\n  ) +\n  annotate(\n    \"point\", x = cross_date, y = cross_value,\n    shape = 21, size = 3.2, fill = PAGE_BG, color = INK, stroke = 1\n  ) +\n  annotate(\n    \"text\", x = cross_date, y = label_y, label = cross_label,\n    color = INK, size = 2.8, vjust = label_vjust, fontface = \"bold\"\n  ) +\n  scale_fill_manual(values = c(\"Positive\" = GAIN, \"Negative\" = LOSS), name = NULL) +\n  scale_color_manual(values = c(\"MACD (12, 26)\" = MACD_COLOR, \"Signal (9)\" = SIGNAL_COLOR), name = NULL) +\n  scale_x_date(date_labels = \"%b %Y\") +\n  labs(\n    title = title_text,\n    x = \"Date\",\n    y = \"MACD Value\"\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.x = element_blank(),\n    panel.grid.minor.x = element_blank(),\n    panel.grid.minor.y = element_blank(),\n    panel.grid.major.y = element_line(color = GRID_FAINT, linewidth = 0.2),\n    axis.title        = element_text(color = INK,      size = 10),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    axis.line         = element_line(color = INK_SOFT),\n    plot.title        = element_text(color = INK, size = 12),\n    legend.position   = \"top\",\n    legend.text       = element_text(color = INK_SOFT, size = 8),\n    legend.key        = element_rect(fill = PAGE_BG, color = NA)\n  )\n\n# --- Save --------------------------------------------------------------------\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"}