{"spec_id":"candlestick-volume","library":"makie","language":"julia","code":"# anyplot.ai\n# candlestick-volume: Stock Candlestick Chart with Volume\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 93/100 | Created: 2026-09-02\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Dates\n\nRandom.seed!(42)\n\n# --- Theme tokens -------------------------------------------------------\nTHEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nPAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nINK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nINK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nUP_COLOR   = colorant\"#009E73\"  # Imprint position 1 — bullish/up (finance semantic exception)\nDOWN_COLOR = colorant\"#AE3030\"  # Imprint position 5 — bearish/down (semantic anchor for loss)\n\n# --- Data: simulated daily OHLCV for a technology stock ------------------\nn_days = 60\ntrade_dates = Date(2024, 3, 1) .+ Day.(0:(n_days - 1))\n\nopen_prices  = Vector{Float64}(undef, n_days)\nclose_prices = Vector{Float64}(undef, n_days)\nhigh_prices  = Vector{Float64}(undef, n_days)\nlow_prices   = Vector{Float64}(undef, n_days)\nvolumes      = Vector{Float64}(undef, n_days)\n\nprice = 182.0\nfor i in 1:n_days\n    o = price\n    c = max(o + randn() * 2.4, 5.0)\n    h = max(o, c) + abs(randn()) * 1.6\n    l = min(o, c) - abs(randn()) * 1.6\n    open_prices[i]  = o\n    close_prices[i] = c\n    high_prices[i]  = h\n    low_prices[i]   = l\n    volumes[i] = 2.4e6 + abs(randn()) * 1.1e6 + abs(c - o) * 3.5e5\n    global price = c\nend\n\nis_up = close_prices .>= open_prices\nbar_colors = [up ? UP_COLOR : DOWN_COLOR for up in is_up]\n# Hollow-vs-filled body convention: up candles are hollow (outline only), down\n# candles are solid-filled. This gives CVD readers a shape cue independent of\n# the green/red hue, on top of the semantic finance color convention.\nbody_fill_colors = [up ? PAGE_BG : DOWN_COLOR for up in is_up]\nx = collect(1:n_days)\n\nwick_points = Vector{Point2f}(undef, 2 * n_days)\nfor i in 1:n_days\n    wick_points[2i - 1] = Point2f(x[i], low_prices[i])\n    wick_points[2i]     = Point2f(x[i], high_prices[i])\nend\nwick_colors = repeat(bar_colors, inner = 2)\n\ngrid_rgba = RGBAf(INK.r, INK.g, INK.b, 0.15)\n\n# --- Plot -----------------------------------------------------------------\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax_price = Axis(\n    fig[1, 1];\n    title               = \"candlestick-volume · julia · makie · anyplot.ai\",\n    titlesize           = 20,\n    titlecolor          = INK,\n    ylabel              = \"Price (USD)\",\n    ylabelsize          = 14,\n    ylabelcolor         = INK,\n    yticklabelsize      = 12,\n    yticklabelcolor     = INK_SOFT,\n    ytickcolor          = INK_SOFT,\n    backgroundcolor     = PAGE_BG,\n    topspinevisible     = false,\n    rightspinevisible   = false,\n    bottomspinevisible  = false,\n    leftspinecolor      = INK_SOFT,\n    xticksvisible       = false,\n    xticklabelsvisible  = false,\n    xgridcolor          = grid_rgba,\n    ygridcolor          = grid_rgba,\n    xminorgridvisible   = false,\n    yminorgridvisible   = false,\n)\n\nax_volume = Axis(\n    fig[2, 1];\n    xlabel              = \"Date\",\n    xlabelsize          = 14,\n    xlabelcolor         = INK,\n    ylabel              = \"Volume (millions)\",\n    ylabelsize          = 14,\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    xgridcolor          = grid_rgba,\n    ygridcolor          = grid_rgba,\n    xminorgridvisible   = false,\n    yminorgridvisible   = false,\n    xticks              = (x[1:7:end], Dates.format.(trade_dates[1:7:end], \"u dd\")),\n    xticklabelrotation  = pi / 6,\n    ytickformat         = vals -> [string(round(v / 1e6, digits = 1), \"M\") for v in vals],\n)\n\nlinkxaxes!(ax_price, ax_volume)\nxlims!(ax_price, 0.3, n_days + 0.7)\nxlims!(ax_volume, 0.3, n_days + 0.7)\n\n# Extra headroom above the highest wick keeps the top-left legend clear of\n# the earliest candles regardless of where the price series peaks.\nprice_span = maximum(high_prices) - minimum(low_prices)\nylims!(ax_price, minimum(low_prices) - 0.05 * price_span, maximum(high_prices) + 0.18 * price_span)\n\nlinesegments!(ax_price, wick_points; color = wick_colors, linewidth = 2.2)\nbarplot!(\n    ax_price, x, close_prices;\n    fillto = open_prices, color = body_fill_colors,\n    strokecolor = bar_colors, strokewidth = 2.2, width = 0.6,\n)\nbarplot!(ax_volume, x, volumes; color = bar_colors, width = 0.6)\n\nrowsize!(fig.layout, 1, Relative(0.72))\nrowsize!(fig.layout, 2, Relative(0.28))\nrowgap!(fig.layout, 12)\n\nlegend_elements = [\n    PolyElement(color = PAGE_BG, strokecolor = UP_COLOR, strokewidth = 2.2),\n    PolyElement(color = DOWN_COLOR, strokecolor = DOWN_COLOR, strokewidth = 2.2),\n]\nLegend(\n    fig[1, 1], legend_elements, [\"Up\", \"Down\"];\n    tellwidth = false, tellheight = false,\n    halign = :left, valign = :top,\n    labelcolor = INK_SOFT, labelsize = 12,\n    backgroundcolor = (ELEVATED_BG, 0.85),\n    framevisible = false,\n    padding = (8, 8, 6, 6),\n)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}