{"spec_id":"indicator-sma","library":"makie","language":"julia","code":"# anyplot.ai\n# indicator-sma: Simple Moving Average (SMA) Indicator Chart\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 92/100 | Created: 2026-09-02\n\nusing CairoMakie\nusing Colors\nusing Dates\nusing Random\nusing Statistics\n\nRandom.seed!(42)\n\n# --- Theme tokens -----------------------------------------------------------\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 IMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\n\n# --- Data ---------------------------------------------------------------\nn = 300\ndaily_returns = 0.0004 .+ 0.013 .* randn(n)\nclose = 180.0 .* cumprod(1.0 .+ daily_returns)\ntrading_day = 1:n\nstart_date = Date(2024, 1, 2)\n\nwindow_short  = 20\nwindow_medium = 50\nwindow_long   = 200\n\nsma_short  = [i < window_short ? NaN : mean(close[(i - window_short + 1):i]) for i in 1:n]\nsma_medium = [i < window_medium ? NaN : mean(close[(i - window_medium + 1):i]) for i in 1:n]\nsma_long   = [i < window_long ? NaN : mean(close[(i - window_long + 1):i]) for i in 1:n]\n\n# Golden-cross / death-cross detection (SMA 20 vs. SMA 50) — the crossover\n# signal that is the entire analytical point of a multi-period SMA overlay.\ndiff_ma = sma_short .- sma_medium\ncrossovers = Tuple{Int,Symbol}[]\nfor i in (window_medium + 1):n\n    if sign(diff_ma[i]) != sign(diff_ma[i - 1])\n        push!(crossovers, (i, diff_ma[i] > 0 ? :golden : :death))\n    end\nend\n\n# Annotate only well-separated crossovers so the callouts stay a focal point\n# rather than clutter.\nfeatured_crossovers = Tuple{Int,Symbol}[]\nlast_i = -Inf\nfor (i, kind) in crossovers\n    if i - last_i >= 40\n        push!(featured_crossovers, (i, kind))\n        global last_i = i\n    end\nend\nfeatured_crossovers = first(featured_crossovers, min(2, length(featured_crossovers)))\n\n# --- Plot -----------------------------------------------------------------\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = \"indicator-sma · julia · makie · anyplot.ai\",\n    titlesize          = 23,\n    titlecolor         = INK,\n    xlabel             = \"Date\",\n    ylabel             = \"Closing Price (\\$)\",\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    xtickformat        = xs -> [Dates.format(start_date + Day(round(Int, x) - 1), \"u yyyy\") for x in xs],\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)\n\nlines!(ax, trading_day, close; color = IMPRINT_PALETTE[1], linewidth = 2.0, label = \"Close\")\nlines!(ax, trading_day, sma_short; color = IMPRINT_PALETTE[2], linewidth = 2.0, label = \"SMA 20\")\nlines!(ax, trading_day, sma_medium; color = IMPRINT_PALETTE[3], linewidth = 2.0, label = \"SMA 50\")\nlines!(ax, trading_day, sma_long; color = IMPRINT_PALETTE[4], linewidth = 2.5, label = \"SMA 200\")\n\n# Golden-cross / death-cross callouts — a Makie-distinctive `scatter!` +\n# `text!` + dotted leader-line annotation combo that gives the overlay its\n# analytical focal point. The label is placed clear of every series' local\n# extremum (not just the marker itself) so it never sits on top of a line.\nmargin = 0.05 * (maximum(close) - minimum(close))\nfor (i, kind) in featured_crossovers\n    is_golden    = kind == :golden\n    marker_color = is_golden ? IMPRINT_PALETTE[1] : IMPRINT_PALETTE[5]\n    marker_glyph = is_golden ? :utriangle : :dtriangle\n    label_text   = is_golden ? \"Golden Cross\" : \"Death Cross\"\n\n    window = max(1, i - 15):min(n, i + 15)\n    local_values = vcat(close[window], filter(!isnan, sma_short[window]),\n                         filter(!isnan, sma_medium[window]), filter(!isnan, sma_long[window]))\n    label_y = is_golden ? maximum(local_values) + margin : minimum(local_values) - margin\n\n    scatter!(\n        ax, [trading_day[i]], [sma_short[i]];\n        marker      = marker_glyph,\n        markersize  = 22,\n        color       = marker_color,\n        strokecolor = INK,\n        strokewidth = 1.5,\n    )\n    lines!(\n        ax, [trading_day[i], trading_day[i]], [sma_short[i], label_y];\n        color     = (marker_color, 0.6),\n        linewidth = 1.2,\n        linestyle = :dot,\n    )\n    text!(\n        ax, trading_day[i], label_y;\n        text     = label_text,\n        align    = (:center, is_golden ? :bottom : :top),\n        color    = marker_color,\n        fontsize = 13,\n        font     = :bold,\n    )\nend\n\naxislegend(ax; position = :lt, labelcolor = INK, framevisible = false, labelsize = 13)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}