{"spec_id":"indicator-rsi","library":"makie","language":"julia","code":"# anyplot.ai\n# indicator-rsi: RSI Technical Indicator Chart\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 93/100 | Created: 2026-09-05\n\nusing CairoMakie\nusing Colors\nusing Dates\nusing Random\nusing Statistics\n\nRandom.seed!(42)\n\n# Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\nconst THEME     = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG   = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst INK       = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT  = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst INK_MUTED = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\n\n# Imprint palette — first series always brand green\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\nconst RSI_COLOR        = IMPRINT_PALETTE[1]  # brand green\nconst OVERBOUGHT_COLOR = IMPRINT_PALETTE[5]  # matte red — semantic: reversal risk\nconst OVERSOLD_COLOR   = IMPRINT_PALETTE[3]  # blue — semantic: cool / opportunity\n\n# Data — simulated daily closing prices over trading days (weekends skipped)\nn_days = 141\nstart_date = Date(2024, 3, 1)\nall_days = collect(start_date:Day(1):(start_date + Day(260)))\ntrading_days = filter(d -> dayofweek(d) <= 5, all_days)[1:n_days]\n\n# Three drift regimes (rally, selloff, choppy recovery) so RSI visibly\n# crosses both the overbought and oversold thresholds, not just the middle band.\nprices = Vector{Float64}(undef, n_days)\nprices[1] = 148.0\nfor i in 2:n_days\n    drift = i <= 40 ? 0.55 : (i <= 75 ? -0.60 : 0.05)\n    sigma = i <= 40 ? 0.9 : (i <= 75 ? 1.0 : 1.4)\n    prices[i] = prices[i - 1] + randn() * sigma + drift\nend\n\nchanges = diff(prices)\nperiod = 14\ngains = max.(changes, 0.0)\nlosses = max.(-changes, 0.0)\n\n# Wilder's smoothing for the 14-period RSI\navg_gain = zeros(length(gains))\navg_loss = zeros(length(gains))\navg_gain[period] = mean(gains[1:period])\navg_loss[period] = mean(losses[1:period])\nfor i in (period + 1):length(gains)\n    avg_gain[i] = (avg_gain[i - 1] * (period - 1) + gains[i]) / period\n    avg_loss[i] = (avg_loss[i - 1] * (period - 1) + losses[i]) / period\nend\nrs = avg_gain ./ avg_loss\nrsi_full = 100.0 .- 100.0 ./ (1.0 .+ rs)\nrsi_values = rsi_full[(period + 1):end]\nrsi_dates = trading_days[(period + 2):end]\nx = 1:length(rsi_values)\n\n# Plot\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title             = \"indicator-rsi · julia · makie · anyplot.ai\",\n    titlesize         = 20,\n    titlecolor        = INK,\n    xlabel            = \"Trading Date\",\n    ylabel            = \"RSI (14-period)\",\n    xlabelsize        = 14,\n    ylabelsize        = 14,\n    xlabelcolor       = INK,\n    ylabelcolor       = INK,\n    xticklabelsize    = 12,\n    yticklabelsize    = 12,\n    xticklabelcolor   = INK_SOFT,\n    yticklabelcolor   = INK_SOFT,\n    xtickcolor        = INK_SOFT,\n    ytickcolor        = INK_SOFT,\n    backgroundcolor   = PAGE_BG,\n    topspinevisible   = false,\n    rightspinevisible = false,\n    leftspinecolor    = INK_SOFT,\n    bottomspinecolor  = INK_SOFT,\n    ygridcolor        = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    xgridvisible      = false,\n    xticks            = (x[1:20:end], Dates.format.(rsi_dates[1:20:end], \"u d\")),\n    xticklabelrotation = pi / 6,\n    yticks            = ([0, 30, 50, 70, 100], [\"0\", \"30\", \"50\", \"70\", \"100\"]),\n)\n\nylims!(ax, 0, 100)\n\n# Overbought / oversold reference zones\nhspan!(ax, 70, 100; color = RGBAf(OVERBOUGHT_COLOR.r, OVERBOUGHT_COLOR.g, OVERBOUGHT_COLOR.b, 0.12))\nhspan!(ax, 0, 30; color = RGBAf(OVERSOLD_COLOR.r, OVERSOLD_COLOR.g, OVERSOLD_COLOR.b, 0.12))\n\n# Threshold and centerline references\nhlines!(ax, [70, 30]; color = INK_SOFT, linestyle = :dash, linewidth = 1.5)\nhlines!(ax, [50]; color = INK_MUTED, linestyle = :dot, linewidth = 1.2)\n\n# Alpha-blended fill from the centerline to the RSI line, reinforcing\n# distance-from-neutral at a glance (band! fills between two curves).\nband!(\n    ax, x, min.(rsi_values, 50.0), max.(rsi_values, 50.0);\n    color = RGBAf(RSI_COLOR.r, RSI_COLOR.g, RSI_COLOR.b, 0.15),\n)\n\nlines!(ax, x, rsi_values; color = RSI_COLOR, linewidth = 3)\n\n# Data-aware label placement: anchor each zone label at the point of\n# greatest vertical clearance from the RSI line within the opening window,\n# rather than a fixed x[1] that could collide with the line for other seeds.\nearly_window = 1:max(1, round(Int, length(x) * 0.2))\nob_idx = early_window[argmin(rsi_values[early_window])]\nos_idx = early_window[argmax(rsi_values[early_window])]\n\ntext!(ax, x[ob_idx], 96; text = \"Overbought\", color = INK_SOFT, fontsize = 12, align = (:left, :top))\ntext!(ax, x[os_idx], 4; text = \"Oversold\", color = INK_SOFT, fontsize = 12, align = (:left, :bottom))\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}