{"spec_id":"candlestick-volume","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' candlestick-volume: Stock Candlestick Chart with Volume\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 91/100 | Created: 2026-09-02\n\nlibrary(ggplot2)\nlibrary(scales)\nlibrary(ragg)\nlibrary(gridExtra)\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\"\n\nUP_COLOR   <- \"#009E73\"  # Imprint position 1 — bullish (semantic: profit/up -> green)\nDOWN_COLOR <- \"#AE3030\"  # Imprint position 5 — bearish (semantic: loss/down -> red)\n\n# --- Data: 60 trading days of a synthetic tech stock --------------------\nn_days <- 60\ncalendar_days <- seq(as.Date(\"2024-02-05\"), by = \"day\", length.out = 90)\ntrading_days <- calendar_days[!format(calendar_days, \"%u\") %in% c(\"6\", \"7\")][1:n_days]\n\ndaily_returns <- rnorm(n_days, mean = 0.0006, sd = 0.017)\nclose <- 150 * cumprod(1 + daily_returns)\nopen <- c(\n  close[1] * (1 + rnorm(1, 0, 0.004)),\n  close[-n_days] * (1 + rnorm(n_days - 1, 0, 0.005))\n)\nbody_hi <- pmax(open, close)\nbody_lo <- pmin(open, close)\nhigh <- body_hi + runif(n_days, 0.1, 1.4) * (close * 0.01)\nlow <- body_lo - runif(n_days, 0.1, 1.4) * (close * 0.01)\nvolume <- pmax(5e5, round(2.4e6 + abs(daily_returns) * 5.5e7 + rnorm(n_days, 0, 2.5e5)))\ndirection <- factor(ifelse(close >= open, \"Up\", \"Down\"), levels = c(\"Up\", \"Down\"))\n\nstock <- tibble::tibble(\n  date = trading_days, open, high, low, close, volume, direction, body_lo, body_hi\n)\n\ncandle_width <- 0.32\nx_breaks <- stock$date[seq(1, n_days, by = 7)]\nx_limits <- range(stock$date) + c(-1, 1)\ntitle_text <- \"NovaTech Inc. · candlestick-volume · r · ggplot2 · anyplot.ai\"\n\n# --- Shared chrome -------------------------------------------------------\nanyplot_theme <- theme_minimal(base_size = 7) +\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.minor    = element_blank(),\n    panel.grid.major.x  = element_blank(),\n    panel.grid.major.y  = element_line(color = INK, linewidth = 0.18),\n    axis.line           = element_line(color = INK_SOFT, linewidth = 0.4),\n    axis.ticks          = element_blank(),\n    axis.title          = element_text(color = INK, size = 10),\n    axis.text           = element_text(color = INK_SOFT, size = 8),\n    legend.position     = \"none\"\n  )\n\n# --- Price pane (top, ~71% of vertical space) -----------------------------\np_price <- ggplot(stock, aes(x = date)) +\n  geom_segment(aes(xend = date, y = low, yend = high, color = direction), linewidth = 0.6) +\n  geom_rect(\n    aes(xmin = date - candle_width, xmax = date + candle_width, ymin = body_lo, ymax = body_hi, fill = direction),\n    color = NA\n  ) +\n  scale_color_manual(values = c(Up = UP_COLOR, Down = DOWN_COLOR)) +\n  scale_fill_manual(values = c(Up = UP_COLOR, Down = DOWN_COLOR)) +\n  scale_x_date(breaks = x_breaks, limits = x_limits, expand = expansion(mult = 0.015)) +\n  scale_y_continuous(labels = label_dollar()) +\n  labs(\n    title = title_text,\n    subtitle = \"Green candle = price up (close ≥ open)  ·  Red candle = price down (close < open)\",\n    y = \"Price (USD)\", x = NULL\n  ) +\n  anyplot_theme +\n  theme(\n    axis.text.x   = element_blank(),\n    axis.line.x   = element_blank(),\n    plot.title    = element_text(color = INK, size = 12, face = \"plain\"),\n    plot.subtitle = element_text(color = INK_SOFT, size = 8),\n    plot.margin   = margin(t = 14, r = 16, b = 4, l = 12)\n  )\n\n# --- Volume pane (bottom, ~29% of vertical space) --------------------------\np_volume <- ggplot(stock, aes(x = date)) +\n  geom_col(aes(y = volume, fill = direction), width = candle_width * 2) +\n  scale_fill_manual(values = c(Up = UP_COLOR, Down = DOWN_COLOR)) +\n  scale_x_date(\n    breaks = x_breaks, limits = x_limits,\n    labels = label_date(\"%b %d\"), expand = expansion(mult = 0.015)\n  ) +\n  scale_y_continuous(labels = label_number(scale = 1e-6, suffix = \"M\")) +\n  labs(y = \"Volume\", x = \"Trading Date\") +\n  anyplot_theme +\n  theme(plot.margin = margin(t = 4, r = 16, b = 12, l = 12))\n\n# --- Combine panes with a shared, width-aligned x-axis ----------------------\ng_price <- ggplotGrob(p_price)\ng_volume <- ggplotGrob(p_volume)\naligned_widths <- grid::unit.pmax(g_price$widths, g_volume$widths)\ng_price$widths <- aligned_widths\ng_volume$widths <- aligned_widths\ncombined <- arrangeGrob(g_price, g_volume, ncol = 1, heights = c(2.5, 1))\n\n# --- Save --------------------------------------------------------------\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = combined,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400,\n  bg       = PAGE_BG\n)\n"}