Window Functions & Time-Series Operations

Powerful window functions for time-series analysis, lag/lead operations, and rolling calculations.

Example Dataset

Using prices table: TICKER, DATE, PRICE

LAG and LEAD Functions

Access previous (LAG) or next (LEAD) row values within a group.

LAG - Previous Row Value

-- Get previous day's price
UPDATE prev_price: LAG(PRICE, 1)
FROM prices
GROUP BY TICKER;

-- Calculate daily return
UPDATE
    prev_price: LAG(PRICE, 1),
    daily_return: PRICE / LAG(PRICE, 1) - 1
FROM prices
GROUP BY TICKER;

-- Get price from 5 days ago
UPDATE price_5d_ago: LAG(PRICE, 5)
FROM prices
GROUP BY TICKER;

LEAD - Next Row Value

-- Get next day's price
UPDATE next_price: LEAD(PRICE, 1)
FROM prices
GROUP BY TICKER;

-- Forward return calculation
UPDATE forward_return: LEAD(PRICE, 1) / PRICE - 1
FROM prices
GROUP BY TICKER;

Cumulative Functions

CUMSUM - Cumulative Sum

-- Running total of prices
UPDATE cumulative_sum: CUMSUM(PRICE)
FROM prices
GROUP BY TICKER;

-- Cumulative return
UPDATE cumulative_return: CUMSUM(daily_return)
FROM prices
GROUP BY TICKER;

CUMMAX - Cumulative Maximum

-- Running maximum of prices
UPDATE cumulative_max: CUMMAX(PRICE)
FROM prices
GROUP BY TICKER;

-- Track high water mark
UPDATE highest_volume: CUMMAX(VOLUME)
FROM prices
GROUP BY TICKER;

CUMPROD - Cumulative Product

-- Calculate split adjustment factor
UPDATE split_adj: CUMPROD(split_ratio)
FROM splits
GROUP BY TICKER;

-- Calculate dividend adjustment
UPDATE dvd_adj: CUMPROD(dvd_adj)
FROM dividends
GROUP BY TICKER;

Moving Average Functions

EMA - Exponential Moving Average

-- 20-day exponential moving average
-- alpha = 2/(N+1) = 2/21 = 0.095
UPDATE price_ema20: EMA(PRICE, 0.095)
FROM prices
GROUP BY TICKER;

-- 50-day EMA (alpha = 2/51 = 0.039)
UPDATE price_ema50: EMA(PRICE, 0.039)
FROM prices
GROUP BY TICKER;

-- Volume EMA with custom alpha
UPDATE volume_ema: EMA(VOLUME, 0.333)
FROM prices
GROUP BY TICKER;

FILL - Forward Fill Missing Values

-- Fill missing prices with last known value
UPDATE PRICE: FILL(PRICE)
FROM prices
GROUP BY TICKER;

-- Fill multiple columns
UPDATE
    PRICE: FILL(PRICE),
    VOLUME: FILL(VOLUME)
FROM prices
GROUP BY TICKER, DATE;

Moving Statistical Functions

MCORR - Moving Correlation

-- Calculate 20-day rolling correlation between two stocks
CREATE TABLE stock_pair AS (
    SELECT
        a.DATE,
        a.PRICE as price_a,
        b.PRICE as price_b
    FROM (SELECT * FROM prices WHERE TICKER = 'AAPL') a
    INNER JOIN (SELECT * FROM prices WHERE TICKER = 'MSFT') b
        ON a.DATE = b.DATE
);

-- Calculate returns first
UPDATE
    return_a: price_a / LAG(price_a, 1) - 1,
    return_b: price_b / LAG(price_b, 1) - 1
FROM stock_pair;

-- Calculate 20-day rolling correlation
UPDATE rolling_corr_20: MCORR(return_a, return_b, 20)
FROM stock_pair;

-- Calculate multiple window sizes
UPDATE
    rolling_corr_5: MCORR(return_a, return_b, 5),
    rolling_corr_20: MCORR(return_a, return_b, 20),
    rolling_corr_60: MCORR(return_a, return_b, 60)
FROM stock_pair;

MCOVAR - Moving Covariance

-- Calculate 20-day rolling covariance
UPDATE rolling_covar_20: MCOVAR(return_a, return_b, 20)
FROM stock_pair;

-- Calculate multiple window sizes for covariance
UPDATE
    rolling_covar_5: MCOVAR(return_a, return_b, 5),
    rolling_covar_20: MCOVAR(return_a, return_b, 20),
    rolling_covar_60: MCOVAR(return_a, return_b, 60)
FROM stock_pair;

-- Relationship between moving correlation and covariance
-- MCOVAR(X,Y,N) = MCORR(X,Y,N) * MSTD(X,N) * MSTD(Y,N)
UPDATE
    rolling_covar: MCOVAR(return_a, return_b, 20),
    rolling_corr: MCORR(return_a, return_b, 20),
    rolling_std_a: MSTD(return_a, 20),
    rolling_std_b: MSTD(return_b, 20)
FROM stock_pair;

Ranking Functions

WRANK - Weighted Rank

-- Rank into 10 quantiles (deciles)
UPDATE decile: TO_INTEGER(WRANK(PRICE, 10))
FROM prices;

-- Rank into 5 quintiles
UPDATE quintile: TO_INTEGER(WRANK(PRICE, 5))
FROM prices;

-- Create cross-sectional ranks by date
UPDATE price_rank: WRANK(PRICE, 100)
FROM prices
GROUP BY DATE;

FIRST and LAST

-- Get first value in group
SELECT
    TICKER,
    FIRST(PRICE) as first_price,
    FIRST(DATE) as first_date
FROM prices
GROUP BY TICKER;

-- Get last value in group
SELECT
    TICKER,
    LAST(PRICE) as last_price,
    LAST(DATE) as last_date
FROM prices
GROUP BY TICKER;

-- Both first and last
UPDATE
    start_price: FIRST(PRICE),
    end_price: LAST(PRICE)
FROM prices
GROUP BY TICKER;

Advanced Patterns

Multiple Window Functions

-- Complex time-series features
UPDATE
    prev_price: LAG(PRICE, 1),
    prev_return: LAG(PRICE, 1) / LAG(PRICE, 2) - 1,
    price_ema: EMA(PRICE, 0.1),
    cum_return: CUMPROD(1 + daily_return) - 1,
    rank_today: WRANK(PRICE, 10)
FROM prices
GROUP BY TICKER;

Nested Window Operations

-- Calculate momentum indicators
UPDATE momentum: PRICE / LAG(PRICE, 20) - 1
FROM prices
GROUP BY TICKER;

-- Then rank the momentum
UPDATE momentum_rank: WRANK(momentum, 10)
FROM prices
GROUP BY DATE;