{"spec_id":"indicator-rsi","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' indicator-rsi: RSI Technical Indicator Chart\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 94/100 | Created: 2026-09-05\n\nlibrary(ggplot2)\nlibrary(dplyr)\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\"\nINK_MUTED   <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\nANYPLOT_MUTED <- INK_MUTED\n\nIMPRINT_PALETTE <- c(\n  \"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n  \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"\n)\nBRAND         <- IMPRINT_PALETTE[1] # RSI line — always the brand green\nANYPLOT_AMBER <- \"#DDCC77\"          # warning anchor — overbought zone\n\n# --- Data: simulate a daily close price series and derive its 14-period RSI -\nrsi_period <- 14\ncalendar_days <- seq(as.Date(\"2024-01-02\"), by = \"day\", length.out = 210)\ntrade_dates   <- calendar_days[!weekdays(calendar_days) %in% c(\"Saturday\", \"Sunday\")]\n\ndaily_change <- rnorm(length(trade_dates), mean = 0.15, sd = 2.2)\nclose_price  <- 150 + cumsum(daily_change)\n\nprice_delta <- diff(close_price)\ngains  <- pmax(price_delta, 0)\nlosses <- pmax(-price_delta, 0)\n\navg_gain <- numeric(length(gains))\navg_loss <- numeric(length(losses))\navg_gain[rsi_period] <- mean(gains[1:rsi_period])\navg_loss[rsi_period] <- mean(losses[1:rsi_period])\nfor (i in (rsi_period + 1):length(gains)) {\n  avg_gain[i] <- (avg_gain[i - 1] * (rsi_period - 1) + gains[i]) / rsi_period\n  avg_loss[i] <- (avg_loss[i - 1] * (rsi_period - 1) + losses[i]) / rsi_period\n}\n\nrelative_strength <- avg_gain / avg_loss\nrsi_values <- 100 - (100 / (1 + relative_strength))\n\ndf <- tibble::tibble(\n  date = trade_dates[(rsi_period + 1):length(trade_dates)],\n  rsi  = rsi_values[rsi_period:length(rsi_values)]\n) %>%\n  filter(!is.na(rsi)) %>%\n  slice_tail(n = 120)\n\n# --- Plot ---------------------------------------------------------------\nchart_start <- min(df$date)\nchart_end   <- max(df$date)\n\npeak_pt   <- df[which.max(df$rsi), ]\ntrough_pt <- df[which.min(df$rsi), ]\n\npeak_hjust <- ifelse(as.numeric(peak_pt$date - chart_start) < 15, 0,\n  ifelse(as.numeric(chart_end - peak_pt$date) < 15, 1, 0.5)\n)\ntrough_hjust <- ifelse(as.numeric(trough_pt$date - chart_start) < 15, 0,\n  ifelse(as.numeric(chart_end - trough_pt$date) < 15, 1, 0.5)\n)\n\np <- ggplot(df, aes(date, rsi)) +\n  geom_ribbon(aes(ymin = 70, ymax = 100), fill = ANYPLOT_AMBER, alpha = 0.15) +\n  geom_ribbon(aes(ymin = 0, ymax = 30), fill = ANYPLOT_MUTED, alpha = 0.15) +\n  geom_hline(yintercept = 50, linetype = \"dotted\", linewidth = 0.4, color = INK_SOFT, alpha = 0.6) +\n  geom_hline(yintercept = c(30, 70), linetype = \"dashed\", linewidth = 0.5, color = INK_SOFT) +\n  geom_line(color = BRAND, linewidth = 1.1) +\n  annotate(\"text\",\n    x = min(df$date), y = 92, label = \"Overbought\", hjust = 0,\n    size = 3.2, color = INK_MUTED\n  ) +\n  annotate(\"text\",\n    x = min(df$date), y = 8, label = \"Oversold\", hjust = 0,\n    size = 3.2, color = INK_MUTED\n  ) +\n  annotate(\"point\", x = peak_pt$date, y = peak_pt$rsi, color = BRAND, size = 2.2) +\n  annotate(\"text\",\n    x = peak_pt$date, y = peak_pt$rsi + 5, label = sprintf(\"Peak %.0f\", peak_pt$rsi),\n    hjust = peak_hjust, size = 3, color = INK, fontface = \"bold\"\n  ) +\n  annotate(\"point\", x = trough_pt$date, y = trough_pt$rsi, color = BRAND, size = 2.2) +\n  annotate(\"text\",\n    x = trough_pt$date, y = trough_pt$rsi - 5, label = sprintf(\"Trough %.0f\", trough_pt$rsi),\n    hjust = trough_hjust, size = 3, color = INK, fontface = \"bold\"\n  ) +\n  scale_y_continuous(limits = c(0, 100), breaks = c(0, 30, 50, 70, 100), expand = c(0, 0)) +\n  scale_x_date(date_labels = \"%b %Y\", date_breaks = \"1 month\", expand = c(0, 0)) +\n  labs(\n    title = \"indicator-rsi · r · ggplot2 · anyplot.ai\",\n    x = \"Date\",\n    y = \"RSI (14-period)\"\n  ) +\n  theme_minimal(base_size = 8) +\n  theme(\n    text              = element_text(size = 7, color = INK),\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   = element_blank(),\n    panel.grid.major.y = element_blank(),\n    axis.title        = element_text(color = INK, size = 10),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    axis.ticks        = element_blank(),\n    plot.title        = element_text(color = INK, size = 12),\n    legend.position   = \"none\"\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"}