{"spec_id":"drawdown-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' drawdown-basic: Drawdown Chart\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 91/100 | Created: 2026-05-23\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\"\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\"\nDD_COLOR    <- \"#AE3030\"  # red — semantic: loss / drawdown\nDD_ALPHA    <- if (THEME == \"light\") 0.25 else 0.45  # more visible in dark mode\n\n# Data: synthetic portfolio NAV, Jan 2021 – Dec 2023\n# bull → correction → full recovery (new highs) → deeper bear → partial recovery\nn_days  <- 756\ndates   <- seq(as.Date(\"2021-01-04\"), by = \"day\", length.out = n_days)\nreturns <- numeric(n_days)\nreturns[1:180]   <- rnorm(180, mean =  0.0010, sd = 0.011)  # initial bull run\nreturns[181:240] <- rnorm(60,  mean = -0.0030, sd = 0.014)  # first correction ~-18%\nreturns[241:340] <- rnorm(100, mean =  0.0020, sd = 0.010)  # strong recovery → new highs\nreturns[341:400] <- rnorm(60,  mean =  0.0010, sd = 0.010)  # brief continued bull\nreturns[401:500] <- rnorm(100, mean = -0.0030, sd = 0.014)  # rate-hike bear ~-26%\nreturns[501:756] <- rnorm(256, mean =  0.0007, sd = 0.011)  # gradual partial recovery\n\nprice        <- 100 * cumprod(c(1, 1 + returns))[seq_len(n_days)]\nrunning_max  <- cummax(price)\ndrawdown_pct <- (price - running_max) / running_max * 100\n\n# Key statistics\nmax_dd_idx  <- which.min(drawdown_pct)\nmax_dd_pct  <- drawdown_pct[max_dd_idx]\nmax_dd_date <- dates[max_dd_idx]\n\nat_peak      <- drawdown_pct >= -0.001\npre_peaks    <- which(at_peak & seq_len(n_days) < max_dd_idx)\npre_peak_idx <- if (length(pre_peaks) > 0) tail(pre_peaks, 1) else 1L\ndd_duration  <- max_dd_idx - pre_peak_idx\n\npost_peaks     <- which(at_peak & seq_len(n_days) > max_dd_idx)\nrecovery_days  <- if (length(post_peaks) > 0) post_peaks[1] - max_dd_idx else NA_integer_\nrecovery_label <- if (!is.na(recovery_days)) sprintf(\"%d days\", recovery_days) else \"Not recovered\"\n\n# Recovery points: transitions from drawdown back to new high (zero-cross)\nprev_not_peak <- c(FALSE, !at_peak[-n_days])\nrecovery_idx  <- which(at_peak & prev_not_peak)\nrecovery_df   <- data.frame(\n  date     = dates[recovery_idx],\n  drawdown = drawdown_pct[recovery_idx]\n)\n\ndf        <- data.frame(date = dates, drawdown = drawdown_pct)\nmax_dd_df <- df[max_dd_idx, ]\n\nsubtitle_text <- sprintf(\n  \"Maximum drawdown: %.1f%%    |    Duration to trough: %d days    |    Recovery: %s\",\n  max_dd_pct, dd_duration, recovery_label\n)\n\n# Plot\np <- ggplot(df, aes(x = date, y = drawdown)) +\n  # Highlight max drawdown period: last peak before trough → trough date\n  annotate(\n    \"rect\",\n    xmin  = dates[pre_peak_idx],\n    xmax  = max_dd_date,\n    ymin  = -Inf,\n    ymax  = Inf,\n    fill  = DD_COLOR,\n    alpha = 0.07\n  ) +\n  geom_area(fill = DD_COLOR, alpha = DD_ALPHA) +\n  geom_line(color = DD_COLOR, linewidth = 0.7) +\n  geom_hline(yintercept = 0, color = INK_SOFT, linewidth = 0.5) +\n  # Recovery point markers: where drawdown returns to zero (new highs)\n  geom_point(\n    data   = recovery_df,\n    aes(x = date, y = drawdown),\n    shape  = 21,\n    size   = 2.5,\n    color  = DD_COLOR,\n    fill   = ELEVATED_BG,\n    stroke = 1.2\n  ) +\n  geom_point(\n    data   = max_dd_df,\n    aes(x = date, y = drawdown),\n    shape  = 21,\n    size   = 3.5,\n    color  = DD_COLOR,\n    fill   = ELEVATED_BG,\n    stroke = 1.5\n  ) +\n  scale_x_date(\n    date_breaks = \"6 months\",\n    date_labels = \"%b '%y\",\n    expand      = c(0.01, 0)\n  ) +\n  scale_y_continuous(\n    labels = function(x) paste0(x, \"%\"),\n    expand = c(0.03, 0)\n  ) +\n  labs(\n    title    = \"drawdown-basic · r · ggplot2 · anyplot.ai\",\n    subtitle = subtitle_text,\n    x        = NULL,\n    y        = \"Drawdown from Peak (%)\"\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.y = element_line(color = INK, linewidth = 0.15),\n    panel.grid.major.x = element_blank(),\n    panel.grid.minor   = element_blank(),\n    panel.border       = element_blank(),\n    axis.title.y       = element_text(color = INK, size = 10),\n    axis.text.x        = element_text(color = INK_SOFT, size = 8),\n    axis.text.y        = element_text(color = INK_SOFT, size = 8),\n    axis.line.x        = element_line(color = INK_SOFT, linewidth = 0.4),\n    plot.title         = element_text(color = INK, size = 12, face = \"bold\"),\n    plot.subtitle      = element_text(color = INK_MUTED, size = 9),\n    plot.margin        = margin(20, 25, 15, 15)\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"}