Rolling Windows — Moving Averages, Bollinger Bands & Momentum

Rolling Windows — Moving Averages, Bollinger Bands & Momentum

Moving averages smooth noise to reveal trends. Bollinger Bands add standard deviation envelopes to detect when a metric is behaving abnormally. Momentum measures rate of change. All of these are pure SQL window functions.

-- 20-day moving average, Bollinger Bands, and momentum for AAPL
WITH daily AS (
    SELECT
        ticker,
        price_date,
        close_cents,
        -- Simple Moving Averages
        AVG(close_cents) OVER (
            PARTITION BY ticker
            ORDER BY price_date
            ROWS BETWEEN 19 PRECEDING AND CURRENT ROW
        )                                               AS sma_20,
        AVG(close_cents) OVER (
            PARTITION BY ticker
            ORDER BY price_date
            ROWS BETWEEN 49 PRECEDING AND CURRENT ROW
        )                                               AS sma_50,
        -- Standard deviation for Bollinger Bands
        STDDEV_SAMP(close_cents) OVER (
            PARTITION BY ticker
            ORDER BY price_date
            ROWS BETWEEN 19 PRECEDING AND CURRENT ROW
        )                                               AS std_20,
        -- Row count to exclude partial windows
        COUNT(*) OVER (
            PARTITION BY ticker
            ORDER BY price_date
            ROWS BETWEEN 19 PRECEDING AND CURRENT ROW
        )                                               AS window_rows
    FROM ts_stock_prices
    WHERE ticker = 'AAPL'
)
SELECT
    price_date,
    ROUND(close_cents / 100.0, 2)                       AS close_usd,
    ROUND(sma_20 / 100.0, 2)                            AS sma_20_usd,
    ROUND(sma_50 / 100.0, 2)                            AS sma_50_usd,
    -- Bollinger Bands: mean ± 2 standard deviations
    ROUND((sma_20 + 2 * std_20) / 100.0, 2)            AS upper_band,
    ROUND((sma_20 - 2 * std_20) / 100.0, 2)            AS lower_band,
    -- Price relative to bands (0 = lower, 1 = upper)
    ROUND((close_cents - (sma_20 - 2 * std_20))
          / NULLIF(4 * std_20, 0), 3)                   AS band_position,
    CASE
        WHEN close_cents > sma_20 + 2 * std_20 THEN 'Overbought'
        WHEN close_cents < sma_20 - 2 * std_20 THEN 'Oversold'
        WHEN close_cents > sma_20               THEN 'Above SMA'
        ELSE                                         'Below SMA'
    END                                                 AS signal
FROM daily
WHERE window_rows = 20
ORDER BY price_date DESC
LIMIT 30;

Purchase this course to unlock the full lesson.

Sign up